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