1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2  *@ Implementation of file-streams.h.
3  *@ TODO tmp_release() should be removed: tmp_open() should take an optional
4  *@ TODO vector of NIL terminated {char const *mode;sz fd_result;} structs,
5  *@ TODO and create all desired copies; drop HOLDSIGS, then, too!
6  *
7  * Copyright (c) 2012 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
8  * SPDX-License-Identifier: ISC
9  *
10  * Permission to use, copy, modify, and/or distribute this software for any
11  * purpose with or without fee is hereby granted, provided that the above
12  * copyright notice and this permission notice appear in all copies.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21  */
22 #undef su_FILE
23 #define su_FILE file_streams
24 #define mx_SOURCE
25 #define mx_SOURCE_FILE_STREAMS
26 
27 #ifndef mx_HAVE_AMALGAMATION
28 # include "mx/nail.h"
29 #endif
30 
31 #include <fcntl.h>
32 
33 #include <su/cs.h>
34 #include <su/mem.h>
35 
36 #include "mx/child.h"
37 #include "mx/cmd-filetype.h"
38 #include "mx/random.h"
39 #include "mx/sigs.h"
40 #include "mx/termcap.h"
41 
42 #include "mx/file-streams.h"
43 #include "su/code-in.h"
44 
45 #ifdef O_CLOEXEC
46 # define a_FS__O_CLOEXEC O_CLOEXEC
47 #else
48 # define a_FS__O_CLOEXEC 0
49 #endif
50 
51 enum{
52    a_FS_PIPE_READ = 0,
53    a_FS_PIPE_WRITE = 1
54 };
55 
56 enum a_fs_ent_flags{
57    a_FS_EF_RAW,
58    a_FS_EF_IMAP = 1u<<1,
59    a_FS_EF_MAILDIR = 1u<<2,
60    a_FS_EF_HOOK = 1u<<3,
61    a_FS_EF_PIPE = 1u<<4,
62    a_FS_EF_MASK = (1u<<7) - 1,
63    /* TODO a_FS_EF_UNLINK: should be in a separate process so that unlinking
64     * TODO the temporary "garbage" is "safe"(r than it is like that) */
65    a_FS_EF_UNLINK = 1u<<8,
66    /* mx_fs_tmp_release() callable? */
67    a_FS_EF_HOLDSIGS = 1u<<9,
68    a_FS_EF_FD_PASS_NEEDS_WAIT = 1u<<10
69 };
70 
71 /* This struct is what backs struct mx_fs_tmp_ctx! */
72 struct a_fs_ent{
73    char *fse_realfile;
74    struct a_fs_ent *fse_link;
75    u32 fse_flags;
76    int fse_omode;
77    long fse_offset; /* TODO SU I/O 64-bit */
78    FILE *fse_fp;
79    char *fse_save_cmd;
80    struct mx_child_ctx fse_cc;
81 };
82 
83 struct a_fs_lpool_ent{
84    struct a_fs_lpool_ent *fsle_last;
85    char *fsle_dat;
86    uz fsle_size;
87 };
88 
89 static struct a_fs_ent *a_fs_fp_head;
90 
91 struct a_fs_lpool_ent *a_fs_lpool_free;
92 struct a_fs_lpool_ent *a_fs_lpool_used;
93 
94 /* Scan file open mode, and turn it to flags.  If & was prepended, set *mode
95  * to NIL to indicate O_REGISTER shall not be set */
96 static boole a_fs_scan_mode(char const **mode, int *omode);
97 
98 /* */
99 static struct a_fs_ent *a_fs_register_file(FILE *fp, int omode,
100       struct mx_child_ctx *ccp,
101       u32 flags, char const *realfile, long offset, char const *save_cmd);
102 static boole a_fs_unregister_file(FILE *fp);
103 
104 /* */
105 static boole a_fs_file_load(uz flags, int infd, int outfd,
106       char const *load_cmd);
107 static boole a_fs_file_save(struct a_fs_ent *fpp);
108 
109 static boole
a_fs_scan_mode(char const ** mode,int * omode)110 a_fs_scan_mode(char const **mode, int *omode){
111    static struct{
112       char const mode[4];
113       int omode;
114    }const maps[] = {
115       {"r", O_RDONLY},
116       {"w", O_WRONLY | O_CREAT | mx_O_NOXY_BITS | O_TRUNC},
117       {"wx", O_WRONLY | O_CREAT | O_EXCL},
118       {"a", O_WRONLY | O_APPEND | O_CREAT | mx_O_NOXY_BITS},
119       {"a+", O_RDWR | O_APPEND | O_CREAT | mx_O_NOXY_BITS},
120       {"r+", O_RDWR},
121       {"w+", O_RDWR | O_CREAT | mx_O_NOXY_BITS | O_TRUNC},
122       {"W+", O_RDWR | O_CREAT | O_EXCL}
123    };
124    boole rv;
125    uz i;
126    char const *xmode;
127    NYD2_IN;
128 
129    if((xmode = *mode)[0] == '&'){
130       *mode = NIL;
131       ++xmode;
132    }
133 
134    for(i = 0; i < NELEM(maps); ++i)
135       if(!su_cs_cmp(maps[i].mode, xmode)){
136          *omode = maps[i].omode;
137          rv = TRU1;
138          goto jleave;
139       }
140 
141    su_DBG( n_alert(_("Internal error: bad stdio open mode %s"), xmode); )
142    su_NDBG( su_err_set_no(su_ERR_INVAL); )
143    *omode = 0; /* (silence CC) */
144 
145    rv = FAL0;
146 jleave:
147    NYD2_OU;
148    return rv;
149 }
150 
151 static struct a_fs_ent *
a_fs_register_file(FILE * fp,int omode,struct mx_child_ctx * ccp,u32 flags,char const * realfile,long offset,char const * save_cmd)152 a_fs_register_file(FILE *fp, int omode, struct mx_child_ctx *ccp, u32 flags,
153       char const *realfile, long offset, char const *save_cmd){
154    struct a_fs_ent *fsep;
155    NYD_IN;
156 
157    ASSERT(!(flags & a_FS_EF_UNLINK) || realfile != NIL);
158 
159    fsep = su_TCALLOC(struct a_fs_ent, 1);
160    if(realfile != NIL)
161       fsep->fse_realfile = su_cs_dup(realfile, 0);
162    fsep->fse_link = a_fs_fp_head;
163    fsep->fse_flags = flags;
164    fsep->fse_omode = omode;
165    fsep->fse_offset = offset;
166    fsep->fse_fp = fp;
167    if(save_cmd != NIL)
168       fsep->fse_save_cmd = su_cs_dup(save_cmd, 0);
169    if(ccp != NIL)
170       fsep->fse_cc = *ccp;
171 
172    a_fs_fp_head = fsep;
173 
174    NYD_OU;
175    return fsep;
176 }
177 
178 static boole
a_fs_unregister_file(FILE * fp)179 a_fs_unregister_file(FILE *fp){
180    struct a_fs_ent **fsepp, *fsep;
181    boole rv;
182    NYD_IN;
183 
184    rv = TRU1;
185 
186    for(fsepp = &a_fs_fp_head;; fsepp = &fsep->fse_link){
187       if(UNLIKELY((fsep = *fsepp) == NIL)){
188          su_DBGOR(n_panic, n_alert)(_("Invalid file pointer"));
189          rv = FAL0;
190          break;
191       }else if(fsep->fse_fp != fp)
192          continue;
193 
194       switch(fsep->fse_flags & a_FS_EF_MASK){
195       case a_FS_EF_RAW:
196       case a_FS_EF_PIPE:
197          break;
198       default:
199          rv = a_fs_file_save(fsep);
200          break;
201       }
202 
203       if((fsep->fse_flags & a_FS_EF_UNLINK) && unlink(fsep->fse_realfile))
204          rv = FAL0;
205 
206       *fsepp = fsep->fse_link;
207       if(fsep->fse_realfile != NIL)
208          su_FREE(fsep->fse_realfile);
209       if(fsep->fse_save_cmd != NIL)
210          su_FREE(fsep->fse_save_cmd);
211 
212       su_FREE(fsep);
213       break;
214    }
215 
216    NYD_OU;
217    return rv;
218 }
219 
220 static boole
a_fs_file_load(uz flags,int infd,int outfd,char const * load_cmd)221 a_fs_file_load(uz flags, int infd, int outfd, char const *load_cmd){
222    struct mx_child_ctx cc;
223    boole rv;
224    NYD2_IN;
225 
226    mx_child_ctx_setup(&cc);
227    cc.cc_flags = mx_CHILD_RUN_WAIT_LIFE;
228    cc.cc_fds[mx_CHILD_FD_IN] = infd;
229    cc.cc_fds[mx_CHILD_FD_OUT] = outfd;
230 
231    switch(flags & a_FS_EF_MASK){
232    case a_FS_EF_IMAP:
233    case a_FS_EF_MAILDIR:
234       rv = TRU1;
235       break;
236    case a_FS_EF_HOOK:
237       cc.cc_cmd = ok_vlook(SHELL);
238       cc.cc_args[0] = "-c";
239       cc.cc_args[1] = load_cmd;
240       if(0){
241       /* FALLTHRU */
242    default:
243          cc.cc_cmd = mx_FS_FILETYPE_CAT_PROG;
244       }
245 
246       rv = (mx_child_run(&cc) && cc.cc_exit_status == 0);
247       break;
248    }
249 
250    NYD2_OU;
251    return rv;
252 }
253 
254 static boole
a_fs_file_save(struct a_fs_ent * fsep)255 a_fs_file_save(struct a_fs_ent *fsep){
256    struct mx_child_ctx cc;
257    boole rv;
258    NYD_IN;
259 
260    if(fsep->fse_omode == O_RDONLY){
261       rv = TRU1;
262       goto jleave;
263    }
264    rv = FAL0;
265 
266    fflush(fsep->fse_fp);
267    clearerr(fsep->fse_fp);
268 
269    /* Ensure the I/O library does not optimize the fseek(3) away! */
270    if(!n_real_seek(fsep->fse_fp, fsep->fse_offset, SEEK_SET)){
271       s32 err;
272 
273       err = su_err_no();
274       n_err(_("Fatal: cannot restore file position and save %s: %s\n"),
275          n_shexp_quote_cp(fsep->fse_realfile, FAL0), su_err_doc(err));
276       goto jleave;
277    }
278 
279 #ifdef mx_HAVE_MAILDIR
280    if((fsep->fse_flags & a_FS_EF_MASK) == a_FS_EF_MAILDIR){
281       rv = maildir_append(fsep->fse_realfile, fsep->fse_fp, fsep->fse_offset);
282       goto jleave;
283    }
284 #endif
285 
286 #ifdef mx_HAVE_IMAP
287    if((fsep->fse_flags & a_FS_EF_MASK) == a_FS_EF_IMAP){
288       rv = imap_append(fsep->fse_realfile, fsep->fse_fp, fsep->fse_offset);
289       goto jleave;
290    }
291 #endif
292 
293    mx_child_ctx_setup(&cc);
294    cc.cc_flags = mx_CHILD_RUN_WAIT_LIFE;
295    cc.cc_fds[mx_CHILD_FD_IN] = fileno(fsep->fse_fp);
296    if((cc.cc_fds[mx_CHILD_FD_OUT] = open(fsep->fse_realfile,
297          ((fsep->fse_omode | O_CREAT |
298           (fsep->fse_omode & O_APPEND ? 0 : O_TRUNC) | mx_O_NOXY_BITS
299          ) & ~O_EXCL), 0666)) == -1){
300       s32 err;
301 
302       err = su_err_no();
303       n_err(_("Fatal: cannot create %s: %s\n"),
304          n_shexp_quote_cp(fsep->fse_realfile, FAL0), su_err_doc(err));
305       goto jleave;
306    }
307 
308    switch(fsep->fse_flags & a_FS_EF_MASK){
309    case a_FS_EF_HOOK:
310       if(n_poption & n_PO_D_V)
311          n_err(_("Using `filetype' handler %s to save %s\n"),
312             n_shexp_quote_cp(fsep->fse_save_cmd, FAL0),
313             n_shexp_quote_cp(fsep->fse_realfile, FAL0));
314       cc.cc_cmd = ok_vlook(SHELL);
315       cc.cc_args[0] = "-c";
316       cc.cc_args[1] = fsep->fse_save_cmd;
317       break;
318    default:
319       cc.cc_cmd = mx_FS_FILETYPE_CAT_PROG;
320       break;
321    }
322 
323    rv = (mx_child_run(&cc) && cc.cc_exit_status == 0);
324 
325    close(cc.cc_fds[mx_CHILD_FD_OUT]); /* XXX no error handling */
326 jleave:
327    NYD_OU;
328    return rv;
329 }
330 
331 FILE *
mx_fs_open(char const * file,char const * oflags)332 mx_fs_open(char const *file, char const *oflags){
333    int osflags, fd;
334    char const *moflags;
335    FILE *fp;
336    NYD_IN;
337 
338    fp = NIL;
339 
340    moflags = oflags;
341    if(!a_fs_scan_mode(&moflags, &osflags))
342       goto jleave;
343    osflags |= a_FS__O_CLOEXEC;
344    if(moflags == NIL)
345       ++oflags;
346 
347    if((fd = open(file, osflags, 0666)) == -1)
348       goto jleave;
349    if(!mx_FS_FD_CLOEXEC_SET(fd)){
350       close(fd);
351       goto jleave;
352    }
353 
354    if((fp = fdopen(fd, oflags)) != NIL && moflags != NIL)
355       a_fs_register_file(fp, osflags, 0, a_FS_EF_RAW, NIL, 0L, NIL);
356 
357 jleave:
358    NYD_OU;
359    return fp;
360 }
361 
362 FILE *
mx_fs_open_any(char const * file,char const * oflags,enum mx_fs_open_state * fs_or_nil)363 mx_fs_open_any(char const *file, char const *oflags, /* TODO take flags */
364       enum mx_fs_open_state *fs_or_nil){ /* TODO as bits, return state */
365    /* TODO Support file locking upon open time */
366    long offset;
367    enum protocol p;
368    enum mx_fs_oflags rof;
369    uz flags;
370    int osflags, omode, infd;
371    char const *cload, *csave;
372    enum mx_fs_open_state fs;
373    s32 err;
374    FILE *rv;
375    NYD_IN;
376 
377    rv = NIL;
378    err = su_ERR_NONE;
379    fs = mx_FS_OPEN_STATE_NONE;
380    cload = csave = NIL;
381 
382    /* !O_REGISTER is disallowed */
383    if(!a_fs_scan_mode(&oflags, &osflags) || oflags == NIL){
384       err = su_ERR_INVAL;
385       goto jleave;
386    }
387 
388    flags = 0;
389    rof = mx_FS_O_RDWR | mx_FS_O_UNLINK;
390    if(osflags & O_APPEND)
391       rof |= mx_FS_O_APPEND;
392    omode = (osflags == O_RDONLY) ? R_OK : R_OK | W_OK;
393 
394    /* We don't want to find mbox.bz2 when doing "copy * mbox", but only for
395     * "file mbox", so don't try hooks when writing */
396    p = which_protocol(csave = file, TRU1, ((omode & W_OK) == 0), &file);
397    fs = S(enum mx_fs_open_state,p);
398    switch(p){
399    default:
400       err = su_ERR_OPNOTSUPP;
401       goto jleave;
402 
403    case n_PROTO_IMAP:
404 #ifdef mx_HAVE_IMAP
405       file = csave;
406       flags |= a_FS_EF_IMAP;
407       osflags = O_RDWR | O_APPEND | O_CREAT | mx_O_NOXY_BITS;
408       infd = -1;
409       break;
410 #else
411       err = su_ERR_OPNOTSUPP;
412       goto jleave;
413 #endif
414 
415    case n_PROTO_MAILDIR:
416 #ifdef mx_HAVE_MAILDIR
417       if(fs_or_nil != NIL && !access(file, F_OK))
418          fs |= mx_FS_OPEN_STATE_EXISTS;
419       flags |= a_FS_EF_MAILDIR;
420       osflags = O_RDWR | O_APPEND | O_CREAT | mx_O_NOXY_BITS;
421       infd = -1;
422       break;
423 #else
424       err = su_ERR_OPNOTSUPP;
425       goto jleave;
426 #endif
427 
428    case n_PROTO_EML:
429       if(!(osflags & O_RDONLY)){
430          err = su_ERR_OPNOTSUPP;
431          goto jleave;
432       }
433       /* FALLTHRU */
434    case n_PROTO_FILE:{
435       struct mx_filetype ft;
436 
437       if(!(osflags & O_EXCL) && fs_or_nil != NIL && !access(file, F_OK))
438          fs |= mx_FS_OPEN_STATE_EXISTS;
439 
440       if(mx_filetype_exists(&ft, file)){/* TODO report real name to outside */
441          flags |= a_FS_EF_HOOK;
442          cload = ft.ft_load_dat;
443          csave = ft.ft_save_dat;
444          /* Cause truncation for compressor/hook output files */
445          osflags &= ~O_APPEND;
446          rof &= ~mx_FS_O_APPEND;
447          if((infd = open(file, (omode & W_OK ? O_RDWR : O_RDONLY))) != -1){
448             fs |= mx_FS_OPEN_STATE_EXISTS;
449             if(n_poption & n_PO_D_V)
450                n_err(_("Using `filetype' handler %s to load %s\n"),
451                   n_shexp_quote_cp(cload, FAL0), n_shexp_quote_cp(file, FAL0));
452          }else{
453             err = su_err_no();
454             if(!(osflags & O_CREAT) || err != su_ERR_NOENT)
455                goto jleave;
456          }
457       }else{
458          /*flags |= a_FS_EF_RAW;*/
459          rv = mx_fs_open(file, oflags);
460          if((osflags & O_EXCL) && rv == NIL)
461             fs |= mx_FS_OPEN_STATE_EXISTS;
462          goto jleave;
463       }
464       }break;
465    }
466 
467    /* Note rv is not yet register_file()d, fclose() it in error path! */
468    if((rv = mx_fs_tmp_open("fopenany", rof, NIL)) == NIL){
469       n_perr(_("tmpfile"), err = su_err_no());
470       goto Jerr;
471    }
472 
473    if(flags & (a_FS_EF_IMAP | a_FS_EF_MAILDIR))
474       ;
475    else if(infd >= 0){
476       if(!a_fs_file_load(flags, infd, fileno(rv), cload)) Jerr:{
477          err = su_err_no();
478          if(rv != NIL)
479             mx_fs_close(rv);
480          rv = NIL;
481          if(infd >= 0)
482             close(infd);
483          goto jleave;
484       }
485    }else{
486       if((infd = creat(file, 0666)) == -1){
487          err = su_err_no();
488          fclose(rv);
489          rv = NIL;
490          goto jleave;
491       }
492    }
493 
494    if(infd >= 0)
495       close(infd);
496    fflush(rv);
497 
498    if(!(osflags & O_APPEND))
499       rewind(rv);
500 
501    if((offset = ftell(rv)) == -1){
502       err = su_err_no();
503       mx_fs_close(rv);
504       rv = NIL;
505       goto jleave;
506    }
507 
508    a_fs_register_file(rv, osflags, 0, flags, file, offset, csave);
509 jleave:
510    if(fs_or_nil != NIL)
511       *fs_or_nil = fs;
512    if(rv == NIL && err != su_ERR_NONE)
513       su_err_set_no(err);
514 
515    NYD_OU;
516    return rv;
517 }
518 
519 FILE *
mx_fs_tmp_open(char const * namehint,u32 oflags,struct mx_fs_tmp_ctx ** fstcp_or_nil)520 mx_fs_tmp_open(char const *namehint, u32 oflags,
521       struct mx_fs_tmp_ctx **fstcp_or_nil){
522    /* The 6 is arbitrary but leaves room for an eight character hint (the
523     * POSIX minimum path length is 14, though we do not check that XXX).
524     * 6 should be more than sufficient given that we use base64url encoding
525     * for our random string */
526    enum {a_RANDCHARS = 6u, a_HINT_MIN = 8u};
527 
528    char *cp_base, *cp;
529    uz maxname, hintlen, i;
530    char const *tmpdir;
531    int osoflags, fd, e;
532    boole relesigs;
533    FILE *fp;
534    NYD_IN;
535 
536    ASSERT(namehint != NIL);
537    ASSERT((oflags & mx_FS_O_WRONLY) || (oflags & mx_FS_O_RDWR));
538    ASSERT(!(oflags & mx_FS_O_RDONLY));
539    ASSERT(!(oflags & mx_FS_O_REGISTER_UNLINK) || (oflags & mx_FS_O_REGISTER));
540    ASSERT(!(oflags & mx_FS_O_HOLDSIGS) || !(oflags & mx_FS_O_UNLINK));
541    ASSERT(fstcp_or_nil == NIL || ((oflags & mx_FS_O_REGISTER) ||
542       (oflags & mx_FS_O_HOLDSIGS) || !(oflags & mx_FS_O_UNLINK)));
543 
544    if(fstcp_or_nil != NIL)
545       *fstcp_or_nil = NIL;
546 
547    fp = NIL;
548    relesigs = FAL0;
549    e = 0;
550    tmpdir = ok_vlook(TMPDIR);
551    maxname = NAME_MAX;
552 
553 #ifdef mx_HAVE_PATHCONF
554    /* C99 */{
555       long pc;
556 
557       if((pc = pathconf(tmpdir, _PC_NAME_MAX)) != -1){
558          maxname = S(uz,pc);
559          if(maxname < a_RANDCHARS + a_HINT_MIN){
560             su_err_set_no(su_ERR_NAMETOOLONG);
561             goto jleave;
562          }
563       }
564    }
565 #endif
566 
567    /* We are prepared to ignore the namehint .. unless we may not */
568    if((oflags & mx_FS_O_SUFFIX) && *namehint != '\0'){
569       if((hintlen = su_cs_len(namehint)) >= maxname - a_RANDCHARS){
570          su_err_set_no(su_ERR_NAMETOOLONG);
571          goto jleave;
572       }
573    }else
574       hintlen = 0;
575 
576    /* Prepare the template string once, then iterate over the random range.
577     * But first ensure we can report the name in !O_REGISTER cases ("hack") */
578    i = su_cs_len(tmpdir) + 1 + maxname +1;
579 
580    if(!(oflags & mx_FS_O_REGISTER) && fstcp_or_nil != NIL){
581       union {struct a_fs_ent *fse; void *v; struct mx_fs_tmp_ctx *fstc;} p;
582 
583       /* Store character data right after the struct */
584       p.v = n_autorec_alloc(sizeof(*p.fse) + i);
585       su_mem_set(p.fse, 0, sizeof(*p.fse));
586       p.fse->fse_realfile = R(char*,&p.fse[1]);
587       if(oflags & mx_FS_O_HOLDSIGS) /* Enable tmp_release() nonetheless? */
588          p.fse->fse_flags = a_FS_EF_HOLDSIGS;
589       *fstcp_or_nil = p.fstc;
590    }
591 
592    cp_base = cp = n_lofi_alloc(i);
593    cp = su_cs_pcopy(cp, tmpdir);
594    *cp++ = '/';
595    /* C99 */{
596       uz j;
597       char *xp;
598 
599       /* xxx We silently assume VAL_UAGENT easily fits in NAME_MAX-1 */
600       xp = su_cs_pcopy(cp, VAL_UAGENT);
601       *xp++ = '-';
602       if(!(oflags & mx_FS_O_SUFFIX))
603          xp = su_cs_pcopy(xp, namehint);
604 
605       /* Just cut off as many of VAL_UAGENT as necessary */
606       if((i = P2UZ(xp - cp)) > (j = maxname - hintlen - a_RANDCHARS))
607          xp -= i - j;
608 
609       if((oflags & mx_FS_O_SUFFIX) && hintlen > 0)
610          su_mem_copy(&xp[a_RANDCHARS], namehint, hintlen);
611 
612       xp[hintlen + a_RANDCHARS] = '\0';
613       cp = xp;
614    }
615 
616    osoflags = O_CREAT | O_EXCL | a_FS__O_CLOEXEC;
617    osoflags |= (oflags & mx_FS_O_WRONLY) ? O_WRONLY : O_RDWR;
618    if(oflags & mx_FS_O_APPEND)
619       osoflags |= O_APPEND;
620 
621    for(relesigs = TRU1, i = 0;; ++i){
622       su_mem_copy(cp, mx_random_create_cp(a_RANDCHARS, NIL), a_RANDCHARS);
623 
624       mx_sigs_all_holdx();
625 
626       if((fd = open(cp_base, osoflags, 0600)) != -1){
627          if(mx_FS_FD_CLOEXEC_SET(fd))
628             break;
629          close(fd);
630       }
631 
632       if(i >= mx_FS_TMP_OPEN_TRIES){
633          e = su_err_no();
634          goto jfree;
635       }
636 
637       mx_sigs_all_rele();
638    }
639 
640    /* C99 */{
641       char const *osflags;
642 
643       osflags = (oflags & mx_FS_O_RDWR ? "w+" : "w");
644 
645       if((fp = fdopen(fd, osflags)) != NIL){
646          if(oflags & mx_FS_O_REGISTER){
647             struct a_fs_ent *fsep;
648             int osflagbits;
649 
650             a_fs_scan_mode(&osflags, &osflagbits); /* TODO osoflags&xy ?!!? */
651             fsep = a_fs_register_file(fp, osflagbits | a_FS__O_CLOEXEC, 0,
652                   (a_FS_EF_RAW |
653                    (oflags & mx_FS_O_REGISTER_UNLINK ? a_FS_EF_UNLINK : 0) |
654                    (oflags & mx_FS_O_HOLDSIGS ? a_FS_EF_HOLDSIGS : 0)),
655                cp_base, 0L, NIL);
656 
657             /* User arg points to registered data in this case */
658             if(fstcp_or_nil != NIL){
659                union {void *v; struct mx_fs_tmp_ctx *fstc;} p;
660 
661                p.v = fsep;
662                *fstcp_or_nil = p.fstc;
663             }
664          }else if(fstcp_or_nil != NIL){
665             /* Otherwise copy filename buffer into autorec storage */
666             cp = UNCONST(char*,(*fstcp_or_nil)->fstc_filename);
667             su_cs_pcopy(cp, cp_base);
668          }
669       }
670    }
671 
672    if(fp == NIL || (oflags & mx_FS_O_UNLINK)){
673       e = su_err_no();
674       unlink(cp_base);
675       if(fp == NIL)
676          close(fd);
677       goto jfree;
678    }else if(fp != NIL){
679       /* We will succeed and keep the file around for further usage, likely
680        * another stream will be opened for pure reading purposes (this is true
681        * at the time of this writing.  A restrictive umask(2) settings may have
682        * turned the path inaccessible, so ensure it may be read at least!
683        * TODO once ok_vlook() can return an integer, look up *umask* first! */
684       (void)fchmod(fd, S_IWUSR | S_IRUSR);
685    }
686 
687    n_lofi_free(cp_base);
688 
689 jleave:
690    if(relesigs && (fp == NIL || !(oflags & mx_FS_O_HOLDSIGS)))
691       mx_sigs_all_rele();
692 
693    if(fp == NIL){
694       su_err_set_no(e);
695       if(fstcp_or_nil != NIL)
696          *fstcp_or_nil = NIL;
697    }
698 
699    NYD_OU;
700    return fp;
701 
702 jfree:
703    if((cp = cp_base) != NIL)
704       n_lofi_free(cp);
705    goto jleave;
706 }
707 
708 void
mx_fs_tmp_release(struct mx_fs_tmp_ctx * fstcp)709 mx_fs_tmp_release(struct mx_fs_tmp_ctx *fstcp){
710    union {void *vp; struct a_fs_ent *fsep;} u;
711    NYD_IN;
712 
713    ASSERT(fstcp != NIL);
714 
715    u.vp = fstcp;
716    ASSERT(u.fsep->fse_flags & a_FS_EF_HOLDSIGS);
717    DBG( if(u.fsep->fse_flags & a_FS_EF_UNLINK)
718       n_alert("tmp_release(): REGISTER_UNLINK set!"); )
719 
720    unlink(u.fsep->fse_realfile);
721 
722    u.fsep->fse_flags &= ~(a_FS_EF_HOLDSIGS | a_FS_EF_UNLINK);
723 
724    mx_sigs_all_rele();
725 
726    NYD_OU;
727 }
728 
729 FILE *
mx_fs_fd_open(sz fd,char const * oflags,boole nocloexec)730 mx_fs_fd_open(sz fd, char const *oflags, boole nocloexec){
731    FILE *fp;
732    int osflags;
733    NYD_IN;
734 
735    a_fs_scan_mode(&oflags, &osflags);
736    if(!nocloexec)
737       osflags |= a_FS__O_CLOEXEC; /* Ensured by caller as documented! */
738 
739    if((fp = fdopen(S(int,fd), oflags)) != NIL && oflags != NIL)
740       a_fs_register_file(fp, osflags, 0, a_FS_EF_RAW, NIL, 0L, NIL);
741 
742    NYD_OU;
743    return fp;
744 }
745 
746 boole
mx_fs_fd_cloexec_set(sz fd)747 mx_fs_fd_cloexec_set(sz fd){
748    s32 ifd;
749    boole rv;
750    NYD2_IN;
751 
752    ifd = S(s32,fd);
753    /*if(!(rv = ((a__fl = fcntl(ifd, F_GETFD)) != -1 && !(ifd & FD_CLOEXEC))))*/
754       rv = (fcntl(ifd, F_SETFD, FD_CLOEXEC) != -1);
755 
756    NYD2_OU;
757    return rv;
758 }
759 
760 boole
mx_fs_close(FILE * fp)761 mx_fs_close(FILE *fp){
762    boole rv;
763    NYD_IN;
764 
765    rv = (a_fs_unregister_file(fp) && fclose(fp) == 0);
766 
767    NYD_OU;
768    return rv;
769 }
770 
771 boole
mx_fs_pipe_cloexec(sz fd[2])772 mx_fs_pipe_cloexec(sz fd[2]){
773    int xfd[2];
774    boole rv;
775    NYD_IN;
776 
777    rv = FAL0;
778 
779 #ifdef mx_HAVE_PIPE2
780    if(pipe2(xfd, O_CLOEXEC) != -1){
781       fd[0] = xfd[0];
782       fd[1] = xfd[1];
783       rv = TRU1;
784    }
785 #else
786    if(pipe(xfd) != -1){
787       fd[0] = xfd[0];
788       fd[1] = xfd[1];
789       mx_fs_fd_cloexec_set(fd[0]);
790       mx_fs_fd_cloexec_set(fd[1]);
791       rv = TRU1;
792    }
793 #endif
794 
795    NYD_OU;
796    return rv;
797 }
798 
799 FILE *
mx_fs_pipe_open(char const * cmd,char const * mode,char const * sh,char const ** env_addon,int newfd1)800 mx_fs_pipe_open(char const *cmd, char const *mode, char const *sh,
801       char const **env_addon, int newfd1){
802    struct mx_child_ctx cc;
803    sz p[2], myside, hisside, fd0, fd1;
804    sigset_t nset;
805    char mod[2];
806    FILE *rv;
807    NYD_IN;
808 
809    rv = NIL;
810    mod[0] = '0', mod[1] = '\0';
811 
812    if(!mx_fs_pipe_cloexec(p))
813       goto jleave;
814 
815    if(*mode == 'r'){
816       myside = p[a_FS_PIPE_READ];
817       fd0 = mx_CHILD_FD_PASS;
818       hisside = fd1 = p[a_FS_PIPE_WRITE];
819       mod[0] = *mode;
820    }else if(*mode == 'W'){
821       myside = p[a_FS_PIPE_WRITE];
822       hisside = fd0 = p[a_FS_PIPE_READ];
823       fd1 = newfd1;
824       mod[0] = 'w';
825    }else{
826       myside = p[a_FS_PIPE_WRITE];
827       hisside = fd0 = p[a_FS_PIPE_READ];
828       fd1 = mx_CHILD_FD_PASS;
829       mod[0] = 'w';
830    }
831 
832    sigemptyset(&nset);
833    mx_child_ctx_setup(&cc);
834    /* Be simple and let's just synchronize completely on that */
835    cc.cc_flags = ((cc.cc_cmd = cmd) == R(char*,-1)) ?  mx_CHILD_SPAWN_CONTROL
836          : mx_CHILD_SPAWN_CONTROL | mx_CHILD_SPAWN_CONTROL_LINGER;
837    cc.cc_mask = &nset;
838    cc.cc_fds[mx_CHILD_FD_IN] = fd0;
839    cc.cc_fds[mx_CHILD_FD_OUT] = fd1;
840    cc.cc_env_addon = env_addon;
841 
842    /* Is this a special in-process fork setup? */
843    if(cmd == R(char*,-1)){
844       if(!mx_child_fork(&cc))
845          n_perr(_("fork"), 0);
846       else if(cc.cc_pid == 0){
847          union {char const *ccp; int (*ptf)(void); int es;} u;
848 
849          mx_child_in_child_setup(&cc);
850          close(S(int,p[a_FS_PIPE_READ]));
851          close(S(int,p[a_FS_PIPE_WRITE]));
852          /* TODO close all other open FDs except stds and reset memory */
853          /* Standard I/O drives me insane!  All we need is a sync operation
854           * that causes n_stdin to forget about any read buffer it may have.
855           * We cannot use fflush(3), this works with Musl and Solaris, but not
856           * with GlibC.  (For at least pipes.)  We cannot use fdreopen(),
857           * because this function does not exist!  Luckily (!!!) we only use
858           * n_stdin not stdin in our child, otherwise all bets were off!
859           * TODO (Unless we would fiddle around with FILE* directly:
860           * TODO #ifdef __GLIBC__
861           * TODO   n_stdin->_IO_read_ptr = n_stdin->_IO_read_end;
862           * TODO #elif *BSD*
863           * TODO   n_stdin->_r = 0;
864           * TODO #elif su_OS_SOLARIS || su_OS_SUNOS
865           * TODO   n_stdin->_cnt = 0;
866           * TODO #endif
867           * TODO ) which should have additional config test for sure! */
868          n_stdin = fdopen(STDIN_FILENO, "r");
869          /*n_stdout = fdopen(STDOUT_FILENO, "w");*/
870          /*n_stderr = fdopen(STDERR_FILENO, "w");*/
871          u.ccp = sh;
872          u.es = (*u.ptf)();
873          /*fflush(NULL);*/
874          for(;;)
875             _exit(u.es);
876       }
877    }else{
878       if(sh != NIL){
879          cc.cc_cmd = sh;
880          cc.cc_args[0] = "-c";
881          cc.cc_args[1] = cmd;
882       }
883 
884       mx_child_run(&cc);
885    }
886 
887    if(cc.cc_pid < 0){
888       close(S(int,p[a_FS_PIPE_READ]));
889       close(S(int,p[a_FS_PIPE_WRITE]));
890       goto jleave;
891    }
892 
893    close(S(int,hisside));
894    if((rv = fdopen(myside, mod)) != NIL)
895       a_fs_register_file(rv, 0, &cc,
896          ((fd0 != mx_CHILD_FD_PASS && fd1 != mx_CHILD_FD_PASS)
897             ? a_FS_EF_PIPE : a_FS_EF_PIPE | a_FS_EF_FD_PASS_NEEDS_WAIT),
898          NIL, 0L, NIL);
899    else
900       close(myside);
901 
902 jleave:
903    NYD_OU;
904    return rv;
905 }
906 
907 s32
mx_fs_pipe_signal(FILE * fp,s32 sig)908 mx_fs_pipe_signal(FILE *fp, s32 sig){
909    s32 rv;
910    struct a_fs_ent *fsep;
911    NYD_IN;
912 
913    for(fsep = a_fs_fp_head;; fsep = fsep->fse_link)
914       if(fsep == NIL){
915          rv = -1;
916          break;
917       }else if(fsep->fse_fp == fp){
918          rv = mx_child_signal(&fsep->fse_cc, sig);
919          break;
920       }
921 
922    NYD_OU;
923    return rv;
924 }
925 
926 boole
mx_fs_pipe_close(FILE * ptr,boole dowait)927 mx_fs_pipe_close(FILE *ptr, boole dowait){
928    n_sighdl_t opipe;
929    struct mx_child_ctx cc;
930    boole rv;
931    struct a_fs_ent *fsep;
932    NYD_IN;
933 
934    for(fsep = a_fs_fp_head;; fsep = fsep->fse_link)
935       if(UNLIKELY(fsep == NIL)){
936          DBG( n_alert(_("pipe_close: invalid file pointer")); )
937          rv = FAL0;
938          goto jleave;
939       }else if(fsep->fse_fp == ptr)
940          break;
941    cc = fsep->fse_cc;
942 
943    ASSERT(!(fsep->fse_flags & a_FS_EF_FD_PASS_NEEDS_WAIT) || dowait);
944 
945    a_fs_unregister_file(ptr);
946 
947    opipe = safe_signal(SIGPIPE, SIG_IGN); /* TODO only because we jump stuff */
948    fclose(ptr);
949    safe_signal(SIGPIPE, opipe);
950 
951    if(dowait)
952       rv = (mx_child_wait(&cc) && cc.cc_exit_status == 0);
953    else{
954       mx_child_forget(&cc);
955       rv = TRU1;
956    }
957 
958 jleave:
959    NYD_OU;
960    return rv;
961 }
962 
963 boole
mx_fs_flush(FILE * fp)964 mx_fs_flush(FILE *fp){
965    boole rv;
966    NYD_IN;
967 
968    rv = (fflush(fp) != EOF);
969 
970    NYD_OU;
971    return rv;
972 }
973 
974 void
mx_fs_close_all(void)975 mx_fs_close_all(void){
976    NYD_IN;
977 
978    while(a_fs_fp_head != NIL)
979       if((a_fs_fp_head->fse_flags & a_FS_EF_MASK) == a_FS_EF_PIPE)
980          mx_fs_pipe_close(a_fs_fp_head->fse_fp, TRU1);
981       else
982          mx_fs_close(a_fs_fp_head->fse_fp);
983 
984    NYD_OU;
985 }
986 
987 void
mx_fs_linepool_aquire(char ** dpp,uz * dsp)988 mx_fs_linepool_aquire(char **dpp, uz *dsp){
989    struct a_fs_lpool_ent *lpep;
990    NYD2_IN;
991 
992    if((lpep = a_fs_lpool_free) != NIL)
993       a_fs_lpool_free = lpep->fsle_last;
994    else
995       lpep = su_TCALLOC(struct a_fs_lpool_ent, 1);
996 
997    lpep->fsle_last = a_fs_lpool_used;
998    a_fs_lpool_used = lpep;
999    *dpp = lpep->fsle_dat;
1000    lpep->fsle_dat = NIL;
1001    *dsp = lpep->fsle_size;
1002    lpep->fsle_size = 0;
1003 
1004    NYD2_OU;
1005 }
1006 
1007 void
mx_fs_linepool_release(char * dp,uz ds)1008 mx_fs_linepool_release(char *dp, uz ds){
1009    struct a_fs_lpool_ent *lpep;
1010    NYD2_IN;
1011 
1012    ASSERT(a_fs_lpool_used != NIL);
1013    lpep = a_fs_lpool_used;
1014    a_fs_lpool_used = lpep->fsle_last;
1015 
1016    lpep->fsle_last = a_fs_lpool_free;
1017    a_fs_lpool_free = lpep;
1018    lpep->fsle_dat = dp;
1019    lpep->fsle_size = ds;
1020 
1021    NYD2_OU;
1022 }
1023 
boole(mx_fs_linepool_book)1024 boole
1025 (mx_fs_linepool_book)(char **dp, uz *dsp, uz len, uz toadd
1026       su_DBG_LOC_ARGS_DECL){
1027    boole rv;
1028    NYD2_IN;
1029 
1030    rv = FAL0;
1031 
1032    if(UZ_MAX - 192 <= toadd)
1033       goto jeoverflow;
1034    toadd = (toadd + 192) & ~127;
1035 
1036    if(!su_mem_get_can_book(1, len, toadd))
1037       goto jeoverflow;
1038    len += toadd;
1039 
1040    if(*dsp < len){
1041       char *ndp;
1042 
1043       if((ndp = su_MEM_REALLOC_LOCOR(*dp, len, su_DBG_LOC_ARGS_ORUSE)) == NIL)
1044          goto jleave;
1045       *dp = ndp;
1046       *dsp = len;
1047    }
1048 
1049    rv = TRU1;
1050 jleave:
1051    NYD2_OU;
1052    return rv;
1053 
1054 jeoverflow:
1055    su_state_err(su_STATE_ERR_OVERFLOW, (su_STATE_ERR_PASS |
1056       su_STATE_ERR_NOERRNO), _("fs_linepool_book(): buffer size overflow"));
1057    goto jleave;
1058 }
1059 
1060 void
mx_fs_linepool_cleanup(boole completely)1061 mx_fs_linepool_cleanup(boole completely){
1062    struct a_fs_lpool_ent *lpep, *tmp, *keep;
1063    NYD2_IN;
1064 
1065    if(!completely)
1066       completely = 3; /* XXX magic */
1067    else
1068       completely = FAL0;
1069 
1070    lpep = a_fs_lpool_free;
1071    a_fs_lpool_free = keep = NIL;
1072 jredo:
1073    while((tmp = lpep) != NIL){
1074       void *vp;
1075 
1076       lpep = lpep->fsle_last;
1077 
1078       if((vp = tmp->fsle_dat) != NIL && completely &&
1079             tmp->fsle_size <= LINESIZE * 2){
1080          --completely;
1081          tmp->fsle_last = a_fs_lpool_free;
1082          a_fs_lpool_free = tmp;
1083       }else{
1084          if(vp != NIL)
1085             su_FREE(vp);
1086          su_FREE(tmp);
1087       }
1088    }
1089 
1090    /* Only called from go_main_loop(), save to throw the hulls away */
1091    if((lpep = a_fs_lpool_used) != NIL){
1092       a_fs_lpool_used = NIL;
1093       goto jredo;
1094    }
1095 
1096    NYD2_OU;
1097 }
1098 
1099 /* TODO The rest below is old-style v15 */
1100 
1101 /* line is a buffer with the result of fgets(). Returns the first newline or
1102  * the last character read */
1103 static uz     _length_of_line(char const *line, uz linesize);
1104 
1105 /* Read a line, one character at a time */
1106 static char *a_fs_fgetline_byone(char **line, uz *linesize, uz *llen_or_nil,
1107       FILE *fp, int appendnl, uz n  su_DBG_LOC_ARGS_DECL);
1108 
1109 static uz
_length_of_line(char const * line,uz linesize)1110 _length_of_line(char const *line, uz linesize)
1111 {
1112    uz i;
1113    NYD2_IN;
1114 
1115    /* Last character is always '\0' and was added by fgets() */
1116    for (--linesize, i = 0; i < linesize; i++)
1117       if (line[i] == '\n')
1118          break;
1119    i = (i < linesize) ? i + 1 : linesize;
1120 
1121    NYD2_OU;
1122    return i;
1123 }
1124 
1125 static char *
a_fs_fgetline_byone(char ** line,uz * linesize,uz * llen_or_nil,FILE * fp,int appendnl,uz n su_DBG_LOC_ARGS_DECL)1126 a_fs_fgetline_byone(char **line, uz *linesize, uz *llen_or_nil, FILE *fp,
1127    int appendnl, uz n  su_DBG_LOC_ARGS_DECL)
1128 {
1129    char *rv;
1130    int c;
1131    NYD2_IN;
1132 
1133    ASSERT(*linesize == 0 || *line != NULL);
1134 
1135    /* Always leave room for NETNL, not only \n */
1136    for (rv = *line;;) {
1137       if (*linesize <= LINESIZE || n >= *linesize - 128) {
1138          *linesize += ((rv == NULL) ? LINESIZE + n + 2 : 256);
1139          *line = rv = su_MEM_REALLOC_LOCOR(rv, *linesize,
1140                su_DBG_LOC_ARGS_ORUSE);
1141       }
1142       c = getc(fp);
1143       if (c != EOF) {
1144          rv[n++] = c;
1145          rv[n] = '\0';
1146          if (c == '\n') {
1147             n_pstate |= n_PS_READLINE_NL;
1148             break;
1149          }
1150       } else {
1151          if (n > 0 && feof(fp)) {
1152             if (appendnl) {
1153                rv[n++] = '\n';
1154                rv[n] = '\0';
1155             }
1156             break;
1157          } else {
1158             rv = NULL;
1159             goto jleave;
1160          }
1161       }
1162    }
1163 
1164    if(llen_or_nil != NIL)
1165       *llen_or_nil = n;
1166 
1167 jleave:
1168    NYD2_OU;
1169    return rv;
1170 }
1171 
1172 char *
1173 (fgetline)(char **line, uz *linesize, uz *cnt, uz *llen_or_nil, FILE *fp,
1174    int appendnl su_DBG_LOC_ARGS_DECL)
1175 {
1176    uz i_llen, size;
1177    char *rv;
1178    NYD2_IN;
1179 
1180    n_pstate &= ~n_PS_READLINE_NL;
1181 
1182    if(llen_or_nil != NIL)
1183       *llen_or_nil = 0;
1184 
1185    if(cnt == NIL){
1186       /* Without we cannot determine where the chars returned by fgets()
1187        * end if there's no newline.  We have to read one character by one */
1188       rv = a_fs_fgetline_byone(line, linesize, llen_or_nil, fp, appendnl, 0
1189             su_DBG_LOC_ARGS_USE);
1190       goto jleave;
1191    }
1192 
1193    if ((rv = *line) == NULL || *linesize < LINESIZE)
1194       *line = rv = su_MEM_REALLOC_LOCOR(rv, *linesize = LINESIZE,
1195             su_DBG_LOC_ARGS_ORUSE);
1196    size = (*linesize <= *cnt) ? *linesize : *cnt + 1;
1197    if (size <= 1 || fgets(rv, size, fp) == NULL) {
1198       /* Leave llen untouched; it is used to determine whether the last line
1199        * was \n-terminated in some callers */
1200       rv = NULL;
1201       goto jleave;
1202    }
1203 
1204    i_llen = _length_of_line(rv, size);
1205    *cnt -= i_llen;
1206    while (rv[i_llen - 1] != '\n') {
1207       *line = rv = su_MEM_REALLOC_LOCOR(rv, *linesize += 256,
1208             su_DBG_LOC_ARGS_ORUSE);
1209       size = *linesize - i_llen;
1210       size = (size <= *cnt) ? size : *cnt + 1;
1211       if (size <= 1) {
1212          if (appendnl) {
1213             rv[i_llen++] = '\n';
1214             rv[i_llen] = '\0';
1215          }
1216          break;
1217       } else if (fgets(rv + i_llen, size, fp) == NULL) {
1218          if (!feof(fp)) {
1219             rv = NULL;
1220             goto jleave;
1221          }
1222          if (appendnl) {
1223             rv[i_llen++] = '\n';
1224             rv[i_llen] = '\0';
1225          }
1226          break;
1227       }
1228       size = _length_of_line(rv + i_llen, size);
1229       i_llen += size;
1230       *cnt -= size;
1231    }
1232 
1233    /* Always leave room for NETNL, not only \n */
1234    if(appendnl && *linesize - i_llen < 3)
1235       *line = rv = su_MEM_REALLOC_LOCOR(rv, *linesize += 256,
1236             su_DBG_LOC_ARGS_ORUSE);
1237 
1238    if(llen_or_nil != NIL)
1239       *llen_or_nil = i_llen;
1240 jleave:
1241    NYD2_OU;
1242    return rv;
1243 }
1244 
1245 int
1246 (readline_restart)(FILE *ibuf, char **linebuf, uz *linesize, uz n
1247    su_DBG_LOC_ARGS_DECL)
1248 {
1249    /* TODO readline_restart(): always *appends* LF just to strip it again;
1250     * TODO should be configurable just as for fgetline(); ..or whatever..
1251     * TODO intwrap */
1252    int rv = -1;
1253    long size;
1254    NYD2_IN;
1255 
1256    clearerr(ibuf);
1257 
1258    /* Interrupts will cause trouble if we are inside a stdio call. As this is
1259     * only relevant if input is from tty, bypass it by read(), then */
1260    if ((n_psonce & n_PSO_TTYIN) && fileno(ibuf) == 0) {
1261       ASSERT(*linesize == 0 || *linebuf != NULL);
1262       n_pstate &= ~n_PS_READLINE_NL;
1263       for (;;) {
1264          if (*linesize <= LINESIZE || n >= *linesize - 128) {
1265             *linesize += ((*linebuf == NULL) ? LINESIZE + n + 1 : 256);
1266             *linebuf = su_MEM_REALLOC_LOCOR(*linebuf, *linesize,
1267                   su_DBG_LOC_ARGS_ORUSE);
1268          }
1269 jagain:
1270          size = read(0, *linebuf + n, *linesize - n - 1);
1271          if (size > 0) {
1272             n += size;
1273             (*linebuf)[n] = '\0';
1274             if ((*linebuf)[n - 1] == '\n') {
1275                n_pstate |= n_PS_READLINE_NL;
1276                break;
1277             }
1278          } else {
1279             if (size == -1 && su_err_no() == su_ERR_INTR)
1280                goto jagain;
1281             /* TODO eh.  what is this?  that now supposed to be a line?!? */
1282             if (n > 0) {
1283                if ((*linebuf)[n - 1] != '\n') {
1284                   (*linebuf)[n++] = '\n';
1285                   (*linebuf)[n] = '\0';
1286                } else
1287                   n_pstate |= n_PS_READLINE_NL;
1288                break;
1289             } else
1290                goto jleave;
1291          }
1292       }
1293    } else {
1294       /* Not reading from standard input or standard input not a terminal. We
1295        * read one char at a time as it is the only way to get lines with
1296        * embedded NUL characters in standard stdio */
1297       if(a_fs_fgetline_byone(linebuf, linesize, &n, ibuf, 1,
1298             n su_DBG_LOC_ARGS_USE) == NIL)
1299          goto jleave;
1300    }
1301    if (n > 0 && (*linebuf)[n - 1] == '\n')
1302       (*linebuf)[--n] = '\0';
1303 
1304    rv = (int)n;
1305 jleave:
1306    NYD2_OU;
1307    return rv;
1308 }
1309 
1310 off_t
fsize(FILE * iob)1311 fsize(FILE *iob)
1312 {
1313    struct stat sbuf;
1314    off_t rv;
1315    NYD_IN;
1316 
1317    rv = (fstat(fileno(iob), &sbuf) == -1) ? 0 : sbuf.st_size;
1318 
1319    NYD_OU;
1320    return rv;
1321 }
1322 
1323 #include "su/code-ou.h"
1324 /* s-it-mode */
1325