1 /***********************************************************************
2 * *
3 * This software is part of the ast package *
4 * Copyright (c) 1992-2012 AT&T Intellectual Property *
5 * and is licensed under the *
6 * Eclipse Public License, Version 1.0 *
7 * by AT&T Intellectual Property *
8 * *
9 * A copy of the License is available at *
10 * http://www.eclipse.org/org/documents/epl-v10.html *
11 * (with md5 checksum b35adb5213ca9657e911e9befb180842) *
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
28 static const char usage[] =
29 "[-?\n@(#)$Id: pathchk (AT&T Research) 2009-07-24 $\n]"
30 USAGE_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 */
mypathconf(const char * path,int op)88 static 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 */
pathchk(char * path,int mode)103 static 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
230 int
b_pathchk(int argc,char ** argv,Shbltin_t * context)231 b_pathchk(int argc, char** argv, Shbltin_t* 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 'a':
242 mode |= COMPONENTS|PATH;
243 continue;
244 case 'p':
245 mode |= COMPONENTS;
246 continue;
247 case 'P':
248 mode |= PATH;
249 continue;
250 case ':':
251 error(2, "%s", opt_info.arg);
252 break;
253 case '?':
254 error(ERROR_usage(2), "%s", opt_info.arg);
255 break;
256 }
257 break;
258 }
259 argv += opt_info.index;
260 if (!*argv || error_info.errors)
261 error(ERROR_usage(2),"%s", optusage(NiL));
262 while (s = *argv++)
263 pathchk(s, mode);
264 return error_info.errors != 0;
265 }
266