pathchk.c revision 34f9b3eef6fdadbda0a846aa4d68691ac40eace5
/***********************************************************************
* *
* This software is part of the ast package *
* Copyright (c) 1992-2009 AT&T Intellectual Property *
* and is licensed under the *
* Common Public License, Version 1.0 *
* by AT&T Intellectual Property *
* *
* 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) 2009-07-24 $\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:components?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.]"
"}"
"[P:path?Write a diagnostic for each pathname operand that:]"
"{"
"[+-?Contains any component with \b-\b as the first character.]"
"[+-?Is empty.]"
"}"
"[a:all|portability?Equivalent to \b--components\b \b--path\b.]"
"\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 COMPONENTS 0x1
#define PATH 0x2
#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* const ops[] = { "NAME_MAX", "PATH_MAX" };
errno = 0;
if ((r = strtol(astconf(ops[op], path, NiL), NiL, 0)) < 0 && !errno)
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)
{
if (mode & PATH)
error(2,"path is empty");
return -1;
}
if(mode & COMPONENTS)
{
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 -1;
}
#endif /*ENAMETOOLONG*/
else
break;
}
}
while(*(cpold=cp))
{
if((mode & PATH) && *cp == '-')
{
error(2,"%s: path component begins with '-'",path,fmtquote(buf, NiL, "'", 1, 0));
return -1;
}
while((c= *cp++) && c!='/')
if((mode & COMPONENTS) && !isport(c))
{
buf[0] = c;
buf[1] = 0;
error(2,"%s: '%s' not in portable character set",path,fmtquote(buf, NiL, "'", 1, 0));
return -1;
}
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 -1;
}
return 0;
err:
error(2, "%s: component name %.*s too long", path, cp-cpold-1, cpold);
return -1;
}
int
b_pathchk(int argc, char** argv, void* context)
{
register int n;
register int mode = 0;
register char* s;
cmdinit(argc, argv, context, ERROR_CATALOG, 0);
for (;;)
{
switch (optget(argv, usage))
{
case 0:
break;
case 'a':
mode |= COMPONENTS|PATH;
continue;
case 'p':
mode |= COMPONENTS;
continue;
case 'P':
mode |= PATH;
continue;
case ':':
error(2, "%s", opt_info.arg);
continue;
case '?':
error(ERROR_usage(2), "%s", opt_info.arg);
continue;
}
break;
}
argv += opt_info.index;
if (!*argv || error_info.errors)
error(ERROR_usage(2),"%s", optusage(NiL));
while (s = *argv++)
pathchk(s, mode);
return error_info.errors != 0;
}