1/*
2 * files.c - file operation builtins
3 *
4 * This file is part of zsh, the Z shell.
5 *
6 * Copyright (c) 1996-1997 Andrew Main
7 * All rights reserved.
8 *
9 * Permission is hereby granted, without written agreement and without
10 * license or royalty fees, to use, copy, modify, and distribute this
11 * software and to distribute modified versions of this software for any
12 * purpose, provided that the above copyright notice and the following
13 * two paragraphs appear in all copies of this software.
14 *
15 * In no event shall Andrew Main or the Zsh Development Group be liable
16 * to any party for direct, indirect, special, incidental, or consequential
17 * damages arising out of the use of this software and its documentation,
18 * even if Andrew Main and the Zsh Development Group have been advised of
19 * the possibility of such damage.
20 *
21 * Andrew Main and the Zsh Development Group specifically disclaim any
22 * warranties, including, but not limited to, the implied warranties of
23 * merchantability and fitness for a particular purpose.  The software
24 * provided hereunder is on an "as is" basis, and Andrew Main and the
25 * Zsh Development Group have no obligation to provide maintenance,
26 * support, updates, enhancements, or modifications.
27 *
28 */
29
30#include "files.mdh"
31
32typedef int (*MoveFunc) _((char const *, char const *));
33typedef int (*RecurseFunc) _((char *, char *, struct stat const *, void *));
34
35#ifndef STDC_HEADERS
36extern int link _((const char *, const char *));
37extern int symlink _((const char *, const char *));
38extern int rename _((const char *, const char *));
39#endif
40
41struct recursivecmd;
42
43#include "files.pro"
44
45/**/
46static int
47ask(void)
48{
49    int a = getchar(), c;
50    for(c = a; c != EOF && c != '\n'; )
51	c = getchar();
52    return a == 'y' || a == 'Y';
53}
54
55/* sync builtin */
56
57/**/
58static int
59bin_sync(UNUSED(char *nam), UNUSED(char **args), UNUSED(Options ops), UNUSED(int func))
60{
61    sync();
62    return 0;
63}
64
65/* mkdir builtin */
66
67/**/
68static int
69bin_mkdir(char *nam, char **args, Options ops, UNUSED(int func))
70{
71    mode_t oumask = umask(0);
72    mode_t mode = 0777 & ~oumask;
73    int err = 0;
74
75    umask(oumask);
76    if(OPT_ISSET(ops,'m')) {
77	char *str = OPT_ARG(ops,'m'), *ptr;
78
79	mode = zstrtol(str, &ptr, 8);
80	if(!*str || *ptr) {
81	    zwarnnam(nam, "invalid mode `%s'", str);
82	    return 1;
83	}
84    }
85    for(; *args; args++) {
86	char *ptr = strchr(*args, 0);
87
88	while(ptr > *args + (**args == '/') && *--ptr == '/')
89	    *ptr = 0;
90	if(OPT_ISSET(ops,'p')) {
91	    char *ptr = *args;
92
93	    for(;;) {
94		while(*ptr == '/')
95		    ptr++;
96		while(*ptr && *ptr != '/')
97		    ptr++;
98		if(!*ptr) {
99		    err |= domkdir(nam, *args, mode, 1);
100		    break;
101		} else {
102		    int e;
103
104		    *ptr = 0;
105		    e = domkdir(nam, *args, mode | 0300, 1);
106		    if(e) {
107			err = 1;
108			break;
109		    }
110		    *ptr = '/';
111		}
112	    }
113	} else
114	    err |= domkdir(nam, *args, mode, 0);
115    }
116    return err;
117}
118
119/**/
120static int
121domkdir(char *nam, char *path, mode_t mode, int p)
122{
123    int err;
124    mode_t oumask;
125    char const *rpath = unmeta(path);
126
127    if(p) {
128	struct stat st;
129
130	if(!stat(rpath, &st) && S_ISDIR(st.st_mode))
131	    return 0;
132    }
133    oumask = umask(0);
134    err = mkdir(path, mode) ? errno : 0;
135    umask(oumask);
136    if(!err)
137	return 0;
138    zwarnnam(nam, "cannot make directory `%s': %e", path, err);
139    return 1;
140}
141
142/* rmdir builtin */
143
144/**/
145static int
146bin_rmdir(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
147{
148    int err = 0;
149
150    for(; *args; args++) {
151	char *rpath = unmeta(*args);
152
153	if(!rpath) {
154	    zwarnnam(nam, "%s: %e", *args, ENAMETOOLONG);
155	    err = 1;
156	} else if(rmdir(rpath)) {
157	    zwarnnam(nam, "cannot remove directory `%s': %e", *args, errno);
158	    err = 1;
159	}
160    }
161    return err;
162}
163
164/* ln and mv builtins */
165
166#define BIN_LN 0
167#define BIN_MV 1
168
169#define MV_NODIRS		(1<<0)
170#define MV_FORCE		(1<<1)
171#define MV_INTERACTIVE		(1<<2)
172#define MV_ASKNW		(1<<3)
173#define MV_ATOMIC		(1<<4)
174#define MV_NOCHASETARGET	(1<<5)
175
176/*
177 * bin_ln actually does three related jobs: hard linking, symbolic
178 * linking, and renaming.  If called as mv it renames, otherwise
179 * it looks at the -s option.  If hard linking, it will refuse to
180 * attempt linking to a directory unless the -d option is given.
181 */
182
183/*
184 * Option compatibility: BSD systems settled on using mostly-standardised
185 * options across multiple commands to deal with symlinks; see, eg,
186 * symlink(7) on a *BSD system for details.  Per this, to work on a link
187 * directly we use "-h" and "ln -hsf" will not follow the target if it
188 * points to a directory.  GNU settled on using -n for ln(1), so we
189 * have "ln -nsf".  We handle them both.
190 *
191 * Logic compared against that of FreeBSD's ln.c, compatible license.
192 */
193
194/**/
195static int
196bin_ln(char *nam, char **args, Options ops, int func)
197{
198    MoveFunc movefn;
199    int flags, have_dir, err = 0;
200    char **a, *ptr, *rp, *buf;
201    struct stat st;
202    size_t blen;
203
204
205    if(func == BIN_MV) {
206	movefn = (MoveFunc) rename;
207	flags = OPT_ISSET(ops,'f') ? 0 : MV_ASKNW;
208	flags |= MV_ATOMIC;
209    } else {
210	flags = OPT_ISSET(ops,'f') ? MV_FORCE : 0;
211#ifdef HAVE_LSTAT
212	if(OPT_ISSET(ops,'h') || OPT_ISSET(ops,'n'))
213	    flags |= MV_NOCHASETARGET;
214	if(OPT_ISSET(ops,'s'))
215	    movefn = (MoveFunc) symlink;
216	else
217#endif
218	{
219	    movefn = (MoveFunc) link;
220	    if(!OPT_ISSET(ops,'d'))
221		flags |= MV_NODIRS;
222	}
223    }
224    if(OPT_ISSET(ops,'i') && !OPT_ISSET(ops,'f'))
225	flags |= MV_INTERACTIVE;
226    for(a = args; a[1]; a++) ;
227    if(a != args) {
228	rp = unmeta(*a);
229	if(rp && !stat(rp, &st) && S_ISDIR(st.st_mode)) {
230	    have_dir = 1;
231	    if((flags & MV_NOCHASETARGET)
232	      && !lstat(rp, &st) && S_ISLNK(st.st_mode)) {
233		/*
234		 * So we have "ln -h" with the target being a symlink pointing
235		 * to a directory; if there are multiple sources but the target
236		 * is a symlink, then it's an error as we're not following
237		 * symlinks; if OTOH there's just one source, then we need to
238		 * either fail EEXIST or if "-f" given then remove the target.
239		 */
240		if(a > args+1) {
241		    errno = ENOTDIR;
242		    zwarnnam(nam, "%s: %e", *a, errno);
243		    return 1;
244		}
245		if(flags & MV_FORCE) {
246		    unlink(rp);
247		    have_dir = 0;
248		} else {
249		    errno = EEXIST;
250		    zwarnnam(nam, "%s: %e", *a, errno);
251		    return 1;
252		}
253	    }
254	    /* Normal case, target is a directory, chase into it */
255	    if (have_dir)
256		goto havedir;
257	}
258    }
259    if(a > args+1) {
260	zwarnnam(nam, "last of many arguments must be a directory");
261	return 1;
262    }
263    if(!args[1]) {
264	ptr = strrchr(args[0], '/');
265	if(ptr)
266	    args[1] = ptr+1;
267	else
268	    args[1] = args[0];
269    }
270    return domove(nam, movefn, args[0], args[1], flags);
271 havedir:
272    buf = ztrdup(*a);
273    *a = NULL;
274    buf = appstr(buf, "/");
275    blen = strlen(buf);
276    for(; *args; args++) {
277
278	ptr = strrchr(*args, '/');
279	if(ptr)
280	    ptr++;
281	else
282	    ptr = *args;
283
284	buf[blen] = 0;
285	buf = appstr(buf, ptr);
286	err |= domove(nam, movefn, *args, buf, flags);
287    }
288    zsfree(buf);
289    return err;
290}
291
292/**/
293static int
294domove(char *nam, MoveFunc movefn, char *p, char *q, int flags)
295{
296    struct stat st;
297    char *pbuf, *qbuf;
298
299    pbuf = ztrdup(unmeta(p));
300    qbuf = unmeta(q);
301    if(flags & MV_NODIRS) {
302	errno = EISDIR;
303	if(lstat(pbuf, &st) || S_ISDIR(st.st_mode)) {
304	    zwarnnam(nam, "%s: %e", p, errno);
305	    zsfree(pbuf);
306	    return 1;
307	}
308    }
309    if(!lstat(qbuf, &st)) {
310	int doit = flags & MV_FORCE;
311	if(S_ISDIR(st.st_mode)) {
312	    zwarnnam(nam, "%s: cannot overwrite directory", q);
313	    zsfree(pbuf);
314	    return 1;
315	} else if(flags & MV_INTERACTIVE) {
316	    nicezputs(nam, stderr);
317	    fputs(": replace `", stderr);
318	    nicezputs(q, stderr);
319	    fputs("'? ", stderr);
320	    fflush(stderr);
321	    if(!ask()) {
322		zsfree(pbuf);
323		return 0;
324	    }
325	    doit = 1;
326	} else if((flags & MV_ASKNW) &&
327		!S_ISLNK(st.st_mode) &&
328		access(qbuf, W_OK)) {
329	    nicezputs(nam, stderr);
330	    fputs(": replace `", stderr);
331	    nicezputs(q, stderr);
332	    fprintf(stderr, "', overriding mode %04o? ",
333		mode_to_octal(st.st_mode));
334	    fflush(stderr);
335	    if(!ask()) {
336		zsfree(pbuf);
337		return 0;
338	    }
339	    doit = 1;
340	}
341	if(doit && !(flags & MV_ATOMIC))
342	    unlink(qbuf);
343    }
344    if(movefn(pbuf, qbuf)) {
345	zwarnnam(nam, "%s: %e", p, errno);
346	zsfree(pbuf);
347	return 1;
348    }
349    zsfree(pbuf);
350    return 0;
351}
352
353/* general recursion */
354
355struct recursivecmd {
356    char *nam;
357    int opt_noerr;
358    int opt_recurse;
359    int opt_safe;
360    RecurseFunc dirpre_func;
361    RecurseFunc dirpost_func;
362    RecurseFunc leaf_func;
363    void *magic;
364};
365
366/**/
367static int
368recursivecmd(char *nam, int opt_noerr, int opt_recurse, int opt_safe,
369    char **args, RecurseFunc dirpre_func, RecurseFunc dirpost_func,
370    RecurseFunc leaf_func, void *magic)
371{
372    int err = 0, len;
373    char *rp, *s;
374    struct dirsav ds;
375    struct recursivecmd reccmd;
376
377    reccmd.nam = nam;
378    reccmd.opt_noerr = opt_noerr;
379    reccmd.opt_recurse = opt_recurse;
380    reccmd.opt_safe = opt_safe;
381    reccmd.dirpre_func = dirpre_func;
382    reccmd.dirpost_func = dirpost_func;
383    reccmd.leaf_func = leaf_func;
384    reccmd.magic = magic;
385    init_dirsav(&ds);
386    if (opt_recurse || opt_safe) {
387	if ((ds.dirfd = open(".", O_RDONLY|O_NOCTTY)) < 0 &&
388	    zgetdir(&ds) && *ds.dirname != '/')
389	    ds.dirfd = open("..", O_RDONLY|O_NOCTTY);
390    }
391    for(; !errflag && !(err & 2) && *args; args++) {
392	rp = ztrdup(*args);
393	unmetafy(rp, &len);
394	if (opt_safe) {
395	    s = strrchr(rp, '/');
396	    if (s && !s[1]) {
397		while (*s == '/' && s > rp)
398		    *s-- = '\0';
399		while (*s != '/' && s > rp)
400		    s--;
401	    }
402	    if (s && s[1]) {
403		int e;
404
405		*s = '\0';
406		e = lchdir(s > rp ? rp : "/", &ds, 1);
407		err |= -e;
408		if (!e) {
409		    struct dirsav d;
410
411		    d.ino = d.dev = 0;
412		    d.dirname = NULL;
413		    d.dirfd = d.level = -1;
414		    err |= recursivecmd_doone(&reccmd, *args, s + 1, &d, 0);
415		    zsfree(d.dirname);
416		    if (restoredir(&ds))
417			err |= 2;
418		} else if(!opt_noerr)
419		    zwarnnam(nam, "%s: %e", *args, errno);
420	    } else
421		err |= recursivecmd_doone(&reccmd, *args, rp, &ds, 0);
422	} else
423	    err |= recursivecmd_doone(&reccmd, *args, rp, &ds, 1);
424	zfree(rp, len + 1);
425    }
426    if ((err & 2) && ds.dirfd >= 0 && restoredir(&ds) && zchdir(pwd)) {
427	zsfree(pwd);
428	pwd = ztrdup("/");
429	if (chdir(pwd) < 0)
430	    zwarn("failed to chdir(%s): %e", pwd, errno);
431    }
432    if (ds.dirfd >= 0)
433	close(ds.dirfd);
434    zsfree(ds.dirname);
435    return !!err;
436}
437
438/**/
439static int
440recursivecmd_doone(struct recursivecmd const *reccmd,
441    char *arg, char *rp, struct dirsav *ds, int first)
442{
443    struct stat st, *sp = NULL;
444
445    if(reccmd->opt_recurse && !lstat(rp, &st)) {
446	if(S_ISDIR(st.st_mode))
447	    return recursivecmd_dorec(reccmd, arg, rp, &st, ds, first);
448	sp = &st;
449    }
450    return reccmd->leaf_func(arg, rp, sp, reccmd->magic);
451}
452
453/**/
454static int
455recursivecmd_dorec(struct recursivecmd const *reccmd,
456    char *arg, char *rp, struct stat const *sp, struct dirsav *ds, int first)
457{
458    char *fn;
459    DIR *d;
460    int err, err1;
461    struct dirsav dsav;
462    char *files = NULL;
463    int fileslen = 0;
464
465    err1 = reccmd->dirpre_func(arg, rp, sp, reccmd->magic);
466    if(err1 & 2)
467	return 2;
468
469    err = -lchdir(rp, ds, !first);
470    if (err) {
471	if(!reccmd->opt_noerr)
472	    zwarnnam(reccmd->nam, "%s: %e", arg, errno);
473	return err;
474    }
475    err = err1;
476
477    init_dirsav(&dsav);
478    d = opendir(".");
479    if(!d) {
480	if(!reccmd->opt_noerr)
481	    zwarnnam(reccmd->nam, "%s: %e", arg, errno);
482	err = 1;
483    } else {
484	int arglen = strlen(arg) + 1;
485
486	while (!errflag && (fn = zreaddir(d, 1))) {
487	    int l = strlen(fn) + 1;
488	    files = hrealloc(files, fileslen, fileslen + l);
489	    strcpy(files + fileslen, fn);
490	    fileslen += l;
491	}
492	closedir(d);
493	for (fn = files; !errflag && !(err & 2) && fn < files + fileslen;) {
494	    int l = strlen(fn) + 1;
495	    VARARR(char, narg, arglen + l);
496
497	    strcpy(narg,arg);
498	    narg[arglen-1] = '/';
499	    strcpy(narg + arglen, fn);
500	    unmetafy(fn, NULL);
501	    err |= recursivecmd_doone(reccmd, narg, fn, &dsav, 0);
502	    fn += l;
503	}
504	hrealloc(files, fileslen, 0);
505    }
506    zsfree(dsav.dirname);
507    if (err & 2)
508	return 2;
509    if (restoredir(ds)) {
510	if(!reccmd->opt_noerr)
511	    zwarnnam(reccmd->nam, "failed to return to previous directory: %e",
512		     errno);
513	return 2;
514    }
515    return err | reccmd->dirpost_func(arg, rp, sp, reccmd->magic);
516}
517
518/**/
519static int
520recurse_donothing(UNUSED(char *arg), UNUSED(char *rp), UNUSED(struct stat const *sp), UNUSED(void *magic))
521{
522    return 0;
523}
524
525/* rm builtin */
526
527struct rmmagic {
528    char *nam;
529    int opt_force;
530    int opt_interact;
531    int opt_unlinkdir;
532};
533
534/**/
535static int
536rm_leaf(char *arg, char *rp, struct stat const *sp, void *magic)
537{
538    struct rmmagic *rmm = magic;
539    struct stat st;
540
541    if(!rmm->opt_unlinkdir || !rmm->opt_force) {
542	if(!sp) {
543	    if(!lstat(rp, &st))
544		sp = &st;
545	}
546	if(sp) {
547	    if(!rmm->opt_unlinkdir && S_ISDIR(sp->st_mode)) {
548		if(rmm->opt_force)
549		    return 0;
550		zwarnnam(rmm->nam, "%s: %e", arg, EISDIR);
551		return 1;
552	    }
553	    if(rmm->opt_interact) {
554		nicezputs(rmm->nam, stderr);
555		fputs(": remove `", stderr);
556		nicezputs(arg, stderr);
557		fputs("'? ", stderr);
558		fflush(stderr);
559		if(!ask())
560		    return 0;
561	    } else if(!rmm->opt_force &&
562		    !S_ISLNK(sp->st_mode) &&
563		    access(rp, W_OK)) {
564		nicezputs(rmm->nam, stderr);
565		fputs(": remove `", stderr);
566		nicezputs(arg, stderr);
567		fprintf(stderr, "', overriding mode %04o? ",
568		    mode_to_octal(sp->st_mode));
569		fflush(stderr);
570		if(!ask())
571		    return 0;
572	    }
573	}
574    }
575    if(unlink(rp) && !rmm->opt_force) {
576	zwarnnam(rmm->nam, "%s: %e", arg, errno);
577	return 1;
578    }
579    return 0;
580}
581
582/**/
583static int
584rm_dirpost(char *arg, char *rp, UNUSED(struct stat const *sp), void *magic)
585{
586    struct rmmagic *rmm = magic;
587
588    if(rmm->opt_interact) {
589	nicezputs(rmm->nam, stderr);
590	fputs(": remove `", stderr);
591	nicezputs(arg, stderr);
592	fputs("'? ", stderr);
593	fflush(stderr);
594	if(!ask())
595	    return 0;
596    }
597    if(rmdir(rp) && !rmm->opt_force) {
598	zwarnnam(rmm->nam, "%s: %e", arg, errno);
599	return 1;
600    }
601    return 0;
602}
603
604/**/
605static int
606bin_rm(char *nam, char **args, Options ops, UNUSED(int func))
607{
608    struct rmmagic rmm;
609    int err;
610
611    rmm.nam = nam;
612    rmm.opt_force = OPT_ISSET(ops,'f');
613    rmm.opt_interact = OPT_ISSET(ops,'i') && !OPT_ISSET(ops,'f');
614    rmm.opt_unlinkdir = OPT_ISSET(ops,'d');
615    err = recursivecmd(nam, OPT_ISSET(ops,'f'),
616		       OPT_ISSET(ops,'r') && !OPT_ISSET(ops,'d'),
617		       OPT_ISSET(ops,'s'),
618	args, recurse_donothing, rm_dirpost, rm_leaf, &rmm);
619    return OPT_ISSET(ops,'f') ? 0 : err;
620}
621
622/* chown builtin */
623
624struct chownmagic {
625    char *nam;
626    uid_t uid;
627    gid_t gid;
628};
629
630/**/
631static int
632chown_dochown(char *arg, char *rp, UNUSED(struct stat const *sp), void *magic)
633{
634    struct chownmagic *chm = magic;
635
636    if(chown(rp, chm->uid, chm->gid)) {
637	zwarnnam(chm->nam, "%s: %e", arg, errno);
638	return 1;
639    }
640    return 0;
641}
642
643/**/
644static int
645chown_dolchown(char *arg, char *rp, UNUSED(struct stat const *sp), void *magic)
646{
647    struct chownmagic *chm = magic;
648
649    if(lchown(rp, chm->uid, chm->gid)) {
650	zwarnnam(chm->nam, "%s: %e", arg, errno);
651	return 1;
652    }
653    return 0;
654}
655
656
657/**/
658static unsigned long getnumeric(char *p, int *errp)
659{
660    unsigned long ret;
661
662    if (!idigit(*p)) {
663	*errp = 1;
664	return 0;
665    }
666    ret = strtoul(p, &p, 10);
667    *errp = !!*p;
668    return ret;
669}
670
671enum { BIN_CHOWN, BIN_CHGRP };
672
673/**/
674static int
675bin_chown(char *nam, char **args, Options ops, int func)
676{
677    struct chownmagic chm;
678    char *uspec = ztrdup(*args), *p = uspec;
679    char *end;
680
681    chm.nam = nam;
682    if(func == BIN_CHGRP) {
683	chm.uid = -1;
684	goto dogroup;
685    }
686    end = strchr(uspec, ':');
687    if(!end)
688	end = strchr(uspec, '.');
689    if(end == uspec) {
690	chm.uid = -1;
691	p++;
692	goto dogroup;
693    } else {
694	struct passwd *pwd;
695	if(end)
696	    *end = 0;
697	pwd = getpwnam(p);
698	if(pwd)
699	    chm.uid = pwd->pw_uid;
700	else {
701	    int err;
702	    chm.uid = getnumeric(p, &err);
703	    if(err) {
704		zwarnnam(nam, "%s: no such user", p);
705		free(uspec);
706		return 1;
707	    }
708	}
709	if(end) {
710	    p = end+1;
711	    if(!*p) {
712		if(!pwd && !(pwd = getpwuid(chm.uid))) {
713		    zwarnnam(nam, "%s: no such user", uspec);
714		    free(uspec);
715		    return 1;
716		}
717		chm.gid = pwd->pw_gid;
718	    } else if(p[0] == ':' && !p[1]) {
719		chm.gid = -1;
720	    } else {
721		struct group *grp;
722		dogroup:
723		grp = getgrnam(p);
724		if(grp)
725		    chm.gid = grp->gr_gid;
726		else {
727		    int err;
728		    chm.gid = getnumeric(p, &err);
729		    if(err) {
730			zwarnnam(nam, "%s: no such group", p);
731			free(uspec);
732			return 1;
733		    }
734		}
735	    }
736	 } else
737	    chm.gid = -1;
738    }
739    free(uspec);
740    return recursivecmd(nam, 0, OPT_ISSET(ops,'R'), OPT_ISSET(ops,'s'),
741	args + 1, OPT_ISSET(ops, 'h') ? chown_dolchown : chown_dochown, recurse_donothing,
742	OPT_ISSET(ops, 'h') ? chown_dolchown : chown_dochown, &chm);
743}
744
745/* module paraphernalia */
746
747#ifdef HAVE_LSTAT
748# define LN_OPTS "dfhins"
749#else
750# define LN_OPTS "dfi"
751#endif
752
753static struct builtin bintab[] = {
754    /* The names which overlap commands without necessarily being
755     * fully compatible. */
756    BUILTIN("chgrp", 0, bin_chown, 2, -1, BIN_CHGRP, "hRs",    NULL),
757    BUILTIN("chown", 0, bin_chown, 2, -1, BIN_CHOWN, "hRs",    NULL),
758    BUILTIN("ln",    0, bin_ln,    1, -1, BIN_LN,    LN_OPTS, NULL),
759    BUILTIN("mkdir", 0, bin_mkdir, 1, -1, 0,         "pm:",   NULL),
760    BUILTIN("mv",    0, bin_ln,    2, -1, BIN_MV,    "fi",    NULL),
761    BUILTIN("rm",    0, bin_rm,    1, -1, 0,         "dfirs", NULL),
762    BUILTIN("rmdir", 0, bin_rmdir, 1, -1, 0,         NULL,    NULL),
763    BUILTIN("sync",  0, bin_sync,  0,  0, 0,         NULL,    NULL),
764    /* The "safe" zsh-only names */
765    BUILTIN("zf_chgrp", 0, bin_chown, 2, -1, BIN_CHGRP, "hRs",    NULL),
766    BUILTIN("zf_chown", 0, bin_chown, 2, -1, BIN_CHOWN, "hRs",    NULL),
767    BUILTIN("zf_ln",    0, bin_ln,    1, -1, BIN_LN,    LN_OPTS, NULL),
768    BUILTIN("zf_mkdir", 0, bin_mkdir, 1, -1, 0,         "pm:",   NULL),
769    BUILTIN("zf_mv",    0, bin_ln,    2, -1, BIN_MV,    "fi",    NULL),
770    BUILTIN("zf_rm",    0, bin_rm,    1, -1, 0,         "dfirs", NULL),
771    BUILTIN("zf_rmdir", 0, bin_rmdir, 1, -1, 0,         NULL,    NULL),
772    BUILTIN("zf_sync",  0, bin_sync,  0,  0, 0,         NULL,    NULL),
773
774};
775
776static struct features module_features = {
777    bintab, sizeof(bintab)/sizeof(*bintab),
778    NULL, 0,
779    NULL, 0,
780    NULL, 0,
781    0
782};
783
784/**/
785int
786setup_(UNUSED(Module m))
787{
788    return 0;
789}
790
791/**/
792int
793features_(Module m, char ***features)
794{
795    *features = featuresarray(m, &module_features);
796    return 0;
797}
798
799/**/
800int
801enables_(Module m, int **enables)
802{
803    return handlefeatures(m, &module_features, enables);
804}
805
806/**/
807int
808boot_(Module m)
809{
810    return 0;
811}
812
813/**/
814int
815cleanup_(Module m)
816{
817    return setfeatureenables(m, &module_features, NULL);
818}
819
820/**/
821int
822finish_(UNUSED(Module m))
823{
824    return 0;
825}
826