From 418f661cedc045889b23a0f2881a7049b3d008a4 Mon Sep 17 00:00:00 2001 From: Kaz Kylheku Date: Fri, 7 Feb 2020 19:47:25 -0800 Subject: New "m" file open mode: non-truncating "w". Quite surprisingly ISO C lacks a way in fopen to open a file for writing such that it is not truncated if it already exists, and not opened in append mode. (But you will be glad to know that ISO C is adding incredibly useful features in this area, like Microsoft's fopen_s!) Let us add modes "m" and "m+" which will be like "w" and "w+", but without the truncation to zero length (no O_TRUNC is passed to open). * stream.c (w_fopen_mode): New static function. (open_file, open_tail, tail_strategy): Use w_fopen_mode instead of directly calling w_fopen. (do_parse_mode): Handle 'm' and set new notrunc flag. * stream.h (struct stdio_mode): New member, notrunc flag. (stdio_mode_init_blank, stdio_mode_init_r, stdio_mode_init_rpb): Initializer macros updated to include initializer for notrunc flag. * txr.1: Documented "m" mode. --- stream.c | 35 ++++++++++++++++++++++++++++++++--- stream.h | 7 ++++--- txr.1 | 10 +++++++++- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/stream.c b/stream.c index 0ca0f799..c0362f3f 100644 --- a/stream.c +++ b/stream.c @@ -1044,6 +1044,28 @@ static struct strm_ops stdio_ops = static struct strm_ops stdio_sock_ops; #endif +static FILE *w_fopen_mode(const wchar_t *wname, const wchar_t *mode, + const struct stdio_mode m) +{ +#if HAVE_FCNTL + if (m.notrunc) { + char *name = utf8_dup_to(wname); + int flags = (m.read ? O_RDWR : O_WRONLY) | O_CREAT; + int fd = open(name, flags, 0777); + free(name); + if (fd < 0) + return NULL; + return (fd < 0) ? NULL : w_fdopen(fd, mode); + } +#else + if (m.notrunc) + uw_throwf(file_error_s, + lit("open-file: system doesn't support \"o\" mode"), nao); +#endif + return w_fopen(wname, mode); +} + + static void tail_calc(unsigned long *state, int *usec, int *mod) { unsigned long count = (*state)++; @@ -1096,7 +1118,7 @@ static void tail_strategy(val stream, unsigned long *state) /* Try to open the file. */ - if (!(newf = w_fopen(c_str(h->descr), c_str(mode)))) { + if (!(newf = w_fopen_mode(c_str(h->descr), c_str(mode), m))) { /* If already have the file open previously, and the name * does not open any more, then the file has rotated. * Have the caller try to read the last bit of data @@ -1349,6 +1371,12 @@ static struct stdio_mode do_parse_mode(val mode_str, struct stdio_mode m_dfl) m.write = 1; m.append = 1; break; + case 'm': + ms++; + m.write = 1; + m.create = 1; + m.notrunc = 1; + break; default: break; } @@ -3947,7 +3975,8 @@ val open_directory(val path) val open_file(val path, val mode_str) { struct stdio_mode m, m_r = stdio_mode_init_r; - FILE *f = w_fopen(c_str(path), c_str(normalize_mode(&m, mode_str, m_r))); + val norm_mode = normalize_mode(&m, mode_str, m_r); + FILE *f = w_fopen_mode(c_str(path), c_str(norm_mode), m); if (!f) { int eno = errno; @@ -3979,7 +4008,7 @@ val open_tail(val path, val mode_str, val seek_end_p) { struct stdio_mode m, m_r = stdio_mode_init_r; val mode = normalize_mode(&m, mode_str, m_r); - FILE *f = w_fopen(c_str(path), c_str(mode)); + FILE *f = w_fopen_mode(c_str(path), c_str(mode), m); struct stdio_handle *h; val stream; unsigned long state = 0; diff --git a/stream.h b/stream.h index 6fb245d6..2d7688c6 100644 --- a/stream.h +++ b/stream.h @@ -108,6 +108,7 @@ struct stdio_mode { unsigned create : 1; unsigned append : 1; unsigned binary : 1; + unsigned notrunc : 1; unsigned interactive : 1; unsigned unbuf : 1; unsigned linebuf : 1; @@ -115,9 +116,9 @@ struct stdio_mode { int redir[STDIO_MODE_NREDIRS][2]; }; -#define stdio_mode_init_blank { 0, 0, 0, 0, 0, 0, 0, 0, 0, -1 } -#define stdio_mode_init_r { 0, 1, 0, 0, 0, 0, 0, 0, 0, -1 } -#define stdio_mode_init_rpb { 0, 1, 1, 0, 0, 1, 0, 0, 0, -1 } +#define stdio_mode_init_blank { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1 } +#define stdio_mode_init_r { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, -1 } +#define stdio_mode_init_rpb { 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, -1 } #define std_input (deref(lookup_var_l(nil, stdin_s))) #define std_output (deref(lookup_var_l(nil, stdout_s))) diff --git a/txr.1 b/txr.1 index 9765dc2f..6f3f2258 100644 --- a/txr.1 +++ b/txr.1 @@ -48925,7 +48925,7 @@ grammar. Note that it permits no whitespace characters: .mono .mets < mode-string := [ < mode ] [ < options ] .mets < mode := { < selector [ + ] | + } -.mets < selector := { r | w | a } +.mets < selector := { r | w | a | m } .mets < options := { b | l | u | < digit | < redirection } .mets < digit := { 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 } .onom @@ -48968,6 +48968,14 @@ to zero length. If it doesn't exist, it is created. .coIP w+ The file is opened for reading and writing. If it exists, it is truncated to zero length. If it doesn't exist, it is created. +.coIP m +The file is opened for modification. This is the same as +.code w +except that the file is not truncated if it exists. +.coIP m+ +The file is opened for reading and modification. This is the same as +.code w+ +except that the file is not truncated if it exists. .coIP a The file is opened for writing. If it doesn't exist, it is created. If it exists, the current position is advanced to -- cgit v1.2.3