1/* pathchk -- check whether file names are valid or portable 2 Copyright (C) 1991-2010 Free Software Foundation, Inc. 3 4 This program is free software: you can redistribute it and/or modify 5 it under the terms of the GNU General Public License as published by 6 the Free Software Foundation, either version 3 of the License, or 7 (at your option) any later version. 8 9 This program is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 GNU General Public License for more details. 13 14 You should have received a copy of the GNU General Public License 15 along with this program. If not, see <http://www.gnu.org/licenses/>. */ 16 17#include <config.h> 18#include <stdio.h> 19#include <getopt.h> 20#include <sys/types.h> 21#if HAVE_WCHAR_H 22# include <wchar.h> 23#endif 24 25#include "system.h" 26#include "error.h" 27#include "quote.h" 28#include "quotearg.h" 29 30#if ! (HAVE_MBRLEN && HAVE_MBSTATE_T) 31# define mbrlen(s, n, ps) 1 32# define mbstate_t int 33#endif 34 35/* The official name of this program (e.g., no `g' prefix). */ 36#define PROGRAM_NAME "pathchk" 37 38#define AUTHORS \ 39 proper_name ("Paul Eggert"), \ 40 proper_name ("David MacKenzie"), \ 41 proper_name ("Jim Meyering") 42 43#ifndef _POSIX_PATH_MAX 44# define _POSIX_PATH_MAX 256 45#endif 46#ifndef _POSIX_NAME_MAX 47# define _POSIX_NAME_MAX 14 48#endif 49 50#ifdef _XOPEN_NAME_MAX 51# define NAME_MAX_MINIMUM _XOPEN_NAME_MAX 52#else 53# define NAME_MAX_MINIMUM _POSIX_NAME_MAX 54#endif 55#ifdef _XOPEN_PATH_MAX 56# define PATH_MAX_MINIMUM _XOPEN_PATH_MAX 57#else 58# define PATH_MAX_MINIMUM _POSIX_PATH_MAX 59#endif 60 61#if ! (HAVE_PATHCONF && defined _PC_NAME_MAX && defined _PC_PATH_MAX) 62# ifndef _PC_NAME_MAX 63# define _PC_NAME_MAX 0 64# define _PC_PATH_MAX 1 65# endif 66# ifndef pathconf 67# define pathconf(file, flag) \ 68 (flag == _PC_NAME_MAX ? NAME_MAX_MINIMUM : PATH_MAX_MINIMUM) 69# endif 70#endif 71 72static bool validate_file_name (char *, bool, bool); 73 74/* For long options that have no equivalent short option, use a 75 non-character as a pseudo short option, starting with CHAR_MAX + 1. */ 76enum 77{ 78 PORTABILITY_OPTION = CHAR_MAX + 1 79}; 80 81static struct option const longopts[] = 82{ 83 {"portability", no_argument, NULL, PORTABILITY_OPTION}, 84 {GETOPT_HELP_OPTION_DECL}, 85 {GETOPT_VERSION_OPTION_DECL}, 86 {NULL, 0, NULL, 0} 87}; 88 89void 90usage (int status) 91{ 92 if (status != EXIT_SUCCESS) 93 fprintf (stderr, _("Try `%s --help' for more information.\n"), 94 program_name); 95 else 96 { 97 printf (_("Usage: %s [OPTION]... NAME...\n"), program_name); 98 fputs (_("\ 99Diagnose invalid or unportable file names.\n\ 100\n\ 101 -p check for most POSIX systems\n\ 102 -P check for empty names and leading \"-\"\n\ 103 --portability check for all POSIX systems (equivalent to -p -P)\n\ 104"), stdout); 105 fputs (HELP_OPTION_DESCRIPTION, stdout); 106 fputs (VERSION_OPTION_DESCRIPTION, stdout); 107 emit_ancillary_info (); 108 } 109 exit (status); 110} 111 112int 113main (int argc, char **argv) 114{ 115 bool ok = true; 116 bool check_basic_portability = false; 117 bool check_extra_portability = false; 118 int optc; 119 120 initialize_main (&argc, &argv); 121 set_program_name (argv[0]); 122 setlocale (LC_ALL, ""); 123 bindtextdomain (PACKAGE, LOCALEDIR); 124 textdomain (PACKAGE); 125 126 atexit (close_stdout); 127 128 while ((optc = getopt_long (argc, argv, "+pP", longopts, NULL)) != -1) 129 { 130 switch (optc) 131 { 132 case PORTABILITY_OPTION: 133 check_basic_portability = true; 134 check_extra_portability = true; 135 break; 136 137 case 'p': 138 check_basic_portability = true; 139 break; 140 141 case 'P': 142 check_extra_portability = true; 143 break; 144 145 case_GETOPT_HELP_CHAR; 146 147 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); 148 149 default: 150 usage (EXIT_FAILURE); 151 } 152 } 153 154 if (optind == argc) 155 { 156 error (0, 0, _("missing operand")); 157 usage (EXIT_FAILURE); 158 } 159 160 for (; optind < argc; ++optind) 161 ok &= validate_file_name (argv[optind], 162 check_basic_portability, check_extra_portability); 163 164 exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); 165} 166 167/* If FILE contains a component with a leading "-", report an error 168 and return false; otherwise, return true. */ 169 170static bool 171no_leading_hyphen (char const *file) 172{ 173 char const *p; 174 175 for (p = file; (p = strchr (p, '-')); p++) 176 if (p == file || p[-1] == '/') 177 { 178 error (0, 0, _("leading `-' in a component of file name %s"), 179 quote (file)); 180 return false; 181 } 182 183 return true; 184} 185 186/* If FILE (of length FILELEN) contains only portable characters, 187 return true, else report an error and return false. */ 188 189static bool 190portable_chars_only (char const *file, size_t filelen) 191{ 192 size_t validlen = strspn (file, 193 ("/" 194 "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 195 "abcdefghijklmnopqrstuvwxyz" 196 "0123456789._-")); 197 char const *invalid = file + validlen; 198 199 if (*invalid) 200 { 201 DECLARE_ZEROED_AGGREGATE (mbstate_t, mbstate); 202 size_t charlen = mbrlen (invalid, filelen - validlen, &mbstate); 203 error (0, 0, 204 _("nonportable character %s in file name %s"), 205 quotearg_n_style_mem (1, locale_quoting_style, invalid, 206 (charlen <= MB_LEN_MAX ? charlen : 1)), 207 quote_n (0, file)); 208 return false; 209 } 210 211 return true; 212} 213 214/* Return the address of the start of the next file name component in F. */ 215 216static char * 217component_start (char *f) 218{ 219 while (*f == '/') 220 f++; 221 return f; 222} 223 224/* Return the size of the file name component F. F must be nonempty. */ 225 226static size_t 227component_len (char const *f) 228{ 229 size_t len; 230 for (len = 1; f[len] != '/' && f[len]; len++) 231 continue; 232 return len; 233} 234 235/* Make sure that 236 strlen (FILE) <= PATH_MAX 237 && strlen (each-existing-directory-in-FILE) <= NAME_MAX 238 239 If CHECK_BASIC_PORTABILITY is true, compare against _POSIX_PATH_MAX and 240 _POSIX_NAME_MAX instead, and make sure that FILE contains no 241 characters not in the POSIX portable filename character set, which 242 consists of A-Z, a-z, 0-9, ., _, - (plus / for separators). 243 244 If CHECK_BASIC_PORTABILITY is false, make sure that all leading directories 245 along FILE that exist are searchable. 246 247 If CHECK_EXTRA_PORTABILITY is true, check that file name components do not 248 begin with "-". 249 250 If either CHECK_BASIC_PORTABILITY or CHECK_EXTRA_PORTABILITY is true, 251 check that the file name is not empty. 252 253 Return true if all of these tests are successful, false if any fail. */ 254 255static bool 256validate_file_name (char *file, bool check_basic_portability, 257 bool check_extra_portability) 258{ 259 size_t filelen = strlen (file); 260 261 /* Start of file name component being checked. */ 262 char *start; 263 264 /* True if component lengths need to be checked. */ 265 bool check_component_lengths; 266 267 /* True if the file is known to exist. */ 268 bool file_exists = false; 269 270 if (check_extra_portability && ! no_leading_hyphen (file)) 271 return false; 272 273 if ((check_basic_portability || check_extra_portability) 274 && filelen == 0) 275 { 276 /* Fail, since empty names are not portable. As of 277 2005-01-06 POSIX does not address whether "pathchk -p ''" 278 should (or is allowed to) fail, so this is not a 279 conformance violation. */ 280 error (0, 0, _("empty file name")); 281 return false; 282 } 283 284 if (check_basic_portability) 285 { 286 if (! portable_chars_only (file, filelen)) 287 return false; 288 } 289 else 290 { 291 /* Check whether a file name component is in a directory that 292 is not searchable, or has some other serious problem. 293 POSIX does not allow "" as a file name, but some non-POSIX 294 hosts do (as an alias for "."), so allow "" if lstat does. */ 295 296 struct stat st; 297 if (lstat (file, &st) == 0) 298 file_exists = true; 299 else if (errno != ENOENT || filelen == 0) 300 { 301 error (0, errno, "%s", file); 302 return false; 303 } 304 } 305 306 if (check_basic_portability 307 || (! file_exists && PATH_MAX_MINIMUM <= filelen)) 308 { 309 size_t maxsize; 310 311 if (check_basic_portability) 312 maxsize = _POSIX_PATH_MAX; 313 else 314 { 315 long int size; 316 char const *dir = (*file == '/' ? "/" : "."); 317 errno = 0; 318 size = pathconf (dir, _PC_PATH_MAX); 319 if (size < 0 && errno != 0) 320 { 321 error (0, errno, 322 _("%s: unable to determine maximum file name length"), 323 dir); 324 return false; 325 } 326 maxsize = MIN (size, SSIZE_MAX); 327 } 328 329 if (maxsize <= filelen) 330 { 331 unsigned long int len = filelen; 332 unsigned long int maxlen = maxsize - 1; 333 error (0, 0, _("limit %lu exceeded by length %lu of file name %s"), 334 maxlen, len, quote (file)); 335 return false; 336 } 337 } 338 339 /* Check whether pathconf (..., _PC_NAME_MAX) can be avoided, i.e., 340 whether all file name components are so short that they are valid 341 in any file system on this platform. If CHECK_BASIC_PORTABILITY, though, 342 it's more convenient to check component lengths below. */ 343 344 check_component_lengths = check_basic_portability; 345 if (! check_component_lengths && ! file_exists) 346 { 347 for (start = file; *(start = component_start (start)); ) 348 { 349 size_t length = component_len (start); 350 351 if (NAME_MAX_MINIMUM < length) 352 { 353 check_component_lengths = true; 354 break; 355 } 356 357 start += length; 358 } 359 } 360 361 if (check_component_lengths) 362 { 363 /* The limit on file name components for the current component. 364 This defaults to NAME_MAX_MINIMUM, for the sake of non-POSIX 365 systems (NFS, say?) where pathconf fails on "." or "/" with 366 errno == ENOENT. */ 367 size_t name_max = NAME_MAX_MINIMUM; 368 369 /* If nonzero, the known limit on file name components. */ 370 size_t known_name_max = (check_basic_portability ? _POSIX_NAME_MAX : 0); 371 372 for (start = file; *(start = component_start (start)); ) 373 { 374 size_t length; 375 376 if (known_name_max) 377 name_max = known_name_max; 378 else 379 { 380 long int len; 381 char const *dir = (start == file ? "." : file); 382 char c = *start; 383 errno = 0; 384 *start = '\0'; 385 len = pathconf (dir, _PC_NAME_MAX); 386 *start = c; 387 if (0 <= len) 388 name_max = MIN (len, SSIZE_MAX); 389 else 390 switch (errno) 391 { 392 case 0: 393 /* There is no limit. */ 394 name_max = SIZE_MAX; 395 break; 396 397 case ENOENT: 398 /* DIR does not exist; use its parent's maximum. */ 399 known_name_max = name_max; 400 break; 401 402 default: 403 *start = '\0'; 404 error (0, errno, "%s", dir); 405 *start = c; 406 return false; 407 } 408 } 409 410 length = component_len (start); 411 412 if (name_max < length) 413 { 414 unsigned long int len = length; 415 unsigned long int maxlen = name_max; 416 char c = start[len]; 417 start[len] = '\0'; 418 error (0, 0, 419 _("limit %lu exceeded by length %lu " 420 "of file name component %s"), 421 maxlen, len, quote (start)); 422 start[len] = c; 423 return false; 424 } 425 426 start += length; 427 } 428 } 429 430 return true; 431} 432