]> git.ipfire.org Git - people/ms/pakfire.git/blame - src/libpakfire/packager.c
packager: Set installsize to total size of the filelist
[people/ms/pakfire.git] / src / libpakfire / packager.c
CommitLineData
6aeb48e6
MT
1/*#############################################################################
2# #
3# Pakfire - The IPFire package management system #
4# Copyright (C) 2021 Pakfire development team #
5# #
6# This program is free software: you can redistribute it and/or modify #
7# it under the terms of the GNU General Public License as published by #
8# the Free Software Foundation, either version 3 of the License, or #
9# (at your option) any later version. #
10# #
11# This program is distributed in the hope that it will be useful, #
12# but WITHOUT ANY WARRANTY; without even the implied warranty of #
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14# GNU General Public License for more details. #
15# #
16# You should have received a copy of the GNU General Public License #
17# along with this program. If not, see <http://www.gnu.org/licenses/>. #
18# #
19#############################################################################*/
20
21#include <errno.h>
1ea1f35b
MT
22#include <fcntl.h>
23#include <linux/limits.h>
6aeb48e6 24#include <stdlib.h>
da08f989
MT
25#include <sys/stat.h>
26#include <sys/types.h>
27#include <unistd.h>
6aeb48e6 28
1ea1f35b 29#include <archive.h>
da08f989 30#include <archive_entry.h>
1ea1f35b 31
2adc4a4a 32#include <pakfire/archive.h>
436677a3 33#include <pakfire/constants.h>
1ba1869e
MT
34#include <pakfire/file.h>
35#include <pakfire/filelist.h>
1ea1f35b 36#include <pakfire/logging.h>
6aeb48e6
MT
37#include <pakfire/package.h>
38#include <pakfire/packager.h>
39#include <pakfire/pakfire.h>
40#include <pakfire/private.h>
677e9e5b 41#include <pakfire/pwd.h>
6aeb48e6
MT
42#include <pakfire/types.h>
43
da08f989
MT
44#define BUFFER_SIZE 64 * 1024
45
6aeb48e6
MT
46struct pakfire_packager {
47 Pakfire pakfire;
48 int nrefs;
49
50 PakfirePackage pkg;
1ba1869e 51 PakfireFilelist filelist;
1ea1f35b 52
738b3582
MT
53 // Reader
54 struct archive* reader;
55
56 // Payload
1ea1f35b 57 struct archive* payload;
7836e21b 58 FILE* fpayload;
6aeb48e6
MT
59};
60
1ea1f35b 61static int pakfire_packager_create_payload(struct pakfire_packager* p, int compress) {
7836e21b
MT
62 char path[] = "/tmp/.pakfire-payload.XXXXXX";
63
1ea1f35b 64 p->payload = archive_write_new();
427fdd80
MT
65 if (!p->payload) {
66 ERROR(p->pakfire, "archive_write_new() failed\n");
67 return 1;
68 }
1ea1f35b
MT
69
70 // Use the PAX format
71 int r = archive_write_set_format_pax(p->payload);
72 if (r) {
73 ERROR(p->pakfire, "Could not set format to PAX: %s\n",
74 archive_error_string(p->payload));
75 return r;
76 }
77
78 // Add filters to compress the payload
79 if (compress) {
80 // Enable Zstd
81 r = archive_write_add_filter_zstd(p->payload);
82 if (r) {
83 ERROR(p->pakfire, "Could not enable Zstandard compression: %s\n",
84 archive_error_string(p->payload));
85 return r;
86 }
87
88 // Set compression level to highest
89 r = archive_write_set_filter_option(p->payload, NULL, "compression-level", "19");
90 if (r) {
91 ERROR(p->pakfire, "Could not set Zstandard compression level: %s\n",
92 archive_error_string(p->payload));
93 return r;
94 }
95 }
96
7836e21b
MT
97 // Create a new temporary file
98 int fd = mkostemp(path, O_CLOEXEC);
1ea1f35b
MT
99 if (fd < 0) {
100 ERROR(p->pakfire, "mkostemp() failed: %s\n", strerror(errno));
101 return 1;
102 }
103
7836e21b
MT
104 // Unlink the file straight away
105 unlink(path);
106
107 // Convert the file descriptor into a file handle
108 p->fpayload = fdopen(fd, "w+");
109 if (!p->fpayload) {
110 close(fd);
111
112 return 1;
113 }
114
115 // Write archive to file
116 r = archive_write_open_FILE(p->payload, p->fpayload);
1ea1f35b
MT
117 if (r)
118 return r;
119
120 return 0;
121}
122
123static void pakfire_packager_free(struct pakfire_packager* packager) {
124 if (packager->payload)
125 archive_write_free(packager->payload);
126
7836e21b
MT
127 if (packager->fpayload)
128 fclose(packager->fpayload);
129
738b3582
MT
130 if (packager->reader)
131 archive_free(packager->reader);
132
1ba1869e 133 pakfire_filelist_unref(packager->filelist);
1ea1f35b
MT
134 pakfire_package_unref(packager->pkg);
135 pakfire_unref(packager->pakfire);
136}
137
677e9e5b
MT
138static const char* pakfire_packager_user_lookup(void* data, la_int64_t uid) {
139 Pakfire pakfire = (Pakfire)data;
140
141 // Fast path for "root"
142 if (uid == 0)
143 return "root";
144
145 // Find a matching entry in /etc/passwd
146 struct passwd* entry = pakfire_getpwuid(pakfire, uid);
147 if (!entry) {
148 ERROR(pakfire, "Could not retrieve uname for %ld: %s\n", uid, strerror(errno));
149 return 0;
150 }
151
152 DEBUG(pakfire, "Mapping UID %ld to %s\n", uid, entry->pw_name);
153
154 return entry->pw_name;
155}
156
157static const char* pakfire_packager_group_lookup(void* data, la_int64_t gid) {
158 Pakfire pakfire = (Pakfire)data;
159
160 // Fast path for "root"
161 if (gid == 0)
162 return "root";
163
164 // Find a matching entry in /etc/group
165 struct group* entry = pakfire_getgrgid(pakfire, gid);
166 if (!entry) {
167 ERROR(pakfire, "Could not retrieve gname for %ld: %s\n", gid, strerror(errno));
168 return 0;
169 }
170
171 DEBUG(pakfire, "Mapping GID %ld to %s\n", gid, entry->gr_name);
172
173 return entry->gr_name;
174}
175
0682aeb3
MT
176static int pakfire_packager_create_reader(struct pakfire_packager* p) {
177 // Open a reader
178 p->reader = archive_read_disk_new();
179 if (!p->reader) {
180 ERROR(p->pakfire, "archive_read_disk_new() failed\n");
181 return 1;
182 }
183
184 // Do not read fflags
185 int r = archive_read_disk_set_behavior(p->reader, ARCHIVE_READDISK_NO_FFLAGS);
186 if (r) {
187 ERROR(p->pakfire, "Could not change behavior of reader: %s\n",
188 archive_error_string(p->reader));
189 return 1;
190 }
191
677e9e5b
MT
192 // Install our own routine for user/group lookups
193 archive_read_disk_set_uname_lookup(p->reader, p->pakfire,
194 pakfire_packager_user_lookup, NULL);
195 archive_read_disk_set_gname_lookup(p->reader, p->pakfire,
196 pakfire_packager_group_lookup, NULL);
197
0682aeb3
MT
198 return 0;
199}
200
6aeb48e6
MT
201PAKFIRE_EXPORT int pakfire_packager_create(struct pakfire_packager** packager,
202 PakfirePackage pkg) {
203 struct pakfire_packager* p = calloc(1, sizeof(*p));
204 if (!p)
205 return ENOMEM;
206
207 // Initialize reference counting
208 p->nrefs = 1;
209
210 // Store a reference to Pakfire
211 p->pakfire = pakfire_package_get_pakfire(pkg);
212
213 // Store a reference to the package
214 p->pkg = pakfire_package_ref(pkg);
215
1ba1869e 216 // Create a new filelist
883b3be9 217 int r = pakfire_filelist_create(&p->filelist, p->pakfire);
1ba1869e
MT
218 if (r)
219 goto ERROR;
220
0682aeb3
MT
221 // Create reader
222 r = pakfire_packager_create_reader(p);
1ba1869e
MT
223 if (r)
224 goto ERROR;
1ea1f35b 225
0682aeb3
MT
226 // Start payload
227 r = pakfire_packager_create_payload(p, 1);
228 if (r)
738b3582 229 goto ERROR;
738b3582 230
6aeb48e6
MT
231 *packager = p;
232
233 return 0;
1ba1869e
MT
234
235ERROR:
236 pakfire_packager_free(p);
237
238 return r;
6aeb48e6
MT
239}
240
6aeb48e6
MT
241PAKFIRE_EXPORT struct pakfire_packager* pakfire_packager_ref(
242 struct pakfire_packager* packager) {
243 ++packager->nrefs;
244
245 return packager;
246}
247
248PAKFIRE_EXPORT struct pakfire_packager* pakfire_packager_unref(
249 struct pakfire_packager* packager) {
250 if (--packager->nrefs > 0)
251 return packager;
252
253 pakfire_packager_free(packager);
254
255 return NULL;
256}
da08f989 257
7836e21b
MT
258static int pakfire_packager_copy_data(struct pakfire_packager* packager,
259 struct archive* a, FILE* f) {
260 char buffer[BUFFER_SIZE];
261
262 while (!feof(f)) {
263 // Read a block from file
264 size_t bytes_read = fread(buffer, 1, sizeof(buffer), f);
265
266 // Check if any error occured
267 if (ferror(f)) {
268 ERROR(packager->pakfire, "Error reading from file: %s\n", strerror(errno));
269 return 1;
270 }
271
272 // Write the block to the archive
273 ssize_t bytes_written = archive_write_data(a, buffer, bytes_read);
274 if (bytes_written < 0) {
275 ERROR(packager->pakfire, "Error writing data to archive: %s\n",
276 archive_error_string(a));
277 return 1;
278 }
279 }
280
281 return 0;
282}
283
436677a3
MT
284static int pakfire_packager_write_format(struct pakfire_packager* packager,
285 struct archive* a) {
286 const char buffer[] = TO_STRING(PACKAGE_FORMAT) "\n";
287
288 // Create a new file entry
289 struct archive_entry* entry = archive_entry_new();
290 if (!entry)
291 return 1;
292
293 // Set filename
b36d7344 294 archive_entry_set_pathname(entry, PAKFIRE_ARCHIVE_FN_FORMAT);
436677a3
MT
295
296 // This is a regular file
297 archive_entry_set_filetype(entry, AE_IFREG);
298 archive_entry_set_perm(entry, 0644);
299
300 // Set length
2adc4a4a 301 archive_entry_set_size(entry, strlen(buffer));
436677a3
MT
302
303 // This is the end of the header
304 int r = archive_write_header(a, entry);
305 if (r) {
306 ERROR(packager->pakfire, "Error writing header: %s\n", archive_error_string(a));
307 archive_entry_free(entry);
308 return r;
309 }
310
311 // Write content
312 r = archive_write_data(a, buffer, strlen(buffer));
313 if (r < 0) {
314 ERROR(packager->pakfire, "Error writing data: %s\n", archive_error_string(a));
315 archive_entry_free(entry);
316 return r;
317 }
318
319 archive_entry_free(entry);
320
321 return 0;
322}
323
2adc4a4a
MT
324static int pakfire_packager_write_payload(struct pakfire_packager* packager,
325 struct archive* a) {
326 struct stat st;
327
328 // Close the payload
329 if (packager->payload) {
330 archive_write_free(packager->payload);
331 packager->payload = NULL;
332 }
333
334 // Reset fd to beginning of the file
335 rewind(packager->fpayload);
336
337 int fd = fileno(packager->fpayload);
338
339 // Stat the payload file
340 int r = fstat(fd, &st);
341 if (r) {
342 ERROR(packager->pakfire, "stat() on fd %d failed: %s\n", fd, strerror(errno));
343 return 1;
344 }
345
346 // Create a new file entry
347 struct archive_entry* entry = archive_entry_new();
348 if (!entry)
349 return 1;
350
351 // Set filename
352 archive_entry_set_pathname(entry, PAKFIRE_ARCHIVE_FN_PAYLOAD);
353
354 // This is a regular file
355 archive_entry_set_filetype(entry, AE_IFREG);
356 archive_entry_set_perm(entry, 0644);
357
358 // Set the file size
359 archive_entry_set_size(entry, st.st_size);
360
361 // This is the end of the header
362 r = archive_write_header(a, entry);
363 if (r) {
364 ERROR(packager->pakfire, "Error writing header: %s\n", archive_error_string(a));
365 goto ERROR;
366 }
367
368 // Copy data
369 r = pakfire_packager_copy_data(packager, a, packager->fpayload);
370 if (r)
371 goto ERROR;
372
373 // Success
374 r = 0;
375
376ERROR:
377 archive_entry_free(entry);
378
379 return r;
380}
381
436677a3
MT
382/*
383 This function is being called at the end when all data has been added to the package.
384
385 It will create a new archive and write the package to the given file descriptor.
386*/
387PAKFIRE_EXPORT char* pakfire_packager_finish(struct pakfire_packager* packager, FILE* f) {
388 char* filename = NULL;
389
fafe383d
MT
390 // Store total instal size
391 pakfire_package_set_installsize(packager->pkg,
392 pakfire_filelist_total_filesize(packager->filelist));
393
436677a3
MT
394 // Open a new archive
395 struct archive* a = archive_write_new();
396 if (!a) {
397 ERROR(packager->pakfire, "archive_write_new() failed\n");
398 goto ERROR;
399 }
400
401 // Use the PAX format
402 int r = archive_write_set_format_pax(a);
403 if (r) {
404 ERROR(packager->pakfire, "Could not set format to PAX: %s\n",
405 archive_error_string(a));
406 goto ERROR;
407 }
408
409 // Write archive to f
410 r = archive_write_open_FILE(a, f);
411 if (r) {
412 ERROR(packager->pakfire, "archive_write_open_FILE() failed: %s\n",
413 archive_error_string(a));
414 goto ERROR;
415 }
416
417 // Start with the format file
418 r = pakfire_packager_write_format(packager, a);
419 if (r)
420 goto ERROR;
421
2adc4a4a
MT
422 // Write the payload
423 r = pakfire_packager_write_payload(packager, a);
424 if (r)
425 goto ERROR;
426
436677a3
MT
427 // XXX set filename
428
429ERROR:
430 if (a)
431 archive_free(a);
432
433 return filename;
434}
435
da08f989
MT
436PAKFIRE_EXPORT int pakfire_packager_add(struct pakfire_packager* packager,
437 const char* path) {
438 FILE* f = NULL;
da08f989
MT
439
440 // Check if path is set
441 if (!path)
442 return EINVAL;
443
7836e21b
MT
444 // Payload has already been closed
445 if (!packager->payload)
446 return EINVAL;
447
da08f989
MT
448 // Create a new file entry
449 struct archive_entry* entry = archive_entry_new();
450 if (!entry)
451 return ENOMEM;
452
453 // Set path in archive
454 archive_entry_set_pathname(entry, path);
455
738b3582
MT
456 // Read all attributes from file
457 int r = archive_read_disk_entry_from_file(packager->reader, entry, -1, NULL);
458 if (r) {
459 ERROR(packager->pakfire, "Could not read attributes from %s: %s\n",
460 path, strerror(errno));
461 goto ERROR;
462 }
da08f989
MT
463
464 // Write the header
465 r = archive_write_header(packager->payload, entry);
466 if (r) {
467 ERROR(packager->pakfire, "Error writing file header: %s\n",
468 archive_error_string(packager->payload));
469 goto ERROR;
470 }
471
472 // Copy the data of regular files
473 if (archive_entry_filetype(entry) == AE_IFREG) {
474 f = fopen(path, "r");
475 if (!f) {
476 ERROR(packager->pakfire, "Could not open %s: %s\n", path, strerror(errno));
477 r = errno;
478 goto ERROR;
479 }
480
7836e21b
MT
481 r = pakfire_packager_copy_data(packager, packager->payload, f);
482 if (r)
483 goto ERROR;
da08f989
MT
484 }
485
c138b06e
MT
486 // Create a file
487 PakfireFile file;
488 r = pakfire_file_create(&file, packager->pakfire);
489 if (r)
490 goto ERROR;
491
492 r = pakfire_file_copy_archive_entry(file, entry);
493 if (r)
494 goto ERROR;
495
1ba1869e
MT
496 // Append the file to the filelist
497 pakfire_filelist_append(packager->filelist, file);
498
da08f989
MT
499 // Successful
500 r = 0;
501
502ERROR:
1ba1869e
MT
503 pakfire_file_unref(file);
504
da08f989
MT
505 if (entry)
506 archive_entry_free(entry);
507
508 if (f)
509 fclose(f);
510
511 return r;
512}