7 |
* Licensed under GPLv2 or later, see file LICENSE in this tarball for details. |
* Licensed under GPLv2 or later, see file LICENSE in this tarball for details. |
8 |
*/ |
*/ |
9 |
|
|
10 |
/* BB_AUDIT SUSv3 defects - unsupported options -H, -L, and -P. */ |
/* BB_AUDIT SUSv3 defects - none? */ |
11 |
/* BB_AUDIT GNU defects - unsupported long options. */ |
/* BB_AUDIT GNU defects - unsupported long options. */ |
12 |
/* http://www.opengroup.org/onlinepubs/007904975/utilities/chown.html */ |
/* http://www.opengroup.org/onlinepubs/007904975/utilities/chown.html */ |
13 |
|
|
14 |
#include "busybox.h" |
#include "libbb.h" |
15 |
|
|
16 |
static struct bb_uidgid_t ugid = { -1, -1 }; |
/* This is a NOEXEC applet. Be very careful! */ |
17 |
|
|
|
static int (*chown_func)(const char *, uid_t, gid_t) = chown; |
|
18 |
|
|
19 |
#define OPT_RECURSE (option_mask32 & 1) |
#define OPT_STR ("Rh" USE_DESKTOP("vcfLHP")) |
20 |
#define OPT_NODEREF (option_mask32 & 2) |
#define BIT_RECURSE 1 |
21 |
#define OPT_VERBOSE (USE_DESKTOP(option_mask32 & 4) SKIP_DESKTOP(0)) |
#define OPT_RECURSE (opt & 1) |
22 |
#define OPT_CHANGED (USE_DESKTOP(option_mask32 & 8) SKIP_DESKTOP(0)) |
#define OPT_NODEREF (opt & 2) |
23 |
#define OPT_QUIET (USE_DESKTOP(option_mask32 & 0x10) SKIP_DESKTOP(0)) |
#define OPT_VERBOSE (USE_DESKTOP(opt & 0x04) SKIP_DESKTOP(0)) |
24 |
#define OPT_STR ("Rh" USE_DESKTOP("vcf")) |
#define OPT_CHANGED (USE_DESKTOP(opt & 0x08) SKIP_DESKTOP(0)) |
25 |
|
#define OPT_QUIET (USE_DESKTOP(opt & 0x10) SKIP_DESKTOP(0)) |
26 |
/* TODO: |
/* POSIX options |
|
* -H if a command line argument is a symbolic link to a directory, traverse it |
|
27 |
* -L traverse every symbolic link to a directory encountered |
* -L traverse every symbolic link to a directory encountered |
28 |
|
* -H if a command line argument is a symbolic link to a directory, traverse it |
29 |
* -P do not traverse any symbolic links (default) |
* -P do not traverse any symbolic links (default) |
30 |
*/ |
* We do not conform to the following: |
31 |
|
* "Specifying more than one of -H, -L, and -P is not an error. |
32 |
|
* The last option specified shall determine the behavior of the utility." */ |
33 |
|
/* -L */ |
34 |
|
#define BIT_TRAVERSE 0x20 |
35 |
|
#define OPT_TRAVERSE (USE_DESKTOP(opt & BIT_TRAVERSE) SKIP_DESKTOP(0)) |
36 |
|
/* -H or -L */ |
37 |
|
#define BIT_TRAVERSE_TOP (0x20|0x40) |
38 |
|
#define OPT_TRAVERSE_TOP (USE_DESKTOP(opt & BIT_TRAVERSE_TOP) SKIP_DESKTOP(0)) |
39 |
|
|
40 |
|
typedef int (*chown_fptr)(const char *, uid_t, gid_t); |
41 |
|
|
42 |
|
struct param_t { |
43 |
|
struct bb_uidgid_t ugid; |
44 |
|
chown_fptr chown_func; |
45 |
|
}; |
46 |
|
|
47 |
static int fileAction(const char *fileName, struct stat *statbuf, |
static int FAST_FUNC fileAction(const char *fileName, struct stat *statbuf, |
48 |
void ATTRIBUTE_UNUSED *junk, int depth) |
void *vparam, int depth UNUSED_PARAM) |
49 |
{ |
{ |
50 |
// TODO: -H/-L/-P |
#define param (*(struct param_t*)vparam) |
51 |
// if (depth ... && S_ISLNK(statbuf->st_mode)) .... |
#define opt option_mask32 |
52 |
|
uid_t u = (param.ugid.uid == (uid_t)-1) ? statbuf->st_uid : param.ugid.uid; |
53 |
|
gid_t g = (param.ugid.gid == (gid_t)-1) ? statbuf->st_gid : param.ugid.gid; |
54 |
|
|
55 |
if (!chown_func(fileName, |
if (param.chown_func(fileName, u, g) == 0) { |
|
(ugid.uid == (uid_t)-1) ? statbuf->st_uid : ugid.uid, |
|
|
(ugid.gid == (gid_t)-1) ? statbuf->st_gid : ugid.gid) |
|
|
) { |
|
56 |
if (OPT_VERBOSE |
if (OPT_VERBOSE |
57 |
|| (OPT_CHANGED && (statbuf->st_uid != ugid.uid || statbuf->st_gid != ugid.gid)) |
|| (OPT_CHANGED && (statbuf->st_uid != u || statbuf->st_gid != g)) |
58 |
) { |
) { |
59 |
printf("changed ownership of '%s' to %u:%u\n", |
printf("changed ownership of '%s' to %u:%u\n", |
60 |
fileName, ugid.uid, ugid.gid); |
fileName, (unsigned)u, (unsigned)g); |
61 |
} |
} |
62 |
return TRUE; |
return TRUE; |
63 |
} |
} |
64 |
if (!OPT_QUIET) |
if (!OPT_QUIET) |
65 |
bb_perror_msg("%s", fileName); /* A filename can have % in it... */ |
bb_simple_perror_msg(fileName); /* A filename can have % in it... */ |
66 |
return FALSE; |
return FALSE; |
67 |
|
#undef opt |
68 |
|
#undef param |
69 |
} |
} |
70 |
|
|
71 |
int chown_main(int argc, char **argv) |
int chown_main(int argc UNUSED_PARAM, char **argv) |
72 |
{ |
{ |
|
char *groupName; |
|
73 |
int retval = EXIT_SUCCESS; |
int retval = EXIT_SUCCESS; |
74 |
|
int opt, flags; |
75 |
|
struct param_t param; |
76 |
|
|
77 |
|
param.ugid.uid = -1; |
78 |
|
param.ugid.gid = -1; |
79 |
|
param.chown_func = chown; |
80 |
|
|
81 |
opt_complementary = "-2"; |
opt_complementary = "-2"; |
82 |
getopt32(argc, argv, OPT_STR); |
opt = getopt32(argv, OPT_STR); |
83 |
argv += optind; |
argv += optind; |
84 |
|
|
85 |
if (OPT_NODEREF) chown_func = lchown; |
/* This matches coreutils behavior (almost - see below) */ |
86 |
|
if (OPT_NODEREF |
87 |
/* First, check if there is a group name here */ |
/* || (OPT_RECURSE && !OPT_TRAVERSE_TOP): */ |
88 |
groupName = strchr(*argv, '.'); /* deprecated? */ |
USE_DESKTOP( || (opt & (BIT_RECURSE|BIT_TRAVERSE_TOP)) == BIT_RECURSE) |
89 |
if (!groupName) |
) { |
90 |
groupName = strchr(*argv, ':'); |
param.chown_func = lchown; |
|
else |
|
|
*groupName = ':'; /* replace '.' with ':' */ |
|
|
|
|
|
/* First, try parsing "user[:[group]]" */ |
|
|
if (!groupName) { /* "user" */ |
|
|
ugid.uid = get_ug_id(*argv, xuname2uid); |
|
|
} else if (groupName == *argv) { /* ":group" */ |
|
|
ugid.gid = get_ug_id(groupName + 1, xgroup2gid); |
|
|
} else { |
|
|
if (!groupName[1]) /* "user:" */ |
|
|
*groupName = '\0'; |
|
|
if (!get_uidgid(&ugid, *argv, 1)) |
|
|
bb_error_msg_and_die("unknown user/group %s", *argv); |
|
91 |
} |
} |
92 |
|
|
93 |
|
flags = ACTION_DEPTHFIRST; /* match coreutils order */ |
94 |
|
if (OPT_RECURSE) |
95 |
|
flags |= ACTION_RECURSE; |
96 |
|
if (OPT_TRAVERSE_TOP) |
97 |
|
flags |= ACTION_FOLLOWLINKS_L0; /* -H/-L: follow links on depth 0 */ |
98 |
|
if (OPT_TRAVERSE) |
99 |
|
flags |= ACTION_FOLLOWLINKS; /* follow links if -L */ |
100 |
|
|
101 |
|
parse_chown_usergroup_or_die(¶m.ugid, argv[0]); |
102 |
|
|
103 |
/* Ok, ready to do the deed now */ |
/* Ok, ready to do the deed now */ |
104 |
argv++; |
argv++; |
105 |
do { |
do { |
106 |
if (!recursive_action(*argv, |
if (!recursive_action(*argv, |
107 |
OPT_RECURSE, // recurse |
flags, /* flags */ |
108 |
FALSE, // follow links: TODO: -H/-L/-P |
fileAction, /* file action */ |
109 |
FALSE, // depth first |
fileAction, /* dir action */ |
110 |
fileAction, // file action |
¶m, /* user data */ |
111 |
fileAction, // dir action |
0) /* depth */ |
|
NULL, // user data |
|
|
0) // depth |
|
112 |
) { |
) { |
113 |
retval = EXIT_FAILURE; |
retval = EXIT_FAILURE; |
114 |
} |
} |
116 |
|
|
117 |
return retval; |
return retval; |
118 |
} |
} |
119 |
|
|
120 |
|
/* |
121 |
|
Testcase. Run in empty directory. |
122 |
|
|
123 |
|
#!/bin/sh |
124 |
|
t1="/tmp/busybox chown" |
125 |
|
t2="/usr/bin/chown" |
126 |
|
create() { |
127 |
|
rm -rf $1; mkdir $1 |
128 |
|
( |
129 |
|
cd $1 || exit 1 |
130 |
|
mkdir dir dir2 |
131 |
|
>up |
132 |
|
>file |
133 |
|
>dir/file |
134 |
|
>dir2/file |
135 |
|
ln -s dir linkdir |
136 |
|
ln -s file linkfile |
137 |
|
ln -s ../up dir/linkup |
138 |
|
ln -s ../dir2 dir/linkupdir2 |
139 |
|
) |
140 |
|
chown -R 0:0 $1 |
141 |
|
} |
142 |
|
tst() { |
143 |
|
create test1 |
144 |
|
create test2 |
145 |
|
echo "[$1]" >>test1.out |
146 |
|
echo "[$1]" >>test2.out |
147 |
|
(cd test1; $t1 $1) >>test1.out 2>&1 |
148 |
|
(cd test2; $t2 $1) >>test2.out 2>&1 |
149 |
|
(cd test1; ls -lnR) >out1 |
150 |
|
(cd test2; ls -lnR) >out2 |
151 |
|
echo "chown $1" >out.diff |
152 |
|
if ! diff -u out1 out2 >>out.diff; then exit 1; fi |
153 |
|
rm out.diff |
154 |
|
} |
155 |
|
tst_for_each() { |
156 |
|
tst "$1 1:1 file" |
157 |
|
tst "$1 1:1 dir" |
158 |
|
tst "$1 1:1 linkdir" |
159 |
|
tst "$1 1:1 linkfile" |
160 |
|
} |
161 |
|
echo "If script produced 'out.diff' file, then at least one testcase failed" |
162 |
|
>test1.out |
163 |
|
>test2.out |
164 |
|
# These match coreutils 6.8: |
165 |
|
tst_for_each "-v" |
166 |
|
tst_for_each "-vR" |
167 |
|
tst_for_each "-vRP" |
168 |
|
tst_for_each "-vRL" |
169 |
|
tst_for_each "-vRH" |
170 |
|
tst_for_each "-vh" |
171 |
|
tst_for_each "-vhR" |
172 |
|
tst_for_each "-vhRP" |
173 |
|
tst_for_each "-vhRL" |
174 |
|
tst_for_each "-vhRH" |
175 |
|
# Fix `name' in coreutils output |
176 |
|
sed 's/`/'"'"'/g' -i test2.out |
177 |
|
# Compare us with coreutils output |
178 |
|
diff -u test1.out test2.out |
179 |
|
|
180 |
|
*/ |