Re: [PATCH 2/2] Remember and use GIT_EXEC_PATH on exec()'s

Subsystems: kernel build + files below scripts/ (unless maintained elsewhere), the rest

10 messages, 4 authors, 2016-06-15 · open the first message on its own page

Re: [PATCH 2/2] Remember and use GIT_EXEC_PATH on exec()'s

From: Michal Ostrowski <hidden>
Date: 2016-06-15 22:42:16

On Tue, 2006-01-10 at 11:47 -0800, Junio C Hamano wrote:

quoted
No. git prepends whatever was passed in --exec-path=/some/path, what's
found in $GIT_EXEC_PATH or the GIT_EXEC_PATH pre-processor macro set
at compile-time, in that order of preference. There's a very big
difference.
True.
quoted
Good point. Perhaps we should only prepend to path when the directory
isn't already in $PATH, or append rather than prepend.
I think appending not prepending would stop letting me say

 $ GIT_EXEC_PATH=/usr/libexec/git-core/0.99.9k git foo

to try out older version, if I have more recent git in my PATH.
But I agree with Michal it is not nice to affect invocations of
"diff" (and things spawned from hooks, which would inherit PATH
from receive-pack).
How about searching for executables in the following places, and in this
order:

1. --exec-path setting, if any
2. GIT_EXEC_PATH env var, if set
3. PATH (never modified)
4. Value of ${bindir} at build time


To accomplish this I'd suggest reworking the logic the git c programs
use to exec another git program.  The patch I sent out this morning
attempts to do this.  (I'll append again to avoid confusion with
previous patches.)  But note that the patch below doesn't implement this
policy above; the point is that it provides for a single location where
it has to be implemented (in exec_cmd.c).  I'd be happy to clean it up
and submit it formally as a clean-up patch (one that doesn't change the
PATH/GIT_EXEC_PATH search behavior currently implemented).

Secondly, the shell scripts as is cannot utilize this search order as
long as they don't religiously use the git potty internally.  If we were
to "sed -e 's/git-/git /g' -i git*.sh" (grotesquely simplified of
course) then they would. 



-- 
Michal Ostrowski [off-list ref]
diff --git a/Makefile b/Makefile
index c9c15b5..db57858 100644
--- a/Makefile
+++ b/Makefile
@@ -173,7 +173,7 @@ DIFF_OBJS = \
 
 LIB_OBJS = \
 	blob.o commit.o connect.o count-delta.o csum-file.o \
-	date.o diff-delta.o entry.o ident.o index.o \
+	date.o diff-delta.o entry.o exec_cmd.o ident.o index.o \
 	object.o pack-check.o patch-delta.o path.o pkt-line.o \
 	quote.o read-cache.o refs.o run-command.o \
 	server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
@@ -184,6 +184,16 @@ LIB_OBJS = \
 LIBS = $(LIB_FILE)
 LIBS += -lz
 
+
+# .exec_cmd.bindir stores $(bindir) used to compile exec_cmd.o
+# If it has changed, store the new value and force exec_cmd.o to be
rebuilt
+ifneq ($(shell cat .exec_cmd.bindir 2>/dev/null),$(bindir))
+.PHONY: exec_cmd.c
+$(shell echo $(bindir) > .exec_cmd.bindir)
+endif
+
+exec_cmd.o: CFLAGS+=-DGIT_EXEC_PATH=\"$(bindir)\"
+
 # Shell quote;
 # Result of this needs to be placed inside ''
 shq = $(subst ','\'',$(1))
diff --git a/daemon.c b/daemon.c
index 3bd1426..ab793bd 100644
--- a/daemon.c
+++ b/daemon.c
@@ -9,6 +9,7 @@
 #include <syslog.h>
 #include "pkt-line.h"
 #include "cache.h"
+#include "exec_cmd.h"
 
 static int log_syslog;
 static int verbose;
@@ -227,7 +228,7 @@ static int upload(char *dir)
 	snprintf(timeout_buf, sizeof timeout_buf, "--timeout=%u", timeout);
 
 	/* git-upload-pack only ever reads stuff, so this is safe */
-	execlp("git-upload-pack", "git-upload-pack", "--strict", timeout_buf,
".", NULL);
+	exec_git_cmd("upload-pack", "--strict", timeout_buf, ".");
 	return -1;
 }
 
diff --git a/fetch-clone.c b/fetch-clone.c
index f46fe6e..5ae9bda 100644
--- a/fetch-clone.c
+++ b/fetch-clone.c
@@ -1,4 +1,5 @@
 #include "cache.h"
+#include "exec_cmd.h"
 #include <sys/wait.h>
 
 static int finish_pack(const char *pack_tmp_name, const char *me)
