lmtfilelib.c /size: 25 Kb    last modification: 2024-01-16 10:22
1/*
2
3    See license.txt in the root of this project.
4
5    This is a replacement for lfs, a file system manipulation library from the Kepler project. I
6    started from the lfs.c file from luatex because we need to keep a similar interface. That
7    file mentioned:
8
9    Copyright Kepler Project 2003 - 2017 (http://keplerproject.github.io/luafilesystem)
10
11    The original library offers the following functions:
12
13        lfs.attributes(filepath [, attributename | attributetable])
14        lfs.chdir(path)
15        lfs.currentdir()
16        lfs.dir(path)
17        lfs.link(old, new[, symlink])
18     -- lfs.lock(fh, mode)
19     -- lfs.lock_dir(path)
20        lfs.mkdir(path)
21        lfs.rmdir(path)
22     -- lfs.setmode(filepath, mode)
23        lfs.symlinkattributes(filepath [, attributename])
24        lfs.touch(filepath [, atime [, mtime]])
25     -- lfs.unlock(fh)
26
27    We have additional code in other modules and the code was already adapted a little. In the
28    meantime the code looks quite different.
29
30    Because \TEX| is multi-platform we try to provide a consistent interface. So, for instance
31    blocksize and inode number are not relevant for us, nor are user and group ids. The lock
32    functions have been removed as they serve no purpose in a \TEX\ system and devices make no
33    sense either. The iterator could be improved. I also fixed some anomalities. Permissions are
34    not useful either.
35
36*/
37
38# include "../lua/lmtinterface.h"
39# include "../utilities/auxmemory.h"
40# include "../utilities/auxfile.h"
41
42# ifndef R_OK
43# define F_OK 0x0
44# define W_OK 0x2
45# define R_OK 0x4
46# endif
47
48# define DIR_METATABLE "file.directory"
49
50# ifndef _WIN32
51    # ifndef _FILE_OFFSET_BITS
52        # define _FILE_OFFSET_BITS 64
53    # endif
54# endif
55
56# ifdef _WIN32
57    # ifndef WINVER
58        # define WINVER       0x0601
59        # undef  _WIN32_WINNT
60        # define _WIN32_WINNT 0x0601
61    # endif
62# endif
63
64// # ifndef _LARGEFILE64_SOURCE
65    # define _LARGEFILE64_SOURCE 1
66// # endif
67
68# include <errno.h>
69# include <stdio.h>
70# include <string.h>
71# include <stdlib.h>
72# include <time.h>
73# include <sys/stat.h>
74
75// # ifdef _MSC_VER
76//     # ifndef MAX_PATH
77//         # define MAX_PATH 256
78//     # endif
79// # endif
80
81# ifdef _WIN32
82
83    # include <direct.h>
84    # include <windows.h>
85    # include <io.h>
86    # include <fileapi.h>
87    # include <sys/locking.h>
88    # include <sys/utime.h>
89    # include <fcntl.h>
90
91    /* 
92        Todo MS Windows: By default, the name is limited to MAX_PATH characters. To extend this 
93        limit to 32,767 wide characters, prepend "\\?\" to the path. For more information, see 
94        Naming Files, Paths, and Namespaces.
95    */
96
97    # ifdef MAX_PATH
98        # define MY_MAXPATHLEN MAX_PATH
99    # else 
100        # define MY_MAXPATHLEN 255
101    # endif 
102
103# else
104
105    /* the next one is sensitive for c99 */
106
107    # include <unistd.h>
108    # include <dirent.h>
109    # include <fcntl.h>
110    # include <sys/types.h>
111    # include <utime.h>
112    # include <sys/param.h>
113
114    # ifdef MAXPATHLEN
115        # define MY_MAXPATHLEN MAXPATHLEN
116    # else 
117        # define MY_MAXPATHLEN 255
118    # endif 
119
120# endif
121
122/* This has to go to the h file. See luainit.c where it's also needed. */
123
124# ifdef _WIN32
125
126    # ifndef S_ISDIR
127        # define S_ISDIR(mode) (mode & _S_IFDIR)
128    # endif
129
130    # ifndef S_ISREG
131        # define S_ISREG(mode) (mode & _S_IFREG)
132    # endif
133
134    # ifndef S_ISLNK
135        # define S_ISLNK(mode) (0)
136    # endif
137
138    # ifndef S_ISSUB
139        # define S_ISSUB(mode) (file_data.attrib & _A_SUBDIR)
140    # endif
141
142    # define info_struct  struct _stati64
143    # define utime_struct struct __utimbuf64
144
145    # define exec_mode_flag  _S_IEXEC
146
147    /*
148        There is a difference between msvc and mingw wrt the daylight saving time correction being
149        applied toy the times. I couldn't figure it out and don't want to waste more time on it.
150    */
151
152    /* 
153        A windows path should not end with a / so maybe we should check for that and remove it when
154        we have one. Even better is to add a period. 
155
156        size_t l = wcslen(w) - 1;
157        if (w[l] == L'/') {
158            w[l] == L'\0');
159        }
160    */
161
162    typedef struct dir_data {
163        intptr_t handle;
164        int      closed;
165        char     pattern[MY_MAXPATHLEN+1];
166    } dir_data;
167
168    static int get_stat(const char *s, info_struct *i)
169    {
170        LPWSTR w = aux_utf8_to_wide(s);
171        int r = _wstati64(w, i);
172        lmt_memory_free(w);
173        return r;
174    }
175
176    static int mk_dir(const char *s)
177    {
178        LPWSTR w = aux_utf8_to_wide(s);
179        int r = _wmkdir(w);
180        lmt_memory_free(w);
181        return r;
182    }
183
184    static int ch_dir(const char *s)
185    {
186        LPWSTR w = aux_utf8_to_wide(s);
187        int r = _wchdir(w);
188        lmt_memory_free(w);
189        return r;
190    }
191
192    static int rm_dir(const char *s)
193    {
194        LPWSTR w = aux_utf8_to_wide(s);
195        int r = _wrmdir(w);
196        lmt_memory_free(w);
197        return r;
198    }
199
200 // # if defined(__MINGW64__) || defined(__MINGW32__)
201 //     extern int CreateSymbolicLinkW(LPCWSTR lpSymlinkFileName, LPCWSTR lpTargetFileName, DWORD dwFlags);
202 // # endif 
203
204    static int mk_symlink(const char *t, const char *f)
205    {
206        LPWSTR wt = aux_utf8_to_wide(t);
207        LPWSTR wf = aux_utf8_to_wide(f);
208        int r = CreateSymbolicLinkW((LPCWSTR) t, (LPCWSTR) f, 0x2) != 0;
209        lmt_memory_free(wt);
210        lmt_memory_free(wf);
211        return r;
212    }
213
214    static int mk_link(const char *t, const char *f)
215    {
216        LPWSTR wt = aux_utf8_to_wide(t);
217        LPWSTR wf = aux_utf8_to_wide(f);
218        int r = CreateSymbolicLinkW((LPCWSTR) t, (LPCWSTR) f, 0x3) != 0;
219        lmt_memory_free(wt);
220        lmt_memory_free(wf);
221        return r;
222    }
223
224    static int ch_to_exec(const char *s, int n)
225    {
226        LPWSTR w = aux_utf8_to_wide(s);
227        int r = _wchmod(w, n);
228        lmt_memory_free(w);
229        return r;
230    }
231
232 // # ifdef _MSC_VER
233 //
234 //     static int set_utime(const char *s, utime_struct *b)
235 //     {
236 //         LPWSTR w = utf8_to_wide(s);
237 //         HANDLE h = CreateFileW(w, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
238 //         int r = -1;
239 //         lmt_memory_free(w);
240 //         if (h != INVALID_HANDLE_VALUE) {
241 //             r = SetFileTime(h, (const struct _FILETIME *) b, (const struct _FILETIME *) b, (const struct _FILETIME *) b);
242 //             CloseHandle(h);
243 //         }
244 //         return r;
245 //     }
246 //
247 // # else
248
249        static int set_utime(const char *s, utime_struct *b)
250        {
251            LPWSTR w = aux_utf8_to_wide(s);
252            int r = _wutime64(w, b);
253            lmt_memory_free(w);
254            return r;
255        }
256
257 // # endif
258
259# else
260
261    # define info_struct     struct stat
262    # define utime_struct    struct utimbuf
263
264    typedef struct dir_data {
265        DIR  *handle;
266        int   closed;
267        char  pattern[MY_MAXPATHLEN+1];
268    } dir_data;
269
270    # define get_stat        stat
271    # define mk_dir(p)       (mkdir((p), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH))
272    # define ch_dir          chdir
273    # define get_cwd         getcwd
274    # define rm_dir          rmdir
275    # define mk_symlink(f,t) (symlink(f,t) != -1)
276    # define mk_link(f,t)    (link(f,t) != -1)
277    # define ch_to_exec(f,n) (chmod(f,n))
278    # define exec_mode_flag  S_IXUSR | S_IXGRP | S_IXOTH
279    # define set_utime(f,b)  utime(f,b)
280
281# endif
282
283# include <lua.h>
284# include <lauxlib.h>
285# include <lualib.h>
286
287/*
288    This function changes the current directory.
289
290    success = chdir(name)
291*/
292
293static int filelib_chdir(lua_State *L) {
294    if (lua_type(L, 1) == LUA_TSTRING) {
295        lua_pushboolean(L, ! ch_dir(luaL_checkstring(L, 1)));
296    } else {
297        lua_pushboolean(L, 0);
298    }
299    return 1;
300}
301
302/*
303    This function returns the current directory or false.
304
305    name = currentdir()
306*/
307
308# ifdef _WIN32
309
310    static int filelib_currentdir(lua_State *L)
311    {
312        LPWSTR wpath = NULL;
313        int size = 256;
314        while (1) {
315            LPWSTR temp = lmt_memory_realloc(wpath, size * sizeof(WCHAR));
316            wpath = temp;
317            if (! wpath) {
318                lua_pushboolean(L, 0);
319                break;
320            } else if (_wgetcwd(wpath, size)) {
321                char *path = aux_utf8_from_wide(wpath);
322                lua_pushstring(L, path);
323                lmt_memory_free(path);
324                break;
325            } else if (errno != ERANGE) {
326                lua_pushboolean(L, 0);
327                break;
328            } else {
329                size *= 2;
330            }
331        }
332        lmt_memory_free(wpath);
333        return 1;
334    }
335
336# else
337
338    static int filelib_currentdir(lua_State *L)
339    {
340        char *path = NULL;
341        size_t size = MY_MAXPATHLEN;
342        while (1) {
343            path = lmt_memory_realloc(path, size);
344            if (! path) {
345                lua_pushboolean(L,0);
346                break;
347            }
348            if (get_cwd(path, size)) {
349                lua_pushstring(L, path);
350                break;
351            }
352            if (errno != ERANGE) {
353                lua_pushboolean(L,0);
354                break;
355            }
356            size *= 2;
357        }
358        lmt_memory_free(path);
359        return 1;
360    }
361
362# endif
363
364/*
365    This functions create a link:
366
367    success = link(target,name,[true=symbolic])
368    success = symlink(target,name)
369*/
370
371static int filelib_link(lua_State *L)
372{
373    if (lua_type(L, 1) == LUA_TSTRING && lua_type(L, 2) == LUA_TSTRING) {
374        const char *oldpath = lua_tostring(L, 1);
375        const char *newpath = lua_tostring(L, 2);
376        lua_pushboolean(L, lua_toboolean(L, 3) ? mk_symlink(oldpath, newpath) : mk_link(oldpath, newpath));
377    } else {
378        lua_pushboolean(L, 0);
379    }
380    return 1;
381}
382
383static int filelib_symlink(lua_State *L)
384{
385    if (lua_type(L, 1) == LUA_TSTRING && lua_type(L, 2) == LUA_TSTRING) {
386        const char *oldpath = lua_tostring(L, 1);
387        const char *newpath = lua_tostring(L, 2);
388        lua_pushboolean(L, mk_symlink(oldpath, newpath));
389    } else {
390        lua_pushboolean(L, 0);
391    }
392    return 1;
393}
394
395/*
396    This function creates a directory.
397
398    success = mkdir(name)
399*/
400
401static int filelib_mkdir(lua_State *L)
402{
403    if (lua_type(L, 1) == LUA_TSTRING) {
404        lua_pushboolean(L, mk_dir(lua_tostring(L, 1)) != -1);
405    } else {
406        lua_pushboolean(L, 0);
407    }
408    return 1;
409}
410
411/*
412    This function removes a directory (non-recursive).
413
414    success = mkdir(name)
415*/
416
417static int filelib_rmdir(lua_State *L)
418{
419    if (lua_type(L, 1) == LUA_TSTRING) {
420        lua_pushboolean(L, rm_dir(luaL_checkstring(L, 1)) != -1);
421    } else {
422        lua_pushboolean(L, 0);
423    }
424    return 1;
425}
426
427/*
428    The directory iterator returns multiple values:
429
430    for name, mode, size, mtime in dir(path) do ... end
431
432    For practical reasons we keep the metatable the same.
433
434*/
435
436# ifdef _WIN32
437
438    inline static int push_entry(lua_State *L, struct _wfinddata_t file_data, int details)
439    {
440        char *s = aux_utf8_from_wide(file_data.name);
441        lua_pushstring(L, s);
442        lmt_memory_free(s);
443        if (S_ISSUB(file_data.attrib)) {
444            lua_push_key(directory);
445        } else {
446            lua_push_key(file);
447        }
448        if (details) {
449            lua_pushinteger(L, file_data.size);
450            lua_pushinteger(L, file_data.time_write);
451            return 4;
452        } else {
453            return 2;
454        }
455    }
456
457    static int filelib_aux_dir_iterator(lua_State *L)
458    {
459        struct _wfinddata_t file_data;
460        int details = 1;
461        dir_data *d = (dir_data *) luaL_checkudata(L, 1, DIR_METATABLE);
462        lua_getiuservalue(L, 1, 1);
463        details = lua_toboolean(L, -1);
464        lua_pop(L, 1);
465        luaL_argcheck(L, d->closed == 0, 1, "closed directory");
466        if (d->handle == 0L) {
467            /* first entry */
468            LPWSTR s = aux_utf8_to_wide(d->pattern);
469            if ((d->handle = _wfindfirst(s, &file_data)) == -1L) {
470                d->closed = 1;
471                lmt_memory_free(s);
472                return 0;
473            } else {
474                lmt_memory_free(s);
475                return push_entry(L, file_data, details);
476            }
477        } else if (_wfindnext(d->handle, &file_data) == -1L) {
478            /* no more entries */
479         /* lmt_memory_free(d->handle); */ /* is done for us */
480            _findclose(d->handle);
481            d->closed = 1;
482            return 0;
483        } else {
484            /* successive entries */
485            return push_entry(L, file_data, details);
486        }
487    }
488
489    static int filelib_aux_dir_close(lua_State *L)
490    {
491        dir_data *d = (dir_data *) lua_touserdata(L, 1);
492        if (!d->closed && d->handle) {
493            _findclose(d->handle);
494        }
495        d->closed = 1;
496        return 0;
497    }
498
499    static int filelib_dir(lua_State *L)
500    {
501        const char *path = luaL_checkstring(L, 1);
502        int detail = lua_type(L, 2) == LUA_TBOOLEAN ? lua_toboolean(L, 2) : 1;
503        dir_data *d ;
504        lua_pushcfunction(L, filelib_aux_dir_iterator);
505        d = (dir_data *) lua_newuserdatauv(L, sizeof(dir_data), 1);
506        lua_pushboolean(L, detail);
507        lua_setiuservalue(L, -2, 1);
508        luaL_getmetatable(L, DIR_METATABLE);
509        lua_setmetatable(L, -2);
510        d->closed = 0;
511        d->handle = 0L;
512        if (path && strlen(path) > MY_MAXPATHLEN-2) {
513            luaL_error(L, "path too long: %s", path);
514        } else {
515            sprintf(d->pattern, "%s/*", path ? path : "."); /* brrr */
516        }
517        return 2;
518    }
519
520# else
521
522    /*tex
523
524        On unix we cannot get the size and time in one go without interference. Also, not all file
525        systems return this field. So eventually we might not do this on unix and revert to the
526        slower method at the lua end when DT_DIR is undefined. After a report from the mailing
527        list about symbolic link issues this is what Taco and I came up with. The |_less| variant
528        is mainly there because in \UNIX\ we then can avoid a costly |stat| when we don't need the
529        details (only a symlink demands such a |stat|).
530
531    */
532
533    static int filelib_aux_dir_iterator(lua_State *L)
534    {
535        struct dirent *entry;
536        dir_data *d;
537        int details = 1;
538        lua_pushcfunction(L, filelib_aux_dir_iterator);
539        d = (dir_data *) luaL_checkudata(L, 1, DIR_METATABLE);
540        lua_getiuservalue(L, 1, 1);
541        details = lua_toboolean(L, -1);
542        lua_pop(L, 1);
543        luaL_argcheck(L, d->closed == 0, 1, "closed directory");
544        entry = readdir (d->handle);
545        if (entry) {
546            lua_pushstring(L, entry->d_name);
547# ifdef _DIRENT_HAVE_D_TYPE
548            if (! details) {
549                if (entry->d_type == DT_DIR) {
550                    lua_push_key(directory);
551                    return 2;
552                } else if (entry->d_type == DT_REG) {
553                    lua_push_key(file);
554                    return 2;
555                }
556            }
557# endif
558            /*tex We can have a symlink and/or we need the details an dfor both we need to |get_stat|. */
559            {
560                info_struct info;
561                char file_path[2*MY_MAXPATHLEN];
562                snprintf(file_path, 2*MY_MAXPATHLEN, "%s/%s", d->pattern, entry->d_name);
563                if (! get_stat(file_path, &info)) {
564                    if (S_ISDIR(info.st_mode)) {
565                        lua_push_key(directory);
566                    } else if (S_ISREG(info.st_mode) || S_ISLNK(info.st_mode)) {
567                        lua_push_key(file);
568                    } else {
569                        lua_pushnil(L);
570                        return 2;
571                    }
572                    if (details) {
573                        lua_pushinteger(L, info.st_size);
574                        lua_pushinteger(L, info.st_mtime);
575                        return 4;
576                    }
577                } else {
578                    lua_pushnil(L);
579                }
580                return 2;
581            }
582        } else {
583            closedir(d->handle);
584            d->closed = 1;
585            return 0;
586        }
587    }
588
589    static int filelib_aux_dir_close(lua_State *L)
590    {
591        dir_data *d = (dir_data *) lua_touserdata(L, 1);
592        if (!d->closed && d->handle) {
593            closedir(d->handle);
594        }
595        d->closed = 1;
596        return 0;
597    }
598
599    static int filelib_dir(lua_State *L)
600    {
601        const char *path = luaL_checkstring(L, 1);
602        dir_data *d;
603        lua_pushcfunction(L, filelib_aux_dir_iterator);
604        d = (dir_data *) lua_newuserdatauv(L, sizeof(dir_data), 1);
605        lua_pushboolean(L, lua_type(L, 2) == LUA_TBOOLEAN ? lua_toboolean(L, 2) : 1);
606        lua_setiuservalue(L, -2, 1);
607        luaL_getmetatable(L, DIR_METATABLE);
608        lua_setmetatable(L, -2);
609        d->closed = 0;
610        d->handle = opendir(path ? path : ".");
611        if (! d->handle) {
612            luaL_error(L, "cannot open %s: %s", path, strerror(errno));
613        }
614        snprintf(d->pattern, MY_MAXPATHLEN, "%s", path ? path : ".");
615        return 2;
616    }
617
618# endif
619
620static int dir_create_meta(lua_State *L)
621{
622    luaL_newmetatable(L, DIR_METATABLE);
623    lua_newtable(L);
624    lua_pushcfunction(L, filelib_aux_dir_iterator);
625    lua_setfield(L, -2, "next");
626    lua_pushcfunction(L, filelib_aux_dir_close);
627    lua_setfield(L, -2, "close");
628    lua_setfield(L, -2, "__index");
629    lua_pushcfunction(L, filelib_aux_dir_close);
630    lua_setfield(L, -2, "__gc");
631    return 1;
632}
633
634# define mode2string(mode) \
635    ((S_ISREG(mode)) ? "file" : ((S_ISDIR(mode)) ? "directory" : ((S_ISLNK(mode)) ? "link" : "other")))
636
637/* We keep this for a while: will change to { r, w, x hash }  */
638
639# ifdef _WIN32
640
641    static const char *perm2string(unsigned short mode)
642    {
643        static char perms[10] = "---------";
644        /* persistent change hence the for loop */
645        for (int i = 0; i < 9; i++) {
646            perms[i]='-';
647        }
648        if (mode & _S_IREAD)  { perms[0] = 'r'; perms[3] = 'r'; perms[6] = 'r'; }
649        if (mode & _S_IWRITE) { perms[1] = 'w'; perms[4] = 'w'; perms[7] = 'w'; }
650        if (mode & _S_IEXEC)  { perms[2] = 'x'; perms[5] = 'x'; perms[8] = 'x'; }
651        return perms;
652    }
653
654# else
655
656    static const char *perm2string(mode_t mode)
657    {
658        static char perms[10] = "---------";
659        /* persistent change hence the for loop */
660        for (int i = 0; i < 9; i++) {
661            perms[i]='-';
662        }
663        if (mode & S_IRUSR) perms[0] = 'r';
664        if (mode & S_IWUSR) perms[1] = 'w';
665        if (mode & S_IXUSR) perms[2] = 'x';
666        if (mode & S_IRGRP) perms[3] = 'r';
667        if (mode & S_IWGRP) perms[4] = 'w';
668        if (mode & S_IXGRP) perms[5] = 'x';
669        if (mode & S_IROTH) perms[6] = 'r';
670        if (mode & S_IWOTH) perms[7] = 'w';
671        if (mode & S_IXOTH) perms[8] = 'x';
672        return perms;
673    }
674
675# endif
676
677/*
678    The next one sets access time and modification values for a file:
679
680    utime(filename)                    : current, current
681    utime(filename,acess)              : access, access
682    utime(filename,acess,modification) : access, modification
683*/
684
685static int filelib_touch(lua_State *L)
686{
687    if (lua_type(L, 1) == LUA_TSTRING) {
688        const char *file = luaL_checkstring(L, 1);
689        utime_struct utb, *buf;
690        if (lua_gettop(L) == 1) {
691            buf = NULL;
692        } else {
693            utb.actime = (time_t) luaL_optinteger(L, 2, 0);
694            utb.modtime = (time_t) luaL_optinteger(L, 3, utb.actime);
695            buf = &utb;
696        }
697        lua_pushboolean(L, set_utime(file, buf) != -1);
698    } else {
699        lua_pushboolean(L, 0);
700    }
701    return 1;
702}
703
704static void push_st_mode (lua_State *L, info_struct *info) { lua_pushstring (L,  mode2string (info->st_mode)); } /* inode protection mode */
705static void push_st_size (lua_State *L, info_struct *info) { lua_pushinteger(L, (lua_Integer) info->st_size);  } /* file size, in bytes */
706static void push_st_mtime(lua_State *L, info_struct *info) { lua_pushinteger(L, (lua_Integer) info->st_mtime); } /* time of last data modification */
707static void push_st_atime(lua_State *L, info_struct *info) { lua_pushinteger(L, (lua_Integer) info->st_atime); } /* time of last access */
708static void push_st_ctime(lua_State *L, info_struct *info) { lua_pushinteger(L, (lua_Integer) info->st_ctime); } /* time of last file status change */
709static void push_st_perm (lua_State *L, info_struct *info) { lua_pushstring (L,  perm2string (info->st_mode)); } /* permissions string */
710static void push_st_nlink(lua_State *L, info_struct *info) { lua_pushinteger(L, (lua_Integer) info->st_nlink); } /* number of hard links to the file */
711
712typedef void (*push_info_struct_function) (lua_State *L, info_struct *info);
713
714struct file_stat_members {
715    const char                *name;
716    push_info_struct_function  push;
717};
718
719static struct file_stat_members members[] = {
720    { "mode",         push_st_mode  },
721    { "size",         push_st_size  },
722    { "modification", push_st_mtime },
723    { "access",       push_st_atime },
724    { "change",       push_st_ctime },
725    { "permissions",  push_st_perm  },
726    { "nlink",        push_st_nlink },
727    { NULL,           NULL          },
728};
729
730/*
731    Get file or symbolic link information. Returns a table or nil.
732*/
733
734static int filelib_attributes(lua_State *L)
735{
736    if (lua_type(L, 1) == LUA_TSTRING) {
737        info_struct info;
738        const char *file = luaL_checkstring(L, 1);
739        if (get_stat(file, &info)) {
740            /* bad news */
741        } else if (lua_isstring(L, 2)) {
742            const char *member = lua_tostring(L, 2);
743            for (int i = 0; members[i].name; i++) {
744                if (strcmp(members[i].name, member) == 0) {
745                    members[i].push(L, &info);
746                    return 1;
747                }
748            }
749        } else {
750            lua_settop(L, 2);
751            if (! lua_istable(L, 2)) {
752                lua_createtable(L, 0, 6);
753            }
754            for (int i = 0; members[i].name; i++) {
755                lua_pushstring(L, members[i].name);
756                members[i].push(L, &info);
757                lua_rawset(L, -3);
758            }
759            return 1;
760        }
761    }
762    lua_pushnil(L);
763    return 1;
764}
765
766# define is_whatever(L,IS_OK,okay) do { \
767    if (lua_type(L, 1) == LUA_TSTRING) { \
768        info_struct info; \
769        const char *name = lua_tostring(L, 1); \
770        if (get_stat(name, &info)) { \
771            lua_pushboolean(L, 0); \
772        } else { \
773            lua_pushboolean(L, okay && ! access(name, IS_OK)); \
774        } \
775    } else { \
776        lua_pushboolean(L, 0); \
777    } \
778    return 1; \
779} while(1)
780
781static int filelib_isdir          (lua_State *L) { is_whatever(L, F_OK,(S_ISDIR(info.st_mode))); }
782static int filelib_isreadabledir  (lua_State *L) { is_whatever(L, R_OK,(S_ISDIR(info.st_mode))); }
783static int filelib_iswriteabledir (lua_State *L) { is_whatever(L, W_OK,(S_ISDIR(info.st_mode))); }
784
785static int filelib_isfile         (lua_State *L) { is_whatever(L, F_OK,(S_ISREG(info.st_mode) || S_ISLNK(info.st_mode))); }
786static int filelib_isreadablefile (lua_State *L) { is_whatever(L, R_OK,(S_ISREG(info.st_mode) || S_ISLNK(info.st_mode))); }
787static int filelib_iswriteablefile(lua_State *L) { is_whatever(L, W_OK,(S_ISREG(info.st_mode) || S_ISLNK(info.st_mode))); }
788
789static int filelib_setexecutable(lua_State *L)
790{
791    int ok = 0;
792    if (lua_type(L, 1) == LUA_TSTRING) {
793        info_struct info;
794        const char *name = lua_tostring(L, 1);
795        if (! get_stat(name, &info) && S_ISREG(info.st_mode)) {
796            if (ch_to_exec(name, info.st_mode | exec_mode_flag)) {
797                /* the setting failed */
798            } else {
799                ok = 1;
800            }
801        } else {
802            /* not a valid file */
803        }
804    }
805    lua_pushboolean(L, ok);
806    return 1;
807}
808
809/*
810    Push the symlink target to the top of the stack. Assumes the file name is at position 1 of the
811    stack. Returns 1 if successful (with the target on top of the stack), 0 on failure (with stack
812    unchanged, and errno set).
813
814    link("name")          : table
815    link("name","target") : targetname
816*/
817
818static int filelib_symlinktarget(lua_State *L)
819{
820    const char *file = aux_utf8_readlink(luaL_checkstring(L, 1));
821    if (file) {
822        lua_pushstring(L, file);
823    } else { 
824        lua_pushnil(L);
825    }
826    return 1;
827}
828
829static const struct luaL_Reg filelib_function_list[] = {
830    { "attributes",        filelib_attributes        },
831    { "chdir",             filelib_chdir             },
832    { "currentdir",        filelib_currentdir        },
833    { "dir",               filelib_dir               },
834    { "mkdir",             filelib_mkdir             },
835    { "rmdir",             filelib_rmdir             },
836    { "touch",             filelib_touch             },
837    /* */
838    { "link",              filelib_link              },
839    { "symlink",           filelib_symlink           },
840    { "setexecutable",     filelib_setexecutable     },
841    { "symlinktarget",     filelib_symlinktarget     }, 
842    /* */
843    { "isdir",             filelib_isdir             },
844    { "isfile",            filelib_isfile            },
845    { "iswriteabledir",    filelib_iswriteabledir    },
846    { "iswriteablefile",   filelib_iswriteablefile   },
847    { "isreadabledir",     filelib_isreadabledir     },
848    { "isreadablefile",    filelib_isreadablefile    },
849    /* */
850    { NULL,                NULL                      },
851};
852
853int luaopen_filelib(lua_State *L) {
854    dir_create_meta(L);
855    luaL_newlib(L,filelib_function_list);
856    return 1;
857}
858