initial revision
[ucgine.git] / lib / cmd / ucg_cmd_parse_file.c
1 /*
2  * Copyright (c) 2009-2015, 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 <stdio.h>
29 #include <inttypes.h>
30 #include <ctype.h>
31 #include <string.h>
32 #include <stdlib.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <unistd.h>
36 #include <libgen.h>
37 #include <dirent.h>
38
39 #include "ucg_cmd_parse.h"
40 #include "ucg_cmd_parse_file.h"
41
42 struct cmd_complete_file_callback {
43         char *token;
44         DIR *dir;
45 };
46
47 static int
48 cmd_parse_file(ucg_cmd_tk_hdr_t *tk, const char *buf,
49         void *res, unsigned ressize)
50 {
51         struct ucg_cmd_tk_file *tk2 = (struct ucg_cmd_tk_file *)tk;
52         struct ucg_cmd_tk_file_data *sd = &tk2->file_data;;
53         unsigned int token_len;
54         struct stat st;
55         int flags, ret;
56         char *tmp, *dname;
57
58         if (res && ressize < UCG_FILENAME_SIZE)
59                 return -1;
60
61         token_len = strlen(buf);
62         if (token_len >= (UCG_FILENAME_SIZE - 1) || token_len == 0)
63                 return -1;
64
65         flags = sd->flags;
66
67         if (flags & PARSE_FILE_F_CREATE) {
68                 /* the directory must exist */
69                 tmp = strdup(buf);
70                 dname = dirname(tmp);
71                 ret = stat(dname, &st);
72                 if (ret != 0)
73                         ret = lstat(dname, &st);
74                 free(tmp);
75                 if (ret != 0)
76                         return -1;
77                 if (!S_ISDIR(st.st_mode))
78                         return -1;
79         }
80         else {
81                 ret = stat(buf, &st);
82                 if (ret != 0)
83                         return -1;
84                 if (flags & PARSE_FILE_F_DIRECTORY)
85                         if (!S_ISDIR(st.st_mode))
86                                 return -1;
87         }
88
89         /* we already checked that token_len is < FILENAME_SIZE-1 */
90         if (res)
91                 strcpy(res, buf);
92
93         return 0;
94 }
95
96 /*
97  * This function is quite similar to dirname(3) except that:
98  * - it allocates the returned string and don't modify the argument
99  * - the result of dirname2() is not exactly the same than dirname()
100  * path          dirname2
101  * "/usr/lib"    "/usr"
102  * "/usr/"       "/usr"
103  * "/usr"        "/"
104  * "usr"         "." or "" if allow_empty == 1
105  * ""            "." or "" if allow_empty == 1
106  * "/"           "/"
107  * "."           "." or "" if allow_empty == 1
108  * "./"          "."
109  * ".."          "." or "" if allow_empty == 1
110  * "../"          ".."
111  */
112 static char *
113 dirname2(const char *path, int allow_empty)
114 {
115         char *s;
116         char *last_slash;
117         int len;
118
119         len = strlen(path);
120         if (len == 0 && allow_empty == 0) {
121                 s = strdup(".");
122                 return s;
123         }
124
125         s = strdup(path);
126         last_slash = strrchr(s, '/');
127         if (last_slash == NULL) {
128                 if (allow_empty)
129                         s[0] = '\0';
130                 else
131                         strcpy(s, ".");
132         }
133         else if (last_slash == s)
134                 s[1] = '\0';
135         else {
136                 *last_slash = '\0';
137         }
138
139         return s;
140 }
141
142 static int
143 cmd_complete_file_start(ucg_cmd_tk_hdr_t *tk,
144         const char *tokstr, void **opaque)
145 {
146         char *dname;
147         struct cmd_complete_file_callback *cb;
148
149         (void)tk;
150         *opaque = NULL;
151         cb = malloc(sizeof(*cb));
152         if (cb == NULL)
153                 return -1;
154         memset(cb, 0, sizeof(*cb));
155         *opaque = cb;
156
157         cb->token = strdup(tokstr);
158         /* we need to copy again tokstr because dirname() alters the string */
159         dname = dirname2(tokstr, 0);
160         cb->dir = opendir(dname);
161         free(dname);
162
163         if (cb->dir == NULL)
164                 return -1;
165
166         return 0;
167 }
168
169 static int
170 cmd_complete_file_iterate(ucg_cmd_tk_hdr_t *tk, void **opaque,
171         char *dstbuf, unsigned int size)
172 {
173         struct ucg_cmd_tk_file *tk2 = (struct ucg_cmd_tk_file *)tk;
174         struct ucg_cmd_tk_file_data *sd = &tk2->file_data;
175         struct cmd_complete_file_callback *cb;
176         struct dirent *de;
177         struct stat st;
178         char *dname;
179         int len;
180         int need_join_slash = 1;
181         int flags = sd->flags;
182
183         cb = *opaque;
184         /* read next dir name, skipping "." and ".." */
185         while (1) {
186                 de = readdir(cb->dir);
187                 if (de == NULL)
188                         return -1;
189                 if (strcmp(de->d_name, ".") == 0 ||
190                         strcmp(de->d_name, "..") == 0)
191                         continue;
192
193
194                 /* do we need a / to join dirname and basename ? */
195                 dname = dirname2(cb->token, 1);
196                 len = strlen(dname);
197                 if (len == 0 || dname[len - 1] == '/')
198                         need_join_slash = 0;
199
200                 /* keep one byte for potential '/' */
201                 len = snprintf(dstbuf, size-1, "%s%s%s", dname,
202                         need_join_slash ? "/" : "", de->d_name);
203                 free(dname);
204                 if (len < 0 || len >= (int)size - 1)
205                         continue;
206
207                 /* append '/' if it's a directory */
208                 if (stat(dstbuf, &st) != 0) {
209                         if (lstat(dstbuf, &st) != 0)
210                                 return -1;
211                 }
212                 if (S_ISDIR(st.st_mode)) {
213                         strcat(dstbuf, "/");
214                         return 1; /* intermediate completion */
215                 }
216                 /* skip non-directories */
217                 else if (flags & PARSE_FILE_F_DIRECTORY)
218                         continue;
219                 break;
220         }
221
222         return 0;
223 }
224
225 static void
226 cmd_complete_file_end(ucg_cmd_tk_hdr_t *tk, void **opaque)
227 {
228         struct cmd_complete_file_callback *cb;
229
230         (void)tk;
231         cb = *opaque;
232
233         if (cb == NULL)
234                 return;
235
236         if (cb->dir)
237                 closedir(cb->dir);
238         if (cb->token)
239                 free(cb->token);
240
241         free(cb);
242 }
243
244
245 static int
246 cmd_help_file(ucg_cmd_tk_hdr_t *tk, char *dstbuf,
247         unsigned int size)
248 {
249         struct ucg_cmd_tk_file *tk2 = (struct ucg_cmd_tk_file *)tk;
250         struct ucg_cmd_tk_file_data *sd = &tk2->file_data;;
251         int flags;
252
253         flags = sd->flags;
254         if (flags & PARSE_FILE_F_DIRECTORY)
255                 snprintf(dstbuf, size, "<dir>");
256         else
257                 snprintf(dstbuf, size, "<file>");
258
259         return 0;
260 }
261
262 struct ucg_cmd_tk_ops ucg_cmd_tk_file_ops = {
263         .parse = cmd_parse_file,
264         .complete_start = cmd_complete_file_start,
265         .complete_iterate = cmd_complete_file_iterate,
266         .complete_end = cmd_complete_file_end,
267         .help = cmd_help_file,
268 };