2 * ========================================================================
3 * Copyright 2013-2022 Eduardo Chappa
4 * Copyright 2006-2008 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
16 * GENERALIZED STORAGE FUNCTIONS. Idea is to allow creation of
17 * storage objects that can be written into and read from without
18 * the caller knowing if the storage is core or in a file
23 #include "../pith/headers.h"
24 #include "../pith/store.h"
25 #include "../pith/status.h"
26 #include "../pith/state.h"
27 #include "../pico/keydefs.h"
29 #include <openssl/buffer.h>
36 void *so_file_open(STORE_S
*);
37 int so_cs_writec(int, STORE_S
*);
38 int so_cs_writec_locale(int, STORE_S
*);
39 int so_file_writec(int, STORE_S
*);
40 int so_file_writec_locale(int, STORE_S
*);
41 int so_cs_readc(unsigned char *, STORE_S
*);
42 int so_cs_readc_locale(unsigned char *, STORE_S
*);
43 int so_cs_readc_getchar(unsigned char *c
, void *extraarg
);
44 int so_file_readc(unsigned char *, STORE_S
*);
45 int so_file_readc_locale(unsigned char *, STORE_S
*);
46 int so_file_readc_getchar(unsigned char *c
, void *extraarg
);
47 int so_cs_puts(STORE_S
*, char *);
48 int so_cs_puts_locale(STORE_S
*, char *);
49 int so_file_puts(STORE_S
*, char *);
50 int so_file_puts_locale(STORE_S
*, char *);
51 int so_reaquire(STORE_S
*);
53 int so_file_readc_windows(unsigned char *, STORE_S
*);
56 int so_bio_writec(int, STORE_S
*);
57 int so_bio_readc(unsigned char *, STORE_S
*);
58 int so_bio_puts(STORE_S
*, char *);
63 * place holders for externally defined storage object driver
65 static struct externalstoreobjectdata
{
66 STORE_S
*(*get
)(void);
67 int (*give
)(STORE_S
**);
68 int (*writec
)(int, STORE_S
*);
69 int (*readc
)(unsigned char *, STORE_S
*);
70 int (*puts
)(STORE_S
*, char *);
71 int (*seek
)(STORE_S
*, long, int);
72 int (*truncate
)(STORE_S
*, off_t
);
73 int (*tell
)(STORE_S
*);
77 #define MSIZE_INIT 8192
78 #define MSIZE_INC 4096
82 #define OP_MD_USER (S_IREAD | S_IWRITE)
84 #define OP_MD_USER 0600
88 #define OP_MD_ALL (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | \
91 #define OP_MD_ALL 0666
96 * allocate resources associated with the specified type of
97 * storage. If requesting a named file object, open it for
98 * appending, else just open a temp file.
100 * return the filled in storage object
103 so_get(SourceType source
, char *name
, int rtype
)
104 /* requested storage type */
106 /* file access type */
108 STORE_S
*so
= (STORE_S
*)fs_get(sizeof(STORE_S
));
110 memset(so
, 0, sizeof(STORE_S
));
113 so
->cb
.cbuf
[0] = '\0';
114 so
->cb
.cbufp
= so
->cb
.cbuf
;
115 so
->cb
.cbufend
= so
->cb
.cbuf
;
117 if(name
) /* stash the name */
118 so
->name
= cpystr(name
);
120 else if(source
== TmpFileStar
|| source
== FileStar
){
122 * Coerce to TmpFileStar. The MSC library's "tmpfile()"
123 * doesn't observe the "TMP" or "TEMP" environment vars and
124 * always wants to write "\". This is problematic in shared,
125 * networked environments.
127 source
= TmpFileStar
;
128 so
->name
= temp_nam(NULL
, "pi");
131 else if(source
== TmpFileStar
) /* make one up! */
132 so
->name
= temp_nam(NULL
, "pine-tmp");
136 if(so
->src
== FileStar
|| so
->src
== TmpFileStar
){
138 so
->writec
= so_file_writec
;
139 so
->readc
= (rtype
& READ_FROM_LOCALE
) ? so_file_readc_windows
142 so
->writec
= (rtype
& WRITE_TO_LOCALE
) ? so_file_writec_locale
144 so
->readc
= (rtype
& READ_FROM_LOCALE
) ? so_file_readc_locale
147 so
->puts
= (rtype
& WRITE_TO_LOCALE
) ? so_file_puts_locale
151 * The reason for both FileStar and TmpFileStar types is
152 * that, named or unnamed, TmpFileStar's are unlinked
153 * when the object is given back to the system. This is
154 * useful for keeping us from running out of file pointers as
155 * the pointer associated with the object can be temporarily
156 * returned to the system without destroying the object.
158 * The programmer is warned to be careful not to assign the
159 * TmpFileStar type to any files that are expected to remain
160 * after the dust has settled!
163 if(!(so
->txt
= so_file_open(so
))){
164 dprint((1, "so_get error: %s : %s",
165 so
->name
? so
->name
: "?",
166 error_description(errno
)));
167 if(source
== TmpFileStar
)
168 our_unlink(so
->name
);
170 fs_give((void **)&so
->name
);
171 fs_give((void **)&so
); /* so freed & set to NULL */
175 if(!(so
->txt
= (void *) create_tmpfile())){
176 dprint((1, "so_get error: tmpfile : %s",
177 error_description(errno
)));
178 fs_give((void **)&so
); /* so freed & set to NULL */
182 else if(so
->src
== ExternalText
&& etsod
.get
){
183 so
->writec
= etsod
.writec
;
184 so
->readc
= etsod
.readc
;
185 so
->puts
= etsod
.puts
;
186 if(!(so
->txt
= (*etsod
.get
)())){
187 dprint((1, "so_get error: external driver allocation error"));
190 fs_give((void **)&so
->name
);
192 fs_give((void **)&so
); /* so freed & set to NULL */
196 else if(so
->src
== BioType
){
197 so
->writec
= so_bio_writec
;
198 so
->readc
= so_bio_readc
;
199 so
->puts
= so_bio_puts
;
201 if(!(so
->txt
= BIO_new(BIO_s_mem()))){
202 dprint((1, "so_get error: BIO driver allocation error"));
205 fs_give((void **) &so
->name
);
207 fs_give((void **) &so
); /* so freed & set to NULL */
212 so
->writec
= (rtype
& WRITE_TO_LOCALE
) ? so_cs_writec_locale
214 so
->readc
= (rtype
& READ_FROM_LOCALE
) ? so_cs_readc_locale
216 so
->puts
= (rtype
& WRITE_TO_LOCALE
) ? so_cs_puts_locale
219 so
->txt
= (void *)fs_get((size_t) MSIZE_INIT
* sizeof(char));
220 so
->dp
= so
->eod
= (unsigned char *) so
->txt
;
221 so
->eot
= so
->dp
+ MSIZE_INIT
;
222 memset(so
->eod
, 0, so
->eot
- so
->eod
);
230 * so_give - free resources associated with a storage object and then
234 so_give(STORE_S
**so
)
241 if((*so
)->src
== FileStar
|| (*so
)->src
== TmpFileStar
){
242 if((*so
)->txt
) /* disassociate from storage */
243 ret
= fclose((FILE *)(*so
)->txt
) == EOF
? -1 : 0;
245 if((*so
)->name
&& (*so
)->src
== TmpFileStar
)
246 our_unlink((*so
)->name
); /* really disassociate! */
248 else if((*so
)->txt
&& (*so
)->src
== ExternalText
){
250 (*etsod
.give
)((*so
)->txt
);
253 else if((*so
)->txt
&& (*so
)->src
== BioType
){
254 BIO
*b
= (BIO
*) (*so
)->txt
;
260 fs_give((void **)&((*so
)->txt
));
263 fs_give((void **)&((*so
)->name
)); /* blast the name */
265 /* release attribute list */
266 mail_free_body_parameter(&(*so
)->attr
);
268 fs_give((void **)so
); /* release the object */
275 * so_register_external_driver - hokey way to get pico-dependent storage object
276 * support out of pith library
279 so_register_external_driver(STORE_S
*(*get
)(void),
280 int (*give
)(STORE_S
**),
281 int (*writec
)(int, STORE_S
*),
282 int (*readc
)(unsigned char *, STORE_S
*),
283 int (*puts
)(STORE_S
*, char *),
284 int (*seek
)(STORE_S
*, long int, int),
285 int (*truncate
)(STORE_S
*, off_t
),
286 int (*tell
)(STORE_S
*))
288 memset(&etsod
, 0, sizeof(etsod
));
296 etsod
.writec
= writec
;
308 etsod
.truncate
= truncate
;
316 so_file_open(STORE_S
*so
)
318 char *type
= ((so
->flags
) & WRITE_ACCESS
) ? "a+" : "r";
320 mode
= (((so
->flags
) & OWNER_ONLY
) || so
->src
== TmpFileStar
)
321 ? OP_MD_USER
: OP_MD_ALL
;
324 * Careful. EDIT_ACCESS and WRITE_TO_LOCALE/READ_FROM_LOCALE will
327 if(so
->flags
& WRITE_ACCESS
){
328 flags
= O_RDWR
| O_CREAT
| O_APPEND
| O_BINARY
;
332 if(so
->flags
& READ_FROM_LOCALE
){
341 * Use open instead of fopen so we can make temp files private.
342 * I believe the "b" or "t" in the type isn't necessary in the fdopen call
343 * because we already set O_BINARY or _O_U8TEXT or _O_WTEXT in the open.
345 return(((fd
= our_open(so
->name
, flags
, mode
)) > -1)
346 ? (so
->txt
= (void *) fdopen(fd
, type
)) : NULL
);
351 * put a character into the specified storage object,
352 * expanding if necessary
354 * return 1 on success and 0 on failure
357 so_cs_writec(int c
, STORE_S
*so
)
359 if(so
->dp
>= so
->eot
){
361 size_t cur_o
= so
->dp
- (unsigned char *) so
->txt
;
362 size_t data_o
= so
->eod
- (unsigned char *) so
->txt
;
363 size_t size
= (so
->eot
- (unsigned char *) so
->txt
);
366 * We estimate the size we're going to need at the beginning
367 * so it shouldn't normally happen that we run out of space and
368 * come here. If we do come here, it is probably because there
369 * are lots of handles in the message or lots of quote coloring.
370 * In either case, the overhead of those is high so we don't want
371 * to keep adding little bits and resizing. Add 50% each time
374 incr
= MAX(size
/2, MSIZE_INC
);
377 fs_resize(&so
->txt
, size
* sizeof(char));
378 so
->dp
= (unsigned char *) so
->txt
+ cur_o
;
379 so
->eod
= (unsigned char *) so
->txt
+ data_o
;
380 so
->eot
= (unsigned char *) so
->txt
+ size
;
381 memset(so
->eod
, 0, so
->eot
- so
->eod
);
384 *so
->dp
++ = (unsigned char) c
;
393 * The locale version converts from UTF-8 to user's locale charset
394 * before writing the characters.
397 so_cs_writec_locale(int c
, STORE_S
*so
)
401 unsigned char obuf
[MAX(MB_LEN_MAX
,32)];
403 if((outchars
= utf8_to_locale(c
, &so
->cb
, obuf
, sizeof(obuf
))) != 0){
404 for(i
= 0; i
< outchars
; i
++)
405 if(so_cs_writec(obuf
[i
], so
) != 1){
416 so_file_writec(int c
, STORE_S
*so
)
418 unsigned char ch
= (unsigned char) c
;
421 if(so
->txt
|| so_reaquire(so
))
423 rv
= fwrite(&ch
,sizeof(unsigned char),(size_t)1,(FILE *)so
->txt
);
424 while(!rv
&& ferror((FILE *)so
->txt
) && errno
== EINTR
);
431 * The locale version converts from UTF-8 to user's locale charset
432 * before writing the characters.
435 so_file_writec_locale(int c
, STORE_S
*so
)
439 unsigned char obuf
[MAX(MB_LEN_MAX
,32)];
441 if((outchars
= utf8_to_locale(c
, &so
->cb
, obuf
, sizeof(obuf
))) != 0){
442 for(i
= 0; i
< outchars
; i
++)
443 if(so_file_writec(obuf
[i
], so
) != 1){
454 * get a character from the specified storage object.
456 * return 1 on success and 0 on failure
459 so_cs_readc(unsigned char *c
, STORE_S
*so
)
461 return((so
->dp
< so
->eod
) ? *c
= *(so
->dp
)++, 1 : 0);
466 * The locale version converts from user's locale charset to UTF-8
467 * after reading the characters and before returning to the caller.
470 so_cs_readc_locale(unsigned char *c
, STORE_S
*so
)
472 return(generic_readc_locale(c
, so_cs_readc_getchar
, so
, &so
->cb
));
477 so_cs_readc_getchar(unsigned char *c
, void *extraarg
)
481 so
= (STORE_S
*) extraarg
;
482 return(so_cs_readc(c
, so
));
487 so_file_readc(unsigned char *c
, STORE_S
*so
)
491 if(so
->txt
|| so_reaquire(so
))
493 rv
= fread(c
, sizeof(char), (size_t)1, (FILE *)so
->txt
);
494 while(!rv
&& ferror((FILE *)so
->txt
) && errno
== EINTR
);
501 * The locale version converts from user's locale charset to UTF-8
502 * after reading the characters and before returning to the caller.
505 so_file_readc_locale(unsigned char *c
, STORE_S
*so
)
507 return(generic_readc_locale(c
, so_file_readc_getchar
, so
, &so
->cb
));
512 so_file_readc_getchar(unsigned char *c
, void *extraarg
)
516 so
= (STORE_S
*) extraarg
;
517 return(so_file_readc(c
, so
));
523 * Read unicode characters from windows filesystem and return
524 * them as a stream of UTF-8 characters. The stream is assumed
525 * opened so that it will know how to put together the unicode.
528 so_file_readc_windows(unsigned char *c
, STORE_S
*so
)
533 /* already got some from previous call? */
534 if(so
->cb
.cbufend
> so
->cb
.cbuf
){
538 if(so
->cb
.cbufp
>= so
->cb
.cbufend
){
539 so
->cb
.cbufend
= so
->cb
.cbuf
;
540 so
->cb
.cbufp
= so
->cb
.cbuf
;
546 if(so
->txt
|| so_reaquire(so
)){
547 /* windows only so second arg is ignored */
548 ucs
= read_a_wide_char((FILE *) so
->txt
, NULL
);
549 rv
= (ucs
== CCONV_EOF
) ? 0 : 1;
554 * Now we need to convert the UCS character to UTF-8
555 * and dole out the UTF-8 one char at a time.
557 so
->cb
.cbufend
= utf8_put(so
->cb
.cbuf
, (unsigned long) ucs
);
558 so
->cb
.cbufp
= so
->cb
.cbuf
;
559 if(so
->cb
.cbufend
> so
->cb
.cbuf
){
562 if(so
->cb
.cbufp
>= so
->cb
.cbufend
){
563 so
->cb
.cbufend
= so
->cb
.cbuf
;
564 so
->cb
.cbufp
= so
->cb
.cbuf
;
573 #endif /* _WINDOWS */
577 * write a string into the specified storage object,
578 * expanding if necessary (and cheating if the object
579 * happens to be a file!)
581 * return 1 on success and 0 on failure
584 so_cs_puts(STORE_S
*so
, char *s
)
586 int slen
= strlen(s
);
588 if(so
->dp
+ slen
>= so
->eot
){
589 register size_t cur_o
= so
->dp
- (unsigned char *) so
->txt
;
590 register size_t data_o
= so
->eod
- (unsigned char *) so
->txt
;
591 register size_t len
= so
->eot
- (unsigned char *) so
->txt
;
592 while(len
<= cur_o
+ slen
+ 1){
595 incr
= MAX(len
/2, MSIZE_INC
);
599 fs_resize(&so
->txt
, len
* sizeof(char));
600 so
->dp
= (unsigned char *)so
->txt
+ cur_o
;
601 so
->eod
= (unsigned char *)so
->txt
+ data_o
;
602 so
->eot
= (unsigned char *)so
->txt
+ len
;
603 memset(so
->eod
, 0, so
->eot
- so
->eod
);
606 memcpy(so
->dp
, s
, slen
);
616 so_cs_puts_locale(STORE_S
*so
, char *s
)
618 int slen
= strlen(s
);
621 if(!so_cs_writec_locale((unsigned char) *s
++, so
))
629 so_file_puts(STORE_S
*so
, char *s
)
633 if(!rv
&& (so
->txt
|| so_reaquire(so
)))
635 rv
= fwrite(s
, strlen(s
)*sizeof(char), (size_t)1, (FILE *)so
->txt
);
636 while(!rv
&& ferror((FILE *)so
->txt
) && errno
== EINTR
);
643 so_file_puts_locale(STORE_S
*so
, char *s
)
645 int slen
= strlen(s
);
648 if(!so_file_writec_locale((unsigned char) *s
++, so
))
657 * put a character into the specified storage object,
658 * expanding if necessary
660 * return 1 on success and 0 on failure
663 so_bio_writec(int c
, STORE_S
*so
)
665 if(so
->txt
&& so
->src
== BioType
){
667 BIO
*b
= (BIO
*) so
->txt
;
669 ch
[0] = (unsigned char) (c
& 0xff);
671 if(BIO_write(b
, ch
, 1) >= 1)
680 so_bio_readc(unsigned char *c
, STORE_S
*so
)
682 if(so
->txt
&& so
->src
== BioType
){
684 BIO
*b
= (BIO
*) so
->txt
;
686 if(BIO_read(b
, ch
, 1) >= 1){
697 * write a string into the specified storage object,
698 * expanding if necessary (and cheating if the object
699 * happens to be a file!)
701 * return 1 on success and 0 on failure
704 so_bio_puts(STORE_S
*so
, char *s
)
707 if(so
->txt
&& so
->src
== BioType
){
708 BIO
*b
= (BIO
*) so
->txt
;
709 int slen
= strlen(s
);
711 if(BIO_puts(b
, s
) >= slen
)
724 so_nputs(STORE_S
*so
, char *s
, long int n
)
727 if(!so_writec((unsigned char) *s
++, so
))
728 return(0); /* ERROR putting char ! */
735 * Position the storage object's pointer to the given offset
736 * from the start of the object's data.
739 so_seek(STORE_S
*so
, long int pos
, int orig
)
741 if(so
->src
== CharStar
){
743 case 0 : /* SEEK_SET */
744 return((pos
< so
->eod
- (unsigned char *) so
->txt
)
745 ? so
->dp
= (unsigned char *)so
->txt
+ pos
, 0 : -1);
746 case 1 : /* SEEK_CUR */
748 ? ((pos
< so
->eod
- so
->dp
) ? so
->dp
+= pos
, 0: -1)
750 ? ((-pos
< so
->dp
- (unsigned char *)so
->txt
)
751 ? so
->dp
+= pos
, 0 : -1)
753 case 2 : /* SEEK_END */
756 : ((-pos
<= so
->eod
- (unsigned char *) so
->txt
)
757 ? so
->dp
= so
->eod
+ pos
, 0 : -1));
762 else if(so
->src
== ExternalText
){
764 return((*etsod
.seek
)(so
->txt
, pos
, orig
));
766 fatal("programmer botch: unsupported so_seek call");
768 return(0); /* suppress dumb compiler warnings */
771 else if(so
->src
== BioType
){
772 BIO
*b
= (BIO
*) so
->txt
;
774 if(b
&& BIO_method_type(b
) != BIO_TYPE_MEM
)
780 else /* FileStar or TmpFileStar */
781 return((so
->txt
|| so_reaquire(so
))
782 ? fseek((FILE *)so
->txt
,pos
,orig
)
788 * Change the given storage object's size to that specified. If size
789 * is less than the current size, the internal pointer is adjusted and
790 * all previous data beyond the given size is lost.
792 * Returns 0 on failure.
795 so_truncate(STORE_S
*so
, long int size
)
797 if(so
->src
== CharStar
){
798 if(so
->eod
< (unsigned char *) so
->txt
+ size
){ /* alloc! */
799 unsigned char *newtxt
= (unsigned char *) so
->txt
;
800 register size_t len
= so
->eot
- (unsigned char *) so
->txt
;
803 len
+= MSIZE_INC
; /* need to resize! */
805 if(len
> so
->eot
- (unsigned char *) newtxt
){
806 fs_resize((void **) &newtxt
, len
* sizeof(char));
807 so
->eot
= newtxt
+ len
;
808 so
->eod
= newtxt
+ (so
->eod
- (unsigned char *) so
->txt
);
809 memset(so
->eod
, 0, so
->eot
- so
->eod
);
812 so
->eod
= newtxt
+ size
;
813 so
->dp
= newtxt
+ (so
->dp
- (unsigned char *) so
->txt
);
816 else if(so
->eod
> (unsigned char *) so
->txt
+ size
){
817 if(so
->dp
> (so
->eod
= (unsigned char *)so
->txt
+ size
))
820 memset(so
->eod
, 0, so
->eot
- so
->eod
);
825 else if(so
->src
== ExternalText
){
827 return((*etsod
.truncate
)(so
, (off_t
) size
));
829 fatal("programmer botch: unsupported so_truncate call");
831 return(0); /* suppress dumb compiler warnings */
834 else if(so
->src
== BioType
){
835 fatal("programmer botch: unsupported so_truncate call for BioType");
837 return(0); /* suppress dumb compiler warnings */
841 BIO
*b
= (BIO
*) so
->txt
;
844 BUF_MEM
*biobuf
= NULL
;
846 BIO_get_mem_ptr(b
, &biobuf
);
848 BUF_MEM_grow(biobuf
, size
);
857 else /* FileStar or TmpFileStar */
858 return(fflush((FILE *) so
->txt
) != EOF
859 && fseek((FILE *) so
->txt
, size
, 0) == 0
860 && ftruncate(fileno((FILE *)so
->txt
), (off_t
) size
) == 0);
865 * Report given storage object's position indicator.
866 * Returns 0 on failure.
871 if(so
->src
== CharStar
){
872 return((long) (so
->dp
- (unsigned char *) so
->txt
));
874 else if(so
->src
== ExternalText
){
876 return((*etsod
.tell
)(so
));
878 fatal("programmer botch: unsupported so_tell call");
880 return(0); /* suppress dumb compiler warnings */
883 else if(so
->src
== BioType
){
884 fatal("programmer botch: unsupported so_tell call for BioType");
886 return(0); /* suppress dumb compiler warnings */
889 else /* FileStar or TmpFileStar */
890 return(ftell((FILE *) so
->txt
));
895 * so_attr - hook to hang random attributes onto the storage object
898 so_attr(STORE_S
*so
, char *key
, char *value
)
902 PARAMETER
**pp
= &so
->attr
;
906 if((*pp
)->attribute
&& !strcmp(key
, (*pp
)->attribute
)){
908 fs_give((void **)&(*pp
)->value
);
916 *pp
= mail_newbody_parameter();
917 (*pp
)->attribute
= cpystr(key
);
922 return((*pp
)->value
= cpystr(value
));
927 for(p
= so
->attr
; p
; p
= p
->next
)
928 if(p
->attribute
&& !strcmp(key
, p
->attribute
))
938 * so_release - a rather misnamed function. the idea is to release
939 * what system resources we can (e.g., open files).
940 * while maintaining a reference to it.
941 * it's up to the functions that deal with this object
942 * next to re-aquire those resources.
945 so_release(STORE_S
*so
)
947 if(so
->txt
&& so
->name
&& (so
->src
== FileStar
|| so
->src
== TmpFileStar
)){
948 if(fget_pos((FILE *)so
->txt
, (fpos_t *)&(so
->used
)) == 0){
949 fclose((FILE *)so
->txt
); /* free the handle! */
959 * so_reaquire - get any previously released system resources we
960 * may need for the given storage object.
961 * NOTE: at the moment, only FILE * types of objects are
962 * effected, so it only needs to be called before
963 * references to them.
967 so_reaquire(STORE_S
*so
)
971 if(!so
->txt
&& (so
->src
== FileStar
|| so
->src
== TmpFileStar
)){
972 if(!(so
->txt
= so_file_open(so
))){
973 q_status_message2(SM_ORDER
,3,5, "ERROR reopening %.200s : %.200s",
974 so
->name
, error_description(errno
));
977 else if(fset_pos((FILE *)so
->txt
, (fpos_t *)&(so
->used
))){
978 q_status_message2(SM_ORDER
, 3, 5,
979 "ERROR positioning in %.200s : %.200s",
980 so
->name
, error_description(errno
));
990 * so_text - return a pointer to the text the store object passed
995 return((so
) ? so
->txt
: NULL
);
1000 * Similar to fgets but reading from a storage object.
1003 so_fgets(STORE_S
*so
, char *s
, size_t size
)
1008 while(--size
> 0 && so_readc(&c
, so
) > 0){
1016 return((p
>s
) ? s
: NULL
);