about summary refs log tree commit diff
path: root/Src
diff options
context:
space:
mode:
Diffstat (limited to 'Src')
-rw-r--r--Src/hist.c109
-rw-r--r--Src/options.c1
-rw-r--r--Src/zsh.h1
3 files changed, 106 insertions, 5 deletions
diff --git a/Src/hist.c b/Src/hist.c
index 58a8c61ff..6deb009d5 100644
--- a/Src/hist.c
+++ b/Src/hist.c
@@ -2021,6 +2021,20 @@ readhistfile(char *fn, int err, int readflags)
     else if (!lockhistfile(fn, 1))
 	return;
     if ((in = fopen(unmeta(fn), "r"))) {
+#ifdef HAVE_FCNTL_H
+	if (isset(HISTFCNTLLOCK)) {
+	    struct flock lck;
+
+	    lck.l_type = F_RDLCK;
+	    lck.l_whence = SEEK_SET;
+	    lck.l_start = 0;
+	    lck.l_len = 0;  /* lock the whole file */
+	    if (fcntl(fileno(in), F_SETLKW, &lck) == -1) {
+		fclose(in);
+		return;
+	    }
+	}
+#endif
 	nwordlist = 64;
 	wordlist = (short *)zalloc(nwordlist*sizeof(short));
 	bufsiz = 1024;
@@ -2149,6 +2163,68 @@ readhistfile(char *fn, int err, int readflags)
     unlockhistfile(fn);
 }
 
+#ifdef HAVE_FCNTL_H
+/**/
+static int
+wlockfile(int fd)
+{
+    struct flock lck;
+    int ctr = 8;
+
+    lck.l_type = F_WRLCK;
+    lck.l_whence = SEEK_SET;
+    lck.l_start = 0;
+    lck.l_len = 0;
+    while (fcntl(fd, F_SETLKW, &lck) == -1) {
+	if (--ctr < 0)
+	    return 1;
+	sleep (1);
+    }
+    return 0;
+}
+#endif
+
+/**/
+static int
+safe_unlink(const char *pathname)
+{
+#ifdef HAVE_FCNTL_H
+    if (isset(HISTFCNTLLOCK)) {
+	int fd = open(pathname, O_WRONLY | O_NOCTTY, 0600);
+	if (fd >= 0) {
+	    int err = wlockfile(fd) || unlink(pathname);
+	    close(fd);
+	    return err;
+	} else {
+	    return errno != ENOENT;
+	}
+    }
+#endif
+    return unlink(pathname) && errno != ENOENT;
+}
+
+/**/
+static int
+safe_rename(const char *oldpath, const char *newpath)
+{
+#ifdef HAVE_FCNTL_H
+    if (isset(HISTFCNTLLOCK)) {
+	int fd = open(newpath, O_CREAT | O_WRONLY | O_NOCTTY, 0600);
+	if (fd < 0) {
+	    return 1;
+	} else if (wlockfile(fd)) {
+	    close(fd);
+	    return 1;
+	} else {
+	    int err = rename(oldpath, newpath);
+	    close(fd);
+	    return err;
+	}
+    }
+#endif
+    return rename(oldpath, newpath);
+}
+
 /**/
 void
 savehistfile(char *fn, int err, int writeflags)
@@ -2158,6 +2234,9 @@ savehistfile(char *fn, int err, int writeflags)
     Histent he;
     zlong xcurhist = curhist - !!(histactive & HA_ACTIVE);
     int extended_history = isset(EXTENDEDHISTORY);
