#ifndef lint static char sccsid[] = "@(#)exportfs.c 1.2 88/06/15 4.0NFSSRC Copyr 1988 Sun Micro"; #endif /* * Export directories (or files) to NFS clients Copyright (c) 1987 Sun * Microsystems, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include /* * options to command */ #define AFLAG 0x01 #define UFLAG 0x02 #define VFLAG 0x04 #define IFLAG 0x08 #define BUFINCRSZ 128 #define MAXNAMELEN 256 #define NETGROUPINCR 32 #define streq(a,b) (strcmp(a, b) == 0) char *getline(FILE *); char *accessjoin(char *, int ngrps, char **); char *strtoken(char **, char *); char *check_malloc(unsigned int); char *check_realloc(char *, unsigned int); void out_of_memory(void); void cannot_open(int, char *); void printexports(void); void printexport(struct exportent *, int); void fillex(struct export *, struct options *); void export_full(char *, int); int map_user(char *, int *); char anon_opt[] = ANON_OPT; char ro_opt[] = RO_OPT; char rw_opt[] = RW_OPT; char root_opt[] = ROOT_OPT; char access_opt[] = ACCESS_OPT; char nohide_opt[] = NOHIDE_OPT; char wsync_opt[] = WSYNC_OPT; char b32clnt_opt[] = B32CLNT_OPT; char noxattr_opt[] = NOXATTR_OPT; char EXPORTFILE[] = "/etc/exports"; static const cap_value_t cap_mount_read[] = {CAP_MOUNT_MGT, CAP_MAC_READ}; struct options { int anon; int flags; int auth; char **hostlist; int hostlistsize; int nhosts; char **accesslist; int accesslistsize; int naccess; char **writelist; int writelistsize; int nwrites; }; main(argc, argv) int argc; char *argv[]; { int i; char *options = NULL; int Argc; char **Argv; int flags; int retstatus = 0; if (!parseargs(argc, argv, &flags, &options, &Argc, &Argv)) { (void) fprintf(stderr, "usage: %s [-aiuv] [-o options] [dir] ...\n", argv[0]); exit(1); } if ((Argc || (flags & AFLAG)) && geteuid() != 0) { fprintf(stderr, "must be superuser to %sexport\n", (flags & UFLAG) ? "un" : ""); exit(1); } if (Argc == 0) { if (flags & AFLAG) { if (flags & UFLAG) { retstatus = (unexportall(flags & VFLAG) < 0); } else { retstatus = (exportall(flags & VFLAG, NULL) < 0); } } else { printexports(); } } else if (flags & UFLAG) { if (options) { (void) fprintf(stderr, "exportfs: options ignored for unexport\n"); } for (i = 0; i < Argc; i++) { retstatus |= (unexport(Argv[i], flags & VFLAG) < 0); } } else { for (i = 0; i < Argc; i++) { if (flags & IFLAG) { retstatus |= (export(Argv[i], options, flags & VFLAG) < 0); } else { retstatus |= (exportall(flags & VFLAG, Argv[i]) < 0); } } } exit(retstatus); /* NOTREACHED */ } /* * Print all exported pathnames */ void printexports(void) { struct exportent *xent; FILE *f; int maxlen; f = fopen(TABFILE, "r"); if (f == NULL) { (void) printf("nothing exported\n"); return; } maxlen = 0; while (xent = getexportent(f)) { if (strlen(xent->xent_dirname) > maxlen) { maxlen = strlen(xent->xent_dirname); } } rewind(f); while (xent = getexportent(f)) { printexport(xent, maxlen + 1); } if (maxlen == 0) { (void) printf("nothing exported\n"); } endexportent(f); } /* * Print just one exported pathname */ void printexport(xent, col) struct exportent *xent; int col; { int i; (void) printf("%s", xent->xent_dirname); if (xent->xent_options) { for (i = strlen(xent->xent_dirname); i < col; i++) { (void) putchar(' '); } (void) printf("-%s", xent->xent_options); } (void) printf("\n"); } /* * Unexport everything in the export tab file */ unexportall(verbose) int verbose; { struct exportent *xent; FILE *f; cap_t ocap; register int retcode = 0; f = setexportent(); if (f == NULL) { cannot_open(0, TABFILE); return -1; } while (xent = getexportent(f)) { (void) remexportent(f, xent->xent_dirname); ocap = cap_acquire (2, cap_mount_read); if (exportfs(xent->xent_dirname, (struct export *) NULL) < 0) { (void) fprintf(stderr, "exportfs: %s: %s\n", xent->xent_dirname, strerror(errno)); retcode = -1; } else { if (verbose) { (void) printf("unexported %s\n", xent->xent_dirname); } } cap_surrender(ocap); } endexportent(f); return retcode; } /* * Unexport just one directory */ unexport(dirname, verbose) char *dirname; int verbose; { FILE *f; cap_t ocap; f = setexportent(); if (f == NULL) { cannot_open(0, TABFILE); return -1; } (void) remexportent(f, dirname); ocap = cap_acquire (2, cap_mount_read); if (exportfs(dirname, (struct export *) NULL) < 0) { cap_surrender(ocap); (void) fprintf(stderr, "exportfs: %s: %s\n", dirname, strerror(errno)); endexportent(f); return -1; } cap_surrender(ocap); endexportent(f); if (verbose) { (void) printf("unexported %s\n", dirname); } return 0; } /* * Export everything in /etc/exports */ exportall(verbose, which) int verbose; char *which; { char *name; FILE *f; char *lp; char *dirname; char *options; char **grps; int grpsize; int ngrps; register int i; int exported, failed; f = fopen(EXPORTFILE, "r"); if (f == NULL) { cannot_open(errno, EXPORTFILE); return -1; } grps = NULL; grpsize = 0; exported = failed = 0; while ( (which == NULL || !exported) && ( (lp = getline(f)) != NULL ) ) { dirname = NULL; options = NULL; ngrps = 0; while ((name = strtoken(&lp, " \t")) != NULL) { if (dirname == NULL) { dirname = name; } else if (options == NULL && name[0] == '-') { if ((options = strdup(name + 1)) == NULL) out_of_memory(); } else { grpsize = addtogrouplist(&grps, grpsize, ngrps, name); ngrps++; } } if (ngrps > 0) { options = accessjoin(options, ngrps, grps); for (i = 0; i < ngrps; i++) free(grps[i]); } if (dirname != NULL && (which == NULL || strcmp(dirname, which) == 0)) { if (export(dirname, options, verbose) < 0) failed = 1; else exported++; } if (options != NULL) { free(options); } } if (failed) { return -1; } if (which != NULL && !exported) { fprintf(stderr, "%s not found in %s\n", which, EXPORTFILE); return -1; } return 0; } int addtogrouplist(grplistp, grplistsize, index, group) char ***grplistp; int grplistsize; int index; char *group; { if (index == grplistsize) { grplistsize += NETGROUPINCR; if (*grplistp == NULL) *grplistp = (char **) check_malloc(((unsigned) grplistsize * sizeof(char *))); else *grplistp = (char **) check_realloc((char *) *grplistp, (unsigned) (grplistsize * sizeof(char *))); } (*grplistp)[index] = group; return grplistsize; } /* * Export just one directory */ export(dirname, options, verbose) char *dirname; char *options; int verbose; { struct export ex; struct options opt; FILE *f; int redo = 0; cap_t ocap; if (!interpret(dirname, &opt, options)) { return -1; } fillex(&ex, &opt); f = setexportent(); if (f == NULL) { cannot_open(0, TABFILE); return -1; } if (insane(f, dirname)) { endexportent(f); return -1; } if (xtab_test(f, dirname)) { (void) remexportent(f, dirname); redo = 1; } ocap = cap_acquire (2, cap_mount_read); if (exportfs(dirname, &ex) < 0) { cap_surrender(ocap); (void) fprintf(stderr, "exportfs: %s: %s\n", dirname, strerror(errno)); endexportent(f); return -1; } cap_surrender(ocap); addexportent(f, dirname, options); endexportent(f); if (verbose) { if (redo) { (void) printf("re-exported %s\n", dirname); } else { (void) printf("exported %s\n", dirname); } } return 0; } /* * Interpret a line of options */ interpret(dirname, opt, line) char *dirname; struct options *opt; char *line; { char *name; /* * Initialize and set up defaults */ opt->anon = -2; opt->flags = 0; opt->auth = AUTH_UNIX; opt->hostlist = NULL; opt->hostlistsize = 0; opt->nhosts = 0; opt->accesslist = NULL; opt->accesslistsize = 0; opt->naccess = 0; opt->writelist = NULL; opt->writelistsize = 0; opt->nwrites = 0; if (line == NULL) { return 1; } for (;;) { name = strtoken(&line, ","); if (name == NULL) { return 1; } if (!dooption(dirname, name, opt)) { return 0; } } } /* * Fill in the root addresses */ static void filladdrs(struct exaddrlist *addrs, char **names, int nnames, char *errstr, int max) { struct hostent *h; struct sockaddr *sa; int i, n; char *machine, *user, *domain; addrs->naddrs = 0; sa = &addrs->addrvec[0]; for (i = 0; i < nnames; i++) { /* check for netgroup */ n = innetgr(names[i], (char *) 0, (char *) 0, (char *) 0); if (n) { setnetgrent(names[i]); while (getnetgrent(&machine, &user, &domain)) { if (machine) { if ((h = gethostbyname(machine)) == NULL) fprintf(stderr,"exportfs: unknown host %s in netgroup %s\n", machine, names[i]); else { if (addrs->naddrs >= max) { export_full(errstr, max); endnetgrent(); return; } if (fillone(h, sa) == 0) { addrs->naddrs++; sa++; } } } else { sethostent(0); while (h = gethostent()) { if (addrs->naddrs >= max) { export_full(errstr, max); endnetgrent(); return; } if (fillone(h, sa) == 0) { addrs->naddrs++; sa++; } } } } /* while */ endnetgrent(); continue; } h = gethostbyname(names[i]); if (h == NULL) { fprintf(stderr, "exportfs: bad host '%s' in %s: %s\n", names[i], errstr, hstrerror(h_errno)); continue; } if (addrs->naddrs >= max) { export_full(errstr, max); return; } if (fillone(h, sa) == 0 ) { addrs->naddrs++; sa++; } } } /* fill an export struct for the given host */ fillone(h, sa) struct hostent *h; struct sockaddr *sa; { bzero((char *) sa, sizeof *sa); sa->sa_family = h->h_addrtype; switch (h->h_addrtype) { case AF_INET: bcopy(h->h_addr, (char *) &((struct sockaddr_in *)sa)->sin_addr, h->h_length); break; default: (void) fprintf(stderr, "exportfs: %d: unknown address type for %s\n", h->h_addrtype, h->h_name); return(-1); } return(0); } /* * Fill an export structure given the options selected */ void fillex(ex, ops) struct export *ex; struct options *ops; { ex->ex_flags = ops->flags; if (ex->ex_flags & EX_RDMOSTLY) { ex->ex_writeaddrs.addrvec = (struct sockaddr *) check_malloc(EXMAXADDRS * sizeof(struct sockaddr)); filladdrs(&ex->ex_writeaddrs, ops->writelist, ops->nwrites, "rw option", EXMAXADDRS); } ex->ex_anon = ops->anon; ex->ex_auth = ops->auth; switch (ops->auth) { case AUTH_UNIX: ex->ex_unix.rootaddrs.naddrs = ops->nhosts; ex->ex_unix.rootaddrs.addrvec = (struct sockaddr *) check_malloc(EXMAXROOTADDRS * sizeof(struct sockaddr)); filladdrs(&ex->ex_unix.rootaddrs, ops->hostlist, ops->nhosts, "root option", EXMAXROOTADDRS); ex->ex_accessaddrs.addrvec = (struct sockaddr *) check_malloc(EXMAXADDRS * sizeof(struct sockaddr)); filladdrs(&ex->ex_accessaddrs, ops->accesslist, ops->naccess, "access option", EXMAXADDRS); break; } } dooption(dirname, opstr, ops) char *dirname; char *opstr; struct options *ops; { # define pstreq(a, b, blen) \ ((strncmp(a, b, blen) == 0) && (a[blen] == '=')) if (streq(opstr, ro_opt)) { ops->flags |= EX_RDONLY; } else if (pstreq(opstr, rw_opt, sizeof(rw_opt) - 1)) { ops->flags |= EX_RDMOSTLY; if (!getacclist(&opstr[sizeof(rw_opt)], &ops->nwrites, &ops->writelist, &ops->writelistsize)) { out_of_memory(); } } else if (pstreq(opstr, anon_opt, sizeof(anon_opt) - 1)) { if (!map_user(&opstr[sizeof(anon_opt)], &ops->anon)) return 0; } else if (streq(opstr, nohide_opt)) { ops->flags |= EX_NOHIDE; } else if (streq(opstr, wsync_opt)) { ops->flags |= EX_WSYNC; } else if (streq(opstr, rw_opt)) { /* read-write export */ /* nothing to do */ } else if (pstreq(opstr, root_opt, sizeof(root_opt) - 1)) { if (!getacclist(&opstr[sizeof(root_opt)], &ops->nhosts, &ops->hostlist, &ops->hostlistsize)) { out_of_memory(); } } else if (pstreq(opstr, access_opt, sizeof(access_opt) - 1)) { ops->flags |= EX_ACCESS; if (!getacclist(&opstr[sizeof(access_opt)], &ops->naccess, &ops->accesslist, &ops->accesslistsize)) { out_of_memory(); } } else if (streq(opstr, b32clnt_opt)) { ops->flags |= EX_B32CLNT; } else if (streq(opstr, noxattr_opt)) { ops->flags |= EX_NOXATTR; } else { (void) fprintf(stderr, "exportfs: unknown option: %s\n", opstr); return 0; } return 1; } void export_full(msg, max) char *msg; int max; { (void) fprintf(stderr, "exportfs: export address list for %s is full (max %d).\n", msg, max); } int getstaticnamelist(list, nnames, namelist, max) char *list; int *nnames; char *namelist[]; int max; { char *name; for (;;) { name = strtoken(&list, ":"); if (name == NULL) { return 1; } if (*nnames == max) { return 0; } namelist[*nnames] = name; (*nnames)++; } } int getacclist(list, nnames, namelist, namelistsize) char *list; int *nnames; char ***namelist; int *namelistsize; { char *name; for (;;) { name = strtoken(&list, ":"); if (name == NULL) { return 1; } if (!innetgr(name, (char *) 0, (char *) 0, (char *) 0) && (gethostbyname(name) == 0)) { fprintf(stderr, "exportfs: unknown host or netgroup in access list: %s\n", name); } else { if ((*namelistsize = addtogrouplist(namelist, *namelistsize, *nnames, name)) == 0) return 0; (*nnames)++; } } } char * accessjoin(options, ngrps, grps) char *options; int ngrps; char **grps; { register char *str; register int i; register unsigned int len; register char *p; if (options != NULL) { len = strlen(options); /* */ if (len != 0) len++; /* , */ } else len = 0; len += (sizeof(access_opt) - 1) + 1; /* = */ for (i = 0; i < ngrps; i++) { len += strlen(grps[i]) + 1; /* group: or group\0 */ } str = check_malloc(len); if (options == NULL || *options == '\0') { (void) sprintf(str, "%s=%s", access_opt, grps[0]); } else { (void) sprintf(str, "%s,%s=%s", options, access_opt, grps[0]); } p = str; for (i = 1; i < ngrps; i++) { p += strlen(p); (void) sprintf(p, ":%s", grps[i]); } if (options != NULL) free(options); return str; } parseargs(argc, argv, flags, options, nargc, nargv) int argc; char *argv[]; int *flags; char **options; int *nargc; char ***nargv; { int i; int j; *flags = 0; for (i = 1; i < argc && argv[i][0] == '-'; i++) { if (argv[i][1] == 0) { return 0; } for (j = 1; argv[i][j] != 0; j++) { switch (argv[i][j]) { case 'a': *flags |= AFLAG; break; case 'u': *flags |= UFLAG; break; case 'v': *flags |= VFLAG; break; case 'i': *flags |= IFLAG; break; case 'o': if (j != 1 || argv[i][2] != 0) { return 0; } if (i + 1 >= argc) { return 0; } *options = argv[++i]; goto breakout; default: return 0; } } breakout: ; } *nargc = argc - i; *nargv = argv + i; return 1; } xtab_test(f, dirname) FILE *f; char *dirname; { struct exportent *xent; rewind(f); while (xent = getexportent(f)) { if (streq(xent->xent_dirname, dirname)) { return 1; } } return 0; } direq(dir1, dir2) char *dir1; char *dir2; { struct stat64 st1; struct stat64 st2; if (stat64(dir1, &st1) < 0) { return 0; } if (stat64(dir2, &st2) < 0) { return 0; } return (st1.st_ino == st2.st_ino && st1.st_dev == st2.st_dev); } insane(f, dir) FILE *f; char *dir; { struct exportent *xent; rewind(f); while (xent = getexportent(f)) { if (direq(xent->xent_dirname, dir)) { continue; } if (issubdir(xent->xent_dirname, dir)) { (void) fprintf(stderr, "exportfs: %s: sub-directory (%s) already exported\n", dir, xent->xent_dirname); return 1; } if (issubdir(dir, xent->xent_dirname)) { (void) fprintf(stderr, "exportfs: %s: parent-directory (%s) already exported\n", dir, xent->xent_dirname); return 1; } } return 0; } #define buf_append(c, bp, buf, bufsz) \ ( \ ( bp == (bufsz) ) ? ( \ (buf) = check_realloc((buf), (bufsz)+BUFINCRSZ), \ (bufsz) += BUFINCRSZ, \ (buf)[bp++] = (c) \ ) : ( \ (buf)[bp++] = (c) \ ) \ ) /* * getline() Read a line from a file, digesting backslash-newline. * Returns a pointer to the read string, or NULL on EOF. * Backslash-newline counts as whitespace except when: * we are processing the options section * and the preceeding char is ':', '=', or ',' */ char * getline(f) FILE *f; { static char *buf = NULL; static size_t bufsz = 0; size_t bp = 0; int cc; enum getline_states { init_state, /* start here, eat blank lines, comments, etc */ bsi_state, /* backslash newline that counts as init */ comment_state, /* comment #..*$ */ dir_state, /* name of exported directory */ bs1_state, /* backslash newline that counts as ws1 */ ws1_state, /* whitespace between dir and options */ opt_state, /* options section */ bs2_state, /* backslash newline conditionally eaten */ ws2_state, /* whitespace after options section */ grp_state, /* hosts and netgroups */ bs3_state, /* backslash newline that counts as ws2 */ done_state /* we're done */ } state, return_state; /* initialize buffer if needed */ if ( buf == NULL ) { buf = check_malloc(BUFINCRSZ); bufsz = BUFINCRSZ; } state = init_state; return_state = done_state; do { cc = getc(f); if ( cc == EOF ) { /* * if there is any data in the buffer, return it */ if ( bp > 0 ) { state = done_state; } else { return NULL; } } /* another simplification */ if ( cc == '\t' ) { cc = ' '; } switch ( state ) { case init_state: switch ( cc ) { /* * We haven't gotten any data yet. Chew up blank * lines, continued lines, and comments until we * hit something of interest. */ case ' ': break; case '\\': state = bsi_state; break; case '#': return_state = init_state; state = comment_state; break; case '\n': break; default: buf_append(cc, bp, buf, bufsz); state = dir_state; break; } break; case bsi_state: /* * We've hit a backslash. If it turns out to be a * backslash-newline, continue on processing in * the init state. Otherwise, we have the first * char of our exported directory. * * Can the first char of the directory ever be * anything but '/'? * * Right now, the directory name does not have to * start in column 0. * * Allow backslash to quote '\\' and '#'. */ switch ( cc ) { case ' ': state = init_state; break; case '\n': state = init_state; break; default: buf_append(cc, bp, buf, bufsz); state = dir_state; break; } break; case comment_state: /* * We've hit a comment. After consuming it we * either will be done or we will continue init * processing depending on the value of * return_state. */ switch ( cc ) { case '\n': state = return_state; break; default: break; } break; case dir_state: /* * We are now processing the exported directory * portion of the line. * Line continuation terminates this section. * We are guaranteed to have at least one char * of the directory name already. * * Question, shouldn't directory names ALWAYS * begin with a '/'? Can I enforce this? */ switch ( cc ) { case ' ': buf_append(cc, bp, buf, bufsz); state = ws1_state; break; case '\\': state = bs1_state; break; case '#': return_state = done_state; state = comment_state; break; case '\n': state = done_state; break; default: buf_append(cc, bp, buf, bufsz); break; } break; case bs1_state: /* * We ran into a backslash while processing the * exported directory name. If it is a line * continuation, move on to the options * processing. If it is just escaping a char, go * back to dir_state. */ switch ( cc ) { case ' ': buf_append(' ', bp, buf, bufsz); state = ws1_state; break; case '\n': buf_append(' ', bp, buf, bufsz); state = ws1_state; break; default: buf_append(cc, bp, buf, bufsz); state = dir_state; break; } break; case ws1_state: /* * White space between the directory and the * options or host/grouplist. * We are guaranteed that there is already a * space in the buffer after the directory. */ switch ( cc ) { case ' ': break; case '\\': state = bs2_state; break; case '#': return_state = done_state; state = comment_state; break; case '\n': state = done_state; break; case '-': buf_append(cc, bp, buf, bufsz); state = opt_state; break; default: buf_append(cc, bp, buf, bufsz); state = grp_state; break; } break; case opt_state: /* * We're now processing the options part of the * line. */ switch ( cc ) { case ' ': buf_append(cc, bp, buf, bufsz); state = ws2_state; break; case '\\': state = bs2_state; break; case '#': return_state = done_state; state = comment_state; break; case '\n': state = done_state; break; default: buf_append(cc, bp, buf, bufsz); break; } break; case bs2_state: /* * We ran into a backslash while processing the * options. This is the only place where a * line continuation cannot always be counted as * a whitespace since it mucks up option * processing. * * There is a slight bug here as * "/dir \foo..." * will process foo as an option and use the wrong * line continuation rules. */ switch ( cc ) { case ' ': buf_append(' ', bp, buf, bufsz); state = ws2_state; break; case '\n': switch ( buf[bp-1] ) { case '-': case '=': case ':': case ',': state = opt_state; break; case ' ': state = ws2_state; break; default: buf_append(' ', bp, buf, bufsz); state = ws2_state; break; } break; default: buf_append(cc, bp, buf, bufsz); state = opt_state; break; } break; case ws2_state: /* * This is the whitespace before a hostname or * netgroup. There is already a space in the * buffer. */ switch ( cc ) { case ' ': break; case '\\': state = bs3_state; break; case '#': return_state = done_state; state = comment_state; break; case '\n': state = done_state; break; default: buf_append(cc, bp, buf, bufsz); state = grp_state; break; } break; case grp_state: switch ( cc ) { case ' ': buf_append(cc, bp, buf, bufsz); state = ws2_state; break; case '\\': state = bs3_state; break; case '#': return_state = done_state; state = comment_state; break; case '\n': state = done_state; break; default: buf_append(cc, bp, buf, bufsz); break; } break; case bs3_state: /* * We ran into a backslash while processing the * hosts/grouplists. */ switch ( cc ) { case ' ': buf_append(' ', bp, buf, bufsz); state = ws2_state; break; case '\n': buf_append(' ', bp, buf, bufsz); state = ws2_state; break; default: buf_append(cc, bp, buf, bufsz); state = grp_state; break; } break; case done_state: /* do nothing */ break; default: /* should never be here - punt */ state = done_state; break; } } while ( state != done_state ); buf_append('\0', bp, buf, bufsz); return buf; } /* getline() */ /* * Like strtok(), but no static data */ char * strtoken(string, sepset) char **string; char *sepset; { char *p; char *q; char *r; char *res; p = *string; if (p == 0) { return NULL; } q = p + strspn(p, sepset); if (*q == 0) { return NULL; } r = strpbrk(q, sepset); if (r == NULL) { *string = 0; if ((res = strdup(q)) == NULL) out_of_memory(); } else { res = check_malloc((unsigned) (r - q + 1)); (void) strncpy(res, q, r - q); res[r - q] = 0; *string = ++r; } return res; } char * check_malloc(size) unsigned int size; { register char *memoryp; if ((memoryp = malloc(size)) == NULL) out_of_memory(); return memoryp; } char * check_realloc(memoryp, size) char *memoryp; unsigned int size; { if ((memoryp = realloc(memoryp, size)) == NULL) out_of_memory(); return memoryp; } void out_of_memory(void) { (void) fprintf(stderr, "exportfs: Out of memory\n"); exit(1); } void cannot_open(error, filename) int error; char *filename; { fprintf(stderr, "exportfs: cannot open %s", filename); if (error) fprintf(stderr, ": %s", strerror(error)); fputc('\n', stderr); } #include #include int map_user(name, uidp) char *name; int *uidp; { struct passwd *pw; if (isdigit(*name) || (name[0] == '-' && isdigit(name[1]))) { *uidp = atoi(name); return 1; } pw = getpwnam(name); if (pw) { *uidp = pw->pw_uid; return 1; } fprintf(stderr, "exportfs: unknown user: %s\n", name); return 0; }