zsh-workers
 help / color / mirror / code / Atom feed
* PATCH: chown and chgrp in files module
@ 1999-12-09 16:02 zefram
  1999-12-09 17:20 ` James Kirkpatrick
  0 siblings, 1 reply; 7+ messages in thread
From: zefram @ 1999-12-09 16:02 UTC (permalink / raw)
  To: zsh-workers

I got a request for chown/chgrp as shell builtins, so here they are.
It's pretty simple; most of the code change is actually just factoring
out the recursion code from rm, so that it can be shared with chown.
As a bonus, chown gets rm's -s option.

I'm not 100% happy about the error handling in the recursion code.
It seems to me that if the current directory is lost, the error message
and return code should not be suppressed by rm's -f option.  But I've
left it as it is, because I don't immediately see how to separate out
that case from the other errors handled by that code.

-zefram

diff -cr ../zsh-/Doc/Zsh/mod_files.yo ./Doc/Zsh/mod_files.yo
*** ../zsh-/Doc/Zsh/mod_files.yo	Sun Nov 28 17:42:27 1999
--- ./Doc/Zsh/mod_files.yo	Thu Dec  9 12:33:23 1999
***************
*** 4,9 ****
--- 4,46 ----
  The tt(files) module makes some standard commands available as builtins:
  
  startitem()
+ findex(chgrp)
+ item(tt(chgrp) [ tt(-Rs) ] var(group) var(filename) ...)(
+ Changes group of files specified.  This is equivalent to tt(chown) with
+ a var(user-spec) argument of `tt(:)var(group)'.
+ )
+ findex(chown)
+ item(tt(chown) [ tt(-Rs) ] var(user-spec) var(filename) ...)(
+ Changes ownership and group of files specified.
+ 
+ The var(user-spec) can be in four forms:
+ 
+ startsitem()
+ sitem(var(user))(change owner to var(user); do not change group)
+ sitem(var(user)tt(:))(change owner to var(user); change group to var(user)'s primary group)
+ sitem(var(user)tt(:)var(group))(change owner to var(user); change group to var(group))
+ sitem(tt(:)var(group))(do not change owner; change group to var(group))
+ endsitem()
+ 
+ In each case, the `tt(:)' may instead be a `tt(.)'.
+ Each of var(user) and var(group) may be either a username (or group name, as
+ appropriate) or a decimal user ID (group ID).  Interpretation as a name
+ takes precedence, if there is an all-numeric username (or group name).
+ 
+ The tt(-R) option causes tt(chown) to recursively descend into directories,
+ changing the ownership of all files in the directory after
+ changing the ownership of the directory itself.
+ 
+ The tt(-s) option is a zsh extension to tt(chown) functionality.  It enables
+ paranoid behaviour, intended to avoid security problems involving
+ a tt(chown) being tricked into affecting files other than the ones
+ intended.  It will refuse to follow symbolic links, so that (for example)
+ ``tt(chown luser /tmp/foo/passwd)'' can't accidentally chown tt(/etc/passwd)
+ if tt(/tmp/foo) happens to be a link to tt(/etc).  It will also check
+ where it is after leaving directories, so that a recursive chown of
+ a deep directory tree can't end up recursively chowning tt(/usr) as
+ a result of directories being moved up the tree.
+ )
  findex(ln)
  xitem(tt(ln) [ tt(-dfis) ] var(filename) var(dest))
  item(tt(ln) [ tt(-dfis) ] var(filename) ... var(dir))(
diff -cr ../zsh-/Src/Modules/files.c ./Src/Modules/files.c
*** ../zsh-/Src/Modules/files.c	Sun Nov 28 17:42:28 1999
--- ./Src/Modules/files.c	Thu Dec  9 15:43:52 1999
***************
*** 30,35 ****
--- 30,36 ----
  #include "files.mdh"
  
  typedef int (*MoveFunc) _((char const *, char const *));
+ typedef int (*RecurseFunc) _((char *, char *, struct stat const *, void *));
  
  #ifndef STDC_HEADERS
  extern int link _((const char *, const char *));
***************
*** 37,42 ****
--- 38,45 ----
  extern int rename _((const char *, const char *));
  #endif
  
+ struct recursivecmd;
+ 
  #include "files.pro"
  
  /**/
***************
*** 312,331 ****
      return 0;
  }
  
! /* rm builtin */
  
  /**/
  static int
! bin_rm(char *nam, char **args, char *ops, int func)
  {
      int err = 0, len;
      char *rp, *s;
      struct dirsav ds;
  
      ds.ino = ds.dev = 0;
      ds.dirname = NULL;
      ds.dirfd = ds.level = -1;
!     if (ops['r'] || ops['s']) {
  	if ((ds.dirfd = open(".", O_RDONLY|O_NOCTTY)) < 0 &&
  	    zgetdir(&ds) && *ds.dirname != '/')
  	    ds.dirfd = open("..", O_RDONLY|O_NOCTTY);
--- 315,356 ----
      return 0;
  }
  
! /* general recursion */
! 
! struct recursivecmd {
!     char *nam;
!     int opt_noerr;
!     int opt_recurse;
!     int opt_safe;
!     RecurseFunc dirpre_func;
!     RecurseFunc dirpost_func;
!     RecurseFunc leaf_func;
!     void *magic;
! };
  
  /**/
  static int
! recursivecmd(char *nam, int opt_noerr, int opt_recurse, int opt_safe,
!     char **args, RecurseFunc dirpre_func, RecurseFunc dirpost_func,
!     RecurseFunc leaf_func, void *magic)
  {
      int err = 0, len;
      char *rp, *s;
      struct dirsav ds;
+     struct recursivecmd reccmd;
  
+     reccmd.nam = nam;
+     reccmd.opt_noerr = opt_noerr;
+     reccmd.opt_recurse = opt_recurse;
+     reccmd.opt_safe = opt_safe;
+     reccmd.dirpre_func = dirpre_func;
+     reccmd.dirpost_func = dirpost_func;
+     reccmd.leaf_func = leaf_func;
+     reccmd.magic = magic;
      ds.ino = ds.dev = 0;
      ds.dirname = NULL;
      ds.dirfd = ds.level = -1;
!     if (opt_recurse || opt_safe) {
  	if ((ds.dirfd = open(".", O_RDONLY|O_NOCTTY)) < 0 &&
  	    zgetdir(&ds) && *ds.dirname != '/')
  	    ds.dirfd = open("..", O_RDONLY|O_NOCTTY);
***************
*** 333,339 ****
      for(; !errflag && !(err & 2) && *args; args++) {
  	rp = ztrdup(*args);
  	unmetafy(rp, &len);
! 	if (ops['s']) {
  	    s = strrchr(rp, '/');
  	    if (s && !s[1]) {
  		while (*s == '/' && s > rp)
--- 358,364 ----
      for(; !errflag && !(err & 2) && *args; args++) {
  	rp = ztrdup(*args);
  	unmetafy(rp, &len);
! 	if (opt_safe) {
  	    s = strrchr(rp, '/');
  	    if (s && !s[1]) {
  		while (*s == '/' && s > rp)
***************
*** 353,368 ****
  		    d.ino = d.dev = 0;
  		    d.dirname = NULL;
  		    d.dirfd = d.level = -1;
! 		    err |= dorm(nam, *args, s + 1, ops, &d, 0);
  		    zsfree(d.dirname);
  		    if (restoredir(&ds))
  			err |= 2;
! 		} else
  		    zwarnnam(nam, "%s: %e", *args, errno);
  	    } else
! 		err |= dorm(nam, *args, rp, ops, &ds, 0);
  	} else
! 	    err |= dorm(nam, *args, rp, ops, &ds, 1);
  	zfree(rp, len + 1);
      }
      if ((err & 2) && ds.dirfd >= 0 && restoredir(&ds) && zchdir(pwd)) {
--- 378,393 ----
  		    d.ino = d.dev = 0;
  		    d.dirname = NULL;
  		    d.dirfd = d.level = -1;
! 		    err |= recursivecmd_doone(&reccmd, *args, s + 1, &d, 0);
  		    zsfree(d.dirname);
  		    if (restoredir(&ds))
  			err |= 2;
! 		} else if(!opt_noerr)
  		    zwarnnam(nam, "%s: %e", *args, errno);
  	    } else
! 		err |= recursivecmd_doone(&reccmd, *args, rp, &ds, 0);
  	} else
! 	    err |= recursivecmd_doone(&reccmd, *args, rp, &ds, 1);
  	zfree(rp, len + 1);
      }
      if ((err & 2) && ds.dirfd >= 0 && restoredir(&ds) && zchdir(pwd)) {
***************
*** 373,448 ****
      if (ds.dirfd >= 0)
  	close(ds.dirfd);
      zsfree(ds.dirname);
!     return ops['f'] ? 0 : !!err;
  }
  
  /**/
  static int
! dorm(char *nam, char *arg, char *rp, char *ops, struct dirsav *ds, int first)
  {
!     struct stat st;
  
!     if((!ops['d'] || !ops['f']) && !lstat(rp, &st)) {
! 	if(!ops['d'] && S_ISDIR(st.st_mode)) {
! 	    if(ops['r'])
! 		return dormr(nam, arg, rp, ops, ds, first);
! 	    if(!ops['f'])
! 		zwarnnam(nam, "%s: %e", arg, EISDIR);
! 	    return 1;
! 	}
! 	if(!ops['f'] && ops['i']) {
! 	    nicezputs(nam, stderr);
! 	    fputs(": remove `", stderr);
! 	    nicezputs(arg, stderr);
! 	    fputs("'? ", stderr);
! 	    fflush(stderr);
! 	    if(!ask())
! 		return 0;
! 	} else if(!ops['f'] &&
! 		!S_ISLNK(st.st_mode) &&
! 	    	access(rp, W_OK)) {
! 	    nicezputs(nam, stderr);
! 	    fputs(": remove `", stderr);
! 	    nicezputs(arg, stderr);
! 	    fprintf(stderr, "', overriding mode %04o? ",
! 		mode_to_octal(st.st_mode));
! 	    fflush(stderr);
! 	    if(!ask())
! 		return 0;
! 	}
      }
!     if(!unlink(rp))
! 	return 0;
!     if(!ops['f'])
! 	zwarnnam(nam, "%s: %e", arg, errno);
!     return 1;
  }
  
  /**/
  static int
! dormr(char *nam, char *arg, char *rp, char *ops, struct dirsav *ds, int first)
  {
      char *fn;
      DIR *d;
!     int err;
      struct dirsav dsav;
      char *files = NULL;
      int fileslen = 0;
  
      err = -lchdir(rp, ds, !first);
      if (err) {
! 	if (!ops['f'])
! 	    zwarnnam(nam, "%s: %e", arg, errno);
  	return err;
      }
  
      dsav.ino = dsav.dev = 0;
      dsav.dirname = NULL;
      dsav.dirfd = dsav.level = -1;
      d = opendir(".");
      if(!d) {
! 	if(!ops['f'])
! 	    zwarnnam(nam, "%s: %e", arg, errno);
  	err = 1;
      } else {
  	int arglen = strlen(arg) + 1;
--- 398,452 ----
      if (ds.dirfd >= 0)
  	close(ds.dirfd);
      zsfree(ds.dirname);
!     return !!err;
  }
  
  /**/
  static int
! recursivecmd_doone(struct recursivecmd const *reccmd,
!     char *arg, char *rp, struct dirsav *ds, int first)
  {
!     struct stat st, *sp = NULL;
  
!     if(reccmd->opt_recurse && !lstat(rp, &st)) {
! 	if(S_ISDIR(st.st_mode))
! 	    return recursivecmd_dorec(reccmd, arg, rp, &st, ds, first);
! 	sp = &st;
      }
!     return reccmd->leaf_func(arg, rp, sp, reccmd->magic);
  }
  
  /**/
  static int
! recursivecmd_dorec(struct recursivecmd const *reccmd,
!     char *arg, char *rp, struct stat const *sp, struct dirsav *ds, int first)
  {
      char *fn;
      DIR *d;
!     int err, err1;
      struct dirsav dsav;
      char *files = NULL;
      int fileslen = 0;
  
+     err1 = reccmd->dirpre_func(arg, rp, sp, reccmd->magic);
+     if(err1 & 2)
+ 	return 2;
+ 
      err = -lchdir(rp, ds, !first);
      if (err) {
! 	if(!reccmd->opt_noerr)
! 	    zwarnnam(reccmd->nam, "%s: %e", arg, errno);
  	return err;
      }
+     err = err1;
  
      dsav.ino = dsav.dev = 0;
      dsav.dirname = NULL;
      dsav.dirfd = dsav.level = -1;
      d = opendir(".");
      if(!d) {
! 	if(!reccmd->opt_noerr)
! 	    zwarnnam(reccmd->nam, "%s: %e", arg, errno);
  	err = 1;
      } else {
  	int arglen = strlen(arg) + 1;
***************
*** 462,468 ****
  	    narg[arglen-1] = '/';
  	    strcpy(narg + arglen, fn);
  	    unmetafy(fn, NULL);
! 	    err |= dorm(nam, narg, fn, ops, &dsav, 0);
  	    fn += l;
  	}
  	hrealloc(files, fileslen, 0);
--- 466,472 ----
  	    narg[arglen-1] = '/';
  	    strcpy(narg + arglen, fn);
  	    unmetafy(fn, NULL);
! 	    err |= recursivecmd_doone(reccmd, narg, fn, &dsav, 0);
  	    fn += l;
  	}
  	hrealloc(files, fileslen, 0);
***************
*** 471,495 ****
      if (err & 2)
  	return 2;
      if (restoredir(ds)) {
! 	if(!ops['f'])
! 	    zwarnnam(nam, "failed to return to previous directory: %e",
  		     NULL, errno);
  	return 2;
      }
!     if(!ops['f'] && ops['i']) {
! 	nicezputs(nam, stderr);
  	fputs(": remove `", stderr);
  	nicezputs(arg, stderr);
  	fputs("'? ", stderr);
  	fflush(stderr);
  	if(!ask())
! 	    return err;
      }
!     if(!rmdir(rp))
! 	return err;
!     if(!ops['f'])
! 	zwarnnam(nam, "%s: %e", arg, errno);
!     return 1;
  }
  
  /* module paraphernalia */
--- 475,693 ----
      if (err & 2)
  	return 2;
      if (restoredir(ds)) {
! 	if(!reccmd->opt_noerr)
! 	    zwarnnam(reccmd->nam, "failed to return to previous directory: %e",
  		     NULL, errno);
  	return 2;
      }
!     return err | reccmd->dirpost_func(arg, rp, sp, reccmd->magic);
! }
! 
! /**/
! static int
! recurse_donothing(char *arg, char *rp, struct stat const *sp, void *magic)
! {
!     return 0;
! }
! 
! /* rm builtin */
! 
! struct rmmagic {
!     char *nam;
!     int opt_force;
!     int opt_interact;
!     int opt_unlinkdir;
! };
! 
! /**/
! static int
! rm_leaf(char *arg, char *rp, struct stat const *sp, void *magic)
! {
!     struct rmmagic *rmm = magic;
!     struct stat st;
! 
!     if(!rmm->opt_unlinkdir || !rmm->opt_force) {
! 	if(!sp) {
! 	    if(!lstat(rp, &st))
! 		sp = &st;
! 	}
! 	if(sp) {
! 	    if(!rmm->opt_unlinkdir && S_ISDIR(sp->st_mode)) {
! 		if(rmm->opt_force)
! 		    return 0;
! 		zwarnnam(rmm->nam, "%s: %e", arg, EISDIR);
! 		return 1;
! 	    }
! 	    if(rmm->opt_interact) {
! 		nicezputs(rmm->nam, stderr);
! 		fputs(": remove `", stderr);
! 		nicezputs(arg, stderr);
! 		fputs("'? ", stderr);
! 		fflush(stderr);
! 		if(!ask())
! 		    return 0;
! 	    } else if(!rmm->opt_force &&
! 		    !S_ISLNK(sp->st_mode) &&
! 		    access(rp, W_OK)) {
! 		nicezputs(rmm->nam, stderr);
! 		fputs(": remove `", stderr);
! 		nicezputs(arg, stderr);
! 		fprintf(stderr, "', overriding mode %04o? ",
! 		    mode_to_octal(sp->st_mode));
! 		fflush(stderr);
! 		if(!ask())
! 		    return 0;
! 	    }
! 	}
!     }
!     if(unlink(rp) && !rmm->opt_force) {
! 	zwarnnam(rmm->nam, "%s: %e", arg, errno);
! 	return 1;
!     }
!     return 0;
! }
! 
! /**/
! static int
! rm_dirpost(char *arg, char *rp, struct stat const *sp, void *magic)
! {
!     struct rmmagic *rmm = magic;
! 
!     if(rmm->opt_interact) {
! 	nicezputs(rmm->nam, stderr);
  	fputs(": remove `", stderr);
  	nicezputs(arg, stderr);
  	fputs("'? ", stderr);
  	fflush(stderr);
  	if(!ask())
! 	    return 0;
      }
!     if(rmdir(rp) && !rmm->opt_force) {
! 	zwarnnam(rmm->nam, "%s: %e", arg, errno);
! 	return 1;
!     }
!     return 0;
! }
! 
! /**/
! static int
! bin_rm(char *nam, char **args, char *ops, int func)
! {
!     struct rmmagic rmm;
!     int err;
! 
!     rmm.nam = nam;
!     rmm.opt_force = ops['f'];
!     rmm.opt_interact = ops['i'] && !ops['f'];
!     rmm.opt_unlinkdir = ops['d'];
!     err = recursivecmd(nam, ops['f'], ops['r'] && !ops['d'], ops['s'],
! 	args, recurse_donothing, rm_dirpost, rm_leaf, &rmm);
!     return ops['f'] ? 0 : err;
! }
! 
! /* chown builtin */
! 
! struct chownmagic {
!     char *nam;
!     uid_t uid;
!     gid_t gid;
! };
! 
! /**/
! static int
! chown_dochown(char *arg, char *rp, struct stat const *sp, void *magic)
! {
!     struct chownmagic *chm = magic;
! 
!     if(lchown(rp, chm->uid, chm->gid)) {
! 	zwarnnam(chm->nam, "%s: %e", arg, errno);
! 	return 1;
!     }
!     return 0;
! }
! 
! /**/
! static unsigned long getnumeric(char *p, int *errp)
! {
!     unsigned long ret;
! 
!     if(*p < '0' || *p > '9') {
! 	*errp = 1;
! 	return 0;
!     }
!     ret = strtoul(p, &p, 10);
!     *errp = !!*p;
!     return ret;
! }
! 
! enum { BIN_CHOWN, BIN_CHGRP };
! 
! /**/
! static int
! bin_chown(char *nam, char **args, char *ops, int func)
! {
!     struct chownmagic chm;
!     char *uspec = ztrdup(*args), *p = uspec;
! 
!     chm.nam = nam;
!     if(func == BIN_CHGRP) {
! 	chm.uid = -1;
! 	goto dogroup;
!     }
!     if(*p == ':' || *p == '.') {
! 	chm.uid = -1;
! 	p++;
! 	goto dogroup;
!     } else {
! 	struct passwd *pwd;
! 	char *end = strchr(p, ':');
! 	if(!end)
! 	    end = strchr(p, '.');
! 	if(end)
! 	    *end = 0;
! 	pwd = getpwnam(p);
! 	if(pwd)
! 	    chm.uid = pwd->pw_uid;
! 	else {
! 	    int err;
! 	    chm.uid = getnumeric(p, &err);
! 	    if(err) {
! 		zwarnnam(nam, "%s: no such user", p, 0);
! 		free(uspec);
! 		return 1;
! 	    }
! 	}
! 	if(end) {
! 	    p = end+1;
! 	    if(!*p) {
! 		if(!pwd && !(pwd = getpwuid(chm.uid))) {
! 		    zwarnnam(nam, "%s: no such user", uspec, 0);
! 		    free(uspec);
! 		    return 1;
! 		}
! 		chm.gid = pwd->pw_gid;
! 	    } else {
! 		struct group *grp;
! 		dogroup:
! 		grp = getgrnam(p);
! 		if(grp)
! 		    chm.gid = grp->gr_gid;
! 		else {
! 		    int err;
! 		    chm.gid = getnumeric(p, &err);
! 		    if(err) {
! 			zwarnnam(nam, "%s: no such group", p, 0);
! 			free(uspec);
! 			return 1;
! 		    }
! 		}
! 	    }
! 	 } else
! 	    chm.gid = -1;
!     }
!     free(uspec);
!     return recursivecmd(nam, 0, ops['R'], ops['s'],
! 	args + 1, chown_dochown, recurse_donothing, chown_dochown, &chm);
  }
  
  /* module paraphernalia */
***************
*** 501,512 ****
  #endif
  
  static struct builtin bintab[] = {
!     BUILTIN("ln",    0, bin_ln,    1, -1, BIN_LN, LN_OPTS, NULL),
!     BUILTIN("mkdir", 0, bin_mkdir, 1, -1, 0,      "pm",    NULL),
!     BUILTIN("mv",    0, bin_ln,    2, -1, BIN_MV, "fi",    NULL),
!     BUILTIN("rm",    0, bin_rm,    1, -1, 0,      "dfirs", NULL),
!     BUILTIN("rmdir", 0, bin_rmdir, 1, -1, 0,      NULL,    NULL),
!     BUILTIN("sync",  0, bin_sync,  0,  0, 0,      NULL,    NULL),
  };
  
  /**/
--- 699,712 ----
  #endif
  
  static struct builtin bintab[] = {
!     BUILTIN("chgrp", 0, bin_chown, 2, -1, BIN_CHGRP, "Rs",    NULL),
!     BUILTIN("chown", 0, bin_chown, 2, -1, BIN_CHOWN, "Rs",    NULL),
!     BUILTIN("ln",    0, bin_ln,    1, -1, BIN_LN,    LN_OPTS, NULL),
!     BUILTIN("mkdir", 0, bin_mkdir, 1, -1, 0,         "pm",    NULL),
!     BUILTIN("mv",    0, bin_ln,    2, -1, BIN_MV,    "fi",    NULL),
!     BUILTIN("rm",    0, bin_rm,    1, -1, 0,         "dfirs", NULL),
!     BUILTIN("rmdir", 0, bin_rmdir, 1, -1, 0,         NULL,    NULL),
!     BUILTIN("sync",  0, bin_sync,  0,  0, 0,         NULL,    NULL),
  };
  
  /**/
diff -cr ../zsh-/Src/Modules/files.mdd ./Src/Modules/files.mdd
*** ../zsh-/Src/Modules/files.mdd	Sun Nov 28 17:42:28 1999
--- ./Src/Modules/files.mdd	Thu Dec  9 12:33:44 1999
***************
*** 1,3 ****
! autobins="ln mkdir mv rm rmdir sync"
  
  objects="files.o"
--- 1,3 ----
! autobins="chgrp chown ln mkdir mv rm rmdir sync"
  
  objects="files.o"
diff -cr ../zsh-/Src/system.h ./Src/system.h
*** ../zsh-/Src/system.h	Sun Nov 28 17:42:28 1999
--- ./Src/system.h	Thu Dec  9 15:19:03 1999
***************
*** 583,588 ****
--- 583,592 ----
  # define R_OK 4
  #endif
  
+ #ifndef HAVE_LCHOWN
+ # define lchown chown
+ #endif
+ 
  #ifndef HAVE_MEMCPY
  # define memcpy memmove
  #endif
diff -cr ../zsh-/configure.in ./configure.in
*** ../zsh-/configure.in	Wed Dec  8 19:58:26 1999
--- ./configure.in	Thu Dec  9 15:18:05 1999
***************
*** 784,790 ****
  dnl need to integrate this function
  dnl AC_FUNC_STRFTIME
  
! AC_CHECK_FUNCS(memcpy memmove \
                strftime waitpid select poll tcsetpgrp tcgetattr strstr lstat \
                getlogin setpgid gettimeofday gethostname mkfifo wait3 difftime \
                sigblock sigsetmask sigrelse sighold killpg sigaction getrlimit \
--- 784,790 ----
  dnl need to integrate this function
  dnl AC_FUNC_STRFTIME
  
! AC_CHECK_FUNCS(lchown memcpy memmove \
                strftime waitpid select poll tcsetpgrp tcgetattr strstr lstat \
                getlogin setpgid gettimeofday gethostname mkfifo wait3 difftime \
                sigblock sigsetmask sigrelse sighold killpg sigaction getrlimit \
END


^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~1999-12-13 12:10 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
1999-12-09 16:02 PATCH: chown and chgrp in files module zefram
1999-12-09 17:20 ` James Kirkpatrick
1999-12-09 17:41   ` Zefram
1999-12-09 18:19     ` James Kirkpatrick
1999-12-09 23:40     ` James Antill
1999-12-13 12:10       ` Zefram
1999-12-10 21:43     ` Clint Adams

Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/zsh/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).