]>
Commit | Line | Data |
---|---|---|
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 |
46 | struct 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 | 61 | static 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 | ||
123 | static 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 |
138 | static 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 | ||
157 | static 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 |
176 | static 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 |
201 | PAKFIRE_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 | |
235 | ERROR: | |
236 | pakfire_packager_free(p); | |
237 | ||
238 | return r; | |
6aeb48e6 MT |
239 | } |
240 | ||
6aeb48e6 MT |
241 | PAKFIRE_EXPORT struct pakfire_packager* pakfire_packager_ref( |
242 | struct pakfire_packager* packager) { | |
243 | ++packager->nrefs; | |
244 | ||
245 | return packager; | |
246 | } | |
247 | ||
248 | PAKFIRE_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 |
258 | static 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 |
284 | static 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 |
324 | static 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 | ||
376 | ERROR: | |
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 | */ | |
387 | PAKFIRE_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 | ||
429 | ERROR: | |
430 | if (a) | |
431 | archive_free(a); | |
432 | ||
433 | return filename; | |
434 | } | |
435 | ||
da08f989 MT |
436 | PAKFIRE_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 | ||
502 | ERROR: | |
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 | } |