1/* See LICENSE file for copyright and license details. */
2
3#include <sys/stat.h>
4
5#include <errno.h>
6#include <limits.h>
7#include <stdint.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11
12#include "util.h"
13
14#define PORTABLE_CHARACTER_SET "0123456789._-qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"
15/* If your system supports more other characters, but not all non-NUL
16 * characters, define SYSTEM_CHARACTER_SET. */
17
18static int most = 0;
19static int extra = 0;
20
21static int
22pathchk(char *filename)
23{
24 char *invalid, *invalid_end, *p, *q;
25 const char *character_set;
26 size_t len, maxlen;
27 struct stat st;
28
29 /* Empty? */
30 if (extra && !*filename)
31 eprintf("empty filename\n");
32
33 /* Leading hyphen? */
34 if (extra && ((*filename == '-') || strstr(filename, "/-")))
35 eprintf("%s: leading '-' in component of filename\n", filename);
36
37 /* Nonportable character? */
38#ifdef SYSTEM_CHARACTER_SET
39 character_set = "/" SYSTEM_CHARACTER_SET;
40#else
41 character_set = 0;
42#endif
43 if (most)
44 character_set = "/" PORTABLE_CHARACTER_SET;
45 if (character_set && *(invalid = filename + strspn(filename, character_set))) {
46 for (invalid_end = invalid + 1; *invalid_end & 0x80; invalid_end++)
47 ;
48 p = estrdup(filename);
49 *invalid_end = 0;
50 eprintf("%s: nonportable character '%s'\n", p, invalid);
51 }
52
53 /* Symlink error? Non-searchable directory? */
54 if (lstat(filename, &st) && errno != ENOENT) {
55 /* lstat rather than stat, so that if filename is a bad symlink,
56 * but all parents are OK, no error will be detected. */
57 eprintf("%s:", filename);
58 }
59
60 /* Too long pathname? */
61 maxlen = most ? _POSIX_PATH_MAX : PATH_MAX;
62 if (strlen(filename) >= maxlen)
63 eprintf("%s: is longer than %zu bytes\n", filename, maxlen);
64
65 /* Too long component? */
66 maxlen = most ? _POSIX_NAME_MAX : NAME_MAX;
67 for (p = filename; p; p = q) {
68 q = strchr(p, '/');
69 len = q ? (size_t)(q++ - p) : strlen(p);
70 if (len > maxlen)
71 eprintf(
72 "%s: includes component longer than %zu "
73 "bytes\n",
74 filename,
75 maxlen
76 );
77 }
78
79 return 0;
80}
81
82static void
83usage(void)
84{
85 eprintf("usage: %s [-pP] filename...\n", argv0);
86}
87
88// ?man pathchk: check pathnames
89// ?man arguments: filename...
90// ?man verify that pathnames are valid and portable
91int
92main(int argc, char *argv[])
93{
94 int ret = 0;
95
96 ARGBEGIN
97 {
98 // ?man -p: preserve file attributes
99 case 'p':
100 most = 1;
101 break;
102 // ?man -P: specify option flag
103 case 'P':
104 extra = 1;
105 break;
106 default:
107 usage();
108 }
109 ARGEND
110
111 if (!argc)
112 usage();
113
114 for (; argc--; argv++)
115 ret |= pathchk(*argv);
116
117 return ret;
118}