aio: Add dgram unix socket support
[jimtcl.git] / jim-file.c
blobdca906d349dbce04057fcfe12d75ae3a41135c1c
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;
218 static int file_cmd_dirname(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
220 const char *path = Jim_String(argv[0]);
221 const char *p = strrchr(path, '/');
223 if (!p && path[0] == '.' && path[1] == '.' && path[2] == '\0') {
224 Jim_SetResultString(interp, "..", -1);
225 } else if (!p) {
226 Jim_SetResultString(interp, ".", -1);
228 else if (p == path) {
229 Jim_SetResultString(interp, "/", -1);
231 else if (ISWINDOWS && p[-1] == ':') {
232 /* z:/dir => z:/ */
233 Jim_SetResultString(interp, path, p - path + 1);
235 else {
236 Jim_SetResultString(interp, path, p - path);
238 return JIM_OK;
241 static int file_cmd_rootname(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
243 const char *path = Jim_String(argv[0]);
244 const char *lastSlash = strrchr(path, '/');
245 const char *p = strrchr(path, '.');
247 if (p == NULL || (lastSlash != NULL && lastSlash > p)) {
248 Jim_SetResult(interp, argv[0]);
250 else {
251 Jim_SetResultString(interp, path, p - path);
253 return JIM_OK;
256 static int file_cmd_extension(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
258 const char *path = Jim_String(argv[0]);
259 const char *lastSlash = strrchr(path, '/');
260 const char *p = strrchr(path, '.');
262 if (p == NULL || (lastSlash != NULL && lastSlash >= p)) {
263 p = "";
265 Jim_SetResultString(interp, p, -1);
266 return JIM_OK;
269 static int file_cmd_tail(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
271 const char *path = Jim_String(argv[0]);
272 const char *lastSlash = strrchr(path, '/');
274 if (lastSlash) {
275 Jim_SetResultString(interp, lastSlash + 1, -1);
277 else {
278 Jim_SetResult(interp, argv[0]);
280 return JIM_OK;
283 static int file_cmd_normalize(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
285 #ifdef HAVE_REALPATH
286 const char *path = Jim_String(argv[0]);
287 char *newname = Jim_Alloc(MAXPATHLEN + 1);
289 if (realpath(path, newname)) {
290 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, newname, -1));
291 return JIM_OK;
293 else {
294 Jim_Free(newname);
295 Jim_SetResultFormatted(interp, "can't normalize \"%#s\": %s", argv[0], strerror(errno));
296 return JIM_ERR;
298 #else
299 Jim_SetResultString(interp, "Not implemented", -1);
300 return JIM_ERR;
301 #endif
304 static int file_cmd_join(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
306 int i;
307 char *newname = Jim_Alloc(MAXPATHLEN + 1);
308 char *last = newname;
310 *newname = 0;
312 /* Simple implementation for now */
313 for (i = 0; i < argc; i++) {
314 int len;
315 const char *part = Jim_GetString(argv[i], &len);
317 if (*part == '/') {
318 /* Absolute component, so go back to the start */
319 last = newname;
321 else if (ISWINDOWS && strchr(part, ':')) {
322 /* Absolute component on mingw, so go back to the start */
323 last = newname;
325 else if (part[0] == '.') {
326 if (part[1] == '/') {
327 part += 2;
328 len -= 2;
330 else if (part[1] == 0 && last != newname) {
331 /* Adding '.' to an existing path does nothing */
332 continue;
336 /* Add a slash if needed */
337 if (last != newname && last[-1] != '/') {
338 *last++ = '/';
341 if (len) {
342 if (last + len - newname >= MAXPATHLEN) {
343 Jim_Free(newname);
344 Jim_SetResultString(interp, "Path too long", -1);
345 return JIM_ERR;
347 memcpy(last, part, len);
348 last += len;
351 /* Remove a slash if needed */
352 if (last > newname + 1 && last[-1] == '/') {
353 /* but on on Windows, leave the trailing slash on "c:/ " */
354 if (!ISWINDOWS || !(last > newname + 2 && last[-2] == ':')) {
355 *--last = 0;
360 *last = 0;
362 /* Probably need to handle some special cases ... */
364 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, newname, last - newname));
366 return JIM_OK;
369 static int file_access(Jim_Interp *interp, Jim_Obj *filename, int mode)
371 Jim_SetResultBool(interp, access(Jim_String(filename), mode) != -1);
373 return JIM_OK;
376 static int file_cmd_readable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
378 return file_access(interp, argv[0], R_OK);
381 static int file_cmd_writable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
383 return file_access(interp, argv[0], W_OK);
386 static int file_cmd_executable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
388 #ifdef X_OK
389 return file_access(interp, argv[0], X_OK);
390 #else
391 /* If no X_OK, just assume true. */
392 Jim_SetResultBool(interp, 1);
393 return JIM_OK;
394 #endif
397 static int file_cmd_exists(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
399 return file_access(interp, argv[0], F_OK);
402 static int file_cmd_delete(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
404 int force = Jim_CompareStringImmediate(interp, argv[0], "-force");
406 if (force || Jim_CompareStringImmediate(interp, argv[0], "--")) {
407 argc++;
408 argv--;
411 while (argc--) {
412 const char *path = Jim_String(argv[0]);
414 if (unlink(path) == -1 && errno != ENOENT) {
415 if (rmdir(path) == -1) {
416 /* Maybe try using the script helper */
417 if (!force || Jim_EvalPrefix(interp, "file delete force", 1, argv) != JIM_OK) {
418 Jim_SetResultFormatted(interp, "couldn't delete file \"%s\": %s", path,
419 strerror(errno));
420 return JIM_ERR;
424 argv++;
426 return JIM_OK;
429 #ifdef HAVE_MKDIR_ONE_ARG
430 #define MKDIR_DEFAULT(PATHNAME) mkdir(PATHNAME)
431 #else
432 #define MKDIR_DEFAULT(PATHNAME) mkdir(PATHNAME, 0755)
433 #endif
436 * Create directory, creating all intermediate paths if necessary.
438 * Returns 0 if OK or -1 on failure (and sets errno)
440 * Note: The path may be modified.
442 static int mkdir_all(char *path)
444 int ok = 1;
446 /* First time just try to make the dir */
447 goto first;
449 while (ok--) {
450 /* Must have failed the first time, so recursively make the parent and try again */
452 char *slash = strrchr(path, '/');
454 if (slash && slash != path) {
455 *slash = 0;
456 if (mkdir_all(path) != 0) {
457 return -1;
459 *slash = '/';
462 first:
463 if (MKDIR_DEFAULT(path) == 0) {
464 return 0;
466 if (errno == ENOENT) {
467 /* Create the parent and try again */
468 continue;
470 /* Maybe it already exists as a directory */
471 if (errno == EEXIST) {
472 struct stat sb;
474 if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) {
475 return 0;
477 /* Restore errno */
478 errno = EEXIST;
480 /* Failed */
481 break;
483 return -1;
486 static int file_cmd_mkdir(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
488 while (argc--) {
489 char *path = Jim_StrDup(Jim_String(argv[0]));
490 int rc = mkdir_all(path);
492 Jim_Free(path);
493 if (rc != 0) {
494 Jim_SetResultFormatted(interp, "can't create directory \"%#s\": %s", argv[0],
495 strerror(errno));
496 return JIM_ERR;
498 argv++;
500 return JIM_OK;
503 static int file_cmd_tempfile(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
505 int fd = Jim_MakeTempFile(interp, (argc >= 1) ? Jim_String(argv[0]) : NULL, 0);
507 if (fd < 0) {
508 return JIM_ERR;
510 close(fd);
512 return JIM_OK;
515 static int file_cmd_rename(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
517 const char *source;
518 const char *dest;
519 int force = 0;
521 if (argc == 3) {
522 if (!Jim_CompareStringImmediate(interp, argv[0], "-force")) {
523 return -1;
525 force++;
526 argv++;
527 argc--;
530 source = Jim_String(argv[0]);
531 dest = Jim_String(argv[1]);
533 if (!force && access(dest, F_OK) == 0) {
534 Jim_SetResultFormatted(interp, "error renaming \"%#s\" to \"%#s\": target exists", argv[0],
535 argv[1]);
536 return JIM_ERR;
539 if (rename(source, dest) != 0) {
540 Jim_SetResultFormatted(interp, "error renaming \"%#s\" to \"%#s\": %s", argv[0], argv[1],
541 strerror(errno));
542 return JIM_ERR;
545 return JIM_OK;
548 #if defined(HAVE_LINK) && defined(HAVE_SYMLINK)
549 static int file_cmd_link(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
551 int ret;
552 const char *source;
553 const char *dest;
554 static const char * const options[] = { "-hard", "-symbolic", NULL };
555 enum { OPT_HARD, OPT_SYMBOLIC, };
556 int option = OPT_HARD;
558 if (argc == 3) {
559 if (Jim_GetEnum(interp, argv[0], options, &option, NULL, JIM_ENUM_ABBREV | JIM_ERRMSG) != JIM_OK) {
560 return JIM_ERR;
562 argv++;
563 argc--;
566 dest = Jim_String(argv[0]);
567 source = Jim_String(argv[1]);
569 if (option == OPT_HARD) {
570 ret = link(source, dest);
572 else {
573 ret = symlink(source, dest);
576 if (ret != 0) {
577 Jim_SetResultFormatted(interp, "error linking \"%#s\" to \"%#s\": %s", argv[0], argv[1],
578 strerror(errno));
579 return JIM_ERR;
582 return JIM_OK;
584 #endif
586 static int file_stat(Jim_Interp *interp, Jim_Obj *filename, struct stat *sb)
588 const char *path = Jim_String(filename);
590 if (stat(path, sb) == -1) {
591 Jim_SetResultFormatted(interp, "could not read \"%#s\": %s", filename, strerror(errno));
592 return JIM_ERR;
594 return JIM_OK;
597 #ifdef HAVE_LSTAT
598 static int file_lstat(Jim_Interp *interp, Jim_Obj *filename, struct stat *sb)
600 const char *path = Jim_String(filename);
602 if (lstat(path, sb) == -1) {
603 Jim_SetResultFormatted(interp, "could not read \"%#s\": %s", filename, strerror(errno));
604 return JIM_ERR;
606 return JIM_OK;
608 #else
609 #define file_lstat file_stat
610 #endif
612 static int file_cmd_atime(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
614 struct stat sb;
616 if (file_stat(interp, argv[0], &sb) != JIM_OK) {
617 return JIM_ERR;
619 Jim_SetResultInt(interp, sb.st_atime);
620 return JIM_OK;
624 * Set file atime/mtime to the given time in microseconds since the epoch.
626 static int JimSetFileTimes(Jim_Interp *interp, const char *filename, jim_wide us)
628 #ifdef HAVE_UTIMES
629 struct timeval times[2];
631 times[1].tv_sec = times[0].tv_sec = us / 1000000;
632 times[1].tv_usec = times[0].tv_usec = us % 1000000;
634 if (utimes(filename, times) != 0) {
635 Jim_SetResultFormatted(interp, "can't set time on \"%s\": %s", filename, strerror(errno));
636 return JIM_ERR;
638 return JIM_OK;
639 #else
640 Jim_SetResultString(interp, "Not implemented", -1);
641 return JIM_ERR;
642 #endif
645 static int file_cmd_mtime(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
647 struct stat sb;
649 if (argc == 2) {
650 jim_wide secs;
651 if (Jim_GetWide(interp, argv[1], &secs) != JIM_OK) {
652 return JIM_ERR;
654 return JimSetFileTimes(interp, Jim_String(argv[0]), secs * 1000000);
656 if (file_stat(interp, argv[0], &sb) != JIM_OK) {
657 return JIM_ERR;
659 Jim_SetResultInt(interp, sb.st_mtime);
660 return JIM_OK;
663 #ifdef STAT_MTIME_US
664 static int file_cmd_mtimeus(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
666 struct stat sb;
668 if (argc == 2) {
669 jim_wide us;
670 if (Jim_GetWide(interp, argv[1], &us) != JIM_OK) {
671 return JIM_ERR;
673 return JimSetFileTimes(interp, Jim_String(argv[0]), us);
675 if (file_stat(interp, argv[0], &sb) != JIM_OK) {
676 return JIM_ERR;
678 Jim_SetResultInt(interp, STAT_MTIME_US(sb));
679 return JIM_OK;
681 #endif
683 static int file_cmd_copy(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
685 return Jim_EvalPrefix(interp, "file copy", argc, argv);
688 static int file_cmd_size(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
690 struct stat sb;
692 if (file_stat(interp, argv[0], &sb) != JIM_OK) {
693 return JIM_ERR;
695 Jim_SetResultInt(interp, sb.st_size);
696 return JIM_OK;
699 static int file_cmd_isdirectory(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
701 struct stat sb;
702 int ret = 0;
704 if (file_stat(interp, argv[0], &sb) == JIM_OK) {
705 ret = S_ISDIR(sb.st_mode);
707 Jim_SetResultInt(interp, ret);
708 return JIM_OK;
711 static int file_cmd_isfile(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
713 struct stat sb;
714 int ret = 0;
716 if (file_stat(interp, argv[0], &sb) == JIM_OK) {
717 ret = S_ISREG(sb.st_mode);
719 Jim_SetResultInt(interp, ret);
720 return JIM_OK;
723 #ifdef HAVE_GETEUID
724 static int file_cmd_owned(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
726 struct stat sb;
727 int ret = 0;
729 if (file_stat(interp, argv[0], &sb) == JIM_OK) {
730 ret = (geteuid() == sb.st_uid);
732 Jim_SetResultInt(interp, ret);
733 return JIM_OK;
735 #endif
737 #if defined(HAVE_READLINK)
738 static int file_cmd_readlink(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
740 const char *path = Jim_String(argv[0]);
741 char *linkValue = Jim_Alloc(MAXPATHLEN + 1);
743 int linkLength = readlink(path, linkValue, MAXPATHLEN);
745 if (linkLength == -1) {
746 Jim_Free(linkValue);
747 Jim_SetResultFormatted(interp, "couldn't readlink \"%#s\": %s", argv[0], strerror(errno));
748 return JIM_ERR;
750 linkValue[linkLength] = 0;
751 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, linkValue, linkLength));
752 return JIM_OK;
754 #endif
756 static int file_cmd_type(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
758 struct stat sb;
760 if (file_lstat(interp, argv[0], &sb) != JIM_OK) {
761 return JIM_ERR;
763 Jim_SetResultString(interp, JimGetFileType((int)sb.st_mode), -1);
764 return JIM_OK;
767 #ifdef HAVE_LSTAT
768 static int file_cmd_lstat(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
770 struct stat sb;
772 if (file_lstat(interp, argv[0], &sb) != JIM_OK) {
773 return JIM_ERR;
775 return StoreStatData(interp, argc == 2 ? argv[1] : NULL, &sb);
777 #else
778 #define file_cmd_lstat file_cmd_stat
779 #endif
781 static int file_cmd_stat(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
783 struct stat sb;
785 if (file_stat(interp, argv[0], &sb) != JIM_OK) {
786 return JIM_ERR;
788 return StoreStatData(interp, argc == 2 ? argv[1] : NULL, &sb);
791 static const jim_subcmd_type file_command_table[] = {
792 { "atime",
793 "name",
794 file_cmd_atime,
797 /* Description: Last access time */
799 { "mtime",
800 "name ?time?",
801 file_cmd_mtime,
804 /* Description: Get or set last modification time */
806 #ifdef STAT_MTIME_US
807 { "mtimeus",
808 "name ?time?",
809 file_cmd_mtimeus,
812 /* Description: Get or set last modification time in microseconds */
814 #endif
815 { "copy",
816 "?-force? source dest",
817 file_cmd_copy,
820 /* Description: Copy source file to destination file */
822 { "dirname",
823 "name",
824 file_cmd_dirname,
827 /* Description: Directory part of the name */
829 { "rootname",
830 "name",
831 file_cmd_rootname,
834 /* Description: Name without any extension */
836 { "extension",
837 "name",
838 file_cmd_extension,
841 /* Description: Last extension including the dot */
843 { "tail",
844 "name",
845 file_cmd_tail,
848 /* Description: Last component of the name */
850 { "normalize",
851 "name",
852 file_cmd_normalize,
855 /* Description: Normalized path of name */
857 { "join",
858 "name ?name ...?",
859 file_cmd_join,
862 /* Description: Join multiple path components */
864 { "readable",
865 "name",
866 file_cmd_readable,
869 /* Description: Is file readable */
871 { "writable",
872 "name",
873 file_cmd_writable,
876 /* Description: Is file writable */
878 { "executable",
879 "name",
880 file_cmd_executable,
883 /* Description: Is file executable */
885 { "exists",
886 "name",
887 file_cmd_exists,
890 /* Description: Does file exist */
892 { "delete",
893 "?-force|--? name ...",
894 file_cmd_delete,
897 /* Description: Deletes the files or directories (must be empty unless -force) */
899 { "mkdir",
900 "dir ...",
901 file_cmd_mkdir,
904 /* Description: Creates the directories */
906 { "tempfile",
907 "?template?",
908 file_cmd_tempfile,
911 /* Description: Creates a temporary filename */
913 { "rename",
914 "?-force? source dest",
915 file_cmd_rename,
918 /* Description: Renames a file */
920 #if defined(HAVE_LINK) && defined(HAVE_SYMLINK)
921 { "link",
922 "?-symbolic|-hard? newname target",
923 file_cmd_link,
926 /* Description: Creates a hard or soft link */
928 #endif
929 #if defined(HAVE_READLINK)
930 { "readlink",
931 "name",
932 file_cmd_readlink,
935 /* Description: Value of the symbolic link */
937 #endif
938 { "size",
939 "name",
940 file_cmd_size,
943 /* Description: Size of file */
945 { "stat",
946 "name ?var?",
947 file_cmd_stat,
950 /* Description: Returns results of stat, and may store in var array */
952 { "lstat",
953 "name ?var?",
954 file_cmd_lstat,
957 /* Description: Returns results of lstat, and may store in var array */
959 { "type",
960 "name",
961 file_cmd_type,
964 /* Description: Returns type of the file */
966 #ifdef HAVE_GETEUID
967 { "owned",
968 "name",
969 file_cmd_owned,
972 /* Description: Returns 1 if owned by the current owner */
974 #endif
975 { "isdirectory",
976 "name",
977 file_cmd_isdirectory,
980 /* Description: Returns 1 if name is a directory */
982 { "isfile",
983 "name",
984 file_cmd_isfile,
987 /* Description: Returns 1 if name is a file */
990 NULL
994 static int Jim_CdCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
996 const char *path;
998 if (argc != 2) {
999 Jim_WrongNumArgs(interp, 1, argv, "dirname");
1000 return JIM_ERR;
1003 path = Jim_String(argv[1]);
1005 if (chdir(path) != 0) {
1006 Jim_SetResultFormatted(interp, "couldn't change working directory to \"%s\": %s", path,
1007 strerror(errno));
1008 return JIM_ERR;
1010 return JIM_OK;
1013 static int Jim_PwdCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1015 char *cwd = Jim_Alloc(MAXPATHLEN);
1017 if (getcwd(cwd, MAXPATHLEN) == NULL) {
1018 Jim_SetResultString(interp, "Failed to get pwd", -1);
1019 Jim_Free(cwd);
1020 return JIM_ERR;
1022 else if (ISWINDOWS) {
1023 /* Try to keep backslashes out of paths */
1024 char *p = cwd;
1025 while ((p = strchr(p, '\\')) != NULL) {
1026 *p++ = '/';
1030 Jim_SetResultString(interp, cwd, -1);
1032 Jim_Free(cwd);
1033 return JIM_OK;
1036 int Jim_fileInit(Jim_Interp *interp)
1038 if (Jim_PackageProvide(interp, "file", "1.0", JIM_ERRMSG))
1039 return JIM_ERR;
1041 Jim_CreateCommand(interp, "file", Jim_SubCmdProc, (void *)file_command_table, NULL);
1042 Jim_CreateCommand(interp, "pwd", Jim_PwdCmd, NULL, NULL);
1043 Jim_CreateCommand(interp, "cd", Jim_CdCmd, NULL, NULL);
1044 return JIM_OK;