[tcsh] Prevent .history file corruption

Vojtěch Vítek vvitek at fedoraproject.org
Thu Feb 16 14:50:10 UTC 2012


commit c8ffd8642254a5ae0fd07524d88a57663417145d
Author: Vojtech Vitek (V-Teq) <vvitek at redhat.com>
Date:   Thu Feb 16 15:47:11 2012 +0100

    Prevent .history file corruption
    
    - Implement file locking using shared readers, exclusive writer
      to prevent any .history file data corruption
    
    - Handle pending signals before flush so that the the .history file
      does not get truncated
    
    Resolves: #653054

 tcsh-6.17.00-handle-signals-before-flush.patch |   36 ++
 tcsh-6.17.00-history-file-locking.patch        |  478 ++++++++++++++++++++++++
 tcsh.spec                                      |   10 +
 3 files changed, 524 insertions(+), 0 deletions(-)
---
diff --git a/tcsh-6.17.00-handle-signals-before-flush.patch b/tcsh-6.17.00-handle-signals-before-flush.patch
new file mode 100644
index 0000000..2fdbfc9
--- /dev/null
+++ b/tcsh-6.17.00-handle-signals-before-flush.patch
@@ -0,0 +1,36 @@
+From 466297a90d3520d5521c9ca6858a5b7981b3edb8 Mon Sep 17 00:00:00 2001
+From: "Vojtech Vitek (V-Teq)" <vvitek at redhat.com>
+Date: Thu, 10 Nov 2011 17:33:01 +0100
+Subject: [PATCH 1/2] Handle pending signals before flush so that the the
+ history file does not get truncated.  (Ted Anderson)
+
+Patch adapted from upstream patch in tcsh V6.17.01 - 20100506
+http://mx.gw.com/pipermail/tcsh/2010-May/004066.html
+---
+ sh.c |    3 +++
+ 1 files changed, 3 insertions(+), 0 deletions(-)
+
+diff --git a/sh.c b/sh.c
+index 73b6d7f..da818fb 100644
+--- a/sh.c
++++ b/sh.c
+@@ -1288,6 +1288,8 @@ main(int argc, char **argv)
+      */
+     process(setintr);
+ 
++    /* Take care of these (especially HUP) here instead of inside flush. */
++    handle_pending_signals();
+     /*
+      * Mop-up.
+      */
+@@ -2018,6 +2020,7 @@ process(int catch)
+     cleanup_pop_mark(omark);
+     resexit(osetexit);
+     exitset--;
++    handle_pending_signals();
+ }
+ 
+ /*ARGSUSED*/
+-- 
+1.7.6.2
+
diff --git a/tcsh-6.17.00-history-file-locking.patch b/tcsh-6.17.00-history-file-locking.patch
new file mode 100644
index 0000000..c30271a
--- /dev/null
+++ b/tcsh-6.17.00-history-file-locking.patch
@@ -0,0 +1,478 @@
+From c0612dbd1c6d78668f46910c20cb801a5c67780b Mon Sep 17 00:00:00 2001
+From: "Vojtech Vitek (V-Teq)" <vvitek at redhat.com>
+Date: Thu, 10 Nov 2011 18:19:57 +0100
+Subject: [PATCH 2/2] Implement .history file locking - shared readers,
+ exclusive writer
+
+Originally reported at Red Hat Bugzilla:
+https://bugzilla.redhat.com/show_bug.cgi?id=648592
+
+Patch by Vojtech Vitek (V-Teq) <vvitek at redhat.com>
+---
+ sh.c       |   91 ++++++++++++++++++++++++++++++++++++++++++++----------------
+ sh.decls.h |    5 ++-
+ sh.dir.c   |    2 +-
+ sh.dol.c   |    2 +-
+ sh.err.c   |   16 ++++++++++
+ sh.h       |   17 +++++++++++
+ sh.hist.c  |   83 +++++++++++++++++++++++++++++-------------------------
+ sh.lex.c   |    8 ++--
+ sh.sem.c   |    2 +-
+ 9 files changed, 154 insertions(+), 72 deletions(-)
+
+diff --git a/sh.c b/sh.c
+index da818fb..283af06 100644
+--- a/sh.c
++++ b/sh.c
+@@ -140,6 +140,7 @@ struct saved_state {
+     int	  cantell;
+     struct Bin	  B;
+     int		  justpr;
++    int		  close_unit;
+ };
+ 
+ static	int		  srccat	(Char *, Char *);
+@@ -1251,7 +1252,7 @@ main(int argc, char **argv)
+ 	/*
+ 	 * Source history before .login so that it is available in .login
+ 	 */
+-	loadhist(NULL, 0);
++	loadhist(NULL, FD_RDLCK);
+ #ifndef LOGINFIRST
+ 	if (loginsh)
+ 	    (void) srccat(varval(STRhome), STRsldotlogin);
+@@ -1404,20 +1405,52 @@ static int
+ #else
+ int
+ #endif /*WINNT_NATIVE*/
+-srcfile(const char *f, int onlyown, int flag, Char **av)
++srcfile(const char *f, int onlyown, int flg, Char **av)
+ {
+-    int unit;
+-
+-    if ((unit = xopen(f, O_RDONLY|O_LARGEFILE)) == -1) 
+-	return 0;
+-    cleanup_push(&unit, open_cleanup);
+-    unit = dmove(unit, -1);
+-    cleanup_ignore(&unit);
+-    cleanup_until(&unit);
+-
+-    (void) close_on_exec(unit, 1);
+-    srcunit(unit, onlyown, flag, av);
+-    return 1;
++    int *unit, copy;
++
++    unit = xmalloc(sizeof(*unit));
++    cleanup_push(unit, xfree);
++    *unit = xopen(f, O_LARGEFILE |
++		    ((flg & FD_WRLCK) ? (O_CREAT|O_RDWR) : O_RDONLY), 0600);
++
++    if (*unit == -1) {
++	if (!bequiet)
++	    stderror(ERR_SYSTEM, f, strerror(errno));
++	return -1;
++    }
++
++    cleanup_push(unit, open_cleanup);
++    *unit = dmove(*unit, -1);
++    (void) close_on_exec(*unit, 1);
++    copy = *unit;
++
++    if (flg & (FD_WRLCK | FD_RDLCK)) {
++	struct flock fl;
++	
++	fl.l_type = (flg & FD_WRLCK) ? F_WRLCK : F_RDLCK;
++	fl.l_whence = SEEK_SET;
++	fl.l_start = 0;
++	fl.l_len = 0;
++
++	if (fcntl(*unit, F_SETLKW, &fl) != -1)
++	    cleanup_push(unit, fcntl_cleanup);
++    }
++
++    srcunit(copy, onlyown, (flg & (SRC_HFLAG | SRC_MFLAG)), av);
++
++    /* Close the unit, if we don't want to leave it open & locked. */
++    if ((flg & (FD_WRLCK | FD_RDLCK)) && (!(flg & FD_LEAVE_LCKD))) {
++	cleanup_until(unit); /* fcntl_cleanup */
++	if (!(flg & FD_LEAVE_OPEN)) {
++	    cleanup_until(unit); /* open_cleanup */
++	    cleanup_until(unit); /* xfree */
++	}
++	
++	return -1; /* Invalid file descriptor. */
++    }
++    
++    return *unit;
+ }
+ 
+ 
+@@ -1475,10 +1508,14 @@ st_save(struct saved_state *st, int unit, int hflg, Char **al, Char **av)
+     st->onelflg		= onelflg;
+     st->enterhist	= enterhist;
+     st->justpr		= justpr;
+-    if (hflg)
++    if (hflg && SRC_HFLAG)
+ 	st->HIST	= HIST;
+     else
+ 	st->HIST	= '\0';
++    if (hflg && FD_LEAVE_OPEN)
++	st->close_unit = 0;
++    else
++	st->close_unit = 1;
+     st->cantell		= cantell;
+     cpybin(st->B, B);
+ 
+@@ -1550,7 +1587,8 @@ st_restore(void *xst)
+     }
+     cpybin(B, st->B);
+ 
+-    xclose(SHIN);
++    if (st->close_unit)
++	xclose(SHIN);
+ 
+     insource	= st->insource;
+     SHIN	= st->SHIN;
+@@ -2024,24 +2062,25 @@ process(int catch)
+ }
+ 
+ /*ARGSUSED*/
+-void
+-dosource(Char **t, struct command *c)
++int
++dosource(Char **t, struct command *c, int flg)
+ {
+     Char *f;
+-    int    hflg = 0;
+     char *file;
++    int fd;
++    int hflg = 0;
+ 
+     USE(c);
+     t++;
+     if (*t && eq(*t, STRmh)) {
+ 	if (*++t == NULL)
+ 	    stderror(ERR_NAME | ERR_HFLAG);
+-	hflg++;
++	hflg |= SRC_HFLAG;
+     }
+     else if (*t && eq(*t, STRmm)) {
+     	if (*++t == NULL)
+ 	    stderror(ERR_NAME | ERR_MFLAG);
+-	hflg = 2;
++	hflg |= SRC_MFLAG;
+     }
+ 
+     f = globone(*t++, G_ERROR);
+@@ -2049,9 +2088,13 @@ dosource(Char **t, struct command *c)
+     cleanup_push(file, xfree);
+     xfree(f);
+     t = glob_all_or_error(t);
+-    if ((!srcfile(file, 0, hflg, t)) && (!hflg) && (!bequiet))
+-	stderror(ERR_SYSTEM, file, strerror(errno));
+-    cleanup_until(file);
++    fd = srcfile(file, 0, (hflg | flg), t);
++    
++    /* Postpone fd cleanup, which is on the top of cleaning stack. */
++    cleanup_ignore(file);
++    xfree(file);
++    
++    return fd; /* File descriptor or -1. */
+ }
+ 
+ /*
+diff --git a/sh.decls.h b/sh.decls.h
+index 70f76a4..806ae3a 100644
+--- a/sh.decls.h
++++ b/sh.decls.h
+@@ -37,7 +37,7 @@
+  * sh.c
+  */
+ extern	Char	 	 *gethdir	(const Char *);
+-extern	void		  dosource	(Char **, struct command *);
++extern	int		  dosource	(Char **, struct command *, int);
+ extern	void		  exitstat	(void);
+ extern	void		  goodbye	(Char **, struct command *);
+ extern	void		  importpath	(Char *);
+@@ -97,6 +97,7 @@ extern	void		  cleanup_until_mark(void);
+ extern	size_t		  cleanup_push_mark(void);
+ extern	void		  cleanup_pop_mark(size_t);
+ extern	void		  open_cleanup(void *);
++extern	void		  fcntl_cleanup(void *);
+ extern	void		  opendir_cleanup(void *);
+ extern	void		  sigint_cleanup(void *);
+ extern	void		  sigprocmask_cleanup(void *);
+@@ -216,7 +217,7 @@ extern  struct Hist 	 *enthist	(int, struct wordent *, int, int);
+ extern	void	 	  savehist	(struct wordent *, int);
+ extern	char		 *fmthist	(int, ptr_t);
+ extern	void		  rechist	(Char *, int);
+-extern	void		  loadhist	(Char *, int);
++extern	int		  loadhist	(Char *, int);
+ 
+ /*
+  * sh.init.c
+diff --git a/sh.dir.c b/sh.dir.c
+index 9f72951..b75c366 100644
+--- a/sh.dir.c
++++ b/sh.dir.c
+@@ -1336,7 +1336,7 @@ loaddirs(Char *fname)
+ 	loaddirs_cmd[1] = fname;
+     else
+ 	loaddirs_cmd[1] = STRtildotdirs;
+-    dosource(loaddirs_cmd, NULL);
++    dosource(loaddirs_cmd, NULL, 0);
+     cleanup_until(&bequiet);
+ }
+ 
+diff --git a/sh.dol.c b/sh.dol.c
+index bafb971..b9fe82a 100644
+--- a/sh.dol.c
++++ b/sh.dol.c
+@@ -1099,6 +1099,6 @@ again:
+     *obp = 0;
+     tmp = short2str(obuf);
+     (void) xwrite(0, tmp, strlen (tmp));
+-    (void) lseek(0, (off_t) 0, L_SET);
++    (void) lseek(0, 0, SEEK_SET);
+     cleanup_until(&inheredoc);
+ }
+diff --git a/sh.err.c b/sh.err.c
+index 279c7b8..9759d72 100644
+--- a/sh.err.c
++++ b/sh.err.c
+@@ -505,6 +505,22 @@ open_cleanup(void *xptr)
+ }
+ 
+ void
++fcntl_cleanup(void *xptr)
++{
++    int *ptr;
++    struct flock fl;
++
++    ptr = xptr;
++    
++    fl.l_type = F_UNLCK;
++    fl.l_whence = SEEK_SET;
++    fl.l_start = 0;
++    fl.l_len = 0;
++    
++    fcntl(*ptr, F_SETLK, &fl);
++}
++
++void
+ opendir_cleanup(void *xdir)
+ {
+     DIR *dir;
+diff --git a/sh.h b/sh.h
+index 83a3e93..c05870f 100644
+--- a/sh.h
++++ b/sh.h
+@@ -50,6 +50,23 @@
+ # include <inttypes.h>
+ #endif
+ 
++#include <unistd.h>
++#include <fcntl.h>
++/*
++ * Source flags (representing -h or -m).
++ * 
++ * File locking flags.
++ * - shared and exclusive (read and write) access to a file
++ * - currently used in sh.c and sh.hist.c files while acessing
++ *   ~/.history file only.
++ */
++#define SRC_HFLAG	0x01 /* History flag */
++#define SRC_MFLAG	0x02 /* Merge flag */
++#define FD_WRLCK	0x04 /* Write lock */
++#define FD_RDLCK	0x08 /* Read lock */
++#define FD_LEAVE_OPEN	0x10 /* Leave file open */
++#define FD_LEAVE_LCKD	0x20 /* Leave file locked */
++
+ #if !defined(HAVE_STDINT_H) && !defined(HAVE_INTTYPES_H) && !defined(WINNT_NATIVE)
+ typedef unsigned long intptr_t;
+ #endif
+diff --git a/sh.hist.c b/sh.hist.c
+index 2863d5c..f0583cb 100644
+--- a/sh.hist.c
++++ b/sh.hist.c
+@@ -255,7 +255,7 @@ dohist(Char **vp, struct command *c)
+     }
+ 
+     if (hflg & (HIST_LOAD | HIST_MERGE))
+-	loadhist(*vp, (hflg & HIST_MERGE) ? 1 : 0);
++	loadhist(*vp, ((hflg & HIST_MERGE) ? SRC_MFLAG : 0) | FD_RDLCK);
+     else if (hflg & HIST_SAVE)
+ 	rechist(*vp, 1);
+     else {
+@@ -374,8 +374,8 @@ fmthist(int fmt, ptr_t ptr)
+ void
+ rechist(Char *fname, int ref)
+ {
+-    Char    *snum;
+-    int     fp, ftmp, oldidfds;
++    Char   *snum;
++    int    fd = -1, ftmp, oldidfds;
+     struct varent *shist;
+     static Char   *dumphist[] = {STRhistory, STRmhT, 0, 0};
+ 
+@@ -405,15 +405,12 @@ rechist(Char *fname, int ref)
+      * with numerous shells being in simultaneous use. Imagine
+      * any kind of window system. All these shells 'share' the same 
+      * ~/.history file for recording their command line history. 
+-     * Currently the automatic merge can only succeed when the shells
+-     * nicely quit one after another. 
+-     *
+-     * Users that like to nuke their environment require here an atomic
+-     * 	loadhist-creat-dohist(dumphist)-close
+-     * sequence.
+-     *
+-     * jw.
+-     */ 
++     * 
++     * Atomic merge loadhist-creat/ftrunc-dohist(dumphist)-close
++     * implemented using fcntl (shared readers, exclusive writer)
++     * by Vojtech Vitek (V-Teq) <vvitek at redhat.com>.
++     */
++
+     /*
+      * We need the didfds stuff before loadhist otherwise
+      * exec in a script will fail to print if merge is set.
+@@ -421,41 +418,49 @@ rechist(Char *fname, int ref)
+      */
+     oldidfds = didfds;
+     didfds = 0;
+-    if ((shist = adrof(STRsavehist)) != NULL && shist->vec != NULL)
+-	if (shist->vec[1] && eq(shist->vec[1], STRmerge)) {
+-	    /*
+-	     * Unset verbose while we read the history file. From:
+-	     * jbastian at redhat.com (Jeffrey Bastian)
+-	     */
+-	    Char *verb = varval(STRverbose);
+-	    if (verb != STRNULL)
+-		unsetv(STRverbose);
+-	    loadhist(fname, 1);
+-	    if (verb != STRNULL)
+-		setv(STRverbose, verb, VAR_READWRITE);
++    if ((shist = adrof(STRsavehist)) != NULL && shist->vec != NULL
++	&& shist->vec[1] && eq(shist->vec[1], STRmerge)) {
++	/*
++	 * Unset verbose while we read the history file. From:
++	 * jbastian at redhat.com (Jeffrey Bastian)
++	 */
++	Char *verb = varval(STRverbose);
++	if (verb != STRNULL)
++	    unsetv(STRverbose);
++	/* Read .history file, leave it's fd open for writing. */
++	fd = loadhist(fname, SRC_MFLAG|FD_WRLCK|FD_LEAVE_OPEN|FD_LEAVE_LCKD);
++	if (fd != -1) {
++	    /* Truncate the .history file. */
++	    (void) ftruncate(fd, 0);
++	    (void) lseek(fd, 0, SEEK_SET);
+ 	}
+-    fp = xcreat(short2str(fname), 0600);
+-    if (fp == -1) {
+-	didfds = oldidfds;
+-	cleanup_until(fname);
+-	return;
++	if (verb != STRNULL)
++	    setv(STRverbose, verb, VAR_READWRITE);
++    }
++    if (fd == -1) {
++        /* Open .history file for writing (if not open yet). */
++	fd = xopen(short2str(fname), O_LARGEFILE|O_CREAT|O_WRONLY|O_TRUNC, 0600);
++	if (fd != -1)
++	    cleanup_push(&fd, open_cleanup);
++    }
++    if (fd != -1) {
++	ftmp = SHOUT;
++	SHOUT = fd;
++	dumphist[2] = snum;
++	/* Write history to the .history file. */
++	dohist(dumphist, NULL);
++	SHOUT = ftmp;
+     }
+-    ftmp = SHOUT;
+-    SHOUT = fp;
+-    dumphist[2] = snum;
+-    dohist(dumphist, NULL);
+-    xclose(fp);
+-    SHOUT = ftmp;
+     didfds = oldidfds;
+     cleanup_until(fname);
+ }
+ 
+ 
+-void
+-loadhist(Char *fname, int mflg)
++int
++loadhist(Char *fname, int flg)
+ {
+     static Char   *loadhist_cmd[] = {STRsource, NULL, NULL, NULL};
+-    loadhist_cmd[1] = mflg ? STRmm : STRmh;
++    loadhist_cmd[1] = (flg & SRC_MFLAG) ? STRmm : STRmh;
+ 
+     if (fname != NULL)
+ 	loadhist_cmd[2] = fname;
+@@ -464,5 +469,5 @@ loadhist(Char *fname, int mflg)
+     else
+ 	loadhist_cmd[2] = STRtildothist;
+ 
+-    dosource(loadhist_cmd, NULL);
++    return dosource(loadhist_cmd, NULL, flg);
+ }
+diff --git a/sh.lex.c b/sh.lex.c
+index 536097e..2543552 100644
+--- a/sh.lex.c
++++ b/sh.lex.c
+@@ -1589,7 +1589,7 @@ wide_read(int fildes, Char *buf, size_t nchars, int use_fclens)
+     /* Throwing away possible partial multibyte characters on error if the
+        stream is not seekable */
+     err = errno;
+-    lseek(fildes, -(off_t)partial, L_INCR);
++    lseek(fildes, -partial, SEEK_CUR);
+     errno = err;
+     return res != 0 ? res : r;
+ }
+@@ -1604,7 +1604,7 @@ bgetc(void)
+     if (cantell) {
+ 	if (fseekp < fbobp || fseekp > feobp) {
+ 	    fbobp = feobp = fseekp;
+-	    (void) lseek(SHIN, fseekp, L_SET);
++	    (void) lseek(SHIN, fseekp, SEEK_SET);
+ 	}
+ 	if (fseekp == feobp) {
+ #ifdef WIDE_STRINGS
+@@ -1808,7 +1808,7 @@ btell(struct Ain *l)
+ void
+ btoeof(void)
+ {
+-    (void) lseek(SHIN, (off_t) 0, L_XTND);
++    (void) lseek(SHIN, 0, SEEK_END);
+     aret = TCSH_F_SEEK;
+     fseekp = feobp;
+     alvec = NULL;
+@@ -1826,7 +1826,7 @@ settell(void)
+     cantell = 0;
+     if (arginp || onelflg || intty)
+ 	return;
+-    if ((x = lseek(SHIN, (off_t) 0, L_INCR)) == -1)
++    if ((x = lseek(SHIN, 0, SEEK_CUR)) == -1)
+ 	return;
+     fbuf = xcalloc(2, sizeof(Char **));
+     fblocks = 1;
+diff --git a/sh.sem.c b/sh.sem.c
+index 7c5511a..b8eee96 100644
+--- a/sh.sem.c
++++ b/sh.sem.c
+@@ -879,7 +879,7 @@ doio(struct command *t, int *pipein, int *pipeout)
+ 	    fd = xopen(tmp, O_WRONLY|O_APPEND|O_LARGEFILE);
+ #else /* !O_APPEND */
+ 	    fd = xopen(tmp, O_WRONLY|O_LARGEFILE);
+-	    (void) lseek(fd, (off_t) 0, L_XTND);
++	    (void) lseek(fd, 0, SEEK_END);
+ #endif /* O_APPEND */
+ 	}
+ 	else
+-- 
+1.7.6.2
+
diff --git a/tcsh.spec b/tcsh.spec
index 656f2ec..6497e58 100644
--- a/tcsh.spec
+++ b/tcsh.spec
@@ -43,6 +43,8 @@ Patch26: tcsh-6.17.00-variable-names.patch
 Patch27: tcsh-6.17.00-status-pipeline-backquote-list-of-cmds.patch
 # Proposed to upstream (http://bugs.gw.com/view.php?id=121)
 Patch28: tcsh-6.17.00-manpage-spelling.patch
+Patch30: tcsh-6.17.00-handle-signals-before-flush.patch
+Patch31: tcsh-6.17.00-history-file-locking.patch
 
 Provides: csh = %{version}
 Requires(post): grep
@@ -85,6 +87,8 @@ like syntax.
 %patch26 -p1 -b .variable-names
 %patch27 -p1 -b .status-pipeline-backquote-list-of-cmds
 %patch28 -p1 -b .manpage-spelling
+%patch30 -p1 -b .handle-signals-before-flush
+%patch31 -p1 -b .history-file-locking
 
 for i in Fixes WishList; do
  iconv -f iso-8859-1 -t utf-8 "$i" > "${i}_" && \
@@ -158,6 +162,12 @@ fi
 %{_mandir}/man1/*.1*
 
 %changelog
+* Thu Feb 16 2012 Vojtech Vitek (V-Teq) <vvitek at redhat.com> - 6.17-19
+- Handle pending signals before flush so that the the .history file
+  does not get truncated (#653054)
+- Implement file locking using shared readers, exclusive writer
+  to prevent any .history file data corruption (#653054)
+
 * Sat Jan 14 2012 Fedora Release Engineering <rel-eng at lists.fedoraproject.org> - 6.17-18
 - Rebuilt for https://fedoraproject.org/wiki/Fedora_17_Mass_Rebuild
 


More information about the scm-commits mailing list