@@ -27,8 +28,7 @@ static int finish_pack(const char *pack_
 		dup2(pipe_fd[1], 1);
 		close(pipe_fd[0]);
 		close(pipe_fd[1]);
-		execlp("git-index-pack","git-index-pack",
-		       "-o", idx, pack_tmp_name, NULL);
+		exec_git_cmd("index-pack", "-o", idx, pack_tmp_name);
 		error("cannot exec git-index-pack <%s> <%s>",
 		      idx, pack_tmp_name);
 		exit(1);
@@ -105,8 +105,7 @@ int receive_unpack_pack(int fd[2], const
 		dup2(fd[0], 0);
 		close(fd[0]);
 		close(fd[1]);
-		execlp("git-unpack-objects", "git-unpack-objects",
-		       quiet ? "-q" : NULL, NULL);
+		exec_git_cmd("unpack-objects", quiet ? "-q" : NULL);
 		die("git-unpack-objects exec failed");
 	}
 	close(fd[0]);
diff --git a/git.c b/git.c
index 5e7da74..a45dc54 100644
--- a/git.c
+++ b/git.c
@@ -10,6 +10,7 @@
 #include <stdarg.h>
 #include <sys/ioctl.h>
 #include "git-compat-util.h"
+#include "exec_cmd.h"
 
 #ifndef PATH_MAX
 # define PATH_MAX 4096
@@ -192,25 +193,6 @@ static void cmd_usage(const char *exec_p
 	exit(1);
 }
 
-static void prepend_to_path(const char *dir, int len)
-{
-	char *path, *old_path = getenv("PATH");
-	int path_len = len;
-
-	if (!old_path)
-		old_path = "/usr/local/bin:/usr/bin:/bin";
-
-	path_len = len + strlen(old_path) + 1;
-
-	path = malloc(path_len + 1);
-
-	memcpy(path, dir, len);
-	path[len] = ':';
-	memcpy(path + len + 1, old_path, path_len - len);
-
-	setenv("PATH", path, 1);
-}
-
 static void show_man_page(char *git_cmd)
 {
 	char *page;
@@ -233,14 +215,10 @@ int main(int argc, char **argv, char **e
 {
 	char git_command[PATH_MAX + 1];
 	char wd[PATH_MAX + 1];
-	int i, len, show_help = 0;
-	char *exec_path = getenv("GIT_EXEC_PATH");
+	int i, show_help = 0;
 
 	getcwd(wd, PATH_MAX);
 
-	if (!exec_path)
-		exec_path = GIT_EXEC_PATH;
-
 	for (i = 1; i < argc; i++) {
 		char *arg = argv[i];
 
@@ -257,9 +235,9 @@ int main(int argc, char **argv, char **e
 		if (!strncmp(arg, "exec-path", 9)) {
 			arg += 9;
 			if (*arg == '=')
-				exec_path = arg + 1;
+				git_set_exec_path(arg + 1);
 			else {
-				puts(exec_path);
+				puts(git_exec_path());
 				exit(0);
 			}
 		}
@@ -275,48 +253,18 @@ int main(int argc, char **argv, char **e
 
 	if (i >= argc || show_help) {
 		if (i >= argc)
-			cmd_usage(exec_path, NULL);
+			cmd_usage(git_exec_path(), NULL);
 
 		show_man_page(argv[i]);
 	}
 
-	if (*exec_path != '/') {
-		if (!getcwd(git_command, sizeof(git_command))) {
-			fprintf(stderr,
-				"git: cannot determine current directory\n");
-			exit(1);
-		}
-		len = strlen(git_command);
-
-		/* Trivial cleanup */
-		while (!strncmp(exec_path, "./", 2)) {
-			exec_path += 2;
-			while (*exec_path == '/')
-				exec_path++;
-		}
-		snprintf(git_command + len, sizeof(git_command) - len,
-			 "/%s", exec_path);
-	}
-	else
-		strcpy(git_command, exec_path);
-	len = strlen(git_command);
-	prepend_to_path(git_command, len);
-
-	len += snprintf(git_command + len, sizeof(git_command) - len,
-			"/git-%s", argv[i]);
-	if (sizeof(git_command) <= len) {
-		fprintf(stderr, "git: command name given is too long.\n");
-		exit(1);
-	}
-
-	/* execve() can only ever return if it fails */
-	execve(git_command, &argv[i], envp);
+	execv_git_cmd(argv + i);
 
 	if (errno == ENOENT)
-		cmd_usage(exec_path, "'%s' is not a git-command", argv[i]);
+		cmd_usage(git_exec_path(), "'%s' is not a git-command",
+			  argv[i]);
 
 	fprintf(stderr, "Failed to run command '%s': %s\n",
 		git_command, strerror(errno));
-
 	return 1;
 }
diff --git a/receive-pack.c b/receive-pack.c
index f847ec2..8e78e32 100644
--- a/receive-pack.c
+++ b/receive-pack.c
@@ -257,7 +257,7 @@ static void read_head_info(void)
 
 static const char *unpack(int *error_code)
 {
-	int code = run_command(unpacker, NULL);
+	int code = run_command_v_opt(1, &unpacker, RUN_GIT_CMD);
 
 	*error_code = 0;
 	switch (code) {
diff --git a/run-command.c b/run-command.c
index 8bf5922..b3d287e 100644
--- a/run-command.c
+++ b/run-command.c
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "run-command.h"
 #include <sys/wait.h>
+#include "exec_cmd.h"
 
 int run_command_v_opt(int argc, char **argv, int flags)
 {
@@ -13,9 +14,13 @@ int run_command_v_opt(int argc, char **a
 			int fd = open("/dev/null", O_RDWR);
 			dup2(fd, 0);
 			dup2(fd, 1);
-			close(fd);			
+			close(fd);
+		}
+		if (flags & RUN_GIT_CMD) {
+			execv_git_cmd(argv);
+		} else {
+			execvp(argv[0], (char *const*) argv);
 		}
-		execvp(argv[0], (char *const*) argv);
 		die("exec %s failed.", argv[0]);
 	}
 	for (;;) {
diff --git a/run-command.h b/run-command.h
index 2469eea..ef3ee05 100644
--- a/run-command.h
+++ b/run-command.h
@@ -12,7 +12,7 @@ enum {
 };
 
 #define RUN_COMMAND_NO_STDIO 1
-
+#define RUN_GIT_CMD	     2	/*If this is to be git sub-command */
 int run_command_v_opt(int argc, char **argv, int opt);
 int run_command_v(int argc, char **argv);
 int run_command(const char *cmd, ...);
diff --git a/shell.c b/shell.c
index cd31618..0d4891f 100644
--- a/shell.c
+++ b/shell.c
@@ -12,7 +12,7 @@ static int do_generic_cmd(const char *me
 	my_argv[1] = arg;
 	my_argv[2] = NULL;
 
-	return execvp(me, (char**) my_argv);
+	return execv_git_cmd((char**) my_argv);
 }
 
 static struct commands {
diff --git a/upload-pack.c b/upload-pack.c
index 1834b6b..6602d68 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -4,6 +4,7 @@
 #include "tag.h"
 #include "object.h"
 #include "commit.h"
+#include "exec_cmd.h"
 
 static const char upload_pack_usage[] = "git-upload-pack [--strict]
[--timeout=nn] <dir>";
 
@@ -60,7 +61,7 @@ static void create_pack_file(void)
 		close(0);
 		close(fd[0]);
 		close(fd[1]);
-		*p++ = "git-rev-list";
+		*p++ = "rev-list";
 		*p++ = "--objects";
 		if (create_full_pack || MAX_NEEDS <= nr_needs)
 			*p++ = "--all";
@@ -79,13 +80,13 @@ static void create_pack_file(void)
 				buf += 41;
 			}
 		*p++ = NULL;
-		execvp("git-rev-list", argv);
+		execv_git_cmd(argv);
 		die("git-upload-pack: unable to exec git-rev-list");
 	}
 	dup2(fd[0], 0);
 	close(fd[0]);
 	close(fd[1]);
-	execlp("git-pack-objects", "git-pack-objects", "--stdout", NULL);
+	exec_git_cmd("pack-objects", "--stdout");
 	die("git-upload-pack: unable to exec git-pack-objects");
 }
 

Re: [PATCH 2/2] Remember and use GIT_EXEC_PATH on exec()'s

From: Andreas Ericsson <hidden>
Date: 2016-06-15 22:42:16

Michal Ostrowski wrote:
On Tue, 2006-01-10 at 11:47 -0800, Junio C Hamano wrote:
quoted
quoted
Good point. Perhaps we should only prepend to path when the directory
isn't already in $PATH, or append rather than prepend.
I think appending not prepending would stop letting me say

$ GIT_EXEC_PATH=/usr/libexec/git-core/0.99.9k git foo

to try out older version, if I have more recent git in my PATH.
But I agree with Michal it is not nice to affect invocations of
"diff" (and things spawned from hooks, which would inherit PATH
from receive-pack).
So how about prepending only when the directory isn't already in the 
PATH? That can be done with a two-line patch to git.c only.

It will break the "diff in another dir" scenario in the highly unlikely 
event that the other diff is located in the same dir as the git suite, 
isn't supposed to be used, and the directory in question isn't in $PATH 
already. People who have such a setup will be too ashamed to admit it, 
so we're not likely to be blamed for it either. ;)
quoted

How about searching for executables in the following places, and in this
order:

1. --exec-path setting, if any
2. GIT_EXEC_PATH env var, if set
3. PATH (never modified)
4. Value of ${bindir} at build time
This is more or less what's done today, with the exception that $PATH 
isn't searched and it throws an error immediately no matter where 
exec_path (the git.c variable) came from.

Adding $PATH to the search-pattern would be a simple matter of falling 
back to execvp() if execve(), but then we could end up with running 
programs from a different release while the user thinks he/she's 
specifically running 1.0.3... Tricky problem, really.
Secondly, the shell scripts as is cannot utilize this search order as
long as they don't religiously use the git potty internally.  If we were
to "sed -e 's/git-/git /g' -i git*.sh" (grotesquely simplified of
course) then they would. 
I think this has been done, but as it happened to be convenient. I'd 
prefer if the git potty could keep prepending the GIT_EXEC_PATH to the 
path, really. We're bound to run into setup-related problems otherwise, 
such as;

Alice writes a script that works fine for her and her friends, so she 
shares it freely with Bob and whoever else might be listening. Bob's 
git-tools aren't in the $PATH but he keeps the potty handy at all times. 
He can't always run it through the potty because in some code-paths 
Alice's script uses git-tools without going through the potty. Bob 
thinks Alice puts entropy in her programs on purpose, so Alice flees, 
sobbing and shaking in anger and betrayed trust. The two never speak again.

Luckily, Bruce Schneier shows up and saves the day.

-- 
Andreas Ericsson                   andreas.ericsson@op5.se
OP5 AB                             www.op5.se
Tel: +46 8-230225                  Fax: +46 8-230231

Re: [PATCH 2/2] Remember and use GIT_EXEC_PATH on exec()'s

From: Junio C Hamano <hidden>
Date: 2016-06-15 22:42:16

Michal Ostrowski [off-list ref] writes:
How about searching for executables in the following places, and in this
order:

1. --exec-path setting, if any
2. GIT_EXEC_PATH env var, if set
3. PATH (never modified)
4. Value of ${bindir} at build time
My preference is to first do this to the Makefile:

-- >8 --
diff --git a/Makefile b/Makefile
index 5817e86..b1e3055 100644
--- a/Makefile
+++ b/Makefile
@@ -71,6 +71,7 @@ ALL_LDFLAGS = $(LDFLAGS)
 
 prefix = $(HOME)
 bindir = $(prefix)/bin
+gitexecdir = $(prefix)/bin
 template_dir = $(prefix)/share/git-core/templates/
 GIT_PYTHON_DIR = $(prefix)/share/git-core/python
 # DESTDIR=
@@ -144,7 +145,7 @@ PROGRAMS = \
 	git-describe$X
 
 # what 'all' will build and 'install' will install.
-ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) git$X
+ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
 
 # Backward compatibility -- to be removed after 1.0
 PROGRAMS += git-ssh-pull$X git-ssh-push$X
@@ -368,13 +369,13 @@ LIB_OBJS += $(COMPAT_OBJS)
 export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir
 ### Build rules
 
-all: $(ALL_PROGRAMS)
+all: $(ALL_PROGRAMS) git$X
 
 all:
 	$(MAKE) -C templates
 
 git$X: git.c $(LIB_FILE)
-	$(CC) -DGIT_EXEC_PATH='"$(bindir)"' -DGIT_VERSION='"$(GIT_VERSION)"' \
+	$(CC) -DGIT_EXEC_PATH='"$(gitexecdir)"' -DGIT_VERSION='"$(GIT_VERSION)"' \
 		$(CFLAGS) $(COMPAT_CFLAGS) -o $@ $(filter %.c,$^) $(LIB_FILE)
 
 $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
@@ -470,7 +471,9 @@ check:
 
 install: all
 	$(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(bindir))
-	$(INSTALL) $(ALL_PROGRAMS) $(call shellquote,$(DESTDIR)$(bindir))
+	$(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(gitexecdir))
+	$(INSTALL) $(ALL_PROGRAMS) $(call shellquote,$(DESTDIR)$(gitexecdir))
+	$(INSTALL) git$X $(call shellquote,$(DESTDIR)$(bindir))
 	$(MAKE) -C templates install
 	$(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(GIT_PYTHON_DIR))
 	$(INSTALL) $(PYMODULES) $(call shellquote,$(DESTDIR)$(GIT_PYTHON_DIR))

-- 8< --
and then make the rule for git things:

 1. --exec-path
 2. GIT_EXEC_PATH environment
 3. $(gitexecdir)

in this order.  Non git things should just use $PATH without
looking at anything else --- as long as a hook script calls a git
wrapper (i.e. "git foo" not "git-foo") I think things should
work fine.
...  The patch I sent out this morning
attempts to do this.  (I'll append again to avoid confusion with
previous patches)...
+		if (flags & RUN_GIT_CMD) {
+			execv_git_cmd(argv);
+		} else {
+			execvp(argv[0], (char *const*) argv);
 		}
This bit sounds good, but if you were to go this route I'd
suggest to rename your exec_git_cmd() to execl_git_cmd() (and
terminate the vararg list with NULL) for naming consistency.
execv_git_cmd() is good---we do not *want* execvp_git_cmd(),
because we do not run git subcommand using PATH.

Re: [PATCH 2/2] Remember and use GIT_EXEC_PATH on exec()'s

From: Michal Ostrowski <hidden>
Date: 2016-06-15 22:42:16

On Tue, 2006-01-10 at 16:42 -0800, Junio C Hamano wrote:
Michal Ostrowski [off-list ref] writes:
quoted
How about searching for executables in the following places, and in this
order:

1. --exec-path setting, if any
2. GIT_EXEC_PATH env var, if set
3. PATH (never modified)
4. Value of ${bindir} at build time
and then make the rule for git things:

 1. --exec-path
 2. GIT_EXEC_PATH environment
 3. $(gitexecdir)

in this order.  Non git things should just use $PATH without
looking at anything else --- as long as a hook script calls a git
wrapper (i.e. "git foo" not "git-foo") I think things should
work fine.
The patch that follows implements your suggested search order and
includes suggested Makefile changes.

-- 
Michal Ostrowski [off-list ref]

[PATCH] Exec git programs without using PATH.

From: Michal Ostrowski <hidden>
Date: 2016-06-15 22:42:16

The git suite may not be in PATH (and thus programs such as
git-send-pack could not exec git-rev-list).  Thus there is a need for
logic that will locate these programs.  Modifying PATH is not
desirable as it result in behavior differing from the user's
intentions, as we may end up prepending "/usr/bin" to PATH.

- git C programs will use exec*_git_cmd() APIs to exec sub-commands.
- exec*_git_cmd() will execute a git program by searching for it in
  the following directories:
	1. --exec-path (as used by "git")
	2. The GIT_EXEC_PATH environment variable.
	3. $(gitexecdir) as set in Makefile (default value $(bindir)).
- git potty will modify PATH as before to enable shell scripts to  
  invoke "git-foo" commands.

Ideally, shell scripts should use the git potty to become independent of
PATH, and then modifying PATH will not be necessary.

Signed-off-by: Michal Ostrowski <redacted>

---

 Makefile       |   21 ++++++++--
 daemon.c       |    3 +
 exec_cmd.c     |  118
++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 exec_cmd.h     |   10 +++++
 fetch-clone.c  |    7 +--
 git.c          |   54 ++++++--------------------
 receive-pack.c |    2 -
 run-command.c  |    9 +++-
 run-command.h  |    2 -
 send-pack.c    |    8 ++--
 shell.c        |    2 -
 upload-pack.c  |    7 ++-
 12 files changed, 181 insertions(+), 62 deletions(-)
 create mode 100644 exec_cmd.c
 create mode 100644 exec_cmd.h

d6dbbb5b9b47c37f44c3494962b9fa534677729f
diff --git a/Makefile b/Makefile
index c9c15b5..912c223 100644
--- a/Makefile
+++ b/Makefile
@@ -68,6 +68,7 @@ ALL_LDFLAGS = $(LDFLAGS)
 
 prefix = $(HOME)
 bindir = $(prefix)/bin
+gitexecdir = $(prefix)/bin
 template_dir = $(prefix)/share/git-core/templates/
 GIT_PYTHON_DIR = $(prefix)/share/git-core/python
 # DESTDIR=
@@ -141,7 +142,7 @@ PROGRAMS = \
 	git-describe$X
 
 # what 'all' will build and 'install' will install.
-ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) git$X
+ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
 
 # Backward compatibility -- to be removed after 1.0
 PROGRAMS += git-ssh-pull$X git-ssh-push$X
@@ -173,7 +174,7 @@ DIFF_OBJS = \
 
 LIB_OBJS = \
 	blob.o commit.o connect.o count-delta.o csum-file.o \
-	date.o diff-delta.o entry.o ident.o index.o \
+	date.o diff-delta.o entry.o exec_cmd.o ident.o index.o \
 	object.o pack-check.o patch-delta.o path.o pkt-line.o \
 	quote.o read-cache.o refs.o run-command.o \
 	server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
@@ -184,6 +185,16 @@ LIB_OBJS = \
 LIBS = $(LIB_FILE)
 LIBS += -lz
 
+
+# .exec_cmd.gitexecdir stores $(gitexecir) used to compile exec_cmd.o
+# If it has changed, store the new value and force exec_cmd.o to be
rebuilt
+ifneq ($(shell cat .exec_cmd.gitexecdir 2>/dev/null),$(gitexecdir))
+.PHONY: exec_cmd.c
+$(shell echo $(gitexecdir) > .exec_cmd.gitexecdir)
+endif
+
+exec_cmd.o: CFLAGS+=-DGIT_EXEC_PATH=\"$(gitexecdir)\"
+
 # Shell quote;
 # Result of this needs to be placed inside ''
 shq = $(subst ','\'',$(1))
@@ -366,13 +377,13 @@ LIB_OBJS += $(COMPAT_OBJS)
 export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir
 ### Build rules
 
-all: $(ALL_PROGRAMS)
+all: $(ALL_PROGRAMS) git$X
 
 all:
 	$(MAKE) -C templates
 
 git$X: git.c $(LIB_FILE)
-	$(CC) -DGIT_EXEC_PATH='"$(bindir)"' -DGIT_VERSION='"$(GIT_VERSION)"' \
+	$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
 		$(CFLAGS) $(COMPAT_CFLAGS) -o $@ $(filter %.c,$^) $(LIB_FILE)
 
 $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
@@ -468,7 +479,9 @@ check:
 
 install: all
 	$(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(bindir))
+	$(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(gitexecdir))
 	$(INSTALL) $(ALL_PROGRAMS) $(call shellquote,$(DESTDIR)$(bindir))
+	$(INSTALL) git$X $(call shellquote,$(DESTDIR)$(gitexecdir))
 	$(MAKE) -C templates install
 	$(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(GIT_PYTHON_DIR))
 	$(INSTALL) $(PYMODULES) $(call shellquote,$(DESTDIR)$(GIT_PYTHON_DIR))
diff --git a/daemon.c b/daemon.c
index 3bd1426..bb014fa 100644
--- a/daemon.c
+++ b/daemon.c
@@ -9,6 +9,7 @@
 #include <syslog.h>
 #include "pkt-line.h"
 #include "cache.h"
+#include "exec_cmd.h"
 
 static int log_syslog;
 static int verbose;
@@ -227,7 +228,7 @@ static int upload(char *dir)
 	snprintf(timeout_buf, sizeof timeout_buf, "--timeout=%u", timeout);
 
 	/* git-upload-pack only ever reads stuff, so this is safe */
-	execlp("git-upload-pack", "git-upload-pack", "--strict", timeout_buf,
".", NULL);
+	execl_git_cmd("upload-pack", "--strict", timeout_buf, ".", NULL);
 	return -1;
 }
 
diff --git a/exec_cmd.c b/exec_cmd.c
new file mode 100644
index 0000000..a3bd40a
--- /dev/null
+++ b/exec_cmd.c
@@ -0,0 +1,118 @@
+#include "cache.h"
+#include "exec_cmd.h"
+#define MAX_ARGS	32
+
+extern char **environ;
+static const char *builtin_exec_path = GIT_EXEC_PATH;
+static const char *current_exec_path = NULL;
+
+void git_set_exec_path(const char *exec_path)
+{
+	current_exec_path = exec_path;
+}
+
+
+/* Returns the highest-priority, location to look for git programs. */
+const char *git_exec_path()
+{
+	const char *env;
+
+	if (current_exec_path)
+		return current_exec_path;
+
+	env = getenv("GIT_EXEC_PATH");
+	if (env) {
+		return env;
+	}
+
+	return builtin_exec_path;
+}
+
+
+int execv_git_cmd(char **argv)
+{
+	char git_command[PATH_MAX + 1];
+	char *tmp;
+	int len, err, i;
+	const char *paths[] = { current_exec_path,
+				getenv("GIT_EXEC_PATH"),
+				builtin_exec_path,
+				NULL };
+
+	for (i = 0; i < 4; ++i) {
+		const char *exec_dir = paths[i];
+		if (!exec_dir) continue;
+
+		if (*exec_dir != '/') {
+			if (!getcwd(git_command, sizeof(git_command))) {
+				fprintf(stderr, "git: cannot determine "
+					"current directory\n");
+				exit(1);
+			}
+			len = strlen(git_command);
+
+			/* Trivial cleanup */
+			while (!strncmp(exec_dir, "./", 2)) {
+				exec_dir += 2;
+				while (*exec_dir == '/')
+					exec_dir++;
+			}
+			snprintf(git_command + len, sizeof(git_command) - len,
+				 "/%s", exec_dir);
+		} else {
+			strcpy(git_command, exec_dir);
+		}
+
+		len = strlen(git_command);
+		len += snprintf(git_command + len, sizeof(git_command) - len,
+				"/git-%s", argv[0]);
+
+		if (sizeof(git_command) <= len) {
+			fprintf(stderr,
+				"git: command name given is too long.\n");
+			break;
+		}
+
+		/* argv[0] must be the git command, but the argv array
+		 * belongs to the caller, and my be reused in
+		 * subsequent loop iterations. Save argv[0] and
+		 * restore it on error.
+		 */
+
+		tmp = argv[0];
+		argv[0] = git_command;
+
+		/* execve() can only ever return if it fails */
+		execve(git_command, argv, environ);
+
+		err = errno;
+
+		argv[0] = tmp;
+	}
+	return -1;
+
+}
+
+
+int execl_git_cmd(char *cmd,...)
+{
+	int argc;
+	char *argv[MAX_ARGS + 1];
+	char *arg;
+	va_list param;
+
+	va_start(param, cmd);
+	argv[0] = cmd;
+	argc = 1;
+	while (argc < MAX_ARGS) {
+		arg = argv[argc++] = va_arg(param, char *);
+		if (!arg)
+			break;
+	}
+	va_end(param);
+	if (MAX_ARGS <= argc)
+		return error("too many args to run %s", cmd);
+
+	argv[argc] = NULL;
+	return execv_git_cmd(argv);
+}
diff --git a/exec_cmd.h b/exec_cmd.h
new file mode 100644
index 0000000..06d5ec3
--- /dev/null
+++ b/exec_cmd.h
@@ -0,0 +1,10 @@
+#ifndef __GIT_EXEC_CMD_H_
+#define __GIT_EXEC_CMD_H_
+
+extern void git_set_exec_path(const char *exec_path);
+extern const char* git_exec_path();
+extern int execv_git_cmd(char **argv); /* NULL terminated */
+extern int execl_git_cmd(char *cmd, ...);
+
+
+#endif /* __GIT_EXEC_CMD_H_ */
diff --git a/fetch-clone.c b/fetch-clone.c
index f46fe6e..859f400 100644
--- a/fetch-clone.c
+++ b/fetch-clone.c
@@ -1,4 +1,5 @@
 #include "cache.h"
+#include "exec_cmd.h"
 #include <sys/wait.h>
 
 static int finish_pack(const char *pack_tmp_name, const char *me)
@@ -27,8 +28,7 @@ static int finish_pack(const char *pack_
 		dup2(pipe_fd[1], 1);
 		close(pipe_fd[0]);
 		close(pipe_fd[1]);
-		execlp("git-index-pack","git-index-pack",
-		       "-o", idx, pack_tmp_name, NULL);
+		execl_git_cmd("index-pack", "-o", idx, pack_tmp_name, NULL);
 		error("cannot exec git-index-pack <%s> <%s>",
 		      idx, pack_tmp_name);
 		exit(1);
@@ -105,8 +105,7 @@ int receive_unpack_pack(int fd[2], const
 		dup2(fd[0], 0);
 		close(fd[0]);
 		close(fd[1]);
-		execlp("git-unpack-objects", "git-unpack-objects",
-		       quiet ? "-q" : NULL, NULL);
+		execl_git_cmd("unpack-objects", quiet ? "-q" : NULL, NULL);
 		die("git-unpack-objects exec failed");
 	}
 	close(fd[0]);
diff --git a/git.c b/git.c
index 5e7da74..fdd02ed 100644
--- a/git.c
+++ b/git.c
@@ -10,6 +10,7 @@
 #include <stdarg.h>
 #include <sys/ioctl.h>
 #include "git-compat-util.h"
+#include "exec_cmd.h"
 
 #ifndef PATH_MAX
 # define PATH_MAX 4096
@@ -233,14 +234,11 @@ int main(int argc, char **argv, char **e
 {
 	char git_command[PATH_MAX + 1];
 	char wd[PATH_MAX + 1];
-	int i, len, show_help = 0;
-	char *exec_path = getenv("GIT_EXEC_PATH");
+	int i, show_help = 0;
+	char *exec_path;
 
 	getcwd(wd, PATH_MAX);
 
-	if (!exec_path)
-		exec_path = GIT_EXEC_PATH;
-
 	for (i = 1; i < argc; i++) {
 		char *arg = argv[i];
 
@@ -256,10 +254,11 @@ int main(int argc, char **argv, char **e
 
 		if (!strncmp(arg, "exec-path", 9)) {
 			arg += 9;
-			if (*arg == '=')
+			if (*arg == '=') {
 				exec_path = arg + 1;
-			else {
-				puts(exec_path);
+				git_set_exec_path(exec_path);
+			} else {
+				puts(git_exec_path());
 				exit(0);
 			}
 		}
@@ -275,48 +274,21 @@ int main(int argc, char **argv, char **e
 
 	if (i >= argc || show_help) {
 		if (i >= argc)
-			cmd_usage(exec_path, NULL);
+			cmd_usage(git_exec_path(), NULL);
 
 		show_man_page(argv[i]);
 	}
 
-	if (*exec_path != '/') {
-		if (!getcwd(git_command, sizeof(git_command))) {
-			fprintf(stderr,
-				"git: cannot determine current directory\n");
-			exit(1);
-		}
-		len = strlen(git_command);
+	exec_path = git_exec_path();
+	prepend_to_path(exec_path, strlen(exec_path));
 
-		/* Trivial cleanup */
-		while (!strncmp(exec_path, "./", 2)) {
-			exec_path += 2;
-			while (*exec_path == '/')
-				exec_path++;
-		}
-		snprintf(git_command + len, sizeof(git_command) - len,
-			 "/%s", exec_path);
-	}
-	else
-		strcpy(git_command, exec_path);
-	len = strlen(git_command);
-	prepend_to_path(git_command, len);
-
-	len += snprintf(git_command + len, sizeof(git_command) - len,
-			"/git-%s", argv[i]);
-	if (sizeof(git_command) <= len) {
-		fprintf(stderr, "git: command name given is too long.\n");
-		exit(1);
-	}
-
-	/* execve() can only ever return if it fails */
-	execve(git_command, &argv[i], envp);
+	execv_git_cmd(argv + i);
 
 	if (errno == ENOENT)
-		cmd_usage(exec_path, "'%s' is not a git-command", argv[i]);
+		cmd_usage(git_exec_path(), "'%s' is not a git-command",
+			  argv[i]);
 
 	fprintf(stderr, "Failed to run command '%s': %s\n",
 		git_command, strerror(errno));
-
 	return 1;
 }
diff --git a/receive-pack.c b/receive-pack.c
index f847ec2..8e78e32 100644
--- a/receive-pack.c
+++ b/receive-pack.c
@@ -257,7 +257,7 @@ static void read_head_info(void)
 
 static const char *unpack(int *error_code)
 {
-	int code = run_command(unpacker, NULL);
+	int code = run_command_v_opt(1, &unpacker, RUN_GIT_CMD);
 
 	*error_code = 0;
 	switch (code) {
diff --git a/run-command.c b/run-command.c
index 8bf5922..b3d287e 100644
--- a/run-command.c
+++ b/run-command.c
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "run-command.h"
 #include <sys/wait.h>
+#include "exec_cmd.h"
 
 int run_command_v_opt(int argc, char **argv, int flags)
 {
@@ -13,9 +14,13 @@ int run_command_v_opt(int argc, char **a
 			int fd = open("/dev/null", O_RDWR);
 			dup2(fd, 0);
 			dup2(fd, 1);
-			close(fd);			
+			close(fd);
+		}
+		if (flags & RUN_GIT_CMD) {
+			execv_git_cmd(argv);
+		} else {
+			execvp(argv[0], (char *const*) argv);
 		}
-		execvp(argv[0], (char *const*) argv);
 		die("exec %s failed.", argv[0]);
 	}
 	for (;;) {
diff --git a/run-command.h b/run-command.h
index 2469eea..ef3ee05 100644
--- a/run-command.h
+++ b/run-command.h
@@ -12,7 +12,7 @@ enum {
 };
 
 #define RUN_COMMAND_NO_STDIO 1
-
+#define RUN_GIT_CMD	     2	/*If this is to be git sub-command */
 int run_command_v_opt(int argc, char **argv, int opt);
 int run_command_v(int argc, char **argv);
 int run_command(const char *cmd, ...);
diff --git a/send-pack.c b/send-pack.c
index cd36193..4a420a6 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -26,11 +26,11 @@ static int is_zero_sha1(const unsigned c
 static void exec_pack_objects(void)
 {
 	static char *args[] = {
-		"git-pack-objects",
+		"pack-objects",
 		"--stdout",
 		NULL
 	};
-	execvp("git-pack-objects", args);
+	execv_git_cmd(args);
 	die("git-pack-objects exec failed (%s)", strerror(errno));
 }
 
@@ -39,7 +39,7 @@ static void exec_rev_list(struct ref *re
 	static char *args[1000];
 	int i = 0;
 
-	args[i++] = "git-rev-list";	/* 0 */
+	args[i++] = "rev-list";	/* 0 */
 	args[i++] = "--objects";	/* 1 */
 	while (refs) {
 		char *buf = malloc(100);
@@ -58,7 +58,7 @@ static void exec_rev_list(struct ref *re
 		refs = refs->next;
 	}
 	args[i] = NULL;
-	execvp("git-rev-list", args);
+	execv_git_cmd(args);
 	die("git-rev-list exec failed (%s)", strerror(errno));
 }
 
diff --git a/shell.c b/shell.c
index cd31618..0d4891f 100644
--- a/shell.c
+++ b/shell.c
@@ -12,7 +12,7 @@ static int do_generic_cmd(const char *me
 	my_argv[1] = arg;
 	my_argv[2] = NULL;
 
-	return execvp(me, (char**) my_argv);
+	return execv_git_cmd((char**) my_argv);
 }
 
 static struct commands {
diff --git a/upload-pack.c b/upload-pack.c
index 1834b6b..d198055 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -4,6 +4,7 @@
 #include "tag.h"
 #include "object.h"
 #include "commit.h"
+#include "exec_cmd.h"
 
 static const char upload_pack_usage[] = "git-upload-pack [--strict]
[--timeout=nn] <dir>";
 
@@ -60,7 +61,7 @@ static void create_pack_file(void)
 		close(0);
 		close(fd[0]);
 		close(fd[1]);
-		*p++ = "git-rev-list";
+		*p++ = "rev-list";
 		*p++ = "--objects";
 		if (create_full_pack || MAX_NEEDS <= nr_needs)
 			*p++ = "--all";
@@ -79,13 +80,13 @@ static void create_pack_file(void)
 				buf += 41;
 			}
 		*p++ = NULL;
-		execvp("git-rev-list", argv);
+		execv_git_cmd(argv);
 		die("git-upload-pack: unable to exec git-rev-list");
 	}
 	dup2(fd[0], 0);
 	close(fd[0]);
 	close(fd[1]);
-	execlp("git-pack-objects", "git-pack-objects", "--stdout", NULL);
+	execl_git_cmd("pack-objects", "--stdout", NULL);
 	die("git-upload-pack: unable to exec git-pack-objects");
 }
 
-- 
0.99.9m-g5a22

Re: [PATCH] Exec git programs without using PATH.

From: Junio C Hamano <hidden>
Date: 2016-06-15 22:42:16

Michal Ostrowski [off-list ref] writes:
 exec_cmd.c     |  118
++++++++++++++++++++++++++++++++++++++++++++++++++++++++
This is a giveaway sign that your MUA is wrapping lines, so
please double-check before sending things out next time around.

It seems yours is not as bad as others I've seen; I could manage
to trivially hand-edit to make it appliable (although I am
inclined to ask you to polish it a bit further).
+ifneq ($(shell cat .exec_cmd.gitexecdir 2>/dev/null),$(gitexecdir))
+.PHONY: exec_cmd.c
+$(shell echo $(gitexecdir) > .exec_cmd.gitexecdir)
+endif
Although this check is much better than the last round, I changed
my mind about this part.  We do not do the equivalent checks and
rebuild when somebody once builds with NO_OPENSSL and then tries
to rebuild without, or for any other compile time configuration
(template_dir, for example).  Ideally we _could_ keep track of
all of them but I suspect that would be an overkill, so I am
inclined to drop this part.

We need to make sure that we clean .exec_cmd.gitexecdir on the
clean: target in the Makefile, if we _were_ to keep this, OTOH.
+exec_cmd.o: CFLAGS+=-DGIT_EXEC_PATH=\"$(gitexecdir)\"
Please do not override CFLAGS like this; it breaks this common
usage:

	$ make CFLAGS='-g -O1' exec_cmd.o
quoted hunk
diff --git a/exec_cmd.c b/exec_cmd.c
+void git_set_exec_path(const char *exec_path)
+{
+	current_exec_path = exec_path;
+}
I've always wondered what we should do when --exec-path is given
more than once, but you decided it for me.  I agree that
silently overwriting is just fine.
+	const char *paths[] = { current_exec_path,
+				getenv("GIT_EXEC_PATH"),
+				builtin_exec_path,
+				NULL };
I do not think you need the last NULL element in paths[] here,
given what the loop that uses this array does.
+	for (i = 0; i < 4; ++i) {
If your paths[] is fixed size, please do this instead:

	for (i = 0; sizeof(paths)/sizeof(paths[0]); i++)

Then I do not have to remember that I need to update "4" when I
update paths[].
quoted hunk
diff --git a/exec_cmd.h b/exec_cmd.h
+extern const char* git_exec_path();
ANSI C please: extern const char* git_exec_path(void);
quoted hunk
diff --git a/git.c b/git.c
 	if (errno == ENOENT)
-		cmd_usage(exec_path, "'%s' is not a git-command", argv[i]);
+		cmd_usage(git_exec_path(), "'%s' is not a git-command",
+			  argv[i]);
You have assigned to exec_path before the failed execv_git_cmd()
already; I think change in this line is unneeded.
quoted hunk
diff --git a/receive-pack.c b/receive-pack.c
index f847ec2..8e78e32 100644
--- a/receive-pack.c
+++ b/receive-pack.c
@@ -257,7 +257,7 @@ static void read_head_info(void)
 
 static const char *unpack(int *error_code)
 {
-	int code = run_command(unpacker, NULL);
+	int code = run_command_v_opt(1, &unpacker, RUN_GIT_CMD);
This one is questionable; run_command_v_opt() calls
execv_git_cmd() which expects the second argument of
run_command_v_opt() to be NULL terminated, doesn't it?
quoted hunk
diff --git a/shell.c b/shell.c
index cd31618..0d4891f 100644
--- a/shell.c
+++ b/shell.c
@@ -12,7 +12,7 @@ static int do_generic_cmd(const char *me
 	my_argv[1] = arg;
 	my_argv[2] = NULL;
 
-	return execvp(me, (char**) my_argv);
+	return execv_git_cmd((char**) my_argv);
 }
Here my_argv comes from cmd_list[] which has "git-" prefix to
their command name, but I thought your exec[vl]_git_cmd() took
command names without.

Here is my suggestion on top of your patch, summarizing all of
the above comments.

-- >8 --
diff --git a/Makefile b/Makefile
index e7c7763..6694627 100644
--- a/Makefile
+++ b/Makefile
@@ -185,16 +185,6 @@ LIB_OBJS = \
 LIBS = $(LIB_FILE)
 LIBS += -lz
 
-
-# .exec_cmd.gitexecdir stores $(gitexecir) used to compile exec_cmd.o
-# If it has changed, store the new value and force exec_cmd.o to be rebuilt
-ifneq ($(shell cat .exec_cmd.gitexecdir 2>/dev/null),$(gitexecdir))
-.PHONY: exec_cmd.c
-$(shell echo $(gitexecdir) > .exec_cmd.gitexecdir)
-endif
-
-exec_cmd.o: CFLAGS+=-DGIT_EXEC_PATH=\"$(gitexecdir)\"
-
 # Shell quote;
 # Result of this needs to be placed inside ''
 shq = $(subst ','\'',$(1))
@@ -423,6 +413,8 @@ git$X git.spec \
 %.o: %.S
 	$(CC) -o $*.o -c $(ALL_CFLAGS) $<
 
+exec_cmd.o: ALL_CFLAGS += -DGIT_EXEC_PATH=\"$(gitexecdir)\"
+
 git-%$X: %.o $(LIB_FILE)
 	$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
 
diff --git a/exec_cmd.c b/exec_cmd.c
index a3bd40a..55af33b 100644
--- a/exec_cmd.c
+++ b/exec_cmd.c
@@ -36,10 +36,9 @@ int execv_git_cmd(char **argv)
 	int len, err, i;
 	const char *paths[] = { current_exec_path,
 				getenv("GIT_EXEC_PATH"),
-				builtin_exec_path,
-				NULL };
+				builtin_exec_path };
 
-	for (i = 0; i < 4; ++i) {
+	for (i = 0; i < sizeof(paths)/sizeof(paths[0]); ++i) {
 		const char *exec_dir = paths[i];
 		if (!exec_dir) continue;
 
diff --git a/exec_cmd.h b/exec_cmd.h
index 06d5ec3..5150ee2 100644
--- a/exec_cmd.h
+++ b/exec_cmd.h
@@ -2,7 +2,7 @@
 #define __GIT_EXEC_CMD_H_
 
 extern void git_set_exec_path(const char *exec_path);
-extern const char* git_exec_path();
+extern const char* git_exec_path(void);
 extern int execv_git_cmd(char **argv); /* NULL terminated */
 extern int execl_git_cmd(char *cmd, ...);
 
diff --git a/git.c b/git.c
index fdd02ed..e3a0b59 100644
--- a/git.c
+++ b/git.c
@@ -285,10 +285,10 @@ int main(int argc, char **argv, char **e
 	execv_git_cmd(argv + i);
 
 	if (errno == ENOENT)
-		cmd_usage(git_exec_path(), "'%s' is not a git-command",
-			  argv[i]);
+		cmd_usage(exec_path, "'%s' is not a git-command", argv[i]);
 
 	fprintf(stderr, "Failed to run command '%s': %s\n",
 		git_command, strerror(errno));
+
 	return 1;
 }
diff --git a/receive-pack.c b/receive-pack.c
index 8e78e32..6120dbe 100644
--- a/receive-pack.c
+++ b/receive-pack.c
@@ -6,7 +6,7 @@
 
 static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
 
-static const char unpacker[] = "git-unpack-objects";
+static char *unpacker[] = { "git-unpack-objects", NULL };
 
 static int report_status = 0;
 
@@ -257,7 +257,7 @@ static void read_head_info(void)
 
 static const char *unpack(int *error_code)
 {
-	int code = run_command_v_opt(1, &unpacker, RUN_GIT_CMD);
+	int code = run_command_v_opt(1, unpacker, RUN_GIT_CMD);
 
 	*error_code = 0;
 	switch (code) {
diff --git a/shell.c b/shell.c
index 0d4891f..d40dfe4 100644
--- a/shell.c
+++ b/shell.c
@@ -7,8 +7,10 @@ static int do_generic_cmd(const char *me
 
 	if (!arg || !(arg = sq_dequote(arg)))
 		die("bad argument");
+	if (strncmp(me, "git-", 4))
+		die("bad command");
 
-	my_argv[0] = me;
+	my_argv[0] = me + 4;
 	my_argv[1] = arg;
 	my_argv[2] = NULL;
 

[PATCH] (Updated) Exec git programs without using PATH.

From: Michal Ostrowski <hidden>
Date: 2016-06-15 22:42:16


The git suite may not be in PATH (and thus programs such as
git-send-pack could not exec git-rev-list).  Thus there is a need for
logic that will locate these programs.  Modifying PATH is not
desirable as it result in behavior differing from the user's
intentions, as we may end up prepending "/usr/bin" to PATH.

- git C programs will use exec*_git_cmd() APIs to exec sub-commands.
- exec*_git_cmd() will execute a git program by searching for it in
  the following directories:
	1. --exec-path (as used by "git")
	2. The GIT_EXEC_PATH environment variable.
	3. $(gitexecdir) as set in Makefile (default value $(bindir)).
- all exec's of git programs will use exec*_git_cmd() interfaces,
- C programs will not modify PATH

If the git suite is not in PATH, C programs will function, but there
is no guarantee that shell scripts will, as shell scripts may invoke
"git-foo" rather than "git foo".  Shell scripts that are modified to
use the git potty should become independent of PATH.

Includes modifications by Junio C Hamano [off-list ref].

Signed-off-by: Michal Ostrowski <redacted>

---

 Makefile       |   13 ++++--
 daemon.c       |    3 +
 exec_cmd.c     |  117 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 exec_cmd.h     |   10 +++++
 fetch-clone.c  |    7 +--
 git.c          |   50 +++++-------------------
 receive-pack.c |    4 +-
 run-command.c  |    9 +++-
 run-command.h  |    2 -
 send-pack.c    |    8 ++--
 shell.c        |    6 ++-
 upload-pack.c  |    7 ++-
 12 files changed, 174 insertions(+), 62 deletions(-)
 create mode 100644 exec_cmd.c
 create mode 100644 exec_cmd.h

565e290d6e7f24bd4e72ca7494297230799d118a
diff --git a/Makefile b/Makefile
index c9c15b5..abcb771 100644
--- a/Makefile
+++ b/Makefile
@@ -68,6 +68,7 @@ ALL_LDFLAGS = $(LDFLAGS)
 
 prefix = $(HOME)
 bindir = $(prefix)/bin
+gitexecdir = $(prefix)/bin
 template_dir = $(prefix)/share/git-core/templates/
 GIT_PYTHON_DIR = $(prefix)/share/git-core/python
 # DESTDIR=
@@ -141,7 +142,7 @@ PROGRAMS = \
 	git-describe$X
 
 # what 'all' will build and 'install' will install.
-ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) git$X
+ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
 
 # Backward compatibility -- to be removed after 1.0
 PROGRAMS += git-ssh-pull$X git-ssh-push$X
@@ -173,7 +174,7 @@ DIFF_OBJS = \
 
 LIB_OBJS = \
 	blob.o commit.o connect.o count-delta.o csum-file.o \
-	date.o diff-delta.o entry.o ident.o index.o \
+	date.o diff-delta.o entry.o exec_cmd.o ident.o index.o \
 	object.o pack-check.o patch-delta.o path.o pkt-line.o \
 	quote.o read-cache.o refs.o run-command.o \
 	server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
@@ -366,13 +367,13 @@ LIB_OBJS += $(COMPAT_OBJS)
 export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir
 ### Build rules
 
-all: $(ALL_PROGRAMS)
+all: $(ALL_PROGRAMS) git$X
 
 all:
 	$(MAKE) -C templates
 
 git$X: git.c $(LIB_FILE)
-	$(CC) -DGIT_EXEC_PATH='"$(bindir)"' -DGIT_VERSION='"$(GIT_VERSION)"' \
+	$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
 		$(CFLAGS) $(COMPAT_CFLAGS) -o $@ $(filter %.c,$^) $(LIB_FILE)
 
 $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
@@ -412,6 +413,8 @@ git$X git.spec \
 %.o: %.S
 	$(CC) -o $*.o -c $(ALL_CFLAGS) $<
 
+exec_cmd.o: ALL_CFLAGS += -DGIT_EXEC_PATH=\"$(gitexecdir)\"
+
 git-%$X: %.o $(LIB_FILE)
 	$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
 
@@ -468,7 +471,9 @@ check:
 
 install: all
 	$(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(bindir))
+	$(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(gitexecdir))
 	$(INSTALL) $(ALL_PROGRAMS) $(call shellquote,$(DESTDIR)$(bindir))
+	$(INSTALL) git$X $(call shellquote,$(DESTDIR)$(gitexecdir))
 	$(MAKE) -C templates install
 	$(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(GIT_PYTHON_DIR))
 	$(INSTALL) $(PYMODULES) $(call shellquote,$(DESTDIR)$(GIT_PYTHON_DIR))
diff --git a/daemon.c b/daemon.c
index 3bd1426..bb014fa 100644
--- a/daemon.c
+++ b/daemon.c
@@ -9,6 +9,7 @@
 #include <syslog.h>
 #include "pkt-line.h"
 #include "cache.h"
+#include "exec_cmd.h"
 
 static int log_syslog;
 static int verbose;
@@ -227,7 +228,7 @@ static int upload(char *dir)
 	snprintf(timeout_buf, sizeof timeout_buf, "--timeout=%u", timeout);
 
 	/* git-upload-pack only ever reads stuff, so this is safe */
-	execlp("git-upload-pack", "git-upload-pack", "--strict", timeout_buf, ".", NULL);
+	execl_git_cmd("upload-pack", "--strict", timeout_buf, ".", NULL);
 	return -1;
 }
 
diff --git a/exec_cmd.c b/exec_cmd.c
new file mode 100644
index 0000000..55af33b
--- /dev/null
+++ b/exec_cmd.c
@@ -0,0 +1,117 @@
+#include "cache.h"
+#include "exec_cmd.h"
+#define MAX_ARGS	32
+
+extern char **environ;
+static const char *builtin_exec_path = GIT_EXEC_PATH;
+static const char *current_exec_path = NULL;
+
+void git_set_exec_path(const char *exec_path)
+{
+	current_exec_path = exec_path;
+}
+
+
+/* Returns the highest-priority, location to look for git programs. */
+const char *git_exec_path()
+{
+	const char *env;
+
+	if (current_exec_path)
+		return current_exec_path;
+
+	env = getenv("GIT_EXEC_PATH");
+	if (env) {
+		return env;
+	}
+
+	return builtin_exec_path;
+}
+
+
+int execv_git_cmd(char **argv)
+{
+	char git_command[PATH_MAX + 1];
+	char *tmp;
+	int len, err, i;
+	const char *paths[] = { current_exec_path,
+				getenv("GIT_EXEC_PATH"),
+				builtin_exec_path };
+
+	for (i = 0; i < sizeof(paths)/sizeof(paths[0]); ++i) {
+		const char *exec_dir = paths[i];
+		if (!exec_dir) continue;
+
+		if (*exec_dir != '/') {
+			if (!getcwd(git_command, sizeof(git_command))) {
+				fprintf(stderr, "git: cannot determine "
+					"current directory\n");
+				exit(1);
+			}
+			len = strlen(git_command);
+
+			/* Trivial cleanup */
+			while (!strncmp(exec_dir, "./", 2)) {
+				exec_dir += 2;
+				while (*exec_dir == '/')
+					exec_dir++;
+			}
+			snprintf(git_command + len, sizeof(git_command) - len,
+				 "/%s", exec_dir);
+		} else {
+			strcpy(git_command, exec_dir);
+		}
+
+		len = strlen(git_command);
+		len += snprintf(git_command + len, sizeof(git_command) - len,
+				"/git-%s", argv[0]);
+
+		if (sizeof(git_command) <= len) {
+			fprintf(stderr,
+				"git: command name given is too long.\n");
+			break;
+		}
+
+		/* argv[0] must be the git command, but the argv array
+		 * belongs to the caller, and my be reused in
+		 * subsequent loop iterations. Save argv[0] and
+		 * restore it on error.
+		 */
+
+		tmp = argv[0];
+		argv[0] = git_command;
+
+		/* execve() can only ever return if it fails */
+		execve(git_command, argv, environ);
+
+		err = errno;
+
+		argv[0] = tmp;
+	}
+	return -1;
+
+}
+
+
+int execl_git_cmd(char *cmd,...)
+{
+	int argc;
+	char *argv[MAX_ARGS + 1];
+	char *arg;
+	va_list param;
+
+	va_start(param, cmd);
+	argv[0] = cmd;
+	argc = 1;
+	while (argc < MAX_ARGS) {
+		arg = argv[argc++] = va_arg(param, char *);
+		if (!arg)
+			break;
+	}
+	va_end(param);
+	if (MAX_ARGS <= argc)
+		return error("too many args to run %s", cmd);
+
+	argv[argc] = NULL;
+	return execv_git_cmd(argv);
+}
diff --git a/exec_cmd.h b/exec_cmd.h
new file mode 100644
index 0000000..5150ee2
--- /dev/null
+++ b/exec_cmd.h
@@ -0,0 +1,10 @@
+#ifndef __GIT_EXEC_CMD_H_
+#define __GIT_EXEC_CMD_H_
+
+extern void git_set_exec_path(const char *exec_path);
+extern const char* git_exec_path(void);
+extern int execv_git_cmd(char **argv); /* NULL terminated */
+extern int execl_git_cmd(char *cmd, ...);
+
+
+#endif /* __GIT_EXEC_CMD_H_ */
diff --git a/fetch-clone.c b/fetch-clone.c
index f46fe6e..859f400 100644
--- a/fetch-clone.c
+++ b/fetch-clone.c
@@ -1,4 +1,5 @@
 #include "cache.h"
+#include "exec_cmd.h"
 #include <sys/wait.h>
 
 static int finish_pack(const char *pack_tmp_name, const char *me)
@@ -27,8 +28,7 @@ static int finish_pack(const char *pack_
 		dup2(pipe_fd[1], 1);
 		close(pipe_fd[0]);
 		close(pipe_fd[1]);
-		execlp("git-index-pack","git-index-pack",
-		       "-o", idx, pack_tmp_name, NULL);
+		execl_git_cmd("index-pack", "-o", idx, pack_tmp_name, NULL);
 		error("cannot exec git-index-pack <%s> <%s>",
 		      idx, pack_tmp_name);
 		exit(1);
@@ -105,8 +105,7 @@ int receive_unpack_pack(int fd[2], const
 		dup2(fd[0], 0);
 		close(fd[0]);
 		close(fd[1]);
-		execlp("git-unpack-objects", "git-unpack-objects",
-		       quiet ? "-q" : NULL, NULL);
+		execl_git_cmd("unpack-objects", quiet ? "-q" : NULL, NULL);
 		die("git-unpack-objects exec failed");
 	}
 	close(fd[0]);
diff --git a/git.c b/git.c
index 5e7da74..4616df6 100644
--- a/git.c
+++ b/git.c
@@ -10,6 +10,7 @@
 #include <stdarg.h>
 #include <sys/ioctl.h>
 #include "git-compat-util.h"
+#include "exec_cmd.h"
 
 #ifndef PATH_MAX
 # define PATH_MAX 4096
@@ -233,14 +234,11 @@ int main(int argc, char **argv, char **e
 {
 	char git_command[PATH_MAX + 1];
 	char wd[PATH_MAX + 1];
-	int i, len, show_help = 0;
-	char *exec_path = getenv("GIT_EXEC_PATH");
+	int i, show_help = 0;
+	const char *exec_path;
 
 	getcwd(wd, PATH_MAX);
 
-	if (!exec_path)
-		exec_path = GIT_EXEC_PATH;
-
 	for (i = 1; i < argc; i++) {
 		char *arg = argv[i];
 
@@ -256,10 +254,11 @@ int main(int argc, char **argv, char **e
 
 		if (!strncmp(arg, "exec-path", 9)) {
 			arg += 9;
-			if (*arg == '=')
+			if (*arg == '=') {
 				exec_path = arg + 1;
-			else {
-				puts(exec_path);
+				git_set_exec_path(exec_path);
+			} else {
+				puts(git_exec_path());
 				exit(0);
 			}
 		}
@@ -275,42 +274,15 @@ int main(int argc, char **argv, char **e
 
 	if (i >= argc || show_help) {
 		if (i >= argc)
-			cmd_usage(exec_path, NULL);
+			cmd_usage(git_exec_path(), NULL);
 
 		show_man_page(argv[i]);
 	}
 
-	if (*exec_path != '/') {
-		if (!getcwd(git_command, sizeof(git_command))) {
-			fprintf(stderr,
-				"git: cannot determine current directory\n");
-			exit(1);
-		}
-		len = strlen(git_command);
-
-		/* Trivial cleanup */
-		while (!strncmp(exec_path, "./", 2)) {
-			exec_path += 2;
-			while (*exec_path == '/')
-				exec_path++;
-		}
-		snprintf(git_command + len, sizeof(git_command) - len,
-			 "/%s", exec_path);
-	}
-	else
-		strcpy(git_command, exec_path);
-	len = strlen(git_command);
-	prepend_to_path(git_command, len);
-
-	len += snprintf(git_command + len, sizeof(git_command) - len,
-			"/git-%s", argv[i]);
-	if (sizeof(git_command) <= len) {
-		fprintf(stderr, "git: command name given is too long.\n");
-		exit(1);
-	}
+	exec_path = git_exec_path();
+	prepend_to_path(exec_path, strlen(exec_path));
 
-	/* execve() can only ever return if it fails */
-	execve(git_command, &argv[i], envp);
+	execv_git_cmd(argv + i);
 
 	if (errno == ENOENT)
 		cmd_usage(exec_path, "'%s' is not a git-command", argv[i]);
diff --git a/receive-pack.c b/receive-pack.c
index f847ec2..6120dbe 100644
--- a/receive-pack.c
+++ b/receive-pack.c
@@ -6,7 +6,7 @@
 
 static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
 
-static const char unpacker[] = "git-unpack-objects";
+static char *unpacker[] = { "git-unpack-objects", NULL };
 
 static int report_status = 0;
 
@@ -257,7 +257,7 @@ static void read_head_info(void)
 
 static const char *unpack(int *error_code)
 {
-	int code = run_command(unpacker, NULL);
+	int code = run_command_v_opt(1, unpacker, RUN_GIT_CMD);
 
 	*error_code = 0;
 	switch (code) {
diff --git a/run-command.c b/run-command.c
index 8bf5922..b3d287e 100644
--- a/run-command.c
+++ b/run-command.c
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "run-command.h"
 #include <sys/wait.h>
+#include "exec_cmd.h"
 
 int run_command_v_opt(int argc, char **argv, int flags)
 {
@@ -13,9 +14,13 @@ int run_command_v_opt(int argc, char **a
 			int fd = open("/dev/null", O_RDWR);
 			dup2(fd, 0);
 			dup2(fd, 1);
-			close(fd);			
+			close(fd);
+		}
+		if (flags & RUN_GIT_CMD) {
+			execv_git_cmd(argv);
+		} else {
+			execvp(argv[0], (char *const*) argv);
 		}
-		execvp(argv[0], (char *const*) argv);
 		die("exec %s failed.", argv[0]);
 	}
 	for (;;) {
diff --git a/run-command.h b/run-command.h
index 2469eea..ef3ee05 100644
--- a/run-command.h
+++ b/run-command.h
@@ -12,7 +12,7 @@ enum {
 };
 
 #define RUN_COMMAND_NO_STDIO 1
-
+#define RUN_GIT_CMD	     2	/*If this is to be git sub-command */
 int run_command_v_opt(int argc, char **argv, int opt);
 int run_command_v(int argc, char **argv);
 int run_command(const char *cmd, ...);
diff --git a/send-pack.c b/send-pack.c
index cd36193..4a420a6 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -26,11 +26,11 @@ static int is_zero_sha1(const unsigned c
 static void exec_pack_objects(void)
 {
 	static char *args[] = {
-		"git-pack-objects",
+		"pack-objects",
 		"--stdout",
 		NULL
 	};
-	execvp("git-pack-objects", args);
+	execv_git_cmd(args);
 	die("git-pack-objects exec failed (%s)", strerror(errno));
 }
 
@@ -39,7 +39,7 @@ static void exec_rev_list(struct ref *re
 	static char *args[1000];
 	int i = 0;
 
-	args[i++] = "git-rev-list";	/* 0 */
+	args[i++] = "rev-list";	/* 0 */
 	args[i++] = "--objects";	/* 1 */
 	while (refs) {
 		char *buf = malloc(100);
@@ -58,7 +58,7 @@ static void exec_rev_list(struct ref *re
 		refs = refs->next;
 	}
 	args[i] = NULL;
-	execvp("git-rev-list", args);
+	execv_git_cmd(args);
 	die("git-rev-list exec failed (%s)", strerror(errno));
 }
 
diff --git a/shell.c b/shell.c
index cd31618..d40dfe4 100644
--- a/shell.c
+++ b/shell.c
@@ -7,12 +7,14 @@ static int do_generic_cmd(const char *me
 
 	if (!arg || !(arg = sq_dequote(arg)))
 		die("bad argument");
+	if (strncmp(me, "git-", 4))
+		die("bad command");
 
-	my_argv[0] = me;
+	my_argv[0] = me + 4;
 	my_argv[1] = arg;
 	my_argv[2] = NULL;
 
-	return execvp(me, (char**) my_argv);
+	return execv_git_cmd((char**) my_argv);
 }
 
 static struct commands {
diff --git a/upload-pack.c b/upload-pack.c
index 1834b6b..d198055 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -4,6 +4,7 @@
 #include "tag.h"
 #include "object.h"
 #include "commit.h"
+#include "exec_cmd.h"
 
 static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>";
 
@@ -60,7 +61,7 @@ static void create_pack_file(void)
 		close(0);
 		close(fd[0]);
 		close(fd[1]);
-		*p++ = "git-rev-list";
+		*p++ = "rev-list";
 		*p++ = "--objects";
 		if (create_full_pack || MAX_NEEDS <= nr_needs)
 			*p++ = "--all";
@@ -79,13 +80,13 @@ static void create_pack_file(void)
 				buf += 41;
 			}
 		*p++ = NULL;
-		execvp("git-rev-list", argv);
+		execv_git_cmd(argv);
 		die("git-upload-pack: unable to exec git-rev-list");
 	}
 	dup2(fd[0], 0);
 	close(fd[0]);
 	close(fd[1]);
-	execlp("git-pack-objects", "git-pack-objects", "--stdout", NULL);
+	execl_git_cmd("pack-objects", "--stdout", NULL);
 	die("git-upload-pack: unable to exec git-pack-objects");
 }
 
-- 
0.99.9m-g5a22

Re: [PATCH] (Updated) Exec git programs without using PATH.

From: Junio C Hamano <hidden>
Date: 2016-06-15 22:42:16

Michal Ostrowski [off-list ref] writes:
The git suite may not be in PATH (and thus programs such as
...
Includes modifications by Junio C Hamano [off-list ref].
Thanks for the update (you did it even without line wrapping
this time).

I made further fixes last night and it is in the proposed
updates ("pu") branch.  This branch is used to hold and showcase
what I plan to eventually merge into the master branch, and
consists of the topic branches merged on top of what is in the
master branch.  To disect to see what shape I munged your patch
into, please look at it with gitk or gitweb and find "Merge
branch mo/exec" commit (my topic branches are named after patch
author and topic keyword).  The second parent of that commit is
the tip of the topic branch mo/exec when the merge was made.

A word of caution to people on the list (not limited to Michal)
is needed again, because it's been a while since I talked about
the proposed updates branch last time (which was pre 1.0.0
release IIRC).

The "pu" branch is regularly rebuilt by merging private topic
branches on top of the then-current master branch, by doing
this:

	$ git checkout pu
        $ git reset --hard master ;# build from scratch!
        $ git pull . topic1
        $ git pull . topic2
        $ ...
        $ git pull . topicN

What this means is that you (not just Michal---"people") should
not base your development on top of "pu" branch proper, since
you will have a hard time updating and merging.  Disecting my
"pu" branch to find the topic branch that corresponds to your
topic, and doing your development and patch preparation based on
that branch tip (and tell me that the patch is against your
topic branch, or "pu") is possible, but even that is not always
an easy option, since I may occasionally have to rebase my topic
branches as well.  You can always send in an updated patch based
on my master, and I'll manage with three-way merge to pick out
the real changes since your last iteration.

For your (primarily Michal, but other interested parties as
well) reference, here is the diff between your patch in the
message I am replying to and what I placed in the "pu" branch
last night.

-- >8 --
Updates on top of comments last night.

 - git-unpack-objects are run via execv_git_cmd, so no "git-"
   prefix should be given.

 - two more files need #include "exec_cmd.h".

---
diff --git a/receive-pack.c b/receive-pack.c
index 6120dbe..eae31e3 100644
--- a/receive-pack.c
+++ b/receive-pack.c
@@ -6,7 +6,7 @@
 
 static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
 
-static char *unpacker[] = { "git-unpack-objects", NULL };
+static char *unpacker[] = { "unpack-objects", NULL };
 
 static int report_status = 0;
 
diff --git a/send-pack.c b/send-pack.c
index 4a420a6..990be3f 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -3,6 +3,7 @@
 #include "tag.h"
 #include "refs.h"
 #include "pkt-line.h"
+#include "exec_cmd.h"
 
 static const char send_pack_usage[] =
 "git-send-pack [--all] [--exec=git-receive-pack] <remote> [<head>...]\n"
diff --git a/shell.c b/shell.c
index d40dfe4..fc0c73c 100644
--- a/shell.c
+++ b/shell.c
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "quote.h"
+#include "exec_cmd.h"
 
 static int do_generic_cmd(const char *me, char *arg)
 {

Re: [PATCH] (Updated) Exec git programs without using PATH.

From: Linus Torvalds <torvalds@osdl.org>
Date: 2016-06-15 22:42:16


On Wed, 11 Jan 2006, Junio C Hamano wrote:
For your (primarily Michal, but other interested parties as
well) reference, here is the diff between your patch in the
message I am replying to and what I placed in the "pu" branch
last night.
Tangentially related note: maybe we should try to move to a "spawn()" like 
interface so that it could port better to native Windows interfaces?

Even under Linux, using vfork()+exec() is actually faster than a regular 
fork/exec, so there are potential advantages.

The real advantage of fork+exec is how you can do arbitrary setup between 
the two, without needing insanely complex rules for file descriptors etc. 
But maybe we don't have tons of those issues?

		Linus

Re: [PATCH] (Updated) Exec git programs without using PATH.

From: Michal Ostrowski <hidden>
Date: 2016-06-15 22:42:16

On Wed, 2006-01-11 at 12:42 -0800, Linus Torvalds wrote:
On Wed, 11 Jan 2006, Junio C Hamano wrote:
quoted
For your (primarily Michal, but other interested parties as
well) reference, here is the diff between your patch in the
message I am replying to and what I placed in the "pu" branch
last night.
Tangentially related note: maybe we should try to move to a "spawn()" like 
interface so that it could port better to native Windows interfaces?

Even under Linux, using vfork()+exec() is actually faster than a regular 
fork/exec, so there are potential advantages.

The real advantage of fork+exec is how you can do arbitrary setup between 
the two, without needing insanely complex rules for file descriptors etc. 
But maybe we don't have tons of those issues?
I briefly tried to consider if I could hide the various fork()+exec()
sequences behind something like the run_command*() interfaces (which
would move us down the direction of something "spawn()"-like).  I found
that there's a lot of variation between the various paths in terms of
what happens between fork() and exec() on the various paths that does
not lend itself to such consolidation.

I'd love to be convinced otherwise.

-- 
Michal Ostrowski [off-list ref]
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help