file: Better support for trailing slashes in pathnames
[jimtcl.git] / jim-file.c
blobf47e23638a982417d68fc34a0776719ea8d5b3b8
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(__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 struct stat *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 && path[0] == '.' && path[1] == '.' && path[2] == '\0') {
258 Jim_SetResultString(interp, "..", -1);
259 } else if (!p) {
260 Jim_SetResultString(interp, ".", -1);
262 else if (p == path) {
263 Jim_SetResultString(interp, "/", -1);
265 else if (ISWINDOWS && p[-1] == ':') {
266 /* z:/dir => z:/ */
267 Jim_SetResultString(interp, path, p - path + 1);
269 else {
270 /* Strip any trailing slashes from the result */
271 int len = JimPathLenNoTrailingSlashes(path, p - path);
272 Jim_SetResultString(interp, path, len);
274 Jim_DecrRefCount(interp, objPtr);
275 return JIM_OK;
278 static int file_cmd_rootname(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
280 Jim_Obj *objPtr = JimStripTrailingSlashes(interp, argv[0]);
281 const char *path = Jim_String(objPtr);
282 const char *lastSlash = strrchr(path, '/');
283 const char *p = strrchr(path, '.');
285 if (p == NULL || (lastSlash != NULL && lastSlash > p)) {
286 Jim_SetResult(interp, objPtr);
288 else {
289 Jim_SetResultString(interp, path, p - path);
291 Jim_DecrRefCount(interp, objPtr);
292 return JIM_OK;
295 static int file_cmd_extension(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
297 Jim_Obj *objPtr = JimStripTrailingSlashes(interp, argv[0]);
298 const char *path = Jim_String(objPtr);
299 const char *lastSlash = strrchr(path, '/');
300 const char *p = strrchr(path, '.');
302 if (p == NULL || (lastSlash != NULL && lastSlash >= p)) {
303 p = "";
305 Jim_SetResultString(interp, p, -1);
306 Jim_DecrRefCount(interp, objPtr);
307 return JIM_OK;
310 static int file_cmd_tail(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
312 Jim_Obj *objPtr = JimStripTrailingSlashes(interp, argv[0]);
313 const char *path = Jim_String(objPtr);
314 const char *lastSlash = strrchr(path, '/');
316 if (lastSlash) {
317 Jim_SetResultString(interp, lastSlash + 1, -1);
319 else {
320 Jim_SetResult(interp, objPtr);
322 Jim_DecrRefCount(interp, objPtr);
323 return JIM_OK;
326 static int file_cmd_normalize(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
328 #ifdef HAVE_REALPATH
329 const char *path = Jim_String(argv[0]);
330 char *newname = Jim_Alloc(MAXPATHLEN + 1);
332 if (realpath(path, newname)) {
333 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, newname, -1));
334 return JIM_OK;
336 else {
337 Jim_Free(newname);
338 Jim_SetResultFormatted(interp, "can't normalize \"%#s\": %s", argv[0], strerror(errno));
339 return JIM_ERR;
341 #else
342 Jim_SetResultString(interp, "Not implemented", -1);
343 return JIM_ERR;
344 #endif
347 static int file_cmd_join(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
349 int i;
350 char *newname = Jim_Alloc(MAXPATHLEN + 1);
351 char *last = newname;
353 *newname = 0;
355 /* Simple implementation for now */
356 for (i = 0; i < argc; i++) {
357 int len;
358 const char *part = Jim_GetString(argv[i], &len);
360 if (*part == '/') {
361 /* Absolute component, so go back to the start */
362 last = newname;
364 else if (ISWINDOWS && strchr(part, ':')) {
365 /* Absolute component on mingw, so go back to the start */
366 last = newname;
368 else if (part[0] == '.') {
369 if (part[1] == '/') {
370 part += 2;
371 len -= 2;
373 else if (part[1] == 0 && last != newname) {
374 /* Adding '.' to an existing path does nothing */
375 continue;
379 /* Add a slash if needed */
380 if (last != newname && last[-1] != '/') {
381 *last++ = '/';
384 if (len) {
385 if (last + len - newname >= MAXPATHLEN) {
386 Jim_Free(newname);
387 Jim_SetResultString(interp, "Path too long", -1);
388 return JIM_ERR;
390 memcpy(last, part, len);
391 last += len;
394 /* Remove a slash if needed */
395 if (last > newname + 1 && last[-1] == '/') {
396 /* but on on Windows, leave the trailing slash on "c:/ " */
397 if (!ISWINDOWS || !(last > newname + 2 && last[-2] == ':')) {
398 *--last = 0;
403 *last = 0;
405 /* Probably need to handle some special cases ... */
407 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, newname, last - newname));
409 return JIM_OK;
412 static int file_access(Jim_Interp *interp, Jim_Obj *filename, int mode)
414 Jim_SetResultBool(interp, access(Jim_String(filename), mode) != -1);
416 return JIM_OK;
419 static int file_cmd_readable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
421 return file_access(interp, argv[0], R_OK);
424 static int file_cmd_writable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
426 return file_access(interp, argv[0], W_OK);
429 static int file_cmd_executable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
431 #ifdef X_OK
432 return file_access(interp, argv[0], X_OK);
433 #else
434 /* If no X_OK, just assume true. */
435 Jim_SetResultBool(interp, 1);
436 return JIM_OK;
437 #endif
440 static int file_cmd_exists(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
442 return file_access(interp, argv[0], F_OK);
445 static int file_cmd_delete(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
447 int force = Jim_CompareStringImmediate(interp, argv[0], "-force");
449 if (force || Jim_CompareStringImmediate(interp, argv[0], "--")) {
450 argc++;
451 argv--;
454 while (argc--) {
455 const char *path = Jim_String(argv[0]);
457 if (unlink(path) == -1 && errno != ENOENT) {
458 if (rmdir(path) == -1) {
459 /* Maybe try using the script helper */
460 if (!force || Jim_EvalPrefix(interp, "file delete force", 1, argv) != JIM_OK) {
461 Jim_SetResultFormatted(interp, "couldn't delete file \"%s\": %s", path,
462 strerror(errno));
463 return JIM_ERR;
467 argv++;
469 return JIM_OK;
472 #ifdef HAVE_MKDIR_ONE_ARG
473 #define MKDIR_DEFAULT(PATHNAME) mkdir(PATHNAME)
474 #else
475 #define MKDIR_DEFAULT(PATHNAME) mkdir(PATHNAME, 0755)
476 #endif
479 * Create directory, creating all intermediate paths if necessary.
481 * Returns 0 if OK or -1 on failure (and sets errno)
483 * Note: The path may be modified.
485 static int mkdir_all(char *path)
487 int ok = 1;
489 /* First time just try to make the dir */
490 goto first;
492 while (ok--) {
493 /* Must have failed the first time, so recursively make the parent and try again */
495 char *slash = strrchr(path, '/');
497 if (slash && slash != path) {
498 *slash = 0;
499 if (mkdir_all(path) != 0) {
500 return -1;
502 *slash = '/';
505 first:
506 if (MKDIR_DEFAULT(path) == 0) {
507 return 0;
509 if (errno == ENOENT) {
510 /* Create the parent and try again */
511 continue;
513 /* Maybe it already exists as a directory */
514 if (errno == EEXIST) {
515 struct stat sb;
517 if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) {
518 return 0;
520 /* Restore errno */
521 errno = EEXIST;
523 /* Failed */
524 break;
526 return -1;
529 static int file_cmd_mkdir(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
531 while (argc--) {
532 char *path = Jim_StrDup(Jim_String(argv[0]));
533 int rc = mkdir_all(path);
535 Jim_Free(path);
536 if (rc != 0) {
537 Jim_SetResultFormatted(interp, "can't create directory \"%#s\": %s", argv[0],
538 strerror(errno));
539 return JIM_ERR;
541 argv++;
543 return JIM_OK;
546 static int file_cmd_tempfile(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
548 int fd = Jim_MakeTempFile(interp, (argc >= 1) ? Jim_String(argv[0]) : NULL, 0);
550 if (fd < 0) {
551 return JIM_ERR;
553 close(fd);
555 return JIM_OK;
558 static int file_cmd_rename(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
560 const char *source;
561 const char *dest;
562 int force = 0;
564 if (argc == 3) {
565 if (!Jim_CompareStringImmediate(interp, argv[0], "-force")) {
566 return -1;
568 force++;
569 argv++;
570 argc--;
573 source = Jim_String(argv[0]);
574 dest = Jim_String(argv[1]);
576 if (!force && access(dest, F_OK) == 0) {
577 Jim_SetResultFormatted(interp, "error renaming \"%#s\" to \"%#s\": target exists", argv[0],
578 argv[1]);
579 return JIM_ERR;
582 if (rename(source, dest) != 0) {
583 Jim_SetResultFormatted(interp, "error renaming \"%#s\" to \"%#s\": %s", argv[0], argv[1],
584 strerror(errno));
585 return JIM_ERR;
588 return JIM_OK;
591 #if defined(HAVE_LINK) && defined(HAVE_SYMLINK)
592 static int file_cmd_link(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
594 int ret;
595 const char *source;
596 const char *dest;
597 static const char * const options[] = { "-hard", "-symbolic", NULL };
598 enum { OPT_HARD, OPT_SYMBOLIC, };
599 int option = OPT_HARD;
601 if (argc == 3) {
602 if (Jim_GetEnum(interp, argv[0], options, &option, NULL, JIM_ENUM_ABBREV | JIM_ERRMSG) != JIM_OK) {
603 return JIM_ERR;
605 argv++;
606 argc--;
609 dest = Jim_String(argv[0]);
610 source = Jim_String(argv[1]);
612 if (option == OPT_HARD) {
613 ret = link(source, dest);
615 else {
616 ret = symlink(source, dest);
619 if (ret != 0) {
620 Jim_SetResultFormatted(interp, "error linking \"%#s\" to \"%#s\": %s", argv[0], argv[1],
621 strerror(errno));
622 return JIM_ERR;
625 return JIM_OK;
627 #endif
629 static int file_stat(Jim_Interp *interp, Jim_Obj *filename, struct stat *sb)
631 const char *path = Jim_String(filename);
633 if (stat(path, sb) == -1) {
634 Jim_SetResultFormatted(interp, "could not read \"%#s\": %s", filename, strerror(errno));
635 return JIM_ERR;
637 return JIM_OK;
640 #ifdef HAVE_LSTAT
641 static int file_lstat(Jim_Interp *interp, Jim_Obj *filename, struct stat *sb)
643 const char *path = Jim_String(filename);
645 if (lstat(path, sb) == -1) {
646 Jim_SetResultFormatted(interp, "could not read \"%#s\": %s", filename, strerror(errno));
647 return JIM_ERR;
649 return JIM_OK;
651 #else
652 #define file_lstat file_stat
653 #endif
655 static int file_cmd_atime(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
657 struct stat sb;
659 if (file_stat(interp, argv[0], &sb) != JIM_OK) {
660 return JIM_ERR;
662 Jim_SetResultInt(interp, sb.st_atime);
663 return JIM_OK;
667 * Set file atime/mtime to the given time in microseconds since the epoch.
669 static int JimSetFileTimes(Jim_Interp *interp, const char *filename, jim_wide us)
671 #ifdef HAVE_UTIMES
672 struct timeval times[2];
674 times[1].tv_sec = times[0].tv_sec = us / 1000000;
675 times[1].tv_usec = times[0].tv_usec = us % 1000000;
677 if (utimes(filename, times) != 0) {
678 Jim_SetResultFormatted(interp, "can't set time on \"%s\": %s", filename, strerror(errno));
679 return JIM_ERR;
681 return JIM_OK;
682 #else
683 Jim_SetResultString(interp, "Not implemented", -1);
684 return JIM_ERR;
685 #endif
688 static int file_cmd_mtime(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
690 struct stat sb;
692 if (argc == 2) {
693 jim_wide secs;
694 if (Jim_GetWide(interp, argv[1], &secs) != JIM_OK) {
695 return JIM_ERR;
697 return JimSetFileTimes(interp, Jim_String(argv[0]), secs * 1000000);
699 if (file_stat(interp, argv[0], &sb) != JIM_OK) {
700 return JIM_ERR;
702 Jim_SetResultInt(interp, sb.st_mtime);
703 return JIM_OK;
706 #ifdef STAT_MTIME_US
707 static int file_cmd_mtimeus(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
709 struct stat sb;
711 if (argc == 2) {
712 jim_wide us;
713 if (Jim_GetWide(interp, argv[1], &us) != JIM_OK) {
714 return JIM_ERR;
716 return JimSetFileTimes(interp, Jim_String(argv[0]), us);
718 if (file_stat(interp, argv[0], &sb) != JIM_OK) {
719 return JIM_ERR;
721 Jim_SetResultInt(interp, STAT_MTIME_US(sb));
722 return JIM_OK;
724 #endif
726 static int file_cmd_copy(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
728 return Jim_EvalPrefix(interp, "file copy", argc, argv);
731 static int file_cmd_size(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
733 struct stat sb;
735 if (file_stat(interp, argv[0], &sb) != JIM_OK) {
736 return JIM_ERR;
738 Jim_SetResultInt(interp, sb.st_size);
739 return JIM_OK;
742 static int file_cmd_isdirectory(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
744 struct stat sb;
745 int ret = 0;
747 if (file_stat(interp, argv[0], &sb) == JIM_OK) {
748 ret = S_ISDIR(sb.st_mode);
750 Jim_SetResultInt(interp, ret);
751 return JIM_OK;
754 static int file_cmd_isfile(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
756 struct stat sb;
757 int ret = 0;
759 if (file_stat(interp, argv[0], &sb) == JIM_OK) {
760 ret = S_ISREG(sb.st_mode);
762 Jim_SetResultInt(interp, ret);
763 return JIM_OK;
766 #ifdef HAVE_GETEUID
767 static int file_cmd_owned(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
769 struct stat sb;
770 int ret = 0;
772 if (file_stat(interp, argv[0], &sb) == JIM_OK) {
773 ret = (geteuid() == sb.st_uid);
775 Jim_SetResultInt(interp, ret);
776 return JIM_OK;
778 #endif
780 #if defined(HAVE_READLINK)
781 static int file_cmd_readlink(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
783 const char *path = Jim_String(argv[0]);
784 char *linkValue = Jim_Alloc(MAXPATHLEN + 1);
786 int linkLength = readlink(path, linkValue, MAXPATHLEN);
788 if (linkLength == -1) {
789 Jim_Free(linkValue);
790 Jim_SetResultFormatted(interp, "couldn't readlink \"%#s\": %s", argv[0], strerror(errno));
791 return JIM_ERR;
793 linkValue[linkLength] = 0;
794 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, linkValue, linkLength));
795 return JIM_OK;
797 #endif
799 static int file_cmd_type(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
801 struct stat sb;
803 if (file_lstat(interp, argv[0], &sb) != JIM_OK) {
804 return JIM_ERR;
806 Jim_SetResultString(interp, JimGetFileType((int)sb.st_mode), -1);
807 return JIM_OK;
810 #ifdef HAVE_LSTAT
811 static int file_cmd_lstat(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
813 struct stat sb;
815 if (file_lstat(interp, argv[0], &sb) != JIM_OK) {
816 return JIM_ERR;
818 return StoreStatData(interp, argc == 2 ? argv[1] : NULL, &sb);
820 #else
821 #define file_cmd_lstat file_cmd_stat
822 #endif
824 static int file_cmd_stat(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
826 struct stat sb;
828 if (file_stat(interp, argv[0], &sb) != JIM_OK) {
829 return JIM_ERR;
831 return StoreStatData(interp, argc == 2 ? argv[1] : NULL, &sb);
834 static const jim_subcmd_type file_command_table[] = {
835 { "atime",
836 "name",
837 file_cmd_atime,
840 /* Description: Last access time */
842 { "mtime",
843 "name ?time?",
844 file_cmd_mtime,
847 /* Description: Get or set last modification time */
849 #ifdef STAT_MTIME_US
850 { "mtimeus",
851 "name ?time?",
852 file_cmd_mtimeus,
855 /* Description: Get or set last modification time in microseconds */
857 #endif
858 { "copy",
859 "?-force? source dest",
860 file_cmd_copy,
863 /* Description: Copy source file to destination file */
865 { "dirname",
866 "name",
867 file_cmd_dirname,
870 /* Description: Directory part of the name */
872 { "rootname",
873 "name",
874 file_cmd_rootname,
877 /* Description: Name without any extension */
879 { "extension",
880 "name",
881 file_cmd_extension,
884 /* Description: Last extension including the dot */
886 { "tail",
887 "name",
888 file_cmd_tail,
891 /* Description: Last component of the name */
893 { "normalize",
894 "name",
895 file_cmd_normalize,
898 /* Description: Normalized path of name */
900 { "join",
901 "name ?name ...?",
902 file_cmd_join,
905 /* Description: Join multiple path components */
907 { "readable",
908 "name",
909 file_cmd_readable,
912 /* Description: Is file readable */
914 { "writable",
915 "name",
916 file_cmd_writable,
919 /* Description: Is file writable */
921 { "executable",
922 "name",
923 file_cmd_executable,
926 /* Description: Is file executable */
928 { "exists",
929 "name",
930 file_cmd_exists,
933 /* Description: Does file exist */
935 { "delete",
936 "?-force|--? name ...",
937 file_cmd_delete,
940 /* Description: Deletes the files or directories (must be empty unless -force) */
942 { "mkdir",
943 "dir ...",
944 file_cmd_mkdir,
947 /* Description: Creates the directories */
949 { "tempfile",
950 "?template?",
951 file_cmd_tempfile,
954 /* Description: Creates a temporary filename */
956 { "rename",
957 "?-force? source dest",
958 file_cmd_rename,
961 /* Description: Renames a file */
963 #if defined(HAVE_LINK) && defined(HAVE_SYMLINK)
964 { "link",
965 "?-symbolic|-hard? newname target",
966 file_cmd_link,
969 /* Description: Creates a hard or soft link */
971 #endif
972 #if defined(HAVE_READLINK)
973 { "readlink",
974 "name",
975 file_cmd_readlink,
978 /* Description: Value of the symbolic link */
980 #endif
981 { "size",
982 "name",
983 file_cmd_size,
986 /* Description: Size of file */
988 { "stat",
989 "name ?var?",
990 file_cmd_stat,
993 /* Description: Returns results of stat, and may store in var array */
995 { "lstat",
996 "name ?var?",
997 file_cmd_lstat,
1000 /* Description: Returns results of lstat, and may store in var array */
1002 { "type",
1003 "name",
1004 file_cmd_type,
1007 /* Description: Returns type of the file */
1009 #ifdef HAVE_GETEUID
1010 { "owned",
1011 "name",
1012 file_cmd_owned,
1015 /* Description: Returns 1 if owned by the current owner */
1017 #endif
1018 { "isdirectory",
1019 "name",
1020 file_cmd_isdirectory,
1023 /* Description: Returns 1 if name is a directory */
1025 { "isfile",
1026 "name",
1027 file_cmd_isfile,
1030 /* Description: Returns 1 if name is a file */
1033 NULL
1037 static int Jim_CdCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1039 const char *path;
1041 if (argc != 2) {
1042 Jim_WrongNumArgs(interp, 1, argv, "dirname");
1043 return JIM_ERR;
1046 path = Jim_String(argv[1]);
1048 if (chdir(path) != 0) {
1049 Jim_SetResultFormatted(interp, "couldn't change working directory to \"%s\": %s", path,
1050 strerror(errno));
1051 return JIM_ERR;
1053 return JIM_OK;
1056 static int Jim_PwdCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1058 char *cwd = Jim_Alloc(MAXPATHLEN);
1060 if (getcwd(cwd, MAXPATHLEN) == NULL) {
1061 Jim_SetResultString(interp, "Failed to get pwd", -1);
1062 Jim_Free(cwd);
1063 return JIM_ERR;
1065 else if (ISWINDOWS) {
1066 /* Try to keep backslashes out of paths */
1067 char *p = cwd;
1068 while ((p = strchr(p, '\\')) != NULL) {
1069 *p++ = '/';
1073 Jim_SetResultString(interp, cwd, -1);
1075 Jim_Free(cwd);
1076 return JIM_OK;
1079 int Jim_fileInit(Jim_Interp *interp)
1081 if (Jim_PackageProvide(interp, "file", "1.0", JIM_ERRMSG))
1082 return JIM_ERR;
1084 Jim_CreateCommand(interp, "file", Jim_SubCmdProc, (void *)file_command_table, NULL);
1085 Jim_CreateCommand(interp, "pwd", Jim_PwdCmd, NULL, NULL);
1086 Jim_CreateCommand(interp, "cd", Jim_CdCmd, NULL, NULL);
1087 return JIM_OK;