about summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Stephenson <p.stephenson@samsung.com>2020-06-09 18:04:46 +0100
committerPeter Stephenson <p.stephenson@samsung.com>2020-06-09 18:07:01 +0100
commit3df604a4be76db6ccf285eda6b4b3f5f6ec7497d (patch)
tree244774fd13ebf129d029f521fa2f8d707763da6f
parent172b646a6baa4037a4dbc4d056f2b4b786eea24e (diff)
downloadzsh-3df604a4be76db6ccf285eda6b4b3f5f6ec7497d.tar.gz
zsh-3df604a4be76db6ccf285eda6b4b3f5f6ec7497d.tar.xz
zsh-3df604a4be76db6ccf285eda6b4b3f5f6ec7497d.zip
46026: Add CLOBBER_EMPTY option.
-rw-r--r--ChangeLog5
-rw-r--r--Doc/Zsh/options.yo16
-rw-r--r--Src/exec.c27
-rw-r--r--Src/options.c1
-rw-r--r--Src/zsh.h1
-rw-r--r--Test/A04redirect.ztst14
6 files changed, 59 insertions, 5 deletions
diff --git a/ChangeLog b/ChangeLog
index 979fa729b..3addf253f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2020-06-09  Peter Stephenson  <p.stephenson@samsung.com>
+
+	* 46026: Doc/Zsh/options.yo, Src/exec.c, Src/options.c,
+	Src/zsh.h, Test/A04redirect.ztst: Add CLOBBER_EMPTY option.
+
 2020-06-08  Peter Stephenson  <p.w.stephenson@ntlworld.com>
 
 	* uwers/24909: Src/exec.c: Don't clean up files used for
diff --git a/Doc/Zsh/options.yo b/Doc/Zsh/options.yo
index 2b7637ff4..6da68308f 100644
--- a/Doc/Zsh/options.yo
+++ b/Doc/Zsh/options.yo
@@ -1168,6 +1168,22 @@ If the option is not set, and the option tt(APPEND_CREATE) is also
 not set, `tt(>>!)' or `tt(>>|)' must be used to create a file.
 If either option is set, `tt(>>)' may be used.
 )
+pindex(CLOBBER_EMPTY)
+pindex(NO_CLOBBER_EMPTY)
+pindex(CLOBBEREMPTY)
+pindex(NOCLOBBEREMPTY)
+cindex(clobbering, of empty files)
+cindex(file clobbering, of empty files)
+item(tt(CLOBBER_EMPTY))(
+This option is only used if the option tt(CLOBBER) is not set: note that
+it is set by default.
+
+If this option is set, then regular files of zero length may be
+ovewritten (`clobbered').  Note that it is possible another process
+has written to the file between this test and use of the file by
+the current process.  This option should therefore not be used in
+cases where files to be clobbered may be written to asynchronously.
+)
 pindex(CORRECT)
 pindex(NO_CORRECT)
 pindex(NOCORRECT)
diff --git a/Src/exec.c b/Src/exec.c
index c72d485b2..045b5d2b9 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -2143,14 +2143,15 @@ clobber_open(struct redir *f)
 {
     struct stat buf;
     int fd, oerrno;
+    char *ufname = unmeta(f->name);
 
     /* If clobbering, just open. */
     if (isset(CLOBBER) || IS_CLOBBER_REDIR(f->type))
-	return open(unmeta(f->name),
+	return open(ufname,
 		O_WRONLY | O_CREAT | O_TRUNC | O_NOCTTY, 0666);
 
     /* If not clobbering, attempt to create file exclusively. */
-    if ((fd = open(unmeta(f->name),
+    if ((fd = open(ufname,
 		   O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY, 0666)) >= 0)
 	return fd;
 
@@ -2158,11 +2159,27 @@ clobber_open(struct redir *f)
      * Try opening, and if it's a regular file then close it again    *
      * because we weren't supposed to open it.                        */
     oerrno = errno;
-    if ((fd = open(unmeta(f->name), O_WRONLY | O_NOCTTY)) != -1) {
-	if(!fstat(fd, &buf) && !S_ISREG(buf.st_mode))
-	    return fd;
+    if ((fd = open(ufname, O_WRONLY | O_NOCTTY)) != -1) {
+	if(!fstat(fd, &buf)) {
+	    if (!S_ISREG(buf.st_mode))
+		return fd;
+	    /*
+	     * If CLOBBER_EMPTY is in effect and the file is empty,
+	     * we are allowed to re-use it.
+	     *
+	     * Note: there is an intrinsic race here because another
+	     * process can write to this file at any time.  The only fix
+	     * would be file locking, which we wish to avoid in basic
+	     * file operations at this level.  This would not be
+	     * fixed. just additionally complicated, by re-opening the
+	     * file and truncating.
+	     */
+	    if (isset(CLOBBEREMPTY) && buf.st_size == 0)
+		return fd;
+	}
 	close(fd);
     }
+
     errno = oerrno;
     return -1;
 }
diff --git a/Src/options.c b/Src/options.c
index 7586d21d2..fba021e7d 100644
--- a/Src/options.c
+++ b/Src/options.c
@@ -114,6 +114,7 @@ static struct optname optns[] = {
 {{NULL, "checkjobs",	      OPT_EMULATE|OPT_ZSH},	 CHECKJOBS},
 {{NULL, "checkrunningjobs",   OPT_EMULATE|OPT_ZSH},	 CHECKRUNNINGJOBS},
 {{NULL, "clobber",	      OPT_EMULATE|OPT_ALL},	 CLOBBER},
+{{NULL, "clobberempty",	      0},			 CLOBBEREMPTY},
 {{NULL, "combiningchars",     0},			 COMBININGCHARS},
 {{NULL, "completealiases",    0},			 COMPLETEALIASES},
 {{NULL, "completeinword",     0},			 COMPLETEINWORD},
diff --git a/Src/zsh.h b/Src/zsh.h
index 1f2d774a1..ed123f2b9 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -2378,6 +2378,7 @@ enum {
     CHECKJOBS,
     CHECKRUNNINGJOBS,
     CLOBBER,
+    CLOBBEREMPTY,
     APPENDCREATE,
     COMBININGCHARS,
     COMPLETEALIASES,
diff --git a/Test/A04redirect.ztst b/Test/A04redirect.ztst
index d60519064..993138e7d 100644
--- a/Test/A04redirect.ztst
+++ b/Test/A04redirect.ztst
@@ -708,3 +708,17 @@
   cat <&$testfd
 0:Regression test for here document with fd declarator
 >  This is, in some sense, a here document.
+
+  (setopt noclobber clobberempty
+  rm -f foo
+  touch foo
+  print Works >foo
+  cat foo
+  print Works not >foo
+  # Make sure the file was not harmed
+  cat foo
+  )
+0:CLOBBER_EMPTY
+>Works
+>Works
+?(eval):6: file exists: foo