lex.c revision 1.45 1 1.45 mrg /* $NetBSD: lex.c,v 1.45 2018/02/04 09:01:12 mrg Exp $ */
2 1.7 christos
3 1.1 cgd /*
4 1.4 deraadt * Copyright (c) 1980, 1993
5 1.4 deraadt * The Regents of the University of California. All rights reserved.
6 1.1 cgd *
7 1.1 cgd * Redistribution and use in source and binary forms, with or without
8 1.1 cgd * modification, are permitted provided that the following conditions
9 1.1 cgd * are met:
10 1.1 cgd * 1. Redistributions of source code must retain the above copyright
11 1.1 cgd * notice, this list of conditions and the following disclaimer.
12 1.1 cgd * 2. Redistributions in binary form must reproduce the above copyright
13 1.1 cgd * notice, this list of conditions and the following disclaimer in the
14 1.1 cgd * documentation and/or other materials provided with the distribution.
15 1.23 agc * 3. Neither the name of the University nor the names of its contributors
16 1.1 cgd * may be used to endorse or promote products derived from this software
17 1.1 cgd * without specific prior written permission.
18 1.1 cgd *
19 1.1 cgd * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 1.1 cgd * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 1.1 cgd * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 1.1 cgd * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 1.1 cgd * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 1.1 cgd * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 1.1 cgd * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 1.1 cgd * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 1.1 cgd * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 1.1 cgd * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 1.1 cgd * SUCH DAMAGE.
30 1.1 cgd */
31 1.1 cgd
32 1.11 lukem #include <sys/cdefs.h>
33 1.1 cgd #ifndef lint
34 1.7 christos #if 0
35 1.8 tls static char sccsid[] = "@(#)lex.c 8.2 (Berkeley) 4/20/95";
36 1.7 christos #else
37 1.45 mrg __RCSID("$NetBSD: lex.c,v 1.45 2018/02/04 09:01:12 mrg Exp $");
38 1.7 christos #endif
39 1.1 cgd #endif /* not lint */
40 1.1 cgd
41 1.30 christos #include <assert.h>
42 1.37 christos #include <util.h>
43 1.30 christos
44 1.1 cgd #include "rcv.h"
45 1.4 deraadt #include "extern.h"
46 1.28 christos #ifdef USE_EDITLINE
47 1.26 christos #include "complete.h"
48 1.26 christos #endif
49 1.30 christos #include "format.h"
50 1.37 christos #include "sig.h"
51 1.30 christos #include "thread.h"
52 1.26 christos
53 1.1 cgd /*
54 1.1 cgd * Mail -- a mail program
55 1.1 cgd *
56 1.1 cgd * Lexical processing of commands.
57 1.1 cgd */
58 1.1 cgd
59 1.30 christos static const char *prompt = DEFAULT_PROMPT;
60 1.30 christos static int *msgvec;
61 1.37 christos static int inithdr; /* Am printing startup headers. */
62 1.37 christos static jmp_buf jmpbuf; /* The reset jmpbuf */
63 1.37 christos static int reset_on_stop; /* To do job control longjmp. */
64 1.37 christos
65 1.37 christos #ifdef DEBUG_FILE_LEAK
66 1.37 christos struct glue {
67 1.37 christos struct glue *next;
68 1.37 christos int niobs;
69 1.37 christos FILE *iobs;
70 1.37 christos };
71 1.37 christos extern struct glue __sglue;
72 1.37 christos
73 1.37 christos static int open_fd_cnt;
74 1.37 christos static int open_fp_cnt;
75 1.37 christos
76 1.37 christos static int
77 1.37 christos file_count(void)
78 1.37 christos {
79 1.37 christos struct glue *gp;
80 1.37 christos FILE *fp;
81 1.37 christos int n;
82 1.37 christos int cnt;
83 1.37 christos
84 1.37 christos cnt = 0;
85 1.37 christos for (gp = &__sglue; gp; gp = gp->next) {
86 1.37 christos for (fp = gp->iobs, n = gp->niobs; --n >= 0; fp++)
87 1.37 christos if (fp->_flags)
88 1.37 christos cnt++;
89 1.37 christos }
90 1.37 christos return cnt;
91 1.37 christos }
92 1.37 christos
93 1.37 christos static int
94 1.37 christos fds_count(void)
95 1.37 christos {
96 1.37 christos int maxfd;
97 1.37 christos int cnt;
98 1.37 christos int fd;
99 1.37 christos
100 1.37 christos maxfd = fcntl(0, F_MAXFD);
101 1.37 christos if (maxfd == -1) {
102 1.37 christos warn("fcntl");
103 1.37 christos return -1;
104 1.37 christos }
105 1.30 christos
106 1.37 christos cnt = 0;
107 1.37 christos for (fd = 0; fd <= maxfd; fd++) {
108 1.37 christos struct stat sb;
109 1.37 christos
110 1.37 christos if (fstat(fd, &sb) != -1)
111 1.37 christos cnt++;
112 1.37 christos else if (errno != EBADF
113 1.37 christos #ifdef BROKEN_CLONE_STAT /* see PRs 37878 and 37550 */
114 1.37 christos && errno != EOPNOTSUPP
115 1.37 christos #endif
116 1.37 christos )
117 1.37 christos warn("fstat(%d): errno=%d", fd, errno);
118 1.37 christos }
119 1.37 christos return cnt;
120 1.37 christos }
121 1.37 christos
122 1.37 christos static void
123 1.37 christos file_leak_init(void)
124 1.37 christos {
125 1.37 christos open_fd_cnt = fds_count();
126 1.37 christos open_fp_cnt = file_count();
127 1.37 christos }
128 1.37 christos
129 1.37 christos static void
130 1.37 christos file_leak_check(void)
131 1.37 christos {
132 1.37 christos if (open_fp_cnt != file_count() ||
133 1.37 christos open_fd_cnt != fds_count()) {
134 1.37 christos (void)printf("FILE LEAK WARNING: "
135 1.37 christos "fp-count: %d (%d) "
136 1.37 christos "fd-count: %d (%d) max-fd: %d\n",
137 1.37 christos file_count(), open_fp_cnt,
138 1.37 christos fds_count(), open_fd_cnt,
139 1.37 christos fcntl(0, F_MAXFD));
140 1.37 christos }
141 1.37 christos }
142 1.37 christos #endif /* DEBUG_FILE_LEAK */
143 1.30 christos
144 1.42 christos static void
145 1.42 christos update_mailname(const char *name)
146 1.42 christos {
147 1.42 christos char tbuf[PATHSIZE];
148 1.42 christos size_t l;
149 1.42 christos
150 1.43 christos /* Don't realpath(3) if it's only an update request */
151 1.43 christos if (name != NULL && realpath(name, mailname) == NULL) {
152 1.42 christos warn("Can't canonicalize `%s'", name);
153 1.42 christos return;
154 1.42 christos }
155 1.42 christos
156 1.42 christos if (getfold(tbuf, sizeof(tbuf)) >= 0) {
157 1.42 christos l = strlen(tbuf);
158 1.42 christos if (l < sizeof(tbuf) - 1)
159 1.42 christos tbuf[l++] = '/';
160 1.42 christos if (strncmp(tbuf, mailname, l) == 0) {
161 1.42 christos char const *sep = "", *cp = mailname + l;
162 1.42 christos
163 1.42 christos l = strlen(cp);
164 1.42 christos if (l >= sizeof(displayname)) {
165 1.42 christos cp += l;
166 1.42 christos cp -= sizeof(displayname) - 5;
167 1.42 christos sep = "...";
168 1.42 christos }
169 1.42 christos (void)snprintf(displayname, sizeof(displayname),
170 1.42 christos "+%s%s", sep, cp);
171 1.42 christos return;
172 1.42 christos }
173 1.42 christos }
174 1.42 christos
175 1.42 christos l = strlen(mailname);
176 1.42 christos if (l < sizeof(displayname))
177 1.42 christos strcpy(displayname, mailname);
178 1.42 christos else {
179 1.42 christos l -= sizeof(displayname) - 4 - sizeof(displayname) / 3;
180 1.42 christos (void)snprintf(displayname, sizeof(displayname), "%.*s...%s",
181 1.42 christos (int)sizeof(displayname) / 3, mailname, mailname + l);
182 1.42 christos }
183 1.42 christos }
184 1.42 christos
185 1.30 christos /*
186 1.30 christos * Set the size of the message vector used to construct argument
187 1.30 christos * lists to message list functions.
188 1.30 christos */
189 1.30 christos static void
190 1.30 christos setmsize(int sz)
191 1.30 christos {
192 1.30 christos if (msgvec != 0)
193 1.30 christos free(msgvec);
194 1.35 christos msgvec = ecalloc((size_t) (sz + 1), sizeof(*msgvec));
195 1.30 christos }
196 1.1 cgd
197 1.1 cgd /*
198 1.1 cgd * Set up editing on the given file name.
199 1.1 cgd * If the first character of name is %, we are considered to be
200 1.1 cgd * editing the file, otherwise we are reading our mail which has
201 1.1 cgd * signficance for mbox and so forth.
202 1.1 cgd */
203 1.30 christos PUBLIC int
204 1.24 christos setfile(const char *name)
205 1.1 cgd {
206 1.1 cgd FILE *ibuf;
207 1.20 wiz int i, fd;
208 1.1 cgd struct stat stb;
209 1.30 christos char isedit = *name != '%' || getuserid(myname) != (int)getuid();
210 1.24 christos const char *who = name[1] ? name + 1 : myname;
211 1.1 cgd static int shudclob;
212 1.20 wiz char tempname[PATHSIZE];
213 1.1 cgd
214 1.18 wiz if ((name = expand(name)) == NULL)
215 1.1 cgd return -1;
216 1.1 cgd
217 1.44 christos if ((ibuf = Fopen(name, "ref")) == NULL) {
218 1.1 cgd if (!isedit && errno == ENOENT)
219 1.1 cgd goto nomail;
220 1.39 christos warn("Can't open `%s'", name);
221 1.30 christos return -1;
222 1.1 cgd }
223 1.1 cgd
224 1.1 cgd if (fstat(fileno(ibuf), &stb) < 0) {
225 1.22 wiz warn("fstat");
226 1.25 christos (void)Fclose(ibuf);
227 1.30 christos return -1;
228 1.1 cgd }
229 1.1 cgd
230 1.1 cgd switch (stb.st_mode & S_IFMT) {
231 1.1 cgd case S_IFDIR:
232 1.25 christos (void)Fclose(ibuf);
233 1.1 cgd errno = EISDIR;
234 1.22 wiz warn("%s", name);
235 1.30 christos return -1;
236 1.1 cgd
237 1.1 cgd case S_IFREG:
238 1.1 cgd break;
239 1.1 cgd
240 1.1 cgd default:
241 1.25 christos (void)Fclose(ibuf);
242 1.1 cgd errno = EINVAL;
243 1.22 wiz warn("%s", name);
244 1.30 christos return -1;
245 1.1 cgd }
246 1.1 cgd
247 1.1 cgd /*
248 1.1 cgd * Looks like all will be well. We must now relinquish our
249 1.1 cgd * hold on the current set of stuff. Must hold signals
250 1.1 cgd * while we are reading the new file, else we will ruin
251 1.1 cgd * the message[] data structure.
252 1.1 cgd */
253 1.1 cgd
254 1.37 christos sig_check();
255 1.37 christos sig_hold();
256 1.1 cgd if (shudclob)
257 1.37 christos quit(jmpbuf);
258 1.1 cgd
259 1.1 cgd /*
260 1.1 cgd * Copy the messages into /tmp
261 1.1 cgd * and set pointers.
262 1.1 cgd */
263 1.1 cgd
264 1.1 cgd readonly = 0;
265 1.33 christos if ((i = open(name, O_WRONLY)) < 0)
266 1.1 cgd readonly++;
267 1.1 cgd else
268 1.25 christos (void)close(i);
269 1.1 cgd if (shudclob) {
270 1.25 christos (void)fclose(itf);
271 1.25 christos (void)fclose(otf);
272 1.1 cgd }
273 1.1 cgd shudclob = 1;
274 1.1 cgd edit = isedit;
275 1.25 christos (void)strcpy(prevfile, mailname);
276 1.43 christos update_mailname(name != mailname ? name : NULL);
277 1.1 cgd mailsize = fsize(ibuf);
278 1.20 wiz (void)snprintf(tempname, sizeof(tempname),
279 1.20 wiz "%s/mail.RxXXXXXXXXXX", tmpdir);
280 1.20 wiz if ((fd = mkstemp(tempname)) == -1 ||
281 1.44 christos (otf = fdopen(fd, "wef")) == NULL)
282 1.39 christos err(EXIT_FAILURE, "Can't create tmp file `%s'", tempname);
283 1.44 christos if ((itf = fopen(tempname, "ref")) == NULL)
284 1.39 christos err(EXIT_FAILURE, "Can't create tmp file `%s'", tempname);
285 1.25 christos (void)rm(tempname);
286 1.25 christos setptr(ibuf, (off_t)0);
287 1.30 christos setmsize(get_abs_msgCount());
288 1.8 tls /*
289 1.9 mikel * New mail may have arrived while we were reading
290 1.9 mikel * the mail file, so reset mailsize to be where
291 1.8 tls * we really are in the file...
292 1.8 tls */
293 1.8 tls mailsize = ftell(ibuf);
294 1.25 christos (void)Fclose(ibuf);
295 1.37 christos sig_release();
296 1.37 christos sig_check();
297 1.1 cgd sawcom = 0;
298 1.30 christos if (!edit && get_abs_msgCount() == 0) {
299 1.1 cgd nomail:
300 1.25 christos (void)fprintf(stderr, "No mail for %s\n", who);
301 1.1 cgd return -1;
302 1.1 cgd }
303 1.30 christos return 0;
304 1.1 cgd }
305 1.1 cgd
306 1.8 tls /*
307 1.8 tls * Incorporate any new mail that has arrived since we first
308 1.8 tls * started reading mail.
309 1.8 tls */
310 1.30 christos PUBLIC int
311 1.17 wiz incfile(void)
312 1.8 tls {
313 1.25 christos off_t newsize;
314 1.30 christos int omsgCount;
315 1.8 tls FILE *ibuf;
316 1.36 christos int rval;
317 1.8 tls
318 1.30 christos omsgCount = get_abs_msgCount();
319 1.30 christos
320 1.44 christos ibuf = Fopen(mailname, "ref");
321 1.8 tls if (ibuf == NULL)
322 1.8 tls return -1;
323 1.37 christos sig_check();
324 1.37 christos sig_hold();
325 1.8 tls newsize = fsize(ibuf);
326 1.36 christos if (newsize == 0 || /* mail box is now empty??? */
327 1.36 christos newsize < mailsize) { /* mail box has shrunk??? */
328 1.36 christos rval = -1;
329 1.36 christos goto done;
330 1.36 christos }
331 1.36 christos if (newsize == mailsize) {
332 1.36 christos rval = 0; /* no new mail */
333 1.36 christos goto done;
334 1.36 christos }
335 1.30 christos setptr(ibuf, mailsize); /* read in new mail */
336 1.30 christos setmsize(get_abs_msgCount()); /* get the new message count */
337 1.8 tls mailsize = ftell(ibuf);
338 1.36 christos rval = get_abs_msgCount() - omsgCount;
339 1.36 christos done:
340 1.25 christos (void)Fclose(ibuf);
341 1.37 christos sig_release();
342 1.37 christos sig_check();
343 1.36 christos return rval;
344 1.8 tls }
345 1.8 tls
346 1.29 christos /*
347 1.29 christos * Return a pointer to the comment character, respecting quoting as
348 1.29 christos * done in getrawlist(). The comment character is ignored inside
349 1.29 christos * quotes.
350 1.29 christos */
351 1.29 christos static char *
352 1.29 christos comment_char(char *line)
353 1.29 christos {
354 1.29 christos char *p;
355 1.29 christos char quotec;
356 1.29 christos quotec = '\0';
357 1.29 christos for (p = line; *p; p++) {
358 1.29 christos if (quotec != '\0') {
359 1.29 christos if (*p == quotec)
360 1.29 christos quotec = '\0';
361 1.29 christos }
362 1.29 christos else if (*p == '"' || *p == '\'')
363 1.29 christos quotec = *p;
364 1.29 christos else if (*p == COMMENT_CHAR)
365 1.29 christos return p;
366 1.29 christos }
367 1.29 christos return NULL;
368 1.29 christos }
369 1.29 christos
370 1.30 christos /*
371 1.30 christos * Signal handler is hooked by setup_piping().
372 1.30 christos * Respond to a broken pipe signal --
373 1.30 christos * probably caused by quitting more.
374 1.1 cgd */
375 1.30 christos static jmp_buf pipestop;
376 1.30 christos
377 1.30 christos /*ARGSUSED*/
378 1.40 joerg __dead static void
379 1.37 christos lex_brokpipe(int signo)
380 1.1 cgd {
381 1.37 christos
382 1.37 christos longjmp(pipestop, signo);
383 1.30 christos }
384 1.1 cgd
385 1.30 christos /*
386 1.30 christos * Check the command line for any requested piping or redirection,
387 1.30 christos * depending on the value of 'c'. If "enable-pipes" is set, search
388 1.30 christos * the command line (cp) for the first occurrence of the character 'c'
389 1.30 christos * that is not in a quote or (parenthese) group.
390 1.30 christos */
391 1.30 christos PUBLIC char *
392 1.30 christos shellpr(char *cp)
393 1.30 christos {
394 1.30 christos int quotec;
395 1.30 christos int level;
396 1.30 christos
397 1.30 christos if (cp == NULL || value(ENAME_ENABLE_PIPES) == NULL)
398 1.30 christos return NULL;
399 1.30 christos
400 1.30 christos level = 0;
401 1.30 christos quotec = 0;
402 1.30 christos for (/*EMPTY*/; *cp != '\0'; cp++) {
403 1.30 christos if (quotec) {
404 1.30 christos if (*cp == quotec)
405 1.30 christos quotec = 0;
406 1.30 christos if (*cp == '\\' &&
407 1.30 christos (cp[1] == quotec || cp[1] == '\\'))
408 1.30 christos cp++;
409 1.30 christos }
410 1.30 christos else {
411 1.30 christos switch (*cp) {
412 1.30 christos case '|':
413 1.30 christos case '>':
414 1.30 christos if (level == 0)
415 1.30 christos return cp;
416 1.30 christos break;
417 1.30 christos case '(':
418 1.30 christos level++;
419 1.1 cgd break;
420 1.30 christos case ')':
421 1.30 christos level--;
422 1.1 cgd break;
423 1.30 christos case '"':
424 1.30 christos case '\'':
425 1.30 christos quotec = *cp;
426 1.1 cgd break;
427 1.30 christos default:
428 1.1 cgd break;
429 1.1 cgd }
430 1.30 christos }
431 1.30 christos }
432 1.30 christos return NULL;
433 1.30 christos }
434 1.30 christos
435 1.37 christos static int
436 1.37 christos do_paging(const char *cmd, int c_pipe)
437 1.37 christos {
438 1.37 christos char *cp, *p;
439 1.37 christos
440 1.37 christos if (value(ENAME_PAGER_OFF) != NULL)
441 1.37 christos return 0;
442 1.37 christos
443 1.37 christos if (c_pipe & C_PIPE_PAGER)
444 1.37 christos return 1;
445 1.37 christos
446 1.37 christos if (c_pipe & C_PIPE_CRT && value(ENAME_CRT) != NULL)
447 1.37 christos return 1;
448 1.37 christos
449 1.37 christos if ((cp = value(ENAME_PAGE_ALSO)) == NULL)
450 1.37 christos return 0;
451 1.37 christos
452 1.37 christos if ((p = strcasestr(cp, cmd)) == NULL)
453 1.37 christos return 0;
454 1.37 christos
455 1.37 christos if (p != cp && p[-1] != ',' && !is_WSP(p[-1]))
456 1.37 christos return 0;
457 1.37 christos
458 1.37 christos p += strlen(cmd);
459 1.37 christos
460 1.37 christos return (*p == '\0' || *p == ',' || is_WSP(*p));
461 1.37 christos }
462 1.37 christos
463 1.30 christos /*
464 1.30 christos * Setup any pipe or redirection that the command line indicates.
465 1.30 christos * If none, then setup the pager unless "pager-off" is defined.
466 1.30 christos */
467 1.30 christos static FILE *fp_stop = NULL;
468 1.30 christos static int oldfd1 = -1;
469 1.37 christos static sig_t old_sigpipe;
470 1.37 christos
471 1.30 christos static int
472 1.37 christos setup_piping(const char *cmd, char *cmdline, int c_pipe)
473 1.30 christos {
474 1.30 christos FILE *fout;
475 1.30 christos FILE *last_file;
476 1.30 christos char *cp;
477 1.30 christos
478 1.37 christos sig_check();
479 1.37 christos
480 1.30 christos last_file = last_registered_file(0);
481 1.30 christos
482 1.30 christos fout = NULL;
483 1.30 christos if ((cp = shellpr(cmdline)) != NULL) {
484 1.30 christos char c;
485 1.30 christos c = *cp;
486 1.30 christos *cp = '\0';
487 1.30 christos cp++;
488 1.30 christos
489 1.30 christos if (c == '|') {
490 1.41 christos if ((fout = Popen(cp, "we")) == NULL) {
491 1.30 christos warn("Popen: %s", cp);
492 1.30 christos return -1;
493 1.26 christos }
494 1.30 christos }
495 1.30 christos else {
496 1.30 christos const char *mode;
497 1.30 christos assert(c == '>');
498 1.41 christos mode = *cp == '>' ? "ae" : "we";
499 1.30 christos if (*cp == '>')
500 1.30 christos cp++;
501 1.30 christos
502 1.34 christos cp = skip_WSP(cp);
503 1.30 christos if ((fout = Fopen(cp, mode)) == NULL) {
504 1.30 christos warn("Fopen: %s", cp);
505 1.30 christos return -1;
506 1.1 cgd }
507 1.1 cgd }
508 1.30 christos
509 1.30 christos }
510 1.37 christos else if (do_paging(cmd, c_pipe)) {
511 1.30 christos const char *pager;
512 1.30 christos pager = value(ENAME_PAGER);
513 1.30 christos if (pager == NULL || *pager == '\0')
514 1.30 christos pager = _PATH_MORE;
515 1.30 christos
516 1.41 christos if ((fout = Popen(pager, "we")) == NULL) {
517 1.30 christos warn("Popen: %s", pager);
518 1.30 christos return -1;
519 1.30 christos }
520 1.30 christos }
521 1.30 christos
522 1.30 christos if (fout) {
523 1.37 christos old_sigpipe = sig_signal(SIGPIPE, lex_brokpipe);
524 1.30 christos (void)fflush(stdout);
525 1.33 christos if ((oldfd1 = dup(STDOUT_FILENO)) == -1)
526 1.30 christos err(EXIT_FAILURE, "dup failed");
527 1.33 christos if (dup2(fileno(fout), STDOUT_FILENO) == -1)
528 1.30 christos err(EXIT_FAILURE, "dup2 failed");
529 1.30 christos fp_stop = last_file;
530 1.30 christos }
531 1.30 christos return 0;
532 1.30 christos }
533 1.30 christos
534 1.30 christos /*
535 1.30 christos * This will close any piping started by setup_piping().
536 1.30 christos */
537 1.30 christos static void
538 1.30 christos close_piping(void)
539 1.30 christos {
540 1.37 christos sigset_t oset;
541 1.37 christos struct sigaction osa;
542 1.37 christos
543 1.30 christos if (oldfd1 != -1) {
544 1.30 christos (void)fflush(stdout);
545 1.33 christos if (fileno(stdout) != oldfd1 &&
546 1.33 christos dup2(oldfd1, STDOUT_FILENO) == -1)
547 1.30 christos err(EXIT_FAILURE, "dup2 failed");
548 1.30 christos
549 1.37 christos (void)sig_ignore(SIGPIPE, &osa, &oset);
550 1.37 christos
551 1.30 christos close_top_files(fp_stop);
552 1.30 christos fp_stop = NULL;
553 1.30 christos (void)close(oldfd1);
554 1.30 christos oldfd1 = -1;
555 1.37 christos
556 1.37 christos (void)sig_signal(SIGPIPE, old_sigpipe);
557 1.37 christos (void)sig_restore(SIGPIPE, &osa, &oset);
558 1.1 cgd }
559 1.37 christos sig_check();
560 1.1 cgd }
561 1.1 cgd
562 1.1 cgd /*
563 1.30 christos * Determine if as1 is a valid prefix of as2.
564 1.30 christos * Return true if yep.
565 1.30 christos */
566 1.30 christos static int
567 1.30 christos isprefix(char *as1, const char *as2)
568 1.30 christos {
569 1.30 christos char *s1;
570 1.30 christos const char *s2;
571 1.30 christos
572 1.30 christos s1 = as1;
573 1.30 christos s2 = as2;
574 1.30 christos while (*s1++ == *s2)
575 1.30 christos if (*s2++ == '\0')
576 1.30 christos return 1;
577 1.30 christos return *--s1 == '\0';
578 1.30 christos }
579 1.30 christos
580 1.30 christos /*
581 1.30 christos * Find the correct command in the command table corresponding
582 1.30 christos * to the passed command "word"
583 1.30 christos */
584 1.30 christos PUBLIC const struct cmd *
585 1.30 christos lex(char word[])
586 1.30 christos {
587 1.30 christos const struct cmd *cp;
588 1.30 christos
589 1.30 christos for (cp = &cmdtab[0]; cp->c_name != NULL; cp++)
590 1.30 christos if (isprefix(word, cp->c_name))
591 1.30 christos return cp;
592 1.30 christos return NULL;
593 1.30 christos }
594 1.30 christos
595 1.30 christos PUBLIC char *
596 1.30 christos get_cmdname(char *buf)
597 1.30 christos {
598 1.30 christos char *cp;
599 1.30 christos char *cmd;
600 1.30 christos size_t len;
601 1.30 christos
602 1.30 christos for (cp = buf; *cp; cp++)
603 1.30 christos if (strchr(" \t0123456789$^.:/-+*'\">|", *cp) != NULL)
604 1.30 christos break;
605 1.33 christos /* XXX - Don't miss the pipe command! */
606 1.33 christos if (cp == buf && *cp == '|')
607 1.33 christos cp++;
608 1.30 christos len = cp - buf + 1;
609 1.30 christos cmd = salloc(len);
610 1.30 christos (void)strlcpy(cmd, buf, len);
611 1.30 christos return cmd;
612 1.30 christos }
613 1.30 christos
614 1.30 christos /*
615 1.1 cgd * Execute a single command.
616 1.1 cgd * Command functions return 0 for success, 1 for error, and -1
617 1.1 cgd * for abort. A 1 or -1 aborts a load or source. A -1 aborts
618 1.1 cgd * the interactive command loop.
619 1.32 christos * execute_contxt_e is in extern.h.
620 1.1 cgd */
621 1.30 christos PUBLIC int
622 1.32 christos execute(char linebuf[], enum execute_contxt_e contxt)
623 1.1 cgd {
624 1.30 christos char *word;
625 1.1 cgd char *arglist[MAXARGC];
626 1.38 apb const struct cmd * volatile com = NULL;
627 1.30 christos char *volatile cp;
628 1.37 christos int retval;
629 1.11 lukem int c;
630 1.45 mrg volatile int e = 1;
631 1.1 cgd
632 1.1 cgd /*
633 1.1 cgd * Strip the white space away from the beginning
634 1.1 cgd * of the command, then scan out a word, which
635 1.1 cgd * consists of anything except digits and white space.
636 1.1 cgd *
637 1.1 cgd * Handle ! escapes differently to get the correct
638 1.1 cgd * lexical conventions.
639 1.1 cgd */
640 1.1 cgd
641 1.34 christos cp = skip_space(linebuf);
642 1.1 cgd if (*cp == '!') {
643 1.1 cgd if (sourcing) {
644 1.25 christos (void)printf("Can't \"!\" while sourcing\n");
645 1.1 cgd goto out;
646 1.1 cgd }
647 1.28 christos (void)shell(cp + 1);
648 1.30 christos return 0;
649 1.1 cgd }
650 1.30 christos
651 1.30 christos word = get_cmdname(cp);
652 1.30 christos cp += strlen(word);
653 1.1 cgd
654 1.1 cgd /*
655 1.1 cgd * Look up the command; if not found, bitch.
656 1.1 cgd * Normally, a blank command would map to the
657 1.1 cgd * first command in the table; while sourcing,
658 1.1 cgd * however, we ignore blank lines to eliminate
659 1.1 cgd * confusion.
660 1.1 cgd */
661 1.1 cgd
662 1.1 cgd if (sourcing && *word == '\0')
663 1.30 christos return 0;
664 1.1 cgd com = lex(word);
665 1.19 wiz if (com == NULL) {
666 1.25 christos (void)printf("Unknown command: \"%s\"\n", word);
667 1.1 cgd goto out;
668 1.1 cgd }
669 1.1 cgd
670 1.1 cgd /*
671 1.1 cgd * See if we should execute the command -- if a conditional
672 1.1 cgd * we always execute it, otherwise, check the state of cond.
673 1.1 cgd */
674 1.1 cgd
675 1.30 christos if ((com->c_argtype & F) == 0 && (cond & CSKIP))
676 1.30 christos return 0;
677 1.1 cgd
678 1.1 cgd /*
679 1.1 cgd * Process the arguments to the command, depending
680 1.1 cgd * on the type he expects. Default to an error.
681 1.1 cgd * If we are sourcing an interactive command, it's
682 1.1 cgd * an error.
683 1.1 cgd */
684 1.1 cgd
685 1.30 christos if (mailmode == mm_sending && (com->c_argtype & M) == 0) {
686 1.25 christos (void)printf("May not execute \"%s\" while sending\n",
687 1.1 cgd com->c_name);
688 1.1 cgd goto out;
689 1.1 cgd }
690 1.1 cgd if (sourcing && com->c_argtype & I) {
691 1.25 christos (void)printf("May not execute \"%s\" while sourcing\n",
692 1.1 cgd com->c_name);
693 1.1 cgd goto out;
694 1.1 cgd }
695 1.1 cgd if (readonly && com->c_argtype & W) {
696 1.25 christos (void)printf("May not execute \"%s\" -- message file is read only\n",
697 1.1 cgd com->c_name);
698 1.1 cgd goto out;
699 1.1 cgd }
700 1.32 christos if (contxt == ec_composing && com->c_argtype & R) {
701 1.25 christos (void)printf("Cannot recursively invoke \"%s\"\n", com->c_name);
702 1.1 cgd goto out;
703 1.1 cgd }
704 1.30 christos
705 1.30 christos if (!sourcing && com->c_pipe && value(ENAME_INTERACTIVE) != NULL) {
706 1.37 christos
707 1.37 christos sig_check();
708 1.30 christos if (setjmp(pipestop))
709 1.30 christos goto out;
710 1.30 christos
711 1.37 christos if (setup_piping(com->c_name, cp, com->c_pipe) == -1)
712 1.30 christos goto out;
713 1.30 christos }
714 1.30 christos switch (com->c_argtype & ARGTYPE_MASK) {
715 1.1 cgd case MSGLIST:
716 1.1 cgd /*
717 1.1 cgd * A message list defaulting to nearest forward
718 1.1 cgd * legal message.
719 1.1 cgd */
720 1.1 cgd if (msgvec == 0) {
721 1.25 christos (void)printf("Illegal use of \"message list\"\n");
722 1.1 cgd break;
723 1.1 cgd }
724 1.1 cgd if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0)
725 1.1 cgd break;
726 1.1 cgd if (c == 0) {
727 1.30 christos *msgvec = first(com->c_msgflag, com->c_msgmask);
728 1.10 pk msgvec[1] = 0;
729 1.1 cgd }
730 1.10 pk if (*msgvec == 0) {
731 1.25 christos (void)printf("No applicable messages\n");
732 1.1 cgd break;
733 1.1 cgd }
734 1.1 cgd e = (*com->c_func)(msgvec);
735 1.1 cgd break;
736 1.1 cgd
737 1.1 cgd case NDMLIST:
738 1.1 cgd /*
739 1.1 cgd * A message list with no defaults, but no error
740 1.1 cgd * if none exist.
741 1.1 cgd */
742 1.1 cgd if (msgvec == 0) {
743 1.25 christos (void)printf("Illegal use of \"message list\"\n");
744 1.1 cgd break;
745 1.1 cgd }
746 1.1 cgd if (getmsglist(cp, msgvec, com->c_msgflag) < 0)
747 1.1 cgd break;
748 1.1 cgd e = (*com->c_func)(msgvec);
749 1.1 cgd break;
750 1.1 cgd
751 1.1 cgd case STRLIST:
752 1.1 cgd /*
753 1.1 cgd * Just the straight string, with
754 1.1 cgd * leading blanks removed.
755 1.1 cgd */
756 1.34 christos cp = skip_space(cp);
757 1.1 cgd e = (*com->c_func)(cp);
758 1.1 cgd break;
759 1.1 cgd
760 1.1 cgd case RAWLIST:
761 1.1 cgd /*
762 1.1 cgd * A vector of strings, in shell style.
763 1.1 cgd */
764 1.37 christos if ((c = getrawlist(cp, arglist, (int)__arraycount(arglist))) < 0)
765 1.1 cgd break;
766 1.1 cgd if (c < com->c_minargs) {
767 1.25 christos (void)printf("%s requires at least %d arg(s)\n",
768 1.1 cgd com->c_name, com->c_minargs);
769 1.1 cgd break;
770 1.1 cgd }
771 1.1 cgd if (c > com->c_maxargs) {
772 1.25 christos (void)printf("%s takes no more than %d arg(s)\n",
773 1.1 cgd com->c_name, com->c_maxargs);
774 1.1 cgd break;
775 1.1 cgd }
776 1.1 cgd e = (*com->c_func)(arglist);
777 1.1 cgd break;
778 1.1 cgd
779 1.1 cgd case NOLIST:
780 1.1 cgd /*
781 1.1 cgd * Just the constant zero, for exiting,
782 1.1 cgd * eg.
783 1.1 cgd */
784 1.1 cgd e = (*com->c_func)(0);
785 1.1 cgd break;
786 1.1 cgd
787 1.1 cgd default:
788 1.39 christos errx(EXIT_FAILURE, "Unknown argtype");
789 1.1 cgd }
790 1.1 cgd
791 1.1 cgd out:
792 1.30 christos close_piping();
793 1.30 christos
794 1.1 cgd /*
795 1.1 cgd * Exit the current source file on
796 1.1 cgd * error.
797 1.1 cgd */
798 1.37 christos retval = 0;
799 1.1 cgd if (e) {
800 1.1 cgd if (e < 0)
801 1.37 christos retval = 1;
802 1.37 christos else if (loading)
803 1.37 christos retval = 1;
804 1.37 christos else if (sourcing)
805 1.25 christos (void)unstack();
806 1.1 cgd }
807 1.37 christos else if (com != NULL) {
808 1.37 christos if (contxt != ec_autoprint && com->c_argtype & P &&
809 1.37 christos value(ENAME_AUTOPRINT) != NULL &&
810 1.37 christos (dot->m_flag & MDELETED) == 0)
811 1.37 christos (void)execute(__UNCONST("print ."), ec_autoprint);
812 1.37 christos if (!sourcing && (com->c_argtype & T) == 0)
813 1.37 christos sawcom = 1;
814 1.37 christos }
815 1.37 christos sig_check();
816 1.37 christos return retval;
817 1.1 cgd }
818 1.1 cgd
819 1.1 cgd /*
820 1.1 cgd * The following gets called on receipt of an interrupt. This is
821 1.1 cgd * to abort printout of a command, mainly.
822 1.37 christos * Dispatching here when commands() is inactive crashes rcv.
823 1.1 cgd * Close all open files except 0, 1, 2, and the temporary.
824 1.1 cgd * Also, unstack all source files.
825 1.1 cgd */
826 1.40 joerg __dead static void
827 1.37 christos lex_intr(int signo)
828 1.1 cgd {
829 1.37 christos
830 1.1 cgd noreset = 0;
831 1.1 cgd if (!inithdr)
832 1.1 cgd sawcom++;
833 1.1 cgd inithdr = 0;
834 1.1 cgd while (sourcing)
835 1.25 christos (void)unstack();
836 1.1 cgd
837 1.30 christos close_piping();
838 1.1 cgd close_all_files();
839 1.1 cgd
840 1.1 cgd if (image >= 0) {
841 1.25 christos (void)close(image);
842 1.1 cgd image = -1;
843 1.1 cgd }
844 1.25 christos (void)fprintf(stderr, "Interrupt\n");
845 1.37 christos longjmp(jmpbuf, signo);
846 1.1 cgd }
847 1.1 cgd
848 1.1 cgd /*
849 1.1 cgd * Branch here on hangup signal and simulate "exit".
850 1.1 cgd */
851 1.1 cgd /*ARGSUSED*/
852 1.40 joerg __dead static void
853 1.37 christos lex_hangup(int s __unused)
854 1.1 cgd {
855 1.37 christos
856 1.1 cgd /* nothing to do? */
857 1.37 christos exit(EXIT_FAILURE);
858 1.37 christos }
859 1.37 christos
860 1.37 christos /*
861 1.37 christos * When we wake up after ^Z, reprint the prompt.
862 1.37 christos *
863 1.37 christos * NOTE: EditLine deals with the prompt and job control, so with it
864 1.37 christos * this does nothing, i.e., reset_on_stop == 0.
865 1.37 christos */
866 1.37 christos static void
867 1.37 christos lex_stop(int signo)
868 1.37 christos {
869 1.37 christos
870 1.37 christos if (reset_on_stop) {
871 1.37 christos reset_on_stop = 0;
872 1.37 christos longjmp(jmpbuf, signo);
873 1.37 christos }
874 1.1 cgd }
875 1.1 cgd
876 1.1 cgd /*
877 1.30 christos * Interpret user commands one by one. If standard input is not a tty,
878 1.30 christos * print no prompt.
879 1.1 cgd */
880 1.30 christos PUBLIC void
881 1.30 christos commands(void)
882 1.1 cgd {
883 1.30 christos int n;
884 1.30 christos char linebuf[LINESIZE];
885 1.30 christos int eofloop;
886 1.30 christos
887 1.37 christos #ifdef DEBUG_FILE_LEAK
888 1.37 christos file_leak_init();
889 1.37 christos #endif
890 1.37 christos
891 1.30 christos if (!sourcing) {
892 1.37 christos sig_check();
893 1.37 christos
894 1.37 christos sig_hold();
895 1.37 christos (void)sig_signal(SIGINT, lex_intr);
896 1.37 christos (void)sig_signal(SIGHUP, lex_hangup);
897 1.37 christos (void)sig_signal(SIGTSTP, lex_stop);
898 1.37 christos (void)sig_signal(SIGTTOU, lex_stop);
899 1.37 christos (void)sig_signal(SIGTTIN, lex_stop);
900 1.37 christos sig_release();
901 1.30 christos }
902 1.37 christos
903 1.37 christos (void)setjmp(jmpbuf); /* "reset" location if we got an interrupt */
904 1.37 christos
905 1.30 christos eofloop = 0; /* initialize this after a possible longjmp */
906 1.30 christos for (;;) {
907 1.37 christos sig_check();
908 1.30 christos (void)fflush(stdout);
909 1.30 christos sreset();
910 1.30 christos /*
911 1.30 christos * Print the prompt, if needed. Clear out
912 1.30 christos * string space, and flush the output.
913 1.30 christos */
914 1.30 christos if (!sourcing && value(ENAME_INTERACTIVE) != NULL) {
915 1.30 christos if ((prompt = value(ENAME_PROMPT)) == NULL)
916 1.30 christos prompt = DEFAULT_PROMPT;
917 1.30 christos prompt = smsgprintf(prompt, dot);
918 1.30 christos if ((value(ENAME_AUTOINC) != NULL) && (incfile() > 0))
919 1.30 christos (void)printf("New mail has arrived.\n");
920 1.37 christos
921 1.30 christos #ifndef USE_EDITLINE
922 1.37 christos reset_on_stop = 1; /* enable job control longjmp */
923 1.30 christos (void)printf("%s", prompt);
924 1.30 christos #endif
925 1.30 christos }
926 1.37 christos #ifdef DEBUG_FILE_LEAK
927 1.37 christos file_leak_check();
928 1.37 christos #endif
929 1.30 christos /*
930 1.30 christos * Read a line of commands from the current input
931 1.30 christos * and handle end of file specially.
932 1.30 christos */
933 1.30 christos n = 0;
934 1.30 christos for (;;) {
935 1.37 christos sig_check();
936 1.30 christos #ifdef USE_EDITLINE
937 1.30 christos if (!sourcing) {
938 1.30 christos char *line;
939 1.37 christos
940 1.37 christos line = my_gets(&elm.command, prompt, NULL);
941 1.37 christos if (line == NULL) {
942 1.30 christos if (n == 0)
943 1.30 christos n = -1;
944 1.30 christos break;
945 1.30 christos }
946 1.30 christos (void)strlcpy(linebuf, line, sizeof(linebuf));
947 1.30 christos }
948 1.30 christos else {
949 1.37 christos if (readline(input, &linebuf[n], LINESIZE - n, 0) < 0) {
950 1.30 christos if (n == 0)
951 1.30 christos n = -1;
952 1.30 christos break;
953 1.30 christos }
954 1.30 christos }
955 1.30 christos #else /* USE_EDITLINE */
956 1.37 christos if (readline(input, &linebuf[n], LINESIZE - n, reset_on_stop) < 0) {
957 1.30 christos if (n == 0)
958 1.30 christos n = -1;
959 1.30 christos break;
960 1.30 christos }
961 1.30 christos #endif /* USE_EDITLINE */
962 1.37 christos if (!sourcing)
963 1.37 christos setscreensize(); /* so we can resize window */
964 1.1 cgd
965 1.30 christos if (sourcing) { /* allow comments in source files */
966 1.30 christos char *ptr;
967 1.30 christos if ((ptr = comment_char(linebuf)) != NULL)
968 1.30 christos *ptr = '\0';
969 1.30 christos }
970 1.37 christos if ((n = (int)strlen(linebuf)) == 0)
971 1.30 christos break;
972 1.30 christos n--;
973 1.30 christos if (linebuf[n] != '\\')
974 1.30 christos break;
975 1.30 christos linebuf[n++] = ' ';
976 1.30 christos }
977 1.37 christos #ifndef USE_EDITLINE
978 1.37 christos sig_check();
979 1.37 christos reset_on_stop = 0; /* disable job control longjmp */
980 1.37 christos #endif
981 1.30 christos if (n < 0) {
982 1.37 christos char *p;
983 1.37 christos
984 1.37 christos /* eof */
985 1.30 christos if (loading)
986 1.30 christos break;
987 1.30 christos if (sourcing) {
988 1.30 christos (void)unstack();
989 1.30 christos continue;
990 1.30 christos }
991 1.30 christos if (value(ENAME_INTERACTIVE) != NULL &&
992 1.37 christos (p = value(ENAME_IGNOREEOF)) != NULL &&
993 1.37 christos ++eofloop < (*p == '\0' ? 25 : atoi(p))) {
994 1.30 christos (void)printf("Use \"quit\" to quit.\n");
995 1.30 christos continue;
996 1.30 christos }
997 1.30 christos break;
998 1.30 christos }
999 1.30 christos eofloop = 0;
1000 1.32 christos if (execute(linebuf, ec_normal))
1001 1.30 christos break;
1002 1.1 cgd }
1003 1.1 cgd }
1004 1.1 cgd
1005 1.1 cgd /*
1006 1.1 cgd * Announce information about the file we are editing.
1007 1.1 cgd * Return a likely place to set dot.
1008 1.1 cgd */
1009 1.30 christos PUBLIC int
1010 1.17 wiz newfileinfo(int omsgCount)
1011 1.1 cgd {
1012 1.11 lukem struct message *mp;
1013 1.30 christos int d, n, s, t, u, mdot;
1014 1.1 cgd
1015 1.30 christos /*
1016 1.30 christos * Figure out where to set the 'dot'. Use the first new or
1017 1.30 christos * unread message.
1018 1.30 christos */
1019 1.30 christos for (mp = get_abs_message(omsgCount + 1); mp;
1020 1.30 christos mp = next_abs_message(mp))
1021 1.1 cgd if (mp->m_flag & MNEW)
1022 1.1 cgd break;
1023 1.30 christos
1024 1.30 christos if (mp == NULL)
1025 1.30 christos for (mp = get_abs_message(omsgCount + 1); mp;
1026 1.30 christos mp = next_abs_message(mp))
1027 1.1 cgd if ((mp->m_flag & MREAD) == 0)
1028 1.1 cgd break;
1029 1.30 christos if (mp != NULL)
1030 1.30 christos mdot = get_msgnum(mp);
1031 1.1 cgd else
1032 1.8 tls mdot = omsgCount + 1;
1033 1.30 christos #ifdef THREAD_SUPPORT
1034 1.30 christos /*
1035 1.30 christos * See if the message is in the current thread.
1036 1.30 christos */
1037 1.30 christos if (mp != NULL && get_message(1) != NULL && get_message(mdot) != mp)
1038 1.30 christos mdot = 0;
1039 1.30 christos #endif
1040 1.30 christos /*
1041 1.30 christos * Scan the message array counting the new, unread, deleted,
1042 1.30 christos * and saved messages.
1043 1.30 christos */
1044 1.30 christos d = n = s = t = u = 0;
1045 1.30 christos for (mp = get_abs_message(1); mp; mp = next_abs_message(mp)) {
1046 1.1 cgd if (mp->m_flag & MNEW)
1047 1.1 cgd n++;
1048 1.1 cgd if ((mp->m_flag & MREAD) == 0)
1049 1.1 cgd u++;
1050 1.1 cgd if (mp->m_flag & MDELETED)
1051 1.1 cgd d++;
1052 1.1 cgd if (mp->m_flag & MSAVED)
1053 1.1 cgd s++;
1054 1.30 christos if (mp->m_flag & MTAGGED)
1055 1.30 christos t++;
1056 1.1 cgd }
1057 1.30 christos /*
1058 1.30 christos * Display the statistics.
1059 1.30 christos */
1060 1.43 christos update_mailname(NULL);
1061 1.42 christos (void)printf("\"%s\": ", displayname);
1062 1.30 christos {
1063 1.30 christos int cnt = get_abs_msgCount();
1064 1.30 christos (void)printf("%d message%s", cnt, cnt == 1 ? "" : "s");
1065 1.30 christos }
1066 1.1 cgd if (n > 0)
1067 1.25 christos (void)printf(" %d new", n);
1068 1.1 cgd if (u-n > 0)
1069 1.25 christos (void)printf(" %d unread", u);
1070 1.30 christos if (t > 0)
1071 1.30 christos (void)printf(" %d tagged", t);
1072 1.1 cgd if (d > 0)
1073 1.25 christos (void)printf(" %d deleted", d);
1074 1.1 cgd if (s > 0)
1075 1.25 christos (void)printf(" %d saved", s);
1076 1.1 cgd if (readonly)
1077 1.25 christos (void)printf(" [Read only]");
1078 1.25 christos (void)printf("\n");
1079 1.30 christos
1080 1.30 christos return mdot;
1081 1.30 christos }
1082 1.30 christos
1083 1.30 christos /*
1084 1.30 christos * Announce the presence of the current Mail version,
1085 1.30 christos * give the message count, and print a header listing.
1086 1.30 christos */
1087 1.30 christos PUBLIC void
1088 1.30 christos announce(void)
1089 1.30 christos {
1090 1.30 christos int vec[2], mdot;
1091 1.30 christos
1092 1.30 christos mdot = newfileinfo(0);
1093 1.30 christos vec[0] = mdot;
1094 1.30 christos vec[1] = 0;
1095 1.30 christos if ((dot = get_message(mdot)) == NULL)
1096 1.30 christos dot = get_abs_message(1); /* make sure we get something! */
1097 1.30 christos if (get_abs_msgCount() > 0 && value(ENAME_NOHEADER) == NULL) {
1098 1.30 christos inithdr++;
1099 1.30 christos (void)headers(vec);
1100 1.30 christos inithdr = 0;
1101 1.30 christos }
1102 1.1 cgd }
1103 1.1 cgd
1104 1.1 cgd /*
1105 1.1 cgd * Print the current version number.
1106 1.1 cgd */
1107 1.1 cgd
1108 1.1 cgd /*ARGSUSED*/
1109 1.30 christos PUBLIC int
1110 1.28 christos pversion(void *v __unused)
1111 1.1 cgd {
1112 1.25 christos (void)printf("Version %s\n", version);
1113 1.30 christos return 0;
1114 1.1 cgd }
1115 1.1 cgd
1116 1.1 cgd /*
1117 1.1 cgd * Load a file of user definitions.
1118 1.1 cgd */
1119 1.30 christos PUBLIC void
1120 1.24 christos load(const char *name)
1121 1.1 cgd {
1122 1.11 lukem FILE *in, *oldin;
1123 1.1 cgd
1124 1.44 christos if ((in = Fopen(name, "ref")) == NULL)
1125 1.1 cgd return;
1126 1.1 cgd oldin = input;
1127 1.1 cgd input = in;
1128 1.1 cgd loading = 1;
1129 1.1 cgd sourcing = 1;
1130 1.1 cgd commands();
1131 1.1 cgd loading = 0;
1132 1.1 cgd sourcing = 0;
1133 1.1 cgd input = oldin;
1134 1.25 christos (void)Fclose(in);
1135 1.1 cgd }
1136