pathchk.c revision da2e3ebdc1edfbc5028edf1354e7dd2fa69a7968
/***********************************************************************
* *
* This software is part of the ast package *
* Copyright (c) 1992-2007 AT&T Knowledge Ventures *
* and is licensed under the *
* Common Public License, Version 1.0 *
* by AT&T Knowledge Ventures *
* *
* A copy of the License is available at *
* http://www.opensource.org/licenses/cpl1.0.txt *
* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) *
* *
* Information and Software Systems Research *
* AT&T Research *
* Florham Park NJ *
* *
* Glenn Fowler <gsf@research.att.com> *
* David Korn <dgk@research.att.com> *
* *
***********************************************************************/
#pragma prototyped
/*
* pathchk
*
* Written by David Korn
*/
static const char usage[] =
"[-?\n@(#)$Id: pathchk (AT&T Research) 2006-09-19 $\n]"
USAGE_LICENSE
"[+NAME?pathchk - check pathnames for portability]"
"[+DESCRIPTION?\bpathchk\b checks each \apathname\a to see if it "
"is valid and/or portable. A \apathname\a is valid if it "
"can be used to access or create a file without causing syntax "
"errors. A file is portable, if no truncation will result on "
"any conforming POSIX.1 implementation.]"
"[+?By default \bpathchk\b checks each component of each \apathname\a "
"based on the underlying file system. A diagnostic is written "
"to standard error for each pathname that:]{"
"[+-?Is longer than \b$(getconf PATH_MAX)\b bytes.]"
"[+-?Contains any component longer than \b$(getconf NAME_MAX)\b bytes.]"
"[+-?Contains any directory component in a directory that is "
"not searchable.]"
"[+-?Contains any character in any component that is not valid in "
"its containing directory.]"
"[+-?Is empty.]"
"}"
"[p:portability?Instead of performing length checks on the underlying "
"file system, write a diagnostic for each pathname operand that:]{"
"[+-?Is longer than \b$(getconf _POSIX_PATH_MAX)\b bytes.]"
"[+-?Contains any component longer than "
"\b$(getconf _POSIX_NAME_MAX)\b bytes.]"
"[+-?Contains any character in any component that is not in the "
"portable filename character set.]"
#if 0
"[+-?Contains any component with \b-\b as the first character.]"
#endif
"[+-?Is empty.]"
"}"
"\n"
"\npathname ...\n"
"\n"
"[+EXIT STATUS?]{"
"[+0?All \apathname\a operands passed all of the checks.]"
"[+>0?An error occurred.]"
"}"
"[+SEE ALSO?\bgetconf\b(1), \bcreat\b(2), \bpathchk\b(2)]"
;
#include <cmd.h>
#include <ls.h>
#define isport(c) (((c)>='a' && (c)<='z') || ((c)>='A' && (c)<='Z') || ((c)>='0' && (c)<='9') || (strchr("._-",(c))!=0) )
/*
* call pathconf and handle unlimited sizes
*/
static long mypathconf(const char *path, int op)
{
register long r;
static const char* ops[] = { "NAME_MAX", "PATH_MAX" };
errno=0;
if((r=strtol(astconf(ops[op], path, NiL), NiL, 0))<0 && errno==0)
return(LONG_MAX);
return(r);
}
/*
* returns 1 if <path> passes test
*/
static int pathchk(char* path, int mode)
{
register char *cp=path, *cpold;
register int c;
register long r,name_max,path_max;
char buf[2];
if(!*path)
{
error(2,"path is empty");
return(0);
}
if(mode)
{
name_max = _POSIX_NAME_MAX;
path_max = _POSIX_PATH_MAX;
}
else
{
char tmp[2];
name_max = path_max = 0;
tmp[0] = (*cp=='/'? '/': '.');
tmp[1] = 0;
if((r=mypathconf(tmp, 0)) > _POSIX_NAME_MAX)
name_max = r;
if((r=mypathconf(tmp, 1)) > _POSIX_PATH_MAX)
path_max = r;
if(*cp!='/')
{
if(name_max==0||path_max==0)
{
if(!(cpold = getcwd((char*)0, 0)) && errno == EINVAL && (cpold = newof(0, char, PATH_MAX, 0)) && !getcwd(cpold, PATH_MAX))
{
free(cpold);
cpold = 0;
}
if(cpold)
{
cp = cpold + strlen(cpold);
while(name_max==0 || path_max==0)
{
if(cp>cpold)
while(--cp>cpold && *cp=='/');
*++cp = 0;
if(name_max==0 && (r=mypathconf(cpold, 0)) > _POSIX_NAME_MAX)
name_max = r;
if(path_max==0 && (r=mypathconf(cpold, 1)) > _POSIX_PATH_MAX)
path_max=r;
if(--cp==cpold)
{
free(cpold);
break;
}
while(*cp!='/')
cp--;
}
cp=path;
}
}
while(*cp=='/')
cp++;
}
if(name_max==0)
name_max=_POSIX_NAME_MAX;
if(path_max==0)
path_max=_POSIX_PATH_MAX;
while(*(cpold=cp))
{
while((c= *cp++) && c!='/');
if((cp-cpold) > name_max)
goto err;
errno=0;
cp[-1] = 0;
r = mypathconf(path, 0);
if((cp[-1]=c)==0)
cp--;
else while(*cp=='/')
cp++;
if(r>=0)
name_max=(r<_POSIX_NAME_MAX?_POSIX_NAME_MAX:r);
else if(errno==EINVAL)
continue;
#ifdef ENAMETOOLONG
else if(errno==ENAMETOOLONG)
{
error(2,"%s: pathname too long",path);
return(0);
}
#endif /*ENAMETOOLONG*/
else
break;
}
}
while(*(cpold=cp))
{
if(mode && *cp == '-')
{
error(2,"%s: path component begins with '-'",path,fmtquote(buf, NiL, "'", 1, 0));
return(0);
}
while((c= *cp++) && c!='/')
if(mode && !isport(c))
{
buf[0] = c;
buf[1] = 0;
error(2,"%s: '%s' not in portable character set",path,fmtquote(buf, NiL, "'", 1, 0));
return(0);
}
if((cp-cpold) > name_max)
goto err;
if(c==0)
break;
while(*cp=='/')
cp++;
}
if((cp-path) >= path_max)
{
error(2,"%s: pathname too long",path);
return(0);
}
return(1);
err:
error(2,"%s: component name %.*s too long",path,cp-cpold-1,cpold);
return(0);
}
int
b_pathchk(int argc, char** argv, void* context)
{
register int n, mode=0;
register char *cp;
cmdinit(argc, argv, context, ERROR_CATALOG, 0);
while (n = optget(argv, usage)) switch (n)
{
case 'p':
mode = 1;
break;
case ':':
error(2, "%s", opt_info.arg);
break;
case '?':
error(ERROR_usage(2), "%s", opt_info.arg);
break;
}
argv += opt_info.index;
if(*argv==0 || error_info.errors)
error(ERROR_usage(2),"%s", optusage((char*)0));
while(cp = *argv++)
{
if(!pathchk(cp,mode))
error_info.errors=1;
}
return(error_info.errors);
}