1/* See LICENSE file for copyright and license details. */
2
3
4#include <sys/stat.h>
5
6#include <errno.h>
7#include <limits.h>
8#include <stdint.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12
13#include "util.h"
14
15#define PORTABLE_CHARACTER_SET "0123456789._-qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"
16/* If your system supports more other characters, but not all non-NUL 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 p = estrdup(filename);
48 *invalid_end = 0;
49 eprintf("%s: nonportable character '%s'\n", p, invalid);
50 }
51
52 /* Symlink error? Non-searchable directory? */
53 if (lstat(filename, &st) && errno != ENOENT) {
54 /* lstat rather than stat, so that if filename is a bad symlink, but
55 * all parents are OK, no error will be detected. */
56 eprintf("%s:", filename);
57 }
58
59 /* Too long pathname? */
60 maxlen = most ? _POSIX_PATH_MAX : PATH_MAX;
61 if (strlen(filename) >= maxlen)
62 eprintf("%s: is longer than %zu bytes\n", filename, maxlen);
63
64 /* Too long component? */
65 maxlen = most ? _POSIX_NAME_MAX : NAME_MAX;
66 for (p = filename; p; p = q) {
67 q = strchr(p, '/');
68 len = q ? (size_t)(q++ - p) : strlen(p);
69 if (len > maxlen)
70 eprintf("%s: includes component longer than %zu bytes\n",
71 filename, maxlen);
72 }
73
74 return 0;
75}
76
77static void
78usage(void)
79{
80 eprintf("usage: %s [-pP] filename...\n", argv0);
81}
82
83// ?man pathchk: check pathnames
84// ?man arguments: filename...
85// ?man verify that pathnames are valid and portable
86int
87main(int argc, char *argv[])
88{
89 int ret = 0;
90
91 ARGBEGIN {
92 // ?man -p: preserve file attributes
93 case 'p':
94 most = 1;
95 break;
96 // ?man -P: specify option flag
97 case 'P':
98 extra = 1;
99 break;
100 default:
101 usage();
102 } ARGEND
103
104 if (!argc)
105 usage();
106
107 for (; argc--; argv++)
108 ret |= pathchk(*argv);
109
110 return ret;
111}