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