core: improvements to garbage collection
[jimtcl.git] / jim-file.c
blob53f81f6c4cbcf648344bf4904841bba06ae32d08
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>
52 #include <jimautoconf.h>
53 #include <jim-subcmd.h>
54 #include <jimiocompat.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(__MSYS__) || defined(_MSC_VER)
75 #define ISWINDOWS 1
76 #else
77 #define ISWINDOWS 0
78 #endif
80 /* extract nanosecond resolution mtime from struct stat */
81 #if defined(HAVE_STRUCT_STAT_ST_MTIMESPEC)
82 #define STAT_MTIME_US(STAT) ((STAT).st_mtimespec.tv_sec * 1000000ll + (STAT).st_mtimespec.tv_nsec / 1000)
83 #elif defined(HAVE_STRUCT_STAT_ST_MTIM)
84 #define STAT_MTIME_US(STAT) ((STAT).st_mtim.tv_sec * 1000000ll + (STAT).st_mtim.tv_nsec / 1000)
85 #endif
88 *----------------------------------------------------------------------
90 * JimGetFileType --
92 * Given a mode word, returns a string identifying the type of a
93 * file.
95 * Results:
96 * A static text string giving the file type from mode.
98 * Side effects:
99 * None.
101 *----------------------------------------------------------------------
104 static const char *JimGetFileType(int mode)
106 if (S_ISREG(mode)) {
107 return "file";
109 else if (S_ISDIR(mode)) {
110 return "directory";
112 #ifdef S_ISCHR
113 else if (S_ISCHR(mode)) {
114 return "characterSpecial";
116 #endif
117 #ifdef S_ISBLK
118 else if (S_ISBLK(mode)) {
119 return "blockSpecial";
121 #endif
122 #ifdef S_ISFIFO
123 else if (S_ISFIFO(mode)) {
124 return "fifo";
126 #endif
127 #ifdef S_ISLNK
128 else if (S_ISLNK(mode)) {
129 return "link";
131 #endif
132 #ifdef S_ISSOCK
133 else if (S_ISSOCK(mode)) {
134 return "socket";
136 #endif
137 return "unknown";
141 *----------------------------------------------------------------------
143 * StoreStatData --
145 * This is a utility procedure that breaks out the fields of a
146 * "stat" structure and stores them in textual form into the
147 * elements of an associative array.
149 * Results:
150 * Returns a standard Tcl return value. If an error occurs then
151 * a message is left in interp->result.
153 * Side effects:
154 * Elements of the associative array given by "varName" are modified.
156 *----------------------------------------------------------------------
158 static void AppendStatElement(Jim_Interp *interp, Jim_Obj *listObj, const char *key, jim_wide value)
160 Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, key, -1));
161 Jim_ListAppendElement(interp, listObj, Jim_NewIntObj(interp, value));
164 static int StoreStatData(Jim_Interp *interp, Jim_Obj *varName, const jim_stat_t *sb)
166 /* Just use a list to store the data */
167 Jim_Obj *listObj = Jim_NewListObj(interp, NULL, 0);
169 AppendStatElement(interp, listObj, "dev", sb->st_dev);
170 AppendStatElement(interp, listObj, "ino", sb->st_ino);
171 AppendStatElement(interp, listObj, "mode", sb->st_mode);
172 AppendStatElement(interp, listObj, "nlink", sb->st_nlink);
173 AppendStatElement(interp, listObj, "uid", sb->st_uid);
174 AppendStatElement(interp, listObj, "gid", sb->st_gid);
175 AppendStatElement(interp, listObj, "size", sb->st_size);
176 AppendStatElement(interp, listObj, "atime", sb->st_atime);
177 AppendStatElement(interp, listObj, "mtime", sb->st_mtime);
178 AppendStatElement(interp, listObj, "ctime", sb->st_ctime);
179 #ifdef STAT_MTIME_US
180 AppendStatElement(interp, listObj, "mtimeus", STAT_MTIME_US(*sb));
181 #endif
182 Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, "type", -1));
183 Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, JimGetFileType((int)sb->st_mode), -1));
185 /* Was a variable specified? */
186 if (varName) {
187 Jim_Obj *objPtr;
188 objPtr = Jim_GetVariable(interp, varName, JIM_NONE);
190 if (objPtr) {
191 Jim_Obj *objv[2];
193 objv[0] = objPtr;
194 objv[1] = listObj;
196 objPtr = Jim_DictMerge(interp, 2, objv);
197 if (objPtr == NULL) {
198 /* This message matches the one from Tcl */
199 Jim_SetResultFormatted(interp, "can't set \"%#s(dev)\": variable isn't array", varName);
200 Jim_FreeNewObj(interp, listObj);
201 return JIM_ERR;
204 Jim_InvalidateStringRep(objPtr);
206 Jim_FreeNewObj(interp, listObj);
207 listObj = objPtr;
209 Jim_SetVariable(interp, varName, listObj);
212 /* And also return the value */
213 Jim_SetResult(interp, listObj);
215 return JIM_OK;
219 * Give a path of length 'len', returns the length of the path
220 * with any trailing slashes removed.
222 static int JimPathLenNoTrailingSlashes(const char *path, int len)
224 int i;
225 for (i = len; i > 1 && path[i - 1] == '/'; i--) {
226 /* Trailing slash, so remove it */
227 if (ISWINDOWS && path[i - 2] == ':') {
228 /* But on windows, we won't remove the trailing slash from c:/ */
229 break;
232 return i;
236 * Give a path in objPtr, returns a new path with any trailing slash removed.
237 * Use Jim_DecrRefCount() on the returned object (which may be identical to objPtr).
239 static Jim_Obj *JimStripTrailingSlashes(Jim_Interp *interp, Jim_Obj *objPtr)
241 int len = Jim_Length(objPtr);
242 const char *path = Jim_String(objPtr);
243 int i = JimPathLenNoTrailingSlashes(path, len);
244 if (i != len) {
245 objPtr = Jim_NewStringObj(interp, path, i);
247 Jim_IncrRefCount(objPtr);
248 return objPtr;
251 static int file_cmd_dirname(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
253 Jim_Obj *objPtr = JimStripTrailingSlashes(interp, argv[0]);
254 const char *path = Jim_String(objPtr);
255 const char *p = strrchr(path, '/');
257 if (!p) {
258 Jim_SetResultString(interp, ".", -1);
260 else if (p[1] == 0) {
261 /* Trailing slash so do nothing */
262 Jim_SetResult(interp, objPtr);
264 else if (p == path) {
265 Jim_SetResultString(interp, "/", -1);
267 else if (ISWINDOWS && p[-1] == ':') {
268 /* z:/dir => z:/ */
269 Jim_SetResultString(interp, path, p - path + 1);
271 else {
272 /* Strip any trailing slashes from the result */
273 int len = JimPathLenNoTrailingSlashes(path, p - path);
274 Jim_SetResultString(interp, path, len);
276 Jim_DecrRefCount(interp, objPtr);
277 return JIM_OK;
280 static int file_cmd_split(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
282 Jim_Obj *listObj = Jim_NewListObj(interp, NULL, 0);
283 const char *path = Jim_String(argv[0]);
285 if (*path == '/') {
286 Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, "/", 1));
289 while (1) {
290 /* Remove leading slashes */
291 while (*path == '/') {
292 path++;
294 if (*path) {
295 const char *pt = strchr(path, '/');
296 if (pt) {
297 Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, path, pt - path));
298 path = pt;
299 continue;
301 Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, path, -1));
303 break;
305 Jim_SetResult(interp, listObj);
306 return JIM_OK;
309 static int file_cmd_rootname(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
311 const char *path = Jim_String(argv[0]);
312 const char *lastSlash = strrchr(path, '/');
313 const char *p = strrchr(path, '.');
315 if (p == NULL || (lastSlash != NULL && lastSlash > p)) {
316 Jim_SetResult(interp, argv[0]);
318 else {
319 Jim_SetResultString(interp, path, p - path);
321 return JIM_OK;
324 static int file_cmd_extension(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
326 Jim_Obj *objPtr = JimStripTrailingSlashes(interp, argv[0]);
327 const char *path = Jim_String(objPtr);
328 const char *lastSlash = strrchr(path, '/');
329 const char *p = strrchr(path, '.');
331 if (p == NULL || (lastSlash != NULL && lastSlash >= p)) {
332 p = "";
334 Jim_SetResultString(interp, p, -1);
335 Jim_DecrRefCount(interp, objPtr);
336 return JIM_OK;
339 static int file_cmd_tail(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
341 Jim_Obj *objPtr = JimStripTrailingSlashes(interp, argv[0]);
342 const char *path = Jim_String(objPtr);
343 const char *lastSlash = strrchr(path, '/');
345 if (lastSlash) {
346 Jim_SetResultString(interp, lastSlash + 1, -1);
348 else {
349 Jim_SetResult(interp, objPtr);
351 Jim_DecrRefCount(interp, objPtr);
352 return JIM_OK;
355 static int file_cmd_normalize(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
357 #ifdef HAVE_REALPATH
358 const char *path = Jim_String(argv[0]);
359 char *newname = Jim_Alloc(MAXPATHLEN + 1);
361 if (realpath(path, newname)) {
362 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, newname, -1));
363 return JIM_OK;
365 else {
366 Jim_Free(newname);
367 Jim_SetResultFormatted(interp, "can't normalize \"%#s\": %s", argv[0], strerror(errno));
368 return JIM_ERR;
370 #else
371 Jim_SetResultString(interp, "Not implemented", -1);
372 return JIM_ERR;
373 #endif
376 static int file_cmd_join(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
378 int i;
379 char *newname = Jim_Alloc(MAXPATHLEN + 1);
380 char *last = newname;
382 *newname = 0;
384 /* Simple implementation for now */
385 for (i = 0; i < argc; i++) {
386 int len;
387 const char *part = Jim_GetString(argv[i], &len);
389 if (*part == '/') {
390 /* Absolute component, so go back to the start */
391 last = newname;
393 else if (ISWINDOWS && strchr(part, ':')) {
394 /* Absolute component on mingw, so go back to the start */
395 last = newname;
397 else if (part[0] == '.') {
398 if (part[1] == '/') {
399 part += 2;
400 len -= 2;
402 else if (part[1] == 0 && last != newname) {
403 /* Adding '.' to an existing path does nothing */
404 continue;
408 /* Add a slash if needed */
409 if (last != newname && last[-1] != '/') {
410 *last++ = '/';
413 if (len) {
414 if (last + len - newname >= MAXPATHLEN) {
415 Jim_Free(newname);
416 Jim_SetResultString(interp, "Path too long", -1);
417 return JIM_ERR;
419 memcpy(last, part, len);
420 last += len;
423 /* Remove a slash if needed */
424 if (last > newname + 1 && last[-1] == '/') {
425 /* but on on Windows, leave the trailing slash on "c:/ " */
426 if (!ISWINDOWS || !(last > newname + 2 && last[-2] == ':')) {
427 *--last = 0;
432 *last = 0;
434 /* Probably need to handle some special cases ... */
436 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, newname, last - newname));
438 return JIM_OK;
441 static int file_access(Jim_Interp *interp, Jim_Obj *filename, int mode)
443 Jim_SetResultBool(interp, access(Jim_String(filename), mode) != -1);
445 return JIM_OK;
448 static int file_cmd_readable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
450 return file_access(interp, argv[0], R_OK);
453 static int file_cmd_writable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
455 return file_access(interp, argv[0], W_OK);
458 static int file_cmd_executable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
460 #ifdef X_OK
461 return file_access(interp, argv[0], X_OK);
462 #else
463 /* If no X_OK, just assume true. */
464 Jim_SetResultBool(interp, 1);
465 return JIM_OK;
466 #endif
469 static int file_cmd_exists(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
471 return file_access(interp, argv[0], F_OK);
474 static int file_cmd_delete(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
476 int force = Jim_CompareStringImmediate(interp, argv[0], "-force");
478 if (force || Jim_CompareStringImmediate(interp, argv[0], "--")) {
479 argc++;
480 argv--;
483 while (argc--) {
484 const char *path = Jim_String(argv[0]);
486 if (unlink(path) == -1 && errno != ENOENT) {
487 if (rmdir(path) == -1) {
488 /* Maybe try using the script helper */
489 if (!force || Jim_EvalPrefix(interp, "file delete force", 1, argv) != JIM_OK) {
490 Jim_SetResultFormatted(interp, "couldn't delete file \"%s\": %s", path,
491 strerror(errno));
492 return JIM_ERR;
496 argv++;
498 return JIM_OK;
501 #ifdef HAVE_MKDIR_ONE_ARG
502 #define MKDIR_DEFAULT(PATHNAME) mkdir(PATHNAME)
503 #else
504 #define MKDIR_DEFAULT(PATHNAME) mkdir(PATHNAME, 0755)
505 #endif
508 * Create directory, creating all intermediate paths if necessary.
510 * Returns 0 if OK or -1 on failure (and sets errno)
512 * Note: The path may be modified.
514 static int mkdir_all(char *path)
516 int ok = 1;
518 /* First time just try to make the dir */
519 goto first;
521 while (ok--) {
522 /* Must have failed the first time, so recursively make the parent and try again */
524 char *slash = strrchr(path, '/');
526 if (slash && slash != path) {
527 *slash = 0;
528 if (mkdir_all(path) != 0) {
529 return -1;
531 *slash = '/';
534 first:
535 if (MKDIR_DEFAULT(path) == 0) {
536 return 0;
538 if (errno == ENOENT) {
539 /* Create the parent and try again */
540 continue;
542 /* Maybe it already exists as a directory */
543 if (errno == EEXIST) {
544 jim_stat_t sb;
546 if (Jim_Stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) {
547 return 0;
549 /* Restore errno */
550 errno = EEXIST;
552 /* Failed */
553 break;
555 return -1;
558 static int file_cmd_mkdir(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
560 while (argc--) {
561 char *path = Jim_StrDup(Jim_String(argv[0]));
562 int rc = mkdir_all(path);
564 Jim_Free(path);
565 if (rc != 0) {
566 Jim_SetResultFormatted(interp, "can't create directory \"%#s\": %s", argv[0],
567 strerror(errno));
568 return JIM_ERR;
570 argv++;
572 return JIM_OK;
575 static int file_cmd_tempfile(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
577 int fd = Jim_MakeTempFile(interp, (argc >= 1) ? Jim_String(argv[0]) : NULL, 0);
579 if (fd < 0) {
580 return JIM_ERR;
582 close(fd);
584 return JIM_OK;
587 static int file_cmd_rename(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
589 const char *source;
590 const char *dest;
591 int force = 0;
593 if (argc == 3) {
594 if (!Jim_CompareStringImmediate(interp, argv[0], "-force")) {
595 return -1;
597 force++;
598 argv++;
599 argc--;
602 source = Jim_String(argv[0]);
603 dest = Jim_String(argv[1]);
605 if (!force && access(dest, F_OK) == 0) {
606 Jim_SetResultFormatted(interp, "error renaming \"%#s\" to \"%#s\": target exists", argv[0],
607 argv[1]);
608 return JIM_ERR;
610 #if ISWINDOWS
611 if (access(dest, F_OK) == 0) {
612 /* Windows won't rename over an existing file */
613 remove(dest);
615 #endif
616 if (rename(source, dest) != 0) {
617 Jim_SetResultFormatted(interp, "error renaming \"%#s\" to \"%#s\": %s", argv[0], argv[1],
618 strerror(errno));
619 return JIM_ERR;
622 return JIM_OK;
625 #if defined(HAVE_LINK) && defined(HAVE_SYMLINK)
626 static int file_cmd_link(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
628 int ret;
629 const char *source;
630 const char *dest;
631 static const char * const options[] = { "-hard", "-symbolic", NULL };
632 enum { OPT_HARD, OPT_SYMBOLIC, };
633 int option = OPT_HARD;
635 if (argc == 3) {
636 if (Jim_GetEnum(interp, argv[0], options, &option, NULL, JIM_ENUM_ABBREV | JIM_ERRMSG) != JIM_OK) {
637 return JIM_ERR;
639 argv++;
640 argc--;
643 dest = Jim_String(argv[0]);
644 source = Jim_String(argv[1]);
646 if (option == OPT_HARD) {
647 ret = link(source, dest);
649 else {
650 ret = symlink(source, dest);
653 if (ret != 0) {
654 Jim_SetResultFormatted(interp, "error linking \"%#s\" to \"%#s\": %s", argv[0], argv[1],
655 strerror(errno));
656 return JIM_ERR;
659 return JIM_OK;
661 #endif
663 static int file_stat(Jim_Interp *interp, Jim_Obj *filename, jim_stat_t *sb)
665 const char *path = Jim_String(filename);
667 if (Jim_Stat(path, sb) == -1) {
668 Jim_SetResultFormatted(interp, "could not read \"%#s\": %s", filename, strerror(errno));
669 return JIM_ERR;
671 return JIM_OK;
674 #ifdef HAVE_LSTAT
675 static int file_lstat(Jim_Interp *interp, Jim_Obj *filename, jim_stat_t *sb)
677 const char *path = Jim_String(filename);
679 if (lstat(path, sb) == -1) {
680 Jim_SetResultFormatted(interp, "could not read \"%#s\": %s", filename, strerror(errno));
681 return JIM_ERR;
683 return JIM_OK;
685 #else
686 #define file_lstat file_stat
687 #endif
689 static int file_cmd_atime(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
691 jim_stat_t sb;
693 if (file_stat(interp, argv[0], &sb) != JIM_OK) {
694 return JIM_ERR;
696 Jim_SetResultInt(interp, sb.st_atime);
697 return JIM_OK;
701 * Set file atime/mtime to the given time in microseconds since the epoch.
703 static int JimSetFileTimes(Jim_Interp *interp, const char *filename, jim_wide us)
705 #ifdef HAVE_UTIMES
706 struct timeval times[2];
708 times[1].tv_sec = times[0].tv_sec = us / 1000000;
709 times[1].tv_usec = times[0].tv_usec = us % 1000000;
711 if (utimes(filename, times) != 0) {
712 Jim_SetResultFormatted(interp, "can't set time on \"%s\": %s", filename, strerror(errno));
713 return JIM_ERR;
715 return JIM_OK;
716 #else
717 Jim_SetResultString(interp, "Not implemented", -1);
718 return JIM_ERR;
719 #endif
722 static int file_cmd_mtime(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
724 jim_stat_t sb;
726 if (argc == 2) {
727 jim_wide secs;
728 if (Jim_GetWide(interp, argv[1], &secs) != JIM_OK) {
729 return JIM_ERR;
731 return JimSetFileTimes(interp, Jim_String(argv[0]), secs * 1000000);
733 if (file_stat(interp, argv[0], &sb) != JIM_OK) {
734 return JIM_ERR;
736 Jim_SetResultInt(interp, sb.st_mtime);
737 return JIM_OK;
740 #ifdef STAT_MTIME_US
741 static int file_cmd_mtimeus(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
743 jim_stat_t sb;
745 if (argc == 2) {
746 jim_wide us;
747 if (Jim_GetWide(interp, argv[1], &us) != JIM_OK) {
748 return JIM_ERR;
750 return JimSetFileTimes(interp, Jim_String(argv[0]), us);
752 if (file_stat(interp, argv[0], &sb) != JIM_OK) {
753 return JIM_ERR;
755 Jim_SetResultInt(interp, STAT_MTIME_US(sb));
756 return JIM_OK;
758 #endif
760 static int file_cmd_copy(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
762 return Jim_EvalPrefix(interp, "file copy", argc, argv);
765 static int file_cmd_size(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
767 jim_stat_t sb;
769 if (file_stat(interp, argv[0], &sb) != JIM_OK) {
770 return JIM_ERR;
772 Jim_SetResultInt(interp, sb.st_size);
773 return JIM_OK;
776 static int file_cmd_isdirectory(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
778 jim_stat_t sb;
779 int ret = 0;
781 if (file_stat(interp, argv[0], &sb) == JIM_OK) {
782 ret = S_ISDIR(sb.st_mode);
784 Jim_SetResultInt(interp, ret);
785 return JIM_OK;
788 static int file_cmd_isfile(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
790 jim_stat_t sb;
791 int ret = 0;
793 if (file_stat(interp, argv[0], &sb) == JIM_OK) {
794 ret = S_ISREG(sb.st_mode);
796 Jim_SetResultInt(interp, ret);
797 return JIM_OK;
800 #ifdef HAVE_GETEUID
801 static int file_cmd_owned(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
803 jim_stat_t sb;
804 int ret = 0;
806 if (file_stat(interp, argv[0], &sb) == JIM_OK) {
807 ret = (geteuid() == sb.st_uid);
809 Jim_SetResultInt(interp, ret);
810 return JIM_OK;
812 #endif
814 #if defined(HAVE_READLINK)
815 static int file_cmd_readlink(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
817 const char *path = Jim_String(argv[0]);
818 char *linkValue = Jim_Alloc(MAXPATHLEN + 1);
820 int linkLength = readlink(path, linkValue, MAXPATHLEN);
822 if (linkLength == -1) {
823 Jim_Free(linkValue);
824 Jim_SetResultFormatted(interp, "could not read link \"%#s\": %s", argv[0], strerror(errno));
825 return JIM_ERR;
827 linkValue[linkLength] = 0;
828 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, linkValue, linkLength));
829 return JIM_OK;
831 #endif
833 static int file_cmd_type(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
835 jim_stat_t sb;
837 if (file_lstat(interp, argv[0], &sb) != JIM_OK) {
838 return JIM_ERR;
840 Jim_SetResultString(interp, JimGetFileType((int)sb.st_mode), -1);
841 return JIM_OK;
844 #ifdef HAVE_LSTAT
845 static int file_cmd_lstat(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
847 jim_stat_t sb;
849 if (file_lstat(interp, argv[0], &sb) != JIM_OK) {
850 return JIM_ERR;
852 return StoreStatData(interp, argc == 2 ? argv[1] : NULL, &sb);
854 #else
855 #define file_cmd_lstat file_cmd_stat
856 #endif
858 static int file_cmd_stat(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
860 jim_stat_t sb;
862 if (file_stat(interp, argv[0], &sb) != JIM_OK) {
863 return JIM_ERR;
865 return StoreStatData(interp, argc == 2 ? argv[1] : NULL, &sb);
868 static const jim_subcmd_type file_command_table[] = {
869 { "atime",
870 "name",
871 file_cmd_atime,
874 /* Description: Last access time */
876 { "mtime",
877 "name ?time?",
878 file_cmd_mtime,
881 /* Description: Get or set last modification time */
883 #ifdef STAT_MTIME_US
884 { "mtimeus",
885 "name ?time?",
886 file_cmd_mtimeus,
889 /* Description: Get or set last modification time in microseconds */
891 #endif
892 { "copy",
893 "?-force? source dest",
894 file_cmd_copy,
897 /* Description: Copy source file to destination file */
899 { "dirname",
900 "name",
901 file_cmd_dirname,
904 /* Description: Directory part of the name */
906 { "rootname",
907 "name",
908 file_cmd_rootname,
911 /* Description: Name without any extension */
913 { "extension",
914 "name",
915 file_cmd_extension,
918 /* Description: Last extension including the dot */
920 { "tail",
921 "name",
922 file_cmd_tail,
925 /* Description: Last component of the name */
927 { "split",
928 "name",
929 file_cmd_split,
932 /* Description: Split path into components as a list */
934 { "normalize",
935 "name",
936 file_cmd_normalize,
939 /* Description: Normalized path of name */
941 { "join",
942 "name ?name ...?",
943 file_cmd_join,
946 /* Description: Join multiple path components */
948 { "readable",
949 "name",
950 file_cmd_readable,
953 /* Description: Is file readable */
955 { "writable",
956 "name",
957 file_cmd_writable,
960 /* Description: Is file writable */
962 { "executable",
963 "name",
964 file_cmd_executable,
967 /* Description: Is file executable */
969 { "exists",
970 "name",
971 file_cmd_exists,
974 /* Description: Does file exist */
976 { "delete",
977 "?-force|--? name ...",
978 file_cmd_delete,
981 /* Description: Deletes the files or directories (must be empty unless -force) */
983 { "mkdir",
984 "dir ...",
985 file_cmd_mkdir,
988 /* Description: Creates the directories */
990 { "tempfile",
991 "?template?",
992 file_cmd_tempfile,
995 /* Description: Creates a temporary filename */
997 { "rename",
998 "?-force? source dest",
999 file_cmd_rename,
1002 /* Description: Renames a file */
1004 #if defined(HAVE_LINK) && defined(HAVE_SYMLINK)
1005 { "link",
1006 "?-symbolic|-hard? newname target",
1007 file_cmd_link,
1010 /* Description: Creates a hard or soft link */
1012 #endif
1013 #if defined(HAVE_READLINK)
1014 { "readlink",
1015 "name",
1016 file_cmd_readlink,
1019 /* Description: Value of the symbolic link */
1021 #endif
1022 { "size",
1023 "name",
1024 file_cmd_size,
1027 /* Description: Size of file */
1029 { "stat",
1030 "name ?var?",
1031 file_cmd_stat,
1034 /* Description: Returns results of stat, and may store in var array */
1036 { "lstat",
1037 "name ?var?",
1038 file_cmd_lstat,
1041 /* Description: Returns results of lstat, and may store in var array */
1043 { "type",
1044 "name",
1045 file_cmd_type,
1048 /* Description: Returns type of the file */
1050 #ifdef HAVE_GETEUID
1051 { "owned",
1052 "name",
1053 file_cmd_owned,
1056 /* Description: Returns 1 if owned by the current owner */
1058 #endif
1059 { "isdirectory",
1060 "name",
1061 file_cmd_isdirectory,
1064 /* Description: Returns 1 if name is a directory */
1066 { "isfile",
1067 "name",
1068 file_cmd_isfile,
1071 /* Description: Returns 1 if name is a file */
1074 NULL
1078 static int Jim_CdCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1080 const char *path;
1082 if (argc != 2) {
1083 Jim_WrongNumArgs(interp, 1, argv, "dirname");
1084 return JIM_ERR;
1087 path = Jim_String(argv[1]);
1089 if (chdir(path) != 0) {
1090 Jim_SetResultFormatted(interp, "couldn't change working directory to \"%s\": %s", path,
1091 strerror(errno));
1092 return JIM_ERR;
1094 return JIM_OK;
1097 static int Jim_PwdCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1099 char *cwd = Jim_Alloc(MAXPATHLEN);
1101 if (getcwd(cwd, MAXPATHLEN) == NULL) {
1102 Jim_SetResultString(interp, "Failed to get pwd", -1);
1103 Jim_Free(cwd);
1104 return JIM_ERR;
1106 else if (ISWINDOWS) {
1107 /* Try to keep backslashes out of paths */
1108 char *p = cwd;
1109 while ((p = strchr(p, '\\')) != NULL) {
1110 *p++ = '/';
1114 Jim_SetResultString(interp, cwd, -1);
1116 Jim_Free(cwd);
1117 return JIM_OK;
1120 int Jim_fileInit(Jim_Interp *interp)
1122 if (Jim_PackageProvide(interp, "file", "1.0", JIM_ERRMSG))
1123 return JIM_ERR;
1125 Jim_CreateCommand(interp, "file", Jim_SubCmdProc, (void *)file_command_table, NULL);
1126 Jim_CreateCommand(interp, "pwd", Jim_PwdCmd, NULL, NULL);
1127 Jim_CreateCommand(interp, "cd", Jim_CdCmd, NULL, NULL);
1128 return JIM_OK;