| /* $NetBSD: chown.c,v 1.8 2012/10/24 01:12:51 enami Exp $ */ |
| |
| /* |
| * Copyright (c) 1988, 1993, 1994, 2003 |
| * The Regents of the University of California. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of the University nor the names of its contributors |
| * may be used to endorse or promote products derived from this software |
| * without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| */ |
| |
| #include <sys/cdefs.h> |
| #ifndef lint |
| __COPYRIGHT("@(#) Copyright (c) 1988, 1993, 1994, 2003\ |
| The Regents of the University of California. All rights reserved."); |
| #endif /* not lint */ |
| |
| #ifndef lint |
| #if 0 |
| static char sccsid[] = "@(#)chown.c 8.8 (Berkeley) 4/4/94"; |
| #else |
| __RCSID("$NetBSD: chown.c,v 1.8 2012/10/24 01:12:51 enami Exp $"); |
| #endif |
| #endif /* not lint */ |
| |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| |
| #include <ctype.h> |
| #include <dirent.h> |
| #include <err.h> |
| #include <errno.h> |
| #include <locale.h> |
| #include <fts.h> |
| #include <grp.h> |
| #include <pwd.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <getopt.h> |
| |
| static void a_gid(const char *); |
| static void a_uid(const char *); |
| static id_t id(const char *, const char *); |
| __dead static void usage(void); |
| |
| static uid_t uid; |
| static gid_t gid; |
| static int ischown; |
| static const char *myname; |
| |
| struct option chown_longopts[] = { |
| { "reference", required_argument, 0, |
| 1 }, |
| { NULL, 0, 0, |
| 0 }, |
| }; |
| |
| int |
| chown_main(int argc, char **argv) |
| { |
| FTS *ftsp; |
| FTSENT *p; |
| int Hflag, Lflag, Rflag, ch, fflag, fts_options, hflag, rval, vflag; |
| char *cp, *reference; |
| int (*change_owner)(const char *, uid_t, gid_t); |
| |
| setprogname(*argv); |
| |
| (void)setlocale(LC_ALL, ""); |
| |
| myname = getprogname(); |
| ischown = (myname[2] == 'o'); |
| reference = NULL; |
| |
| Hflag = Lflag = Rflag = fflag = hflag = vflag = 0; |
| while ((ch = getopt_long(argc, argv, "HLPRfhv", |
| chown_longopts, NULL)) != -1) |
| switch (ch) { |
| case 1: |
| reference = optarg; |
| break; |
| case 'H': |
| Hflag = 1; |
| Lflag = 0; |
| break; |
| case 'L': |
| Lflag = 1; |
| Hflag = 0; |
| break; |
| case 'P': |
| Hflag = Lflag = 0; |
| break; |
| case 'R': |
| Rflag = 1; |
| break; |
| case 'f': |
| fflag = 1; |
| break; |
| case 'h': |
| /* |
| * In System V the -h option causes chown/chgrp to |
| * change the owner/group of the symbolic link. |
| * 4.4BSD's symbolic links didn't have owners/groups, |
| * so it was an undocumented noop. |
| * In NetBSD 1.3, lchown(2) is introduced. |
| */ |
| hflag = 1; |
| break; |
| case 'v': |
| vflag = 1; |
| break; |
| case '?': |
| default: |
| usage(); |
| } |
| argv += optind; |
| argc -= optind; |
| |
| if (argc == 0 || (argc == 1 && reference == NULL)) |
| usage(); |
| |
| fts_options = FTS_PHYSICAL; |
| if (Rflag) { |
| if (Hflag) |
| fts_options |= FTS_COMFOLLOW; |
| if (Lflag) { |
| if (hflag) |
| errx(EXIT_FAILURE, |
| "the -L and -h options " |
| "may not be specified together."); |
| fts_options &= ~FTS_PHYSICAL; |
| fts_options |= FTS_LOGICAL; |
| } |
| } else if (!hflag) |
| fts_options |= FTS_COMFOLLOW; |
| |
| uid = (uid_t)-1; |
| gid = (gid_t)-1; |
| if (reference == NULL) { |
| if (ischown) { |
| if ((cp = strchr(*argv, ':')) != NULL) { |
| *cp++ = '\0'; |
| a_gid(cp); |
| } |
| #ifdef SUPPORT_DOT |
| else if ((cp = strrchr(*argv, '.')) != NULL) { |
| if (uid_from_user(*argv, &uid) == -1) { |
| *cp++ = '\0'; |
| a_gid(cp); |
| } |
| } |
| #endif |
| a_uid(*argv); |
| } else |
| a_gid(*argv); |
| argv++; |
| } else { |
| struct stat st; |
| |
| if (stat(reference, &st) == -1) |
| err(EXIT_FAILURE, "Cannot stat `%s'", reference); |
| if (ischown) |
| uid = st.st_uid; |
| gid = st.st_gid; |
| } |
| |
| if ((ftsp = fts_open(argv, fts_options, NULL)) == NULL) |
| err(EXIT_FAILURE, "fts_open"); |
| |
| for (rval = EXIT_SUCCESS; (p = fts_read(ftsp)) != NULL;) { |
| change_owner = chown; |
| switch (p->fts_info) { |
| case FTS_D: |
| if (!Rflag) /* Change it at FTS_DP. */ |
| fts_set(ftsp, p, FTS_SKIP); |
| continue; |
| case FTS_DNR: /* Warn, chown, continue. */ |
| warnx("%s: %s", p->fts_path, strerror(p->fts_errno)); |
| rval = EXIT_FAILURE; |
| break; |
| case FTS_ERR: /* Warn, continue. */ |
| case FTS_NS: |
| warnx("%s: %s", p->fts_path, strerror(p->fts_errno)); |
| rval = EXIT_FAILURE; |
| continue; |
| case FTS_SL: /* Ignore unless -h. */ |
| /* |
| * All symlinks we found while doing a physical |
| * walk end up here. |
| */ |
| if (!hflag) |
| continue; |
| /* |
| * Note that if we follow a symlink, fts_info is |
| * not FTS_SL but FTS_F or whatever. And we should |
| * use lchown only for FTS_SL and should use chown |
| * for others. |
| */ |
| change_owner = lchown; |
| break; |
| case FTS_SLNONE: /* Ignore. */ |
| /* |
| * The only symlinks that end up here are ones that |
| * don't point to anything. Note that if we are |
| * doing a phisycal walk, we never reach here unless |
| * we asked to follow explicitly. |
| */ |
| continue; |
| default: |
| break; |
| } |
| |
| if ((*change_owner)(p->fts_accpath, uid, gid) && !fflag) { |
| warn("%s", p->fts_path); |
| rval = EXIT_FAILURE; |
| } else { |
| if (vflag) |
| printf("%s\n", p->fts_path); |
| } |
| } |
| if (errno) |
| err(EXIT_FAILURE, "fts_read"); |
| exit(rval); |
| /* NOTREACHED */ |
| } |
| |
| static void |
| a_gid(const char *s) |
| { |
| struct group *gr; |
| |
| if (*s == '\0') /* Argument was "uid[:.]". */ |
| return; |
| gr = *s == '#' ? NULL : getgrnam(s); |
| if (gr == NULL) |
| gid = id(s, "group"); |
| else |
| gid = gr->gr_gid; |
| return; |
| } |
| |
| static void |
| a_uid(const char *s) |
| { |
| if (*s == '\0') /* Argument was "[:.]gid". */ |
| return; |
| if (*s == '#' || uid_from_user(s, &uid) == -1) { |
| uid = id(s, "user"); |
| } |
| return; |
| } |
| |
| static id_t |
| id(const char *name, const char *type) |
| { |
| id_t val; |
| char *ep; |
| |
| errno = 0; |
| if (*name == '#') |
| name++; |
| val = (id_t)strtoul(name, &ep, 10); |
| if (errno) |
| err(EXIT_FAILURE, "%s", name); |
| if (*ep != '\0') |
| errx(EXIT_FAILURE, "%s: invalid %s name", name, type); |
| return (val); |
| } |
| |
| static void |
| usage(void) |
| { |
| |
| (void)fprintf(stderr, |
| "Usage: %s [-R [-H | -L | -P]] [-fhv] %s file ...\n" |
| "\t%s [-R [-H | -L | -P]] [-fhv] --reference=rfile file ...\n", |
| myname, ischown ? "owner:group|owner|:group" : "group", |
| myname); |
| exit(EXIT_FAILURE); |
| } |