1 /*
2 * Copyright (C) 2016 Red Hat, Inc
3 *
4 * osinfo-db-export: export a database archive
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 2 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 along
17 * with this program. If not, see <http://www.gnu.org/licenses/>
18 *
19 * Authors:
20 * Daniel P. Berrange <berrange@redhat.com>
21 */
22
23 #include <locale.h>
24 #include <glib/gi18n.h>
25 #include <stdlib.h>
26 #include <archive.h>
27 #include <archive_entry.h>
28
29 #include "osinfo-db-util.h"
30
31 const char *argv0;
32
33 time_t entryts;
34
35 static int osinfo_db_export_create_file(const gchar *prefix,
36 GFile *file,
37 GFileInfo *info,
38 GFile *base,
39 const gchar *target,
40 struct archive *arc,
41 gboolean verbose);
42
43
osinfo_db_export_create_reg(GFile * file,const gchar * abspath,const gchar * target,struct archive * arc)44 static int osinfo_db_export_create_reg(GFile *file,
45 const gchar *abspath,
46 const gchar *target,
47 struct archive *arc)
48 {
49 g_autoptr(GFileInputStream) is = NULL;
50 g_autoptr(GError) err = NULL;
51 g_autofree gchar *buf = NULL;
52 gsize size;
53 gsize rv;
54
55 is = g_file_read(file, NULL, &err);
56 if (!is) {
57 g_printerr("%s: cannot read file %s: %s\n",
58 argv0, abspath, err->message);
59 return -1;
60 }
61
62 size = 64 * 1024;
63 buf = g_new0(char, size);
64 while (1) {
65 rv = g_input_stream_read(G_INPUT_STREAM(is),
66 buf,
67 size,
68 NULL,
69 &err);
70 if (rv == -1) {
71 g_printerr("%s: cannot read data %s: %s\n",
72 argv0, abspath, err->message);
73 return -1;
74 }
75
76 if (rv == 0)
77 break;
78
79 if (archive_write_data(arc, buf, rv) < 0) {
80 g_printerr("%s: cannot write archive data for %s to %s: %s\n",
81 argv0, abspath, target, archive_error_string(arc));
82 return -1;
83 }
84 }
85
86 return 0;
87 }
88
osinfo_db_export_create_dir(const gchar * prefix,GFile * file,GFile * base,const gchar * abspath,const gchar * target,struct archive * arc,gboolean verbose)89 static int osinfo_db_export_create_dir(const gchar *prefix,
90 GFile *file,
91 GFile *base,
92 const gchar *abspath,
93 const gchar *target,
94 struct archive *arc,
95 gboolean verbose)
96 {
97 g_autoptr(GFileEnumerator) children = NULL;
98 g_autoptr(GError) err = NULL;
99
100 children = g_file_enumerate_children(file,
101 G_FILE_ATTRIBUTE_STANDARD_NAME
102 ","
103 G_FILE_ATTRIBUTE_STANDARD_SIZE,
104 G_FILE_QUERY_INFO_NONE,
105 NULL,
106 &err);
107 if (!children) {
108 g_printerr("%s: cannot read directory %s: %s\n",
109 argv0, abspath, err->message);
110 return -1;
111 }
112
113 while (1) {
114 g_autoptr(GFileInfo) childinfo = NULL;
115 g_autoptr(GFile) child = NULL;
116 int export_create_ret;
117
118 childinfo = g_file_enumerator_next_file(children, NULL, &err);
119 if (!childinfo) {
120 if (err) {
121 g_printerr("%s: cannot read directory entry %s: %s\n",
122 argv0, abspath, err->message);
123 return -1;
124 } else {
125 break;
126 }
127 }
128
129 child = g_file_enumerator_get_child(children, childinfo);
130
131 export_create_ret = osinfo_db_export_create_file(prefix, child, childinfo, base, target, arc, verbose);
132
133 if (export_create_ret < 0)
134 return -1;
135 }
136
137 return 0;
138 }
139
140
osinfo_db_export_create_file(const gchar * prefix,GFile * file,GFileInfo * info,GFile * base,const gchar * target,struct archive * arc,gboolean verbose)141 static int osinfo_db_export_create_file(const gchar *prefix,
142 GFile *file,
143 GFileInfo *info,
144 GFile *base,
145 const gchar *target,
146 struct archive *arc,
147 gboolean verbose)
148 {
149 GFileType type = g_file_query_file_type(file,
150 G_FILE_QUERY_INFO_NONE,
151 NULL);
152
153 g_autoptr(GError) err = NULL;
154 g_autofree gchar *abspath = NULL;
155 g_autofree gchar *relpath = NULL;
156 g_autofree gchar *entpath = NULL;
157 struct archive_entry *entry = NULL;
158
159 abspath = g_file_get_path(file);
160 relpath = g_file_get_relative_path(base, file);
161
162 if (!info) {
163 info = g_file_query_info(file,
164 G_FILE_ATTRIBUTE_STANDARD_NAME
165 ","
166 G_FILE_ATTRIBUTE_STANDARD_SIZE,
167 G_FILE_QUERY_INFO_NONE,
168 NULL,
169 &err);
170 } else {
171 g_object_ref(info);
172 }
173 if (!info) {
174 g_printerr("%s: cannot get file info %s: %s\n",
175 argv0, abspath, err->message);
176 return -1;
177 }
178
179 entpath = g_strdup_printf("%s/%s", prefix, relpath ? relpath : "");
180
181 entry = archive_entry_new();
182 archive_entry_set_pathname(entry, entpath);
183
184 archive_entry_set_atime(entry, entryts, 0);
185 archive_entry_set_ctime(entry, entryts, 0);
186 archive_entry_set_mtime(entry, entryts, 0);
187 archive_entry_set_birthtime(entry, entryts, 0);
188
189 switch (type) {
190 case G_FILE_TYPE_REGULAR:
191 case G_FILE_TYPE_SYMBOLIC_LINK:
192 if (g_file_info_get_is_backup(info) ||
193 g_file_info_get_is_hidden(info)) {
194 return 0;
195 }
196 if (!g_str_has_suffix(entpath, ".rng") &&
197 !g_str_has_suffix(entpath, ".xml") &&
198 !g_str_has_suffix(entpath, ".ids")) {
199 return 0;
200 }
201
202 if (verbose) {
203 g_print("%s: r %s\n", argv0, entpath);
204 }
205 archive_entry_set_filetype(entry, AE_IFREG);
206 archive_entry_set_perm(entry, 0644);
207 archive_entry_set_size(entry, g_file_info_get_size(info));
208 break;
209
210 case G_FILE_TYPE_DIRECTORY:
211 if (verbose) {
212 g_print("%s: d %s\n", argv0, entpath);
213 }
214
215 archive_entry_set_filetype(entry, AE_IFDIR);
216 archive_entry_set_perm(entry, 0755);
217 archive_entry_set_size(entry, 0);
218 break;
219
220 case G_FILE_TYPE_SPECIAL:
221 g_printerr("%s: cannot archive special file type %s\n",
222 argv0, abspath);
223 return -1;
224
225 case G_FILE_TYPE_SHORTCUT:
226 g_printerr("%s: cannot archive shortcut file type %s\n",
227 argv0, abspath);
228 return -1;
229
230 case G_FILE_TYPE_MOUNTABLE:
231 g_printerr("%s: cannot archive mount file type %s\n",
232 argv0, abspath);
233 return -1;
234
235 case G_FILE_TYPE_UNKNOWN:
236 default:
237 g_printerr("%s: cannot archive unknown file type %s\n",
238 argv0, abspath);
239 return -1;
240 }
241
242 if (archive_write_header(arc, entry) != ARCHIVE_OK) {
243 g_printerr("%s: cannot write archive header %s: %s\n",
244 argv0, target, archive_error_string(arc));
245 return -1;
246 }
247
248 switch (type) {
249 case G_FILE_TYPE_REGULAR:
250 case G_FILE_TYPE_SYMBOLIC_LINK:
251 if (osinfo_db_export_create_reg(file, abspath, target, arc) < 0)
252 return -1;
253 break;
254
255 case G_FILE_TYPE_DIRECTORY:
256 if (osinfo_db_export_create_dir(prefix, file, base, abspath, target, arc, verbose) < 0)
257 return -1;
258 break;
259
260 default:
261 g_assert_not_reached();
262 }
263
264 return 0;
265 }
266
osinfo_db_export_create_version(const gchar * prefix,const gchar * version,const gchar * target,struct archive * arc,gboolean verbose)267 static int osinfo_db_export_create_version(const gchar *prefix,
268 const gchar *version,
269 const gchar *target,
270 struct archive *arc,
271 gboolean verbose)
272 {
273 int ret = -1;
274 struct archive_entry *entry = NULL;
275 g_autofree gchar *entpath = NULL;
276
277 entpath = g_strdup_printf("%s/VERSION", prefix);
278 entry = archive_entry_new();
279 archive_entry_set_pathname(entry, entpath);
280
281 archive_entry_set_atime(entry, entryts, 0);
282 archive_entry_set_ctime(entry, entryts, 0);
283 archive_entry_set_mtime(entry, entryts, 0);
284 archive_entry_set_birthtime(entry, entryts, 0);
285
286 if (verbose) {
287 g_print("%s: r %s\n", argv0, entpath);
288 }
289 archive_entry_set_filetype(entry, AE_IFREG);
290 archive_entry_set_perm(entry, 0644);
291 archive_entry_set_size(entry, strlen(version));
292
293 if (archive_write_header(arc, entry) != ARCHIVE_OK) {
294 g_printerr("%s: cannot write archive header %s: %s\n",
295 argv0, target, archive_error_string(arc));
296 goto cleanup;
297 }
298
299 if (archive_write_data(arc, version, strlen(version)) < 0) {
300 g_printerr("%s: cannot write archive data for %s to %s: %s\n",
301 argv0, entpath, target, archive_error_string(arc));
302 goto cleanup;
303 }
304
305 ret = 0;
306 cleanup:
307 archive_entry_free(entry);
308 return ret;
309 }
310
osinfo_db_export_create_license(const gchar * prefix,const gchar * license,const gchar * target,struct archive * arc,gboolean verbose)311 static int osinfo_db_export_create_license(const gchar *prefix,
312 const gchar *license,
313 const gchar *target,
314 struct archive *arc,
315 gboolean verbose)
316 {
317 int ret = -1;
318 struct archive_entry *entry = NULL;
319 g_autofree gchar *entpath = NULL;
320 g_autoptr(GFile) file = NULL;
321 g_autoptr(GFileInfo) info = NULL;
322 g_autoptr(GError) err = NULL;
323
324 file = g_file_new_for_path(license);
325
326 info = g_file_query_info(file,
327 G_FILE_ATTRIBUTE_STANDARD_NAME
328 ","
329 G_FILE_ATTRIBUTE_STANDARD_SIZE,
330 G_FILE_QUERY_INFO_NONE,
331 NULL,
332 &err);
333 if (!info) {
334 g_printerr("%s: cannot get file info %s: %s\n",
335 argv0, license, err->message);
336 goto cleanup;
337 }
338
339 entpath = g_strdup_printf("%s/LICENSE", prefix);
340 entry = archive_entry_new();
341 archive_entry_set_pathname(entry, entpath);
342
343 archive_entry_set_atime(entry, entryts, 0);
344 archive_entry_set_ctime(entry, entryts, 0);
345 archive_entry_set_mtime(entry, entryts, 0);
346 archive_entry_set_birthtime(entry, entryts, 0);
347
348 if (verbose) {
349 g_print("%s: r %s\n", argv0, entpath);
350 }
351 archive_entry_set_filetype(entry, AE_IFREG);
352 archive_entry_set_perm(entry, 0644);
353 archive_entry_set_size(entry, g_file_info_get_size(info));
354
355 if (archive_write_header(arc, entry) != ARCHIVE_OK) {
356 g_printerr("%s: cannot write archive header %s: %s\n",
357 argv0, target, archive_error_string(arc));
358 goto cleanup;
359 }
360
361 if (osinfo_db_export_create_reg(file, license, target, arc) < 0)
362 goto cleanup;
363
364 ret = 0;
365 cleanup:
366 archive_entry_free(entry);
367 return ret;
368 }
369
osinfo_db_export_create(const gchar * prefix,const gchar * version,GFile * source,const gchar * target,const gchar * license,gboolean verbose)370 static int osinfo_db_export_create(const gchar *prefix,
371 const gchar *version,
372 GFile *source,
373 const gchar *target,
374 const gchar *license,
375 gboolean verbose)
376 {
377 struct archive *arc;
378 int ret = -1;
379 int r;
380
381 arc = archive_write_new();
382
383 archive_write_add_filter_xz(arc);
384 archive_write_set_format_pax(arc);
385
386 if (target != NULL && g_str_equal(target, "-"))
387 target = NULL;
388
389 if ((r = archive_write_open_filename(arc, target)) != ARCHIVE_OK) {
390 g_printerr("%s: cannot open archive %s: %s\n",
391 argv0, target, archive_error_string(arc));
392 goto cleanup;
393 }
394
395 if (osinfo_db_export_create_file(prefix, source, NULL, source, target, arc, verbose) < 0) {
396 goto cleanup;
397 }
398
399 if (osinfo_db_export_create_version(prefix, version, target, arc, verbose) < 0) {
400 goto cleanup;
401 }
402
403 if (license != NULL &&
404 osinfo_db_export_create_license(prefix, license, target, arc, verbose) < 0) {
405 goto cleanup;
406 }
407
408 if (archive_write_close(arc) != ARCHIVE_OK) {
409 g_printerr("%s: cannot finish writing archive %s: %s\n",
410 argv0, target, archive_error_string(arc));
411 goto cleanup;
412 }
413
414 ret = 0;
415 cleanup:
416 archive_write_free(arc);
417 return ret;
418 }
419
420
osinfo_db_version(void)421 static gchar *osinfo_db_version(void)
422 {
423 g_autoptr(GTimeZone) tz = g_time_zone_new_utc();
424 g_autoptr(GDateTime) now = g_date_time_new_now(tz);
425 gchar *ret;
426
427 ret = g_strdup_printf("%04d%02d%02d",
428 g_date_time_get_year(now),
429 g_date_time_get_month(now),
430 g_date_time_get_day_of_month(now));
431 return ret;
432 }
433
434
main(gint argc,gchar ** argv)435 gint main(gint argc, gchar **argv)
436 {
437 g_autoptr(GOptionContext) context = NULL;
438 g_autoptr(GError) error = NULL;
439 g_autoptr(GFile) dir = NULL;
440 gboolean verbose = FALSE;
441 gboolean user = FALSE;
442 gboolean local = FALSE;
443 gboolean system = FALSE;
444 g_autofree gchar *archive = NULL;
445 g_autofree gchar *autoversion = NULL;
446 g_autofree gchar *prefix = NULL;
447 const gchar *root = "";
448 const gchar *custom = NULL;
449 const gchar *version = NULL;
450 const gchar *license = NULL;
451 int locs = 0;
452 const GOptionEntry entries[] = {
453 { "verbose", 'v', 0, G_OPTION_ARG_NONE, (void*)&verbose,
454 N_("Verbose progress information"), NULL, },
455 { "user", 0, 0, G_OPTION_ARG_NONE, (void *)&user,
456 N_("Export the osinfo-db user directory"), NULL, },
457 { "local", 0, 0, G_OPTION_ARG_NONE, (void *)&local,
458 N_("Export the osinfo-db local directory"), NULL, },
459 { "system", 0, 0, G_OPTION_ARG_NONE, (void *)&system,
460 N_("Export the osinfo-db system directory"), NULL, },
461 { "dir", 0, 0, G_OPTION_ARG_STRING, (void *)&custom,
462 N_("Export an osinfo-db custom directory"), NULL, },
463 { "version", 0, 0, G_OPTION_ARG_STRING, (void *)&version,
464 N_("Set version number of archive"), NULL, },
465 { "root", 0, 0, G_OPTION_ARG_STRING, &root,
466 N_("Export the osinfo-db root directory"), NULL, },
467 { "license", 0, 0, G_OPTION_ARG_STRING, &license,
468 N_("License file"), NULL, },
469 { NULL, 0, 0, 0, NULL, NULL, NULL },
470 };
471 argv0 = argv[0];
472
473 setlocale(LC_ALL, "");
474 textdomain(GETTEXT_PACKAGE);
475 bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
476 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
477
478 context = g_option_context_new(_("- Export database archive "));
479
480 g_option_context_add_main_entries(context, entries, GETTEXT_PACKAGE);
481
482 if (!g_option_context_parse(context, &argc, &argv, &error)) {
483 g_printerr(_("%s: error while parsing commandline options: %s\n\n"),
484 argv0, error->message);
485 g_printerr("%s\n", g_option_context_get_help(context, FALSE, NULL));
486 return EXIT_FAILURE;
487 }
488
489 if (argc > 2) {
490 g_printerr(_("%s: expected path to one archive file to export\n"),
491 argv0);
492 return EXIT_FAILURE;
493 }
494
495 if (local)
496 locs++;
497 if (system)
498 locs++;
499 if (user)
500 locs++;
501 if (custom)
502 locs++;
503 if (locs > 1) {
504 g_printerr(_("Only one of --user, --local, --system & --dir can be used\n"));
505 return EXIT_FAILURE;
506 }
507
508 entryts = time(NULL);
509 if (version == NULL) {
510 autoversion = osinfo_db_version();
511 version = autoversion;
512 }
513 prefix = g_strdup_printf("osinfo-db-%s", version);
514 if (argc == 2) {
515 archive = g_strdup(argv[1]);
516 } else {
517 archive = g_strdup_printf("%s.tar.xz", prefix);
518 }
519 dir = osinfo_db_get_path(root, user, local, system, custom);
520 if (osinfo_db_export_create(prefix, version, dir, archive,
521 license, verbose) < 0)
522 return EXIT_FAILURE;
523
524 return EXIT_SUCCESS;
525 }
526
527
528 /*
529 =pod
530
531 =head1 NAME
532
533 osinfo-db-export - Export to a osinfo database archive
534
535 =head1 SYNOPSIS
536
537 osinfo-db-export [OPTIONS...] [ARCHIVE-FILE]
538
539 =head1 DESCRIPTION
540
541 The B<osinfo-db-export> tool will create an osinfo database
542 archive file containing the content from one of the standard
543 local database locations:
544
545 =over 1
546
547 =item B<system>
548
549 This is the primary system-wide database location, intended
550 for use by operating system vendors distributing database
551 files in the native package format.
552
553 =item B<local>
554
555 This is the secondary system-wide database location, intended
556 for use by system administrators wishing to provide an updated
557 database for all users.
558
559 =item B<user>
560
561 This is the user private database location, intended for use
562 by unprivileged local users wishing to provide applications
563 they use with an updated database.
564
565 =back
566
567 If run by a privileged account (ie root), the B<local> database
568 location will be used by default, otherwise the B<user> location
569 will be used.
570
571 If no B<ARCHIVE-FILE> path is given, an automatically generated
572 filename will be used, taking the format B<osinfo-db-$VERSION.tar.xz>.
573
574 =head1 OPTIONS
575
576 =over 8
577
578 =item B<--user>
579
580 Override the default behaviour to force archiving files from the
581 B<user> database location.
582
583 =item B<--local>
584
585 Override the default behaviour to force archiving files from the
586 B<local> database location.
587
588 =item B<--system>
589
590 Override the default behaviour to force archiving files from the
591 B<system> database location.
592
593 =item B<--dir=PATH>
594
595 Override the default behaviour to force archiving files from the
596 custom directory B<PATH>.
597
598 =item B<--root=PATH>
599
600 Prefix the database location with the root directory given by
601 C<PATH>. This is useful when wishing to archive files that are
602 in a chroot environment or equivalent.
603
604 =item B<--version=VERSION>
605
606 Set the version string for the files in the archive to
607 B<VERSION>. If this argument is not given, the version
608 will be set to the current date in the format B<YYYYMMDD>.
609
610 =item B<--license=LICENSE-FILE>
611
612 Add C<LICENSE-FILE> to the generated archive as an entry
613 named "LICENSE".
614
615 =item B<-v>, B<--verbose>
616
617 Display verbose progress information when archiving files
618
619 =back
620
621 =head1 EXIT STATUS
622
623 The exit status will be 0 if all files were packed
624 successfully, or 1 if at least one file could not be
625 packed into the archive.
626
627 =head1 SEE ALSO
628
629 C<osinfo-db-import(1)>, C<osinfo-db-path(1)>
630
631 =head1 AUTHORS
632
633 Daniel P. Berrange <berrange@redhat.com>
634
635 =head1 COPYRIGHT
636
637 Copyright (C) 2016 Red Hat, Inc.
638
639 =head1 LICENSE
640
641 C<osinfo-db-export> is distributed under the terms of the GNU LGPL v2+
642 license. This is free software; see the source for copying conditions.
643 There is NO warranty; not even for MERCHANTABILITY or FITNESS
644 FOR A PARTICULAR PURPOSE
645
646 =cut
647 */
648
649 /*
650 * Local variables:
651 * indent-tabs-mode: nil
652 * c-indent-level: 4
653 * c-basic-offset: 4
654 * End:
655 */
656