/*
* LibGCab
* Copyright (c) 2012, Marc-André Lureau <marcandre.lureau@gmail.com>
* Copyright (c) 2017, Richard Hughes <richard@hughsie.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#include "config.h"
#include <stdlib.h>
#include <libgcab.h>
#include <glib/gprintf.h>
#include <locale.h>
#include <glib/gi18n.h>
int verbose = 0;
static gboolean
file_callback (GCabFile *cabfile, gpointer data)
{
GFile *file = gcab_file_get_file (cabfile);
GFile *cwd = G_FILE (data);
if (!verbose)
return TRUE;
if (file) {
g_autofree gchar *path = g_file_get_relative_path (cwd, file);
if (!path)
path = g_file_get_parse_name (file);
g_print ("%s\n", path);
} else {
g_print ("%s\n", gcab_file_get_name (cabfile));
}
return TRUE;
}
static const gchar *
remove_leading_path (gchar *name)
{
unsigned i;
i = 0;
if (name[0] == G_DIR_SEPARATOR)
i = 1;
while (name[i] == '.' && name[i + 1] == '.' && name[i + 2] == G_DIR_SEPARATOR)
i += 3;
if (i != 0) {
gchar c = name[i];
name[i] = '\0';
g_debug ("removing leading '%s' from member names", name);
name[i] = c;
}
return name + i;
}
static gboolean
save_array_to_file (const GByteArray *array, const gchar *base, const gchar *suffix, GError **error)
{
g_autofree gchar *resname = NULL;
g_autoptr(GFile) outputfile = NULL;
g_autoptr(GOutputStream) output = NULL;
resname = g_strdup_printf ("%s.%s", base, suffix);
g_print ("%s %s...", _("Dumping data to:"), resname);
outputfile = g_file_new_for_commandline_arg (resname);
output = G_OUTPUT_STREAM (g_file_replace (outputfile, NULL, FALSE, 0, NULL, error));
if (output == NULL) {
g_prefix_error (error, "cannot write %s: ", resname);
return FALSE;
}
if (!g_output_stream_write_all (output, array->data, array->len, NULL, NULL, error)) {
g_prefix_error (error, "cannot write %s: ", resname);
return FALSE;
}
return TRUE;
}
int
main (int argc, char *argv[])
{
g_autoptr(GError) error = NULL;
g_autoptr(GOptionContext) context = NULL;
g_auto(GStrv) args = NULL;
g_autofree gchar *change = NULL;
gboolean version = FALSE;
gboolean nopath = FALSE;
gboolean space = FALSE;
gboolean compress = FALSE;
gboolean list = FALSE;
gboolean list_details = FALSE;
gboolean create = FALSE;
gboolean extract = FALSE;
gboolean dump_reserved = FALSE;
GOptionEntry entries[] = {
{ "version", 0, 0, G_OPTION_ARG_NONE, &version, N_("Print program version"), NULL },
{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, N_("Be verbose"), NULL },
{ "create", 'c', 0, G_OPTION_ARG_NONE, &create, N_("Create archive"), NULL },
{ "extract", 'x', 0, G_OPTION_ARG_NONE, &extract, N_("Extract all files"), NULL },
{ "dump-reserved", 'D', 0, G_OPTION_ARG_NONE, &dump_reserved, N_("Dump reserved and extra data"), NULL },
{ "list", 't', 0, G_OPTION_ARG_NONE, &list, N_("List content"), NULL },
{ "list-details", 'l', 0, G_OPTION_ARG_NONE, &list_details, N_("List content with file details"), NULL },
{ "directory", 'C', 0, G_OPTION_ARG_FILENAME, &change, N_("Change to directory DIR"), N_("DIR") },
{ "zip", 'z', 0, G_OPTION_ARG_NONE, &compress, N_("Use zip compression"), NULL },
{ "nopath", 'n', 0, G_OPTION_ARG_NONE, &nopath, N_("Do not include path"), NULL },
{ "space", 's', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_INT, &space, N_("Reserve space in cabinet for signing (e.g. -s 6144 reserves 6K bytes)"), NULL },
{ G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &args, NULL, N_("FILE INPUT_FILES...") },
{ NULL }
};
setlocale (LC_ALL, "");
textdomain (GETTEXT_PACKAGE);
bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
#if !GLIB_CHECK_VERSION(2,35,1)
g_type_init ();
#endif
g_set_prgname (PACKAGE_NAME);
context = g_option_context_new (_("- create a Cabinet file"));
g_autofree gchar *s = g_strdup_printf ("%s %s", _("Report bugs to:"), PACKAGE_BUGREPORT);
g_option_context_set_description (context, s);
g_option_context_set_summary (context, _("\
gcab saves many files together into a cabinet archive, and can restore\n\
individual files from the archive.\
"));
g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
if (!g_option_context_parse (context, &argc, &argv, &error)) {
g_printerr ("%s: %s\n", _("Option parsing failed"), error->message);
return EXIT_FAILURE;
}
if (version) {
g_printf (PACKAGE_STRING "\n");
return 0;
}
if ((list + extract + create + dump_reserved + list_details) != 1) {
g_printerr ("%s\n", _("Please specify a single operation"));
return EXIT_FAILURE;
}
if (!args || args[0] == NULL) {
g_printerr ("%s\n", _("Cabinet file must be specified"));
return EXIT_FAILURE;
}
g_autoptr(GCabCabinet) cabinet = gcab_cabinet_new ();
g_autoptr(GCabFolder) folder = NULL;
g_autoptr(GCancellable) cancellable = g_cancellable_new ();
g_autoptr(GFile) cwd = NULL;
g_autoptr(GFile) outputfile = NULL;
g_autoptr(GOutputStream) output = NULL;
if (list || list_details || extract || dump_reserved) {
g_autoptr(GFile) file = g_file_new_for_commandline_arg (args[0]);
g_autoptr(GInputStream) in = G_INPUT_STREAM (g_file_read (file, cancellable, &error));
if (in == NULL) {
g_printerr ("%s %s: %s\n", _("Cannot open file for reading"), args[0], error->message);
return EXIT_FAILURE;
}
if (!gcab_cabinet_load (cabinet, in, cancellable, &error)) {
g_printerr ("%s %s: %s\n", _("Error reading"), args[0], error->message);
return EXIT_FAILURE;
}
if (list || list_details) {
GPtrArray *folders = gcab_cabinet_get_folders (cabinet);
for (guint i = 0; i < folders->len; i++) {
GSList *l;
g_autoptr(GSList) files = gcab_folder_get_files (g_ptr_array_index (folders, i));
for (l = files; l != NULL; l = l->next) {
if (list_details) {
gchar date[32];
struct tm *tm;
GTimeVal tv = {0};
gcab_file_get_date (GCAB_FILE (l->data), &tv);
tm = localtime (&tv.tv_sec);
strftime (date, sizeof (date), "%Y-%m-%d %H:%M:%S", tm);
g_print ("%s %u %s 0x%X\n",
gcab_file_get_name (GCAB_FILE (l->data)),
gcab_file_get_size (GCAB_FILE (l->data)),
date,
gcab_file_get_attributes (GCAB_FILE (l->data)));
} else {
g_print ("%s\n", gcab_file_get_name (GCAB_FILE (l->data)));
}
}
}
} else if (extract) {
g_autoptr(GFile) file2 = NULL;
if (change == NULL)
change = g_get_current_dir ();
file2 = g_file_new_for_path (change);
if (!gcab_cabinet_extract (cabinet, file2, file_callback, NULL, NULL, cancellable, &error)) {
g_printerr ("%s: %s\n", _("Error during extraction"), error->message);
return EXIT_FAILURE;
}
} else if (dump_reserved) {
g_autoptr(GByteArray) reserved = NULL;
g_object_get (cabinet, "reserved", &reserved, NULL);
if (reserved != NULL) {
if (!save_array_to_file (reserved, args[0], "header", &error)) {
g_printerr ("%s\n", error->message);
return EXIT_FAILURE;
}
}
reserved = (GByteArray *)gcab_cabinet_get_signature (cabinet, cancellable, &error);
if (reserved == NULL) {
g_printerr ("%s: %s\n", _("Error while reading signature"), error->message);
return EXIT_FAILURE;
}
if (!save_array_to_file (reserved, args[0], "signature", &error)) {
g_printerr ("%s\n", error->message);
return EXIT_FAILURE;
}
}
return EXIT_SUCCESS;
}
if (args[1] == NULL) {
g_printerr ("%s\n", _("No input files specified"));
return EXIT_FAILURE;
}
if (space) {
g_autoptr(GByteArray) reserved = g_byte_array_sized_new (space);
g_byte_array_set_size (reserved, space);
g_object_set (cabinet, "reserved", reserved, NULL);
}
folder = gcab_folder_new (compress ? GCAB_COMPRESSION_MSZIP : 0);
for (gint i = 1; args[i]; i++) {
g_autoptr(GFile) file = g_file_new_for_commandline_arg (args[i]);
g_autofree gchar *name = nopath ? g_path_get_basename (args[i]) : g_strdup (args[i]);
g_autoptr(GCabFile) cabfile = gcab_file_new_with_file (
remove_leading_path (name), file);
if (!gcab_folder_add_file (folder, cabfile, TRUE, NULL, &error)) {
g_warning ("%s %s: %s", _("Cannot add file"), args[i], error->message);
g_clear_error (&error);
}
}
if (gcab_folder_get_nfiles (folder) == 0) {
g_printerr ("%s\n", _("No files to be archived"));
return EXIT_FAILURE;
}
outputfile = g_file_new_for_commandline_arg (args[0]);
output = G_OUTPUT_STREAM (g_file_replace (outputfile, NULL, FALSE,
0, NULL, &error));
if (output == NULL) {
g_printerr ("%s %s: %s\n", _("Cannot create cab file"), args[0], error->message);
return EXIT_FAILURE;
}
cwd = g_file_new_for_commandline_arg (".");
if (!gcab_cabinet_add_folder (cabinet, folder, &error)) {
g_printerr ("%s %s: %s\n", _("Cannot add folder to cab file"), args[0], error->message);
return EXIT_FAILURE;
}
if (!gcab_cabinet_write (cabinet, output,
file_callback,
NULL,
cwd,
NULL,
&error)) {
g_printerr ("%s %s: %s\n", _("Cannot write cab file"), args[0], error->message);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}