ftwin 0.8.10
ft_traverse.c
1#include <apr_file_io.h>
2#include <apr_strings.h>
3#include <pcre.h>
4
5#include "debug.h"
6#include "ft_traverse.h"
7#include "ft_types.h"
8#include "ft_config.h"
9
10#define MATCH_VECTOR_SIZE 210
11
12static apr_status_t traverse_recursive(ft_conf_t *conf, const char *filename, apr_pool_t *gc_pool, struct stats const *stats,
13 ft_ignore_context_t * parent_ctx)
14{
15 char errbuf[128];
16 apr_finfo_t finfo;
17 apr_dir_t *dir;
18 apr_int32_t statmask =
19 APR_FINFO_SIZE | APR_FINFO_MTIME | APR_FINFO_TYPE | APR_FINFO_USER | APR_FINFO_GROUP | APR_FINFO_UPROT |
20 APR_FINFO_GPROT;
21 apr_size_t fname_len;
22 apr_uint32_t hash_value;
23 apr_status_t status;
24
25 if (!is_option_set(conf->mask, OPTION_FSYML)) {
26 statmask |= APR_FINFO_LINK;
27 }
28
29 if (APR_SUCCESS != (status = apr_stat(&finfo, filename, statmask, gc_pool))) {
30 if (is_option_set(conf->mask, OPTION_FSYML)) {
31 statmask ^= APR_FINFO_LINK;
32 if ((APR_SUCCESS == apr_stat(&finfo, filename, statmask, gc_pool)) && (finfo.filetype & APR_LNK)) {
33 if (is_option_set(conf->mask, OPTION_VERBO)) {
34 (void) fprintf(stderr, "Skipping : [%s] (broken link)\n", filename);
35 }
36 return APR_SUCCESS;
37 }
38 }
39
40 DEBUG_ERR("error calling apr_stat on filename %s : %s", filename, apr_strerror(status, errbuf, 128));
41 return status;
42 }
43
44 if (conf->respect_gitignore && parent_ctx) {
45 ft_ignore_match_result_t match = ft_ignore_match(parent_ctx, filename, finfo.filetype == APR_DIR);
46 if (match == FT_IGNORE_MATCH_IGNORED) {
47 if (is_option_set(conf->mask, OPTION_VERBO)) {
48 (void) fprintf(stderr, "Ignoring (gitignore): [%s]\n", filename);
49 }
50 return APR_SUCCESS;
51 }
52 }
53
54 if (0 != conf->userid) {
55 if (finfo.user == conf->userid) {
56 if (!(APR_UREAD & finfo.protection)) {
57 if (is_option_set(conf->mask, OPTION_VERBO)) {
58 (void) fprintf(stderr, "Skipping : [%s] (bad permission)\n", filename);
59 }
60 return APR_SUCCESS;
61 }
62 }
63 else if (NULL != napr_hash_search(conf->gids, &finfo.group, sizeof(gid_t), &hash_value)) {
64 if (!(APR_GREAD & finfo.protection)) {
65 if (is_option_set(conf->mask, OPTION_VERBO)) {
66 (void) fprintf(stderr, "Skipping : [%s] (bad permission)\n", filename);
67 }
68 return APR_SUCCESS;
69 }
70 }
71 else if (!(APR_WREAD & finfo.protection)) {
72 if (is_option_set(conf->mask, OPTION_VERBO)) {
73 (void) fprintf(stderr, "Skipping : [%s] (bad permission)\n", filename);
74 }
75 return APR_SUCCESS;
76 }
77 }
78
79 if (APR_DIR == finfo.filetype) {
80 if (0 != conf->userid) {
81 if (finfo.user == conf->userid) {
82 if (!(APR_UEXECUTE & finfo.protection)) {
83 if (is_option_set(conf->mask, OPTION_VERBO)) {
84 (void) fprintf(stderr, "Skipping : [%s] (bad permission)\n", filename);
85 }
86 return APR_SUCCESS;
87 }
88 }
89 else if (NULL != napr_hash_search(conf->gids, &finfo.group, sizeof(gid_t), &hash_value)) {
90 if (!(APR_GEXECUTE & finfo.protection)) {
91 if (is_option_set(conf->mask, OPTION_VERBO)) {
92 (void) fprintf(stderr, "Skipping : [%s] (bad permission)\n", filename);
93 }
94 return APR_SUCCESS;
95 }
96 }
97 else if (!(APR_WEXECUTE & finfo.protection)) {
98 if (is_option_set(conf->mask, OPTION_VERBO)) {
99 (void) fprintf(stderr, "Skipping : [%s] (bad permission)\n", filename);
100 }
101 return APR_SUCCESS;
102 }
103 }
104
105 if (APR_SUCCESS != (status = apr_dir_open(&dir, filename, gc_pool))) {
106 DEBUG_ERR("error calling apr_dir_open(%s): %s", filename, apr_strerror(status, errbuf, 128));
107 return status;
108 }
109 fname_len = strlen(filename);
110
111 ft_ignore_context_t *current_ctx = parent_ctx;
112 if (conf->respect_gitignore) {
113 const char *gitignore_path = apr_pstrcat(gc_pool, filename, "/.gitignore", NULL);
114 apr_finfo_t gitignore_finfo;
115
116 if (APR_SUCCESS == apr_stat(&gitignore_finfo, gitignore_path, APR_FINFO_TYPE, gc_pool)
117 && gitignore_finfo.filetype == APR_REG) {
118 ft_ignore_context_t *local_ctx = ft_ignore_context_create(gc_pool, parent_ctx, filename);
119 if (APR_SUCCESS == ft_ignore_load_file(local_ctx, gitignore_path)) {
120 current_ctx = local_ctx;
121 if (is_option_set(conf->mask, OPTION_VERBO)) {
122 (void) fprintf(stderr, "Loaded .gitignore from: [%s]\n", filename);
123 }
124 }
125 }
126 }
127 while ((APR_SUCCESS == (status = apr_dir_read(&finfo, APR_FINFO_NAME | APR_FINFO_TYPE, dir)))
128 && (NULL != finfo.name)) {
129 char *fullname;
130 apr_size_t fullname_len;
131 struct stats child;
132 struct stats const *ancestor;
133
134 if (NULL != napr_hash_search(conf->ig_files, finfo.name, strlen(finfo.name), NULL)) {
135 continue;
136 }
137
138 if ('.' == finfo.name[0] && !is_option_set(conf->mask, OPTION_SHOW_HIDDEN)) {
139 continue;
140 }
141
142 if (APR_DIR == finfo.filetype && !is_option_set(conf->mask, OPTION_RECSD)) {
143 continue;
144 }
145
146 fullname = apr_pstrcat(gc_pool, filename, ('/' == filename[fname_len - 1]) ? "" : "/", finfo.name, NULL);
147 fullname_len = strlen(fullname);
148
149 if ((NULL != conf->ig_regex) && (APR_DIR != finfo.filetype)) {
150 int match_code;
151 int ovector[MATCH_VECTOR_SIZE];
152 match_code = pcre_exec(conf->ig_regex, NULL, fullname, fullname_len, 0, 0, ovector, MATCH_VECTOR_SIZE);
153 if (match_code >= 0) {
154 continue;
155 }
156 }
157
158 if ((NULL != conf->wl_regex) && (APR_DIR != finfo.filetype)) {
159 int match_code;
160 int ovector[MATCH_VECTOR_SIZE];
161 match_code = pcre_exec(conf->wl_regex, NULL, fullname, fullname_len, 0, 0, ovector, MATCH_VECTOR_SIZE);
162 if (match_code < 0) {
163 continue;
164 }
165 }
166
167 if (stats) {
168 if (stats->stat.inode) {
169 for (ancestor = stats; (ancestor = ancestor->parent) != 0;) {
170 if (ancestor->stat.inode == stats->stat.inode && ancestor->stat.device == stats->stat.device) {
171 if (is_option_set(conf->mask, OPTION_VERBO)) {
172 (void) fprintf(stderr, "Warning: %s: recursive directory loop\n", filename);
173 }
174 return APR_SUCCESS;
175 }
176 }
177 }
178 }
179 child.parent = stats;
180 child.stat = finfo;
181
182 status = traverse_recursive(conf, fullname, gc_pool, &child, current_ctx);
183
184 if (APR_SUCCESS != status) {
185 DEBUG_ERR("error recursively calling traverse_recursive: %s", apr_strerror(status, errbuf, 128));
186 return status;
187 }
188 }
189 if ((APR_SUCCESS != status) && (APR_ENOENT != status)) {
190 DEBUG_ERR("error calling apr_dir_read: %s", apr_strerror(status, errbuf, 128));
191 return status;
192 }
193
194 if (APR_SUCCESS != (status = apr_dir_close(dir))) {
195 DEBUG_ERR("error calling apr_dir_close: %s", apr_strerror(status, errbuf, 128));
196 return status;
197 }
198 }
199 else if (APR_REG == finfo.filetype || ((APR_LNK == finfo.filetype) && (is_option_set(conf->mask, OPTION_FSYML)))) {
200 if (finfo.size >= conf->minsize && (conf->maxsize == 0 || finfo.size <= conf->maxsize)) {
201 ft_file_t *file;
202 ft_fsize_t *fsize;
203
204 file = apr_palloc(conf->pool, sizeof(struct ft_file_t));
205 file->path = apr_pstrdup(conf->pool, filename);
206 file->size = finfo.size;
207 file->mtime = finfo.mtime;
208
209 if ((conf->p_path) && (strlen(filename) >= conf->p_path_len)
210 && ((is_option_set(conf->mask, OPTION_ICASE) && !strncasecmp(filename, conf->p_path, conf->p_path_len))
211 || (!is_option_set(conf->mask, OPTION_ICASE) && !memcmp(filename, conf->p_path, conf->p_path_len)))) {
212 file->prioritized |= 0x1;
213 }
214 else {
215 file->prioritized &= 0x0;
216 }
217 file->cvec_ok &= 0x0;
218 napr_heap_insert(conf->heap, file);
219
220 if (NULL == (fsize = napr_hash_search(conf->sizes, &finfo.size, sizeof(apr_off_t), &hash_value))) {
221 fsize = apr_palloc(conf->pool, sizeof(struct ft_fsize_t));
222 fsize->val = finfo.size;
223 fsize->chksum_array = NULL;
224 fsize->nb_checksumed = 0;
225 fsize->nb_files = 0;
226 napr_hash_set(conf->sizes, fsize, hash_value);
227 }
228 fsize->nb_files++;
229 }
230 }
231
232 return APR_SUCCESS;
233}
234
235apr_status_t ft_traverse_path(ft_conf_t *conf, const char *path)
236{
237 apr_pool_t *gc_pool;
238 apr_status_t status;
239
240 if (APR_SUCCESS != (status = apr_pool_create(&gc_pool, conf->pool))) {
241 char errbuf[128];
242 DEBUG_ERR("error calling apr_pool_create: %s", apr_strerror(status, errbuf, 128));
243 return status;
244 }
245
246 status = traverse_recursive(conf, path, gc_pool, NULL, conf->global_ignores);
247
248 apr_pool_destroy(gc_pool);
249
250 return status;
251}
UTIL debug output macros.
#define DEBUG_ERR(str, arg...)
Display error message at the level error.
Definition debug.h:31
ft_ignore_context_t * ft_ignore_context_create(apr_pool_t *pool, ft_ignore_context_t *parent, const char *base_dir)
Creates a new ignore context.
Definition ft_ignore.c:184
apr_status_t ft_ignore_load_file(ft_ignore_context_t *ctx, const char *filepath)
Loads and parses an ignore file (like .gitignore) into a context.
Definition ft_ignore.c:239
ft_ignore_match_result_t ft_ignore_match(ft_ignore_context_t *ctx, const char *fullpath, int is_dir)
Checks if a given path should be ignored based on the hierarchical context.
Definition ft_ignore.c:268
ft_ignore_match_result_t
Result codes for an ignore match operation.
Definition ft_ignore.h:63
@ FT_IGNORE_MATCH_IGNORED
The path is matched by an ignore pattern.
Definition ft_ignore.h:65
apr_status_t napr_hash_set(napr_hash_t *hash, void *data, apr_uint32_t hash_value)
Inserts or updates an item in the hash table.
Definition napr_hash.c:242
void * napr_hash_search(napr_hash_t *hash, const void *key, apr_size_t key_len, apr_uint32_t *hash_value)
Searches the hash table for an item.
Definition napr_hash.c:145
int napr_heap_insert(napr_heap_t *heap, void *datum)
Inserts an element into the heap, maintaining the heap property.
Definition napr_heap.c:70
Main configuration structure for the ftwin application.
Definition ft_config.h:94
Represents the ignore rules for a specific directory and its descendants.
Definition ft_ignore.h:51