a_amv_lopts_add(): with HAVE_PUTENV, do not save ENV pointer (memleak)
[s-mailx.git] / fio.c
blob361da73fddfc36b8cd2d05191f0b4fff76a70a91
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ File operations.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
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 /* line is a buffer with the result of fgets(). Returns the first newline or
43 * the last character read */
44 static size_t _length_of_line(char const *line, size_t linesize);
46 /* Read a line, one character at a time */
47 static char * _fgetline_byone(char **line, size_t *linesize, size_t *llen,
48 FILE *fp, int appendnl, size_t n n_MEMORY_DEBUG_ARGS);
50 /* Workhorse */
51 static bool_t a_file_lock(int fd, enum n_file_lock_type ft, off_t off,
52 off_t len);
54 static size_t
55 _length_of_line(char const *line, size_t linesize)
57 size_t i;
58 NYD2_ENTER;
60 /* Last character is always '\0' and was added by fgets() */
61 for (--linesize, i = 0; i < linesize; i++)
62 if (line[i] == '\n')
63 break;
64 i = (i < linesize) ? i + 1 : linesize;
65 NYD2_LEAVE;
66 return i;
69 static char *
70 _fgetline_byone(char **line, size_t *linesize, size_t *llen, FILE *fp,
71 int appendnl, size_t n n_MEMORY_DEBUG_ARGS)
73 char *rv;
74 int c;
75 NYD2_ENTER;
77 assert(*linesize == 0 || *line != NULL);
78 n_pstate &= ~n_PS_READLINE_NL;
80 for (rv = *line;;) {
81 if (*linesize <= LINESIZE || n >= *linesize - 128) {
82 *linesize += ((rv == NULL) ? LINESIZE + n + 1 : 256);
83 *line = rv = (n_realloc)(rv, *linesize n_MEMORY_DEBUG_ARGSCALL);
85 c = getc(fp);
86 if (c != EOF) {
87 rv[n++] = c;
88 rv[n] = '\0';
89 if (c == '\n') {
90 n_pstate |= n_PS_READLINE_NL;
91 break;
93 } else {
94 if (n > 0) {
95 if (appendnl) {
96 rv[n++] = '\n';
97 rv[n] = '\0';
99 break;
100 } else {
101 rv = NULL;
102 goto jleave;
106 if (llen)
107 *llen = n;
108 jleave:
109 NYD2_LEAVE;
110 return rv;
113 static bool_t
114 a_file_lock(int fd, enum n_file_lock_type flt, off_t off, off_t len)
116 struct flock flp;
117 bool_t rv;
118 NYD2_ENTER;
120 memset(&flp, 0, sizeof flp);
122 switch (flt) {
123 default:
124 case FLT_READ: rv = F_RDLCK; break;
125 case FLT_WRITE: rv = F_WRLCK; break;
127 flp.l_type = rv;
128 flp.l_start = off;
129 flp.l_whence = SEEK_SET;
130 flp.l_len = len;
132 if (!(rv = (fcntl(fd, F_SETLK, &flp) != -1)))
133 switch (n_err_no) {
134 case n_ERR_BADF:
135 case n_ERR_INVAL:
136 rv = TRUM1;
137 break;
139 NYD2_LEAVE;
140 return rv;
143 FL char *
144 (fgetline)(char **line, size_t *linesize, size_t *cnt, size_t *llen, FILE *fp,
145 int appendnl n_MEMORY_DEBUG_ARGS)
147 size_t i_llen, sz;
148 char *rv;
149 NYD2_ENTER;
151 if (cnt == NULL) {
152 /* Without count, we can't determine where the chars returned by fgets()
153 * end if there's no newline. We have to read one character by one */
154 rv = _fgetline_byone(line, linesize, llen, fp, appendnl, 0
155 n_MEMORY_DEBUG_ARGSCALL);
156 goto jleave;
159 n_pstate &= ~n_PS_READLINE_NL;
161 if ((rv = *line) == NULL || *linesize < LINESIZE)
162 *line = rv = (n_realloc)(rv, *linesize = LINESIZE
163 n_MEMORY_DEBUG_ARGSCALL);
164 sz = (*linesize <= *cnt) ? *linesize : *cnt + 1;
165 if (sz <= 1 || fgets(rv, sz, fp) == NULL) {
166 /* Leave llen untouched; it is used to determine whether the last line
167 * was \n-terminated in some callers */
168 rv = NULL;
169 goto jleave;
172 i_llen = _length_of_line(rv, sz);
173 *cnt -= i_llen;
174 while (rv[i_llen - 1] != '\n') {
175 *line = rv = (n_realloc)(rv, *linesize += 256 n_MEMORY_DEBUG_ARGSCALL);
176 sz = *linesize - i_llen;
177 sz = (sz <= *cnt) ? sz : *cnt + 1;
178 if (sz <= 1 || fgets(rv + i_llen, sz, fp) == NULL) {
179 if (appendnl) {
180 rv[i_llen++] = '\n';
181 rv[i_llen] = '\0';
183 break;
185 sz = _length_of_line(rv + i_llen, sz);
186 i_llen += sz;
187 *cnt -= sz;
189 if (llen)
190 *llen = i_llen;
191 jleave:
192 NYD2_LEAVE;
193 return rv;
196 FL int
197 (readline_restart)(FILE *ibuf, char **linebuf, size_t *linesize, size_t n
198 n_MEMORY_DEBUG_ARGS)
200 /* TODO readline_restart(): always *appends* LF just to strip it again;
201 * TODO should be configurable just as for fgetline(); ..or whatever..
202 * TODO intwrap */
203 int rv = -1;
204 long sz;
205 NYD2_ENTER;
207 clearerr(ibuf);
209 /* Interrupts will cause trouble if we are inside a stdio call. As this is
210 * only relevant if input is from tty, bypass it by read(), then */
211 if ((n_psonce & n_PSO_TTYIN) && fileno(ibuf) == 0) {
212 assert(*linesize == 0 || *linebuf != NULL);
213 n_pstate &= ~n_PS_READLINE_NL;
214 for (;;) {
215 if (*linesize <= LINESIZE || n >= *linesize - 128) {
216 *linesize += ((*linebuf == NULL) ? LINESIZE + n + 1 : 256);
217 *linebuf = (n_realloc)(*linebuf, *linesize n_MEMORY_DEBUG_ARGSCALL);
219 jagain:
220 sz = read(0, *linebuf + n, *linesize - n - 1);
221 if (sz > 0) {
222 n += sz;
223 (*linebuf)[n] = '\0';
224 if ((*linebuf)[n - 1] == '\n') {
225 n_pstate |= n_PS_READLINE_NL;
226 break;
228 } else {
229 if (sz < 0 && n_err_no == n_ERR_INTR)
230 goto jagain;
231 /* TODO eh. what is this? that now supposed to be a line?!? */
232 if (n > 0) {
233 if ((*linebuf)[n - 1] != '\n') {
234 (*linebuf)[n++] = '\n';
235 (*linebuf)[n] = '\0';
236 } else
237 n_pstate |= n_PS_READLINE_NL;
238 break;
239 } else
240 goto jleave;
243 } else {
244 /* Not reading from standard input or standard input not a terminal. We
245 * read one char at a time as it is the only way to get lines with
246 * embedded NUL characters in standard stdio */
247 if (_fgetline_byone(linebuf, linesize, &n, ibuf, 1, n
248 n_MEMORY_DEBUG_ARGSCALL) == NULL)
249 goto jleave;
251 if (n > 0 && (*linebuf)[n - 1] == '\n')
252 (*linebuf)[--n] = '\0';
253 rv = (int)n;
254 jleave:
255 NYD2_LEAVE;
256 return rv;
259 FL void
260 setptr(FILE *ibuf, off_t offset)
262 struct message self;
263 char *cp, *linebuf = NULL;
264 char const *cp2;
265 int c, selfcnt = 0;
266 bool_t need_rfc4155, maybe, inhead, from_;
267 size_t linesize = 0, filesize, cnt;
268 NYD_ENTER;
270 memset(&self, 0, sizeof self);
271 self.m_flag = MUSED | MNEW | MNEWEST;
272 filesize = mailsize - offset;
273 offset = ftell(mb.mb_otf);
274 need_rfc4155 = ok_blook(mbox_rfc4155);
275 maybe = TRU1;
276 inhead = FAL0;
278 for (;;) {
279 if (fgetline(&linebuf, &linesize, &filesize, &cnt, ibuf, 0) == NULL) {
280 self.m_xsize = self.m_size;
281 self.m_xlines = self.m_lines;
282 self.m_content_info = CI_HAVE_HEADER | CI_HAVE_BODY;
283 if (selfcnt > 0)
284 message_append(&self);
285 message_append_null();
286 if (linebuf != NULL)
287 free(linebuf);
288 break;
291 #ifdef notdef
292 if (linebuf[0] == '\0')
293 linebuf[0] = '.';
294 #endif
295 /* XXX Convert CRLF to LF; this should be rethought in that
296 * XXX CRLF input should possibly end as CRLF output? */
297 if (cnt >= 2 && linebuf[cnt - 1] == '\n' && linebuf[cnt - 2] == '\r')
298 linebuf[--cnt - 1] = '\n';
299 fwrite(linebuf, sizeof *linebuf, cnt, mb.mb_otf);
300 if (ferror(mb.mb_otf)) {
301 n_perr(_("/tmp"), 0);
302 exit(n_EXIT_ERR);
304 if (linebuf[cnt - 1] == '\n')
305 linebuf[cnt - 1] = '\0';
306 /* TODO In v15 this should use a/the flat MIME parser in order to ignore
307 * TODO "From " when MIME boundaries are active -- whereas this opens
308 * TODO another can of worms, it very likely is better than messing up
309 * TODO MIME because of a "From " line! */
310 if (maybe && linebuf[0] == 'F' &&
311 (from_ = is_head(linebuf, cnt, TRU1)) &&
312 (from_ == TRU1 || !need_rfc4155)) {
313 /* TODO char date[n_FROM_DATEBUF];
314 * TODO extract_date_from_from_(linebuf, cnt, date);
315 * TODO self.m_time = 10000; */
316 if (from_ == TRUM1) {
317 if (n_poption & n_PO_D_V)
318 n_err(_("Invalid MBOX \"From_ line\": %.*s\n"),
319 (int)cnt, linebuf);
320 else if (!(mb.mb_active & MB_FROM__WARNED))
321 n_err(_("MBOX mailbox contains non-conforming From_ line(s)!\n"
322 " Message boundaries may have been falsely detected!\n"
323 " Setting variable *mbox-rfc4155* and reopen should improve "
324 "the result.\n"
325 " If so, make changes permanent: \"copy * SOME-FILE\". "
326 "Then unset *mbox-rfc4155*\n"));
327 mb.mb_active |= MB_FROM__WARNED;
329 self.m_xsize = self.m_size;
330 self.m_xlines = self.m_lines;
331 self.m_content_info = CI_HAVE_HEADER | CI_HAVE_BODY;
332 if (selfcnt++ > 0)
333 message_append(&self);
334 msgCount++;
335 self.m_flag = MUSED | MNEW | MNEWEST;
336 self.m_size = 0;
337 self.m_lines = 0;
338 self.m_block = mailx_blockof(offset);
339 self.m_offset = mailx_offsetof(offset);
340 inhead = TRU1;
341 } else if (linebuf[0] == 0) {
342 inhead = FAL0;
343 } else if (inhead) {
344 for (cp = linebuf, cp2 = "status";; ++cp) {
345 if ((c = *cp2++) == 0) {
346 while (c = *cp++, whitechar(c))
348 if (cp[-1] != ':')
349 break;
350 while ((c = *cp++) != '\0')
351 if (c == 'R')
352 self.m_flag |= MREAD;
353 else if (c == 'O')
354 self.m_flag &= ~MNEW;
355 break;
357 if (*cp != c && *cp != upperconv(c))
358 break;
360 for (cp = linebuf, cp2 = "x-status";; ++cp) {
361 if ((c = *cp2++) == 0) {
362 while ((c = *cp++, whitechar(c)))
364 if (cp[-1] != ':')
365 break;
366 while ((c = *cp++) != '\0')
367 if (c == 'F')
368 self.m_flag |= MFLAGGED;
369 else if (c == 'A')
370 self.m_flag |= MANSWERED;
371 else if (c == 'T')
372 self.m_flag |= MDRAFTED;
373 break;
375 if (*cp != c && *cp != upperconv(c))
376 break;
379 offset += cnt;
380 self.m_size += cnt;
381 ++self.m_lines;
382 maybe = (linebuf[0] == 0);
384 NYD_LEAVE;
387 FL off_t
388 fsize(FILE *iob)
390 struct stat sbuf;
391 off_t rv;
392 NYD_ENTER;
394 rv = (fstat(fileno(iob), &sbuf) == -1) ? 0 : sbuf.st_size;
395 NYD_LEAVE;
396 return rv;
399 FL bool_t
400 n_file_lock(int fd, enum n_file_lock_type flt, off_t off, off_t len,
401 size_t pollmsecs)
403 size_t tries;
404 bool_t didmsg, rv;
405 NYD_ENTER;
407 if(pollmsecs == UIZ_MAX)
408 pollmsecs = FILE_LOCK_MILLIS;
410 n_UNINIT(rv, 0);
411 for (didmsg = FAL0, tries = 0; tries <= FILE_LOCK_TRIES; ++tries) {
412 rv = a_file_lock(fd, flt, off, len);
414 if (rv == TRUM1) {
415 rv = FAL0;
416 break;
418 if (rv || pollmsecs == 0)
419 break;
420 else {
421 if(!didmsg){
422 n_err(_("Failed to create a file lock, waiting %lu milliseconds "),
423 pollmsecs);
424 didmsg = TRU1;
425 }else
426 n_err(".");
427 n_msleep(pollmsecs, FAL0);
430 if(didmsg)
431 n_err(" %s\n", (rv ? _("ok") : _("failure")));
432 NYD_LEAVE;
433 return rv;
436 /* s-it-mode */