/ src / create.c
create.c
  1  /*-
  2   * Copyright (c) 2011-2025 Baptiste Daroussin <bapt@FreeBSD.org>
  3   * Copyright (c) 2011-2012 Julien Laffaye <jlaffaye@FreeBSD.org>
  4   * Copyright (c) 2011 Will Andrews <will@FreeBSD.org>
  5   * Copyright (c) 2015 Matthew Seaman <matthew@FreeBSD.org>
  6   *
  7   * SPDX-License-Identifier: BSD-2-Clause
  8   */
  9  
 10  #ifdef HAVE_CONFIG_H
 11  #include "pkg_config.h"
 12  #endif
 13  
 14  #include <sys/param.h>
 15  
 16  #ifdef PKG_COMPAT
 17  #include <sys/stat.h>
 18  #include <sys/types.h>
 19  #include <dirent.h>
 20  #endif
 21  
 22  #include <err.h>
 23  #include <getopt.h>
 24  #include <stdio.h>
 25  #include <stdlib.h>
 26  #include <pkg.h>
 27  #include <string.h>
 28  #include <strings.h>
 29  #include <unistd.h>
 30  
 31  #include "pkgcli.h"
 32  
 33  void
 34  usage_create(void)
 35  {
 36  	fprintf(stderr, "Usage: pkg create [-ehnqv] [-f format] [-l level] "
 37  		"[-o outdir] [-p plist] [-r rootdir] [-t timestamp] [-T threads] -m metadatadir\n");
 38  	fprintf(stderr, "Usage: pkg create [-ehnqv] [-f format] [-l level] "
 39  		"[-o outdir] [-r rootdir] [-t timestamp] [-T threads] -M manifest\n");
 40  	fprintf(stderr, "       pkg create [-eghnqvx] [-f format] [-l level] "
 41  		"[-o outdir] [-r rootdir] [-t timestamp] [-T threads] pkg-name ...\n");
 42  	fprintf(stderr, "       pkg create [-ehnqv] [-f format] [-l level] "
 43  		"[-o outdir] [-r rootdir] [-t timestamp] [-T threads] -a\n\n");
 44  	fprintf(stderr, "For more information see 'pkg help create'.\n");
 45  }
 46  
 47  static int
 48  pkg_create_matches(int argc, char **argv, match_t match, struct pkg_create *pc)
 49  {
 50  	int i, ret = EPKG_OK, retcode = EXIT_SUCCESS;
 51  	struct pkg *pkg = NULL;
 52  	struct pkgdb *db = NULL;
 53  	struct pkgdb_it *it = NULL;
 54  	int query_flags = PKG_LOAD_DEPS | PKG_LOAD_FILES |
 55  	    PKG_LOAD_CATEGORIES | PKG_LOAD_DIRS | PKG_LOAD_SCRIPTS |
 56  	    PKG_LOAD_OPTIONS | PKG_LOAD_LICENSES |
 57  	    PKG_LOAD_USERS | PKG_LOAD_GROUPS | PKG_LOAD_SHLIBS_REQUIRED |
 58  	    PKG_LOAD_PROVIDES | PKG_LOAD_REQUIRES |
 59  	    PKG_LOAD_SHLIBS_PROVIDED | PKG_LOAD_ANNOTATIONS | PKG_LOAD_LUA_SCRIPTS;
 60  	bool foundone;
 61  	vec_t(struct pkg *) pkglist = vec_init();
 62  
 63  	if (pkgdb_open(&db, PKGDB_DEFAULT_READONLY) != EPKG_OK) {
 64  		pkgdb_close(db);
 65  		return (EXIT_FAILURE);
 66  	}
 67  	/* XXX: get rid of hardcoded timeouts */
 68  	if (pkgdb_obtain_lock(db, PKGDB_LOCK_READONLY) != EPKG_OK) {
 69  		pkgdb_close(db);
 70  		warnx("Cannot get a read lock on a database, it is locked by another process");
 71  		return (EXIT_FAILURE);
 72  	}
 73  
 74  	for (i = 0; i < argc || match == MATCH_ALL; i++) {
 75  		if (match == MATCH_ALL) {
 76  			printf("Loading the package list...\n");
 77  			if ((it = pkgdb_query(db, NULL, match)) == NULL) {
 78  				retcode = EXIT_FAILURE;
 79  				goto cleanup;
 80  			}
 81  			match = !MATCH_ALL;
 82  		} else {
 83  			if ((it = pkgdb_query(db, argv[i], match)) == NULL) {
 84  				retcode = EXIT_FAILURE;
 85  				goto cleanup;
 86  			}
 87  		}
 88  
 89  		foundone = false;
 90  		while ((ret = pkgdb_it_next(it, &pkg, query_flags)) == EPKG_OK) {
 91  			vec_push(&pkglist, pkg);
 92  			pkg = NULL;
 93  			foundone = true;
 94  		}
 95  		if (!foundone) {
 96  			warnx("No installed package matching \"%s\" found\n",
 97  			    argv[i]);
 98  			retcode = EXIT_FAILURE;
 99  		}
100  
101  		pkgdb_it_free(it);
102  		if (ret != EPKG_END)
103  			retcode = EXIT_FAILURE;
104  	}
105  
106  	vec_foreach(pkglist, i) {
107  		pkg_printf("Creating package for %n-%v\n", pkglist.d[i], pkglist.d[i]);
108  		ret = pkg_create_i(pc, pkglist.d[i], false);
109  		if (ret == EPKG_EXIST) {
110  			pkg_printf("%n-%v already packaged, skipping...\n",
111  			  pkglist.d[i], pkglist.d[i]);
112  		}
113  		if (ret != EPKG_OK && ret != EPKG_EXIST)
114  			retcode = EXIT_FAILURE;
115  	}
116  
117  cleanup:
118  	vec_free_and_free(&pkglist, pkg_free);
119  	pkgdb_release_lock(db, PKGDB_LOCK_READONLY);
120  	pkgdb_close(db);
121  
122  	return (retcode);
123  }
124  
125  /*
126   * options:
127   * -M: manifest file
128   * -f <format>: format could be tzst, txz, tgz, tbz or tar
129   * -g: globbing
130   * -h: pkg name with hash and symlink
131   * -m: path to dir where to find the metadata
132   * -o: output directory where to create packages by default ./ is used
133   * -q: quiet mode
134   * -r: rootdir for the package
135   * -x: regex
136   */
137  
138  int
139  exec_create(int argc, char **argv)
140  {
141  	struct pkg_create *pc;
142  	match_t		 match = MATCH_EXACT;
143  	const char	*outdir = NULL;
144  	const char	*format = NULL;
145  	const char	*rootdir = NULL;
146  	const char	*metadatadir = NULL;
147  	const char	*manifest = NULL;
148  	char		*plist = NULL;
149  	char	*endptr;
150  	int		 ch;
151  	int		 level;
152  	bool		 level_is_set = false;
153  	int		 threads;
154  	bool		 threads_is_set = false;
155  	int		 ret;
156  	bool		 hash = false;
157  	bool		 overwrite = true;
158  	bool		 expand_manifest = false;
159  	time_t		 ts = (time_t)-1;
160  
161  	/* Sentinel values: INT_MIN (fast), -1 (default per pkg),
162  	 * 0 (default per libarchive), INT_MAX (best). */
163  	level = -1;
164  
165  	/* POLA: pkg create is quiet by default, unless
166  	 * PKG_CREATE_VERBOSE is set in pkg.conf.  This is for
167  	 * historical reasons. */
168  
169  	quiet = !pkg_object_bool(pkg_config_get("PKG_CREATE_VERBOSE"));
170  
171  	struct option longopts[] = {
172  		{ "all",	no_argument,		NULL,	'a' },
173  		{ "expand-manifest",	no_argument,	NULL,	'e' },
174  		{ "format",	required_argument,	NULL,	'f' },
175  		{ "glob",	no_argument,		NULL,	'g' },
176  		{ "hash",	no_argument,		NULL,	'h' },
177  		{ "level",	required_argument,	NULL,	'l' },
178  		{ "regex",	no_argument,		NULL,	'x' },
179  		{ "root-dir",	required_argument,	NULL,	'r' },
180  		{ "metadata",	required_argument,	NULL,	'm' },
181  		{ "manifest",	required_argument,	NULL,	'M' },
182  		{ "no-clobber", no_argument,		NULL,	'n' },
183  		{ "out-dir",	required_argument,	NULL,	'o' },
184  		{ "plist",	required_argument,	NULL,	'p' },
185  		{ "quiet",	no_argument,		NULL,	'q' },
186  		{ "timestamp",	required_argument,	NULL,	't' },
187  		{ "verbose",	no_argument,		NULL,	'v' },
188  		{ NULL,		0,			NULL,	0   },
189  	};
190  
191  	while ((ch = getopt_long(argc, argv, "+aeghxf:l:r:m:M:no:p:qvt:T:", longopts, NULL)) != -1) {
192  		switch (ch) {
193  		case 'a':
194  			match = MATCH_ALL;
195  			break;
196  		case 'e':
197  			expand_manifest = true;
198  			break;
199  		case 'f':
200  			format = optarg;
201  			break;
202  		case 'g':
203  			match = MATCH_GLOB;
204  			break;
205  		case 'h':
206  			hash = true;
207  			break;
208  		case 'l':
209  			{
210  			const char *errstr;
211  
212  			level_is_set = true;
213  			level = strtonum(optarg, -200, 200, &errstr);
214  			if (errstr == NULL)
215  				break;
216  			if (STRIEQ(optarg, "best")) {
217  				level = INT_MAX;
218  				break;
219  			} else if (STRIEQ(optarg, "fast")) {
220  				level = INT_MIN;
221  				break;
222  			}
223  			warnx("Invalid compression level %s", optarg);
224  			return (EXIT_FAILURE);
225  			}
226  		case 'm':
227  			metadatadir = optarg;
228  			break;
229  		case 'M':
230  			manifest = optarg;
231  			break;
232  		case 'o':
233  			outdir = optarg;
234  			break;
235  		case 'n':
236  			overwrite = false;
237  			break;
238  		case 'p':
239  			plist = optarg;
240  			break;
241  		case 'q':
242  			quiet = true;
243  			break;
244  		case 'r':
245  			rootdir = optarg;
246  			break;
247  		case 't':
248  			endptr = NULL;
249  			ts = (time_t)strtoimax(optarg, &endptr, 10);
250  			if (*endptr != '\0') {
251  				warnx("Invalid timestamp %s", optarg);
252  				return (EXIT_FAILURE);
253  			}
254  			break;
255  		case 'T':
256  			{
257  			const char *errstr;
258  
259  			threads_is_set = true;
260  			threads = strtonum(optarg, 0, INT_MAX, &errstr);
261  			if (errstr == NULL)
262  				break;
263  			if (STRIEQ(optarg, "auto")) {
264  				threads = 0;
265  				break;
266  			}
267  			warnx("Invalid compression threads %s", optarg);
268  			return (EXIT_FAILURE);
269  			}
270  		case 'v':
271  			quiet = false;
272  			break;
273  		case 'x':
274  			match = MATCH_REGEX;
275  			break;
276  		default:
277  			usage_create();
278  			return (EXIT_FAILURE);
279  		}
280  	}
281  	argc -= optind;
282  	argv += optind;
283  
284  	if (match != MATCH_ALL && metadatadir == NULL && manifest == NULL &&
285  	    argc == 0) {
286  		usage_create();
287  		return (EXIT_FAILURE);
288  	}
289  
290  	if (metadatadir == NULL && manifest == NULL && rootdir != NULL) {
291  		warnx("Do not specify a rootdir without also specifying "
292  		    "either a metadatadir or manifest");
293  		usage_create();
294  		return (EXIT_FAILURE);
295  	}
296  
297  	if (outdir == NULL)
298  		outdir = "./";
299  
300  	pc = pkg_create_new();
301  	if (format != NULL) {
302  		if (format[0] == '.')
303  			++format;
304  		if (!pkg_create_set_format(pc, format))
305  			warnx("unknown format %s, using the default", format);
306  	}
307  	if (level_is_set)
308  		pkg_create_set_compression_level(pc, level);
309  	if (threads_is_set)
310  		pkg_create_set_compression_threads(pc, threads);
311  	pkg_create_set_overwrite(pc, overwrite);
312  	pkg_create_set_rootdir(pc, rootdir);
313  	pkg_create_set_output_dir(pc, outdir);
314  	pkg_create_set_expand_manifest(pc, expand_manifest);
315  	if (ts != (time_t)-1)
316  		pkg_create_set_timestamp(pc, ts);
317  
318  	if (metadatadir == NULL && manifest == NULL) {
319  		ret = pkg_create_matches(argc, argv, match, pc);
320  		pkg_create_free(pc);
321  		return (ret == EPKG_OK ? EXIT_SUCCESS : EXIT_FAILURE);
322  	}
323  	ret = pkg_create(pc, metadatadir != NULL ? metadatadir : manifest, plist,
324  	    hash);
325  	pkg_create_free(pc);
326  	if (ret == EPKG_EXIST || ret == EPKG_OK)
327  		return (EXIT_SUCCESS);
328  	return (EXIT_FAILURE);
329  }
330