+#ifdef HAVE_FTRUNCATE
+    int truncate_history = 0;
+#endif
     int ret;
 
     if (!interact || savehistsiz <= 0 || !hist_ring
@@ -2196,12 +2275,16 @@ savehistfile(char *fn, int err, int writeflags)
 	tmpfile = NULL;
 	out = fd >= 0 ? fdopen(fd, "a") : NULL;
     } else if (!isset(HISTSAVEBYCOPY)) {
-	int fd = open(unmeta(fn), O_CREAT | O_WRONLY | O_TRUNC | O_NOCTTY, 0600);
+	int fd = open(unmeta(fn), O_CREAT | O_WRONLY | O_NOCTTY, 0600);
 	tmpfile = NULL;
 	out = fd >= 0 ? fdopen(fd, "w") : NULL;
+#ifdef HAVE_FTRUNCATE
+	/* The file should be truncated after its locking. */
+	truncate_history = 1;
+#endif
     } else {
 	tmpfile = bicat(unmeta(fn), ".new");
-	if (unlink(tmpfile) < 0 && errno != ENOENT)
+	if (safe_unlink(tmpfile))
 	    out = NULL;
 	else {
 	    struct stat sb;
@@ -2239,7 +2322,18 @@ savehistfile(char *fn, int err, int writeflags)
 #endif
 	}
     }
+#ifdef HAVE_FCNTL_H
+    if (out && isset(HISTFCNTLLOCK) && wlockfile(fileno(out))) {
+	zerr("can't lock file (timeout) -- history %s not updated", fn);
+	err = 0; /* Don't report a generic error below. */
+	out = NULL;
+    }
+#endif
     if (out) {
+#ifdef HAVE_FTRUNCATE
+	if (truncate_history)
+	    ftruncate(fileno(out), 0);
+#endif
 	ret = 0;
 	for (; he && he->histnum <= xcurhist; he = down_histent(he)) {
 	    if ((writeflags & HFILE_SKIPDUPS && he->node.flags & HIST_DUP)
@@ -2285,16 +2379,19 @@ savehistfile(char *fn, int err, int writeflags)
 		zsfree(lasthist.text);
 		lasthist.text = ztrdup(start);
 	    }
-	}
-	if (fclose(out) < 0 && ret >= 0)
+	} else if (ret >= 0 && fflush(out) < 0) {
 	    ret = -1;
+	}
 	if (ret >= 0) {
 	    if (tmpfile) {
-		if (rename(tmpfile, unmeta(fn)) < 0)
+		/* out has been flushed and the file must be renamed while
+		   being open so that the lock is still valid */
+		if (safe_rename(tmpfile, unmeta(fn)))
 		    zerr("can't rename %s.new to $HISTFILE", fn);
 		free(tmpfile);
 		tmpfile = NULL;
 	    }
+	    fclose(out);
 
 	    if (writeflags & HFILE_SKIPOLD
 		&& !(writeflags & (HFILE_FAST | HFILE_NO_REWRITE))) {
@@ -2314,6 +2411,8 @@ savehistfile(char *fn, int err, int writeflags)
 		pophiststack();
 		histactive = remember_histactive;
 	    }
+	} else {
+	    fclose(out);
 	}
     } else
 	ret = -1;
diff --git a/Src/options.c b/Src/options.c
index a206b2910..9fb3f3388 100644
--- a/Src/options.c
+++ b/Src/options.c
@@ -135,6 +135,7 @@ static struct optname optns[] = {
 {{NULL, "histallowclobber",   0},			 HISTALLOWCLOBBER},
 {{NULL, "histbeep",	      OPT_ALL},			 HISTBEEP},
 {{NULL, "histexpiredupsfirst",0},			 HISTEXPIREDUPSFIRST},
+{{NULL, "histfcntllock",      0},			 HISTFCNTLLOCK},
 {{NULL, "histfindnodups",     0},			 HISTFINDNODUPS},
 {{NULL, "histignorealldups",  0},			 HISTIGNOREALLDUPS},
 {{NULL, "histignoredups",     0},			 HISTIGNOREDUPS},
diff --git a/Src/zsh.h b/Src/zsh.h
index 2b8646cb1..67e4c0c31 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -1749,6 +1749,7 @@ enum {
     HISTALLOWCLOBBER,
     HISTBEEP,
     HISTEXPIREDUPSFIRST,
+    HISTFCNTLLOCK,
     HISTFINDNODUPS,
     HISTIGNOREALLDUPS,
     HISTIGNOREDUPS,