cmdline: add a new 'file' token
[libcmdline.git] / src / lib / cmdline_parse_file.c
1 /*
2  * Copyright (c) 2009, Olivier MATZ <zer0@droids-corp.org>
3  * All rights reserved.
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 "cmdline_parse.h"
40 #include "cmdline_parse_file.h"
41
42 struct cmdline_complete_file_callback {
43         char *token;
44         DIR *dir;
45 };
46
47 static int
48 cmdline_parse_file(cmdline_parse_token_hdr_t *tk, const char *buf, void *res,
49                    unsigned ressize)
50 {
51         struct cmdline_token_file *tk2 = (struct cmdline_token_file *)tk;
52         struct cmdline_token_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 < FILENAME_SIZE)
59                 return -1;
60
61         token_len = strlen(buf);
62         if (token_len >= (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 cmdline_complete_file_start(cmdline_parse_token_hdr_t *tk,
144                             const char *tokstr, void **opaque)
145 {
146         char *dname;
147         struct cmdline_complete_file_callback *cb;
148
149         *opaque = NULL;
150         cb = malloc(sizeof(*cb));
151         if (cb == NULL)
152                 return -1;
153         memset(cb, 0, sizeof(*cb));
154         *opaque = cb;
155
156         cb->token = strdup(tokstr);
157         /* we need to copy again tokstr because dirname() alters the string */
158         dname = dirname2(tokstr, 0);
159         cb->dir = opendir(dname);
160         free(dname);
161
162         if (cb->dir == NULL)
163                 return -1;
164
165         return 0;
166 }
167
168 static int
169 cmdline_complete_file_iterate(cmdline_parse_token_hdr_t *tk, void **opaque,
170                               char *dstbuf, unsigned int size)
171 {
172         struct cmdline_token_file *tk2 = (struct cmdline_token_file *)tk;
173         struct cmdline_token_file_data *sd = &tk2->file_data;
174         struct cmdline_complete_file_callback *cb;
175         struct dirent *de;
176         struct stat st;
177         char *dname;
178         int len;
179         int need_join_slash = 1;
180         int flags = sd->flags;
181
182         cb = *opaque;
183         /* read next dir name, skipping "." and ".." */
184         while (1) {
185                 de = readdir(cb->dir);
186                 if (de == NULL)
187                         return -1;
188                 if (strcmp(de->d_name, ".") == 0 ||
189                     strcmp(de->d_name, "..") == 0)
190                         continue;
191
192
193                 /* do we need a / to join dirname and basename ? */
194                 dname = dirname2(cb->token, 1);
195                 len = strlen(dname);
196                 if (len == 0 || dname[len - 1] == '/')
197                         need_join_slash = 0;
198
199                 /* keep one byte for potential '/' */
200                 len = snprintf(dstbuf, size-1, "%s%s%s", dname,
201                                need_join_slash ? "/" : "", de->d_name);
202                 free(dname);
203                 if (len < 0 || len >= size-1)
204                         continue;
205
206                 /* append '/' if it's a directory */
207                 if (stat(dstbuf, &st) != 0) {
208                         if (lstat(dstbuf, &st) != 0)
209                                 return -1;
210                 }
211                 if (S_ISDIR(st.st_mode)) {
212                         strcat(dstbuf, "/");
213                         return 1; /* intermediate completion */
214                 }
215                 /* skip non-directories */
216                 else if (flags & PARSE_FILE_F_DIRECTORY)
217                         continue;
218                 break;
219         }
220
221         return 0;
222 }
223
224 static void
225 cmdline_complete_file_end(cmdline_parse_token_hdr_t *tk, void **opaque)
226 {
227         struct cmdline_complete_file_callback *cb;
228
229         cb = *opaque;
230
231         if (cb == NULL)
232                 return;
233
234         if (cb->dir)
235                 closedir(cb->dir);
236         if (cb->token)
237                 free(cb->token);
238
239         free(cb);
240 }
241
242
243 static int
244 cmdline_help_file(cmdline_parse_token_hdr_t *tk, char *dstbuf,
245                     unsigned int size)
246 {
247         struct cmdline_token_file *tk2 = (struct cmdline_token_file *)tk;
248         struct cmdline_token_file_data *sd = &tk2->file_data;;
249         int flags;
250
251         flags = sd->flags;
252         if (flags & PARSE_FILE_F_DIRECTORY)
253                 snprintf(dstbuf, size, "DIR");
254         else
255                 snprintf(dstbuf, size, "FILE");
256
257         return 0;
258 }
259
260 struct cmdline_token_ops cmdline_token_file_ops = {
261         .parse = cmdline_parse_file,
262         .complete_start = cmdline_complete_file_start,
263         .complete_iterate = cmdline_complete_file_iterate,
264         .complete_end = cmdline_complete_file_end,
265         .help = cmdline_help_file,
266 };