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