1 /***********************************************************************
2  *                                                                      *
3  *               This software is part of the ast package               *
4  *          Copyright (c) 1985-2011 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 <glenn.s.fowler@gmail.com>                *
18  *                    David Korn <dgkorn@gmail.com>                     *
19  *                     Phong Vo <phongvo@gmail.com>                     *
20  *                                                                      *
21  ***********************************************************************/
22 //
23 // Glenn Fowler
24 // AT&T Research
25 //
26 // Return 1 if path exisis
27 // Maintains a cache to minimize stat(2) calls
28 // Path is modified in-place but restored on return
29 // Path components checked in pairs to cut stat()'s
30 // in half by checking ENOTDIR vs. ENOENT
31 // Case ignorance infection unavoidable here
32 //
33 #include "config_ast.h"  // IWYU pragma: keep
34 
35 #include <errno.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <sys/stat.h>
39 
40 #include "ast.h"
41 
42 // This tree caches name and modes of files and directories
43 typedef struct Tree_s {
44     // `next` points to next subdirectory of parent directory
45     struct Tree_s *next;
46     // If current node is a directory, `name` and `modes` of it's subdirectories and files are
47     // cached under this tree
48     struct Tree_s *tree;
49     // Modes can be PATH_READ, PATH_WRITE and PATH_EXECUTE
50     int mode;
51     char name[1];
52 } Tree_t;
53 
pathexists(char * path,int mode)54 int pathexists(char *path, int mode) {
55     // Path is splitted by '/''s and each component is processed seaprately
56     // This variable points to current component of path that is being processed
57     char *current_path_component;
58 
59     // Next path component
60     char *next_path_component;
61 
62     // Points to next slash in path
63     char *next_slash;
64 
65     // Points to parent directory
66     Tree_t *parent_tree;
67     Tree_t *current_tree;
68 
69     int c;
70     int cc;
71     int x;
72     struct stat statbuf;
73 
74     static Tree_t tree;
75 
76     current_tree = &tree;
77     // If path starts with `/`, initialize next slash character after first character
78     next_slash = (c = *path) == '/' ? path + 1 : path;
79     while (c) {
80         parent_tree = current_tree;
81         // Try to search for next slash character
82         for (current_path_component = next_slash; *next_slash && *next_slash != '/'; next_slash++) {
83             ;
84         }
85 
86         // Save value pointed by `next_slash` variable
87         c = *next_slash;
88         // and put a null character there to mark end of path, so `foo/bar/baz` becomes `foo`
89         *next_slash = 0;
90         for (current_tree = parent_tree->tree;
91              current_tree && strcmp(current_path_component, current_tree->name);
92              current_tree = current_tree->next) {
93             ;
94         }
95 
96         // Path with name `current_path_name` does not exist in tree, time to create a new node.
97         if (!current_tree) {
98             current_tree = calloc(1, sizeof(Tree_t) + strlen(current_path_component));
99             if (!current_tree) {
100                 *next_slash = c;
101                 return 0;
102             }
103             strcpy(current_tree->name, current_path_component);
104             current_tree->next = parent_tree->tree;
105             parent_tree->tree = current_tree;
106 
107             // If `c` is set, we are not at end of path
108             if (c) {
109                 *next_slash = c;
110                 for (current_path_component = next_path_component = next_slash + 1;
111                      *next_path_component && *next_path_component != '/'; next_path_component++) {
112                     ;
113                 }
114                 cc = *next_path_component;
115                 *next_path_component = 0;
116             } else {
117                 next_path_component = 0;
118             }
119             x = stat(path, &statbuf);
120 
121             // Create a new node for subdirectory (or file)
122             if (next_path_component) {
123                 Tree_t *new_tree;
124                 next_slash = next_path_component;
125                 c = cc;
126                 if (!x || errno == ENOENT) current_tree->mode = PATH_READ | PATH_EXECUTE;
127                 new_tree = calloc(1, sizeof(Tree_t) + strlen(current_path_component));
128                 if (!new_tree) {
129                     *next_slash = c;
130                     return 0;
131                 }
132                 strcpy(new_tree->name, current_path_component);
133                 new_tree->next = current_tree->tree;
134                 current_tree->tree = new_tree;
135                 // Set new node as current node
136                 current_tree = new_tree;
137             }
138             if (x) {
139                 *next_slash = c;
140                 return 0;
141             }
142             if (statbuf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) current_tree->mode |= PATH_READ;
143             if (statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)) current_tree->mode |= PATH_WRITE;
144             if (statbuf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) current_tree->mode |= PATH_EXECUTE;
145             if (!S_ISDIR(statbuf.st_mode)) current_tree->mode |= PATH_REGULAR;
146         }
147 
148         // Restore `/` character
149         *next_slash++ = c;
150         if (!current_tree->mode || (c && (current_tree->mode & PATH_REGULAR))) return 0;
151     }
152     mode &= (PATH_READ | PATH_WRITE | PATH_EXECUTE | PATH_REGULAR);
153     return (current_tree->mode & mode) == mode;
154 }
155