2 * Copyright (c) 2016, Olivier MATZ <zer0@droids-corp.org>
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are met:
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.
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.
28 #include <sys/types.h>
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>
47 EC_LOG_TYPE_REGISTER(node_file);
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,
63 ec_node_file_parse(const struct ec_node *gen_node,
64 struct ec_parsed *state,
65 const struct ec_strvec *strvec)
70 if (ec_strvec_len(strvec) == 0)
71 return EC_PARSED_NOMATCH;
77 * Almost the same than dirname (3) and basename (3) except that:
78 * - it always returns a substring of the given path, which can
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().
84 * path dirname basename split_path
85 * /usr/lib /usr lib /usr/ lib
92 static int split_path(const char *path, char **dname_p, char **bname_p)
101 last_slash = strrchr(path, '/');
102 if (last_slash == NULL)
105 dirlen = last_slash - path + 1;
107 dname = ec_strdup(path);
110 dname[dirlen] = '\0';
112 bname = ec_strdup(path + dirlen);
125 ec_node_file_complete(const struct ec_node *gen_node,
126 struct ec_completed *completed,
127 const struct ec_strvec *strvec)
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;
136 struct dirent *de = NULL;
138 char *comp_str = NULL;
139 char *disp_str = NULL;
143 * Example with this file tree:
154 * Input Output completions
155 * / [dir1/, dir2/, file5]
158 * /dir1/ [file1, file2, subdir/]
164 if (ec_strvec_len(strvec) != 1)
167 input = ec_strvec_val(strvec, 0);
168 if (split_path(input, &dname, &bname) < 0)
171 if (strcmp(dname, "") == 0)
174 effective_dir = dname;
176 if (node->lstat(effective_dir, &st) < 0)
178 if (!S_ISDIR(st.st_mode))
181 dir = node->opendir(effective_dir);
185 bname_len = strlen(bname);
188 de = node->readdir(dir);
196 if (!ec_str_startswith(de->d_name, bname))
198 if (bname[0] != '.' && de->d_name[0] == '.')
201 /* add '/' if it's a dir */
202 if (de->d_type == DT_DIR) {
204 } else if (de->d_type == DT_UNKNOWN) {
205 int dir_fd = node->dirfd(dir);
209 if (node->fstatat(dir_fd, de->d_name, &st2, 0) < 0)
211 if (S_ISDIR(st2.st_mode))
220 type = EC_COMP_PARTIAL;
221 if (ec_asprintf(&comp_str, "%s%s/", input,
222 &de->d_name[bname_len]) < 0)
224 if (ec_asprintf(&disp_str, "%s/", de->d_name) < 0)
228 if (ec_asprintf(&comp_str, "%s%s", input,
229 &de->d_name[bname_len]) < 0)
231 if (ec_asprintf(&disp_str, "%s", de->d_name) < 0)
234 if (ec_completed_add_item(completed, gen_node, &item,
235 type, input, comp_str) < 0)
238 /* fix the display string: we don't want to display the full
240 if (ec_completed_item_set_display(item, disp_str) < 0)
271 ec_node_file_init_priv(struct ec_node *gen_node)
273 struct ec_node_file *node = (struct ec_node_file *)gen_node;
276 node->opendir = opendir;
277 node->readdir = readdir;
279 node->fstatat = fstatat;
284 static struct ec_node_type ec_node_file_type = {
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,
292 EC_NODE_TYPE_REGISTER(ec_node_file_type);
294 /* LCOV_EXCL_START */
296 test_lstat(const char *pathname, struct stat *buf)
298 if (!strcmp(pathname, "/tmp/toto/")) {
299 struct stat st = { .st_mode = S_IFDIR };
300 memcpy(buf, &st, sizeof(*buf));
309 test_opendir(const char *name)
313 if (strcmp(name, "/tmp/toto/")) {
318 p = malloc(sizeof(int));
325 static struct dirent *
326 test_readdir(DIR *dirp)
328 static struct dirent de[] = {
329 { .d_type = DT_DIR, .d_name = ".." },
330 { .d_type = DT_DIR, .d_name = "." },
331 { .d_type = DT_REG, .d_name = "bar" },
332 { .d_type = DT_UNKNOWN, .d_name = "bar2" },
333 { .d_type = DT_REG, .d_name = "foo" },
334 { .d_type = DT_DIR, .d_name = "titi" },
335 { .d_type = DT_UNKNOWN, .d_name = "tutu" },
338 int *p = (int *)dirp;
339 struct dirent *ret = &de[*p];
341 if (!strcmp(ret->d_name, ""))
350 test_closedir(DIR *dirp)
357 test_dirfd(DIR *dirp)
359 int *p = (int *)dirp;
364 test_fstatat(int dirfd, const char *pathname, struct stat *buf,
370 if (!strcmp(pathname, "bar2")) {
371 struct stat st = { .st_mode = S_IFREG };
372 memcpy(buf, &st, sizeof(*buf));
374 } else if (!strcmp(pathname, "tutu")) {
375 struct stat st = { .st_mode = S_IFDIR };
376 memcpy(buf, &st, sizeof(*buf));
385 ec_node_file_override_functions(struct ec_node *gen_node)
387 struct ec_node_file *node = (struct ec_node_file *)gen_node;
389 node->lstat = test_lstat;
390 node->opendir = test_opendir;
391 node->readdir = test_readdir;
392 node->closedir = test_closedir;
393 node->dirfd = test_dirfd;
394 node->fstatat = test_fstatat;
399 static int ec_node_file_testcase(void)
401 struct ec_node *node;
404 node = ec_node("file", EC_NO_ID);
406 EC_LOG(EC_LOG_ERR, "cannot create node\n");
409 ec_node_file_override_functions(node);
411 /* any string matches */
412 testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
413 testres |= EC_TEST_CHECK_PARSE(node, 1, "/tmp/bar");
414 testres |= EC_TEST_CHECK_PARSE(node, -1);
416 /* test completion */
417 testres |= EC_TEST_CHECK_COMPLETE(node,
420 testres |= EC_TEST_CHECK_COMPLETE(node,
421 "/tmp/toto/t", EC_NODE_ENDLIST,
423 testres |= EC_TEST_CHECK_COMPLETE_PARTIAL(node,
424 "/tmp/toto/t", EC_NODE_ENDLIST,
425 "/tmp/toto/titi/", "/tmp/toto/tutu/", EC_NODE_ENDLIST);
426 testres |= EC_TEST_CHECK_COMPLETE(node,
427 "/tmp/toto/f", EC_NODE_ENDLIST,
428 "/tmp/toto/foo", EC_NODE_ENDLIST);
429 testres |= EC_TEST_CHECK_COMPLETE(node,
430 "/tmp/toto/b", EC_NODE_ENDLIST,
431 "/tmp/toto/bar", "/tmp/toto/bar2", EC_NODE_ENDLIST);
439 static struct ec_test ec_node_file_test = {
441 .test = ec_node_file_testcase,
444 EC_TEST_REGISTER(ec_node_file_test);