1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 /*
6 ** Netscape portable install command.
7 */
8 #include <stdio.h> /* OSF/1 requires this before grp.h, so put it first */
9 #include <assert.h>
10 #include <fcntl.h>
11 #include <string.h>
12 #if defined(_WINDOWS)
13 #include <windows.h>
14 typedef unsigned int mode_t;
15 #else
16 #include <grp.h>
17 #include <pwd.h>
18 #include <errno.h>
19 #include <stdlib.h>
20 #include <unistd.h>
21 #include <utime.h>
22 #endif
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include "pathsub.h"
26
27 #define HAVE_LCHOWN
28
29 #if defined(AIX) || defined(BSDI) || defined(HPUX) || defined(LINUX) || defined(SUNOS4) || defined(SCO) || defined(UNIXWARE) || defined(NTO) || defined(DARWIN) || defined(BEOS) || defined(__riscos__)
30 #undef HAVE_LCHOWN
31 #endif
32
33 #define HAVE_FCHMOD
34
35 #if defined(BEOS)
36 #undef HAVE_FCHMOD
37 #endif
38
39 #ifdef LINUX
40 #include <getopt.h>
41 #endif
42
43 #if defined(SCO) || defined(UNIXWARE) || defined(SNI) || defined(NCR) || defined(NEC)
44 #if !defined(S_ISLNK) && defined(S_IFLNK)
45 #define S_ISLNK(a) (((a) & S_IFMT) == S_IFLNK)
46 #endif
47 #endif
48
49 #if defined(SNI)
50 extern int fchmod(int fildes, mode_t mode);
51 #endif
52
53
54 #ifdef GETCWD_CANT_MALLOC
55 /*
56 * this should probably go into a utility library in case other applications
57 * need it.
58 */
59 static char *
getcwd_do_malloc(char * path,int len)60 getcwd_do_malloc(char *path, int len) {
61
62 if (!path) {
63 path = malloc(PATH_MAX +1);
64 if (!path) return NULL;
65 }
66 return getcwd(path, PATH_MAX);
67 }
68 #define GETCWD getcwd_do_malloc
69 #else
70 #define GETCWD getcwd
71 #endif
72
73
74 static void
usage(void)75 usage(void)
76 {
77 fprintf(stderr,
78 "usage: %s [-C cwd] [-L linkprefix] [-m mode] [-o owner] [-g group]\n"
79 " %*s [-DdltR] file [file ...] directory\n",
80 program, (int)strlen(program), "");
81 exit(2);
82 }
83
84 /* this is more-or-less equivalent to mkdir -p */
85 static int
mkdirs(char * path,mode_t mode)86 mkdirs(char *path, mode_t mode)
87 {
88 char * cp;
89 int rv;
90 struct stat sb;
91
92 if (!path || !path[0])
93 fail("Null pointer or empty string passed to mkdirs()");
94 while (*path == '/' && path[1] == '/')
95 path++;
96 for (cp = strrchr(path, '/'); cp && cp != path && *(cp - 1) == '/'; cp--);
97 if (cp && cp != path) {
98 *cp = '\0';
99 if ((stat(path, &sb) < 0 || !S_ISDIR(sb.st_mode)) &&
100 mkdirs(path, mode) < 0) {
101 return -1;
102 }
103 *cp = '/';
104 }
105 rv = mkdir(path, mode);
106 if (rv) {
107 if (errno != EEXIST)
108 fail("mkdirs cannot make %s", path);
109 fprintf(stderr, "directory creation race: %s\n", path);
110 if (!stat(path, &sb) && S_ISDIR(sb.st_mode))
111 rv = 0;
112 }
113 return rv;
114 }
115
116 static uid_t
touid(char * owner)117 touid(char *owner)
118 {
119 struct passwd *pw;
120 uid_t uid;
121 char *cp;
122
123 if (!owner || !owner[0])
124 fail("Null pointer or empty string passed to touid()");
125 pw = getpwnam(owner);
126 if (pw)
127 return pw->pw_uid;
128 uid = strtol(owner, &cp, 0);
129 if (uid == 0 && cp == owner)
130 fail("cannot find uid for %s", owner);
131 return uid;
132 }
133
134 static gid_t
togid(char * group)135 togid(char *group)
136 {
137 struct group *gr;
138 gid_t gid;
139 char *cp;
140
141 if (!group || !group[0])
142 fail("Null pointer or empty string passed to togid()");
143 gr = getgrnam(group);
144 if (gr)
145 return gr->gr_gid;
146 gid = strtol(group, &cp, 0);
147 if (gid == 0 && cp == group)
148 fail("cannot find gid for %s", group);
149 return gid;
150 }
151
152 void * const uninit = (void *)0xdeadbeef;
153
154 int
main(int argc,char ** argv)155 main(int argc, char **argv)
156 {
157 char * base = uninit;
158 char * bp = uninit;
159 char * cp = uninit;
160 char * cwd = 0;
161 char * group = 0;
162 char * linkname = 0;
163 char * linkprefix = 0;
164 char * name = uninit;
165 char * owner = 0;
166 char * todir = uninit;
167 char * toname = uninit;
168
169 int bnlen = -1;
170 int cc = 0;
171 int dodir = 0;
172 int dolink = 0;
173 int dorelsymlink = 0;
174 int dotimes = 0;
175 int exists = 0;
176 int fromfd = -1;
177 int len = -1;
178 int lplen = 0;
179 int onlydir = 0;
180 int opt = -1;
181 int tdlen = -1;
182 int tofd = -1;
183 int wc = -1;
184
185 mode_t mode = 0755;
186
187 uid_t uid = -1;
188 gid_t gid = -1;
189
190 struct stat sb;
191 struct stat tosb;
192 struct utimbuf utb;
193 char buf[BUFSIZ];
194
195 program = strrchr(argv[0], '/');
196 if (!program)
197 program = strrchr(argv[0], '\\');
198 program = program ? program+1 : argv[0];
199
200
201 while ((opt = getopt(argc, argv, "C:DdlL:Rm:o:g:t")) != EOF) {
202 switch (opt) {
203 case 'C': cwd = optarg; break;
204 case 'D': onlydir = 1; break;
205 case 'd': dodir = 1; break;
206 case 'l': dolink = 1; break;
207 case 'L':
208 linkprefix = optarg;
209 lplen = strlen(linkprefix);
210 dolink = 1;
211 break;
212 case 'R': dolink = dorelsymlink = 1; break;
213 case 'm':
214 mode = strtoul(optarg, &cp, 8);
215 if (mode == 0 && cp == optarg)
216 usage();
217 break;
218 case 'o': owner = optarg; break;
219 case 'g': group = optarg; break;
220 case 't': dotimes = 1; break;
221 default: usage();
222 }
223 }
224
225 argc -= optind;
226 argv += optind;
227 if (argc < 2 - onlydir)
228 usage();
229
230 todir = argv[argc-1];
231 if ((stat(todir, &sb) < 0 || !S_ISDIR(sb.st_mode)) &&
232 mkdirs(todir, 0777) < 0) {
233 fail("cannot mkdir -p %s", todir);
234 }
235 if (onlydir)
236 return 0;
237
238 if (!cwd) {
239 cwd = GETCWD(0, PATH_MAX);
240 if (!cwd)
241 fail("could not get CWD");
242 }
243
244 /* make sure we can get into todir. */
245 xchdir(todir);
246 todir = GETCWD(0, PATH_MAX);
247 if (!todir)
248 fail("could not get CWD in todir");
249 tdlen = strlen(todir);
250
251 /* back to original directory. */
252 xchdir(cwd);
253
254 uid = owner ? touid(owner) : -1;
255 gid = group ? togid(group) : -1;
256
257 while (--argc > 0) {
258 name = *argv++;
259 len = strlen(name);
260 base = xbasename(name);
261 bnlen = strlen(base);
262 toname = (char*)xmalloc(tdlen + 1 + bnlen + 1);
263 sprintf(toname, "%s/%s", todir, base);
264 retry:
265 exists = (lstat(toname, &tosb) == 0);
266
267 if (dodir) {
268 /* -d means create a directory, always */
269 if (exists && !S_ISDIR(tosb.st_mode)) {
270 int rv = unlink(toname);
271 if (rv)
272 fail("cannot unlink %s", toname);
273 exists = 0;
274 }
275 if (!exists && mkdir(toname, mode) < 0) {
276 /* we probably have two nsinstall programs in a race here. */
277 if (errno == EEXIST && !stat(toname, &sb) &&
278 S_ISDIR(sb.st_mode)) {
279 fprintf(stderr, "directory creation race: %s\n", toname);
280 goto retry;
281 }
282 fail("cannot make directory %s", toname);
283 }
284 if ((owner || group) && chown(toname, uid, gid) < 0)
285 fail("cannot change owner of %s", toname);
286 } else if (dolink) {
287 if (*name == '/') {
288 /* source is absolute pathname, link to it directly */
289 linkname = 0;
290 } else {
291 if (linkprefix) {
292 /* -L implies -l and prefixes names with a $cwd arg. */
293 len += lplen + 1;
294 linkname = (char*)xmalloc(len + 1);
295 sprintf(linkname, "%s/%s", linkprefix, name);
296 } else if (dorelsymlink) {
297 /* Symlink the relative path from todir to source name. */
298 linkname = (char*)xmalloc(PATH_MAX);
299
300 if (*todir == '/') {
301 /* todir is absolute: skip over common prefix. */
302 lplen = relatepaths(todir, cwd, linkname);
303 strcpy(linkname + lplen, name);
304 } else {
305 /* todir is named by a relative path: reverse it. */
306 reversepath(todir, name, len, linkname);
307 xchdir(cwd);
308 }
309
310 len = strlen(linkname);
311 }
312 name = linkname;
313 }
314
315 /* Check for a pre-existing symlink with identical content. */
316 if (exists &&
317 (!S_ISLNK(tosb.st_mode) ||
318 readlink(toname, buf, sizeof buf) != len ||
319 strncmp(buf, name, len) != 0)) {
320 int rmrv;
321 rmrv = (S_ISDIR(tosb.st_mode) ? rmdir : unlink)(toname);
322 if (rmrv < 0) {
323 fail("destination exists, cannot remove %s", toname);
324 }
325 exists = 0;
326 }
327 if (!exists && symlink(name, toname) < 0) {
328 if (errno == EEXIST) {
329 fprintf(stderr, "symlink creation race: %s\n", toname);
330 fail("symlink was attempted in working directory %s "
331 "from %s to %s.\n", cwd, name, toname);
332 goto retry;
333 }
334 diagnosePath(toname);
335 fail("cannot make symbolic link %s", toname);
336 }
337 #ifdef HAVE_LCHOWN
338 if ((owner || group) && lchown(toname, uid, gid) < 0)
339 fail("cannot change owner of %s", toname);
340 #endif
341
342 if (linkname) {
343 free(linkname);
344 linkname = 0;
345 }
346 } else {
347 /* Copy from name to toname, which might be the same file. */
348 fromfd = open(name, O_RDONLY);
349 if (fromfd < 0 || fstat(fromfd, &sb) < 0)
350 fail("cannot access %s", name);
351 if (exists &&
352 (!S_ISREG(tosb.st_mode) || access(toname, W_OK) < 0)) {
353 int rmrv;
354 rmrv = (S_ISDIR(tosb.st_mode) ? rmdir : unlink)(toname);
355 if (rmrv < 0) {
356 fail("destination exists, cannot remove %s", toname);
357 }
358 }
359 tofd = open(toname, O_CREAT | O_WRONLY, 0666);
360 if (tofd < 0)
361 fail("cannot create %s", toname);
362
363 bp = buf;
364 while ((cc = read(fromfd, bp, sizeof buf)) > 0) {
365 while ((wc = write(tofd, bp, cc)) > 0) {
366 if ((cc -= wc) == 0)
367 break;
368 bp += wc;
369 }
370 if (wc < 0)
371 fail("cannot write to %s", toname);
372 }
373 if (cc < 0)
374 fail("cannot read from %s", name);
375
376 if (ftruncate(tofd, sb.st_size) < 0)
377 fail("cannot truncate %s", toname);
378 if (dotimes) {
379 utb.actime = sb.st_atime;
380 utb.modtime = sb.st_mtime;
381 if (utime(toname, &utb) < 0)
382 fail("cannot set times of %s", toname);
383 }
384 #ifdef HAVE_FCHMOD
385 if (fchmod(tofd, mode) < 0)
386 #else
387 if (chmod(toname, mode) < 0)
388 #endif
389 fail("cannot change mode of %s", toname);
390
391 if ((owner || group) && fchown(tofd, uid, gid) < 0)
392 fail("cannot change owner of %s", toname);
393
394 /* Must check for delayed (NFS) write errors on close. */
395 if (close(tofd) < 0)
396 fail("close reports write error on %s", toname);
397 close(fromfd);
398 }
399
400 free(toname);
401 }
402
403 free(cwd);
404 free(todir);
405 return 0;
406 }
407
408