Skip to content

Add -z|--zero options to basename and dirname builtins and fix support for PATH_LEADING_SLASHES (plus small buffer overflow fix) #800

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Dec 26, 2024
18 changes: 12 additions & 6 deletions src/cmd/ksh93/sh/arith.c
Original file line number Diff line number Diff line change
Expand Up @@ -194,24 +194,30 @@ static Namval_t *scope(Namval_t *np,struct lval *lvalue,int assign)
return np;
}

static Math_f sh_mathstdfun(const char *fname, size_t fsize, short * nargs)
/* lookup a function in the standard math function table */
static Math_f sh_mathstdfun(const char *fname, size_t fsize, short *nargs)
{
const struct mathtab *tp;
char c = fname[0];
for(tp=shtab_math; *tp->fname; tp++)
char firstc = fname[0];
/* first byte of tp->fname is num. args and return type, unless empty */
for(tp=shtab_math; tp->fname[0]=='\0'; tp++)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is incorrect; it inverts the sense of the second for expression, so that no math function is ever found. That for statement should not change.

{
if(*tp->fname > c)
/* shtab_math is in alphabetic order - if first character is greater, we're done */
if(tp->fname[1] > firstc)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is incorrect, because it assumes that shtab_math is in alphabetical order. Unfortunately, a look at the generated shtab_math in arch/*/src/cmd/ksh93/FEATURE/math shows that it is not sorted, alphabetically or otherwise.

What on earth AT&T meant to accomplish with the original if(*tp->fname > c) is a mystery. In practice, it's a no-op. The first byte of fname is non-printable and never greater than octal 12, i.e. 10 (going by the generated shtab_math on my machine). It is compared against a printable ASCII character which will always be greater than that, so the break is never reached. Those two lines can be deleted.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment provided for historical interest; feel free to ignore.

I was curious why the if(*tp->fname > c) break; nonsense referenced above was there, so I did a little research in the ast-open-archive repo.

In the earliest available version (1995), shtab_math was in fact hardcoded and sorted alphabetically, and the fname field was a simple name without a preceding byte with option bits.

By the next available version from 1999, shtab_math had transitioned to its current form, but that if was still there, now nonsensical, and by pure luck a no-op. So it looks like they simply forgot to delete it, and there it remained until now.

break;
if(tp->fname[1]==c && tp->fname[fsize+1]==0 && strncmp(&tp->fname[1],fname,fsize)==0)
if(tp->fname[1]==firstc && strncmp(&tp->fname[1],fname,fsize)==0 && tp->fname[fsize+1]=='\0')
{
if(nargs)
*nargs = *tp->fname;
{
*nargs = tp->fname[0];
}
return tp->fnptr;
}
}
return NULL;
}


int sh_mathstd(const char *name)
{
return sh_mathstdfun(name,strlen(name),NULL)!=0;
Expand Down
56 changes: 41 additions & 15 deletions src/lib/libcmd/basename.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ static const char usage[] =
"[s:suffix?All operands are treated as \astring\a and each modified "
"pathname, with \asuffix\a removed if it exists, is printed on a "
"separate line on the standard output.]:[suffix]"
"[z:zero?Each line of output is terminated with a NUL character instead "
"of a newline.]"
"\n"
"\n string [suffix]\n"
"string ...\n"
Expand All @@ -63,44 +65,59 @@ static const char usage[] =

#include <cmd.h>

static void namebase(Sfio_t *outfile, char *pathname, char *suffix)
static void l_basename(Sfio_t *outfile, const char *pathname, const char *suffix, char termch)
{
char *first, *last;
const char *first=pathname;
const char *last;
int n=0;
for(first=last=pathname; *last; last++);
/* back over trailing '/' */
/* go to end of path */
for(last=pathname; *last != '\0'; last++);
/* back over any trailing '/' */
if(last>first)
{
while(*--last=='/' && last > first);
if(last==first && *last=='/')
}
if(last==first && *first=='/') /* just a '/' */
{
/* all '/' or "" */
if(*first=='/')
if(*++last=='/') /* keep leading // */
last++;
/* advance back over first '/' */
last++;
/* preserve leading '//' if PATH_LEADING_SLASHES is set */
if(*last=='/' && *astconf("PATH_LEADING_SLASHES",NULL,NULL)=='1')
{
last++;
}
}
else
{
/* set to first / from end */
for(first=last++;first>pathname && *first!='/';first--);
if(*first=='/')
{
first++;
}
/* check for trailing suffix */
if(suffix && (n=strlen(suffix)) && n<(last-first))
{
if(memcmp(last-n,suffix,n)==0)
last -=n;
{
last -= n;
}
}
}
if(last>first)
{
sfwrite(outfile,first,last-first);
sfputc(outfile,'\n');
}
sfputc(outfile,termch);
}

int
b_basename(int argc, char** argv, Shbltin_t* context)
{
char* string;
char* suffix = 0;
char *string;
char *suffix = 0;
int all = 0;
char termch = '\n';

cmdinit(argc, argv, context, ERROR_CATALOG, 0);
for (;;)
Expand All @@ -114,6 +131,9 @@ b_basename(int argc, char** argv, Shbltin_t* context)
all = 1;
suffix = opt_info.arg;
continue;
case 'z':
termch = '\0';
continue;
case ':':
error(2, "%s", opt_info.arg);
break;
Expand All @@ -131,9 +151,15 @@ b_basename(int argc, char** argv, Shbltin_t* context)
UNREACHABLE();
}
if (!all)
namebase(sfstdout, argv[0], argv[1]);
{
l_basename(sfstdout, argv[0], argv[1], termch);
}
else
{
while (string = *argv++)
namebase(sfstdout, string, suffix);
{
l_basename(sfstdout, string, suffix, termch);
}
}
return 0;
}
38 changes: 29 additions & 9 deletions src/lib/libcmd/dirname.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ static const char usage[] =
"[+?If \astring\a consists solely of \b/\b characters the output will "
"be a single \b/\b unless \bPATH_LEADING_SLASHES\b returned by "
"\bgetconf\b(1) is \b1\b and \astring\a consists of multiple "
"\b/\b characters in which case \b//\b will be output. "
"\b/\b characters in which case \b//\b will be output. "
"Otherwise, trailing \b/\b characters are removed, and if "
"there are no remaining \b/\b characters in \astring\a, "
"the string \b.\b will be written to standard output. "
Expand All @@ -48,6 +48,8 @@ static const char usage[] =
"[f:file?Print the \b$PATH\b relative regular file path for \astring\a.]"
"[r:relative?Print the \b$PATH\b relative readable file path for \astring\a.]"
"[x:executable?Print the \b$PATH\b relative executable file path for \astring\a.]"
"[z:zero?Each line of output is terminated with a NUL character instead "
"of a newline.]"
"\n"
"\nstring\n"
"\n"
Expand All @@ -60,7 +62,7 @@ static const char usage[] =

