Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/pkg
Path: blob/main/libpkg/pkg_create.c
2645 views
1
/*-
2
* Copyright (c) 2011-2020 Baptiste Daroussin <[email protected]>
3
* Copyright (c) 2011-2012 Julien Laffaye <[email protected]>
4
* Copyright (c) 2014-2015 Matthew Seaman <[email protected]>
5
* Copyright (c) 2014 Vsevolod Stakhov <[email protected]>
6
* Copyright (c) 2023 Serenity Cyber Security, LLC
7
* Author: Gleb Popov <[email protected]>
8
* All rights reserved.
9
*
10
* Redistribution and use in source and binary forms, with or without
11
* modification, are permitted provided that the following conditions
12
* are met:
13
* 1. Redistributions of source code must retain the above copyright
14
* notice, this list of conditions and the following disclaimer
15
* in this position and unchanged.
16
* 2. Redistributions in binary form must reproduce the above copyright
17
* notice, this list of conditions and the following disclaimer in the
18
* documentation and/or other materials provided with the distribution.
19
*
20
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
21
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23
* IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
24
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
*/
31
32
#include <sys/stat.h>
33
34
#include <errno.h>
35
#include <regex.h>
36
#include <fcntl.h>
37
38
#include <bsd_compat.h>
39
40
#include "pkg.h"
41
#include "private/event.h"
42
#include "private/pkg.h"
43
#include "private/pkg_abi.h"
44
#include "xmalloc.h"
45
46
#define TICK 100
47
48
static int load_metadata(struct pkg *pkg, const char *metadata, const char *plist,
49
const char *rootdir);
50
static void fixup_abi(struct pkg *pkg, const char *rootdir, bool testing);
51
static void counter_init(const char *what, int64_t max);
52
static void counter_count(void);
53
static void counter_end(void);
54
55
extern struct pkg_ctx ctx;
56
57
static int
58
pkg_create_from_dir(struct pkg *pkg, const char *root,
59
struct pkg_create *pc, struct packing *pkg_archive,
60
bool trust_filesystem)
61
{
62
char fpath[MAXPATHLEN];
63
struct pkg_file *file = NULL;
64
struct pkg_dir *dir = NULL;
65
int ret;
66
struct stat st;
67
int64_t flatsize = 0;
68
int64_t nfiles;
69
const char *relocation;
70
char *manifest;
71
ucl_object_t *obj;
72
hardlinks_t hardlinks = vec_init();
73
ssize_t linklen;
74
75
if (pkg_is_valid(pkg) != EPKG_OK) {
76
pkg_emit_error("the package is not valid");
77
return (EPKG_FATAL);
78
}
79
80
relocation = pkg_kv_get(&pkg->annotations, "relocated");
81
if (relocation == NULL)
82
relocation = "";
83
if (ctx.pkg_rootdir != NULL)
84
relocation = ctx.pkg_rootdir;
85
86
/*
87
* Get / compute size / checksum if not provided in the manifest
88
*/
89
90
nfiles = pkghash_count(pkg->filehash);
91
counter_init("file sizes/checksums", nfiles);
92
93
while (pkg_files(pkg, &file) == EPKG_OK) {
94
95
snprintf(fpath, sizeof(fpath), "%s%s%s", root ? root : "",
96
relocation, file->path);
97
98
if (lstat(fpath, &st) == -1) {
99
pkg_emit_error("file '%s' is missing", fpath);
100
vec_free_and_free(&hardlinks, free);
101
return (EPKG_FATAL);
102
}
103
104
if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) {
105
pkg_emit_error("file '%s' is not a regular file or symlink", fpath);
106
vec_free_and_free(&hardlinks, free);
107
return (EPKG_FATAL);
108
}
109
110
if (file->size == 0)
111
file->size = (int64_t)st.st_size;
112
113
if (st.st_nlink == 1 || !check_for_hardlink(&hardlinks, &st)) {
114
flatsize += file->size;
115
}
116
117
if (file->perm == 0) {
118
file->perm = st.st_mode & ~S_IFMT;
119
}
120
121
if (trust_filesystem) {
122
free(file->sum);
123
file->sum = pkg_checksum_generate_file(fpath,
124
PKG_HASH_TYPE_SHA256_HEX);
125
if (file->sum == NULL) {
126
vec_free_and_free(&hardlinks, free);
127
return (EPKG_FATAL);
128
}
129
130
if (S_ISLNK(st.st_mode)) {
131
char link[MAXPATHLEN] = { 0 };
132
linklen = readlink(fpath, link, sizeof(link) -1);
133
if (linklen == -1) {
134
vec_free_and_free(&hardlinks, free);
135
pkg_emit_errno("pkg_create_from_dir", "readlink failed");
136
return (EPKG_FATAL);
137
}
138
free(file->symlink_target);
139
file->symlink_target = xstrdup(link);
140
}
141
142
if (pc->timestamp > (time_t)-1) {
143
file->time[0].tv_sec = pc->timestamp;
144
file->time[1].tv_sec = pc->timestamp;
145
} else {
146
file->time[0] = st.st_atim;
147
file->time[1] = st.st_mtim;
148
}
149
}
150
151
counter_count();
152
}
153
vec_free_and_free(&hardlinks, free);
154
155
while (pkg_dirs(pkg, &dir) == EPKG_OK) {
156
snprintf(fpath, sizeof(fpath), "%s%s%s", root ? root : "",
157
relocation, dir->path);
158
159
if (lstat(fpath, &st) == -1) {
160
pkg_emit_error("dir '%s' is missing", fpath);
161
return (EPKG_FATAL);
162
}
163
164
if (!S_ISDIR(st.st_mode)) {
165
pkg_emit_error("dir '%s' is not a directory", fpath);
166
return (EPKG_FATAL);
167
}
168
169
if (dir->perm == 0) {
170
dir->perm = st.st_mode & ~S_IFMT;
171
}
172
}
173
174
counter_end();
175
176
pkg->flatsize = flatsize;
177
178
if (pkg->type == PKG_OLD_FILE) {
179
pkg_emit_error("Cannot create an old format package");
180
return (EPKG_FATAL);
181
}
182
183
obj = pkg_emit_object(pkg, PKG_MANIFEST_EMIT_COMPACT);
184
manifest = ucl_object_emit(obj, UCL_EMIT_JSON_COMPACT);
185
ucl_object_unref(obj);
186
packing_append_buffer(pkg_archive, manifest, "+COMPACT_MANIFEST", strlen(manifest));
187
free(manifest);
188
obj = pkg_emit_object(pkg, 0);
189
if (pc->expand_manifest) {
190
manifest = ucl_object_emit(obj, UCL_EMIT_CONFIG);
191
} else {
192
manifest = ucl_object_emit(obj, UCL_EMIT_JSON_COMPACT);
193
}
194
ucl_object_unref(obj);
195
packing_append_buffer(pkg_archive, manifest, "+MANIFEST", strlen(manifest));
196
free(manifest);
197
198
counter_init("packing files", nfiles);
199
200
while (pkg_files(pkg, &file) == EPKG_OK) {
201
char dpath[MAXPATHLEN];
202
const char *dp = file->path;
203
204
if (pkg->oprefix != NULL) {
205
size_t l = strlen(pkg->prefix);
206
if (strncmp(file->path, pkg->prefix, l) == 0 &&
207
(file->path[l] == '/' || l == 1)) {
208
snprintf(dpath, sizeof(dpath), "%s%s%s",
209
pkg->oprefix, l == 1 ? "/" : "", file->path + l);
210
dp = dpath;
211
}
212
}
213
214
snprintf(fpath, sizeof(fpath), "%s%s%s", root ? root : "",
215
relocation, file->path);
216
217
ret = packing_append_file_attr(pkg_archive, fpath, dp,
218
file->uname, file->gname, file->perm, file->fflags);
219
if (ctx.developer_mode && ret != EPKG_OK)
220
return (ret);
221
counter_count();
222
}
223
224
counter_end();
225
226
nfiles = pkghash_count(pkg->dirhash);
227
counter_init("packing directories", nfiles);
228
229
while (pkg_dirs(pkg, &dir) == EPKG_OK) {
230
snprintf(fpath, sizeof(fpath), "%s%s%s", root ? root : "",
231
relocation, dir->path);
232
233
ret = packing_append_file_attr(pkg_archive, fpath, dir->path,
234
dir->uname, dir->gname, dir->perm, dir->fflags);
235
if (ctx.developer_mode && ret != EPKG_OK)
236
return (ret);
237
counter_count();
238
}
239
240
counter_end();
241
242
return (EPKG_OK);
243
}
244
245
static struct packing *
246
pkg_create_archive(struct pkg *pkg, struct pkg_create *pc, unsigned required_flags)
247
{
248
char *pkg_path = NULL;
249
struct packing *pkg_archive = NULL;
250
251
/*
252
* Ensure that we have all the information we need
253
*/
254
if (pkg->type != PKG_OLD_FILE)
255
assert((pkg->flags & required_flags) == required_flags);
256
257
if (pkg_mkdirs(pc->outdir) != EPKG_OK)
258
return NULL;
259
260
if (pkg_asprintf(&pkg_path, "%S/%n-%v", pc->outdir, pkg, pkg) == -1) {
261
pkg_emit_errno("pkg_asprintf", "");
262
return (NULL);
263
}
264
265
if (packing_init(&pkg_archive, pkg_path, pc->format,
266
pc->compression_level, pc->compression_threads, pc->timestamp, pc->overwrite) != EPKG_OK) {
267
pkg_archive = NULL;
268
}
269
270
free(pkg_path);
271
272
return pkg_archive;
273
}
274
275
static const char * const scripts[] = {
276
"+INSTALL",
277
"+PRE_INSTALL",
278
"+POST_INSTALL",
279
"+POST_INSTALL",
280
"+DEINSTALL",
281
"+PRE_DEINSTALL",
282
"+POST_DEINSTALL",
283
"pkg-install",
284
"pkg-pre-install",
285
"pkg-post-install",
286
"pkg-deinstall",
287
"pkg-pre-deinstall",
288
"pkg-post-deinstall",
289
NULL
290
};
291
292
static const char * const lua_scripts[] = {
293
"pkg-pre-install.lua",
294
"pkg-post-install.lua",
295
"pkg-pre-deinstall.lua",
296
"pkg-post-deinstall.lua",
297
NULL
298
};
299
300
struct pkg_create *
301
pkg_create_new(void)
302
{
303
struct pkg_create *pc;
304
305
pc = xcalloc(1, sizeof(*pc));
306
pc->format = packing_format_from_string(ctx.compression_format);
307
pc->compression_level = ctx.compression_level;
308
pc->compression_threads = ctx.compression_threads;
309
pc->timestamp = (time_t) -1;
310
pc->overwrite = true;
311
pc->expand_manifest = false;
312
313
return (pc);
314
}
315
316
void
317
pkg_create_free(struct pkg_create *pc)
318
{
319
free(pc);
320
}
321
322
bool
323
pkg_create_set_format(struct pkg_create *pc, const char *format)
324
{
325
if (STREQ(format, "tzst"))
326
pc->format = TZS;
327
else if (STREQ(format, "txz"))
328
pc->format = TXZ;
329
else if (STREQ(format, "tbz"))
330
pc->format = TBZ;
331
else if (STREQ(format, "tgz"))
332
pc->format = TGZ;
333
else if (STREQ(format, "tar"))
334
pc->format = TAR;
335
else
336
return (false);
337
return (true);
338
}
339
340
void
341
pkg_create_set_compression_level(struct pkg_create *pc, int clevel)
342
{
343
pc->compression_level = clevel;
344
}
345
346
void
347
pkg_create_set_compression_threads(struct pkg_create *pc, int threads)
348
{
349
pc->compression_threads = threads;
350
}
351
352
void
353
pkg_create_set_expand_manifest(struct pkg_create *pc, bool expand)
354
{
355
pc->expand_manifest = expand;
356
}
357
358
void
359
pkg_create_set_rootdir(struct pkg_create *pc, const char *rootdir)
360
{
361
pc->rootdir = rootdir;
362
}
363
364
void
365
pkg_create_set_output_dir(struct pkg_create *pc, const char *outdir)
366
{
367
pc->outdir = outdir;
368
}
369
370
void
371
pkg_create_set_timestamp(struct pkg_create *pc, time_t timestamp)
372
{
373
pc->timestamp = timestamp;
374
}
375
376
void
377
pkg_create_set_overwrite(struct pkg_create *pc, bool overwrite)
378
{
379
pc->overwrite = overwrite;
380
}
381
382
static int
383
hash_file(struct pkg *pkg)
384
{
385
char hash_dest[MAXPATHLEN];
386
char filename[MAXPATHLEN];
387
388
/* Find the hash and rename the file and create a symlink */
389
pkg_snprintf(filename, sizeof(filename), "%n-%v.pkg",
390
pkg, pkg);
391
pkg->sum = pkg_checksum_file(filename,
392
PKG_HASH_TYPE_SHA256_HEX);
393
pkg_snprintf(hash_dest, sizeof(hash_dest), "%n-%v-%z.pkg",
394
pkg, pkg, pkg);
395
396
pkg_debug(1, "Rename the pkg file from: %s to: %s",
397
filename, hash_dest);
398
if (rename(filename, hash_dest) == -1) {
399
pkg_emit_errno("rename", hash_dest);
400
unlink(hash_dest);
401
return (EPKG_FATAL);
402
}
403
if (symlink(hash_dest, filename) == -1) {
404
pkg_emit_errno("symlink", hash_dest);
405
return (EPKG_FATAL);
406
}
407
return (EPKG_OK);
408
}
409
410
int
411
pkg_create_i(struct pkg_create *pc, struct pkg *pkg, bool hash)
412
{
413
struct packing *pkg_archive = NULL;
414
int ret;
415
416
unsigned required_flags = PKG_LOAD_DEPS | PKG_LOAD_FILES |
417
PKG_LOAD_CATEGORIES | PKG_LOAD_DIRS | PKG_LOAD_SCRIPTS |
418
PKG_LOAD_OPTIONS | PKG_LOAD_LICENSES | PKG_LOAD_LUA_SCRIPTS;
419
420
assert(pkg->type == PKG_INSTALLED || pkg->type == PKG_OLD_FILE);
421
422
pkg_archive = pkg_create_archive(pkg, pc, required_flags);
423
if (pkg_archive == NULL) {
424
if (errno == EEXIST)
425
return (EPKG_EXIST);
426
pkg_emit_error("unable to create archive");
427
return (EPKG_FATAL);
428
}
429
430
if ((ret = pkg_create_from_dir(pkg, NULL, pc, pkg_archive, false)) != EPKG_OK) {
431
pkg_emit_error("package creation failed");
432
}
433
packing_finish(pkg_archive);
434
435
if (hash && ret == EPKG_OK)
436
ret = hash_file(pkg);
437
438
return (ret);
439
}
440
441
int
442
pkg_create(struct pkg_create *pc, const char *metadata, const char *plist,
443
bool hash)
444
{
445
struct pkg *pkg = NULL;
446
struct packing *pkg_archive = NULL;
447
int ret = ENOMEM;
448
449
pkg_debug(1, "Creating package");
450
if (pkg_new(&pkg, PKG_FILE) != EPKG_OK) {
451
return (EPKG_FATAL);
452
}
453
454
if (load_metadata(pkg, metadata, plist, pc->rootdir) != EPKG_OK) {
455
pkg_free(pkg);
456
return (EPKG_FATAL);
457
}
458
fixup_abi(pkg, pc->rootdir, false);
459
460
pkg_archive = pkg_create_archive(pkg, pc, 0);
461
if (pkg_archive == NULL) {
462
if (errno == EEXIST) {
463
pkg_emit_notice("%s-%s already packaged, skipping...\n",
464
pkg->name, pkg->version);
465
pkg_free(pkg);
466
return (EPKG_EXIST);
467
}
468
pkg_free(pkg);
469
return (EPKG_FATAL);
470
}
471
472
if ((ret = pkg_create_from_dir(pkg, pc->rootdir, pc, pkg_archive, true)) != EPKG_OK)
473
pkg_emit_error("package creation failed");
474
475
packing_finish(pkg_archive);
476
if (hash && ret == EPKG_OK)
477
ret = hash_file(pkg);
478
479
pkg_free(pkg);
480
return (ret);
481
}
482
483
static int
484
pkg_load_message_from_file(int fd, struct pkg *pkg, const char *path)
485
{
486
char *buf = NULL;
487
off_t size = 0;
488
int ret;
489
ucl_object_t *obj;
490
491
assert(pkg != NULL);
492
assert(path != NULL);
493
494
if (faccessat(fd, path, F_OK, 0) == -1) {
495
return (EPKG_FATAL);
496
}
497
498
pkg_debug(1, "Reading message: '%s'", path);
499
if ((ret = file_to_bufferat(fd, path, &buf, &size)) != EPKG_OK) {
500
return (ret);
501
}
502
503
if (*buf == '[') {
504
ret = pkg_message_from_str(pkg, buf, size);
505
free(buf);
506
return (ret);
507
}
508
obj = ucl_object_fromstring_common(buf, size,
509
UCL_STRING_RAW|UCL_STRING_TRIM);
510
ret = pkg_message_from_ucl(pkg, obj);
511
ucl_object_unref(obj);
512
free(buf);
513
514
return (ret);
515
}
516
517
/* TODO use file descriptor for rootdir */
518
static int
519
load_manifest(struct pkg *pkg, const char *metadata, const char *plist,
520
const char *rootdir)
521
{
522
int ret;
523
524
ret = pkg_parse_manifest_file(pkg, metadata);
525
526
if (ret == EPKG_OK && plist != NULL)
527
ret = ports_parse_plist(pkg, plist, rootdir);
528
return (ret);
529
}
530
531
/* TODO use file descriptor for rootdir */
532
static int
533
load_metadata(struct pkg *pkg, const char *metadata, const char *plist,
534
const char *rootdir)
535
{
536
regex_t preg;
537
regmatch_t pmatch[2];
538
size_t size;
539
int fd, i;
540
541
/* Let's see if we have a directory or a manifest */
542
if ((fd = open(metadata, O_DIRECTORY|O_CLOEXEC)) == -1) {
543
if (errno == ENOTDIR)
544
return (load_manifest(pkg, metadata, plist, rootdir));
545
pkg_emit_errno("open", metadata);
546
return (EPKG_FATAL);
547
}
548
549
if ((pkg_parse_manifest_fileat(fd, pkg, "+MANIFEST")) != EPKG_OK) {
550
pkg_emit_error("Error parsing %s/+MANIFEST", metadata);
551
close(fd);
552
return (EPKG_FATAL);
553
}
554
/* ensure the uid is properly */
555
free(pkg->uid);
556
pkg->uid = xstrdup(pkg->name);
557
558
pkg_load_message_from_file(fd, pkg, "+DISPLAY");
559
if (pkg->desc == NULL)
560
pkg_set_from_fileat(fd, pkg, PKG_ATTR_DESC, "+DESC", false);
561
562
for (i = 0; scripts[i] != NULL; i++) {
563
if (faccessat(fd, scripts[i], F_OK, 0) == 0)
564
pkg_addscript_fileat(fd, pkg, scripts[i]);
565
}
566
567
for (i = 0; lua_scripts[i] != NULL; i++) {
568
if (faccessat(fd, lua_scripts[i], F_OK, 0) == 0)
569
pkg_addluascript_fileat(fd, pkg, lua_scripts[i]);
570
}
571
572
if (plist != NULL && ports_parse_plist(pkg, plist, rootdir) != EPKG_OK) {
573
return (EPKG_FATAL);
574
}
575
close(fd);
576
577
if (pkg->www == NULL) {
578
if (pkg->desc == NULL) {
579
pkg_emit_error("No www or desc defined in manifest");
580
return (EPKG_FATAL);
581
}
582
regcomp(&preg, "^WWW:[[:space:]]*(.*)$",
583
REG_EXTENDED|REG_ICASE|REG_NEWLINE);
584
if (regexec(&preg, pkg->desc, 2, pmatch, 0) == 0) {
585
size = pmatch[1].rm_eo - pmatch[1].rm_so;
586
pkg->www = xstrndup(&pkg->desc[pmatch[1].rm_so], size);
587
} else {
588
pkg->www = xstrdup("UNKNOWN");
589
}
590
regfree(&preg);
591
}
592
593
return (EPKG_OK);
594
}
595
596
static void
597
fixup_abi(struct pkg *pkg, const char *rootdir, bool testing)
598
{
599
bool defaultarch = false;
600
601
/* if no arch autodetermine it */
602
if (pkg->abi == NULL) {
603
if (ctx.abi.os == PKG_OS_FREEBSD) {
604
char *str_osversion;
605
xasprintf(&str_osversion, "%d", pkg_abi_get_freebsd_osversion(&ctx.abi));
606
pkg_kv_add(&pkg->annotations, "FreeBSD_version", str_osversion, "annotation");
607
}
608
pkg->abi = pkg_abi_to_string(&ctx.abi);
609
defaultarch = true;
610
}
611
612
if (!testing)
613
pkg_analyse_files(NULL, pkg, rootdir);
614
615
if (ctx.developer_mode)
616
suggest_arch(pkg, defaultarch);
617
}
618
619
int
620
pkg_load_metadata(struct pkg *pkg, const char *mfile, const char *md_dir,
621
const char *plist, const char *rootdir, bool testing)
622
{
623
int ret;
624
625
ret = load_metadata(pkg, md_dir != NULL ? md_dir: mfile, plist, rootdir);
626
if (ret != EPKG_OK)
627
return (ret);
628
629
fixup_abi(pkg, rootdir, testing);
630
return (ret);
631
}
632
633
static int64_t count;
634
static int64_t maxcount;
635
static const char *what;
636
637
static int magnitude(int64_t num)
638
{
639
int oom;
640
641
if (num == 0)
642
return (1);
643
if (num < 0)
644
num = -num;
645
646
for (oom = 1; num >= 10; oom++)
647
num /= 10;
648
649
return (oom);
650
}
651
652
static void
653
counter_init(const char *count_what, int64_t max)
654
{
655
count = 0;
656
what = count_what;
657
maxcount = max;
658
pkg_emit_progress_start("%-20s%*s[%jd]", what,
659
6 - magnitude(maxcount), " ", (intmax_t)maxcount);
660
661
return;
662
}
663
664
static void
665
counter_count(void)
666
{
667
count++;
668
669
if (count % TICK == 0)
670
pkg_emit_progress_tick(count, maxcount);
671
672
return;
673
}
674
675
static void
676
counter_end(void)
677
{
678
pkg_emit_progress_tick(count, maxcount);
679
return;
680
}
681
682