1
0
Fork 0
mirror of git://git.code.sf.net/p/zsh/code synced 2024-05-04 14:46:20 +02:00
zsh/Src/Modules/files.c
Felipe Contreras df48cc8404 47785: remove deprecated autoconf functions
STDC_HEADERS and TIME_WITH_SYS_TIME are deprecated.
2021-04-09 21:45:11 +02:00

872 lines
19 KiB
C

/*
* files.c - file operation builtins
*
* This file is part of zsh, the Z shell.
*
* Copyright (c) 1996-1997 Andrew Main
* All rights reserved.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and to distribute modified versions of this software for any
* purpose, provided that the above copyright notice and the following
* two paragraphs appear in all copies of this software.
*
* In no event shall Andrew Main or the Zsh Development Group be liable
* to any party for direct, indirect, special, incidental, or consequential
* damages arising out of the use of this software and its documentation,
* even if Andrew Main and the Zsh Development Group have been advised of
* the possibility of such damage.
*
* Andrew Main and the Zsh Development Group specifically disclaim any
* warranties, including, but not limited to, the implied warranties of
* merchantability and fitness for a particular purpose. The software
* provided hereunder is on an "as is" basis, and Andrew Main and the
* Zsh Development Group have no obligation to provide maintenance,
* support, updates, enhancements, or modifications.
*
*/
#include "files.mdh"
typedef int (*MoveFunc) _((char const *, char const *));
typedef int (*RecurseFunc) _((char *, char *, struct stat const *, void *));
struct recursivecmd;
#include "files.pro"
/**/
static int
ask(void)
{
int a = getchar(), c;
for(c = a; c != EOF && c != '\n'; )
c = getchar();
return a == 'y' || a == 'Y';
}
/* sync builtin */
/**/
static int
bin_sync(UNUSED(char *nam), UNUSED(char **args), UNUSED(Options ops), UNUSED(int func))
{
sync();
return 0;
}
/* mkdir builtin */
/**/
static int
bin_mkdir(char *nam, char **args, Options ops, UNUSED(int func))
{
mode_t oumask = umask(0);
mode_t mode = 0777 & ~oumask;
int err = 0;
umask(oumask);
if(OPT_ISSET(ops,'m')) {
char *str = OPT_ARG(ops,'m'), *ptr;
mode = zstrtol(str, &ptr, 8);
if(!*str || *ptr) {
zwarnnam(nam, "invalid mode `%s'", str);
return 1;
}
}
for(; *args; args++) {
char *ptr = strchr(*args, 0);
while(ptr > *args + (**args == '/') && *--ptr == '/')
*ptr = 0;
if(OPT_ISSET(ops,'p')) {
char *ptr = *args;
for(;;) {
while(*ptr == '/')
ptr++;
while(*ptr && *ptr != '/')
ptr++;
if(!*ptr) {
err |= domkdir(nam, *args, mode, 1);
break;
} else {
int e;
*ptr = 0;
e = domkdir(nam, *args, mode | 0300, 1);
if(e) {
err = 1;
break;
}
*ptr = '/';
}
}
} else
err |= domkdir(nam, *args, mode, 0);
}
return err;
}
/**/
static int
domkdir(char *nam, char *path, mode_t mode, int p)
{
int err;
mode_t oumask;
struct stat st;
int n = 8;
char const *rpath = unmeta(path);
while(n-- > 0) {
oumask = umask(0);
err = mkdir(rpath, mode) ? errno : 0;
umask(oumask);
if (!err)
return 0;
if(!p || err != EEXIST)
break;
if(stat(rpath, &st)) {
if(errno == ENOENT)
continue;
err = errno;
break;
}
if(S_ISDIR(st.st_mode))
return 0;
break;
}
zwarnnam(nam, "cannot make directory `%s': %e", path, err);
return 1;
}
/* rmdir builtin */
/**/
static int
bin_rmdir(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
{
int err = 0;
for(; *args; args++) {
char *rpath = unmeta(*args);
if(!rpath) {
zwarnnam(nam, "%s: %e", *args, ENAMETOOLONG);
err = 1;
} else if(rmdir(rpath)) {
zwarnnam(nam, "cannot remove directory `%s': %e", *args, errno);
err = 1;
}
}
return err;
}
/* ln and mv builtins */
#define BIN_LN 0
#define BIN_MV 1
#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 movefn;
int flags, have_dir, err = 0;
char **a, *ptr, *rp, *buf;
struct stat st;
size_t blen;
if(func == BIN_MV) {
movefn = (MoveFunc) rename;
flags = OPT_ISSET(ops,'f') ? 0 : MV_ASKNW;
flags |= MV_ATOMIC;
} 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'))
movefn = (MoveFunc) symlink;
else
#endif
{
movefn = (MoveFunc) link;
if(!OPT_ISSET(ops,'d'))
flags |= MV_NODIRS;
}
}
if(OPT_ISSET(ops,'i') && !OPT_ISSET(ops,'f'))
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)) {
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");
return 1;
}
if(!args[1]) {
ptr = strrchr(args[0], '/');
if(ptr)
args[1] = ptr+1;
else
args[1] = args[0];
}
return domove(nam, movefn, args[0], args[1], flags);
havedir:
buf = ztrdup(*a);
*a = NULL;
buf = appstr(buf, "/");
blen = strlen(buf);
for(; *args; args++) {
ptr = strrchr(*args, '/');
if(ptr)
ptr++;
else
ptr = *args;
buf[blen] = 0;
buf = appstr(buf, ptr);
err |= domove(nam, movefn, *args, buf, flags);
}
zsfree(buf);
return err;
}
/**/
static int
domove(char *nam, MoveFunc movefn, char *p, char *q, int flags)
{
struct stat st;
char *pbuf, *qbuf;
pbuf = ztrdup(unmeta(p));
qbuf = unmeta(q);
if(flags & MV_NODIRS) {
errno = EISDIR;
if(lstat(pbuf, &st) || S_ISDIR(st.st_mode)) {
zwarnnam(nam, "%s: %e", p, errno);
zsfree(pbuf);
return 1;
}
}
if(!lstat(qbuf, &st)) {
int doit = flags & MV_FORCE;
if(S_ISDIR(st.st_mode)) {
zwarnnam(nam, "%s: cannot overwrite directory", q);
zsfree(pbuf);
return 1;
} else if(flags & MV_INTERACTIVE) {
nicezputs(nam, stderr);
fputs(": replace `", stderr);
nicezputs(q, stderr);
fputs("'? ", stderr);
fflush(stderr);
if(!ask()) {
zsfree(pbuf);
return 0;
}
doit = 1;
} else if((flags & MV_ASKNW) &&
!S_ISLNK(st.st_mode) &&
access(qbuf, W_OK)) {
nicezputs(nam, stderr);
fputs(": replace `", stderr);
nicezputs(q, stderr);
fprintf(stderr, "', overriding mode %04o? ",
mode_to_octal(st.st_mode));
fflush(stderr);
if(!ask()) {
zsfree(pbuf);
return 0;
}
doit = 1;
}
if(doit && !(flags & MV_ATOMIC))
unlink(qbuf);
}
if(movefn(pbuf, qbuf)) {
zwarnnam(nam, "%s: %e", p, errno);
zsfree(pbuf);
return 1;
}
zsfree(pbuf);
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;
init_dirsav(&ds);
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);
}
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)
*s-- = '\0';
while (*s != '/' && s > rp)
s--;
}
if (s && s[1]) {
int e;
*s = '\0';
e = lchdir(s > rp ? rp : "/", &ds, 1);
err |= -e;
if (!e) {
struct dirsav d;
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)) {
zsfree(pwd);
pwd = ztrdup("/");
if (chdir(pwd) < 0)
zwarn("failed to chdir(%s): %e", pwd, errno);
}
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;
init_dirsav(&dsav);
d = opendir(".");
if(!d) {
if(!reccmd->opt_noerr)
zwarnnam(reccmd->nam, "%s: %e", arg, errno);
err = 1;
} else {
int arglen = strlen(arg) + 1;
while (!errflag && (fn = zreaddir(d, 1))) {
int l = strlen(fn) + 1;
files = hrealloc(files, fileslen, fileslen + l);
strcpy(files + fileslen, fn);
fileslen += l;
}
closedir(d);
for (fn = files; !errflag && !(err & 2) && fn < files + fileslen;) {
int l = strlen(fn) + 1;
VARARR(char, narg, arglen + l);
strcpy(narg,arg);
narg[arglen-1] = '/';
strcpy(narg + arglen, fn);
unmetafy(fn, NULL);
err |= recursivecmd_doone(reccmd, narg, fn, &dsav, 0);
fn += l;
}
hrealloc(files, fileslen, 0);
}
zsfree(dsav.dirname);
if (err & 2)
return 2;
if (restoredir(ds)) {
if(!reccmd->opt_noerr)
zwarnnam(reccmd->nam, "failed to return to previous directory: %e",
errno);
return 2;
}
return err | reccmd->dirpost_func(arg, rp, sp, reccmd->magic);
}
/**/
static int
recurse_donothing(UNUSED(char *arg), UNUSED(char *rp), UNUSED(struct stat const *sp), UNUSED(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, UNUSED(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, Options ops, UNUSED(int func))
{
struct rmmagic rmm;
int err;
rmm.nam = nam;
rmm.opt_force = OPT_ISSET(ops,'f');
rmm.opt_interact = OPT_ISSET(ops,'i') && !OPT_ISSET(ops,'f');
rmm.opt_unlinkdir = OPT_ISSET(ops,'d');
err = recursivecmd(nam, OPT_ISSET(ops,'f'),
!OPT_ISSET(ops,'d') && (OPT_ISSET(ops,'R') ||
OPT_ISSET(ops,'r')),
OPT_ISSET(ops,'s'),
args, recurse_donothing, rm_dirpost, rm_leaf, &rmm);
return OPT_ISSET(ops,'f') ? 0 : err;
}
/* chmod builtin */
struct chmodmagic {
char *nam;
mode_t mode;
};
/**/
static int
chmod_dochmod(char *arg, char *rp, UNUSED(struct stat const *sp), void *magic)
{
struct chmodmagic *chm = magic;
if(chmod(rp, chm->mode)) {
zwarnnam(chm->nam, "%s: %e", arg, errno);
return 1;
}
return 0;
}
/**/
static int
bin_chmod(char *nam, char **args, Options ops, int func)
{
struct chmodmagic chm;
char *str = args[0], *ptr;
chm.nam = nam;
chm.mode = zstrtol(str, &ptr, 8);
if(!*str || *ptr) {
zwarnnam(nam, "invalid mode `%s'", str);
return 1;
}
return recursivecmd(nam, 0, OPT_ISSET(ops,'R'), OPT_ISSET(ops,'s'),
args + 1, chmod_dochmod, recurse_donothing, chmod_dochmod, &chm);
}
/* chown builtin */
struct chownmagic {
char *nam;
uid_t uid;
gid_t gid;
};
/**/
static int
chown_dochown(char *arg, char *rp, UNUSED(struct stat const *sp), void *magic)
{
struct chownmagic *chm = magic;
if(chown(rp, chm->uid, chm->gid)) {
zwarnnam(chm->nam, "%s: %e", arg, errno);
return 1;
}
return 0;
}
/**/
static int
chown_dolchown(char *arg, char *rp, UNUSED(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 (!idigit(*p)) {
*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, Options ops, int func)
{
struct chownmagic chm;
char *uspec = ztrdup(*args), *p = uspec;
char *end;
chm.nam = nam;
if(func == BIN_CHGRP) {
chm.uid = -1;
goto dogroup;
}
end = strchr(uspec, ':');
if(!end)
end = strchr(uspec, '.');
if(end == uspec) {
chm.uid = -1;
p++;
goto dogroup;
} else {
struct passwd *pwd;
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);
free(uspec);
return 1;
}
}
if(end) {
p = end+1;
if(!*p) {
if(!pwd && !(pwd = getpwuid(chm.uid))) {
zwarnnam(nam, "%s: no such user", uspec);
free(uspec);
return 1;
}
chm.gid = pwd->pw_gid;
} else if(p[0] == ':' && !p[1]) {
chm.gid = -1;
} 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);
free(uspec);
return 1;
}
}
}
} else
chm.gid = -1;
}
free(uspec);
return recursivecmd(nam, 0, OPT_ISSET(ops,'R'), OPT_ISSET(ops,'s'),
args + 1, OPT_ISSET(ops, 'h') ? chown_dolchown : chown_dochown, recurse_donothing,
OPT_ISSET(ops, 'h') ? chown_dolchown : chown_dochown, &chm);
}
/* module paraphernalia */
#ifdef HAVE_LSTAT
# 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("chmod", 0, bin_chmod, 2, -1, 0, "Rs", NULL),
BUILTIN("chown", 0, bin_chown, 2, -1, BIN_CHOWN, "hRs", 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, "dfiRrs", 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_chmod", 0, bin_chmod, 2, -1, 0, "Rs", 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, "dfiRrs", 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 = {
bintab, sizeof(bintab)/sizeof(*bintab),
NULL, 0,
NULL, 0,
NULL, 0,
0
};
/**/
int
setup_(UNUSED(Module m))
{
return 0;
}
/**/
int
features_(Module m, char ***features)
{
*features = featuresarray(m, &module_features);
return 0;
}
/**/
int
enables_(Module m, int **enables)
{
return handlefeatures(m, &module_features, enables);
}
/**/
int
boot_(UNUSED(Module m))
{
return 0;
}
/**/
int
cleanup_(Module m)
{
return setfeatureenables(m, &module_features, NULL);
}
/**/
int
finish_(UNUSED(Module m))
{
return 0;
}