got-portable-0.111/0002755000175000017500000000000015001741325007654 5got-portable-0.111/lib/0002755000175000017500000000000015001741323010420 5got-portable-0.111/lib/got_lib_gitproto.h0000644000175000017500000000460615001740614014065 /* * Copyright (c) 2019 Ori Bernstein * Copyright (c) 2021 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #define GOT_CAPA_AGENT "agent" #define GOT_CAPA_OFS_DELTA "ofs-delta" #define GOT_CAPA_SIDE_BAND_64K "side-band-64k" #define GOT_CAPA_REPORT_STATUS "report-status" #define GOT_CAPA_DELETE_REFS "delete-refs" #define GOT_CAPA_NO_THIN "no-thin" #define GOT_SIDEBAND_PACKFILE_DATA 1 #define GOT_SIDEBAND_PROGRESS_INFO 2 #define GOT_SIDEBAND_ERROR_INFO 3 #define GOT_SIDEBAND_64K_PACKFILE_DATA_MAX (GOT_PKT_MAX - 17) struct got_capability { const char *key; const char *value; }; struct got_pathlist_head; const struct got_error *got_gitproto_parse_refline(char **id_str, char **refname, char **server_capabilities, char *line, int len); const struct got_error *got_gitproto_parse_want_line(char **id_str, char **capabilities, char *line, int len); const struct got_error *got_gitproto_parse_have_line(char **id_str, char *line, int len); const struct got_error *got_gitproto_parse_ref_update_line(char **old_id_str, char **new_id_str, char **refname, char **client_capabilities, char *line, size_t len); const struct got_error *got_gitproto_match_capabilities( char **common_capabilities, struct got_pathlist_head *symrefs, char *capabilities, const struct got_capability my_capabilities[], size_t ncapa); const struct got_error *got_gitproto_append_capabilities(size_t *capalen, char *buf, size_t offset, size_t bufsize, const struct got_capability my_capabilities[], size_t ncapa); const struct got_error *got_gitproto_split_capabilities_str( struct got_capability **capabilities, size_t *ncapabilities, char *capabilities_str); got-portable-0.111/lib/object_create.c0000644000175000017500000005143015001741021013271 /* * Copyright (c) 2019 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_object.h" #include "got_repository.h" #include "got_opentemp.h" #include "got_path.h" #include "got_sigs.h" #include "got_lib_hash.h" #include "got_lib_deflate.h" #include "got_lib_delta.h" #include "got_lib_object.h" #include "got_lib_object_parse.h" #include "got_lib_lockfile.h" #include "got_lib_object_create.h" #include "buf.h" #ifndef nitems #define nitems(_a) (sizeof(_a) / sizeof((_a)[0])) #endif static const struct got_error * create_object_file(struct got_object_id *id, FILE *content, off_t content_len, struct got_repository *repo) { const struct got_error *err = NULL, *unlock_err = NULL; char *objpath = NULL, *tmppath = NULL; FILE *tmpfile = NULL; struct got_lockfile *lf = NULL; off_t tmplen = 0; err = got_object_get_path(&objpath, id, repo); if (err) return err; err = got_opentemp_named(&tmppath, &tmpfile, objpath, ""); if (err) { char *parent_path; if (!(err->code == GOT_ERR_ERRNO && errno == ENOENT)) goto done; err = got_path_dirname(&parent_path, objpath); if (err) goto done; err = got_path_mkdir(parent_path); free(parent_path); if (err) goto done; err = got_opentemp_named(&tmppath, &tmpfile, objpath, ""); if (err) goto done; } if (fchmod(fileno(tmpfile), GOT_DEFAULT_FILE_MODE) != 0) { err = got_error_from_errno2("fchmod", tmppath); goto done; } err = got_deflate_to_file(&tmplen, content, content_len, tmpfile, NULL); if (err) goto done; err = got_lockfile_lock(&lf, objpath, -1); if (err) goto done; if (rename(tmppath, objpath) != 0) { err = got_error_from_errno3("rename", tmppath, objpath); goto done; } free(tmppath); tmppath = NULL; done: free(objpath); if (tmppath) { if (unlink(tmppath) != 0 && err == NULL) err = got_error_from_errno2("unlink", tmppath); free(tmppath); } if (tmpfile && fclose(tmpfile) == EOF && err == NULL) err = got_error_from_errno("fclose"); if (lf) unlock_err = got_lockfile_unlock(lf, -1); return err ? err : unlock_err; } const struct got_error * got_object_blob_file_create(struct got_object_id **id, FILE **blobfile, off_t *blobsize, const char *ondisk_path, struct got_repository *repo) { const struct got_error *err = NULL; char *header = NULL; int fd = -1; struct stat sb; struct got_hash ctx; size_t headerlen = 0, n; *id = NULL; *blobfile = NULL; *blobsize = 0; got_hash_init(&ctx, got_repo_get_object_format(repo)); fd = open(ondisk_path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (fd == -1) { if (!got_err_open_nofollow_on_symlink()) return got_error_from_errno2("open", ondisk_path); if (lstat(ondisk_path, &sb) == -1) { err = got_error_from_errno2("lstat", ondisk_path); goto done; } } else if (fstat(fd, &sb) == -1) { err = got_error_from_errno2("fstat", ondisk_path); goto done; } if (asprintf(&header, "%s %lld", GOT_OBJ_LABEL_BLOB, (long long)sb.st_size) == -1) { err = got_error_from_errno("asprintf"); goto done; } headerlen = strlen(header) + 1; got_hash_update(&ctx, header, headerlen); *blobfile = got_opentemp(); if (*blobfile == NULL) { err = got_error_from_errno("got_opentemp"); goto done; } n = fwrite(header, 1, headerlen, *blobfile); if (n != headerlen) { err = got_ferror(*blobfile, GOT_ERR_IO); goto done; } *blobsize += headerlen; for (;;) { char buf[PATH_MAX * 8]; ssize_t inlen; if (S_ISLNK(sb.st_mode)) { inlen = readlink(ondisk_path, buf, sizeof(buf)); if (inlen == -1) { err = got_error_from_errno("readlink"); goto done; } } else { inlen = read(fd, buf, sizeof(buf)); if (inlen == -1) { err = got_error_from_errno("read"); goto done; } } if (inlen == 0) break; /* EOF */ got_hash_update(&ctx, buf, inlen); n = fwrite(buf, 1, inlen, *blobfile); if (n != inlen) { err = got_ferror(*blobfile, GOT_ERR_IO); goto done; } *blobsize += n; if (S_ISLNK(sb.st_mode)) break; } *id = calloc(1, sizeof(**id)); if (*id == NULL) { err = got_error_from_errno("calloc"); goto done; } got_hash_final_object_id(&ctx, *id); if (fflush(*blobfile) != 0) { err = got_error_from_errno("fflush"); goto done; } rewind(*blobfile); done: free(header); if (fd != -1 && close(fd) == -1 && err == NULL) err = got_error_from_errno("close"); if (err) { free(*id); *id = NULL; if (*blobfile) { fclose(*blobfile); *blobfile = NULL; } } return err; } const struct got_error * got_object_blob_create(struct got_object_id **id, const char *ondisk_path, struct got_repository *repo) { const struct got_error *err = NULL; FILE *blobfile = NULL; off_t blobsize; err = got_object_blob_file_create(id, &blobfile, &blobsize, ondisk_path, repo); if (err) return err; err = create_object_file(*id, blobfile, blobsize, repo); if (fclose(blobfile) == EOF && err == NULL) err = got_error_from_errno("fclose"); if (err) { free(*id); *id = NULL; } return err; } static const struct got_error * te_mode2str(char *buf, size_t len, struct got_tree_entry *te) { int ret; mode_t mode; /* * Some Git implementations are picky about modes seen in tree entries. * For best compatibility we normalize the file/directory mode here. */ if (S_ISREG(te->mode)) { mode = GOT_DEFAULT_FILE_MODE; if (te->mode & (S_IXUSR | S_IXGRP | S_IXOTH)) mode |= S_IXUSR | S_IXGRP | S_IXOTH; } else if (got_object_tree_entry_is_submodule(te)) mode = S_IFDIR | S_IFLNK; else if (S_ISLNK(te->mode)) mode = S_IFLNK; /* Git leaves all the other bits unset. */ else if (S_ISDIR(te->mode)) mode = S_IFDIR; /* Git leaves all the other bits unset. */ else return got_error(GOT_ERR_BAD_FILETYPE); ret = snprintf(buf, len, "%o ", mode); if (ret < 0 || (size_t)ret >= len) return got_error(GOT_ERR_NO_SPACE); return NULL; } /* * Git expects directory tree entries to be sorted with an imaginary slash * appended to their name, and will break otherwise. Let's be nice. * This function is intended to be used with mergesort(3) to sort an * array of pointers to struct got_tree_entry objects. */ static int sort_tree_entries_the_way_git_likes_it(const void *arg1, const void *arg2) { struct got_tree_entry * const *te1 = arg1; struct got_tree_entry * const *te2 = arg2; char name1[NAME_MAX + 2]; char name2[NAME_MAX + 2]; strlcpy(name1, (*te1)->name, sizeof(name1)); strlcpy(name2, (*te2)->name, sizeof(name2)); if (S_ISDIR((*te1)->mode)) strlcat(name1, "/", sizeof(name1)); if (S_ISDIR((*te2)->mode)) strlcat(name2, "/", sizeof(name2)); return strcmp(name1, name2); } const struct got_error * got_object_tree_create(struct got_object_id **id, struct got_pathlist_head *paths, int nentries, struct got_repository *repo) { const struct got_error *err = NULL; char modebuf[sizeof("100644 ")]; struct got_hash ctx; char *header = NULL; size_t headerlen, len = 0, n, digest_len; FILE *treefile = NULL; off_t treesize = 0; struct got_pathlist_entry *pe; struct got_tree_entry **sorted_entries; struct got_tree_entry *te; enum got_hash_algorithm algo; int i; *id = NULL; algo = got_repo_get_object_format(repo); digest_len = got_hash_digest_length(algo); got_hash_init(&ctx, algo); sorted_entries = calloc(nentries, sizeof(struct got_tree_entry *)); if (sorted_entries == NULL) return got_error_from_errno("calloc"); i = 0; RB_FOREACH(pe, got_pathlist_head, paths) sorted_entries[i++] = pe->data; mergesort(sorted_entries, nentries, sizeof(struct got_tree_entry *), sort_tree_entries_the_way_git_likes_it); for (i = 0; i < nentries; i++) { te = sorted_entries[i]; err = te_mode2str(modebuf, sizeof(modebuf), te); if (err) goto done; len += strlen(modebuf) + strlen(te->name) + 1 + digest_len; } if (asprintf(&header, "%s %zd", GOT_OBJ_LABEL_TREE, len) == -1) { err = got_error_from_errno("asprintf"); goto done; } headerlen = strlen(header) + 1; got_hash_update(&ctx, header, headerlen); treefile = got_opentemp(); if (treefile == NULL) { err = got_error_from_errno("got_opentemp"); goto done; } n = fwrite(header, 1, headerlen, treefile); if (n != headerlen) { err = got_ferror(treefile, GOT_ERR_IO); goto done; } treesize += headerlen; for (i = 0; i < nentries; i++) { te = sorted_entries[i]; err = te_mode2str(modebuf, sizeof(modebuf), te); if (err) goto done; len = strlen(modebuf); n = fwrite(modebuf, 1, len, treefile); if (n != len) { err = got_ferror(treefile, GOT_ERR_IO); goto done; } got_hash_update(&ctx, modebuf, len); treesize += n; len = strlen(te->name) + 1; /* must include NUL */ n = fwrite(te->name, 1, len, treefile); if (n != len) { err = got_ferror(treefile, GOT_ERR_IO); goto done; } got_hash_update(&ctx, te->name, len); treesize += n; len = digest_len; n = fwrite(te->id.hash, 1, len, treefile); if (n != len) { err = got_ferror(treefile, GOT_ERR_IO); goto done; } got_hash_update(&ctx, te->id.hash, len); treesize += n; } *id = calloc(1, sizeof(**id)); if (*id == NULL) { err = got_error_from_errno("calloc"); goto done; } got_hash_final_object_id(&ctx, *id); if (fflush(treefile) != 0) { err = got_error_from_errno("fflush"); goto done; } rewind(treefile); err = create_object_file(*id, treefile, treesize, repo); done: free(header); free(sorted_entries); if (treefile && fclose(treefile) == EOF && err == NULL) err = got_error_from_errno("fclose"); if (err) { free(*id); *id = NULL; } return err; } const struct got_error * got_object_commit_create(struct got_object_id **id, struct got_object_id *tree_id, struct got_object_id_queue *parent_ids, int nparents, const char *author, time_t author_time, const char *committer, time_t committer_time, const char *logmsg, struct got_repository *repo) { const struct got_error *err = NULL; struct got_hash ctx; char *header = NULL, *tree_str = NULL; char *author_str = NULL, *committer_str = NULL; char *id_str = NULL; size_t headerlen, len = 0, n, digest_string_len; FILE *commitfile = NULL; off_t commitsize = 0; struct got_object_qid *qid; enum got_hash_algorithm algo; char *msg0, *msg; *id = NULL; algo = got_repo_get_object_format(repo); digest_string_len = got_hash_digest_string_length(algo); got_hash_init(&ctx, algo); msg0 = strdup(logmsg); if (msg0 == NULL) return got_error_from_errno("strdup"); msg = msg0; while (isspace((unsigned char)msg[0])) msg++; len = strlen(msg); while (len > 0 && isspace((unsigned char)msg[len - 1])) { msg[len - 1] = '\0'; len--; } if (asprintf(&author_str, "%s%s %lld +0000\n", GOT_COMMIT_LABEL_AUTHOR, author, (long long)author_time) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (asprintf(&committer_str, "%s%s %lld +0000\n", GOT_COMMIT_LABEL_COMMITTER, committer ? committer : author, (long long)(committer ? committer_time : author_time)) == -1) { err = got_error_from_errno("asprintf"); goto done; } len = strlen(GOT_COMMIT_LABEL_TREE) + digest_string_len + nparents * (strlen(GOT_COMMIT_LABEL_PARENT) + digest_string_len) + + strlen(author_str) + strlen(committer_str) + 2 + strlen(msg); if (asprintf(&header, "%s %zd", GOT_OBJ_LABEL_COMMIT, len) == -1) { err = got_error_from_errno("asprintf"); goto done; } headerlen = strlen(header) + 1; got_hash_update(&ctx, header, headerlen); commitfile = got_opentemp(); if (commitfile == NULL) { err = got_error_from_errno("got_opentemp"); goto done; } n = fwrite(header, 1, headerlen, commitfile); if (n != headerlen) { err = got_ferror(commitfile, GOT_ERR_IO); goto done; } commitsize += headerlen; err = got_object_id_str(&id_str, tree_id); if (err) goto done; if (asprintf(&tree_str, "%s%s\n", GOT_COMMIT_LABEL_TREE, id_str) == -1) { err = got_error_from_errno("asprintf"); goto done; } len = strlen(tree_str); got_hash_update(&ctx, tree_str, len); n = fwrite(tree_str, 1, len, commitfile); if (n != len) { err = got_ferror(commitfile, GOT_ERR_IO); goto done; } commitsize += n; if (parent_ids) { free(id_str); id_str = NULL; STAILQ_FOREACH(qid, parent_ids, entry) { char *parent_str = NULL; err = got_object_id_str(&id_str, &qid->id); if (err) goto done; if (asprintf(&parent_str, "%s%s\n", GOT_COMMIT_LABEL_PARENT, id_str) == -1) { err = got_error_from_errno("asprintf"); goto done; } len = strlen(parent_str); got_hash_update(&ctx, parent_str, len); n = fwrite(parent_str, 1, len, commitfile); if (n != len) { err = got_ferror(commitfile, GOT_ERR_IO); free(parent_str); goto done; } commitsize += n; free(parent_str); free(id_str); id_str = NULL; } } len = strlen(author_str); got_hash_update(&ctx, author_str, len); n = fwrite(author_str, 1, len, commitfile); if (n != len) { err = got_ferror(commitfile, GOT_ERR_IO); goto done; } commitsize += n; len = strlen(committer_str); got_hash_update(&ctx, committer_str, len); n = fwrite(committer_str, 1, len, commitfile); if (n != len) { err = got_ferror(commitfile, GOT_ERR_IO); goto done; } commitsize += n; got_hash_update(&ctx, "\n", 1); n = fwrite("\n", 1, 1, commitfile); if (n != 1) { err = got_ferror(commitfile, GOT_ERR_IO); goto done; } commitsize += n; len = strlen(msg); got_hash_update(&ctx, msg, len); n = fwrite(msg, 1, len, commitfile); if (n != len) { err = got_ferror(commitfile, GOT_ERR_IO); goto done; } commitsize += n; got_hash_update(&ctx, "\n", 1); n = fwrite("\n", 1, 1, commitfile); if (n != 1) { err = got_ferror(commitfile, GOT_ERR_IO); goto done; } commitsize += n; *id = calloc(1, sizeof(**id)); if (*id == NULL) { err = got_error_from_errno("calloc"); goto done; } got_hash_final_object_id(&ctx, *id); if (fflush(commitfile) != 0) { err = got_error_from_errno("fflush"); goto done; } rewind(commitfile); err = create_object_file(*id, commitfile, commitsize, repo); done: free(id_str); free(msg0); free(header); free(tree_str); free(author_str); free(committer_str); if (commitfile && fclose(commitfile) == EOF && err == NULL) err = got_error_from_errno("fclose"); if (err) { free(*id); *id = NULL; } return err; } const struct got_error * got_object_tag_create(struct got_object_id **id, const char *tag_name, struct got_object_id *object_id, const char *tagger, time_t tagger_time, const char *tagmsg, const char *signer_id, struct got_repository *repo, int verbosity) { const struct got_error *err = NULL; struct got_hash ctx; char *header = NULL; char *tag_str = NULL, *tagger_str = NULL; char *id_str = NULL, *obj_str = NULL, *type_str = NULL; size_t headerlen, len = 0, sig_len = 0, n; FILE *tagfile = NULL; off_t tagsize = 0; char *msg0 = NULL, *msg; const char *obj_type_str; int obj_type; BUF *buf = NULL; *id = NULL; got_hash_init(&ctx, got_repo_get_object_format(repo)); err = got_object_id_str(&id_str, object_id); if (err) goto done; if (asprintf(&obj_str, "%s%s\n", GOT_TAG_LABEL_OBJECT, id_str) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = got_object_get_type(&obj_type, repo, object_id); if (err) goto done; switch (obj_type) { case GOT_OBJ_TYPE_BLOB: obj_type_str = GOT_OBJ_LABEL_BLOB; break; case GOT_OBJ_TYPE_TREE: obj_type_str = GOT_OBJ_LABEL_TREE; break; case GOT_OBJ_TYPE_COMMIT: obj_type_str = GOT_OBJ_LABEL_COMMIT; break; case GOT_OBJ_TYPE_TAG: obj_type_str = GOT_OBJ_LABEL_TAG; break; default: err = got_error(GOT_ERR_OBJ_TYPE); goto done; } if (asprintf(&type_str, "%s%s\n", GOT_TAG_LABEL_TYPE, obj_type_str) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (asprintf(&tag_str, "%s%s\n", GOT_TAG_LABEL_TAG, tag_name) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (asprintf(&tagger_str, "%s%s %lld +0000\n", GOT_TAG_LABEL_TAGGER, tagger, (long long)tagger_time) == -1) return got_error_from_errno("asprintf"); msg0 = strdup(tagmsg); if (msg0 == NULL) { err = got_error_from_errno("strdup"); goto done; } msg = msg0; while (isspace((unsigned char)msg[0])) msg++; if (signer_id) { pid_t pid; size_t len; int in_fd, out_fd; int status; err = buf_alloc(&buf, 0); if (err) goto done; /* signed message */ err = buf_puts(&len, buf, obj_str); if (err) goto done; err = buf_puts(&len, buf, type_str); if (err) goto done; err = buf_puts(&len, buf, tag_str); if (err) goto done; err = buf_puts(&len, buf, tagger_str); if (err) goto done; err = buf_putc(buf, '\n'); if (err) goto done; err = buf_puts(&len, buf, msg); if (err) goto done; err = buf_putc(buf, '\n'); if (err) goto done; err = got_sigs_sign_tag_ssh(&pid, &in_fd, &out_fd, signer_id, verbosity); if (err) goto done; if (buf_write_fd(buf, in_fd) == -1) { err = got_error_from_errno("write"); goto done; } if (close(in_fd) == -1) { err = got_error_from_errno("close"); goto done; } if (waitpid(pid, &status, 0) == -1) { err = got_error_from_errno("waitpid"); goto done; } if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { err = got_error(GOT_ERR_SIGNING_TAG); goto done; } buf_empty(buf); err = buf_load_fd(&buf, out_fd); if (err) goto done; sig_len = buf_len(buf); if (close(out_fd) == -1) { err = got_error_from_errno("close"); goto done; } } len = strlen(obj_str) + strlen(type_str) + strlen(tag_str) + strlen(tagger_str) + 1 + strlen(msg) + 1 + sig_len; if (asprintf(&header, "%s %zd", GOT_OBJ_LABEL_TAG, len) == -1) { err = got_error_from_errno("asprintf"); goto done; } headerlen = strlen(header) + 1; got_hash_update(&ctx, header, headerlen); tagfile = got_opentemp(); if (tagfile == NULL) { err = got_error_from_errno("got_opentemp"); goto done; } n = fwrite(header, 1, headerlen, tagfile); if (n != headerlen) { err = got_ferror(tagfile, GOT_ERR_IO); goto done; } tagsize += headerlen; len = strlen(obj_str); got_hash_update(&ctx, obj_str, len); n = fwrite(obj_str, 1, len, tagfile); if (n != len) { err = got_ferror(tagfile, GOT_ERR_IO); goto done; } tagsize += n; len = strlen(type_str); got_hash_update(&ctx, type_str, len); n = fwrite(type_str, 1, len, tagfile); if (n != len) { err = got_ferror(tagfile, GOT_ERR_IO); goto done; } tagsize += n; len = strlen(tag_str); got_hash_update(&ctx, tag_str, len); n = fwrite(tag_str, 1, len, tagfile); if (n != len) { err = got_ferror(tagfile, GOT_ERR_IO); goto done; } tagsize += n; len = strlen(tagger_str); got_hash_update(&ctx, tagger_str, len); n = fwrite(tagger_str, 1, len, tagfile); if (n != len) { err = got_ferror(tagfile, GOT_ERR_IO); goto done; } tagsize += n; got_hash_update(&ctx, "\n", 1); n = fwrite("\n", 1, 1, tagfile); if (n != 1) { err = got_ferror(tagfile, GOT_ERR_IO); goto done; } tagsize += n; len = strlen(msg); got_hash_update(&ctx, msg, len); n = fwrite(msg, 1, len, tagfile); if (n != len) { err = got_ferror(tagfile, GOT_ERR_IO); goto done; } tagsize += n; got_hash_update(&ctx, "\n", 1); n = fwrite("\n", 1, 1, tagfile); if (n != 1) { err = got_ferror(tagfile, GOT_ERR_IO); goto done; } tagsize += n; if (signer_id && buf_len(buf) > 0) { len = buf_len(buf); got_hash_update(&ctx, buf_get(buf), len); n = fwrite(buf_get(buf), 1, len, tagfile); if (n != len) { err = got_ferror(tagfile, GOT_ERR_IO); goto done; } tagsize += n; } *id = calloc(1, sizeof(**id)); if (*id == NULL) { err = got_error_from_errno("calloc"); goto done; } got_hash_final_object_id(&ctx, *id); if (fflush(tagfile) != 0) { err = got_error_from_errno("fflush"); goto done; } rewind(tagfile); err = create_object_file(*id, tagfile, tagsize, repo); done: free(msg0); free(header); free(obj_str); free(tagger_str); if (buf) buf_release(buf); if (tagfile && fclose(tagfile) == EOF && err == NULL) err = got_error_from_errno("fclose"); if (err) { free(*id); *id = NULL; } return err; } got-portable-0.111/lib/got_lib_diff.h0000644000175000017500000000450315001740614013122 /* * Copyright (c) 2020 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "arraylist.h" #include "diff_main.h" #include "diff_output.h" enum got_diff_output_format { GOT_DIFF_OUTPUT_UNIDIFF, GOT_DIFF_OUTPUT_PLAIN, }; struct got_diffreg_result { struct diff_result *result; char *map1; size_t size1; char *map2; size_t size2; struct diff_data left; struct diff_data right; }; #define GOT_DIFF_CONFLICT_MARKER_BEGIN "<<<<<<<" #define GOT_DIFF_CONFLICT_MARKER_ORIG "|||||||" #define GOT_DIFF_CONFLICT_MARKER_SEP "=======" #define GOT_DIFF_CONFLICT_MARKER_END ">>>>>>>" const struct got_error *got_diff_get_config(struct diff_config **, enum got_diff_algorithm, diff_atomize_func_t, void *); const struct got_error *got_diff_prepare_file(FILE *, char **, size_t *, struct diff_data *, const struct diff_config *, int, int); const struct got_error *got_diffreg(struct got_diffreg_result **, FILE *, FILE *, enum got_diff_algorithm, int, int); const struct got_error *got_diffreg_output(struct got_diff_line **, size_t *, struct got_diffreg_result *, int, int, const char *, const char *, enum got_diff_output_format, int, FILE *); const struct got_error *got_diffreg_result_free(struct got_diffreg_result *); const struct got_error *got_diffreg_close(char *, size_t, char *, size_t); const struct got_error *got_merge_diff3(int *, int, FILE *, FILE *, FILE *, const char *, const char *, const char *, enum got_diff_algorithm); const struct got_error *got_diff_files(struct got_diffreg_result **, FILE *, int, const char *, FILE *, int, const char *, int, int, int, FILE *, enum got_diff_algorithm); got-portable-0.111/lib/pack.c0000644000175000017500000015256715001741021011433 /* * Copyright (c) 2018, 2019, 2020 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_object.h" #include "got_path.h" #include "got_lib_hash.h" #include "got_lib_delta.h" #include "got_lib_delta_cache.h" #include "got_lib_inflate.h" #include "got_lib_object.h" #include "got_lib_object_qid.h" #include "got_lib_object_parse.h" #include "got_lib_privsep.h" #include "got_lib_pack.h" #ifndef nitems #define nitems(_a) (sizeof(_a) / sizeof((_a)[0])) #endif #ifndef MIN #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b)) #endif static const struct got_error * verify_fanout_table(uint32_t *fanout_table) { int i; for (i = 0; i < 0xff - 1; i++) { if (be32toh(fanout_table[i]) > be32toh(fanout_table[i + 1])) return got_error(GOT_ERR_BAD_PACKIDX); } return NULL; } const struct got_error * got_packidx_init_hdr(struct got_packidx *p, int verify, off_t packfile_size) { const struct got_error *err = NULL; struct got_packidx_v2_hdr *h; struct got_hash ctx; uint8_t hash[GOT_HASH_DIGEST_MAXLEN]; size_t nobj, len_fanout, len_ids, offset, remain, digest_string_len; ssize_t n; int i; got_hash_init(&ctx, p->algo); digest_string_len = got_hash_digest_length(p->algo); h = &p->hdr; offset = 0; remain = p->len; if (remain < sizeof(*h->magic)) { err = got_error(GOT_ERR_BAD_PACKIDX); goto done; } if (p->map) h->magic = (uint32_t *)(p->map + offset); else { h->magic = malloc(sizeof(*h->magic)); if (h->magic == NULL) { err = got_error_from_errno("malloc"); goto done; } n = read(p->fd, h->magic, sizeof(*h->magic)); if (n < 0) { err = got_error_from_errno("read"); goto done; } else if (n != sizeof(*h->magic)) { err = got_error(GOT_ERR_BAD_PACKIDX); goto done; } } if (*h->magic != htobe32(GOT_PACKIDX_V2_MAGIC)) { err = got_error(GOT_ERR_BAD_PACKIDX); goto done; } offset += sizeof(*h->magic); remain -= sizeof(*h->magic); if (verify) got_hash_update(&ctx, h->magic, sizeof(*h->magic)); if (remain < sizeof(*h->version)) { err = got_error(GOT_ERR_BAD_PACKIDX); goto done; } if (p->map) h->version = (uint32_t *)(p->map + offset); else { h->version = malloc(sizeof(*h->version)); if (h->version == NULL) { err = got_error_from_errno("malloc"); goto done; } n = read(p->fd, h->version, sizeof(*h->version)); if (n < 0) { err = got_error_from_errno("read"); goto done; } else if (n != sizeof(*h->version)) { err = got_error(GOT_ERR_BAD_PACKIDX); goto done; } } if (*h->version != htobe32(GOT_PACKIDX_VERSION)) { err = got_error(GOT_ERR_BAD_PACKIDX); goto done; } offset += sizeof(*h->version); remain -= sizeof(*h->version); if (verify) got_hash_update(&ctx, h->version, sizeof(*h->version)); len_fanout = sizeof(*h->fanout_table) * GOT_PACKIDX_V2_FANOUT_TABLE_ITEMS; if (remain < len_fanout) { err = got_error(GOT_ERR_BAD_PACKIDX); goto done; } if (p->map) h->fanout_table = (uint32_t *)(p->map + offset); else { h->fanout_table = malloc(len_fanout); if (h->fanout_table == NULL) { err = got_error_from_errno("malloc"); goto done; } n = read(p->fd, h->fanout_table, len_fanout); if (n < 0) { err = got_error_from_errno("read"); goto done; } else if (n != len_fanout) { err = got_error(GOT_ERR_BAD_PACKIDX); goto done; } } err = verify_fanout_table(h->fanout_table); if (err) goto done; if (verify) got_hash_update(&ctx, h->fanout_table, len_fanout); offset += len_fanout; remain -= len_fanout; nobj = be32toh(h->fanout_table[0xff]); len_ids = nobj * got_hash_digest_length(p->algo); if (len_ids <= nobj || len_ids > remain) { err = got_error(GOT_ERR_BAD_PACKIDX); goto done; } if (p->map) h->sorted_ids = p->map + offset; else { h->sorted_ids = malloc(len_ids); if (h->sorted_ids == NULL) { err = got_error(GOT_ERR_BAD_PACKIDX); goto done; } n = read(p->fd, h->sorted_ids, len_ids); if (n < 0) err = got_error_from_errno("read"); else if (n != len_ids) { err = got_error(GOT_ERR_BAD_PACKIDX); goto done; } } if (verify) got_hash_update(&ctx, h->sorted_ids, len_ids); offset += len_ids; remain -= len_ids; if (remain < nobj * sizeof(*h->crc32)) { err = got_error(GOT_ERR_BAD_PACKIDX); goto done; } if (p->map) h->crc32 = (uint32_t *)((uint8_t*)(p->map + offset)); else { h->crc32 = malloc(nobj * sizeof(*h->crc32)); if (h->crc32 == NULL) { err = got_error_from_errno("malloc"); goto done; } n = read(p->fd, h->crc32, nobj * sizeof(*h->crc32)); if (n < 0) err = got_error_from_errno("read"); else if (n != nobj * sizeof(*h->crc32)) { err = got_error(GOT_ERR_BAD_PACKIDX); goto done; } } if (verify) got_hash_update(&ctx, h->crc32, nobj * sizeof(*h->crc32)); remain -= nobj * sizeof(*h->crc32); offset += nobj * sizeof(*h->crc32); if (remain < nobj * sizeof(*h->offsets)) { err = got_error(GOT_ERR_BAD_PACKIDX); goto done; } if (p->map) h->offsets = (uint32_t *)((uint8_t*)(p->map + offset)); else { h->offsets = malloc(nobj * sizeof(*h->offsets)); if (h->offsets == NULL) { err = got_error_from_errno("malloc"); goto done; } n = read(p->fd, h->offsets, nobj * sizeof(*h->offsets)); if (n < 0) err = got_error_from_errno("read"); else if (n != nobj * sizeof(*h->offsets)) { err = got_error(GOT_ERR_BAD_PACKIDX); goto done; } } if (verify) got_hash_update(&ctx, h->offsets, nobj * sizeof(*h->offsets)); remain -= nobj * sizeof(*h->offsets); offset += nobj * sizeof(*h->offsets); /* Large file offsets are contained only in files > 2GB. */ if (verify || packfile_size > 0x7fffffff) { for (i = 0; i < nobj; i++) { uint32_t o = h->offsets[i]; if (o & htobe32(GOT_PACKIDX_OFFSET_VAL_IS_LARGE_IDX)) p->nlargeobj++; } } if (p->nlargeobj == 0) goto checksum; else if (packfile_size <= 0x7fffffff) { err = got_error(GOT_ERR_BAD_PACKIDX); goto done; } if (remain < p->nlargeobj * sizeof(*h->large_offsets)) { err = got_error(GOT_ERR_BAD_PACKIDX); goto done; } if (p->map) h->large_offsets = (uint64_t *)((uint8_t*)(p->map + offset)); else { h->large_offsets = malloc(p->nlargeobj * sizeof(*h->large_offsets)); if (h->large_offsets == NULL) { err = got_error_from_errno("malloc"); goto done; } n = read(p->fd, h->large_offsets, p->nlargeobj * sizeof(*h->large_offsets)); if (n < 0) err = got_error_from_errno("read"); else if (n != p->nlargeobj * sizeof(*h->large_offsets)) { err = got_error(GOT_ERR_BAD_PACKIDX); goto done; } } if (verify) got_hash_update(&ctx, h->large_offsets, p->nlargeobj * sizeof(*h->large_offsets)); remain -= p->nlargeobj * sizeof(*h->large_offsets); offset += p->nlargeobj * sizeof(*h->large_offsets); checksum: if (remain < digest_string_len * 2) { err = got_error(GOT_ERR_BAD_PACKIDX); goto done; } if (p->map) { memcpy(h->trailer.packfile_hash, p->map + offset, digest_string_len); memcpy(h->trailer.packidx_hash, p->map + offset + digest_string_len, digest_string_len); } else { n = read(p->fd, h->trailer.packfile_hash, digest_string_len); if (n < 0) err = got_error_from_errno("read"); else if (n != digest_string_len) { err = got_error(GOT_ERR_BAD_PACKIDX); goto done; } n = read(p->fd, h->trailer.packidx_hash, digest_string_len); if (n < 0) err = got_error_from_errno("read"); else if (n != digest_string_len) { err = got_error(GOT_ERR_BAD_PACKIDX); goto done; } } if (verify) { got_hash_update(&ctx, h->trailer.packfile_hash, digest_string_len); got_hash_final(&ctx, hash); if (got_hash_cmp(ctx.algo, hash, h->trailer.packidx_hash) != 0) err = got_error(GOT_ERR_PACKIDX_CSUM); } done: return err; } const struct got_error * got_packidx_open(struct got_packidx **packidx, int dir_fd, const char *relpath, int verify, enum got_hash_algorithm algo) { const struct got_error *err = NULL; struct got_packidx *p = NULL; char *pack_relpath; struct stat idx_sb, pack_sb; *packidx = NULL; err = got_packidx_get_packfile_path(&pack_relpath, relpath); if (err) return err; /* * Ensure that a corresponding pack file exists. * Some Git repositories have this problem. Git seems to ignore * the existence of lonely pack index files but we do not. */ if (fstatat(dir_fd, pack_relpath, &pack_sb, 0) == -1) { if (errno == ENOENT) err = got_error_path(relpath, GOT_ERR_LONELY_PACKIDX); else err = got_error_from_errno2("fstatat", pack_relpath); goto done; } p = calloc(1, sizeof(*p)); if (p == NULL) { err = got_error_from_errno("calloc"); goto done; } p->algo = algo; p->fd = openat(dir_fd, relpath, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (p->fd == -1) { err = got_error_from_errno2("openat", relpath); goto done; } if (fstat(p->fd, &idx_sb) != 0) { err = got_error_from_errno2("fstat", relpath); goto done; } p->len = idx_sb.st_size; if (p->len < sizeof(p->hdr)) { err = got_error(GOT_ERR_BAD_PACKIDX); goto done; } p->path_packidx = strdup(relpath); if (p->path_packidx == NULL) { err = got_error_from_errno("strdup"); goto done; } #ifndef GOT_PACK_NO_MMAP if (p->len > 0 && p->len <= SIZE_MAX) { p->map = mmap(NULL, p->len, PROT_READ, MAP_PRIVATE, p->fd, 0); if (p->map == MAP_FAILED) { if (errno != ENOMEM) { err = got_error_from_errno("mmap"); goto done; } p->map = NULL; /* fall back to read(2) */ } } #endif err = got_packidx_init_hdr(p, verify, pack_sb.st_size); done: if (err) { if (p) got_packidx_close(p); } else *packidx = p; free(pack_relpath); return err; } const struct got_error * got_packidx_close(struct got_packidx *packidx) { const struct got_error *err = NULL; free(packidx->path_packidx); if (packidx->map) { if (munmap(packidx->map, packidx->len) == -1) err = got_error_from_errno("munmap"); } else { free(packidx->hdr.magic); free(packidx->hdr.version); free(packidx->hdr.fanout_table); free(packidx->hdr.sorted_ids); free(packidx->hdr.crc32); free(packidx->hdr.offsets); free(packidx->hdr.large_offsets); } if (close(packidx->fd) == -1 && err == NULL) err = got_error_from_errno("close"); free(packidx->sorted_offsets); free(packidx->sorted_large_offsets); free(packidx); return err; } const struct got_error * got_packidx_get_packfile_path(char **path_packfile, const char *path_packidx) { size_t size; /* Packfile path contains ".pack" instead of ".idx", so add one byte. */ size = strlen(path_packidx) + 2; if (size < GOT_PACKFILE_NAMELEN + 1) return got_error_path(path_packidx, GOT_ERR_BAD_PATH); *path_packfile = malloc(size); if (*path_packfile == NULL) return got_error_from_errno("malloc"); /* Copy up to and excluding ".idx". */ if (strlcpy(*path_packfile, path_packidx, size - strlen(GOT_PACKIDX_SUFFIX) - 1) >= size) return got_error(GOT_ERR_NO_SPACE); if (strlcat(*path_packfile, GOT_PACKFILE_SUFFIX, size) >= size) return got_error(GOT_ERR_NO_SPACE); return NULL; } off_t got_packidx_get_object_offset(struct got_packidx *packidx, int idx) { uint32_t offset = be32toh(packidx->hdr.offsets[idx]); if (offset & GOT_PACKIDX_OFFSET_VAL_IS_LARGE_IDX) { uint64_t loffset; idx = offset & GOT_PACKIDX_OFFSET_VAL_MASK; if (idx < 0 || idx >= packidx->nlargeobj || packidx->hdr.large_offsets == NULL) return -1; loffset = be64toh(packidx->hdr.large_offsets[idx]); return (loffset > INT64_MAX ? -1 : (off_t)loffset); } return (off_t)(offset & GOT_PACKIDX_OFFSET_VAL_MASK); } int got_packidx_get_object_idx(struct got_packidx *packidx, struct got_object_id *id) { u_int8_t id0 = id->hash[0]; uint32_t totobj = be32toh(packidx->hdr.fanout_table[0xff]); int left = 0, right = totobj - 1; size_t digest_len = got_hash_digest_length(packidx->algo); if (id0 > 0) left = be32toh(packidx->hdr.fanout_table[id0 - 1]); while (left <= right) { uint8_t *oid; int i, cmp; i = ((left + right) / 2); oid = packidx->hdr.sorted_ids + i * digest_len; cmp = memcmp(id->hash, oid, digest_len); if (cmp == 0) return i; else if (cmp > 0) left = i + 1; else if (cmp < 0) right = i - 1; } return -1; } static int offset_cmp(const void *pa, const void *pb) { const struct got_pack_offset_index *a, *b; a = (const struct got_pack_offset_index *)pa; b = (const struct got_pack_offset_index *)pb; if (a->offset < b->offset) return -1; else if (a->offset > b->offset) return 1; return 0; } static int large_offset_cmp(const void *pa, const void *pb) { const struct got_pack_large_offset_index *a, *b; a = (const struct got_pack_large_offset_index *)pa; b = (const struct got_pack_large_offset_index *)pb; if (a->offset < b->offset) return -1; else if (a->offset > b->offset) return 1; return 0; } static const struct got_error * build_offset_index(struct got_packidx *p) { uint32_t nobj = be32toh(p->hdr.fanout_table[0xff]); unsigned int i, j, k; p->sorted_offsets = calloc(nobj - p->nlargeobj, sizeof(p->sorted_offsets[0])); if (p->sorted_offsets == NULL) return got_error_from_errno("calloc"); if (p->nlargeobj > 0) { p->sorted_large_offsets = calloc(p->nlargeobj, sizeof(p->sorted_large_offsets[0])); if (p->sorted_large_offsets == NULL) return got_error_from_errno("calloc"); } j = 0; k = 0; for (i = 0; i < nobj; i++) { uint32_t offset = be32toh(p->hdr.offsets[i]); if (offset & GOT_PACKIDX_OFFSET_VAL_IS_LARGE_IDX) { uint64_t loffset; uint32_t idx; idx = offset & GOT_PACKIDX_OFFSET_VAL_MASK; if (idx >= p->nlargeobj || p->nlargeobj == 0 || p->hdr.large_offsets == NULL) return got_error(GOT_ERR_BAD_PACKIDX); loffset = be64toh(p->hdr.large_offsets[idx]); p->sorted_large_offsets[j].offset = loffset; p->sorted_large_offsets[j].idx = i; j++; } else { p->sorted_offsets[k].offset = offset; p->sorted_offsets[k].idx = i; k++; } } if (j != p->nlargeobj || k != nobj - p->nlargeobj) return got_error(GOT_ERR_BAD_PACKIDX); qsort(p->sorted_offsets, nobj - p->nlargeobj, sizeof(p->sorted_offsets[0]), offset_cmp); if (p->sorted_large_offsets) qsort(p->sorted_large_offsets, p->nlargeobj, sizeof(p->sorted_large_offsets[0]), large_offset_cmp); return NULL; } const struct got_error * got_packidx_get_offset_idx(int *idx, struct got_packidx *packidx, off_t offset) { const struct got_error *err; uint32_t totobj = be32toh(packidx->hdr.fanout_table[0xff]); int i, left, right; *idx = -1; if (packidx->sorted_offsets == NULL) { err = build_offset_index(packidx); if (err) return err; } if (offset >= 0x7fffffff) { uint64_t lo; left = 0, right = packidx->nlargeobj - 1; while (left <= right) { i = ((left + right) / 2); lo = packidx->sorted_large_offsets[i].offset; if (lo == offset) { *idx = packidx->sorted_large_offsets[i].idx; break; } else if (offset > lo) left = i + 1; else if (offset < lo) right = i - 1; } } else { uint32_t o; left = 0, right = totobj - packidx->nlargeobj - 1; while (left <= right) { i = ((left + right) / 2); o = packidx->sorted_offsets[i].offset; if (o == offset) { *idx = packidx->sorted_offsets[i].idx; break; } else if (offset > o) left = i + 1; else if (offset < o) right = i - 1; } } return NULL; } const struct got_error * got_packidx_get_object_id(struct got_object_id *id, struct got_packidx *packidx, int idx) { uint32_t totobj = be32toh(packidx->hdr.fanout_table[0xff]); uint8_t *oid; size_t digest_len = got_hash_digest_length(packidx->algo); if (idx < 0 || idx >= totobj) return got_error(GOT_ERR_NO_OBJ); oid = packidx->hdr.sorted_ids + idx * digest_len; memcpy(id->hash, oid, digest_len); id->algo = packidx->algo; return NULL; } const struct got_error * got_packidx_match_id_str_prefix(struct got_object_id_queue *matched_ids, struct got_packidx *packidx, const char *id_str_prefix) { const struct got_error *err = NULL; u_int8_t id0; uint32_t totobj = be32toh(packidx->hdr.fanout_table[0xff]); char hex[3]; size_t prefix_len = strlen(id_str_prefix); uint8_t *oid; uint32_t i = 0; size_t digest_len = got_hash_digest_length(packidx->algo); if (prefix_len < 2) return got_error_path(id_str_prefix, GOT_ERR_BAD_OBJ_ID_STR); hex[0] = id_str_prefix[0]; hex[1] = id_str_prefix[1]; hex[2] = '\0'; if (!got_parse_xdigit(&id0, hex)) return got_error_path(id_str_prefix, GOT_ERR_BAD_OBJ_ID_STR); if (id0 > 0) i = be32toh(packidx->hdr.fanout_table[id0 - 1]); oid = packidx->hdr.sorted_ids + i * digest_len; while (i < totobj && oid[0] == id0) { char id_str[GOT_HASH_DIGEST_STRING_MAXLEN]; struct got_object_qid *qid; int cmp; if (!got_hash_digest_to_str(oid, id_str, sizeof(id_str), packidx->algo)) return got_error(GOT_ERR_NO_SPACE); cmp = strncmp(id_str, id_str_prefix, prefix_len); if (cmp < 0) { oid = packidx->hdr.sorted_ids + (++i) * digest_len; continue; } else if (cmp > 0) break; err = got_object_qid_alloc_partial(&qid); if (err) return err; memcpy(qid->id.hash, oid, digest_len); qid->id.algo = packidx->algo; STAILQ_INSERT_TAIL(matched_ids, qid, entry); oid = packidx->hdr.sorted_ids + (++i) * digest_len; } return NULL; } static void set_max_datasize(void) { struct rlimit rl; if (getrlimit(RLIMIT_DATA, &rl) != 0) return; rl.rlim_cur = rl.rlim_max; setrlimit(RLIMIT_DATA, &rl); } const struct got_error * got_pack_start_privsep_child(struct got_pack *pack, struct got_packidx *packidx) { const struct got_error *err = NULL; int imsg_fds[2]; pid_t pid; struct imsgbuf *ibuf; ibuf = calloc(1, sizeof(*ibuf)); if (ibuf == NULL) return got_error_from_errno("calloc"); pack->privsep_child = calloc(1, sizeof(*pack->privsep_child)); if (pack->privsep_child == NULL) { err = got_error_from_errno("calloc"); free(ibuf); return err; } pack->child_has_tempfiles = 0; pack->child_has_delta_outfd = 0; if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1) { err = got_error_from_errno("socketpair"); goto done; } pid = fork(); if (pid == -1) { err = got_error_from_errno("fork"); close(imsg_fds[0]); close(imsg_fds[1]); goto done; } else if (pid == 0) { set_max_datasize(); got_privsep_exec_child(imsg_fds, GOT_PATH_PROG_READ_PACK, pack->path_packfile); /* not reached */ } if (close(imsg_fds[1]) == -1) { err = got_error_from_errno("close"); close(imsg_fds[0]); goto done; } pack->privsep_child->imsg_fd = imsg_fds[0]; pack->privsep_child->pid = pid; if (imsgbuf_init(ibuf, imsg_fds[0]) == -1) { err = got_error_from_errno("imsgbuf_init"); close(imsg_fds[0]); goto done; } imsgbuf_allow_fdpass(ibuf); pack->privsep_child->ibuf = ibuf; err = got_privsep_init_pack_child(ibuf, pack, packidx); if (err) { const struct got_error *child_err; err = got_privsep_send_stop(pack->privsep_child->imsg_fd); child_err = got_privsep_wait_for_child( pack->privsep_child->pid); if (child_err && err == NULL) err = child_err; } done: if (err) { imsgbuf_clear(ibuf); free(ibuf); free(pack->privsep_child); pack->privsep_child = NULL; } return err; } static const struct got_error * pack_stop_privsep_child(struct got_pack *pack) { const struct got_error *err = NULL; const struct got_error *close_err = NULL, *child_err = NULL; if (pack->privsep_child == NULL) return NULL; err = got_privsep_send_stop(pack->privsep_child->imsg_fd); if (close(pack->privsep_child->imsg_fd) == -1) close_err = got_error_from_errno("close"); if (close_err && err == NULL) err = close_err; child_err = got_privsep_wait_for_child(pack->privsep_child->pid); if (child_err && err == NULL) err = child_err; imsgbuf_clear(pack->privsep_child->ibuf); free(pack->privsep_child->ibuf); free(pack->privsep_child); pack->privsep_child = NULL; return err; } const struct got_error * got_pack_close(struct got_pack *pack) { const struct got_error *err = NULL; err = pack_stop_privsep_child(pack); if (pack->map && munmap(pack->map, pack->filesize) == -1 && !err) err = got_error_from_errno("munmap"); if (pack->fd != -1 && close(pack->fd) == -1 && err == NULL) err = got_error_from_errno("close"); pack->fd = -1; free(pack->path_packfile); pack->path_packfile = NULL; pack->filesize = 0; if (pack->delta_cache) { got_delta_cache_free(pack->delta_cache); pack->delta_cache = NULL; } /* * Leave accumfd and basefd alone. They are managed by the * repository layer and can be reused. */ return err; } const struct got_error * got_pack_parse_object_type_and_size(uint8_t *type, uint64_t *size, size_t *len, struct got_pack *pack, off_t offset) { uint8_t t = 0; uint64_t s = 0; uint8_t sizeN; size_t mapoff = 0; int i = 0; *len = 0; if (offset >= pack->filesize) return got_error(GOT_ERR_PACK_OFFSET); if (pack->map) { if (offset > SIZE_MAX) { return got_error_fmt(GOT_ERR_PACK_OFFSET, "offset %lld overflows size_t", (long long)offset); } mapoff = (size_t)offset; } else { if (lseek(pack->fd, offset, SEEK_SET) == -1) return got_error_from_errno("lseek"); } do { /* We do not support size values which don't fit in 64 bit. */ if (i > 9) return got_error_fmt(GOT_ERR_OBJ_TOO_LARGE, "packfile offset %lld", (long long)offset); if (pack->map) { if (mapoff + sizeof(sizeN) >= pack->filesize) return got_error(GOT_ERR_BAD_PACKFILE); sizeN = *(pack->map + mapoff); mapoff += sizeof(sizeN); } else { ssize_t n = read(pack->fd, &sizeN, sizeof(sizeN)); if (n < 0) return got_error_from_errno("read"); if (n != sizeof(sizeN)) return got_error(GOT_ERR_BAD_PACKFILE); } *len += sizeof(sizeN); if (i == 0) { t = (sizeN & GOT_PACK_OBJ_SIZE0_TYPE_MASK) >> GOT_PACK_OBJ_SIZE0_TYPE_MASK_SHIFT; s = (sizeN & GOT_PACK_OBJ_SIZE0_VAL_MASK); } else { size_t shift = 4 + 7 * (i - 1); s |= ((sizeN & GOT_PACK_OBJ_SIZE_VAL_MASK) << shift); } i++; } while (sizeN & GOT_PACK_OBJ_SIZE_MORE); *type = t; *size = s; return NULL; } static const struct got_error * open_plain_object(struct got_object **obj, struct got_object_id *id, uint8_t type, off_t offset, size_t size, int idx) { *obj = calloc(1, sizeof(**obj)); if (*obj == NULL) return got_error_from_errno("calloc"); (*obj)->type = type; (*obj)->flags = GOT_OBJ_FLAG_PACKED; (*obj)->pack_idx = idx; (*obj)->hdrlen = 0; (*obj)->size = size; memcpy(&(*obj)->id, id, sizeof((*obj)->id)); (*obj)->pack_offset = offset; return NULL; } static const struct got_error * parse_negative_offset(int64_t *offset, size_t *len, struct got_pack *pack, off_t delta_offset) { int64_t o = 0; uint8_t offN; int i = 0; *offset = 0; *len = 0; do { /* We do not support offset values which don't fit in 64 bit. */ if (i > 8) return got_error(GOT_ERR_NO_SPACE); if (pack->map) { size_t mapoff; if (delta_offset > SIZE_MAX - *len) { return got_error_fmt(GOT_ERR_PACK_OFFSET, "mapoff %lld would overflow size_t", (long long)delta_offset + *len); } mapoff = (size_t)delta_offset + *len; if (mapoff + sizeof(offN) >= pack->filesize) return got_error(GOT_ERR_PACK_OFFSET); offN = *(pack->map + mapoff); } else { ssize_t n; n = read(pack->fd, &offN, sizeof(offN)); if (n < 0) return got_error_from_errno("read"); if (n != sizeof(offN)) return got_error(GOT_ERR_BAD_PACKFILE); } *len += sizeof(offN); if (i == 0) o = (offN & GOT_PACK_OBJ_DELTA_OFF_VAL_MASK); else { o++; o <<= 7; o += (offN & GOT_PACK_OBJ_DELTA_OFF_VAL_MASK); } i++; } while (offN & GOT_PACK_OBJ_DELTA_OFF_MORE); *offset = o; return NULL; } const struct got_error * got_pack_parse_offset_delta(off_t *base_offset, size_t *len, struct got_pack *pack, off_t offset, size_t tslen) { const struct got_error *err; int64_t negoffset; size_t negofflen; *len = 0; err = parse_negative_offset(&negoffset, &negofflen, pack, offset + tslen); if (err) return err; /* Compute the base object's offset (must be in the same pack file). */ *base_offset = (offset - negoffset); if (*base_offset <= 0) return got_error(GOT_ERR_BAD_PACKFILE); *len = negofflen; return NULL; } static const struct got_error * read_delta_data(uint8_t **delta_buf, size_t *delta_len, size_t *delta_compressed_len, size_t delta_data_offset, struct got_pack *pack) { const struct got_error *err = NULL; size_t consumed = 0; if (pack->map) { if (delta_data_offset >= pack->filesize) return got_error(GOT_ERR_PACK_OFFSET); err = got_inflate_to_mem_mmap(delta_buf, delta_len, &consumed, NULL, pack->map, delta_data_offset, pack->filesize - delta_data_offset); if (err) return err; } else { if (lseek(pack->fd, delta_data_offset, SEEK_SET) == -1) return got_error_from_errno("lseek"); err = got_inflate_to_mem_fd(delta_buf, delta_len, &consumed, NULL, 0, pack->fd); if (err) return err; } if (delta_compressed_len) *delta_compressed_len = consumed; return NULL; } static const struct got_error * add_delta(struct got_delta_chain *deltas, off_t delta_offset, size_t tslen, int delta_type, size_t delta_size, off_t delta_data_offset) { struct got_delta *delta; delta = got_delta_open(delta_offset, tslen, delta_type, delta_size, delta_data_offset); if (delta == NULL) return got_error_from_errno("got_delta_open"); /* delta is freed in got_object_close() */ deltas->nentries++; STAILQ_INSERT_HEAD(&deltas->entries, delta, entry); return NULL; } static const struct got_error * resolve_offset_delta(struct got_delta_chain *deltas, struct got_packidx *packidx, struct got_pack *pack, off_t delta_offset, size_t tslen, int delta_type, size_t delta_size, unsigned int recursion) { const struct got_error *err; off_t base_offset; uint8_t base_type; uint64_t base_size; size_t base_tslen; off_t delta_data_offset; size_t consumed; err = got_pack_parse_offset_delta(&base_offset, &consumed, pack, delta_offset, tslen); if (err) return err; delta_data_offset = delta_offset + tslen + consumed; if (delta_data_offset >= pack->filesize) return got_error(GOT_ERR_PACK_OFFSET); if (pack->map == NULL) { delta_data_offset = lseek(pack->fd, 0, SEEK_CUR); if (delta_data_offset == -1) return got_error_from_errno("lseek"); } err = add_delta(deltas, delta_offset, tslen, delta_type, delta_size, delta_data_offset); if (err) return err; /* An offset delta must be in the same packfile. */ if (base_offset >= pack->filesize) return got_error(GOT_ERR_PACK_OFFSET); err = got_pack_parse_object_type_and_size(&base_type, &base_size, &base_tslen, pack, base_offset); if (err) return err; return got_pack_resolve_delta_chain(deltas, packidx, pack, base_offset, base_tslen, base_type, base_size, recursion - 1); } const struct got_error * got_pack_parse_ref_delta(struct got_object_id *id, struct got_pack *pack, off_t delta_offset, int tslen) { size_t digest_len = got_hash_digest_length(pack->algo); memset(id, 0, sizeof(*id)); id->algo = pack->algo; if (pack->map) { size_t mapoff; if (delta_offset > SIZE_MAX - tslen) { return got_error_fmt(GOT_ERR_PACK_OFFSET, "mapoff %lld would overflow size_t", (long long)delta_offset + tslen); } mapoff = delta_offset + tslen; if (mapoff + sizeof(*id) >= pack->filesize) return got_error(GOT_ERR_PACK_OFFSET); memcpy(id->hash, pack->map + mapoff, digest_len); } else { ssize_t n; n = read(pack->fd, id->hash, digest_len); if (n < 0) return got_error_from_errno("read"); if (n != digest_len) return got_error(GOT_ERR_BAD_PACKFILE); } return NULL; } static const struct got_error * resolve_ref_delta(struct got_delta_chain *deltas, struct got_packidx *packidx, struct got_pack *pack, off_t delta_offset, size_t tslen, int delta_type, size_t delta_size, unsigned int recursion) { const struct got_error *err; struct got_object_id id; int idx; off_t base_offset; uint8_t base_type; uint64_t base_size; size_t base_tslen; off_t delta_data_offset; if (delta_offset + tslen >= pack->filesize) return got_error(GOT_ERR_PACK_OFFSET); err = got_pack_parse_ref_delta(&id, pack, delta_offset, tslen); if (err) return err; if (pack->map) { delta_data_offset = delta_offset + tslen + got_hash_digest_length(packidx->algo); } else { delta_data_offset = lseek(pack->fd, 0, SEEK_CUR); if (delta_data_offset == -1) return got_error_from_errno("lseek"); } err = add_delta(deltas, delta_offset, tslen, delta_type, delta_size, delta_data_offset); if (err) return err; /* Delta base must be in the same pack file. */ idx = got_packidx_get_object_idx(packidx, &id); if (idx == -1) return got_error(GOT_ERR_NO_OBJ); base_offset = got_packidx_get_object_offset(packidx, idx); if (base_offset == -1) return got_error(GOT_ERR_BAD_PACKIDX); if (base_offset >= pack->filesize) return got_error(GOT_ERR_PACK_OFFSET); err = got_pack_parse_object_type_and_size(&base_type, &base_size, &base_tslen, pack, base_offset); if (err) return err; return got_pack_resolve_delta_chain(deltas, packidx, pack, base_offset, base_tslen, base_type, base_size, recursion - 1); } const struct got_error * got_pack_resolve_delta_chain(struct got_delta_chain *deltas, struct got_packidx *packidx, struct got_pack *pack, off_t delta_offset, size_t tslen, int delta_type, size_t delta_size, unsigned int recursion) { const struct got_error *err = NULL; if (--recursion == 0) return got_error(GOT_ERR_RECURSION); switch (delta_type) { case GOT_OBJ_TYPE_COMMIT: case GOT_OBJ_TYPE_TREE: case GOT_OBJ_TYPE_BLOB: case GOT_OBJ_TYPE_TAG: /* Plain types are the final delta base. Recursion ends. */ err = add_delta(deltas, delta_offset, tslen, delta_type, delta_size, 0); break; case GOT_OBJ_TYPE_OFFSET_DELTA: err = resolve_offset_delta(deltas, packidx, pack, delta_offset, tslen, delta_type, delta_size, recursion - 1); break; case GOT_OBJ_TYPE_REF_DELTA: err = resolve_ref_delta(deltas, packidx, pack, delta_offset, tslen, delta_type, delta_size, recursion - 1); break; default: return got_error(GOT_ERR_OBJ_TYPE); } return err; } static const struct got_error * open_delta_object(struct got_object **obj, struct got_packidx *packidx, struct got_pack *pack, struct got_object_id *id, off_t offset, size_t tslen, int delta_type, size_t delta_size, int idx) { const struct got_error *err = NULL; int resolved_type; *obj = calloc(1, sizeof(**obj)); if (*obj == NULL) return got_error_from_errno("calloc"); (*obj)->flags = 0; (*obj)->hdrlen = 0; (*obj)->size = 0; /* Not known because deltas aren't applied yet. */ memcpy(&(*obj)->id, id, sizeof((*obj)->id)); (*obj)->pack_offset = offset + tslen; STAILQ_INIT(&(*obj)->deltas.entries); (*obj)->flags |= GOT_OBJ_FLAG_DELTIFIED; (*obj)->flags |= GOT_OBJ_FLAG_PACKED; (*obj)->pack_idx = idx; err = got_pack_resolve_delta_chain(&(*obj)->deltas, packidx, pack, offset, tslen, delta_type, delta_size, GOT_DELTA_CHAIN_RECURSION_MAX); if (err) goto done; err = got_delta_chain_get_base_type(&resolved_type, &(*obj)->deltas); if (err) goto done; (*obj)->type = resolved_type; done: if (err) { got_object_close(*obj); *obj = NULL; } return err; } const struct got_error * got_packfile_open_object(struct got_object **obj, struct got_pack *pack, struct got_packidx *packidx, int idx, struct got_object_id *id) { const struct got_error *err = NULL; off_t offset; uint8_t type; uint64_t size; size_t tslen; *obj = NULL; offset = got_packidx_get_object_offset(packidx, idx); if (offset == -1) return got_error(GOT_ERR_BAD_PACKIDX); err = got_pack_parse_object_type_and_size(&type, &size, &tslen, pack, offset); if (err) return err; switch (type) { case GOT_OBJ_TYPE_COMMIT: case GOT_OBJ_TYPE_TREE: case GOT_OBJ_TYPE_BLOB: case GOT_OBJ_TYPE_TAG: err = open_plain_object(obj, id, type, offset + tslen, size, idx); break; case GOT_OBJ_TYPE_OFFSET_DELTA: case GOT_OBJ_TYPE_REF_DELTA: err = open_delta_object(obj, packidx, pack, id, offset, tslen, type, size, idx); break; default: err = got_error(GOT_ERR_OBJ_TYPE); break; } return err; } const struct got_error * got_pack_get_delta_chain_max_size(uint64_t *max_size, struct got_delta_chain *deltas, struct got_pack *pack) { struct got_delta *delta; uint64_t base_size = 0, result_size = 0; *max_size = 0; STAILQ_FOREACH(delta, &deltas->entries, entry) { /* Plain object types are the delta base. */ if (delta->type != GOT_OBJ_TYPE_COMMIT && delta->type != GOT_OBJ_TYPE_TREE && delta->type != GOT_OBJ_TYPE_BLOB && delta->type != GOT_OBJ_TYPE_TAG) { const struct got_error *err; uint8_t *delta_buf = NULL; size_t delta_len; int cached = 1; if (pack->delta_cache) { got_delta_cache_get(&delta_buf, &delta_len, NULL, NULL, pack->delta_cache, delta->data_offset); } if (delta_buf == NULL) { cached = 0; err = read_delta_data(&delta_buf, &delta_len, NULL, delta->data_offset, pack); if (err) return err; } if (pack->delta_cache && !cached) { err = got_delta_cache_add(pack->delta_cache, delta->data_offset, delta_buf, delta_len); if (err == NULL) cached = 1; else if (err->code != GOT_ERR_NO_SPACE) { free(delta_buf); return err; } } err = got_delta_get_sizes(&base_size, &result_size, delta_buf, delta_len); if (!cached) free(delta_buf); if (err) return err; } else base_size = delta->size; if (base_size > *max_size) *max_size = base_size; if (result_size > *max_size) *max_size = result_size; } return NULL; } const struct got_error * got_pack_get_max_delta_object_size(uint64_t *size, struct got_object *obj, struct got_pack *pack) { if ((obj->flags & GOT_OBJ_FLAG_DELTIFIED) == 0) return got_error(GOT_ERR_OBJ_TYPE); return got_pack_get_delta_chain_max_size(size, &obj->deltas, pack); } const struct got_error * got_pack_dump_delta_chain_to_file(size_t *result_size, struct got_delta_chain *deltas, struct got_pack *pack, FILE *outfile, FILE *base_file, FILE *accum_file) { const struct got_error *err = NULL; struct got_delta *delta; uint8_t *base_buf = NULL, *accum_buf = NULL; size_t base_bufsz = 0, accum_bufsz = 0, accum_size = 0; /* We process small enough files entirely in memory for speed. */ const size_t max_bufsize = GOT_DELTA_RESULT_SIZE_CACHED_MAX; uint64_t max_size = 0; int n = 0; *result_size = 0; if (STAILQ_EMPTY(&deltas->entries)) return got_error(GOT_ERR_BAD_DELTA_CHAIN); if (pack->delta_cache) { uint8_t *delta_buf = NULL, *fulltext = NULL; size_t delta_len, fulltext_len; delta = STAILQ_LAST(&deltas->entries, got_delta, entry); got_delta_cache_get(&delta_buf, &delta_len, &fulltext, &fulltext_len, pack->delta_cache, delta->data_offset); if (fulltext) { size_t w; w = fwrite(fulltext, 1, fulltext_len, outfile); if (w != fulltext_len) return got_ferror(outfile, GOT_ERR_IO); if (fflush(outfile) != 0) return got_error_from_errno("fflush"); *result_size = fulltext_len; return NULL; } } if (fseeko(base_file, 0L, SEEK_SET) == -1) return got_error_from_errno("fseeko"); if (fseeko(accum_file, 0L, SEEK_SET) == -1) return got_error_from_errno("fseeko"); /* Deltas are ordered in ascending order. */ STAILQ_FOREACH(delta, &deltas->entries, entry) { uint8_t *delta_buf = NULL, *fulltext = NULL; size_t delta_len, fulltext_len; uint64_t base_size, result_size = 0; int cached = 1; if (n == 0) { size_t mapoff; off_t delta_data_offset; /* Plain object types are the delta base. */ if (delta->type != GOT_OBJ_TYPE_COMMIT && delta->type != GOT_OBJ_TYPE_TREE && delta->type != GOT_OBJ_TYPE_BLOB && delta->type != GOT_OBJ_TYPE_TAG) { err = got_error(GOT_ERR_BAD_DELTA_CHAIN); goto done; } delta_data_offset = delta->offset + delta->tslen; if (delta_data_offset >= pack->filesize) { err = got_error(GOT_ERR_PACK_OFFSET); goto done; } if (pack->map == NULL) { if (lseek(pack->fd, delta_data_offset, SEEK_SET) == -1) { err = got_error_from_errno("lseek"); goto done; } } if (delta->size > max_size) max_size = delta->size; if (max_size > max_bufsize) { if (pack->map) { if (delta_data_offset > SIZE_MAX) { return got_error_fmt( GOT_ERR_RANGE, "delta offset %lld " "overflows size_t", (long long) delta_data_offset); } mapoff = delta_data_offset; err = got_inflate_to_file_mmap( &base_bufsz, NULL, NULL, pack->map, mapoff, pack->filesize - mapoff, base_file); } else err = got_inflate_to_file_fd( &base_bufsz, NULL, NULL, pack->fd, base_file); } else { accum_buf = malloc(max_size); if (accum_buf == NULL) { err = got_error_from_errno("malloc"); goto done; } accum_bufsz = max_size; if (pack->map) { if (delta_data_offset > SIZE_MAX) { err = got_error_fmt( GOT_ERR_RANGE, "delta offset %lld " "overflows size_t", (long long) delta_data_offset); goto done; } mapoff = delta_data_offset; err = got_inflate_to_mem_mmap(&base_buf, &base_bufsz, NULL, NULL, pack->map, mapoff, pack->filesize - mapoff); } else err = got_inflate_to_mem_fd(&base_buf, &base_bufsz, NULL, NULL, max_size, pack->fd); } if (err) goto done; n++; if (base_buf == NULL) rewind(base_file); else if (pack->delta_cache && fulltext == NULL) { err = got_delta_cache_add(pack->delta_cache, delta_data_offset, NULL, 0); if (err) { if (err->code != GOT_ERR_NO_SPACE) goto done; err = NULL; } else { err = got_delta_cache_add_fulltext( pack->delta_cache, delta_data_offset, base_buf, base_bufsz); if (err && err->code != GOT_ERR_NO_SPACE) goto done; err = NULL; } } continue; } if (pack->delta_cache) { got_delta_cache_get(&delta_buf, &delta_len, &fulltext, &fulltext_len, pack->delta_cache, delta->data_offset); } if (delta_buf == NULL) { cached = 0; err = read_delta_data(&delta_buf, &delta_len, NULL, delta->data_offset, pack); if (err) goto done; } if (pack->delta_cache && !cached) { err = got_delta_cache_add(pack->delta_cache, delta->data_offset, delta_buf, delta_len); if (err == NULL) cached = 1; else if (err->code != GOT_ERR_NO_SPACE) { free(delta_buf); goto done; } } err = got_delta_get_sizes(&base_size, &result_size, delta_buf, delta_len); if (err) { if (!cached) free(delta_buf); goto done; } if (base_size > max_size) max_size = base_size; if (result_size > max_size) max_size = result_size; if (fulltext_len > max_size) max_size = fulltext_len; if (base_buf && max_size > max_bufsize) { /* Switch from buffers to temporary files. */ size_t w = fwrite(base_buf, 1, base_bufsz, base_file); if (w != base_bufsz) { err = got_ferror(outfile, GOT_ERR_IO); if (!cached) free(delta_buf); goto done; } free(base_buf); base_buf = NULL; free(accum_buf); accum_buf = NULL; } if (base_buf && max_size > base_bufsz) { uint8_t *p = realloc(base_buf, max_size); if (p == NULL) { err = got_error_from_errno("realloc"); if (!cached) free(delta_buf); goto done; } base_buf = p; base_bufsz = max_size; } if (accum_buf && max_size > accum_bufsz) { uint8_t *p = realloc(accum_buf, max_size); if (p == NULL) { err = got_error_from_errno("realloc"); if (!cached) free(delta_buf); goto done; } accum_buf = p; accum_bufsz = max_size; } if (base_buf) { if (fulltext) { memcpy(accum_buf, fulltext, fulltext_len); accum_size = fulltext_len; err = NULL; } else { err = got_delta_apply_in_mem(base_buf, base_bufsz, delta_buf, delta_len, accum_buf, &accum_size, max_size); } n++; if (!cached) free(delta_buf); if (err) goto done; if (fulltext == NULL) { err = got_delta_cache_add_fulltext( pack->delta_cache, delta->data_offset, accum_buf, accum_size); if (err) { if (err->code != GOT_ERR_NO_SPACE) goto done; err = NULL; } } } else { err = got_delta_apply(base_file, delta_buf, delta_len, /* Final delta application writes to output file. */ ++n < deltas->nentries ? accum_file : outfile, &accum_size); if (!cached) free(delta_buf); if (err) goto done; } if (n < deltas->nentries) { /* Accumulated delta becomes the new base. */ if (base_buf) { uint8_t *tmp = accum_buf; size_t tmp_size = accum_bufsz; accum_buf = base_buf; accum_bufsz = base_bufsz; base_buf = tmp; base_bufsz = tmp_size; } else { FILE *tmp = accum_file; accum_file = base_file; base_file = tmp; rewind(base_file); rewind(accum_file); } } } done: free(base_buf); if (err) { free(accum_buf); accum_buf = NULL; } if (accum_buf) { size_t len = fwrite(accum_buf, 1, accum_size, outfile); free(accum_buf); if (len != accum_size) err = got_ferror(outfile, GOT_ERR_IO); } rewind(outfile); if (err == NULL) *result_size = accum_size; return err; } const struct got_error * got_pack_dump_delta_chain_to_mem(uint8_t **outbuf, size_t *outlen, struct got_delta_chain *deltas, struct got_pack *pack) { const struct got_error *err = NULL; struct got_delta *delta; uint8_t *base_buf = NULL, *accum_buf = NULL; size_t base_bufsz = 0, accum_bufsz = 0, accum_size = 0; uint64_t max_size = 0; int n = 0; *outbuf = NULL; *outlen = 0; if (STAILQ_EMPTY(&deltas->entries)) return got_error(GOT_ERR_BAD_DELTA_CHAIN); if (pack->delta_cache) { uint8_t *delta_buf = NULL, *fulltext = NULL; size_t delta_len, fulltext_len; delta = STAILQ_LAST(&deltas->entries, got_delta, entry); got_delta_cache_get(&delta_buf, &delta_len, &fulltext, &fulltext_len, pack->delta_cache, delta->data_offset); if (fulltext) { *outbuf = malloc(fulltext_len); if (*outbuf == NULL) return got_error_from_errno("malloc"); memcpy(*outbuf, fulltext, fulltext_len); *outlen = fulltext_len; return NULL; } } /* Deltas are ordered in ascending order. */ STAILQ_FOREACH(delta, &deltas->entries, entry) { uint8_t *delta_buf = NULL, *fulltext = NULL; size_t delta_len, fulltext_len = 0; uint64_t base_size, result_size = 0; int cached = 1; if (n == 0) { off_t delta_data_offset; /* Plain object types are the delta base. */ if (delta->type != GOT_OBJ_TYPE_COMMIT && delta->type != GOT_OBJ_TYPE_TREE && delta->type != GOT_OBJ_TYPE_BLOB && delta->type != GOT_OBJ_TYPE_TAG) { err = got_error(GOT_ERR_BAD_DELTA_CHAIN); goto done; } delta_data_offset = delta->offset + delta->tslen; if (delta_data_offset >= pack->filesize) { err = got_error(GOT_ERR_PACK_OFFSET); goto done; } if (pack->delta_cache) { got_delta_cache_get(&delta_buf, &delta_len, &fulltext, &fulltext_len, pack->delta_cache, delta_data_offset); } if (delta->size > max_size) max_size = delta->size; if (delta->size > fulltext_len) max_size = fulltext_len; if (fulltext) { base_buf = malloc(fulltext_len); if (base_buf == NULL) { err = got_error_from_errno("malloc"); goto done; } memcpy(base_buf, fulltext, fulltext_len); base_bufsz = fulltext_len; } else if (pack->map) { size_t mapoff; if (delta_data_offset > SIZE_MAX) { return got_error_fmt(GOT_ERR_RANGE, "delta %lld offset would " "overflow size_t", (long long)delta_data_offset); } mapoff = delta_data_offset; err = got_inflate_to_mem_mmap(&base_buf, &base_bufsz, NULL, NULL, pack->map, mapoff, pack->filesize - mapoff); } else { if (lseek(pack->fd, delta_data_offset, SEEK_SET) == -1) { err = got_error_from_errno("lseek"); goto done; } err = got_inflate_to_mem_fd(&base_buf, &base_bufsz, NULL, NULL, max_size, pack->fd); } if (err) goto done; n++; if (pack->delta_cache && fulltext == NULL) { err = got_delta_cache_add(pack->delta_cache, delta_data_offset, NULL, 0); if (err) { if (err->code != GOT_ERR_NO_SPACE) goto done; err = NULL; } else { err = got_delta_cache_add_fulltext( pack->delta_cache, delta_data_offset, base_buf, base_bufsz); if (err && err->code != GOT_ERR_NO_SPACE) goto done; err = NULL; } } continue; } if (pack->delta_cache) { got_delta_cache_get(&delta_buf, &delta_len, &fulltext, &fulltext_len, pack->delta_cache, delta->data_offset); } if (delta_buf == NULL) { cached = 0; err = read_delta_data(&delta_buf, &delta_len, NULL, delta->data_offset, pack); if (err) goto done; } if (pack->delta_cache && !cached) { err = got_delta_cache_add(pack->delta_cache, delta->data_offset, delta_buf, delta_len); if (err == NULL) cached = 1; else if (err->code != GOT_ERR_NO_SPACE) { free(delta_buf); goto done; } } err = got_delta_get_sizes(&base_size, &result_size, delta_buf, delta_len); if (err) { if (!cached) free(delta_buf); goto done; } if (base_size > max_size) max_size = base_size; if (result_size > max_size) max_size = result_size; if (fulltext_len > max_size) max_size = fulltext_len; if (max_size > base_bufsz) { uint8_t *p = realloc(base_buf, max_size); if (p == NULL) { err = got_error_from_errno("realloc"); if (!cached) free(delta_buf); goto done; } base_buf = p; base_bufsz = max_size; } if (max_size > accum_bufsz) { uint8_t *p = realloc(accum_buf, max_size); if (p == NULL) { err = got_error_from_errno("realloc"); if (!cached) free(delta_buf); goto done; } accum_buf = p; accum_bufsz = max_size; } if (fulltext) { memcpy(accum_buf, fulltext, fulltext_len); accum_size = fulltext_len; err = NULL; } else { err = got_delta_apply_in_mem(base_buf, base_bufsz, delta_buf, delta_len, accum_buf, &accum_size, max_size); } if (!cached) free(delta_buf); n++; if (err) goto done; if (fulltext == NULL) { err = got_delta_cache_add_fulltext(pack->delta_cache, delta->data_offset, accum_buf, accum_size); if (err) { if (err->code != GOT_ERR_NO_SPACE) goto done; err = NULL; } } if (n < deltas->nentries) { /* Accumulated delta becomes the new base. */ uint8_t *tmp = accum_buf; size_t tmp_size = accum_bufsz; accum_buf = base_buf; accum_bufsz = base_bufsz; base_buf = tmp; base_bufsz = tmp_size; } } done: free(base_buf); if (err) { free(accum_buf); *outbuf = NULL; *outlen = 0; } else { *outbuf = accum_buf; *outlen = accum_size; } return err; } const struct got_error * got_packfile_extract_object(struct got_pack *pack, struct got_object *obj, FILE *outfile, FILE *base_file, FILE *accum_file) { const struct got_error *err = NULL; if ((obj->flags & GOT_OBJ_FLAG_PACKED) == 0) return got_error(GOT_ERR_OBJ_NOT_PACKED); if ((obj->flags & GOT_OBJ_FLAG_DELTIFIED) == 0) { if (obj->pack_offset >= pack->filesize) return got_error(GOT_ERR_PACK_OFFSET); if (pack->map) { size_t mapoff; if (obj->pack_offset > SIZE_MAX) { return got_error_fmt(GOT_ERR_RANGE, "pack offset %lld would overflow size_t", (long long)obj->pack_offset); } mapoff = obj->pack_offset; err = got_inflate_to_file_mmap(&obj->size, NULL, NULL, pack->map, mapoff, pack->filesize - mapoff, outfile); } else { if (lseek(pack->fd, obj->pack_offset, SEEK_SET) == -1) return got_error_from_errno("lseek"); err = got_inflate_to_file_fd(&obj->size, NULL, NULL, pack->fd, outfile); } } else err = got_pack_dump_delta_chain_to_file(&obj->size, &obj->deltas, pack, outfile, base_file, accum_file); return err; } const struct got_error * got_packfile_extract_object_to_mem(uint8_t **buf, size_t *len, struct got_object *obj, struct got_pack *pack) { const struct got_error *err = NULL; if ((obj->flags & GOT_OBJ_FLAG_PACKED) == 0) return got_error(GOT_ERR_OBJ_NOT_PACKED); if ((obj->flags & GOT_OBJ_FLAG_DELTIFIED) == 0) { if (obj->pack_offset >= pack->filesize) return got_error(GOT_ERR_PACK_OFFSET); if (pack->map) { size_t mapoff; if (obj->pack_offset > SIZE_MAX) { return got_error_fmt(GOT_ERR_RANGE, "pack offset %lld would overflow size_t", (long long)obj->pack_offset); } mapoff = obj->pack_offset; err = got_inflate_to_mem_mmap(buf, len, NULL, NULL, pack->map, mapoff, pack->filesize - mapoff); } else { if (lseek(pack->fd, obj->pack_offset, SEEK_SET) == -1) return got_error_from_errno("lseek"); err = got_inflate_to_mem_fd(buf, len, NULL, NULL, obj->size, pack->fd); } } else err = got_pack_dump_delta_chain_to_mem(buf, len, &obj->deltas, pack); return err; } static const struct got_error * read_raw_delta_data(uint8_t **delta_buf, size_t *delta_len, size_t *delta_len_compressed, uint64_t *base_size, uint64_t *result_size, off_t delta_data_offset, struct got_pack *pack, struct got_packidx *packidx) { const struct got_error *err = NULL; /* Validate decompression and obtain the decompressed size. */ err = read_delta_data(delta_buf, delta_len, delta_len_compressed, delta_data_offset, pack); if (err) return err; /* Read delta base/result sizes from head of delta stream. */ err = got_delta_get_sizes(base_size, result_size, *delta_buf, *delta_len); if (err) goto done; /* Discard decompressed delta and read it again in compressed form. */ free(*delta_buf); *delta_buf = malloc(*delta_len_compressed); if (*delta_buf == NULL) { err = got_error_from_errno("malloc"); goto done; } if (pack->map) { if (delta_data_offset >= pack->filesize) { err = got_error(GOT_ERR_PACK_OFFSET); goto done; } memcpy(*delta_buf, pack->map + delta_data_offset, *delta_len_compressed); } else { ssize_t n; if (lseek(pack->fd, delta_data_offset, SEEK_SET) == -1) { err = got_error_from_errno("lseek"); goto done; } n = read(pack->fd, *delta_buf, *delta_len_compressed); if (n < 0) { err = got_error_from_errno("read"); goto done; } else if (n != *delta_len_compressed) { err = got_error(GOT_ERR_IO); goto done; } } done: if (err) { free(*delta_buf); *delta_buf = NULL; *delta_len = 0; *delta_len_compressed = 0; *base_size = 0; *result_size = 0; } return err; } const struct got_error * got_packfile_extract_raw_delta(uint8_t **delta_buf, size_t *delta_size, size_t *delta_compressed_size, off_t *delta_offset, off_t *delta_data_offset, off_t *base_offset, struct got_object_id *base_id, uint64_t *base_size, uint64_t *result_size, struct got_pack *pack, struct got_packidx *packidx, int idx) { const struct got_error *err = NULL; off_t offset; uint8_t type; uint64_t size; size_t tslen, delta_hdrlen; *delta_buf = NULL; *delta_size = 0; *delta_compressed_size = 0; *delta_offset = 0; *delta_data_offset = 0; *base_offset = 0; *base_size = 0; *result_size = 0; offset = got_packidx_get_object_offset(packidx, idx); if (offset == -1) return got_error(GOT_ERR_BAD_PACKIDX); if (offset >= pack->filesize) return got_error(GOT_ERR_PACK_OFFSET); err = got_pack_parse_object_type_and_size(&type, &size, &tslen, pack, offset); if (err) return err; if (tslen + size < tslen || offset + size < size || tslen + offset < tslen) return got_error(GOT_ERR_PACK_OFFSET); switch (type) { case GOT_OBJ_TYPE_OFFSET_DELTA: err = got_pack_parse_offset_delta(base_offset, &delta_hdrlen, pack, offset, tslen); if (err) return err; break; case GOT_OBJ_TYPE_REF_DELTA: err = got_pack_parse_ref_delta(base_id, pack, offset, tslen); if (err) return err; delta_hdrlen = got_hash_digest_length(pack->algo); break; default: return got_error_fmt(GOT_ERR_OBJ_TYPE, "non-delta object type %d found at offset %lld", type, (long long)offset); } if (tslen + delta_hdrlen < delta_hdrlen || offset + delta_hdrlen < delta_hdrlen) return got_error(GOT_ERR_BAD_DELTA); *delta_data_offset = offset + tslen + delta_hdrlen; err = read_raw_delta_data(delta_buf, delta_size, delta_compressed_size, base_size, result_size, *delta_data_offset, pack, packidx); if (err) return err; if (*delta_size != size) { err = got_error(GOT_ERR_BAD_DELTA); goto done; } *delta_offset = offset; done: if (err) { free(*delta_buf); *delta_buf = NULL; *delta_size = 0; *delta_compressed_size = 0; *delta_offset = 0; *base_offset = 0; *base_size = 0; *result_size = 0; } return err; } got-portable-0.111/lib/bloom.h0000644000175000017500000001334715001740614011631 /* * Copyright (c) 2012-2017, Jyri J. Virkki * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* Obtained from https://github.com/jvirkki/libbloom */ #ifndef _BLOOM_H #define _BLOOM_H #ifdef __cplusplus extern "C" { #endif /** *************************************************************************** * Structure to keep track of one bloom filter. Caller needs to * allocate this and pass it to the functions below. First call for * every struct must be to bloom_init(). * */ struct bloom { // These fields are part of the public interface of this structure. // Client code may read these values if desired. Client code MUST NOT // modify any of these. int entries; double error; int bits; int bytes; int hashes; uint32_t seed; // Fields below are private to the implementation. These may go away or // change incompatibly at any moment. Client code MUST NOT access or rely // on these. double bpe; unsigned char * bf; int ready; }; /** *************************************************************************** * Initialize the bloom filter for use. * * The filter is initialized with a bit field and number of hash functions * according to the computations from the wikipedia entry: * http://en.wikipedia.org/wiki/Bloom_filter * * Optimal number of bits is: * bits = (entries * ln(error)) / ln(2)^2 * * Optimal number of hash functions is: * hashes = bpe * ln(2) * * Parameters: * ----------- * bloom - Pointer to an allocated struct bloom (see above). * entries - The expected number of entries which will be inserted. * Must be at least 1000 (in practice, likely much larger). * error - Probability of collision (as long as entries are not * exceeded). * * Return: * ------- * 0 - on success * 1 - on failure * */ int bloom_init(struct bloom * bloom, int entries, double error); /** *************************************************************************** * Deprecated, use bloom_init() * */ int bloom_init_size(struct bloom * bloom, int entries, double error, unsigned int cache_size); /** *************************************************************************** * Check if the given element is in the bloom filter. Remember this may * return false positive if a collision occurred. * * Parameters: * ----------- * bloom - Pointer to an allocated struct bloom (see above). * buffer - Pointer to buffer containing element to check. * len - Size of 'buffer'. * * Return: * ------- * 0 - element is not present * 1 - element is present (or false positive due to collision) * -1 - bloom not initialized * */ int bloom_check(struct bloom * bloom, const void * buffer, int len); /** *************************************************************************** * Add the given element to the bloom filter. * The return code indicates if the element (or a collision) was already in, * so for the common check+add use case, no need to call check separately. * * Parameters: * ----------- * bloom - Pointer to an allocated struct bloom (see above). * buffer - Pointer to buffer containing element to add. * len - Size of 'buffer'. * * Return: * ------- * 0 - element was not present and was added * 1 - element (or a collision) had already been added previously * -1 - bloom not initialized * */ int bloom_add(struct bloom * bloom, const void * buffer, int len); /** *************************************************************************** * Print (to stdout) info about this bloom filter. Debugging aid. * */ void bloom_print(struct bloom * bloom); /** *************************************************************************** * Deallocate internal storage. * * Upon return, the bloom struct is no longer usable. You may call bloom_init * again on the same struct to reinitialize it again. * * Parameters: * ----------- * bloom - Pointer to an allocated struct bloom (see above). * * Return: none * */ void bloom_free(struct bloom * bloom); /** *************************************************************************** * Erase internal storage. * * Erases all elements. Upon return, the bloom struct returns to its initial * (initialized) state. * * Parameters: * ----------- * bloom - Pointer to an allocated struct bloom (see above). * * Return: * 0 - on success * 1 - on failure * */ int bloom_reset(struct bloom * bloom); #ifdef __cplusplus } #endif #endif got-portable-0.111/lib/diff_debug.h0000644000175000017500000001255615001740614012600 /* * Copyright (c) 2020 Neels Hofmeyr * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #define DEBUG 0 #if DEBUG #include #include #define print(args...) fprintf(stderr, ##args) #define debug print #define debug_dump dump #define debug_dump_atom dump_atom #define debug_dump_atoms dump_atoms static inline void print_atom_byte(unsigned char c) { if (c == '\r') print("\\r"); else if (c == '\n') print("\\n"); else if ((c < 32 || c >= 127) && (c != '\t')) print("\\x%02x", c); else print("%c", c); } static inline void dump_atom(const struct diff_data *left, const struct diff_data *right, const struct diff_atom *atom) { if (!atom) { print("NULL atom\n"); return; } if (left) print(" %3u '", diff_atom_root_idx(left, atom)); if (atom->at == NULL) { off_t remain = atom->len; if (fseek(atom->root->f, atom->pos, SEEK_SET) == -1) abort(); /* cannot return error */ while (remain > 0) { char buf[16]; size_t r; int i; r = fread(buf, 1, MIN(remain, sizeof(buf)), atom->root->f); if (r == 0) break; remain -= r; for (i = 0; i < r; i++) print_atom_byte(buf[i]); } } else { const char *s; for (s = atom->at; s < (const char*)(atom->at + atom->len); s++) print_atom_byte(*s); } print("'\n"); } static inline void dump_atoms(const struct diff_data *d, struct diff_atom *atom, unsigned int count) { if (count > 42) { dump_atoms(d, atom, 20); print("[%u lines skipped]\n", count - 20 - 20); dump_atoms(d, atom + count - 20, 20); return; } else { struct diff_atom *i; foreach_diff_atom(i, atom, count) { dump_atom(d, NULL, i); } } } static inline void dump(struct diff_data *d) { dump_atoms(d, d->atoms.head, d->atoms.len); } /* kd is a quadratic space myers matrix from the original Myers algorithm. * kd_forward and kd_backward are linear slices of a myers matrix from the Myers * Divide algorithm. */ static inline void dump_myers_graph(const struct diff_data *l, const struct diff_data *r, int *kd, int *kd_forward, int kd_forward_d, int *kd_backward, int kd_backward_d) { #define COLOR_YELLOW "\033[1;33m" #define COLOR_GREEN "\033[1;32m" #define COLOR_BLUE "\033[1;34m" #define COLOR_RED "\033[1;31m" #define COLOR_END "\033[0;m" int x; int y; print(" "); for (x = 0; x <= l->atoms.len; x++) print("%2d", x % 100); print("\n"); for (y = 0; y <= r->atoms.len; y++) { print("%3d ", y); for (x = 0; x <= l->atoms.len; x++) { /* print d advancements from kd, if any. */ char label = 'o'; char *color = NULL; if (kd) { int max = l->atoms.len + r->atoms.len; size_t kd_len = max + 1 + max; int *kd_pos = kd; int di; #define xk_to_y(X, K) ((X) - (K)) for (di = 0; di < max; di++) { int ki; for (ki = di; ki >= -di; ki -= 2) { if (x != kd_pos[ki] || y != xk_to_y(x, ki)) continue; label = '0' + (di % 10); color = COLOR_YELLOW; break; } if (label != 'o') break; kd_pos += kd_len; } } if (kd_forward && kd_forward_d >= 0) { #define xc_to_y(X, C, DELTA) ((X) - (C) + (DELTA)) int ki; for (ki = kd_forward_d; ki >= -kd_forward_d; ki -= 2) { if (x != kd_forward[ki]) continue; if (y != xk_to_y(x, ki)) continue; label = 'F'; color = COLOR_GREEN; break; } } if (kd_backward && kd_backward_d >= 0) { int delta = (int)r->atoms.len - (int)l->atoms.len; int ki; for (ki = kd_backward_d; ki >= -kd_backward_d; ki -= 2) { if (x != kd_backward[ki]) continue; if (y != xc_to_y(x, ki, delta)) continue; if (label == 'o') { label = 'B'; color = COLOR_BLUE; } else { label = 'X'; color = COLOR_RED; } break; } } if (color) print("%s", color); print("%c", label); if (color) print("%s", COLOR_END); if (x < l->atoms.len) print("-"); } print("\n"); if (y == r->atoms.len) break; print(" "); for (x = 0; x < l->atoms.len; x++) { bool same; diff_atom_same(&same, &l->atoms.head[x], &r->atoms.head[y]); if (same) print("|\\"); else print("| "); } print("|\n"); } } static inline void debug_dump_myers_graph(const struct diff_data *l, const struct diff_data *r, int *kd, int *kd_forward, int kd_forward_d, int *kd_backward, int kd_backward_d) { if (l->atoms.len > 99 || r->atoms.len > 99) return; dump_myers_graph(l, r, kd, kd_forward, kd_forward_d, kd_backward, kd_backward_d); } #else #define debug(args...) #define debug_dump(args...) #define debug_dump_atom(args...) #define debug_dump_atoms(args...) #define debug_dump_myers_graph(args...) #endif got-portable-0.111/lib/pack_create_io.c0000644000175000017500000002377515001741021013443 /* * Copyright (c) 2020 Ori Bernstein * Copyright (c) 2021, 2022 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_cancel.h" #include "got_object.h" #include "got_reference.h" #include "got_repository_admin.h" #include "got_path.h" #include "got_lib_delta.h" #include "got_lib_hash.h" #include "got_lib_object.h" #include "got_lib_object_cache.h" #include "got_lib_object_idset.h" #include "got_lib_ratelimit.h" #include "got_lib_pack.h" #include "got_lib_pack_create.h" #include "got_lib_repository.h" static const struct got_error * get_base_object_id(struct got_object_id *base_id, struct got_packidx *packidx, off_t base_offset) { const struct got_error *err; int idx; err = got_packidx_get_offset_idx(&idx, packidx, base_offset); if (err) return err; if (idx == -1) return got_error(GOT_ERR_BAD_PACKIDX); return got_packidx_get_object_id(base_id, packidx, idx); } struct search_deltas_arg { struct got_pack_metavec *v; struct got_packidx *packidx; struct got_pack *pack; struct got_object_idset *idset; int ncolored, nfound, ntrees, ncommits; got_pack_progress_cb progress_cb; void *progress_arg; struct got_ratelimit *rl; got_cancel_cb cancel_cb; void *cancel_arg; }; static const struct got_error * search_delta_for_object(struct got_object_id *id, void *data, void *arg) { const struct got_error *err; struct search_deltas_arg *a = arg; int obj_idx; uint8_t *delta_buf = NULL; uint64_t base_size, result_size; size_t delta_size, delta_compressed_size; off_t delta_offset, delta_data_offset, base_offset; struct got_object_id base_id; if (a->cancel_cb) { err = a->cancel_cb(a->cancel_arg); if (err) return err; } obj_idx = got_packidx_get_object_idx(a->packidx, id); if (obj_idx == -1) return NULL; /* object not present in our pack file */ err = got_packfile_extract_raw_delta(&delta_buf, &delta_size, &delta_compressed_size, &delta_offset, &delta_data_offset, &base_offset, &base_id, &base_size, &result_size, a->pack, a->packidx, obj_idx); if (err) { if (err->code == GOT_ERR_OBJ_TYPE) return NULL; /* object not stored as a delta */ return err; } /* * If this is an offset delta we must determine the base * object ID ourselves. */ if (base_offset != 0) { err = get_base_object_id(&base_id, a->packidx, base_offset); if (err) goto done; } if (got_object_idset_contains(a->idset, &base_id)) { struct got_pack_meta *m, *base; m = got_object_idset_get(a->idset, id); if (m == NULL) { err = got_error_msg(GOT_ERR_NO_OBJ, "delta object not found"); goto done; } base = got_object_idset_get(a->idset, &base_id); if (m == NULL) { err = got_error_msg(GOT_ERR_NO_OBJ, "delta base object not found"); goto done; } m->base_obj_id = got_object_id_dup(&base_id); if (m->base_obj_id == NULL) { err = got_error_from_errno("got_object_id_dup"); goto done; } m->prev = base; m->size = result_size; m->delta_len = delta_size; m->delta_compressed_len = delta_compressed_size; m->reused_delta_offset = delta_data_offset; m->delta_offset = 0; err = got_pack_add_meta(m, a->v); if (err) goto done; err = got_pack_report_progress(a->progress_cb, a->progress_arg, a->rl, a->ncolored, a->nfound, a->ntrees, 0L, a->ncommits, got_object_idset_num_elements(a->idset), a->v->nmeta, 0, 0); if (err) goto done; } done: free(delta_buf); return err; } const struct got_error * got_pack_search_deltas(struct got_packidx **packidx, struct got_pack **pack, struct got_pack_metavec *v, struct got_object_idset *idset, int ncolored, int nfound, int ntrees, int ncommits, struct got_repository *repo, got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; struct search_deltas_arg sda; *packidx = NULL; *pack = NULL; err = got_pack_find_pack_for_reuse(packidx, repo); if (err) return err; if (*packidx == NULL) return NULL; err = got_pack_cache_pack_for_packidx(pack, *packidx, repo); if (err) return err; memset(&sda, 0, sizeof(sda)); sda.v = v; sda.idset = idset; sda.pack = *pack; sda.packidx = *packidx; sda.ncolored = ncolored; sda.nfound = nfound; sda.ntrees = ntrees; sda.ncommits = ncommits; sda.progress_cb = progress_cb; sda.progress_arg = progress_arg; sda.rl = rl; sda.cancel_cb = cancel_cb; sda.cancel_arg = cancel_arg; return got_object_idset_for_each(idset, search_delta_for_object, &sda); } const struct got_error * got_pack_load_packed_object_ids(int *found_all_objects, struct got_object_id **ours, int nours, struct got_object_id **theirs, int ntheirs, int want_meta, uint32_t seed, struct got_object_idset *idset, struct got_object_idset *idset_exclude, int loose_obj_only, struct got_repository *repo, struct got_packidx *packidx, int *ncolored, int *nfound, int *ntrees, got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg) { /* We do not need this optimized traversal while using direct I/O. */ *found_all_objects = 0; return NULL; } const struct got_error * got_pack_paint_commits(int *ncolored, struct got_object_id_queue *ids, int nids, struct got_object_idset *keep, struct got_object_idset *drop, struct got_object_idset *skip, struct got_repository *repo, got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; struct got_commit_object *commit = NULL; struct got_packidx *packidx = NULL; struct got_pack *pack = NULL; const struct got_object_id_queue *parents; struct got_object_qid *qid = NULL; int nqueued = nids, nskip = 0; while (!STAILQ_EMPTY(ids) && nskip != nqueued) { intptr_t color; if (cancel_cb) { err = cancel_cb(cancel_arg); if (err) break; } qid = STAILQ_FIRST(ids); STAILQ_REMOVE_HEAD(ids, entry); nqueued--; color = (intptr_t)qid->data; if (color == COLOR_SKIP) nskip--; if (got_object_idset_contains(skip, &qid->id)) { got_object_qid_free(qid); qid = NULL; continue; } if (color == COLOR_KEEP && got_object_idset_contains(keep, &qid->id)) { got_object_qid_free(qid); qid = NULL; continue; } if (color == COLOR_DROP && got_object_idset_contains(drop, &qid->id)) { got_object_qid_free(qid); qid = NULL; continue; } switch (color) { case COLOR_KEEP: if (got_object_idset_contains(drop, &qid->id)) { err = got_pack_paint_commit(qid, COLOR_SKIP); if (err) goto done; err = got_object_idset_add(skip, &qid->id, NULL); if (err) goto done; err = got_pack_repaint_parent_commits(&qid->id, COLOR_SKIP, skip, skip, repo); if (err) goto done; } else (*ncolored)++; err = got_object_idset_add(keep, &qid->id, NULL); if (err) goto done; break; case COLOR_DROP: if (got_object_idset_contains(keep, &qid->id)) { err = got_pack_paint_commit(qid, COLOR_SKIP); if (err) goto done; err = got_object_idset_add(skip, &qid->id, NULL); if (err) goto done; err = got_pack_repaint_parent_commits(&qid->id, COLOR_SKIP, skip, skip, repo); if (err) goto done; } else (*ncolored)++; err = got_object_idset_add(drop, &qid->id, NULL); if (err) goto done; break; case COLOR_SKIP: if (!got_object_idset_contains(skip, &qid->id)) { err = got_object_idset_add(skip, &qid->id, NULL); if (err) goto done; } break; default: /* should not happen */ err = got_error_fmt(GOT_ERR_NOT_IMPL, "%s invalid commit color %"PRIdPTR, __func__, color); goto done; } err = got_pack_report_progress(progress_cb, progress_arg, rl, *ncolored, 0, 0, 0L, 0, 0, 0, 0, 0); if (err) break; err = got_object_open_as_commit(&commit, repo, &qid->id); if (err) break; parents = got_object_commit_get_parent_ids(commit); if (parents) { struct got_object_qid *pid; color = (intptr_t)qid->data; STAILQ_FOREACH(pid, parents, entry) { err = got_pack_queue_commit_id(ids, &pid->id, color, repo); if (err) goto done; nqueued++; if (color == COLOR_SKIP) nskip++; } } if (pack == NULL && (commit->flags & GOT_COMMIT_FLAG_PACKED)) { /* * We now know that at least one pack file exists. * Pin a suitable pack to ensure it remains cached * while we are churning through commit history. */ if (packidx == NULL) { err = got_pack_find_pack_for_commit_painting( &packidx, ids, repo); if (err) goto done; } if (packidx != NULL) { err = got_pack_cache_pack_for_packidx(&pack, packidx, repo); if (err) goto done; err = got_repo_pin_pack(repo, packidx, pack); if (err) goto done; } } got_object_commit_close(commit); commit = NULL; got_object_qid_free(qid); qid = NULL; } done: if (commit) got_object_commit_close(commit); got_object_qid_free(qid); got_repo_unpin_pack(repo); return err; } got-portable-0.111/lib/gotconfig.c0000644000175000017500000000405115001741021012454 /* * Copyright (c) 2020 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "got_compat.h" #include "got_error.h" #include "got_object.h" #include "got_repository.h" #include "got_lib_gotconfig.h" #include "got_gotconfig.h" void got_gotconfig_free(struct got_gotconfig *conf) { int i; if (conf == NULL) return; free(conf->author); for (i = 0; i < conf->nremotes; i++) got_repo_free_remote_repo_data(&conf->remotes[i]); free(conf->remotes); free(conf); } const char * got_gotconfig_get_author(const struct got_gotconfig *conf) { return conf->author; } void got_gotconfig_get_remotes(int *nremotes, const struct got_remote_repo **remotes, const struct got_gotconfig *conf) { *nremotes = conf->nremotes; *remotes = conf->remotes; } const char * got_gotconfig_get_allowed_signers_file(const struct got_gotconfig *conf) { return conf->allowed_signers_file; } const char * got_gotconfig_get_revoked_signers_file(const struct got_gotconfig *conf) { return conf->revoked_signers_file; } const char * got_gotconfig_get_signer_id(const struct got_gotconfig *conf) { return conf->signer_id; } got-portable-0.111/lib/pollfd.c0000644000175000017500000000622415001741021011761 /* * Copyright (c) 2018, 2022 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include "got_error.h" #include "got_lib_poll.h" const struct got_error * got_poll_fd(int fd, int events, int timeout) { struct pollfd pfd[1]; struct timespec ts; sigset_t sigset; int n; pfd[0].fd = fd; pfd[0].events = events; ts.tv_sec = timeout; ts.tv_nsec = 0; if (sigemptyset(&sigset) == -1) return got_error_from_errno("sigemptyset"); if (sigaddset(&sigset, SIGWINCH) == -1) return got_error_from_errno("sigaddset"); n = ppoll(pfd, 1, timeout == INFTIM ? NULL : &ts, &sigset); if (n == -1) return got_error_from_errno("ppoll"); if (n == 0) { if (pfd[0].revents & POLLHUP) return got_error(GOT_ERR_EOF); return got_error(GOT_ERR_TIMEOUT); } if (pfd[0].revents & (POLLERR | POLLNVAL)) return got_error_from_errno("poll error"); if (pfd[0].revents & events) return NULL; if (pfd[0].revents & POLLHUP) return got_error(GOT_ERR_EOF); return got_error(GOT_ERR_INTERRUPT); } const struct got_error * got_poll_read_full_timeout(int fd, size_t *len, void *buf, size_t bufsize, size_t minbytes, int timeout) { const struct got_error *err = NULL; size_t have = 0; ssize_t r; if (minbytes > bufsize) return got_error(GOT_ERR_NO_SPACE); while (have < minbytes) { err = got_poll_fd(fd, POLLIN, timeout); if (err) return err; r = read(fd, buf + have, bufsize - have); if (r == -1) return got_error_from_errno("read"); if (r == 0) return got_error(GOT_ERR_EOF); have += r; } *len = have; return NULL; } const struct got_error * got_poll_read_full(int fd, size_t *len, void *buf, size_t bufsize, size_t minbytes) { return got_poll_read_full_timeout(fd, len, buf, bufsize, minbytes, INFTIM); } const struct got_error * got_poll_write_full(int fd, const void *buf, off_t len) { return got_poll_write_full_timeout(fd, buf, len, INFTIM); } const struct got_error * got_poll_write_full_timeout(int fd, const void *buf, off_t len, int timeout) { const struct got_error *err = NULL; off_t wlen = 0; ssize_t w = 0; while (wlen != len) { if (wlen > 0) { err = got_poll_fd(fd, POLLOUT, timeout); if (err) return err; } w = write(fd, buf + wlen, len - wlen); if (w == -1) { if (errno != EAGAIN) return got_error_from_errno("write"); } else wlen += w; } return NULL; } got-portable-0.111/lib/arraylist.h0000644000175000017500000000753215001741021012523 /* Auto-reallocating array for arbitrary member types. */ /* * Copyright (c) 2020 Neels Hofmeyr * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* Usage: * * ARRAYLIST(any_type_t) list; * // OR * typedef ARRAYLIST(any_type_t) any_type_list_t; * any_type_list_t list; * * // pass the number of (at first unused) members to add on each realloc: * ARRAYLIST_INIT(list, 128); * any_type_t *x; * while (bar) { * // This enlarges the allocated array as needed; * // list.head may change due to realloc: * ARRAYLIST_ADD(x, list); * if (!x) * return ENOMEM; * *x = random_foo_value; * } * for (i = 0; i < list.len; i++) * printf("%s", foo_to_str(list.head[i])); * ARRAYLIST_FREE(list); */ #include "got_compat.h" #define ARRAYLIST(MEMBER_TYPE) \ struct { \ MEMBER_TYPE *head; \ MEMBER_TYPE *p; \ unsigned int len; \ unsigned int allocated; \ unsigned int alloc_blocksize; \ } #define ARRAYLIST_INIT(ARRAY_LIST, ALLOC_BLOCKSIZE) do { \ (ARRAY_LIST).head = NULL; \ (ARRAY_LIST).len = 0; \ (ARRAY_LIST).allocated = 0; \ (ARRAY_LIST).alloc_blocksize = ALLOC_BLOCKSIZE; \ } while(0) #define ARRAYLIST_ADD(NEW_ITEM_P, ARRAY_LIST) do { \ if ((ARRAY_LIST).len && !(ARRAY_LIST).allocated) { \ NEW_ITEM_P = NULL; \ break; \ } \ if ((ARRAY_LIST).head == NULL \ || (ARRAY_LIST).allocated < (ARRAY_LIST).len + 1) { \ (ARRAY_LIST).p = recallocarray((ARRAY_LIST).head, \ (ARRAY_LIST).len, \ (ARRAY_LIST).allocated + \ ((ARRAY_LIST).allocated ? \ (ARRAY_LIST).allocated / 2 : \ (ARRAY_LIST).alloc_blocksize ? \ (ARRAY_LIST).alloc_blocksize : 8), \ sizeof(*(ARRAY_LIST).head)); \ if ((ARRAY_LIST).p == NULL) { \ NEW_ITEM_P = NULL; \ break; \ } \ (ARRAY_LIST).allocated += \ (ARRAY_LIST).allocated ? \ (ARRAY_LIST).allocated / 2 : \ (ARRAY_LIST).alloc_blocksize ? \ (ARRAY_LIST).alloc_blocksize : 8, \ (ARRAY_LIST).head = (ARRAY_LIST).p; \ (ARRAY_LIST).p = NULL; \ }; \ if ((ARRAY_LIST).head == NULL \ || (ARRAY_LIST).allocated < (ARRAY_LIST).len + 1) { \ NEW_ITEM_P = NULL; \ break; \ } \ (NEW_ITEM_P) = &(ARRAY_LIST).head[(ARRAY_LIST).len]; \ (ARRAY_LIST).len++; \ } while (0) #define ARRAYLIST_INSERT(NEW_ITEM_P, ARRAY_LIST, AT_IDX) do { \ int _at_idx = (AT_IDX); \ ARRAYLIST_ADD(NEW_ITEM_P, ARRAY_LIST); \ if ((NEW_ITEM_P) \ && _at_idx >= 0 \ && _at_idx < (ARRAY_LIST).len) { \ memmove(&(ARRAY_LIST).head[_at_idx + 1], \ &(ARRAY_LIST).head[_at_idx], \ ((ARRAY_LIST).len - 1 - _at_idx) \ * sizeof(*(ARRAY_LIST).head)); \ (NEW_ITEM_P) = &(ARRAY_LIST).head[_at_idx]; \ }; \ } while (0) #define ARRAYLIST_CLEAR(ARRAY_LIST) \ (ARRAY_LIST).len = 0 #define ARRAYLIST_FREE(ARRAY_LIST) \ do { \ if ((ARRAY_LIST).head && (ARRAY_LIST).allocated) \ free((ARRAY_LIST).head); \ ARRAYLIST_INIT(ARRAY_LIST, (ARRAY_LIST).alloc_blocksize); \ } while(0) #define ARRAYLIST_FOREACH(ITEM_P, ARRAY_LIST) \ for ((ITEM_P) = (ARRAY_LIST).head; \ (ITEM_P) - (ARRAY_LIST).head < (ARRAY_LIST).len; \ (ITEM_P)++) #define ARRAYLIST_IDX(ITEM_P, ARRAY_LIST) ((ITEM_P) - (ARRAY_LIST).head) got-portable-0.111/lib/diff_myers.c0000644000175000017500000013003615001741021012627 /* Myers diff algorithm implementation, invented by Eugene W. Myers [1]. * Implementations of both the Myers Divide Et Impera (using linear space) * and the canonical Myers algorithm (using quadratic space). */ /* * Copyright (c) 2020 Neels Hofmeyr * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include "diff_internal.h" #include "diff_debug.h" /* Myers' diff algorithm [1] is nicely explained in [2]. * [1] http://www.xmailserver.org/diff2.pdf * [2] https://blog.jcoglan.com/2017/02/12/the-myers-diff-algorithm-part-1/ ff. * * Myers approaches finding the smallest diff as a graph problem. * The crux is that the original algorithm requires quadratic amount of memory: * both sides' lengths added, and that squared. So if we're diffing lines of * text, two files with 1000 lines each would blow up to a matrix of about * 2000 * 2000 ints of state, about 16 Mb of RAM to figure out 2 kb of text. * The solution is using Myers' "divide and conquer" extension algorithm, which * does the original traversal from both ends of the files to reach a middle * where these "snakes" touch, hence does not need to backtrace the traversal, * and so gets away with only keeping a single column of that huge state matrix * in memory. */ struct diff_box { unsigned int left_start; unsigned int left_end; unsigned int right_start; unsigned int right_end; }; /* If the two contents of a file are A B C D E and X B C Y, * the Myers diff graph looks like: * * k0 k1 * \ \ * k-1 0 1 2 3 4 5 * \ A B C D E * 0 o-o-o-o-o-o * X | | | | | | * 1 o-o-o-o-o-o * B | |\| | | | * 2 o-o-o-o-o-o * C | | |\| | | * 3 o-o-o-o-o-o * Y | | | | | |\ * 4 o-o-o-o-o-o c1 * \ \ * c-1 c0 * * Moving right means delete an atom from the left-hand-side, * Moving down means add an atom from the right-hand-side. * Diagonals indicate identical atoms on both sides, the challenge is to use as * many diagonals as possible. * * The original Myers algorithm walks all the way from the top left to the * bottom right, remembers all steps, and then backtraces to find the shortest * path. However, that requires keeping the entire graph in memory, which needs * quadratic space. * * Myers adds a variant that uses linear space -- note, not linear time, only * linear space: walk forward and backward, find a meeting point in the middle, * and recurse on the two separate sections. This is called "divide and * conquer". * * d: the step number, starting with 0, a.k.a. the distance from the starting * point. * k: relative index in the state array for the forward scan, indicating on * which diagonal through the diff graph we currently are. * c: relative index in the state array for the backward scan, indicating the * diagonal number from the bottom up. * * The "divide and conquer" traversal through the Myers graph looks like this: * * | d= 0 1 2 3 2 1 0 * ----+-------------------------------------------- * k= | c= * 4 | 3 * | * 3 | 3,0 5,2 2 * | / \ * 2 | 2,0 5,3 1 * | / \ * 1 | 1,0 4,3 >= 4,3 5,4<-- 0 * | / / \ / * 0 | -->0,0 3,3 4,4 -1 * | \ / / * -1 | 0,1 1,2 3,4 -2 * | \ / * -2 | 0,2 -3 * | \ * | 0,3 * | forward-> <-backward * * x,y pairs here are the coordinates in the Myers graph: * x = atom index in left-side source, y = atom index in the right-side source. * * Only one forward column and one backward column are kept in mem, each need at * most left.len + 1 + right.len items. Note that each d step occupies either * the even or the odd items of a column: if e.g. the previous column is in the * odd items, the next column is formed in the even items, without overwriting * the previous column's results. * * Also note that from the diagonal index k and the x coordinate, the y * coordinate can be derived: * y = x - k * Hence the state array only needs to keep the x coordinate, i.e. the position * in the left-hand file, and the y coordinate, i.e. position in the right-hand * file, is derived from the index in the state array. * * The two traces meet at 4,3, the first step (here found in the forward * traversal) where a forward position is on or past a backward traced position * on the same diagonal. * * This divides the problem space into: * * 0 1 2 3 4 5 * A B C D E * 0 o-o-o-o-o * X | | | | | * 1 o-o-o-o-o * B | |\| | | * 2 o-o-o-o-o * C | | |\| | * 3 o-o-o-o-*-o *: forward and backward meet here * Y | | * 4 o-o * * Doing the same on each section lead to: * * 0 1 2 3 4 5 * A B C D E * 0 o-o * X | | * 1 o-b b: backward d=1 first reaches here (sliding up the snake) * B \ f: then forward d=2 reaches here (sliding down the snake) * 2 o As result, the box from b to f is found to be identical; * C \ leaving a top box from 0,0 to 1,1 and a bottom trivial * 3 f-o tail 3,3 to 4,3. * * 3 o-* * Y | * 4 o *: forward and backward meet here * * and solving the last top left box gives: * * 0 1 2 3 4 5 * A B C D E -A * 0 o-o +X * X | B * 1 o C * B \ -D * 2 o -E * C \ +Y * 3 o-o-o * Y | * 4 o * */ #define xk_to_y(X, K) ((X) - (K)) #define xc_to_y(X, C, DELTA) ((X) - (C) + (DELTA)) #define k_to_c(K, DELTA) ((K) + (DELTA)) #define c_to_k(C, DELTA) ((C) - (DELTA)) /* Do one forwards step in the "divide and conquer" graph traversal. * left: the left side to diff. * right: the right side to diff against. * kd_forward: the traversal state for forwards traversal, modified by this * function. * This is carried over between invocations with increasing d. * kd_forward points at the center of the state array, allowing * negative indexes. * kd_backward: the traversal state for backwards traversal, to find a meeting * point. * Since forwards is done first, kd_backward will be valid for d - * 1, not d. * kd_backward points at the center of the state array, allowing * negative indexes. * d: Step or distance counter, indicating for what value of d the kd_forward * should be populated. * For d == 0, kd_forward[0] is initialized, i.e. the first invocation should * be for d == 0. * meeting_snake: resulting meeting point, if any. * Return true when a meeting point has been identified. */ static int diff_divide_myers_forward(bool *found_midpoint, struct diff_data *left, struct diff_data *right, int *kd_forward, int *kd_backward, int d, struct diff_box *meeting_snake) { int delta = (int)right->atoms.len - (int)left->atoms.len; int k; int x; int prev_x; int prev_y; int x_before_slide; *found_midpoint = false; for (k = d; k >= -d; k -= 2) { if (k < -(int)right->atoms.len || k > (int)left->atoms.len) { /* This diagonal is completely outside of the Myers * graph, don't calculate it. */ if (k < 0) { /* We are traversing negatively, and already * below the entire graph, nothing will come of * this. */ debug(" break\n"); break; } debug(" continue\n"); continue; } if (d == 0) { /* This is the initializing step. There is no prev_k * yet, get the initial x from the top left of the Myers * graph. */ x = 0; prev_x = x; prev_y = xk_to_y(x, k); } /* Favoring "-" lines first means favoring moving rightwards in * the Myers graph. * For this, all k should derive from k - 1, only the bottom * most k derive from k + 1: * * | d= 0 1 2 * ----+---------------- * k= | * 2 | 2,0 <-- from prev_k = 2 - 1 = 1 * | / * 1 | 1,0 * | / * 0 | -->0,0 3,3 * | \\ / * -1 | 0,1 <-- bottom most for d=1 from * | \\ prev_k = -1 + 1 = 0 * -2 | 0,2 <-- bottom most for d=2 from * prev_k = -2 + 1 = -1 * * Except when a k + 1 from a previous run already means a * further advancement in the graph. * If k == d, there is no k + 1 and k - 1 is the only option. * If k < d, use k + 1 in case that yields a larger x. Also use * k + 1 if k - 1 is outside the graph. */ else if (k > -d && (k == d || (k - 1 >= -(int)right->atoms.len && kd_forward[k - 1] >= kd_forward[k + 1]))) { /* Advance from k - 1. * From position prev_k, step to the right in the Myers * graph: x += 1. */ int prev_k = k - 1; prev_x = kd_forward[prev_k]; prev_y = xk_to_y(prev_x, prev_k); x = prev_x + 1; } else { /* The bottom most one. * From position prev_k, step to the bottom in the Myers * graph: y += 1. * Incrementing y is achieved by decrementing k while * keeping the same x. * (since we're deriving y from y = x - k). */ int prev_k = k + 1; prev_x = kd_forward[prev_k]; prev_y = xk_to_y(prev_x, prev_k); x = prev_x; } x_before_slide = x; /* Slide down any snake that we might find here. */ while (x < left->atoms.len && xk_to_y(x, k) < right->atoms.len) { bool same; int r = diff_atom_same(&same, &left->atoms.head[x], &right->atoms.head[ xk_to_y(x, k)]); if (r) return r; if (!same) break; x++; } kd_forward[k] = x; #if 0 if (x_before_slide != x) { debug(" down %d similar lines\n", x - x_before_slide); } #if DEBUG { int fi; for (fi = d; fi >= k; fi--) { debug("kd_forward[%d] = (%d, %d)\n", fi, kd_forward[fi], kd_forward[fi] - fi); } } #endif #endif if (x < 0 || x > left->atoms.len || xk_to_y(x, k) < 0 || xk_to_y(x, k) > right->atoms.len) continue; /* Figured out a new forwards traversal, see if this has gone * onto or even past a preceding backwards traversal. * * If the delta in length is odd, then d and backwards_d hit the * same state indexes: * | d= 0 1 2 1 0 * ----+---------------- ---------------- * k= | c= * 4 | 3 * | * 3 | 2 * | same * 2 | 2,0====5,3 1 * | / \ * 1 | 1,0 5,4<-- 0 * | / / * 0 | -->0,0 3,3====4,4 -1 * | \ / * -1 | 0,1 -2 * | \ * -2 | 0,2 -3 * | * * If the delta is even, they end up off-by-one, i.e. on * different diagonals: * * | d= 0 1 2 1 0 * ----+---------------- ---------------- * | c= * 3 | 3 * | * 2 | 2,0 off 2 * | / \\ * 1 | 1,0 4,3 1 * | / // \ * 0 | -->0,0 3,3 4,4<-- 0 * | \ / / * -1 | 0,1 3,4 -1 * | \ // * -2 | 0,2 -2 * | * * So in the forward path, we can only match up diagonals when * the delta is odd. */ if ((delta & 1) == 0) continue; /* Forwards is done first, so the backwards one was still at * d - 1. Can't do this for d == 0. */ int backwards_d = d - 1; if (backwards_d < 0) continue; /* If both sides have the same length, forward and backward * start on the same diagonal, meaning the backwards state index * c == k. * As soon as the lengths are not the same, the backwards * traversal starts on a different diagonal, and c = k shifted * by the difference in length. */ int c = k_to_c(k, delta); /* When the file sizes are very different, the traversal trees * start on far distant diagonals. * They don't necessarily meet straight on. See whether this * forward value is on a diagonal that is also valid in * kd_backward[], and match them if so. */ if (c >= -backwards_d && c <= backwards_d) { /* Current k is on a diagonal that exists in * kd_backward[]. If the two x positions have met or * passed (forward walked onto or past backward), then * we've found a midpoint / a mid-box. * * When forwards and backwards traversals meet, the * endpoints of the mid-snake are not the two points in * kd_forward and kd_backward, but rather the section * that was slid (if any) of the current * forward/backward traversal only. * * For example: * * o * \ * o * \ * o * \ * o * \ * X o o * | | | * o-o-o o * \| * M * \ * o * \ * A o * | | * o-o-o * * The forward traversal reached M from the top and slid * downwards to A. The backward traversal already * reached X, which is not a straight line from M * anymore, so picking a mid-snake from M to X would * yield a mistake. * * The correct mid-snake is between M and A. M is where * the forward traversal hit the diagonal that the * backward traversal has already passed, and A is what * it reaches when sliding down identical lines. */ int backward_x = kd_backward[c]; if (x >= backward_x) { if (x_before_slide != x) { /* met after sliding up a mid-snake */ *meeting_snake = (struct diff_box){ .left_start = x_before_slide, .left_end = x, .right_start = xc_to_y(x_before_slide, c, delta), .right_end = xk_to_y(x, k), }; } else { /* met after a side step, non-identical * line. Mark that as box divider * instead. This makes sure that * myers_divide never returns the same * box that came as input, avoiding * "infinite" looping. */ *meeting_snake = (struct diff_box){ .left_start = prev_x, .left_end = x, .right_start = prev_y, .right_end = xk_to_y(x, k), }; } debug("HIT x=(%u,%u) - y=(%u,%u)\n", meeting_snake->left_start, meeting_snake->right_start, meeting_snake->left_end, meeting_snake->right_end); debug_dump_myers_graph(left, right, NULL, kd_forward, d, kd_backward, d-1); *found_midpoint = true; return 0; } } } return 0; } /* Do one backwards step in the "divide and conquer" graph traversal. * left: the left side to diff. * right: the right side to diff against. * kd_forward: the traversal state for forwards traversal, to find a meeting * point. * Since forwards is done first, after this, both kd_forward and * kd_backward will be valid for d. * kd_forward points at the center of the state array, allowing * negative indexes. * kd_backward: the traversal state for backwards traversal, to find a meeting * point. * This is carried over between invocations with increasing d. * kd_backward points at the center of the state array, allowing * negative indexes. * d: Step or distance counter, indicating for what value of d the kd_backward * should be populated. * Before the first invocation, kd_backward[0] shall point at the bottom * right of the Myers graph (left.len, right.len). * The first invocation will be for d == 1. * meeting_snake: resulting meeting point, if any. * Return true when a meeting point has been identified. */ static int diff_divide_myers_backward(bool *found_midpoint, struct diff_data *left, struct diff_data *right, int *kd_forward, int *kd_backward, int d, struct diff_box *meeting_snake) { int delta = (int)right->atoms.len - (int)left->atoms.len; int c; int x; int prev_x; int prev_y; int x_before_slide; *found_midpoint = false; for (c = d; c >= -d; c -= 2) { if (c < -(int)left->atoms.len || c > (int)right->atoms.len) { /* This diagonal is completely outside of the Myers * graph, don't calculate it. */ if (c < 0) { /* We are traversing negatively, and already * below the entire graph, nothing will come of * this. */ break; } continue; } if (d == 0) { /* This is the initializing step. There is no prev_c * yet, get the initial x from the bottom right of the * Myers graph. */ x = left->atoms.len; prev_x = x; prev_y = xc_to_y(x, c, delta); } /* Favoring "-" lines first means favoring moving rightwards in * the Myers graph. * For this, all c should derive from c - 1, only the bottom * most c derive from c + 1: * * 2 1 0 * --------------------------------------------------- * c= * 3 * * from prev_c = c - 1 --> 5,2 2 * \ * 5,3 1 * \ * 4,3 5,4<-- 0 * \ / * bottom most for d=1 from c + 1 --> 4,4 -1 * / * bottom most for d=2 --> 3,4 -2 * * Except when a c + 1 from a previous run already means a * further advancement in the graph. * If c == d, there is no c + 1 and c - 1 is the only option. * If c < d, use c + 1 in case that yields a larger x. * Also use c + 1 if c - 1 is outside the graph. */ else if (c > -d && (c == d || (c - 1 >= -(int)right->atoms.len && kd_backward[c - 1] <= kd_backward[c + 1]))) { /* A top one. * From position prev_c, step upwards in the Myers * graph: y -= 1. * Decrementing y is achieved by incrementing c while * keeping the same x. (since we're deriving y from * y = x - c + delta). */ int prev_c = c - 1; prev_x = kd_backward[prev_c]; prev_y = xc_to_y(prev_x, prev_c, delta); x = prev_x; } else { /* The bottom most one. * From position prev_c, step to the left in the Myers * graph: x -= 1. */ int prev_c = c + 1; prev_x = kd_backward[prev_c]; prev_y = xc_to_y(prev_x, prev_c, delta); x = prev_x - 1; } /* Slide up any snake that we might find here (sections of * identical lines on both sides). */ #if 0 debug("c=%d x-1=%d Yb-1=%d-1=%d\n", c, x-1, xc_to_y(x, c, delta), xc_to_y(x, c, delta)-1); if (x > 0) { debug(" l="); debug_dump_atom(left, right, &left->atoms.head[x-1]); } if (xc_to_y(x, c, delta) > 0) { debug(" r="); debug_dump_atom(right, left, &right->atoms.head[xc_to_y(x, c, delta)-1]); } #endif x_before_slide = x; while (x > 0 && xc_to_y(x, c, delta) > 0) { bool same; int r = diff_atom_same(&same, &left->atoms.head[x-1], &right->atoms.head[ xc_to_y(x, c, delta)-1]); if (r) return r; if (!same) break; x--; } kd_backward[c] = x; #if 0 if (x_before_slide != x) { debug(" up %d similar lines\n", x_before_slide - x); } if (DEBUG) { int fi; for (fi = d; fi >= c; fi--) { debug("kd_backward[%d] = (%d, %d)\n", fi, kd_backward[fi], kd_backward[fi] - fi + delta); } } #endif if (x < 0 || x > left->atoms.len || xc_to_y(x, c, delta) < 0 || xc_to_y(x, c, delta) > right->atoms.len) continue; /* Figured out a new backwards traversal, see if this has gone * onto or even past a preceding forwards traversal. * * If the delta in length is even, then d and backwards_d hit * the same state indexes -- note how this is different from in * the forwards traversal, because now both d are the same: * * | d= 0 1 2 2 1 0 * ----+---------------- -------------------- * k= | c= * 4 | * | * 3 | 3 * | same * 2 | 2,0====5,2 2 * | / \ * 1 | 1,0 5,3 1 * | / / \ * 0 | -->0,0 3,3====4,3 5,4<-- 0 * | \ / / * -1 | 0,1 4,4 -1 * | \ * -2 | 0,2 -2 * | * -3 * If the delta is odd, they end up off-by-one, i.e. on * different diagonals. * So in the backward path, we can only match up diagonals when * the delta is even. */ if ((delta & 1) != 0) continue; /* Forwards was done first, now both d are the same. */ int forwards_d = d; /* As soon as the lengths are not the same, the * backwards traversal starts on a different diagonal, * and c = k shifted by the difference in length. */ int k = c_to_k(c, delta); /* When the file sizes are very different, the traversal trees * start on far distant diagonals. * They don't necessarily meet straight on. See whether this * backward value is also on a valid diagonal in kd_forward[], * and match them if so. */ if (k >= -forwards_d && k <= forwards_d) { /* Current c is on a diagonal that exists in * kd_forward[]. If the two x positions have met or * passed (backward walked onto or past forward), then * we've found a midpoint / a mid-box. * * When forwards and backwards traversals meet, the * endpoints of the mid-snake are not the two points in * kd_forward and kd_backward, but rather the section * that was slid (if any) of the current * forward/backward traversal only. * * For example: * * o-o-o * | | * o A * | \ * o o * \ * M * |\ * o o-o-o * | | | * o o X * \ * o * \ * o * \ * o * * The backward traversal reached M from the bottom and * slid upwards. The forward traversal already reached * X, which is not a straight line from M anymore, so * picking a mid-snake from M to X would yield a * mistake. * * The correct mid-snake is between M and A. M is where * the backward traversal hit the diagonal that the * forwards traversal has already passed, and A is what * it reaches when sliding up identical lines. */ int forward_x = kd_forward[k]; if (forward_x >= x) { if (x_before_slide != x) { /* met after sliding down a mid-snake */ *meeting_snake = (struct diff_box){ .left_start = x, .left_end = x_before_slide, .right_start = xc_to_y(x, c, delta), .right_end = xk_to_y(x_before_slide, k), }; } else { /* met after a side step, non-identical * line. Mark that as box divider * instead. This makes sure that * myers_divide never returns the same * box that came as input, avoiding * "infinite" looping. */ *meeting_snake = (struct diff_box){ .left_start = x, .left_end = prev_x, .right_start = xc_to_y(x, c, delta), .right_end = prev_y, }; } debug("HIT x=%u,%u - y=%u,%u\n", meeting_snake->left_start, meeting_snake->right_start, meeting_snake->left_end, meeting_snake->right_end); debug_dump_myers_graph(left, right, NULL, kd_forward, d, kd_backward, d); *found_midpoint = true; return 0; } } } return 0; } /* Integer square root approximation */ static int shift_sqrt(int val) { int i; for (i = 1; val > 0; val >>= 2) i <<= 1; return i; } #define DIFF_EFFORT_MIN 1024 /* Myers "Divide et Impera": tracing forwards from the start and backwards from * the end to find a midpoint that divides the problem into smaller chunks. * Requires only linear amounts of memory. */ int diff_algo_myers_divide(const struct diff_algo_config *algo_config, struct diff_state *state) { int rc = ENOMEM; struct diff_data *left = &state->left; struct diff_data *right = &state->right; int *kd_buf; debug("\n** %s\n", __func__); debug("left:\n"); debug_dump(left); debug("right:\n"); debug_dump(right); /* Allocate two columns of a Myers graph, one for the forward and one * for the backward traversal. */ unsigned int max = left->atoms.len + right->atoms.len; size_t kd_len = max + 1; size_t kd_buf_size = kd_len << 1; if (state->kd_buf_size < kd_buf_size) { kd_buf = reallocarray(state->kd_buf, kd_buf_size, sizeof(int)); if (!kd_buf) return ENOMEM; state->kd_buf = kd_buf; state->kd_buf_size = kd_buf_size; } else kd_buf = state->kd_buf; int i; for (i = 0; i < kd_buf_size; i++) kd_buf[i] = -1; int *kd_forward = kd_buf; int *kd_backward = kd_buf + kd_len; int max_effort = shift_sqrt(max/2); if (max_effort < DIFF_EFFORT_MIN) max_effort = DIFF_EFFORT_MIN; /* The 'k' axis in Myers spans positive and negative indexes, so point * the kd to the middle. * It is then possible to index from -max/2 .. max/2. */ kd_forward += max/2; kd_backward += max/2; int d; struct diff_box mid_snake = {}; bool found_midpoint = false; for (d = 0; d <= (max/2); d++) { int r; r = diff_divide_myers_forward(&found_midpoint, left, right, kd_forward, kd_backward, d, &mid_snake); if (r) return r; if (found_midpoint) break; r = diff_divide_myers_backward(&found_midpoint, left, right, kd_forward, kd_backward, d, &mid_snake); if (r) return r; if (found_midpoint) break; /* Limit the effort spent looking for a mid snake. If files have * very few lines in common, the effort spent to find nice mid * snakes is just not worth it, the diff result will still be * essentially minus everything on the left, plus everything on * the right, with a few useless matches here and there. */ if (d > max_effort) { /* pick the furthest reaching point from * kd_forward and kd_backward, and use that as a * midpoint, to not step into another diff algo * recursion with unchanged box. */ int delta = (int)right->atoms.len - (int)left->atoms.len; int x = 0; int y; int i; int best_forward_i = 0; int best_forward_distance = 0; int best_backward_i = 0; int best_backward_distance = 0; int distance; int best_forward_x; int best_forward_y; int best_backward_x; int best_backward_y; debug("~~~ HIT d = %d > max_effort = %d\n", d, max_effort); debug_dump_myers_graph(left, right, NULL, kd_forward, d, kd_backward, d); for (i = d; i >= -d; i -= 2) { if (i >= -(int)right->atoms.len && i <= (int)left->atoms.len) { x = kd_forward[i]; y = xk_to_y(x, i); distance = x + y; if (distance > best_forward_distance) { best_forward_distance = distance; best_forward_i = i; } } if (i >= -(int)left->atoms.len && i <= (int)right->atoms.len) { x = kd_backward[i]; y = xc_to_y(x, i, delta); distance = (right->atoms.len - x) + (left->atoms.len - y); if (distance >= best_backward_distance) { best_backward_distance = distance; best_backward_i = i; } } } /* The myers-divide didn't meet in the middle. We just * figured out the places where the forward path * advanced the most, and the backward path advanced the * most. Just divide at whichever one of those two is better. * * o-o * | * o * \ * o * \ * F <-- cut here * * * * or here --> B * \ * o * \ * o * | * o-o */ best_forward_x = kd_forward[best_forward_i]; best_forward_y = xk_to_y(best_forward_x, best_forward_i); best_backward_x = kd_backward[best_backward_i]; best_backward_y = xc_to_y(best_backward_x, best_backward_i, delta); if (best_forward_distance >= best_backward_distance) { x = best_forward_x; y = best_forward_y; } else { x = best_backward_x; y = best_backward_y; } debug("max_effort cut at x=%d y=%d\n", x, y); if (x < 0 || y < 0 || x > left->atoms.len || y > right->atoms.len) break; found_midpoint = true; mid_snake = (struct diff_box){ .left_start = x, .left_end = x, .right_start = y, .right_end = y, }; break; } } if (!found_midpoint) { /* Divide and conquer failed to find a meeting point. Use the * fallback_algo defined in the algo_config (leave this to the * caller). This is just paranoia/sanity, we normally should * always find a midpoint. */ debug(" no midpoint \n"); rc = DIFF_RC_USE_DIFF_ALGO_FALLBACK; goto return_rc; } else { debug(" mid snake L: %u to %u of %u R: %u to %u of %u\n", mid_snake.left_start, mid_snake.left_end, left->atoms.len, mid_snake.right_start, mid_snake.right_end, right->atoms.len); /* Section before the mid-snake. */ debug("Section before the mid-snake\n"); struct diff_atom *left_atom = &left->atoms.head[0]; unsigned int left_section_len = mid_snake.left_start; struct diff_atom *right_atom = &right->atoms.head[0]; unsigned int right_section_len = mid_snake.right_start; if (left_section_len && right_section_len) { /* Record an unsolved chunk, the caller will apply * inner_algo() on this chunk. */ if (!diff_state_add_chunk(state, false, left_atom, left_section_len, right_atom, right_section_len)) goto return_rc; } else if (left_section_len && !right_section_len) { /* Only left atoms and none on the right, they form a * "minus" chunk, then. */ if (!diff_state_add_chunk(state, true, left_atom, left_section_len, right_atom, 0)) goto return_rc; } else if (!left_section_len && right_section_len) { /* No left atoms, only atoms on the right, they form a * "plus" chunk, then. */ if (!diff_state_add_chunk(state, true, left_atom, 0, right_atom, right_section_len)) goto return_rc; } /* else: left_section_len == 0 and right_section_len == 0, i.e. * nothing before the mid-snake. */ if (mid_snake.left_end > mid_snake.left_start || mid_snake.right_end > mid_snake.right_start) { /* The midpoint is a section of identical data on both * sides, or a certain differing line: that section * immediately becomes a solved chunk. */ debug("the mid-snake\n"); if (!diff_state_add_chunk(state, true, &left->atoms.head[mid_snake.left_start], mid_snake.left_end - mid_snake.left_start, &right->atoms.head[mid_snake.right_start], mid_snake.right_end - mid_snake.right_start)) goto return_rc; } /* Section after the mid-snake. */ debug("Section after the mid-snake\n"); debug(" left_end %u right_end %u\n", mid_snake.left_end, mid_snake.right_end); debug(" left_count %u right_count %u\n", left->atoms.len, right->atoms.len); left_atom = &left->atoms.head[mid_snake.left_end]; left_section_len = left->atoms.len - mid_snake.left_end; right_atom = &right->atoms.head[mid_snake.right_end]; right_section_len = right->atoms.len - mid_snake.right_end; if (left_section_len && right_section_len) { /* Record an unsolved chunk, the caller will apply * inner_algo() on this chunk. */ if (!diff_state_add_chunk(state, false, left_atom, left_section_len, right_atom, right_section_len)) goto return_rc; } else if (left_section_len && !right_section_len) { /* Only left atoms and none on the right, they form a * "minus" chunk, then. */ if (!diff_state_add_chunk(state, true, left_atom, left_section_len, right_atom, 0)) goto return_rc; } else if (!left_section_len && right_section_len) { /* No left atoms, only atoms on the right, they form a * "plus" chunk, then. */ if (!diff_state_add_chunk(state, true, left_atom, 0, right_atom, right_section_len)) goto return_rc; } /* else: left_section_len == 0 and right_section_len == 0, i.e. * nothing after the mid-snake. */ } rc = DIFF_RC_OK; return_rc: debug("** END %s\n", __func__); return rc; } /* Myers Diff tracing from the start all the way through to the end, requiring * quadratic amounts of memory. This can fail if the required space surpasses * algo_config->permitted_state_size. */ int diff_algo_myers(const struct diff_algo_config *algo_config, struct diff_state *state) { /* do a diff_divide_myers_forward() without a _backward(), so that it * walks forward across the entire files to reach the end. Keep each * run's state, and do a final backtrace. */ int rc = ENOMEM; struct diff_data *left = &state->left; struct diff_data *right = &state->right; int *kd_buf; debug("\n** %s\n", __func__); debug("left:\n"); debug_dump(left); debug("right:\n"); debug_dump(right); debug_dump_myers_graph(left, right, NULL, NULL, 0, NULL, 0); /* Allocate two columns of a Myers graph, one for the forward and one * for the backward traversal. */ unsigned int max = left->atoms.len + right->atoms.len; size_t kd_len = max + 1 + max; size_t kd_buf_size = kd_len * kd_len; size_t kd_state_size = kd_buf_size * sizeof(int); debug("state size: %zu\n", kd_state_size); if (kd_buf_size < kd_len /* overflow? */ || (SIZE_MAX / kd_len ) < kd_len || kd_state_size > algo_config->permitted_state_size) { debug("state size %zu > permitted_state_size %zu, use fallback_algo\n", kd_state_size, algo_config->permitted_state_size); return DIFF_RC_USE_DIFF_ALGO_FALLBACK; } if (state->kd_buf_size < kd_buf_size) { kd_buf = reallocarray(state->kd_buf, kd_buf_size, sizeof(int)); if (!kd_buf) return ENOMEM; state->kd_buf = kd_buf; state->kd_buf_size = kd_buf_size; } else kd_buf = state->kd_buf; int i; for (i = 0; i < kd_buf_size; i++) kd_buf[i] = -1; /* The 'k' axis in Myers spans positive and negative indexes, so point * the kd to the middle. * It is then possible to index from -max .. max. */ int *kd_origin = kd_buf + max; int *kd_column = kd_origin; int d; int backtrack_d = -1; int backtrack_k = 0; int k; int x, y; for (d = 0; d <= max; d++, kd_column += kd_len) { debug("-- %s d=%d\n", __func__, d); for (k = d; k >= -d; k -= 2) { if (k < -(int)right->atoms.len || k > (int)left->atoms.len) { /* This diagonal is completely outside of the * Myers graph, don't calculate it. */ if (k < -(int)right->atoms.len) debug(" %d k <" " -(int)right->atoms.len %d\n", k, -(int)right->atoms.len); else debug(" %d k > left->atoms.len %d\n", k, left->atoms.len); if (k < 0) { /* We are traversing negatively, and * already below the entire graph, * nothing will come of this. */ debug(" break\n"); break; } debug(" continue\n"); continue; } if (d == 0) { /* This is the initializing step. There is no * prev_k yet, get the initial x from the top * left of the Myers graph. */ x = 0; } else { int *kd_prev_column = kd_column - kd_len; /* Favoring "-" lines first means favoring * moving rightwards in the Myers graph. * For this, all k should derive from k - 1, * only the bottom most k derive from k + 1: * * | d= 0 1 2 * ----+---------------- * k= | * 2 | 2,0 <-- from * | / prev_k = 2 - 1 = 1 * 1 | 1,0 * | / * 0 | -->0,0 3,3 * | \\ / * -1 | 0,1 <-- bottom most for d=1 * | \\ from prev_k = -1+1 = 0 * -2 | 0,2 <-- bottom most for * d=2 from * prev_k = -2+1 = -1 * * Except when a k + 1 from a previous run * already means a further advancement in the * graph. * If k == d, there is no k + 1 and k - 1 is the * only option. * If k < d, use k + 1 in case that yields a * larger x. Also use k + 1 if k - 1 is outside * the graph. */ if (k > -d && (k == d || (k - 1 >= -(int)right->atoms.len && kd_prev_column[k - 1] >= kd_prev_column[k + 1]))) { /* Advance from k - 1. * From position prev_k, step to the * right in the Myers graph: x += 1. */ int prev_k = k - 1; int prev_x = kd_prev_column[prev_k]; x = prev_x + 1; } else { /* The bottom most one. * From position prev_k, step to the * bottom in the Myers graph: y += 1. * Incrementing y is achieved by * decrementing k while keeping the same * x. (since we're deriving y from y = * x - k). */ int prev_k = k + 1; int prev_x = kd_prev_column[prev_k]; x = prev_x; } } /* Slide down any snake that we might find here. */ while (x < left->atoms.len && xk_to_y(x, k) < right->atoms.len) { bool same; int r = diff_atom_same(&same, &left->atoms.head[x], &right->atoms.head[ xk_to_y(x, k)]); if (r) return r; if (!same) break; x++; } kd_column[k] = x; if (x == left->atoms.len && xk_to_y(x, k) == right->atoms.len) { /* Found a path */ backtrack_d = d; backtrack_k = k; debug("Reached the end at d = %d, k = %d\n", backtrack_d, backtrack_k); break; } } if (backtrack_d >= 0) break; } debug_dump_myers_graph(left, right, kd_origin, NULL, 0, NULL, 0); /* backtrack. A matrix spanning from start to end of the file is ready: * * | d= 0 1 2 3 4 * ----+--------------------------------- * k= | * 3 | * | * 2 | 2,0 * | / * 1 | 1,0 4,3 * | / / \ * 0 | -->0,0 3,3 4,4 --> backtrack_d = 4, backtrack_k = 0 * | \ / \ * -1 | 0,1 3,4 * | \ * -2 | 0,2 * | * * From (4,4) backwards, find the previous position that is the largest, and remember it. * */ for (d = backtrack_d, k = backtrack_k; d >= 0; d--) { x = kd_column[k]; y = xk_to_y(x, k); /* When the best position is identified, remember it for that * kd_column. * That kd_column is no longer needed otherwise, so just * re-purpose kd_column[0] = x and kd_column[1] = y, * so that there is no need to allocate more memory. */ kd_column[0] = x; kd_column[1] = y; debug("Backtrack d=%d: xy=(%d, %d)\n", d, kd_column[0], kd_column[1]); /* Don't access memory before kd_buf */ if (d == 0) break; int *kd_prev_column = kd_column - kd_len; /* When y == 0, backtracking downwards (k-1) is the only way. * When x == 0, backtracking upwards (k+1) is the only way. * * | d= 0 1 2 3 4 * ----+--------------------------------- * k= | * 3 | * | ..y == 0 * 2 | 2,0 * | / * 1 | 1,0 4,3 * | / / \ * 0 | -->0,0 3,3 4,4 --> backtrack_d = 4, * | \ / \ backtrack_k = 0 * -1 | 0,1 3,4 * | \ * -2 | 0,2__ * | x == 0 */ if (y == 0 || (x > 0 && kd_prev_column[k - 1] >= kd_prev_column[k + 1])) { k = k - 1; debug("prev k=k-1=%d x=%d y=%d\n", k, kd_prev_column[k], xk_to_y(kd_prev_column[k], k)); } else { k = k + 1; debug("prev k=k+1=%d x=%d y=%d\n", k, kd_prev_column[k], xk_to_y(kd_prev_column[k], k)); } kd_column = kd_prev_column; } /* Forwards again, this time recording the diff chunks. * Definitely start from 0,0. kd_column[0] may actually point to the * bottom of a snake starting at 0,0 */ x = 0; y = 0; kd_column = kd_origin; for (d = 0; d <= backtrack_d; d++, kd_column += kd_len) { int next_x = kd_column[0]; int next_y = kd_column[1]; debug("Forward track from xy(%d,%d) to xy(%d,%d)\n", x, y, next_x, next_y); struct diff_atom *left_atom = &left->atoms.head[x]; int left_section_len = next_x - x; struct diff_atom *right_atom = &right->atoms.head[y]; int right_section_len = next_y - y; rc = ENOMEM; if (left_section_len && right_section_len) { /* This must be a snake slide. * Snake slides have a straight line leading into them * (except when starting at (0,0)). Find out whether the * lead-in is horizontal or vertical: * * left * ----------> * | * r| o-o o * i| \ | * g| o o * h| \ \ * t| o o * v * * If left_section_len > right_section_len, the lead-in * is horizontal, meaning first remove one atom from the * left before sliding down the snake. * If right_section_len > left_section_len, the lead-in * is vertical, so add one atom from the right before * sliding down the snake. */ if (left_section_len == right_section_len + 1) { if (!diff_state_add_chunk(state, true, left_atom, 1, right_atom, 0)) goto return_rc; left_atom++; left_section_len--; } else if (right_section_len == left_section_len + 1) { if (!diff_state_add_chunk(state, true, left_atom, 0, right_atom, 1)) goto return_rc; right_atom++; right_section_len--; } else if (left_section_len != right_section_len) { /* The numbers are making no sense. Should never * happen. */ rc = DIFF_RC_USE_DIFF_ALGO_FALLBACK; goto return_rc; } if (!diff_state_add_chunk(state, true, left_atom, left_section_len, right_atom, right_section_len)) goto return_rc; } else if (left_section_len && !right_section_len) { /* Only left atoms and none on the right, they form a * "minus" chunk, then. */ if (!diff_state_add_chunk(state, true, left_atom, left_section_len, right_atom, 0)) goto return_rc; } else if (!left_section_len && right_section_len) { /* No left atoms, only atoms on the right, they form a * "plus" chunk, then. */ if (!diff_state_add_chunk(state, true, left_atom, 0, right_atom, right_section_len)) goto return_rc; } x = next_x; y = next_y; } rc = DIFF_RC_OK; return_rc: debug("** END %s rc=%d\n", __func__, rc); return rc; } got-portable-0.111/lib/repository.c0000644000175000017500000017415115001741021012725 /* * Copyright (c) 2018, 2019, 2020 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bloom.h" #include "got_error.h" #include "got_reference.h" #include "got_repository.h" #include "got_path.h" #include "got_cancel.h" #include "got_object.h" #include "got_opentemp.h" #include "got_lib_delta.h" #include "got_lib_delta_cache.h" #include "got_lib_hash.h" #include "got_lib_inflate.h" #include "got_lib_object.h" #include "got_lib_object_parse.h" #include "got_lib_object_create.h" #include "got_lib_pack.h" #include "got_lib_privsep.h" #include "got_lib_object_cache.h" #include "got_lib_repository.h" #include "got_lib_gotconfig.h" #ifndef nitems #define nitems(_a) (sizeof(_a) / sizeof((_a)[0])) #endif #define GOT_PACK_NUM_TEMPFILES GOT_PACK_CACHE_SIZE * 2 RB_PROTOTYPE(got_packidx_bloom_filter_tree, got_packidx_bloom_filter, entry, got_packidx_bloom_filter_cmp); static inline int is_boolean_val(const char *val) { return (strcasecmp(val, "true") == 0 || strcasecmp(val, "false") == 0 || strcasecmp(val, "on") == 0 || strcasecmp(val, "off") == 0 || strcasecmp(val, "yes") == 0 || strcasecmp(val, "no") == 0 || strcasecmp(val, "1") == 0 || strcasecmp(val, "0") == 0); } static inline int get_boolean_val(const char *val) { return (strcasecmp(val, "true") == 0 || strcasecmp(val, "on") == 0 || strcasecmp(val, "yes") == 0 || strcasecmp(val, "1") == 0); } const char * got_repo_get_path(struct got_repository *repo) { return repo->path; } const char * got_repo_get_path_git_dir(struct got_repository *repo) { return repo->path_git_dir; } int got_repo_get_fd(struct got_repository *repo) { return repo->gitdir_fd; } enum got_hash_algorithm got_repo_get_object_format(struct got_repository *repo) { return repo->algo; } const char * got_repo_get_gitconfig_author_name(struct got_repository *repo) { return repo->gitconfig_author_name; } const char * got_repo_get_gitconfig_author_email(struct got_repository *repo) { return repo->gitconfig_author_email; } const char * got_repo_get_global_gitconfig_author_name(struct got_repository *repo) { return repo->global_gitconfig_author_name; } const char * got_repo_get_global_gitconfig_author_email(struct got_repository *repo) { return repo->global_gitconfig_author_email; } const char * got_repo_get_gitconfig_owner(struct got_repository *repo) { return repo->gitconfig_owner; } int got_repo_has_extension(struct got_repository *repo, const char *ext) { int i; for (i = 0; i < repo->nextensions; ++i) { if (!strcasecmp(ext, repo->extnames[i])) return get_boolean_val(repo->extvals[i]); } return 0; } int got_repo_is_bare(struct got_repository *repo) { return (strcmp(repo->path, repo->path_git_dir) == 0); } static char * get_path_git_child(struct got_repository *repo, const char *basename) { char *path_child; if (asprintf(&path_child, "%s/%s", repo->path_git_dir, basename) == -1) return NULL; return path_child; } char * got_repo_get_path_objects(struct got_repository *repo) { return get_path_git_child(repo, GOT_OBJECTS_DIR); } char * got_repo_get_path_objects_pack(struct got_repository *repo) { return get_path_git_child(repo, GOT_OBJECTS_PACK_DIR); } char * got_repo_get_path_refs(struct got_repository *repo) { return get_path_git_child(repo, GOT_REFS_DIR); } char * got_repo_get_path_packed_refs(struct got_repository *repo) { return get_path_git_child(repo, GOT_PACKED_REFS_FILE); } static char * get_path_head(struct got_repository *repo) { return get_path_git_child(repo, GOT_HEAD_FILE); } char * got_repo_get_path_gitconfig(struct got_repository *repo) { return get_path_git_child(repo, GOT_GITCONFIG); } char * got_repo_get_path_gotconfig(struct got_repository *repo) { return get_path_git_child(repo, GOT_GOTCONFIG_FILENAME); } const struct got_gotconfig * got_repo_get_gotconfig(struct got_repository *repo) { return repo->gotconfig; } void got_repo_get_gitconfig_remotes(int *nremotes, const struct got_remote_repo **remotes, struct got_repository *repo) { *nremotes = repo->ngitconfig_remotes; *remotes = repo->gitconfig_remotes; } static int is_git_repo(struct got_repository *repo) { const char *path_git = got_repo_get_path_git_dir(repo); char *path_objects = got_repo_get_path_objects(repo); char *path_refs = got_repo_get_path_refs(repo); char *path_head = get_path_head(repo); int ret = 0; struct stat sb; struct got_reference *head_ref; if (lstat(path_git, &sb) == -1) goto done; if (!S_ISDIR(sb.st_mode)) goto done; if (lstat(path_objects, &sb) == -1) goto done; if (!S_ISDIR(sb.st_mode)) goto done; if (lstat(path_refs, &sb) == -1) goto done; if (!S_ISDIR(sb.st_mode)) goto done; if (lstat(path_head, &sb) == -1) goto done; if (!S_ISREG(sb.st_mode)) goto done; /* Check if the HEAD reference can be opened. */ if (got_ref_open(&head_ref, repo, GOT_REF_HEAD, 0) != NULL) goto done; got_ref_close(head_ref); ret = 1; done: free(path_objects); free(path_refs); free(path_head); return ret; } static const struct got_error * close_tempfiles(int *fds, size_t nfds) { const struct got_error *err = NULL; int i; for (i = 0; i < nfds; i++) { if (fds[i] == -1) continue; if (close(fds[i]) == -1) { err = got_error_from_errno("close"); break; } } free(fds); return err; } static const struct got_error * open_tempfiles(int **fds, size_t array_size, size_t nfds) { const struct got_error *err = NULL; int i; *fds = calloc(array_size, sizeof(**fds)); if (*fds == NULL) return got_error_from_errno("calloc"); for (i = 0; i < array_size; i++) (*fds)[i] = -1; for (i = 0; i < nfds; i++) { (*fds)[i] = got_opentempfd(); if ((*fds)[i] == -1) { err = got_error_from_errno("got_opentempfd"); close_tempfiles(*fds, nfds); *fds = NULL; return err; } } return NULL; } static const struct got_error * get_pack_cache_size(int *pack_cache_size) { struct rlimit rl; if (getrlimit(RLIMIT_NOFILE, &rl) == -1) return got_error_from_errno("getrlimit"); *pack_cache_size = GOT_PACK_CACHE_SIZE; if (*pack_cache_size > rl.rlim_cur / 8) *pack_cache_size = rl.rlim_cur / 8; return NULL; } const struct got_error * got_repo_pack_fds_open(int **pack_fds) { const struct got_error *err; int nfds; err = get_pack_cache_size(&nfds); if (err) return err; /* * We need one basefd and one accumfd per cached pack. * Our constants should be set up in a way such that * this error never triggers. */ if (nfds * 2 > GOT_PACK_NUM_TEMPFILES) return got_error(GOT_ERR_NO_SPACE); return open_tempfiles(pack_fds, GOT_PACK_NUM_TEMPFILES, nfds * 2); } const struct got_error * got_repo_pack_fds_close(int *pack_fds) { return close_tempfiles(pack_fds, GOT_PACK_NUM_TEMPFILES); } const struct got_error * got_repo_temp_fds_open(int **temp_fds) { return open_tempfiles(temp_fds, GOT_REPO_NUM_TEMPFILES, GOT_REPO_NUM_TEMPFILES); } void got_repo_temp_fds_set(struct got_repository *repo, int *temp_fds) { int i; for (i = 0; i < GOT_REPO_NUM_TEMPFILES; i++) repo->tempfiles[i] = temp_fds[i]; } const struct got_error * got_repo_temp_fds_get(int *fd, int *idx, struct got_repository *repo) { int i; *fd = -1; *idx = -1; for (i = 0; i < nitems(repo->tempfiles); i++) { if (repo->tempfile_use_mask & (1 << i)) continue; if (repo->tempfiles[i] != -1) { if (ftruncate(repo->tempfiles[i], 0L) == -1) return got_error_from_errno("ftruncate"); *fd = repo->tempfiles[i]; *idx = i; repo->tempfile_use_mask |= (1 << i); return NULL; } } return got_error(GOT_ERR_REPO_TEMPFILE); } void got_repo_temp_fds_put(int idx, struct got_repository *repo) { repo->tempfile_use_mask &= ~(1 << idx); } const struct got_error * got_repo_temp_fds_close(int *temp_fds) { return close_tempfiles(temp_fds, GOT_REPO_NUM_TEMPFILES); } const struct got_error * got_repo_cache_object(struct got_repository *repo, struct got_object_id *id, struct got_object *obj) { #ifndef GOT_NO_OBJ_CACHE const struct got_error *err = NULL; err = got_object_cache_add(&repo->objcache, id, obj); if (err) { if (err->code == GOT_ERR_OBJ_EXISTS || err->code == GOT_ERR_OBJ_TOO_LARGE) err = NULL; return err; } obj->refcnt++; #endif return NULL; } struct got_object * got_repo_get_cached_object(struct got_repository *repo, struct got_object_id *id) { return (struct got_object *)got_object_cache_get(&repo->objcache, id); } const struct got_error * got_repo_cache_tree(struct got_repository *repo, struct got_object_id *id, struct got_tree_object *tree) { #ifndef GOT_NO_OBJ_CACHE const struct got_error *err = NULL; err = got_object_cache_add(&repo->treecache, id, tree); if (err) { if (err->code == GOT_ERR_OBJ_EXISTS || err->code == GOT_ERR_OBJ_TOO_LARGE) err = NULL; return err; } tree->refcnt++; #endif return NULL; } struct got_tree_object * got_repo_get_cached_tree(struct got_repository *repo, struct got_object_id *id) { return (struct got_tree_object *)got_object_cache_get( &repo->treecache, id); } const struct got_error * got_repo_cache_commit(struct got_repository *repo, struct got_object_id *id, struct got_commit_object *commit) { #ifndef GOT_NO_OBJ_CACHE const struct got_error *err = NULL; err = got_object_cache_add(&repo->commitcache, id, commit); if (err) { if (err->code == GOT_ERR_OBJ_EXISTS || err->code == GOT_ERR_OBJ_TOO_LARGE) err = NULL; return err; } commit->refcnt++; #endif return NULL; } struct got_commit_object * got_repo_get_cached_commit(struct got_repository *repo, struct got_object_id *id) { return (struct got_commit_object *)got_object_cache_get( &repo->commitcache, id); } const struct got_error * got_repo_cache_tag(struct got_repository *repo, struct got_object_id *id, struct got_tag_object *tag) { #ifndef GOT_NO_OBJ_CACHE const struct got_error *err = NULL; err = got_object_cache_add(&repo->tagcache, id, tag); if (err) { if (err->code == GOT_ERR_OBJ_EXISTS || err->code == GOT_ERR_OBJ_TOO_LARGE) err = NULL; return err; } tag->refcnt++; #endif return NULL; } struct got_tag_object * got_repo_get_cached_tag(struct got_repository *repo, struct got_object_id *id) { return (struct got_tag_object *)got_object_cache_get( &repo->tagcache, id); } const struct got_error * got_repo_cache_raw_object(struct got_repository *repo, struct got_object_id *id, struct got_raw_object *raw) { #ifndef GOT_NO_OBJ_CACHE const struct got_error *err = NULL; err = got_object_cache_add(&repo->rawcache, id, raw); if (err) { if (err->code == GOT_ERR_OBJ_EXISTS || err->code == GOT_ERR_OBJ_TOO_LARGE) err = NULL; return err; } raw->refcnt++; #endif return NULL; } struct got_raw_object * got_repo_get_cached_raw_object(struct got_repository *repo, struct got_object_id *id) { return (struct got_raw_object *)got_object_cache_get(&repo->rawcache, id); } static const struct got_error * open_repo(struct got_repository *repo, const char *path) { const struct got_error *err = NULL; repo->gitdir_fd = -1; /* bare git repository? */ repo->path_git_dir = strdup(path); if (repo->path_git_dir == NULL) return got_error_from_errno("strdup"); if (is_git_repo(repo)) { repo->path = strdup(repo->path_git_dir); if (repo->path == NULL) { err = got_error_from_errno("strdup"); goto done; } repo->gitdir_fd = open(repo->path_git_dir, O_DIRECTORY | O_CLOEXEC); if (repo->gitdir_fd == -1) { err = got_error_from_errno2("open", repo->path_git_dir); goto done; } return NULL; } /* git repository with working tree? */ free(repo->path_git_dir); repo->path_git_dir = NULL; if (asprintf(&repo->path_git_dir, "%s/%s", path, GOT_GIT_DIR) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (is_git_repo(repo)) { repo->path = strdup(path); if (repo->path == NULL) { err = got_error_from_errno("strdup"); goto done; } repo->gitdir_fd = open(repo->path_git_dir, O_DIRECTORY | O_CLOEXEC); if (repo->gitdir_fd == -1) { err = got_error_from_errno2("open", repo->path_git_dir); goto done; } return NULL; } err = got_error(GOT_ERR_NOT_GIT_REPO); done: if (err) { free(repo->path); repo->path = NULL; free(repo->path_git_dir); repo->path_git_dir = NULL; if (repo->gitdir_fd != -1) close(repo->gitdir_fd); repo->gitdir_fd = -1; } return err; } static const struct got_error * read_gitconfig(struct got_repository *repo, const char *global_gitconfig_path) { const struct got_error *err = NULL; char *repo_gitconfig_path = NULL; if (global_gitconfig_path) { /* Read settings from ~/.gitconfig. */ int dummy_repo_version; err = got_repo_read_gitconfig(&dummy_repo_version, &repo->global_gitconfig_author_name, &repo->global_gitconfig_author_email, NULL, NULL, NULL, NULL, NULL, NULL, global_gitconfig_path); if (err) return err; } /* Read repository's .git/config file. */ repo_gitconfig_path = got_repo_get_path_gitconfig(repo); if (repo_gitconfig_path == NULL) return got_error_from_errno("got_repo_get_path_gitconfig"); err = got_repo_read_gitconfig( &repo->gitconfig_repository_format_version, &repo->gitconfig_author_name, &repo->gitconfig_author_email, &repo->gitconfig_remotes, &repo->ngitconfig_remotes, &repo->gitconfig_owner, &repo->extnames, &repo->extvals, &repo->nextensions, repo_gitconfig_path); if (err) goto done; if (getenv("GOT_IGNORE_GITCONFIG") != NULL) { int i; for (i = 0; i < repo->ngitconfig_remotes; i++) { got_repo_free_remote_repo_data( &repo->gitconfig_remotes[i]); } free(repo->gitconfig_remotes); repo->gitconfig_remotes = NULL; repo->ngitconfig_remotes = 0; free(repo->gitconfig_author_name); repo->gitconfig_author_name = NULL; free(repo->gitconfig_author_email); repo->gitconfig_author_email = NULL; free(repo->global_gitconfig_author_name); repo->global_gitconfig_author_name = NULL; free(repo->global_gitconfig_author_email); repo->global_gitconfig_author_email = NULL; } done: free(repo_gitconfig_path); return err; } static const struct got_error * read_gotconfig(struct got_repository *repo) { const struct got_error *err = NULL; char *gotconfig_path; gotconfig_path = got_repo_get_path_gotconfig(repo); if (gotconfig_path == NULL) return got_error_from_errno("got_repo_get_path_gotconfig"); err = got_gotconfig_read(&repo->gotconfig, gotconfig_path); free(gotconfig_path); return err; } /* Supported repository format extensions. */ static const char *const repo_extensions[] = { "noop", /* Got supports repository format version 1. */ "preciousObjects", /* Supported by gotadmin cleanup. */ "worktreeConfig", /* Got does not care about Git work trees. */ }; const struct got_error * got_repo_open(struct got_repository **repop, const char *path, const char *global_gitconfig_path, int *pack_fds) { struct got_repository *repo = NULL; const struct got_error *err = NULL; char *repo_path = NULL; size_t i, j = 0; *repop = NULL; repo = calloc(1, sizeof(*repo)); if (repo == NULL) return got_error_from_errno("calloc"); RB_INIT(&repo->packidx_bloom_filters); RB_INIT(&repo->packidx_paths); for (i = 0; i < nitems(repo->privsep_children); i++) { memset(&repo->privsep_children[i], 0, sizeof(repo->privsep_children[0])); repo->privsep_children[i].imsg_fd = -1; } err = got_object_cache_init(&repo->objcache, GOT_OBJECT_CACHE_TYPE_OBJ); if (err) goto done; err = got_object_cache_init(&repo->treecache, GOT_OBJECT_CACHE_TYPE_TREE); if (err) goto done; err = got_object_cache_init(&repo->commitcache, GOT_OBJECT_CACHE_TYPE_COMMIT); if (err) goto done; err = got_object_cache_init(&repo->tagcache, GOT_OBJECT_CACHE_TYPE_TAG); if (err) goto done; err = got_object_cache_init(&repo->rawcache, GOT_OBJECT_CACHE_TYPE_RAW); if (err) goto done; err = get_pack_cache_size(&repo->pack_cache_size); if (err) goto done; for (i = 0; i < nitems(repo->packs); i++) { if (pack_fds != NULL && i < repo->pack_cache_size) { repo->packs[i].basefd = pack_fds[j++]; repo->packs[i].accumfd = pack_fds[j++]; } else { repo->packs[i].basefd = -1; repo->packs[i].accumfd = -1; } } for (i = 0; i < nitems(repo->tempfiles); i++) repo->tempfiles[i] = -1; repo->pinned_pack = -1; repo->pinned_packidx = -1; repo->pinned_pid = 0; repo_path = realpath(path, NULL); if (repo_path == NULL) { err = got_error_from_errno2("realpath", path); goto done; } for (;;) { char *parent_path; err = open_repo(repo, repo_path); if (err == NULL) break; if (err->code != GOT_ERR_NOT_GIT_REPO) goto done; if (repo_path[0] == '/' && repo_path[1] == '\0') { err = got_error(GOT_ERR_NOT_GIT_REPO); goto done; } err = got_path_dirname(&parent_path, repo_path); if (err) goto done; free(repo_path); repo_path = parent_path; } err = read_gotconfig(repo); if (err) goto done; err = read_gitconfig(repo, global_gitconfig_path); if (err) goto done; if (repo->gitconfig_repository_format_version > 1) { err = got_error_path(path, GOT_ERR_GIT_REPO_FORMAT); goto done; } for (i = 0; i < repo->nextensions; i++) { char *ext = repo->extnames[i]; char *val = repo->extvals[i]; int j, supported = 0; if (repo->gitconfig_repository_format_version == 1 && strcasecmp(ext, "objectformat") == 0) { if (strcmp(val, "sha1") == 0) continue; if (strcmp(val, "sha256") == 0) { repo->algo = GOT_HASH_SHA256; continue; } err = got_error_path(val, GOT_ERR_OBJECT_FORMAT); goto done; } if (!is_boolean_val(val)) { err = got_error_path(ext, GOT_ERR_GIT_REPO_EXT); goto done; } if (!get_boolean_val(val)) continue; for (j = 0; j < nitems(repo_extensions); j++) { if (strcmp(ext, repo_extensions[j]) == 0) { supported = 1; break; } } if (!supported) { err = got_error_path(ext, GOT_ERR_GIT_REPO_EXT); goto done; } } err = got_repo_list_packidx(&repo->packidx_paths, repo); done: if (err) got_repo_close(repo); else *repop = repo; free(repo_path); return err; } const struct got_error * got_repo_close(struct got_repository *repo) { const struct got_error *err = NULL, *child_err; struct got_packidx_bloom_filter *bf; size_t i; for (i = 0; i < repo->pack_cache_size; i++) { if (repo->packidx_cache[i] == NULL) break; got_packidx_close(repo->packidx_cache[i]); } while ((bf = RB_MIN(got_packidx_bloom_filter_tree, &repo->packidx_bloom_filters))) { RB_REMOVE(got_packidx_bloom_filter_tree, &repo->packidx_bloom_filters, bf); bloom_free(bf->bloom); free(bf->bloom); free(bf); } for (i = 0; i < repo->pack_cache_size; i++) if (repo->packs[i].path_packfile) if (repo->packs[i].path_packfile) got_pack_close(&repo->packs[i]); free(repo->path); free(repo->path_git_dir); got_object_cache_close(&repo->objcache); got_object_cache_close(&repo->treecache); got_object_cache_close(&repo->commitcache); got_object_cache_close(&repo->tagcache); got_object_cache_close(&repo->rawcache); for (i = 0; i < nitems(repo->privsep_children); i++) { if (repo->privsep_children[i].imsg_fd == -1) continue; imsgbuf_clear(repo->privsep_children[i].ibuf); free(repo->privsep_children[i].ibuf); err = got_privsep_send_stop(repo->privsep_children[i].imsg_fd); if (err && err->code == GOT_ERR_EOF) err = NULL; child_err = got_privsep_wait_for_child( repo->privsep_children[i].pid); if (child_err && err == NULL) err = child_err; if (close(repo->privsep_children[i].imsg_fd) == -1 && err == NULL) err = got_error_from_errno("close"); } if (repo->gitdir_fd != -1 && close(repo->gitdir_fd) == -1 && err == NULL) err = got_error_from_errno("close"); if (repo->gotconfig) got_gotconfig_free(repo->gotconfig); free(repo->gitconfig_author_name); free(repo->gitconfig_author_email); for (i = 0; i < repo->ngitconfig_remotes; i++) got_repo_free_remote_repo_data(&repo->gitconfig_remotes[i]); free(repo->gitconfig_remotes); for (i = 0; i < repo->nextensions; i++) { free(repo->extnames[i]); free(repo->extvals[i]); } free(repo->extnames); free(repo->extvals); got_pathlist_free(&repo->packidx_paths, GOT_PATHLIST_FREE_PATH); free(repo); return err; } const struct got_error * got_repo_remote_repo_dup(struct got_remote_repo **newp, const struct got_remote_repo *repo) { const struct got_error *err = NULL; struct got_remote_repo *new; int i; new = calloc(1, sizeof(*new)); if (new == NULL) return got_error_from_errno("calloc"); if (repo->name) { new->name = strdup(repo->name); if (new->name == NULL) { err = got_error_from_errno("strdup"); goto done; } } if (repo->fetch_url) { new->fetch_url = strdup(repo->fetch_url); if (new->fetch_url == NULL) { err = got_error_from_errno("strdup"); goto done; } } if (repo->send_url) { new->send_url = strdup(repo->send_url); if (new->send_url == NULL) { err = got_error_from_errno("strdup"); goto done; } } new->mirror_references = repo->mirror_references; new->fetch_all_branches = repo->fetch_all_branches; new->nfetch_branches = repo->nfetch_branches; if (repo->fetch_branches) { new->fetch_branches = calloc(repo->nfetch_branches, sizeof(char *)); if (new->fetch_branches == NULL) { err = got_error_from_errno("calloc"); goto done; } for (i = 0; i < repo->nfetch_branches; i++) { new->fetch_branches[i] = strdup( repo->fetch_branches[i]); if (new->fetch_branches[i] == NULL) { err = got_error_from_errno("strdup"); goto done; } } } new->nsend_branches = repo->nsend_branches; if (repo->send_branches) { new->send_branches = calloc(repo->nsend_branches, sizeof(char *)); if (new->send_branches == NULL) { err = got_error_from_errno("calloc"); goto done; } for (i = 0; i < repo->nsend_branches; i++) { new->send_branches[i] = strdup( repo->send_branches[i]); if (new->send_branches[i] == NULL) { err = got_error_from_errno("strdup"); goto done; } } } new->nfetch_refs = repo->nfetch_refs; if (repo->fetch_refs) { new->fetch_refs = calloc(repo->nfetch_refs, sizeof(char *)); if (new->fetch_refs == NULL) { err = got_error_from_errno("calloc"); goto done; } for (i = 0; i < repo->nfetch_refs; i++) { new->fetch_refs[i] = strdup( repo->fetch_refs[i]); if (new->fetch_refs[i] == NULL) { err = got_error_from_errno("strdup"); goto done; } } } done: if (err) { got_repo_free_remote_repo_data(new); free(new); } else *newp = new; return err; } void got_repo_free_remote_repo_data(struct got_remote_repo *repo) { int i; if (repo == NULL) return; free(repo->name); repo->name = NULL; free(repo->fetch_url); repo->fetch_url = NULL; free(repo->send_url); repo->send_url = NULL; for (i = 0; i < repo->nfetch_branches; i++) free(repo->fetch_branches[i]); free(repo->fetch_branches); repo->fetch_branches = NULL; repo->nfetch_branches = 0; for (i = 0; i < repo->nsend_branches; i++) free(repo->send_branches[i]); free(repo->send_branches); repo->send_branches = NULL; repo->nsend_branches = 0; for (i = 0; i < repo->nfetch_refs; i++) free(repo->fetch_refs[i]); free(repo->fetch_refs); repo->fetch_refs = NULL; repo->nfetch_refs = 0; } const struct got_error * got_repo_map_path(char **in_repo_path, struct got_repository *repo, const char *input_path) { const struct got_error *err = NULL; const char *repo_abspath = NULL; size_t repolen, len; char *canonpath, *path = NULL; *in_repo_path = NULL; canonpath = strdup(input_path); if (canonpath == NULL) { err = got_error_from_errno("strdup"); goto done; } err = got_canonpath(input_path, canonpath, strlen(canonpath) + 1); if (err) goto done; repo_abspath = got_repo_get_path(repo); if (canonpath[0] == '\0') { path = strdup(canonpath); if (path == NULL) { err = got_error_from_errno("strdup"); goto done; } } else { path = realpath(canonpath, NULL); if (path == NULL) { if (errno != ENOENT) { err = got_error_from_errno2("realpath", canonpath); goto done; } /* * Path is not on disk. * Assume it is already relative to repository root. */ path = strdup(canonpath); if (path == NULL) { err = got_error_from_errno("strdup"); goto done; } } repolen = strlen(repo_abspath); len = strlen(path); if (strcmp(path, repo_abspath) == 0) { free(path); path = strdup(""); if (path == NULL) { err = got_error_from_errno("strdup"); goto done; } } else if (len > repolen && got_path_is_child(path, repo_abspath, repolen)) { /* Matched an on-disk path inside repository. */ if (got_repo_is_bare(repo)) { /* * Matched an on-disk path inside repository * database. Treat input as repository-relative. */ free(path); path = canonpath; canonpath = NULL; } else { char *child; /* Strip common prefix with repository path. */ err = got_path_skip_common_ancestor(&child, repo_abspath, path); if (err) goto done; free(path); path = child; } } else { /* * Matched unrelated on-disk path. * Treat input as repository-relative. */ free(path); path = canonpath; canonpath = NULL; } } /* Make in-repository path absolute */ if (path[0] != '/') { char *abspath; if (asprintf(&abspath, "/%s", path) == -1) { err = got_error_from_errno("asprintf"); goto done; } free(path); path = abspath; } done: free(canonpath); if (err) free(path); else *in_repo_path = path; return err; } static const struct got_error * cache_packidx(struct got_repository *repo, struct got_packidx *packidx, const char *path_packidx) { const struct got_error *err = NULL; size_t i; for (i = 0; i < repo->pack_cache_size; i++) { if (repo->packidx_cache[i] == NULL) break; if (strcmp(repo->packidx_cache[i]->path_packidx, path_packidx) == 0) { return got_error(GOT_ERR_CACHE_DUP_ENTRY); } } if (i == repo->pack_cache_size) { do { i--; } while (i > 0 && repo->pinned_packidx >= 0 && i == repo->pinned_packidx); err = got_packidx_close(repo->packidx_cache[i]); if (err) return err; } repo->packidx_cache[i] = packidx; return NULL; } int got_repo_is_packidx_filename(const char *name, size_t len, enum got_hash_algorithm algo) { size_t digest_string_len; digest_string_len = got_hash_digest_string_length(algo); if (len != GOT_PACKIDX_NAMELEN(digest_string_len)) return 0; if (strncmp(name, GOT_PACK_PREFIX, strlen(GOT_PACK_PREFIX)) != 0) return 0; if (strcmp(name + strlen(GOT_PACK_PREFIX) + digest_string_len - 1, GOT_PACKIDX_SUFFIX) != 0) return 0; return 1; } static struct got_packidx_bloom_filter * get_packidx_bloom_filter(struct got_repository *repo, const char *path, size_t path_len) { struct got_packidx_bloom_filter key; if (strlcpy(key.path, path, sizeof(key.path)) >= sizeof(key.path)) return NULL; /* XXX */ key.path_len = path_len; return RB_FIND(got_packidx_bloom_filter_tree, &repo->packidx_bloom_filters, &key); } int got_repo_check_packidx_bloom_filter(struct got_repository *repo, const char *path_packidx, struct got_object_id *id) { struct got_packidx_bloom_filter *bf; bf = get_packidx_bloom_filter(repo, path_packidx, strlen(path_packidx)); if (bf) return bloom_check(bf->bloom, id->hash, got_hash_digest_length(id->algo)); /* No bloom filter means this pack index must be searched. */ return 1; } static const struct got_error * add_packidx_bloom_filter(struct got_repository *repo, struct got_packidx *packidx, const char *path_packidx) { int i, nobjects = be32toh(packidx->hdr.fanout_table[0xff]); struct got_packidx_bloom_filter *bf; size_t len, digest_len; digest_len = got_hash_digest_length(repo->algo); /* * Don't use bloom filters for very large pack index files. * Large pack files will contain a relatively large fraction * of our objects so we will likely need to visit them anyway. * The more objects a pack file contains the higher the probability * of a false-positive match from the bloom filter. And reading * all object IDs from a large pack index file can be expensive. */ if (nobjects > 100000) /* cut-off at about 2MB, at 20 bytes per ID */ return NULL; /* Do we already have a filter for this pack index? */ if (get_packidx_bloom_filter(repo, path_packidx, strlen(path_packidx)) != NULL) return NULL; bf = calloc(1, sizeof(*bf)); if (bf == NULL) return got_error_from_errno("calloc"); bf->bloom = calloc(1, sizeof(*bf->bloom)); if (bf->bloom == NULL) { free(bf); return got_error_from_errno("calloc"); } len = strlcpy(bf->path, path_packidx, sizeof(bf->path)); if (len >= sizeof(bf->path)) { free(bf->bloom); free(bf); return got_error(GOT_ERR_NO_SPACE); } bf->path_len = len; /* Minimum size supported by our bloom filter is 1000 entries. */ bloom_init(bf->bloom, nobjects < 1000 ? 1000 : nobjects, 0.1); for (i = 0; i < nobjects; i++) { uint8_t *id = packidx->hdr.sorted_ids + i * digest_len; bloom_add(bf->bloom, id, digest_len); } RB_INSERT(got_packidx_bloom_filter_tree, &repo->packidx_bloom_filters, bf); return NULL; } static void purge_packidx_paths(struct got_pathlist_head *packidx_paths) { struct got_pathlist_entry *pe; while (!RB_EMPTY(packidx_paths)) { pe = RB_MIN(got_pathlist_head, packidx_paths); RB_REMOVE(got_pathlist_head, packidx_paths, pe); free((char *)pe->path); free(pe); } } static const struct got_error * refresh_packidx_paths(struct got_repository *repo) { const struct got_error *err = NULL; char *objects_pack_dir = NULL; struct stat sb; objects_pack_dir = got_repo_get_path_objects_pack(repo); if (objects_pack_dir == NULL) return got_error_from_errno("got_repo_get_path_objects_pack"); if (stat(objects_pack_dir, &sb) == -1) { if (errno != ENOENT) { err = got_error_from_errno2("stat", objects_pack_dir); goto done; } } else if (RB_EMPTY(&repo->packidx_paths) || sb.st_mtim.tv_sec != repo->pack_path_mtime.tv_sec || sb.st_mtim.tv_nsec != repo->pack_path_mtime.tv_nsec) { purge_packidx_paths(&repo->packidx_paths); err = got_repo_list_packidx(&repo->packidx_paths, repo); if (err) goto done; } done: free(objects_pack_dir); return err; } const struct got_error * got_repo_search_packidx(struct got_packidx **packidx, int *idx, struct got_repository *repo, struct got_object_id *id) { const struct got_error *err; struct got_pathlist_entry *pe; size_t i; /* Search pack index cache. */ for (i = 0; i < repo->pack_cache_size; i++) { if (repo->packidx_cache[i] == NULL) break; if (!got_repo_check_packidx_bloom_filter(repo, repo->packidx_cache[i]->path_packidx, id)) continue; /* object will not be found in this index */ *idx = got_packidx_get_object_idx(repo->packidx_cache[i], id); if (*idx != -1) { *packidx = repo->packidx_cache[i]; /* * Move this cache entry to the front. Repeatedly * searching a wrong pack index can be expensive. */ if (i > 0) { memmove(&repo->packidx_cache[1], &repo->packidx_cache[0], i * sizeof(repo->packidx_cache[0])); repo->packidx_cache[0] = *packidx; if (repo->pinned_packidx >= 0 && repo->pinned_packidx < i) repo->pinned_packidx++; else if (repo->pinned_packidx == i) repo->pinned_packidx = 0; } return NULL; } } /* No luck. Search the filesystem. */ err = refresh_packidx_paths(repo); if (err) return err; RB_FOREACH(pe, got_pathlist_head, &repo->packidx_paths) { const char *path_packidx = pe->path; int is_cached = 0; if (!got_repo_check_packidx_bloom_filter(repo, pe->path, id)) continue; /* object will not be found in this index */ for (i = 0; i < repo->pack_cache_size; i++) { if (repo->packidx_cache[i] == NULL) break; if (strcmp(repo->packidx_cache[i]->path_packidx, path_packidx) == 0) { is_cached = 1; break; } } if (is_cached) continue; /* already searched */ err = got_packidx_open(packidx, got_repo_get_fd(repo), path_packidx, 0, repo->algo); if (err) { if (err->code == GOT_ERR_LONELY_PACKIDX) { err = NULL; continue; } goto done; } err = add_packidx_bloom_filter(repo, *packidx, path_packidx); if (err) goto done; err = cache_packidx(repo, *packidx, path_packidx); if (err) goto done; *idx = got_packidx_get_object_idx(*packidx, id); if (*idx != -1) { err = NULL; /* found the object */ goto done; } } err = got_error_no_obj(id); done: return err; } const struct got_error * got_repo_list_packidx(struct got_pathlist_head *packidx_paths, struct got_repository *repo) { const struct got_error *err = NULL; DIR *packdir = NULL; struct dirent *dent; char *path_packidx = NULL; int packdir_fd; struct stat sb; packdir_fd = openat(got_repo_get_fd(repo), GOT_OBJECTS_PACK_DIR, O_DIRECTORY | O_CLOEXEC); if (packdir_fd == -1) { return got_error_from_errno_fmt("openat: %s/%s", got_repo_get_path_git_dir(repo), GOT_OBJECTS_PACK_DIR); } packdir = fdopendir(packdir_fd); if (packdir == NULL) { err = got_error_from_errno("fdopendir"); close(packdir_fd); goto done; } if (fstat(packdir_fd, &sb) == -1) { err = got_error_from_errno("fstat"); goto done; } repo->pack_path_mtime.tv_sec = sb.st_mtim.tv_sec; repo->pack_path_mtime.tv_nsec = sb.st_mtim.tv_nsec; while ((dent = readdir(packdir)) != NULL) { if (!got_repo_is_packidx_filename(dent->d_name, strlen(dent->d_name), repo->algo)) continue; if (asprintf(&path_packidx, "%s/%s", GOT_OBJECTS_PACK_DIR, dent->d_name) == -1) { err = got_error_from_errno("asprintf"); path_packidx = NULL; break; } err = got_pathlist_insert(NULL, packidx_paths, path_packidx, NULL); if (err) break; } done: if (err) free(path_packidx); if (packdir && closedir(packdir) != 0 && err == NULL) err = got_error_from_errno("closedir"); return err; } const struct got_error * got_repo_get_packidx(struct got_packidx **packidx, const char *path_packidx, struct got_repository *repo) { const struct got_error *err; size_t i; *packidx = NULL; /* Search pack index cache. */ for (i = 0; i < repo->pack_cache_size; i++) { if (repo->packidx_cache[i] == NULL) break; if (strcmp(repo->packidx_cache[i]->path_packidx, path_packidx) == 0) { *packidx = repo->packidx_cache[i]; return NULL; } } /* No luck. Search the filesystem. */ err = got_packidx_open(packidx, got_repo_get_fd(repo), path_packidx, 0, repo->algo); if (err) return err; err = add_packidx_bloom_filter(repo, *packidx, path_packidx); if (err) goto done; err = cache_packidx(repo, *packidx, path_packidx); done: if (err) { got_packidx_close(*packidx); *packidx = NULL; } return err; } static const struct got_error * read_packfile_hdr(int fd, struct got_packidx *packidx) { const struct got_error *err = NULL; uint32_t totobj = be32toh(packidx->hdr.fanout_table[0xff]); struct got_packfile_hdr hdr; ssize_t n; n = read(fd, &hdr, sizeof(hdr)); if (n < 0) return got_error_from_errno("read"); if (n != sizeof(hdr)) return got_error(GOT_ERR_BAD_PACKFILE); if (be32toh(hdr.signature) != GOT_PACKFILE_SIGNATURE || be32toh(hdr.version) != GOT_PACKFILE_VERSION || be32toh(hdr.nobjects) != totobj) err = got_error(GOT_ERR_BAD_PACKFILE); return err; } static const struct got_error * open_packfile(int *fd, struct got_repository *repo, const char *relpath, struct got_packidx *packidx) { const struct got_error *err = NULL; *fd = openat(got_repo_get_fd(repo), relpath, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (*fd == -1) return got_error_from_errno_fmt("openat: %s/%s", got_repo_get_path_git_dir(repo), relpath); if (packidx) { err = read_packfile_hdr(*fd, packidx); if (err) { close(*fd); *fd = -1; } } return err; } const struct got_error * got_repo_cache_pack(struct got_pack **packp, struct got_repository *repo, const char *path_packfile, struct got_packidx *packidx) { const struct got_error *err = NULL; struct got_pack *pack = NULL; struct got_pack tmp; struct stat sb; size_t i; if (packp) *packp = NULL; for (i = 0; i < repo->pack_cache_size; i++) { pack = &repo->packs[i]; if (pack->path_packfile == NULL) break; if (strcmp(pack->path_packfile, path_packfile) == 0) return got_error(GOT_ERR_CACHE_DUP_ENTRY); } if (i == repo->pack_cache_size) { do { i--; } while (i > 0 && repo->pinned_pack >= 0 && i == repo->pinned_pack); err = got_pack_close(&repo->packs[i]); if (err) return err; if (ftruncate(repo->packs[i].basefd, 0L) == -1) return got_error_from_errno("ftruncate"); if (ftruncate(repo->packs[i].accumfd, 0L) == -1) return got_error_from_errno("ftruncate"); } if (i != 0) { memcpy(&tmp, &repo->packs[i], sizeof(tmp)); memcpy(&repo->packs[i], &repo->packs[0], sizeof(repo->packs[i])); memcpy(&repo->packs[0], &tmp, sizeof(repo->packs[0])); if (repo->pinned_pack == 0) repo->pinned_pack = i; else if (repo->pinned_pack == i) repo->pinned_pack = 0; i = 0; } pack = &repo->packs[i]; pack->path_packfile = strdup(path_packfile); if (pack->path_packfile == NULL) { err = got_error_from_errno("strdup"); goto done; } err = open_packfile(&pack->fd, repo, path_packfile, packidx); if (err) goto done; if (fstat(pack->fd, &sb) != 0) { err = got_error_from_errno("fstat"); goto done; } pack->filesize = sb.st_size; pack->algo = repo->algo; pack->privsep_child = NULL; err = got_delta_cache_alloc(&pack->delta_cache); if (err) goto done; #ifndef GOT_PACK_NO_MMAP if (pack->filesize > 0 && pack->filesize <= SIZE_MAX) { pack->map = mmap(NULL, pack->filesize, PROT_READ, MAP_PRIVATE, pack->fd, 0); if (pack->map == MAP_FAILED) { if (errno != ENOMEM) { err = got_error_from_errno("mmap"); goto done; } pack->map = NULL; /* fall back to read(2) */ } } #endif done: if (err) { if (pack) got_pack_close(pack); } else if (packp) *packp = pack; return err; } struct got_pack * got_repo_get_cached_pack(struct got_repository *repo, const char *path_packfile) { struct got_pack *pack = NULL; size_t i; for (i = 0; i < repo->pack_cache_size; i++) { pack = &repo->packs[i]; if (pack->path_packfile == NULL) break; if (strcmp(pack->path_packfile, path_packfile) == 0) return pack; } return NULL; } const struct got_error * got_repo_pin_pack(struct got_repository *repo, struct got_packidx *packidx, struct got_pack *pack) { size_t i; int pinned_pack = -1, pinned_packidx = -1; for (i = 0; i < repo->pack_cache_size; i++) { if (repo->packidx_cache[i] && strcmp(repo->packidx_cache[i]->path_packidx, packidx->path_packidx) == 0) pinned_packidx = i; if (repo->packs[i].path_packfile && strcmp(repo->packs[i].path_packfile, pack->path_packfile) == 0) pinned_pack = i; } if (pinned_packidx == -1 || pinned_pack == -1) return got_error(GOT_ERR_PIN_PACK); repo->pinned_pack = pinned_pack; repo->pinned_packidx = pinned_packidx; if (repo->packs[pinned_pack].privsep_child) repo->pinned_pid = repo->packs[pinned_pack].privsep_child->pid; return NULL; } struct got_pack * got_repo_get_pinned_pack(struct got_repository *repo) { if (repo->pinned_pack >= 0 && repo->pinned_pack < repo->pack_cache_size) return &repo->packs[repo->pinned_pack]; return NULL; } void got_repo_unpin_pack(struct got_repository *repo) { repo->pinned_packidx = -1; repo->pinned_pack = -1; repo->pinned_pid = 0; } static const struct got_error * match_packed_object(struct got_object_id **unique_id, struct got_repository *repo, const char *id_str_prefix, int obj_type) { const struct got_error *err = NULL; struct got_object_id_queue matched_ids; struct got_pathlist_entry *pe; struct timespec tv; int retries = 0; const int max_retries = 10; STAILQ_INIT(&matched_ids); err = refresh_packidx_paths(repo); if (err) return err; /* * Opening objects while iterating over the pack-index path * list is racy. If the set of pack files in the repository * changes during loop iteration, refresh_packidx_paths() will * be called again, via got_object_get_type(), invalidating * the packidx_paths list we are iterating over. * To work around this we keep track of the current modification * time and retry the entire loop if it changes. */ retry: tv.tv_sec = repo->pack_path_mtime.tv_sec; tv.tv_nsec = repo->pack_path_mtime.tv_nsec; RB_FOREACH(pe, got_pathlist_head, &repo->packidx_paths) { const char *path_packidx; struct got_packidx *packidx; struct got_object_qid *qid; path_packidx = pe->path; err = got_packidx_open(&packidx, got_repo_get_fd(repo), path_packidx, 0, repo->algo); if (err) { if (err->code == GOT_ERR_LONELY_PACKIDX) { err = NULL; continue; } break; } got_object_id_queue_free(&matched_ids); err = got_packidx_match_id_str_prefix(&matched_ids, packidx, id_str_prefix); if (err) { got_packidx_close(packidx); break; } err = got_packidx_close(packidx); if (err) break; STAILQ_FOREACH(qid, &matched_ids, entry) { if (obj_type != GOT_OBJ_TYPE_ANY) { int matched_type; err = got_object_get_type(&matched_type, repo, &qid->id); if (err) goto done; /* * If the modification time of the * 'objects/pack' directory has changed then * 'pe' could now be an invalid pointer. */ if (tv.tv_sec != repo->pack_path_mtime.tv_sec || tv.tv_nsec != repo->pack_path_mtime.tv_nsec) { if (++retries > max_retries) { err = got_error_msg( GOT_ERR_TIMEOUT, "too many concurrent " "pack file modifications"); goto done; } goto retry; } if (matched_type != obj_type) continue; } if (*unique_id == NULL) { *unique_id = got_object_id_dup(&qid->id); if (*unique_id == NULL) { err = got_error_from_errno("malloc"); goto done; } } else { if (got_object_id_cmp(*unique_id, &qid->id) == 0) continue; /* packed multiple times */ err = got_error(GOT_ERR_AMBIGUOUS_ID); goto done; } } } done: got_object_id_queue_free(&matched_ids); if (err) { free(*unique_id); *unique_id = NULL; } return err; } static const struct got_error * match_loose_object(struct got_object_id **unique_id, const char *path_objects, const char *object_dir, const char *id_str_prefix, int obj_type, struct got_repository *repo) { const struct got_error *err = NULL; char *path, *id_str = NULL; DIR *dir = NULL; struct dirent *dent; struct got_object_id id; if (asprintf(&path, "%s/%s", path_objects, object_dir) == -1) { err = got_error_from_errno("asprintf"); goto done; } dir = opendir(path); if (dir == NULL) { if (errno == ENOENT) { err = NULL; goto done; } err = got_error_from_errno2("opendir", path); goto done; } while ((dent = readdir(dir)) != NULL) { int cmp; free(id_str); id_str = NULL; if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0) continue; if (asprintf(&id_str, "%s%s", object_dir, dent->d_name) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (!got_parse_object_id(&id, id_str, repo->algo)) continue; /* * Directory entries do not necessarily appear in * sorted order, so we must iterate over all of them. */ cmp = strncmp(id_str, id_str_prefix, strlen(id_str_prefix)); if (cmp != 0) continue; if (*unique_id == NULL) { if (obj_type != GOT_OBJ_TYPE_ANY) { int matched_type; err = got_object_get_type(&matched_type, repo, &id); if (err) goto done; if (matched_type != obj_type) continue; } *unique_id = got_object_id_dup(&id); if (*unique_id == NULL) { err = got_error_from_errno("got_object_id_dup"); goto done; } } else { if (got_object_id_cmp(*unique_id, &id) == 0) continue; /* both packed and loose */ err = got_error(GOT_ERR_AMBIGUOUS_ID); goto done; } } done: if (dir && closedir(dir) != 0 && err == NULL) err = got_error_from_errno("closedir"); if (err) { free(*unique_id); *unique_id = NULL; } free(id_str); free(path); return err; } const struct got_error * got_repo_match_object_id_prefix(struct got_object_id **id, const char *id_str_prefix, int obj_type, struct got_repository *repo) { const struct got_error *err = NULL; char *path_objects = NULL, *object_dir = NULL; size_t len, digest_string_len; int i; *id = NULL; path_objects = got_repo_get_path_objects(repo); digest_string_len = got_hash_digest_string_length(repo->algo); len = strlen(id_str_prefix); if (len > digest_string_len - 1) { err = got_error_path(id_str_prefix, GOT_ERR_BAD_OBJ_ID_STR); goto done; } for (i = 0; i < len; i++) { if (isxdigit((unsigned char)id_str_prefix[i])) continue; err = got_error_path(id_str_prefix, GOT_ERR_BAD_OBJ_ID_STR); goto done; } if (len >= 2) { err = match_packed_object(id, repo, id_str_prefix, obj_type); if (err) goto done; object_dir = strndup(id_str_prefix, 2); if (object_dir == NULL) { err = got_error_from_errno("strdup"); goto done; } err = match_loose_object(id, path_objects, object_dir, id_str_prefix, obj_type, repo); } else if (len == 1) { int i; for (i = 0; i < 0xf; i++) { if (asprintf(&object_dir, "%s%.1x", id_str_prefix, i) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = match_packed_object(id, repo, object_dir, obj_type); if (err) goto done; err = match_loose_object(id, path_objects, object_dir, id_str_prefix, obj_type, repo); if (err) goto done; } } else { err = got_error_path(id_str_prefix, GOT_ERR_BAD_OBJ_ID_STR); goto done; } done: free(path_objects); free(object_dir); if (err) { free(*id); *id = NULL; } else if (*id == NULL) { switch (obj_type) { case GOT_OBJ_TYPE_BLOB: err = got_error_fmt(GOT_ERR_NO_OBJ, "%s %s", GOT_OBJ_LABEL_BLOB, id_str_prefix); break; case GOT_OBJ_TYPE_TREE: err = got_error_fmt(GOT_ERR_NO_OBJ, "%s %s", GOT_OBJ_LABEL_TREE, id_str_prefix); break; case GOT_OBJ_TYPE_COMMIT: err = got_error_fmt(GOT_ERR_NO_OBJ, "%s %s", GOT_OBJ_LABEL_COMMIT, id_str_prefix); break; case GOT_OBJ_TYPE_TAG: err = got_error_fmt(GOT_ERR_NO_OBJ, "%s %s", GOT_OBJ_LABEL_TAG, id_str_prefix); break; default: err = got_error_path(id_str_prefix, GOT_ERR_NO_OBJ); break; } } return err; } const struct got_error * got_repo_match_object_id(struct got_object_id **id, char **label, const char *id_str, int obj_type, struct got_reflist_head *refs, struct got_repository *repo) { const struct got_error *err; struct got_tag_object *tag; struct got_reference *ref = NULL; *id = NULL; if (label) *label = NULL; if (refs) { err = got_repo_object_match_tag(&tag, id_str, obj_type, refs, repo); if (err == NULL) { *id = got_object_id_dup( got_object_tag_get_object_id(tag)); if (*id == NULL) err = got_error_from_errno("got_object_id_dup"); else if (label && asprintf(label, "refs/tags/%s", got_object_tag_get_name(tag)) == -1) { err = got_error_from_errno("asprintf"); free(*id); *id = NULL; } got_object_tag_close(tag); return err; } else if (err->code != GOT_ERR_OBJ_TYPE && err->code != GOT_ERR_NO_OBJ) return err; } err = got_ref_open(&ref, repo, id_str, 0); if (err == NULL) { err = got_ref_resolve(id, repo, ref); if (err) goto done; if (label) { *label = strdup(got_ref_get_name(ref)); if (*label == NULL) { err = got_error_from_errno("strdup"); goto done; } } } else { if (err->code != GOT_ERR_NOT_REF && err->code != GOT_ERR_BAD_REF_NAME) goto done; err = got_repo_match_object_id_prefix(id, id_str, obj_type, repo); if (err) { if (err->code == GOT_ERR_BAD_OBJ_ID_STR) err = got_error_not_ref(id_str); goto done; } if (label) { err = got_object_id_str(label, *id); if (*label == NULL) { err = got_error_from_errno("strdup"); goto done; } } } done: if (ref) got_ref_close(ref); return err; } const struct got_error * got_repo_object_match_tag(struct got_tag_object **tag, const char *name, int obj_type, struct got_reflist_head *refs, struct got_repository *repo) { const struct got_error *err = NULL; struct got_reflist_entry *re; struct got_object_id *tag_id; int name_is_absolute = (strncmp(name, "refs/", 5) == 0); *tag = NULL; TAILQ_FOREACH(re, refs, entry) { const char *refname; refname = got_ref_get_name(re->ref); if (got_ref_is_symbolic(re->ref)) continue; if (strncmp(refname, "refs/tags/", 10) != 0) continue; if (!name_is_absolute) refname += strlen("refs/tags/"); if (strcmp(refname, name) != 0) continue; err = got_ref_resolve(&tag_id, repo, re->ref); if (err) break; err = got_object_open_as_tag(tag, repo, tag_id); free(tag_id); if (err) break; if (obj_type == GOT_OBJ_TYPE_ANY || got_object_tag_get_object_type(*tag) == obj_type) break; got_object_tag_close(*tag); *tag = NULL; } if (err == NULL && *tag == NULL) err = got_error_fmt(GOT_ERR_NO_OBJ, "%s %s", GOT_OBJ_LABEL_TAG, name); return err; } const struct got_error * got_repo_find_object_id(struct got_object_id *id, struct got_repository *repo) { const struct got_error *err; struct got_object_id *matched_id = NULL; char *id_str = NULL; err = got_object_id_str(&id_str, id); if (err) return err; err = got_repo_match_object_id_prefix(&matched_id, id_str, GOT_OBJ_TYPE_ANY, repo); free(matched_id); free(id_str); return err; } static const struct got_error * alloc_added_blob_tree_entry(struct got_tree_entry **new_te, const char *name, mode_t mode, struct got_object_id *blob_id) { const struct got_error *err = NULL; *new_te = NULL; *new_te = calloc(1, sizeof(**new_te)); if (*new_te == NULL) return got_error_from_errno("calloc"); if (strlcpy((*new_te)->name, name, sizeof((*new_te)->name)) >= sizeof((*new_te)->name)) { err = got_error(GOT_ERR_NO_SPACE); goto done; } if (S_ISLNK(mode)) { (*new_te)->mode = S_IFLNK; } else { (*new_te)->mode = S_IFREG; (*new_te)->mode |= (mode & (S_IRWXU | S_IRWXG | S_IRWXO)); } memcpy(&(*new_te)->id, blob_id, sizeof((*new_te)->id)); done: if (err && *new_te) { free(*new_te); *new_te = NULL; } return err; } static const struct got_error * import_file(struct got_tree_entry **new_te, struct dirent *de, const char *path, struct got_repository *repo) { const struct got_error *err; struct got_object_id *blob_id = NULL; char *filepath; struct stat sb; if (asprintf(&filepath, "%s%s%s", path, path[0] == '\0' ? "" : "/", de->d_name) == -1) return got_error_from_errno("asprintf"); if (lstat(filepath, &sb) != 0) { err = got_error_from_errno2("lstat", path); goto done; } err = got_object_blob_create(&blob_id, filepath, repo); if (err) goto done; err = alloc_added_blob_tree_entry(new_te, de->d_name, sb.st_mode, blob_id); done: free(filepath); if (err) free(blob_id); return err; } static const struct got_error * insert_dir_entry(struct dirent *de, long pos, struct got_pathlist_head *dirents) { const struct got_error *err = NULL; struct got_pathlist_entry *new_pe; char *d_name; long *ppos; d_name = strdup(de->d_name); if (d_name == NULL) return got_error_from_errno("strdup"); /* Record the offset to this dirent in struct DIR as path data. */ ppos = malloc(sizeof(*ppos)); if (ppos == NULL) { err = got_error_from_errno("malloc"); goto done; } *ppos = pos; err = got_pathlist_insert(&new_pe, dirents, d_name, ppos); if (err) goto done; if (new_pe == NULL) err = got_error(GOT_ERR_TREE_DUP_ENTRY); done: if (err) { free(d_name); free(ppos); } return err; } static const struct got_error * insert_tree_entry(struct got_tree_entry *new_te, struct got_pathlist_head *paths) { const struct got_error *err = NULL; struct got_pathlist_entry *new_pe; err = got_pathlist_insert(&new_pe, paths, new_te->name, new_te); if (err) return err; if (new_pe == NULL) return got_error(GOT_ERR_TREE_DUP_ENTRY); return NULL; } static const struct got_error *write_tree(struct got_object_id **, const char *, struct got_pathlist_head *, struct got_repository *, got_repo_import_cb progress_cb, void *progress_arg); static const struct got_error * import_subdir(struct got_tree_entry **new_te, struct dirent *de, const char *path, struct got_pathlist_head *ignores, struct got_repository *repo, got_repo_import_cb progress_cb, void *progress_arg) { const struct got_error *err; struct got_object_id *id = NULL; char *subdirpath; if (asprintf(&subdirpath, "%s%s%s", path, path[0] == '\0' ? "" : "/", de->d_name) == -1) return got_error_from_errno("asprintf"); (*new_te) = calloc(1, sizeof(**new_te)); if (*new_te == NULL) return got_error_from_errno("calloc"); (*new_te)->mode = S_IFDIR; if (strlcpy((*new_te)->name, de->d_name, sizeof((*new_te)->name)) >= sizeof((*new_te)->name)) { err = got_error(GOT_ERR_NO_SPACE); goto done; } err = write_tree(&id, subdirpath, ignores, repo, progress_cb, progress_arg); if (err) goto done; memcpy(&(*new_te)->id, id, sizeof((*new_te)->id)); done: free(id); free(subdirpath); if (err) { free(*new_te); *new_te = NULL; } return err; } static const struct got_error * match_ignores(int *ignore, char *d_name, int type, struct got_pathlist_head *ignores) { struct got_pathlist_entry *pe; *ignore = 0; RB_FOREACH(pe, got_pathlist_head, ignores) { if (type == DT_DIR && pe->path_len > 0 && pe->path[pe->path_len - 1] == '/') { char stripped[PATH_MAX]; if (strlcpy(stripped, pe->path, sizeof(stripped)) >= sizeof(stripped)) return got_error(GOT_ERR_NO_SPACE); got_path_strip_trailing_slashes(stripped); if (fnmatch(stripped, d_name, 0) == 0) { *ignore = 1; break; } } else if (fnmatch(pe->path, d_name, 0) == 0) { *ignore = 1; break; } } return NULL; } static const struct got_error * write_tree(struct got_object_id **new_tree_id, const char *path_dir, struct got_pathlist_head *ignores, struct got_repository *repo, got_repo_import_cb progress_cb, void *progress_arg) { const struct got_error *err = NULL; DIR *dir; struct dirent *de; int nentries; struct got_tree_entry *new_te = NULL; struct got_pathlist_head paths, dirents; struct got_pathlist_entry *pe; long pos; *new_tree_id = NULL; RB_INIT(&paths); RB_INIT(&dirents); dir = opendir(path_dir); if (dir == NULL) { err = got_error_from_errno2("opendir", path_dir); goto done; } /* * Sort dirents into a pathlist. Otherwise subdir-iteration order * depends on the readdir() function, which varies between different * operating systems, making regression testing more difficult. * This smells of TOCTOU, but if dirents appear, disappear, or change * during import then we have a racy situation no matter what. */ pos = telldir(dir); if (pos == -1) { err = got_error_from_errno2("telldir", path_dir); goto done; } while ((de = readdir(dir)) != NULL) { if (strcmp(de->d_name, ".") != 0 && strcmp(de->d_name, "..") != 0) { err = insert_dir_entry(de, pos, &dirents); if (err) goto done; } pos = telldir(dir); if (pos == -1) { err = got_error_from_errno2("telldir", path_dir); goto done; } } nentries = 0; RB_FOREACH(pe, got_pathlist_head, &dirents) { int ignore = 0; int type; long *pos = pe->data; seekdir(dir, *pos); de = readdir(dir); if (de == NULL) { /* should not happen */ err = got_error_fmt(GOT_ERR_EOF, "unexpected EOF on directory '%s' while seeking " "to entry '%s' at offset '%ld'\n", path_dir, pe->path, *pos); goto done; } err = got_path_dirent_type(&type, path_dir, de); if (err) goto done; err = match_ignores(&ignore, de->d_name, type, ignores); if (ignore) continue; if (type == DT_DIR) { err = import_subdir(&new_te, de, path_dir, ignores, repo, progress_cb, progress_arg); if (err) { if (err->code != GOT_ERR_NO_TREE_ENTRY) goto done; err = NULL; continue; } } else if (type == DT_REG || type == DT_LNK) { err = import_file(&new_te, de, path_dir, repo); if (err) goto done; } else continue; err = insert_tree_entry(new_te, &paths); if (err) goto done; nentries++; } if (RB_EMPTY(&paths)) { err = got_error_msg(GOT_ERR_NO_TREE_ENTRY, "cannot create tree without any entries"); goto done; } RB_FOREACH(pe, got_pathlist_head, &paths) { struct got_tree_entry *te = pe->data; char *path; if (!S_ISREG(te->mode) && !S_ISLNK(te->mode)) continue; if (asprintf(&path, "%s/%s", path_dir, pe->path) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = (*progress_cb)(progress_arg, path); free(path); if (err) goto done; } err = got_object_tree_create(new_tree_id, &paths, nentries, repo); done: if (dir) closedir(dir); got_pathlist_free(&dirents, GOT_PATHLIST_FREE_ALL); got_pathlist_free(&paths, GOT_PATHLIST_FREE_NONE); return err; } const struct got_error * got_repo_import(struct got_object_id **new_commit_id, const char *path_dir, const char *logmsg, const char *author, struct got_pathlist_head *ignores, struct got_repository *repo, got_repo_import_cb progress_cb, void *progress_arg) { const struct got_error *err; struct got_object_id *new_tree_id; err = write_tree(&new_tree_id, path_dir, ignores, repo, progress_cb, progress_arg); if (err) return err; err = got_object_commit_create(new_commit_id, new_tree_id, NULL, 0, author, time(NULL), author, time(NULL), logmsg, repo); free(new_tree_id); return err; } const struct got_error * got_repo_get_loose_object_info(int *nobjects, off_t *ondisk_size, struct got_repository *repo) { const struct got_error *err = NULL; char *path_objects = NULL, *path = NULL; DIR *dir = NULL; struct got_object_id id; int i; *nobjects = 0; *ondisk_size = 0; path_objects = got_repo_get_path_objects(repo); if (path_objects == NULL) return got_error_from_errno("got_repo_get_path_objects"); for (i = 0; i <= 0xff; i++) { struct dirent *dent; if (asprintf(&path, "%s/%.2x", path_objects, i) == -1) { err = got_error_from_errno("asprintf"); break; } dir = opendir(path); if (dir == NULL) { if (errno == ENOENT) { err = NULL; continue; } err = got_error_from_errno2("opendir", path); break; } while ((dent = readdir(dir)) != NULL) { char *id_str; int fd; struct stat sb; if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0) continue; if (asprintf(&id_str, "%.2x%s", i, dent->d_name) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (!got_parse_object_id(&id, id_str, repo->algo)) { free(id_str); continue; } free(id_str); err = got_object_open_loose_fd(&fd, &id, repo); if (err) goto done; if (fstat(fd, &sb) == -1) { err = got_error_from_errno("fstat"); close(fd); goto done; } (*nobjects)++; (*ondisk_size) += sb.st_size; if (close(fd) == -1) { err = got_error_from_errno("close"); goto done; } } if (closedir(dir) != 0) { err = got_error_from_errno("closedir"); goto done; } dir = NULL; free(path); path = NULL; } done: if (dir && closedir(dir) != 0 && err == NULL) err = got_error_from_errno("closedir"); if (err) { *nobjects = 0; *ondisk_size = 0; } free(path_objects); free(path); return err; } const struct got_error * got_repo_get_packfile_info(int *npackfiles, int *nobjects, off_t *total_packsize, struct got_repository *repo) { const struct got_error *err = NULL; DIR *packdir = NULL; struct dirent *dent; struct got_packidx *packidx = NULL; char *path_packidx; char *path_packfile; int packdir_fd; struct stat sb; *npackfiles = 0; *nobjects = 0; *total_packsize = 0; packdir_fd = openat(got_repo_get_fd(repo), GOT_OBJECTS_PACK_DIR, O_DIRECTORY); if (packdir_fd == -1) { return got_error_from_errno_fmt("openat: %s/%s", got_repo_get_path_git_dir(repo), GOT_OBJECTS_PACK_DIR); } packdir = fdopendir(packdir_fd); if (packdir == NULL) { err = got_error_from_errno("fdopendir"); close(packdir_fd); goto done; } while ((dent = readdir(packdir)) != NULL) { if (!got_repo_is_packidx_filename(dent->d_name, strlen(dent->d_name), repo->algo)) continue; if (asprintf(&path_packidx, "%s/%s", GOT_OBJECTS_PACK_DIR, dent->d_name) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = got_packidx_open(&packidx, got_repo_get_fd(repo), path_packidx, 0, repo->algo); free(path_packidx); if (err) { if (err->code == GOT_ERR_LONELY_PACKIDX) { err = NULL; continue; } goto done; } if (fstat(packidx->fd, &sb) == -1) goto done; *total_packsize += sb.st_size; err = got_packidx_get_packfile_path(&path_packfile, packidx->path_packidx); if (err) goto done; if (fstatat(got_repo_get_fd(repo), path_packfile, &sb, 0) == -1) { free(path_packfile); goto done; } free(path_packfile); *total_packsize += sb.st_size; *nobjects += be32toh(packidx->hdr.fanout_table[0xff]); (*npackfiles)++; got_packidx_close(packidx); packidx = NULL; } done: if (packidx) got_packidx_close(packidx); if (packdir && closedir(packdir) != 0 && err == NULL) err = got_error_from_errno("closedir"); if (err) { *npackfiles = 0; *nobjects = 0; *total_packsize = 0; } return err; } RB_GENERATE(got_packidx_bloom_filter_tree, got_packidx_bloom_filter, entry, got_packidx_bloom_filter_cmp); got-portable-0.111/lib/inflate.c0000644000175000017500000003505115001741021012123 /* * Copyright (c) 2018 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_object.h" #include "got_path.h" #include "got_lib_hash.h" #include "got_lib_inflate.h" #include "got_lib_poll.h" #ifndef MIN #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b)) #endif static const struct got_error * wrap_inflate_error(int zerr, const char *prefix) { if (zerr == Z_ERRNO) return got_error_from_errno(prefix); if (zerr == Z_MEM_ERROR) return got_error_set_errno(ENOMEM, prefix); return got_error(GOT_ERR_DECOMPRESSION); } const struct got_error * got_inflate_init(struct got_inflate_buf *zb, uint8_t *outbuf, size_t bufsize, struct got_inflate_checksum *csum) { const struct got_error *err = NULL; int zerr; memset(zb, 0, sizeof(*zb)); zb->z.zalloc = Z_NULL; zb->z.zfree = Z_NULL; zerr = inflateInit(&zb->z); if (zerr != Z_OK) return wrap_inflate_error(zerr, "inflateInit"); zb->inlen = zb->outlen = bufsize; zb->inbuf = calloc(1, zb->inlen); if (zb->inbuf == NULL) { err = got_error_from_errno("calloc"); goto done; } zb->flags = 0; if (outbuf == NULL) { zb->outbuf = calloc(1, zb->outlen); if (zb->outbuf == NULL) { err = got_error_from_errno("calloc"); goto done; } zb->flags |= GOT_INFLATE_F_OWN_OUTBUF; } else zb->outbuf = outbuf; zb->csum = csum; done: if (err) got_inflate_end(zb); return err; } static void csum_input(struct got_inflate_checksum *csum, const uint8_t *buf, size_t len) { if (csum->input_crc) *csum->input_crc = crc32(*csum->input_crc, buf, len); if (csum->input_ctx) got_hash_update(csum->input_ctx, buf, len); } static void csum_output(struct got_inflate_checksum *csum, const uint8_t *buf, size_t len) { if (csum->output_crc) *csum->output_crc = crc32(*csum->output_crc, buf, len); if (csum->output_ctx) got_hash_update(csum->output_ctx, buf, len); } const struct got_error * got_inflate_read(struct got_inflate_buf *zb, FILE *f, size_t *outlenp, size_t *consumed) { size_t last_total_out = zb->z.total_out; size_t last_total_in = zb->z.total_in; z_stream *z = &zb->z; int ret = Z_ERRNO; z->next_out = zb->outbuf; z->avail_out = zb->outlen; *outlenp = 0; if (consumed) *consumed = 0; do { uint8_t *csum_in = NULL, *csum_out = NULL; size_t csum_avail_in = 0, csum_avail_out = 0; if (z->avail_in == 0) { size_t n = fread(zb->inbuf, 1, zb->inlen, f); if (n == 0) { if (ferror(f)) return got_ferror(f, GOT_ERR_IO); /* EOF */ ret = Z_STREAM_END; break; } z->next_in = zb->inbuf; z->avail_in = n; } if (zb->csum) { csum_in = z->next_in; csum_avail_in = z->avail_in; csum_out = z->next_out; csum_avail_out = z->avail_out; } ret = inflate(z, Z_SYNC_FLUSH); if (zb->csum) { csum_input(zb->csum, csum_in, csum_avail_in - z->avail_in); csum_output(zb->csum, csum_out, csum_avail_out - z->avail_out); } } while (ret == Z_OK && z->avail_out > 0); if (ret == Z_OK || ret == Z_BUF_ERROR) { zb->flags |= GOT_INFLATE_F_HAVE_MORE; } else { if (ret != Z_STREAM_END) return wrap_inflate_error(ret, "inflate"); zb->flags &= ~GOT_INFLATE_F_HAVE_MORE; } *outlenp = z->total_out - last_total_out; if (consumed) *consumed += z->total_in - last_total_in; return NULL; } const struct got_error * got_inflate_read_fd(struct got_inflate_buf *zb, int fd, size_t *outlenp, size_t *consumed) { const struct got_error *err = NULL; size_t last_total_out = zb->z.total_out; size_t last_total_in = zb->z.total_in; z_stream *z = &zb->z; int ret = Z_ERRNO; z->next_out = zb->outbuf; z->avail_out = zb->outlen; *outlenp = 0; if (consumed) *consumed = 0; do { uint8_t *csum_in = NULL, *csum_out = NULL; size_t csum_avail_in = 0, csum_avail_out = 0; if (z->avail_in == 0) { ssize_t n; err = got_poll_fd(fd, POLLIN, INFTIM); if (err) { if (err->code == GOT_ERR_EOF) { ret = Z_STREAM_END; break; } return err; } n = read(fd, zb->inbuf, zb->inlen); if (n < 0) return got_error_from_errno("read"); else if (n == 0) { /* EOF */ ret = Z_STREAM_END; break; } z->next_in = zb->inbuf; z->avail_in = n; } if (zb->csum) { csum_in = z->next_in; csum_avail_in = z->avail_in; csum_out = z->next_out; csum_avail_out = z->avail_out; } ret = inflate(z, Z_SYNC_FLUSH); if (zb->csum) { csum_input(zb->csum, csum_in, csum_avail_in - z->avail_in); csum_output(zb->csum, csum_out, csum_avail_out - z->avail_out); } } while (ret == Z_OK && z->avail_out > 0); if (ret == Z_OK || ret == Z_BUF_ERROR) { zb->flags |= GOT_INFLATE_F_HAVE_MORE; } else { if (ret != Z_STREAM_END) return wrap_inflate_error(ret, "inflate"); zb->flags &= ~GOT_INFLATE_F_HAVE_MORE; } *outlenp = z->total_out - last_total_out; if (consumed) *consumed += z->total_in - last_total_in; return NULL; } const struct got_error * got_inflate_read_mmap(struct got_inflate_buf *zb, uint8_t *map, size_t offset, size_t len, size_t *outlenp, size_t *consumed) { size_t last_total_out = zb->z.total_out; z_stream *z = &zb->z; int ret = Z_ERRNO; z->next_out = zb->outbuf; z->avail_out = zb->outlen; *outlenp = 0; *consumed = 0; do { uint8_t *csum_in = NULL, *csum_out = NULL; size_t csum_avail_in = 0, csum_avail_out = 0; size_t last_total_in = zb->z.total_in; if (z->avail_in == 0) { if (len == 0) { /* EOF */ ret = Z_STREAM_END; break; } z->next_in = map + offset + *consumed; if (len - *consumed > UINT_MAX) z->avail_in = UINT_MAX; else z->avail_in = len - *consumed; } if (zb->csum) { csum_in = z->next_in; csum_avail_in = z->avail_in; csum_out = z->next_out; csum_avail_out = z->avail_out; } ret = inflate(z, Z_SYNC_FLUSH); if (zb->csum) { csum_input(zb->csum, csum_in, csum_avail_in - z->avail_in); csum_output(zb->csum, csum_out, csum_avail_out - z->avail_out); } *consumed += z->total_in - last_total_in; } while (ret == Z_OK && z->avail_out > 0); if (ret == Z_OK || ret == Z_BUF_ERROR) { zb->flags |= GOT_INFLATE_F_HAVE_MORE; } else { if (ret != Z_STREAM_END) return wrap_inflate_error(ret, "inflate"); zb->flags &= ~GOT_INFLATE_F_HAVE_MORE; } *outlenp = z->total_out - last_total_out; return NULL; } void got_inflate_end(struct got_inflate_buf *zb) { free(zb->inbuf); if (zb->flags & GOT_INFLATE_F_OWN_OUTBUF) free(zb->outbuf); inflateEnd(&zb->z); } const struct got_error * got_inflate_to_mem(uint8_t **outbuf, size_t *outlen, size_t *consumed_total, struct got_inflate_checksum *csum, FILE *f) { const struct got_error *err; size_t avail, consumed; struct got_inflate_buf zb; void *newbuf; int nbuf = 1; if (outbuf) { *outbuf = malloc(GOT_INFLATE_BUFSIZE); if (*outbuf == NULL) return got_error_from_errno("malloc"); err = got_inflate_init(&zb, *outbuf, GOT_INFLATE_BUFSIZE, csum); } else err = got_inflate_init(&zb, NULL, GOT_INFLATE_BUFSIZE, csum); if (err) return err; *outlen = 0; if (consumed_total) *consumed_total = 0; do { err = got_inflate_read(&zb, f, &avail, &consumed); if (err) goto done; *outlen += avail; if (consumed_total) *consumed_total += consumed; if (zb.flags & GOT_INFLATE_F_HAVE_MORE) { if (outbuf == NULL) continue; newbuf = reallocarray(*outbuf, ++nbuf, GOT_INFLATE_BUFSIZE); if (newbuf == NULL) { err = got_error_from_errno("reallocarray"); free(*outbuf); *outbuf = NULL; *outlen = 0; goto done; } *outbuf = newbuf; zb.outbuf = newbuf + *outlen; zb.outlen = (nbuf * GOT_INFLATE_BUFSIZE) - *outlen; } } while (zb.flags & GOT_INFLATE_F_HAVE_MORE); done: got_inflate_end(&zb); return err; } const struct got_error * got_inflate_to_mem_fd(uint8_t **outbuf, size_t *outlen, size_t *consumed_total, struct got_inflate_checksum *csum, size_t expected_size, int infd) { const struct got_error *err; size_t avail, consumed; struct got_inflate_buf zb; void *newbuf; int nbuf = 1; size_t bufsize = GOT_INFLATE_BUFSIZE; /* Optimize buffer size in case short reads should suffice. */ if (expected_size > 0 && expected_size < bufsize) bufsize = expected_size; if (outbuf) { *outbuf = malloc(bufsize); if (*outbuf == NULL) return got_error_from_errno("malloc"); err = got_inflate_init(&zb, *outbuf, GOT_INFLATE_BUFSIZE, csum); } else err = got_inflate_init(&zb, NULL, bufsize, csum); if (err) goto done; *outlen = 0; if (consumed_total) *consumed_total = 0; do { err = got_inflate_read_fd(&zb, infd, &avail, &consumed); if (err) goto done; *outlen += avail; if (consumed_total) *consumed_total += consumed; if (zb.flags & GOT_INFLATE_F_HAVE_MORE) { if (outbuf == NULL) continue; newbuf = reallocarray(*outbuf, ++nbuf, GOT_INFLATE_BUFSIZE); if (newbuf == NULL) { err = got_error_from_errno("reallocarray"); free(*outbuf); *outbuf = NULL; *outlen = 0; goto done; } *outbuf = newbuf; zb.outbuf = newbuf + *outlen; zb.outlen = (nbuf * GOT_INFLATE_BUFSIZE) - *outlen; } } while (zb.flags & GOT_INFLATE_F_HAVE_MORE); done: got_inflate_end(&zb); return err; } const struct got_error * got_inflate_to_mem_mmap(uint8_t **outbuf, size_t *outlen, size_t *consumed_total, struct got_inflate_checksum *csum, uint8_t *map, size_t offset, size_t len) { const struct got_error *err; size_t avail, consumed; struct got_inflate_buf zb; void *newbuf; int nbuf = 1; if (outbuf) { *outbuf = malloc(GOT_INFLATE_BUFSIZE); if (*outbuf == NULL) return got_error_from_errno("malloc"); err = got_inflate_init(&zb, *outbuf, GOT_INFLATE_BUFSIZE, csum); if (err) { free(*outbuf); *outbuf = NULL; return err; } } else { err = got_inflate_init(&zb, NULL, GOT_INFLATE_BUFSIZE, csum); if (err) return err; } *outlen = 0; if (consumed_total) *consumed_total = 0; do { err = got_inflate_read_mmap(&zb, map, offset, len, &avail, &consumed); if (err) goto done; offset += consumed; if (consumed_total) *consumed_total += consumed; len -= consumed; *outlen += avail; if (len == 0) break; if (zb.flags & GOT_INFLATE_F_HAVE_MORE) { if (outbuf == NULL) continue; newbuf = reallocarray(*outbuf, ++nbuf, GOT_INFLATE_BUFSIZE); if (newbuf == NULL) { err = got_error_from_errno("reallocarray"); free(*outbuf); *outbuf = NULL; *outlen = 0; goto done; } *outbuf = newbuf; zb.outbuf = newbuf + *outlen; zb.outlen = (nbuf * GOT_INFLATE_BUFSIZE) - *outlen; } } while (zb.flags & GOT_INFLATE_F_HAVE_MORE); done: got_inflate_end(&zb); return err; } const struct got_error * got_inflate_to_fd(size_t *outlen, FILE *infile, struct got_inflate_checksum *csum, int outfd) { const struct got_error *err = NULL; size_t avail; struct got_inflate_buf zb; err = got_inflate_init(&zb, NULL, GOT_INFLATE_BUFSIZE, csum); if (err) goto done; *outlen = 0; do { err = got_inflate_read(&zb, infile, &avail, NULL); if (err) goto done; if (avail > 0) { ssize_t n; n = write(outfd, zb.outbuf, avail); if (n != avail) { err = got_error_from_errno("write"); goto done; } *outlen += avail; } } while (zb.flags & GOT_INFLATE_F_HAVE_MORE); done: if (err == NULL) { if (lseek(outfd, SEEK_SET, 0) == -1) err = got_error_from_errno("lseek"); } got_inflate_end(&zb); return err; } const struct got_error * got_inflate_to_file(size_t *outlen, FILE *infile, struct got_inflate_checksum *csum, FILE *outfile) { const struct got_error *err; size_t avail; struct got_inflate_buf zb; err = got_inflate_init(&zb, NULL, GOT_INFLATE_BUFSIZE, csum); if (err) goto done; *outlen = 0; do { err = got_inflate_read(&zb, infile, &avail, NULL); if (err) goto done; if (avail > 0) { size_t n; n = fwrite(zb.outbuf, avail, 1, outfile); if (n != 1) { err = got_ferror(outfile, GOT_ERR_IO); goto done; } *outlen += avail; } } while (zb.flags & GOT_INFLATE_F_HAVE_MORE); done: if (err == NULL) rewind(outfile); got_inflate_end(&zb); return err; } const struct got_error * got_inflate_to_file_fd(size_t *outlen, size_t *consumed_total, struct got_inflate_checksum *csum, int infd, FILE *outfile) { const struct got_error *err; size_t avail, consumed; struct got_inflate_buf zb; err = got_inflate_init(&zb, NULL, GOT_INFLATE_BUFSIZE, csum); if (err) goto done; *outlen = 0; if (consumed_total) *consumed_total = 0; do { err = got_inflate_read_fd(&zb, infd, &avail, &consumed); if (err) goto done; if (avail > 0) { size_t n; n = fwrite(zb.outbuf, avail, 1, outfile); if (n != 1) { err = got_ferror(outfile, GOT_ERR_IO); goto done; } *outlen += avail; if (consumed_total) *consumed_total += consumed; } } while (zb.flags & GOT_INFLATE_F_HAVE_MORE); done: if (err == NULL) rewind(outfile); got_inflate_end(&zb); return err; } const struct got_error * got_inflate_to_file_mmap(size_t *outlen, size_t *consumed_total, struct got_inflate_checksum *csum, uint8_t *map, size_t offset, size_t len, FILE *outfile) { const struct got_error *err; size_t avail, consumed; struct got_inflate_buf zb; err = got_inflate_init(&zb, NULL, GOT_INFLATE_BUFSIZE, csum); if (err) goto done; *outlen = 0; if (consumed_total) *consumed_total = 0; do { err = got_inflate_read_mmap(&zb, map, offset, len, &avail, &consumed); if (err) goto done; offset += consumed; if (consumed_total) *consumed_total += consumed; len -= consumed; if (avail > 0) { size_t n; n = fwrite(zb.outbuf, avail, 1, outfile); if (n != 1) { err = got_ferror(outfile, GOT_ERR_IO); goto done; } *outlen += avail; } } while (zb.flags & GOT_INFLATE_F_HAVE_MORE); done: if (err == NULL) rewind(outfile); got_inflate_end(&zb); return err; } got-portable-0.111/lib/got_lib_dial.h0000644000175000017500000000276715001740614013135 /* * Copyright (c) 2019 Ori Bernstein * Copyright (c) 2021 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #define GOT_DIAL_CMD_SEND "git-receive-pack" #define GOT_DIAL_CMD_FETCH "git-upload-pack" const struct got_error *got_dial_git(int *newfd, const char *host, const char *port, const char *path, const char *command); const struct got_error *got_dial_ssh(pid_t *newpid, int *newfd, const char *host, const char *port, const char *path, const char *jumphost, const char *identity_file, const char *command, int verbosity); const struct got_error *got_dial_http(pid_t *newpid, int *newfd, const char *host, const char *port, const char *path, int, int); const struct got_error *got_dial_parse_command(char **command, char **repo_path, const char *gitcmd); got-portable-0.111/lib/date.c0000644000175000017500000000221615001740614011422 /* * Copyright (c) 2022 Josh Rickmar * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "got_date.h" void got_date_format_gmtoff(char *buf, size_t sz, time_t gmtoff) { long long h, m; char sign = '+'; if (gmtoff < 0) { sign = '-'; gmtoff = -gmtoff; } h = (long long)gmtoff / 3600; m = ((long long)gmtoff - h*3600) / 60; snprintf(buf, sz, "%c%02lld%02lld", sign, h, m); } got-portable-0.111/lib/send.c0000644000175000017500000004572415001741021011442 /* * Copyright (c) 2018, 2019 Ori Bernstein * Copyright (c) 2021 Stefan Sperling * Copyright (c) 2023 Josh Rickmar * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_reference.h" #include "got_repository.h" #include "got_path.h" #include "got_cancel.h" #include "got_worktree.h" #include "got_object.h" #include "got_opentemp.h" #include "got_send.h" #include "got_repository_admin.h" #include "got_commit_graph.h" #include "got_lib_delta.h" #include "got_lib_hash.h" #include "got_lib_inflate.h" #include "got_lib_object.h" #include "got_lib_object_parse.h" #include "got_lib_object_create.h" #include "got_lib_pack.h" #include "got_lib_privsep.h" #include "got_lib_object_cache.h" #include "got_lib_repository.h" #include "got_lib_ratelimit.h" #include "got_lib_pack_create.h" #include "got_lib_dial.h" #include "got_lib_worktree_cvg.h" #include "got_lib_poll.h" #ifndef nitems #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) #endif #ifndef ssizeof #define ssizeof(_x) ((ssize_t)(sizeof(_x))) #endif #ifndef MIN #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b)) #endif const struct got_error * got_send_connect(pid_t *sendpid, int *sendfd, const char *proto, const char *host, const char *port, const char *server_path, const char *jumphost, const char *identity_file, int verbosity) { const struct got_error *err = NULL; *sendpid = -1; *sendfd = -1; if (strcmp(proto, "ssh") == 0 || strcmp(proto, "git+ssh") == 0) err = got_dial_ssh(sendpid, sendfd, host, port, server_path, jumphost, identity_file, GOT_DIAL_CMD_SEND, verbosity); else if (strcmp(proto, "git") == 0) err = got_dial_git(sendfd, host, port, server_path, GOT_DIAL_CMD_SEND); else if (strcmp(proto, "http") == 0 || strcmp(proto, "git+http") == 0) err = got_error_path(proto, GOT_ERR_NOT_IMPL); else err = got_error_path(proto, GOT_ERR_BAD_PROTO); return err; } struct pack_progress_arg { got_send_progress_cb progress_cb; void *progress_arg; int sendfd; int ncolored; int nfound; int ntrees; off_t packfile_size; int ncommits; int nobj_total; int nobj_deltify; int nobj_written; }; static const struct got_error * pack_progress(void *arg, int ncolored, int nfound, int ntrees, off_t packfile_size, int ncommits, int nobj_total, int nobj_deltify, int nobj_written, int pack_done) { const struct got_error *err; struct pack_progress_arg *a = arg; err = a->progress_cb(a->progress_arg, ncolored, nfound, ntrees, packfile_size, ncommits, nobj_total, nobj_deltify, nobj_written, 0, NULL, NULL, 0); if (err) return err; /* * Detect the server closing our connection while we are * busy creating a pack file. * * XXX This should be a temporary workaround. A better fix would * be to avoid use of an on-disk tempfile for pack file data. * Instead we could stream pack file data to got-send-pack while * the pack file is being generated. Write errors in got-send-pack * would then automatically abort the creation of pack file data. */ err = got_poll_fd(a->sendfd, 0, 0); if (err && err->code != GOT_ERR_TIMEOUT) { if (err->code == GOT_ERR_EOF) { err = got_error_msg(GOT_ERR_EOF, "server unexpectedly closed the connection"); } return err; } a->ncolored= ncolored; a->nfound = nfound; a->ntrees = ntrees; a->packfile_size = packfile_size; a->ncommits = ncommits; a->nobj_total = nobj_total; a->nobj_deltify = nobj_deltify; a->nobj_written = nobj_written; return NULL; } static const struct got_error * insert_sendable_ref(struct got_pathlist_head *refs, const char *refname, const char *target_refname, struct got_repository *repo) { const struct got_error *err; struct got_reference *ref; struct got_object_id *id = NULL; struct got_pathlist_entry *new = NULL; int obj_type; err = got_ref_open(&ref, repo, refname, 0); if (err) return err; if (got_ref_is_symbolic(ref)) { err = got_error_fmt(GOT_ERR_BAD_REF_TYPE, "cannot send symbolic reference %s", refname); goto done; } err = got_ref_resolve(&id, repo, ref); if (err) goto done; err = got_object_get_type(&obj_type, repo, id); if (err) goto done; switch (obj_type) { case GOT_OBJ_TYPE_COMMIT: case GOT_OBJ_TYPE_TAG: break; default: err = got_error_fmt(GOT_ERR_OBJ_TYPE, "cannot send %s", refname); goto done; } err = got_pathlist_insert(&new, refs, target_refname, id); if (new == NULL && err == NULL) err = got_error(GOT_ERR_REF_DUP_ENTRY); done: if (ref) got_ref_close(ref); if (err) free(id); return err; } static const struct got_error * check_common_ancestry(const char *refname, struct got_object_id *my_id, struct got_object_id *their_id, struct got_repository *repo, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; struct got_object_id *yca_id; int obj_type; err = got_object_get_type(&obj_type, repo, their_id); if (err) return err; if (obj_type != GOT_OBJ_TYPE_COMMIT) return got_error_fmt(GOT_ERR_OBJ_TYPE, "bad object type on server for %s", refname); err = got_commit_graph_find_youngest_common_ancestor(&yca_id, my_id, their_id, 0, 1, repo, cancel_cb, cancel_arg); if (err) return err; if (yca_id == NULL) return got_error_fmt(GOT_ERR_SEND_ANCESTRY, "%s", refname); if (got_object_id_cmp(their_id, yca_id) != 0) err = got_error_fmt(GOT_ERR_SEND_ANCESTRY, "%s", refname); free(yca_id); return err; } static const struct got_error * realloc_ids(struct got_object_id ***ids, size_t *nalloc, size_t n) { struct got_object_id **new; const size_t alloc_chunksz = 256; if (*nalloc >= n) return NULL; new = recallocarray(*ids, *nalloc, *nalloc + alloc_chunksz, sizeof(struct got_object_id)); if (new == NULL) return got_error_from_errno("recallocarray"); *ids = new; *nalloc += alloc_chunksz; return NULL; } static struct got_pathlist_entry * find_ref(struct got_pathlist_head *refs, const char *refname) { struct got_pathlist_entry find; find.path = refname; find.path_len = strlen(refname); return RB_FIND(got_pathlist_head, refs, &find); } static const struct got_error * get_remote_refname(char **remote_refname, const char *remote_name, const char *refname) { if (strncmp(refname, "refs/", 5) == 0) refname += 5; if (strncmp(refname, "heads/", 6) == 0) refname += 6; if (asprintf(remote_refname, "refs/remotes/%s/%s", remote_name, refname) == -1) return got_error_from_errno("asprintf"); return NULL; } static const struct got_error * update_remote_ref(struct got_pathlist_entry *my_ref, const char *remote_name, struct got_repository *repo) { const struct got_error *err, *unlock_err; const char *refname = my_ref->path; struct got_object_id *my_id = my_ref->data; struct got_reference *ref = NULL; char *remote_refname = NULL; int ref_locked = 0; err = get_remote_refname(&remote_refname, remote_name, refname); if (err) goto done; err = got_ref_open(&ref, repo, remote_refname, 1 /* lock */); if (err) { if (err->code != GOT_ERR_NOT_REF) goto done; err = got_ref_alloc(&ref, remote_refname, my_id); if (err) goto done; } else { ref_locked = 1; err = got_ref_change_ref(ref, my_id); if (err) goto done; } err = got_ref_write(ref, repo); done: if (ref) { if (ref_locked) { unlock_err = got_ref_unlock(ref); if (unlock_err && err == NULL) err = unlock_err; } got_ref_close(ref); } free(remote_refname); return err; } const struct got_error* got_send_pack(const char *remote_name, struct got_pathlist_head *branch_names, struct got_pathlist_head *tag_names, struct got_pathlist_head *delete_branches, int verbosity, int overwrite_refs, int sendfd, struct got_repository *repo, got_send_progress_cb progress_cb, void *progress_arg, got_cancel_cb cancel_cb, void *cancel_arg) { int imsg_sendfds[2] = { -1, -1 }; int npackfd = -1, nsendfd = -1; int sendstatus, done = 0; const struct got_error *err; struct imsgbuf sendibuf; pid_t sendpid = -1; struct got_pathlist_head have_refs; struct got_pathlist_head their_refs; struct got_pathlist_entry *pe; struct got_object_id **our_ids = NULL; struct got_object_id **their_ids = NULL; int nours = 0, ntheirs = 0; size_t nalloc_ours = 0, nalloc_theirs = 0; int refs_to_send = 0, refs_to_delete = 0; off_t bytes_sent = 0, bytes_sent_cur = 0; struct pack_progress_arg ppa; struct got_object_id packhash; int packfd = -1; FILE *delta_cache = NULL; char *s = NULL; RB_INIT(&have_refs); RB_INIT(&their_refs); memset(&sendibuf, 0, sizeof(sendibuf)); if (got_repo_get_object_format(repo) != GOT_HASH_SHA1) return got_error_fmt(GOT_ERR_NOT_IMPL, "sha256 object IDs unsupported in network protocol"); RB_FOREACH(pe, got_pathlist_head, branch_names) { const char *branchname = pe->path; const char *targetname = pe->data; if (targetname == NULL) targetname = branchname; if (strncmp(targetname, "refs/heads/", 11) != 0) { if (asprintf(&s, "refs/heads/%s", targetname) == -1) { err = got_error_from_errno("asprintf"); goto done; } } else { if ((s = strdup(targetname)) == NULL) { err = got_error_from_errno("strdup"); goto done; } } err = insert_sendable_ref(&have_refs, branchname, s, repo); if (err) { if (err->code != GOT_ERR_REF_DUP_ENTRY) goto done; err = NULL; free(s); } s = NULL; } RB_FOREACH(pe, got_pathlist_head, delete_branches) { const char *branchname = pe->path; struct got_pathlist_entry *ref; if (strncmp(branchname, "refs/heads/", 11) != 0) { err = got_error_fmt(GOT_ERR_SEND_DELETE_REF, "%s", branchname); goto done; } ref = find_ref(&have_refs, branchname); if (ref) { err = got_error_fmt(GOT_ERR_SEND_DELETE_REF, "changes on %s will be sent to server", branchname); goto done; } } RB_FOREACH(pe, got_pathlist_head, tag_names) { const char *tagname = pe->path; if (strncmp(tagname, "refs/tags/", 10) != 0) { if (asprintf(&s, "refs/tags/%s", tagname) == -1) { err = got_error_from_errno("asprintf"); goto done; } } else { if ((s = strdup(pe->path)) == NULL) { err = got_error_from_errno("strdup"); goto done; } } err = insert_sendable_ref(&have_refs, s, s, repo); if (err) { if (err->code != GOT_ERR_REF_DUP_ENTRY) goto done; err = NULL; free(s); } s = NULL; } if (RB_EMPTY(&have_refs) && RB_EMPTY(delete_branches)) { err = got_error(GOT_ERR_SEND_EMPTY); goto done; } packfd = got_opentempfd(); if (packfd == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } delta_cache = got_opentemp(); if (delta_cache == NULL) { err = got_error_from_errno("got_opentemp"); goto done; } if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_sendfds) == -1) { err = got_error_from_errno("socketpair"); goto done; } sendpid = fork(); if (sendpid == -1) { err = got_error_from_errno("fork"); goto done; } else if (sendpid == 0) { got_privsep_exec_child(imsg_sendfds, GOT_PATH_PROG_SEND_PACK, got_repo_get_path(repo)); } if (close(imsg_sendfds[1]) == -1) { err = got_error_from_errno("close"); goto done; } imsg_sendfds[1] = -1; if (imsgbuf_init(&sendibuf, imsg_sendfds[0]) == -1) { err = got_error_from_errno("imsgbuf_init"); goto done; } imsgbuf_allow_fdpass(&sendibuf); nsendfd = dup(sendfd); if (nsendfd == -1) { err = got_error_from_errno("dup"); goto done; } /* * Prepare the array of our object IDs which * will be needed for generating a pack file. */ RB_FOREACH(pe, got_pathlist_head, &have_refs) { struct got_object_id *id = pe->data; err = realloc_ids(&our_ids, &nalloc_ours, nours + 1); if (err) goto done; our_ids[nours] = id; nours++; } err = got_privsep_send_send_req(&sendibuf, nsendfd, &have_refs, delete_branches, verbosity); if (err) goto done; nsendfd = -1; err = got_privsep_recv_send_remote_refs(&their_refs, &sendibuf); if (err) goto done; /* * Process references reported by the server. * Push appropriate object IDs onto the "their IDs" array. * This array will be used to exclude objects which already * exist on the server from our pack file. */ RB_FOREACH(pe, got_pathlist_head, &their_refs) { const char *refname = pe->path; struct got_object_id *their_id = pe->data; int have_their_id; struct got_object *obj; struct got_pathlist_entry *my_ref = NULL; int is_tag = 0; /* Don't blindly trust the server to send us valid names. */ if (!got_ref_name_is_valid(refname)) continue; if (strncmp(refname, "refs/tags/", 10) == 0) is_tag = 1; /* * Find out whether this is a reference we want to upload. * Otherwise we can still use this reference as a hint to * avoid uploading any objects the server already has. */ my_ref = find_ref(&have_refs, refname); if (my_ref) { struct got_object_id *my_id = my_ref->data; if (got_object_id_cmp(my_id, their_id) != 0) { if (!overwrite_refs && is_tag) { err = got_error_fmt( GOT_ERR_SEND_TAG_EXISTS, "%s", refname); goto done; } refs_to_send++; } } /* Check if their object exists locally. */ err = got_object_open(&obj, repo, their_id); if (err) { if (err->code != GOT_ERR_NO_OBJ) goto done; if (!overwrite_refs && my_ref != NULL) { err = got_error_fmt(GOT_ERR_SEND_ANCESTRY, "%s", refname); goto done; } have_their_id = 0; } else { got_object_close(obj); have_their_id = 1; } err = realloc_ids(&their_ids, &nalloc_theirs, ntheirs + 1); if (err) goto done; if (have_their_id) { /* Enforce linear ancestry if required. */ if (!overwrite_refs && my_ref && !is_tag) { struct got_object_id *my_id = my_ref->data; err = check_common_ancestry(refname, my_id, their_id, repo, cancel_cb, cancel_arg); if (err) goto done; } /* Exclude any objects reachable via their ID. */ their_ids[ntheirs] = their_id; ntheirs++; } else if (!is_tag) { char *remote_refname; struct got_reference *ref; /* * Exclude any objects which exist on the server * according to a locally cached remote reference. */ err = get_remote_refname(&remote_refname, remote_name, refname); if (err) goto done; err = got_ref_open(&ref, repo, remote_refname, 0); free(remote_refname); if (err) { if (err->code != GOT_ERR_NOT_REF) goto done; err = NULL; } else { err = got_ref_resolve(&their_ids[ntheirs], repo, ref); got_ref_close(ref); if (err) goto done; ntheirs++; } } } /* Account for any new references we are going to upload. */ RB_FOREACH(pe, got_pathlist_head, &have_refs) { const char *refname = pe->path; if (find_ref(&their_refs, refname) == NULL) refs_to_send++; } /* Account for any existing references we are going to delete. */ RB_FOREACH(pe, got_pathlist_head, delete_branches) { const char *branchname = pe->path; if (find_ref(&their_refs, branchname)) refs_to_delete++; } if (refs_to_send == 0 && refs_to_delete == 0) { got_privsep_send_stop(imsg_sendfds[0]); goto done; } if (refs_to_send > 0) { struct got_ratelimit rl; got_ratelimit_init(&rl, 0, 500); memset(&ppa, 0, sizeof(ppa)); ppa.progress_cb = progress_cb; ppa.progress_arg = progress_arg; ppa.sendfd = sendfd; err = got_pack_create(&packhash, packfd, delta_cache, their_ids, ntheirs, our_ids, nours, repo, 0, 1, 0, pack_progress, &ppa, &rl, cancel_cb, cancel_arg); if (err) goto done; npackfd = dup(packfd); if (npackfd == -1) { err = got_error_from_errno("dup"); goto done; } err = got_privsep_send_packfd(&sendibuf, npackfd); if (err != NULL) goto done; npackfd = -1; } else { err = got_privsep_send_packfd(&sendibuf, -1); if (err != NULL) goto done; } while (!done) { int success = 0; char *refname = NULL; char *errmsg = NULL; if (cancel_cb) { err = (*cancel_cb)(cancel_arg); if (err) goto done; } err = got_privsep_recv_send_progress(&done, &bytes_sent, &success, &refname, &errmsg, &sendibuf); if (err) goto done; if (refname && got_ref_name_is_valid(refname) && success && strncmp(refname, "refs/tags/", 10) != 0) { struct got_pathlist_entry *my_ref; /* * The server has accepted our changes. * Update our reference in refs/remotes/ accordingly. */ my_ref = find_ref(&have_refs, refname); if (my_ref) { err = update_remote_ref(my_ref, remote_name, repo); if (err) goto done; } } if (refname != NULL || bytes_sent_cur != bytes_sent) { err = progress_cb(progress_arg, ppa.ncolored, ppa.nfound, ppa.ntrees, ppa.packfile_size, ppa.ncommits, ppa.nobj_total, ppa.nobj_deltify, ppa.nobj_written, bytes_sent, refname, errmsg, success); if (err) { free(refname); free(errmsg); goto done; } bytes_sent_cur = bytes_sent; } free(refname); free(errmsg); } done: if (sendibuf.w) imsgbuf_clear(&sendibuf); if (sendpid != -1) { if (err) got_privsep_send_stop(imsg_sendfds[0]); if (waitpid(sendpid, &sendstatus, 0) == -1 && err == NULL) err = got_error_from_errno("waitpid"); } if (imsg_sendfds[0] != -1 && close(imsg_sendfds[0]) == -1 && err == NULL) err = got_error_from_errno("close"); if (imsg_sendfds[1] != -1 && close(imsg_sendfds[1]) == -1 && err == NULL) err = got_error_from_errno("close"); if (packfd != -1 && close(packfd) == -1 && err == NULL) err = got_error_from_errno("close"); if (delta_cache && fclose(delta_cache) == EOF && err == NULL) err = got_error_from_errno("fclose"); if (nsendfd != -1 && close(nsendfd) == -1 && err == NULL) err = got_error_from_errno("close"); if (npackfd != -1 && close(npackfd) == -1 && err == NULL) err = got_error_from_errno("close"); got_pathlist_free(&have_refs, GOT_PATHLIST_FREE_ALL); got_pathlist_free(&their_refs, GOT_PATHLIST_FREE_ALL); /* * Object ids are owned by have_refs/their_refs and are already freed; * Only the arrays must be freed. */ free(our_ids); free(their_ids); free(s); return err; } got-portable-0.111/lib/fetch.c0000644000175000017500000003700315001741021011571 /* * Copyright (c) 2018, 2019 Ori Bernstein * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_reference.h" #include "got_repository.h" #include "got_path.h" #include "got_cancel.h" #include "got_worktree.h" #include "got_object.h" #include "got_opentemp.h" #include "got_fetch.h" #include "got_lib_hash.h" #include "got_lib_delta.h" #include "got_lib_inflate.h" #include "got_lib_object.h" #include "got_lib_object_parse.h" #include "got_lib_object_create.h" #include "got_lib_pack.h" #include "got_lib_privsep.h" #include "got_lib_object_cache.h" #include "got_lib_repository.h" #include "got_lib_dial.h" #include "got_lib_pkt.h" #ifndef nitems #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) #endif #ifndef ssizeof #define ssizeof(_x) ((ssize_t)(sizeof(_x))) #endif #ifndef MIN #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b)) #endif const struct got_error * got_fetch_connect(pid_t *fetchpid, int *fetchfd, const char *proto, const char *host, const char *port, const char *server_path, const char *jumphost, const char *identity_file, int verbosity) { const struct got_error *err = NULL; *fetchpid = -1; *fetchfd = -1; if (strcmp(proto, "ssh") == 0 || strcmp(proto, "git+ssh") == 0) err = got_dial_ssh(fetchpid, fetchfd, host, port, server_path, jumphost, identity_file, GOT_DIAL_CMD_FETCH, verbosity); else if (strcmp(proto, "git") == 0) err = got_dial_git(fetchfd, host, port, server_path, GOT_DIAL_CMD_FETCH); else if (strcmp(proto, "http") == 0 || strcmp(proto, "git+http") == 0 || strcmp(proto, "https") == 0 || strcmp(proto, "git+https") == 0) err = got_dial_http(fetchpid, fetchfd, host, port, server_path, verbosity, strstr(proto, "https") != NULL); else err = got_error_path(proto, GOT_ERR_BAD_PROTO); return err; } const struct got_error * got_fetch_pack(struct got_object_id **pack_hash, struct got_pathlist_head *refs, struct got_pathlist_head *symrefs, const char *remote_name, int mirror_references, int fetch_all_branches, struct got_pathlist_head *wanted_branches, struct got_pathlist_head *wanted_refs, int list_refs_only, int verbosity, int fetchfd, struct got_repository *repo, const char *worktree_refname, const char *remote_head, int no_head, got_fetch_progress_cb progress_cb, void *progress_arg) { size_t i; int imsg_fetchfds[2], imsg_idxfds[2]; int packfd = -1, npackfd = -1, idxfd = -1, nidxfd = -1, nfetchfd = -1; int tmpfds[3]; int fetchstatus, idxstatus, done = 0; const struct got_error *err; struct imsgbuf fetchibuf, idxibuf; pid_t fetchpid, idxpid; char *tmppackpath = NULL, *tmpidxpath = NULL; char *packpath = NULL, *idxpath = NULL, *id_str = NULL; const char *repo_path = NULL; struct got_pathlist_head have_refs; struct got_pathlist_entry *pe, *new; struct got_reflist_head my_refs; struct got_reflist_entry *re; off_t packfile_size = 0; struct got_packfile_hdr pack_hdr; uint32_t nobj = 0; char *path; char *progress = NULL; *pack_hash = NULL; memset(&fetchibuf, 0, sizeof(fetchibuf)); memset(&idxibuf, 0, sizeof(idxibuf)); if (repo && got_repo_get_object_format(repo) != GOT_HASH_SHA1) return got_error_fmt(GOT_ERR_NOT_IMPL, "sha256 object IDs unsupported in network protocol"); /* * Prevent fetching of references that won't make any * sense outside of the remote repository's context. */ RB_FOREACH(pe, got_pathlist_head, wanted_refs) { const char *refname = pe->path; if (strncmp(refname, "refs/got/", 9) == 0 || strncmp(refname, "got/", 4) == 0 || strncmp(refname, "refs/remotes/", 13) == 0 || strncmp(refname, "remotes/", 8) == 0) return got_error_path(refname, GOT_ERR_FETCH_BAD_REF); } if (!list_refs_only) repo_path = got_repo_get_path_git_dir(repo); for (i = 0; i < nitems(tmpfds); i++) tmpfds[i] = -1; RB_INIT(&have_refs); TAILQ_INIT(&my_refs); if (!list_refs_only) { err = got_ref_list(&my_refs, repo, NULL, got_ref_cmp_by_name, NULL); if (err) goto done; } TAILQ_FOREACH(re, &my_refs, entry) { struct got_object_id *id; char *refname; if (got_ref_is_symbolic(re->ref)) continue; err = got_ref_resolve(&id, repo, re->ref); if (err) goto done; refname = strdup(got_ref_get_name(re->ref)); if (refname == NULL) { err = got_error_from_errno("strdup"); goto done; } err = got_pathlist_insert(&new, &have_refs, refname, id); if (err) goto done; if (new == NULL){ free(refname); free(id); } } if (list_refs_only) { packfd = got_opentempfd(); if (packfd == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } } else { if (asprintf(&path, "%s/%s/fetching.pack", repo_path, GOT_OBJECTS_PACK_DIR) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = got_opentemp_named_fd(&tmppackpath, &packfd, path, ""); free(path); if (err) goto done; if (fchmod(packfd, GOT_DEFAULT_FILE_MODE) != 0) { err = got_error_from_errno2("fchmod", tmppackpath); goto done; } } if (list_refs_only) { idxfd = got_opentempfd(); if (idxfd == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } } else { if (asprintf(&path, "%s/%s/fetching.idx", repo_path, GOT_OBJECTS_PACK_DIR) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = got_opentemp_named_fd(&tmpidxpath, &idxfd, path, ""); free(path); if (err) goto done; if (fchmod(idxfd, GOT_DEFAULT_FILE_MODE) != 0) { err = got_error_from_errno2("fchmod", tmpidxpath); goto done; } } nidxfd = dup(idxfd); if (nidxfd == -1) { err = got_error_from_errno("dup"); goto done; } for (i = 0; i < nitems(tmpfds); i++) { tmpfds[i] = got_opentempfd(); if (tmpfds[i] == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } } if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fetchfds) == -1) { err = got_error_from_errno("socketpair"); goto done; } fetchpid = fork(); if (fetchpid == -1) { err = got_error_from_errno("fork"); goto done; } else if (fetchpid == 0){ got_privsep_exec_child(imsg_fetchfds, GOT_PATH_PROG_FETCH_PACK, tmppackpath); } if (close(imsg_fetchfds[1]) == -1) { err = got_error_from_errno("close"); goto done; } if (imsgbuf_init(&fetchibuf, imsg_fetchfds[0]) == -1) { err = got_error_from_errno("imsgbuf_init"); goto done; } imsgbuf_allow_fdpass(&fetchibuf); nfetchfd = dup(fetchfd); if (nfetchfd == -1) { err = got_error_from_errno("dup"); goto done; } err = got_privsep_send_fetch_req(&fetchibuf, nfetchfd, &have_refs, fetch_all_branches, wanted_branches, wanted_refs, list_refs_only, worktree_refname, remote_head, no_head, verbosity); if (err != NULL) goto done; nfetchfd = -1; npackfd = dup(packfd); if (npackfd == -1) { err = got_error_from_errno("dup"); goto done; } err = got_privsep_send_fetch_outfd(&fetchibuf, npackfd); if (err != NULL) goto done; npackfd = -1; packfile_size = 0; progress = calloc(GOT_PKT_MAX, 1); if (progress == NULL) { err = got_error_from_errno("calloc"); goto done; } *pack_hash = calloc(1, sizeof(**pack_hash)); if (*pack_hash == NULL) { err = got_error_from_errno("calloc"); goto done; } while (!done) { struct got_object_id *id = NULL; char *refname = NULL; char *server_progress = NULL; off_t packfile_size_cur = 0; err = got_privsep_recv_fetch_progress(&done, &id, &refname, symrefs, &server_progress, &packfile_size_cur, (*pack_hash)->hash, &fetchibuf); if (err != NULL) goto done; /* Don't report size progress for an empty pack file. */ if (packfile_size_cur <= ssizeof(pack_hdr) + SHA1_DIGEST_LENGTH) packfile_size_cur = 0; if (!done && refname && id) { err = got_pathlist_insert(&pe, refs, refname, id); if (err || pe == NULL) { free(id); free(refname); if (err != NULL) goto done; } } else if (!done && server_progress) { char *p; /* * XXX git-daemon tends to send batched output with * lines spanning separate packets. Buffer progress * output until we see a CR or LF to avoid giving * partial lines of progress output to the callback. */ if (strlcat(progress, server_progress, GOT_PKT_MAX) >= GOT_PKT_MAX) { progress[0] = '\0'; /* discard */ continue; } while ((p = strchr(progress, '\r')) != NULL || (p = strchr(progress, '\n')) != NULL) { char *s; size_t n; char c = *p; *p = '\0'; if (asprintf(&s, "%s%s", progress, c == '\n' ? "\n" : "") == -1) { err = got_error_from_errno("asprintf"); goto done; } err = progress_cb(progress_arg, s, packfile_size_cur, 0, 0, 0, 0); free(s); if (err) break; n = strlen(progress); if (n < GOT_PKT_MAX - 1) { memmove(progress, &progress[n + 1], GOT_PKT_MAX - n - 1); } else progress[0] = '\0'; } free(server_progress); if (err) goto done; } else if (!done && packfile_size_cur != packfile_size) { err = progress_cb(progress_arg, NULL, packfile_size_cur, 0, 0, 0, 0); if (err) break; packfile_size = packfile_size_cur; } } if (close(imsg_fetchfds[0]) == -1) { err = got_error_from_errno("close"); goto done; } if (waitpid(fetchpid, &fetchstatus, 0) == -1) { err = got_error_from_errno("waitpid"); goto done; } if (lseek(packfd, 0, SEEK_SET) == -1) { err = got_error_from_errno("lseek"); goto done; } /* If zero data was fetched without error we are already up-to-date. */ if (packfile_size == 0) { free(*pack_hash); *pack_hash = NULL; goto done; } else if (packfile_size < ssizeof(pack_hdr) + SHA1_DIGEST_LENGTH) { err = got_error_msg(GOT_ERR_BAD_PACKFILE, "short pack file"); goto done; } else { ssize_t n; n = read(packfd, &pack_hdr, ssizeof(pack_hdr)); if (n == -1) { err = got_error_from_errno("read"); goto done; } if (n != ssizeof(pack_hdr)) { err = got_error(GOT_ERR_IO); goto done; } if (pack_hdr.signature != htobe32(GOT_PACKFILE_SIGNATURE)) { err = got_error_msg(GOT_ERR_BAD_PACKFILE, "bad pack file signature"); goto done; } if (pack_hdr.version != htobe32(GOT_PACKFILE_VERSION)) { err = got_error_msg(GOT_ERR_BAD_PACKFILE, "bad pack file version"); goto done; } nobj = be32toh(pack_hdr.nobjects); if (nobj == 0 && packfile_size > ssizeof(pack_hdr) + SHA1_DIGEST_LENGTH) { err = got_error_msg(GOT_ERR_BAD_PACKFILE, "bad pack file with zero objects"); goto done; } if (nobj != 0 && packfile_size <= ssizeof(pack_hdr) + SHA1_DIGEST_LENGTH) { err = got_error_msg(GOT_ERR_BAD_PACKFILE, "empty pack file with non-zero object count"); goto done; } } /* * If the pack file contains no objects, we may only need to update * references in our repository. The caller will take care of that. */ if (nobj == 0) { free(*pack_hash); *pack_hash = NULL; goto done; } if (lseek(packfd, 0, SEEK_SET) == -1) { err = got_error_from_errno("lseek"); goto done; } if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_idxfds) == -1) { err = got_error_from_errno("socketpair"); goto done; } idxpid = fork(); if (idxpid == -1) { err= got_error_from_errno("fork"); goto done; } else if (idxpid == 0) got_privsep_exec_child(imsg_idxfds, GOT_PATH_PROG_INDEX_PACK, tmppackpath); if (close(imsg_idxfds[1]) == -1) { err = got_error_from_errno("close"); goto done; } if (imsgbuf_init(&idxibuf, imsg_idxfds[0]) == -1) { err = got_error_from_errno("imsgbuf_init"); goto done; } imsgbuf_allow_fdpass(&idxibuf); npackfd = dup(packfd); if (npackfd == -1) { err = got_error_from_errno("dup"); goto done; } err = got_privsep_send_index_pack_req(&idxibuf, *pack_hash, npackfd); if (err != NULL) goto done; npackfd = -1; err = got_privsep_send_index_pack_outfd(&idxibuf, nidxfd); if (err != NULL) goto done; nidxfd = -1; for (i = 0; i < nitems(tmpfds); i++) { err = got_privsep_send_tmpfd(&idxibuf, tmpfds[i]); if (err != NULL) goto done; tmpfds[i] = -1; } done = 0; while (!done) { int nobj_total, nobj_indexed, nobj_loose, nobj_resolved; err = got_privsep_recv_index_progress(&done, &nobj_total, &nobj_indexed, &nobj_loose, &nobj_resolved, &idxibuf); if (err != NULL) goto done; if (nobj_indexed != 0) { err = progress_cb(progress_arg, NULL, packfile_size, nobj_total, nobj_indexed, nobj_loose, nobj_resolved); if (err) break; } } if (close(imsg_idxfds[0]) == -1) { err = got_error_from_errno("close"); goto done; } if (waitpid(idxpid, &idxstatus, 0) == -1) { err = got_error_from_errno("waitpid"); goto done; } err = got_object_id_str(&id_str, *pack_hash); if (err) goto done; if (asprintf(&packpath, "%s/%s/pack-%s.pack", repo_path, GOT_OBJECTS_PACK_DIR, id_str) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (asprintf(&idxpath, "%s/%s/pack-%s.idx", repo_path, GOT_OBJECTS_PACK_DIR, id_str) == -1) { err = got_error_from_errno("asprintf"); goto done; } free(id_str); id_str = NULL; if (rename(tmppackpath, packpath) == -1) { err = got_error_from_errno3("rename", tmppackpath, packpath); goto done; } free(tmppackpath); tmppackpath = NULL; if (rename(tmpidxpath, idxpath) == -1) { err = got_error_from_errno3("rename", tmpidxpath, idxpath); goto done; } free(tmpidxpath); tmpidxpath = NULL; done: if (fetchibuf.w) imsgbuf_clear(&fetchibuf); if (idxibuf.w) imsgbuf_clear(&idxibuf); if (tmppackpath && unlink(tmppackpath) == -1 && err == NULL) err = got_error_from_errno2("unlink", tmppackpath); if (tmpidxpath && unlink(tmpidxpath) == -1 && err == NULL) err = got_error_from_errno2("unlink", tmpidxpath); if (nfetchfd != -1 && close(nfetchfd) == -1 && err == NULL) err = got_error_from_errno("close"); if (npackfd != -1 && close(npackfd) == -1 && err == NULL) err = got_error_from_errno("close"); if (packfd != -1 && close(packfd) == -1 && err == NULL) err = got_error_from_errno("close"); if (idxfd != -1 && close(idxfd) == -1 && err == NULL) err = got_error_from_errno("close"); for (i = 0; i < nitems(tmpfds); i++) { if (tmpfds[i] != -1 && close(tmpfds[i]) == -1 && err == NULL) err = got_error_from_errno("close"); } free(tmppackpath); free(tmpidxpath); free(idxpath); free(id_str); free(packpath); free(progress); got_pathlist_free(&have_refs, GOT_PATHLIST_FREE_ALL); got_ref_list_free(&my_refs); if (err) { free(*pack_hash); *pack_hash = NULL; got_pathlist_free(refs, GOT_PATHLIST_FREE_ALL); got_pathlist_free(symrefs, GOT_PATHLIST_FREE_ALL); } return err; } got-portable-0.111/lib/got_lib_pkt.h0000644000175000017500000000262015001740614013006 /* * Copyright (c) 2019 Ori Bernstein * Copyright (c) 2021 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #define GOT_PKT_MAX 65536 const struct got_error *got_pkt_readn(ssize_t *off, int fd, void *buf, size_t n, int timeout); const struct got_error *got_pkt_flushpkt(int fd, int chattygot); const struct got_error *got_pkt_readlen(int *len, const char *str, int chattygot); const struct got_error *got_pkt_readhdr(int *datalen, int fd, int chattygot, int timeout); const struct got_error *got_pkt_readpkt(int *outlen, int fd, char *buf, int buflen, int chattygot, int timeout); const struct got_error *got_pkt_writepkt(int fd, char *buf, int nbuf, int chattygot); got-portable-0.111/lib/object_idset.c0000644000175000017500000001523315001741021013137 /* * Copyright (c) 2022 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "got_compat.h" #include "got_object.h" #include "got_error.h" #include "got_lib_delta.h" #include "got_lib_hash.h" #include "got_lib_inflate.h" #include "got_lib_object.h" #include "got_lib_object_qid.h" #include "got_lib_object_idset.h" #include "got_lib_object_parse.h" #define GOT_OBJECT_IDSET_MIN_BUCKETS 64 struct got_object_idset { struct got_object_id_queue *ids; size_t nbuckets; unsigned int totelem; unsigned int flags; #define GOT_OBJECT_IDSET_F_TRAVERSAL 0x01 #define GOT_OBJECT_IDSET_F_NOMEM 0x02 SIPHASH_KEY key; }; struct got_object_idset * got_object_idset_alloc(void) { struct got_object_idset *set; int i; set = malloc(sizeof(*set)); if (set == NULL) return NULL; set->ids = calloc(GOT_OBJECT_IDSET_MIN_BUCKETS, sizeof(set->ids[0])); if (set->ids == NULL) { free(set); return NULL; } for (i = 0; i < GOT_OBJECT_IDSET_MIN_BUCKETS; i++) STAILQ_INIT(&set->ids[i]); set->totelem = 0; set->nbuckets = GOT_OBJECT_IDSET_MIN_BUCKETS; set->flags = 0; arc4random_buf(&set->key, sizeof(set->key)); return set; } void got_object_idset_free(struct got_object_idset *set) { size_t i; struct got_object_qid *qid; for (i = 0; i < set->nbuckets; i++) { while (!STAILQ_EMPTY(&set->ids[i])) { qid = STAILQ_FIRST(&set->ids[i]); STAILQ_REMOVE(&set->ids[i], qid, got_object_qid, entry); got_object_qid_free(qid); } } /* User data should be freed by caller. */ free(set->ids); free(set); } static uint64_t idset_hash(struct got_object_idset *set, struct got_object_id *id) { return SipHash24(&set->key, id->hash, got_hash_digest_length(id->algo)); } static const struct got_error * idset_resize(struct got_object_idset *set, size_t nbuckets) { struct got_object_id_queue *ids; size_t i; ids = calloc(nbuckets, sizeof(ids[0])); if (ids == NULL) { if (errno != ENOMEM) return got_error_from_errno("calloc"); /* Proceed with our current amount of hash buckets. */ set->flags |= GOT_OBJECT_IDSET_F_NOMEM; return NULL; } for (i = 0; i < nbuckets; i++) STAILQ_INIT(&ids[i]); arc4random_buf(&set->key, sizeof(set->key)); for (i = 0; i < set->nbuckets; i++) { while (!STAILQ_EMPTY(&set->ids[i])) { struct got_object_qid *qid; uint64_t idx; qid = STAILQ_FIRST(&set->ids[i]); STAILQ_REMOVE(&set->ids[i], qid, got_object_qid, entry); idx = idset_hash(set, &qid->id) % nbuckets; STAILQ_INSERT_HEAD(&ids[idx], qid, entry); } } free(set->ids); set->ids = ids; set->nbuckets = nbuckets; return NULL; } static const struct got_error * idset_grow(struct got_object_idset *set) { size_t nbuckets; if (set->flags & GOT_OBJECT_IDSET_F_NOMEM) return NULL; if (set->nbuckets >= UINT_MAX / 2) nbuckets = UINT_MAX; else nbuckets = set->nbuckets * 2; return idset_resize(set, nbuckets); } const struct got_error * got_object_idset_add(struct got_object_idset *set, struct got_object_id *id, void *data) { const struct got_error *err; struct got_object_qid *qid; uint64_t idx; struct got_object_id_queue *head; /* This function may resize the set. */ if (set->flags & GOT_OBJECT_IDSET_F_TRAVERSAL) return got_error_msg(GOT_ERR_NOT_IMPL, "cannot add elements to idset during traversal"); if (set->totelem == UINT_MAX) return got_error(GOT_ERR_NO_SPACE); err = got_object_qid_alloc_partial(&qid); if (err) return err; memcpy(&qid->id, id, sizeof(qid->id)); qid->data = data; idx = idset_hash(set, id) % set->nbuckets; head = &set->ids[idx]; STAILQ_INSERT_HEAD(head, qid, entry); set->totelem++; if (set->nbuckets < set->totelem) err = idset_grow(set); return err; } static struct got_object_qid * find_element(struct got_object_idset *set, struct got_object_id *id) { uint64_t idx = idset_hash(set, id) % set->nbuckets; struct got_object_id_queue *head = &set->ids[idx]; struct got_object_qid *qid; STAILQ_FOREACH(qid, head, entry) { if (got_object_id_cmp(&qid->id, id) == 0) return qid; } return NULL; } void * got_object_idset_get(struct got_object_idset *set, struct got_object_id *id) { struct got_object_qid *qid = find_element(set, id); return qid ? qid->data : NULL; } const struct got_error * got_object_idset_remove(void **data, struct got_object_idset *set, struct got_object_id *id) { uint64_t idx; struct got_object_id_queue *head; struct got_object_qid *qid; if (data) *data = NULL; if (set->totelem == 0) return got_error(GOT_ERR_NO_OBJ); if (id == NULL) { /* Remove a "random" element. */ for (idx = 0; idx < set->nbuckets; idx++) { head = &set->ids[idx]; qid = STAILQ_FIRST(head); if (qid) break; } } else { idx = idset_hash(set, id) % set->nbuckets; head = &set->ids[idx]; STAILQ_FOREACH(qid, head, entry) { if (got_object_id_cmp(&qid->id, id) == 0) break; } if (qid == NULL) return got_error_no_obj(id); } if (data) *data = qid->data; STAILQ_REMOVE(head, qid, got_object_qid, entry); got_object_qid_free(qid); set->totelem--; return NULL; } int got_object_idset_contains(struct got_object_idset *set, struct got_object_id *id) { struct got_object_qid *qid = find_element(set, id); return qid ? 1 : 0; } const struct got_error * got_object_idset_for_each(struct got_object_idset *set, const struct got_error *(*cb)(struct got_object_id *, void *, void *), void *arg) { const struct got_error *err = NULL; struct got_object_id_queue *head; struct got_object_qid *qid, *tmp; size_t i; set->flags |= GOT_OBJECT_IDSET_F_TRAVERSAL; for (i = 0; i < set->nbuckets; i++) { head = &set->ids[i]; STAILQ_FOREACH_SAFE(qid, head, entry, tmp) { err = (*cb)(&qid->id, qid->data, arg); if (err) goto done; } } done: set->flags &= ~GOT_OBJECT_IDSET_F_TRAVERSAL; return err; } int got_object_idset_num_elements(struct got_object_idset *set) { return set->totelem; } got-portable-0.111/lib/pack_create.c0000644000175000017500000014475415001741021012755 /* * Copyright (c) 2020 Ori Bernstein * Copyright (c) 2021 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_cancel.h" #include "got_object.h" #include "got_path.h" #include "got_reference.h" #include "got_repository.h" #include "got_repository_admin.h" #include "got_lib_deltify.h" #include "got_lib_delta.h" #include "got_lib_hash.h" #include "got_lib_object.h" #include "got_lib_object_idset.h" #include "got_lib_object_cache.h" #include "got_lib_deflate.h" #include "got_lib_ratelimit.h" #include "got_lib_pack.h" #include "got_lib_pack_create.h" #include "got_lib_repository.h" #include "got_lib_inflate.h" #include "got_lib_poll.h" #include "murmurhash2.h" #ifndef MIN #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b)) #endif #ifndef MAX #define MAX(_a,_b) ((_a) > (_b) ? (_a) : (_b)) #endif #ifndef nitems #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) #endif static const struct got_error * alloc_meta(struct got_pack_meta **new, struct got_object_id *id, const char *path, int obj_type, time_t mtime, uint32_t seed) { struct got_pack_meta *m; *new = NULL; m = calloc(1, sizeof(*m)); if (m == NULL) return got_error_from_errno("calloc"); memcpy(&m->id, id, sizeof(m->id)); m->path_hash = murmurhash2(path, strlen(path), seed); m->obj_type = obj_type; m->mtime = mtime; *new = m; return NULL; } static void clear_meta(struct got_pack_meta *meta) { if (meta == NULL) return; meta->path_hash = 0; free(meta->delta_buf); meta->delta_buf = NULL; free(meta->base_obj_id); meta->base_obj_id = NULL; meta->reused_delta_offset = 0; got_deltify_free(meta->dtab); meta->dtab = NULL; } static void free_nmeta(struct got_pack_meta **meta, int nmeta) { int i; for (i = 0; i < nmeta; i++) clear_meta(meta[i]); free(meta); } static int delta_order_cmp(const void *pa, const void *pb) { struct got_pack_meta *a, *b; a = *(struct got_pack_meta **)pa; b = *(struct got_pack_meta **)pb; if (a->obj_type != b->obj_type) return a->obj_type - b->obj_type; if (a->path_hash < b->path_hash) return -1; if (a->path_hash > b->path_hash) return 1; if (a->mtime < b->mtime) return -1; if (a->mtime > b->mtime) return 1; return got_object_id_cmp(&a->id, &b->id); } static off_t delta_size(struct got_delta_instruction *deltas, int ndeltas) { int i; off_t size = 32; for (i = 0; i < ndeltas; i++) { if (deltas[i].copy) size += GOT_DELTA_SIZE_SHIFT; else size += deltas[i].len + 1; } return size; } static const struct got_error * append(unsigned char **p, size_t *len, off_t *sz, void *seg, int nseg) { char *n; if (*len + nseg >= *sz) { while (*len + nseg >= *sz) *sz += *sz / 2; n = realloc(*p, *sz); if (n == NULL) return got_error_from_errno("realloc"); *p = n; } memcpy(*p + *len, seg, nseg); *len += nseg; return NULL; } static const struct got_error * encode_delta_in_mem(struct got_pack_meta *m, struct got_raw_object *o, struct got_delta_instruction *deltas, int ndeltas, off_t delta_size, off_t base_size) { const struct got_error *err; unsigned char buf[16], *bp; int i, j; size_t len = 0, compressed_len; off_t bufsize = delta_size; off_t n; struct got_delta_instruction *d; uint8_t *delta_buf; delta_buf = malloc(bufsize); if (delta_buf == NULL) return got_error_from_errno("malloc"); /* base object size */ buf[0] = base_size & GOT_DELTA_SIZE_VAL_MASK; n = base_size >> GOT_DELTA_SIZE_SHIFT; for (i = 1; n > 0; i++) { buf[i - 1] |= GOT_DELTA_SIZE_MORE; buf[i] = n & GOT_DELTA_SIZE_VAL_MASK; n >>= GOT_DELTA_SIZE_SHIFT; } err = append(&delta_buf, &len, &bufsize, buf, i); if (err) goto done; /* target object size */ buf[0] = o->size & GOT_DELTA_SIZE_VAL_MASK; n = o->size >> GOT_DELTA_SIZE_SHIFT; for (i = 1; n > 0; i++) { buf[i - 1] |= GOT_DELTA_SIZE_MORE; buf[i] = n & GOT_DELTA_SIZE_VAL_MASK; n >>= GOT_DELTA_SIZE_SHIFT; } err = append(&delta_buf, &len, &bufsize, buf, i); if (err) goto done; for (j = 0; j < ndeltas; j++) { d = &deltas[j]; if (d->copy) { n = d->offset; bp = &buf[1]; buf[0] = GOT_DELTA_BASE_COPY; for (i = 0; i < 4; i++) { /* DELTA_COPY_OFF1 ... DELTA_COPY_OFF4 */ buf[0] |= 1 << i; *bp++ = n & 0xff; n >>= 8; if (n == 0) break; } n = d->len; if (n != GOT_DELTA_COPY_DEFAULT_LEN) { /* DELTA_COPY_LEN1 ... DELTA_COPY_LEN3 */ for (i = 0; i < 3 && n > 0; i++) { buf[0] |= 1 << (i + 4); *bp++ = n & 0xff; n >>= 8; } } err = append(&delta_buf, &len, &bufsize, buf, bp - buf); if (err) goto done; } else if (o->f == NULL) { n = 0; while (n != d->len) { buf[0] = (d->len - n < 127) ? d->len - n : 127; err = append(&delta_buf, &len, &bufsize, buf, 1); if (err) goto done; err = append(&delta_buf, &len, &bufsize, o->data + o->hdrlen + d->offset + n, buf[0]); if (err) goto done; n += buf[0]; } } else { char content[128]; size_t r; if (fseeko(o->f, o->hdrlen + d->offset, SEEK_SET) == -1) { err = got_error_from_errno("fseeko"); goto done; } n = 0; while (n != d->len) { buf[0] = (d->len - n < 127) ? d->len - n : 127; err = append(&delta_buf, &len, &bufsize, buf, 1); if (err) goto done; r = fread(content, 1, buf[0], o->f); if (r != buf[0]) { err = got_ferror(o->f, GOT_ERR_IO); goto done; } err = append(&delta_buf, &len, &bufsize, content, buf[0]); if (err) goto done; n += buf[0]; } } } err = got_deflate_to_mem_mmap(&m->delta_buf, &compressed_len, NULL, NULL, delta_buf, 0, len); if (err) goto done; m->delta_len = len; m->delta_compressed_len = compressed_len; done: free(delta_buf); return err; } static const struct got_error * encode_delta(struct got_pack_meta *m, struct got_raw_object *o, struct got_delta_instruction *deltas, int ndeltas, off_t base_size, FILE *f) { const struct got_error *err; unsigned char buf[16], *bp; int i, j; off_t n; struct got_deflate_buf zb; struct got_delta_instruction *d; off_t delta_len = 0, compressed_len = 0; err = got_deflate_init(&zb, NULL, GOT_DEFLATE_BUFSIZE); if (err) return err; /* base object size */ buf[0] = base_size & GOT_DELTA_SIZE_VAL_MASK; n = base_size >> GOT_DELTA_SIZE_SHIFT; for (i = 1; n > 0; i++) { buf[i - 1] |= GOT_DELTA_SIZE_MORE; buf[i] = n & GOT_DELTA_SIZE_VAL_MASK; n >>= GOT_DELTA_SIZE_SHIFT; } err = got_deflate_append_to_file_mmap(&zb, &compressed_len, buf, 0, i, f, NULL); if (err) goto done; delta_len += i; /* target object size */ buf[0] = o->size & GOT_DELTA_SIZE_VAL_MASK; n = o->size >> GOT_DELTA_SIZE_SHIFT; for (i = 1; n > 0; i++) { buf[i - 1] |= GOT_DELTA_SIZE_MORE; buf[i] = n & GOT_DELTA_SIZE_VAL_MASK; n >>= GOT_DELTA_SIZE_SHIFT; } err = got_deflate_append_to_file_mmap(&zb, &compressed_len, buf, 0, i, f, NULL); if (err) goto done; delta_len += i; for (j = 0; j < ndeltas; j++) { d = &deltas[j]; if (d->copy) { n = d->offset; bp = &buf[1]; buf[0] = GOT_DELTA_BASE_COPY; for (i = 0; i < 4; i++) { /* DELTA_COPY_OFF1 ... DELTA_COPY_OFF4 */ buf[0] |= 1 << i; *bp++ = n & 0xff; n >>= 8; if (n == 0) break; } n = d->len; if (n != GOT_DELTA_COPY_DEFAULT_LEN) { /* DELTA_COPY_LEN1 ... DELTA_COPY_LEN3 */ for (i = 0; i < 3 && n > 0; i++) { buf[0] |= 1 << (i + 4); *bp++ = n & 0xff; n >>= 8; } } err = got_deflate_append_to_file_mmap(&zb, &compressed_len, buf, 0, bp - buf, f, NULL); if (err) goto done; delta_len += (bp - buf); } else if (o->f == NULL) { n = 0; while (n != d->len) { buf[0] = (d->len - n < 127) ? d->len - n : 127; err = got_deflate_append_to_file_mmap(&zb, &compressed_len, buf, 0, 1, f, NULL); if (err) goto done; delta_len++; err = got_deflate_append_to_file_mmap(&zb, &compressed_len, o->data + o->hdrlen + d->offset + n, 0, buf[0], f, NULL); if (err) goto done; delta_len += buf[0]; n += buf[0]; } } else { char content[128]; size_t r; if (fseeko(o->f, o->hdrlen + d->offset, SEEK_SET) == -1) { err = got_error_from_errno("fseeko"); goto done; } n = 0; while (n != d->len) { buf[0] = (d->len - n < 127) ? d->len - n : 127; err = got_deflate_append_to_file_mmap(&zb, &compressed_len, buf, 0, 1, f, NULL); if (err) goto done; delta_len++; r = fread(content, 1, buf[0], o->f); if (r != buf[0]) { err = got_ferror(o->f, GOT_ERR_IO); goto done; } err = got_deflate_append_to_file_mmap(&zb, &compressed_len, content, 0, buf[0], f, NULL); if (err) goto done; delta_len += buf[0]; n += buf[0]; } } } err = got_deflate_flush(&zb, f, NULL, &compressed_len); if (err) goto done; /* sanity check */ if (compressed_len != ftello(f) - m->delta_offset) { err = got_error(GOT_ERR_COMPRESSION); goto done; } m->delta_len = delta_len; m->delta_compressed_len = compressed_len; done: got_deflate_end(&zb); return err; } const struct got_error * got_pack_report_progress(got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, int ncolored, int nfound, int ntrees, off_t packfile_size, int ncommits, int nobj_total, int obj_deltify, int nobj_written, int pack_done) { const struct got_error *err; int elapsed; if (progress_cb == NULL) return NULL; err = got_ratelimit_check(&elapsed, rl); if (err || !elapsed) return err; return progress_cb(progress_arg, ncolored, nfound, ntrees, packfile_size, ncommits, nobj_total, obj_deltify, nobj_written, pack_done); } const struct got_error * got_pack_add_meta(struct got_pack_meta *m, struct got_pack_metavec *v) { if (v->nmeta == v->metasz){ size_t newsize = 2 * v->metasz; struct got_pack_meta **new; new = reallocarray(v->meta, newsize, sizeof(*new)); if (new == NULL) return got_error_from_errno("reallocarray"); v->meta = new; v->metasz = newsize; } v->meta[v->nmeta++] = m; return NULL; } const struct got_error * got_pack_find_pack_for_reuse(struct got_packidx **best_packidx, struct got_repository *repo) { const struct got_error *err = NULL; struct got_pathlist_entry *pe; const char *best_packidx_path = NULL; int nobj_max = 0; *best_packidx = NULL; RB_FOREACH(pe, got_pathlist_head, &repo->packidx_paths) { const char *path_packidx = pe->path; struct got_packidx *packidx; int nobj; err = got_repo_get_packidx(&packidx, path_packidx, repo); if (err) break; nobj = be32toh(packidx->hdr.fanout_table[0xff]); if (nobj > nobj_max) { best_packidx_path = path_packidx; nobj_max = nobj; } } if (best_packidx_path) { err = got_repo_get_packidx(best_packidx, best_packidx_path, repo); } return err; } const struct got_error * got_pack_cache_pack_for_packidx(struct got_pack **pack, struct got_packidx *packidx, struct got_repository *repo) { const struct got_error *err; char *path_packfile = NULL; err = got_packidx_get_packfile_path(&path_packfile, packidx->path_packidx); if (err) return err; *pack = got_repo_get_cached_pack(repo, path_packfile); if (*pack == NULL) { err = got_repo_cache_pack(pack, repo, path_packfile, packidx); if (err) goto done; } done: free(path_packfile); return err; } static const struct got_error * pick_deltas(struct got_pack_meta **meta, int nmeta, int ncolored, int nfound, int ntrees, int ncommits, int nreused, FILE *delta_cache, struct got_repository *repo, got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; struct got_pack_meta *m = NULL, *base = NULL; struct got_raw_object *raw = NULL, *base_raw = NULL; struct got_delta_instruction *deltas = NULL, *best_deltas = NULL; int i, j, ndeltas, best_ndeltas; off_t size, best_size; const int max_base_candidates = 3; size_t delta_memsize = 0; const size_t max_delta_memsize = 4 * GOT_DELTA_RESULT_SIZE_CACHED_MAX; int outfd = -1; uint32_t delta_seed; delta_seed = arc4random(); qsort(meta, nmeta, sizeof(struct got_pack_meta *), delta_order_cmp); for (i = 0; i < nmeta; i++) { if (cancel_cb) { err = (*cancel_cb)(cancel_arg); if (err) break; } err = got_pack_report_progress(progress_cb, progress_arg, rl, ncolored, nfound, ntrees, 0L, ncommits, nreused + nmeta, nreused + i, 0, 0); if (err) goto done; m = meta[i]; if (m->obj_type == GOT_OBJ_TYPE_COMMIT || m->obj_type == GOT_OBJ_TYPE_TAG) continue; err = got_object_raw_open(&raw, &outfd, repo, &m->id); if (err) goto done; m->size = raw->size; if (raw->f == NULL) { err = got_deltify_init_mem(&m->dtab, raw->data, raw->hdrlen, raw->size + raw->hdrlen, delta_seed); } else { err = got_deltify_init(&m->dtab, raw->f, raw->hdrlen, raw->size + raw->hdrlen, delta_seed); } if (err) goto done; if (i > max_base_candidates) { struct got_pack_meta *n = NULL; n = meta[i - (max_base_candidates + 1)]; got_deltify_free(n->dtab); n->dtab = NULL; } best_size = raw->size; best_ndeltas = 0; for (j = MAX(0, i - max_base_candidates); j < i; j++) { if (cancel_cb) { err = (*cancel_cb)(cancel_arg); if (err) goto done; } base = meta[j]; /* long chains make unpacking slow, avoid such bases */ if (base->nchain >= 128 || base->obj_type != m->obj_type) continue; err = got_object_raw_open(&base_raw, &outfd, repo, &base->id); if (err) goto done; if (raw->f == NULL && base_raw->f == NULL) { err = got_deltify_mem_mem(&deltas, &ndeltas, raw->data, raw->hdrlen, raw->size + raw->hdrlen, delta_seed, base->dtab, base_raw->data, base_raw->hdrlen, base_raw->size + base_raw->hdrlen); } else if (raw->f == NULL) { err = got_deltify_mem_file(&deltas, &ndeltas, raw->data, raw->hdrlen, raw->size + raw->hdrlen, delta_seed, base->dtab, base_raw->f, base_raw->hdrlen, base_raw->size + base_raw->hdrlen); } else if (base_raw->f == NULL) { err = got_deltify_file_mem(&deltas, &ndeltas, raw->f, raw->hdrlen, raw->size + raw->hdrlen, delta_seed, base->dtab, base_raw->data, base_raw->hdrlen, base_raw->size + base_raw->hdrlen); } else { err = got_deltify(&deltas, &ndeltas, raw->f, raw->hdrlen, raw->size + raw->hdrlen, delta_seed, base->dtab, base_raw->f, base_raw->hdrlen, base_raw->size + base_raw->hdrlen); } got_object_raw_close(base_raw); base_raw = NULL; if (err) goto done; size = delta_size(deltas, ndeltas); if (size + 32 < best_size){ /* * if we already picked a best delta, * replace it. */ best_size = size; free(best_deltas); best_deltas = deltas; best_ndeltas = ndeltas; deltas = NULL; m->nchain = base->nchain + 1; m->prev = base; m->head = base->head; if (m->head == NULL) m->head = base; } else { free(deltas); deltas = NULL; ndeltas = 0; } } if (best_ndeltas > 0) { if (best_size <= GOT_DELTA_RESULT_SIZE_CACHED_MAX && delta_memsize + best_size <= max_delta_memsize) { delta_memsize += best_size; err = encode_delta_in_mem(m, raw, best_deltas, best_ndeltas, best_size, m->prev->size); } else { m->delta_offset = ftello(delta_cache); err = encode_delta(m, raw, best_deltas, best_ndeltas, m->prev->size, delta_cache); } free(best_deltas); best_deltas = NULL; best_ndeltas = 0; if (err) goto done; } got_object_raw_close(raw); raw = NULL; } done: if (raw) got_object_raw_close(raw); if (base_raw) got_object_raw_close(base_raw); if (outfd != -1 && close(outfd) == -1 && err == NULL) err = got_error_from_errno("close"); free(deltas); free(best_deltas); return err; } static const struct got_error * search_packidx(int *found, struct got_object_id *id, struct got_repository *repo) { const struct got_error *err = NULL; struct got_packidx *packidx = NULL; int idx; *found = 0; err = got_repo_search_packidx(&packidx, &idx, repo, id); if (err == NULL) *found = 1; /* object is already packed */ else if (err->code == GOT_ERR_NO_OBJ) err = NULL; return err; } const struct got_error * got_pack_add_object(int want_meta, struct got_object_idset *idset, struct got_object_id *id, const char *path, int obj_type, time_t mtime, uint32_t seed, int loose_obj_only, struct got_repository *repo, int *ncolored, int *nfound, int *ntrees, got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl) { const struct got_error *err; struct got_pack_meta *m = NULL; if (loose_obj_only) { int is_packed; err = search_packidx(&is_packed, id, repo); if (err) return err; if (is_packed && want_meta) return NULL; } if (want_meta) { err = alloc_meta(&m, id, path, obj_type, mtime, seed); if (err) return err; (*nfound)++; err = got_pack_report_progress(progress_cb, progress_arg, rl, *ncolored, *nfound, *ntrees, 0L, 0, 0, 0, 0, 0); if (err) { clear_meta(m); free(m); return err; } } err = got_object_idset_add(idset, id, m); if (err) { clear_meta(m); free(m); } return err; } const struct got_error * got_pack_load_tree_entries(struct got_object_id_queue *ids, int want_meta, struct got_object_idset *idset, struct got_object_idset *idset_exclude, struct got_tree_object *tree, const char *dpath, time_t mtime, uint32_t seed, struct got_repository *repo, int loose_obj_only, int *ncolored, int *nfound, int *ntrees, got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err; char *p = NULL; int i; (*ntrees)++; err = got_pack_report_progress(progress_cb, progress_arg, rl, *ncolored, *nfound, *ntrees, 0L, 0, 0, 0, 0, 0); if (err) return err; for (i = 0; i < got_object_tree_get_nentries(tree); i++) { struct got_tree_entry *e = got_object_tree_get_entry(tree, i); struct got_object_id *id = got_tree_entry_get_id(e); mode_t mode = got_tree_entry_get_mode(e); if (cancel_cb) { err = (*cancel_cb)(cancel_arg); if (err) break; } if (got_object_tree_entry_is_submodule(e) || got_object_idset_contains(idset, id) || got_object_idset_contains(idset_exclude, id)) continue; /* * If got-read-pack is crawling trees for us then * we are only here to collect blob IDs. */ if (ids == NULL && S_ISDIR(mode)) continue; if (asprintf(&p, "%s%s%s", dpath, got_path_is_root_dir(dpath) ? "" : "/", got_tree_entry_get_name(e)) == -1) { err = got_error_from_errno("asprintf"); break; } if (S_ISDIR(mode)) { struct got_object_qid *qid; err = got_object_qid_alloc(&qid, id); if (err) break; qid->data = p; p = NULL; STAILQ_INSERT_TAIL(ids, qid, entry); } else if (S_ISREG(mode) || S_ISLNK(mode)) { err = got_pack_add_object(want_meta, want_meta ? idset : idset_exclude, id, p, GOT_OBJ_TYPE_BLOB, mtime, seed, loose_obj_only, repo, ncolored, nfound, ntrees, progress_cb, progress_arg, rl); if (err) break; free(p); p = NULL; } else { free(p); p = NULL; } } free(p); return err; } const struct got_error * got_pack_load_tree(int want_meta, struct got_object_idset *idset, struct got_object_idset *idset_exclude, struct got_object_id *tree_id, const char *dpath, time_t mtime, uint32_t seed, struct got_repository *repo, int loose_obj_only, int *ncolored, int *nfound, int *ntrees, got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; struct got_object_id_queue tree_ids; struct got_object_qid *qid; struct got_tree_object *tree = NULL; if (got_object_idset_contains(idset, tree_id) || got_object_idset_contains(idset_exclude, tree_id)) return NULL; err = got_object_qid_alloc(&qid, tree_id); if (err) return err; qid->data = strdup(dpath); if (qid->data == NULL) { err = got_error_from_errno("strdup"); got_object_qid_free(qid); return err; } STAILQ_INIT(&tree_ids); STAILQ_INSERT_TAIL(&tree_ids, qid, entry); while (!STAILQ_EMPTY(&tree_ids)) { const char *path; if (cancel_cb) { err = (*cancel_cb)(cancel_arg); if (err) break; } qid = STAILQ_FIRST(&tree_ids); STAILQ_REMOVE_HEAD(&tree_ids, entry); path = qid->data; if (got_object_idset_contains(idset, &qid->id) || got_object_idset_contains(idset_exclude, &qid->id)) { free(qid->data); got_object_qid_free(qid); continue; } err = got_pack_add_object(want_meta, want_meta ? idset : idset_exclude, &qid->id, path, GOT_OBJ_TYPE_TREE, mtime, seed, loose_obj_only, repo, ncolored, nfound, ntrees, progress_cb, progress_arg, rl); if (err) { free(qid->data); got_object_qid_free(qid); break; } err = got_object_open_as_tree(&tree, repo, &qid->id); if (err) { free(qid->data); got_object_qid_free(qid); break; } err = got_pack_load_tree_entries(&tree_ids, want_meta, idset, idset_exclude, tree, path, mtime, seed, repo, loose_obj_only, ncolored, nfound, ntrees, progress_cb, progress_arg, rl, cancel_cb, cancel_arg); free(qid->data); got_object_qid_free(qid); if (err) break; got_object_tree_close(tree); tree = NULL; } STAILQ_FOREACH(qid, &tree_ids, entry) free(qid->data); got_object_id_queue_free(&tree_ids); if (tree) got_object_tree_close(tree); return err; } static const struct got_error * load_commit(int want_meta, struct got_object_idset *idset, struct got_object_idset *idset_exclude, struct got_object_id *id, struct got_repository *repo, uint32_t seed, int loose_obj_only, int *ncolored, int *nfound, int *ntrees, got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err; struct got_commit_object *commit; if (got_object_idset_contains(idset, id) || got_object_idset_contains(idset_exclude, id)) return NULL; if (loose_obj_only) { int is_packed; err = search_packidx(&is_packed, id, repo); if (err) return err; if (is_packed && want_meta) return NULL; } err = got_object_open_as_commit(&commit, repo, id); if (err) return err; err = got_pack_add_object(want_meta, want_meta ? idset : idset_exclude, id, "", GOT_OBJ_TYPE_COMMIT, got_object_commit_get_committer_time(commit), seed, loose_obj_only, repo, ncolored, nfound, ntrees, progress_cb, progress_arg, rl); if (err) goto done; err = got_pack_load_tree(want_meta, idset, idset_exclude, got_object_commit_get_tree_id(commit), "", got_object_commit_get_committer_time(commit), seed, repo, loose_obj_only, ncolored, nfound, ntrees, progress_cb, progress_arg, rl, cancel_cb, cancel_arg); done: got_object_commit_close(commit); return err; } static const struct got_error * load_tag(int want_meta, struct got_object_idset *idset, struct got_object_idset *idset_exclude, struct got_object_id *id, struct got_repository *repo, uint32_t seed, int loose_obj_only, int *ncolored, int *nfound, int *ntrees, got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err; struct got_tag_object *tag = NULL; if (got_object_idset_contains(idset, id) || got_object_idset_contains(idset_exclude, id)) return NULL; if (loose_obj_only) { int is_packed; err = search_packidx(&is_packed, id, repo); if (err) return err; if (is_packed && want_meta) return NULL; } err = got_object_open_as_tag(&tag, repo, id); if (err) return err; err = got_pack_add_object(want_meta, want_meta ? idset : idset_exclude, id, "", GOT_OBJ_TYPE_TAG, got_object_tag_get_tagger_time(tag), seed, loose_obj_only, repo, ncolored, nfound, ntrees, progress_cb, progress_arg, rl); if (err) goto done; switch (got_object_tag_get_object_type(tag)) { case GOT_OBJ_TYPE_COMMIT: err = load_commit(want_meta, idset, idset_exclude, got_object_tag_get_object_id(tag), repo, seed, loose_obj_only, ncolored, nfound, ntrees, progress_cb, progress_arg, rl, cancel_cb, cancel_arg); break; case GOT_OBJ_TYPE_TREE: err = got_pack_load_tree(want_meta, idset, idset_exclude, got_object_tag_get_object_id(tag), "", got_object_tag_get_tagger_time(tag), seed, repo, loose_obj_only, ncolored, nfound, ntrees, progress_cb, progress_arg, rl, cancel_cb, cancel_arg); break; default: break; } done: got_object_tag_close(tag); return err; } const struct got_error * got_pack_paint_commit(struct got_object_qid *qid, intptr_t color) { if (color < 0 || color >= COLOR_MAX) return got_error(GOT_ERR_RANGE); qid->data = (void *)color; return NULL; } const struct got_error * got_pack_repaint_parent_commits(struct got_object_id *commit_id, int color, struct got_object_idset *set, struct got_object_idset *skip, struct got_repository *repo) { const struct got_error *err; struct got_object_id_queue ids; struct got_object_qid *qid; struct got_commit_object *commit; const struct got_object_id_queue *parents; STAILQ_INIT(&ids); err = got_object_open_as_commit(&commit, repo, commit_id); if (err) return err; while (commit) { parents = got_object_commit_get_parent_ids(commit); if (parents) { struct got_object_qid *pid; STAILQ_FOREACH(pid, parents, entry) { /* * No need to traverse parents which are * already in the desired set or are * marked for skipping already. */ if (got_object_idset_contains(set, &pid->id)) continue; if (skip != set && got_object_idset_contains(skip, &pid->id)) continue; err = got_pack_queue_commit_id(&ids, &pid->id, color, repo); if (err) break; } } got_object_commit_close(commit); commit = NULL; qid = STAILQ_FIRST(&ids); if (qid == NULL) break; STAILQ_REMOVE_HEAD(&ids, entry); err = got_object_idset_add(set, &qid->id, NULL); if (err) break; err = got_object_open_as_commit(&commit, repo, &qid->id); if (err) break; got_object_qid_free(qid); qid = NULL; } if (commit) got_object_commit_close(commit); if (qid) got_object_qid_free(qid); got_object_id_queue_free(&ids); return err; } const struct got_error * got_pack_queue_commit_id(struct got_object_id_queue *ids, struct got_object_id *id, intptr_t color, struct got_repository *repo) { const struct got_error *err; struct got_object_qid *qid; err = got_object_qid_alloc(&qid, id); if (err) return err; STAILQ_INSERT_TAIL(ids, qid, entry); return got_pack_paint_commit(qid, color); } struct append_id_arg { struct got_object_id **array; int idx; struct got_object_idset *drop; struct got_object_idset *skip; }; static const struct got_error * append_id(struct got_object_id *id, void *data, void *arg) { struct append_id_arg *a = arg; if (got_object_idset_contains(a->skip, id) || got_object_idset_contains(a->drop, id)) return NULL; a->array[++a->idx] = got_object_id_dup(id); if (a->array[a->idx] == NULL) return got_error_from_errno("got_object_id_dup"); return NULL; } static const struct got_error * free_meta(struct got_object_id *id, void *data, void *arg) { struct got_pack_meta *meta = data; clear_meta(meta); free(meta); return NULL; } static const struct got_error * queue_commit_or_tag_id(struct got_object_id *id, intptr_t color, struct got_object_id_queue *ids, struct got_repository *repo) { const struct got_error *err; struct got_tag_object *tag = NULL; int obj_type; err = got_object_get_type(&obj_type, repo, id); if (err) return err; if (obj_type == GOT_OBJ_TYPE_TAG) { err = got_object_open_as_tag(&tag, repo, id); if (err) return err; obj_type = got_object_tag_get_object_type(tag); id = got_object_tag_get_object_id(tag); } if (obj_type == GOT_OBJ_TYPE_COMMIT) { err = got_pack_queue_commit_id(ids, id, color, repo); if (err) goto done; } done: if (tag) got_object_tag_close(tag); return err; } const struct got_error * got_pack_find_pack_for_commit_painting(struct got_packidx **best_packidx, struct got_object_id_queue *ids, struct got_repository *repo) { const struct got_error *err = NULL; struct got_pathlist_entry *pe; const char *best_packidx_path = NULL; int nobj_max = 0; int ncommits_max = 0; *best_packidx = NULL; /* * Find the largest pack which contains at least some of the * commits we are interested in. */ RB_FOREACH(pe, got_pathlist_head, &repo->packidx_paths) { const char *path_packidx = pe->path; struct got_packidx *packidx; int nobj, idx, ncommits = 0; struct got_object_qid *qid; err = got_repo_get_packidx(&packidx, path_packidx, repo); if (err) break; nobj = be32toh(packidx->hdr.fanout_table[0xff]); if (nobj <= nobj_max) continue; STAILQ_FOREACH(qid, ids, entry) { idx = got_packidx_get_object_idx(packidx, &qid->id); if (idx != -1) ncommits++; } if (ncommits > ncommits_max) { best_packidx_path = path_packidx; nobj_max = nobj; ncommits_max = ncommits; } } if (best_packidx_path && err == NULL) { err = got_repo_get_packidx(best_packidx, best_packidx_path, repo); } return err; } static const struct got_error * findtwixt(struct got_object_id ***res, int *nres, int *ncolored, struct got_object_id **head, int nhead, struct got_object_id **tail, int ntail, struct got_repository *repo, got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; struct got_object_id_queue ids; struct got_object_idset *keep, *drop, *skip = NULL; int i, nkeep, nqueued = 0; STAILQ_INIT(&ids); *res = NULL; *nres = 0; *ncolored = 0; keep = got_object_idset_alloc(); if (keep == NULL) return got_error_from_errno("got_object_idset_alloc"); drop = got_object_idset_alloc(); if (drop == NULL) { err = got_error_from_errno("got_object_idset_alloc"); goto done; } skip = got_object_idset_alloc(); if (skip == NULL) { err = got_error_from_errno("got_object_idset_alloc"); goto done; } for (i = 0; i < nhead; i++) { struct got_object_id *id = head[i]; if (id == NULL) continue; err = queue_commit_or_tag_id(id, COLOR_KEEP, &ids, repo); if (err) goto done; nqueued++; } for (i = 0; i < ntail; i++) { struct got_object_id *id = tail[i]; if (id == NULL) continue; err = queue_commit_or_tag_id(id, COLOR_DROP, &ids, repo); if (err) goto done; nqueued++; } err = got_pack_paint_commits(ncolored, &ids, nqueued, keep, drop, skip, repo, progress_cb, progress_arg, rl, cancel_cb, cancel_arg); if (err) goto done; nkeep = got_object_idset_num_elements(keep); if (nkeep > 0) { struct append_id_arg arg; arg.array = calloc(nkeep, sizeof(struct got_object_id *)); if (arg.array == NULL) { err = got_error_from_errno("calloc"); goto done; } arg.idx = -1; arg.skip = skip; arg.drop = drop; err = got_object_idset_for_each(keep, append_id, &arg); if (err) { free(arg.array); goto done; } *res = arg.array; *nres = arg.idx + 1; } done: got_object_idset_free(keep); got_object_idset_free(drop); if (skip) got_object_idset_free(skip); got_object_id_queue_free(&ids); return err; } static const struct got_error * find_pack_for_enumeration(struct got_packidx **best_packidx, struct got_object_id **ids, int nids, struct got_repository *repo) { const struct got_error *err = NULL; struct got_pathlist_entry *pe; const char *best_packidx_path = NULL; int nobj_max = 0; int ncommits_max = 0; *best_packidx = NULL; /* * Find the largest pack which contains at least some of the * commits and tags we are interested in. */ RB_FOREACH(pe, got_pathlist_head, &repo->packidx_paths) { const char *path_packidx = pe->path; struct got_packidx *packidx; int nobj, i, idx, ncommits = 0; err = got_repo_get_packidx(&packidx, path_packidx, repo); if (err) break; nobj = be32toh(packidx->hdr.fanout_table[0xff]); if (nobj <= nobj_max) continue; for (i = 0; i < nids; i++) { idx = got_packidx_get_object_idx(packidx, ids[i]); if (idx != -1) ncommits++; } if (ncommits > ncommits_max) { best_packidx_path = path_packidx; nobj_max = nobj; ncommits_max = ncommits; } } if (best_packidx_path && err == NULL) { err = got_repo_get_packidx(best_packidx, best_packidx_path, repo); } return err; } static const struct got_error * load_object_ids(int *ncolored, int *nfound, int *ntrees, struct got_object_idset *idset, struct got_object_id **theirs, int ntheirs, struct got_object_id **ours, int nours, struct got_repository *repo, uint32_t seed, int loose_obj_only, got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; struct got_object_id **ids = NULL; struct got_packidx *packidx = NULL; int i, nobj = 0, obj_type, found_all_objects = 0; struct got_object_idset *idset_exclude; idset_exclude = got_object_idset_alloc(); if (idset_exclude == NULL) return got_error_from_errno("got_object_idset_alloc"); *ncolored = 0; *nfound = 0; *ntrees = 0; err = findtwixt(&ids, &nobj, ncolored, ours, nours, theirs, ntheirs, repo, progress_cb, progress_arg, rl, cancel_cb, cancel_arg); if (err) goto done; err = find_pack_for_enumeration(&packidx, theirs, ntheirs, repo); if (err) goto done; if (packidx) { err = got_pack_load_packed_object_ids(&found_all_objects, theirs, ntheirs, NULL, 0, 0, seed, idset, idset_exclude, loose_obj_only, repo, packidx, ncolored, nfound, ntrees, progress_cb, progress_arg, rl, cancel_cb, cancel_arg); if (err) goto done; } for (i = 0; i < ntheirs; i++) { struct got_object_id *id = theirs[i]; if (id == NULL) continue; err = got_object_get_type(&obj_type, repo, id); if (err) return err; if (obj_type == GOT_OBJ_TYPE_COMMIT) { if (!found_all_objects) { err = load_commit(0, idset, idset_exclude, id, repo, seed, loose_obj_only, ncolored, nfound, ntrees, progress_cb, progress_arg, rl, cancel_cb, cancel_arg); if (err) goto done; } } else if (obj_type == GOT_OBJ_TYPE_TAG) { err = load_tag(0, idset, idset_exclude, id, repo, seed, loose_obj_only, ncolored, nfound, ntrees, progress_cb, progress_arg, rl, cancel_cb, cancel_arg); if (err) goto done; } } found_all_objects = 0; err = find_pack_for_enumeration(&packidx, ids, nobj, repo); if (err) goto done; if (packidx) { err = got_pack_load_packed_object_ids(&found_all_objects, ids, nobj, theirs, ntheirs, 1, seed, idset, idset_exclude, loose_obj_only, repo, packidx, ncolored, nfound, ntrees, progress_cb, progress_arg, rl, cancel_cb, cancel_arg); if (err) goto done; } if (!found_all_objects) { for (i = 0; i < nobj; i++) { err = load_commit(1, idset, idset_exclude, ids[i], repo, seed, loose_obj_only, ncolored, nfound, ntrees, progress_cb, progress_arg, rl, cancel_cb, cancel_arg); if (err) goto done; } } for (i = 0; i < nours; i++) { struct got_object_id *id = ours[i]; struct got_pack_meta *m; if (id == NULL) continue; m = got_object_idset_get(idset, id); if (m == NULL) { err = got_object_get_type(&obj_type, repo, id); if (err) goto done; } else obj_type = m->obj_type; if (obj_type != GOT_OBJ_TYPE_TAG) continue; err = load_tag(1, idset, idset_exclude, id, repo, seed, loose_obj_only, ncolored, nfound, ntrees, progress_cb, progress_arg, rl, cancel_cb, cancel_arg); if (err) goto done; } done: for (i = 0; i < nobj; i++) { free(ids[i]); } free(ids); got_object_idset_free(idset_exclude); return err; } static const struct got_error * hwrite(int fd, const void *buf, off_t len, struct got_hash *ctx) { got_hash_update(ctx, buf, len); return got_poll_write_full(fd, buf, len); } static const struct got_error * hcopy(FILE *fsrc, int fd_dst, off_t len, struct got_hash *ctx) { const struct got_error *err; unsigned char buf[65536]; off_t remain = len; size_t n; while (remain > 0) { size_t copylen = MIN(sizeof(buf), remain); n = fread(buf, 1, copylen, fsrc); if (n != copylen) return got_ferror(fsrc, GOT_ERR_IO); got_hash_update(ctx, buf, copylen); err = got_poll_write_full(fd_dst, buf, copylen); if (err) return err; remain -= copylen; } return NULL; } static const struct got_error * hcopy_mmap(uint8_t *src, off_t src_offset, size_t src_size, int fd, off_t len, struct got_hash *ctx) { if (src_offset + len > src_size) return got_error(GOT_ERR_RANGE); got_hash_update(ctx, src + src_offset, len); return got_poll_write_full(fd, src + src_offset, len); } static void putbe32(char *b, uint32_t n) { b[0] = n >> 24; b[1] = n >> 16; b[2] = n >> 8; b[3] = n >> 0; } static int write_order_cmp(const void *pa, const void *pb) { struct got_pack_meta *a, *b, *ahd, *bhd; a = *(struct got_pack_meta **)pa; b = *(struct got_pack_meta **)pb; ahd = (a->head == NULL) ? a : a->head; bhd = (b->head == NULL) ? b : b->head; if (bhd->mtime < ahd->mtime) return -1; if (bhd->mtime > ahd->mtime) return 1; if (bhd < ahd) return -1; if (bhd > ahd) return 1; if (a->nchain != b->nchain) return a->nchain - b->nchain; if (a->mtime < b->mtime) return -1; if (a->mtime > b->mtime) return 1; return got_object_id_cmp(&a->id, &b->id); } static int reuse_write_order_cmp(const void *pa, const void *pb) { struct got_pack_meta *a, *b; a = *(struct got_pack_meta **)pa; b = *(struct got_pack_meta **)pb; if (a->reused_delta_offset < b->reused_delta_offset) return -1; if (a->reused_delta_offset > b->reused_delta_offset) return 1; return 0; } static const struct got_error * packhdr(int *hdrlen, char *hdr, size_t bufsize, int obj_type, size_t len) { size_t i; *hdrlen = 0; hdr[0] = obj_type << 4; hdr[0] |= len & 0xf; len >>= 4; for (i = 1; len != 0; i++){ if (i >= bufsize) return got_error(GOT_ERR_NO_SPACE); hdr[i - 1] |= GOT_DELTA_SIZE_MORE; hdr[i] = len & GOT_DELTA_SIZE_VAL_MASK; len >>= GOT_DELTA_SIZE_SHIFT; } *hdrlen = i; return NULL; } static int packoff(char *hdr, off_t off) { int i, j; char rbuf[8]; rbuf[0] = off & GOT_DELTA_SIZE_VAL_MASK; for (i = 1; (off >>= GOT_DELTA_SIZE_SHIFT) != 0; i++) { rbuf[i] = (--off & GOT_DELTA_SIZE_VAL_MASK) | GOT_DELTA_SIZE_MORE; } j = 0; while (i > 0) hdr[j++] = rbuf[--i]; return j; } static const struct got_error * deltahdr(off_t *packfile_size, struct got_hash *ctx, int packfd, int force_refdelta, struct got_pack_meta *m) { const struct got_error *err; char buf[32]; int nh; size_t digest_len = got_hash_digest_length(m->prev->id.algo); if (m->prev->off != 0 && !force_refdelta) { err = packhdr(&nh, buf, sizeof(buf), GOT_OBJ_TYPE_OFFSET_DELTA, m->delta_len); if (err) return err; nh += packoff(buf + nh, m->off - m->prev->off); err = hwrite(packfd, buf, nh, ctx); if (err) return err; *packfile_size += nh; } else { err = packhdr(&nh, buf, sizeof(buf), GOT_OBJ_TYPE_REF_DELTA, m->delta_len); if (err) return err; err = hwrite(packfd, buf, nh, ctx); if (err) return err; *packfile_size += nh; err = hwrite(packfd, m->prev->id.hash, digest_len, ctx); if (err) return err; *packfile_size += digest_len; } return NULL; } static const struct got_error * write_packed_object(off_t *packfile_size, int packfd, FILE *delta_cache, uint8_t *delta_cache_map, size_t delta_cache_size, struct got_pack_meta *m, int *outfd, struct got_hash *ctx, struct got_repository *repo, int force_refdelta) { const struct got_error *err = NULL; struct got_deflate_checksum csum; char buf[32]; int nh; struct got_raw_object *raw = NULL; off_t outlen, delta_offset; memset(&csum, 0, sizeof(csum)); csum.output_ctx = ctx; if (m->reused_delta_offset) delta_offset = m->reused_delta_offset; else delta_offset = m->delta_offset; m->off = *packfile_size; if (m->delta_len == 0) { err = got_object_raw_open(&raw, outfd, repo, &m->id); if (err) goto done; err = packhdr(&nh, buf, sizeof(buf), m->obj_type, raw->size); if (err) goto done; err = hwrite(packfd, buf, nh, ctx); if (err) goto done; *packfile_size += nh; if (raw->f == NULL) { err = got_deflate_to_fd_mmap(&outlen, raw->data + raw->hdrlen, 0, raw->size, packfd, &csum); if (err) goto done; } else { if (fseeko(raw->f, raw->hdrlen, SEEK_SET) == -1) { err = got_error_from_errno("fseeko"); goto done; } err = got_deflate_to_fd(&outlen, raw->f, raw->size, packfd, &csum); if (err) goto done; } *packfile_size += outlen; got_object_raw_close(raw); raw = NULL; } else if (m->delta_buf) { err = deltahdr(packfile_size, ctx, packfd, force_refdelta, m); if (err) goto done; err = hwrite(packfd, m->delta_buf, m->delta_compressed_len, ctx); if (err) goto done; *packfile_size += m->delta_compressed_len; free(m->delta_buf); m->delta_buf = NULL; } else if (delta_cache_map) { err = deltahdr(packfile_size, ctx, packfd, force_refdelta, m); if (err) goto done; err = hcopy_mmap(delta_cache_map, delta_offset, delta_cache_size, packfd, m->delta_compressed_len, ctx); if (err) goto done; *packfile_size += m->delta_compressed_len; } else { if (fseeko(delta_cache, delta_offset, SEEK_SET) == -1) { err = got_error_from_errno("fseeko"); goto done; } err = deltahdr(packfile_size, ctx, packfd, force_refdelta, m); if (err) goto done; err = hcopy(delta_cache, packfd, m->delta_compressed_len, ctx); if (err) goto done; *packfile_size += m->delta_compressed_len; } done: if (raw) got_object_raw_close(raw); return err; } static const struct got_error * genpack(struct got_object_id *pack_hash, int packfd, struct got_pack *reuse_pack, FILE *delta_cache, struct got_pack_meta **deltify, int ndeltify, struct got_pack_meta **reuse, int nreuse, int ncolored, int nfound, int ntrees, int nours, struct got_repository *repo, int force_refdelta, got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; int i; struct got_hash ctx; struct got_pack_meta *m; char buf[32]; off_t packfile_size = 0; int outfd = -1; int delta_cache_fd = -1; uint8_t *delta_cache_map = NULL; size_t delta_cache_size = 0; FILE *packfile = NULL; enum got_hash_algorithm algo; size_t digest_len; algo = got_repo_get_object_format(repo); digest_len = got_hash_digest_length(algo); got_hash_init(&ctx, algo); memset(pack_hash, 0, sizeof(*pack_hash)); pack_hash->algo = algo; #ifndef GOT_PACK_NO_MMAP delta_cache_fd = dup(fileno(delta_cache)); if (delta_cache_fd != -1) { struct stat sb; if (fstat(delta_cache_fd, &sb) == -1) { err = got_error_from_errno("fstat"); goto done; } if (sb.st_size > 0 && sb.st_size <= SIZE_MAX) { delta_cache_map = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, delta_cache_fd, 0); if (delta_cache_map == MAP_FAILED) { if (errno != ENOMEM) { err = got_error_from_errno("mmap"); goto done; } delta_cache_map = NULL; /* fallback on stdio */ } else delta_cache_size = (size_t)sb.st_size; } } #endif err = hwrite(packfd, "PACK", 4, &ctx); if (err) goto done; putbe32(buf, GOT_PACKFILE_VERSION); err = hwrite(packfd, buf, 4, &ctx); if (err) goto done; putbe32(buf, ndeltify + nreuse); err = hwrite(packfd, buf, 4, &ctx); if (err) goto done; qsort(deltify, ndeltify, sizeof(struct got_pack_meta *), write_order_cmp); for (i = 0; i < ndeltify; i++) { err = got_pack_report_progress(progress_cb, progress_arg, rl, ncolored, nfound, ntrees, packfile_size, nours, ndeltify + nreuse, ndeltify + nreuse, i, 0); if (err) goto done; m = deltify[i]; err = write_packed_object(&packfile_size, packfd, delta_cache, delta_cache_map, delta_cache_size, m, &outfd, &ctx, repo, force_refdelta); if (err) goto done; } qsort(reuse, nreuse, sizeof(struct got_pack_meta *), reuse_write_order_cmp); if (nreuse > 0 && reuse_pack->map == NULL) { int fd = dup(reuse_pack->fd); if (fd == -1) { err = got_error_from_errno("dup"); goto done; } packfile = fdopen(fd, "r"); if (packfile == NULL) { err = got_error_from_errno("fdopen"); close(fd); goto done; } } for (i = 0; i < nreuse; i++) { err = got_pack_report_progress(progress_cb, progress_arg, rl, ncolored, nfound, ntrees, packfile_size, nours, ndeltify + nreuse, ndeltify + nreuse, ndeltify + i, 0); if (err) goto done; m = reuse[i]; err = write_packed_object(&packfile_size, packfd, packfile, reuse_pack->map, reuse_pack->filesize, m, &outfd, &ctx, repo, force_refdelta); if (err) goto done; } got_hash_final_object_id(&ctx, pack_hash); err = got_poll_write_full(packfd, pack_hash->hash, digest_len); if (err) goto done; packfile_size += digest_len; packfile_size += sizeof(struct got_packfile_hdr); if (progress_cb) { err = progress_cb(progress_arg, ncolored, nfound, ntrees, packfile_size, nours, ndeltify + nreuse, ndeltify + nreuse, ndeltify + nreuse, 1); if (err) goto done; } done: if (outfd != -1 && close(outfd) == -1 && err == NULL) err = got_error_from_errno("close"); if (delta_cache_map && munmap(delta_cache_map, delta_cache_size) == -1) err = got_error_from_errno("munmap"); if (delta_cache_fd != -1 && close(delta_cache_fd) == -1 && err == NULL) err = got_error_from_errno("close"); if (packfile && fclose(packfile) == EOF && err == NULL) err = got_error_from_errno("fclose"); return err; } static const struct got_error * add_meta_idset_cb(struct got_object_id *id, void *data, void *arg) { struct got_pack_meta *m = data; struct got_pack_metavec *v = arg; if (m->reused_delta_offset != 0) return NULL; return got_pack_add_meta(m, v); } const struct got_error * got_pack_create(struct got_object_id *packhash, int packfd, FILE *delta_cache, struct got_object_id **theirs, int ntheirs, struct got_object_id **ours, int nours, struct got_repository *repo, int loose_obj_only, int allow_empty, int force_refdelta, got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err; struct got_object_idset *idset; struct got_packidx *reuse_packidx = NULL; struct got_pack *reuse_pack = NULL; struct got_pack_metavec deltify, reuse; int ncolored = 0, nfound = 0, ntrees = 0; size_t ndeltify; uint32_t seed; seed = arc4random(); memset(&deltify, 0, sizeof(deltify)); memset(&reuse, 0, sizeof(reuse)); idset = got_object_idset_alloc(); if (idset == NULL) return got_error_from_errno("got_object_idset_alloc"); err = load_object_ids(&ncolored, &nfound, &ntrees, idset, theirs, ntheirs, ours, nours, repo, seed, loose_obj_only, progress_cb, progress_arg, rl, cancel_cb, cancel_arg); if (err) goto done; if (progress_cb) { err = progress_cb(progress_arg, ncolored, nfound, ntrees, 0L, nours, got_object_idset_num_elements(idset), 0, 0, 0); if (err) goto done; } if (got_object_idset_num_elements(idset) == 0 && !allow_empty) { err = got_error(GOT_ERR_CANNOT_PACK); goto done; } reuse.metasz = 64; reuse.meta = calloc(reuse.metasz, sizeof(struct got_pack_meta *)); if (reuse.meta == NULL) { err = got_error_from_errno("calloc"); goto done; } err = got_pack_search_deltas(&reuse_packidx, &reuse_pack, &reuse, idset, ncolored, nfound, ntrees, nours, repo, progress_cb, progress_arg, rl, cancel_cb, cancel_arg); if (err) goto done; if (reuse_packidx && reuse_pack) { err = got_repo_pin_pack(repo, reuse_packidx, reuse_pack); if (err) goto done; } if (fseeko(delta_cache, 0L, SEEK_END) == -1) { err = got_error_from_errno("fseeko"); goto done; } ndeltify = got_object_idset_num_elements(idset) - reuse.nmeta; if (ndeltify > 0) { deltify.meta = calloc(ndeltify, sizeof(struct got_pack_meta *)); if (deltify.meta == NULL) { err = got_error_from_errno("calloc"); goto done; } deltify.metasz = ndeltify; err = got_object_idset_for_each(idset, add_meta_idset_cb, &deltify); if (err) goto done; if (deltify.nmeta > 0) { err = pick_deltas(deltify.meta, deltify.nmeta, ncolored, nfound, ntrees, nours, reuse.nmeta, delta_cache, repo, progress_cb, progress_arg, rl, cancel_cb, cancel_arg); if (err) goto done; } } if (fflush(delta_cache) == EOF) { err = got_error_from_errno("fflush"); goto done; } if (progress_cb) { /* * Report a 1-byte packfile write to indicate we are about * to start sending packfile data. gotd(8) needs this. */ err = progress_cb(progress_arg, ncolored, nfound, ntrees, 1 /* packfile_size */, nours, got_object_idset_num_elements(idset), deltify.nmeta + reuse.nmeta, 0, 0); if (err) goto done; } /* Pinned pack may have moved to different cache slot. */ reuse_pack = got_repo_get_pinned_pack(repo); err = genpack(packhash, packfd, reuse_pack, delta_cache, deltify.meta, deltify.nmeta, reuse.meta, reuse.nmeta, ncolored, nfound, ntrees, nours, repo, force_refdelta, progress_cb, progress_arg, rl, cancel_cb, cancel_arg); if (err) goto done; done: free_nmeta(deltify.meta, deltify.nmeta); free_nmeta(reuse.meta, reuse.nmeta); got_object_idset_for_each(idset, free_meta, NULL); got_object_idset_free(idset); got_repo_unpin_pack(repo); return err; } got-portable-0.111/lib/diff3.c0000644000175000017500000006751515001741021011506 /* $OpenBSD: diff3.c,v 1.41 2016/10/18 21:06:52 millert Exp $ */ /* * Copyright (C) Caldera International Inc. 2001-2002. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code and documentation must retain the above * copyright notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed or owned by Caldera * International, Inc. * 4. Neither the name of Caldera International, Inc. nor the names of other * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA * INTERNATIONAL, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL CALDERA INTERNATIONAL, INC. BE LIABLE FOR ANY DIRECT, * INDIRECT INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /*- * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)diff3.c 8.1 (Berkeley) 6/6/93 */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_opentemp.h" #include "got_object.h" #include "got_diff.h" #include "buf.h" #include "rcsutil.h" #include "got_lib_diff.h" #ifndef nitems #define nitems(_a) (sizeof(_a) / sizeof((_a)[0])) #endif /* diff3 - 3-way differential file comparison */ /* diff3 [-ex3EX] d13 d23 f1 f2 f3 [m1 m3] * * d13 = diff report on f1 vs f3 * d23 = diff report on f2 vs f3 * f1, f2, f3 the 3 files * if changes in f1 overlap with changes in f3, m1 and m3 are used * to mark the overlaps; otherwise, the file names f1 and f3 are used * (only for options E and X). */ /* * "from" is first in range of changed lines; "to" is last+1 * from=to=line after point of insertion for added lines. */ struct line_range { int from; int to; }; struct off_range { off_t from; off_t to; }; struct diff { struct line_range old; struct line_range new; struct off_range oldo; struct off_range newo; }; struct diff3_state { size_t szchanges; struct diff *d13; struct diff *d23; /* * "de" is used to gather editing scripts. These are later spewed out * in reverse order. Its first element must be all zero, the "new" * component of "de" contains line positions, and "oldo" and "newo" * components contain byte positions. * Array overlap indicates which sections in "de" correspond to lines * that are different in all three files. */ struct diff *de; char *overlap; int overlapcnt; FILE *fp[3]; int cline[3]; /* # of the last-read line in each file (0-2) */ /* * the latest known correspondence between line numbers of the 3 files * is stored in last[1-3]; */ int last[4]; char f1mark[PATH_MAX]; char f2mark[PATH_MAX]; char f3mark[PATH_MAX]; char *buf; int no_eofnl; /* set if the merged file has no eof newline */ BUF *diffbuf; }; static const struct got_error *duplicate(int *, int, struct line_range *, struct line_range *, struct diff3_state *); static const struct got_error *edit(struct diff *, int, int *, struct diff3_state *); static const struct got_error *getchange(char **, FILE *, struct diff3_state *); static const struct got_error *get_line(char **, FILE *, size_t *, struct diff3_state *); static int number(char **); static const struct got_error *readin(size_t *, char *, struct diff **, struct diff3_state *); static int ed_patch_lines(struct rcs_lines *, struct rcs_lines *); static const struct got_error *skip(size_t *, int, int, struct diff3_state *); static const struct got_error *edscript(int, struct diff3_state *); static const struct got_error *merge(size_t, size_t, struct diff3_state *); static const struct got_error *prange(struct line_range *, struct diff3_state *); static const struct got_error *repos(int, struct diff3_state *); static const struct got_error *increase(struct diff3_state *); static const struct got_error *diff3_internal(char *, char *, char *, char *, char *, const char *, const char *, struct diff3_state *, const char *, const char *, const char *); static const struct got_error * diff_output(BUF *diffbuf, const char *fmt, ...) { const struct got_error *err = NULL; va_list vap; int i; char *str; size_t newsize; va_start(vap, fmt); i = vasprintf(&str, fmt, vap); va_end(vap); if (i == -1) return got_error_from_errno("vasprintf"); err = buf_append(&newsize, diffbuf, str, strlen(str)); free(str); return err; } static const struct got_error* diffreg(BUF **d, const char *path1, const char *path2, enum got_diff_algorithm diff_algo) { const struct got_error *err = NULL; FILE *f1 = NULL, *f2 = NULL, *outfile = NULL; char *outpath = NULL; struct got_diffreg_result *diffreg_result = NULL; *d = NULL; f1 = fopen(path1, "re"); if (f1 == NULL) { err = got_error_from_errno2("fopen", path1); goto done; } f2 = fopen(path2, "re"); if (f1 == NULL) { err = got_error_from_errno2("fopen", path2); goto done; } err = got_opentemp_named(&outpath, &outfile, GOT_TMPDIR_STR "/got-diffreg", ""); if (err) goto done; err = got_diffreg(&diffreg_result, f1, f2, diff_algo, 0, 0); if (err) goto done; if (diffreg_result) { struct diff_result *diff_result = diffreg_result->result; int atomizer_flags = (diff_result->left->atomizer_flags | diff_result->right->atomizer_flags); if ((atomizer_flags & DIFF_ATOMIZER_FOUND_BINARY_DATA)) { err = got_error(GOT_ERR_FILE_BINARY); goto done; } } err = got_diffreg_output(NULL, NULL, diffreg_result, 1, 1, "", "", GOT_DIFF_OUTPUT_PLAIN, 0, outfile); if (err) goto done; if (fflush(outfile) != 0) { err = got_error_from_errno2("fflush", outpath); goto done; } if (fseek(outfile, 0L, SEEK_SET) == -1) { err = got_ferror(outfile, GOT_ERR_IO); goto done; } err = buf_load(d, outfile); done: if (outpath) { if (unlink(outpath) == -1 && err == NULL) err = got_error_from_errno2("unlink", outpath); free(outpath); } if (outfile && fclose(outfile) == EOF && err == NULL) err = got_error_from_errno("fclose"); if (f1 && fclose(f1) == EOF && err == NULL) err = got_error_from_errno("fclose"); if (f2 && fclose(f2) == EOF && err == NULL) err = got_error_from_errno("fclose"); return err; } /* * For merge(1). */ const struct got_error * got_merge_diff3(int *overlapcnt, int outfd, FILE *f1, FILE *f2, FILE *f3, const char *label1, const char *label2, const char *label3, enum got_diff_algorithm diff_algo) { const struct got_error *err = NULL; char *dp13, *dp23, *path1, *path2, *path3; BUF *b1, *b2, *b3, *d1, *d2, *diffb; u_char *data, *patch; size_t dlen, plen, i; struct diff3_state *d3s; *overlapcnt = 0; d3s = calloc(1, sizeof(*d3s)); if (d3s == NULL) return got_error_from_errno("calloc"); b1 = b2 = b3 = d1 = d2 = diffb = NULL; dp13 = dp23 = path1 = path2 = path3 = NULL; data = patch = NULL; err = buf_load(&b1, f1); if (err) goto out; err = buf_load(&b2, f2); if (err) goto out; err = buf_load(&b3, f3); if (err) goto out; err = buf_alloc(&diffb, 128); if (err) goto out; if (asprintf(&path1, GOT_TMPDIR_STR "/got-diff1.XXXXXXXXXX") == -1) { err = got_error_from_errno("asprintf"); goto out; } if (asprintf(&path2, GOT_TMPDIR_STR "/got-diff2.XXXXXXXXXX") == -1) { err = got_error_from_errno("asprintf"); goto out; } if (asprintf(&path3, GOT_TMPDIR_STR "/got-diff3.XXXXXXXXXX") == -1) { err = got_error_from_errno("asprintf"); goto out; } err = buf_write_stmp(b1, path1); if (err) goto out; err = buf_write_stmp(b2, path2); if (err) goto out; err = buf_write_stmp(b3, path3); if (err) goto out; buf_free(b2); b2 = NULL; err = diffreg(&d1, path1, path3, diff_algo); if (err) { buf_free(diffb); diffb = NULL; goto out; } err = diffreg(&d2, path2, path3, diff_algo); if (err) { buf_free(diffb); diffb = NULL; goto out; } if (asprintf(&dp13, GOT_TMPDIR_STR "/got-d13.XXXXXXXXXX") == -1) { err = got_error_from_errno("asprintf"); goto out; } err = buf_write_stmp(d1, dp13); if (err) goto out; buf_free(d1); d1 = NULL; if (asprintf(&dp23, GOT_TMPDIR_STR "/got-d23.XXXXXXXXXX") == -1) { err = got_error_from_errno("asprintf"); goto out; } err = buf_write_stmp(d2, dp23); if (err) goto out; buf_free(d2); d2 = NULL; d3s->diffbuf = diffb; err = diff3_internal(dp13, dp23, path1, path2, path3, label1, label3, d3s, label1, label2, label3); if (err) { buf_free(diffb); diffb = NULL; goto out; } plen = buf_len(diffb); patch = buf_release(diffb); dlen = buf_len(b1); data = buf_release(b1); diffb = rcs_patchfile(data, dlen, patch, plen, ed_patch_lines); out: buf_free(b2); buf_free(b3); buf_free(d1); buf_free(d2); if (unlink(path1) == -1 && err == NULL) err = got_error_from_errno2("unlink", path1); if (unlink(path2) == -1 && err == NULL) err = got_error_from_errno2("unlink", path2); if (unlink(path3) == -1 && err == NULL) err = got_error_from_errno2("unlink", path3); if (unlink(dp13) == -1 && err == NULL) err = got_error_from_errno2("unlink", dp13); if (unlink(dp23) == -1 && err == NULL) err = got_error_from_errno2("unlink", dp23); free(path1); free(path2); free(path3); free(dp13); free(dp23); free(data); free(patch); for (i = 0; i < nitems(d3s->fp); i++) { if (d3s->fp[i] && fclose(d3s->fp[i]) == EOF && err == NULL) err = got_error_from_errno("fclose"); } if (err == NULL && diffb) { dlen = buf_len(diffb); if (d3s->no_eofnl && dlen > 0 && buf_getc(diffb, dlen - 1) == '\n') --diffb->cb_len; if (buf_write_fd(diffb, outfd) < 0) err = got_error_from_errno("buf_write_fd"); *overlapcnt = d3s->overlapcnt; } free(d3s); buf_free(diffb); return err; } static const struct got_error * diff3_internal(char *dp13, char *dp23, char *path1, char *path2, char *path3, const char *fmark, const char *rmark, struct diff3_state *d3s, const char *label1, const char *label2, const char *label3) { const struct got_error *err = NULL; ssize_t m, n; int i; i = snprintf(d3s->f1mark, sizeof(d3s->f1mark), "%s%s%s", GOT_DIFF_CONFLICT_MARKER_BEGIN, label1 ? " " : "", label1 ? label1 : ""); if (i < 0 || i >= (int)sizeof(d3s->f1mark)) return got_error(GOT_ERR_NO_SPACE); i = snprintf(d3s->f2mark, sizeof(d3s->f2mark), "%s%s%s", GOT_DIFF_CONFLICT_MARKER_ORIG, label2 ? " " : "", label2 ? label2 : ""); if (i < 0 || i >= (int)sizeof(d3s->f2mark)) return got_error(GOT_ERR_NO_SPACE); i = snprintf(d3s->f3mark, sizeof(d3s->f3mark), "%s%s%s", GOT_DIFF_CONFLICT_MARKER_END, label3 ? " " : "", label3 ? label3 : ""); if (i < 0 || i >= (int)sizeof(d3s->f3mark)) return got_error(GOT_ERR_NO_SPACE); err = increase(d3s); if (err) return err; err = readin(&m, dp13, &d3s->d13, d3s); if (err) return err; err = readin(&n, dp23, &d3s->d23, d3s); if (err) return err; if ((d3s->fp[0] = fopen(path1, "re")) == NULL) return got_error_from_errno2("fopen", path1); if ((d3s->fp[1] = fopen(path2, "re")) == NULL) return got_error_from_errno2("fopen", path2); if ((d3s->fp[2] = fopen(path3, "re")) == NULL) return got_error_from_errno2("fopen", path3); return merge(m, n, d3s); } static int ed_patch_lines(struct rcs_lines *dlines, struct rcs_lines *plines) { char op, *ep; struct rcs_line *sort, *lp, *dlp, *ndlp, *insert_after; int start, end, i, lineno; u_char tmp; dlp = TAILQ_FIRST(&(dlines->l_lines)); lp = TAILQ_FIRST(&(plines->l_lines)); end = 0; for (lp = TAILQ_NEXT(lp, l_list); lp != NULL; lp = TAILQ_NEXT(lp, l_list)) { /* Skip blank lines */ if (lp->l_len < 2) continue; /* NUL-terminate line buffer for strtol() safety. */ tmp = lp->l_line[lp->l_len - 1]; lp->l_line[lp->l_len - 1] = '\0'; /* len - 1 is NUL terminator so we use len - 2 for 'op' */ op = lp->l_line[lp->l_len - 2]; start = (int)strtol(lp->l_line, &ep, 10); /* Restore the last byte of the buffer */ lp->l_line[lp->l_len - 1] = tmp; if (op == 'a') { if (start > dlines->l_nblines || start < 0 || *ep != 'a') return -1; } else if (op == 'c') { if (start > dlines->l_nblines || start < 0 || (*ep != ',' && *ep != 'c')) return -1; if (*ep == ',') { ep++; end = (int)strtol(ep, &ep, 10); if (end < 0 || *ep != 'c') return -1; } else { end = start; } } for (;;) { if (dlp == NULL) break; if (dlp->l_lineno == start) break; if (dlp->l_lineno > start) { dlp = TAILQ_PREV(dlp, tqh, l_list); } else if (dlp->l_lineno < start) { ndlp = TAILQ_NEXT(dlp, l_list); if (ndlp->l_lineno > start) break; dlp = ndlp; } } if (dlp == NULL) return -1; if (op == 'c') { insert_after = TAILQ_PREV(dlp, tqh, l_list); for (i = 0; i <= (end - start); i++) { ndlp = TAILQ_NEXT(dlp, l_list); TAILQ_REMOVE(&(dlines->l_lines), dlp, l_list); dlp = ndlp; } dlp = insert_after; } if (op == 'a' || op == 'c') { for (;;) { ndlp = lp; lp = TAILQ_NEXT(lp, l_list); if (lp == NULL) return -1; if (lp->l_len == 2 && lp->l_line[0] == '.' && lp->l_line[1] == '\n') break; if (lp->l_line[0] == ':') { lp->l_line++; lp->l_len--; } TAILQ_REMOVE(&(plines->l_lines), lp, l_list); TAILQ_INSERT_AFTER(&(dlines->l_lines), dlp, lp, l_list); dlp = lp; lp->l_lineno = start; lp = ndlp; } } /* * always resort lines as the markers might be put at the * same line as we first started editing. */ lineno = 0; TAILQ_FOREACH(sort, &(dlines->l_lines), l_list) sort->l_lineno = lineno++; dlines->l_nblines = lineno - 1; } return (0); } /* * Pick up the line numbers of all changes from one change file. * (This puts the numbers in a vector, which is not strictly necessary, * since the vector is processed in one sequential pass. * The vector could be optimized out of existence) */ static const struct got_error * readin(size_t *n, char *name, struct diff **dd, struct diff3_state *d3s) { const struct got_error *err = NULL; FILE *f; int a, b, c, d; char kind, *p; size_t i = 0; *n = 0; f = fopen(name, "re"); if (f == NULL) return got_error_from_errno2("fopen", name); err = getchange(&p, f, d3s); if (err) goto done; for (i = 0; p; i++) { if (i >= d3s->szchanges - 1) { err = increase(d3s); if (err) goto done; } a = b = number(&p); if (*p == ',') { p++; b = number(&p); } kind = *p++; c = d = number(&p); if (*p == ',') { p++; d = number(&p); } if (kind == 'a') a++; if (kind == 'd') c++; b++; d++; (*dd)[i].old.from = a; (*dd)[i].old.to = b; (*dd)[i].new.from = c; (*dd)[i].new.to = d; err = getchange(&p, f, d3s); if (err) goto done; } if (i) { (*dd)[i].old.from = (*dd)[i - 1].old.to; (*dd)[i].new.from = (*dd)[i - 1].new.to; } done: if (fclose(f) == EOF && err == NULL) err = got_error_from_errno("fclose"); if (err == NULL) *n = i; return err; } static int number(char **lc) { int nn; nn = 0; while (isdigit((unsigned char)(**lc))) nn = nn*10 + *(*lc)++ - '0'; return (nn); } static const struct got_error * getchange(char **line, FILE *b, struct diff3_state *d3s) { const struct got_error *err = NULL; *line = NULL; do { if (*line && isdigit((unsigned char)(*line)[0])) return NULL; err = get_line(line, b, NULL, d3s); if (err) return err; } while (*line); return NULL; } static const struct got_error * get_line(char **ret, FILE *b, size_t *n, struct diff3_state *d3s) { const struct got_error *err = NULL; char *cp = NULL; size_t size = 0; ssize_t len; char *new; *ret = NULL; if (n != NULL) *n = 0; len = getline(&cp, &size, b); if (len == -1) { if (ferror(b)) err = got_error_from_errno("getline"); goto done; } if (cp[len - 1] != '\n') { len++; if (len + 1 > size) { new = realloc(cp, len + 1); if (new == NULL) { err = got_error_from_errno("realloc"); goto done; } cp = new; } cp[len - 1] = '\n'; cp[len] = '\0'; } free(d3s->buf); *ret = d3s->buf = cp; cp = NULL; if (n != NULL) *n = len; done: free(cp); return err; } static const struct got_error * merge(size_t m1, size_t m2, struct diff3_state *d3s) { const struct got_error *err = NULL; struct diff *d1, *d2; int dpl, j, t1, t2; d1 = d3s->d13; d2 = d3s->d23; j = 0; for (;;) { t1 = (d1 < d3s->d13 + m1); t2 = (d2 < d3s->d23 + m2); if (!t1 && !t2) break; /* first file is different from others */ if (!t2 || (t1 && d1->new.to < d2->new.from)) { /* stuff peculiar to 1st file */ d1++; continue; } /* second file is different from others */ if (!t1 || (t2 && d2->new.to < d1->new.from)) { d2++; continue; } /* * Merge overlapping changes in first file * this happens after extension (see below). */ if (d1 + 1 < d3s->d13 + m1 && d1->new.to >= d1[1].new.from) { d1[1].old.from = d1->old.from; d1[1].new.from = d1->new.from; d1++; continue; } /* merge overlapping changes in second */ if (d2 + 1 < d3s->d23 + m2 && d2->new.to >= d2[1].new.from) { d2[1].old.from = d2->old.from; d2[1].new.from = d2->new.from; d2++; continue; } /* stuff peculiar to third file or different in all */ if (d1->new.from == d2->new.from && d1->new.to == d2->new.to) { err = duplicate(&dpl, j, &d1->old, &d2->old, d3s); if (err) return err; /* * dpl = 0 means all files differ * dpl = 1 means files 1 and 2 identical */ err = edit(d1, dpl, &j, d3s); if (err) return err; d1++; d2++; continue; } /* * Overlapping changes from file 1 and 2; extend changes * appropriately to make them coincide. */ if (d1->new.from < d2->new.from) { d2->old.from -= d2->new.from - d1->new.from; d2->new.from = d1->new.from; } else if (d2->new.from < d1->new.from) { d1->old.from -= d1->new.from - d2->new.from; d1->new.from = d2->new.from; } if (d1->new.to > d2->new.to) { d2->old.to += d1->new.to - d2->new.to; d2->new.to = d1->new.to; } else if (d2->new.to > d1->new.to) { d1->old.to += d2->new.to - d1->new.to; d1->new.to = d2->new.to; } } return (edscript(j, d3s)); } /* * print the range of line numbers, rold.from thru rold.to, as n1,n2 or n1 */ static const struct got_error * prange(struct line_range *rold, struct diff3_state *d3s) { const struct got_error *err = NULL; if (rold->to <= rold->from) { err = diff_output(d3s->diffbuf, "%da\n", rold->from - 1); if (err) return err; } else { err = diff_output(d3s->diffbuf, "%d", rold->from); if (err) return err; if (rold->to > rold->from + 1) { err = diff_output(d3s->diffbuf, ",%d", rold->to - 1); if (err) return err; } err = diff_output(d3s->diffbuf, "c\n"); if (err) return err; } return NULL; } /* * Skip to just before line number from in file "i". * Return the number of bytes skipped in *nskipped. */ static const struct got_error * skip(size_t *nskipped, int i, int from, struct diff3_state *d3s) { const struct got_error *err = NULL; size_t len, n; char *line; *nskipped = 0; for (n = 0; d3s->cline[i] < from - 1; n += len) { err = get_line(&line, d3s->fp[i], &len, d3s); if (err) return err; d3s->cline[i]++; } *nskipped = n; return NULL; } /* * Set *dpl to 1 or 0 according as the old range (in file 1) contains exactly * the same data as the new range (in file 2). * * If this change could overlap, remember start/end offsets in file 2 so we * can write out the original lines of text if a merge conflict occurs. */ static const struct got_error * duplicate(int *dpl, int j, struct line_range *r1, struct line_range *r2, struct diff3_state *d3s) { const struct got_error *err = NULL; int c,d; int nchar; int nline; size_t nskipped; off_t off; *dpl = 0; if (r1->to - r1->from != r2->to - r2->from) return NULL; err = skip(&nskipped, 0, r1->from, d3s); if (err) return err; err = skip(&nskipped, 1, r2->from, d3s); if (err) return err; off = ftello(d3s->fp[1]); if (off == -1) return got_error_from_errno("ftello"); d3s->de[j + 1].oldo.from = off; /* original lines start here */ nchar = 0; for (nline = 0; nline < r1->to - r1->from; nline++) { do { c = getc(d3s->fp[0]); d = getc(d3s->fp[1]); if (c == EOF && d == EOF) break; else if (c == EOF) return got_ferror(d3s->fp[0], GOT_ERR_EOF); else if (d == EOF) return got_ferror(d3s->fp[1], GOT_ERR_EOF); nchar++; if (c != d) { long orig_line_len = nchar; while (d != '\n') { d = getc(d3s->fp[1]); if (d == EOF) break; orig_line_len++; } if (orig_line_len > nchar && fseek(d3s->fp[1], -(orig_line_len - nchar), SEEK_CUR) == -1) return got_ferror(d3s->fp[1], GOT_ERR_IO); /* original lines end here */ d3s->de[j + 1].oldo.to = off + orig_line_len; err = repos(nchar, d3s); if (err) return err; return NULL; } } while (c != '\n'); } /* original lines end here */ d3s->de[j + 1].oldo.to = off + nchar; err = repos(nchar, d3s); if (err) return err; *dpl = 1; return NULL; } static const struct got_error * repos(int nchar, struct diff3_state *d3s) { int i; for (i = 0; i < 2; i++) { if (fseek(d3s->fp[i], (long)-nchar, SEEK_CUR) == -1) return got_ferror(d3s->fp[i], GOT_ERR_IO); } return NULL; } /* * collect an editing script for later regurgitation */ static const struct got_error * edit(struct diff *diff, int fdup, int *j, struct diff3_state *d3s) { const struct got_error *err = NULL; size_t nskipped; if (((fdup + 1) & 3) == 0) return NULL; (*j)++; d3s->overlap[*j] = !fdup; if (!fdup) d3s->overlapcnt++; d3s->de[*j].old.from = diff->old.from; d3s->de[*j].old.to = diff->old.to; err = skip(&nskipped, 2, diff->new.from, d3s); if (err) return err; d3s->de[*j].newo.from = d3s->de[*j - 1].newo.to + nskipped; err = skip(&nskipped, 2, diff->new.to, d3s); if (err) return err; d3s->de[*j].newo.to = d3s->de[*j].newo.from + nskipped; return NULL; } /* regurgitate */ static const struct got_error * edscript(int n, struct diff3_state *d3s) { const struct got_error *err = NULL; off_t len; char *line = NULL; size_t linesize = 0; ssize_t linelen = 0, k; for (; n > 0; n--) { if (!d3s->overlap[n]) { err = prange(&d3s->de[n].old, d3s); if (err) return err; } else if (d3s->de[n].oldo.from < d3s->de[n].oldo.to) { /* Output a block of 3-way diff base file content. */ err = diff_output(d3s->diffbuf, "%da\n:%s\n", d3s->de[n].old.to - 1, d3s->f2mark); if (err) return err; if (fseeko(d3s->fp[1], d3s->de[n].oldo.from, SEEK_SET) == -1) return got_error_from_errno("fseeko"); len = (d3s->de[n].oldo.to - d3s->de[n].oldo.from); for (k = 0; k < (ssize_t)len; k += linelen) { linelen = getline(&line, &linesize, d3s->fp[1]); if (linelen == -1) { if (feof(d3s->fp[1])) break; err = got_ferror(d3s->fp[1], GOT_ERR_IO); goto done; } err = diff_output(d3s->diffbuf, ":%s", line); if (err) goto done; } err = diff_output(d3s->diffbuf, "%s%s\n", linelen > 0 && line[linelen] == '\n' ? ":" : "", GOT_DIFF_CONFLICT_MARKER_SEP); if (err) goto done; } else { err = diff_output(d3s->diffbuf, "%da\n:%s\n", d3s->de[n].old.to -1, GOT_DIFF_CONFLICT_MARKER_SEP); if (err) goto done; } if (fseeko(d3s->fp[2], d3s->de[n].newo.from, SEEK_SET) == -1) { err = got_error_from_errno("fseek"); goto done; } len = (d3s->de[n].newo.to - d3s->de[n].newo.from); for (k = 0; k < (ssize_t)len; k += linelen) { linelen = getline(&line, &linesize, d3s->fp[2]); if (linelen == -1) { if (feof(d3s->fp[2])) break; err = got_ferror(d3s->fp[2], GOT_ERR_IO); goto done; } err = diff_output(d3s->diffbuf, ":%s", line); if (err) goto done; } if (!d3s->overlap[n]) { size_t len; len = buf_len(d3s->diffbuf); if (len > 0) { if (buf_getc(d3s->diffbuf, len - 1) != '\n') { err = buf_putc(d3s->diffbuf, '\n'); if (err != NULL) goto done; d3s->no_eofnl = 1; } } err = diff_output(d3s->diffbuf, ".\n"); if (err) goto done; } else { err = diff_output(d3s->diffbuf, "%s%s\n.\n", linelen > 0 && line[linelen] == '\n' ? ":" : "", d3s->f3mark); if (err) goto done; err = diff_output(d3s->diffbuf, "%da\n:%s\n.\n", d3s->de[n].old.from - 1, d3s->f1mark); if (err) goto done; } } done: free(line); return err; } static const struct got_error * increase(struct diff3_state *d3s) { size_t newsz, incr; struct diff *d; char *s; /* are the memset(3) calls needed? */ newsz = d3s->szchanges == 0 ? 64 : 2 * d3s->szchanges; incr = newsz - d3s->szchanges; d = reallocarray(d3s->d13, newsz, sizeof(*d3s->d13)); if (d == NULL) return got_error_from_errno("reallocarray"); d3s->d13 = d; memset(d3s->d13 + d3s->szchanges, 0, incr * sizeof(*d3s->d13)); d = reallocarray(d3s->d23, newsz, sizeof(*d3s->d23)); if (d == NULL) return got_error_from_errno("reallocarray"); d3s->d23 = d; memset(d3s->d23 + d3s->szchanges, 0, incr * sizeof(*d3s->d23)); d = reallocarray(d3s->de, newsz, sizeof(*d3s->de)); if (d == NULL) return got_error_from_errno("reallocarray"); d3s->de = d; memset(d3s->de + d3s->szchanges, 0, incr * sizeof(*d3s->de)); s = reallocarray(d3s->overlap, newsz, sizeof(*d3s->overlap)); if (s == NULL) return got_error_from_errno("reallocarray"); d3s->overlap = s; memset(d3s->overlap + d3s->szchanges, 0, incr * sizeof(*d3s->overlap)); d3s->szchanges = newsz; return NULL; } got-portable-0.111/lib/gotsys_uidset.c0000644000175000017500000001217415001740614013416 /* * Copyright (c) 2018, 2019, 2025 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_object.h" #include "gotsysd.h" struct gotsys_uidset_element { RB_ENTRY(gotsys_uidset_element) entry; uid_t uid; }; RB_HEAD(gotsys_uidset_tree, gotsys_uidset_element); static int cmp_uid(uid_t uid1, uid_t uid2) { if (uid1 > uid2) return 1; if (uid1 < uid2) return -1; return 0; } static int cmp_elements(const struct gotsys_uidset_element *e1, const struct gotsys_uidset_element *e2) { return cmp_uid(e1->uid, e2->uid); } RB_PROTOTYPE(gotsys_uidset_tree, gotsys_uidset_element, entry, cmp_elements); struct gotsys_uidset { struct gotsys_uidset_tree entries; int totelem; #define GOTSYS_UIDSET_MAX_ELEM INT_MAX }; struct gotsys_uidset * gotsys_uidset_alloc(void) { struct gotsys_uidset *set; set = malloc(sizeof(*set)); if (set == NULL) return NULL; RB_INIT(&set->entries); set->totelem = 0; return set; } void gotsys_uidset_free(struct gotsys_uidset *set) { struct gotsys_uidset_element *entry; if (set == NULL) return; while ((entry = RB_MIN(gotsys_uidset_tree, &set->entries))) { RB_REMOVE(gotsys_uidset_tree, &set->entries, entry); free(entry); } free(set); } const struct got_error * gotsys_uidset_add(struct gotsys_uidset *set, uid_t uid) { struct gotsys_uidset_element *new; if (set->totelem >= GOTSYS_UIDSET_MAX_ELEM) return got_error(GOT_ERR_NO_SPACE); new = malloc(sizeof(*new)); if (new == NULL) return got_error_from_errno("malloc"); new->uid = uid; if (RB_INSERT(gotsys_uidset_tree, &set->entries, new) != NULL) { free(new); return got_error(GOT_ERR_USER_EXISTS); } set->totelem++; return NULL; } static struct gotsys_uidset_element * find_element(struct gotsys_uidset *set, uid_t uid) { struct gotsys_uidset_element *entry; entry = RB_ROOT(&set->entries); while (entry) { int cmp = cmp_uid(uid, entry->uid); if (cmp < 0) entry = RB_LEFT(entry, entry); else if (cmp > 0) entry = RB_RIGHT(entry, entry); else break; } return entry; } const struct got_error * gotsys_uidset_remove(void **data, struct gotsys_uidset *set, uid_t uid) { struct gotsys_uidset_element *entry; if (set->totelem == 0) return got_error(GOT_ERR_UID); entry = find_element(set, uid); if (entry == NULL) return got_error(GOT_ERR_UID); RB_REMOVE(gotsys_uidset_tree, &set->entries, entry); free(entry); set->totelem--; return NULL; } int gotsys_uidset_contains(struct gotsys_uidset *set, uid_t uid) { struct gotsys_uidset_element *entry = find_element(set, uid); return entry ? 1 : 0; } uid_t gotsys_uidset_min_uid(struct gotsys_uidset *set, uid_t fallback) { struct gotsys_uidset_element *entry; if (RB_EMPTY(&set->entries)) return fallback; entry = RB_MIN(gotsys_uidset_tree, &set->entries); return entry->uid; } uid_t gotsys_uidset_max_uid(struct gotsys_uidset *set, uid_t fallback) { struct gotsys_uidset_element *entry; if (RB_EMPTY(&set->entries)) return fallback; entry = RB_MAX(gotsys_uidset_tree, &set->entries); return entry->uid; } const struct got_error * gotsys_uidset_for_each(struct gotsys_uidset *set, const struct got_error *(*cb)(uid_t, void *), void *arg) { const struct got_error *err; struct gotsys_uidset_element *entry, *tmp; RB_FOREACH_SAFE(entry, gotsys_uidset_tree, &set->entries, tmp) { err = (*cb)(entry->uid, arg); if (err) return err; } return NULL; } int gotsys_uidset_num_elements(struct gotsys_uidset *set) { return set->totelem; } struct gotsys_uidset_element * gotsys_uidset_get_element(struct gotsys_uidset *set, uid_t uid) { return find_element(set, uid); } const struct got_error * gotsys_uidset_for_each_element(struct gotsys_uidset *set, const struct got_error *(*cb)(struct gotsys_uidset_element *, void *), void *arg) { const struct got_error *err; struct gotsys_uidset_element *entry, *tmp; RB_FOREACH_SAFE(entry, gotsys_uidset_tree, &set->entries, tmp) { err = (*cb)(entry, arg); if (err) return err; } return NULL; } void gotsys_uidset_remove_element(struct gotsys_uidset *set, struct gotsys_uidset_element *entry) { RB_REMOVE(gotsys_uidset_tree, &set->entries, entry); free(entry); set->totelem--; } RB_GENERATE(gotsys_uidset_tree, gotsys_uidset_element, entry, cmp_elements); got-portable-0.111/lib/diff_main.c0000644000175000017500000004134115001741021012414 /* Generic infrastructure to implement various diff algorithms (implementation). */ /* * Copyright (c) 2020 Neels Hofmeyr * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "got_compat.h" #include "diff_internal.h" #include "diff_debug.h" inline enum diff_chunk_type diff_chunk_type(const struct diff_chunk *chunk) { if (!chunk->left_count && !chunk->right_count) return CHUNK_EMPTY; if (!chunk->solved) return CHUNK_ERROR; if (!chunk->right_count) return CHUNK_MINUS; if (!chunk->left_count) return CHUNK_PLUS; if (chunk->left_count != chunk->right_count) return CHUNK_ERROR; return CHUNK_SAME; } static int read_at(FILE *f, off_t at_pos, unsigned char *buf, size_t len) { int r; if (fseeko(f, at_pos, SEEK_SET) == -1) return errno; r = fread(buf, sizeof(char), len, f); if ((r == 0 || r < len) && ferror(f)) return EIO; if (r != len) return EIO; return 0; } static int buf_cmp(const unsigned char *left, size_t left_len, const unsigned char *right, size_t right_len, bool ignore_whitespace) { int cmp; if (ignore_whitespace) { int il = 0, ir = 0; while (il < left_len && ir < right_len) { unsigned char cl = left[il]; unsigned char cr = right[ir]; if (isspace((unsigned char)cl) && il < left_len) { il++; continue; } if (isspace((unsigned char)cr) && ir < right_len) { ir++; continue; } if (cl > cr) return 1; if (cr > cl) return -1; il++; ir++; } while (il < left_len) { unsigned char cl = left[il++]; if (!isspace((unsigned char)cl)) return 1; } while (ir < right_len) { unsigned char cr = right[ir++]; if (!isspace((unsigned char)cr)) return -1; } return 0; } cmp = memcmp(left, right, MIN(left_len, right_len)); if (cmp) return cmp; if (left_len == right_len) return 0; return (left_len > right_len) ? 1 : -1; } int diff_atom_cmp(int *cmp, const struct diff_atom *left, const struct diff_atom *right) { off_t remain_left, remain_right; int flags = (left->root->diff_flags | right->root->diff_flags); bool ignore_whitespace = (flags & DIFF_FLAG_IGNORE_WHITESPACE); if (!left->len && !right->len) { *cmp = 0; return 0; } if (!ignore_whitespace) { if (!right->len) { *cmp = 1; return 0; } if (!left->len) { *cmp = -1; return 0; } } if (left->at != NULL && right->at != NULL) { *cmp = buf_cmp(left->at, left->len, right->at, right->len, ignore_whitespace); return 0; } remain_left = left->len; remain_right = right->len; while (remain_left > 0 || remain_right > 0) { const size_t chunksz = 8192; unsigned char buf_left[chunksz], buf_right[chunksz]; const uint8_t *p_left, *p_right; off_t n_left, n_right; ssize_t r; if (!remain_right) { *cmp = 1; return 0; } if (!remain_left) { *cmp = -1; return 0; } n_left = MIN(chunksz, remain_left); n_right = MIN(chunksz, remain_right); if (left->at == NULL) { r = read_at(left->root->f, left->pos + (left->len - remain_left), buf_left, n_left); if (r) { *cmp = 0; return r; } p_left = buf_left; } else { p_left = left->at + (left->len - remain_left); } if (right->at == NULL) { r = read_at(right->root->f, right->pos + (right->len - remain_right), buf_right, n_right); if (r) { *cmp = 0; return r; } p_right = buf_right; } else { p_right = right->at + (right->len - remain_right); } r = buf_cmp(p_left, n_left, p_right, n_right, ignore_whitespace); if (r) { *cmp = r; return 0; } remain_left -= n_left; remain_right -= n_right; } *cmp = 0; return 0; } int diff_atom_same(bool *same, const struct diff_atom *left, const struct diff_atom *right) { int cmp; int r; if (left->hash != right->hash) { *same = false; return 0; } r = diff_atom_cmp(&cmp, left, right); if (r) { *same = true; return r; } *same = (cmp == 0); return 0; } static struct diff_chunk * diff_state_add_solved_chunk(struct diff_state *state, const struct diff_chunk *chunk) { diff_chunk_arraylist_t *result; struct diff_chunk *new_chunk; enum diff_chunk_type last_t; enum diff_chunk_type new_t; struct diff_chunk *last; /* Append to solved chunks; make sure that adjacent chunks of same type are combined, and that a minus chunk * never directly follows a plus chunk. */ result = &state->result->chunks; last_t = result->len ? diff_chunk_type(&result->head[result->len - 1]) : CHUNK_EMPTY; new_t = diff_chunk_type(chunk); debug("ADD %s chunk #%u:\n", chunk->solved ? "solved" : "UNSOLVED", result->len); debug("L\n"); debug_dump_atoms(&state->left, chunk->left_start, chunk->left_count); debug("R\n"); debug_dump_atoms(&state->right, chunk->right_start, chunk->right_count); if (result->len) { last = &result->head[result->len - 1]; assert(chunk->left_start == last->left_start + last->left_count); assert(chunk->right_start == last->right_start + last->right_count); } if (new_t == last_t) { new_chunk = &result->head[result->len - 1]; new_chunk->left_count += chunk->left_count; new_chunk->right_count += chunk->right_count; debug(" - added chunk touches previous one of same type, joined:\n"); debug("L\n"); debug_dump_atoms(&state->left, new_chunk->left_start, new_chunk->left_count); debug("R\n"); debug_dump_atoms(&state->right, new_chunk->right_start, new_chunk->right_count); } else if (last_t == CHUNK_PLUS && new_t == CHUNK_MINUS) { enum diff_chunk_type prev_last_t = result->len > 1 ? diff_chunk_type(&result->head[result->len - 2]) : CHUNK_EMPTY; /* If a minus-chunk follows a plus-chunk, place it above the plus-chunk-> * Is the one before that also a minus? combine. */ if (prev_last_t == CHUNK_MINUS) { new_chunk = &result->head[result->len - 2]; new_chunk->left_count += chunk->left_count; new_chunk->right_count += chunk->right_count; debug(" - added minus-chunk follows plus-chunk," " put before that plus-chunk and joined" " with preceding minus-chunk:\n"); debug("L\n"); debug_dump_atoms(&state->left, new_chunk->left_start, new_chunk->left_count); debug("R\n"); debug_dump_atoms(&state->right, new_chunk->right_start, new_chunk->right_count); } else { ARRAYLIST_INSERT(new_chunk, *result, result->len - 1); if (!new_chunk) return NULL; *new_chunk = *chunk; /* The new minus chunk indicates to which position on * the right it corresponds, even though it doesn't add * any lines on the right. By moving above a plus chunk, * that position on the right has shifted. */ last = &result->head[result->len - 1]; new_chunk->right_start = last->right_start; debug(" - added minus-chunk follows plus-chunk," " put before that plus-chunk\n"); } /* That last_t == CHUNK_PLUS indicates to which position on the * left it corresponds, even though it doesn't add any lines on * the left. By inserting/extending the prev_last_t == * CHUNK_MINUS, that position on the left has shifted. */ last = &result->head[result->len - 1]; last->left_start = new_chunk->left_start + new_chunk->left_count; } else { ARRAYLIST_ADD(new_chunk, *result); if (!new_chunk) return NULL; *new_chunk = *chunk; } return new_chunk; } /* Even if a left or right side is empty, diff output may need to know the * position in that file. * So left_start or right_start must never be NULL -- pass left_count or * right_count as zero to indicate staying at that position without consuming * any lines. */ struct diff_chunk * diff_state_add_chunk(struct diff_state *state, bool solved, struct diff_atom *left_start, unsigned int left_count, struct diff_atom *right_start, unsigned int right_count) { struct diff_chunk *new_chunk; struct diff_chunk chunk = { .solved = solved, .left_start = left_start, .left_count = left_count, .right_start = right_start, .right_count = right_count, }; /* An unsolved chunk means store as intermediate result for later * re-iteration. * If there already are intermediate results, that means even a * following solved chunk needs to go to intermediate results, so that * it is later put in the final correct position in solved chunks. */ if (!solved || state->temp_result.len) { /* Append to temp_result */ debug("ADD %s chunk to temp result:\n", chunk.solved ? "solved" : "UNSOLVED"); debug("L\n"); debug_dump_atoms(&state->left, left_start, left_count); debug("R\n"); debug_dump_atoms(&state->right, right_start, right_count); ARRAYLIST_ADD(new_chunk, state->temp_result); if (!new_chunk) return NULL; *new_chunk = chunk; return new_chunk; } return diff_state_add_solved_chunk(state, &chunk); } static void diff_data_init_root(struct diff_data *d, FILE *f, const uint8_t *data, unsigned long long len, int diff_flags) { *d = (struct diff_data){ .f = f, .pos = 0, .data = data, .len = len, .root = d, .diff_flags = diff_flags, }; } void diff_data_init_subsection(struct diff_data *d, struct diff_data *parent, struct diff_atom *from_atom, unsigned int atoms_count) { struct diff_atom *last_atom; debug("diff_data %p parent %p from_atom %p atoms_count %u\n", d, parent, from_atom, atoms_count); debug(" from_atom "); debug_dump_atom(parent, NULL, from_atom); if (atoms_count == 0) { *d = (struct diff_data){ .f = NULL, .pos = 0, .data = NULL, .len = 0, .root = parent->root, .atoms.head = NULL, .atoms.len = atoms_count, }; return; } last_atom = from_atom + atoms_count - 1; *d = (struct diff_data){ .f = NULL, .pos = from_atom->pos, .data = from_atom->at, .len = (last_atom->pos + last_atom->len) - from_atom->pos, .root = parent->root, .atoms.head = from_atom, .atoms.len = atoms_count, }; debug("subsection:\n"); debug_dump(d); } void diff_data_free(struct diff_data *diff_data) { if (!diff_data) return; if (diff_data->atoms.allocated) ARRAYLIST_FREE(diff_data->atoms); } int diff_algo_none(const struct diff_algo_config *algo_config, struct diff_state *state) { debug("\n** %s\n", __func__); debug("left:\n"); debug_dump(&state->left); debug("right:\n"); debug_dump(&state->right); debug_dump_myers_graph(&state->left, &state->right, NULL, NULL, 0, NULL, 0); /* Add a chunk of equal lines, if any */ struct diff_atom *l = state->left.atoms.head; unsigned int l_len = state->left.atoms.len; struct diff_atom *r = state->right.atoms.head; unsigned int r_len = state->right.atoms.len; unsigned int equal_atoms_start = 0; unsigned int equal_atoms_end = 0; unsigned int l_idx = 0; unsigned int r_idx = 0; while (equal_atoms_start < l_len && equal_atoms_start < r_len) { int err; bool same; err = diff_atom_same(&same, &l[equal_atoms_start], &r[equal_atoms_start]); if (err) return err; if (!same) break; equal_atoms_start++; } while (equal_atoms_end < (l_len - equal_atoms_start) && equal_atoms_end < (r_len - equal_atoms_start)) { int err; bool same; err = diff_atom_same(&same, &l[l_len - 1 - equal_atoms_end], &r[r_len - 1 - equal_atoms_end]); if (err) return err; if (!same) break; equal_atoms_end++; } /* Add a chunk of equal lines at the start */ if (equal_atoms_start) { if (!diff_state_add_chunk(state, true, l, equal_atoms_start, r, equal_atoms_start)) return ENOMEM; l_idx += equal_atoms_start; r_idx += equal_atoms_start; } /* Add a "minus" chunk with all lines from the left. */ if (equal_atoms_start + equal_atoms_end < l_len) { unsigned int add_len = l_len - equal_atoms_start - equal_atoms_end; if (!diff_state_add_chunk(state, true, &l[l_idx], add_len, &r[r_idx], 0)) return ENOMEM; l_idx += add_len; } /* Add a "plus" chunk with all lines from the right. */ if (equal_atoms_start + equal_atoms_end < r_len) { unsigned int add_len = r_len - equal_atoms_start - equal_atoms_end; if (!diff_state_add_chunk(state, true, &l[l_idx], 0, &r[r_idx], add_len)) return ENOMEM; r_idx += add_len; } /* Add a chunk of equal lines at the end */ if (equal_atoms_end) { if (!diff_state_add_chunk(state, true, &l[l_idx], equal_atoms_end, &r[r_idx], equal_atoms_end)) return ENOMEM; } return DIFF_RC_OK; } static int diff_run_algo(const struct diff_algo_config *algo_config, struct diff_state *state) { int rc; if (!algo_config || !algo_config->impl || !state->recursion_depth_left || !state->left.atoms.len || !state->right.atoms.len) { debug("Fall back to diff_algo_none():%s%s%s\n", (!algo_config || !algo_config->impl) ? " no-cfg" : "", (!state->recursion_depth_left) ? " max-depth" : "", (!state->left.atoms.len || !state->right.atoms.len)? " trivial" : ""); return diff_algo_none(algo_config, state); } ARRAYLIST_FREE(state->temp_result); ARRAYLIST_INIT(state->temp_result, DIFF_RESULT_ALLOC_BLOCKSIZE); rc = algo_config->impl(algo_config, state); switch (rc) { case DIFF_RC_USE_DIFF_ALGO_FALLBACK: debug("Got DIFF_RC_USE_DIFF_ALGO_FALLBACK (%p)\n", algo_config->fallback_algo); rc = diff_run_algo(algo_config->fallback_algo, state); goto return_rc; case DIFF_RC_OK: /* continue below */ break; default: /* some error happened */ goto return_rc; } /* Pick up any diff chunks that are still unsolved and feed to * inner_algo. inner_algo will solve unsolved chunks and append to * result, and subsequent solved chunks on this level are then appended * to result afterwards. */ int i; for (i = 0; i < state->temp_result.len; i++) { struct diff_chunk *c = &state->temp_result.head[i]; if (c->solved) { diff_state_add_solved_chunk(state, c); continue; } /* c is an unsolved chunk, feed to inner_algo */ struct diff_state inner_state = { .result = state->result, .recursion_depth_left = state->recursion_depth_left - 1, .kd_buf = state->kd_buf, .kd_buf_size = state->kd_buf_size, }; diff_data_init_subsection(&inner_state.left, &state->left, c->left_start, c->left_count); diff_data_init_subsection(&inner_state.right, &state->right, c->right_start, c->right_count); rc = diff_run_algo(algo_config->inner_algo, &inner_state); state->kd_buf = inner_state.kd_buf; state->kd_buf_size = inner_state.kd_buf_size; if (rc != DIFF_RC_OK) goto return_rc; } rc = DIFF_RC_OK; return_rc: ARRAYLIST_FREE(state->temp_result); return rc; } int diff_atomize_file(struct diff_data *d, const struct diff_config *config, FILE *f, const uint8_t *data, off_t len, int diff_flags) { if (!config->atomize_func) return EINVAL; diff_data_init_root(d, f, data, len, diff_flags); return config->atomize_func(config->atomize_func_data, d); } struct diff_result * diff_main(const struct diff_config *config, struct diff_data *left, struct diff_data *right) { struct diff_result *result = malloc(sizeof(struct diff_result)); if (!result) return NULL; *result = (struct diff_result){}; result->left = left; result->right = right; struct diff_state state = { .result = result, .recursion_depth_left = config->max_recursion_depth ? config->max_recursion_depth : UINT_MAX, .kd_buf = NULL, .kd_buf_size = 0, }; diff_data_init_subsection(&state.left, left, left->atoms.head, left->atoms.len); diff_data_init_subsection(&state.right, right, right->atoms.head, right->atoms.len); result->rc = diff_run_algo(config->algo, &state); free(state.kd_buf); return result; } void diff_result_free(struct diff_result *result) { if (!result) return; ARRAYLIST_FREE(result->chunks); free(result); } int diff_result_contains_printable_chunks(struct diff_result *result) { struct diff_chunk *c; enum diff_chunk_type t; int i; for (i = 0; i < result->chunks.len; i++) { c = &result->chunks.head[i]; t = diff_chunk_type(c); if (t == CHUNK_MINUS || t == CHUNK_PLUS) return 1; } return 0; } got-portable-0.111/lib/got_lib_hash.h0000644000175000017500000000602015001740614013131 /* * Copyright (c) 2018 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #define GOT_SHA1_STRING_ZERO "0000000000000000000000000000000000000000" #define GOT_SHA256_STRING_ZERO "0000000000000000000000000000000000000000000000000000000000000000" #define GOT_HASH_DIGEST_MAXLEN SHA256_DIGEST_LENGTH #define GOT_HASH_DIGEST_STRING_MAXLEN SHA256_DIGEST_STRING_LENGTH int got_parse_xdigit(uint8_t *, const char *); char *got_sha1_digest_to_str(const uint8_t *, char *, size_t); char *got_sha256_digest_to_str(const uint8_t *, char *, size_t); char *got_hash_digest_to_str(const uint8_t *, char *, size_t, enum got_hash_algorithm); int got_parse_hash_digest(uint8_t *, const char *, enum got_hash_algorithm); /* * Write the string representation fo an object ID in the given buffer. * This buffer must be at least GOT_OBJECT_ID_HEX_MAXLEN bytes in size. * The output depends on the hash function used by the repository format. */ char *got_object_id_hex(struct got_object_id *, char *, size_t); int got_parse_object_id(struct got_object_id *, const char *, enum got_hash_algorithm); static inline int got_hash_digest_length(enum got_hash_algorithm algo) { switch (algo) { case GOT_HASH_SHA1: return SHA1_DIGEST_LENGTH; case GOT_HASH_SHA256: return SHA256_DIGEST_LENGTH; default: return 0; } } static inline int got_hash_digest_string_length(enum got_hash_algorithm algo) { switch (algo) { case GOT_HASH_SHA1: return SHA1_DIGEST_STRING_LENGTH; case GOT_HASH_SHA256: return SHA256_DIGEST_STRING_LENGTH; default: return 0; } } struct got_hash { SHA1_CTX sha1_ctx; SHA2_CTX sha256_ctx; enum got_hash_algorithm algo; }; /* * These functions allow to compute and check hashes. * The hash function used is specified during got_hash_init. * Data can be added with got_hash_update and, once done, the checksum * saved in a buffer long at least GOT_HASH_DIGEST_MAXLEN bytes with * got_hash_final or in an got_object_id with got_hash_final_object_id. */ void got_hash_init(struct got_hash *, enum got_hash_algorithm); void got_hash_update(struct got_hash *, const void *, size_t); void got_hash_final(struct got_hash *, uint8_t *); void got_hash_final_object_id(struct got_hash *, struct got_object_id *); /* * Compare two hash digest; similar to memcmp(). */ int got_hash_cmp(enum got_hash_algorithm, uint8_t *, uint8_t *); got-portable-0.111/lib/gitconfig.c0000644000175000017500000004223115001741021012450 /* $OpenBSD: conf.c,v 1.107 2017/10/27 08:29:32 mpi Exp $ */ /* $EOM: conf.c,v 1.48 2000/12/04 02:04:29 angelos Exp $ */ /* * Copyright (c) 1998, 1999, 2000, 2001 Niklas Hallqvist. All rights reserved. * Copyright (c) 2000, 2001, 2002 Håkan Olsson. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include "got_compat.h" #include "got_error.h" #include "got_lib_gitconfig.h" #ifndef nitems #define nitems(_a) (sizeof(_a) / sizeof((_a)[0])) #endif #define LOG_MISC 0 #define LOG_REPORT 1 #ifdef GITCONFIG_DEBUG #define LOG_DBG(x) log_debug x #else #define LOG_DBG(x) #endif #define log_print(...) fprintf(stderr, __VA_ARGS__) #define log_error(...) fprintf(stderr, __VA_ARGS__) #ifdef GITCONFIG_DEBUG static void log_debug(int cls, int level, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); putc('\n', stderr); } #endif struct got_gitconfig_trans { TAILQ_ENTRY(got_gitconfig_trans) link; int trans; enum got_gitconfig_op { CONF_SET, CONF_REMOVE, CONF_REMOVE_SECTION } op; char *section; char *tag; char *value; int override; int is_default; }; TAILQ_HEAD(got_gitconfig_trans_head, got_gitconfig_trans); struct got_gitconfig_binding { LIST_ENTRY(got_gitconfig_binding) link; char *section; char *tag; char *value; int is_default; }; LIST_HEAD(got_gitconfig_bindings, got_gitconfig_binding); struct got_gitconfig { struct got_gitconfig_bindings bindings[256]; struct got_gitconfig_trans_head trans_queue; char *addr; int seq; }; static __inline__ u_int8_t conf_hash(const char *s) { u_int8_t hash = 0; while (*s) { hash = ((hash << 1) | (hash >> 7)) ^ tolower((unsigned char)*s); s++; } return hash; } /* * Insert a tag-value combination from LINE (the equal sign is at POS) */ static int conf_remove_now(struct got_gitconfig *conf, char *section, char *tag) { struct got_gitconfig_binding *cb, *next; for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb; cb = next) { next = LIST_NEXT(cb, link); if (strcasecmp(cb->section, section) == 0 && strcasecmp(cb->tag, tag) == 0) { LIST_REMOVE(cb, link); LOG_DBG((LOG_MISC, 95, "[%s]:%s->%s removed", section, tag, cb->value)); free(cb->section); free(cb->tag); free(cb->value); free(cb); return 0; } } return 1; } static int conf_remove_section_now(struct got_gitconfig *conf, char *section) { struct got_gitconfig_binding *cb, *next; int unseen = 1; for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb; cb = next) { next = LIST_NEXT(cb, link); if (strcasecmp(cb->section, section) == 0) { unseen = 0; LIST_REMOVE(cb, link); LOG_DBG((LOG_MISC, 95, "[%s]:%s->%s removed", section, cb->tag, cb->value)); free(cb->section); free(cb->tag); free(cb->value); free(cb); } } return unseen; } /* * Insert a tag-value combination from LINE (the equal sign is at POS) * into SECTION of our configuration database. */ static int conf_set_now(struct got_gitconfig *conf, char *section, char *tag, char *value, int override, int is_default) { struct got_gitconfig_binding *node = 0; if (override) conf_remove_now(conf, section, tag); else if (got_gitconfig_get_str(conf, section, tag)) { if (!is_default) LOG_DBG((LOG_MISC, 95, "conf_set_now: duplicate tag [%s]:%s, " "ignoring...", section, tag)); return 1; } node = calloc(1, sizeof *node); if (!node) { log_error("conf_set_now: calloc (1, %lu) failed", (unsigned long)sizeof *node); return 1; } node->section = node->tag = node->value = NULL; if ((node->section = strdup(section)) == NULL) goto fail; if ((node->tag = strdup(tag)) == NULL) goto fail; if ((node->value = strdup(value)) == NULL) goto fail; node->is_default = is_default; LIST_INSERT_HEAD(&conf->bindings[conf_hash(section)], node, link); LOG_DBG((LOG_MISC, 95, "conf_set_now: [%s]:%s->%s", node->section, node->tag, node->value)); return 0; fail: free(node->value); free(node->tag); free(node->section); free(node); return 1; } /* * Parse the line LINE of SZ bytes. Skip Comments, recognize section * headers and feed tag-value pairs into our configuration database. */ static const struct got_error * conf_parse_line(char **section, struct got_gitconfig *conf, int trans, char *line, int ln, size_t sz) { char *val; size_t i; int j; /* '[section]' parsing... */ if (*line == '[') { for (i = 1; i < sz; i++) if (line[i] == ']') break; free(*section); if (i == sz) { log_print("conf_parse_line: %d:" "unmatched ']', ignoring until next section", ln); *section = NULL; return NULL; } *section = strndup(line + 1, i - 1); if (*section == NULL) return got_error_from_errno("strndup"); return NULL; } while (sz > 0 && isspace((unsigned char)*line)) { line++; sz--; } /* Lines starting with '#' or ';' are comments. */ if (*line == '#' || *line == ';') return NULL; /* Deal with assignments. */ for (i = 0; i < sz; i++) if (line[i] == '=') { /* If no section, we are ignoring the lines. */ if (!*section) { log_print("conf_parse_line: %d: ignoring line " "due to no section", ln); return NULL; } line[strcspn(line, " \t=")] = '\0'; val = line + i + 1 + strspn(line + i + 1, " \t"); /* Skip trailing whitespace, if any */ for (j = sz - (val - line) - 1; j > 0 && isspace((unsigned char)val[j]); j--) val[j] = '\0'; /* XXX Perhaps should we not ignore errors? */ got_gitconfig_set(conf, trans, *section, line, val, 0, 0); return NULL; } /* Other non-empty lines are weird. */ i = strspn(line, " \t"); if (line[i]) log_print("conf_parse_line: %d: syntax error", ln); return NULL; } /* Parse the mapped configuration file. */ static const struct got_error * conf_parse(struct got_gitconfig *conf, int trans, char *buf, size_t sz) { const struct got_error *err = NULL; char *cp = buf; char *bufend = buf + sz; char *line, *section = NULL; int ln = 1; line = cp; while (cp < bufend) { if (*cp == '\n') { /* Check for escaped newlines. */ if (cp > buf && *(cp - 1) == '\\') *(cp - 1) = *cp = ' '; else { *cp = '\0'; err = conf_parse_line(§ion, conf, trans, line, ln, cp - line); if (err) return err; line = cp + 1; } ln++; } cp++; } if (cp != line) log_print("conf_parse: last line unterminated, ignored."); return NULL; } const struct got_error * got_gitconfig_open(struct got_gitconfig **conf, int fd) { size_t i; *conf = calloc(1, sizeof(**conf)); if (*conf == NULL) return got_error_from_errno("malloc"); for (i = 0; i < nitems((*conf)->bindings); i++) LIST_INIT(&(*conf)->bindings[i]); TAILQ_INIT(&(*conf)->trans_queue); return got_gitconfig_reinit(*conf, fd); } static void conf_clear(struct got_gitconfig *conf) { struct got_gitconfig_binding *cb; size_t i; if (conf->addr) { for (i = 0; i < nitems(conf->bindings); i++) for (cb = LIST_FIRST(&conf->bindings[i]); cb; cb = LIST_FIRST(&conf->bindings[i])) conf_remove_now(conf, cb->section, cb->tag); free(conf->addr); conf->addr = NULL; } } /* Execute all queued operations for this transaction. Cleanup. */ static int conf_end(struct got_gitconfig *conf, int transaction, int commit) { struct got_gitconfig_trans *node, *next; for (node = TAILQ_FIRST(&conf->trans_queue); node; node = next) { next = TAILQ_NEXT(node, link); if (node->trans == transaction) { if (commit) switch (node->op) { case CONF_SET: conf_set_now(conf, node->section, node->tag, node->value, node->override, node->is_default); break; case CONF_REMOVE: conf_remove_now(conf, node->section, node->tag); break; case CONF_REMOVE_SECTION: conf_remove_section_now(conf, node->section); break; default: log_print("got_gitconfig_end: unknown " "operation: %d", node->op); } TAILQ_REMOVE(&conf->trans_queue, node, link); free(node->section); free(node->tag); free(node->value); free(node); } } return 0; } void got_gitconfig_close(struct got_gitconfig *conf) { conf_clear(conf); free(conf); } static int conf_begin(struct got_gitconfig *conf) { return ++conf->seq; } /* Open the config file and map it into our address space, then parse it. */ const struct got_error * got_gitconfig_reinit(struct got_gitconfig *conf, int fd) { const struct got_error *err = NULL; int trans; size_t sz; char *new_conf_addr = 0; struct stat st; if (fstat(fd, &st)) { err = got_error_from_errno("fstat"); goto fail; } sz = st.st_size; new_conf_addr = malloc(sz); if (new_conf_addr == NULL) { err = got_error_from_errno("malloc"); goto fail; } /* XXX I assume short reads won't happen here. */ if (read(fd, new_conf_addr, sz) != (int)sz) { err = got_error_from_errno("read"); goto fail; } trans = conf_begin(conf); err = conf_parse(conf, trans, new_conf_addr, sz); if (err) goto fail; /* Free potential existing configuration. */ conf_clear(conf); conf_end(conf, trans, 1); conf->addr = new_conf_addr; return NULL; fail: free(new_conf_addr); return err; } /* * Return the numeric value denoted by TAG in section SECTION or DEF * if that tag does not exist. */ int got_gitconfig_get_num(struct got_gitconfig *conf, const char *section, const char *tag, int def) { char *value = got_gitconfig_get_str(conf, section, tag); if (value) return atoi(value); return def; } /* Validate X according to the range denoted by TAG in section SECTION. */ int got_gitconfig_match_num(struct got_gitconfig *conf, char *section, char *tag, int x) { char *value = got_gitconfig_get_str(conf, section, tag); int val, min, max, n; if (!value) return 0; n = sscanf(value, "%d,%d:%d", &val, &min, &max); switch (n) { case 1: LOG_DBG((LOG_MISC, 95, "got_gitconfig_match_num: %s:%s %d==%d?", section, tag, val, x)); return x == val; case 3: LOG_DBG((LOG_MISC, 95, "got_gitconfig_match_num: %s:%s %d<=%d<=%d?", section, tag, min, x, max)); return min <= x && max >= x; default: log_error("got_gitconfig_match_num: section %s tag %s: invalid number " "spec %s", section, tag, value); } return 0; } /* Return the string value denoted by TAG in section SECTION. */ char * got_gitconfig_get_str(struct got_gitconfig *conf, const char *section, const char *tag) { struct got_gitconfig_binding *cb; for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb; cb = LIST_NEXT(cb, link)) if (strcasecmp(section, cb->section) == 0 && strcasecmp(tag, cb->tag) == 0) { LOG_DBG((LOG_MISC, 95, "got_gitconfig_get_str: [%s]:%s->%s", section, tag, cb->value)); return cb->value; } LOG_DBG((LOG_MISC, 95, "got_gitconfig_get_str: configuration value not found [%s]:%s", section, tag)); return 0; } const struct got_error * got_gitconfig_get_section_list(struct got_gitconfig_list **sections, struct got_gitconfig *conf) { const struct got_error *err = NULL; struct got_gitconfig_list *list = NULL; struct got_gitconfig_list_node *node = 0; struct got_gitconfig_binding *cb; size_t i; *sections = NULL; list = malloc(sizeof *list); if (!list) return got_error_from_errno("malloc"); TAILQ_INIT(&list->fields); list->cnt = 0; for (i = 0; i < nitems(conf->bindings); i++) { for (cb = LIST_FIRST(&conf->bindings[i]); cb; cb = LIST_NEXT(cb, link)) { int section_present = 0; TAILQ_FOREACH(node, &list->fields, link) { if (strcmp(node->field, cb->section) == 0) { section_present = 1; break; } } if (section_present) continue; list->cnt++; node = calloc(1, sizeof *node); if (!node) { err = got_error_from_errno("calloc"); goto cleanup; } node->field = strdup(cb->section); if (!node->field) { err = got_error_from_errno("strdup"); goto cleanup; } TAILQ_INSERT_TAIL(&list->fields, node, link); } } *sections = list; return NULL; cleanup: free(node); if (list) got_gitconfig_free_list(list); return err; } /* * Build a list of string values out of the comma separated value denoted by * TAG in SECTION. */ struct got_gitconfig_list * got_gitconfig_get_list(struct got_gitconfig *conf, char *section, char *tag) { char *liststr = 0, *p, *field, *t; struct got_gitconfig_list *list = 0; struct got_gitconfig_list_node *node = 0; list = malloc(sizeof *list); if (!list) goto cleanup; TAILQ_INIT(&list->fields); list->cnt = 0; liststr = got_gitconfig_get_str(conf, section, tag); if (!liststr) goto cleanup; liststr = strdup(liststr); if (!liststr) goto cleanup; p = liststr; while ((field = strsep(&p, ",")) != NULL) { /* Skip leading whitespace */ while (isspace((unsigned char)*field)) field++; /* Skip trailing whitespace */ if (p) for (t = p - 1; t > field && isspace((unsigned char)*t); t--) *t = '\0'; if (*field == '\0') { log_print("got_gitconfig_get_list: empty field, ignoring..."); continue; } list->cnt++; node = calloc(1, sizeof *node); if (!node) goto cleanup; node->field = strdup(field); if (!node->field) goto cleanup; TAILQ_INSERT_TAIL(&list->fields, node, link); } free(liststr); return list; cleanup: free(node); if (list) got_gitconfig_free_list(list); free(liststr); return 0; } struct got_gitconfig_list * got_gitconfig_get_tag_list(struct got_gitconfig *conf, const char *section) { struct got_gitconfig_list *list = 0; struct got_gitconfig_list_node *node = 0; struct got_gitconfig_binding *cb; list = malloc(sizeof *list); if (!list) goto cleanup; TAILQ_INIT(&list->fields); list->cnt = 0; for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb; cb = LIST_NEXT(cb, link)) if (strcasecmp(section, cb->section) == 0) { list->cnt++; node = calloc(1, sizeof *node); if (!node) goto cleanup; node->field = strdup(cb->tag); if (!node->field) goto cleanup; TAILQ_INSERT_TAIL(&list->fields, node, link); } return list; cleanup: free(node); if (list) got_gitconfig_free_list(list); return 0; } void got_gitconfig_free_list(struct got_gitconfig_list *list) { struct got_gitconfig_list_node *node = TAILQ_FIRST(&list->fields); while (node) { TAILQ_REMOVE(&list->fields, node, link); free(node->field); free(node); node = TAILQ_FIRST(&list->fields); } free(list); } static int got_gitconfig_trans_node(struct got_gitconfig *conf, int transaction, enum got_gitconfig_op op, char *section, char *tag, char *value, int override, int is_default) { struct got_gitconfig_trans *node; node = calloc(1, sizeof *node); if (!node) { log_error("got_gitconfig_trans_node: calloc (1, %lu) failed", (unsigned long)sizeof *node); return 1; } node->trans = transaction; node->op = op; node->override = override; node->is_default = is_default; if (section && (node->section = strdup(section)) == NULL) goto fail; if (tag && (node->tag = strdup(tag)) == NULL) goto fail; if (value && (node->value = strdup(value)) == NULL) goto fail; TAILQ_INSERT_TAIL(&conf->trans_queue, node, link); return 0; fail: free(node->section); free(node->tag); free(node->value); free(node); return 1; } /* Queue a set operation. */ int got_gitconfig_set(struct got_gitconfig *conf, int transaction, char *section, char *tag, char *value, int override, int is_default) { return got_gitconfig_trans_node(conf, transaction, CONF_SET, section, tag, value, override, is_default); } /* Queue a remove operation. */ int got_gitconfig_remove(struct got_gitconfig *conf, int transaction, char *section, char *tag) { return got_gitconfig_trans_node(conf, transaction, CONF_REMOVE, section, tag, NULL, 0, 0); } /* Queue a remove section operation. */ int got_gitconfig_remove_section(struct got_gitconfig *conf, int transaction, char *section) { return got_gitconfig_trans_node(conf, transaction, CONF_REMOVE_SECTION, section, NULL, NULL, 0, 0); } got-portable-0.111/lib/diff_output_edscript.c0000644000175000017500000001243415001740614014735 /* Produce ed(1) script output from a diff_result. */ /* * Copyright (c) 2020 Neels Hofmeyr * Copyright (c) 2020 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include "diff_internal.h" static int output_edscript_chunk(struct diff_output_info *outinfo, FILE *dest, const struct diff_input_info *info, const struct diff_result *result, struct diff_chunk_context *cc) { off_t outoff = 0, *offp; int left_start, left_len, right_start, right_len; int rc; left_len = cc->left.end - cc->left.start; if (left_len < 0) return EINVAL; else if (result->left->atoms.len == 0) left_start = 0; else if (left_len == 0 && cc->left.start > 0) left_start = cc->left.start; else if (cc->left.end > 0) left_start = cc->left.start + 1; else left_start = cc->left.start; right_len = cc->right.end - cc->right.start; if (right_len < 0) return EINVAL; else if (result->right->atoms.len == 0) right_start = 0; else if (right_len == 0 && cc->right.start > 0) right_start = cc->right.start; else if (cc->right.end > 0) right_start = cc->right.start + 1; else right_start = cc->right.start; if (left_len == 0) { /* addition */ if (right_len == 1) { rc = fprintf(dest, "%da%d\n", left_start, right_start); } else { rc = fprintf(dest, "%da%d,%d\n", left_start, right_start, cc->right.end); } } else if (right_len == 0) { /* deletion */ if (left_len == 1) { rc = fprintf(dest, "%dd%d\n", left_start, right_start); } else { rc = fprintf(dest, "%d,%dd%d\n", left_start, cc->left.end, right_start); } } else { /* change */ if (left_len == 1 && right_len == 1) { rc = fprintf(dest, "%dc%d\n", left_start, right_start); } else if (left_len == 1) { rc = fprintf(dest, "%dc%d,%d\n", left_start, right_start, cc->right.end); } else if (right_len == 1) { rc = fprintf(dest, "%d,%dc%d\n", left_start, cc->left.end, right_start); } else { rc = fprintf(dest, "%d,%dc%d,%d\n", left_start, cc->left.end, right_start, cc->right.end); } } if (rc < 0) return errno; if (outinfo) { ARRAYLIST_ADD(offp, outinfo->line_offsets); if (offp == NULL) return ENOMEM; outoff += rc; *offp = outoff; } return DIFF_RC_OK; } int diff_output_edscript(struct diff_output_info **output_info, FILE *dest, const struct diff_input_info *info, const struct diff_result *result) { struct diff_output_info *outinfo = NULL; struct diff_chunk_context cc = {}; int atomizer_flags = (result->left->atomizer_flags| result->right->atomizer_flags); int flags = (result->left->root->diff_flags | result->right->root->diff_flags); bool force_text = (flags & DIFF_FLAG_FORCE_TEXT_DATA); bool have_binary = (atomizer_flags & DIFF_ATOMIZER_FOUND_BINARY_DATA); int i, rc; if (!result) return EINVAL; if (result->rc != DIFF_RC_OK) return result->rc; if (output_info) { *output_info = diff_output_info_alloc(); if (*output_info == NULL) return ENOMEM; outinfo = *output_info; } if (have_binary && !force_text) { for (i = 0; i < result->chunks.len; i++) { struct diff_chunk *c = &result->chunks.head[i]; enum diff_chunk_type t = diff_chunk_type(c); if (t != CHUNK_MINUS && t != CHUNK_PLUS) continue; fprintf(dest, "Binary files %s and %s differ\n", diff_output_get_label_left(info), diff_output_get_label_right(info)); break; } return DIFF_RC_OK; } for (i = 0; i < result->chunks.len; i++) { struct diff_chunk *chunk = &result->chunks.head[i]; enum diff_chunk_type t = diff_chunk_type(chunk); struct diff_chunk_context next; if (t != CHUNK_MINUS && t != CHUNK_PLUS) continue; if (diff_chunk_context_empty(&cc)) { /* Note down the start point, any number of subsequent * chunks may be joined up to this chunk by being * directly adjacent. */ diff_chunk_context_get(&cc, result, i, 0); continue; } /* There already is a previous chunk noted down for being * printed. Does it join up with this one? */ diff_chunk_context_get(&next, result, i, 0); if (diff_chunk_contexts_touch(&cc, &next)) { /* This next context touches or overlaps the previous * one, join. */ diff_chunk_contexts_merge(&cc, &next); continue; } rc = output_edscript_chunk(outinfo, dest, info, result, &cc); if (rc != DIFF_RC_OK) return rc; cc = next; } if (!diff_chunk_context_empty(&cc)) return output_edscript_chunk(outinfo, dest, info, result, &cc); return DIFF_RC_OK; } got-portable-0.111/lib/murmurhash2.h0000644000175000017500000000054315001740614012770 //----------------------------------------------------------------------------- // MurmurHash2 was written by Austin Appleby, and is placed in the public // domain. The author hereby disclaims copyright to this source code. /* Obtained from https://github.com/aappleby/smhasher */ uint32_t murmurhash2(const unsigned char *key, int len, uint32_t seed); got-portable-0.111/lib/got_lib_worktree_cvg.h0000644000175000017500000000173115001740614014713 /* * Copyright (c) 2023 Josh Rickmar * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* Reference pointing to temporary commits that may need trivial rebasing. */ #define GOT_WORKTREE_COMMIT_REF_PREFIX "refs/got/worktree/commit" #define GOT_WORKTREE_COMMIT_REF_PREFIX_LEN 24 got-portable-0.111/lib/repository_admin.c0000644000175000017500000012301715001741021014070 /* * Copyright (c) 2020 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_cancel.h" #include "got_object.h" #include "got_reference.h" #include "got_repository.h" #include "got_repository_admin.h" #include "got_opentemp.h" #include "got_path.h" #include "got_lib_delta.h" #include "got_lib_hash.h" #include "got_lib_object.h" #include "got_lib_object_idset.h" #include "got_lib_object_cache.h" #include "got_lib_pack.h" #include "got_lib_privsep.h" #include "got_lib_repository.h" #include "got_lib_ratelimit.h" #include "got_lib_pack_create.h" #include "got_lib_lockfile.h" #ifndef nitems #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) #endif static const struct got_error * get_reflist_object_ids(struct got_object_id ***ids, int *nobjects, unsigned int wanted_obj_type_mask, struct got_reflist_head *refs, struct got_repository *repo, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; const size_t alloc_chunksz = 256; size_t nalloc; struct got_reflist_entry *re; int i; *ids = NULL; *nobjects = 0; err = got_reflist_sort(refs, got_ref_cmp_by_commit_timestamp_descending, repo); if (err) return err; *ids = reallocarray(NULL, alloc_chunksz, sizeof(struct got_object_id *)); if (*ids == NULL) return got_error_from_errno("reallocarray"); nalloc = alloc_chunksz; TAILQ_FOREACH(re, refs, entry) { struct got_object_id *id; if (cancel_cb) { err = cancel_cb(cancel_arg); if (err) goto done; } err = got_ref_resolve(&id, repo, re->ref); if (err) goto done; if (wanted_obj_type_mask != GOT_OBJ_TYPE_ANY) { int obj_type; err = got_object_get_type(&obj_type, repo, id); if (err) goto done; if ((wanted_obj_type_mask & (1 << obj_type)) == 0) { free(id); id = NULL; continue; } } if (nalloc <= *nobjects) { struct got_object_id **new; new = recallocarray(*ids, nalloc, nalloc + alloc_chunksz, sizeof(struct got_object_id *)); if (new == NULL) { err = got_error_from_errno( "recallocarray"); goto done; } *ids = new; nalloc += alloc_chunksz; } (*ids)[*nobjects] = id; if ((*ids)[*nobjects] == NULL) { err = got_error_from_errno("got_object_id_dup"); goto done; } (*nobjects)++; } done: if (err) { for (i = 0; i < *nobjects; i++) free((*ids)[i]); free(*ids); *ids = NULL; *nobjects = 0; } return err; } static const struct got_error * create_temp_packfile(int *packfd, char **tmpfile_path, struct got_repository *repo) { const struct got_error *err = NULL; char *path; *packfd = -1; if (asprintf(&path, "%s/%s/packing.pack", got_repo_get_path_git_dir(repo), GOT_OBJECTS_PACK_DIR) == -1) return got_error_from_errno("asprintf"); err = got_opentemp_named_fd(tmpfile_path, packfd, path, ""); if (err) goto done; if (fchmod(*packfd, GOT_DEFAULT_PACK_MODE) == -1) err = got_error_from_errno2("fchmod", *tmpfile_path); done: free(path); if (err) { if (*packfd != -1) close(*packfd); *packfd = -1; free(*tmpfile_path); *tmpfile_path = NULL; } return err; } static const struct got_error * install_packfile(FILE **packfile, int *packfd, char **packfile_path, char **tmpfile_path, struct got_object_id *pack_hash, struct got_repository *repo) { const struct got_error *err; char *hash_str; err = got_object_id_str(&hash_str, pack_hash); if (err) return err; if (asprintf(packfile_path, "%s/%s/pack-%s.pack", got_repo_get_path_git_dir(repo), GOT_OBJECTS_PACK_DIR, hash_str) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (lseek(*packfd, 0L, SEEK_SET) == -1) { err = got_error_from_errno("lseek"); goto done; } if (rename(*tmpfile_path, *packfile_path) == -1) { err = got_error_from_errno3("rename", *tmpfile_path, *packfile_path); goto done; } free(*tmpfile_path); *tmpfile_path = NULL; *packfile = fdopen(*packfd, "w"); if (*packfile == NULL) { err = got_error_from_errno2("fdopen", *packfile_path); goto done; } *packfd = -1; done: free(hash_str); return err; } const struct got_error * got_repo_pack_objects(FILE **packfile, struct got_object_id **pack_hash, struct got_reflist_head *include_refs, struct got_reflist_head *exclude_refs, struct got_repository *repo, int loose_obj_only, int force_refdelta, got_pack_progress_cb progress_cb, void *progress_arg, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; struct got_object_id **ours = NULL, **theirs = NULL; int nours = 0, ntheirs = 0, packfd = -1, i; char *tmpfile_path = NULL, *packfile_path = NULL; FILE *delta_cache = NULL; struct got_ratelimit rl; *packfile = NULL; *pack_hash = NULL; got_ratelimit_init(&rl, 0, 500); err = create_temp_packfile(&packfd, &tmpfile_path, repo); if (err) return err; delta_cache = got_opentemp(); if (delta_cache == NULL) { err = got_error_from_errno("got_opentemp"); goto done; } err = get_reflist_object_ids(&ours, &nours, (1 << GOT_OBJ_TYPE_COMMIT) | (1 << GOT_OBJ_TYPE_TAG), include_refs, repo, cancel_cb, cancel_arg); if (err) goto done; if (nours == 0) { err = got_error(GOT_ERR_CANNOT_PACK); goto done; } if (!TAILQ_EMPTY(exclude_refs)) { err = get_reflist_object_ids(&theirs, &ntheirs, (1 << GOT_OBJ_TYPE_COMMIT) | (1 << GOT_OBJ_TYPE_TAG), exclude_refs, repo, cancel_cb, cancel_arg); if (err) goto done; } *pack_hash = calloc(1, sizeof(**pack_hash)); if (*pack_hash == NULL) { err = got_error_from_errno("calloc"); goto done; } err = got_pack_create(*pack_hash, packfd, delta_cache, theirs, ntheirs, ours, nours, repo, loose_obj_only, 0, force_refdelta, progress_cb, progress_arg, &rl, cancel_cb, cancel_arg); if (err) goto done; err = install_packfile(packfile, &packfd, &packfile_path, &tmpfile_path, *pack_hash, repo); done: for (i = 0; i < nours; i++) free(ours[i]); free(ours); for (i = 0; i < ntheirs; i++) free(theirs[i]); free(theirs); if (packfd != -1 && close(packfd) == -1 && err == NULL) err = got_error_from_errno2("close", packfile_path ? packfile_path : tmpfile_path); if (delta_cache && fclose(delta_cache) == EOF && err == NULL) err = got_error_from_errno("fclose"); if (tmpfile_path && unlink(tmpfile_path) == -1 && err == NULL) err = got_error_from_errno2("unlink", tmpfile_path); free(tmpfile_path); free(packfile_path); if (err) { free(*pack_hash); *pack_hash = NULL; if (*packfile) fclose(*packfile); *packfile = NULL; } return err; } const struct got_error * got_repo_index_pack(char **idxpath, FILE *packfile, struct got_object_id *pack_hash, struct got_repository *repo, got_pack_index_progress_cb progress_cb, void *progress_arg, got_cancel_cb cancel_cb, void *cancel_arg) { size_t i; char *path; int imsg_idxfds[2]; int npackfd = -1, idxfd = -1, nidxfd = -1; int tmpfds[3]; int idxstatus, done = 0; int nobj_total = 0, nobj_indexed = 0, nobj_loose = 0, nobj_resolved = 0; const struct got_error *err; struct imsgbuf idxibuf; pid_t idxpid; char *tmpidxpath = NULL; char *packfile_path = NULL, *id_str = NULL; const char *repo_path = got_repo_get_path_git_dir(repo); struct stat sb; *idxpath = NULL; memset(&idxibuf, 0, sizeof(idxibuf)); for (i = 0; i < nitems(tmpfds); i++) tmpfds[i] = -1; if (asprintf(&path, "%s/%s/indexing.idx", repo_path, GOT_OBJECTS_PACK_DIR) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = got_opentemp_named_fd(&tmpidxpath, &idxfd, path, ""); free(path); if (err) goto done; if (fchmod(idxfd, GOT_DEFAULT_PACK_MODE) == -1) { err = got_error_from_errno2("fchmod", tmpidxpath); goto done; } nidxfd = dup(idxfd); if (nidxfd == -1) { err = got_error_from_errno("dup"); goto done; } for (i = 0; i < nitems(tmpfds); i++) { tmpfds[i] = got_opentempfd(); if (tmpfds[i] == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } } err = got_object_id_str(&id_str, pack_hash); if (err) goto done; if (asprintf(&packfile_path, "%s/%s/pack-%s.pack", repo_path, GOT_OBJECTS_PACK_DIR, id_str) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (fstat(fileno(packfile), &sb) == -1) { err = got_error_from_errno2("fstat", packfile_path); goto done; } if (asprintf(idxpath, "%s/%s/pack-%s.idx", repo_path, GOT_OBJECTS_PACK_DIR, id_str) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_idxfds) == -1) { err = got_error_from_errno("socketpair"); goto done; } idxpid = fork(); if (idxpid == -1) { err= got_error_from_errno("fork"); goto done; } else if (idxpid == 0) got_privsep_exec_child(imsg_idxfds, GOT_PATH_PROG_INDEX_PACK, packfile_path); if (close(imsg_idxfds[1]) == -1) { err = got_error_from_errno("close"); goto done; } if (imsgbuf_init(&idxibuf, imsg_idxfds[0]) == -1) { err = got_error_from_errno("imsgbuf_init"); goto done; } imsgbuf_allow_fdpass(&idxibuf); npackfd = dup(fileno(packfile)); if (npackfd == -1) { err = got_error_from_errno("dup"); goto done; } err = got_privsep_send_index_pack_req(&idxibuf, pack_hash, npackfd); if (err != NULL) goto done; npackfd = -1; err = got_privsep_send_index_pack_outfd(&idxibuf, nidxfd); if (err != NULL) goto done; nidxfd = -1; for (i = 0; i < nitems(tmpfds); i++) { err = got_privsep_send_tmpfd(&idxibuf, tmpfds[i]); if (err != NULL) goto done; tmpfds[i] = -1; } done = 0; while (!done) { if (cancel_cb) { err = cancel_cb(cancel_arg); if (err) goto done; } err = got_privsep_recv_index_progress(&done, &nobj_total, &nobj_indexed, &nobj_loose, &nobj_resolved, &idxibuf); if (err != NULL) goto done; if (nobj_indexed != 0) { err = progress_cb(progress_arg, sb.st_size, nobj_total, nobj_indexed, nobj_loose, nobj_resolved, 0); if (err) break; } } if (done) { err = progress_cb(progress_arg, sb.st_size, nobj_total, nobj_indexed, nobj_loose, nobj_resolved, done); if (err) goto done; } if (close(imsg_idxfds[0]) == -1) { err = got_error_from_errno("close"); goto done; } if (waitpid(idxpid, &idxstatus, 0) == -1) { err = got_error_from_errno("waitpid"); goto done; } if (rename(tmpidxpath, *idxpath) == -1) { err = got_error_from_errno3("rename", tmpidxpath, *idxpath); goto done; } free(tmpidxpath); tmpidxpath = NULL; done: if (idxibuf.w) imsgbuf_clear(&idxibuf); if (tmpidxpath && unlink(tmpidxpath) == -1 && err == NULL) err = got_error_from_errno2("unlink", tmpidxpath); if (npackfd != -1 && close(npackfd) == -1 && err == NULL) err = got_error_from_errno("close"); if (idxfd != -1 && close(idxfd) == -1 && err == NULL) err = got_error_from_errno("close"); for (i = 0; i < nitems(tmpfds); i++) { if (tmpfds[i] != -1 && close(tmpfds[i]) == -1 && err == NULL) err = got_error_from_errno("close"); } free(tmpidxpath); free(packfile_path); return err; } const struct got_error * got_repo_find_pack(FILE **packfile, struct got_object_id **pack_hash, struct got_repository *repo, const char *packfile_path) { const struct got_error *err = NULL; const char *packdir_path = NULL; char *packfile_name = NULL, *p, *dot; struct got_object_id id; int packfd = -1; *packfile = NULL; *pack_hash = NULL; packdir_path = got_repo_get_path_objects_pack(repo); if (packdir_path == NULL) return got_error_from_errno("got_repo_get_path_objects_pack"); if (!got_path_is_child(packfile_path, packdir_path, strlen(packdir_path))) { err = got_error_path(packfile_path, GOT_ERR_BAD_PATH); goto done; } err = got_path_basename(&packfile_name, packfile_path); if (err) goto done; p = packfile_name; if (strncmp(p, "pack-", 5) != 0) { err = got_error_fmt(GOT_ERR_BAD_PATH, "'%s' is not a valid pack file name", packfile_name); goto done; } p += 5; dot = strchr(p, '.'); if (dot == NULL) { err = got_error_fmt(GOT_ERR_BAD_PATH, "'%s' is not a valid pack file name", packfile_name); goto done; } if (strcmp(dot + 1, "pack") != 0) { err = got_error_fmt(GOT_ERR_BAD_PATH, "'%s' is not a valid pack file name", packfile_name); goto done; } *dot = '\0'; if (!got_parse_object_id(&id, p, repo->algo)) { err = got_error_fmt(GOT_ERR_BAD_PATH, "'%s' is not a valid pack file name", packfile_name); goto done; } *pack_hash = got_object_id_dup(&id); if (*pack_hash == NULL) { err = got_error_from_errno("got_object_id_dup"); goto done; } packfd = open(packfile_path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (packfd == -1) { err = got_error_from_errno2("open", packfile_path); goto done; } *packfile = fdopen(packfd, "r"); if (*packfile == NULL) { err = got_error_from_errno2("fdopen", packfile_path); goto done; } packfd = -1; done: if (packfd != -1 && close(packfd) == -1 && err == NULL) err = got_error_from_errno2("close", packfile_path); free(packfile_name); if (err) { free(*pack_hash); *pack_hash = NULL; } return err; } const struct got_error * got_repo_list_pack(FILE *packfile, struct got_object_id *pack_hash, struct got_repository *repo, got_pack_list_cb list_cb, void *list_arg, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; char *id_str = NULL, *idxpath = NULL, *packpath = NULL; struct got_packidx *packidx = NULL; struct got_pack *pack = NULL; uint32_t nobj, i; size_t digest_len = got_hash_digest_length(repo->algo); err = got_object_id_str(&id_str, pack_hash); if (err) goto done; if (asprintf(&packpath, "%s/pack-%s.pack", GOT_OBJECTS_PACK_DIR, id_str) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (asprintf(&idxpath, "%s/pack-%s.idx", GOT_OBJECTS_PACK_DIR, id_str) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = got_packidx_open(&packidx, got_repo_get_fd(repo), idxpath, 1, repo->algo); if (err) goto done; err = got_repo_cache_pack(&pack, repo, packpath, packidx); if (err) goto done; nobj = be32toh(packidx->hdr.fanout_table[0xff]); for (i = 0; i < nobj; i++) { uint8_t *oid; struct got_object_id id, base_id; off_t offset, base_offset = 0; uint8_t type; uint64_t size; size_t tslen, len; if (cancel_cb) { err = cancel_cb(cancel_arg); if (err) break; } oid = packidx->hdr.sorted_ids + i * digest_len; id.algo = repo->algo; memcpy(id.hash, oid, digest_len); offset = got_packidx_get_object_offset(packidx, i); if (offset == -1) { err = got_error(GOT_ERR_BAD_PACKIDX); goto done; } err = got_pack_parse_object_type_and_size(&type, &size, &tslen, pack, offset); if (err) goto done; switch (type) { case GOT_OBJ_TYPE_OFFSET_DELTA: err = got_pack_parse_offset_delta(&base_offset, &len, pack, offset, tslen); if (err) goto done; break; case GOT_OBJ_TYPE_REF_DELTA: err = got_pack_parse_ref_delta(&base_id, pack, offset, tslen); if (err) goto done; break; } err = (*list_cb)(list_arg, &id, type, offset, size, base_offset, &base_id); if (err) goto done; } done: free(id_str); free(idxpath); free(packpath); if (packidx) got_packidx_close(packidx); return err; } static const struct got_error * repo_cleanup_lock(struct got_repository *repo, struct got_lockfile **lk) { const struct got_error *err; char myname[_POSIX_HOST_NAME_MAX + 1]; if (gethostname(myname, sizeof(myname)) == -1) return got_error_from_errno("gethostname"); err = got_lockfile_lock(lk, "gc.pid", got_repo_get_fd(repo)); if (err) return err; /* * Git uses these info to provide some verbiage when finds a * lock during `git gc --force' so don't try too hard to avoid * short writes and don't care if a race happens between the * lockfile creation and the write itself. */ if (dprintf((*lk)->fd, "%d %s", getpid(), myname) < 0) return got_error_from_errno("dprintf"); return NULL; } static const struct got_error * report_cleanup_progress(got_cleanup_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, int ncommits, int nloose, int npurged, int nredundant) { const struct got_error *err; int elapsed; if (progress_cb == NULL) return NULL; err = got_ratelimit_check(&elapsed, rl); if (err || !elapsed) return err; return progress_cb(progress_arg, ncommits, nloose, npurged, nredundant); } static const struct got_error * get_loose_object_ids(struct got_object_idset **loose_ids, off_t *ondisk_size, int ncommits, got_cleanup_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, struct got_repository *repo) { const struct got_error *err = NULL; char *path_objects = NULL, *path = NULL; DIR *dir = NULL; struct got_object *obj = NULL; struct got_object_id id; int i, fd = -1; struct stat sb; *ondisk_size = 0; *loose_ids = got_object_idset_alloc(); if (*loose_ids == NULL) return got_error_from_errno("got_object_idset_alloc"); path_objects = got_repo_get_path_objects(repo); if (path_objects == NULL) { err = got_error_from_errno("got_repo_get_path_objects"); goto done; } for (i = 0; i <= 0xff; i++) { struct dirent *dent; if (asprintf(&path, "%s/%.2x", path_objects, i) == -1) { err = got_error_from_errno("asprintf"); break; } dir = opendir(path); if (dir == NULL) { if (errno == ENOENT) { err = NULL; continue; } err = got_error_from_errno2("opendir", path); break; } while ((dent = readdir(dir)) != NULL) { char *id_str; if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0) continue; if (asprintf(&id_str, "%.2x%s", i, dent->d_name) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (!got_parse_object_id(&id, id_str, repo->algo)) { free(id_str); continue; } free(id_str); err = got_object_open_loose_fd(&fd, &id, repo); if (err) goto done; if (fstat(fd, &sb) == -1) { err = got_error_from_errno("fstat"); goto done; } err = got_object_read_header_privsep(&obj, &id, repo, fd); if (err) goto done; fd = -1; /* already closed */ switch (obj->type) { case GOT_OBJ_TYPE_COMMIT: case GOT_OBJ_TYPE_TREE: case GOT_OBJ_TYPE_BLOB: case GOT_OBJ_TYPE_TAG: break; default: err = got_error_fmt(GOT_ERR_OBJ_TYPE, "%d", obj->type); goto done; } got_object_close(obj); obj = NULL; (*ondisk_size) += sb.st_size; err = got_object_idset_add(*loose_ids, &id, NULL); if (err) goto done; err = report_cleanup_progress(progress_cb, progress_arg, rl, ncommits, got_object_idset_num_elements(*loose_ids), -1, -1); if (err) goto done; } if (closedir(dir) != 0) { err = got_error_from_errno("closedir"); goto done; } dir = NULL; free(path); path = NULL; } done: if (dir && closedir(dir) != 0 && err == NULL) err = got_error_from_errno("closedir"); if (fd != -1 && close(fd) == -1 && err == NULL) err = got_error_from_errno("close"); if (err) { got_object_idset_free(*loose_ids); *loose_ids = NULL; } if (obj) got_object_close(obj); free(path_objects); free(path); return err; } static const struct got_error * load_tree_entries(struct got_object_id_queue *ids, struct got_object_idset *traversed_ids, struct got_object_id *tree_id, const char *dpath, struct got_repository *repo, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err; struct got_tree_object *tree; char *p = NULL; int i; err = got_object_open_as_tree(&tree, repo, tree_id); if (err) return err; for (i = 0; i < got_object_tree_get_nentries(tree); i++) { struct got_tree_entry *e = got_object_tree_get_entry(tree, i); struct got_object_id *id = got_tree_entry_get_id(e); mode_t mode = got_tree_entry_get_mode(e); if (cancel_cb) { err = (*cancel_cb)(cancel_arg); if (err) break; } if (got_object_tree_entry_is_symlink(e) || got_object_tree_entry_is_submodule(e) || got_object_idset_contains(traversed_ids, id)) continue; if (asprintf(&p, "%s%s%s", dpath, dpath[0] != '\0' ? "/" : "", got_tree_entry_get_name(e)) == -1) { err = got_error_from_errno("asprintf"); break; } if (S_ISDIR(mode)) { struct got_object_qid *qid; err = got_object_qid_alloc(&qid, id); if (err) break; STAILQ_INSERT_TAIL(ids, qid, entry); } else if (S_ISREG(mode)) { /* This blob is referenced. */ err = got_object_idset_add(traversed_ids, id, NULL); if (err) break; } free(p); p = NULL; } got_object_tree_close(tree); free(p); return err; } static const struct got_error * load_tree(struct got_object_idset *traversed_ids, struct got_object_id *tree_id, const char *dpath, struct got_repository *repo, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; struct got_object_id_queue tree_ids; struct got_object_qid *qid; err = got_object_qid_alloc(&qid, tree_id); if (err) return err; STAILQ_INIT(&tree_ids); STAILQ_INSERT_TAIL(&tree_ids, qid, entry); while (!STAILQ_EMPTY(&tree_ids)) { if (cancel_cb) { err = (*cancel_cb)(cancel_arg); if (err) break; } qid = STAILQ_FIRST(&tree_ids); STAILQ_REMOVE_HEAD(&tree_ids, entry); if (got_object_idset_contains(traversed_ids, &qid->id)) { got_object_qid_free(qid); continue; } err = got_object_idset_add(traversed_ids, &qid->id, NULL); if (err) { got_object_qid_free(qid); break; } err = load_tree_entries(&tree_ids, traversed_ids, &qid->id, dpath, repo, cancel_cb, cancel_arg); got_object_qid_free(qid); if (err) break; } got_object_id_queue_free(&tree_ids); return err; } static const struct got_error * load_commit_or_tag(int *ncommits, struct got_object_idset *traversed_ids, struct got_object_id *id, struct got_repository *repo, got_cleanup_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err; struct got_commit_object *commit = NULL; struct got_tag_object *tag = NULL; struct got_object_id *tree_id = NULL; struct got_object_id_queue ids; struct got_object_qid *qid; int obj_type; err = got_object_qid_alloc(&qid, id); if (err) return err; STAILQ_INIT(&ids); STAILQ_INSERT_TAIL(&ids, qid, entry); while (!STAILQ_EMPTY(&ids)) { if (cancel_cb) { err = (*cancel_cb)(cancel_arg); if (err) break; } qid = STAILQ_FIRST(&ids); STAILQ_REMOVE_HEAD(&ids, entry); if (got_object_idset_contains(traversed_ids, &qid->id)) { got_object_qid_free(qid); qid = NULL; continue; } err = got_object_idset_add(traversed_ids, &qid->id, NULL); if (err) break; err = got_object_get_type(&obj_type, repo, &qid->id); if (err) break; switch (obj_type) { case GOT_OBJ_TYPE_COMMIT: err = got_object_open_as_commit(&commit, repo, &qid->id); if (err) goto done; tree_id = got_object_commit_get_tree_id(commit); break; case GOT_OBJ_TYPE_TAG: err = got_object_open_as_tag(&tag, repo, &qid->id); if (err) goto done; /* tree_id will be set below */ break; case GOT_OBJ_TYPE_TREE: tree_id = &qid->id; break; case GOT_OBJ_TYPE_BLOB: tree_id = NULL; break; default: /* should not happen */ err = got_error(GOT_ERR_OBJ_TYPE); goto done; } if (tag) { struct got_object_id *id; obj_type = got_object_tag_get_object_type(tag); while (obj_type == GOT_OBJ_TYPE_TAG) { struct got_tag_object *next_tag; id = got_object_tag_get_object_id(tag); if (!got_object_idset_contains(traversed_ids, id)) { err = got_object_idset_add( traversed_ids, id, NULL); if (err) goto done; } err = got_object_open_as_tag(&next_tag, repo, id); if (err) goto done; got_object_tag_close(tag); tag = next_tag; obj_type = got_object_tag_get_object_type(tag); } id = got_object_tag_get_object_id(tag); switch (obj_type) { case GOT_OBJ_TYPE_COMMIT: err = got_object_open_as_commit(&commit, repo, id); if (err) goto done; tree_id = got_object_commit_get_tree_id(commit); break; case GOT_OBJ_TYPE_TREE: tree_id = id; break; case GOT_OBJ_TYPE_BLOB: if (got_object_idset_contains(traversed_ids, id)) break; err = got_object_idset_add(traversed_ids, id, NULL); if (err) goto done; break; default: /* should not happen */ err = got_error(GOT_ERR_OBJ_TYPE); goto done; } } else if (tree_id == NULL) { /* Blob which has already been marked as traversed. */ continue; } if (tree_id) { err = load_tree(traversed_ids, tree_id, "", repo, cancel_cb, cancel_arg); if (err) break; tree_id = NULL; } if (commit || tag) (*ncommits)++; /* scanned tags are counted as commits */ err = report_cleanup_progress(progress_cb, progress_arg, rl, *ncommits, -1, -1, -1); if (err) break; if (commit) { /* Find parent commits to scan. */ const struct got_object_id_queue *parent_ids; parent_ids = got_object_commit_get_parent_ids(commit); err = got_object_id_queue_copy(parent_ids, &ids); if (err) break; got_object_commit_close(commit); commit = NULL; } if (tag) { got_object_tag_close(tag); tag = NULL; } got_object_qid_free(qid); qid = NULL; } done: if (qid) got_object_qid_free(qid); if (commit) got_object_commit_close(commit); if (tag) got_object_tag_close(tag); got_object_id_queue_free(&ids); return err; } static const struct got_error * is_object_packed(int *packed, struct got_repository *repo, struct got_object_id *id) { const struct got_error *err; struct got_object *obj; *packed = 0; err = got_object_open_packed(&obj, id, repo); if (err) { if (err->code == GOT_ERR_NO_OBJ) err = NULL; return err; } got_object_close(obj); *packed = 1; return NULL; } struct purge_loose_object_arg { struct got_repository *repo; got_cleanup_progress_cb progress_cb; void *progress_arg; struct got_ratelimit *rl; struct got_object_idset *traversed_ids; int nloose; int ncommits; int npacked; int npurged; off_t size_purged; int dry_run; time_t max_mtime; int ignore_mtime; }; static const struct got_error * purge_loose_object(struct got_object_id *id, void *data, void *arg) { struct purge_loose_object_arg *a = arg; const struct got_error *err, *unlock_err = NULL; char *path = NULL; int packed, fd = -1; struct stat sb; struct got_lockfile *lf = NULL; err = is_object_packed(&packed, a->repo, id); if (err) return err; if (!packed && got_object_idset_contains(a->traversed_ids, id)) return NULL; if (packed) a->npacked++; err = got_object_get_path(&path, id, a->repo); if (err) return err; err = got_object_open_loose_fd(&fd, id, a->repo); if (err) goto done; if (fstat(fd, &sb) == -1) { err = got_error_from_errno("fstat"); goto done; } /* * Do not delete objects which are younger than our maximum * modification time threshold. This prevents a race where * new objects which are being added to the repository * concurrently would be deleted. */ if (a->ignore_mtime || sb.st_mtime <= a->max_mtime) { if (!a->dry_run) { err = got_lockfile_lock(&lf, path, -1); if (err) goto done; if (unlink(path) == -1) { err = got_error_from_errno2("unlink", path); goto done; } } a->npurged++; a->size_purged += sb.st_size; err = report_cleanup_progress(a->progress_cb, a->progress_arg, a->rl, a->ncommits, a->nloose, a->npurged, -1); if (err) goto done; } done: if (fd != -1 && close(fd) == -1 && err == NULL) err = got_error_from_errno("close"); free(path); if (lf) unlock_err = got_lockfile_unlock(lf, -1); return err ? err : unlock_err; } static const struct got_error * repo_purge_unreferenced_loose_objects(struct got_repository *repo, struct got_object_idset *traversed_ids, off_t *size_before, off_t *size_after, int ncommits, int *nloose, int *npacked, int *npurged, int dry_run, int ignore_mtime, time_t max_mtime, struct got_ratelimit *rl, got_cleanup_progress_cb progress_cb, void *progress_arg, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err; struct got_object_idset *loose_ids; struct purge_loose_object_arg arg; err = get_loose_object_ids(&loose_ids, size_before, ncommits, progress_cb, progress_arg, rl, repo); if (err) return err; *nloose = got_object_idset_num_elements(loose_ids); if (*nloose == 0) { got_object_idset_free(loose_ids); if (progress_cb) { err = progress_cb(progress_arg, 0, 0, 0, -1); if (err) return err; } return NULL; } memset(&arg, 0, sizeof(arg)); arg.repo = repo; arg.progress_arg = progress_arg; arg.progress_cb = progress_cb; arg.rl = rl; arg.traversed_ids = traversed_ids; arg.nloose = *nloose; arg.npacked = 0; arg.npurged = 0; arg.size_purged = 0; arg.dry_run = dry_run; arg.max_mtime = max_mtime; arg.ignore_mtime = ignore_mtime; err = got_object_idset_for_each(loose_ids, purge_loose_object, &arg); if (err) goto done; *size_after = *size_before - arg.size_purged; *npacked = arg.npacked; *npurged = arg.npurged; /* Produce a final progress report. */ if (progress_cb) { err = progress_cb(progress_arg, ncommits, *nloose, arg.npurged, -1); if (err) goto done; } done: got_object_idset_free(loose_ids); return err; } static const struct got_error * purge_redundant_pack(struct got_repository *repo, const char *packidx_path, int dry_run, int ignore_mtime, time_t max_mtime, int *remove, off_t *size_before, off_t *size_after) { static const char *ext[] = {".idx", ".pack", ".rev", ".bitmap", ".promisor", ".mtimes"}; struct stat sb; char *dot, path[PATH_MAX]; size_t i; if (strlcpy(path, packidx_path, sizeof(path)) >= sizeof(path)) return got_error(GOT_ERR_NO_SPACE); /* * Do not delete pack files which are younger than our maximum * modification time threshold. This prevents a race where a * new pack file which is being added to the repository * concurrently would be deleted. */ if (fstatat(got_repo_get_fd(repo), path, &sb, 0) == -1) { if (errno == ENOENT) return NULL; return got_error_from_errno2("fstatat", path); } if (!ignore_mtime && sb.st_mtime > max_mtime) *remove = 0; /* * For compatibility with Git, if a matching .keep file exist * don't delete the packfile. */ dot = strrchr(path, '.'); *dot = '\0'; if (strlcat(path, ".keep", sizeof(path)) >= sizeof(path)) return got_error(GOT_ERR_NO_SPACE); if (faccessat(got_repo_get_fd(repo), path, F_OK, 0) == 0) *remove = 0; for (i = 0; i < nitems(ext); ++i) { *dot = '\0'; if (strlcat(path, ext[i], sizeof(path)) >= sizeof(path)) return got_error(GOT_ERR_NO_SPACE); if (fstatat(got_repo_get_fd(repo), path, &sb, 0) == -1) { if (errno == ENOENT) continue; return got_error_from_errno2("fstatat", path); } *size_before += sb.st_size; if (!*remove) { *size_after += sb.st_size; continue; } if (dry_run) continue; if (unlinkat(got_repo_get_fd(repo), path, 0) == -1) { if (errno == ENOENT) continue; return got_error_from_errno2("unlinkat", path); } } return NULL; } static const struct got_error * pack_is_redundant(int *redundant, struct got_repository *repo, struct got_object_idset *traversed_ids, const char *packidx_path, struct got_object_idset *idset) { const struct got_error *err; struct got_packidx *packidx; uint8_t *pid; struct got_object_id id; size_t i, nobjects; size_t digest_len = got_hash_digest_length(repo->algo); *redundant = 1; err = got_repo_get_packidx(&packidx, packidx_path, repo); if (err) return err; nobjects = be32toh(packidx->hdr.fanout_table[0xff]); for (i = 0; i < nobjects; ++i) { pid = packidx->hdr.sorted_ids + i * digest_len; memset(&id, 0, sizeof(id)); memcpy(&id.hash, pid, digest_len); id.algo = repo->algo; if (got_object_idset_contains(idset, &id)) continue; if (!got_object_idset_contains(traversed_ids, &id)) continue; *redundant = 0; err = got_object_idset_add(idset, &id, NULL); if (err) return err; } return NULL; } struct pack_info { const char *path; size_t nobjects; }; static int pack_info_cmp(const void *a, const void *b) { const struct pack_info *pa, *pb; pa = a; pb = b; if (pa->nobjects == pb->nobjects) return strcmp(pa->path, pb->path); if (pa->nobjects > pb->nobjects) return -1; return 1; } static const struct got_error * repo_purge_redundant_packfiles(struct got_repository *repo, struct got_object_idset *traversed_ids, off_t *size_before, off_t *size_after, int dry_run, int ignore_mtime, time_t max_mtime, int nloose, int ncommits, int npurged, struct got_ratelimit *rl, got_cleanup_progress_cb progress_cb, void *progress_arg, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err; struct pack_info *pinfo, *sorted = NULL; struct got_packidx *packidx; struct got_object_idset *idset = NULL; struct got_pathlist_entry *pe; size_t i, npacks; int remove, redundant_packs = 0; npacks = 0; RB_FOREACH(pe, got_pathlist_head, &repo->packidx_paths) npacks++; if (npacks == 0) return NULL; sorted = calloc(npacks, sizeof(*sorted)); if (sorted == NULL) return got_error_from_errno("calloc"); i = 0; RB_FOREACH(pe, got_pathlist_head, &repo->packidx_paths) { err = got_repo_get_packidx(&packidx, pe->path, repo); if (err) goto done; pinfo = &sorted[i++]; pinfo->path = pe->path; pinfo->nobjects = be32toh(packidx->hdr.fanout_table[0xff]); } qsort(sorted, npacks, sizeof(*sorted), pack_info_cmp); idset = got_object_idset_alloc(); if (idset == NULL) { err = got_error_from_errno("got_object_idset_alloc"); goto done; } for (i = 0; i < npacks; ++i) { if (cancel_cb) { err = (*cancel_cb)(cancel_arg); if (err) break; } err = pack_is_redundant(&remove, repo, traversed_ids, sorted[i].path, idset); if (err) goto done; err = purge_redundant_pack(repo, sorted[i].path, dry_run, ignore_mtime, max_mtime, &remove, size_before, size_after); if (err) goto done; if (!remove) continue; err = report_cleanup_progress(progress_cb, progress_arg, rl, ncommits, nloose, npurged, ++redundant_packs); if (err) goto done; } /* Produce a final progress report. */ if (progress_cb) { err = progress_cb(progress_arg, ncommits, nloose, npurged, redundant_packs); if (err) goto done; } done: free(sorted); if (idset) got_object_idset_free(idset); return err; } const struct got_error * got_repo_cleanup(struct got_repository *repo, off_t *loose_before, off_t *loose_after, off_t *pack_before, off_t *pack_after, int *ncommits, int *nloose, int *npacked, int dry_run, int ignore_mtime, got_cleanup_progress_cb progress_cb, void *progress_arg, got_pack_progress_cb pack_progress_cb, void *pack_progress_arg, got_pack_index_progress_cb index_progress_cb, void *index_progress_arg, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *unlock_err, *err = NULL; struct got_lockfile *lk = NULL; struct got_ratelimit rl; struct got_reflist_head refs; struct got_object_idset *traversed_ids = NULL; struct got_reflist_entry *re; struct got_object_id **referenced_ids; int i, nreferenced; int npurged = 0, packfd = -1; char *tmpfile_path = NULL, *packfile_path = NULL, *idxpath = NULL; FILE *delta_cache = NULL, *packfile = NULL; struct got_object_id pack_hash; time_t max_mtime = 0; TAILQ_INIT(&refs); got_ratelimit_init(&rl, 0, 500); memset(&pack_hash, 0, sizeof(pack_hash)); *loose_before = 0; *loose_after = 0; *pack_before = 0; *pack_after = 0; *ncommits = 0; *nloose = 0; *npacked = 0; err = repo_cleanup_lock(repo, &lk); if (err) return err; err = create_temp_packfile(&packfd, &tmpfile_path, repo); if (err) goto done; delta_cache = got_opentemp(); if (delta_cache == NULL) { err = got_error_from_errno("got_opentemp"); goto done; } traversed_ids = got_object_idset_alloc(); if (traversed_ids == NULL) { err = got_error_from_errno("got_object_idset_alloc"); goto done; } err = got_ref_list(&refs, repo, "", got_ref_cmp_by_name, NULL); if (err) goto done; if (!ignore_mtime) { TAILQ_FOREACH(re, &refs, entry) { time_t mtime = got_ref_get_mtime(re->ref); if (mtime > max_mtime) max_mtime = mtime; } /* * For safety, keep objects created within 10 minutes * before the youngest reference was created. */ if (max_mtime >= 600) max_mtime -= 600; } err = get_reflist_object_ids(&referenced_ids, &nreferenced, GOT_OBJ_TYPE_ANY, &refs, repo, cancel_cb, cancel_arg); if (err) goto done; for (i = 0; i < nreferenced; i++) { struct got_object_id *id = referenced_ids[i]; err = load_commit_or_tag(ncommits, traversed_ids, id, repo, progress_cb, progress_arg, &rl, cancel_cb, cancel_arg); if (err) goto done; } err = got_pack_create(&pack_hash, packfd, delta_cache, NULL, 0, referenced_ids, nreferenced, repo, 0, 0, 0, pack_progress_cb, pack_progress_arg, &rl, cancel_cb, cancel_arg); if (err) goto done; err = install_packfile(&packfile, &packfd, &packfile_path, &tmpfile_path, &pack_hash, repo); if (err) goto done; err = got_repo_index_pack(&idxpath, packfile, &pack_hash, repo, index_progress_cb, index_progress_arg, cancel_cb, cancel_arg); if (err) goto done; err = got_repo_list_packidx(&repo->packidx_paths, repo); if (err) goto done; err = repo_purge_unreferenced_loose_objects(repo, traversed_ids, loose_before, loose_after, *ncommits, nloose, npacked, &npurged, dry_run, ignore_mtime, max_mtime, &rl, progress_cb, progress_arg, cancel_cb, cancel_arg); if (err) goto done; err = repo_purge_redundant_packfiles(repo, traversed_ids, pack_before, pack_after, dry_run, ignore_mtime, max_mtime, *nloose, *ncommits, npurged, &rl, progress_cb, progress_arg, cancel_cb, cancel_arg); if (err) goto done; if (dry_run) { if (idxpath && unlink(idxpath) == -1) err = got_error_from_errno2("unlink", idxpath); if (packfile_path && unlink(packfile_path) == -1 && err == NULL) err = got_error_from_errno2("unlink", packfile_path); } done: if (lk) { unlock_err = got_lockfile_unlock(lk, got_repo_get_fd(repo)); if (err == NULL) err = unlock_err; } if (traversed_ids) got_object_idset_free(traversed_ids); if (packfd != -1 && close(packfd) == -1 && err == NULL) err = got_error_from_errno2("close", packfile_path ? packfile_path : tmpfile_path); if (delta_cache && fclose(delta_cache) == EOF && err == NULL) err = got_error_from_errno("fclose"); if (tmpfile_path && unlink(tmpfile_path) == -1 && err == NULL) err = got_error_from_errno2("unlink", tmpfile_path); free(tmpfile_path); free(packfile_path); free(idxpath); return err; } const struct got_error * got_repo_remove_lonely_packidx(struct got_repository *repo, int dry_run, got_lonely_packidx_progress_cb progress_cb, void *progress_arg, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; DIR *packdir = NULL; struct dirent *dent; char *pack_relpath = NULL; int packdir_fd; struct stat sb; packdir_fd = openat(got_repo_get_fd(repo), GOT_OBJECTS_PACK_DIR, O_DIRECTORY | O_CLOEXEC); if (packdir_fd == -1) { if (errno == ENOENT) return NULL; return got_error_from_errno_fmt("openat: %s/%s", got_repo_get_path_git_dir(repo), GOT_OBJECTS_PACK_DIR); } packdir = fdopendir(packdir_fd); if (packdir == NULL) { err = got_error_from_errno("fdopendir"); close(packdir_fd); goto done; } while ((dent = readdir(packdir)) != NULL) { if (cancel_cb) { err = cancel_cb(cancel_arg); if (err) goto done; } if (!got_repo_is_packidx_filename(dent->d_name, strlen(dent->d_name), got_repo_get_object_format(repo))) continue; err = got_packidx_get_packfile_path(&pack_relpath, dent->d_name); if (err) goto done; if (fstatat(packdir_fd, pack_relpath, &sb, 0) != -1) { free(pack_relpath); pack_relpath = NULL; continue; } if (errno != ENOENT) { err = got_error_from_errno_fmt("fstatat: %s/%s/%s", got_repo_get_path_git_dir(repo), GOT_OBJECTS_PACK_DIR, pack_relpath); goto done; } if (!dry_run) { if (unlinkat(packdir_fd, dent->d_name, 0) == -1) { err = got_error_from_errno("unlinkat"); goto done; } } if (progress_cb) { char *path; if (asprintf(&path, "%s/%s/%s", got_repo_get_path_git_dir(repo), GOT_OBJECTS_PACK_DIR, dent->d_name) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = progress_cb(progress_arg, path); free(path); if (err) goto done; } free(pack_relpath); pack_relpath = NULL; } done: if (packdir && closedir(packdir) != 0 && err == NULL) err = got_error_from_errno("closedir"); free(pack_relpath); return err; } got-portable-0.111/lib/load.c0000644000175000017500000003413515001741021011422 /* * Copyright (c) 2023 Omar Polo * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_cancel.h" #include "got_object.h" #include "got_opentemp.h" #include "got_path.h" #include "got_reference.h" #include "got_repository.h" #include "got_repository_load.h" #include "got_lib_delta.h" #include "got_lib_hash.h" #include "got_lib_object.h" #include "got_lib_object_cache.h" #include "got_lib_pack.h" #include "got_lib_ratelimit.h" #include "got_lib_repository.h" #include "got_lib_privsep.h" #define GIT_BUNDLE_SIGNATURE_V2 "# v2 git bundle\n" #define GIT_BUNDLE_SIGNATURE_V3 "# v3 git bundle\n" #ifndef nitems #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) #endif #ifndef ssizeof #define ssizeof(_x) ((ssize_t)(sizeof(_x))) #endif static const struct got_error * temp_file(int *fd, char **path, const char *ext, struct got_repository *repo) { const struct got_error *err; char p[PATH_MAX]; int r; *path = NULL; r = snprintf(p, sizeof(p), "%s/%s/loading", got_repo_get_path_git_dir(repo), GOT_OBJECTS_PACK_DIR); if (r < 0 || (size_t)r >= sizeof(p)) return got_error_from_errno("snprintf"); err = got_opentemp_named_fd(path, fd, p, ext); if (err) return err; if (fchmod(*fd, GOT_DEFAULT_FILE_MODE) == -1) return got_error_from_errno("fchmod"); return NULL; } static const struct got_error * load_report_progress(got_load_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, off_t packsiz, int nobj_total, int nobj_indexed, int nobj_loose, int nobj_resolved) { const struct got_error *err; int elapsed; if (progress_cb == NULL) return NULL; err = got_ratelimit_check(&elapsed, rl); if (err || !elapsed) return err; return progress_cb(progress_arg, packsiz, nobj_total, nobj_indexed, nobj_loose, nobj_resolved); } static const struct got_error * copypack(FILE *in, int outfd, off_t *tot, struct got_object_id *id, enum got_hash_algorithm algo, struct got_ratelimit *rl, got_load_progress_cb progress_cb, void *progress_arg, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err; struct got_hash hash; struct got_object_id expected_id; char buf[BUFSIZ], hashbuf[GOT_HASH_DIGEST_MAXLEN]; size_t r, digest_len, hashlen = 0; *tot = 0; digest_len = got_hash_digest_length(algo); got_hash_init(&hash, algo); for (;;) { err = cancel_cb(cancel_arg); if (err) return err; r = fread(buf, 1, sizeof(buf), in); if (r == 0) break; /* * An expected a checksum sits at the end of the pack * file. Since we don't know the file size ahead of * time we have to keep digest_len bytes buffered and * avoid mixing those bytes int our hash computation * until we know for sure that additional pack file * data bytes follow. * * We can assume that BUFSIZE is greater than * digest_len and that a short read means that we've * reached EOF. */ if (r >= digest_len) { *tot += hashlen; got_hash_update(&hash, hashbuf, hashlen); if (write(outfd, hashbuf, hashlen) == -1) return got_error_from_errno("write"); r -= digest_len; memcpy(hashbuf, &buf[r], digest_len); hashlen = digest_len; *tot += r; got_hash_update(&hash, buf, r); if (write(outfd, buf, r) == -1) return got_error_from_errno("write"); err = load_report_progress(progress_cb, progress_arg, rl, *tot, 0, 0, 0, 0); if (err) return err; continue; } if (hashlen == 0) return got_error(GOT_ERR_BAD_PACKFILE); /* short read, we've reached EOF */ *tot += r; got_hash_update(&hash, hashbuf, r); if (write(outfd, hashbuf, r) == -1) return got_error_from_errno("write"); memmove(&hashbuf[0], &hashbuf[r], digest_len - r); memcpy(&hashbuf[digest_len - r], buf, r); break; } if (hashlen == 0) return got_error(GOT_ERR_BAD_PACKFILE); got_hash_final_object_id(&hash, id); memset(&expected_id, 0, sizeof(expected_id)); expected_id.algo = algo; memcpy(&expected_id.hash, hashbuf, digest_len); if (got_object_id_cmp(id, &expected_id) != 0) return got_error(GOT_ERR_PACKIDX_CSUM); /* re-add the expected hash at the end of the pack */ if (write(outfd, hashbuf, digest_len) == -1) return got_error_from_errno("write"); *tot += digest_len; err = progress_cb(progress_arg, *tot, 0, 0, 0, 0); if (err) return err; return NULL; } const struct got_error * got_repo_load(FILE *in, struct got_pathlist_head *refs_found, struct got_repository *repo, int list_refs_only, int noop, got_load_progress_cb progress_cb, void *progress_arg, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; struct got_object_id id; struct got_object *obj; struct got_packfile_hdr pack_hdr; struct got_ratelimit rl; struct imsgbuf idxibuf; const char *repo_path; char *packpath = NULL, *idxpath = NULL; char *tmppackpath = NULL, *tmpidxpath = NULL; int packfd = -1, idxfd = -1; char *spc, *refname, *id_str = NULL; char *line = NULL; size_t linesize = 0; ssize_t linelen; size_t i, digest_len; ssize_t n; off_t packsiz; int tmpfds[3] = {-1, -1, -1}; int imsg_idxfds[2] = {-1, -1}; int ch, done, nobj, idxstatus; pid_t idxpid; enum got_hash_algorithm repo_algo, bundle_algo; memset(&idxibuf, 0, sizeof(idxibuf)); got_ratelimit_init(&rl, 0, 500); repo_algo = got_repo_get_object_format(repo); digest_len = got_hash_digest_length(repo_algo); repo_path = got_repo_get_path_git_dir(repo); /* bundles will use v3 and a capability to advertise sha256 */ bundle_algo = GOT_HASH_SHA1; linelen = getline(&line, &linesize, in); if (linelen == -1) { err = got_ferror(in, GOT_ERR_IO); goto done; } if (strcmp(line, GIT_BUNDLE_SIGNATURE_V2) != 0 && strcmp(line, GIT_BUNDLE_SIGNATURE_V3) != 0) { err = got_error(GOT_ERR_BUNDLE_FORMAT); goto done; } /* Parse the capabilities */ for (;;) { char *key, *val; ch = fgetc(in); if (ch != '@') { if (ch != EOF) ungetc(ch, in); break; } linelen = getline(&line, &linesize, in); if (linelen == -1) { err = got_ferror(in, GOT_ERR_IO); goto done; } if (line[linelen - 1] == '\n') line[linelen - 1] = '\0'; key = line; val = strchr(key, '='); if (val == NULL) { err = got_error_path(key, GOT_ERR_UNKNOWN_CAPA); goto done; } *val++ = '\0'; if (!strcmp(key, "object-format")) { if (!strcmp(val, "sha1")) { bundle_algo = GOT_HASH_SHA1; continue; } if (!strcmp(val, "sha256")) { bundle_algo = GOT_HASH_SHA256; continue; } } err = got_error_path(key, GOT_ERR_UNKNOWN_CAPA); goto done; } if (bundle_algo != repo_algo) { err = got_error(GOT_ERR_OBJECT_FORMAT); goto done; } /* Parse the prerequisite */ for (;;) { ch = fgetc(in); if (ch != '-') { if (ch != EOF) ungetc(ch, in); break; } linelen = getline(&line, &linesize, in); if (linelen == -1) { err = got_ferror(in, GOT_ERR_IO); goto done; } if (line[linelen - 1] == '\n') line[linelen - 1] = '\0'; if (!got_parse_object_id(&id, line, repo_algo)) { err = got_error_path(line, GOT_ERR_BAD_OBJ_ID_STR); goto done; } err = got_object_open(&obj, repo, &id); if (err) goto done; got_object_close(obj); } /* Read references */ for (;;) { struct got_object_id *id; char *dup; struct got_pathlist_entry *new; linelen = getline(&line, &linesize, in); if (linelen == -1) { err = got_ferror(in, GOT_ERR_IO); goto done; } if (line[linelen - 1] == '\n') line[linelen - 1] = '\0'; if (*line == '\0') break; spc = strchr(line, ' '); if (spc == NULL) { err = got_error(GOT_ERR_IO); goto done; } *spc = '\0'; refname = spc + 1; if (!got_ref_name_is_valid(refname)) { err = got_error(GOT_ERR_BAD_REF_DATA); goto done; } id = malloc(sizeof(*id)); if (id == NULL) { err = got_error_from_errno("malloc"); goto done; } if (!got_parse_object_id(id, line, repo_algo)) { free(id); err = got_error(GOT_ERR_BAD_OBJ_ID_STR); goto done; } dup = strdup(refname); if (dup == NULL) { free(id); err = got_error_from_errno("strdup"); goto done; } err = got_pathlist_insert(&new, refs_found, dup, id); if (err || new == NULL) { free(id); free(dup); if (err) goto done; } } if (list_refs_only) goto done; err = temp_file(&packfd, &tmppackpath, ".pack", repo); if (err) goto done; err = temp_file(&idxfd, &tmpidxpath, ".idx", repo); if (err) goto done; err = copypack(in, packfd, &packsiz, &id, repo_algo, &rl, progress_cb, progress_arg, cancel_cb, cancel_arg); if (err) goto done; if (lseek(packfd, 0, SEEK_SET) == -1) { err = got_error_from_errno("lseek"); goto done; } /* Safety checks on the pack' content. */ if (packsiz <= ssizeof(pack_hdr) + digest_len) { err = got_error_msg(GOT_ERR_BAD_PACKFILE, "short pack file"); goto done; } n = read(packfd, &pack_hdr, ssizeof(pack_hdr)); if (n == -1) { err = got_error_from_errno("read"); goto done; } if (n != ssizeof(pack_hdr)) { err = got_error(GOT_ERR_IO); goto done; } if (pack_hdr.signature != htobe32(GOT_PACKFILE_SIGNATURE)) { err = got_error_msg(GOT_ERR_BAD_PACKFILE, "bad pack file signature"); goto done; } if (pack_hdr.version != htobe32(GOT_PACKFILE_VERSION)) { err = got_error_msg(GOT_ERR_BAD_PACKFILE, "bad pack file version"); goto done; } nobj = be32toh(pack_hdr.nobjects); if (nobj == 0 && packsiz > ssizeof(pack_hdr) + digest_len) { err = got_error_msg(GOT_ERR_BAD_PACKFILE, "bad pack file with zero objects"); goto done; } if (nobj != 0 && packsiz <= ssizeof(pack_hdr) + digest_len) { err = got_error_msg(GOT_ERR_BAD_PACKFILE, "empty pack file with non-zero object count"); goto done; } /* nothing to do if there are no objects. */ if (nobj == 0) goto done; for (i = 0; i < nitems(tmpfds); i++) { tmpfds[i] = got_opentempfd(); if (tmpfds[i] == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } } if (lseek(packfd, 0, SEEK_SET) == -1) { err = got_error_from_errno("lseek"); goto done; } if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_idxfds) == -1) { err = got_error_from_errno("socketpair"); goto done; } idxpid = fork(); if (idxpid == -1) { err= got_error_from_errno("fork"); goto done; } else if (idxpid == 0) got_privsep_exec_child(imsg_idxfds, GOT_PATH_PROG_INDEX_PACK, tmppackpath); if (close(imsg_idxfds[1]) == -1) { err = got_error_from_errno("close"); goto done; } imsg_idxfds[1] = -1; if (imsgbuf_init(&idxibuf, imsg_idxfds[0]) == -1) { err = got_error_from_errno("imsgbuf_init"); goto done; } imsgbuf_allow_fdpass(&idxibuf); err = got_privsep_send_index_pack_req(&idxibuf, &id, packfd); if (err) goto done; packfd = -1; err = got_privsep_send_index_pack_outfd(&idxibuf, idxfd); if (err) goto done; idxfd = -1; for (i = 0; i < nitems(tmpfds); i++) { err = got_privsep_send_tmpfd(&idxibuf, tmpfds[i]); if (err != NULL) goto done; tmpfds[i] = -1; } done = 0; while (!done) { int nobj_total, nobj_indexed, nobj_loose, nobj_resolved; err = got_privsep_recv_index_progress(&done, &nobj_total, &nobj_indexed, &nobj_loose, &nobj_resolved, &idxibuf); if (err) goto done; if (nobj_indexed != 0) { err = load_report_progress(progress_cb, progress_arg, &rl, packsiz, nobj_total, nobj_indexed, nobj_loose, nobj_resolved); if (err) goto done; } } if (close(imsg_idxfds[0]) == -1) { err = got_error_from_errno("close"); goto done; } imsg_idxfds[0] = -1; if (waitpid(idxpid, &idxstatus, 0) == -1) { err = got_error_from_errno("waitpid"); goto done; } if (noop) goto done; err = got_object_id_str(&id_str, &id); if (err) goto done; if (asprintf(&packpath, "%s/%s/pack-%s.pack", repo_path, GOT_OBJECTS_PACK_DIR, id_str) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (asprintf(&idxpath, "%s/%s/pack-%s.idx", repo_path, GOT_OBJECTS_PACK_DIR, id_str) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (rename(tmppackpath, packpath) == -1) { err = got_error_from_errno3("rename", tmppackpath, packpath); goto done; } free(tmppackpath); tmppackpath = NULL; if (rename(tmpidxpath, idxpath) == -1) { err = got_error_from_errno3("rename", tmpidxpath, idxpath); goto done; } free(tmpidxpath); tmpidxpath = NULL; done: if (idxibuf.w) imsgbuf_clear(&idxibuf); free(line); free(packpath); free(idxpath); free(id_str); if (tmppackpath && unlink(tmppackpath) == -1 && err == NULL) err = got_error_from_errno2("unlink", tmppackpath); if (packfd != -1 && close(packfd) == -1 && err == NULL) err = got_error_from_errno("close"); free(tmppackpath); if (tmpidxpath && unlink(tmpidxpath) == -1 && err == NULL) err = got_error_from_errno2("unlink", tmpidxpath); if (idxfd != -1 && close(idxfd) == -1 && err == NULL) err = got_error_from_errno("close"); free(tmpidxpath); if (imsg_idxfds[0] != -1 && close(imsg_idxfds[0]) == -1 && err == NULL) err = got_error_from_errno("close"); if (imsg_idxfds[1] != -1 && close(imsg_idxfds[1]) == -1 && err == NULL) err = got_error_from_errno("close"); for (i = 0; i < nitems(tmpfds); ++i) if (tmpfds[i] != -1 && close(tmpfds[i]) == -1 && err == NULL) err = got_error_from_errno("close"); return err; } got-portable-0.111/lib/got_lib_poll.h0000644000175000017500000000227215001740614013161 /* * Copyright (c) 2022 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ const struct got_error *got_poll_fd(int fd, int events, int timeout); const struct got_error *got_poll_read_full_timeout(int, size_t *, void *, size_t, size_t, int); const struct got_error *got_poll_read_full(int, size_t *, void *, size_t, size_t); const struct got_error *got_poll_write_full(int, const void *, off_t); const struct got_error *got_poll_write_full_timeout(int, const void *, off_t, int); got-portable-0.111/lib/got_lib_object_parse.h0000644000175000017500000000500215001740614014645 /* * Copyright (c) 2018, 2019, 2020 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ struct got_pathlist_head; const struct got_error *got_object_type_label(const char **, int); struct got_commit_object *got_object_commit_alloc_partial(void); struct got_tree_entry *got_alloc_tree_entry_partial(void); const struct got_error *got_object_parse_commit(struct got_commit_object **, char *, size_t, enum got_hash_algorithm); const struct got_error *got_object_read_commit(struct got_commit_object **, int, struct got_object_id *, size_t); struct got_parsed_tree_entry { const char *name; /* Points to name in parsed buffer */ size_t namelen; /* strlen(name) */ mode_t mode; /* Mode parsed from tree buffer. */ uint8_t *id; /* Points to ID in parsed tree buffer. */ size_t digest_len; int algo; }; const struct got_error *got_object_parse_tree_entry( struct got_parsed_tree_entry *, size_t *, char *, size_t, size_t, enum got_hash_algorithm); const struct got_error *got_object_parse_tree(struct got_parsed_tree_entry **, size_t *, size_t *, uint8_t *, size_t, enum got_hash_algorithm); const struct got_error *got_object_read_tree(struct got_parsed_tree_entry **, size_t *, size_t *, uint8_t **, int, struct got_object_id *); const struct got_error *got_object_parse_tag(struct got_tag_object **, uint8_t *, size_t, enum got_hash_algorithm); const struct got_error *got_object_read_tag(struct got_tag_object **, int, struct got_object_id *, size_t); struct got_pack; struct got_packidx; struct got_inflate_checksum; const struct got_error *got_object_parse_header(struct got_object **, char *, size_t); const struct got_error *got_object_read_header(struct got_object **, int); const struct got_error *got_object_read_raw(uint8_t **, off_t *, size_t *, size_t, int, struct got_object_id *, int); got-portable-0.111/lib/commit_graph.c0000644000175000017500000006235415001741021013160 /* * Copyright (c) 2018, 2019, 2020 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "got_compat.h" #include "got_error.h" #include "got_object.h" #include "got_cancel.h" #include "got_commit_graph.h" #include "got_path.h" #include "got_lib_delta.h" #include "got_lib_inflate.h" #include "got_lib_object.h" #include "got_lib_object_idset.h" #include "got_lib_object_qid.h" #ifndef nitems #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) #endif struct got_commit_graph_node { struct got_object_id id; /* Used for topological sorting. */ struct got_commit_graph_node *parents[2]; struct got_commit_graph_node **more_parents; int nparents; int indegree; /* Used only during iteration. */ time_t timestamp; TAILQ_ENTRY(got_commit_graph_node) entry; }; TAILQ_HEAD(got_commit_graph_iter_list, got_commit_graph_node); struct got_commit_graph_branch_tip { struct got_object_id *commit_id; struct got_commit_object *commit; struct got_commit_graph_node *new_node; }; struct got_commit_graph { /* The set of all commits we have traversed. */ struct got_object_idset *node_ids; int flags; #define GOT_COMMIT_GRAPH_FIRST_PARENT_TRAVERSAL 0x01 #define GOT_COMMIT_GRAPH_TOPOSORT 0x02 /* * A set of object IDs of known parent commits which we have not yet * traversed. Each commit ID in this set represents a branch in commit * history: Either the first-parent branch of the head node, or another * branch corresponding to a traversed merge commit for which we have * not traversed a branch point commit yet. * * Whenever we add a commit with a matching ID to the graph, we remove * its corresponding element from this set, and add new elements for * each of that commit's parent commits which were not traversed yet. * * When API users ask us to fetch more commits, we fetch commits from * all currently open branches. This allows API users to process * commits in linear order even though the history contains branches. */ struct got_object_idset *open_branches; /* Array of branch tips for fetch_commits_from_open_branches(). */ struct got_commit_graph_branch_tip *tips; int ntips; /* Path of tree entry of interest to the API user. */ char *path; /* * Nodes which will be passed to the API user next, sorted by * commit timestamp. Sorted in topological order only if topological * sorting was requested. */ struct got_commit_graph_iter_list iter_list; }; static const struct got_error * detect_changed_path(int *changed, struct got_commit_object *commit, struct got_object_id *commit_id, const char *path, struct got_repository *repo) { const struct got_error *err = NULL; struct got_commit_object *pcommit = NULL; struct got_tree_object *tree = NULL, *ptree = NULL; struct got_object_qid *pid; if (got_path_is_root_dir(path)) { *changed = 1; return NULL; } *changed = 0; pid = STAILQ_FIRST(&commit->parent_ids); if (pid == NULL) { struct got_object_id *obj_id; err = got_object_id_by_path(&obj_id, repo, commit, path); if (err) { if (err->code == GOT_ERR_NO_TREE_ENTRY) err = NULL; } else *changed = 1; /* The path was created in this commit. */ free(obj_id); return err; } err = got_object_open_as_tree(&tree, repo, commit->tree_id); if (err) return err; err = got_object_open_as_commit(&pcommit, repo, &pid->id); if (err) goto done; err = got_object_open_as_tree(&ptree, repo, pcommit->tree_id); if (err) goto done; err = got_object_tree_path_changed(changed, tree, ptree, path, repo); done: if (tree) got_object_tree_close(tree); if (ptree) got_object_tree_close(ptree); if (pcommit) got_object_commit_close(pcommit); return err; } static void add_node_to_iter_list(struct got_commit_graph *graph, struct got_commit_graph_node *node, time_t committer_time) { struct got_commit_graph_node *n, *next; node->timestamp = committer_time; n = TAILQ_FIRST(&graph->iter_list); while (n) { next = TAILQ_NEXT(n, entry); if (next && node->timestamp >= next->timestamp) { TAILQ_INSERT_BEFORE(next, node, entry); return; } n = next; } TAILQ_INSERT_TAIL(&graph->iter_list, node, entry); } static const struct got_error * add_node(struct got_commit_graph_node **new_node, struct got_commit_graph *graph, struct got_object_id *commit_id, struct got_repository *repo) { const struct got_error *err = NULL; struct got_commit_graph_node *node; *new_node = NULL; node = calloc(1, sizeof(*node)); if (node == NULL) return got_error_from_errno("calloc"); memcpy(&node->id, commit_id, sizeof(node->id)); node->nparents = -1; err = got_object_idset_add(graph->node_ids, &node->id, node); if (err) free(node); else *new_node = node; return err; } /* * Ask got-read-pack to traverse first-parent history until a commit is * encountered which modified graph->path, or until the pack file runs * out of relevant commits. This is faster than sending an individual * request for each commit stored in the pack file. */ static const struct got_error * packed_first_parent_traversal(int *ncommits_traversed, struct got_commit_graph *graph, struct got_object_id *commit_id, struct got_repository *repo) { const struct got_error *err = NULL; struct got_object_id_queue traversed_commits; struct got_object_qid *qid; STAILQ_INIT(&traversed_commits); *ncommits_traversed = 0; err = got_traverse_packed_commits(&traversed_commits, commit_id, graph->path, repo); if (err) return err; /* Add all traversed commits to the graph... */ STAILQ_FOREACH(qid, &traversed_commits, entry) { if (got_object_idset_contains(graph->open_branches, &qid->id)) continue; if (got_object_idset_contains(graph->node_ids, &qid->id)) continue; (*ncommits_traversed)++; /* ... except the last commit is the new branch tip. */ if (STAILQ_NEXT(qid, entry) == NULL) { err = got_object_idset_add(graph->open_branches, &qid->id, NULL); break; } err = got_object_idset_add(graph->node_ids, &qid->id, NULL); if (err) break; } got_object_id_queue_free(&traversed_commits); return err; } static const struct got_error * close_branch(struct got_commit_graph *graph, struct got_object_id *commit_id) { const struct got_error *err; err = got_object_idset_remove(NULL, graph->open_branches, commit_id); if (err && err->code != GOT_ERR_NO_OBJ) return err; return NULL; } static const struct got_error * advance_branch(struct got_commit_graph *graph, struct got_object_id *commit_id, struct got_commit_object *commit, struct got_repository *repo) { const struct got_error *err; struct got_object_qid *qid; struct got_object_id *merged_id = NULL; err = close_branch(graph, commit_id); if (err) return err; if (graph->flags & GOT_COMMIT_GRAPH_FIRST_PARENT_TRAVERSAL) { qid = STAILQ_FIRST(&commit->parent_ids); if (qid == NULL || got_object_idset_contains(graph->open_branches, &qid->id)) return NULL; /* * The root directory always changes by definition, and when * logging the root we want to traverse consecutive commits * even if they point at the same tree. * But if we are looking for a specific path then we can avoid * fetching packed commits which did not modify the path and * only fetch their IDs. This speeds up 'got blame'. */ if (!got_path_is_root_dir(graph->path) && (commit->flags & GOT_COMMIT_FLAG_PACKED)) { int ncommits = 0; err = packed_first_parent_traversal(&ncommits, graph, &qid->id, repo); if (err || ncommits > 0) return err; } return got_object_idset_add(graph->open_branches, &qid->id, NULL); } /* * If we are graphing commits for a specific path, skip branches * which do not contribute any content to this path. */ if (commit->nparents > 1 && !got_path_is_root_dir(graph->path)) { err = got_object_id_by_path(&merged_id, repo, commit, graph->path); if (err && err->code != GOT_ERR_NO_TREE_ENTRY) return err; /* The requested path does not exist in this merge commit. */ } if (commit->nparents > 1 && !got_path_is_root_dir(graph->path) && merged_id != NULL) { struct got_object_id *prev_id = NULL; int branches_differ = 0; STAILQ_FOREACH(qid, &commit->parent_ids, entry) { struct got_object_id *id = NULL; struct got_commit_object *pcommit = NULL; if (got_object_idset_contains(graph->open_branches, &qid->id)) continue; err = got_object_open_as_commit(&pcommit, repo, &qid->id); if (err) { free(merged_id); free(prev_id); return err; } err = got_object_id_by_path(&id, repo, pcommit, graph->path); got_object_commit_close(pcommit); pcommit = NULL; if (err) { if (err->code == GOT_ERR_NO_TREE_ENTRY) { branches_differ = 1; continue; } free(merged_id); free(prev_id); return err; } if (prev_id) { if (!branches_differ && got_object_id_cmp(id, prev_id) != 0) branches_differ = 1; free(prev_id); } prev_id = id; /* * If a branch has created the merged content we can * skip any other branches. */ if (got_object_id_cmp(merged_id, id) == 0) { err = got_object_idset_add(graph->open_branches, &qid->id, NULL); free(merged_id); free(id); return err; } } free(prev_id); prev_id = NULL; free(merged_id); merged_id = NULL; /* * If the path's content is the same on all branches, * follow the first parent only. */ if (!branches_differ) { qid = STAILQ_FIRST(&commit->parent_ids); if (qid == NULL) return NULL; if (got_object_idset_contains(graph->open_branches, &qid->id)) return NULL; if (got_object_idset_contains(graph->node_ids, &qid->id)) return NULL; /* parent already traversed */ return got_object_idset_add(graph->open_branches, &qid->id, NULL); } } STAILQ_FOREACH(qid, &commit->parent_ids, entry) { if (got_object_idset_contains(graph->open_branches, &qid->id)) continue; if (got_object_idset_contains(graph->node_ids, &qid->id)) continue; /* parent already traversed */ err = got_object_idset_add(graph->open_branches, &qid->id, NULL); if (err) return err; } return NULL; } const struct got_error * got_commit_graph_open(struct got_commit_graph **graph, const char *path, int first_parent_traversal) { const struct got_error *err = NULL; *graph = calloc(1, sizeof(**graph)); if (*graph == NULL) return got_error_from_errno("calloc"); TAILQ_INIT(&(*graph)->iter_list); (*graph)->path = strdup(path); if ((*graph)->path == NULL) { err = got_error_from_errno("strdup"); goto done; } (*graph)->node_ids = got_object_idset_alloc(); if ((*graph)->node_ids == NULL) { err = got_error_from_errno("got_object_idset_alloc"); goto done; } (*graph)->open_branches = got_object_idset_alloc(); if ((*graph)->open_branches == NULL) { err = got_error_from_errno("got_object_idset_alloc"); goto done; } if (first_parent_traversal) (*graph)->flags |= GOT_COMMIT_GRAPH_FIRST_PARENT_TRAVERSAL; done: if (err) { got_commit_graph_close(*graph); *graph = NULL; } return err; } struct add_branch_tip_arg { struct got_commit_graph_branch_tip *tips; int ntips; struct got_repository *repo; struct got_commit_graph *graph; }; static const struct got_error * add_branch_tip(struct got_object_id *commit_id, void *data, void *arg) { const struct got_error *err; struct add_branch_tip_arg *a = arg; struct got_commit_graph_node *new_node; struct got_commit_object *commit; err = got_object_open_as_commit(&commit, a->repo, commit_id); if (err) return err; err = add_node(&new_node, a->graph, commit_id, a->repo); if (err) { got_object_commit_close(commit); return err; } a->tips[a->ntips].commit_id = &new_node->id; a->tips[a->ntips].commit = commit; a->tips[a->ntips].new_node = new_node; a->ntips++; return NULL; } static const struct got_error * fetch_commits_from_open_branches(struct got_commit_graph *graph, struct got_repository *repo, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err; struct add_branch_tip_arg arg; int i, ntips; ntips = got_object_idset_num_elements(graph->open_branches); if (ntips == 0) return NULL; /* (Re-)allocate branch tips array if necessary. */ if (graph->ntips < ntips) { struct got_commit_graph_branch_tip *tips; tips = recallocarray(graph->tips, graph->ntips, ntips, sizeof(*tips)); if (tips == NULL) return got_error_from_errno("recallocarray"); graph->tips = tips; graph->ntips = ntips; } arg.tips = graph->tips; arg.ntips = 0; /* add_branch_tip() will increment */ arg.repo = repo; arg.graph = graph; err = got_object_idset_for_each(graph->open_branches, add_branch_tip, &arg); if (err) goto done; for (i = 0; i < arg.ntips; i++) { struct got_object_id *commit_id; struct got_commit_object *commit; struct got_commit_graph_node *new_node; int changed; if (cancel_cb) { err = (*cancel_cb)(cancel_arg); if (err) break; } commit_id = arg.tips[i].commit_id; commit = arg.tips[i].commit; new_node = arg.tips[i].new_node; err = detect_changed_path(&changed, commit, commit_id, graph->path, repo); if (err) { if (err->code != GOT_ERR_NO_OBJ) break; /* * History of the path stops here on the current * branch. Keep going on other branches. */ err = close_branch(graph, commit_id); if (err) break; continue; } if (changed) { add_node_to_iter_list(graph, new_node, got_object_commit_get_committer_time(commit)); arg.tips[i].new_node = NULL; } err = advance_branch(graph, commit_id, commit, repo); if (err) break; } done: for (i = 0; i < arg.ntips; i++) { got_object_commit_close(arg.tips[i].commit); free(arg.tips[i].new_node); } return err; } void got_commit_graph_close(struct got_commit_graph *graph) { struct got_commit_graph_node *node; while ((node = TAILQ_FIRST(&graph->iter_list))) { TAILQ_REMOVE(&graph->iter_list, node, entry); free(node->more_parents); free(node); } if (graph->open_branches) got_object_idset_free(graph->open_branches); if (graph->node_ids) got_object_idset_free(graph->node_ids); free(graph->tips); free(graph->path); free(graph); } static const struct got_error * remove_branch_tip(struct got_object_id *commit_id, void *data, void *arg) { struct got_object_idset *open_branches = arg; return got_object_idset_remove(NULL, open_branches, commit_id); } const struct got_error * got_commit_graph_bfsort(struct got_commit_graph *graph, struct got_object_id *id, struct got_repository *repo, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; struct got_commit_graph_node *node; graph->flags &= ~GOT_COMMIT_GRAPH_TOPOSORT; /* Clear left-over state from previous iteration attempts. */ while ((node = TAILQ_FIRST(&graph->iter_list))) TAILQ_REMOVE(&graph->iter_list, node, entry); err = got_object_idset_for_each(graph->open_branches, remove_branch_tip, graph->open_branches); if (err) return err; err = got_object_idset_add(graph->open_branches, id, NULL); if (err) return err; /* Locate first commit which changed graph->path. */ while (TAILQ_EMPTY(&graph->iter_list) && got_object_idset_num_elements(graph->open_branches) > 0) { err = fetch_commits_from_open_branches(graph, repo, cancel_cb, cancel_arg); if (err) return err; } if (TAILQ_EMPTY(&graph->iter_list)) { const char *path; if (got_path_is_root_dir(graph->path)) return got_error_no_obj(id); path = graph->path; while (path[0] == '/') path++; return got_error_path(path, GOT_ERR_NO_TREE_ENTRY); } return NULL; } const struct got_error * got_commit_graph_iter_next(struct got_object_id *id, struct got_commit_graph *graph, struct got_repository *repo, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; struct got_commit_graph_node *node, *pnode; int i; node = TAILQ_FIRST(&graph->iter_list); if (node == NULL) { /* We are done iterating, or iteration was not started. */ return got_error(GOT_ERR_ITER_COMPLETED); } if (graph->flags & GOT_COMMIT_GRAPH_TOPOSORT) { /* At least one node with in-degree zero must exist. */ while (node->indegree != 0) node = TAILQ_NEXT(node, entry); } else { while (TAILQ_NEXT(node, entry) == NULL && got_object_idset_num_elements(graph->open_branches) > 0) { err = fetch_commits_from_open_branches(graph, repo, cancel_cb, cancel_arg); if (err) return err; } } memcpy(id, &node->id, sizeof(*id)); TAILQ_REMOVE(&graph->iter_list, node, entry); if (graph->flags & GOT_COMMIT_GRAPH_TOPOSORT) { /* When visiting a commit decrement in-degree of all parents. */ for (i = 0; i < node->nparents; i++) { if (i < nitems(node->parents)) pnode = node->parents[i]; else pnode = node->more_parents[i]; pnode->indegree--; } } free(node); return NULL; } static const struct got_error * find_yca_add_id(struct got_object_id **yca_id, struct got_commit_graph *graph, struct got_object_idset *commit_ids, struct got_repository *repo, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; struct got_object_id id; err = got_commit_graph_iter_next(&id, graph, repo, cancel_cb, cancel_arg); if (err) return err; if (got_object_idset_contains(commit_ids, &id)) { *yca_id = got_object_id_dup(&id); if (*yca_id == NULL) err = got_error_from_errno("got_object_id_dup"); return err; } return got_object_idset_add(commit_ids, &id, NULL); } /* * Sets *yca_id to the youngest common ancestor of commit_id and * commit_id2. Returns got_error(GOT_ERR_ANCESTRY) if they have no * common ancestors. * * If first_parent_traversal is nonzero, only linear history is considered. * If toposort is set then sort commits in topological order before * traversing them. */ const struct got_error * got_commit_graph_find_youngest_common_ancestor(struct got_object_id **yca_id, struct got_object_id *commit_id, struct got_object_id *commit_id2, int first_parent_traversal, int toposort, struct got_repository *repo, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; struct got_commit_graph *graph = NULL, *graph2 = NULL; int completed = 0, completed2 = 0; struct got_object_idset *commit_ids; *yca_id = NULL; commit_ids = got_object_idset_alloc(); if (commit_ids == NULL) return got_error_from_errno("got_object_idset_alloc"); err = got_commit_graph_open(&graph, "/", first_parent_traversal); if (err) goto done; err = got_commit_graph_open(&graph2, "/", first_parent_traversal); if (err) goto done; if (toposort) { err = got_commit_graph_toposort(graph, commit_id, repo, cancel_cb, cancel_arg); if (err) goto done; err = got_commit_graph_toposort(graph2, commit_id2, repo, cancel_cb, cancel_arg); if (err) goto done; } else { err = got_commit_graph_bfsort(graph, commit_id, repo, cancel_cb, cancel_arg); if (err) goto done; err = got_commit_graph_bfsort(graph2, commit_id2, repo, cancel_cb, cancel_arg); if (err) goto done; } for (;;) { if (cancel_cb) { err = (*cancel_cb)(cancel_arg); if (err) break; } if (!completed) { err = find_yca_add_id(yca_id, graph, commit_ids, repo, cancel_cb, cancel_arg); if (err) { if (err->code != GOT_ERR_ITER_COMPLETED) break; err = NULL; completed = 1; } if (*yca_id) break; } if (!completed2) { err = find_yca_add_id(yca_id, graph2, commit_ids, repo, cancel_cb, cancel_arg); if (err) { if (err->code != GOT_ERR_ITER_COMPLETED) break; err = NULL; completed2 = 1; } if (*yca_id) break; } if (completed && completed2) { err = got_error(GOT_ERR_ANCESTRY); break; } } done: got_object_idset_free(commit_ids); if (graph) got_commit_graph_close(graph); if (graph2) got_commit_graph_close(graph2); return err; } /* * Sort the graph for traversal in topological order. * * This implementation is based on the description of topological sorting * of git commits by Derrick Stolee at * https://github.blog/2022-08-30-gits-database-internals-ii-commit-history-queries/#topological-sorting * which reads as follows: * * The basic algorithm for topological sorting is Kahn’s algorithm which * follows two big steps: * 1. Walk all reachable commits, counting the number of times a commit appears * as a parent of another commit. Call these numbers the in-degree of the * commit, referencing the number of incoming edges. * 2. Walk the reachable commits, but only visit a commit if its in-degree * value is zero. When visiting a commit, decrement the in-degree value of * each parent. * * This algorithm works because at least one of our starting points will * have in-degree zero, and then decrementing the in-degree value is similar * to deleting the commit from the graph, always having at least one commit * with in-degree zero. */ const struct got_error * got_commit_graph_toposort(struct got_commit_graph *graph, struct got_object_id *id, struct got_repository *repo, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; struct got_commit_graph_node *node = NULL, *pnode = NULL; struct got_commit_object *commit = NULL; struct got_object_id_queue commits; const struct got_object_id_queue *parent_ids; struct got_object_qid *qid = NULL, *pid; int i; STAILQ_INIT(&commits); if (graph->flags & GOT_COMMIT_GRAPH_FIRST_PARENT_TRAVERSAL) return got_commit_graph_bfsort(graph, id, repo, cancel_cb, cancel_arg); /* Clear left-over state from previous iteration attempts. */ while ((node = TAILQ_FIRST(&graph->iter_list))) TAILQ_REMOVE(&graph->iter_list, node, entry); err = got_object_idset_for_each(graph->open_branches, remove_branch_tip, graph->open_branches); if (err) return err; graph->flags |= GOT_COMMIT_GRAPH_TOPOSORT; /* * Sorting the commit graph in topological order requires visiting * every reachable commit. This is very expensive but there are * ways to speed this up significantly in the future: * 1) Run this loop in got-read-pack if possible. * 2) Use Git's commit-graph file to compute the result incrementally. * See the blog post linked above for details. */ err = got_object_qid_alloc_partial(&qid); if (err) return err; memcpy(&qid->id, id, sizeof(qid->id)); STAILQ_INSERT_TAIL(&commits, qid, entry); while (!STAILQ_EMPTY(&commits)) { if (cancel_cb) { err = (*cancel_cb)(cancel_arg); if (err) break; } qid = STAILQ_FIRST(&commits); STAILQ_REMOVE_HEAD(&commits, entry); err = got_object_open_as_commit(&commit, repo, &qid->id); if (err) break; node = got_object_idset_get(graph->node_ids, &qid->id); if (node == NULL) { err = add_node(&node, graph, id, repo); if (err) break; TAILQ_INSERT_TAIL(&graph->iter_list, node, entry); } got_object_qid_free(qid); qid = NULL; if (node->timestamp != 0) /* already traversed once */ continue; if (node->nparents == -1) { node->nparents = got_object_commit_get_nparents(commit); if (node->nparents > nitems(node->parents)) { node->more_parents = calloc(node->nparents, sizeof(*node->more_parents)); if (node->more_parents == NULL) { err = got_error_from_errno("calloc"); break; } } } node->timestamp = got_object_commit_get_committer_time(commit); parent_ids = got_object_commit_get_parent_ids(commit); i = 0; STAILQ_FOREACH(pid, parent_ids, entry) { if (cancel_cb) { err = (*cancel_cb)(cancel_arg); if (err) goto done; } /* * Increment the in-degree counter every time a given * commit appears as the parent of another commit. */ pnode = got_object_idset_get(graph->node_ids, &pid->id); if (pnode == NULL) { err = add_node(&pnode, graph, &pid->id, repo); if (err) goto done; TAILQ_INSERT_TAIL(&graph->iter_list, pnode, entry); } pnode->indegree++; /* * Cache parent pointers on the node to make future * in-degree updates easier. */ if (node->nparents <= nitems(node->parents)) { node->parents[i] = pnode; } else { node->more_parents[i] = pnode; if (i < nitems(node->parents)) node->parents[i] = pnode; } i++; /* Keep traversing through all parent commits. */ err = got_object_qid_alloc_partial(&qid); if (err) goto done; memcpy(&qid->id, &pid->id, sizeof(qid->id)); STAILQ_INSERT_TAIL(&commits, qid, entry); qid = NULL; } got_object_commit_close(commit); commit = NULL; } done: if (commit) got_object_commit_close(commit); got_object_qid_free(qid); got_object_id_queue_free(&commits); return err; } got-portable-0.111/lib/serve.c0000644000175000017500000010365115001741021011627 /* * Copyright (c) 2022 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_serve.h" #include "got_path.h" #include "got_version.h" #include "got_reference.h" #include "got_object.h" #include "got_lib_pkt.h" #include "got_lib_dial.h" #include "got_lib_gitproto.h" #include "got_lib_hash.h" #include "got_lib_poll.h" #include "gotd.h" #ifndef nitems #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) #endif /* * Number of seconds until we give up trying to read more * data from the client connection. */ static const int timeout = 60; static const struct got_capability read_capabilities[] = { { GOT_CAPA_AGENT, "got/" GOT_VERSION_STR }, { GOT_CAPA_OFS_DELTA, NULL }, { GOT_CAPA_SIDE_BAND_64K, NULL }, }; static const struct got_capability write_capabilities[] = { { GOT_CAPA_AGENT, "got/" GOT_VERSION_STR }, { GOT_CAPA_OFS_DELTA, NULL }, { GOT_CAPA_REPORT_STATUS, NULL }, { GOT_CAPA_NO_THIN, NULL }, { GOT_CAPA_DELETE_REFS, NULL }, }; static const struct got_error * append_read_capabilities(size_t *capalen, size_t len, const char *symrefstr, uint8_t *buf, size_t bufsize) { struct got_capability capa[nitems(read_capabilities) + 1]; size_t ncapa; memcpy(&capa, read_capabilities, sizeof(read_capabilities)); if (symrefstr) { capa[nitems(read_capabilities)].key = "symref"; capa[nitems(read_capabilities)].value = symrefstr; ncapa = nitems(capa); } else ncapa = nitems(read_capabilities); return got_gitproto_append_capabilities(capalen, buf, len, bufsize, capa, ncapa); } static const struct got_error * send_ref(int outfd, uint8_t *id, const char *refname, int send_capabilities, int client_is_reading, const char *symrefstr, int chattygot) { const struct got_error *err = NULL; char hex[SHA1_DIGEST_STRING_LENGTH]; char buf[GOT_PKT_MAX]; size_t len, capalen = 0; if (got_sha1_digest_to_str(id, hex, sizeof(hex)) == NULL) return got_error(GOT_ERR_BAD_OBJ_ID); len = snprintf(buf, sizeof(buf), "%s %s", hex, refname); if (len >= sizeof(buf)) return got_error(GOT_ERR_NO_SPACE); if (send_capabilities) { if (client_is_reading) { err = append_read_capabilities(&capalen, len, symrefstr, buf, sizeof(buf)); } else { err = got_gitproto_append_capabilities(&capalen, buf, len, sizeof(buf), write_capabilities, nitems(write_capabilities)); } if (err) return err; len += capalen; } if (len + 1 >= sizeof(buf)) return got_error(GOT_ERR_NO_SPACE); buf[len] = '\n'; len++; buf[len] = '\0'; return got_pkt_writepkt(outfd, buf, len, chattygot); } static const struct got_error * send_zero_refs(int outfd, int client_is_reading, int chattygot) { const struct got_error *err = NULL; const char *line = GOT_SHA1_STRING_ZERO " capabilities^{}"; char buf[GOT_PKT_MAX]; size_t len, capalen = 0; len = strlcpy(buf, line, sizeof(buf)); if (len >= sizeof(buf)) return got_error(GOT_ERR_NO_SPACE); if (client_is_reading) { err = got_gitproto_append_capabilities(&capalen, buf, len, sizeof(buf), read_capabilities, nitems(read_capabilities)); if (err) return err; } else { err = got_gitproto_append_capabilities(&capalen, buf, len, sizeof(buf), write_capabilities, nitems(write_capabilities)); if (err) return err; } return got_pkt_writepkt(outfd, buf, len + capalen, chattygot); } static void echo_error(const struct got_error *err, int outfd, int chattygot) { char buf[4 + GOT_ERR_MAX_MSG_SIZE]; size_t len; /* * Echo the error to the client on a pkt-line. * The client should then terminate its session. */ buf[0] = 'E'; buf[1] = 'R'; buf[2] = 'R'; buf[3] = ' '; buf[4] = '\0'; len = strlcat(buf, err->msg, sizeof(buf)); got_pkt_writepkt(outfd, buf, len, chattygot); } static const struct got_error * announce_refs(int outfd, struct imsgbuf *ibuf, int client_is_reading, const char *repo_path, int chattygot) { const struct got_error *err = NULL; struct imsg imsg; size_t datalen; struct gotd_imsg_list_refs lsref; struct gotd_imsg_reflist ireflist; struct gotd_imsg_ref iref; struct gotd_imsg_symref isymref; size_t nrefs = 0; int have_nrefs = 0, sent_capabilities = 0; char *symrefname = NULL, *symreftarget = NULL, *symrefstr = NULL; char *refname = NULL; memset(&imsg, 0, sizeof(imsg)); memset(&lsref, 0, sizeof(lsref)); if (strlcpy(lsref.repo_name, repo_path, sizeof(lsref.repo_name)) >= sizeof(lsref.repo_name)) return got_error(GOT_ERR_NO_SPACE); lsref.client_is_reading = client_is_reading; if (imsg_compose(ibuf, GOTD_IMSG_LIST_REFS, 0, 0, -1, &lsref, sizeof(lsref)) == -1) return got_error_from_errno("imsg_compose LIST_REFS"); err = gotd_imsg_flush(ibuf); if (err) { if (err->code == GOT_ERR_EOF) { const struct got_error *err2; /* Connection closed early. Try to find out why. */ err2 = gotd_imsg_poll_recv(&imsg, ibuf, 0); if (err2) return err; if (imsg.hdr.type == GOTD_IMSG_ERROR) err = gotd_imsg_recv_error(NULL, &imsg); imsg_free(&imsg); } return err; } while (!have_nrefs || nrefs > 0) { err = gotd_imsg_poll_recv(&imsg, ibuf, 0); if (err) goto done; datalen = imsg.hdr.len - IMSG_HEADER_SIZE; switch (imsg.hdr.type) { case GOTD_IMSG_ERROR: err = gotd_imsg_recv_error(NULL, &imsg); goto done; case GOTD_IMSG_REFLIST: if (have_nrefs || nrefs > 0) { err = got_error(GOT_ERR_PRIVSEP_MSG); goto done; } if (datalen != sizeof(ireflist)) { err = got_error(GOT_ERR_PRIVSEP_MSG); goto done; } memcpy(&ireflist, imsg.data, sizeof(ireflist)); nrefs = ireflist.nrefs; have_nrefs = 1; if (nrefs == 0) err = send_zero_refs(outfd, client_is_reading, chattygot); break; case GOTD_IMSG_REF: if (!have_nrefs || nrefs == 0) { err = got_error(GOT_ERR_PRIVSEP_MSG); goto done; } if (datalen < sizeof(iref)) { err = got_error(GOT_ERR_PRIVSEP_MSG); goto done; } memcpy(&iref, imsg.data, sizeof(iref)); if (datalen != sizeof(iref) + iref.name_len) { err = got_error(GOT_ERR_PRIVSEP_LEN); goto done; } refname = strndup(imsg.data + sizeof(iref), iref.name_len); if (refname == NULL) { err = got_error_from_errno("strndup"); goto done; } err = send_ref(outfd, iref.id, refname, !sent_capabilities, client_is_reading, NULL, chattygot); free(refname); refname = NULL; if (err) goto done; sent_capabilities = 1; if (nrefs > 0) nrefs--; break; case GOTD_IMSG_SYMREF: if (!have_nrefs || nrefs == 0) { err = got_error(GOT_ERR_PRIVSEP_MSG); goto done; } if (datalen < sizeof(isymref)) { err = got_error(GOT_ERR_PRIVSEP_LEN); goto done; } memcpy(&isymref, imsg.data, sizeof(isymref)); if (datalen != sizeof(isymref) + isymref.name_len + isymref.target_len) { err = got_error(GOT_ERR_PRIVSEP_LEN); goto done; } /* * For now, we only announce one symbolic ref, * as part of our capability advertisement. */ if (sent_capabilities || symrefstr != NULL || symrefname != NULL || symreftarget != NULL) break; symrefname = strndup(imsg.data + sizeof(isymref), isymref.name_len); if (symrefname == NULL) { err = got_error_from_errno("malloc"); goto done; } symreftarget = strndup( imsg.data + sizeof(isymref) + isymref.name_len, isymref.target_len); if (symreftarget == NULL) { err = got_error_from_errno("strndup"); goto done; } if (asprintf(&symrefstr, "%s:%s", symrefname, symreftarget) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = send_ref(outfd, isymref.target_id, symrefname, !sent_capabilities, client_is_reading, symrefstr, chattygot); free(refname); refname = NULL; if (err) goto done; sent_capabilities = 1; if (nrefs > 0) nrefs--; break; default: err = got_error(GOT_ERR_PRIVSEP_MSG); break; } imsg_free(&imsg); } err = got_pkt_flushpkt(outfd, chattygot); if (err) goto done; done: free(symrefstr); free(symrefname); free(symreftarget); return err; } static const struct got_error * parse_want_line(char **common_capabilities, uint8_t *id, char *buf, size_t len) { const struct got_error *err; char *id_str = NULL, *client_capabilities = NULL; err = got_gitproto_parse_want_line(&id_str, &client_capabilities, buf, len); if (err) return err; if (!got_parse_hash_digest(id, id_str, GOT_HASH_SHA1)) { err = got_error_msg(GOT_ERR_BAD_PACKET, "want-line with bad object ID"); goto done; } if (client_capabilities) { err = got_gitproto_match_capabilities(common_capabilities, NULL, client_capabilities, read_capabilities, nitems(read_capabilities)); if (err) goto done; } done: free(id_str); free(client_capabilities); return err; } static const struct got_error * parse_have_line(uint8_t *id, char *buf, size_t len) { const struct got_error *err; char *id_str = NULL; err = got_gitproto_parse_have_line(&id_str, buf, len); if (err) return err; if (!got_parse_hash_digest(id, id_str, GOT_HASH_SHA1)) { err = got_error_msg(GOT_ERR_BAD_PACKET, "have-line with bad object ID"); goto done; } done: free(id_str); return err; } static const struct got_error * send_capability(struct got_capability *capa, struct imsgbuf *ibuf) { const struct got_error *err = NULL; struct gotd_imsg_capability icapa; size_t len; struct ibuf *wbuf; memset(&icapa, 0, sizeof(icapa)); icapa.key_len = strlen(capa->key); len = sizeof(icapa) + icapa.key_len; if (capa->value) { icapa.value_len = strlen(capa->value); len += icapa.value_len; } wbuf = imsg_create(ibuf, GOTD_IMSG_CAPABILITY, 0, 0, len); if (wbuf == NULL) { err = got_error_from_errno("imsg_create CAPABILITY"); return err; } if (imsg_add(wbuf, &icapa, sizeof(icapa)) == -1) return got_error_from_errno("imsg_add CAPABILITY"); if (imsg_add(wbuf, capa->key, icapa.key_len) == -1) return got_error_from_errno("imsg_add CAPABILITY"); if (capa->value) { if (imsg_add(wbuf, capa->value, icapa.value_len) == -1) return got_error_from_errno("imsg_add CAPABILITY"); } imsg_close(ibuf, wbuf); return NULL; } static const struct got_error * send_capabilities(int *use_sidebands, int *report_status, char *capabilities_str, struct imsgbuf *ibuf) { const struct got_error *err = NULL; struct gotd_imsg_capabilities icapas; struct got_capability *capa = NULL; size_t ncapa, i; err = got_gitproto_split_capabilities_str(&capa, &ncapa, capabilities_str); if (err) return err; icapas.ncapabilities = ncapa; if (imsg_compose(ibuf, GOTD_IMSG_CAPABILITIES, 0, 0, -1, &icapas, sizeof(icapas)) == -1) { err = got_error_from_errno("imsg_compose IMSG_CAPABILITIES"); goto done; } for (i = 0; i < ncapa; i++) { err = send_capability(&capa[i], ibuf); if (err) goto done; if (use_sidebands && strcmp(capa[i].key, GOT_CAPA_SIDE_BAND_64K) == 0) *use_sidebands = 1; if (report_status && strcmp(capa[i].key, GOT_CAPA_REPORT_STATUS) == 0) *report_status = 1; } done: free(capa); return err; } static const struct got_error * forward_flushpkt(struct imsgbuf *ibuf) { if (imsg_compose(ibuf, GOTD_IMSG_FLUSH, 0, 0, -1, NULL, 0) == -1) return got_error_from_errno("imsg_compose FLUSH"); return gotd_imsg_flush(ibuf); } static const struct got_error * recv_ack(struct imsg *imsg, uint8_t *expected_id) { struct gotd_imsg_ack iack; size_t datalen; datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen != sizeof(iack)) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&iack, imsg->data, sizeof(iack)); if (memcmp(iack.object_id, expected_id, SHA1_DIGEST_LENGTH) != 0) return got_error(GOT_ERR_BAD_OBJ_ID); return NULL; } static const struct got_error * recv_nak(struct imsg *imsg, uint8_t *expected_id) { struct gotd_imsg_ack inak; size_t datalen; datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen != sizeof(inak)) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&inak, imsg->data, sizeof(inak)); if (memcmp(inak.object_id, expected_id, SHA1_DIGEST_LENGTH) != 0) return got_error(GOT_ERR_BAD_OBJ_ID); return NULL; } static const struct got_error * recv_want(int *use_sidebands, int outfd, struct imsgbuf *ibuf, char *buf, size_t len, int expect_capabilities, int chattygot) { const struct got_error *err; struct gotd_imsg_want iwant; char *capabilities_str; int done = 0; struct imsg imsg; memset(&iwant, 0, sizeof(iwant)); memset(&imsg, 0, sizeof(imsg)); err = parse_want_line(&capabilities_str, iwant.object_id, buf, len); if (err) return err; if (capabilities_str) { if (!expect_capabilities) { err = got_error_msg(GOT_ERR_BAD_PACKET, "unexpected capability announcement received"); goto done; } err = send_capabilities(use_sidebands, NULL, capabilities_str, ibuf); if (err) goto done; } if (imsg_compose(ibuf, GOTD_IMSG_WANT, 0, 0, -1, &iwant, sizeof(iwant)) == -1) { err = got_error_from_errno("imsg_compose WANT"); goto done; } err = gotd_imsg_flush(ibuf); if (err) goto done; /* * Wait for an ACK, or an error in case the desired object * does not exist. */ while (!done && err == NULL) { err = gotd_imsg_poll_recv(&imsg, ibuf, 0); if (err) break; switch (imsg.hdr.type) { case GOTD_IMSG_ERROR: err = gotd_imsg_recv_error(NULL, &imsg); break; case GOTD_IMSG_ACK: err = recv_ack(&imsg, iwant.object_id); if (err) break; done = 1; break; default: err = got_error(GOT_ERR_PRIVSEP_MSG); break; } imsg_free(&imsg); } done: free(capabilities_str); return err; } static const struct got_error * send_ack(int outfd, uint8_t *id, int chattygot) { char hex[SHA1_DIGEST_STRING_LENGTH]; char buf[GOT_PKT_MAX]; int len; if (got_sha1_digest_to_str(id, hex, sizeof(hex)) == NULL) return got_error(GOT_ERR_BAD_OBJ_ID); len = snprintf(buf, sizeof(buf), "ACK %s\n", hex); if (len >= sizeof(buf)) return got_error(GOT_ERR_NO_SPACE); return got_pkt_writepkt(outfd, buf, len, chattygot); } static const struct got_error * send_nak(int outfd, int chattygot) { char buf[5]; int len; len = snprintf(buf, sizeof(buf), "NAK\n"); if (len >= sizeof(buf)) return got_error(GOT_ERR_NO_SPACE); return got_pkt_writepkt(outfd, buf, len, chattygot); } static const struct got_error * recv_have(int *have_ack, int outfd, struct imsgbuf *ibuf, char *buf, size_t len, int chattygot) { const struct got_error *err; struct gotd_imsg_have ihave; int done = 0; struct imsg imsg; memset(&ihave, 0, sizeof(ihave)); memset(&imsg, 0, sizeof(imsg)); err = parse_have_line(ihave.object_id, buf, len); if (err) return err; if (imsg_compose(ibuf, GOTD_IMSG_HAVE, 0, 0, -1, &ihave, sizeof(ihave)) == -1) return got_error_from_errno("imsg_compose HAVE"); err = gotd_imsg_flush(ibuf); if (err) return err; /* * Wait for an ACK or a NAK, indicating whether a common * commit object has been found. */ while (!done && err == NULL) { err = gotd_imsg_poll_recv(&imsg, ibuf, 0); if (err) return err; switch (imsg.hdr.type) { case GOTD_IMSG_ERROR: err = gotd_imsg_recv_error(NULL, &imsg); break; case GOTD_IMSG_ACK: err = recv_ack(&imsg, ihave.object_id); if (err) break; if (!*have_ack) { err = send_ack(outfd, ihave.object_id, chattygot); if (err) return err; *have_ack = 1; } done = 1; break; case GOTD_IMSG_NAK: err = recv_nak(&imsg, ihave.object_id); if (err) break; done = 1; break; default: err = got_error(GOT_ERR_PRIVSEP_MSG); break; } imsg_free(&imsg); } return err; } static const struct got_error * recv_done(int *packfd, int outfd, struct imsgbuf *ibuf, int chattygot) { const struct got_error *err; struct imsg imsg; int fd; *packfd = -1; if (imsg_compose(ibuf, GOTD_IMSG_DONE, 0, 0, -1, NULL, 0) == -1) return got_error_from_errno("imsg_compose DONE"); err = gotd_imsg_flush(ibuf); if (err) return err; while (*packfd == -1 && err == NULL) { err = gotd_imsg_poll_recv(&imsg, ibuf, 0); if (err) break; switch (imsg.hdr.type) { case GOTD_IMSG_ERROR: err = gotd_imsg_recv_error(NULL, &imsg); break; case GOTD_IMSG_PACKFILE_PIPE: fd = imsg_get_fd(&imsg); if (fd == -1) { err = got_error(GOT_ERR_PRIVSEP_NO_FD); break; } *packfd = fd; if (imsg_compose(ibuf, GOTD_IMSG_PACKFILE_READY, 0, 0, -1, NULL, 0) == -1) err = got_error_from_errno("imsg_compose " "PACKFILE_READY"); err = gotd_imsg_flush(ibuf); break; default: err = got_error(GOT_ERR_PRIVSEP_MSG); break; } imsg_free(&imsg); } return err; } static const struct got_error * relay_progress_reports(struct imsgbuf *ibuf, int outfd, int chattygot) { const struct got_error *err = NULL; int pack_starting = 0; struct gotd_imsg_packfile_progress iprog; char buf[GOT_PKT_MAX]; struct imsg imsg; size_t datalen; int p_deltify = 0, n; const char *eol = "\r"; memset(&imsg, 0, sizeof(imsg)); while (!pack_starting && err == NULL) { err = gotd_imsg_poll_recv(&imsg, ibuf, 0); if (err) break; datalen = imsg.hdr.len - IMSG_HEADER_SIZE; switch (imsg.hdr.type) { case GOTD_IMSG_ERROR: err = gotd_imsg_recv_error(NULL, &imsg); break; case GOTD_IMSG_PACKFILE_READY: eol = "\n"; pack_starting = 1; /* fallthrough */ case GOTD_IMSG_PACKFILE_PROGRESS: if (datalen != sizeof(iprog)) { err = got_error(GOT_ERR_PRIVSEP_LEN); break; } memcpy(&iprog, imsg.data, sizeof(iprog)); if (iprog.nobj_total > 0) { p_deltify = (iprog.nobj_deltify * 100) / iprog.nobj_total; } buf[0] = GOT_SIDEBAND_PROGRESS_INFO; n = snprintf(&buf[1], sizeof(buf) - 1, "%d commits colored, " "%d objects found, " "deltify %d%%%s", iprog.ncolored, iprog.nfound, p_deltify, eol); if (n >= sizeof(buf) - 1) break; err = got_pkt_writepkt(outfd, buf, 1 + n, chattygot); break; default: err = got_error(GOT_ERR_PRIVSEP_MSG); break; } imsg_free(&imsg); } return err; } static const struct got_error * serve_read(int infd, int outfd, int gotd_sock, const char *repo_path, int chattygot) { const struct got_error *err = NULL; char buf[GOT_PKT_MAX]; struct imsgbuf ibuf; enum protostate { STATE_EXPECT_WANT, STATE_EXPECT_MORE_WANT, STATE_EXPECT_HAVE_OR_DONE, STATE_DONE, }; enum protostate curstate = STATE_EXPECT_WANT; int have_ack = 0, use_sidebands = 0, seen_have = 0; int packfd = -1; size_t pack_chunksize; if (imsgbuf_init(&ibuf, gotd_sock) == -1) return got_error_from_errno("imsgbuf_init"); imsgbuf_allow_fdpass(&ibuf); err = announce_refs(outfd, &ibuf, 1, repo_path, chattygot); if (err) goto done; while (curstate != STATE_DONE) { int n; buf[0] = '\0'; err = got_pkt_readpkt(&n, infd, buf, sizeof(buf), chattygot, timeout); if (err) goto done; if (n == 0) { if (curstate != STATE_EXPECT_WANT && curstate != STATE_EXPECT_MORE_WANT && curstate != STATE_EXPECT_HAVE_OR_DONE) { err = got_error_msg(GOT_ERR_BAD_PACKET, "unexpected flush packet received"); goto done; } if (curstate == STATE_EXPECT_WANT) { ssize_t r; /* * If the client does not want to fetch * anything we should receive a flush * packet followed by EOF. */ r = read(infd, buf, sizeof(buf)); if (r == -1) { err = got_error_from_errno("read"); goto done; } if (r == 0) /* EOF */ goto done; /* Zero-length field followed by payload. */ err = got_error_msg(GOT_ERR_BAD_PACKET, "unexpected flush packet received"); goto done; } if (curstate == STATE_EXPECT_WANT || curstate == STATE_EXPECT_MORE_WANT || curstate == STATE_EXPECT_HAVE_OR_DONE) { err = forward_flushpkt(&ibuf); if (err) goto done; } if (curstate == STATE_EXPECT_HAVE_OR_DONE && !have_ack) { err = send_nak(outfd, chattygot); if (err) goto done; } if (curstate == STATE_EXPECT_MORE_WANT) curstate = STATE_EXPECT_HAVE_OR_DONE; } else if (n >= 5 && strncmp(buf, "want ", 5) == 0) { if (curstate != STATE_EXPECT_WANT && curstate != STATE_EXPECT_MORE_WANT) { err = got_error_msg(GOT_ERR_BAD_PACKET, "unexpected 'want' packet"); goto done; } err = recv_want(&use_sidebands, outfd, &ibuf, buf, n, curstate == STATE_EXPECT_WANT ? 1 : 0, chattygot); if (err) goto done; if (curstate == STATE_EXPECT_WANT) curstate = STATE_EXPECT_MORE_WANT; } else if (n >= 5 && strncmp(buf, "have ", 5) == 0) { if (curstate != STATE_EXPECT_HAVE_OR_DONE) { err = got_error_msg(GOT_ERR_BAD_PACKET, "unexpected 'have' packet"); goto done; } err = recv_have(&have_ack, outfd, &ibuf, buf, n, chattygot); if (err) goto done; seen_have = 1; } else if (n == 5 && strncmp(buf, "done\n", 5) == 0) { if (curstate != STATE_EXPECT_HAVE_OR_DONE) { err = got_error_msg(GOT_ERR_BAD_PACKET, "unexpected 'done' packet"); goto done; } err = recv_done(&packfd, outfd, &ibuf, chattygot); if (err) goto done; curstate = STATE_DONE; break; } else { err = got_error(GOT_ERR_BAD_PACKET); goto done; } } if (!seen_have) { err = send_nak(outfd, chattygot); if (err) goto done; } if (use_sidebands) { err = relay_progress_reports(&ibuf, outfd, chattygot); if (err) goto done; pack_chunksize = GOT_SIDEBAND_64K_PACKFILE_DATA_MAX; } else pack_chunksize = sizeof(buf); for (;;) { ssize_t r; r = read(packfd, use_sidebands ? &buf[1] : buf, pack_chunksize); if (r == -1) { err = got_error_from_errno("read"); break; } else if (r == 0) { err = got_pkt_flushpkt(outfd, chattygot); break; } if (use_sidebands) { buf[0] = GOT_SIDEBAND_PACKFILE_DATA; err = got_pkt_writepkt(outfd, buf, 1 + r, chattygot); if (err) break; } else { err = got_poll_write_full(outfd, buf, r); if (err) { if (err->code == GOT_ERR_EOF) err = NULL; break; } } } done: imsgbuf_clear(&ibuf); if (packfd != -1 && close(packfd) == -1 && err == NULL) err = got_error_from_errno("close"); if (err) echo_error(err, outfd, chattygot); return err; } static const struct got_error * parse_ref_update_line(char **common_capabilities, char **refname, uint8_t *old_id, uint8_t *new_id, char *buf, size_t len) { const struct got_error *err; char *old_id_str = NULL, *new_id_str = NULL; char *client_capabilities = NULL; *refname = NULL; err = got_gitproto_parse_ref_update_line(&old_id_str, &new_id_str, refname, &client_capabilities, buf, len); if (err) return err; if (!got_parse_hash_digest(old_id, old_id_str, GOT_HASH_SHA1) || !got_parse_hash_digest(new_id, new_id_str, GOT_HASH_SHA1)) { err = got_error_msg(GOT_ERR_BAD_PACKET, "ref-update with bad object ID"); goto done; } if (!got_ref_name_is_valid(*refname)) { err = got_error_msg(GOT_ERR_BAD_PACKET, "ref-update with bad reference name"); goto done; } if (client_capabilities) { err = got_gitproto_match_capabilities(common_capabilities, NULL, client_capabilities, write_capabilities, nitems(write_capabilities)); if (err) goto done; } done: free(old_id_str); free(new_id_str); free(client_capabilities); if (err) { free(*refname); *refname = NULL; } return err; } static const struct got_error * recv_ref_update(int *report_status, int outfd, struct imsgbuf *ibuf, char *buf, size_t len, int expect_capabilities, int chattygot) { const struct got_error *err; struct gotd_imsg_ref_update iref; struct ibuf *wbuf; char *capabilities_str = NULL, *refname = NULL; int done = 0; struct imsg imsg; memset(&iref, 0, sizeof(iref)); memset(&imsg, 0, sizeof(imsg)); err = parse_ref_update_line(&capabilities_str, &refname, iref.old_id, iref.new_id, buf, len); if (err) return err; if (capabilities_str) { if (!expect_capabilities) { err = got_error_msg(GOT_ERR_BAD_PACKET, "unexpected capability announcement received"); goto done; } err = send_capabilities(NULL, report_status, capabilities_str, ibuf); if (err) goto done; } iref.name_len = strlen(refname); len = sizeof(iref) + iref.name_len; wbuf = imsg_create(ibuf, GOTD_IMSG_REF_UPDATE, 0, 0, len); if (wbuf == NULL) { err = got_error_from_errno("imsg_create REF_UPDATE"); goto done; } if (imsg_add(wbuf, &iref, sizeof(iref)) == -1) return got_error_from_errno("imsg_add REF_UPDATE"); if (imsg_add(wbuf, refname, iref.name_len) == -1) return got_error_from_errno("imsg_add REF_UPDATE"); imsg_close(ibuf, wbuf); err = gotd_imsg_flush(ibuf); if (err) goto done; /* Wait for ACK or an error. */ while (!done && err == NULL) { err = gotd_imsg_poll_recv(&imsg, ibuf, 0); if (err) break; switch (imsg.hdr.type) { case GOTD_IMSG_ERROR: err = gotd_imsg_recv_error(NULL, &imsg); break; case GOTD_IMSG_ACK: err = recv_ack(&imsg, iref.new_id); if (err) break; done = 1; break; default: err = got_error(GOT_ERR_PRIVSEP_MSG); break; } imsg_free(&imsg); } done: free(capabilities_str); free(refname); return err; } static const struct got_error * recv_packfile(struct imsg *imsg, int infd) { const struct got_error *err = NULL; size_t datalen; int packfd; char buf[GOT_PKT_MAX]; int pack_done = 0; datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen != 0) return got_error(GOT_ERR_PRIVSEP_MSG); packfd = imsg_get_fd(imsg); if (packfd == -1) return got_error(GOT_ERR_PRIVSEP_NO_FD); while (!pack_done) { ssize_t r = 0; err = got_poll_fd(infd, POLLIN, 1); if (err) { if (err->code != GOT_ERR_TIMEOUT) break; err = NULL; } else { r = read(infd, buf, sizeof(buf)); if (r == -1) { err = got_error_from_errno("read"); break; } if (r == 0) { /* * Git clients hang up their side of the * connection after sending the pack file. */ err = NULL; pack_done = 1; break; } } if (r == 0) { /* Detect gotd(8) closing the pack pipe when done. */ err = got_poll_fd(packfd, 0, 1); if (err) { if (err->code != GOT_ERR_TIMEOUT && err->code != GOT_ERR_EOF) break; if (err->code == GOT_ERR_EOF) pack_done = 1; err = NULL; } } else { /* Write pack data and/or detect pipe being closed. */ err = got_poll_write_full(packfd, buf, r); if (err) { if (err->code == GOT_ERR_EOF) err = NULL; break; } } } close(packfd); return err; } static const struct got_error * report_unpack_status(struct imsg *imsg, int outfd, int chattygot) { const struct got_error *err = NULL; struct gotd_imsg_packfile_status istatus; char buf[GOT_PKT_MAX]; size_t datalen, len; char *reason = NULL; datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen < sizeof(istatus)) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&istatus, imsg->data, sizeof(istatus)); if (datalen != sizeof(istatus) + istatus.reason_len) return got_error(GOT_ERR_PRIVSEP_LEN); reason = strndup(imsg->data + sizeof(istatus), istatus.reason_len); if (reason == NULL) { err = got_error_from_errno("strndup"); goto done; } if (err == NULL) len = snprintf(buf, sizeof(buf), "unpack ok\n"); else len = snprintf(buf, sizeof(buf), "unpack %s\n", reason); if (len >= sizeof(buf)) { err = got_error(GOT_ERR_NO_SPACE); goto done; } err = got_pkt_writepkt(outfd, buf, len, chattygot); done: free(reason); return err; } static const struct got_error * recv_ref_update_ok(struct imsg *imsg, int outfd, int chattygot) { const struct got_error *err = NULL; struct gotd_imsg_ref_update_ok iok; size_t datalen, len; char buf[GOT_PKT_MAX]; char *refname = NULL; datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen < sizeof(iok)) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&iok, imsg->data, sizeof(iok)); if (datalen != sizeof(iok) + iok.name_len) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&iok, imsg->data, sizeof(iok)); refname = strndup(imsg->data + sizeof(iok), iok.name_len); if (refname == NULL) return got_error_from_errno("strndup"); len = snprintf(buf, sizeof(buf), "ok %s\n", refname); if (len >= sizeof(buf)) { err = got_error(GOT_ERR_NO_SPACE); goto done; } err = got_pkt_writepkt(outfd, buf, len, chattygot); done: free(refname); return err; } static const struct got_error * recv_ref_update_ng(struct imsg *imsg, int outfd, int chattygot) { const struct got_error *err = NULL; struct gotd_imsg_ref_update_ng ing; size_t datalen, len; char buf[GOT_PKT_MAX]; char *refname = NULL, *reason = NULL; datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen < sizeof(ing)) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&ing, imsg->data, sizeof(ing)); if (datalen != sizeof(ing) + ing.name_len + ing.reason_len) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&ing, imsg->data, sizeof(ing)); refname = strndup(imsg->data + sizeof(ing), ing.name_len); if (refname == NULL) return got_error_from_errno("strndup"); reason = strndup(imsg->data + sizeof(ing) + ing.name_len, ing.reason_len); if (reason == NULL) { err = got_error_from_errno("strndup"); goto done; } len = snprintf(buf, sizeof(buf), "ng %s %s\n", refname, reason); if (len >= sizeof(buf)) { err = got_error(GOT_ERR_NO_SPACE); goto done; } err = got_pkt_writepkt(outfd, buf, len, chattygot); done: free(refname); free(reason); return err; } static const struct got_error * serve_write(int infd, int outfd, int gotd_sock, const char *repo_path, int chattygot) { const struct got_error *err = NULL; char buf[GOT_PKT_MAX]; struct imsgbuf ibuf; enum protostate { STATE_EXPECT_REF_UPDATE, STATE_EXPECT_MORE_REF_UPDATES, STATE_EXPECT_PACKFILE, STATE_PACKFILE_RECEIVED, STATE_REFS_UPDATED, }; enum protostate curstate = STATE_EXPECT_REF_UPDATE; struct imsg imsg; int report_status = 0; if (imsgbuf_init(&ibuf, gotd_sock) == -1) return got_error_from_errno("imsgbuf_init"); imsgbuf_allow_fdpass(&ibuf); memset(&imsg, 0, sizeof(imsg)); err = announce_refs(outfd, &ibuf, 0, repo_path, chattygot); if (err) goto done; while (curstate != STATE_EXPECT_PACKFILE) { int n; buf[0] = '\0'; err = got_pkt_readpkt(&n, infd, buf, sizeof(buf), chattygot, timeout); if (err) goto done; if (n == 0) { if (curstate == STATE_EXPECT_REF_UPDATE) { /* The client will not send us anything. */ goto done; } else if (curstate != STATE_EXPECT_MORE_REF_UPDATES) { err = got_error_msg(GOT_ERR_BAD_PACKET, "unexpected flush packet received"); goto done; } err = forward_flushpkt(&ibuf); if (err) goto done; curstate = STATE_EXPECT_PACKFILE; } else if (n >= (SHA1_DIGEST_STRING_LENGTH * 2) + 2) { if (curstate != STATE_EXPECT_REF_UPDATE && curstate != STATE_EXPECT_MORE_REF_UPDATES) { err = got_error_msg(GOT_ERR_BAD_PACKET, "unexpected ref-update packet"); goto done; } if (curstate == STATE_EXPECT_REF_UPDATE) { err = recv_ref_update(&report_status, outfd, &ibuf, buf, n, 1, chattygot); } else { err = recv_ref_update(NULL, outfd, &ibuf, buf, n, 0, chattygot); } if (err) goto done; curstate = STATE_EXPECT_MORE_REF_UPDATES; } else { err = got_error(GOT_ERR_BAD_PACKET); goto done; } } while (curstate != STATE_PACKFILE_RECEIVED) { err = gotd_imsg_poll_recv(&imsg, &ibuf, 0); if (err) goto done; switch (imsg.hdr.type) { case GOTD_IMSG_ERROR: err = gotd_imsg_recv_error(NULL, &imsg); goto done; case GOTD_IMSG_PACKFILE_PIPE: err = recv_packfile(&imsg, infd); if (err) { if (err->code != GOT_ERR_EOF) goto done; /* * EOF is reported when the client hangs up, * which can happen with Git clients. * The socket should stay half-open so we * can still send our reports if requested. */ err = NULL; } curstate = STATE_PACKFILE_RECEIVED; break; default: err = got_error(GOT_ERR_PRIVSEP_MSG); break; } imsg_free(&imsg); if (err) goto done; } while (curstate != STATE_REFS_UPDATED && err == NULL) { err = gotd_imsg_poll_recv(&imsg, &ibuf, 0); if (err) break; switch (imsg.hdr.type) { case GOTD_IMSG_ERROR: err = gotd_imsg_recv_error(NULL, &imsg); break; case GOTD_IMSG_PACKFILE_STATUS: if (!report_status) break; err = report_unpack_status(&imsg, outfd, chattygot); break; case GOTD_IMSG_REF_UPDATE_OK: if (!report_status) break; err = recv_ref_update_ok(&imsg, outfd, chattygot); break; case GOTD_IMSG_REF_UPDATE_NG: if (!report_status) break; err = recv_ref_update_ng(&imsg, outfd, chattygot); break; case GOTD_IMSG_REFS_UPDATED: curstate = STATE_REFS_UPDATED; err = got_pkt_flushpkt(outfd, chattygot); break; default: err = got_error(GOT_ERR_PRIVSEP_MSG); break; } imsg_free(&imsg); } done: imsgbuf_clear(&ibuf); if (err) echo_error(err, outfd, chattygot); return err; } const struct got_error * got_serve(int infd, int outfd, const char *command, const char *repo_path, int gotd_sock, int chattygot) { const struct got_error *err = NULL; if (strcmp(command, GOT_DIAL_CMD_FETCH) == 0) err = serve_read(infd, outfd, gotd_sock, repo_path, chattygot); else if (strcmp(command, GOT_DIAL_CMD_SEND) == 0) err = serve_write(infd, outfd, gotd_sock, repo_path, chattygot); else err = got_error(GOT_ERR_BAD_PACKET); return err; } got-portable-0.111/lib/object.c0000644000175000017500000005215315001741021011751 /* * Copyright (c) 2018, 2019 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_object.h" #include "got_repository.h" #include "got_opentemp.h" #include "got_path.h" #include "got_lib_hash.h" #include "got_lib_delta.h" #include "got_lib_inflate.h" #include "got_lib_object.h" #include "got_lib_object_idcache.h" #include "got_lib_object_cache.h" #include "got_lib_object_parse.h" #include "got_lib_pack.h" #include "got_lib_repository.h" #ifndef MIN #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b)) #endif #ifndef nitems #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) #endif struct got_object_id * got_object_get_id(struct got_object *obj) { return &obj->id; } const struct got_error * got_object_get_id_str(char **outbuf, struct got_object *obj) { return got_object_id_str(outbuf, &obj->id); } const struct got_error * got_object_get_type(int *type, struct got_repository *repo, struct got_object_id *id) { const struct got_error *err = NULL; struct got_object *obj; err = got_object_open(&obj, repo, id); if (err) return err; switch (obj->type) { case GOT_OBJ_TYPE_COMMIT: case GOT_OBJ_TYPE_TREE: case GOT_OBJ_TYPE_BLOB: case GOT_OBJ_TYPE_TAG: *type = obj->type; break; default: err = got_error(GOT_ERR_OBJ_TYPE); break; } got_object_close(obj); return err; } const struct got_error * got_object_get_path(char **path, struct got_object_id *id, struct got_repository *repo) { const struct got_error *err = NULL; char *hex = NULL; char *path_objects; *path = NULL; path_objects = got_repo_get_path_objects(repo); if (path_objects == NULL) return got_error_from_errno("got_repo_get_path_objects"); err = got_object_id_str(&hex, id); if (err) goto done; if (asprintf(path, "%s/%.2x/%s", path_objects, id->hash[0], hex + 2) == -1) err = got_error_from_errno("asprintf"); done: free(hex); free(path_objects); return err; } const struct got_error * got_object_open_loose_fd(int *fd, struct got_object_id *id, struct got_repository *repo) { const struct got_error *err = NULL; char *path; err = got_object_get_path(&path, id, repo); if (err) return err; *fd = open(path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (*fd == -1) { if (errno == ENOENT) err = got_error_no_obj(id); else err = got_error_from_errno2("open", path); goto done; } done: free(path); return err; } const struct got_error * got_object_open_by_id_str(struct got_object **obj, struct got_repository *repo, const char *id_str) { struct got_object_id id; if (!got_parse_object_id(&id, id_str, repo->algo)) return got_error_path(id_str, GOT_ERR_BAD_OBJ_ID_STR); return got_object_open(obj, repo, &id); } const struct got_error * got_object_resolve_id_str(struct got_object_id **id, struct got_repository *repo, const char *id_str) { const struct got_error *err = NULL; struct got_object *obj; err = got_object_open_by_id_str(&obj, repo, id_str); if (err) return err; *id = got_object_id_dup(got_object_get_id(obj)); got_object_close(obj); if (*id == NULL) return got_error_from_errno("got_object_id_dup"); return NULL; } int got_object_tree_get_nentries(struct got_tree_object *tree) { return tree->nentries; } struct got_tree_entry * got_object_tree_get_first_entry(struct got_tree_object *tree) { return got_object_tree_get_entry(tree, 0); } struct got_tree_entry * got_object_tree_get_last_entry(struct got_tree_object *tree) { return got_object_tree_get_entry(tree, tree->nentries - 1); } struct got_tree_entry * got_object_tree_get_entry(struct got_tree_object *tree, int i) { if (i < 0 || i >= tree->nentries) return NULL; return &tree->entries[i]; } mode_t got_tree_entry_get_mode(struct got_tree_entry *te) { return te->mode; } const char * got_tree_entry_get_name(struct got_tree_entry *te) { return &te->name[0]; } struct got_object_id * got_tree_entry_get_id(struct got_tree_entry *te) { return &te->id; } const struct got_error * got_object_blob_read_to_str(char **s, struct got_blob_object *blob) { const struct got_error *err = NULL; size_t len, totlen, hdrlen, offset; *s = NULL; hdrlen = got_object_blob_get_hdrlen(blob); totlen = 0; offset = 0; do { char *p; err = got_object_blob_read_block(&len, blob); if (err) return err; if (len == 0) break; totlen += len - hdrlen; p = realloc(*s, totlen + 1); if (p == NULL) { err = got_error_from_errno("realloc"); free(*s); *s = NULL; return err; } *s = p; /* Skip blob object header first time around. */ memcpy(*s + offset, got_object_blob_get_read_buf(blob) + hdrlen, len - hdrlen); hdrlen = 0; offset = totlen; } while (len > 0); (*s)[totlen] = '\0'; return NULL; } const struct got_error * got_tree_entry_get_symlink_target(char **link_target, struct got_tree_entry *te, struct got_repository *repo) { const struct got_error *err = NULL; struct got_blob_object *blob = NULL; int fd = -1; *link_target = NULL; if (!got_object_tree_entry_is_symlink(te)) return got_error(GOT_ERR_TREE_ENTRY_TYPE); fd = got_opentempfd(); if (fd == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } err = got_object_open_as_blob(&blob, repo, got_tree_entry_get_id(te), PATH_MAX, fd); if (err) goto done; err = got_object_blob_read_to_str(link_target, blob); done: if (fd != -1 && close(fd) == -1 && err == NULL) err = got_error_from_errno("close"); if (blob) got_object_blob_close(blob); if (err) { free(*link_target); *link_target = NULL; } return err; } int got_tree_entry_get_index(struct got_tree_entry *te) { return te->idx; } struct got_tree_entry * got_tree_entry_get_next(struct got_tree_object *tree, struct got_tree_entry *te) { return got_object_tree_get_entry(tree, te->idx + 1); } struct got_tree_entry * got_tree_entry_get_prev(struct got_tree_object *tree, struct got_tree_entry *te) { return got_object_tree_get_entry(tree, te->idx - 1); } const struct got_error * got_object_blob_close(struct got_blob_object *blob) { const struct got_error *err = NULL; free(blob->read_buf); if (blob->f && fclose(blob->f) == EOF) err = got_error_from_errno("fclose"); free(blob->data); free(blob); return err; } void got_object_blob_rewind(struct got_blob_object *blob) { if (blob->f) rewind(blob->f); } char * got_object_blob_id_str(struct got_blob_object *blob, char *buf, size_t size) { return got_object_id_hex(&blob->id, buf, size); } size_t got_object_blob_get_hdrlen(struct got_blob_object *blob) { return blob->hdrlen; } const uint8_t * got_object_blob_get_read_buf(struct got_blob_object *blob) { return blob->read_buf; } const struct got_error * got_object_blob_read_block(size_t *outlenp, struct got_blob_object *blob) { size_t n; n = fread(blob->read_buf, 1, blob->blocksize, blob->f); if (n == 0 && ferror(blob->f)) return got_ferror(blob->f, GOT_ERR_IO); *outlenp = n; return NULL; } const struct got_error * got_object_blob_is_binary(int *binary, struct got_blob_object *blob) { const struct got_error *err; size_t hdrlen, len; *binary = 0; hdrlen = got_object_blob_get_hdrlen(blob); if (fseeko(blob->f, hdrlen, SEEK_SET) == -1) return got_error_from_errno("fseeko"); err = got_object_blob_read_block(&len, blob); if (err) return err; *binary = memchr(blob->read_buf, '\0', len) != NULL; if (fseeko(blob->f, hdrlen, SEEK_SET) == -1) return got_error_from_errno("fseeko"); return NULL; } const struct got_error * got_object_blob_getline(char **line, ssize_t *linelen, size_t *linesize, struct got_blob_object *blob) { *linelen = getline(line, linesize, blob->f); if (*linelen == -1 && !feof(blob->f)) return got_error_from_errno("getline"); return NULL; } const struct got_error * got_object_blob_dump_to_file(off_t *filesize, int *nlines, off_t **line_offsets, FILE *outfile, struct got_blob_object *blob) { const struct got_error *err = NULL; size_t n, len, hdrlen; const uint8_t *buf; int i; const int alloc_chunksz = 512; size_t nalloc = 0; off_t off = 0, total_len = 0; if (line_offsets) *line_offsets = NULL; if (filesize) *filesize = 0; if (nlines) *nlines = 0; hdrlen = got_object_blob_get_hdrlen(blob); do { err = got_object_blob_read_block(&len, blob); if (err) return err; if (len == 0) break; buf = got_object_blob_get_read_buf(blob); i = hdrlen; if (nlines) { if (line_offsets && *line_offsets == NULL) { /* Have some data but perhaps no '\n'. */ *nlines = 1; nalloc = alloc_chunksz; *line_offsets = calloc(nalloc, sizeof(**line_offsets)); if (*line_offsets == NULL) return got_error_from_errno("calloc"); /* Skip forward over end of first line. */ while (i < len) { if (buf[i] == '\n') break; i++; } } /* Scan '\n' offsets in remaining chunk of data. */ while (i < len) { if (buf[i] != '\n') { i++; continue; } (*nlines)++; if (line_offsets && nalloc < *nlines) { size_t n = *nlines + alloc_chunksz; off_t *o = recallocarray(*line_offsets, nalloc, n, sizeof(**line_offsets)); if (o == NULL) { free(*line_offsets); *line_offsets = NULL; return got_error_from_errno( "recallocarray"); } *line_offsets = o; nalloc = n; } if (line_offsets) { off = total_len + i - hdrlen + 1; (*line_offsets)[*nlines - 1] = off; } i++; } } /* Skip blob object header first time around. */ n = fwrite(buf + hdrlen, 1, len - hdrlen, outfile); if (n != len - hdrlen) return got_ferror(outfile, GOT_ERR_IO); total_len += len - hdrlen; hdrlen = 0; } while (len != 0); if (fflush(outfile) != 0) return got_error_from_errno("fflush"); rewind(outfile); if (filesize) *filesize = total_len; return NULL; } const char * got_object_tag_get_name(struct got_tag_object *tag) { return tag->tag; } int got_object_tag_get_object_type(struct got_tag_object *tag) { return tag->obj_type; } struct got_object_id * got_object_tag_get_object_id(struct got_tag_object *tag) { return &tag->id; } time_t got_object_tag_get_tagger_time(struct got_tag_object *tag) { return tag->tagger_time; } time_t got_object_tag_get_tagger_gmtoff(struct got_tag_object *tag) { return tag->tagger_gmtoff; } const char * got_object_tag_get_tagger(struct got_tag_object *tag) { return tag->tagger; } const char * got_object_tag_get_message(struct got_tag_object *tag) { return tag->tagmsg; } static struct got_tree_entry * find_entry_by_name(struct got_tree_object *tree, const char *name, size_t len) { int i; /* Note that tree entries are sorted in strncmp() order. */ for (i = 0; i < tree->nentries; i++) { struct got_tree_entry *te = &tree->entries[i]; int cmp = strncmp(te->name, name, len); if (cmp < 0) continue; if (cmp > 0) break; if (te->name[len] == '\0') return te; } return NULL; } struct got_tree_entry * got_object_tree_find_entry(struct got_tree_object *tree, const char *name) { return find_entry_by_name(tree, name, strlen(name)); } const struct got_error * got_object_tree_find_path(struct got_object_id **id, mode_t *mode, struct got_repository *repo, struct got_tree_object *tree, const char *path) { const struct got_error *err = NULL; struct got_tree_object *subtree = NULL; struct got_tree_entry *te = NULL; const char *seg, *s; size_t seglen; *id = NULL; s = path; while (s[0] == '/') s++; seg = s; seglen = 0; subtree = tree; while (*s) { struct got_tree_object *next_tree; if (*s != '/') { s++; seglen++; if (*s) continue; } te = find_entry_by_name(subtree, seg, seglen); if (te == NULL) { err = got_error_path(path, GOT_ERR_NO_TREE_ENTRY); goto done; } if (*s == '\0') break; seg = s + 1; seglen = 0; s++; if (*s) { err = got_object_open_as_tree(&next_tree, repo, &te->id); te = NULL; if (err) goto done; if (subtree != tree) got_object_tree_close(subtree); subtree = next_tree; } } if (te) { *id = got_object_id_dup(&te->id); if (*id == NULL) return got_error_from_errno("got_object_id_dup"); if (mode) *mode = te->mode; } else err = got_error_path(path, GOT_ERR_NO_TREE_ENTRY); done: if (subtree && subtree != tree) got_object_tree_close(subtree); return err; } const struct got_error * got_object_id_by_path(struct got_object_id **id, struct got_repository *repo, struct got_commit_object *commit, const char *path) { const struct got_error *err = NULL; struct got_tree_object *tree = NULL; *id = NULL; /* Handle opening of root of commit's tree. */ if (got_path_is_root_dir(path)) { *id = got_object_id_dup(commit->tree_id); if (*id == NULL) err = got_error_from_errno("got_object_id_dup"); } else { err = got_object_open_as_tree(&tree, repo, commit->tree_id); if (err) goto done; err = got_object_tree_find_path(id, NULL, repo, tree, path); } done: if (tree) got_object_tree_close(tree); return err; } /* * Normalize file mode bits to avoid false positive tree entry differences * in case tree entries have unexpected mode bits set. */ static mode_t normalize_mode_for_comparison(mode_t mode) { /* * For directories, the only relevant bit is the IFDIR bit. * This allows us to detect paths changing from a directory * to a file and vice versa. */ if (S_ISDIR(mode)) return mode & S_IFDIR; /* * For symlinks, the only relevant bit is the IFLNK bit. * This allows us to detect paths changing from a symlinks * to a file or directory and vice versa. */ if (S_ISLNK(mode)) return mode & S_IFLNK; /* For files, the only change we care about is the executable bit. */ return mode & S_IXUSR; } const struct got_error * got_object_tree_path_changed(int *changed, struct got_tree_object *tree01, struct got_tree_object *tree02, const char *path, struct got_repository *repo) { const struct got_error *err = NULL; struct got_tree_object *tree1 = NULL, *tree2 = NULL; struct got_tree_entry *te1 = NULL, *te2 = NULL; const char *seg, *s; size_t seglen; *changed = 0; /* We not do support comparing the root path. */ if (got_path_is_root_dir(path)) return got_error_path(path, GOT_ERR_BAD_PATH); tree1 = tree01; tree2 = tree02; s = path; while (*s == '/') s++; seg = s; seglen = 0; while (*s) { struct got_tree_object *next_tree1, *next_tree2; mode_t mode1, mode2; if (*s != '/') { s++; seglen++; if (*s) continue; } te1 = find_entry_by_name(tree1, seg, seglen); if (te1 == NULL) { err = got_error(GOT_ERR_NO_OBJ); goto done; } if (tree2) te2 = find_entry_by_name(tree2, seg, seglen); if (te2) { mode1 = normalize_mode_for_comparison(te1->mode); mode2 = normalize_mode_for_comparison(te2->mode); if (mode1 != mode2) { *changed = 1; goto done; } if (got_object_id_cmp(&te1->id, &te2->id) == 0) { *changed = 0; goto done; } } if (*s == '\0') { /* final path element */ *changed = 1; goto done; } seg = s + 1; s++; seglen = 0; if (*s) { err = got_object_open_as_tree(&next_tree1, repo, &te1->id); te1 = NULL; if (err) goto done; if (tree1 != tree01) got_object_tree_close(tree1); tree1 = next_tree1; if (te2) { err = got_object_open_as_tree(&next_tree2, repo, &te2->id); te2 = NULL; if (err) goto done; if (tree2 != tree02) got_object_tree_close(tree2); tree2 = next_tree2; } else if (tree2) { if (tree2 != tree02) got_object_tree_close(tree2); tree2 = NULL; } } } done: if (tree1 && tree1 != tree01) got_object_tree_close(tree1); if (tree2 && tree2 != tree02) got_object_tree_close(tree2); return err; } const struct got_error * got_object_tree_entry_dup(struct got_tree_entry **new_te, struct got_tree_entry *te) { const struct got_error *err = NULL; *new_te = calloc(1, sizeof(**new_te)); if (*new_te == NULL) return got_error_from_errno("calloc"); (*new_te)->mode = te->mode; memcpy((*new_te)->name, te->name, sizeof((*new_te)->name)); memcpy(&(*new_te)->id, &te->id, sizeof((*new_te)->id)); return err; } int got_object_tree_entry_is_submodule(struct got_tree_entry *te) { return (te->mode & S_IFMT) == (S_IFDIR | S_IFLNK); } int got_object_tree_entry_is_symlink(struct got_tree_entry *te) { /* S_IFDIR check avoids confusing symlinks with submodules. */ return ((te->mode & (S_IFDIR | S_IFLNK)) == S_IFLNK); } static const struct got_error * resolve_symlink(char **link_target, const char *path, struct got_commit_object *commit, struct got_repository *repo) { const struct got_error *err = NULL; char buf[PATH_MAX]; char *name, *parent_path = NULL; struct got_object_id *tree_obj_id = NULL; struct got_tree_object *tree = NULL; struct got_tree_entry *te = NULL; *link_target = NULL; if (strlcpy(buf, path, sizeof(buf)) >= sizeof(buf)) return got_error(GOT_ERR_NO_SPACE); name = basename(buf); if (name == NULL) return got_error_from_errno2("basename", path); err = got_path_dirname(&parent_path, path); if (err) return err; err = got_object_id_by_path(&tree_obj_id, repo, commit, parent_path); if (err) { if (err->code == GOT_ERR_NO_TREE_ENTRY) { /* Display the complete path in error message. */ err = got_error_path(path, err->code); } goto done; } err = got_object_open_as_tree(&tree, repo, tree_obj_id); if (err) goto done; te = got_object_tree_find_entry(tree, name); if (te == NULL) { err = got_error_path(path, GOT_ERR_NO_TREE_ENTRY); goto done; } if (got_object_tree_entry_is_symlink(te)) { err = got_tree_entry_get_symlink_target(link_target, te, repo); if (err) goto done; if (!got_path_is_absolute(*link_target)) { char *abspath; if (asprintf(&abspath, "%s/%s", parent_path, *link_target) == -1) { err = got_error_from_errno("asprintf"); goto done; } free(*link_target); *link_target = malloc(PATH_MAX); if (*link_target == NULL) { err = got_error_from_errno("malloc"); goto done; } err = got_canonpath(abspath, *link_target, PATH_MAX); free(abspath); if (err) goto done; } } done: free(parent_path); free(tree_obj_id); if (tree) got_object_tree_close(tree); if (err) { free(*link_target); *link_target = NULL; } return err; } const struct got_error * got_object_resolve_symlinks(char **link_target, const char *path, struct got_commit_object *commit, struct got_repository *repo) { const struct got_error *err = NULL; char *next_target = NULL; int max_recursion = 40; /* matches Git */ *link_target = NULL; do { err = resolve_symlink(&next_target, *link_target ? *link_target : path, commit, repo); if (err) break; if (next_target) { free(*link_target); if (--max_recursion == 0) { err = got_error_path(path, GOT_ERR_RECURSION); *link_target = NULL; break; } *link_target = next_target; } } while (next_target); return err; } void got_object_commit_retain(struct got_commit_object *commit) { commit->refcnt++; } const struct got_error * got_object_raw_alloc(struct got_raw_object **obj, uint8_t *outbuf, int *outfd, size_t max_in_mem_size, size_t hdrlen, off_t size) { const struct got_error *err = NULL; off_t tot; tot = hdrlen + size; *obj = calloc(1, sizeof(**obj)); if (*obj == NULL) { err = got_error_from_errno("calloc"); goto done; } (*obj)->fd = -1; (*obj)->tempfile_idx = -1; if (outbuf) { (*obj)->data = outbuf; } else { struct stat sb; if (fstat(*outfd, &sb) == -1) { err = got_error_from_errno("fstat"); goto done; } if (sb.st_size != tot) { err = got_error_msg(GOT_ERR_BAD_OBJ_HDR, "raw object has unexpected size"); goto done; } #ifndef GOT_PACK_NO_MMAP if (tot > 0 && tot <= max_in_mem_size) { (*obj)->data = mmap(NULL, tot, PROT_READ, MAP_PRIVATE, *outfd, 0); if ((*obj)->data == MAP_FAILED) { if (errno != ENOMEM) { err = got_error_from_errno("mmap"); goto done; } (*obj)->data = NULL; } else { (*obj)->fd = *outfd; *outfd = -1; } } #endif if (*outfd != -1) { (*obj)->f = fdopen(*outfd, "r"); if ((*obj)->f == NULL) { err = got_error_from_errno("fdopen"); goto done; } *outfd = -1; } } (*obj)->hdrlen = hdrlen; (*obj)->size = size; done: if (err) { if (*obj) { got_object_raw_close(*obj); *obj = NULL; } } else (*obj)->refcnt++; return err; } got-portable-0.111/lib/got_lib_inflate.h0000644000175000017500000000534715001740614013643 /* * Copyright (c) 2018 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ struct got_inflate_checksum { /* If not NULL, mix input bytes into this CRC checksum. */ uint32_t *input_crc; /* if not NULL, mix input bytes into this hash context. */ struct got_hash *input_ctx; /* If not NULL, mix output bytes into this CRC checksum. */ uint32_t *output_crc; /* If not NULL, mix output bytes into this hash context. */ struct got_hash *output_ctx; }; struct got_inflate_buf { z_stream z; uint8_t *inbuf; size_t inlen; uint8_t *outbuf; size_t outlen; int flags; #define GOT_INFLATE_F_HAVE_MORE 0x01 #define GOT_INFLATE_F_OWN_OUTBUF 0x02 struct got_inflate_checksum *csum; }; #define GOT_INFLATE_BUFSIZE 32768 const struct got_error *got_inflate_init(struct got_inflate_buf *, uint8_t *, size_t, struct got_inflate_checksum *); const struct got_error *got_inflate_read(struct got_inflate_buf *, FILE *, size_t *, size_t *); const struct got_error *got_inflate_read_fd(struct got_inflate_buf *, int, size_t *, size_t *); const struct got_error *got_inflate_read_mmap(struct got_inflate_buf *, uint8_t *, size_t, size_t, size_t *, size_t *); void got_inflate_end(struct got_inflate_buf *); const struct got_error *got_inflate_to_mem(uint8_t **, size_t *, size_t *, struct got_inflate_checksum *, FILE *); const struct got_error *got_inflate_to_mem_fd(uint8_t **, size_t *, size_t *, struct got_inflate_checksum *, size_t, int); const struct got_error *got_inflate_to_mem_mmap(uint8_t **, size_t *, size_t *, struct got_inflate_checksum *, uint8_t *, size_t, size_t); const struct got_error *got_inflate_to_file(size_t *, FILE *, struct got_inflate_checksum *, FILE *); const struct got_error *got_inflate_to_file_fd(size_t *, size_t *, struct got_inflate_checksum *, int, FILE *); const struct got_error *got_inflate_to_fd(size_t *, FILE *, struct got_inflate_checksum *, int); const struct got_error *got_inflate_to_file_mmap(size_t *, size_t *, struct got_inflate_checksum *, uint8_t *, size_t, size_t, FILE *); got-portable-0.111/lib/object_parse.c0000644000175000017500000006313715001741021013147 /* * Copyright (c) 2018, 2019, 2020 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_object.h" #include "got_repository.h" #include "got_opentemp.h" #include "got_path.h" #include "got_lib_hash.h" #include "got_lib_delta.h" #include "got_lib_inflate.h" #include "got_lib_object.h" #include "got_lib_object_parse.h" #include "got_lib_object_qid.h" #include "got_lib_object_cache.h" #include "got_lib_pack.h" #include "got_lib_repository.h" #ifndef nitems #define nitems(_a) (sizeof(_a) / sizeof((_a)[0])) #endif const struct got_error * got_object_type_label(const char **label, int obj_type) { const struct got_error *err = NULL; switch (obj_type) { case GOT_OBJ_TYPE_BLOB: *label = GOT_OBJ_LABEL_BLOB; break; case GOT_OBJ_TYPE_TREE: *label = GOT_OBJ_LABEL_TREE; break; case GOT_OBJ_TYPE_COMMIT: *label = GOT_OBJ_LABEL_COMMIT; break; case GOT_OBJ_TYPE_TAG: *label = GOT_OBJ_LABEL_TAG; break; default: *label = NULL; err = got_error(GOT_ERR_OBJ_TYPE); break; } return err; } void got_object_close(struct got_object *obj) { if (obj->refcnt > 0) { obj->refcnt--; if (obj->refcnt > 0) return; } if (obj->flags & GOT_OBJ_FLAG_DELTIFIED) { struct got_delta *delta; while (!STAILQ_EMPTY(&obj->deltas.entries)) { delta = STAILQ_FIRST(&obj->deltas.entries); STAILQ_REMOVE_HEAD(&obj->deltas.entries, entry); free(delta); } } free(obj); } const struct got_error * got_object_raw_close(struct got_raw_object *obj) { const struct got_error *err = NULL; if (obj->refcnt > 0) { obj->refcnt--; if (obj->refcnt > 0) return NULL; } if (obj->close_cb) obj->close_cb(obj); if (obj->f == NULL) { if (obj->fd != -1) { if (munmap(obj->data, obj->hdrlen + obj->size) == -1) err = got_error_from_errno("munmap"); if (close(obj->fd) == -1 && err == NULL) err = got_error_from_errno("close"); } else free(obj->data); } else { if (fclose(obj->f) == EOF && err == NULL) err = got_error_from_errno("fclose"); } free(obj); return err; } const struct got_error * got_object_parse_header(struct got_object **obj, char *buf, size_t len) { const char *obj_labels[] = { GOT_OBJ_LABEL_COMMIT, GOT_OBJ_LABEL_TREE, GOT_OBJ_LABEL_BLOB, GOT_OBJ_LABEL_TAG, }; const int obj_types[] = { GOT_OBJ_TYPE_COMMIT, GOT_OBJ_TYPE_TREE, GOT_OBJ_TYPE_BLOB, GOT_OBJ_TYPE_TAG, }; int type = 0; size_t size = 0; size_t i; char *end; *obj = NULL; end = memchr(buf, '\0', len); if (end == NULL) return got_error(GOT_ERR_BAD_OBJ_HDR); for (i = 0; i < nitems(obj_labels); i++) { const char *label = obj_labels[i]; size_t label_len = strlen(label); const char *errstr; if (len <= label_len || buf + label_len >= end || strncmp(buf, label, label_len) != 0) continue; type = obj_types[i]; size = strtonum(buf + label_len, 0, LONG_MAX, &errstr); if (errstr != NULL) return got_error(GOT_ERR_BAD_OBJ_HDR); break; } if (type == 0) return got_error(GOT_ERR_BAD_OBJ_HDR); *obj = calloc(1, sizeof(**obj)); if (*obj == NULL) return got_error_from_errno("calloc"); (*obj)->type = type; (*obj)->hdrlen = end - buf + 1; (*obj)->size = size; return NULL; } const struct got_error * got_object_read_header(struct got_object **obj, int fd) { const struct got_error *err; struct got_inflate_buf zb; uint8_t *buf; const size_t zbsize = 64; size_t outlen, totlen; int nbuf = 1; *obj = NULL; buf = malloc(zbsize); if (buf == NULL) return got_error_from_errno("malloc"); buf[0] = '\0'; err = got_inflate_init(&zb, buf, zbsize, NULL); if (err) return err; totlen = 0; do { err = got_inflate_read_fd(&zb, fd, &outlen, NULL); if (err) goto done; if (outlen == 0) break; totlen += outlen; if (memchr(zb.outbuf, '\0', outlen) == NULL) { uint8_t *newbuf; nbuf++; newbuf = recallocarray(buf, nbuf - 1, nbuf, zbsize); if (newbuf == NULL) { err = got_error_from_errno("recallocarray"); goto done; } buf = newbuf; zb.outbuf = newbuf + totlen; zb.outlen = (nbuf * zbsize) - totlen; } } while (memchr(zb.outbuf, '\0', outlen) == NULL); err = got_object_parse_header(obj, buf, totlen); done: free(buf); got_inflate_end(&zb); return err; } const struct got_error * got_object_read_raw(uint8_t **outbuf, off_t *size, size_t *hdrlen, size_t max_in_mem_size, int outfd, struct got_object_id *expected_id, int infd) { const struct got_error *err = NULL; struct got_object *obj; struct got_inflate_checksum csum; struct got_object_id id; struct got_hash ctx; size_t len, consumed; FILE *f = NULL; *outbuf = NULL; *size = 0; *hdrlen = 0; got_hash_init(&ctx, expected_id->algo); memset(&csum, 0, sizeof(csum)); csum.output_ctx = &ctx; if (lseek(infd, SEEK_SET, 0) == -1) return got_error_from_errno("lseek"); err = got_object_read_header(&obj, infd); if (err) return err; if (lseek(infd, SEEK_SET, 0) == -1) return got_error_from_errno("lseek"); if (obj->size + obj->hdrlen <= max_in_mem_size) { err = got_inflate_to_mem_fd(outbuf, &len, &consumed, &csum, obj->size + obj->hdrlen, infd); } else { int fd; /* * XXX This uses an extra file descriptor for no good reason. * We should have got_inflate_fd_to_fd(). */ fd = dup(infd); if (fd == -1) return got_error_from_errno("dup"); f = fdopen(fd, "r"); if (f == NULL) { err = got_error_from_errno("fdopen"); close(fd); goto done; } err = got_inflate_to_fd(&len, f, &csum, outfd); } if (err) goto done; if (len < obj->hdrlen || len != obj->hdrlen + obj->size) { err = got_error(GOT_ERR_BAD_OBJ_HDR); goto done; } got_hash_final_object_id(&ctx, &id); if (got_object_id_cmp(expected_id, &id) != 0) { err = got_error_checksum(expected_id); goto done; } *size = obj->size; *hdrlen = obj->hdrlen; done: got_object_close(obj); if (f && fclose(f) == EOF && err == NULL) err = got_error_from_errno("fclose"); return err; } struct got_commit_object * got_object_commit_alloc_partial(void) { struct got_commit_object *commit; commit = calloc(1, sizeof(*commit)); if (commit == NULL) return NULL; commit->tree_id = malloc(sizeof(*commit->tree_id)); if (commit->tree_id == NULL) { free(commit); return NULL; } STAILQ_INIT(&commit->parent_ids); return commit; } const struct got_error * got_object_commit_add_parent(struct got_commit_object *commit, const char *id_str, enum got_hash_algorithm algo) { const struct got_error *err = NULL; struct got_object_qid *qid; err = got_object_qid_alloc_partial(&qid); if (err) return err; if (!got_parse_object_id(&qid->id, id_str, algo)) { err = got_error(GOT_ERR_BAD_OBJ_DATA); got_object_qid_free(qid); return err; } STAILQ_INSERT_TAIL(&commit->parent_ids, qid, entry); commit->nparents++; return NULL; } static const struct got_error * parse_gmtoff(time_t *gmtoff, const char *tzstr) { int sign = 1; const char *p = tzstr; time_t h, m; *gmtoff = 0; if (*p == '-') sign = -1; else if (*p != '+') return got_error(GOT_ERR_BAD_OBJ_DATA); p++; if (!isdigit((unsigned char)*p) && !isdigit((unsigned char)*(p + 1))) return got_error(GOT_ERR_BAD_OBJ_DATA); h = (((*p - '0') * 10) + (*(p + 1) - '0')); p += 2; if (!isdigit((unsigned char)*p) && !isdigit((unsigned char)*(p + 1))) return got_error(GOT_ERR_BAD_OBJ_DATA); m = ((*p - '0') * 10) + (*(p + 1) - '0'); *gmtoff = (h * 60 * 60 + m * 60) * sign; return NULL; } static const struct got_error * parse_commit_time(time_t *time, time_t *gmtoff, char *committer) { const struct got_error *err = NULL; const char *errstr; char *space, *tzstr; /* Parse and strip off trailing timezone indicator string. */ space = strrchr(committer, ' '); if (space == NULL) return got_error(GOT_ERR_BAD_OBJ_DATA); tzstr = strdup(space + 1); if (tzstr == NULL) return got_error_from_errno("strdup"); err = parse_gmtoff(gmtoff, tzstr); free(tzstr); if (err) { if (err->code != GOT_ERR_BAD_OBJ_DATA) return err; /* Old versions of Git omitted the timestamp. */ *time = 0; *gmtoff = 0; return NULL; } *space = '\0'; /* Timestamp is separated from committer name + email by space. */ space = strrchr(committer, ' '); if (space == NULL) return got_error(GOT_ERR_BAD_OBJ_DATA); /* Timestamp parsed here is expressed as UNIX timestamp (UTC). */ *time = strtonum(space + 1, 0, INT64_MAX, &errstr); if (errstr) return got_error(GOT_ERR_BAD_OBJ_DATA); /* Strip off parsed time information, leaving just author and email. */ *space = '\0'; return NULL; } void got_object_commit_close(struct got_commit_object *commit) { if (commit->refcnt > 0) { commit->refcnt--; if (commit->refcnt > 0) return; } got_object_id_queue_free(&commit->parent_ids); free(commit->tree_id); free(commit->author); free(commit->committer); free(commit->logmsg); free(commit); } struct got_object_id * got_object_commit_get_tree_id(struct got_commit_object *commit) { return commit->tree_id; } int got_object_commit_get_nparents(struct got_commit_object *commit) { return commit->nparents; } const struct got_object_id_queue * got_object_commit_get_parent_ids(struct got_commit_object *commit) { return &commit->parent_ids; } const char * got_object_commit_get_author(struct got_commit_object *commit) { return commit->author; } time_t got_object_commit_get_author_time(struct got_commit_object *commit) { return commit->author_time; } time_t got_object_commit_get_author_gmtoff(struct got_commit_object *commit) { return commit->author_gmtoff; } const char * got_object_commit_get_committer(struct got_commit_object *commit) { return commit->committer; } time_t got_object_commit_get_committer_time(struct got_commit_object *commit) { return commit->committer_time; } time_t got_object_commit_get_committer_gmtoff(struct got_commit_object *commit) { return commit->committer_gmtoff; } const struct got_error * got_object_commit_get_logmsg(char **logmsg, struct got_commit_object *commit) { const struct got_error *err = NULL; const char *src; char *dst; size_t len; len = strlen(commit->logmsg); *logmsg = malloc(len + 2); /* leave room for a trailing \n and \0 */ if (*logmsg == NULL) return got_error_from_errno("malloc"); /* * Strip out unusual headers. Headers are separated from the commit * message body by a single empty line. */ src = commit->logmsg; dst = *logmsg; while (*src != '\0' && *src != '\n') { int copy_header = 1, eol = 0; if (strncmp(src, GOT_COMMIT_LABEL_TREE, strlen(GOT_COMMIT_LABEL_TREE)) != 0 && strncmp(src, GOT_COMMIT_LABEL_AUTHOR, strlen(GOT_COMMIT_LABEL_AUTHOR)) != 0 && strncmp(src, GOT_COMMIT_LABEL_PARENT, strlen(GOT_COMMIT_LABEL_PARENT)) != 0 && strncmp(src, GOT_COMMIT_LABEL_COMMITTER, strlen(GOT_COMMIT_LABEL_COMMITTER)) != 0) copy_header = 0; while (*src != '\0' && !eol) { if (copy_header) { *dst = *src; dst++; } if (*src == '\n') eol = 1; src++; } } *dst = '\0'; if (strlcat(*logmsg, src, len + 1) >= len + 1) { err = got_error(GOT_ERR_NO_SPACE); goto done; } /* Trim redundant trailing whitespace. */ len = strlen(*logmsg); while (len > 1 && isspace((unsigned char)(*logmsg)[len - 2]) && isspace((unsigned char)(*logmsg)[len - 1])) { (*logmsg)[len - 1] = '\0'; len--; } /* Append a trailing newline if missing. */ if (len > 0 && (*logmsg)[len - 1] != '\n') { (*logmsg)[len] = '\n'; (*logmsg)[len + 1] = '\0'; } done: if (err) { free(*logmsg); *logmsg = NULL; } return err; } const char * got_object_commit_get_logmsg_raw(struct got_commit_object *commit) { return commit->logmsg; } const struct got_error * got_object_parse_commit(struct got_commit_object **commit, char *buf, size_t len, enum got_hash_algorithm algo) { const struct got_error *err = NULL; char *s = buf; size_t label_len, digest_string_len; ssize_t remain = (ssize_t)len; digest_string_len = got_hash_digest_string_length(algo); if (remain == 0) return got_error(GOT_ERR_BAD_OBJ_DATA); *commit = got_object_commit_alloc_partial(); if (*commit == NULL) return got_error_from_errno("got_object_commit_alloc_partial"); label_len = strlen(GOT_COMMIT_LABEL_TREE); if (strncmp(s, GOT_COMMIT_LABEL_TREE, label_len) == 0) { remain -= label_len; if (remain < digest_string_len) { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } s += label_len; if (!got_parse_object_id((*commit)->tree_id, s, algo)) { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } remain -= digest_string_len; s += digest_string_len; } else { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } label_len = strlen(GOT_COMMIT_LABEL_PARENT); while (strncmp(s, GOT_COMMIT_LABEL_PARENT, label_len) == 0) { remain -= label_len; if (remain < digest_string_len) { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } s += label_len; err = got_object_commit_add_parent(*commit, s, algo); if (err) goto done; remain -= digest_string_len; s += digest_string_len; } label_len = strlen(GOT_COMMIT_LABEL_AUTHOR); if (strncmp(s, GOT_COMMIT_LABEL_AUTHOR, label_len) == 0) { char *p; size_t slen; remain -= label_len; if (remain <= 0) { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } s += label_len; p = memchr(s, '\n', remain); if (p == NULL) { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } *p = '\0'; slen = strlen(s); err = parse_commit_time(&(*commit)->author_time, &(*commit)->author_gmtoff, s); if (err) goto done; (*commit)->author = strdup(s); if ((*commit)->author == NULL) { err = got_error_from_errno("strdup"); goto done; } s += slen + 1; remain -= slen + 1; } label_len = strlen(GOT_COMMIT_LABEL_COMMITTER); if (strncmp(s, GOT_COMMIT_LABEL_COMMITTER, label_len) == 0) { char *p; size_t slen; remain -= label_len; if (remain <= 0) { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } s += label_len; p = memchr(s, '\n', remain); if (p == NULL) { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } *p = '\0'; slen = strlen(s); err = parse_commit_time(&(*commit)->committer_time, &(*commit)->committer_gmtoff, s); if (err) goto done; (*commit)->committer = strdup(s); if ((*commit)->committer == NULL) { err = got_error_from_errno("strdup"); goto done; } s += slen + 1; remain -= slen + 1; } (*commit)->logmsg = strndup(s, remain); if ((*commit)->logmsg == NULL) { err = got_error_from_errno("strndup"); goto done; } done: if (err) { got_object_commit_close(*commit); *commit = NULL; } return err; } const struct got_error * got_object_read_commit(struct got_commit_object **commit, int fd, struct got_object_id *expected_id, size_t expected_size) { struct got_object *obj = NULL; const struct got_error *err = NULL; size_t len; uint8_t *p; struct got_inflate_checksum csum; struct got_hash ctx; struct got_object_id id; got_hash_init(&ctx, expected_id->algo); memset(&csum, 0, sizeof(csum)); csum.output_ctx = &ctx; err = got_inflate_to_mem_fd(&p, &len, NULL, &csum, expected_size, fd); if (err) return err; got_hash_final_object_id(&ctx, &id); if (got_object_id_cmp(expected_id, &id) != 0) { err = got_error_checksum(expected_id); goto done; } err = got_object_parse_header(&obj, p, len); if (err) goto done; if (len < obj->hdrlen + obj->size) { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } if (obj->type != GOT_OBJ_TYPE_COMMIT) { err = got_error(GOT_ERR_OBJ_TYPE); goto done; } /* Skip object header. */ len -= obj->hdrlen; err = got_object_parse_commit(commit, p + obj->hdrlen, len, expected_id->algo); done: free(p); if (obj) got_object_close(obj); return err; } void got_object_tree_close(struct got_tree_object *tree) { if (tree->refcnt > 0) { tree->refcnt--; if (tree->refcnt > 0) return; } free(tree->entries); free(tree); } const struct got_error * got_object_parse_tree_entry(struct got_parsed_tree_entry *pte, size_t *elen, char *buf, size_t maxlen, size_t digest_len, enum got_hash_algorithm algo) { char *p, *space; *elen = 0; *elen = strnlen(buf, maxlen) + 1; if (*elen > maxlen) return got_error(GOT_ERR_BAD_OBJ_DATA); space = memchr(buf, ' ', *elen); if (space == NULL || space <= buf) return got_error(GOT_ERR_BAD_OBJ_DATA); pte->mode = 0; p = buf; while (p < space) { if (*p < '0' || *p > '7') return got_error(GOT_ERR_BAD_OBJ_DATA); pte->mode <<= 3; pte->mode |= *p - '0'; p++; } if (*elen > maxlen || maxlen - *elen < digest_len) return got_error(GOT_ERR_BAD_OBJ_DATA); pte->name = space + 1; pte->namelen = strlen(pte->name); buf += *elen; pte->id = buf; pte->digest_len = digest_len; pte->algo = algo; *elen += digest_len; return NULL; } static int pte_cmp(const void *pa, const void *pb) { const struct got_parsed_tree_entry *a = pa, *b = pb; return got_path_cmp(a->name, b->name, a->namelen, b->namelen); } const struct got_error * got_object_parse_tree(struct got_parsed_tree_entry **entries, size_t *nentries, size_t *nentries_alloc, uint8_t *buf, size_t len, enum got_hash_algorithm algo) { const struct got_error *err = NULL; size_t digest_len, remain = len; const size_t nalloc = 16; struct got_parsed_tree_entry *pte; int i; digest_len = got_hash_digest_length(algo); *nentries = 0; if (remain == 0) return NULL; /* tree is empty */ while (remain > 0) { size_t elen; if (*nentries >= *nentries_alloc) { pte = recallocarray(*entries, *nentries_alloc, *nentries_alloc + nalloc, sizeof(**entries)); if (pte == NULL) { err = got_error_from_errno("recallocarray"); goto done; } *entries = pte; *nentries_alloc += nalloc; } pte = &(*entries)[*nentries]; err = got_object_parse_tree_entry(pte, &elen, buf, remain, digest_len, algo); if (err) goto done; buf += elen; remain -= elen; (*nentries)++; } if (remain != 0) { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } if (*nentries > 1) { mergesort(*entries, *nentries, sizeof(**entries), pte_cmp); for (i = 0; i < *nentries - 1; i++) { struct got_parsed_tree_entry *prev = &(*entries)[i]; pte = &(*entries)[i + 1]; if (got_path_cmp(prev->name, pte->name, prev->namelen, pte->namelen) == 0) { err = got_error(GOT_ERR_TREE_DUP_ENTRY); break; } } } done: if (err) *nentries = 0; return err; } const struct got_error * got_object_read_tree(struct got_parsed_tree_entry **entries, size_t *nentries, size_t *nentries_alloc, uint8_t **p, int fd, struct got_object_id *expected_id) { const struct got_error *err = NULL; struct got_object *obj = NULL; size_t len; struct got_inflate_checksum csum; struct got_hash ctx; struct got_object_id id; got_hash_init(&ctx, expected_id->algo); memset(&csum, 0, sizeof(csum)); csum.output_ctx = &ctx; err = got_inflate_to_mem_fd(p, &len, NULL, &csum, 0, fd); if (err) return err; got_hash_final_object_id(&ctx, &id); if (got_object_id_cmp(expected_id, &id) != 0) { err = got_error_checksum(expected_id); goto done; } err = got_object_parse_header(&obj, *p, len); if (err) goto done; if (len < obj->hdrlen + obj->size) { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } /* Skip object header. */ len -= obj->hdrlen; err = got_object_parse_tree(entries, nentries, nentries_alloc, *p + obj->hdrlen, len, expected_id->algo); done: if (obj) got_object_close(obj); return err; } void got_object_tag_close(struct got_tag_object *tag) { if (tag->refcnt > 0) { tag->refcnt--; if (tag->refcnt > 0) return; } free(tag->tag); free(tag->tagger); free(tag->tagmsg); free(tag); } const struct got_error * got_object_parse_tag(struct got_tag_object **tag, uint8_t *buf, size_t len, enum got_hash_algorithm algo) { const struct got_error *err = NULL; size_t remain = len; char *s = buf; size_t label_len, digest_string_len; digest_string_len = got_hash_digest_string_length(algo); if (remain == 0) return got_error(GOT_ERR_BAD_OBJ_DATA); *tag = calloc(1, sizeof(**tag)); if (*tag == NULL) return got_error_from_errno("calloc"); label_len = strlen(GOT_TAG_LABEL_OBJECT); if (strncmp(s, GOT_TAG_LABEL_OBJECT, label_len) == 0) { remain -= label_len; if (remain < digest_string_len) { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } s += label_len; if (!got_parse_object_id(&(*tag)->id, s, algo)) { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } remain -= digest_string_len; s += digest_string_len; } else { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } if (remain <= 0) { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } label_len = strlen(GOT_TAG_LABEL_TYPE); if (strncmp(s, GOT_TAG_LABEL_TYPE, label_len) == 0) { remain -= label_len; if (remain <= 0) { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } s += label_len; if (strncmp(s, GOT_OBJ_LABEL_COMMIT, strlen(GOT_OBJ_LABEL_COMMIT)) == 0) { (*tag)->obj_type = GOT_OBJ_TYPE_COMMIT; label_len = strlen(GOT_OBJ_LABEL_COMMIT); s += label_len; remain -= label_len; } else if (strncmp(s, GOT_OBJ_LABEL_TREE, strlen(GOT_OBJ_LABEL_TREE)) == 0) { (*tag)->obj_type = GOT_OBJ_TYPE_TREE; label_len = strlen(GOT_OBJ_LABEL_TREE); s += label_len; remain -= label_len; } else if (strncmp(s, GOT_OBJ_LABEL_BLOB, strlen(GOT_OBJ_LABEL_BLOB)) == 0) { (*tag)->obj_type = GOT_OBJ_TYPE_BLOB; label_len = strlen(GOT_OBJ_LABEL_BLOB); s += label_len; remain -= label_len; } else if (strncmp(s, GOT_OBJ_LABEL_TAG, strlen(GOT_OBJ_LABEL_TAG)) == 0) { (*tag)->obj_type = GOT_OBJ_TYPE_TAG; label_len = strlen(GOT_OBJ_LABEL_TAG); s += label_len; remain -= label_len; } else { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } if (remain <= 0 || *s != '\n') { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } s++; remain--; if (remain <= 0) { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } } else { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } label_len = strlen(GOT_TAG_LABEL_TAG); if (strncmp(s, GOT_TAG_LABEL_TAG, label_len) == 0) { char *p; size_t slen; remain -= label_len; if (remain <= 0) { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } s += label_len; p = memchr(s, '\n', remain); if (p == NULL) { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } *p = '\0'; slen = strlen(s); (*tag)->tag = strndup(s, slen); if ((*tag)->tag == NULL) { err = got_error_from_errno("strndup"); goto done; } s += slen + 1; remain -= slen + 1; if (remain <= 0) { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } } else { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } label_len = strlen(GOT_TAG_LABEL_TAGGER); if (strncmp(s, GOT_TAG_LABEL_TAGGER, label_len) == 0) { char *p; size_t slen; remain -= label_len; if (remain <= 0) { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } s += label_len; p = memchr(s, '\n', remain); if (p == NULL) { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } *p = '\0'; slen = strlen(s); err = parse_commit_time(&(*tag)->tagger_time, &(*tag)->tagger_gmtoff, s); if (err) goto done; (*tag)->tagger = strdup(s); if ((*tag)->tagger == NULL) { err = got_error_from_errno("strdup"); goto done; } s += slen + 1; remain -= slen + 1; if (remain < 0) { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } } else { /* Some old tags in the Linux git repo have no tagger. */ (*tag)->tagger = strdup(""); if ((*tag)->tagger == NULL) { err = got_error_from_errno("strdup"); goto done; } } (*tag)->tagmsg = strndup(s, remain); if ((*tag)->tagmsg == NULL) { err = got_error_from_errno("strndup"); goto done; } done: if (err) { got_object_tag_close(*tag); *tag = NULL; } return err; } const struct got_error * got_object_read_tag(struct got_tag_object **tag, int fd, struct got_object_id *expected_id, size_t expected_size) { const struct got_error *err = NULL; struct got_object *obj = NULL; size_t len; uint8_t *p; struct got_inflate_checksum csum; struct got_hash ctx; struct got_object_id id; got_hash_init(&ctx, expected_id->algo); memset(&csum, 0, sizeof(csum)); csum.output_ctx = &ctx; err = got_inflate_to_mem_fd(&p, &len, NULL, &csum, expected_size, fd); if (err) return err; got_hash_final_object_id(&ctx, &id); if (got_object_id_cmp(expected_id, &id) != 0) { err = got_error_checksum(expected_id); goto done; } err = got_object_parse_header(&obj, p, len); if (err) goto done; if (len < obj->hdrlen + obj->size) { err = got_error(GOT_ERR_BAD_OBJ_DATA); goto done; } /* Skip object header. */ len -= obj->hdrlen; err = got_object_parse_tag(tag, p + obj->hdrlen, len, expected_id->algo); done: free(p); if (obj) got_object_close(obj); return err; } got-portable-0.111/lib/worktree_open.c0000644000175000017500000002162315001741021013364 /* * Copyright (c) 2018, 2019, 2020 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include "got_cancel.h" #include "got_error.h" #include "got_reference.h" #include "got_path.h" #include "got_worktree.h" #include "got_repository.h" #include "got_gotconfig.h" #include "got_object.h" #include "got_lib_worktree.h" #include "got_lib_gotconfig.h" #ifndef nitems #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) #endif static const struct got_error * read_meta_file(char **content, const char *path_got, const char *name) { const struct got_error *err = NULL; char *path; int fd = -1; ssize_t n; struct stat sb; *content = NULL; if (asprintf(&path, "%s/%s", path_got, name) == -1) { err = got_error_from_errno("asprintf"); path = NULL; goto done; } fd = open(path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (fd == -1) { if (errno == ENOENT) err = got_error_path(path, GOT_ERR_WORKTREE_META); else err = got_error_from_errno2("open", path); goto done; } if (flock(fd, LOCK_SH | LOCK_NB) == -1) { err = (errno == EWOULDBLOCK ? got_error(GOT_ERR_WORKTREE_BUSY) : got_error_from_errno2("flock", path)); goto done; } if (fstat(fd, &sb) != 0) { err = got_error_from_errno2("fstat", path); goto done; } if (sb.st_size == 0) { err = got_error_path(path, GOT_ERR_WORKTREE_META); goto done; } *content = calloc(1, sb.st_size); if (*content == NULL) { err = got_error_from_errno("calloc"); goto done; } n = read(fd, *content, sb.st_size); if (n != sb.st_size) { err = (n == -1 ? got_error_from_errno2("read", path) : got_error_path(path, GOT_ERR_WORKTREE_META)); goto done; } if ((*content)[sb.st_size - 1] != '\n') { err = got_error_path(path, GOT_ERR_WORKTREE_META); goto done; } (*content)[sb.st_size - 1] = '\0'; done: if (fd != -1 && close(fd) == -1 && err == NULL) err = got_error_from_errno2("close", path_got); free(path); if (err) { free(*content); *content = NULL; } return err; } static const struct got_error * open_worktree(struct got_worktree **worktree, const char *path, const char *meta_dir) { const struct got_error *err = NULL; char *path_meta; char *formatstr = NULL; char *uuidstr = NULL; char *path_lock = NULL; char *base_commit_id_str = NULL; int version, fd = -1; const char *errstr; struct got_repository *repo = NULL; int *pack_fds = NULL; uint32_t uuid_status; *worktree = NULL; if (asprintf(&path_meta, "%s/%s", path, meta_dir) == -1) { err = got_error_from_errno("asprintf"); path_meta = NULL; goto done; } if (asprintf(&path_lock, "%s/%s", path_meta, GOT_WORKTREE_LOCK) == -1) { err = got_error_from_errno("asprintf"); path_lock = NULL; goto done; } fd = open(path_lock, O_RDWR | O_EXLOCK | O_NONBLOCK | O_CLOEXEC); if (fd == -1) { err = (errno == EWOULDBLOCK ? got_error(GOT_ERR_WORKTREE_BUSY) : got_error_from_errno2("open", path_lock)); goto done; } err = read_meta_file(&formatstr, path_meta, GOT_WORKTREE_FORMAT); if (err) goto done; version = strtonum(formatstr, 1, INT_MAX, &errstr); if (errstr) { err = got_error_msg(GOT_ERR_WORKTREE_META, "could not parse work tree format version number"); goto done; } if (version != GOT_WORKTREE_FORMAT_VERSION) { err = got_error(GOT_ERR_WORKTREE_VERS); goto done; } *worktree = calloc(1, sizeof(**worktree)); if (*worktree == NULL) { err = got_error_from_errno("calloc"); goto done; } (*worktree)->lockfd = -1; (*worktree)->format_version = version; (*worktree)->root_path = realpath(path, NULL); if ((*worktree)->root_path == NULL) { err = got_error_from_errno2("realpath", path); goto done; } (*worktree)->meta_dir = meta_dir; err = read_meta_file(&(*worktree)->repo_path, path_meta, GOT_WORKTREE_REPOSITORY); if (err) goto done; err = read_meta_file(&(*worktree)->path_prefix, path_meta, GOT_WORKTREE_PATH_PREFIX); if (err) goto done; err = read_meta_file(&base_commit_id_str, path_meta, GOT_WORKTREE_BASE_COMMIT); if (err) goto done; err = read_meta_file(&uuidstr, path_meta, GOT_WORKTREE_UUID); if (err) goto done; uuid_from_string(uuidstr, &(*worktree)->uuid, &uuid_status); if (uuid_status != uuid_s_ok) { err = got_error_uuid(uuid_status, "uuid_from_string"); goto done; } err = got_repo_pack_fds_open(&pack_fds); if (err) goto done; err = got_repo_open(&repo, (*worktree)->repo_path, NULL, pack_fds); if (err) goto done; err = got_object_resolve_id_str(&(*worktree)->base_commit_id, repo, base_commit_id_str); if (err) goto done; err = read_meta_file(&(*worktree)->head_ref_name, path_meta, GOT_WORKTREE_HEAD_REF); if (err) goto done; if (asprintf(&(*worktree)->gotconfig_path, "%s/%s/%s", (*worktree)->root_path, (*worktree)->meta_dir, GOT_GOTCONFIG_FILENAME) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = got_gotconfig_read(&(*worktree)->gotconfig, (*worktree)->gotconfig_path); if (err) goto done; (*worktree)->root_fd = open((*worktree)->root_path, O_DIRECTORY | O_CLOEXEC); if ((*worktree)->root_fd == -1) { err = got_error_from_errno2("open", (*worktree)->root_path); goto done; } done: if (repo) { const struct got_error *close_err = got_repo_close(repo); if (err == NULL) err = close_err; } if (pack_fds) { const struct got_error *pack_err = got_repo_pack_fds_close(pack_fds); if (err == NULL) err = pack_err; } free(path_meta); free(path_lock); free(base_commit_id_str); free(uuidstr); free(formatstr); if (err) { if (fd != -1) close(fd); if (*worktree != NULL) got_worktree_close(*worktree); *worktree = NULL; } else (*worktree)->lockfd = fd; return err; } const struct got_error * got_worktree_open(struct got_worktree **worktree, const char *path, const char *meta_dir) { const struct got_error *err = NULL; char *worktree_path; const char *meta_dirs[] = { GOT_WORKTREE_GOT_DIR, GOT_WORKTREE_CVG_DIR }; int i; worktree_path = strdup(path); if (worktree_path == NULL) return got_error_from_errno("strdup"); for (;;) { char *parent_path; if (meta_dir == NULL) { for (i = 0; i < nitems(meta_dirs); i++) { err = open_worktree(worktree, worktree_path, meta_dirs[i]); if (err == NULL || err->code == GOT_ERR_WORKTREE_BUSY) break; else if (err->code != GOT_ERR_ERRNO || errno != ENOENT) { free(worktree_path); return err; } } } else err = open_worktree(worktree, worktree_path, meta_dir); if (err && !(err->code == GOT_ERR_ERRNO && errno == ENOENT)) { free(worktree_path); return err; } if (*worktree) { free(worktree_path); return NULL; } if (worktree_path[0] == '/' && worktree_path[1] == '\0') break; err = got_path_dirname(&parent_path, worktree_path); if (err) { if (err->code != GOT_ERR_BAD_PATH) { free(worktree_path); return err; } break; } free(worktree_path); worktree_path = parent_path; } free(worktree_path); return got_error(GOT_ERR_NOT_WORKTREE); } const struct got_error * got_worktree_close(struct got_worktree *worktree) { const struct got_error *err = NULL; if (worktree->lockfd != -1) { if (close(worktree->lockfd) == -1) err = got_error_from_errno2("close", got_worktree_get_root_path(worktree)); } if (close(worktree->root_fd) == -1 && err == NULL) err = got_error_from_errno2("close", got_worktree_get_root_path(worktree)); free(worktree->repo_path); free(worktree->path_prefix); free(worktree->base_commit_id); free(worktree->head_ref_name); free(worktree->root_path); free(worktree->gotconfig_path); got_gotconfig_free(worktree->gotconfig); free(worktree); return err; } const char * got_worktree_get_root_path(struct got_worktree *worktree) { return worktree->root_path; } const char * got_worktree_get_repo_path(struct got_worktree *worktree) { return worktree->repo_path; } const char * got_worktree_get_path_prefix(struct got_worktree *worktree) { return worktree->path_prefix; } int got_worktree_get_format_version(struct got_worktree *worktree) { return worktree->format_version; } got-portable-0.111/lib/got_lib_gotconfig.h0000644000175000017500000000216515001740614014173 /* * Copyright (c) 2020 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #define GOT_GOTCONFIG_FILENAME "got.conf" struct got_gotconfig { char *author; int nremotes; struct got_remote_repo *remotes; char *allowed_signers_file; char *revoked_signers_file; char *signer_id; }; const struct got_error *got_gotconfig_read(struct got_gotconfig **, const char *); void got_gotconfig_free(struct got_gotconfig *); got-portable-0.111/lib/deflate.c0000644000175000017500000002561615001741021012113 /* * Copyright (c) 2019 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include "got_error.h" #include "got_object.h" #include "got_path.h" #include "got_lib_deflate.h" #include "got_lib_hash.h" #include "got_lib_poll.h" #ifndef MIN #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b)) #endif static const struct got_error * wrap_deflate_error(int zerr, const char *prefix) { if (zerr == Z_ERRNO) return got_error_from_errno(prefix); if (zerr == Z_MEM_ERROR) return got_error_set_errno(ENOMEM, prefix); return got_error(GOT_ERR_COMPRESSION); } const struct got_error * got_deflate_init(struct got_deflate_buf *zb, uint8_t *outbuf, size_t bufsize) { const struct got_error *err = NULL; int zerr; memset(zb, 0, sizeof(*zb)); zb->z.zalloc = Z_NULL; zb->z.zfree = Z_NULL; zerr = deflateInit(&zb->z, Z_DEFAULT_COMPRESSION); if (zerr != Z_OK) return wrap_deflate_error(zerr, "deflateInit"); zb->inlen = zb->outlen = bufsize; zb->inbuf = calloc(1, zb->inlen); if (zb->inbuf == NULL) { err = got_error_from_errno("calloc"); goto done; } zb->flags = 0; if (outbuf == NULL) { zb->outbuf = calloc(1, zb->outlen); if (zb->outbuf == NULL) { err = got_error_from_errno("calloc"); goto done; } zb->flags |= GOT_DEFLATE_F_OWN_OUTBUF; } else zb->outbuf = outbuf; done: if (err) got_deflate_end(zb); return err; } static void csum_output(struct got_deflate_checksum *csum, const uint8_t *buf, size_t len) { if (csum->output_crc) *csum->output_crc = crc32(*csum->output_crc, buf, len); if (csum->output_ctx) got_hash_update(csum->output_ctx, buf, len); } const struct got_error * got_deflate_read(struct got_deflate_buf *zb, FILE *f, off_t len, size_t *outlenp, off_t *consumed) { size_t last_total_out = zb->z.total_out; z_stream *z = &zb->z; int ret = Z_ERRNO; z->next_out = zb->outbuf; z->avail_out = zb->outlen; *outlenp = 0; *consumed = 0; do { size_t last_total_in = z->total_in; if (z->avail_in == 0) { size_t n = 0; if (*consumed < len) { n = fread(zb->inbuf, 1, MIN(zb->inlen, len - *consumed), f); } if (n == 0) { if (ferror(f)) return got_ferror(f, GOT_ERR_IO); /* EOF */ ret = deflate(z, Z_FINISH); break; } z->next_in = zb->inbuf; z->avail_in = n; } ret = deflate(z, Z_NO_FLUSH); *consumed += z->total_in - last_total_in; } while (ret == Z_OK && z->avail_out > 0); if (ret == Z_OK) { zb->flags |= GOT_DEFLATE_F_HAVE_MORE; } else { if (ret != Z_STREAM_END) return wrap_deflate_error(ret, "deflate"); zb->flags &= ~GOT_DEFLATE_F_HAVE_MORE; } *outlenp = z->total_out - last_total_out; return NULL; } static const struct got_error * deflate_read_mmap(struct got_deflate_buf *zb, uint8_t *map, size_t offset, size_t len, size_t *outlenp, size_t *consumed, int flush_on_eof) { z_stream *z = &zb->z; size_t last_total_out = z->total_out; int ret = Z_ERRNO; z->next_out = zb->outbuf; z->avail_out = zb->outlen; *outlenp = 0; *consumed = 0; do { size_t last_total_in = z->total_in; if (z->avail_in == 0) { z->next_in = map + offset + *consumed; if (len - *consumed > UINT_MAX) z->avail_in = UINT_MAX; else z->avail_in = len - *consumed; if (z->avail_in == 0) { /* EOF */ if (flush_on_eof) ret = deflate(z, Z_FINISH); break; } } ret = deflate(z, Z_NO_FLUSH); *consumed += z->total_in - last_total_in; } while (ret == Z_OK && z->avail_out > 0); if (ret == Z_OK) { zb->flags |= GOT_DEFLATE_F_HAVE_MORE; } else { if (ret != Z_STREAM_END) return wrap_deflate_error(ret, "deflate"); zb->flags &= ~GOT_DEFLATE_F_HAVE_MORE; } *outlenp = z->total_out - last_total_out; return NULL; } const struct got_error * got_deflate_read_mmap(struct got_deflate_buf *zb, uint8_t *map, size_t offset, size_t len, size_t *outlenp, size_t *consumed) { return deflate_read_mmap(zb, map, offset, len, outlenp, consumed, 1); } const struct got_error * got_deflate_flush(struct got_deflate_buf *zb, FILE *outfile, struct got_deflate_checksum *csum, off_t *outlenp) { int ret; size_t n; z_stream *z = &zb->z; if (z->avail_in != 0) return got_error_msg(GOT_ERR_COMPRESSION, "cannot flush zb with pending input data"); do { size_t avail, last_total_out = zb->z.total_out; z->next_out = zb->outbuf; z->avail_out = zb->outlen; ret = deflate(z, Z_FINISH); if (ret != Z_STREAM_END && ret != Z_OK) return wrap_deflate_error(ret, "deflate"); avail = z->total_out - last_total_out; if (avail > 0) { n = fwrite(zb->outbuf, avail, 1, outfile); if (n != 1) return got_ferror(outfile, GOT_ERR_IO); if (csum) csum_output(csum, zb->outbuf, avail); if (outlenp) *outlenp += avail; } } while (ret != Z_STREAM_END); zb->flags &= ~GOT_DEFLATE_F_HAVE_MORE; return NULL; } void got_deflate_end(struct got_deflate_buf *zb) { free(zb->inbuf); if (zb->flags & GOT_DEFLATE_F_OWN_OUTBUF) free(zb->outbuf); deflateEnd(&zb->z); } const struct got_error * got_deflate_to_fd(off_t *outlen, FILE *infile, off_t len, int outfd, struct got_deflate_checksum *csum) { const struct got_error *err; size_t avail; off_t consumed; struct got_deflate_buf zb; err = got_deflate_init(&zb, NULL, GOT_DEFLATE_BUFSIZE); if (err) goto done; *outlen = 0; do { err = got_deflate_read(&zb, infile, len, &avail, &consumed); if (err) goto done; len -= consumed; if (avail > 0) { err = got_poll_write_full(outfd, zb.outbuf, avail); if (err) goto done; if (csum) csum_output(csum, zb.outbuf, avail); *outlen += avail; } } while (zb.flags & GOT_DEFLATE_F_HAVE_MORE); done: got_deflate_end(&zb); return err; } const struct got_error * got_deflate_to_fd_mmap(off_t *outlen, uint8_t *map, size_t offset, size_t len, int outfd, struct got_deflate_checksum *csum) { const struct got_error *err; size_t avail, consumed; struct got_deflate_buf zb; err = got_deflate_init(&zb, NULL, GOT_DEFLATE_BUFSIZE); if (err) goto done; *outlen = 0; do { err = got_deflate_read_mmap(&zb, map, offset, len, &avail, &consumed); if (err) goto done; offset += consumed; len -= consumed; if (avail > 0) { err = got_poll_write_full(outfd, zb.outbuf, avail); if (err) goto done; if (csum) csum_output(csum, zb.outbuf, avail); *outlen += avail; } } while (zb.flags & GOT_DEFLATE_F_HAVE_MORE); done: got_deflate_end(&zb); return err; } const struct got_error * got_deflate_to_file(off_t *outlen, FILE *infile, off_t len, FILE *outfile, struct got_deflate_checksum *csum) { const struct got_error *err; size_t avail; off_t consumed; struct got_deflate_buf zb; err = got_deflate_init(&zb, NULL, GOT_DEFLATE_BUFSIZE); if (err) goto done; *outlen = 0; do { err = got_deflate_read(&zb, infile, len, &avail, &consumed); if (err) goto done; len -= consumed; if (avail > 0) { size_t n; n = fwrite(zb.outbuf, avail, 1, outfile); if (n != 1) { err = got_ferror(outfile, GOT_ERR_IO); goto done; } if (csum) csum_output(csum, zb.outbuf, avail); *outlen += avail; } } while (zb.flags & GOT_DEFLATE_F_HAVE_MORE); done: got_deflate_end(&zb); return err; } const struct got_error * got_deflate_to_file_mmap(off_t *outlen, uint8_t *map, size_t offset, size_t len, FILE *outfile, struct got_deflate_checksum *csum) { const struct got_error *err; size_t avail, consumed; struct got_deflate_buf zb; err = got_deflate_init(&zb, NULL, GOT_DEFLATE_BUFSIZE); if (err) goto done; *outlen = 0; do { err = got_deflate_read_mmap(&zb, map, offset, len, &avail, &consumed); if (err) goto done; offset += consumed; len -= consumed; if (avail > 0) { size_t n; n = fwrite(zb.outbuf, avail, 1, outfile); if (n != 1) { err = got_ferror(outfile, GOT_ERR_IO); goto done; } if (csum) csum_output(csum, zb.outbuf, avail); *outlen += avail; } } while (zb.flags & GOT_DEFLATE_F_HAVE_MORE); done: got_deflate_end(&zb); return err; } const struct got_error * got_deflate_append_to_file_mmap(struct got_deflate_buf *zb, off_t *outlen, uint8_t *map, size_t offset, size_t len, FILE *outfile, struct got_deflate_checksum *csum) { const struct got_error *err; size_t avail, consumed; do { err = deflate_read_mmap(zb, map, offset, len, &avail, &consumed, 0); if (err) break; offset += consumed; len -= consumed; if (avail > 0) { size_t n; n = fwrite(zb->outbuf, avail, 1, outfile); if (n != 1) { err = got_ferror(outfile, GOT_ERR_IO); break; } if (csum) csum_output(csum, zb->outbuf, avail); if (outlen) *outlen += avail; } } while ((zb->flags & GOT_DEFLATE_F_HAVE_MORE) && len > 0); return err; } const struct got_error * got_deflate_to_mem_mmap(uint8_t **outbuf, size_t *outlen, size_t *consumed_total, struct got_deflate_checksum *csum, uint8_t *map, size_t offset, size_t len) { const struct got_error *err; size_t avail, consumed; struct got_deflate_buf zb; void *newbuf; size_t nbuf = 1; if (outbuf) { *outbuf = malloc(GOT_DEFLATE_BUFSIZE); if (*outbuf == NULL) return got_error_from_errno("malloc"); err = got_deflate_init(&zb, *outbuf, GOT_DEFLATE_BUFSIZE); if (err) { free(*outbuf); *outbuf = NULL; return err; } } else { err = got_deflate_init(&zb, NULL, GOT_DEFLATE_BUFSIZE); if (err) return err; } *outlen = 0; if (consumed_total) *consumed_total = 0; do { err = got_deflate_read_mmap(&zb, map, offset, len, &avail, &consumed); if (err) goto done; offset += consumed; if (consumed_total) *consumed_total += consumed; len -= consumed; if (avail > 0 && csum) csum_output(csum, zb.outbuf, avail); *outlen += avail; if ((zb.flags & GOT_DEFLATE_F_HAVE_MORE) && outbuf != NULL) { newbuf = reallocarray(*outbuf, ++nbuf, GOT_DEFLATE_BUFSIZE); if (newbuf == NULL) { err = got_error_from_errno("reallocarray"); free(*outbuf); *outbuf = NULL; *outlen = 0; goto done; } *outbuf = newbuf; zb.outbuf = newbuf + *outlen; zb.outlen = (nbuf * GOT_DEFLATE_BUFSIZE) - *outlen; } } while (zb.flags & GOT_DEFLATE_F_HAVE_MORE); done: got_deflate_end(&zb); return err; } got-portable-0.111/lib/got_lib_object_idcache.h0000644000175000017500000000301015001740614015110 /* * Copyright (c) 2018 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ struct got_object_idcache; struct got_object_idcache *got_object_idcache_alloc(int); void got_object_idcache_free(struct got_object_idcache *); const struct got_error *got_object_idcache_add(struct got_object_idcache *, struct got_object_id *, void *); void *got_object_idcache_get(struct got_object_idcache *, struct got_object_id *); const struct got_error *got_object_idcache_remove_one(void **, struct got_object_idcache *, struct got_object_id *); int got_object_idcache_contains(struct got_object_idcache *, struct got_object_id *); void got_object_idcache_for_each(struct got_object_idcache *, void (*cb)(struct got_object_id *, void *, void *), void *); int got_object_idcache_num_elements(struct got_object_idcache *); got-portable-0.111/lib/log.c0000644000175000017500000000660415001741021011264 /* * Copyright (c) 2003, 2004 Henning Brauer * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include "log.h" static int debug; static int verbose; const char *log_procname; void log_init(int n_debug, int facility) { debug = n_debug; verbose = n_debug; log_procinit(getprogname()); if (!debug) openlog(getprogname(), LOG_PID | LOG_NDELAY, facility); tzset(); } void log_procinit(const char *procname) { if (procname != NULL) log_procname = procname; } void log_setverbose(int v) { verbose = v; } int log_getverbose(void) { return (verbose); } void logit(int pri, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vlog(pri, fmt, ap); va_end(ap); } void vlog(int pri, const char *fmt, va_list ap) { char *nfmt; int saved_errno = errno; if (debug) { /* best effort in out of mem situations */ if (asprintf(&nfmt, "%s: %s\n", log_procname, fmt) == -1) { vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); } else { vfprintf(stderr, nfmt, ap); free(nfmt); } fflush(stderr); } else vsyslog(pri, fmt, ap); errno = saved_errno; } void log_warn(const char *emsg, ...) { char *nfmt; va_list ap; int saved_errno = errno; /* best effort to even work in out of memory situations */ if (emsg == NULL) logit(LOG_CRIT, "%s", strerror(saved_errno)); else { va_start(ap, emsg); if (asprintf(&nfmt, "%s: %s", emsg, strerror(saved_errno)) == -1) { /* we tried it... */ vlog(LOG_CRIT, emsg, ap); logit(LOG_CRIT, "%s", strerror(saved_errno)); } else { vlog(LOG_CRIT, nfmt, ap); free(nfmt); } va_end(ap); } errno = saved_errno; } void log_warnx(const char *emsg, ...) { va_list ap; va_start(ap, emsg); vlog(LOG_CRIT, emsg, ap); va_end(ap); } void log_info(const char *emsg, ...) { va_list ap; if (verbose > 0) { va_start(ap, emsg); vlog(LOG_INFO, emsg, ap); va_end(ap); } } void log_debug(const char *emsg, ...) { va_list ap; if (verbose > 1) { va_start(ap, emsg); vlog(LOG_DEBUG, emsg, ap); va_end(ap); } } static void vfatalc(int code, const char *emsg, va_list ap) { static char s[BUFSIZ]; const char *sep; if (emsg != NULL) { (void)vsnprintf(s, sizeof(s), emsg, ap); sep = ": "; } else { s[0] = '\0'; sep = ""; } if (code) logit(LOG_CRIT, "%s%s%s", s, sep, strerror(code)); else logit(LOG_CRIT, "%s", s); } void fatal(const char *emsg, ...) { va_list ap; va_start(ap, emsg); vfatalc(errno, emsg, ap); va_end(ap); exit(1); } void fatalx(const char *emsg, ...) { va_list ap; va_start(ap, emsg); vfatalc(0, emsg, ap); va_end(ap); exit(1); } got-portable-0.111/lib/pack_create_privsep.c0000644000175000017500000004700115001741021014510 /* * Copyright (c) 2020 Ori Bernstein * Copyright (c) 2021, 2022 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "got_compat.h" #include "got_error.h" #include "got_cancel.h" #include "got_object.h" #include "got_reference.h" #include "got_repository_admin.h" #include "got_path.h" #include "got_lib_delta.h" #include "got_lib_hash.h" #include "got_lib_object.h" #include "got_lib_object_cache.h" #include "got_lib_object_idset.h" #include "got_lib_privsep.h" #include "got_lib_ratelimit.h" #include "got_lib_pack.h" #include "got_lib_pack_create.h" #include "got_lib_repository.h" struct send_id_arg { struct imsgbuf *ibuf; struct got_object_id *ids[GOT_IMSG_OBJ_ID_LIST_MAX_NIDS]; size_t nids; }; static const struct got_error * send_id(struct got_object_id *id, void *data, void *arg) { const struct got_error *err = NULL; struct send_id_arg *a = arg; a->ids[a->nids++] = id; if (a->nids >= GOT_IMSG_OBJ_ID_LIST_MAX_NIDS) { err = got_privsep_send_object_idlist(a->ibuf, a->ids, a->nids); if (err) return err; a->nids = 0; } return NULL; } static const struct got_error * send_idset(struct imsgbuf *ibuf, struct got_object_idset *idset) { const struct got_error *err; struct send_id_arg sia; memset(&sia, 0, sizeof(sia)); sia.ibuf = ibuf; err = got_object_idset_for_each(idset, send_id, &sia); if (err) return err; if (sia.nids > 0) { err = got_privsep_send_object_idlist(ibuf, sia.ids, sia.nids); if (err) return err; } return got_privsep_send_object_idlist_done(ibuf); } static const struct got_error * send_filtered_id_queue(struct imsgbuf *ibuf, struct got_object_id_queue *ids, uintptr_t color) { const struct got_error *err = NULL; struct got_object_qid *qid; struct got_object_id *filtered_ids[GOT_IMSG_OBJ_ID_LIST_MAX_NIDS]; int nids = 0; STAILQ_FOREACH(qid, ids, entry) { if (color != (intptr_t)qid->data) continue; filtered_ids[nids++] = &qid->id; if (nids >= GOT_IMSG_OBJ_ID_LIST_MAX_NIDS) { err = got_privsep_send_object_idlist(ibuf, filtered_ids, nids); if (err) return err; nids = 0; } } if (nids > 0) { err = got_privsep_send_object_idlist(ibuf, filtered_ids, nids); if (err) return err; } return got_privsep_send_object_idlist_done(ibuf); } static const struct got_error * recv_reused_delta(struct got_imsg_reused_delta *delta, struct got_object_idset *idset, struct got_pack_metavec *v) { struct got_pack_meta *m, *base; if (delta->delta_offset + delta->delta_size < delta->delta_offset || delta->delta_offset + delta->delta_compressed_size < delta->delta_offset) return got_error(GOT_ERR_BAD_PACKFILE); m = got_object_idset_get(idset, &delta->id); if (m == NULL) return got_error(GOT_ERR_NO_OBJ); base = got_object_idset_get(idset, &delta->base_id); if (base == NULL) return got_error(GOT_ERR_NO_OBJ); m->delta_len = delta->delta_size; m->delta_compressed_len = delta->delta_compressed_size; m->delta_offset = 0; m->prev = base; m->size = delta->result_size; m->reused_delta_offset = delta->delta_offset; m->base_obj_id = got_object_id_dup(&delta->base_id); if (m->base_obj_id == NULL) return got_error_from_errno("got_object_id_dup"); return got_pack_add_meta(m, v); } const struct got_error * got_pack_search_deltas(struct got_packidx **packidx, struct got_pack **pack, struct got_pack_metavec *v, struct got_object_idset *idset, int ncolored, int nfound, int ntrees, int ncommits, struct got_repository *repo, got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; struct got_imsg_reused_delta deltas[GOT_IMSG_REUSED_DELTAS_MAX_NDELTAS]; size_t ndeltas, i; *packidx = NULL; *pack = NULL; err = got_pack_find_pack_for_reuse(packidx, repo); if (err) return err; if (*packidx == NULL) return NULL; err = got_pack_cache_pack_for_packidx(pack, *packidx, repo); if (err) goto done; if ((*pack)->privsep_child == NULL) { err = got_pack_start_privsep_child(*pack, *packidx); if (err) goto done; } err = got_privsep_send_delta_reuse_req((*pack)->privsep_child->ibuf); if (err) goto done; err = send_idset((*pack)->privsep_child->ibuf, idset); if (err) goto done; for (;;) { int done = 0; if (cancel_cb) { err = (*cancel_cb)(cancel_arg); if (err) break; } err = got_privsep_recv_reused_deltas(&done, deltas, &ndeltas, (*pack)->privsep_child->ibuf); if (err || done) break; for (i = 0; i < ndeltas; i++) { struct got_imsg_reused_delta *delta = &deltas[i]; err = recv_reused_delta(delta, idset, v); if (err) goto done; } err = got_pack_report_progress(progress_cb, progress_arg, rl, ncolored, nfound, ntrees, 0L, ncommits, got_object_idset_num_elements(idset), v->nmeta, 0, 0); if (err) break; } done: return err; } struct recv_painted_commit_arg { int *ncolored; int *nqueued; int *nskip; struct got_object_id_queue *ids; struct got_object_idset *keep; struct got_object_idset *drop; struct got_object_idset *skip; got_pack_progress_cb progress_cb; void *progress_arg; struct got_ratelimit *rl; got_cancel_cb cancel_cb; void *cancel_arg; struct got_object_qid **qid0; }; static const struct got_error * recv_painted_commit(void *arg, struct got_object_id *id, intptr_t color) { const struct got_error *err = NULL; struct recv_painted_commit_arg *a = arg; struct got_object_qid *qid, *tmp; if (a->cancel_cb) { err = a->cancel_cb(a->cancel_arg); if (err) return err; } switch (color) { case COLOR_KEEP: if (!got_object_idset_contains(a->keep, id)) { err = got_object_idset_add(a->keep, id, NULL); if (err) return err; (*a->ncolored)++; } break; case COLOR_DROP: if (!got_object_idset_contains(a->drop, id)) { err = got_object_idset_add(a->drop, id, NULL); if (err) return err; (*a->ncolored)++; } break; case COLOR_SKIP: if (!got_object_idset_contains(a->skip, id)) { err = got_object_idset_add(a->skip, id, NULL); if (err) return err; } break; default: /* should not happen */ return got_error_fmt(GOT_ERR_NOT_IMPL, "%s invalid commit color %"PRIdPTR, __func__, color); } STAILQ_FOREACH_SAFE(qid, a->ids, entry, tmp) { if (got_object_id_cmp(&qid->id, id) != 0) continue; STAILQ_REMOVE(a->ids, qid, got_object_qid, entry); color = (intptr_t)qid->data; if (*(a->qid0) == qid) *(a->qid0) = NULL; got_object_qid_free(qid); (*a->nqueued)--; if (color == COLOR_SKIP) (*a->nskip)--; break; } return got_pack_report_progress(a->progress_cb, a->progress_arg, a->rl, *a->ncolored, 0, 0, 0L, 0, 0, 0, 0, 0); } static const struct got_error * paint_packed_commits(struct got_object_qid **qid0, struct got_pack *pack, intptr_t color, int *ncolored, int *nqueued, int *nskip, struct got_object_id_queue *ids, struct got_object_idset *keep, struct got_object_idset *drop, struct got_object_idset *skip, struct got_repository *repo, got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; struct got_object_id_queue next_ids; struct got_object_qid *qid, *tmp; struct recv_painted_commit_arg arg; STAILQ_INIT(&next_ids); err = got_privsep_send_painting_request(pack->privsep_child->ibuf); if (err) return err; err = send_filtered_id_queue(pack->privsep_child->ibuf, ids, COLOR_KEEP); if (err) return err; err = send_filtered_id_queue(pack->privsep_child->ibuf, ids, COLOR_DROP); if (err) return err; err = send_filtered_id_queue(pack->privsep_child->ibuf, ids, COLOR_SKIP); if (err) return err; arg.ncolored = ncolored; arg.nqueued = nqueued; arg.nskip = nskip; arg.ids = ids; arg.keep = keep; arg.drop = drop; arg.skip = skip; arg.progress_cb = progress_cb; arg.progress_arg = progress_arg; arg.rl = rl; arg.cancel_cb = cancel_cb; arg.cancel_arg = cancel_arg; arg.qid0 = qid0; err = got_privsep_recv_painted_commits(&next_ids, recv_painted_commit, &arg, pack->privsep_child->ibuf); if (err) return err; STAILQ_FOREACH_SAFE(qid, &next_ids, entry, tmp) { struct got_object_qid *old_id; intptr_t qcolor, ocolor; STAILQ_FOREACH(old_id, ids, entry) { if (got_object_id_cmp(&qid->id, &old_id->id)) continue; qcolor = (intptr_t)qid->data; ocolor = (intptr_t)old_id->data; STAILQ_REMOVE(&next_ids, qid, got_object_qid, entry); got_object_qid_free(qid); qid = NULL; if (qcolor != ocolor) { got_pack_paint_commit(old_id, qcolor); if (ocolor == COLOR_SKIP) (*nskip)--; else if (qcolor == COLOR_SKIP) (*nskip)++; } break; } } while (!STAILQ_EMPTY(&next_ids)) { qid = STAILQ_FIRST(&next_ids); STAILQ_REMOVE_HEAD(&next_ids, entry); got_pack_paint_commit(qid, color); STAILQ_INSERT_TAIL(ids, qid, entry); (*nqueued)++; if (color == COLOR_SKIP) (*nskip)++; } return err; } static const struct got_error * pin_pack_for_commit_painting(struct got_pack **pack, struct got_packidx **packidx, struct got_object_id_queue *ids, struct got_repository *repo) { const struct got_error *err; err = got_pack_find_pack_for_commit_painting(packidx, ids, repo); if (err || *packidx == NULL) { *pack = NULL; return err; } err = got_pack_cache_pack_for_packidx(pack, *packidx, repo); if (err) return err; if ((*pack)->privsep_child == NULL) { err = got_pack_start_privsep_child(*pack, *packidx); if (err) return err; } return got_repo_pin_pack(repo, *packidx, *pack); } const struct got_error * got_pack_paint_commits(int *ncolored, struct got_object_id_queue *ids, int nids, struct got_object_idset *keep, struct got_object_idset *drop, struct got_object_idset *skip, struct got_repository *repo, got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; struct got_commit_object *commit = NULL; struct got_packidx *packidx = NULL; struct got_pack *pack = NULL; const struct got_object_id_queue *parents; struct got_object_qid *qid = NULL; int nqueued = nids, nskip = 0; int idx; err = pin_pack_for_commit_painting(&pack, &packidx, ids, repo); if (err) return err; while (!STAILQ_EMPTY(ids) && nskip != nqueued) { intptr_t color; if (cancel_cb) { err = cancel_cb(cancel_arg); if (err) break; } /* Pinned pack may have moved to different cache slot. */ pack = got_repo_get_pinned_pack(repo); if (pack == NULL) { err = pin_pack_for_commit_painting(&pack, &packidx, ids, repo); if (err) break; } qid = STAILQ_FIRST(ids); color = (intptr_t)qid->data; /* Use the fast path if we have a suitable pack file. */ if (packidx && pack) { idx = got_packidx_get_object_idx(packidx, &qid->id); if (idx != -1) { err = got_privsep_init_commit_painting( pack->privsep_child->ibuf); if (err) goto done; err = send_idset(pack->privsep_child->ibuf, keep); if (err) goto done; err = send_idset(pack->privsep_child->ibuf, drop); if (err) goto done; err = send_idset(pack->privsep_child->ibuf, skip); if (err) goto done; err = paint_packed_commits(&qid, pack, color, ncolored, &nqueued, &nskip, ids, keep, drop, skip, repo, progress_cb, progress_arg, rl, cancel_cb, cancel_arg); if (err && qid == NULL) goto done; if (qid) { STAILQ_REMOVE(ids, qid, got_object_qid, entry); nqueued--; got_object_qid_free(qid); qid = NULL; if (err) goto done; } continue; } } STAILQ_REMOVE_HEAD(ids, entry); nqueued--; got_repo_unpin_pack(repo); pack = NULL; packidx = NULL; if (color == COLOR_SKIP) nskip--; if (got_object_idset_contains(skip, &qid->id)) { got_object_qid_free(qid); qid = NULL; continue; } if (color == COLOR_KEEP && got_object_idset_contains(keep, &qid->id)) { got_object_qid_free(qid); qid = NULL; continue; } if (color == COLOR_DROP && got_object_idset_contains(drop, &qid->id)) { got_object_qid_free(qid); qid = NULL; continue; } switch (color) { case COLOR_KEEP: if (got_object_idset_contains(drop, &qid->id)) { err = got_pack_paint_commit(qid, COLOR_SKIP); if (err) goto done; err = got_object_idset_add(skip, &qid->id, NULL); if (err) goto done; err = got_pack_repaint_parent_commits(&qid->id, COLOR_SKIP, skip, skip, repo); if (err) goto done; } else (*ncolored)++; err = got_object_idset_add(keep, &qid->id, NULL); if (err) goto done; break; case COLOR_DROP: if (got_object_idset_contains(keep, &qid->id)) { err = got_pack_paint_commit(qid, COLOR_SKIP); if (err) goto done; err = got_object_idset_add(skip, &qid->id, NULL); if (err) goto done; err = got_pack_repaint_parent_commits(&qid->id, COLOR_SKIP, skip, skip, repo); if (err) goto done; } else (*ncolored)++; err = got_object_idset_add(drop, &qid->id, NULL); if (err) goto done; break; case COLOR_SKIP: if (!got_object_idset_contains(skip, &qid->id)) { err = got_object_idset_add(skip, &qid->id, NULL); if (err) goto done; } break; default: /* should not happen */ err = got_error_fmt(GOT_ERR_NOT_IMPL, "%s invalid commit color %"PRIdPTR, __func__, color); goto done; } err = got_pack_report_progress(progress_cb, progress_arg, rl, *ncolored, 0, 0, 0L, 0, 0, 0, 0, 0); if (err) break; err = got_object_open_as_commit(&commit, repo, &qid->id); if (err) break; parents = got_object_commit_get_parent_ids(commit); if (parents) { struct got_object_qid *pid; color = (intptr_t)qid->data; STAILQ_FOREACH(pid, parents, entry) { err = got_pack_queue_commit_id(ids, &pid->id, color, repo); if (err) goto done; nqueued++; if (color == COLOR_SKIP) nskip++; } } got_object_commit_close(commit); commit = NULL; got_object_qid_free(qid); qid = NULL; } done: if (pack) { const struct got_error *pack_err; pack_err = got_privsep_send_painting_commits_done( pack->privsep_child->ibuf); if (err == NULL) err = pack_err; } if (commit) got_object_commit_close(commit); got_object_qid_free(qid); got_repo_unpin_pack(repo); return err; } struct load_packed_obj_arg { /* output parameters: */ struct got_object_id *id; char *dpath; time_t mtime; /* input parameters: */ uint32_t seed; int want_meta; struct got_object_idset *idset; struct got_object_idset *idset_exclude; int loose_obj_only; int *ncolored; int *nfound; int *ntrees; got_pack_progress_cb progress_cb; void *progress_arg; struct got_ratelimit *rl; got_cancel_cb cancel_cb; void *cancel_arg; }; static const struct got_error * load_packed_commit_id(void *arg, time_t mtime, struct got_object_id *id, struct got_repository *repo) { struct load_packed_obj_arg *a = arg; if (got_object_idset_contains(a->idset, id) || got_object_idset_contains(a->idset_exclude, id)) return NULL; return got_pack_add_object(a->want_meta, a->want_meta ? a->idset : a->idset_exclude, id, "", GOT_OBJ_TYPE_COMMIT, mtime, a->seed, a->loose_obj_only, repo, a->ncolored, a->nfound, a->ntrees, a->progress_cb, a->progress_arg, a->rl); } static const struct got_error * load_packed_tree_ids(void *arg, struct got_tree_object *tree, time_t mtime, struct got_object_id *id, const char *dpath, struct got_repository *repo) { const struct got_error *err; struct load_packed_obj_arg *a = arg; const char *relpath; /* * When we receive a tree's ID and path but not the tree itself, * this tree object was not found in the pack file. This is the * last time we are being called for this optimized traversal. * Return from here and switch to loading objects the slow way. */ if (tree == NULL) { free(a->id); a->id = got_object_id_dup(id); if (a->id == NULL) { err = got_error_from_errno("got_object_id_dup"); free(a->dpath); a->dpath = NULL; return err; } free(a->dpath); a->dpath = strdup(dpath); if (a->dpath == NULL) { err = got_error_from_errno("strdup"); free(a->id); a->id = NULL; return err; } a->mtime = mtime; return NULL; } if (got_object_idset_contains(a->idset, id) || got_object_idset_contains(a->idset_exclude, id)) return NULL; relpath = dpath; while (relpath[0] == '/') relpath++; err = got_pack_add_object(a->want_meta, a->want_meta ? a->idset : a->idset_exclude, id, relpath, GOT_OBJ_TYPE_TREE, mtime, a->seed, a->loose_obj_only, repo, a->ncolored, a->nfound, a->ntrees, a->progress_cb, a->progress_arg, a->rl); if (err) return err; return got_pack_load_tree_entries(NULL, a->want_meta, a->idset, a->idset_exclude, tree, dpath, mtime, a->seed, repo, a->loose_obj_only, a->ncolored, a->nfound, a->ntrees, a->progress_cb, a->progress_arg, a->rl, a->cancel_cb, a->cancel_arg); } const struct got_error * got_pack_load_packed_object_ids(int *found_all_objects, struct got_object_id **ours, int nours, struct got_object_id **theirs, int ntheirs, int want_meta, uint32_t seed, struct got_object_idset *idset, struct got_object_idset *idset_exclude, int loose_obj_only, struct got_repository *repo, struct got_packidx *packidx, int *ncolored, int *nfound, int *ntrees, got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; struct load_packed_obj_arg lpa; memset(&lpa, 0, sizeof(lpa)); lpa.seed = seed; lpa.want_meta = want_meta; lpa.idset = idset; lpa.idset_exclude = idset_exclude; lpa.loose_obj_only = loose_obj_only; lpa.ncolored = ncolored; lpa.nfound = nfound; lpa.ntrees = ntrees; lpa.progress_cb = progress_cb; lpa.progress_arg = progress_arg; lpa.rl = rl; lpa.cancel_cb = cancel_cb; lpa.cancel_arg = cancel_arg; /* Attempt to load objects via got-read-pack, as far as possible. */ err = got_object_enumerate(found_all_objects, load_packed_commit_id, load_packed_tree_ids, &lpa, ours, nours, theirs, ntheirs, packidx, repo); if (err) return err; if (lpa.id == NULL) return NULL; /* * An incomplete tree hierarchy was present in the pack file * and caused loading to be aborted. * Continue loading trees the slow way. */ err = got_pack_load_tree(want_meta, idset, idset_exclude, lpa.id, lpa.dpath, lpa.mtime, seed, repo, loose_obj_only, ncolored, nfound, ntrees, progress_cb, progress_arg, rl, cancel_cb, cancel_arg); free(lpa.id); free(lpa.dpath); return err; } got-portable-0.111/lib/got_lib_deflate.h0000644000175000017500000000471315001740614013621 /* * Copyright (c) 2019 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ struct got_deflate_checksum { /* If not NULL, mix output bytes into this CRC checksum. */ uint32_t *output_crc; /* If not NULL, mix output bytes into this hash context. */ struct got_hash *output_ctx; }; struct got_deflate_buf { z_stream z; uint8_t *inbuf; size_t inlen; uint8_t *outbuf; size_t outlen; int flags; #define GOT_DEFLATE_F_HAVE_MORE 0x01 #define GOT_DEFLATE_F_OWN_OUTBUF 0x02 }; #define GOT_DEFLATE_BUFSIZE 8192 const struct got_error *got_deflate_init(struct got_deflate_buf *, uint8_t *, size_t); const struct got_error *got_deflate_read(struct got_deflate_buf *, FILE *, off_t, size_t *, off_t *); const struct got_error *got_deflate_read_mmap(struct got_deflate_buf *, uint8_t *, size_t, size_t, size_t *, size_t *); void got_deflate_end(struct got_deflate_buf *); const struct got_error *got_deflate_to_fd(off_t *, FILE *, off_t, int, struct got_deflate_checksum *); const struct got_error *got_deflate_to_fd_mmap(off_t *, uint8_t *, size_t, size_t, int, struct got_deflate_checksum *); const struct got_error *got_deflate_to_file(off_t *, FILE *, off_t, FILE *, struct got_deflate_checksum *); const struct got_error *got_deflate_to_file_mmap(off_t *, uint8_t *, size_t, size_t, FILE *, struct got_deflate_checksum *); const struct got_error *got_deflate_flush(struct got_deflate_buf *, FILE *, struct got_deflate_checksum *, off_t *); const struct got_error *got_deflate_append_to_file_mmap( struct got_deflate_buf *, off_t *, uint8_t *, size_t, size_t, FILE *, struct got_deflate_checksum *); const struct got_error *got_deflate_to_mem_mmap(uint8_t **, size_t *, size_t *, struct got_deflate_checksum *, uint8_t *, size_t, size_t); got-portable-0.111/lib/read_gotconfig_privsep.c0000644000175000017500000000776315001741021015234 /* * Copyright (c) 2020 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "got_compat.h" #include "got_error.h" #include "got_object.h" #include "got_repository.h" #include "got_lib_delta.h" #include "got_lib_hash.h" #include "got_lib_object.h" #include "got_lib_privsep.h" #include "got_lib_gotconfig.h" #include "got_gotconfig.h" const struct got_error * got_gotconfig_read(struct got_gotconfig **conf, const char *gotconfig_path) { const struct got_error *err = NULL, *child_err = NULL; int fd = -1; int imsg_fds[2] = { -1, -1 }; pid_t pid; struct imsgbuf ibuf; memset(&ibuf, 0, sizeof(ibuf)); *conf = calloc(1, sizeof(**conf)); if (*conf == NULL) return got_error_from_errno("calloc"); fd = open(gotconfig_path, O_RDONLY | O_CLOEXEC); if (fd == -1) { if (errno == ENOENT) return NULL; err = got_error_from_errno2("open", gotconfig_path); goto done; } if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1) { err = got_error_from_errno("socketpair"); goto done; } pid = fork(); if (pid == -1) { err = got_error_from_errno("fork"); goto done; } else if (pid == 0) { got_privsep_exec_child(imsg_fds, GOT_PATH_PROG_READ_GOTCONFIG, gotconfig_path); /* not reached */ } if (close(imsg_fds[1]) == -1) { err = got_error_from_errno("close"); goto wait; } imsg_fds[1] = -1; if (imsgbuf_init(&ibuf, imsg_fds[0]) == -1) { err = got_error_from_errno("imsgbuf_init"); goto wait; } imsgbuf_allow_fdpass(&ibuf); err = got_privsep_send_gotconfig_parse_req(&ibuf, fd); if (err) goto wait; fd = -1; err = got_privsep_send_gotconfig_author_req(&ibuf); if (err) goto wait; err = got_privsep_recv_gotconfig_str(&(*conf)->author, &ibuf); if (err) goto wait; err = got_privsep_send_gotconfig_allowed_signers_req(&ibuf); if (err) goto wait; err = got_privsep_recv_gotconfig_str(&(*conf)->allowed_signers_file, &ibuf); if (err) goto wait; err = got_privsep_send_gotconfig_revoked_signers_req(&ibuf); if (err) goto wait; err = got_privsep_recv_gotconfig_str(&(*conf)->revoked_signers_file, &ibuf); if (err) goto wait; err = got_privsep_send_gotconfig_signer_id_req(&ibuf); if (err) goto wait; err = got_privsep_recv_gotconfig_str(&(*conf)->signer_id, &ibuf); if (err) goto wait; err = got_privsep_send_gotconfig_remotes_req(&ibuf); if (err) goto wait; err = got_privsep_recv_gotconfig_remotes(&(*conf)->remotes, &(*conf)->nremotes, &ibuf); if (err) goto wait; wait: if (imsg_fds[0] != -1) got_privsep_send_stop(imsg_fds[0]); child_err = got_privsep_wait_for_child(pid); if (child_err && err == NULL) err = child_err; done: if (imsg_fds[0] != -1 && close(imsg_fds[0]) == -1 && err == NULL) err = got_error_from_errno("close"); if (imsg_fds[1] != -1 && close(imsg_fds[1]) == -1 && err == NULL) err = got_error_from_errno("close"); if (fd != -1 && close(fd) == -1 && err == NULL) err = got_error_from_errno2("close", gotconfig_path); if (err) { got_gotconfig_free(*conf); *conf = NULL; } if (ibuf.w) imsgbuf_clear(&ibuf); return err; } got-portable-0.111/lib/got_lib_ratelimit.h0000644000175000017500000000177215001740614014211 /* * Copyright (c) 2022 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ struct got_ratelimit { struct timespec last; struct timespec interval; }; void got_ratelimit_init(struct got_ratelimit *, time_t, unsigned int); const struct got_error *got_ratelimit_check(int *, struct got_ratelimit *); got-portable-0.111/lib/repository_init.c0000644000175000017500000000637015001741021013745 /* * Copyright (c) 2019, 2025 Stefan Sperling * Copyright (c) 2022 Mark Jamsek * Copyright (c) 2024 Omar Polo * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include "got_error.h" #include "got_path.h" #include "got_object.h" #include "got_repository.h" #include "got_lib_hash.h" #include "got_lib_delta.h" #include "got_lib_object.h" #include "got_lib_object_cache.h" #include "got_lib_pack.h" #include "got_lib_repository.h" #ifndef nitems #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) #endif const struct got_error * got_repo_init(const char *repo_path, const char *head_name, enum got_hash_algorithm algo) { const struct got_error *err = NULL; const char *dirnames[] = { GOT_OBJECTS_DIR, GOT_OBJECTS_PACK_DIR, GOT_REFS_DIR, }; const char *description_str = "Unnamed repository; " "edit this file 'description' to name the repository."; const char *headref = "ref: refs/heads/"; const char *gitconfig_sha1 = "[core]\n" "\trepositoryformatversion = 0\n" "\tfilemode = true\n" "\tbare = true\n"; const char *gitconfig_sha256 = "[core]\n" "\trepositoryformatversion = 1\n" "\tfilemode = true\n" "\tbare = true\n" "[extensions]\n" "\tobjectformat = sha256\n"; const char *gitconfig = gitconfig_sha1; char *headref_str, *path; size_t i; if (algo == GOT_HASH_SHA256) gitconfig = gitconfig_sha256; if (!got_path_dir_is_empty(repo_path)) return got_error(GOT_ERR_DIR_NOT_EMPTY); for (i = 0; i < nitems(dirnames); i++) { if (asprintf(&path, "%s/%s", repo_path, dirnames[i]) == -1) { return got_error_from_errno("asprintf"); } err = got_path_mkdir(path); free(path); if (err) return err; } if (asprintf(&path, "%s/%s", repo_path, "description") == -1) return got_error_from_errno("asprintf"); err = got_path_create_file(path, description_str); free(path); if (err) return err; if (asprintf(&path, "%s/%s", repo_path, GOT_HEAD_FILE) == -1) return got_error_from_errno("asprintf"); if (asprintf(&headref_str, "%s%s", headref, head_name ? head_name : "main") == -1) { free(path); return got_error_from_errno("asprintf"); } err = got_path_create_file(path, headref_str); free(headref_str); free(path); if (err) return err; if (asprintf(&path, "%s/%s", repo_path, "config") == -1) return got_error_from_errno("asprintf"); err = got_path_create_file(path, gitconfig); free(path); if (err) return err; return NULL; } got-portable-0.111/lib/got_lib_pack_create.h0000644000175000017500000001433415001740614014456 /* * Copyright (c) 2021 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * Write pack file data into the provided open packfile handle, for all * objects reachable via the commits listed in 'ours'. * Exclude any objects for commits listed in 'theirs' if 'theirs' is not NULL. * Return the hash digest of the resulting pack file in pack_hash which must * be pre-allocated by the caller with at least GOT_HASH_DIGEST_MAXLEN bytes. */ const struct got_error *got_pack_create(struct got_object_id *pack_hash, int packfd, FILE *delta_cache, struct got_object_id **theirs, int ntheirs, struct got_object_id **ours, int nours, struct got_repository *repo, int loose_obj_only, int allow_empty, int force_refdelta, got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *, got_cancel_cb cancel_cb, void *cancel_arg); const struct got_error * got_pack_cache_pack_for_packidx(struct got_pack **pack, struct got_packidx *packidx, struct got_repository *repo); const struct got_error * got_pack_find_pack_for_commit_painting(struct got_packidx **best_packidx, struct got_object_id_queue *ids, struct got_repository *repo); const struct got_error *got_pack_find_pack_for_reuse( struct got_packidx **best_packidx, struct got_repository *repo); struct got_ratelimit; const struct got_error *got_pack_paint_commits(int *ncolored, struct got_object_id_queue *ids, int nids, struct got_object_idset *keep, struct got_object_idset *drop, struct got_object_idset *skip, struct got_repository *repo, got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg); enum got_pack_findtwixt_color { COLOR_KEEP = 0, COLOR_DROP, COLOR_SKIP, COLOR_MAX, }; const struct got_error *got_pack_paint_commit(struct got_object_qid *qid, intptr_t color); const struct got_error *got_pack_repaint_parent_commits( struct got_object_id *commit_id, int color, struct got_object_idset *set, struct got_object_idset *skip, struct got_repository *repo); const struct got_error *got_pack_queue_commit_id( struct got_object_id_queue *ids, struct got_object_id *id, intptr_t color, struct got_repository *repo); struct got_pack_metavec { struct got_pack_meta **meta; int nmeta; int metasz; }; struct got_pack_meta { struct got_object_id id; uint32_t path_hash; int obj_type; off_t size; time_t mtime; /* The best delta we picked */ struct got_pack_meta *head; struct got_pack_meta *prev; unsigned char *delta_buf; /* if encoded in memory (compressed) */ off_t delta_offset; /* offset in delta cache file (compressed) */ off_t delta_len; /* encoded delta length */ off_t delta_compressed_len; /* encoded+compressed delta length */ int nchain; off_t reused_delta_offset; /* offset of delta in reused pack file */ struct got_object_id *base_obj_id; /* Only used for delta window */ struct got_delta_table *dtab; /* Only used for writing offset deltas */ off_t off; }; const struct got_error *got_pack_add_meta(struct got_pack_meta *m, struct got_pack_metavec *v); const struct got_error * got_pack_search_deltas(struct got_packidx **packidx, struct got_pack **pack, struct got_pack_metavec *v, struct got_object_idset *idset, int ncolored, int nfound, int ntrees, int ncommits, struct got_repository *repo, got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg); const struct got_error * got_pack_report_progress(got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, int ncolored, int nfound, int ntrees, off_t packfile_size, int ncommits, int nobj_total, int obj_deltify, int nobj_written, int pack_done); const struct got_error * got_pack_load_packed_object_ids(int *found_all_objects, struct got_object_id **ours, int nours, struct got_object_id **theirs, int ntheirs, int want_meta, uint32_t seed, struct got_object_idset *idset, struct got_object_idset *idset_exclude, int loose_obj_only, struct got_repository *repo, struct got_packidx *packidx, int *ncolored, int *nfound, int *ntrees, got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg); const struct got_error * got_pack_load_tree_entries(struct got_object_id_queue *ids, int want_meta, struct got_object_idset *idset, struct got_object_idset *idset_exclude, struct got_tree_object *tree, const char *dpath, time_t mtime, uint32_t seed, struct got_repository *repo, int loose_obj_only, int *ncolored, int *nfound, int *ntrees, got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg); const struct got_error * got_pack_load_tree(int want_meta, struct got_object_idset *idset, struct got_object_idset *idset_exclude, struct got_object_id *tree_id, const char *dpath, time_t mtime, uint32_t seed, struct got_repository *repo, int loose_obj_only, int *ncolored, int *nfound, int *ntrees, got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl, got_cancel_cb cancel_cb, void *cancel_arg); const struct got_error * got_pack_add_object(int want_meta, struct got_object_idset *idset, struct got_object_id *id, const char *path, int obj_type, time_t mtime, uint32_t seed, int loose_obj_only, struct got_repository *repo, int *ncolored, int *nfound, int *ntrees, got_pack_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl); got-portable-0.111/lib/diff_main.h0000644000175000017500000002257515001740614012440 /* Generic infrastructure to implement various diff algorithms. */ /* * Copyright (c) 2020 Neels Hofmeyr * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ struct diff_range { int start; int end; }; /* List of all possible return codes of a diff invocation. */ #define DIFF_RC_USE_DIFF_ALGO_FALLBACK -1 #define DIFF_RC_OK 0 /* Any positive return values are errno values from sys/errno.h */ struct diff_atom { struct diff_data *root; /* back pointer to root diff data */ off_t pos; /* set whether memory-mapped or not */ const uint8_t *at; /* only set if memory-mapped */ off_t len; /* This hash is just a very cheap speed up for finding *mismatching* * atoms. When hashes match, we still need to compare entire atoms to * find out whether they are indeed identical or not. * Calculated over all atom bytes with diff_atom_hash_update(). */ unsigned int hash; }; /* Mix another atom_byte into the provided hash value and return the result. * The hash value passed in for the first byte of the atom must be zero. */ unsigned int diff_atom_hash_update(unsigned int hash, unsigned char atom_byte); /* Compare two atoms for equality. Return 0 on success, or errno on failure. * Set cmp to -1, 0, or 1, just like strcmp(). */ int diff_atom_cmp(int *cmp, const struct diff_atom *left, const struct diff_atom *right); /* The atom's index in the entire file. For atoms divided by lines of text, this * yields the line number (starting with 0). Also works for diff_data that * reference only a subsection of a file, always reflecting the global position * in the file (and not the relative position within the subsection). */ #define diff_atom_root_idx(DIFF_DATA, ATOM) \ ((ATOM) && ((ATOM) >= (DIFF_DATA)->root->atoms.head) \ ? (unsigned int)((ATOM) - ((DIFF_DATA)->root->atoms.head)) \ : (DIFF_DATA)->root->atoms.len) /* The atom's index within DIFF_DATA. For atoms divided by lines of text, this * yields the line number (starting with 0). */ #define diff_atom_idx(DIFF_DATA, ATOM) \ ((ATOM) && ((ATOM) >= (DIFF_DATA)->atoms.head) \ ? (unsigned int)((ATOM) - ((DIFF_DATA)->atoms.head)) \ : (DIFF_DATA)->atoms.len) #define foreach_diff_atom(ATOM, FIRST_ATOM, COUNT) \ for ((ATOM) = (FIRST_ATOM); \ (ATOM) \ && ((ATOM) >= (FIRST_ATOM)) \ && ((ATOM) - (FIRST_ATOM) < (COUNT)); \ (ATOM)++) #define diff_data_foreach_atom(ATOM, DIFF_DATA) \ foreach_diff_atom(ATOM, (DIFF_DATA)->atoms.head, (DIFF_DATA)->atoms.len) #define diff_data_foreach_atom_from(FROM, ATOM, DIFF_DATA) \ for ((ATOM) = (FROM); \ (ATOM) \ && ((ATOM) >= (DIFF_DATA)->atoms.head) \ && ((ATOM) - (DIFF_DATA)->atoms.head < (DIFF_DATA)->atoms.len); \ (ATOM)++) #define diff_data_foreach_atom_backwards_from(FROM, ATOM, DIFF_DATA) \ for ((ATOM) = (FROM); \ (ATOM) \ && ((ATOM) >= (DIFF_DATA)->atoms.head) \ && ((ATOM) - (DIFF_DATA)->atoms.head >= 0); \ (ATOM)--) /* For each file, there is a "root" struct diff_data referencing the entire * file, which the atoms are parsed from. In recursion of diff algorithm, there * may be "child" struct diff_data only referencing a subsection of the file, * re-using the atoms parsing. For "root" structs, atoms_allocated will be * nonzero, indicating that the array of atoms is owned by that struct. For * "child" structs, atoms_allocated == 0, to indicate that the struct is * referencing a subset of atoms. */ struct diff_data { FILE *f; /* if root diff_data and not memory-mapped */ off_t pos; /* if not memory-mapped */ const uint8_t *data; /* if memory-mapped */ off_t len; int atomizer_flags; ARRAYLIST(struct diff_atom) atoms; struct diff_data *root; struct diff_data *current; void *algo_data; int diff_flags; int err; }; /* Flags set by file atomizer. */ #define DIFF_ATOMIZER_FOUND_BINARY_DATA 0x00000001 /* Flags set by caller of diff_main(). */ #define DIFF_FLAG_IGNORE_WHITESPACE 0x00000001 #define DIFF_FLAG_SHOW_PROTOTYPES 0x00000002 #define DIFF_FLAG_FORCE_TEXT_DATA 0x00000004 void diff_data_free(struct diff_data *diff_data); struct diff_chunk; typedef ARRAYLIST(struct diff_chunk) diff_chunk_arraylist_t; struct diff_result { int rc; /* * Pointers to diff data passed in via diff_main. * Do not free these diff_data before freeing the diff_result struct. */ struct diff_data *left; struct diff_data *right; diff_chunk_arraylist_t chunks; }; enum diff_chunk_type { CHUNK_EMPTY, CHUNK_PLUS, CHUNK_MINUS, CHUNK_SAME, CHUNK_ERROR, }; enum diff_chunk_type diff_chunk_type(const struct diff_chunk *c); struct diff_state; /* Signature of a utility function to divide a file into diff atoms. * An example is diff_atomize_text_by_line() in diff_atomize_text.c. * * func_data: context pointer (free to be used by implementation). * d: struct diff_data with d->data and d->len already set up, and * d->atoms to be created and d->atomizer_flags to be set up. */ typedef int (*diff_atomize_func_t)(void *func_data, struct diff_data *d); extern int diff_atomize_text_by_line(void *func_data, struct diff_data *d); struct diff_algo_config; typedef int (*diff_algo_impl_t)( const struct diff_algo_config *algo_config, struct diff_state *state); /* Form a result with all left-side removed and all right-side added, i.e. no * actual diff algorithm involved. */ int diff_algo_none(const struct diff_algo_config *algo_config, struct diff_state *state); /* Myers Diff tracing from the start all the way through to the end, requiring * quadratic amounts of memory. This can fail if the required space surpasses * algo_config->permitted_state_size. */ extern int diff_algo_myers(const struct diff_algo_config *algo_config, struct diff_state *state); /* Myers "Divide et Impera": tracing forwards from the start and backwards from * the end to find a midpoint that divides the problem into smaller chunks. * Requires only linear amounts of memory. */ extern int diff_algo_myers_divide( const struct diff_algo_config *algo_config, struct diff_state *state); /* Patience Diff algorithm, which divides a larger diff into smaller chunks. For * very specific scenarios, it may lead to a complete diff result by itself, but * needs a fallback algo to solve chunks that don't have common-unique atoms. */ extern int diff_algo_patience( const struct diff_algo_config *algo_config, struct diff_state *state); /* Diff algorithms to use, possibly nested. For example: * * struct diff_algo_config myers, patience, myers_divide; * * myers = (struct diff_algo_config){ * .impl = diff_algo_myers, * .permitted_state_size = 32 * 1024 * 1024, * // When too large, do diff_algo_patience: * .fallback_algo = &patience, * }; * * const struct diff_algo_config patience = (struct diff_algo_config){ * .impl = diff_algo_patience, * // After subdivision, do Patience again: * .inner_algo = &patience, * // If subdivision failed, do Myers Divide et Impera: * .fallback_algo = &myers_then_myers_divide, * }; * * const struct diff_algo_config myers_divide = (struct diff_algo_config){ * .impl = diff_algo_myers_divide, * // When division succeeded, start from the top: * .inner_algo = &myers_then_myers_divide, * // (fallback_algo = NULL implies diff_algo_none). * }; * struct diff_config config = { * .algo = &myers, * ... * }; * diff_main(&config, ...); */ struct diff_algo_config { diff_algo_impl_t impl; /* Fail this algo if it would use more than this amount of memory, and * instead use fallback_algo (diff_algo_myers). permitted_state_size == * 0 means no limitation. */ size_t permitted_state_size; /* For algorithms that divide into smaller chunks, use this algorithm to * solve the divided chunks. */ const struct diff_algo_config *inner_algo; /* If the algorithm fails (e.g. diff_algo_myers_if_small needs too large * state, or diff_algo_patience can't find any common-unique atoms), * then use this algorithm instead. */ const struct diff_algo_config *fallback_algo; }; struct diff_config { diff_atomize_func_t atomize_func; void *atomize_func_data; const struct diff_algo_config *algo; /* How deep to step into subdivisions of a source file, a paranoia / * safety measure to guard against infinite loops through diff * algorithms. When the maximum recursion is reached, employ * diff_algo_none (i.e. remove all left atoms and add all right atoms). */ unsigned int max_recursion_depth; }; int diff_atomize_file(struct diff_data *d, const struct diff_config *config, FILE *f, const uint8_t *data, off_t len, int diff_flags); struct diff_result *diff_main(const struct diff_config *config, struct diff_data *left, struct diff_data *right); void diff_result_free(struct diff_result *result); int diff_result_contains_printable_chunks(struct diff_result *result); got-portable-0.111/lib/got_lib_worktree.h0000644000175000017500000001045515001740614014057 /* * Copyright (c) 2018 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ struct got_worktree { char *root_path; const char *meta_dir; char *repo_path; int root_fd; char *path_prefix; struct got_object_id *base_commit_id; char *head_ref_name; uuid_t uuid; int format_version; /* * File descriptor for the lock file, open while a work tree is open. * When a work tree is opened, a shared lock on the lock file is * acquired with flock(2). This shared lock is held until the work * tree is closed, i.e. throughout the lifetime of any operation * which uses a work tree. * Before any modifications are made to the on-disk state of work * tree meta data, tracked files, or directory tree structure, this * shared lock must be upgraded to an exclusive lock. */ int lockfd; /* Absolute path to worktree's got.conf file. */ char *gotconfig_path; /* Settings read from got.conf. */ struct got_gotconfig *gotconfig; }; struct got_commitable { char *path; char *in_repo_path; char *ondisk_path; unsigned char status; unsigned char staged_status; struct got_object_id *blob_id; struct got_object_id *base_blob_id; struct got_object_id *staged_blob_id; struct got_object_id *base_commit_id; mode_t mode; int flags; #define GOT_COMMITABLE_ADDED 0x01 }; /* Also defined in got_worktree.h */ #ifndef GOT_WORKTREE_GOT_DIR #define GOT_WORKTREE_GOT_DIR ".got" #endif #ifndef GOT_WORKTREE_CVG_DIR #define GOT_WORKTREE_CVG_DIR ".cvg" #endif #define GOT_WORKTREE_FILE_INDEX "file-index" #define GOT_WORKTREE_REPOSITORY "repository" #define GOT_WORKTREE_PATH_PREFIX "path-prefix" #define GOT_WORKTREE_HEAD_REF "head-ref" #define GOT_WORKTREE_BASE_COMMIT "base-commit" #define GOT_WORKTREE_LOCK "lock" #define GOT_WORKTREE_FORMAT "format" #define GOT_WORKTREE_UUID "uuid" #define GOT_WORKTREE_HISTEDIT_SCRIPT "histedit-script" #define GOT_WORKTREE_FORMAT_VERSION 1 #define GOT_WORKTREE_BASE_REF_PREFIX "refs/got/worktree/base" /* Temporary branch which accumulates commits during a rebase operation. */ #define GOT_WORKTREE_REBASE_TMP_REF_PREFIX "refs/got/worktree/rebase/tmp" /* Symbolic reference pointing at the name of the new base branch. */ #define GOT_WORKTREE_NEWBASE_REF_PREFIX "refs/got/worktree/rebase/newbase" /* Symbolic reference pointing at the name of the branch being rebased. */ #define GOT_WORKTREE_REBASE_BRANCH_REF_PREFIX "refs/got/worktree/rebase/branch" /* Reference pointing at the ID of the current commit being rebased. */ #define GOT_WORKTREE_REBASE_COMMIT_REF_PREFIX "refs/got/worktree/rebase/commit" /* Temporary branch which accumulates commits during a histedit operation. */ #define GOT_WORKTREE_HISTEDIT_TMP_REF_PREFIX "refs/got/worktree/histedit/tmp" /* Symbolic reference pointing at the name of the branch being edited. */ #define GOT_WORKTREE_HISTEDIT_BRANCH_REF_PREFIX \ "refs/got/worktree/histedit/branch" /* Reference pointing at the ID of the work tree's pre-edit base commit. */ #define GOT_WORKTREE_HISTEDIT_BASE_COMMIT_REF_PREFIX \ "refs/got/worktree/histedit/base-commit" /* Reference pointing at the ID of the current commit being edited. */ #define GOT_WORKTREE_HISTEDIT_COMMIT_REF_PREFIX \ "refs/got/worktree/histedit/commit" /* Symbolic reference pointing at the name of the merge source branch. */ #define GOT_WORKTREE_MERGE_BRANCH_REF_PREFIX "refs/got/worktree/merge/branch" /* Reference pointing at the ID of the merge source branches's tip commit. */ #define GOT_WORKTREE_MERGE_COMMIT_REF_PREFIX "refs/got/worktree/merge/commit" /* Reference pointing to temporary commits that may need trivial rebasing. */ #define GOT_WORKTREE_COMMIT_REF_PREFIX "refs/got/worktree/commit" got-portable-0.111/lib/read_gitconfig_privsep.c0000644000175000017500000001237315001741021015217 /* * Copyright (c) 2019, 2022 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "got_compat.h" #include "got_error.h" #include "got_object.h" #include "got_repository.h" #include "got_path.h" #include "got_lib_delta.h" #include "got_lib_hash.h" #include "got_lib_object.h" #include "got_lib_object_cache.h" #include "got_lib_privsep.h" #include "got_lib_pack.h" #include "got_lib_repository.h" const struct got_error * got_repo_read_gitconfig(int *gitconfig_repository_format_version, char **gitconfig_author_name, char **gitconfig_author_email, struct got_remote_repo **remotes, int *nremotes, char **gitconfig_owner, char ***extnames, char ***extvals, int *nextensions, const char *gitconfig_path) { const struct got_error *err = NULL, *child_err = NULL; int fd = -1; int imsg_fds[2] = { -1, -1 }; pid_t pid; struct imsgbuf ibuf; memset(&ibuf, 0, sizeof(ibuf)); *gitconfig_repository_format_version = 0; if (extnames) *extnames = NULL; if (extvals) *extvals = NULL; if (nextensions) *nextensions = 0; *gitconfig_author_name = NULL; *gitconfig_author_email = NULL; if (remotes) *remotes = NULL; if (nremotes) *nremotes = 0; if (gitconfig_owner) *gitconfig_owner = NULL; fd = open(gitconfig_path, O_RDONLY | O_CLOEXEC); if (fd == -1) { if (errno == ENOENT) return NULL; return got_error_from_errno2("open", gitconfig_path); } if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1) { err = got_error_from_errno("socketpair"); goto done; } pid = fork(); if (pid == -1) { err = got_error_from_errno("fork"); goto done; } else if (pid == 0) { got_privsep_exec_child(imsg_fds, GOT_PATH_PROG_READ_GITCONFIG, gitconfig_path); /* not reached */ } if (close(imsg_fds[1]) == -1) { err = got_error_from_errno("close"); goto wait; } imsg_fds[1] = -1; if (imsgbuf_init(&ibuf, imsg_fds[0]) == -1) { err = got_error_from_errno("imsgbuf_init"); goto wait; } imsgbuf_allow_fdpass(&ibuf); err = got_privsep_send_gitconfig_parse_req(&ibuf, fd); if (err) goto wait; fd = -1; err = got_privsep_send_gitconfig_repository_format_version_req(&ibuf); if (err) goto wait; err = got_privsep_recv_gitconfig_int( gitconfig_repository_format_version, &ibuf); if (err) goto wait; if (extnames && extvals && nextensions) { err = got_privsep_send_gitconfig_repository_extensions_req( &ibuf); if (err) goto wait; err = got_privsep_recv_gitconfig_int(nextensions, &ibuf); if (err) goto wait; if (*nextensions > 0) { int i; *extnames = calloc(*nextensions, sizeof(char *)); if (*extnames == NULL) { err = got_error_from_errno("calloc"); goto wait; } *extvals = calloc(*nextensions, sizeof(char *)); if (*extvals == NULL) { err = got_error_from_errno("calloc"); goto wait; } for (i = 0; i < *nextensions; i++) { char *ext, *val; err = got_privsep_recv_gitconfig_pair(&ext, &val, &ibuf); if (err) goto wait; (*extnames)[i] = ext; (*extvals)[i] = val; } } } err = got_privsep_send_gitconfig_author_name_req(&ibuf); if (err) goto wait; err = got_privsep_recv_gitconfig_str(gitconfig_author_name, &ibuf); if (err) goto wait; err = got_privsep_send_gitconfig_author_email_req(&ibuf); if (err) goto wait; err = got_privsep_recv_gitconfig_str(gitconfig_author_email, &ibuf); if (err) goto wait; if (remotes && nremotes) { err = got_privsep_send_gitconfig_remotes_req(&ibuf); if (err) goto wait; err = got_privsep_recv_gitconfig_remotes(remotes, nremotes, &ibuf); if (err) goto wait; } if (gitconfig_owner) { err = got_privsep_send_gitconfig_owner_req(&ibuf); if (err) goto wait; err = got_privsep_recv_gitconfig_str(gitconfig_owner, &ibuf); if (err) goto wait; } wait: if (imsg_fds[0] != -1) got_privsep_send_stop(imsg_fds[0]); child_err = got_privsep_wait_for_child(pid); if (child_err && err == NULL) err = child_err; done: if (imsg_fds[0] != -1 && close(imsg_fds[0]) == -1 && err == NULL) err = got_error_from_errno("close"); if (imsg_fds[1] != -1 && close(imsg_fds[1]) == -1 && err == NULL) err = got_error_from_errno("close"); if (fd != -1 && close(fd) == -1 && err == NULL) err = got_error_from_errno2("close", gitconfig_path); if (ibuf.w) imsgbuf_clear(&ibuf); return err; } got-portable-0.111/lib/object_open_privsep.c0000644000175000017500000007776715001741021014564 /* * Copyright (c) 2018, 2019, 2022 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_object.h" #include "got_repository.h" #include "got_opentemp.h" #include "got_path.h" #include "got_lib_delta.h" #include "got_lib_object.h" #include "got_lib_hash.h" #include "got_lib_privsep.h" #include "got_lib_object_cache.h" #include "got_lib_pack.h" #include "got_lib_repository.h" static const struct got_error * request_packed_object(struct got_object **obj, struct got_pack *pack, int idx, struct got_object_id *id) { const struct got_error *err = NULL; struct imsgbuf *ibuf = pack->privsep_child->ibuf; err = got_privsep_send_packed_obj_req(ibuf, idx, id); if (err) return err; err = got_privsep_recv_obj(obj, ibuf); if (err) return err; memcpy(&(*obj)->id, id, sizeof((*obj)->id)); return NULL; } /* Create temporary files used during delta application. */ static const struct got_error * pack_child_send_tempfiles(struct imsgbuf *ibuf, struct got_pack *pack) { const struct got_error *err; int basefd = -1, accumfd = -1; /* * For performance reasons, the child will keep reusing the * same temporary files during every object request. * Opening and closing new files for every object request is * too expensive during operations such as 'gotadmin pack'. */ if (pack->child_has_tempfiles) return NULL; basefd = dup(pack->basefd); if (basefd == -1) return got_error_from_errno("dup"); accumfd = dup(pack->accumfd); if (accumfd == -1) { err = got_error_from_errno("dup"); goto done; } err = got_privsep_send_tmpfd(ibuf, basefd); if (err) goto done; err = got_privsep_send_tmpfd(ibuf, accumfd); done: if (err) { if (basefd != -1) close(basefd); if (accumfd != -1) close(accumfd); } else pack->child_has_tempfiles = 1; return err; } static const struct got_error * request_packed_object_raw(uint8_t **outbuf, off_t *size, size_t *hdrlen, int outfd, struct got_pack *pack, int idx, struct got_object_id *id) { const struct got_error *err = NULL; struct imsgbuf *ibuf = pack->privsep_child->ibuf; int outfd_child; err = pack_child_send_tempfiles(ibuf, pack); if (err) return err; outfd_child = dup(outfd); if (outfd_child == -1) return got_error_from_errno("dup"); err = got_privsep_send_packed_raw_obj_req(ibuf, idx, id); if (err) { close(outfd_child); return err; } err = got_privsep_send_raw_obj_outfd(ibuf, outfd_child); if (err) return err; err = got_privsep_recv_raw_obj(outbuf, size, hdrlen, ibuf); if (err) return err; return NULL; } static const struct got_error * read_packed_object_privsep(struct got_object **obj, struct got_repository *repo, struct got_pack *pack, struct got_packidx *packidx, int idx, struct got_object_id *id) { const struct got_error *err = NULL; if (pack->privsep_child == NULL) { err = got_pack_start_privsep_child(pack, packidx); if (err) return err; } return request_packed_object(obj, pack, idx, id); } static const struct got_error * read_packed_object_raw_privsep(uint8_t **outbuf, off_t *size, size_t *hdrlen, int outfd, struct got_pack *pack, struct got_packidx *packidx, int idx, struct got_object_id *id) { const struct got_error *err = NULL; if (pack->privsep_child == NULL) { err = got_pack_start_privsep_child(pack, packidx); if (err) return err; } return request_packed_object_raw(outbuf, size, hdrlen, outfd, pack, idx, id); } const struct got_error * got_object_open_packed(struct got_object **obj, struct got_object_id *id, struct got_repository *repo) { const struct got_error *err = NULL; struct got_pack *pack = NULL; struct got_packidx *packidx = NULL; int idx; char *path_packfile; err = got_repo_search_packidx(&packidx, &idx, repo, id); if (err) return err; err = got_packidx_get_packfile_path(&path_packfile, packidx->path_packidx); if (err) return err; pack = got_repo_get_cached_pack(repo, path_packfile); if (pack == NULL) { err = got_repo_cache_pack(&pack, repo, path_packfile, packidx); if (err) goto done; } err = read_packed_object_privsep(obj, repo, pack, packidx, idx, id); if (err) goto done; done: free(path_packfile); return err; } const struct got_error * got_object_open_from_packfile(struct got_object **obj, struct got_object_id *id, struct got_pack *pack, struct got_packidx *packidx, int obj_idx, struct got_repository *repo) { return read_packed_object_privsep(obj, repo, pack, packidx, obj_idx, id); } const struct got_error * got_object_read_raw_delta(uint64_t *base_size, uint64_t *result_size, off_t *delta_size, off_t *delta_compressed_size, off_t *delta_offset, off_t *delta_out_offset, struct got_object_id **base_id, int delta_cache_fd, struct got_packidx *packidx, int obj_idx, struct got_object_id *id, struct got_repository *repo) { const struct got_error *err = NULL; struct got_pack *pack = NULL; char *path_packfile; *base_size = 0; *result_size = 0; *delta_size = 0; *delta_compressed_size = 0; *delta_offset = 0; *delta_out_offset = 0; err = got_packidx_get_packfile_path(&path_packfile, packidx->path_packidx); if (err) return err; pack = got_repo_get_cached_pack(repo, path_packfile); if (pack == NULL) { err = got_repo_cache_pack(&pack, repo, path_packfile, packidx); if (err) return err; } if (pack->privsep_child == NULL) { err = got_pack_start_privsep_child(pack, packidx); if (err) return err; } if (!pack->child_has_delta_outfd) { int outfd_child; outfd_child = dup(delta_cache_fd); if (outfd_child == -1) return got_error_from_errno("dup"); err = got_privsep_send_raw_delta_outfd( pack->privsep_child->ibuf, outfd_child); if (err) return err; pack->child_has_delta_outfd = 1; } err = got_privsep_send_raw_delta_req(pack->privsep_child->ibuf, obj_idx, id); if (err) return err; return got_privsep_recv_raw_delta(base_size, result_size, delta_size, delta_compressed_size, delta_offset, delta_out_offset, base_id, pack->privsep_child->ibuf); } static const struct got_error * request_object(struct got_object **obj, struct got_object_id *id, struct got_repository *repo, int fd) { const struct got_error *err = NULL; struct imsgbuf *ibuf; ibuf = repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_OBJECT].ibuf; err = got_privsep_send_obj_req(ibuf, fd, id); if (err) return err; return got_privsep_recv_obj(obj, ibuf); } static const struct got_error * request_raw_object(uint8_t **outbuf, off_t *size, size_t *hdrlen, int outfd, struct got_object_id *id, struct got_repository *repo, int infd) { const struct got_error *err = NULL; struct imsgbuf *ibuf; int outfd_child; ibuf = repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_OBJECT].ibuf; outfd_child = dup(outfd); if (outfd_child == -1) return got_error_from_errno("dup"); err = got_privsep_send_raw_obj_req(ibuf, infd, id); if (err) return err; err = got_privsep_send_raw_obj_outfd(ibuf, outfd_child); if (err) return err; return got_privsep_recv_raw_obj(outbuf, size, hdrlen, ibuf); } static const struct got_error * start_child(struct got_repository *repo, int type) { const struct got_error *err = NULL; int imsg_fds[2]; pid_t pid; struct imsgbuf *ibuf; const char *prog_path; switch (type) { case GOT_REPO_PRIVSEP_CHILD_OBJECT: prog_path = GOT_PATH_PROG_READ_OBJECT; break; case GOT_REPO_PRIVSEP_CHILD_TREE: prog_path = GOT_PATH_PROG_READ_TREE; break; case GOT_REPO_PRIVSEP_CHILD_COMMIT: prog_path = GOT_PATH_PROG_READ_COMMIT; break; case GOT_REPO_PRIVSEP_CHILD_BLOB: prog_path = GOT_PATH_PROG_READ_BLOB; break; case GOT_REPO_PRIVSEP_CHILD_TAG: prog_path = GOT_PATH_PROG_READ_TAG; break; default: return got_error(GOT_ERR_OBJ_TYPE); } ibuf = calloc(1, sizeof(*ibuf)); if (ibuf == NULL) return got_error_from_errno("calloc"); if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1) { err = got_error_from_errno("socketpair"); free(ibuf); return err; } pid = fork(); if (pid == -1) { err = got_error_from_errno("fork"); close(imsg_fds[0]); close(imsg_fds[1]); free(ibuf); return err; } else if (pid == 0) { got_privsep_exec_child(imsg_fds, prog_path, repo->path); /* not reached */ } if (close(imsg_fds[1]) == -1) { err = got_error_from_errno("close"); close(imsg_fds[0]); free(ibuf); return err; } repo->privsep_children[type].imsg_fd = imsg_fds[0]; repo->privsep_children[type].pid = pid; if (imsgbuf_init(ibuf, imsg_fds[0]) == -1) { err = got_error_from_errno("imsgbuf_init"); close(imsg_fds[0]); free(ibuf); return err; } imsgbuf_allow_fdpass(ibuf); repo->privsep_children[type].ibuf = ibuf; return NULL; } const struct got_error * got_object_read_header_privsep(struct got_object **obj, struct got_object_id *id, struct got_repository *repo, int obj_fd) { const struct got_error *err; if (repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_OBJECT].imsg_fd != -1) return request_object(obj, id, repo, obj_fd); err = start_child(repo, GOT_REPO_PRIVSEP_CHILD_OBJECT); if (err) return err; return request_object(obj, id, repo, obj_fd); } static const struct got_error * read_object_raw_privsep(uint8_t **outbuf, off_t *size, size_t *hdrlen, int outfd, struct got_object_id *id, struct got_repository *repo, int obj_fd) { const struct got_error *err; if (repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_OBJECT].imsg_fd != -1) return request_raw_object(outbuf, size, hdrlen, outfd, id, repo, obj_fd); err = start_child(repo, GOT_REPO_PRIVSEP_CHILD_OBJECT); if (err) return err; return request_raw_object(outbuf, size, hdrlen, outfd, id, repo, obj_fd); } const struct got_error * got_object_open(struct got_object **obj, struct got_repository *repo, struct got_object_id *id) { const struct got_error *err = NULL; int fd; *obj = got_repo_get_cached_object(repo, id); if (*obj != NULL) { (*obj)->refcnt++; return NULL; } err = got_object_open_packed(obj, id, repo); if (err && err->code != GOT_ERR_NO_OBJ) return err; if (*obj) { (*obj)->refcnt++; return got_repo_cache_object(repo, id, *obj); } err = got_object_open_loose_fd(&fd, id, repo); if (err) return err; err = got_object_read_header_privsep(obj, id, repo, fd); if (err) return err; memcpy(&(*obj)->id, id, sizeof(*id)); (*obj)->refcnt++; return got_repo_cache_object(repo, id, *obj); } /* *outfd must be initialized to -1 by caller */ const struct got_error * got_object_raw_open(struct got_raw_object **obj, int *outfd, struct got_repository *repo, struct got_object_id *id) { const struct got_error *err = NULL; struct got_packidx *packidx = NULL; int idx; uint8_t *outbuf = NULL; off_t size = 0; size_t hdrlen = 0; char *path_packfile = NULL; *obj = got_repo_get_cached_raw_object(repo, id); if (*obj != NULL) { (*obj)->refcnt++; return NULL; } if (*outfd == -1) { *outfd = got_opentempfd(); if (*outfd == -1) return got_error_from_errno("got_opentempfd"); } err = got_repo_search_packidx(&packidx, &idx, repo, id); if (err == NULL) { struct got_pack *pack = NULL; err = got_packidx_get_packfile_path(&path_packfile, packidx->path_packidx); if (err) goto done; pack = got_repo_get_cached_pack(repo, path_packfile); if (pack == NULL) { err = got_repo_cache_pack(&pack, repo, path_packfile, packidx); if (err) goto done; } err = read_packed_object_raw_privsep(&outbuf, &size, &hdrlen, *outfd, pack, packidx, idx, id); if (err) goto done; } else if (err->code == GOT_ERR_NO_OBJ) { int fd; err = got_object_open_loose_fd(&fd, id, repo); if (err) goto done; err = read_object_raw_privsep(&outbuf, &size, &hdrlen, *outfd, id, repo, fd); if (err) goto done; } err = got_object_raw_alloc(obj, outbuf, outfd, GOT_DELTA_RESULT_SIZE_CACHED_MAX, hdrlen, size); if (err) goto done; err = got_repo_cache_raw_object(repo, id, *obj); done: free(path_packfile); if (err) { if (*obj) { got_object_raw_close(*obj); *obj = NULL; } free(outbuf); } return err; } static const struct got_error * request_packed_commit(struct got_commit_object **commit, struct got_pack *pack, int pack_idx, struct got_object_id *id) { const struct got_error *err = NULL; err = got_privsep_send_commit_req(pack->privsep_child->ibuf, -1, id, pack_idx); if (err) return err; err = got_privsep_recv_commit(commit, pack->privsep_child->ibuf); if (err) return err; (*commit)->flags |= GOT_COMMIT_FLAG_PACKED; return NULL; } static const struct got_error * read_packed_commit_privsep(struct got_commit_object **commit, struct got_pack *pack, struct got_packidx *packidx, int idx, struct got_object_id *id) { const struct got_error *err = NULL; if (pack->privsep_child) return request_packed_commit(commit, pack, idx, id); err = got_pack_start_privsep_child(pack, packidx); if (err) return err; return request_packed_commit(commit, pack, idx, id); } static const struct got_error * request_commit(struct got_commit_object **commit, struct got_repository *repo, int fd, struct got_object_id *id) { const struct got_error *err = NULL; struct imsgbuf *ibuf; ibuf = repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_COMMIT].ibuf; err = got_privsep_send_commit_req(ibuf, fd, id, -1); if (err) return err; return got_privsep_recv_commit(commit, ibuf); } static const struct got_error * read_commit_privsep(struct got_commit_object **commit, int obj_fd, struct got_object_id *id, struct got_repository *repo) { const struct got_error *err; if (repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_COMMIT].imsg_fd != -1) return request_commit(commit, repo, obj_fd, id); err = start_child(repo, GOT_REPO_PRIVSEP_CHILD_COMMIT); if (err) return err; return request_commit(commit, repo, obj_fd, id); } static const struct got_error * open_commit(struct got_commit_object **commit, struct got_repository *repo, struct got_object_id *id, int check_cache) { const struct got_error *err = NULL; struct got_packidx *packidx = NULL; int idx; char *path_packfile = NULL; if (check_cache) { *commit = got_repo_get_cached_commit(repo, id); if (*commit != NULL) { (*commit)->refcnt++; return NULL; } } else *commit = NULL; err = got_repo_search_packidx(&packidx, &idx, repo, id); if (err == NULL) { struct got_pack *pack = NULL; err = got_packidx_get_packfile_path(&path_packfile, packidx->path_packidx); if (err) return err; pack = got_repo_get_cached_pack(repo, path_packfile); if (pack == NULL) { err = got_repo_cache_pack(&pack, repo, path_packfile, packidx); if (err) goto done; } err = read_packed_commit_privsep(commit, pack, packidx, idx, id); } else if (err->code == GOT_ERR_NO_OBJ) { int fd; err = got_object_open_loose_fd(&fd, id, repo); if (err) return err; err = read_commit_privsep(commit, fd, id, repo); } if (err == NULL) { (*commit)->refcnt++; err = got_repo_cache_commit(repo, id, *commit); } done: free(path_packfile); return err; } const struct got_error * got_object_open_as_commit(struct got_commit_object **commit, struct got_repository *repo, struct got_object_id *id) { *commit = got_repo_get_cached_commit(repo, id); if (*commit != NULL) { (*commit)->refcnt++; return NULL; } return open_commit(commit, repo, id, 0); } const struct got_error * got_object_commit_open(struct got_commit_object **commit, struct got_repository *repo, struct got_object *obj) { return open_commit(commit, repo, got_object_get_id(obj), 1); } static const struct got_error * request_packed_tree(struct got_tree_object **tree, struct got_pack *pack, int pack_idx, struct got_object_id *id) { const struct got_error *err = NULL; err = got_privsep_send_tree_req(pack->privsep_child->ibuf, -1, id, pack_idx); if (err) return err; return got_privsep_recv_tree(tree, pack->privsep_child->ibuf); } static const struct got_error * read_packed_tree_privsep(struct got_tree_object **tree, struct got_pack *pack, struct got_packidx *packidx, int idx, struct got_object_id *id) { const struct got_error *err = NULL; if (pack->privsep_child) return request_packed_tree(tree, pack, idx, id); err = got_pack_start_privsep_child(pack, packidx); if (err) return err; return request_packed_tree(tree, pack, idx, id); } static const struct got_error * request_tree(struct got_tree_object **tree, struct got_repository *repo, int fd, struct got_object_id *id) { const struct got_error *err = NULL; struct imsgbuf *ibuf; ibuf = repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_TREE].ibuf; err = got_privsep_send_tree_req(ibuf, fd, id, -1); if (err) return err; return got_privsep_recv_tree(tree, ibuf); } static const struct got_error * read_tree_privsep(struct got_tree_object **tree, int obj_fd, struct got_object_id *id, struct got_repository *repo) { const struct got_error *err; if (repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_TREE].imsg_fd != -1) return request_tree(tree, repo, obj_fd, id); err = start_child(repo, GOT_REPO_PRIVSEP_CHILD_TREE); if (err) return err; return request_tree(tree, repo, obj_fd, id); } static const struct got_error * open_tree(struct got_tree_object **tree, struct got_repository *repo, struct got_object_id *id, int check_cache) { const struct got_error *err = NULL; struct got_packidx *packidx = NULL; int idx; char *path_packfile = NULL; if (check_cache) { *tree = got_repo_get_cached_tree(repo, id); if (*tree != NULL) { (*tree)->refcnt++; return NULL; } } else *tree = NULL; err = got_repo_search_packidx(&packidx, &idx, repo, id); if (err == NULL) { struct got_pack *pack = NULL; err = got_packidx_get_packfile_path(&path_packfile, packidx->path_packidx); if (err) return err; pack = got_repo_get_cached_pack(repo, path_packfile); if (pack == NULL) { err = got_repo_cache_pack(&pack, repo, path_packfile, packidx); if (err) goto done; } err = read_packed_tree_privsep(tree, pack, packidx, idx, id); } else if (err->code == GOT_ERR_NO_OBJ) { int fd; err = got_object_open_loose_fd(&fd, id, repo); if (err) return err; err = read_tree_privsep(tree, fd, id, repo); } if (err == NULL) { (*tree)->refcnt++; err = got_repo_cache_tree(repo, id, *tree); } done: free(path_packfile); return err; } const struct got_error * got_object_open_as_tree(struct got_tree_object **tree, struct got_repository *repo, struct got_object_id *id) { *tree = got_repo_get_cached_tree(repo, id); if (*tree != NULL) { (*tree)->refcnt++; return NULL; } return open_tree(tree, repo, id, 0); } const struct got_error * got_object_tree_open(struct got_tree_object **tree, struct got_repository *repo, struct got_object *obj) { return open_tree(tree, repo, got_object_get_id(obj), 1); } static const struct got_error * request_packed_blob(uint8_t **outbuf, size_t *size, size_t *hdrlen, int outfd, struct got_pack *pack, struct got_packidx *packidx, int idx, struct got_object_id *id) { const struct got_error *err = NULL; struct imsgbuf *ibuf = pack->privsep_child->ibuf; int outfd_child; err = pack_child_send_tempfiles(ibuf, pack); if (err) return err; outfd_child = dup(outfd); if (outfd_child == -1) return got_error_from_errno("dup"); err = got_privsep_send_blob_req(pack->privsep_child->ibuf, -1, id, idx); if (err) return err; err = got_privsep_send_blob_outfd(pack->privsep_child->ibuf, outfd_child); if (err) { return err; } err = got_privsep_recv_blob(outbuf, size, hdrlen, pack->privsep_child->ibuf); if (err) return err; if (lseek(outfd, SEEK_SET, 0) == -1) err = got_error_from_errno("lseek"); return err; } static const struct got_error * read_packed_blob_privsep(uint8_t **outbuf, size_t *size, size_t *hdrlen, int outfd, struct got_pack *pack, struct got_packidx *packidx, int idx, struct got_object_id *id) { const struct got_error *err = NULL; if (pack->privsep_child == NULL) { err = got_pack_start_privsep_child(pack, packidx); if (err) return err; } return request_packed_blob(outbuf, size, hdrlen, outfd, pack, packidx, idx, id); } static const struct got_error * request_blob(uint8_t **outbuf, size_t *size, size_t *hdrlen, int outfd, int infd, struct got_object_id *id, struct imsgbuf *ibuf) { const struct got_error *err = NULL; int outfd_child; outfd_child = dup(outfd); if (outfd_child == -1) return got_error_from_errno("dup"); err = got_privsep_send_blob_req(ibuf, infd, id, -1); if (err) return err; err = got_privsep_send_blob_outfd(ibuf, outfd_child); if (err) return err; err = got_privsep_recv_blob(outbuf, size, hdrlen, ibuf); if (err) return err; if (lseek(outfd, SEEK_SET, 0) == -1) return got_error_from_errno("lseek"); return err; } static const struct got_error * read_blob_privsep(uint8_t **outbuf, size_t *size, size_t *hdrlen, int outfd, int infd, struct got_object_id *id, struct got_repository *repo) { const struct got_error *err; struct imsgbuf *ibuf; if (repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_BLOB].imsg_fd != -1) { ibuf = repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_BLOB].ibuf; return request_blob(outbuf, size, hdrlen, outfd, infd, id, ibuf); } err = start_child(repo, GOT_REPO_PRIVSEP_CHILD_BLOB); if (err) return err; ibuf = repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_BLOB].ibuf; return request_blob(outbuf, size, hdrlen, outfd, infd, id, ibuf); } static const struct got_error * open_blob(struct got_blob_object **blob, struct got_repository *repo, struct got_object_id *id, size_t blocksize, int outfd) { const struct got_error *err = NULL; struct got_packidx *packidx = NULL; int idx, dfd = -1; char *path_packfile = NULL; uint8_t *outbuf; size_t size, hdrlen; struct stat sb; *blob = calloc(1, sizeof(**blob)); if (*blob == NULL) return got_error_from_errno("calloc"); (*blob)->read_buf = malloc(blocksize); if ((*blob)->read_buf == NULL) { err = got_error_from_errno("malloc"); goto done; } if (ftruncate(outfd, 0L) == -1) { err = got_error_from_errno("ftruncate"); goto done; } if (lseek(outfd, SEEK_SET, 0) == -1) { err = got_error_from_errno("lseek"); goto done; } err = got_repo_search_packidx(&packidx, &idx, repo, id); if (err == NULL) { struct got_pack *pack = NULL; err = got_packidx_get_packfile_path(&path_packfile, packidx->path_packidx); if (err) goto done; pack = got_repo_get_cached_pack(repo, path_packfile); if (pack == NULL) { err = got_repo_cache_pack(&pack, repo, path_packfile, packidx); if (err) goto done; } err = read_packed_blob_privsep(&outbuf, &size, &hdrlen, outfd, pack, packidx, idx, id); } else if (err->code == GOT_ERR_NO_OBJ) { int infd; err = got_object_open_loose_fd(&infd, id, repo); if (err) goto done; err = read_blob_privsep(&outbuf, &size, &hdrlen, outfd, infd, id, repo); } if (err) goto done; if (hdrlen > size) { err = got_error(GOT_ERR_BAD_OBJ_HDR); goto done; } if (outbuf && size > 0) { (*blob)->f = fmemopen(outbuf, size, "rb"); if ((*blob)->f == NULL) { err = got_error_from_errno("fmemopen"); free(outbuf); goto done; } (*blob)->data = outbuf; } else { if (fstat(outfd, &sb) == -1) { err = got_error_from_errno("fstat"); goto done; } if (sb.st_size != size) { err = got_error(GOT_ERR_PRIVSEP_LEN); goto done; } dfd = dup(outfd); if (dfd == -1) { err = got_error_from_errno("dup"); goto done; } (*blob)->f = fdopen(dfd, "rb"); if ((*blob)->f == NULL) { err = got_error_from_errno("fdopen"); close(dfd); dfd = -1; goto done; } } (*blob)->hdrlen = hdrlen; (*blob)->blocksize = blocksize; memcpy(&(*blob)->id, id, sizeof(*id)); done: free(path_packfile); if (err) { if (*blob) { got_object_blob_close(*blob); *blob = NULL; } } return err; } const struct got_error * got_object_open_as_blob(struct got_blob_object **blob, struct got_repository *repo, struct got_object_id *id, size_t blocksize, int outfd) { return open_blob(blob, repo, id, blocksize, outfd); } const struct got_error * got_object_blob_open(struct got_blob_object **blob, struct got_repository *repo, struct got_object *obj, size_t blocksize, int outfd) { return open_blob(blob, repo, got_object_get_id(obj), blocksize, outfd); } static const struct got_error * request_packed_tag(struct got_tag_object **tag, struct got_pack *pack, int pack_idx, struct got_object_id *id) { const struct got_error *err = NULL; err = got_privsep_send_tag_req(pack->privsep_child->ibuf, -1, id, pack_idx); if (err) return err; return got_privsep_recv_tag(tag, pack->privsep_child->ibuf); } static const struct got_error * read_packed_tag_privsep(struct got_tag_object **tag, struct got_pack *pack, struct got_packidx *packidx, int idx, struct got_object_id *id) { const struct got_error *err = NULL; if (pack->privsep_child) return request_packed_tag(tag, pack, idx, id); err = got_pack_start_privsep_child(pack, packidx); if (err) return err; return request_packed_tag(tag, pack, idx, id); } static const struct got_error * request_tag(struct got_tag_object **tag, struct got_repository *repo, int fd, struct got_object_id *id) { const struct got_error *err = NULL; struct imsgbuf *ibuf; ibuf = repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_TAG].ibuf; err = got_privsep_send_tag_req(ibuf, fd, id, -1); if (err) return err; return got_privsep_recv_tag(tag, ibuf); } static const struct got_error * read_tag_privsep(struct got_tag_object **tag, int obj_fd, struct got_object_id *id, struct got_repository *repo) { const struct got_error *err; if (repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_TAG].imsg_fd != -1) return request_tag(tag, repo, obj_fd, id); err = start_child(repo, GOT_REPO_PRIVSEP_CHILD_TAG); if (err) return err; return request_tag(tag, repo, obj_fd, id); } static const struct got_error * open_tag(struct got_tag_object **tag, struct got_repository *repo, struct got_object_id *id, int check_cache) { const struct got_error *err = NULL; struct got_packidx *packidx = NULL; int idx; char *path_packfile = NULL; struct got_object *obj = NULL; int obj_type = GOT_OBJ_TYPE_ANY; if (check_cache) { *tag = got_repo_get_cached_tag(repo, id); if (*tag != NULL) { (*tag)->refcnt++; return NULL; } } else *tag = NULL; err = got_repo_search_packidx(&packidx, &idx, repo, id); if (err == NULL) { struct got_pack *pack = NULL; err = got_packidx_get_packfile_path(&path_packfile, packidx->path_packidx); if (err) return err; pack = got_repo_get_cached_pack(repo, path_packfile); if (pack == NULL) { err = got_repo_cache_pack(&pack, repo, path_packfile, packidx); if (err) goto done; } /* Beware of "lightweight" tags: Check object type first. */ err = read_packed_object_privsep(&obj, repo, pack, packidx, idx, id); if (err) goto done; obj_type = obj->type; got_object_close(obj); if (obj_type != GOT_OBJ_TYPE_TAG) { err = got_error(GOT_ERR_OBJ_TYPE); goto done; } err = read_packed_tag_privsep(tag, pack, packidx, idx, id); } else if (err->code == GOT_ERR_NO_OBJ) { int fd; err = got_object_open_loose_fd(&fd, id, repo); if (err) return err; err = got_object_read_header_privsep(&obj, id, repo, fd); if (err) return err; obj_type = obj->type; got_object_close(obj); if (obj_type != GOT_OBJ_TYPE_TAG) return got_error(GOT_ERR_OBJ_TYPE); err = got_object_open_loose_fd(&fd, id, repo); if (err) return err; err = read_tag_privsep(tag, fd, id, repo); } if (err == NULL) { (*tag)->refcnt++; err = got_repo_cache_tag(repo, id, *tag); } done: free(path_packfile); return err; } const struct got_error * got_object_open_as_tag(struct got_tag_object **tag, struct got_repository *repo, struct got_object_id *id) { *tag = got_repo_get_cached_tag(repo, id); if (*tag != NULL) { (*tag)->refcnt++; return NULL; } return open_tag(tag, repo, id, 0); } const struct got_error * got_object_tag_open(struct got_tag_object **tag, struct got_repository *repo, struct got_object *obj) { return open_tag(tag, repo, got_object_get_id(obj), 1); } const struct got_error * got_traverse_packed_commits(struct got_object_id_queue *traversed_commits, struct got_object_id *commit_id, const char *path, struct got_repository *repo) { const struct got_error *err = NULL; struct got_pack *pack = NULL; struct got_packidx *packidx = NULL; char *path_packfile = NULL; struct got_commit_object *changed_commit = NULL; struct got_object_qid *changed_commit_qid = NULL; int idx; err = got_repo_search_packidx(&packidx, &idx, repo, commit_id); if (err) { if (err->code != GOT_ERR_NO_OBJ) return err; return NULL; } err = got_packidx_get_packfile_path(&path_packfile, packidx->path_packidx); if (err) return err; pack = got_repo_get_cached_pack(repo, path_packfile); if (pack == NULL) { err = got_repo_cache_pack(&pack, repo, path_packfile, packidx); if (err) goto done; } if (pack->privsep_child == NULL) { err = got_pack_start_privsep_child(pack, packidx); if (err) goto done; } err = got_privsep_send_commit_traversal_request( pack->privsep_child->ibuf, commit_id, idx, path); if (err) goto done; err = got_privsep_recv_traversed_commits(&changed_commit, traversed_commits, pack->privsep_child->ibuf); if (err) goto done; if (changed_commit) { /* * Cache the commit in which the path was changed. * This commit might be opened again soon. */ changed_commit->refcnt++; changed_commit_qid = STAILQ_LAST(traversed_commits, got_object_qid, entry); err = got_repo_cache_commit(repo, &changed_commit_qid->id, changed_commit); got_object_commit_close(changed_commit); } done: free(path_packfile); return err; } const struct got_error * got_object_enumerate(int *found_all_objects, got_object_enumerate_commit_cb cb_commit, got_object_enumerate_tree_cb cb_tree, void *cb_arg, struct got_object_id **ours, int nours, struct got_object_id **theirs, int ntheirs, struct got_packidx *packidx, struct got_repository *repo) { const struct got_error *err = NULL; struct got_pack *pack; char *path_packfile = NULL; err = got_packidx_get_packfile_path(&path_packfile, packidx->path_packidx); if (err) return err; pack = got_repo_get_cached_pack(repo, path_packfile); if (pack == NULL) { err = got_repo_cache_pack(&pack, repo, path_packfile, packidx); if (err) goto done; } if (pack->privsep_child == NULL) { err = got_pack_start_privsep_child(pack, packidx); if (err) goto done; } err = got_privsep_send_object_enumeration_request( pack->privsep_child->ibuf); if (err) goto done; err = got_privsep_send_object_idlist(pack->privsep_child->ibuf, ours, nours); if (err) goto done; err = got_privsep_send_object_idlist_done(pack->privsep_child->ibuf); if (err) goto done; err = got_privsep_send_object_idlist(pack->privsep_child->ibuf, theirs, ntheirs); if (err) goto done; err = got_privsep_send_object_idlist_done(pack->privsep_child->ibuf); if (err) goto done; err = got_privsep_recv_enumerated_objects(found_all_objects, pack->privsep_child->ibuf, cb_commit, cb_tree, cb_arg, repo); done: free(path_packfile); return err; } got-portable-0.111/lib/dial.c0000644000175000017500000003064715001741021011420 /* * Copyright (c) 2018, 2019 Ori Bernstein * Copyright (c) 2021 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_path.h" #include "got_object.h" #include "got_compat.h" #include "got_lib_dial.h" #include "got_lib_delta.h" #include "got_lib_hash.h" #include "got_lib_object.h" #include "got_lib_privsep.h" #include "got_dial.h" #ifndef nitems #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) #endif #ifndef ssizeof #define ssizeof(_x) ((ssize_t)(sizeof(_x))) #endif #ifndef MIN #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b)) #endif #ifndef GOT_DIAL_PATH_SSH #define GOT_DIAL_PATH_SSH "/usr/bin/ssh" #endif /* IANA assigned */ #define GOT_DEFAULT_GIT_PORT 9418 #define GOT_DEFAULT_GIT_PORT_STR "9418" const struct got_error * got_dial_apply_unveil(const char *proto) { if (strcmp(proto, "git+ssh") == 0 || strcmp(proto, "ssh") == 0) { if (unveil(GOT_DIAL_PATH_SSH, "x") != 0) { return got_error_from_errno2("unveil", GOT_DIAL_PATH_SSH); } } if (strstr(proto, "http") != NULL) { if (unveil(GOT_PATH_PROG_FETCH_HTTP, "x") != 0) { return got_error_from_errno2("unveil", GOT_PATH_PROG_FETCH_HTTP); } } return NULL; } static int hassuffix(const char *base, const char *suf) { int nb, ns; nb = strlen(base); ns = strlen(suf); if (ns <= nb && strcmp(base + (nb - ns), suf) == 0) return 1; return 0; } const struct got_error * got_dial_parse_uri(char **proto, char **host, char **port, char **server_path, char **repo_name, const char *uri) { const struct got_error *err = NULL; char *s, *p, *q; *proto = *host = *port = *server_path = *repo_name = NULL; p = strstr(uri, "://"); if (!p) { /* Try parsing Git's "scp" style URL syntax. */ *proto = strdup("ssh"); if (*proto == NULL) { err = got_error_from_errno("strdup"); goto done; } s = (char *)uri; q = strchr(s, ':'); if (q == NULL) { err = got_error(GOT_ERR_PARSE_URI); goto done; } /* No slashes allowed before first colon. */ p = strchr(s, '/'); if (p && q > p) { err = got_error(GOT_ERR_PARSE_URI); goto done; } *host = strndup(s, q - s); if (*host == NULL) { err = got_error_from_errno("strndup"); goto done; } if ((*host)[0] == '\0') { err = got_error(GOT_ERR_PARSE_URI); goto done; } p = q + 1; } else { *proto = strndup(uri, p - uri); if (*proto == NULL) { err = got_error_from_errno("strndup"); goto done; } s = p + 3; p = strstr(s, "/"); if (p == NULL || strlen(p) == 1) { err = got_error(GOT_ERR_PARSE_URI); goto done; } q = memchr(s, ':', p - s); if (q) { *host = strndup(s, q - s); if (*host == NULL) { err = got_error_from_errno("strndup"); goto done; } if ((*host)[0] == '\0') { err = got_error(GOT_ERR_PARSE_URI); goto done; } *port = strndup(q + 1, p - (q + 1)); if (*port == NULL) { err = got_error_from_errno("strndup"); goto done; } if ((*port)[0] == '\0') { err = got_error(GOT_ERR_PARSE_URI); goto done; } } else { *host = strndup(s, p - s); if (*host == NULL) { err = got_error_from_errno("strndup"); goto done; } if ((*host)[0] == '\0') { err = got_error(GOT_ERR_PARSE_URI); goto done; } } } while (p[0] == '/' && (p[1] == '/' || p[1] == '~')) p++; *server_path = strdup(p); if (*server_path == NULL) { err = got_error_from_errno("strdup"); goto done; } got_path_strip_trailing_slashes(*server_path); if ((*server_path)[0] == '\0') { err = got_error(GOT_ERR_PARSE_URI); goto done; } err = got_path_basename(repo_name, *server_path); if (err) goto done; if (hassuffix(*repo_name, ".git")) (*repo_name)[strlen(*repo_name) - 4] = '\0'; if ((*repo_name)[0] == '\0') err = got_error(GOT_ERR_PARSE_URI); done: if (err) { free(*proto); *proto = NULL; free(*host); *host = NULL; free(*port); *port = NULL; free(*server_path); *server_path = NULL; free(*repo_name); *repo_name = NULL; } return err; } /* * Escape a given path for the shell which will be started by sshd. * In particular, git-shell is known to require single-quote characters * around its repository path argument and will refuse to run otherwise. */ static const struct got_error * escape_path(char *buf, size_t bufsize, const char *path) { const char *p; char *q; p = path; q = buf; if (bufsize > 1) *q++ = '\''; while (*p != '\0' && (q - buf < bufsize)) { /* git escapes ! too */ if (*p != '\'' && *p != '!') { *q++ = *p++; continue; } if (q - buf + 4 >= bufsize) break; *q++ = '\''; *q++ = '\\'; *q++ = *p++; *q++ = '\''; } if (*p == '\0' && (q - buf + 1 < bufsize)) { *q++ = '\''; *q = '\0'; return NULL; } return got_error_fmt(GOT_ERR_NO_SPACE, "overlong path: %s", path); } const struct got_error * got_dial_ssh(pid_t *newpid, int *newfd, const char *host, const char *port, const char *path, const char *jumphost, const char *identity_file, const char *command, int verbosity) { const struct got_error *error = NULL; int pid, pfd[2]; char cmd[64]; char escaped_path[PATH_MAX]; const char *argv[15]; int i = 0, j; *newpid = -1; *newfd = -1; error = escape_path(escaped_path, sizeof(escaped_path), path); if (error) return error; argv[i++] = GOT_DIAL_PATH_SSH; if (port != NULL) { argv[i++] = "-p"; argv[i++] = (char *)port; } if (verbosity <= 0) { argv[i++] = "-q"; } else if (verbosity > 1) { /* ssh(1) allows up to 3 "-v" options. */ for (j = 0; j < MIN(3, verbosity); j++) argv[i++] = "-v"; } if (identity_file) { argv[i++] = "-i"; argv[i++] = identity_file; } if (jumphost) { argv[i++] = "-J"; argv[i++] = jumphost; } argv[i++] = "--"; argv[i++] = (char *)host; argv[i++] = (char *)cmd; argv[i++] = (char *)escaped_path; argv[i++] = NULL; assert(i <= nitems(argv)); if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pfd) == -1) return got_error_from_errno("socketpair"); pid = fork(); if (pid == -1) { error = got_error_from_errno("fork"); close(pfd[0]); close(pfd[1]); return error; } else if (pid == 0) { if (close(pfd[1]) == -1) err(1, "close"); if (dup2(pfd[0], 0) == -1) err(1, "dup2"); if (dup2(pfd[0], 1) == -1) err(1, "dup2"); if (strlcpy(cmd, command, sizeof(cmd)) >= sizeof(cmd)) err(1, "snprintf"); if (execv(GOT_DIAL_PATH_SSH, (char *const *)argv) == -1) err(1, "execv %s", GOT_DIAL_PATH_SSH); abort(); /* not reached */ } else { if (close(pfd[0]) == -1) return got_error_from_errno("close"); *newpid = pid; *newfd = pfd[1]; return NULL; } } const struct got_error * got_dial_git(int *newfd, const char *host, const char *port, const char *path, const char *command) { const struct got_error *err = NULL; struct addrinfo hints, *servinfo, *p; char *cmd = NULL; int fd = -1, len, r, eaicode; *newfd = -1; if (port == NULL) port = GOT_DEFAULT_GIT_PORT_STR; memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; eaicode = getaddrinfo(host, port, &hints, &servinfo); if (eaicode) { char msg[512]; snprintf(msg, sizeof(msg), "%s: %s", host, gai_strerror(eaicode)); return got_error_msg(GOT_ERR_ADDRINFO, msg); } for (p = servinfo; p != NULL; p = p->ai_next) { if ((fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) continue; if (connect(fd, p->ai_addr, p->ai_addrlen) == 0) { err = NULL; break; } err = got_error_from_errno("connect"); close(fd); } freeaddrinfo(servinfo); if (p == NULL) goto done; if (asprintf(&cmd, "%s %s", command, path) == -1) { err = got_error_from_errno("asprintf"); goto done; } len = 4 + strlen(cmd) + 1 + strlen("host=") + strlen(host) + 1; r = dprintf(fd, "%04x%s%chost=%s%c", len, cmd, '\0', host, '\0'); if (r < 0) err = got_error_from_errno("dprintf"); done: free(cmd); if (err) { if (fd != -1) close(fd); } else *newfd = fd; return err; } const struct got_error * got_dial_http(pid_t *newpid, int *newfd, const char *host, const char *port, const char *path, int verbosity, int tls) { const struct got_error *error = NULL; int pid, pfd[2]; const char *argv[8]; int i = 0; *newpid = -1; *newfd = -1; if (!port) port = tls ? "443" : "80"; argv[i++] = GOT_PATH_PROG_FETCH_HTTP; if (verbosity == -1) argv[i++] = "-q"; else if (verbosity > 0) argv[i++] = "-v"; argv[i++] = "--"; argv[i++] = tls ? "https" : "http"; argv[i++] = host; argv[i++] = port; argv[i++] = path; argv[i++] = NULL; assert(i <= nitems(argv)); if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pfd) == -1) return got_error_from_errno("socketpair"); pid = fork(); if (pid == -1) { error = got_error_from_errno("fork"); close(pfd[0]); close(pfd[1]); return error; } else if (pid == 0) { if (close(pfd[1]) == -1) err(1, "close"); if (dup2(pfd[0], 0) == -1) err(1, "dup2"); if (dup2(pfd[0], 1) == -1) err(1, "dup2"); if (execv(GOT_PATH_PROG_FETCH_HTTP, (char *const *)argv) == -1) err(1, "execv %s", GOT_PATH_PROG_FETCH_HTTP); abort(); /* not reached */ } else { if (close(pfd[0]) == -1) return got_error_from_errno("close"); *newpid = pid; *newfd = pfd[1]; return NULL; } } const struct got_error * got_dial_parse_command(char **command, char **repo_path, const char *gitcmd) { const struct got_error *err = NULL; size_t len, cmdlen, pathlen; char *path0 = NULL, *path, *abspath = NULL, *canonpath = NULL; const char *relpath; *command = NULL; *repo_path = NULL; len = strlen(gitcmd); if (len >= strlen(GOT_DIAL_CMD_SEND) && strncmp(gitcmd, GOT_DIAL_CMD_SEND, strlen(GOT_DIAL_CMD_SEND)) == 0) cmdlen = strlen(GOT_DIAL_CMD_SEND); else if (len >= strlen(GOT_DIAL_CMD_FETCH) && strncmp(gitcmd, GOT_DIAL_CMD_FETCH, strlen(GOT_DIAL_CMD_FETCH)) == 0) cmdlen = strlen(GOT_DIAL_CMD_FETCH); else return got_error(GOT_ERR_BAD_PACKET); if (len <= cmdlen + 1 || gitcmd[cmdlen] != ' ') return got_error(GOT_ERR_BAD_PACKET); if (memchr(&gitcmd[cmdlen + 1], '\0', len - cmdlen) == NULL) return got_error(GOT_ERR_BAD_PATH); /* Forbid linefeeds in paths, like Git does. */ if (memchr(&gitcmd[cmdlen + 1], '\n', len - cmdlen) != NULL) return got_error(GOT_ERR_BAD_PATH); path0 = strdup(&gitcmd[cmdlen + 1]); if (path0 == NULL) return got_error_from_errno("strdup"); path = path0; pathlen = strlen(path); /* * Git clients send a shell command. * Trim spaces and quotes around the path. */ while (path[0] == '\'' || path[0] == '\"' || path[0] == ' ') { path++; pathlen--; } while (pathlen > 0 && (path[pathlen - 1] == '\'' || path[pathlen - 1] == '\"' || path[pathlen - 1] == ' ')) { path[pathlen - 1] = '\0'; pathlen--; } /* Deny an empty repository path. */ if (path[0] == '\0' || got_path_is_root_dir(path)) { err = got_error(GOT_ERR_NOT_GIT_REPO); goto done; } if (asprintf(&abspath, "/%s", path) == -1) { err = got_error_from_errno("asprintf"); goto done; } pathlen = strlen(abspath); canonpath = malloc(pathlen + 1); if (canonpath == NULL) { err = got_error_from_errno("malloc"); goto done; } err = got_canonpath(abspath, canonpath, pathlen + 1); if (err) goto done; relpath = canonpath; while (relpath[0] == '/') relpath++; *repo_path = strdup(relpath); if (*repo_path == NULL) { err = got_error_from_errno("strdup"); goto done; } *command = strndup(gitcmd, cmdlen); if (*command == NULL) err = got_error_from_errno("strndup"); done: free(path0); free(abspath); free(canonpath); if (err) { free(*repo_path); *repo_path = NULL; } return err; } got-portable-0.111/lib/bufio.c0000644000175000017500000001706615001741021011613 /* * bufio.c was written by Omar Polo * * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include "bufio.h" int buf_init(struct buf *buf) { const size_t cap = BIO_CHUNK; memset(buf, 0, sizeof(*buf)); if ((buf->buf = malloc(cap)) == NULL) return (-1); buf->cap = cap; return (0); } static int buf_grow(struct buf *buf) { size_t newcap; void *t; newcap = buf->cap + BIO_CHUNK; t = realloc(buf->buf, newcap); if (t == NULL) return (-1); buf->buf = t; buf->cap = newcap; return (0); } int buf_has_line(struct buf *buf, const char *nl) { return (memmem(buf->buf, buf->len, nl, strlen(nl)) != NULL); } char * buf_getdelim(struct buf *buf, const char *nl, size_t *len) { uint8_t *endl; size_t nlen; *len = 0; nlen = strlen(nl); if ((endl = memmem(buf->buf, buf->len, nl, nlen)) == NULL) return (NULL); *len = endl + nlen - buf->buf; *endl = '\0'; return (buf->buf); } void buf_drain(struct buf *buf, size_t l) { buf->cur = 0; if (l >= buf->len) { buf->len = 0; return; } memmove(buf->buf, buf->buf + l, buf->len - l); buf->len -= l; } void buf_drain_line(struct buf *buf, const char *nl) { uint8_t *endln; size_t nlen; nlen = strlen(nl); if ((endln = memmem(buf->buf, buf->len, nl, nlen)) == NULL) return; buf_drain(buf, endln + nlen - buf->buf); } void buf_free(struct buf *buf) { free(buf->buf); memset(buf, 0, sizeof(*buf)); } int bufio_init(struct bufio *bio) { memset(bio, 0, sizeof(*bio)); bio->fd = -1; if (buf_init(&bio->wbuf) == -1) return (-1); if (buf_init(&bio->rbuf) == -1) { buf_free(&bio->wbuf); return (-1); } return (0); } void bufio_free(struct bufio *bio) { if (bio->ctx) tls_free(bio->ctx); bio->ctx = NULL; if (bio->fd != -1) close(bio->fd); bio->fd = -1; buf_free(&bio->rbuf); buf_free(&bio->wbuf); } int bufio_close(struct bufio *bio) { if (bio->ctx == NULL) return (0); switch (tls_close(bio->ctx)) { case 0: return 0; case TLS_WANT_POLLIN: errno = EAGAIN; bio->wantev = BUFIO_WANT_READ; return (-1); case TLS_WANT_POLLOUT: errno = EAGAIN; bio->wantev = BUFIO_WANT_WRITE; return (-1); default: return (-1); } } int bufio_reset(struct bufio *bio) { bufio_free(bio); return (bufio_init(bio)); } void bufio_set_fd(struct bufio *bio, int fd) { bio->fd = fd; } int bufio_starttls(struct bufio *bio, const char *host, int insecure, const uint8_t *cert, size_t certlen, const uint8_t *key, size_t keylen) { struct tls_config *conf; if ((conf = tls_config_new()) == NULL) return (-1); if (insecure) { tls_config_insecure_noverifycert(conf); tls_config_insecure_noverifyname(conf); tls_config_insecure_noverifytime(conf); } if (cert && tls_config_set_keypair_mem(conf, cert, certlen, key, keylen) == -1) { tls_config_free(conf); return (-1); } if ((bio->ctx = tls_client()) == NULL) { tls_config_free(conf); return (-1); } if (tls_configure(bio->ctx, conf) == -1) { tls_config_free(conf); return (-1); } tls_config_free(conf); if (tls_connect_socket(bio->ctx, bio->fd, host) == -1) return (-1); return (0); } int bufio_ev(struct bufio *bio) { short ev; if (bio->wantev) return (bio->wantev); ev = BUFIO_WANT_READ; if (bio->wbuf.len != 0) ev |= BUFIO_WANT_WRITE; return (ev); } int bufio_handshake(struct bufio *bio) { if (bio->ctx == NULL) { errno = EINVAL; return (-1); } switch (tls_handshake(bio->ctx)) { case 0: return (0); case TLS_WANT_POLLIN: errno = EAGAIN; bio->wantev = BUFIO_WANT_READ; return (-1); case TLS_WANT_POLLOUT: errno = EAGAIN; bio->wantev = BUFIO_WANT_WRITE; return (-1); default: return (-1); } } ssize_t bufio_read(struct bufio *bio) { struct buf *rbuf = &bio->rbuf; ssize_t r; assert(rbuf->cap >= rbuf->len); if (rbuf->cap - rbuf->len < BIO_CHUNK) { if (buf_grow(rbuf) == -1) return (-1); } if (bio->ctx) { r = tls_read(bio->ctx, rbuf->buf + rbuf->len, rbuf->cap - rbuf->len); switch (r) { case TLS_WANT_POLLIN: errno = EAGAIN; bio->wantev = BUFIO_WANT_READ; return (-1); case TLS_WANT_POLLOUT: errno = EAGAIN; bio->wantev = BUFIO_WANT_WRITE; return (-1); case -1: bio->wantev = 0; errno = 0; return (-1); default: bio->wantev = 0; rbuf->len += r; return (r); } } r = read(bio->fd, rbuf->buf + rbuf->len, rbuf->cap - rbuf->len); if (r == -1) return (-1); rbuf->len += r; return (r); } size_t bufio_drain(struct bufio *bio, void *d, size_t len) { struct buf *rbuf = &bio->rbuf; if (len > rbuf->len) len = rbuf->len; memcpy(d, rbuf->buf, len); buf_drain(rbuf, len); return (len); } ssize_t bufio_write(struct bufio *bio) { struct buf *wbuf = &bio->wbuf; ssize_t w; if (bio->ctx) { switch (w = tls_write(bio->ctx, wbuf->buf, wbuf->len)) { case TLS_WANT_POLLIN: errno = EAGAIN; bio->wantev = BUFIO_WANT_READ; return (-1); case TLS_WANT_POLLOUT: errno = EAGAIN; bio->wantev = BUFIO_WANT_WRITE; return (-1); case -1: return (-1); default: bio->wantev = 0; buf_drain(wbuf, w); return (w); } } w = write(bio->fd, wbuf->buf, wbuf->len); if (w == -1) return (-1); buf_drain(wbuf, w); return (w); } const char * bufio_io_err(struct bufio *bio) { if (bio->ctx) return tls_error(bio->ctx); return strerror(errno); } int bufio_compose(struct bufio *bio, const void *d, size_t len) { struct buf *wbuf = &bio->wbuf; while (wbuf->cap - wbuf->len < len) { if (buf_grow(wbuf) == -1) return (-1); } memcpy(wbuf->buf + wbuf->len, d, len); wbuf->len += len; return (0); } int bufio_compose_str(struct bufio *bio, const char *str) { return (bufio_compose(bio, str, strlen(str))); } int bufio_compose_fmt(struct bufio *bio, const char *fmt, ...) { va_list ap; char *str; int r; va_start(ap, fmt); r = vasprintf(&str, fmt, ap); va_end(ap); if (r == -1) return (-1); r = bufio_compose(bio, str, r); free(str); return (r); } void bufio_rewind_cursor(struct bufio *bio) { bio->rbuf.cur = 0; } int bufio_get_cb(void *d) { struct bufio *bio = d; struct buf *rbuf = &bio->rbuf; if (rbuf->cur >= rbuf->len) return (EOF); return (rbuf->buf[rbuf->cur++]); } int bufio_peek_cb(void *d) { struct bufio *bio = d; struct buf *rbuf = &bio->rbuf; if (rbuf->cur >= rbuf->len) return (EOF); return (rbuf->buf[rbuf->cur]); } got-portable-0.111/lib/worktree_cvg.c0000644000175000017500000024457415001741021013216 /* * Copyright (c) 2018, 2019, 2020 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_repository.h" #include "got_reference.h" #include "got_object.h" #include "got_path.h" #include "got_cancel.h" #include "got_worktree.h" #include "got_worktree_cvg.h" #include "got_opentemp.h" #include "got_diff.h" #include "got_send.h" #include "got_fetch.h" #include "got_lib_worktree.h" #include "got_lib_hash.h" #include "got_lib_fileindex.h" #include "got_lib_inflate.h" #include "got_lib_delta.h" #include "got_lib_object.h" #include "got_lib_object_parse.h" #include "got_lib_object_create.h" #include "got_lib_object_idset.h" #include "got_lib_diff.h" #ifndef MIN #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b)) #endif #define GOT_MERGE_LABEL_MERGED "merged change" #define GOT_MERGE_LABEL_BASE "3-way merge base" static const struct got_error * lock_worktree(struct got_worktree *worktree, int operation) { if (flock(worktree->lockfd, operation | LOCK_NB) == -1) return (errno == EWOULDBLOCK ? got_error(GOT_ERR_WORKTREE_BUSY) : got_error_from_errno2("flock", got_worktree_get_root_path(worktree))); return NULL; } static const struct got_error * is_bad_symlink_target(int *is_bad_symlink, const char *target_path, size_t target_len, const char *ondisk_path, const char *wtroot_path) { const struct got_error *err = NULL; char canonpath[PATH_MAX]; char *path_got = NULL; *is_bad_symlink = 0; if (target_len >= sizeof(canonpath)) { *is_bad_symlink = 1; return NULL; } /* * We do not use realpath(3) to resolve the symlink's target * path because we don't want to resolve symlinks recursively. * Instead we make the path absolute and then canonicalize it. * Relative symlink target lookup should begin at the directory * in which the blob object is being installed. */ if (!got_path_is_absolute(target_path)) { char *abspath, *parent; err = got_path_dirname(&parent, ondisk_path); if (err) return err; if (asprintf(&abspath, "%s/%s", parent, target_path) == -1) { free(parent); return got_error_from_errno("asprintf"); } free(parent); if (strlen(abspath) >= sizeof(canonpath)) { err = got_error_path(abspath, GOT_ERR_BAD_PATH); free(abspath); return err; } err = got_canonpath(abspath, canonpath, sizeof(canonpath)); free(abspath); if (err) return err; } else { err = got_canonpath(target_path, canonpath, sizeof(canonpath)); if (err) return err; } /* Only allow symlinks pointing at paths within the work tree. */ if (!got_path_is_child(canonpath, wtroot_path, strlen(wtroot_path))) { *is_bad_symlink = 1; return NULL; } /* Do not allow symlinks pointing into the .got directory. */ if (asprintf(&path_got, "%s/%s", wtroot_path, GOT_WORKTREE_GOT_DIR) == -1) return got_error_from_errno("asprintf"); if (got_path_is_child(canonpath, path_got, strlen(path_got))) *is_bad_symlink = 1; free(path_got); return NULL; } /* * Upgrade STATUS_MODIFY to STATUS_CONFLICT if a * conflict marker is found in newly added lines only. */ static const struct got_error * get_modified_file_content_status(unsigned char *status, struct got_blob_object *blob, const char *path, struct stat *sb, FILE *ondisk_file) { const struct got_error *err, *free_err; const char *markers[3] = { GOT_DIFF_CONFLICT_MARKER_BEGIN, GOT_DIFF_CONFLICT_MARKER_SEP, GOT_DIFF_CONFLICT_MARKER_END }; FILE *f1 = NULL; struct got_diffreg_result *diffreg_result = NULL; struct diff_result *r; int nchunks_parsed, n, i = 0, ln = 0; char *line = NULL; size_t linesize = 0; ssize_t linelen; if (*status != GOT_STATUS_MODIFY) return NULL; f1 = got_opentemp(); if (f1 == NULL) return got_error_from_errno("got_opentemp"); if (blob) { got_object_blob_rewind(blob); err = got_object_blob_dump_to_file(NULL, NULL, NULL, f1, blob); if (err) goto done; } err = got_diff_files(&diffreg_result, f1, 1, NULL, ondisk_file, 1, NULL, 0, 0, 1, NULL, GOT_DIFF_ALGORITHM_MYERS); if (err) goto done; r = diffreg_result->result; for (n = 0; n < r->chunks.len; n += nchunks_parsed) { struct diff_chunk *c; struct diff_chunk_context cc = {}; off_t pos; /* * We can optimise a little by advancing straight * to the next chunk if this one has no added lines. */ c = diff_chunk_get(r, n); if (diff_chunk_type(c) != CHUNK_PLUS) { nchunks_parsed = 1; continue; /* removed or unchanged lines */ } pos = diff_chunk_get_right_start_pos(c); if (fseek(ondisk_file, pos, SEEK_SET) == -1) { err = got_ferror(ondisk_file, GOT_ERR_IO); goto done; } diff_chunk_context_load_change(&cc, &nchunks_parsed, r, n, 0); ln = cc.right.start; while (ln < cc.right.end) { linelen = getline(&line, &linesize, ondisk_file); if (linelen == -1) { if (feof(ondisk_file)) break; err = got_ferror(ondisk_file, GOT_ERR_IO); break; } if (line && strncmp(line, markers[i], strlen(markers[i])) == 0) { if (strcmp(markers[i], GOT_DIFF_CONFLICT_MARKER_END) == 0) { *status = GOT_STATUS_CONFLICT; goto done; } else i++; } ++ln; } } done: free(line); if (f1 != NULL && fclose(f1) == EOF && err == NULL) err = got_error_from_errno("fclose"); free_err = got_diffreg_result_free(diffreg_result); if (err == NULL) err = free_err; return err; } static int xbit_differs(struct got_fileindex_entry *ie, uint16_t st_mode) { mode_t ie_mode = got_fileindex_perms_to_st(ie); return ((ie_mode & S_IXUSR) != (st_mode & S_IXUSR)); } static int stat_info_differs(struct got_fileindex_entry *ie, struct stat *sb) { return !(ie->ctime_sec == sb->st_ctim.tv_sec && ie->ctime_nsec == sb->st_ctim.tv_nsec && ie->mtime_sec == sb->st_mtim.tv_sec && ie->mtime_nsec == sb->st_mtim.tv_nsec && ie->size == (sb->st_size & 0xffffffff) && !xbit_differs(ie, sb->st_mode)); } static unsigned char get_staged_status(struct got_fileindex_entry *ie) { switch (got_fileindex_entry_stage_get(ie)) { case GOT_FILEIDX_STAGE_ADD: return GOT_STATUS_ADD; case GOT_FILEIDX_STAGE_DELETE: return GOT_STATUS_DELETE; case GOT_FILEIDX_STAGE_MODIFY: return GOT_STATUS_MODIFY; default: return GOT_STATUS_NO_CHANGE; } } static const struct got_error * get_symlink_modification_status(unsigned char *status, struct got_fileindex_entry *ie, const char *abspath, int dirfd, const char *de_name, struct got_blob_object *blob) { const struct got_error *err = NULL; char target_path[PATH_MAX]; char etarget[PATH_MAX]; ssize_t elen; size_t len, target_len = 0; const uint8_t *buf = got_object_blob_get_read_buf(blob); size_t hdrlen = got_object_blob_get_hdrlen(blob); *status = GOT_STATUS_NO_CHANGE; /* Blob object content specifies the target path of the link. */ do { err = got_object_blob_read_block(&len, blob); if (err) return err; if (len + target_len >= sizeof(target_path)) { /* * Should not happen. The blob contents were OK * when this symlink was installed. */ return got_error(GOT_ERR_NO_SPACE); } if (len > 0) { /* Skip blob object header first time around. */ memcpy(target_path + target_len, buf + hdrlen, len - hdrlen); target_len += len - hdrlen; hdrlen = 0; } } while (len != 0); target_path[target_len] = '\0'; if (dirfd != -1) { elen = readlinkat(dirfd, de_name, etarget, sizeof(etarget)); if (elen == -1) return got_error_from_errno2("readlinkat", abspath); } else { elen = readlink(abspath, etarget, sizeof(etarget)); if (elen == -1) return got_error_from_errno2("readlink", abspath); } if (elen != target_len || memcmp(etarget, target_path, target_len) != 0) *status = GOT_STATUS_MODIFY; return NULL; } static const struct got_error * get_file_status(unsigned char *status, struct stat *sb, struct got_fileindex_entry *ie, const char *abspath, int dirfd, const char *de_name, struct got_repository *repo) { const struct got_error *err = NULL; struct got_object_id id; size_t hdrlen; int fd = -1, fd1 = -1; FILE *f = NULL; uint8_t fbuf[8192]; struct got_blob_object *blob = NULL; size_t flen, blen; unsigned char staged_status; staged_status = get_staged_status(ie); *status = GOT_STATUS_NO_CHANGE; memset(sb, 0, sizeof(*sb)); /* * Whenever the caller provides a directory descriptor and a * directory entry name for the file, use them! This prevents * race conditions if filesystem paths change beneath our feet. */ if (dirfd != -1) { if (fstatat(dirfd, de_name, sb, AT_SYMLINK_NOFOLLOW) == -1) { if (errno == ENOENT) { if (got_fileindex_entry_has_file_on_disk(ie)) *status = GOT_STATUS_MISSING; else *status = GOT_STATUS_DELETE; goto done; } err = got_error_from_errno2("fstatat", abspath); goto done; } } else { fd = open(abspath, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (fd == -1 && errno != ENOENT && !got_err_open_nofollow_on_symlink()) return got_error_from_errno2("open", abspath); else if (fd == -1 && got_err_open_nofollow_on_symlink()) { if (lstat(abspath, sb) == -1) return got_error_from_errno2("lstat", abspath); } else if (fd == -1 || fstat(fd, sb) == -1) { if (errno == ENOENT) { if (got_fileindex_entry_has_file_on_disk(ie)) *status = GOT_STATUS_MISSING; else *status = GOT_STATUS_DELETE; goto done; } err = got_error_from_errno2("fstat", abspath); goto done; } } if (!S_ISREG(sb->st_mode) && !S_ISLNK(sb->st_mode)) { *status = GOT_STATUS_OBSTRUCTED; goto done; } if (!got_fileindex_entry_has_file_on_disk(ie)) { *status = GOT_STATUS_DELETE; goto done; } else if (!got_fileindex_entry_has_blob(ie) && staged_status != GOT_STATUS_ADD) { *status = GOT_STATUS_ADD; goto done; } if (!stat_info_differs(ie, sb)) goto done; if (S_ISLNK(sb->st_mode) && got_fileindex_entry_filetype_get(ie) != GOT_FILEIDX_MODE_SYMLINK) { *status = GOT_STATUS_MODIFY; goto done; } if (staged_status == GOT_STATUS_MODIFY || staged_status == GOT_STATUS_ADD) got_fileindex_entry_get_staged_blob_id(&id, ie); else got_fileindex_entry_get_blob_id(&id, ie); fd1 = got_opentempfd(); if (fd1 == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } err = got_object_open_as_blob(&blob, repo, &id, sizeof(fbuf), fd1); if (err) goto done; if (S_ISLNK(sb->st_mode)) { err = get_symlink_modification_status(status, ie, abspath, dirfd, de_name, blob); goto done; } if (dirfd != -1) { fd = openat(dirfd, de_name, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (fd == -1) { err = got_error_from_errno2("openat", abspath); goto done; } } f = fdopen(fd, "r"); if (f == NULL) { err = got_error_from_errno2("fdopen", abspath); goto done; } fd = -1; hdrlen = got_object_blob_get_hdrlen(blob); for (;;) { const uint8_t *bbuf = got_object_blob_get_read_buf(blob); err = got_object_blob_read_block(&blen, blob); if (err) goto done; /* Skip length of blob object header first time around. */ flen = fread(fbuf, 1, sizeof(fbuf) - hdrlen, f); if (flen == 0 && ferror(f)) { err = got_error_from_errno("fread"); goto done; } if (blen - hdrlen == 0) { if (flen != 0) *status = GOT_STATUS_MODIFY; break; } else if (flen == 0) { if (blen - hdrlen != 0) *status = GOT_STATUS_MODIFY; break; } else if (blen - hdrlen == flen) { /* Skip blob object header first time around. */ if (memcmp(bbuf + hdrlen, fbuf, flen) != 0) { *status = GOT_STATUS_MODIFY; break; } } else { *status = GOT_STATUS_MODIFY; break; } hdrlen = 0; } if (*status == GOT_STATUS_MODIFY) { rewind(f); err = get_modified_file_content_status(status, blob, ie->path, sb, f); } else if (xbit_differs(ie, sb->st_mode)) *status = GOT_STATUS_MODE_CHANGE; done: if (fd1 != -1 && close(fd1) == -1 && err == NULL) err = got_error_from_errno("close"); if (blob) got_object_blob_close(blob); if (f != NULL && fclose(f) == EOF && err == NULL) err = got_error_from_errno2("fclose", abspath); if (fd != -1 && close(fd) == -1 && err == NULL) err = got_error_from_errno2("close", abspath); return err; } static const struct got_error * get_ref_name(char **refname, struct got_worktree *worktree, const char *prefix) { const struct got_error *err = NULL; char *uuidstr = NULL; *refname = NULL; err = got_worktree_get_uuid(&uuidstr, worktree); if (err) return err; if (asprintf(refname, "%s-%s", prefix, uuidstr) == -1) { err = got_error_from_errno("asprintf"); *refname = NULL; } free(uuidstr); return err; } static const struct got_error * get_base_ref_name(char **refname, struct got_worktree *worktree) { return get_ref_name(refname, worktree, GOT_WORKTREE_BASE_REF_PREFIX); } /* * Prevent Git's garbage collector from deleting our base commit by * setting a reference to our base commit's ID. */ static const struct got_error * ref_base_commit(struct got_worktree *worktree, struct got_repository *repo) { const struct got_error *err = NULL; struct got_reference *ref = NULL; char *refname; err = get_base_ref_name(&refname, worktree); if (err) return err; err = got_ref_alloc(&ref, refname, worktree->base_commit_id); if (err) goto done; err = got_ref_write(ref, repo); done: free(refname); if (ref) got_ref_close(ref); return err; } static const struct got_error * get_fileindex_path(char **fileindex_path, struct got_worktree *worktree) { const struct got_error *err = NULL; if (asprintf(fileindex_path, "%s/%s/%s", worktree->root_path, GOT_WORKTREE_GOT_DIR, GOT_WORKTREE_FILE_INDEX) == -1) { err = got_error_from_errno("asprintf"); *fileindex_path = NULL; } return err; } static const struct got_error * open_fileindex(struct got_fileindex **fileindex, char **fileindex_path, struct got_worktree *worktree, enum got_hash_algorithm algo) { const struct got_error *err = NULL; FILE *index = NULL; *fileindex_path = NULL; *fileindex = got_fileindex_alloc(algo); if (*fileindex == NULL) return got_error_from_errno("got_fileindex_alloc"); err = get_fileindex_path(fileindex_path, worktree); if (err) goto done; index = fopen(*fileindex_path, "rbe"); if (index == NULL) { if (errno != ENOENT) err = got_error_from_errno2("fopen", *fileindex_path); } else { err = got_fileindex_read(*fileindex, index, algo); if (fclose(index) == EOF && err == NULL) err = got_error_from_errno("fclose"); } done: if (err) { free(*fileindex_path); *fileindex_path = NULL; got_fileindex_free(*fileindex); *fileindex = NULL; } return err; } static const struct got_error * sync_fileindex(struct got_fileindex *fileindex, const char *fileindex_path) { const struct got_error *err = NULL; char *new_fileindex_path = NULL; FILE *new_index = NULL; struct timespec timeout; err = got_opentemp_named(&new_fileindex_path, &new_index, fileindex_path, ""); if (err) goto done; err = got_fileindex_write(fileindex, new_index); if (err) goto done; if (rename(new_fileindex_path, fileindex_path) != 0) { err = got_error_from_errno3("rename", new_fileindex_path, fileindex_path); unlink(new_fileindex_path); } /* * Sleep for a short amount of time to ensure that files modified after * this program exits have a different time stamp from the one which * was recorded in the file index. */ timeout.tv_sec = 0; timeout.tv_nsec = 1; nanosleep(&timeout, NULL); done: if (new_index) fclose(new_index); free(new_fileindex_path); return err; } struct diff_dir_cb_arg { struct got_fileindex *fileindex; struct got_worktree *worktree; const char *status_path; size_t status_path_len; struct got_repository *repo; got_worktree_status_cb status_cb; void *status_arg; got_cancel_cb cancel_cb; void *cancel_arg; /* A pathlist containing per-directory pathlists of ignore patterns. */ struct got_pathlist_head *ignores; int report_unchanged; int no_ignores; }; static const struct got_error * report_file_status(struct got_fileindex_entry *ie, const char *abspath, int dirfd, const char *de_name, got_worktree_status_cb status_cb, void *status_arg, struct got_repository *repo, int report_unchanged) { const struct got_error *err = NULL; unsigned char status = GOT_STATUS_NO_CHANGE; unsigned char staged_status; struct stat sb; struct got_object_id blob_id, commit_id, staged_blob_id; struct got_object_id *blob_idp = NULL, *commit_idp = NULL; struct got_object_id *staged_blob_idp = NULL; staged_status = get_staged_status(ie); err = get_file_status(&status, &sb, ie, abspath, dirfd, de_name, repo); if (err) return err; if (status == GOT_STATUS_NO_CHANGE && staged_status == GOT_STATUS_NO_CHANGE && !report_unchanged) return NULL; if (got_fileindex_entry_has_blob(ie)) blob_idp = got_fileindex_entry_get_blob_id(&blob_id, ie); if (got_fileindex_entry_has_commit(ie)) commit_idp = got_fileindex_entry_get_commit_id(&commit_id, ie); if (staged_status == GOT_STATUS_ADD || staged_status == GOT_STATUS_MODIFY) { staged_blob_idp = got_fileindex_entry_get_staged_blob_id( &staged_blob_id, ie); } return (*status_cb)(status_arg, status, staged_status, ie->path, blob_idp, staged_blob_idp, commit_idp, dirfd, de_name); } static const struct got_error * status_old_new(void *arg, struct got_fileindex_entry *ie, struct dirent *de, const char *parent_path, int dirfd) { const struct got_error *err = NULL; struct diff_dir_cb_arg *a = arg; char *abspath; if (a->cancel_cb) { err = a->cancel_cb(a->cancel_arg); if (err) return err; } if (got_path_cmp(parent_path, a->status_path, strlen(parent_path), a->status_path_len) != 0 && !got_path_is_child(parent_path, a->status_path, a->status_path_len)) return NULL; if (parent_path[0]) { if (asprintf(&abspath, "%s/%s/%s", a->worktree->root_path, parent_path, de->d_name) == -1) return got_error_from_errno("asprintf"); } else { if (asprintf(&abspath, "%s/%s", a->worktree->root_path, de->d_name) == -1) return got_error_from_errno("asprintf"); } err = report_file_status(ie, abspath, dirfd, de->d_name, a->status_cb, a->status_arg, a->repo, a->report_unchanged); free(abspath); return err; } static const struct got_error * status_old(void *arg, struct got_fileindex_entry *ie, const char *parent_path) { const struct got_error *err = NULL; struct diff_dir_cb_arg *a = arg; struct got_object_id blob_id, commit_id; unsigned char status; if (a->cancel_cb) { err = a->cancel_cb(a->cancel_arg); if (err) return err; } if (!got_path_is_child(ie->path, a->status_path, a->status_path_len)) return NULL; got_fileindex_entry_get_blob_id(&blob_id, ie); got_fileindex_entry_get_commit_id(&commit_id, ie); if (got_fileindex_entry_has_file_on_disk(ie)) status = GOT_STATUS_MISSING; else status = GOT_STATUS_DELETE; return (*a->status_cb)(a->status_arg, status, get_staged_status(ie), ie->path, &blob_id, NULL, &commit_id, -1, NULL); } static void free_ignores(struct got_pathlist_head *ignores) { struct got_pathlist_entry *pe; RB_FOREACH(pe, got_pathlist_head, ignores) { struct got_pathlist_head *ignorelist = pe->data; got_pathlist_free(ignorelist, GOT_PATHLIST_FREE_PATH); } got_pathlist_free(ignores, GOT_PATHLIST_FREE_PATH); } static const struct got_error * read_ignores(struct got_pathlist_head *ignores, const char *path, FILE *f) { const struct got_error *err = NULL; struct got_pathlist_entry *pe = NULL; struct got_pathlist_head *ignorelist; char *line = NULL, *pattern, *dirpath = NULL; size_t linesize = 0; ssize_t linelen; ignorelist = calloc(1, sizeof(*ignorelist)); if (ignorelist == NULL) return got_error_from_errno("calloc"); RB_INIT(ignorelist); while ((linelen = getline(&line, &linesize, f)) != -1) { if (linelen > 0 && line[linelen - 1] == '\n') line[linelen - 1] = '\0'; /* Git's ignores may contain comments. */ if (line[0] == '#') continue; /* Git's negated patterns are not (yet?) supported. */ if (line[0] == '!') continue; if (asprintf(&pattern, "%s%s%s", path, path[0] ? "/" : "", line) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = got_pathlist_insert(NULL, ignorelist, pattern, NULL); if (err) goto done; } if (ferror(f)) { err = got_error_from_errno("getline"); goto done; } dirpath = strdup(path); if (dirpath == NULL) { err = got_error_from_errno("strdup"); goto done; } err = got_pathlist_insert(&pe, ignores, dirpath, ignorelist); done: free(line); if (err || pe == NULL) { free(dirpath); got_pathlist_free(ignorelist, GOT_PATHLIST_FREE_PATH); } return err; } static int match_path(const char *pattern, size_t pattern_len, const char *path, int flags) { char buf[PATH_MAX]; /* * Trailing slashes signify directories. * Append a * to make such patterns conform to fnmatch rules. */ if (pattern_len > 0 && pattern[pattern_len - 1] == '/') { if (snprintf(buf, sizeof(buf), "%s*", pattern) >= sizeof(buf)) return FNM_NOMATCH; /* XXX */ return fnmatch(buf, path, flags); } return fnmatch(pattern, path, flags); } static int match_ignores(struct got_pathlist_head *ignores, const char *path) { struct got_pathlist_entry *pe; /* Handle patterns which match in all directories. */ RB_FOREACH(pe, got_pathlist_head, ignores) { struct got_pathlist_head *ignorelist = pe->data; struct got_pathlist_entry *pi; RB_FOREACH(pi, got_pathlist_head, ignorelist) { const char *p; if (pi->path_len < 3 || strncmp(pi->path, "**/", 3) != 0) continue; p = path; while (*p) { if (match_path(pi->path + 3, pi->path_len - 3, p, FNM_PATHNAME | FNM_LEADING_DIR)) { /* Retry in next directory. */ while (*p && *p != '/') p++; while (*p == '/') p++; continue; } return 1; } } } /* * The ignores pathlist contains ignore lists from children before * parents, so we can find the most specific ignorelist by walking * ignores backwards. */ pe = RB_MAX(got_pathlist_head, ignores); while (pe) { if (got_path_is_child(path, pe->path, pe->path_len)) { struct got_pathlist_head *ignorelist = pe->data; struct got_pathlist_entry *pi; RB_FOREACH(pi, got_pathlist_head, ignorelist) { int flags = FNM_LEADING_DIR; if (strstr(pi->path, "/**/") == NULL) flags |= FNM_PATHNAME; if (match_path(pi->path, pi->path_len, path, flags)) continue; return 1; } } pe = RB_PREV(got_pathlist_head, ignores, pe); } return 0; } static const struct got_error * add_ignores(struct got_pathlist_head *ignores, const char *root_path, const char *path, int dirfd, const char *ignores_filename) { const struct got_error *err = NULL; char *ignorespath; int fd = -1; FILE *ignoresfile = NULL; if (asprintf(&ignorespath, "%s/%s%s%s", root_path, path, path[0] ? "/" : "", ignores_filename) == -1) return got_error_from_errno("asprintf"); if (dirfd != -1) { fd = openat(dirfd, ignores_filename, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (fd == -1) { if (errno != ENOENT && errno != EACCES) err = got_error_from_errno2("openat", ignorespath); } else { ignoresfile = fdopen(fd, "r"); if (ignoresfile == NULL) err = got_error_from_errno2("fdopen", ignorespath); else { fd = -1; err = read_ignores(ignores, path, ignoresfile); } } } else { ignoresfile = fopen(ignorespath, "re"); if (ignoresfile == NULL) { if (errno != ENOENT && errno != EACCES) err = got_error_from_errno2("fopen", ignorespath); } else err = read_ignores(ignores, path, ignoresfile); } if (ignoresfile && fclose(ignoresfile) == EOF && err == NULL) err = got_error_from_errno2("fclose", path); if (fd != -1 && close(fd) == -1 && err == NULL) err = got_error_from_errno2("close", path); free(ignorespath); return err; } static const struct got_error * status_new(int *ignore, void *arg, struct dirent *de, const char *parent_path, int dirfd) { const struct got_error *err = NULL; struct diff_dir_cb_arg *a = arg; char *path = NULL; if (ignore != NULL) *ignore = 0; if (a->cancel_cb) { err = a->cancel_cb(a->cancel_arg); if (err) return err; } if (parent_path[0]) { if (asprintf(&path, "%s/%s", parent_path, de->d_name) == -1) return got_error_from_errno("asprintf"); } else { path = de->d_name; } if (de->d_type == DT_DIR) { if (!a->no_ignores && ignore != NULL && match_ignores(a->ignores, path)) *ignore = 1; } else if (!match_ignores(a->ignores, path) && got_path_is_child(path, a->status_path, a->status_path_len)) err = (*a->status_cb)(a->status_arg, GOT_STATUS_UNVERSIONED, GOT_STATUS_NO_CHANGE, path, NULL, NULL, NULL, -1, NULL); if (parent_path[0]) free(path); return err; } static const struct got_error * status_traverse(void *arg, const char *path, int dirfd) { const struct got_error *err = NULL; struct diff_dir_cb_arg *a = arg; if (a->no_ignores) return NULL; err = add_ignores(a->ignores, a->worktree->root_path, path, dirfd, ".cvsignore"); if (err) return err; err = add_ignores(a->ignores, a->worktree->root_path, path, dirfd, ".gitignore"); return err; } static const struct got_error * report_single_file_status(const char *path, const char *ondisk_path, struct got_fileindex *fileindex, got_worktree_status_cb status_cb, void *status_arg, struct got_repository *repo, int report_unchanged, struct got_pathlist_head *ignores, int no_ignores) { struct got_fileindex_entry *ie; struct stat sb; ie = got_fileindex_entry_get(fileindex, path, strlen(path)); if (ie) return report_file_status(ie, ondisk_path, -1, NULL, status_cb, status_arg, repo, report_unchanged); if (lstat(ondisk_path, &sb) == -1) { if (errno != ENOENT) return got_error_from_errno2("lstat", ondisk_path); return (*status_cb)(status_arg, GOT_STATUS_NONEXISTENT, GOT_STATUS_NO_CHANGE, path, NULL, NULL, NULL, -1, NULL); } if (!no_ignores && match_ignores(ignores, path)) return NULL; if (S_ISREG(sb.st_mode) || S_ISLNK(sb.st_mode)) return (*status_cb)(status_arg, GOT_STATUS_UNVERSIONED, GOT_STATUS_NO_CHANGE, path, NULL, NULL, NULL, -1, NULL); return NULL; } static const struct got_error * add_ignores_from_parent_paths(struct got_pathlist_head *ignores, const char *root_path, const char *path) { const struct got_error *err; char *parent_path, *next_parent_path = NULL; err = add_ignores(ignores, root_path, "", -1, ".cvsignore"); if (err) return err; err = add_ignores(ignores, root_path, "", -1, ".gitignore"); if (err) return err; err = got_path_dirname(&parent_path, path); if (err) { if (err->code == GOT_ERR_BAD_PATH) return NULL; /* cannot traverse parent */ return err; } for (;;) { err = add_ignores(ignores, root_path, parent_path, -1, ".cvsignore"); if (err) break; err = add_ignores(ignores, root_path, parent_path, -1, ".gitignore"); if (err) break; err = got_path_dirname(&next_parent_path, parent_path); if (err) { if (err->code == GOT_ERR_BAD_PATH) err = NULL; /* traversed everything */ break; } if (got_path_is_root_dir(parent_path)) break; free(parent_path); parent_path = next_parent_path; next_parent_path = NULL; } free(parent_path); free(next_parent_path); return err; } struct find_missing_children_args { const char *parent_path; size_t parent_len; struct got_pathlist_head *children; got_cancel_cb cancel_cb; void *cancel_arg; }; static const struct got_error * find_missing_children(void *arg, struct got_fileindex_entry *ie) { const struct got_error *err = NULL; struct find_missing_children_args *a = arg; if (a->cancel_cb) { err = a->cancel_cb(a->cancel_arg); if (err) return err; } if (got_path_is_child(ie->path, a->parent_path, a->parent_len)) err = got_pathlist_insert(NULL, a->children, ie->path, NULL); return err; } static const struct got_error * report_children(struct got_pathlist_head *children, struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_repository *repo, int is_root_dir, int report_unchanged, struct got_pathlist_head *ignores, int no_ignores, got_worktree_status_cb status_cb, void *status_arg, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; struct got_pathlist_entry *pe; char *ondisk_path = NULL; RB_FOREACH(pe, got_pathlist_head, children) { if (cancel_cb) { err = cancel_cb(cancel_arg); if (err) break; } if (asprintf(&ondisk_path, "%s%s%s", worktree->root_path, !is_root_dir ? "/" : "", pe->path) == -1) { err = got_error_from_errno("asprintf"); ondisk_path = NULL; break; } err = report_single_file_status(pe->path, ondisk_path, fileindex, status_cb, status_arg, repo, report_unchanged, ignores, no_ignores); if (err) break; free(ondisk_path); ondisk_path = NULL; } free(ondisk_path); return err; } static const struct got_error * worktree_status(struct got_worktree *worktree, const char *path, struct got_fileindex *fileindex, struct got_repository *repo, got_worktree_status_cb status_cb, void *status_arg, got_cancel_cb cancel_cb, void *cancel_arg, int no_ignores, int report_unchanged) { const struct got_error *err = NULL; int fd = -1; struct got_fileindex_diff_dir_cb fdiff_cb; struct diff_dir_cb_arg arg; char *ondisk_path = NULL; struct got_pathlist_head ignores, missing_children; struct got_fileindex_entry *ie; RB_INIT(&ignores); RB_INIT(&missing_children); if (asprintf(&ondisk_path, "%s%s%s", worktree->root_path, path[0] ? "/" : "", path) == -1) return got_error_from_errno("asprintf"); ie = got_fileindex_entry_get(fileindex, path, strlen(path)); if (ie) { err = report_single_file_status(path, ondisk_path, fileindex, status_cb, status_arg, repo, report_unchanged, &ignores, no_ignores); goto done; } else { struct find_missing_children_args fmca; fmca.parent_path = path; fmca.parent_len = strlen(path); fmca.children = &missing_children; fmca.cancel_cb = cancel_cb; fmca.cancel_arg = cancel_arg; err = got_fileindex_for_each_entry_safe(fileindex, find_missing_children, &fmca); if (err) goto done; } fd = open(ondisk_path, O_RDONLY | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC); if (fd == -1) { if (errno != ENOTDIR && errno != ENOENT && errno != EACCES && !got_err_open_nofollow_on_symlink()) err = got_error_from_errno2("open", ondisk_path); else { if (!no_ignores) { err = add_ignores_from_parent_paths(&ignores, worktree->root_path, ondisk_path); if (err) goto done; } if (RB_EMPTY(&missing_children)) { err = report_single_file_status(path, ondisk_path, fileindex, status_cb, status_arg, repo, report_unchanged, &ignores, no_ignores); if (err) goto done; } else { err = report_children(&missing_children, worktree, fileindex, repo, (path[0] == '\0'), report_unchanged, &ignores, no_ignores, status_cb, status_arg, cancel_cb, cancel_arg); if (err) goto done; } } } else { fdiff_cb.diff_old_new = status_old_new; fdiff_cb.diff_old = status_old; fdiff_cb.diff_new = status_new; fdiff_cb.diff_traverse = status_traverse; arg.fileindex = fileindex; arg.worktree = worktree; arg.status_path = path; arg.status_path_len = strlen(path); arg.repo = repo; arg.status_cb = status_cb; arg.status_arg = status_arg; arg.cancel_cb = cancel_cb; arg.cancel_arg = cancel_arg; arg.report_unchanged = report_unchanged; arg.no_ignores = no_ignores; if (!no_ignores) { err = add_ignores_from_parent_paths(&ignores, worktree->root_path, path); if (err) goto done; } arg.ignores = &ignores; err = got_fileindex_diff_dir(fileindex, fd, worktree->root_path, path, repo, &fdiff_cb, &arg); } done: free_ignores(&ignores); if (fd != -1 && close(fd) == -1 && err == NULL) err = got_error_from_errno("close"); free(ondisk_path); return err; } static void free_commitable(struct got_commitable *ct) { free(ct->path); free(ct->in_repo_path); free(ct->ondisk_path); free(ct->blob_id); free(ct->base_blob_id); free(ct->staged_blob_id); free(ct->base_commit_id); free(ct); } struct collect_commitables_arg { struct got_pathlist_head *commitable_paths; struct got_repository *repo; struct got_worktree *worktree; struct got_fileindex *fileindex; int have_staged_files; int allow_bad_symlinks; int diff_header_shown; int commit_conflicts; FILE *diff_outfile; FILE *f1; FILE *f2; }; /* * Create a file which contains the target path of a symlink so we can feed * it as content to the diff engine. */ static const struct got_error * get_symlink_target_file(int *fd, int dirfd, const char *de_name, const char *abspath) { const struct got_error *err = NULL; char target_path[PATH_MAX]; ssize_t target_len, outlen; *fd = -1; if (dirfd != -1) { target_len = readlinkat(dirfd, de_name, target_path, PATH_MAX); if (target_len == -1) return got_error_from_errno2("readlinkat", abspath); } else { target_len = readlink(abspath, target_path, PATH_MAX); if (target_len == -1) return got_error_from_errno2("readlink", abspath); } *fd = got_opentempfd(); if (*fd == -1) return got_error_from_errno("got_opentempfd"); outlen = write(*fd, target_path, target_len); if (outlen == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } if (lseek(*fd, 0, SEEK_SET) == -1) { err = got_error_from_errno2("lseek", abspath); goto done; } done: if (err) { close(*fd); *fd = -1; } return err; } static const struct got_error * append_ct_diff(struct got_commitable *ct, int *diff_header_shown, FILE *diff_outfile, FILE *f1, FILE *f2, int dirfd, const char *de_name, int diff_staged, struct got_repository *repo, struct got_worktree *worktree) { const struct got_error *err = NULL; struct got_blob_object *blob1 = NULL; int fd = -1, fd1 = -1, fd2 = -1; FILE *ondisk_file = NULL; char *label1 = NULL; struct stat sb; off_t size1 = 0; int f2_exists = 0; char *id_str = NULL; memset(&sb, 0, sizeof(sb)); if (diff_staged) { if (ct->staged_status != GOT_STATUS_MODIFY && ct->staged_status != GOT_STATUS_ADD && ct->staged_status != GOT_STATUS_DELETE) return NULL; } else { if (ct->status != GOT_STATUS_MODIFY && ct->status != GOT_STATUS_ADD && ct->status != GOT_STATUS_DELETE && ct->status != GOT_STATUS_CONFLICT) return NULL; } err = got_opentemp_truncate(f1); if (err) return got_error_from_errno("got_opentemp_truncate"); err = got_opentemp_truncate(f2); if (err) return got_error_from_errno("got_opentemp_truncate"); if (!*diff_header_shown) { err = got_object_id_str(&id_str, worktree->base_commit_id); if (err) return err; fprintf(diff_outfile, "diff %s%s\n", diff_staged ? "-s " : "", got_worktree_get_root_path(worktree)); fprintf(diff_outfile, "commit - %s\n", id_str); fprintf(diff_outfile, "path + %s%s\n", got_worktree_get_root_path(worktree), diff_staged ? " (staged changes)" : ""); *diff_header_shown = 1; } if (diff_staged) { const char *label1 = NULL, *label2 = NULL; switch (ct->staged_status) { case GOT_STATUS_MODIFY: label1 = ct->path; label2 = ct->path; break; case GOT_STATUS_ADD: label2 = ct->path; break; case GOT_STATUS_DELETE: label1 = ct->path; break; default: return got_error(GOT_ERR_FILE_STATUS); } fd1 = got_opentempfd(); if (fd1 == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } fd2 = got_opentempfd(); if (fd2 == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } err = got_diff_objects_as_blobs(NULL, NULL, f1, f2, fd1, fd2, ct->base_blob_id, ct->staged_blob_id, label1, label2, GOT_DIFF_ALGORITHM_PATIENCE, 3, 0, 0, NULL, repo, diff_outfile); goto done; } fd1 = got_opentempfd(); if (fd1 == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } if (ct->status != GOT_STATUS_ADD) { err = got_object_open_as_blob(&blob1, repo, ct->base_blob_id, 8192, fd1); if (err) goto done; } if (ct->status != GOT_STATUS_DELETE) { if (dirfd != -1) { fd = openat(dirfd, de_name, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (fd == -1) { if (!got_err_open_nofollow_on_symlink()) { err = got_error_from_errno2("openat", ct->ondisk_path); goto done; } err = get_symlink_target_file(&fd, dirfd, de_name, ct->ondisk_path); if (err) goto done; } } else { fd = open(ct->ondisk_path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (fd == -1) { if (!got_err_open_nofollow_on_symlink()) { err = got_error_from_errno2("open", ct->ondisk_path); goto done; } err = get_symlink_target_file(&fd, dirfd, de_name, ct->ondisk_path); if (err) goto done; } } if (fstatat(fd, ct->ondisk_path, &sb, AT_SYMLINK_NOFOLLOW) == -1) { err = got_error_from_errno2("fstatat", ct->ondisk_path); goto done; } ondisk_file = fdopen(fd, "r"); if (ondisk_file == NULL) { err = got_error_from_errno2("fdopen", ct->ondisk_path); goto done; } fd = -1; f2_exists = 1; } if (blob1) { err = got_object_blob_dump_to_file(&size1, NULL, NULL, f1, blob1); if (err) goto done; } err = got_diff_blob_file(NULL, NULL, blob1, f1, size1, label1, ondisk_file ? ondisk_file : f2, f2_exists, &sb, ct->path, GOT_DIFF_ALGORITHM_PATIENCE, 3, 0, 0, NULL, diff_outfile); done: if (fd1 != -1 && close(fd1) == -1 && err == NULL) err = got_error_from_errno("close"); if (fd2 != -1 && close(fd2) == -1 && err == NULL) err = got_error_from_errno("close"); if (blob1) got_object_blob_close(blob1); if (fd != -1 && close(fd) == -1 && err == NULL) err = got_error_from_errno("close"); if (ondisk_file && fclose(ondisk_file) == EOF && err == NULL) err = got_error_from_errno("fclose"); return err; } static const struct got_error * collect_commitables(void *arg, unsigned char status, unsigned char staged_status, const char *relpath, struct got_object_id *blob_id, struct got_object_id *staged_blob_id, struct got_object_id *commit_id, int dirfd, const char *de_name) { struct collect_commitables_arg *a = arg; const struct got_error *err = NULL; struct got_commitable *ct = NULL; struct got_pathlist_entry *new = NULL; char *parent_path = NULL, *path = NULL; struct stat sb; if (a->have_staged_files) { if (staged_status != GOT_STATUS_MODIFY && staged_status != GOT_STATUS_ADD && staged_status != GOT_STATUS_DELETE) return NULL; } else { if (status == GOT_STATUS_CONFLICT && !a->commit_conflicts) { printf("C %s\n", relpath); return got_error(GOT_ERR_COMMIT_CONFLICT); } if (status != GOT_STATUS_MODIFY && status != GOT_STATUS_MODE_CHANGE && status != GOT_STATUS_ADD && status != GOT_STATUS_DELETE && status != GOT_STATUS_CONFLICT) return NULL; } if (asprintf(&path, "/%s", relpath) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (strcmp(path, "/") == 0) { parent_path = strdup(""); if (parent_path == NULL) return got_error_from_errno("strdup"); } else { err = got_path_dirname(&parent_path, path); if (err) return err; } ct = calloc(1, sizeof(*ct)); if (ct == NULL) { err = got_error_from_errno("calloc"); goto done; } if (asprintf(&ct->ondisk_path, "%s/%s", a->worktree->root_path, relpath) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (staged_status == GOT_STATUS_ADD || staged_status == GOT_STATUS_MODIFY) { struct got_fileindex_entry *ie; ie = got_fileindex_entry_get(a->fileindex, path, strlen(path)); switch (got_fileindex_entry_staged_filetype_get(ie)) { case GOT_FILEIDX_MODE_REGULAR_FILE: case GOT_FILEIDX_MODE_BAD_SYMLINK: ct->mode = S_IFREG; break; case GOT_FILEIDX_MODE_SYMLINK: ct->mode = S_IFLNK; break; default: err = got_error_path(path, GOT_ERR_BAD_FILETYPE); goto done; } ct->mode |= got_fileindex_entry_perms_get(ie); } else if (status != GOT_STATUS_DELETE && staged_status != GOT_STATUS_DELETE) { if (dirfd != -1) { if (fstatat(dirfd, de_name, &sb, AT_SYMLINK_NOFOLLOW) == -1) { err = got_error_from_errno2("fstatat", ct->ondisk_path); goto done; } } else if (lstat(ct->ondisk_path, &sb) == -1) { err = got_error_from_errno2("lstat", ct->ondisk_path); goto done; } ct->mode = sb.st_mode; } if (asprintf(&ct->in_repo_path, "%s%s%s", a->worktree->path_prefix, got_path_is_root_dir(a->worktree->path_prefix) ? "" : "/", relpath) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (S_ISLNK(ct->mode) && staged_status == GOT_STATUS_NO_CHANGE && status == GOT_STATUS_ADD && !a->allow_bad_symlinks) { int is_bad_symlink; char target_path[PATH_MAX]; ssize_t target_len; target_len = readlink(ct->ondisk_path, target_path, sizeof(target_path)); if (target_len == -1) { err = got_error_from_errno2("readlink", ct->ondisk_path); goto done; } err = is_bad_symlink_target(&is_bad_symlink, target_path, target_len, ct->ondisk_path, a->worktree->root_path); if (err) goto done; if (is_bad_symlink) { err = got_error_path(ct->ondisk_path, GOT_ERR_BAD_SYMLINK); goto done; } } ct->status = status; ct->staged_status = staged_status; ct->blob_id = NULL; /* will be filled in when blob gets created */ if (ct->status != GOT_STATUS_ADD && ct->staged_status != GOT_STATUS_ADD) { ct->base_blob_id = got_object_id_dup(blob_id); if (ct->base_blob_id == NULL) { err = got_error_from_errno("got_object_id_dup"); goto done; } ct->base_commit_id = got_object_id_dup(commit_id); if (ct->base_commit_id == NULL) { err = got_error_from_errno("got_object_id_dup"); goto done; } } if (ct->staged_status == GOT_STATUS_ADD || ct->staged_status == GOT_STATUS_MODIFY) { ct->staged_blob_id = got_object_id_dup(staged_blob_id); if (ct->staged_blob_id == NULL) { err = got_error_from_errno("got_object_id_dup"); goto done; } } ct->path = strdup(path); if (ct->path == NULL) { err = got_error_from_errno("strdup"); goto done; } err = got_pathlist_insert(&new, a->commitable_paths, ct->path, ct); if (err) goto done; if (a->diff_outfile && ct && new != NULL) { err = append_ct_diff(ct, &a->diff_header_shown, a->diff_outfile, a->f1, a->f2, dirfd, de_name, a->have_staged_files, a->repo, a->worktree); if (err) goto done; } done: if (ct && (err || new == NULL)) free_commitable(ct); free(parent_path); free(path); return err; } static const struct got_error *write_tree(struct got_object_id **, int *, struct got_tree_object *, const char *, struct got_pathlist_head *, got_worktree_status_cb status_cb, void *status_arg, struct got_repository *); static const struct got_error * write_subtree(struct got_object_id **new_subtree_id, int *nentries, struct got_tree_entry *te, const char *parent_path, struct got_pathlist_head *commitable_paths, got_worktree_status_cb status_cb, void *status_arg, struct got_repository *repo) { const struct got_error *err = NULL; struct got_tree_object *subtree; char *subpath; if (asprintf(&subpath, "%s%s%s", parent_path, got_path_is_root_dir(parent_path) ? "" : "/", te->name) == -1) return got_error_from_errno("asprintf"); err = got_object_open_as_tree(&subtree, repo, &te->id); if (err) return err; err = write_tree(new_subtree_id, nentries, subtree, subpath, commitable_paths, status_cb, status_arg, repo); got_object_tree_close(subtree); free(subpath); return err; } static const struct got_error * match_ct_parent_path(int *match, struct got_commitable *ct, const char *path) { const struct got_error *err = NULL; char *ct_parent_path = NULL; *match = 0; if (strchr(ct->in_repo_path, '/') == NULL) { *match = got_path_is_root_dir(path); return NULL; } err = got_path_dirname(&ct_parent_path, ct->in_repo_path); if (err) return err; *match = (strcmp(path, ct_parent_path) == 0); free(ct_parent_path); return err; } static mode_t get_ct_file_mode(struct got_commitable *ct) { if (S_ISLNK(ct->mode)) return S_IFLNK; return S_IFREG | (ct->mode & ((S_IRWXU | S_IRWXG | S_IRWXO))); } static const struct got_error * alloc_modified_blob_tree_entry(struct got_tree_entry **new_te, struct got_tree_entry *te, struct got_commitable *ct) { const struct got_error *err = NULL; *new_te = NULL; err = got_object_tree_entry_dup(new_te, te); if (err) goto done; (*new_te)->mode = get_ct_file_mode(ct); if (ct->staged_status == GOT_STATUS_MODIFY) memcpy(&(*new_te)->id, ct->staged_blob_id, sizeof((*new_te)->id)); else memcpy(&(*new_te)->id, ct->blob_id, sizeof((*new_te)->id)); done: if (err && *new_te) { free(*new_te); *new_te = NULL; } return err; } static const struct got_error * alloc_added_blob_tree_entry(struct got_tree_entry **new_te, struct got_commitable *ct) { const struct got_error *err = NULL; char *ct_name = NULL; *new_te = NULL; *new_te = calloc(1, sizeof(**new_te)); if (*new_te == NULL) return got_error_from_errno("calloc"); err = got_path_basename(&ct_name, ct->path); if (err) goto done; if (strlcpy((*new_te)->name, ct_name, sizeof((*new_te)->name)) >= sizeof((*new_te)->name)) { err = got_error(GOT_ERR_NO_SPACE); goto done; } (*new_te)->mode = get_ct_file_mode(ct); if (ct->staged_status == GOT_STATUS_ADD) memcpy(&(*new_te)->id, ct->staged_blob_id, sizeof((*new_te)->id)); else memcpy(&(*new_te)->id, ct->blob_id, sizeof((*new_te)->id)); done: free(ct_name); if (err && *new_te) { free(*new_te); *new_te = NULL; } return err; } static const struct got_error * insert_tree_entry(struct got_tree_entry *new_te, struct got_pathlist_head *paths) { const struct got_error *err = NULL; struct got_pathlist_entry *new_pe; err = got_pathlist_insert(&new_pe, paths, new_te->name, new_te); if (err) return err; if (new_pe == NULL) return got_error(GOT_ERR_TREE_DUP_ENTRY); return NULL; } static const struct got_error * report_ct_status(struct got_commitable *ct, got_worktree_status_cb status_cb, void *status_arg) { const char *ct_path = ct->path; unsigned char status; if (status_cb == NULL) /* no commit progress output desired */ return NULL; while (ct_path[0] == '/') ct_path++; if (ct->staged_status != GOT_STATUS_NO_CHANGE) status = ct->staged_status; else status = ct->status; return (*status_cb)(status_arg, status, GOT_STATUS_NO_CHANGE, ct_path, ct->blob_id, NULL, NULL, -1, NULL); } static const struct got_error * match_modified_subtree(int *modified, struct got_tree_entry *te, const char *base_tree_path, struct got_pathlist_head *commitable_paths) { const struct got_error *err = NULL; struct got_pathlist_entry *pe; char *te_path; *modified = 0; if (asprintf(&te_path, "%s%s%s", base_tree_path, got_path_is_root_dir(base_tree_path) ? "" : "/", te->name) == -1) return got_error_from_errno("asprintf"); RB_FOREACH(pe, got_pathlist_head, commitable_paths) { struct got_commitable *ct = pe->data; *modified = got_path_is_child(ct->in_repo_path, te_path, strlen(te_path)); if (*modified) break; } free(te_path); return err; } static const struct got_error * match_deleted_or_modified_ct(struct got_commitable **ctp, struct got_tree_entry *te, const char *base_tree_path, struct got_pathlist_head *commitable_paths) { const struct got_error *err = NULL; struct got_pathlist_entry *pe; *ctp = NULL; RB_FOREACH(pe, got_pathlist_head, commitable_paths) { struct got_commitable *ct = pe->data; char *ct_name = NULL; int path_matches; if (ct->staged_status == GOT_STATUS_NO_CHANGE) { if (ct->status != GOT_STATUS_MODIFY && ct->status != GOT_STATUS_MODE_CHANGE && ct->status != GOT_STATUS_DELETE && ct->status != GOT_STATUS_CONFLICT) continue; } else { if (ct->staged_status != GOT_STATUS_MODIFY && ct->staged_status != GOT_STATUS_DELETE) continue; } if (got_object_id_cmp(ct->base_blob_id, &te->id) != 0) continue; err = match_ct_parent_path(&path_matches, ct, base_tree_path); if (err) return err; if (!path_matches) continue; err = got_path_basename(&ct_name, pe->path); if (err) return err; if (strcmp(te->name, ct_name) != 0) { free(ct_name); continue; } free(ct_name); *ctp = ct; break; } return err; } static const struct got_error * make_subtree_for_added_blob(struct got_tree_entry **new_tep, const char *child_path, const char *path_base_tree, struct got_pathlist_head *commitable_paths, got_worktree_status_cb status_cb, void *status_arg, struct got_repository *repo) { const struct got_error *err = NULL; struct got_tree_entry *new_te; char *subtree_path; struct got_object_id *id = NULL; int nentries; *new_tep = NULL; if (asprintf(&subtree_path, "%s%s%s", path_base_tree, got_path_is_root_dir(path_base_tree) ? "" : "/", child_path) == -1) return got_error_from_errno("asprintf"); new_te = calloc(1, sizeof(*new_te)); if (new_te == NULL) return got_error_from_errno("calloc"); new_te->mode = S_IFDIR; if (strlcpy(new_te->name, child_path, sizeof(new_te->name)) >= sizeof(new_te->name)) { err = got_error(GOT_ERR_NO_SPACE); goto done; } err = write_tree(&id, &nentries, NULL, subtree_path, commitable_paths, status_cb, status_arg, repo); if (err) { free(new_te); goto done; } memcpy(&new_te->id, id, sizeof(new_te->id)); done: free(id); free(subtree_path); if (err == NULL) *new_tep = new_te; return err; } static const struct got_error * write_tree(struct got_object_id **new_tree_id, int *nentries, struct got_tree_object *base_tree, const char *path_base_tree, struct got_pathlist_head *commitable_paths, got_worktree_status_cb status_cb, void *status_arg, struct got_repository *repo) { const struct got_error *err = NULL; struct got_pathlist_head paths; struct got_tree_entry *te, *new_te = NULL; struct got_pathlist_entry *pe; RB_INIT(&paths); *nentries = 0; /* Insert, and recurse into, newly added entries first. */ RB_FOREACH(pe, got_pathlist_head, commitable_paths) { struct got_commitable *ct = pe->data; char *child_path = NULL, *slash; if ((ct->status != GOT_STATUS_ADD && ct->staged_status != GOT_STATUS_ADD) || (ct->flags & GOT_COMMITABLE_ADDED)) continue; if (!got_path_is_child(ct->in_repo_path, path_base_tree, strlen(path_base_tree))) continue; err = got_path_skip_common_ancestor(&child_path, path_base_tree, ct->in_repo_path); if (err) goto done; slash = strchr(child_path, '/'); if (slash == NULL) { err = alloc_added_blob_tree_entry(&new_te, ct); if (err) goto done; err = report_ct_status(ct, status_cb, status_arg); if (err) goto done; ct->flags |= GOT_COMMITABLE_ADDED; err = insert_tree_entry(new_te, &paths); if (err) goto done; (*nentries)++; } else { *slash = '\0'; /* trim trailing path components */ if (base_tree == NULL || got_object_tree_find_entry(base_tree, child_path) == NULL) { err = make_subtree_for_added_blob(&new_te, child_path, path_base_tree, commitable_paths, status_cb, status_arg, repo); if (err) goto done; err = insert_tree_entry(new_te, &paths); if (err) goto done; (*nentries)++; } } } if (base_tree) { int i, nbase_entries; /* Handle modified and deleted entries. */ nbase_entries = got_object_tree_get_nentries(base_tree); for (i = 0; i < nbase_entries; i++) { struct got_commitable *ct = NULL; te = got_object_tree_get_entry(base_tree, i); if (got_object_tree_entry_is_submodule(te)) { /* Entry is a submodule; just copy it. */ err = got_object_tree_entry_dup(&new_te, te); if (err) goto done; err = insert_tree_entry(new_te, &paths); if (err) goto done; (*nentries)++; continue; } if (S_ISDIR(te->mode)) { int modified; err = got_object_tree_entry_dup(&new_te, te); if (err) goto done; err = match_modified_subtree(&modified, te, path_base_tree, commitable_paths); if (err) goto done; /* Avoid recursion into unmodified subtrees. */ if (modified) { struct got_object_id *new_id; int nsubentries; err = write_subtree(&new_id, &nsubentries, te, path_base_tree, commitable_paths, status_cb, status_arg, repo); if (err) goto done; if (nsubentries == 0) { /* All entries were deleted. */ free(new_id); continue; } memcpy(&new_te->id, new_id, sizeof(new_te->id)); free(new_id); } err = insert_tree_entry(new_te, &paths); if (err) goto done; (*nentries)++; continue; } err = match_deleted_or_modified_ct(&ct, te, path_base_tree, commitable_paths); if (err) goto done; if (ct) { /* NB: Deleted entries get dropped here. */ if (ct->status == GOT_STATUS_MODIFY || ct->status == GOT_STATUS_MODE_CHANGE || ct->status == GOT_STATUS_CONFLICT || ct->staged_status == GOT_STATUS_MODIFY) { err = alloc_modified_blob_tree_entry( &new_te, te, ct); if (err) goto done; err = insert_tree_entry(new_te, &paths); if (err) goto done; (*nentries)++; } err = report_ct_status(ct, status_cb, status_arg); if (err) goto done; } else { /* Entry is unchanged; just copy it. */ err = got_object_tree_entry_dup(&new_te, te); if (err) goto done; err = insert_tree_entry(new_te, &paths); if (err) goto done; (*nentries)++; } } } /* Write new list of entries; deleted entries have been dropped. */ err = got_object_tree_create(new_tree_id, &paths, *nentries, repo); done: got_pathlist_free(&paths, GOT_PATHLIST_FREE_NONE); return err; } static const struct got_error * update_fileindex_after_commit(struct got_worktree *worktree, struct got_pathlist_head *commitable_paths, struct got_object_id *new_base_commit_id, struct got_fileindex *fileindex, int have_staged_files) { const struct got_error *err = NULL; struct got_pathlist_entry *pe; char *relpath = NULL; RB_FOREACH(pe, got_pathlist_head, commitable_paths) { struct got_fileindex_entry *ie; struct got_commitable *ct = pe->data; ie = got_fileindex_entry_get(fileindex, pe->path, pe->path_len); err = got_path_skip_common_ancestor(&relpath, worktree->root_path, ct->ondisk_path); if (err) goto done; if (ie) { if (ct->status == GOT_STATUS_DELETE || ct->staged_status == GOT_STATUS_DELETE) { got_fileindex_entry_remove(fileindex, ie); } else if (ct->staged_status == GOT_STATUS_ADD || ct->staged_status == GOT_STATUS_MODIFY) { got_fileindex_entry_stage_set(ie, GOT_FILEIDX_STAGE_NONE); got_fileindex_entry_staged_filetype_set(ie, 0); err = got_fileindex_entry_update(ie, worktree->root_fd, relpath, ct->staged_blob_id, new_base_commit_id, !have_staged_files); } else err = got_fileindex_entry_update(ie, worktree->root_fd, relpath, ct->blob_id, new_base_commit_id, !have_staged_files); } else { err = got_fileindex_entry_alloc(&ie, pe->path); if (err) goto done; err = got_fileindex_entry_update(ie, worktree->root_fd, relpath, ct->blob_id, new_base_commit_id, 1); if (err) { got_fileindex_entry_free(ie); goto done; } err = got_fileindex_entry_add(fileindex, ie); if (err) { got_fileindex_entry_free(ie); goto done; } } free(relpath); relpath = NULL; } done: free(relpath); return err; } static const struct got_error * check_out_of_date(const char *in_repo_path, unsigned char status, unsigned char staged_status, struct got_object_id *base_blob_id, struct got_object_id *base_commit_id, struct got_object_id *head_commit_id, struct got_repository *repo, int ood_errcode) { const struct got_error *err = NULL; struct got_commit_object *commit = NULL; struct got_object_id *id = NULL; if (status != GOT_STATUS_ADD && staged_status != GOT_STATUS_ADD) { /* Trivial case: base commit == head commit */ if (got_object_id_cmp(base_commit_id, head_commit_id) == 0) return NULL; /* * Ensure file content which local changes were based * on matches file content in the branch head. */ err = got_object_open_as_commit(&commit, repo, head_commit_id); if (err) goto done; err = got_object_id_by_path(&id, repo, commit, in_repo_path); if (err) { if (err->code == GOT_ERR_NO_TREE_ENTRY) err = got_error(ood_errcode); goto done; } else if (got_object_id_cmp(id, base_blob_id) != 0) err = got_error(ood_errcode); } else { /* Require that added files don't exist in the branch head. */ err = got_object_open_as_commit(&commit, repo, head_commit_id); if (err) goto done; err = got_object_id_by_path(&id, repo, commit, in_repo_path); if (err && err->code != GOT_ERR_NO_TREE_ENTRY) goto done; err = id ? got_error(ood_errcode) : NULL; } done: free(id); if (commit) got_object_commit_close(commit); return err; } static const struct got_error * commit_worktree(struct got_object_id **new_commit_id, struct got_pathlist_head *commitable_paths, struct got_object_id *head_commit_id, struct got_object_id *parent_id2, struct got_worktree *worktree, const char *author, const char *committer, char *diff_path, got_worktree_commit_msg_cb commit_msg_cb, void *commit_arg, got_worktree_status_cb status_cb, void *status_arg, struct got_repository *repo) { const struct got_error *err = NULL; struct got_pathlist_entry *pe; struct got_commit_object *head_commit = NULL; struct got_tree_object *head_tree = NULL; struct got_object_id *new_tree_id = NULL; int nentries, nparents = 0; struct got_object_id_queue parent_ids; struct got_object_qid *pid = NULL; char *logmsg = NULL; time_t timestamp; *new_commit_id = NULL; STAILQ_INIT(&parent_ids); err = got_object_open_as_commit(&head_commit, repo, head_commit_id); if (err) goto done; err = got_object_open_as_tree(&head_tree, repo, head_commit->tree_id); if (err) goto done; if (commit_msg_cb != NULL) { err = commit_msg_cb(commitable_paths, diff_path, &logmsg, commit_arg); if (err) goto done; } if (logmsg == NULL || strlen(logmsg) == 0) { err = got_error(GOT_ERR_COMMIT_MSG_EMPTY); goto done; } /* Create blobs from added and modified files and record their IDs. */ RB_FOREACH(pe, got_pathlist_head, commitable_paths) { struct got_commitable *ct = pe->data; char *ondisk_path; /* Blobs for staged files already exist. */ if (ct->staged_status == GOT_STATUS_ADD || ct->staged_status == GOT_STATUS_MODIFY) continue; if (ct->status != GOT_STATUS_ADD && ct->status != GOT_STATUS_MODIFY && ct->status != GOT_STATUS_MODE_CHANGE && ct->status != GOT_STATUS_CONFLICT) continue; if (asprintf(&ondisk_path, "%s/%s", worktree->root_path, pe->path) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = got_object_blob_create(&ct->blob_id, ondisk_path, repo); free(ondisk_path); if (err) goto done; } /* Recursively write new tree objects. */ err = write_tree(&new_tree_id, &nentries, head_tree, "/", commitable_paths, status_cb, status_arg, repo); if (err) goto done; err = got_object_qid_alloc(&pid, head_commit_id); if (err) goto done; STAILQ_INSERT_TAIL(&parent_ids, pid, entry); nparents++; if (parent_id2) { err = got_object_qid_alloc(&pid, parent_id2); if (err) goto done; STAILQ_INSERT_TAIL(&parent_ids, pid, entry); nparents++; } timestamp = time(NULL); err = got_object_commit_create(new_commit_id, new_tree_id, &parent_ids, nparents, author, timestamp, committer, timestamp, logmsg, repo); if (logmsg != NULL) free(logmsg); if (err) goto done; done: got_object_id_queue_free(&parent_ids); if (head_tree) got_object_tree_close(head_tree); if (head_commit) got_object_commit_close(head_commit); return err; } static const struct got_error * check_path_is_commitable(const char *path, struct got_pathlist_head *commitable_paths) { struct got_pathlist_entry *cpe = NULL; size_t path_len = strlen(path); RB_FOREACH(cpe, got_pathlist_head, commitable_paths) { struct got_commitable *ct = cpe->data; const char *ct_path = ct->path; while (ct_path[0] == '/') ct_path++; if (strcmp(path, ct_path) == 0 || got_path_is_child(ct_path, path, path_len)) break; } if (cpe == NULL) return got_error_path(path, GOT_ERR_BAD_PATH); return NULL; } static const struct got_error * check_staged_file(void *arg, struct got_fileindex_entry *ie) { int *have_staged_files = arg; if (got_fileindex_entry_stage_get(ie) != GOT_FILEIDX_STAGE_NONE) { *have_staged_files = 1; return got_error(GOT_ERR_CANCELLED); } return NULL; } static const struct got_error * check_non_staged_files(struct got_fileindex *fileindex, struct got_pathlist_head *paths) { struct got_pathlist_entry *pe; struct got_fileindex_entry *ie; RB_FOREACH(pe, got_pathlist_head, paths) { if (pe->path[0] == '\0') continue; ie = got_fileindex_entry_get(fileindex, pe->path, pe->path_len); if (ie == NULL) return got_error_path(pe->path, GOT_ERR_BAD_PATH); if (got_fileindex_entry_stage_get(ie) == GOT_FILEIDX_STAGE_NONE) return got_error_path(pe->path, GOT_ERR_FILE_NOT_STAGED); } return NULL; } static void print_load_info(int print_colored, int print_found, int print_trees, int ncolored, int nfound, int ntrees) { if (print_colored) { printf("%d commit%s colored", ncolored, ncolored == 1 ? "" : "s"); } if (print_found) { printf("%s%d object%s found", ncolored > 0 ? "; " : "", nfound, nfound == 1 ? "" : "s"); } if (print_trees) { printf("; %d tree%s scanned", ntrees, ntrees == 1 ? "" : "s"); } } struct got_send_progress_arg { char last_scaled_packsize[FMT_SCALED_STRSIZE]; int verbosity; int last_ncolored; int last_nfound; int last_ntrees; int loading_done; int last_ncommits; int last_nobj_total; int last_p_deltify; int last_p_written; int last_p_sent; int printed_something; int sent_something; struct got_pathlist_head *delete_branches; }; static const struct got_error * send_progress(void *arg, int ncolored, int nfound, int ntrees, off_t packfile_size, int ncommits, int nobj_total, int nobj_deltify, int nobj_written, off_t bytes_sent, const char *refname, const char *errmsg, int success) { struct got_send_progress_arg *a = arg; char scaled_packsize[FMT_SCALED_STRSIZE]; char scaled_sent[FMT_SCALED_STRSIZE]; int p_deltify = 0, p_written = 0, p_sent = 0; int print_colored = 0, print_found = 0, print_trees = 0; int print_searching = 0, print_total = 0; int print_deltify = 0, print_written = 0, print_sent = 0; if (a->verbosity < 0) return NULL; if (refname) { const char *status = success ? "accepted" : "rejected"; if (success) { struct got_pathlist_entry *pe; RB_FOREACH(pe, got_pathlist_head, a->delete_branches) { const char *branchname = pe->path; if (got_path_cmp(branchname, refname, strlen(branchname), strlen(refname)) == 0) { status = "deleted"; a->sent_something = 1; break; } } } if (a->printed_something) putchar('\n'); printf("Server has %s %s", status, refname); if (errmsg) printf(": %s", errmsg); a->printed_something = 1; return NULL; } if (a->last_ncolored != ncolored) { print_colored = 1; a->last_ncolored = ncolored; } if (a->last_nfound != nfound) { print_colored = 1; print_found = 1; a->last_nfound = nfound; } if (a->last_ntrees != ntrees) { print_colored = 1; print_found = 1; print_trees = 1; a->last_ntrees = ntrees; } if ((print_colored || print_found || print_trees) && !a->loading_done) { printf("\r"); print_load_info(print_colored, print_found, print_trees, ncolored, nfound, ntrees); a->printed_something = 1; fflush(stdout); return NULL; } else if (!a->loading_done) { printf("\r"); print_load_info(1, 1, 1, ncolored, nfound, ntrees); printf("\n"); a->loading_done = 1; } if (fmt_scaled(packfile_size, scaled_packsize) == -1) return got_error_from_errno("fmt_scaled"); if (fmt_scaled(bytes_sent, scaled_sent) == -1) return got_error_from_errno("fmt_scaled"); if (a->last_ncommits != ncommits) { print_searching = 1; a->last_ncommits = ncommits; } if (a->last_nobj_total != nobj_total) { print_searching = 1; print_total = 1; a->last_nobj_total = nobj_total; } if (packfile_size > 0 && (a->last_scaled_packsize[0] == '\0' || strcmp(scaled_packsize, a->last_scaled_packsize)) != 0) { if (strlcpy(a->last_scaled_packsize, scaled_packsize, FMT_SCALED_STRSIZE) >= FMT_SCALED_STRSIZE) return got_error(GOT_ERR_NO_SPACE); } if (nobj_deltify > 0 || nobj_written > 0) { if (nobj_deltify > 0) { p_deltify = (nobj_deltify * 100) / nobj_total; if (p_deltify != a->last_p_deltify) { a->last_p_deltify = p_deltify; print_searching = 1; print_total = 1; print_deltify = 1; } } if (nobj_written > 0) { p_written = (nobj_written * 100) / nobj_total; if (p_written != a->last_p_written) { a->last_p_written = p_written; print_searching = 1; print_total = 1; print_deltify = 1; print_written = 1; } } } if (bytes_sent > 0) { p_sent = (bytes_sent * 100) / packfile_size; if (p_sent != a->last_p_sent) { a->last_p_sent = p_sent; print_searching = 1; print_total = 1; print_deltify = 1; print_written = 1; print_sent = 1; } a->sent_something = 1; } if (print_searching || print_total || print_deltify || print_written || print_sent) printf("\r"); if (print_searching) printf("packing %d reference%s", ncommits, ncommits == 1 ? "" : "s"); if (print_total) printf("; %d object%s", nobj_total, nobj_total == 1 ? "" : "s"); if (print_deltify) printf("; deltify: %d%%", p_deltify); if (print_sent) printf("; uploading pack: %*s %d%%", FMT_SCALED_STRSIZE - 2, scaled_packsize, p_sent); else if (print_written) printf("; writing pack: %*s %d%%", FMT_SCALED_STRSIZE - 2, scaled_packsize, p_written); if (print_searching || print_total || print_deltify || print_written || print_sent) { a->printed_something = 1; fflush(stdout); } return NULL; } struct got_fetch_progress_arg { char last_scaled_size[FMT_SCALED_STRSIZE]; int last_p_indexed; int last_p_resolved; int verbosity; struct got_repository *repo; }; static const struct got_error * fetch_progress(void *arg, const char *message, off_t packfile_size, int nobj_total, int nobj_indexed, int nobj_loose, int nobj_resolved) { struct got_fetch_progress_arg *a = arg; char scaled_size[FMT_SCALED_STRSIZE]; int p_indexed, p_resolved; int print_size = 0, print_indexed = 0, print_resolved = 0; if (a->verbosity < 0) return NULL; if (message && message[0] != '\0') { printf("\rserver: %s", message); fflush(stdout); return NULL; } if (packfile_size > 0 || nobj_indexed > 0) { if (fmt_scaled(packfile_size, scaled_size) == 0 && (a->last_scaled_size[0] == '\0' || strcmp(scaled_size, a->last_scaled_size)) != 0) { print_size = 1; if (strlcpy(a->last_scaled_size, scaled_size, FMT_SCALED_STRSIZE) >= FMT_SCALED_STRSIZE) return got_error(GOT_ERR_NO_SPACE); } if (nobj_indexed > 0) { p_indexed = (nobj_indexed * 100) / nobj_total; if (p_indexed != a->last_p_indexed) { a->last_p_indexed = p_indexed; print_indexed = 1; print_size = 1; } } if (nobj_resolved > 0) { p_resolved = (nobj_resolved * 100) / (nobj_total - nobj_loose); if (p_resolved != a->last_p_resolved) { a->last_p_resolved = p_resolved; print_resolved = 1; print_indexed = 1; print_size = 1; } } } if (print_size || print_indexed || print_resolved) printf("\r"); if (print_size) printf("%*s fetched", FMT_SCALED_STRSIZE - 2, scaled_size); if (print_indexed) printf("; indexing %d%%", p_indexed); if (print_resolved) printf("; resolving deltas %d%%", p_resolved); if (print_size || print_indexed || print_resolved) { putchar('\n'); fflush(stdout); } return NULL; } static const struct got_error * create_symref(const char *refname, struct got_reference *target_ref, int verbosity, struct got_repository *repo) { const struct got_error *err; struct got_reference *head_symref; err = got_ref_alloc_symref(&head_symref, refname, target_ref); if (err) return err; err = got_ref_write(head_symref, repo); if (err == NULL && verbosity > 0) { printf("Created reference %s: %s\n", GOT_REF_HEAD, got_ref_get_name(target_ref)); } got_ref_close(head_symref); return err; } static const struct got_error * create_ref(const char *refname, struct got_object_id *id, int verbosity, struct got_repository *repo) { const struct got_error *err = NULL; struct got_reference *ref; char *id_str; err = got_object_id_str(&id_str, id); if (err) return err; err = got_ref_alloc(&ref, refname, id); if (err) goto done; err = got_ref_write(ref, repo); got_ref_close(ref); if (err == NULL && verbosity >= 0) printf("Created reference %s: %s\n", refname, id_str); done: free(id_str); return err; } static const struct got_error * update_ref(struct got_reference *ref, struct got_object_id *new_id, int verbosity, struct got_repository *repo) { const struct got_error *err = NULL; char *new_id_str = NULL; struct got_object_id *old_id = NULL; err = got_object_id_str(&new_id_str, new_id); if (err) goto done; if (strncmp(got_ref_get_name(ref), "refs/tags/", 10) == 0) { err = got_ref_resolve(&old_id, repo, ref); if (err) goto done; if (got_object_id_cmp(old_id, new_id) == 0) goto done; if (verbosity >= 0) { printf("Rejecting update of existing tag %s: %s\n", got_ref_get_name(ref), new_id_str); } goto done; } if (got_ref_is_symbolic(ref)) { if (verbosity >= 0) { printf("Replacing reference %s: %s\n", got_ref_get_name(ref), got_ref_get_symref_target(ref)); } err = got_ref_change_symref_to_ref(ref, new_id); if (err) goto done; err = got_ref_write(ref, repo); if (err) goto done; } else { err = got_ref_resolve(&old_id, repo, ref); if (err) goto done; if (got_object_id_cmp(old_id, new_id) == 0) goto done; err = got_ref_change_ref(ref, new_id); if (err) goto done; err = got_ref_write(ref, repo); if (err) goto done; } if (verbosity >= 0) printf("Updated %s: %s\n", got_ref_get_name(ref), new_id_str); done: free(old_id); free(new_id_str); return err; } static const struct got_error * fetch_updated_remote(const char *proto, const char *host, const char *port, const char *server_path, const char *jumphost, const char *identity_file, int verbosity, const struct got_remote_repo *remote, struct got_repository *repo, struct got_reference *head_ref, const char *head_refname) { const struct got_error *err = NULL, *unlock_err = NULL; struct got_pathlist_entry *pe; struct got_pathlist_head learned_refs; struct got_pathlist_head symrefs; struct got_pathlist_head wanted_branches; struct got_pathlist_head wanted_refs; struct got_object_id *pack_hash; struct got_fetch_progress_arg fpa; int fetchfd = -1; pid_t fetchpid = -1; RB_INIT(&learned_refs); RB_INIT(&symrefs); RB_INIT(&wanted_branches); RB_INIT(&wanted_refs); err = got_pathlist_insert(NULL, &wanted_branches, head_refname, NULL); if (err) goto done; err = got_fetch_connect(&fetchpid, &fetchfd, proto, host, port, server_path, jumphost, identity_file, verbosity); if (err) goto done; fpa.last_scaled_size[0] = '\0'; fpa.last_p_indexed = -1; fpa.last_p_resolved = -1; fpa.verbosity = verbosity; fpa.repo = repo; err = got_fetch_pack(&pack_hash, &learned_refs, &symrefs, remote->name, 1, 0, &wanted_branches, &wanted_refs, 0, verbosity, fetchfd, repo, head_refname, NULL, 0, fetch_progress, &fpa); if (err) goto done; /* Update references provided with the pack file. */ RB_FOREACH(pe, got_pathlist_head, &learned_refs) { const char *refname = pe->path; struct got_object_id *id = pe->data; struct got_reference *ref; err = got_ref_open(&ref, repo, refname, 0); if (err) { if (err->code != GOT_ERR_NOT_REF) goto done; err = create_ref(refname, id, verbosity, repo); if (err) goto done; } else { err = update_ref(ref, id, verbosity, repo); unlock_err = got_ref_unlock(ref); if (unlock_err && err == NULL) err = unlock_err; got_ref_close(ref); if (err) goto done; } } /* Set the HEAD reference if the server provided one. */ RB_FOREACH(pe, got_pathlist_head, &symrefs) { struct got_reference *target_ref; const char *refname = pe->path; const char *target = pe->data; char *remote_refname = NULL, *remote_target = NULL; if (strcmp(refname, GOT_REF_HEAD) != 0) continue; err = got_ref_open(&target_ref, repo, target, 0); if (err) { if (err->code == GOT_ERR_NOT_REF) { err = NULL; continue; } goto done; } err = create_symref(refname, target_ref, verbosity, repo); got_ref_close(target_ref); if (err) goto done; if (remote->mirror_references) continue; if (strncmp("refs/heads/", target, 11) != 0) continue; if (asprintf(&remote_refname, "refs/remotes/%s/%s", GOT_FETCH_DEFAULT_REMOTE_NAME, refname) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (asprintf(&remote_target, "refs/remotes/%s/%s", GOT_FETCH_DEFAULT_REMOTE_NAME, target + 11) == -1) { err = got_error_from_errno("asprintf"); free(remote_refname); goto done; } err = got_ref_open(&target_ref, repo, remote_target, 0); if (err) { free(remote_refname); free(remote_target); if (err->code == GOT_ERR_NOT_REF) { err = NULL; continue; } goto done; } err = create_symref(remote_refname, target_ref, verbosity - 1, repo); free(remote_refname); free(remote_target); got_ref_close(target_ref); if (err) goto done; } done: got_pathlist_free(&learned_refs, GOT_PATHLIST_FREE_NONE); got_pathlist_free(&symrefs, GOT_PATHLIST_FREE_NONE); got_pathlist_free(&wanted_branches, GOT_PATHLIST_FREE_NONE); got_pathlist_free(&wanted_refs, GOT_PATHLIST_FREE_NONE); return err; } const struct got_error * got_worktree_cvg_commit(struct got_object_id **new_commit_id, struct got_worktree *worktree, struct got_pathlist_head *paths, const char *author, const char *committer, int allow_bad_symlinks, int show_diff, int commit_conflicts, got_worktree_commit_msg_cb commit_msg_cb, void *commit_arg, got_worktree_status_cb status_cb, void *status_arg, const char *proto, const char *host, const char *port, const char *server_path, const char *jumphost, const char *identity_file, int verbosity, const struct got_remote_repo *remote, got_cancel_cb check_cancelled, struct got_repository *repo) { const struct got_error *err = NULL, *unlockerr = NULL, *sync_err; struct got_fileindex *fileindex = NULL; char *fileindex_path = NULL; struct got_pathlist_head commitable_paths; struct collect_commitables_arg cc_arg; struct got_pathlist_entry *pe; struct got_reference *head_ref = NULL, *head_ref2 = NULL; struct got_reference *commit_ref = NULL; struct got_object_id *head_commit_id = NULL; struct got_object_id *head_commit_id2 = NULL; char *head_refname = NULL; char *commit_refname = NULL; char *diff_path = NULL; int have_staged_files = 0; int sendfd = -1; pid_t sendpid = -1; struct got_send_progress_arg spa; struct got_pathlist_head commit_reflist; struct got_pathlist_head tag_names; struct got_pathlist_head delete_branches; *new_commit_id = NULL; memset(&cc_arg, 0, sizeof(cc_arg)); RB_INIT(&commitable_paths); RB_INIT(&commit_reflist); RB_INIT(&tag_names); RB_INIT(&delete_branches); err = lock_worktree(worktree, LOCK_EX); if (err) goto done; err = got_worktree_cvg_get_commit_ref_name(&commit_refname, worktree); if (err) goto done; head_refname = worktree->head_ref_name; err = got_ref_open(&head_ref, repo, head_refname, 0); if (err) goto done; err = got_ref_resolve(&head_commit_id, repo, head_ref); if (err) goto done; err = got_ref_alloc(&commit_ref, commit_refname, head_commit_id); if (err) goto done; err = got_ref_write(commit_ref, repo); if (err) goto done; err = open_fileindex(&fileindex, &fileindex_path, worktree, got_repo_get_object_format(repo)); if (err) goto done; err = got_fileindex_for_each_entry_safe(fileindex, check_staged_file, &have_staged_files); if (err && err->code != GOT_ERR_CANCELLED) goto done; if (have_staged_files) { err = check_non_staged_files(fileindex, paths); if (err) goto done; } cc_arg.commitable_paths = &commitable_paths; cc_arg.worktree = worktree; cc_arg.fileindex = fileindex; cc_arg.repo = repo; cc_arg.have_staged_files = have_staged_files; cc_arg.allow_bad_symlinks = allow_bad_symlinks; cc_arg.diff_header_shown = 0; cc_arg.commit_conflicts = commit_conflicts; if (show_diff) { err = got_opentemp_named(&diff_path, &cc_arg.diff_outfile, GOT_TMPDIR_STR "/got", ".diff"); if (err) goto done; cc_arg.f1 = got_opentemp(); if (cc_arg.f1 == NULL) { err = got_error_from_errno("got_opentemp"); goto done; } cc_arg.f2 = got_opentemp(); if (cc_arg.f2 == NULL) { err = got_error_from_errno("got_opentemp"); goto done; } } RB_FOREACH(pe, got_pathlist_head, paths) { err = worktree_status(worktree, pe->path, fileindex, repo, collect_commitables, &cc_arg, NULL, NULL, 0, 0); if (err) goto done; } if (show_diff) { if (fflush(cc_arg.diff_outfile) == EOF) { err = got_error_from_errno("fflush"); goto done; } } if (RB_EMPTY(&commitable_paths)) { err = got_error(GOT_ERR_COMMIT_NO_CHANGES); goto done; } RB_FOREACH(pe, got_pathlist_head, paths) { err = check_path_is_commitable(pe->path, &commitable_paths); if (err) goto done; } RB_FOREACH(pe, got_pathlist_head, &commitable_paths) { struct got_commitable *ct = pe->data; const char *ct_path = ct->in_repo_path; while (ct_path[0] == '/') ct_path++; err = check_out_of_date(ct_path, ct->status, ct->staged_status, ct->base_blob_id, ct->base_commit_id, head_commit_id, repo, GOT_ERR_COMMIT_OUT_OF_DATE); if (err) goto done; } err = commit_worktree(new_commit_id, &commitable_paths, head_commit_id, NULL, worktree, author, committer, (diff_path && cc_arg.diff_header_shown) ? diff_path : NULL, commit_msg_cb, commit_arg, status_cb, status_arg, repo); if (err) goto done; /* * Check if a concurrent commit to our branch has occurred. * Lock the reference here to prevent concurrent modification. */ err = got_ref_open(&head_ref2, repo, head_refname, 1); if (err) goto done; err = got_ref_resolve(&head_commit_id2, repo, head_ref2); if (err) goto done; if (got_object_id_cmp(head_commit_id, head_commit_id2) != 0) { err = got_error(GOT_ERR_COMMIT_HEAD_CHANGED); goto done; } err = got_pathlist_insert(&pe, &commit_reflist, commit_refname, head_refname); if (err) goto done; /* Update commit ref in repository. */ err = got_ref_change_ref(commit_ref, *new_commit_id); if (err) goto done; err = got_ref_write(commit_ref, repo); if (err) goto done; if (verbosity >= 0) { printf("Connecting to \"%s\" %s://%s%s%s%s%s\n", remote->name, proto, host, port ? ":" : "", port ? port : "", *server_path == '/' ? "" : "/", server_path); } /* Attempt send to remote branch. */ err = got_send_connect(&sendpid, &sendfd, proto, host, port, server_path, jumphost, identity_file, verbosity); if (err) goto done; memset(&spa, 0, sizeof(spa)); spa.last_scaled_packsize[0] = '\0'; spa.last_p_deltify = -1; spa.last_p_written = -1; spa.verbosity = verbosity; spa.delete_branches = &delete_branches; err = got_send_pack(remote->name, &commit_reflist, &tag_names, &delete_branches, verbosity, 0, sendfd, repo, send_progress, &spa, check_cancelled, NULL); if (spa.printed_something) putchar('\n'); if (err != NULL && err->code == GOT_ERR_SEND_ANCESTRY) { /* * Fetch new changes since remote has diverged. * No trivial-rebase yet; require update to be run manually. */ err = fetch_updated_remote(proto, host, port, server_path, jumphost, identity_file, verbosity, remote, repo, head_ref, head_refname); if (err == NULL) goto done; err = got_error(GOT_ERR_COMMIT_OUT_OF_DATE); goto done; /* XXX: Rebase commit over fetched remote branch. */ } if (err) { goto done; } /* Update branch head in repository. */ err = got_ref_change_ref(head_ref2, *new_commit_id); if (err) goto done; err = got_ref_write(head_ref2, repo); if (err) goto done; err = got_worktree_set_base_commit_id(worktree, repo, *new_commit_id); if (err) goto done; err = ref_base_commit(worktree, repo); if (err) goto done; /* XXX: fileindex must be updated for other fetched changes? */ err = update_fileindex_after_commit(worktree, &commitable_paths, *new_commit_id, fileindex, have_staged_files); sync_err = sync_fileindex(fileindex, fileindex_path); if (sync_err && err == NULL) err = sync_err; done: if (head_ref2) { unlockerr = got_ref_unlock(head_ref2); if (unlockerr && err == NULL) err = unlockerr; got_ref_close(head_ref2); } if (commit_ref) got_ref_close(commit_ref); if (fileindex) got_fileindex_free(fileindex); unlockerr = lock_worktree(worktree, LOCK_SH); if (unlockerr && err == NULL) err = unlockerr; RB_FOREACH(pe, got_pathlist_head, &commitable_paths) { struct got_commitable *ct = pe->data; free_commitable(ct); } got_pathlist_free(&commitable_paths, GOT_PATHLIST_FREE_NONE); if (diff_path && unlink(diff_path) == -1 && err == NULL) err = got_error_from_errno2("unlink", diff_path); if (cc_arg.diff_outfile && fclose(cc_arg.diff_outfile) == EOF && err == NULL) err = got_error_from_errno("fclose"); free(head_commit_id); free(head_commit_id2); free(commit_refname); free(fileindex_path); free(diff_path); return err; } const struct got_error * got_worktree_cvg_get_commit_ref_name(char **refname, struct got_worktree *worktree) { return get_ref_name(refname, worktree, GOT_WORKTREE_COMMIT_REF_PREFIX); } got-portable-0.111/lib/got_lib_object_create.h0000644000175000017500000000260715001740614015006 /* * Copyright (c) 2019 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ const struct got_error *got_object_blob_file_create(struct got_object_id **, FILE **, off_t *, const char *, struct got_repository *); const struct got_error *got_object_blob_create(struct got_object_id **, const char *, struct got_repository *); const struct got_error *got_object_tree_create(struct got_object_id **, struct got_pathlist_head *, int, struct got_repository *); const struct got_error *got_object_commit_create(struct got_object_id **, struct got_object_id *, struct got_object_id_queue *, int, const char *, time_t, const char *, time_t, const char *, struct got_repository *); got-portable-0.111/lib/error.c0000644000175000017500000004474215001741021011641 /* * Copyright (c) 2018 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include "got_compat.h" #include "got_error.h" #include "got_object.h" #include "got_lib_delta.h" #include "got_lib_inflate.h" #include "got_lib_object.h" #include "got_lib_hash.h" #include "got_lib_object_parse.h" #ifndef nitems #define nitems(_a) (sizeof(_a) / sizeof((_a)[0])) #endif #if defined(__GLIBC__) /* * The autoconf test for strerror_r is broken in current versions * of autoconf: https://savannah.gnu.org/support/?110367 */ char *__xpg_strerror_r(int, char *, size_t); #define strerror_r __xpg_strerror_r #endif static const struct got_error got_errors[] = { { GOT_ERR_OK, "no error occurred?!?" }, { GOT_ERR_ERRNO, "see errno" }, { GOT_ERR_NOT_GIT_REPO, "no git repository found" }, { GOT_ERR_BAD_FILETYPE, "bad file type" }, { GOT_ERR_BAD_PATH, "bad path" }, { GOT_ERR_NOT_REF, "no such reference found" }, { GOT_ERR_IO, "input/output error" }, { GOT_ERR_EOF, "unexpected end of file" }, { GOT_ERR_DECOMPRESSION,"decompression failed" }, { GOT_ERR_NO_SPACE, "buffer too small" }, { GOT_ERR_BAD_OBJ_HDR, "bad object header" }, { GOT_ERR_OBJ_TYPE, "wrong type of object" }, { GOT_ERR_BAD_OBJ_DATA, "bad object data" }, { GOT_ERR_AMBIGUOUS_ID, "ambiguous object ID" }, { GOT_ERR_BAD_PACKIDX, "bad pack index file" }, { GOT_ERR_PACKIDX_CSUM, "pack index file checksum error" }, { GOT_ERR_BAD_PACKFILE, "bad pack file" }, { GOT_ERR_NO_OBJ, "object not found" }, { GOT_ERR_NOT_IMPL, "feature not implemented" }, { GOT_ERR_OBJ_NOT_PACKED,"object is not packed" }, { GOT_ERR_BAD_DELTA_CHAIN,"bad delta chain" }, { GOT_ERR_BAD_DELTA, "bad delta" }, { GOT_ERR_COMPRESSION, "compression failed" }, { GOT_ERR_BAD_OBJ_ID_STR,"bad object id string" }, { GOT_ERR_WORKTREE_EXISTS,"worktree already exists" }, { GOT_ERR_WORKTREE_META,"bad worktree meta data" }, { GOT_ERR_WORKTREE_VERS,"unsupported worktree format version" }, { GOT_ERR_WORKTREE_BUSY,"worktree already locked" }, { GOT_ERR_FILE_OBSTRUCTED,"file is obstructed" }, { GOT_ERR_RECURSION, "recursion limit reached" }, { GOT_ERR_TIMEOUT, "operation timed out" }, { GOT_ERR_INTERRUPT, "operation interrupted" }, { GOT_ERR_PRIVSEP_READ, "no data received in imsg" }, { GOT_ERR_PRIVSEP_LEN, "unexpected amount of data received in imsg" }, { GOT_ERR_PRIVSEP_PIPE, "privsep peer process closed pipe" }, { GOT_ERR_PRIVSEP_NO_FD,"privsep file descriptor unavailable" }, { GOT_ERR_PRIVSEP_MSG, "received unexpected privsep message" }, { GOT_ERR_PRIVSEP_DIED, "unprivileged process died unexpectedly" }, { GOT_ERR_PRIVSEP_EXIT, "bad exit code from unprivileged process" }, { GOT_ERR_PACK_OFFSET, "bad offset in pack file" }, { GOT_ERR_OBJ_EXISTS, "object already exists" }, { GOT_ERR_BAD_OBJ_ID, "bad object id" }, { GOT_ERR_OBJECT_FORMAT, "object format not supported" }, { GOT_ERR_ITER_COMPLETED,"iteration completed" }, { GOT_ERR_RANGE, "value out of range" }, { GOT_ERR_EXPECTED, "expected an error but have no error" }, { GOT_ERR_CANCELLED, "operation in progress has been cancelled" }, { GOT_ERR_NO_TREE_ENTRY,"no such entry found in tree" }, { GOT_ERR_FILEIDX_SIG, "bad file index signature" }, { GOT_ERR_FILEIDX_VER, "unknown file index format version" }, { GOT_ERR_FILEIDX_CSUM, "bad file index checksum" }, { GOT_ERR_PATH_PREFIX, "worktree already contains items from a " "different path prefix" }, { GOT_ERR_ANCESTRY, "target commit is on a different branch" }, { GOT_ERR_FILEIDX_BAD, "file index is corrupt" }, { GOT_ERR_BAD_REF_DATA, "could not parse reference data" }, { GOT_ERR_TREE_DUP_ENTRY,"duplicate entry in tree object" }, { GOT_ERR_DIR_DUP_ENTRY,"duplicate entry in directory" }, { GOT_ERR_NOT_WORKTREE, "no work tree found" }, { GOT_ERR_UUID_VERSION, "bad uuid version" }, { GOT_ERR_UUID_INVALID, "uuid invalid" }, { GOT_ERR_UUID, "uuid error" }, { GOT_ERR_LOCKFILE_TIMEOUT,"lockfile timeout" }, { GOT_ERR_BAD_REF_NAME, "bad reference name" }, { GOT_ERR_WORKTREE_REPO,"cannot create worktree inside a git repository" }, { GOT_ERR_FILE_MODIFIED,"file contains modifications" }, { GOT_ERR_FILE_STATUS, "file has unexpected status" }, { GOT_ERR_COMMIT_CONFLICT,"cannot commit file in conflicted status" }, { GOT_ERR_BAD_REF_TYPE, "bad reference type" }, { GOT_ERR_COMMIT_NO_AUTHOR,"GOT_AUTHOR environment variable is not set" }, { GOT_ERR_COMMIT_HEAD_CHANGED, "branch head in repository has changed " "while commit was in progress" }, { GOT_ERR_COMMIT_OUT_OF_DATE, "work tree must be updated before these " "changes can be committed" }, { GOT_ERR_COMMIT_MSG_EMPTY, "commit message cannot be empty" }, { GOT_ERR_DIR_NOT_EMPTY, "directory exists and is not empty" }, { GOT_ERR_COMMIT_NO_CHANGES, "no changes to commit" }, { GOT_ERR_BRANCH_MOVED, "work tree's head reference now points to a " "different branch; new head reference and/or update -b required" }, { GOT_ERR_OBJ_TOO_LARGE, "object too large" }, { GOT_ERR_SAME_BRANCH, "commit is already contained in this branch" }, { GOT_ERR_ROOT_COMMIT, "specified commit has no parent commit" }, { GOT_ERR_MIXED_COMMITS,"work tree contains files from multiple " "base commits; the entire work tree must be updated first" }, { GOT_ERR_CONFLICTS, "work tree contains conflicted files; these " "conflicts must be resolved first" }, { GOT_ERR_BRANCH_EXISTS,"specified branch already exists" }, { GOT_ERR_MODIFIED, "work tree contains local changes; these " "changes must be committed or reverted first" }, { GOT_ERR_NOT_REBASING, "rebase operation not in progress" }, { GOT_ERR_REBASE_COMMITID,"rebase commit ID mismatch" }, { GOT_ERR_WRONG_BRANCH, "update -b required" }, { GOT_ERR_REBASING, "a rebase operation is in progress in this " "work tree and must be continued or aborted first" }, { GOT_ERR_REBASE_PATH, "cannot rebase branch which contains " "changes outside of this work tree's path prefix" }, { GOT_ERR_NOT_HISTEDIT, "histedit operation not in progress" }, { GOT_ERR_EMPTY_HISTEDIT,"no commits to edit; perhaps the work tree " "must be updated to an older commit first" }, { GOT_ERR_NO_HISTEDIT_CMD,"no histedit commands provided" }, { GOT_ERR_HISTEDIT_SYNTAX,"syntax error in histedit command list" }, { GOT_ERR_HISTEDIT_CANCEL,"histedit operation cancelled" }, { 95, "unused error code" }, { GOT_ERR_HISTEDIT_BUSY,"histedit operation is in progress in this " "work tree and must be continued or aborted first" }, { GOT_ERR_HISTEDIT_CMD, "bad histedit command" }, { GOT_ERR_HISTEDIT_PATH, "cannot edit branch history which contains " "changes outside of this work tree's path prefix" }, { GOT_ERR_PACKFILE_CSUM, "pack file checksum error" }, { GOT_ERR_COMMIT_BRANCH, "will not commit to a branch outside the " "\"refs/heads/\" reference namespace" }, { GOT_ERR_FILE_STAGED, "file is staged" }, { GOT_ERR_STAGE_NO_CHANGE, "no changes to stage" }, { GOT_ERR_STAGE_CONFLICT, "cannot stage file in conflicted status" }, { GOT_ERR_STAGE_OUT_OF_DATE, "work tree must be updated before " "changes can be staged" }, { GOT_ERR_FILE_NOT_STAGED, "file is not staged" }, { GOT_ERR_STAGED_PATHS, "work tree contains files with staged " "changes; these changes must be committed or unstaged first" }, { GOT_ERR_PATCH_CHOICE, "invalid patch choice" }, { GOT_ERR_COMMIT_NO_EMAIL, "commit author's email address is required " "for compatibility with Git" }, { GOT_ERR_TAG_EXISTS,"specified tag already exists" }, { GOT_ERR_GIT_REPO_FORMAT,"unknown git repository format version" }, { GOT_ERR_REBASE_REQUIRED,"specified branch must be rebased first" }, { GOT_ERR_REGEX, "regular expression error" }, { GOT_ERR_REF_NAME_MINUS, "reference name may not start with '-'" }, { GOT_ERR_GITCONFIG_SYNTAX, "gitconfig syntax error" }, { GOT_ERR_REBASE_OUT_OF_DATE, "work tree must be updated before it " "can be used to rebase a branch" }, { GOT_ERR_CACHE_DUP_ENTRY, "duplicate cache entry" }, { GOT_ERR_FETCH_FAILED, "fetch failed" }, { GOT_ERR_PARSE_URI, "failed to parse uri" }, { GOT_ERR_BAD_PROTO, "unknown protocol" }, { GOT_ERR_ADDRINFO, "getaddrinfo failed" }, { GOT_ERR_BAD_PACKET, "bad packet received" }, { GOT_ERR_NO_REMOTE, "remote repository not found" }, { GOT_ERR_FETCH_NO_BRANCH, "could not find any branches to fetch" }, { GOT_ERR_FETCH_BAD_REF, "reference cannot be fetched" }, { GOT_ERR_TREE_ENTRY_TYPE, "unexpected tree entry type" }, { GOT_ERR_PARSE_CONFIG, "configuration file syntax error" }, { GOT_ERR_NO_CONFIG_FILE, "configuration file doesn't exit" }, { GOT_ERR_BAD_SYMLINK, "symbolic link points outside of paths under " "version control" }, { GOT_ERR_GIT_REPO_EXT, "unsupported repository format extension" }, { GOT_ERR_CANNOT_PACK, "not enough objects to pack" }, { GOT_ERR_LONELY_PACKIDX, "pack index has no corresponding pack file; " "pack file must be restored or 'gotadmin cleanup -p' must be run" }, { GOT_ERR_OBJ_CSUM, "bad object checksum" }, { GOT_ERR_SEND_BAD_REF, "reference cannot be sent" }, { GOT_ERR_SEND_FAILED, "could not send pack file" }, { GOT_ERR_SEND_EMPTY, "no references to send" }, { GOT_ERR_SEND_ANCESTRY, "branch on server has a different ancestry; either fetch changes from server and then rebase or merge local branch before sending, or ignore ancestry with send -f (can lead to data loss on server)" }, { GOT_ERR_CAPA_DELETE_REFS, "server cannot delete references" }, { GOT_ERR_SEND_DELETE_REF, "reference cannot be deleted" }, { GOT_ERR_SEND_TAG_EXISTS, "tag already exists on server" }, { GOT_ERR_NOT_MERGING, "merge operation not in progress" }, { GOT_ERR_MERGE_OUT_OF_DATE, "work tree must be updated before it " "can be used to merge a branch" }, { GOT_ERR_MERGE_STAGED_PATHS, "work tree contains files with staged " "changes; these changes must be unstaged before merging can " "proceed" }, { GOT_ERR_MERGE_BUSY,"a merge operation is in progress in this " "work tree and must be continued or aborted first" }, { GOT_ERR_MERGE_PATH, "cannot merge branch which contains " "changes outside of this work tree's path prefix" }, { GOT_ERR_FILE_BINARY, "found a binary file instead of text" }, { GOT_ERR_PATCH_MALFORMED, "malformed patch" }, { GOT_ERR_PATCH_TRUNCATED, "patch truncated" }, { GOT_ERR_NO_PATCH, "no patch found" }, { GOT_ERR_HUNK_FAILED, "hunk failed to apply" }, { GOT_ERR_PATCH_FAILED, "patch failed to apply" }, { GOT_ERR_FILEIDX_DUP_ENTRY, "duplicate file index entry" }, { GOT_ERR_PIN_PACK, "could not pin pack file" }, { GOT_ERR_BAD_TAG_SIGNATURE, "invalid tag signature" }, { GOT_ERR_VERIFY_TAG_SIGNATURE, "cannot verify signature" }, { GOT_ERR_SIGNING_TAG, "unable to sign tag" }, { GOT_ERR_BAD_OPTION, "option cannot be used" }, { GOT_ERR_BAD_QUERYSTRING, "invalid query string" }, { GOT_ERR_INTEGRATE_BRANCH, "will not integrate into a reference " "outside the \"refs/heads/\" reference namespace" }, { GOT_ERR_BAD_REQUEST, "unexpected request received" }, { GOT_ERR_CLIENT_ID, "unknown client identifier" }, { GOT_ERR_REPO_TEMPFILE, "no repository tempfile available" }, { GOT_ERR_REFS_PROTECTED, "reference namespace is protected" }, { GOT_ERR_REF_PROTECTED, "reference is protected" }, { GOT_ERR_REF_BUSY, "reference cannot be updated; please try again" }, { GOT_ERR_COMMIT_BAD_AUTHOR, "commit author formatting would " "make Git unhappy" }, { GOT_ERR_UID, "bad user ID" }, { GOT_ERR_GID, "bad group ID" }, { GOT_ERR_NO_PROG, "command not found or not accessible" }, { GOT_ERR_MERGE_COMMIT_OUT_OF_DATE, "merging cannot proceed because " "the work tree is no longer up-to-date; merge must be aborted " "and retried" }, { GOT_ERR_BUNDLE_FORMAT, "unknown git bundle version" }, { GOT_ERR_BAD_KEYWORD, "invalid commit keyword" }, { GOT_ERR_UNKNOWN_CAPA, "unknown capability" }, { GOT_ERR_REF_DUP_ENTRY, "duplicate reference entry" }, { GOT_ERR_DIFF_NOCHANGES, "no changes match the requested diff" }, { GOT_ERR_USER, "no such user" }, { GOT_ERR_USER_EXISTS, "user already exists" }, { GOT_ERR_GROUP, "no such group" }, { GOT_ERR_GROUP_EXISTS, "group already exists" }, { GOT_ERR_AUTHORIZED_KEY, "no authorized key found" }, { GOT_ERR_CONNECTION_LIMIT, "connection limit exceeded" }, }; static struct got_custom_error { struct got_error err; char msg[GOT_ERR_MAX_MSG_SIZE]; } custom_errors[16]; static struct got_custom_error * get_custom_err(void) { static unsigned int idx; return &custom_errors[(idx++) % nitems(custom_errors)]; } const struct got_error * got_error(int code) { size_t i; for (i = 0; i < nitems(got_errors); i++) { if (code == got_errors[i].code) return &got_errors[i]; } abort(); } const struct got_error * got_error_msg(int code, const char *msg) { struct got_custom_error *cerr = get_custom_err(); struct got_error *err = &cerr->err; size_t i; for (i = 0; i < nitems(got_errors); i++) { if (code == got_errors[i].code) { err->code = code; strlcpy(cerr->msg, msg, sizeof(cerr->msg)); err->msg = cerr->msg; return err; } } abort(); } const struct got_error * got_error_from_errno(const char *prefix) { struct got_custom_error *cerr = get_custom_err(); struct got_error *err = &cerr->err; char strerr[128]; strerror_r(errno, strerr, sizeof(strerr)); snprintf(cerr->msg, sizeof(cerr->msg), "%s: %s", prefix, strerr); err->code = GOT_ERR_ERRNO; err->msg = cerr->msg; return err; } const struct got_error * got_error_from_errno2(const char *prefix, const char *prefix2) { struct got_custom_error *cerr = get_custom_err(); struct got_error *err = &cerr->err; char strerr[128]; strerror_r(errno, strerr, sizeof(strerr)); snprintf(cerr->msg, sizeof(cerr->msg), "%s: %s: %s", prefix, prefix2, strerr); err->code = GOT_ERR_ERRNO; err->msg = cerr->msg; return err; } const struct got_error * got_error_from_errno3(const char *prefix, const char *prefix2, const char *prefix3) { struct got_custom_error *cerr = get_custom_err(); struct got_error *err = &cerr->err; char strerr[128]; strerror_r(errno, strerr, sizeof(strerr)); snprintf(cerr->msg, sizeof(cerr->msg), "%s: %s: %s: %s", prefix, prefix2, prefix3, strerr); err->code = GOT_ERR_ERRNO; err->msg = cerr->msg; return err; } const struct got_error * got_error_from_errno_fmt(const char *fmt, ...) { struct got_custom_error *cerr = get_custom_err(); struct got_error *err = &cerr->err; char buf[PATH_MAX * 4]; char strerr[128]; va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); strerror_r(errno, strerr, sizeof(strerr)); snprintf(cerr->msg, sizeof(cerr->msg), "%s: %s", buf, strerr); err->code = GOT_ERR_ERRNO; err->msg = cerr->msg; return err; } const struct got_error * got_error_set_errno(int code, const char *prefix) { errno = code; return got_error_from_errno(prefix); } const struct got_error * got_ferror(FILE *f, int code) { if (ferror(f)) return got_error_from_errno(""); return got_error(code); } const struct got_error * got_error_no_obj(struct got_object_id *id) { char id_str[GOT_OBJECT_ID_HEX_MAXLEN]; char msg[sizeof("object not found") + sizeof(id_str)]; int ret; if (!got_object_id_hex(id, id_str, sizeof(id_str))) return got_error(GOT_ERR_NO_OBJ); ret = snprintf(msg, sizeof(msg), "object %s not found", id_str); if (ret < 0 || (size_t)ret >= sizeof(msg)) return got_error(GOT_ERR_NO_OBJ); return got_error_msg(GOT_ERR_NO_OBJ, msg); } const struct got_error * got_error_checksum(struct got_object_id *id) { char id_str[GOT_OBJECT_ID_HEX_MAXLEN]; char msg[sizeof("checksum failure for object ") + sizeof(id_str)]; int ret; if (!got_object_id_hex(id, id_str, sizeof(id_str))) return got_error(GOT_ERR_OBJ_CSUM); ret = snprintf(msg, sizeof(msg), "checksum failure for object %s", id_str); if (ret < 0 || (size_t)ret >= sizeof(msg)) return got_error(GOT_ERR_OBJ_CSUM); return got_error_msg(GOT_ERR_OBJ_CSUM, msg); } const struct got_error * got_error_not_ref(const char *refname) { char msg[sizeof("reference not found") + 1004]; int ret; ret = snprintf(msg, sizeof(msg), "reference %s not found", refname); if (ret < 0 || (size_t)ret >= sizeof(msg)) return got_error(GOT_ERR_NOT_REF); return got_error_msg(GOT_ERR_NOT_REF, msg); } const struct got_error * got_error_uuid(uint32_t uuid_status, const char *prefix) { switch (uuid_status) { case uuid_s_ok: return NULL; case uuid_s_bad_version: return got_error(GOT_ERR_UUID_VERSION); case uuid_s_invalid_string_uuid: return got_error(GOT_ERR_UUID_INVALID); case uuid_s_no_memory: return got_error_set_errno(ENOMEM, prefix); default: return got_error(GOT_ERR_UUID); } } const struct got_error * got_error_path(const char *path, int code) { struct got_custom_error *cerr = get_custom_err(); struct got_error *err = &cerr->err; size_t i; for (i = 0; i < nitems(got_errors); i++) { if (code == got_errors[i].code) { err->code = code; snprintf(cerr->msg, sizeof(cerr->msg), "%s: %s", path, got_errors[i].msg); err->msg = cerr->msg; return err; } } abort(); } const struct got_error * got_error_fmt(int code, const char *fmt, ...) { struct got_custom_error *cerr = get_custom_err(); struct got_error *err = &cerr->err; char buf[PATH_MAX * 4]; va_list ap; size_t i; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); for (i = 0; i < nitems(got_errors); i++) { if (code == got_errors[i].code) { err->code = code; snprintf(cerr->msg, sizeof(cerr->msg), "%s: %s", buf, got_errors[i].msg); err->msg = cerr->msg; return err; } } abort(); } int got_err_open_nofollow_on_symlink(void) { /* * Check whether open(2) with O_NOFOLLOW failed on a symlink. * Posix mandates ELOOP and OpenBSD follows it. Others return * different error codes. We carry this workaround to help the * portable version a little. */ return (errno == ELOOP #ifdef EMLINK || errno == EMLINK #endif #ifdef EFTYPE || errno == EFTYPE #endif ); } got-portable-0.111/lib/murmurhash2.c0000644000175000017500000000234315001740614012763 //----------------------------------------------------------------------------- // MurmurHash2 was written by Austin Appleby, and is placed in the public // domain. The author hereby disclaims copyright to this source code. /* Obtained from https://github.com/aappleby/smhasher */ #include #include #include "murmurhash2.h" uint32_t murmurhash2(const unsigned char * key, int len, uint32_t seed) { // 'm' and 'r' are mixing constants generated offline. // They're not really 'magic', they just happen to work well. const uint32_t m = 0x5bd1e995; const int r = 24; // Initialize the hash to a 'random' value uint32_t h = seed ^ len; // Mix 4 bytes at a time into the hash const unsigned char *data = key; while(len >= 4) { uint32_t k; memcpy(&k, data, sizeof(k)); k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; data += 4; len -= 4; } // Handle the last few bytes of the input array switch(len) { case 3: h ^= data[2] << 16; case 2: h ^= data[1] << 8; case 1: h ^= data[0]; h *= m; }; // Do a few final mixes of the hash to ensure the last few // bytes are well-incorporated. h ^= h >> 13; h *= m; h ^= h >> 15; return h; } got-portable-0.111/lib/patch.c0000644000175000017500000006500715001741021011604 /* * Copyright (c) 2022 Omar Polo * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Apply patches. * * Things that we may want to support: * + support indented patches? * + support other kinds of patches? */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_object.h" #include "got_path.h" #include "got_reference.h" #include "got_cancel.h" #include "got_worktree.h" #include "got_repository.h" #include "got_opentemp.h" #include "got_patch.h" #include "got_diff.h" #include "got_lib_delta.h" #include "got_lib_diff.h" #include "got_lib_hash.h" #include "got_lib_object.h" #include "got_lib_privsep.h" #ifndef MIN #define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif struct got_patch_line { char mode; char *line; size_t len; }; struct got_patch_hunk { STAILQ_ENTRY(got_patch_hunk) entries; const struct got_error *err; int ws_mangled; int offset; int old_nonl; int new_nonl; int old_from; int old_lines; int new_from; int new_lines; size_t len; size_t cap; struct got_patch_line *lines; }; STAILQ_HEAD(got_patch_hunk_head, got_patch_hunk); struct got_patch { int xbit; char *old; char *new; char cid[GOT_HASH_DIGEST_STRING_MAXLEN]; char blob[GOT_HASH_DIGEST_STRING_MAXLEN]; struct got_patch_hunk_head head; }; struct patch_args { got_patch_progress_cb progress_cb; void *progress_arg; struct got_patch_hunk_head *head; }; static mode_t apply_umask(mode_t mode) { mode_t um; um = umask(000); umask(um); return mode & ~um; } static const struct got_error * send_patch(struct imsgbuf *ibuf, int fd) { const struct got_error *err = NULL; if (imsg_compose(ibuf, GOT_IMSG_PATCH_FILE, 0, 0, fd, NULL, 0) == -1) { err = got_error_from_errno( "imsg_compose GOT_IMSG_PATCH_FILE"); close(fd); return err; } return got_privsep_flush_imsg(ibuf); } static void patch_free(struct got_patch *p) { struct got_patch_hunk *h; size_t i; while (!STAILQ_EMPTY(&p->head)) { h = STAILQ_FIRST(&p->head); STAILQ_REMOVE_HEAD(&p->head, entries); for (i = 0; i < h->len; ++i) free(h->lines[i].line); free(h->lines); free(h); } free(p->new); free(p->old); memset(p, 0, sizeof(*p)); STAILQ_INIT(&p->head); } static const struct got_error * pushline(struct got_patch_hunk *h, const char *line, size_t len) { void *t; size_t newcap; if (h->len == h->cap) { if ((newcap = h->cap * 1.5) == 0) newcap = 16; t = recallocarray(h->lines, h->cap, newcap, sizeof(h->lines[0])); if (t == NULL) return got_error_from_errno("recallocarray"); h->lines = t; h->cap = newcap; } if ((t = malloc(len - 1)) == NULL) return got_error_from_errno("malloc"); memcpy(t, line + 1, len - 1); /* skip the line type */ h->lines[h->len].mode = *line; h->lines[h->len].line = t; h->lines[h->len].len = len - 2; /* line type and trailing NUL */ h->len++; return NULL; } static const struct got_error * recv_patch(struct imsgbuf *ibuf, int *done, struct got_patch *p, int strip) { const struct got_error *err = NULL; struct imsg imsg; struct got_imsg_patch_hunk hdr; struct got_imsg_patch patch; struct got_patch_hunk *h = NULL; size_t datalen; int lastmode = -1; memset(p, 0, sizeof(*p)); STAILQ_INIT(&p->head); err = got_privsep_recv_imsg(&imsg, ibuf, 0); if (err) return err; if (imsg.hdr.type == GOT_IMSG_PATCH_EOF) { *done = 1; goto done; } if (imsg.hdr.type != GOT_IMSG_PATCH) { err = got_error(GOT_ERR_PRIVSEP_MSG); goto done; } datalen = imsg.hdr.len - IMSG_HEADER_SIZE; if (datalen != sizeof(patch)) { err = got_error(GOT_ERR_PRIVSEP_LEN); goto done; } memcpy(&patch, imsg.data, sizeof(patch)); if (patch.old[sizeof(patch.old)-1] != '\0' || patch.new[sizeof(patch.new)-1] != '\0' || patch.cid[sizeof(patch.cid)-1] != '\0' || patch.blob[sizeof(patch.blob)-1] != '\0') { err = got_error(GOT_ERR_PRIVSEP_LEN); goto done; } if (*patch.cid != '\0') strlcpy(p->cid, patch.cid, sizeof(p->cid)); if (*patch.blob != '\0') strlcpy(p->blob, patch.blob, sizeof(p->blob)); p->xbit = patch.xbit; /* automatically set strip=1 for git-style diffs */ if (strip == -1 && patch.git && (*patch.old == '\0' || !strncmp(patch.old, "a/", 2)) && (*patch.new == '\0' || !strncmp(patch.new, "b/", 2))) strip = 1; /* prefer the new name if not /dev/null for not git-style diffs */ if (!patch.git && *patch.new != '\0' && *patch.old != '\0') { err = got_path_strip(&p->old, patch.new, strip); if (err) goto done; } else if (*patch.old != '\0') { err = got_path_strip(&p->old, patch.old, strip); if (err) goto done; } if (*patch.new != '\0') { err = got_path_strip(&p->new, patch.new, strip); if (err) goto done; } if (p->old == NULL && p->new == NULL) { err = got_error(GOT_ERR_PATCH_MALFORMED); goto done; } imsg_free(&imsg); for (;;) { char *t; err = got_privsep_recv_imsg(&imsg, ibuf, 0); if (err) { patch_free(p); return err; } datalen = imsg.hdr.len - IMSG_HEADER_SIZE; switch (imsg.hdr.type) { case GOT_IMSG_PATCH_DONE: if (h != NULL && h->len == 0) err = got_error(GOT_ERR_PATCH_MALFORMED); goto done; case GOT_IMSG_PATCH_HUNK: if (h != NULL && (h->len == 0 || h->old_nonl || h->new_nonl)) { err = got_error(GOT_ERR_PATCH_MALFORMED); goto done; } lastmode = -1; if (datalen != sizeof(hdr)) { err = got_error(GOT_ERR_PRIVSEP_LEN); goto done; } memcpy(&hdr, imsg.data, sizeof(hdr)); if (hdr.oldfrom < 0 || hdr.newfrom < 0) { err = got_error(GOT_ERR_PRIVSEP_LEN); goto done; } if ((h = calloc(1, sizeof(*h))) == NULL) { err = got_error_from_errno("calloc"); goto done; } h->old_from = hdr.oldfrom; h->old_lines = hdr.oldlines; h->new_from = hdr.newfrom; h->new_lines = hdr.newlines; STAILQ_INSERT_TAIL(&p->head, h, entries); break; case GOT_IMSG_PATCH_LINE: if (h == NULL) { err = got_error(GOT_ERR_PRIVSEP_MSG); goto done; } t = imsg.data; /* at least one char */ if (datalen < 2 || t[datalen-1] != '\0') { err = got_error(GOT_ERR_PRIVSEP_MSG); goto done; } if (*t != ' ' && *t != '-' && *t != '+' && *t != '\\') { err = got_error(GOT_ERR_PRIVSEP_MSG); goto done; } if (*t != '\\') err = pushline(h, t, datalen); else if (lastmode == '-') h->old_nonl = 1; else if (lastmode == '+') h->new_nonl = 1; else err = got_error(GOT_ERR_PATCH_MALFORMED); if (err) goto done; lastmode = *t; break; default: err = got_error(GOT_ERR_PRIVSEP_MSG); goto done; } imsg_free(&imsg); } done: if (err) patch_free(p); imsg_free(&imsg); return err; } static void reverse_patch(struct got_patch *p) { struct got_patch_hunk *h; size_t i; int tmp; STAILQ_FOREACH(h, &p->head, entries) { tmp = h->old_from; h->old_from = h->new_from; h->new_from = tmp; tmp = h->old_lines; h->old_lines = h->new_lines; h->new_lines = tmp; tmp = h->old_nonl; h->old_nonl = h->new_nonl; h->new_nonl = tmp; for (i = 0; i < h->len; ++i) { if (h->lines[i].mode == '+') h->lines[i].mode = '-'; else if (h->lines[i].mode == '-') h->lines[i].mode = '+'; } } } /* * Copy data from orig starting at copypos until pos into tmp. * If pos is -1, copy until EOF. */ static const struct got_error * copy(FILE *tmp, FILE *orig, off_t copypos, off_t pos) { char buf[BUFSIZ]; size_t len, r, w; if (fseeko(orig, copypos, SEEK_SET) == -1) return got_error_from_errno("fseeko"); while (pos == -1 || copypos < pos) { len = sizeof(buf); if (pos > 0) len = MIN(len, (size_t)pos - copypos); r = fread(buf, 1, len, orig); if (r != len && ferror(orig)) return got_error_from_errno("fread"); w = fwrite(buf, 1, r, tmp); if (w != r) return got_error_from_errno("fwrite"); copypos += len; if (r != len && feof(orig)) { if (pos == -1) return NULL; return got_error(GOT_ERR_HUNK_FAILED); } } return NULL; } static int lines_eq(struct got_patch_line *, const char *, size_t, int *); static const struct got_error * locate_hunk(FILE *orig, struct got_patch_hunk *h, off_t *pos, int *lineno) { const struct got_error *err = NULL; struct got_patch_line *l = &h->lines[0]; char *line = NULL; char mode = l->mode; size_t linesize = 0; ssize_t linelen; off_t match = -1; int mangled = 0, match_lineno = -1; for (;;) { (*lineno)++; linelen = getline(&line, &linesize, orig); if (linelen == -1) { if (ferror(orig)) err = got_error_from_errno("getline"); /* An EOF is fine iff the target file is empty. */ if (feof(orig) && match == -1 && h->old_lines != 0) err = got_error(GOT_ERR_HUNK_FAILED); match = 0; match_lineno = (*lineno)-1; break; } if ((mode == ' ' && lines_eq(l, line, linelen, &mangled)) || (mode == '-' && lines_eq(l, line, linelen, &mangled)) || (mode == '+' && *lineno == h->old_from)) { match = ftello(orig); if (match == -1) { err = got_error_from_errno("ftello"); break; } match -= linelen; match_lineno = (*lineno)-1; } if (*lineno >= h->old_from && match != -1) { if (mangled) h->ws_mangled = 1; break; } } if (err == NULL) { *pos = match; *lineno = match_lineno; if (fseeko(orig, match, SEEK_SET) == -1) err = got_error_from_errno("fseeko"); } free(line); return err; } static int lines_eq(struct got_patch_line *l, const char *b, size_t len, int *mangled) { char *a = l->line; size_t i, j; if (len > 00 && b[len - 1] == '\n') len--; *mangled = 0; if (l->len == len && !memcmp(a, b, len)) return 1; *mangled = 1; i = j = 0; for (;;) { while (i < l->len && (a[i] == '\t' || a[i] == ' ' || a[i] == '\f')) i++; while (j < len && (b[j] == '\t' || b[j] == ' ' || b[j] == '\f')) j++; if (i == l->len || j == len || a[i] != b[j]) break; i++, j++; } return (i == l->len && j == len); } static const struct got_error * test_hunk(FILE *orig, struct got_patch_hunk *h) { const struct got_error *err = NULL; char *line = NULL; size_t linesize = 0, i = 0; ssize_t linelen; int mangled; for (i = 0; i < h->len; ++i) { switch (h->lines[i].mode) { case '+': continue; case ' ': case '-': linelen = getline(&line, &linesize, orig); if (linelen == -1) { if (ferror(orig)) err = got_error_from_errno("getline"); else err = got_error( GOT_ERR_HUNK_FAILED); goto done; } if (!lines_eq(&h->lines[i], line, linelen, &mangled)) { err = got_error(GOT_ERR_HUNK_FAILED); goto done; } if (mangled) h->ws_mangled = 1; break; } } done: free(line); return err; } static const struct got_error * apply_hunk(FILE *orig, FILE *tmp, struct got_patch_hunk *h, int *lineno, off_t from) { const struct got_error *err = NULL; const char *t; size_t linesize = 0, i, new = 0; char *line = NULL; char mode; size_t l; ssize_t linelen; if (orig != NULL && fseeko(orig, from, SEEK_SET) == -1) return got_error_from_errno("fseeko"); for (i = 0; i < h->len; ++i) { switch (mode = h->lines[i].mode) { case '-': case ' ': (*lineno)++; if (orig != NULL) { linelen = getline(&line, &linesize, orig); if (linelen == -1) { err = got_error_from_errno("getline"); goto done; } if (line[linelen - 1] == '\n') line[linelen - 1] = '\0'; t = line; l = linelen - 1; } else { t = h->lines[i].line; l = h->lines[i].len; } if (mode == '-') continue; if (fwrite(t, 1, l, tmp) != l || fputc('\n', tmp) == EOF) { err = got_error_from_errno("fprintf"); goto done; } break; case '+': new++; t = h->lines[i].line; l = h->lines[i].len; if (fwrite(t, 1, l, tmp) != l) { err = got_error_from_errno("fprintf"); goto done; } if (new != h->new_lines || !h->new_nonl) { if (fprintf(tmp, "\n") < 0) { err = got_error_from_errno("fprintf"); goto done; } } break; } } done: free(line); return err; } static const struct got_error * patch_file(struct got_patch *p, FILE *orig, FILE *tmp) { const struct got_error *err = NULL; struct got_patch_hunk *h; struct stat sb; int lineno = 0; off_t copypos, pos; char *line = NULL; size_t linesize = 0; ssize_t linelen; if (p->old == NULL) { /* create */ h = STAILQ_FIRST(&p->head); if (h == NULL || STAILQ_NEXT(h, entries) != NULL) return got_error(GOT_ERR_PATCH_MALFORMED); return apply_hunk(orig, tmp, h, &lineno, 0); } /* When deleting binary files there are no hunks to apply. */ if (p->new == NULL && STAILQ_EMPTY(&p->head)) return NULL; if (fstat(fileno(orig), &sb) == -1) return got_error_from_errno("fstat"); copypos = 0; STAILQ_FOREACH(h, &p->head, entries) { tryagain: err = locate_hunk(orig, h, &pos, &lineno); if (err != NULL && err->code == GOT_ERR_HUNK_FAILED) h->err = err; if (err != NULL) return err; err = copy(tmp, orig, copypos, pos); if (err != NULL) return err; copypos = pos; err = test_hunk(orig, h); if (err != NULL && err->code == GOT_ERR_HUNK_FAILED) { /* * try to apply the hunk again starting the search * after the previous partial match. */ if (fseeko(orig, pos, SEEK_SET) == -1) return got_error_from_errno("fseeko"); linelen = getline(&line, &linesize, orig); if (linelen == -1) return got_error_from_errno("getline"); lineno++; goto tryagain; } if (err != NULL) return err; if (lineno + 1 != h->old_from) h->offset = lineno + 1 - h->old_from; err = apply_hunk(orig, tmp, h, &lineno, pos); if (err != NULL) return err; copypos = ftello(orig); if (copypos == -1) return got_error_from_errno("ftello"); } if (p->new == NULL && sb.st_size != copypos) { h = STAILQ_FIRST(&p->head); h->err = got_error(GOT_ERR_HUNK_FAILED); err = h->err; } else if (!feof(orig)) err = copy(tmp, orig, copypos, -1); return err; } static const struct got_error * report_progress(struct patch_args *pa, const char *old, const char *new, unsigned char status, const struct got_error *orig_error) { const struct got_error *err; struct got_patch_hunk *h; err = pa->progress_cb(pa->progress_arg, old, new, status, orig_error, 0, 0, 0, 0, 0, 0, NULL); if (err) return err; STAILQ_FOREACH(h, pa->head, entries) { if (h->offset == 0 && !h->ws_mangled && h->err == NULL) continue; err = pa->progress_cb(pa->progress_arg, old, new, 0, NULL, h->old_from, h->old_lines, h->new_from, h->new_lines, h->offset, h->ws_mangled, h->err); if (err) return err; } return NULL; } static const struct got_error * patch_delete(void *arg, unsigned char status, unsigned char staged_status, const char *path) { return report_progress(arg, path, NULL, status, NULL); } static const struct got_error * patch_add(void *arg, unsigned char status, const char *path) { return report_progress(arg, NULL, path, status, NULL); } static const struct got_error * open_blob(char **path, FILE **fp, const char *blobid, struct got_repository *repo) { const struct got_error *err = NULL; struct got_blob_object *blob = NULL; struct got_object_id id, *idptr, *matched_id = NULL; enum got_hash_algorithm algo; int fd = -1; *fp = NULL; *path = NULL; algo = got_repo_get_object_format(repo); if (strlen(blobid) != got_hash_digest_string_length(algo) - 1) { err = got_repo_match_object_id(&matched_id, NULL, blobid, GOT_OBJ_TYPE_BLOB, NULL /* do not resolve tags */, repo); if (err) return err; idptr = matched_id; } else { if (!got_parse_object_id(&id, blobid, algo)) return got_error(GOT_ERR_BAD_OBJ_ID_STR); idptr = &id; } fd = got_opentempfd(); if (fd == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } err = got_object_open_as_blob(&blob, repo, idptr, 8192, fd); if (err) goto done; err = got_opentemp_named(path, fp, GOT_TMPDIR_STR "/got-patch-blob", ""); if (err) goto done; err = got_object_blob_dump_to_file(NULL, NULL, NULL, *fp, blob); if (err) goto done; done: if (fd != -1 && close(fd) == -1 && err == NULL) err = got_error_from_errno("close"); if (blob) got_object_blob_close(blob); if (matched_id != NULL) free(matched_id); if (err) { if (*fp != NULL) fclose(*fp); if (*path != NULL) unlink(*path); free(*path); *fp = NULL; *path = NULL; } return err; } static const struct got_error * prepare_merge(int *do_merge, char **apath, FILE **afile, struct got_worktree *worktree, struct got_repository *repo, struct got_patch *p, struct got_object_id *commit_id, struct got_tree_object *tree, const char *path) { const struct got_error *err = NULL; *do_merge = 0; *apath = NULL; *afile = NULL; /* don't run the diff3 merge on creations/deletions */ if (p->old == NULL || p->new == NULL) return NULL; if (commit_id) { struct got_object_id *id; err = got_object_tree_find_path(&id, NULL, repo, tree, path); if (err) return err; got_object_id_hex(id, p->blob, sizeof(p->blob)); got_object_id_hex(commit_id, p->cid, sizeof(p->cid)); free(id); err = open_blob(apath, afile, p->blob, repo); *do_merge = err == NULL; } else if (*p->blob != '\0') { err = open_blob(apath, afile, p->blob, repo); /* * ignore failures to open this blob, we might have * parsed gibberish. */ if (err && !(err->code == GOT_ERR_ERRNO && errno == ENOENT) && err->code != GOT_ERR_NO_OBJ) return err; *do_merge = err == NULL; err = NULL; } return err; } static const struct got_error * apply_patch(int *overlapcnt, struct got_worktree *worktree, struct got_repository *repo, struct got_fileindex *fileindex, const char *old, const char *new, struct got_patch *p, int nop, int reverse, struct got_object_id *commit_id, struct got_tree_object *tree, struct patch_args *pa, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; struct stat sb; int do_merge = 0, file_renamed = 0; char *oldlabel = NULL, *newlabel = NULL, *anclabel = NULL; char *oldpath = NULL, *newpath = NULL; char *tmppath = NULL, *template = NULL; char *apath = NULL, *mergepath = NULL; FILE *oldfile = NULL, *tmpfile = NULL, *afile = NULL, *mergefile = NULL; int outfd; mode_t mode = GOT_DEFAULT_FILE_MODE; *overlapcnt = 0; err = prepare_merge(&do_merge, &apath, &afile, worktree, repo, p, commit_id, tree, old); if (err) return err; if (reverse && !do_merge) reverse_patch(p); if (asprintf(&oldpath, "%s/%s", got_worktree_get_root_path(worktree), old) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (asprintf(&newpath, "%s/%s", got_worktree_get_root_path(worktree), new) == -1) { err = got_error_from_errno("asprintf"); goto done; } file_renamed = strcmp(oldpath, newpath); if (asprintf(&template, "%s/got-patch", got_worktree_get_root_path(worktree)) == -1) { err = got_error_from_errno(template); goto done; } if (p->old != NULL) { if ((oldfile = fopen(oldpath, "r")) == NULL) { err = got_error_from_errno2("open", oldpath); goto done; } if (fstat(fileno(oldfile), &sb) == -1) { err = got_error_from_errno2("fstat", oldpath); goto done; } mode = sb.st_mode; } else if (p->xbit) mode |= (S_IXUSR | S_IXGRP | S_IXOTH); err = got_opentemp_named(&tmppath, &tmpfile, template, ""); if (err) goto done; outfd = fileno(tmpfile); err = patch_file(p, afile != NULL ? afile : oldfile, tmpfile); if (err) goto done; if (do_merge) { const char *type, *id; if (fseeko(afile, 0, SEEK_SET) == -1 || fseeko(oldfile, 0, SEEK_SET) == -1 || fseeko(tmpfile, 0, SEEK_SET) == -1) { err = got_error_from_errno("fseeko"); goto done; } if (asprintf(&oldlabel, "--- %s", p->old) == -1) { err = got_error_from_errno("asprintf"); oldlabel = NULL; goto done; } if (asprintf(&newlabel, "+++ %s", p->new) == -1) { err = got_error_from_errno("asprintf"); newlabel = NULL; goto done; } if (*p->cid != '\0') { type = "commit"; id = p->cid; } else { type = "blob"; id = p->blob; } if (asprintf(&anclabel, "%s %s", type, id) == -1) { err = got_error_from_errno("asprintf"); anclabel = NULL; goto done; } if (reverse) { char *s; FILE *t; s = anclabel; anclabel = newlabel; newlabel = s; t = afile; afile = tmpfile; tmpfile = t; } err = got_opentemp_named(&mergepath, &mergefile, template, ""); if (err) goto done; outfd = fileno(mergefile); err = got_merge_diff3(overlapcnt, outfd, tmpfile, afile, oldfile, oldlabel, anclabel, newlabel, GOT_DIFF_ALGORITHM_PATIENCE); if (err) goto done; } if (nop) goto done; if (p->old != NULL && p->new == NULL) { err = got_worktree_patch_schedule_rm(old, repo, worktree, fileindex, patch_delete, pa); goto done; } if (fchmod(outfd, apply_umask(mode)) == -1) { err = got_error_from_errno2("chmod", tmppath); goto done; } if (mergepath) { err = got_path_move_file(mergepath, newpath); if (err) goto done; free(mergepath); mergepath = NULL; } else { err = got_path_move_file(tmppath, newpath); if (err) goto done; free(tmppath); tmppath = NULL; } if (file_renamed) { err = got_worktree_patch_schedule_rm(old, repo, worktree, fileindex, patch_delete, pa); if (err == NULL) err = got_worktree_patch_schedule_add(new, repo, worktree, fileindex, patch_add, pa); if (err) unlink(newpath); } else if (p->old == NULL) { err = got_worktree_patch_schedule_add(new, repo, worktree, fileindex, patch_add, pa); if (err) unlink(newpath); } else if (*overlapcnt != 0) err = report_progress(pa, old, new, GOT_STATUS_CONFLICT, NULL); else if (do_merge) err = report_progress(pa, old, new, GOT_STATUS_MERGE, NULL); else err = report_progress(pa, old, new, GOT_STATUS_MODIFY, NULL); done: free(template); if (tmppath != NULL && unlink(tmppath) == -1 && err == NULL) err = got_error_from_errno("unlink"); if (tmpfile != NULL && fclose(tmpfile) == EOF && err == NULL) err = got_error_from_errno("fclose"); free(tmppath); free(oldpath); if (oldfile != NULL && fclose(oldfile) == EOF && err == NULL) err = got_error_from_errno("fclose"); if (apath != NULL && unlink(apath) == -1 && err == NULL) err = got_error_from_errno("unlink"); if (afile != NULL && fclose(afile) == EOF && err == NULL) err = got_error_from_errno("fclose"); free(apath); if (mergepath != NULL && unlink(mergepath) == -1 && err == NULL) err = got_error_from_errno("unlink"); if (mergefile != NULL && fclose(mergefile) == EOF && err == NULL) err = got_error_from_errno("fclose"); free(mergepath); free(newpath); free(oldlabel); free(newlabel); free(anclabel); return err; } const struct got_error * got_patch(int fd, struct got_worktree *worktree, struct got_repository *repo, int nop, int strip, int reverse, struct got_object_id *commit_id, got_patch_progress_cb progress_cb, void *progress_arg, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL, *complete_err = NULL; struct got_fileindex *fileindex = NULL; struct got_commit_object *commit = NULL; struct got_tree_object *tree = NULL; char *fileindex_path = NULL; char *oldpath, *newpath; struct imsgbuf ibuf; int imsg_fds[2] = {-1, -1}; int overlapcnt, done = 0, failed = 0; pid_t pid; memset(&ibuf, 0, sizeof(ibuf)); if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1) { err = got_error_from_errno("socketpair"); goto done; } pid = fork(); if (pid == -1) { err = got_error_from_errno("fork"); goto done; } else if (pid == 0) { got_privsep_exec_child(imsg_fds, GOT_PATH_PROG_READ_PATCH, NULL); /* not reached */ } if (close(imsg_fds[1]) == -1) { err = got_error_from_errno("close"); goto done; } imsg_fds[1] = -1; if (imsgbuf_init(&ibuf, imsg_fds[0]) == -1) { err = got_error_from_errno("imsgbuf_init"); goto done; } imsgbuf_allow_fdpass(&ibuf); err = send_patch(&ibuf, fd); fd = -1; if (err) goto done; err = got_worktree_patch_prepare(&fileindex, &fileindex_path, worktree, repo); if (err) goto done; if (commit_id) { err = got_object_open_as_commit(&commit, repo, commit_id); if (err) goto done; err = got_object_open_as_tree(&tree, repo, commit->tree_id); if (err) goto done; } while (!done && err == NULL) { struct got_patch p; struct patch_args pa; pa.progress_cb = progress_cb; pa.progress_arg = progress_arg; pa.head = &p.head; err = recv_patch(&ibuf, &done, &p, strip); if (err || done) break; err = got_worktree_patch_check_path(p.old, p.new, &oldpath, &newpath, worktree, repo, fileindex); if (err == NULL) err = apply_patch(&overlapcnt, worktree, repo, fileindex, oldpath, newpath, &p, nop, reverse, commit_id, tree, &pa, cancel_cb, cancel_arg); if (err != NULL) { failed = 1; /* recoverable errors */ if (err->code == GOT_ERR_FILE_STATUS || (err->code == GOT_ERR_ERRNO && errno == ENOENT)) err = report_progress(&pa, p.old, p.new, GOT_STATUS_CANNOT_UPDATE, err); else if (err->code == GOT_ERR_HUNK_FAILED) err = report_progress(&pa, p.old, p.new, GOT_STATUS_CANNOT_UPDATE, NULL); } if (overlapcnt != 0) failed = 1; free(oldpath); free(newpath); patch_free(&p); if (err) break; } done: complete_err = got_worktree_patch_complete(worktree, fileindex, fileindex_path); if (complete_err && err == NULL) err = complete_err; free(fileindex_path); if (tree) got_object_tree_close(tree); if (commit) got_object_commit_close(commit); if (fd != -1 && close(fd) == -1 && err == NULL) err = got_error_from_errno("close"); if (ibuf.w) imsgbuf_clear(&ibuf); if (imsg_fds[0] != -1 && close(imsg_fds[0]) == -1 && err == NULL) err = got_error_from_errno("close"); if (imsg_fds[1] != -1 && close(imsg_fds[1]) == -1 && err == NULL) err = got_error_from_errno("close"); if (err == NULL && failed) err = got_error(GOT_ERR_PATCH_FAILED); return err; } got-portable-0.111/lib/got_lib_privsep.h0000644000175000017500000006657115001741021013710 /* * Copyright (c) 2018, 2019 Stefan Sperling * Copyright (c) 2019, Ori Bernstein * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * All code runs under the same UID but sensitive code paths are run in a * separate process with tighter pledge(2) promises; data is communicated * between processes via imsgbuf_flush(3) and imsgbuf_read(3). * This behaviour is transparent to users of the library. * * We generally transmit data in imsg buffers, split across several messages * if necessary. File descriptors are used in cases where this is impractical, * such as when accessing pack files or when transferring large blobs. * * We exec(2) after a fork(2). Parts of our library functionality are * accessible via separate executables in a libexec directory. */ #define GOT_IMSG_FD_CHILD (STDERR_FILENO + 1) #ifndef GOT_LIBEXECDIR #define GOT_LIBEXECDIR /usr/local/bin #endif /* Names of helper programs in libexec directory */ #define GOT_PROG_READ_OBJECT got-read-object #define GOT_PROG_READ_TREE got-read-tree #define GOT_PROG_READ_COMMIT got-read-commit #define GOT_PROG_READ_BLOB got-read-blob #define GOT_PROG_READ_TAG got-read-tag #define GOT_PROG_READ_PACK got-read-pack #define GOT_PROG_READ_GITCONFIG got-read-gitconfig #define GOT_PROG_READ_GOTCONFIG got-read-gotconfig #define GOT_PROG_READ_PATCH got-read-patch #define GOT_PROG_FETCH_PACK got-fetch-pack #define GOT_PROG_INDEX_PACK got-index-pack #define GOT_PROG_SEND_PACK got-send-pack #define GOT_PROG_FETCH_HTTP got-fetch-http #define GOT_STRINGIFY(x) #x #define GOT_STRINGVAL(x) GOT_STRINGIFY(x) /* Paths to helper programs in libexec directory */ #define GOT_PATH_PROG_READ_OBJECT \ GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_READ_OBJECT) #define GOT_PATH_PROG_READ_TREE \ GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_READ_TREE) #define GOT_PATH_PROG_READ_COMMIT \ GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_READ_COMMIT) #define GOT_PATH_PROG_READ_BLOB \ GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_READ_BLOB) #define GOT_PATH_PROG_READ_TAG \ GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_READ_TAG) #define GOT_PATH_PROG_READ_PACK \ GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_READ_PACK) #define GOT_PATH_PROG_READ_GITCONFIG \ GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_READ_GITCONFIG) #define GOT_PATH_PROG_READ_GOTCONFIG \ GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_READ_GOTCONFIG) #define GOT_PATH_PROG_READ_PATCH \ GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_READ_PATCH) #define GOT_PATH_PROG_FETCH_PACK \ GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_FETCH_PACK) #define GOT_PATH_PROG_SEND_PACK \ GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_SEND_PACK) #define GOT_PATH_PROG_INDEX_PACK \ GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_INDEX_PACK) #define GOT_PATH_PROG_FETCH_HTTP \ GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_FETCH_HTTP) enum got_imsg_type { /* An error occurred while processing a request. */ GOT_IMSG_ERROR, /* Stop the child process. */ GOT_IMSG_STOP, /* * Messages concerned with read access to objects in a repository. * Object and pack files are opened by the main process, where * data may be read as a byte string but without any interpretation. * Decompression and parsing of object and pack files occurs in a * separate process which runs under pledge("stdio recvfd"). * This sandboxes our own repository parsing code, as well as zlib. */ GOT_IMSG_OBJECT_REQUEST, GOT_IMSG_OBJECT, GOT_IMSG_COMMIT_REQUEST, GOT_IMSG_COMMIT, GOT_IMSG_COMMIT_LOGMSG, GOT_IMSG_TREE_REQUEST, GOT_IMSG_TREE, GOT_IMSG_TREE_ENTRIES, GOT_IMSG_BLOB_REQUEST, GOT_IMSG_BLOB_OUTFD, GOT_IMSG_BLOB, GOT_IMSG_TAG_REQUEST, GOT_IMSG_TAG, GOT_IMSG_TAG_TAGMSG, /* Messages related to networking. */ GOT_IMSG_FETCH_REQUEST, GOT_IMSG_FETCH_HAVE_REF, GOT_IMSG_FETCH_WANTED_BRANCH, GOT_IMSG_FETCH_WANTED_REF, GOT_IMSG_FETCH_OUTFD, GOT_IMSG_FETCH_SYMREFS, GOT_IMSG_FETCH_REF, GOT_IMSG_FETCH_SERVER_PROGRESS, GOT_IMSG_FETCH_DOWNLOAD_PROGRESS, GOT_IMSG_FETCH_DONE, GOT_IMSG_IDXPACK_REQUEST, GOT_IMSG_IDXPACK_OUTFD, GOT_IMSG_IDXPACK_PROGRESS, GOT_IMSG_IDXPACK_DONE, GOT_IMSG_SEND_REQUEST, GOT_IMSG_SEND_REF, GOT_IMSG_SEND_REMOTE_REF, GOT_IMSG_SEND_REF_STATUS, GOT_IMSG_SEND_PACK_REQUEST, GOT_IMSG_SEND_PACKFD, GOT_IMSG_SEND_UPLOAD_PROGRESS, GOT_IMSG_SEND_DONE, /* Messages related to pack files. */ GOT_IMSG_PACKIDX, GOT_IMSG_PACK, GOT_IMSG_PACKED_OBJECT_REQUEST, GOT_IMSG_COMMIT_TRAVERSAL_REQUEST, GOT_IMSG_TRAVERSED_COMMITS, GOT_IMSG_COMMIT_TRAVERSAL_DONE, GOT_IMSG_OBJECT_ENUMERATION_REQUEST, GOT_IMSG_ENUMERATED_COMMIT, GOT_IMSG_ENUMERATED_TREE, GOT_IMSG_TREE_ENUMERATION_DONE, GOT_IMSG_OBJECT_ENUMERATION_DONE, GOT_IMSG_OBJECT_ENUMERATION_INCOMPLETE, /* Message sending file descriptor to a temporary file. */ GOT_IMSG_TMPFD, /* Messages related to gitconfig files. */ GOT_IMSG_GITCONFIG_PARSE_REQUEST, GOT_IMSG_GITCONFIG_REPOSITORY_FORMAT_VERSION_REQUEST, GOT_IMSG_GITCONFIG_REPOSITORY_EXTENSIONS_REQUEST, GOT_IMSG_GITCONFIG_AUTHOR_NAME_REQUEST, GOT_IMSG_GITCONFIG_AUTHOR_EMAIL_REQUEST, GOT_IMSG_GITCONFIG_REMOTES_REQUEST, GOT_IMSG_GITCONFIG_INT_VAL, GOT_IMSG_GITCONFIG_STR_VAL, GOT_IMSG_GITCONFIG_PAIR, GOT_IMSG_GITCONFIG_REMOTES, GOT_IMSG_GITCONFIG_REMOTE, GOT_IMSG_GITCONFIG_OWNER_REQUEST, GOT_IMSG_GITCONFIG_OWNER, /* Messages related to gotconfig files. */ GOT_IMSG_GOTCONFIG_PARSE_REQUEST, GOT_IMSG_GOTCONFIG_AUTHOR_REQUEST, GOT_IMSG_GOTCONFIG_ALLOWEDSIGNERS_REQUEST, GOT_IMSG_GOTCONFIG_REVOKEDSIGNERS_REQUEST, GOT_IMSG_GOTCONFIG_SIGNERID_REQUEST, GOT_IMSG_GOTCONFIG_REMOTES_REQUEST, GOT_IMSG_GOTCONFIG_INT_VAL, GOT_IMSG_GOTCONFIG_STR_VAL, GOT_IMSG_GOTCONFIG_REMOTES, GOT_IMSG_GOTCONFIG_REMOTE, /* Raw object access. Uncompress object data but do not parse it. */ GOT_IMSG_RAW_OBJECT_REQUEST, GOT_IMSG_RAW_OBJECT_OUTFD, GOT_IMSG_PACKED_RAW_OBJECT_REQUEST, GOT_IMSG_RAW_OBJECT, /* Read raw delta data from pack files. */ GOT_IMSG_RAW_DELTA_OUTFD, GOT_IMSG_RAW_DELTA_REQUEST, GOT_IMSG_RAW_DELTA, /* Reuse deltas found in a pack file. */ GOT_IMSG_DELTA_REUSE_REQUEST, GOT_IMSG_REUSED_DELTAS, GOT_IMSG_DELTA_REUSE_DONE, /* Commit coloring in got-read-pack. */ GOT_IMSG_COMMIT_PAINTING_INIT, GOT_IMSG_COMMIT_PAINTING_REQUEST, GOT_IMSG_PAINTED_COMMITS, GOT_IMSG_COMMIT_PAINTING_DONE, /* Transfer a list of object IDs. */ GOT_IMSG_OBJ_ID_LIST, GOT_IMSG_OBJ_ID_LIST_DONE, /* Messages related to patch files. */ GOT_IMSG_PATCH_FILE, GOT_IMSG_PATCH_HUNK, GOT_IMSG_PATCH_DONE, GOT_IMSG_PATCH_LINE, GOT_IMSG_PATCH, GOT_IMSG_PATCH_EOF, }; /* Structure for GOT_IMSG_ERROR. */ struct got_imsg_error { int code; /* an error code from got_error.h */ int errno_code; /* in case code equals GOT_ERR_ERRNO */ } __attribute__((__packed__)); /* * Structure for GOT_IMSG_TREE_REQUEST and GOT_IMSG_OBJECT data. */ struct got_imsg_object { struct got_object_id id; /* These fields are the same as in struct got_object. */ int type; int flags; size_t hdrlen; size_t size; off_t pack_offset; int pack_idx; } __attribute__((__packed__)); /* Structure for GOT_IMSG_COMMIT data. */ struct got_imsg_commit_object { struct got_object_id tree_id; size_t author_len; time_t author_time; time_t author_gmtoff; size_t committer_len; time_t committer_time; time_t committer_gmtoff; size_t logmsg_len; int nparents; /* * Followed by author_len + committer_len data bytes */ /* Followed by 'nparents' struct got_object_id */ /* * Followed by 'logmsg_len' bytes of commit log message data in * one or more GOT_IMSG_COMMIT_LOGMSG messages. */ } __attribute__((__packed__)); struct got_imsg_tree_entry { char id[GOT_HASH_DIGEST_MAXLEN]; int algo; mode_t mode; size_t namelen; /* Followed by namelen bytes of entry's name, not NUL-terminated. */ } __attribute__((__packed__)); /* Structure for GOT_IMSG_TREE_ENTRIES. */ struct got_imsg_tree_entries { size_t nentries; /* Number of tree entries contained in this message. */ /* Followed by nentries * struct got_imsg_tree_entry */ }; /* Structure for GOT_IMSG_TREE_OBJECT_REPLY data. */ struct got_imsg_tree_object { int nentries; /* This many tree entries follow. */ }; /* Structure for GOT_IMSG_BLOB. */ struct got_imsg_blob { size_t size; size_t hdrlen; /* * If size <= GOT_PRIVSEP_INLINE_BLOB_DATA_MAX, blob data follows * in the imsg buffer. Otherwise, blob data has been written to a * file descriptor passed via the GOT_IMSG_BLOB_OUTFD imsg. */ #define GOT_PRIVSEP_INLINE_BLOB_DATA_MAX \ (MAX_IMSGSIZE - IMSG_HEADER_SIZE - sizeof(struct got_imsg_blob)) }; /* Structure for GOT_IMSG_RAW_OBJECT. */ struct got_imsg_raw_obj { off_t size; size_t hdrlen; /* * If size <= GOT_PRIVSEP_INLINE_OBJECT_DATA_MAX, object data follows * in the imsg buffer. Otherwise, object data has been written to a * file descriptor passed via the GOT_IMSG_RAW_OBJECT_OUTFD imsg. */ #define GOT_PRIVSEP_INLINE_OBJECT_DATA_MAX \ (MAX_IMSGSIZE - IMSG_HEADER_SIZE - sizeof(struct got_imsg_raw_obj)) }; /* Structure for GOT_IMSG_RAW_DELTA. */ struct got_imsg_raw_delta { struct got_object_id base_id; uint64_t base_size; uint64_t result_size; off_t delta_size; off_t delta_compressed_size; off_t delta_offset; off_t delta_out_offset; /* * Delta data has been written at delta_out_offset to the file * descriptor passed via the GOT_IMSG_RAW_DELTA_OUTFD imsg. */ }; /* Structures for GOT_IMSG_REUSED_DELTAS. */ struct got_imsg_reused_delta { struct got_object_id id; struct got_object_id base_id; uint64_t base_size; uint64_t result_size; off_t delta_size; off_t delta_compressed_size; off_t delta_offset; }; struct got_imsg_reused_deltas { size_t ndeltas; /* * Followed by ndeltas * struct got_imsg_reused_delta. */ #define GOT_IMSG_REUSED_DELTAS_MAX_NDELTAS \ ((MAX_IMSGSIZE - IMSG_HEADER_SIZE - \ sizeof(struct got_imsg_reused_deltas)) \ / sizeof(struct got_imsg_reused_delta)) }; /* Structure for GOT_IMSG_PAINTED_COMMITS. */ struct got_imsg_painted_commit { struct got_object_id id; intptr_t color; } __attribute__((__packed__)); struct got_imsg_painted_commits { int ncommits; int present_in_pack; /* * Followed by ncommits * struct got_imsg_painted_commit. */ } __attribute__((__packed__)); /* Structure for GOT_IMSG_TAG data. */ struct got_imsg_tag_object { struct got_object_id id; int obj_type; size_t tag_len; size_t tagger_len; time_t tagger_time; time_t tagger_gmtoff; size_t tagmsg_len; /* * Followed by tag_len + tagger_len data bytes */ /* * Followed by 'tagmsg_len' bytes of tag message data in * one or more GOT_IMSG_TAG_TAGMSG messages. */ } __attribute__((__packed__)); /* Structure for GOT_IMSG_FETCH_HAVE_REF data. */ struct got_imsg_fetch_have_ref { struct got_object_id id; size_t name_len; /* Followed by name_len data bytes. */ } __attribute__((__packed__)); /* Structure for GOT_IMSG_FETCH_WANTED_BRANCH data. */ struct got_imsg_fetch_wanted_branch { size_t name_len; /* Followed by name_len data bytes. */ } __attribute__((__packed__)); /* Structure for GOT_IMSG_FETCH_WANTED_REF data. */ struct got_imsg_fetch_wanted_ref { size_t name_len; /* Followed by name_len data bytes. */ } __attribute__((__packed__)); /* Structure for GOT_IMSG_FETCH_REQUEST data. */ struct got_imsg_fetch_request { int no_head; int fetch_all_branches; int list_refs_only; int verbosity; size_t worktree_branch_len; size_t remote_head_len; size_t n_have_refs; size_t n_wanted_branches; size_t n_wanted_refs; /* Followed by worktree_branch_len bytes of reference name. */ /* Followed by remote_head_len bytes of reference name. */ /* Followed by n_have_refs GOT_IMSG_FETCH_HAVE_REF messages. */ /* Followed by n_wanted_branches times GOT_IMSG_FETCH_WANTED_BRANCH. */ /* Followed by n_wanted_refs times GOT_IMSG_FETCH_WANTED_REF. */ } __attribute__((__packed__)); /* Structures for GOT_IMSG_FETCH_SYMREFS data. */ struct got_imsg_fetch_symref { size_t name_len; size_t target_len; /* * Followed by name_len + target_len data bytes. */ } __attribute__((__packed__)); struct got_imsg_fetch_symrefs { size_t nsymrefs; /* Followed by nsymrefs times of got_imsg_fetch_symref data. */ } __attribute__((__packed__)); /* Structure for GOT_IMSG_FETCH_REF data. */ struct got_imsg_fetch_ref { /* Describes a reference which will be fetched. */ struct got_object_id refid; /* Followed by reference name in remaining data of imsg buffer. */ }; /* Structure for GOT_IMSG_FETCH_DOWNLOAD_PROGRESS data. */ struct got_imsg_fetch_download_progress { /* Number of packfile data bytes downloaded so far. */ off_t packfile_bytes; }; /* Structure for GOT_IMSG_SEND_REQUEST data. */ struct got_imsg_send_request { int verbosity; size_t nrefs; /* Followed by nrefs GOT_IMSG_SEND_REF messages. */ } __attribute__((__packed__)); /* Structure for GOT_IMSG_SEND_UPLOAD_PROGRESS data. */ struct got_imsg_send_upload_progress { /* Number of packfile data bytes uploaded so far. */ off_t packfile_bytes; }; /* Structure for GOT_IMSG_SEND_REF data. */ struct got_imsg_send_ref { struct got_object_id id; int delete; size_t name_len; /* Followed by name_len data bytes. */ } __attribute__((__packed__)); /* Structure for GOT_IMSG_SEND_REMOTE_REF data. */ struct got_imsg_send_remote_ref { struct got_object_id id; size_t name_len; /* Followed by name_len data bytes. */ } __attribute__((__packed__)); /* Structure for GOT_IMSG_SEND_REF_STATUS data. */ struct got_imsg_send_ref_status { int success; size_t name_len; size_t errmsg_len; /* Followed by name_len data bytes. */ /* Followed by errmsg_len data bytes. */ } __attribute__((__packed__)); /* Structure for GOT_IMSG_IDXPACK_REQUEST data. */ struct got_imsg_index_pack_request { struct got_object_id id; } __attribute__((__packed__)); /* Structure for GOT_IMSG_IDXPACK_PROGRESS data. */ struct got_imsg_index_pack_progress { /* Total number of objects in pack file. */ int nobj_total; /* Number of objects indexed so far. */ int nobj_indexed; /* Number of non-deltified objects in pack file. */ int nobj_loose; /* Number of deltified objects resolved so far. */ int nobj_resolved; }; /* Structure for GOT_IMSG_PACKIDX. */ struct got_imsg_packidx { size_t len; off_t packfile_size; int algo; /* Additionally, a file descriptor is passed via imsg. */ }; /* Structure for GOT_IMSG_PACK. */ struct got_imsg_pack { char path_packfile[PATH_MAX]; off_t filesize; int algo; /* Additionally, a file descriptor is passed via imsg. */ } __attribute__((__packed__)); /* * Structure for GOT_IMSG_OBJECT_REQUEST, GOT_IMSG_BLOB_REQUEST, * GOT_IMSG_TREE_REQUEST, GOT_IMSG_COMMIT_REQUEST, and * GOT_IMSG_TAG_REQUEST data. */ struct got_object_id; /* * Structure for GOT_IMSG_PACKED_OBJECT_REQUEST and * GOT_IMSG_PACKED_RAW_OBJECT_REQUEST data. */ struct got_imsg_packed_object { struct got_object_id id; int idx; } __attribute__((__packed__)); /* * Structure for GOT_IMSG_DELTA data. */ struct got_imsg_delta { /* These fields are the same as in struct got_delta. */ off_t offset; size_t tslen; int type; size_t size; off_t data_offset; }; /* * Structure for GOT_IMSG_RAW_DELTA_REQUEST data. */ struct got_imsg_raw_delta_request { struct got_object_id id; int idx; }; /* * Structure for GOT_IMSG_OBJ_ID_LIST data. * Multiple such messages may be sent back-to-back, where each message * contains a chunk of IDs. The entire list must be terminated with a * GOT_IMSG_OBJ_ID_LIST_DONE message. */ struct got_imsg_object_idlist { size_t nids; /* * Followed by nids * struct got_object_id. */ #define GOT_IMSG_OBJ_ID_LIST_MAX_NIDS \ ((MAX_IMSGSIZE - IMSG_HEADER_SIZE - \ sizeof(struct got_imsg_object_idlist)) / sizeof(struct got_object_id)) }; /* Structure for GOT_IMSG_COMMIT_TRAVERSAL_REQUEST */ struct got_imsg_commit_traversal_request { struct got_imsg_packed_object iobj; size_t path_len; /* Followed by path_len bytes of path data */ } __attribute__((__packed__)); /* Structure for GOT_IMSG_TRAVERSED_COMMITS */ struct got_imsg_traversed_commits { size_t ncommits; /* Followed by ncommit struct got_object_id */ } __attribute__((__packed__)); /* Structure for GOT_IMSG_ENUMERATED_COMMIT */ struct got_imsg_enumerated_commit { struct got_object_id id; time_t mtime; } __attribute__((__packed__)); /* Structure for GOT_IMSG_ENUMERATED_TREE */ struct got_imsg_enumerated_tree { struct got_object_id id; /* tree ID */ int nentries; /* number of tree entries */ /* Followed by tree's path in remaining data of imsg buffer. */ /* Followed by nentries * GOT_IMSG_TREE_ENTRY messages. */ } __attribute__((__packed__)); /* * Structure for GOT_IMSG_GOTCONFIG_REMOTE and * GOT_IMSG_GOTCONFIG_REMOTE data. */ struct got_imsg_remote { size_t name_len; size_t fetch_url_len; size_t send_url_len; int mirror_references; int fetch_all_branches; int nfetch_branches; int nsend_branches; int nfetch_refs; /* Followed by name_len data bytes. */ /* Followed by fetch_url_len + send_url_len data bytes. */ /* Followed by nfetch_branches GOT_IMSG_GITCONFIG_STR_VAL messages. */ /* Followed by nsend_branches GOT_IMSG_GITCONFIG_STR_VAL messages. */ /* Followed by nfetch_refs GOT_IMSG_GITCONFIG_STR_VAL messages. */ } __attribute__((__packed__)); /* * Structure for GOT_IMSG_GITCONFIG_REMOTES data. */ struct got_imsg_remotes { int nremotes; /* This many GOT_IMSG_GITCONFIG_REMOTE messages follow. */ }; /* * Structure for GOT_IMSG_GITCONFIG_PAIR. */ struct got_imsg_gitconfig_pair { size_t klen; size_t vlen; /* Followed by klen data bytes of key string. */ /* Followed by vlen data bytes of value string. */ }; /* * Structure for GOT_IMSG_PATCH data. */ struct got_imsg_patch { int git; int xbit; char old[PATH_MAX]; char new[PATH_MAX]; char cid[GOT_HASH_DIGEST_STRING_MAXLEN]; char blob[GOT_HASH_DIGEST_STRING_MAXLEN]; }; /* * Structure for GOT_IMSG_PATCH_HUNK data. */ struct got_imsg_patch_hunk { int oldfrom; int oldlines; int newfrom; int newlines; }; struct got_remote_repo; struct got_pack; struct got_packidx; struct got_pathlist_head; const struct got_error *got_send_ack(pid_t); const struct got_error *got_privsep_wait_for_child(pid_t); const struct got_error *got_privsep_flush_imsg(struct imsgbuf *); const struct got_error *got_privsep_send_stop(int); const struct got_error *got_privsep_recv_imsg(struct imsg *, struct imsgbuf *, size_t); void got_privsep_send_error(struct imsgbuf *, const struct got_error *); const struct got_error *got_privsep_send_ack(struct imsgbuf *); const struct got_error *got_privsep_wait_ack(struct imsgbuf *); const struct got_error *got_privsep_send_obj_req(struct imsgbuf *, int, struct got_object_id *); const struct got_error *got_privsep_send_raw_obj_req(struct imsgbuf *, int, struct got_object_id *); const struct got_error *got_privsep_send_raw_obj_outfd(struct imsgbuf *, int); const struct got_error *got_privsep_send_commit_req(struct imsgbuf *, int, struct got_object_id *, int); const struct got_error *got_privsep_send_tree_req(struct imsgbuf *, int, struct got_object_id *, int); const struct got_error *got_privsep_send_tag_req(struct imsgbuf *, int, struct got_object_id *, int); const struct got_error *got_privsep_send_blob_req(struct imsgbuf *, int, struct got_object_id *, int); const struct got_error *got_privsep_send_blob_outfd(struct imsgbuf *, int); const struct got_error *got_privsep_send_tmpfd(struct imsgbuf *, int); const struct got_error *got_privsep_send_obj(struct imsgbuf *, struct got_object *); const struct got_error *got_privsep_send_index_pack_req(struct imsgbuf *, struct got_object_id *, int); const struct got_error *got_privsep_send_index_pack_outfd(struct imsgbuf *, int); const struct got_error *got_privsep_recv_index_progress(int *, int *, int *, int *, int *, struct imsgbuf *ibuf); const struct got_error *got_privsep_send_fetch_req(struct imsgbuf *, int, struct got_pathlist_head *, int, struct got_pathlist_head *, struct got_pathlist_head *, int, const char *, const char *, int, int); const struct got_error *got_privsep_send_fetch_outfd(struct imsgbuf *, int); const struct got_error *got_privsep_recv_fetch_progress(int *, struct got_object_id **, char **, struct got_pathlist_head *, char **, off_t *, uint8_t *, struct imsgbuf *); const struct got_error *got_privsep_send_send_req(struct imsgbuf *, int, struct got_pathlist_head *, struct got_pathlist_head *, int); const struct got_error *got_privsep_recv_send_remote_refs( struct got_pathlist_head *, struct imsgbuf *); const struct got_error *got_privsep_send_packfd(struct imsgbuf *, int); const struct got_error *got_privsep_recv_send_progress(int *, off_t *, int *, char **, char **, struct imsgbuf *); const struct got_error *got_privsep_get_imsg_obj(struct got_object **, struct imsg *, struct imsgbuf *); const struct got_error *got_privsep_recv_obj(struct got_object **, struct imsgbuf *); const struct got_error *got_privsep_send_raw_obj(struct imsgbuf *, off_t, size_t, uint8_t *); const struct got_error *got_privsep_recv_raw_obj(uint8_t **, off_t *, size_t *, struct imsgbuf *); const struct got_error *got_privsep_send_commit(struct imsgbuf *, struct got_commit_object *); const struct got_error *got_privsep_recv_commit(struct got_commit_object **, struct imsgbuf *); const struct got_error *got_privsep_recv_tree(struct got_tree_object **, struct imsgbuf *); struct got_parsed_tree_entry; const struct got_error *got_privsep_send_tree(struct imsgbuf *, struct got_parsed_tree_entry *, int); const struct got_error *got_privsep_send_blob(struct imsgbuf *, size_t, size_t, const uint8_t *); const struct got_error *got_privsep_recv_blob(uint8_t **, size_t *, size_t *, struct imsgbuf *); const struct got_error *got_privsep_send_tag(struct imsgbuf *, struct got_tag_object *); const struct got_error *got_privsep_recv_tag(struct got_tag_object **, struct imsgbuf *); const struct got_error *got_privsep_init_pack_child(struct imsgbuf *, struct got_pack *, struct got_packidx *); const struct got_error *got_privsep_send_packed_obj_req(struct imsgbuf *, int, struct got_object_id *); const struct got_error *got_privsep_send_packed_raw_obj_req(struct imsgbuf *, int, struct got_object_id *); const struct got_error *got_privsep_send_pack_child_ready(struct imsgbuf *); const struct got_error *got_privsep_send_gitconfig_parse_req(struct imsgbuf *, int); const struct got_error * got_privsep_send_gitconfig_repository_format_version_req(struct imsgbuf *); const struct got_error *got_privsep_send_gitconfig_repository_extensions_req( struct imsgbuf *); const struct got_error *got_privsep_send_gitconfig_author_name_req( struct imsgbuf *); const struct got_error *got_privsep_send_gitconfig_author_email_req( struct imsgbuf *); const struct got_error *got_privsep_send_gitconfig_remotes_req( struct imsgbuf *); const struct got_error *got_privsep_send_gitconfig_owner_req(struct imsgbuf *); const struct got_error *got_privsep_recv_gitconfig_str(char **, struct imsgbuf *); const struct got_error *got_privsep_recv_gitconfig_pair(char **, char **, struct imsgbuf *); const struct got_error *got_privsep_recv_gitconfig_int(int *, struct imsgbuf *); const struct got_error *got_privsep_recv_gitconfig_remotes( struct got_remote_repo **, int *, struct imsgbuf *); const struct got_error *got_privsep_send_gotconfig_parse_req(struct imsgbuf *, int); const struct got_error *got_privsep_send_gotconfig_author_req(struct imsgbuf *); const struct got_error *got_privsep_send_gotconfig_allowed_signers_req( struct imsgbuf *); const struct got_error *got_privsep_send_gotconfig_revoked_signers_req( struct imsgbuf *); const struct got_error *got_privsep_send_gotconfig_signer_id_req( struct imsgbuf *); const struct got_error *got_privsep_send_gotconfig_remotes_req( struct imsgbuf *); const struct got_error *got_privsep_recv_gotconfig_str(char **, struct imsgbuf *); const struct got_error *got_privsep_recv_gotconfig_remotes( struct got_remote_repo **, int *, struct imsgbuf *); const struct got_error *got_privsep_send_commit_traversal_request( struct imsgbuf *, struct got_object_id *, int, const char *); const struct got_error *got_privsep_recv_traversed_commits( struct got_commit_object **, struct got_object_id_queue *, struct imsgbuf *); const struct got_error *got_privsep_send_enumerated_tree(size_t *, struct imsgbuf *, struct got_object_id *, const char *, struct got_parsed_tree_entry *, int); const struct got_error *got_privsep_send_object_enumeration_request( struct imsgbuf *); const struct got_error *got_privsep_send_object_enumeration_done( struct imsgbuf *); const struct got_error *got_privsep_send_object_enumeration_incomplete( struct imsgbuf *); const struct got_error *got_privsep_send_enumerated_commit(struct imsgbuf *, struct got_object_id *, time_t); const struct got_error *got_privsep_recv_enumerated_objects(int *, struct imsgbuf *, got_object_enumerate_commit_cb, got_object_enumerate_tree_cb, void *, struct got_repository *); const struct got_error *got_privsep_send_raw_delta_req(struct imsgbuf *, int, struct got_object_id *); const struct got_error *got_privsep_send_raw_delta_outfd(struct imsgbuf *, int); const struct got_error *got_privsep_send_raw_delta(struct imsgbuf *, uint64_t, uint64_t, off_t, off_t, off_t, off_t, struct got_object_id *); const struct got_error *got_privsep_recv_raw_delta(uint64_t *, uint64_t *, off_t *, off_t *, off_t *, off_t *, struct got_object_id **, struct imsgbuf *); const struct got_error *got_privsep_send_object_idlist(struct imsgbuf *, struct got_object_id **, size_t); const struct got_error *got_privsep_send_object_idlist_done(struct imsgbuf *); const struct got_error *got_privsep_recv_object_idlist(int *, struct got_object_id **, size_t *, struct imsgbuf *); const struct got_error *got_privsep_send_delta_reuse_req(struct imsgbuf *); const struct got_error *got_privsep_send_reused_deltas(struct imsgbuf *, struct got_imsg_reused_delta *, size_t); const struct got_error *got_privsep_send_reused_deltas_done(struct imsgbuf *); const struct got_error *got_privsep_recv_reused_deltas(int *, struct got_imsg_reused_delta *, size_t *, struct imsgbuf *); const struct got_error *got_privsep_init_commit_painting(struct imsgbuf *); const struct got_error *got_privsep_send_painting_request(struct imsgbuf *); typedef const struct got_error *(*got_privsep_recv_painted_commit_cb)(void *, struct got_object_id *, intptr_t); const struct got_error *got_privsep_send_painted_commits(struct imsgbuf *, struct got_object_id_queue *, int *, int, int); const struct got_error *got_privsep_send_painting_commits_done(struct imsgbuf *); const struct got_error *got_privsep_recv_painted_commits( struct got_object_id_queue *, got_privsep_recv_painted_commit_cb, void *, struct imsgbuf *); void got_privsep_exec_child(int[2], const char *, const char *); got-portable-0.111/lib/dump.c0000644000175000017500000001143115001741021011442 /* * Copyright (c) 2023 Omar Polo * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_cancel.h" #include "got_reference.h" #include "got_repository.h" #include "got_repository_admin.h" /* XXX for pack_progress */ #include "got_object.h" #include "got_opentemp.h" #include "got_repository_dump.h" #include "got_lib_delta.h" #include "got_lib_hash.h" #include "got_lib_object.h" #include "got_lib_object_idset.h" #include "got_lib_ratelimit.h" #include "got_lib_pack_create.h" #define GIT_BUNDLE_SIGNATURE_V2 "# v2 git bundle" #define GIT_BUNDLE_SIGNATURE_V3 "# v3 git bundle" struct idvec { struct got_object_id **ids; size_t len; size_t size; }; static const struct got_error * idvec_push(struct idvec *v, struct got_object_id *id) { size_t newsize; void *t; if (v->len == v->size) { newsize = v->size + 8; t = reallocarray(v->ids, newsize, sizeof(*v->ids)); if (t == NULL) return got_error_from_errno("reallocarray"); v->ids = t; v->size = newsize; } v->ids[v->len++] = id; return NULL; } static void idvec_free(struct idvec *v) { size_t i; for (i = 0; i < v->len; ++i) free(v->ids[i]); free(v->ids); } const struct got_error * got_repo_dump(FILE *out, struct got_reflist_head *include_refs, struct got_reflist_head *exclude_refs, struct got_repository *repo, got_pack_progress_cb progress_cb, void *progress_arg, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; struct got_ratelimit rl; struct got_object_id packhash; FILE *delta_cache = NULL; struct got_reflist_entry *e; struct got_object_id *id = NULL; struct got_commit_object *commit = NULL; struct idvec ours, theirs; char *nl, *s, *hex, *logmsg = NULL; const char *refname, *signature; enum got_hash_algorithm algo; int r; algo = got_repo_get_object_format(repo); switch (algo) { case GOT_HASH_SHA1: signature = GIT_BUNDLE_SIGNATURE_V2; break; case GOT_HASH_SHA256: signature = GIT_BUNDLE_SIGNATURE_V3; break; default: return got_error(GOT_ERR_OBJECT_FORMAT); } got_ratelimit_init(&rl, 0, 500); memset(&ours, 0, sizeof(ours)); memset(&theirs, 0, sizeof(theirs)); r = fprintf(out, "%s\n", signature); if (r != strlen(GIT_BUNDLE_SIGNATURE_V2) + 1) return got_ferror(out, GOT_ERR_IO); if (algo == GOT_HASH_SHA256) fprintf(out, "@object-format=sha256\n"); TAILQ_FOREACH(e, exclude_refs, entry) { err = got_ref_resolve(&id, repo, e->ref); if (err) goto done; idvec_push(&theirs, id); if (err) goto done; err = got_object_open_as_commit(&commit, repo, id); if (err) goto done; err = got_object_commit_get_logmsg(&logmsg, commit); if (err) goto done; s = logmsg; while (isspace((unsigned char)*s)) s++; nl = strchr(s, '\n'); if (nl) *nl = '\0'; err = got_object_id_str(&hex, id); if (err) goto done; fprintf(out, "-%s %s\n", hex, s); free(hex); got_object_commit_close(commit); commit = NULL; free(logmsg); logmsg = NULL; } TAILQ_FOREACH(e, include_refs, entry) { err = got_ref_resolve(&id, repo, e->ref); if (err) goto done; err = idvec_push(&ours, id); if (err) goto done; refname = got_ref_get_name(e->ref); err = got_object_id_str(&hex, id); if (err) goto done; fprintf(out, "%s %s\n", hex, refname); free(hex); } if (fputc('\n', out) == EOF || fflush(out) == EOF) { err = got_ferror(out, GOT_ERR_IO); goto done; } delta_cache = got_opentemp(); if (delta_cache == NULL) { err = got_error_from_errno("got_opentemp"); goto done; } err = got_pack_create(&packhash, fileno(out), delta_cache, theirs.ids, theirs.len, ours.ids, ours.len, repo, 0, 0, 0, progress_cb, progress_arg, &rl, cancel_cb, cancel_arg); done: idvec_free(&ours); idvec_free(&theirs); if (commit) got_object_commit_close(commit); if (delta_cache && fclose(delta_cache) == EOF && err == NULL) err = got_error_from_errno("fclose"); return err; } got-portable-0.111/lib/pack_index.c0000644000175000017500000006177615001741021012623 /* * Copyright (c) 2019 Ori Bernstein * Copyright (c) 2020, 2022 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_object.h" #include "got_lib_hash.h" #include "got_lib_delta.h" #include "got_lib_inflate.h" #include "got_lib_object.h" #include "got_lib_object_parse.h" #include "got_lib_object_idset.h" #include "got_lib_privsep.h" #include "got_lib_pack.h" #include "got_lib_ratelimit.h" #include "got_lib_pack_index.h" #include "got_lib_delta_cache.h" struct got_indexed_object { struct got_object_id id; /* * Has this object been fully resolved? * If so, we know its ID, otherwise we don't and 'id' is invalid. */ int valid; /* Offset of type+size field for this object in pack file. */ off_t off; /* Type+size values parsed from pack file. */ uint8_t type; uint64_t size; /* Length of on-disk type+size data. */ size_t tslen; /* Length of object data following type+size. */ size_t len; uint32_t crc; union { struct { /* For ref deltas. */ struct got_object_id ref_id; } ref; struct { /* For offset deltas. */ off_t base_offset; size_t base_offsetlen; } ofs; } delta; }; static void putbe32(char *b, uint32_t n) { b[0] = n >> 24; b[1] = n >> 16; b[2] = n >> 8; b[3] = n >> 0; } static const struct got_error * read_checksum(uint32_t *crc, struct got_hash *ctx, int fd, size_t len) { uint8_t buf[8192]; size_t n; ssize_t r; for (n = len; n > 0; n -= r){ r = read(fd, buf, n > sizeof(buf) ? sizeof(buf) : n); if (r == -1) return got_error_from_errno("read"); if (r == 0) break; if (crc) *crc = crc32(*crc, buf, r); if (ctx) got_hash_update(ctx, buf, r); } return NULL; } static const struct got_error * read_file_digest(struct got_hash *ctx, FILE *f, size_t len) { uint8_t buf[8192]; size_t n, r; for (n = len; n > 0; n -= r) { r = fread(buf, 1, n > sizeof(buf) ? sizeof(buf) : n, f); if (r == 0) { if (feof(f)) return NULL; return got_ferror(f, GOT_ERR_IO); } got_hash_update(ctx, buf, r); } return NULL; } static const struct got_error * read_packed_object(struct got_pack *pack, struct got_indexed_object *obj, FILE *tmpfile, struct got_hash *pack_hash_ctx) { const struct got_error *err = NULL; struct got_hash ctx; uint8_t *data = NULL; size_t datalen = 0; ssize_t n; char *header; size_t headerlen; const char *obj_label; size_t mapoff = obj->off; struct got_inflate_checksum csum; size_t digest_len; memset(&csum, 0, sizeof(csum)); csum.input_ctx = pack_hash_ctx; csum.input_crc = &obj->crc; digest_len = got_hash_digest_length(pack->algo); err = got_pack_parse_object_type_and_size(&obj->type, &obj->size, &obj->tslen, pack, obj->off); if (err) return err; if (pack->map) { obj->crc = crc32(obj->crc, pack->map + mapoff, obj->tslen); got_hash_update(pack_hash_ctx, pack->map + mapoff, obj->tslen); mapoff += obj->tslen; } else { /* XXX Seek back and get the CRC of on-disk type+size bytes. */ if (lseek(pack->fd, obj->off, SEEK_SET) == -1) return got_error_from_errno("lseek"); err = read_checksum(&obj->crc, pack_hash_ctx, pack->fd, obj->tslen); if (err) return err; } switch (obj->type) { case GOT_OBJ_TYPE_BLOB: case GOT_OBJ_TYPE_COMMIT: case GOT_OBJ_TYPE_TREE: case GOT_OBJ_TYPE_TAG: if (obj->size > GOT_DELTA_RESULT_SIZE_CACHED_MAX) { if (fseek(tmpfile, 0L, SEEK_SET) == -1) { err = got_error_from_errno("fseek"); break; } if (pack->map) { err = got_inflate_to_file_mmap(&datalen, &obj->len, &csum, pack->map, mapoff, pack->filesize - mapoff, tmpfile); } else { err = got_inflate_to_file_fd(&datalen, &obj->len, &csum, pack->fd, tmpfile); } } else { if (pack->map) { err = got_inflate_to_mem_mmap(&data, &datalen, &obj->len, &csum, pack->map, mapoff, pack->filesize - mapoff); } else { err = got_inflate_to_mem_fd(&data, &datalen, &obj->len, &csum, obj->size, pack->fd); } } if (err) break; got_hash_init(&ctx, pack->algo); err = got_object_type_label(&obj_label, obj->type); if (err) { free(data); break; } if (asprintf(&header, "%s %lld", obj_label, (long long)obj->size) == -1) { err = got_error_from_errno("asprintf"); free(data); break; } headerlen = strlen(header) + 1; got_hash_update(&ctx, header, headerlen); if (obj->size > GOT_DELTA_RESULT_SIZE_CACHED_MAX) { err = read_file_digest(&ctx, tmpfile, datalen); if (err) { free(header); free(data); break; } } else got_hash_update(&ctx, data, datalen); got_hash_final_object_id(&ctx, &obj->id); free(header); free(data); break; case GOT_OBJ_TYPE_REF_DELTA: memset(obj->id.hash, 0xff, digest_len); obj->id.algo = pack->algo; if (pack->map) { if (mapoff + digest_len >= pack->filesize) { err = got_error(GOT_ERR_BAD_PACKFILE); break; } memcpy(obj->delta.ref.ref_id.hash, pack->map + mapoff, digest_len); obj->crc = crc32(obj->crc, pack->map + mapoff, digest_len); got_hash_update(pack_hash_ctx, pack->map + mapoff, digest_len); mapoff += digest_len; err = got_inflate_to_mem_mmap(NULL, &datalen, &obj->len, &csum, pack->map, mapoff, pack->filesize - mapoff); if (err) break; } else { n = read(pack->fd, obj->delta.ref.ref_id.hash, digest_len); if (n == -1) { err = got_error_from_errno("read"); break; } if (n < digest_len) { err = got_error(GOT_ERR_BAD_PACKFILE); break; } obj->crc = crc32(obj->crc, obj->delta.ref.ref_id.hash, digest_len); got_hash_update(pack_hash_ctx, obj->delta.ref.ref_id.hash, digest_len); err = got_inflate_to_mem_fd(NULL, &datalen, &obj->len, &csum, obj->size, pack->fd); if (err) break; } obj->len += digest_len; break; case GOT_OBJ_TYPE_OFFSET_DELTA: memset(obj->id.hash, 0xff, digest_len); obj->id.algo = pack->algo; err = got_pack_parse_offset_delta(&obj->delta.ofs.base_offset, &obj->delta.ofs.base_offsetlen, pack, obj->off, obj->tslen); if (err) break; if (pack->map) { if (mapoff + obj->delta.ofs.base_offsetlen >= pack->filesize) { err = got_error(GOT_ERR_BAD_PACKFILE); break; } if (mapoff + obj->delta.ofs.base_offsetlen > SIZE_MAX) { err = got_error_fmt(GOT_ERR_RANGE, "mapoff %lld would overflow size_t", (long long)mapoff + obj->delta.ofs.base_offsetlen); break; } obj->crc = crc32(obj->crc, pack->map + mapoff, obj->delta.ofs.base_offsetlen); got_hash_update(pack_hash_ctx, pack->map + mapoff, obj->delta.ofs.base_offsetlen); mapoff += obj->delta.ofs.base_offsetlen; err = got_inflate_to_mem_mmap(NULL, &datalen, &obj->len, &csum, pack->map, mapoff, pack->filesize - mapoff); if (err) break; } else { /* * XXX Seek back and get CRC and hash digest * of on-disk offset bytes. */ if (lseek(pack->fd, obj->off + obj->tslen, SEEK_SET) == -1) { err = got_error_from_errno("lseek"); break; } err = read_checksum(&obj->crc, pack_hash_ctx, pack->fd, obj->delta.ofs.base_offsetlen); if (err) break; err = got_inflate_to_mem_fd(NULL, &datalen, &obj->len, &csum, obj->size, pack->fd); if (err) break; } obj->len += obj->delta.ofs.base_offsetlen; break; default: err = got_error(GOT_ERR_OBJ_TYPE); break; } return err; } const struct got_error * got_pack_hwrite(int fd, void *buf, int len, struct got_hash *ctx) { ssize_t w; got_hash_update(ctx, buf, len); w = write(fd, buf, len); if (w == -1) return got_error_from_errno("write"); if (w != len) return got_error(GOT_ERR_IO); return NULL; } static const struct got_error * resolve_deltified_object(struct got_pack *pack, struct got_packidx *packidx, struct got_indexed_object *obj, FILE *tmpfile, FILE *delta_base_file, FILE *delta_accum_file) { const struct got_error *err = NULL; struct got_delta_chain deltas; struct got_delta *delta; uint8_t *buf = NULL; size_t len = 0; struct got_hash ctx; char *header = NULL; size_t headerlen; uint64_t max_size; int base_obj_type; const char *obj_label; deltas.nentries = 0; STAILQ_INIT(&deltas.entries); err = got_pack_resolve_delta_chain(&deltas, packidx, pack, obj->off, obj->tslen, obj->type, obj->size, GOT_DELTA_CHAIN_RECURSION_MAX); if (err) goto done; err = got_pack_get_delta_chain_max_size(&max_size, &deltas, pack); if (err) goto done; if (max_size > GOT_DELTA_RESULT_SIZE_CACHED_MAX) { rewind(tmpfile); rewind(delta_base_file); rewind(delta_accum_file); err = got_pack_dump_delta_chain_to_file(&len, &deltas, pack, tmpfile, delta_base_file, delta_accum_file); if (err) goto done; } else { err = got_pack_dump_delta_chain_to_mem(&buf, &len, &deltas, pack); } if (err) goto done; err = got_delta_chain_get_base_type(&base_obj_type, &deltas); if (err) goto done; err = got_object_type_label(&obj_label, base_obj_type); if (err) goto done; if (asprintf(&header, "%s %zd", obj_label, len) == -1) { err = got_error_from_errno("asprintf"); goto done; } headerlen = strlen(header) + 1; got_hash_init(&ctx, pack->algo); got_hash_update(&ctx, header, headerlen); if (max_size > GOT_DELTA_RESULT_SIZE_CACHED_MAX) { err = read_file_digest(&ctx, tmpfile, len); if (err) goto done; } else got_hash_update(&ctx, buf, len); got_hash_final_object_id(&ctx, &obj->id); done: free(buf); free(header); while (!STAILQ_EMPTY(&deltas.entries)) { delta = STAILQ_FIRST(&deltas.entries); STAILQ_REMOVE_HEAD(&deltas.entries, entry); free(delta); } return err; } /* Determine the slot in the pack index a given object ID should use. */ static int find_object_idx(struct got_packidx *packidx, uint8_t *hash) { u_int8_t id0 = hash[0]; uint32_t nindexed = be32toh(packidx->hdr.fanout_table[0xff]); int left = 0, right = nindexed - 1; int cmp = 0, i = 0; size_t digest_len = got_hash_digest_length(packidx->algo); if (id0 > 0) left = be32toh(packidx->hdr.fanout_table[id0 - 1]); while (left <= right) { uint8_t *oid; i = ((left + right) / 2); oid = packidx->hdr.sorted_ids + i * digest_len; cmp = memcmp(hash, oid, digest_len); if (cmp == 0) return -1; /* object already indexed */ else if (cmp > 0) left = i + 1; else if (cmp < 0) right = i - 1; } return left; } #if 0 static void print_packidx(struct got_packidx *packidx) { uint32_t nindexed = be32toh(packidx->hdr.fanout_table[0xff]); size_t digest_len = got_hash_digest_length(packidx->algo); int i; fprintf(stderr, "object IDs:\n"); for (i = 0; i < nindexed; i++) { char hex[GOT_HASH_DIGEST_STRING_MAXLEN]; got_hash_digest_to_str(packidx->hdr.sorted_ids + i * digest_len, hex, sizeof(hex), packidx->algo); fprintf(stderr, "%s\n", hex); } fprintf(stderr, "\n"); fprintf(stderr, "object offsets:\n"); for (i = 0; i < nindexed; i++) { uint32_t offset = be32toh(packidx->hdr.offsets[i]); if (offset & GOT_PACKIDX_OFFSET_VAL_IS_LARGE_IDX) { int j = offset & GOT_PACKIDX_OFFSET_VAL_MASK; fprintf(stderr, "%u -> %llu\n", offset, be64toh(packidx->hdr.large_offsets[j])); } else fprintf(stderr, "%u\n", offset); } fprintf(stderr, "\n"); fprintf(stderr, "fanout table:"); for (i = 0; i <= 0xff; i++) fprintf(stderr, " %u", be32toh(packidx->hdr.fanout_table[i])); fprintf(stderr, "\n"); } #endif static void add_indexed_object(struct got_packidx *packidx, uint32_t idx, struct got_indexed_object *obj) { int i; uint8_t *oid; size_t digest_len = got_hash_digest_length(packidx->algo); oid = packidx->hdr.sorted_ids + idx * digest_len; memcpy(oid, obj->id.hash, digest_len); obj->id.algo = packidx->algo; packidx->hdr.crc32[idx] = htobe32(obj->crc); if (obj->off < GOT_PACKIDX_OFFSET_VAL_IS_LARGE_IDX) packidx->hdr.offsets[idx] = htobe32(obj->off); else { packidx->hdr.offsets[idx] = htobe32(packidx->nlargeobj | GOT_PACKIDX_OFFSET_VAL_IS_LARGE_IDX); packidx->hdr.large_offsets[packidx->nlargeobj] = htobe64(obj->off); packidx->nlargeobj++; } for (i = obj->id.hash[0]; i <= 0xff; i++) { uint32_t n = be32toh(packidx->hdr.fanout_table[i]); packidx->hdr.fanout_table[i] = htobe32(n + 1); } } static int indexed_obj_cmp(const void *pa, const void *pb) { struct got_indexed_object *a, *b; a = (struct got_indexed_object *)pa; b = (struct got_indexed_object *)pb; return got_object_id_cmp(&a->id, &b->id); } static void make_packidx(struct got_packidx *packidx, uint32_t nobj, struct got_indexed_object *objects) { struct got_indexed_object *obj; int i; uint32_t idx = 0; qsort(objects, nobj, sizeof(struct got_indexed_object), indexed_obj_cmp); memset(packidx->hdr.fanout_table, 0, GOT_PACKIDX_V2_FANOUT_TABLE_ITEMS * sizeof(uint32_t)); packidx->nlargeobj = 0; for (i = 0; i < nobj; i++) { obj = &objects[i]; if (obj->valid) add_indexed_object(packidx, idx++, obj); } } static void update_packidx(struct got_packidx *packidx, uint32_t nobj, struct got_indexed_object *obj) { int idx; uint32_t nindexed = be32toh(packidx->hdr.fanout_table[0xff]); size_t digest_len = got_hash_digest_length(packidx->algo); uint8_t *from, *to; idx = find_object_idx(packidx, obj->id.hash); if (idx == -1) return; /* object already indexed */ from = packidx->hdr.sorted_ids + idx * digest_len; to = from + digest_len; memmove(to, from, digest_len * (nindexed - idx)); memmove(&packidx->hdr.offsets[idx + 1], &packidx->hdr.offsets[idx], sizeof(uint32_t) * (nindexed - idx)); add_indexed_object(packidx, idx, obj); } static const struct got_error * report_progress(uint32_t nobj_total, uint32_t nobj_indexed, uint32_t nobj_loose, uint32_t nobj_resolved, struct got_ratelimit *rl, got_pack_index_progress_cb progress_cb, void *progress_arg) { const struct got_error *err; int elapsed = 0; if (rl) { err = got_ratelimit_check(&elapsed, rl); if (err || !elapsed) return err; } return progress_cb(progress_arg, nobj_total, nobj_indexed, nobj_loose, nobj_resolved); } const struct got_error * got_pack_index(struct got_pack *pack, int idxfd, FILE *tmpfile, FILE *delta_base_file, FILE *delta_accum_file, struct got_object_id *pack_hash_expected, got_pack_index_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl) { const struct got_error *err; struct got_packfile_hdr hdr; struct got_packidx packidx; char buf[8]; struct got_object_id pack_hash; uint32_t nobj, nvalid, nloose, nresolved = 0, i; struct got_indexed_object *objects = NULL, *obj; struct got_hash ctx; uint8_t packidx_hash[GOT_HASH_DIGEST_MAXLEN]; ssize_t r, w; int pass, have_ref_deltas = 0, first_delta_idx = -1; size_t mapoff = 0; int p_indexed = 0, last_p_indexed = -1; int p_resolved = 0, last_p_resolved = -1; ssize_t digest_len; /* This has to be signed for lseek(2) later */ digest_len = got_hash_digest_length(pack->algo); /* Require that pack file header and hash trailer are present. */ if (pack->filesize < sizeof(hdr) + digest_len) return got_error_msg(GOT_ERR_BAD_PACKFILE, "short pack file"); if (pack->map) { memcpy(&hdr, pack->map, sizeof(hdr)); mapoff += sizeof(hdr); } else { r = read(pack->fd, &hdr, sizeof(hdr)); if (r == -1) return got_error_from_errno("read"); if (r < sizeof(hdr)) return got_error_msg(GOT_ERR_BAD_PACKFILE, "short pack file"); } if (hdr.signature != htobe32(GOT_PACKFILE_SIGNATURE)) return got_error_msg(GOT_ERR_BAD_PACKFILE, "bad packfile signature"); if (hdr.version != htobe32(GOT_PACKFILE_VERSION)) return got_error_msg(GOT_ERR_BAD_PACKFILE, "bad packfile version"); nobj = be32toh(hdr.nobjects); if (nobj == 0) return got_error_msg(GOT_ERR_BAD_PACKFILE, "bad packfile with zero objects"); /* We compute the hash of pack file contents and verify later on. */ got_hash_init(&ctx, pack->algo); got_hash_update(&ctx, &hdr, sizeof(hdr)); /* * Create an in-memory pack index which will grow as objects * IDs in the pack file are discovered. Only fields used to * read deltified objects will be needed by the pack.c library * code, so setting up just a pack index header is sufficient. */ memset(&packidx, 0, sizeof(packidx)); packidx.hdr.magic = malloc(sizeof(uint32_t)); if (packidx.hdr.magic == NULL) return got_error_from_errno("malloc"); *packidx.hdr.magic = htobe32(GOT_PACKIDX_V2_MAGIC); packidx.hdr.version = malloc(sizeof(uint32_t)); if (packidx.hdr.version == NULL) { err = got_error_from_errno("malloc"); goto done; } *packidx.hdr.version = htobe32(GOT_PACKIDX_VERSION); packidx.hdr.fanout_table = calloc(GOT_PACKIDX_V2_FANOUT_TABLE_ITEMS, sizeof(uint32_t)); if (packidx.hdr.fanout_table == NULL) { err = got_error_from_errno("calloc"); goto done; } packidx.hdr.sorted_ids = calloc(nobj, digest_len); if (packidx.hdr.sorted_ids == NULL) { err = got_error_from_errno("calloc"); goto done; } packidx.hdr.crc32 = calloc(nobj, sizeof(uint32_t)); if (packidx.hdr.crc32 == NULL) { err = got_error_from_errno("calloc"); goto done; } packidx.hdr.offsets = calloc(nobj, sizeof(uint32_t)); if (packidx.hdr.offsets == NULL) { err = got_error_from_errno("calloc"); goto done; } packidx.algo = pack->algo; /* Large offsets table is empty for pack files < 2 GB. */ if (pack->filesize >= GOT_PACKIDX_OFFSET_VAL_IS_LARGE_IDX) { packidx.hdr.large_offsets = calloc(nobj, sizeof(uint64_t)); if (packidx.hdr.large_offsets == NULL) { err = got_error_from_errno("calloc"); goto done; } } nvalid = 0; nloose = 0; objects = calloc(nobj, sizeof(struct got_indexed_object)); if (objects == NULL) return got_error_from_errno("calloc"); /* * First pass: locate all objects and identify un-deltified objects. * * When this pass has completed we will know offset, type, size, and * CRC information for all objects in this pack file. We won't know * any of the actual object IDs of deltified objects yet since we * will not yet attempt to combine deltas. */ pass = 1; for (i = 0; i < nobj; i++) { /* Don't send too many progress privsep messages. */ p_indexed = ((i + 1) * 100) / nobj; if (p_indexed != last_p_indexed) { err = report_progress(nobj, i + 1, nloose, 0, rl, progress_cb, progress_arg); if (err) goto done; last_p_indexed = p_indexed; } obj = &objects[i]; obj->crc = crc32(0L, NULL, 0); /* Store offset to type+size information for this object. */ if (pack->map) { obj->off = mapoff; } else { obj->off = lseek(pack->fd, 0, SEEK_CUR); if (obj->off == -1) { err = got_error_from_errno("lseek"); goto done; } } err = read_packed_object(pack, obj, tmpfile, &ctx); if (err) goto done; if (pack->map) { mapoff += obj->tslen + obj->len; } else { if (lseek(pack->fd, obj->off + obj->tslen + obj->len, SEEK_SET) == -1) { err = got_error_from_errno("lseek"); goto done; } } if (obj->type == GOT_OBJ_TYPE_BLOB || obj->type == GOT_OBJ_TYPE_TREE || obj->type == GOT_OBJ_TYPE_COMMIT || obj->type == GOT_OBJ_TYPE_TAG) { obj->valid = 1; nloose++; } else { if (first_delta_idx == -1) first_delta_idx = i; if (obj->type == GOT_OBJ_TYPE_REF_DELTA) have_ref_deltas = 1; } } nvalid = nloose; /* * Having done a full pass over the pack file and can now * verify its checksum. */ got_hash_final_object_id(&ctx, &pack_hash); if (got_object_id_cmp(pack_hash_expected, &pack_hash) != 0) { err = got_error(GOT_ERR_PACKFILE_CSUM); goto done; } /* Verify the hash checksum stored at the end of the pack file. */ if (pack->map) { if (pack->filesize > SIZE_MAX) { err = got_error_fmt(GOT_ERR_RANGE, "filesize %lld overflows size_t", (long long)pack->filesize); goto done; } memcpy(pack_hash_expected, pack->map + pack->filesize - digest_len, digest_len); } else { ssize_t n; if (lseek(pack->fd, -digest_len, SEEK_END) == -1) { err = got_error_from_errno("lseek"); goto done; } n = read(pack->fd, pack_hash_expected, digest_len); if (n == -1) { err = got_error_from_errno("read"); goto done; } if (n != digest_len) { err = got_error(GOT_ERR_IO); goto done; } } if (got_object_id_cmp(pack_hash_expected, &pack_hash) != 0) { err = got_error_msg(GOT_ERR_BAD_PACKFILE, "bad checksum in pack file trailer"); goto done; } if (first_delta_idx == -1) first_delta_idx = 0; /* In order to resolve ref deltas we need an in-progress pack index. */ if (have_ref_deltas) make_packidx(&packidx, nobj, objects); /* * Second pass: We can now resolve deltas to compute the IDs of * objects which appear in deltified form. Because deltas can be * chained this pass may require a couple of iterations until all * IDs of deltified objects have been discovered. */ pass++; while (nvalid != nobj) { int n = 0; /* * This loop will only run once unless the pack file * contains ref deltas which refer to objects located * later in the pack file, which is unusual. * Offset deltas can always be resolved in one pass * unless the packfile is corrupt. */ for (i = first_delta_idx; i < nobj; i++) { obj = &objects[i]; if (obj->type != GOT_OBJ_TYPE_REF_DELTA && obj->type != GOT_OBJ_TYPE_OFFSET_DELTA) continue; if (obj->valid) continue; if (pack->map == NULL && lseek(pack->fd, obj->off + obj->tslen, SEEK_SET) == -1) { err = got_error_from_errno("lseek"); goto done; } err = resolve_deltified_object(pack, &packidx, obj, tmpfile, delta_base_file, delta_accum_file); if (err) { if (err->code != GOT_ERR_NO_OBJ) goto done; /* * We cannot resolve this object yet because * a delta base is unknown. Try again later. */ continue; } obj->valid = 1; n++; if (have_ref_deltas) update_packidx(&packidx, nobj, obj); /* Don't send too many progress privsep messages. */ p_resolved = ((nresolved + n) * 100) / nobj; if (p_resolved != last_p_resolved) { err = report_progress(nobj, nobj, nloose, nresolved + n, rl, progress_cb, progress_arg); if (err) goto done; last_p_resolved = p_resolved; } } if (pass++ > 3 && n == 0) { err = got_error_msg(GOT_ERR_BAD_PACKFILE, "could not resolve any of deltas; packfile could " "be corrupt"); goto done; } nresolved += n; nvalid += n; } if (nloose + nresolved != nobj) { static char msg[64]; snprintf(msg, sizeof(msg), "discovered only %d of %d objects", nloose + nresolved, nobj); err = got_error_msg(GOT_ERR_BAD_PACKFILE, msg); goto done; } err = report_progress(nobj, nobj, nloose, nresolved, NULL, progress_cb, progress_arg); if (err) goto done; make_packidx(&packidx, nobj, objects); free(objects); objects = NULL; got_hash_init(&ctx, pack->algo); putbe32(buf, GOT_PACKIDX_V2_MAGIC); putbe32(buf + 4, GOT_PACKIDX_VERSION); err = got_pack_hwrite(idxfd, buf, 8, &ctx); if (err) goto done; err = got_pack_hwrite(idxfd, packidx.hdr.fanout_table, GOT_PACKIDX_V2_FANOUT_TABLE_ITEMS * sizeof(uint32_t), &ctx); if (err) goto done; err = got_pack_hwrite(idxfd, packidx.hdr.sorted_ids, nobj * digest_len, &ctx); if (err) goto done; err = got_pack_hwrite(idxfd, packidx.hdr.crc32, nobj * sizeof(uint32_t), &ctx); if (err) goto done; err = got_pack_hwrite(idxfd, packidx.hdr.offsets, nobj * sizeof(uint32_t), &ctx); if (err) goto done; if (packidx.nlargeobj > 0) { err = got_pack_hwrite(idxfd, packidx.hdr.large_offsets, packidx.nlargeobj * sizeof(uint64_t), &ctx); if (err) goto done; } err = got_pack_hwrite(idxfd, &pack_hash.hash, digest_len, &ctx); if (err) goto done; got_hash_final(&ctx, packidx_hash); w = write(idxfd, packidx_hash, digest_len); if (w == -1) { err = got_error_from_errno("write"); goto done; } if (w != digest_len) { err = got_error(GOT_ERR_IO); goto done; } done: free(objects); free(packidx.hdr.magic); free(packidx.hdr.version); free(packidx.hdr.fanout_table); free(packidx.hdr.sorted_ids); free(packidx.hdr.offsets); free(packidx.hdr.large_offsets); return err; } got-portable-0.111/lib/diff_atomize_text.c0000644000175000017500000001171115001740614014211 /* Split source by line breaks, and calculate a simplistic checksum. */ /* * Copyright (c) 2020 Neels Hofmeyr * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "diff_internal.h" #include "diff_debug.h" unsigned int diff_atom_hash_update(unsigned int hash, unsigned char atom_byte) { return hash * 23 + atom_byte; } static int diff_data_atomize_text_lines_fd(struct diff_data *d) { off_t pos = 0; const off_t end = pos + d->len; unsigned int array_size_estimate = d->len / 50; unsigned int pow2 = 1; bool ignore_whitespace = (d->diff_flags & DIFF_FLAG_IGNORE_WHITESPACE); bool embedded_nul = false; while (array_size_estimate >>= 1) pow2++; ARRAYLIST_INIT(d->atoms, 1 << pow2); if (fseek(d->root->f, 0L, SEEK_SET) == -1) return errno; while (pos < end) { off_t line_end = pos; unsigned int hash = 0; unsigned char buf[512]; size_t r, i; struct diff_atom *atom; int eol = 0; while (eol == 0 && line_end < end) { r = fread(buf, sizeof(char), sizeof(buf), d->root->f); if (r == 0 && ferror(d->root->f)) return EIO; i = 0; while (eol == 0 && i < r) { if (buf[i] != '\r' && buf[i] != '\n') { if (!ignore_whitespace || !isspace((unsigned char)buf[i])) hash = diff_atom_hash_update( hash, buf[i]); if (buf[i] == '\0') embedded_nul = true; line_end++; } else eol = buf[i]; i++; } } /* When not at the end of data, the line ending char ('\r' or * '\n') must follow */ if (line_end < end) line_end++; /* If that was an '\r', also pull in any following '\n' */ if (line_end < end && eol == '\r') { if (fseeko(d->root->f, line_end, SEEK_SET) == -1) return errno; r = fread(buf, sizeof(char), sizeof(buf), d->root->f); if (r == 0 && ferror(d->root->f)) return EIO; if (r > 0 && buf[0] == '\n') line_end++; } /* Record the found line as diff atom */ ARRAYLIST_ADD(atom, d->atoms); if (!atom) return ENOMEM; *atom = (struct diff_atom){ .root = d, .pos = pos, .at = NULL, /* atom data is not memory-mapped */ .len = line_end - pos, .hash = hash, }; /* Starting point for next line: */ pos = line_end; if (fseeko(d->root->f, pos, SEEK_SET) == -1) return errno; } /* File are considered binary if they contain embedded '\0' bytes. */ if (embedded_nul) d->atomizer_flags |= DIFF_ATOMIZER_FOUND_BINARY_DATA; return DIFF_RC_OK; } static int diff_data_atomize_text_lines_mmap(struct diff_data *d) { const uint8_t *pos = d->data; const uint8_t *end = pos + d->len; bool ignore_whitespace = (d->diff_flags & DIFF_FLAG_IGNORE_WHITESPACE); bool embedded_nul = false; unsigned int array_size_estimate = d->len / 50; unsigned int pow2 = 1; while (array_size_estimate >>= 1) pow2++; ARRAYLIST_INIT(d->atoms, 1 << pow2); while (pos < end) { const uint8_t *line_end = pos; unsigned int hash = 0; while (line_end < end && *line_end != '\r' && *line_end != '\n') { if (!ignore_whitespace || !isspace((unsigned char)*line_end)) hash = diff_atom_hash_update(hash, *line_end); if (*line_end == '\0') embedded_nul = true; line_end++; } /* When not at the end of data, the line ending char ('\r' or * '\n') must follow */ if (line_end < end && *line_end == '\r') line_end++; if (line_end < end && *line_end == '\n') line_end++; /* Record the found line as diff atom */ struct diff_atom *atom; ARRAYLIST_ADD(atom, d->atoms); if (!atom) return ENOMEM; *atom = (struct diff_atom){ .root = d, .pos = (off_t)(pos - d->data), .at = pos, .len = line_end - pos, .hash = hash, }; /* Starting point for next line: */ pos = line_end; } /* File are considered binary if they contain embedded '\0' bytes. */ if (embedded_nul) d->atomizer_flags |= DIFF_ATOMIZER_FOUND_BINARY_DATA; return DIFF_RC_OK; } static int diff_data_atomize_text_lines(struct diff_data *d) { if (d->data == NULL) return diff_data_atomize_text_lines_fd(d); else return diff_data_atomize_text_lines_mmap(d); } int diff_atomize_text_by_line(void *func_data, struct diff_data *d) { return diff_data_atomize_text_lines(d); } got-portable-0.111/lib/got_lib_object.h0000644000175000017500000001320315001740614013455 /* * Copyright (c) 2018, 2019, 2020 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ struct got_object { int type; int flags; #define GOT_OBJ_FLAG_PACKED 0x01 #define GOT_OBJ_FLAG_DELTIFIED 0x02 size_t hdrlen; size_t size; struct got_object_id id; int pack_idx; /* if packed */ off_t pack_offset; /* if packed */ struct got_delta_chain deltas; /* if deltified */ int refcnt; /* > 0 if open and/or cached */ }; /* A callback function which is invoked when a raw object is closed. */ struct got_raw_object; typedef void (got_object_raw_close_cb)(struct got_raw_object *); struct got_raw_object { FILE *f; /* NULL if data buffer is being used */ int fd; /* -1 unless data buffer is memory-mapped */ int tempfile_idx; /* -1 unless using a repository-tempfile */ uint8_t *data; off_t size; size_t hdrlen; int refcnt; /* > 0 if open and/or cached */ got_object_raw_close_cb *close_cb; void *close_arg; }; struct got_commit_object { struct got_object_id *tree_id; unsigned int nparents; struct got_object_id_queue parent_ids; char *author; time_t author_time; /* UTC */ time_t author_gmtoff; char *committer; time_t committer_time; /* UTC */ time_t committer_gmtoff; char *logmsg; int refcnt; /* > 0 if open and/or cached */ int flags; #define GOT_COMMIT_FLAG_PACKED 0x01 }; struct got_tree_entry { mode_t mode; char name[NAME_MAX + 1 /* NUL */]; struct got_object_id id; int idx; }; struct got_tree_object { int nentries; struct got_tree_entry *entries; int refcnt; }; struct got_blob_object { FILE *f; uint8_t *data; size_t hdrlen; size_t blocksize; uint8_t *read_buf; struct got_object_id id; }; struct got_tag_object { struct got_object_id id; int obj_type; char *tag; time_t tagger_time; time_t tagger_gmtoff; char *tagger; char *tagmsg; int refcnt; /* > 0 if open and/or cached */ }; struct got_object_id *got_object_get_id(struct got_object *); const struct got_error *got_object_get_id_str(char **, struct got_object *); const struct got_error *got_object_get_path(char **, struct got_object_id *, struct got_repository *); const struct got_error *got_object_open_loose_fd(int *, struct got_object_id *, struct got_repository *); const struct got_error *got_object_open_packed(struct got_object **, struct got_object_id *, struct got_repository *); struct got_pack; struct got_packidx; const struct got_error *got_object_open_from_packfile(struct got_object **, struct got_object_id *, struct got_pack *, struct got_packidx *, int, struct got_repository *); const struct got_error *got_object_read_raw_delta(uint64_t *, uint64_t *, off_t *, off_t *, off_t *, off_t *, struct got_object_id **, int, struct got_packidx *, int, struct got_object_id *, struct got_repository *); const struct got_error *got_object_prepare_delta_reuse(struct got_pack **, struct got_packidx *, int, struct got_repository *); const struct got_error *got_object_read_header_privsep(struct got_object **, struct got_object_id *, struct got_repository *, int); const struct got_error *got_object_open(struct got_object **, struct got_repository *, struct got_object_id *); const struct got_error *got_object_raw_open(struct got_raw_object **, int *, struct got_repository *, struct got_object_id *); const struct got_error *got_object_raw_close(struct got_raw_object *); const struct got_error *got_object_open_by_id_str(struct got_object **, struct got_repository *, const char *); void got_object_close(struct got_object *); const struct got_error *got_object_commit_open(struct got_commit_object **, struct got_repository *, struct got_object *); const struct got_error *got_object_tree_open(struct got_tree_object **, struct got_repository *, struct got_object *); const struct got_error *got_object_blob_open(struct got_blob_object **, struct got_repository *, struct got_object *, size_t, int); char *got_object_blob_id_str(struct got_blob_object*, char *, size_t); const struct got_error *got_object_tag_open(struct got_tag_object **, struct got_repository *, struct got_object *); const struct got_error *got_object_tree_entry_dup(struct got_tree_entry **, struct got_tree_entry *); const struct got_error *got_traverse_packed_commits( struct got_object_id_queue *, struct got_object_id *, const char *, struct got_repository *); typedef const struct got_error *(*got_object_enumerate_commit_cb)(void *, time_t, struct got_object_id *, struct got_repository *); typedef const struct got_error *(*got_object_enumerate_tree_cb)(void *, struct got_tree_object *, time_t, struct got_object_id *, const char *, struct got_repository *); const struct got_error *got_object_enumerate(int *, got_object_enumerate_commit_cb, got_object_enumerate_tree_cb, void *, struct got_object_id **, int, struct got_object_id **, int, struct got_packidx *, struct got_repository *); const struct got_error *got_object_raw_alloc(struct got_raw_object **, uint8_t *, int *, size_t, size_t, off_t); got-portable-0.111/lib/gotsys_imsg.c0000644000175000017500000005213215001741021013047 /* * Copyright (c) 2025 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_path.h" #include "got_object.h" #include "gotsysd.h" #include "gotsys.h" #ifndef MIN #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b)) #endif const struct got_error * gotsys_imsg_send_users(struct gotsysd_imsgev *iev, struct gotsys_userlist *users, int imsg_type, int imsg_done_type, int send_passwords) { const struct got_error *err; struct gotsys_user *u; size_t totlen, remain, mlen; const size_t maxmesg = MAX_IMSGSIZE - IMSG_HEADER_SIZE; struct gotsysd_imsg_sysconf_user iuser; struct ibuf *wbuf = NULL; u = STAILQ_FIRST(users); totlen = 0; while (u) { size_t namelen, pwlen = 0, ulen; namelen = strlen(u->name); if (send_passwords) pwlen = (u->password ? strlen(u->password) : 0); if (namelen + pwlen < namelen) { return got_error_msg(GOT_ERR_NO_SPACE, "user name/password length overflow"); } ulen = namelen + pwlen; if (totlen > INT_MAX - sizeof(iuser) - ulen) { return got_error_msg(GOT_ERR_NO_SPACE, "user data length overflow"); } totlen += sizeof(iuser) + ulen; u = STAILQ_NEXT(u, entry); } if (totlen == 0) return NULL; u = STAILQ_FIRST(users); remain = totlen; mlen = 0; while (u) { size_t ulen; iuser.name_len = strlen(u->name); iuser.password_len = (send_passwords && u->password ? strlen(u->password) : 0); ulen = iuser.name_len + iuser.password_len; if (wbuf != NULL && mlen + sizeof(iuser) + ulen > maxmesg) { imsg_close(&iev->ibuf, wbuf); err = gotsysd_imsg_flush(&iev->ibuf); if (err) return err; wbuf = NULL; mlen = 0; } if (wbuf == NULL) { wbuf = imsg_create(&iev->ibuf, imsg_type, 0, 0, MIN(remain, maxmesg)); if (wbuf == NULL) { return got_error_from_errno_fmt( "imsg_create %d", imsg_type); } } if (imsg_add(wbuf, &iuser, sizeof(iuser)) == -1) return got_error_from_errno_fmt("imsg_add %d", imsg_type); if (imsg_add(wbuf, u->name, iuser.name_len) == -1) return got_error_from_errno_fmt("imsg_add %d", imsg_type); if (imsg_add(wbuf, u->password, iuser.password_len) == -1) return got_error_from_errno_fmt("imsg_add %d", imsg_type); remain -= sizeof(iuser) + ulen; mlen += sizeof(iuser) + ulen; u = STAILQ_NEXT(u, entry); } imsg_close(&iev->ibuf, wbuf); err = gotsysd_imsg_flush(&iev->ibuf); if (err) return err; if (gotsysd_imsg_compose_event(iev, imsg_done_type, 0, -1, NULL, 0) == -1) return got_error_from_errno_fmt("imsg_compose %d", imsg_done_type); return NULL; } const struct got_error * gotsys_imsg_recv_users(struct imsg *imsg, struct gotsys_userlist *users) { const struct got_error *err = NULL; struct gotsysd_imsg_sysconf_user iuser; struct gotsys_user *user = NULL; char *name = NULL; size_t datalen, offset, remain; datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen < sizeof(iuser)) return got_error(GOT_ERR_PRIVSEP_LEN); remain = datalen; offset = 0; while (remain > 0) { size_t namelen, pwlen, ulen; int is_anonymous_user = 0; if (remain < sizeof(iuser)) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&iuser, imsg->data + offset, sizeof(iuser)); namelen = iuser.name_len; if (namelen <= 0 || namelen > _PW_NAME_LEN) return got_error(GOT_ERR_PRIVSEP_LEN); pwlen = iuser.password_len; if (pwlen > _PASSWORD_LEN) return got_error(GOT_ERR_PRIVSEP_LEN); if (namelen + pwlen < namelen || namelen + pwlen < namelen || namelen + pwlen < namelen + pwlen) { return got_error_msg(GOT_ERR_NO_SPACE, "user name/password/sshpubkey length overflow"); } ulen = namelen + pwlen; if (sizeof(iuser) + ulen < sizeof(iuser)) { return got_error_msg(GOT_ERR_NO_SPACE, "user data length overflow"); } if (sizeof(iuser) + ulen > remain) return got_error(GOT_ERR_PRIVSEP_LEN); name = strndup(imsg->data + offset + sizeof(iuser), namelen); if (name == NULL) return got_error_from_errno("strndup"); if (strlen(name) != namelen) { free(name); return got_error(GOT_ERR_PRIVSEP_LEN); } STAILQ_FOREACH(user, users, entry) { if (strcmp(name, user->name) == 0) break; } if (user != NULL) { free(name); name = NULL; user = NULL; continue; } is_anonymous_user = (strcmp(name, "anonymous") == 0); if (!is_anonymous_user) { err = gotsys_conf_validate_name(name, "user"); if (err) { free(name); return err; } } err = gotsys_conf_new_user(&user, name); free(name); name = NULL; if (err) return err; if (pwlen) { if (is_anonymous_user) { err = got_error_msg(GOT_ERR_PRIVSEP_MSG, "the \"anonymous\" user must use an " "empty password"); gotsys_user_free(user); return err; } user->password = strndup(imsg->data + offset + sizeof(iuser) + namelen, pwlen); if (user->password == NULL) { err = got_error_from_errno("strndup"); gotsys_user_free(user); return err; } if (strlen(user->password) != pwlen) { err = got_error(GOT_ERR_PRIVSEP_LEN); gotsys_user_free(user); return err; } } else if (is_anonymous_user) { user->password = strdup(""); if (user->password == NULL) { err = got_error_from_errno("strdup"); gotsys_user_free(user); return err; } } #if 0 log_debug("user %s: password '%s' ssh key '%s'", user->name, user->password ? user->password : "", user->ssh_pubkey ? user->ssh_pubkey : ""); #endif STAILQ_INSERT_TAIL(users, user, entry); user = NULL; offset += sizeof(iuser) + ulen; remain -= sizeof(iuser) + ulen; } return NULL; } const struct got_error * gotsys_imsg_send_groups(struct gotsysd_imsgev *iev, struct gotsys_grouplist *groups, int imsg_group_type, int imsg_group_members_type, int imsg_group_members_done_type, int imsg_done_type) { const struct got_error *err; struct gotsys_group *g; struct gotsysd_imsg_sysconf_group igroup; struct ibuf *wbuf = NULL; g = STAILQ_FIRST(groups); while (g) { igroup.name_len = strlen(g->name); wbuf = imsg_create(&iev->ibuf, imsg_group_type, 0, 0, sizeof(igroup) + igroup.name_len); if (wbuf == NULL) { return got_error_from_errno( "imsg_create SYSCONF_GROUP"); } if (imsg_add(wbuf, &igroup, sizeof(igroup)) == -1) { return got_error_from_errno_fmt("imsg_add %d", imsg_group_type); } if (imsg_add(wbuf, g->name, igroup.name_len) == -1) { return got_error_from_errno_fmt("imsg_add %d", imsg_group_type); } imsg_close(&iev->ibuf, wbuf); err = gotsysd_imsg_flush(&iev->ibuf); if (err) return err; err = gotsys_imsg_send_users(iev, &g->members, imsg_group_members_type, imsg_group_members_done_type, 0); if (err) return err; g = STAILQ_NEXT(g, entry); } if (gotsysd_imsg_compose_event(iev, imsg_done_type, 0, -1, NULL, 0) == -1) { return got_error_from_errno_fmt("imsg_compose %d", imsg_done_type); } return NULL; } const struct got_error * gotsys_imsg_recv_group(struct imsg *imsg, struct gotsys_group **group) { const struct got_error *err = NULL; struct gotsysd_imsg_sysconf_group igroup; char *name = NULL; size_t datalen; *group = NULL; datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen < sizeof(igroup)) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&igroup, imsg->data, sizeof(igroup)); if (igroup.name_len <= 0 || igroup.name_len > _PW_NAME_LEN || sizeof(igroup) + igroup.name_len > datalen) return got_error(GOT_ERR_PRIVSEP_LEN); name = strndup(imsg->data + sizeof(igroup), igroup.name_len); if (name == NULL) return got_error_from_errno("strdup"); if (strlen(name) != igroup.name_len) { free(name); return got_error(GOT_ERR_PRIVSEP_LEN); } err = gotsys_conf_validate_name(name, "group"); if (err) { free(name); return err; } err = gotsys_conf_new_group(group, name); free(name); return err; } const struct got_error * gotsys_imsg_send_authorized_keys_user(struct gotsysd_imsgev *iev, const char *username, int imsg_type) { const struct got_error *err; struct gotsysd_imsg_sysconf_authorized_keys_user iuser; struct ibuf *wbuf = NULL; size_t userlen; err = gotsys_conf_validate_name(username, "user"); if (err) return err; userlen = strlen(username); iuser.name_len = strlen(username); wbuf = imsg_create(&iev->ibuf, imsg_type, 0, 0, sizeof(iuser) + userlen); if (wbuf == NULL) return got_error_from_errno_fmt("imsg_create %d", imsg_type); if (imsg_add(wbuf, &iuser, sizeof(iuser)) == -1) return got_error_from_errno_fmt("imsg_add %d", imsg_type); if (imsg_add(wbuf, username, userlen) == -1) return got_error_from_errno_fmt("imsg_add %d", imsg_type); imsg_close(&iev->ibuf, wbuf); gotsysd_imsg_event_add(iev); return NULL; } const struct got_error * gotsys_imsg_recv_authorized_keys_user(char **username, struct imsg *imsg) { const struct got_error *err; struct gotsysd_imsg_sysconf_authorized_keys_user iuser; size_t datalen; *username = NULL; datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen < sizeof(iuser)) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&iuser, imsg->data, sizeof(iuser)); if (iuser.name_len > _PW_NAME_LEN || datalen != sizeof(iuser) + iuser.name_len) return got_error(GOT_ERR_PRIVSEP_LEN); *username = strndup(imsg->data + sizeof(iuser), iuser.name_len); if (*username == NULL) return got_error_from_errno("strndup"); if (strlen(*username) != iuser.name_len) { err = got_error(GOT_ERR_PRIVSEP_LEN); goto done; } err = gotsys_conf_validate_name(*username, "user"); done: if (err) { free(*username); *username = NULL; } return err; } const struct got_error * gotsys_imsg_send_authorized_keys(struct gotsysd_imsgev *iev, struct gotsys_authorized_keys_list *keys, int imsg_type) { const struct got_error *err; struct gotsys_authorized_key *k; size_t totlen, remain, mlen; const size_t maxmesg = MAX_IMSGSIZE - IMSG_HEADER_SIZE; struct gotsysd_imsg_sysconf_authorized_key ikey; struct ibuf *wbuf = NULL; k = STAILQ_FIRST(keys); totlen = 0; while (k) { size_t typelen, datalen, commentlen, klen; typelen = strlen(k->keytype); if (typelen == 0) { return got_error_msg(GOT_ERR_AUTHORIZED_KEY, "empty authorized key type"); } if (typelen > GOTSYS_AUTHORIZED_KEY_MAXLEN) { return got_error_fmt(GOT_ERR_NO_SPACE, "authorized key type too long: %s:", k->keytype); } datalen = strlen(k->key); if (datalen == 0) { return got_error_msg(GOT_ERR_AUTHORIZED_KEY, "empty authorized key"); } if (datalen > GOTSYS_AUTHORIZED_KEY_MAXLEN || typelen + datalen > GOTSYS_AUTHORIZED_KEY_MAXLEN) { return got_error_fmt(GOT_ERR_NO_SPACE, "authorized key too long: %s:", k->key); } commentlen = strlen(k->comment); if (commentlen > GOTSYS_AUTHORIZED_KEY_MAXLEN) { return got_error_fmt(GOT_ERR_NO_SPACE, "authorized key comment too long: %s:", k->comment); } klen = typelen + datalen + commentlen; if (klen > GOTSYS_AUTHORIZED_KEY_MAXLEN) { return got_error_fmt(GOT_ERR_NO_SPACE, "authorized key too long: %s:", k->key); } totlen += sizeof(ikey) + klen; k = STAILQ_NEXT(k, entry); } k = STAILQ_FIRST(keys); remain = totlen; mlen = 0; while (k && remain > 0) { size_t klen; ikey.keytype_len = strlen(k->keytype); ikey.keydata_len = strlen(k->key); ikey.comment_len = strlen(k->comment); klen = ikey.keytype_len + ikey.keydata_len + ikey.comment_len; if (wbuf != NULL && mlen + sizeof(ikey) + klen > maxmesg) { imsg_close(&iev->ibuf, wbuf); err = gotsysd_imsg_flush(&iev->ibuf); if (err) return err; wbuf = NULL; mlen = 0; } if (wbuf == NULL) { wbuf = imsg_create(&iev->ibuf, imsg_type, 0, 0, MIN(remain, maxmesg)); if (wbuf == NULL) { return got_error_from_errno_fmt( "imsg_create %d", imsg_type); } } if (imsg_add(wbuf, &ikey, sizeof(ikey)) == -1) return got_error_from_errno_fmt("imsg_add %d", imsg_type); if (imsg_add(wbuf, k->keytype, ikey.keytype_len) == -1) return got_error_from_errno_fmt("imsg_add %d", imsg_type); if (imsg_add(wbuf, k->key, ikey.keydata_len) == -1) return got_error_from_errno_fmt("imsg_add %d", imsg_type); if (imsg_add(wbuf, k->comment, ikey.comment_len) == -1) return got_error_from_errno_fmt("imsg_add %d", imsg_type); remain -= sizeof(ikey) + klen; mlen += sizeof(ikey) + klen; k = STAILQ_NEXT(k, entry); } if (wbuf) { imsg_close(&iev->ibuf, wbuf); gotsysd_imsg_event_add(iev); } return NULL; } const struct got_error * gotsys_imsg_recv_authorized_keys(struct imsg *imsg, struct gotsys_authorized_keys_list *keys) { const struct got_error *err = NULL; struct gotsysd_imsg_sysconf_authorized_key ikey; struct gotsys_authorized_key *key = NULL; char *keytype = NULL, *keydata = NULL, *comment = NULL; size_t datalen, offset, remain; datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen < sizeof(ikey)) return got_error(GOT_ERR_PRIVSEP_LEN); remain = datalen; offset = 0; while (remain > 0) { size_t klen; if (remain < sizeof(ikey)) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&ikey, imsg->data + offset, sizeof(ikey)); if (ikey.keytype_len == 0 || ikey.keydata_len == 0 || ikey.keytype_len > GOTSYS_AUTHORIZED_KEY_MAXLEN || ikey.keydata_len > GOTSYS_AUTHORIZED_KEY_MAXLEN || ikey.comment_len > GOTSYS_AUTHORIZED_KEY_MAXLEN) return got_error(GOT_ERR_PRIVSEP_LEN); klen = ikey.keytype_len + ikey.keydata_len + ikey.comment_len; if (klen > GOTSYS_AUTHORIZED_KEY_MAXLEN || sizeof(ikey) + klen > remain) return got_error(GOT_ERR_PRIVSEP_LEN); keytype = strndup(imsg->data + offset + sizeof(ikey), ikey.keytype_len); if (keytype == NULL) return got_error_from_errno("strndup"); if (strlen(keytype) != ikey.keytype_len) { free(keytype); return got_error(GOT_ERR_PRIVSEP_LEN); } keydata = strndup(imsg->data + offset + sizeof(ikey) + ikey.keytype_len, ikey.keydata_len); if (keydata == NULL) { err = got_error_from_errno("strndup"); goto done; } if (strlen(keydata) != ikey.keydata_len) { err = got_error(GOT_ERR_PRIVSEP_LEN); goto done; } if (ikey.comment_len > 0) { comment = strndup(imsg->data + offset + sizeof(ikey) + ikey.keytype_len + ikey.keydata_len, ikey.comment_len); if (comment == NULL) { err = got_error_from_errno("strndup"); goto done; } if (strlen(comment) != ikey.comment_len) { err = got_error(GOT_ERR_PRIVSEP_LEN); goto done; } } err = gotsys_conf_new_authorized_key(&key, keytype, keydata, comment); if (err) goto done; free(keytype); free(keydata); free(comment); keytype = NULL; keydata = NULL; comment = NULL; STAILQ_INSERT_TAIL(keys, key, entry); key = NULL; offset += sizeof(ikey) + klen; remain -= sizeof(ikey) + klen; } done: free(keytype); free(keydata); free(comment); gotsys_authorized_key_free(key); return err; } static const struct got_error * send_access_rule(struct gotsysd_imsgev *iev, struct gotsys_access_rule *rule) { struct gotsysd_imsg_sysconf_access_rule irule; struct ibuf *wbuf = NULL; switch (rule->access) { case GOTSYS_ACCESS_DENIED: irule.access = GOTSYSD_IMSG_ACCESS_DENIED; break; case GOTSYS_ACCESS_PERMITTED: irule.access = GOTSYSD_IMSG_ACCESS_PERMITTED; break; default: return got_error_fmt(GOT_ERR_NOT_IMPL, "unknown access %d", rule->access); } irule.authorization = rule->authorization; irule.identifier_len = strlen(rule->identifier); wbuf = imsg_create(&iev->ibuf, GOTSYSD_IMSG_SYSCONF_ACCESS_RULE, 0, 0, sizeof(irule) + irule.identifier_len); if (wbuf == NULL) return got_error_from_errno("imsg_create SYSCONF_ACCESS_RULE"); if (imsg_add(wbuf, &irule, sizeof(irule)) == -1) return got_error_from_errno("imsg_add SYSCONF_ACCESS_FULE"); if (imsg_add(wbuf, rule->identifier, irule.identifier_len) == -1) return got_error_from_errno("imsg_add SYSCONF_ACCESS_FULE"); imsg_close(&iev->ibuf, wbuf); return gotsysd_imsg_flush(&iev->ibuf); } static const struct got_error * send_repo(struct gotsysd_imsgev *iev, struct gotsys_repo *repo) { const struct got_error *err; struct gotsysd_imsg_sysconf_repo irepo; struct gotsys_access_rule *rule; struct ibuf *wbuf = NULL; irepo.name_len = strlen(repo->name); wbuf = imsg_create(&iev->ibuf, GOTSYSD_IMSG_SYSCONF_REPO, 0, 0, sizeof(irepo) + irepo.name_len); if (wbuf == NULL) return got_error_from_errno("imsg_create SYSCONF_REPO"); if (imsg_add(wbuf, &irepo, sizeof(irepo)) == -1) return got_error_from_errno("imsg_add SYSCONF_REPO"); if (imsg_add(wbuf, repo->name, irepo.name_len) == -1) return got_error_from_errno("imsg_add SYSCONF_REPO"); imsg_close(&iev->ibuf, wbuf); err = gotsysd_imsg_flush(&iev->ibuf); if (err) return err; STAILQ_FOREACH(rule, &repo->access_rules, entry) { err = send_access_rule(iev, rule); if (err) return err; } if (gotsysd_imsg_compose_event(iev, GOTSYSD_IMSG_SYSCONF_ACCESS_RULES_DONE, 0, -1, NULL, 0) == -1) { return got_error_from_errno("gotsysd_imsg_compose_event"); } /* TODO: send protected tags and branches */ /* TODO: send notification config */ return NULL; } const struct got_error * gotsys_imsg_send_repositories(struct gotsysd_imsgev *iev, struct gotsys_repolist *repos) { const struct got_error *err = NULL; struct gotsys_repo *repo; TAILQ_FOREACH(repo, repos, entry) { err = send_repo(iev, repo); if (err) return err; } if (gotsysd_imsg_compose_event(iev, GOTSYSD_IMSG_SYSCONF_REPOS_DONE, 0, -1, NULL, 0) == -1) return got_error_from_errno("gotsysd_imsg_compose_event"); return NULL; } const struct got_error * gotsys_imsg_recv_repository(struct gotsys_repo **repo, struct imsg *imsg) { const struct got_error *err; struct gotsysd_imsg_sysconf_repo irepo; size_t datalen; char *name = NULL; *repo = NULL; datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen < sizeof(irepo)) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&irepo, imsg->data, sizeof(irepo)); if (datalen != sizeof(irepo) + irepo.name_len) return got_error(GOT_ERR_PRIVSEP_LEN); name = strndup(imsg->data + sizeof(irepo), irepo.name_len); if (name == NULL) return got_error_from_errno("strndup"); if (strlen(name) != irepo.name_len) { err = got_error(GOT_ERR_PRIVSEP_LEN); free(name); return err; } err = gotsys_conf_new_repo(repo, name); free(name); return err; } const struct got_error * gotsys_imsg_recv_access_rule(struct gotsys_access_rule **rule, struct imsg *imsg, struct gotsys_userlist *users, struct gotsys_grouplist *groups) { const struct got_error *err; struct gotsysd_imsg_sysconf_access_rule irule; enum gotsys_access access; size_t datalen; char *identifier = NULL; *rule = NULL; datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen < sizeof(irule)) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&irule, imsg->data, sizeof(irule)); if (datalen != sizeof(irule) + irule.identifier_len) return got_error(GOT_ERR_PRIVSEP_LEN); if (irule.identifier_len == 0) { return got_error_msg(GOT_ERR_PRIVSEP_LEN, "empty access rule identifier"); } if (irule.identifier_len > _PW_NAME_LEN) { return got_error_msg(GOT_ERR_PRIVSEP_LEN, "access rule identifier too long"); } switch (irule.access) { case GOTSYSD_IMSG_ACCESS_PERMITTED: if (irule.authorization == 0) { return got_error_msg(GOT_ERR_PRIVSEP_MSG, "permit access rule without read or write " "authorization"); } access = GOTSYS_ACCESS_PERMITTED; break; case GOTSYSD_IMSG_ACCESS_DENIED: if (irule.authorization != 0) { return got_error_msg(GOT_ERR_PRIVSEP_MSG, "deny access rule with read or write " "authorization"); } access = GOTSYS_ACCESS_DENIED; break; default: return got_error_msg(GOT_ERR_PRIVSEP_MSG, "invalid access rule"); } if (irule.authorization & ~(GOTSYS_AUTH_READ | GOTSYS_AUTH_WRITE)) { return got_error_msg(GOT_ERR_PRIVSEP_MSG, "invalid access rule authorization flags"); } identifier = strndup(imsg->data + sizeof(irule), irule.identifier_len); if (identifier == NULL) return got_error_from_errno("strndup"); if (strlen(identifier) != irule.identifier_len) { err = got_error(GOT_ERR_PRIVSEP_LEN); free(identifier); return err; } err = gotsys_conf_new_access_rule(rule, access, irule.authorization, identifier, users, groups); free(identifier); return err; } got-portable-0.111/lib/delta.c0000644000175000017500000002273615001741021011600 /* * Copyright (c) 2018 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "got_compat.h" #include "got_error.h" #include "got_repository.h" #include "got_object.h" #include "got_path.h" #include "got_lib_delta.h" #include "got_lib_inflate.h" #include "got_lib_object.h" #ifndef MIN #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b)) #endif struct got_delta * got_delta_open(off_t offset, size_t tslen, int type, size_t size, off_t data_offset) { struct got_delta *delta; delta = malloc(sizeof(*delta)); if (delta == NULL) return NULL; delta->type = type; delta->offset = offset; delta->tslen = tslen; delta->size = size; delta->data_offset = data_offset; return delta; } const struct got_error * got_delta_chain_get_base_type(int *type, struct got_delta_chain *deltas) { struct got_delta *delta; /* The first delta in the chain should represent the base object. */ delta = STAILQ_FIRST(&deltas->entries); if (delta->type == GOT_OBJ_TYPE_COMMIT || delta->type == GOT_OBJ_TYPE_TREE || delta->type == GOT_OBJ_TYPE_BLOB || delta->type == GOT_OBJ_TYPE_TAG) { *type = delta->type; return NULL; } return got_error(GOT_ERR_BAD_DELTA_CHAIN); } /* Fetch another (required) byte from the delta stream. */ static const struct got_error * next_delta_byte(const uint8_t **p, size_t *remain) { if (--(*remain) == 0) return got_error_msg(GOT_ERR_BAD_DELTA, "delta data truncated"); (*p)++; return NULL; } static const struct got_error * parse_size(uint64_t *size, const uint8_t **p, size_t *remain) { const struct got_error *err = NULL; int i = 0; *size = 0; do { /* We do not support size values which don't fit in 64 bit. */ if (i > 9) return got_error(GOT_ERR_NO_SPACE); if (i == 0) *size = ((**p) & GOT_DELTA_SIZE_VAL_MASK); else { size_t shift = GOT_DELTA_SIZE_SHIFT * i; *size |= (((**p) & GOT_DELTA_SIZE_VAL_MASK) << shift); } if (((**p) & GOT_DELTA_SIZE_MORE) == 0) break; i++; err = next_delta_byte(p, remain); } while (err == NULL); return err; } static const struct got_error * parse_opcode(off_t *offset, size_t *len, const uint8_t **p, size_t *remain) { const struct got_error *err = NULL; off_t o = 0; size_t l = 0; uint8_t opcode = **p; if (opcode & GOT_DELTA_COPY_OFF1) { err = next_delta_byte(p, remain); if (err) return err; o = (off_t)(**p); } if (opcode & GOT_DELTA_COPY_OFF2) { err = next_delta_byte(p, remain); if (err) return err; o |= ((off_t)(**p)) << 8; } if (opcode & GOT_DELTA_COPY_OFF3) { err = next_delta_byte(p, remain); if (err) return err; o |= ((off_t)(**p)) << 16; } if (opcode & GOT_DELTA_COPY_OFF4) { err = next_delta_byte(p, remain); if (err) return err; o |= ((off_t)(**p)) << 24; } if (opcode & GOT_DELTA_COPY_LEN1) { err = next_delta_byte(p, remain); if (err) return err; l = (off_t)(**p); } if (opcode & GOT_DELTA_COPY_LEN2) { err = next_delta_byte(p, remain); if (err) return err; l |= ((off_t)(**p)) << 8; } if (opcode & GOT_DELTA_COPY_LEN3) { err = next_delta_byte(p, remain); if (err) return err; l |= ((off_t)(**p)) << 16; } if (o == 0) o = GOT_DELTA_COPY_DEFAULT_OFF; if (l == 0) l = GOT_DELTA_COPY_DEFAULT_LEN; *offset = o; *len = l; return NULL; } static const struct got_error * copy_from_base(FILE *base_file, off_t offset, size_t size, FILE *outfile) { if (fseeko(base_file, offset, SEEK_SET) != 0) return got_error_from_errno("fseeko"); while (size > 0) { uint8_t data[2048]; size_t len = MIN(size, sizeof(data)); size_t n; n = fread(data, len, 1, base_file); if (n != 1) return got_ferror(base_file, GOT_ERR_IO); n = fwrite(data, len, 1, outfile); if (n != 1) return got_ferror(outfile, GOT_ERR_IO); size -= len; } return NULL; } static const struct got_error * copy_from_delta(const uint8_t **p, size_t *remain, size_t len, FILE *outfile) { size_t n; if (*remain < len) return got_error_msg(GOT_ERR_BAD_DELTA, "copy from beyond end of delta data"); n = fwrite(*p, len, 1, outfile); if (n != 1) return got_ferror(outfile, GOT_ERR_IO); *p += len; *remain -= len; return NULL; } static const struct got_error * parse_delta_sizes(uint64_t *base_size, uint64_t *result_size, const uint8_t **p, size_t *remain) { const struct got_error *err; /* Read the two size fields at the beginning of the stream. */ err = parse_size(base_size, p, remain); if (err) return err; err = next_delta_byte(p, remain); if (err) return err; err = parse_size(result_size, p, remain); if (err) return err; return NULL; } const struct got_error * got_delta_get_sizes(uint64_t *base_size, uint64_t *result_size, const uint8_t *delta_buf, size_t delta_len) { size_t remain; const uint8_t *p; if (delta_len < GOT_DELTA_STREAM_LENGTH_MIN) return got_error_msg(GOT_ERR_BAD_DELTA, "delta too small"); p = delta_buf; remain = delta_len; return parse_delta_sizes(base_size, result_size, &p, &remain); } const struct got_error * got_delta_apply_in_mem(uint8_t *base_buf, size_t base_bufsz, const uint8_t *delta_buf, size_t delta_len, uint8_t *outbuf, size_t *outsize, size_t maxoutsize) { const struct got_error *err = NULL; uint64_t base_size, result_size; size_t remain; const uint8_t *p; *outsize= 0; if (delta_len < GOT_DELTA_STREAM_LENGTH_MIN) return got_error_msg(GOT_ERR_BAD_DELTA, "delta too small"); p = delta_buf; remain = delta_len; err = parse_delta_sizes(&base_size, &result_size, &p, &remain); if (err) return err; /* Decode and execute copy instructions from the delta stream. */ err = next_delta_byte(&p, &remain); while (err == NULL && remain > 0) { if (*p & GOT_DELTA_BASE_COPY) { off_t offset = 0; size_t len = 0; err = parse_opcode(&offset, &len, &p, &remain); if (err) break; if (SIZE_MAX - offset < len || offset + len < 0 || base_bufsz < offset + len || *outsize + len > maxoutsize) return got_error_msg(GOT_ERR_BAD_DELTA, "bad delta copy length"); memcpy(outbuf + *outsize, base_buf + offset, len); if (err == NULL) { *outsize += len; if (remain > 0) { p++; remain--; } } } else { size_t len = (size_t)*p; if (len == 0) { err = got_error_msg(GOT_ERR_BAD_DELTA, "zero length delta"); break; } err = next_delta_byte(&p, &remain); if (err) break; if (remain < len || SIZE_MAX - *outsize < len || *outsize + len > maxoutsize) return got_error_msg(GOT_ERR_BAD_DELTA, "bad delta copy length"); memcpy(outbuf + *outsize, p, len); p += len; remain -= len; *outsize += len; } } if (*outsize != result_size) err = got_error_msg(GOT_ERR_BAD_DELTA, "delta application result size mismatch"); return err; } const struct got_error * got_delta_apply(FILE *base_file, const uint8_t *delta_buf, size_t delta_len, FILE *outfile, size_t *outsize) { const struct got_error *err = NULL; uint64_t base_size, result_size; size_t remain = 0; const uint8_t *p; FILE *memstream = NULL; char *memstream_buf = NULL; size_t memstream_size = 0; *outsize = 0; if (delta_len < GOT_DELTA_STREAM_LENGTH_MIN) return got_error_msg(GOT_ERR_BAD_DELTA, "delta too small"); p = delta_buf; remain = delta_len; err = parse_delta_sizes(&base_size, &result_size, &p, &remain); if (err) return err; if (result_size < GOT_DELTA_RESULT_SIZE_CACHED_MAX) memstream = open_memstream(&memstream_buf, &memstream_size); /* Decode and execute copy instructions from the delta stream. */ err = next_delta_byte(&p, &remain); while (err == NULL && remain > 0) { if (*p & GOT_DELTA_BASE_COPY) { off_t offset = 0; size_t len = 0; err = parse_opcode(&offset, &len, &p, &remain); if (err) break; err = copy_from_base(base_file, offset, len, memstream ? memstream : outfile); if (err == NULL) { *outsize += len; if (remain > 0) { p++; remain--; } } } else { size_t len = (size_t)*p; if (len == 0) { err = got_error_msg(GOT_ERR_BAD_DELTA, "zero length delta"); break; } err = next_delta_byte(&p, &remain); if (err) break; err = copy_from_delta(&p, &remain, len, memstream ? memstream : outfile); if (err == NULL) *outsize += len; } } if (*outsize != result_size) err = got_error_msg(GOT_ERR_BAD_DELTA, "delta application result size mismatch"); if (memstream != NULL) { if (fclose(memstream) == EOF) err = got_error_from_errno("fclose"); if (err == NULL) { size_t n; n = fwrite(memstream_buf, 1, memstream_size, outfile); if (n != memstream_size) err = got_ferror(outfile, GOT_ERR_IO); } free(memstream_buf); } if (err == NULL) rewind(outfile); return err; } got-portable-0.111/lib/got_lib_pack_index.h0000644000175000017500000000245215001740614014320 /* * Copyright (c) 2022 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ typedef const struct got_error *(got_pack_index_progress_cb)(void *, uint32_t nobj_total, uint32_t nobj_indexed, uint32_t nobj_loose, uint32_t nobj_resolved); const struct got_error *got_pack_hwrite(int, void *, int, struct got_hash *); const struct got_error * got_pack_index(struct got_pack *pack, int idxfd, FILE *tmpfile, FILE *delta_base_file, FILE *delta_accum_file, struct got_object_id *pack_hash_expected, got_pack_index_progress_cb progress_cb, void *progress_arg, struct got_ratelimit *rl); got-portable-0.111/lib/reference_parse.c0000644000175000017500000000416715001740614013644 /* * Copyright (c) 2018, 2019 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "got_reference.h" #include "got_lib_lockfile.h" #ifndef nitems #define nitems(_a) (sizeof(_a) / sizeof((_a)[0])) #endif int got_ref_name_is_valid(const char *name) { const char *s, *seg; const char forbidden[] = { ' ', '~', '^', ':', '?', '*', '[' , '\\' }; const char *forbidden_seq[] = { "//", "..", "@{" }; const char *lfs = GOT_LOCKFILE_SUFFIX; const size_t lfs_len = sizeof(GOT_LOCKFILE_SUFFIX) - 1; size_t i; if (name[0] == '@' && name[1] == '\0') return 0; s = name; seg = s; if (seg[0] == '\0' || seg[0] == '.' || seg[0] == '/') return 0; while (*s) { for (i = 0; i < nitems(forbidden); i++) { if (*s == forbidden[i]) return 0; } for (i = 0; i < nitems(forbidden_seq); i++) { if (s[0] == forbidden_seq[i][0] && s[1] == forbidden_seq[i][1]) return 0; } if (iscntrl((unsigned char)s[0])) return 0; if (s[0] == '.' && s[1] == '\0') return 0; if (*s == '/') { const char *nextseg = s + 1; if (nextseg[0] == '\0' || nextseg[0] == '.' || nextseg[0] == '/') return 0; if (seg <= s - lfs_len && strncmp(s - lfs_len, lfs, lfs_len) == 0) return 0; seg = nextseg; } s++; } if (seg <= s - lfs_len && strncmp(s - lfs_len, lfs, lfs_len) == 0) return 0; return 1; } got-portable-0.111/lib/gitproto.c0000644000175000017500000002351415001741021012351 /* * Copyright (c) 2019 Ori Bernstein * Copyright (c) 2021 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include "got_error.h" #include "got_path.h" #include "got_lib_gitproto.h" #ifndef nitems #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) #endif static void free_tokens(char **tokens, size_t ntokens) { int i; for (i = 0; i < ntokens; i++) { free(tokens[i]); tokens[i] = NULL; } } static const struct got_error * tokenize_line(char **tokens, char *line, int len, int mintokens, int maxtokens) { const struct got_error *err = NULL; char *p; size_t i, n = 0; for (i = 0; i < maxtokens; i++) tokens[i] = NULL; for (i = 0; n < len && i < maxtokens; i++) { while (n < len && isspace((unsigned char)*line)) { line++; n++; } p = line; while (*line != '\0' && n < len && (!isspace((unsigned char)*line) || i == maxtokens - 1)) { line++; n++; } tokens[i] = strndup(p, line - p); if (tokens[i] == NULL) { err = got_error_from_errno("strndup"); goto done; } /* Skip \0 field-delimiter at end of token. */ while (line[0] == '\0' && n < len) { line++; n++; } } if (i < mintokens) err = got_error_msg(GOT_ERR_BAD_PACKET, "pkt-line contains too few tokens"); done: if (err) free_tokens(tokens, i); return err; } const struct got_error * got_gitproto_parse_refline(char **id_str, char **refname, char **server_capabilities, char *line, int len) { const struct got_error *err = NULL; char *tokens[3]; *id_str = NULL; *refname = NULL; /* don't reset *server_capabilities */ err = tokenize_line(tokens, line, len, 2, nitems(tokens)); if (err) return err; if (tokens[0]) *id_str = tokens[0]; if (tokens[1]) *refname = tokens[1]; if (tokens[2]) { if (*server_capabilities == NULL) { char *p; *server_capabilities = tokens[2]; p = strrchr(*server_capabilities, '\n'); if (p) *p = '\0'; } else free(tokens[2]); } return NULL; } const struct got_error * got_gitproto_parse_want_line(char **id_str, char **capabilities, char *line, int len) { const struct got_error *err = NULL; char *tokens[3]; *id_str = NULL; /* don't reset *capabilities */ err = tokenize_line(tokens, line, len, 2, nitems(tokens)); if (err) return err; if (tokens[0] == NULL) { free_tokens(tokens, nitems(tokens)); return got_error_msg(GOT_ERR_BAD_PACKET, "empty want-line"); } if (strcmp(tokens[0], "want") != 0) { free_tokens(tokens, nitems(tokens)); return got_error_msg(GOT_ERR_BAD_PACKET, "bad want-line"); } free(tokens[0]); if (tokens[1]) *id_str = tokens[1]; if (tokens[2]) { if (*capabilities == NULL) { char *p; *capabilities = tokens[2]; p = strrchr(*capabilities, '\n'); if (p) *p = '\0'; } else free(tokens[2]); } return NULL; } const struct got_error * got_gitproto_parse_have_line(char **id_str, char *line, int len) { const struct got_error *err = NULL; char *tokens[2]; *id_str = NULL; err = tokenize_line(tokens, line, len, 2, nitems(tokens)); if (err) return err; if (tokens[0] == NULL) { free_tokens(tokens, nitems(tokens)); return got_error_msg(GOT_ERR_BAD_PACKET, "empty have-line"); } if (strcmp(tokens[0], "have") != 0) { free_tokens(tokens, nitems(tokens)); return got_error_msg(GOT_ERR_BAD_PACKET, "bad have-line"); } free(tokens[0]); if (tokens[1]) *id_str = tokens[1]; return NULL; } const struct got_error * got_gitproto_parse_ref_update_line(char **old_id_str, char **new_id_str, char **refname, char **capabilities, char *line, size_t len) { const struct got_error *err = NULL; char *tokens[4]; *old_id_str = NULL; *new_id_str = NULL; *refname = NULL; /* don't reset *capabilities */ err = tokenize_line(tokens, line, len, 3, nitems(tokens)); if (err) return err; if (tokens[0] == NULL || tokens[1] == NULL || tokens[2] == NULL) { free_tokens(tokens, nitems(tokens)); return got_error_msg(GOT_ERR_BAD_PACKET, "empty ref-update"); } *old_id_str = tokens[0]; *new_id_str = tokens[1]; *refname = tokens[2]; if (tokens[3]) { if (*capabilities == NULL) { char *p; *capabilities = tokens[3]; p = strrchr(*capabilities, '\n'); if (p) *p = '\0'; } else free(tokens[3]); } return NULL; } static const struct got_error * match_capability(char **my_capabilities, const char *capa, const struct got_capability *mycapa) { char *equalsign; char *s; equalsign = strchr(capa, '='); if (equalsign) { if (strncmp(capa, mycapa->key, equalsign - capa) != 0) return NULL; } else { if (strcmp(capa, mycapa->key) != 0) return NULL; } if (asprintf(&s, "%s %s%s%s", *my_capabilities != NULL ? *my_capabilities : "", mycapa->key, mycapa->value != NULL ? "=" : "", mycapa->value != NULL ? mycapa->value : "") == -1) return got_error_from_errno("asprintf"); free(*my_capabilities); *my_capabilities = s; return NULL; } static const struct got_error * add_symref(struct got_pathlist_head *symrefs, char *capa) { const struct got_error *err = NULL; char *colon, *name = NULL, *target = NULL; struct got_pathlist_entry *new; /* Need at least "A:B" */ if (strlen(capa) < 3) return NULL; colon = strchr(capa, ':'); if (colon == NULL) return NULL; *colon = '\0'; name = strdup(capa); if (name == NULL) return got_error_from_errno("strdup"); target = strdup(colon + 1); if (target == NULL) { err = got_error_from_errno("strdup"); goto done; } /* We can't validate the ref itself here. The main process will. */ err = got_pathlist_insert(&new, symrefs, name, target); if (err == NULL && new == NULL) err = got_error(GOT_ERR_REF_DUP_ENTRY); done: if (err) { free(name); free(target); } return err; } const struct got_error * got_gitproto_match_capabilities(char **common_capabilities, struct got_pathlist_head *symrefs, char *capabilities, const struct got_capability my_capabilities[], size_t ncapa) { const struct got_error *err = NULL; char *capa, *equalsign; size_t i; *common_capabilities = NULL; do { capa = strsep(&capabilities, " "); if (capa == NULL) return NULL; equalsign = strchr(capa, '='); if (equalsign != NULL && symrefs != NULL && strncmp(capa, "symref", equalsign - capa) == 0) { err = add_symref(symrefs, equalsign + 1); if (err && err->code != GOT_ERR_REF_DUP_ENTRY) break; continue; } for (i = 0; i < ncapa; i++) { err = match_capability(common_capabilities, capa, &my_capabilities[i]); if (err) break; } } while (capa); if (*common_capabilities == NULL) { *common_capabilities = strdup(""); if (*common_capabilities == NULL) err = got_error_from_errno("strdup"); } return err; } const struct got_error * got_gitproto_append_capabilities(size_t *capalen, char *buf, size_t offset, size_t bufsize, const struct got_capability my_capabilities[], size_t ncapa) { char *p = buf + offset; size_t i, len, remain = bufsize - offset; *capalen = 0; if (offset >= bufsize || remain < 1) return got_error(GOT_ERR_NO_SPACE); /* Capabilities are hidden behind a NUL byte. */ *p = '\0'; p++; remain--; *capalen += 1; for (i = 0; i < ncapa; i++) { len = strlcat(p, " ", remain); if (len >= remain) return got_error(GOT_ERR_NO_SPACE); remain -= len; *capalen += 1; len = strlcat(p, my_capabilities[i].key, remain); if (len >= remain) return got_error(GOT_ERR_NO_SPACE); remain -= len; *capalen += strlen(my_capabilities[i].key); if (my_capabilities[i].value == NULL) continue; len = strlcat(p, "=", remain); if (len >= remain) return got_error(GOT_ERR_NO_SPACE); remain -= len; *capalen += 1; len = strlcat(p, my_capabilities[i].value, remain); if (len >= remain) return got_error(GOT_ERR_NO_SPACE); remain -= len; *capalen += strlen(my_capabilities[i].value); } return NULL; } const struct got_error * got_gitproto_split_capabilities_str(struct got_capability **capabilities, size_t *ncapabilities, char *capabilities_str) { char *capastr, *capa; size_t i; *capabilities = NULL; *ncapabilities = 0; /* Compute number of capabilities on a copy of the input string. */ capastr = strdup(capabilities_str); if (capastr == NULL) return got_error_from_errno("strdup"); do { capa = strsep(&capastr, " "); if (capa && *capa != '\0') (*ncapabilities)++; } while (capa); free(capastr); *capabilities = calloc(*ncapabilities, sizeof(**capabilities)); if (*capabilities == NULL) return got_error_from_errno("calloc"); /* Modify input string in place, splitting it into key/value tuples. */ i = 0; for (;;) { char *key = NULL, *value = NULL, *equalsign; capa = strsep(&capabilities_str, " "); if (capa == NULL) break; if (*capa == '\0') continue; if (i >= *ncapabilities) { /* should not happen */ free(*capabilities); *capabilities = NULL; *ncapabilities = 0; return got_error(GOT_ERR_NO_SPACE); } key = capa; equalsign = strchr(capa, '='); if (equalsign != NULL) { *equalsign = '\0'; value = equalsign + 1; } (*capabilities)[i].key = key; (*capabilities)[i].value = value; i++; } return NULL; } got-portable-0.111/lib/hash.c0000644000175000017500000001267115001741021011427 /* * Copyright (c) 2018 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include "got_object.h" #include "got_error.h" #include "got_lib_hash.h" struct got_object_id * got_object_id_dup(struct got_object_id *id1) { struct got_object_id *id2; id2 = malloc(sizeof(*id2)); if (id2 == NULL) return NULL; memcpy(id2, id1, sizeof(*id2)); return id2; } int got_object_id_cmp(const struct got_object_id *id1, const struct got_object_id *id2) { if (id1->algo != id2->algo) abort(); return memcmp(id1->hash, id2->hash, got_hash_digest_length(id1->algo)); } const struct got_error * got_object_id_str(char **outbuf, struct got_object_id *id) { static const size_t len = GOT_OBJECT_ID_HEX_MAXLEN; *outbuf = malloc(len); if (*outbuf == NULL) return got_error_from_errno("malloc"); if (got_object_id_hex(id, *outbuf, len) == NULL) { free(*outbuf); *outbuf = NULL; return got_error(GOT_ERR_BAD_OBJ_ID_STR); } return NULL; } int got_parse_xdigit(uint8_t *val, const char *hex) { char *ep; long lval; errno = 0; lval = strtol(hex, &ep, 16); if (hex[0] == '\0' || *ep != '\0') return 0; if (errno == ERANGE && (lval == LONG_MAX || lval == LONG_MIN)) return 0; *val = (uint8_t)lval; return 1; } static int parse_digest(uint8_t *digest, int len, const char *line) { uint8_t b = 0; char hex[3] = {'\0', '\0', '\0'}; int i, j; for (i = 0; i < len; i++) { if (line[0] == '\0' || line[1] == '\0') return 0; for (j = 0; j < 2; j++) { hex[j] = *line; line++; } if (!got_parse_xdigit(&b, hex)) return 0; digest[i] = b; } return 1; } static char * digest_to_str(const uint8_t *digest, int len, char *buf) { const char hex[] = "0123456789abcdef"; char *p = buf; int i; for (i = 0; i < len; i++) { *p++ = hex[digest[i] >> 4]; *p++ = hex[digest[i] & 0xf]; } *p = '\0'; return buf; } char * got_sha1_digest_to_str(const uint8_t *digest, char *buf, size_t size) { if (size < SHA1_DIGEST_STRING_LENGTH) return NULL; return digest_to_str(digest, SHA1_DIGEST_LENGTH, buf); } char * got_sha256_digest_to_str(const uint8_t *digest, char *buf, size_t size) { if (size < SHA256_DIGEST_STRING_LENGTH) return NULL; return digest_to_str(digest, SHA256_DIGEST_LENGTH, buf); } char * got_hash_digest_to_str(const uint8_t *digest, char *buf, size_t size, enum got_hash_algorithm algo) { switch (algo) { case GOT_HASH_SHA1: return got_sha1_digest_to_str(digest, buf, size); case GOT_HASH_SHA256: return got_sha256_digest_to_str(digest, buf, size); default: abort(); return NULL; } } int got_parse_hash_digest(uint8_t *digest, const char *line, enum got_hash_algorithm algo) { switch (algo) { case GOT_HASH_SHA1: return parse_digest(digest, SHA1_DIGEST_LENGTH, line); case GOT_HASH_SHA256: return parse_digest(digest, SHA256_DIGEST_LENGTH, line); default: return 0; } } char * got_object_id_hex(struct got_object_id *id, char *buf, size_t len) { if (id->algo == GOT_HASH_SHA1) return got_sha1_digest_to_str(id->hash, buf, len); if (id->algo == GOT_HASH_SHA256) return got_sha256_digest_to_str(id->hash, buf, len); abort(); } int got_parse_object_id(struct got_object_id *id, const char *line, enum got_hash_algorithm algo) { memset(id, 0, sizeof(*id)); id->algo = algo; return got_parse_hash_digest(id->hash, line, algo); } void got_hash_init(struct got_hash *hash, enum got_hash_algorithm algo) { memset(hash, 0, sizeof(*hash)); hash->algo = algo; if (algo == GOT_HASH_SHA1) SHA1Init(&hash->sha1_ctx); else if (algo == GOT_HASH_SHA256) SHA256Init(&hash->sha256_ctx); else abort(); } void got_hash_update(struct got_hash *hash, const void *data, size_t len) { if (hash->algo == GOT_HASH_SHA1) SHA1Update(&hash->sha1_ctx, data, len); else if (hash->algo == GOT_HASH_SHA256) SHA256Update(&hash->sha256_ctx, data, len); else abort(); } void got_hash_final(struct got_hash *hash, uint8_t *out) { if (hash->algo == GOT_HASH_SHA1) SHA1Final(out, &hash->sha1_ctx); else if (hash->algo == GOT_HASH_SHA256) SHA256Final(out, &hash->sha256_ctx); else abort(); } void got_hash_final_object_id(struct got_hash *hash, struct got_object_id *id) { memset(id, 0, sizeof(*id)); id->algo = hash->algo; if (hash->algo == GOT_HASH_SHA1) SHA1Final(id->hash, &hash->sha1_ctx); else if (hash->algo == GOT_HASH_SHA256) SHA256Final(id->hash, &hash->sha256_ctx); else abort(); } int got_hash_cmp(enum got_hash_algorithm algo, uint8_t *b1, uint8_t *b2) { if (algo == GOT_HASH_SHA1) return memcmp(b1, b2, SHA1_DIGEST_LENGTH); else if (algo == GOT_HASH_SHA256) return memcmp(b1, b2, SHA256_DIGEST_LENGTH); else abort(); return -1; } got-portable-0.111/lib/got_lib_object_idset.h0000644000175000017500000000310015001740614014640 /* * Copyright (c) 2018 Stefan Sperling * Copyright (c) 2019 Ori Bernstein * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ struct got_object_idset; struct got_object_idset *got_object_idset_alloc(void); void got_object_idset_free(struct got_object_idset *); const struct got_error *got_object_idset_add(struct got_object_idset *, struct got_object_id *, void *); void *got_object_idset_get(struct got_object_idset *, struct got_object_id *); const struct got_error *got_object_idset_remove(void **, struct got_object_idset *, struct got_object_id *); int got_object_idset_contains(struct got_object_idset *, struct got_object_id *); const struct got_error *got_object_idset_for_each(struct got_object_idset *, const struct got_error *(*cb)(struct got_object_id *, void *, void *), void *); int got_object_idset_num_elements(struct got_object_idset *); got-portable-0.111/lib/worktree.c0000644000175000017500000103540615001741021012350 /* * Copyright (c) 2018, 2019, 2020 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_repository.h" #include "got_reference.h" #include "got_object.h" #include "got_path.h" #include "got_cancel.h" #include "got_worktree.h" #include "got_opentemp.h" #include "got_diff.h" #include "got_lib_worktree.h" #include "got_lib_hash.h" #include "got_lib_fileindex.h" #include "got_lib_inflate.h" #include "got_lib_delta.h" #include "got_lib_object.h" #include "got_lib_object_parse.h" #include "got_lib_object_create.h" #include "got_lib_object_idset.h" #include "got_lib_diff.h" #include "got_lib_gotconfig.h" #ifndef MIN #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b)) #endif #define GOT_MERGE_LABEL_MERGED "merged change" #define GOT_MERGE_LABEL_BASE "3-way merge base" static mode_t apply_umask(mode_t mode) { mode_t um; um = umask(000); umask(um); return mode & ~um; } static const struct got_error * create_meta_file(const char *path_got, const char *name, const char *content) { const struct got_error *err = NULL; char *path; if (asprintf(&path, "%s/%s", path_got, name) == -1) return got_error_from_errno("asprintf"); err = got_path_create_file(path, content); free(path); return err; } static const struct got_error * update_meta_file(const char *path_got, const char *name, const char *content) { const struct got_error *err = NULL; FILE *tmpfile = NULL; char *tmppath = NULL; char *path = NULL; if (asprintf(&path, "%s/%s", path_got, name) == -1) { err = got_error_from_errno("asprintf"); path = NULL; goto done; } err = got_opentemp_named(&tmppath, &tmpfile, path, ""); if (err) goto done; if (content) { int len = fprintf(tmpfile, "%s\n", content); if (len != strlen(content) + 1) { err = got_error_from_errno2("fprintf", tmppath); goto done; } } if (rename(tmppath, path) != 0) { err = got_error_from_errno3("rename", tmppath, path); unlink(tmppath); goto done; } done: if (fclose(tmpfile) == EOF && err == NULL) err = got_error_from_errno2("fclose", tmppath); free(tmppath); return err; } static const struct got_error * write_head_ref(const char *path_got, struct got_reference *head_ref) { const struct got_error *err = NULL; char *refstr = NULL; if (got_ref_is_symbolic(head_ref)) { refstr = got_ref_to_str(head_ref); if (refstr == NULL) return got_error_from_errno("got_ref_to_str"); } else { refstr = strdup(got_ref_get_name(head_ref)); if (refstr == NULL) return got_error_from_errno("strdup"); } err = update_meta_file(path_got, GOT_WORKTREE_HEAD_REF, refstr); free(refstr); return err; } const struct got_error * got_worktree_init(const char *path, struct got_reference *head_ref, const char *prefix, const char *meta_dir, struct got_repository *repo) { const struct got_error *err = NULL; struct got_object_id *commit_id = NULL; uuid_t uuid; uint32_t uuid_status; int obj_type; char *path_got = NULL; char *formatstr = NULL; char *absprefix = NULL; char *basestr = NULL; char *uuidstr = NULL; if (strcmp(path, got_repo_get_path(repo)) == 0) { err = got_error(GOT_ERR_WORKTREE_REPO); goto done; } err = got_ref_resolve(&commit_id, repo, head_ref); if (err) return err; err = got_object_get_type(&obj_type, repo, commit_id); if (err) return err; if (obj_type != GOT_OBJ_TYPE_COMMIT) return got_error(GOT_ERR_OBJ_TYPE); if (!got_path_is_absolute(prefix)) { if (asprintf(&absprefix, "/%s", prefix) == -1) return got_error_from_errno("asprintf"); } /* Create top-level directory (may already exist). */ if (mkdir(path, GOT_DEFAULT_DIR_MODE) == -1 && errno != EEXIST) { err = got_error_from_errno2("mkdir", path); goto done; } /* Create .got/.cvg directory (may already exist). */ if (asprintf(&path_got, "%s/%s", path, meta_dir) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (mkdir(path_got, GOT_DEFAULT_DIR_MODE) == -1 && errno != EEXIST) { err = got_error_from_errno2("mkdir", path_got); goto done; } /* Create an empty lock file. */ err = create_meta_file(path_got, GOT_WORKTREE_LOCK, NULL); if (err) goto done; /* Create an empty file index. */ err = create_meta_file(path_got, GOT_WORKTREE_FILE_INDEX, NULL); if (err) goto done; /* Write the HEAD reference. */ err = write_head_ref(path_got, head_ref); if (err) goto done; /* Record our base commit. */ err = got_object_id_str(&basestr, commit_id); if (err) goto done; err = create_meta_file(path_got, GOT_WORKTREE_BASE_COMMIT, basestr); if (err) goto done; /* Store path to repository. */ err = create_meta_file(path_got, GOT_WORKTREE_REPOSITORY, got_repo_get_path(repo)); if (err) goto done; /* Store in-repository path prefix. */ err = create_meta_file(path_got, GOT_WORKTREE_PATH_PREFIX, absprefix ? absprefix : prefix); if (err) goto done; /* Generate UUID. */ uuid_create(&uuid, &uuid_status); if (uuid_status != uuid_s_ok) { err = got_error_uuid(uuid_status, "uuid_create"); goto done; } uuid_to_string(&uuid, &uuidstr, &uuid_status); if (uuid_status != uuid_s_ok) { err = got_error_uuid(uuid_status, "uuid_to_string"); goto done; } err = create_meta_file(path_got, GOT_WORKTREE_UUID, uuidstr); if (err) goto done; /* Stamp work tree with format file. */ if (asprintf(&formatstr, "%d", GOT_WORKTREE_FORMAT_VERSION) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = create_meta_file(path_got, GOT_WORKTREE_FORMAT, formatstr); if (err) goto done; done: free(commit_id); free(path_got); free(formatstr); free(absprefix); free(basestr); free(uuidstr); return err; } const struct got_error * got_worktree_match_path_prefix(int *match, struct got_worktree *worktree, const char *path_prefix) { char *absprefix = NULL; if (!got_path_is_absolute(path_prefix)) { if (asprintf(&absprefix, "/%s", path_prefix) == -1) return got_error_from_errno("asprintf"); } *match = (strcmp(absprefix ? absprefix : path_prefix, worktree->path_prefix) == 0); free(absprefix); return NULL; } const char * got_worktree_get_head_ref_name(struct got_worktree *worktree) { return worktree->head_ref_name; } const struct got_error * got_worktree_set_head_ref(struct got_worktree *worktree, struct got_reference *head_ref) { const struct got_error *err = NULL; char *path_got = NULL, *head_ref_name = NULL; if (asprintf(&path_got, "%s/%s", worktree->root_path, worktree->meta_dir) == -1) { err = got_error_from_errno("asprintf"); path_got = NULL; goto done; } head_ref_name = strdup(got_ref_get_name(head_ref)); if (head_ref_name == NULL) { err = got_error_from_errno("strdup"); goto done; } err = write_head_ref(path_got, head_ref); if (err) goto done; free(worktree->head_ref_name); worktree->head_ref_name = head_ref_name; done: free(path_got); if (err) free(head_ref_name); return err; } struct got_object_id * got_worktree_get_base_commit_id(struct got_worktree *worktree) { return worktree->base_commit_id; } const struct got_error * got_worktree_set_base_commit_id(struct got_worktree *worktree, struct got_repository *repo, struct got_object_id *commit_id) { const struct got_error *err; struct got_object *obj = NULL; char *id_str = NULL; char *path_got = NULL; if (asprintf(&path_got, "%s/%s", worktree->root_path, worktree->meta_dir) == -1) { err = got_error_from_errno("asprintf"); path_got = NULL; goto done; } err = got_object_open(&obj, repo, commit_id); if (err) return err; if (obj->type != GOT_OBJ_TYPE_COMMIT) { err = got_error(GOT_ERR_OBJ_TYPE); goto done; } /* Record our base commit. */ err = got_object_id_str(&id_str, commit_id); if (err) goto done; err = update_meta_file(path_got, GOT_WORKTREE_BASE_COMMIT, id_str); if (err) goto done; free(worktree->base_commit_id); worktree->base_commit_id = got_object_id_dup(commit_id); if (worktree->base_commit_id == NULL) { err = got_error_from_errno("got_object_id_dup"); goto done; } done: if (obj) got_object_close(obj); free(id_str); free(path_got); return err; } const struct got_gotconfig * got_worktree_get_gotconfig(struct got_worktree *worktree) { return worktree->gotconfig; } static const struct got_error * lock_worktree(struct got_worktree *worktree, int operation) { if (flock(worktree->lockfd, operation | LOCK_NB) == -1) return (errno == EWOULDBLOCK ? got_error(GOT_ERR_WORKTREE_BUSY) : got_error_from_errno2("flock", got_worktree_get_root_path(worktree))); return NULL; } static const struct got_error * add_dir_on_disk(struct got_worktree *worktree, const char *path) { const struct got_error *err = NULL; char *abspath; /* We only accept worktree-relative paths here. */ if (got_path_is_absolute(path)) { return got_error_fmt(GOT_ERR_BAD_PATH, "%s does not accept absolute paths: %s", __func__, path); } if (asprintf(&abspath, "%s/%s", worktree->root_path, path) == -1) return got_error_from_errno("asprintf"); err = got_path_mkdir(abspath); if (err && err->code == GOT_ERR_ERRNO && errno == EEXIST) { struct stat sb; err = NULL; if (lstat(abspath, &sb) == -1) { err = got_error_from_errno2("lstat", abspath); } else if (!S_ISDIR(sb.st_mode)) { /* TODO directory is obstructed; do something */ err = got_error_path(abspath, GOT_ERR_FILE_OBSTRUCTED); } } free(abspath); return err; } static const struct got_error * check_file_contents_equal(int *same, FILE *f1, FILE *f2) { const struct got_error *err = NULL; uint8_t fbuf1[8192]; uint8_t fbuf2[8192]; size_t flen1 = 0, flen2 = 0; *same = 1; for (;;) { flen1 = fread(fbuf1, 1, sizeof(fbuf1), f1); if (flen1 == 0 && ferror(f1)) { err = got_error_from_errno("fread"); break; } flen2 = fread(fbuf2, 1, sizeof(fbuf2), f2); if (flen2 == 0 && ferror(f2)) { err = got_error_from_errno("fread"); break; } if (flen1 == 0) { if (flen2 != 0) *same = 0; break; } else if (flen2 == 0) { if (flen1 != 0) *same = 0; break; } else if (flen1 == flen2) { if (memcmp(fbuf1, fbuf2, flen2) != 0) { *same = 0; break; } } else { *same = 0; break; } } return err; } static const struct got_error * check_files_equal(int *same, FILE *f1, FILE *f2) { struct stat sb; size_t size1, size2; *same = 1; if (fstat(fileno(f1), &sb) != 0) return got_error_from_errno("fstat"); size1 = sb.st_size; if (fstat(fileno(f2), &sb) != 0) return got_error_from_errno("fstat"); size2 = sb.st_size; if (size1 != size2) { *same = 0; return NULL; } if (fseek(f1, 0L, SEEK_SET) == -1) return got_ferror(f1, GOT_ERR_IO); if (fseek(f2, 0L, SEEK_SET) == -1) return got_ferror(f2, GOT_ERR_IO); return check_file_contents_equal(same, f1, f2); } static const struct got_error * copy_file_to_fd(off_t *outsize, FILE *f, int outfd) { uint8_t fbuf[65536]; size_t flen; ssize_t outlen; *outsize = 0; if (fseek(f, 0L, SEEK_SET) == -1) return got_ferror(f, GOT_ERR_IO); for (;;) { flen = fread(fbuf, 1, sizeof(fbuf), f); if (flen == 0) { if (ferror(f)) return got_error_from_errno("fread"); if (feof(f)) break; } outlen = write(outfd, fbuf, flen); if (outlen == -1) return got_error_from_errno("write"); if (outlen != flen) return got_error(GOT_ERR_IO); *outsize += outlen; } return NULL; } static const struct got_error * merge_binary_file(int *overlapcnt, int merged_fd, FILE *f_deriv, FILE *f_orig, FILE *f_deriv2, const char *label_deriv, const char *label_orig, const char *label_deriv2, const char *ondisk_path) { const struct got_error *err = NULL; int same_content, changed_deriv, changed_deriv2; int fd_orig = -1, fd_deriv = -1, fd_deriv2 = -1; off_t size_orig = 0, size_deriv = 0, size_deriv2 = 0; char *path_orig = NULL, *path_deriv = NULL, *path_deriv2 = NULL; char *base_path_orig = NULL, *base_path_deriv = NULL; char *base_path_deriv2 = NULL; *overlapcnt = 0; err = check_files_equal(&same_content, f_deriv, f_deriv2); if (err) return err; if (same_content) return copy_file_to_fd(&size_deriv, f_deriv, merged_fd); err = check_files_equal(&same_content, f_deriv, f_orig); if (err) return err; changed_deriv = !same_content; err = check_files_equal(&same_content, f_deriv2, f_orig); if (err) return err; changed_deriv2 = !same_content; if (changed_deriv && changed_deriv2) { *overlapcnt = 1; if (asprintf(&base_path_orig, "%s-orig", ondisk_path) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (asprintf(&base_path_deriv, "%s-1", ondisk_path) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (asprintf(&base_path_deriv2, "%s-2", ondisk_path) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = got_opentemp_named_fd(&path_orig, &fd_orig, base_path_orig, ""); if (err) goto done; err = got_opentemp_named_fd(&path_deriv, &fd_deriv, base_path_deriv, ""); if (err) goto done; err = got_opentemp_named_fd(&path_deriv2, &fd_deriv2, base_path_deriv2, ""); if (err) goto done; err = copy_file_to_fd(&size_orig, f_orig, fd_orig); if (err) goto done; err = copy_file_to_fd(&size_deriv, f_deriv, fd_deriv); if (err) goto done; err = copy_file_to_fd(&size_deriv2, f_deriv2, fd_deriv2); if (err) goto done; if (dprintf(merged_fd, "Binary files differ and cannot be " "merged automatically:\n") < 0) { err = got_error_from_errno("dprintf"); goto done; } if (dprintf(merged_fd, "%s%s%s\nfile %s\n", GOT_DIFF_CONFLICT_MARKER_BEGIN, label_deriv ? " " : "", label_deriv ? label_deriv : "", path_deriv) < 0) { err = got_error_from_errno("dprintf"); goto done; } if (size_orig > 0) { if (dprintf(merged_fd, "%s%s%s\nfile %s\n", GOT_DIFF_CONFLICT_MARKER_ORIG, label_orig ? " " : "", label_orig ? label_orig : "", path_orig) < 0) { err = got_error_from_errno("dprintf"); goto done; } } if (dprintf(merged_fd, "%s\nfile %s\n%s%s%s\n", GOT_DIFF_CONFLICT_MARKER_SEP, path_deriv2, GOT_DIFF_CONFLICT_MARKER_END, label_deriv2 ? " " : "", label_deriv2 ? label_deriv2 : "") < 0) { err = got_error_from_errno("dprintf"); goto done; } } else if (changed_deriv) err = copy_file_to_fd(&size_deriv, f_deriv, merged_fd); else if (changed_deriv2) err = copy_file_to_fd(&size_deriv2, f_deriv2, merged_fd); done: if (size_orig == 0 && path_orig && unlink(path_orig) == -1 && err == NULL) err = got_error_from_errno2("unlink", path_orig); if (fd_orig != -1 && close(fd_orig) == -1 && err == NULL) err = got_error_from_errno2("close", path_orig); if (fd_deriv != -1 && close(fd_deriv) == -1 && err == NULL) err = got_error_from_errno2("close", path_deriv); if (fd_deriv2 != -1 && close(fd_deriv2) == -1 && err == NULL) err = got_error_from_errno2("close", path_deriv2); free(path_orig); free(path_deriv); free(path_deriv2); free(base_path_orig); free(base_path_deriv); free(base_path_deriv2); return err; } /* * Perform a 3-way merge where the file f_orig acts as the common * ancestor, the file f_deriv acts as the first derived version, * and the file f_deriv2 acts as the second derived version. * The merge result will be written to a new file at ondisk_path; any * existing file at this path will be replaced. */ static const struct got_error * merge_file(int *local_changes_subsumed, struct got_worktree *worktree, FILE *f_orig, FILE *f_deriv, FILE *f_deriv2, const char *ondisk_path, const char *path, uint16_t st_mode, const char *label_orig, const char *label_deriv, const char *label_deriv2, enum got_diff_algorithm diff_algo, struct got_repository *repo, got_worktree_checkout_cb progress_cb, void *progress_arg) { const struct got_error *err = NULL; int merged_fd = -1; FILE *f_merged = NULL; char *merged_path = NULL, *base_path = NULL; int overlapcnt = 0; char *parent = NULL; *local_changes_subsumed = 0; err = got_path_dirname(&parent, ondisk_path); if (err) return err; if (asprintf(&base_path, "%s/got-merged", parent) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = got_opentemp_named_fd(&merged_path, &merged_fd, base_path, ""); if (err) goto done; err = got_merge_diff3(&overlapcnt, merged_fd, f_deriv, f_orig, f_deriv2, label_deriv, label_orig, label_deriv2, diff_algo); if (err) { if (err->code != GOT_ERR_FILE_BINARY) goto done; err = merge_binary_file(&overlapcnt, merged_fd, f_deriv, f_orig, f_deriv2, label_deriv, label_orig, label_deriv2, ondisk_path); if (err) goto done; } err = (*progress_cb)(progress_arg, overlapcnt > 0 ? GOT_STATUS_CONFLICT : GOT_STATUS_MERGE, path); if (err) goto done; if (fsync(merged_fd) != 0) { err = got_error_from_errno("fsync"); goto done; } f_merged = fdopen(merged_fd, "r"); if (f_merged == NULL) { err = got_error_from_errno("fdopen"); goto done; } merged_fd = -1; /* Check if a clean merge has subsumed all local changes. */ if (overlapcnt == 0) { err = check_files_equal(local_changes_subsumed, f_deriv, f_merged); if (err) goto done; } if (fchmod(fileno(f_merged), apply_umask(st_mode)) != 0) { err = got_error_from_errno2("fchmod", merged_path); goto done; } if (rename(merged_path, ondisk_path) != 0) { err = got_error_from_errno3("rename", merged_path, ondisk_path); goto done; } done: if (err) { if (merged_path) unlink(merged_path); } if (merged_fd != -1 && close(merged_fd) == -1 && err == NULL) err = got_error_from_errno("close"); if (f_merged && fclose(f_merged) == EOF && err == NULL) err = got_error_from_errno("fclose"); free(merged_path); free(base_path); free(parent); return err; } static const struct got_error * update_symlink(const char *ondisk_path, const char *target_path, size_t target_len) { /* This is not atomic but matches what 'ln -sf' does. */ if (unlink(ondisk_path) == -1) return got_error_from_errno2("unlink", ondisk_path); if (symlink(target_path, ondisk_path) == -1) return got_error_from_errno3("symlink", target_path, ondisk_path); return NULL; } /* * Overwrite a symlink (or a regular file in case there was a "bad" symlink) * in the work tree with a file that contains conflict markers and the * conflicting target paths of the original version, a "derived version" * of a symlink from an incoming change, and a local version of the symlink. * * The original versions's target path can be NULL if it is not available, * such as if both derived versions added a new symlink at the same path. * * The incoming derived symlink target is NULL in case the incoming change * has deleted this symlink. */ static const struct got_error * install_symlink_conflict(const char *deriv_target, struct got_object_id *deriv_base_commit_id, const char *orig_target, const char *label_orig, const char *local_target, const char *ondisk_path) { const struct got_error *err; char *id_str = NULL, *label_deriv = NULL, *path = NULL; FILE *f = NULL; err = got_object_id_str(&id_str, deriv_base_commit_id); if (err) return got_error_from_errno("asprintf"); if (asprintf(&label_deriv, "%s: commit %s", GOT_MERGE_LABEL_MERGED, id_str) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = got_opentemp_named(&path, &f, "got-symlink-conflict", ""); if (err) goto done; if (fchmod(fileno(f), apply_umask(GOT_DEFAULT_FILE_MODE)) == -1) { err = got_error_from_errno2("fchmod", path); goto done; } if (fprintf(f, "%s %s\n%s\n%s%s%s%s%s\n%s\n%s\n", GOT_DIFF_CONFLICT_MARKER_BEGIN, label_deriv, deriv_target ? deriv_target : "(symlink was deleted)", orig_target ? label_orig : "", orig_target ? "\n" : "", orig_target ? orig_target : "", orig_target ? "\n" : "", GOT_DIFF_CONFLICT_MARKER_SEP, local_target, GOT_DIFF_CONFLICT_MARKER_END) < 0) { err = got_error_from_errno2("fprintf", path); goto done; } if (unlink(ondisk_path) == -1) { err = got_error_from_errno2("unlink", ondisk_path); goto done; } if (rename(path, ondisk_path) == -1) { err = got_error_from_errno3("rename", path, ondisk_path); goto done; } done: if (f != NULL && fclose(f) == EOF && err == NULL) err = got_error_from_errno2("fclose", path); free(path); free(id_str); free(label_deriv); return err; } /* forward declaration */ static const struct got_error * merge_blob(int *, struct got_worktree *, struct got_blob_object *, const char *, const char *, uint16_t, const char *, struct got_blob_object *, struct got_object_id *, struct got_repository *, got_worktree_checkout_cb, void *); /* * Merge a symlink into the work tree, where blob_orig acts as the common * ancestor, deriv_target is the link target of the first derived version, * and the symlink on disk acts as the second derived version. * Assume that contents of both blobs represent symlinks. */ static const struct got_error * merge_symlink(struct got_worktree *worktree, struct got_blob_object *blob_orig, const char *ondisk_path, const char *path, const char *label_orig, const char *deriv_target, struct got_object_id *deriv_base_commit_id, struct got_repository *repo, got_worktree_checkout_cb progress_cb, void *progress_arg) { const struct got_error *err = NULL; char *ancestor_target = NULL; struct stat sb; ssize_t ondisk_len, deriv_len; char ondisk_target[PATH_MAX]; int have_local_change = 0; int have_incoming_change = 0; if (lstat(ondisk_path, &sb) == -1) return got_error_from_errno2("lstat", ondisk_path); ondisk_len = readlink(ondisk_path, ondisk_target, sizeof(ondisk_target)); if (ondisk_len == -1) { err = got_error_from_errno2("readlink", ondisk_path); goto done; } ondisk_target[ondisk_len] = '\0'; if (blob_orig) { err = got_object_blob_read_to_str(&ancestor_target, blob_orig); if (err) goto done; } if (ancestor_target == NULL || (ondisk_len != strlen(ancestor_target) || memcmp(ondisk_target, ancestor_target, ondisk_len) != 0)) have_local_change = 1; deriv_len = strlen(deriv_target); if (ancestor_target == NULL || (deriv_len != strlen(ancestor_target) || memcmp(deriv_target, ancestor_target, deriv_len) != 0)) have_incoming_change = 1; if (!have_local_change && !have_incoming_change) { if (ancestor_target) { /* Both sides made the same change. */ err = (*progress_cb)(progress_arg, GOT_STATUS_MERGE, path); } else if (deriv_len == ondisk_len && memcmp(ondisk_target, deriv_target, deriv_len) == 0) { /* Both sides added the same symlink. */ err = (*progress_cb)(progress_arg, GOT_STATUS_MERGE, path); } else { /* Both sides added symlinks which don't match. */ err = install_symlink_conflict(deriv_target, deriv_base_commit_id, ancestor_target, label_orig, ondisk_target, ondisk_path); if (err) goto done; err = (*progress_cb)(progress_arg, GOT_STATUS_CONFLICT, path); } } else if (!have_local_change && have_incoming_change) { /* Apply the incoming change. */ err = update_symlink(ondisk_path, deriv_target, strlen(deriv_target)); if (err) goto done; err = (*progress_cb)(progress_arg, GOT_STATUS_MERGE, path); } else if (have_local_change && have_incoming_change) { if (deriv_len == ondisk_len && memcmp(deriv_target, ondisk_target, deriv_len) == 0) { /* Both sides made the same change. */ err = (*progress_cb)(progress_arg, GOT_STATUS_MERGE, path); } else { err = install_symlink_conflict(deriv_target, deriv_base_commit_id, ancestor_target, label_orig, ondisk_target, ondisk_path); if (err) goto done; err = (*progress_cb)(progress_arg, GOT_STATUS_CONFLICT, path); } } done: free(ancestor_target); return err; } static const struct got_error * dump_symlink_target_path_to_file(FILE **outfile, const char *ondisk_path) { const struct got_error *err = NULL; char target_path[PATH_MAX]; ssize_t target_len; size_t n; FILE *f; *outfile = NULL; f = got_opentemp(); if (f == NULL) return got_error_from_errno("got_opentemp"); target_len = readlink(ondisk_path, target_path, sizeof(target_path)); if (target_len == -1) { err = got_error_from_errno2("readlink", ondisk_path); goto done; } n = fwrite(target_path, 1, target_len, f); if (n != target_len) { err = got_ferror(f, GOT_ERR_IO); goto done; } if (fflush(f) == EOF) { err = got_error_from_errno("fflush"); goto done; } if (fseek(f, 0L, SEEK_SET) == -1) { err = got_ferror(f, GOT_ERR_IO); goto done; } done: if (err) fclose(f); else *outfile = f; return err; } /* * Perform a 3-way merge where blob_orig acts as the common ancestor, * blob_deriv acts as the first derived version, and the file on disk * acts as the second derived version. */ static const struct got_error * merge_blob(int *local_changes_subsumed, struct got_worktree *worktree, struct got_blob_object *blob_orig, const char *ondisk_path, const char *path, uint16_t st_mode, const char *label_orig, struct got_blob_object *blob_deriv, struct got_object_id *deriv_base_commit_id, struct got_repository *repo, got_worktree_checkout_cb progress_cb, void *progress_arg) { const struct got_error *err = NULL; FILE *f_orig = NULL, *f_deriv = NULL, *f_deriv2 = NULL; char *blob_orig_path = NULL; char *blob_deriv_path = NULL, *base_path = NULL, *id_str = NULL; char *label_deriv = NULL, *parent = NULL; *local_changes_subsumed = 0; err = got_path_dirname(&parent, ondisk_path); if (err) return err; if (blob_orig) { if (asprintf(&base_path, "%s/got-merge-blob-orig", parent) == -1) { err = got_error_from_errno("asprintf"); base_path = NULL; goto done; } err = got_opentemp_named(&blob_orig_path, &f_orig, base_path, ""); if (err) goto done; err = got_object_blob_dump_to_file(NULL, NULL, NULL, f_orig, blob_orig); if (err) goto done; free(base_path); } else { /* * No common ancestor exists. This is an "add vs add" conflict * and we simply use an empty ancestor file to make both files * appear in the merged result in their entirety. */ f_orig = got_opentemp(); if (f_orig == NULL) { err = got_error_from_errno("got_opentemp"); goto done; } } if (asprintf(&base_path, "%s/got-merge-blob-deriv", parent) == -1) { err = got_error_from_errno("asprintf"); base_path = NULL; goto done; } err = got_opentemp_named(&blob_deriv_path, &f_deriv, base_path, ""); if (err) goto done; err = got_object_blob_dump_to_file(NULL, NULL, NULL, f_deriv, blob_deriv); if (err) goto done; err = got_object_id_str(&id_str, deriv_base_commit_id); if (err) goto done; if (asprintf(&label_deriv, "%s: commit %s", GOT_MERGE_LABEL_MERGED, id_str) == -1) { err = got_error_from_errno("asprintf"); goto done; } /* * In order the run a 3-way merge with a symlink we copy the symlink's * target path into a temporary file and use that file with diff3. */ if (S_ISLNK(st_mode)) { err = dump_symlink_target_path_to_file(&f_deriv2, ondisk_path); if (err) goto done; } else { int fd; fd = open(ondisk_path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (fd == -1) { err = got_error_from_errno2("open", ondisk_path); goto done; } f_deriv2 = fdopen(fd, "r"); if (f_deriv2 == NULL) { err = got_error_from_errno2("fdopen", ondisk_path); close(fd); goto done; } } err = merge_file(local_changes_subsumed, worktree, f_orig, f_deriv, f_deriv2, ondisk_path, path, st_mode, label_orig, label_deriv, NULL, GOT_DIFF_ALGORITHM_MYERS, repo, progress_cb, progress_arg); done: if (f_orig && fclose(f_orig) == EOF && err == NULL) err = got_error_from_errno("fclose"); if (f_deriv && fclose(f_deriv) == EOF && err == NULL) err = got_error_from_errno("fclose"); if (f_deriv2 && fclose(f_deriv2) == EOF && err == NULL) err = got_error_from_errno("fclose"); free(base_path); if (blob_orig_path) { unlink(blob_orig_path); free(blob_orig_path); } if (blob_deriv_path) { unlink(blob_deriv_path); free(blob_deriv_path); } free(id_str); free(label_deriv); free(parent); return err; } static const struct got_error * create_fileindex_entry(struct got_fileindex_entry **new_iep, struct got_fileindex *fileindex, struct got_object_id *base_commit_id, int wt_fd, const char *path, struct got_object_id *blob_id, int update_timestamps) { const struct got_error *err = NULL; struct got_fileindex_entry *new_ie; *new_iep = NULL; err = got_fileindex_entry_alloc(&new_ie, path); if (err) return err; err = got_fileindex_entry_update(new_ie, wt_fd, path, blob_id, base_commit_id, update_timestamps); if (err) goto done; err = got_fileindex_entry_add(fileindex, new_ie); done: if (err) got_fileindex_entry_free(new_ie); else *new_iep = new_ie; return err; } static mode_t get_ondisk_perms(int executable, mode_t st_mode) { mode_t xbits = S_IXUSR; if (executable) { /* Map read bits to execute bits. */ if (st_mode & S_IRGRP) xbits |= S_IXGRP; if (st_mode & S_IROTH) xbits |= S_IXOTH; return st_mode | xbits; } return st_mode; } /* forward declaration */ static const struct got_error * install_blob(struct got_worktree *worktree, const char *ondisk_path, const char *path, mode_t te_mode, mode_t st_mode, struct got_blob_object *blob, int restoring_missing_file, int reverting_versioned_file, int installing_bad_symlink, int path_is_unversioned, int *update_timestamps, struct got_repository *repo, got_worktree_checkout_cb progress_cb, void *progress_arg); /* * This function assumes that the provided symlink target points at a * safe location in the work tree! */ static const struct got_error * replace_existing_symlink(int *did_something, const char *ondisk_path, const char *target_path, size_t target_len) { const struct got_error *err = NULL; ssize_t elen; char etarget[PATH_MAX]; int fd; *did_something = 0; /* * "Bad" symlinks (those pointing outside the work tree or into the * .got directory) are installed in the work tree as a regular file * which contains the bad symlink target path. * The new symlink target has already been checked for safety by our * caller. If we can successfully open a regular file then we simply * replace this file with a symlink below. */ fd = open(ondisk_path, O_RDWR | O_EXCL | O_NOFOLLOW | O_CLOEXEC); if (fd == -1) { if (!got_err_open_nofollow_on_symlink()) return got_error_from_errno2("open", ondisk_path); /* We are updating an existing on-disk symlink. */ elen = readlink(ondisk_path, etarget, sizeof(etarget)); if (elen == -1) return got_error_from_errno2("readlink", ondisk_path); if (elen == target_len && memcmp(etarget, target_path, target_len) == 0) return NULL; /* nothing to do */ } *did_something = 1; err = update_symlink(ondisk_path, target_path, target_len); if (fd != -1 && close(fd) == -1 && err == NULL) err = got_error_from_errno2("close", ondisk_path); return err; } static const struct got_error * is_bad_symlink_target(int *is_bad_symlink, const char *target_path, size_t target_len, const char *ondisk_path, const char *wtroot_path, const char *meta_dir) { const struct got_error *err = NULL; char canonpath[PATH_MAX]; char *path_got = NULL; *is_bad_symlink = 0; if (target_len >= sizeof(canonpath)) { *is_bad_symlink = 1; return NULL; } /* * We do not use realpath(3) to resolve the symlink's target * path because we don't want to resolve symlinks recursively. * Instead we make the path absolute and then canonicalize it. * Relative symlink target lookup should begin at the directory * in which the blob object is being installed. */ if (!got_path_is_absolute(target_path)) { char *abspath, *parent; err = got_path_dirname(&parent, ondisk_path); if (err) return err; if (asprintf(&abspath, "%s/%s", parent, target_path) == -1) { free(parent); return got_error_from_errno("asprintf"); } free(parent); if (strlen(abspath) >= sizeof(canonpath)) { err = got_error_path(abspath, GOT_ERR_BAD_PATH); free(abspath); return err; } err = got_canonpath(abspath, canonpath, sizeof(canonpath)); free(abspath); if (err) return err; } else { err = got_canonpath(target_path, canonpath, sizeof(canonpath)); if (err) return err; } /* Only allow symlinks pointing at paths within the work tree. */ if (!got_path_is_child(canonpath, wtroot_path, strlen(wtroot_path))) { *is_bad_symlink = 1; return NULL; } /* Do not allow symlinks pointing into the meta directory. */ if (asprintf(&path_got, "%s/%s", wtroot_path, meta_dir) == -1) return got_error_from_errno("asprintf"); if (got_path_is_child(canonpath, path_got, strlen(path_got))) *is_bad_symlink = 1; free(path_got); return NULL; } static const struct got_error * install_symlink(int *is_bad_symlink, struct got_worktree *worktree, const char *ondisk_path, const char *path, struct got_blob_object *blob, int restoring_missing_file, int reverting_versioned_file, int path_is_unversioned, int allow_bad_symlinks, struct got_repository *repo, got_worktree_checkout_cb progress_cb, void *progress_arg) { const struct got_error *err = NULL; char target_path[PATH_MAX]; size_t len, target_len = 0; const uint8_t *buf = got_object_blob_get_read_buf(blob); size_t hdrlen = got_object_blob_get_hdrlen(blob); *is_bad_symlink = 0; /* * Blob object content specifies the target path of the link. * If a symbolic link cannot be installed we instead create * a regular file which contains the link target path stored * in the blob object. */ do { err = got_object_blob_read_block(&len, blob); if (err) return err; if (len + target_len >= sizeof(target_path)) { /* Path too long; install as a regular file. */ *is_bad_symlink = 1; got_object_blob_rewind(blob); return install_blob(worktree, ondisk_path, path, GOT_DEFAULT_FILE_MODE, GOT_DEFAULT_FILE_MODE, blob, restoring_missing_file, reverting_versioned_file, 1, path_is_unversioned, NULL, repo, progress_cb, progress_arg); } if (len > 0) { /* Skip blob object header first time around. */ memcpy(target_path + target_len, buf + hdrlen, len - hdrlen); target_len += len - hdrlen; hdrlen = 0; } } while (len != 0); target_path[target_len] = '\0'; err = is_bad_symlink_target(is_bad_symlink, target_path, target_len, ondisk_path, worktree->root_path, worktree->meta_dir); if (err) return err; if (*is_bad_symlink && !allow_bad_symlinks) { /* install as a regular file */ got_object_blob_rewind(blob); err = install_blob(worktree, ondisk_path, path, GOT_DEFAULT_FILE_MODE, GOT_DEFAULT_FILE_MODE, blob, restoring_missing_file, reverting_versioned_file, 1, path_is_unversioned, NULL, repo, progress_cb, progress_arg); return err; } if (symlink(target_path, ondisk_path) == -1) { if (errno == EEXIST) { int symlink_replaced; if (path_is_unversioned) { err = (*progress_cb)(progress_arg, GOT_STATUS_UNVERSIONED, path); return err; } err = replace_existing_symlink(&symlink_replaced, ondisk_path, target_path, target_len); if (err) return err; if (progress_cb) { if (symlink_replaced) { err = (*progress_cb)(progress_arg, reverting_versioned_file ? GOT_STATUS_REVERT : GOT_STATUS_UPDATE, path); } else { err = (*progress_cb)(progress_arg, GOT_STATUS_EXISTS, path); } } return err; /* Nothing else to do. */ } if (errno == ENOENT) { char *parent; err = got_path_dirname(&parent, ondisk_path); if (err) return err; err = got_path_mkdir(parent); free(parent); if (err) return err; /* * Retry, and fall through to error handling * below if this second attempt fails. */ if (symlink(target_path, ondisk_path) != -1) { err = NULL; /* success */ if (progress_cb) { err = (*progress_cb)(progress_arg, reverting_versioned_file ? GOT_STATUS_REVERT : GOT_STATUS_ADD, path); } return err; } } /* Handle errors from first or second creation attempt. */ if (errno == ENAMETOOLONG) { /* bad target path; install as a regular file */ *is_bad_symlink = 1; got_object_blob_rewind(blob); err = install_blob(worktree, ondisk_path, path, GOT_DEFAULT_FILE_MODE, GOT_DEFAULT_FILE_MODE, blob, restoring_missing_file, reverting_versioned_file, 1, path_is_unversioned, NULL, repo, progress_cb, progress_arg); } else if (errno == ENOTDIR) { err = got_error_path(ondisk_path, GOT_ERR_FILE_OBSTRUCTED); } else { err = got_error_from_errno3("symlink", target_path, ondisk_path); } } else if (progress_cb) err = (*progress_cb)(progress_arg, reverting_versioned_file ? GOT_STATUS_REVERT : GOT_STATUS_ADD, path); return err; } static const struct got_error * install_blob(struct got_worktree *worktree, const char *ondisk_path, const char *path, mode_t te_mode, mode_t st_mode, struct got_blob_object *blob, int restoring_missing_file, int reverting_versioned_file, int installing_bad_symlink, int path_is_unversioned, int *update_timestamps, struct got_repository *repo, got_worktree_checkout_cb progress_cb, void *progress_arg) { const struct got_error *err = NULL; int fd = -1; size_t len, hdrlen; int update = 0; char *tmppath = NULL; mode_t mode; if (update_timestamps) *update_timestamps = 1; mode = get_ondisk_perms(te_mode & S_IXUSR, GOT_DEFAULT_FILE_MODE); fd = open(ondisk_path, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, mode); if (fd == -1) { if (errno == ENOENT || errno == ENOTDIR) { char *parent; err = got_path_dirname(&parent, path); if (err) return err; err = add_dir_on_disk(worktree, parent); if (err && err->code == GOT_ERR_FILE_OBSTRUCTED) err = got_error_path(path, err->code); free(parent); if (err) return err; fd = open(ondisk_path, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, mode); if (fd == -1) return got_error_from_errno2("open", ondisk_path); } else if (errno == EEXIST) { if (path_is_unversioned) { if (update_timestamps) *update_timestamps = 0; err = (*progress_cb)(progress_arg, GOT_STATUS_EXISTS, path); goto done; } if (!(S_ISLNK(st_mode) && S_ISREG(te_mode)) && !S_ISREG(st_mode) && !installing_bad_symlink) { /* TODO file is obstructed; do something */ err = got_error_path(ondisk_path, GOT_ERR_FILE_OBSTRUCTED); goto done; } else { err = got_opentemp_named_fd(&tmppath, &fd, ondisk_path, ""); if (err) goto done; update = 1; if (fchmod(fd, apply_umask(mode)) == -1) { err = got_error_from_errno2("fchmod", tmppath); goto done; } } } else return got_error_from_errno2("open", ondisk_path); } if (progress_cb) { if (restoring_missing_file) err = (*progress_cb)(progress_arg, GOT_STATUS_MISSING, path); else if (reverting_versioned_file) err = (*progress_cb)(progress_arg, GOT_STATUS_REVERT, path); else err = (*progress_cb)(progress_arg, update ? GOT_STATUS_UPDATE : GOT_STATUS_ADD, path); if (err) goto done; } hdrlen = got_object_blob_get_hdrlen(blob); do { const uint8_t *buf = got_object_blob_get_read_buf(blob); err = got_object_blob_read_block(&len, blob); if (err) break; if (len > 0) { /* Skip blob object header first time around. */ ssize_t outlen = write(fd, buf + hdrlen, len - hdrlen); if (outlen == -1) { err = got_error_from_errno("write"); goto done; } else if (outlen != len - hdrlen) { err = got_error(GOT_ERR_IO); goto done; } hdrlen = 0; } } while (len != 0); if (fsync(fd) != 0) { err = got_error_from_errno("fsync"); goto done; } if (update) { if (S_ISLNK(st_mode) && unlink(ondisk_path) == -1) { err = got_error_from_errno2("unlink", ondisk_path); goto done; } if (rename(tmppath, ondisk_path) != 0) { err = got_error_from_errno3("rename", tmppath, ondisk_path); goto done; } free(tmppath); tmppath = NULL; } done: if (fd != -1 && close(fd) == -1 && err == NULL) err = got_error_from_errno("close"); if (tmppath != NULL && unlink(tmppath) == -1 && err == NULL) err = got_error_from_errno2("unlink", tmppath); free(tmppath); return err; } /* * Detect whether a file is in STATUS_MODIFY or in STATUS_CONFLICT. * The latter is determined by the presence of conflict markers on * newly added lines. */ static const struct got_error * get_modified_file_status(unsigned char *status, struct got_blob_object *blob, const char *path, struct stat *sb, FILE *ondisk_file) { const struct got_error *err, *free_err; const char *markers[3] = { GOT_DIFF_CONFLICT_MARKER_BEGIN, GOT_DIFF_CONFLICT_MARKER_SEP, GOT_DIFF_CONFLICT_MARKER_END }; FILE *f1 = NULL; struct got_diffreg_result *diffreg_result = NULL; struct diff_result *r; int nchunks_parsed, n, i = 0, ln = 0; char *line = NULL; size_t linesize = 0; ssize_t linelen; f1 = got_opentemp(); if (f1 == NULL) return got_error_from_errno("got_opentemp"); if (blob) { got_object_blob_rewind(blob); err = got_object_blob_dump_to_file(NULL, NULL, NULL, f1, blob); if (err) goto done; } err = got_diff_files(&diffreg_result, f1, 1, NULL, ondisk_file, 1, NULL, 0, 0, 1, NULL, GOT_DIFF_ALGORITHM_MYERS); if (err) goto done; r = diffreg_result->result; for (n = 0; n < r->chunks.len; n += nchunks_parsed) { struct diff_chunk *c; struct diff_chunk_context cc = {}; off_t pos; c = diff_chunk_get(r, n); /* * Upgrade NO_CHANGE to MODIFY in case there are * chunks which contain changes. */ if (*status == GOT_STATUS_NO_CHANGE && (diff_chunk_type(c) == CHUNK_MINUS || diff_chunk_type(c) == CHUNK_PLUS)) *status = GOT_STATUS_MODIFY; /* * We can optimise conflict detection a little by * advancing straight to the next chunk if this * one has no added lines. */ if (diff_chunk_type(c) != CHUNK_PLUS) { nchunks_parsed = 1; continue; /* removed or unchanged lines */ } pos = diff_chunk_get_right_start_pos(c); if (fseek(ondisk_file, pos, SEEK_SET) == -1) { err = got_ferror(ondisk_file, GOT_ERR_IO); goto done; } diff_chunk_context_load_change(&cc, &nchunks_parsed, r, n, 0); ln = cc.right.start; while (ln < cc.right.end) { linelen = getline(&line, &linesize, ondisk_file); if (linelen == -1) { if (feof(ondisk_file)) break; err = got_ferror(ondisk_file, GOT_ERR_IO); break; } if (line && strncmp(line, markers[i], strlen(markers[i])) == 0) { if (strcmp(markers[i], GOT_DIFF_CONFLICT_MARKER_END) == 0) { *status = GOT_STATUS_CONFLICT; goto done; } else i++; } ++ln; } } done: free(line); if (f1 != NULL && fclose(f1) == EOF && err == NULL) err = got_error_from_errno("fclose"); free_err = got_diffreg_result_free(diffreg_result); if (err == NULL) err = free_err; return err; } static int xbit_differs(struct got_fileindex_entry *ie, uint16_t st_mode) { mode_t ie_mode = got_fileindex_perms_to_st(ie); return ((ie_mode & S_IXUSR) != (st_mode & S_IXUSR)); } static int stat_info_differs(struct got_fileindex_entry *ie, struct stat *sb) { return !(ie->ctime_sec == sb->st_ctim.tv_sec && ie->ctime_nsec == sb->st_ctim.tv_nsec && ie->mtime_sec == sb->st_mtim.tv_sec && ie->mtime_nsec == sb->st_mtim.tv_nsec && ie->size == (sb->st_size & 0xffffffff) && !xbit_differs(ie, sb->st_mode)); } static unsigned char get_staged_status(struct got_fileindex_entry *ie) { switch (got_fileindex_entry_stage_get(ie)) { case GOT_FILEIDX_STAGE_ADD: return GOT_STATUS_ADD; case GOT_FILEIDX_STAGE_DELETE: return GOT_STATUS_DELETE; case GOT_FILEIDX_STAGE_MODIFY: return GOT_STATUS_MODIFY; default: return GOT_STATUS_NO_CHANGE; } } static const struct got_error * get_symlink_modification_status(unsigned char *status, struct got_fileindex_entry *ie, const char *abspath, int dirfd, const char *de_name, struct got_blob_object *blob) { const struct got_error *err = NULL; char target_path[PATH_MAX]; char etarget[PATH_MAX]; ssize_t elen; size_t len, target_len = 0; const uint8_t *buf = got_object_blob_get_read_buf(blob); size_t hdrlen = got_object_blob_get_hdrlen(blob); *status = GOT_STATUS_NO_CHANGE; /* Blob object content specifies the target path of the link. */ do { err = got_object_blob_read_block(&len, blob); if (err) return err; if (len + target_len >= sizeof(target_path)) { /* * Should not happen. The blob contents were OK * when this symlink was installed. */ return got_error(GOT_ERR_NO_SPACE); } if (len > 0) { /* Skip blob object header first time around. */ memcpy(target_path + target_len, buf + hdrlen, len - hdrlen); target_len += len - hdrlen; hdrlen = 0; } } while (len != 0); target_path[target_len] = '\0'; if (dirfd != -1) { elen = readlinkat(dirfd, de_name, etarget, sizeof(etarget)); if (elen == -1) return got_error_from_errno2("readlinkat", abspath); } else { elen = readlink(abspath, etarget, sizeof(etarget)); if (elen == -1) return got_error_from_errno2("readlink", abspath); } if (elen != target_len || memcmp(etarget, target_path, target_len) != 0) *status = GOT_STATUS_MODIFY; return NULL; } static const struct got_error * file_is_binary(int *is_binary , FILE *f) { char buf[8192]; size_t r; off_t pos; *is_binary = 0; pos = ftello(f); if (pos == -1) return got_error_from_errno("ftello"); if (fseeko(f, 0L, SEEK_SET) == -1) return got_ferror(f, GOT_ERR_IO); r = fread(buf, 1, sizeof(buf), f); if (r == 0 && ferror(f)) return got_error_from_errno("fread"); if (r > 0) *is_binary = memchr(buf, '\0', r) != NULL; if (fseeko(f, pos, SEEK_SET) == -1) return got_ferror(f, GOT_ERR_IO); return NULL; } static const struct got_error * get_file_status(unsigned char *status, struct stat *sb, struct got_fileindex_entry *ie, const char *abspath, int dirfd, const char *de_name, struct got_repository *repo) { const struct got_error *err = NULL; struct got_object_id id; int fd = -1, fd1 = -1; FILE *f = NULL; uint8_t fbuf[8192]; struct got_blob_object *blob = NULL; unsigned char staged_status; staged_status = get_staged_status(ie); *status = GOT_STATUS_NO_CHANGE; memset(sb, 0, sizeof(*sb)); /* * Whenever the caller provides a directory descriptor and a * directory entry name for the file, use them! This prevents * race conditions if filesystem paths change beneath our feet. */ if (dirfd != -1) { if (fstatat(dirfd, de_name, sb, AT_SYMLINK_NOFOLLOW) == -1) { if (errno == ENOENT) { if (got_fileindex_entry_has_file_on_disk(ie)) *status = GOT_STATUS_MISSING; else *status = GOT_STATUS_DELETE; goto done; } err = got_error_from_errno2("fstatat", abspath); goto done; } } else { fd = open(abspath, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (fd == -1 && errno != ENOENT && !got_err_open_nofollow_on_symlink()) return got_error_from_errno2("open", abspath); else if (fd == -1 && got_err_open_nofollow_on_symlink()) { if (lstat(abspath, sb) == -1) return got_error_from_errno2("lstat", abspath); } else if (fd == -1 || fstat(fd, sb) == -1) { if (errno == ENOENT) { if (got_fileindex_entry_has_file_on_disk(ie)) *status = GOT_STATUS_MISSING; else *status = GOT_STATUS_DELETE; goto done; } err = got_error_from_errno2("fstat", abspath); goto done; } } if (!S_ISREG(sb->st_mode) && !S_ISLNK(sb->st_mode)) { *status = GOT_STATUS_OBSTRUCTED; goto done; } if (!got_fileindex_entry_has_file_on_disk(ie)) { *status = GOT_STATUS_DELETE; goto done; } else if (!got_fileindex_entry_has_blob(ie) && staged_status != GOT_STATUS_ADD) { *status = GOT_STATUS_ADD; goto done; } if (!stat_info_differs(ie, sb)) goto done; if (S_ISLNK(sb->st_mode) && got_fileindex_entry_filetype_get(ie) != GOT_FILEIDX_MODE_SYMLINK) { *status = GOT_STATUS_MODIFY; goto done; } if (staged_status == GOT_STATUS_MODIFY || staged_status == GOT_STATUS_ADD) got_fileindex_entry_get_staged_blob_id(&id, ie); else got_fileindex_entry_get_blob_id(&id, ie); fd1 = got_opentempfd(); if (fd1 == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } err = got_object_open_as_blob(&blob, repo, &id, sizeof(fbuf), fd1); if (err) goto done; if (S_ISLNK(sb->st_mode)) { err = get_symlink_modification_status(status, ie, abspath, dirfd, de_name, blob); goto done; } if (dirfd != -1) { fd = openat(dirfd, de_name, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (fd == -1) { err = got_error_from_errno2("openat", abspath); goto done; } } f = fdopen(fd, "r"); if (f == NULL) { err = got_error_from_errno2("fdopen", abspath); goto done; } fd = -1; if (staged_status == GOT_STATUS_NO_CHANGE && S_ISREG(sb->st_mode) && got_fileindex_entry_filetype_get(ie) == GOT_FILEIDX_MODE_REGULAR_FILE && ie->size != (sb->st_size & 0xffffffff)) { int is_binary; /* The size of regular files differs. */ *status = GOT_STATUS_MODIFY; /* * We can skip the diff engine for binary files on disk. * Text files still need to be checked for conflict markers. */ err = file_is_binary(&is_binary, f); if (err || is_binary) goto done; } err = get_modified_file_status(status, blob, ie->path, sb, f); if (err) goto done; if (*status == GOT_STATUS_NO_CHANGE && xbit_differs(ie, sb->st_mode)) *status = GOT_STATUS_MODE_CHANGE; done: if (fd1 != -1 && close(fd1) == -1 && err == NULL) err = got_error_from_errno("close"); if (blob) got_object_blob_close(blob); if (f != NULL && fclose(f) == EOF && err == NULL) err = got_error_from_errno2("fclose", abspath); if (fd != -1 && close(fd) == -1 && err == NULL) err = got_error_from_errno2("close", abspath); return err; } /* * Update timestamps in the file index if a file is unmodified and * we had to run a full content comparison to find out. */ static const struct got_error * sync_timestamps(int wt_fd, const char *path, unsigned char status, struct got_fileindex_entry *ie, struct stat *sb) { if (status == GOT_STATUS_NO_CHANGE && stat_info_differs(ie, sb)) return got_fileindex_entry_update(ie, wt_fd, path, &ie->blob, &ie->commit, 1); return NULL; } static const struct got_error *remove_ondisk_file(const char *, const char *); static const struct got_error * update_blob(struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_fileindex_entry *ie, struct got_tree_entry *te, const char *path, struct got_repository *repo, got_worktree_checkout_cb progress_cb, void *progress_arg) { const struct got_error *err = NULL; struct got_blob_object *blob = NULL; char *ondisk_path = NULL; unsigned char status = GOT_STATUS_NO_CHANGE; struct stat sb; int fd1 = -1, fd2 = -1; int update_timestamps = 0; if (asprintf(&ondisk_path, "%s/%s", worktree->root_path, path) == -1) return got_error_from_errno("asprintf"); if (ie) { if (get_staged_status(ie) != GOT_STATUS_NO_CHANGE) { err = got_error_path(ie->path, GOT_ERR_FILE_STAGED); goto done; } err = get_file_status(&status, &sb, ie, ondisk_path, -1, NULL, repo); if (err) goto done; if (status == GOT_STATUS_MISSING || status == GOT_STATUS_DELETE) sb.st_mode = got_fileindex_perms_to_st(ie); } else { if (stat(ondisk_path, &sb) == -1) { if (errno != ENOENT && errno != ENOTDIR) { err = got_error_from_errno2("stat", ondisk_path); goto done; } sb.st_mode = GOT_DEFAULT_FILE_MODE; status = GOT_STATUS_UNVERSIONED; } else { if (S_ISREG(sb.st_mode) || S_ISLNK(sb.st_mode)) status = GOT_STATUS_UNVERSIONED; else status = GOT_STATUS_OBSTRUCTED; } } if (status == GOT_STATUS_OBSTRUCTED) { if (ie) got_fileindex_entry_mark_skipped(ie); err = (*progress_cb)(progress_arg, status, path); goto done; } if (status == GOT_STATUS_CONFLICT) { if (ie) got_fileindex_entry_mark_skipped(ie); err = (*progress_cb)(progress_arg, GOT_STATUS_CANNOT_UPDATE, path); goto done; } if (S_ISDIR(te->mode)) { /* file changing into a directory */ if (status == GOT_STATUS_UNVERSIONED) { err = (*progress_cb)(progress_arg, status, path); } else if (status != GOT_STATUS_NO_CHANGE && status != GOT_STATUS_DELETE && status != GOT_STATUS_NONEXISTENT && status != GOT_STATUS_MISSING) { err = (*progress_cb)(progress_arg, GOT_STATUS_CANNOT_DELETE, path); } else if (ie) { if (status != GOT_STATUS_DELETE && status != GOT_STATUS_NONEXISTENT && status != GOT_STATUS_MISSING) { err = remove_ondisk_file(worktree->root_path, ie->path); if (err && !(err->code == GOT_ERR_ERRNO && errno == ENOENT)) goto done; } got_fileindex_entry_remove(fileindex, ie); err = (*progress_cb)(progress_arg, GOT_STATUS_DELETE, ie->path); } goto done; /* nothing else to do */ } if (ie && status != GOT_STATUS_MISSING && S_ISREG(sb.st_mode) && (S_ISLNK(te->mode) || (te->mode & S_IXUSR) == (sb.st_mode & S_IXUSR))) { /* * This is a regular file or an installed bad symlink. * If the file index indicates that this file is already * up-to-date with respect to the repository we can skip * updating contents of this file. */ if (got_fileindex_entry_has_commit(ie) && got_object_id_cmp(&ie->commit, worktree->base_commit_id) == 0) { /* Same commit. */ err = sync_timestamps(worktree->root_fd, path, status, ie, &sb); if (err) goto done; err = (*progress_cb)(progress_arg, GOT_STATUS_EXISTS, path); goto done; } if (got_fileindex_entry_has_blob(ie) && got_object_id_cmp(&ie->blob, &te->id) == 0) { /* Different commit but the same blob. */ if (got_fileindex_entry_has_commit(ie)) { /* Update the base commit ID of this file. */ memcpy(&ie->commit, worktree->base_commit_id, sizeof(ie->commit)); } err = sync_timestamps(worktree->root_fd, path, status, ie, &sb); if (err) goto done; err = (*progress_cb)(progress_arg, GOT_STATUS_EXISTS, path); goto done; } } fd1 = got_opentempfd(); if (fd1 == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } err = got_object_open_as_blob(&blob, repo, &te->id, 8192, fd1); if (err) goto done; if (status == GOT_STATUS_MODIFY || status == GOT_STATUS_ADD) { struct got_blob_object *blob2 = NULL; char *label_orig = NULL; if (got_fileindex_entry_has_blob(ie)) { fd2 = got_opentempfd(); if (fd2 == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } struct got_object_id id2; got_fileindex_entry_get_blob_id(&id2, ie); err = got_object_open_as_blob(&blob2, repo, &id2, 8192, fd2); if (err) goto done; } if (got_fileindex_entry_has_commit(ie)) { char id_str[GOT_HASH_DIGEST_STRING_MAXLEN]; if (got_hash_digest_to_str(ie->commit.hash, id_str, sizeof(id_str), ie->commit.algo) == NULL) { err = got_error_path(id_str, GOT_ERR_BAD_OBJ_ID_STR); goto done; } if (asprintf(&label_orig, "%s: commit %s", GOT_MERGE_LABEL_BASE, id_str) == -1) { err = got_error_from_errno("asprintf"); goto done; } } if (S_ISLNK(te->mode) && S_ISLNK(sb.st_mode)) { char *link_target; err = got_object_blob_read_to_str(&link_target, blob); if (err) goto done; err = merge_symlink(worktree, blob2, ondisk_path, path, label_orig, link_target, worktree->base_commit_id, repo, progress_cb, progress_arg); free(link_target); } else { err = merge_blob(&update_timestamps, worktree, blob2, ondisk_path, path, sb.st_mode, label_orig, blob, worktree->base_commit_id, repo, progress_cb, progress_arg); } free(label_orig); if (fd2 != -1 && close(fd2) == -1 && err == NULL) { err = got_error_from_errno("close"); goto done; } if (blob2) got_object_blob_close(blob2); if (err) goto done; /* * Do not update timestamps of files with local changes. * Otherwise, a future status walk would treat them as * unmodified files again. */ err = got_fileindex_entry_update(ie, worktree->root_fd, path, &blob->id, worktree->base_commit_id, update_timestamps); } else if (status == GOT_STATUS_MODE_CHANGE) { err = got_fileindex_entry_update(ie, worktree->root_fd, path, &blob->id, worktree->base_commit_id, 0); } else if (status == GOT_STATUS_DELETE) { err = (*progress_cb)(progress_arg, GOT_STATUS_MERGE, path); if (err) goto done; err = got_fileindex_entry_update(ie, worktree->root_fd, path, &blob->id, worktree->base_commit_id, 0); if (err) goto done; } else { int is_bad_symlink = 0; if (S_ISLNK(te->mode)) { err = install_symlink(&is_bad_symlink, worktree, ondisk_path, path, blob, status == GOT_STATUS_MISSING, 0, status == GOT_STATUS_UNVERSIONED, 0, repo, progress_cb, progress_arg); } else { err = install_blob(worktree, ondisk_path, path, te->mode, sb.st_mode, blob, status == GOT_STATUS_MISSING, 0, 0, status == GOT_STATUS_UNVERSIONED, &update_timestamps, repo, progress_cb, progress_arg); } if (err) goto done; if (ie) { err = got_fileindex_entry_update(ie, worktree->root_fd, path, &blob->id, worktree->base_commit_id, 1); } else { err = create_fileindex_entry(&ie, fileindex, worktree->base_commit_id, worktree->root_fd, path, &blob->id, update_timestamps); } if (err) goto done; if (is_bad_symlink) { got_fileindex_entry_filetype_set(ie, GOT_FILEIDX_MODE_BAD_SYMLINK); } } if (fd1 != -1 && close(fd1) == -1 && err == NULL) { err = got_error_from_errno("close"); goto done; } got_object_blob_close(blob); done: free(ondisk_path); return err; } static const struct got_error * remove_ondisk_file(const char *root_path, const char *path) { const struct got_error *err = NULL; char *ondisk_path = NULL, *parent = NULL; if (asprintf(&ondisk_path, "%s/%s", root_path, path) == -1) return got_error_from_errno("asprintf"); if (unlink(ondisk_path) == -1) { if (errno != ENOENT) err = got_error_from_errno2("unlink", ondisk_path); } else { size_t root_len = strlen(root_path); err = got_path_dirname(&parent, ondisk_path); if (err) goto done; while (got_path_cmp(parent, root_path, strlen(parent), root_len) != 0) { free(ondisk_path); ondisk_path = parent; parent = NULL; if (rmdir(ondisk_path) == -1) { if (errno != ENOTEMPTY) err = got_error_from_errno2("rmdir", ondisk_path); break; } err = got_path_dirname(&parent, ondisk_path); if (err) break; } } done: free(ondisk_path); free(parent); return err; } static const struct got_error * delete_blob(struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_fileindex_entry *ie, struct got_repository *repo, got_worktree_checkout_cb progress_cb, void *progress_arg) { const struct got_error *err = NULL; unsigned char status; struct stat sb; char *ondisk_path; if (get_staged_status(ie) != GOT_STATUS_NO_CHANGE) return got_error_path(ie->path, GOT_ERR_FILE_STAGED); if (asprintf(&ondisk_path, "%s/%s", worktree->root_path, ie->path) == -1) return got_error_from_errno("asprintf"); err = get_file_status(&status, &sb, ie, ondisk_path, -1, NULL, repo); if (err) goto done; if (S_ISLNK(sb.st_mode) && status != GOT_STATUS_NO_CHANGE) { char ondisk_target[PATH_MAX]; ssize_t ondisk_len = readlink(ondisk_path, ondisk_target, sizeof(ondisk_target)); if (ondisk_len == -1) { err = got_error_from_errno2("readlink", ondisk_path); goto done; } ondisk_target[ondisk_len] = '\0'; err = install_symlink_conflict(NULL, worktree->base_commit_id, NULL, NULL, /* XXX pass common ancestor info? */ ondisk_target, ondisk_path); if (err) goto done; err = (*progress_cb)(progress_arg, GOT_STATUS_CONFLICT, ie->path); goto done; } if (status == GOT_STATUS_MODIFY || status == GOT_STATUS_CONFLICT || status == GOT_STATUS_ADD) { err = (*progress_cb)(progress_arg, GOT_STATUS_MERGE, ie->path); if (err) goto done; /* * Preserve the working file and change the deleted blob's * entry into a schedule-add entry. */ err = got_fileindex_entry_update(ie, worktree->root_fd, ie->path, NULL, NULL, 0); } else { err = (*progress_cb)(progress_arg, GOT_STATUS_DELETE, ie->path); if (err) goto done; if (status == GOT_STATUS_NO_CHANGE) { err = remove_ondisk_file(worktree->root_path, ie->path); if (err) goto done; } got_fileindex_entry_remove(fileindex, ie); } done: free(ondisk_path); return err; } struct diff_cb_arg { struct got_fileindex *fileindex; struct got_worktree *worktree; struct got_repository *repo; got_worktree_checkout_cb progress_cb; void *progress_arg; got_cancel_cb cancel_cb; void *cancel_arg; }; static const struct got_error * diff_old_new(void *arg, struct got_fileindex_entry *ie, struct got_tree_entry *te, const char *parent_path) { const struct got_error *err = NULL; struct diff_cb_arg *a = arg; if (a->cancel_cb) { err = a->cancel_cb(a->cancel_arg); if (err) return err; } return update_blob(a->worktree, a->fileindex, ie, te, ie->path, a->repo, a->progress_cb, a->progress_arg); } static const struct got_error * diff_old(void *arg, struct got_fileindex_entry *ie, const char *parent_path) { const struct got_error *err = NULL; struct diff_cb_arg *a = arg; if (a->cancel_cb) { err = a->cancel_cb(a->cancel_arg); if (err) return err; } return delete_blob(a->worktree, a->fileindex, ie, a->repo, a->progress_cb, a->progress_arg); } static const struct got_error * diff_new(void *arg, struct got_tree_entry *te, const char *parent_path) { const struct got_error *err = NULL; struct diff_cb_arg *a = arg; char *path; if (a->cancel_cb) { err = a->cancel_cb(a->cancel_arg); if (err) return err; } if (got_object_tree_entry_is_submodule(te)) return NULL; if (!S_ISREG(te->mode) && !S_ISLNK(te->mode)) return NULL; if (asprintf(&path, "%s%s%s", parent_path, parent_path[0] ? "/" : "", te->name) == -1) return got_error_from_errno("asprintf"); err = update_blob(a->worktree, a->fileindex, NULL, te, path, a->repo, a->progress_cb, a->progress_arg); free(path); return err; } const struct got_error * got_worktree_get_uuid(char **uuidstr, struct got_worktree *worktree) { uint32_t uuid_status; uuid_to_string(&worktree->uuid, uuidstr, &uuid_status); if (uuid_status != uuid_s_ok) { *uuidstr = NULL; return got_error_uuid(uuid_status, "uuid_to_string"); } return NULL; } static const struct got_error * get_ref_name(char **refname, struct got_worktree *worktree, const char *prefix) { const struct got_error *err = NULL; char *uuidstr = NULL; *refname = NULL; err = got_worktree_get_uuid(&uuidstr, worktree); if (err) return err; if (asprintf(refname, "%s-%s", prefix, uuidstr) == -1) { err = got_error_from_errno("asprintf"); *refname = NULL; } free(uuidstr); return err; } const struct got_error * got_worktree_get_logmsg_ref_name(char **refname, struct got_worktree *worktree, const char *prefix) { return get_ref_name(refname, worktree, prefix); } static const struct got_error * get_base_ref_name(char **refname, struct got_worktree *worktree) { return get_ref_name(refname, worktree, GOT_WORKTREE_BASE_REF_PREFIX); } static const struct got_error * get_rebase_tmp_ref_name(char **refname, struct got_worktree *worktree) { return get_ref_name(refname, worktree, GOT_WORKTREE_REBASE_TMP_REF_PREFIX); } static const struct got_error * get_newbase_symref_name(char **refname, struct got_worktree *worktree) { return get_ref_name(refname, worktree, GOT_WORKTREE_NEWBASE_REF_PREFIX); } static const struct got_error * get_rebase_branch_symref_name(char **refname, struct got_worktree *worktree) { return get_ref_name(refname, worktree, GOT_WORKTREE_REBASE_BRANCH_REF_PREFIX); } static const struct got_error * get_rebase_commit_ref_name(char **refname, struct got_worktree *worktree) { return get_ref_name(refname, worktree, GOT_WORKTREE_REBASE_COMMIT_REF_PREFIX); } static const struct got_error * get_histedit_tmp_ref_name(char **refname, struct got_worktree *worktree) { return get_ref_name(refname, worktree, GOT_WORKTREE_HISTEDIT_TMP_REF_PREFIX); } static const struct got_error * get_histedit_branch_symref_name(char **refname, struct got_worktree *worktree) { return get_ref_name(refname, worktree, GOT_WORKTREE_HISTEDIT_BRANCH_REF_PREFIX); } static const struct got_error * get_histedit_base_commit_ref_name(char **refname, struct got_worktree *worktree) { return get_ref_name(refname, worktree, GOT_WORKTREE_HISTEDIT_BASE_COMMIT_REF_PREFIX); } static const struct got_error * get_histedit_commit_ref_name(char **refname, struct got_worktree *worktree) { return get_ref_name(refname, worktree, GOT_WORKTREE_HISTEDIT_COMMIT_REF_PREFIX); } const struct got_error * got_worktree_get_histedit_script_path(char **path, struct got_worktree *worktree) { if (asprintf(path, "%s/%s/%s", worktree->root_path, worktree->meta_dir, GOT_WORKTREE_HISTEDIT_SCRIPT) == -1) { *path = NULL; return got_error_from_errno("asprintf"); } return NULL; } static const struct got_error * get_merge_branch_ref_name(char **refname, struct got_worktree *worktree) { return get_ref_name(refname, worktree, GOT_WORKTREE_MERGE_BRANCH_REF_PREFIX); } static const struct got_error * get_merge_commit_ref_name(char **refname, struct got_worktree *worktree) { return get_ref_name(refname, worktree, GOT_WORKTREE_MERGE_COMMIT_REF_PREFIX); } /* * Prevent Git's garbage collector from deleting our base commit by * setting a reference to our base commit's ID. */ static const struct got_error * ref_base_commit(struct got_worktree *worktree, struct got_repository *repo) { const struct got_error *err = NULL; struct got_reference *ref = NULL; char *refname; err = get_base_ref_name(&refname, worktree); if (err) return err; err = got_ref_alloc(&ref, refname, worktree->base_commit_id); if (err) goto done; err = got_ref_write(ref, repo); done: free(refname); if (ref) got_ref_close(ref); return err; } static const struct got_error * get_fileindex_path(char **fileindex_path, struct got_worktree *worktree) { const struct got_error *err = NULL; if (asprintf(fileindex_path, "%s/%s/%s", worktree->root_path, worktree->meta_dir, GOT_WORKTREE_FILE_INDEX) == -1) { err = got_error_from_errno("asprintf"); *fileindex_path = NULL; } return err; } static const struct got_error * open_fileindex(struct got_fileindex **fileindex, char **fileindex_path, struct got_worktree *worktree, enum got_hash_algorithm algo) { const struct got_error *err = NULL; FILE *index = NULL; *fileindex = got_fileindex_alloc(algo); *fileindex_path = NULL; if (*fileindex == NULL) return got_error_from_errno("got_fileindex_alloc"); err = get_fileindex_path(fileindex_path, worktree); if (err) goto done; index = fopen(*fileindex_path, "rbe"); if (index == NULL) { if (errno != ENOENT) err = got_error_from_errno2("fopen", *fileindex_path); } else { err = got_fileindex_read(*fileindex, index, algo); if (fclose(index) == EOF && err == NULL) err = got_error_from_errno("fclose"); } done: if (err) { free(*fileindex_path); *fileindex_path = NULL; got_fileindex_free(*fileindex); *fileindex = NULL; } return err; } struct bump_base_commit_id_arg { struct got_object_id *base_commit_id; const char *path; size_t path_len; const char *entry_name; got_worktree_checkout_cb progress_cb; void *progress_arg; }; static const struct got_error * bump_base_commit_id(void *arg, struct got_fileindex_entry *ie) { const struct got_error *err; struct bump_base_commit_id_arg *a = arg; if (a->entry_name) { if (strcmp(ie->path, a->path) != 0) return NULL; } else if (!got_path_is_child(ie->path, a->path, a->path_len)) return NULL; if (got_fileindex_entry_was_skipped(ie)) return NULL; if (got_object_id_cmp(&ie->commit, a->base_commit_id) == 0) return NULL; if (a->progress_cb) { err = (*a->progress_cb)(a->progress_arg, GOT_STATUS_BUMP_BASE, ie->path); if (err) return err; } memcpy(&ie->commit, a->base_commit_id, sizeof(ie->commit)); return NULL; } /* Bump base commit ID of all files within an updated part of the work tree. */ static const struct got_error * bump_base_commit_id_everywhere(struct got_worktree *worktree, struct got_fileindex *fileindex, got_worktree_checkout_cb progress_cb, void *progress_arg) { struct bump_base_commit_id_arg bbc_arg; bbc_arg.base_commit_id = worktree->base_commit_id; bbc_arg.entry_name = NULL; bbc_arg.path = ""; bbc_arg.path_len = 0; bbc_arg.progress_cb = progress_cb; bbc_arg.progress_arg = progress_arg; return got_fileindex_for_each_entry_safe(fileindex, bump_base_commit_id, &bbc_arg); } static const struct got_error * sync_fileindex(struct got_fileindex *fileindex, const char *fileindex_path) { const struct got_error *err = NULL; char *new_fileindex_path = NULL; FILE *new_index = NULL; struct timespec timeout; err = got_opentemp_named(&new_fileindex_path, &new_index, fileindex_path, ""); if (err) goto done; err = got_fileindex_write(fileindex, new_index); if (err) goto done; if (rename(new_fileindex_path, fileindex_path) != 0) { err = got_error_from_errno3("rename", new_fileindex_path, fileindex_path); unlink(new_fileindex_path); } /* * Sleep for a short amount of time to ensure that files modified after * this program exits have a different time stamp from the one which * was recorded in the file index. */ timeout.tv_sec = 0; timeout.tv_nsec = 1; nanosleep(&timeout, NULL); done: if (new_index) fclose(new_index); free(new_fileindex_path); return err; } static const struct got_error * find_tree_entry_for_checkout(int *entry_type, char **tree_relpath, struct got_object_id **tree_id, const char *wt_relpath, struct got_commit_object *base_commit, struct got_worktree *worktree, struct got_repository *repo) { const struct got_error *err = NULL; struct got_object_id *id = NULL; char *in_repo_path = NULL; int is_root_wt = got_path_is_root_dir(worktree->path_prefix); *entry_type = GOT_OBJ_TYPE_ANY; *tree_relpath = NULL; *tree_id = NULL; if (wt_relpath[0] == '\0') { /* Check out all files within the work tree. */ *entry_type = GOT_OBJ_TYPE_TREE; *tree_relpath = strdup(""); if (*tree_relpath == NULL) { err = got_error_from_errno("strdup"); goto done; } err = got_object_id_by_path(tree_id, repo, base_commit, worktree->path_prefix); if (err) goto done; return NULL; } /* Check out a subset of files in the work tree. */ if (asprintf(&in_repo_path, "%s%s%s", worktree->path_prefix, is_root_wt ? "" : "/", wt_relpath) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = got_object_id_by_path(&id, repo, base_commit, in_repo_path); if (err) goto done; free(in_repo_path); in_repo_path = NULL; err = got_object_get_type(entry_type, repo, id); if (err) goto done; if (*entry_type == GOT_OBJ_TYPE_BLOB) { /* Check out a single file. */ if (strchr(wt_relpath, '/') == NULL) { /* Check out a single file in work tree's root dir. */ in_repo_path = strdup(worktree->path_prefix); if (in_repo_path == NULL) { err = got_error_from_errno("strdup"); goto done; } *tree_relpath = strdup(""); if (*tree_relpath == NULL) { err = got_error_from_errno("strdup"); goto done; } } else { /* Check out a single file in a subdirectory. */ err = got_path_dirname(tree_relpath, wt_relpath); if (err) return err; if (asprintf(&in_repo_path, "%s%s%s", worktree->path_prefix, is_root_wt ? "" : "/", *tree_relpath) == -1) { err = got_error_from_errno("asprintf"); goto done; } } err = got_object_id_by_path(tree_id, repo, base_commit, in_repo_path); } else { /* Check out all files within a subdirectory. */ *tree_id = got_object_id_dup(id); if (*tree_id == NULL) { err = got_error_from_errno("got_object_id_dup"); goto done; } *tree_relpath = strdup(wt_relpath); if (*tree_relpath == NULL) { err = got_error_from_errno("strdup"); goto done; } } done: free(id); free(in_repo_path); if (err) { *entry_type = GOT_OBJ_TYPE_ANY; free(*tree_relpath); *tree_relpath = NULL; free(*tree_id); *tree_id = NULL; } return err; } static const struct got_error * checkout_files(struct got_worktree *worktree, struct got_fileindex *fileindex, const char *relpath, struct got_object_id *tree_id, const char *entry_name, struct got_repository *repo, got_worktree_checkout_cb progress_cb, void *progress_arg, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; struct got_commit_object *commit = NULL; struct got_tree_object *tree = NULL; struct got_fileindex_diff_tree_cb diff_cb; struct diff_cb_arg arg; err = ref_base_commit(worktree, repo); if (err) { if (!(err->code == GOT_ERR_ERRNO && (errno == EACCES || errno == EROFS))) goto done; err = (*progress_cb)(progress_arg, GOT_STATUS_BASE_REF_ERR, worktree->root_path); if (err) return err; } err = got_object_open_as_commit(&commit, repo, worktree->base_commit_id); if (err) goto done; err = got_object_open_as_tree(&tree, repo, tree_id); if (err) goto done; if (entry_name && got_object_tree_find_entry(tree, entry_name) == NULL) { err = got_error_path(entry_name, GOT_ERR_NO_TREE_ENTRY); goto done; } diff_cb.diff_old_new = diff_old_new; diff_cb.diff_old = diff_old; diff_cb.diff_new = diff_new; arg.fileindex = fileindex; arg.worktree = worktree; arg.repo = repo; arg.progress_cb = progress_cb; arg.progress_arg = progress_arg; arg.cancel_cb = cancel_cb; arg.cancel_arg = cancel_arg; err = got_fileindex_diff_tree(fileindex, tree, relpath, entry_name, repo, &diff_cb, &arg); done: if (tree) got_object_tree_close(tree); if (commit) got_object_commit_close(commit); return err; } const struct got_error * got_worktree_checkout_files(struct got_worktree *worktree, struct got_pathlist_head *paths, struct got_repository *repo, got_worktree_checkout_cb progress_cb, void *progress_arg, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL, *sync_err, *unlockerr; struct got_commit_object *commit = NULL; struct got_tree_object *tree = NULL; struct got_fileindex *fileindex = NULL; char *fileindex_path = NULL; struct got_pathlist_entry *pe; struct tree_path_data { STAILQ_ENTRY(tree_path_data) entry; struct got_object_id *tree_id; int entry_type; char *relpath; char *entry_name; } *tpd = NULL; STAILQ_HEAD(tree_paths, tree_path_data) tree_paths; STAILQ_INIT(&tree_paths); err = lock_worktree(worktree, LOCK_EX); if (err) return err; err = got_object_open_as_commit(&commit, repo, worktree->base_commit_id); if (err) goto done; /* Map all specified paths to in-repository trees. */ RB_FOREACH(pe, got_pathlist_head, paths) { tpd = malloc(sizeof(*tpd)); if (tpd == NULL) { err = got_error_from_errno("malloc"); goto done; } err = find_tree_entry_for_checkout(&tpd->entry_type, &tpd->relpath, &tpd->tree_id, pe->path, commit, worktree, repo); if (err) { free(tpd); goto done; } if (tpd->entry_type == GOT_OBJ_TYPE_BLOB) { err = got_path_basename(&tpd->entry_name, pe->path); if (err) { free(tpd->relpath); free(tpd->tree_id); free(tpd); goto done; } } else tpd->entry_name = NULL; STAILQ_INSERT_TAIL(&tree_paths, tpd, entry); } /* * Read the file index. * Checking out files is supposed to be an idempotent operation. * If the on-disk file index is incomplete we will try to complete it. */ err = open_fileindex(&fileindex, &fileindex_path, worktree, got_repo_get_object_format(repo)); if (err) goto done; tpd = STAILQ_FIRST(&tree_paths); RB_FOREACH(pe, got_pathlist_head, paths) { struct bump_base_commit_id_arg bbc_arg; err = checkout_files(worktree, fileindex, tpd->relpath, tpd->tree_id, tpd->entry_name, repo, progress_cb, progress_arg, cancel_cb, cancel_arg); if (err) break; bbc_arg.base_commit_id = worktree->base_commit_id; bbc_arg.entry_name = tpd->entry_name; bbc_arg.path = pe->path; bbc_arg.path_len = pe->path_len; bbc_arg.progress_cb = progress_cb; bbc_arg.progress_arg = progress_arg; err = got_fileindex_for_each_entry_safe(fileindex, bump_base_commit_id, &bbc_arg); if (err) break; tpd = STAILQ_NEXT(tpd, entry); } sync_err = sync_fileindex(fileindex, fileindex_path); if (sync_err && err == NULL) err = sync_err; done: free(fileindex_path); if (tree) got_object_tree_close(tree); if (commit) got_object_commit_close(commit); if (fileindex) got_fileindex_free(fileindex); while (!STAILQ_EMPTY(&tree_paths)) { tpd = STAILQ_FIRST(&tree_paths); STAILQ_REMOVE_HEAD(&tree_paths, entry); free(tpd->relpath); free(tpd->tree_id); free(tpd); } unlockerr = lock_worktree(worktree, LOCK_SH); if (unlockerr && err == NULL) err = unlockerr; return err; } static const struct got_error * add_file(struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_fileindex_entry *ie, const char *ondisk_path, const char *path2, struct got_blob_object *blob2, mode_t mode2, int restoring_missing_file, int reverting_versioned_file, int path_is_unversioned, int allow_bad_symlinks, struct got_repository *repo, got_worktree_checkout_cb progress_cb, void *progress_arg) { const struct got_error *err = NULL; int is_bad_symlink = 0; if (S_ISLNK(mode2)) { err = install_symlink(&is_bad_symlink, worktree, ondisk_path, path2, blob2, restoring_missing_file, reverting_versioned_file, path_is_unversioned, allow_bad_symlinks, repo, progress_cb, progress_arg); } else { err = install_blob(worktree, ondisk_path, path2, mode2, GOT_DEFAULT_FILE_MODE, blob2, restoring_missing_file, reverting_versioned_file, 0, path_is_unversioned, NULL, repo, progress_cb, progress_arg); } if (err) return err; if (ie == NULL) { /* Adding an unversioned file. */ err = got_fileindex_entry_alloc(&ie, path2); if (err) return err; err = got_fileindex_entry_update(ie, worktree->root_fd, path2, NULL, NULL, 1); if (err) { got_fileindex_entry_free(ie); return err; } err = got_fileindex_entry_add(fileindex, ie); if (err) { got_fileindex_entry_free(ie); return err; } } else { /* Re-adding a locally deleted file. */ err = got_fileindex_entry_update(ie, worktree->root_fd, path2, &ie->blob, worktree->base_commit_id, 0); if (err) return err; } if (is_bad_symlink) { got_fileindex_entry_filetype_set(ie, GOT_FILEIDX_MODE_BAD_SYMLINK); } return NULL; } struct merge_file_cb_arg { struct got_worktree *worktree; struct got_fileindex *fileindex; got_worktree_checkout_cb progress_cb; void *progress_arg; got_cancel_cb cancel_cb; void *cancel_arg; const char *label_orig; struct got_object_id *commit_id2; int allow_bad_symlinks; }; static const struct got_error * merge_file_cb(void *arg, struct got_blob_object *blob1, struct got_blob_object *blob2, FILE *f1, FILE *f2, struct got_object_id *id1, struct got_object_id *id2, const char *path1, const char *path2, mode_t mode1, mode_t mode2, struct got_repository *repo) { static const struct got_error *err = NULL; struct merge_file_cb_arg *a = arg; struct got_fileindex_entry *ie; char *ondisk_path = NULL; struct stat sb; unsigned char status; int local_changes_subsumed; FILE *f_orig = NULL, *f_deriv = NULL, *f_deriv2 = NULL; char *id_str = NULL, *label_deriv2 = NULL; struct got_object_id *id = NULL; if (blob1 && blob2) { ie = got_fileindex_entry_get(a->fileindex, path2, strlen(path2)); if (ie == NULL) return (*a->progress_cb)(a->progress_arg, GOT_STATUS_MISSING, path2); if (asprintf(&ondisk_path, "%s/%s", a->worktree->root_path, path2) == -1) return got_error_from_errno("asprintf"); err = get_file_status(&status, &sb, ie, ondisk_path, -1, NULL, repo); if (err) goto done; if (status == GOT_STATUS_DELETE) { err = (*a->progress_cb)(a->progress_arg, GOT_STATUS_MERGE, path2); goto done; } if (status != GOT_STATUS_NO_CHANGE && status != GOT_STATUS_MODIFY && status != GOT_STATUS_CONFLICT && status != GOT_STATUS_ADD) { err = (*a->progress_cb)(a->progress_arg, status, path2); goto done; } if (S_ISLNK(mode1) && S_ISLNK(mode2)) { char *link_target2; err = got_object_blob_read_to_str(&link_target2, blob2); if (err) goto done; err = merge_symlink(a->worktree, blob1, ondisk_path, path2, a->label_orig, link_target2, a->commit_id2, repo, a->progress_cb, a->progress_arg); free(link_target2); } else { int fd; f_orig = got_opentemp(); if (f_orig == NULL) { err = got_error_from_errno("got_opentemp"); goto done; } err = got_object_blob_dump_to_file(NULL, NULL, NULL, f_orig, blob1); if (err) goto done; f_deriv2 = got_opentemp(); if (f_deriv2 == NULL) goto done; err = got_object_blob_dump_to_file(NULL, NULL, NULL, f_deriv2, blob2); if (err) goto done; fd = open(ondisk_path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (fd == -1) { err = got_error_from_errno2("open", ondisk_path); goto done; } f_deriv = fdopen(fd, "r"); if (f_deriv == NULL) { err = got_error_from_errno2("fdopen", ondisk_path); close(fd); goto done; } err = got_object_id_str(&id_str, a->commit_id2); if (err) goto done; if (asprintf(&label_deriv2, "%s: commit %s", GOT_MERGE_LABEL_MERGED, id_str) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = merge_file(&local_changes_subsumed, a->worktree, f_orig, f_deriv, f_deriv2, ondisk_path, path2, mode2, a->label_orig, NULL, label_deriv2, GOT_DIFF_ALGORITHM_PATIENCE, repo, a->progress_cb, a->progress_arg); } } else if (blob1) { ie = got_fileindex_entry_get(a->fileindex, path1, strlen(path1)); if (ie == NULL) return (*a->progress_cb)(a->progress_arg, GOT_STATUS_MISSING, path1); if (asprintf(&ondisk_path, "%s/%s", a->worktree->root_path, path1) == -1) return got_error_from_errno("asprintf"); err = get_file_status(&status, &sb, ie, ondisk_path, -1, NULL, repo); if (err) goto done; switch (status) { case GOT_STATUS_NO_CHANGE: err = (*a->progress_cb)(a->progress_arg, GOT_STATUS_DELETE, path1); if (err) goto done; err = remove_ondisk_file(a->worktree->root_path, path1); if (err) goto done; if (ie) got_fileindex_entry_mark_deleted_from_disk(ie); break; case GOT_STATUS_DELETE: case GOT_STATUS_MISSING: err = (*a->progress_cb)(a->progress_arg, GOT_STATUS_DELETE, path1); if (err) goto done; if (ie) got_fileindex_entry_mark_deleted_from_disk(ie); break; case GOT_STATUS_MODIFY: { FILE *blob1_f; off_t blob1_size; int obj_type; /* * Delete the file only if its content already * exists in the repository. */ err = got_object_blob_file_create(&id, &blob1_f, &blob1_size, path1, repo); if (err) goto done; if (fclose(blob1_f) == EOF) { err = got_error_from_errno("fclose"); goto done; } /* Implied existence check. */ err = got_object_get_type(&obj_type, repo, id); if (err) { if (err->code != GOT_ERR_NO_OBJ) goto done; err = (*a->progress_cb)(a->progress_arg, GOT_STATUS_CANNOT_DELETE, path1); goto done; } else if (obj_type != GOT_OBJ_TYPE_BLOB) { err = (*a->progress_cb)(a->progress_arg, GOT_STATUS_CANNOT_DELETE, path1); goto done; } err = (*a->progress_cb)(a->progress_arg, GOT_STATUS_DELETE, path1); if (err) goto done; err = remove_ondisk_file(a->worktree->root_path, path1); if (err) goto done; if (ie) got_fileindex_entry_mark_deleted_from_disk(ie); break; } case GOT_STATUS_ADD: { FILE *blob1_f; off_t blob1_size; /* * Delete the file only if its content already * exists in the repository. */ err = got_object_blob_file_create(&id, &blob1_f, &blob1_size, path1, repo); if (err) goto done; if (fclose(blob1_f) == EOF) { err = got_error_from_errno("fclose"); goto done; } if (got_object_id_cmp(id, id1) == 0) { err = (*a->progress_cb)(a->progress_arg, GOT_STATUS_DELETE, path1); if (err) goto done; err = remove_ondisk_file(a->worktree->root_path, path1); if (err) goto done; if (ie) got_fileindex_entry_remove(a->fileindex, ie); } else { err = (*a->progress_cb)(a->progress_arg, GOT_STATUS_CANNOT_DELETE, path1); } if (err) goto done; break; } case GOT_STATUS_CONFLICT: err = (*a->progress_cb)(a->progress_arg, GOT_STATUS_CANNOT_DELETE, path1); if (err) goto done; break; case GOT_STATUS_OBSTRUCTED: err = (*a->progress_cb)(a->progress_arg, status, path1); if (err) goto done; break; default: break; } } else if (blob2) { if (asprintf(&ondisk_path, "%s/%s", a->worktree->root_path, path2) == -1) return got_error_from_errno("asprintf"); ie = got_fileindex_entry_get(a->fileindex, path2, strlen(path2)); if (ie) { err = get_file_status(&status, &sb, ie, ondisk_path, -1, NULL, repo); if (err) goto done; if (status != GOT_STATUS_NO_CHANGE && status != GOT_STATUS_MODIFY && status != GOT_STATUS_CONFLICT && status != GOT_STATUS_ADD && status != GOT_STATUS_DELETE) { err = (*a->progress_cb)(a->progress_arg, status, path2); goto done; } if (S_ISLNK(mode2) && S_ISLNK(sb.st_mode)) { char *link_target2; err = got_object_blob_read_to_str(&link_target2, blob2); if (err) goto done; err = merge_symlink(a->worktree, NULL, ondisk_path, path2, a->label_orig, link_target2, a->commit_id2, repo, a->progress_cb, a->progress_arg); free(link_target2); } else if (S_ISREG(sb.st_mode)) { err = merge_blob(&local_changes_subsumed, a->worktree, NULL, ondisk_path, path2, sb.st_mode, a->label_orig, blob2, a->commit_id2, repo, a->progress_cb, a->progress_arg); } else if (status != GOT_STATUS_DELETE) { err = got_error_path(ondisk_path, GOT_ERR_FILE_OBSTRUCTED); } if (err) goto done; if (status == GOT_STATUS_DELETE) { /* Re-add file with content from new blob. */ err = add_file(a->worktree, a->fileindex, ie, ondisk_path, path2, blob2, mode2, 0, 0, 0, a->allow_bad_symlinks, repo, a->progress_cb, a->progress_arg); if (err) goto done; } } else { err = add_file(a->worktree, a->fileindex, NULL, ondisk_path, path2, blob2, mode2, 0, 0, 1, a->allow_bad_symlinks, repo, a->progress_cb, a->progress_arg); if (err) goto done; } } done: if (f_orig && fclose(f_orig) == EOF && err == NULL) err = got_error_from_errno("fclose"); if (f_deriv && fclose(f_deriv) == EOF && err == NULL) err = got_error_from_errno("fclose"); if (f_deriv2 && fclose(f_deriv2) == EOF && err == NULL) err = got_error_from_errno("fclose"); free(id_str); free(id); free(label_deriv2); free(ondisk_path); return err; } struct check_mixed_commits_args { struct got_worktree *worktree; got_cancel_cb cancel_cb; void *cancel_arg; }; static const struct got_error * check_mixed_commits(void *arg, struct got_fileindex_entry *ie) { const struct got_error *err = NULL; struct check_mixed_commits_args *a = arg; if (a->cancel_cb) { err = a->cancel_cb(a->cancel_arg); if (err) return err; } /* Reject merges into a work tree with mixed base commits. */ if (got_fileindex_entry_has_commit(ie) && got_object_id_cmp(&ie->commit, a->worktree->base_commit_id) != 0) return got_error(GOT_ERR_MIXED_COMMITS); return NULL; } const struct got_error * got_worktree_get_state(char *state, struct got_repository *repo, struct got_worktree *worktree, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err; struct got_object_id *base_id, *head_id = NULL; struct got_reference *head_ref; struct got_fileindex *fileindex = NULL; char *fileindex_path = NULL; struct check_mixed_commits_args cma; if (worktree == NULL) return got_error(GOT_ERR_NOT_WORKTREE); err = got_ref_open(&head_ref, repo, got_worktree_get_head_ref_name(worktree), 0); if (err) return err; err = got_ref_resolve(&head_id, repo, head_ref); if (err) goto done; *state = GOT_WORKTREE_STATE_UNKNOWN; base_id = got_worktree_get_base_commit_id(worktree); cma.worktree = worktree; cma.cancel_cb = cancel_cb; cma.cancel_arg = cancel_arg; if (got_object_id_cmp(base_id, head_id) == 0) { err = open_fileindex(&fileindex, &fileindex_path, worktree, got_repo_get_object_format(repo)); if (err) goto done; err = got_fileindex_for_each_entry_safe(fileindex, check_mixed_commits, &cma); if (err == NULL) *state = GOT_WORKTREE_STATE_UPTODATE; else if (err->code == GOT_ERR_MIXED_COMMITS) { *state = GOT_WORKTREE_STATE_OUTOFDATE; err = NULL; } } else *state = GOT_WORKTREE_STATE_OUTOFDATE; done: free(head_id); free(fileindex_path); got_ref_close(head_ref); if (fileindex != NULL) got_fileindex_free(fileindex); return err; } struct check_merge_conflicts_arg { struct got_worktree *worktree; struct got_fileindex *fileindex; struct got_repository *repo; }; static const struct got_error * check_merge_conflicts(void *arg, struct got_blob_object *blob1, struct got_blob_object *blob2, FILE *f1, FILE *f2, struct got_object_id *id1, struct got_object_id *id2, const char *path1, const char *path2, mode_t mode1, mode_t mode2, struct got_repository *repo) { const struct got_error *err = NULL; struct check_merge_conflicts_arg *a = arg; unsigned char status; struct stat sb; struct got_fileindex_entry *ie; const char *path = path2 ? path2 : path1; struct got_object_id *id = id2 ? id2 : id1; char *ondisk_path; if (id == NULL) return NULL; ie = got_fileindex_entry_get(a->fileindex, path, strlen(path)); if (ie == NULL) return NULL; if (asprintf(&ondisk_path, "%s/%s", a->worktree->root_path, ie->path) == -1) return got_error_from_errno("asprintf"); /* Reject merges into a work tree with conflicted files. */ err = get_file_status(&status, &sb, ie, ondisk_path, -1, NULL, a->repo); free(ondisk_path); if (err) return err; if (status == GOT_STATUS_CONFLICT) return got_error(GOT_ERR_CONFLICTS); return NULL; } static const struct got_error * merge_files(struct got_worktree *worktree, struct got_fileindex *fileindex, const char *fileindex_path, struct got_object_id *commit_id1, struct got_object_id *commit_id2, struct got_repository *repo, got_worktree_checkout_cb progress_cb, void *progress_arg, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL, *sync_err; struct got_object_id *tree_id1 = NULL, *tree_id2 = NULL; struct got_tree_object *tree1 = NULL, *tree2 = NULL; struct got_commit_object *commit1 = NULL, *commit2 = NULL; struct check_merge_conflicts_arg cmc_arg; struct merge_file_cb_arg arg; char *label_orig = NULL; FILE *f1 = NULL, *f2 = NULL; int fd1 = -1, fd2 = -1; if (commit_id1) { err = got_object_open_as_commit(&commit1, repo, commit_id1); if (err) goto done; err = got_object_id_by_path(&tree_id1, repo, commit1, worktree->path_prefix); if (err && err->code != GOT_ERR_NO_TREE_ENTRY) goto done; } if (tree_id1) { char *id_str; err = got_object_open_as_tree(&tree1, repo, tree_id1); if (err) goto done; err = got_object_id_str(&id_str, commit_id1); if (err) goto done; if (asprintf(&label_orig, "%s: commit %s", GOT_MERGE_LABEL_BASE, id_str) == -1) { err = got_error_from_errno("asprintf"); free(id_str); goto done; } free(id_str); f1 = got_opentemp(); if (f1 == NULL) { err = got_error_from_errno("got_opentemp"); goto done; } } err = got_object_open_as_commit(&commit2, repo, commit_id2); if (err) goto done; err = got_object_id_by_path(&tree_id2, repo, commit2, worktree->path_prefix); if (err) goto done; err = got_object_open_as_tree(&tree2, repo, tree_id2); if (err) goto done; f2 = got_opentemp(); if (f2 == NULL) { err = got_error_from_errno("got_opentemp"); goto done; } fd1 = got_opentempfd(); if (fd1 == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } fd2 = got_opentempfd(); if (fd2 == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } cmc_arg.worktree = worktree; cmc_arg.fileindex = fileindex; cmc_arg.repo = repo; err = got_diff_tree(tree1, tree2, f1, f2, fd1, fd2, "", "", repo, check_merge_conflicts, &cmc_arg, 0); if (err) goto done; arg.worktree = worktree; arg.fileindex = fileindex; arg.progress_cb = progress_cb; arg.progress_arg = progress_arg; arg.cancel_cb = cancel_cb; arg.cancel_arg = cancel_arg; arg.label_orig = label_orig; arg.commit_id2 = commit_id2; arg.allow_bad_symlinks = 1; /* preserve bad symlinks across merges */ err = got_diff_tree(tree1, tree2, f1, f2, fd1, fd2, "", "", repo, merge_file_cb, &arg, 1); sync_err = sync_fileindex(fileindex, fileindex_path); if (sync_err && err == NULL) err = sync_err; done: if (commit1) got_object_commit_close(commit1); if (commit2) got_object_commit_close(commit2); if (tree1) got_object_tree_close(tree1); if (tree2) got_object_tree_close(tree2); if (f1 && fclose(f1) == EOF && err == NULL) err = got_error_from_errno("fclose"); if (f2 && fclose(f2) == EOF && err == NULL) err = got_error_from_errno("fclose"); if (fd1 != -1 && close(fd1) == -1 && err == NULL) err = got_error_from_errno("close"); if (fd2 != -1 && close(fd2) == -1 && err == NULL) err = got_error_from_errno("close"); free(label_orig); return err; } const struct got_error * got_worktree_merge_files(struct got_worktree *worktree, struct got_object_id *commit_id1, struct got_object_id *commit_id2, struct got_repository *repo, got_worktree_checkout_cb progress_cb, void *progress_arg, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err, *unlockerr; char *fileindex_path = NULL; struct got_fileindex *fileindex = NULL; struct check_mixed_commits_args cma; err = lock_worktree(worktree, LOCK_EX); if (err) return err; err = open_fileindex(&fileindex, &fileindex_path, worktree, got_repo_get_object_format(repo)); if (err) goto done; cma.worktree = worktree; cma.cancel_cb = cancel_cb; cma.cancel_arg = cancel_arg; err = got_fileindex_for_each_entry_safe(fileindex, check_mixed_commits, &cma); if (err) goto done; err = merge_files(worktree, fileindex, fileindex_path, commit_id1, commit_id2, repo, progress_cb, progress_arg, cancel_cb, cancel_arg); done: if (fileindex) got_fileindex_free(fileindex); free(fileindex_path); unlockerr = lock_worktree(worktree, LOCK_SH); if (unlockerr && err == NULL) err = unlockerr; return err; } struct diff_dir_cb_arg { struct got_fileindex *fileindex; struct got_worktree *worktree; const char *status_path; size_t status_path_len; struct got_repository *repo; got_worktree_status_cb status_cb; void *status_arg; got_cancel_cb cancel_cb; void *cancel_arg; /* A pathlist containing per-directory pathlists of ignore patterns. */ struct got_pathlist_head *ignores; int report_unchanged; int no_ignores; }; static const struct got_error * report_file_status(struct got_fileindex_entry *ie, const char *abspath, int dirfd, const char *de_name, got_worktree_status_cb status_cb, void *status_arg, struct got_repository *repo, int report_unchanged) { const struct got_error *err = NULL; unsigned char status = GOT_STATUS_NO_CHANGE; unsigned char staged_status; struct stat sb; struct got_object_id blob_id, commit_id, staged_blob_id; struct got_object_id *blob_idp = NULL, *commit_idp = NULL; struct got_object_id *staged_blob_idp = NULL; staged_status = get_staged_status(ie); err = get_file_status(&status, &sb, ie, abspath, dirfd, de_name, repo); if (err) return err; if (status == GOT_STATUS_NO_CHANGE && staged_status == GOT_STATUS_NO_CHANGE && !report_unchanged) return NULL; if (got_fileindex_entry_has_blob(ie)) blob_idp = got_fileindex_entry_get_blob_id(&blob_id, ie); if (got_fileindex_entry_has_commit(ie)) commit_idp = got_fileindex_entry_get_commit_id(&commit_id, ie); if (staged_status == GOT_STATUS_ADD || staged_status == GOT_STATUS_MODIFY) { staged_blob_idp = got_fileindex_entry_get_staged_blob_id( &staged_blob_id, ie); } return (*status_cb)(status_arg, status, staged_status, ie->path, blob_idp, staged_blob_idp, commit_idp, dirfd, de_name); } static const struct got_error * status_old_new(void *arg, struct got_fileindex_entry *ie, struct dirent *de, const char *parent_path, int dirfd) { const struct got_error *err = NULL; struct diff_dir_cb_arg *a = arg; char *abspath; if (a->cancel_cb) { err = a->cancel_cb(a->cancel_arg); if (err) return err; } if (got_path_cmp(parent_path, a->status_path, strlen(parent_path), a->status_path_len) != 0 && !got_path_is_child(parent_path, a->status_path, a->status_path_len)) return NULL; if (parent_path[0]) { if (asprintf(&abspath, "%s/%s/%s", a->worktree->root_path, parent_path, de->d_name) == -1) return got_error_from_errno("asprintf"); } else { if (asprintf(&abspath, "%s/%s", a->worktree->root_path, de->d_name) == -1) return got_error_from_errno("asprintf"); } err = report_file_status(ie, abspath, dirfd, de->d_name, a->status_cb, a->status_arg, a->repo, a->report_unchanged); free(abspath); return err; } static const struct got_error * status_old(void *arg, struct got_fileindex_entry *ie, const char *parent_path) { const struct got_error *err; struct diff_dir_cb_arg *a = arg; struct got_object_id blob_id, commit_id; unsigned char status; if (a->cancel_cb) { err = a->cancel_cb(a->cancel_arg); if (err) return err; } if (!got_path_is_child(ie->path, a->status_path, a->status_path_len)) return NULL; got_fileindex_entry_get_blob_id(&blob_id, ie); got_fileindex_entry_get_commit_id(&commit_id, ie); if (got_fileindex_entry_has_file_on_disk(ie)) status = GOT_STATUS_MISSING; else status = GOT_STATUS_DELETE; return (*a->status_cb)(a->status_arg, status, get_staged_status(ie), ie->path, &blob_id, NULL, &commit_id, -1, NULL); } static void free_ignores(struct got_pathlist_head *ignores) { struct got_pathlist_entry *pe; RB_FOREACH(pe, got_pathlist_head, ignores) { struct got_pathlist_head *ignorelist = pe->data; got_pathlist_free(ignorelist, GOT_PATHLIST_FREE_PATH); } got_pathlist_free(ignores, GOT_PATHLIST_FREE_ALL); } static const struct got_error * read_ignores(struct got_pathlist_head *ignores, const char *path, FILE *f) { const struct got_error *err = NULL; struct got_pathlist_entry *pe = NULL; struct got_pathlist_head *ignorelist; char *line = NULL, *pattern, *dirpath = NULL; size_t linesize = 0; ssize_t linelen; ignorelist = calloc(1, sizeof(*ignorelist)); if (ignorelist == NULL) return got_error_from_errno("calloc"); RB_INIT(ignorelist); while ((linelen = getline(&line, &linesize, f)) != -1) { if (linelen > 0 && line[linelen - 1] == '\n') line[linelen - 1] = '\0'; /* Skip blank lines. */ if (line[0] == '\0') continue; /* Git's ignores may contain comments. */ if (line[0] == '#') continue; /* Git's negated patterns are not (yet?) supported. */ if (line[0] == '!') continue; if (asprintf(&pattern, "%s%s%s", path, path[0] ? "/" : "", line) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = got_pathlist_insert(NULL, ignorelist, pattern, NULL); if (err) goto done; } if (ferror(f)) { err = got_error_from_errno("getline"); goto done; } dirpath = strdup(path); if (dirpath == NULL) { err = got_error_from_errno("strdup"); goto done; } err = got_pathlist_insert(&pe, ignores, dirpath, ignorelist); done: free(line); if (err || pe == NULL) { free(dirpath); got_pathlist_free(ignorelist, GOT_PATHLIST_FREE_PATH); free(ignorelist); } return err; } static int match_path(const char *pattern, size_t pattern_len, const char *path, int flags) { char buf[PATH_MAX]; /* * Trailing slashes signify directories. * Append a * to make such patterns conform to fnmatch rules. */ if (pattern_len > 0 && pattern[pattern_len - 1] == '/') { if (snprintf(buf, sizeof(buf), "%s*", pattern) >= sizeof(buf)) return FNM_NOMATCH; /* XXX */ return fnmatch(buf, path, flags); } return fnmatch(pattern, path, flags); } static int match_ignores(struct got_pathlist_head *ignores, const char *path) { struct got_pathlist_entry *pe; /* Handle patterns which match in all directories. */ RB_FOREACH(pe, got_pathlist_head, ignores) { struct got_pathlist_head *ignorelist = pe->data; struct got_pathlist_entry *pi; RB_FOREACH(pi, got_pathlist_head, ignorelist) { const char *p; if (pi->path_len < 3 || strncmp(pi->path, "**/", 3) != 0) continue; p = path; while (*p) { if (match_path(pi->path + 3, pi->path_len - 3, p, FNM_PATHNAME | FNM_LEADING_DIR)) { /* Retry in next directory. */ while (*p && *p != '/') p++; while (*p == '/') p++; continue; } return 1; } } } /* * The ignores pathlist contains ignore lists from children before * parents, so we can find the most specific ignorelist by walking * ignores backwards. */ pe = RB_MAX(got_pathlist_head, ignores); while (pe) { if (got_path_is_child(path, pe->path, pe->path_len)) { struct got_pathlist_head *ignorelist = pe->data; struct got_pathlist_entry *pi; RB_FOREACH(pi, got_pathlist_head, ignorelist) { int flags = FNM_LEADING_DIR; if (strstr(pi->path, "/**/") == NULL) flags |= FNM_PATHNAME; if (match_path(pi->path, pi->path_len, path, flags)) continue; return 1; } } pe = RB_PREV(got_pathlist_head, ignores, pe); } return 0; } static const struct got_error * add_ignores(struct got_pathlist_head *ignores, const char *root_path, const char *path, int dirfd, const char *ignores_filename) { const struct got_error *err = NULL; char *ignorespath; int fd = -1; FILE *ignoresfile = NULL; if (asprintf(&ignorespath, "%s/%s%s%s", root_path, path, path[0] ? "/" : "", ignores_filename) == -1) return got_error_from_errno("asprintf"); if (dirfd != -1) { fd = openat(dirfd, ignores_filename, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (fd == -1) { if (errno != ENOENT && errno != EACCES) err = got_error_from_errno2("openat", ignorespath); } else { ignoresfile = fdopen(fd, "r"); if (ignoresfile == NULL) err = got_error_from_errno2("fdopen", ignorespath); else { fd = -1; err = read_ignores(ignores, path, ignoresfile); } } } else { ignoresfile = fopen(ignorespath, "re"); if (ignoresfile == NULL) { if (errno != ENOENT && errno != EACCES) err = got_error_from_errno2("fopen", ignorespath); } else err = read_ignores(ignores, path, ignoresfile); } if (ignoresfile && fclose(ignoresfile) == EOF && err == NULL) err = got_error_from_errno2("fclose", path); if (fd != -1 && close(fd) == -1 && err == NULL) err = got_error_from_errno2("close", path); free(ignorespath); return err; } static const struct got_error * status_new(int *ignore, void *arg, struct dirent *de, const char *parent_path, int dirfd) { const struct got_error *err = NULL; struct diff_dir_cb_arg *a = arg; char *path = NULL; if (ignore != NULL) *ignore = 0; if (a->cancel_cb) { err = a->cancel_cb(a->cancel_arg); if (err) return err; } if (parent_path[0]) { if (asprintf(&path, "%s/%s", parent_path, de->d_name) == -1) return got_error_from_errno("asprintf"); } else { path = de->d_name; } if (de->d_type == DT_DIR) { if (!a->no_ignores && ignore != NULL && match_ignores(a->ignores, path)) *ignore = 1; } else if (!match_ignores(a->ignores, path) && got_path_is_child(path, a->status_path, a->status_path_len)) err = (*a->status_cb)(a->status_arg, GOT_STATUS_UNVERSIONED, GOT_STATUS_NO_CHANGE, path, NULL, NULL, NULL, -1, NULL); if (parent_path[0]) free(path); return err; } static const struct got_error * status_traverse(void *arg, const char *path, int dirfd) { const struct got_error *err = NULL; struct diff_dir_cb_arg *a = arg; if (a->no_ignores) return NULL; err = add_ignores(a->ignores, a->worktree->root_path, path, dirfd, ".cvsignore"); if (err) return err; err = add_ignores(a->ignores, a->worktree->root_path, path, dirfd, ".gitignore"); return err; } static const struct got_error * report_single_file_status(const char *path, const char *ondisk_path, struct got_fileindex *fileindex, got_worktree_status_cb status_cb, void *status_arg, struct got_repository *repo, int report_unchanged, struct got_pathlist_head *ignores, int no_ignores) { struct got_fileindex_entry *ie; struct stat sb; ie = got_fileindex_entry_get(fileindex, path, strlen(path)); if (ie) return report_file_status(ie, ondisk_path, -1, NULL, status_cb, status_arg, repo, report_unchanged); if (lstat(ondisk_path, &sb) == -1) { if (errno != ENOENT) return got_error_from_errno2("lstat", ondisk_path); return (*status_cb)(status_arg, GOT_STATUS_NONEXISTENT, GOT_STATUS_NO_CHANGE, path, NULL, NULL, NULL, -1, NULL); } if (!no_ignores && match_ignores(ignores, path)) return NULL; if (S_ISREG(sb.st_mode) || S_ISLNK(sb.st_mode)) return (*status_cb)(status_arg, GOT_STATUS_UNVERSIONED, GOT_STATUS_NO_CHANGE, path, NULL, NULL, NULL, -1, NULL); return NULL; } static const struct got_error * add_ignores_from_parent_paths(struct got_pathlist_head *ignores, const char *root_path, const char *path) { const struct got_error *err; char *parent_path, *next_parent_path = NULL; err = add_ignores(ignores, root_path, "", -1, ".cvsignore"); if (err) return err; err = add_ignores(ignores, root_path, "", -1, ".gitignore"); if (err) return err; err = got_path_dirname(&parent_path, path); if (err) { if (err->code == GOT_ERR_BAD_PATH) return NULL; /* cannot traverse parent */ return err; } for (;;) { err = add_ignores(ignores, root_path, parent_path, -1, ".cvsignore"); if (err) break; err = add_ignores(ignores, root_path, parent_path, -1, ".gitignore"); if (err) break; err = got_path_dirname(&next_parent_path, parent_path); if (err) { if (err->code == GOT_ERR_BAD_PATH) err = NULL; /* traversed everything */ break; } if (got_path_is_root_dir(parent_path)) break; free(parent_path); parent_path = next_parent_path; next_parent_path = NULL; } free(parent_path); free(next_parent_path); return err; } struct find_missing_children_args { const char *parent_path; size_t parent_len; struct got_pathlist_head *children; got_cancel_cb cancel_cb; void *cancel_arg; }; static const struct got_error * find_missing_children(void *arg, struct got_fileindex_entry *ie) { const struct got_error *err = NULL; struct find_missing_children_args *a = arg; if (a->cancel_cb) { err = a->cancel_cb(a->cancel_arg); if (err) return err; } if (got_path_is_child(ie->path, a->parent_path, a->parent_len)) err = got_pathlist_insert(NULL, a->children, ie->path, NULL); return err; } static const struct got_error * report_children(struct got_pathlist_head *children, struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_repository *repo, int is_root_dir, int report_unchanged, struct got_pathlist_head *ignores, int no_ignores, got_worktree_status_cb status_cb, void *status_arg, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; struct got_pathlist_entry *pe; char *ondisk_path = NULL; RB_FOREACH(pe, got_pathlist_head, children) { if (cancel_cb) { err = cancel_cb(cancel_arg); if (err) break; } if (asprintf(&ondisk_path, "%s%s%s", worktree->root_path, !is_root_dir ? "/" : "", pe->path) == -1) { err = got_error_from_errno("asprintf"); ondisk_path = NULL; break; } err = report_single_file_status(pe->path, ondisk_path, fileindex, status_cb, status_arg, repo, report_unchanged, ignores, no_ignores); if (err) break; free(ondisk_path); ondisk_path = NULL; } free(ondisk_path); return err; } static const struct got_error * worktree_status(struct got_worktree *worktree, const char *path, struct got_fileindex *fileindex, struct got_repository *repo, got_worktree_status_cb status_cb, void *status_arg, got_cancel_cb cancel_cb, void *cancel_arg, int no_ignores, int report_unchanged) { const struct got_error *err = NULL; int fd = -1; struct got_fileindex_diff_dir_cb fdiff_cb; struct diff_dir_cb_arg arg; char *ondisk_path = NULL; struct got_pathlist_head ignores, missing_children; struct got_fileindex_entry *ie; RB_INIT(&ignores); RB_INIT(&missing_children); if (asprintf(&ondisk_path, "%s%s%s", worktree->root_path, path[0] ? "/" : "", path) == -1) return got_error_from_errno("asprintf"); ie = got_fileindex_entry_get(fileindex, path, strlen(path)); if (ie) { err = report_single_file_status(path, ondisk_path, fileindex, status_cb, status_arg, repo, report_unchanged, &ignores, no_ignores); goto done; } else { struct find_missing_children_args fmca; fmca.parent_path = path; fmca.parent_len = strlen(path); fmca.children = &missing_children; fmca.cancel_cb = cancel_cb; fmca.cancel_arg = cancel_arg; err = got_fileindex_for_each_entry_safe(fileindex, find_missing_children, &fmca); if (err) goto done; } fd = open(ondisk_path, O_RDONLY | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC); if (fd == -1) { if (errno != ENOTDIR && errno != ENOENT && errno != EACCES && !got_err_open_nofollow_on_symlink()) err = got_error_from_errno2("open", ondisk_path); else { if (!no_ignores) { err = add_ignores_from_parent_paths(&ignores, worktree->root_path, ondisk_path); if (err) goto done; } if (RB_EMPTY(&missing_children)) { err = report_single_file_status(path, ondisk_path, fileindex, status_cb, status_arg, repo, report_unchanged, &ignores, no_ignores); if (err) goto done; } else { err = report_children(&missing_children, worktree, fileindex, repo, (path[0] == '\0'), report_unchanged, &ignores, no_ignores, status_cb, status_arg, cancel_cb, cancel_arg); if (err) goto done; } } } else { fdiff_cb.diff_old_new = status_old_new; fdiff_cb.diff_old = status_old; fdiff_cb.diff_new = status_new; fdiff_cb.diff_traverse = status_traverse; arg.fileindex = fileindex; arg.worktree = worktree; arg.status_path = path; arg.status_path_len = strlen(path); arg.repo = repo; arg.status_cb = status_cb; arg.status_arg = status_arg; arg.cancel_cb = cancel_cb; arg.cancel_arg = cancel_arg; arg.report_unchanged = report_unchanged; arg.no_ignores = no_ignores; if (!no_ignores) { err = add_ignores_from_parent_paths(&ignores, worktree->root_path, path); if (err) goto done; } arg.ignores = &ignores; err = got_fileindex_diff_dir(fileindex, fd, worktree->root_path, path, repo, &fdiff_cb, &arg); } done: free_ignores(&ignores); got_pathlist_free(&missing_children, GOT_PATHLIST_FREE_NONE); if (fd != -1 && close(fd) == -1 && err == NULL) err = got_error_from_errno("close"); free(ondisk_path); return err; } const struct got_error * got_worktree_status(struct got_worktree *worktree, struct got_pathlist_head *paths, struct got_repository *repo, int no_ignores, got_worktree_status_cb status_cb, void *status_arg, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL; char *fileindex_path = NULL; struct got_fileindex *fileindex = NULL; struct got_pathlist_entry *pe; err = open_fileindex(&fileindex, &fileindex_path, worktree, got_repo_get_object_format(repo)); if (err) return err; RB_FOREACH(pe, got_pathlist_head, paths) { err = worktree_status(worktree, pe->path, fileindex, repo, status_cb, status_arg, cancel_cb, cancel_arg, no_ignores, 0); if (err) break; } free(fileindex_path); got_fileindex_free(fileindex); return err; } const struct got_error * got_worktree_resolve_path(char **wt_path, struct got_worktree *worktree, const char *arg) { const struct got_error *err = NULL; char *resolved = NULL, *cwd = NULL, *path = NULL; size_t len; struct stat sb; char *abspath = NULL; char canonpath[PATH_MAX]; *wt_path = NULL; cwd = getcwd(NULL, 0); if (cwd == NULL) return got_error_from_errno("getcwd"); if (lstat(arg, &sb) == -1) { if (errno != ENOENT) { err = got_error_from_errno2("lstat", arg); goto done; } sb.st_mode = 0; } if (S_ISLNK(sb.st_mode)) { /* * We cannot use realpath(3) with symlinks since we want to * operate on the symlink itself. * But we can make the path absolute, assuming it is relative * to the current working directory, and then canonicalize it. */ if (!got_path_is_absolute(arg)) { if (asprintf(&abspath, "%s/%s", cwd, arg) == -1) { err = got_error_from_errno("asprintf"); goto done; } } err = got_canonpath(abspath ? abspath : arg, canonpath, sizeof(canonpath)); if (err) goto done; resolved = strdup(canonpath); if (resolved == NULL) { err = got_error_from_errno("strdup"); goto done; } } else { resolved = realpath(arg, NULL); if (resolved == NULL) { if (errno != ENOENT) { err = got_error_from_errno2("realpath", arg); goto done; } if (asprintf(&abspath, "%s/%s", cwd, arg) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = got_canonpath(abspath, canonpath, sizeof(canonpath)); if (err) goto done; resolved = strdup(canonpath); if (resolved == NULL) { err = got_error_from_errno("strdup"); goto done; } } } if (strncmp(got_worktree_get_root_path(worktree), resolved, strlen(got_worktree_get_root_path(worktree)))) { err = got_error_path(resolved, GOT_ERR_BAD_PATH); goto done; } if (strlen(resolved) > strlen(got_worktree_get_root_path(worktree))) { err = got_path_skip_common_ancestor(&path, got_worktree_get_root_path(worktree), resolved); if (err) goto done; } else { path = strdup(""); if (path == NULL) { err = got_error_from_errno("strdup"); goto done; } } /* XXX status walk can't deal with trailing slash! */ len = strlen(path); while (len > 0 && path[len - 1] == '/') { path[len - 1] = '\0'; len--; } done: free(abspath); free(resolved); free(cwd); if (err == NULL) *wt_path = path; else free(path); return err; } struct schedule_addition_args { struct got_worktree *worktree; struct got_fileindex *fileindex; got_worktree_checkout_cb progress_cb; void *progress_arg; struct got_repository *repo; }; static int add_noop_status(unsigned char status) { return (status == GOT_STATUS_ADD || status == GOT_STATUS_MODIFY || status == GOT_STATUS_CONFLICT || status == GOT_STATUS_MODE_CHANGE || status == GOT_STATUS_NO_CHANGE); } static const struct got_error * schedule_addition(void *arg, unsigned char status, unsigned char staged_status, const char *relpath, struct got_object_id *blob_id, struct got_object_id *staged_blob_id, struct got_object_id *commit_id, int dirfd, const char *de_name) { struct schedule_addition_args *a = arg; const struct got_error *err = NULL; struct got_fileindex_entry *ie; struct stat sb; char *ondisk_path; if (asprintf(&ondisk_path, "%s/%s", a->worktree->root_path, relpath) == -1) return got_error_from_errno("asprintf"); ie = got_fileindex_entry_get(a->fileindex, relpath, strlen(relpath)); if (ie) { err = get_file_status(&status, &sb, ie, ondisk_path, dirfd, de_name, a->repo); if (err) goto done; /* Re-adding an existing entry is a no-op. */ if (staged_status == GOT_STATUS_NO_CHANGE && add_noop_status(status)) goto done; err = got_error_path(relpath, GOT_ERR_FILE_STATUS); if (err) goto done; } if (status != GOT_STATUS_UNVERSIONED) { if (status == GOT_STATUS_NONEXISTENT) err = got_error_set_errno(ENOENT, ondisk_path); else err = got_error_path(ondisk_path, GOT_ERR_FILE_STATUS); goto done; } err = got_fileindex_entry_alloc(&ie, relpath); if (err) goto done; err = got_fileindex_entry_update(ie, a->worktree->root_fd, relpath, NULL, NULL, 1); if (err) { got_fileindex_entry_free(ie); goto done; } err = got_fileindex_entry_add(a->fileindex, ie); if (err) { got_fileindex_entry_free(ie); goto done; } done: free(ondisk_path); if (err) return err; if (staged_status == GOT_STATUS_NO_CHANGE && add_noop_status(status)) return NULL; return (*a->progress_cb)(a->progress_arg, GOT_STATUS_ADD, relpath); } const struct got_error * got_worktree_schedule_add(struct got_worktree *worktree, struct got_pathlist_head *paths, got_worktree_checkout_cb progress_cb, void *progress_arg, struct got_repository *repo, int no_ignores) { struct got_fileindex *fileindex = NULL; char *fileindex_path = NULL; const struct got_error *err = NULL, *sync_err, *unlockerr; struct got_pathlist_entry *pe; struct schedule_addition_args saa; err = lock_worktree(worktree, LOCK_EX); if (err) return err; err = open_fileindex(&fileindex, &fileindex_path, worktree, got_repo_get_object_format(repo)); if (err) goto done; saa.worktree = worktree; saa.fileindex = fileindex; saa.progress_cb = progress_cb; saa.progress_arg = progress_arg; saa.repo = repo; RB_FOREACH(pe, got_pathlist_head, paths) { err = worktree_status(worktree, pe->path, fileindex, repo, schedule_addition, &saa, NULL, NULL, no_ignores, 0); if (err) break; } sync_err = sync_fileindex(fileindex, fileindex_path); if (sync_err && err == NULL) err = sync_err; done: free(fileindex_path); if (fileindex) got_fileindex_free(fileindex); unlockerr = lock_worktree(worktree, LOCK_SH); if (unlockerr && err == NULL) err = unlockerr; return err; } struct schedule_deletion_args { struct got_worktree *worktree; struct got_fileindex *fileindex; got_worktree_delete_cb progress_cb; void *progress_arg; struct got_repository *repo; int delete_local_mods; int keep_on_disk; int ignore_missing_paths; const char *status_path; size_t status_path_len; const char *status_codes; }; static const struct got_error * schedule_for_deletion(void *arg, unsigned char status, unsigned char staged_status, const char *relpath, struct got_object_id *blob_id, struct got_object_id *staged_blob_id, struct got_object_id *commit_id, int dirfd, const char *de_name) { struct schedule_deletion_args *a = arg; const struct got_error *err = NULL; struct got_fileindex_entry *ie = NULL; struct stat sb; char *ondisk_path; if (status == GOT_STATUS_NONEXISTENT) { if (a->ignore_missing_paths) return NULL; return got_error_set_errno(ENOENT, relpath); } ie = got_fileindex_entry_get(a->fileindex, relpath, strlen(relpath)); if (ie == NULL) return got_error_path(relpath, GOT_ERR_FILE_STATUS); staged_status = get_staged_status(ie); if (staged_status != GOT_STATUS_NO_CHANGE) { if (staged_status == GOT_STATUS_DELETE) return NULL; return got_error_path(relpath, GOT_ERR_FILE_STAGED); } if (asprintf(&ondisk_path, "%s/%s", a->worktree->root_path, relpath) == -1) return got_error_from_errno("asprintf"); err = get_file_status(&status, &sb, ie, ondisk_path, dirfd, de_name, a->repo); if (err) goto done; if (a->status_codes) { size_t ncodes = strlen(a->status_codes); int i; for (i = 0; i < ncodes ; i++) { if (status == a->status_codes[i]) break; } if (i == ncodes) { /* Do not delete files in non-matching status. */ free(ondisk_path); return NULL; } if (a->status_codes[i] != GOT_STATUS_MODIFY && a->status_codes[i] != GOT_STATUS_MISSING) { static char msg[64]; snprintf(msg, sizeof(msg), "invalid status code '%c'", a->status_codes[i]); err = got_error_msg(GOT_ERR_FILE_STATUS, msg); goto done; } } if (status != GOT_STATUS_NO_CHANGE) { if (status == GOT_STATUS_DELETE) goto done; if (status == GOT_STATUS_MODIFY && !a->delete_local_mods) { err = got_error_path(relpath, GOT_ERR_FILE_MODIFIED); goto done; } if (status == GOT_STATUS_MISSING && !a->ignore_missing_paths) { err = got_error_set_errno(ENOENT, relpath); goto done; } if (status != GOT_STATUS_MODIFY && status != GOT_STATUS_MISSING) { err = got_error_path(relpath, GOT_ERR_FILE_STATUS); goto done; } } if (!a->keep_on_disk && status != GOT_STATUS_MISSING) { size_t root_len; if (dirfd != -1) { if (unlinkat(dirfd, de_name, 0) == -1) { err = got_error_from_errno2("unlinkat", ondisk_path); goto done; } } else if (unlink(ondisk_path) == -1) { err = got_error_from_errno2("unlink", ondisk_path); goto done; } root_len = strlen(a->worktree->root_path); do { char *parent; err = got_path_dirname(&parent, ondisk_path); if (err) goto done; free(ondisk_path); ondisk_path = parent; if (got_path_cmp(ondisk_path, a->status_path, strlen(ondisk_path), a->status_path_len) != 0 && !got_path_is_child(ondisk_path, a->status_path, a->status_path_len)) break; if (rmdir(ondisk_path) == -1) { if (errno != ENOTEMPTY) err = got_error_from_errno2("rmdir", ondisk_path); break; } } while (got_path_cmp(ondisk_path, a->worktree->root_path, strlen(ondisk_path), root_len) != 0); } if (got_fileindex_entry_has_blob(ie)) got_fileindex_entry_mark_deleted_from_disk(ie); else got_fileindex_entry_remove(a->fileindex, ie); done: free(ondisk_path); if (err) return err; if (status == GOT_STATUS_DELETE) return NULL; return (*a->progress_cb)(a->progress_arg, GOT_STATUS_DELETE, staged_status, relpath); } const struct got_error * got_worktree_schedule_delete(struct got_worktree *worktree, struct got_pathlist_head *paths, int delete_local_mods, const char *status_codes, got_worktree_delete_cb progress_cb, void *progress_arg, struct got_repository *repo, int keep_on_disk, int ignore_missing_paths) { struct got_fileindex *fileindex = NULL; char *fileindex_path = NULL; const struct got_error *err = NULL, *sync_err, *unlockerr; struct got_pathlist_entry *pe; struct schedule_deletion_args sda; err = lock_worktree(worktree, LOCK_EX); if (err) return err; err = open_fileindex(&fileindex, &fileindex_path, worktree, got_repo_get_object_format(repo)); if (err) goto done; sda.worktree = worktree; sda.fileindex = fileindex; sda.progress_cb = progress_cb; sda.progress_arg = progress_arg; sda.repo = repo; sda.delete_local_mods = delete_local_mods; sda.keep_on_disk = keep_on_disk; sda.ignore_missing_paths = ignore_missing_paths; sda.status_codes = status_codes; RB_FOREACH(pe, got_pathlist_head, paths) { char *ondisk_status_path; if (asprintf(&ondisk_status_path, "%s%s%s", got_worktree_get_root_path(worktree), pe->path[0] == '\0' ? "" : "/", pe->path) == -1) { err = got_error_from_errno("asprintf"); goto done; } sda.status_path = ondisk_status_path; sda.status_path_len = strlen(ondisk_status_path); err = worktree_status(worktree, pe->path, fileindex, repo, schedule_for_deletion, &sda, NULL, NULL, 1, 1); free(ondisk_status_path); if (err) break; } sync_err = sync_fileindex(fileindex, fileindex_path); if (sync_err && err == NULL) err = sync_err; done: free(fileindex_path); if (fileindex) got_fileindex_free(fileindex); unlockerr = lock_worktree(worktree, LOCK_SH); if (unlockerr && err == NULL) err = unlockerr; return err; } static const struct got_error * copy_one_line(FILE *infile, FILE *outfile, FILE *rejectfile) { const struct got_error *err = NULL; char *line = NULL; size_t linesize = 0, n; ssize_t linelen; linelen = getline(&line, &linesize, infile); if (linelen == -1) { if (ferror(infile)) { err = got_error_from_errno("getline"); goto done; } return NULL; } if (outfile) { n = fwrite(line, 1, linelen, outfile); if (n != linelen) { err = got_ferror(outfile, GOT_ERR_IO); goto done; } } if (rejectfile) { n = fwrite(line, 1, linelen, rejectfile); if (n != linelen) err = got_ferror(rejectfile, GOT_ERR_IO); } done: free(line); return err; } static const struct got_error * skip_one_line(FILE *f) { char *line = NULL; size_t linesize = 0; ssize_t linelen; linelen = getline(&line, &linesize, f); if (linelen == -1) { if (ferror(f)) return got_error_from_errno("getline"); return NULL; } free(line); return NULL; } static const struct got_error * copy_change(FILE *f1, FILE *f2, int *line_cur1, int *line_cur2, int start_old, int end_old, int start_new, int end_new, FILE *outfile, FILE *rejectfile) { const struct got_error *err; /* Copy old file's lines leading up to patch. */ while (!feof(f1) && *line_cur1 < start_old) { err = copy_one_line(f1, outfile, NULL); if (err) return err; (*line_cur1)++; } /* Skip new file's lines leading up to patch. */ while (!feof(f2) && *line_cur2 < start_new) { if (rejectfile) err = copy_one_line(f2, NULL, rejectfile); else err = skip_one_line(f2); if (err) return err; (*line_cur2)++; } /* Copy patched lines. */ while (!feof(f2) && *line_cur2 <= end_new) { err = copy_one_line(f2, outfile, NULL); if (err) return err; (*line_cur2)++; } /* Skip over old file's replaced lines. */ while (!feof(f1) && *line_cur1 <= end_old) { if (rejectfile) err = copy_one_line(f1, NULL, rejectfile); else err = skip_one_line(f1); if (err) return err; (*line_cur1)++; } return NULL; } static const struct got_error * copy_remaining_content(FILE *f1, FILE *f2, int *line_cur1, int *line_cur2, FILE *outfile, FILE *rejectfile) { const struct got_error *err; if (outfile) { /* Copy old file's lines until EOF. */ while (!feof(f1)) { err = copy_one_line(f1, outfile, NULL); if (err) return err; (*line_cur1)++; } } if (rejectfile) { /* Copy new file's lines until EOF. */ while (!feof(f2)) { err = copy_one_line(f2, NULL, rejectfile); if (err) return err; (*line_cur2)++; } } return NULL; } static const struct got_error * accept_or_reject_binary_change(int *choice, const char *path, got_worktree_patch_cb patch_cb, void *patch_arg) { const struct got_error *err; FILE *f; *choice = GOT_PATCH_CHOICE_NONE; f = got_opentemp(); if (f == NULL) return got_error_from_errno("got_opentemp"); if (fprintf(f, "Binary files %s and %s differ\n", path, path) < 0) { err = got_error_msg(GOT_ERR_IO, "fprintf"); goto done; } if (fseeko(f, 0L, SEEK_SET) == -1) { err = got_error_from_errno("fseeko"); goto done; } err = (*patch_cb)(choice, patch_arg, GOT_STATUS_MODIFY, path, f, 1, 1); done: if (f != NULL && fclose(f) == EOF && err == NULL) err = got_error_from_errno("fclose"); return err; } static int diff_result_has_binary(struct diff_result *r) { return (r->left->atomizer_flags | r->right->atomizer_flags) & DIFF_ATOMIZER_FOUND_BINARY_DATA; } static const struct got_error * apply_or_reject_change(int *choice, int *nchunks_used, struct diff_result *diff_result, int n, const char *relpath, FILE *f1, FILE *f2, int *line_cur1, int *line_cur2, FILE *outfile, FILE *rejectfile, int changeno, int nchanges, got_worktree_patch_cb patch_cb, void *patch_arg) { const struct got_error *err = NULL; struct diff_chunk_context cc = {}; int start_old, end_old, start_new, end_new; FILE *hunkfile; struct diff_output_unidiff_state *diff_state; struct diff_input_info diff_info; int rc; *choice = GOT_PATCH_CHOICE_NONE; /* Get changed line numbers without context lines for copy_change(). */ diff_chunk_context_load_change(&cc, NULL, diff_result, n, 0); start_old = cc.left.start; end_old = cc.left.end; start_new = cc.right.start; end_new = cc.right.end; /* Get the same change with context lines for display. */ memset(&cc, 0, sizeof(cc)); diff_chunk_context_load_change(&cc, nchunks_used, diff_result, n, 3); memset(&diff_info, 0, sizeof(diff_info)); diff_info.left_path = relpath; diff_info.right_path = relpath; diff_state = diff_output_unidiff_state_alloc(); if (diff_state == NULL) return got_error_set_errno(ENOMEM, "diff_output_unidiff_state_alloc"); hunkfile = got_opentemp(); if (hunkfile == NULL) { err = got_error_from_errno("got_opentemp"); goto done; } rc = diff_output_unidiff_chunk(NULL, hunkfile, diff_state, &diff_info, diff_result, &cc); if (rc != DIFF_RC_OK) { err = got_error_set_errno(rc, "diff_output_unidiff_chunk"); goto done; } if (fseek(hunkfile, 0L, SEEK_SET) == -1) { err = got_ferror(hunkfile, GOT_ERR_IO); goto done; } err = (*patch_cb)(choice, patch_arg, GOT_STATUS_MODIFY, relpath, hunkfile, changeno, nchanges); if (err) goto done; switch (*choice) { case GOT_PATCH_CHOICE_YES: err = copy_change(f1, f2, line_cur1, line_cur2, start_old, end_old, start_new, end_new, outfile, rejectfile); break; case GOT_PATCH_CHOICE_NO: err = copy_change(f1, f2, line_cur1, line_cur2, start_old, end_old, start_new, end_new, rejectfile, outfile); break; case GOT_PATCH_CHOICE_QUIT: break; default: err = got_error(GOT_ERR_PATCH_CHOICE); break; } done: diff_output_unidiff_state_free(diff_state); if (hunkfile && fclose(hunkfile) == EOF && err == NULL) err = got_error_from_errno("fclose"); return err; } struct revert_file_args { struct got_worktree *worktree; struct got_fileindex *fileindex; got_worktree_checkout_cb progress_cb; void *progress_arg; got_worktree_patch_cb patch_cb; void *patch_arg; struct got_repository *repo; int unlink_added_files; struct got_pathlist_head *added_files_to_unlink; }; static const struct got_error * create_patched_content(char **path_outfile, int *confirm_binary_change, int reverse_patch, struct got_object_id *blob_id, const char *path2, int dirfd2, const char *de_name2, const char *relpath, struct got_repository *repo, got_worktree_patch_cb patch_cb, void *patch_arg) { const struct got_error *err, *free_err; struct got_blob_object *blob = NULL; FILE *f1 = NULL, *f2 = NULL, *outfile = NULL; int fd = -1, fd2 = -1; char link_target[PATH_MAX]; ssize_t link_len = 0; char *path1 = NULL, *id_str = NULL; struct stat sb2; struct got_diffreg_result *diffreg_result = NULL; int choice, line_cur1 = 1, line_cur2 = 1, have_content = 0; int i = 0, n = 0, nchunks_used = 0, nchanges = 0; *path_outfile = NULL; *confirm_binary_change = 0; err = got_object_id_str(&id_str, blob_id); if (err) return err; if (dirfd2 != -1) { fd2 = openat(dirfd2, de_name2, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (fd2 == -1) { if (!got_err_open_nofollow_on_symlink()) { err = got_error_from_errno2("openat", path2); goto done; } link_len = readlinkat(dirfd2, de_name2, link_target, sizeof(link_target)); if (link_len == -1) { return got_error_from_errno2("readlinkat", path2); } sb2.st_mode = S_IFLNK; sb2.st_size = link_len; } } else { fd2 = open(path2, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (fd2 == -1) { if (!got_err_open_nofollow_on_symlink()) { err = got_error_from_errno2("open", path2); goto done; } link_len = readlink(path2, link_target, sizeof(link_target)); if (link_len == -1) return got_error_from_errno2("readlink", path2); sb2.st_mode = S_IFLNK; sb2.st_size = link_len; } } if (fd2 != -1) { if (fstat(fd2, &sb2) == -1) { err = got_error_from_errno2("fstat", path2); goto done; } f2 = fdopen(fd2, "r"); if (f2 == NULL) { err = got_error_from_errno2("fdopen", path2); goto done; } fd2 = -1; } else { size_t n; f2 = got_opentemp(); if (f2 == NULL) { err = got_error_from_errno2("got_opentemp", path2); goto done; } n = fwrite(link_target, 1, link_len, f2); if (n != link_len) { err = got_ferror(f2, GOT_ERR_IO); goto done; } if (fflush(f2) == EOF) { err = got_error_from_errno("fflush"); goto done; } rewind(f2); } fd = got_opentempfd(); if (fd == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } err = got_object_open_as_blob(&blob, repo, blob_id, 8192, fd); if (err) goto done; err = got_opentemp_named(&path1, &f1, "got-patched-blob", ""); if (err) goto done; err = got_object_blob_dump_to_file(NULL, NULL, NULL, f1, blob); if (err) goto done; err = got_diff_files(&diffreg_result, f1, 1, id_str, f2, 1, path2, 3, 0, 1, NULL, GOT_DIFF_ALGORITHM_MYERS); if (err) goto done; if (diff_result_has_binary(diffreg_result->result)) { err = accept_or_reject_binary_change(&choice, relpath, patch_cb, patch_arg); if (err == NULL && choice == GOT_PATCH_CHOICE_YES) *confirm_binary_change = 1; goto done; } err = got_opentemp_named(path_outfile, &outfile, "got-patched-content", ""); if (err) goto done; if (fseek(f1, 0L, SEEK_SET) == -1) return got_ferror(f1, GOT_ERR_IO); if (fseek(f2, 0L, SEEK_SET) == -1) return got_ferror(f2, GOT_ERR_IO); /* Count the number of actual changes in the diff result. */ for (n = 0; n < diffreg_result->result->chunks.len; n += nchunks_used) { struct diff_chunk_context cc = {}; diff_chunk_context_load_change(&cc, &nchunks_used, diffreg_result->result, n, 0); nchanges++; } for (n = 0; n < diffreg_result->result->chunks.len; n += nchunks_used) { err = apply_or_reject_change(&choice, &nchunks_used, diffreg_result->result, n, relpath, f1, f2, &line_cur1, &line_cur2, reverse_patch ? NULL : outfile, reverse_patch ? outfile : NULL, ++i, nchanges, patch_cb, patch_arg); if (err) goto done; if (choice == GOT_PATCH_CHOICE_YES) have_content = 1; else if (choice == GOT_PATCH_CHOICE_QUIT) break; } if (have_content) { err = copy_remaining_content(f1, f2, &line_cur1, &line_cur2, reverse_patch ? NULL : outfile, reverse_patch ? outfile : NULL); if (err) goto done; if (!S_ISLNK(sb2.st_mode)) { mode_t mode; mode = apply_umask(sb2.st_mode); if (fchmod(fileno(outfile), mode) == -1) { err = got_error_from_errno2("fchmod", path2); goto done; } } } done: free(id_str); if (fd != -1 && close(fd) == -1 && err == NULL) err = got_error_from_errno("close"); if (blob) got_object_blob_close(blob); free_err = got_diffreg_result_free(diffreg_result); if (err == NULL) err = free_err; if (f1 && fclose(f1) == EOF && err == NULL) err = got_error_from_errno2("fclose", path1); if (f2 && fclose(f2) == EOF && err == NULL) err = got_error_from_errno2("fclose", path2); if (fd2 != -1 && close(fd2) == -1 && err == NULL) err = got_error_from_errno2("close", path2); if (outfile && fclose(outfile) == EOF && err == NULL) err = got_error_from_errno2("fclose", *path_outfile); if (path1 && unlink(path1) == -1 && err == NULL) err = got_error_from_errno2("unlink", path1); if (err || !have_content) { if (*path_outfile && unlink(*path_outfile) == -1 && err == NULL) err = got_error_from_errno2("unlink", *path_outfile); free(*path_outfile); *path_outfile = NULL; } free(path1); return err; } static const struct got_error * revert_file(void *arg, unsigned char status, unsigned char staged_status, const char *relpath, struct got_object_id *blob_id, struct got_object_id *staged_blob_id, struct got_object_id *commit_id, int dirfd, const char *de_name) { struct revert_file_args *a = arg; const struct got_error *err = NULL; char *parent_path = NULL; struct got_fileindex_entry *ie; struct got_commit_object *base_commit = NULL; struct got_tree_object *tree = NULL; struct got_object_id *tree_id = NULL; const struct got_tree_entry *te = NULL; char *tree_path = NULL, *te_name; char *ondisk_path = NULL, *path_content = NULL; struct got_blob_object *blob = NULL; int fd = -1; /* Reverting a staged deletion is a no-op. */ if (status == GOT_STATUS_DELETE && staged_status != GOT_STATUS_NO_CHANGE) return NULL; if (status == GOT_STATUS_UNVERSIONED) return (*a->progress_cb)(a->progress_arg, GOT_STATUS_UNVERSIONED, relpath); ie = got_fileindex_entry_get(a->fileindex, relpath, strlen(relpath)); if (ie == NULL) return got_error_path(relpath, GOT_ERR_BAD_PATH); /* Construct in-repository path of tree which contains this blob. */ err = got_path_dirname(&parent_path, ie->path); if (err) { if (err->code != GOT_ERR_BAD_PATH) goto done; parent_path = strdup("/"); if (parent_path == NULL) { err = got_error_from_errno("strdup"); goto done; } } if (got_path_is_root_dir(a->worktree->path_prefix)) { tree_path = strdup(parent_path); if (tree_path == NULL) { err = got_error_from_errno("strdup"); goto done; } } else { if (got_path_is_root_dir(parent_path)) { tree_path = strdup(a->worktree->path_prefix); if (tree_path == NULL) { err = got_error_from_errno("strdup"); goto done; } } else { if (asprintf(&tree_path, "%s/%s", a->worktree->path_prefix, parent_path) == -1) { err = got_error_from_errno("asprintf"); goto done; } } } err = got_object_open_as_commit(&base_commit, a->repo, a->worktree->base_commit_id); if (err) goto done; err = got_object_id_by_path(&tree_id, a->repo, base_commit, tree_path); if (err) { if (!(err->code == GOT_ERR_NO_TREE_ENTRY && (status == GOT_STATUS_ADD || staged_status == GOT_STATUS_ADD))) goto done; } else { err = got_object_open_as_tree(&tree, a->repo, tree_id); if (err) goto done; err = got_path_basename(&te_name, ie->path); if (err) goto done; te = got_object_tree_find_entry(tree, te_name); free(te_name); if (te == NULL && status != GOT_STATUS_ADD && staged_status != GOT_STATUS_ADD) { err = got_error_path(ie->path, GOT_ERR_NO_TREE_ENTRY); goto done; } } switch (status) { case GOT_STATUS_ADD: if (a->patch_cb) { int choice = GOT_PATCH_CHOICE_NONE; err = (*a->patch_cb)(&choice, a->patch_arg, status, ie->path, NULL, 1, 1); if (err) goto done; if (choice != GOT_PATCH_CHOICE_YES) break; } err = (*a->progress_cb)(a->progress_arg, GOT_STATUS_REVERT, ie->path); if (err) goto done; got_fileindex_entry_remove(a->fileindex, ie); if (a->unlink_added_files) { int do_unlink = a->added_files_to_unlink ? 0 : 1; if (a->added_files_to_unlink) { struct got_pathlist_entry *pe; RB_FOREACH(pe, got_pathlist_head, a->added_files_to_unlink) { if (got_path_cmp(pe->path, relpath, pe->path_len, strlen(relpath))) continue; do_unlink = 1; break; } } if (do_unlink) { if (asprintf(&ondisk_path, "%s/%s", got_worktree_get_root_path(a->worktree), relpath) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (unlink(ondisk_path) == -1) { err = got_error_from_errno2("unlink", ondisk_path); break; } } } break; case GOT_STATUS_DELETE: if (a->patch_cb) { int choice = GOT_PATCH_CHOICE_NONE; err = (*a->patch_cb)(&choice, a->patch_arg, status, ie->path, NULL, 1, 1); if (err) goto done; if (choice != GOT_PATCH_CHOICE_YES) break; } /* fall through */ case GOT_STATUS_MODIFY: case GOT_STATUS_MODE_CHANGE: case GOT_STATUS_CONFLICT: case GOT_STATUS_MISSING: { struct got_object_id id; if (staged_status == GOT_STATUS_ADD || staged_status == GOT_STATUS_MODIFY) got_fileindex_entry_get_staged_blob_id(&id, ie); else got_fileindex_entry_get_blob_id(&id, ie); fd = got_opentempfd(); if (fd == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } err = got_object_open_as_blob(&blob, a->repo, &id, 8192, fd); if (err) goto done; if (asprintf(&ondisk_path, "%s/%s", got_worktree_get_root_path(a->worktree), relpath) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (a->patch_cb && (status == GOT_STATUS_MODIFY || status == GOT_STATUS_CONFLICT)) { int is_bad_symlink = 0, revert_binary_file = 0; err = create_patched_content(&path_content, &revert_binary_file, 1, &id, ondisk_path, dirfd, de_name, ie->path, a->repo, a->patch_cb, a->patch_arg); if (err != NULL) goto done; if (revert_binary_file){ err = install_blob(a->worktree, ondisk_path, ie->path, te ? te->mode : GOT_DEFAULT_FILE_MODE, got_fileindex_perms_to_st(ie), blob, 0, 1, 0, 0, NULL, a->repo, a->progress_cb, a->progress_arg); if (err != NULL) goto done; } if (path_content == NULL) break; if (te && S_ISLNK(te->mode)) { if (unlink(path_content) == -1) { err = got_error_from_errno2("unlink", path_content); break; } err = install_symlink(&is_bad_symlink, a->worktree, ondisk_path, ie->path, blob, 0, 1, 0, 0, a->repo, a->progress_cb, a->progress_arg); } else { if (rename(path_content, ondisk_path) == -1) { err = got_error_from_errno3("rename", path_content, ondisk_path); goto done; } } err = got_fileindex_entry_update(ie, a->worktree->root_fd, relpath, &blob->id, &ie->commit, 0); if (err) goto done; } else { int is_bad_symlink = 0; if (te && S_ISLNK(te->mode)) { err = install_symlink(&is_bad_symlink, a->worktree, ondisk_path, ie->path, blob, 0, 1, 0, 0, a->repo, a->progress_cb, a->progress_arg); } else { err = install_blob(a->worktree, ondisk_path, ie->path, te ? te->mode : GOT_DEFAULT_FILE_MODE, got_fileindex_perms_to_st(ie), blob, 0, 1, 0, 0, NULL, a->repo, a->progress_cb, a->progress_arg); } if (err) goto done; err = got_fileindex_entry_update(ie, a->worktree->root_fd, relpath, &blob->id, &ie->commit, 0); if (err) goto done; if (is_bad_symlink) { got_fileindex_entry_filetype_set(ie, GOT_FILEIDX_MODE_BAD_SYMLINK); } } break; } default: break; } done: free(ondisk_path); free(path_content); free(parent_path); free(tree_path); if (fd != -1 && close(fd) == -1 && err == NULL) err = got_error_from_errno("close"); if (blob) got_object_blob_close(blob); if (tree) got_object_tree_close(tree); free(tree_id); if (base_commit) got_object_commit_close(base_commit); return err; } const struct got_error * got_worktree_revert(struct got_worktree *worktree, struct got_pathlist_head *paths, got_worktree_checkout_cb progress_cb, void *progress_arg, got_worktree_patch_cb patch_cb, void *patch_arg, struct got_repository *repo) { struct got_fileindex *fileindex = NULL; char *fileindex_path = NULL; const struct got_error *err = NULL, *unlockerr = NULL; const struct got_error *sync_err = NULL; struct got_pathlist_entry *pe; struct revert_file_args rfa; err = lock_worktree(worktree, LOCK_EX); if (err) return err; err = open_fileindex(&fileindex, &fileindex_path, worktree, got_repo_get_object_format(repo)); if (err) goto done; rfa.worktree = worktree; rfa.fileindex = fileindex; rfa.progress_cb = progress_cb; rfa.progress_arg = progress_arg; rfa.patch_cb = patch_cb; rfa.patch_arg = patch_arg; rfa.repo = repo; rfa.unlink_added_files = 0; RB_FOREACH(pe, got_pathlist_head, paths) { err = worktree_status(worktree, pe->path, fileindex, repo, revert_file, &rfa, NULL, NULL, 1, 0); if (err) break; } sync_err = sync_fileindex(fileindex, fileindex_path); if (sync_err && err == NULL) err = sync_err; done: free(fileindex_path); if (fileindex) got_fileindex_free(fileindex); unlockerr = lock_worktree(worktree, LOCK_SH); if (unlockerr && err == NULL) err = unlockerr; return err; } static void free_commitable(struct got_commitable *ct) { free(ct->path); free(ct->in_repo_path); free(ct->ondisk_path); free(ct->blob_id); free(ct->base_blob_id); free(ct->staged_blob_id); free(ct->base_commit_id); free(ct); } struct collect_commitables_arg { struct got_pathlist_head *commitable_paths; struct got_repository *repo; struct got_worktree *worktree; struct got_fileindex *fileindex; int have_staged_files; int allow_bad_symlinks; int diff_header_shown; int commit_conflicts; FILE *diff_outfile; FILE *f1; FILE *f2; }; /* * Create a file which contains the target path of a symlink so we can feed * it as content to the diff engine. */ static const struct got_error * get_symlink_target_file(int *fd, int dirfd, const char *de_name, const char *abspath) { const struct got_error *err = NULL; char target_path[PATH_MAX]; ssize_t target_len, outlen; *fd = -1; if (dirfd != -1) { target_len = readlinkat(dirfd, de_name, target_path, PATH_MAX); if (target_len == -1) return got_error_from_errno2("readlinkat", abspath); } else { target_len = readlink(abspath, target_path, PATH_MAX); if (target_len == -1) return got_error_from_errno2("readlink", abspath); } *fd = got_opentempfd(); if (*fd == -1) return got_error_from_errno("got_opentempfd"); outlen = write(*fd, target_path, target_len); if (outlen == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } if (lseek(*fd, 0, SEEK_SET) == -1) { err = got_error_from_errno2("lseek", abspath); goto done; } done: if (err) { close(*fd); *fd = -1; } return err; } static const struct got_error * append_ct_diff(struct got_commitable *ct, int *diff_header_shown, FILE *diff_outfile, FILE *f1, FILE *f2, int dirfd, const char *de_name, int diff_staged, struct got_repository *repo, struct got_worktree *worktree) { const struct got_error *err = NULL; struct got_blob_object *blob1 = NULL; int fd = -1, fd1 = -1, fd2 = -1; FILE *ondisk_file = NULL; char *label1 = NULL; struct stat sb; off_t size1 = 0; int f2_exists = 0; char *id_str = NULL; memset(&sb, 0, sizeof(sb)); if (diff_staged) { if (ct->staged_status != GOT_STATUS_MODIFY && ct->staged_status != GOT_STATUS_ADD && ct->staged_status != GOT_STATUS_DELETE) return NULL; } else { if (ct->status != GOT_STATUS_MODIFY && ct->status != GOT_STATUS_ADD && ct->status != GOT_STATUS_DELETE && ct->status != GOT_STATUS_CONFLICT) return NULL; } err = got_opentemp_truncate(f1); if (err) return got_error_from_errno("got_opentemp_truncate"); err = got_opentemp_truncate(f2); if (err) return got_error_from_errno("got_opentemp_truncate"); if (!*diff_header_shown) { err = got_object_id_str(&id_str, worktree->base_commit_id); if (err) return err; fprintf(diff_outfile, "diff %s%s\n", diff_staged ? "-s " : "", got_worktree_get_root_path(worktree)); fprintf(diff_outfile, "commit - %s\n", id_str); fprintf(diff_outfile, "path + %s%s\n", got_worktree_get_root_path(worktree), diff_staged ? " (staged changes)" : ""); *diff_header_shown = 1; } if (diff_staged) { const char *label1 = NULL, *label2 = NULL; switch (ct->staged_status) { case GOT_STATUS_MODIFY: label1 = ct->path; label2 = ct->path; break; case GOT_STATUS_ADD: label2 = ct->path; break; case GOT_STATUS_DELETE: label1 = ct->path; break; default: return got_error(GOT_ERR_FILE_STATUS); } fd1 = got_opentempfd(); if (fd1 == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } fd2 = got_opentempfd(); if (fd2 == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } err = got_diff_objects_as_blobs(NULL, NULL, f1, f2, fd1, fd2, ct->base_blob_id, ct->staged_blob_id, label1, label2, GOT_DIFF_ALGORITHM_PATIENCE, 3, 0, 0, NULL, repo, diff_outfile); goto done; } fd1 = got_opentempfd(); if (fd1 == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } if (ct->status != GOT_STATUS_ADD) { err = got_object_open_as_blob(&blob1, repo, ct->base_blob_id, 8192, fd1); if (err) goto done; } if (ct->status != GOT_STATUS_DELETE) { if (dirfd != -1) { fd = openat(dirfd, de_name, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (fd == -1) { if (!got_err_open_nofollow_on_symlink()) { err = got_error_from_errno2("openat", ct->ondisk_path); goto done; } err = get_symlink_target_file(&fd, dirfd, de_name, ct->ondisk_path); if (err) goto done; } } else { fd = open(ct->ondisk_path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (fd == -1) { if (!got_err_open_nofollow_on_symlink()) { err = got_error_from_errno2("open", ct->ondisk_path); goto done; } err = get_symlink_target_file(&fd, dirfd, de_name, ct->ondisk_path); if (err) goto done; } } if (fstatat(fd, ct->ondisk_path, &sb, AT_SYMLINK_NOFOLLOW) == -1) { err = got_error_from_errno2("fstatat", ct->ondisk_path); goto done; } ondisk_file = fdopen(fd, "r"); if (ondisk_file == NULL) { err = got_error_from_errno2("fdopen", ct->ondisk_path); goto done; } fd = -1; f2_exists = 1; } if (blob1) { err = got_object_blob_dump_to_file(&size1, NULL, NULL, f1, blob1); if (err) goto done; } err = got_diff_blob_file(NULL, NULL, blob1, f1, size1, label1, ondisk_file ? ondisk_file : f2, f2_exists, &sb, ct->path, GOT_DIFF_ALGORITHM_PATIENCE, 3, 0, 0, NULL, diff_outfile); done: if (fd1 != -1 && close(fd1) == -1 && err == NULL) err = got_error_from_errno("close"); if (fd2 != -1 && close(fd2) == -1 && err == NULL) err = got_error_from_errno("close"); if (blob1) got_object_blob_close(blob1); if (fd != -1 && close(fd) == -1 && err == NULL) err = got_error_from_errno("close"); if (ondisk_file && fclose(ondisk_file) == EOF && err == NULL) err = got_error_from_errno("fclose"); return err; } static const struct got_error * collect_commitables(void *arg, unsigned char status, unsigned char staged_status, const char *relpath, struct got_object_id *blob_id, struct got_object_id *staged_blob_id, struct got_object_id *commit_id, int dirfd, const char *de_name) { struct collect_commitables_arg *a = arg; const struct got_error *err = NULL; struct got_commitable *ct = NULL; struct got_pathlist_entry *new = NULL; char *parent_path = NULL, *path = NULL; struct stat sb; if (a->have_staged_files) { if (staged_status != GOT_STATUS_MODIFY && staged_status != GOT_STATUS_ADD && staged_status != GOT_STATUS_DELETE) return NULL; } else { if (status == GOT_STATUS_CONFLICT && !a->commit_conflicts) { printf("C %s\n", relpath); return got_error(GOT_ERR_COMMIT_CONFLICT); } if (status != GOT_STATUS_MODIFY && status != GOT_STATUS_MODE_CHANGE && status != GOT_STATUS_ADD && status != GOT_STATUS_DELETE && status != GOT_STATUS_CONFLICT) return NULL; } if (asprintf(&path, "/%s", relpath) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (strcmp(path, "/") == 0) { parent_path = strdup(""); if (parent_path == NULL) return got_error_from_errno("strdup"); } else { err = got_path_dirname(&parent_path, path); if (err) return err; } ct = calloc(1, sizeof(*ct)); if (ct == NULL) { err = got_error_from_errno("calloc"); goto done; } if (asprintf(&ct->ondisk_path, "%s/%s", a->worktree->root_path, relpath) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (staged_status == GOT_STATUS_ADD || staged_status == GOT_STATUS_MODIFY) { struct got_fileindex_entry *ie; ie = got_fileindex_entry_get(a->fileindex, path, strlen(path)); switch (got_fileindex_entry_staged_filetype_get(ie)) { case GOT_FILEIDX_MODE_REGULAR_FILE: case GOT_FILEIDX_MODE_BAD_SYMLINK: ct->mode = S_IFREG; break; case GOT_FILEIDX_MODE_SYMLINK: ct->mode = S_IFLNK; break; default: err = got_error_path(path, GOT_ERR_BAD_FILETYPE); goto done; } ct->mode |= got_fileindex_entry_perms_get(ie); } else if (status != GOT_STATUS_DELETE && staged_status != GOT_STATUS_DELETE) { if (dirfd != -1) { if (fstatat(dirfd, de_name, &sb, AT_SYMLINK_NOFOLLOW) == -1) { err = got_error_from_errno2("fstatat", ct->ondisk_path); goto done; } } else if (lstat(ct->ondisk_path, &sb) == -1) { err = got_error_from_errno2("lstat", ct->ondisk_path); goto done; } ct->mode = sb.st_mode; } if (asprintf(&ct->in_repo_path, "%s%s%s", a->worktree->path_prefix, got_path_is_root_dir(a->worktree->path_prefix) ? "" : "/", relpath) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (S_ISLNK(ct->mode) && staged_status == GOT_STATUS_NO_CHANGE && status == GOT_STATUS_ADD && !a->allow_bad_symlinks) { int is_bad_symlink; char target_path[PATH_MAX]; ssize_t target_len; target_len = readlink(ct->ondisk_path, target_path, sizeof(target_path)); if (target_len == -1) { err = got_error_from_errno2("readlink", ct->ondisk_path); goto done; } err = is_bad_symlink_target(&is_bad_symlink, target_path, target_len, ct->ondisk_path, a->worktree->root_path, a->worktree->meta_dir); if (err) goto done; if (is_bad_symlink) { err = got_error_path(ct->ondisk_path, GOT_ERR_BAD_SYMLINK); goto done; } } ct->status = status; ct->staged_status = staged_status; ct->blob_id = NULL; /* will be filled in when blob gets created */ if (ct->status != GOT_STATUS_ADD && ct->staged_status != GOT_STATUS_ADD) { ct->base_blob_id = got_object_id_dup(blob_id); if (ct->base_blob_id == NULL) { err = got_error_from_errno("got_object_id_dup"); goto done; } ct->base_commit_id = got_object_id_dup(commit_id); if (ct->base_commit_id == NULL) { err = got_error_from_errno("got_object_id_dup"); goto done; } } if (ct->staged_status == GOT_STATUS_ADD || ct->staged_status == GOT_STATUS_MODIFY) { ct->staged_blob_id = got_object_id_dup(staged_blob_id); if (ct->staged_blob_id == NULL) { err = got_error_from_errno("got_object_id_dup"); goto done; } } ct->path = strdup(path); if (ct->path == NULL) { err = got_error_from_errno("strdup"); goto done; } if (a->diff_outfile) { err = append_ct_diff(ct, &a->diff_header_shown, a->diff_outfile, a->f1, a->f2, dirfd, de_name, a->have_staged_files, a->repo, a->worktree); if (err) goto done; } err = got_pathlist_insert(&new, a->commitable_paths, ct->path, ct); done: if (ct && (err || new == NULL)) free_commitable(ct); free(parent_path); free(path); return err; } static const struct got_error *write_tree(struct got_object_id **, int *, struct got_tree_object *, const char *, struct got_pathlist_head *, got_worktree_status_cb status_cb, void *status_arg, struct got_repository *); static const struct got_error * write_subtree(struct got_object_id **new_subtree_id, int *nentries, struct got_tree_entry *te, const char *parent_path, struct got_pathlist_head *commitable_paths, got_worktree_status_cb status_cb, void *status_arg, struct got_repository *repo) { const struct got_error *err = NULL; struct got_tree_object *subtree; char *subpath; if (asprintf(&subpath, "%s%s%s", parent_path, got_path_is_root_dir(parent_path) ? "" : "/", te->name) == -1) return got_error_from_errno("asprintf"); err = got_object_open_as_tree(&subtree, repo, &te->id); if (err) return err; err = write_tree(new_subtree_id, nentries, subtree, subpath, commitable_paths, status_cb, status_arg, repo); got_object_tree_close(subtree); free(subpath); return err; } static const struct got_error * match_ct_parent_path(int *match, struct got_commitable *ct, const char *path) { const struct got_error *err = NULL; char *ct_parent_path = NULL; *match = 0; if (strchr(ct->in_repo_path, '/') == NULL) { *match = got_path_is_root_dir(path); return NULL; } err = got_path_dirname(&ct_parent_path, ct->in_repo_path); if (err) return err; *match = (strcmp(path, ct_parent_path) == 0); free(ct_parent_path); return err; } static mode_t get_ct_file_mode(struct got_commitable *ct) { if (S_ISLNK(ct->mode)) return S_IFLNK; return S_IFREG | (ct->mode & ((S_IRWXU | S_IRWXG | S_IRWXO))); } static const struct got_error * alloc_modified_blob_tree_entry(struct got_tree_entry **new_te, struct got_tree_entry *te, struct got_commitable *ct) { const struct got_error *err = NULL; *new_te = NULL; err = got_object_tree_entry_dup(new_te, te); if (err) goto done; (*new_te)->mode = get_ct_file_mode(ct); if (ct->staged_status == GOT_STATUS_MODIFY) memcpy(&(*new_te)->id, ct->staged_blob_id, sizeof((*new_te)->id)); else memcpy(&(*new_te)->id, ct->blob_id, sizeof((*new_te)->id)); done: if (err && *new_te) { free(*new_te); *new_te = NULL; } return err; } static const struct got_error * alloc_added_blob_tree_entry(struct got_tree_entry **new_te, struct got_commitable *ct) { const struct got_error *err = NULL; char *ct_name = NULL; *new_te = NULL; *new_te = calloc(1, sizeof(**new_te)); if (*new_te == NULL) return got_error_from_errno("calloc"); err = got_path_basename(&ct_name, ct->path); if (err) goto done; if (strlcpy((*new_te)->name, ct_name, sizeof((*new_te)->name)) >= sizeof((*new_te)->name)) { err = got_error(GOT_ERR_NO_SPACE); goto done; } (*new_te)->mode = get_ct_file_mode(ct); if (ct->staged_status == GOT_STATUS_ADD) memcpy(&(*new_te)->id, ct->staged_blob_id, sizeof((*new_te)->id)); else memcpy(&(*new_te)->id, ct->blob_id, sizeof((*new_te)->id)); done: free(ct_name); if (err && *new_te) { free(*new_te); *new_te = NULL; } return err; } static const struct got_error * insert_tree_entry(struct got_tree_entry *new_te, struct got_pathlist_head *paths) { const struct got_error *err = NULL; struct got_pathlist_entry *new_pe; err = got_pathlist_insert(&new_pe, paths, new_te->name, new_te); if (err) return err; if (new_pe == NULL) return got_error(GOT_ERR_TREE_DUP_ENTRY); return NULL; } static const struct got_error * report_ct_status(struct got_commitable *ct, got_worktree_status_cb status_cb, void *status_arg) { const char *ct_path = ct->path; unsigned char status; if (status_cb == NULL) /* no commit progress output desired */ return NULL; while (ct_path[0] == '/') ct_path++; if (ct->staged_status != GOT_STATUS_NO_CHANGE) status = ct->staged_status; else status = ct->status; return (*status_cb)(status_arg, status, GOT_STATUS_NO_CHANGE, ct_path, ct->blob_id, NULL, NULL, -1, NULL); } static const struct got_error * match_modified_subtree(int *modified, struct got_tree_entry *te, const char *base_tree_path, struct got_pathlist_head *commitable_paths) { const struct got_error *err = NULL; struct got_pathlist_entry *pe; char *te_path; *modified = 0; if (asprintf(&te_path, "%s%s%s", base_tree_path, got_path_is_root_dir(base_tree_path) ? "" : "/", te->name) == -1) return got_error_from_errno("asprintf"); RB_FOREACH(pe, got_pathlist_head, commitable_paths) { struct got_commitable *ct = pe->data; *modified = got_path_is_child(ct->in_repo_path, te_path, strlen(te_path)); if (*modified) break; } free(te_path); return err; } static const struct got_error * match_deleted_or_modified_ct(struct got_commitable **ctp, struct got_tree_entry *te, const char *base_tree_path, struct got_pathlist_head *commitable_paths) { const struct got_error *err = NULL; struct got_pathlist_entry *pe; *ctp = NULL; RB_FOREACH(pe, got_pathlist_head, commitable_paths) { struct got_commitable *ct = pe->data; char *ct_name = NULL; int path_matches; if (ct->staged_status == GOT_STATUS_NO_CHANGE) { if (ct->status != GOT_STATUS_MODIFY && ct->status != GOT_STATUS_MODE_CHANGE && ct->status != GOT_STATUS_DELETE && ct->status != GOT_STATUS_CONFLICT) continue; } else { if (ct->staged_status != GOT_STATUS_MODIFY && ct->staged_status != GOT_STATUS_DELETE) continue; } if (got_object_id_cmp(ct->base_blob_id, &te->id) != 0) continue; err = match_ct_parent_path(&path_matches, ct, base_tree_path); if (err) return err; if (!path_matches) continue; err = got_path_basename(&ct_name, pe->path); if (err) return err; if (strcmp(te->name, ct_name) != 0) { free(ct_name); continue; } free(ct_name); *ctp = ct; break; } return err; } static const struct got_error * make_subtree_for_added_blob(struct got_tree_entry **new_tep, const char *child_path, const char *path_base_tree, struct got_pathlist_head *commitable_paths, got_worktree_status_cb status_cb, void *status_arg, struct got_repository *repo) { const struct got_error *err = NULL; struct got_tree_entry *new_te; char *subtree_path; struct got_object_id *id = NULL; int nentries; *new_tep = NULL; if (asprintf(&subtree_path, "%s%s%s", path_base_tree, got_path_is_root_dir(path_base_tree) ? "" : "/", child_path) == -1) return got_error_from_errno("asprintf"); new_te = calloc(1, sizeof(*new_te)); if (new_te == NULL) return got_error_from_errno("calloc"); new_te->mode = S_IFDIR; if (strlcpy(new_te->name, child_path, sizeof(new_te->name)) >= sizeof(new_te->name)) { err = got_error(GOT_ERR_NO_SPACE); goto done; } err = write_tree(&id, &nentries, NULL, subtree_path, commitable_paths, status_cb, status_arg, repo); if (err) { free(new_te); goto done; } memcpy(&new_te->id, id, sizeof(new_te->id)); done: free(id); free(subtree_path); if (err == NULL) *new_tep = new_te; return err; } static const struct got_error * write_tree(struct got_object_id **new_tree_id, int *nentries, struct got_tree_object *base_tree, const char *path_base_tree, struct got_pathlist_head *commitable_paths, got_worktree_status_cb status_cb, void *status_arg, struct got_repository *repo) { const struct got_error *err = NULL; struct got_pathlist_head paths; struct got_tree_entry *te, *new_te = NULL; struct got_pathlist_entry *pe; RB_INIT(&paths); *nentries = 0; /* Insert, and recurse into, newly added entries first. */ RB_FOREACH(pe, got_pathlist_head, commitable_paths) { struct got_commitable *ct = pe->data; char *child_path = NULL, *slash; if ((ct->status != GOT_STATUS_ADD && ct->staged_status != GOT_STATUS_ADD) || (ct->flags & GOT_COMMITABLE_ADDED)) continue; if (!got_path_is_child(ct->in_repo_path, path_base_tree, strlen(path_base_tree))) continue; err = got_path_skip_common_ancestor(&child_path, path_base_tree, ct->in_repo_path); if (err) goto done; slash = strchr(child_path, '/'); if (slash == NULL) { err = alloc_added_blob_tree_entry(&new_te, ct); if (err) goto done; err = report_ct_status(ct, status_cb, status_arg); if (err) goto done; ct->flags |= GOT_COMMITABLE_ADDED; err = insert_tree_entry(new_te, &paths); if (err) goto done; (*nentries)++; } else { *slash = '\0'; /* trim trailing path components */ if (base_tree == NULL || got_object_tree_find_entry(base_tree, child_path) == NULL) { err = make_subtree_for_added_blob(&new_te, child_path, path_base_tree, commitable_paths, status_cb, status_arg, repo); if (err) goto done; err = insert_tree_entry(new_te, &paths); if (err) goto done; (*nentries)++; } } } if (base_tree) { int i, nbase_entries; /* Handle modified and deleted entries. */ nbase_entries = got_object_tree_get_nentries(base_tree); for (i = 0; i < nbase_entries; i++) { struct got_commitable *ct = NULL; te = got_object_tree_get_entry(base_tree, i); if (got_object_tree_entry_is_submodule(te)) { /* Entry is a submodule; just copy it. */ err = got_object_tree_entry_dup(&new_te, te); if (err) goto done; err = insert_tree_entry(new_te, &paths); if (err) goto done; (*nentries)++; continue; } if (S_ISDIR(te->mode)) { int modified; err = got_object_tree_entry_dup(&new_te, te); if (err) goto done; err = match_modified_subtree(&modified, te, path_base_tree, commitable_paths); if (err) goto done; /* Avoid recursion into unmodified subtrees. */ if (modified) { struct got_object_id *new_id; int nsubentries; err = write_subtree(&new_id, &nsubentries, te, path_base_tree, commitable_paths, status_cb, status_arg, repo); if (err) goto done; if (nsubentries == 0) { /* All entries were deleted. */ free(new_id); continue; } memcpy(&new_te->id, new_id, sizeof(new_te->id)); free(new_id); } err = insert_tree_entry(new_te, &paths); if (err) goto done; (*nentries)++; continue; } err = match_deleted_or_modified_ct(&ct, te, path_base_tree, commitable_paths); if (err) goto done; if (ct) { /* NB: Deleted entries get dropped here. */ if (ct->status == GOT_STATUS_MODIFY || ct->status == GOT_STATUS_MODE_CHANGE || ct->status == GOT_STATUS_CONFLICT || ct->staged_status == GOT_STATUS_MODIFY) { err = alloc_modified_blob_tree_entry( &new_te, te, ct); if (err) goto done; err = insert_tree_entry(new_te, &paths); if (err) goto done; (*nentries)++; } err = report_ct_status(ct, status_cb, status_arg); if (err) goto done; } else { /* Entry is unchanged; just copy it. */ err = got_object_tree_entry_dup(&new_te, te); if (err) goto done; err = insert_tree_entry(new_te, &paths); if (err) goto done; (*nentries)++; } } } /* Write new list of entries; deleted entries have been dropped. */ err = got_object_tree_create(new_tree_id, &paths, *nentries, repo); done: got_pathlist_free(&paths, GOT_PATHLIST_FREE_NONE); return err; } static const struct got_error * update_fileindex_after_commit(struct got_worktree *worktree, struct got_pathlist_head *commitable_paths, struct got_object_id *new_base_commit_id, struct got_fileindex *fileindex, int have_staged_files) { const struct got_error *err = NULL; struct got_pathlist_entry *pe; char *relpath = NULL; RB_FOREACH(pe, got_pathlist_head, commitable_paths) { struct got_fileindex_entry *ie; struct got_commitable *ct = pe->data; ie = got_fileindex_entry_get(fileindex, pe->path, pe->path_len); err = got_path_skip_common_ancestor(&relpath, worktree->root_path, ct->ondisk_path); if (err) goto done; if (ie) { if (ct->status == GOT_STATUS_DELETE || ct->staged_status == GOT_STATUS_DELETE) { got_fileindex_entry_remove(fileindex, ie); } else if (ct->staged_status == GOT_STATUS_ADD || ct->staged_status == GOT_STATUS_MODIFY) { got_fileindex_entry_stage_set(ie, GOT_FILEIDX_STAGE_NONE); got_fileindex_entry_staged_filetype_set(ie, 0); err = got_fileindex_entry_update(ie, worktree->root_fd, relpath, ct->staged_blob_id, new_base_commit_id, !have_staged_files); } else err = got_fileindex_entry_update(ie, worktree->root_fd, relpath, ct->blob_id, new_base_commit_id, !have_staged_files); } else { err = got_fileindex_entry_alloc(&ie, pe->path); if (err) goto done; err = got_fileindex_entry_update(ie, worktree->root_fd, relpath, ct->blob_id, new_base_commit_id, 1); if (err) { got_fileindex_entry_free(ie); goto done; } err = got_fileindex_entry_add(fileindex, ie); if (err) { got_fileindex_entry_free(ie); goto done; } } free(relpath); relpath = NULL; } done: free(relpath); return err; } static const struct got_error * check_out_of_date(const char *in_repo_path, unsigned char status, unsigned char staged_status, struct got_object_id *base_blob_id, struct got_object_id *base_commit_id, struct got_object_id *head_commit_id, struct got_repository *repo, int ood_errcode) { const struct got_error *err = NULL; struct got_commit_object *commit = NULL; struct got_object_id *id = NULL; if (status != GOT_STATUS_ADD && staged_status != GOT_STATUS_ADD) { /* Trivial case: base commit == head commit */ if (got_object_id_cmp(base_commit_id, head_commit_id) == 0) return NULL; /* * Ensure file content which local changes were based * on matches file content in the branch head. */ err = got_object_open_as_commit(&commit, repo, head_commit_id); if (err) goto done; err = got_object_id_by_path(&id, repo, commit, in_repo_path); if (err) { if (err->code == GOT_ERR_NO_TREE_ENTRY) err = got_error(ood_errcode); goto done; } else if (got_object_id_cmp(id, base_blob_id) != 0) err = got_error(ood_errcode); } else { /* Require that added files don't exist in the branch head. */ err = got_object_open_as_commit(&commit, repo, head_commit_id); if (err) goto done; err = got_object_id_by_path(&id, repo, commit, in_repo_path); if (err && err->code != GOT_ERR_NO_TREE_ENTRY) goto done; err = id ? got_error(ood_errcode) : NULL; } done: free(id); if (commit) got_object_commit_close(commit); return err; } static const struct got_error * commit_worktree(struct got_object_id **new_commit_id, struct got_pathlist_head *commitable_paths, struct got_object_id *head_commit_id, struct got_object_id *parent_id2, struct got_worktree *worktree, const char *author, const char *committer, char *diff_path, got_worktree_commit_msg_cb commit_msg_cb, void *commit_arg, got_worktree_status_cb status_cb, void *status_arg, struct got_repository *repo) { const struct got_error *err = NULL, *unlockerr = NULL; struct got_pathlist_entry *pe; const char *head_ref_name = NULL; struct got_commit_object *head_commit = NULL; struct got_reference *head_ref2 = NULL; struct got_object_id *head_commit_id2 = NULL; struct got_tree_object *head_tree = NULL; struct got_object_id *new_tree_id = NULL; int nentries, nparents = 0; struct got_object_id_queue parent_ids; struct got_object_qid *pid = NULL; char *logmsg = NULL; time_t timestamp; *new_commit_id = NULL; STAILQ_INIT(&parent_ids); err = got_object_open_as_commit(&head_commit, repo, head_commit_id); if (err) goto done; err = got_object_open_as_tree(&head_tree, repo, head_commit->tree_id); if (err) goto done; if (commit_msg_cb != NULL) { err = commit_msg_cb(commitable_paths, diff_path, &logmsg, commit_arg); if (err) goto done; } if (logmsg == NULL || strlen(logmsg) == 0) { err = got_error(GOT_ERR_COMMIT_MSG_EMPTY); goto done; } /* Create blobs from added and modified files and record their IDs. */ RB_FOREACH(pe, got_pathlist_head, commitable_paths) { struct got_commitable *ct = pe->data; char *ondisk_path; /* Blobs for staged files already exist. */ if (ct->staged_status == GOT_STATUS_ADD || ct->staged_status == GOT_STATUS_MODIFY) continue; if (ct->status != GOT_STATUS_ADD && ct->status != GOT_STATUS_MODIFY && ct->status != GOT_STATUS_MODE_CHANGE && ct->status != GOT_STATUS_CONFLICT) continue; if (asprintf(&ondisk_path, "%s/%s", worktree->root_path, pe->path) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = got_object_blob_create(&ct->blob_id, ondisk_path, repo); free(ondisk_path); if (err) goto done; } /* Recursively write new tree objects. */ err = write_tree(&new_tree_id, &nentries, head_tree, "/", commitable_paths, status_cb, status_arg, repo); if (err) goto done; err = got_object_qid_alloc(&pid, head_commit_id); if (err) goto done; STAILQ_INSERT_TAIL(&parent_ids, pid, entry); nparents++; if (parent_id2) { err = got_object_qid_alloc(&pid, parent_id2); if (err) goto done; STAILQ_INSERT_TAIL(&parent_ids, pid, entry); nparents++; } timestamp = time(NULL); err = got_object_commit_create(new_commit_id, new_tree_id, &parent_ids, nparents, author, timestamp, committer, timestamp, logmsg, repo); if (logmsg != NULL) free(logmsg); if (err) goto done; /* Check if a concurrent commit to our branch has occurred. */ head_ref_name = got_worktree_get_head_ref_name(worktree); if (head_ref_name == NULL) { err = got_error_from_errno("got_worktree_get_head_ref_name"); goto done; } /* Lock the reference here to prevent concurrent modification. */ err = got_ref_open(&head_ref2, repo, head_ref_name, 1); if (err) goto done; err = got_ref_resolve(&head_commit_id2, repo, head_ref2); if (err) goto done; if (got_object_id_cmp(head_commit_id, head_commit_id2) != 0) { err = got_error(GOT_ERR_COMMIT_HEAD_CHANGED); goto done; } /* Update branch head in repository. */ err = got_ref_change_ref(head_ref2, *new_commit_id); if (err) goto done; err = got_ref_write(head_ref2, repo); if (err) goto done; err = got_worktree_set_base_commit_id(worktree, repo, *new_commit_id); if (err) goto done; err = ref_base_commit(worktree, repo); if (err) goto done; done: got_object_id_queue_free(&parent_ids); if (head_tree) got_object_tree_close(head_tree); if (head_commit) got_object_commit_close(head_commit); free(head_commit_id2); if (head_ref2) { unlockerr = got_ref_unlock(head_ref2); if (unlockerr && err == NULL) err = unlockerr; got_ref_close(head_ref2); } return err; } static const struct got_error * check_path_is_commitable(const char *path, struct got_pathlist_head *commitable_paths) { struct got_pathlist_entry *cpe = NULL; size_t path_len = strlen(path); RB_FOREACH(cpe, got_pathlist_head, commitable_paths) { struct got_commitable *ct = cpe->data; const char *ct_path = ct->path; while (ct_path[0] == '/') ct_path++; if (strcmp(path, ct_path) == 0 || got_path_is_child(ct_path, path, path_len)) break; } if (cpe == NULL) return got_error_path(path, GOT_ERR_COMMIT_NO_CHANGES); return NULL; } static const struct got_error * check_staged_file(void *arg, struct got_fileindex_entry *ie) { int *have_staged_files = arg; if (got_fileindex_entry_stage_get(ie) != GOT_FILEIDX_STAGE_NONE) { *have_staged_files = 1; return got_error(GOT_ERR_CANCELLED); } return NULL; } static const struct got_error * check_non_staged_files(struct got_fileindex *fileindex, struct got_pathlist_head *paths) { struct got_pathlist_entry *pe; struct got_fileindex_entry *ie; RB_FOREACH(pe, got_pathlist_head, paths) { if (pe->path[0] == '\0') continue; ie = got_fileindex_entry_get(fileindex, pe->path, pe->path_len); if (ie == NULL) return got_error_path(pe->path, GOT_ERR_BAD_PATH); if (got_fileindex_entry_stage_get(ie) == GOT_FILEIDX_STAGE_NONE) return got_error_path(pe->path, GOT_ERR_FILE_NOT_STAGED); } return NULL; } const struct got_error * got_worktree_commit(struct got_object_id **new_commit_id, struct got_worktree *worktree, struct got_pathlist_head *paths, const char *author, const char *committer, int allow_bad_symlinks, int show_diff, int commit_conflicts, got_worktree_commit_msg_cb commit_msg_cb, void *commit_arg, got_worktree_status_cb status_cb, void *status_arg, struct got_repository *repo) { const struct got_error *err = NULL, *unlockerr = NULL, *sync_err; struct got_fileindex *fileindex = NULL; char *fileindex_path = NULL; struct got_pathlist_head commitable_paths; struct collect_commitables_arg cc_arg; struct got_pathlist_entry *pe; struct got_reference *head_ref = NULL; struct got_object_id *head_commit_id = NULL; char *diff_path = NULL; int have_staged_files = 0; *new_commit_id = NULL; memset(&cc_arg, 0, sizeof(cc_arg)); RB_INIT(&commitable_paths); err = lock_worktree(worktree, LOCK_EX); if (err) goto done; err = got_ref_open(&head_ref, repo, worktree->head_ref_name, 0); if (err) goto done; err = got_ref_resolve(&head_commit_id, repo, head_ref); if (err) goto done; err = open_fileindex(&fileindex, &fileindex_path, worktree, got_repo_get_object_format(repo)); if (err) goto done; err = got_fileindex_for_each_entry_safe(fileindex, check_staged_file, &have_staged_files); if (err && err->code != GOT_ERR_CANCELLED) goto done; if (have_staged_files) { err = check_non_staged_files(fileindex, paths); if (err) goto done; } cc_arg.commitable_paths = &commitable_paths; cc_arg.worktree = worktree; cc_arg.fileindex = fileindex; cc_arg.repo = repo; cc_arg.have_staged_files = have_staged_files; cc_arg.allow_bad_symlinks = allow_bad_symlinks; cc_arg.diff_header_shown = 0; cc_arg.commit_conflicts = commit_conflicts; if (show_diff) { err = got_opentemp_named(&diff_path, &cc_arg.diff_outfile, GOT_TMPDIR_STR "/got", ".diff"); if (err) goto done; cc_arg.f1 = got_opentemp(); if (cc_arg.f1 == NULL) { err = got_error_from_errno("got_opentemp"); goto done; } cc_arg.f2 = got_opentemp(); if (cc_arg.f2 == NULL) { err = got_error_from_errno("got_opentemp"); goto done; } } RB_FOREACH(pe, got_pathlist_head, paths) { err = worktree_status(worktree, pe->path, fileindex, repo, collect_commitables, &cc_arg, NULL, NULL, 0, 0); if (err) goto done; } if (show_diff) { if (fflush(cc_arg.diff_outfile) == EOF) { err = got_error_from_errno("fflush"); goto done; } } if (RB_EMPTY(&commitable_paths)) { err = got_error(GOT_ERR_COMMIT_NO_CHANGES); goto done; } RB_FOREACH(pe, got_pathlist_head, paths) { err = check_path_is_commitable(pe->path, &commitable_paths); if (err) goto done; } RB_FOREACH(pe, got_pathlist_head, &commitable_paths) { struct got_commitable *ct = pe->data; const char *ct_path = ct->in_repo_path; while (ct_path[0] == '/') ct_path++; err = check_out_of_date(ct_path, ct->status, ct->staged_status, ct->base_blob_id, ct->base_commit_id, head_commit_id, repo, GOT_ERR_COMMIT_OUT_OF_DATE); if (err) goto done; } err = commit_worktree(new_commit_id, &commitable_paths, head_commit_id, NULL, worktree, author, committer, (diff_path && cc_arg.diff_header_shown) ? diff_path : NULL, commit_msg_cb, commit_arg, status_cb, status_arg, repo); if (err) goto done; err = update_fileindex_after_commit(worktree, &commitable_paths, *new_commit_id, fileindex, have_staged_files); sync_err = sync_fileindex(fileindex, fileindex_path); if (sync_err && err == NULL) err = sync_err; done: if (fileindex) got_fileindex_free(fileindex); free(fileindex_path); unlockerr = lock_worktree(worktree, LOCK_SH); if (unlockerr && err == NULL) err = unlockerr; RB_FOREACH(pe, got_pathlist_head, &commitable_paths) { struct got_commitable *ct = pe->data; free_commitable(ct); } got_pathlist_free(&commitable_paths, GOT_PATHLIST_FREE_NONE); if (diff_path && unlink(diff_path) == -1 && err == NULL) err = got_error_from_errno2("unlink", diff_path); free(diff_path); if (cc_arg.diff_outfile && fclose(cc_arg.diff_outfile) == EOF && err == NULL) err = got_error_from_errno("fclose"); return err; } const char * got_commitable_get_path(struct got_commitable *ct) { return ct->path; } unsigned int got_commitable_get_status(struct got_commitable *ct) { return ct->status; } struct check_rebase_ok_arg { struct got_worktree *worktree; struct got_repository *repo; }; static const struct got_error * check_rebase_ok(void *arg, struct got_fileindex_entry *ie) { const struct got_error *err = NULL; struct check_rebase_ok_arg *a = arg; unsigned char status; struct stat sb; char *ondisk_path; /* Reject rebase of a work tree with mixed base commits. */ if (got_object_id_cmp(&ie->commit, a->worktree->base_commit_id)) return got_error(GOT_ERR_MIXED_COMMITS); if (asprintf(&ondisk_path, "%s/%s", a->worktree->root_path, ie->path) == -1) return got_error_from_errno("asprintf"); /* Reject rebase of a work tree with modified or staged files. */ err = get_file_status(&status, &sb, ie, ondisk_path, -1, NULL, a->repo); free(ondisk_path); if (err) return err; if (status != GOT_STATUS_NO_CHANGE) return got_error(GOT_ERR_MODIFIED); if (get_staged_status(ie) != GOT_STATUS_NO_CHANGE) return got_error_path(ie->path, GOT_ERR_FILE_STAGED); return NULL; } const struct got_error * got_worktree_rebase_prepare(struct got_reference **new_base_branch_ref, struct got_reference **tmp_branch, struct got_fileindex **fileindex, struct got_worktree *worktree, struct got_reference *branch, struct got_repository *repo) { const struct got_error *err = NULL; char *tmp_branch_name = NULL, *new_base_branch_ref_name = NULL; char *branch_ref_name = NULL; char *fileindex_path = NULL; struct check_rebase_ok_arg ok_arg; struct got_reference *wt_branch = NULL, *branch_ref = NULL; struct got_object_id *wt_branch_tip = NULL; *new_base_branch_ref = NULL; *tmp_branch = NULL; *fileindex = NULL; err = lock_worktree(worktree, LOCK_EX); if (err) return err; err = open_fileindex(fileindex, &fileindex_path, worktree, got_repo_get_object_format(repo)); if (err) goto done; ok_arg.worktree = worktree; ok_arg.repo = repo; err = got_fileindex_for_each_entry_safe(*fileindex, check_rebase_ok, &ok_arg); if (err) goto done; err = get_rebase_tmp_ref_name(&tmp_branch_name, worktree); if (err) goto done; err = get_newbase_symref_name(&new_base_branch_ref_name, worktree); if (err) goto done; err = get_rebase_branch_symref_name(&branch_ref_name, worktree); if (err) goto done; err = got_ref_open(&wt_branch, repo, worktree->head_ref_name, 0); if (err) goto done; err = got_ref_resolve(&wt_branch_tip, repo, wt_branch); if (err) goto done; if (got_object_id_cmp(worktree->base_commit_id, wt_branch_tip) != 0) { err = got_error(GOT_ERR_REBASE_OUT_OF_DATE); goto done; } err = got_ref_alloc_symref(new_base_branch_ref, new_base_branch_ref_name, wt_branch); if (err) goto done; err = got_ref_write(*new_base_branch_ref, repo); if (err) goto done; /* TODO Lock original branch's ref while rebasing? */ err = got_ref_alloc_symref(&branch_ref, branch_ref_name, branch); if (err) goto done; err = got_ref_write(branch_ref, repo); if (err) goto done; err = got_ref_alloc(tmp_branch, tmp_branch_name, worktree->base_commit_id); if (err) goto done; err = got_ref_write(*tmp_branch, repo); if (err) goto done; err = got_worktree_set_head_ref(worktree, *tmp_branch); if (err) goto done; done: free(fileindex_path); free(tmp_branch_name); free(new_base_branch_ref_name); free(branch_ref_name); if (branch_ref) got_ref_close(branch_ref); if (wt_branch) got_ref_close(wt_branch); free(wt_branch_tip); if (err) { if (*new_base_branch_ref) { got_ref_close(*new_base_branch_ref); *new_base_branch_ref = NULL; } if (*tmp_branch) { got_ref_close(*tmp_branch); *tmp_branch = NULL; } if (*fileindex) { got_fileindex_free(*fileindex); *fileindex = NULL; } lock_worktree(worktree, LOCK_SH); } return err; } const struct got_error * got_worktree_rebase_continue(struct got_object_id **commit_id, struct got_reference **new_base_branch, struct got_reference **tmp_branch, struct got_reference **branch, struct got_fileindex **fileindex, struct got_worktree *worktree, struct got_repository *repo) { const struct got_error *err; char *commit_ref_name = NULL, *new_base_branch_ref_name = NULL; char *tmp_branch_name = NULL, *branch_ref_name = NULL; struct got_reference *commit_ref = NULL, *branch_ref = NULL; char *fileindex_path = NULL; int have_staged_files = 0; *commit_id = NULL; *new_base_branch = NULL; *tmp_branch = NULL; *branch = NULL; *fileindex = NULL; err = lock_worktree(worktree, LOCK_EX); if (err) return err; err = open_fileindex(fileindex, &fileindex_path, worktree, got_repo_get_object_format(repo)); if (err) goto done; err = got_fileindex_for_each_entry_safe(*fileindex, check_staged_file, &have_staged_files); if (err && err->code != GOT_ERR_CANCELLED) goto done; if (have_staged_files) { err = got_error(GOT_ERR_STAGED_PATHS); goto done; } err = get_rebase_tmp_ref_name(&tmp_branch_name, worktree); if (err) goto done; err = get_rebase_branch_symref_name(&branch_ref_name, worktree); if (err) goto done; err = get_rebase_commit_ref_name(&commit_ref_name, worktree); if (err) goto done; err = get_newbase_symref_name(&new_base_branch_ref_name, worktree); if (err) goto done; err = got_ref_open(&branch_ref, repo, branch_ref_name, 0); if (err) goto done; err = got_ref_open(branch, repo, got_ref_get_symref_target(branch_ref), 0); if (err) goto done; err = got_ref_open(&commit_ref, repo, commit_ref_name, 0); if (err) goto done; err = got_ref_resolve(commit_id, repo, commit_ref); if (err) goto done; err = got_ref_open(new_base_branch, repo, new_base_branch_ref_name, 0); if (err) goto done; err = got_ref_open(tmp_branch, repo, tmp_branch_name, 0); if (err) goto done; done: free(commit_ref_name); free(branch_ref_name); free(fileindex_path); if (commit_ref) got_ref_close(commit_ref); if (branch_ref) got_ref_close(branch_ref); if (err) { free(*commit_id); *commit_id = NULL; if (*tmp_branch) { got_ref_close(*tmp_branch); *tmp_branch = NULL; } if (*new_base_branch) { got_ref_close(*new_base_branch); *new_base_branch = NULL; } if (*branch) { got_ref_close(*branch); *branch = NULL; } if (*fileindex) { got_fileindex_free(*fileindex); *fileindex = NULL; } lock_worktree(worktree, LOCK_SH); } return err; } const struct got_error * got_worktree_rebase_in_progress(int *in_progress, struct got_worktree *worktree) { const struct got_error *err; char *tmp_branch_name = NULL; err = get_rebase_tmp_ref_name(&tmp_branch_name, worktree); if (err) return err; *in_progress = (strcmp(tmp_branch_name, worktree->head_ref_name) == 0); free(tmp_branch_name); return NULL; } const struct got_error * got_worktree_rebase_info(char **new_base_branch_name, char **branch_name, struct got_worktree *worktree, struct got_repository *repo) { const struct got_error *err; char *new_base_branch_ref_name = NULL; char *branch_ref_name = NULL; struct got_reference *branch_ref = NULL, *branch = NULL; struct got_reference *new_base_branch = NULL; *new_base_branch_name = NULL; *branch_name = NULL; err = get_rebase_branch_symref_name(&branch_ref_name, worktree); if (err) goto done; err = get_newbase_symref_name(&new_base_branch_ref_name, worktree); if (err) goto done; err = got_ref_open(&branch_ref, repo, branch_ref_name, 0); if (err) goto done; err = got_ref_open(&branch, repo, got_ref_get_symref_target(branch_ref), 0); if (err) goto done; err = got_ref_open(&new_base_branch, repo, new_base_branch_ref_name, 0); if (err) goto done; if (!got_ref_is_symbolic(new_base_branch)) { err = got_error_fmt(GOT_ERR_BAD_REF_TYPE, "%s is not a symbolic reference", got_ref_get_name(branch_ref)); goto done; } *new_base_branch_name = strdup(got_ref_get_symref_target( new_base_branch)); if (*new_base_branch_name == NULL) { err = got_error_from_errno("strdup"); goto done; } *branch_name = strdup(got_ref_get_name(branch)); if (*branch_name == NULL) { err = got_error_from_errno("strdup"); goto done; } done: free(branch_ref_name); if (branch_ref) got_ref_close(branch_ref); if (new_base_branch) got_ref_close(new_base_branch); if (branch) got_ref_close(branch); return err; } static const struct got_error * collect_rebase_commit_msg(struct got_pathlist_head *commitable_paths, const char *diff_path, char **logmsg, void *arg) { *logmsg = arg; return NULL; } static const struct got_error * rebase_status(void *arg, unsigned char status, unsigned char staged_status, const char *path, struct got_object_id *blob_id, struct got_object_id *staged_blob_id, struct got_object_id *commit_id, int dirfd, const char *de_name) { return NULL; } struct collect_merged_paths_arg { got_worktree_checkout_cb progress_cb; void *progress_arg; struct got_pathlist_head *merged_paths; }; static const struct got_error * collect_merged_paths(void *arg, unsigned char status, const char *path) { const struct got_error *err; struct collect_merged_paths_arg *a = arg; char *p; struct got_pathlist_entry *new; err = (*a->progress_cb)(a->progress_arg, status, path); if (err) return err; if (status != GOT_STATUS_MERGE && status != GOT_STATUS_ADD && status != GOT_STATUS_DELETE && status != GOT_STATUS_CONFLICT) return NULL; p = strdup(path); if (p == NULL) return got_error_from_errno("strdup"); err = got_pathlist_insert(&new, a->merged_paths, p, NULL); if (err || new == NULL) free(p); return err; } static const struct got_error * store_commit_id(const char *commit_ref_name, struct got_object_id *commit_id, int is_rebase, struct got_repository *repo) { const struct got_error *err; struct got_reference *commit_ref = NULL; err = got_ref_open(&commit_ref, repo, commit_ref_name, 0); if (err) { if (err->code != GOT_ERR_NOT_REF) goto done; err = got_ref_alloc(&commit_ref, commit_ref_name, commit_id); if (err) goto done; err = got_ref_write(commit_ref, repo); if (err) goto done; } else if (is_rebase) { struct got_object_id *stored_id; int cmp; err = got_ref_resolve(&stored_id, repo, commit_ref); if (err) goto done; cmp = got_object_id_cmp(commit_id, stored_id); free(stored_id); if (cmp != 0) { err = got_error(GOT_ERR_REBASE_COMMITID); goto done; } } done: if (commit_ref) got_ref_close(commit_ref); return err; } static const struct got_error * rebase_merge_files(struct got_pathlist_head *merged_paths, const char *commit_ref_name, struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_object_id *parent_commit_id, struct got_object_id *commit_id, struct got_repository *repo, got_worktree_checkout_cb progress_cb, void *progress_arg, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err; struct got_reference *commit_ref = NULL; struct collect_merged_paths_arg cmp_arg; char *fileindex_path; /* Work tree is locked/unlocked during rebase preparation/teardown. */ err = get_fileindex_path(&fileindex_path, worktree); if (err) return err; cmp_arg.progress_cb = progress_cb; cmp_arg.progress_arg = progress_arg; cmp_arg.merged_paths = merged_paths; err = merge_files(worktree, fileindex, fileindex_path, parent_commit_id, commit_id, repo, collect_merged_paths, &cmp_arg, cancel_cb, cancel_arg); if (commit_ref) got_ref_close(commit_ref); return err; } const struct got_error * got_worktree_rebase_merge_files(struct got_pathlist_head *merged_paths, struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_object_id *parent_commit_id, struct got_object_id *commit_id, struct got_repository *repo, got_worktree_checkout_cb progress_cb, void *progress_arg, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err; char *commit_ref_name; err = get_rebase_commit_ref_name(&commit_ref_name, worktree); if (err) return err; err = store_commit_id(commit_ref_name, commit_id, 1, repo); if (err) goto done; err = rebase_merge_files(merged_paths, commit_ref_name, worktree, fileindex, parent_commit_id, commit_id, repo, progress_cb, progress_arg, cancel_cb, cancel_arg); done: free(commit_ref_name); return err; } const struct got_error * got_worktree_histedit_merge_files(struct got_pathlist_head *merged_paths, struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_object_id *parent_commit_id, struct got_object_id *commit_id, struct got_repository *repo, got_worktree_checkout_cb progress_cb, void *progress_arg, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err; char *commit_ref_name; err = get_histedit_commit_ref_name(&commit_ref_name, worktree); if (err) return err; err = store_commit_id(commit_ref_name, commit_id, 0, repo); if (err) goto done; err = rebase_merge_files(merged_paths, commit_ref_name, worktree, fileindex, parent_commit_id, commit_id, repo, progress_cb, progress_arg, cancel_cb, cancel_arg); done: free(commit_ref_name); return err; } static const struct got_error * rebase_commit(struct got_object_id **new_commit_id, struct got_pathlist_head *merged_paths, struct got_reference *commit_ref, struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_reference *tmp_branch, const char *committer, struct got_commit_object *orig_commit, const char *new_logmsg, int allow_conflict, struct got_repository *repo) { const struct got_error *err, *sync_err; struct got_pathlist_head commitable_paths; struct collect_commitables_arg cc_arg; char *fileindex_path = NULL; struct got_reference *head_ref = NULL; struct got_object_id *head_commit_id = NULL; char *logmsg = NULL; memset(&cc_arg, 0, sizeof(cc_arg)); RB_INIT(&commitable_paths); *new_commit_id = NULL; /* Work tree is locked/unlocked during rebase preparation/teardown. */ err = get_fileindex_path(&fileindex_path, worktree); if (err) return err; cc_arg.commitable_paths = &commitable_paths; cc_arg.worktree = worktree; cc_arg.repo = repo; cc_arg.have_staged_files = 0; cc_arg.commit_conflicts = allow_conflict; /* * If possible get the status of individual files directly to * avoid crawling the entire work tree once per rebased commit. * * Ideally, merged_paths would contain a list of commitables * we could use so we could skip worktree_status() entirely. * However, we would then need carefully keep track of cumulative * effects of operations such as file additions and deletions * in 'got histedit -f' (folding multiple commits into one), * and this extra complexity is not really worth it. */ if (merged_paths) { struct got_pathlist_entry *pe; RB_FOREACH(pe, got_pathlist_head, merged_paths) { err = worktree_status(worktree, pe->path, fileindex, repo, collect_commitables, &cc_arg, NULL, NULL, 1, 0); if (err) goto done; } } else { err = worktree_status(worktree, "", fileindex, repo, collect_commitables, &cc_arg, NULL, NULL, 1, 0); if (err) goto done; } if (RB_EMPTY(&commitable_paths)) { /* No-op change; commit will be elided. */ err = got_ref_delete(commit_ref, repo); if (err) goto done; err = got_error(GOT_ERR_COMMIT_NO_CHANGES); goto done; } err = got_ref_open(&head_ref, repo, worktree->head_ref_name, 0); if (err) goto done; err = got_ref_resolve(&head_commit_id, repo, head_ref); if (err) goto done; if (new_logmsg) { logmsg = strdup(new_logmsg); if (logmsg == NULL) { err = got_error_from_errno("strdup"); goto done; } } else { err = got_object_commit_get_logmsg(&logmsg, orig_commit); if (err) goto done; } /* NB: commit_worktree will call free(logmsg) */ err = commit_worktree(new_commit_id, &commitable_paths, head_commit_id, NULL, worktree, got_object_commit_get_author(orig_commit), committer ? committer : got_object_commit_get_committer(orig_commit), NULL, collect_rebase_commit_msg, logmsg, rebase_status, NULL, repo); if (err) goto done; err = got_ref_change_ref(tmp_branch, *new_commit_id); if (err) goto done; err = got_ref_delete(commit_ref, repo); if (err) goto done; err = update_fileindex_after_commit(worktree, &commitable_paths, *new_commit_id, fileindex, 0); sync_err = sync_fileindex(fileindex, fileindex_path); if (sync_err && err == NULL) err = sync_err; done: free(fileindex_path); free(head_commit_id); if (head_ref) got_ref_close(head_ref); if (err) { free(*new_commit_id); *new_commit_id = NULL; } return err; } const struct got_error * got_worktree_rebase_commit(struct got_object_id **new_commit_id, struct got_pathlist_head *merged_paths, struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_reference *tmp_branch, const char *committer, struct got_commit_object *orig_commit, struct got_object_id *orig_commit_id, int allow_conflict, struct got_repository *repo) { const struct got_error *err; char *commit_ref_name; struct got_reference *commit_ref = NULL; struct got_object_id *commit_id = NULL; err = get_rebase_commit_ref_name(&commit_ref_name, worktree); if (err) return err; err = got_ref_open(&commit_ref, repo, commit_ref_name, 0); if (err) goto done; err = got_ref_resolve(&commit_id, repo, commit_ref); if (err) goto done; if (got_object_id_cmp(commit_id, orig_commit_id) != 0) { err = got_error(GOT_ERR_REBASE_COMMITID); goto done; } err = rebase_commit(new_commit_id, merged_paths, commit_ref, worktree, fileindex, tmp_branch, committer, orig_commit, NULL, allow_conflict, repo); done: if (commit_ref) got_ref_close(commit_ref); free(commit_ref_name); free(commit_id); return err; } const struct got_error * got_worktree_histedit_commit(struct got_object_id **new_commit_id, struct got_pathlist_head *merged_paths, struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_reference *tmp_branch, const char *committer, struct got_commit_object *orig_commit, struct got_object_id *orig_commit_id, const char *new_logmsg, int allow_conflict, struct got_repository *repo) { const struct got_error *err; char *commit_ref_name; struct got_reference *commit_ref = NULL; err = get_histedit_commit_ref_name(&commit_ref_name, worktree); if (err) return err; err = got_ref_open(&commit_ref, repo, commit_ref_name, 0); if (err) goto done; err = rebase_commit(new_commit_id, merged_paths, commit_ref, worktree, fileindex, tmp_branch, committer, orig_commit, new_logmsg, allow_conflict, repo); done: if (commit_ref) got_ref_close(commit_ref); free(commit_ref_name); return err; } const struct got_error * got_worktree_rebase_postpone(struct got_worktree *worktree, struct got_fileindex *fileindex) { if (fileindex) got_fileindex_free(fileindex); return lock_worktree(worktree, LOCK_SH); } static const struct got_error * delete_ref(const char *name, struct got_repository *repo) { const struct got_error *err; struct got_reference *ref; err = got_ref_open(&ref, repo, name, 0); if (err) { if (err->code == GOT_ERR_NOT_REF) return NULL; return err; } err = got_ref_delete(ref, repo); got_ref_close(ref); return err; } static const struct got_error * delete_rebase_refs(struct got_worktree *worktree, struct got_repository *repo) { const struct got_error *err; char *tmp_branch_name = NULL, *new_base_branch_ref_name = NULL; char *branch_ref_name = NULL, *commit_ref_name = NULL; err = get_rebase_tmp_ref_name(&tmp_branch_name, worktree); if (err) goto done; err = delete_ref(tmp_branch_name, repo); if (err) goto done; err = get_newbase_symref_name(&new_base_branch_ref_name, worktree); if (err) goto done; err = delete_ref(new_base_branch_ref_name, repo); if (err) goto done; err = get_rebase_branch_symref_name(&branch_ref_name, worktree); if (err) goto done; err = delete_ref(branch_ref_name, repo); if (err) goto done; err = get_rebase_commit_ref_name(&commit_ref_name, worktree); if (err) goto done; err = delete_ref(commit_ref_name, repo); if (err) goto done; done: free(tmp_branch_name); free(new_base_branch_ref_name); free(branch_ref_name); free(commit_ref_name); return err; } static const struct got_error * create_backup_ref(const char *backup_ref_prefix, struct got_reference *branch, struct got_object_id *new_commit_id, struct got_repository *repo) { const struct got_error *err; struct got_reference *ref = NULL; struct got_object_id *old_commit_id = NULL; const char *branch_name = NULL; char *new_id_str = NULL; char *refname = NULL; branch_name = got_ref_get_name(branch); if (strncmp(branch_name, "refs/heads/", 11) != 0) return got_error(GOT_ERR_BAD_REF_NAME); /* should not happen */ branch_name += 11; err = got_object_id_str(&new_id_str, new_commit_id); if (err) return err; if (asprintf(&refname, "%s/%s/%s", backup_ref_prefix, branch_name, new_id_str) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = got_ref_resolve(&old_commit_id, repo, branch); if (err) goto done; err = got_ref_alloc(&ref, refname, old_commit_id); if (err) goto done; err = got_ref_write(ref, repo); done: free(new_id_str); free(refname); free(old_commit_id); if (ref) got_ref_close(ref); return err; } const struct got_error * got_worktree_rebase_complete(struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_reference *tmp_branch, struct got_reference *rebased_branch, struct got_repository *repo, int create_backup) { const struct got_error *err, *unlockerr, *sync_err; struct got_object_id *new_head_commit_id = NULL; char *fileindex_path = NULL; err = got_ref_resolve(&new_head_commit_id, repo, tmp_branch); if (err) return err; if (create_backup) { err = create_backup_ref(GOT_WORKTREE_REBASE_BACKUP_REF_PREFIX, rebased_branch, new_head_commit_id, repo); if (err) goto done; } err = got_ref_change_ref(rebased_branch, new_head_commit_id); if (err) goto done; err = got_ref_write(rebased_branch, repo); if (err) goto done; err = got_worktree_set_head_ref(worktree, rebased_branch); if (err) goto done; err = delete_rebase_refs(worktree, repo); if (err) goto done; err = get_fileindex_path(&fileindex_path, worktree); if (err) goto done; err = bump_base_commit_id_everywhere(worktree, fileindex, NULL, NULL); sync_err = sync_fileindex(fileindex, fileindex_path); if (sync_err && err == NULL) err = sync_err; done: got_fileindex_free(fileindex); free(fileindex_path); free(new_head_commit_id); unlockerr = lock_worktree(worktree, LOCK_SH); if (unlockerr && err == NULL) err = unlockerr; return err; } static const struct got_error * get_paths_changed_between_commits(struct got_pathlist_head *paths, struct got_object_id *id1, struct got_object_id *id2, struct got_repository *repo) { const struct got_error *err; struct got_commit_object *commit1 = NULL, *commit2 = NULL; struct got_tree_object *tree1 = NULL, *tree2 = NULL; if (id1) { err = got_object_open_as_commit(&commit1, repo, id1); if (err) goto done; err = got_object_open_as_tree(&tree1, repo, got_object_commit_get_tree_id(commit1)); if (err) goto done; } if (id2) { err = got_object_open_as_commit(&commit2, repo, id2); if (err) goto done; err = got_object_open_as_tree(&tree2, repo, got_object_commit_get_tree_id(commit2)); if (err) goto done; } err = got_diff_tree(tree1, tree2, NULL, NULL, -1, -1, "", "", repo, got_diff_tree_collect_changed_paths, paths, 0); if (err) goto done; done: if (commit1) got_object_commit_close(commit1); if (commit2) got_object_commit_close(commit2); if (tree1) got_object_tree_close(tree1); if (tree2) got_object_tree_close(tree2); return err; } static const struct got_error * get_paths_added_between_commits(struct got_pathlist_head *added_paths, struct got_object_id *id1, struct got_object_id *id2, const char *path_prefix, struct got_repository *repo) { const struct got_error *err; struct got_pathlist_head merged_paths; struct got_pathlist_entry *pe; char *abspath = NULL, *wt_path = NULL; RB_INIT(&merged_paths); err = get_paths_changed_between_commits(&merged_paths, id1, id2, repo); if (err) goto done; RB_FOREACH(pe, got_pathlist_head, &merged_paths) { struct got_diff_changed_path *change = pe->data; if (change->status != GOT_STATUS_ADD) continue; if (got_path_is_root_dir(path_prefix)) { wt_path = strdup(pe->path); if (wt_path == NULL) { err = got_error_from_errno("strdup"); goto done; } } else { if (asprintf(&abspath, "/%s", pe->path) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = got_path_skip_common_ancestor(&wt_path, path_prefix, abspath); if (err) goto done; free(abspath); abspath = NULL; } err = got_pathlist_insert(NULL, added_paths, wt_path, NULL); if (err) goto done; wt_path = NULL; } done: got_pathlist_free(&merged_paths, GOT_PATHLIST_FREE_ALL); free(abspath); free(wt_path); return err; } static const struct got_error * get_paths_added_in_commit(struct got_pathlist_head *added_paths, struct got_object_id *id, const char *path_prefix, struct got_repository *repo) { const struct got_error *err; struct got_commit_object *commit = NULL; struct got_object_qid *pid; err = got_object_open_as_commit(&commit, repo, id); if (err) goto done; pid = STAILQ_FIRST(got_object_commit_get_parent_ids(commit)); err = get_paths_added_between_commits(added_paths, pid ? &pid->id : NULL, id, path_prefix, repo); if (err) goto done; done: if (commit) got_object_commit_close(commit); return err; } const struct got_error * got_worktree_rebase_abort(struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_repository *repo, struct got_reference *new_base_branch, got_worktree_checkout_cb progress_cb, void *progress_arg) { const struct got_error *err, *unlockerr, *sync_err; struct got_reference *resolved = NULL; struct got_object_id *commit_id = NULL; struct got_object_id *merged_commit_id = NULL; struct got_commit_object *commit = NULL; char *fileindex_path = NULL; char *commit_ref_name = NULL; struct got_reference *commit_ref = NULL; struct revert_file_args rfa; struct got_object_id *tree_id = NULL; struct got_pathlist_head added_paths; RB_INIT(&added_paths); err = lock_worktree(worktree, LOCK_EX); if (err) return err; err = get_rebase_commit_ref_name(&commit_ref_name, worktree); if (err) goto done; err = got_ref_open(&commit_ref, repo, commit_ref_name, 0); if (err) goto done; err = got_ref_resolve(&merged_commit_id, repo, commit_ref); if (err) goto done; /* * Determine which files in added status can be safely removed * from disk while reverting changes in the work tree. * We want to avoid deleting unrelated files which were added by * the user for conflict resolution purposes. */ err = get_paths_added_in_commit(&added_paths, merged_commit_id, got_worktree_get_path_prefix(worktree), repo); if (err) goto done; err = got_ref_open(&resolved, repo, got_ref_get_symref_target(new_base_branch), 0); if (err) goto done; err = got_worktree_set_head_ref(worktree, resolved); if (err) goto done; /* * XXX commits to the base branch could have happened while * we were busy rebasing; should we store the original commit ID * when rebase begins and read it back here? */ err = got_ref_resolve(&commit_id, repo, resolved); if (err) goto done; err = got_worktree_set_base_commit_id(worktree, repo, commit_id); if (err) goto done; err = got_object_open_as_commit(&commit, repo, worktree->base_commit_id); if (err) goto done; err = got_object_id_by_path(&tree_id, repo, commit, worktree->path_prefix); if (err) goto done; err = delete_rebase_refs(worktree, repo); if (err) goto done; err = get_fileindex_path(&fileindex_path, worktree); if (err) goto done; rfa.worktree = worktree; rfa.fileindex = fileindex; rfa.progress_cb = progress_cb; rfa.progress_arg = progress_arg; rfa.patch_cb = NULL; rfa.patch_arg = NULL; rfa.repo = repo; rfa.unlink_added_files = 1; rfa.added_files_to_unlink = &added_paths; err = worktree_status(worktree, "", fileindex, repo, revert_file, &rfa, NULL, NULL, 1, 0); if (err) goto sync; err = checkout_files(worktree, fileindex, "", tree_id, NULL, repo, progress_cb, progress_arg, NULL, NULL); sync: sync_err = sync_fileindex(fileindex, fileindex_path); if (sync_err && err == NULL) err = sync_err; done: got_pathlist_free(&added_paths, GOT_PATHLIST_FREE_PATH); got_ref_close(resolved); free(tree_id); free(commit_id); free(merged_commit_id); if (commit) got_object_commit_close(commit); if (fileindex) got_fileindex_free(fileindex); free(fileindex_path); free(commit_ref_name); if (commit_ref) got_ref_close(commit_ref); unlockerr = lock_worktree(worktree, LOCK_SH); if (unlockerr && err == NULL) err = unlockerr; return err; } const struct got_error * got_worktree_histedit_prepare(struct got_reference **tmp_branch, struct got_reference **branch_ref, struct got_object_id **base_commit_id, struct got_fileindex **fileindex, struct got_worktree *worktree, struct got_repository *repo) { const struct got_error *err = NULL; char *tmp_branch_name = NULL; char *branch_ref_name = NULL; char *base_commit_ref_name = NULL; char *fileindex_path = NULL; struct check_rebase_ok_arg ok_arg; struct got_reference *wt_branch = NULL; struct got_reference *base_commit_ref = NULL; *tmp_branch = NULL; *branch_ref = NULL; *base_commit_id = NULL; *fileindex = NULL; err = lock_worktree(worktree, LOCK_EX); if (err) return err; err = open_fileindex(fileindex, &fileindex_path, worktree, got_repo_get_object_format(repo)); if (err) goto done; ok_arg.worktree = worktree; ok_arg.repo = repo; err = got_fileindex_for_each_entry_safe(*fileindex, check_rebase_ok, &ok_arg); if (err) goto done; err = get_histedit_tmp_ref_name(&tmp_branch_name, worktree); if (err) goto done; err = get_histedit_branch_symref_name(&branch_ref_name, worktree); if (err) goto done; err = get_histedit_base_commit_ref_name(&base_commit_ref_name, worktree); if (err) goto done; err = got_ref_open(&wt_branch, repo, worktree->head_ref_name, 0); if (err) goto done; err = got_ref_alloc_symref(branch_ref, branch_ref_name, wt_branch); if (err) goto done; err = got_ref_write(*branch_ref, repo); if (err) goto done; err = got_ref_alloc(&base_commit_ref, base_commit_ref_name, worktree->base_commit_id); if (err) goto done; err = got_ref_write(base_commit_ref, repo); if (err) goto done; *base_commit_id = got_object_id_dup(worktree->base_commit_id); if (*base_commit_id == NULL) { err = got_error_from_errno("got_object_id_dup"); goto done; } err = got_ref_alloc(tmp_branch, tmp_branch_name, worktree->base_commit_id); if (err) goto done; err = got_ref_write(*tmp_branch, repo); if (err) goto done; err = got_worktree_set_head_ref(worktree, *tmp_branch); if (err) goto done; done: free(fileindex_path); free(tmp_branch_name); free(branch_ref_name); free(base_commit_ref_name); if (wt_branch) got_ref_close(wt_branch); if (err) { if (*branch_ref) { got_ref_close(*branch_ref); *branch_ref = NULL; } if (*tmp_branch) { got_ref_close(*tmp_branch); *tmp_branch = NULL; } free(*base_commit_id); if (*fileindex) { got_fileindex_free(*fileindex); *fileindex = NULL; } lock_worktree(worktree, LOCK_SH); } return err; } const struct got_error * got_worktree_histedit_postpone(struct got_worktree *worktree, struct got_fileindex *fileindex) { if (fileindex) got_fileindex_free(fileindex); return lock_worktree(worktree, LOCK_SH); } const struct got_error * got_worktree_histedit_in_progress(int *in_progress, struct got_worktree *worktree) { const struct got_error *err; char *tmp_branch_name = NULL; err = get_histedit_tmp_ref_name(&tmp_branch_name, worktree); if (err) return err; *in_progress = (strcmp(tmp_branch_name, worktree->head_ref_name) == 0); free(tmp_branch_name); return NULL; } const struct got_error * got_worktree_histedit_continue(struct got_object_id **commit_id, struct got_reference **tmp_branch, struct got_reference **branch_ref, struct got_object_id **base_commit_id, struct got_fileindex **fileindex, struct got_worktree *worktree, struct got_repository *repo) { const struct got_error *err; char *commit_ref_name = NULL, *base_commit_ref_name = NULL; char *tmp_branch_name = NULL, *branch_ref_name = NULL; struct got_reference *commit_ref = NULL; struct got_reference *base_commit_ref = NULL; char *fileindex_path = NULL; int have_staged_files = 0; *commit_id = NULL; *tmp_branch = NULL; *branch_ref = NULL; *base_commit_id = NULL; *fileindex = NULL; err = lock_worktree(worktree, LOCK_EX); if (err) return err; err = open_fileindex(fileindex, &fileindex_path, worktree, got_repo_get_object_format(repo)); if (err) goto done; err = got_fileindex_for_each_entry_safe(*fileindex, check_staged_file, &have_staged_files); if (err && err->code != GOT_ERR_CANCELLED) goto done; if (have_staged_files) { err = got_error(GOT_ERR_STAGED_PATHS); goto done; } err = get_histedit_tmp_ref_name(&tmp_branch_name, worktree); if (err) goto done; err = get_histedit_branch_symref_name(&branch_ref_name, worktree); if (err) goto done; err = get_histedit_commit_ref_name(&commit_ref_name, worktree); if (err) goto done; err = get_histedit_base_commit_ref_name(&base_commit_ref_name, worktree); if (err) goto done; err = got_ref_open(branch_ref, repo, branch_ref_name, 0); if (err) goto done; err = got_ref_open(&commit_ref, repo, commit_ref_name, 0); if (err) goto done; err = got_ref_resolve(commit_id, repo, commit_ref); if (err) goto done; err = got_ref_open(&base_commit_ref, repo, base_commit_ref_name, 0); if (err) goto done; err = got_ref_resolve(base_commit_id, repo, base_commit_ref); if (err) goto done; err = got_ref_open(tmp_branch, repo, tmp_branch_name, 0); if (err) goto done; done: free(commit_ref_name); free(branch_ref_name); free(fileindex_path); if (commit_ref) got_ref_close(commit_ref); if (base_commit_ref) got_ref_close(base_commit_ref); if (err) { free(*commit_id); *commit_id = NULL; free(*base_commit_id); *base_commit_id = NULL; if (*tmp_branch) { got_ref_close(*tmp_branch); *tmp_branch = NULL; } if (*branch_ref) { got_ref_close(*branch_ref); *branch_ref = NULL; } if (*fileindex) { got_fileindex_free(*fileindex); *fileindex = NULL; } lock_worktree(worktree, LOCK_EX); } return err; } const struct got_error * got_worktree_histedit_info(char **branch_name, struct got_worktree *worktree, struct got_repository *repo) { const struct got_error *err; struct got_reference *branch_ref = NULL; char *branch_ref_name = NULL; *branch_name = NULL; err = get_histedit_branch_symref_name(&branch_ref_name, worktree); if (err) goto done; err = got_ref_open(&branch_ref, repo, branch_ref_name, 0); if (err) goto done; if (!got_ref_is_symbolic(branch_ref)) { err = got_error_fmt(GOT_ERR_BAD_REF_TYPE, "%s is not a symbolic reference", got_ref_get_name(branch_ref)); goto done; } *branch_name = strdup(got_ref_get_symref_target(branch_ref)); if (*branch_name == NULL) { err = got_error_from_errno("strdup"); goto done; } done: free(branch_ref_name); if (branch_ref) got_ref_close(branch_ref); if (err) { free(*branch_name); *branch_name = NULL; } return err; } static const struct got_error * delete_histedit_refs(struct got_worktree *worktree, struct got_repository *repo) { const struct got_error *err; char *tmp_branch_name = NULL, *base_commit_ref_name = NULL; char *branch_ref_name = NULL, *commit_ref_name = NULL; err = get_histedit_tmp_ref_name(&tmp_branch_name, worktree); if (err) goto done; err = delete_ref(tmp_branch_name, repo); if (err) goto done; err = get_histedit_base_commit_ref_name(&base_commit_ref_name, worktree); if (err) goto done; err = delete_ref(base_commit_ref_name, repo); if (err) goto done; err = get_histedit_branch_symref_name(&branch_ref_name, worktree); if (err) goto done; err = delete_ref(branch_ref_name, repo); if (err) goto done; err = get_histedit_commit_ref_name(&commit_ref_name, worktree); if (err) goto done; err = delete_ref(commit_ref_name, repo); if (err) goto done; done: free(tmp_branch_name); free(base_commit_ref_name); free(branch_ref_name); free(commit_ref_name); return err; } const struct got_error * got_worktree_histedit_abort(struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_repository *repo, struct got_reference *branch, struct got_object_id *base_commit_id, got_worktree_checkout_cb progress_cb, void *progress_arg) { const struct got_error *err, *unlockerr, *sync_err; struct got_reference *resolved = NULL; char *fileindex_path = NULL; struct got_object_id *merged_commit_id = NULL; struct got_commit_object *commit = NULL; char *commit_ref_name = NULL; struct got_reference *commit_ref = NULL; struct got_object_id *tree_id = NULL; struct revert_file_args rfa; struct got_pathlist_head added_paths; RB_INIT(&added_paths); err = lock_worktree(worktree, LOCK_EX); if (err) return err; err = get_histedit_commit_ref_name(&commit_ref_name, worktree); if (err) goto done; err = got_ref_open(&commit_ref, repo, commit_ref_name, 0); if (err) { if (err->code != GOT_ERR_NOT_REF) goto done; /* Can happen on early abort due to invalid histedit script. */ commit_ref = NULL; } if (commit_ref) { err = got_ref_resolve(&merged_commit_id, repo, commit_ref); if (err) goto done; /* * Determine which files in added status can be safely removed * from disk while reverting changes in the work tree. * We want to avoid deleting unrelated files added by the * user during conflict resolution or during histedit -e. */ err = get_paths_added_in_commit(&added_paths, merged_commit_id, got_worktree_get_path_prefix(worktree), repo); if (err) goto done; } err = got_ref_open(&resolved, repo, got_ref_get_symref_target(branch), 0); if (err) goto done; err = got_worktree_set_head_ref(worktree, resolved); if (err) goto done; err = got_worktree_set_base_commit_id(worktree, repo, base_commit_id); if (err) goto done; err = got_object_open_as_commit(&commit, repo, worktree->base_commit_id); if (err) goto done; err = got_object_id_by_path(&tree_id, repo, commit, worktree->path_prefix); if (err) goto done; err = delete_histedit_refs(worktree, repo); if (err) goto done; err = get_fileindex_path(&fileindex_path, worktree); if (err) goto done; rfa.worktree = worktree; rfa.fileindex = fileindex; rfa.progress_cb = progress_cb; rfa.progress_arg = progress_arg; rfa.patch_cb = NULL; rfa.patch_arg = NULL; rfa.repo = repo; rfa.unlink_added_files = 1; rfa.added_files_to_unlink = &added_paths; err = worktree_status(worktree, "", fileindex, repo, revert_file, &rfa, NULL, NULL, 1, 0); if (err) goto sync; err = checkout_files(worktree, fileindex, "", tree_id, NULL, repo, progress_cb, progress_arg, NULL, NULL); sync: sync_err = sync_fileindex(fileindex, fileindex_path); if (sync_err && err == NULL) err = sync_err; done: if (resolved) got_ref_close(resolved); if (commit_ref) got_ref_close(commit_ref); free(merged_commit_id); free(tree_id); free(fileindex_path); free(commit_ref_name); unlockerr = lock_worktree(worktree, LOCK_SH); if (unlockerr && err == NULL) err = unlockerr; return err; } const struct got_error * got_worktree_histedit_complete(struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_reference *tmp_branch, struct got_reference *edited_branch, struct got_repository *repo) { const struct got_error *err, *unlockerr, *sync_err; struct got_object_id *new_head_commit_id = NULL; struct got_reference *resolved = NULL; char *fileindex_path = NULL; err = got_ref_resolve(&new_head_commit_id, repo, tmp_branch); if (err) return err; err = got_ref_open(&resolved, repo, got_ref_get_symref_target(edited_branch), 0); if (err) goto done; err = create_backup_ref(GOT_WORKTREE_HISTEDIT_BACKUP_REF_PREFIX, resolved, new_head_commit_id, repo); if (err) goto done; err = got_ref_change_ref(resolved, new_head_commit_id); if (err) goto done; err = got_ref_write(resolved, repo); if (err) goto done; err = got_worktree_set_head_ref(worktree, resolved); if (err) goto done; err = delete_histedit_refs(worktree, repo); if (err) goto done; err = get_fileindex_path(&fileindex_path, worktree); if (err) goto done; err = bump_base_commit_id_everywhere(worktree, fileindex, NULL, NULL); sync_err = sync_fileindex(fileindex, fileindex_path); if (sync_err && err == NULL) err = sync_err; done: got_fileindex_free(fileindex); free(fileindex_path); free(new_head_commit_id); unlockerr = lock_worktree(worktree, LOCK_SH); if (unlockerr && err == NULL) err = unlockerr; return err; } const struct got_error * got_worktree_histedit_skip_commit(struct got_worktree *worktree, struct got_object_id *commit_id, struct got_repository *repo) { const struct got_error *err; char *commit_ref_name; err = get_histedit_commit_ref_name(&commit_ref_name, worktree); if (err) return err; err = store_commit_id(commit_ref_name, commit_id, 0, repo); if (err) goto done; err = delete_ref(commit_ref_name, repo); done: free(commit_ref_name); return err; } const struct got_error * got_worktree_integrate_prepare(struct got_fileindex **fileindex, struct got_reference **branch_ref, struct got_reference **base_branch_ref, struct got_worktree *worktree, const char *refname, struct got_repository *repo) { const struct got_error *err = NULL; char *fileindex_path = NULL; struct check_rebase_ok_arg ok_arg; *fileindex = NULL; *branch_ref = NULL; *base_branch_ref = NULL; err = lock_worktree(worktree, LOCK_EX); if (err) return err; if (strcmp(refname, got_worktree_get_head_ref_name(worktree)) == 0) { err = got_error_msg(GOT_ERR_SAME_BRANCH, "cannot integrate a branch into itself; " "update -b or different branch name required"); goto done; } err = open_fileindex(fileindex, &fileindex_path, worktree, got_repo_get_object_format(repo)); if (err) goto done; /* Preconditions are the same as for rebase. */ ok_arg.worktree = worktree; ok_arg.repo = repo; err = got_fileindex_for_each_entry_safe(*fileindex, check_rebase_ok, &ok_arg); if (err) goto done; err = got_ref_open(branch_ref, repo, refname, 1); if (err) goto done; err = got_ref_open(base_branch_ref, repo, got_worktree_get_head_ref_name(worktree), 1); done: if (err) { if (*branch_ref) { got_ref_close(*branch_ref); *branch_ref = NULL; } if (*base_branch_ref) { got_ref_close(*base_branch_ref); *base_branch_ref = NULL; } if (*fileindex) { got_fileindex_free(*fileindex); *fileindex = NULL; } lock_worktree(worktree, LOCK_SH); } return err; } const struct got_error * got_worktree_integrate_continue(struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_repository *repo, struct got_reference *branch_ref, struct got_reference *base_branch_ref, got_worktree_checkout_cb progress_cb, void *progress_arg, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err = NULL, *sync_err, *unlockerr; char *fileindex_path = NULL; struct got_object_id *tree_id = NULL, *commit_id = NULL; struct got_commit_object *commit = NULL; err = get_fileindex_path(&fileindex_path, worktree); if (err) goto done; err = got_ref_resolve(&commit_id, repo, branch_ref); if (err) goto done; err = got_object_open_as_commit(&commit, repo, commit_id); if (err) goto done; err = got_object_id_by_path(&tree_id, repo, commit, worktree->path_prefix); if (err) goto done; err = got_worktree_set_base_commit_id(worktree, repo, commit_id); if (err) goto done; err = checkout_files(worktree, fileindex, "", tree_id, NULL, repo, progress_cb, progress_arg, cancel_cb, cancel_arg); if (err) goto sync; err = got_ref_change_ref(base_branch_ref, commit_id); if (err) goto sync; err = got_ref_write(base_branch_ref, repo); if (err) goto sync; err = bump_base_commit_id_everywhere(worktree, fileindex, NULL, NULL); sync: sync_err = sync_fileindex(fileindex, fileindex_path); if (sync_err && err == NULL) err = sync_err; done: unlockerr = got_ref_unlock(branch_ref); if (unlockerr && err == NULL) err = unlockerr; got_ref_close(branch_ref); unlockerr = got_ref_unlock(base_branch_ref); if (unlockerr && err == NULL) err = unlockerr; got_ref_close(base_branch_ref); got_fileindex_free(fileindex); free(fileindex_path); free(tree_id); if (commit) got_object_commit_close(commit); unlockerr = lock_worktree(worktree, LOCK_SH); if (unlockerr && err == NULL) err = unlockerr; return err; } const struct got_error * got_worktree_integrate_abort(struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_repository *repo, struct got_reference *branch_ref, struct got_reference *base_branch_ref) { const struct got_error *err = NULL, *unlockerr = NULL; got_fileindex_free(fileindex); err = lock_worktree(worktree, LOCK_SH); unlockerr = got_ref_unlock(branch_ref); if (unlockerr && err == NULL) err = unlockerr; got_ref_close(branch_ref); unlockerr = got_ref_unlock(base_branch_ref); if (unlockerr && err == NULL) err = unlockerr; got_ref_close(base_branch_ref); return err; } const struct got_error * got_worktree_merge_postpone(struct got_worktree *worktree, struct got_fileindex *fileindex) { const struct got_error *err, *sync_err; char *fileindex_path = NULL; err = get_fileindex_path(&fileindex_path, worktree); if (err) goto done; sync_err = sync_fileindex(fileindex, fileindex_path); err = lock_worktree(worktree, LOCK_SH); if (sync_err && err == NULL) err = sync_err; done: got_fileindex_free(fileindex); free(fileindex_path); return err; } static const struct got_error * delete_merge_refs(struct got_worktree *worktree, struct got_repository *repo) { const struct got_error *err; char *branch_refname = NULL, *commit_refname = NULL; err = get_merge_branch_ref_name(&branch_refname, worktree); if (err) goto done; err = delete_ref(branch_refname, repo); if (err) goto done; err = get_merge_commit_ref_name(&commit_refname, worktree); if (err) goto done; err = delete_ref(commit_refname, repo); if (err) goto done; done: free(branch_refname); free(commit_refname); return err; } struct merge_commit_msg_arg { struct got_worktree *worktree; const char *branch_name; }; static const struct got_error * merge_commit_msg_cb(struct got_pathlist_head *commitable_paths, const char *diff_path, char **logmsg, void *arg) { struct merge_commit_msg_arg *a = arg; if (asprintf(logmsg, "merge %s into %s\n", a->branch_name, got_worktree_get_head_ref_name(a->worktree)) == -1) return got_error_from_errno("asprintf"); return NULL; } const struct got_error * got_worktree_merge_branch(struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_object_id *yca_commit_id, struct got_object_id *branch_tip, struct got_repository *repo, got_worktree_checkout_cb progress_cb, void *progress_arg, got_cancel_cb cancel_cb, void *cancel_arg) { const struct got_error *err; char *fileindex_path = NULL; struct check_mixed_commits_args cma; err = get_fileindex_path(&fileindex_path, worktree); if (err) goto done; cma.worktree = worktree; cma.cancel_cb = cancel_cb; cma.cancel_arg = cancel_arg; err = got_fileindex_for_each_entry_safe(fileindex, check_mixed_commits, &cma); if (err) goto done; err = merge_files(worktree, fileindex, fileindex_path, yca_commit_id, branch_tip, repo, progress_cb, progress_arg, cancel_cb, cancel_arg); done: free(fileindex_path); return err; } const struct got_error * got_worktree_merge_commit(struct got_object_id **new_commit_id, struct got_worktree *worktree, struct got_fileindex *fileindex, const char *author, const char *committer, int allow_bad_symlinks, struct got_object_id *branch_tip, const char *branch_name, int allow_conflict, struct got_repository *repo, got_worktree_status_cb status_cb, void *status_arg) { const struct got_error *err = NULL, *sync_err; struct got_pathlist_head commitable_paths; struct collect_commitables_arg cc_arg; struct got_pathlist_entry *pe; struct got_reference *head_ref = NULL; struct got_object_id *head_commit_id = NULL; int have_staged_files = 0; struct merge_commit_msg_arg mcm_arg; char *fileindex_path = NULL; memset(&cc_arg, 0, sizeof(cc_arg)); *new_commit_id = NULL; RB_INIT(&commitable_paths); err = get_fileindex_path(&fileindex_path, worktree); if (err) goto done; err = got_ref_open(&head_ref, repo, worktree->head_ref_name, 0); if (err) goto done; err = got_ref_resolve(&head_commit_id, repo, head_ref); if (err) goto done; err = got_fileindex_for_each_entry_safe(fileindex, check_staged_file, &have_staged_files); if (err && err->code != GOT_ERR_CANCELLED) goto done; if (have_staged_files) { err = got_error(GOT_ERR_MERGE_STAGED_PATHS); goto done; } cc_arg.commitable_paths = &commitable_paths; cc_arg.worktree = worktree; cc_arg.fileindex = fileindex; cc_arg.repo = repo; cc_arg.have_staged_files = have_staged_files; cc_arg.allow_bad_symlinks = allow_bad_symlinks; cc_arg.commit_conflicts = allow_conflict; err = worktree_status(worktree, "", fileindex, repo, collect_commitables, &cc_arg, NULL, NULL, 1, 0); if (err) goto done; mcm_arg.worktree = worktree; mcm_arg.branch_name = branch_name; err = commit_worktree(new_commit_id, &commitable_paths, head_commit_id, branch_tip, worktree, author, committer, NULL, merge_commit_msg_cb, &mcm_arg, status_cb, status_arg, repo); if (err) goto done; err = update_fileindex_after_commit(worktree, &commitable_paths, *new_commit_id, fileindex, have_staged_files); sync_err = sync_fileindex(fileindex, fileindex_path); if (sync_err && err == NULL) err = sync_err; done: RB_FOREACH(pe, got_pathlist_head, &commitable_paths) { struct got_commitable *ct = pe->data; free_commitable(ct); } got_pathlist_free(&commitable_paths, GOT_PATHLIST_FREE_NONE); free(fileindex_path); return err; } const struct got_error * got_worktree_merge_complete(struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_repository *repo) { const struct got_error *err, *unlockerr, *sync_err; char *fileindex_path = NULL; err = delete_merge_refs(worktree, repo); if (err) goto done; err = get_fileindex_path(&fileindex_path, worktree); if (err) goto done; err = bump_base_commit_id_everywhere(worktree, fileindex, NULL, NULL); sync_err = sync_fileindex(fileindex, fileindex_path); if (sync_err && err == NULL) err = sync_err; done: got_fileindex_free(fileindex); free(fileindex_path); unlockerr = lock_worktree(worktree, LOCK_SH); if (unlockerr && err == NULL) err = unlockerr; return err; } const struct got_error * got_worktree_merge_in_progress(int *in_progress, struct got_worktree *worktree, struct got_repository *repo) { const struct got_error *err; char *branch_refname = NULL; struct got_reference *branch_ref = NULL; *in_progress = 0; err = get_merge_branch_ref_name(&branch_refname, worktree); if (err) return err; err = got_ref_open(&branch_ref, repo, branch_refname, 0); free(branch_refname); if (err) { if (err->code != GOT_ERR_NOT_REF) return err; } else *in_progress = 1; return NULL; } const struct got_error *got_worktree_merge_prepare( struct got_fileindex **fileindex, struct got_worktree *worktree, struct got_repository *repo) { const struct got_error *err = NULL; char *fileindex_path = NULL; struct got_reference *wt_branch = NULL; struct got_object_id *wt_branch_tip = NULL; struct check_rebase_ok_arg ok_arg; *fileindex = NULL; err = lock_worktree(worktree, LOCK_EX); if (err) return err; err = open_fileindex(fileindex, &fileindex_path, worktree, got_repo_get_object_format(repo)); if (err) goto done; /* Preconditions are the same as for rebase. */ ok_arg.worktree = worktree; ok_arg.repo = repo; err = got_fileindex_for_each_entry_safe(*fileindex, check_rebase_ok, &ok_arg); if (err) goto done; err = got_ref_open(&wt_branch, repo, worktree->head_ref_name, 0); if (err) goto done; err = got_ref_resolve(&wt_branch_tip, repo, wt_branch); if (err) goto done; if (got_object_id_cmp(worktree->base_commit_id, wt_branch_tip) != 0) { err = got_error(GOT_ERR_MERGE_OUT_OF_DATE); goto done; } done: free(fileindex_path); if (wt_branch) got_ref_close(wt_branch); free(wt_branch_tip); if (err) { if (*fileindex) { got_fileindex_free(*fileindex); *fileindex = NULL; } lock_worktree(worktree, LOCK_SH); } return err; } const struct got_error *got_worktree_merge_write_refs( struct got_worktree *worktree, struct got_reference *branch, struct got_repository *repo) { const struct got_error *err = NULL; char *branch_refname = NULL, *commit_refname = NULL; struct got_reference *branch_ref = NULL, *commit_ref = NULL; struct got_object_id *branch_tip = NULL; err = get_merge_branch_ref_name(&branch_refname, worktree); if (err) return err; err = get_merge_commit_ref_name(&commit_refname, worktree); if (err) return err; err = got_ref_resolve(&branch_tip, repo, branch); if (err) goto done; err = got_ref_alloc_symref(&branch_ref, branch_refname, branch); if (err) goto done; err = got_ref_write(branch_ref, repo); if (err) goto done; err = got_ref_alloc(&commit_ref, commit_refname, branch_tip); if (err) goto done; err = got_ref_write(commit_ref, repo); if (err) goto done; done: free(branch_refname); free(commit_refname); if (branch_ref) got_ref_close(branch_ref); if (commit_ref) got_ref_close(commit_ref); free(branch_tip); return err; } const struct got_error * got_worktree_merge_continue(char **branch_name, struct got_object_id **branch_tip, struct got_fileindex **fileindex, struct got_worktree *worktree, struct got_repository *repo) { const struct got_error *err; char *commit_refname = NULL, *branch_refname = NULL; struct got_reference *commit_ref = NULL, *branch_ref = NULL; char *fileindex_path = NULL; int have_staged_files = 0; *branch_name = NULL; *branch_tip = NULL; *fileindex = NULL; err = lock_worktree(worktree, LOCK_EX); if (err) return err; err = open_fileindex(fileindex, &fileindex_path, worktree, got_repo_get_object_format(repo)); if (err) goto done; err = got_fileindex_for_each_entry_safe(*fileindex, check_staged_file, &have_staged_files); if (err && err->code != GOT_ERR_CANCELLED) goto done; if (have_staged_files) { err = got_error(GOT_ERR_STAGED_PATHS); goto done; } err = get_merge_branch_ref_name(&branch_refname, worktree); if (err) goto done; err = get_merge_commit_ref_name(&commit_refname, worktree); if (err) goto done; err = got_ref_open(&branch_ref, repo, branch_refname, 0); if (err) goto done; if (!got_ref_is_symbolic(branch_ref)) { err = got_error_fmt(GOT_ERR_BAD_REF_TYPE, "%s is not a symbolic reference", got_ref_get_name(branch_ref)); goto done; } *branch_name = strdup(got_ref_get_symref_target(branch_ref)); if (*branch_name == NULL) { err = got_error_from_errno("strdup"); goto done; } err = got_ref_open(&commit_ref, repo, commit_refname, 0); if (err) goto done; err = got_ref_resolve(branch_tip, repo, commit_ref); if (err) goto done; done: free(commit_refname); free(branch_refname); free(fileindex_path); if (commit_ref) got_ref_close(commit_ref); if (branch_ref) got_ref_close(branch_ref); if (err) { if (*branch_name) { free(*branch_name); *branch_name = NULL; } free(*branch_tip); *branch_tip = NULL; if (*fileindex) { got_fileindex_free(*fileindex); *fileindex = NULL; } lock_worktree(worktree, LOCK_SH); } return err; } const struct got_error * got_worktree_merge_info(char **branch_name, struct got_worktree *worktree, struct got_repository *repo) { const struct got_error *err; char *branch_refname = NULL; struct got_reference *branch_ref = NULL; *branch_name = NULL; err = get_merge_branch_ref_name(&branch_refname, worktree); if (err) goto done; err = got_ref_open(&branch_ref, repo, branch_refname, 0); if (err) goto done; if (!got_ref_is_symbolic(branch_ref)) { err = got_error_fmt(GOT_ERR_BAD_REF_TYPE, "%s is not a symbolic reference", got_ref_get_name(branch_ref)); goto done; } *branch_name = strdup(got_ref_get_symref_target(branch_ref)); if (*branch_name == NULL) { err = got_error_from_errno("strdup"); goto done; } done: free(branch_refname); if (branch_ref) got_ref_close(branch_ref); if (err) { if (*branch_name) { free(*branch_name); *branch_name = NULL; } } return err; } const struct got_error * got_worktree_merge_abort(struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_repository *repo, got_worktree_checkout_cb progress_cb, void *progress_arg) { const struct got_error *err, *unlockerr, *sync_err; struct got_commit_object *commit = NULL; char *fileindex_path = NULL; struct revert_file_args rfa; char *commit_ref_name = NULL; struct got_reference *commit_ref = NULL; struct got_object_id *merged_commit_id = NULL; struct got_object_id *tree_id = NULL; struct got_pathlist_head added_paths; RB_INIT(&added_paths); err = get_merge_commit_ref_name(&commit_ref_name, worktree); if (err) goto done; err = got_ref_open(&commit_ref, repo, commit_ref_name, 0); if (err) goto done; err = got_ref_resolve(&merged_commit_id, repo, commit_ref); if (err) goto done; /* * Determine which files in added status can be safely removed * from disk while reverting changes in the work tree. * We want to avoid deleting unrelated files which were added by * the user for conflict resolution purposes. */ err = get_paths_added_between_commits(&added_paths, got_worktree_get_base_commit_id(worktree), merged_commit_id, got_worktree_get_path_prefix(worktree), repo); if (err) goto done; err = got_object_open_as_commit(&commit, repo, worktree->base_commit_id); if (err) goto done; err = got_object_id_by_path(&tree_id, repo, commit, worktree->path_prefix); if (err) goto done; err = delete_merge_refs(worktree, repo); if (err) goto done; err = get_fileindex_path(&fileindex_path, worktree); if (err) goto done; rfa.worktree = worktree; rfa.fileindex = fileindex; rfa.progress_cb = progress_cb; rfa.progress_arg = progress_arg; rfa.patch_cb = NULL; rfa.patch_arg = NULL; rfa.repo = repo; rfa.unlink_added_files = 1; rfa.added_files_to_unlink = &added_paths; err = worktree_status(worktree, "", fileindex, repo, revert_file, &rfa, NULL, NULL, 1, 0); if (err) goto sync; err = checkout_files(worktree, fileindex, "", tree_id, NULL, repo, progress_cb, progress_arg, NULL, NULL); sync: sync_err = sync_fileindex(fileindex, fileindex_path); if (sync_err && err == NULL) err = sync_err; done: free(tree_id); free(merged_commit_id); if (commit) got_object_commit_close(commit); if (fileindex) got_fileindex_free(fileindex); free(fileindex_path); if (commit_ref) got_ref_close(commit_ref); free(commit_ref_name); unlockerr = lock_worktree(worktree, LOCK_SH); if (unlockerr && err == NULL) err = unlockerr; return err; } struct check_stage_ok_arg { struct got_object_id *head_commit_id; struct got_worktree *worktree; struct got_fileindex *fileindex; struct got_repository *repo; int have_changes; }; static const struct got_error * check_stage_ok(void *arg, unsigned char status, unsigned char staged_status, const char *relpath, struct got_object_id *blob_id, struct got_object_id *staged_blob_id, struct got_object_id *commit_id, int dirfd, const char *de_name) { struct check_stage_ok_arg *a = arg; const struct got_error *err = NULL; struct got_fileindex_entry *ie; struct got_object_id base_commit_id; struct got_object_id *base_commit_idp = NULL; char *in_repo_path = NULL, *p; if (status == GOT_STATUS_UNVERSIONED || status == GOT_STATUS_NO_CHANGE) return NULL; if (status == GOT_STATUS_NONEXISTENT) return got_error_set_errno(ENOENT, relpath); ie = got_fileindex_entry_get(a->fileindex, relpath, strlen(relpath)); if (ie == NULL) return got_error_path(relpath, GOT_ERR_FILE_STATUS); if (asprintf(&in_repo_path, "%s%s%s", a->worktree->path_prefix, got_path_is_root_dir(a->worktree->path_prefix) ? "" : "/", relpath) == -1) return got_error_from_errno("asprintf"); if (got_fileindex_entry_has_commit(ie)) { base_commit_idp = got_fileindex_entry_get_commit_id( &base_commit_id, ie); } if (status == GOT_STATUS_CONFLICT) { err = got_error_path(ie->path, GOT_ERR_STAGE_CONFLICT); goto done; } else if (status != GOT_STATUS_ADD && status != GOT_STATUS_MODIFY && status != GOT_STATUS_DELETE) { err = got_error_path(ie->path, GOT_ERR_FILE_STATUS); goto done; } a->have_changes = 1; p = in_repo_path; while (p[0] == '/') p++; err = check_out_of_date(p, status, staged_status, blob_id, base_commit_idp, a->head_commit_id, a->repo, GOT_ERR_STAGE_OUT_OF_DATE); done: free(in_repo_path); return err; } struct stage_path_arg { struct got_worktree *worktree; struct got_fileindex *fileindex; struct got_repository *repo; got_worktree_status_cb status_cb; void *status_arg; got_worktree_patch_cb patch_cb; void *patch_arg; int staged_something; int allow_bad_symlinks; }; static const struct got_error * stage_path(void *arg, unsigned char status, unsigned char staged_status, const char *relpath, struct got_object_id *blob_id, struct got_object_id *staged_blob_id, struct got_object_id *commit_id, int dirfd, const char *de_name) { struct stage_path_arg *a = arg; const struct got_error *err = NULL; struct got_fileindex_entry *ie; char *ondisk_path = NULL, *path_content = NULL; uint32_t stage; struct got_object_id *new_staged_blob_id = NULL; struct stat sb; if (status == GOT_STATUS_UNVERSIONED) return NULL; ie = got_fileindex_entry_get(a->fileindex, relpath, strlen(relpath)); if (ie == NULL) return got_error_path(relpath, GOT_ERR_FILE_STATUS); if (asprintf(&ondisk_path, "%s/%s", a->worktree->root_path, relpath)== -1) return got_error_from_errno("asprintf"); switch (status) { case GOT_STATUS_ADD: case GOT_STATUS_MODIFY: /* XXX could sb.st_mode be passed in by our caller? */ if (lstat(ondisk_path, &sb) == -1) { err = got_error_from_errno2("lstat", ondisk_path); break; } if (a->patch_cb) { if (status == GOT_STATUS_ADD) { int choice = GOT_PATCH_CHOICE_NONE; err = (*a->patch_cb)(&choice, a->patch_arg, status, ie->path, NULL, 1, 1); if (err) break; if (choice != GOT_PATCH_CHOICE_YES) break; } else { int stage_binary_file = 0; err = create_patched_content(&path_content, &stage_binary_file, 0, staged_blob_id ? staged_blob_id : blob_id, ondisk_path, dirfd, de_name, ie->path, a->repo, a->patch_cb, a->patch_arg); if (!stage_binary_file && (err || path_content == NULL)) break; } } err = got_object_blob_create(&new_staged_blob_id, path_content ? path_content : ondisk_path, a->repo); if (err) break; memcpy(&ie->staged_blob, new_staged_blob_id, sizeof(ie->staged_blob)); if (status == GOT_STATUS_ADD || staged_status == GOT_STATUS_ADD) stage = GOT_FILEIDX_STAGE_ADD; else stage = GOT_FILEIDX_STAGE_MODIFY; got_fileindex_entry_stage_set(ie, stage); if (S_ISLNK(sb.st_mode)) { int is_bad_symlink = 0; if (!a->allow_bad_symlinks) { char target_path[PATH_MAX]; ssize_t target_len; target_len = readlink(ondisk_path, target_path, sizeof(target_path)); if (target_len == -1) { err = got_error_from_errno2("readlink", ondisk_path); break; } err = is_bad_symlink_target(&is_bad_symlink, target_path, target_len, ondisk_path, a->worktree->root_path, a->worktree->meta_dir); if (err) break; if (is_bad_symlink) { err = got_error_path(ondisk_path, GOT_ERR_BAD_SYMLINK); break; } } if (is_bad_symlink) got_fileindex_entry_staged_filetype_set(ie, GOT_FILEIDX_MODE_BAD_SYMLINK); else got_fileindex_entry_staged_filetype_set(ie, GOT_FILEIDX_MODE_SYMLINK); } else { got_fileindex_entry_staged_filetype_set(ie, GOT_FILEIDX_MODE_REGULAR_FILE); } a->staged_something = 1; if (a->status_cb == NULL) break; err = (*a->status_cb)(a->status_arg, GOT_STATUS_NO_CHANGE, get_staged_status(ie), relpath, blob_id, new_staged_blob_id, NULL, dirfd, de_name); if (err) break; /* * When staging the reverse of the staged diff, * implicitly unstage the file. */ if (got_object_id_cmp(&ie->staged_blob, &ie->blob) == 0) { got_fileindex_entry_stage_set(ie, GOT_FILEIDX_STAGE_NONE); } break; case GOT_STATUS_DELETE: if (staged_status == GOT_STATUS_DELETE) break; if (a->patch_cb) { int choice = GOT_PATCH_CHOICE_NONE; err = (*a->patch_cb)(&choice, a->patch_arg, status, ie->path, NULL, 1, 1); if (err) break; if (choice == GOT_PATCH_CHOICE_NO) break; if (choice != GOT_PATCH_CHOICE_YES) { err = got_error(GOT_ERR_PATCH_CHOICE); break; } } stage = GOT_FILEIDX_STAGE_DELETE; got_fileindex_entry_stage_set(ie, stage); a->staged_something = 1; if (a->status_cb == NULL) break; err = (*a->status_cb)(a->status_arg, GOT_STATUS_NO_CHANGE, get_staged_status(ie), relpath, NULL, NULL, NULL, dirfd, de_name); break; case GOT_STATUS_NO_CHANGE: break; case GOT_STATUS_CONFLICT: err = got_error_path(relpath, GOT_ERR_STAGE_CONFLICT); break; case GOT_STATUS_NONEXISTENT: err = got_error_set_errno(ENOENT, relpath); break; default: err = got_error_path(relpath, GOT_ERR_FILE_STATUS); break; } if (path_content && unlink(path_content) == -1 && err == NULL) err = got_error_from_errno2("unlink", path_content); free(path_content); free(ondisk_path); free(new_staged_blob_id); return err; } const struct got_error * got_worktree_stage(struct got_worktree *worktree, struct got_pathlist_head *paths, got_worktree_status_cb status_cb, void *status_arg, got_worktree_patch_cb patch_cb, void *patch_arg, int allow_bad_symlinks, struct got_repository *repo) { const struct got_error *err = NULL, *sync_err, *unlockerr; struct got_pathlist_entry *pe; struct got_fileindex *fileindex = NULL; char *fileindex_path = NULL; struct got_reference *head_ref = NULL; struct got_object_id *head_commit_id = NULL; struct check_stage_ok_arg oka; struct stage_path_arg spa; err = lock_worktree(worktree, LOCK_EX); if (err) return err; err = got_ref_open(&head_ref, repo, got_worktree_get_head_ref_name(worktree), 0); if (err) goto done; err = got_ref_resolve(&head_commit_id, repo, head_ref); if (err) goto done; err = open_fileindex(&fileindex, &fileindex_path, worktree, got_repo_get_object_format(repo)); if (err) goto done; /* Check pre-conditions before staging anything. */ oka.head_commit_id = head_commit_id; oka.worktree = worktree; oka.fileindex = fileindex; oka.repo = repo; oka.have_changes = 0; RB_FOREACH(pe, got_pathlist_head, paths) { err = worktree_status(worktree, pe->path, fileindex, repo, check_stage_ok, &oka, NULL, NULL, 1, 0); if (err) goto done; } if (!oka.have_changes) { err = got_error(GOT_ERR_STAGE_NO_CHANGE); goto done; } spa.worktree = worktree; spa.fileindex = fileindex; spa.repo = repo; spa.patch_cb = patch_cb; spa.patch_arg = patch_arg; spa.status_cb = status_cb; spa.status_arg = status_arg; spa.staged_something = 0; spa.allow_bad_symlinks = allow_bad_symlinks; RB_FOREACH(pe, got_pathlist_head, paths) { err = worktree_status(worktree, pe->path, fileindex, repo, stage_path, &spa, NULL, NULL, 1, 0); if (err) goto done; } if (!spa.staged_something) { err = got_error(GOT_ERR_STAGE_NO_CHANGE); goto done; } sync_err = sync_fileindex(fileindex, fileindex_path); if (sync_err && err == NULL) err = sync_err; done: if (head_ref) got_ref_close(head_ref); free(head_commit_id); free(fileindex_path); if (fileindex) got_fileindex_free(fileindex); unlockerr = lock_worktree(worktree, LOCK_SH); if (unlockerr && err == NULL) err = unlockerr; return err; } struct unstage_path_arg { struct got_worktree *worktree; struct got_fileindex *fileindex; struct got_repository *repo; got_worktree_checkout_cb progress_cb; void *progress_arg; got_worktree_patch_cb patch_cb; void *patch_arg; }; static const struct got_error * create_unstaged_content(char **path_unstaged_content, char **path_new_staged_content, int *confirm_binary_change, struct got_object_id *blob_id, struct got_object_id *staged_blob_id, const char *relpath, struct got_repository *repo, got_worktree_patch_cb patch_cb, void *patch_arg) { const struct got_error *err, *free_err; struct got_blob_object *blob = NULL, *staged_blob = NULL; FILE *f1 = NULL, *f2 = NULL, *outfile = NULL, *rejectfile = NULL; char *path1 = NULL, *path2 = NULL, *label1 = NULL; struct got_diffreg_result *diffreg_result = NULL; int choice, line_cur1 = 1, line_cur2 = 1, n = 0, nchunks_used = 0; int have_content = 0, have_rejected_content = 0, i = 0, nchanges = 0; int fd1 = -1, fd2 = -1; *confirm_binary_change = 0; *path_unstaged_content = NULL; *path_new_staged_content = NULL; err = got_object_id_str(&label1, blob_id); if (err) return err; fd1 = got_opentempfd(); if (fd1 == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } fd2 = got_opentempfd(); if (fd2 == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } err = got_object_open_as_blob(&blob, repo, blob_id, 8192, fd1); if (err) goto done; err = got_opentemp_named(&path1, &f1, "got-unstage-blob-base", ""); if (err) goto done; err = got_object_blob_dump_to_file(NULL, NULL, NULL, f1, blob); if (err) goto done; err = got_object_open_as_blob(&staged_blob, repo, staged_blob_id, 8192, fd2); if (err) goto done; err = got_opentemp_named(&path2, &f2, "got-unstage-blob-staged", ""); if (err) goto done; err = got_object_blob_dump_to_file(NULL, NULL, NULL, f2, staged_blob); if (err) goto done; err = got_diff_files(&diffreg_result, f1, 1, label1, f2, 1, path2, 3, 0, 1, NULL, GOT_DIFF_ALGORITHM_MYERS); if (err) goto done; if (diff_result_has_binary(diffreg_result->result)) { err = accept_or_reject_binary_change(&choice, relpath, patch_cb, patch_arg); if (err == NULL && choice == GOT_PATCH_CHOICE_YES) *confirm_binary_change = 1; goto done; } err = got_opentemp_named(path_unstaged_content, &outfile, "got-unstaged-content", ""); if (err) goto done; err = got_opentemp_named(path_new_staged_content, &rejectfile, "got-new-staged-content", ""); if (err) goto done; if (fseek(f1, 0L, SEEK_SET) == -1) { err = got_ferror(f1, GOT_ERR_IO); goto done; } if (fseek(f2, 0L, SEEK_SET) == -1) { err = got_ferror(f2, GOT_ERR_IO); goto done; } /* Count the number of actual changes in the diff result. */ for (n = 0; n < diffreg_result->result->chunks.len; n += nchunks_used) { struct diff_chunk_context cc = {}; diff_chunk_context_load_change(&cc, &nchunks_used, diffreg_result->result, n, 0); nchanges++; } for (n = 0; n < diffreg_result->result->chunks.len; n += nchunks_used) { err = apply_or_reject_change(&choice, &nchunks_used, diffreg_result->result, n, relpath, f1, f2, &line_cur1, &line_cur2, outfile, rejectfile, ++i, nchanges, patch_cb, patch_arg); if (err) goto done; if (choice == GOT_PATCH_CHOICE_YES) have_content = 1; else have_rejected_content = 1; if (choice == GOT_PATCH_CHOICE_QUIT) break; } if (have_content || have_rejected_content) err = copy_remaining_content(f1, f2, &line_cur1, &line_cur2, outfile, rejectfile); done: free(label1); if (fd1 != -1 && close(fd1) == -1 && err == NULL) err = got_error_from_errno("close"); if (blob) got_object_blob_close(blob); if (fd2 != -1 && close(fd2) == -1 && err == NULL) err = got_error_from_errno("close"); if (staged_blob) got_object_blob_close(staged_blob); free_err = got_diffreg_result_free(diffreg_result); if (free_err && err == NULL) err = free_err; if (f1 && fclose(f1) == EOF && err == NULL) err = got_error_from_errno2("fclose", path1); if (f2 && fclose(f2) == EOF && err == NULL) err = got_error_from_errno2("fclose", path2); if (outfile && fclose(outfile) == EOF && err == NULL) err = got_error_from_errno2("fclose", *path_unstaged_content); if (rejectfile && fclose(rejectfile) == EOF && err == NULL) err = got_error_from_errno2("fclose", *path_new_staged_content); if (path1 && unlink(path1) == -1 && err == NULL) err = got_error_from_errno2("unlink", path1); if (path2 && unlink(path2) == -1 && err == NULL) err = got_error_from_errno2("unlink", path2); if (err || !have_content) { if (*path_unstaged_content && unlink(*path_unstaged_content) == -1 && err == NULL) err = got_error_from_errno2("unlink", *path_unstaged_content); free(*path_unstaged_content); *path_unstaged_content = NULL; } if (err || !have_content || !have_rejected_content) { if (*path_new_staged_content && unlink(*path_new_staged_content) == -1 && err == NULL) err = got_error_from_errno2("unlink", *path_new_staged_content); free(*path_new_staged_content); *path_new_staged_content = NULL; } free(path1); free(path2); return err; } static const struct got_error * unstage_hunks(int *confirm_binary_change, struct got_object_id *staged_blob_id, struct got_blob_object *blob_base, struct got_object_id *blob_id, struct got_fileindex_entry *ie, const char *ondisk_path, const char *label_orig, struct got_worktree *worktree, struct got_repository *repo, got_worktree_patch_cb patch_cb, void *patch_arg, got_worktree_checkout_cb progress_cb, void *progress_arg) { const struct got_error *err = NULL; char *path_unstaged_content = NULL; char *path_new_staged_content = NULL; char *parent = NULL, *base_path = NULL; char *blob_base_path = NULL; struct got_object_id *new_staged_blob_id = NULL; FILE *f = NULL, *f_base = NULL, *f_deriv2 = NULL; struct stat sb; err = create_unstaged_content(&path_unstaged_content, &path_new_staged_content, confirm_binary_change, blob_id, staged_blob_id, ie->path, repo, patch_cb, patch_arg); if (err) return err; if (path_unstaged_content == NULL) return NULL; if (path_new_staged_content) { err = got_object_blob_create(&new_staged_blob_id, path_new_staged_content, repo); if (err) goto done; } f = fopen(path_unstaged_content, "re"); if (f == NULL) { err = got_error_from_errno2("fopen", path_unstaged_content); goto done; } if (fstat(fileno(f), &sb) == -1) { err = got_error_from_errno2("fstat", path_unstaged_content); goto done; } if (got_fileindex_entry_staged_filetype_get(ie) == GOT_FILEIDX_MODE_SYMLINK && sb.st_size < PATH_MAX) { char link_target[PATH_MAX]; size_t r; r = fread(link_target, 1, sizeof(link_target), f); if (r == 0 && ferror(f)) { err = got_error_from_errno("fread"); goto done; } if (r >= sizeof(link_target)) { /* should not happen */ err = got_error(GOT_ERR_NO_SPACE); goto done; } link_target[r] = '\0'; err = merge_symlink(worktree, blob_base, ondisk_path, ie->path, label_orig, link_target, worktree->base_commit_id, repo, progress_cb, progress_arg); } else { int local_changes_subsumed; err = got_path_dirname(&parent, ondisk_path); if (err) return err; if (asprintf(&base_path, "%s/got-unstage-blob-orig", parent) == -1) { err = got_error_from_errno("asprintf"); base_path = NULL; goto done; } err = got_opentemp_named(&blob_base_path, &f_base, base_path, ""); if (err) goto done; err = got_object_blob_dump_to_file(NULL, NULL, NULL, f_base, blob_base); if (err) goto done; /* * In order the run a 3-way merge with a symlink we copy the symlink's * target path into a temporary file and use that file with diff3. */ if (S_ISLNK(got_fileindex_perms_to_st(ie))) { err = dump_symlink_target_path_to_file(&f_deriv2, ondisk_path); if (err) goto done; } else { int fd; fd = open(ondisk_path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (fd == -1) { err = got_error_from_errno2("open", ondisk_path); goto done; } f_deriv2 = fdopen(fd, "r"); if (f_deriv2 == NULL) { err = got_error_from_errno2("fdopen", ondisk_path); close(fd); goto done; } } err = merge_file(&local_changes_subsumed, worktree, f_base, f, f_deriv2, ondisk_path, ie->path, got_fileindex_perms_to_st(ie), label_orig, "unstaged", NULL, GOT_DIFF_ALGORITHM_MYERS, repo, progress_cb, progress_arg); } if (err) goto done; if (new_staged_blob_id) { memcpy(&ie->staged_blob, new_staged_blob_id, sizeof(ie->staged_blob)); } else { got_fileindex_entry_stage_set(ie, GOT_FILEIDX_STAGE_NONE); got_fileindex_entry_staged_filetype_set(ie, 0); } done: free(new_staged_blob_id); if (path_unstaged_content && unlink(path_unstaged_content) == -1 && err == NULL) err = got_error_from_errno2("unlink", path_unstaged_content); if (path_new_staged_content && unlink(path_new_staged_content) == -1 && err == NULL) err = got_error_from_errno2("unlink", path_new_staged_content); if (blob_base_path && unlink(blob_base_path) == -1 && err == NULL) err = got_error_from_errno2("unlink", blob_base_path); if (f_base && fclose(f_base) == EOF && err == NULL) err = got_error_from_errno2("fclose", path_unstaged_content); if (f && fclose(f) == EOF && err == NULL) err = got_error_from_errno2("fclose", path_unstaged_content); if (f_deriv2 && fclose(f_deriv2) == EOF && err == NULL) err = got_error_from_errno2("fclose", ondisk_path); free(path_unstaged_content); free(path_new_staged_content); free(blob_base_path); free(parent); free(base_path); return err; } static const struct got_error * unstage_path(void *arg, unsigned char status, unsigned char staged_status, const char *relpath, struct got_object_id *blob_id, struct got_object_id *staged_blob_id, struct got_object_id *commit_id, int dirfd, const char *de_name) { const struct got_error *err = NULL; struct unstage_path_arg *a = arg; struct got_fileindex_entry *ie; struct got_blob_object *blob_base = NULL, *blob_staged = NULL; char *ondisk_path = NULL; char *id_str = NULL, *label_orig = NULL; int local_changes_subsumed; struct stat sb; int fd1 = -1, fd2 = -1; if (staged_status != GOT_STATUS_ADD && staged_status != GOT_STATUS_MODIFY && staged_status != GOT_STATUS_DELETE) return NULL; ie = got_fileindex_entry_get(a->fileindex, relpath, strlen(relpath)); if (ie == NULL) return got_error_path(relpath, GOT_ERR_FILE_STATUS); if (asprintf(&ondisk_path, "%s/%s", a->worktree->root_path, relpath) == -1) return got_error_from_errno("asprintf"); err = got_object_id_str(&id_str, commit_id ? commit_id : a->worktree->base_commit_id); if (err) goto done; if (asprintf(&label_orig, "%s: commit %s", GOT_MERGE_LABEL_BASE, id_str) == -1) { err = got_error_from_errno("asprintf"); goto done; } fd1 = got_opentempfd(); if (fd1 == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } fd2 = got_opentempfd(); if (fd2 == -1) { err = got_error_from_errno("got_opentempfd"); goto done; } switch (staged_status) { case GOT_STATUS_MODIFY: err = got_object_open_as_blob(&blob_base, a->repo, blob_id, 8192, fd1); if (err) break; /* fall through */ case GOT_STATUS_ADD: if (status == GOT_STATUS_MISSING) { /* Cannot merge changes into missing files. */ err = (*a->progress_cb)(a->progress_arg, status, relpath); goto done; } if (a->patch_cb) { if (staged_status == GOT_STATUS_ADD) { int choice = GOT_PATCH_CHOICE_NONE; err = (*a->patch_cb)(&choice, a->patch_arg, staged_status, ie->path, NULL, 1, 1); if (err) break; if (choice != GOT_PATCH_CHOICE_YES) break; } else { int unstage_binary_file = 0; err = unstage_hunks(&unstage_binary_file, staged_blob_id, blob_base, blob_id, ie, ondisk_path, label_orig, a->worktree, a->repo, a->patch_cb, a->patch_arg, a->progress_cb, a->progress_arg); if (!unstage_binary_file) break; /* Done with this file. */ } } err = got_object_open_as_blob(&blob_staged, a->repo, staged_blob_id, 8192, fd2); if (err) break; switch (got_fileindex_entry_staged_filetype_get(ie)) { case GOT_FILEIDX_MODE_BAD_SYMLINK: case GOT_FILEIDX_MODE_REGULAR_FILE: err = merge_blob(&local_changes_subsumed, a->worktree, blob_base, ondisk_path, relpath, got_fileindex_perms_to_st(ie), label_orig, blob_staged, commit_id ? commit_id : a->worktree->base_commit_id, a->repo, a->progress_cb, a->progress_arg); break; case GOT_FILEIDX_MODE_SYMLINK: if (S_ISLNK(got_fileindex_perms_to_st(ie))) { char *staged_target; err = got_object_blob_read_to_str( &staged_target, blob_staged); if (err) goto done; err = merge_symlink(a->worktree, blob_base, ondisk_path, relpath, label_orig, staged_target, commit_id ? commit_id : a->worktree->base_commit_id, a->repo, a->progress_cb, a->progress_arg); free(staged_target); } else { err = merge_blob(&local_changes_subsumed, a->worktree, blob_base, ondisk_path, relpath, got_fileindex_perms_to_st(ie), label_orig, blob_staged, commit_id ? commit_id : a->worktree->base_commit_id, a->repo, a->progress_cb, a->progress_arg); } break; default: err = got_error_path(relpath, GOT_ERR_BAD_FILETYPE); break; } if (err == NULL) { got_fileindex_entry_stage_set(ie, GOT_FILEIDX_STAGE_NONE); got_fileindex_entry_staged_filetype_set(ie, 0); } break; case GOT_STATUS_DELETE: if (a->patch_cb) { int choice = GOT_PATCH_CHOICE_NONE; err = (*a->patch_cb)(&choice, a->patch_arg, staged_status, ie->path, NULL, 1, 1); if (err) break; if (choice == GOT_PATCH_CHOICE_NO) break; if (choice != GOT_PATCH_CHOICE_YES) { err = got_error(GOT_ERR_PATCH_CHOICE); break; } } got_fileindex_entry_stage_set(ie, GOT_FILEIDX_STAGE_NONE); got_fileindex_entry_staged_filetype_set(ie, 0); err = get_file_status(&status, &sb, ie, ondisk_path, dirfd, de_name, a->repo); if (err) break; err = (*a->progress_cb)(a->progress_arg, status, relpath); break; } done: free(ondisk_path); if (fd1 != -1 && close(fd1) == -1 && err == NULL) err = got_error_from_errno("close"); if (blob_base) got_object_blob_close(blob_base); if (fd2 != -1 && close(fd2) == -1 && err == NULL) err = got_error_from_errno("close"); if (blob_staged) got_object_blob_close(blob_staged); free(id_str); free(label_orig); return err; } const struct got_error * got_worktree_unstage(struct got_worktree *worktree, struct got_pathlist_head *paths, got_worktree_checkout_cb progress_cb, void *progress_arg, got_worktree_patch_cb patch_cb, void *patch_arg, struct got_repository *repo) { const struct got_error *err = NULL, *sync_err, *unlockerr; struct got_pathlist_entry *pe; struct got_fileindex *fileindex = NULL; char *fileindex_path = NULL; struct unstage_path_arg upa; err = lock_worktree(worktree, LOCK_EX); if (err) return err; err = open_fileindex(&fileindex, &fileindex_path, worktree, got_repo_get_object_format(repo)); if (err) goto done; upa.worktree = worktree; upa.fileindex = fileindex; upa.repo = repo; upa.progress_cb = progress_cb; upa.progress_arg = progress_arg; upa.patch_cb = patch_cb; upa.patch_arg = patch_arg; RB_FOREACH(pe, got_pathlist_head, paths) { err = worktree_status(worktree, pe->path, fileindex, repo, unstage_path, &upa, NULL, NULL, 1, 0); if (err) goto done; } sync_err = sync_fileindex(fileindex, fileindex_path); if (sync_err && err == NULL) err = sync_err; done: free(fileindex_path); if (fileindex) got_fileindex_free(fileindex); unlockerr = lock_worktree(worktree, LOCK_SH); if (unlockerr && err == NULL) err = unlockerr; return err; } struct report_file_info_arg { struct got_worktree *worktree; got_worktree_path_info_cb info_cb; void *info_arg; struct got_pathlist_head *paths; got_cancel_cb cancel_cb; void *cancel_arg; }; static const struct got_error * report_file_info(void *arg, struct got_fileindex_entry *ie) { const struct got_error *err; struct report_file_info_arg *a = arg; struct got_pathlist_entry *pe; struct got_object_id blob_id, staged_blob_id, commit_id; struct got_object_id *blob_idp = NULL, *staged_blob_idp = NULL; struct got_object_id *commit_idp = NULL; int stage; if (a->cancel_cb) { err = a->cancel_cb(a->cancel_arg); if (err) return err; } RB_FOREACH(pe, got_pathlist_head, a->paths) { if (pe->path_len == 0 || strcmp(pe->path, ie->path) == 0 || got_path_is_child(ie->path, pe->path, pe->path_len)) break; } if (pe == NULL) /* not found */ return NULL; if (got_fileindex_entry_has_blob(ie)) blob_idp = got_fileindex_entry_get_blob_id(&blob_id, ie); stage = got_fileindex_entry_stage_get(ie); if (stage == GOT_FILEIDX_STAGE_MODIFY || stage == GOT_FILEIDX_STAGE_ADD) { staged_blob_idp = got_fileindex_entry_get_staged_blob_id( &staged_blob_id, ie); } if (got_fileindex_entry_has_commit(ie)) commit_idp = got_fileindex_entry_get_commit_id(&commit_id, ie); return a->info_cb(a->info_arg, ie->path, got_fileindex_perms_to_st(ie), (time_t)ie->mtime_sec, blob_idp, staged_blob_idp, commit_idp); } const struct got_error * got_worktree_path_info_prepare(struct got_fileindex **fileindex, struct got_worktree *worktree, struct got_repository *repo) { const struct got_error *err; char *fileindex_path; err = lock_worktree(worktree, LOCK_SH); if (err) return err; err = open_fileindex(fileindex, &fileindex_path, worktree, got_repo_get_object_format(repo)); free(fileindex_path); return err; } uint32_t got_worktree_get_fileindex_version(struct got_fileindex *fileindex) { return got_fileindex_get_version(fileindex); } const struct got_error * got_worktree_path_info(struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_pathlist_head *paths, got_worktree_path_info_cb info_cb, void *info_arg, got_cancel_cb cancel_cb, void *cancel_arg) { struct report_file_info_arg arg; arg.worktree = worktree; arg.info_cb = info_cb; arg.info_arg = info_arg; arg.paths = paths; arg.cancel_cb = cancel_cb; arg.cancel_arg = cancel_arg; return got_fileindex_for_each_entry_safe(fileindex, report_file_info, &arg); } const struct got_error * got_worktree_path_info_complete(struct got_fileindex *fileindex, struct got_worktree *worktree) { if (fileindex) got_fileindex_free(fileindex); return lock_worktree(worktree, LOCK_UN); } static const struct got_error * patch_check_path(const char *p, char **path, unsigned char *status, unsigned char *staged_status, struct got_fileindex *fileindex, struct got_worktree *worktree, struct got_repository *repo) { const struct got_error *err; struct got_fileindex_entry *ie; struct stat sb; char *ondisk_path = NULL; err = got_worktree_resolve_path(path, worktree, p); if (err) return err; if (asprintf(&ondisk_path, "%s%s%s", worktree->root_path, *path[0] ? "/" : "", *path) == -1) return got_error_from_errno("asprintf"); ie = got_fileindex_entry_get(fileindex, *path, strlen(*path)); if (ie) { *staged_status = get_staged_status(ie); err = get_file_status(status, &sb, ie, ondisk_path, -1, NULL, repo); if (err) goto done; } else { *staged_status = GOT_STATUS_NO_CHANGE; *status = GOT_STATUS_UNVERSIONED; if (lstat(ondisk_path, &sb) == -1) { if (errno != ENOENT) { err = got_error_from_errno2("lstat", ondisk_path); goto done; } *status = GOT_STATUS_NONEXISTENT; } } done: free(ondisk_path); return err; } static const struct got_error * patch_can_rm(const char *path, unsigned char status, unsigned char staged_status) { if (status == GOT_STATUS_NONEXISTENT) return got_error_set_errno(ENOENT, path); if (status != GOT_STATUS_NO_CHANGE && status != GOT_STATUS_ADD && status != GOT_STATUS_MODIFY && status != GOT_STATUS_MODE_CHANGE) return got_error_path(path, GOT_ERR_FILE_STATUS); if (staged_status == GOT_STATUS_DELETE) return got_error_path(path, GOT_ERR_FILE_STATUS); return NULL; } static const struct got_error * patch_can_add(const char *path, unsigned char status) { if (status != GOT_STATUS_NONEXISTENT) return got_error_path(path, GOT_ERR_FILE_STATUS); return NULL; } static const struct got_error * patch_can_edit(const char *path, unsigned char status, unsigned char staged_status) { if (status == GOT_STATUS_NONEXISTENT) return got_error_set_errno(ENOENT, path); if (status != GOT_STATUS_NO_CHANGE && status != GOT_STATUS_ADD && status != GOT_STATUS_MODIFY) return got_error_path(path, GOT_ERR_FILE_STATUS); if (staged_status == GOT_STATUS_DELETE) return got_error_path(path, GOT_ERR_FILE_STATUS); return NULL; } const struct got_error * got_worktree_patch_prepare(struct got_fileindex **fileindex, char **fileindex_path, struct got_worktree *worktree, struct got_repository *repo) { const struct got_error *err; err = lock_worktree(worktree, LOCK_EX); if (err) return err; return open_fileindex(fileindex, fileindex_path, worktree, got_repo_get_object_format(repo)); } const struct got_error * got_worktree_patch_check_path(const char *old, const char *new, char **oldpath, char **newpath, struct got_worktree *worktree, struct got_repository *repo, struct got_fileindex *fileindex) { const struct got_error *err = NULL; int file_renamed = 0; unsigned char status_old, staged_status_old; unsigned char status_new, staged_status_new; *oldpath = NULL; *newpath = NULL; err = patch_check_path(old != NULL ? old : new, oldpath, &status_old, &staged_status_old, fileindex, worktree, repo); if (err) goto done; err = patch_check_path(new != NULL ? new : old, newpath, &status_new, &staged_status_new, fileindex, worktree, repo); if (err) goto done; if (old != NULL && new != NULL && strcmp(old, new) != 0) file_renamed = 1; if (old != NULL && new == NULL) err = patch_can_rm(*oldpath, status_old, staged_status_old); else if (file_renamed) { err = patch_can_rm(*oldpath, status_old, staged_status_old); if (err == NULL) err = patch_can_add(*newpath, status_new); } else if (old == NULL) err = patch_can_add(*newpath, status_new); else err = patch_can_edit(*newpath, status_new, staged_status_new); done: if (err) { free(*oldpath); *oldpath = NULL; free(*newpath); *newpath = NULL; } return err; } const struct got_error * got_worktree_patch_schedule_add(const char *path, struct got_repository *repo, struct got_worktree *worktree, struct got_fileindex *fileindex, got_worktree_checkout_cb progress_cb, void *progress_arg) { struct schedule_addition_args saa; memset(&saa, 0, sizeof(saa)); saa.worktree = worktree; saa.fileindex = fileindex; saa.progress_cb = progress_cb; saa.progress_arg = progress_arg; saa.repo = repo; return worktree_status(worktree, path, fileindex, repo, schedule_addition, &saa, NULL, NULL, 1, 0); } const struct got_error * got_worktree_patch_schedule_rm(const char *path, struct got_repository *repo, struct got_worktree *worktree, struct got_fileindex *fileindex, got_worktree_delete_cb progress_cb, void *progress_arg) { const struct got_error *err; struct schedule_deletion_args sda; char *ondisk_status_path; memset(&sda, 0, sizeof(sda)); sda.worktree = worktree; sda.fileindex = fileindex; sda.progress_cb = progress_cb; sda.progress_arg = progress_arg; sda.repo = repo; sda.delete_local_mods = 0; sda.keep_on_disk = 0; sda.ignore_missing_paths = 0; sda.status_codes = NULL; if (asprintf(&ondisk_status_path, "%s/%s", got_worktree_get_root_path(worktree), path) == -1) return got_error_from_errno("asprintf"); sda.status_path = ondisk_status_path; sda.status_path_len = strlen(ondisk_status_path); err = worktree_status(worktree, path, fileindex, repo, schedule_for_deletion, &sda, NULL, NULL, 1, 1); free(ondisk_status_path); return err; } const struct got_error * got_worktree_patch_complete(struct got_worktree *worktree, struct got_fileindex *fileindex, const char *fileindex_path) { const struct got_error *err = NULL, *unlock_err; if (fileindex) { err = sync_fileindex(fileindex, fileindex_path); got_fileindex_free(fileindex); } unlock_err = lock_worktree(worktree, LOCK_UN); if (unlock_err && err == NULL) err = unlock_err; return err; } got-portable-0.111/lib/sigs.c0000644000175000017500000002243115001741021011444 /* * Copyright (c) 2022 Josh Rickmar * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_date.h" #include "got_object.h" #include "got_opentemp.h" #include "got_sigs.h" #include "buf.h" #ifndef MIN #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b)) #endif #ifndef nitems #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) #endif #ifndef GOT_TAG_PATH_SSH_KEYGEN #define GOT_TAG_PATH_SSH_KEYGEN "/usr/bin/ssh-keygen" #endif #ifndef GOT_TAG_PATH_SIGNIFY #define GOT_TAG_PATH_SIGNIFY "/usr/bin/signify" #endif const struct got_error * got_sigs_apply_unveil(void) { if (unveil(GOT_TAG_PATH_SSH_KEYGEN, "x") != 0) { return got_error_from_errno2("unveil", GOT_TAG_PATH_SSH_KEYGEN); } if (unveil(GOT_TAG_PATH_SIGNIFY, "x") != 0) { return got_error_from_errno2("unveil", GOT_TAG_PATH_SIGNIFY); } return NULL; } const struct got_error * got_sigs_sign_tag_ssh(pid_t *newpid, int *in_fd, int *out_fd, const char* key_file, int verbosity) { const struct got_error *error = NULL; int pid, in_pfd[2], out_pfd[2]; const char* argv[11]; int i = 0, j; *newpid = -1; *in_fd = -1; *out_fd = -1; argv[i++] = GOT_TAG_PATH_SSH_KEYGEN; argv[i++] = "-Y"; argv[i++] = "sign"; argv[i++] = "-f"; argv[i++] = key_file; argv[i++] = "-n"; argv[i++] = "git"; if (verbosity <= 0) { argv[i++] = "-q"; } else { /* ssh(1) allows up to 3 "-v" options. */ for (j = 0; j < MIN(3, verbosity); j++) argv[i++] = "-v"; } argv[i++] = NULL; assert(i <= nitems(argv)); if (pipe(in_pfd) == -1) return got_error_from_errno("pipe"); if (pipe(out_pfd) == -1) return got_error_from_errno("pipe"); pid = fork(); if (pid == -1) { error = got_error_from_errno("fork"); close(in_pfd[0]); close(in_pfd[1]); close(out_pfd[0]); close(out_pfd[1]); return error; } else if (pid == 0) { if (close(in_pfd[1]) == -1) err(1, "close"); if (close(out_pfd[0]) == -1) err(1, "close"); if (dup2(in_pfd[0], 0) == -1) err(1, "dup2"); if (dup2(out_pfd[1], 1) == -1) err(1, "dup2"); if (execv(GOT_TAG_PATH_SSH_KEYGEN, (char **const)argv) == -1) err(1, "execv %s", GOT_TAG_PATH_SSH_KEYGEN); abort(); /* not reached */ } if (close(in_pfd[0]) == -1) return got_error_from_errno("close"); if (close(out_pfd[1]) == -1) return got_error_from_errno("close"); *newpid = pid; *in_fd = in_pfd[1]; *out_fd = out_pfd[0]; return NULL; } static char * signer_identity(const char *tagger) { char *lt, *gt; lt = strstr(tagger, " <"); gt = strrchr(tagger, '>'); if (lt && gt && lt+1 < gt) return strndup(lt+2, gt-lt-2); return NULL; } static const char* BEGIN_SSH_SIG = "-----BEGIN SSH SIGNATURE-----\n"; static const char* END_SSH_SIG = "-----END SSH SIGNATURE-----\n"; const char * got_sigs_get_tagmsg_ssh_signature(const char *tagmsg) { const char *s = tagmsg, *begin = NULL, *end = NULL; while ((s = strstr(s, BEGIN_SSH_SIG)) != NULL) { begin = s; s += strlen(BEGIN_SSH_SIG); } if (begin) end = strstr(begin+strlen(BEGIN_SSH_SIG), END_SSH_SIG); if (end == NULL) return NULL; return (end[strlen(END_SSH_SIG)] == '\0') ? begin : NULL; } static const struct got_error * got_tag_write_signed_data(BUF *buf, struct got_tag_object *tag, const char *start_sig) { const struct got_error *err = NULL; struct got_object_id *id; char *id_str = NULL; char *tagger = NULL; const char *tagmsg; char gmtoff[6]; size_t len; id = got_object_tag_get_object_id(tag); err = got_object_id_str(&id_str, id); if (err) goto done; const char *type_label = NULL; switch (got_object_tag_get_object_type(tag)) { case GOT_OBJ_TYPE_BLOB: type_label = GOT_OBJ_LABEL_BLOB; break; case GOT_OBJ_TYPE_TREE: type_label = GOT_OBJ_LABEL_TREE; break; case GOT_OBJ_TYPE_COMMIT: type_label = GOT_OBJ_LABEL_COMMIT; break; case GOT_OBJ_TYPE_TAG: type_label = GOT_OBJ_LABEL_TAG; break; default: break; } got_date_format_gmtoff(gmtoff, sizeof(gmtoff), got_object_tag_get_tagger_gmtoff(tag)); if (asprintf(&tagger, "%s %lld %s", got_object_tag_get_tagger(tag), (long long)got_object_tag_get_tagger_time(tag), gmtoff) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = buf_puts(&len, buf, GOT_TAG_LABEL_OBJECT); if (err) goto done; err = buf_puts(&len, buf, id_str); if (err) goto done; err = buf_putc(buf, '\n'); if (err) goto done; err = buf_puts(&len, buf, GOT_TAG_LABEL_TYPE); if (err) goto done; err = buf_puts(&len, buf, type_label); if (err) goto done; err = buf_putc(buf, '\n'); if (err) goto done; err = buf_puts(&len, buf, GOT_TAG_LABEL_TAG); if (err) goto done; err = buf_puts(&len, buf, got_object_tag_get_name(tag)); if (err) goto done; err = buf_putc(buf, '\n'); if (err) goto done; err = buf_puts(&len, buf, GOT_TAG_LABEL_TAGGER); if (err) goto done; err = buf_puts(&len, buf, tagger); if (err) goto done; err = buf_puts(&len, buf, "\n"); if (err) goto done; tagmsg = got_object_tag_get_message(tag); err = buf_append(&len, buf, tagmsg, start_sig-tagmsg); if (err) goto done; done: free(id_str); free(tagger); return err; } const struct got_error * got_sigs_verify_tag_ssh(char **msg, struct got_tag_object *tag, const char *start_sig, const char* allowed_signers, const char* revoked, int verbosity) { const struct got_error *error = NULL; const char* argv[17]; int pid, status, in_pfd[2], out_pfd[2]; char* parsed_identity = NULL; const char *identity; char *tmppath = NULL; FILE *tmpsig = NULL; BUF *buf; int i = 0, j; *msg = NULL; error = got_opentemp_named(&tmppath, &tmpsig, GOT_TMPDIR_STR "/got-tagsig", ""); if (error) goto done; identity = got_object_tag_get_tagger(tag); parsed_identity = signer_identity(identity); if (parsed_identity != NULL) identity = parsed_identity; if (fputs(start_sig, tmpsig) == EOF) { error = got_error_from_errno("fputs"); goto done; } if (fflush(tmpsig) == EOF) { error = got_error_from_errno("fflush"); goto done; } error = buf_alloc(&buf, 0); if (error) goto done; error = got_tag_write_signed_data(buf, tag, start_sig); if (error) goto done; argv[i++] = GOT_TAG_PATH_SSH_KEYGEN; argv[i++] = "-Y"; argv[i++] = "verify"; argv[i++] = "-f"; argv[i++] = allowed_signers; argv[i++] = "-I"; argv[i++] = identity; argv[i++] = "-n"; argv[i++] = "git"; argv[i++] = "-s"; argv[i++] = tmppath; if (revoked) { argv[i++] = "-r"; argv[i++] = revoked; } if (verbosity > 0) { /* ssh(1) allows up to 3 "-v" options. */ for (j = 0; j < MIN(3, verbosity); j++) argv[i++] = "-v"; } argv[i++] = NULL; assert(i <= nitems(argv)); if (pipe(in_pfd) == -1) { error = got_error_from_errno("pipe"); goto done; } if (pipe(out_pfd) == -1) { error = got_error_from_errno("pipe"); goto done; } pid = fork(); if (pid == -1) { error = got_error_from_errno("fork"); close(in_pfd[0]); close(in_pfd[1]); close(out_pfd[0]); close(out_pfd[1]); return error; } else if (pid == 0) { if (close(in_pfd[1]) == -1) err(1, "close"); if (close(out_pfd[0]) == -1) err(1, "close"); if (dup2(in_pfd[0], 0) == -1) err(1, "dup2"); if (dup2(out_pfd[1], 1) == -1) err(1, "dup2"); if (execv(GOT_TAG_PATH_SSH_KEYGEN, (char **const)argv) == -1) err(1, "execv %s", GOT_TAG_PATH_SSH_KEYGEN); abort(); /* not reached */ } if (close(in_pfd[0]) == -1) { error = got_error_from_errno("close"); goto done; } if (close(out_pfd[1]) == -1) { error = got_error_from_errno("close"); goto done; } if (buf_write_fd(buf, in_pfd[1]) == -1) { error = got_error_from_errno("write"); goto done; } if (close(in_pfd[1]) == -1) { error = got_error_from_errno("close"); goto done; } if (waitpid(pid, &status, 0) == -1) { error = got_error_from_errno("waitpid"); goto done; } if (!WIFEXITED(status)) { error = got_error(GOT_ERR_BAD_TAG_SIGNATURE); goto done; } error = buf_load_fd(&buf, out_pfd[0]); if (error) goto done; error = buf_putc(buf, '\0'); if (error) goto done; if (close(out_pfd[0]) == -1) { error = got_error_from_errno("close"); goto done; } *msg = buf_get(buf); if (WEXITSTATUS(status) != 0) error = got_error(GOT_ERR_BAD_TAG_SIGNATURE); done: free(parsed_identity); if (tmppath && unlink(tmppath) == -1 && error == NULL) error = got_error_from_errno("unlink"); free(tmppath); close(out_pfd[0]); if (tmpsig && fclose(tmpsig) == EOF && error == NULL) error = got_error_from_errno("fclose"); return error; } got-portable-0.111/lib/read_gitconfig.c0000644000175000017500000001621615001741021013447 /* * Copyright (c) 2022 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_object.h" #include "got_repository.h" #include "got_path.h" #include "got_lib_gitconfig.h" #include "got_lib_delta.h" #include "got_lib_hash.h" #include "got_lib_object.h" #include "got_lib_object_cache.h" #include "got_lib_privsep.h" #include "got_lib_pack.h" #include "got_lib_repository.h" static int get_boolean_val(char *val) { return (strcasecmp(val, "true") == 0 || strcasecmp(val, "on") == 0 || strcasecmp(val, "yes") == 0 || strcmp(val, "1") == 0); } static int skip_node(struct got_gitconfig *gitconfig, struct got_gitconfig_list_node *node) { /* * Skip config nodes which do not describe remotes, and remotes * which do not have a fetch URL defined (as used by git-annex). */ return (strncasecmp("remote \"", node->field, 8) != 0 || got_gitconfig_get_str(gitconfig, node->field, "url") == NULL); } const struct got_error * got_repo_read_gitconfig(int *gitconfig_repository_format_version, char **gitconfig_author_name, char **gitconfig_author_email, struct got_remote_repo **remotes, int *nremotes, char **gitconfig_owner, char ***extnames, char ***extvals, int *nextensions, const char *gitconfig_path) { const struct got_error *err = NULL; struct got_gitconfig *gitconfig = NULL; struct got_gitconfig_list *tags; struct got_gitconfig_list_node *node; int fd, i; const char *author, *email, *owner; *gitconfig_repository_format_version = 0; if (extnames) *extnames = NULL; if (extvals) *extvals = NULL; if (nextensions) *nextensions = 0; *gitconfig_author_name = NULL; *gitconfig_author_email = NULL; if (remotes) *remotes = NULL; if (nremotes) *nremotes = 0; if (gitconfig_owner) *gitconfig_owner = NULL; fd = open(gitconfig_path, O_RDONLY | O_CLOEXEC); if (fd == -1) { if (errno == ENOENT) return NULL; return got_error_from_errno2("open", gitconfig_path); } err = got_gitconfig_open(&gitconfig, fd); if (err) goto done; *gitconfig_repository_format_version = got_gitconfig_get_num(gitconfig, "core", "repositoryformatversion", 0); tags = got_gitconfig_get_tag_list(gitconfig, "extensions"); if (extnames && extvals && nextensions && tags) { size_t numext = 0; TAILQ_FOREACH(node, &tags->fields, link) numext++; *extnames = calloc(numext, sizeof(char *)); if (*extnames == NULL) { err = got_error_from_errno("calloc"); goto done; } *extvals = calloc(numext, sizeof(char *)); if (*extvals == NULL) { err = got_error_from_errno("calloc"); goto done; } TAILQ_FOREACH(node, &tags->fields, link) { char *ext = node->field; char *val = got_gitconfig_get_str(gitconfig, "extensions", ext); char *extstr = NULL, *valstr = NULL; extstr = strdup(ext); if (extstr == NULL) { err = got_error_from_errno("strdup"); goto done; } valstr = strdup(val); if (valstr == NULL) { err = got_error_from_errno("strdup"); goto done; } (*extnames)[(*nextensions)] = extstr; (*extvals)[(*nextensions)] = valstr; (*nextensions)++; } } author = got_gitconfig_get_str(gitconfig, "user", "name"); if (author) { *gitconfig_author_name = strdup(author); if (*gitconfig_author_name == NULL) { err = got_error_from_errno("strdup"); goto done; } } email = got_gitconfig_get_str(gitconfig, "user", "email"); if (email) { *gitconfig_author_email = strdup(email); if (*gitconfig_author_email == NULL) { err = got_error_from_errno("strdup"); goto done; } } if (gitconfig_owner) { owner = got_gitconfig_get_str(gitconfig, "gotweb", "owner"); if (owner == NULL) owner = got_gitconfig_get_str(gitconfig, "gitweb", "owner"); if (owner) { *gitconfig_owner = strdup(owner); if (*gitconfig_owner == NULL) { err = got_error_from_errno("strdup"); goto done; } } } if (remotes && nremotes) { struct got_gitconfig_list *sections; size_t nalloc = 0; err = got_gitconfig_get_section_list(§ions, gitconfig); if (err) return err; TAILQ_FOREACH(node, §ions->fields, link) { if (skip_node(gitconfig, node)) continue; nalloc++; } *remotes = recallocarray(NULL, 0, nalloc, sizeof(**remotes)); if (*remotes == NULL) { err = got_error_from_errno("recallocarray"); goto done; } i = 0; TAILQ_FOREACH(node, §ions->fields, link) { struct got_remote_repo *remote; char *name, *end, *mirror; const char *fetch_url, *send_url; if (skip_node(gitconfig, node) != 0) continue; remote = &(*remotes)[i]; name = strdup(node->field + 8); if (name == NULL) { err = got_error_from_errno("strdup"); goto done; } end = strrchr(name, '"'); if (end) *end = '\0'; remote->name = name; fetch_url = got_gitconfig_get_str(gitconfig, node->field, "url"); remote->fetch_url = strdup(fetch_url); if (remote->fetch_url == NULL) { err = got_error_from_errno("strdup"); free(remote->name); remote->name = NULL; goto done; } send_url = got_gitconfig_get_str(gitconfig, node->field, "pushurl"); if (send_url == NULL) send_url = got_gitconfig_get_str(gitconfig, node->field, "url"); remote->send_url = strdup(send_url); if (remote->send_url == NULL) { err = got_error_from_errno("strdup"); free(remote->name); remote->name = NULL; free(remote->fetch_url); remote->fetch_url = NULL; goto done; } remote->mirror_references = 0; mirror = got_gitconfig_get_str(gitconfig, node->field, "mirror"); if (mirror != NULL && get_boolean_val(mirror)) remote->mirror_references = 1; i++; (*nremotes)++; } } done: if (fd != -1) close(fd); if (gitconfig) got_gitconfig_close(gitconfig); if (err) { if (extnames && extvals && nextensions) { for (i = 0; i < (*nextensions); i++) { free((*extnames)[i]); free((*extvals)[i]); } free(*extnames); *extnames = NULL; free(*extvals); *extvals = NULL; *nextensions = 0; } if (remotes && nremotes) { for (i = 0; i < (*nremotes); i++) { struct got_remote_repo *remote; remote = &(*remotes)[i]; free(remote->name); free(remote->fetch_url); free(remote->send_url); } free(*remotes); *remotes = NULL; *nremotes = 0; } } return err; } got-portable-0.111/lib/object_qid.c0000644000175000017500000000444615001741021012610 /* * Copyright (c) 2018, 2019, 2020, 2023 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include "got_object.h" #include "got_error.h" #include "got_lib_object_qid.h" #include "got_lib_hash.h" const struct got_error * got_object_qid_alloc_partial(struct got_object_qid **qid) { *qid = malloc(sizeof(**qid)); if (*qid == NULL) return got_error_from_errno("malloc"); (*qid)->data = NULL; return NULL; } const struct got_error * got_object_qid_alloc(struct got_object_qid **qid, struct got_object_id *id) { *qid = calloc(1, sizeof(**qid)); if (*qid == NULL) return got_error_from_errno("calloc"); memcpy(&(*qid)->id, id, sizeof((*qid)->id)); return NULL; } void got_object_qid_free(struct got_object_qid *qid) { free(qid); } void got_object_id_queue_free(struct got_object_id_queue *ids) { struct got_object_qid *qid; while (!STAILQ_EMPTY(ids)) { qid = STAILQ_FIRST(ids); STAILQ_REMOVE_HEAD(ids, entry); got_object_qid_free(qid); } } const struct got_error * got_object_id_queue_copy(const struct got_object_id_queue *src, struct got_object_id_queue *dest) { const struct got_error *err; struct got_object_qid *qid; STAILQ_FOREACH(qid, src, entry) { struct got_object_qid *new; /* * Deep-copy the object ID only. Let the caller deal * with setting up the new->data pointer if needed. */ err = got_object_qid_alloc(&new, &qid->id); if (err) { got_object_id_queue_free(dest); return err; } STAILQ_INSERT_TAIL(dest, new, entry); } return NULL; } got-portable-0.111/lib/diff_output_unidiff.c0000644000175000017500000004177715001740614014560 /* Produce a unidiff output from a diff_result. */ /* * Copyright (c) 2020 Neels Hofmeyr * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include "diff_internal.h" #include "diff_debug.h" off_t diff_chunk_get_left_start_pos(const struct diff_chunk *c) { return c->left_start->pos; } off_t diff_chunk_get_right_start_pos(const struct diff_chunk *c) { return c->right_start->pos; } bool diff_chunk_context_empty(const struct diff_chunk_context *cc) { return diff_range_empty(&cc->chunk); } int diff_chunk_get_left_start(const struct diff_chunk *c, const struct diff_result *r, int context_lines) { int left_start = diff_atom_root_idx(r->left, c->left_start); return MAX(0, left_start - context_lines); } int diff_chunk_get_left_end(const struct diff_chunk *c, const struct diff_result *r, int context_lines) { int left_start = diff_chunk_get_left_start(c, r, 0); return MIN(r->left->atoms.len, left_start + c->left_count + context_lines); } int diff_chunk_get_right_start(const struct diff_chunk *c, const struct diff_result *r, int context_lines) { int right_start = diff_atom_root_idx(r->right, c->right_start); return MAX(0, right_start - context_lines); } int diff_chunk_get_right_end(const struct diff_chunk *c, const struct diff_result *r, int context_lines) { int right_start = diff_chunk_get_right_start(c, r, 0); return MIN(r->right->atoms.len, right_start + c->right_count + context_lines); } struct diff_chunk * diff_chunk_get(const struct diff_result *r, int chunk_idx) { return &r->chunks.head[chunk_idx]; } int diff_chunk_get_left_count(struct diff_chunk *c) { return c->left_count; } int diff_chunk_get_right_count(struct diff_chunk *c) { return c->right_count; } void diff_chunk_context_get(struct diff_chunk_context *cc, const struct diff_result *r, int chunk_idx, int context_lines) { const struct diff_chunk *c = &r->chunks.head[chunk_idx]; int left_start = diff_chunk_get_left_start(c, r, context_lines); int left_end = diff_chunk_get_left_end(c, r, context_lines); int right_start = diff_chunk_get_right_start(c, r, context_lines); int right_end = diff_chunk_get_right_end(c, r, context_lines); *cc = (struct diff_chunk_context){ .chunk = { .start = chunk_idx, .end = chunk_idx + 1, }, .left = { .start = left_start, .end = left_end, }, .right = { .start = right_start, .end = right_end, }, }; } bool diff_chunk_contexts_touch(const struct diff_chunk_context *cc, const struct diff_chunk_context *other) { return diff_ranges_touch(&cc->chunk, &other->chunk) || diff_ranges_touch(&cc->left, &other->left) || diff_ranges_touch(&cc->right, &other->right); } void diff_chunk_contexts_merge(struct diff_chunk_context *cc, const struct diff_chunk_context *other) { diff_ranges_merge(&cc->chunk, &other->chunk); diff_ranges_merge(&cc->left, &other->left); diff_ranges_merge(&cc->right, &other->right); } void diff_chunk_context_load_change(struct diff_chunk_context *cc, int *nchunks_used, struct diff_result *result, int start_chunk_idx, int context_lines) { int i; int seen_minus = 0, seen_plus = 0; if (nchunks_used) *nchunks_used = 0; for (i = start_chunk_idx; i < result->chunks.len; i++) { struct diff_chunk *chunk = &result->chunks.head[i]; enum diff_chunk_type t = diff_chunk_type(chunk); struct diff_chunk_context next; if (t != CHUNK_MINUS && t != CHUNK_PLUS) { if (nchunks_used) (*nchunks_used)++; if (seen_minus || seen_plus) break; else continue; } else if (t == CHUNK_MINUS) seen_minus = 1; else if (t == CHUNK_PLUS) seen_plus = 1; if (diff_chunk_context_empty(cc)) { /* Note down the start point, any number of subsequent * chunks may be joined up to this chunk by being * directly adjacent. */ diff_chunk_context_get(cc, result, i, context_lines); if (nchunks_used) (*nchunks_used)++; continue; } /* There already is a previous chunk noted down for being * printed. Does it join up with this one? */ diff_chunk_context_get(&next, result, i, context_lines); if (diff_chunk_contexts_touch(cc, &next)) { /* This next context touches or overlaps the previous * one, join. */ diff_chunk_contexts_merge(cc, &next); if (nchunks_used) (*nchunks_used)++; continue; } else break; } } struct diff_output_unidiff_state { bool header_printed; char prototype[DIFF_FUNCTION_CONTEXT_SIZE]; int last_prototype_idx; }; struct diff_output_unidiff_state * diff_output_unidiff_state_alloc(void) { struct diff_output_unidiff_state *state; state = calloc(1, sizeof(struct diff_output_unidiff_state)); if (state != NULL) diff_output_unidiff_state_reset(state); return state; } void diff_output_unidiff_state_reset(struct diff_output_unidiff_state *state) { state->header_printed = false; memset(state->prototype, 0, sizeof(state->prototype)); state->last_prototype_idx = 0; } void diff_output_unidiff_state_free(struct diff_output_unidiff_state *state) { free(state); } static int output_unidiff_chunk(struct diff_output_info *outinfo, FILE *dest, struct diff_output_unidiff_state *state, const struct diff_input_info *info, const struct diff_result *result, bool print_header, bool show_function_prototypes, const struct diff_chunk_context *cc) { int rc, left_start, left_len, right_start, right_len; off_t outoff = 0, *offp; uint8_t *typep; if (diff_range_empty(&cc->left) && diff_range_empty(&cc->right)) return DIFF_RC_OK; if (outinfo && outinfo->line_offsets.len > 0) { unsigned int idx = outinfo->line_offsets.len - 1; outoff = outinfo->line_offsets.head[idx]; } if (print_header && !(state->header_printed)) { rc = fprintf(dest, "--- %s\n", diff_output_get_label_left(info)); if (rc < 0) return errno; if (outinfo) { ARRAYLIST_ADD(offp, outinfo->line_offsets); if (offp == NULL) return ENOMEM; outoff += rc; *offp = outoff; ARRAYLIST_ADD(typep, outinfo->line_types); if (typep == NULL) return ENOMEM; *typep = DIFF_LINE_MINUS; } rc = fprintf(dest, "+++ %s\n", diff_output_get_label_right(info)); if (rc < 0) return errno; if (outinfo) { ARRAYLIST_ADD(offp, outinfo->line_offsets); if (offp == NULL) return ENOMEM; outoff += rc; *offp = outoff; ARRAYLIST_ADD(typep, outinfo->line_types); if (typep == NULL) return ENOMEM; *typep = DIFF_LINE_PLUS; } state->header_printed = true; } left_len = cc->left.end - cc->left.start; if (result->left->atoms.len == 0) left_start = 0; else if (left_len == 0 && cc->left.start > 0) left_start = cc->left.start; else left_start = cc->left.start + 1; right_len = cc->right.end - cc->right.start; if (result->right->atoms.len == 0) right_start = 0; else if (right_len == 0 && cc->right.start > 0) right_start = cc->right.start; else right_start = cc->right.start + 1; if (show_function_prototypes) { rc = diff_output_match_function_prototype(state->prototype, sizeof(state->prototype), &state->last_prototype_idx, result, cc); if (rc) return rc; } if (left_len == 1 && right_len == 1) { rc = fprintf(dest, "@@ -%d +%d @@%s%s\n", left_start, right_start, state->prototype[0] ? " " : "", state->prototype[0] ? state->prototype : ""); } else if (left_len == 1 && right_len != 1) { rc = fprintf(dest, "@@ -%d +%d,%d @@%s%s\n", left_start, right_start, right_len, state->prototype[0] ? " " : "", state->prototype[0] ? state->prototype : ""); } else if (left_len != 1 && right_len == 1) { rc = fprintf(dest, "@@ -%d,%d +%d @@%s%s\n", left_start, left_len, right_start, state->prototype[0] ? " " : "", state->prototype[0] ? state->prototype : ""); } else { rc = fprintf(dest, "@@ -%d,%d +%d,%d @@%s%s\n", left_start, left_len, right_start, right_len, state->prototype[0] ? " " : "", state->prototype[0] ? state->prototype : ""); } if (rc < 0) return errno; if (outinfo) { ARRAYLIST_ADD(offp, outinfo->line_offsets); if (offp == NULL) return ENOMEM; outoff += rc; *offp = outoff; ARRAYLIST_ADD(typep, outinfo->line_types); if (typep == NULL) return ENOMEM; *typep = DIFF_LINE_HUNK; } /* Got the absolute line numbers where to start printing, and the index * of the interesting (non-context) chunk. * To print context lines above the interesting chunk, nipping on the * previous chunk index may be necessary. * It is guaranteed to be only context lines where left == right, so it * suffices to look on the left. */ const struct diff_chunk *first_chunk; int chunk_start_line; first_chunk = &result->chunks.head[cc->chunk.start]; chunk_start_line = diff_atom_root_idx(result->left, first_chunk->left_start); if (cc->left.start < chunk_start_line) { rc = diff_output_lines(outinfo, dest, " ", &result->left->atoms.head[cc->left.start], chunk_start_line - cc->left.start); if (rc) return rc; } /* Now write out all the joined chunks and contexts between them */ int c_idx; for (c_idx = cc->chunk.start; c_idx < cc->chunk.end; c_idx++) { const struct diff_chunk *c = &result->chunks.head[c_idx]; if (c->left_count && c->right_count) rc = diff_output_lines(outinfo, dest, c->solved ? " " : "?", c->left_start, c->left_count); else if (c->left_count && !c->right_count) rc = diff_output_lines(outinfo, dest, c->solved ? "-" : "?", c->left_start, c->left_count); else if (c->right_count && !c->left_count) rc = diff_output_lines(outinfo, dest, c->solved ? "+" : "?", c->right_start, c->right_count); if (rc) return rc; if (cc->chunk.end == result->chunks.len) { rc = diff_output_trailing_newline_msg(outinfo, dest, c); if (rc != DIFF_RC_OK) return rc; } } /* Trailing context? */ const struct diff_chunk *last_chunk; int chunk_end_line; last_chunk = &result->chunks.head[cc->chunk.end - 1]; chunk_end_line = diff_atom_root_idx(result->left, last_chunk->left_start + last_chunk->left_count); if (cc->left.end > chunk_end_line) { rc = diff_output_lines(outinfo, dest, " ", &result->left->atoms.head[chunk_end_line], cc->left.end - chunk_end_line); if (rc) return rc; if (cc->left.end == result->left->atoms.len) { rc = diff_output_trailing_newline_msg(outinfo, dest, &result->chunks.head[result->chunks.len - 1]); if (rc != DIFF_RC_OK) return rc; } } return DIFF_RC_OK; } int diff_output_unidiff_chunk(struct diff_output_info **output_info, FILE *dest, struct diff_output_unidiff_state *state, const struct diff_input_info *info, const struct diff_result *result, const struct diff_chunk_context *cc) { struct diff_output_info *outinfo = NULL; int flags = (result->left->root->diff_flags | result->right->root->diff_flags); bool show_function_prototypes = (flags & DIFF_FLAG_SHOW_PROTOTYPES); if (output_info) { *output_info = diff_output_info_alloc(); if (*output_info == NULL) return ENOMEM; outinfo = *output_info; } return output_unidiff_chunk(outinfo, dest, state, info, result, false, show_function_prototypes, cc); } int diff_output_unidiff(struct diff_output_info **output_info, FILE *dest, const struct diff_input_info *info, const struct diff_result *result, unsigned int context_lines) { struct diff_output_unidiff_state *state; struct diff_chunk_context cc = {}; struct diff_output_info *outinfo = NULL; int atomizer_flags = (result->left->atomizer_flags| result->right->atomizer_flags); int flags = (result->left->root->diff_flags | result->right->root->diff_flags); bool show_function_prototypes = (flags & DIFF_FLAG_SHOW_PROTOTYPES); bool force_text = (flags & DIFF_FLAG_FORCE_TEXT_DATA); bool have_binary = (atomizer_flags & DIFF_ATOMIZER_FOUND_BINARY_DATA); off_t outoff = 0, *offp; uint8_t *typep; int rc, i; if (!result) return EINVAL; if (result->rc != DIFF_RC_OK) return result->rc; if (output_info) { *output_info = diff_output_info_alloc(); if (*output_info == NULL) return ENOMEM; outinfo = *output_info; } if (have_binary && !force_text) { for (i = 0; i < result->chunks.len; i++) { struct diff_chunk *c = &result->chunks.head[i]; enum diff_chunk_type t = diff_chunk_type(c); if (t != CHUNK_MINUS && t != CHUNK_PLUS) continue; if (outinfo && outinfo->line_offsets.len > 0) { unsigned int idx = outinfo->line_offsets.len - 1; outoff = outinfo->line_offsets.head[idx]; } rc = fprintf(dest, "Binary files %s and %s differ\n", diff_output_get_label_left(info), diff_output_get_label_right(info)); if (outinfo) { ARRAYLIST_ADD(offp, outinfo->line_offsets); if (offp == NULL) return ENOMEM; outoff += rc; *offp = outoff; ARRAYLIST_ADD(typep, outinfo->line_types); if (typep == NULL) return ENOMEM; *typep = DIFF_LINE_NONE; } break; } return DIFF_RC_OK; } state = diff_output_unidiff_state_alloc(); if (state == NULL) { if (output_info) { diff_output_info_free(*output_info); *output_info = NULL; } return ENOMEM; } #if DEBUG unsigned int check_left_pos, check_right_pos; check_left_pos = 0; check_right_pos = 0; for (i = 0; i < result->chunks.len; i++) { struct diff_chunk *c = &result->chunks.head[i]; enum diff_chunk_type t = diff_chunk_type(c); debug("[%d] %s lines L%d R%d @L %d @R %d\n", i, (t == CHUNK_MINUS ? "minus" : (t == CHUNK_PLUS ? "plus" : (t == CHUNK_SAME ? "same" : "?"))), c->left_count, c->right_count, c->left_start ? diff_atom_root_idx(result->left, c->left_start) : -1, c->right_start ? diff_atom_root_idx(result->right, c->right_start) : -1); assert(check_left_pos == diff_atom_root_idx(result->left, c->left_start)); assert(check_right_pos == diff_atom_root_idx(result->right, c->right_start)); check_left_pos += c->left_count; check_right_pos += c->right_count; } assert(check_left_pos == result->left->atoms.len); assert(check_right_pos == result->right->atoms.len); #endif for (i = 0; i < result->chunks.len; i++) { struct diff_chunk *c = &result->chunks.head[i]; enum diff_chunk_type t = diff_chunk_type(c); struct diff_chunk_context next; if (t != CHUNK_MINUS && t != CHUNK_PLUS) continue; if (diff_chunk_context_empty(&cc)) { /* These are the first lines being printed. * Note down the start point, any number of subsequent * chunks may be joined up to this unidiff chunk by * context lines or by being directly adjacent. */ diff_chunk_context_get(&cc, result, i, context_lines); debug("new chunk to be printed:" " chunk %d-%d left %d-%d right %d-%d\n", cc.chunk.start, cc.chunk.end, cc.left.start, cc.left.end, cc.right.start, cc.right.end); continue; } /* There already is a previous chunk noted down for being * printed. Does it join up with this one? */ diff_chunk_context_get(&next, result, i, context_lines); debug("new chunk to be printed:" " chunk %d-%d left %d-%d right %d-%d\n", next.chunk.start, next.chunk.end, next.left.start, next.left.end, next.right.start, next.right.end); if (diff_chunk_contexts_touch(&cc, &next)) { /* This next context touches or overlaps the previous * one, join. */ diff_chunk_contexts_merge(&cc, &next); debug("new chunk to be printed touches previous chunk," " now: left %d-%d right %d-%d\n", cc.left.start, cc.left.end, cc.right.start, cc.right.end); continue; } /* No touching, so the previous context is complete with a gap * between it and this next one. Print the previous one and * start fresh here. */ debug("new chunk to be printed does not touch previous chunk;" " print left %d-%d right %d-%d\n", cc.left.start, cc.left.end, cc.right.start, cc.right.end); output_unidiff_chunk(outinfo, dest, state, info, result, true, show_function_prototypes, &cc); cc = next; debug("new unprinted chunk is left %d-%d right %d-%d\n", cc.left.start, cc.left.end, cc.right.start, cc.right.end); } if (!diff_chunk_context_empty(&cc)) output_unidiff_chunk(outinfo, dest, state, info, result, true, show_function_prototypes, &cc); diff_output_unidiff_state_free(state); return DIFF_RC_OK; } got-portable-0.111/lib/bufio.h0000644000175000017500000000522315001740614011617 /* * bufio.h was written by Omar Polo * * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ struct tls; #define BIO_CHUNK (64 * 1024) struct buf { uint8_t *buf; size_t len; size_t cap; size_t cur; }; struct bufio { int fd; struct tls *ctx; int wantev; struct buf wbuf; struct buf rbuf; }; #define BUFIO_WANT_READ 0x1 #define BUFIO_WANT_WRITE 0x2 int buf_init(struct buf *); int buf_has_line(struct buf *, const char *); char *buf_getdelim(struct buf *, const char *, size_t *); void buf_drain(struct buf *, size_t); void buf_drain_line(struct buf *, const char *); void buf_free(struct buf *); int bufio_init(struct bufio *); void bufio_free(struct bufio *); int bufio_close(struct bufio *); int bufio_reset(struct bufio *); void bufio_set_fd(struct bufio *, int); int bufio_starttls(struct bufio *, const char *, int, const uint8_t *, size_t, const uint8_t *, size_t); int bufio_ev(struct bufio *); int bufio_handshake(struct bufio *); ssize_t bufio_read(struct bufio *); size_t bufio_drain(struct bufio *, void *, size_t); ssize_t bufio_write(struct bufio *); const char *bufio_io_err(struct bufio *); int bufio_compose(struct bufio *, const void *, size_t); int bufio_compose_str(struct bufio *, const char *); int bufio_compose_fmt(struct bufio *, const char *, ...) __attribute__((__format__ (printf, 2, 3))); void bufio_rewind_cursor(struct bufio *); /* callbacks for pdjson */ int bufio_get_cb(void *); int bufio_peek_cb(void *); got-portable-0.111/lib/rcsutil.h0000644000175000017500000000342615001740614012203 /* $OpenBSD: rcsutil.h,v 1.15 2016/07/04 01:39:12 millert Exp $ */ /* * Copyright (c) 2006 Xavier Santolaria * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef RCSUTIL_H #define RCSUTIL_H struct rcs_line { u_char *l_line; int l_lineno; size_t l_len; TAILQ_ENTRY(rcs_line) l_list; }; TAILQ_HEAD(tqh, rcs_line); struct rcs_lines { int l_nblines; struct tqh l_lines; }; /* rcsutil.c */ BUF *rcs_patchfile(u_char *, size_t, u_char *, size_t, int (*p)(struct rcs_lines *,struct rcs_lines *)); struct rcs_lines *rcs_splitlines(u_char *, size_t); void rcs_freelines(struct rcs_lines *); #endif /* RCSUTIL_H */ got-portable-0.111/lib/blame.c0000644000175000017500000003744015001741021011565 /* * Copyright (c) 2018, 2019, 2020 Stefan Sperling * Copyright (c) 2020 Neels Hofmeyr * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_object.h" #include "got_cancel.h" #include "got_commit_graph.h" #include "got_opentemp.h" #include "got_diff.h" #include "got_blame.h" #include "got_lib_inflate.h" #include "got_lib_delta.h" #include "got_lib_object.h" #include "got_lib_diff.h" #ifndef MAX #define MAX(_a,_b) ((_a) > (_b) ? (_a) : (_b)) #endif struct got_blame_line { int annotated; struct got_object_id id; }; struct got_blame { struct diff_config *cfg; int nlines; /* number of lines in file being blamed */ int nannotated; /* number of lines already annotated */ struct got_blame_line *lines; /* one per line */ int ncommits; /* * These change with every traversed commit. After diffing * commits N:N-1, in preparation for diffing commits N-1:N-2, * data for commit N is retained and flipped into data for N-1. * */ FILE *f1; /* older version from commit N-1. */ FILE *f2; /* newer version from commit N. */ int fd; unsigned char *map1; unsigned char *map2; off_t size1; off_t size2; int nlines1; int nlines2; off_t *line_offsets1; off_t *line_offsets2; /* * Map line numbers of an older version of the file to valid line * numbers in the version of the file being blamed. This map is * updated with each commit we traverse throughout the file's history. * Lines mapped to -1 do not correspond to any line in the version * being blamed. */ int *linemap1; int *linemap2; struct diff_data *data1; struct diff_data *data2; }; static const struct got_error * annotate_line(struct got_blame *blame, int lineno, struct got_commit_object *commit, struct got_object_id *id, got_blame_cb cb, void *arg) { const struct got_error *err = NULL; struct got_blame_line *line; if (lineno < 0 || lineno >= blame->nlines) return NULL; line = &blame->lines[lineno]; if (line->annotated) return NULL; memcpy(&line->id, id, sizeof(line->id)); line->annotated = 1; blame->nannotated++; if (cb) err = cb(arg, blame->nlines, lineno + 1, commit, id); return err; } static const struct got_error * blame_changes(struct got_blame *blame, struct diff_result *diff_result, struct got_commit_object *commit, struct got_object_id *commit_id, got_blame_cb cb, void *arg) { const struct got_error *err = NULL; int i; int idx1 = 0, idx2 = 0; for (i = 0; i < diff_result->chunks.len && blame->nannotated < blame->nlines; i++) { struct diff_chunk *c = diff_chunk_get(diff_result, i); unsigned int left_count, right_count; int j; /* * We do not need to worry about idx1/idx2 growing out * of bounds because the diff implementation ensures * that chunk ranges never exceed the number of lines * in the left/right input files. */ left_count = diff_chunk_get_left_count(c); right_count = diff_chunk_get_right_count(c); if (left_count == right_count) { for (j = 0; j < left_count; j++) { blame->linemap1[idx1++] = blame->linemap2[idx2++]; } continue; } if (right_count == 0) { for (j = 0; j < left_count; j++) { blame->linemap1[idx1++] = -1; } continue; } for (j = 0; j < right_count; j++) { int ln = blame->linemap2[idx2++]; err = annotate_line(blame, ln, commit, commit_id, cb, arg); if (err) return err; if (blame->nlines == blame->nannotated) break; } } return NULL; } static const struct got_error * blame_prepare_file(FILE *f, unsigned char **p, off_t *size, int *nlines, off_t **line_offsets, struct diff_data *diff_data, const struct diff_config *cfg, struct got_blob_object *blob) { const struct got_error *err = NULL; int diff_flags = 0, rc; err = got_object_blob_dump_to_file(size, nlines, line_offsets, f, blob); if (err) return err; #ifndef GOT_DIFF_NO_MMAP *p = mmap(NULL, *size, PROT_READ, MAP_PRIVATE, fileno(f), 0); if (*p == MAP_FAILED) #endif *p = NULL; /* fall back on file I/O */ /* Allow blaming lines in binary files even though it's useless. */ diff_flags |= DIFF_FLAG_FORCE_TEXT_DATA; rc = diff_atomize_file(diff_data, cfg, f, *p, *size, diff_flags); if (rc) return got_error_set_errno(rc, "diff_atomize_file"); return NULL; } static const struct got_error * blame_commit(struct got_blame *blame, struct got_object_id *id, const char *path, struct got_repository *repo, got_blame_cb cb, void *arg) { const struct got_error *err = NULL; struct got_commit_object *commit = NULL, *pcommit = NULL; struct got_object_qid *pid = NULL; struct got_object_id *pblob_id = NULL; struct got_blob_object *pblob = NULL; struct diff_result *diff_result = NULL; err = got_object_open_as_commit(&commit, repo, id); if (err) return err; pid = STAILQ_FIRST(got_object_commit_get_parent_ids(commit)); if (pid == NULL) { got_object_commit_close(commit); return NULL; } err = got_object_open_as_commit(&pcommit, repo, &pid->id); if (err) goto done; err = got_object_id_by_path(&pblob_id, repo, pcommit, path); if (err) { if (err->code == GOT_ERR_NO_TREE_ENTRY) err = NULL; goto done; } err = got_object_open_as_blob(&pblob, repo, pblob_id, 8192, blame->fd); if (err) goto done; err = blame_prepare_file(blame->f1, &blame->map1, &blame->size1, &blame->nlines1, &blame->line_offsets1, blame->data1, blame->cfg, pblob); if (err) goto done; diff_result = diff_main(blame->cfg, blame->data1, blame->data2); if (diff_result == NULL) { err = got_error_set_errno(ENOMEM, "malloc"); goto done; } if (diff_result->rc != DIFF_RC_OK) { err = got_error_set_errno(diff_result->rc, "diff"); goto done; } if (diff_result->chunks.len > 0) { if (blame->nlines1 > 0) { blame->linemap1 = calloc(blame->nlines1, sizeof(*blame->linemap1)); if (blame->linemap1 == NULL) { err = got_error_from_errno("malloc"); goto done; } } err = blame_changes(blame, diff_result, commit, id, cb, arg); if (err) goto done; } else if (cb) err = cb(arg, blame->nlines, -1, commit, id); done: if (diff_result) diff_result_free(diff_result); if (commit) got_object_commit_close(commit); if (pcommit) got_object_commit_close(pcommit); free(pblob_id); if (pblob) got_object_blob_close(pblob); return err; } static const struct got_error * blame_close(struct got_blame *blame) { const struct got_error *err = NULL; diff_data_free(blame->data1); free(blame->data1); diff_data_free(blame->data2); free(blame->data2); if (blame->map1) { if (munmap(blame->map1, blame->size1) == -1 && err == NULL) err = got_error_from_errno("munmap"); } if (blame->map2) { if (munmap(blame->map2, blame->size2) == -1 && err == NULL) err = got_error_from_errno("munmap"); } free(blame->lines); free(blame->line_offsets1); free(blame->line_offsets2); free(blame->linemap1); free(blame->linemap2); free(blame->cfg); free(blame); return err; } static int atomize_file(struct diff_data *d, FILE *f, off_t filesize, int nlines, off_t *line_offsets) { int i, rc = DIFF_RC_OK; int embedded_nul = 0; ARRAYLIST_INIT(d->atoms, nlines); for (i = 0; i < nlines; i++) { struct diff_atom *atom; off_t len, pos = line_offsets[i]; unsigned int hash = 0; int j; ARRAYLIST_ADD(atom, d->atoms); if (atom == NULL) { rc = errno; break; } if (i < nlines - 1) len = line_offsets[i + 1] - pos; else len = filesize - pos; if (fseeko(f, pos, SEEK_SET) == -1) { rc = errno; break; } for (j = 0; j < len; j++) { int c = fgetc(f); if (c == EOF) { if (feof(f)) rc = EIO; /* unexpected EOF */ else rc = errno; goto done; } hash = diff_atom_hash_update(hash, (unsigned char)c); if (c == '\0') embedded_nul = 1; } *atom = (struct diff_atom){ .root = d, .pos = pos, .at = NULL, /* atom data is not memory-mapped */ .len = len, .hash = hash, }; } /* File are considered binary if they contain embedded '\0' bytes. */ if (embedded_nul) d->atomizer_flags |= DIFF_ATOMIZER_FOUND_BINARY_DATA; done: if (rc) ARRAYLIST_FREE(d->atoms); return rc; } static int atomize_file_mmap(struct diff_data *d, unsigned char *p, off_t filesize, int nlines, off_t *line_offsets) { int i, rc = DIFF_RC_OK; int embedded_nul = 0; ARRAYLIST_INIT(d->atoms, nlines); for (i = 0; i < nlines; i++) { struct diff_atom *atom; off_t len, pos = line_offsets[i]; unsigned int hash = 0; int j; ARRAYLIST_ADD(atom, d->atoms); if (atom == NULL) { rc = errno; break; } if (i < nlines - 1) len = line_offsets[i + 1] - pos; else len = filesize - pos; for (j = 0; j < len; j++) hash = diff_atom_hash_update(hash, p[pos + j]); if (!embedded_nul && memchr(&p[pos], '\0', len) != NULL) embedded_nul = 1; *atom = (struct diff_atom){ .root = d, .pos = pos, .at = &p[pos], .len = len, .hash = hash, }; } /* File are considered binary if they contain embedded '\0' bytes. */ if (embedded_nul) d->atomizer_flags |= DIFF_ATOMIZER_FOUND_BINARY_DATA; if (rc) ARRAYLIST_FREE(d->atoms); return rc; } /* Implements diff_atomize_func_t */ static int blame_atomize_file(void *arg, struct diff_data *d) { struct got_blame *blame = arg; if (d->f == blame->f1) { if (blame->map1) return atomize_file_mmap(d, blame->map1, blame->size1, blame->nlines1, blame->line_offsets1); else return atomize_file(d, blame->f1, blame->size1, blame->nlines1, blame->line_offsets1); } else if (d->f == blame->f2) { if (d->atoms.len > 0) { /* Reuse data from previous commit. */ return DIFF_RC_OK; } if (blame->map2) return atomize_file_mmap(d, blame->map2, blame->size2, blame->nlines2, blame->line_offsets2); else return atomize_file(d, blame->f2, blame->size2, blame->nlines2, blame->line_offsets2); } return DIFF_RC_OK; } static const struct got_error * flip_files(struct got_blame *blame) { const struct got_error *err = NULL; struct diff_data *d; FILE *tmp; free(blame->line_offsets2); blame->line_offsets2 = blame->line_offsets1; blame->line_offsets1 = NULL; free(blame->linemap2); blame->linemap2 = blame->linemap1; blame->linemap1 = NULL; if (blame->map2) { if (munmap(blame->map2, blame->size2) == -1) return got_error_from_errno("munmap"); blame->map2 = blame->map1; blame->map1 = NULL; } blame->size2 = blame->size1; err = got_opentemp_truncate(blame->f2); if (err) return err; tmp = blame->f2; blame->f2 = blame->f1; blame->f1 = tmp; blame->size1 = 0; blame->nlines2 = blame->nlines1; blame->nlines1 = 0; diff_data_free(blame->data2); /* does not free pointer itself */ memset(blame->data2, 0, sizeof(*blame->data2)); d = blame->data2; blame->data2 = blame->data1; blame->data1 = d; return NULL; } static const struct got_error * blame_open(struct got_blame **blamep, const char *path, struct got_object_id *start_commit_id, struct got_repository *repo, enum got_diff_algorithm diff_algo, got_blame_cb cb, void *arg, got_cancel_cb cancel_cb, void *cancel_arg, int fd1, int fd2, FILE *f1, FILE *f2) { const struct got_error *err = NULL; struct got_commit_object *start_commit = NULL, *last_commit = NULL; struct got_object_id *obj_id = NULL; struct got_blob_object *blob = NULL; struct got_blame *blame = NULL; struct got_object_id id; int lineno, have_id = 0; struct got_commit_graph *graph = NULL; *blamep = NULL; err = got_object_open_as_commit(&start_commit, repo, start_commit_id); if (err) goto done; err = got_object_id_by_path(&obj_id, repo, start_commit, path); if (err) goto done; err = got_object_open_as_blob(&blob, repo, obj_id, 8192, fd1); if (err) goto done; blame = calloc(1, sizeof(*blame)); if (blame == NULL) { err = got_error_from_errno("calloc"); goto done; } blame->data1 = calloc(1, sizeof(*blame->data1)); if (blame->data1 == NULL) { err = got_error_from_errno("calloc"); goto done; } blame->data2 = calloc(1, sizeof(*blame->data2)); if (blame->data2 == NULL) { err = got_error_from_errno("calloc"); goto done; } blame->f1 = f1; blame->f2 = f2; blame->fd = fd2; err = got_diff_get_config(&blame->cfg, diff_algo, blame_atomize_file, blame); if (err) goto done; err = blame_prepare_file(blame->f2, &blame->map2, &blame->size2, &blame->nlines2, &blame->line_offsets2, blame->data2, blame->cfg, blob); blame->nlines = blame->nlines2; if (err || blame->nlines == 0) goto done; got_object_blob_close(blob); blob = NULL; /* Don't include \n at EOF in the blame line count. */ if (blame->line_offsets2[blame->nlines - 1] == blame->size2) blame->nlines--; blame->lines = calloc(blame->nlines, sizeof(*blame->lines)); if (blame->lines == NULL) { err = got_error_from_errno("calloc"); goto done; } blame->linemap2 = calloc(blame->nlines2, sizeof(*blame->linemap2)); if (blame->linemap2 == NULL) { err = got_error_from_errno("calloc"); goto done; } for (lineno = 0; lineno < blame->nlines2; lineno++) blame->linemap2[lineno] = lineno; err = got_commit_graph_open(&graph, path, 1); if (err) goto done; err = got_commit_graph_bfsort(graph, start_commit_id, repo, cancel_cb, cancel_arg); if (err) goto done; for (;;) { err = got_commit_graph_iter_next(&id, graph, repo, cancel_cb, cancel_arg); if (err) { if (err->code == GOT_ERR_ITER_COMPLETED) { err = NULL; break; } goto done; } have_id = 1; err = blame_commit(blame, &id, path, repo, cb, arg); if (err) { if (err->code == GOT_ERR_ITER_COMPLETED) err = NULL; goto done; } if (blame->nannotated == blame->nlines) break; err = flip_files(blame); if (err) goto done; } if (have_id && blame->nannotated < blame->nlines) { /* Annotate remaining non-annotated lines with last commit. */ err = got_object_open_as_commit(&last_commit, repo, &id); if (err) goto done; for (lineno = 0; lineno < blame->nlines; lineno++) { err = annotate_line(blame, lineno, last_commit, &id, cb, arg); if (err) goto done; } } done: if (graph) got_commit_graph_close(graph); free(obj_id); if (blob) got_object_blob_close(blob); if (start_commit) got_object_commit_close(start_commit); if (last_commit) got_object_commit_close(last_commit); if (err) { if (blame) blame_close(blame); } else *blamep = blame; return err; } const struct got_error * got_blame(const char *path, struct got_object_id *commit_id, struct got_repository *repo, enum got_diff_algorithm diff_algo, got_blame_cb cb, void *arg, got_cancel_cb cancel_cb, void* cancel_arg, int fd1, int fd2, FILE *f1, FILE *f2) { const struct got_error *err = NULL, *close_err = NULL; struct got_blame *blame; char *abspath; if (asprintf(&abspath, "%s%s", path[0] == '/' ? "" : "/", path) == -1) return got_error_from_errno2("asprintf", path); err = blame_open(&blame, abspath, commit_id, repo, diff_algo, cb, arg, cancel_cb, cancel_arg, fd1, fd2, f1, f2); free(abspath); if (blame) close_err = blame_close(blame); return err ? err : close_err; } got-portable-0.111/lib/gotsys_conf.c0000644000175000017500000004317215001741021013041 /* * Copyright (c) 2025 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_path.h" #include "gotsys.h" #ifndef nitems #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) #endif void gotsys_conf_init(struct gotsys_conf *gotsysconf) { memset(gotsysconf, 0, sizeof(*gotsysconf)); STAILQ_INIT(&gotsysconf->users); STAILQ_INIT(&gotsysconf->groups); TAILQ_INIT(&gotsysconf->repos); } void gotsys_authorized_key_free(struct gotsys_authorized_key *key) { if (key == NULL) return; free(key->keytype); free(key->key); free(key->comment); free(key); } void gotsys_authorized_keys_list_purge(struct gotsys_authorized_keys_list *keys) { if (keys == NULL) return; while (!STAILQ_EMPTY(keys)) { struct gotsys_authorized_key *key; key = STAILQ_FIRST(keys); STAILQ_REMOVE_HEAD(keys, entry); gotsys_authorized_key_free(key); } } void gotsys_user_free(struct gotsys_user *user) { if (user == NULL) return; free(user->name); free(user->password); gotsys_authorized_keys_list_purge(&user->authorized_keys); free(user); } void gotsys_group_free(struct gotsys_group *group) { if (group == NULL) return; while (!STAILQ_EMPTY(&group->members)) { struct gotsys_user *member; member = STAILQ_FIRST(&group->members); STAILQ_REMOVE_HEAD(&group->members, entry); gotsys_user_free(member); } free(group->name); free(group); } void gotsys_access_rule_free(struct gotsys_access_rule *rule) { if (rule == NULL) return; free(rule->identifier); free(rule); } void gotsys_notification_target_free(struct gotsys_notification_target *target) { if (target == NULL) return; switch (target->type) { case GOTSYS_NOTIFICATION_VIA_EMAIL: free(target->conf.email.sender); free(target->conf.email.recipient); free(target->conf.email.responder); free(target->conf.email.hostname); free(target->conf.email.port); break; case GOTSYS_NOTIFICATION_VIA_HTTP: free(target->conf.http.hostname); free(target->conf.http.port); free(target->conf.http.path); free(target->conf.http.user); free(target->conf.http.password); free(target->conf.http.hmac_secret); break; default: abort(); /* NOTREACHED */ } free(target); } void gotsys_repo_free(struct gotsys_repo *repo) { if (repo == NULL) return; while (!STAILQ_EMPTY(&repo->access_rules)) { struct gotsys_access_rule *rule; rule = STAILQ_FIRST(&repo->access_rules); STAILQ_REMOVE_HEAD(&repo->access_rules, entry); gotsys_access_rule_free(rule); } got_pathlist_free(&repo->protected_tag_namespaces, GOT_PATHLIST_FREE_PATH); got_pathlist_free(&repo->protected_branch_namespaces, GOT_PATHLIST_FREE_PATH); got_pathlist_free(&repo->protected_branches, GOT_PATHLIST_FREE_PATH); got_pathlist_free(&repo->notification_refs, GOT_PATHLIST_FREE_PATH); got_pathlist_free(&repo->notification_ref_namespaces, GOT_PATHLIST_FREE_PATH); while (!STAILQ_EMPTY(&repo->notification_targets)) { struct gotsys_notification_target *target; target = STAILQ_FIRST(&repo->notification_targets); STAILQ_REMOVE_HEAD(&repo->notification_targets, entry); gotsys_notification_target_free(target); } } void gotsys_userlist_purge(struct gotsys_userlist *users) { while (!STAILQ_EMPTY(users)) { struct gotsys_user *user; user = STAILQ_FIRST(users); STAILQ_REMOVE_HEAD(users, entry); gotsys_user_free(user); } } void gotsys_grouplist_purge(struct gotsys_grouplist *groups) { while (!STAILQ_EMPTY(groups)) { struct gotsys_group *group; group = STAILQ_FIRST(groups); STAILQ_REMOVE_HEAD(groups, entry); gotsys_group_free(group); } } void gotsys_conf_clear(struct gotsys_conf *gotsysconf) { gotsys_userlist_purge(&gotsysconf->users); gotsys_grouplist_purge(&gotsysconf->groups); while (!TAILQ_EMPTY(&gotsysconf->repos)) { struct gotsys_repo *repo; repo = TAILQ_FIRST(&gotsysconf->repos); TAILQ_REMOVE(&gotsysconf->repos, repo, entry); gotsys_repo_free(repo); } } static const char *wellknown_users[] = { "anonymous", "root", "daemon", "operator", "bin", "build", "sshd", "www", "nobody", }; static const char *wellknown_groups[] = { "wheel", "daemon", "kmem", "sys", "tty", "operator", "bin", "wsrc", "users", "auth", "games", "staff", "wobj", "sshd", "guest", "utmp", "crontab", "www", "network", "authpf", "dialer", "nogroup", "nobody", }; const struct got_error * gotsys_conf_validate_name(const char *name, const char *type) { size_t i, len; if (name[0] == '\0') return got_error_fmt(GOT_ERR_PARSE_CONFIG, "empty %s name", type); /* Forbid use of well-known names, regardless of requested type. */ for (i = 0; i < nitems(wellknown_users); i++) { if (strcmp(name, wellknown_users[i]) == 0) { return got_error_fmt(GOT_ERR_PARSE_CONFIG, "%s name '%s' is reserved and cannot be used", type, name); } } for (i = 0; i < nitems(wellknown_groups); i++) { if (strcmp(name, wellknown_groups[i]) == 0) { return got_error_fmt(GOT_ERR_PARSE_CONFIG, "%s name '%s' is reserved and cannot be used", type, name); } } /* * Quoting useradd(3): * * It is recommended that login names contain only lowercase * characters and digits. They may also contain uppercase * characters, non-leading hyphens, periods, underscores, and a * trailing ‘$’. Login names may not be longer than 31 characters. */ len = strlen(name); if (len > _PW_NAME_LEN) { return got_error_fmt(GOT_ERR_PARSE_CONFIG, "%s name is too long (exceeds %d bytes): %s", type, _PW_NAME_LEN, name); } /* * In addition to the regular useradd(3) rules above, disallow * leading digits to prevent a name from being misinterpreted * as a number in any context by any tool. */ if (isdigit(name[0])) goto invalid; /* * In addition to the regular useradd(3) rules above, disallow * leading underscores to prevent collisions with system daemon * accounts. * Prevent leading periods as well, because we can. * A trailing $ is required for compat with Samba. We prevent it * for now until interaction with Samba is proven to be useful. */ for (i = 0; i < len; i++) { /* * On non-OpenBSD systems, isalnum(3) can suffer from * locale-dependent-behaviour syndrom. * Prevent non-ASCII characters in a portable way. */ if (name[i] & 0x80) goto invalid; if (isalnum(name[i]) || (i > 0 && name[i] == '-') || (i > 0 && name[i] == '_') || (i > 0 && name[i] == '.')) continue; goto invalid; } return NULL; invalid: return got_error_fmt(GOT_ERR_PARSE_CONFIG, "%s names may only contain alphabetic ASCII " "characters, non-leading digits, non-leading hyphens, " "non-leading underscores, or non-leading periods: %s", type, name); } const struct got_error * gotsys_conf_validate_repo_name(const char *name) { size_t len, i; if (name[0] == '\0') return got_error_msg(GOT_ERR_PARSE_CONFIG, "empty repository name"); /* * Disallow leading digits to prevent a name from being * misinterpreted as a number in any context by any tool. */ if (isdigit(name[0])) goto invalid; len = strlen(name); for (i = 0; i < len; i++) { if (isalnum(name[i]) || (i > 0 && name[i] == '-') || (i > 0 && name[i] == '_') || (i > 0 && name[i] == '.')) continue; goto invalid; } return NULL; invalid: return got_error_fmt(GOT_ERR_PARSE_CONFIG, "repository names may only contain alphabetic ASCII " "characters, non-leading digits, non-leading hyphens, " "non-leading underscores, or non-leading periods: %s", name); } static int validate_password(const char *s, size_t len) { static const u_int8_t base64chars[] = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; size_t i; for (i = 0; i < len; i++) { if (strchr(base64chars, s[i]) == NULL) return 0; } return 1; } const struct got_error * gotsys_conf_validate_password(const char *username, const char *password) { size_t len = strlen(password); if (len < 8 || len > _PASSWORD_LEN) goto invalid; if (password[0] != '$' || password[1] != '2' || /* bcrypt version */ !(password[2] == 'a' || password[2] == 'b') || /* minor versions */ password[3] != '$' || !(isdigit(password[4]) && isdigit(password[5])) || /* num rounds */ password[6] != '$') goto invalid; /* The remainder must be base64 data. */ if (!validate_password(&password[7], len - 7)) goto invalid; return NULL; invalid: return got_error_fmt(GOT_ERR_PARSE_CONFIG, "password for user %s " "was not encrypted with the encrypt(1) utility", username); } static const struct got_error * validate_comment(const char *comment, size_t len) { size_t i; /* Require printable ASCII characters. */ for (i = 0; i < len; i++) { /* * On non-OpenBSD systems, isalnum(3) can suffer from * locale-dependent-behaviour syndrom. * Prevent non-ASCII characters in a portable way. */ if (comment[i] & 0x80) goto invalid; if (!isalnum(comment[i]) && !ispunct(comment[i])) goto invalid; } return NULL; invalid: return got_error_fmt(GOT_ERR_PARSE_CONFIG, "authorized key comments may only contain " "printable ASCII characters and no whitespace"); } static int validate_authorized_key(const char *s, size_t len) { static const u_int8_t base64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; size_t i; for (i = 0; i < len; i++) { if (strchr(base64chars, s[i]) == NULL) return 0; } return 1; } const struct got_error * gotsys_conf_new_authorized_key(struct gotsys_authorized_key **key, char *keytype, char *keydata, char *comment) { const struct got_error *err = NULL; static const char *known_keytypes[] = { "sk-ecdsa-sha2-nistp256@openssh.com", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521", "sk-ssh-ed25519@openssh.com", "ssh-ed25519", "ssh-rsa" }; size_t i, typelen, datalen, commentlen = 0, totlen; *key = NULL; for (i = 0; i < nitems(known_keytypes); i++) { if (strcmp(keytype, known_keytypes[i]) == 0) break; } if (i >= nitems(known_keytypes)) { return got_error_fmt(GOT_ERR_PARSE_CONFIG, "unknown authorized key type: %s", keytype); } typelen = strlen(keytype); if (typelen > GOTSYS_AUTHORIZED_KEY_MAXLEN) { return got_error_fmt(GOT_ERR_NO_SPACE, "authorized key type too long: %s", keytype); } datalen = strlen(keydata); if (datalen == 0) { return got_error_msg(GOT_ERR_AUTHORIZED_KEY, "empty authorized key"); } if (datalen > GOTSYS_AUTHORIZED_KEY_MAXLEN || typelen + datalen > GOTSYS_AUTHORIZED_KEY_MAXLEN) { return got_error_fmt(GOT_ERR_NO_SPACE, "authorized key too long: %s:", keydata); } if (!validate_authorized_key(keydata, datalen)) { return got_error_fmt(GOT_ERR_PARSE_CONFIG, "authorized key data must be base64-encoded"); } if (comment) { commentlen = strlen(comment); if (commentlen > GOTSYS_AUTHORIZED_KEY_MAXLEN) { return got_error_fmt(GOT_ERR_NO_SPACE, "authorized key comment too long: %s:", comment); } err = validate_comment(comment, commentlen); if (err) return err; } /* Won't overflow since values are < GOTSYS_AUTHORIZED_KEY_MAXLEN. */ totlen = typelen + datalen + commentlen; if (totlen > GOTSYS_AUTHORIZED_KEY_MAXLEN) { return got_error_fmt(GOT_ERR_NO_SPACE, "authorized key too long: %s %s %s", keytype, keydata, comment ? comment : ""); } *key = calloc(1, sizeof(**key)); if (*key == NULL) return got_error_from_errno("calloc"); (*key)->keytype = strdup(keytype); if ((*key)->keytype == NULL) { err = got_error_from_errno("strdup"); goto done; } (*key)->key = strdup(keydata); if ((*key)->key == NULL) { err = got_error_from_errno("strdup"); goto done; } if (comment) { (*key)->comment = strdup(comment); if ((*key)->comment == NULL) { err = got_error_from_errno("strdup"); goto done; } } done: if (err) { gotsys_authorized_key_free(*key); *key = NULL; } return NULL; } const struct got_error * gotsys_conf_new_user(struct gotsys_user **user, const char *username) { const struct got_error *err; *user = calloc(1, sizeof(**user)); if (*user == NULL) return got_error_from_errno("calloc"); (*user)->name = strdup(username); if ((*user)->name == NULL) { err = got_error_from_errno("strdup"); free(*user); *user = NULL; return err; } STAILQ_INIT(&(*user)->authorized_keys); return NULL; } const struct got_error * gotsys_conf_new_group(struct gotsys_group **group, const char *groupname) { const struct got_error *err; *group = calloc(1, sizeof(**group)); if (*group == NULL) return got_error_from_errno("calloc"); (*group)->name = strdup(groupname); if ((*group)->name == NULL) { err = got_error_from_errno("strdup"); free(*group); *group = NULL; return err; } STAILQ_INIT(&(*group)->members); return NULL; } const struct got_error * gotsys_conf_new_group_member(struct gotsys_grouplist *groups, const char *groupname, const char *username) { struct gotsys_group *group = NULL; struct gotsys_user *member = NULL; STAILQ_FOREACH(group, groups, entry) { if (strcmp(group->name, groupname) == 0) break; } if (group == NULL) { return got_error_fmt(GOT_ERR_PARSE_CONFIG, "reference to undeclared group '%s' via user '%s'", groupname, username); } STAILQ_FOREACH(member, &group->members, entry) { if (strcmp(member->name, username) == 0) break; } if (member) return NULL; member = calloc(1, sizeof(*member)); if (member == NULL) return got_error_from_errno("calloc"); member->name = strdup(username); if (member->name == NULL) { free(member); return got_error_from_errno("strdup"); } STAILQ_INSERT_TAIL(&group->members, member, entry); return NULL; } const struct got_error * gotsys_conf_new_repo(struct gotsys_repo **new_repo, const char *name) { const struct got_error *err = NULL; struct gotsys_repo *repo; *new_repo = NULL; err = gotsys_conf_validate_repo_name(name); if (err) return err; repo = calloc(1, sizeof(*repo)); if (repo == NULL) return got_error_from_errno("calloc"); STAILQ_INIT(&repo->access_rules); RB_INIT(&repo->protected_tag_namespaces); RB_INIT(&repo->protected_branch_namespaces); RB_INIT(&repo->protected_branches); RB_INIT(&repo->notification_refs); RB_INIT(&repo->notification_ref_namespaces); STAILQ_INIT(&repo->notification_targets); if (strlcpy(repo->name, name, sizeof(repo->name)) >= sizeof(repo->name)) { free(repo); return got_error_fmt(GOT_ERR_BAD_PATH, "repository name too long: %s", name); } *new_repo = repo; return NULL; } const struct got_error * gotsys_conf_new_access_rule(struct gotsys_access_rule **rule, enum gotsys_access access, int authorization, const char *identifier, struct gotsys_userlist *users, struct gotsys_grouplist *groups) { const struct got_error *err = NULL; const char *name; *rule = NULL; switch (access) { case GOTSYS_ACCESS_PERMITTED: if (authorization == 0) { return got_error_msg(GOT_ERR_PARSE_CONFIG, "permit access rule without read or write " "authorization"); } break; case GOTSYS_ACCESS_DENIED: if (authorization != 0) { return got_error_msg(GOT_ERR_PARSE_CONFIG, "deny access rule with read or write " "authorization"); } break; default: return got_error_msg(GOT_ERR_PARSE_CONFIG, "invalid access rule"); } if (authorization & ~(GOTSYS_AUTH_READ | GOTSYS_AUTH_WRITE)) { return got_error_msg(GOT_ERR_PARSE_CONFIG, "invalid access rule authorization flags"); } name = identifier; if (name[0] == '\0') return got_error_fmt(GOT_ERR_PARSE_CONFIG, "empty identifier in access rule"); if (name[0] == ':') { struct gotsys_group *group = NULL; name++; if (name[0] == '\0') return got_error_fmt(GOT_ERR_PARSE_CONFIG, "empty group name in access rule"); STAILQ_FOREACH(group, groups, entry) { if (strcmp(group->name, name) == 0) break; } if (group == NULL) { return got_error_fmt(GOT_ERR_PARSE_CONFIG, "reference to undeclared group '%s' via " "access rule", name); } } else if (strcmp(name, "anonymous") == 0) { if (access == GOTSYS_ACCESS_PERMITTED && (authorization & GOTSYS_AUTH_WRITE)) { return got_error_msg(GOT_ERR_PARSE_CONFIG, "the \"anonymous\" user must not have write " "permission"); } } else { struct gotsys_user *user = NULL; STAILQ_FOREACH(user, users, entry) { if (strcmp(user->name, name) == 0) break; } if (user == NULL) { return got_error_fmt(GOT_ERR_PARSE_CONFIG, "reference to undeclared user '%s' via " "access rule", name); } } *rule = calloc(1, sizeof(**rule)); if (*rule == NULL) return got_error_from_errno("calloc"); (*rule)->access = access; (*rule)->authorization = authorization; (*rule)->identifier = strdup(identifier); if ((*rule)->identifier == NULL) { err = got_error_from_errno("strdup"); gotsys_access_rule_free(*rule); *rule = NULL; } return err; } got-portable-0.111/lib/fileindex.c0000644000175000017500000010377715001741021012463 /* * Copyright (c) 2018, 2019 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_object.h" #include "got_path.h" #include "got_lib_hash.h" #include "got_lib_fileindex.h" #include "got_lib_worktree.h" /* got_fileindex_entry flags */ #define GOT_FILEIDX_F_PATH_LEN 0x00000fff #define GOT_FILEIDX_F_STAGE 0x0000f000 #define GOT_FILEIDX_F_STAGE_SHIFT 12 #define GOT_FILEIDX_F_NOT_FLUSHED 0x00010000 #define GOT_FILEIDX_F_NO_BLOB 0x00020000 #define GOT_FILEIDX_F_NO_COMMIT 0x00040000 #define GOT_FILEIDX_F_NO_FILE_ON_DISK 0x00080000 #define GOT_FILEIDX_F_REMOVE_ON_FLUSH 0x00100000 #define GOT_FILEIDX_F_SKIPPED 0x00200000 struct got_fileindex { struct got_fileindex_tree entries; uint32_t version; int nentries; /* Does not include entries marked for removal. */ #define GOT_FILEIDX_MAX_ENTRIES INT32_MAX enum got_hash_algorithm algo; }; mode_t got_fileindex_entry_perms_get(struct got_fileindex_entry *ie) { return ((ie->mode & GOT_FILEIDX_MODE_PERMS) >> GOT_FILEIDX_MODE_PERMS_SHIFT); } static void fileindex_entry_perms_set(struct got_fileindex_entry *ie, mode_t mode) { ie->mode &= ~GOT_FILEIDX_MODE_PERMS; ie->mode |= ((mode << GOT_FILEIDX_MODE_PERMS_SHIFT) & GOT_FILEIDX_MODE_PERMS); } mode_t got_fileindex_perms_to_st(struct got_fileindex_entry *ie) { mode_t perms = got_fileindex_entry_perms_get(ie); int type = got_fileindex_entry_filetype_get(ie); uint32_t ftype; if (type == GOT_FILEIDX_MODE_REGULAR_FILE || type == GOT_FILEIDX_MODE_BAD_SYMLINK) ftype = S_IFREG; else ftype = S_IFLNK; return (ftype | (perms & (S_IRWXU | S_IRWXG | S_IRWXO))); } const struct got_error * got_fileindex_entry_update(struct got_fileindex_entry *ie, int wt_fd, const char *ondisk_path, struct got_object_id *blob, struct got_object_id *commit, int update_timestamps) { struct stat sb; if (fstatat(wt_fd, ondisk_path, &sb, AT_SYMLINK_NOFOLLOW) != 0) { if (!((ie->flags & GOT_FILEIDX_F_NO_FILE_ON_DISK) && errno == ENOENT)) return got_error_from_errno2("fstatat", ondisk_path); sb.st_mode = GOT_DEFAULT_FILE_MODE; } else { if (sb.st_mode & S_IFDIR) return got_error_set_errno(EISDIR, ondisk_path); ie->flags &= ~GOT_FILEIDX_F_NO_FILE_ON_DISK; } if ((ie->flags & GOT_FILEIDX_F_NO_FILE_ON_DISK) == 0) { if (update_timestamps) { ie->ctime_sec = sb.st_ctim.tv_sec; ie->ctime_nsec = sb.st_ctim.tv_nsec; ie->mtime_sec = sb.st_mtim.tv_sec; ie->mtime_nsec = sb.st_mtim.tv_nsec; } ie->uid = sb.st_uid; ie->gid = sb.st_gid; ie->size = (sb.st_size & 0xffffffff); if (S_ISLNK(sb.st_mode)) { got_fileindex_entry_filetype_set(ie, GOT_FILEIDX_MODE_SYMLINK); fileindex_entry_perms_set(ie, 0); } else { got_fileindex_entry_filetype_set(ie, GOT_FILEIDX_MODE_REGULAR_FILE); fileindex_entry_perms_set(ie, sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)); } } if (blob) { memmove(&ie->blob, blob, sizeof(ie->blob)); ie->flags &= ~GOT_FILEIDX_F_NO_BLOB; } else ie->flags |= GOT_FILEIDX_F_NO_BLOB; if (commit) { memmove(&ie->commit, commit, sizeof(ie->commit)); ie->flags &= ~GOT_FILEIDX_F_NO_COMMIT; } else ie->flags |= GOT_FILEIDX_F_NO_COMMIT; return NULL; } void got_fileindex_entry_mark_deleted_from_disk(struct got_fileindex_entry *ie) { ie->flags |= GOT_FILEIDX_F_NO_FILE_ON_DISK; } void got_fileindex_entry_mark_skipped(struct got_fileindex_entry *ie) { ie->flags |= GOT_FILEIDX_F_SKIPPED; } const struct got_error * got_fileindex_entry_alloc(struct got_fileindex_entry **ie, const char *relpath) { size_t len; *ie = calloc(1, sizeof(**ie)); if (*ie == NULL) return got_error_from_errno("calloc"); (*ie)->path = strdup(relpath); if ((*ie)->path == NULL) { const struct got_error *err = got_error_from_errno("strdup"); free(*ie); *ie = NULL; return err; } len = strlen(relpath); if (len > GOT_FILEIDX_F_PATH_LEN) len = GOT_FILEIDX_F_PATH_LEN; (*ie)->flags |= len; return NULL; } void got_fileindex_entry_free(struct got_fileindex_entry *ie) { free(ie->path); free(ie); } size_t got_fileindex_entry_path_len(const struct got_fileindex_entry *ie) { return (size_t)(ie->flags & GOT_FILEIDX_F_PATH_LEN); } uint32_t got_fileindex_entry_stage_get(const struct got_fileindex_entry *ie) { return ((ie->flags & GOT_FILEIDX_F_STAGE) >> GOT_FILEIDX_F_STAGE_SHIFT); } void got_fileindex_entry_stage_set(struct got_fileindex_entry *ie, uint32_t stage) { ie->flags &= ~GOT_FILEIDX_F_STAGE; ie->flags |= ((stage << GOT_FILEIDX_F_STAGE_SHIFT) & GOT_FILEIDX_F_STAGE); } int got_fileindex_entry_filetype_get(struct got_fileindex_entry *ie) { return (ie->mode & GOT_FILEIDX_MODE_FILE_TYPE_ONDISK); } void got_fileindex_entry_filetype_set(struct got_fileindex_entry *ie, int type) { ie->mode &= ~GOT_FILEIDX_MODE_FILE_TYPE_ONDISK; ie->mode |= (type & GOT_FILEIDX_MODE_FILE_TYPE_ONDISK); } void got_fileindex_entry_staged_filetype_set(struct got_fileindex_entry *ie, int type) { ie->mode &= ~GOT_FILEIDX_MODE_FILE_TYPE_STAGED; ie->mode |= ((type << GOT_FILEIDX_MODE_FILE_TYPE_STAGED_SHIFT) & GOT_FILEIDX_MODE_FILE_TYPE_STAGED); } int got_fileindex_entry_staged_filetype_get(struct got_fileindex_entry *ie) { return (ie->mode & GOT_FILEIDX_MODE_FILE_TYPE_STAGED) >> GOT_FILEIDX_MODE_FILE_TYPE_STAGED_SHIFT; } int got_fileindex_entry_has_blob(struct got_fileindex_entry *ie) { return (ie->flags & GOT_FILEIDX_F_NO_BLOB) == 0; } int got_fileindex_entry_has_commit(struct got_fileindex_entry *ie) { return (ie->flags & GOT_FILEIDX_F_NO_COMMIT) == 0; } int got_fileindex_entry_has_file_on_disk(struct got_fileindex_entry *ie) { return (ie->flags & GOT_FILEIDX_F_NO_FILE_ON_DISK) == 0; } int got_fileindex_entry_was_skipped(struct got_fileindex_entry *ie) { return (ie->flags & GOT_FILEIDX_F_SKIPPED) != 0; } static const struct got_error * add_entry(struct got_fileindex *fileindex, struct got_fileindex_entry *ie) { if (fileindex->nentries >= GOT_FILEIDX_MAX_ENTRIES) return got_error(GOT_ERR_NO_SPACE); if (RB_INSERT(got_fileindex_tree, &fileindex->entries, ie) != NULL) return got_error_path(ie->path, GOT_ERR_FILEIDX_DUP_ENTRY); fileindex->nentries++; return NULL; } const struct got_error * got_fileindex_entry_add(struct got_fileindex *fileindex, struct got_fileindex_entry *ie) { /* Flag this entry until it gets written out to disk. */ ie->flags |= GOT_FILEIDX_F_NOT_FLUSHED; return add_entry(fileindex, ie); } void got_fileindex_entry_remove(struct got_fileindex *fileindex, struct got_fileindex_entry *ie) { /* * Removing an entry from the RB tree immediately breaks * in-progress iterations over file index entries. * So flag this entry for removal and remove it once the index * is written out to disk. Meanwhile, pretend this entry no longer * exists if we get queried for it again before then. */ ie->flags |= GOT_FILEIDX_F_REMOVE_ON_FLUSH; fileindex->nentries--; } struct got_fileindex_entry * got_fileindex_entry_get(struct got_fileindex *fileindex, const char *path, size_t path_len) { struct got_fileindex_entry *ie; struct got_fileindex_entry key; memset(&key, 0, sizeof(key)); key.path = (char *)path; key.flags = (path_len & GOT_FILEIDX_F_PATH_LEN); ie = RB_FIND(got_fileindex_tree, &fileindex->entries, &key); if (ie && (ie->flags & GOT_FILEIDX_F_REMOVE_ON_FLUSH)) return NULL; return ie; } const struct got_error * got_fileindex_for_each_entry_safe(struct got_fileindex *fileindex, got_fileindex_cb cb, void *cb_arg) { const struct got_error *err; struct got_fileindex_entry *ie, *tmp; RB_FOREACH_SAFE(ie, got_fileindex_tree, &fileindex->entries, tmp) { if (ie->flags & GOT_FILEIDX_F_REMOVE_ON_FLUSH) continue; err = (*cb)(cb_arg, ie); if (err) return err; } return NULL; } struct got_fileindex * got_fileindex_alloc(enum got_hash_algorithm algo) { struct got_fileindex *fileindex; fileindex = calloc(1, sizeof(*fileindex)); if (fileindex == NULL) return NULL; fileindex->version = GOT_FILE_INDEX_VERSION; fileindex->algo = algo; RB_INIT(&fileindex->entries); return fileindex; } void got_fileindex_free(struct got_fileindex *fileindex) { struct got_fileindex_entry *ie; while ((ie = RB_MIN(got_fileindex_tree, &fileindex->entries))) { RB_REMOVE(got_fileindex_tree, &fileindex->entries, ie); got_fileindex_entry_free(ie); } free(fileindex); } static const struct got_error * write_fileindex_val64(struct got_hash *ctx, uint64_t val, FILE *outfile) { size_t n; val = htobe64(val); got_hash_update(ctx, &val, sizeof(val)); n = fwrite(&val, 1, sizeof(val), outfile); if (n != sizeof(val)) return got_ferror(outfile, GOT_ERR_IO); return NULL; } static const struct got_error * write_fileindex_val32(struct got_hash *ctx, uint32_t val, FILE *outfile) { size_t n; val = htobe32(val); got_hash_update(ctx, &val, sizeof(val)); n = fwrite(&val, 1, sizeof(val), outfile); if (n != sizeof(val)) return got_ferror(outfile, GOT_ERR_IO); return NULL; } static const struct got_error * write_fileindex_val16(struct got_hash *ctx, uint16_t val, FILE *outfile) { size_t n; val = htobe16(val); got_hash_update(ctx, &val, sizeof(val)); n = fwrite(&val, 1, sizeof(val), outfile); if (n != sizeof(val)) return got_ferror(outfile, GOT_ERR_IO); return NULL; } static const struct got_error * write_fileindex_path(struct got_hash *ctx, const char *path, FILE *outfile) { size_t n, len, pad = 0; static const uint8_t zero[8] = { 0 }; len = strlen(path); while ((len + pad) % 8 != 0) pad++; if (pad == 0) pad = 8; /* NUL-terminate */ got_hash_update(ctx, path, len); n = fwrite(path, 1, len, outfile); if (n != len) return got_ferror(outfile, GOT_ERR_IO); got_hash_update(ctx, zero, pad); n = fwrite(zero, 1, pad, outfile); if (n != pad) return got_ferror(outfile, GOT_ERR_IO); return NULL; } static const struct got_error * write_fileindex_entry(struct got_hash *ctx, struct got_fileindex_entry *ie, FILE *outfile) { const struct got_error *err; size_t n; uint32_t stage; size_t digest_len = got_hash_digest_length(ctx->algo); err = write_fileindex_val64(ctx, ie->ctime_sec, outfile); if (err) return err; err = write_fileindex_val64(ctx, ie->ctime_nsec, outfile); if (err) return err; err = write_fileindex_val64(ctx, ie->mtime_sec, outfile); if (err) return err; err = write_fileindex_val64(ctx, ie->mtime_nsec, outfile); if (err) return err; err = write_fileindex_val32(ctx, ie->uid, outfile); if (err) return err; err = write_fileindex_val32(ctx, ie->gid, outfile); if (err) return err; err = write_fileindex_val32(ctx, ie->size, outfile); if (err) return err; err = write_fileindex_val16(ctx, ie->mode, outfile); if (err) return err; got_hash_update(ctx, ie->blob.hash, digest_len); n = fwrite(ie->blob.hash, 1, digest_len, outfile); if (n != digest_len) return got_ferror(outfile, GOT_ERR_IO); got_hash_update(ctx, ie->commit.hash, digest_len); n = fwrite(ie->commit.hash, 1, digest_len, outfile); if (n != digest_len) return got_ferror(outfile, GOT_ERR_IO); err = write_fileindex_val32(ctx, ie->flags, outfile); if (err) return err; err = write_fileindex_path(ctx, ie->path, outfile); if (err) return err; stage = got_fileindex_entry_stage_get(ie); if (stage == GOT_FILEIDX_STAGE_MODIFY || stage == GOT_FILEIDX_STAGE_ADD) { got_hash_update(ctx, ie->staged_blob.hash, digest_len); n = fwrite(ie->staged_blob.hash, 1, digest_len, outfile); if (n != digest_len) return got_ferror(outfile, GOT_ERR_IO); } return NULL; } const struct got_error * got_fileindex_write(struct got_fileindex *fileindex, FILE *outfile) { const struct got_error *err = NULL; struct got_fileindex_hdr hdr; struct got_hash ctx; uint8_t hash[GOT_HASH_DIGEST_MAXLEN]; size_t n, digest_len = got_hash_digest_length(fileindex->algo); struct got_fileindex_entry *ie, *tmp; memset(hash, 0, sizeof(hash)); got_hash_init(&ctx, fileindex->algo); hdr.signature = htobe32(GOT_FILE_INDEX_SIGNATURE); hdr.version = htobe32(GOT_FILE_INDEX_VERSION); hdr.nentries = htobe32(fileindex->nentries); hdr.algo = htobe32(fileindex->algo); got_hash_update(&ctx, &hdr.signature, sizeof(hdr.signature)); got_hash_update(&ctx, &hdr.version, sizeof(hdr.version)); got_hash_update(&ctx, &hdr.nentries, sizeof(hdr.nentries)); got_hash_update(&ctx, &hdr.algo, sizeof(hdr.algo)); n = fwrite(&hdr.signature, 1, sizeof(hdr.signature), outfile); if (n != sizeof(hdr.signature)) return got_ferror(outfile, GOT_ERR_IO); n = fwrite(&hdr.version, 1, sizeof(hdr.version), outfile); if (n != sizeof(hdr.version)) return got_ferror(outfile, GOT_ERR_IO); n = fwrite(&hdr.nentries, 1, sizeof(hdr.nentries), outfile); if (n != sizeof(hdr.nentries)) return got_ferror(outfile, GOT_ERR_IO); n = fwrite(&hdr.algo, 1, sizeof(hdr.algo), outfile); if (n != sizeof(hdr.nentries)) return got_ferror(outfile, GOT_ERR_IO); RB_FOREACH_SAFE(ie, got_fileindex_tree, &fileindex->entries, tmp) { ie->flags &= ~GOT_FILEIDX_F_NOT_FLUSHED; ie->flags &= ~GOT_FILEIDX_F_SKIPPED; if (ie->flags & GOT_FILEIDX_F_REMOVE_ON_FLUSH) { RB_REMOVE(got_fileindex_tree, &fileindex->entries, ie); got_fileindex_entry_free(ie); continue; } err = write_fileindex_entry(&ctx, ie, outfile); if (err) return err; } got_hash_final(&ctx, hash); n = fwrite(hash, 1, digest_len, outfile); if (n != digest_len) return got_ferror(outfile, GOT_ERR_IO); if (fflush(outfile) != 0) return got_error_from_errno("fflush"); return NULL; } static const struct got_error * read_fileindex_val64(uint64_t *val, struct got_hash *ctx, FILE *infile) { size_t n; n = fread(val, 1, sizeof(*val), infile); if (n != sizeof(*val)) return got_ferror(infile, GOT_ERR_FILEIDX_BAD); got_hash_update(ctx, val, sizeof(*val)); *val = be64toh(*val); return NULL; } static const struct got_error * read_fileindex_val32(uint32_t *val, struct got_hash *ctx, FILE *infile) { size_t n; n = fread(val, 1, sizeof(*val), infile); if (n != sizeof(*val)) return got_ferror(infile, GOT_ERR_FILEIDX_BAD); got_hash_update(ctx, val, sizeof(*val)); *val = be32toh(*val); return NULL; } static const struct got_error * read_fileindex_val16(uint16_t *val, struct got_hash *ctx, FILE *infile) { size_t n; n = fread(val, 1, sizeof(*val), infile); if (n != sizeof(*val)) return got_ferror(infile, GOT_ERR_FILEIDX_BAD); got_hash_update(ctx, val, sizeof(*val)); *val = be16toh(*val); return NULL; } static const struct got_error * read_fileindex_path(char **path, struct got_hash *ctx, FILE *infile) { const size_t chunk_size = 8; char p[PATH_MAX]; size_t n, len = 0; do { if (len + chunk_size > sizeof(p)) return got_error(GOT_ERR_FILEIDX_BAD); n = fread(&p[len], 1, chunk_size, infile); if (n != chunk_size) return got_ferror(infile, GOT_ERR_FILEIDX_BAD); got_hash_update(ctx, &p[len], chunk_size); len += chunk_size; } while (memchr(&p[len - chunk_size], '\0', chunk_size) == NULL); *path = strdup(p); if (*path == NULL) return got_error_from_errno("strdup"); return NULL; } static const struct got_error * read_fileindex_entry(struct got_fileindex_entry **iep, struct got_hash *ctx, FILE *infile, uint32_t version, enum got_hash_algorithm algo) { const struct got_error *err; struct got_fileindex_entry *ie; size_t n, digest_len = got_hash_digest_length(algo); *iep = NULL; ie = calloc(1, sizeof(*ie)); if (ie == NULL) return got_error_from_errno("calloc"); err = read_fileindex_val64(&ie->ctime_sec, ctx, infile); if (err) goto done; err = read_fileindex_val64(&ie->ctime_nsec, ctx, infile); if (err) goto done; err = read_fileindex_val64(&ie->mtime_sec, ctx, infile); if (err) goto done; err = read_fileindex_val64(&ie->mtime_nsec, ctx, infile); if (err) goto done; err = read_fileindex_val32(&ie->uid, ctx, infile); if (err) goto done; err = read_fileindex_val32(&ie->gid, ctx, infile); if (err) goto done; err = read_fileindex_val32(&ie->size, ctx, infile); if (err) goto done; err = read_fileindex_val16(&ie->mode, ctx, infile); if (err) goto done; ie->blob.algo = algo; n = fread(ie->blob.hash, 1, digest_len, infile); if (n != digest_len) { err = got_ferror(infile, GOT_ERR_FILEIDX_BAD); goto done; } got_hash_update(ctx, ie->blob.hash, digest_len); ie->commit.algo = algo; n = fread(ie->commit.hash, 1, digest_len, infile); if (n != digest_len) { err = got_ferror(infile, GOT_ERR_FILEIDX_BAD); goto done; } got_hash_update(ctx, ie->commit.hash, digest_len); err = read_fileindex_val32(&ie->flags, ctx, infile); if (err) goto done; err = read_fileindex_path(&ie->path, ctx, infile); if (err) goto done; if (version >= 2) { uint32_t stage = got_fileindex_entry_stage_get(ie); if (stage == GOT_FILEIDX_STAGE_MODIFY || stage == GOT_FILEIDX_STAGE_ADD) { ie->staged_blob.algo = algo; n = fread(ie->staged_blob.hash, 1, digest_len, infile); if (n != digest_len) { err = got_ferror(infile, GOT_ERR_FILEIDX_BAD); goto done; } got_hash_update(ctx, ie->staged_blob.hash, digest_len); } } else { /* GOT_FILE_INDEX_VERSION 1 does not support staging. */ ie->flags &= ~GOT_FILEIDX_F_STAGE; } done: if (err) got_fileindex_entry_free(ie); else *iep = ie; return err; } const struct got_error * got_fileindex_read(struct got_fileindex *fileindex, FILE *infile, enum got_hash_algorithm repo_algo) { const struct got_error *err = NULL; struct got_fileindex_hdr hdr; struct got_hash ctx; struct got_fileindex_entry *ie; enum got_hash_algorithm algo = repo_algo; uint8_t hash_expected[GOT_HASH_DIGEST_MAXLEN]; uint8_t hash[GOT_HASH_DIGEST_MAXLEN]; size_t n, digest_len; uint32_t version; int i; n = fread(&hdr.signature, 1, sizeof(hdr.signature), infile); if (n != sizeof(hdr.signature)) { if (n == 0) /* EOF */ return NULL; return got_ferror(infile, GOT_ERR_FILEIDX_BAD); } n = fread(&hdr.version, 1, sizeof(hdr.version), infile); if (n != sizeof(hdr.version)) { if (n == 0) /* EOF */ return NULL; return got_ferror(infile, GOT_ERR_FILEIDX_BAD); } n = fread(&hdr.nentries, 1, sizeof(hdr.nentries), infile); if (n != sizeof(hdr.nentries)) { if (n == 0) /* EOF */ return NULL; return got_ferror(infile, GOT_ERR_FILEIDX_BAD); } version = be32toh(hdr.version); if (version >= 3) { n = fread(&hdr.algo, 1, sizeof(hdr.algo), infile); if (n != sizeof(hdr.algo)) { if (n == 0) /* EOF */ return NULL; return got_ferror(infile, GOT_ERR_FILEIDX_BAD); } algo = be32toh(hdr.algo); if (algo != repo_algo) { const char *fmt = "unknown"; if (repo_algo == GOT_HASH_SHA1) fmt = "sha1"; else if (repo_algo == GOT_HASH_SHA256) fmt = "sha256"; return got_error_path(fmt, GOT_ERR_OBJECT_FORMAT); } } digest_len = got_hash_digest_length(algo); got_hash_init(&ctx, algo); got_hash_update(&ctx, &hdr.signature, sizeof(hdr.signature)); got_hash_update(&ctx, &hdr.version, sizeof(hdr.version)); got_hash_update(&ctx, &hdr.nentries, sizeof(hdr.nentries)); if (version >= 3) got_hash_update(&ctx, &hdr.algo, sizeof(hdr.algo)); hdr.signature = be32toh(hdr.signature); hdr.version = be32toh(hdr.version); hdr.nentries = be32toh(hdr.nentries); if (hdr.signature != GOT_FILE_INDEX_SIGNATURE) return got_error(GOT_ERR_FILEIDX_SIG); if (hdr.version > GOT_FILE_INDEX_VERSION) return got_error(GOT_ERR_FILEIDX_VER); fileindex->version = version; fileindex->algo = algo; for (i = 0; i < hdr.nentries; i++) { err = read_fileindex_entry(&ie, &ctx, infile, hdr.version, algo); if (err) return err; err = add_entry(fileindex, ie); if (err) { got_fileindex_entry_free(ie); return err; } } n = fread(hash_expected, 1, digest_len, infile); if (n != digest_len) return got_ferror(infile, GOT_ERR_FILEIDX_BAD); got_hash_final(&ctx, hash); if (got_hash_cmp(algo, hash, hash_expected) != 0) return got_error(GOT_ERR_FILEIDX_CSUM); return NULL; } uint32_t got_fileindex_get_version(struct got_fileindex *fileindex) { return fileindex->version; } static struct got_fileindex_entry * walk_fileindex(struct got_fileindex *fileindex, struct got_fileindex_entry *ie) { struct got_fileindex_entry *next; next = RB_NEXT(got_fileindex_tree, &fileindex->entries, ie); /* Skip entries which were added or removed by diff callbacks. */ while (next && (next->flags & (GOT_FILEIDX_F_NOT_FLUSHED | GOT_FILEIDX_F_REMOVE_ON_FLUSH))) next = RB_NEXT(got_fileindex_tree, &fileindex->entries, next); return next; } static const struct got_error * diff_fileindex_tree(struct got_fileindex *, struct got_fileindex_entry **ie, struct got_tree_object *tree, const char *, const char *, struct got_repository *, struct got_fileindex_diff_tree_cb *, void *); static const struct got_error * walk_tree(struct got_tree_entry **next, struct got_fileindex *fileindex, struct got_fileindex_entry **ie, struct got_tree_object *tree, int *tidx, const char *path, const char *entry_name, struct got_repository *repo, struct got_fileindex_diff_tree_cb *cb, void *cb_arg) { const struct got_error *err = NULL; struct got_tree_entry *te = got_object_tree_get_entry(tree, *tidx); if (!got_object_tree_entry_is_submodule(te) && S_ISDIR(got_tree_entry_get_mode(te))) { char *subpath; struct got_tree_object *subtree; if (asprintf(&subpath, "%s%s%s", path, path[0] == '\0' ? "" : "/", got_tree_entry_get_name(te)) == -1) return got_error_from_errno("asprintf"); err = got_object_open_as_tree(&subtree, repo, got_tree_entry_get_id(te)); if (err) { free(subpath); return err; } err = diff_fileindex_tree(fileindex, ie, subtree, subpath, entry_name, repo, cb, cb_arg); free(subpath); got_object_tree_close(subtree); if (err) return err; } (*tidx)++; *next = got_object_tree_get_entry(tree, *tidx); return NULL; } static const struct got_error * diff_fileindex_tree(struct got_fileindex *fileindex, struct got_fileindex_entry **ie, struct got_tree_object *tree, const char *path, const char *entry_name, struct got_repository *repo, struct got_fileindex_diff_tree_cb *cb, void *cb_arg) { const struct got_error *err = NULL; struct got_tree_entry *te = NULL; size_t path_len = strlen(path); struct got_fileindex_entry *next; int tidx = 0; te = got_object_tree_get_entry(tree, tidx); while ((*ie && got_path_is_child((*ie)->path, path, path_len)) || te) { if (te && *ie) { char *te_path; const char *te_name = got_tree_entry_get_name(te); int cmp; if (asprintf(&te_path, "%s/%s", path, te_name) == -1) { err = got_error_from_errno("asprintf"); break; } cmp = got_path_cmp((*ie)->path, te_path, got_fileindex_entry_path_len(*ie), strlen(te_path)); free(te_path); if (cmp == 0) { if (got_path_is_child((*ie)->path, path, path_len) && !got_object_tree_entry_is_submodule(te) && (entry_name == NULL || strcmp(te_name, entry_name) == 0)) { err = cb->diff_old_new(cb_arg, *ie, te, path); if (err || entry_name) break; } *ie = walk_fileindex(fileindex, *ie); err = walk_tree(&te, fileindex, ie, tree, &tidx, path, entry_name, repo, cb, cb_arg); } else if (cmp < 0) { next = walk_fileindex(fileindex, *ie); if (got_path_is_child((*ie)->path, path, path_len) && entry_name == NULL) { err = cb->diff_old(cb_arg, *ie, path); if (err || entry_name) break; } *ie = next; } else { if ((entry_name == NULL || strcmp(te_name, entry_name) == 0)) { err = cb->diff_new(cb_arg, te, path); if (err || entry_name) break; } err = walk_tree(&te, fileindex, ie, tree, &tidx, path, entry_name, repo, cb, cb_arg); } if (err) break; } else if (*ie) { next = walk_fileindex(fileindex, *ie); if (got_path_is_child((*ie)->path, path, path_len) && (entry_name == NULL || (te && strcmp(got_tree_entry_get_name(te), entry_name) == 0))) { err = cb->diff_old(cb_arg, *ie, path); if (err || entry_name) break; } *ie = next; } else if (te) { if (!got_object_tree_entry_is_submodule(te) && (entry_name == NULL || strcmp(got_tree_entry_get_name(te), entry_name) == 0)) { err = cb->diff_new(cb_arg, te, path); if (err || entry_name) break; } err = walk_tree(&te, fileindex, ie, tree, &tidx, path, entry_name, repo, cb, cb_arg); if (err) break; } } return err; } const struct got_error * got_fileindex_diff_tree(struct got_fileindex *fileindex, struct got_tree_object *tree, const char *path, const char *entry_name, struct got_repository *repo, struct got_fileindex_diff_tree_cb *cb, void *cb_arg) { struct got_fileindex_entry *ie; ie = RB_MIN(got_fileindex_tree, &fileindex->entries); while (ie && !got_path_is_child(ie->path, path, strlen(path))) ie = walk_fileindex(fileindex, ie); return diff_fileindex_tree(fileindex, &ie, tree, path, entry_name, repo, cb, cb_arg); } static const struct got_error * diff_fileindex_dir(struct got_fileindex *, struct got_fileindex_entry **, struct got_pathlist_head *, int, const char *, const char *, struct got_repository *, struct got_fileindex_diff_dir_cb *, void *); static struct dirent * copy_dirent(const struct dirent *de) { size_t amt = de->d_reclen; struct dirent *copy; copy = malloc(amt); if (copy != NULL) { memcpy(copy, de, amt); } return copy; } static const struct got_error * read_dirlist(struct got_pathlist_head *dirlist, DIR *dir, const char *path) { const struct got_error *err = NULL; struct got_pathlist_entry *new = NULL; struct dirent *de = NULL; for (;;) { errno = 0; if ((de = readdir(dir)) == NULL) { if (errno != 0) { err = got_error_from_errno("readdir"); } break; } if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0 || (path[0] == '\0' && strcmp(de->d_name, GOT_WORKTREE_GOT_DIR) == 0) || (path[0] == '\0' && strcmp(de->d_name, GOT_WORKTREE_CVG_DIR) == 0)) { continue; } de = copy_dirent(de); if (de == NULL) { err = got_error_from_errno("malloc"); break; } err = got_pathlist_insert(&new, dirlist, de->d_name, de); if (err) { free(de); break; } if (new == NULL) { err = got_error(GOT_ERR_DIR_DUP_ENTRY); free(de); break; } } return err; } static int have_tracked_file_in_dir(struct got_fileindex *fileindex, const char *path) { struct got_fileindex_entry *ie; size_t path_len = strlen(path); int cmp; ie = RB_ROOT(&fileindex->entries); while (ie) { if (got_path_is_child(ie->path, path, path_len)) return 1; cmp = got_path_cmp(path, ie->path, path_len, got_fileindex_entry_path_len(ie)); if (cmp < 0) ie = RB_LEFT(ie, entry); else if (cmp > 0) ie = RB_RIGHT(ie, entry); else break; } return 0; } static const struct got_error * walk_dir(struct got_pathlist_entry **next, struct got_fileindex *fileindex, struct got_fileindex_entry **ie, struct got_pathlist_entry *dle, struct got_pathlist_head *dlh, int fd, const char *path, const char *rootpath, struct got_repository *repo, int ignore, struct got_fileindex_diff_dir_cb *cb, void *cb_arg) { const struct got_error *err = NULL; struct dirent *de = dle->data; DIR *subdir = NULL; int subdirfd = -1; *next = NULL; /* Must traverse ignored directories if they contain tracked files. */ if (de->d_type == DT_DIR && ignore && have_tracked_file_in_dir(fileindex, path)) ignore = 0; if (de->d_type == DT_DIR && !ignore) { char *subpath; char *subdirpath; struct got_pathlist_head subdirlist; RB_INIT(&subdirlist); if (asprintf(&subpath, "%s%s%s", path, path[0] == '\0' ? "" : "/", de->d_name) == -1) return got_error_from_errno("asprintf"); if (asprintf(&subdirpath, "%s/%s", rootpath, subpath) == -1) { free(subpath); return got_error_from_errno("asprintf"); } subdirfd = openat(fd, de->d_name, O_RDONLY | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC); if (subdirfd == -1) { if (errno == EACCES) { *next = RB_NEXT(got_pathlist_head, dlh, dle); return NULL; } err = got_error_from_errno2("openat", subdirpath); free(subpath); free(subdirpath); return err; } subdir = fdopendir(subdirfd); if (subdir == NULL) { err = got_error_from_errno2("fdopendir", path); close(subdirfd); free(subpath); free(subdirpath); return err; } subdirfd = -1; err = read_dirlist(&subdirlist, subdir, subdirpath); if (err) { free(subpath); free(subdirpath); closedir(subdir); return err; } err = diff_fileindex_dir(fileindex, ie, &subdirlist, dirfd(subdir), rootpath, subpath, repo, cb, cb_arg); if (subdir && closedir(subdir) == -1 && err == NULL) err = got_error_from_errno2("closedir", subdirpath); free(subpath); free(subdirpath); got_pathlist_free(&subdirlist, GOT_PATHLIST_FREE_DATA); if (err) return err; } *next = RB_NEXT(got_pathlist_head, dlh, dle); return NULL; } static const struct got_error * dirent_type_fixup(struct dirent *de, const char *rootpath, const char *path) { const struct got_error *err; char *dir_path; int type; if (de->d_type != DT_UNKNOWN) return NULL; /* DT_UNKNOWN occurs on NFS mounts without "readdir plus" RPC. */ if (asprintf(&dir_path, "%s/%s", rootpath, path) == -1) return got_error_from_errno("asprintf"); err = got_path_dirent_type(&type, dir_path, de); free(dir_path); if (err) return err; de->d_type = type; return NULL; } static const struct got_error * diff_fileindex_dir(struct got_fileindex *fileindex, struct got_fileindex_entry **ie, struct got_pathlist_head *dirlist, int dirfd, const char *rootpath, const char *path, struct got_repository *repo, struct got_fileindex_diff_dir_cb *cb, void *cb_arg) { const struct got_error *err = NULL; struct dirent *de = NULL; size_t path_len = strlen(path); struct got_pathlist_entry *dle; int ignore; if (cb->diff_traverse) { err = cb->diff_traverse(cb_arg, path, dirfd); if (err) return err; } dle = RB_MIN(got_pathlist_head, dirlist); while ((*ie && got_path_is_child((*ie)->path, path, path_len)) || dle) { if (dle && *ie) { char *de_path; int cmp; de = dle->data; err = dirent_type_fixup(de, rootpath, path); if (err) break; if (asprintf(&de_path, "%s/%s", path, de->d_name) == -1) { err = got_error_from_errno("asprintf"); break; } cmp = got_path_cmp((*ie)->path, de_path, got_fileindex_entry_path_len(*ie), strlen(path) + 1 + strlen(de->d_name)); free(de_path); if (cmp == 0) { err = cb->diff_old_new(cb_arg, *ie, de, path, dirfd); if (err) break; *ie = walk_fileindex(fileindex, *ie); err = walk_dir(&dle, fileindex, ie, dle, dirlist, dirfd, path, rootpath, repo, 0, cb, cb_arg); } else if (cmp < 0) { err = cb->diff_old(cb_arg, *ie, path); if (err) break; *ie = walk_fileindex(fileindex, *ie); } else { err = cb->diff_new(&ignore, cb_arg, de, path, dirfd); if (err) break; err = walk_dir(&dle, fileindex, ie, dle, dirlist, dirfd, path, rootpath, repo, ignore, cb, cb_arg); } if (err) break; } else if (*ie) { err = cb->diff_old(cb_arg, *ie, path); if (err) break; *ie = walk_fileindex(fileindex, *ie); } else if (dle) { de = dle->data; err = dirent_type_fixup(de, rootpath, path); if (err) break; err = cb->diff_new(&ignore, cb_arg, de, path, dirfd); if (err) break; err = walk_dir(&dle, fileindex, ie, dle, dirlist, dirfd, path, rootpath, repo, ignore, cb, cb_arg); if (err) break; } } return err; } const struct got_error * got_fileindex_diff_dir(struct got_fileindex *fileindex, int fd, const char *rootpath, const char *path, struct got_repository *repo, struct got_fileindex_diff_dir_cb *cb, void *cb_arg) { const struct got_error *err; struct got_fileindex_entry *ie; struct got_pathlist_head dirlist; int fd2; DIR *dir; RB_INIT(&dirlist); /* * Duplicate the file descriptor so we can call closedir() below * without closing the file descriptor passed in by our caller. */ fd2 = dup(fd); if (fd2 == -1) return got_error_from_errno2("dup", path); if (lseek(fd2, 0, SEEK_SET) == -1) { err = got_error_from_errno2("lseek", path); close(fd2); return err; } dir = fdopendir(fd2); if (dir == NULL) { err = got_error_from_errno2("fdopendir", path); close(fd2); return err; } err = read_dirlist(&dirlist, dir, path); if (err) { closedir(dir); return err; } ie = RB_MIN(got_fileindex_tree, &fileindex->entries); while (ie && !got_path_is_child(ie->path, path, strlen(path))) ie = walk_fileindex(fileindex, ie); err = diff_fileindex_dir(fileindex, &ie, &dirlist, dirfd(dir), rootpath, path, repo, cb, cb_arg); if (closedir(dir) == -1 && err == NULL) err = got_error_from_errno2("closedir", path); got_pathlist_free(&dirlist, GOT_PATHLIST_FREE_DATA); return err; } struct got_object_id * got_fileindex_entry_get_staged_blob_id(struct got_object_id *id, struct got_fileindex_entry *ie) { return memcpy(id, &ie->staged_blob, sizeof(*id)); } struct got_object_id * got_fileindex_entry_get_blob_id(struct got_object_id *id, struct got_fileindex_entry *ie) { return memcpy(id, &ie->blob, sizeof(*id)); } struct got_object_id * got_fileindex_entry_get_commit_id(struct got_object_id *id, struct got_fileindex_entry *ie) { return memcpy(id, &ie->commit, sizeof(*id)); } RB_GENERATE(got_fileindex_tree, got_fileindex_entry, entry, got_fileindex_cmp); got-portable-0.111/lib/object_open_io.c0000644000175000017500000005604215001741021013462 /* * Copyright (c) 2022 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_object.h" #include "got_repository.h" #include "got_path.h" #include "got_lib_delta.h" #include "got_lib_hash.h" #include "got_lib_object.h" #include "got_lib_object_cache.h" #include "got_lib_object_parse.h" #include "got_lib_pack.h" #include "got_lib_repository.h" #include "got_lib_inflate.h" const struct got_error * got_object_open_packed(struct got_object **obj, struct got_object_id *id, struct got_repository *repo) { const struct got_error *err = NULL; struct got_pack *pack = NULL; struct got_packidx *packidx = NULL; int idx; char *path_packfile; err = got_repo_search_packidx(&packidx, &idx, repo, id); if (err) return err; err = got_packidx_get_packfile_path(&path_packfile, packidx->path_packidx); if (err) return err; pack = got_repo_get_cached_pack(repo, path_packfile); if (pack == NULL) { err = got_repo_cache_pack(&pack, repo, path_packfile, packidx); if (err) goto done; } err = got_packfile_open_object(obj, pack, packidx, idx, id); if (err) return err; (*obj)->refcnt++; err = got_repo_cache_object(repo, id, *obj); if (err) { if (err->code == GOT_ERR_OBJ_EXISTS || err->code == GOT_ERR_OBJ_TOO_LARGE) err = NULL; } done: free(path_packfile); return err; } const struct got_error * got_object_open_from_packfile(struct got_object **obj, struct got_object_id *id, struct got_pack *pack, struct got_packidx *packidx, int obj_idx, struct got_repository *repo) { const struct got_error *err; *obj = got_repo_get_cached_object(repo, id); if (*obj != NULL) { (*obj)->refcnt++; return NULL; } err = got_packfile_open_object(obj, pack, packidx, obj_idx, id); if (err) return err; (*obj)->refcnt++; err = got_repo_cache_object(repo, id, *obj); if (err) { if (err->code == GOT_ERR_OBJ_EXISTS || err->code == GOT_ERR_OBJ_TOO_LARGE) err = NULL; return err; } (*obj)->refcnt++; return NULL; } const struct got_error * got_object_read_raw_delta(uint64_t *base_size, uint64_t *result_size, off_t *delta_size, off_t *delta_compressed_size, off_t *delta_offset, off_t *delta_out_offset, struct got_object_id **base_id, int delta_cache_fd, struct got_packidx *packidx, int obj_idx, struct got_object_id *id, struct got_repository *repo) { return got_error(GOT_ERR_NOT_IMPL); } const struct got_error * got_object_open(struct got_object **obj, struct got_repository *repo, struct got_object_id *id) { const struct got_error *err = NULL; int fd; *obj = got_repo_get_cached_object(repo, id); if (*obj != NULL) { (*obj)->refcnt++; return NULL; } err = got_object_open_packed(obj, id, repo); if (err) { if (err->code != GOT_ERR_NO_OBJ) return err; } else return NULL; err = got_object_open_loose_fd(&fd, id, repo); if (err) return err; err = got_object_read_header(obj, fd); if (err) goto done; memcpy(&(*obj)->id, id, sizeof((*obj)->id)); (*obj)->refcnt++; err = got_repo_cache_object(repo, id, *obj); if (err) { if (err->code == GOT_ERR_OBJ_EXISTS || err->code == GOT_ERR_OBJ_TOO_LARGE) err = NULL; } done: if (close(fd) == -1 && err == NULL) err = got_error_from_errno("close"); return err; } static const struct got_error * wrap_fd(FILE **f, int wrapped_fd) { const struct got_error *err = NULL; int fd; if (ftruncate(wrapped_fd, 0L) == -1) return got_error_from_errno("ftruncate"); if (lseek(wrapped_fd, 0L, SEEK_SET) == -1) return got_error_from_errno("lseek"); fd = dup(wrapped_fd); if (fd == -1) return got_error_from_errno("dup"); *f = fdopen(fd, "w+"); if (*f == NULL) { err = got_error_from_errno("fdopen"); close(fd); } return err; } static const struct got_error * read_packed_object_raw(uint8_t **outbuf, off_t *size, size_t *hdrlen, int outfd, struct got_pack *pack, struct got_packidx *packidx, int idx, struct got_object_id *id) { const struct got_error *err = NULL; uint64_t raw_size = 0; struct got_object *obj; FILE *outfile = NULL, *basefile = NULL, *accumfile = NULL; *outbuf = NULL; *size = 0; *hdrlen = 0; err = got_packfile_open_object(&obj, pack, packidx, idx, id); if (err) return err; if (obj->flags & GOT_OBJ_FLAG_DELTIFIED) { err = got_pack_get_max_delta_object_size(&raw_size, obj, pack); if (err) goto done; } else raw_size = obj->size; if (raw_size <= GOT_DELTA_RESULT_SIZE_CACHED_MAX) { size_t len; err = got_packfile_extract_object_to_mem(outbuf, &len, obj, pack); if (err) goto done; *size = (off_t)len; } else { /* * XXX This uses 3 file extra descriptors for no good reason. * We should have got_packfile_extract_object_to_fd(). */ err = wrap_fd(&outfile, outfd); if (err) goto done; err = wrap_fd(&basefile, pack->basefd); if (err) goto done; err = wrap_fd(&accumfile, pack->accumfd); if (err) goto done; err = got_packfile_extract_object(pack, obj, outfile, basefile, accumfile); if (err) goto done; *size = obj->size; } *hdrlen = obj->hdrlen; done: got_object_close(obj); if (outfile && fclose(outfile) == EOF && err == NULL) err = got_error_from_errno("fclose"); if (basefile && fclose(basefile) == EOF && err == NULL) err = got_error_from_errno("fclose"); if (accumfile && fclose(accumfile) == EOF && err == NULL) err = got_error_from_errno("fclose"); return err; } static void put_raw_object_tempfile(struct got_raw_object *obj) { struct got_repository *repo = obj->close_arg; if (obj->tempfile_idx != -1) got_repo_temp_fds_put(obj->tempfile_idx, repo); } /* *outfd must be initialized to -1 by caller */ const struct got_error * got_object_raw_open(struct got_raw_object **obj, int *outfd, struct got_repository *repo, struct got_object_id *id) { const struct got_error *err = NULL; struct got_packidx *packidx = NULL; int idx, tempfd, tempfile_idx; uint8_t *outbuf = NULL; off_t size = 0; size_t hdrlen = 0; char *path_packfile = NULL; *obj = got_repo_get_cached_raw_object(repo, id); if (*obj != NULL) { (*obj)->refcnt++; return NULL; } err = got_repo_temp_fds_get(&tempfd, &tempfile_idx, repo); if (err) return err; err = got_repo_search_packidx(&packidx, &idx, repo, id); if (err == NULL) { struct got_pack *pack = NULL; err = got_packidx_get_packfile_path(&path_packfile, packidx->path_packidx); if (err) goto done; pack = got_repo_get_cached_pack(repo, path_packfile); if (pack == NULL) { err = got_repo_cache_pack(&pack, repo, path_packfile, packidx); if (err) goto done; } err = read_packed_object_raw(&outbuf, &size, &hdrlen, tempfd, pack, packidx, idx, id); if (err) goto done; } else if (err->code == GOT_ERR_NO_OBJ) { int fd; err = got_object_open_loose_fd(&fd, id, repo); if (err) goto done; err = got_object_read_raw(&outbuf, &size, &hdrlen, GOT_DELTA_RESULT_SIZE_CACHED_MAX, tempfd, id, fd); if (close(fd) == -1 && err == NULL) err = got_error_from_errno("close"); if (err) goto done; } if (outbuf == NULL) { if (*outfd != -1) { err = got_error_msg(GOT_ERR_NOT_IMPL, "bad outfd"); goto done; } /* * Duplicate tempfile descriptor to allow use of * fdopen(3) inside got_object_raw_alloc(). */ *outfd = dup(tempfd); if (*outfd == -1) { err = got_error_from_errno("dup"); goto done; } } err = got_object_raw_alloc(obj, outbuf, outfd, GOT_DELTA_RESULT_SIZE_CACHED_MAX, hdrlen, size); if (err) goto done; err = got_repo_cache_raw_object(repo, id, *obj); if (err) { if (err->code == GOT_ERR_OBJ_EXISTS || err->code == GOT_ERR_OBJ_TOO_LARGE) err = NULL; } done: free(path_packfile); if (err) { if (*obj) { got_object_raw_close(*obj); *obj = NULL; } free(outbuf); got_repo_temp_fds_put(tempfile_idx, repo); if (*outfd != -1) { close(*outfd); *outfd = -1; } } else { if (((*obj)->f == NULL && (*obj)->fd == -1)) { /* This raw object is not backed by a file. */ got_repo_temp_fds_put(tempfile_idx, repo); if (*outfd != -1) { close(*outfd); *outfd = -1; } } else { (*obj)->tempfile_idx = tempfile_idx; (*obj)->close_cb = put_raw_object_tempfile; (*obj)->close_arg = repo; } } return err; } static const struct got_error * open_commit(struct got_commit_object **commit, struct got_repository *repo, struct got_object_id *id, int check_cache) { const struct got_error *err = NULL; struct got_packidx *packidx = NULL; int idx; char *path_packfile = NULL; if (check_cache) { *commit = got_repo_get_cached_commit(repo, id); if (*commit != NULL) { (*commit)->refcnt++; return NULL; } } else *commit = NULL; err = got_repo_search_packidx(&packidx, &idx, repo, id); if (err == NULL) { struct got_pack *pack = NULL; struct got_object *obj; uint8_t *buf; size_t len; err = got_packidx_get_packfile_path(&path_packfile, packidx->path_packidx); if (err) return err; pack = got_repo_get_cached_pack(repo, path_packfile); if (pack == NULL) { err = got_repo_cache_pack(&pack, repo, path_packfile, packidx); if (err) goto done; } err = got_packfile_open_object(&obj, pack, packidx, idx, id); if (err) goto done; err = got_packfile_extract_object_to_mem(&buf, &len, obj, pack); got_object_close(obj); if (err) goto done; err = got_object_parse_commit(commit, buf, len, got_repo_get_object_format(repo)); free(buf); } else if (err->code == GOT_ERR_NO_OBJ) { int fd; err = got_object_open_loose_fd(&fd, id, repo); if (err) return err; err = got_object_read_commit(commit, fd, id, 0); if (close(fd) == -1 && err == NULL) err = got_error_from_errno("close"); if (err) return err; } if (err == NULL) { (*commit)->refcnt++; err = got_repo_cache_commit(repo, id, *commit); if (err) { if (err->code == GOT_ERR_OBJ_EXISTS || err->code == GOT_ERR_OBJ_TOO_LARGE) err = NULL; } } done: free(path_packfile); return err; } const struct got_error * got_object_open_as_commit(struct got_commit_object **commit, struct got_repository *repo, struct got_object_id *id) { *commit = got_repo_get_cached_commit(repo, id); if (*commit != NULL) { (*commit)->refcnt++; return NULL; } return open_commit(commit, repo, id, 0); } const struct got_error * got_object_commit_open(struct got_commit_object **commit, struct got_repository *repo, struct got_object *obj) { return open_commit(commit, repo, got_object_get_id(obj), 1); } static const struct got_error * open_tree(struct got_tree_object **tree, struct got_repository *repo, struct got_object_id *id, int check_cache) { const struct got_error *err = NULL; struct got_packidx *packidx = NULL; int idx; char *path_packfile = NULL; struct got_parsed_tree_entry *entries = NULL; size_t nentries = 0, nentries_alloc = 0, i; uint8_t *buf = NULL; if (check_cache) { *tree = got_repo_get_cached_tree(repo, id); if (*tree != NULL) { (*tree)->refcnt++; return NULL; } } else *tree = NULL; err = got_repo_search_packidx(&packidx, &idx, repo, id); if (err == NULL) { struct got_pack *pack = NULL; struct got_object *obj; size_t len; err = got_packidx_get_packfile_path(&path_packfile, packidx->path_packidx); if (err) return err; pack = got_repo_get_cached_pack(repo, path_packfile); if (pack == NULL) { err = got_repo_cache_pack(&pack, repo, path_packfile, packidx); if (err) goto done; } err = got_packfile_open_object(&obj, pack, packidx, idx, id); if (err) goto done; err = got_packfile_extract_object_to_mem(&buf, &len, obj, pack); got_object_close(obj); if (err) goto done; err = got_object_parse_tree(&entries, &nentries, &nentries_alloc, buf, len, got_repo_get_object_format(repo)); if (err) goto done; } else if (err->code == GOT_ERR_NO_OBJ) { int fd; err = got_object_open_loose_fd(&fd, id, repo); if (err) return err; err = got_object_read_tree(&entries, &nentries, &nentries_alloc, &buf, fd, id); if (close(fd) == -1 && err == NULL) err = got_error_from_errno("close"); if (err) goto done; } else goto done; *tree = malloc(sizeof(**tree)); if (*tree == NULL) { err = got_error_from_errno("malloc"); goto done; } (*tree)->entries = calloc(nentries, sizeof(struct got_tree_entry)); if ((*tree)->entries == NULL) { err = got_error_from_errno("malloc"); goto done; } (*tree)->nentries = nentries; (*tree)->refcnt = 0; for (i = 0; i < nentries; i++) { struct got_parsed_tree_entry *pe = &entries[i]; struct got_tree_entry *te = &(*tree)->entries[i]; if (strlcpy(te->name, pe->name, sizeof(te->name)) >= sizeof(te->name)) { err = got_error(GOT_ERR_NO_SPACE); goto done; } memcpy(te->id.hash, pe->id, pe->digest_len); te->id.algo = pe->algo; te->mode = pe->mode; te->idx = i; } done: free(path_packfile); free(entries); free(buf); if (err == NULL) { (*tree)->refcnt++; err = got_repo_cache_tree(repo, id, *tree); if (err) { if (err->code == GOT_ERR_OBJ_EXISTS || err->code == GOT_ERR_OBJ_TOO_LARGE) err = NULL; } } if (err) { if (*tree) free((*tree)->entries); free(*tree); *tree = NULL; } return err; } const struct got_error * got_object_open_as_tree(struct got_tree_object **tree, struct got_repository *repo, struct got_object_id *id) { *tree = got_repo_get_cached_tree(repo, id); if (*tree != NULL) { (*tree)->refcnt++; return NULL; } return open_tree(tree, repo, id, 0); } const struct got_error * got_object_tree_open(struct got_tree_object **tree, struct got_repository *repo, struct got_object *obj) { return open_tree(tree, repo, got_object_get_id(obj), 1); } static const struct got_error * read_packed_blob(uint8_t **outbuf, size_t *size, size_t *hdrlen, int outfd, struct got_pack *pack, struct got_packidx *packidx, int idx, struct got_object_id *id, struct got_repository *repo) { const struct got_error *err = NULL; struct got_object *obj; FILE *outfile = NULL, *basefile = NULL, *accumfile = NULL; uint64_t blob_size; *hdrlen = 0; err = got_object_open_from_packfile(&obj, id, pack, packidx, idx, repo); if (err) return err; if (obj->flags & GOT_OBJ_FLAG_DELTIFIED) { err = got_pack_get_max_delta_object_size(&blob_size, obj, pack); if (err) goto done; } else blob_size = obj->size; if (blob_size <= GOT_DELTA_RESULT_SIZE_CACHED_MAX) { err = got_packfile_extract_object_to_mem(outbuf, size, obj, pack); } else { /* * XXX This uses 3 file extra descriptors for no good reason. * We should have got_packfile_extract_object_to_fd(). */ err = wrap_fd(&outfile, outfd); if (err) goto done; err = wrap_fd(&basefile, pack->basefd); if (err) goto done; err = wrap_fd(&accumfile, pack->accumfd); if (err) goto done; err = got_packfile_extract_object(pack, obj, outfile, basefile, accumfile); if (err) goto done; *size = obj->size; } /* XXX verify checksum? */ done: got_object_close(obj); if (outfile && fclose(outfile) == EOF && err == NULL) err = got_error_from_errno("fclose"); if (basefile && fclose(basefile) == EOF && err == NULL) err = got_error_from_errno("fclose"); if (accumfile && fclose(accumfile) == EOF && err == NULL) err = got_error_from_errno("fclose"); return err; } static const struct got_error * read_blob(uint8_t **outbuf, size_t *size, size_t *hdrlen, int outfd, int infd, struct got_object_id *id, struct got_repository *repo) { const struct got_error *err = NULL; struct got_object *obj = NULL; FILE *f = NULL; struct got_object_id expected_id; struct got_inflate_checksum csum; struct got_hash ctx; got_hash_init(&ctx, got_repo_get_object_format(repo)); memset(&csum, 0, sizeof(csum)); csum.output_ctx = &ctx; memcpy(&expected_id, id, sizeof(expected_id)); err = got_object_read_header(&obj, infd); if (err) goto done; if (lseek(infd, SEEK_SET, 0) == -1) { err = got_error_from_errno("lseek"); goto done; } f = fdopen(infd, "rb"); if (f == NULL) { err = got_error_from_errno("fdopen"); goto done; } infd = -1; if (obj->size + obj->hdrlen <= GOT_DELTA_RESULT_SIZE_CACHED_MAX) { err = got_inflate_to_mem(outbuf, size, NULL, &csum, f); if (err) goto done; } else { err = got_inflate_to_fd(size, f, &csum, outfd); if (err) goto done; } if (*size < obj->hdrlen) { err = got_error(GOT_ERR_BAD_OBJ_HDR); goto done; } *hdrlen = obj->hdrlen; got_hash_final_object_id(&ctx, id); if (got_object_id_cmp(&expected_id, id) != 0) { err = got_error_checksum(&expected_id); goto done; } done: if (f && fclose(f) == EOF && err == NULL) err = got_error_from_errno("fclose"); if (infd != -1 && close(infd) == -1 && err == NULL) err = got_error_from_errno("close"); return err; } static const struct got_error * open_blob(struct got_blob_object **blob, struct got_repository *repo, struct got_object_id *id, size_t blocksize, int outfd) { const struct got_error *err = NULL; struct got_packidx *packidx = NULL; int idx, dfd = -1; char *path_packfile = NULL; uint8_t *outbuf; size_t size, hdrlen; struct stat sb; *blob = calloc(1, sizeof(**blob)); if (*blob == NULL) return got_error_from_errno("calloc"); (*blob)->read_buf = malloc(blocksize); if ((*blob)->read_buf == NULL) { err = got_error_from_errno("malloc"); goto done; } if (ftruncate(outfd, 0L) == -1) { err = got_error_from_errno("ftruncate"); goto done; } if (lseek(outfd, SEEK_SET, 0) == -1) { err = got_error_from_errno("lseek"); goto done; } err = got_repo_search_packidx(&packidx, &idx, repo, id); if (err == NULL) { struct got_pack *pack = NULL; err = got_packidx_get_packfile_path(&path_packfile, packidx->path_packidx); if (err) goto done; pack = got_repo_get_cached_pack(repo, path_packfile); if (pack == NULL) { err = got_repo_cache_pack(&pack, repo, path_packfile, packidx); if (err) goto done; } err = read_packed_blob(&outbuf, &size, &hdrlen, outfd, pack, packidx, idx, id, repo); } else if (err->code == GOT_ERR_NO_OBJ) { int infd; err = got_object_open_loose_fd(&infd, id, repo); if (err) goto done; err = read_blob(&outbuf, &size, &hdrlen, outfd, infd, id, repo); } if (err) goto done; if (hdrlen > size) { err = got_error(GOT_ERR_BAD_OBJ_HDR); goto done; } if (outbuf && size > 0) { (*blob)->f = fmemopen(outbuf, size, "rb"); if ((*blob)->f == NULL) { err = got_error_from_errno("fmemopen"); free(outbuf); goto done; } (*blob)->data = outbuf; } else { if (fstat(outfd, &sb) == -1) { err = got_error_from_errno("fstat"); goto done; } if (sb.st_size != size) { err = got_error(GOT_ERR_PRIVSEP_LEN); goto done; } dfd = dup(outfd); if (dfd == -1) { err = got_error_from_errno("dup"); goto done; } (*blob)->f = fdopen(dfd, "rb"); if ((*blob)->f == NULL) { err = got_error_from_errno("fdopen"); close(dfd); dfd = -1; goto done; } } (*blob)->hdrlen = hdrlen; (*blob)->blocksize = blocksize; memcpy(&(*blob)->id, id, sizeof(*id)); done: free(path_packfile); if (err) { if (*blob) { got_object_blob_close(*blob); *blob = NULL; } } return err; } const struct got_error * got_object_open_as_blob(struct got_blob_object **blob, struct got_repository *repo, struct got_object_id *id, size_t blocksize, int outfd) { return open_blob(blob, repo, id, blocksize, outfd); } const struct got_error * got_object_blob_open(struct got_blob_object **blob, struct got_repository *repo, struct got_object *obj, size_t blocksize, int outfd) { return open_blob(blob, repo, got_object_get_id(obj), blocksize, outfd); } static const struct got_error * open_tag(struct got_tag_object **tag, struct got_repository *repo, struct got_object_id *id, int check_cache) { const struct got_error *err = NULL; struct got_packidx *packidx = NULL; int idx; char *path_packfile = NULL; struct got_object *obj = NULL; int obj_type = GOT_OBJ_TYPE_ANY; if (check_cache) { *tag = got_repo_get_cached_tag(repo, id); if (*tag != NULL) { (*tag)->refcnt++; return NULL; } } else *tag = NULL; err = got_repo_search_packidx(&packidx, &idx, repo, id); if (err == NULL) { struct got_pack *pack = NULL; uint8_t *buf = NULL; size_t len; err = got_packidx_get_packfile_path(&path_packfile, packidx->path_packidx); if (err) return err; pack = got_repo_get_cached_pack(repo, path_packfile); if (pack == NULL) { err = got_repo_cache_pack(&pack, repo, path_packfile, packidx); if (err) goto done; } /* Beware of "lightweight" tags: Check object type first. */ err = got_packfile_open_object(&obj, pack, packidx, idx, id); if (err) goto done; obj_type = obj->type; if (obj_type != GOT_OBJ_TYPE_TAG) { err = got_error(GOT_ERR_OBJ_TYPE); got_object_close(obj); goto done; } err = got_packfile_extract_object_to_mem(&buf, &len, obj, pack); got_object_close(obj); if (err) goto done; err = got_object_parse_tag(tag, buf, len, got_repo_get_object_format(repo)); free(buf); } else if (err->code == GOT_ERR_NO_OBJ) { int fd; err = got_object_open_loose_fd(&fd, id, repo); if (err) return err; err = got_object_read_header(&obj, fd); if (close(fd) == -1 && err == NULL) err = got_error_from_errno("close"); if (err) return err; obj_type = obj->type; got_object_close(obj); if (obj_type != GOT_OBJ_TYPE_TAG) return got_error(GOT_ERR_OBJ_TYPE); err = got_object_open_loose_fd(&fd, id, repo); if (err) return err; err = got_object_read_tag(tag, fd, id, 0); if (close(fd) == -1 && err == NULL) err = got_error_from_errno("close"); if (err) return err; } if (err == NULL) { (*tag)->refcnt++; err = got_repo_cache_tag(repo, id, *tag); if (err) { if (err->code == GOT_ERR_OBJ_EXISTS || err->code == GOT_ERR_OBJ_TOO_LARGE) err = NULL; } } done: free(path_packfile); return err; } const struct got_error * got_object_open_as_tag(struct got_tag_object **tag, struct got_repository *repo, struct got_object_id *id) { *tag = got_repo_get_cached_tag(repo, id); if (*tag != NULL) { (*tag)->refcnt++; return NULL; } return open_tag(tag, repo, id, 0); } const struct got_error * got_object_tag_open(struct got_tag_object **tag, struct got_repository *repo, struct got_object *obj) { return open_tag(tag, repo, got_object_get_id(obj), 1); } got-portable-0.111/lib/gotd_imsg.c0000644000175000017500000000601615001741021012454 /* * Copyright (c) 2022 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_object.h" #include "got_path.h" #include "got_lib_poll.h" #include "gotd.h" const struct got_error * gotd_imsg_recv_error(uint32_t *client_id, struct imsg *imsg) { struct gotd_imsg_error ierr; size_t datalen; datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen != sizeof(ierr)) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&ierr, imsg->data, sizeof(ierr)); if (client_id) *client_id = ierr.client_id; if (ierr.code == GOT_ERR_ERRNO) errno = ierr.errno_code; return got_error_msg(ierr.code, ierr.msg); } const struct got_error * gotd_imsg_flush(struct imsgbuf *ibuf) { const struct got_error *err = NULL; while (imsgbuf_queuelen(ibuf) > 0) { err = got_poll_fd(ibuf->fd, POLLOUT, INFTIM); if (err) break; if (imsgbuf_write(ibuf) == -1) { err = got_error_from_errno("imsgbuf_write"); break; } } return err; } int gotd_imsg_send_error(struct imsgbuf *ibuf, uint32_t peerid, uint32_t client_id, const struct got_error *err) { const struct got_error *flush_err; struct gotd_imsg_error ierr; int ret; ierr.code = err->code; if (err->code == GOT_ERR_ERRNO) ierr.errno_code = errno; else ierr.errno_code = 0; ierr.client_id = client_id; strlcpy(ierr.msg, err->msg, sizeof(ierr.msg)); ret = imsg_compose(ibuf, GOTD_IMSG_ERROR, peerid, getpid(), -1, &ierr, sizeof(ierr)); if (ret == -1) return -1; flush_err = gotd_imsg_flush(ibuf); if (flush_err) return -1; return 0; } void gotd_imsg_event_add(struct gotd_imsgev *iev) { iev->events = EV_READ; if (imsgbuf_queuelen(&iev->ibuf)) iev->events |= EV_WRITE; event_del(&iev->ev); event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev); event_add(&iev->ev, NULL); } int gotd_imsg_compose_event(struct gotd_imsgev *iev, uint16_t type, uint32_t peerid, int fd, void *data, uint16_t datalen) { int ret; ret = imsg_compose(&iev->ibuf, type, peerid, getpid(), fd, data, datalen); if (ret != -1) gotd_imsg_event_add(iev); return ret; } got-portable-0.111/lib/diff_output_plain.c0000644000175000017500000001547515001740614014233 /* Output all lines of a diff_result. */ /* * Copyright (c) 2020 Neels Hofmeyr * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include "diff_internal.h" static int output_plain_chunk(struct diff_output_info *outinfo, FILE *dest, const struct diff_input_info *info, const struct diff_result *result, struct diff_chunk_context *cc, off_t *outoff, bool headers_only) { off_t *offp; int left_start, left_len, right_start, right_len; int rc; bool change = false; left_len = cc->left.end - cc->left.start; if (left_len < 0) return EINVAL; else if (result->left->atoms.len == 0) left_start = 0; else if (left_len == 0 && cc->left.start > 0) left_start = cc->left.start; else if (cc->left.end > 0) left_start = cc->left.start + 1; else left_start = cc->left.start; right_len = cc->right.end - cc->right.start; if (right_len < 0) return EINVAL; else if (result->right->atoms.len == 0) right_start = 0; else if (right_len == 0 && cc->right.start > 0) right_start = cc->right.start; else if (cc->right.end > 0) right_start = cc->right.start + 1; else right_start = cc->right.start; if (left_len == 0) { /* addition */ if (right_len == 1) { rc = fprintf(dest, "%da%d\n", left_start, right_start); } else { rc = fprintf(dest, "%da%d,%d\n", left_start, right_start, cc->right.end); } } else if (right_len == 0) { /* deletion */ if (left_len == 1) { rc = fprintf(dest, "%dd%d\n", left_start, right_start); } else { rc = fprintf(dest, "%d,%dd%d\n", left_start, cc->left.end, right_start); } } else { /* change */ change = true; if (left_len == 1 && right_len == 1) { rc = fprintf(dest, "%dc%d\n", left_start, right_start); } else if (left_len == 1) { rc = fprintf(dest, "%dc%d,%d\n", left_start, right_start, cc->right.end); } else if (right_len == 1) { rc = fprintf(dest, "%d,%dc%d\n", left_start, cc->left.end, right_start); } else { rc = fprintf(dest, "%d,%dc%d,%d\n", left_start, cc->left.end, right_start, cc->right.end); } } if (rc < 0) return errno; if (outinfo) { ARRAYLIST_ADD(offp, outinfo->line_offsets); if (offp == NULL) return ENOMEM; *outoff += rc; *offp = *outoff; } /* * Now write out all the joined chunks. * * If the hunk denotes a change, it will come in the form of a deletion * chunk followed by a addition chunk. Print a marker to break up the * additions and deletions when this happens. */ int c_idx; for (c_idx = cc->chunk.start; !headers_only && c_idx < cc->chunk.end; c_idx++) { const struct diff_chunk *c = &result->chunks.head[c_idx]; if (c->left_count && !c->right_count) rc = diff_output_lines(outinfo, dest, c->solved ? "< " : "?", c->left_start, c->left_count); else if (c->right_count && !c->left_count) { if (change) { rc = fprintf(dest, "---\n"); if (rc < 0) return errno; if (outinfo) { ARRAYLIST_ADD(offp, outinfo->line_offsets); if (offp == NULL) return ENOMEM; *outoff += rc; *offp = *outoff; } } rc = diff_output_lines(outinfo, dest, c->solved ? "> " : "?", c->right_start, c->right_count); } if (rc) return rc; if (cc->chunk.end == result->chunks.len) { rc = diff_output_trailing_newline_msg(outinfo, dest, c); if (rc != DIFF_RC_OK) return rc; } } return DIFF_RC_OK; } int diff_output_plain(struct diff_output_info **output_info, FILE *dest, const struct diff_input_info *info, const struct diff_result *result, int hunk_headers_only) { struct diff_output_info *outinfo = NULL; struct diff_chunk_context cc = {}; int atomizer_flags = (result->left->atomizer_flags| result->right->atomizer_flags); int flags = (result->left->root->diff_flags | result->right->root->diff_flags); bool force_text = (flags & DIFF_FLAG_FORCE_TEXT_DATA); bool have_binary = (atomizer_flags & DIFF_ATOMIZER_FOUND_BINARY_DATA); int i, rc; off_t outoff = 0, *offp; if (!result) return EINVAL; if (result->rc != DIFF_RC_OK) return result->rc; if (output_info) { *output_info = diff_output_info_alloc(); if (*output_info == NULL) return ENOMEM; outinfo = *output_info; } if (have_binary && !force_text) { for (i = 0; i < result->chunks.len; i++) { struct diff_chunk *c = &result->chunks.head[i]; enum diff_chunk_type t = diff_chunk_type(c); if (t != CHUNK_MINUS && t != CHUNK_PLUS) continue; rc = fprintf(dest, "Binary files %s and %s differ\n", diff_output_get_label_left(info), diff_output_get_label_right(info)); if (rc < 0) return errno; if (outinfo) { ARRAYLIST_ADD(offp, outinfo->line_offsets); if (offp == NULL) return ENOMEM; outoff += rc; *offp = outoff; } break; } return DIFF_RC_OK; } for (i = 0; i < result->chunks.len; i++) { struct diff_chunk *chunk = &result->chunks.head[i]; enum diff_chunk_type t = diff_chunk_type(chunk); struct diff_chunk_context next; if (t != CHUNK_MINUS && t != CHUNK_PLUS) continue; if (diff_chunk_context_empty(&cc)) { /* Note down the start point, any number of subsequent * chunks may be joined up to this chunk by being * directly adjacent. */ diff_chunk_context_get(&cc, result, i, 0); continue; } /* There already is a previous chunk noted down for being * printed. Does it join up with this one? */ diff_chunk_context_get(&next, result, i, 0); if (diff_chunk_contexts_touch(&cc, &next)) { /* This next context touches or overlaps the previous * one, join. */ diff_chunk_contexts_merge(&cc, &next); /* When we merge the last chunk we can end up with one * hanging chunk and have to come back for it after the * loop */ continue; } rc = output_plain_chunk(outinfo, dest, info, result, &cc, &outoff, hunk_headers_only); if (rc != DIFF_RC_OK) return rc; cc = next; } if (!diff_chunk_context_empty(&cc)) return output_plain_chunk(outinfo, dest, info, result, &cc, &outoff, hunk_headers_only); return DIFF_RC_OK; } got-portable-0.111/lib/diff_internal.h0000644000175000017500000001157115001740614013322 /* Generic infrastructure to implement various diff algorithms. */ /* * Copyright (c) 2020 Neels Hofmeyr * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MAX #define MAX(A,B) ((A)>(B)?(A):(B)) #endif #ifndef MIN #define MIN(A,B) ((A)<(B)?(A):(B)) #endif static inline bool diff_range_empty(const struct diff_range *r) { return r->start == r->end; } static inline bool diff_ranges_touch(const struct diff_range *a, const struct diff_range *b) { return (a->end >= b->start) && (a->start <= b->end); } static inline void diff_ranges_merge(struct diff_range *a, const struct diff_range *b) { *a = (struct diff_range){ .start = MIN(a->start, b->start), .end = MAX(a->end, b->end), }; } static inline int diff_range_len(const struct diff_range *r) { if (!r) return 0; return r->end - r->start; } /* Indicate whether two given diff atoms match. */ int diff_atom_same(bool *same, const struct diff_atom *left, const struct diff_atom *right); /* A diff chunk represents a set of atoms on the left and/or a set of atoms on * the right. * * If solved == false: * The diff algorithm has divided the source file, and this is a chunk that the * inner_algo should run on next. * The lines on the left should be diffed against the lines on the right. * (If there are no left lines or no right lines, it implies solved == true, * because there is nothing to diff.) * * If solved == true: * If there are only left atoms, it is a chunk removing atoms from the left ("a * minus chunk"). * If there are only right atoms, it is a chunk adding atoms from the right ("a * plus chunk"). * If there are both left and right lines, it is a chunk of equal content on * both sides, and left_count == right_count: * * - foo } * - bar }-- diff_chunk{ left_start = &left.atoms.head[0], left_count = 3, * - baz } right_start = NULL, right_count = 0 } * moo } * goo }-- diff_chunk{ left_start = &left.atoms.head[3], left_count = 3, * zoo } right_start = &right.atoms.head[0], right_count = 3 } * +loo } * +roo }-- diff_chunk{ left_start = NULL, left_count = 0, * +too } right_start = &right.atoms.head[3], right_count = 3 } * */ struct diff_chunk { bool solved; struct diff_atom *left_start; unsigned int left_count; struct diff_atom *right_start; unsigned int right_count; }; #define DIFF_RESULT_ALLOC_BLOCKSIZE 128 struct diff_chunk_context; bool diff_chunk_context_empty(const struct diff_chunk_context *cc); bool diff_chunk_contexts_touch(const struct diff_chunk_context *cc, const struct diff_chunk_context *other); void diff_chunk_contexts_merge(struct diff_chunk_context *cc, const struct diff_chunk_context *other); struct diff_state { /* The final result passed to the original diff caller. */ struct diff_result *result; /* The root diff_data is in result->left,right, these are (possibly) * subsections of the root data. */ struct diff_data left; struct diff_data right; unsigned int recursion_depth_left; /* Remaining chunks from one diff algorithm pass, if any solved == false * chunks came up. */ diff_chunk_arraylist_t temp_result; /* State buffer used by Myers algorithm. */ int *kd_buf; size_t kd_buf_size; /* in units of sizeof(int), not bytes */ }; struct diff_chunk *diff_state_add_chunk(struct diff_state *state, bool solved, struct diff_atom *left_start, unsigned int left_count, struct diff_atom *right_start, unsigned int right_count); struct diff_output_info; int diff_output_lines(struct diff_output_info *output_info, FILE *dest, const char *prefix, struct diff_atom *start_atom, unsigned int count); int diff_output_trailing_newline_msg(struct diff_output_info *outinfo, FILE *dest, const struct diff_chunk *c); #define DIFF_FUNCTION_CONTEXT_SIZE 55 int diff_output_match_function_prototype(char *prototype, size_t prototype_size, int *last_prototype_idx, const struct diff_result *result, const struct diff_chunk_context *cc); struct diff_output_info *diff_output_info_alloc(void); void diff_data_init_subsection(struct diff_data *d, struct diff_data *parent, struct diff_atom *from_atom, unsigned int atoms_count); got-portable-0.111/lib/log.h0000644000175000017500000000310315001741021011260 /* * Copyright (c) 2022 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" void log_init(int, int); void log_procinit(const char *); void log_setverbose(int); int log_getverbose(void); void log_warn(const char *, ...) __attribute__((__format__ (printf, 1, 2))); void log_warnx(const char *, ...) __attribute__((__format__ (printf, 1, 2))); void log_info(const char *, ...) __attribute__((__format__ (printf, 1, 2))); void log_debug(const char *, ...) __attribute__((__format__ (printf, 1, 2))); void logit(int, const char *, ...) __attribute__((__format__ (printf, 2, 3))); void vlog(int, const char *, va_list) __attribute__((__format__ (printf, 2, 0))); __dead void fatal(const char *, ...) __attribute__((__format__ (printf, 1, 2))); __dead void fatalx(const char *, ...) __attribute__((__format__ (printf, 1, 2))); got-portable-0.111/lib/object_cache.c0000644000175000017500000002475515001741021013103 /* * Copyright (c) 2018 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "got_compat.h" #include "got_error.h" #include "got_object.h" #include "got_lib_delta.h" #include "got_lib_inflate.h" #include "got_lib_object.h" #include "got_lib_object_idset.h" #include "got_lib_object_cache.h" /* * XXX This should be reworked to track cache size and usage in bytes, * rather than tracking N elements capped to a maximum element size. */ #define GOT_OBJECT_CACHE_SIZE_OBJ 256 #define GOT_OBJECT_CACHE_SIZE_TREE 256 #define GOT_OBJECT_CACHE_SIZE_COMMIT 64 #define GOT_OBJECT_CACHE_SIZE_TAG 256 #define GOT_OBJECT_CACHE_SIZE_RAW 16 #define GOT_OBJECT_CACHE_MAX_ELEM_SIZE 1048576 /* 1 MB */ const struct got_error * got_object_cache_init(struct got_object_cache *cache, enum got_object_cache_type type) { struct rlimit rl; memset(cache, 0, sizeof(*cache)); cache->idset = got_object_idset_alloc(); if (cache->idset == NULL) return got_error_from_errno("got_object_idset_alloc"); cache->type = type; switch (type) { case GOT_OBJECT_CACHE_TYPE_OBJ: cache->size = GOT_OBJECT_CACHE_SIZE_OBJ; break; case GOT_OBJECT_CACHE_TYPE_TREE: cache->size = GOT_OBJECT_CACHE_SIZE_TREE; break; case GOT_OBJECT_CACHE_TYPE_COMMIT: cache->size = GOT_OBJECT_CACHE_SIZE_COMMIT; break; case GOT_OBJECT_CACHE_TYPE_TAG: cache->size = GOT_OBJECT_CACHE_SIZE_TAG; break; case GOT_OBJECT_CACHE_TYPE_RAW: if (getrlimit(RLIMIT_NOFILE, &rl) == -1) return got_error_from_errno("getrlimit"); cache->size = GOT_OBJECT_CACHE_SIZE_RAW; if (cache->size > rl.rlim_cur / 16) cache->size = rl.rlim_cur / 16; break; } return NULL; } static size_t get_size_obj(struct got_object *obj) { size_t size = sizeof(*obj); struct got_delta *delta; if ((obj->flags & GOT_OBJ_FLAG_DELTIFIED) == 0) return size; STAILQ_FOREACH(delta, &obj->deltas.entries, entry) { if (SIZE_MAX - sizeof(*delta) < size) return SIZE_MAX; size += sizeof(*delta); } return size; } static size_t get_size_tree(struct got_tree_object *tree) { size_t size = sizeof(*tree); size += sizeof(struct got_tree_entry) * tree->nentries; return size; } static size_t get_size_commit(struct got_commit_object *commit) { size_t size = sizeof(*commit); struct got_object_qid *qid; size += sizeof(*commit->tree_id); size += strlen(commit->author); size += strlen(commit->committer); size += strlen(commit->logmsg); STAILQ_FOREACH(qid, &commit->parent_ids, entry) size += sizeof(*qid) + sizeof(qid->id); return size; } static size_t get_size_tag(struct got_tag_object *tag) { size_t size = sizeof(*tag); size += strlen(tag->tag); size += strlen(tag->tagger); size += strlen(tag->tagmsg); return size; } static size_t get_size_raw(struct got_raw_object *raw) { return sizeof(*raw); } const struct got_error * got_object_cache_add(struct got_object_cache *cache, struct got_object_id *id, void *item) { const struct got_error *err = NULL; struct got_object_cache_entry *ce; int nelem; size_t size; switch (cache->type) { case GOT_OBJECT_CACHE_TYPE_OBJ: size = get_size_obj((struct got_object *)item); break; case GOT_OBJECT_CACHE_TYPE_TREE: size = get_size_tree((struct got_tree_object *)item); break; case GOT_OBJECT_CACHE_TYPE_COMMIT: size = get_size_commit((struct got_commit_object *)item); break; case GOT_OBJECT_CACHE_TYPE_TAG: size = get_size_tag((struct got_tag_object *)item); break; case GOT_OBJECT_CACHE_TYPE_RAW: size = get_size_raw((struct got_raw_object *)item); break; default: return got_error(GOT_ERR_OBJ_TYPE); } if (size > GOT_OBJECT_CACHE_MAX_ELEM_SIZE) { #ifdef GOT_OBJ_CACHE_DEBUG char *id_str; if (got_object_id_str(&id_str, id) != NULL) return got_error_from_errno("got_object_id_str"); fprintf(stderr, "%s: not caching ", getprogname()); switch (cache->type) { case GOT_OBJECT_CACHE_TYPE_OBJ: fprintf(stderr, "object"); break; case GOT_OBJECT_CACHE_TYPE_TREE: fprintf(stderr, "tree"); break; case GOT_OBJECT_CACHE_TYPE_COMMIT: fprintf(stderr, "commit"); break; case GOT_OBJECT_CACHE_TYPE_TAG: fprintf(stderr, "tag"); break; case GOT_OBJECT_CACHE_TYPE_RAW: fprintf(stderr, "raw"); break; } fprintf(stderr, " %s (%zd bytes; %zd MB)\n", id_str, size, size/1024/1024); free(id_str); #endif cache->cache_toolarge++; return got_error(GOT_ERR_OBJ_TOO_LARGE); } nelem = got_object_idset_num_elements(cache->idset); if (nelem >= cache->size) { err = got_object_idset_remove((void **)&ce, cache->idset, NULL); if (err) return err; switch (cache->type) { case GOT_OBJECT_CACHE_TYPE_OBJ: got_object_close(ce->data.obj); break; case GOT_OBJECT_CACHE_TYPE_TREE: got_object_tree_close(ce->data.tree); break; case GOT_OBJECT_CACHE_TYPE_COMMIT: got_object_commit_close(ce->data.commit); break; case GOT_OBJECT_CACHE_TYPE_TAG: got_object_tag_close(ce->data.tag); break; case GOT_OBJECT_CACHE_TYPE_RAW: got_object_raw_close(ce->data.raw); break; } memset(ce, 0, sizeof(*ce)); cache->cache_evict++; } else { ce = malloc(sizeof(*ce)); if (ce == NULL) return got_error_from_errno("malloc"); } memcpy(&ce->id, id, sizeof(ce->id)); switch (cache->type) { case GOT_OBJECT_CACHE_TYPE_OBJ: ce->data.obj = (struct got_object *)item; break; case GOT_OBJECT_CACHE_TYPE_TREE: ce->data.tree = (struct got_tree_object *)item; break; case GOT_OBJECT_CACHE_TYPE_COMMIT: ce->data.commit = (struct got_commit_object *)item; break; case GOT_OBJECT_CACHE_TYPE_TAG: ce->data.tag = (struct got_tag_object *)item; break; case GOT_OBJECT_CACHE_TYPE_RAW: ce->data.raw = (struct got_raw_object *)item; break; } err = got_object_idset_add(cache->idset, id, ce); if (err) free(ce); else if (size > cache->max_cached_size) cache->max_cached_size = size; return err; } void * got_object_cache_get(struct got_object_cache *cache, struct got_object_id *id) { struct got_object_cache_entry *ce; cache->cache_searches++; ce = got_object_idset_get(cache->idset, id); if (ce) { cache->cache_hit++; switch (cache->type) { case GOT_OBJECT_CACHE_TYPE_OBJ: return ce->data.obj; case GOT_OBJECT_CACHE_TYPE_TREE: return ce->data.tree; case GOT_OBJECT_CACHE_TYPE_COMMIT: return ce->data.commit; case GOT_OBJECT_CACHE_TYPE_TAG: return ce->data.tag; case GOT_OBJECT_CACHE_TYPE_RAW: return ce->data.raw; } } cache->cache_miss++; return NULL; } #ifdef GOT_OBJ_CACHE_DEBUG static void print_cache_stats(struct got_object_cache *cache, const char *name) { fprintf(stderr, "%s: %s cache: %d elements, %d searches, %d hits, " "%d missed, %d evicted, %d too large, max cached %zd bytes\n", getprogname(), name, cache->idset ? got_object_idset_num_elements(cache->idset) : -1, cache->cache_searches, cache->cache_hit, cache->cache_miss, cache->cache_evict, cache->cache_toolarge, cache->max_cached_size); } static const struct got_error * check_refcount(struct got_object_id *id, void *data, void *arg) { struct got_object_cache *cache = arg; struct got_object_cache_entry *ce = data; struct got_object *obj; struct got_tree_object *tree; struct got_commit_object *commit; struct got_tag_object *tag; struct got_raw_object *raw; char *id_str; if (got_object_id_str(&id_str, id) != NULL) return NULL; switch (cache->type) { case GOT_OBJECT_CACHE_TYPE_OBJ: obj = ce->data.obj; if (obj->refcnt == 1) break; fprintf(stderr, "object %s has %d unclaimed references\n", id_str, obj->refcnt - 1); break; case GOT_OBJECT_CACHE_TYPE_TREE: tree = ce->data.tree; if (tree->refcnt == 1) break; fprintf(stderr, "tree %s has %d unclaimed references\n", id_str, tree->refcnt - 1); break; case GOT_OBJECT_CACHE_TYPE_COMMIT: commit = ce->data.commit; if (commit->refcnt == 1) break; fprintf(stderr, "commit %s has %d unclaimed references\n", id_str, commit->refcnt - 1); break; case GOT_OBJECT_CACHE_TYPE_TAG: tag = ce->data.tag; if (tag->refcnt == 1) break; fprintf(stderr, "tag %s has %d unclaimed references\n", id_str, tag->refcnt - 1); break; case GOT_OBJECT_CACHE_TYPE_RAW: raw = ce->data.raw; if (raw->refcnt == 1) break; fprintf(stderr, "raw %s has %d unclaimed references\n", id_str, raw->refcnt - 1); break; } free(id_str); return NULL; } #endif static const struct got_error * free_entry(struct got_object_id *id, void *data, void *arg) { struct got_object_cache *cache = arg; struct got_object_cache_entry *ce = data; switch (cache->type) { case GOT_OBJECT_CACHE_TYPE_OBJ: got_object_close(ce->data.obj); break; case GOT_OBJECT_CACHE_TYPE_TREE: got_object_tree_close(ce->data.tree); break; case GOT_OBJECT_CACHE_TYPE_COMMIT: got_object_commit_close(ce->data.commit); break; case GOT_OBJECT_CACHE_TYPE_TAG: got_object_tag_close(ce->data.tag); break; case GOT_OBJECT_CACHE_TYPE_RAW: got_object_raw_close(ce->data.raw); break; } free(ce); return NULL; } void got_object_cache_close(struct got_object_cache *cache) { #ifdef GOT_OBJ_CACHE_DEBUG switch (cache->type) { case GOT_OBJECT_CACHE_TYPE_OBJ: print_cache_stats(cache, "object"); break; case GOT_OBJECT_CACHE_TYPE_TREE: print_cache_stats(cache, "tree"); break; case GOT_OBJECT_CACHE_TYPE_COMMIT: print_cache_stats(cache, "commit"); break; case GOT_OBJECT_CACHE_TYPE_TAG: print_cache_stats(cache, "tag"); break; case GOT_OBJECT_CACHE_TYPE_RAW: print_cache_stats(cache, "raw"); break; } if (cache->idset) got_object_idset_for_each(cache->idset, check_refcount, cache); #endif if (cache->idset) { got_object_idset_for_each(cache->idset, free_entry, cache); got_object_idset_free(cache->idset); cache->idset = NULL; } cache->size = 0; } got-portable-0.111/lib/got_lib_fileindex.h0000644000175000017500000001765115001740614014171 /* * Copyright (c) 2018, 2019 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * State information for a tracked file in a work tree. * When written to disk, multi-byte fields are written in big-endian. * Some fields are based on results from stat(2). These are only used in * order to detect modifications made to on-disk files, they are never * applied back to the filesystem. */ struct got_fileindex_entry { RB_ENTRY(got_fileindex_entry) entry; uint64_t ctime_sec; uint64_t ctime_nsec; uint64_t mtime_sec; uint64_t mtime_nsec; uint32_t uid; uint32_t gid; /* * On-disk size is truncated to the lower 32 bits. * The value is only used to check for modifications anyway. */ uint32_t size; uint16_t mode; #define GOT_FILEIDX_MODE_FILE_TYPE 0x000f #define GOT_FILEIDX_MODE_FILE_TYPE_ONDISK 0x0003 #define GOT_FILEIDX_MODE_FILE_TYPE_STAGED 0x000c #define GOT_FILEIDX_MODE_FILE_TYPE_STAGED_SHIFT 2 #define GOT_FILEIDX_MODE_REGULAR_FILE 1 #define GOT_FILEIDX_MODE_SYMLINK 2 #define GOT_FILEIDX_MODE_BAD_SYMLINK 3 #define GOT_FILEIDX_MODE_PERMS 0xfff0 #define GOT_FILEIDX_MODE_PERMS_SHIFT 4 /* * id of corresponding blob in repository. only the actual * hash is written to the disk. */ struct got_object_id blob; /* * id of corresponding base commit in repository. only the * actual hash is written to the disk. */ struct got_object_id commit; uint32_t flags; /* * UNIX-style path, relative to work tree root. * Variable length, and NUL-padded to a multiple of 8 on disk. */ char *path; /* * (since GOT_FILE_INDEX_VERSION 2) * Hash of staged blob in repository if stage equals either * GOT_FILEIDX_STAGE_MODIFY or GOT_FILEIDX_STAGE_ADD. * Otherwise, this field is not written to disk. * Only the actual hash is written to the disk. */ struct got_object_id staged_blob; }; /* Modifications explicitly staged for commit. */ #define GOT_FILEIDX_STAGE_NONE 0 #define GOT_FILEIDX_STAGE_MODIFY 1 #define GOT_FILEIDX_STAGE_ADD 2 #define GOT_FILEIDX_STAGE_DELETE 3 struct got_fileindex; RB_HEAD(got_fileindex_tree, got_fileindex_entry); size_t got_fileindex_entry_path_len(const struct got_fileindex_entry *); static inline int got_fileindex_cmp(const struct got_fileindex_entry *e1, const struct got_fileindex_entry *e2) { return got_path_cmp(e1->path, e2->path, got_fileindex_entry_path_len(e1), got_fileindex_entry_path_len(e2)); } RB_PROTOTYPE(got_fileindex_tree, got_fileindex_entry, entry, got_fileindex_cmp); /* On-disk file index header structure. */ struct got_fileindex_hdr { uint32_t signature; /* big-endian */ #define GOT_FILE_INDEX_SIGNATURE 0x676f7449 /* 'g', 'o', 't', 'I' */ uint32_t version; /* big-endian */ #define GOT_FILE_INDEX_VERSION 3 uint32_t nentries; /* big-endian */ uint32_t algo; /* big-endian -- since v3 */ /* list of concatenated fileindex entries */ /* * checksum of above on-disk data, the actual length of the * hash depends on the algorithm. */ uint8_t hash[GOT_HASH_DIGEST_MAXLEN]; }; mode_t got_fileindex_entry_perms_get(struct got_fileindex_entry *); uint16_t got_fileindex_perms_from_st(struct stat *); mode_t got_fileindex_perms_to_st(struct got_fileindex_entry *); const struct got_error *got_fileindex_entry_update(struct got_fileindex_entry *, int, const char *, struct got_object_id *, struct got_object_id *, int); void got_fileindex_entry_mark_skipped(struct got_fileindex_entry *); const struct got_error *got_fileindex_entry_alloc(struct got_fileindex_entry **, const char *); void got_fileindex_entry_free(struct got_fileindex_entry *); struct got_fileindex *got_fileindex_alloc(enum got_hash_algorithm); void got_fileindex_free(struct got_fileindex *); const struct got_error *got_fileindex_write(struct got_fileindex *, FILE *); const struct got_error *got_fileindex_entry_add(struct got_fileindex *, struct got_fileindex_entry *); void got_fileindex_entry_remove(struct got_fileindex *, struct got_fileindex_entry *); struct got_fileindex_entry *got_fileindex_entry_get(struct got_fileindex *, const char *, size_t); const struct got_error *got_fileindex_read(struct got_fileindex *, FILE *, enum got_hash_algorithm); uint32_t got_fileindex_get_version(struct got_fileindex *); typedef const struct got_error *(*got_fileindex_cb)(void *, struct got_fileindex_entry *); const struct got_error *got_fileindex_for_each_entry_safe( struct got_fileindex *, got_fileindex_cb cb, void *); typedef const struct got_error *(*got_fileindex_diff_tree_old_new_cb)(void *, struct got_fileindex_entry *, struct got_tree_entry *, const char *); typedef const struct got_error *(*got_fileindex_diff_tree_old_cb)(void *, struct got_fileindex_entry *, const char *); typedef const struct got_error *(*got_fileindex_diff_tree_new_cb)(void *, struct got_tree_entry *, const char *); struct got_fileindex_diff_tree_cb { got_fileindex_diff_tree_old_new_cb diff_old_new; got_fileindex_diff_tree_old_cb diff_old; got_fileindex_diff_tree_new_cb diff_new; }; const struct got_error *got_fileindex_diff_tree(struct got_fileindex *, struct got_tree_object *, const char *, const char *, struct got_repository *, struct got_fileindex_diff_tree_cb *, void *); typedef const struct got_error *(*got_fileindex_diff_dir_old_new_cb)(void *, struct got_fileindex_entry *, struct dirent *, const char *, int); typedef const struct got_error *(*got_fileindex_diff_dir_old_cb)(void *, struct got_fileindex_entry *, const char *); typedef const struct got_error *(*got_fileindex_diff_dir_new_cb)(int *, void *, struct dirent *, const char *, int); typedef const struct got_error *(*got_fileindex_diff_dir_traverse)(void *, const char *, int); struct got_fileindex_diff_dir_cb { got_fileindex_diff_dir_old_new_cb diff_old_new; got_fileindex_diff_dir_old_cb diff_old; got_fileindex_diff_dir_new_cb diff_new; got_fileindex_diff_dir_traverse diff_traverse; }; const struct got_error *got_fileindex_diff_dir(struct got_fileindex *, int, const char *, const char *, struct got_repository *, struct got_fileindex_diff_dir_cb *, void *); int got_fileindex_entry_has_blob(struct got_fileindex_entry *); int got_fileindex_entry_has_commit(struct got_fileindex_entry *); int got_fileindex_entry_has_file_on_disk(struct got_fileindex_entry *); int got_fileindex_entry_was_skipped(struct got_fileindex_entry *); uint32_t got_fileindex_entry_stage_get(const struct got_fileindex_entry *); void got_fileindex_entry_stage_set(struct got_fileindex_entry *ie, uint32_t); int got_fileindex_entry_filetype_get(struct got_fileindex_entry *); void got_fileindex_entry_filetype_set(struct got_fileindex_entry *, int); void got_fileindex_entry_staged_filetype_set(struct got_fileindex_entry *, int); int got_fileindex_entry_staged_filetype_get(struct got_fileindex_entry *); void got_fileindex_entry_mark_deleted_from_disk(struct got_fileindex_entry *); /* * Retrieve staged, blob or commit id from a fileindex entry, and return * the given object id. */ struct got_object_id *got_fileindex_entry_get_staged_blob_id( struct got_object_id *, struct got_fileindex_entry *); struct got_object_id *got_fileindex_entry_get_blob_id(struct got_object_id *, struct got_fileindex_entry *); struct got_object_id *got_fileindex_entry_get_commit_id(struct got_object_id *, struct got_fileindex_entry *); got-portable-0.111/lib/got_lib_lockfile.h0000644000175000017500000000227115001740614014002 /* * Copyright (c) 2019 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * Git-compatible lock file implementation. Lock files are used to * ensure exclusive access when files in a Git repository are modified. */ #define GOT_LOCKFILE_SUFFIX ".lock" struct got_lockfile { char *path; char *locked_path; int fd; }; const struct got_error *got_lockfile_lock(struct got_lockfile **, const char *, int); const struct got_error *got_lockfile_unlock(struct got_lockfile *, int); got-portable-0.111/lib/diff_patience.c0000644000175000017500000004546715001740614013304 /* Implementation of the Patience Diff algorithm invented by Bram Cohen: * Divide a diff problem into smaller chunks by an LCS (Longest Common Sequence) * of common-unique lines. */ /* * Copyright (c) 2020 Neels Hofmeyr * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include "diff_internal.h" #include "diff_debug.h" /* Algorithm to find unique lines: * 0: stupidly iterate atoms * 1: qsort * 2: mergesort */ #define UNIQUE_STRATEGY 1 /* Per-atom state for the Patience Diff algorithm */ struct atom_patience { #if UNIQUE_STRATEGY == 0 bool unique_here; #endif bool unique_in_both; struct diff_atom *pos_in_other; struct diff_atom *prev_stack; struct diff_range identical_lines; }; /* A diff_atom has a backpointer to the root diff_data. That points to the * current diff_data, a possibly smaller section of the root. That current * diff_data->algo_data is a pointer to an array of struct atom_patience. The * atom's index in current diff_data gives the index in the atom_patience array. */ #define PATIENCE(ATOM) \ (((struct atom_patience*)((ATOM)->root->current->algo_data))\ [diff_atom_idx((ATOM)->root->current, ATOM)]) #if UNIQUE_STRATEGY == 0 /* Stupid iteration and comparison of all atoms */ static int diff_atoms_mark_unique(struct diff_data *d, unsigned int *unique_count) { struct diff_atom *i; unsigned int count = 0; diff_data_foreach_atom(i, d) { PATIENCE(i).unique_here = true; PATIENCE(i).unique_in_both = true; count++; } diff_data_foreach_atom(i, d) { struct diff_atom *j; if (!PATIENCE(i).unique_here) continue; diff_data_foreach_atom_from(i + 1, j, d) { bool same; int r = diff_atom_same(&same, i, j); if (r) return r; if (!same) continue; if (PATIENCE(i).unique_here) { PATIENCE(i).unique_here = false; PATIENCE(i).unique_in_both = false; count--; } PATIENCE(j).unique_here = false; PATIENCE(j).unique_in_both = false; count--; } } if (unique_count) *unique_count = count; return 0; } /* Mark those lines as PATIENCE(atom).unique_in_both = true that appear exactly * once in each side. */ static int diff_atoms_mark_unique_in_both(struct diff_data *left, struct diff_data *right, unsigned int *unique_in_both_count) { /* Derive the final unique_in_both count without needing an explicit * iteration. So this is just some optimization to save one iteration * in the end. */ unsigned int unique_in_both; int r; r = diff_atoms_mark_unique(left, &unique_in_both); if (r) return r; r = diff_atoms_mark_unique(right, NULL); if (r) return r; debug("unique_in_both %u\n", unique_in_both); struct diff_atom *i; diff_data_foreach_atom(i, left) { if (!PATIENCE(i).unique_here) continue; struct diff_atom *j; int found_in_b = 0; diff_data_foreach_atom(j, right) { bool same; int r = diff_atom_same(&same, i, j); if (r) return r; if (!same) continue; if (!PATIENCE(j).unique_here) { found_in_b = 2; /* or more */ break; } else { found_in_b = 1; PATIENCE(j).pos_in_other = i; PATIENCE(i).pos_in_other = j; } } if (found_in_b == 0 || found_in_b > 1) { PATIENCE(i).unique_in_both = false; unique_in_both--; debug("unique_in_both %u (%d) ", unique_in_both, found_in_b); debug_dump_atom(left, NULL, i); } } /* Still need to unmark right[*]->patience.unique_in_both for atoms that * don't exist in left */ diff_data_foreach_atom(i, right) { if (!PATIENCE(i).unique_here || !PATIENCE(i).unique_in_both) continue; struct diff_atom *j; bool found_in_a = false; diff_data_foreach_atom(j, left) { bool same; int r; if (!PATIENCE(j).unique_in_both) continue; r = diff_atom_same(&same, i, j); if (r) return r; if (!same) continue; found_in_a = true; break; } if (!found_in_a) PATIENCE(i).unique_in_both = false; } if (unique_in_both_count) *unique_in_both_count = unique_in_both; return 0; } #else /* UNIQUE_STRATEGY != 0 */ /* Use an optimized sorting algorithm (qsort, mergesort) to find unique lines */ static int diff_atoms_compar(const void *_a, const void *_b) { const struct diff_atom *a = *(struct diff_atom**)_a; const struct diff_atom *b = *(struct diff_atom**)_b; int cmp; int rc = 0; /* If there's been an error (e.g. I/O error) in a previous compar, we * have no way to abort the sort but just report the rc and stop * comparing. Make sure to catch errors on either side. If atoms are * from more than one diff_data, make sure the error, if any, spreads * to all of them, so we can cut short all future comparisons. */ if (a->root->err) rc = a->root->err; if (b->root->err) rc = b->root->err; if (rc) { a->root->err = rc; b->root->err = rc; /* just return 'equal' to not swap more positions */ return 0; } /* Sort by the simplistic hash */ if (a->hash < b->hash) return -1; if (a->hash > b->hash) return 1; /* If hashes are the same, the lines may still differ. Do a full cmp. */ rc = diff_atom_cmp(&cmp, a, b); if (rc) { /* Mark the I/O error so that the caller can find out about it. * For the case atoms are from more than one diff_data, mark in * both. */ a->root->err = rc; if (a->root != b->root) b->root->err = rc; return 0; } return cmp; } /* Sort an array of struct diff_atom* in-place. */ static int diff_atoms_sort(struct diff_atom *atoms[], size_t atoms_count) { #if UNIQUE_STRATEGY == 1 qsort(atoms, atoms_count, sizeof(struct diff_atom*), diff_atoms_compar); #else mergesort(atoms, atoms_count, sizeof(struct diff_atom*), diff_atoms_compar); #endif return atoms[0]->root->err; } static int diff_atoms_mark_unique_in_both(struct diff_data *left, struct diff_data *right, unsigned int *unique_in_both_count_p) { struct diff_atom *a; struct diff_atom *b; struct diff_atom **all_atoms; unsigned int len = 0; unsigned int i; unsigned int unique_in_both_count = 0; int rc; all_atoms = calloc(left->atoms.len + right->atoms.len, sizeof(struct diff_atom *)); if (all_atoms == NULL) return ENOMEM; left->err = 0; right->err = 0; left->root->err = 0; right->root->err = 0; diff_data_foreach_atom(a, left) { all_atoms[len++] = a; } diff_data_foreach_atom(b, right) { all_atoms[len++] = b; } rc = diff_atoms_sort(all_atoms, len); if (rc) goto free_and_exit; /* Now we have a sorted array of atom pointers. All similar lines are * adjacent. Walk through the array and mark those that are unique on * each side, but exist once in both sources. */ for (i = 0; i < len; i++) { bool same; unsigned int next_differing_i; unsigned int last_identical_i; unsigned int j; unsigned int count_first_side = 1; unsigned int count_other_side = 0; a = all_atoms[i]; debug("a: "); debug_dump_atom(a->root, NULL, a); /* Do as few diff_atom_cmp() as possible: first walk forward * only using the cheap hash as indicator for differing atoms; * then walk backwards until hitting an identical atom. */ for (next_differing_i = i + 1; next_differing_i < len; next_differing_i++) { b = all_atoms[next_differing_i]; if (a->hash != b->hash) break; } for (last_identical_i = next_differing_i - 1; last_identical_i > i; last_identical_i--) { b = all_atoms[last_identical_i]; rc = diff_atom_same(&same, a, b); if (rc) goto free_and_exit; if (same) break; } next_differing_i = last_identical_i + 1; for (j = i+1; j < next_differing_i; j++) { b = all_atoms[j]; /* A following atom is the same. See on which side the * repetition counts. */ if (a->root == b->root) count_first_side ++; else count_other_side ++; debug("b: "); debug_dump_atom(b->root, NULL, b); debug(" count_first_side=%d count_other_side=%d\n", count_first_side, count_other_side); } /* Counted a section of similar atoms, put the results back to * the atoms. */ if ((count_first_side == 1) && (count_other_side == 1)) { b = all_atoms[i+1]; PATIENCE(a).unique_in_both = true; PATIENCE(a).pos_in_other = b; PATIENCE(b).unique_in_both = true; PATIENCE(b).pos_in_other = a; unique_in_both_count++; } /* j now points at the first atom after 'a' that is not * identical to 'a'. j is always > i. */ i = j - 1; } *unique_in_both_count_p = unique_in_both_count; rc = 0; free_and_exit: free(all_atoms); return rc; } #endif /* UNIQUE_STRATEGY != 0 */ /* binary search to find the stack to put this atom "card" on. */ static int find_target_stack(struct diff_atom *atom, struct diff_atom **patience_stacks, unsigned int patience_stacks_count) { unsigned int lo = 0; unsigned int hi = patience_stacks_count; while (lo < hi) { unsigned int mid = (lo + hi) >> 1; if (PATIENCE(patience_stacks[mid]).pos_in_other < PATIENCE(atom).pos_in_other) lo = mid + 1; else hi = mid; } return lo; } /* Among the lines that appear exactly once in each side, find the longest * streak that appear in both files in the same order (with other stuff allowed * to interleave). Use patience sort for that, as in the Patience Diff * algorithm. * See https://bramcohen.livejournal.com/73318.html and, for a much more * detailed explanation, * https://blog.jcoglan.com/2017/09/19/the-patience-diff-algorithm/ */ int diff_algo_patience(const struct diff_algo_config *algo_config, struct diff_state *state) { int rc; struct diff_data *left = &state->left; struct diff_data *right = &state->right; struct atom_patience *atom_patience_left = calloc(left->atoms.len, sizeof(struct atom_patience)); struct atom_patience *atom_patience_right = calloc(right->atoms.len, sizeof(struct atom_patience)); unsigned int unique_in_both_count; struct diff_atom **lcs = NULL; debug("\n** %s\n", __func__); left->root->current = left; right->root->current = right; left->algo_data = atom_patience_left; right->algo_data = atom_patience_right; /* Find those lines that appear exactly once in 'left' and exactly once * in 'right'. */ rc = diff_atoms_mark_unique_in_both(left, right, &unique_in_both_count); if (rc) goto free_and_exit; debug("unique_in_both_count %u\n", unique_in_both_count); debug("left:\n"); debug_dump(left); debug("right:\n"); debug_dump(right); if (!unique_in_both_count) { /* Cannot apply Patience, tell the caller to use fallback_algo * instead. */ rc = DIFF_RC_USE_DIFF_ALGO_FALLBACK; goto free_and_exit; } rc = ENOMEM; /* An array of Longest Common Sequence is the result of the below * subscope: */ unsigned int lcs_count = 0; struct diff_atom *lcs_tail = NULL; { /* This subscope marks the lifetime of the atom_pointers * allocation */ /* One chunk of storage for atom pointers */ struct diff_atom **atom_pointers; atom_pointers = recallocarray(NULL, 0, unique_in_both_count * 2, sizeof(struct diff_atom*)); if (atom_pointers == NULL) return ENOMEM; /* Half for the list of atoms that still need to be put on * stacks */ struct diff_atom **uniques = atom_pointers; /* Half for the patience sort state's "card stacks" -- we * remember only each stack's topmost "card" */ struct diff_atom **patience_stacks; patience_stacks = atom_pointers + unique_in_both_count; unsigned int patience_stacks_count = 0; /* Take all common, unique items from 'left' ... */ struct diff_atom *atom; struct diff_atom **uniques_end = uniques; diff_data_foreach_atom(atom, left) { if (!PATIENCE(atom).unique_in_both) continue; *uniques_end = atom; uniques_end++; } /* ...and sort them to the order found in 'right'. * The idea is to find the leftmost stack that has a higher line * number and add it to the stack's top. * If there is no such stack, open a new one on the right. The * line number is derived from the atom*, which are array items * and hence reflect the relative position in the source file. * So we got the common-uniques from 'left' and sort them * according to PATIENCE(atom).pos_in_other. */ unsigned int i; for (i = 0; i < unique_in_both_count; i++) { atom = uniques[i]; unsigned int target_stack; target_stack = find_target_stack(atom, patience_stacks, patience_stacks_count); assert(target_stack <= patience_stacks_count); patience_stacks[target_stack] = atom; if (target_stack == patience_stacks_count) patience_stacks_count++; /* Record a back reference to the next stack on the * left, which will form the final longest sequence * later. */ PATIENCE(atom).prev_stack = target_stack ? patience_stacks[target_stack - 1] : NULL; { int xx; for (xx = 0; xx < patience_stacks_count; xx++) { debug(" %s%d", (xx == target_stack) ? ">" : "", diff_atom_idx(right, PATIENCE(patience_stacks[xx]).pos_in_other)); } debug("\n"); } } /* backtrace through prev_stack references to form the final * longest common sequence */ lcs_tail = patience_stacks[patience_stacks_count - 1]; lcs_count = patience_stacks_count; /* uniques and patience_stacks are no longer needed. * Backpointers are in PATIENCE(atom).prev_stack */ free(atom_pointers); } lcs = recallocarray(NULL, 0, lcs_count, sizeof(struct diff_atom*)); struct diff_atom **lcs_backtrace_pos = &lcs[lcs_count - 1]; struct diff_atom *atom; for (atom = lcs_tail; atom; atom = PATIENCE(atom).prev_stack, lcs_backtrace_pos--) { assert(lcs_backtrace_pos >= lcs); *lcs_backtrace_pos = atom; } unsigned int i; if (DEBUG) { debug("\npatience LCS:\n"); for (i = 0; i < lcs_count; i++) { debug("\n L "); debug_dump_atom(left, right, lcs[i]); debug(" R "); debug_dump_atom(right, left, PATIENCE(lcs[i]).pos_in_other); } } /* TODO: For each common-unique line found (now listed in lcs), swallow * lines upwards and downwards that are identical on each side. Requires * a way to represent atoms being glued to adjacent atoms. */ debug("\ntraverse LCS, possibly recursing:\n"); /* Now we have pinned positions in both files at which it makes sense to * divide the diff problem into smaller chunks. Go into the next round: * look at each section in turn, trying to again find common-unique * lines in those smaller sections. As soon as no more are found, the * remaining smaller sections are solved by Myers. */ /* left_pos and right_pos are indexes in left/right->atoms.head until * which the atoms are already handled (added to result chunks). */ unsigned int left_pos = 0; unsigned int right_pos = 0; for (i = 0; i <= lcs_count; i++) { struct diff_atom *atom; struct diff_atom *atom_r; /* left_idx and right_idx are indexes of the start of this * section of identical lines on both sides. * left_pos marks the index of the first still unhandled line, * left_idx is the start of an identical section some way * further down, and this loop adds an unsolved chunk of * [left_pos..left_idx[ and a solved chunk of * [left_idx..identical_lines.end[. */ unsigned int left_idx; unsigned int right_idx; debug("iteration %u of %u left_pos %u right_pos %u\n", i, lcs_count, left_pos, right_pos); if (i < lcs_count) { atom = lcs[i]; atom_r = PATIENCE(atom).pos_in_other; debug("lcs[%u] = left[%u] = right[%u]\n", i, diff_atom_idx(left, atom), diff_atom_idx(right, atom_r)); left_idx = diff_atom_idx(left, atom); right_idx = diff_atom_idx(right, atom_r); } else { /* There are no more identical lines until the end of * left and right. */ atom = NULL; atom_r = NULL; left_idx = left->atoms.len; right_idx = right->atoms.len; } /* 'atom' (if not NULL) now marks an atom that matches on both * sides according to patience-diff (a common-unique identical * atom in both files). * Handle the section before and the atom itself; the section * after will be handled by the next loop iteration -- note that * i loops to last element + 1 ("i <= lcs_count"), so that there * will be another final iteration to pick up the last remaining * items after the last LCS atom. */ debug("iteration %u left_pos %u left_idx %u" " right_pos %u right_idx %u\n", i, left_pos, left_idx, right_pos, right_idx); /* Section before the matching atom */ struct diff_atom *left_atom = &left->atoms.head[left_pos]; unsigned int left_section_len = left_idx - left_pos; struct diff_atom *right_atom = &(right->atoms.head[right_pos]); unsigned int right_section_len = right_idx - right_pos; if (left_section_len && right_section_len) { /* Record an unsolved chunk, the caller will apply * inner_algo() on this chunk. */ if (!diff_state_add_chunk(state, false, left_atom, left_section_len, right_atom, right_section_len)) goto free_and_exit; } else if (left_section_len && !right_section_len) { /* Only left atoms and none on the right, they form a * "minus" chunk, then. */ if (!diff_state_add_chunk(state, true, left_atom, left_section_len, right_atom, 0)) goto free_and_exit; } else if (!left_section_len && right_section_len) { /* No left atoms, only atoms on the right, they form a * "plus" chunk, then. */ if (!diff_state_add_chunk(state, true, left_atom, 0, right_atom, right_section_len)) goto free_and_exit; } /* else: left_section_len == 0 and right_section_len == 0, i.e. * nothing here. */ /* The atom found to match on both sides forms a chunk of equals * on each side. In the very last iteration of this loop, there * is no matching atom, we were just cleaning out the remaining * lines. */ if (atom) { void *ok; ok = diff_state_add_chunk(state, true, atom, 1, PATIENCE(atom).pos_in_other, 1); if (!ok) goto free_and_exit; } left_pos = left_idx + 1; right_pos = right_idx + 1; debug("end of iteration %u left_pos %u left_idx %u" " right_pos %u right_idx %u\n", i, left_pos, left_idx, right_pos, right_idx); } debug("** END %s\n", __func__); rc = DIFF_RC_OK; free_and_exit: left->root->current = NULL; right->root->current = NULL; free(atom_patience_left); free(atom_patience_right); if (lcs) free(lcs); return rc; } got-portable-0.111/lib/got_lib_object_qid.h0000644000175000017500000000157415001740614014322 /* * Copyright (c) 2018, 2019, 2020, 2023 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ const struct got_error *got_object_qid_alloc_partial(struct got_object_qid **); got-portable-0.111/lib/got_lib_object_cache.h0000644000175000017500000000337015001740614014604 /* * Copyright (c) 2018 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ enum got_object_cache_type { GOT_OBJECT_CACHE_TYPE_OBJ, GOT_OBJECT_CACHE_TYPE_TREE, GOT_OBJECT_CACHE_TYPE_COMMIT, GOT_OBJECT_CACHE_TYPE_TAG, GOT_OBJECT_CACHE_TYPE_RAW, }; struct got_object_cache_entry { struct got_object_id id; union { struct got_object *obj; struct got_tree_object *tree; struct got_commit_object *commit; struct got_tag_object *tag; struct got_raw_object *raw; } data; }; struct got_object_cache { enum got_object_cache_type type; struct got_object_idset *idset; size_t size; int cache_searches; int cache_hit; int cache_miss; int cache_evict; int cache_toolarge; size_t max_cached_size; }; const struct got_error *got_object_cache_init(struct got_object_cache *, enum got_object_cache_type); const struct got_error *got_object_cache_add(struct got_object_cache *, struct got_object_id *, void *); void *got_object_cache_get(struct got_object_cache *, struct got_object_id *); void got_object_cache_close(struct got_object_cache *); got-portable-0.111/lib/got_lib_repository.h0000644000175000017500000001524015001740614014431 /* * Copyright (c) 2018 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #define GOT_GIT_DIR ".git" /* Mandatory files and directories inside the git directory. */ #define GOT_OBJECTS_DIR "objects" #define GOT_REFS_DIR "refs" #define GOT_HEAD_FILE "HEAD" #define GOT_GITCONFIG "config" /* Other files and directories inside the git directory. */ #define GOT_FETCH_HEAD_FILE "FETCH_HEAD" #define GOT_ORIG_HEAD_FILE "ORIG_HEAD" #define GOT_OBJECTS_PACK_DIR "objects/pack" #define GOT_PACKED_REFS_FILE "packed-refs" #define GOT_PACK_CACHE_SIZE 32 /* * An open repository needs this many temporary files. * This limit sets an upper bound on how many raw objects or blobs can * be kept open in parallel. */ #define GOT_REPO_NUM_TEMPFILES 32 struct got_packidx_bloom_filter { RB_ENTRY(got_packidx_bloom_filter) entry; char path[PATH_MAX]; /* on-disk path */ size_t path_len; struct bloom *bloom; }; RB_HEAD(got_packidx_bloom_filter_tree, got_packidx_bloom_filter); static inline int got_packidx_bloom_filter_cmp(const struct got_packidx_bloom_filter *f1, const struct got_packidx_bloom_filter *f2) { return got_path_cmp(f1->path, f2->path, f1->path_len, f2->path_len); } struct got_repo_privsep_child { int imsg_fd; pid_t pid; struct imsgbuf *ibuf; }; struct got_repository { char *path; char *path_git_dir; int gitdir_fd; enum got_hash_algorithm algo; struct got_pathlist_head packidx_paths; struct timespec pack_path_mtime; /* The pack index cache speeds up search for packed objects. */ struct got_packidx *packidx_cache[GOT_PACK_CACHE_SIZE]; /* * List of bloom filters for pack index files. * Used to avoid opening a pack index in search of an * object ID which is not contained in this pack index. */ struct got_packidx_bloom_filter_tree packidx_bloom_filters; /* * Open file handles for pack files. Each struct got_pack uses * a number of file descriptors. See struct got_pack for details. */ struct got_pack packs[GOT_PACK_CACHE_SIZE]; /* Open file handles for temporary files. */ int tempfiles[GOT_REPO_NUM_TEMPFILES]; uint32_t tempfile_use_mask; /* * The cache size limit may be lower than GOT_PACK_CACHE_SIZE, * depending on resource limits. */ int pack_cache_size; /* * Index to cache entries which are pinned to avoid eviction. * This may be used to keep one got-index-pack process alive * across searches for arbitrary objects which may be stored * in other pack files. */ int pinned_pack; pid_t pinned_pid; int pinned_packidx; /* Handles to child processes for reading loose objects. */ struct got_repo_privsep_child privsep_children[5]; #define GOT_REPO_PRIVSEP_CHILD_OBJECT 0 #define GOT_REPO_PRIVSEP_CHILD_COMMIT 1 #define GOT_REPO_PRIVSEP_CHILD_TREE 2 #define GOT_REPO_PRIVSEP_CHILD_BLOB 3 #define GOT_REPO_PRIVSEP_CHILD_TAG 4 /* Caches for open objects. */ struct got_object_cache objcache; struct got_object_cache treecache; struct got_object_cache commitcache; struct got_object_cache tagcache; struct got_object_cache rawcache; /* Settings read from Git configuration files. */ int gitconfig_repository_format_version; char *gitconfig_author_name; char *gitconfig_author_email; char *global_gitconfig_author_name; char *global_gitconfig_author_email; int ngitconfig_remotes; struct got_remote_repo *gitconfig_remotes; char *gitconfig_owner; char **extnames; char **extvals; int nextensions; /* Settings read from got.conf. */ struct got_gotconfig *gotconfig; /* cleanup lockfile */ struct got_lockfile *cleanup_lock; }; const struct got_error*got_repo_cache_object(struct got_repository *, struct got_object_id *, struct got_object *); struct got_object *got_repo_get_cached_object(struct got_repository *, struct got_object_id *); const struct got_error*got_repo_cache_tree(struct got_repository *, struct got_object_id *, struct got_tree_object *); struct got_tree_object *got_repo_get_cached_tree(struct got_repository *, struct got_object_id *); const struct got_error*got_repo_cache_commit(struct got_repository *, struct got_object_id *, struct got_commit_object *); struct got_commit_object *got_repo_get_cached_commit(struct got_repository *, struct got_object_id *); const struct got_error*got_repo_cache_tag(struct got_repository *, struct got_object_id *, struct got_tag_object *); struct got_tag_object *got_repo_get_cached_tag(struct got_repository *, struct got_object_id *); const struct got_error*got_repo_cache_raw_object(struct got_repository *, struct got_object_id *, struct got_raw_object *); struct got_raw_object *got_repo_get_cached_raw_object(struct got_repository *, struct got_object_id *); int got_repo_is_packidx_filename(const char *, size_t, enum got_hash_algorithm); int got_repo_check_packidx_bloom_filter(struct got_repository *, const char *, struct got_object_id *); const struct got_error *got_repo_search_packidx(struct got_packidx **, int *, struct got_repository *, struct got_object_id *); const struct got_error *got_repo_list_packidx(struct got_pathlist_head *, struct got_repository *); const struct got_error *got_repo_get_packidx(struct got_packidx **, const char *, struct got_repository *); const struct got_error *got_repo_cache_pack(struct got_pack **, struct got_repository *, const char *, struct got_packidx *); struct got_pack *got_repo_get_cached_pack(struct got_repository *, const char *); const struct got_error *got_repo_pin_pack(struct got_repository *, struct got_packidx *, struct got_pack *); struct got_pack *got_repo_get_pinned_pack(struct got_repository *); void got_repo_unpin_pack(struct got_repository *); const struct got_error *got_repo_read_gitconfig(int *, char **, char **, struct got_remote_repo **, int *, char **, char ***, char ***, int *, const char *); const struct got_error *got_repo_temp_fds_get(int *, int *, struct got_repository *); void got_repo_temp_fds_put(int, struct got_repository *); const struct got_error *got_repo_find_object_id(struct got_object_id *, struct got_repository *); got-portable-0.111/lib/bloom.c0000644000175000017500000001143115001740614011614 /* * Copyright (c) 2012-2019, Jyri J. Virkki * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* Obtained from https://github.com/jvirkki/libbloom */ /* * Refer to bloom.h for documentation on the public interfaces. */ #include #include #include #include #include #include #include #include #include #include #include "bloom.h" #include "murmurhash2.h" #define MAKESTRING(n) STRING(n) #define STRING(n) #n inline static int test_bit_set_bit(unsigned char * buf, unsigned int x, int set_bit) { unsigned int byte = x >> 3; unsigned char c = buf[byte]; // expensive memory access unsigned int mask = 1 << (x % 8); if (c & mask) { return 1; } else { if (set_bit) { buf[byte] = c | mask; } return 0; } } static int bloom_check_add(struct bloom * bloom, const void * buffer, int len, int add) { if (bloom->ready == 0) { printf("bloom at %p not initialized!\n", (void *)bloom); return -1; } int hits = 0; register unsigned int a = murmurhash2(buffer, len, bloom->seed); register unsigned int b = murmurhash2(buffer, len, a); register unsigned int x; register unsigned int i; for (i = 0; i < bloom->hashes; i++) { x = (a + i*b) % bloom->bits; if (test_bit_set_bit(bloom->bf, x, add)) { hits++; } else if (!add) { // Don't care about the presence of all the bits. Just our own. return 0; } } if (hits == bloom->hashes) { return 1; // 1 == element already in (or collision) } return 0; } int bloom_init_size(struct bloom * bloom, int entries, double error, unsigned int cache_size) { return bloom_init(bloom, entries, error); } int bloom_init(struct bloom * bloom, int entries, double error) { bloom->ready = 0; bloom->seed = arc4random(); if (entries < 1000 || error == 0) { return 1; } bloom->entries = entries; bloom->error = error; double num = log(bloom->error); double denom = 0.480453013918201; // ln(2)^2 bloom->bpe = -(num / denom); double dentries = (double)entries; bloom->bits = (int)(dentries * bloom->bpe); if (bloom->bits % 8) { bloom->bytes = (bloom->bits / 8) + 1; } else { bloom->bytes = bloom->bits / 8; } bloom->hashes = (int)ceil(0.693147180559945 * bloom->bpe); // ln(2) bloom->bf = (unsigned char *)calloc(bloom->bytes, sizeof(unsigned char)); if (bloom->bf == NULL) { // LCOV_EXCL_START return 1; } // LCOV_EXCL_STOP bloom->ready = 1; return 0; } int bloom_check(struct bloom * bloom, const void * buffer, int len) { return bloom_check_add(bloom, buffer, len, 0); } int bloom_add(struct bloom * bloom, const void * buffer, int len) { return bloom_check_add(bloom, buffer, len, 1); } void bloom_print(struct bloom * bloom) { printf("bloom at %p\n", (void *)bloom); printf(" ->entries = %d\n", bloom->entries); printf(" ->error = %f\n", bloom->error); printf(" ->bits = %d\n", bloom->bits); printf(" ->bits per elem = %f\n", bloom->bpe); printf(" ->bytes = %d\n", bloom->bytes); printf(" ->hash functions = %d\n", bloom->hashes); } void bloom_free(struct bloom * bloom) { if (bloom->ready) { free(bloom->bf); } bloom->ready = 0; } int bloom_reset(struct bloom * bloom) { if (!bloom->ready) return 1; memset(bloom->bf, 0, bloom->bytes); return 0; } got-portable-0.111/lib/pkt.c0000644000175000017500000001073615001740614011311 /* * Copyright (c) 2019 Ori Bernstein * Copyright (c) 2021 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "got_error.h" #include "got_lib_pkt.h" #include "got_lib_poll.h" const struct got_error * got_pkt_readn(ssize_t *off, int fd, void *buf, size_t n, int timeout) { const struct got_error *err; size_t len; err = got_poll_read_full_timeout(fd, &len, buf, n, n, timeout); if (err) return err; /* XXX size_t -> ssize_t */ if (len > SSIZE_MAX) return got_error(GOT_ERR_RANGE); *off = len; return NULL; } const struct got_error * got_pkt_flushpkt(int fd, int chattygot) { ssize_t w; if (chattygot > 1) fprintf(stderr, "%s: writepkt: 0000\n", getprogname()); w = write(fd, "0000", 4); if (w == -1) return got_error_from_errno("write"); if (w != 4) return got_error(GOT_ERR_IO); return NULL; } const struct got_error * got_pkt_readlen(int *len, const char *str, int chattygot) { int i; *len = 0; for (i = 0; i < 4; i++) { if ('0' <= str[i] && str[i] <= '9') { *len *= 16; *len += str[i] - '0'; } else if ('a' <= str[i] && str[i] <= 'f') { *len *= 16; *len += str[i] - 'a' + 10; } else { if (chattygot) fprintf(stderr, "%s: bad length: '.4%s'\n", getprogname(), str); return got_error_msg(GOT_ERR_BAD_PACKET, "packet length has invalid format"); } } return NULL; } /* * Packet header contains a 4-byte hexstring which specifies the length * of data which follows. */ const struct got_error * got_pkt_readhdr(int *datalen, int fd, int chattygot, int timeout) { static const struct got_error *err; char lenstr[4]; ssize_t r; int n; *datalen = 0; err = got_pkt_readn(&r, fd, lenstr, 4, timeout); if (err) return err; if (r == 0) { /* implicit "0000" */ if (chattygot > 1) fprintf(stderr, "%s: readpkt: 0000\n", getprogname()); return NULL; } if (r != 4) return got_error_msg(GOT_ERR_BAD_PACKET, "wrong packet header length"); err = got_pkt_readlen(&n, lenstr, chattygot); if (n == 0) return err; if (n <= 4) return got_error_msg(GOT_ERR_BAD_PACKET, "packet too short"); n -= 4; *datalen = n; return NULL; } const struct got_error * got_pkt_readpkt(int *outlen, int fd, char *buf, int buflen, int chattygot, int timeout) { const struct got_error *err = NULL; int datalen, i; ssize_t n; err = got_pkt_readhdr(&datalen, fd, chattygot, timeout); if (err) return err; if (datalen > buflen) return got_error(GOT_ERR_NO_SPACE); err = got_pkt_readn(&n, fd, buf, datalen, timeout); if (err) return err; if (n != datalen) return got_error_msg(GOT_ERR_BAD_PACKET, "short packet"); if (chattygot > 1) { fprintf(stderr, "%s: readpkt: %zd:\t", getprogname(), n); for (i = 0; i < n; i++) { if (isprint((unsigned char)buf[i])) fputc(buf[i], stderr); else fprintf(stderr, "[0x%.2x]", buf[i]); } fputc('\n', stderr); } *outlen = n; return NULL; } const struct got_error * got_pkt_writepkt(int fd, char *buf, int nbuf, int chattygot) { char len[5]; int i, ret; ssize_t w; ret = snprintf(len, sizeof(len), "%04x", nbuf + 4); if (ret < 0 || (size_t)ret >= sizeof(len)) return got_error(GOT_ERR_NO_SPACE); w = write(fd, len, 4); if (w == -1) return got_error_from_errno("write"); if (w != 4) return got_error(GOT_ERR_IO); w = write(fd, buf, nbuf); if (w == -1) return got_error_from_errno("write"); if (w != nbuf) return got_error(GOT_ERR_IO); if (chattygot > 1) { fprintf(stderr, "%s: writepkt: %s:\t", getprogname(), len); for (i = 0; i < nbuf; i++) { if (isprint((unsigned char)buf[i])) fputc(buf[i], stderr); else fprintf(stderr, "[0x%.2x]", buf[i]); } fputc('\n', stderr); } return NULL; } got-portable-0.111/lib/diff_output.c0000644000175000017500000002142315001740614013036 /* Common parts for printing diff output */ /* * Copyright (c) 2020 Neels Hofmeyr * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include "diff_internal.h" static int get_atom_byte(int *ch, struct diff_atom *atom, off_t off) { off_t cur; if (atom->at != NULL) { *ch = atom->at[off]; return 0; } cur = ftello(atom->root->f); if (cur == -1) return errno; if (cur != atom->pos + off && fseeko(atom->root->f, atom->pos + off, SEEK_SET) == -1) return errno; *ch = fgetc(atom->root->f); if (*ch == EOF && ferror(atom->root->f)) return errno; return 0; } #define DIFF_OUTPUT_BUF_SIZE 512 int diff_output_lines(struct diff_output_info *outinfo, FILE *dest, const char *prefix, struct diff_atom *start_atom, unsigned int count) { struct diff_atom *atom; off_t outoff = 0, *offp; uint8_t *typep; int rc; if (outinfo && outinfo->line_offsets.len > 0) { unsigned int idx = outinfo->line_offsets.len - 1; outoff = outinfo->line_offsets.head[idx]; } foreach_diff_atom(atom, start_atom, count) { off_t outlen = 0; int i, ch, nbuf = 0; unsigned int len = atom->len; unsigned char buf[DIFF_OUTPUT_BUF_SIZE + 1 /* '\n' */]; size_t n; n = strlcpy(buf, prefix, sizeof(buf)); if (n >= DIFF_OUTPUT_BUF_SIZE) /* leave room for '\n' */ return ENOBUFS; nbuf += n; if (len) { rc = get_atom_byte(&ch, atom, len - 1); if (rc) return rc; if (ch == '\n') len--; } for (i = 0; i < len; i++) { rc = get_atom_byte(&ch, atom, i); if (rc) return rc; if (nbuf >= DIFF_OUTPUT_BUF_SIZE) { rc = fwrite(buf, 1, nbuf, dest); if (rc != nbuf) return errno; outlen += rc; nbuf = 0; } buf[nbuf++] = ch; } buf[nbuf++] = '\n'; rc = fwrite(buf, 1, nbuf, dest); if (rc != nbuf) return errno; outlen += rc; if (outinfo) { ARRAYLIST_ADD(offp, outinfo->line_offsets); if (offp == NULL) return ENOMEM; outoff += outlen; *offp = outoff; ARRAYLIST_ADD(typep, outinfo->line_types); if (typep == NULL) return ENOMEM; *typep = *prefix == ' ' ? DIFF_LINE_CONTEXT : *prefix == '-' ? DIFF_LINE_MINUS : *prefix == '+' ? DIFF_LINE_PLUS : DIFF_LINE_NONE; } } return DIFF_RC_OK; } int diff_output_chunk_left_version(struct diff_output_info **output_info, FILE *dest, const struct diff_input_info *info, const struct diff_result *result, const struct diff_chunk_context *cc) { int rc, c_idx; struct diff_output_info *outinfo = NULL; if (diff_range_empty(&cc->left)) return DIFF_RC_OK; if (output_info) { *output_info = diff_output_info_alloc(); if (*output_info == NULL) return ENOMEM; outinfo = *output_info; } /* Write out all chunks on the left side. */ for (c_idx = cc->chunk.start; c_idx < cc->chunk.end; c_idx++) { const struct diff_chunk *c = &result->chunks.head[c_idx]; if (c->left_count) { rc = diff_output_lines(outinfo, dest, "", c->left_start, c->left_count); if (rc) return rc; } } return DIFF_RC_OK; } int diff_output_chunk_right_version(struct diff_output_info **output_info, FILE *dest, const struct diff_input_info *info, const struct diff_result *result, const struct diff_chunk_context *cc) { int rc, c_idx; struct diff_output_info *outinfo = NULL; if (diff_range_empty(&cc->right)) return DIFF_RC_OK; if (output_info) { *output_info = diff_output_info_alloc(); if (*output_info == NULL) return ENOMEM; outinfo = *output_info; } /* Write out all chunks on the right side. */ for (c_idx = cc->chunk.start; c_idx < cc->chunk.end; c_idx++) { const struct diff_chunk *c = &result->chunks.head[c_idx]; if (c->right_count) { rc = diff_output_lines(outinfo, dest, "", c->right_start, c->right_count); if (rc) return rc; } } return DIFF_RC_OK; } int diff_output_trailing_newline_msg(struct diff_output_info *outinfo, FILE *dest, const struct diff_chunk *c) { enum diff_chunk_type chunk_type = diff_chunk_type(c); struct diff_atom *atom, *start_atom; unsigned int atom_count; int rc, ch; off_t outoff = 0, *offp; uint8_t *typep; if (chunk_type == CHUNK_MINUS || chunk_type == CHUNK_SAME) { start_atom = c->left_start; atom_count = c->left_count; } else if (chunk_type == CHUNK_PLUS) { start_atom = c->right_start; atom_count = c->right_count; } else return EINVAL; /* Locate the last atom. */ if (atom_count == 0) return EINVAL; atom = &start_atom[atom_count - 1]; rc = get_atom_byte(&ch, atom, atom->len - 1); if (rc != DIFF_RC_OK) return rc; if (ch != '\n') { if (outinfo && outinfo->line_offsets.len > 0) { unsigned int idx = outinfo->line_offsets.len - 1; outoff = outinfo->line_offsets.head[idx]; } rc = fprintf(dest, "\\ No newline at end of file\n"); if (rc < 0) return errno; if (outinfo) { ARRAYLIST_ADD(offp, outinfo->line_offsets); if (offp == NULL) return ENOMEM; outoff += rc; *offp = outoff; ARRAYLIST_ADD(typep, outinfo->line_types); if (typep == NULL) return ENOMEM; *typep = DIFF_LINE_NONE; } } return DIFF_RC_OK; } static bool is_function_prototype(unsigned char ch) { return (isalpha((unsigned char)ch) || ch == '_' || ch == '$'); } #define begins_with(s, pre) (strncmp(s, pre, sizeof(pre)-1) == 0) int diff_output_match_function_prototype(char *prototype, size_t prototype_size, int *last_prototype_idx, const struct diff_result *result, const struct diff_chunk_context *cc) { struct diff_atom *start_atom, *atom; const struct diff_data *data; unsigned char buf[DIFF_FUNCTION_CONTEXT_SIZE]; const char *state = NULL; int rc, i, ch; if (result->left->atoms.len > 0 && cc->left.start > 0) { data = result->left; start_atom = &data->atoms.head[cc->left.start - 1]; } else return DIFF_RC_OK; diff_data_foreach_atom_backwards_from(start_atom, atom, data) { int atom_idx = diff_atom_root_idx(data, atom); if (atom_idx < *last_prototype_idx) break; rc = get_atom_byte(&ch, atom, 0); if (rc) return rc; buf[0] = (unsigned char)ch; if (!is_function_prototype(buf[0])) continue; for (i = 1; i < atom->len && i < sizeof(buf) - 1; i++) { rc = get_atom_byte(&ch, atom, i); if (rc) return rc; if (ch == '\n') break; buf[i] = (unsigned char)ch; } buf[i] = '\0'; if (begins_with(buf, "private:")) { if (!state) state = " (private)"; } else if (begins_with(buf, "protected:")) { if (!state) state = " (protected)"; } else if (begins_with(buf, "public:")) { if (!state) state = " (public)"; } else { if (state) /* don't care about truncation */ strlcat(buf, state, sizeof(buf)); strlcpy(prototype, buf, prototype_size); break; } } *last_prototype_idx = diff_atom_root_idx(data, start_atom); return DIFF_RC_OK; } struct diff_output_info * diff_output_info_alloc(void) { struct diff_output_info *output_info; off_t *offp; uint8_t *typep; output_info = malloc(sizeof(*output_info)); if (output_info != NULL) { ARRAYLIST_INIT(output_info->line_offsets, 128); ARRAYLIST_ADD(offp, output_info->line_offsets); if (offp == NULL) { diff_output_info_free(output_info); return NULL; } *offp = 0; ARRAYLIST_INIT(output_info->line_types, 128); ARRAYLIST_ADD(typep, output_info->line_types); if (typep == NULL) { diff_output_info_free(output_info); return NULL; } *typep = DIFF_LINE_NONE; } return output_info; } void diff_output_info_free(struct diff_output_info *output_info) { ARRAYLIST_FREE(output_info->line_offsets); ARRAYLIST_FREE(output_info->line_types); free(output_info); } const char * diff_output_get_label_left(const struct diff_input_info *info) { if (info->flags & DIFF_INPUT_LEFT_NONEXISTENT) return "/dev/null"; return info->left_path ? info->left_path : "a"; } const char * diff_output_get_label_right(const struct diff_input_info *info) { if (info->flags & DIFF_INPUT_RIGHT_NONEXISTENT) return "/dev/null"; return info->right_path ? info->right_path : "b"; } got-portable-0.111/lib/utf8.c0000644000175000017500000000407215001741021011366 /* * Copyright (c) 2015 Ingo Schwarze * Copyright (c) 2018 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include "got_error.h" #include "got_utf8.h" const struct got_error * got_mbsavis(char** outp, int *widthp, const char *mbs) { const char *src; /* Iterate mbs. */ char *dst; /* Iterate *outp. */ wchar_t wc; int total_width; /* Display width of the whole string. */ int width; /* Display width of a single Unicode char. */ int len; /* Length in bytes of UTF-8 encoded string. */ len = strlen(mbs); if ((*outp = malloc(len + 1)) == NULL) return got_error_from_errno("malloc"); if (MB_CUR_MAX == 1) { memcpy(*outp, mbs, len + 1); *widthp = len; return NULL; } src = mbs; dst = *outp; total_width = 0; while (*src != '\0') { if ((len = mbtowc(&wc, src, MB_CUR_MAX)) == -1) { total_width++; *dst++ = '?'; src++; } else if ((width = wcwidth(wc)) == -1) { total_width++; *dst++ = '?'; src += len; } else { total_width += width; while (len-- > 0) *dst++ = *src++; } } *dst = '\0'; *widthp = total_width; return NULL; } int got_locale_is_utf8(void) { char *codeset = nl_langinfo(CODESET); return (strcmp(codeset, "UTF-8") == 0); } got-portable-0.111/lib/got_lib_delta.h0000644000175000017500000000776715001740614013322 /* * Copyright (c) 2018 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ struct got_delta { STAILQ_ENTRY(got_delta) entry; off_t offset; size_t tslen; int type; size_t size; off_t data_offset; }; struct got_delta_chain { int nentries; STAILQ_HEAD(, got_delta) entries; }; #define GOT_DELTA_CHAIN_RECURSION_MAX 500 struct got_delta *got_delta_open(off_t, size_t, int, size_t, off_t); const struct got_error *got_delta_chain_get_base_type(int *, struct got_delta_chain *); const struct got_error *got_delta_get_sizes(uint64_t *, uint64_t *, const uint8_t *, size_t); const struct got_error *got_delta_apply_in_mem(uint8_t *, size_t, const uint8_t *, size_t, uint8_t *, size_t *, size_t); const struct got_error *got_delta_apply(FILE *, const uint8_t *, size_t, FILE *, size_t *); /* * The amount of result data we may keep in RAM while applying deltas. * Data larger than this is written to disk during delta application (slow). */ #define GOT_DELTA_RESULT_SIZE_CACHED_MAX (8 * 1024 * 1024) /* bytes */ /* * Definitions for delta data streams. */ #define GOT_DELTA_STREAM_LENGTH_MIN 4 /* bytes */ /* * A delta stream begins with two size fields. The first specifies the * size of the delta base, and the second describes the expected size of * the data which results from applying the delta to the delta base. * * Each size field uses a variable length encoding: * size0...sizeN form a 7+7+7+...+7 bit integer, where size0 is the * least significant part and sizeN is the most significant part. * If the MSB of a size byte is set, an additional size byte follows. */ #define GOT_DELTA_SIZE_VAL_MASK 0x7f #define GOT_DELTA_SIZE_SHIFT 7 #define GOT_DELTA_SIZE_MORE 0x80 /* * The rest of the delta stream contains copy instructions. * * A base copy instruction copies N bytes starting at offset X from the delta * base to the output. Base copy instructions begin with a byte which has its * MSB set. The remaining bits of this byte describe how many offset and * length value bytes follow. * The offset X is encoded in 1 to 4 bytes, and the length N is encoded in * 1 to 3 bytes. For both values, the first byte contributes the least * significant part and the last byte which is present contributes the * most significant part. * If the offset value is omitted, an offset of zero is implied. * If the length value is omitted, a default length of 65536 bytes is implied. * * An inline copy instruction copies data from the delta stream to the output. * Such instructions begin with one byte which does not have the MSB set * and which specifies the length of the inline data which follows (i.e. * at most 127 bytes). A length value of zero is invalid. */ #define GOT_DELTA_BASE_COPY 0x80 #define GOT_DELTA_COPY_OFF1 0x01 /* byte 1 of offset is present */ #define GOT_DELTA_COPY_OFF2 0x02 /* byte 2 of offset is present */ #define GOT_DELTA_COPY_OFF3 0x04 /* byte 3 of offset is present */ #define GOT_DELTA_COPY_OFF4 0x08 /* byte 4 of offset is present */ #define GOT_DELTA_COPY_LEN1 0x10 /* byte 1 of length is present */ #define GOT_DELTA_COPY_LEN2 0x20 /* byte 2 of length is present */ #define GOT_DELTA_COPY_LEN3 0x40 /* byte 3 of length is present */ #define GOT_DELTA_COPY_DEFAULT_OFF 0x0 /* default offset if omitted */ #define GOT_DELTA_COPY_DEFAULT_LEN 0x10000 /* default length if omitted */ got-portable-0.111/lib/buf.c0000644000175000017500000001766515001741021011270 /* $OpenBSD: buf.c,v 1.27 2016/10/16 13:35:51 okan Exp $ */ /* * Copyright (c) 2003 Jean-Francois Brousseau * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include "buf.h" #include "got_error.h" #define BUF_INCR 128 #define SIZE_LEFT(b) ((b)->cb_size - (b)->cb_len) static const struct got_error *buf_grow(BUF *, size_t); /* * Create a new buffer structure and return a pointer to it. This structure * uses dynamically-allocated memory and must be freed with buf_free(), once * the buffer is no longer needed. */ const struct got_error * buf_alloc(BUF **b, size_t len) { const struct got_error *err = NULL; *b = malloc(sizeof(**b)); if (*b == NULL) return got_error_from_errno("malloc"); /* Postpone creation of zero-sized buffers */ if (len > 0) { (*b)->cb_buf = calloc(1, len); if ((*b)->cb_buf == NULL) { err = got_error_from_errno("calloc"); free(*b); *b = NULL; return err; } } else (*b)->cb_buf = NULL; (*b)->cb_size = len; (*b)->cb_len = 0; return NULL; } /* * Open the file specified by and load all of its contents into a * buffer. * Returns the loaded buffer on success or NULL on failure. * Sets errno on error. */ const struct got_error * buf_load(BUF **buf, FILE *f) { const struct got_error *err = NULL; size_t ret; size_t len; u_char *bp; struct stat st; *buf = NULL; if (fstat(fileno(f), &st) == -1) { err = got_error_from_errno("fstat"); goto out; } if ((uintmax_t)st.st_size > SIZE_MAX) { err = got_error_set_errno(EFBIG, "cannot fit file into memory buffer"); goto out; } err = buf_alloc(buf, st.st_size); if (err) goto out; for (bp = (*buf)->cb_buf; ; bp += ret) { len = SIZE_LEFT(*buf); ret = fread(bp, 1, len, f); if (ret == 0 && ferror(f)) { err = got_ferror(f, GOT_ERR_IO); goto out; } else if (ret == 0) break; (*buf)->cb_len += (size_t)ret; } out: if (err) { buf_free(*buf); *buf = NULL; } return err; } const struct got_error * buf_load_fd(BUF **buf, int fd) { const struct got_error *err = NULL; unsigned char out[8192]; ssize_t r; size_t len; err = buf_alloc(buf, 8192); if (err) return err; do { r = read(fd, out, sizeof(out)); if (r == -1) return got_error_from_errno("read"); if (r > 0) { err = buf_append(&len, *buf, out, r); if (err) return err; } } while (r > 0); return NULL; } void buf_free(BUF *b) { if (b == NULL) return; free(b->cb_buf); free(b); } /* * Free the buffer 's structural information but do not free the contents * of the buffer. Instead, they are returned and should be freed later using * free(). */ void * buf_release(BUF *b) { void *tmp; tmp = b->cb_buf; free(b); return (tmp); } u_char * buf_get(BUF *b) { return (b->cb_buf); } /* * Empty the contents of the buffer and reset pointers. */ void buf_empty(BUF *b) { memset(b->cb_buf, 0, b->cb_size); b->cb_len = 0; } /* Discard the leading bytes from the buffer. */ const struct got_error * buf_discard(BUF *b, size_t n) { if (n > b->cb_len) return got_error(GOT_ERR_RANGE); if (n == b->cb_len) buf_empty(b); else { memmove(b->cb_buf, b->cb_buf + n, b->cb_len - n); b->cb_len -= n; } return NULL; } /* * Append a single character to the end of the buffer . */ const struct got_error * buf_putc(BUF *b, int c) { const struct got_error *err = NULL; u_char *bp; if (SIZE_LEFT(b) == 0) { err = buf_grow(b, BUF_INCR); if (err) return err; } bp = b->cb_buf + b->cb_len; *bp = (u_char)c; b->cb_len++; return NULL; } /* * Append a string to the end of buffer . */ const struct got_error * buf_puts(size_t *newlen, BUF *b, const char *str) { return buf_append(newlen, b, str, strlen(str)); } /* * Return u_char at buffer position . */ u_char buf_getc(BUF *b, size_t pos) { return (b->cb_buf[pos]); } /* * Append bytes of data pointed to by to the buffer . If the * buffer is too small to accept all data, it will get resized to an * appropriate size to accept all data. * Returns the number of bytes successfully appended to the buffer. */ const struct got_error * buf_append(size_t *newlen, BUF *b, const void *data, size_t len) { const struct got_error *err = NULL; size_t left, rlen; u_char *bp; left = SIZE_LEFT(b); rlen = len; if (left < len) { err = buf_grow(b, len - left); if (err) return err; } bp = b->cb_buf + b->cb_len; memcpy(bp, data, rlen); b->cb_len += rlen; *newlen = rlen; return NULL; } /* * Returns the size of the buffer that is being used. */ size_t buf_len(BUF *b) { return (b->cb_len); } /* * Write the contents of the buffer to the specified */ int buf_write_fd(BUF *b, int fd) { u_char *bp; size_t len; ssize_t ret; len = b->cb_len; bp = b->cb_buf; do { ret = write(fd, bp, len); if (ret == -1) { if (errno == EINTR || errno == EAGAIN) continue; return (-1); } len -= (size_t)ret; bp += (size_t)ret; } while (len > 0); return (0); } /* * Write the contents of the buffer to the file whose path is given in * . If the file does not exist, it is created with mode . */ const struct got_error * buf_write(BUF *b, const char *path, mode_t mode) { const struct got_error *err = NULL; int fd; open: if ((fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, mode)) == -1) { err = got_error_from_errno2("open", path); if (errno == EACCES && unlink(path) != -1) goto open; else return err; } if (buf_write_fd(b, fd) == -1) { err = got_error_from_errno("buf_write_fd"); (void)unlink(path); return err; } if (fchmod(fd, mode) < 0) err = got_error_from_errno2("fchmod", path); if (close(fd) == -1 && err == NULL) err = got_error_from_errno2("close", path); return err; } /* * Write the contents of the buffer to a temporary file whose path is * specified using