From: Tracey Emery Subject: Re: add got.conf(5) To: Stefan Sperling Cc: gameoftrees@openbsd.org Date: Wed, 9 Sep 2020 15:58:00 -0600 On Sun, Sep 06, 2020 at 06:09:44PM +0200, Stefan Sperling wrote: > 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? Ok. There are some whitespace problems pointed out below, but those can be handled after the commit. Also, this diff doesn't take care of removing got/parse.y. Were you going to handle that after the commit? > > 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; > + } > + } extra end tab > > 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)); > + extra tab > + 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); > + } > + } extra tab > + > + 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 4x spaces > + ; > +remoteopts1 : REPOSITORY STRING { > + remote->repository = strdup($2); 4x spaces > + if (remote->repository == NULL) { > + free($2); > + yyerror("strdup"); > + YYERROR; > + } > + free($2); > + } 4x spaces > + | SERVER STRING { 4x spaces > + remote->server = strdup($2); 4x spaces > + if (remote->server == NULL) { > + free($2); > + yyerror("strdup"); > + YYERROR; > + } > + free($2); > + } > + | PROTOCOL STRING { > + remote->protocol = strdup($2); 4x spaces > + if (remote->protocol == NULL) { > + free($2); > + yyerror("strdup"); > + YYERROR; > + } > + free($2); > + } > + | MIRROR_REFERENCES boolean { > + remote->mirror_references = $2; > + } > + | PORT portplain { > + remote->port = $2; > + } > + ; 4x spaces > +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); 4x spaces > + 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 -- Tracey Emery