1/***********************************************************************
2*                                                                      *
3*               This software is part of the ast package               *
4*          Copyright (c) 1992-2010 AT&T Intellectual Property          *
5*                      and is licensed under the                       *
6*                  Common Public License, Version 1.0                  *
7*                    by AT&T Intellectual Property                     *
8*                                                                      *
9*                A copy of the License is available at                 *
10*            http://www.opensource.org/licenses/cpl1.0.txt             *
11*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
12*                                                                      *
13*              Information and Software Systems Research               *
14*                            AT&T Research                             *
15*                           Florham Park NJ                            *
16*                                                                      *
17*                 Glenn Fowler <gsf@research.att.com>                  *
18*                  David Korn <dgk@research.att.com>                   *
19*                                                                      *
20***********************************************************************/
21#pragma prototyped
22/*
23 * pathchk
24 *
25 * Written by David Korn
26 */
27
28static const char usage[] =
29"[-?\n@(#)$Id: pathchk (AT&T Research) 2009-07-24 $\n]"
30USAGE_LICENSE
31"[+NAME?pathchk - check pathnames for portability]"
32"[+DESCRIPTION?\bpathchk\b checks each \apathname\a to see if it is "
33    "valid and/or portable. A \apathname\a is valid if it can be used to "
34    "access or create a file without causing syntax errors. A file is "
35    "portable if no truncation will result on any conforming POSIX.1 "
36    "implementation.]"
37"[+?By default \bpathchk\b checks each component of each \apathname\a "
38    "based on the underlying file system. A diagnostic is written to "
39    "standard error for each pathname that:]"
40    "{"
41        "[+-?Is longer than \b$(getconf PATH_MAX)\b bytes.]"
42        "[+-?Contains any component longer than \b$(getconf NAME_MAX)\b "
43            "bytes.]"
44        "[+-?Contains any directory component in a directory that is not "
45            "searchable.]"
46        "[+-?Contains any character in any component that is not valid "
47            "in its containing directory.]"
48        "[+-?Is empty.]"
49    "}"
50"[p:components?Instead of performing length checks on the underlying "
51    "file system, write a diagnostic for each pathname operand that:]"
52    "{"
53        "[+-?Is longer than \b$(getconf _POSIX_PATH_MAX)\b bytes.]"
54        "[+-?Contains any component longer than \b$(getconf "
55            "_POSIX_NAME_MAX)\b bytes.]"
56        "[+-?Contains any character in any component that is not in the "
57            "portable filename character set.]"
58    "}"
59"[P:path?Write a diagnostic for each pathname operand that:]"
60    "{"
61        "[+-?Contains any component with \b-\b as the first character.]"
62        "[+-?Is empty.]"
63    "}"
64"[a:all|portability?Equivalent to \b--components\b \b--path\b.]"
65"\n"
66"\npathname ...\n"
67"\n"
68"[+EXIT STATUS?]"
69    "{"
70        "[+0?All \apathname\a operands passed all of the checks.]"
71        "[+>0?An error occurred.]"
72    "}"
73"[+SEE ALSO?\bgetconf\b(1), \bcreat\b(2), \bpathchk\b(2)]"
74;
75
76
77#include	<cmd.h>
78#include	<ls.h>
79
80#define COMPONENTS	0x1
81#define PATH	0x2
82
83#define isport(c)	(((c)>='a' && (c)<='z') || ((c)>='A' && (c)<='Z') || ((c)>='0' && (c)<='9') || (strchr("._-",(c))!=0) )
84
85/*
86 * call pathconf and handle unlimited sizes
87 */
88static long mypathconf(const char *path, int op)
89{
90	register long			r;
91
92	static const char* const	ops[] = { "NAME_MAX", "PATH_MAX" };
93
94	errno = 0;
95	if ((r = strtol(astconf(ops[op], path, NiL), NiL, 0)) < 0 && !errno)
96		return LONG_MAX;
97	return r;
98}
99
100/*
101 * returns 1 if <path> passes test
102 */
103static int pathchk(char* path, int mode)
104{
105	register char *cp=path, *cpold;
106	register int c;
107	register long r,name_max,path_max;
108	char buf[2];
109
110	if(!*path)
111	{
112		if (mode & PATH)
113			error(2,"path is empty");
114		return -1;
115	}
116	if(mode & COMPONENTS)
117	{
118		name_max = _POSIX_NAME_MAX;
119		path_max = _POSIX_PATH_MAX;
120	}
121	else
122	{
123		char tmp[2];
124		name_max = path_max = 0;
125		tmp[0] = (*cp=='/'? '/': '.');
126		tmp[1] = 0;
127		if((r=mypathconf(tmp, 0)) > _POSIX_NAME_MAX)
128			name_max = r;
129		if((r=mypathconf(tmp, 1)) > _POSIX_PATH_MAX)
130			path_max = r;
131		if(*cp!='/')
132		{
133			if(name_max==0||path_max==0)
134			{
135				if(!(cpold = getcwd((char*)0, 0)) && errno == EINVAL && (cpold = newof(0, char, PATH_MAX, 0)) && !getcwd(cpold, PATH_MAX))
136				{
137					free(cpold);
138					cpold = 0;
139				}
140				if(cpold)
141				{
142					cp = cpold + strlen(cpold);
143					while(name_max==0 || path_max==0)
144					{
145						if(cp>cpold)
146							while(--cp>cpold && *cp=='/');
147						*++cp = 0;
148						if(name_max==0 && (r=mypathconf(cpold, 0)) > _POSIX_NAME_MAX)
149							name_max = r;
150						if(path_max==0 && (r=mypathconf(cpold, 1)) > _POSIX_PATH_MAX)
151							path_max=r;
152						if(--cp==cpold)
153						{
154							free(cpold);
155							break;
156						}
157						while(*cp!='/')
158							cp--;
159					}
160					cp=path;
161				}
162			}
163			while(*cp=='/')
164				cp++;
165		}
166		if(name_max==0)
167			name_max=_POSIX_NAME_MAX;
168		if(path_max==0)
169			path_max=_POSIX_PATH_MAX;
170		while(*(cpold=cp))
171		{
172			while((c= *cp++) && c!='/');
173			if((cp-cpold) > name_max)
174				goto err;
175			errno=0;
176			cp[-1] = 0;
177			r = mypathconf(path, 0);
178			if((cp[-1]=c)==0)
179				cp--;
180			else while(*cp=='/')
181				cp++;
182			if(r>=0)
183				name_max=(r<_POSIX_NAME_MAX?_POSIX_NAME_MAX:r);
184			else if(errno==EINVAL)
185				continue;
186#ifdef ENAMETOOLONG
187			else if(errno==ENAMETOOLONG)
188			{
189				error(2,"%s: pathname too long",path);
190				return -1;
191			}
192#endif /*ENAMETOOLONG*/
193			else
194				break;
195		}
196	}
197	while(*(cpold=cp))
198	{
199		if((mode & PATH) && *cp == '-')
200		{
201			error(2,"%s: path component begins with '-'",path,fmtquote(buf, NiL, "'", 1, 0));
202			return -1;
203		}
204		while((c= *cp++) && c!='/')
205			if((mode & COMPONENTS) && !isport(c))
206			{
207				buf[0] = c;
208				buf[1] = 0;
209				error(2,"%s: '%s' not in portable character set",path,fmtquote(buf, NiL, "'", 1, 0));
210				return -1;
211			}
212		if((cp-cpold) > name_max)
213			goto err;
214		if(c==0)
215			break;
216		while(*cp=='/')
217			cp++;
218	}
219	if((cp-path) >= path_max)
220	{
221		error(2, "%s: pathname too long", path);
222		return -1;
223	}
224	return 0;
225 err:
226	error(2, "%s: component name %.*s too long", path, cp-cpold-1, cpold);
227	return -1;
228}
229
230int
231b_pathchk(int argc, char** argv, void* context)
232{
233	register int	mode = 0;
234	register char*	s;
235
236	cmdinit(argc, argv, context, ERROR_CATALOG, 0);
237	for (;;)
238	{
239		switch (optget(argv, usage))
240		{
241		case 0:
242			break;
243  		case 'a':
244			mode |= COMPONENTS|PATH;
245			continue;
246  		case 'p':
247			mode |= COMPONENTS;
248			continue;
249  		case 'P':
250			mode |= PATH;
251			continue;
252		case ':':
253			error(2, "%s", opt_info.arg);
254			continue;
255		case '?':
256			error(ERROR_usage(2), "%s", opt_info.arg);
257			continue;
258		}
259		break;
260	}
261	argv += opt_info.index;
262	if (!*argv || error_info.errors)
263		error(ERROR_usage(2),"%s", optusage(NiL));
264	while (s = *argv++)
265		pathchk(s, mode);
266	return error_info.errors != 0;
267}
268