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