61f7cd6c951f6cf55f8e707259d486d798054acd
[protos/libecoli.git] / lib / ecoli_node_file.c
1 /*
2  * Copyright (c) 2016, Olivier MATZ <zer0@droids-corp.org>
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  *     * Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *     * Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *     * Neither the name of the University of California, Berkeley nor the
13  *       names of its contributors may be used to endorse or promote products
14  *       derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <errno.h>
34 #include <unistd.h>
35 #include <dirent.h>
36
37 #include <ecoli_log.h>
38 #include <ecoli_malloc.h>
39 #include <ecoli_test.h>
40 #include <ecoli_strvec.h>
41 #include <ecoli_string.h>
42 #include <ecoli_node.h>
43 #include <ecoli_parsed.h>
44 #include <ecoli_completed.h>
45 #include <ecoli_node_file.h>
46
47 EC_LOG_TYPE_REGISTER(node_file);
48
49 struct ec_node_file {
50         struct ec_node gen;
51
52         /* below functions pointers are only useful for test */
53         int (*lstat)(const char *pathname, struct stat *buf);
54         DIR *(*opendir)(const char *name);
55         struct dirent *(*readdir)(DIR *dirp);
56         int (*closedir)(DIR *dirp);
57         int (*dirfd)(DIR *dirp);
58         int (*fstatat)(int dirfd, const char *pathname, struct stat *buf,
59                 int flags);
60 };
61
62 static int
63 ec_node_file_parse(const struct ec_node *gen_node,
64                 struct ec_parsed *state,
65                 const struct ec_strvec *strvec)
66 {
67         (void)gen_node;
68         (void)state;
69
70         if (ec_strvec_len(strvec) == 0)
71                 return EC_PARSED_NOMATCH;
72
73         return 1;
74 }
75
76 /*
77  * Almost the same than dirname (3) and basename (3) except that:
78  * - it always returns a substring of the given path, which can
79  *   be empty.
80  * - the behavior is different when the path finishes with a '/'
81  * - the path argument is not modified
82  * - the outputs are allocated and must be freed with ec_free().
83  *
84  *   path       dirname   basename       split_path
85  *   /usr/lib   /usr      lib          /usr/     lib
86  *   /usr/      /         usr          /usr/
87  *   usr        .         usr                    usr
88  *   /          /         /            /
89  *   .          .         .                      .
90  *   ..         .         ..                     ..
91  */
92 static int split_path(const char *path, char **dname_p, char **bname_p)
93 {
94         char *last_slash;
95         size_t dirlen;
96         char *dname, *bname;
97
98         *dname_p = NULL;
99         *bname_p = NULL;
100
101         last_slash = strrchr(path, '/');
102         if (last_slash == NULL)
103                 dirlen = 0;
104         else
105                 dirlen = last_slash - path + 1;
106
107         dname = ec_strdup(path);
108         if (dname == NULL)
109                 return -1;
110         dname[dirlen] = '\0';
111
112         bname = ec_strdup(path + dirlen);
113         if (bname == NULL) {
114                 ec_free(dname);
115                 return -1;
116         }
117
118         *dname_p = dname;
119         *bname_p = bname;
120
121         return 0;
122 }
123
124 static int
125 ec_node_file_complete(const struct ec_node *gen_node,
126                 struct ec_completed *completed,
127                 const struct ec_strvec *strvec)
128 {
129         struct ec_node_file *node = (struct ec_node_file *)gen_node;
130         char *dname = NULL, *bname = NULL, *effective_dir;
131         struct ec_completed_item *item = NULL;
132         enum ec_completed_type type;
133         struct stat st, st2;
134         const char *input;
135         size_t bname_len;
136         struct dirent *de = NULL;
137         DIR *dir = NULL;
138         char *comp_str = NULL;
139         char *disp_str = NULL;
140         int is_dir = 0;
141
142         /*
143          * Example with this file tree:
144          * /
145          * ├── dir1
146          * │   ├── file1
147          * │   ├── file2
148          * │   └── subdir
149          * │       └── file3
150          * ├── dir2
151          * │   └── file4
152          * └── file5
153          *
154          * Input     Output completions
155          *   /       [dir1/, dir2/, file5]
156          *   /d      [dir1/, dir2/]
157          *   /f      [file5]
158          *   /dir1/  [file1, file2, subdir/]
159          *
160          *
161          *
162          */
163
164         if (ec_strvec_len(strvec) != 1)
165                 return 0;
166
167         input = ec_strvec_val(strvec, 0);
168         if (split_path(input, &dname, &bname) < 0)
169                 return -1;
170
171         if (strcmp(dname, "") == 0)
172                 effective_dir = ".";
173         else
174                 effective_dir = dname;
175
176         if (node->lstat(effective_dir, &st) < 0)
177                 goto fail;
178         if (!S_ISDIR(st.st_mode))
179                 goto out;
180
181         dir = node->opendir(effective_dir);
182         if (dir == NULL)
183                 goto fail;
184
185         bname_len = strlen(bname);
186         while (1) {
187                 errno = 0;
188                 de = node->readdir(dir);
189                 if (de == NULL) {
190                         if (errno == 0)
191                                 goto out;
192                         else
193                                 goto fail;
194                 }
195
196                 if (!ec_str_startswith(de->d_name, bname))
197                         continue;
198                 if (bname[0] != '.' && de->d_name[0] == '.')
199                         continue;
200
201                 /* add '/' if it's a dir */
202                 if (de->d_type == DT_DIR) {
203                         is_dir = 1;
204                 } else if (de->d_type == DT_UNKNOWN) {
205                         int dir_fd = node->dirfd(dir);
206
207                         if (dir_fd < 0)
208                                 goto fail;
209                         if (node->fstatat(dir_fd, de->d_name, &st2, 0) < 0)
210                                 goto fail;
211                         if (S_ISDIR(st2.st_mode))
212                                 is_dir = 1;
213                         else
214                                 is_dir = 0;
215                 } else {
216                         is_dir = 0;
217                 }
218
219                 if (is_dir) {
220                         type = EC_COMP_PARTIAL;
221                         if (ec_asprintf(&comp_str, "%s%s/", input,
222                                         &de->d_name[bname_len]) < 0)
223                                 goto fail;
224                         if (ec_asprintf(&disp_str, "%s/", de->d_name) < 0)
225                                 goto fail;
226                 } else {
227                         type = EC_COMP_FULL;
228                         if (ec_asprintf(&comp_str, "%s%s", input,
229                                         &de->d_name[bname_len]) < 0)
230                                 goto fail;
231                         if (ec_asprintf(&disp_str, "%s", de->d_name) < 0)
232                                 goto fail;
233                 }
234                 if (ec_completed_add_item(completed, gen_node, &item,
235                                                 type, input, comp_str) < 0)
236                         goto out;
237
238                 /* fix the display string: we don't want to display the full
239                  * path. */
240                 if (ec_completed_item_set_display(item, disp_str) < 0)
241                         goto out;
242
243                 item = NULL;
244                 ec_free(comp_str);
245                 comp_str = NULL;
246                 ec_free(disp_str);
247                 disp_str = NULL;
248         }
249 out:
250         ec_free(comp_str);
251         ec_free(disp_str);
252         ec_free(dname);
253         ec_free(bname);
254         if (dir != NULL)
255                 node->closedir(dir);
256
257         return 0;
258
259 fail:
260         ec_free(comp_str);
261         ec_free(disp_str);
262         ec_free(dname);
263         ec_free(bname);
264         if (dir != NULL)
265                 node->closedir(dir);
266
267         return -1;
268 }
269
270 static int
271 ec_node_file_init_priv(struct ec_node *gen_node)
272 {
273         struct ec_node_file *node = (struct ec_node_file *)gen_node;
274
275         node->lstat = lstat;
276         node->opendir = opendir;
277         node->readdir = readdir;
278         node->dirfd = dirfd;
279         node->fstatat = fstatat;
280
281         return 0;
282 }
283
284 static struct ec_node_type ec_node_file_type = {
285         .name = "file",
286         .parse = ec_node_file_parse,
287         .complete = ec_node_file_complete,
288         .size = sizeof(struct ec_node_file),
289         .init_priv = ec_node_file_init_priv,
290 };
291
292 EC_NODE_TYPE_REGISTER(ec_node_file_type);
293
294 /* LCOV_EXCL_START */
295 static int
296 test_lstat(const char *pathname, struct stat *buf)
297 {
298 //      return lstat(pathname, buf);
299
300         if (!strcmp(pathname, "/tmp/toto/")) {
301                 struct stat st = { .st_mode = S_IFDIR };
302                 memcpy(buf, &st, sizeof(*buf));
303                 return 0;
304         }
305
306         errno = ENOENT;
307         return -1;
308 }
309
310 static DIR *
311 test_opendir(const char *name)
312 {
313 //      return opendir(name);
314         int *p;
315
316         if (strcmp(name, "/tmp/toto/")) {
317                 errno = ENOENT;
318                 return NULL;
319         }
320
321         p = malloc(sizeof(int));
322         if (p)
323                 *p = 0;
324
325         return (DIR *)p;
326 }
327
328 static struct dirent *
329 test_readdir(DIR *dirp)
330 {
331         //return readdir(dirp);
332         static struct dirent de[] = {
333                 { .d_type = DT_DIR, .d_name = ".." },
334                 { .d_type = DT_DIR, .d_name = "." },
335                 { .d_type = DT_REG, .d_name = "bar" },
336                 { .d_type = DT_UNKNOWN, .d_name = "bar2" },
337                 { .d_type = DT_REG, .d_name = "foo" },
338                 { .d_type = DT_DIR, .d_name = "titi" },
339                 { .d_type = DT_UNKNOWN, .d_name = "tutu" },
340                 { .d_name = "" },
341         };
342         int *p = (int *)dirp;
343         struct dirent *ret = &de[*p];
344
345         if (!strcmp(ret->d_name, ""))
346                 return NULL;
347
348         *p = *p + 1;
349
350         return ret;
351 }
352
353 static int
354 test_closedir(DIR *dirp)
355 {
356         free(dirp);
357         return 0;
358 }
359
360 static int
361 test_dirfd(DIR *dirp)
362 {
363         int *p = (int *)dirp;
364         return *p;
365 }
366
367 static int
368 test_fstatat(int dirfd, const char *pathname, struct stat *buf,
369         int flags)
370 {
371         (void)dirfd;
372         (void)flags;
373
374         if (!strcmp(pathname, "bar2")) {
375                 struct stat st = { .st_mode = S_IFREG };
376                 memcpy(buf, &st, sizeof(*buf));
377                 return 0;
378         } else if (!strcmp(pathname, "tutu")) {
379                 struct stat st = { .st_mode = S_IFDIR };
380                 memcpy(buf, &st, sizeof(*buf));
381                 return 0;
382         }
383
384         errno = ENOENT;
385         return -1;
386 }
387
388 static int
389 ec_node_file_override_functions(struct ec_node *gen_node)
390 {
391         struct ec_node_file *node = (struct ec_node_file *)gen_node;
392
393         node->lstat = test_lstat;
394         node->opendir = test_opendir;
395         node->readdir = test_readdir;
396         node->closedir = test_closedir;
397         node->dirfd = test_dirfd;
398         node->fstatat = test_fstatat;
399
400         return 0;
401 }
402
403 static int ec_node_file_testcase(void)
404 {
405         struct ec_node *node;
406         int ret = 0;
407
408         node = ec_node("file", EC_NO_ID);
409         if (node == NULL) {
410                 EC_LOG(EC_LOG_ERR, "cannot create node\n");
411                 return -1;
412         }
413         ec_node_file_override_functions(node);
414
415         /* any string matches */
416         ret |= EC_TEST_CHECK_PARSE(node, 1, "foo");
417         ret |= EC_TEST_CHECK_PARSE(node, 1, "/tmp/bar");
418         ret |= EC_TEST_CHECK_PARSE(node, -1);
419
420         /* test completion */
421         ret |= EC_TEST_CHECK_COMPLETE(node,
422                 EC_NODE_ENDLIST,
423                 EC_NODE_ENDLIST);
424         ret |= EC_TEST_CHECK_COMPLETE(node,
425                 "/tmp/toto/t", EC_NODE_ENDLIST,
426                 EC_NODE_ENDLIST);
427         ret |= EC_TEST_CHECK_COMPLETE_PARTIAL(node,
428                 "/tmp/toto/t", EC_NODE_ENDLIST,
429                 "/tmp/toto/titi/", "/tmp/toto/tutu/", EC_NODE_ENDLIST);
430         ret |= EC_TEST_CHECK_COMPLETE(node,
431                 "/tmp/toto/f", EC_NODE_ENDLIST,
432                 "/tmp/toto/foo", EC_NODE_ENDLIST);
433         ret |= EC_TEST_CHECK_COMPLETE(node,
434                 "/tmp/toto/b", EC_NODE_ENDLIST,
435                 "/tmp/toto/bar", "/tmp/toto/bar2", EC_NODE_ENDLIST);
436
437         ec_node_free(node);
438
439         return ret;
440 }
441 /* LCOV_EXCL_STOP */
442
443 static struct ec_test ec_node_file_test = {
444         .name = "node_file",
445         .test = ec_node_file_testcase,
446 };
447
448 EC_TEST_REGISTER(ec_node_file_test);