Improve performance of script validation commit
[jimtcl.git] / jim-file.c
blob065ff2c5d82ff010057070c36c197d8836f6a916
1 /*
2 * Implements the file command for jim
4 * (c) 2008 Steve Bennett <steveb@workware.net.au>
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above
13 * copyright notice, this list of conditions and the following
14 * disclaimer in the documentation and/or other materials
15 * provided with the distribution.
17 * THIS SOFTWARE IS PROVIDED BY THE JIM TCL PROJECT ``AS IS'' AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
19 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
21 * JIM TCL PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
22 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
28 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 * The views and conclusions contained in the software and documentation
31 * are those of the authors and should not be interpreted as representing
32 * official policies, either expressed or implied, of the Jim Tcl Project.
34 * Based on code originally from Tcl 6.7:
36 * Copyright 1987-1991 Regents of the University of California
37 * Permission to use, copy, modify, and distribute this
38 * software and its documentation for any purpose and without
39 * fee is hereby granted, provided that the above copyright
40 * notice appear in all copies. The University of California
41 * makes no representations about the suitability of this
42 * software for any purpose. It is provided "as is" without
43 * express or implied warranty.
46 #include <limits.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <stdio.h>
50 #include <errno.h>
51 #include <sys/stat.h>
53 #include <jimautoconf.h>
54 #include <jim-subcmd.h>
56 #ifdef HAVE_UTIMES
57 #include <sys/time.h>
58 #endif
59 #ifdef HAVE_UNISTD_H
60 #include <unistd.h>
61 #elif defined(_MSC_VER)
62 #include <direct.h>
63 #define F_OK 0
64 #define W_OK 2
65 #define R_OK 4
66 #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
67 #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
68 #endif
70 # ifndef MAXPATHLEN
71 # define MAXPATHLEN JIM_PATH_LEN
72 # endif
74 #if defined(__MINGW32__) || defined(_MSC_VER)
75 #define ISWINDOWS 1
76 #else
77 #define ISWINDOWS 0
78 #endif
81 *----------------------------------------------------------------------
83 * JimGetFileType --
85 * Given a mode word, returns a string identifying the type of a
86 * file.
88 * Results:
89 * A static text string giving the file type from mode.
91 * Side effects:
92 * None.
94 *----------------------------------------------------------------------
97 static const char *JimGetFileType(int mode)
99 if (S_ISREG(mode)) {
100 return "file";
102 else if (S_ISDIR(mode)) {
103 return "directory";
105 #ifdef S_ISCHR
106 else if (S_ISCHR(mode)) {
107 return "characterSpecial";
109 #endif
110 #ifdef S_ISBLK
111 else if (S_ISBLK(mode)) {
112 return "blockSpecial";
114 #endif
115 #ifdef S_ISFIFO
116 else if (S_ISFIFO(mode)) {
117 return "fifo";
119 #endif
120 #ifdef S_ISLNK
121 else if (S_ISLNK(mode)) {
122 return "link";
124 #endif
125 #ifdef S_ISSOCK
126 else if (S_ISSOCK(mode)) {
127 return "socket";
129 #endif
130 return "unknown";
134 *----------------------------------------------------------------------
136 * StoreStatData --
138 * This is a utility procedure that breaks out the fields of a
139 * "stat" structure and stores them in textual form into the
140 * elements of an associative array.
142 * Results:
143 * Returns a standard Tcl return value. If an error occurs then
144 * a message is left in interp->result.
146 * Side effects:
147 * Elements of the associative array given by "varName" are modified.
149 *----------------------------------------------------------------------
151 static void AppendStatElement(Jim_Interp *interp, Jim_Obj *listObj, const char *key, jim_wide value)
153 Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, key, -1));
154 Jim_ListAppendElement(interp, listObj, Jim_NewIntObj(interp, value));
157 static int StoreStatData(Jim_Interp *interp, Jim_Obj *varName, const struct stat *sb)
159 /* Just use a list to store the data */
160 Jim_Obj *listObj = Jim_NewListObj(interp, NULL, 0);
162 AppendStatElement(interp, listObj, "dev", sb->st_dev);
163 AppendStatElement(interp, listObj, "ino", sb->st_ino);
164 AppendStatElement(interp, listObj, "mode", sb->st_mode);
165 AppendStatElement(interp, listObj, "nlink", sb->st_nlink);
166 AppendStatElement(interp, listObj, "uid", sb->st_uid);
167 AppendStatElement(interp, listObj, "gid", sb->st_gid);
168 AppendStatElement(interp, listObj, "size", sb->st_size);
169 AppendStatElement(interp, listObj, "atime", sb->st_atime);
170 AppendStatElement(interp, listObj, "mtime", sb->st_mtime);
171 AppendStatElement(interp, listObj, "ctime", sb->st_ctime);
172 Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, "type", -1));
173 Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, JimGetFileType((int)sb->st_mode), -1));
175 /* Was a variable specified? */
176 if (varName) {
177 Jim_Obj *objPtr = Jim_GetVariable(interp, varName, JIM_NONE);
178 if (objPtr) {
179 if (Jim_DictSize(interp, objPtr) < 0) {
180 /* This message matches the one from Tcl */
181 Jim_SetResultFormatted(interp, "can't set \"%#s(dev)\": variable isn't array", varName);
182 Jim_FreeNewObj(interp, listObj);
183 return JIM_ERR;
186 if (Jim_IsShared(objPtr))
187 objPtr = Jim_DuplicateObj(interp, objPtr);
189 /* Just cheat here and append as a list and convert to a dict */
190 Jim_ListAppendList(interp, objPtr, listObj);
191 Jim_DictSize(interp, objPtr);
192 Jim_InvalidateStringRep(objPtr);
194 Jim_FreeNewObj(interp, listObj);
195 listObj = objPtr;
197 Jim_SetVariable(interp, varName, listObj);
200 /* And also return the value */
201 Jim_SetResult(interp, listObj);
203 return JIM_OK;
206 static int file_cmd_dirname(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
208 const char *path = Jim_String(argv[0]);
209 const char *p = strrchr(path, '/');
211 if (!p && path[0] == '.' && path[1] == '.' && path[2] == '\0') {
212 Jim_SetResultString(interp, "..", -1);
213 } else if (!p) {
214 Jim_SetResultString(interp, ".", -1);
216 else if (p == path) {
217 Jim_SetResultString(interp, "/", -1);
219 else if (ISWINDOWS && p[-1] == ':') {
220 /* z:/dir => z:/ */
221 Jim_SetResultString(interp, path, p - path + 1);
223 else {
224 Jim_SetResultString(interp, path, p - path);
226 return JIM_OK;
229 static int file_cmd_rootname(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
231 const char *path = Jim_String(argv[0]);
232 const char *lastSlash = strrchr(path, '/');
233 const char *p = strrchr(path, '.');
235 if (p == NULL || (lastSlash != NULL && lastSlash > p)) {
236 Jim_SetResult(interp, argv[0]);
238 else {
239 Jim_SetResultString(interp, path, p - path);
241 return JIM_OK;
244 static int file_cmd_extension(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
246 const char *path = Jim_String(argv[0]);
247 const char *lastSlash = strrchr(path, '/');
248 const char *p = strrchr(path, '.');
250 if (p == NULL || (lastSlash != NULL && lastSlash >= p)) {
251 p = "";
253 Jim_SetResultString(interp, p, -1);
254 return JIM_OK;
257 static int file_cmd_tail(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
259 const char *path = Jim_String(argv[0]);
260 const char *lastSlash = strrchr(path, '/');
262 if (lastSlash) {
263 Jim_SetResultString(interp, lastSlash + 1, -1);
265 else {
266 Jim_SetResult(interp, argv[0]);
268 return JIM_OK;
271 static int file_cmd_normalize(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
273 #ifdef HAVE_REALPATH
274 const char *path = Jim_String(argv[0]);
275 char *newname = Jim_Alloc(MAXPATHLEN + 1);
277 if (realpath(path, newname)) {
278 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, newname, -1));
279 return JIM_OK;
281 else {
282 Jim_Free(newname);
283 Jim_SetResultFormatted(interp, "can't normalize \"%#s\": %s", argv[0], strerror(errno));
284 return JIM_ERR;
286 #else
287 Jim_SetResultString(interp, "Not implemented", -1);
288 return JIM_ERR;
289 #endif
292 static int file_cmd_join(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
294 int i;
295 char *newname = Jim_Alloc(MAXPATHLEN + 1);
296 char *last = newname;
298 *newname = 0;
300 /* Simple implementation for now */
301 for (i = 0; i < argc; i++) {
302 int len;
303 const char *part = Jim_GetString(argv[i], &len);
305 if (*part == '/') {
306 /* Absolute component, so go back to the start */
307 last = newname;
309 else if (ISWINDOWS && strchr(part, ':')) {
310 /* Absolute component on mingw, so go back to the start */
311 last = newname;
313 else if (part[0] == '.') {
314 if (part[1] == '/') {
315 part += 2;
316 len -= 2;
318 else if (part[1] == 0 && last != newname) {
319 /* Adding '.' to an existing path does nothing */
320 continue;
324 /* Add a slash if needed */
325 if (last != newname && last[-1] != '/') {
326 *last++ = '/';
329 if (len) {
330 if (last + len - newname >= MAXPATHLEN) {
331 Jim_Free(newname);
332 Jim_SetResultString(interp, "Path too long", -1);
333 return JIM_ERR;
335 memcpy(last, part, len);
336 last += len;
339 /* Remove a slash if needed */
340 if (last > newname + 1 && last[-1] == '/') {
341 /* but on on Windows, leave the trailing slash on "c:/ " */
342 if (!ISWINDOWS || !(last > newname + 2 && last[-2] == ':')) {
343 *--last = 0;
348 *last = 0;
350 /* Probably need to handle some special cases ... */
352 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, newname, last - newname));
354 return JIM_OK;
357 static int file_access(Jim_Interp *interp, Jim_Obj *filename, int mode)
359 Jim_SetResultBool(interp, access(Jim_String(filename), mode) != -1);
361 return JIM_OK;
364 static int file_cmd_readable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
366 return file_access(interp, argv[0], R_OK);
369 static int file_cmd_writable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
371 return file_access(interp, argv[0], W_OK);
374 static int file_cmd_executable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
376 #ifdef X_OK
377 return file_access(interp, argv[0], X_OK);
378 #else
379 /* If no X_OK, just assume true. */
380 Jim_SetResultBool(interp, 1);
381 return JIM_OK;
382 #endif
385 static int file_cmd_exists(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
387 return file_access(interp, argv[0], F_OK);
390 static int file_cmd_delete(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
392 int force = Jim_CompareStringImmediate(interp, argv[0], "-force");
394 if (force || Jim_CompareStringImmediate(interp, argv[0], "--")) {
395 argc++;
396 argv--;
399 while (argc--) {
400 const char *path = Jim_String(argv[0]);
402 if (unlink(path) == -1 && errno != ENOENT) {
403 if (rmdir(path) == -1) {
404 /* Maybe try using the script helper */
405 if (!force || Jim_EvalPrefix(interp, "file delete force", 1, argv) != JIM_OK) {
406 Jim_SetResultFormatted(interp, "couldn't delete file \"%s\": %s", path,
407 strerror(errno));
408 return JIM_ERR;
412 argv++;
414 return JIM_OK;
417 #ifdef HAVE_MKDIR_ONE_ARG
418 #define MKDIR_DEFAULT(PATHNAME) mkdir(PATHNAME)
419 #else
420 #define MKDIR_DEFAULT(PATHNAME) mkdir(PATHNAME, 0755)
421 #endif
424 * Create directory, creating all intermediate paths if necessary.
426 * Returns 0 if OK or -1 on failure (and sets errno)
428 * Note: The path may be modified.
430 static int mkdir_all(char *path)
432 int ok = 1;
434 /* First time just try to make the dir */
435 goto first;
437 while (ok--) {
438 /* Must have failed the first time, so recursively make the parent and try again */
440 char *slash = strrchr(path, '/');
442 if (slash && slash != path) {
443 *slash = 0;
444 if (mkdir_all(path) != 0) {
445 return -1;
447 *slash = '/';
450 first:
451 if (MKDIR_DEFAULT(path) == 0) {
452 return 0;
454 if (errno == ENOENT) {
455 /* Create the parent and try again */
456 continue;
458 /* Maybe it already exists as a directory */
459 if (errno == EEXIST) {
460 struct stat sb;
462 if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) {
463 return 0;
465 /* Restore errno */
466 errno = EEXIST;
468 /* Failed */
469 break;
471 return -1;
474 static int file_cmd_mkdir(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
476 while (argc--) {
477 char *path = Jim_StrDup(Jim_String(argv[0]));
478 int rc = mkdir_all(path);
480 Jim_Free(path);
481 if (rc != 0) {
482 Jim_SetResultFormatted(interp, "can't create directory \"%#s\": %s", argv[0],
483 strerror(errno));
484 return JIM_ERR;
486 argv++;
488 return JIM_OK;
491 static int file_cmd_tempfile(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
493 int fd = Jim_MakeTempFile(interp, (argc >= 1) ? Jim_String(argv[0]) : NULL);
495 if (fd < 0) {
496 return JIM_ERR;
498 close(fd);
500 return JIM_OK;
503 static int file_cmd_rename(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
505 const char *source;
506 const char *dest;
507 int force = 0;
509 if (argc == 3) {
510 if (!Jim_CompareStringImmediate(interp, argv[0], "-force")) {
511 return -1;
513 force++;
514 argv++;
515 argc--;
518 source = Jim_String(argv[0]);
519 dest = Jim_String(argv[1]);
521 if (!force && access(dest, F_OK) == 0) {
522 Jim_SetResultFormatted(interp, "error renaming \"%#s\" to \"%#s\": target exists", argv[0],
523 argv[1]);
524 return JIM_ERR;
527 if (rename(source, dest) != 0) {
528 Jim_SetResultFormatted(interp, "error renaming \"%#s\" to \"%#s\": %s", argv[0], argv[1],
529 strerror(errno));
530 return JIM_ERR;
533 return JIM_OK;
536 #if defined(HAVE_LINK) && defined(HAVE_SYMLINK)
537 static int file_cmd_link(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
539 int ret;
540 const char *source;
541 const char *dest;
542 static const char * const options[] = { "-hard", "-symbolic", NULL };
543 enum { OPT_HARD, OPT_SYMBOLIC, };
544 int option = OPT_HARD;
546 if (argc == 3) {
547 if (Jim_GetEnum(interp, argv[0], options, &option, NULL, JIM_ENUM_ABBREV | JIM_ERRMSG) != JIM_OK) {
548 return JIM_ERR;
550 argv++;
551 argc--;
554 dest = Jim_String(argv[0]);
555 source = Jim_String(argv[1]);
557 if (option == OPT_HARD) {
558 ret = link(source, dest);
560 else {
561 ret = symlink(source, dest);
564 if (ret != 0) {
565 Jim_SetResultFormatted(interp, "error linking \"%#s\" to \"%#s\": %s", argv[0], argv[1],
566 strerror(errno));
567 return JIM_ERR;
570 return JIM_OK;
572 #endif
574 static int file_stat(Jim_Interp *interp, Jim_Obj *filename, struct stat *sb)
576 const char *path = Jim_String(filename);
578 if (stat(path, sb) == -1) {
579 Jim_SetResultFormatted(interp, "could not read \"%#s\": %s", filename, strerror(errno));
580 return JIM_ERR;
582 return JIM_OK;
585 #ifdef HAVE_LSTAT
586 static int file_lstat(Jim_Interp *interp, Jim_Obj *filename, struct stat *sb)
588 const char *path = Jim_String(filename);
590 if (lstat(path, sb) == -1) {
591 Jim_SetResultFormatted(interp, "could not read \"%#s\": %s", filename, strerror(errno));
592 return JIM_ERR;
594 return JIM_OK;
596 #else
597 #define file_lstat file_stat
598 #endif
600 static int file_cmd_atime(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
602 struct stat sb;
604 if (file_stat(interp, argv[0], &sb) != JIM_OK) {
605 return JIM_ERR;
607 Jim_SetResultInt(interp, sb.st_atime);
608 return JIM_OK;
611 static int file_cmd_mtime(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
613 struct stat sb;
615 if (argc == 2) {
616 #ifdef HAVE_UTIMES
617 jim_wide newtime;
618 struct timeval times[2];
620 if (Jim_GetWide(interp, argv[1], &newtime) != JIM_OK) {
621 return JIM_ERR;
624 times[1].tv_sec = times[0].tv_sec = newtime;
625 times[1].tv_usec = times[0].tv_usec = 0;
627 if (utimes(Jim_String(argv[0]), times) != 0) {
628 Jim_SetResultFormatted(interp, "can't set time on \"%#s\": %s", argv[0], strerror(errno));
629 return JIM_ERR;
631 #else
632 Jim_SetResultString(interp, "Not implemented", -1);
633 return JIM_ERR;
634 #endif
636 if (file_stat(interp, argv[0], &sb) != JIM_OK) {
637 return JIM_ERR;
639 Jim_SetResultInt(interp, sb.st_mtime);
640 return JIM_OK;
643 static int file_cmd_copy(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
645 return Jim_EvalPrefix(interp, "file copy", argc, argv);
648 static int file_cmd_size(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
650 struct stat sb;
652 if (file_stat(interp, argv[0], &sb) != JIM_OK) {
653 return JIM_ERR;
655 Jim_SetResultInt(interp, sb.st_size);
656 return JIM_OK;
659 static int file_cmd_isdirectory(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
661 struct stat sb;
662 int ret = 0;
664 if (file_stat(interp, argv[0], &sb) == JIM_OK) {
665 ret = S_ISDIR(sb.st_mode);
667 Jim_SetResultInt(interp, ret);
668 return JIM_OK;
671 static int file_cmd_isfile(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
673 struct stat sb;
674 int ret = 0;
676 if (file_stat(interp, argv[0], &sb) == JIM_OK) {
677 ret = S_ISREG(sb.st_mode);
679 Jim_SetResultInt(interp, ret);
680 return JIM_OK;
683 #ifdef HAVE_GETEUID
684 static int file_cmd_owned(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
686 struct stat sb;
687 int ret = 0;
689 if (file_stat(interp, argv[0], &sb) == JIM_OK) {
690 ret = (geteuid() == sb.st_uid);
692 Jim_SetResultInt(interp, ret);
693 return JIM_OK;
695 #endif
697 #if defined(HAVE_READLINK)
698 static int file_cmd_readlink(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
700 const char *path = Jim_String(argv[0]);
701 char *linkValue = Jim_Alloc(MAXPATHLEN + 1);
703 int linkLength = readlink(path, linkValue, MAXPATHLEN);
705 if (linkLength == -1) {
706 Jim_Free(linkValue);
707 Jim_SetResultFormatted(interp, "couldn't readlink \"%#s\": %s", argv[0], strerror(errno));
708 return JIM_ERR;
710 linkValue[linkLength] = 0;
711 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, linkValue, linkLength));
712 return JIM_OK;
714 #endif
716 static int file_cmd_type(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
718 struct stat sb;
720 if (file_lstat(interp, argv[0], &sb) != JIM_OK) {
721 return JIM_ERR;
723 Jim_SetResultString(interp, JimGetFileType((int)sb.st_mode), -1);
724 return JIM_OK;
727 #ifdef HAVE_LSTAT
728 static int file_cmd_lstat(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
730 struct stat sb;
732 if (file_lstat(interp, argv[0], &sb) != JIM_OK) {
733 return JIM_ERR;
735 return StoreStatData(interp, argc == 2 ? argv[1] : NULL, &sb);
737 #else
738 #define file_cmd_lstat file_cmd_stat
739 #endif
741 static int file_cmd_stat(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
743 struct stat sb;
745 if (file_stat(interp, argv[0], &sb) != JIM_OK) {
746 return JIM_ERR;
748 return StoreStatData(interp, argc == 2 ? argv[1] : NULL, &sb);
751 static const jim_subcmd_type file_command_table[] = {
752 { "atime",
753 "name",
754 file_cmd_atime,
757 /* Description: Last access time */
759 { "mtime",
760 "name ?time?",
761 file_cmd_mtime,
764 /* Description: Get or set last modification time */
766 { "copy",
767 "?-force? source dest",
768 file_cmd_copy,
771 /* Description: Copy source file to destination file */
773 { "dirname",
774 "name",
775 file_cmd_dirname,
778 /* Description: Directory part of the name */
780 { "rootname",
781 "name",
782 file_cmd_rootname,
785 /* Description: Name without any extension */
787 { "extension",
788 "name",
789 file_cmd_extension,
792 /* Description: Last extension including the dot */
794 { "tail",
795 "name",
796 file_cmd_tail,
799 /* Description: Last component of the name */
801 { "normalize",
802 "name",
803 file_cmd_normalize,
806 /* Description: Normalized path of name */
808 { "join",
809 "name ?name ...?",
810 file_cmd_join,
813 /* Description: Join multiple path components */
815 { "readable",
816 "name",
817 file_cmd_readable,
820 /* Description: Is file readable */
822 { "writable",
823 "name",
824 file_cmd_writable,
827 /* Description: Is file writable */
829 { "executable",
830 "name",
831 file_cmd_executable,
834 /* Description: Is file executable */
836 { "exists",
837 "name",
838 file_cmd_exists,
841 /* Description: Does file exist */
843 { "delete",
844 "?-force|--? name ...",
845 file_cmd_delete,
848 /* Description: Deletes the files or directories (must be empty unless -force) */
850 { "mkdir",
851 "dir ...",
852 file_cmd_mkdir,
855 /* Description: Creates the directories */
857 { "tempfile",
858 "?template?",
859 file_cmd_tempfile,
862 /* Description: Creates a temporary filename */
864 { "rename",
865 "?-force? source dest",
866 file_cmd_rename,
869 /* Description: Renames a file */
871 #if defined(HAVE_LINK) && defined(HAVE_SYMLINK)
872 { "link",
873 "?-symbolic|-hard? newname target",
874 file_cmd_link,
877 /* Description: Creates a hard or soft link */
879 #endif
880 #if defined(HAVE_READLINK)
881 { "readlink",
882 "name",
883 file_cmd_readlink,
886 /* Description: Value of the symbolic link */
888 #endif
889 { "size",
890 "name",
891 file_cmd_size,
894 /* Description: Size of file */
896 { "stat",
897 "name ?var?",
898 file_cmd_stat,
901 /* Description: Returns results of stat, and may store in var array */
903 { "lstat",
904 "name ?var?",
905 file_cmd_lstat,
908 /* Description: Returns results of lstat, and may store in var array */
910 { "type",
911 "name",
912 file_cmd_type,
915 /* Description: Returns type of the file */
917 #ifdef HAVE_GETEUID
918 { "owned",
919 "name",
920 file_cmd_owned,
923 /* Description: Returns 1 if owned by the current owner */
925 #endif
926 { "isdirectory",
927 "name",
928 file_cmd_isdirectory,
931 /* Description: Returns 1 if name is a directory */
933 { "isfile",
934 "name",
935 file_cmd_isfile,
938 /* Description: Returns 1 if name is a file */
941 NULL
945 static int Jim_CdCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
947 const char *path;
949 if (argc != 2) {
950 Jim_WrongNumArgs(interp, 1, argv, "dirname");
951 return JIM_ERR;
954 path = Jim_String(argv[1]);
956 if (chdir(path) != 0) {
957 Jim_SetResultFormatted(interp, "couldn't change working directory to \"%s\": %s", path,
958 strerror(errno));
959 return JIM_ERR;
961 return JIM_OK;
964 static int Jim_PwdCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
966 char *cwd = Jim_Alloc(MAXPATHLEN);
968 if (getcwd(cwd, MAXPATHLEN) == NULL) {
969 Jim_SetResultString(interp, "Failed to get pwd", -1);
970 Jim_Free(cwd);
971 return JIM_ERR;
973 else if (ISWINDOWS) {
974 /* Try to keep backslashes out of paths */
975 char *p = cwd;
976 while ((p = strchr(p, '\\')) != NULL) {
977 *p++ = '/';
981 Jim_SetResultString(interp, cwd, -1);
983 Jim_Free(cwd);
984 return JIM_OK;
987 int Jim_fileInit(Jim_Interp *interp)
989 if (Jim_PackageProvide(interp, "file", "1.0", JIM_ERRMSG))
990 return JIM_ERR;
992 Jim_CreateCommand(interp, "file", Jim_SubCmdProc, (void *)file_command_table, NULL);
993 Jim_CreateCommand(interp, "pwd", Jim_PwdCmd, NULL, NULL);
994 Jim_CreateCommand(interp, "cd", Jim_CdCmd, NULL, NULL);
995 return JIM_OK;