cc-test.sh: do a simple \`Resend' test
[s-mailx.git] / fio.c
blobf385b5737e0d97f95dca61507fb2b7b333bec73d
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ File I/O, including resource file loading etc.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
35 #undef n_FILE
36 #define n_FILE fio
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 #include <sys/wait.h>
44 #ifdef HAVE_SOCKETS
45 # include <sys/socket.h>
47 # include <netdb.h>
49 # include <netinet/in.h>
51 # ifdef HAVE_ARPA_INET_H
52 # include <arpa/inet.h>
53 # endif
54 #endif
56 #ifdef HAVE_WORDEXP
57 # include <wordexp.h>
58 #endif
60 #ifdef HAVE_OPENSSL
61 # include <openssl/err.h>
62 # include <openssl/rand.h>
63 # include <openssl/ssl.h>
64 # include <openssl/x509v3.h>
65 # include <openssl/x509.h>
66 #endif
68 #ifdef HAVE_DOTLOCK
69 # include "dotlock.h"
70 #endif
72 struct fio_stack {
73 FILE *s_file; /* File we were in. */
74 void *s_cond; /* Saved state of conditional stack */
75 int s_loading; /* Loading .mailrc, etc. */
78 struct shvar_stack {
79 struct shvar_stack *shs_next;
80 char const *shs_value; /* Remaining value to expand */
81 size_t shs_len; /* gth of .shs_dat this level */
82 char const *shs_dat; /* Result data of this level */
85 /* Slots in ::message */
86 static size_t _message_space;
88 /* XXX Our Popen() main() takes void, temporary global data store */
89 #ifdef HAVE_DOTLOCK
90 static enum file_lock_type _dotlock_flt;
91 static int _dotlock_fd;
92 struct dotlock_info * _dotlock_dip;
93 #endif
95 /* */
96 static struct fio_stack _fio_stack[FIO_STACK_SIZE];
97 static size_t _fio_stack_size;
98 static FILE * _fio_input;
100 /* Locate the user's mailbox file (where new, unread mail is queued) */
101 static void _findmail(char *buf, size_t bufsize, char const *user,
102 bool_t force);
104 /* Perform shell variable expansion */
105 static char * _shvar_exp(char const *name);
106 static char * __shvar_exp(struct shvar_stack *shsp);
108 /* Perform shell meta character expansion TODO obsolete (INSECURE!) */
109 static char * _globname(char const *name, enum fexp_mode fexpm);
111 /* line is a buffer with the result of fgets(). Returns the first newline or
112 * the last character read */
113 static size_t _length_of_line(char const *line, size_t linesize);
115 /* Read a line, one character at a time */
116 static char * _fgetline_byone(char **line, size_t *linesize, size_t *llen,
117 FILE *fp, int appendnl, size_t n SMALLOC_DEBUG_ARGS);
119 /* Take the data out of the passed ghost file and toss it into a dynamically
120 * allocated message structure */
121 static void makemessage(void);
123 static enum okay get_header(struct message *mp);
125 /* Workhorse */
126 static bool_t _file_lock(int fd, enum file_lock_type ft,
127 off_t off, off_t len);
129 /* main() of fork(2)ed dot file locker */
130 #ifdef HAVE_DOTLOCK
131 static int _dotlock_main(void);
132 #endif
134 /* Write to socket fd, restarting on EINTR, unless anything is written */
135 #ifdef HAVE_SOCKETS
136 static long xwrite(int fd, char const *data, size_t sz);
137 #endif
139 static void
140 _findmail(char *buf, size_t bufsize, char const *user, bool_t force)
142 char *cp;
143 NYD_ENTER;
145 if (!strcmp(user, myname) && !force && (cp = ok_vlook(folder)) != NULL) {
146 switch (which_protocol(cp)) {
147 case PROTO_IMAP:
148 if (strcmp(cp, protbase(cp)))
149 goto jcopy;
150 snprintf(buf, bufsize, "%s/INBOX", cp);
151 goto jleave;
152 default:
153 break;
157 if (force || (cp = ok_vlook(MAIL)) == NULL)
158 snprintf(buf, bufsize, "%s/%s", MAILSPOOL, user);
159 else
160 jcopy:
161 n_strlcpy(buf, cp, bufsize);
162 jleave:
163 NYD_LEAVE;
166 static char *
167 _shvar_exp(char const *name)
169 struct shvar_stack top;
170 char *rv;
171 NYD2_ENTER;
173 memset(&top, 0, sizeof top);
174 top.shs_value = name;
175 rv = __shvar_exp(&top);
177 NYD2_LEAVE;
178 return rv;
181 static char *
182 __shvar_exp(struct shvar_stack *shsp)
184 struct shvar_stack next, *np, *tmp;
185 char const *vp;
186 char lc, c, *cp, *rv;
187 size_t i;
188 NYD2_ENTER;
190 if (*(vp = shsp->shs_value) != '$') {
191 union {bool_t hadbs; char c;} u = {FAL0};
193 shsp->shs_dat = vp;
194 for (lc = '\0', i = 0; ((c = *vp) != '\0'); ++i, ++vp) {
195 if (c == '$' && lc != '\\')
196 break;
197 lc = (lc == '\\') ? (u.hadbs = TRU1, '\0') : c;
199 shsp->shs_len = i;
201 if (u.hadbs) {
202 shsp->shs_dat = cp = savestrbuf(shsp->shs_dat, i);
204 for (lc = '\0', rv = cp; (u.c = *cp++) != '\0';) {
205 if (u.c != '\\' || lc == '\\')
206 *rv++ = u.c;
207 lc = (lc == '\\') ? '\0' : u.c;
209 *rv = '\0';
211 shsp->shs_len = PTR2SIZE(rv - shsp->shs_dat);
213 } else {
214 if ((lc = (*++vp == '{')))
215 ++vp;
217 shsp->shs_dat = vp;
218 for (i = 0; (c = *vp) != '\0'; ++i, ++vp)
219 if (!alnumchar(c) && c != '_')
220 break;
222 if (lc) {
223 if (c != '}') {
224 n_err(_("Variable name misses closing \"}\": \"%s\"\n"),
225 shsp->shs_value);
226 shsp->shs_len = strlen(shsp->shs_value);
227 shsp->shs_dat = shsp->shs_value;
228 goto junroll;
230 c = *++vp;
233 shsp->shs_len = i;
234 if ((cp = vok_vlook(savestrbuf(shsp->shs_dat, i))) != NULL)
235 shsp->shs_len = strlen(shsp->shs_dat = cp);
237 if (c != '\0')
238 goto jrecurse;
240 /* That level made the great and completed encoding. Build result */
241 junroll:
242 for (i = 0, np = shsp, shsp = NULL; np != NULL;) {
243 i += np->shs_len;
244 tmp = np->shs_next;
245 np->shs_next = shsp;
246 shsp = np;
247 np = tmp;
250 cp = rv = salloc(i +1);
251 while (shsp != NULL) {
252 np = shsp;
253 shsp = shsp->shs_next;
254 memcpy(cp, np->shs_dat, np->shs_len);
255 cp += np->shs_len;
257 *cp = '\0';
259 jleave:
260 NYD2_LEAVE;
261 return rv;
262 jrecurse:
263 memset(&next, 0, sizeof next);
264 next.shs_next = shsp;
265 next.shs_value = vp;
266 rv = __shvar_exp(&next);
267 goto jleave;
270 static char *
271 _globname(char const *name, enum fexp_mode fexpm)
273 #ifdef HAVE_WORDEXP
274 wordexp_t we;
275 char *cp = NULL;
276 sigset_t nset;
277 int i;
278 NYD_ENTER;
280 /* Mac OS X Snow Leopard and Linux don't init fields on error, causing
281 * SIGSEGV in wordfree(3); so let's just always zero it ourselfs */
282 memset(&we, 0, sizeof we);
284 /* Some systems (notably Open UNIX 8.0.0) fork a shell for wordexp()
285 * and wait, which will fail if our SIGCHLD handler is active */
286 sigemptyset(&nset);
287 sigaddset(&nset, SIGCHLD);
288 sigprocmask(SIG_BLOCK, &nset, NULL);
289 # ifndef WRDE_NOCMD
290 # define WRDE_NOCMD 0
291 # endif
292 i = wordexp(name, &we, WRDE_NOCMD);
293 sigprocmask(SIG_UNBLOCK, &nset, NULL);
295 switch (i) {
296 case 0:
297 break;
298 #ifdef WRDE_CMDSUB
299 case WRDE_CMDSUB:
300 if (!(fexpm & FEXP_SILENT))
301 n_err(_("\"%s\": Command substitution not allowed\n"), name);
302 goto jleave;
303 #endif
304 case WRDE_NOSPACE:
305 if (!(fexpm & FEXP_SILENT))
306 n_err(_("\"%s\": Expansion buffer overflow\n"), name);
307 goto jleave;
308 case WRDE_BADCHAR:
309 case WRDE_SYNTAX:
310 default:
311 if (!(fexpm & FEXP_SILENT))
312 n_err(_("Syntax error in \"%s\"\n"), name);
313 goto jleave;
316 switch (we.we_wordc) {
317 case 1:
318 cp = savestr(we.we_wordv[0]);
319 break;
320 case 0:
321 if (!(fexpm & FEXP_SILENT))
322 n_err(_("\"%s\": No match\n"), name);
323 break;
324 default:
325 if (fexpm & FEXP_MULTIOK) {
326 size_t j, l;
328 for (l = 0, j = 0; j < we.we_wordc; ++j)
329 l += strlen(we.we_wordv[j]) + 1;
330 ++l;
331 cp = salloc(l);
332 for (l = 0, j = 0; j < we.we_wordc; ++j) {
333 size_t x = strlen(we.we_wordv[j]);
334 memcpy(cp + l, we.we_wordv[j], x);
335 l += x;
336 cp[l++] = ' ';
338 cp[l] = '\0';
339 } else if (!(fexpm & FEXP_SILENT))
340 n_err(_("\"%s\": Ambiguous\n"), name);
341 break;
343 jleave:
344 wordfree(&we);
345 NYD_LEAVE;
346 return cp;
348 #else /* HAVE_WORDEXP */
349 struct stat sbuf;
350 char xname[PATH_MAX], cmdbuf[PATH_MAX], /* also used for files */
351 *shellp, *cp = NULL;
352 int pivec[2], pid, l, waits;
353 NYD_ENTER;
355 if (pipe(pivec) < 0) {
356 n_perr(_("pipe"), 0);
357 goto jleave;
359 snprintf(cmdbuf, sizeof cmdbuf, "echo %s", name);
360 if ((shellp = ok_vlook(SHELL)) == NULL)
361 shellp = UNCONST(XSHELL);
362 pid = start_command(shellp, NULL, -1, pivec[1], "-c", cmdbuf, NULL, NULL);
363 if (pid < 0) {
364 close(pivec[0]);
365 close(pivec[1]);
366 goto jleave;
368 close(pivec[1]);
370 jagain:
371 l = read(pivec[0], xname, sizeof xname);
372 if (l < 0) {
373 if (errno == EINTR)
374 goto jagain;
375 n_perr(_("read"), 0);
376 close(pivec[0]);
377 goto jleave;
379 close(pivec[0]);
380 if (!wait_child(pid, &waits) && WTERMSIG(waits) != SIGPIPE) {
381 if (!(fexpm & FEXP_SILENT))
382 n_err(_("\"%s\": Expansion failed\n"), name);
383 goto jleave;
385 if (l == 0) {
386 if (!(fexpm & FEXP_SILENT))
387 n_err(_("\"%s\": No match\n"), name);
388 goto jleave;
390 if (l == sizeof xname) {
391 if (!(fexpm & FEXP_SILENT))
392 n_err(_("\"%s\": Expansion buffer overflow\n"), name);
393 goto jleave;
395 xname[l] = 0;
396 for (cp = xname + l - 1; *cp == '\n' && cp > xname; --cp)
398 cp[1] = '\0';
399 if (!(fexpm & FEXP_MULTIOK) && strchr(xname, ' ') != NULL &&
400 stat(xname, &sbuf) < 0) {
401 if (!(fexpm & FEXP_SILENT))
402 n_err(_("\"%s\": Ambiguous\n"), name);
403 cp = NULL;
404 goto jleave;
406 cp = savestr(xname);
407 jleave:
408 NYD_LEAVE;
409 return cp;
410 #endif /* !HAVE_WORDEXP */
413 static size_t
414 _length_of_line(char const *line, size_t linesize)
416 size_t i;
417 NYD2_ENTER;
419 /* Last character is always '\0' and was added by fgets() */
420 for (--linesize, i = 0; i < linesize; i++)
421 if (line[i] == '\n')
422 break;
423 i = (i < linesize) ? i + 1 : linesize;
424 NYD2_LEAVE;
425 return i;
428 static char *
429 _fgetline_byone(char **line, size_t *linesize, size_t *llen, FILE *fp,
430 int appendnl, size_t n SMALLOC_DEBUG_ARGS)
432 char *rv;
433 int c;
434 NYD2_ENTER;
436 assert(*linesize == 0 || *line != NULL);
437 for (rv = *line;;) {
438 if (*linesize <= LINESIZE || n >= *linesize - 128) {
439 *linesize += ((rv == NULL) ? LINESIZE + n + 1 : 256);
440 *line = rv = (srealloc)(rv, *linesize SMALLOC_DEBUG_ARGSCALL);
442 c = getc(fp);
443 if (c != EOF) {
444 rv[n++] = c;
445 rv[n] = '\0';
446 if (c == '\n')
447 break;
448 } else {
449 if (n > 0) {
450 if (appendnl) {
451 rv[n++] = '\n';
452 rv[n] = '\0';
454 break;
455 } else {
456 rv = NULL;
457 goto jleave;
461 if (llen)
462 *llen = n;
463 jleave:
464 NYD2_LEAVE;
465 return rv;
468 static void
469 makemessage(void)
471 NYD_ENTER;
472 if (msgCount == 0)
473 message_append(NULL);
474 setdot(message);
475 message[msgCount].m_size = 0;
476 message[msgCount].m_lines = 0;
477 NYD_LEAVE;
480 static enum okay
481 get_header(struct message *mp)
483 enum okay rv;
484 NYD_ENTER;
485 UNUSED(mp);
487 switch (mb.mb_type) {
488 case MB_FILE:
489 case MB_MAILDIR:
490 rv = OKAY;
491 break;
492 #ifdef HAVE_POP3
493 case MB_POP3:
494 rv = pop3_header(mp);
495 break;
496 #endif
497 #ifdef HAVE_IMAP
498 case MB_IMAP:
499 case MB_CACHE:
500 rv = imap_header(mp);
501 break;
502 #endif
503 case MB_VOID:
504 default:
505 rv = STOP;
506 break;
508 NYD_LEAVE;
509 return rv;
512 static bool_t
513 _file_lock(int fd, enum file_lock_type flt, off_t off, off_t len)
515 struct flock flp;
516 bool_t rv;
517 NYD2_ENTER;
519 memset(&flp, 0, sizeof flp);
521 switch (flt) {
522 default:
523 case FLT_READ: rv = F_RDLCK; break;
524 case FLT_WRITE: rv = F_WRLCK; break;
526 flp.l_type = rv;
527 flp.l_start = off;
528 flp.l_whence = SEEK_SET;
529 flp.l_len = len;
531 rv = (fcntl(fd, F_SETLK, &flp) != -1);
532 NYD2_LEAVE;
533 return rv;
536 #ifdef HAVE_DOTLOCK
537 static int
538 _dotlock_main(void)
540 struct dotlock_info di;
541 struct stat stb, fdstb;
542 char name[NAME_MAX];
543 enum dotlock_state dls;
544 char const *cp;
545 int fd;
546 enum file_lock_type flt;
547 NYD_ENTER;
549 /* Get the arguments "passed to us" */
550 flt = _dotlock_flt;
551 fd = _dotlock_fd;
552 UNUSED(fd);
553 di = *_dotlock_dip;
555 /* chdir(2)? */
556 jislink:
557 dls = DLS_CANT_CHDIR | DLS_ABANDON;
559 if ((cp = strrchr(di.di_file_name, '/')) != NULL) {
560 char const *fname = cp + 1;
562 while (PTRCMP(cp - 1, >, di.di_file_name) && cp[-1] == '/')
563 --cp;
564 cp = savestrbuf(di.di_file_name, PTR2SIZE(cp - di.di_file_name));
565 if (chdir(cp))
566 goto jmsg;
568 di.di_file_name = fname;
571 /* So we're here, but then again the file can be a symbolic link!
572 * This is however only true if we do not have realpath(3) available since
573 * that'll have resolved the path already otherwise; nonetheless, let
574 * readlink(2) be a precondition for dotlocking and keep this code */
575 if (lstat(cp = di.di_file_name, &stb) == -1)
576 goto jmsg;
577 if (S_ISLNK(stb.st_mode)) {
578 /* Use salloc() and hope we stay in builtin buffer.. */
579 char *x;
580 size_t i;
581 ssize_t sr;
583 for (x = NULL, i = PATH_MAX;; i += PATH_MAX) {
584 x = salloc(i +1);
585 sr = readlink(cp, x, i);
586 if (sr <= 0) {
587 dls = DLS_FISHY | DLS_ABANDON;
588 goto jmsg;
590 if (UICMP(z, sr, <, i)) {
591 x[sr] = '\0';
592 i = (size_t)sr;
593 break;
596 di.di_file_name = x;
597 goto jislink;
600 dls = DLS_FISHY | DLS_ABANDON;
602 /* Bail out if the file has changed its identity in the meanwhile */
603 if (fstat(fd, &fdstb) == -1 ||
604 fdstb.st_dev != stb.st_dev || fdstb.st_ino != stb.st_ino ||
605 fdstb.st_uid != stb.st_uid || fdstb.st_gid != stb.st_gid ||
606 fdstb.st_mode != stb.st_mode)
607 goto jmsg;
609 /* Be aware, even if the error is false! */
611 int i = snprintf(name, sizeof name, "%s.lock", di.di_file_name);
612 if (i < 0 || UICMP(z, NAME_MAX, <=, (uiz_t)i + 1 +1)) {
613 dls = DLS_NAMETOOLONG | DLS_ABANDON;
614 goto jmsg;
616 # ifdef HAVE_PATHCONF
617 else {
618 /* fd is a file, not portable to use for _PC_NAME_MAX */
619 long pc;
621 if ((pc = pathconf(".", _PC_NAME_MAX)) == -1 || pc <= (long)i + 1 +1) {
622 dls = DLS_NAMETOOLONG | DLS_ABANDON;
623 goto jmsg;
626 # endif
627 di.di_lock_name = name;
630 /* Ignore SIGPIPE, the child reacts upon EPIPE instead */
631 safe_signal(SIGPIPE, SIG_IGN);
633 /* We are in the directory of the mailbox for which we have to create
634 * a dotlock file for. We don't know wether we have realpath(3) available,
635 * and manually resolving the path is due especially given that S-nail
636 * supports the special "%:" syntax to warp any file into a "system
637 * mailbox"; there may also be multiple system mailbox directories...
638 * So what we do is that we fstat(2) the mailbox and check its UID and
639 * GID against that of our own process: if any of those mismatch we must
640 * either assume a directory we are not allowed to write in, or that we run
641 * via -u/$USER/%USER as someone else, in which case we favour our
642 * privilege-separated dotlock process */
643 if (stb.st_uid != user_id || stb.st_gid != group_id || access(".", W_OK)) {
644 char itoabuf[64];
645 char const *args[13];
647 snprintf(itoabuf, sizeof itoabuf, "%" PRIuZ, di.di_pollmsecs);
648 args[ 0] = PRIVSEP;
649 args[ 1] = (flt == FLT_READ ? "rdotlock" : "wdotlock");
650 args[ 2] = "mailbox"; args[ 3] = di.di_file_name;
651 args[ 4] = "name"; args[ 5] = di.di_lock_name;
652 args[ 6] = "hostname"; args[ 7] = di.di_hostname;
653 args[ 8] = "randstr"; args[ 9] = di.di_randstr;
654 args[10] = "pollmsecs"; args[11] = itoabuf;
655 args[12] = NULL;
656 execv(LIBEXECDIR "/" UAGENT "-privsep", UNCONST(args));
658 dls = DLS_NOEXEC;
659 write(STDOUT_FILENO, &dls, sizeof dls);
660 /* But fall through and try it with normal privileges! */
663 /* So let's try and call it ourselfs! Note that we don't block signals just
664 * like our privsep child does, the user will anyway be able to remove his
665 * file again, and if we're in -u/$USER mode then we are allowed to access
666 * the user's box: shall we leave behind a stale dotlock then at least we
667 * start a friendly human conversation. Since we cannot handle SIGKILL and
668 * SIGSTOP malicious things could happen whatever we do */
669 safe_signal(SIGHUP, SIG_IGN);
670 safe_signal(SIGINT, SIG_IGN);
671 safe_signal(SIGQUIT, SIG_IGN);
672 safe_signal(SIGTERM, SIG_IGN);
674 NYD;
675 dls = _dotlock_create(&di);
676 NYD;
678 /* Finally: notify our parent about the actual lock state.. */
679 jmsg:
680 write(STDOUT_FILENO, &dls, sizeof dls);
681 close(STDOUT_FILENO);
683 /* ..then eventually wait until we shall remove the lock again, which will
684 * be notified via the read returning */
685 if (dls == DLS_NONE) {
686 read(STDIN_FILENO, &dls, sizeof dls);
688 unlink(name);
690 NYD_LEAVE;
691 return EXIT_OK;
693 #endif /* HAVE_DOTLOCK */
695 #ifdef HAVE_SOCKETS
696 static long
697 xwrite(int fd, char const *data, size_t sz)
699 long rv = -1, wo;
700 size_t wt = 0;
701 NYD_ENTER;
703 do {
704 if ((wo = write(fd, data + wt, sz - wt)) < 0) {
705 if (errno == EINTR)
706 continue;
707 else
708 goto jleave;
710 wt += wo;
711 } while (wt < sz);
712 rv = (long)sz;
713 jleave:
714 NYD_LEAVE;
715 return rv;
717 #endif /* HAVE_SOCKETS */
719 FL char *
720 (fgetline)(char **line, size_t *linesize, size_t *cnt, size_t *llen, FILE *fp,
721 int appendnl SMALLOC_DEBUG_ARGS)
723 size_t i_llen, sz;
724 char *rv;
725 NYD2_ENTER;
727 if (cnt == NULL) {
728 /* Without count, we can't determine where the chars returned by fgets()
729 * end if there's no newline. We have to read one character by one */
730 rv = _fgetline_byone(line, linesize, llen, fp, appendnl, 0
731 SMALLOC_DEBUG_ARGSCALL);
732 goto jleave;
735 if ((rv = *line) == NULL || *linesize < LINESIZE)
736 *line = rv = (srealloc)(rv, *linesize = LINESIZE SMALLOC_DEBUG_ARGSCALL);
737 sz = (*linesize <= *cnt) ? *linesize : *cnt + 1;
738 if (sz <= 1 || fgets(rv, sz, fp) == NULL) {
739 /* Leave llen untouched; it is used to determine whether the last line
740 * was \n-terminated in some callers */
741 rv = NULL;
742 goto jleave;
745 i_llen = _length_of_line(rv, sz);
746 *cnt -= i_llen;
747 while (rv[i_llen - 1] != '\n') {
748 *line = rv = (srealloc)(rv, *linesize += 256 SMALLOC_DEBUG_ARGSCALL);
749 sz = *linesize - i_llen;
750 sz = (sz <= *cnt) ? sz : *cnt + 1;
751 if (sz <= 1 || fgets(rv + i_llen, sz, fp) == NULL) {
752 if (appendnl) {
753 rv[i_llen++] = '\n';
754 rv[i_llen] = '\0';
756 break;
758 sz = _length_of_line(rv + i_llen, sz);
759 i_llen += sz;
760 *cnt -= sz;
762 if (llen)
763 *llen = i_llen;
764 jleave:
765 NYD2_LEAVE;
766 return rv;
769 FL int
770 (readline_restart)(FILE *ibuf, char **linebuf, size_t *linesize, size_t n
771 SMALLOC_DEBUG_ARGS)
773 /* TODO readline_restart(): always *appends* LF just to strip it again;
774 * TODO should be configurable just as for fgetline(); ..or whatever.. */
775 int rv = -1;
776 long sz;
777 NYD2_ENTER;
779 clearerr(ibuf);
781 /* Interrupts will cause trouble if we are inside a stdio call. As this is
782 * only relevant if input is from tty, bypass it by read(), then */
783 if (fileno(ibuf) == 0 && (options & OPT_TTYIN)) {
784 assert(*linesize == 0 || *linebuf != NULL);
785 for (;;) {
786 if (*linesize <= LINESIZE || n >= *linesize - 128) {
787 *linesize += ((*linebuf == NULL) ? LINESIZE + n + 1 : 256);
788 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
790 jagain:
791 sz = read(0, *linebuf + n, *linesize - n - 1);
792 if (sz > 0) {
793 n += sz;
794 (*linebuf)[n] = '\0';
795 if (n > 0 && (*linebuf)[n - 1] == '\n')
796 break;
797 } else {
798 if (sz < 0 && errno == EINTR)
799 goto jagain;
800 if (n > 0) {
801 if ((*linebuf)[n - 1] != '\n') {
802 (*linebuf)[n++] = '\n';
803 (*linebuf)[n] = '\0';
805 break;
806 } else
807 goto jleave;
810 } else {
811 /* Not reading from standard input or standard input not a terminal. We
812 * read one char at a time as it is the only way to get lines with
813 * embedded NUL characters in standard stdio */
814 if (_fgetline_byone(linebuf, linesize, &n, ibuf, 1, n
815 SMALLOC_DEBUG_ARGSCALL) == NULL)
816 goto jleave;
818 if (n > 0 && (*linebuf)[n - 1] == '\n')
819 (*linebuf)[--n] = '\0';
820 rv = (int)n;
821 jleave:
822 NYD2_LEAVE;
823 return rv;
826 FL int
827 (readline_input)(char const *prompt, bool_t nl_escape, char **linebuf,
828 size_t *linesize, char const *string SMALLOC_DEBUG_ARGS)
830 /* TODO readline: linebuf pool! */
831 FILE *ifile = (_fio_input != NULL) ? _fio_input : stdin;
832 bool_t doprompt, dotty;
833 int n, nold;
834 NYD2_ENTER;
836 doprompt = (!(pstate & PS_SOURCING) && (options & OPT_INTERACTIVE));
837 dotty = (doprompt && !ok_blook(line_editor_disable));
838 if (!doprompt)
839 prompt = NULL;
840 else if (prompt == NULL)
841 prompt = getprompt();
843 /* Ensure stdout is flushed first anyway */
844 if (!dotty && prompt == NULL)
845 fflush(stdout);
847 for (nold = n = 0;;) {
848 if (dotty) {
849 assert(ifile == stdin);
850 if (string != NULL && (n = (int)strlen(string)) > 0) {
851 if (*linesize > 0)
852 *linesize += n +1;
853 else
854 *linesize = (size_t)n + LINESIZE +1;
855 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
856 memcpy(*linebuf, string, (size_t)n +1);
858 string = NULL;
859 /* TODO if nold>0, don't redisplay the entire line!
860 * TODO needs complete redesign ... */
861 n = (tty_readline)(prompt, linebuf, linesize, n
862 SMALLOC_DEBUG_ARGSCALL);
863 } else {
864 if (prompt != NULL) {
865 if (*prompt != '\0')
866 fputs(prompt, stdout);
867 fflush(stdout);
869 n = (readline_restart)(ifile, linebuf, linesize, n
870 SMALLOC_DEBUG_ARGSCALL);
872 if (n > 0 && nold > 0) {
873 int i = 0;
874 char const *cp = *linebuf + nold;
876 while (blankspacechar(*cp) && nold + i < n)
877 ++cp, ++i;
878 if (i > 0) {
879 memmove(*linebuf + nold, cp, n - nold - i);
880 n -= i;
881 (*linebuf)[n] = '\0';
885 if (n <= 0)
886 break;
888 /* POSIX says:
889 * An unquoted <backslash> at the end of a command line shall
890 * be discarded and the next line shall continue the command */
891 if (!nl_escape || n == 0 || (*linebuf)[n - 1] != '\\')
892 break;
893 (*linebuf)[nold = --n] = '\0';
894 if (prompt != NULL && *prompt != '\0')
895 prompt = ".. "; /* XXX PS2 .. */
898 if (n >= 0 && (options & OPT_D_VV))
899 n_err(_("%s %d bytes <%.*s>\n"),
900 ((pstate & PS_LOADING) ? "LOAD"
901 : (pstate & PS_SOURCING) ? "SOURCE" : "READ"),
902 n, n, *linebuf);
903 NYD2_LEAVE;
904 return n;
907 FL char *
908 readstr_input(char const *prompt, char const *string)
910 /* FIXME readstr_input: leaks on sigjmp without linepool */
911 size_t linesize = 0;
912 char *linebuf = NULL, *rv = NULL;
913 int n;
914 NYD2_ENTER;
916 n = readline_input(prompt, FAL0, &linebuf, &linesize, string);
917 if (n > 0)
918 rv = savestrbuf(linebuf, (size_t)n + 1);
920 if (linebuf != NULL)
921 free(linebuf);
922 NYD2_LEAVE;
923 return rv;
926 FL void
927 setptr(FILE *ibuf, off_t offset)
929 struct message self;
930 char *cp, *linebuf = NULL;
931 char const *cp2;
932 int c, maybe = 1, inhead = 0, selfcnt = 0;
933 size_t linesize = 0, filesize, cnt;
934 NYD_ENTER;
936 memset(&self, 0, sizeof self);
937 self.m_flag = MUSED | MNEW | MNEWEST;
938 filesize = mailsize - offset;
939 offset = ftell(mb.mb_otf);
941 for (;;) {
942 if (fgetline(&linebuf, &linesize, &filesize, &cnt, ibuf, 0) == NULL) {
943 self.m_xsize = self.m_size;
944 self.m_xlines = self.m_lines;
945 self.m_have = HAVE_HEADER | HAVE_BODY;
946 if (selfcnt > 0)
947 message_append(&self);
948 makemessage();
949 if (linebuf)
950 free(linebuf);
951 break;
954 #ifdef notdef
955 if (linebuf[0] == '\0')
956 linebuf[0] = '.';
957 #endif
958 /* XXX Convert CRLF to LF; this should be rethought in that
959 * XXX CRLF input should possibly end as CRLF output? */
960 if (cnt >= 2 && linebuf[cnt - 1] == '\n' && linebuf[cnt - 2] == '\r')
961 linebuf[--cnt - 1] = '\n';
962 fwrite(linebuf, sizeof *linebuf, cnt, mb.mb_otf);
963 if (ferror(mb.mb_otf)) {
964 n_perr(_("/tmp"), 0);
965 exit(EXIT_ERR);
967 if (linebuf[cnt - 1] == '\n')
968 linebuf[cnt - 1] = '\0';
969 if (maybe && linebuf[0] == 'F' && is_head(linebuf, cnt, FAL0)) {
970 /* TODO char date[FROM_DATEBUF];
971 * TODO extract_date_from_from_(linebuf, cnt, date);
972 * TODO self.m_time = 10000; */
973 self.m_xsize = self.m_size;
974 self.m_xlines = self.m_lines;
975 self.m_have = HAVE_HEADER | HAVE_BODY;
976 if (selfcnt++ > 0)
977 message_append(&self);
978 msgCount++;
979 self.m_flag = MUSED | MNEW | MNEWEST;
980 self.m_size = 0;
981 self.m_lines = 0;
982 self.m_block = mailx_blockof(offset);
983 self.m_offset = mailx_offsetof(offset);
984 inhead = 1;
985 } else if (linebuf[0] == 0) {
986 inhead = 0;
987 } else if (inhead) {
988 for (cp = linebuf, cp2 = "status";; ++cp) {
989 if ((c = *cp2++) == 0) {
990 while (c = *cp++, whitechar(c))
992 if (cp[-1] != ':')
993 break;
994 while ((c = *cp++) != '\0')
995 if (c == 'R')
996 self.m_flag |= MREAD;
997 else if (c == 'O')
998 self.m_flag &= ~MNEW;
999 break;
1001 if (*cp != c && *cp != upperconv(c))
1002 break;
1004 for (cp = linebuf, cp2 = "x-status";; ++cp) {
1005 if ((c = *cp2++) == 0) {
1006 while ((c = *cp++, whitechar(c)))
1008 if (cp[-1] != ':')
1009 break;
1010 while ((c = *cp++) != '\0')
1011 if (c == 'F')
1012 self.m_flag |= MFLAGGED;
1013 else if (c == 'A')
1014 self.m_flag |= MANSWERED;
1015 else if (c == 'T')
1016 self.m_flag |= MDRAFTED;
1017 break;
1019 if (*cp != c && *cp != upperconv(c))
1020 break;
1023 offset += cnt;
1024 self.m_size += cnt;
1025 ++self.m_lines;
1026 maybe = linebuf[0] == 0;
1028 NYD_LEAVE;
1031 FL int
1032 putline(FILE *obuf, char *linebuf, size_t cnt)
1034 int rv = -1;
1035 NYD_ENTER;
1037 fwrite(linebuf, sizeof *linebuf, cnt, obuf);
1038 putc('\n', obuf);
1039 if (!ferror(obuf))
1040 rv = (int)(cnt + 1);
1041 NYD_LEAVE;
1042 return rv;
1045 FL FILE *
1046 setinput(struct mailbox *mp, struct message *m, enum needspec need)
1048 FILE *rv = NULL;
1049 enum okay ok = STOP;
1050 NYD_ENTER;
1052 switch (need) {
1053 case NEED_HEADER:
1054 ok = (m->m_have & HAVE_HEADER) ? OKAY : get_header(m);
1055 break;
1056 case NEED_BODY:
1057 ok = (m->m_have & HAVE_BODY) ? OKAY : get_body(m);
1058 break;
1059 case NEED_UNSPEC:
1060 ok = OKAY;
1061 break;
1063 if (ok != OKAY)
1064 goto jleave;
1066 fflush(mp->mb_otf);
1067 if (fseek(mp->mb_itf, (long)mailx_positionof(m->m_block, m->m_offset),
1068 SEEK_SET) < 0) {
1069 n_perr(_("fseek"), 0);
1070 n_panic(_("temporary file seek"));
1072 rv = mp->mb_itf;
1073 jleave:
1074 NYD_LEAVE;
1075 return rv;
1078 FL void
1079 message_reset(void)
1081 NYD_ENTER;
1082 if (message != NULL) {
1083 free(message);
1084 message = NULL;
1086 msgCount = 0;
1087 _message_space = 0;
1088 NYD_LEAVE;
1091 FL void
1092 message_append(struct message *mp)
1094 NYD_ENTER;
1095 if (UICMP(z, msgCount + 1, >=, _message_space)) {
1096 /* XXX remove _message_space magics (or use s_Vector) */
1097 _message_space = (_message_space >= 128 && _message_space <= 1000000)
1098 ? _message_space << 1 : _message_space + 64;
1099 message = srealloc(message, _message_space * sizeof *message);
1101 if (msgCount > 0) {
1102 if (mp != NULL)
1103 message[msgCount - 1] = *mp;
1104 else
1105 memset(message + msgCount - 1, 0, sizeof *message);
1107 NYD_LEAVE;
1110 FL bool_t
1111 message_match(struct message *mp, struct search_expr const *sep,
1112 bool_t with_headers)
1114 char **line;
1115 size_t *linesize, cnt;
1116 FILE *fp;
1117 bool_t rv = FAL0;
1118 NYD_ENTER;
1120 if ((fp = Ftmp(NULL, "mpmatch", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
1121 NULL)
1122 goto j_leave;
1124 if (sendmp(mp, fp, NULL, NULL, SEND_TOSRCH, NULL) < 0)
1125 goto jleave;
1126 fflush_rewind(fp);
1128 cnt = fsize(fp);
1129 line = &termios_state.ts_linebuf; /* XXX line pool */
1130 linesize = &termios_state.ts_linesize; /* XXX line pool */
1132 if (!with_headers)
1133 while (fgetline(line, linesize, &cnt, NULL, fp, 0))
1134 if (**line == '\n')
1135 break;
1137 while (fgetline(line, linesize, &cnt, NULL, fp, 0)) {
1138 #ifdef HAVE_REGEX
1139 if (sep->ss_sexpr == NULL) {
1140 if (regexec(&sep->ss_regex, *line, 0,NULL, 0) == REG_NOMATCH)
1141 continue;
1142 } else
1143 #endif
1144 if (!substr(*line, sep->ss_sexpr))
1145 continue;
1146 rv = TRU1;
1147 break;
1150 jleave:
1151 Fclose(fp);
1152 j_leave:
1153 NYD_LEAVE;
1154 return rv;
1157 FL struct message *
1158 setdot(struct message *mp)
1160 NYD_ENTER;
1161 if (dot != mp) {
1162 prevdot = dot;
1163 pstate &= ~PS_DID_PRINT_DOT;
1165 dot = mp;
1166 uncollapse1(dot, 0);
1167 NYD_LEAVE;
1168 return dot;
1171 FL int
1172 rm(char const *name)
1174 struct stat sb;
1175 int rv = -1;
1176 NYD_ENTER;
1178 if (stat(name, &sb) < 0)
1180 else if (!S_ISREG(sb.st_mode))
1181 errno = EISDIR;
1182 else
1183 rv = unlink(name);
1184 NYD_LEAVE;
1185 return rv;
1188 FL off_t
1189 fsize(FILE *iob)
1191 struct stat sbuf;
1192 off_t rv;
1193 NYD_ENTER;
1195 rv = (fstat(fileno(iob), &sbuf) < 0) ? 0 : sbuf.st_size;
1196 NYD_LEAVE;
1197 return rv;
1200 FL char *
1201 fexpand(char const *name, enum fexp_mode fexpm)
1203 char cbuf[PATH_MAX];
1204 char const *res;
1205 struct str s;
1206 bool_t dyn;
1207 NYD_ENTER;
1209 /* The order of evaluation is "%" and "#" expand into constants.
1210 * "&" can expand into "+". "+" can expand into shell meta characters.
1211 * Shell meta characters expand into constants.
1212 * This way, we make no recursive expansion */
1213 if ((fexpm & FEXP_NSHORTCUT) || (res = shortcut_expand(name)) == NULL)
1214 res = UNCONST(name);
1216 if (fexpm & FEXP_SHELL) {
1217 dyn = FAL0;
1218 goto jshell;
1220 jnext:
1221 dyn = FAL0;
1222 switch (*res) {
1223 case '%':
1224 if (res[1] == ':' && res[2] != '\0') {
1225 res = &res[2];
1226 goto jnext;
1228 _findmail(cbuf, sizeof cbuf, (res[1] != '\0' ? res + 1 : myname),
1229 (res[1] != '\0' || (options & OPT_u_FLAG)));
1230 res = cbuf;
1231 goto jislocal;
1232 case '#':
1233 if (res[1] != '\0')
1234 break;
1235 if (prevfile[0] == '\0') {
1236 n_err(_("No previous file\n"));
1237 res = NULL;
1238 goto jleave;
1240 res = prevfile;
1241 goto jislocal;
1242 case '&':
1243 if (res[1] == '\0') {
1244 if ((res = ok_vlook(MBOX)) == NULL)
1245 res = UNCONST("~/mbox"); /* XXX no magics (POSIX though) */
1246 else if (res[0] != '&' || res[1] != '\0')
1247 goto jnext;
1249 break;
1252 if (res[0] == '@' && which_protocol(mailname) == PROTO_IMAP) {
1253 res = str_concat_csvl(&s, protbase(mailname), "/", res + 1, NULL)->s;
1254 dyn = TRU1;
1257 if (res[0] == '+' && getfold(cbuf, sizeof cbuf)) {
1258 size_t i = strlen(cbuf);
1260 res = str_concat_csvl(&s, cbuf,
1261 ((i > 0 && cbuf[i - 1] == '/') ? "" : "/"), res + 1, NULL)->s;
1262 dyn = TRU1;
1264 if (res[0] == '%' && res[1] == ':') {
1265 res += 2;
1266 goto jnext;
1270 /* Catch the most common shell meta character */
1271 jshell:
1272 if (res[0] == '~' && (res[1] == '/' || res[1] == '\0')) {
1273 res = str_concat_csvl(&s, homedir, res + 1, NULL)->s;
1274 dyn = TRU1;
1276 if (anyof(res, "|&;<>{}()[]*?$`'\"\\"))
1277 switch (which_protocol(res)) {
1278 case PROTO_FILE:
1279 case PROTO_MAILDIR:
1280 res = (fexpm & FEXP_NSHELL) ? _shvar_exp(res) : _globname(res, fexpm);
1281 dyn = TRU1;
1282 goto jleave;
1283 default:
1284 break;
1286 jislocal:
1287 if (fexpm & FEXP_LOCAL)
1288 switch (which_protocol(res)) {
1289 case PROTO_FILE:
1290 case PROTO_MAILDIR:
1291 break;
1292 default:
1293 n_err(_("Not a local file or directory: \"%s\"\n"), name);
1294 res = NULL;
1295 break;
1297 jleave:
1298 if (res && !dyn)
1299 res = savestr(res);
1300 NYD_LEAVE;
1301 return UNCONST(res);
1304 FL char *
1305 fexpand_nshell_quote(char const *name)
1307 size_t i, j;
1308 char *rv, c;
1309 NYD_ENTER;
1311 for (i = j = 0; (c = name[i]) != '\0'; ++i)
1312 if (c == '\\')
1313 ++j;
1315 if (j == 0)
1316 rv = savestrbuf(name, i);
1317 else {
1318 rv = salloc(i + j +1);
1319 for (i = j = 0; (c = name[i]) != '\0'; ++i) {
1320 rv[j++] = c;
1321 if (c == '\\')
1322 rv[j++] = c;
1324 rv[j] = '\0';
1326 NYD_LEAVE;
1327 return rv;
1330 FL bool_t
1331 var_folder_updated(char const *name, char **store)
1333 char rv = TRU1;
1334 char *folder, *unres = NULL, *res = NULL;
1335 NYD_ENTER;
1337 if ((folder = UNCONST(name)) == NULL)
1338 goto jleave;
1340 /* Expand the *folder*; skip %: prefix for simplicity of use */
1341 /* XXX This *only* works because we do NOT
1342 * XXX update environment variables via the "set" mechanism */
1343 if (folder[0] == '%' && folder[1] == ':')
1344 folder += 2;
1345 if ((folder = fexpand(folder, FEXP_FULL)) == NULL) /* XXX error? */
1346 goto jleave;
1348 switch (which_protocol(folder)) {
1349 case PROTO_POP3:
1350 n_err(_("*folder* cannot be set to a flat, readonly POP3 account\n"));
1351 rv = FAL0;
1352 goto jleave;
1353 case PROTO_IMAP:
1354 /* Simply assign what we have, even including `%:' prefix */
1355 if (folder != name)
1356 goto jvcopy;
1357 goto jleave;
1358 default:
1359 /* Further expansion desired */
1360 break;
1363 /* All non-absolute paths are relative to our home directory */
1364 if (*folder != '/') {
1365 size_t l1 = strlen(homedir), l2 = strlen(folder);
1366 unres = ac_alloc(l1 + l2 + 1 +1);
1367 memcpy(unres, homedir, l1);
1368 unres[l1] = '/';
1369 memcpy(unres + l1 + 1, folder, l2);
1370 unres[l1 + 1 + l2] = '\0';
1371 folder = unres;
1374 /* Since lex.c:_update_mailname() uses realpath(3) if available to
1375 * avoid that we loose track of our currently open folder in case we
1376 * chdir away, but still checks the leading path portion against
1377 * getfold() to be able to abbreviate to the +FOLDER syntax if
1378 * possible, we need to realpath(3) the folder, too */
1379 #ifdef HAVE_REALPATH
1380 res = ac_alloc(PATH_MAX +1);
1381 if (realpath(folder, res) == NULL)
1382 n_err(_("Can't canonicalize \"%s\"\n"), folder);
1383 else
1384 folder = res;
1385 #endif
1387 jvcopy:
1388 *store = sstrdup(folder);
1390 if (res != NULL)
1391 ac_free(res);
1392 if (unres != NULL)
1393 ac_free(unres);
1394 jleave:
1395 NYD_LEAVE;
1396 return rv;
1399 FL bool_t
1400 getfold(char *name, size_t size)
1402 char const *folder;
1403 NYD_ENTER;
1405 if ((folder = ok_vlook(folder)) != NULL)
1406 n_strlcpy(name, folder, size);
1407 NYD_LEAVE;
1408 return (folder != NULL);
1411 FL char const *
1412 getdeadletter(void) /* XXX should that be in auxlily.c? */
1414 char const *cp;
1415 NYD_ENTER;
1417 if ((cp = ok_vlook(DEAD)) == NULL || (cp = fexpand(cp, FEXP_LOCAL)) == NULL)
1418 cp = fexpand("~/dead.letter", FEXP_LOCAL | FEXP_SHELL);
1419 else if (*cp != '/') {
1420 size_t sz = strlen(cp) + 2 +1;
1421 char *buf = ac_alloc(sz);
1423 snprintf(buf, sz, "~/%s", cp);
1424 cp = fexpand(buf, FEXP_LOCAL | FEXP_SHELL);
1425 ac_free(buf);
1428 if (cp == NULL)
1429 cp = "dead.letter"; /* XXX magic -> nail.h (POSIX thing though) */
1430 NYD_LEAVE;
1431 return cp;
1434 FL enum okay
1435 get_body(struct message *mp)
1437 enum okay rv;
1438 NYD_ENTER;
1439 UNUSED(mp);
1441 switch (mb.mb_type) {
1442 case MB_FILE:
1443 case MB_MAILDIR:
1444 rv = OKAY;
1445 break;
1446 #ifdef HAVE_POP3
1447 case MB_POP3:
1448 rv = pop3_body(mp);
1449 break;
1450 #endif
1451 #ifdef HAVE_IMAP
1452 case MB_IMAP:
1453 case MB_CACHE:
1454 rv = imap_body(mp);
1455 break;
1456 #endif
1457 case MB_VOID:
1458 default:
1459 rv = STOP;
1460 break;
1462 NYD_LEAVE;
1463 return rv;
1466 FL bool_t
1467 file_lock(int fd, enum file_lock_type flt, off_t off, off_t len,
1468 size_t pollmsecs)
1470 size_t tries;
1471 bool_t rv;
1472 NYD_ENTER;
1474 for (tries = 0; tries <= FILE_LOCK_TRIES; ++tries)
1475 if ((rv = _file_lock(fd, flt, off, len)) || pollmsecs == 0)
1476 break;
1477 else
1478 sleep(1); /* TODO pollmsecs -> use finer grain */
1479 NYD_LEAVE;
1480 return rv;
1483 FL FILE *
1484 dot_lock(char const *fname, int fd, enum file_lock_type flt,
1485 off_t off, off_t len, size_t pollmsecs)
1487 #undef _DOMSG
1488 #ifdef HAVE_DOTLOCK
1489 # define _DOMSG() n_err(_("Creating dotlock for \"%s\" "), fname)
1490 #else
1491 # define _DOMSG() n_err(_("Trying to lock file \"%s\" "), fname)
1492 #endif
1494 #ifdef HAVE_DOTLOCK
1495 int cpipe[2];
1496 struct dotlock_info di;
1497 enum dotlock_state dls;
1498 char const *emsg = NULL;
1499 #endif
1500 int UNINIT(serrno, 0);
1501 union {size_t tries; int (*ptf)(void); char const *sh; ssize_t r;} u;
1502 bool_t flocked, didmsg = FAL0;
1503 FILE *rv = NULL;
1504 NYD_ENTER;
1506 if (options & OPT_D_VV) {
1507 _DOMSG();
1508 didmsg = TRUM1;
1511 flocked = FAL0;
1512 for (u.tries = 0; !_file_lock(fd, flt, off, len);)
1513 switch ((serrno = errno)) {
1514 case EACCES:
1515 case EAGAIN:
1516 case ENOLCK:
1517 if (pollmsecs > 0 && ++u.tries < FILE_LOCK_TRIES) {
1518 if (!didmsg)
1519 _DOMSG();
1520 n_err(".");
1521 didmsg = TRUM1;
1522 sleep(1); /* TODO pollmsecs -> use finer grain */
1523 continue;
1525 /* FALLTHRU */
1526 default:
1527 goto jleave;
1529 flocked = TRU1;
1531 #ifndef HAVE_DOTLOCK
1532 jleave:
1533 if (didmsg == TRUM1)
1534 n_err("\n");
1535 if (flocked)
1536 rv = (FILE*)-1;
1537 else
1538 errno = serrno;
1539 NYD_LEAVE;
1540 return rv;
1542 #else
1543 /* Create control-pipe for our dot file locker process, which will remove
1544 * the lock and terminate once the pipe is closed, for whatever reason */
1545 if (pipe_cloexec(cpipe) == -1) {
1546 serrno = errno;
1547 emsg = N_(" Can't create file lock control pipe\n");
1548 goto jemsg;
1551 /* And the locker process itself; it'll be a (rather cheap) thread only
1552 * unless the lock has to be placed in the system spool and we have our
1553 * privilege-separated dotlock program available, in which case that will be
1554 * executed and do "it" with changed group-id */
1555 di.di_file_name = fname;
1556 di.di_pollmsecs = pollmsecs;
1557 /* Initialize some more stuff; query the two strings in the parent in order
1558 * to cache the result of the former and anyway minimalize child page-ins.
1559 * Especially uname(3) may hang for multiple seconds when it is called the
1560 * first time! */
1561 di.di_hostname = nodename(FAL0);
1562 di.di_randstr = getrandstring(16);
1563 _dotlock_flt = flt;
1564 _dotlock_fd = fd;
1565 _dotlock_dip = &di;
1567 u.ptf = &_dotlock_main;
1568 rv = Popen((char*)-1, "W", u.sh, NULL, cpipe[1]);
1569 serrno = errno;
1571 close(cpipe[1]);
1572 if (rv == NULL) {
1573 close(cpipe[0]);
1574 emsg = N_(" Can't create file lock process\n");
1575 goto jemsg;
1578 /* Let's check wether we were able to create the dotlock file */
1579 for (;;) {
1580 u.r = read(cpipe[0], &dls, sizeof dls);
1581 if (UICMP(z, u.r, !=, sizeof dls)) {
1582 serrno = (u.r != -1) ? EAGAIN : errno;
1583 dls = DLS_DUNNO | DLS_ABANDON;
1584 } else
1585 serrno = 0;
1587 if (dls & DLS_ABANDON)
1588 close(cpipe[0]);
1590 switch (dls & ~DLS_ABANDON) {
1591 case DLS_NONE:
1592 goto jleave;
1593 case DLS_CANT_CHDIR:
1594 if (options & OPT_D_V)
1595 emsg = N_(" Can't change to directory! Please check permissions\n");
1596 serrno = EACCES;
1597 break;
1598 case DLS_NAMETOOLONG:
1599 emsg = N_("Resulting dotlock filename would be too long\n");
1600 serrno = EACCES;
1601 break;
1602 case DLS_NOPERM:
1603 if (options & OPT_D_V)
1604 emsg = N_(" Can't create a lock file! Please check permissions\n"
1605 " (Maybe setting *dotlock-ignore-error* variable helps.)\n");
1606 serrno = EACCES;
1607 break;
1608 case DLS_NOEXEC:
1609 if (options & OPT_D_V)
1610 emsg = N_(" Can't find privilege-separated file lock program\n");
1611 serrno = ENOENT;
1612 break;
1613 case DLS_PRIVFAILED:
1614 emsg = N_(" Privilege-separated file lock program can't change "
1615 "privileges\n");
1616 serrno = EPERM;
1617 break;
1618 case DLS_EXIST:
1619 emsg = N_(" It seems there is a stale dotlock file?\n"
1620 " Please remove the lock file manually, then retry\n");
1621 serrno = EEXIST;
1622 break;
1623 case DLS_FISHY:
1624 emsg = N_(" Fishy! Is someone trying to \"steal\" foreign files?\n"
1625 " Please check the mailbox file etc. manually, then retry\n");
1626 serrno = EAGAIN; /* ? Hack to ignore *dotlock-ignore-error* xxx */
1627 break;
1628 default:
1629 case DLS_DUNNO:
1630 emsg = N_(" Unspecified dotlock file control process error.\n"
1631 " Like broken I/O pipe; this one is unlikely to happen\n");
1632 if (serrno != EAGAIN)
1633 serrno = EINVAL;
1634 break;
1635 case DLS_PING:
1636 if (!didmsg)
1637 _DOMSG();
1638 n_err(".");
1639 didmsg = TRUM1;
1640 continue;
1643 if (emsg != NULL) {
1644 if (!didmsg) {
1645 _DOMSG();
1646 didmsg = TRUM1;
1648 if (didmsg == TRUM1)
1649 n_err("\n");
1650 didmsg = TRU1;
1651 n_err(V_(emsg));
1652 emsg = NULL;
1655 if (dls & DLS_ABANDON) {
1656 Pclose(rv, FAL0);
1657 rv = NULL;
1658 break;
1662 jleave:
1663 if (didmsg == TRUM1)
1664 n_err("\n");
1665 if (rv == NULL) {
1666 if (flocked && serrno != EAGAIN && serrno != EEXIST &&
1667 ok_blook(dotlock_ignore_error))
1668 rv = (FILE*)-1;
1669 else
1670 errno = serrno;
1672 NYD_LEAVE;
1673 return rv;
1674 jemsg:
1675 if (!didmsg)
1676 _DOMSG();
1677 n_err("\n");
1678 didmsg = TRU1;
1679 n_err(V_(emsg));
1680 goto jleave;
1681 #endif /* HAVE_DOTLOCK */
1682 #undef _DOMSG
1685 #ifdef HAVE_SOCKETS
1686 FL int
1687 sclose(struct sock *sp)
1689 int i;
1690 NYD_ENTER;
1692 i = sp->s_fd;
1693 sp->s_fd = -1;
1694 /* TODO NOTE: we MUST NOT close the descriptor 0 here...
1695 * TODO of course this should be handled in a VMAILFS->open() .s_fd=-1,
1696 * TODO but unfortunately it isn't yet */
1697 if (i <= 0)
1698 i = 0;
1699 else {
1700 if (sp->s_onclose != NULL)
1701 (*sp->s_onclose)();
1702 if (sp->s_wbuf != NULL)
1703 free(sp->s_wbuf);
1704 # ifdef HAVE_OPENSSL
1705 if (sp->s_use_ssl) {
1706 void *s_ssl = sp->s_ssl;
1708 sp->s_ssl = NULL;
1709 sp->s_use_ssl = 0;
1710 while (!SSL_shutdown(s_ssl)) /* XXX proper error handling;signals! */
1712 SSL_free(s_ssl);
1714 # endif
1715 i = close(i);
1717 NYD_LEAVE;
1718 return i;
1721 FL enum okay
1722 swrite(struct sock *sp, char const *data)
1724 enum okay rv;
1725 NYD2_ENTER;
1727 rv = swrite1(sp, data, strlen(data), 0);
1728 NYD2_LEAVE;
1729 return rv;
1732 FL enum okay
1733 swrite1(struct sock *sp, char const *data, int sz, int use_buffer)
1735 enum okay rv = STOP;
1736 int x;
1737 NYD2_ENTER;
1739 if (use_buffer > 0) {
1740 int di;
1742 if (sp->s_wbuf == NULL) {
1743 sp->s_wbufsize = 4096;
1744 sp->s_wbuf = smalloc(sp->s_wbufsize);
1745 sp->s_wbufpos = 0;
1747 while (sp->s_wbufpos + sz > sp->s_wbufsize) {
1748 di = sp->s_wbufsize - sp->s_wbufpos;
1749 sz -= di;
1750 if (sp->s_wbufpos > 0) {
1751 memcpy(sp->s_wbuf + sp->s_wbufpos, data, di);
1752 rv = swrite1(sp, sp->s_wbuf, sp->s_wbufsize, -1);
1753 } else
1754 rv = swrite1(sp, data, sp->s_wbufsize, -1);
1755 if (rv != OKAY)
1756 goto jleave;
1757 data += di;
1758 sp->s_wbufpos = 0;
1760 if (sz == sp->s_wbufsize) {
1761 rv = swrite1(sp, data, sp->s_wbufsize, -1);
1762 if (rv != OKAY)
1763 goto jleave;
1764 } else if (sz) {
1765 memcpy(sp->s_wbuf+ sp->s_wbufpos, data, sz);
1766 sp->s_wbufpos += sz;
1768 rv = OKAY;
1769 goto jleave;
1770 } else if (use_buffer == 0 && sp->s_wbuf != NULL && sp->s_wbufpos > 0) {
1771 x = sp->s_wbufpos;
1772 sp->s_wbufpos = 0;
1773 if ((rv = swrite1(sp, sp->s_wbuf, x, -1)) != OKAY)
1774 goto jleave;
1776 if (sz == 0) {
1777 rv = OKAY;
1778 goto jleave;
1781 # ifdef HAVE_OPENSSL
1782 if (sp->s_use_ssl) {
1783 jssl_retry:
1784 x = SSL_write(sp->s_ssl, data, sz);
1785 if (x < 0) {
1786 switch (SSL_get_error(sp->s_ssl, x)) {
1787 case SSL_ERROR_WANT_READ:
1788 case SSL_ERROR_WANT_WRITE:
1789 goto jssl_retry;
1792 } else
1793 # endif
1795 x = xwrite(sp->s_fd, data, sz);
1797 if (x != sz) {
1798 char o[512];
1799 snprintf(o, sizeof o, "%s write error",
1800 (sp->s_desc ? sp->s_desc : "socket"));
1801 # ifdef HAVE_OPENSSL
1802 if (sp->s_use_ssl)
1803 ssl_gen_err("%s", o);
1804 else
1805 # endif
1806 n_perr(o, 0);
1807 if (x < 0)
1808 sclose(sp);
1809 rv = STOP;
1810 goto jleave;
1812 rv = OKAY;
1813 jleave:
1814 NYD2_LEAVE;
1815 return rv;
1818 # if defined HAVE_GETADDRINFO || defined HAVE_SSL
1819 static sigjmp_buf __sopen_actjmp; /* TODO someday, we won't need it no more */
1820 static int __sopen_sig; /* TODO someday, we won't need it no more */
1821 static void
1822 __sopen_onsig(int sig) /* TODO someday, we won't need it no more */
1824 NYD_X; /* Signal handler */
1825 __sopen_sig = sig;
1826 siglongjmp(__sopen_actjmp, 1);
1828 # endif
1830 FL bool_t
1831 sopen(struct sock *sp, struct url *urlp) /* TODO sighandling; refactor */
1833 # ifdef HAVE_SO_SNDTIMEO
1834 struct timeval tv;
1835 # endif
1836 # ifdef HAVE_SO_LINGER
1837 struct linger li;
1838 # endif
1839 # ifdef HAVE_GETADDRINFO
1840 char hbuf[NI_MAXHOST];
1841 struct addrinfo hints, *res0, *res;
1842 int volatile errval = 0 /* pacify CC */;
1843 # else
1844 struct sockaddr_in servaddr;
1845 struct in_addr **pptr;
1846 struct hostent *hp;
1847 struct servent *ep;
1848 # endif
1849 # if defined HAVE_GETADDRINFO || defined HAVE_SSL
1850 sighandler_type volatile ohup, oint;
1851 char const * volatile serv;
1852 int volatile sofd = -1;
1853 # else
1854 char const *serv;
1855 int sofd = -1; /* TODO may leak without getaddrinfo(3) support (pre v15.0) */
1856 # endif
1857 NYD_ENTER;
1859 /* Connect timeouts after 30 seconds XXX configurable */
1860 # ifdef HAVE_SO_SNDTIMEO
1861 tv.tv_sec = 30;
1862 tv.tv_usec = 0;
1863 # endif
1864 serv = (urlp->url_port != NULL) ? urlp->url_port : urlp->url_proto;
1866 if (options & OPT_VERB)
1867 n_err(_("Resolving host \"%s:%s\" ... "),
1868 urlp->url_host.s, serv);
1870 # ifdef HAVE_GETADDRINFO
1871 res0 = NULL;
1873 hold_sigs();
1874 __sopen_sig = 0;
1875 ohup = safe_signal(SIGHUP, &__sopen_onsig);
1876 oint = safe_signal(SIGINT, &__sopen_onsig);
1877 if (sigsetjmp(__sopen_actjmp, 0)) {
1878 n_err("%s\n",
1879 (__sopen_sig == SIGHUP ? _("Hangup") : _("Interrupted")));
1880 if (sofd >= 0) {
1881 close(sofd);
1882 sofd = -1;
1884 goto jjumped;
1886 rele_sigs();
1888 for (;;) {
1889 memset(&hints, 0, sizeof hints);
1890 hints.ai_socktype = SOCK_STREAM;
1891 if ((errval = getaddrinfo(urlp->url_host.s, serv, &hints, &res0)) == 0)
1892 break;
1894 if (options & OPT_VERB)
1895 n_err(_("failed\n"));
1896 n_err(_("Lookup of \"%s:%s\" failed: %s\n"),
1897 urlp->url_host.s, serv, gai_strerror(errval));
1899 /* Error seems to depend on how "smart" the /etc/service code is: is it
1900 * "able" to state wether the service as such is NONAME or does it only
1901 * check for the given ai_socktype.. */
1902 if (errval == EAI_NONAME || errval == EAI_SERVICE) {
1903 if (serv == urlp->url_proto &&
1904 (serv = url_servbyname(urlp, NULL)) != NULL) {
1905 n_err(_(" Trying standard protocol port \"%s\"\n"
1906 " If that succeeds consider including the port in the URL!\n"),
1907 serv);
1908 continue;
1910 if (serv != urlp->url_port)
1911 n_err(_(" Including a port number in the URL may "
1912 "circumvent this problem\n"));
1914 assert(sofd == -1);
1915 errval = 0;
1916 goto jjumped;
1918 if (options & OPT_VERB)
1919 n_err(_("done\n"));
1921 for (res = res0; res != NULL && sofd < 0; res = res->ai_next) {
1922 if (options & OPT_VERB) {
1923 if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf, sizeof hbuf,
1924 NULL, 0, NI_NUMERICHOST))
1925 memcpy(hbuf, "unknown host", sizeof("unknown host"));
1926 n_err(_("%sConnecting to \"%s:%s\" ..."),
1927 (res == res0 ? "" : "\n"), hbuf, serv);
1930 sofd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
1931 if (sofd >= 0) {
1932 # ifdef HAVE_SO_SNDTIMEO
1933 (void)setsockopt(sofd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv);
1934 # endif
1935 if (connect(sofd, res->ai_addr, res->ai_addrlen)) {
1936 errval = errno;
1937 close(sofd);
1938 sofd = -1;
1943 jjumped:
1944 if (res0 != NULL) {
1945 freeaddrinfo(res0);
1946 res0 = NULL;
1949 hold_sigs();
1950 safe_signal(SIGINT, oint);
1951 safe_signal(SIGHUP, ohup);
1952 rele_sigs();
1954 if (sofd < 0) {
1955 if (errval != 0) {
1956 errno = errval;
1957 n_perr(_("Could not connect"), 0);
1959 goto jleave;
1962 # else /* HAVE_GETADDRINFO */
1963 if (serv == urlp->url_proto) {
1964 if ((ep = getservbyname(UNCONST(serv), "tcp")) != NULL)
1965 urlp->url_portno = ep->s_port;
1966 else {
1967 if (options & OPT_VERB)
1968 n_err(_("failed\n"));
1969 if ((serv = url_servbyname(urlp, &urlp->url_portno)) != NULL)
1970 n_err(_(" Unknown service: \"%s\"\n"
1971 " Trying standard protocol port \"%s\"\n"
1972 " If that succeeds consider including the port in the URL!\n"),
1973 urlp->url_proto, serv);
1974 else {
1975 n_err(_(" Unknown service: \"%s\"\n"
1976 " Including a port in the URL may circumvent this problem\n"),
1977 urlp->url_proto);
1978 goto jleave;
1983 if ((hp = gethostbyname(urlp->url_host.s)) == NULL) {
1984 char const *emsg;
1986 if (options & OPT_VERB)
1987 n_err(_("failed\n"));
1988 switch (h_errno) {
1989 case HOST_NOT_FOUND: emsg = N_("host not found"); break;
1990 default:
1991 case TRY_AGAIN: emsg = N_("(maybe) try again later"); break;
1992 case NO_RECOVERY: emsg = N_("non-recoverable server error"); break;
1993 case NO_DATA: emsg = N_("valid name without IP address"); break;
1995 n_err(_("Lookup of \"%s:%s\" failed: %s\n"),
1996 urlp->url_host.s, serv, V_(emsg));
1997 goto jleave;
1998 } else if (options & OPT_VERB)
1999 n_err(_("done\n"));
2001 pptr = (struct in_addr**)hp->h_addr_list;
2002 if ((sofd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
2003 n_perr(_("could not create socket"), 0);
2004 goto jleave;
2007 memset(&servaddr, 0, sizeof servaddr);
2008 servaddr.sin_family = AF_INET;
2009 servaddr.sin_port = htons(urlp->url_portno);
2010 memcpy(&servaddr.sin_addr, *pptr, sizeof(struct in_addr));
2011 if (options & OPT_VERB)
2012 n_err(_("%sConnecting to \"%s:%d\" ... "),
2013 "", inet_ntoa(**pptr), (int)urlp->url_portno);
2014 # ifdef HAVE_SO_SNDTIMEO
2015 (void)setsockopt(sofd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv);
2016 # endif
2017 if (connect(sofd, (struct sockaddr*)&servaddr, sizeof servaddr)) {
2018 n_perr(_("could not connect"), 0);
2019 close(sofd);
2020 sofd = -1;
2021 goto jleave;
2023 # endif /* !HAVE_GETADDRINFO */
2025 if (options & OPT_VERB)
2026 n_err(_("connected.\n"));
2028 /* And the regular timeouts XXX configurable */
2029 # ifdef HAVE_SO_SNDTIMEO
2030 tv.tv_sec = 42;
2031 tv.tv_usec = 0;
2032 (void)setsockopt(sofd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv);
2033 (void)setsockopt(sofd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
2034 # endif
2035 # ifdef HAVE_SO_LINGER
2036 li.l_onoff = 1;
2037 li.l_linger = 42;
2038 (void)setsockopt(sofd, SOL_SOCKET, SO_LINGER, &li, sizeof li);
2039 # endif
2041 memset(sp, 0, sizeof *sp);
2042 sp->s_fd = sofd;
2044 /* SSL/TLS upgrade? */
2045 # ifdef HAVE_SSL
2046 if (urlp->url_needs_tls) {
2047 hold_sigs();
2048 ohup = safe_signal(SIGHUP, &__sopen_onsig);
2049 oint = safe_signal(SIGINT, &__sopen_onsig);
2050 if (sigsetjmp(__sopen_actjmp, 0)) {
2051 n_err(_("%s during SSL/TLS handshake\n"),
2052 (__sopen_sig == SIGHUP ? _("Hangup") : _("Interrupted")));
2053 goto jsclose;
2055 rele_sigs();
2057 if (ssl_open(urlp, sp) != OKAY) {
2058 jsclose:
2059 sclose(sp);
2060 sofd = -1;
2063 hold_sigs();
2064 safe_signal(SIGINT, oint);
2065 safe_signal(SIGHUP, ohup);
2066 rele_sigs();
2068 # endif /* HAVE_SSL */
2070 jleave:
2071 /* May need to bounce the signal to the lex.c trampoline (or wherever) */
2072 # if defined HAVE_GETADDRINFO || defined HAVE_SSL
2073 if (__sopen_sig != 0) {
2074 sigset_t cset;
2075 sigemptyset(&cset);
2076 sigaddset(&cset, __sopen_sig);
2077 sigprocmask(SIG_UNBLOCK, &cset, NULL);
2078 n_raise(__sopen_sig);
2080 #endif
2081 NYD_LEAVE;
2082 return (sofd >= 0);
2085 FL int
2086 (sgetline)(char **line, size_t *linesize, size_t *linelen, struct sock *sp
2087 SMALLOC_DEBUG_ARGS)
2089 int rv;
2090 size_t lsize;
2091 char *lp_base, *lp;
2092 NYD2_ENTER;
2094 lsize = *linesize;
2095 lp_base = *line;
2096 lp = lp_base;
2098 if (sp->s_rsz < 0) {
2099 sclose(sp);
2100 rv = sp->s_rsz;
2101 goto jleave;
2104 do {
2105 if (lp_base == NULL || PTRCMP(lp, >, lp_base + lsize - 128)) {
2106 size_t diff = PTR2SIZE(lp - lp_base);
2107 *linesize = (lsize += 256); /* XXX magic */
2108 *line = lp_base = (srealloc)(lp_base, lsize SMALLOC_DEBUG_ARGSCALL);
2109 lp = lp_base + diff;
2112 if (sp->s_rbufptr == NULL ||
2113 PTRCMP(sp->s_rbufptr, >=, sp->s_rbuf + sp->s_rsz)) {
2114 # ifdef HAVE_OPENSSL
2115 if (sp->s_use_ssl) {
2116 jssl_retry:
2117 sp->s_rsz = SSL_read(sp->s_ssl, sp->s_rbuf, sizeof sp->s_rbuf);
2118 if (sp->s_rsz <= 0) {
2119 if (sp->s_rsz < 0) {
2120 char o[512];
2121 switch(SSL_get_error(sp->s_ssl, sp->s_rsz)) {
2122 case SSL_ERROR_WANT_READ:
2123 case SSL_ERROR_WANT_WRITE:
2124 goto jssl_retry;
2126 snprintf(o, sizeof o, "%s",
2127 (sp->s_desc ? sp->s_desc : "socket"));
2128 ssl_gen_err("%s", o);
2130 break;
2132 } else
2133 # endif
2135 jagain:
2136 sp->s_rsz = read(sp->s_fd, sp->s_rbuf, sizeof sp->s_rbuf);
2137 if (sp->s_rsz <= 0) {
2138 if (sp->s_rsz < 0) {
2139 char o[512];
2140 if (errno == EINTR)
2141 goto jagain;
2142 snprintf(o, sizeof o, "%s",
2143 (sp->s_desc ? sp->s_desc : "socket"));
2144 n_perr(o, 0);
2146 break;
2149 sp->s_rbufptr = sp->s_rbuf;
2151 } while ((*lp++ = *sp->s_rbufptr++) != '\n');
2152 *lp = '\0';
2153 lsize = PTR2SIZE(lp - lp_base);
2155 if (linelen)
2156 *linelen = lsize;
2157 rv = (int)lsize;
2158 jleave:
2159 NYD2_LEAVE;
2160 return rv;
2162 #endif /* HAVE_SOCKETS */
2164 FL void
2165 load(char const *name)
2167 struct str n;
2168 void *cond;
2169 FILE *in, *oldin;
2170 NYD_ENTER;
2172 if (name == NULL || *name == '\0' || (in = Fopen(name, "r")) == NULL)
2173 goto jleave;
2175 oldin = _fio_input;
2176 _fio_input = in;
2177 pstate |= PS_IN_LOAD;
2178 /* commands() may sreset(), copy over file name */
2179 n.l = strlen(name);
2180 n.s = ac_alloc(n.l +1);
2181 memcpy(n.s, name, n.l +1);
2183 cond = condstack_release();
2184 if (!commands())
2185 n_err(_("Stopped loading \"%s\" due to errors "
2186 "(enable *debug* for trace)\n"), n.s);
2187 condstack_take(cond);
2189 ac_free(n.s);
2190 pstate &= ~PS_IN_LOAD;
2191 _fio_input = oldin;
2192 Fclose(in);
2193 jleave:
2194 NYD_LEAVE;
2197 FL int
2198 c_source(void *v)
2200 int rv = 1;
2201 char **arglist = v, *cp;
2202 FILE *fi;
2203 NYD_ENTER;
2205 if ((cp = fexpand(*arglist, FEXP_LOCAL)) == NULL)
2206 goto jleave;
2207 if ((fi = Fopen(cp, "r")) == NULL) {
2208 n_perr(cp, 0);
2209 goto jleave;
2212 if (temporary_localopts_store != NULL) {
2213 n_err(_("Before v15 you cannot `source' from within macros, sorry\n"));
2214 Fclose(fi);
2215 goto jleave;
2217 if (_fio_stack_size >= NELEM(_fio_stack)) {
2218 n_err(_("Too many `source' recursions\n"));
2219 Fclose(fi);
2220 goto jleave;
2223 _fio_stack[_fio_stack_size].s_file = _fio_input;
2224 _fio_stack[_fio_stack_size].s_cond = condstack_release();
2225 _fio_stack[_fio_stack_size].s_loading = !!(pstate & PS_LOADING);
2226 ++_fio_stack_size;
2227 pstate &= ~PS_LOADING;
2228 pstate |= PS_SOURCING;
2229 _fio_input = fi;
2230 rv = 0;
2231 jleave:
2232 NYD_LEAVE;
2233 return rv;
2236 FL int
2237 unstack(void)
2239 int rv = 1;
2240 NYD_ENTER;
2242 if (_fio_stack_size == 0) {
2243 n_err(_("`source' stack over-pop\n"));
2244 pstate &= ~PS_SOURCING;
2245 goto jleave;
2248 Fclose(_fio_input);
2250 --_fio_stack_size;
2251 if (!condstack_take(_fio_stack[_fio_stack_size].s_cond))
2252 n_err(_("Unmatched \"if\"\n"));
2253 if (_fio_stack[_fio_stack_size].s_loading)
2254 pstate |= PS_LOADING;
2255 else
2256 pstate &= ~PS_LOADING;
2257 _fio_input = _fio_stack[_fio_stack_size].s_file;
2258 if (_fio_stack_size == 0) {
2259 if (pstate & PS_LOADING)
2260 pstate |= PS_SOURCING;
2261 else
2262 pstate &= ~PS_SOURCING;
2264 rv = 0;
2265 jleave:
2266 NYD_LEAVE;
2267 return rv;
2270 /* s-it-mode */