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.
53 #include <sys/param.h>
56 #include "jimautoconf.h"
57 #include "jim-subcmd.h"
60 # define MAXPATHLEN JIM_PATH_LEN
64 *----------------------------------------------------------------------
68 * Given a mode word, returns a string identifying the type of a
72 * A static text string giving the file type from mode.
77 *----------------------------------------------------------------------
80 static const char *JimGetFileType(int mode
)
85 else if (S_ISDIR(mode
)) {
88 else if (S_ISCHR(mode
)) {
89 return "characterSpecial";
91 else if (S_ISBLK(mode
)) {
92 return "blockSpecial";
94 else if (S_ISFIFO(mode
)) {
98 else if (S_ISLNK(mode
)) {
103 else if (S_ISSOCK(mode
)) {
111 *----------------------------------------------------------------------
115 * This is a utility procedure that breaks out the fields of a
116 * "stat" structure and stores them in textual form into the
117 * elements of an associative array.
120 * Returns a standard Tcl return value. If an error occurs then
121 * a message is left in interp->result.
124 * Elements of the associative array given by "varName" are modified.
126 *----------------------------------------------------------------------
129 static int set_array_int_value(Jim_Interp
*interp
, Jim_Obj
*container
, const char *key
,
132 Jim_Obj
*nameobj
= Jim_NewStringObj(interp
, key
, -1);
133 Jim_Obj
*valobj
= Jim_NewWideObj(interp
, value
);
135 if (Jim_SetDictKeysVector(interp
, container
, &nameobj
, 1, valobj
) != JIM_OK
) {
136 Jim_FreeObj(interp
, nameobj
);
137 Jim_FreeObj(interp
, valobj
);
143 static int set_array_string_value(Jim_Interp
*interp
, Jim_Obj
*container
, const char *key
,
146 Jim_Obj
*nameobj
= Jim_NewStringObj(interp
, key
, -1);
147 Jim_Obj
*valobj
= Jim_NewStringObj(interp
, value
, -1);
149 if (Jim_SetDictKeysVector(interp
, container
, &nameobj
, 1, valobj
) != JIM_OK
) {
150 Jim_FreeObj(interp
, nameobj
);
151 Jim_FreeObj(interp
, valobj
);
157 static int StoreStatData(Jim_Interp
*interp
, Jim_Obj
*varName
, const struct stat
*sb
)
159 if (set_array_int_value(interp
, varName
, "dev", sb
->st_dev
) != JIM_OK
) {
160 Jim_SetResultFormatted(interp
, "can't set \"%#s(dev)\": variables isn't array", varName
);
163 set_array_int_value(interp
, varName
, "ino", sb
->st_ino
);
164 set_array_int_value(interp
, varName
, "mode", sb
->st_mode
);
165 set_array_int_value(interp
, varName
, "nlink", sb
->st_nlink
);
166 set_array_int_value(interp
, varName
, "uid", sb
->st_uid
);
167 set_array_int_value(interp
, varName
, "gid", sb
->st_gid
);
168 set_array_int_value(interp
, varName
, "size", sb
->st_size
);
169 set_array_int_value(interp
, varName
, "atime", sb
->st_atime
);
170 set_array_int_value(interp
, varName
, "mtime", sb
->st_mtime
);
171 set_array_int_value(interp
, varName
, "ctime", sb
->st_ctime
);
172 set_array_string_value(interp
, varName
, "type", JimGetFileType((int)sb
->st_mode
));
174 /* And also return the value */
175 Jim_SetResult(interp
, Jim_GetVariable(interp
, varName
, 0));
180 static int file_cmd_dirname(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
182 const char *path
= Jim_String(argv
[0]);
183 const char *p
= strrchr(path
, '/');
186 Jim_SetResultString(interp
, ".", -1);
188 else if (p
== path
) {
189 Jim_SetResultString(interp
, "/", -1);
191 #if defined(__MINGW32__)
192 else if (p
[-1] == ':') {
194 Jim_SetResultString(interp
, path
, p
- path
+ 1);
198 Jim_SetResultString(interp
, path
, p
- path
);
203 static int file_cmd_rootname(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
205 const char *path
= Jim_String(argv
[0]);
206 const char *lastSlash
= strrchr(path
, '/');
207 const char *p
= strrchr(path
, '.');
209 if (p
== NULL
|| (lastSlash
!= NULL
&& lastSlash
> p
)) {
210 Jim_SetResult(interp
, argv
[0]);
213 Jim_SetResultString(interp
, path
, p
- path
);
218 static int file_cmd_extension(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
220 const char *path
= Jim_String(argv
[0]);
221 const char *lastSlash
= strrchr(path
, '/');
222 const char *p
= strrchr(path
, '.');
224 if (p
== NULL
|| (lastSlash
!= NULL
&& lastSlash
>= p
)) {
227 Jim_SetResultString(interp
, p
, -1);
231 static int file_cmd_tail(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
233 const char *path
= Jim_String(argv
[0]);
234 const char *lastSlash
= strrchr(path
, '/');
237 Jim_SetResultString(interp
, lastSlash
+ 1, -1);
240 Jim_SetResult(interp
, argv
[0]);
245 static int file_cmd_normalize(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
248 const char *path
= Jim_String(argv
[0]);
249 char *newname
= Jim_Alloc(MAXPATHLEN
+ 1);
251 if (realpath(path
, newname
)) {
252 Jim_SetResult(interp
, Jim_NewStringObjNoAlloc(interp
, newname
, -1));
256 Jim_SetResult(interp
, argv
[0]);
260 Jim_SetResultString(interp
, "Not implemented", -1);
265 static int file_cmd_join(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
268 char *newname
= Jim_Alloc(MAXPATHLEN
+ 1);
269 char *last
= newname
;
273 /* Simple implementation for now */
274 for (i
= 0; i
< argc
; i
++) {
276 const char *part
= Jim_GetString(argv
[i
], &len
);
279 /* Absolute component, so go back to the start */
282 #if defined(__MINGW32__)
283 else if (strchr(part
, ':')) {
284 /* Absolute compontent on mingw, so go back to the start */
288 else if (part
[0] == '.') {
289 if (part
[1] == '/') {
293 else if (part
[1] == 0 && last
!= newname
) {
294 /* Adding '.' to an existing path does nothing */
299 /* Add a slash if needed */
300 if (last
!= newname
&& last
[-1] != '/') {
305 if (last
+ len
- newname
>= MAXPATHLEN
) {
307 Jim_SetResultString(interp
, "Path too long", -1);
310 memcpy(last
, part
, len
);
314 /* Remove a slash if needed */
315 if (last
> newname
+ 1 && last
[-1] == '/') {
322 /* Probably need to handle some special cases ... */
324 Jim_SetResult(interp
, Jim_NewStringObjNoAlloc(interp
, newname
, last
- newname
));
329 static int file_access(Jim_Interp
*interp
, Jim_Obj
*filename
, int mode
)
331 const char *path
= Jim_String(filename
);
332 int rc
= access(path
, mode
);
334 Jim_SetResultBool(interp
, rc
!= -1);
339 static int file_cmd_readable(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
341 return file_access(interp
, argv
[0], R_OK
);
344 static int file_cmd_writable(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
346 return file_access(interp
, argv
[0], W_OK
);
349 static int file_cmd_executable(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
351 return file_access(interp
, argv
[0], X_OK
);
354 static int file_cmd_exists(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
356 return file_access(interp
, argv
[0], F_OK
);
359 static int file_cmd_delete(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
361 int force
= Jim_CompareStringImmediate(interp
, argv
[0], "-force");
363 if (force
|| Jim_CompareStringImmediate(interp
, argv
[0], "--")) {
369 const char *path
= Jim_String(argv
[0]);
371 if (unlink(path
) == -1 && errno
!= ENOENT
) {
372 if (rmdir(path
) == -1) {
373 /* Maybe try using the script helper */
374 if (!force
|| Jim_EvalObjPrefix(interp
, "file delete force", 1, argv
) != JIM_OK
) {
375 Jim_SetResultFormatted(interp
, "couldn't delete file \"%s\": %s", path
,
386 #ifdef HAVE_MKDIR_ONE_ARG
387 #define MKDIR_DEFAULT(PATHNAME) mkdir(PATHNAME)
389 #define MKDIR_DEFAULT(PATHNAME) mkdir(PATHNAME, 0755)
393 * Create directory, creating all intermediate paths if necessary.
395 * Returns 0 if OK or -1 on failure (and sets errno)
397 * Note: The path may be modified.
399 static int mkdir_all(char *path
)
403 /* First time just try to make the dir */
407 /* Must have failed the first time, so recursively make the parent and try again */
408 char *slash
= strrchr(path
, '/');
410 if (slash
&& slash
!= path
) {
412 if (mkdir_all(path
) != 0) {
418 if (MKDIR_DEFAULT(path
) == 0) {
421 if (errno
== ENOENT
) {
422 /* Create the parent and try again */
425 /* Maybe it already exists as a directory */
426 if (errno
== EEXIST
) {
429 if (stat(path
, &sb
) == 0 && S_ISDIR(sb
.st_mode
)) {
441 static int file_cmd_mkdir(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
444 char *path
= Jim_StrDup(Jim_String(argv
[0]));
445 int rc
= mkdir_all(path
);
449 Jim_SetResultFormatted(interp
, "can't create directory \"%#s\": %s", argv
[0],
459 static int file_cmd_tempfile(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
463 const char *template = "/tmp/tcl.tmp.XXXXXX";
466 template = Jim_String(argv
[0]);
468 filename
= Jim_StrDup(template);
470 fd
= mkstemp(filename
);
472 Jim_SetResultString(interp
, "Failed to create tempfile", -1);
477 Jim_SetResult(interp
, Jim_NewStringObjNoAlloc(interp
, filename
, -1));
482 static int file_cmd_rename(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
489 if (!Jim_CompareStringImmediate(interp
, argv
[0], "-force")) {
497 source
= Jim_String(argv
[0]);
498 dest
= Jim_String(argv
[1]);
500 if (!force
&& access(dest
, F_OK
) == 0) {
501 Jim_SetResultFormatted(interp
, "error renaming \"%#s\" to \"%#s\": target exists", argv
[0],
506 if (rename(source
, dest
) != 0) {
507 Jim_SetResultFormatted(interp
, "error renaming \"%#s\" to \"%#s\": %s", argv
[0], argv
[1],
515 static int file_stat(Jim_Interp
*interp
, Jim_Obj
*filename
, struct stat
*sb
)
517 const char *path
= Jim_String(filename
);
519 if (stat(path
, sb
) == -1) {
520 Jim_SetResultFormatted(interp
, "could not read \"%#s\": %s", filename
, strerror(errno
));
530 static int file_lstat(Jim_Interp
*interp
, Jim_Obj
*filename
, struct stat
*sb
)
532 const char *path
= Jim_String(filename
);
534 if (lstat(path
, sb
) == -1) {
535 Jim_SetResultFormatted(interp
, "could not read \"%#s\": %s", filename
, strerror(errno
));
541 static int file_cmd_atime(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
545 if (file_stat(interp
, argv
[0], &sb
) != JIM_OK
) {
548 Jim_SetResultInt(interp
, sb
.st_atime
);
552 static int file_cmd_mtime(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
556 if (file_stat(interp
, argv
[0], &sb
) != JIM_OK
) {
559 Jim_SetResultInt(interp
, sb
.st_mtime
);
563 static int file_cmd_copy(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
565 return Jim_EvalObjPrefix(interp
, "file copy", argc
, argv
);
568 static int file_cmd_size(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
572 if (file_stat(interp
, argv
[0], &sb
) != JIM_OK
) {
575 Jim_SetResultInt(interp
, sb
.st_size
);
579 static int file_cmd_isdirectory(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
584 if (file_stat(interp
, argv
[0], &sb
) == JIM_OK
) {
585 ret
= S_ISDIR(sb
.st_mode
);
587 Jim_SetResultInt(interp
, ret
);
591 static int file_cmd_isfile(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
596 if (file_stat(interp
, argv
[0], &sb
) == JIM_OK
) {
597 ret
= S_ISREG(sb
.st_mode
);
599 Jim_SetResultInt(interp
, ret
);
604 static int file_cmd_owned(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
609 if (file_stat(interp
, argv
[0], &sb
) == JIM_OK
) {
610 ret
= (geteuid() == sb
.st_uid
);
612 Jim_SetResultInt(interp
, ret
);
617 #if defined(HAVE_READLINK)
618 static int file_cmd_readlink(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
620 const char *path
= Jim_String(argv
[0]);
621 char *linkValue
= Jim_Alloc(MAXPATHLEN
+ 1);
623 int linkLength
= readlink(path
, linkValue
, MAXPATHLEN
);
625 if (linkLength
== -1) {
627 Jim_SetResultFormatted(interp
, "couldn't readlink \"%s\": %s", argv
[0], strerror(errno
));
630 linkValue
[linkLength
] = 0;
631 Jim_SetResult(interp
, Jim_NewStringObjNoAlloc(interp
, linkValue
, linkLength
));
636 static int file_cmd_type(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
640 if (file_lstat(interp
, argv
[0], &sb
) != JIM_OK
) {
643 Jim_SetResultString(interp
, JimGetFileType((int)sb
.st_mode
), -1);
647 static int file_cmd_lstat(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
651 if (file_lstat(interp
, argv
[0], &sb
) != JIM_OK
) {
654 return StoreStatData(interp
, argv
[1], &sb
);
657 static int file_cmd_stat(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
661 if (file_stat(interp
, argv
[0], &sb
) != JIM_OK
) {
664 return StoreStatData(interp
, argv
[1], &sb
);
667 static const jim_subcmd_type file_command_table
[] = {
670 .function
= file_cmd_atime
,
673 .description
= "Last access time"
677 .function
= file_cmd_mtime
,
680 .description
= "Last modification time"
683 .args
= "?-force? source dest",
684 .function
= file_cmd_copy
,
687 .description
= "Copy source file to destination file"
691 .function
= file_cmd_dirname
,
694 .description
= "Directory part of the name"
698 .function
= file_cmd_rootname
,
701 .description
= "Name without any extension"
703 { .cmd
= "extension",
705 .function
= file_cmd_extension
,
708 .description
= "Last extension including the dot"
712 .function
= file_cmd_tail
,
715 .description
= "Last component of the name"
717 { .cmd
= "normalize",
719 .function
= file_cmd_normalize
,
722 .description
= "Normalized path of name"
725 .args
= "name ?name ...?",
726 .function
= file_cmd_join
,
729 .description
= "Join multiple path components"
733 .function
= file_cmd_readable
,
736 .description
= "Is file readable"
740 .function
= file_cmd_writable
,
743 .description
= "Is file writable"
745 { .cmd
= "executable",
747 .function
= file_cmd_executable
,
750 .description
= "Is file executable"
754 .function
= file_cmd_exists
,
757 .description
= "Does file exist"
760 .args
= "?-force|--? name ...",
761 .function
= file_cmd_delete
,
764 .description
= "Deletes the files or directories (must be empty unless -force)"
768 .function
= file_cmd_mkdir
,
771 .description
= "Creates the directories"
775 .args
= "?template?",
776 .function
= file_cmd_tempfile
,
779 .description
= "Creates a temporary filename"
783 .args
= "?-force? source dest",
784 .function
= file_cmd_rename
,
787 .description
= "Renames a file"
789 #if defined(HAVE_READLINK)
792 .function
= file_cmd_readlink
,
795 .description
= "Value of the symbolic link"
800 .function
= file_cmd_size
,
803 .description
= "Size of file"
807 .function
= file_cmd_stat
,
810 .description
= "Stores results of stat in var array"
814 .function
= file_cmd_lstat
,
817 .description
= "Stores results of lstat in var array"
821 .function
= file_cmd_type
,
824 .description
= "Returns type of the file"
829 .function
= file_cmd_owned
,
832 .description
= "Returns 1 if owned by the current owner"
835 { .cmd
= "isdirectory",
837 .function
= file_cmd_isdirectory
,
840 .description
= "Returns 1 if name is a directory"
844 .function
= file_cmd_isfile
,
847 .description
= "Returns 1 if name is a file"
854 static int Jim_CdCmd(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
859 Jim_WrongNumArgs(interp
, 1, argv
, "dirname");
863 path
= Jim_String(argv
[1]);
865 if (chdir(path
) != 0) {
866 Jim_SetResultFormatted(interp
, "couldn't change working directory to \"%s\": %s", path
,
873 static int Jim_PwdCmd(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
875 const int cwd_len
= 2048;
876 char *cwd
= malloc(cwd_len
);
878 if (getcwd(cwd
, cwd_len
) == NULL
) {
879 Jim_SetResultString(interp
, "Failed to get pwd", -1);
882 #if defined(__MINGW32__)
884 /* Try to keep backlashes out of paths */
886 while ((p
= strchr(p
, '\\')) != NULL
) {
892 Jim_SetResultString(interp
, cwd
, -1);
898 int Jim_fileInit(Jim_Interp
*interp
)
900 if (Jim_PackageProvide(interp
, "file", "1.0", JIM_ERRMSG
))
903 Jim_CreateCommand(interp
, "file", Jim_SubCmdProc
, (void *)file_command_table
, NULL
);
904 Jim_CreateCommand(interp
, "pwd", Jim_PwdCmd
, NULL
, NULL
);
905 Jim_CreateCommand(interp
, "cd", Jim_CdCmd
, NULL
, NULL
);