|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 /* |
|
7 ** Netscape portable install command. |
|
8 ** |
|
9 ** Brendan Eich, 7/20/95 |
|
10 */ |
|
11 #include <stdio.h> /* OSF/1 requires this before grp.h, so put it first */ |
|
12 #include <assert.h> |
|
13 #include <fcntl.h> |
|
14 #include <grp.h> |
|
15 #include <pwd.h> |
|
16 #include <stdlib.h> |
|
17 #include <string.h> |
|
18 #include <unistd.h> |
|
19 #include <utime.h> |
|
20 #include <sys/types.h> |
|
21 #include <sys/stat.h> |
|
22 #include <dirent.h> |
|
23 #include <errno.h> |
|
24 #include <stdarg.h> |
|
25 #ifdef USE_REENTRANT_LIBC |
|
26 #include "libc_r.h" |
|
27 #endif /* USE_REENTRANT_LIBC */ |
|
28 |
|
29 #include "pathsub.h" |
|
30 |
|
31 #define HAVE_FCHMOD |
|
32 |
|
33 #if defined(BEOS) |
|
34 #undef HAVE_FCHMOD |
|
35 #endif |
|
36 |
|
37 /* |
|
38 * Does getcwd() take NULL as the first argument and malloc |
|
39 * the result buffer? |
|
40 */ |
|
41 #if !defined(DARWIN) |
|
42 #define GETCWD_CAN_MALLOC |
|
43 #endif |
|
44 |
|
45 #if defined(LINUX) || defined(__GNU__) || defined(__GLIBC__) |
|
46 #include <getopt.h> |
|
47 #endif |
|
48 |
|
49 #if defined(SCO) || defined(UNIXWARE) |
|
50 #if !defined(S_ISLNK) && defined(S_IFLNK) |
|
51 #define S_ISLNK(a) (((a) & S_IFMT) == S_IFLNK) |
|
52 #endif |
|
53 #endif |
|
54 |
|
55 #ifdef QNX |
|
56 #define d_ino d_stat.st_ino |
|
57 #endif |
|
58 |
|
59 static void |
|
60 usage(void) |
|
61 { |
|
62 fprintf(stderr, |
|
63 "usage: %s [-C cwd] [-L linkprefix] [-m mode] [-o owner] [-g group]\n" |
|
64 " %*s [-DdltR] file [file ...] directory\n", |
|
65 program, (int)strlen(program), ""); |
|
66 exit(2); |
|
67 } |
|
68 |
|
69 static int |
|
70 mkdirs(char *path, mode_t mode) |
|
71 { |
|
72 char *cp; |
|
73 struct stat sb; |
|
74 int res; |
|
75 |
|
76 while (*path == '/' && path[1] == '/') |
|
77 path++; |
|
78 for (cp = strrchr(path, '/'); cp && cp != path && cp[-1] == '/'; cp--) |
|
79 ; |
|
80 if (cp && cp != path) { |
|
81 *cp = '\0'; |
|
82 if ((stat(path, &sb) < 0 || !S_ISDIR(sb.st_mode)) && |
|
83 mkdirs(path, mode) < 0) { |
|
84 return -1; |
|
85 } |
|
86 *cp = '/'; |
|
87 } |
|
88 res = mkdir(path, mode); |
|
89 if ((res != 0) && (errno == EEXIST)) |
|
90 return 0; |
|
91 else |
|
92 return res; |
|
93 } |
|
94 |
|
95 static uid_t |
|
96 touid(char *owner) |
|
97 { |
|
98 struct passwd *pw; |
|
99 uid_t uid; |
|
100 char *cp; |
|
101 |
|
102 pw = getpwnam(owner); |
|
103 if (pw) |
|
104 return pw->pw_uid; |
|
105 uid = strtol(owner, &cp, 0); |
|
106 if (uid == 0 && cp == owner) |
|
107 fail("cannot find uid for %s", owner); |
|
108 return uid; |
|
109 } |
|
110 |
|
111 static gid_t |
|
112 togid(char *group) |
|
113 { |
|
114 struct group *gr; |
|
115 gid_t gid; |
|
116 char *cp; |
|
117 |
|
118 gr = getgrnam(group); |
|
119 if (gr) |
|
120 return gr->gr_gid; |
|
121 gid = strtol(group, &cp, 0); |
|
122 if (gid == 0 && cp == group) |
|
123 fail("cannot find gid for %s", group); |
|
124 return gid; |
|
125 } |
|
126 |
|
127 int |
|
128 main(int argc, char **argv) |
|
129 { |
|
130 int onlydir, dodir, dolink, dorelsymlink, dotimes, opt, len, lplen, tdlen, bnlen, exists, fromfd, tofd, cc, wc; |
|
131 mode_t mode = 0755; |
|
132 char *linkprefix, *owner, *group, *cp, *cwd, *todir, *toname, *name, *base, *linkname, *bp, buf[BUFSIZ]; |
|
133 uid_t uid; |
|
134 gid_t gid; |
|
135 struct stat sb, tosb; |
|
136 struct utimbuf utb; |
|
137 |
|
138 program = argv[0]; |
|
139 cwd = linkname = linkprefix = owner = group = 0; |
|
140 onlydir = dodir = dolink = dorelsymlink = dotimes = lplen = 0; |
|
141 |
|
142 while ((opt = getopt(argc, argv, "C:DdlL:Rm:o:g:t")) != EOF) { |
|
143 switch (opt) { |
|
144 case 'C': |
|
145 cwd = optarg; |
|
146 break; |
|
147 case 'D': |
|
148 onlydir = 1; |
|
149 break; |
|
150 case 'd': |
|
151 dodir = 1; |
|
152 break; |
|
153 case 'l': |
|
154 dolink = 1; |
|
155 break; |
|
156 case 'L': |
|
157 linkprefix = optarg; |
|
158 lplen = strlen(linkprefix); |
|
159 dolink = 1; |
|
160 break; |
|
161 case 'R': |
|
162 dolink = dorelsymlink = 1; |
|
163 break; |
|
164 case 'm': |
|
165 mode = strtoul(optarg, &cp, 8); |
|
166 if (mode == 0 && cp == optarg) |
|
167 usage(); |
|
168 break; |
|
169 case 'o': |
|
170 owner = optarg; |
|
171 break; |
|
172 case 'g': |
|
173 group = optarg; |
|
174 break; |
|
175 case 't': |
|
176 dotimes = 1; |
|
177 break; |
|
178 default: |
|
179 usage(); |
|
180 } |
|
181 } |
|
182 |
|
183 argc -= optind; |
|
184 argv += optind; |
|
185 if (argc < 2 - onlydir) |
|
186 usage(); |
|
187 |
|
188 todir = argv[argc-1]; |
|
189 if ((stat(todir, &sb) < 0 || !S_ISDIR(sb.st_mode)) && |
|
190 mkdirs(todir, 0777) < 0) { |
|
191 fail("cannot make directory %s", todir); |
|
192 } |
|
193 if (onlydir) |
|
194 return 0; |
|
195 |
|
196 if (!cwd) { |
|
197 #ifdef GETCWD_CAN_MALLOC |
|
198 cwd = getcwd(0, PATH_MAX); |
|
199 #else |
|
200 cwd = malloc(PATH_MAX + 1); |
|
201 cwd = getcwd(cwd, PATH_MAX); |
|
202 #endif |
|
203 } |
|
204 xchdir(todir); |
|
205 #ifdef GETCWD_CAN_MALLOC |
|
206 todir = getcwd(0, PATH_MAX); |
|
207 #else |
|
208 todir = malloc(PATH_MAX + 1); |
|
209 todir = getcwd(todir, PATH_MAX); |
|
210 #endif |
|
211 xchdir(cwd); |
|
212 tdlen = strlen(todir); |
|
213 |
|
214 uid = owner ? touid(owner) : -1; |
|
215 gid = group ? togid(group) : -1; |
|
216 |
|
217 while (--argc > 0) { |
|
218 name = *argv++; |
|
219 len = strlen(name); |
|
220 base = xbasename(name); |
|
221 bnlen = strlen(base); |
|
222 toname = (char*)xmalloc(tdlen + 1 + bnlen + 1); |
|
223 sprintf(toname, "%s/%s", todir, base); |
|
224 exists = (lstat(toname, &tosb) == 0); |
|
225 |
|
226 if (dodir) { |
|
227 /* -d means create a directory, always */ |
|
228 if (exists && !S_ISDIR(tosb.st_mode)) { |
|
229 (void) unlink(toname); |
|
230 exists = 0; |
|
231 } |
|
232 if (!exists && mkdir(toname, mode) < 0) |
|
233 fail("cannot make directory %s", toname); |
|
234 if ((owner || group) && chown(toname, uid, gid) < 0) |
|
235 fail("cannot change owner of %s", toname); |
|
236 } else if (dolink) { |
|
237 if (*name == '/') { |
|
238 /* source is absolute pathname, link to it directly */ |
|
239 linkname = 0; |
|
240 } else { |
|
241 if (linkprefix) { |
|
242 /* -L implies -l and prefixes names with a $cwd arg. */ |
|
243 len += lplen + 1; |
|
244 linkname = (char*)xmalloc(len + 1); |
|
245 sprintf(linkname, "%s/%s", linkprefix, name); |
|
246 } else if (dorelsymlink) { |
|
247 /* Symlink the relative path from todir to source name. */ |
|
248 linkname = (char*)xmalloc(PATH_MAX); |
|
249 |
|
250 if (*todir == '/') { |
|
251 /* todir is absolute: skip over common prefix. */ |
|
252 lplen = relatepaths(todir, cwd, linkname); |
|
253 strcpy(linkname + lplen, name); |
|
254 } else { |
|
255 /* todir is named by a relative path: reverse it. */ |
|
256 reversepath(todir, name, len, linkname); |
|
257 xchdir(cwd); |
|
258 } |
|
259 |
|
260 len = strlen(linkname); |
|
261 } |
|
262 name = linkname; |
|
263 } |
|
264 |
|
265 /* Check for a pre-existing symlink with identical content. */ |
|
266 if (exists && |
|
267 (!S_ISLNK(tosb.st_mode) || |
|
268 readlink(toname, buf, sizeof buf) != len || |
|
269 strncmp(buf, name, len) != 0)) { |
|
270 (void) (S_ISDIR(tosb.st_mode) ? rmdir : unlink)(toname); |
|
271 exists = 0; |
|
272 } |
|
273 if (!exists && symlink(name, toname) < 0) |
|
274 fail("cannot make symbolic link %s", toname); |
|
275 #ifdef HAVE_LCHOWN |
|
276 if ((owner || group) && lchown(toname, uid, gid) < 0) |
|
277 fail("cannot change owner of %s", toname); |
|
278 #endif |
|
279 |
|
280 if (linkname) { |
|
281 free(linkname); |
|
282 linkname = 0; |
|
283 } |
|
284 } else { |
|
285 /* Copy from name to toname, which might be the same file. */ |
|
286 fromfd = open(name, O_RDONLY); |
|
287 if (fromfd < 0 || fstat(fromfd, &sb) < 0) |
|
288 fail("cannot access %s", name); |
|
289 if (exists && (!S_ISREG(tosb.st_mode) || access(toname, W_OK) < 0)) |
|
290 (void) (S_ISDIR(tosb.st_mode) ? rmdir : unlink)(toname); |
|
291 tofd = open(toname, O_CREAT | O_WRONLY, 0666); |
|
292 if (tofd < 0) |
|
293 fail("cannot create %s", toname); |
|
294 |
|
295 bp = buf; |
|
296 while ((cc = read(fromfd, bp, sizeof buf)) > 0) { |
|
297 while ((wc = write(tofd, bp, cc)) > 0) { |
|
298 if ((cc -= wc) == 0) |
|
299 break; |
|
300 bp += wc; |
|
301 } |
|
302 if (wc < 0) |
|
303 fail("cannot write to %s", toname); |
|
304 } |
|
305 if (cc < 0) |
|
306 fail("cannot read from %s", name); |
|
307 |
|
308 if (ftruncate(tofd, sb.st_size) < 0) |
|
309 fail("cannot truncate %s", toname); |
|
310 if (dotimes) { |
|
311 utb.actime = sb.st_atime; |
|
312 utb.modtime = sb.st_mtime; |
|
313 if (utime(toname, &utb) < 0) |
|
314 fail("cannot set times of %s", toname); |
|
315 } |
|
316 #ifdef HAVE_FCHMOD |
|
317 if (fchmod(tofd, mode) < 0) |
|
318 #else |
|
319 if (chmod(toname, mode) < 0) |
|
320 #endif |
|
321 fail("cannot change mode of %s", toname); |
|
322 if ((owner || group) && fchown(tofd, uid, gid) < 0) |
|
323 fail("cannot change owner of %s", toname); |
|
324 |
|
325 /* Must check for delayed (NFS) write errors on close. */ |
|
326 if (close(tofd) < 0) |
|
327 fail("cannot write to %s", toname); |
|
328 close(fromfd); |
|
329 } |
|
330 |
|
331 free(toname); |
|
332 } |
|
333 |
|
334 free(cwd); |
|
335 free(todir); |
|
336 return 0; |
|
337 } |
|
338 |
|
339 /* |
|
340 ** Pathname subroutines. |
|
341 ** |
|
342 ** Brendan Eich, 8/29/95 |
|
343 */ |
|
344 |
|
345 char *program; |
|
346 |
|
347 void |
|
348 fail(char *format, ...) |
|
349 { |
|
350 int error; |
|
351 va_list ap; |
|
352 |
|
353 #ifdef USE_REENTRANT_LIBC |
|
354 R_STRERROR_INIT_R(); |
|
355 #endif |
|
356 |
|
357 error = errno; |
|
358 fprintf(stderr, "%s: ", program); |
|
359 va_start(ap, format); |
|
360 vfprintf(stderr, format, ap); |
|
361 va_end(ap); |
|
362 if (error) |
|
363 |
|
364 #ifdef USE_REENTRANT_LIBC |
|
365 R_STRERROR_R(errno); |
|
366 fprintf(stderr, ": %s", r_strerror_r); |
|
367 #else |
|
368 fprintf(stderr, ": %s", strerror(errno)); |
|
369 #endif |
|
370 |
|
371 putc('\n', stderr); |
|
372 exit(1); |
|
373 } |
|
374 |
|
375 char * |
|
376 getcomponent(char *path, char *name) |
|
377 { |
|
378 if (*path == '\0') |
|
379 return 0; |
|
380 if (*path == '/') { |
|
381 *name++ = '/'; |
|
382 } else { |
|
383 do { |
|
384 *name++ = *path++; |
|
385 } while (*path != '/' && *path != '\0'); |
|
386 } |
|
387 *name = '\0'; |
|
388 while (*path == '/') |
|
389 path++; |
|
390 return path; |
|
391 } |
|
392 |
|
393 #ifdef UNIXWARE_READDIR_BUFFER_TOO_SMALL |
|
394 /* Sigh. The static buffer in Unixware's readdir is too small. */ |
|
395 struct dirent * readdir(DIR *d) |
|
396 { |
|
397 static struct dirent *buf = NULL; |
|
398 #define MAX_PATH_LEN 1024 |
|
399 |
|
400 |
|
401 if(buf == NULL) |
|
402 buf = (struct dirent *) malloc(sizeof(struct dirent) + MAX_PATH_LEN) |
|
403 ; |
|
404 return(readdir_r(d, buf)); |
|
405 } |
|
406 #endif |
|
407 |
|
408 char * |
|
409 ino2name(ino_t ino, char *dir) |
|
410 { |
|
411 DIR *dp; |
|
412 struct dirent *ep; |
|
413 char *name; |
|
414 |
|
415 dp = opendir(".."); |
|
416 if (!dp) |
|
417 fail("cannot read parent directory"); |
|
418 for (;;) { |
|
419 if (!(ep = readdir(dp))) |
|
420 fail("cannot find current directory"); |
|
421 if (ep->d_ino == ino) |
|
422 break; |
|
423 } |
|
424 name = xstrdup(ep->d_name); |
|
425 closedir(dp); |
|
426 return name; |
|
427 } |
|
428 |
|
429 void * |
|
430 xmalloc(size_t size) |
|
431 { |
|
432 void *p = malloc(size); |
|
433 if (!p) |
|
434 fail("cannot allocate %u bytes", size); |
|
435 return p; |
|
436 } |
|
437 |
|
438 char * |
|
439 xstrdup(char *s) |
|
440 { |
|
441 return strcpy((char*)xmalloc(strlen(s) + 1), s); |
|
442 } |
|
443 |
|
444 char * |
|
445 xbasename(char *path) |
|
446 { |
|
447 char *cp; |
|
448 |
|
449 while ((cp = strrchr(path, '/')) && cp[1] == '\0') |
|
450 *cp = '\0'; |
|
451 if (!cp) return path; |
|
452 return cp + 1; |
|
453 } |
|
454 |
|
455 void |
|
456 xchdir(char *dir) |
|
457 { |
|
458 if (chdir(dir) < 0) |
|
459 fail("cannot change directory to %s", dir); |
|
460 } |
|
461 |
|
462 int |
|
463 relatepaths(char *from, char *to, char *outpath) |
|
464 { |
|
465 char *cp, *cp2; |
|
466 int len; |
|
467 char buf[NAME_MAX]; |
|
468 |
|
469 assert(*from == '/' && *to == '/'); |
|
470 for (cp = to, cp2 = from; *cp == *cp2; cp++, cp2++) |
|
471 if (*cp == '\0') |
|
472 break; |
|
473 while (cp[-1] != '/') |
|
474 cp--, cp2--; |
|
475 if (cp - 1 == to) { |
|
476 /* closest common ancestor is /, so use full pathname */ |
|
477 len = strlen(strcpy(outpath, to)); |
|
478 if (outpath[len] != '/') { |
|
479 outpath[len++] = '/'; |
|
480 outpath[len] = '\0'; |
|
481 } |
|
482 } else { |
|
483 len = 0; |
|
484 while ((cp2 = getcomponent(cp2, buf)) != 0) { |
|
485 strcpy(outpath + len, "../"); |
|
486 len += 3; |
|
487 } |
|
488 while ((cp = getcomponent(cp, buf)) != 0) { |
|
489 sprintf(outpath + len, "%s/", buf); |
|
490 len += strlen(outpath + len); |
|
491 } |
|
492 } |
|
493 return len; |
|
494 } |
|
495 |
|
496 void |
|
497 reversepath(char *inpath, char *name, int len, char *outpath) |
|
498 { |
|
499 char *cp, *cp2; |
|
500 char buf[NAME_MAX]; |
|
501 struct stat sb; |
|
502 |
|
503 cp = strcpy(outpath + PATH_MAX - (len + 1), name); |
|
504 cp2 = inpath; |
|
505 while ((cp2 = getcomponent(cp2, buf)) != 0) { |
|
506 if (strcmp(buf, ".") == 0) |
|
507 continue; |
|
508 if (strcmp(buf, "..") == 0) { |
|
509 if (stat(".", &sb) < 0) |
|
510 fail("cannot stat current directory"); |
|
511 name = ino2name(sb.st_ino, ".."); |
|
512 len = strlen(name); |
|
513 cp -= len + 1; |
|
514 strcpy(cp, name); |
|
515 cp[len] = '/'; |
|
516 free(name); |
|
517 xchdir(".."); |
|
518 } else { |
|
519 cp -= 3; |
|
520 strncpy(cp, "../", 3); |
|
521 xchdir(buf); |
|
522 } |
|
523 } |
|
524 strcpy(outpath, cp); |
|
525 } |