26763: fix problem on failed cd -s to relative path
[zsh.git] / Src / Modules / files.c
blob0b991c5569b78ff68c6480de974a7e427c6cb65a
1 /*
2 * files.c - file operation builtins
4 * This file is part of zsh, the Z shell.
6 * Copyright (c) 1996-1997 Andrew Main
7 * All rights reserved.
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.
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.
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.
30 #include "files.mdh"
32 typedef int (*MoveFunc) _((char const *, char const *));
33 typedef int (*RecurseFunc) _((char *, char *, struct stat const *, void *));
35 #ifndef STDC_HEADERS
36 extern int link _((const char *, const char *));
37 extern int symlink _((const char *, const char *));
38 extern int rename _((const char *, const char *));
39 #endif
41 struct recursivecmd;
43 #include "files.pro"
45 /**/
46 static int
47 ask(void)
49 int a = getchar(), c;
50 for(c = a; c != EOF && c != '\n'; )
51 c = getchar();
52 return a == 'y' || a == 'Y';
55 /* sync builtin */
57 /**/
58 static int
59 bin_sync(UNUSED(char *nam), UNUSED(char **args), UNUSED(Options ops), UNUSED(int func))
61 sync();
62 return 0;
65 /* mkdir builtin */
67 /**/
68 static int
69 bin_mkdir(char *nam, char **args, Options ops, UNUSED(int func))
71 mode_t oumask = umask(0);
72 mode_t mode = 0777 & ~oumask;
73 int err = 0;
75 umask(oumask);
76 if(OPT_ISSET(ops,'m')) {
77 char *str = OPT_ARG(ops,'m'), *ptr;
79 mode = zstrtol(str, &ptr, 8);
80 if(!*str || *ptr) {
81 zwarnnam(nam, "invalid mode `%s'", str);
82 return 1;
85 for(; *args; args++) {
86 char *ptr = strchr(*args, 0);
88 while(ptr > *args + (**args == '/') && *--ptr == '/')
89 *ptr = 0;
90 if(OPT_ISSET(ops,'p')) {
91 char *ptr = *args;
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;
104 *ptr = 0;
105 e = domkdir(nam, *args, mode | 0300, 1);
106 if(e) {
107 err = 1;
108 break;
110 *ptr = '/';
113 } else
114 err |= domkdir(nam, *args, mode, 0);
116 return err;
119 /**/
120 static int
121 domkdir(char *nam, char *path, mode_t mode, int p)
123 int err;
124 mode_t oumask;
125 char const *rpath = unmeta(path);
127 if(p) {
128 struct stat st;
130 if(!stat(rpath, &st) && S_ISDIR(st.st_mode))
131 return 0;
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;
142 /* rmdir builtin */
144 /**/
145 static int
146 bin_rmdir(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
148 int err = 0;
150 for(; *args; args++) {
151 char *rpath = unmeta(*args);
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;
161 return err;
164 /* ln and mv builtins */
166 #define BIN_LN 0
167 #define BIN_MV 1
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)
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.
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.
191 * Logic compared against that of FreeBSD's ln.c, compatible license.
194 /**/
195 static int
196 bin_ln(char *nam, char **args, Options ops, int func)
198 MoveFunc move;
199 int flags, have_dir, err = 0;
200 char **a, *ptr, *rp, *buf;
201 struct stat st;
202 size_t blen;
205 if(func == BIN_MV) {
206 move = (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 move = (MoveFunc) symlink;
216 else
217 #endif
219 move = (MoveFunc) link;
220 if(!OPT_ISSET(ops,'d'))
221 flags |= MV_NODIRS;
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)) {
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.
240 if(a > args+1) {
241 errno = ENOTDIR;
242 zwarnnam(nam, "%s: %e", *a, errno);
243 return 1;
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;
254 /* Normal case, target is a directory, chase into it */
255 if (have_dir)
256 goto havedir;
259 if(a > args+1) {
260 zwarnnam(nam, "last of many arguments must be a directory");
261 return 1;
263 if(!args[1]) {
264 ptr = strrchr(args[0], '/');
265 if(ptr)
266 args[1] = ptr+1;
267 else
268 args[1] = args[0];
270 return domove(nam, move, 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++) {
278 ptr = strrchr(*args, '/');
279 if(ptr)
280 ptr++;
281 else
282 ptr = *args;
284 buf[blen] = 0;
285 buf = appstr(buf, ptr);
286 err |= domove(nam, move, *args, buf, flags);
288 zsfree(buf);
289 return err;
292 /**/
293 static int
294 domove(char *nam, MoveFunc move, char *p, char *q, int flags)
296 struct stat st;
297 char *pbuf, *qbuf;
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;
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;
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;
339 doit = 1;
341 if(doit && !(flags & MV_ATOMIC))
342 unlink(qbuf);
344 if(move(pbuf, qbuf)) {
345 zwarnnam(nam, "%s: %e", p, errno);
346 zsfree(pbuf);
347 return 1;
349 zsfree(pbuf);
350 return 0;
353 /* general recursion */
355 struct 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;
366 /**/
367 static int
368 recursivecmd(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)
372 int err = 0, len;
373 char *rp, *s;
374 struct dirsav ds;
375 struct recursivecmd reccmd;
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 ds.ino = ds.dev = 0;
386 ds.dirname = NULL;
387 ds.dirfd = ds.level = -1;
388 if (opt_recurse || opt_safe) {
389 if ((ds.dirfd = open(".", O_RDONLY|O_NOCTTY)) < 0 &&
390 zgetdir(&ds) && *ds.dirname != '/')
391 ds.dirfd = open("..", O_RDONLY|O_NOCTTY);
393 for(; !errflag && !(err & 2) && *args; args++) {
394 rp = ztrdup(*args);
395 unmetafy(rp, &len);
396 if (opt_safe) {
397 s = strrchr(rp, '/');
398 if (s && !s[1]) {
399 while (*s == '/' && s > rp)
400 *s-- = '\0';
401 while (*s != '/' && s > rp)
402 s--;
404 if (s && s[1]) {
405 int e;
407 *s = '\0';
408 e = lchdir(s > rp ? rp : "/", &ds, 1);
409 err |= -e;
410 if (!e) {
411 struct dirsav d;
413 d.ino = d.dev = 0;
414 d.dirname = NULL;
415 d.dirfd = d.level = -1;
416 err |= recursivecmd_doone(&reccmd, *args, s + 1, &d, 0);
417 zsfree(d.dirname);
418 if (restoredir(&ds))
419 err |= 2;
420 } else if(!opt_noerr)
421 zwarnnam(nam, "%s: %e", *args, errno);
422 } else
423 err |= recursivecmd_doone(&reccmd, *args, rp, &ds, 0);
424 } else
425 err |= recursivecmd_doone(&reccmd, *args, rp, &ds, 1);
426 zfree(rp, len + 1);
428 if ((err & 2) && ds.dirfd >= 0 && restoredir(&ds) && zchdir(pwd)) {
429 zsfree(pwd);
430 pwd = ztrdup("/");
431 if (chdir(pwd) < 0)
432 zwarn("failed to chdir(%s): %e", pwd, errno);
434 if (ds.dirfd >= 0)
435 close(ds.dirfd);
436 zsfree(ds.dirname);
437 return !!err;
440 /**/
441 static int
442 recursivecmd_doone(struct recursivecmd const *reccmd,
443 char *arg, char *rp, struct dirsav *ds, int first)
445 struct stat st, *sp = NULL;
447 if(reccmd->opt_recurse && !lstat(rp, &st)) {
448 if(S_ISDIR(st.st_mode))
449 return recursivecmd_dorec(reccmd, arg, rp, &st, ds, first);
450 sp = &st;
452 return reccmd->leaf_func(arg, rp, sp, reccmd->magic);
455 /**/
456 static int
457 recursivecmd_dorec(struct recursivecmd const *reccmd,
458 char *arg, char *rp, struct stat const *sp, struct dirsav *ds, int first)
460 char *fn;
461 DIR *d;
462 int err, err1;
463 struct dirsav dsav;
464 char *files = NULL;
465 int fileslen = 0;
467 err1 = reccmd->dirpre_func(arg, rp, sp, reccmd->magic);
468 if(err1 & 2)
469 return 2;
471 err = -lchdir(rp, ds, !first);
472 if (err) {
473 if(!reccmd->opt_noerr)
474 zwarnnam(reccmd->nam, "%s: %e", arg, errno);
475 return err;
477 err = err1;
479 dsav.ino = dsav.dev = 0;
480 dsav.dirname = NULL;
481 dsav.dirfd = dsav.level = -1;
482 d = opendir(".");
483 if(!d) {
484 if(!reccmd->opt_noerr)
485 zwarnnam(reccmd->nam, "%s: %e", arg, errno);
486 err = 1;
487 } else {
488 int arglen = strlen(arg) + 1;
490 while (!errflag && (fn = zreaddir(d, 1))) {
491 int l = strlen(fn) + 1;
492 files = hrealloc(files, fileslen, fileslen + l);
493 strcpy(files + fileslen, fn);
494 fileslen += l;
496 closedir(d);
497 for (fn = files; !errflag && !(err & 2) && fn < files + fileslen;) {
498 int l = strlen(fn) + 1;
499 VARARR(char, narg, arglen + l);
501 strcpy(narg,arg);
502 narg[arglen-1] = '/';
503 strcpy(narg + arglen, fn);
504 unmetafy(fn, NULL);
505 err |= recursivecmd_doone(reccmd, narg, fn, &dsav, 0);
506 fn += l;
508 hrealloc(files, fileslen, 0);
510 zsfree(dsav.dirname);
511 if (err & 2)
512 return 2;
513 if (restoredir(ds)) {
514 if(!reccmd->opt_noerr)
515 zwarnnam(reccmd->nam, "failed to return to previous directory: %e",
516 errno);
517 return 2;
519 return err | reccmd->dirpost_func(arg, rp, sp, reccmd->magic);
522 /**/
523 static int
524 recurse_donothing(UNUSED(char *arg), UNUSED(char *rp), UNUSED(struct stat const *sp), UNUSED(void *magic))
526 return 0;
529 /* rm builtin */
531 struct rmmagic {
532 char *nam;
533 int opt_force;
534 int opt_interact;
535 int opt_unlinkdir;
538 /**/
539 static int
540 rm_leaf(char *arg, char *rp, struct stat const *sp, void *magic)
542 struct rmmagic *rmm = magic;
543 struct stat st;
545 if(!rmm->opt_unlinkdir || !rmm->opt_force) {
546 if(!sp) {
547 if(!lstat(rp, &st))
548 sp = &st;
550 if(sp) {
551 if(!rmm->opt_unlinkdir && S_ISDIR(sp->st_mode)) {
552 if(rmm->opt_force)
553 return 0;
554 zwarnnam(rmm->nam, "%s: %e", arg, EISDIR);
555 return 1;
557 if(rmm->opt_interact) {
558 nicezputs(rmm->nam, stderr);
559 fputs(": remove `", stderr);
560 nicezputs(arg, stderr);
561 fputs("'? ", stderr);
562 fflush(stderr);
563 if(!ask())
564 return 0;
565 } else if(!rmm->opt_force &&
566 !S_ISLNK(sp->st_mode) &&
567 access(rp, W_OK)) {
568 nicezputs(rmm->nam, stderr);
569 fputs(": remove `", stderr);
570 nicezputs(arg, stderr);
571 fprintf(stderr, "', overriding mode %04o? ",
572 mode_to_octal(sp->st_mode));
573 fflush(stderr);
574 if(!ask())
575 return 0;
579 if(unlink(rp) && !rmm->opt_force) {
580 zwarnnam(rmm->nam, "%s: %e", arg, errno);
581 return 1;
583 return 0;
586 /**/
587 static int
588 rm_dirpost(char *arg, char *rp, UNUSED(struct stat const *sp), void *magic)
590 struct rmmagic *rmm = magic;
592 if(rmm->opt_interact) {
593 nicezputs(rmm->nam, stderr);
594 fputs(": remove `", stderr);
595 nicezputs(arg, stderr);
596 fputs("'? ", stderr);
597 fflush(stderr);
598 if(!ask())
599 return 0;
601 if(rmdir(rp) && !rmm->opt_force) {
602 zwarnnam(rmm->nam, "%s: %e", arg, errno);
603 return 1;
605 return 0;
608 /**/
609 static int
610 bin_rm(char *nam, char **args, Options ops, UNUSED(int func))
612 struct rmmagic rmm;
613 int err;
615 rmm.nam = nam;
616 rmm.opt_force = OPT_ISSET(ops,'f');
617 rmm.opt_interact = OPT_ISSET(ops,'i') && !OPT_ISSET(ops,'f');
618 rmm.opt_unlinkdir = OPT_ISSET(ops,'d');
619 err = recursivecmd(nam, OPT_ISSET(ops,'f'),
620 OPT_ISSET(ops,'r') && !OPT_ISSET(ops,'d'),
621 OPT_ISSET(ops,'s'),
622 args, recurse_donothing, rm_dirpost, rm_leaf, &rmm);
623 return OPT_ISSET(ops,'f') ? 0 : err;
626 /* chown builtin */
628 struct chownmagic {
629 char *nam;
630 uid_t uid;
631 gid_t gid;
634 /**/
635 static int
636 chown_dochown(char *arg, char *rp, UNUSED(struct stat const *sp), void *magic)
638 struct chownmagic *chm = magic;
640 if(chown(rp, chm->uid, chm->gid)) {
641 zwarnnam(chm->nam, "%s: %e", arg, errno);
642 return 1;
644 return 0;
647 /**/
648 static int
649 chown_dolchown(char *arg, char *rp, UNUSED(struct stat const *sp), void *magic)
651 struct chownmagic *chm = magic;
653 if(lchown(rp, chm->uid, chm->gid)) {
654 zwarnnam(chm->nam, "%s: %e", arg, errno);
655 return 1;
657 return 0;
661 /**/
662 static unsigned long getnumeric(char *p, int *errp)
664 unsigned long ret;
666 if (!idigit(*p)) {
667 *errp = 1;
668 return 0;
670 ret = strtoul(p, &p, 10);
671 *errp = !!*p;
672 return ret;
675 enum { BIN_CHOWN, BIN_CHGRP };
677 /**/
678 static int
679 bin_chown(char *nam, char **args, Options ops, int func)
681 struct chownmagic chm;
682 char *uspec = ztrdup(*args), *p = uspec;
683 char *end;
685 chm.nam = nam;
686 if(func == BIN_CHGRP) {
687 chm.uid = -1;
688 goto dogroup;
690 end = strchr(uspec, ':');
691 if(!end)
692 end = strchr(uspec, '.');
693 if(end == uspec) {
694 chm.uid = -1;
695 p++;
696 goto dogroup;
697 } else {
698 struct passwd *pwd;
699 if(end)
700 *end = 0;
701 pwd = getpwnam(p);
702 if(pwd)
703 chm.uid = pwd->pw_uid;
704 else {
705 int err;
706 chm.uid = getnumeric(p, &err);
707 if(err) {
708 zwarnnam(nam, "%s: no such user", p);
709 free(uspec);
710 return 1;
713 if(end) {
714 p = end+1;
715 if(!*p) {
716 if(!pwd && !(pwd = getpwuid(chm.uid))) {
717 zwarnnam(nam, "%s: no such user", uspec);
718 free(uspec);
719 return 1;
721 chm.gid = pwd->pw_gid;
722 } else if(p[0] == ':' && !p[1]) {
723 chm.gid = -1;
724 } else {
725 struct group *grp;
726 dogroup:
727 grp = getgrnam(p);
728 if(grp)
729 chm.gid = grp->gr_gid;
730 else {
731 int err;
732 chm.gid = getnumeric(p, &err);
733 if(err) {
734 zwarnnam(nam, "%s: no such group", p);
735 free(uspec);
736 return 1;
740 } else
741 chm.gid = -1;
743 free(uspec);
744 return recursivecmd(nam, 0, OPT_ISSET(ops,'R'), OPT_ISSET(ops,'s'),
745 args + 1, OPT_ISSET(ops, 'h') ? chown_dolchown : chown_dochown, recurse_donothing,
746 OPT_ISSET(ops, 'h') ? chown_dolchown : chown_dochown, &chm);
749 /* module paraphernalia */
751 #ifdef HAVE_LSTAT
752 # define LN_OPTS "dfhins"
753 #else
754 # define LN_OPTS "dfi"
755 #endif
757 static struct builtin bintab[] = {
758 /* The names which overlap commands without necessarily being
759 * fully compatible. */
760 BUILTIN("chgrp", 0, bin_chown, 2, -1, BIN_CHGRP, "hRs", NULL),
761 BUILTIN("chown", 0, bin_chown, 2, -1, BIN_CHOWN, "hRs", NULL),
762 BUILTIN("ln", 0, bin_ln, 1, -1, BIN_LN, LN_OPTS, NULL),
763 BUILTIN("mkdir", 0, bin_mkdir, 1, -1, 0, "pm:", NULL),
764 BUILTIN("mv", 0, bin_ln, 2, -1, BIN_MV, "fi", NULL),
765 BUILTIN("rm", 0, bin_rm, 1, -1, 0, "dfirs", NULL),
766 BUILTIN("rmdir", 0, bin_rmdir, 1, -1, 0, NULL, NULL),
767 BUILTIN("sync", 0, bin_sync, 0, 0, 0, NULL, NULL),
768 /* The "safe" zsh-only names */
769 BUILTIN("zf_chgrp", 0, bin_chown, 2, -1, BIN_CHGRP, "hRs", NULL),
770 BUILTIN("zf_chown", 0, bin_chown, 2, -1, BIN_CHOWN, "hRs", NULL),
771 BUILTIN("zf_ln", 0, bin_ln, 1, -1, BIN_LN, LN_OPTS, NULL),
772 BUILTIN("zf_mkdir", 0, bin_mkdir, 1, -1, 0, "pm:", NULL),
773 BUILTIN("zf_mv", 0, bin_ln, 2, -1, BIN_MV, "fi", NULL),
774 BUILTIN("zf_rm", 0, bin_rm, 1, -1, 0, "dfirs", NULL),
775 BUILTIN("zf_rmdir", 0, bin_rmdir, 1, -1, 0, NULL, NULL),
776 BUILTIN("zf_sync", 0, bin_sync, 0, 0, 0, NULL, NULL),
780 static struct features module_features = {
781 bintab, sizeof(bintab)/sizeof(*bintab),
782 NULL, 0,
783 NULL, 0,
784 NULL, 0,
788 /**/
790 setup_(UNUSED(Module m))
792 return 0;
795 /**/
797 features_(Module m, char ***features)
799 *features = featuresarray(m, &module_features);
800 return 0;
803 /**/
805 enables_(Module m, int **enables)
807 return handlefeatures(m, &module_features, enables);
810 /**/
812 boot_(Module m)
814 return 0;
817 /**/
819 cleanup_(Module m)
821 return setfeatureenables(m, &module_features, NULL);
824 /**/
826 finish_(UNUSED(Module m))
828 return 0;