/* project-tree.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/panic.h" #include "hackerlab/bugs/exception.h" #include "hackerlab/os/errno.h" #include "hackerlab/os/errno-to-string.h" #include "hackerlab/vu/safe.h" #include "hackerlab/fs/cwd.h" #include "hackerlab/fs/file-names.h" #include "hackerlab/char/str.h" #include "libfsutils/file-contents.h" #include "libawk/trim.h" #include "libarch/ancestry.h" #include "libarch/namespace.h" #include "libarch/patch-id.h" #include "libarch/patch-logs.h" #include "libarch/changelogs.h" #include "libarch/inv-ids.h" #include "libarch/null-project-tree.h" #include "libarch/tla-project-tree.h" #include "libarch/pfs.h" #include "libarch/project-tree.h" static void arch_project_tree_check_name_ext (arch_project_tree_t * tree, struct arch_archive ** arch, t_uchar ** namespace, t_uchar const * const name, struct arch_archive * (*connect_function)(t_uchar const * const name, t_uchar **name_out)); static int arch_project_tree_destructor (void * data); /* These must agree about the format version number. */ void arch_init_tree (t_uchar const * const tree_root) { tla_project_tree_init_directory (tree_root); } /* Move to fsutils or hackerlab */ static int is_dir (t_uchar const *path) { int errn; int answer; struct stat stat_buf; answer = vu_lstat (&errn, (char *)path, &stat_buf); if (answer) { /* couldn't stat, pretend its not a dir */ return 0; } return S_ISDIR(stat_buf.st_mode); } /** * \brief find the tree root. * * finds the tree root for a tree, for any version tree */ t_uchar * arch_tree_root (enum arch_tree_state * state, t_uchar const * const input_dir, int accurate) { return arch_tree_root_ext (NULL, state, input_dir, accurate, NULL, 0); } /** * \brief find the root of a tree and provide a vtable for the found tree * \param exact the path is the tree root, do not search, and panic if missing */ t_uchar * arch_tree_root_ext (void * context, enum arch_tree_state * state, t_uchar const * const _input_dir, int accurate, arch_project_tree_t **object_out, int exact) { int here_fd = -1; /* shutup gcc, here_fd is used properly */ int errn; t_uchar * dir; t_uchar * answer; t_uchar * input_dir = str_save (0, _input_dir); answer = 0; if (str_length(input_dir)) { if (!is_dir (input_dir)) { input_dir = str_replace (input_dir, file_name_directory_file (0, input_dir)); if (! input_dir) input_dir = str_save (0, "."); } here_fd = safe_open (".", O_RDONLY, 0); safe_chdir (input_dir); } dir = (t_uchar *)current_working_directory (&errn, 0); if (!dir) panic ("unable to compute current working directory"); if (input_dir) { safe_fchdir (here_fd); safe_close (here_fd); } if (exact) { if (!arch_project_tree_dir_is_root (context, dir, object_out)) panic ("exact tree root is not a tree - perhaps an unsupported tree type ?"); answer = str_save (0, dir); } else while (1) { t_uchar * next_dir; if (arch_project_tree_dir_is_root (context, dir, object_out)) { answer = str_save (0, dir); break; } if (!str_cmp (dir, "/")) break; next_dir = file_name_directory_file (0, dir); lim_free (0, dir); dir = next_dir; next_dir = 0; } if (answer && accurate) { t_uchar * rc_name; t_uchar * cd_name; t_uchar * mc_name; rc_name = file_name_in_vicinity (0, answer, "{arch}/++resolve-conflicts"); cd_name = file_name_in_vicinity (0, answer, "{arch}/++commit-definite"); mc_name = file_name_in_vicinity (0, answer, "{arch}/++mid-commit"); if (state) { if (!safe_access (rc_name, F_OK)) *state = arch_tree_in_resolve_conflicts; else if (!safe_access (cd_name, F_OK)) *state = arch_tree_in_commit_definite; else if (!safe_access (mc_name, F_OK)) *state = arch_tree_in_mid_commit; else *state = arch_tree_in_ok_state; } } lim_free (0, dir); lim_free (0, input_dir); return answer; } t_uchar * arch_tree_ctl_dir (t_uchar const * tree_root) { return file_name_in_vicinity (0, tree_root, "{arch}"); } void arch_set_tree_version (t_uchar const * tree_root, arch_patch_id * patch_id) { t_uchar * default_version_file = 0; t_uchar * tmp_file = 0; int out_fd; invariant (arch_valid_package_name (arch_patch_id_branch (patch_id), arch_req_archive, arch_req_version, 0)); tmp_file = file_name_in_vicinity (0, tree_root, "{arch}/,,set-tree-version"); default_version_file = file_name_in_vicinity (0, tree_root, "{arch}/++default-version"); out_fd = safe_open (tmp_file, O_WRONLY | O_CREAT, 0666); safe_ftruncate (out_fd, (long)0); safe_printfmt (out_fd, "%s\n", arch_patch_id_branch (patch_id)); safe_close (out_fd); safe_rename (tmp_file, default_version_file); lim_free (0, tmp_file); lim_free (0, default_version_file); } t_uchar * arch_tree_version (t_uchar const * tree_root) { t_uchar * default_version_file; int in_fd; t_uchar * file_contents; size_t file_contents_len; t_uchar * nl; default_version_file = file_name_in_vicinity (0, tree_root, "{arch}/++default-version"); if (safe_access (default_version_file, F_OK)) { lim_free (0, default_version_file); return 0; } in_fd = safe_open (default_version_file, O_RDONLY, 0666); file_contents = 0; file_contents_len = 0; safe_file_to_string (&file_contents, &file_contents_len, in_fd); safe_close (in_fd); nl = str_chr_index_n (file_contents, file_contents_len, '\n'); if (nl) file_contents_len = nl - file_contents; file_contents = lim_realloc (0, file_contents, file_contents_len + 1); file_contents[file_contents_len] = 0; invariant (arch_valid_package_name (file_contents, arch_req_archive, arch_req_version, 0)); lim_free (0, default_version_file); return file_contents; } t_uchar * arch_try_tree_version_dir (t_uchar * cmd, arch_project_tree_t * tree) { t_uchar * version_spec = 0; if (!tree->root) { safe_printfmt (2, "%s: not in a project tree\n", cmd); exit (2); } version_spec = arch_tree_version (tree->root); if (!version_spec) { safe_printfmt (2, "%s: tree has no default version set\n tree: %s\n", cmd, tree->root); exit (2); } return version_spec; } t_uchar * arch_try_tree_version (t_uchar * cmd) { t_uchar * answer; arch_project_tree_t * tree = arch_project_tree_new (talloc_context, "."); answer = arch_try_tree_version_dir (cmd, tree); talloc_unlink (talloc_context, tree); return answer; } void arch_start_tree_commit (arch_project_tree_t * tree, t_uchar * log) { int ign; t_uchar * mid_commit_file = 0; t_uchar * mid_commit_tmp = 0; int out_fd; mid_commit_file = file_name_in_vicinity (0, tree->root, "{arch}/++mid-commit"); mid_commit_tmp = file_name_in_vicinity (0, tree->root, "{arch}/,,mid-commit"); vu_unlink (&ign, mid_commit_tmp); vu_unlink (&ign, mid_commit_file); out_fd = safe_open (mid_commit_tmp, O_WRONLY | O_CREAT | O_EXCL, 0666); safe_printfmt (out_fd, "%s", log); safe_close (out_fd); safe_rename (mid_commit_tmp, mid_commit_file); lim_free (0, mid_commit_file); } void arch_finish_tree_commit (arch_project_tree_t * tree, t_uchar * archive, t_uchar * revision, t_uchar * changelog_loc) { t_uchar * mid_commit_file = 0; t_uchar * commit_definite_file = 0; mid_commit_file = file_name_in_vicinity (0, tree->root, "{arch}/++mid-commit"); commit_definite_file = file_name_in_vicinity (0, tree->root, "{arch}/++commit-definite"); safe_rename (mid_commit_file, commit_definite_file); if (changelog_loc) { struct stat statb; t_uchar * level = 0; t_uchar * version = 0; t_uchar * changelog_path = 0; t_uchar * changelog_dir = 0; t_uchar * changelog_tmp = 0; mode_t mode; int fd; level = arch_parse_package_name (arch_ret_patch_level, 0, revision); version = arch_parse_package_name (arch_ret_package_version, 0, revision); changelog_path = file_name_in_vicinity (0, tree->root, changelog_loc); changelog_dir = file_name_directory_file (0, changelog_path); changelog_tmp = file_name_in_vicinity (0, changelog_dir, ",,new-changelog"); safe_stat (changelog_path, &statb); mode = statb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); fd = safe_open (changelog_tmp, O_WRONLY | O_CREAT | O_TRUNC, mode); safe_fchmod (fd, mode); arch_generate_changelog (fd, tree, 0, 0, level, commit_definite_file, archive, version); safe_close (fd); safe_rename (changelog_tmp, changelog_path); lim_free (0, level); lim_free (0, version); lim_free (0, changelog_path); lim_free (0, changelog_dir); lim_free (0, changelog_tmp); } arch_copy_to_patch_log (tree->root, archive, revision, commit_definite_file); safe_unlink (commit_definite_file); lim_free (0, mid_commit_file); lim_free (0, commit_definite_file); } void arch_abort_tree_commit (arch_project_tree_t * tree, t_uchar * archive, t_uchar * revision) { t_uchar * mid_commit_file = 0; mid_commit_file = file_name_in_vicinity (0, tree->root, "{arch}/++mid-commit"); safe_unlink (mid_commit_file); lim_free (0, mid_commit_file); } /** * \brief common project_tree initialisation * * root must be set explicitly (NULL is ok) before calling this. * \param tree the tree to init */ void arch_project_tree_init (arch_project_tree_t *tree) { t_uchar * latest_log; tree->archive = NULL; tree->fqversion = NULL; tree->fqrevision = NULL; tree->version = NULL; tree->revision = NULL; tree->ancestry = NULL; tree->local_ancestry = NULL; { /* these do not belong here. however layering is still tricky for * them. so we give them arbitrary initialized values and then the * concrete classes can override */ tree->untagged_is_source = 0; tree->id_tagging_shortcut = NULL; tree->explicit_skips = NULL; } arch_patch_log_dirs_init (&tree->patch_logs); if (!tree->root) { /* FIXME audit for non root tree-usage, and nuke it. * all those should become null trees */ tree->untagged_is_source = 1; tree->tag_method = arch_names_id_tagging; /* ancient default */ return ; } tree->fqversion = arch_tree_version (tree->root); if (tree->fqversion) { tree->archive = arch_parse_package_name (arch_ret_archive, 0, tree->fqversion); tree->version = arch_parse_package_name (arch_ret_non_archive, 0, tree->fqversion); /* tree_root should have done this right.. but lets be sure */ invariant (!!tree->archive); invariant (!!tree->version); latest_log = arch_highest_patch_level (tree->root, tree->archive, tree->version); if (latest_log) { tree->fqrevision = str_alloc_cat_many (0, tree->archive, "/", tree->version, "--", latest_log, str_end); tree->revision = str_alloc_cat_many (0, tree->version, "--", latest_log, str_end); lim_free (0, latest_log); } } if (tree->vtable) tree->vtable->init (tree); } /** * \brief initialize a project tree object * \param tree_root the possible tree root * \return an initialized tree. If no root was found, a tree with no vtable is returned. */ struct arch_project_tree * arch_project_tree_new (void * context, t_uchar const * tree_root) { return arch_project_tree_new_ext (context, tree_root, 0, 0); } /** * \brief initialize a project tree object * \param tree_root the exact path to the tree root * \return an initialized tree. If no root was found, a panic occurs */ struct arch_project_tree * arch_project_tree_new_exact (void * context, t_uchar const * tree_root) { return arch_project_tree_new_ext (context, tree_root, 0, 1); } /** * \brief initialize a project tree object * \param tree_root the possible tree root * \param null_ok if true, and no tree root was found, initialize a null tree. * \return an initialized tree. If no root was found, a tree with no vtable is returned. */ struct arch_project_tree * arch_project_tree_new_ext (void * context, t_uchar const * tree_root, int null_ok, int exact) { arch_project_tree_t * result; t_uchar * root; root = arch_tree_root_ext (context, 0, tree_root, 0, &result, exact); if (!root && null_ok) { if (null_project_tree_dir_is_root (context, tree_root, &result)) root = arch_abs_path (tree_root); } if (!root) { result = talloc (context, arch_project_tree_t); talloc_set_destructor (result, arch_project_tree_destructor); result->vtable = NULL; } result->root = root; arch_project_tree_init (result); return result; } int arch_project_tree_destructor (void * data) { arch_project_tree_t * tree = talloc_get_type (data, arch_project_tree_t); if (!tree) Throw (exception (EINVAL, "invalid tree in arch_project_tree_destructor")); arch_project_tree_finalise (tree); return 0; } void arch_project_tree_finalise (arch_project_tree_t *tree) { lim_free (0, tree->root); lim_free (0, tree->archive); lim_free (0, tree->fqversion); lim_free (0, tree->fqrevision); lim_free (0, tree->version); lim_free (0, tree->revision); rel_free_table (tree->ancestry); free_assoc_table (tree->local_ancestry); arch_patch_log_dirs_finalise (&tree->patch_logs); free_assoc_table (tree->id_tagging_shortcut); free_assoc_table (tree->explicit_skips); } int arch_project_tree_file_exists(struct arch_project_tree *tree, t_uchar const *rel_path) { t_uchar *path = file_name_in_vicinity (0, tree->root, rel_path); struct stat stat_buf; int result = stat (path, &stat_buf); lim_free (0, path); return result == 0; } t_uchar * arch_project_tree_file_contents(struct arch_project_tree *tree, t_uchar const *rel_path) { t_uchar *path = file_name_in_vicinity (0, tree->root, rel_path); t_uchar *answer = file_contents (path); lim_free (0, path); return answer; } /* * \brief get as much of the trees ancestry as we can. Uses the archive * to retrieve the ancestry. * Ownership of the table is retained by the project tree struct * \throw as per patch_ancestry */ rel_table /* const */ arch_project_tree_ancestry (struct arch_project_tree *tree) { if (tree->ancestry) return tree->ancestry; arch_patch_id * patch_id = arch_patch_id_new (tree->fqrevision); tree->ancestry = patch_ancestry (tree, tree, patch_id, -1); talloc_free (patch_id); return tree->ancestry; } int arch_project_tree_has_patch (arch_project_tree_t *tree, t_uchar const *patch_id) { int answer = 0; t_uchar *log_file = arch_project_tree_patch_filename (tree, patch_id); /* log files are never symlinks etc */ if (!safe_access (log_file, F_OK)) answer = -1; lim_free (0, log_file); return answer; } /** * \@ brief find the filename a patch will be stored under, if it is present * as a file in the tree */ t_uchar * arch_project_tree_patch_filename (arch_project_tree_t *tree, t_uchar const *patch_id) { t_uchar * log_dir, *log_file; arch_patch_id thePatch; arch_patch_id_init (&thePatch, patch_id); log_dir = arch_log_dir (tree->root, arch_patch_id_archive(&thePatch), arch_patch_id_version(&thePatch)); log_file = file_name_in_vicinity (0, log_dir, arch_patch_id_patchlevel(&thePatch)); lim_free (0, log_dir); arch_patch_id_finalise (&thePatch); return log_file; } t_uchar * arch_project_tree_patch_log (arch_project_tree_t * tree, t_uchar const * fqrevision) { t_uchar * log_filename = NULL; t_uchar * log_text = NULL; if (!arch_project_tree_has_patch (tree, fqrevision)) return NULL; log_filename = arch_project_tree_patch_filename (tree, fqrevision); log_text = file_contents (log_filename); lim_free (0, log_filename); return log_text; } /** * \brief get the inventory entry for a given path from tree * \param tree duh * \param path the tree relative path, optionally prefixed with ./ * \param known_lstat a cached stat result. if not present and the tree type requires it, a stat will be performed */ inventory_entry_t * arch_project_tree_path_inventory (arch_project_tree_t * tree, t_uchar const * const path, struct stat * known_lstat) { int errn = 0; inventory_entry_t *answer = tree->vtable->path_id (tree, &errn, path, known_lstat); if (!answer && errn) { safe_printfmt (2, "error finding file id (%d: %s)\n path: %s\n", errn, errno_to_string(errn), path); panic ("arch_project_tree_path_inventory"); } return answer; } /** * \brief given a filename, qualify it relative to a tree root: * This uses the current working directory if needed - not suitable * for use by libarch / * functions. * \throw EINVAL if either parameter is NULL, or if filename * while qualified is not relative to the tree root. * \param tree - the tree object * \param filename - the file to make relative * \return the relative file path */ t_uchar * arch_project_tree_rel_path_from_user_input (arch_project_tree_t * tree, t_uchar const * const filename) { t_uchar * abs_filename; t_uchar * qualified; if (!tree->root || !filename) Throw (exception (EINVAL, "arch_project_tree_rel_path_from_user_input: invalid parameters")); abs_filename = arch_abs_path (filename); qualified = arch_project_tree_rel_path_from_abs(tree, abs_filename); lim_free (0, abs_filename); return qualified; } /** * \brief canonicalise a path within a project tree. this depends on the tree root and the abs path both being in canonical normal form * * passing in a path that isn't in the tree will cause a panic - this is deliberate, as user input never hits this directly. * \param tree the tree * \param abs_path the path to canonicalise */ t_uchar * arch_project_tree_rel_path_from_abs (arch_project_tree_t * tree, t_uchar const * const abs_path) { t_uchar * result; invariant (!!tree->root); /* TODO canonicalise both etc etc */ if ( str_cmp_prefix (tree->root, abs_path)) { safe_printfmt (2, "abs_path (%s) is not a subpath of tree root (%s)\n", abs_path, tree->root); exit (2); } result = str_save (0, abs_path+str_length(tree->root)); return str_replace (result, str_alloc_cat (0, ".", result)); } /** * \brief establish an archive handle (possibly lazy connecting) to a user name, in the context of a tree with a connection type constraint. * * i.e. patch-1 -> tree-archive & tree-version * foo--bar--0 -> tree-archive & & foo--bar--0 * foo/bar.. -> foo & bar * on failure, *arch is null; */ void arch_project_tree_check_name_ext (arch_project_tree_t * tree, struct arch_archive ** arch, t_uchar ** namespace, t_uchar const * const name, struct arch_archive * (*connect_function)(t_uchar const * const name, t_uchar **name_out)) { /* its a patch level : qualify with the tree version */ if (tree->fqversion && arch_valid_patch_level_name (name)) { /* get the official name for the tree */ *arch = connect_function (tree->fqversion, namespace); if (!*arch) return; /* append the patch-level */ *namespace = str_replace (*namespace, str_alloc_cat_many (0, *namespace, "--", name, str_end)); return; } /* its a relative branch name : qualify with the tree archive */ if (tree->archive && arch_valid_package_name (name, arch_no_archive, arch_req_package, 1)) { /* get the official name for the tree */ *arch = connect_function (tree->archive, namespace); if (!*arch) return; /* append the user input */ *namespace = str_replace (*namespace, str_alloc_cat_many (0, *namespace, "/", name, str_end)); return; } /* its an official name or an archive url */ *arch = connect_function (name, namespace); } /** * \brief establish an archive handle (possibly lazy connecting) to a user name, in the context of a tree * * i.e. patch-1 -> tree-archive & tree-version * foo--bar--0 -> tree-archive & & foo--bar--0 * foo/bar.. -> foo & bar * on failure, *arch is null; */ void arch_project_tree_check_name (arch_project_tree_t * tree, struct arch_archive ** arch, t_uchar ** namespace, t_uchar const * const name) { arch_project_tree_check_name_ext (tree, arch, namespace, name, arch_archive_connect_branch); } /** * \brief establish an archive handle (possibly lazy connecting) to a user name, in the context of a tree. * this will be a commitable name, unless a url isprovided, in which case the url is presumed good. * * i.e. patch-1 -> tree-archive & tree-version * foo--bar--0 -> tree-archive & & foo--bar--0 * foo/bar.. -> foo & bar * on failure, *arch is null; */ void arch_project_tree_check_commitable_name (arch_project_tree_t * tree, struct arch_archive ** arch, t_uchar ** namespace, t_uchar const * const name) { arch_project_tree_check_name_ext (tree, arch, namespace, name, arch_archive_connect_commitable_branch); } /** * \brief probe wether dir is a project tree root */ int arch_project_tree_dir_is_root (void * context, t_uchar const * dir, arch_project_tree_t **object_out) { return tla_project_tree_dir_is_root (context, dir, object_out); } /** * \brief a placeholder to ensure vtable entries are added. * always last in the vtable */ int *** arch_project_tree_vtable_end (char const * const * const * foo) { return NULL; } /** * \brief perform a changeset inventory traversal. some tree types may cache the results of this */ void arch_project_tree_changeset_inventory_traversal (arch_project_tree_t * tree, struct arch_inventory_options * options, inv_callback callback, void * closure) { return tree->vtable->changeset_inventory_traveral (tree, options, callback, closure); } /** * \brief inform a tree that files in it have been changed, to flush any caches * * optional method */ void arch_project_tree_mutated (arch_project_tree_t * tree) { if (tree->vtable->mutated) tree->vtable->mutated (tree); } /* tag: Tom Lord Mon May 12 10:12:38 2003 (project-tree.c) */