From: Stefan Sperling Subject: add got.conf(5) To: gameoftrees@openbsd.org Date: Sun, 6 Sep 2020 18:09:44 +0200 This adds a got.conf(5) configuration file, based on parse.y code added by Tracey in the got/ subdirectory. The new got.conf file is parsed by a new libexec helper when a repository is opened. This is not done because we really need privsep, but because yacc generates code with global variables that cannot be used in a library context. We can work around this by running our parse.y code in a separate address space. At present, only author information (for got commit/import) and remote repositories (for got fetch) can be configured. We can expand this scope later as needed. ok? diff refs/heads/master refs/heads/gotconfig blob - 19122e372a158d8a95216bb854924e9a25225a76 blob + f74cc058dc06317e6752bfcbebd77f4e23bccba8 --- got/got.1 +++ got/got.1 @@ -88,7 +88,9 @@ The command requires the .Ev GOT_AUTHOR environment variable to be set, -unless Git's +unless an author has been configured in +.Xr got.conf 5 +or Git's .Dv user.name and .Dv user.email @@ -186,6 +188,8 @@ More details about the pack file format are documented .Pp .Cm got clone creates a remote repository entry in the +.Xr got.conf 5 +and .Pa config file of the cloned repository to store the .Ar repository-url @@ -234,8 +238,10 @@ This is useful if the cloned repository will not be us locally created commits. .Pp The repository's +.Xr got.conf 5 +and .Pa config -file will be set up with the +files will be set up with the .Dq mirror option enabled, such that .Cm got fetch @@ -310,6 +316,8 @@ is specified, .Dq origin will be used. The remote repository's URL is obtained from the corresponding entry in the +.Xr got.conf 5 +or .Pa config file of the local repository, as created by .Cm got clone . @@ -1228,7 +1236,9 @@ The command requires the .Ev GOT_AUTHOR environment variable to be set, -unless Git's +unless an author has been configured in +.Xr got.conf 5 +or Git's .Dv user.name and .Dv user.email @@ -1920,19 +1930,25 @@ attempts to reject .Ev GOT_AUTHOR environment variables with a missing email address. .Pp -If present, Git's +If present, +configuration settings in +.Xr got.conf 5 , +or Git's .Dv user.name and .Dv user.email configuration settings in the repository's .Pa .git/config -file will override the value of +file, +will override the value of .Ev GOT_AUTHOR . However, the .Dv user.name and .Dv user.email -configuration settings contained in Git's global +configuration settings contained in +.Xr got.conf 5 +or Git's global .Pa ~/.gitconfig configuration file will be used only if the .Ev GOT_AUTHOR @@ -1951,6 +1967,16 @@ The default limit on the number of commits traversed b If set to zero, the limit is unbounded. This variable will be silently ignored if it is set to a non-numeric value. .El +.Sh FILES +.Bl -tag -width packed-refs -compact +.It Pa got.conf +Repository-wide configuration settings for +.Nm . +If present, this configuration file is located in the root directory +of a Git repository and supersedes any relevant settings in Git's +.Pa config +file. +.El .Sh EXIT STATUS .Ex -std got .Sh EXAMPLES @@ -2257,7 +2283,8 @@ repository with .Sh SEE ALSO .Xr tog 1 , .Xr git-repository 5 , -.Xr got-worktree 5 +.Xr got-worktree 5 , +.Xr got.conf 5 .Sh AUTHORS .An Stefan Sperling Aq Mt stsp@openbsd.org .An Martin Pieuchot Aq Mt mpi@openbsd.org blob - 2b4d757744e69bb5461dd333ec37c8351cdeaae0 blob + ee68786d7a1608cf5b225ba31744511639efc4f6 --- got/got.c +++ got/got.c @@ -523,25 +523,30 @@ get_author(char **author, struct got_repository *repo) *author = NULL; - name = got_repo_get_gitconfig_author_name(repo); - email = got_repo_get_gitconfig_author_email(repo); - if (name && email) { - if (asprintf(author, "%s <%s>", name, email) == -1) - return got_error_from_errno("asprintf"); - return NULL; - } - - got_author = getenv("GOT_AUTHOR"); + got_author = got_repo_get_gotconfig_author(repo); if (got_author == NULL) { - name = got_repo_get_global_gitconfig_author_name(repo); - email = got_repo_get_global_gitconfig_author_email(repo); + name = got_repo_get_gitconfig_author_name(repo); + email = got_repo_get_gitconfig_author_email(repo); if (name && email) { if (asprintf(author, "%s <%s>", name, email) == -1) return got_error_from_errno("asprintf"); return NULL; } - /* TODO: Look up user in password database? */ - return got_error(GOT_ERR_COMMIT_NO_AUTHOR); + + got_author = getenv("GOT_AUTHOR"); + if (got_author == NULL) { + name = got_repo_get_global_gitconfig_author_name(repo); + email = got_repo_get_global_gitconfig_author_email( + repo); + if (name && email) { + if (asprintf(author, "%s <%s>", name, email) + == -1) + return got_error_from_errno("asprintf"); + return NULL; + } + /* TODO: Look up user in password database? */ + return got_error(GOT_ERR_COMMIT_NO_AUTHOR); + } } *author = strdup(got_author); @@ -1037,9 +1042,9 @@ cmd_clone(int argc, char *argv[]) pid_t fetchpid = -1; struct got_fetch_progress_arg fpa; char *git_url = NULL; - char *gitconfig_path = NULL; - char *gitconfig = NULL; - FILE *gitconfig_file = NULL; + char *gitconfig_path = NULL, *gotconfig_path = NULL; + char *gitconfig = NULL, *gotconfig = NULL; + FILE *gitconfig_file = NULL, *gotconfig_file = NULL; ssize_t n; int verbosity = 0, fetch_all_branches = 0, mirror_references = 0; int list_refs_only = 0; @@ -1340,7 +1345,40 @@ cmd_clone(int argc, char *argv[]) } } - /* Create a config file git-fetch(1) can understand. */ + /* Create got.conf(5). */ + gotconfig_path = got_repo_get_path_gotconfig(repo); + if (gotconfig_path == NULL) { + error = got_error_from_errno("got_repo_get_path_gotconfig"); + goto done; + } + gotconfig_file = fopen(gotconfig_path, "a"); + if (gotconfig_file == NULL) { + error = got_error_from_errno2("fopen", gotconfig_path); + goto done; + } + got_path_strip_trailing_slashes(server_path); + if (asprintf(&gotconfig, + "remote \"%s\" {\n" + "\tserver %s\n" + "\tprotocol %s\n" + "%s%s%s" + "\trepository \"%s\"\n" + "%s" + "}\n", + GOT_FETCH_DEFAULT_REMOTE_NAME, host, proto, + port ? "\tport " : "", port ? port : "", port ? "\n" : "", + server_path, + mirror_references ? "\tmirror-references yes\n" : "") == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + n = fwrite(gotconfig, 1, strlen(gotconfig), gotconfig_file); + if (n != strlen(gotconfig)) { + error = got_ferror(gotconfig_file, GOT_ERR_IO); + goto done; + } + + /* Create a config file Git can understand. */ gitconfig_path = got_repo_get_path_gitconfig(repo); if (gitconfig_path == NULL) { error = got_error_from_errno("got_repo_get_path_gitconfig"); @@ -1413,6 +1451,8 @@ done: } if (fetchfd != -1 && close(fetchfd) == -1 && error == NULL) error = got_error_from_errno("close"); + if (gotconfig_file && fclose(gotconfig_file) == EOF && error == NULL) + error = got_error_from_errno("fclose"); if (gitconfig_file && fclose(gitconfig_file) == EOF && error == NULL) error = got_error_from_errno("fclose"); if (repo) @@ -1438,6 +1478,9 @@ done: free(server_path); free(repo_name); free(default_destdir); + free(gotconfig); + free(gitconfig); + free(gotconfig_path); free(gitconfig_path); free(git_url); return error; @@ -1858,16 +1901,24 @@ cmd_fetch(int argc, char *argv[]) if (error) goto done; - got_repo_get_gitconfig_remotes(&nremotes, &remotes, repo); + got_repo_get_gotconfig_remotes(&nremotes, &remotes, repo); for (i = 0; i < nremotes; i++) { remote = &remotes[i]; if (strcmp(remote->name, remote_name) == 0) break; } if (i == nremotes) { - error = got_error_path(remote_name, GOT_ERR_NO_REMOTE); - goto done; - } + got_repo_get_gitconfig_remotes(&nremotes, &remotes, repo); + for (i = 0; i < nremotes; i++) { + remote = &remotes[i]; + if (strcmp(remote->name, remote_name) == 0) + break; + } + if (i == nremotes) { + error = got_error_path(remote_name, GOT_ERR_NO_REMOTE); + goto done; + } + } error = got_fetch_parse_uri(&proto, &host, &port, &server_path, &repo_name, remote->url); blob - /dev/null blob + d6d11e44ab3797c4469bc5d41a882b8be638c18d (mode 644) --- /dev/null +++ got/got.conf.5 @@ -0,0 +1,156 @@ +.\" +.\" 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. +.\" +.Dd $Mdocdate$ +.Dt GOT.CONF 5 +.Os +.Sh NAME +.Nm got.conf +.Nd Game of Trees configuration file +.Sh DESCRIPTION +.Nm +is the run-time configuration file for +.Xr got 1 . +.Pp +The file format is line-based, with one configuration directive per line. +Any lines beginning with a +.Sq # +are treated as comments and ignored. +.Pp +The available configuration directives are as follows: +.Bl -tag -width Ds +.It Ic author Dq Real Name +Configure the author's name and email address for +.Cm got commit +and +.Cm got import +when operating on this repository. +Author information specified here overrides the +.Ev GOT_AUTHOR +environment variable. +.Pp +Because +.Xr git 1 +may fail to parse commits without an email address in author data, +.Xr got 1 +attempts to reject author information with a missing email address. +.It Ic remote Ar name Brq ... +Define a remote repository. +The specified +.Ar name +can be used to refer to the remote repository on the command line of +.Cm got fetch . +.Pp +Information about this repository is declared in a block of options +enclosed in curly brackets: +.Bl -tag -width Ds +.It Ic server Ar hostname +Defines the hostname to use for contacting the remote repository's server. +.It Ic repository Ar path +Defines the path to the repository on the remote repository's server. +.It Ic protocol Ar scheme +Defines the protocol to use for communicating with the remote repository's +server. +.Pp +The following protocol schemes are supported: +.Bl -tag -width git+ssh +.It git +The Git protocol as implemented by the +.Xr git-daemon 1 +server. +Use of this protocol is discouraged since it supports neither authentication +nor encryption. +.It git+ssh +The Git protocol wrapped in an authenticated and encrypted +.Xr ssh 1 +tunnel. +With this protocol the hostname may contain an embedded username for +.Xr ssh 1 +to use: +.Mt user@hostname +.It ssh +Short alias for git+ssh. +.El +.It Ic port Ar port +Defines the port to use for connecting to the remote repository's server. +The +.Ar port +can be specified by number or name. +The port name to number mappings are found in the file +.Pa /etc/services ; +see +.Xr services 5 +for details. +If not specified, the default port of the specified +.Cm protocol +will be used. +.It Ic mirror-references Ar yes | no +This option controls the behaviour of +.Cm got fetch +when updating references. +.Sy Enabling this option can lead to the loss of local commits. +Maintaining custom changes in a mirror repository is therefore discouraged. +.Pp +If this option is not specified or set to +.Ar no , +.Cm got fetch +will map references of the remote repository into the local repository's +.Dq refs/remotes/ +namespace. +.Pp +If this option is set to +.Ar yes , +all branches in the +.Dq refs/heads/ +namespace will be updated directly to match the corresponding branches in +the remote repository. +.El +.Sh EXAMPLES +Configure author information: +.Bd -literal -offset indent +author "Flan Hacker " +.Ed +.Pp +Remote repository specification for the Game of Trees repository: +.Bd -literal -offset indent +remote "origin" { + server git.gameoftrees.org + protocol git + repository got +} +.Ed +.Pp +Mirror the OpenBSD src repository from Github: +.Bd -literal -offset indent +remote "origin" { + repository "openbsd/src" + server git@github.com + protocol git+ssh + mirror-references yes +} +.Ed +.Sh FILES +.Bl -tag -width Ds -compact +.It Pa got.conf +If present, the +.Nm +configuration file is located in the root directory of a Git repository +and supersedes any relevant settings in Git's +.Pa config +file. +.El +.Sh SEE ALSO +.Xr got 1 , +.Xr git-repository 5 blob - 93dc3d73083ef892787d94815eeedb416e7fc4e3 blob + 93c6e03f94ed0176787d92976b2a68085ae10de3 --- gotweb/parse.y +++ gotweb/parse.y @@ -234,7 +234,7 @@ yyerror(const char *fmt, ...) gerror = got_error_from_errno("asprintf"); return(0); } - gerror = got_error_msg(GOT_ERR_PARSE_Y_YY, strdup(err)); + gerror = got_error_msg(GOT_ERR_PARSE_CONFIG, strdup(err)); free(msg); free(err); return(0); blob - b3805d39c06efa63932243754b6f446fa0218eda blob + 0f3e07a914451fb4ee60661753010efd60284036 --- include/got_error.h +++ include/got_error.h @@ -141,7 +141,7 @@ #define GOT_ERR_FETCH_NO_BRANCH 124 #define GOT_ERR_FETCH_BAD_REF 125 #define GOT_ERR_TREE_ENTRY_TYPE 126 -#define GOT_ERR_PARSE_Y_YY 127 +#define GOT_ERR_PARSE_CONFIG 127 #define GOT_ERR_NO_CONFIG_FILE 128 #define GOT_ERR_BAD_SYMLINK 129 @@ -291,7 +291,7 @@ static const struct got_error { { 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_Y_YY, "yyerror error" }, + { 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" }, blob - 1e0ca3b4ba3d818cceefe6c470559274e954788c blob + bc9bddc1aebed32b0412d1f87f93b3ec0b753271 --- include/got_repository.h +++ include/got_repository.h @@ -59,10 +59,17 @@ struct got_remote_repo { int mirror_references; }; +/* Obtain the commit author if parsed from got.conf, else NULL. */ +const char *got_repo_get_gotconfig_author(struct got_repository *); + /* Obtain the list of remote repositories parsed from gitconfig. */ void got_repo_get_gitconfig_remotes(int *, struct got_remote_repo **, struct got_repository *); +/* Obtain the list of remote repositories parsed from got.conf. */ +void got_repo_get_gotconfig_remotes(int *, struct got_remote_repo **, + struct got_repository *); + /* * Obtain paths to various directories within a repository. * The caller must dispose of a path with free(3). @@ -72,6 +79,7 @@ char *got_repo_get_path_objects_pack(struct got_reposi char *got_repo_get_path_refs(struct got_repository *); char *got_repo_get_path_packed_refs(struct got_repository *); char *got_repo_get_path_gitconfig(struct got_repository *); +char *got_repo_get_path_gotconfig(struct got_repository *); struct got_reference; blob - 950723fdd3cab55b86ef0eb87c5739432be13b7c blob + 51acfb2fde45abd2e9073411ad36ae9a24fed30e --- lib/got_lib_privsep.h +++ lib/got_lib_privsep.h @@ -43,6 +43,7 @@ #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_FETCH_PACK got-fetch-pack #define GOT_PROG_INDEX_PACK got-index-pack #define GOT_PROG_SEND_PACK got-send-pack @@ -65,6 +66,8 @@ 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_FETCH_PACK \ GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_FETCH_PACK) #define GOT_PATH_PROG_SEND_PACK \ @@ -147,6 +150,15 @@ enum got_imsg_type { 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_REMOTES_REQUEST, + GOT_IMSG_GOTCONFIG_INT_VAL, + GOT_IMSG_GOTCONFIG_STR_VAL, + GOT_IMSG_GOTCONFIG_REMOTES, + GOT_IMSG_GOTCONFIG_REMOTE, }; /* Structure for GOT_IMSG_ERROR. */ @@ -457,6 +469,16 @@ const struct got_error *got_privsep_recv_gitconfig_str 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_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( blob - df440da3c28c0038eb1d0ecb1cf3af9501734493 blob + 53390eff4e10f3f105c870ac188b9a0db1df2416 --- lib/got_lib_repository.h +++ lib/got_lib_repository.h @@ -21,6 +21,7 @@ #define GOT_REFS_DIR "refs" #define GOT_HEAD_FILE "HEAD" #define GOT_GITCONFIG "config" +#define GOT_GOTCONFIG "got.conf" /* Other files and directories inside the git directory. */ #define GOT_FETCH_HEAD_FILE "FETCH_HEAD" @@ -64,6 +65,11 @@ struct got_repository { int ngitconfig_remotes; struct got_remote_repo *gitconfig_remotes; char *gitconfig_owner; + + /* Settings read from got.conf. */ + char *gotconfig_author; + int ngotconfig_remotes; + struct got_remote_repo *gotconfig_remotes; }; const struct got_error*got_repo_cache_object(struct got_repository *, blob - 44f5cc811353036797f501a79e4fb1ee0f11dcf1 blob + b91bda4fa54fee65a9eb14a87a3662af9a624a11 --- lib/privsep.c +++ lib/privsep.c @@ -1887,6 +1887,208 @@ got_privsep_recv_gitconfig_remotes(struct got_remote_r } const struct got_error * +got_privsep_send_gotconfig_parse_req(struct imsgbuf *ibuf, int fd) +{ + const struct got_error *err = NULL; + + if (imsg_compose(ibuf, GOT_IMSG_GOTCONFIG_PARSE_REQUEST, 0, 0, fd, + NULL, 0) == -1) { + err = got_error_from_errno("imsg_compose " + "GOTCONFIG_PARSE_REQUEST"); + close(fd); + return err; + } + + return flush_imsg(ibuf); +} + +const struct got_error * +got_privsep_send_gotconfig_author_req(struct imsgbuf *ibuf) +{ + if (imsg_compose(ibuf, + GOT_IMSG_GOTCONFIG_AUTHOR_REQUEST, 0, 0, -1, NULL, 0) == -1) + return got_error_from_errno("imsg_compose " + "GOTCONFIG_AUTHOR_REQUEST"); + + return flush_imsg(ibuf); +} + +const struct got_error * +got_privsep_send_gotconfig_remotes_req(struct imsgbuf *ibuf) +{ + if (imsg_compose(ibuf, + GOT_IMSG_GOTCONFIG_REMOTES_REQUEST, 0, 0, -1, NULL, 0) == -1) + return got_error_from_errno("imsg_compose " + "GOTCONFIG_REMOTE_REQUEST"); + + return flush_imsg(ibuf); +} + +const struct got_error * +got_privsep_recv_gotconfig_str(char **str, struct imsgbuf *ibuf) +{ + const struct got_error *err = NULL; + struct imsg imsg; + size_t datalen; + const size_t min_datalen = 0; + + *str = NULL; + + err = got_privsep_recv_imsg(&imsg, ibuf, min_datalen); + if (err) + return err; + datalen = imsg.hdr.len - IMSG_HEADER_SIZE; + + switch (imsg.hdr.type) { + case GOT_IMSG_ERROR: + if (datalen < sizeof(struct got_imsg_error)) { + err = got_error(GOT_ERR_PRIVSEP_LEN); + break; + } + err = recv_imsg_error(&imsg, datalen); + break; + case GOT_IMSG_GOTCONFIG_STR_VAL: + if (datalen == 0) + break; + *str = malloc(datalen); + if (*str == NULL) { + err = got_error_from_errno("malloc"); + break; + } + if (strlcpy(*str, imsg.data, datalen) >= datalen) + err = got_error(GOT_ERR_NO_SPACE); + break; + default: + err = got_error(GOT_ERR_PRIVSEP_MSG); + break; + } + + imsg_free(&imsg); + return err; +} + +const struct got_error * +got_privsep_recv_gotconfig_remotes(struct got_remote_repo **remotes, + int *nremotes, struct imsgbuf *ibuf) +{ + const struct got_error *err = NULL; + struct imsg imsg; + size_t datalen; + struct got_imsg_remotes iremotes; + struct got_imsg_remote iremote; + const size_t min_datalen = + MIN(sizeof(struct got_imsg_error), sizeof(iremotes)); + + *remotes = NULL; + *nremotes = 0; + iremotes.nremotes = 0; + + err = got_privsep_recv_imsg(&imsg, ibuf, min_datalen); + if (err) + return err; + datalen = imsg.hdr.len - IMSG_HEADER_SIZE; + + switch (imsg.hdr.type) { + case GOT_IMSG_ERROR: + if (datalen < sizeof(struct got_imsg_error)) { + err = got_error(GOT_ERR_PRIVSEP_LEN); + break; + } + err = recv_imsg_error(&imsg, datalen); + break; + case GOT_IMSG_GOTCONFIG_REMOTES: + if (datalen != sizeof(iremotes)) { + err = got_error(GOT_ERR_PRIVSEP_LEN); + break; + } + memcpy(&iremotes, imsg.data, sizeof(iremotes)); + if (iremotes.nremotes == 0) { + imsg_free(&imsg); + return NULL; + } + break; + default: + imsg_free(&imsg); + return got_error(GOT_ERR_PRIVSEP_MSG); + } + + imsg_free(&imsg); + + *remotes = recallocarray(NULL, 0, iremotes.nremotes, sizeof(**remotes)); + if (*remotes == NULL) + return got_error_from_errno("recallocarray"); + + while (*nremotes < iremotes.nremotes) { + struct got_remote_repo *remote; + const size_t min_datalen = + MIN(sizeof(struct got_imsg_error), sizeof(iremote)); + + err = got_privsep_recv_imsg(&imsg, ibuf, min_datalen); + if (err) + break; + datalen = imsg.hdr.len - IMSG_HEADER_SIZE; + + switch (imsg.hdr.type) { + case GOT_IMSG_ERROR: + if (datalen < sizeof(struct got_imsg_error)) { + err = got_error(GOT_ERR_PRIVSEP_LEN); + break; + } + err = recv_imsg_error(&imsg, datalen); + break; + case GOT_IMSG_GOTCONFIG_REMOTE: + remote = &(*remotes)[*nremotes]; + if (datalen < sizeof(iremote)) { + err = got_error(GOT_ERR_PRIVSEP_LEN); + break; + } + memcpy(&iremote, imsg.data, sizeof(iremote)); + if (iremote.name_len == 0 || iremote.url_len == 0 || + (sizeof(iremote) + iremote.name_len + + iremote.url_len) > datalen) { + err = got_error(GOT_ERR_PRIVSEP_LEN); + break; + } + remote->name = strndup(imsg.data + sizeof(iremote), + iremote.name_len); + if (remote->name == NULL) { + err = got_error_from_errno("strndup"); + break; + } + remote->url = strndup(imsg.data + sizeof(iremote) + + iremote.name_len, iremote.url_len); + if (remote->url == NULL) { + err = got_error_from_errno("strndup"); + free(remote->name); + break; + } + remote->mirror_references = iremote.mirror_references; + (*nremotes)++; + break; + default: + err = got_error(GOT_ERR_PRIVSEP_MSG); + break; + } + + imsg_free(&imsg); + if (err) + break; + } + + if (err) { + int i; + for (i = 0; i < *nremotes; i++) { + free((*remotes)[i].name); + free((*remotes)[i].url); + } + free(*remotes); + *remotes = NULL; + *nremotes = 0; + } + return err; +} + +const struct got_error * got_privsep_send_commit_traversal_request(struct imsgbuf *ibuf, struct got_object_id *id, int idx, const char *path) { @@ -2008,6 +2210,7 @@ got_privsep_unveil_exec_helpers(void) GOT_PATH_PROG_READ_BLOB, GOT_PATH_PROG_READ_TAG, GOT_PATH_PROG_READ_GITCONFIG, + GOT_PATH_PROG_READ_GOTCONFIG, GOT_PATH_PROG_FETCH_PACK, GOT_PATH_PROG_INDEX_PACK, }; blob - b8408a749c9e5ff2c1983eb5d006ed171fdfb6e8 blob + 794725948774f714efc8876112a3a0da332e47be --- lib/repository.c +++ lib/repository.c @@ -159,6 +159,12 @@ got_repo_get_path_gitconfig(struct got_repository *rep 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); +} + void got_repo_get_gitconfig_remotes(int *nremotes, struct got_remote_repo **remotes, struct got_repository *repo) @@ -167,6 +173,20 @@ got_repo_get_gitconfig_remotes(int *nremotes, struct g *remotes = repo->gitconfig_remotes; } +const char * +got_repo_get_gotconfig_author(struct got_repository *repo) +{ + return repo->gotconfig_author; +} + +void +got_repo_get_gotconfig_remotes(int *nremotes, struct got_remote_repo **remotes, + struct got_repository *repo) +{ + *nremotes = repo->ngotconfig_remotes; + *remotes = repo->gotconfig_remotes; +} + static int is_git_repo(struct got_repository *repo) { @@ -516,6 +536,124 @@ done: return err; } +static const struct got_error * +parse_gotconfig_file(char **author, + struct got_remote_repo **remotes, int *nremotes, + 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; + + if (author) + *author = NULL; + if (remotes) + *remotes = NULL; + if (nremotes) + *nremotes = 0; + + fd = open(gotconfig_path, O_RDONLY); + if (fd == -1) { + if (errno == ENOENT) + return NULL; + return got_error_from_errno2("open", gotconfig_path); + } + + ibuf = calloc(1, sizeof(*ibuf)); + if (ibuf == NULL) { + err = got_error_from_errno("calloc"); + 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 done; + } + imsg_fds[1] = -1; + imsg_init(ibuf, imsg_fds[0]); + + err = got_privsep_send_gotconfig_parse_req(ibuf, fd); + if (err) + goto done; + fd = -1; + + if (author) { + err = got_privsep_send_gotconfig_author_req(ibuf); + if (err) + goto done; + + err = got_privsep_recv_gotconfig_str(author, ibuf); + if (err) + goto done; + } + + if (remotes && nremotes) { + err = got_privsep_send_gotconfig_remotes_req(ibuf); + if (err) + goto done; + + err = got_privsep_recv_gotconfig_remotes(remotes, + nremotes, ibuf); + if (err) + goto done; + } + + imsg_clear(ibuf); + err = 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) { + if (author) { + free(*author); + *author = NULL; + } + } + free(ibuf); + 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 = parse_gotconfig_file(&repo->gotconfig_author, + &repo->gotconfig_remotes, &repo->ngotconfig_remotes, + gotconfig_path); + free(gotconfig_path); + return err; +} + const struct got_error * got_repo_open(struct got_repository **repop, const char *path, const char *global_gitconfig_path) @@ -589,6 +727,10 @@ got_repo_open(struct got_repository **repop, const cha } } while (path); + err = read_gotconfig(repo); + if (err) + goto done; + err = read_gitconfig(repo, global_gitconfig_path); if (err) goto done; @@ -644,6 +786,12 @@ got_repo_close(struct got_repository *repo) err = got_error_from_errno("close"); } + free(repo->gotconfig_author); + for (i = 0; i < repo->ngotconfig_remotes; i++) { + free(repo->gotconfig_remotes[i].name); + free(repo->gotconfig_remotes[i].url); + } + free(repo->gotconfig_remotes); free(repo->gitconfig_author_name); free(repo->gitconfig_author_email); for (i = 0; i < repo->ngitconfig_remotes; i++) { blob - 41c5a86903979f5cc3495303157b082ea384ee7f blob + 1e55c9808beb7f0984445d4aae36eaddd0ceb5d4 --- libexec/Makefile +++ libexec/Makefile @@ -1,5 +1,5 @@ SUBDIR = got-read-blob got-read-commit got-read-object got-read-tree \ got-read-tag got-fetch-pack got-index-pack got-read-pack \ - got-read-gitconfig + got-read-gitconfig got-read-gotconfig .include blob - /dev/null blob + a683cf2ee1b6cf0753a4a7f2b006bfc29f853a8c (mode 644) --- /dev/null +++ libexec/got-read-gotconfig/Makefile @@ -0,0 +1,13 @@ +.PATH:${.CURDIR}/../../lib + +.include "../../got-version.mk" + +PROG= got-read-gotconfig +SRCS= got-read-gotconfig.c error.c inflate.c object_parse.c \ + path.c privsep.c sha1.c parse.y + +CPPFLAGS = -I${.CURDIR}/../../include -I${.CURDIR}/../../lib -I${.CURDIR} +LDADD = -lutil -lz +DPADD = ${LIBZ} ${LIBUTIL} + +.include blob - /dev/null blob + ff7e52b3c971f60ef8eea16eaa9fb51f78f337bb (mode 644) --- /dev/null +++ libexec/got-read-gotconfig/got-read-gotconfig.c @@ -0,0 +1,358 @@ +/* + * 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 + +#include "got_error.h" +#include "got_object.h" +#include "got_repository.h" + +#include "got_lib_delta.h" +#include "got_lib_object.h" +#include "got_lib_privsep.h" + +#include "gotconfig.h" + +/* parse.y */ +static volatile sig_atomic_t sigint_received; + +static void +catch_sigint(int signo) +{ + sigint_received = 1; +} + +static const struct got_error * +make_repo_url(char **url, struct gotconfig_remote_repo *repo) +{ + const struct got_error *err = NULL; + char *s = NULL, *p = NULL; + + *url = NULL; + + if (asprintf(&s, "%s://", repo->protocol) == -1) + return got_error_from_errno("asprintf"); + + if (repo->server) { + p = s; + s = NULL; + if (asprintf(&s, "%s%s", p, repo->server) == -1) { + err = got_error_from_errno("asprintf"); + goto done; + } + free(p); + p = NULL; + } + + if (repo->port) { + p = s; + s = NULL; + if (asprintf(&s, "%s:%d", p, repo->port) == -1) { + err = got_error_from_errno("asprintf"); + goto done; + } + free(p); + p = NULL; + } + + if (repo->repository) { + p = s; + s = NULL; + if (asprintf(&s, "%s/%s", p, repo->repository) == -1) { + err = got_error_from_errno("asprintf"); + goto done; + } + free(p); + p = NULL; + } +done: + if (err) { + free(s); + free(p); + } else + *url = s; + return err; +} + +static const struct got_error * +send_gotconfig_str(struct imsgbuf *ibuf, const char *value) +{ + size_t len = value ? strlen(value) + 1 : 0; + + if (imsg_compose(ibuf, GOT_IMSG_GOTCONFIG_STR_VAL, 0, 0, -1, + value, len) == -1) + return got_error_from_errno("imsg_compose GOTCONFIG_STR_VAL"); + + return got_privsep_flush_imsg(ibuf); +} + +static const struct got_error * +send_gotconfig_remotes(struct imsgbuf *ibuf, + struct gotconfig_remote_repo_list *remotes, int nremotes) +{ + const struct got_error *err = NULL; + struct got_imsg_remotes iremotes; + struct gotconfig_remote_repo *repo; + char *url = NULL; + + iremotes.nremotes = nremotes; + if (imsg_compose(ibuf, GOT_IMSG_GOTCONFIG_REMOTES, 0, 0, -1, + &iremotes, sizeof(iremotes)) == -1) + return got_error_from_errno("imsg_compose GOTCONFIG_REMOTES"); + + err = got_privsep_flush_imsg(ibuf); + imsg_clear(ibuf); + if (err) + return err; + + TAILQ_FOREACH(repo, remotes, entry) { + struct got_imsg_remote iremote; + size_t len = sizeof(iremote); + struct ibuf *wbuf; + + iremote.mirror_references = repo->mirror_references; + + iremote.name_len = strlen(repo->name); + len += iremote.name_len; + + err = make_repo_url(&url, repo); + if (err) + break; + iremote.url_len = strlen(url); + len += iremote.url_len; + + wbuf = imsg_create(ibuf, GOT_IMSG_GOTCONFIG_REMOTE, 0, 0, len); + if (wbuf == NULL) { + err = got_error_from_errno( + "imsg_create GOTCONFIG_REMOTE"); + break; + } + + if (imsg_add(wbuf, &iremote, sizeof(iremote)) == -1) { + err = got_error_from_errno( + "imsg_add GOTCONFIG_REMOTE"); + ibuf_free(wbuf); + break; + } + + if (imsg_add(wbuf, repo->name, iremote.name_len) == -1) { + err = got_error_from_errno( + "imsg_add GOTCONFIG_REMOTE"); + ibuf_free(wbuf); + break; + } + if (imsg_add(wbuf, url, iremote.url_len) == -1) { + err = got_error_from_errno( + "imsg_add GOTCONFIG_REMOTE"); + ibuf_free(wbuf); + break; + } + + wbuf->fd = -1; + imsg_close(ibuf, wbuf); + err = got_privsep_flush_imsg(ibuf); + if (err) + break; + + free(url); + url = NULL; + } + + free(url); + return err; +} + +static const struct got_error * +validate_config(struct gotconfig *gotconfig) +{ + struct gotconfig_remote_repo *repo, *repo2; + static char msg[512]; + + TAILQ_FOREACH(repo, &gotconfig->remotes, entry) { + if (repo->name == NULL) { + return got_error_msg(GOT_ERR_PARSE_CONFIG, + "name required for remote repository"); + } + + TAILQ_FOREACH(repo2, &gotconfig->remotes, entry) { + if (repo == repo2 || + strcmp(repo->name, repo2->name) != 0) + continue; + snprintf(msg, sizeof(msg), + "duplicate remote repository name '%s'", + repo->name); + return got_error_msg(GOT_ERR_PARSE_CONFIG, msg); + } + + if (repo->server == NULL) { + snprintf(msg, sizeof(msg), + "server required for remote repository \"%s\"", + repo->name); + return got_error_msg(GOT_ERR_PARSE_CONFIG, msg); + } + + if (repo->protocol == NULL) { + snprintf(msg, sizeof(msg), + "protocol required for remote repository \"%s\"", + repo->name); + return got_error_msg(GOT_ERR_PARSE_CONFIG, msg); + } + if (strcmp(repo->protocol, "ssh") != 0 && + strcmp(repo->protocol, "git+ssh") != 0 && + strcmp(repo->protocol, "git") != 0) { + snprintf(msg, sizeof(msg),"unknown protocol \"%s\" " + "for remote repository \"%s\"", repo->protocol, + repo->name); + return got_error_msg(GOT_ERR_PARSE_CONFIG, msg); + } + + if (repo->repository == NULL) { + snprintf(msg, sizeof(msg), + "repository path required for remote " + "repository \"%s\"", repo->name); + return got_error_msg(GOT_ERR_PARSE_CONFIG, msg); + } + } + + return NULL; +} + +int +main(int argc, char *argv[]) +{ + const struct got_error *err = NULL; + struct imsgbuf ibuf; + struct gotconfig *gotconfig; + size_t datalen; + const char *filename = "got.conf"; +#if 0 + static int attached; + + while (!attached) + sleep(1); +#endif + signal(SIGINT, catch_sigint); + + imsg_init(&ibuf, GOT_IMSG_FD_CHILD); + +#ifndef PROFILE + /* revoke access to most system calls */ + if (pledge("stdio recvfd", NULL) == -1) { + err = got_error_from_errno("pledge"); + got_privsep_send_error(&ibuf, err); + return 1; + } +#endif + + if (argc > 1) + filename = argv[1]; + + for (;;) { + struct imsg imsg; + + memset(&imsg, 0, sizeof(imsg)); + imsg.fd = -1; + + if (sigint_received) { + err = got_error(GOT_ERR_CANCELLED); + break; + } + + err = got_privsep_recv_imsg(&imsg, &ibuf, 0); + if (err) { + if (err->code == GOT_ERR_PRIVSEP_PIPE) + err = NULL; + break; + } + + if (imsg.hdr.type == GOT_IMSG_STOP) + break; + + switch (imsg.hdr.type) { + case GOT_IMSG_GOTCONFIG_PARSE_REQUEST: + datalen = imsg.hdr.len - IMSG_HEADER_SIZE; + if (datalen != 0) { + err = got_error(GOT_ERR_PRIVSEP_LEN); + break; + } + if (imsg.fd == -1){ + err = got_error(GOT_ERR_PRIVSEP_NO_FD); + break; + } + + if (gotconfig) + gotconfig_free(gotconfig); + err = gotconfig_parse(&gotconfig, filename, &imsg.fd); + if (err) + break; + err = validate_config(gotconfig); + break; + case GOT_IMSG_GOTCONFIG_AUTHOR_REQUEST: + if (gotconfig == NULL) { + err = got_error(GOT_ERR_PRIVSEP_MSG); + break; + } + err = send_gotconfig_str(&ibuf, + gotconfig->author ? gotconfig->author : ""); + break; + case GOT_IMSG_GOTCONFIG_REMOTES_REQUEST: + if (gotconfig == NULL) { + err = got_error(GOT_ERR_PRIVSEP_MSG); + break; + } + err = send_gotconfig_remotes(&ibuf, + &gotconfig->remotes, gotconfig->nremotes); + break; + default: + err = got_error(GOT_ERR_PRIVSEP_MSG); + break; + } + + if (imsg.fd != -1) { + if (close(imsg.fd) == -1 && err == NULL) + err = got_error_from_errno("close"); + } + + imsg_free(&imsg); + if (err) + break; + } + + imsg_clear(&ibuf); + if (err) { + if (!sigint_received && err->code != GOT_ERR_PRIVSEP_PIPE) { + fprintf(stderr, "%s: %s\n", getprogname(), err->msg); + got_privsep_send_error(&ibuf, err); + } + } + if (close(GOT_IMSG_FD_CHILD) != 0 && err == NULL) + err = got_error_from_errno("close"); + return err ? 1 : 0; +} blob - /dev/null blob + ab55bd31f17ddbcdf0a483b21903f7e00a588c20 (mode 644) --- /dev/null +++ libexec/got-read-gotconfig/gotconfig.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020 Tracey Emery + * 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. + */ + +struct gotconfig_remote_repo { + TAILQ_ENTRY(gotconfig_remote_repo) entry; + char *name; + char *repository; + char *server; + char *protocol; + int port; + int mirror_references; +}; +TAILQ_HEAD(gotconfig_remote_repo_list, gotconfig_remote_repo); + +struct gotconfig { + char *author; + struct gotconfig_remote_repo_list remotes; + int nremotes; +}; + +/* + * Parse individual gotconfig repository files + */ +const struct got_error *gotconfig_parse(struct gotconfig **, const char *, + int *); +void gotconfig_free(struct gotconfig *); blob - /dev/null blob + ea6e5db706853103c807b6c22841c8aba8770b71 (mode 644) --- /dev/null +++ libexec/got-read-gotconfig/parse.y @@ -0,0 +1,762 @@ +/* + * Copyright (c) 2020 Tracey Emery + * Copyright (c) 2020 Stefan Sperling + * Copyright (c) 2004, 2005 Esben Norby + * Copyright (c) 2004 Ryan McBride + * Copyright (c) 2002, 2003, 2004 Henning Brauer + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. + * + * 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 +#include +#include +#include +#include + +#include "got_error.h" +#include "gotconfig.h" + +static struct file { + FILE *stream; + const char *name; + size_t ungetpos; + size_t ungetsize; + u_char *ungetbuf; + int eof_reached; + int lineno; +} *file; +static const struct got_error* newfile(struct file**, const char *, int *); +static void closefile(struct file *); +int yyparse(void); +int yylex(void); +int yyerror(const char *, ...) + __attribute__((__format__ (printf, 1, 2))) + __attribute__((__nonnull__ (1))); +int kw_cmp(const void *, const void *); +int lookup(char *); +int igetc(void); +int lgetc(int); +void lungetc(int); +int findeol(void); +static int parseport(char *, long long *); + +TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); +struct sym { + TAILQ_ENTRY(sym) entry; + int used; + int persist; + char *nam; + char *val; +}; + +int symset(const char *, const char *, int); +char *symget(const char *); + +static int atoul(char *, u_long *); + +static const struct got_error* gerror; +static struct gotconfig_remote_repo *remote; +static struct gotconfig gotconfig; +static const struct got_error* new_remote(struct gotconfig_remote_repo **); + +typedef struct { + union { + int64_t number; + char *string; + } v; + int lineno; +} YYSTYPE; + +%} + +%token ERROR +%token REMOTE REPOSITORY SERVER PORT PROTOCOL MIRROR_REFERENCES AUTHOR +%token STRING +%token NUMBER +%type boolean portplain +%type numberstring + +%% + +grammar : /* empty */ + | grammar '\n' + | grammar author '\n' + | grammar remote '\n' + ; +boolean : STRING { + if (strcasecmp($1, "true") == 0 || + strcasecmp($1, "yes") == 0) + $$ = 1; + else if (strcasecmp($1, "false") == 0 || + strcasecmp($1, "no") == 0) + $$ = 0; + else { + yyerror("invalid boolean value '%s'", $1); + free($1); + YYERROR; + } + free($1); + } + ; +numberstring : NUMBER { + char *s; + if (asprintf(&s, "%lld", $1) == -1) { + yyerror("string: asprintf"); + YYERROR; + } + $$ = s; + } + | STRING + ; +portplain : numberstring { + if (parseport($1, &$$) == -1) { + free($1); + YYERROR; + } + free($1); + } + ; +remoteopts2 : remoteopts2 remoteopts1 nl + | remoteopts1 optnl + ; +remoteopts1 : REPOSITORY STRING { + remote->repository = strdup($2); + if (remote->repository == NULL) { + free($2); + yyerror("strdup"); + YYERROR; + } + free($2); + } + | SERVER STRING { + remote->server = strdup($2); + if (remote->server == NULL) { + free($2); + yyerror("strdup"); + YYERROR; + } + free($2); + } + | PROTOCOL STRING { + remote->protocol = strdup($2); + if (remote->protocol == NULL) { + free($2); + yyerror("strdup"); + YYERROR; + } + free($2); + } + | MIRROR_REFERENCES boolean { + remote->mirror_references = $2; + } + | PORT portplain { + remote->port = $2; + } + ; +remote : REMOTE STRING { + static const struct got_error* error; + + error = new_remote(&remote); + if (error) { + free($2); + yyerror("%s", error->msg); + YYERROR; + } + remote->name = strdup($2); + if (remote->name == NULL) { + free($2); + yyerror("strdup"); + YYERROR; + } + free($2); + } '{' optnl remoteopts2 '}' { + TAILQ_INSERT_TAIL(&gotconfig.remotes, remote, entry); + gotconfig.nremotes++; + } + ; +author : AUTHOR STRING { + gotconfig.author = strdup($2); + if (gotconfig.author == NULL) { + free($2); + yyerror("strdup"); + YYERROR; + } + free($2); + } + ; +optnl : '\n' optnl + | /* empty */ + ; +nl : '\n' optnl + ; +%% + +struct keywords { + const char *k_name; + int k_val; +}; + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + char *msg; + char *err = NULL; + + va_start(ap, fmt); + if (vasprintf(&msg, fmt, ap) == -1) { + gerror = got_error_from_errno("vasprintf"); + return 0; + } + va_end(ap); + if (asprintf(&err, "%s: line %d: %s", file->name, yylval.lineno, + msg) == -1) { + gerror = got_error_from_errno("asprintf"); + return(0); + } + gerror = got_error_msg(GOT_ERR_PARSE_CONFIG, strdup(err)); + free(msg); + free(err); + return(0); +} +int +kw_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct keywords *)e)->k_name)); +} + +int +lookup(char *s) +{ + /* This has to be sorted always. */ + static const struct keywords keywords[] = { + {"author", AUTHOR}, + {"mirror-references", MIRROR_REFERENCES}, + {"port", PORT}, + {"protocol", PROTOCOL}, + {"remote", REMOTE}, + {"repository", REPOSITORY}, + {"server", SERVER}, + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) + return (p->k_val); + else + return (STRING); +} + +#define START_EXPAND 1 +#define DONE_EXPAND 2 + +static int expanding; + +int +igetc(void) +{ + int c; + + while (1) { + if (file->ungetpos > 0) + c = file->ungetbuf[--file->ungetpos]; + else + c = getc(file->stream); + + if (c == START_EXPAND) + expanding = 1; + else if (c == DONE_EXPAND) + expanding = 0; + else + break; + } + return (c); +} + +int +lgetc(int quotec) +{ + int c, next; + + if (quotec) { + c = igetc(); + if (c == EOF) { + yyerror("reached end of file while parsing " + "quoted string"); + } + return (c); + } + + c = igetc(); + while (c == '\\') { + next = igetc(); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = file->lineno; + file->lineno++; + } + + return (c); +} + +void +lungetc(int c) +{ + if (c == EOF) + return; + + if (file->ungetpos >= file->ungetsize) { + void *p = reallocarray(file->ungetbuf, file->ungetsize, 2); + if (p == NULL) + err(1, "%s", __func__); + file->ungetbuf = p; + file->ungetsize *= 2; + } + file->ungetbuf[file->ungetpos++] = c; +} + +int +findeol(void) +{ + int c; + + /* Skip to either EOF or the first real EOL. */ + while (1) { + c = lgetc(0); + if (c == '\n') { + file->lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} + +static long long +getservice(char *n) +{ + struct servent *s; + u_long ulval; + + if (atoul(n, &ulval) == 0) { + if (ulval > 65535) { + yyerror("illegal port value %lu", ulval); + return (-1); + } + return ulval; + } else { + s = getservbyname(n, "tcp"); + if (s == NULL) + s = getservbyname(n, "udp"); + if (s == NULL) { + yyerror("unknown port %s", n); + return (-1); + } + return (s->s_port); + } +} + +static int +parseport(char *port, long long *pn) +{ + if ((*pn = getservice(port)) == -1) { + *pn = 0LL; + return (-1); + } + return (0); +} + + +int +yylex(void) +{ + unsigned char buf[8096]; + unsigned char *p, *val; + int quotec, next, c; + int token; + +top: + p = buf; + c = lgetc(0); + while (c == ' ' || c == '\t') + c = lgetc(0); /* nothing */ + + yylval.lineno = file->lineno; + if (c == '#') { + c = lgetc(0); + while (c != '\n' && c != EOF) + c = lgetc(0); /* nothing */ + } + if (c == '$' && !expanding) { + while (1) { + c = lgetc(0); + if (c == EOF) + return (0); + + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + if (isalnum(c) || c == '_') { + *p++ = c; + continue; + } + *p = '\0'; + lungetc(c); + break; + } + val = symget(buf); + if (val == NULL) { + yyerror("macro '%s' not defined", buf); + return (findeol()); + } + p = val + strlen(val) - 1; + lungetc(DONE_EXPAND); + while (p >= val) { + lungetc(*p); + p--; + } + lungetc(START_EXPAND); + goto top; + } + + switch (c) { + case '\'': + case '"': + quotec = c; + while (1) { + c = lgetc(quotec); + if (c == EOF) + return (0); + if (c == '\n') { + file->lineno++; + continue; + } else if (c == '\\') { + next = lgetc(quotec); + if (next == EOF) + return (0); + if (next == quotec || c == ' ' || c == '\t') + c = next; + else if (next == '\n') { + file->lineno++; + continue; + } else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } else if (c == '\0') { + yyerror("syntax error"); + return (findeol()); + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + *p++ = c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + err(1, "%s", __func__); + return (STRING); + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + c = lgetc(0); + } while (c != EOF && isdigit(c)); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + const char *errstr = NULL; + + *p = '\0'; + yylval.v.number = strtonum(buf, LLONG_MIN, + LLONG_MAX, &errstr); + if (errstr) { + yyerror("\"%s\" invalid number: %s", + buf, errstr); + return (findeol()); + } + return (NUMBER); + } else { +nodigits: + while (p > buf + 1) + lungetc(*--p); + c = *--p; + if (c == '-') + return (c); + } + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && \ + x != '!' && x != '=' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_') { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + c = lgetc(0); + } while (c != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + token = lookup(buf); + if (token == STRING) { + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + err(1, "%s", __func__); + } + return (token); + } + if (c == '\n') { + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +static const struct got_error* +newfile(struct file **nfile, const char *filename, int *fd) +{ + const struct got_error* error = NULL; + + (*nfile) = calloc(1, sizeof(struct file)); + if ((*nfile) == NULL) + return got_error_from_errno("calloc"); + (*nfile)->stream = fdopen(*fd, "r"); + if ((*nfile)->stream == NULL) { + error = got_error_from_errno("fdopen"); + free((*nfile)); + return error; + } + *fd = -1; /* Stream owns the file descriptor now. */ + (*nfile)->name = filename; + (*nfile)->lineno = 1; + (*nfile)->ungetsize = 16; + (*nfile)->ungetbuf = malloc((*nfile)->ungetsize); + if ((*nfile)->ungetbuf == NULL) { + error = got_error_from_errno("malloc"); + fclose((*nfile)->stream); + free((*nfile)); + return error; + } + return NULL; +} + +static const struct got_error* +new_remote(struct gotconfig_remote_repo **remote) +{ + const struct got_error *error = NULL; + + *remote = calloc(1, sizeof(**remote)); + if (*remote == NULL) + error = got_error_from_errno("calloc"); + return error; +} + +static void +closefile(struct file *file) +{ + fclose(file->stream); + free(file->ungetbuf); + free(file); +} + +const struct got_error * +gotconfig_parse(struct gotconfig **conf, const char *filename, int *fd) +{ + const struct got_error *err = NULL; + struct sym *sym, *next; + + *conf = NULL; + + err = newfile(&file, filename, fd); + if (err) + return err; + + TAILQ_INIT(&gotconfig.remotes); + + yyparse(); + closefile(file); + + /* Free macros and check which have not been used. */ + TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) { + if (!sym->persist) { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + + if (gerror == NULL) + *conf = &gotconfig; + return gerror; +} + +void +gotconfig_free(struct gotconfig *conf) +{ + struct gotconfig_remote_repo *remote; + + free(conf->author); + while (!TAILQ_EMPTY(&conf->remotes)) { + remote = TAILQ_FIRST(&conf->remotes); + TAILQ_REMOVE(&conf->remotes, remote, entry); + free(remote->name); + free(remote->repository); + free(remote->server); + free(remote->protocol); + free(remote); + } +} + +int +symset(const char *nam, const char *val, int persist) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) { + if (strcmp(nam, sym->nam) == 0) + break; + } + + if (sym != NULL) { + if (sym->persist == 1) + return (0); + else { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + sym = calloc(1, sizeof(*sym)); + if (sym == NULL) + return (-1); + + sym->nam = strdup(nam); + if (sym->nam == NULL) { + free(sym); + return (-1); + } + sym->val = strdup(val); + if (sym->val == NULL) { + free(sym->nam); + free(sym); + return (-1); + } + sym->used = 0; + sym->persist = persist; + TAILQ_INSERT_TAIL(&symhead, sym, entry); + return (0); +} + +int +cmdline_symset(char *s) +{ + char *sym, *val; + int ret; + size_t len; + + val = strrchr(s, '='); + if (val == NULL) + return (-1); + + len = strlen(s) - strlen(val) + 1; + sym = malloc(len); + if (sym == NULL) + errx(1, "cmdline_symset: malloc"); + + strlcpy(sym, s, len); + + ret = symset(sym, val + 1, 1); + free(sym); + + return (ret); +} + +char * +symget(const char *nam) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) { + if (strcmp(nam, sym->nam) == 0) { + sym->used = 1; + return (sym->val); + } + } + return (NULL); +} + +static int +atoul(char *s, u_long *ulvalp) +{ + u_long ulval; + char *ep; + + errno = 0; + ulval = strtoul(s, &ep, 0); + if (s[0] == '\0' || *ep != '\0') + return (-1); + if (errno == ERANGE && ulval == ULONG_MAX) + return (-1); + *ulvalp = ulval; + return (0); +} blob - 34abaa95cade02c6d2fc7fe82a0cae32a089e962 blob + 13a4e58bcc3693cd4eb419089a8e29ea5e377524 --- regress/cmdline/commit.sh +++ regress/cmdline/commit.sh @@ -699,6 +699,43 @@ function test_commit_tree_entry_sorting { test_done "$testroot" "$ret" } +function test_commit_gotconfig_author { + local testroot=`test_init commit_gotconfig_author` + + got checkout $testroot/repo $testroot/wt > /dev/null + ret="$?" + if [ "$ret" != "0" ]; then + test_done "$testroot" "$ret" + return 1 + fi + echo 'author "Flan Luck "' \ + > $testroot/repo/.git/got.conf + + echo "modified alpha" > $testroot/wt/alpha + (cd $testroot/wt && got commit -m 'test gotconfig author' > /dev/null) + ret="$?" + if [ "$ret" != "0" ]; then + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/repo && got log -l1 | grep ^from: > $testroot/stdout) + ret="$?" + if [ "$ret" != "0" ]; then + test_done "$testroot" "$ret" + return 1 + fi + + echo "from: Flan Luck " \ + > $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + fi + test_done "$testroot" "$ret" +} + function test_commit_gitconfig_author { local testroot=`test_init commit_gitconfig_author` @@ -1297,6 +1334,7 @@ run_test test_commit_selected_paths run_test test_commit_outside_refs_heads run_test test_commit_no_email run_test test_commit_tree_entry_sorting +run_test test_commit_gotconfig_author run_test test_commit_gitconfig_author run_test test_commit_xbit_change run_test test_commit_normalizes_filemodes