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