/* build-revision.c: * * vim:smartindent ts=8:sts=2:sta:et:ai:shiftwidth=2 **************************************************************** * Copyright (C) 2003 Tom Lord * * See the file "COPYING" for further information about * the copyright and warranty status of this work. */ #include "hackerlab/bugs/exception.h" #include "hackerlab/bugs/panic.h" #include "hackerlab/mem/alloc-limits.h" #include "hackerlab/mem/mem.h" #include "hackerlab/char/str.h" #include "hackerlab/fs/file-names.h" #include "hackerlab/vu/safe.h" #include "libfsutils/copy-file.h" #include "libfsutils/dir-listing.h" #include "libfsutils/tmp-files.h" #include "libfsutils/rmrf.h" #include "libfsutils/link-tree.h" #include "libfsutils/ensure-dir.h" #include "libarch/ancestry.h" #include "libarch/arch.h" #include "libarch/namespace.h" #include "libarch/libraries.h" #include "libarch/project-tree.h" #include "libarch/archive.h" #include "libarch/archives.h" #include "libarch/apply-changeset.h" #include "libarch/pristines.h" #include "libarch/invent.h" #include "libarch/inode-sig.h" #include "libarch/chatter.h" #include "libarch/patch-logs.h" #include "libarch/library-txn.h" #include "libarch/build-revision.h" #include "libarch/debug.h" static void finalize_library_revision(t_uchar const * const dest_directory, t_uchar * tmp_dir, arch_patch_id * patch_id); static int build_revision (int * patch_list_length, t_uchar const * const dest_dir, struct arch_archive * arch, arch_patch_id * const revision, arch_project_tree_t * cache, int try_link, t_uchar const * const non_sparse_lib); static void select_patch_list (ar_patch_id * patch_list, ar_patch_id candidate); static ar_patch_id make_patch_list (arch_patch_id * ancestor, arch_patch_id * descendant, assoc_table contin_list, struct arch_archive *arch); static int patch_direction (ar_patch_id patch_list, arch_patch_id * const revision); static arch_patch_id * decrement_revision (struct arch_archive ** my_arch, arch_patch_id * const current_revision, assoc_table contin_list, struct arch_archive * arch); static void find_patchlists (ar_patch_id * loc_patches, ar_patch_id * arch_patches, arch_patch_id * patch_id, struct arch_archive *arch, assoc_table * contin_list, arch_project_tree_t * cache); static int direct_descendant (struct arch_archive * arch, arch_patch_id *revision, arch_patch_id * candidate_descendant); static arch_patch_id * find_previous_library (arch_patch_id * revision); static arch_patch_id * find_previous_pristine (struct arch_archive *arch, arch_patch_id * revision, arch_project_tree_t * cache); static int find_multisource (arch_patch_id ** lib_rev, arch_patch_id ** arch_rev, arch_patch_id ** continuation, struct arch_archive * arch, arch_patch_id * revision, arch_project_tree_t * cache); static arch_patch_id * scan_archive (enum arch_revision_type *type, int * num_scanned, arch_patch_id ** newest_cached, struct arch_archive *arch, arch_patch_id * start_revision, arch_patch_id * end_revision, int max_count_after_cacherev); static void find_prev_revision (assoc_table * contin_list, arch_patch_id ** local_rev, arch_patch_id ** arch_rev, struct arch_archive * arch, arch_patch_id * start_revision, arch_project_tree_t * cache); static arch_patch_id * find_library_forwards (arch_patch_id * revision); static int copy_local_revision (t_uchar const * const dest_dir, arch_patch_id * revision, arch_project_tree_t * cache, int try_link); static int copy_library_revision (t_uchar const * const dest_dir, arch_patch_id * revision, int try_link); static int copy_pristine_revision (t_uchar const * const dest_dir, arch_patch_id * revision, arch_project_tree_t * cache); static void move_tmp_revision (t_uchar const * const dest_dir, t_uchar const * const tmpdir); static int copy_archive_revision(t_uchar const * const dest_dir, struct arch_archive *arch, arch_patch_id * revision); static void copy_cached_revision (t_uchar const * const dest_dir, struct arch_archive * arch, arch_patch_id * revision); static void copy_import_revision (t_uchar const * const dest_dir, struct arch_archive * arch, arch_patch_id * const revision); static arch_patch_id * find_for_previous_revision (struct arch_archive ** prev_arch, assoc_table contin_list, struct arch_archive * arch, arch_patch_id * const revision); static void apply_revision_patch (t_uchar const * const dest_tree_root, struct arch_archive * arch, arch_patch_id * const revision, int reverse); static void apply_revisions (t_uchar const * const dest_dir, struct arch_archive * arch, ar_patch_id patch_list, int apply_direction, t_uchar const * const non_sparse_lib); static void add_to_library (t_uchar const * const tree_dir, t_uchar const * const library, arch_patch_id * patch_id); /* return non-0 if it came from a library. */ int arch_build_revision (t_uchar const * const dest_dir, struct arch_archive * arch, t_uchar const * const archive, t_uchar const * const revision, arch_project_tree_t * cache) { struct arch_archive * my_arch = talloc_reference (NULL, arch); arch_patch_id * revision_patch = arch_patch_id_new_archive (archive, revision); int from_library = 0; int n_patches; if (!my_arch) my_arch = arch_archive_connect_branch(archive, NULL); invariant (my_arch != NULL); from_library = build_revision (&n_patches, dest_dir, my_arch, revision_patch, cache, 0, NULL) == 1; if (n_patches > 1) from_library = 0; talloc_free (my_arch); talloc_free (revision_patch); return from_library; } void arch_build_library_revision(int no_patch_noise, t_uchar const * const dest_directory, struct arch_archive * arch, arch_patch_id * patch_id, t_uchar const * const opt_library, t_uchar const * const opt_same_dev_path, int sparse, arch_apply_changeset_report_callback callback, int escape_classes) { t_uchar * dest_dir_dir = 0; t_uchar * tmp_dir = 0; t_uchar * my_opt_library = str_save (0, opt_library); int n_patches; dest_dir_dir = file_name_directory_file (0, dest_directory); ensure_directory_exists (dest_dir_dir); tmp_dir = talloc_tmp_file_name (talloc_context, dest_dir_dir, ",,new-revision"); if (my_opt_library == NULL) { my_opt_library = arch_library_add_choice (patch_id, my_opt_library, dest_dir_dir); } /* * WARNING: tag patches are not reversible in older * arch archives, so do not consider removing the cross-archive limit. */ safe_mkdir (tmp_dir, 0777); build_revision (&n_patches, tmp_dir, arch, patch_id, 0, 1, (sparse) ? NULL : my_opt_library); finalize_library_revision (dest_directory, tmp_dir, patch_id); lim_free (0, dest_dir_dir); talloc_free (tmp_dir); lim_free (0, my_opt_library); } /* \brief Turn a temp dir containing a revision into a library revision * * Finish up adding ancillary files to the tmp-dir and then * install it in the library. * * \param dest_directory The directory to store in * \param tmp_dir The directory the revision is currently in * \param the revision identifier for the revision being produced */ void finalize_library_revision(t_uchar const * const dest_directory, t_uchar * tmp_dir, arch_patch_id * patch_id) { t_uchar * index_by_name_path = 0; t_uchar * index_path = 0; int index_by_name_fd = -1; int index_fd = -1; arch_project_tree_t * tree; rel_table index = 0; arch_set_tree_version (tmp_dir, patch_id); tree = arch_project_tree_new (talloc_context, tmp_dir); index = arch_source_inventory (tree, 1, 0, 0); index_by_name_path = file_name_in_vicinity (0, tmp_dir, ",,index-by-name"); index_path = file_name_in_vicinity (0, tmp_dir, ",,index"); rmrf_file (index_by_name_path); rmrf_file (index_path); index_by_name_fd = safe_open (index_by_name_path, O_WRONLY | O_CREAT | O_EXCL, 0444); rel_print_pika_escape_iso8859_1_table (index_by_name_fd, arch_escape_classes, index); safe_close (index_by_name_fd); rel_sort_table_by_field (0, index, 1); index_fd = safe_open (index_path, O_WRONLY | O_CREAT | O_EXCL, 0444); rel_print_pika_escape_iso8859_1_table (index_fd, arch_escape_classes, index); safe_close (index_fd); arch_snap_inode_sig (tree, patch_id); rel_free_table (index); lim_free (0, index_by_name_path); lim_free (0, index_path); arch_project_tree_delete (tree); safe_rename (tmp_dir, dest_directory); } /** * \brief Produce a revision, using any available resources * \param n_patches The number of patches that were applied is written here * \param dest_dir The directory to store the revision in. It must not exist, * but its parent should. * \param arch (optional) an archive that may be useful for building the * revision * \param cache_dir A directory to look for pristine trees in * \param try_link If true, attempt to hard-link to a revision library * \param non_sparse_lib If non-NULL, a revision library to add intermediate * revisions to * \return 0 if no revision was built (but usually panics first) * \return 1 if built by copying a revision library * \return 2 if built by hard-linking from a revision library * \return 3 if built from a pristine * \return 4 if built from an import revision * \return 5 if built from a cached revision * * The selection code is built on the following false assumptions: * 1 all patches are the same size * 2 cached trees take around as long to download as 16 patches * 3 throughput is the same for all archives * * This is because we don't know the truth. But * 1 on *average*, this is true (by definition). * 2 value reached by experimentation w/ a particular tree * 3 this a common case. */ static int build_revision (int * n_patches, t_uchar const * const dest_dir, struct arch_archive * arch, arch_patch_id * const revision, arch_project_tree_t * cache, int try_link, t_uchar const * const non_sparse_lib) { struct arch_archive * my_arch = talloc_reference (NULL, arch); int from_library = 0; int archive_revision_cost = 50; assoc_table contin_list = 0; ar_patch_id loc_patches = NULL; ar_patch_id arch_patches = NULL; ar_patch_id patch_list = NULL; int status = 0; arch_patch_id * my_patch; int n_arch_patches; find_patchlists (&loc_patches, &arch_patches, revision, arch, &contin_list, cache); if (ar_size (loc_patches) == 0 && ar_size (arch_patches) == 0) { panic ("The requested revision cannot be built. It is based on archives that are not registered."); } patch_list = loc_patches; *n_patches = ar_size (patch_list); n_arch_patches = ar_size (arch_patches); if (*n_patches == 0 || (n_arch_patches > 0 && (*n_patches > n_arch_patches + archive_revision_cost))) { patch_list = arch_patches; ar_free_patch_id (&loc_patches); *n_patches = ar_size (patch_list); } else { /* choosing location based answer */ ar_free_patch_id (&arch_patches); } if (patch_direction(patch_list, revision) == -1) my_patch = patch_list[*n_patches -1]; else my_patch = patch_list[0]; if (patch_list == arch_patches) { struct arch_archive * tmp_arch; /* FIXME this should be tmp_arch = arch_archive_connect_branch * unconditionally - RBC 20050519 */ if (str_cmp (arch->official_name, arch_patch_id_archive (my_patch))) { tmp_arch = arch_archive_connect_branch (arch_patch_id_patch_id (my_patch), NULL); invariant (!!tmp_arch); } else tmp_arch = talloc_reference (NULL, arch); status = copy_archive_revision (dest_dir, tmp_arch, my_patch); talloc_free (tmp_arch); } else { status = copy_local_revision (dest_dir, my_patch, cache, try_link); invariant (status != 0); from_library = (status == 2); } talloc_free (my_arch); apply_revisions (dest_dir, arch, patch_list, patch_direction (patch_list, revision), non_sparse_lib); ar_free_patch_id (&patch_list); { arch_project_tree_t * tree; arch_set_tree_version (dest_dir, revision); tree = arch_project_tree_new (talloc_context, dest_dir); arch_snap_inode_sig (tree, revision); arch_project_tree_delete (tree); } return status; } static void select_patch_list (ar_patch_id * patch_list, ar_patch_id candidate) { int candidate_n = ar_size (candidate); int current_n = ar_size (*patch_list); if (candidate_n < 1) { ar_free_patch_id (&candidate); return; } else if (current_n < 1) { ar_free_patch_id (patch_list); * patch_list = candidate; } else if (current_n > candidate_n) { ar_free_patch_id (patch_list); * patch_list = candidate; } } ar_patch_id make_patch_list (arch_patch_id * ancestor, arch_patch_id * descendant, assoc_table contin_list, struct arch_archive * arch) { ar_patch_id patch_list = NULL; arch_patch_id * my_revision = talloc_reference (NULL, descendant); struct arch_archive * my_arch = talloc_reference (NULL, arch); while (arch_patch_id_cmp (my_revision, ancestor)) { /* loop backwards from descendant until we reach ancestor * descendant is passed to decrement revisionas a check for archive closure I think */ ar_push_patch_id (&patch_list, my_revision); talloc_reference (ar_base (patch_list), my_revision); if (!(my_revision = decrement_revision (&my_arch, my_revision, contin_list, arch))) ar_free_patch_id (&patch_list); } ar_push_patch_id (&patch_list, my_revision); talloc_reference (ar_base (patch_list), my_revision); talloc_free (my_revision); talloc_free (my_arch); /* TODO factor into ar_reverse .. */ { int a; int b; a = 0; b = ar_size (patch_list) - 1; while (a < b) { arch_patch_id * tmp; tmp = patch_list[a]; patch_list[a] = patch_list[b]; patch_list[b] = tmp; ++a; --b; } } return patch_list; } static int patch_direction (ar_patch_id patch_list, arch_patch_id * const revision) { int apply_direction = 1; if (ar_size (patch_list) > 0) { if (!arch_patch_id_cmp (revision , patch_list[0])) apply_direction = -1; } return apply_direction; } static arch_patch_id * decrement_revision (struct arch_archive ** my_arch, arch_patch_id * const current_revision, assoc_table contin_list, struct arch_archive * arch) { struct arch_archive *prev_arch = 0; arch_patch_id * prev_revision; if (!(prev_revision = find_for_previous_revision (&prev_arch, contin_list, *my_arch, current_revision))) return NULL; talloc_free (*my_arch); *my_arch = prev_arch; talloc_free (current_revision); return talloc_reference (NULL, prev_revision); } static void find_patchlists (ar_patch_id * loc_patches, ar_patch_id * arch_patches, arch_patch_id * patch_id, struct arch_archive * arch, assoc_table * contin_list, arch_project_tree_t * cache) { arch_patch_id * lib_descendant_revision_patch; ar_patch_id loc_ancestor_patches = NULL; ar_patch_id descendant_patches = NULL; arch_patch_id * loc_ancestor = 0; arch_patch_id * arch_ancestor = NULL; debug (dbg_builder, 1, "* Scanning for full-tree revision: "); find_prev_revision (contin_list, &loc_ancestor, &arch_ancestor, arch, patch_id, cache); if (loc_ancestor) { loc_ancestor_patches = make_patch_list (loc_ancestor, patch_id, *contin_list, arch); talloc_free (loc_ancestor); } if (arch_ancestor) *arch_patches = make_patch_list (arch_ancestor, patch_id, *contin_list, arch); if ((lib_descendant_revision_patch = find_library_forwards (patch_id))) { if (direct_descendant (arch, patch_id, lib_descendant_revision_patch)) descendant_patches = make_patch_list (patch_id, lib_descendant_revision_patch, *contin_list, arch); talloc_free (lib_descendant_revision_patch); } debug (dbg_builder, 1, "\n"); select_patch_list (loc_patches, descendant_patches); select_patch_list (loc_patches, loc_ancestor_patches); talloc_free (arch_ancestor); } /** * \brief Determine whether a revision is a descedant of a candidate revision * \note Because of in-version tags or imports, a namespace-subsequent revision * may not be a descendant. * \param arch An archive that may be useful. May not be NULL * \param revision The possible ancestor * \param candidate_descendant The possible descendant */ static int direct_descendant (struct arch_archive * arch, arch_patch_id *revision, arch_patch_id * candidate_descendant) { enum arch_revision_type type; int num_scanned; arch_patch_id * last_revision = scan_archive (&type, &num_scanned, NULL, arch, candidate_descendant, revision, -1); int answer = arch_patch_id_cmp (last_revision, revision) == 0; invariant_str_cmp (arch_patch_id_archive (revision), arch->official_name); invariant_str_cmp (arch_patch_id_archive (candidate_descendant), arch->official_name); talloc_free (last_revision); return answer; } /* * Note that tag-based branches, the previous revision is not an *ancestor* */ static arch_patch_id * find_previous_library (arch_patch_id * revision) { int r; rel_table revisions = arch_library_revisions (arch_patch_id_archive (revision), arch_patch_id_version (revision), 0); arch_patch_id * found_revision = NULL; for (r = rel_n_records (revisions)-1; r >=0; r--) { if (arch_cmp_revision (revisions[r][0], arch_patch_id_patchlevel (revision)) <= 0) { t_uchar * temp = str_alloc_cat_many (0, arch_patch_id_version (revision), "--", revisions[r][0], str_end); found_revision = arch_patch_id_new_archive (arch_patch_id_archive (revision), temp); debug (dbg_builder, 3, "* found previous library revision %s\n", arch_patch_id_patch_id (found_revision)); lim_free (0, temp); break; } } rel_free_table(revisions); return found_revision; } /* * Note that tag-based branches, the previous revision is not an *ancestor* */ static arch_patch_id * find_previous_pristine (struct arch_archive *arch, arch_patch_id * revision, arch_project_tree_t * cache) { arch_patch_id * my_revision; if (!cache) return NULL; my_revision = talloc_reference (NULL, revision); while (my_revision) { arch_patch_id * prev_revision; /**************************************************************** * Try the pristine cache. */ arch_project_tree_t * pristine_copy = arch_find_pristine (cache, my_revision, arch_cache_dir_pristine_search); if (pristine_copy) { arch_project_tree_delete (pristine_copy); return my_revision; } /******************************** * Find the previous revision */ prev_revision = arch_previous_revision (arch, my_revision); talloc_free (my_revision); my_revision = prev_revision; } return NULL; } /** * \brief Finds a ancestor or descendant full trees for a revision * \param lib_rev library revisions */ static int find_multisource (arch_patch_id ** lib_rev, arch_patch_id ** arch_rev, arch_patch_id ** continuation, struct arch_archive * arch, arch_patch_id * revision, arch_project_tree_t * cache) { int i = 0; arch_patch_id * candidate_revision; arch_patch_id * pristine_candidate; arch_patch_id * last_revision = NULL; arch_patch_id * newest_cached = NULL; arch_patch_id ** newest_cached_ptr = (arch_rev != NULL) ? &newest_cached : NULL; enum arch_revision_type type; debug (dbg_builder, 3, "* checking for %s/%s or earlier\n", arch->official_name, revision); if (arch_rev && *arch_rev) arch_rev = 0; candidate_revision = find_previous_library (revision); pristine_candidate = find_previous_pristine (arch, revision, cache); if (pristine_candidate) { int use_pristine = 0; debug (dbg_builder, 3, "* found candidate pristine %s\n", arch_patch_id_patch_id (pristine_candidate)); if (!candidate_revision || /* should this be arch_patch_id_cmp () < 0 ? RBC 20050522 */ (arch_cmp_revision (arch_patch_id_patchlevel (candidate_revision), arch_patch_id_patchlevel (pristine_candidate)) < 0)) use_pristine = 1; { talloc_free (candidate_revision); candidate_revision = talloc_reference (NULL, pristine_candidate); } talloc_free (pristine_candidate); } last_revision = scan_archive (&type, &i, newest_cached_ptr, arch, revision, candidate_revision, 50); if (arch_rev) { if (newest_cached) *arch_rev = newest_cached; else if (type == arch_import_revision) *arch_rev = talloc_reference (NULL, last_revision); if (*arch_rev) debug (dbg_builder, 3, "* found archive revision %s\n", arch_patch_id_patch_id (*arch_rev)); } if (candidate_revision && !arch_patch_id_cmp (candidate_revision, last_revision)) *lib_rev = talloc_reference (NULL, candidate_revision); else lib_rev = NULL; if (type == arch_continuation_revision) *continuation = talloc_reference (NULL, last_revision); talloc_free (candidate_revision); talloc_free (last_revision); return i; } static void find_prev_revision (assoc_table * contin_list, arch_patch_id ** local_rev, arch_patch_id ** arch_rev, struct arch_archive * arch, arch_patch_id * start_revision, arch_project_tree_t * cache) { struct arch_archive * my_arch = talloc_reference (NULL, arch); int patch_n = 0; int arch_rev_n = -1; arch_patch_id * my_start_revision = talloc_reference (NULL, start_revision); *arch_rev = NULL; while (1) { arch_patch_id * prev_revision = 0; arch_patch_id * continuation = NULL; patch_n += find_multisource (local_rev, *arch_rev ? NULL : arch_rev, &continuation, my_arch, my_start_revision, cache); talloc_free (my_start_revision); my_start_revision = NULL; /* BUG FOR MULTIPLE CACHEREVS */ if (*arch_rev && arch_rev_n == -1) { ar_patch_id arch_rev_patches = make_patch_list (*arch_rev, start_revision, *contin_list, arch); arch_rev_n = ar_size (arch_rev_patches) - 1; } if (continuation) { prev_revision = arch_get_continuation (my_arch, continuation); if (!arch_valid_package_name (arch_patch_id_patch_id (prev_revision), arch_req_archive, arch_req_patch_level, 0)) { safe_printfmt (2, "arch_build_revision: archive contains bogus CONTINUATION file\n localtion: %s\n revision: %s\n bogosity: %s\n", arch->location, arch_patch_id_patch_id (continuation), arch_patch_id_patch_id (prev_revision)); exit (2); } assoc_set (contin_list, arch_patch_id_patch_id (continuation), arch_patch_id_patch_id (prev_revision) ); talloc_free (continuation); } if (*local_rev) return; if (arch_rev_n >= 0 && arch_rev_n + 50 < patch_n) return; if (prev_revision) { my_start_revision = arch_patch_id_copy (prev_revision); if ( str_cmp (my_arch->official_name, arch_patch_id_archive (prev_revision)) ) { talloc_free (my_arch); my_arch = arch_archive_connect_readonly (arch_patch_id_archive (prev_revision)); } if (!my_arch) debug (dbg_builder, 3, "* archive %s is not registered or is inaccessible\n", arch_patch_id_archive (prev_revision)); } if (!my_arch || !my_start_revision) break; } talloc_free (my_arch); } arch_patch_id * scan_archive(enum arch_revision_type * type, int * num_scanned, arch_patch_id ** newest_cached, struct arch_archive *arch, arch_patch_id * start_revision, arch_patch_id * end_revision, int max_count_after_cacherev) { int count_after_cacherev = 0; arch_patch_id * my_patch = arch_patch_id_copy (start_revision); debug (dbg_builder, 6, "scanning from '%s'\n", arch_patch_id_patch_id (my_patch)); if (newest_cached != NULL) *newest_cached = NULL; for (*num_scanned = 0; count_after_cacherev < max_count_after_cacherev || max_count_after_cacherev == -1; ++*num_scanned) { int is_cached = 0; int * is_cached_ptr = &is_cached; arch_patch_id * prev_revision = NULL; if (newest_cached == NULL || *newest_cached != NULL) { is_cached_ptr = NULL; count_after_cacherev ++; } arch_revision_type (type, is_cached_ptr, NULL, arch, my_patch); debug (dbg_builder, 1, "."); if (is_cached) { if (newest_cached != NULL && *newest_cached == 0) *newest_cached = arch_patch_id_copy (my_patch); } if (*type != arch_simple_revision) break; if (end_revision && !arch_patch_id_cmp (my_patch, end_revision)) break; prev_revision = arch_previous_revision (arch, my_patch); /* simple revisions are always in the same archive namespace */ if (prev_revision) arch_ancestry_note_ancestor (my_patch, arch_patch_id_patch_id (prev_revision)); talloc_free (my_patch); my_patch = prev_revision; } return my_patch; } /** * \brief find a revision library entry forwards from revision * \return a patch_id or NULL if nothing was found. */ arch_patch_id * find_library_forwards (arch_patch_id * revision) { int r; arch_patch_id * answer = NULL; rel_table revisions = arch_library_revisions (arch_patch_id_archive (revision), arch_patch_id_version (revision), 0); debug (dbg_builder, 3, "* checking libraries for %s or later\n", arch_patch_id_patch_id (revision)); for (r = 0; r < rel_n_records (revisions); r++ ) { if (arch_cmp_revision (revisions[r][0], arch_patch_id_patchlevel (revision)) >= 0) { t_uchar * f_revision = str_alloc_cat_many (0, arch_patch_id_branch (revision), "--", revisions[r][0], str_end); answer = arch_patch_id_new (f_revision); lim_free (0, f_revision); debug (dbg_builder, 3, "* found descendant revision %s\n", arch_patch_id_patch_id (answer)); break; } } rel_free_table(revisions); return answer; } static int copy_local_revision (t_uchar const * const dest_dir, arch_patch_id * revision, arch_project_tree_t * cache, int try_link) { if (copy_pristine_revision (dest_dir, revision, cache)) return 3; else return copy_library_revision (dest_dir, revision, try_link); return 0; } static int copy_library_revision (t_uchar const * const dest_dir, arch_patch_id * revision, int try_link) { rel_table index_by_name = 0; int status = 0; arch_project_tree_t * library_rev_tree; library_rev_tree = arch_library_find (0, revision, 1); if (!library_rev_tree) return status; if (try_link) { struct stat from, to; safe_stat (library_rev_tree->root, &from); safe_stat (dest_dir, &to); if (from.st_dev == to.st_dev) { debug (dbg_builder, 1, "* from revision library (linking): %s\n", arch_patch_id_patch_id (revision)); safe_rmdir (dest_dir); build_link_tree (library_rev_tree->root, dest_dir); status = 2; goto link_done; } } debug (dbg_builder, 1, "* from revision library: %s\n", arch_patch_id_patch_id (revision)); index_by_name = arch_library_index (revision); rel_sort_table_by_field (0, index_by_name, 0); copy_file_list (dest_dir, library_rev_tree->root, index_by_name); rel_free_table (index_by_name); status = 1; link_done: arch_project_tree_delete (library_rev_tree); return status; } static int copy_pristine_revision (t_uchar const * const dest_dir, arch_patch_id * revision, arch_project_tree_t * cache) { arch_project_tree_t * pristine_copy; rel_table file_list = 0; if (!cache) return 0; pristine_copy = arch_find_pristine (cache, revision, arch_cache_dir_pristine_search); if (!pristine_copy) return 0; debug (dbg_builder, 1, "* from pristine cache: %s\n", arch_patch_id_patch_id (revision)); file_list = arch_source_inventory (pristine_copy, 1, 0, 0); copy_file_list (dest_dir, pristine_copy->root, file_list); rel_free_table (file_list); arch_project_tree_delete(pristine_copy); return 1; } static void move_tmp_revision (t_uchar const * const dest_dir, t_uchar const * const tmpdir) { int x; rel_table files = directory_files (tmpdir); for (x = 0; x < rel_n_records (files); ++x) { if (str_cmp (".", files[x][0]) && str_cmp ("..", files[x][0])) { t_uchar * frm = 0; t_uchar * to = 0; frm = file_name_in_vicinity (0, tmpdir, files[x][0]); to = file_name_in_vicinity (0, dest_dir, files[x][0]); safe_rename (frm, to); lim_free (0, frm); lim_free (0, to); } } safe_rmdir (tmpdir); rel_free_table (files); } static int copy_archive_revision(t_uchar const * const dest_dir, struct arch_archive *arch, arch_patch_id * revision) { enum arch_revision_type type; int is_cached; arch_revision_type (&type, &is_cached, NULL, arch, revision); /******************************** * Archive Cached Revision? */ if (is_cached) { copy_cached_revision (dest_dir, arch, revision); /* ensure that inode sigs from the archive are ignored */ arch_clear_inode_sigs (dest_dir); return 5; } /******************************** * Import Revision? */ else if (type == arch_import_revision) { copy_import_revision (dest_dir, arch, revision); /* ensure that inode sigs from the archive are ignored */ arch_clear_inode_sigs (dest_dir); return 4; } else return 0; } static void copy_cached_revision(t_uchar const * const dest_dir, struct arch_archive * arch, arch_patch_id * revision) { t_uchar * tmpdir = 0; debug (dbg_builder, 1, "* from archive cached: %s\n", arch_patch_id_patch_id (revision)); tmpdir = talloc_tmp_file_name (talloc_context, dest_dir, ",,cached-revision"); arch_get_cached_revision (arch, revision, tmpdir); move_tmp_revision (dest_dir, tmpdir); talloc_free (tmpdir); } static void copy_import_revision (t_uchar const * const dest_dir, struct arch_archive * arch, arch_patch_id * const revision) { t_uchar * tmpdir = 0; debug (dbg_builder, 1, "* from import revision: %s\n", arch_patch_id_patch_id (revision)); tmpdir = talloc_tmp_file_name (talloc_context, dest_dir, ",,import-revision"); arch_get_import_revision (arch, revision, tmpdir); move_tmp_revision (dest_dir, tmpdir); talloc_free (tmpdir); } /** * \brief find the predecessor to revision, and return it with an archive connection * \param prev_arch the archive handle to set to an archive connection. after use it should be closed * \return NULL if there is no predecessor or the archive for it is unable to be connected to */ static arch_patch_id * find_for_previous_revision (struct arch_archive ** prev_arch, assoc_table contin_list, struct arch_archive * arch, arch_patch_id * const revision) { arch_patch_id * prev_revision; t_uchar * fq_prev_revision = assoc_ref (contin_list, arch_patch_id_patch_id (revision)); if (!fq_prev_revision) { prev_revision = arch_previous_revision (arch, revision); if (!prev_revision) return NULL; *prev_arch = talloc_reference (NULL, arch); return prev_revision; } else { if (!arch_valid_package_name (fq_prev_revision, arch_req_archive, arch_req_patch_level, 0)) { safe_printfmt (2, "arch_build_revision: continuation list contains bogus entry\n revision: %s\n bogosity: %s\n", arch_patch_id_patch_id (revision), fq_prev_revision); exit (2); } prev_revision = arch_patch_id_new (fq_prev_revision); *prev_arch = arch_archive_connect_readonly (arch_patch_id_archive (prev_revision)); if (*prev_arch == NULL) { /* cannot connect */ talloc_free (prev_revision); return NULL; } return prev_revision; } } static void apply_revision_patch (t_uchar const * const dest_tree_root, struct arch_archive * arch, arch_patch_id * const revision, int reverse) { t_uchar * tmppatch = 0; struct arch_apply_changeset_report * r; arch_project_tree_t * dest_tree; dest_tree = arch_project_tree_new (talloc_context, dest_tree_root); tmppatch = talloc_tmp_file_name (talloc_context, dest_tree->root, ",,next-patch"); arch_get_patch (arch, revision, tmppatch); mem_set0 ((t_uchar *)&r, sizeof (r)); r = arch_apply_changeset (tmppatch, talloc_context, dest_tree, arch_unspecified_id_tagging, arch_inventory_unrecognized, reverse, 0, 0, NULL, NULL); if (arch_conflicts_occurred (r)) panic ("conflict applying patch in arch_build_revision"); talloc_free (r); rmrf_file (tmppatch); talloc_free (tmppatch); arch_project_tree_delete (dest_tree); } /** * \brief Apply the changesets for listed revisions to the specified directory * * The specified directory must contain a tree which can be patched into * the right revision. If going forwards, it must be the first-listed revision. * If going backwards, it must be the last-listed revision. * * The revision list is actually a list of deltas: for each revision n, the * delta between patch_list[n-1] and patchlist[n] will be applied. However, * this implementation requires that n is a direct descendant of n-1. * * \param dest_dir Directory to apply changesets to * \param arch An archive which may be useful for this. If it is not useful, * it will be ignored. It may also be NULL. * \param patch_list The list of deltas to apply, archive in column 0, * revision in 1. * \param apply_direction +1 to apply from 0 to the end, -1 to apply from end * to 0 * \param non_sparse_lib If non-NULL, a non-sparse library for revisions */ static void apply_revisions (t_uchar const * const dest_dir, struct arch_archive * arch, ar_patch_id patch_list, int apply_direction, t_uchar const * non_sparse_lib) { int patch_index; struct arch_archive * my_arch = talloc_reference (NULL, arch); int reverse = (apply_direction == 1) ? 0 : 1; int start; int end; t_uchar * reverse_text = (apply_direction < 0)? " (in reverse)" : ""; if (apply_direction == 1) { start = 1; end = ar_size (patch_list); } else { start = ar_size (patch_list) - 1; end = 0; } if (start != end) debug (dbg_builder, 1, "* Applying %d revisions%s: ", ar_size (patch_list) - 1, reverse_text); for (patch_index=start; patch_index != end; patch_index +=apply_direction) { /* non-sparse adding should not be done for the final target revision */ if (patch_index+apply_direction == end) non_sparse_lib = NULL; if (my_arch == NULL || str_cmp(my_arch->official_name, arch_patch_id_archive (patch_list[patch_index]))) { talloc_free (my_arch); my_arch = arch_archive_connect_readonly (arch_patch_id_archive (patch_list[patch_index])); if (!my_arch) { safe_printfmt (2, "Unable to connect to archive for patch %s\n", arch_patch_id_patch_id (patch_list[patch_index])); exit (2); } invariant_str_cmp (my_arch->official_name, arch_patch_id_archive (patch_list[patch_index])); } debug (dbg_builder, 1, "."); apply_revision_patch (dest_dir, my_arch, patch_list[patch_index], reverse); if (non_sparse_lib != NULL) { arch_patch_id * patch_id; if (reverse) patch_id = patch_list[patch_index - 1]; else patch_id = patch_list[patch_index]; add_to_library (dest_dir, non_sparse_lib, patch_id); } } if (start != end) debug (dbg_builder, 1, "\n"); talloc_free (my_arch); } /** * \brief Add a reference tree to the library. * * The tree is hard-linked, not copied or moved. Assumed to be on the same * filesystem as the library. * \param tree_dir The directory containing the reference tree * \param library The library to add the the revision to * \param patch_id the revision id of the revision being added */ void add_to_library (t_uchar const * const tree_dir, t_uchar const * const library, arch_patch_id * patch_id) { t_uchar * tmp_lib = str_save (0, library); t_uchar * dest_directory = arch_library_revision_dir_in_lib (tmp_lib, patch_id); t_uchar * dest_dir_dir = file_name_directory_file (0, dest_directory); t_uchar * tmp_dir = talloc_tmp_file_name (talloc_context, dest_dir_dir, ",,new-revision"); ensure_directory_exists (dest_dir_dir); build_link_tree(tree_dir, tmp_dir); finalize_library_revision (dest_directory, tmp_dir, patch_id); talloc_free (tmp_dir); lim_free (0, dest_dir_dir); lim_free (0, dest_directory); lim_free (0, tmp_lib); } /* tag: Tom Lord Wed May 21 15:59:22 2003 (build-revision.c) */