1 #if !defined(lint) && !defined(DOS)
2 static char rcsid
[] = "$Id: store.c 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $";
6 * ========================================================================
7 * Copyright 2013-2017 Eduardo Chappa
8 * Copyright 2006-2008 University of Washington
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
20 * GENERALIZED STORAGE FUNCTIONS. Idea is to allow creation of
21 * storage objects that can be written into and read from without
22 * the caller knowing if the storage is core or in a file
27 #include "../pith/headers.h"
28 #include "../pith/store.h"
29 #include "../pith/status.h"
30 #include "../pith/state.h"
31 #include "../pico/keydefs.h"
33 #include <openssl/buffer.h>
40 void *so_file_open(STORE_S
*);
41 int so_cs_writec(int, STORE_S
*);
42 int so_cs_writec_locale(int, STORE_S
*);
43 int so_file_writec(int, STORE_S
*);
44 int so_file_writec_locale(int, STORE_S
*);
45 int so_cs_readc(unsigned char *, STORE_S
*);
46 int so_cs_readc_locale(unsigned char *, STORE_S
*);
47 int so_cs_readc_getchar(unsigned char *c
, void *extraarg
);
48 int so_file_readc(unsigned char *, STORE_S
*);
49 int so_file_readc_locale(unsigned char *, STORE_S
*);
50 int so_file_readc_getchar(unsigned char *c
, void *extraarg
);
51 int so_cs_puts(STORE_S
*, char *);
52 int so_cs_puts_locale(STORE_S
*, char *);
53 int so_file_puts(STORE_S
*, char *);
54 int so_file_puts_locale(STORE_S
*, char *);
55 int so_reaquire(STORE_S
*);
57 int so_file_readc_windows(unsigned char *, STORE_S
*);
60 int so_bio_writec(int, STORE_S
*);
61 int so_bio_readc(unsigned char *, STORE_S
*);
62 int so_bio_puts(STORE_S
*, char *);
67 * place holders for externally defined storage object driver
69 static struct externalstoreobjectdata
{
70 STORE_S
*(*get
)(void);
71 int (*give
)(STORE_S
**);
72 int (*writec
)(int, STORE_S
*);
73 int (*readc
)(unsigned char *, STORE_S
*);
74 int (*puts
)(STORE_S
*, char *);
75 int (*seek
)(STORE_S
*, long, int);
76 int (*truncate
)(STORE_S
*, off_t
);
77 int (*tell
)(STORE_S
*);
81 #define MSIZE_INIT 8192
82 #define MSIZE_INC 4096
86 #define OP_MD_USER (S_IREAD | S_IWRITE)
88 #define OP_MD_USER 0600
92 #define OP_MD_ALL (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | \
95 #define OP_MD_ALL 0666
100 * allocate resources associated with the specified type of
101 * storage. If requesting a named file object, open it for
102 * appending, else just open a temp file.
104 * return the filled in storage object
107 so_get(SourceType source
, char *name
, int rtype
)
108 /* requested storage type */
110 /* file access type */
112 STORE_S
*so
= (STORE_S
*)fs_get(sizeof(STORE_S
));
114 memset(so
, 0, sizeof(STORE_S
));
117 so
->cb
.cbuf
[0] = '\0';
118 so
->cb
.cbufp
= so
->cb
.cbuf
;
119 so
->cb
.cbufend
= so
->cb
.cbuf
;
121 if(name
) /* stash the name */
122 so
->name
= cpystr(name
);
124 else if(source
== TmpFileStar
|| source
== FileStar
){
126 * Coerce to TmpFileStar. The MSC library's "tmpfile()"
127 * doesn't observe the "TMP" or "TEMP" environment vars and
128 * always wants to write "\". This is problematic in shared,
129 * networked environments.
131 source
= TmpFileStar
;
132 so
->name
= temp_nam(NULL
, "pi");
135 else if(source
== TmpFileStar
) /* make one up! */
136 so
->name
= temp_nam(NULL
, "pine-tmp");
140 if(so
->src
== FileStar
|| so
->src
== TmpFileStar
){
142 so
->writec
= so_file_writec
;
143 so
->readc
= (rtype
& READ_FROM_LOCALE
) ? so_file_readc_windows
146 so
->writec
= (rtype
& WRITE_TO_LOCALE
) ? so_file_writec_locale
148 so
->readc
= (rtype
& READ_FROM_LOCALE
) ? so_file_readc_locale
151 so
->puts
= (rtype
& WRITE_TO_LOCALE
) ? so_file_puts_locale
155 * The reason for both FileStar and TmpFileStar types is
156 * that, named or unnamed, TmpFileStar's are unlinked
157 * when the object is given back to the system. This is
158 * useful for keeping us from running out of file pointers as
159 * the pointer associated with the object can be temporarily
160 * returned to the system without destroying the object.
162 * The programmer is warned to be careful not to assign the
163 * TmpFileStar type to any files that are expected to remain
164 * after the dust has settled!
167 if(!(so
->txt
= so_file_open(so
))){
168 dprint((1, "so_get error: %s : %s",
169 so
->name
? so
->name
: "?",
170 error_description(errno
)));
171 if(source
== TmpFileStar
)
172 our_unlink(so
->name
);
174 fs_give((void **)&so
->name
);
175 fs_give((void **)&so
); /* so freed & set to NULL */
179 if(!(so
->txt
= (void *) create_tmpfile())){
180 dprint((1, "so_get error: tmpfile : %s",
181 error_description(errno
)));
182 fs_give((void **)&so
); /* so freed & set to NULL */
186 else if(so
->src
== ExternalText
&& etsod
.get
){
187 so
->writec
= etsod
.writec
;
188 so
->readc
= etsod
.readc
;
189 so
->puts
= etsod
.puts
;
190 if(!(so
->txt
= (*etsod
.get
)())){
191 dprint((1, "so_get error: external driver allocation error"));
194 fs_give((void **)&so
->name
);
196 fs_give((void **)&so
); /* so freed & set to NULL */
200 else if(so
->src
== BioType
){
201 so
->writec
= so_bio_writec
;
202 so
->readc
= so_bio_readc
;
203 so
->puts
= so_bio_puts
;
205 if(!(so
->txt
= BIO_new(BIO_s_mem()))){
206 dprint((1, "so_get error: BIO driver allocation error"));
209 fs_give((void **) &so
->name
);
211 fs_give((void **) &so
); /* so freed & set to NULL */
216 so
->writec
= (rtype
& WRITE_TO_LOCALE
) ? so_cs_writec_locale
218 so
->readc
= (rtype
& READ_FROM_LOCALE
) ? so_cs_readc_locale
220 so
->puts
= (rtype
& WRITE_TO_LOCALE
) ? so_cs_puts_locale
223 so
->txt
= (void *)fs_get((size_t) MSIZE_INIT
* sizeof(char));
224 so
->dp
= so
->eod
= (unsigned char *) so
->txt
;
225 so
->eot
= so
->dp
+ MSIZE_INIT
;
226 memset(so
->eod
, 0, so
->eot
- so
->eod
);
234 * so_give - free resources associated with a storage object and then
238 so_give(STORE_S
**so
)
245 if((*so
)->src
== FileStar
|| (*so
)->src
== TmpFileStar
){
246 if((*so
)->txt
) /* disassociate from storage */
247 ret
= fclose((FILE *)(*so
)->txt
) == EOF
? -1 : 0;
249 if((*so
)->name
&& (*so
)->src
== TmpFileStar
)
250 our_unlink((*so
)->name
); /* really disassociate! */
252 else if((*so
)->txt
&& (*so
)->src
== ExternalText
){
254 (*etsod
.give
)((*so
)->txt
);
257 else if((*so
)->txt
&& (*so
)->src
== BioType
){
258 BIO
*b
= (BIO
*) (*so
)->txt
;
264 fs_give((void **)&((*so
)->txt
));
267 fs_give((void **)&((*so
)->name
)); /* blast the name */
269 /* release attribute list */
270 mail_free_body_parameter(&(*so
)->attr
);
272 fs_give((void **)so
); /* release the object */
279 * so_register_external_driver - hokey way to get pico-dependent storage object
280 * support out of pith library
283 so_register_external_driver(STORE_S
*(*get
)(void),
284 int (*give
)(STORE_S
**),
285 int (*writec
)(int, STORE_S
*),
286 int (*readc
)(unsigned char *, STORE_S
*),
287 int (*puts
)(STORE_S
*, char *),
288 int (*seek
)(STORE_S
*, long int, int),
289 int (*truncate
)(STORE_S
*, off_t
),
290 int (*tell
)(STORE_S
*))
292 memset(&etsod
, 0, sizeof(etsod
));
300 etsod
.writec
= writec
;
312 etsod
.truncate
= truncate
;
320 so_file_open(STORE_S
*so
)
322 char *type
= ((so
->flags
) & WRITE_ACCESS
) ? "a+" : "r";
324 mode
= (((so
->flags
) & OWNER_ONLY
) || so
->src
== TmpFileStar
)
325 ? OP_MD_USER
: OP_MD_ALL
;
328 * Careful. EDIT_ACCESS and WRITE_TO_LOCALE/READ_FROM_LOCALE will
331 if(so
->flags
& WRITE_ACCESS
){
332 flags
= O_RDWR
| O_CREAT
| O_APPEND
| O_BINARY
;
336 if(so
->flags
& READ_FROM_LOCALE
){
345 * Use open instead of fopen so we can make temp files private.
346 * I believe the "b" or "t" in the type isn't necessary in the fdopen call
347 * because we already set O_BINARY or _O_U8TEXT or _O_WTEXT in the open.
349 return(((fd
= our_open(so
->name
, flags
, mode
)) > -1)
350 ? (so
->txt
= (void *) fdopen(fd
, type
)) : NULL
);
355 * put a character into the specified storage object,
356 * expanding if neccessary
358 * return 1 on success and 0 on failure
361 so_cs_writec(int c
, STORE_S
*so
)
363 if(so
->dp
>= so
->eot
){
365 size_t cur_o
= so
->dp
- (unsigned char *) so
->txt
;
366 size_t data_o
= so
->eod
- (unsigned char *) so
->txt
;
367 size_t size
= (so
->eot
- (unsigned char *) so
->txt
);
370 * We estimate the size we're going to need at the beginning
371 * so it shouldn't normally happen that we run out of space and
372 * come here. If we do come here, it is probably because there
373 * are lots of handles in the message or lots of quote coloring.
374 * In either case, the overhead of those is high so we don't want
375 * to keep adding little bits and resizing. Add 50% each time
378 incr
= MAX(size
/2, MSIZE_INC
);
381 fs_resize(&so
->txt
, size
* sizeof(char));
382 so
->dp
= (unsigned char *) so
->txt
+ cur_o
;
383 so
->eod
= (unsigned char *) so
->txt
+ data_o
;
384 so
->eot
= (unsigned char *) so
->txt
+ size
;
385 memset(so
->eod
, 0, so
->eot
- so
->eod
);
388 *so
->dp
++ = (unsigned char) c
;
397 * The locale version converts from UTF-8 to user's locale charset
398 * before writing the characters.
401 so_cs_writec_locale(int c
, STORE_S
*so
)
405 unsigned char obuf
[MAX(MB_LEN_MAX
,32)];
407 if((outchars
= utf8_to_locale(c
, &so
->cb
, obuf
, sizeof(obuf
))) != 0){
408 for(i
= 0; i
< outchars
; i
++)
409 if(so_cs_writec(obuf
[i
], so
) != 1){
420 so_file_writec(int c
, STORE_S
*so
)
422 unsigned char ch
= (unsigned char) c
;
425 if(so
->txt
|| so_reaquire(so
))
427 rv
= fwrite(&ch
,sizeof(unsigned char),(size_t)1,(FILE *)so
->txt
);
428 while(!rv
&& ferror((FILE *)so
->txt
) && errno
== EINTR
);
435 * The locale version converts from UTF-8 to user's locale charset
436 * before writing the characters.
439 so_file_writec_locale(int c
, STORE_S
*so
)
443 unsigned char obuf
[MAX(MB_LEN_MAX
,32)];
445 if((outchars
= utf8_to_locale(c
, &so
->cb
, obuf
, sizeof(obuf
))) != 0){
446 for(i
= 0; i
< outchars
; i
++)
447 if(so_file_writec(obuf
[i
], so
) != 1){
458 * get a character from the specified storage object.
460 * return 1 on success and 0 on failure
463 so_cs_readc(unsigned char *c
, STORE_S
*so
)
465 return((so
->dp
< so
->eod
) ? *c
= *(so
->dp
)++, 1 : 0);
470 * The locale version converts from user's locale charset to UTF-8
471 * after reading the characters and before returning to the caller.
474 so_cs_readc_locale(unsigned char *c
, STORE_S
*so
)
476 return(generic_readc_locale(c
, so_cs_readc_getchar
, so
, &so
->cb
));
481 so_cs_readc_getchar(unsigned char *c
, void *extraarg
)
485 so
= (STORE_S
*) extraarg
;
486 return(so_cs_readc(c
, so
));
491 so_file_readc(unsigned char *c
, STORE_S
*so
)
495 if(so
->txt
|| so_reaquire(so
))
497 rv
= fread(c
, sizeof(char), (size_t)1, (FILE *)so
->txt
);
498 while(!rv
&& ferror((FILE *)so
->txt
) && errno
== EINTR
);
505 * The locale version converts from user's locale charset to UTF-8
506 * after reading the characters and before returning to the caller.
509 so_file_readc_locale(unsigned char *c
, STORE_S
*so
)
511 return(generic_readc_locale(c
, so_file_readc_getchar
, so
, &so
->cb
));
516 so_file_readc_getchar(unsigned char *c
, void *extraarg
)
520 so
= (STORE_S
*) extraarg
;
521 return(so_file_readc(c
, so
));
527 * Read unicode characters from windows filesystem and return
528 * them as a stream of UTF-8 characters. The stream is assumed
529 * opened so that it will know how to put together the unicode.
532 so_file_readc_windows(unsigned char *c
, STORE_S
*so
)
537 /* already got some from previous call? */
538 if(so
->cb
.cbufend
> so
->cb
.cbuf
){
542 if(so
->cb
.cbufp
>= so
->cb
.cbufend
){
543 so
->cb
.cbufend
= so
->cb
.cbuf
;
544 so
->cb
.cbufp
= so
->cb
.cbuf
;
550 if(so
->txt
|| so_reaquire(so
)){
551 /* windows only so second arg is ignored */
552 ucs
= read_a_wide_char((FILE *) so
->txt
, NULL
);
553 rv
= (ucs
== CCONV_EOF
) ? 0 : 1;
558 * Now we need to convert the UCS character to UTF-8
559 * and dole out the UTF-8 one char at a time.
561 so
->cb
.cbufend
= utf8_put(so
->cb
.cbuf
, (unsigned long) ucs
);
562 so
->cb
.cbufp
= so
->cb
.cbuf
;
563 if(so
->cb
.cbufend
> so
->cb
.cbuf
){
566 if(so
->cb
.cbufp
>= so
->cb
.cbufend
){
567 so
->cb
.cbufend
= so
->cb
.cbuf
;
568 so
->cb
.cbufp
= so
->cb
.cbuf
;
577 #endif /* _WINDOWS */
581 * write a string into the specified storage object,
582 * expanding if necessary (and cheating if the object
583 * happens to be a file!)
585 * return 1 on success and 0 on failure
588 so_cs_puts(STORE_S
*so
, char *s
)
590 int slen
= strlen(s
);
592 if(so
->dp
+ slen
>= so
->eot
){
593 register size_t cur_o
= so
->dp
- (unsigned char *) so
->txt
;
594 register size_t data_o
= so
->eod
- (unsigned char *) so
->txt
;
595 register size_t len
= so
->eot
- (unsigned char *) so
->txt
;
596 while(len
<= cur_o
+ slen
+ 1){
599 incr
= MAX(len
/2, MSIZE_INC
);
603 fs_resize(&so
->txt
, len
* sizeof(char));
604 so
->dp
= (unsigned char *)so
->txt
+ cur_o
;
605 so
->eod
= (unsigned char *)so
->txt
+ data_o
;
606 so
->eot
= (unsigned char *)so
->txt
+ len
;
607 memset(so
->eod
, 0, so
->eot
- so
->eod
);
610 memcpy(so
->dp
, s
, slen
);
620 so_cs_puts_locale(STORE_S
*so
, char *s
)
622 int slen
= strlen(s
);
625 if(!so_cs_writec_locale((unsigned char) *s
++, so
))
633 so_file_puts(STORE_S
*so
, char *s
)
637 if(!rv
&& (so
->txt
|| so_reaquire(so
)))
639 rv
= fwrite(s
, strlen(s
)*sizeof(char), (size_t)1, (FILE *)so
->txt
);
640 while(!rv
&& ferror((FILE *)so
->txt
) && errno
== EINTR
);
647 so_file_puts_locale(STORE_S
*so
, char *s
)
649 int slen
= strlen(s
);
652 if(!so_file_writec_locale((unsigned char) *s
++, so
))
661 * put a character into the specified storage object,
662 * expanding if neccessary
664 * return 1 on success and 0 on failure
667 so_bio_writec(int c
, STORE_S
*so
)
669 if(so
->txt
&& so
->src
== BioType
){
671 BIO
*b
= (BIO
*) so
->txt
;
673 ch
[0] = (unsigned char) (c
& 0xff);
675 if(BIO_write(b
, ch
, 1) >= 1)
684 so_bio_readc(unsigned char *c
, STORE_S
*so
)
686 if(so
->txt
&& so
->src
== BioType
){
688 BIO
*b
= (BIO
*) so
->txt
;
690 if(BIO_read(b
, ch
, 1) >= 1){
701 * write a string into the specified storage object,
702 * expanding if necessary (and cheating if the object
703 * happens to be a file!)
705 * return 1 on success and 0 on failure
708 so_bio_puts(STORE_S
*so
, char *s
)
711 if(so
->txt
&& so
->src
== BioType
){
712 BIO
*b
= (BIO
*) so
->txt
;
713 int slen
= strlen(s
);
715 if(BIO_puts(b
, s
) >= slen
)
728 so_nputs(STORE_S
*so
, char *s
, long int n
)
731 if(!so_writec((unsigned char) *s
++, so
))
732 return(0); /* ERROR putting char ! */
739 * Position the storage object's pointer to the given offset
740 * from the start of the object's data.
743 so_seek(STORE_S
*so
, long int pos
, int orig
)
745 if(so
->src
== CharStar
){
747 case 0 : /* SEEK_SET */
748 return((pos
< so
->eod
- (unsigned char *) so
->txt
)
749 ? so
->dp
= (unsigned char *)so
->txt
+ pos
, 0 : -1);
750 case 1 : /* SEEK_CUR */
752 ? ((pos
< so
->eod
- so
->dp
) ? so
->dp
+= pos
, 0: -1)
754 ? ((-pos
< so
->dp
- (unsigned char *)so
->txt
)
755 ? so
->dp
+= pos
, 0 : -1)
757 case 2 : /* SEEK_END */
760 : ((-pos
<= so
->eod
- (unsigned char *) so
->txt
)
761 ? so
->dp
= so
->eod
+ pos
, 0 : -1));
766 else if(so
->src
== ExternalText
){
768 return((*etsod
.seek
)(so
->txt
, pos
, orig
));
770 fatal("programmer botch: unsupported so_seek call");
772 return(0); /* suppress dumb compiler warnings */
775 else if(so
->src
== BioType
){
776 BIO
*b
= (BIO
*) so
->txt
;
778 if(b
&& BIO_method_type(b
) != BIO_TYPE_MEM
)
784 else /* FileStar or TmpFileStar */
785 return((so
->txt
|| so_reaquire(so
))
786 ? fseek((FILE *)so
->txt
,pos
,orig
)
792 * Change the given storage object's size to that specified. If size
793 * is less than the current size, the internal pointer is adjusted and
794 * all previous data beyond the given size is lost.
796 * Returns 0 on failure.
799 so_truncate(STORE_S
*so
, long int size
)
801 if(so
->src
== CharStar
){
802 if(so
->eod
< (unsigned char *) so
->txt
+ size
){ /* alloc! */
803 unsigned char *newtxt
= (unsigned char *) so
->txt
;
804 register size_t len
= so
->eot
- (unsigned char *) so
->txt
;
807 len
+= MSIZE_INC
; /* need to resize! */
809 if(len
> so
->eot
- (unsigned char *) newtxt
){
810 fs_resize((void **) &newtxt
, len
* sizeof(char));
811 so
->eot
= newtxt
+ len
;
812 so
->eod
= newtxt
+ (so
->eod
- (unsigned char *) so
->txt
);
813 memset(so
->eod
, 0, so
->eot
- so
->eod
);
816 so
->eod
= newtxt
+ size
;
817 so
->dp
= newtxt
+ (so
->dp
- (unsigned char *) so
->txt
);
820 else if(so
->eod
> (unsigned char *) so
->txt
+ size
){
821 if(so
->dp
> (so
->eod
= (unsigned char *)so
->txt
+ size
))
824 memset(so
->eod
, 0, so
->eot
- so
->eod
);
829 else if(so
->src
== ExternalText
){
831 return((*etsod
.truncate
)(so
, (off_t
) size
));
833 fatal("programmer botch: unsupported so_truncate call");
835 return(0); /* suppress dumb compiler warnings */
838 else if(so
->src
== BioType
){
839 fatal("programmer botch: unsupported so_truncate call for BioType");
841 return(0); /* suppress dumb compiler warnings */
845 BIO
*b
= (BIO
*) so
->txt
;
848 BUF_MEM
*biobuf
= NULL
;
850 BIO_get_mem_ptr(b
, &biobuf
);
852 BUF_MEM_grow(biobuf
, size
);
861 else /* FileStar or TmpFileStar */
862 return(fflush((FILE *) so
->txt
) != EOF
863 && fseek((FILE *) so
->txt
, size
, 0) == 0
864 && ftruncate(fileno((FILE *)so
->txt
), (off_t
) size
) == 0);
869 * Report given storage object's position indicator.
870 * Returns 0 on failure.
875 if(so
->src
== CharStar
){
876 return((long) (so
->dp
- (unsigned char *) so
->txt
));
878 else if(so
->src
== ExternalText
){
880 return((*etsod
.tell
)(so
));
882 fatal("programmer botch: unsupported so_tell call");
884 return(0); /* suppress dumb compiler warnings */
887 else if(so
->src
== BioType
){
888 fatal("programmer botch: unsupported so_tell call for BioType");
890 return(0); /* suppress dumb compiler warnings */
893 else /* FileStar or TmpFileStar */
894 return(ftell((FILE *) so
->txt
));
899 * so_attr - hook to hang random attributes onto the storage object
902 so_attr(STORE_S
*so
, char *key
, char *value
)
906 PARAMETER
**pp
= &so
->attr
;
910 if((*pp
)->attribute
&& !strcmp(key
, (*pp
)->attribute
)){
912 fs_give((void **)&(*pp
)->value
);
920 *pp
= mail_newbody_parameter();
921 (*pp
)->attribute
= cpystr(key
);
926 return((*pp
)->value
= cpystr(value
));
931 for(p
= so
->attr
; p
; p
= p
->next
)
932 if(p
->attribute
&& !strcmp(key
, p
->attribute
))
942 * so_release - a rather misnamed function. the idea is to release
943 * what system resources we can (e.g., open files).
944 * while maintaining a reference to it.
945 * it's up to the functions that deal with this object
946 * next to re-aquire those resources.
949 so_release(STORE_S
*so
)
951 if(so
->txt
&& so
->name
&& (so
->src
== FileStar
|| so
->src
== TmpFileStar
)){
952 if(fget_pos((FILE *)so
->txt
, (fpos_t *)&(so
->used
)) == 0){
953 fclose((FILE *)so
->txt
); /* free the handle! */
963 * so_reaquire - get any previously released system resources we
964 * may need for the given storage object.
965 * NOTE: at the moment, only FILE * types of objects are
966 * effected, so it only needs to be called before
967 * references to them.
971 so_reaquire(STORE_S
*so
)
975 if(!so
->txt
&& (so
->src
== FileStar
|| so
->src
== TmpFileStar
)){
976 if(!(so
->txt
= so_file_open(so
))){
977 q_status_message2(SM_ORDER
,3,5, "ERROR reopening %.200s : %.200s",
978 so
->name
, error_description(errno
));
981 else if(fset_pos((FILE *)so
->txt
, (fpos_t *)&(so
->used
))){
982 q_status_message2(SM_ORDER
, 3, 5,
983 "ERROR positioning in %.200s : %.200s",
984 so
->name
, error_description(errno
));
994 * so_text - return a pointer to the text the store object passed
999 return((so
) ? so
->txt
: NULL
);
1004 * Similar to fgets but reading from a storage object.
1007 so_fgets(STORE_S
*so
, char *s
, size_t size
)
1012 while(--size
> 0 && so_readc(&c
, so
) > 0){
1020 return((p
>s
) ? s
: NULL
);