1/* 2 * Implements the file command for jim 3 * 4 * (c) 2008 Steve Bennett <steveb@workware.net.au> 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 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. 16 * 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. 29 * 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. 33 * 34 * Based on code originally from Tcl 6.7: 35 * 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. 44 */ 45 46#include <limits.h> 47#include <stdlib.h> 48#include <string.h> 49#include <stdio.h> 50#include <unistd.h> 51#include <errno.h> 52#include <sys/stat.h> 53#include <sys/param.h> 54#include <sys/time.h> 55 56#include "jim.h" 57#include "jimautoconf.h" 58#include "jim-subcmd.h" 59 60# ifndef MAXPATHLEN 61# define MAXPATHLEN JIM_PATH_LEN 62# endif 63 64/* 65 *---------------------------------------------------------------------- 66 * 67 * JimGetFileType -- 68 * 69 * Given a mode word, returns a string identifying the type of a 70 * file. 71 * 72 * Results: 73 * A static text string giving the file type from mode. 74 * 75 * Side effects: 76 * None. 77 * 78 *---------------------------------------------------------------------- 79 */ 80 81static const char *JimGetFileType(int mode) 82{ 83 if (S_ISREG(mode)) { 84 return "file"; 85 } 86 else if (S_ISDIR(mode)) { 87 return "directory"; 88 } 89 else if (S_ISCHR(mode)) { 90 return "characterSpecial"; 91 } 92 else if (S_ISBLK(mode)) { 93 return "blockSpecial"; 94 } 95 else if (S_ISFIFO(mode)) { 96 return "fifo"; 97#ifdef S_ISLNK 98 } 99 else if (S_ISLNK(mode)) { 100 return "link"; 101#endif 102#ifdef S_ISSOCK 103 } 104 else if (S_ISSOCK(mode)) { 105 return "socket"; 106#endif 107 } 108 return "unknown"; 109} 110 111/* 112 *---------------------------------------------------------------------- 113 * 114 * StoreStatData -- 115 * 116 * This is a utility procedure that breaks out the fields of a 117 * "stat" structure and stores them in textual form into the 118 * elements of an associative array. 119 * 120 * Results: 121 * Returns a standard Tcl return value. If an error occurs then 122 * a message is left in interp->result. 123 * 124 * Side effects: 125 * Elements of the associative array given by "varName" are modified. 126 * 127 *---------------------------------------------------------------------- 128 */ 129 130static int set_array_int_value(Jim_Interp *interp, Jim_Obj *container, const char *key, 131 jim_wide value) 132{ 133 Jim_Obj *nameobj = Jim_NewStringObj(interp, key, -1); 134 Jim_Obj *valobj = Jim_NewWideObj(interp, value); 135 136 if (Jim_SetDictKeysVector(interp, container, &nameobj, 1, valobj, JIM_ERRMSG) != JIM_OK) { 137 Jim_FreeObj(interp, nameobj); 138 Jim_FreeObj(interp, valobj); 139 return JIM_ERR; 140 } 141 return JIM_OK; 142} 143 144static int set_array_string_value(Jim_Interp *interp, Jim_Obj *container, const char *key, 145 const char *value) 146{ 147 Jim_Obj *nameobj = Jim_NewStringObj(interp, key, -1); 148 Jim_Obj *valobj = Jim_NewStringObj(interp, value, -1); 149 150 if (Jim_SetDictKeysVector(interp, container, &nameobj, 1, valobj, JIM_ERRMSG) != JIM_OK) { 151 Jim_FreeObj(interp, nameobj); 152 Jim_FreeObj(interp, valobj); 153 return JIM_ERR; 154 } 155 return JIM_OK; 156} 157 158static int StoreStatData(Jim_Interp *interp, Jim_Obj *varName, const struct stat *sb) 159{ 160 if (set_array_int_value(interp, varName, "dev", sb->st_dev) != JIM_OK) { 161 Jim_SetResultFormatted(interp, "can't set \"%#s(dev)\": variables isn't array", varName); 162 return JIM_ERR; 163 } 164 set_array_int_value(interp, varName, "ino", sb->st_ino); 165 set_array_int_value(interp, varName, "mode", sb->st_mode); 166 set_array_int_value(interp, varName, "nlink", sb->st_nlink); 167 set_array_int_value(interp, varName, "uid", sb->st_uid); 168 set_array_int_value(interp, varName, "gid", sb->st_gid); 169 set_array_int_value(interp, varName, "size", sb->st_size); 170 set_array_int_value(interp, varName, "atime", sb->st_atime); 171 set_array_int_value(interp, varName, "mtime", sb->st_mtime); 172 set_array_int_value(interp, varName, "ctime", sb->st_ctime); 173 set_array_string_value(interp, varName, "type", JimGetFileType((int)sb->st_mode)); 174 175 /* And also return the value */ 176 Jim_SetResult(interp, Jim_GetVariable(interp, varName, 0)); 177 178 return JIM_OK; 179} 180 181static int file_cmd_dirname(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 182{ 183 const char *path = Jim_String(argv[0]); 184 const char *p = strrchr(path, '/'); 185 186 if (!p) { 187 Jim_SetResultString(interp, ".", -1); 188 } 189 else if (p == path) { 190 Jim_SetResultString(interp, "/", -1); 191 } 192#if defined(__MINGW32__) 193 else if (p[-1] == ':') { 194 /* z:/dir => z:/ */ 195 Jim_SetResultString(interp, path, p - path + 1); 196 } 197#endif 198 else { 199 Jim_SetResultString(interp, path, p - path); 200 } 201 return JIM_OK; 202} 203 204static int file_cmd_rootname(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 205{ 206 const char *path = Jim_String(argv[0]); 207 const char *lastSlash = strrchr(path, '/'); 208 const char *p = strrchr(path, '.'); 209 210 if (p == NULL || (lastSlash != NULL && lastSlash > p)) { 211 Jim_SetResult(interp, argv[0]); 212 } 213 else { 214 Jim_SetResultString(interp, path, p - path); 215 } 216 return JIM_OK; 217} 218 219static int file_cmd_extension(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 220{ 221 const char *path = Jim_String(argv[0]); 222 const char *lastSlash = strrchr(path, '/'); 223 const char *p = strrchr(path, '.'); 224 225 if (p == NULL || (lastSlash != NULL && lastSlash >= p)) { 226 p = ""; 227 } 228 Jim_SetResultString(interp, p, -1); 229 return JIM_OK; 230} 231 232static int file_cmd_tail(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 233{ 234 const char *path = Jim_String(argv[0]); 235 const char *lastSlash = strrchr(path, '/'); 236 237 if (lastSlash) { 238 Jim_SetResultString(interp, lastSlash + 1, -1); 239 } 240 else { 241 Jim_SetResult(interp, argv[0]); 242 } 243 return JIM_OK; 244} 245 246static int file_cmd_normalize(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 247{ 248#ifdef HAVE_REALPATH 249 const char *path = Jim_String(argv[0]); 250 char *newname = Jim_Alloc(MAXPATHLEN + 1); 251 252 if (realpath(path, newname)) { 253 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, newname, -1)); 254 } 255 else { 256 Jim_Free(newname); 257 Jim_SetResult(interp, argv[0]); 258 } 259 return JIM_OK; 260#else 261 Jim_SetResultString(interp, "Not implemented", -1); 262 return JIM_ERR; 263#endif 264} 265 266static int file_cmd_join(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 267{ 268 int i; 269 char *newname = Jim_Alloc(MAXPATHLEN + 1); 270 char *last = newname; 271 272 *newname = 0; 273 274 /* Simple implementation for now */ 275 for (i = 0; i < argc; i++) { 276 int len; 277 const char *part = Jim_GetString(argv[i], &len); 278 279 if (*part == '/') { 280 /* Absolute component, so go back to the start */ 281 last = newname; 282 } 283#if defined(__MINGW32__) 284 else if (strchr(part, ':')) { 285 /* Absolute compontent on mingw, so go back to the start */ 286 last = newname; 287 } 288#endif 289 else if (part[0] == '.') { 290 if (part[1] == '/') { 291 part += 2; 292 len -= 2; 293 } 294 else if (part[1] == 0 && last != newname) { 295 /* Adding '.' to an existing path does nothing */ 296 continue; 297 } 298 } 299 300 /* Add a slash if needed */ 301 if (last != newname && last[-1] != '/') { 302 *last++ = '/'; 303 } 304 305 if (len) { 306 if (last + len - newname >= MAXPATHLEN) { 307 Jim_Free(newname); 308 Jim_SetResultString(interp, "Path too long", -1); 309 return JIM_ERR; 310 } 311 memcpy(last, part, len); 312 last += len; 313 } 314 315 /* Remove a slash if needed */ 316 if (last > newname + 1 && last[-1] == '/') { 317 *--last = 0; 318 } 319 } 320 321 *last = 0; 322 323 /* Probably need to handle some special cases ... */ 324 325 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, newname, last - newname)); 326 327 return JIM_OK; 328} 329 330static int file_access(Jim_Interp *interp, Jim_Obj *filename, int mode) 331{ 332 const char *path = Jim_String(filename); 333 int rc = access(path, mode); 334 335 Jim_SetResultBool(interp, rc != -1); 336 337 return JIM_OK; 338} 339 340static int file_cmd_readable(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 341{ 342 return file_access(interp, argv[0], R_OK); 343} 344 345static int file_cmd_writable(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 346{ 347 return file_access(interp, argv[0], W_OK); 348} 349 350static int file_cmd_executable(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 351{ 352 return file_access(interp, argv[0], X_OK); 353} 354 355static int file_cmd_exists(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 356{ 357 return file_access(interp, argv[0], F_OK); 358} 359 360static int file_cmd_delete(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 361{ 362 int force = Jim_CompareStringImmediate(interp, argv[0], "-force"); 363 364 if (force || Jim_CompareStringImmediate(interp, argv[0], "--")) { 365 argc++; 366 argv--; 367 } 368 369 while (argc--) { 370 const char *path = Jim_String(argv[0]); 371 372 if (unlink(path) == -1 && errno != ENOENT) { 373 if (rmdir(path) == -1) { 374 /* Maybe try using the script helper */ 375 if (!force || Jim_EvalPrefix(interp, "file delete force", 1, argv) != JIM_OK) { 376 Jim_SetResultFormatted(interp, "couldn't delete file \"%s\": %s", path, 377 strerror(errno)); 378 return JIM_ERR; 379 } 380 } 381 } 382 argv++; 383 } 384 return JIM_OK; 385} 386 387#ifdef HAVE_MKDIR_ONE_ARG 388#define MKDIR_DEFAULT(PATHNAME) mkdir(PATHNAME) 389#else 390#define MKDIR_DEFAULT(PATHNAME) mkdir(PATHNAME, 0755) 391#endif 392 393/** 394 * Create directory, creating all intermediate paths if necessary. 395 * 396 * Returns 0 if OK or -1 on failure (and sets errno) 397 * 398 * Note: The path may be modified. 399 */ 400static int mkdir_all(char *path) 401{ 402 int ok = 1; 403 404 /* First time just try to make the dir */ 405 goto first; 406 407 while (ok--) { 408 /* Must have failed the first time, so recursively make the parent and try again */ 409 char *slash = strrchr(path, '/'); 410 411 if (slash && slash != path) { 412 *slash = 0; 413 if (mkdir_all(path) != 0) { 414 return -1; 415 } 416 *slash = '/'; 417 } 418 first: 419 if (MKDIR_DEFAULT(path) == 0) { 420 return 0; 421 } 422 if (errno == ENOENT) { 423 /* Create the parent and try again */ 424 continue; 425 } 426 /* Maybe it already exists as a directory */ 427 if (errno == EEXIST) { 428 struct stat sb; 429 430 if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) { 431 return 0; 432 } 433 /* Restore errno */ 434 errno = EEXIST; 435 } 436 /* Failed */ 437 break; 438 } 439 return -1; 440} 441 442static int file_cmd_mkdir(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 443{ 444 while (argc--) { 445 char *path = Jim_StrDup(Jim_String(argv[0])); 446 int rc = mkdir_all(path); 447 448 Jim_Free(path); 449 if (rc != 0) { 450 Jim_SetResultFormatted(interp, "can't create directory \"%#s\": %s", argv[0], 451 strerror(errno)); 452 return JIM_ERR; 453 } 454 argv++; 455 } 456 return JIM_OK; 457} 458 459#ifdef HAVE_MKSTEMP 460static int file_cmd_tempfile(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 461{ 462 int fd; 463 char *filename; 464 const char *template = "/tmp/tcl.tmp.XXXXXX"; 465 466 if (argc >= 1) { 467 template = Jim_String(argv[0]); 468 } 469 filename = Jim_StrDup(template); 470 471 fd = mkstemp(filename); 472 if (fd < 0) { 473 Jim_SetResultString(interp, "Failed to create tempfile", -1); 474 return JIM_ERR; 475 } 476 close(fd); 477 478 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, filename, -1)); 479 return JIM_OK; 480} 481#endif 482 483static int file_cmd_rename(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 484{ 485 const char *source; 486 const char *dest; 487 int force = 0; 488 489 if (argc == 3) { 490 if (!Jim_CompareStringImmediate(interp, argv[0], "-force")) { 491 return -1; 492 } 493 force++; 494 argv++; 495 argc--; 496 } 497 498 source = Jim_String(argv[0]); 499 dest = Jim_String(argv[1]); 500 501 if (!force && access(dest, F_OK) == 0) { 502 Jim_SetResultFormatted(interp, "error renaming \"%#s\" to \"%#s\": target exists", argv[0], 503 argv[1]); 504 return JIM_ERR; 505 } 506 507 if (rename(source, dest) != 0) { 508 Jim_SetResultFormatted(interp, "error renaming \"%#s\" to \"%#s\": %s", argv[0], argv[1], 509 strerror(errno)); 510 return JIM_ERR; 511 } 512 513 return JIM_OK; 514} 515 516static int file_stat(Jim_Interp *interp, Jim_Obj *filename, struct stat *sb) 517{ 518 const char *path = Jim_String(filename); 519 520 if (stat(path, sb) == -1) { 521 Jim_SetResultFormatted(interp, "could not read \"%#s\": %s", filename, strerror(errno)); 522 return JIM_ERR; 523 } 524 return JIM_OK; 525} 526 527#ifndef HAVE_LSTAT 528#define lstat stat 529#endif 530 531static int file_lstat(Jim_Interp *interp, Jim_Obj *filename, struct stat *sb) 532{ 533 const char *path = Jim_String(filename); 534 535 if (lstat(path, sb) == -1) { 536 Jim_SetResultFormatted(interp, "could not read \"%#s\": %s", filename, strerror(errno)); 537 return JIM_ERR; 538 } 539 return JIM_OK; 540} 541 542static int file_cmd_atime(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 543{ 544 struct stat sb; 545 546 if (file_stat(interp, argv[0], &sb) != JIM_OK) { 547 return JIM_ERR; 548 } 549 Jim_SetResultInt(interp, sb.st_atime); 550 return JIM_OK; 551} 552 553static int file_cmd_mtime(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 554{ 555 struct stat sb; 556 557 if (argc == 2) { 558#ifdef HAVE_UTIMES 559 jim_wide newtime; 560 struct timeval times[2]; 561 562 if (Jim_GetWide(interp, argv[1], &newtime) != JIM_OK) { 563 return JIM_ERR; 564 } 565 566 times[1].tv_sec = times[0].tv_sec = newtime; 567 times[1].tv_usec = times[0].tv_usec = 0; 568 569 if (utimes(Jim_String(argv[0]), times) != 0) { 570 Jim_SetResultFormatted(interp, "can't set time on \"%#s\": %s", argv[0], strerror(errno)); 571 return JIM_ERR; 572 } 573#else 574 Jim_SetResultString(interp, "Not implemented", -1); 575 return JIM_ERR; 576#endif 577 } 578 if (file_stat(interp, argv[0], &sb) != JIM_OK) { 579 return JIM_ERR; 580 } 581 Jim_SetResultInt(interp, sb.st_mtime); 582 return JIM_OK; 583} 584 585static int file_cmd_copy(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 586{ 587 return Jim_EvalPrefix(interp, "file copy", argc, argv); 588} 589 590static int file_cmd_size(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 591{ 592 struct stat sb; 593 594 if (file_stat(interp, argv[0], &sb) != JIM_OK) { 595 return JIM_ERR; 596 } 597 Jim_SetResultInt(interp, sb.st_size); 598 return JIM_OK; 599} 600 601static int file_cmd_isdirectory(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 602{ 603 struct stat sb; 604 int ret = 0; 605 606 if (file_stat(interp, argv[0], &sb) == JIM_OK) { 607 ret = S_ISDIR(sb.st_mode); 608 } 609 Jim_SetResultInt(interp, ret); 610 return JIM_OK; 611} 612 613static int file_cmd_isfile(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 614{ 615 struct stat sb; 616 int ret = 0; 617 618 if (file_stat(interp, argv[0], &sb) == JIM_OK) { 619 ret = S_ISREG(sb.st_mode); 620 } 621 Jim_SetResultInt(interp, ret); 622 return JIM_OK; 623} 624 625#ifdef HAVE_GETEUID 626static int file_cmd_owned(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 627{ 628 struct stat sb; 629 int ret = 0; 630 631 if (file_stat(interp, argv[0], &sb) == JIM_OK) { 632 ret = (geteuid() == sb.st_uid); 633 } 634 Jim_SetResultInt(interp, ret); 635 return JIM_OK; 636} 637#endif 638 639#if defined(HAVE_READLINK) 640static int file_cmd_readlink(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 641{ 642 const char *path = Jim_String(argv[0]); 643 char *linkValue = Jim_Alloc(MAXPATHLEN + 1); 644 645 int linkLength = readlink(path, linkValue, MAXPATHLEN); 646 647 if (linkLength == -1) { 648 Jim_Free(linkValue); 649 Jim_SetResultFormatted(interp, "couldn't readlink \"%#s\": %s", argv[0], strerror(errno)); 650 return JIM_ERR; 651 } 652 linkValue[linkLength] = 0; 653 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, linkValue, linkLength)); 654 return JIM_OK; 655} 656#endif 657 658static int file_cmd_type(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 659{ 660 struct stat sb; 661 662 if (file_lstat(interp, argv[0], &sb) != JIM_OK) { 663 return JIM_ERR; 664 } 665 Jim_SetResultString(interp, JimGetFileType((int)sb.st_mode), -1); 666 return JIM_OK; 667} 668 669static int file_cmd_lstat(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 670{ 671 struct stat sb; 672 673 if (file_lstat(interp, argv[0], &sb) != JIM_OK) { 674 return JIM_ERR; 675 } 676 return StoreStatData(interp, argv[1], &sb); 677} 678 679static int file_cmd_stat(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 680{ 681 struct stat sb; 682 683 if (file_stat(interp, argv[0], &sb) != JIM_OK) { 684 return JIM_ERR; 685 } 686 return StoreStatData(interp, argv[1], &sb); 687} 688 689static const jim_subcmd_type file_command_table[] = { 690 { .cmd = "atime", 691 .args = "name", 692 .function = file_cmd_atime, 693 .minargs = 1, 694 .maxargs = 1, 695 .description = "Last access time" 696 }, 697 { .cmd = "mtime", 698 .args = "name ?time?", 699 .function = file_cmd_mtime, 700 .minargs = 1, 701 .maxargs = 2, 702 .description = "Get or set last modification time" 703 }, 704 { .cmd = "copy", 705 .args = "?-force? source dest", 706 .function = file_cmd_copy, 707 .minargs = 2, 708 .maxargs = 3, 709 .description = "Copy source file to destination file" 710 }, 711 { .cmd = "dirname", 712 .args = "name", 713 .function = file_cmd_dirname, 714 .minargs = 1, 715 .maxargs = 1, 716 .description = "Directory part of the name" 717 }, 718 { .cmd = "rootname", 719 .args = "name", 720 .function = file_cmd_rootname, 721 .minargs = 1, 722 .maxargs = 1, 723 .description = "Name without any extension" 724 }, 725 { .cmd = "extension", 726 .args = "name", 727 .function = file_cmd_extension, 728 .minargs = 1, 729 .maxargs = 1, 730 .description = "Last extension including the dot" 731 }, 732 { .cmd = "tail", 733 .args = "name", 734 .function = file_cmd_tail, 735 .minargs = 1, 736 .maxargs = 1, 737 .description = "Last component of the name" 738 }, 739 { .cmd = "normalize", 740 .args = "name", 741 .function = file_cmd_normalize, 742 .minargs = 1, 743 .maxargs = 1, 744 .description = "Normalized path of name" 745 }, 746 { .cmd = "join", 747 .args = "name ?name ...?", 748 .function = file_cmd_join, 749 .minargs = 1, 750 .maxargs = -1, 751 .description = "Join multiple path components" 752 }, 753 { .cmd = "readable", 754 .args = "name", 755 .function = file_cmd_readable, 756 .minargs = 1, 757 .maxargs = 1, 758 .description = "Is file readable" 759 }, 760 { .cmd = "writable", 761 .args = "name", 762 .function = file_cmd_writable, 763 .minargs = 1, 764 .maxargs = 1, 765 .description = "Is file writable" 766 }, 767 { .cmd = "executable", 768 .args = "name", 769 .function = file_cmd_executable, 770 .minargs = 1, 771 .maxargs = 1, 772 .description = "Is file executable" 773 }, 774 { .cmd = "exists", 775 .args = "name", 776 .function = file_cmd_exists, 777 .minargs = 1, 778 .maxargs = 1, 779 .description = "Does file exist" 780 }, 781 { .cmd = "delete", 782 .args = "?-force|--? name ...", 783 .function = file_cmd_delete, 784 .minargs = 1, 785 .maxargs = -1, 786 .description = "Deletes the files or directories (must be empty unless -force)" 787 }, 788 { .cmd = "mkdir", 789 .args = "dir ...", 790 .function = file_cmd_mkdir, 791 .minargs = 1, 792 .maxargs = -1, 793 .description = "Creates the directories" 794 }, 795#ifdef HAVE_MKSTEMP 796 { .cmd = "tempfile", 797 .args = "?template?", 798 .function = file_cmd_tempfile, 799 .minargs = 0, 800 .maxargs = 1, 801 .description = "Creates a temporary filename" 802 }, 803#endif 804 { .cmd = "rename", 805 .args = "?-force? source dest", 806 .function = file_cmd_rename, 807 .minargs = 2, 808 .maxargs = 3, 809 .description = "Renames a file" 810 }, 811#if defined(HAVE_READLINK) 812 { .cmd = "readlink", 813 .args = "name", 814 .function = file_cmd_readlink, 815 .minargs = 1, 816 .maxargs = 1, 817 .description = "Value of the symbolic link" 818 }, 819#endif 820 { .cmd = "size", 821 .args = "name", 822 .function = file_cmd_size, 823 .minargs = 1, 824 .maxargs = 1, 825 .description = "Size of file" 826 }, 827 { .cmd = "stat", 828 .args = "name var", 829 .function = file_cmd_stat, 830 .minargs = 2, 831 .maxargs = 2, 832 .description = "Stores results of stat in var array" 833 }, 834 { .cmd = "lstat", 835 .args = "name var", 836 .function = file_cmd_lstat, 837 .minargs = 2, 838 .maxargs = 2, 839 .description = "Stores results of lstat in var array" 840 }, 841 { .cmd = "type", 842 .args = "name", 843 .function = file_cmd_type, 844 .minargs = 1, 845 .maxargs = 1, 846 .description = "Returns type of the file" 847 }, 848#ifdef HAVE_GETEUID 849 { .cmd = "owned", 850 .args = "name", 851 .function = file_cmd_owned, 852 .minargs = 1, 853 .maxargs = 1, 854 .description = "Returns 1 if owned by the current owner" 855 }, 856#endif 857 { .cmd = "isdirectory", 858 .args = "name", 859 .function = file_cmd_isdirectory, 860 .minargs = 1, 861 .maxargs = 1, 862 .description = "Returns 1 if name is a directory" 863 }, 864 { .cmd = "isfile", 865 .args = "name", 866 .function = file_cmd_isfile, 867 .minargs = 1, 868 .maxargs = 1, 869 .description = "Returns 1 if name is a file" 870 }, 871 { 872 .cmd = 0 873 } 874}; 875 876static int Jim_CdCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 877{ 878 const char *path; 879 880 if (argc != 2) { 881 Jim_WrongNumArgs(interp, 1, argv, "dirname"); 882 return JIM_ERR; 883 } 884 885 path = Jim_String(argv[1]); 886 887 if (chdir(path) != 0) { 888 Jim_SetResultFormatted(interp, "couldn't change working directory to \"%s\": %s", path, 889 strerror(errno)); 890 return JIM_ERR; 891 } 892 return JIM_OK; 893} 894 895static int Jim_PwdCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) 896{ 897 const int cwd_len = 2048; 898 char *cwd = malloc(cwd_len); 899 900 if (getcwd(cwd, cwd_len) == NULL) { 901 Jim_SetResultString(interp, "Failed to get pwd", -1); 902 return JIM_ERR; 903 } 904#if defined(__MINGW32__) 905 { 906 /* Try to keep backlashes out of paths */ 907 char *p = cwd; 908 while ((p = strchr(p, '\\')) != NULL) { 909 *p++ = '/'; 910 } 911 } 912#endif 913 914 Jim_SetResultString(interp, cwd, -1); 915 916 free(cwd); 917 return JIM_OK; 918} 919 920int Jim_fileInit(Jim_Interp *interp) 921{ 922 if (Jim_PackageProvide(interp, "file", "1.0", JIM_ERRMSG)) 923 return JIM_ERR; 924 925 Jim_CreateCommand(interp, "file", Jim_SubCmdProc, (void *)file_command_table, NULL); 926 Jim_CreateCommand(interp, "pwd", Jim_PwdCmd, NULL, NULL); 927 Jim_CreateCommand(interp, "cd", Jim_CdCmd, NULL, NULL); 928 return JIM_OK; 929} 930