Even less shell globbing (Ivan Tham)
[s-mailx.git] / fio.c
blob669f9d85e2541dab2e444129eaec7c500dd2a502
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 - 2017 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, off_t len);
53 static size_t
54 _length_of_line(char const *line, size_t linesize)
56 size_t i;
57 NYD2_ENTER;
59 /* Last character is always '\0' and was added by fgets() */
60 for (--linesize, i = 0; i < linesize; i++)
61 if (line[i] == '\n')
62 break;
63 i = (i < linesize) ? i + 1 : linesize;
64 NYD2_LEAVE;
65 return i;
68 static char *
69 _fgetline_byone(char **line, size_t *linesize, size_t *llen, FILE *fp,
70 int appendnl, size_t n n_MEMORY_DEBUG_ARGS)
72 char *rv;
73 int c;
74 NYD2_ENTER;
76 assert(*linesize == 0 || *line != NULL);
77 n_pstate &= ~n_PS_READLINE_NL;
79 for (rv = *line;;) {
80 if (*linesize <= LINESIZE || n >= *linesize - 128) {
81 *linesize += ((rv == NULL) ? LINESIZE + n + 1 : 256);
82 *line = rv = (n_realloc)(rv, *linesize n_MEMORY_DEBUG_ARGSCALL);
84 c = getc(fp);
85 if (c != EOF) {
86 rv[n++] = c;
87 rv[n] = '\0';
88 if (c == '\n') {
89 n_pstate |= n_PS_READLINE_NL;
90 break;
92 } else {
93 if (n > 0) {
94 if (appendnl) {
95 rv[n++] = '\n';
96 rv[n] = '\0';
98 break;
99 } else {
100 rv = NULL;
101 goto jleave;
105 if (llen)
106 *llen = n;
107 jleave:
108 NYD2_LEAVE;
109 return rv;
112 static bool_t
113 a_file_lock(int fd, enum n_file_lock_type flt, off_t off, off_t len)
115 struct flock flp;
116 bool_t rv;
117 NYD2_ENTER;
119 memset(&flp, 0, sizeof flp);
121 switch (flt) {
122 default:
123 case FLT_READ: rv = F_RDLCK; break;
124 case FLT_WRITE: rv = F_WRLCK; break;
126 flp.l_type = rv;
127 flp.l_start = off;
128 flp.l_whence = SEEK_SET;
129 flp.l_len = len;
131 if (!(rv = (fcntl(fd, F_SETLK, &flp) != -1)))
132 switch (n_err_no) {
133 case n_ERR_BADF:
134 case n_ERR_INVAL:
135 rv = TRUM1;
136 break;
138 NYD2_LEAVE;
139 return rv;
142 FL char *
143 (fgetline)(char **line, size_t *linesize, size_t *cnt, size_t *llen, FILE *fp,
144 int appendnl n_MEMORY_DEBUG_ARGS)
146 size_t i_llen, sz;
147 char *rv;
148 NYD2_ENTER;
150 if (cnt == NULL) {
151 /* Without count, we can't determine where the chars returned by fgets()
152 * end if there's no newline. We have to read one character by one */
153 rv = _fgetline_byone(line, linesize, llen, fp, appendnl, 0
154 n_MEMORY_DEBUG_ARGSCALL);
155 goto jleave;
158 n_pstate &= ~n_PS_READLINE_NL;
160 if ((rv = *line) == NULL || *linesize < LINESIZE)
161 *line = rv = (n_realloc)(rv, *linesize = LINESIZE
162 n_MEMORY_DEBUG_ARGSCALL);
163 sz = (*linesize <= *cnt) ? *linesize : *cnt + 1;
164 if (sz <= 1 || fgets(rv, sz, fp) == NULL) {
165 /* Leave llen untouched; it is used to determine whether the last line
166 * was \n-terminated in some callers */
167 rv = NULL;
168 goto jleave;
171 i_llen = _length_of_line(rv, sz);
172 *cnt -= i_llen;
173 while (rv[i_llen - 1] != '\n') {
174 *line = rv = (n_realloc)(rv, *linesize += 256 n_MEMORY_DEBUG_ARGSCALL);
175 sz = *linesize - i_llen;
176 sz = (sz <= *cnt) ? sz : *cnt + 1;
177 if (sz <= 1 || fgets(rv + i_llen, sz, fp) == NULL) {
178 if (appendnl) {
179 rv[i_llen++] = '\n';
180 rv[i_llen] = '\0';
182 break;
184 sz = _length_of_line(rv + i_llen, sz);
185 i_llen += sz;
186 *cnt -= sz;
188 if (llen)
189 *llen = i_llen;
190 jleave:
191 NYD2_LEAVE;
192 return rv;
195 FL int
196 (readline_restart)(FILE *ibuf, char **linebuf, size_t *linesize, size_t n
197 n_MEMORY_DEBUG_ARGS)
199 /* TODO readline_restart(): always *appends* LF just to strip it again;
200 * TODO should be configurable just as for fgetline(); ..or whatever..
201 * TODO intwrap */
202 int rv = -1;
203 long sz;
204 NYD2_ENTER;
206 clearerr(ibuf);
208 /* Interrupts will cause trouble if we are inside a stdio call. As this is
209 * only relevant if input is from tty, bypass it by read(), then */
210 if ((n_psonce & n_PSO_TTYIN) && fileno(ibuf) == 0) {
211 assert(*linesize == 0 || *linebuf != NULL);
212 n_pstate &= ~n_PS_READLINE_NL;
213 for (;;) {
214 if (*linesize <= LINESIZE || n >= *linesize - 128) {
215 *linesize += ((*linebuf == NULL) ? LINESIZE + n + 1 : 256);
216 *linebuf = (n_realloc)(*linebuf, *linesize n_MEMORY_DEBUG_ARGSCALL);
218 jagain:
219 sz = read(0, *linebuf + n, *linesize - n - 1);
220 if (sz > 0) {
221 n += sz;
222 (*linebuf)[n] = '\0';
223 if ((*linebuf)[n - 1] == '\n') {
224 n_pstate |= n_PS_READLINE_NL;
225 break;
227 } else {
228 if (sz < 0 && n_err_no == n_ERR_INTR)
229 goto jagain;
230 /* TODO eh. what is this? that now supposed to be a line?!? */
231 if (n > 0) {
232 if ((*linebuf)[n - 1] != '\n') {
233 (*linebuf)[n++] = '\n';
234 (*linebuf)[n] = '\0';
235 } else
236 n_pstate |= n_PS_READLINE_NL;
237 break;
238 } else
239 goto jleave;
242 } else {
243 /* Not reading from standard input or standard input not a terminal. We
244 * read one char at a time as it is the only way to get lines with
245 * embedded NUL characters in standard stdio */
246 if (_fgetline_byone(linebuf, linesize, &n, ibuf, 1, n
247 n_MEMORY_DEBUG_ARGSCALL) == NULL)
248 goto jleave;
250 if (n > 0 && (*linebuf)[n - 1] == '\n')
251 (*linebuf)[--n] = '\0';
252 rv = (int)n;
253 jleave:
254 NYD2_LEAVE;
255 return rv;
258 FL void
259 setptr(FILE *ibuf, off_t offset)
261 struct message self;
262 char *cp, *linebuf = NULL;
263 char const *cp2;
264 int c, selfcnt = 0;
265 bool_t need_rfc4155, maybe, inhead, from_;
266 size_t linesize = 0, filesize, cnt;
267 NYD_ENTER;
269 memset(&self, 0, sizeof self);
270 self.m_flag = MUSED | MNEW | MNEWEST;
271 filesize = mailsize - offset;
272 offset = ftell(mb.mb_otf);
273 need_rfc4155 = ok_blook(mbox_rfc4155);
274 maybe = TRU1;
275 inhead = FAL0;
277 for (;;) {
278 if (fgetline(&linebuf, &linesize, &filesize, &cnt, ibuf, 0) == NULL) {
279 self.m_xsize = self.m_size;
280 self.m_xlines = self.m_lines;
281 self.m_content_info = CI_HAVE_HEADER | CI_HAVE_BODY;
282 if (selfcnt > 0)
283 message_append(&self);
284 message_append_null();
285 if (linebuf != NULL)
286 free(linebuf);
287 break;
290 #ifdef notdef
291 if (linebuf[0] == '\0')
292 linebuf[0] = '.';
293 #endif
294 /* XXX Convert CRLF to LF; this should be rethought in that
295 * XXX CRLF input should possibly end as CRLF output? */
296 if (cnt >= 2 && linebuf[cnt - 1] == '\n' && linebuf[cnt - 2] == '\r')
297 linebuf[--cnt - 1] = '\n';
298 fwrite(linebuf, sizeof *linebuf, cnt, mb.mb_otf);
299 if (ferror(mb.mb_otf)) {
300 n_perr(_("/tmp"), 0);
301 exit(n_EXIT_ERR);
303 if (linebuf[cnt - 1] == '\n')
304 linebuf[cnt - 1] = '\0';
305 /* TODO In v15 this should use a/the flat MIME parser in order to ignore
306 * TODO "From " when MIME boundaries are active -- whereas this opens
307 * TODO another can of worms, it very likely is better than messing up
308 * TODO MIME because of a "From " line! */
309 if (maybe && linebuf[0] == 'F' &&
310 (from_ = is_head(linebuf, cnt, TRU1)) &&
311 (from_ == TRU1 || !need_rfc4155)) {
312 /* TODO char date[n_FROM_DATEBUF];
313 * TODO extract_date_from_from_(linebuf, cnt, date);
314 * TODO self.m_time = 10000; */
315 if (from_ == TRUM1) {
316 if (n_poption & n_PO_D_V)
317 n_err(_("Invalid MBOX \"From_ line\": %.*s\n"),
318 (int)cnt, linebuf);
319 else if (!(mb.mb_active & MB_FROM__WARNED))
320 n_err(_("MBOX mailbox contains non-conforming From_ line(s)!\n"
321 " Message boundaries may have been falsely detected!\n"
322 " Setting variable *mbox-rfc4155* and reopen should improve "
323 "the result.\n"
324 " If so, make changes permanent: \"copy * SOME-FILE\". "
325 "Then unset *mbox-rfc4155*\n"));
326 mb.mb_active |= MB_FROM__WARNED;
328 self.m_xsize = self.m_size;
329 self.m_xlines = self.m_lines;
330 self.m_content_info = CI_HAVE_HEADER | CI_HAVE_BODY;
331 if (selfcnt++ > 0)
332 message_append(&self);
333 msgCount++;
334 self.m_flag = MUSED | MNEW | MNEWEST;
335 self.m_size = 0;
336 self.m_lines = 0;
337 self.m_block = mailx_blockof(offset);
338 self.m_offset = mailx_offsetof(offset);
339 inhead = TRU1;
340 } else if (linebuf[0] == 0) {
341 inhead = FAL0;
342 } else if (inhead) {
343 for (cp = linebuf, cp2 = "status";; ++cp) {
344 if ((c = *cp2++) == 0) {
345 while (c = *cp++, whitechar(c))
347 if (cp[-1] != ':')
348 break;
349 while ((c = *cp++) != '\0')
350 if (c == 'R')
351 self.m_flag |= MREAD;
352 else if (c == 'O')
353 self.m_flag &= ~MNEW;
354 break;
356 if (*cp != c && *cp != upperconv(c))
357 break;
359 for (cp = linebuf, cp2 = "x-status";; ++cp) {
360 if ((c = *cp2++) == 0) {
361 while ((c = *cp++, whitechar(c)))
363 if (cp[-1] != ':')
364 break;
365 while ((c = *cp++) != '\0')
366 if (c == 'F')
367 self.m_flag |= MFLAGGED;
368 else if (c == 'A')
369 self.m_flag |= MANSWERED;
370 else if (c == 'T')
371 self.m_flag |= MDRAFTED;
372 break;
374 if (*cp != c && *cp != upperconv(c))
375 break;
378 offset += cnt;
379 self.m_size += cnt;
380 ++self.m_lines;
381 maybe = (linebuf[0] == 0);
383 NYD_LEAVE;
386 FL off_t
387 fsize(FILE *iob)
389 struct stat sbuf;
390 off_t rv;
391 NYD_ENTER;
393 rv = (fstat(fileno(iob), &sbuf) == -1) ? 0 : sbuf.st_size;
394 NYD_LEAVE;
395 return rv;
398 FL bool_t
399 n_file_lock(int fd, enum n_file_lock_type flt, off_t off, off_t len,
400 size_t pollmsecs)
402 size_t tries;
403 bool_t didmsg, rv;
404 NYD_ENTER;
406 if(pollmsecs == UIZ_MAX)
407 pollmsecs = FILE_LOCK_MILLIS;
409 n_UNINIT(rv, 0);
410 for (didmsg = FAL0, tries = 0; tries <= FILE_LOCK_TRIES; ++tries) {
411 rv = a_file_lock(fd, flt, off, len);
413 if (rv == TRUM1) {
414 rv = FAL0;
415 break;
417 if (rv || pollmsecs == 0)
418 break;
419 else {
420 if(!didmsg){
421 n_err(_("Failed to create a file lock, waiting %lu milliseconds "),
422 pollmsecs);
423 didmsg = TRU1;
424 }else
425 n_err(".");
426 n_msleep(pollmsecs, FAL0);
429 if(didmsg)
430 n_err(" %s\n", (rv ? _("ok") : _("failure")));
431 NYD_LEAVE;
432 return rv;
435 /* s-it-mode */