UNIXworkcode

1 /******************************************************************************* 2 * * 3 * fileUtils.c -- File utilities for Nirvana applications * 4 * * 5 * Copyright (C) 1999 Mark Edel * 6 * * 7 * This is free software; you can redistribute it and/or modify it under the * 8 * terms of the GNU General Public License as published by the Free Software * 9 * Foundation; either version 2 of the License, or (at your option) any later * 10 * version. In addition, you may distribute versions of this program linked to * 11 * Motif or Open Motif. See README for details. * 12 * * 13 * This software is distributed in the hope that it will be useful, but WITHOUT * 14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * 15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * 16 * for more details. * 17 * * 18 * You should have received a copy of the GNU General Public License along with * 19 * software; if not, write to the Free Software Foundation, Inc., 59 Temple * 20 * Place, Suite 330, Boston, MA 02111-1307 USA * 21 * * 22 * Nirvana Text Editor * 23 * July 28, 1992 * 24 * * 25 * Written by Mark Edel * 26 * * 27 * Modified by: DMR - Ported to VMS (1st stage for Histo-Scope) * 28 * * 29 *******************************************************************************/ 30 31 #ifdef HAVE_CONFIG_H 32 #include "../config.h" 33 #endif 34 35 #include "fileUtils.h" 36 #include "utils.h" 37 #include "nedit_malloc.h" 38 39 #include <stdlib.h> 40 #include <stdio.h> 41 #include <string.h> 42 #include <errno.h> 43 #include <X11/Intrinsic.h> 44 #include <sys/types.h> 45 #include <sys/param.h> 46 #include <sys/stat.h> 47 #include <unistd.h> 48 #include <pwd.h> 49 50 #ifdef HAVE_DEBUG_H 51 #include "../debug.h" 52 #endif 53 54 #ifndef MAXSYMLINKS /* should be defined in <sys/param.h> */ 55 #define MAXSYMLINKS 20 56 #endif 57 58 #define TRUE 1 59 #define FALSE 0 60 61 /* Parameters to algorithm used to auto-detect DOS format files. NEdit will 62 scan up to the lesser of FORMAT_SAMPLE_LINES lines and FORMAT_SAMPLE_CHARS 63 characters of the beginning of the file, checking that all newlines are 64 paired with carriage returns. If even a single counterexample exists, 65 the file is judged to be in Unix format. */ 66 #define FORMAT_SAMPLE_LINES 5 67 #define FORMAT_SAMPLE_CHARS 2000 68 69 static char *nextSlash(char *ptr); 70 static char *prevSlash(char *ptr); 71 static int compareThruSlash(const char *string1, const char *string2); 72 static void copyThruSlash(char **toString, char **fromString); 73 74 75 /* 76 ** Decompose a Unix file name into a file name and a path. 77 ** Return non-zero value if it fails, zero else. 78 ** For now we assume that filename and pathname are at 79 ** least MAXPATHLEN chars long. 80 ** To skip setting filename or pathname pass NULL for that argument. 81 */ 82 int 83 ParseFilename(const char *fullname, char *filename, char *pathname) 84 { 85 int fullLen = strlen(fullname); 86 int i, pathLen, fileLen; 87 88 char *viewExtendPath; 89 int scanStart; 90 91 /* For clearcase version extended paths, slash characters after the "@@/" 92 should be considered part of the file name, rather than the path */ 93 if ((viewExtendPath = strstr(fullname, "@@/")) != NULL) 94 scanStart = viewExtendPath - fullname - 1; 95 else 96 scanStart = fullLen - 1; 97 98 /* find the last slash */ 99 for (i=scanStart; i>=0; i--) { 100 if (fullname[i] == '/') 101 break; 102 } 103 104 /* move chars before / (or ] or :) into pathname,& after into filename */ 105 pathLen = i + 1; 106 fileLen = fullLen - pathLen; 107 if (pathname) { 108 if (pathLen >= MAXPATHLEN) { 109 return 1; 110 } 111 strncpy(pathname, fullname, pathLen); 112 pathname[pathLen] = 0; 113 } 114 if (filename) { 115 if (fileLen >= MAXPATHLEN) { 116 return 2; 117 } 118 strncpy(filename, &fullname[pathLen], fileLen); 119 filename[fileLen] = 0; 120 } 121 122 #ifndef VMS /* UNIX specific... Modify at a later date for VMS */ 123 if(pathname) { 124 if (NormalizePathname(pathname)) { 125 return 1; /* pathname too long */ 126 } 127 pathLen = strlen(pathname); 128 } 129 #endif 130 131 if (filename && pathname && ((pathLen + 1 + fileLen) >= MAXPATHLEN)) { 132 return 1; /* pathname + filename too long */ 133 } 134 return 0; 135 } 136 137 #ifndef VMS 138 139 140 /* 141 ** Expand tilde characters which begin file names as done by the shell 142 ** If it doesn't work just out leave pathname unmodified. 143 ** This implementation is neither fast, nor elegant, nor ... 144 */ 145 int 146 ExpandTilde(char *pathname) 147 { 148 struct passwd *passwdEntry; 149 char username[MAXPATHLEN], temp[MAXPATHLEN]; 150 char *nameEnd; 151 unsigned len_left; 152 153 if (pathname[0] != '~') 154 return TRUE; 155 nameEnd = strchr(&pathname[1], '/'); 156 if (nameEnd == NULL) { 157 nameEnd = pathname + strlen(pathname); 158 } 159 strncpy(username, &pathname[1], nameEnd - &pathname[1]); 160 username[nameEnd - &pathname[1]] = '\0'; 161 /* We might consider to re-use the GetHomeDir() function, 162 but to keep the code more similar for both cases ... */ 163 if (username[0] == '\0') { 164 passwdEntry = getpwuid(getuid()); 165 if ((passwdEntry == NULL) || (*(passwdEntry->pw_dir)== '\0')) { 166 /* This is really serious, so just exit. */ 167 perror("XNEdit/nc: getpwuid() failed "); 168 exit(EXIT_FAILURE); 169 } 170 } 171 else { 172 passwdEntry = getpwnam(username); 173 if ((passwdEntry == NULL) || (*(passwdEntry->pw_dir)== '\0')) { 174 /* username was just an input by the user, this is no 175 indication for some (serious) problems */ 176 return FALSE; 177 } 178 } 179 180 strcpy(temp, passwdEntry->pw_dir); 181 strcat(temp, "/"); 182 len_left= sizeof(temp)-strlen(temp)-1; 183 if (len_left < strlen(nameEnd)) { 184 /* It won't work out */ 185 return FALSE; 186 } 187 strcat(temp, nameEnd); 188 strcpy(pathname, temp); 189 return TRUE; 190 } 191 192 /* 193 * Resolve symbolic links (if any) for the absolute path given in pathIn 194 * and place the resolved absolute path in pathResolved. 195 * - pathIn must contain an absolute path spec. 196 * - pathResolved must point to a buffer of minimum size MAXPATHLEN. 197 * 198 * Returns: 199 * TRUE if pathResolved contains a valid resolved path 200 * OR pathIn is not a symlink (pathResolved will have the same 201 * contents like pathIn) 202 * 203 * FALSE an error occured while trying to resolve the symlink, i.e. 204 * pathIn was no absolute path or the link is a loop. 205 */ 206 int 207 ResolvePath(const char * pathIn, char * pathResolved) 208 { 209 char resolveBuf[MAXPATHLEN], pathBuf[MAXPATHLEN]; 210 char *pathEnd; 211 int rlResult, loops; 212 213 #ifdef NO_READLINK 214 strncpy(pathResolved, pathIn, MAXPATHLEN); 215 /* If there are no links at all, it's a valid "resolved" path */ 216 return TRUE; 217 #else 218 /* !! readlink does NOT recognize loops, i.e. links like file -> ./file */ 219 for (loops=0; loops<MAXSYMLINKS; loops++) { 220 #ifdef UNICOS 221 rlResult=readlink((char *)pathIn, resolveBuf, MAXPATHLEN-1); 222 #else 223 rlResult=readlink(pathIn, resolveBuf, MAXPATHLEN-1); 224 #endif 225 if (rlResult<0) { 226 227 #ifndef __Lynx__ 228 if (errno == EINVAL) 229 #else 230 if (errno == ENXIO) 231 #endif 232 { 233 /* It's not a symlink - we are done */ 234 strncpy(pathResolved, pathIn, MAXPATHLEN); 235 pathResolved[MAXPATHLEN-1] = '\0'; 236 return TRUE; 237 } else { 238 return FALSE; 239 } 240 } else if (rlResult == 0) { 241 return FALSE; 242 } 243 244 resolveBuf[rlResult]=0; 245 246 if (resolveBuf[0]!='/') { 247 strncpy(pathBuf, pathIn, MAXPATHLEN); 248 pathBuf[MAXPATHLEN-1] = '\0'; 249 pathEnd=strrchr(pathBuf, '/'); 250 if (!pathEnd) { 251 return FALSE; 252 } 253 strcpy(pathEnd+1, resolveBuf); 254 } else { 255 strcpy(pathBuf, resolveBuf); 256 } 257 NormalizePathname(pathBuf); 258 pathIn=pathBuf; 259 } 260 261 return FALSE; 262 #endif /* NO_READLINK */ 263 } 264 265 266 /* 267 ** Return 0 if everything's fine. In fact it always return 0... (No it doesn't) 268 ** Capable to handle arbitrary path length (>MAXPATHLEN)! 269 ** 270 ** FIXME: Documentation 271 ** FIXME: Change return value to False and True. 272 */ 273 int NormalizePathname(char *pathname) 274 { 275 /* if this is a relative pathname, prepend current directory */ 276 #ifdef __EMX__ 277 /* OS/2, ...: welcome to the world of drive letters ... */ 278 if (!_fnisabs(pathname)) { 279 #else 280 if (pathname[0] != '/') { 281 #endif 282 char *oldPathname; 283 size_t len; 284 285 /* make a copy of pathname to work from */ 286 oldPathname=(char *)malloc(strlen(pathname)+1); 287 strcpy(oldPathname, pathname); 288 /* get the working directory and prepend to the path */ 289 strcpy(pathname, GetCurrentDir()); 290 291 /* check for trailing slash, or pathname being root dir "/": 292 don't add a second '/' character as this may break things 293 on non-un*x systems */ 294 len = strlen(pathname); /* GetCurrentDir() returns non-NULL value */ 295 296 /* Apart from the fact that people putting conditional expressions in 297 ifs should be caned: How should len ever become 0 if GetCurrentDir() 298 always returns a useful value? 299 FIXME: Check and document GetCurrentDir() return behaviour. */ 300 if (0 == len ? 1 : pathname[len-1] != '/') 301 { 302 strcat(pathname, "/"); 303 } 304 strcat(pathname, oldPathname); 305 free(oldPathname); 306 } 307 308 /* compress out .. and . */ 309 return CompressPathname(pathname); 310 } 311 312 313 /* 314 ** Return 0 if everything's fine, 1 else. 315 ** 316 ** FIXME: Documentation 317 ** FIXME: Change return value to False and True. 318 */ 319 int CompressPathname(char *pathname) 320 { 321 char *buf, *inPtr, *outPtr; 322 struct stat statbuf; 323 324 /* (Added by schwarzenberg) 325 ** replace multiple slashes by a single slash 326 ** (added by yooden) 327 ** Except for the first slash. From the Single UNIX Spec: "A pathname 328 ** that begins with two successive slashes may be interpreted in an 329 ** implementation-dependent manner" 330 */ 331 inPtr = pathname; 332 buf = (char*) malloc(strlen(pathname) + 2); 333 outPtr = buf; 334 *outPtr++ = *inPtr++; 335 while (*inPtr) 336 { 337 *outPtr = *inPtr++; 338 if ('/' == *outPtr) 339 { 340 while ('/' == *inPtr) 341 { 342 inPtr++; 343 } 344 } 345 outPtr++; 346 } 347 *outPtr=0; 348 strcpy(pathname, buf); 349 350 /* compress out . and .. */ 351 inPtr = pathname; 352 outPtr = buf; 353 /* copy initial / */ 354 copyThruSlash(&outPtr, &inPtr); 355 while (inPtr != NULL) { 356 /* if the next component is "../", remove previous component */ 357 if (compareThruSlash(inPtr, "../")) { 358 *outPtr = 0; 359 /* If the ../ is at the beginning, or if the previous component 360 is a symbolic link, preserve the ../. It is not valid to 361 compress ../ when the previous component is a symbolic link 362 because ../ is relative to where the link points. If there's 363 no S_ISLNK macro, assume system does not do symbolic links. */ 364 #ifdef S_ISLNK 365 if(outPtr-1 == buf || (lstat(buf, &statbuf) == 0 && 366 S_ISLNK(statbuf.st_mode))) { 367 copyThruSlash(&outPtr, &inPtr); 368 } else 369 #endif 370 { 371 /* back up outPtr to remove last path name component */ 372 outPtr = prevSlash(outPtr); 373 inPtr = nextSlash(inPtr); 374 } 375 } else if (compareThruSlash(inPtr, "./")) { 376 /* don't copy the component if it's the redundant "./" */ 377 inPtr = nextSlash(inPtr); 378 } else { 379 /* copy the component to outPtr */ 380 copyThruSlash(&outPtr, &inPtr); 381 } 382 } 383 /* updated pathname with the new value */ 384 if (strlen(buf)>MAXPATHLEN) { 385 fprintf(stderr, "nedit: CompressPathname(): file name too long %s\n", 386 pathname); 387 free(buf); 388 return 1; 389 } 390 else { 391 strcpy(pathname, buf); 392 free(buf); 393 return 0; 394 } 395 } 396 397 static char 398 *nextSlash(char *ptr) 399 { 400 for(; *ptr!='/'; ptr++) { 401 if (*ptr == '\0') 402 return NULL; 403 } 404 return ptr + 1; 405 } 406 407 static char 408 *prevSlash(char *ptr) 409 { 410 for(ptr -= 2; *ptr!='/'; ptr--); 411 return ptr + 1; 412 } 413 414 static int 415 compareThruSlash(const char *string1, const char *string2) 416 { 417 while (TRUE) { 418 if (*string1 != *string2) 419 return FALSE; 420 if (*string1 =='\0' || *string1=='/') 421 return TRUE; 422 string1++; 423 string2++; 424 } 425 } 426 427 static void 428 copyThruSlash(char **toString, char **fromString) 429 { 430 char *to = *toString; 431 char *from = *fromString; 432 433 while (TRUE) { 434 *to = *from; 435 if (*from =='\0') { 436 *fromString = NULL; 437 return; 438 } 439 if (*from=='/') { 440 *toString = to + 1; 441 *fromString = from + 1; 442 return; 443 } 444 from++; 445 to++; 446 } 447 } 448 449 #else /* VMS */ 450 451 /* 452 ** Dummy versions of the public functions for VMS. 453 */ 454 455 /* 456 ** Return 0 if everything's fine, 1 else. 457 */ 458 int NormalizePathname(char *pathname) 459 { 460 return 0; 461 } 462 463 /* 464 ** Return 0 if everything's fine, 1 else. 465 */ 466 int CompressPathname(char *pathname) 467 { 468 return 0; 469 } 470 471 /* 472 * Returns: 473 * TRUE if no error occured 474 * 475 * FALSE if an error occured. 476 */ 477 int ResolvePath(const char * pathIn, char * pathResolved) 478 { 479 if (strlen(pathIn) < MAXPATHLEN) { 480 strcpy(pathResolved, pathIn); 481 return TRUE; 482 } else { 483 return FALSE; 484 } 485 } 486 487 #endif /* VMS */ 488 489 /* 490 ** Return the trailing 'n' no. of path components 491 */ 492 const char 493 *GetTrailingPathComponents(const char* path, 494 int noOfComponents) 495 { 496 /* Start from the rear */ 497 const char* ptr = path + strlen(path); 498 int count = 0; 499 500 while (--ptr > path) { 501 if (*ptr == '/') { 502 if (count++ == noOfComponents) { 503 break; 504 } 505 } 506 } 507 return(ptr); 508 } 509 510 /* 511 ** Samples up to a maximum of FORMAT_SAMPLE_LINES lines and FORMAT_SAMPLE_CHARS 512 ** characters, to determine whether fileString represents a MS DOS or Macintosh 513 ** format file. If there's ANY ambiguity (a newline in the sample not paired 514 ** with a return in an otherwise DOS looking file, or a newline appearing in 515 ** the sampled portion of a Macintosh looking file), the file is judged to be 516 ** Unix format. 517 */ 518 int FormatOfFile(const char *fileString) 519 { 520 const char *p; 521 int nNewlines = 0, nReturns = 0; 522 523 for (p=fileString; *p!='\0' && p < fileString + FORMAT_SAMPLE_CHARS; p++) { 524 if (*p == '\n') { 525 nNewlines++; 526 if (p == fileString || *(p-1) != '\r') 527 return UNIX_FILE_FORMAT; 528 if (nNewlines >= FORMAT_SAMPLE_LINES) 529 return DOS_FILE_FORMAT; 530 } else if (*p == '\r') 531 nReturns++; 532 } 533 if (nNewlines > 0) 534 return DOS_FILE_FORMAT; 535 if (nReturns > 0) 536 return MAC_FILE_FORMAT; 537 return UNIX_FILE_FORMAT; 538 } 539 540 /* 541 ** Converts a string (which may represent the entire contents of the file) 542 ** from DOS or Macintosh format to Unix format. Conversion is done in-place. 543 ** In the DOS case, the length will be shorter, and passed length will be 544 ** modified to reflect the new length. The routine has support for blockwise 545 ** file to string conversion: if the fileString has a trailing '\r' and 546 ** 'pendingCR' is not zero, the '\r' is deposited in there and is not 547 ** converted. If there is no trailing '\r', a 0 is deposited in 'pendingCR' 548 ** It's the caller's responsability to make sure that the pending character, 549 ** if present, is inserted at the beginning of the next block to convert. 550 */ 551 void ConvertFromDosFileString(char *fileString, int *length, 552 char* pendingCR) 553 { 554 char *outPtr = fileString; 555 char *inPtr = fileString; 556 if (pendingCR) *pendingCR = 0; 557 while (inPtr < fileString + *length) { 558 if (*inPtr == '\r') { 559 if (inPtr < fileString + *length - 1) { 560 if (*(inPtr + 1) == '\n') 561 inPtr++; 562 } else { 563 if (pendingCR) { 564 *pendingCR = *inPtr; 565 break; /* Don't copy this trailing '\r' */ 566 } 567 } 568 } 569 *outPtr++ = *inPtr++; 570 } 571 *outPtr = '\0'; 572 *length = outPtr - fileString; 573 } 574 void ConvertFromMacFileString(char *fileString, int length) 575 { 576 char *inPtr = fileString; 577 while (inPtr < fileString + length) { 578 if (*inPtr == '\r' ) 579 *inPtr = '\n'; 580 inPtr++; 581 } 582 } 583 584 /* 585 ** Converts a string (which may represent the entire contents of the file) from 586 ** Unix to DOS format. String is re-allocated (with malloc), and length is 587 ** modified. If allocation fails, which it may, because this can potentially 588 ** be a huge hunk of memory, returns FALSE and no conversion is done. 589 ** 590 ** This could be done more efficiently by asking doSave to allocate some 591 ** extra memory for this, and only re-allocating if it wasn't enough. If 592 ** anyone cares about the performance or the potential for running out of 593 ** memory on a save, it should probably be redone. 594 */ 595 int ConvertToDosFileString(char **fileString, int *length) 596 { 597 char *outPtr, *outString; 598 char *inPtr = *fileString; 599 int inLength = *length; 600 int outLength = 0; 601 602 /* How long a string will we need? */ 603 while (inPtr < *fileString + inLength) { 604 if (*inPtr == '\n') 605 outLength++; 606 inPtr++; 607 outLength++; 608 } 609 610 /* Allocate the new string */ 611 outString = (char*)malloc(outLength + 1); 612 if (outString == NULL) 613 return FALSE; 614 615 /* Do the conversion, free the old string */ 616 inPtr = *fileString; 617 outPtr = outString; 618 while (inPtr < *fileString + inLength) { 619 if (*inPtr == '\n') 620 *outPtr++ = '\r'; 621 *outPtr++ = *inPtr++; 622 } 623 *outPtr = '\0'; 624 NEditFree(*fileString); 625 *fileString = outString; 626 *length = outLength; 627 return TRUE; 628 } 629 630 /* 631 ** Converts a string (which may represent the entire contents of the file) 632 ** from Unix to Macintosh format. 633 */ 634 void ConvertToMacFileString(char *fileString, int length) 635 { 636 char *inPtr = fileString; 637 638 while (inPtr < fileString + length) { 639 if (*inPtr == '\n' ) 640 *inPtr = '\r'; 641 inPtr++; 642 } 643 } 644 645 /* 646 ** Reads a text file into a string buffer, converting line breaks to 647 ** unix-style if appropriate. 648 ** 649 ** Force a terminating \n, if this is requested 650 */ 651 char *ReadAnyTextFile(const char *fileName, int forceNL) 652 { 653 struct stat statbuf; 654 FILE *fp; 655 int fileLen, readLen; 656 char *fileString; 657 int format; 658 659 /* Read the whole file into fileString */ 660 if ((fp = fopen(fileName, "r")) == NULL) { 661 return NULL; 662 } 663 if (fstat(fileno(fp), &statbuf) != 0) { 664 fclose(fp); 665 return NULL; 666 } 667 fileLen = statbuf.st_size; 668 /* +1 = space for null 669 ** +1 = possible additional \n 670 */ 671 fileString = (char*)NEditMalloc(fileLen + 2); 672 readLen = fread(fileString, sizeof(char), fileLen, fp); 673 if (ferror(fp)) { 674 NEditFree(fileString); 675 fclose(fp); 676 return NULL; 677 } 678 fclose(fp); 679 fileString[readLen] = 0; 680 681 /* Convert linebreaks? */ 682 format = FormatOfFile(fileString); 683 if (format == DOS_FILE_FORMAT){ 684 char pendingCR; 685 ConvertFromDosFileString(fileString, &readLen, &pendingCR); 686 } else if (format == MAC_FILE_FORMAT){ 687 ConvertFromMacFileString(fileString, readLen); 688 } 689 690 /* now, that the fileString is in Unix format, check for terminating \n */ 691 if (forceNL && fileString[readLen - 1] != '\n') { 692 fileString[readLen] = '\n'; 693 fileString[readLen + 1] = '\0'; 694 } 695 696 return fileString; 697 } 698