1 /*
2  *  wslua_dir.c
3  *
4  * (c) 2014, Hadriel Kaplan <hadrielk at yahoo dot com>
5  *
6  * Wireshark - Network traffic analyzer
7  * By Gerald Combs <gerald@wireshark.org>
8  * Copyright 1998 Gerald Combs
9  *
10  * SPDX-License-Identifier: GPL-2.0-or-later
11  */
12 
13 #include "config.h"
14 
15 /* WSLUA_MODULE Dir Directory Handling Functions */
16 
17 #include "wslua.h"
18 #include <wsutil/file_util.h>
19 
20 WSLUA_CLASS_DEFINE(Dir,FAIL_ON_NULL("Dir")); /* A Directory object, as well as associated functions. */
21 
22 WSLUA_CONSTRUCTOR Dir_make(lua_State* L) {
23     /* Creates a directory.
24 
25        The created directory is set for permission mode 0755 (octal), meaning it is
26        read+write+execute by owner, but only read+execute by group members and others.
27 
28        If the directory was created successfully, a boolean `true` is returned.
29        If the directory cannot be made because it already exists, `false` is returned.
30        If the directory cannot be made because an error occurred, `nil` is returned.
31 
32        @since 1.11.3
33     */
34 #define WSLUA_ARG_Dir_make_NAME 1 /* The name of the directory, possibly including path. */
35 
36     const char *dir_path = luaL_checkstring(L, WSLUA_ARG_Dir_make_NAME);
37     ws_statb64 s_buf;
38     int ret;
39 
40     if (ws_stat64(dir_path, &s_buf) != 0 && errno == ENOENT) {
41         ret = ws_mkdir(dir_path, 0755);
42         if (ret == -1) {
43             lua_pushnil(L);
44         } else {
45             lua_pushboolean(L, 1);
46         }
47     } else {
48         lua_pushboolean(L, 0);
49     }
50 
51     WSLUA_RETURN(1); /* Boolean `true` on success, `false` if the directory already exists, `nil` on error. */
52 }
53 
54 WSLUA_CONSTRUCTOR Dir_exists(lua_State* L) {
55     /* Returns true if the given directory name exists.
56 
57        If the directory exists, a boolean `true` is returned.
58        If the path is a file instead, `false` is returned.
59        If the path does not exist or an error occurred, `nil` is returned.
60 
61        @since 1.11.3
62     */
63 #define WSLUA_ARG_Dir_exists_NAME 1 /* The name of the directory, possibly including path. */
64 
65     const char *dir_path = luaL_checkstring(L, WSLUA_ARG_Dir_exists_NAME);
66     int ret;
67 
68     if ((ret = test_for_directory (dir_path)) == EISDIR) {
69         lua_pushboolean(L, 1);
70     } else {
71         if (ret == 0) {
72             lua_pushboolean(L, 0);
73         } else {
74             lua_pushnil(L);
75         }
76     }
77 
78     WSLUA_RETURN(1); /* Boolean `true` if the directory exists, `false` if it's a file, `nil` on error or not-exist. */
79 }
80 
81 WSLUA_CONSTRUCTOR Dir_remove(lua_State* L) {
82     /* Removes an empty directory.
83 
84        If the directory was removed successfully, a boolean `true` is returned.
85        If the directory cannot be removed because it does not exist, `false` is returned.
86        If the directory cannot be removed because an error occurred, `nil` is returned.
87 
88        This function only removes empty directories. To remove a directory regardless,
89        use `Dir.remove_all()`.
90 
91        @since 1.11.3
92     */
93 #define WSLUA_ARG_Dir_remove_NAME 1 /* The name of the directory, possibly including path. */
94 
95     const char *dir_path = luaL_checkstring(L, WSLUA_ARG_Dir_remove_NAME);
96     int ret;
97 
98     if (test_for_directory (dir_path) == EISDIR) {
99         ret = ws_remove(dir_path);
100         if (ret != 0) {
101             lua_pushnil(L);
102         } else {
103             lua_pushboolean(L, 1);
104         }
105     } else {
106         lua_pushboolean(L, 0);
107     }
108 
109     WSLUA_RETURN(1); /* Boolean `true` on success, `false` if does not exist, `nil` on error. */
110 }
111 
112 static int delete_directory(const char *directory) {
113     WS_DIR *dir;
114     WS_DIRENT *file;
115     gchar *filename;
116     int ret = 0;
117 
118     /* delete all contents of directory */
119     if ((dir = ws_dir_open(directory, 0, NULL)) != NULL) {
120         while ((file = ws_dir_read_name(dir)) != NULL) {
121             filename = g_build_filename(directory, ws_dir_get_name(file), NULL);
122             if (test_for_directory(filename) != EISDIR) {
123                 ret = ws_remove(filename);
124             } else {
125                 /* recurse */
126                 ret = delete_directory (filename);
127             }
128             g_free(filename);
129             if (ret != 0) {
130                 break;
131             }
132         }
133         ws_dir_close(dir);
134     }
135 
136     if (ret == 0) {
137         ret = ws_remove(directory);
138     }
139 
140     return ret;
141 }
142 
143 
144 WSLUA_CONSTRUCTOR Dir_remove_all(lua_State* L) {
145     /* Removes an empty or non-empty directory.
146 
147        If the directory was removed successfully, a boolean `true` is returned.
148        If the directory cannot be removed because it does not exist, `false` is returned.
149        If the directory cannot be removed because an error occurred, `nil` is returned.
150 
151        @since 1.11.3
152     */
153 #define WSLUA_ARG_Dir_remove_all_NAME 1 /* The name of the directory, possibly including path. */
154 
155     const char *dir_path = luaL_checkstring(L, WSLUA_ARG_Dir_remove_all_NAME);
156     int ret;
157 
158     if (test_for_directory (dir_path) == EISDIR) {
159         ret = delete_directory(dir_path);
160         if (ret != 0) {
161             lua_pushnil(L);
162         } else {
163             lua_pushboolean(L, 1);
164         }
165     } else {
166         lua_pushboolean(L, 0);
167     }
168 
169     WSLUA_RETURN(1); /* Boolean `true` on success, `false` if does not exist, `nil` on error. */
170 }
171 
172 WSLUA_CONSTRUCTOR Dir_open(lua_State* L) {
173     /* Opens a directory and returns a <<lua_class_Dir,`Dir`>> object representing the files in the directory.
174 
175     ==== Example
176 
177     [source,lua]
178     ----
179     -- Print the contents of a directory
180     for filename in Dir.open('/path/to/dir') do
181             print(filename)
182     end
183     ----
184     */
185 #define WSLUA_ARG_Dir_open_PATHNAME 1 /* The pathname of the directory. */
186 #define WSLUA_OPTARG_Dir_open_EXTENSION 2 /* If given, only files with this extension will be returned. */
187 
188     const char* dirname = luaL_checkstring(L,WSLUA_ARG_Dir_open_PATHNAME);
189     const char* extension = luaL_optstring(L,WSLUA_OPTARG_Dir_open_EXTENSION,NULL);
190     Dir dir;
191     char* dirname_clean;
192 
193     dirname_clean = wslua_get_actual_filename(dirname);
194     if (!dirname_clean) {
195         WSLUA_ARG_ERROR(Dir_open,PATHNAME,"directory does not exist");
196         return 0;
197     }
198 
199     if (!test_for_directory(dirname_clean))  {
200         g_free(dirname_clean);
201         WSLUA_ARG_ERROR(Dir_open,PATHNAME, "must be a directory");
202         return 0;
203     }
204 
205     dir = (Dir)g_malloc(sizeof(struct _wslua_dir));
206     dir->dir = g_dir_open(dirname_clean, 0, NULL);
207     g_free(dirname_clean);
208 
209     if (dir->dir == NULL) {
210         g_free(dir);
211 
212         WSLUA_ARG_ERROR(Dir_open,PATHNAME,"could not open directory");
213         return 0;
214     }
215 
216     dir->ext = g_strdup(extension);
217 
218     pushDir(L,dir);
219     WSLUA_RETURN(1); /* The <<lua_class_Dir,`Dir`>> object. */
220 }
221 
222 WSLUA_METAMETHOD Dir__call(lua_State* L) {
223     /*
224     Gets the next file or subdirectory within the directory, or `nil` when done.
225 
226     ==== Example
227 
228     [source,lua]
229     ----
230     -- Open a directory and print the name of the first file or subdirectory
231     local dir = Dir.open('/path/to/dir')
232     local first = dir()
233     print(tostring(file))
234     ----
235     */
236 
237     Dir dir = checkDir(L,1);
238     const gchar* file;
239     const gchar* filename;
240     const char* ext;
241 
242     if (!dir->dir) {
243         return 0;
244     }
245 
246     if ( ! ( file = g_dir_read_name(dir->dir ) )) {
247         g_dir_close(dir->dir);
248         dir->dir = NULL;
249         return 0;
250     }
251 
252 
253     if ( ! dir->ext ) {
254         lua_pushstring(L,file);
255         return 1;
256     }
257 
258     do {
259         filename = file;
260 
261         /* XXX strstr returns ptr to first match,
262             this fails ext=".xxx" filename="aaa.xxxz.xxx"  */
263         if ( ( ext = strstr(filename,dir->ext)) && g_str_equal(ext,dir->ext) ) {
264             lua_pushstring(L,filename);
265             return 1;
266         }
267     } while(( file = g_dir_read_name(dir->dir) ));
268 
269     g_dir_close(dir->dir);
270     dir->dir = NULL;
271     return 0;
272 }
273 
274 WSLUA_METHOD Dir_close(lua_State* L) {
275     /* Closes the directory. Called automatically during garbage collection of a <<lua_class_Dir,`Dir`>> object. */
276     Dir dir = checkDir(L,1);
277 
278     if (dir->dir) {
279         g_dir_close(dir->dir);
280         dir->dir = NULL;
281     }
282 
283     return 0;
284 }
285 
286 WSLUA_CONSTRUCTOR Dir_personal_config_path(lua_State* L) {
287     /* Gets the https://www.wireshark.org/docs/wsug_html_chunked/ChAppFilesConfigurationSection.html[personal configuration] directory path, with filename if supplied.
288 
289        @since 1.11.3
290     */
291 #define WSLUA_OPTARG_personal_config_path_FILENAME 1 /* A filename. */
292     const char *fname = luaL_optstring(L, WSLUA_OPTARG_personal_config_path_FILENAME,"");
293     char* filename = get_persconffile_path(fname,FALSE);
294 
295     lua_pushstring(L,filename);
296     g_free(filename);
297     WSLUA_RETURN(1); /* The full pathname for a file in the personal configuration directory. */
298 }
299 
300 WSLUA_CONSTRUCTOR Dir_global_config_path(lua_State* L) {
301     /* Gets the https://www.wireshark.org/docs/wsug_html_chunked/ChAppFilesConfigurationSection.html[global configuration] directory path, with filename if supplied.
302 
303        @since 1.11.3
304     */
305 #define WSLUA_OPTARG_global_config_path_FILENAME 1 /* A filename */
306     const char *fname = luaL_optstring(L, WSLUA_OPTARG_global_config_path_FILENAME,"");
307     char* filename;
308 
309     filename = get_datafile_path(fname);
310     lua_pushstring(L,filename);
311     g_free(filename);
312     WSLUA_RETURN(1); /* The full pathname for a file in Wireshark's configuration directory. */
313 }
314 
315 WSLUA_CONSTRUCTOR Dir_personal_plugins_path(lua_State* L) {
316     /* Gets the personal plugins directory path.
317 
318        @since 1.11.3
319     */
320     lua_pushstring(L, get_plugins_pers_dir());
321     WSLUA_RETURN(1); /* The pathname of the https://www.wireshark.org/docs/wsug_html_chunked/ChPluginFolders.html[personal plugins] directory. */
322 }
323 
324 WSLUA_CONSTRUCTOR Dir_global_plugins_path(lua_State* L) {
325     /* Gets the global plugins directory path.
326 
327        @since 1.11.3
328     */
329     lua_pushstring(L, get_plugins_dir());
330     WSLUA_RETURN(1); /* The pathname of the https://www.wireshark.org/docs/wsug_html_chunked/ChPluginFolders.html[global plugins] directory. */
331 }
332 
333 /* Gets registered as metamethod automatically by WSLUA_REGISTER_CLASS/META */
334 static int Dir__gc(lua_State* L) {
335     Dir dir = toDir(L,1);
336 
337     if(!dir) return 0;
338 
339     if (dir->dir) {
340         g_dir_close(dir->dir);
341     }
342 
343     g_free(dir->ext);
344     g_free(dir);
345 
346     return 0;
347 }
348 
349 WSLUA_METHODS Dir_methods[] = {
350     WSLUA_CLASS_FNREG(Dir,make),
351     WSLUA_CLASS_FNREG(Dir,exists),
352     WSLUA_CLASS_FNREG(Dir,remove),
353     WSLUA_CLASS_FNREG(Dir,remove_all),
354     WSLUA_CLASS_FNREG(Dir,open),
355     WSLUA_CLASS_FNREG(Dir,close),
356     WSLUA_CLASS_FNREG(Dir,personal_config_path),
357     WSLUA_CLASS_FNREG(Dir,global_config_path),
358     WSLUA_CLASS_FNREG(Dir,personal_plugins_path),
359     WSLUA_CLASS_FNREG(Dir,global_plugins_path),
360     { NULL, NULL }
361 };
362 
363 WSLUA_META Dir_meta[] = {
364     WSLUA_CLASS_MTREG(Dir,call),
365     { NULL, NULL }
366 };
367 
368 int Dir_register(lua_State* L) {
369     WSLUA_REGISTER_CLASS(Dir);
370     return 0;
371 }
372 
373 /*
374  * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
375  *
376  * Local variables:
377  * c-basic-offset: 4
378  * tab-width: 8
379  * indent-tabs-mode: nil
380  * End:
381  *
382  * vi: set shiftwidth=4 tabstop=8 expandtab:
383  * :indentSize=4:tabSize=8:noTabs=true:
384  */
385