|
|
1.1 ! root 1: /* File IO for GNU Emacs. ! 2: Copyright (C) 1985 Richard M. Stallman. ! 3: ! 4: This file is part of GNU Emacs. ! 5: ! 6: GNU Emacs is distributed in the hope that it will be useful, ! 7: but WITHOUT ANY WARRANTY. No author or distributor ! 8: accepts responsibility to anyone for the consequences of using it ! 9: or for whether it serves any particular purpose or works at all, ! 10: unless he says so in writing. Refer to the GNU Emacs General Public ! 11: License for full details. ! 12: ! 13: Everyone is granted permission to copy, modify and redistribute ! 14: GNU Emacs, but only under the conditions described in the ! 15: GNU Emacs General Public License. A copy of this license is ! 16: supposed to have been given to you along with GNU Emacs so you ! 17: can know your rights and responsibilities. It should be in a ! 18: file named COPYING. Among other things, the copyright notice ! 19: and this notice must be preserved on all copies. */ ! 20: ! 21: ! 22: #include <sys/types.h> ! 23: #include <sys/stat.h> ! 24: #include <pwd.h> ! 25: #include <ctype.h> ! 26: #include <sys/dir.h> ! 27: #include <errno.h> ! 28: #undef NULL ! 29: #include "config.h" ! 30: #include "lisp.h" ! 31: #include "buffer.h" ! 32: #include "window.h" ! 33: ! 34: #define min(a, b) ((a) < (b) ? (a) : (b)) ! 35: #define max(a, b) ((a) > (b) ? (a) : (b)) ! 36: ! 37: /* Nonzero during writing of auto-save files */ ! 38: int auto_saving; ! 39: ! 40: /* Nonzero means, when reading a filename in the minibuffer, ! 41: start out by inserting the default directory into the minibuffer. */ ! 42: int insert_default_directory; ! 43: ! 44: Lisp_Object Qfile_error, Qfile_already_exists; ! 45: ! 46: report_file_error (string, data) ! 47: char *string; ! 48: Lisp_Object data; ! 49: { ! 50: Lisp_Object errstring; ! 51: extern char *sys_errlist[]; ! 52: extern int errno; ! 53: extern int sys_nerr; ! 54: ! 55: if (errno < sys_nerr) ! 56: errstring = build_string (sys_errlist[errno]); ! 57: else ! 58: errstring = build_string ("undocumented error code"); ! 59: ! 60: /* System error messages are capitalized. Downcase the initial. */ ! 61: if (XSTRING (errstring)->data[0] >= 'A' && ! 62: XSTRING (errstring)->data[0] <= 'Z') ! 63: XSTRING (errstring)->data[0] += 040; ! 64: ! 65: while (1) ! 66: Fsignal (Qfile_error, ! 67: Fcons (build_string (string), Fcons (errstring, data))); ! 68: } ! 69: ! 70: DEFUN ("file-name-directory", Ffile_name_directory, Sfile_name_directory, ! 71: 1, 1, 0, ! 72: "Return the directory component in file name NAME.\n\ ! 73: Return nil if NAME does not include a directory.\n\ ! 74: Otherwise return a string ending in a slash.") ! 75: (file) ! 76: Lisp_Object file; ! 77: { ! 78: register unsigned char *beg; ! 79: register unsigned char *p; ! 80: ! 81: CHECK_STRING (file, 0); ! 82: ! 83: beg = XSTRING (file)->data; ! 84: p = beg + XSTRING (file)->size; ! 85: ! 86: while (p != beg && p[-1] != '/') p--; ! 87: ! 88: if (p == beg) ! 89: return Qnil; ! 90: return make_string (beg, p - beg); ! 91: } ! 92: ! 93: DEFUN ("file-name-nondirectory", Ffile_name_nondirectory, Sfile_name_nondirectory, ! 94: 1, 1, 0, ! 95: "Return file name NAME sans its directory.\n\ ! 96: This is everything after the last slash in NAME, if NAME contains a slash.") ! 97: (file) ! 98: Lisp_Object file; ! 99: { ! 100: register unsigned char *beg, *p, *end; ! 101: ! 102: CHECK_STRING (file, 0); ! 103: ! 104: beg = XSTRING (file)->data; ! 105: end = p = beg + XSTRING (file)->size; ! 106: ! 107: while (p != beg && p[-1] != '/') p--; ! 108: ! 109: return make_string (p, end - p); ! 110: } ! 111: ! 112: DEFUN ("make-temp-name", Fmake_temp_name, Smake_temp_name, 1, 1, 0, ! 113: "Generate temporary name (string) starting with PREFIX (a string).") ! 114: (prefix) ! 115: Lisp_Object prefix; ! 116: { ! 117: Lisp_Object val; ! 118: val = concat2 (prefix, build_string ("XXXXXX")); ! 119: mktemp (XSTRING (val)->data); ! 120: return val; ! 121: } ! 122: ! 123: DEFUN ("expand-file-name", Fexpand_file_name, Sexpand_file_name, 1, 2, 0, ! 124: "Convert FILENAME to absolute, and canonicalize it.\n\ ! 125: Second arg DEFAULT is directory to start with if FILENAME is relative\n\ ! 126: (does not start with slash); if DEFAULT is nil or missing,\n\ ! 127: the current buffer's value of default-directory is used.\n\ ! 128: Filenames containing . or .. as components are simplified;\n\ ! 129: initial ~ is expanded. See also the function substitute-in-file-name.") ! 130: (name, defalt) ! 131: Lisp_Object name, defalt; ! 132: { ! 133: unsigned char *nm; ! 134: ! 135: register unsigned char *newdir, *p, *o; ! 136: int tlen; ! 137: unsigned char *target; ! 138: struct passwd *pw; ! 139: int lose; ! 140: ! 141: CHECK_STRING (name, 0); ! 142: ! 143: nm = XSTRING (name)->data; ! 144: ! 145: /* If nm is absolute, flush ...// and detect /./ and /../. ! 146: If no /./ or /../ we can return right away. */ ! 147: if (nm[0] == '/') ! 148: { ! 149: p = nm; ! 150: lose = 0; ! 151: while (*p) ! 152: { ! 153: if (p[0] == '/' && p[1] == '/' ! 154: #ifdef APOLLO ! 155: /* // at start of filename is meaningful on Apollo system */ ! 156: && nm != p ! 157: #endif /* APOLLO */ ! 158: ) ! 159: nm = p + 1; ! 160: if (p[0] == '/' && p[1] == '~') ! 161: nm = p + 1, lose = 1; ! 162: if (p[0] == '/' && p[1] == '.' ! 163: && (p[2] == '/' || p[2] == 0 ! 164: || (p[2] == '.' && (p[3] == '/' || p[3] == 0)))) ! 165: lose = 1; ! 166: p++; ! 167: } ! 168: if (!lose) ! 169: { ! 170: if (nm == XSTRING (name)->data) ! 171: return name; ! 172: return build_string (nm); ! 173: } ! 174: } ! 175: ! 176: /* Now determine directory to start with and put it in newdir */ ! 177: ! 178: newdir = 0; ! 179: ! 180: if (nm[0] == '~') /* prefix ~ */ ! 181: if (nm[1] == '/' || nm[1] == 0)/* ~/filename */ ! 182: { ! 183: if (!(newdir = (unsigned char *) getenv ("HOME"))) ! 184: newdir = (unsigned char *) ""; ! 185: nm++; ! 186: } ! 187: else /* ~user/filename */ ! 188: { ! 189: for (p = nm; *p && *p != '/'; p++); ! 190: o = (unsigned char *) alloca (p - nm + 1); ! 191: bcopy ((char *) nm, o, p - nm); ! 192: o [p - nm] = 0; ! 193: ! 194: pw = (struct passwd *) getpwnam (o + 1); ! 195: if (!pw) ! 196: error ("\"%s\" isn't a registered user", o + 1); ! 197: ! 198: nm = p; ! 199: newdir = (unsigned char *) pw -> pw_dir; ! 200: } ! 201: ! 202: if (nm[0] != '/' && !newdir) ! 203: { ! 204: if (NULL (defalt)) ! 205: defalt = bf_cur->directory; ! 206: CHECK_STRING (defalt, 1); ! 207: newdir = XSTRING (defalt)->data; ! 208: } ! 209: ! 210: /* Now concatenate the directory and name to new space in thestack frame */ ! 211: ! 212: tlen = (newdir ? strlen (newdir) + 1 : 0) + strlen (nm) + 1; ! 213: target = (unsigned char *) alloca (tlen); ! 214: *target = 0; ! 215: ! 216: if (newdir) ! 217: { ! 218: strcpy (target, newdir); ! 219: ! 220: /* Make sure there is a slash to separate directory from following */ ! 221: if (target[strlen (target) - 1] != '/' && nm[0] != '/' && nm[0]) ! 222: strcat (target, "/"); ! 223: } ! 224: ! 225: strcat (target, nm); ! 226: ! 227: /* Now canonicalize by removing /. and /foo/.. if they appear */ ! 228: ! 229: p = target; ! 230: o = target; ! 231: ! 232: while (*p) ! 233: { ! 234: if (*p != '/') ! 235: { ! 236: *o++ = *p++; ! 237: } ! 238: else if (!strncmp (p, "//", 2) ! 239: #ifdef APOLLO ! 240: /* // at start of filename is meaningful in Apollo system */ ! 241: && o != target ! 242: #endif /* APOLLO */ ! 243: ) ! 244: { ! 245: o = target; ! 246: p++; ! 247: } ! 248: else if (p[0] == '/' && p[1] == '.' && ! 249: (p[2] == '/' || p[2] == 0)) ! 250: p += 2; ! 251: else if (!strncmp (p, "/..", 3) ! 252: && (p[3] == '/' || p[3] == 0)) ! 253: { ! 254: while (o != target && *--o != '/'); ! 255: p += 3; ! 256: } ! 257: else ! 258: { ! 259: *o++ = *p++; ! 260: } ! 261: } ! 262: ! 263: return make_string (target, o - target); ! 264: } ! 265: ! 266: DEFUN ("substitute-in-file-name", Fsubstitute_in_file_name, ! 267: Ssubstitute_in_file_name, 1, 1, 0, ! 268: "Substitute environment variables referred to in STRING.\n\ ! 269: A $ begins a request to substitute; the env variable name is\n\ ! 270: the alphanumeric characters after the $, or else is surrounded by braces.\n\ ! 271: If a ~ appears following a /, everything through that / is discarded.") ! 272: (string) ! 273: Lisp_Object string; ! 274: { ! 275: unsigned char *nm; ! 276: ! 277: register unsigned char *s, *p, *o, *x, *endp; ! 278: unsigned char *target; ! 279: int total = 0; ! 280: unsigned char *xnm; ! 281: ! 282: CHECK_STRING (string, 0); ! 283: ! 284: nm = XSTRING (string)->data; ! 285: endp = nm + XSTRING (string)->size; ! 286: ! 287: /* If /~ or // appears, discard everything through first slash. */ ! 288: ! 289: for (p = nm; p != endp; p++) ! 290: { ! 291: if ((p[0] == '~' || ! 292: #ifdef APOLLO ! 293: /* // at start of file name is meaningful in Apollo system */ ! 294: (p[0] == '/' && p - 1 != nm) ! 295: #else /* not APOLLO */ ! 296: p[0] == '/' ! 297: #endif /* not APOLLO */ ! 298: ) ! 299: && p != nm && p[-1] == '/') ! 300: { ! 301: nm = p; ! 302: total = 1; ! 303: } ! 304: } ! 305: ! 306: /* See if any variables are substituted into the string ! 307: and find the total length of their values in `total' */ ! 308: ! 309: for (p = nm; p != endp;) ! 310: if (*p == '$') ! 311: { ! 312: p++; ! 313: if (p == endp) goto badsubst; ! 314: if (*p == '{') ! 315: { ! 316: o = ++p; ! 317: while (p != endp && *p != '}') p++; ! 318: if (*p != '}') goto missingclose; ! 319: s = p; ! 320: } ! 321: else ! 322: { ! 323: o = p; ! 324: while (p != endp && isalnum(*p)) p++; ! 325: s = p; ! 326: } ! 327: ! 328: /* Copy out the variable name */ ! 329: target = (unsigned char *) alloca (s - o + 1); ! 330: strncpy (target, o, s - o); ! 331: target[s - o] = 0; ! 332: ! 333: /* Get variable value */ ! 334: o = (unsigned char *) getenv (target); ! 335: /* The presence of this code makes vax 5.0 crash, for reasons yet unknown */ ! 336: #if 0 ! 337: #ifdef USG ! 338: if (!o && !strcmp (target, "USER")) ! 339: o = (unsigned char *) getenv ("LOGNAME"); ! 340: #endif /* USG */ ! 341: #endif /* 0 */ ! 342: if (!o) ! 343: goto badvar; ! 344: total += strlen (o); ! 345: } ! 346: else p++; ! 347: ! 348: if (!total) ! 349: return string; ! 350: ! 351: /* If substitution required, recopy the string and do it */ ! 352: /* Make space in stack frame for the new copy */ ! 353: xnm = (unsigned char *) alloca (XSTRING (string)->size + total + 1); ! 354: x = xnm; ! 355: ! 356: /* Copy the rest of the name through, replacing $ constructs with values */ ! 357: for (p = nm; *p;) ! 358: if (*p == '$') ! 359: { ! 360: p++; ! 361: if (p == endp) goto badsubst; ! 362: if (*p == '{') ! 363: { ! 364: o = ++p; ! 365: while (p != endp && *p != '}') p++; ! 366: if (*p != '}') goto missingclose; ! 367: s = p++; ! 368: } ! 369: else ! 370: { ! 371: o = p; ! 372: while (p != endp && isalnum(*p)) p++; ! 373: s = p; ! 374: } ! 375: ! 376: /* Copy out the variable name */ ! 377: target = (unsigned char *) alloca (s - o + 1); ! 378: strncpy (target, o, s - o); ! 379: target[s - o] = 0; ! 380: ! 381: /* Get variable value */ ! 382: o = (unsigned char *) getenv (target); ! 383: /* The presence of this code makes vax 5.0 crash, for reasons yet unknown */ ! 384: #if 0 ! 385: #ifdef USG ! 386: if (!o && !strcmp (target, "USER")) ! 387: o = (unsigned char *) getenv ("LOGNAME"); ! 388: #endif /* USG */ ! 389: #endif /* 0 */ ! 390: if (!o) ! 391: goto badvar; ! 392: ! 393: strcpy (x, o); ! 394: x += strlen (o); ! 395: } ! 396: else ! 397: *x++ = *p++; ! 398: ! 399: *x = 0; ! 400: ! 401: /* If /~ or // appears, discard everything through first slash. */ ! 402: ! 403: for (p = xnm; p != x; p++) ! 404: if ((p[0] == '~' || ! 405: #ifdef APOLLO ! 406: /* // at start of file name is meaningful in Apollo system */ ! 407: (p[0] == '/' && p - 1 != xnm) ! 408: #else /* not APOLLO */ ! 409: p[0] == '/' ! 410: #endif /* not APOLLO */ ! 411: ) ! 412: && p != nm && p[-1] == '/') ! 413: xnm = p; ! 414: ! 415: return make_string (xnm, x - xnm); ! 416: ! 417: badsubst: ! 418: error ("Bad format environment-variable substitution"); ! 419: missingclose: ! 420: error ("Missing \"}\" in environment-variable substitution"); ! 421: badvar: ! 422: error ("Substituting nonexistent environment variable %s", target); ! 423: ! 424: /* NOTREACHED */ ! 425: } ! 426: ! 427: barf_or_query_if_file_exists (absname, querystring) ! 428: Lisp_Object absname; ! 429: unsigned char *querystring; ! 430: { ! 431: Lisp_Object tem; ! 432: if (access (XSTRING (absname)->data, 4) >= 0) ! 433: if (!(Finteractive_p ()) || ! 434: (tem = Fyes_or_no_p ! 435: (format1 ("File %s already exists; %s anyway? ", ! 436: XSTRING (absname)->data, querystring)), ! 437: NULL (tem))) ! 438: Fsignal (Qfile_already_exists, Fcons (absname, Qnil)); ! 439: return; ! 440: } ! 441: ! 442: DEFUN ("copy-file", Fcopy_file, Scopy_file, 2, 3, ! 443: "fCopy file: \nFCopy %s to file: ", ! 444: "Copy FILE to NEWNAME. Both args strings.\n\ ! 445: Signals a file-already-exists error if NEWNAME already exists,\n\ ! 446: unless a third argument OK-IF-ALREADY-EXISTS is supplied and non-nil.") ! 447: (filename, newname, ok_if_already_exists) ! 448: Lisp_Object filename, newname, ok_if_already_exists; ! 449: { ! 450: int ifd, ofd, n; ! 451: char buf[2048]; ! 452: struct stat st; ! 453: ! 454: CHECK_STRING (filename, 0); ! 455: CHECK_STRING (newname, 1); ! 456: filename = Fexpand_file_name (filename, Qnil); ! 457: newname = Fexpand_file_name (newname, Qnil); ! 458: if (NULL (ok_if_already_exists)) ! 459: barf_or_query_if_file_exists (newname, "copy to it"); ! 460: ! 461: ifd = open (XSTRING (filename)->data, 0); ! 462: if (ifd < 0) ! 463: report_file_error ("Opening input file", Fcons (filename, Qnil)); ! 464: ! 465: ofd = creat (XSTRING (newname)->data, 0666); ! 466: if (ofd < 0) ! 467: { ! 468: close (ifd); ! 469: report_file_error ("Opening output file", Fcons (newname, Qnil)); ! 470: } ! 471: ! 472: while ((n = read (ifd, buf, sizeof buf)) > 0) ! 473: write (ofd, buf, n); ! 474: ! 475: if (fstat (ifd, &st) >= 0) ! 476: #if defined (BSD) && !defined (BSD4_1) ! 477: fchmod (ofd, st.st_mode & 07777); ! 478: #else ! 479: chmod (XSTRING (newname)->data, st.st_mode & 07777); ! 480: #endif ! 481: ! 482: close (ifd); ! 483: close (ofd); ! 484: return Qnil; ! 485: } ! 486: ! 487: DEFUN ("delete-file", Fdelete_file, Sdelete_file, 1, 1, "fDelete file: ", ! 488: "Delete specified file. One argument, a file name string.\n\ ! 489: If file has multiple names, it continues to exist with the other names.") ! 490: (filename) ! 491: Lisp_Object filename; ! 492: { ! 493: CHECK_STRING (filename, 0); ! 494: filename = Fexpand_file_name (filename, Qnil); ! 495: if (0 > unlink (XSTRING (filename)->data)) ! 496: report_file_error ("Removing old name", Flist (1, &filename)); ! 497: return Qnil; ! 498: } ! 499: ! 500: DEFUN ("rename-file", Frename_file, Srename_file, 2, 3, ! 501: "fRename file: \nFRename %s to file: ", ! 502: "Rename FILE as NEWNAME. Both args strings.\n\ ! 503: If file has names other than FILE, it continues to have those names.\n\ ! 504: Signals a file-already-exists error if NEWNAME already exists\n\ ! 505: unless optional third argument OK-IF-ALREADY-EXISTS is non-nil.") ! 506: (filename, newname, ok_if_already_exists) ! 507: Lisp_Object filename, newname, ok_if_already_exists; ! 508: { ! 509: extern int errno; ! 510: #ifdef NO_ARG_ARRAY ! 511: Lisp_Object args[2]; ! 512: #endif ! 513: ! 514: CHECK_STRING (filename, 0); ! 515: CHECK_STRING (newname, 1); ! 516: filename = Fexpand_file_name (filename, Qnil); ! 517: newname = Fexpand_file_name (newname, Qnil); ! 518: if (NULL (ok_if_already_exists)) ! 519: barf_or_query_if_file_exists (newname, "rename to it"); ! 520: #ifndef BSD4_1 ! 521: if (0 > rename (XSTRING (filename)->data, XSTRING (newname)->data)) ! 522: #else ! 523: if (0 > link (XSTRING (filename)->data, XSTRING (newname)->data) ! 524: || 0 > unlink (XSTRING (filename)->data)) ! 525: #endif ! 526: { ! 527: if (errno == EXDEV) ! 528: { ! 529: Fcopy_file (filename, newname, ok_if_already_exists); ! 530: Fdelete_file (filename); ! 531: } ! 532: else ! 533: #ifdef NO_ARG_ARRAY ! 534: { ! 535: args[0] = filename; ! 536: args[1] = newname; ! 537: report_file_error ("Renaming", Flist (2, args)); ! 538: } ! 539: #else ! 540: report_file_error ("Renaming", Flist (2, &filename)); ! 541: #endif ! 542: } ! 543: return Qnil; ! 544: } ! 545: ! 546: DEFUN ("add-name-to-file", Fadd_name_to_file, Sadd_name_to_file, 2, 3, ! 547: "fAdd name to file: \nFName to add to %s: ", ! 548: "Give FILE additional name NEWNAME. Both args strings.\n\ ! 549: Signals a file-already-exists error if NEWNAME already exists\n\ ! 550: unlesss optional third argument OK-IF-ALREADY-EXISTS is non-nil.") ! 551: (filename, newname, ok_if_already_exists) ! 552: Lisp_Object filename, newname, ok_if_already_exists; ! 553: { ! 554: #ifdef NO_ARG_ARRAY ! 555: Lisp_Object args[2]; ! 556: #endif ! 557: ! 558: CHECK_STRING (filename, 0); ! 559: CHECK_STRING (newname, 1); ! 560: filename = Fexpand_file_name (filename, Qnil); ! 561: newname = Fexpand_file_name (newname, Qnil); ! 562: if (NULL (ok_if_already_exists)) ! 563: barf_or_query_if_file_exists (newname, "make it a new name"); ! 564: unlink (XSTRING (newname)->data); ! 565: if (0 > link (XSTRING (filename)->data, XSTRING (newname)->data)) ! 566: { ! 567: #ifdef NO_ARG_ARRAY ! 568: args[0] = filename; ! 569: args[1] = newname; ! 570: report_file_error ("Adding new name", Flist (2, args)); ! 571: #else ! 572: report_file_error ("Adding new name", Flist (2, &filename)); ! 573: #endif ! 574: } ! 575: ! 576: return Qnil; ! 577: } ! 578: ! 579: #ifdef S_IFLNK ! 580: DEFUN ("make-symbolic-link", Fmake_symbolic_link, Smake_symbolic_link, 2, 3, ! 581: "FMake symbolic link to file: \nFMake symbolic link to file %s: ", ! 582: "Make a symbolic link to FILENAME, named LINKNAME. Both args strings.\n\ ! 583: Signals a file-already-exists error if NEWNAME already exists\n\ ! 584: unlesss optional third argument OK-IF-ALREADY-EXISTS is non-nil.") ! 585: ! 586: (filename, newname, ok_if_already_exists) ! 587: Lisp_Object filename, newname, ok_if_already_exists; ! 588: { ! 589: #ifdef NO_ARG_ARRAY ! 590: Lisp_Object args[2]; ! 591: #endif ! 592: ! 593: CHECK_STRING (filename, 0); ! 594: CHECK_STRING (newname, 1); ! 595: filename = Fexpand_file_name (filename, Qnil); ! 596: newname = Fexpand_file_name (newname, Qnil); ! 597: if (NULL (ok_if_already_exists)) ! 598: barf_or_query_if_file_exists (newname, "make it a link"); ! 599: if (0 > symlink (XSTRING (filename)->data, XSTRING (newname)->data)) ! 600: { ! 601: #ifdef NO_ARG_ARRAY ! 602: args[0] = filename; ! 603: args[1] = newname; ! 604: report_file_error ("Making symbolic link", Flist (2, args)); ! 605: #else ! 606: report_file_error ("Making symbolic link", Flist (2, &filename)); ! 607: #endif ! 608: } ! 609: return Qnil; ! 610: } ! 611: #endif /* S_IFLNK */ ! 612: ! 613: DEFUN ("file-exists-p", Ffile_exists_p, Sfile_exists_p, 1, 1, 0, ! 614: "Return t if file FILENAME exists and you can read it.\n\ ! 615: Use file-attributes to check for existence not caring about readability.") ! 616: (filename) ! 617: Lisp_Object filename; ! 618: { ! 619: Lisp_Object abspath; ! 620: ! 621: CHECK_STRING (filename, 0); ! 622: abspath = Fexpand_file_name (filename, Qnil); ! 623: return (access (XSTRING (abspath)->data, 4) >= 0) ? Qt : Qnil; ! 624: } ! 625: ! 626: DEFUN ("file-writable-p", Ffile_writable_p, Sfile_writable_p, 1, 1, 0, ! 627: "Return t if file FILENAME can be written or created by you.") ! 628: (filename) ! 629: Lisp_Object filename; ! 630: { ! 631: Lisp_Object abspath, dir; ! 632: ! 633: CHECK_STRING (filename, 0); ! 634: abspath = Fexpand_file_name (filename, Qnil); ! 635: if (access (XSTRING (abspath)->data, 0) >= 0) ! 636: return (access (XSTRING (abspath)->data, 2) >= 0) ? Qt : Qnil; ! 637: dir = Ffile_name_directory (abspath); ! 638: return (access (!NULL (dir) ? (char *) XSTRING (dir)->data : "", 2) >= 0 ! 639: ? Qt : Qnil); ! 640: } ! 641: ! 642: DEFUN ("file-symlink-p", Ffile_symlink_p, Sfile_symlink_p, 1, 1, 0, ! 643: "If file FILENAME is the name of a symbolic link\n\ ! 644: returns the name of the file to which it is linked.\n\ ! 645: Otherwise returns NIL.") ! 646: (filename) ! 647: Lisp_Object filename; ! 648: { ! 649: #ifdef S_IFLNK ! 650: char *buf; ! 651: int bufsize; ! 652: int valsize; ! 653: Lisp_Object val; ! 654: ! 655: CHECK_STRING (filename, 0); ! 656: ! 657: bufsize = 100; ! 658: while (1) ! 659: { ! 660: buf = (char *) xmalloc (bufsize); ! 661: bzero (buf, bufsize); ! 662: valsize = readlink (XSTRING (filename)->data, buf, bufsize); ! 663: if (valsize < bufsize) break; ! 664: /* Buffer was not long enough */ ! 665: free (buf); ! 666: bufsize *= 2; ! 667: } ! 668: if (valsize == -1) ! 669: { ! 670: free (buf); ! 671: return Qnil; ! 672: } ! 673: val = make_string (buf, valsize); ! 674: free (buf); ! 675: return val; ! 676: #else /* not S_IFLNK */ ! 677: return Qnil; ! 678: #endif /* not S_IFLNK */ ! 679: } ! 680: ! 681: DEFUN ("file-directory-p", Ffile_directory_p, Sfile_directory_p, 1, 1, 0, ! 682: "Return t if file FILENAME is the name of a directory.") ! 683: (filename) ! 684: Lisp_Object filename; ! 685: { ! 686: register Lisp_Object abspath; ! 687: struct stat st; ! 688: ! 689: abspath = Fexpand_file_name (filename, bf_cur->directory); ! 690: /* Remove final slash, if any (unless path is root). ! 691: stat behaves differently depending! */ ! 692: if (XSTRING (abspath)->size > 1 ! 693: && XSTRING (abspath)->data[XSTRING (abspath)->size - 1] == '/') ! 694: { ! 695: if (EQ (abspath, filename)) ! 696: abspath = Fcopy_sequence (abspath); ! 697: XSTRING (abspath)->data[XSTRING (abspath)->size - 1] = 0; ! 698: } ! 699: ! 700: if (stat (XSTRING (abspath)->data, &st) < 0) ! 701: return Qnil; ! 702: return (st.st_mode & S_IFMT) == S_IFDIR ? Qt : Qnil; ! 703: } ! 704: ! 705: DEFUN ("file-modes", Ffile_modes, Sfile_modes, 1, 1, 0, ! 706: "Return mode bits of FILE, as an integer.") ! 707: (filename) ! 708: Lisp_Object filename; ! 709: { ! 710: Lisp_Object abspath; ! 711: struct stat st; ! 712: ! 713: abspath = Fexpand_file_name (filename, bf_cur->directory); ! 714: ! 715: /* Remove final slash, if any (unless path is the root). ! 716: stat behaves differently depending! */ ! 717: if (XSTRING (abspath)->size > 1 ! 718: && XSTRING (abspath)->data[XSTRING (abspath)->size - 1] == '/') ! 719: { ! 720: if (EQ (abspath, filename)) ! 721: abspath = Fcopy_sequence (abspath); ! 722: XSTRING (abspath)->data[XSTRING (abspath)->size - 1] = 0; ! 723: } ! 724: ! 725: if (stat (XSTRING (abspath)->data, &st) < 0) ! 726: return Qnil; ! 727: return make_number (st.st_mode & 07777); ! 728: } ! 729: ! 730: DEFUN ("set-file-modes", Fset_file_modes, Sset_file_modes, 2, 2, 0, ! 731: "Set mode bits of FILE to MODE (an integer).\n\ ! 732: Only the 12 low bits of MODE are used.") ! 733: (filename, mode) ! 734: Lisp_Object filename, mode; ! 735: { ! 736: Lisp_Object abspath; ! 737: ! 738: abspath = Fexpand_file_name (filename, bf_cur->directory); ! 739: CHECK_NUMBER (mode, 1); ! 740: if (chmod (XSTRING (abspath)->data, XINT (mode)) < 0) ! 741: report_file_error ("Doing chmod", Fcons (abspath, Qnil)); ! 742: return Qnil; ! 743: } ! 744: ! 745: close_file_unwind (fd) ! 746: Lisp_Object fd; ! 747: { ! 748: close (XFASTINT (fd)); ! 749: } ! 750: ! 751: DEFUN ("insert-file-contents", Finsert_file_contents, Sinsert_file_contents, ! 752: 1, 2, 0, ! 753: "Insert contents of file FILENAME after point.\n\ ! 754: Returns list of absolute pathname and length of data inserted.\n\ ! 755: If second argument VISIT is non-nil, the buffer's\n\ ! 756: visited filename and last save file modtime are set,\n\ ! 757: and it is marked unmodified.") ! 758: (filename, visit) ! 759: Lisp_Object filename, visit; ! 760: { ! 761: struct stat st; ! 762: register int fd; ! 763: register int n, i; ! 764: int count = specpdl_ptr - specpdl; ! 765: ! 766: if (!NULL (bf_cur->read_only)) ! 767: Fbarf_if_buffer_read_only(); ! 768: ! 769: CHECK_STRING (filename, 0); ! 770: filename = Fexpand_file_name (filename, Qnil); ! 771: ! 772: if (stat (XSTRING (filename)->data, &st) < 0 ! 773: || (fd = open (XSTRING (filename)->data, 0)) < 0) ! 774: report_file_error ("Opening input file", Fcons (filename, Qnil)); ! 775: ! 776: record_unwind_protect (close_file_unwind, make_number (fd)); ! 777: ! 778: if (NULL (visit)) ! 779: prepare_to_modify_buffer (); ! 780: ! 781: RecordInsert (point, st.st_size); ! 782: bf_modified++; ! 783: ! 784: GapTo (point); ! 785: if (bf_gap < st.st_size) ! 786: make_gap (st.st_size); ! 787: ! 788: n = 0; ! 789: while ((i = read (fd, bf_p1 + bf_s1 + 1, st.st_size - n)) > 0) ! 790: { ! 791: bf_s1 += i; ! 792: bf_gap -= i; ! 793: bf_p2 -= i; ! 794: n += i; ! 795: } ! 796: ! 797: if (!NULL (visit)) ! 798: DoneIsDone (); ! 799: ! 800: close (fd); ! 801: /* Discard the unwind protect */ ! 802: specpdl_ptr = specpdl + count; ! 803: ! 804: if (i < 0) ! 805: error ("IO error reading %s", XSTRING (filename)->data); ! 806: ! 807: if (!NULL (visit)) ! 808: { ! 809: bf_cur->modtime = st.st_mtime; ! 810: bf_cur->save_modified = bf_modified; ! 811: bf_cur->auto_save_modified = bf_modified; ! 812: XFASTINT (bf_cur->save_length) = NumCharacters; ! 813: #ifdef CLASH_DETECTION ! 814: if (!NULL (bf_cur->filename)) ! 815: unlock_file (bf_cur->filename); ! 816: unlock_file (filename); ! 817: #endif /* CLASH_DETECTION */ ! 818: bf_cur->filename = filename; ! 819: } ! 820: ! 821: return Fcons (filename, Fcons (make_number (st.st_size), Qnil)); ! 822: } ! 823: ! 824: DEFUN ("write-region", Fwrite_region, Swrite_region, 3, 5, ! 825: "r\nFWrite region to file: ", ! 826: "Write current region into specified file.\n\ ! 827: When called from a program, takes three arguments:\n\ ! 828: START, END and FILENAME. START and END are buffer positions.\n\ ! 829: Optional fourth argument APPEND if non-nil means\n\ ! 830: append to existing file contents (if any).\n\ ! 831: Optional fifth argument VISIT if t means\n\ ! 832: set last-save-file-modtime of buffer to this file's modtime\n\ ! 833: and mark buffer not modified.\n\ ! 834: If VISIT is neither t nor nil, it means do not print\n\ ! 835: the \"Wrote file\" message.") ! 836: (start, end, filename, append, visit) ! 837: Lisp_Object start, end, filename, append, visit; ! 838: { ! 839: register int fd; ! 840: int failure = 0; ! 841: unsigned char *fn; ! 842: struct stat st; ! 843: int tem; ! 844: int count = specpdl_ptr - specpdl; ! 845: ! 846: /* Special kludge to simplify auto-saving */ ! 847: if (NULL (start)) ! 848: { ! 849: XFASTINT (start) = 1; ! 850: XFASTINT (end) = 1 + bf_s1 + bf_s2; ! 851: } ! 852: else ! 853: validate_region (&start, &end); ! 854: ! 855: filename = Fexpand_file_name (filename, Qnil); ! 856: fn = XSTRING (filename)->data; ! 857: ! 858: #ifdef CLASH_DETECTION ! 859: if (!auto_saving) ! 860: lock_file (filename); ! 861: #endif /* CLASH_DETECTION */ ! 862: ! 863: fd = -1; ! 864: if (!NULL (append)) ! 865: fd = open (fn, 1); ! 866: ! 867: if (fd < 0) ! 868: fd = creat (fn, 0666); ! 869: ! 870: if (fd < 0) ! 871: { ! 872: #ifdef CLASH_DETECTION ! 873: if (!auto_saving) unlock_file (filename); ! 874: #endif /* CLASH_DETECTION */ ! 875: report_file_error ("Opening output file", Fcons (filename, Qnil)); ! 876: } ! 877: ! 878: record_unwind_protect (close_file_unwind, make_number (fd)); ! 879: ! 880: if (!NULL (append)) ! 881: if (lseek (fd, 0, 2) < 0) ! 882: { ! 883: #ifdef CLASH_DETECTION ! 884: if (!auto_saving) unlock_file (filename); ! 885: #endif /* CLASH_DETECTION */ ! 886: report_file_error ("Lseek error", Fcons (filename, Qnil)); ! 887: } ! 888: ! 889: failure = 0; ! 890: if (XINT (start) != XINT (end)) ! 891: { ! 892: if (XINT (start) - 1 < bf_s1) ! 893: failure = 0 > e_write (fd, &CharAt (XINT (start)), ! 894: min (bf_s1 + 1, XINT (end)) - XINT (start)); ! 895: ! 896: if (XINT (end) - 1 > bf_s1 && !failure) ! 897: { ! 898: tem = max (XINT (start), bf_s1 + 1); ! 899: failure = 0 > e_write (fd, &CharAt (tem), XINT (end) - tem); ! 900: } ! 901: } ! 902: ! 903: fstat (fd, &st); ! 904: close (fd); ! 905: /* Discard the unwind protect */ ! 906: specpdl_ptr = specpdl + count; ! 907: ! 908: #ifdef CLASH_DETECTION ! 909: if (!auto_saving) ! 910: unlock_file (filename); ! 911: #endif /* CLASH_DETECTION */ ! 912: ! 913: if (failure) ! 914: error ("IO error writing %s", fn); ! 915: ! 916: if (EQ (visit, Qt)) ! 917: { ! 918: bf_cur->modtime = st.st_mtime; ! 919: bf_cur->save_modified = bf_modified; ! 920: XFASTINT (bf_cur->save_length) = NumCharacters; ! 921: bf_cur->filename = filename; ! 922: } ! 923: else if (!NULL (visit)) ! 924: return Qnil; ! 925: ! 926: if (!auto_saving) ! 927: message ("Wrote %s", fn); ! 928: ! 929: return Qnil; ! 930: } ! 931: ! 932: int ! 933: e_write (fd, addr, len) ! 934: int fd; ! 935: register char *addr; ! 936: register int len; ! 937: { ! 938: char buf[1024]; ! 939: register char *p, *end; ! 940: ! 941: if (!EQ (bf_cur->selective_display, Qt)) ! 942: return write (fd, addr, len) - len; ! 943: else ! 944: { ! 945: p = buf; ! 946: end = p + sizeof buf; ! 947: while (len--) ! 948: { ! 949: if (p == end) ! 950: { ! 951: if (write (fd, buf, sizeof buf) != sizeof buf) ! 952: return -1; ! 953: p = buf; ! 954: } ! 955: *p = *addr++; ! 956: if (*p++ == '\015') ! 957: p[-1] = '\n'; ! 958: } ! 959: if (p != buf) ! 960: if (write (fd, buf, p - buf) != p - buf) ! 961: return -1; ! 962: } ! 963: return 0; ! 964: } ! 965: ! 966: DEFUN ("verify-visited-file-modtime", Fverify_visited_file_modtime, ! 967: Sverify_visited_file_modtime, 1, 1, 0, ! 968: "Return t if last mod time of BUF's visited file matches what BUF records.\n\ ! 969: This means that the file has not been changed since it was visited or saved.") ! 970: (buf) ! 971: Lisp_Object buf; ! 972: { ! 973: struct buffer *b = XBUFFER (buf); ! 974: struct stat st; ! 975: ! 976: CHECK_BUFFER (buf, 0); ! 977: ! 978: if (XTYPE (b->filename) != Lisp_String) return Qt; ! 979: if (!b->modtime) return Qt; ! 980: if (stat (XSTRING (b->filename)->data, &st) < 0) ! 981: return Qnil; ! 982: if (st.st_mtime != b->modtime) ! 983: return Qnil; ! 984: return Qt; ! 985: } ! 986: ! 987: DEFUN ("clear-visited-file-modtime", Fclear_visited_file_modtime, ! 988: Sclear_visited_file_modtime, 0, 0, 0, ! 989: "Clear out records of last mod time of visited file.\n\ ! 990: Next attempt to save will certainly not complain of a discrepancy.") ! 991: () ! 992: { ! 993: bf_cur->modtime = 0; ! 994: return Qnil; ! 995: } ! 996: ! 997: Lisp_Object ! 998: auto_save_error () ! 999: { ! 1000: return Qnil; ! 1001: } ! 1002: ! 1003: Lisp_Object ! 1004: auto_save_1 () ! 1005: { ! 1006: return ! 1007: Fwrite_region (Qnil, Qnil, ! 1008: bf_cur->auto_save_file_name, ! 1009: Qnil, Qlambda); ! 1010: } ! 1011: ! 1012: DEFUN ("do-auto-save", Fdo_auto_save, Sdo_auto_save, 0, 1, "", ! 1013: "Auto-save all buffers that need it.\n\ ! 1014: This is all buffers that have auto-saving enabled\n\ ! 1015: and are changed since last auto-saved.\n\ ! 1016: Auto-saving writes the buffer into a file\n\ ! 1017: so that your editing is not lost if the system crashes.\n\ ! 1018: This file is not the file you visited; that changes only when you save.\n\n\ ! 1019: Non-nil argument means do not print any message.") ! 1020: (nomsg) ! 1021: Lisp_Object nomsg; ! 1022: { ! 1023: struct buffer *old = bf_cur, *b; ! 1024: Lisp_Object tail, buf; ! 1025: int auto_saved = 0; ! 1026: char *omessage = minibuf_message; ! 1027: extern MinibufDepth; ! 1028: ! 1029: auto_saving = 1; ! 1030: if (MinibufDepth) ! 1031: nomsg = Qt; ! 1032: ! 1033: bf_cur->text = bf_text; ! 1034: ! 1035: for (tail = Vbuffer_alist; XGCTYPE (tail) == Lisp_Cons; ! 1036: tail = XCONS (tail)->cdr) ! 1037: { ! 1038: buf = XCONS (XCONS (tail)->car)->cdr; ! 1039: b = XBUFFER (buf); ! 1040: /* Check for auto save enabled ! 1041: and file changed since last auto save ! 1042: and file changed since last real save. */ ! 1043: if (XTYPE (b->auto_save_file_name) == Lisp_String ! 1044: && b->save_modified < b->text.modified ! 1045: && b->auto_save_modified < b->text.modified) ! 1046: { ! 1047: if (XFASTINT (b->save_length) * 10 ! 1048: > (b->text.size1 + b->text.size2) * 13) ! 1049: { ! 1050: /* It has shrunk too much; don't chckpoint. */ ! 1051: /*** Should report this to user somehow ***/ ! 1052: continue; ! 1053: } ! 1054: SetBfp (b); ! 1055: if (!auto_saved && NULL (nomsg)) ! 1056: message1 ("Auto-saving..."); ! 1057: internal_condition_case (auto_save_1, Qt, auto_save_error); ! 1058: auto_saved++; ! 1059: b->auto_save_modified = b->text.modified; ! 1060: XFASTINT (bf_cur->save_length) = NumCharacters; ! 1061: SetBfp (old); ! 1062: } ! 1063: } ! 1064: ! 1065: if (auto_saved && NULL (nomsg)) ! 1066: message1 (omessage ? omessage : "Auto-saving...done"); ! 1067: ! 1068: auto_saving = 0; ! 1069: return Qnil; ! 1070: } ! 1071: ! 1072: DEFUN ("set-buffer-auto-saved", Fset_buffer_auto_saved, ! 1073: Sset_buffer_auto_saved, 0, 0, 0, ! 1074: "Mark current buffer as auto-saved with its current text.\n\ ! 1075: No auto-save file will be written until the buffer changes again.") ! 1076: () ! 1077: { ! 1078: bf_cur->auto_save_modified = bf_modified; ! 1079: return Qnil; ! 1080: } ! 1081: ! 1082: DEFUN ("recent-auto-save-p", Frecent_auto_save_p, Srecent_auto_save_p, ! 1083: 0, 0, 0, ! 1084: "Return t if buffer has been auto-saved since last read in or saved.") ! 1085: () ! 1086: { ! 1087: return (bf_cur->save_modified < bf_cur->auto_save_modified) ? Qt : Qnil; ! 1088: } ! 1089: ! 1090: /* Reading and completing file names */ ! 1091: extern Lisp_Object Ffile_name_completion (), Ffile_name_all_completions (); ! 1092: ! 1093: DEFUN ("read-file-name-internal", Fread_file_name_internal, Sread_file_name_internal, ! 1094: 3, 3, 0, ! 1095: "Internal subroutine for read-file-name. Do not call this.") ! 1096: (string, dir, action) ! 1097: Lisp_Object string, dir, action; ! 1098: /* action is nil for complete, t for return list of completions, ! 1099: lambda for verify final value */ ! 1100: { ! 1101: Lisp_Object name, specdir, realdir, val; ! 1102: if (XSTRING (string)->size == 0) ! 1103: { ! 1104: name = string; ! 1105: realdir = dir; ! 1106: if (EQ (action, Qlambda)) ! 1107: return Qnil; ! 1108: } ! 1109: else ! 1110: { ! 1111: string = Fsubstitute_in_file_name (string); ! 1112: name = Ffile_name_nondirectory (string); ! 1113: realdir = Ffile_name_directory (string); ! 1114: if (NULL (realdir)) ! 1115: realdir = dir; ! 1116: else ! 1117: realdir = Fexpand_file_name (realdir, dir); ! 1118: } ! 1119: ! 1120: if (NULL (action)) ! 1121: { ! 1122: specdir = Ffile_name_directory (string); ! 1123: val = Ffile_name_completion (name, realdir); ! 1124: if (XTYPE (val) == Lisp_String && !NULL (specdir)) ! 1125: return concat2 (specdir, val); ! 1126: return val; ! 1127: } ! 1128: if (EQ (action, Qt)) ! 1129: return Ffile_name_all_completions (name, realdir); ! 1130: /* Only other case actually used is ACTION = lambda */ ! 1131: return Ffile_exists_p (string); ! 1132: } ! 1133: ! 1134: DEFUN ("read-file-name", Fread_file_name, Sread_file_name, 1, 4, 0, ! 1135: "Read file name, prompting with PROMPT and completing in directory DIR.\n\ ! 1136: Value is not expanded! You must call expand-file-name yourself.\n\ ! 1137: Default name to DEFAULT if user enters a null string.\n\ ! 1138: Fourth arg MUSTMATCH non-nil means require existing file's name.\n\ ! 1139: Non-nil and non-t means also require confirmation after completion.\n\ ! 1140: DIR defaults to current buffer's directory default.") ! 1141: (prompt, dir, defalt, mustmatch) ! 1142: Lisp_Object prompt, dir, defalt, mustmatch; ! 1143: { ! 1144: Lisp_Object val, insdef, tem; ! 1145: struct gcpro gcpro1, gcpro2; ! 1146: register char *homedir; ! 1147: ! 1148: if (NULL (dir)) ! 1149: dir = bf_cur->directory; ! 1150: if (NULL (defalt)) ! 1151: defalt = bf_cur->filename; ! 1152: ! 1153: /* If dir starts with user's homedir, change that to ~. */ ! 1154: homedir = (char *) getenv ("HOME"); ! 1155: if (XTYPE (dir) == Lisp_String ! 1156: && !strncmp (homedir, XSTRING (dir)->data, strlen (homedir)) ! 1157: && XSTRING (dir)->data[strlen (homedir)] == '/') ! 1158: { ! 1159: dir = make_string (XSTRING (dir)->data + strlen (homedir) - 1, ! 1160: XSTRING (dir)->size - strlen (homedir) + 1); ! 1161: XSTRING (dir)->data[0] = '~'; ! 1162: } ! 1163: ! 1164: if (insert_default_directory) ! 1165: insdef = dir; ! 1166: else ! 1167: insdef = build_string (""); ! 1168: ! 1169: GCPRO2 (insdef, defalt); ! 1170: val = Fcompleting_read (prompt, intern ("read-file-name-internal"), ! 1171: dir, mustmatch, ! 1172: insert_default_directory ? insdef : Qnil); ! 1173: UNGCPRO; ! 1174: if (NULL (val)) ! 1175: error ("No file name specified"); ! 1176: tem = Fstring_equal (val, insdef); ! 1177: if (!NULL (tem) && !NULL (defalt)) ! 1178: return defalt; ! 1179: return Fsubstitute_in_file_name (val); ! 1180: } ! 1181: ! 1182: syms_of_fileio () ! 1183: { ! 1184: Qfile_error = intern ("file-error"); ! 1185: staticpro (&Qfile_error); ! 1186: Qfile_already_exists = intern("file-already-exists"); ! 1187: staticpro (&Qfile_already_exists); ! 1188: ! 1189: Fput (Qfile_error, Qerror_conditions, ! 1190: Fcons (Qfile_error, Fcons (Qerror, Qnil))); ! 1191: Fput (Qfile_error, Qerror_message, ! 1192: build_string ("File error")); ! 1193: ! 1194: Fput (Qfile_already_exists, Qerror_conditions, ! 1195: Fcons (Qfile_already_exists, ! 1196: Fcons (Qfile_error, Fcons (Qerror, Qnil)))); ! 1197: Fput (Qfile_already_exists, Qerror_message, ! 1198: build_string ("File already exists")); ! 1199: ! 1200: DefBoolVar ("insert-default-directory", &insert_default_directory, ! 1201: "*Non-nil means when reading a filename start with default dir in minibuffer."); ! 1202: insert_default_directory = 1; ! 1203: ! 1204: defsubr (&Sfile_name_directory); ! 1205: defsubr (&Sfile_name_nondirectory); ! 1206: defsubr (&Smake_temp_name); ! 1207: defsubr (&Sexpand_file_name); ! 1208: defsubr (&Ssubstitute_in_file_name); ! 1209: defsubr (&Scopy_file); ! 1210: defsubr (&Sdelete_file); ! 1211: defsubr (&Srename_file); ! 1212: defsubr (&Sadd_name_to_file); ! 1213: #ifdef S_IFLNK ! 1214: defsubr (&Smake_symbolic_link); ! 1215: #endif /* S_IFLNK */ ! 1216: defsubr (&Sfile_exists_p); ! 1217: defalias (&Sfile_exists_p, "file-readable-p"); ! 1218: defsubr (&Sfile_writable_p); ! 1219: defsubr (&Sfile_symlink_p); ! 1220: defsubr (&Sfile_directory_p); ! 1221: defsubr (&Sfile_modes); ! 1222: defsubr (&Sset_file_modes); ! 1223: defsubr (&Sinsert_file_contents); ! 1224: defsubr (&Swrite_region); ! 1225: defsubr (&Sverify_visited_file_modtime); ! 1226: defsubr (&Sclear_visited_file_modtime); ! 1227: defsubr (&Sdo_auto_save); ! 1228: defsubr (&Sset_buffer_auto_saved); ! 1229: defsubr (&Srecent_auto_save_p); ! 1230: ! 1231: defsubr (&Sread_file_name_internal); ! 1232: defsubr (&Sread_file_name); ! 1233: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.