#include <cmd.h>

static void l_dirname(Sfio_t *outfile, const char *pathname)
static void l_dirname(Sfio_t *outfile, const char *pathname, char termch)
{
const char *last;
/* go to end of path */
Expand All @@ -73,30 +75,39 @@ static void l_dirname(Sfio_t *outfile, const char *pathname)
{
/* all '/' or "" */
if(*pathname!='/')
{
last = pathname = ".";
}
}
else
{
/* back over trailing '/' */
for(;*last=='/' && last > pathname; last--);
}
/* preserve // */
if(last!=pathname && pathname[0]=='/' && pathname[1]=='/')
/* preserve leading '//' */
if(pathname[0]=='/' && pathname[1]=='/')
{
/* skip any '/' until last two */
while(pathname[2]=='/' && pathname<last)
{
pathname++;
if(last!=pathname && pathname[0]=='/' && pathname[1]=='/' && *astconf("PATH_LEADING_SLASHES",NULL,NULL)!='1')
}
/* skip first '/' if PATH_LEADING_SLASHES not set */
if(pathname[0]=='/' && pathname[1]=='/' && *astconf("PATH_LEADING_SLASHES",NULL,NULL)!='1')
{
pathname++;
}
}
sfwrite(outfile,pathname,last+1-pathname);
sfputc(outfile,'\n');
sfputc(outfile,termch);
}

int
b_dirname(int argc, char** argv, Shbltin_t* context)
{
int mode = 0;
char buf[PATH_MAX];
char termch = '\n';

cmdinit(argc, argv, context, ERROR_CATALOG, 0);
for (;;)
Expand All @@ -113,6 +124,9 @@ b_dirname(int argc, char** argv, Shbltin_t* context)
case 'x':
mode |= PATH_EXECUTE;
continue;
case 'z':
termch = '\0';
continue;
case ':':
error(2, "%s", opt_info.arg);
break;
Expand All @@ -126,14 +140,20 @@ b_dirname(int argc, char** argv, Shbltin_t* context)
argc -= opt_info.index;
if(error_info.errors || argc != 1)
{
error(ERROR_usage(2),"%s", optusage(NULL));
error(ERROR_usage(2), "%s", optusage(NULL));
UNREACHABLE();
}
if(!mode)
l_dirname(sfstdout,argv[0]);
{
l_dirname(sfstdout,argv[0],termch);
}
else if(pathpath(argv[0], "", mode, buf, sizeof(buf)))
sfputr(sfstdout, buf, '\n');
{
sfputr(sfstdout, buf, termch);
}
else
{
error(1|ERROR_WARNING, "%s: relative path not found", argv[0]);
}
return 0;
}