/*** * Files, directories and * file system manipulation. * @module fs */ #include #include #include #include #include #include #include #ifdef BSD # include #endif #include #include #include #include "callisto.h" #include "util.h" /* Error messages */ #define E_PATHNAMETOOLONG "pathname too long" #define E_WORKDIRNOTVALID "working directory is no longer valid" #define E_COMPNOTEXIST "component of path does not exist" #define E_DIRNOTEMPTY "directory not empty" #define E_MOUNTPOINT "directory is busy" #define E_NOSUCHDEST "no such file or directory" #define E_FTRAVERSE "failed to traverse directory" #define E_NOSUCHDIR "no such directory" #define E_STICKYDIR "permission denied (sticky directory)" #define E_INTFAULT "internal error (EFAULT)" #define E_MVPARENT "cannot move a parent directory of pathname" #define E_FEXISTS "file exists" #define E_MAXLINK "maximum link count reached" #define E_NOSPACE "insufficient space left on file system" #define E_NOTADIR "pathname or a component of pathname is not a directory" #define E_SYMLINK "could not translate pathname; too many symbolic links" #define E_DIFFFS "pathnames are on different file systems" #define E_DIRDOT "last component of path is '.'" #define E_ISADIR "cannot move a file to the name of a directory" #define E_IOERR "I/O error" #define E_NOMEM "insufficent memory" #define E_QUOTA "file system quota reached" #define E_PERM "permission denied" #define E_ROFS "read-only file system" /*** * Returns the last component of the given pathname, * removing any trailing '/' characters. If the given * path consists entirely of '/' characters, the string * `"/"` is returned. If *path* is an empty string, * the string `"."` is returned. * * This function may return nil if the given * pathname exceeds the system's path length * limit (On most Linux systems this will be 4096). * * @function basename * @usage fs.basename(arg[0]) * @tparam string path The path to process. */ static int fs_basename(lua_State *L) { const char *ret; char *path; /* parameter 1 (string) */ path = strndup(luaL_checkstring(L, 1), lua_rawlen(L, 1)); ret = basename(path); if (ret == NULL && errno == ENAMETOOLONG) /* check if path is too long */ return lfail(L, E_PATHNAMETOOLONG); lua_pushstring(L, ret); return 1; } /*** * Returns the parent directory of the pathname * given. Any trailing '/' characters are not * counted as part of the directory name. * If the given path is an empty string or contains * no '/' characters, the string `"."` is returned, * signifying the current directory. * * This function may return nil if the given * pathname exceeds the system's path length * limit (On most Linux systems this will be 4096). * * @function dirname * @usage fs.dirname(arg[0]) * @tparam string path The path to process. */ static int fs_dirname(lua_State *L) { const char *ret; char *path; /* parameter 1 (string) */ path = strndup(luaL_checkstring(L, 1), lua_rawlen(L, 1)); ret = dirname(path); if (ret == NULL && errno == ENAMETOOLONG) /* check if path is too long */ return lfail(L, E_PATHNAMETOOLONG); lua_pushstring(L, ret); return 1; } /*** * Returns true if the given pathname exists * in the file system, or returns false if it * does not. * * This function may throw an error if the given * pathname exceeds the system's path length * limit (On most Linux systems this will be 4096). * * @function exists * @usage if fs.exists("/etc/fstab") then -- ... end * @tparam string path The path of the file to look for. */ static int fs_exists(lua_State *L) { const char *path; int ret; path = luaL_checkstring(L, 1); ret = access(path, F_OK); /* check if file exists */ if (ret == -1 && errno == ENAMETOOLONG) /* check if path is too long */ return lfail(L, E_PATHNAMETOOLONG); lua_pushboolean(L, ret == 0); return 1; } /* * Taken from OpenBSD mkdir(1) * mkpath -- create directories. * path - path * mode - file mode of terminal directory * dir_mode - file mode of intermediate directories */ static int mkpath(char *path, mode_t mode, mode_t dir_mode) { struct stat sb; char *slash; int done; slash = path; for (;;) { slash += strspn(slash, "/"); slash += strcspn(slash, "/"); done = (*slash == '\0'); *slash = '\0'; if (mkdir(path, done ? mode : dir_mode) == 0) { if (mode > 0777 && chmod(path, mode) == -1) return -1; } else { int mkdir_errno = errno; if (stat(path, &sb) == -1) { /* Not there; use mkdir()s errno */ errno = mkdir_errno; return -1; } if (!S_ISDIR(sb.st_mode)) { /* Is there, but isn't a directory */ errno = ENOTDIR; return -1; } } if (done) break; *slash = '/'; } return 0; } /*** * Creates a new directory. * * If *recursive* is true, creates intermediate directories * as required; behaves as POSIX `mkdir -p` would. * * On success, returns true. Otherwise returns nil plus * an error message. * * This function will return nil plus an error message * if one of the following conditions are met: * * - A component of one of the given pathnames is not a directory * * - The given pathname exceeds the system's path length limit * * - A component of the path prefix does not exist * * - Search permission is denied for a component of the path prefix, * or write permission is denied on the parent directory of the * directory to be created. * * - The given pathname could not be translated as a result * of too many symbolic links * * - The directory to be created resides on a read-only file system * * - The pathname given exists as a file * * - There is no space left on the file system * * - The current user's quota of disk blocks on the file system * containing the parent directory of the directory to be created * has been exhausted * * @function mkdir * @usage fs.mkdir("/usr/local/bin") * @tparam string dir The path of the directory to create. * @tparam[opt] boolean recursive Whether to create intermediate * directories as required. */ static int fs_mkdir(lua_State *L) { char *dir; /* parameter 1 (string) */ int recursive; /* parameter 2 (boolean) */ int ret; dir = (char *)luaL_checkstring(L, 1); if (!lua_isboolean(L, 2) && !lua_isnoneornil(L, 2)) { luaL_typeerror(L, 2, "boolean"); } recursive = lua_toboolean(L, 2); if (recursive) { ret = mkpath(dir, 0777, 0777); } else { ret = mkdir(dir, 0777); } if (ret == 0) { lua_pushboolean(L, 1); return 1; } switch (errno) { case ENOTDIR: return lfail(L, E_NOTADIR); break; case ENAMETOOLONG: return lfail(L, E_PATHNAMETOOLONG); break; case ENOENT: return lfail(L, E_COMPNOTEXIST); break; case EACCES: return lfail(L, E_PERM); break; case ELOOP: return lfail(L, E_SYMLINK); break; case EROFS: return lfail(L, E_ROFS); break; case EEXIST: return lfail(L, E_FEXISTS); break; case ENOSPC: return lfail(L, E_NOSPACE); break; case EDQUOT: return lfail(L, E_QUOTA); break; case EIO: return lfail(L, E_IOERR); break; case EFAULT: return lfail(L, E_INTFAULT); break; } return 0; } /*** * Moves the file at the path *src* to the path *dest*, * moving it between directories if required. If *dest* * exists, it is first removed. Both *src* and *dest* * must be of the same type (that is, both directories or * both non-directories) and must reside on the same file system. * * This function will return nil plus an error message * if one of the following conditions are met: * * - One of the given pathnames exceeds the system's path length limit * * - One of the given pathnames point to a file that does not exist * * - An attempt was made to move a parent directory of a pathname * * - The current user is denied permission to perform the action * * - One of the given pathnames could not be translated as a result * of too many symbolic links * * - A component of one of the given pathnames is not a directory * * - *src* is a directory, but *dest* is not * * - *dest* is a directory, but *src* is not * * - The given pathnames are on different file systems * * - There is no space left on the file system * * - The current user's quota of disk blocks on the file system * containing the directory of *dest* has been exhausted * * - The directory containing *dest* resides on a read-only file system * * @function move * @usage fs.move("file1", "file2") * @tparam string src The pathname of the file to move. * @tparam string dest The destination pathname. */ static int fs_move(lua_State *L) { int ret; const char *src; /* parameter 1 (string) */ const char *dest; /* parameter 2 (string) */ src = luaL_checkstring(L, 1); dest = luaL_checkstring(L, 2); ret = rename(src, dest); /* move file */ if (ret == 0) { /* check for success */ lua_pushboolean(L, 1); return 1; } switch (errno) { case ENAMETOOLONG: return lfail(L, E_PATHNAMETOOLONG); break; case ENOENT: return lfail(L, E_NOSUCHDEST); break; case EACCES: return lfail(L, E_PERM); break; case EPERM: return lfail(L, E_STICKYDIR); break; case ELOOP: return lfail(L, E_SYMLINK); break; case EMLINK: return lfail(L, E_MAXLINK); break; case ENOTDIR: return lfail(L, E_NOTADIR); break; case EISDIR: return lfail(L, E_ISADIR); break; case EXDEV: return lfail(L, E_DIFFFS); break; case ENOSPC: return lfail(L, E_NOSPACE); break; case EDQUOT: return lfail(L, E_QUOTA); break; case EIO: return lfail(L, E_IOERR); break; case EROFS: return lfail(L, E_ROFS); break; case EFAULT: return lfail(L, E_INTFAULT); break; case EINVAL: return lfail(L, E_MVPARENT); break; } return 0; } /*** * Removes an empty directory. * * On success, returns true. Otherwise returns nil plus * an error message. * * This function will return nil plus an error message * if one of the following conditions are met: * * - A component of the given path is not a directory * * - The given pathname exceeds the system's path length limit * * - The named directory does not exist * * - The given pathname could not be translated as a result * of too many symbolic links * * - The named directory contains files other than '.' and '..' in it * * - Search permission is denied for a component of the path prefix, * or write permission is denied on the directory containing the directory * to be removed * * - The directory containing the directory to be removed is marked sticky, * and neither the containing directory nor the directory to be removed are * owned by the current user * * - The directory to be removed or the directory containing it has its * immutable or append-only flag set * * - The directory to be removed is the mount point for a mounted file system * * - The last component of the directory to be removed is a '.' * * - The directory to be created resides on a read-only file system * * @function mkdir * @usage fs.rmdir("yourdirectory") * @tparam string dir The path of the directory to remove. */ static int fs_rmdir(lua_State *L) { const char *dir; int ret; dir = luaL_checkstring(L, 1); ret = rmdir(dir); if (ret == 0) { lua_pushboolean(L, 1); return 1; } switch (errno) { case ENOTDIR: return lfail(L, E_NOTADIR); break; case ENAMETOOLONG: return lfail(L, E_PATHNAMETOOLONG); break; case ENOENT: return lfail(L, E_NOSUCHDIR); break; case ELOOP: return lfail(L, E_SYMLINK); break; case ENOTEMPTY: return lfail(L, E_DIRNOTEMPTY); break; case EACCES: return lfail(L, E_PERM); break; case EBUSY: return lfail(L, E_MOUNTPOINT); break; case EINVAL: return lfail(L, E_DIRDOT); break; case EIO: return lfail(L, E_IOERR); break; case EROFS: return lfail(L, E_ROFS); break; case EFAULT: return lfail(L, E_INTFAULT); break; } return 0; } /*** * Returns or sets the current working directory. * * If *dir* is nil, returns the current working * directory. Otherwise, sets the current working * directory to *dir* and returns true on success. * * This function will return nil and an error * message if one of the following conditions are met: * * - One of the given pathnames exceeds the system's path length limit * * - The current user is denied permission to perform the action * * - The current working directory or *dir* no longer exists * * - *dir* is not nil, nor is it the name of a directory * * - *dir* could not be translated as a result of too many symbolic links * * @function workdir * @usage -- Store the current working directory in a variable: local wd = fs.workdir() -- Set the current working directory to the value of -- the HOME environment variable: fs.workdir(environ["HOME"]) * @tparam[opt] string dir The directory to set as the current working directory. */ static int fs_workdir(lua_State *L) { const char *workdir; /* parameter 1 (string) */ char *buffer; /* buffer used by getcwd() */ char *ret; if (lua_isnoneornil(L, 1)) { /* if first argument was not given... */ buffer = malloc(sizeof(char *) * 512); ret = getcwd(buffer, 512); if (ret != NULL) { lua_pushstring(L, buffer); free(buffer); return 1; } free(buffer); switch (errno) { case EACCES: return lfail(L, E_PERM); break; case EFAULT: return lfail(L, E_INTFAULT); break; case ENOENT: return lfail(L, E_WORKDIRNOTVALID); break; case ENOMEM: return lfail(L, E_NOMEM); break; case ERANGE: case ENAMETOOLONG: /* glibc */ return lfail(L, E_PATHNAMETOOLONG); break; } return 0; } else { workdir = luaL_checkstring(L, 1); if (chdir(workdir) == 0) { lua_pushboolean(L, 1); return 1; } switch (errno) { case ENOTDIR: return lfail(L, E_NOTADIR); break; case ENAMETOOLONG: return lfail(L, E_PATHNAMETOOLONG); break; case ENOENT: return lfail(L, E_NOSUCHDEST); break; case ELOOP: return lfail(L, E_SYMLINK); break; case EACCES: return lfail(L, E_PERM); break; case EFAULT: return lfail(L, E_INTFAULT); break; case EIO: return lfail(L, E_IOERR); break; } return 0; } } static const luaL_Reg fslib[] = { {"basename", fs_basename}, {"dirname", fs_dirname}, {"exists", fs_exists}, {"mkdir", fs_mkdir}, {"move", fs_move}, {"rmdir", fs_rmdir}, {"workdir", fs_workdir}, {NULL, NULL} }; int luaopen_fs(lua_State *L) { luaL_newlib(L, fslib); return 1; }