diff --git a/NEWS b/NEWS index aba476ca5..6be27e019 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,12 @@ Any uppercase BUG_* names are modernish shell bug IDs. - Fixed an issue on illumos that caused some parameters in the getconf builtin to fail. +- The cd built-in command now supports a -e option (as specified in + https://www.austingroupbugs.net/view.php?id=253). Passing -e alongside -P + is used to guarantee the cd built-in returns with exit status 1 if the + current working directory couldn't be determined after successfully changing + the directory. + 2021-12-01: - Fixed a memory fault that occurred when a discipline function exited diff --git a/src/cmd/ksh93/bltins/cd_pwd.c b/src/cmd/ksh93/bltins/cd_pwd.c index e441805fd..2b5206839 100644 --- a/src/cmd/ksh93/bltins/cd_pwd.c +++ b/src/cmd/ksh93/bltins/cd_pwd.c @@ -20,8 +20,8 @@ ***********************************************************************/ #pragma prototyped /* - * cd [-LP] [dirname] - * cd [-LP] [old] [new] + * cd [-L] [-Pe] [dirname] + * cd [-L] [-Pe] [old] [new] * pwd [-LP] * * David Korn @@ -57,33 +57,43 @@ int b_cd(int argc, char *argv[],Shbltin_t *context) register const char *dp; register Shell_t *shp = context->shp; int saverrno=0; - int rval,flag=0; + int rval,pflag=0,eflag=0,ret=1; char *oldpwd; Namval_t *opwdnod, *pwdnod; - if(sh_isoption(SH_RESTRICTED)) - { - errormsg(SH_DICT,ERROR_exit(1),e_restricted+4); - UNREACHABLE(); - } while((rval = optget(argv,sh_optcd))) switch(rval) { + case 'e': + eflag = 1; + break; case 'L': - flag = 0; + pflag = 0; break; case 'P': - flag = 1; + pflag = 1; break; case ':': + if(sh_isoption(SH_RESTRICTED)) + break; errormsg(SH_DICT,2, "%s", opt_info.arg); break; case '?': + if(sh_isoption(SH_RESTRICTED)) + break; errormsg(SH_DICT,ERROR_usage(2), "%s", opt_info.arg); UNREACHABLE(); } + if(pflag && eflag) + ret = 2; /* exit status is 2 if -eP are both on and chdir failed */ + if(sh_isoption(SH_RESTRICTED)) + { + /* restricted shells cannot change the directory */ + errormsg(SH_DICT,ERROR_exit(ret),e_restricted+4); + UNREACHABLE(); + } argv += opt_info.index; argc -= opt_info.index; dir = argv[0]; - if(error_info.errors>0 || argc >2) + if(error_info.errors>0 || argc>2) { errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0)); UNREACHABLE(); @@ -106,7 +116,7 @@ int b_cd(int argc, char *argv[],Shbltin_t *context) dir = nv_getval(opwdnod); if(!dir || *dir==0) { - errormsg(SH_DICT,ERROR_exit(1),argc==2?e_subst+4:e_direct); + errormsg(SH_DICT,ERROR_exit(ret),argc==2?e_subst+4:e_direct); UNREACHABLE(); } /* @@ -179,7 +189,7 @@ int b_cd(int argc, char *argv[],Shbltin_t *context) stakputs(last+PATH_OFFSET); stakputc(0); } - if(!flag) + if(!pflag) { register char *cp; stakseek(PATH_MAX+PATH_OFFSET); @@ -200,19 +210,19 @@ int b_cd(int argc, char *argv[],Shbltin_t *context) { if(saverrno) errno = saverrno; - errormsg(SH_DICT,ERROR_system(1),"%s:",dir); + errormsg(SH_DICT,ERROR_system(ret),"%s:",dir); UNREACHABLE(); } success: if(dir == nv_getval(opwdnod) || argc==2) dp = dir; /* print out directory for cd - */ - if(flag) + if(pflag) { dir = stakptr(PATH_OFFSET); if (!(dir=pathcanon(dir,PATH_PHYSICAL))) { dir = stakptr(PATH_OFFSET); - errormsg(SH_DICT,ERROR_system(1),"%s:",dir); + errormsg(SH_DICT,ERROR_system(ret),"%s:",dir); UNREACHABLE(); } stakseek(dir-stakptr(0)); @@ -223,10 +233,10 @@ success: nv_putval(opwdnod,oldpwd,NV_RDONLY); if(*dir == '/') { - flag = strlen(dir); + size_t len = strlen(dir); /* delete trailing '/' */ - while(--flag>0 && dir[flag]=='/') - dir[flag] = 0; + while(--len>0 && dir[len]=='/') + dir[len] = 0; nv_putval(pwdnod,dir,NV_RDONLY); nv_onattr(pwdnod,NV_EXPORT); if(shp->pwd) @@ -243,13 +253,18 @@ success: path_pwd(shp,0); if(*shp->pwd != '/') { - errormsg(SH_DICT,ERROR_system(1),e_direct); + errormsg(SH_DICT,ERROR_system(ret),e_direct); UNREACHABLE(); } } nv_scan(sh_subtracktree(1),rehash,(void*)0,NV_TAGGED,NV_TAGGED); path_newdir(shp,shp->pathlist); path_newdir(shp,shp->cdpathlist); + if(pflag && eflag) + { + /* Verify the current working directory matches $PWD */ + return(!test_inode(e_dot,nv_getval(pwdnod))); + } return(0); } diff --git a/src/cmd/ksh93/data/builtins.c b/src/cmd/ksh93/data/builtins.c index b0327868d..855deaac0 100644 --- a/src/cmd/ksh93/data/builtins.c +++ b/src/cmd/ksh93/data/builtins.c @@ -441,7 +441,7 @@ const char sh_optbuiltin[] = ; const char sh_optcd[] = -"[-1c?\n@(#)$Id: cd (ksh 93u+m) 2021-01-19 $\n]" +"[-1c?\n@(#)$Id: cd (ksh 93u+m) 2021-12-02 $\n]" "[--catalog?" SH_DICT "]" "[+NAME?cd - change working directory ]" "[+DESCRIPTION?\bcd\b changes the current working directory of the " @@ -482,13 +482,22 @@ const char sh_optcd[] = "[P?The present working directory is first converted to an absolute pathname " "that does not contain symbolic link components and symbolic name " "components are expanded in the resulting directory name.]" +"[e?If the \b-P\b option is in effect and the correct \bPWD\b cannot be " + "determined, exit with status 1. All other errors encountered while " + "both \b-e\b and \b-P\b are active result in exit status >1 (i.e., " + "exit status 2 unless an out of memory error occurred).]" "\n" "\n[directory]\n" "old new\n" "\n" "[+EXIT STATUS?]{" - "[+0?Directory successfully changed.]" - "[+>0?An error occurred.]" + "[+0?Directory successfully changed and the \bPWD\b is correct.]" + "[+0?Directory successfully changed, the \bPWD\b couldn't be obtained " + "and a combination of \b-eP\b is not active.]" + "[+>0?An error occurred and a combination of \b-eP\b is not active.]" + "[+1?Directory successfully changed, the \bPWD\b couldn't be obtained " + "and a combination of \b-eP\b is active.]" + "[+>1?An error occurred and a combination of \b-eP\b is active.]" "}" "[+SEE ALSO?\bpwd\b(1), \bgetconf\b(1)]" ; diff --git a/src/cmd/ksh93/sh.1 b/src/cmd/ksh93/sh.1 index 73f77fdc0..a9683e08a 100644 --- a/src/cmd/ksh93/sh.1 +++ b/src/cmd/ksh93/sh.1 @@ -5772,9 +5772,9 @@ and invokes this function with an argument of .BR 0 . .TP .PD 0 -\f3cd\fP \*(OK \f3\-LP\fP \*(CK \*(OK \f2arg\^\fP \*(CK +\f3cd\fP \*(OK \f3\-L\fP \*(CK \*(OK \f3-eP\fP \*(CK \*(OK \f2arg\^\fP \*(CK .TP -\f3cd\fP \*(OK \f3\-LP\fP \*(CK \f2old\^\fP \f2new\^\fP +\f3cd\fP \*(OK \f3\-L\fP \*(CK \*(OK \f3-eP\fP \*(CK \f2old\^\fP \f2new\^\fP .PD This command can be in either of two forms. In the first form it @@ -5846,6 +5846,20 @@ or on the command line determines which method is used. .sp .5 +If +.B \-e +and +.B \-P +are both in effect and the correct +.BR PWD +could not be determined after successfully changing the directory, +.B cd +will return with exit status one and produce no output. +If any other error occurs while both flags are active, +the exit status is greater than one. +This means the exit status is effectively +two unless an out of memory error occurs. +.sp .5 The .B cd\^ command may not be executed by diff --git a/src/cmd/ksh93/tests/builtins.sh b/src/cmd/ksh93/tests/builtins.sh index 3eebd96d4..4e20cea82 100755 --- a/src/cmd/ksh93/tests/builtins.sh +++ b/src/cmd/ksh93/tests/builtins.sh @@ -1354,5 +1354,47 @@ if builtin rm 2> /dev/null; then [[ -f $tmp/nonemptydir2/shouldexist || -d $tmp/nonemptydir2 ]] && err_exit 'rm builtin fails to remove all folders and files with -rd options' fi +# ====== +# These are regression tests for the cd command's -e and -P flags +mkdir -p "$tmp/failpwd1" +cd "$tmp/failpwd1" +rmdir ../failpwd1 +cd -P . +got=$?; exp=0 +(( got == exp )) || err_exit "cd -P without -e exits with error status if \$PWD doesn't exist (expected $exp, got $got)" +cd -eP . +got=$?; exp=1 +(( got == exp )) || err_exit "cd -eP doesn't fail if \$PWD doesn't exist (expected $exp, got $got)" +cd "$tmp" +cd -P "$tmp/notadir" >/dev/null 2>&1 +got=$?; exp=1 +(( got == exp )) || err_exit "cd -P without -e fails with wrong exit status on nonexistent dir (expected $exp, got $got)" +cd -eP "$tmp/notadir" >/dev/null 2>&1 +got=$?; exp=2 +(( got == exp )) || err_exit "cd -eP fails with wrong exit status on nonexistent dir (expected $exp, got $got)" +OLDPWD="$tmp/baddir" +cd -P - >/dev/null 2>&1 +got=$?; exp=1 +(( got == exp )) || err_exit "cd -P without -e fails with wrong exit status on \$OLDPWD (expected $exp, got $got)" +cd -eP - >/dev/null 2>&1 +got=$?; exp=2 +(( got == exp )) || err_exit "cd -eP fails with wrong exit status on \$OLDPWD (expected $exp, got $got)" +cd "$tmp" || err_exit "couldn't change directory from nonexistent dir" +(set -o restricted; cd -P /) >/dev/null 2>&1 +got=$?; exp=1 +(( got == exp )) || err_exit "cd -P in restricted shell has wrong exit status (expected $exp, got $got)" +(set -o restricted; cd -eP /) >/dev/null 2>&1 +got=$?; exp=2 +(( got == exp )) || err_exit "cd -eP in restricted shell has wrong exit status (expected $exp, got $got)" +(set -o restricted; cd -?) >/dev/null 2>&1 +got=$?; exp=1 +(( got == exp )) || err_exit "cd -? shows usage info in restricted shell and has wrong exit status (expected $exp, got $got)" +(cd -P '') >/dev/null 2>&1 +got=$?; exp=1 +(( got == exp )) || err_exit "cd -P to empty string has wrong exit status (expected $exp, got $got)" +(cd -eP '') >/dev/null 2>&1 +got=$?; exp=2 +(( got == exp )) || err_exit "cd -eP to empty string has wrong exit status (expected $exp, got $got)" + # ====== exit $((Errors<125?Errors:125))