Index: Doc/Zsh/mod_files.yo =================================================================== RCS file: /home/cvsroot/zsh/Doc/Zsh/mod_files.yo,v retrieving revision 1.3 diff -p -u -r1.3 mod_files.yo --- Doc/Zsh/mod_files.yo 21 Aug 2007 22:59:49 -0000 1.3 +++ Doc/Zsh/mod_files.yo 8 May 2008 05:54:02 -0000 @@ -2,7 +2,17 @@ COMMENT(!MOD!zsh/files Some basic file manipulation commands as builtins. !MOD!) cindex(files, manipulating) -The tt(zsh/files) module makes some standard commands available as builtins: +The tt(zsh/files) module makes available some common commands for file +manipulation as builtins; these commands are probably not needed for +many normal situations but can be useful in emergency recovery +situations with constrained resources. The commands do not implement +all features now required by relevant standards committees. + +For all commands, a variant beginning tt(zf_) is also available and loaded +automatically. Using the features capability of zmodload will let you load +only those names you want. + +The commands loaded by default are: startitem() findex(chgrp) @@ -51,8 +61,8 @@ a deep directory tree can't end up recur 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))( +xitem(tt(ln) [ tt(-dfhins) ] var(filename) var(dest)) +item(tt(ln) [ tt(-dfhins) ] var(filename) ... var(dir))( Creates hard (or, with tt(-s), symbolic) links. In the first form, the specified var(dest)ination is created, as a link to the specified var(filename). In the second form, each of the var(filename)s is @@ -69,6 +79,16 @@ By default, existing files cannot be rep The tt(-i) option causes the user to be queried about replacing existing files. The tt(-f) option causes existing files to be silently deleted, without querying. tt(-f) takes precedence. + +The tt(-h) and tt(-n) options are identical and both exist for +compatibility; either one indicates that if the target is a symlink +then it should not be dereferenced. +Typically this is used in combination with tt(-sf) so that if an +existing link points to a directory then it will be removed, +instead of followed. +If this option is used with multiple filenames and the target +is a symbolic link pointing to a directory then the result is +an error. ) findex(mkdir) item(tt(mkdir) [ tt(-p) ] [ tt(-m) var(mode) ] var(dir) ...)( Index: Src/Modules/files.c =================================================================== RCS file: /home/cvsroot/zsh/Src/Modules/files.c,v retrieving revision 1.18 diff -p -u -r1.18 files.c --- Src/Modules/files.c 21 Aug 2007 22:59:49 -0000 1.18 +++ Src/Modules/files.c 8 May 2008 05:56:18 -0000 @@ -166,23 +166,37 @@ bin_rmdir(char *nam, char **args, UNUSED #define BIN_LN 0 #define BIN_MV 1 -#define MV_NODIRS (1<<0) -#define MV_FORCE (1<<1) -#define MV_INTER (1<<2) -#define MV_ASKNW (1<<3) -#define MV_ATOMIC (1<<4) - -/* bin_ln actually does three related jobs: hard linking, symbolic * - * linking, and renaming. If called as mv it renames, otherwise * - * it looks at the -s option. If hard linking, it will refuse to * - * attempt linking to a directory unless the -d option is given. */ +#define MV_NODIRS (1<<0) +#define MV_FORCE (1<<1) +#define MV_INTERACTIVE (1<<2) +#define MV_ASKNW (1<<3) +#define MV_ATOMIC (1<<4) +#define MV_NOCHASETARGET (1<<5) + +/* + * bin_ln actually does three related jobs: hard linking, symbolic + * linking, and renaming. If called as mv it renames, otherwise + * it looks at the -s option. If hard linking, it will refuse to + * attempt linking to a directory unless the -d option is given. + */ + +/* + * Option compatibility: BSD systems settled on using mostly-standardised + * options across multiple commands to deal with symlinks; see, eg, + * symlink(7) on a *BSD system for details. Per this, to work on a link + * directly we use "-h" and "ln -hsf" will not follow the target if it + * points to a directory. GNU settled on using -n for ln(1), so we + * have "ln -nsf". We handle them both. + * + * Logic compared against that of FreeBSD's ln.c, compatible license. + */ /**/ static int bin_ln(char *nam, char **args, Options ops, int func) { MoveFunc move; - int flags, err = 0; + int flags, have_dir, err = 0; char **a, *ptr, *rp, *buf; struct stat st; size_t blen; @@ -195,6 +209,8 @@ bin_ln(char *nam, char **args, Options o } else { flags = OPT_ISSET(ops,'f') ? MV_FORCE : 0; #ifdef HAVE_LSTAT + if(OPT_ISSET(ops,'h') || OPT_ISSET(ops,'n')) + flags |= MV_NOCHASETARGET; if(OPT_ISSET(ops,'s')) move = (MoveFunc) symlink; else @@ -206,12 +222,39 @@ bin_ln(char *nam, char **args, Options o } } if(OPT_ISSET(ops,'i') && !OPT_ISSET(ops,'f')) - flags |= MV_INTER; + flags |= MV_INTERACTIVE; for(a = args; a[1]; a++) ; if(a != args) { rp = unmeta(*a); - if(rp && !stat(rp, &st) && S_ISDIR(st.st_mode)) - goto havedir; + if(rp && !stat(rp, &st) && S_ISDIR(st.st_mode)) { + have_dir = 1; + if((flags & MV_NOCHASETARGET) + && !lstat(rp, &st) && S_ISLNK(st.st_mode)) { + /* + * So we have "ln -h" with the target being a symlink pointing + * to a directory; if there are multiple sources but the target + * is a symlink, then it's an error as we're not following + * symlinks; if OTOH there's just one source, then we need to + * either fail EEXIST or if "-f" given then remove the target. + */ + if(a > args+1) { + errno = ENOTDIR; + zwarnnam(nam, "%s: %e", *a, errno); + return 1; + } + if(flags & MV_FORCE) { + unlink(rp); + have_dir = 0; + } else { + errno = EEXIST; + zwarnnam(nam, "%s: %e", *a, errno); + return 1; + } + } + /* Normal case, target is a directory, chase into it */ + if (have_dir) + goto havedir; + } } if(a > args+1) { zwarnnam(nam, "last of many arguments must be a directory"); @@ -269,7 +312,7 @@ domove(char *nam, MoveFunc move, char *p zwarnnam(nam, "%s: cannot overwrite directory", q); zsfree(pbuf); return 1; - } else if(flags & MV_INTER) { + } else if(flags & MV_INTERACTIVE) { nicezputs(nam, stderr); fputs(": replace `", stderr); nicezputs(q, stderr); @@ -705,12 +748,14 @@ bin_chown(char *nam, char **args, Option /* module paraphernalia */ #ifdef HAVE_LSTAT -# define LN_OPTS "dfis" +# define LN_OPTS "dfhins" #else # define LN_OPTS "dfi" #endif static struct builtin bintab[] = { + /* The names which overlap commands without necessarily being + * fully compatible. */ BUILTIN("chgrp", 0, bin_chown, 2, -1, BIN_CHGRP, "hRs", NULL), BUILTIN("chown", 0, bin_chown, 2, -1, BIN_CHOWN, "hRs", NULL), BUILTIN("ln", 0, bin_ln, 1, -1, BIN_LN, LN_OPTS, NULL), @@ -719,6 +764,16 @@ static struct builtin bintab[] = { 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), + /* The "safe" zsh-only names */ + BUILTIN("zf_chgrp", 0, bin_chown, 2, -1, BIN_CHGRP, "hRs", NULL), + BUILTIN("zf_chown", 0, bin_chown, 2, -1, BIN_CHOWN, "hRs", NULL), + BUILTIN("zf_ln", 0, bin_ln, 1, -1, BIN_LN, LN_OPTS, NULL), + BUILTIN("zf_mkdir", 0, bin_mkdir, 1, -1, 0, "pm:", NULL), + BUILTIN("zf_mv", 0, bin_ln, 2, -1, BIN_MV, "fi", NULL), + BUILTIN("zf_rm", 0, bin_rm, 1, -1, 0, "dfirs", NULL), + BUILTIN("zf_rmdir", 0, bin_rmdir, 1, -1, 0, NULL, NULL), + BUILTIN("zf_sync", 0, bin_sync, 0, 0, 0, NULL, NULL), + }; static struct features module_features = { Index: Src/Modules/files.mdd =================================================================== RCS file: /home/cvsroot/zsh/Src/Modules/files.mdd,v retrieving revision 1.3 diff -p -u -r1.3 files.mdd --- Src/Modules/files.mdd 20 Jun 2007 20:59:17 -0000 1.3 +++ Src/Modules/files.mdd 8 May 2008 04:18:56 -0000 @@ -2,6 +2,6 @@ name=zsh/files link=dynamic load=no -autofeatures="b:chgrp b:chown b:ln b:mkdir b:mv b:rm b:rmdir b:sync" +autofeatures="b:chgrp b:chown b:ln b:mkdir b:mv b:rm b:rmdir b:sync b:zf_chgrp b:zf_chown b:zf_ln b:zf_mkdir b:zf_mv b:zf_rm b:zf_rmdir b:zf_sync" objects="files.o"