blob: 831f9b802924c1aa93df97905c954e8f8a0fe854
1 | /* vi: set sw=4 ts=4: */ |
2 | /* |
3 | * Copyright (C) 2003 by Glenn McGrath |
4 | * SELinux support: by Yuichi Nakamura <ynakam@hitachisoft.jp> |
5 | * |
6 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. |
7 | */ |
8 | //config:config INSTALL |
9 | //config: bool "install" |
10 | //config: default y |
11 | //config: help |
12 | //config: Copy files and set attributes. |
13 | //config: |
14 | //config:config FEATURE_INSTALL_LONG_OPTIONS |
15 | //config: bool "Enable long options" |
16 | //config: default y |
17 | //config: depends on INSTALL && LONG_OPTS |
18 | //config: help |
19 | //config: Support long options for the install applet. |
20 | |
21 | //applet:IF_INSTALL(APPLET(install, BB_DIR_USR_BIN, BB_SUID_DROP)) |
22 | |
23 | //kbuild:lib-$(CONFIG_INSTALL) += install.o |
24 | |
25 | /* -v, -b, -c are ignored */ |
26 | //usage:#define install_trivial_usage |
27 | //usage: "[-cdDsp] [-o USER] [-g GRP] [-m MODE] [-t DIR] [SOURCE]... DEST" |
28 | //usage:#define install_full_usage "\n\n" |
29 | //usage: "Copy files and set attributes\n" |
30 | //usage: "\n -c Just copy (default)" |
31 | //usage: "\n -d Create directories" |
32 | //usage: "\n -D Create leading target directories" |
33 | //usage: "\n -s Strip symbol table" |
34 | //usage: "\n -p Preserve date" |
35 | //usage: "\n -o USER Set ownership" |
36 | //usage: "\n -g GRP Set group ownership" |
37 | //usage: "\n -m MODE Set permissions" |
38 | //usage: "\n -t DIR Install to DIR" |
39 | //usage: IF_SELINUX( |
40 | //usage: "\n -Z Set security context" |
41 | //usage: ) |
42 | |
43 | #include "libbb.h" |
44 | #include "libcoreutils/coreutils.h" |
45 | |
46 | #if ENABLE_FEATURE_INSTALL_LONG_OPTIONS |
47 | static const char install_longopts[] ALIGN1 = |
48 | IF_FEATURE_VERBOSE( |
49 | "verbose\0" No_argument "v" |
50 | ) |
51 | "directory\0" No_argument "d" |
52 | "preserve-timestamps\0" No_argument "p" |
53 | "strip\0" No_argument "s" |
54 | "group\0" Required_argument "g" |
55 | "mode\0" Required_argument "m" |
56 | "owner\0" Required_argument "o" |
57 | "target-directory\0" Required_argument "t" |
58 | /* autofs build insists of using -b --suffix=.orig */ |
59 | /* TODO? (short option for --suffix is -S) */ |
60 | #if ENABLE_SELINUX |
61 | "context\0" Required_argument "Z" |
62 | "preserve_context\0" No_argument "\xff" |
63 | "preserve-context\0" No_argument "\xff" |
64 | #endif |
65 | ; |
66 | #endif |
67 | |
68 | |
69 | #if ENABLE_SELINUX |
70 | static void setdefaultfilecon(const char *path) |
71 | { |
72 | struct stat s; |
73 | security_context_t scontext = NULL; |
74 | |
75 | if (!is_selinux_enabled()) { |
76 | return; |
77 | } |
78 | if (lstat(path, &s) != 0) { |
79 | return; |
80 | } |
81 | |
82 | if (matchpathcon(path, s.st_mode, &scontext) < 0) { |
83 | goto out; |
84 | } |
85 | if (strcmp(scontext, "<<none>>") == 0) { |
86 | goto out; |
87 | } |
88 | |
89 | if (lsetfilecon(path, scontext) < 0) { |
90 | if (errno != ENOTSUP) { |
91 | bb_perror_msg("warning: can't change context" |
92 | " of %s to %s", path, scontext); |
93 | } |
94 | } |
95 | |
96 | out: |
97 | freecon(scontext); |
98 | } |
99 | |
100 | #endif |
101 | |
102 | int install_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
103 | int install_main(int argc, char **argv) |
104 | { |
105 | struct stat statbuf; |
106 | mode_t mode; |
107 | uid_t uid; |
108 | gid_t gid; |
109 | char *arg, *last; |
110 | const char *gid_str; |
111 | const char *uid_str; |
112 | const char *mode_str; |
113 | int mkdir_flags = FILEUTILS_RECUR; |
114 | int copy_flags = FILEUTILS_DEREFERENCE | FILEUTILS_FORCE; |
115 | int opts; |
116 | int ret = EXIT_SUCCESS; |
117 | int isdir; |
118 | #if ENABLE_SELINUX |
119 | security_context_t scontext; |
120 | bool use_default_selinux_context = 1; |
121 | #endif |
122 | enum { |
123 | OPT_c = 1 << 0, |
124 | OPT_v = 1 << 1, |
125 | OPT_b = 1 << 2, |
126 | OPT_MKDIR_LEADING = 1 << 3, |
127 | OPT_DIRECTORY = 1 << 4, |
128 | OPT_PRESERVE_TIME = 1 << 5, |
129 | OPT_STRIP = 1 << 6, |
130 | OPT_GROUP = 1 << 7, |
131 | OPT_MODE = 1 << 8, |
132 | OPT_OWNER = 1 << 9, |
133 | OPT_TARGET = 1 << 10, |
134 | #if ENABLE_SELINUX |
135 | OPT_SET_SECURITY_CONTEXT = 1 << 11, |
136 | OPT_PRESERVE_SECURITY_CONTEXT = 1 << 12, |
137 | #endif |
138 | }; |
139 | |
140 | #if ENABLE_FEATURE_INSTALL_LONG_OPTIONS |
141 | applet_long_options = install_longopts; |
142 | #endif |
143 | opt_complementary = "t--d:d--t:s--d:d--s" IF_FEATURE_INSTALL_LONG_OPTIONS(IF_SELINUX(":Z--\xff:\xff--Z")); |
144 | /* -c exists for backwards compatibility, it's needed */ |
145 | /* -b is ignored ("make a backup of each existing destination file") */ |
146 | opts = getopt32(argv, "cvb" "Ddpsg:m:o:t:" IF_SELINUX("Z:"), |
147 | &gid_str, &mode_str, &uid_str, &last |
148 | IF_SELINUX(, &scontext)); |
149 | argc -= optind; |
150 | argv += optind; |
151 | |
152 | #if ENABLE_SELINUX |
153 | if (opts & (OPT_PRESERVE_SECURITY_CONTEXT|OPT_SET_SECURITY_CONTEXT)) { |
154 | selinux_or_die(); |
155 | use_default_selinux_context = 0; |
156 | if (opts & OPT_PRESERVE_SECURITY_CONTEXT) { |
157 | copy_flags |= FILEUTILS_PRESERVE_SECURITY_CONTEXT; |
158 | } |
159 | if (opts & OPT_SET_SECURITY_CONTEXT) { |
160 | setfscreatecon_or_die(scontext); |
161 | copy_flags |= FILEUTILS_SET_SECURITY_CONTEXT; |
162 | } |
163 | } |
164 | #endif |
165 | |
166 | if ((opts & OPT_v) && FILEUTILS_VERBOSE) { |
167 | mkdir_flags |= FILEUTILS_VERBOSE; |
168 | copy_flags |= FILEUTILS_VERBOSE; |
169 | } |
170 | |
171 | /* preserve access and modification time, this is GNU behaviour, |
172 | * BSD only preserves modification time */ |
173 | if (opts & OPT_PRESERVE_TIME) { |
174 | copy_flags |= FILEUTILS_PRESERVE_STATUS; |
175 | } |
176 | mode = 0755; /* GNU coreutils 6.10 compat */ |
177 | if (opts & OPT_MODE) |
178 | mode = bb_parse_mode(mode_str, mode); |
179 | uid = (opts & OPT_OWNER) ? get_ug_id(uid_str, xuname2uid) : getuid(); |
180 | gid = (opts & OPT_GROUP) ? get_ug_id(gid_str, xgroup2gid) : getgid(); |
181 | |
182 | /* If -t DIR is in use, then isdir=true, last="DIR" */ |
183 | isdir = (opts & OPT_TARGET); |
184 | if (!(opts & (OPT_TARGET|OPT_DIRECTORY))) { |
185 | /* Neither -t DIR nor -d is in use */ |
186 | argc--; |
187 | last = argv[argc]; |
188 | argv[argc] = NULL; |
189 | /* coreutils install resolves link in this case, don't use lstat */ |
190 | isdir = stat(last, &statbuf) < 0 ? 0 : S_ISDIR(statbuf.st_mode); |
191 | } |
192 | |
193 | if (argc < 1) |
194 | bb_show_usage(); |
195 | |
196 | while ((arg = *argv++) != NULL) { |
197 | char *dest; |
198 | |
199 | if (opts & OPT_DIRECTORY) { |
200 | dest = arg; |
201 | /* GNU coreutils 6.9 does not set uid:gid |
202 | * on intermediate created directories |
203 | * (only on last one) */ |
204 | if (bb_make_directory(dest, 0755, mkdir_flags)) { |
205 | ret = EXIT_FAILURE; |
206 | goto next; |
207 | } |
208 | } else { |
209 | dest = last; |
210 | if (opts & OPT_MKDIR_LEADING) { |
211 | char *ddir = xstrdup(dest); |
212 | bb_make_directory(dirname(ddir), 0755, mkdir_flags); |
213 | /* errors are not checked. copy_file |
214 | * will fail if dir is not created. |
215 | */ |
216 | free(ddir); |
217 | } |
218 | if (isdir) |
219 | dest = concat_path_file(last, bb_basename(arg)); |
220 | if (copy_file(arg, dest, copy_flags) != 0) { |
221 | /* copy is not made */ |
222 | ret = EXIT_FAILURE; |
223 | goto next; |
224 | } |
225 | if (opts & OPT_STRIP) { |
226 | char *args[4]; |
227 | args[0] = (char*)"strip"; |
228 | args[1] = (char*)"-p"; /* -p --preserve-dates */ |
229 | args[2] = dest; |
230 | args[3] = NULL; |
231 | if (spawn_and_wait(args)) { |
232 | bb_perror_msg("strip"); |
233 | ret = EXIT_FAILURE; |
234 | } |
235 | } |
236 | } |
237 | |
238 | /* Set the file mode (always, not only with -m). |
239 | * GNU coreutils 6.10 is not affected by umask. */ |
240 | if (chmod(dest, mode) == -1) { |
241 | bb_perror_msg("can't change %s of %s", "permissions", dest); |
242 | ret = EXIT_FAILURE; |
243 | } |
244 | #if ENABLE_SELINUX |
245 | if (use_default_selinux_context) |
246 | setdefaultfilecon(dest); |
247 | #endif |
248 | /* Set the user and group id */ |
249 | if ((opts & (OPT_OWNER|OPT_GROUP)) |
250 | && lchown(dest, uid, gid) == -1 |
251 | ) { |
252 | bb_perror_msg("can't change %s of %s", "ownership", dest); |
253 | ret = EXIT_FAILURE; |
254 | } |
255 | next: |
256 | if (ENABLE_FEATURE_CLEAN_UP && isdir) |
257 | free(dest); |
258 | } |
259 | |
260 | return ret; |
261 | } |
262 |