1/*
2 * FreeBSD install - a package for the installation and maintenance
3 * of non-core utilities.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * Jordan K. Hubbard
15 * 18 July 1993
16 *
17 * This is the main body of the create module.
18 *
19 */
20
21#include <sys/cdefs.h>
22__FBSDID("$FreeBSD$");
23
24#include "lib.h"
25#include "create.h"
26
27#include <err.h>
28#include <libgen.h>
29#include <signal.h>
30#include <stdlib.h>
31#include <sys/types.h>
32#include <sys/stat.h>
33#include <sys/syslimits.h>
34#include <sys/wait.h>
35#include <unistd.h>
36
37static void sanity_check(void);
38static void make_dist(const char *, const char *, const char *, Package *);
39static int create_from_installed_recursive(const char *, const char *);
40static int create_from_installed(const char *, const char *, const char *);
41
42int
43pkg_perform(char **pkgs)
44{
45    static const char *home;
46    char *pkg = *pkgs;		/* Only one arg to create */
47    char *cp;
48    FILE *pkg_in, *fp;
49    Package plist;
50    int len;
51    const char *suf;
52
53    /* Preliminary setup */
54    if (InstalledPkg == NULL)
55	sanity_check();
56    if (Verbose && !PlistOnly)
57	printf("Creating package %s\n", pkg);
58
59    /* chop suffix off if already specified, remembering if we want to compress  */
60    len = strlen(pkg);
61    if (len > 4) {
62	if (!strcmp(&pkg[len - 4], ".tbz")) {
63	    Zipper = BZIP2;
64	    pkg[len - 4] = '\0';
65	}
66	else if (!strcmp(&pkg[len - 4], ".tgz")) {
67	    Zipper = GZIP;
68	    pkg[len - 4] = '\0';
69	}
70	else if (!strcmp(&pkg[len - 4], ".txz")) {
71	    Zipper = XZ;
72	    pkg[len - 4] = '\0';
73	}
74	else if (!strcmp(&pkg[len - 4], ".tar")) {
75	    Zipper = NONE;
76	    pkg[len - 4] = '\0';
77	}
78    }
79    if (Zipper == BZIP2) {
80	suf = "tbz";
81	setenv("BZIP2", "--best", 0);
82    } else if (Zipper == GZIP) {
83	suf = "tgz";
84	setenv("GZIP", "-9", 0);
85    } else if (Zipper == XZ) {
86	suf = "txz";
87    } else
88	suf = "tar";
89
90    if (InstalledPkg != NULL) {
91	char *pkgglob[] = { InstalledPkg, NULL };
92	char **matched, **pkgs;
93	int i, error;
94
95	pkgs = pkgglob;
96	if (MatchType != MATCH_EXACT) {
97		matched = matchinstalled(MatchType, pkgs, &error);
98		if (!error && matched != NULL)
99			pkgs = matched;
100		else if (MatchType != MATCH_GLOB)
101	    		errx(1, "no packages match pattern");
102	}
103	/*
104	 * Is there is only one installed package matching the pattern,
105	 * we need to respect the optional pkg-filename parameter.  If,
106	 * however, the pattern matches several packages, this parameter
107	 * makes no sense and is ignored.
108	 */
109	if (pkgs[1] == NULL) {
110	    if (pkg == InstalledPkg)
111		pkg = *pkgs;
112	    InstalledPkg = *pkgs;
113	    if (!Recursive)
114		return (create_from_installed(InstalledPkg, pkg, suf));
115	    return (create_from_installed_recursive(pkg, suf));
116	}
117	for (i = 0; pkgs[i] != NULL; i++) {
118	    InstalledPkg = pkg = pkgs[i];
119	    if (!Recursive)
120		create_from_installed(pkg, pkg, suf);
121	    else
122	        create_from_installed_recursive(pkg, suf);
123	}
124	return TRUE;
125    }
126
127    get_dash_string(&Comment);
128    get_dash_string(&Desc);
129    if (!strcmp(Contents, "-"))
130	pkg_in = stdin;
131    else {
132	pkg_in = fopen(Contents, "r");
133	if (!pkg_in) {
134	    cleanup(0);
135	    errx(2, "%s: unable to open contents file '%s' for input",
136		__func__, Contents);
137	}
138    }
139    plist.head = plist.tail = NULL;
140
141    /* Stick the dependencies, if any, at the top */
142    if (Pkgdeps) {
143	char **deps, *deporigin;
144	int i;
145	int ndeps = 0;
146
147	if (Verbose && !PlistOnly)
148	    printf("Registering depends:");
149
150	/* Count number of dependencies */
151	for (cp = Pkgdeps; cp != NULL && *cp != '\0';
152			   cp = strpbrk(++cp, " \t\n")) {
153	    ndeps++;
154	}
155
156	if (ndeps != 0) {
157	    /* Create easy to use NULL-terminated list */
158	    deps = alloca(sizeof(*deps) * ndeps + 1);
159	    if (deps == NULL) {
160		errx(2, "%s: alloca() failed", __func__);
161		/* Not reached */
162	    }
163	    for (i = 0; Pkgdeps;) {
164		cp = strsep(&Pkgdeps, " \t\n");
165		if (*cp) {
166		    deps[i] = cp;
167		    i++;
168		}
169	    }
170	    ndeps = i;
171	    deps[ndeps] = NULL;
172
173	    sortdeps(deps);
174	    for (i = 0; i < ndeps; i++) {
175		deporigin = strchr(deps[i], ':');
176		if (deporigin != NULL) {
177		    *deporigin = '\0';
178		    add_plist_top(&plist, PLIST_DEPORIGIN, ++deporigin);
179		}
180		add_plist_top(&plist, PLIST_PKGDEP, deps[i]);
181		if (Verbose && !PlistOnly)
182		    printf(" %s", deps[i]);
183	    }
184	}
185
186	if (Verbose && !PlistOnly)
187	    printf(".\n");
188    }
189
190    /* Put the conflicts directly after the dependencies, if any */
191    if (Conflicts) {
192	if (Verbose && !PlistOnly)
193	    printf("Registering conflicts:");
194	while (Conflicts) {
195	   cp = strsep(&Conflicts, " \t\n");
196	   if (*cp) {
197		add_plist(&plist, PLIST_CONFLICTS, cp);
198		if (Verbose && !PlistOnly)
199		    printf(" %s", cp);
200	   }
201	}
202	if (Verbose && !PlistOnly)
203	    printf(".\n");
204    }
205
206    /* If a SrcDir override is set, add it now */
207    if (SrcDir) {
208	if (Verbose && !PlistOnly)
209	    printf("Using SrcDir value of %s\n", SrcDir);
210	add_plist(&plist, PLIST_SRC, SrcDir);
211    }
212
213    /* Slurp in the packing list */
214    read_plist(&plist, pkg_in);
215
216    /* Prefix should add an @cwd to the packing list */
217    if (Prefix) {
218	if (Prefix[0] != '/') {
219		char resolved_prefix[PATH_MAX];
220		if (realpath(Prefix, resolved_prefix) == NULL)
221		    err(EXIT_FAILURE, "couldn't resolve path for prefix: %s", Prefix);
222		add_plist_top(&plist, PLIST_CWD, resolved_prefix);
223	} else {
224		add_plist_top(&plist, PLIST_CWD, Prefix);
225	}
226    }
227
228    /* Add the origin if asked, at the top */
229    if (Origin)
230	add_plist_top(&plist, PLIST_ORIGIN, Origin);
231
232    /*
233     * Run down the list and see if we've named it, if not stick in a name
234     * at the top.
235     */
236    if (find_plist(&plist, PLIST_NAME) == NULL)
237	add_plist_top(&plist, PLIST_NAME, basename(pkg));
238
239    if (asprintf(&cp, "PKG_FORMAT_REVISION:%d.%d", PLIST_FMT_VER_MAJOR,
240		 PLIST_FMT_VER_MINOR) == -1) {
241	errx(2, "%s: asprintf() failed", __func__);
242    }
243    add_plist_top(&plist, PLIST_COMMENT, cp);
244    free(cp);
245
246    /*
247     * We're just here for to dump out a revised plist for the FreeBSD ports
248     * hack.  It's not a real create in progress.
249     */
250    if (PlistOnly) {
251	check_list(home, &plist);
252	write_plist(&plist, stdout);
253	exit(0);
254    }
255
256    /* Make a directory to stomp around in */
257    home = make_playpen(PlayPen, 0);
258    signal(SIGINT, cleanup);
259    signal(SIGHUP, cleanup);
260
261    /* Make first "real contents" pass over it */
262    check_list(home, &plist);
263    (void) umask(022);	/*
264			 * Make sure gen'ed directories, files don't have
265			 * group or other write bits.
266			 */
267    /* copy_plist(home, &plist); */
268    /* mark_plist(&plist); */
269
270    /* Now put the release specific items in */
271    if (!Prefix) {
272	add_plist(&plist, PLIST_CWD, ".");
273    }
274    write_file(COMMENT_FNAME, Comment);
275    add_plist(&plist, PLIST_IGNORE, NULL);
276    add_plist(&plist, PLIST_FILE, COMMENT_FNAME);
277    add_cksum(&plist, plist.tail, COMMENT_FNAME);
278    write_file(DESC_FNAME, Desc);
279    add_plist(&plist, PLIST_IGNORE, NULL);
280    add_plist(&plist, PLIST_FILE, DESC_FNAME);
281    add_cksum(&plist, plist.tail, DESC_FNAME);
282
283    if (Install) {
284	copy_file(home, Install, INSTALL_FNAME);
285	add_plist(&plist, PLIST_IGNORE, NULL);
286	add_plist(&plist, PLIST_FILE, INSTALL_FNAME);
287	add_cksum(&plist, plist.tail, INSTALL_FNAME);
288    }
289    if (PostInstall) {
290	copy_file(home, PostInstall, POST_INSTALL_FNAME);
291	add_plist(&plist, PLIST_IGNORE, NULL);
292	add_plist(&plist, PLIST_FILE, POST_INSTALL_FNAME);
293	add_cksum(&plist, plist.tail, POST_INSTALL_FNAME);
294    }
295    if (DeInstall) {
296	copy_file(home, DeInstall, DEINSTALL_FNAME);
297	add_plist(&plist, PLIST_IGNORE, NULL);
298	add_plist(&plist, PLIST_FILE, DEINSTALL_FNAME);
299	add_cksum(&plist, plist.tail, DEINSTALL_FNAME);
300    }
301    if (PostDeInstall) {
302	copy_file(home, PostDeInstall, POST_DEINSTALL_FNAME);
303	add_plist(&plist, PLIST_IGNORE, NULL);
304	add_plist(&plist, PLIST_FILE, POST_DEINSTALL_FNAME);
305	add_cksum(&plist, plist.tail, POST_DEINSTALL_FNAME);
306    }
307    if (Require) {
308	copy_file(home, Require, REQUIRE_FNAME);
309	add_plist(&plist, PLIST_IGNORE, NULL);
310	add_plist(&plist, PLIST_FILE, REQUIRE_FNAME);
311	add_cksum(&plist, plist.tail, REQUIRE_FNAME);
312    }
313    if (Display) {
314	copy_file(home, Display, DISPLAY_FNAME);
315	add_plist(&plist, PLIST_IGNORE, NULL);
316	add_plist(&plist, PLIST_FILE, DISPLAY_FNAME);
317	add_cksum(&plist, plist.tail, DISPLAY_FNAME);
318	add_plist(&plist, PLIST_DISPLAY, DISPLAY_FNAME);
319    }
320    if (Mtree) {
321	copy_file(home, Mtree, MTREE_FNAME);
322	add_plist(&plist, PLIST_IGNORE, NULL);
323	add_plist(&plist, PLIST_FILE, MTREE_FNAME);
324	add_cksum(&plist, plist.tail, MTREE_FNAME);
325	add_plist(&plist, PLIST_MTREE, MTREE_FNAME);
326    }
327
328    /* Finally, write out the packing list */
329    fp = fopen(CONTENTS_FNAME, "w");
330    if (!fp) {
331	cleanup(0);
332	errx(2, "%s: can't open file %s for writing",
333	    __func__, CONTENTS_FNAME);
334    }
335    write_plist(&plist, fp);
336    if (fclose(fp)) {
337	cleanup(0);
338	errx(2, "%s: error while closing %s",
339	    __func__, CONTENTS_FNAME);
340    }
341
342    /* And stick it into a tar ball */
343    make_dist(home, pkg, suf, &plist);
344
345    /* Cleanup */
346    free(Comment);
347    free(Desc);
348    free_plist(&plist);
349    leave_playpen();
350    return TRUE;	/* Success */
351}
352
353static void
354make_dist(const char *homedir, const char *pkg, const char *suff, Package *plist)
355{
356    struct stat sb;
357    char tball[FILENAME_MAX];
358    PackingList p;
359    int ret;
360    const char *args[50];	/* Much more than enough. */
361    int nargs = 0;
362    int pipefds[2];
363    FILE *totar;
364    pid_t pid;
365    const char *cname;
366    char *prefix = NULL;
367
368
369    args[nargs++] = "tar";	/* argv[0] */
370
371    if (*pkg == '/')
372	snprintf(tball, FILENAME_MAX, "%s.%s", pkg, suff);
373    else
374	snprintf(tball, FILENAME_MAX, "%s/%s.%s", homedir, pkg, suff);
375
376    /*
377     * If the package tarball exists already, and we are running in `no
378     * clobber' mode, skip this package.
379     */
380    if (stat(tball, &sb) == 0 && Regenerate == FALSE) {
381	if (Verbose)
382	    printf("Skipping package '%s'.  It already exists.\n", tball);
383	return;
384    }
385
386    args[nargs++] = "-c";
387    args[nargs++] = "-f";
388    args[nargs++] = tball;
389    if (strchr(suff, 'z')) {	/* Compress/gzip/bzip2? */
390	if (Zipper == BZIP2) {
391	    args[nargs++] = "-j";
392	    cname = "bzip'd ";
393	}
394	else if (Zipper == XZ) {
395	    args[nargs++] = "-J";
396	    cname = "xz'd ";
397	}
398	else {
399	    args[nargs++] = "-z";
400	    cname = "gzip'd ";
401	}
402    } else {
403	cname = "";
404    }
405    if (Dereference)
406	args[nargs++] = "-h";
407    if (ExcludeFrom) {
408	args[nargs++] = "-X";
409	args[nargs++] = ExcludeFrom;
410    }
411    args[nargs++] = "-T";	/* Take filenames from file instead of args. */
412    args[nargs++] = "-";	/* Use stdin for the file. */
413    args[nargs] = NULL;
414
415    if (Verbose)
416	printf("Creating %star ball in '%s'\n", cname, tball);
417
418    /* Set up a pipe for passing the filenames, and fork off a tar process. */
419    if (pipe(pipefds) == -1) {
420	cleanup(0);
421	errx(2, "%s: cannot create pipe", __func__);
422    }
423    if ((pid = fork()) == -1) {
424	cleanup(0);
425	errx(2, "%s: cannot fork process for tar", __func__);
426    }
427    if (pid == 0) {	/* The child */
428	dup2(pipefds[0], 0);
429	close(pipefds[0]);
430	close(pipefds[1]);
431	execv("/usr/bin/tar", (char * const *)(uintptr_t)args);
432	cleanup(0);
433	errx(2, "%s: failed to execute tar command", __func__);
434    }
435
436    /* Meanwhile, back in the parent process ... */
437    close(pipefds[0]);
438    if ((totar = fdopen(pipefds[1], "w")) == NULL) {
439	cleanup(0);
440	errx(2, "%s: fdopen failed", __func__);
441    }
442
443    fprintf(totar, "%s\n", CONTENTS_FNAME);
444    fprintf(totar, "%s\n", COMMENT_FNAME);
445    fprintf(totar, "%s\n", DESC_FNAME);
446
447    if (Install)
448	fprintf(totar, "%s\n", INSTALL_FNAME);
449    if (PostInstall)
450	fprintf(totar, "%s\n", POST_INSTALL_FNAME);
451    if (DeInstall)
452	fprintf(totar, "%s\n", DEINSTALL_FNAME);
453    if (PostDeInstall)
454	fprintf(totar, "%s\n", POST_DEINSTALL_FNAME);
455    if (Require)
456	fprintf(totar, "%s\n", REQUIRE_FNAME);
457    if (Display)
458	fprintf(totar, "%s\n", DISPLAY_FNAME);
459    if (Mtree)
460	fprintf(totar, "%s\n", MTREE_FNAME);
461
462    for (p = plist->head; p; p = p->next) {
463	if (p->type == PLIST_FILE)
464	    fprintf(totar, "%s\n", p->name);
465	else if (p->type == PLIST_CWD && p->name == NULL)
466	    fprintf(totar, "-C\n%s\n", prefix);
467	else if (p->type == PLIST_CWD && BaseDir && p->name && p->name[0] == '/')
468	    fprintf(totar, "-C\n%s%s\n", BaseDir, p->name);
469	else if (p->type == PLIST_CWD || p->type == PLIST_SRC)
470	    fprintf(totar, "-C\n%s\n", p->name);
471	else if (p->type == PLIST_IGNORE)
472	     p = p->next;
473	if (p->type == PLIST_CWD && !prefix)
474	    prefix = p->name;
475
476    }
477
478    fclose(totar);
479    wait(&ret);
480    /* assume either signal or bad exit is enough for us */
481    if (ret) {
482	cleanup(0);
483	errx(2, "%s: tar command failed with code %d", __func__, ret);
484    }
485}
486
487static void
488sanity_check()
489{
490    if (!Comment) {
491	cleanup(0);
492	errx(2, "%s: required package comment string is missing (-c comment)",
493	    __func__);
494    }
495    if (!Desc) {
496	cleanup(0);
497	errx(2,	"%s: required package description string is missing (-d desc)",
498	    __func__);
499    }
500    if (!Contents) {
501	cleanup(0);
502	errx(2,	"%s: required package contents list is missing (-f [-]file)",
503	    __func__);
504    }
505}
506
507
508/* Clean up those things that would otherwise hang around */
509void
510cleanup(int sig)
511{
512    int in_cleanup = 0;
513
514    if (!in_cleanup) {
515	in_cleanup = 1;
516    	leave_playpen();
517    }
518    if (sig)
519	exit(1);
520}
521
522static int
523create_from_installed_recursive(const char *pkg, const char *suf)
524{
525    FILE *fp;
526    Package plist;
527    PackingList p;
528    char tmp[PATH_MAX];
529    int rval;
530
531    if (!create_from_installed(InstalledPkg, pkg, suf))
532	return FALSE;
533    snprintf(tmp, sizeof(tmp), "%s/%s/%s", LOG_DIR, InstalledPkg, CONTENTS_FNAME);
534    if (!fexists(tmp)) {
535	warnx("can't find package '%s' installed!", InstalledPkg);
536	return FALSE;
537    }
538    /* Suck in the contents list */
539    plist.head = plist.tail = NULL;
540    fp = fopen(tmp, "r");
541    if (!fp) {
542	warnx("unable to open %s file", tmp);
543	return FALSE;
544    }
545    read_plist(&plist, fp);
546    fclose(fp);
547    rval = TRUE;
548    for (p = plist.head; p ; p = p->next) {
549	if (p->type != PLIST_PKGDEP)
550	    continue;
551	if (Verbose)
552	    printf("Creating package %s\n", p->name);
553	if (!create_from_installed(p->name, p->name, suf)) {
554	    rval = FALSE;
555	    break;
556	}
557    }
558    free_plist(&plist);
559    return rval;
560}
561
562static int
563create_from_installed(const char *ipkg, const char *pkg, const char *suf)
564{
565    FILE *fp;
566    Package plist;
567    char homedir[MAXPATHLEN], log_dir[FILENAME_MAX];
568
569    snprintf(log_dir, sizeof(log_dir), "%s/%s", LOG_DIR, ipkg);
570    if (!fexists(log_dir)) {
571	warnx("can't find package '%s' installed!", ipkg);
572	return FALSE;
573    }
574    getcwd(homedir, sizeof(homedir));
575    if (chdir(log_dir) == FAIL) {
576	warnx("can't change directory to '%s'!", log_dir);
577	return FALSE;
578    }
579    /* Suck in the contents list */
580    plist.head = plist.tail = NULL;
581    fp = fopen(CONTENTS_FNAME, "r");
582    if (!fp) {
583	warnx("unable to open %s file", CONTENTS_FNAME);
584	return FALSE;
585    }
586    read_plist(&plist, fp);
587    fclose(fp);
588
589    Install = isfile(INSTALL_FNAME) ? (char *)INSTALL_FNAME : NULL;
590    PostInstall = isfile(POST_INSTALL_FNAME) ?
591	(char *)POST_INSTALL_FNAME : NULL;
592    DeInstall = isfile(DEINSTALL_FNAME) ? (char *)DEINSTALL_FNAME : NULL;
593    PostDeInstall = isfile(POST_DEINSTALL_FNAME) ?
594	(char *)POST_DEINSTALL_FNAME : NULL;
595    Require = isfile(REQUIRE_FNAME) ? (char *)REQUIRE_FNAME : NULL;
596    Display = isfile(DISPLAY_FNAME) ? (char *)DISPLAY_FNAME : NULL;
597    Mtree = isfile(MTREE_FNAME) ?  (char *)MTREE_FNAME : NULL;
598
599    make_dist(homedir, pkg, suf, &plist);
600
601    free_plist(&plist);
602    if (chdir(homedir) == FAIL) {
603	warnx("can't change directory to '%s'!", homedir);
604	return FALSE;
605    }
606    return TRUE;
607}
608