diff options
Diffstat (limited to 'Src')
-rw-r--r-- | Src/Modules/files.c | 85 | ||||
-rw-r--r-- | Src/Modules/files.mdd | 2 |
2 files changed, 71 insertions, 16 deletions
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" |