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
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.
54 #include "jimautoconf.h"
55 #include "jim-subcmd.h"
62 #elif defined(_MSC_VER)
67 #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
68 #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
72 # define MAXPATHLEN JIM_PATH_LEN
76 *----------------------------------------------------------------------
80 * Given a mode word, returns a string identifying the type of a
84 * A static text string giving the file type from mode.
89 *----------------------------------------------------------------------
92 static const char *JimGetFileType(int mode
)
97 else if (S_ISDIR(mode
)) {
101 else if (S_ISCHR(mode
)) {
102 return "characterSpecial";
106 else if (S_ISBLK(mode
)) {
107 return "blockSpecial";
111 else if (S_ISFIFO(mode
)) {
116 else if (S_ISLNK(mode
)) {
121 else if (S_ISSOCK(mode
)) {
129 *----------------------------------------------------------------------
133 * This is a utility procedure that breaks out the fields of a
134 * "stat" structure and stores them in textual form into the
135 * elements of an associative array.
138 * Returns a standard Tcl return value. If an error occurs then
139 * a message is left in interp->result.
142 * Elements of the associative array given by "varName" are modified.
144 *----------------------------------------------------------------------
147 static int set_array_int_value(Jim_Interp
*interp
, Jim_Obj
*container
, const char *key
,
150 Jim_Obj
*nameobj
= Jim_NewStringObj(interp
, key
, -1);
151 Jim_Obj
*valobj
= Jim_NewWideObj(interp
, value
);
153 if (Jim_SetDictKeysVector(interp
, container
, &nameobj
, 1, valobj
, JIM_ERRMSG
) != JIM_OK
) {
154 Jim_FreeObj(interp
, nameobj
);
155 Jim_FreeObj(interp
, valobj
);
161 static int set_array_string_value(Jim_Interp
*interp
, Jim_Obj
*container
, const char *key
,
164 Jim_Obj
*nameobj
= Jim_NewStringObj(interp
, key
, -1);
165 Jim_Obj
*valobj
= Jim_NewStringObj(interp
, value
, -1);
167 if (Jim_SetDictKeysVector(interp
, container
, &nameobj
, 1, valobj
, JIM_ERRMSG
) != JIM_OK
) {
168 Jim_FreeObj(interp
, nameobj
);
169 Jim_FreeObj(interp
, valobj
);
175 static int StoreStatData(Jim_Interp
*interp
, Jim_Obj
*varName
, const struct stat
*sb
)
177 if (set_array_int_value(interp
, varName
, "dev", sb
->st_dev
) != JIM_OK
) {
178 Jim_SetResultFormatted(interp
, "can't set \"%#s(dev)\": variable isn't array", varName
);
181 set_array_int_value(interp
, varName
, "ino", sb
->st_ino
);
182 set_array_int_value(interp
, varName
, "mode", sb
->st_mode
);
183 set_array_int_value(interp
, varName
, "nlink", sb
->st_nlink
);
184 set_array_int_value(interp
, varName
, "uid", sb
->st_uid
);
185 set_array_int_value(interp
, varName
, "gid", sb
->st_gid
);
186 set_array_int_value(interp
, varName
, "size", sb
->st_size
);
187 set_array_int_value(interp
, varName
, "atime", sb
->st_atime
);
188 set_array_int_value(interp
, varName
, "mtime", sb
->st_mtime
);
189 set_array_int_value(interp
, varName
, "ctime", sb
->st_ctime
);
190 set_array_string_value(interp
, varName
, "type", JimGetFileType((int)sb
->st_mode
));
192 /* And also return the value */
193 Jim_SetResult(interp
, Jim_GetVariable(interp
, varName
, 0));
198 static int file_cmd_dirname(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
200 const char *path
= Jim_String(argv
[0]);
201 const char *p
= strrchr(path
, '/');
204 Jim_SetResultString(interp
, ".", -1);
206 else if (p
== path
) {
207 Jim_SetResultString(interp
, "/", -1);
209 #if defined(__MINGW32__) || defined(_MSC_VER)
210 else if (p
[-1] == ':') {
212 Jim_SetResultString(interp
, path
, p
- path
+ 1);
216 Jim_SetResultString(interp
, path
, p
- path
);
221 static int file_cmd_rootname(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
223 const char *path
= Jim_String(argv
[0]);
224 const char *lastSlash
= strrchr(path
, '/');
225 const char *p
= strrchr(path
, '.');
227 if (p
== NULL
|| (lastSlash
!= NULL
&& lastSlash
> p
)) {
228 Jim_SetResult(interp
, argv
[0]);
231 Jim_SetResultString(interp
, path
, p
- path
);
236 static int file_cmd_extension(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
238 const char *path
= Jim_String(argv
[0]);
239 const char *lastSlash
= strrchr(path
, '/');
240 const char *p
= strrchr(path
, '.');
242 if (p
== NULL
|| (lastSlash
!= NULL
&& lastSlash
>= p
)) {
245 Jim_SetResultString(interp
, p
, -1);
249 static int file_cmd_tail(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
251 const char *path
= Jim_String(argv
[0]);
252 const char *lastSlash
= strrchr(path
, '/');
255 Jim_SetResultString(interp
, lastSlash
+ 1, -1);
258 Jim_SetResult(interp
, argv
[0]);
263 static int file_cmd_normalize(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
266 const char *path
= Jim_String(argv
[0]);
267 char *newname
= Jim_Alloc(MAXPATHLEN
+ 1);
269 if (realpath(path
, newname
)) {
270 Jim_SetResult(interp
, Jim_NewStringObjNoAlloc(interp
, newname
, -1));
274 Jim_SetResult(interp
, argv
[0]);
278 Jim_SetResultString(interp
, "Not implemented", -1);
283 static int file_cmd_join(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
286 char *newname
= Jim_Alloc(MAXPATHLEN
+ 1);
287 char *last
= newname
;
291 /* Simple implementation for now */
292 for (i
= 0; i
< argc
; i
++) {
294 const char *part
= Jim_GetString(argv
[i
], &len
);
297 /* Absolute component, so go back to the start */
300 #if defined(__MINGW32__) || defined(_MSC_VER)
301 else if (strchr(part
, ':')) {
302 /* Absolute compontent on mingw, so go back to the start */
306 else if (part
[0] == '.') {
307 if (part
[1] == '/') {
311 else if (part
[1] == 0 && last
!= newname
) {
312 /* Adding '.' to an existing path does nothing */
317 /* Add a slash if needed */
318 if (last
!= newname
&& last
[-1] != '/') {
323 if (last
+ len
- newname
>= MAXPATHLEN
) {
325 Jim_SetResultString(interp
, "Path too long", -1);
328 memcpy(last
, part
, len
);
332 /* Remove a slash if needed */
333 if (last
> newname
+ 1 && last
[-1] == '/') {
340 /* Probably need to handle some special cases ... */
342 Jim_SetResult(interp
, Jim_NewStringObjNoAlloc(interp
, newname
, last
- newname
));
347 static int file_access(Jim_Interp
*interp
, Jim_Obj
*filename
, int mode
)
349 const char *path
= Jim_String(filename
);
350 int rc
= access(path
, mode
);
352 Jim_SetResultBool(interp
, rc
!= -1);
357 static int file_cmd_readable(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
359 return file_access(interp
, argv
[0], R_OK
);
362 static int file_cmd_writable(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
364 return file_access(interp
, argv
[0], W_OK
);
367 static int file_cmd_executable(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
370 return file_access(interp
, argv
[0], X_OK
);
372 /* XXX: X_OK doesn't work under Windows.
373 * In any case, may need to add .exe, etc. so just lie!
375 Jim_SetResultBool(interp
, 1);
380 static int file_cmd_exists(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
382 return file_access(interp
, argv
[0], F_OK
);
385 static int file_cmd_delete(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
387 int force
= Jim_CompareStringImmediate(interp
, argv
[0], "-force");
389 if (force
|| Jim_CompareStringImmediate(interp
, argv
[0], "--")) {
395 const char *path
= Jim_String(argv
[0]);
397 if (unlink(path
) == -1 && errno
!= ENOENT
) {
398 if (rmdir(path
) == -1) {
399 /* Maybe try using the script helper */
400 if (!force
|| Jim_EvalPrefix(interp
, "file delete force", 1, argv
) != JIM_OK
) {
401 Jim_SetResultFormatted(interp
, "couldn't delete file \"%s\": %s", path
,
412 #ifdef HAVE_MKDIR_ONE_ARG
413 #define MKDIR_DEFAULT(PATHNAME) mkdir(PATHNAME)
415 #define MKDIR_DEFAULT(PATHNAME) mkdir(PATHNAME, 0755)
419 * Create directory, creating all intermediate paths if necessary.
421 * Returns 0 if OK or -1 on failure (and sets errno)
423 * Note: The path may be modified.
425 static int mkdir_all(char *path
)
429 /* First time just try to make the dir */
433 /* Must have failed the first time, so recursively make the parent and try again */
434 char *slash
= strrchr(path
, '/');
436 if (slash
&& slash
!= path
) {
438 if (mkdir_all(path
) != 0) {
444 if (MKDIR_DEFAULT(path
) == 0) {
447 if (errno
== ENOENT
) {
448 /* Create the parent and try again */
451 /* Maybe it already exists as a directory */
452 if (errno
== EEXIST
) {
455 if (stat(path
, &sb
) == 0 && S_ISDIR(sb
.st_mode
)) {
467 static int file_cmd_mkdir(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
470 char *path
= Jim_StrDup(Jim_String(argv
[0]));
471 int rc
= mkdir_all(path
);
475 Jim_SetResultFormatted(interp
, "can't create directory \"%#s\": %s", argv
[0],
485 static int file_cmd_tempfile(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
489 const char *template = "/tmp/tcl.tmp.XXXXXX";
492 template = Jim_String(argv
[0]);
494 filename
= Jim_StrDup(template);
496 fd
= mkstemp(filename
);
498 Jim_SetResultString(interp
, "Failed to create tempfile", -1);
503 Jim_SetResult(interp
, Jim_NewStringObjNoAlloc(interp
, filename
, -1));
508 static int file_cmd_rename(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
515 if (!Jim_CompareStringImmediate(interp
, argv
[0], "-force")) {
523 source
= Jim_String(argv
[0]);
524 dest
= Jim_String(argv
[1]);
526 if (!force
&& access(dest
, F_OK
) == 0) {
527 Jim_SetResultFormatted(interp
, "error renaming \"%#s\" to \"%#s\": target exists", argv
[0],
532 if (rename(source
, dest
) != 0) {
533 Jim_SetResultFormatted(interp
, "error renaming \"%#s\" to \"%#s\": %s", argv
[0], argv
[1],
541 static int file_stat(Jim_Interp
*interp
, Jim_Obj
*filename
, struct stat
*sb
)
543 const char *path
= Jim_String(filename
);
545 if (stat(path
, sb
) == -1) {
546 Jim_SetResultFormatted(interp
, "could not read \"%#s\": %s", filename
, strerror(errno
));
556 static int file_lstat(Jim_Interp
*interp
, Jim_Obj
*filename
, struct stat
*sb
)
558 const char *path
= Jim_String(filename
);
560 if (lstat(path
, sb
) == -1) {
561 Jim_SetResultFormatted(interp
, "could not read \"%#s\": %s", filename
, strerror(errno
));
567 static int file_cmd_atime(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
571 if (file_stat(interp
, argv
[0], &sb
) != JIM_OK
) {
574 Jim_SetResultInt(interp
, sb
.st_atime
);
578 static int file_cmd_mtime(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
585 struct timeval times
[2];
587 if (Jim_GetWide(interp
, argv
[1], &newtime
) != JIM_OK
) {
591 times
[1].tv_sec
= times
[0].tv_sec
= newtime
;
592 times
[1].tv_usec
= times
[0].tv_usec
= 0;
594 if (utimes(Jim_String(argv
[0]), times
) != 0) {
595 Jim_SetResultFormatted(interp
, "can't set time on \"%#s\": %s", argv
[0], strerror(errno
));
599 Jim_SetResultString(interp
, "Not implemented", -1);
603 if (file_stat(interp
, argv
[0], &sb
) != JIM_OK
) {
606 Jim_SetResultInt(interp
, sb
.st_mtime
);
610 static int file_cmd_copy(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
612 return Jim_EvalPrefix(interp
, "file copy", argc
, argv
);
615 static int file_cmd_size(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
619 if (file_stat(interp
, argv
[0], &sb
) != JIM_OK
) {
622 Jim_SetResultInt(interp
, sb
.st_size
);
626 static int file_cmd_isdirectory(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
631 if (file_stat(interp
, argv
[0], &sb
) == JIM_OK
) {
632 ret
= S_ISDIR(sb
.st_mode
);
634 Jim_SetResultInt(interp
, ret
);
638 static int file_cmd_isfile(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
643 if (file_stat(interp
, argv
[0], &sb
) == JIM_OK
) {
644 ret
= S_ISREG(sb
.st_mode
);
646 Jim_SetResultInt(interp
, ret
);
651 static int file_cmd_owned(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
656 if (file_stat(interp
, argv
[0], &sb
) == JIM_OK
) {
657 ret
= (geteuid() == sb
.st_uid
);
659 Jim_SetResultInt(interp
, ret
);
664 #if defined(HAVE_READLINK)
665 static int file_cmd_readlink(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
667 const char *path
= Jim_String(argv
[0]);
668 char *linkValue
= Jim_Alloc(MAXPATHLEN
+ 1);
670 int linkLength
= readlink(path
, linkValue
, MAXPATHLEN
);
672 if (linkLength
== -1) {
674 Jim_SetResultFormatted(interp
, "couldn't readlink \"%#s\": %s", argv
[0], strerror(errno
));
677 linkValue
[linkLength
] = 0;
678 Jim_SetResult(interp
, Jim_NewStringObjNoAlloc(interp
, linkValue
, linkLength
));
683 static int file_cmd_type(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
687 if (file_lstat(interp
, argv
[0], &sb
) != JIM_OK
) {
690 Jim_SetResultString(interp
, JimGetFileType((int)sb
.st_mode
), -1);
694 static int file_cmd_lstat(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
698 if (file_lstat(interp
, argv
[0], &sb
) != JIM_OK
) {
701 return StoreStatData(interp
, argv
[1], &sb
);
704 static int file_cmd_stat(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
708 if (file_stat(interp
, argv
[0], &sb
) != JIM_OK
) {
711 return StoreStatData(interp
, argv
[1], &sb
);
714 static const jim_subcmd_type file_command_table
[] = {
720 /* Description: Last access time */
727 /* Description: Get or set last modification time */
730 "?-force? source dest",
734 /* Description: Copy source file to destination file */
741 /* Description: Directory part of the name */
748 /* Description: Name without any extension */
755 /* Description: Last extension including the dot */
762 /* Description: Last component of the name */
769 /* Description: Normalized path of name */
776 /* Description: Join multiple path components */
783 /* Description: Is file readable */
790 /* Description: Is file writable */
797 /* Description: Is file executable */
804 /* Description: Does file exist */
807 "?-force|--? name ...",
811 /* Description: Deletes the files or directories (must be empty unless -force) */
818 /* Description: Creates the directories */
826 /* Description: Creates a temporary filename */
830 "?-force? source dest",
834 /* Description: Renames a file */
836 #if defined(HAVE_READLINK)
842 /* Description: Value of the symbolic link */
850 /* Description: Size of file */
857 /* Description: Stores results of stat in var array */
864 /* Description: Stores results of lstat in var array */
871 /* Description: Returns type of the file */
879 /* Description: Returns 1 if owned by the current owner */
884 file_cmd_isdirectory
,
887 /* Description: Returns 1 if name is a directory */
894 /* Description: Returns 1 if name is a file */
901 static int Jim_CdCmd(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
906 Jim_WrongNumArgs(interp
, 1, argv
, "dirname");
910 path
= Jim_String(argv
[1]);
912 if (chdir(path
) != 0) {
913 Jim_SetResultFormatted(interp
, "couldn't change working directory to \"%s\": %s", path
,
920 static int Jim_PwdCmd(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
922 const int cwd_len
= 2048;
923 char *cwd
= malloc(cwd_len
);
925 if (getcwd(cwd
, cwd_len
) == NULL
) {
926 Jim_SetResultString(interp
, "Failed to get pwd", -1);
929 #if defined(__MINGW32__) || defined(_MSC_VER)
931 /* Try to keep backlashes out of paths */
933 while ((p
= strchr(p
, '\\')) != NULL
) {
939 Jim_SetResultString(interp
, cwd
, -1);
945 int Jim_fileInit(Jim_Interp
*interp
)
947 if (Jim_PackageProvide(interp
, "file", "1.0", JIM_ERRMSG
))
950 Jim_CreateCommand(interp
, "file", Jim_SubCmdProc
, (void *)file_command_table
, NULL
);
951 Jim_CreateCommand(interp
, "pwd", Jim_PwdCmd
, NULL
, NULL
);
952 Jim_CreateCommand(interp
, "cd", Jim_CdCmd
, NULL
, NULL
);