Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/pkg
Path: blob/main/libpkg/packing.c
2646 views
1
/*-
2
* Copyright (c) 2011-2025 Baptiste Daroussin <[email protected]>
3
* Copyright (c) 2011 Will Andrews <[email protected]>
4
* All rights reserved.
5
*
6
* Redistribution and use in source and binary forms, with or without
7
* modification, are permitted provided that the following conditions
8
* are met:
9
* 1. Redistributions of source code must retain the above copyright
10
* notice, this list of conditions and the following disclaimer
11
* in this position and unchanged.
12
* 2. Redistributions in binary form must reproduce the above copyright
13
* notice, this list of conditions and the following disclaimer in the
14
* documentation and/or other materials provided with the distribution.
15
*
16
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
17
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19
* IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
20
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
*/
27
28
#include <sys/uio.h>
29
30
#include <archive.h>
31
#include <archive_entry.h>
32
#include <assert.h>
33
#include <bsd_compat.h>
34
#include <fcntl.h>
35
#include <fts.h>
36
#include <string.h>
37
#include <pwd.h>
38
#include <grp.h>
39
#include <errno.h>
40
41
#include "pkg.h"
42
#include "private/event.h"
43
#include "private/pkg.h"
44
#include "private/packing.h"
45
46
#define dbg(x, ...) pkg_dbg(PKG_DBG_PACKING, x, __VA_ARGS__);
47
48
int
49
packing_init(struct packing **pack, const char *path, pkg_formats format, int clevel,
50
int threads, time_t timestamp, bool overwrite)
51
{
52
char archive_path[MAXPATHLEN];
53
char *archive_name;
54
const char *ext;
55
const char *source_date_epoch;
56
char *endptr;
57
time_t ts;
58
59
assert(pack != NULL);
60
61
*pack = xcalloc(1, sizeof(struct packing));
62
(*pack)->timestamp = timestamp;
63
64
if ((*pack)->timestamp == (time_t)-1 &&
65
(source_date_epoch = getenv("SOURCE_DATE_EPOCH")) != NULL) {
66
ts = (time_t)strtoimax(source_date_epoch, &endptr, 10);
67
if (*endptr != '\0') {
68
pkg_emit_error("Ignoring bad environment variable "
69
"SOURCE_DATE_EPOCH: %s", source_date_epoch);
70
} else {
71
(*pack)->timestamp = ts;
72
}
73
}
74
75
(*pack)->aread = archive_read_disk_new();
76
archive_read_disk_set_standard_lookup((*pack)->aread);
77
archive_read_disk_set_symlink_physical((*pack)->aread);
78
79
(*pack)->awrite = archive_write_new();
80
archive_write_set_format_pax_restricted((*pack)->awrite);
81
ext = packing_set_format((*pack)->awrite, format, clevel, threads);
82
if (ext == NULL) {
83
archive_read_close((*pack)->aread);
84
archive_read_free((*pack)->aread);
85
archive_write_close((*pack)->awrite);
86
archive_write_free((*pack)->awrite);
87
free(*pack);
88
*pack = NULL;
89
return (EPKG_FATAL); /* error set by _set_format() */
90
}
91
snprintf(archive_path, sizeof(archive_path), "%s.pkg", path);
92
archive_name = strrchr(archive_path, '/');
93
if (archive_name == NULL)
94
archive_name = archive_path;
95
else
96
archive_name++;
97
98
if (!overwrite && access(archive_path, F_OK) == 0) {
99
archive_read_close((*pack)->aread);
100
archive_read_free((*pack)->aread);
101
archive_write_close((*pack)->awrite);
102
archive_write_free((*pack)->awrite);
103
free(*pack);
104
*pack = NULL;
105
errno = EEXIST;
106
return (EPKG_EXIST);
107
}
108
dbg(1, "target file '%s'", archive_path);
109
if (archive_write_open_filename(
110
(*pack)->awrite, archive_path) != ARCHIVE_OK) {
111
pkg_emit_errno("archive_write_open_filename",
112
archive_path);
113
archive_read_close((*pack)->aread);
114
archive_read_free((*pack)->aread);
115
archive_write_close((*pack)->awrite);
116
archive_write_free((*pack)->awrite);
117
*pack = NULL;
118
return EPKG_FATAL;
119
}
120
121
(*pack)->resolver = archive_entry_linkresolver_new();
122
archive_entry_linkresolver_set_strategy((*pack)->resolver,
123
archive_format((*pack)->awrite));
124
125
return (EPKG_OK);
126
}
127
128
int
129
packing_append_iovec(struct packing *pack, const char *path, struct iovec *iov,
130
int niov)
131
{
132
struct archive_entry *entry;
133
int ret = EPKG_OK, size = 0;
134
135
dbg(1, "adding file '%s'", path);
136
for (int idx = 0; idx < niov; idx++) {
137
size += iov[idx].iov_len;
138
}
139
entry = archive_entry_new();
140
archive_entry_clear(entry);
141
archive_entry_set_filetype(entry, AE_IFREG);
142
archive_entry_set_perm(entry, 0644);
143
archive_entry_set_gname(entry, "wheel");
144
archive_entry_set_uname(entry, "root");
145
archive_entry_set_pathname(entry, path);
146
archive_entry_set_size(entry, size);
147
if (archive_write_header(pack->awrite, entry) == -1) {
148
pkg_emit_errno("archive_write_header", path);
149
ret = EPKG_FATAL;
150
goto cleanup;
151
}
152
153
for (int idx = 0; idx < niov; idx++) {
154
const char *buffer;
155
156
buffer = iov[idx].iov_base;
157
size = iov[idx].iov_len;
158
if (archive_write_data(pack->awrite, buffer, size) == -1) {
159
pkg_emit_errno("archive_write_data", path);
160
ret = EPKG_FATAL;
161
}
162
}
163
164
cleanup:
165
archive_entry_free(entry);
166
167
return (ret);
168
}
169
170
int
171
packing_append_buffer(struct packing *pack, const char *buffer,
172
const char *path, int size)
173
{
174
struct iovec iov;
175
176
dbg(1, "adding file '%s'", path);
177
iov.iov_base = __DECONST(char *, buffer);
178
iov.iov_len = size;
179
return (packing_append_iovec(pack, path, &iov, 1));
180
}
181
182
int
183
packing_append_file_attr(struct packing *pack, const char *filepath,
184
const char *newpath, const char *uname, const char *gname, mode_t perm,
185
u_long fflags)
186
{
187
int fd;
188
int retcode = EPKG_OK;
189
int ret;
190
struct stat st;
191
struct archive_entry *entry, *sparse_entry;
192
bool unset_timestamp;
193
char buf[32768];
194
int len;
195
196
entry = archive_entry_new();
197
archive_entry_copy_sourcepath(entry, filepath);
198
199
dbg(1, "adding file '%s'", filepath);
200
201
if (lstat(filepath, &st) != 0) {
202
pkg_emit_errno("lstat", filepath);
203
retcode = EPKG_FATAL;
204
goto cleanup;
205
}
206
207
ret = archive_read_disk_entry_from_file(pack->aread, entry, -1,
208
&st);
209
if (ret != ARCHIVE_OK) {
210
pkg_emit_error("%s: %s", filepath,
211
archive_error_string(pack->aread));
212
retcode = EPKG_FATAL;
213
goto cleanup;
214
}
215
216
if (newpath != NULL)
217
archive_entry_set_pathname(entry, newpath);
218
219
if (archive_entry_filetype(entry) != AE_IFREG) {
220
archive_entry_set_size(entry, 0);
221
}
222
223
if (uname != NULL && uname[0] != '\0') {
224
archive_entry_set_uname(entry, uname);
225
}
226
#ifdef __FreeBSD__
227
/*
228
* Set this so that libarchive does not embed the current user's ID,
229
* breaking reproducibility.
230
*/
231
archive_entry_set_uid(entry, 65534 /* nobody */);
232
#endif
233
234
if (gname != NULL && gname[0] != '\0') {
235
archive_entry_set_gname(entry, gname);
236
}
237
#ifdef __FreeBSD__
238
/*
239
* Set this so that libarchive does not embed the current user's ID,
240
* breaking reproducibility.
241
*/
242
archive_entry_set_gid(entry, 65534 /* nobody */);
243
#endif
244
245
if (fflags > 0)
246
archive_entry_set_fflags(entry, fflags, 0);
247
248
if (perm != 0)
249
archive_entry_set_perm(entry, perm);
250
251
unset_timestamp = pkg_object_bool(pkg_config_get("UNSET_TIMESTAMP"));
252
253
if (unset_timestamp) {
254
archive_entry_unset_atime(entry);
255
archive_entry_unset_ctime(entry);
256
archive_entry_unset_mtime(entry);
257
archive_entry_unset_birthtime(entry);
258
}
259
260
if (pack->timestamp != (time_t) -1) {
261
archive_entry_set_atime(entry, pack->timestamp, 0);
262
archive_entry_set_ctime(entry, pack->timestamp, 0);
263
archive_entry_set_mtime(entry, pack->timestamp, 0);
264
archive_entry_set_birthtime(entry, pack->timestamp, 0);
265
}
266
267
archive_entry_linkify(pack->resolver, &entry, &sparse_entry);
268
269
if (sparse_entry != NULL && entry == NULL)
270
entry = sparse_entry;
271
272
archive_write_header(pack->awrite, entry);
273
274
if (archive_entry_size(entry) <= 0)
275
goto cleanup;
276
277
if ((fd = open(filepath, O_RDONLY)) < 0) {
278
pkg_emit_errno("open", filepath);
279
retcode = EPKG_FATAL;
280
goto cleanup;
281
}
282
283
while ((len = read(fd, buf, sizeof(buf))) > 0) {
284
if (archive_write_data(pack->awrite, buf, len) == -1) {
285
pkg_emit_errno("archive_write_data", "archive write error");
286
retcode = EPKG_FATAL;
287
break;
288
}
289
}
290
291
if (len == -1) {
292
pkg_emit_errno("read", "file read error");
293
retcode = EPKG_FATAL;
294
}
295
close(fd);
296
297
cleanup:
298
archive_entry_free(entry);
299
return (retcode);
300
}
301
302
void
303
packing_finish(struct packing *pack)
304
{
305
if (pack == NULL)
306
return;
307
308
archive_read_close(pack->aread);
309
archive_read_free(pack->aread);
310
archive_entry_linkresolver_free(pack->resolver);
311
312
archive_write_close(pack->awrite);
313
archive_write_free(pack->awrite);
314
315
free(pack);
316
}
317
318
const char *
319
packing_set_format(struct archive *a, pkg_formats format, int clevel, int threads)
320
{
321
const char *notsupp_fmt = "%s is not supported, trying %s";
322
const char *notbltin_fmt = "%s is supported, but not builtin";
323
int error;
324
325
pkg_formats elected_format;
326
327
/*
328
* For several of these formats, ARCHIVE_WARN will be returned when an
329
* external program will be used to satisfy the request.
330
*/
331
switch (format) {
332
case TZS:
333
#ifdef HAVE_ARCHIVE_WRITE_ADD_FILTER_ZSTD
334
error = archive_write_add_filter_zstd(a);
335
if (error == ARCHIVE_OK) {
336
elected_format = TZS;
337
if (clevel == -1)
338
clevel = 19;
339
goto out;
340
} else if (error == ARCHIVE_WARN) {
341
pkg_emit_error(notbltin_fmt, "zstd");
342
return (NULL);
343
}
344
#endif
345
pkg_emit_error(notsupp_fmt, "zstd", "xz");
346
/* FALLTHRU */
347
case TXZ:
348
if (archive_write_add_filter_xz(a) == ARCHIVE_OK) {
349
elected_format = TXZ;
350
goto out;
351
}
352
pkg_emit_error(notsupp_fmt, "xz", "bzip2");
353
/* FALLTHRU */
354
case TBZ:
355
error = archive_write_add_filter_bzip2(a);
356
if (error == ARCHIVE_OK) {
357
elected_format = TBZ;
358
goto out;
359
} else if (error == ARCHIVE_WARN) {
360
pkg_emit_error(notbltin_fmt, "bzip2");
361
return (NULL);
362
}
363
pkg_emit_error(notsupp_fmt, "bzip2", "gzip");
364
/* FALLTHRU */
365
case TGZ:
366
error = archive_write_add_filter_gzip(a);
367
if (error == ARCHIVE_OK) {
368
elected_format = TGZ;
369
goto out;
370
} else if (error == ARCHIVE_WARN) {
371
pkg_emit_error(notbltin_fmt, "gzip");
372
return (NULL);
373
}
374
pkg_emit_error(notsupp_fmt, "gzip", "plain tar");
375
/* FALLTHRU */
376
case TAR:
377
archive_write_add_filter_none(a);
378
elected_format = TAR;
379
break;
380
default:
381
return (NULL);
382
}
383
384
out:
385
if (clevel == -1)
386
clevel = 0;
387
/*
388
* N.B., we only want to whine about this if the user actually selected
389
* tar and specified a compress level. If we had to fallback to tar,
390
* that's not the user's fault.
391
*/
392
if (format == TAR && clevel != 0)
393
pkg_emit_error("Plain tar and a compression level does not make sense");
394
395
if (elected_format != TAR) {
396
char buf[16];
397
if (clevel != 0) {
398
/*
399
* A bit of a kludge but avoids dragging in headers for all of
400
* these libraries.
401
*/
402
if (clevel == INT_MIN) {
403
switch (elected_format) {
404
case TZS:
405
clevel = -5;
406
break;
407
case TXZ:
408
case TBZ:
409
case TGZ:
410
clevel = 1;
411
break;
412
default:
413
__unreachable();
414
}
415
} else if (clevel == INT_MAX) {
416
switch (elected_format) {
417
case TZS:
418
clevel = 19;
419
break;
420
case TXZ:
421
case TBZ:
422
case TGZ:
423
clevel = 9;
424
break;
425
default:
426
__unreachable();
427
}
428
}
429
430
snprintf(buf, sizeof(buf), "%d", clevel);
431
if (archive_write_set_filter_option(a, NULL, "compression-level", buf) != ARCHIVE_OK)
432
pkg_emit_error("bad compression-level %d", clevel);
433
}
434
if (threads >= 0) {
435
snprintf(buf, sizeof(buf), "%d", threads);
436
if (archive_write_set_filter_option(a, NULL, "threads", buf) != ARCHIVE_OK)
437
pkg_emit_error("bad threads value %d", threads);
438
}
439
}
440
441
return (packing_format_to_string(elected_format));
442
}
443
444
pkg_formats
445
packing_format_from_string(const char *str)
446
{
447
if (str == NULL)
448
return DEFAULT_COMPRESSION;
449
if (STREQ(str, "tzst"))
450
return TZS;
451
if (STREQ(str, "txz"))
452
return TXZ;
453
if (STREQ(str, "tbz"))
454
return TBZ;
455
if (STREQ(str, "tgz"))
456
return TGZ;
457
if (STREQ(str, "tar"))
458
return TAR;
459
pkg_emit_error("unknown format %s, using txz", str);
460
return TXZ;
461
}
462
463
bool
464
packing_is_valid_format(const char *str)
465
{
466
if (str == NULL)
467
return (false);
468
if (STREQ(str, "pkg") ||
469
STREQ(str, "tzst") ||
470
STREQ(str, "txz") ||
471
STREQ(str, "tbz") ||
472
STREQ(str, "tgz") ||
473
STREQ(str, "tar"))
474
return (true);
475
return (false);
476
}
477
478
const char*
479
packing_format_to_string(pkg_formats format)
480
{
481
const char *res = NULL;
482
483
switch (format) {
484
case TZS:
485
res = "tzst";
486
break;
487
case TXZ:
488
res = "txz";
489
break;
490
case TBZ:
491
res = "tbz";
492
break;
493
case TGZ:
494
res = "tgz";
495
break;
496
case TAR:
497
res = "tar";
498
break;
499
}
500
501
return (res);
502
}
503
504