UNIXworkcode

1 /******************************************************************************* 2 * * 3 * file.c -- Nirvana Editor file i/o * 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 * May 10, 1991 * 24 * * 25 * Written by Mark Edel * 26 * * 27 *******************************************************************************/ 28 29 #ifdef HAVE_CONFIG_H 30 #include "../config.h" 31 #endif 32 33 #include "file.h" 34 #include "filter.h" 35 #include "textBuf.h" 36 #include "text.h" 37 #include "window.h" 38 #include "preferences.h" 39 #include "undo.h" 40 #include "menu.h" 41 #include "tags.h" 42 #include "server.h" 43 #include "interpret.h" 44 #include "editorconfig.h" 45 #include "../util/misc.h" 46 #include "../util/DialogF.h" 47 #include "../util/fileUtils.h" 48 #include "../util/getfiles.h" 49 #include "../util/printUtils.h" 50 #include "../util/utils.h" 51 #include "../util/nedit_malloc.h" 52 #include "../util/libxattr.h" 53 54 #include <errno.h> 55 #include <limits.h> 56 #include <stdio.h> 57 #include <stdlib.h> 58 #include <string.h> 59 #include <unistd.h> 60 #include <ctype.h> 61 #include <iconv.h> 62 #include <locale.h> 63 64 #include <sys/types.h> 65 #include <sys/stat.h> 66 #ifndef __MVS__ 67 #include <sys/param.h> 68 #endif 69 #include <fcntl.h> 70 71 #include <Xm/Xm.h> 72 #include <Xm/ToggleB.h> 73 #include <Xm/FileSB.h> 74 #include <Xm/RowColumn.h> 75 #include <Xm/Form.h> 76 #include <Xm/Label.h> 77 78 #ifdef HAVE_DEBUG_H 79 #include "../debug.h" 80 #endif 81 82 #include <inttypes.h> 83 84 #define ENC_ERROR_LIST_LEN 8 85 86 typedef struct Locale { 87 char *locale; 88 char *encoding; 89 } Locale; 90 91 static Locale locales[] = { 92 {"aa_DJ", "ISO8859-1"}, 93 {"af_ZA", "ISO8859-1"}, 94 {"an_ES", "ISO8859-15"}, 95 {"ar_AE", "ISO8859-6"}, 96 {"ar_BH", "ISO8859-6"}, 97 {"ar_DZ", "ISO8859-6"}, 98 {"ar_EG", "ISO8859-6"}, 99 {"ar_IQ", "ISO8859-6"}, 100 {"ar_JO", "ISO8859-6"}, 101 {"ar_KW", "ISO8859-6"}, 102 {"ar_LB", "ISO8859-6"}, 103 {"ar_LY", "ISO8859-6"}, 104 {"ar_MA", "ISO8859-6"}, 105 {"ar_OM", "ISO8859-6"}, 106 {"ar_QA", "ISO8859-6"}, 107 {"ar_SA", "ISO8859-6"}, 108 {"ar_SD", "ISO8859-6"}, 109 {"ar_SY", "ISO8859-6"}, 110 {"ar_TN", "ISO8859-6"}, 111 {"ar_YE", "ISO8859-6"}, 112 {"ast_ES", "ISO8859-15"}, 113 {"be_BY", "CP1251"}, 114 {"bg_BG", "CP1251"}, 115 {"br_FR", "ISO8859-15"}, 116 {"br_FR@euro", "ISO8859-15"}, 117 {"bs_BA", "ISO8859-2"}, 118 {"ca_AD", "ISO8859-15"}, 119 {"ca_ES", "ISO8859-15"}, 120 {"ca_ES@euro", "ISO8859-15"}, 121 {"ca_FR", "ISO8859-15"}, 122 {"ca_IT", "ISO8859-15"}, 123 {"cs_CZ", "ISO8859-2"}, 124 {"cy_GB", "ISO8859-14"}, 125 {"da_DK", "ISO8859-1"}, 126 {"de_AT", "ISO8859-15"}, 127 {"de_AT@euro", "ISO8859-15"}, 128 {"de_BE", "ISO8859-15"}, 129 {"de_BE@euro", "ISO8859-15"}, 130 {"de_CH", "ISO8859-1"}, 131 {"de_DE", "ISO8859-15"}, 132 {"de_DE@euro", "ISO8859-15"}, 133 {"de_LU", "ISO8859-15"}, 134 {"de_LU@euro", "ISO8859-15"}, 135 {"el_GR", "ISO8859-7"}, 136 {"el_CY", "ISO8859-7"}, 137 {"en_AU", "ISO8859-1"}, 138 {"en_BW", "ISO8859-1"}, 139 {"en_CA", "ISO8859-1"}, 140 {"en_DK", "ISO8859-1"}, 141 {"en_GB", "ISO8859-1"}, 142 {"en_HK", "ISO8859-1"}, 143 {"en_IE", "ISO8859-15"}, 144 {"en_IE@euro", "ISO8859-15"}, 145 {"en_NZ", "ISO8859-1"}, 146 {"en_PH", "ISO8859-1"}, 147 {"en_SG", "ISO8859-1"}, 148 {"en_US", "ISO8859-1"}, 149 {"en_ZA", "ISO8859-1"}, 150 {"en_ZW", "ISO8859-1"}, 151 {"es_AR", "ISO8859-1"}, 152 {"es_BO", "ISO8859-1"}, 153 {"es_CL", "ISO8859-1"}, 154 {"es_CO", "ISO8859-1"}, 155 {"es_CR", "ISO8859-1"}, 156 {"es_DO", "ISO8859-1"}, 157 {"es_EC", "ISO8859-1"}, 158 {"es_ES", "ISO8859-15"}, 159 {"es_ES@euro", "ISO8859-15"}, 160 {"es_GT", "ISO8859-1"}, 161 {"es_HN", "ISO8859-1"}, 162 {"es_MX", "ISO8859-1"}, 163 {"es_NI", "ISO8859-1"}, 164 {"es_PA", "ISO8859-1"}, 165 {"es_PE", "ISO8859-1"}, 166 {"es_PR", "ISO8859-1"}, 167 {"es_PY", "ISO8859-1"}, 168 {"es_SV", "ISO8859-1"}, 169 {"es_US", "ISO8859-1"}, 170 {"es_UY", "ISO8859-1"}, 171 {"es_VE", "ISO8859-1"}, 172 {"et_EE", "ISO8859-15"}, 173 {"et_EE.ISO8859-15", "ISO8859-15"}, 174 {"eu_ES", "ISO8859-15"}, 175 {"eu_ES@euro", "ISO8859-15"}, 176 {"fi_FI", "ISO8859-15"}, 177 {"fi_FI@euro", "ISO8859-15"}, 178 {"fo_FO", "ISO8859-1"}, 179 {"fr_BE", "ISO8859-15"}, 180 {"fr_BE@euro", "ISO8859-15"}, 181 {"fr_CA", "ISO8859-15"}, 182 {"fr_CH", "ISO8859-1"}, 183 {"fr_FR", "ISO8859-15"}, 184 {"fr_FR@euro", "ISO8859-15"}, 185 {"fr_LU", "ISO8859-15"}, 186 {"fr_LU@euro", "ISO8859-15"}, 187 {"ga_IE", "ISO8859-15"}, 188 {"ga_IE@euro", "ISO8859-15"}, 189 {"gd_GB", "ISO8859-15"}, 190 {"gl_ES", "ISO8859-15"}, 191 {"gl_ES@euro", "ISO8859-15"}, 192 {"gv_GB", "ISO8859-1"}, 193 {"he_IL", "ISO8859-8"}, 194 {"hr_HR", "ISO8859-2"}, 195 {"hsb_DE", "ISO8859-2"}, 196 {"hu_HU", "ISO8859-2"}, 197 {"hy_AM.ARMSCII-8", "ARMSCII-8"}, 198 {"id_ID", "ISO8859-1"}, 199 {"is_IS", "ISO8859-1"}, 200 {"it_CH", "ISO8859-1"}, 201 {"it_IT", "ISO8859-15"}, 202 {"it_IT@euro", "ISO8859-15"}, 203 {"iw_IL", "ISO8859-8"}, 204 {"ja_JP.EUC-JP", "EUC-JP"}, 205 {"ka_GE", "GEORGIAN-PS"}, 206 {"kk_KZ", "PT154"}, 207 {"kl_GL", "ISO8859-1"}, 208 {"ko_KR.EUC-KR", "EUC-KR"}, 209 {"ku_TR", "ISO8859-9"}, 210 {"kw_GB", "ISO8859-1"}, 211 {"lg_UG", "ISO8859-10"}, 212 {"lt_LT", "ISO8859-13"}, 213 {"lv_LV", "ISO8859-13"}, 214 {"mg_MG", "ISO8859-15"}, 215 {"mi_NZ", "ISO8859-13"}, 216 {"mk_MK", "ISO8859-5"}, 217 {"ms_MY", "ISO8859-1"}, 218 {"mt_MT", "ISO8859-3"}, 219 {"nb_NO", "ISO8859-1"}, 220 {"nl_BE", "ISO8859-15"}, 221 {"nl_BE@euro", "ISO8859-15"}, 222 {"nl_NL", "ISO8859-15"}, 223 {"nl_NL@euro", "ISO8859-15"}, 224 {"nn_NO", "ISO8859-1"}, 225 {"oc_FR", "ISO8859-1"}, 226 {"om_KE", "ISO8859-1"}, 227 {"pl_PL", "ISO8859-2"}, 228 {"pt_BR", "ISO8859-1"}, 229 {"pt_PT", "ISO8859-15"}, 230 {"pt_PT@euro", "ISO8859-15"}, 231 {"ro_RO", "ISO8859-2"}, 232 {"ru_RU.KOI8-R", "KOI8-R"}, 233 {"ru_RU", "ISO8859-5"}, 234 {"ru_UA", "KOI8-U"}, 235 {"sk_SK", "ISO8859-2"}, 236 {"sl_SI", "ISO8859-2"}, 237 {"so_DJ", "ISO8859-1"}, 238 {"so_KE", "ISO8859-1"}, 239 {"so_SO", "ISO8859-1"}, 240 {"sq_AL", "ISO8859-1"}, 241 {"st_ZA", "ISO8859-1"}, 242 {"sv_FI", "ISO8859-15"}, 243 {"sv_FI@euro", "ISO8859-15"}, 244 {"sv_SE", "ISO8859-1"}, 245 {"tg_TJ", "KOI8-T"}, 246 {"th_TH", "TIS-620"}, 247 {"tl_PH", "ISO8859-1"}, 248 {"tr_CY", "ISO8859-9"}, 249 {"tr_TR", "ISO8859-9"}, 250 {"uk_UA", "KOI8-U"}, 251 {"uz_UZ", "ISO8859-1"}, 252 {"wa_BE", "ISO8859-15"}, 253 {"wa_BE@euro", "ISO8859-15"}, 254 {"xh_ZA", "ISO8859-1"}, 255 {"yi_US", "CP1255"}, 256 {"zh_CN.GB18030", "GB18030"}, 257 {"zh_CN.GBK", "GBK"}, 258 {"zh_CN", "GB2312"}, 259 {"zh_HK", "BIG5-HKSCS"}, 260 {"zh_SG.GBK", "GBK"}, 261 {"zh_SG", "GB2312"}, 262 {"zh_TW.EUC-TW", "EUC-TW"}, 263 {"zh_TW", "BIG5"}, 264 {"zu_ZA", "ISO8859-1"}, 265 {NULL, NULL} 266 }; 267 268 /* Maximum frequency in miliseconds of checking for external modifications. 269 The periodic check is only performed on buffer modification, and the check 270 interval is only to prevent checking on every keystroke in case of a file 271 system which is slow to process stat requests (which I'm not sure exists) */ 272 #define MOD_CHECK_INTERVAL 3000 273 274 static int doSave(WindowInfo *window, Boolean setEncAttr); 275 static void safeClose(WindowInfo *window); 276 static int doOpen(WindowInfo *window, const char *name, const char *path, 277 const char *encoding, const char *filter_name, int flags); 278 static void backupFileName(WindowInfo *window, char *name, size_t len); 279 static int writeBckVersion(WindowInfo *window); 280 static int bckError(WindowInfo *window, const char *errString, const char *file); 281 static int fileWasModifiedExternally(WindowInfo *window); 282 static const char *errorString(void); 283 static void addWrapNewlines(WindowInfo *window); 284 static int cmpWinAgainstFile(WindowInfo *window, const char *fileName); 285 static int min(int i1, int i2); 286 static void modifiedWindowDestroyedCB(Widget w, XtPointer clientData, 287 XtPointer callData); 288 static void forceShowLineNumbers(WindowInfo *window); 289 290 static char* getEncodingAttribute(const char *path); 291 292 293 WindowInfo *EditNewFile(WindowInfo *inWindow, char *geometry, int iconic, 294 const char *languageMode, const char *defaultPath) 295 { 296 char name[MAXPATHLEN]; 297 WindowInfo *window; 298 size_t pathlen; 299 char *path; 300 301 /*... test for creatability? */ 302 303 /* Find a (relatively) unique name for the new file */ 304 UniqueUntitledName(name); 305 306 /* create new window/document */ 307 if (inWindow) 308 window = CreateDocument(inWindow, name); 309 else 310 window = CreateWindow(name, geometry, iconic); 311 312 path = window->path; 313 strcpy(window->filename, name); 314 strcpy(path, (defaultPath && *defaultPath) ? defaultPath : GetCurrentDir()); 315 pathlen = strlen(window->path); 316 317 /* do we have a "/" at the end? if not, add one */ 318 if (0 < pathlen && path[pathlen - 1] != '/' && pathlen < MAXPATHLEN - 1) { 319 strcpy(&path[pathlen], "/"); 320 } 321 322 SetWindowModified(window, FALSE); 323 CLEAR_ALL_LOCKS(window->lockReasons); 324 UpdateWindowReadOnly(window); 325 UpdateStatsLine(window); 326 UpdateWindowTitle(window); 327 RefreshTabState(window); 328 329 if (languageMode == NULL) 330 DetermineLanguageMode(window, True); 331 else 332 SetLanguageMode(window, FindLanguageMode(languageMode), True); 333 334 ShowTabBar(window, GetShowTabBar(window)); 335 336 if (iconic && IsIconic(window)) 337 RaiseDocument(window); 338 else 339 RaiseDocumentWindow(window); 340 341 SortTabBar(window); 342 return window; 343 } 344 345 static void ApplyEditorConfig(WindowInfo *window, EditorConfig ec) { 346 // apply editorconfig 347 char *params[1]; 348 char numStr[25]; 349 350 if(ec.indent_size > 0 && ec.tab_width == 0) { 351 ec.tab_width = ec.indent_size; 352 } 353 354 // indent style / tab width 355 int indent_size_set = 0; 356 if(ec.indent_style == EC_TAB) { 357 params[0] = numStr; 358 snprintf(numStr, 24, "%d", 1); 359 XtCallActionProc(window->textArea, "set_use_tabs", NULL, params, 1); 360 } else if(ec.indent_style == EC_SPACE) { 361 int emTabDist = ec.indent_size; 362 if(emTabDist == 0) { 363 XtVaGetValues(window->textArea, textNemulateTabs, &emTabDist, NULL); 364 if(emTabDist == 0) { 365 emTabDist = BufGetTabDistance(window->buffer); 366 } 367 } 368 369 params[0] = numStr; 370 snprintf(numStr, 24, "%d", 0); 371 XtCallActionProc(window->textArea, "set_use_tabs", NULL, params, 1); 372 373 params[0] = numStr; 374 snprintf(numStr, 24, "%d", emTabDist); 375 XtCallActionProc(window->textArea, "set_em_tab_dist", NULL, params, 1); 376 indent_size_set = 1; 377 } 378 379 if(ec.tab_width > 0) { 380 params[0] = numStr; 381 snprintf(numStr, 24, "%d", ec.tab_width); 382 XtCallActionProc(window->textArea, "set_tab_dist", NULL, params, 1); 383 } 384 385 if(ec.indent_size > 0 && !indent_size_set) { 386 params[0] = numStr; 387 snprintf(numStr, 24, "%d", ec.indent_size); 388 XtCallActionProc(window->textArea, "set_em_tab_dist", NULL, params, 1); 389 } 390 391 // newline 392 switch(ec.end_of_line) { 393 default: break; 394 case EC_LF: { 395 window->fileFormat = UNIX_FILE_FORMAT; 396 break; 397 } 398 case EC_CR: { 399 window->fileFormat = MAC_FILE_FORMAT; 400 break; 401 } 402 case EC_CRLF: { 403 window->fileFormat = DOS_FILE_FORMAT; 404 break; 405 } 406 } 407 } 408 409 /* 410 ** Open an existing file specified by name and path. Use the window inWindow 411 ** unless inWindow is NULL or points to a window which is already in use 412 ** (displays a file other than Untitled, or is Untitled but modified). Flags 413 ** can be any of: 414 ** 415 ** CREATE: If file is not found, (optionally) prompt the 416 ** user whether to create 417 ** SUPPRESS_CREATE_WARN When creating a file, don't ask the user 418 ** PREF_READ_ONLY Make the file read-only regardless 419 ** 420 ** If languageMode is passed as NULL, it will be determined automatically 421 ** from the file extension or file contents. 422 ** 423 ** If bgOpen is True, then the file will be open in background. This 424 ** works in association with the SetLanguageMode() function that has 425 ** the syntax highlighting deferred, in order to speed up the file- 426 ** opening operation when multiple files are being opened in succession. 427 */ 428 WindowInfo *EditExistingFile(WindowInfo *inWindow, const char *name, 429 const char *path, const char *encoding, const char *filter, int flags, 430 char *geometry, int iconic, const char *languageMode, int tabbed, 431 int bgOpen) 432 { 433 WindowInfo *window; 434 char fullname[MAXPATHLEN]; 435 436 /* first look to see if file is already displayed in a window */ 437 window = FindWindowWithFile(name, path); 438 if (window != NULL) { 439 if (!bgOpen) { 440 if (iconic) 441 RaiseDocument(window); 442 else 443 RaiseDocumentWindow(window); 444 } 445 return window; 446 } 447 448 /* If an existing window isn't specified; or the window is already 449 in use (not Untitled or Untitled and modified), or is currently 450 busy running a macro; create the window */ 451 if (inWindow == NULL) { 452 window = CreateWindow(name, geometry, iconic); 453 } 454 else if (inWindow->filenameSet || inWindow->fileChanged || 455 inWindow->macroCmdData != NULL) { 456 if (tabbed) { 457 window = CreateDocument(inWindow, name); 458 } 459 else { 460 window = CreateWindow(name, geometry, iconic); 461 } 462 } 463 else { 464 /* open file in untitled document */ 465 window = inWindow; 466 strcpy(window->path, path); 467 strcpy(window->filename, name); 468 if(encoding) { 469 SetEncoding(window, encoding); 470 } else { 471 window->encoding[0] = '\0'; 472 } 473 474 if (!iconic && !bgOpen) { 475 RaiseDocumentWindow(window); 476 } 477 } 478 479 // look for .editorconfig 480 EditorConfig ec; 481 if(GetEditorConfig()) { 482 ec = EditorConfigGet(path, name); 483 } else { 484 memset(&ec, 0, sizeof(EditorConfig)); 485 } 486 if(ec.charset && !encoding) { 487 encoding = ec.charset; 488 if(ec.bom != EC_BOM_UNSET) { 489 window->bom = True; 490 } 491 } 492 493 /* Open the file */ 494 if (!doOpen(window, name, path, encoding, filter, flags)) { 495 /* The user may have destroyed the window instead of closing the 496 warning dialog; don't close it twice */ 497 safeClose(window); 498 499 if(ec.charset) { 500 free(ec.charset); 501 } 502 return NULL; 503 } 504 forceShowLineNumbers(window); 505 506 /* Decide what language mode to use, trigger language specific actions */ 507 if(window->buffer->length < DISABLE_LANG_THRESHOLD) { 508 if (languageMode == NULL) { 509 DetermineLanguageMode(window, True); 510 } else { 511 SetLanguageMode(window, FindLanguageMode(languageMode), True); 512 } 513 } else { 514 SetLanguageMode(window, PLAIN_LANGUAGE_MODE, True); 515 } 516 517 518 /* if a large file was opened, the previous SetLanguageMode should't set 519 * the wrap mode, however the user should be free to set any mode */ 520 window->wrapModeNoneForced = False; 521 522 /* update tab label and tooltip */ 523 RefreshTabState(window); 524 SortTabBar(window); 525 ShowTabBar(window, GetShowTabBar(window)); 526 527 if (!bgOpen) 528 RaiseDocument(window); 529 530 /* Bring the title bar and statistics line up to date, doOpen does 531 not necessarily set the window title or read-only status */ 532 UpdateWindowTitle(window); 533 UpdateWindowReadOnly(window); 534 UpdateStatsLine(window); 535 536 /* Add the name to the convenience menu of previously opened files */ 537 strcpy(fullname, path); 538 strcat(fullname, name); 539 if(GetPrefAlwaysCheckRelTagsSpecs()) 540 AddRelTagsFile(GetPrefTagFile(), path, TAG); 541 AddToPrevOpenMenu(fullname); 542 543 if(ec.found) { 544 ApplyEditorConfig(window, ec); 545 } 546 547 if(ec.charset) { 548 free(ec.charset); 549 } 550 551 return window; 552 } 553 554 void RevertToSaved(WindowInfo *window, char *newEncoding) 555 { 556 char name[MAXPATHLEN], path[MAXPATHLEN]; 557 char *encoding; 558 int i; 559 int insertPositions[MAX_PANES], topLines[MAX_PANES]; 560 int horizOffsets[MAX_PANES]; 561 int openFlags = 0; 562 Widget text; 563 564 /* Can't revert untitled windows */ 565 if (!window->filenameSet) 566 { 567 DialogF(DF_WARN, window->shell, 1, "Error", 568 "Window ''%s'' was never saved, can''t re-read", "OK", 569 window->filename); 570 return; 571 } 572 573 /* save insert & scroll positions of all of the panes to restore later */ 574 for (i=0; i<=window->nPanes; i++) { 575 text = i==0 ? window->textArea : window->textPanes[i-1]; 576 insertPositions[i] = TextGetCursorPos(text); 577 TextGetScroll(text, &topLines[i], &horizOffsets[i]); 578 } 579 580 /* re-read the file, update the window title if new file is different */ 581 strcpy(name, window->filename); 582 strcpy(path, window->path); 583 584 if(newEncoding) { 585 encoding = newEncoding; 586 } else if(window->encoding[0] != '\0') { 587 encoding = window->encoding; 588 } else { 589 encoding = NULL; 590 } 591 592 593 RemoveBackupFile(window); 594 ClearUndoList(window); 595 openFlags |= IS_USER_LOCKED(window->lockReasons) && !IS_ENCODING_LOCKED(window->lockReasons) ? PREF_READ_ONLY : 0; 596 if (!doOpen(window, name, path, encoding, window->filter, openFlags)) { 597 /* This is a bit sketchy. The only error in doOpen that irreperably 598 damages the window is "too much binary data". It should be 599 pretty rare to be reverting something that was fine only to find 600 that now it has too much binary data. */ 601 if (!window->fileMissing) 602 safeClose(window); 603 else { 604 /* Treat it like an externally modified file */ 605 window->lastModTime=0; 606 window->fileMissing=FALSE; 607 } 608 return; 609 } 610 forceShowLineNumbers(window); 611 UpdateWindowTitle(window); 612 UpdateWindowReadOnly(window); 613 614 /* restore the insert and scroll positions of each pane */ 615 for (i=0; i<=window->nPanes; i++) { 616 text = i==0 ? window->textArea : window->textPanes[i-1]; 617 TextSetCursorPos(text, insertPositions[i]); 618 TextSetScroll(text, topLines[i], horizOffsets[i]); 619 } 620 } 621 622 /* 623 ** Checks whether a window is still alive, and closes it only if so. 624 ** Intended to be used when the file could not be opened for some reason. 625 ** Normally the window is still alive, but the user may have closed the 626 ** window instead of the error dialog. In that case, we shouldn't close the 627 ** window a second time. 628 */ 629 static void safeClose(WindowInfo *window) 630 { 631 WindowInfo* p = WindowList; 632 while(p) { 633 if (p == window) { 634 CloseWindow(window); 635 return; 636 } 637 p = p->next; 638 } 639 } 640 641 static char bom_utf8[3] = { (char)0xEF, (char)0xBB, (char)0xBF }; 642 static char bom_utf16be[2] = { (char)0xFE, (char)0xFF }; 643 static char bom_utf16le[2] = { (char)0xFF, (char)0xFE }; 644 static char bom_utf32be[4] = { (char)0, (char)0, (char)0xFE, (char)0xFF }; 645 static char bom_utf32le[4] = { (char)0xFF, (char)0xFE, (char)0, (char)0 }; 646 static char bom_gb18030[4] = { (char)0x84, (char)0x31, (char)0x95, (char)0x33 }; 647 static char bom_utfebcdic[4] = { (char)0xDD, (char)0x73, (char)0x66, (char)0x73 }; 648 649 typedef size_t(*ConvertFunc)(iconv_t, char **, size_t *, char **, size_t *); 650 651 /* 652 * a function with an iconv like interface but it just copies bytes 653 */ 654 size_t copyBytes( 655 iconv_t cd, 656 char **inbuf, 657 size_t *inbytesleft, 658 char **outbuf, 659 size_t *outbytesleft) 660 { 661 if(!inbuf || !outbuf) { 662 return 0; 663 } 664 size_t in = *inbytesleft; 665 size_t out = *outbytesleft; 666 size_t cp = in > out ? out : in; 667 memcpy(*outbuf, *inbuf, cp); 668 *inbuf = (*inbuf) + cp; 669 *outbuf = (*outbuf) + cp; 670 *inbytesleft = in - cp; 671 *outbytesleft = out - cp; 672 return 0; 673 } 674 675 676 #define IO_BUFSIZE 2048 677 678 static int doOpen(WindowInfo *window, const char *name, const char *path, 679 const char *encoding, const char *filter_name, int flags) 680 { 681 char fullname[MAXPATHLEN]; 682 int resp; 683 684 // make sure, encoding doesn't point to window->encoding 685 char encoding_buffer[MAX_ENCODING_LENGTH]; 686 if(encoding == window->encoding) { 687 size_t enclen = strlen(window->encoding); 688 if(enclen > MAX_ENCODING_LENGTH) { 689 enclen = MAX_ENCODING_LENGTH-1; 690 } 691 memcpy(encoding_buffer, window->encoding, enclen); 692 encoding_buffer[enclen] = 0; 693 encoding = encoding_buffer; 694 } 695 696 /* initialize lock reasons */ 697 CLEAR_ALL_LOCKS(window->lockReasons); 698 699 /* Update the window data structure */ 700 size_t name_len = strlen(name); 701 size_t path_len = strlen(path); 702 if(name_len + path_len >= MAXPATHLEN-1) { 703 window->filenameSet = FALSE; /* Temp. prevent check for changes. */ 704 DialogF(DF_ERR, window->shell, 1, "Error opening File", 705 "Path too long", "OK"); 706 window->filenameSet = TRUE; 707 return False; 708 } 709 710 memcpy(window->filename, name, name_len+1); 711 memcpy(window->path, path, path_len+1); 712 window->encoding[0] = '\0'; 713 window->filenameSet = TRUE; 714 window->fileMissing = TRUE; 715 716 /* Get the full name of the file */ 717 memcpy(fullname, path, path_len); 718 memcpy(fullname+path_len, name, name_len+1); 719 720 FileContent content; 721 if(GetFileContent(window->shell, fullname, encoding, filter_name, &content)) { 722 if(content.err == ENOENT && flags & CREATE) { 723 /* Give option to create (or to exit if this is the only window) */ 724 if (!(flags & SUPPRESS_CREATE_WARN)) { 725 /* on Solaris 2.6, and possibly other OSes, dialog won't 726 show if parent window is iconized. */ 727 RaiseShellWindow(window->shell, False); 728 729 /* ask user for next action if file not found */ 730 if (WindowList == window && window->next == NULL) { 731 resp = DialogF(DF_WARN, window->shell, 3, "New File", 732 "Can''t open %s:\n%s", "New File", "Cancel", 733 "Exit NEdit", fullname, strerror(content.err)); 734 } else { 735 resp = DialogF(DF_WARN, window->shell, 2, "New File", 736 "Can''t open %s:\n%s", "New File", "Cancel", fullname, 737 strerror(content.err)); 738 } 739 740 if (resp == 2) { 741 return FALSE; 742 } else if (resp == 3) { 743 exit(EXIT_SUCCESS); 744 } 745 } 746 747 /* Test if new file can be created */ 748 int fd; 749 if ((fd = creat(fullname, 0666)) == -1) { 750 DialogF(DF_ERR, window->shell, 1, "Error creating File", 751 "Can''t create %s:\n%s", "OK", fullname, errorString()); 752 return FALSE; 753 } else { 754 close(fd); 755 remove(fullname); 756 } 757 758 SetWindowModified(window, FALSE); 759 if ((flags & PREF_READ_ONLY) != 0) { 760 SET_USER_LOCKED(window->lockReasons, TRUE); 761 } 762 UpdateWindowReadOnly(window); 763 return TRUE; 764 } else if(content.isdir) { 765 window->filenameSet = FALSE; /* Temp. prevent check for changes. */ 766 DialogF(DF_ERR, window->shell, 1, "Error opening File", 767 "Can''t open directory %s", "OK", name); 768 window->filenameSet = TRUE; 769 } else if(content.isblk) { 770 window->filenameSet = FALSE; /* Temp. prevent check for changes. */ 771 DialogF(DF_ERR, window->shell, 1, "Error opening File", 772 "Can''t open block device %s", "OK", name); 773 window->filenameSet = TRUE; 774 } else if(content.allocerror) { 775 window->filenameSet = FALSE; /* Temp. prevent check for changes. */ 776 DialogF(DF_ERR, window->shell, 1, "Error while opening File", 777 "File is too large to edit", "OK"); 778 window->filenameSet = TRUE; 779 } else if(content.iconverror) { 780 window->filenameSet = FALSE; /* Temp. prevent check for changes. */ 781 char *format = "File cannot be converted from %s to UTF8"; 782 size_t msglen = strlen(format) + strlen(encoding) + 4; 783 char *msgbuf = NEditMalloc(msglen); 784 snprintf(msgbuf, msglen, format, encoding); 785 DialogF(DF_ERR, window->shell, 1, "Error while opening File", 786 msgbuf, "OK"); 787 NEditFree(msgbuf); 788 window->filenameSet = TRUE; 789 } else { 790 /* A true error */ 791 DialogF(DF_ERR, window->shell, 1, "Error opening File", 792 "Could not open %s%s:\n%s", "OK", path, name, 793 strerror(content.err)); 794 return FALSE; 795 } 796 return 0; 797 } 798 SetEncoding(window, content.encoding); 799 800 SET_ENCODING_LOCKED(window->lockReasons, FALSE); 801 if(content.readonly) { 802 SET_PERM_LOCKED(window->lockReasons, TRUE); 803 } 804 805 int show_infobar = FALSE; 806 int lock_enc_error = FALSE; 807 if(content.skipped > 0) { 808 char *lockmsg = ""; 809 if(GetPrefLockEncodingError()) { 810 lockmsg = ": file locked to prevent accidental changes"; 811 flags = flags | PREF_READ_ONLY; 812 lock_enc_error = TRUE; 813 } 814 815 char msgbuf[256]; 816 snprintf(msgbuf, 256, "%d non-convertible characters skipped%s", content.skipped, lockmsg); 817 818 show_infobar = TRUE; 819 SetEncodingInfoBarLabel(window, msgbuf); 820 SetEncErrors(window, content.enc_errors, content.num_enc_errors); 821 } else { 822 SetEncodingInfoBarLabel(window, "No conversion errors"); 823 SetEncErrors(window, NULL, 0); 824 NEditFree(content.enc_errors); 825 } 826 827 SetFilter(window, filter_name); 828 829 window->bom = content.hasBOM; 830 window->fileFormat = content.fileFormat; 831 832 /* Any errors that happen after this point leave the window in a 833 "broken" state, and thus RevertToSaved will abandon the window if 834 window->fileMissing is FALSE and doOpen fails. */ 835 window->fileMode = content.statbuf.st_mode; 836 window->fileUid = content.statbuf.st_uid; 837 window->fileGid = content.statbuf.st_gid; 838 window->lastModTime = content.statbuf.st_mtime; 839 window->device = content.statbuf.st_dev; 840 window->inode = content.statbuf.st_ino; 841 window->fileMissing = FALSE; 842 843 /* Disable continuous wrapping if the file is too big */ 844 if(content.length > DISABLE_WRAPPING_THRESHOLD) { 845 SetAutoWrap(window, NO_WRAP); 846 window->wrapModeNoneForced = True; 847 } 848 849 /* Display the file contents in the text widget */ 850 window->ignoreModify = True; 851 BufSetAll(window->buffer, content.content); 852 window->ignoreModify = False; 853 854 /* Check that the length that the buffer thinks it has is the same 855 as what we gave it. If not, there were probably nuls in the file. 856 Substitute them with another character. If that is impossible, warn 857 the user, make the file read-only, and force a substitution */ 858 if (window->buffer->length != content.length) { 859 if (!BufSubstituteNullChars(content.content, content.length, window->buffer)) { 860 resp = DialogF(DF_ERR, window->shell, 2, "Error while opening File", 861 "Too much binary data in file. You may view\n" 862 "it, but not modify or re-save its contents.", "View", 863 "Cancel"); 864 if (resp == 2) { 865 return FALSE; 866 } 867 868 SET_TMBD_LOCKED(window->lockReasons, TRUE); 869 for (char *c = content.content; c < &content.content[content.length]; c++) { 870 if (*c == '\0') { 871 *c = (char) 0xfe; 872 } 873 } 874 window->buffer->nullSubsChar = (char) 0xfe; 875 } 876 window->ignoreModify = True; 877 BufSetAll(window->buffer, content.content); 878 window->ignoreModify = False; 879 } 880 881 /* Release the memory that holds fileString */ 882 NEditFree(content.content); 883 884 /* Set window title and file changed flag */ 885 if ((flags & PREF_READ_ONLY) != 0) { 886 SET_USER_LOCKED(window->lockReasons, TRUE); 887 SET_ENCODING_LOCKED(window->lockReasons, lock_enc_error); 888 } 889 if (IS_PERM_LOCKED(window->lockReasons)) { 890 window->fileChanged = FALSE; 891 UpdateWindowTitle(window); 892 } else { 893 SetWindowModified(window, FALSE); 894 if (IS_ANY_LOCKED(window->lockReasons)) { 895 UpdateWindowTitle(window); 896 } 897 } 898 UpdateWindowReadOnly(window); 899 900 // show infobar, if needed 901 if(show_infobar) { 902 ShowEncodingInfoBar(window, 1); 903 } 904 905 return TRUE; 906 } 907 908 int GetFileContent(Widget shell, const char *path, const char *encoding, const char *filter_name, FileContent *content) 909 { 910 memset(content, 0, sizeof(FileContent)); 911 912 off_t fileLen, readLen; 913 char *fileString, *c; 914 char buf[IO_BUFSIZE]; 915 FILE *fp = NULL; 916 FileStream *stream = NULL;; 917 int err; 918 919 char encoding_buffer[MAX_ENCODING_LENGTH]; 920 if(encoding) { 921 size_t enclen = strlen(encoding); 922 if(enclen >= MAX_ENCODING_LENGTH) { 923 enclen = MAX_ENCODING_LENGTH-1; 924 } 925 memcpy(encoding_buffer, encoding, enclen); 926 encoding_buffer[enclen] = 0; 927 encoding = encoding_buffer; 928 } else { 929 encoding_buffer[0] = 0; 930 } 931 encoding = encoding_buffer[0] != '\0' ? encoding_buffer : NULL; 932 933 934 /* Open the file */ 935 fp = fopen(path, "rb+"); 936 if(!fp) { 937 fp = fopen(path, "rb"); 938 if(fp) { 939 /* File is read only */ 940 content->readonly = 1; 941 } else { 942 content->err = errno; 943 return 1; 944 } 945 } 946 947 /* Get the length of the file, the protection mode, and the time of the 948 last modification to the file */ 949 if (fstat(fileno(fp), &content->statbuf) != 0) { 950 fclose(fp); 951 content->err = errno; 952 return 1; 953 } 954 955 if (S_ISDIR(content->statbuf.st_mode)) { 956 fclose(fp); 957 content->isdir = 1; 958 return 1; 959 } 960 961 #ifdef S_ISBLK 962 if (S_ISBLK(content->statbuf.st_mode)) { 963 fclose(fp); 964 content->isblk = 1; 965 return 1; 966 } 967 #endif 968 969 fileLen = content->statbuf.st_size; 970 971 // create stream object (optionally with filter) 972 IOFilter* filter = GetFilterFromName(filter_name); 973 char *filter_cmd = NULL; 974 if(filter && filter->cmdin && strlen(filter->cmdin) > 0) { 975 filter_cmd = filter->cmdin; 976 } 977 stream = filestream_open_r(shell, fp, filter_cmd); 978 979 // check if the file has the 'charset' exnteded attribute 980 if(!encoding) { 981 char *xattr_charset = getEncodingAttribute(path); 982 if(xattr_charset) { 983 size_t enclen = strlen(xattr_charset); 984 if(enclen >= MAX_ENCODING_LENGTH) { 985 enclen = MAX_ENCODING_LENGTH-1; 986 } 987 memcpy(encoding_buffer, xattr_charset, enclen); 988 encoding_buffer[enclen] = 0; 989 encoding = encoding_buffer; 990 NEditFree(xattr_charset); 991 } 992 } 993 994 int checkBOM = 1; 995 int checkEncoding = 0; 996 if(encoding) { 997 /* check if the encoding string starts with UTF */ 998 if(strlen(encoding) < 3) { 999 /* no UTF encoding */ 1000 checkBOM = 0; 1001 } else { 1002 char encpre[4]; 1003 encpre[0] = encoding[0]; 1004 encpre[1] = encoding[1]; 1005 encpre[2] = encoding[2]; 1006 encpre[3] = 0; 1007 if(strcasecmp(encpre, "UTF")) { 1008 // encoding doesn't start with "UTF" -> no BOM 1009 checkBOM = 0; 1010 } 1011 } 1012 if(!strcasecmp(encoding, "GB18030")) { 1013 checkBOM = 1; /* GB18030 is unicode and could have a BOM */ 1014 } 1015 } else { 1016 /* file has no extended attributes, use locale charset */ 1017 encoding = GetPrefDefaultCharset(); 1018 checkEncoding = 1; 1019 } 1020 1021 char *setEncoding = NULL; 1022 int hasBOM = 0; 1023 if(checkBOM) { 1024 /* read Byte Order Mark */ 1025 int bom = 0; 1026 size_t r = filestream_read(buf, 4, stream); 1027 do { 1028 if(r >= 4) { 1029 bom = 4; 1030 if(!memcmp(buf, bom_utf32be, 4)) { 1031 setEncoding = "UTF-32BE"; 1032 hasBOM = TRUE; 1033 break; 1034 } else if(!memcmp(buf, bom_utf32le, 4)) { 1035 setEncoding = "UTF-32LE"; 1036 hasBOM = TRUE; 1037 break; 1038 } else if(!memcmp(buf, bom_gb18030, 4)) { 1039 setEncoding = "GB18030"; 1040 hasBOM = TRUE; 1041 break; 1042 } else if(!memcmp(buf, bom_utfebcdic, 4)) { 1043 setEncoding = "UTF-EBCDIC"; 1044 hasBOM = TRUE; 1045 break; 1046 } 1047 } 1048 if(r >= 3) { 1049 bom = 3; 1050 if(!memcmp(buf, bom_utf8, 3)) { 1051 setEncoding = "UTF-8"; 1052 hasBOM = TRUE; 1053 break; 1054 } 1055 } 1056 if(r >= 2) { 1057 bom = 2; 1058 if(!memcmp(buf, bom_utf16be, 2)) { 1059 setEncoding = "UTF-16BE"; 1060 hasBOM = TRUE; 1061 break; 1062 } else if(!memcmp(buf, bom_utf16le, 2)) { 1063 setEncoding = "UTF-16LE"; 1064 hasBOM = TRUE; 1065 break; 1066 } 1067 } 1068 bom = 0; 1069 } while (0); 1070 filestream_reset(stream, bom); 1071 } 1072 if(setEncoding) { 1073 encoding = setEncoding; 1074 checkEncoding = 0; 1075 } 1076 1077 if(checkEncoding) { 1078 size_t r = filestream_read(buf, IO_BUFSIZE, stream); 1079 const char *newEnc = DetectEncoding(buf, r, encoding); 1080 if(newEnc && newEnc != encoding) { 1081 encoding = newEnc; 1082 } 1083 filestream_reset(stream, 0); 1084 } 1085 1086 /* Allocate space for the whole contents of the file (unfortunately) */ 1087 size_t strAlloc = fileLen; 1088 fileString = malloc(strAlloc + 1); /* +1 = space for null */ 1089 if (fileString == NULL) { 1090 filestream_close(stream); 1091 content->allocerror = 1; 1092 return 1; 1093 } 1094 1095 iconv_t ic = NULL; 1096 ConvertFunc strconv = copyBytes; 1097 if(encoding) { 1098 ic = iconv_open("UTF-8", encoding); 1099 if(ic == (iconv_t) -1) { 1100 filestream_close(stream); 1101 free(fileString); 1102 return 1; 1103 } 1104 strconv = (ConvertFunc)iconv; 1105 1106 /* set final encoding */ 1107 size_t len = strlen(encoding); 1108 if(len+1 >= MAX_ENCODING_LENGTH) { 1109 fprintf(stderr, "Error: Encoding string too large\n"); 1110 len = MAX_ENCODING_LENGTH-1; 1111 } 1112 memcpy(content->encoding, encoding, len); 1113 content->encoding[len] = 0; 1114 } 1115 1116 EncError *encErrors = NEditCalloc(ENC_ERROR_LIST_LEN, sizeof(EncError)); 1117 size_t numEncErrors = 0; 1118 size_t allocEncErrors = ENC_ERROR_LIST_LEN; 1119 1120 err = 0; 1121 int skipped = 0; 1122 size_t r = 0; 1123 readLen = 0; 1124 char *outStr = fileString; 1125 size_t prev = 0; 1126 while((r = filestream_read(buf+prev, IO_BUFSIZE-prev, stream)) > 0 && !err) { 1127 char *str = buf; 1128 size_t inleft = prev + r; 1129 size_t outleft = strAlloc - readLen; 1130 prev = 0; 1131 while(inleft > 0) { 1132 size_t w = outleft; 1133 size_t rc = strconv(ic, &str, &inleft, &outStr, &outleft); 1134 w = w - outleft; 1135 readLen += w; 1136 1137 if(rc == (size_t)-1) { 1138 /* iconv wants more bytes */ 1139 int extendBuf = 0; 1140 switch(errno) { 1141 default: err = 1; content->iconverror = 1; break; 1142 case EILSEQ: { 1143 if(inleft > 0) { 1144 // replace with unicode replacement char 1145 if(outleft < 3) { 1146 // jump to extendBuf 1147 // next strconv run will try to convert 1148 // the same character, but this time 1149 // we have the space to store the 1150 // unicode replacement character 1151 extendBuf = 1; 1152 break; 1153 } 1154 1155 outStr[0] = 0xEF; 1156 outStr[1] = 0xBF; 1157 outStr[2] = 0xBD; 1158 1159 // add unconvertible character to the error list 1160 if(numEncErrors >= allocEncErrors) { 1161 allocEncErrors += 16; 1162 encErrors = NEditRealloc(encErrors, allocEncErrors * sizeof(EncError)); 1163 } 1164 encErrors[numEncErrors].c = (unsigned char)*str; 1165 encErrors[numEncErrors].pos = outStr - fileString; 1166 numEncErrors++; 1167 1168 1169 outStr += 3; 1170 outleft -= 3; 1171 readLen += 3; 1172 1173 str++; 1174 inleft--; 1175 1176 skipped++; 1177 } 1178 break; 1179 } 1180 case EINVAL: { 1181 memcpy(buf, str, inleft); 1182 prev = inleft; 1183 inleft = 0; 1184 break; 1185 } 1186 case E2BIG: { 1187 extendBuf = 1; 1188 break; 1189 } 1190 } 1191 1192 if(extendBuf) { 1193 // either strconv needs more space, or 1194 // the unicode replacement character couldn't be stored 1195 // -> extend buffer 1196 strAlloc += 512; 1197 size_t outpos = outStr - fileString; 1198 fileString = realloc(fileString, strAlloc + 1); 1199 if(!fileString) { 1200 err = 1; 1201 break; 1202 } 1203 outStr = fileString + outpos; 1204 outleft = strAlloc - readLen; 1205 } 1206 1207 if(err) { 1208 break; 1209 } 1210 } 1211 } 1212 } 1213 1214 if (filestream_close(stream) != 0) { 1215 content->closeerror = 1; 1216 content->err = errno; 1217 } 1218 fileString[readLen] = 0; 1219 1220 if(ic) { 1221 iconv_close(ic); 1222 } 1223 1224 content->hasBOM = hasBOM; 1225 content->skipped = skipped; 1226 1227 /* Detect and convert DOS and Macintosh format files */ 1228 if (GetPrefForceOSConversion()) { 1229 content->fileFormat = FormatOfFile(fileString); 1230 int rLen = readLen; 1231 if (content->fileFormat == DOS_FILE_FORMAT) { 1232 ConvertFromDosFileString(fileString, &rLen, NULL); 1233 } else if (content->fileFormat == MAC_FILE_FORMAT) { 1234 ConvertFromMacFileString(fileString, rLen); 1235 } 1236 readLen = rLen; 1237 } 1238 1239 if(err) { 1240 free(fileString); 1241 free(encErrors); 1242 } else { 1243 content->content = fileString; 1244 content->length = readLen; 1245 content->enc_errors = encErrors; 1246 content->num_enc_errors = numEncErrors; 1247 } 1248 1249 return err; 1250 } 1251 1252 int IncludeFile(WindowInfo *window, const char *name, const char *encoding, const char *filter_name) 1253 { 1254 int err = 0; 1255 1256 /* Open the file */ 1257 FileContent content; 1258 if(GetFileContent(window->shell, name, encoding, filter_name, &content)) { 1259 int filenameSet = window->filenameSet; 1260 if(content.isdir) { 1261 window->filenameSet = FALSE; /* Temp. prevent check for changes. */ 1262 DialogF(DF_ERR, window->shell, 1, "Error opening File", 1263 "Can''t open directory %s", "OK", name); 1264 window->filenameSet = filenameSet; 1265 } else if(content.isblk) { 1266 window->filenameSet = FALSE; /* Temp. prevent check for changes. */ 1267 DialogF(DF_ERR, window->shell, 1, "Error opening File", 1268 "Can''t open block device %s", "OK", name); 1269 window->filenameSet = filenameSet; 1270 } else if(content.allocerror) { 1271 window->filenameSet = FALSE; /* Temp. prevent check for changes. */ 1272 DialogF(DF_ERR, window->shell, 1, "Error while opening File", 1273 "File is too large to include", "OK"); 1274 window->filenameSet = filenameSet; 1275 } else if(content.iconverror) { 1276 window->filenameSet = FALSE; /* Temp. prevent check for changes. */ 1277 char *format = "File cannot be converted from %s to UTF8"; 1278 size_t msglen = strlen(format) + strlen(encoding) + 4; 1279 char *msgbuf = NEditMalloc(msglen); 1280 snprintf(msgbuf, msglen, format, encoding); 1281 DialogF(DF_ERR, window->shell, 1, "Error while opening File", 1282 msgbuf, "OK"); 1283 NEditFree(msgbuf); 1284 window->filenameSet = filenameSet; 1285 } else { 1286 window->filenameSet = FALSE; /* Temp. prevent check for changes. */ 1287 DialogF(DF_ERR, window->shell, 1, "Error while opening File", 1288 "Unknown error", "OK"); 1289 window->filenameSet = filenameSet; 1290 } 1291 return 0; 1292 } 1293 1294 if(content.skipped > 0) { 1295 int btn = DialogF(DF_WARN, window->shell, 2, "Encoding warning", 1296 "%d non-convertible characters skipped\n" 1297 "Include anyway?", "NO", "YES", 1298 content.skipped); 1299 if(btn == 1) { 1300 err = TRUE; 1301 } 1302 } 1303 1304 if(!err) { 1305 /* If the file contained ascii nulls, re-map them */ 1306 if (!BufSubstituteNullChars(content.content, content.length, window->buffer)) 1307 { 1308 DialogF(DF_ERR, window->shell, 1, "Error opening File", 1309 "Too much binary data in file", "OK"); 1310 } else { 1311 /* insert the contents of the file in the selection or at the insert 1312 position in the window if no selection exists */ 1313 if (window->buffer->primary.selected) { 1314 BufReplaceSelected(window->buffer, content.content); 1315 } else { 1316 BufInsert(window->buffer, TextGetCursorPos(window->lastFocus), content.content); 1317 } 1318 } 1319 } 1320 1321 NEditFree(content.content); 1322 NEditFree(content.enc_errors); 1323 1324 return TRUE; 1325 } 1326 1327 /* 1328 ** Close all files and windows, leaving one untitled window 1329 */ 1330 int CloseAllFilesAndWindows(void) 1331 { 1332 while (WindowList->next != NULL || 1333 WindowList->filenameSet || WindowList->fileChanged) { 1334 /* 1335 * When we're exiting through a macro, the document running the 1336 * macro does not disappear from the list, so we could get stuck 1337 * in an endless loop if we try to close it. Therefore, we close 1338 * other documents first. (Note that the document running the macro 1339 * may get closed because it is in the same window as another 1340 * document that gets closed, but it won't disappear; it becomes 1341 * Untitled.) 1342 */ 1343 if (WindowList == MacroRunWindow() && WindowList->next != NULL) { 1344 if (!CloseAllDocumentInWindow(WindowList->next)) { 1345 return False; 1346 } 1347 } 1348 else { 1349 if (!CloseAllDocumentInWindow(WindowList)) { 1350 return False; 1351 } 1352 } 1353 } 1354 1355 return TRUE; 1356 } 1357 1358 int CloseFileAndWindow(WindowInfo *window, int preResponse) 1359 { 1360 int response, stat; 1361 1362 /* Make sure that the window is not in iconified state */ 1363 if (window->fileChanged) 1364 RaiseDocumentWindow(window); 1365 1366 /* If the window is a normal & unmodified file or an empty new file, 1367 or if the user wants to ignore external modifications then 1368 just close it. Otherwise ask for confirmation first. */ 1369 if (!window->fileChanged && 1370 /* Normal File */ 1371 ((!window->fileMissing && window->lastModTime > 0) || 1372 /* New File*/ 1373 (window->fileMissing && window->lastModTime == 0) || 1374 /* File deleted/modified externally, ignored by user. */ 1375 !GetPrefWarnFileMods())) 1376 { 1377 CloseWindow(window); 1378 /* up-to-date windows don't have outstanding backup files to close */ 1379 } else 1380 { 1381 if (preResponse == PROMPT_SBC_DIALOG_RESPONSE) 1382 { 1383 response = DialogF(DF_WARN, window->shell, 3, "Save File", 1384 "Save %s before closing?", "Yes", "No", "Cancel", window->filename); 1385 } else 1386 { 1387 response = preResponse; 1388 } 1389 1390 if (response == YES_SBC_DIALOG_RESPONSE) 1391 { 1392 /* Save */ 1393 stat = SaveWindow(window); 1394 if (stat) 1395 { 1396 CloseWindow(window); 1397 } else 1398 { 1399 return FALSE; 1400 } 1401 } else if (response == NO_SBC_DIALOG_RESPONSE) 1402 { 1403 /* Don't Save */ 1404 RemoveBackupFile(window); 1405 CloseWindow(window); 1406 } else /* 3 == Cancel */ 1407 { 1408 return FALSE; 1409 } 1410 } 1411 return TRUE; 1412 } 1413 1414 int SaveWindow(WindowInfo *window) 1415 { 1416 int stat; 1417 1418 /* Try to ensure our information is up-to-date */ 1419 CheckForChangesToFile(window); 1420 1421 /* Return success if the file is normal & unchanged or is a 1422 read-only file. */ 1423 if ( (!window->fileChanged && !window->fileMissing && 1424 window->lastModTime > 0) || 1425 IS_ANY_LOCKED_IGNORING_PERM(window->lockReasons)) 1426 return TRUE; 1427 /* Prompt for a filename if this is an Untitled window */ 1428 if (!window->filenameSet) 1429 return SaveWindowAs(window, NULL); 1430 1431 /* Check for external modifications and warn the user */ 1432 if (GetPrefWarnFileMods() && fileWasModifiedExternally(window)) 1433 { 1434 stat = DialogF(DF_WARN, window->shell, 2, "Save File", 1435 "%s has been modified by another program.\n\n" 1436 "Continuing this operation will overwrite any external\n" 1437 "modifications to the file since it was opened in NEdit,\n" 1438 "and your work or someone else''s may potentially be lost.\n\n" 1439 "To preserve the modified file, cancel this operation and\n" 1440 "use Save As... to save this file under a different name,\n" 1441 "or Revert to Saved to revert to the modified version.", 1442 "Continue", "Cancel", window->filename); 1443 if (stat == 2) 1444 { 1445 /* Cancel and mark file as externally modified */ 1446 window->lastModTime = 0; 1447 window->fileMissing = FALSE; 1448 return FALSE; 1449 } 1450 } 1451 1452 if (writeBckVersion(window)) 1453 return FALSE; 1454 stat = doSave(window, 0); 1455 if (stat) 1456 RemoveBackupFile(window); 1457 1458 return stat; 1459 } 1460 1461 int SaveWindowAs(WindowInfo *window, FileSelection *file) 1462 { 1463 int response, retVal, fileFormat; 1464 char filename[MAXPATHLEN], pathname[MAXPATHLEN]; 1465 WindowInfo *otherWindow; 1466 char fullname[MAXPATHLEN]; 1467 1468 /* Get the new name for the file */ 1469 FileSelection newFile; 1470 if (!file) { 1471 memset(&newFile, 0, sizeof(FileSelection)); 1472 newFile.extraoptions = True; 1473 newFile.encoding = strlen(window->encoding) > 0 ? window->encoding : NULL; 1474 newFile.format = window->fileFormat; 1475 newFile.writebom = window->bom; 1476 1477 response = PromptForNewFile(window, "Save File As", &newFile, &fileFormat); 1478 if (response != GFN_OK) 1479 return FALSE; 1480 window->bom = newFile.writebom; 1481 window->fileFormat = newFile.format; 1482 SetFilter(window, newFile.filter); 1483 size_t pathlen = strlen(newFile.path); 1484 if(pathlen >= MAXPATHLEN) { 1485 fprintf(stderr, "Error: Path too long\n"); 1486 NEditFree(newFile.path); 1487 return FALSE; 1488 } 1489 memcpy(fullname, newFile.path, pathlen); 1490 fullname[pathlen] = '\0'; 1491 NEditFree(newFile.path); 1492 1493 if(newFile.encoding) { 1494 if(!strcmp(newFile.encoding, "UTF-8")) { 1495 window->encoding[0] = '\0'; 1496 } else { 1497 SetEncoding(window, newFile.encoding); 1498 } 1499 } 1500 1501 file = &newFile; 1502 } else 1503 { 1504 strcpy(fullname, file->path); 1505 if(!strcmp(file->encoding, "UTF-8")) { 1506 window->encoding[0] = '\0'; 1507 } else { 1508 SetEncoding(window, file->encoding); 1509 } 1510 window->bom = file->writebom; 1511 window->fileFormat = file->format; 1512 SetFilter(window, file->filter); 1513 } 1514 1515 if (1 == NormalizePathname(fullname)) 1516 { 1517 return False; 1518 } 1519 1520 /* Add newlines if requested */ 1521 if (file->addwrap) 1522 addWrapNewlines(window); 1523 1524 if (ParseFilename(fullname, filename, pathname) != 0) { 1525 return FALSE; 1526 } 1527 1528 /* If the requested file is this file, just save it and return */ 1529 if (!strcmp(window->filename, filename) && 1530 !strcmp(window->path, pathname)) { 1531 if (writeBckVersion(window)) 1532 return FALSE; 1533 return doSave(window, file->setxattr); 1534 } 1535 1536 /* If the file is open in another window, make user close it. Note that 1537 it is possible for user to close the window by hand while the dialog 1538 is still up, because the dialog is not application modal, so after 1539 doing the dialog, check again whether the window still exists. */ 1540 otherWindow = FindWindowWithFile(filename, pathname); 1541 if (otherWindow != NULL) 1542 { 1543 response = DialogF(DF_WARN, window->shell, 2, "File open", 1544 "%s is open in another XNEdit window", "Cancel", 1545 "Close Other Window", filename); 1546 1547 if (response == 1) 1548 { 1549 return FALSE; 1550 } 1551 if (otherWindow == FindWindowWithFile(filename, pathname)) 1552 { 1553 if (!CloseFileAndWindow(otherWindow, PROMPT_SBC_DIALOG_RESPONSE)) 1554 { 1555 return FALSE; 1556 } 1557 } 1558 } 1559 1560 /* Destroy the file closed property for the original file */ 1561 DeleteFileClosedProperty(window); 1562 1563 /* Change the name of the file and save it under the new name */ 1564 RemoveBackupFile(window); 1565 strcpy(window->filename, filename); 1566 strcpy(window->path, pathname); 1567 window->fileMode = 0; 1568 window->fileUid = 0; 1569 window->fileGid = 0; 1570 CLEAR_ALL_LOCKS(window->lockReasons); 1571 retVal = doSave(window, file->setxattr); 1572 UpdateWindowReadOnly(window); 1573 RefreshTabState(window); 1574 1575 /* Add the name to the convenience menu of previously opened files */ 1576 AddToPrevOpenMenu(fullname); 1577 1578 /* If name has changed, language mode may have changed as well, unless 1579 it's an Untitled window for which the user already set a language 1580 mode; it's probably the right one. */ 1581 if (PLAIN_LANGUAGE_MODE == window->languageMode || window->filenameSet) { 1582 DetermineLanguageMode(window, False); 1583 } 1584 window->filenameSet = True; 1585 1586 /* Update the stats line and window title with the new filename */ 1587 UpdateWindowTitle(window); 1588 UpdateStatsLine(window); 1589 1590 SortTabBar(window); 1591 return retVal; 1592 } 1593 1594 static int getBOM(char *encoding, char **bom) 1595 { 1596 int len = 0; 1597 *bom = NULL; 1598 if(!encoding || strlen(encoding) == 0 || !strcasecmp(encoding, "UTF-8")) { 1599 *bom = bom_utf8; 1600 len = 3; 1601 } else if(!strcasecmp(encoding, "UTF-16BE")) { 1602 *bom = bom_utf16be; 1603 len = 2; 1604 } else if(!strcasecmp(encoding, "UTF-16LE")) { 1605 *bom = bom_utf16le; 1606 len = 2; 1607 } else if(!strcasecmp(encoding, "UTF-32BE")) { 1608 *bom = bom_utf32be; 1609 len = 4; 1610 } else if(!strcasecmp(encoding, "UTF-32LE")) { 1611 *bom = bom_utf32le; 1612 len = 4; 1613 } else if(!strcasecmp(encoding, "GB18030")) { 1614 *bom = bom_gb18030; 1615 len = 4; 1616 } else if(!strcasecmp(encoding, "UTF-EBCDIC")) { 1617 *bom = bom_utfebcdic; 1618 len = 4; 1619 } 1620 return len; 1621 } 1622 1623 static int doSave(WindowInfo *window, Boolean setEncAttr) 1624 { 1625 char *fileString = NULL; 1626 char fullname[MAXPATHLEN]; 1627 struct stat statbuf; 1628 FILE *fp; 1629 int fileLen, result; 1630 1631 iconv_t ic = NULL; 1632 ConvertFunc strconv = copyBytes; 1633 if(strlen(window->encoding) > 0) { 1634 ic = iconv_open(window->encoding, "UTF-8"); 1635 if(ic == (iconv_t) -1) { 1636 DialogF(DF_ERR, window->shell, 1, "Error saving File", 1637 "The text cannot be converted to %s", "OK", window->encoding); 1638 return FALSE; 1639 } 1640 strconv = (ConvertFunc)iconv; 1641 } 1642 1643 /* Get the full name of the file */ 1644 strcpy(fullname, window->path); 1645 strcat(fullname, window->filename); 1646 1647 /* Check for root and warn him if he wants to write to a file with 1648 none of the write bits set. */ 1649 if ((0 == getuid()) 1650 && (0 == stat(fullname, &statbuf)) 1651 && !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))) 1652 { 1653 result = DialogF(DF_WARN, window->shell, 2, "Writing Read-only File", 1654 "File ''%s'' is marked as read-only.\n" 1655 "Do you want to save anyway?", 1656 "Save", "Cancel", window->filename); 1657 if (1 != result) 1658 { 1659 return True; 1660 } 1661 } 1662 1663 1664 /* add a terminating newline if the file doesn't already have one for 1665 Unix utilities which get confused otherwise 1666 NOTE: this must be done _before_ we create/open the file, because the 1667 (potential) buffer modification can trigger a check for file 1668 changes. If the file is created for the first time, it has 1669 zero size on disk, and the check would falsely conclude that the 1670 file has changed on disk, and would pop up a warning dialog */ 1671 if (BufGetCharacter(window->buffer, window->buffer->length - 1) != '\n' 1672 && window->buffer->length != 0 1673 && GetPrefAppendLF()) 1674 { 1675 BufInsert(window->buffer, window->buffer->length, "\n"); 1676 } 1677 1678 /* open the file */ 1679 fp = fopen(fullname, "wb"); 1680 if (fp == NULL) 1681 { 1682 result = DialogF(DF_WARN, window->shell, 2, "Error saving File", 1683 "Unable to save %s:\n%s\n\nSave as a new file?", 1684 "Save As...", "Cancel", 1685 window->filename, errorString()); 1686 1687 if (result == 1) 1688 { 1689 return SaveWindowAs(window, NULL); 1690 } 1691 return FALSE; 1692 } 1693 1694 /* get the text buffer contents and its length */ 1695 fileString = BufGetAll(window->buffer); 1696 fileLen = window->buffer->length; 1697 1698 /* If null characters are substituted for, put them back */ 1699 BufUnsubstituteNullChars(fileString, window->buffer); 1700 1701 /* If the file is to be saved in DOS or Macintosh format, reconvert */ 1702 if (window->fileFormat == DOS_FILE_FORMAT) 1703 { 1704 if (!ConvertToDosFileString(&fileString, &fileLen)) 1705 { 1706 DialogF(DF_ERR, window->shell, 1, "Out of Memory", 1707 "Out of memory! Try\nsaving in Unix format", "OK"); 1708 fclose(fp); 1709 return FALSE; 1710 } 1711 } else if (window->fileFormat == MAC_FILE_FORMAT) 1712 { 1713 ConvertToMacFileString(fileString, fileLen); 1714 } 1715 1716 /* write to the file */ 1717 IOFilter *filter = GetFilterFromName(window->filter); 1718 char *filter_cmd = NULL; 1719 if(filter && filter->cmdout && strlen(filter->cmdout) > 0) { 1720 filter_cmd = filter->cmdout; 1721 } 1722 FileStream *stream = filestream_open_w(window->shell, fp, filter_cmd); 1723 1724 /* write bom if requsted */ 1725 if(window->bom) { 1726 char *bom; 1727 int bomLen = getBOM(window->encoding, &bom); 1728 if(bomLen > 0) { 1729 if(filestream_write(bom, bomLen, stream) != bomLen) { 1730 fileLen = 0; 1731 } 1732 } 1733 } 1734 1735 /* convert text if required and write it to the file */ 1736 int skipped = 0; 1737 int nonreversible = 0; 1738 int unerr = 0; 1739 char buf[IO_BUFSIZE]; 1740 char *in = fileString; 1741 size_t inleft = fileLen; 1742 while(in) { 1743 char *out = buf; 1744 size_t outleft = IO_BUFSIZE; 1745 size_t w = outleft; 1746 if (inleft == 0) { 1747 /* be sure to flush out any partially converted input */ 1748 in = NULL; 1749 } 1750 size_t rc = strconv(ic, &in, &inleft, &out, &outleft); 1751 w -= outleft; 1752 1753 if(w > 0) { 1754 filestream_write(buf, w, stream); 1755 } 1756 1757 if(rc == (size_t)-1) { 1758 size_t skip; 1759 switch (errno) { 1760 case EILSEQ: 1761 case EINVAL: 1762 /* An invalid multibyte sequence is encountered in the input */ 1763 skip = Utf8CharLen((const unsigned char*)in); 1764 ++skipped; 1765 in += skip; 1766 if(inleft >= skip) { 1767 inleft -= skip; 1768 } else { 1769 inleft = 0; 1770 } 1771 break; 1772 case E2BIG: 1773 /* Conversion succeeded but output buffer is full */ 1774 break; 1775 default: 1776 /* Unknown error encountered */ 1777 ++unerr; 1778 } 1779 } 1780 1781 if (inleft == 0) { 1782 /* add # of nonreversible conversions */ 1783 nonreversible += rc; 1784 } 1785 } 1786 1787 unsigned int eresp = 0; 1788 if (skipped > 0 || nonreversible > 0 || unerr > 0) { 1789 eresp = DialogF(DF_WARN, window->shell, 2, "Encoding warning", 1790 "%d non-convertible characters skipped\n" 1791 "%d non-reversible characters encountered\n" 1792 "%d unknown errors occurred\n" 1793 "Save anyway?", "YES", "NO", 1794 skipped, nonreversible, unerr); 1795 } 1796 1797 if(ic) { 1798 iconv_close(ic); 1799 } 1800 1801 if (ferror(fp)) 1802 { 1803 DialogF(DF_ERR, window->shell, 1, "Error saving File", 1804 "%s not saved:\n%s", "OK", window->filename, errorString()); 1805 } 1806 1807 if (ferror(fp) || eresp == 2) { 1808 filestream_close(stream); 1809 remove(fullname); 1810 NEditFree(fileString); 1811 return FALSE; 1812 } 1813 1814 if(setEncAttr) { 1815 size_t encLen = strlen(window->encoding); 1816 char *encStr; 1817 char *encCopy = NULL; 1818 if(encLen == 0) { 1819 encStr = "utf-8"; 1820 encLen = 5; 1821 } else { 1822 encCopy = NEditStrdup(window->encoding); 1823 for(int i=0;i<encLen;i++) { 1824 encCopy[i] = tolower(encCopy[i]); 1825 } 1826 encStr = encCopy; 1827 } 1828 1829 if(xattr_set(fullname, "charset", encStr, encLen)) { 1830 perror("xattr_set failed"); 1831 } 1832 1833 if(encCopy) { 1834 NEditFree(encCopy); 1835 } 1836 } else { 1837 ssize_t len = 0; 1838 char *fileAttr = xattr_get(fullname, "charset", &len); 1839 if(fileAttr) { 1840 size_t winEncLen = strlen(window->encoding); 1841 size_t cmpLen = winEncLen > len ? len : winEncLen; 1842 if(len != winEncLen || memcmp(fileAttr, window->encoding, cmpLen)) { 1843 if(xattr_remove(fullname, "charset")) { 1844 DialogF(DF_ERR, window->shell, 1, "Error saving File", 1845 "Cannot remove previous charset attribute"); 1846 } 1847 } 1848 free(fileAttr); 1849 } 1850 } 1851 1852 /* close the file */ 1853 if (filestream_close(stream) != 0) 1854 { 1855 DialogF(DF_ERR, window->shell, 1, "Error closing File", 1856 "Error closing file:\n%s", "OK", errorString()); 1857 NEditFree(fileString); 1858 return FALSE; 1859 } 1860 1861 /* free the text buffer copy returned from XmTextGetString */ 1862 NEditFree(fileString); 1863 1864 /* success, file was written */ 1865 SetWindowModified(window, FALSE); 1866 1867 /* update the modification time */ 1868 if (stat(fullname, &statbuf) == 0) { 1869 window->lastModTime = statbuf.st_mtime; 1870 window->fileMissing = FALSE; 1871 window->device = statbuf.st_dev; 1872 window->inode = statbuf.st_ino; 1873 } else { 1874 /* This needs to produce an error message -- the file can't be 1875 accessed! */ 1876 window->lastModTime = 0; 1877 window->fileMissing = TRUE; 1878 window->device = 0; 1879 window->inode = 0; 1880 } 1881 1882 return TRUE; 1883 } 1884 1885 /* 1886 ** Create a backup file for the current window. The name for the backup file 1887 ** is generated using the name and path stored in the window and adding a 1888 ** tilde (~) on UNIX and underscore (_) on VMS to the beginning of the name. 1889 */ 1890 int WriteBackupFile(WindowInfo *window) 1891 { 1892 char *fileString = NULL; 1893 char name[MAXPATHLEN]; 1894 FILE *fp; 1895 int fd, fileLen; 1896 1897 /* Generate a name for the autoSave file */ 1898 backupFileName(window, name, sizeof(name)); 1899 1900 /* remove the old backup file. 1901 Well, this might fail - we'll notice later however. */ 1902 remove(name); 1903 1904 /* open the file, set more restrictive permissions (using default 1905 permissions was somewhat of a security hole, because permissions were 1906 independent of those of the original file being edited */ 1907 if ((fd = open(name, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR | S_IWUSR)) < 0 1908 || (fp = fdopen(fd, "w")) == NULL) 1909 { 1910 DialogF(DF_WARN, window->shell, 1, "Error writing Backup", 1911 "Unable to save backup for %s:\n%s\n" 1912 "Automatic backup is now off", "OK", window->filename, 1913 errorString()); 1914 window->autoSave = FALSE; 1915 SetToggleButtonState(window, window->autoSaveItem, FALSE, FALSE); 1916 return FALSE; 1917 } 1918 1919 /* get the text buffer contents and its length */ 1920 fileString = BufGetAll(window->buffer); 1921 fileLen = window->buffer->length; 1922 1923 /* If null characters are substituted for, put them back */ 1924 BufUnsubstituteNullChars(fileString, window->buffer); 1925 1926 /* add a terminating newline if the file doesn't already have one */ 1927 if (fileLen != 0 && fileString[fileLen-1] != '\n') 1928 fileString[fileLen++] = '\n'; /* null terminator no longer needed */ 1929 1930 /* write out the file */ 1931 fwrite(fileString, sizeof(char), fileLen, fp); 1932 if (ferror(fp)) 1933 { 1934 DialogF(DF_ERR, window->shell, 1, "Error saving Backup", 1935 "Error while saving backup for %s:\n%s\n" 1936 "Automatic backup is now off", "OK", window->filename, 1937 errorString()); 1938 fclose(fp); 1939 remove(name); 1940 NEditFree(fileString); 1941 window->autoSave = FALSE; 1942 return FALSE; 1943 } 1944 1945 /* close the backup file */ 1946 if (fclose(fp) != 0) { 1947 NEditFree(fileString); 1948 return FALSE; 1949 } 1950 1951 /* Free the text buffer copy returned from XmTextGetString */ 1952 NEditFree(fileString); 1953 1954 return TRUE; 1955 } 1956 1957 /* 1958 ** Remove the backup file associated with this window 1959 */ 1960 void RemoveBackupFile(WindowInfo *window) 1961 { 1962 char name[MAXPATHLEN]; 1963 1964 /* Don't delete backup files when backups aren't activated. */ 1965 if (window->autoSave == FALSE) 1966 return; 1967 1968 backupFileName(window, name, sizeof(name)); 1969 remove(name); 1970 } 1971 1972 /* 1973 ** Generate the name of the backup file for this window from the filename 1974 ** and path in the window data structure & write into name 1975 */ 1976 static void backupFileName(WindowInfo *window, char *name, size_t len) 1977 { 1978 char bckname[MAXPATHLEN]; 1979 if (window->filenameSet) 1980 { 1981 sprintf(name, "%s~%s", window->path, window->filename); 1982 } else 1983 { 1984 strcpy(bckname, "~"); 1985 strncat(bckname, window->filename, MAXPATHLEN - 1); 1986 PrependHome(bckname, name, len); 1987 } 1988 } 1989 1990 /* 1991 ** If saveOldVersion is on, copies the existing version of the file to 1992 ** <filename>.bck in anticipation of a new version being saved. Returns 1993 ** True if backup fails and user requests that the new file not be written. 1994 */ 1995 static int writeBckVersion(WindowInfo *window) 1996 { 1997 char fullname[MAXPATHLEN], bckname[MAXPATHLEN]; 1998 struct stat statbuf; 1999 int in_fd, out_fd; 2000 char *io_buffer; 2001 #define IO_BUFFER_SIZE ((size_t)(1024*1024)) 2002 2003 /* Do only if version backups are turned on */ 2004 if (!window->saveOldVersion) { 2005 return False; 2006 } 2007 2008 /* Get the full name of the file */ 2009 strcpy(fullname, window->path); 2010 strcat(fullname, window->filename); 2011 2012 /* Generate name for old version */ 2013 if ((strlen(fullname) + 5) > (size_t) MAXPATHLEN) { 2014 return bckError(window, "file name too long", window->filename); 2015 } 2016 if(snprintf(bckname, MAXPATHLEN, "%s.bck", fullname) >= MAXPATHLEN) { 2017 return FALSE; 2018 } 2019 2020 /* Delete the old backup file */ 2021 /* Errors are ignored; we'll notice them later. */ 2022 remove(bckname); 2023 2024 /* open the file being edited. If there are problems with the 2025 old file, don't bother the user, just skip the backup */ 2026 in_fd = open(fullname, O_RDONLY); 2027 if (in_fd<0) { 2028 return FALSE; 2029 } 2030 2031 /* Get permissions of the file. 2032 We preserve the normal permissions but not ownership, extended 2033 attributes, et cetera. */ 2034 if (fstat(in_fd, &statbuf) != 0) { 2035 close(in_fd); 2036 return FALSE; 2037 } 2038 2039 /* open the destination file exclusive and with restrictive permissions. */ 2040 out_fd = open(bckname, O_CREAT|O_EXCL|O_TRUNC|O_WRONLY, S_IRUSR | S_IWUSR); 2041 if (out_fd < 0) { 2042 close(in_fd); 2043 return bckError(window, "Error open backup file", bckname); 2044 } 2045 2046 /* Set permissions on new file */ 2047 if (fchmod(out_fd, statbuf.st_mode) != 0) { 2048 close(in_fd); 2049 close(out_fd); 2050 remove(bckname); 2051 return bckError(window, "fchmod() failed", bckname); 2052 } 2053 2054 /* Allocate I/O buffer */ 2055 io_buffer = (char*) malloc(IO_BUFFER_SIZE); 2056 if (NULL == io_buffer) { 2057 close(in_fd); 2058 close(out_fd); 2059 remove(bckname); 2060 return bckError(window, "out of memory", bckname); 2061 } 2062 2063 /* copy loop */ 2064 for(;;) { 2065 ssize_t bytes_read; 2066 ssize_t bytes_written; 2067 bytes_read = read(in_fd, io_buffer, IO_BUFFER_SIZE); 2068 2069 if (bytes_read < 0) { 2070 close(in_fd); 2071 close(out_fd); 2072 remove(bckname); 2073 free(io_buffer); 2074 return bckError(window, "read() error", window->filename); 2075 } 2076 2077 if (0 == bytes_read) { 2078 break; /* EOF */ 2079 } 2080 2081 /* write to the file */ 2082 bytes_written = write(out_fd, io_buffer, (size_t) bytes_read); 2083 if (bytes_written != bytes_read) { 2084 close(in_fd); 2085 close(out_fd); 2086 remove(bckname); 2087 free(io_buffer); 2088 return bckError(window, errorString(), bckname); 2089 } 2090 } 2091 2092 /* close the input and output files */ 2093 close(in_fd); 2094 close(out_fd); 2095 2096 free(io_buffer); 2097 2098 return FALSE; 2099 } 2100 2101 /* 2102 ** Error processing for writeBckVersion, gives the user option to cancel 2103 ** the subsequent save, or continue and optionally turn off versioning 2104 */ 2105 static int bckError(WindowInfo *window, const char *errString, const char *file) 2106 { 2107 int resp; 2108 2109 resp = DialogF(DF_ERR, window->shell, 3, "Error writing Backup", 2110 "Couldn''t write .bck (last version) file.\n%s: %s", "Cancel Save", 2111 "Turn off Backups", "Continue", file, errString); 2112 if (resp == 1) 2113 return TRUE; 2114 if (resp == 2) { 2115 window->saveOldVersion = FALSE; 2116 SetToggleButtonState(window, window->saveLastItem, FALSE, FALSE); 2117 } 2118 return FALSE; 2119 } 2120 2121 void PrintWindow(WindowInfo *window, int selectedOnly) 2122 { 2123 textBuffer *buf = window->buffer; 2124 selection *sel = &buf->primary; 2125 char *fileString = NULL; 2126 int fileLen; 2127 2128 /* get the contents of the text buffer from the text area widget. Add 2129 wrapping newlines if necessary to make it match the displayed text */ 2130 if (selectedOnly) { 2131 if (!sel->selected) { 2132 XBell(TheDisplay, 0); 2133 return; 2134 } 2135 if (sel->rectangular) { 2136 fileString = BufGetSelectionText(buf); 2137 fileLen = strlen(fileString); 2138 } else 2139 fileString = TextGetWrapped(window->textArea, sel->start, sel->end, 2140 &fileLen); 2141 } else 2142 fileString = TextGetWrapped(window->textArea, 0, buf->length, &fileLen); 2143 2144 /* If null characters are substituted for, put them back */ 2145 BufUnsubstituteNullChars(fileString, buf); 2146 2147 /* add a terminating newline if the file doesn't already have one */ 2148 if (fileLen != 0 && fileString[fileLen-1] != '\n') 2149 fileString[fileLen++] = '\n'; /* null terminator no longer needed */ 2150 2151 /* Print the string */ 2152 PrintString(fileString, fileLen, window->shell, window->filename); 2153 2154 /* Free the text buffer copy returned from XmTextGetString */ 2155 NEditFree(fileString); 2156 } 2157 2158 /* 2159 ** Print a string (length is required). parent is the dialog parent, for 2160 ** error dialogs, and jobName is the print title. 2161 */ 2162 void PrintString(const char *string, int length, Widget parent, const char *jobName) 2163 { 2164 char tmpFileName[L_tmpnam]; /* L_tmpnam defined in stdio.h */ 2165 FILE *fp; 2166 int fd; 2167 2168 /* Generate a temporary file name */ 2169 /* If the glibc is used, the linker issues a warning at this point. This is 2170 very thoughtful of him, but does not apply to NEdit. The recommended 2171 replacement mkstemp(3) uses the same algorithm as NEdit, namely 2172 1. Create a filename 2173 2. Open the file with the O_CREAT|O_EXCL flags 2174 So all an attacker can do is a DoS on the print function. */ 2175 #ifdef __GLIBC__ 2176 mkstemp(tmpFileName); 2177 #else 2178 tmpnam(tmpFileName); 2179 #endif 2180 2181 /* open the temporary file */ 2182 if ((fd = open(tmpFileName, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR | S_IWUSR)) < 0 || (fp = fdopen(fd, "w")) == NULL) 2183 { 2184 DialogF(DF_WARN, parent, 1, "Error while Printing", 2185 "Unable to write file for printing:\n%s", "OK", 2186 errorString()); 2187 return; 2188 } 2189 2190 /* write to the file */ 2191 fwrite(string, sizeof(char), length, fp); 2192 if (ferror(fp)) 2193 { 2194 DialogF(DF_ERR, parent, 1, "Error while Printing", 2195 "%s not printed:\n%s", "OK", jobName, errorString()); 2196 fclose(fp); /* should call close(fd) in turn! */ 2197 remove(tmpFileName); 2198 return; 2199 } 2200 2201 /* close the temporary file */ 2202 if (fclose(fp) != 0) 2203 { 2204 DialogF(DF_ERR, parent, 1, "Error while Printing", 2205 "Error closing temp. print file:\n%s", "OK", 2206 errorString()); 2207 remove(tmpFileName); 2208 return; 2209 } 2210 2211 /* Print the temporary file, then delete it and return success */ 2212 PrintFile(parent, tmpFileName, jobName); 2213 remove(tmpFileName); 2214 return; 2215 } 2216 2217 /* 2218 ** Wrapper for GetExistingFilename which uses the current window's path 2219 ** (if set) as the default directory. 2220 */ 2221 int PromptForExistingFile(WindowInfo *window, char *prompt, FileSelection *file) 2222 { 2223 char *savedDefaultDir; 2224 int retVal; 2225 2226 /* Temporarily set default directory to window->path, prompt for file, 2227 then, if the call was unsuccessful, restore the original default 2228 directory */ 2229 savedDefaultDir = GetFileDialogDefaultDirectory(); 2230 if (*window->path != '\0') 2231 SetFileDialogDefaultDirectory(window->path); 2232 retVal = GetExistingFilename(window->shell, prompt, file); 2233 if (retVal != GFN_OK) 2234 SetFileDialogDefaultDirectory(savedDefaultDir); 2235 2236 NEditFree(savedDefaultDir); 2237 2238 return retVal; 2239 } 2240 2241 /* 2242 ** Wrapper for HandleCustomNewFileSB which uses the current window's path 2243 ** (if set) as the default directory, and asks about embedding newlines 2244 ** to make wrapping permanent. 2245 */ 2246 int PromptForNewFile(WindowInfo *window, char *prompt, FileSelection *file, 2247 int *fileFormat) 2248 { 2249 int retVal; 2250 char *savedDefaultDir; 2251 2252 *fileFormat = window->fileFormat; 2253 2254 /* Temporarily set default directory to window->path, prompt for file, 2255 then, if the call was unsuccessful, restore the original default 2256 directory */ 2257 savedDefaultDir = GetFileDialogDefaultDirectory(); 2258 if (*window->path != '\0') 2259 SetFileDialogDefaultDirectory(window->path); 2260 2261 char *prevPath = NULL; 2262 if(window->path[0] != '\0' && window->filename[0] != '\0' && window->filenameSet) { 2263 size_t plen = strlen(window->path); 2264 size_t nlen = strlen(window->filename); 2265 prevPath = NEditMalloc(plen + nlen + 2); 2266 memcpy(prevPath, window->path, plen); 2267 if(window->path[plen-1] != '/') { 2268 prevPath[plen] = '/'; 2269 plen++; 2270 } 2271 memcpy(prevPath+plen, window->filename, nlen); 2272 prevPath[plen+nlen] = '\0'; 2273 } 2274 2275 file->path = prevPath; 2276 retVal = GetNewFilename(window->shell, prompt, file, ""); 2277 if(prevPath) { 2278 NEditFree(prevPath); 2279 } 2280 2281 if (retVal != GFN_OK) 2282 SetFileDialogDefaultDirectory(savedDefaultDir); 2283 2284 NEditFree(savedDefaultDir); 2285 2286 return retVal; 2287 } 2288 2289 /* 2290 ** Find a name for an untitled file, unique in the name space of in the opened 2291 ** files in this session, i.e. Untitled or Untitled_nn, and write it into 2292 ** the string "name". 2293 */ 2294 void UniqueUntitledName(char *name) 2295 { 2296 WindowInfo *w; 2297 int i; 2298 2299 for (i=0; i<INT_MAX; i++) { 2300 if (i == 0) 2301 sprintf(name, "Untitled"); 2302 else 2303 sprintf(name, "Untitled_%d", i); 2304 for (w=WindowList; w!=NULL; w=w->next) 2305 if (!strcmp(w->filename, name)) 2306 break; 2307 if (w == NULL) 2308 break; 2309 } 2310 } 2311 2312 /* 2313 ** Callback that guards us from trying to access a window after it has 2314 ** been destroyed while a modal dialog is up. 2315 */ 2316 static void modifiedWindowDestroyedCB(Widget w, XtPointer clientData, 2317 XtPointer callData) 2318 { 2319 *(Bool*)clientData = TRUE; 2320 } 2321 2322 /* 2323 ** Check if the file in the window was changed by an external source. 2324 ** and put up a warning dialog if it has. 2325 */ 2326 void CheckForChangesToFile(WindowInfo *window) 2327 { 2328 static WindowInfo* lastCheckWindow = NULL; 2329 static Time lastCheckTime = 0; 2330 char fullname[MAXPATHLEN]; 2331 struct stat statbuf; 2332 Time timestamp; 2333 FILE *fp; 2334 int resp, silent = 0; 2335 XWindowAttributes winAttr; 2336 Boolean windowIsDestroyed = False; 2337 2338 if(!window->filenameSet) 2339 return; 2340 2341 /* If last check was very recent, don't impact performance */ 2342 timestamp = XtLastTimestampProcessed(XtDisplay(window->shell)); 2343 if (window == lastCheckWindow && 2344 timestamp - lastCheckTime < MOD_CHECK_INTERVAL) 2345 return; 2346 lastCheckWindow = window; 2347 lastCheckTime = timestamp; 2348 2349 /* Update the status, but don't pop up a dialog if we're called 2350 from a place where the window might be iconic (e.g., from the 2351 replace dialog) or on another desktop. 2352 2353 This works, but I bet it costs a round-trip to the server. 2354 Might be better to capture MapNotify/Unmap events instead. 2355 2356 For tabs that are not on top, we don't want the dialog either, 2357 and we don't even need to contact the server to find out. By 2358 performing this check first, we avoid a server round-trip for 2359 most files in practice. */ 2360 if (!IsTopDocument(window)) 2361 silent = 1; 2362 else { 2363 XGetWindowAttributes(XtDisplay(window->shell), 2364 XtWindow(window->shell), 2365 &winAttr); 2366 2367 if (winAttr.map_state != IsViewable) 2368 silent = 1; 2369 } 2370 2371 /* Get the file mode and modification time */ 2372 strcpy(fullname, window->path); 2373 strcat(fullname, window->filename); 2374 if (stat(fullname, &statbuf) != 0) { 2375 /* Return if we've already warned the user or we can't warn him now */ 2376 if (window->fileMissing || silent) { 2377 return; 2378 } 2379 2380 /* Can't stat the file -- maybe it's been deleted. 2381 The filename is now invalid */ 2382 window->fileMissing = TRUE; 2383 window->lastModTime = 1; 2384 window->device = 0; 2385 window->inode = 0; 2386 2387 /* Warn the user, if they like to be warned (Maybe this should be its 2388 own preference setting: GetPrefWarnFileDeleted()) */ 2389 if (GetPrefWarnFileMods()) { 2390 char* title; 2391 char* body; 2392 2393 /* See note below about pop-up timing and XUngrabPointer */ 2394 XUngrabPointer(XtDisplay(window->shell), timestamp); 2395 2396 /* If the window (and the dialog) are destroyed while the dialog 2397 is up (typically closed via the window manager), we should 2398 avoid accessing the window afterwards. */ 2399 XtAddCallback(window->shell, XmNdestroyCallback, 2400 modifiedWindowDestroyedCB, &windowIsDestroyed); 2401 2402 /* Set title, message body and button to match stat()'s error. */ 2403 switch (errno) { 2404 case ENOENT: 2405 /* A component of the path file_name does not exist. */ 2406 title = "File not Found"; 2407 body = "File ''%s'' (or directory in its path)\n" 2408 "no longer exists.\n" 2409 "Another program may have deleted or moved it."; 2410 resp = DialogF(DF_ERR, window->shell, 2, title, body, 2411 "Save", "Cancel", window->filename); 2412 break; 2413 case EACCES: 2414 /* Search permission denied for a path component. We add 2415 one to the response because Re-Save wouldn't really 2416 make sense here. */ 2417 title = "Permission Denied"; 2418 body = "You no longer have access to file ''%s''.\n" 2419 "Another program may have changed the permissions of\n" 2420 "one of its parent directories."; 2421 resp = 1 + DialogF(DF_ERR, window->shell, 1, title, body, 2422 "Cancel", window->filename); 2423 break; 2424 default: 2425 /* Everything else. This hints at an internal error (eg. 2426 ENOTDIR) or at some bad state at the host. */ 2427 title = "File not Accessible"; 2428 body = "Error while checking the status of file ''%s'':\n" 2429 " ''%s''\n" 2430 "Please make sure that no data is lost before closing\n" 2431 "this window."; 2432 resp = DialogF(DF_ERR, window->shell, 2, title, body, 2433 "Save", "Cancel", window->filename, 2434 errorString()); 2435 break; 2436 } 2437 2438 if (!windowIsDestroyed) { 2439 XtRemoveCallback(window->shell, XmNdestroyCallback, 2440 modifiedWindowDestroyedCB, &windowIsDestroyed); 2441 } 2442 2443 switch (resp) { 2444 case 1: 2445 SaveWindow(window); 2446 break; 2447 /* Good idea, but this leads to frequent crashes, see 2448 SF#1578869. Reinsert this if circumstances change by 2449 uncommenting this part and inserting a "Close" button 2450 before each Cancel button above. 2451 case 2: 2452 CloseWindow(window); 2453 return; 2454 */ 2455 } 2456 } 2457 2458 /* A missing or (re-)saved file can't be read-only. */ 2459 /* TODO: A document without a file can be locked though. */ 2460 /* Make sure that the window was not destroyed behind our back! */ 2461 if (!windowIsDestroyed) { 2462 SET_PERM_LOCKED(window->lockReasons, False); 2463 UpdateWindowTitle(window); 2464 UpdateWindowReadOnly(window); 2465 } 2466 return; 2467 } 2468 2469 /* Check that the file's read-only status is still correct (but 2470 only if the file can still be opened successfully in read mode) */ 2471 if (window->fileMode != statbuf.st_mode || 2472 window->fileUid != statbuf.st_uid || 2473 window->fileGid != statbuf.st_gid) { 2474 window->fileMode = statbuf.st_mode; 2475 window->fileUid = statbuf.st_uid; 2476 window->fileGid = statbuf.st_gid; 2477 if ((fp = fopen(fullname, "r")) != NULL) { 2478 int readOnly; 2479 fclose(fp); 2480 #ifndef DONT_USE_ACCESS 2481 readOnly = access(fullname, W_OK) != 0; 2482 #else 2483 if (((fp = fopen(fullname, "r+")) != NULL)) { 2484 readOnly = FALSE; 2485 fclose(fp); 2486 } else 2487 readOnly = TRUE; 2488 #endif 2489 if (IS_PERM_LOCKED(window->lockReasons) != readOnly) { 2490 SET_PERM_LOCKED(window->lockReasons, readOnly); 2491 UpdateWindowTitle(window); 2492 UpdateWindowReadOnly(window); 2493 } 2494 } 2495 } 2496 2497 /* Warn the user if the file has been modified, unless checking is 2498 turned off or the user has already been warned. Popping up a dialog 2499 from a focus callback (which is how this routine is usually called) 2500 seems to catch Motif off guard, and if the timing is just right, the 2501 dialog can be left with a still active pointer grab from a Motif menu 2502 which is still in the process of popping down. The workaround, below, 2503 of calling XUngrabPointer is inelegant but seems to fix the problem. */ 2504 if (!silent && 2505 ((window->lastModTime != 0 && 2506 window->lastModTime != statbuf.st_mtime) || 2507 window->fileMissing) ){ 2508 window->lastModTime = 0; /* Inhibit further warnings */ 2509 window->fileMissing = FALSE; 2510 if (!GetPrefWarnFileMods()) 2511 return; 2512 if (GetPrefWarnRealFileMods() && 2513 !cmpWinAgainstFile(window, fullname)) { 2514 /* Contents hasn't changed. Update the modification time. */ 2515 window->lastModTime = statbuf.st_mtime; 2516 return; 2517 } 2518 XUngrabPointer(XtDisplay(window->shell), timestamp); 2519 if (window->fileChanged) 2520 resp = DialogF(DF_WARN, window->shell, 2, 2521 "File modified externally", 2522 "%s has been modified by another program. Reload?\n\n" 2523 "WARNING: Reloading will discard changes made in this\n" 2524 "editing session!", "Reload", "Cancel", window->filename); 2525 else 2526 resp = DialogF(DF_WARN, window->shell, 2, 2527 "File modified externally", 2528 "%s has been modified by another\nprogram. Reload?", 2529 "Reload", "Cancel", window->filename); 2530 if (resp == 1) 2531 RevertToSaved(window, NULL); 2532 } 2533 } 2534 2535 /* 2536 ** Return true if the file displayed in window has been modified externally 2537 ** to nedit. This should return FALSE if the file has been deleted or is 2538 ** unavailable. 2539 */ 2540 static int fileWasModifiedExternally(WindowInfo *window) 2541 { 2542 char fullname[MAXPATHLEN]; 2543 struct stat statbuf; 2544 2545 if(!window->filenameSet) 2546 return FALSE; 2547 /* if (window->lastModTime == 0) 2548 return FALSE; */ 2549 strcpy(fullname, window->path); 2550 strcat(fullname, window->filename); 2551 if (stat(fullname, &statbuf) != 0) 2552 return FALSE; 2553 if (window->lastModTime == statbuf.st_mtime) 2554 return FALSE; 2555 if (GetPrefWarnRealFileMods() && 2556 !cmpWinAgainstFile(window, fullname)) { 2557 return FALSE; 2558 } 2559 return TRUE; 2560 } 2561 2562 /* 2563 ** Check the read-only or locked status of the window and beep and return 2564 ** false if the window should not be written in. 2565 */ 2566 int CheckReadOnly(WindowInfo *window) 2567 { 2568 if (IS_ANY_LOCKED(window->lockReasons)) { 2569 XBell(TheDisplay, 0); 2570 return True; 2571 } 2572 return False; 2573 } 2574 2575 /* 2576 ** Wrapper for strerror 2577 */ 2578 static const char *errorString(void) 2579 { 2580 return strerror(errno); 2581 } 2582 2583 2584 /* 2585 ** Callback procedure for toggle button requesting newlines to be inserted 2586 ** to emulate continuous wrapping. 2587 */ 2588 // TODO: reimplement this error message in the new filedialog 2589 /* 2590 static void addWrapCB(Widget w, XtPointer clientData, XtPointer callData) 2591 { 2592 int resp; 2593 int *addWrap = (int *)clientData; 2594 2595 if (XmToggleButtonGetState(w)) 2596 { 2597 resp = DialogF(DF_WARN, w, 2, "Add Wrap", 2598 "This operation adds permanent line breaks to\n" 2599 "match the automatic wrapping done by the\n" 2600 "Continuous Wrap mode Preferences Option.\n\n" 2601 "*** This Option is Irreversable ***\n\n" 2602 "Once newlines are inserted, continuous wrapping\n" 2603 "will no longer work automatically on these lines", "OK", 2604 "Cancel"); 2605 if (resp == 2) 2606 { 2607 XmToggleButtonSetState(w, False, False); 2608 *addWrap = False; 2609 } else 2610 { 2611 *addWrap = True; 2612 } 2613 } else 2614 { 2615 *addWrap = False; 2616 } 2617 } 2618 */ 2619 2620 /* 2621 ** Change a window created in NEdit's continuous wrap mode to the more 2622 ** conventional Unix format of embedded newlines. Indicate to the user 2623 ** by turning off Continuous Wrap mode. 2624 */ 2625 static void addWrapNewlines(WindowInfo *window) 2626 { 2627 int fileLen, i, insertPositions[MAX_PANES], topLines[MAX_PANES]; 2628 int horizOffset; 2629 Widget text; 2630 char *fileString; 2631 2632 /* save the insert and scroll positions of each pane */ 2633 for (i=0; i<=window->nPanes; i++) { 2634 text = i==0 ? window->textArea : window->textPanes[i-1]; 2635 insertPositions[i] = TextGetCursorPos(text); 2636 TextGetScroll(text, &topLines[i], &horizOffset); 2637 } 2638 2639 /* Modify the buffer to add wrapping */ 2640 fileString = TextGetWrapped(window->textArea, 0, 2641 window->buffer->length, &fileLen); 2642 BufSetAll(window->buffer, fileString); 2643 NEditFree(fileString); 2644 2645 /* restore the insert and scroll positions of each pane */ 2646 for (i=0; i<=window->nPanes; i++) { 2647 text = i==0 ? window->textArea : window->textPanes[i-1]; 2648 TextSetCursorPos(text, insertPositions[i]); 2649 TextSetScroll(text, topLines[i], 0); 2650 } 2651 2652 /* Show the user that something has happened by turning off 2653 Continuous Wrap mode */ 2654 SetToggleButtonState(window, window->continuousWrapItem, False, True); 2655 } 2656 2657 /* 2658 * Number of bytes read at once by cmpWinAgainstFile 2659 */ 2660 #define PREFERRED_CMPBUF_LEN 32768 2661 2662 /* 2663 * Check if the contens of the textBuffer *buf is equal 2664 * the contens of the file named fileName. The format of 2665 * the file (UNIX/DOS/MAC) is handled properly. 2666 * 2667 * Return values 2668 * 0: no difference found 2669 * !0: difference found or could not compare contents. 2670 */ 2671 static int cmpWinAgainstFile(WindowInfo *window, const char *fileName) 2672 { 2673 char fileString[PREFERRED_CMPBUF_LEN + 2]; 2674 struct stat statbuf; 2675 int fileLen, restLen, nRead, bufPos, rv, offset, filePos; 2676 char pendingCR = 0; 2677 int fileFormat = window->fileFormat; 2678 char message[MAXPATHLEN+50]; 2679 textBuffer *buf = window->buffer; 2680 FILE *fp; 2681 2682 fp = fopen(fileName, "r"); 2683 if (!fp) 2684 return (1); 2685 if (fstat(fileno(fp), &statbuf) != 0) { 2686 fclose(fp); 2687 return (1); 2688 } 2689 2690 fileLen = statbuf.st_size; 2691 /* For DOS files, we can't simply check the length */ 2692 if (fileFormat != DOS_FILE_FORMAT) { 2693 if (fileLen != buf->length) { 2694 fclose(fp); 2695 return (1); 2696 } 2697 } else { 2698 /* If a DOS file is smaller on disk, it's certainly different */ 2699 if (fileLen < buf->length) { 2700 fclose(fp); 2701 return (1); 2702 } 2703 } 2704 2705 /* For large files, the comparison can take a while. If it takes too long, 2706 the user should be given a clue about what is happening. */ 2707 sprintf(message, "Comparing externally modified %s ...", window->filename); 2708 restLen = min(PREFERRED_CMPBUF_LEN, fileLen); 2709 bufPos = 0; 2710 filePos = 0; 2711 while (restLen > 0) { 2712 AllWindowsBusy(message); 2713 if (pendingCR) { 2714 fileString[0] = pendingCR; 2715 offset = 1; 2716 } else { 2717 offset = 0; 2718 } 2719 2720 nRead = fread(fileString+offset, sizeof(char), restLen, fp); 2721 if (nRead != restLen) { 2722 fclose(fp); 2723 AllWindowsUnbusy(); 2724 return (1); 2725 } 2726 filePos += nRead; 2727 2728 nRead += offset; 2729 2730 /* check for on-disk file format changes, but only for the first hunk */ 2731 if (bufPos == 0 && fileFormat != FormatOfFile(fileString)) { 2732 fclose(fp); 2733 AllWindowsUnbusy(); 2734 return (1); 2735 } 2736 2737 if (fileFormat == MAC_FILE_FORMAT) 2738 ConvertFromMacFileString(fileString, nRead); 2739 else if (fileFormat == DOS_FILE_FORMAT) 2740 ConvertFromDosFileString(fileString, &nRead, &pendingCR); 2741 2742 /* Beware of 0 chars ! */ 2743 BufSubstituteNullChars(fileString, nRead, buf); 2744 rv = BufCmp(buf, bufPos, nRead, fileString); 2745 if (rv) { 2746 fclose(fp); 2747 AllWindowsUnbusy(); 2748 return (rv); 2749 } 2750 bufPos += nRead; 2751 restLen = min(fileLen - filePos, PREFERRED_CMPBUF_LEN); 2752 } 2753 AllWindowsUnbusy(); 2754 fclose(fp); 2755 if (pendingCR) { 2756 rv = BufCmp(buf, bufPos, 1, &pendingCR); 2757 if (rv) { 2758 return (rv); 2759 } 2760 bufPos += 1; 2761 } 2762 if (bufPos != buf->length) { 2763 return (1); 2764 } 2765 return (0); 2766 } 2767 2768 /* 2769 ** Force ShowLineNumbers() to re-evaluate line counts for the window if line 2770 ** counts are required. 2771 */ 2772 static void forceShowLineNumbers(WindowInfo *window) 2773 { 2774 Boolean showLineNum = window->showLineNumbers; 2775 if (showLineNum) { 2776 window->showLineNumbers = False; 2777 ShowLineNumbers(window, showLineNum); 2778 } 2779 } 2780 2781 static int min(int i1, int i2) 2782 { 2783 return i1 <= i2 ? i1 : i2; 2784 } 2785 2786 static const char * GetDefaultEncoding(void) { 2787 const char *fallback = GetPrefFallbackCharset(); 2788 if(strcmp(fallback, "locale")) { 2789 return fallback; 2790 } 2791 2792 char *lc = setlocale (LC_ALL, ""); 2793 char *d = strchr(lc, '.'); 2794 if(d) { 2795 *d = 0; 2796 } 2797 2798 int i = 0; 2799 while(locales[i].locale) { 2800 if(!strcmp(locales[i].locale, lc)) { 2801 return locales[i].encoding; 2802 } 2803 i++; 2804 } 2805 2806 return "ISO8859-1"; 2807 } 2808 2809 const char * DetectEncoding(const char *buf, size_t len, const char *def) { 2810 int utf8Err = 0; // number of utf8 encoding errors 2811 int utf8Mb = 0; // number of multibyte characters 2812 2813 const unsigned char *u = (const unsigned char*)buf; 2814 int charLen = 0; // length of char - 1 2815 for(size_t i=0;i<len;i++) { 2816 unsigned char c = u[i]; 2817 if(charLen == 0) { 2818 if(c >= 240) { 2819 charLen = 3; 2820 } else if(c >= 224) { 2821 charLen = 2; 2822 } else if(c > 192) { 2823 charLen = 1; 2824 } 2825 } else { 2826 if((c & 192) == 128) { 2827 if(--charLen == 0) { 2828 utf8Mb++; 2829 } 2830 } else { 2831 utf8Err++; 2832 charLen = 0; 2833 } 2834 } 2835 } 2836 2837 if(utf8Err == 0 || utf8Mb - utf8Err > 2) { 2838 return "UTF-8"; 2839 } 2840 2841 char defbuf[4]; 2842 memset(defbuf, 0, 4); 2843 if(def && strlen(def) > 3) { 2844 memcpy(defbuf, def, 3); 2845 } 2846 if(!strcasecmp(defbuf, "utf")) { 2847 return GetDefaultEncoding(); 2848 } 2849 2850 return def; 2851 } 2852 2853 /* 2854 * If available, get the charset xattr value 2855 */ 2856 static char* getEncodingAttribute(const char *path) 2857 { 2858 char *enc_attr = NULL; 2859 2860 ssize_t attrlen = 0; 2861 enc_attr = xattr_get(path, "charset", &attrlen); 2862 /* enc_attr is null-terminated */ 2863 if(enc_attr) { 2864 if(attrlen == 0) { 2865 free(enc_attr); 2866 return NULL; 2867 } 2868 2869 // check if we need to trim the value 2870 if(isspace(enc_attr[0]) || isspace(enc_attr[attrlen-1])) { 2871 char *enc_trim = enc_attr; 2872 size_t etlen = attrlen; 2873 while(etlen > 0 && isspace(*enc_trim)) { 2874 enc_trim++; 2875 etlen--; 2876 } 2877 while(etlen > 0 && isspace(enc_trim[etlen-1])) { 2878 etlen--; 2879 } 2880 enc_trim[etlen] = '\0'; 2881 2882 if(etlen == 0) { 2883 free(enc_attr); 2884 return NULL; 2885 } 2886 2887 char *enc_str = malloc(attrlen + 1); 2888 if(enc_str) { 2889 memcpy(enc_str, enc_trim, etlen+1); 2890 } 2891 free(enc_attr); 2892 enc_attr = enc_str; 2893 } 2894 } 2895 2896 return enc_attr; 2897 } 2898