From: Junio C Hamano <hidden> Date: 2016-06-15 22:52:30
"Frans Klaver" [off-list ref] writes:
Actually I went through diagnosing and fixing it. After tracking it
down, I did wonder about this question myself and I didn't come to a
definitive conclusion on it. On one hand I do agree that it may be an
incentive for the user to fix his path. On the other hand I found it
an obscure one to track down; git's behavior doesn't match bash
behavior:
$ git config --global alias.aliasedinit init &&
mkdir searchpath && chmod 400 searchpath &&
PATH=$(pwd)/searchpath:$PATH && export PATH &&
mkdir someproject && cd someproject &&
git aliasedinit
fatal: cannot exec 'git-aliasedinit': Permission denied
Imagine you did not have alias.aliasedinit in ~/.gitconfig but had a
script called $(pwd)/searchpath/git-aliasedinit which we would fail to
execute. What message would we get in that case? Currently I think we get
permission denied.
Would we get the same with your patch, or something that does not hint
at all that there is a permission problem?
See also the "tangent" part of
http://thread.gmane.org/gmane.comp.version-control.git/171755
and the discussion that follows it. I do not think we reached any
conclusion nor a patch.
From: Frans Klaver <hidden> Date: 2016-06-15 22:52:30
On Tue, Nov 22, 2011 at 12:54 AM, Junio C Hamano [off-list ref] wrote:
Imagine you did not have alias.aliasedinit in ~/.gitconfig but had a
script called $(pwd)/searchpath/git-aliasedinit which we would fail to
execute. What message would we get in that case? Currently I think we get
permission denied.
Correct.
Would we get the same with your patch, or something that does not hint
at all that there is a permission problem?
Nope. That would be just as confusing and inarguably incorrect at that
-- bash differentiates between commands that exist, but cannot be
executed due to permissions (access denied) and paths that cannot be
read (they are ignored in the search).
There's no black-on-white conclusion there. I get the impression that
no one really has an idea of what they want when encountering EACCES.
Git has to do what's reasonable to provide the user with information.
Currently I think it does too little. Jonathan N. gave the option of
optionally using libexplain[1]. It's pretty verbose and accurate:
fatal: cannot exec 'git-frotz': execvp(pathname = "git-frotz", argv =
["git-frotz"]) failed, Permission denied (13, EACCES) because the
process does not have search permission to the pathname
"/home/frans/devsw/searchpath" directory, the process effective UID
1000 "frans" matches the directory owner UID 1000 "frans" and the
owner permission mode is "r--", and the process is not privileged
(does not have the DAC_READ_SEARCH capability): Success
I wouldn't be in favor of adding the dependency just to enable users
to track down PATH issues though. Also, I think "Cannot access
/home/frans/devsw/searchpath" would just as well do the trick.
For Jonathan's example[2] libexplain doesn't have a clear answer either:
fatal: cannot exec 'git-frotz': execvp(pathname = "git-frotz", argv =
["git-frotz"]) failed, Permission denied (13, EACCES): Permission
denied
If git is going to do some diagnostics on why the execvp returned
EACCES, it can still give a few hints. Most of the more likely options
are then ruled out.
Frans
[1] http://article.gmane.org/gmane.comp.version-control.git/171860
[2] http://article.gmane.org/gmane.comp.version-control.git/171848
From: Frans Klaver <hidden> Date: 2016-06-15 22:52:31
On Tue, Nov 22, 2011 at 10:31 AM, Frans Klaver [off-list ref] wrote:
If git is going to do some diagnostics on why the execvp returned
EACCES, it can still give a few hints. Most of the more likely options
are then ruled out.
If there are no objections, I'm going to cook up a patch that
- Keeps the current behavior (bail on EACCES)
- Adds a more helpful diagnostic message somewhat like libexplain's,
but more terse and if possible with slightly more domain knowledge
- Takes into account the notes made following
http://article.gmane.org/gmane.comp.version-control.git/171838
Frans
On Wed, Nov 23, 2011 at 3:17 PM, Frans Klaver [off-list ref] wrote:
If there are no objections, I'm going to cook up a patch that
- Keeps the current behavior (bail on EACCES)
- Adds a more helpful diagnostic message somewhat like libexplain's,
but more terse and if possible with slightly more domain knowledge
If you print diagnostic messages with trace_printf() and friends (only
showed when GIT_TRACE variable is set), then there's no need for being
terse.
--
Duy
From: Frans Klaver <hidden> Date: 2016-06-15 22:52:31
On Wed, 23 Nov 2011 09:17:43 +0100, Frans Klaver [off-list ref]
wrote:
On Tue, Nov 22, 2011 at 10:31 AM, Frans Klaver [off-list ref]
wrote:
quoted
If git is going to do some diagnostics on why the execvp returned
EACCES, it can still give a few hints. Most of the more likely options
are then ruled out.
If there are no objections, I'm going to cook up a patch that
- Keeps the current behavior (bail on EACCES)
- Adds a more helpful diagnostic message somewhat like libexplain's,
but more terse and if possible with slightly more domain knowledge
- Takes into account the notes made following
http://article.gmane.org/gmane.comp.version-control.git/171838
So here be some tests I intend to use (based on t0061.3):
run_command reports EACCES, file permissions:
cat hello-script >hello.sh &&
chmod -x hello.sh &&
test_must_fail test-run-command run-command ./hello.sh 2>err &&
grep "fatal: cannot exec.*hello.sh" err
run_command reports EACCES, search path permisions:
mkdir -p inaccessible &&
PATH=$(pwd)/inaccessible:$PATH &&
export PATH &&
cat hello-script >inaccessible/hello.sh &&
chmod 400 inaccessible &&
test_must_fail test-run-command run-command hello.sh 2>err &&
grep "fatal: cannot exec.*hello.sh" err &&
grep "incorrect PATH entry" err
run_command reports EACCES, interpreter fails:
cat incorrect-interpreter-script >hello.sh &&
chmod +x incorrect-interpreter-script &&
chmod -x someinterpreter &&
test_must_fail test-run-command run-command ./hello.sh 2>err &&
grep "fatal: cannot exec.*hello.sh" err &&
grep "cannot execute interpreter" err
Possibly getting (over)ambitious on the interpreter test, but hey, gotta
aim high.
If anybody has a test case that isn't covered, I'd be much obliged.
Frans
From: Frans Klaver <hidden> Date: 2016-06-15 22:52:34
So here's a couple of patches that introduce some more elaborate investigation
into what went wrong when receiving EACCES. This is probably something that
could be expanded in the future, as running a command doesn't always produce
equally obvious error messages.
"run-command: Add checks after execvp fails with EACCES" provides some basic checks
on the permissions in PATH, and gives just a warning that none of its checks
indicate a problem, so the user should check at least the interpreter permissions.
"run-command: Add interpreter permissions check" actually adds interpreter checking.
---
run-command.c | 172 ++++++++++++++++++++++++++++++++++++++++++++++++
t/t0061-run-command.sh | 38 ++++++++++-
2 files changed, 209 insertions(+), 1 deletions(-)
From: Frans Klaver <hidden> Date: 2016-06-15 22:52:34
execvp returns ENOENT if a command was not found after searching PATH.
If path contains a directory that current user has insufficient
privileges to, EACCES is returned. This may still mean the program
wasn't found and may cause confusion to the user, especially when the
file mentioned doesn't exist -- that is, the user would expect NOENT to
be returned -- and the user was actually hoping for an alias to be executed.
To help users track down the core issue more easily, perform some checks
on the path and file permissions involved. Output errors when paths or
files don't have enough permissions.
Signed-off-by: Frans Klaver <redacted>
---
run-command.c | 118 ++++++++++++++++++++++++++++++++++++++++++++++++
t/t0061-run-command.sh | 16 ++++++-
2 files changed, 133 insertions(+), 1 deletions(-)
@@ -134,6 +135,119 @@ static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure)returncode;}+#ifndef WIN32+staticintis_in_group(gid_tgid)+{+gid_t*groups;+intngroups,gc;+intyes;++if(gid==getgid())+return1;++groups=NULL;+ngroups=getgroups(0,NULL);+if(ngroups>0){+groups=(gid_t*)xmalloc(ngroups*sizeof(gid_t));+if(getgroups(ngroups,groups)<0){+free(groups);+return0;+}+}++yes=0;+for(gc=0;gc<ngroups;gc++)+if(groups[gc]==gid)+yes=1;++free(groups);+returnyes;+}++staticinthave_read_execute_permissions(constchar*path)+{+structstats;+trace_printf("checking '%s'\n",path);++if(stat(path,&s)<0){+trace_printf("could not stat '%s': %s\n",+path,strerror(errno));+return0;+}+trace_printf("uid: %d, gid: %d\n",s.st_uid,s.st_gid);+trace_printf("mode: %o\n",s.st_mode);++/* check world permissions */+if((s.st_mode&(S_IXOTH|S_IROTH))==(S_IXOTH|S_IROTH))+return1;++/* check group permissions & membership */+if((s.st_mode&(S_IXGRP|S_IRGRP))==(S_IXGRP|S_IRGRP)&&+is_in_group(s.st_gid))+return1;++/* check owner permissions & ownership */+if((s.st_mode&(S_IXUSR|S_IRUSR))==(S_IXUSR|S_IRUSR)&&+s.st_uid==getuid())+return1;++return0;+}++staticvoiddiagnose_execvp_eacces(constchar*cmd,constchar**argv)+{+/* man 2 execve states that EACCES is returned for:+*-Searchpermissionisdeniedonacomponentofthepathprefix+*ofcmdorthenameofascriptinterpreter+*-Thefileorscriptinterpreterisnotaregularfile+*-Executepermissionisdeniedforthefile,scriptorELF+*interpreter+*-Thefilesystemismountednoexec+*/+structstrbufsb=STRBUF_INIT;+char*path=getenv("PATH");+char*next;++if(strchr(cmd,'/')){+if(!have_read_execute_permissions(cmd))+error("no read/execute permissions on '%s'\n",cmd);+return;+}++for(;;){+next=strchrnul(path,':');+if(path<next)+strbuf_add(&sb,path,next-path);+else+strbuf_addch(&sb,'.');++if(!have_read_execute_permissions(sb.buf))+error("no read/execute permissions on '%s'\n",sb.buf);++if(sb.len&&sb.buf[sb.len-1]!='/')+strbuf_addch(&sb,'/');+strbuf_addstr(&sb,cmd);++if(file_exists(sb.buf)){+if(!have_read_execute_permissions(sb.buf))+error("no read/execute permissions on '%s'\n",+sb.buf);+else+warn("file '%s' exists and permissions "+"seem OK.\nIf this is a script, see if you "+"have sufficient privileges to run the "+"interpreter",sb.buf);+}++strbuf_release(&sb);++if(!*next)+break;+path=next+1;+}+}+#endif+intstart_command(structchild_process*cmd){intneed_in,need_out,need_err;
From: Frans Klaver <hidden> Date: 2016-06-15 22:52:34
If a script is started and the interpreter of that script given in the
shebang cannot be started due to permissions, we can get a rather
obscure situation. All permission checks pass for the script itself,
but we still get EACCES from execvp.
Try to find out if the above is the case and warn the user about it.
Signed-off-by: Frans Klaver <redacted>
---
run-command.c | 66 +++++++++++++++++++++++++++++++++++++++++++----
t/t0061-run-command.sh | 22 ++++++++++++++++
2 files changed, 82 insertions(+), 6 deletions(-)
@@ -194,6 +194,63 @@ static int have_read_execute_permissions(const char *path)return0;}+staticvoidcheck_interpreter(constchar*cmd)+{+FILE*f;+structstrbufsb=STRBUF_INIT;+/* bash reads an 80 character line when determining the interpreter.+*BSDapparentlyonlyallows32characters,asitisthesizeof+*youraveragebinaryexecutableheader.+*/+charfirstline[80];+char*interpreter=NULL;+size_ts,i;++f=fopen(cmd,"r");+if(!f){+error("cannot open file '%s': %s\n",cmd,strerror(errno));+return;+}++s=fread(firstline,1,sizeof(firstline),f);+if(s<2){+trace_printf("cannot determine file type");+fclose(f);+return;+}++if(firstline[0]!='#'||firstline[1]!='!'){+trace_printf("file '%s' is not a script or"+" is a script without '#!'",cmd);+fclose(f);+return;+}++/* see if the given path has the executable bit set */+for(i=2;i<s;i++){+if(!interpreter&&firstline[i]!=' '&&firstline[i]!='\t')+interpreter=firstline+i;++if(interpreter&&(firstline[i]==' '||+firstline[i]=='\n')){+strbuf_add(&sb,interpreter,+(firstline+i)-interpreter);+break;+}+}+if(!sb.len){+error("could not determine interpreter");+strbuf_release(&sb);+return;+}++if(!have_read_execute_permissions(sb.buf))+error("bad interpreter: no read/execute permissions on '%s'\n",+sb.buf);++strbuf_release(&sb);+}+staticvoiddiagnose_execvp_eacces(constchar*cmd,constchar**argv){/* man 2 execve states that EACCES is returned for:
@@ -233,10 +290,7 @@ static void diagnose_execvp_eacces(const char *cmd, const char **argv)error("no read/execute permissions on '%s'\n",sb.buf);else-warn("file '%s' exists and permissions "-"seem OK.\nIf this is a script, see if you "-"have sufficient privileges to run the "-"interpreter",sb.buf);+check_interpreter(sb.buf);}strbuf_release(&sb);
From: Frans Klaver <hidden> Date: 2016-06-15 22:52:37
This replaces $gmane/186388
I had a lot of short stints incorporating the review remarks, so I might just
have missed something.
[PATCH 1/2] run-command: Add checks after execvp fails with EACCES
[PATCH 2/2] run-command: Add interpreter permissions check
run-command.c | 130 ++++++++++++++++++++++++++++++++++++++++++++++++
t/t0061-run-command.sh | 38 ++++++++++++++-
2 files changed, 167 insertions(+), 1 deletions(-)
From: Frans Klaver <hidden> Date: 2016-06-15 22:52:37
execvp returns ENOENT if a command was not found after searching PATH.
If path contains a directory that current user has insufficient
privileges to, EACCES is returned. This may still mean the program
wasn't found and may cause confusion to the user, especially when the
file mentioned doesn't exist -- that is, the user would expect NOENT to
be returned -- and the user was actually hoping for an alias to be executed.
To help users track down the core issue more easily, perform some checks
on the path and file permissions involved. Output errors when paths or
files don't have enough permissions.
Signed-off-by: Frans Klaver <redacted>
---
run-command.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++++
t/t0061-run-command.sh | 16 +++++++++-
2 files changed, 94 insertions(+), 1 deletions(-)
@@ -134,6 +135,80 @@ static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure)returncode;}+#ifndef WIN32+staticinthave_read_execute_permissions(constchar*path)+{+if(access(path,R_OK|X_OK)==0)+return1;++if(errno==EACCES)+return0;++trace_printf("could not determine permissions for '%s': %s\n",path,+strerror(errno));+return0;+}++staticvoiddiagnose_execvp_eacces(constchar*cmd,constchar**argv)+{+/*+*man2execvestatesthatEACCESisreturnedfor:+*-Searchpermissionisdeniedonacomponentofthepathprefix+*ofcmdorthenameofascriptinterpreter+*-Thefileorscriptinterpreterisnotaregularfile+*-Executepermissionisdeniedforthefile,scriptorELF+*interpreter+*-Thefilesystemismountednoexec+*/+structstrbufsb=STRBUF_INIT;+char*path;+char*next;++if(strchr(cmd,'/')){+if(!have_read_execute_permissions(cmd))+error("no read/execute permissions on '%s'\n",cmd);+return;+}++path=getenv("PATH");+while(path){+next=strchrnul(path,':');+if(path<next)+strbuf_add(&sb,path,next-path);+else+strbuf_addch(&sb,'.');++if(!*next)+path=NULL;+else+path=next+1;++if(!have_read_execute_permissions(sb.buf)){+error("no read/execute permissions on '%s'\n",sb.buf);+strbuf_release(&sb);+continue;+}++if(sb.len&&sb.buf[sb.len-1]!='/')+strbuf_addch(&sb,'/');+strbuf_addstr(&sb,cmd);++if(file_exists(sb.buf)){+if(!have_read_execute_permissions(sb.buf))+error("no read/execute permissions on '%s'\n",+sb.buf);+else+warning("file '%s' exists and permissions "+"seem OK.\nIf this is a script, see if you "+"have sufficient privileges to run the "+"interpreter",sb.buf);+}++strbuf_release(&sb);+}+}+#endif+intstart_command(structchild_process*cmd){intneed_in,need_out,need_err;
From: Frans Klaver <hidden> Date: 2016-06-15 22:52:37
If a script is started and the interpreter of that script given in the
shebang cannot be started due to permissions, we can get a rather
obscure situation. All permission checks pass for the script itself,
but we still get EACCES from execvp.
Try to find out if the above is the case and warn the user about it.
Signed-off-by: Frans Klaver <redacted>
---
run-command.c | 59 ++++++++++++++++++++++++++++++++++++++++++++---
t/t0061-run-command.sh | 22 ++++++++++++++++++
2 files changed, 77 insertions(+), 4 deletions(-)
@@ -149,6 +149,55 @@ static int have_read_execute_permissions(const char *path)return0;}+staticvoidcheck_interpreter(constchar*cmd)+{+FILE*f;+structstrbufsb=STRBUF_INIT;+/*+*bashreadsan80characterlinewhendeterminingtheinterpreter.+*BSDapparentlyonlyallows32characters,asitisthesizeof+*youraveragebinaryexecutableheader.+*/+charfirstline[80];+size_ts,start,end;++f=fopen(cmd,"r");+if(!f){+error("cannot open file '%s': %s\n",cmd,strerror(errno));+return;+}++s=fread(firstline,1,sizeof(firstline),f);+if(s<2){+trace_printf("cannot determine file type");+fclose(f);+return;+}++if(firstline[0]!='#'||firstline[1]!='!'){+trace_printf("file '%s' is not a script or"+" is a script without '#!'",cmd);+fclose(f);+return;+}++/* see if the given path has the executable bit set */+start=strspn(&firstline[2]," \t")+2;+end=strcspn(&firstline[start]," \t\r\n")+start;+if(start>=end){+error("could not determine interpreter\n");+return;+}++strbuf_add(&sb,&firstline[start],end-start);++if(!have_read_execute_permissions(sb.buf))+error("bad interpreter: no read/execute permissions on '%s'\n",+sb.buf);++strbuf_release(&sb);+}+staticvoiddiagnose_execvp_eacces(constchar*cmd,constchar**argv){/*
@@ -197,11 +248,11 @@ static void diagnose_execvp_eacces(const char *cmd, const char **argv)if(!have_read_execute_permissions(sb.buf))error("no read/execute permissions on '%s'\n",sb.buf);+elseif(access(sb.buf,R_OK)==0)+check_interpreter(sb.buf);else-warning("file '%s' exists and permissions "-"seem OK.\nIf this is a script, see if you "-"have sufficient privileges to run the "-"interpreter",sb.buf);+trace_printf("cannot determine interpreter "+"on '%s'\n",sb.buf);}strbuf_release(&sb);