]> git.ipfire.org Git - people/ms/pakfire.git/blame - src/libpakfire/packager.c
file: Add function to copy attributes from stat()
[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>
1ea1f35b 34#include <pakfire/logging.h>
6aeb48e6
MT
35#include <pakfire/package.h>
36#include <pakfire/packager.h>
37#include <pakfire/pakfire.h>
38#include <pakfire/private.h>
39#include <pakfire/types.h>
40
da08f989
MT
41#define BUFFER_SIZE 64 * 1024
42
6aeb48e6
MT
43struct pakfire_packager {
44 Pakfire pakfire;
45 int nrefs;
46
47 PakfirePackage pkg;
1ea1f35b
MT
48
49 struct archive* payload;
7836e21b 50 FILE* fpayload;
6aeb48e6
MT
51};
52
1ea1f35b 53static int pakfire_packager_create_payload(struct pakfire_packager* p, int compress) {
7836e21b
MT
54 char path[] = "/tmp/.pakfire-payload.XXXXXX";
55
1ea1f35b 56 p->payload = archive_write_new();
427fdd80
MT
57 if (!p->payload) {
58 ERROR(p->pakfire, "archive_write_new() failed\n");
59 return 1;
60 }
1ea1f35b
MT
61
62 // Use the PAX format
63 int r = archive_write_set_format_pax(p->payload);
64 if (r) {
65 ERROR(p->pakfire, "Could not set format to PAX: %s\n",
66 archive_error_string(p->payload));
67 return r;
68 }
69
70 // Add filters to compress the payload
71 if (compress) {
72 // Enable Zstd
73 r = archive_write_add_filter_zstd(p->payload);
74 if (r) {
75 ERROR(p->pakfire, "Could not enable Zstandard compression: %s\n",
76 archive_error_string(p->payload));
77 return r;
78 }
79
80 // Set compression level to highest
81 r = archive_write_set_filter_option(p->payload, NULL, "compression-level", "19");
82 if (r) {
83 ERROR(p->pakfire, "Could not set Zstandard compression level: %s\n",
84 archive_error_string(p->payload));
85 return r;
86 }
87 }
88
7836e21b
MT
89 // Create a new temporary file
90 int fd = mkostemp(path, O_CLOEXEC);
1ea1f35b
MT
91 if (fd < 0) {
92 ERROR(p->pakfire, "mkostemp() failed: %s\n", strerror(errno));
93 return 1;
94 }
95
7836e21b
MT
96 // Unlink the file straight away
97 unlink(path);
98
99 // Convert the file descriptor into a file handle
100 p->fpayload = fdopen(fd, "w+");
101 if (!p->fpayload) {
102 close(fd);
103
104 return 1;
105 }
106
107 // Write archive to file
108 r = archive_write_open_FILE(p->payload, p->fpayload);
1ea1f35b
MT
109 if (r)
110 return r;
111
112 return 0;
113}
114
115static void pakfire_packager_free(struct pakfire_packager* packager) {
116 if (packager->payload)
117 archive_write_free(packager->payload);
118
7836e21b
MT
119 if (packager->fpayload)
120 fclose(packager->fpayload);
121
1ea1f35b
MT
122 pakfire_package_unref(packager->pkg);
123 pakfire_unref(packager->pakfire);
124}
125
6aeb48e6
MT
126PAKFIRE_EXPORT int pakfire_packager_create(struct pakfire_packager** packager,
127 PakfirePackage pkg) {
128 struct pakfire_packager* p = calloc(1, sizeof(*p));
129 if (!p)
130 return ENOMEM;
131
132 // Initialize reference counting
133 p->nrefs = 1;
134
135 // Store a reference to Pakfire
136 p->pakfire = pakfire_package_get_pakfire(pkg);
137
138 // Store a reference to the package
139 p->pkg = pakfire_package_ref(pkg);
140
1ea1f35b
MT
141 // Start payload
142 int r = pakfire_packager_create_payload(p, 1);
143 if (r) {
144 pakfire_packager_free(p);
145 return r;
146 }
147
6aeb48e6
MT
148 *packager = p;
149
150 return 0;
151}
152
6aeb48e6
MT
153PAKFIRE_EXPORT struct pakfire_packager* pakfire_packager_ref(
154 struct pakfire_packager* packager) {
155 ++packager->nrefs;
156
157 return packager;
158}
159
160PAKFIRE_EXPORT struct pakfire_packager* pakfire_packager_unref(
161 struct pakfire_packager* packager) {
162 if (--packager->nrefs > 0)
163 return packager;
164
165 pakfire_packager_free(packager);
166
167 return NULL;
168}
da08f989 169
7836e21b
MT
170static int pakfire_packager_copy_data(struct pakfire_packager* packager,
171 struct archive* a, FILE* f) {
172 char buffer[BUFFER_SIZE];
173
174 while (!feof(f)) {
175 // Read a block from file
176 size_t bytes_read = fread(buffer, 1, sizeof(buffer), f);
177
178 // Check if any error occured
179 if (ferror(f)) {
180 ERROR(packager->pakfire, "Error reading from file: %s\n", strerror(errno));
181 return 1;
182 }
183
184 // Write the block to the archive
185 ssize_t bytes_written = archive_write_data(a, buffer, bytes_read);
186 if (bytes_written < 0) {
187 ERROR(packager->pakfire, "Error writing data to archive: %s\n",
188 archive_error_string(a));
189 return 1;
190 }
191 }
192
193 return 0;
194}
195
436677a3
MT
196static int pakfire_packager_write_format(struct pakfire_packager* packager,
197 struct archive* a) {
198 const char buffer[] = TO_STRING(PACKAGE_FORMAT) "\n";
199
200 // Create a new file entry
201 struct archive_entry* entry = archive_entry_new();
202 if (!entry)
203 return 1;
204
205 // Set filename
b36d7344 206 archive_entry_set_pathname(entry, PAKFIRE_ARCHIVE_FN_FORMAT);
436677a3
MT
207
208 // This is a regular file
209 archive_entry_set_filetype(entry, AE_IFREG);
210 archive_entry_set_perm(entry, 0644);
211
212 // Set length
2adc4a4a 213 archive_entry_set_size(entry, strlen(buffer));
436677a3
MT
214
215 // This is the end of the header
216 int r = archive_write_header(a, entry);
217 if (r) {
218 ERROR(packager->pakfire, "Error writing header: %s\n", archive_error_string(a));
219 archive_entry_free(entry);
220 return r;
221 }
222
223 // Write content
224 r = archive_write_data(a, buffer, strlen(buffer));
225 if (r < 0) {
226 ERROR(packager->pakfire, "Error writing data: %s\n", archive_error_string(a));
227 archive_entry_free(entry);
228 return r;
229 }
230
231 archive_entry_free(entry);
232
233 return 0;
234}
235
2adc4a4a
MT
236static int pakfire_packager_write_payload(struct pakfire_packager* packager,
237 struct archive* a) {
238 struct stat st;
239
240 // Close the payload
241 if (packager->payload) {
242 archive_write_free(packager->payload);
243 packager->payload = NULL;
244 }
245
246 // Reset fd to beginning of the file
247 rewind(packager->fpayload);
248
249 int fd = fileno(packager->fpayload);
250
251 // Stat the payload file
252 int r = fstat(fd, &st);
253 if (r) {
254 ERROR(packager->pakfire, "stat() on fd %d failed: %s\n", fd, strerror(errno));
255 return 1;
256 }
257
258 // Create a new file entry
259 struct archive_entry* entry = archive_entry_new();
260 if (!entry)
261 return 1;
262
263 // Set filename
264 archive_entry_set_pathname(entry, PAKFIRE_ARCHIVE_FN_PAYLOAD);
265
266 // This is a regular file
267 archive_entry_set_filetype(entry, AE_IFREG);
268 archive_entry_set_perm(entry, 0644);
269
270 // Set the file size
271 archive_entry_set_size(entry, st.st_size);
272
273 // This is the end of the header
274 r = archive_write_header(a, entry);
275 if (r) {
276 ERROR(packager->pakfire, "Error writing header: %s\n", archive_error_string(a));
277 goto ERROR;
278 }
279
280 // Copy data
281 r = pakfire_packager_copy_data(packager, a, packager->fpayload);
282 if (r)
283 goto ERROR;
284
285 // Success
286 r = 0;
287
288ERROR:
289 archive_entry_free(entry);
290
291 return r;
292}
293
436677a3
MT
294/*
295 This function is being called at the end when all data has been added to the package.
296
297 It will create a new archive and write the package to the given file descriptor.
298*/
299PAKFIRE_EXPORT char* pakfire_packager_finish(struct pakfire_packager* packager, FILE* f) {
300 char* filename = NULL;
301
302 // Open a new archive
303 struct archive* a = archive_write_new();
304 if (!a) {
305 ERROR(packager->pakfire, "archive_write_new() failed\n");
306 goto ERROR;
307 }
308
309 // Use the PAX format
310 int r = archive_write_set_format_pax(a);
311 if (r) {
312 ERROR(packager->pakfire, "Could not set format to PAX: %s\n",
313 archive_error_string(a));
314 goto ERROR;
315 }
316
317 // Write archive to f
318 r = archive_write_open_FILE(a, f);
319 if (r) {
320 ERROR(packager->pakfire, "archive_write_open_FILE() failed: %s\n",
321 archive_error_string(a));
322 goto ERROR;
323 }
324
325 // Start with the format file
326 r = pakfire_packager_write_format(packager, a);
327 if (r)
328 goto ERROR;
329
2adc4a4a
MT
330 // Write the payload
331 r = pakfire_packager_write_payload(packager, a);
332 if (r)
333 goto ERROR;
334
436677a3
MT
335 // XXX set filename
336
337ERROR:
338 if (a)
339 archive_free(a);
340
341 return filename;
342}
343
da08f989
MT
344PAKFIRE_EXPORT int pakfire_packager_add(struct pakfire_packager* packager,
345 const char* path) {
346 FILE* f = NULL;
347 struct stat st;
da08f989
MT
348
349 // Check if path is set
350 if (!path)
351 return EINVAL;
352
7836e21b
MT
353 // Payload has already been closed
354 if (!packager->payload)
355 return EINVAL;
356
da08f989
MT
357 // Stat the input file
358 int r = stat(path, &st);
359 if (r) {
360 ERROR(packager->pakfire, "stat() on %s failed: %s\n", path, strerror(errno));
361 return r;
362 }
363
364 // Create a new file entry
365 struct archive_entry* entry = archive_entry_new();
366 if (!entry)
367 return ENOMEM;
368
369 // Set path in archive
370 archive_entry_set_pathname(entry, path);
371
372 // Copy all attributes from stat()
373 archive_entry_copy_stat(entry, &st);
374
375 // Write the header
376 r = archive_write_header(packager->payload, entry);
377 if (r) {
378 ERROR(packager->pakfire, "Error writing file header: %s\n",
379 archive_error_string(packager->payload));
380 goto ERROR;
381 }
382
383 // Copy the data of regular files
384 if (archive_entry_filetype(entry) == AE_IFREG) {
385 f = fopen(path, "r");
386 if (!f) {
387 ERROR(packager->pakfire, "Could not open %s: %s\n", path, strerror(errno));
388 r = errno;
389 goto ERROR;
390 }
391
7836e21b
MT
392 r = pakfire_packager_copy_data(packager, packager->payload, f);
393 if (r)
394 goto ERROR;
da08f989
MT
395 }
396
397 // Successful
398 r = 0;
399
400ERROR:
401 if (entry)
402 archive_entry_free(entry);
403
404 if (f)
405 fclose(f);
406
407 return r;
408}