about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog6
-rw-r--r--Doc/Zsh/mod_files.yo26
-rw-r--r--Src/Modules/files.c85
-rw-r--r--Src/Modules/files.mdd2
4 files changed, 100 insertions, 19 deletions
diff --git a/ChangeLog b/ChangeLog
index c1fc3664a..c3321dc52 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2008-05-08  Peter Stephenson  <pws@csr.com>
+
+	* 24972: Phil Pennock: Doc/Zsh/mod_files.yo, Src/Modules/files.c,
+	Src/Modules/files.mdd:  Add zf_* commands for zsh/files modules
+	plus a few extra options.
+
 2008-05-07  Peter Stephenson  <p.w.stephenson@ntlworld.com>
 
 	* 24962: Oliver: Functions/Prompts/prompt_oliver_setup:
diff --git a/Doc/Zsh/mod_files.yo b/Doc/Zsh/mod_files.yo
index 2bc83b5db..5dbdae7d2 100644
--- a/Doc/Zsh/mod_files.yo
+++ b/Doc/Zsh/mod_files.yo
@@ -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 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))(
+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 replaced by links.
 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) ...)(
diff --git a/Src/Modules/files.c b/Src/Modules/files.c
index db4d74de0..f22b23fb7 100644
--- a/Src/Modules/files.c
+++ b/Src/Modules/files.c
@@ -166,23 +166,37 @@ bin_rmdir(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 #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)
+#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.   */
+/*
+ * 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 ops, int func)
     } 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 ops, int func)
 	}
     }
     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, char *q, int flags)
 	    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, Options ops, int func)
 /* 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 = {
diff --git a/Src/Modules/files.mdd b/Src/Modules/files.mdd
index 43b9f985a..b8e0ff32f 100644
--- a/Src/Modules/files.mdd
+++ b/Src/Modules/files.mdd
@@ -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"