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