Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/pkg
Path: blob/main/libpkg/pkg_repo_create.c
2065 views
1
/*-
2
* Copyright (c) 2011-2025 Baptiste Daroussin <[email protected]>
3
* Copyright (c) 2011-2012 Julien Laffaye <[email protected]>
4
* Copyright (c) 2011-2012 Marin Atanasov Nikolov <[email protected]>
5
* Copyright (c) 2012-2013 Matthew Seaman <[email protected]>
6
* Copyright (c) 2014 Vsevolod Stakhov <[email protected]>
7
* Copyright (c) 2023-2024 Serenity Cyber Security, LLC
8
* Author: Gleb Popov <[email protected]>
9
*
10
* SPDX-License-Identifier: BSD-2-Clause
11
*/
12
13
#include "pkg_config.h"
14
15
#include <sys/types.h>
16
#include <sys/stat.h>
17
#include <sys/uio.h>
18
#include <sys/file.h>
19
#include <sys/time.h>
20
#include <sys/wait.h>
21
22
#include <archive_entry.h>
23
#include <assert.h>
24
#include <fts.h>
25
#include <libgen.h>
26
#include <sqlite3.h>
27
#include <string.h>
28
#include <stdio.h>
29
#include <stdbool.h>
30
#include <unistd.h>
31
#include <errno.h>
32
#include <math.h>
33
#include <pthread.h>
34
#include <fcntl.h>
35
#include <dirent.h>
36
37
#include "pkg.h"
38
#include "private/event.h"
39
#include "private/utils.h"
40
#include "private/pkg.h"
41
#include "private/pkgdb.h"
42
#include "private/pkgsign.h"
43
44
enum {
45
MSG_PKG_DONE=0,
46
MSG_PKG_READY,
47
};
48
49
static int pkg_repo_pack_db(const char *name, const char *archive, char *path,
50
struct pkgsign_ctx *ctx, struct pkg_repo_create *prc);
51
52
static int
53
hash_file(struct pkg_repo_meta *meta, struct pkg *pkg, char *path)
54
{
55
char tmp_repo[MAXPATHLEN] = { 0 };
56
char tmp_name[MAXPATHLEN] = { 0 };
57
char repo_name[MAXPATHLEN] = { 0 };
58
char hash_name[MAXPATHLEN] = { 0 };
59
char link_name[MAXPATHLEN] = { 0 };
60
char *rel_repo = NULL;
61
char *rel_dir = NULL;
62
char *rel_link = NULL;
63
char *ext = NULL;
64
65
/* Don't rename symlinks */
66
if (is_link(path))
67
return (EPKG_OK);
68
69
ext = strrchr(path, '.');
70
71
strlcpy(tmp_name, path, sizeof(tmp_name));
72
rel_dir = get_dirname(tmp_name);
73
while (strstr(rel_dir, "/Hashed") != NULL) {
74
rel_dir = get_dirname(rel_dir);
75
}
76
strlcpy(tmp_name, rel_dir, sizeof(tmp_name));
77
rel_dir = (char *)&tmp_name;
78
79
rel_repo = path;
80
if (strncmp(rel_repo, meta->repopath, strlen(meta->repopath)) == 0) {
81
rel_repo += strlen(meta->repopath);
82
while (rel_repo[0] == '/')
83
rel_repo++;
84
}
85
strlcpy(tmp_repo, rel_repo, sizeof(tmp_repo));
86
rel_repo = get_dirname(tmp_repo);
87
while (strstr(rel_repo, "/Hashed") != NULL) {
88
rel_repo = get_dirname(rel_repo);
89
}
90
strlcpy(tmp_repo, rel_repo, sizeof(tmp_repo));
91
rel_repo = (char *)&tmp_repo;
92
93
pkg_snprintf(repo_name, sizeof(repo_name), "%S/%S/%n-%v%S%z%S",
94
rel_repo, PKG_HASH_DIR, pkg, pkg, PKG_HASH_SEPSTR, pkg, ext);
95
pkg_snprintf(link_name, sizeof(repo_name), "%S/%n-%v%S",
96
rel_dir, pkg, pkg, ext);
97
pkg_snprintf(hash_name, sizeof(hash_name), "%S/%S/%n-%v%S%z%S",
98
rel_dir, PKG_HASH_DIR, pkg, pkg, PKG_HASH_SEPSTR, pkg, ext);
99
rel_link = (char *)&hash_name;
100
rel_link += strlen(rel_dir);
101
while (rel_link[0] == '/')
102
rel_link++;
103
104
snprintf(tmp_name, sizeof(tmp_name), "%s/%s", rel_dir, PKG_HASH_DIR);
105
rel_dir = (char *)&tmp_name;
106
if (!is_dir(rel_dir)) {
107
pkg_debug(1, "Making directory: %s", rel_dir);
108
(void)pkg_mkdirs(rel_dir);
109
}
110
111
if (!STREQ(path, hash_name)) {
112
pkg_debug(1, "Rename the pkg from: %s to: %s", path, hash_name);
113
if (rename(path, hash_name) == -1) {
114
pkg_emit_errno("rename", hash_name);
115
return (EPKG_FATAL);
116
}
117
}
118
if (meta->hash_symlink) {
119
pkg_debug(1, "Symlinking pkg file from: %s to: %s", rel_link,
120
link_name);
121
(void)unlink(link_name);
122
if (symlink(rel_link, link_name) == -1) {
123
pkg_emit_errno("symlink", link_name);
124
return (EPKG_FATAL);
125
}
126
}
127
free(pkg->repopath);
128
pkg->repopath = xstrdup(repo_name);
129
130
return (EPKG_OK);
131
}
132
133
struct pkg_fts_item {
134
char *fts_accpath;
135
char *pkg_path;
136
char *fts_name;
137
off_t fts_size;
138
int fts_info;
139
};
140
typedef vec_t(struct pkg_fts_item *) fts_item_t;
141
142
static struct pkg_fts_item*
143
pkg_create_repo_fts_new(FTSENT *fts, const char *root_path)
144
{
145
struct pkg_fts_item *item;
146
char *pkg_path;
147
148
item = xmalloc(sizeof(*item));
149
item->fts_accpath = xstrdup(fts->fts_accpath);
150
item->fts_name = xstrdup(fts->fts_name);
151
item->fts_size = fts->fts_statp->st_size;
152
item->fts_info = fts->fts_info;
153
154
pkg_path = fts->fts_path;
155
pkg_path += strlen(root_path);
156
while (pkg_path[0] == '/')
157
pkg_path++;
158
159
item->pkg_path = xstrdup(pkg_path);
160
161
return (item);
162
}
163
164
static void
165
pkg_create_repo_fts_free(struct pkg_fts_item *item)
166
{
167
free(item->fts_accpath);
168
free(item->pkg_path);
169
free(item->fts_name);
170
free(item);
171
}
172
173
static int
174
pkg_create_repo_read_fts(fts_item_t *items, FTS *fts,
175
const char *repopath, size_t *plen, struct pkg_repo_meta *meta)
176
{
177
FTSENT *fts_ent;
178
struct pkg_fts_item *fts_cur;
179
char *ext;
180
int linklen = 0;
181
char tmp_name[MAXPATHLEN] = { 0 };
182
char repo_path[MAXPATHLEN];
183
size_t repo_path_len;
184
185
if (realpath(repopath, repo_path) == NULL) {
186
pkg_emit_errno("invalid repo path", repopath);
187
return (EPKG_FATAL);
188
}
189
repo_path_len = strlen(repo_path);
190
errno = 0;
191
192
while ((fts_ent = fts_read(fts)) != NULL) {
193
/*
194
* Skip directories starting with '.' to avoid Poudriere
195
* symlinks.
196
*/
197
if ((fts_ent->fts_info == FTS_D ||
198
fts_ent->fts_info == FTS_DP) &&
199
fts_ent->fts_namelen > 2 &&
200
fts_ent->fts_name[0] == '.') {
201
fts_set(fts, fts_ent, FTS_SKIP);
202
continue;
203
}
204
/*
205
* Ignore 'Latest' directory as it is just symlinks back to
206
* already-processed packages.
207
*/
208
if ((fts_ent->fts_info == FTS_D ||
209
fts_ent->fts_info == FTS_DP ||
210
fts_ent->fts_info == FTS_SL) &&
211
STREQ(fts_ent->fts_name, "Latest")) {
212
fts_set(fts, fts_ent, FTS_SKIP);
213
continue;
214
}
215
/* Follow symlinks. */
216
if (fts_ent->fts_info == FTS_SL) {
217
/*
218
* Skip symlinks pointing inside the repo
219
* and dead symlinks
220
*/
221
if (realpath(fts_ent->fts_path, tmp_name) == NULL)
222
continue;
223
if (strncmp(repo_path, tmp_name, repo_path_len) == 0)
224
continue;
225
/* Skip symlinks to hashed packages */
226
if (meta->hash) {
227
linklen = readlink(fts_ent->fts_path,
228
(char *)&tmp_name, MAXPATHLEN);
229
if (linklen < 0)
230
continue;
231
tmp_name[linklen] = '\0';
232
if (strstr(tmp_name, PKG_HASH_DIR) != NULL)
233
continue;
234
}
235
fts_set(fts, fts_ent, FTS_FOLLOW);
236
/* Restart. Next entry will be the resolved file. */
237
continue;
238
}
239
/* Skip everything that is not a file */
240
if (fts_ent->fts_info != FTS_F)
241
continue;
242
243
ext = strrchr(fts_ent->fts_name, '.');
244
245
if (ext == NULL)
246
continue;
247
248
if (!packing_is_valid_format(ext + 1))
249
continue;
250
251
/* skip all files which are not .pkg */
252
if (!ctx.repo_accept_legacy_pkg && !STREQ(ext + 1, "pkg"))
253
continue;
254
255
256
*ext = '\0';
257
258
if (pkg_repo_meta_is_old_file(fts_ent->fts_name, meta)) {
259
unlink(fts_ent->fts_path);
260
continue;
261
}
262
if (STREQ(fts_ent->fts_name, "meta") ||
263
pkg_repo_meta_is_special_file(fts_ent->fts_name, meta)) {
264
*ext = '.';
265
continue;
266
}
267
268
*ext = '.';
269
fts_cur = pkg_create_repo_fts_new(fts_ent, repopath);
270
if (fts_cur == NULL)
271
return (EPKG_FATAL);
272
273
vec_push(items, fts_cur);
274
(*plen) ++;
275
}
276
277
if (errno != 0) {
278
pkg_emit_errno("fts_read", "pkg_create_repo_read_fts");
279
return (EPKG_FATAL);
280
}
281
282
return (EPKG_OK);
283
}
284
285
struct thr_env {
286
int ntask;
287
FILE *ffile;
288
FILE *mfile;
289
FILE *dfile;
290
struct ucl_emitter_context *ctx;
291
struct pkg_repo_meta *meta;
292
fts_item_t fts_items;
293
pthread_mutex_t nlock;
294
pthread_mutex_t llock;
295
pthread_mutex_t flock;
296
pthread_cond_t cond;
297
};
298
299
static void *
300
pkg_create_repo_thread(void *arg)
301
{
302
struct thr_env *te = (struct thr_env *)arg;
303
int flags, ret = EPKG_OK;
304
struct pkg *pkg = NULL;
305
char *path;
306
const char *repopath;
307
struct pkg_fts_item *items = NULL;
308
309
pkg_debug(1, "start worker to parse packages");
310
311
if (te->ffile != NULL)
312
flags = PKG_OPEN_MANIFEST_ONLY;
313
else
314
flags = PKG_OPEN_MANIFEST_ONLY | PKG_OPEN_MANIFEST_COMPACT;
315
316
for (;;) {
317
if (items != NULL)
318
pkg_create_repo_fts_free(items);
319
pthread_mutex_lock(&te->llock);
320
if (te->fts_items.len == 0) {
321
pthread_mutex_unlock(&te->llock);
322
goto cleanup;
323
}
324
items = vec_pop(&te->fts_items);
325
pthread_mutex_unlock(&te->llock);
326
path = items->fts_accpath;
327
repopath = items->pkg_path;
328
if (pkg_open(&pkg, path, flags) == EPKG_OK) {
329
struct stat st;
330
331
pkg->sum = pkg_checksum_file(path, PKG_HASH_TYPE_SHA256_HEX);
332
stat(path, &st);
333
pkg->pkgsize = st.st_size;
334
if (te->meta->hash) {
335
ret = hash_file(te->meta, pkg, path);
336
if (ret != EPKG_OK)
337
goto cleanup;
338
} else {
339
pkg->repopath = xstrdup(repopath);
340
}
341
342
/*
343
* TODO: use pkg_checksum for new manifests
344
*/
345
pthread_mutex_lock(&te->flock);
346
ucl_object_t *o = pkg_emit_object(pkg, 0);
347
ucl_object_emit_streamline_add_object(te->ctx, o);
348
ucl_object_emit_file(o, UCL_EMIT_JSON_COMPACT, te->mfile);
349
fprintf(te->mfile, "\n");
350
ucl_object_unref(o);
351
352
if (te->ffile != NULL) {
353
pkg_emit_filelist(pkg, te->ffile);
354
}
355
356
pthread_mutex_unlock(&te->flock);
357
358
pkg_free(pkg);
359
}
360
pthread_mutex_lock(&te->nlock);
361
te->ntask++;
362
pthread_cond_signal(&te->cond);
363
pthread_mutex_unlock(&te->nlock);
364
}
365
366
cleanup:
367
pkg_debug(1, "worker done");
368
return (NULL);
369
}
370
371
#if defined (__linux__) || defined(_DARWIN_C_SOURCE) || defined (__APPLE__)
372
typedef const FTSENT *FTSENTP;
373
#else
374
typedef const FTSENT *const FTSENTP;
375
#endif
376
377
static int
378
fts_compare(FTSENTP *a, FTSENTP *b)
379
{
380
/* Sort files before directories, then alpha order */
381
if ((*a)->fts_info != FTS_D && (*b)->fts_info == FTS_D)
382
return -1;
383
if ((*a)->fts_info == FTS_D && (*b)->fts_info != FTS_D)
384
return 1;
385
return (strcmp((*a)->fts_name, (*b)->fts_name));
386
}
387
388
struct pkg_repo_create *
389
pkg_repo_create_new(void)
390
{
391
struct pkg_repo_create *prc;
392
393
prc = xcalloc(1, sizeof(*prc));
394
prc->ofd = -1;
395
396
return (prc);
397
}
398
399
void
400
pkg_repo_create_free(struct pkg_repo_create *prc)
401
{
402
if (prc == NULL)
403
return;
404
pkg_repo_meta_free(prc->meta);
405
if (prc->ofd != -1)
406
close(prc->ofd);
407
ucl_object_unref(prc->groups);
408
free(prc);
409
}
410
411
static ucl_object_t*
412
ucl_load(int dfd, const char *name, ucl_object_t *schema)
413
{
414
struct ucl_parser *p;
415
ucl_object_t *obj = NULL;
416
int fd;
417
struct ucl_schema_error err;
418
419
fd = openat(dfd, name, O_RDONLY);
420
if (fd == -1) {
421
pkg_emit_error("Unable to open UCL file: %s", name);
422
return (NULL);
423
}
424
425
p = ucl_parser_new(0);
426
if (!ucl_parser_add_fd(p, fd)) {
427
pkg_emit_error("Error parsing UCL file '%s': %s'",
428
name, ucl_parser_get_error(p));
429
ucl_parser_free(p);
430
close(fd);
431
return (NULL);
432
}
433
close(fd);
434
435
obj = ucl_parser_get_object(p);
436
ucl_parser_free(p);
437
if (obj == NULL)
438
return (NULL);
439
440
if (!ucl_object_validate(schema, obj, &err)) {
441
pkg_emit_error("UCL definition %s cannot be validated: %s",
442
name, err.msg);
443
ucl_object_unref(obj);
444
return (NULL);
445
}
446
447
return (obj);
448
}
449
450
static const char group_schema_str[] = ""
451
"{"
452
" type = object;"
453
" properties: {"
454
" name: { type = string };"
455
" requires: { "
456
" type = array;"
457
" item = { type = string };"
458
" };"
459
" depends: { "
460
" type = array;"
461
" item = { type = string };"
462
" };"
463
" comment: { type = string };"
464
" };"
465
" required = [ name, comment ];"
466
"};";
467
468
static const char expired_schema_str[] = ""
469
"{"
470
" type = object;"
471
" properties: {"
472
" name: { type = string };"
473
" reason: { type = string };"
474
" replaced_by: { type = string };"
475
" };"
476
" required = [ name ];"
477
"};";
478
479
static ucl_object_t *
480
open_schema(const char* schema_str, size_t schema_str_len)
481
{
482
struct ucl_parser *parser;
483
ucl_object_t *schema;
484
parser = ucl_parser_new(UCL_PARSER_NO_FILEVARS);
485
if (!ucl_parser_add_chunk(parser, schema_str,
486
schema_str_len - 1)) {
487
pkg_emit_error("Cannot parse schema string: %s",
488
ucl_parser_get_error(parser));
489
ucl_parser_free(parser);
490
return (NULL);
491
}
492
schema = ucl_parser_get_object(parser);
493
ucl_parser_free(parser);
494
return (schema);
495
}
496
497
static void
498
read_ucl_dir(struct pkg_repo_create *prc, const char *path, ucl_object_t *schema, void (*callback)(struct pkg_repo_create *prc, ucl_object_t* parsed_obj))
499
{
500
int dfd = open(path, O_DIRECTORY);
501
DIR *d;
502
struct dirent *e;
503
struct stat st;
504
505
if (dfd == -1) {
506
pkg_emit_error("Unable to open directory '%s'", path);
507
return;
508
}
509
510
d = fdopendir(dfd);
511
if (d == NULL) {
512
pkg_emit_error("Unable to open directory '%s'", path);
513
close(dfd);
514
return;
515
}
516
517
while ((e = readdir(d)) != NULL) {
518
const char *ext;
519
ucl_object_t* parsed_obj;
520
/* ignore all hidden files */
521
if (e->d_name[0] == '.')
522
continue;
523
/* only consider files ending with .ucl */
524
ext = strrchr(e->d_name, '.');
525
if (ext == NULL)
526
continue;
527
if (strcmp(ext, ".ucl") != 0)
528
continue;
529
/* only regular files are considered */
530
if (fstatat(dfd, e->d_name, &st, AT_SYMLINK_NOFOLLOW) != 0) {
531
pkg_emit_errno("fstatat", e->d_name);
532
goto cleanup;
533
}
534
if (!S_ISREG(st.st_mode))
535
continue;
536
parsed_obj = ucl_load(dfd, e->d_name, schema);
537
if (parsed_obj)
538
callback (prc, parsed_obj);
539
}
540
cleanup:
541
closedir(d);
542
}
543
544
static void
545
append_groups(struct pkg_repo_create *prc, ucl_object_t* groups_obj)
546
{
547
if (prc->groups == NULL)
548
prc->groups = ucl_object_typed_new(UCL_ARRAY);
549
ucl_array_append(prc->groups, groups_obj);
550
}
551
552
void
553
pkg_repo_create_set_groups(struct pkg_repo_create *prc, const char *path)
554
{
555
ucl_object_t *schema;
556
schema = open_schema(group_schema_str, sizeof(group_schema_str));
557
558
read_ucl_dir(prc, path, schema, append_groups);
559
560
ucl_object_unref(schema);
561
}
562
563
static void
564
append_expired_packages(struct pkg_repo_create *prc, ucl_object_t* expired_packages_obj)
565
{
566
if (prc->expired_packages == NULL)
567
prc->expired_packages = ucl_object_typed_new(UCL_ARRAY);
568
ucl_array_append(prc->expired_packages, expired_packages_obj);
569
}
570
571
void
572
pkg_repo_create_set_expired_packages(struct pkg_repo_create *prc, const char *path)
573
{
574
ucl_object_t *schema;
575
schema = open_schema(expired_schema_str, sizeof(expired_schema_str));
576
577
read_ucl_dir(prc, path, schema, append_expired_packages);
578
579
ucl_object_unref(schema);
580
}
581
582
void
583
pkg_repo_create_set_create_filelist(struct pkg_repo_create *prc, bool val)
584
{
585
prc->filelist = val;
586
}
587
588
void
589
pkg_repo_create_set_hash(struct pkg_repo_create *prc, bool val)
590
{
591
prc->hash = val;
592
}
593
594
void
595
pkg_repo_create_set_hash_symlink(struct pkg_repo_create *prc, bool val)
596
{
597
prc->hash_symlink = val;
598
}
599
600
void
601
pkg_repo_create_set_output_dir(struct pkg_repo_create *prc, const char *out)
602
{
603
prc->outdir = out;
604
}
605
606
void
607
pkg_repo_create_set_metafile(struct pkg_repo_create *prc, const char *metafile)
608
{
609
prc->metafile = metafile;
610
}
611
612
void
613
pkg_repo_create_set_sign(struct pkg_repo_create *prc, char **argv, int argc, pkg_password_cb *cb)
614
{
615
prc->sign.argc = argc;
616
prc->sign.argv = argv;
617
prc->sign.cb = cb;
618
}
619
620
static int
621
pkg_repo_create_pack_and_sign(struct pkg_repo_create *prc)
622
{
623
char repo_path[MAXPATHLEN];
624
char repo_archive[MAXPATHLEN];
625
char *key_file;
626
const char *key_type;
627
struct pkgsign_ctx *sctx = NULL;
628
struct stat st;
629
int ret = EPKG_OK, nfile = 0;
630
const int files_to_pack = 4;
631
632
if (prc->sign.argc == 1) {
633
char *cpos;
634
635
key_type = key_file = prc->sign.argv[0];
636
if ((cpos = strchr(key_type, ':')) != NULL) {
637
key_file = cpos + 1;
638
*(key_file - 1) = '\0';
639
} else {
640
key_type = "rsa";
641
}
642
643
pkg_debug(1, "Loading %s key from '%s' for signing", key_type, key_file);
644
ret = pkgsign_new_sign(key_type, &sctx);
645
if (ret != EPKG_OK) {
646
pkg_emit_error("'%s' signer not found", key_type);
647
return (EPKG_FATAL);
648
}
649
650
pkgsign_set(sctx, prc->sign.cb, key_file);
651
ret = EPKG_OK;
652
}
653
654
if (prc->sign.argc > 1 && !STREQ(prc->sign.argv[0], "signing_command:"))
655
return (EPKG_FATAL);
656
657
if (prc->sign.argc > 1) {
658
prc->sign.argc--;
659
prc->sign.argv++;
660
}
661
662
pkg_emit_progress_start("Packing files for repository");
663
pkg_emit_progress_tick(nfile++, files_to_pack);
664
665
snprintf(repo_path, sizeof(repo_path), "%s/%s", prc->outdir,
666
prc->meta->manifests);
667
snprintf(repo_archive, sizeof(repo_archive), "%s/%s", prc->outdir,
668
prc->meta->manifests_archive);
669
if (pkg_repo_pack_db(prc->meta->manifests, repo_archive, repo_path, sctx, prc) != EPKG_OK) {
670
ret = EPKG_FATAL;
671
goto cleanup;
672
}
673
674
pkg_emit_progress_tick(nfile++, files_to_pack);
675
676
if (prc->filelist) {
677
snprintf(repo_path, sizeof(repo_path), "%s/%s", prc->outdir,
678
prc->meta->filesite);
679
snprintf(repo_archive, sizeof(repo_archive), "%s/%s",
680
prc->outdir, prc->meta->filesite_archive);
681
if (pkg_repo_pack_db(prc->meta->filesite, repo_archive, repo_path, sctx, prc) != EPKG_OK) {
682
ret = EPKG_FATAL;
683
goto cleanup;
684
}
685
}
686
687
pkg_emit_progress_tick(nfile++, files_to_pack);
688
snprintf(repo_path, sizeof(repo_path), "%s/%s", prc->outdir, prc->meta->data);
689
snprintf(repo_archive, sizeof(repo_archive), "%s/%s", prc->outdir,
690
prc->meta->data_archive);
691
if (pkg_repo_pack_db(prc->meta->data, repo_archive, repo_path, sctx, prc) != EPKG_OK) {
692
ret = EPKG_FATAL;
693
goto cleanup;
694
}
695
696
pkg_emit_progress_tick(nfile++, files_to_pack);
697
698
if (fstatat(prc->ofd, "meta.conf", &st, 0) == 0) {
699
struct timespec ts[2] = {
700
{
701
.tv_sec = st.st_mtime,
702
.tv_nsec = 0,
703
},
704
{
705
.tv_sec = st.st_mtime,
706
.tv_nsec = 0,
707
},
708
};
709
snprintf(repo_archive, sizeof(repo_archive), "%s.pkg",
710
prc->meta->manifests_archive);
711
utimensat(prc->ofd, repo_archive, ts, 0);
712
if (prc->filelist) {
713
snprintf(repo_archive, sizeof(repo_archive),
714
"%s.pkg", prc->meta->filesite_archive);
715
utimensat(prc->ofd, repo_archive, ts, 0);
716
}
717
snprintf(repo_archive, sizeof(repo_archive), "%s.pkg",
718
prc->meta->data_archive);
719
utimensat(prc->ofd, repo_archive, ts, 0);
720
}
721
722
cleanup:
723
pkg_emit_progress_tick(files_to_pack, files_to_pack);
724
725
pkgsign_free(sctx);
726
727
return (ret);
728
}
729
730
int
731
pkg_repo_create(struct pkg_repo_create *prc, char *path)
732
{
733
FTS *fts = NULL;
734
int num_workers;
735
pthread_t *threads;
736
struct thr_env te = { 0 };
737
size_t len;
738
int fd;
739
int dfd, ffd, mfd;
740
int retcode = EPKG_FATAL;
741
ucl_object_t *meta_dump;
742
char *repopath[2];
743
744
if (prc->outdir == NULL)
745
prc->outdir = path;
746
747
te.dfile = te.ffile = te.mfile = NULL;
748
749
if (!is_dir(path)) {
750
pkg_emit_error("%s is not a directory", path);
751
return (EPKG_FATAL);
752
}
753
754
errno = 0;
755
if (!is_dir(prc->outdir)) {
756
/* Try to create dir */
757
if (errno == ENOENT) {
758
if (mkdir(prc->outdir, 00755) == -1) {
759
pkg_fatal_errno("cannot create output directory %s",
760
prc->outdir);
761
}
762
}
763
else {
764
pkg_emit_error("%s is not a directory", prc->outdir);
765
return (EPKG_FATAL);
766
}
767
}
768
if ((prc->ofd = open(prc->outdir, O_DIRECTORY)) == -1) {
769
pkg_emit_error("Cannot open %s", prc->outdir);
770
return (EPKG_FATAL);
771
}
772
773
if (prc->metafile != NULL) {
774
fd = open(prc->metafile, O_RDONLY);
775
if (fd == -1) {
776
pkg_emit_error("meta loading error while trying %s", prc->metafile);
777
return (EPKG_FATAL);
778
}
779
if (pkg_repo_meta_load(fd, &prc->meta) != EPKG_OK) {
780
pkg_emit_error("meta loading error while trying %s", prc->metafile);
781
close(fd);
782
return (EPKG_FATAL);
783
}
784
close(fd);
785
} else {
786
prc->meta = pkg_repo_meta_default();
787
}
788
prc->meta->repopath = path;
789
prc->meta->hash = prc->hash;
790
prc->meta->hash_symlink = prc->hash_symlink;
791
792
te.meta = prc->meta;
793
794
/* initialize mutexes & conditions */
795
pthread_mutex_init(&te.nlock, 0);
796
pthread_mutex_init(&te.llock, 0);
797
pthread_mutex_init(&te.flock, 0);
798
pthread_cond_init(&te.cond, 0);
799
800
repopath[0] = path;
801
repopath[1] = NULL;
802
803
num_workers = pkg_object_int(pkg_config_get("WORKERS_COUNT"));
804
if (num_workers <= 0) {
805
num_workers = (int)sysconf(_SC_NPROCESSORS_ONLN);
806
if (num_workers == -1)
807
num_workers = 6;
808
}
809
810
if ((fts = fts_open(repopath, FTS_PHYSICAL|FTS_NOCHDIR, fts_compare)) == NULL) {
811
pkg_emit_errno("fts_open", path);
812
goto cleanup;
813
}
814
815
if ((mfd = openat(prc->ofd, prc->meta->manifests,
816
O_CREAT|O_TRUNC|O_WRONLY, 00644)) == -1) {
817
goto cleanup;
818
}
819
if ((te.mfile = fdopen(mfd,"w")) == NULL) {
820
goto cleanup;
821
}
822
823
if ((dfd = openat(prc->ofd, prc->meta->data,
824
O_CREAT|O_TRUNC|O_WRONLY, 00644)) == -1) {
825
goto cleanup;
826
}
827
if ((te.dfile = fdopen(dfd,"w")) == NULL) {
828
goto cleanup;
829
}
830
831
if (prc->filelist) {
832
if ((ffd = openat(prc->ofd, prc->meta->filesite,
833
O_CREAT|O_TRUNC|O_WRONLY, 00644)) == -1) {
834
goto cleanup;
835
}
836
if ((te.ffile = fdopen(ffd,"w")) == NULL) {
837
goto cleanup;
838
}
839
}
840
841
len = 0;
842
843
pkg_create_repo_read_fts(&te.fts_items, fts, path, &len, prc->meta);
844
845
if (len == 0) {
846
/* Nothing to do */
847
pkg_emit_error("No package files have been found");
848
goto cleanup;
849
}
850
851
/* Split items over all workers */
852
num_workers = MIN(num_workers, len);
853
854
/* Launch workers */
855
pkg_emit_progress_start("Creating repository in %s", prc->outdir);
856
857
threads = xcalloc(num_workers, sizeof(pthread_t));
858
859
struct ucl_emitter_functions *f;
860
ucl_object_t *obj = ucl_object_typed_new(UCL_OBJECT);
861
/*
862
* Work around a bug in the streamline exporter which creates an invalid
863
* json if there is nothing in the object, prior to the streamline to
864
* start. So always add at least an empty groups array
865
*/
866
ucl_object_insert_key(obj,
867
prc->groups == NULL ? ucl_object_typed_new(UCL_ARRAY) : prc->groups,
868
"groups", 0, false);
869
ucl_object_insert_key(obj,
870
prc->expired_packages == NULL ? ucl_object_typed_new(UCL_ARRAY) : prc->expired_packages,
871
"expired_packages", 0, false);
872
f = ucl_object_emit_file_funcs(te.dfile);
873
te.ctx = ucl_object_emit_streamline_new(obj, UCL_EMIT_JSON_COMPACT, f);
874
ucl_object_t *ar = ucl_object_typed_new(UCL_ARRAY);
875
ar->key = "packages";
876
ar->keylen = sizeof("packages") -1;
877
878
ucl_object_emit_streamline_start_container(te.ctx, ar);
879
880
for (int i = 0; i < num_workers; i++) {
881
/* Create new worker */
882
pthread_create(&threads[i], NULL, &pkg_create_repo_thread, &te);
883
}
884
885
pthread_mutex_lock(&te.nlock);
886
while (te.ntask < len) {
887
pthread_cond_wait(&te.cond, &te.nlock);
888
pkg_emit_progress_tick(te.ntask, len);
889
}
890
pthread_mutex_unlock(&te.nlock);
891
892
for (int i = 0; i < num_workers; i++)
893
pthread_join(threads[i], NULL);
894
free(threads);
895
ucl_object_emit_streamline_end_container(te.ctx);
896
pkg_emit_progress_tick(len, len);
897
ucl_object_emit_streamline_finish(te.ctx);
898
ucl_object_emit_funcs_free(f);
899
ucl_object_unref(obj);
900
ucl_object_unref(ar);
901
902
/* Write metafile */
903
904
fd = openat(prc->ofd, "meta", O_CREAT|O_TRUNC|O_CLOEXEC|O_WRONLY,
905
0644);
906
if (fd != -1) {
907
meta_dump = pkg_repo_meta_to_ucl(prc->meta);
908
ucl_object_emit_fd(meta_dump, UCL_EMIT_CONFIG, fd);
909
close(fd);
910
fd = openat(prc->ofd, "meta.conf",
911
O_CREAT|O_TRUNC|O_CLOEXEC|O_WRONLY, 0644);
912
if (fd != -1) {
913
ucl_object_emit_fd(meta_dump, UCL_EMIT_CONFIG, fd);
914
close(fd);;
915
} else {
916
pkg_emit_notice("cannot create metafile at 'meta.conf'");
917
}
918
ucl_object_unref(meta_dump);
919
}
920
else {
921
pkg_emit_notice("cannot create metafile at 'meta'");
922
}
923
retcode = EPKG_OK;
924
cleanup:
925
if (te.mfile != NULL)
926
fclose(te.mfile);
927
if (te.ffile != NULL)
928
fclose(te.ffile);
929
if (te.dfile != NULL)
930
fclose(te.dfile);
931
if (fts != NULL)
932
fts_close(fts);
933
934
vec_free_and_free(&te.fts_items, pkg_create_repo_fts_free);
935
936
if (retcode != EPKG_OK)
937
return (retcode);
938
939
return (pkg_repo_create_pack_and_sign(prc));
940
}
941
942
static int
943
pkg_repo_sign(const char *path, char **argv, int argc, char **sig, size_t *siglen,
944
char **sigtype, char **cert, size_t *certlen)
945
{
946
FILE *fps[2];
947
char *sha256;
948
xstring *cmd = NULL;
949
xstring *buf = NULL;
950
xstring *sigstr = NULL;
951
xstring *certstr = NULL;
952
xstring *typestr = NULL;
953
char *line = NULL;
954
size_t linecap = 0;
955
ssize_t linelen;
956
pid_t spid;
957
int i, pstatus, ret = EPKG_OK;
958
bool end_seen = false;
959
960
sha256 = pkg_checksum_file(path, PKG_HASH_TYPE_SHA256_HEX);
961
if (!sha256)
962
return (EPKG_FATAL);
963
964
cmd = xstring_new();
965
966
for (i = 0; i < argc; i++) {
967
if (strspn(argv[i], " \t\n") > 0)
968
fprintf(cmd->fp, " \"%s\" ", argv[i]);
969
else
970
fprintf(cmd->fp, " %s ", argv[i]);
971
}
972
973
fflush(cmd->fp);
974
if ((spid = process_spawn_pipe(fps, cmd->buf)) < 0) {
975
ret = EPKG_FATAL;
976
goto done;
977
}
978
979
fprintf(fps[1], "%s\n", sha256);
980
fflush(fps[1]);
981
982
sigstr = xstring_new();
983
certstr = xstring_new();
984
typestr = xstring_new();
985
986
while ((linelen = getline(&line, &linecap, fps[0])) > 0 ) {
987
if (STREQ(line, "SIGNATURE\n")) {
988
buf = sigstr;
989
continue;
990
} else if (STREQ(line, "CERT\n")) {
991
buf = certstr;
992
continue;
993
} else if (STREQ(line, "TYPE\n")) {
994
buf = typestr;
995
continue;
996
} else if (STREQ(line, "END\n")) {
997
end_seen = true;
998
break;
999
}
1000
if (buf != NULL) {
1001
fwrite(line, linelen, 1, buf->fp);
1002
}
1003
}
1004
free(line);
1005
1006
*sigtype = xstring_get(typestr);
1007
*cert = xstring_get_binary(certstr, certlen);
1008
*sig = xstring_get_binary(sigstr, siglen);
1009
1010
/*
1011
* cert could be DER-encoded rather than PEM, so strip off any trailing
1012
* END marker if we ran over it.
1013
*/
1014
if (!end_seen && *certlen >= 4 &&
1015
STREQ(&(*cert)[*certlen - 4], "END\n"))
1016
*certlen -= 4;
1017
1018
/* remove the latest \n */
1019
*siglen -= 1;
1020
1021
waitpid(spid, &pstatus, WNOHANG);
1022
fclose(fps[0]);
1023
fclose(fps[1]);
1024
1025
done:
1026
free(sha256);
1027
xstring_free(cmd);
1028
1029
return (ret);
1030
}
1031
1032
static int
1033
pack_sign(struct packing *pack, struct pkgsign_ctx *sctx, const char *path,
1034
const char *name)
1035
{
1036
unsigned char *sigret = NULL;
1037
const char *sigtype;
1038
size_t siglen = 0;
1039
struct iovec iov[2];
1040
char buf[32];
1041
int offset, size;
1042
1043
if (sctx == NULL)
1044
return (EPKG_FATAL);
1045
1046
if (pkgsign_sign(sctx, path, &sigret, &siglen) != EPKG_OK) {
1047
free(sigret);
1048
return (EPKG_FATAL);
1049
}
1050
1051
offset = 0;
1052
sigtype = pkgsign_impl_name(sctx);
1053
if (!STREQ(sigtype, "rsa")) {
1054
size = snprintf(buf, sizeof(buf), "%s%s$", PKGSIGN_HEAD, sigtype);
1055
if (size >= sizeof(buf)) {
1056
free(sigret);
1057
return (EPKG_FATAL);
1058
}
1059
1060
iov[offset].iov_base = buf;
1061
iov[offset++].iov_len = size;
1062
}
1063
1064
iov[offset].iov_base = sigret;
1065
iov[offset++].iov_len = siglen;
1066
1067
if (packing_append_iovec(pack, name, iov, offset) != EPKG_OK) {
1068
free(sigret);
1069
return (EPKG_FATAL);
1070
}
1071
free(sigret);
1072
1073
return (EPKG_OK);
1074
}
1075
1076
static int
1077
pack_command_sign(struct packing *pack, const char *path, char **argv, int argc,
1078
const char *name)
1079
{
1080
size_t pub_len = 0, signature_len = 0;
1081
char fname[MAXPATHLEN];
1082
char *sig, *sigtype, *pub;
1083
char buf[32];
1084
struct iovec iov[2];
1085
int offset, size;
1086
1087
sig = NULL;
1088
pub = NULL;
1089
1090
if (pkg_repo_sign(path, argv, argc, &sig, &signature_len, &sigtype, &pub,
1091
&pub_len) != EPKG_OK) {
1092
free(sig);
1093
free(pub);
1094
return (EPKG_FATAL);
1095
}
1096
1097
offset = 0;
1098
snprintf(fname, sizeof(fname), "%s.sig", name);
1099
if (*sigtype != '\0' && !STREQ(sigtype, "rsa")) {
1100
int typelen;
1101
1102
typelen = strlen(sigtype);
1103
if (sigtype[typelen - 1] == '\n')
1104
sigtype[--typelen] = '\0';
1105
size = snprintf(buf, sizeof(buf), "%s%s$", PKGSIGN_HEAD, sigtype);
1106
free(sigtype);
1107
if (size >= sizeof(buf)) {
1108
free(sig);
1109
free(pub);
1110
return (EPKG_FATAL);
1111
}
1112
1113
iov[offset].iov_base = buf;
1114
iov[offset++].iov_len = size;
1115
} else {
1116
free(sigtype);
1117
}
1118
1119
iov[offset].iov_base = sig;
1120
iov[offset].iov_len = signature_len;
1121
1122
if (packing_append_iovec(pack, fname, iov, offset + 1) != EPKG_OK) {
1123
free(sig);
1124
free(pub);
1125
return (EPKG_FATAL);
1126
}
1127
free(sig);
1128
1129
snprintf(fname, sizeof(fname), "%s.pub", name);
1130
iov[offset].iov_base = pub;
1131
iov[offset].iov_len = pub_len;
1132
if (packing_append_iovec(pack, fname, iov, offset + 1) != EPKG_OK) {
1133
free(pub);
1134
return (EPKG_FATAL);
1135
}
1136
free(pub);
1137
1138
return (EPKG_OK);
1139
}
1140
1141
static int
1142
pkg_repo_pack_db(const char *name, const char *archive, char *path,
1143
struct pkgsign_ctx *sctx, struct pkg_repo_create *prc)
1144
{
1145
struct packing *pack;
1146
int ret = EPKG_OK;
1147
1148
if (packing_init(&pack, archive, prc->meta->packing_format, 0, 0, (time_t)-1, true, true) != EPKG_OK)
1149
return (EPKG_FATAL);
1150
1151
if (sctx != NULL) {
1152
ret = pack_sign(pack, sctx, path, "signature");
1153
} else if (prc->sign.argc >= 1) {
1154
ret = pack_command_sign(pack, path, prc->sign.argv, prc->sign.argc, name);
1155
}
1156
packing_append_file_attr(pack, path, name, "root", "wheel", 0644, 0);
1157
1158
packing_finish(pack);
1159
unlink(path);
1160
1161
return (ret);
1162
}
1163
1164