/* * Implements the file command for jim * * (c) 2008 Steve Bennett * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE JIM TCL PROJECT ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * JIM TCL PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation * are those of the authors and should not be interpreted as representing * official policies, either expressed or implied, of the Jim Tcl Project. * * Based on code originally from Tcl 6.7: * * Copyright 1987-1991 Regents of the University of California * Permission to use, copy, modify, and distribute this * software and its documentation for any purpose and without * fee is hereby granted, provided that the above copyright * notice appear in all copies. The University of California * makes no representations about the suitability of this * software for any purpose. It is provided "as is" without * express or implied warranty. */ #include #include #include #include #include #include #include #include #ifdef HAVE_UTIMES #include #endif #ifdef HAVE_UNISTD_H #include #elif defined(_MSC_VER) #include #define F_OK 0 #define W_OK 2 #define R_OK 4 #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #endif # ifndef MAXPATHLEN # define MAXPATHLEN JIM_PATH_LEN # endif #if defined(__MINGW32__) || defined(__MSYS__) || defined(_MSC_VER) #define ISWINDOWS 1 #else #define ISWINDOWS 0 #endif /* extract nanosecond resolution mtime from struct stat */ #if defined(HAVE_STRUCT_STAT_ST_MTIMESPEC) #define STAT_MTIME_US(STAT) ((STAT).st_mtimespec.tv_sec * 1000000ll + (STAT).st_mtimespec.tv_nsec / 1000) #elif defined(HAVE_STRUCT_STAT_ST_MTIM) #define STAT_MTIME_US(STAT) ((STAT).st_mtim.tv_sec * 1000000ll + (STAT).st_mtim.tv_nsec / 1000) #endif /* *---------------------------------------------------------------------- * * JimGetFileType -- * * Given a mode word, returns a string identifying the type of a * file. * * Results: * A static text string giving the file type from mode. * * Side effects: * None. * *---------------------------------------------------------------------- */ static const char *JimGetFileType(int mode) { if (S_ISREG(mode)) { return "file"; } else if (S_ISDIR(mode)) { return "directory"; } #ifdef S_ISCHR else if (S_ISCHR(mode)) { return "characterSpecial"; } #endif #ifdef S_ISBLK else if (S_ISBLK(mode)) { return "blockSpecial"; } #endif #ifdef S_ISFIFO else if (S_ISFIFO(mode)) { return "fifo"; } #endif #ifdef S_ISLNK else if (S_ISLNK(mode)) { return "link"; } #endif #ifdef S_ISSOCK else if (S_ISSOCK(mode)) { return "socket"; } #endif return "unknown"; } /* *---------------------------------------------------------------------- * * StoreStatData -- * * This is a utility procedure that breaks out the fields of a * "stat" structure and stores them in textual form into the * elements of an associative array. * * Results: * Returns a standard Tcl return value. If an error occurs then * a message is left in interp->result. * * Side effects: * Elements of the associative array given by "varName" are modified. * *---------------------------------------------------------------------- */ static void AppendStatElement(Jim_Interp *interp, Jim_Obj *listObj, const char *key, jim_wide value) { Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, key, -1)); Jim_ListAppendElement(interp, listObj, Jim_NewIntObj(interp, value)); } static int StoreStatData(Jim_Interp *interp, Jim_Obj *varName, const jim_stat_t *sb) { /* Just use a list to store the data */ Jim_Obj *listObj = Jim_NewListObj(interp, NULL, 0); AppendStatElement(interp, listObj, "dev", sb->st_dev); AppendStatElement(interp, listObj, "ino", sb->st_ino); AppendStatElement(interp, listObj, "mode", sb->st_mode); AppendStatElement(interp, listObj, "nlink", sb->st_nlink); AppendStatElement(interp, listObj, "uid", sb->st_uid); AppendStatElement(interp, listObj, "gid", sb->st_gid); AppendStatElement(interp, listObj, "size", sb->st_size); AppendStatElement(interp, listObj, "atime", sb->st_atime); AppendStatElement(interp, listObj, "mtime", sb->st_mtime); AppendStatElement(interp, listObj, "ctime", sb->st_ctime); #ifdef STAT_MTIME_US AppendStatElement(interp, listObj, "mtimeus", STAT_MTIME_US(*sb)); #endif Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, "type", -1)); Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, JimGetFileType((int)sb->st_mode), -1)); /* Was a variable specified? */ if (varName) { Jim_Obj *objPtr; objPtr = Jim_GetVariable(interp, varName, JIM_NONE); if (objPtr) { Jim_Obj *objv[2]; objv[0] = objPtr; objv[1] = listObj; objPtr = Jim_DictMerge(interp, 2, objv); if (objPtr == NULL) { /* This message matches the one from Tcl */ Jim_SetResultFormatted(interp, "can't set \"%#s(dev)\": variable isn't array", varName); Jim_FreeNewObj(interp, listObj); return JIM_ERR; } Jim_InvalidateStringRep(objPtr); Jim_FreeNewObj(interp, listObj); listObj = objPtr; } Jim_SetVariable(interp, varName, listObj); } /* And also return the value */ Jim_SetResult(interp, listObj); return JIM_OK; } /** * Give a path of length 'len', returns the length of the path * with any trailing slashes removed. */ static int JimPathLenNoTrailingSlashes(const char *path, int len) { int i; for (i = len; i > 1 && path[i - 1] == '/'; i--) { /* Trailing slash, so remove it */ if (ISWINDOWS && path[i - 2] == ':') { /* But on windows, we won't remove the trailing slash from c:/ */ break; } } return i; } /** * Give a path in objPtr, returns a new path with any trailing slash removed. * Use Jim_DecrRefCount() on the returned object (which may be identical to objPtr). */ static Jim_Obj *JimStripTrailingSlashes(Jim_Interp *interp, Jim_Obj *objPtr) { int len = Jim_Length(objPtr); const char *path = Jim_String(objPtr); int i = JimPathLenNoTrailingSlashes(path, len); if (i != len) { objPtr = Jim_NewStringObj(interp, path, i); } Jim_IncrRefCount(objPtr); return objPtr; } static int file_cmd_dirname(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { Jim_Obj *objPtr = JimStripTrailingSlashes(interp, argv[0]); const char *path = Jim_String(objPtr); const char *p = strrchr(path, '/'); if (!p) { Jim_SetResultString(interp, ".", -1); } else if (p[1] == 0) { /* Trailing slash so do nothing */ Jim_SetResult(interp, objPtr); } else if (p == path) { Jim_SetResultString(interp, "/", -1); } else if (ISWINDOWS && p[-1] == ':') { /* z:/dir => z:/ */ Jim_SetResultString(interp, path, p - path + 1); } else { /* Strip any trailing slashes from the result */ int len = JimPathLenNoTrailingSlashes(path, p - path); Jim_SetResultString(interp, path, len); } Jim_DecrRefCount(interp, objPtr); return JIM_OK; } static int file_cmd_split(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { Jim_Obj *listObj = Jim_NewListObj(interp, NULL, 0); const char *path = Jim_String(argv[0]); if (*path == '/') { Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, "/", 1)); } while (1) { /* Remove leading slashes */ while (*path == '/') { path++; } if (*path) { const char *pt = strchr(path, '/'); if (pt) { Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, path, pt - path)); path = pt; continue; } Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, path, -1)); } break; } Jim_SetResult(interp, listObj); return JIM_OK; } static int file_cmd_rootname(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { const char *path = Jim_String(argv[0]); const char *lastSlash = strrchr(path, '/'); const char *p = strrchr(path, '.'); if (p == NULL || (lastSlash != NULL && lastSlash > p)) { Jim_SetResult(interp, argv[0]); } else { Jim_SetResultString(interp, path, p - path); } return JIM_OK; } static int file_cmd_extension(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { Jim_Obj *objPtr = JimStripTrailingSlashes(interp, argv[0]); const char *path = Jim_String(objPtr); const char *lastSlash = strrchr(path, '/'); const char *p = strrchr(path, '.'); if (p == NULL || (lastSlash != NULL && lastSlash >= p)) { p = ""; } Jim_SetResultString(interp, p, -1); Jim_DecrRefCount(interp, objPtr); return JIM_OK; } static int file_cmd_tail(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { Jim_Obj *objPtr = JimStripTrailingSlashes(interp, argv[0]); const char *path = Jim_String(objPtr); const char *lastSlash = strrchr(path, '/'); if (lastSlash) { Jim_SetResultString(interp, lastSlash + 1, -1); } else { Jim_SetResult(interp, objPtr); } Jim_DecrRefCount(interp, objPtr); return JIM_OK; } static int file_cmd_normalize(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { #ifdef HAVE_REALPATH const char *path = Jim_String(argv[0]); char *newname = Jim_Alloc(MAXPATHLEN + 1); if (realpath(path, newname)) { Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, newname, -1)); return JIM_OK; } else { Jim_Free(newname); Jim_SetResultFormatted(interp, "can't normalize \"%#s\": %s", argv[0], strerror(errno)); return JIM_ERR; } #else Jim_SetResultString(interp, "Not implemented", -1); return JIM_ERR; #endif } static int file_cmd_join(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { int i; char *newname = Jim_Alloc(MAXPATHLEN + 1); char *last = newname; *newname = 0; /* Simple implementation for now */ for (i = 0; i < argc; i++) { int len; const char *part = Jim_GetString(argv[i], &len); if (*part == '/') { /* Absolute component, so go back to the start */ last = newname; } else if (ISWINDOWS && strchr(part, ':')) { /* Absolute component on mingw, so go back to the start */ last = newname; } else if (part[0] == '.') { if (part[1] == '/') { part += 2; len -= 2; } else if (part[1] == 0 && last != newname) { /* Adding '.' to an existing path does nothing */ continue; } } /* Add a slash if needed */ if (last != newname && last[-1] != '/') { *last++ = '/'; } if (len) { if (last + len - newname >= MAXPATHLEN) { Jim_Free(newname); Jim_SetResultString(interp, "Path too long", -1); return JIM_ERR; } memcpy(last, part, len); last += len; } /* Remove a slash if needed */ if (last > newname + 1 && last[-1] == '/') { /* but on on Windows, leave the trailing slash on "c:/ " */ if (!ISWINDOWS || !(last > newname + 2 && last[-2] == ':')) { *--last = 0; } } } *last = 0; /* Probably need to handle some special cases ... */ Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, newname, last - newname)); return JIM_OK; } static int file_access(Jim_Interp *interp, Jim_Obj *filename, int mode) { Jim_SetResultBool(interp, access(Jim_String(filename), mode) != -1); return JIM_OK; } static int file_cmd_readable(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { return file_access(interp, argv[0], R_OK); } static int file_cmd_writable(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { return file_access(interp, argv[0], W_OK); } static int file_cmd_executable(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { #ifdef X_OK return file_access(interp, argv[0], X_OK); #else /* If no X_OK, just assume true. */ Jim_SetResultBool(interp, 1); return JIM_OK; #endif } static int file_cmd_exists(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { return file_access(interp, argv[0], F_OK); } static int file_cmd_delete(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { int force = Jim_CompareStringImmediate(interp, argv[0], "-force"); if (force || Jim_CompareStringImmediate(interp, argv[0], "--")) { argc++; argv--; } while (argc--) { const char *path = Jim_String(argv[0]); if (unlink(path) == -1 && errno != ENOENT) { if (rmdir(path) == -1) { /* Maybe try using the script helper */ if (!force || Jim_EvalPrefix(interp, "file delete force", 1, argv) != JIM_OK) { Jim_SetResultFormatted(interp, "couldn't delete file \"%s\": %s", path, strerror(errno)); return JIM_ERR; } } } argv++; } return JIM_OK; } #ifdef HAVE_MKDIR_ONE_ARG #define MKDIR_DEFAULT(PATHNAME) mkdir(PATHNAME) #else #define MKDIR_DEFAULT(PATHNAME) mkdir(PATHNAME, 0755) #endif /** * Create directory, creating all intermediate paths if necessary. * * Returns 0 if OK or -1 on failure (and sets errno) * * Note: The path may be modified. */ static int mkdir_all(char *path) { int ok = 1; /* First time just try to make the dir */ goto first; while (ok--) { /* Must have failed the first time, so recursively make the parent and try again */ { char *slash = strrchr(path, '/'); if (slash && slash != path) { *slash = 0; if (mkdir_all(path) != 0) { return -1; } *slash = '/'; } } first: if (MKDIR_DEFAULT(path) == 0) { return 0; } if (errno == ENOENT) { /* Create the parent and try again */ continue; } /* Maybe it already exists as a directory */ if (errno == EEXIST) { jim_stat_t sb; if (Jim_Stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) { return 0; } /* Restore errno */ errno = EEXIST; } /* Failed */ break; } return -1; } static int file_cmd_mkdir(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { while (argc--) { char *path = Jim_StrDup(Jim_String(argv[0])); int rc = mkdir_all(path); Jim_Free(path); if (rc != 0) { Jim_SetResultFormatted(interp, "can't create directory \"%#s\": %s", argv[0], strerror(errno)); return JIM_ERR; } argv++; } return JIM_OK; } static int file_cmd_tempfile(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { int fd = Jim_MakeTempFile(interp, (argc >= 1) ? Jim_String(argv[0]) : NULL, 0); if (fd < 0) { return JIM_ERR; } close(fd); return JIM_OK; } static int file_cmd_rename(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { const char *source; const char *dest; int force = 0; if (argc == 3) { if (!Jim_CompareStringImmediate(interp, argv[0], "-force")) { return -1; } force++; argv++; argc--; } source = Jim_String(argv[0]); dest = Jim_String(argv[1]); if (!force && access(dest, F_OK) == 0) { Jim_SetResultFormatted(interp, "error renaming \"%#s\" to \"%#s\": target exists", argv[0], argv[1]); return JIM_ERR; } #if ISWINDOWS if (access(dest, F_OK) == 0) { /* Windows won't rename over an existing file */ remove(dest); } #endif if (rename(source, dest) != 0) { Jim_SetResultFormatted(interp, "error renaming \"%#s\" to \"%#s\": %s", argv[0], argv[1], strerror(errno)); return JIM_ERR; } return JIM_OK; } #if defined(HAVE_LINK) && defined(HAVE_SYMLINK) static int file_cmd_link(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { int ret; const char *source; const char *dest; static const char * const options[] = { "-hard", "-symbolic", NULL }; enum { OPT_HARD, OPT_SYMBOLIC, }; int option = OPT_HARD; if (argc == 3) { if (Jim_GetEnum(interp, argv[0], options, &option, NULL, JIM_ENUM_ABBREV | JIM_ERRMSG) != JIM_OK) { return JIM_ERR; } argv++; argc--; } dest = Jim_String(argv[0]); source = Jim_String(argv[1]); if (option == OPT_HARD) { ret = link(source, dest); } else { ret = symlink(source, dest); } if (ret != 0) { Jim_SetResultFormatted(interp, "error linking \"%#s\" to \"%#s\": %s", argv[0], argv[1], strerror(errno)); return JIM_ERR; } return JIM_OK; } #endif static int file_stat(Jim_Interp *interp, Jim_Obj *filename, jim_stat_t *sb) { const char *path = Jim_String(filename); if (Jim_Stat(path, sb) == -1) { Jim_SetResultFormatted(interp, "could not read \"%#s\": %s", filename, strerror(errno)); return JIM_ERR; } return JIM_OK; } #ifdef HAVE_LSTAT static int file_lstat(Jim_Interp *interp, Jim_Obj *filename, jim_stat_t *sb) { const char *path = Jim_String(filename); if (lstat(path, sb) == -1) { Jim_SetResultFormatted(interp, "could not read \"%#s\": %s", filename, strerror(errno)); return JIM_ERR; } return JIM_OK; } #else #define file_lstat file_stat #endif static int file_cmd_atime(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { jim_stat_t sb; if (file_stat(interp, argv[0], &sb) != JIM_OK) { return JIM_ERR; } Jim_SetResultInt(interp, sb.st_atime); return JIM_OK; } /** * Set file atime/mtime to the given time in microseconds since the epoch. */ static int JimSetFileTimes(Jim_Interp *interp, const char *filename, jim_wide us) { #ifdef HAVE_UTIMES struct timeval times[2]; times[1].tv_sec = times[0].tv_sec = us / 1000000; times[1].tv_usec = times[0].tv_usec = us % 1000000; if (utimes(filename, times) != 0) { Jim_SetResultFormatted(interp, "can't set time on \"%s\": %s", filename, strerror(errno)); return JIM_ERR; } return JIM_OK; #else Jim_SetResultString(interp, "Not implemented", -1); return JIM_ERR; #endif } static int file_cmd_mtime(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { jim_stat_t sb; if (argc == 2) { jim_wide secs; if (Jim_GetWide(interp, argv[1], &secs) != JIM_OK) { return JIM_ERR; } return JimSetFileTimes(interp, Jim_String(argv[0]), secs * 1000000); } if (file_stat(interp, argv[0], &sb) != JIM_OK) { return JIM_ERR; } Jim_SetResultInt(interp, sb.st_mtime); return JIM_OK; } #ifdef STAT_MTIME_US static int file_cmd_mtimeus(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { jim_stat_t sb; if (argc == 2) { jim_wide us; if (Jim_GetWide(interp, argv[1], &us) != JIM_OK) { return JIM_ERR; } return JimSetFileTimes(interp, Jim_String(argv[0]), us); } if (file_stat(interp, argv[0], &sb) != JIM_OK) { return JIM_ERR; } Jim_SetResultInt(interp, STAT_MTIME_US(sb)); return JIM_OK; } #endif static int file_cmd_copy(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { return Jim_EvalPrefix(interp, "file copy", argc, argv); } static int file_cmd_size(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { jim_stat_t sb; if (file_stat(interp, argv[0], &sb) != JIM_OK) { return JIM_ERR; } Jim_SetResultInt(interp, sb.st_size); return JIM_OK; } static int file_cmd_isdirectory(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { jim_stat_t sb; int ret = 0; if (file_stat(interp, argv[0], &sb) == JIM_OK) { ret = S_ISDIR(sb.st_mode); } Jim_SetResultInt(interp, ret); return JIM_OK; } static int file_cmd_isfile(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { jim_stat_t sb; int ret = 0; if (file_stat(interp, argv[0], &sb) == JIM_OK) { ret = S_ISREG(sb.st_mode); } Jim_SetResultInt(interp, ret); return JIM_OK; } #ifdef HAVE_GETEUID static int file_cmd_owned(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { jim_stat_t sb; int ret = 0; if (file_stat(interp, argv[0], &sb) == JIM_OK) { ret = (geteuid() == sb.st_uid); } Jim_SetResultInt(interp, ret); return JIM_OK; } #endif #if defined(HAVE_READLINK) static int file_cmd_readlink(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { const char *path = Jim_String(argv[0]); char *linkValue = Jim_Alloc(MAXPATHLEN + 1); int linkLength = readlink(path, linkValue, MAXPATHLEN); if (linkLength == -1) { Jim_Free(linkValue); Jim_SetResultFormatted(interp, "could not read link \"%#s\": %s", argv[0], strerror(errno)); return JIM_ERR; } linkValue[linkLength] = 0; Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, linkValue, linkLength)); return JIM_OK; } #endif static int file_cmd_type(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { jim_stat_t sb; if (file_lstat(interp, argv[0], &sb) != JIM_OK) { return JIM_ERR; } Jim_SetResultString(interp, JimGetFileType((int)sb.st_mode), -1); return JIM_OK; } #ifdef HAVE_LSTAT static int file_cmd_lstat(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { jim_stat_t sb; if (file_lstat(interp, argv[0], &sb) != JIM_OK) { return JIM_ERR; } return StoreStatData(interp, argc == 2 ? argv[1] : NULL, &sb); } #else #define file_cmd_lstat file_cmd_stat #endif static int file_cmd_stat(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { jim_stat_t sb; if (file_stat(interp, argv[0], &sb) != JIM_OK) { return JIM_ERR; } return StoreStatData(interp, argc == 2 ? argv[1] : NULL, &sb); } static const jim_subcmd_type file_command_table[] = { { "atime", "name", file_cmd_atime, 1, 1, /* Description: Last access time */ }, { "mtime", "name ?time?", file_cmd_mtime, 1, 2, /* Description: Get or set last modification time */ }, #ifdef STAT_MTIME_US { "mtimeus", "name ?time?", file_cmd_mtimeus, 1, 2, /* Description: Get or set last modification time in microseconds */ }, #endif { "copy", "?-force? source dest", file_cmd_copy, 2, 3, /* Description: Copy source file to destination file */ }, { "dirname", "name", file_cmd_dirname, 1, 1, /* Description: Directory part of the name */ }, { "rootname", "name", file_cmd_rootname, 1, 1, /* Description: Name without any extension */ }, { "extension", "name", file_cmd_extension, 1, 1, /* Description: Last extension including the dot */ }, { "tail", "name", file_cmd_tail, 1, 1, /* Description: Last component of the name */ }, { "split", "name", file_cmd_split, 1, 1, /* Description: Split path into components as a list */ }, { "normalize", "name", file_cmd_normalize, 1, 1, /* Description: Normalized path of name */ }, { "join", "name ?name ...?", file_cmd_join, 1, -1, /* Description: Join multiple path components */ }, { "readable", "name", file_cmd_readable, 1, 1, /* Description: Is file readable */ }, { "writable", "name", file_cmd_writable, 1, 1, /* Description: Is file writable */ }, { "executable", "name", file_cmd_executable, 1, 1, /* Description: Is file executable */ }, { "exists", "name", file_cmd_exists, 1, 1, /* Description: Does file exist */ }, { "delete", "?-force|--? name ...", file_cmd_delete, 1, -1, /* Description: Deletes the files or directories (must be empty unless -force) */ }, { "mkdir", "dir ...", file_cmd_mkdir, 1, -1, /* Description: Creates the directories */ }, { "tempfile", "?template?", file_cmd_tempfile, 0, 1, /* Description: Creates a temporary filename */ }, { "rename", "?-force? source dest", file_cmd_rename, 2, 3, /* Description: Renames a file */ }, #if defined(HAVE_LINK) && defined(HAVE_SYMLINK) { "link", "?-symbolic|-hard? newname target", file_cmd_link, 2, 3, /* Description: Creates a hard or soft link */ }, #endif #if defined(HAVE_READLINK) { "readlink", "name", file_cmd_readlink, 1, 1, /* Description: Value of the symbolic link */ }, #endif { "size", "name", file_cmd_size, 1, 1, /* Description: Size of file */ }, { "stat", "name ?var?", file_cmd_stat, 1, 2, /* Description: Returns results of stat, and may store in var array */ }, { "lstat", "name ?var?", file_cmd_lstat, 1, 2, /* Description: Returns results of lstat, and may store in var array */ }, { "type", "name", file_cmd_type, 1, 1, /* Description: Returns type of the file */ }, #ifdef HAVE_GETEUID { "owned", "name", file_cmd_owned, 1, 1, /* Description: Returns 1 if owned by the current owner */ }, #endif { "isdirectory", "name", file_cmd_isdirectory, 1, 1, /* Description: Returns 1 if name is a directory */ }, { "isfile", "name", file_cmd_isfile, 1, 1, /* Description: Returns 1 if name is a file */ }, { NULL } }; static int Jim_CdCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { const char *path; if (argc != 2) { Jim_WrongNumArgs(interp, 1, argv, "dirname"); return JIM_ERR; } path = Jim_String(argv[1]); if (chdir(path) != 0) { Jim_SetResultFormatted(interp, "couldn't change working directory to \"%s\": %s", path, strerror(errno)); return JIM_ERR; } return JIM_OK; } static int Jim_PwdCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { char *cwd = Jim_Alloc(MAXPATHLEN); if (getcwd(cwd, MAXPATHLEN) == NULL) { Jim_SetResultString(interp, "Failed to get pwd", -1); Jim_Free(cwd); return JIM_ERR; } else if (ISWINDOWS) { /* Try to keep backslashes out of paths */ char *p = cwd; while ((p = strchr(p, '\\')) != NULL) { *p++ = '/'; } } Jim_SetResultString(interp, cwd, -1); Jim_Free(cwd); return JIM_OK; } int Jim_fileInit(Jim_Interp *interp) { Jim_PackageProvideCheck(interp, "file"); Jim_CreateCommand(interp, "file", Jim_SubCmdProc, (void *)file_command_table, NULL); Jim_CreateCommand(interp, "pwd", Jim_PwdCmd, NULL, NULL); Jim_CreateCommand(interp, "cd", Jim_CdCmd, NULL, NULL); return JIM_OK; }