/* export.c: * **************************************************************** * Copyright (C) 2003 Tom Lord * 2005 Canonical Ltd * * See the file "COPYING" for further information about * the copyright and warranty status of this work. */ #include "config-options.h" #include "po/gettext.h" #include "hackerlab/bugs/exception.h" #include "hackerlab/cmd/main.h" #include "hackerlab/fs/file-names.h" #include "hackerlab/fs/cwd.h" #include "hackerlab/char/str.h" #include "libfsutils/tmp-files.h" #include "libfsutils/rmrf.h" #include "libfsutils/copy-file.h" #include "libarch/namespace.h" #include "libarch/invent.h" #include "libarch/project-tree.h" #include "libarch/my.h" #include "libarch/archive.h" #include "libarch/pristines.h" #include "libarch/build-revision.h" #include "libarch/debug.h" #include "libarch/inode-sig.h" #include "libarch/libraries.h" #include "libarch/local-cache.h" #include "libarch/library-txn.h" #include "commands/cmd.h" #include "commands/cmdutils.h" #include "commands/export.h" #include "commands/version.h" static t_uchar * usage = N_("[options] [revision] [dir]"); #define OPTS(OP) \ OP (opt_help_msg, "h", "help", 0, \ N_("Display a help message and exit.")) \ OP (opt_long_help, "H", 0, 0, \ N_("Display a verbose help message and exit.")) \ OP (opt_version, "V", "version", 0, \ N_("Display a release identifier string\n" \ "and exit.")) \ OP (opt_silent, "s", "silent", 0, \ N_("no output")) t_uchar arch_cmd_export_help[] = N_("export all or some of a tree revision\n" "Extract REVISION from an archive, creating a new source tree with no control information.\n" "\n" ); enum options { OPTS (OPT_ENUM) }; static struct opt_desc opts[] = { OPTS (OPT_DESC) {-1, 0, 0, 0, 0} }; static int filename_matches (regex_t * pattern, char * filename) { int answer; answer = regexec (pattern, filename, 0, 0, 0); if (answer == REG_NOMATCH) return 0; if (answer == REG_NOERROR) return 1; panic ("unexpected regexec error in arch_inventory_traversal"); return -1; } /* FIXME: add doxygen docs * return true if the path is in .arch-ids or {arch}, ie if it matches * s/(\{arch\}|\.arch-ids)/. */ int arch_is_in_control_dir(t_uchar * path) { static int compiled = 0; static regex_t control_pattern = {0,}; if (!compiled) { int re_error; re_error = regcomp (&control_pattern, "\\.arch-ids|\\{arch\\}", REG_EXTENDED); invariant (!re_error); compiled = 1; } return filename_matches (&control_pattern, path); } /*FIXME: add doxygen docs * return a new rel_table which does not contain any control files */ rel_table arch_filter_control_files (rel_table * index) { rel_table new_index = 0; int l = 0; for (l = 0; l < rel_n_records (*index); ++l) { if (!( (arch_is_control_file ((*index)[l][0], NULL) && str_cmp_suffix_n((*index)[l][0], ".arch-inventory", 15)) || arch_is_in_control_dir ((*index)[l][0]))) { rel_add_records (&new_index, rel_copy_record ((*index)[l]), 0); } } safe_flush (1); return new_index; } /*FIXME: add doxygen docs * return a new rel_table which begins with prefix */ rel_table arch_filter_to_subdir (rel_table * index, t_uchar * prefix) { rel_table new_index = 0; int l = 0; for (l = 0; l < rel_n_records (*index); ++l) { if (!str_cmp_prefix(prefix, (t_uchar *)(*index)[l][0])) rel_add_records (&new_index, rel_copy_record ((*index)[l]), 0); } return new_index; } int arch_cmd_export (t_uchar * program_name, int argc, char * argv[]) { int o; struct opt_parsed * option; t_uchar * cache_dir_spec = 0; t_uchar * selected_library = 0; int silent = 0; safe_buffer_fd (1, 0, O_WRONLY, 0); option = 0; while (1) { o = opt_standard (lim_use_must_malloc, &option, opts, &argc, argv, program_name, usage, libarch_version_string, arch_cmd_export_help, opt_help_msg, opt_long_help, opt_version); if (o == opt_none) break; switch (o) { default: safe_printfmt (2, "unhandled option `%s'\n", option->opt_string); panic ("internal error parsing arguments"); usage_error: opt_usage (2, argv[0], program_name, usage, 1); exit (1); /* bogus_arg: */ safe_printfmt (2, "ill-formed argument for `%s' (`%s')\n", option->opt_string, option->arg_string); goto usage_error; case opt_silent: { silent = 1; break; } } } if ((argc < 2) || (argc > 3)) goto usage_error; { int here_fd; t_uchar * revision_spec = 0; t_uchar * revision = 0; t_uchar * output_dir = 0; t_uchar * tmp_dir = 0; t_uchar * last_arg = 0; t_uchar * subdir = 0; struct arch_archive * arch = 0; arch_project_tree_t * tree; here_fd = safe_open (".", O_RDONLY, 0); tree = arch_project_tree_new (talloc_context, "."); last_arg = str_save (0, argv[1]); /* three cases: * no args - tree version, automatic dir. * XXX not currently supported. (see the check up above) * 1 arg - named revision or output dir if there is a tree, named revision otherwise. * 2 args - named revision and output dir */ /* input canonicalisation */ { /* disambiguate dir or revision */ if (tree) { arch_project_tree_check_name (tree, &arch, &revision_spec, (argc > 1) ? argv[1] : (char *)tree->fqrevision); if (!arch && argc == 2) { /* ambigous, could be a dir spec */ arch_project_tree_check_name (tree, &arch, &revision_spec, tree->fqrevision); output_dir = str_save (0, argv[1]); } else if (argc == 3) output_dir = str_save (0, argv[2]); } else { if (argc < 2) goto usage_error; /* no tree, argv[1] must be a revision */ arch = arch_archive_connect_branch (argv[1], &revision_spec); if (argc == 3) output_dir = str_save (0, argv[2]); } if (!arch) { safe_printfmt (2, _("Cannot connect to archive for revision %s\n"), argv[1]); exit (2); } } invariant (!!arch); revision_spec = str_replace (revision_spec, arch_determine_fqrevision(&arch, NULL, revision_spec, argv[0])); revision = arch_parse_package_name (arch_ret_non_archive, NULL, revision_spec); if (!output_dir) { /* name of revision part */ t_uchar * cwd = safe_current_working_directory (); output_dir = file_name_in_vicinity (0, cwd, revision); lim_free (0, cwd); } invariant (!!output_dir); { t_uchar * canonical_dir = file_name_from_directory (0, output_dir); t_uchar * output_dirname = file_name_directory (0, canonical_dir); int output_dirname_exists = safe_access (output_dirname, F_OK) == 0; if (!output_dirname_exists) { safe_printfmt (2, _("Parent dir of output directory %s does not exist\n"), output_dirname); exit (2); } tmp_dir = tmp_file_name_in_tmp ("export"); } if (!safe_access (output_dir, F_OK)) { safe_printfmt (2, "%s: output directory already exists (%s)\n", argv[0], output_dir); exit (1); } rmrf_file (tmp_dir); safe_mkdir (tmp_dir, 0777); { t_uchar * revision_dir = 0; rel_table index = 0; rel_table index_stripped = 0; rel_table index_subdird = 0; arch_project_tree_t * tmp_tree; int old_debug_level = arch_debug_levels[dbg_builder]; /* FIXME: inefficient, since arch_build_revision is already * doing an inventory and throwing it away. */ if (silent) arch_debug_levels[dbg_builder] = -1; arch_build_revision (tmp_dir, arch, arch->official_name, revision, NULL); arch_debug_levels[dbg_builder] = old_debug_level; tmp_tree = arch_project_tree_new (talloc_context, tmp_dir); index = arch_source_inventory (tmp_tree, 1, 0, 0); safe_flush (1); index_stripped = arch_filter_control_files (&index); if (subdir) { safe_printfmt(2," limiting to %s", subdir); index_subdird = arch_filter_to_subdir (&index_stripped, str_alloc_cat(0, "./", subdir)); } else { index_subdird = index_stripped; } rel_sort_table_by_field (0, index_subdird, 0); copy_file_list (output_dir, tmp_dir, index_subdird); lim_free (0, revision_dir); arch_project_tree_delete (tmp_tree); } arch_archive_close (arch); safe_fchdir (here_fd); safe_close (here_fd); rmrf_file (tmp_dir); lim_free (0, revision_spec); lim_free (0, revision); lim_free (0, output_dir); lim_free (0, tmp_dir); arch_project_tree_delete (tree); } lim_free (0, selected_library); lim_free (0, cache_dir_spec); return 0; }