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