CVS rebase
[nedit-bw.git] / handle_hardlink.patch
blob37428cad7a62a17a692e7f79629b163b6e2591e7
1 ---
3 doc/help.etx | 19 +++++++++
4 source/file.c | 107 +++++++++++++++++++++++++++++++++++++++++++++------
5 source/menu.c | 51 +++++++++++++++++++++++-
6 source/nedit.h | 6 ++
7 source/preferences.c | 20 +++++++++
8 source/preferences.h | 2
9 source/server.c | 6 ++
10 source/window.c | 33 +++++++++++++++
11 source/window.h | 1
12 9 files changed, 232 insertions(+), 13 deletions(-)
14 diff --quilt old/source/file.c new/source/file.c
15 --- old/source/file.c
16 +++ new/source/file.c
17 @@ -62,10 +62,11 @@ static const char CVSID[] = "$Id: file.c
18 #include <stat.h>
19 #include <unixio.h>
20 #else
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 +#include <utime.h>
24 #ifndef __MVS__
25 #include <sys/param.h>
26 #endif
27 #include <fcntl.h>
28 #endif /*VMS*/
29 @@ -183,10 +184,12 @@ WindowInfo *EditExistingFile(WindowInfo
30 const char *path, int flags, char *geometry, int iconic,
31 const char *languageMode, int tabbed, int bgOpen)
33 WindowInfo *window;
34 char fullname[MAXPATHLEN];
35 + struct stat statbuf;
36 + int response, doUnlink = 0;
38 /* first look to see if file is already displayed in a window */
39 window = FindWindowWithFile(name, path);
40 if (window != NULL) {
41 if (!bgOpen) {
42 @@ -195,11 +198,42 @@ WindowInfo *EditExistingFile(WindowInfo
43 else
44 RaiseDocumentWindow(window);
46 return window;
50 + /* Get the full name of the file */
51 + strcpy(fullname, path);
52 + strcat(fullname, name);
54 + if (GetPrefHardlinkMode() != HARDLINK_IGNORE
55 + && stat(fullname, &statbuf) == 0
56 + && statbuf.st_nlink > 1) {
58 + response = 0;
59 + window = FindWindowWithInode(name, path);
60 + if (window != NULL) {
61 + if (GetPrefHardlinkMode() == HARDLINK_PROMPT) {
62 + response = DialogF(DF_INF, window->shell, 3, "Open File",
63 + "%s has more than one hardlink\n"
64 + "and is already opened in an other window.\n\n"
65 + "Unlink this file after open (make this file uniq)\n"
66 + "or show the other window?",
67 + "Open & Unlink", "Show other", "Cancel", fullname);
68 + }
70 + if (response == 3)
71 + return NULL;
72 + else if (response == 2) {
73 + RaiseShellWindow(window->shell, True);
74 + return window;
75 + } else if (response == 1 || GetPrefHardlinkMode() == HARDLINK_UNLINK) {
76 + doUnlink = 1;
77 + }
78 + }
79 + }
81 /* If an existing window isn't specified; or the window is already
82 in use (not Untitled or Untitled and modified), or is currently
83 busy running a macro; create the window */
84 if (inWindow == NULL) {
85 window = CreateWindow(name, geometry, iconic);
86 @@ -252,16 +286,32 @@ WindowInfo *EditExistingFile(WindowInfo
87 UpdateWindowTitle(window);
88 UpdateWindowReadOnly(window);
89 UpdateStatsLine(window);
91 /* Add the name to the convenience menu of previously opened files */
92 - strcpy(fullname, path);
93 - strcat(fullname, name);
94 if(GetPrefAlwaysCheckRelTagsSpecs())
95 AddRelTagsFile(GetPrefTagFile(), path, TAG);
96 AddToPrevOpenMenu(fullname);
98 + if (doUnlink) {
99 + if (unlink(fullname) != 0) {
100 + DialogF(DF_ERR, window->shell, 1,
101 + "Error open File",
102 + "Error can't unlink %s.\n"
103 + "Changes are made to multiple opended files.",
104 + "Continue", fullname);
105 + } else {
106 + struct utimbuf utimbuf;
107 + doSave(window);
108 + chmod(fullname, statbuf.st_mode);
109 + chown(fullname, statbuf.st_uid, statbuf.st_gid);
110 + utimbuf.actime = statbuf.st_atime;
111 + utimbuf.modtime = statbuf.st_mtime;
112 + utime(fullname, &utimbuf);
116 MacroApplyHook(window, "post_open_hook", 0, NULL, NULL);
118 return window;
121 @@ -887,12 +937,18 @@ int CloseFileAndWindow(WindowInfo *windo
122 return TRUE;
125 int SaveWindow(WindowInfo *window)
127 - int stat;
129 + int response;
130 + struct stat statbuf;
131 + char fullname[MAXPATHLEN];
133 + /* Get the full name of the file */
134 + strcpy(fullname, window->path);
135 + strcat(fullname, window->filename);
137 /* Try to ensure our information is up-to-date */
138 CheckForChangesToFile(window);
140 /* Return success if the file is normal & unchanged or is a
141 read-only file. */
142 @@ -905,39 +961,66 @@ int SaveWindow(WindowInfo *window)
143 return SaveWindowAs(window, NULL, False);
145 /* Check for external modifications and warn the user */
146 if (GetPrefWarnFileMods() && fileWasModifiedExternally(window))
148 - stat = DialogF(DF_WARN, window->shell, 2, "Save File",
149 + response = DialogF(DF_WARN, window->shell, 2, "Save File",
150 "%s has been modified by another program.\n\n"
151 "Continuing this operation will overwrite any external\n"
152 "modifications to the file since it was opened in NEdit,\n"
153 "and your work or someone else's may potentially be lost.\n\n"
154 "To preserve the modified file, cancel this operation and\n"
155 "use Save As... to save this file under a different name,\n"
156 "or Revert to Saved to revert to the modified version.",
157 "Continue", "Cancel", window->filename);
158 - if (stat == 2)
159 + if (response == 2)
161 /* Cancel and mark file as externally modified */
162 window->lastModTime = 0;
163 window->fileMissing = FALSE;
164 return FALSE;
169 + response = 0;
170 + if (GetPrefHardlinkMode() != HARDLINK_IGNORE
171 + && stat(fullname, &statbuf) == 0
172 + && statbuf.st_nlink > 1) {
174 + if (GetPrefHardlinkMode() == HARDLINK_PROMPT
175 + && window->hardlinkDontPromptAgain == False) {
176 + response = DialogF(DF_QUES, window->shell, 3, "Save File",
177 + "%s has more than one hardlink.\n\n"
178 + "Unlink this file before saving (make this file uniq)?",
179 + "Unlink & Save", "Save", "Cancel", fullname);
182 + if (response == 3) {
183 + return FALSE;
184 + } else if (response == 2) {
185 + window->hardlinkDontPromptAgain = True;
186 + } else if (GetPrefHardlinkMode() == HARDLINK_UNLINK || response == 1) {
187 + if (unlink(fullname) != 0) {
188 + DialogF(DF_ERR, window->shell, 1,
189 + "Error saving File",
190 + "Error can't unlink %s",
191 + "Cancel", fullname);
196 #ifdef VMS
197 RemoveBackupFile(window);
198 - stat = doSave(window);
199 + response = doSave(window);
200 #else
201 if (writeBckVersion(window))
202 return FALSE;
203 - stat = doSave(window);
204 - if (stat)
205 + response = doSave(window);
206 + if (response)
207 RemoveBackupFile(window);
208 #endif /*VMS*/
209 - return stat;
210 + return response;
213 int SaveWindowAs(WindowInfo *window, const char *newName, int addWrap)
215 int response, retVal, fileFormat;
216 diff --quilt old/source/menu.c new/source/menu.c
217 --- old/source/menu.c
218 +++ new/source/menu.c
219 @@ -139,10 +139,13 @@ static void statsCB(Widget w, WindowInfo
220 static void autoIndentOffDefCB(Widget w, WindowInfo *window, caddr_t callData);
221 static void autoIndentDefCB(Widget w, WindowInfo *window, caddr_t callData);
222 static void smartIndentDefCB(Widget w, WindowInfo *window, caddr_t callData);
223 static void autoSaveDefCB(Widget w, WindowInfo *window, caddr_t callData);
224 static void preserveDefCB(Widget w, WindowInfo *window, caddr_t callData);
225 +static void ignoreHardlinkDefCB(Widget w, WindowInfo *window, caddr_t callData);
226 +static void promptHardlinkDefCB(Widget w, WindowInfo *window, caddr_t callData);
227 +static void unlinkHardlinkDefCB(Widget w, WindowInfo *window, caddr_t callData);
228 static void noWrapDefCB(Widget w, WindowInfo *window, caddr_t callData);
229 static void newlineWrapDefCB(Widget w, WindowInfo *window, caddr_t callData);
230 static void contWrapDefCB(Widget w, WindowInfo *window, caddr_t callData);
231 static void wrapMarginDefCB(Widget w, WindowInfo *window, caddr_t callData);
232 static void shellSelDefCB(Widget widget, WindowInfo* window, caddr_t callData);
233 @@ -1016,11 +1019,23 @@ Widget CreateMenuBar(Widget parent, Wind
234 GetPrefSaveOldVersion(), SHORT);
235 window->autoSaveDefItem = createMenuToggle(subPane, "incrementalBackup",
236 "Incremental Backup", 'B', autoSaveDefCB, window, GetPrefAutoSave(),
237 SHORT);
240 + /* Hardlink mode default sub menu */
241 + subSubPane = createMenu(subPane, "hardlinkMode", "Hardlinks", 'H',
242 + NULL, FULL);
243 + window->hardlinkDefItem[HARDLINK_IGNORE] = createMenuRadioToggle(
244 + subSubPane, "ignore", "Ignore", 'I', ignoreHardlinkDefCB, window,
245 + GetPrefHardlinkMode() == HARDLINK_IGNORE, SHORT);
246 + window->hardlinkDefItem[HARDLINK_PROMPT] = createMenuRadioToggle(
247 + subSubPane, "prompt", "Prompt", 'P', promptHardlinkDefCB, window,
248 + GetPrefHardlinkMode() == HARDLINK_PROMPT, SHORT);
249 + window->hardlinkDefItem[HARDLINK_UNLINK] = createMenuRadioToggle(
250 + subSubPane, "unlink", "Unlink", 'U', unlinkHardlinkDefCB, window,
251 + GetPrefHardlinkMode() == HARDLINK_UNLINK, SHORT);
253 /* Show Matching sub menu */
254 subSubPane = createMenu(subPane, "showMatching", "Show Matching (..)", 'M',
255 NULL, FULL);
256 window->showMatchingOffDefItem = createMenuRadioToggle(subSubPane, "off",
257 "Off", 'O', showMatchingOffDefCB, window,
258 @@ -1860,10 +1875,44 @@ static void preserveDefCB(Widget w, Wind
259 if (IsTopDocument(win))
260 XmToggleButtonSetState(win->saveLastDefItem, state, False);
264 +static void setHardlinkModeMenu(enum hardlinkMode mode)
266 + WindowInfo *win;
267 + int i;
269 + if (mode >= N_HARDLINK_MODES) {
270 + return;
273 + /* Set the preference and make the other windows' menus agree */
274 + SetPrefHardlinkMode(mode);
275 + for (win = WindowList; win != NULL; win = win->next) {
276 + for (i = 0; i < N_HARDLINK_MODES; i++) {
277 + XmToggleButtonSetState(win->hardlinkDefItem[i],
278 + mode == i ? True : False, False);
283 +static void ignoreHardlinkDefCB(Widget w, WindowInfo *window, caddr_t callData)
285 + setHardlinkModeMenu(HARDLINK_IGNORE);
288 +static void promptHardlinkDefCB(Widget w, WindowInfo *window, caddr_t callData)
290 + setHardlinkModeMenu(HARDLINK_PROMPT);
293 +static void unlinkHardlinkDefCB(Widget w, WindowInfo *window, caddr_t callData)
295 + setHardlinkModeMenu(HARDLINK_UNLINK);
298 static void fontDefCB(Widget w, WindowInfo *window, caddr_t callData)
300 HidePointerOnKeyedEvent(WidgetToWindow(MENU_WIDGET(w))->lastFocus,
301 ((XmAnyCallbackStruct *)callData)->event);
302 ChooseFonts(WidgetToWindow(MENU_WIDGET(w)), False);
303 diff --quilt old/source/nedit.h new/source/nedit.h
304 --- old/source/nedit.h
305 +++ new/source/nedit.h
306 @@ -111,10 +111,14 @@ enum showWrapMarginEnums {SHOW_WRAP_MARG
308 /* This enum must be kept in parallel to the array TruncSubstitutionModes[]
309 in preferences.c */
310 enum truncSubstitution {TRUNCSUBST_SILENT, TRUNCSUBST_FAIL, TRUNCSUBST_WARN, TRUNCSUBST_IGNORE};
312 +/* This enum must be kept in sync with HardlinkModes[] in in preferences.c */
313 +enum hardlinkMode {HARDLINK_IGNORE, HARDLINK_PROMPT, HARDLINK_UNLINK,
314 + N_HARDLINK_MODES};
316 #define NO_FLASH_STRING "off"
317 #define FLASH_DELIMIT_STRING "delimiter"
318 #define FLASH_RANGE_STRING "range"
320 #define CHARSET (XmStringCharSet)XmSTRING_DEFAULT_CHARSET
321 @@ -568,10 +572,12 @@ typedef struct _WindowInfo {
322 UserMenuCache *userMenuCache; /* cache user menus: */
323 UserBGMenuCache userBGMenuCache; /* shell & macro menu are shared over all
324 "tabbed" documents, while each document
325 has its own background menu. */
326 int inMacroHook; /* to protect GC in MacroApplyHook() */
327 + Widget hardlinkDefItem[N_HARDLINK_MODES];
328 + Boolean hardlinkDontPromptAgain;
329 } WindowInfo;
331 extern WindowInfo *WindowList;
332 extern Display *TheDisplay;
333 extern Widget TheAppShell;
334 diff --quilt old/source/preferences.c new/source/preferences.c
335 --- old/source/preferences.c
336 +++ new/source/preferences.c
337 @@ -167,10 +167,17 @@ static char* TruncSubstitutionModes[] =
338 #define DEFAULT_WRAP -1
339 #define DEFAULT_INDENT -1
340 #define DEFAULT_TAB_DIST -1
341 #define DEFAULT_EM_TAB_DIST -1
343 +static char *HardlinkModes[] = {
344 + "Ignore",
345 + "Prompt",
346 + "Unlink",
347 + NULL
350 /* list of available language modes and language specific preferences */
351 static int NLanguageModes = 0;
352 typedef struct {
353 char *name;
354 int nExtensions;
355 @@ -337,10 +344,11 @@ static struct prefData {
356 int focusOnRaise;
357 Boolean honorSymlinks;
358 int truncSubstitution;
359 Boolean forceOSConversion;
360 Boolean showScrolltip;
361 + int hardlinkMode;
362 } PrefData;
364 /* Temporary storage for preferences strings which are discarded after being
365 read */
366 static struct {
367 @@ -1167,10 +1175,12 @@ static PrefDescripRec PrefDescrip[] = {
368 &PrefData.honorSymlinks, NULL, False},
369 {"showCursorline", "ShowCursorline", PREF_BOOLEAN, "True",
370 &PrefData.showCursorline, NULL, True},
371 // {"showScrolltip", "ShowScrolltip", PREF_BOOLEAN, "True",
372 // &PrefData.showScrolltip, NULL, False},
373 + {"hardlinkMode", "HardlinkMode", PREF_ENUM, "Ignore",
374 + &PrefData.hardlinkMode, HardlinkModes, True},
377 static XrmOptionDescRec OpTable[] = {
378 {"-wrap", ".autoWrap", XrmoptionNoArg, (caddr_t)"Continuous"},
379 {"-nowrap", ".autoWrap", XrmoptionNoArg, (caddr_t)"None"},
380 @@ -2313,10 +2323,20 @@ Boolean GetPrefShowScrolltip(void)
382 //return PrefData.showScrolltip;
383 return True;
386 +void SetPrefHardlinkMode(int mode)
388 + setIntPref(&PrefData.hardlinkMode, mode);
391 +int GetPrefHardlinkMode(void)
393 + return PrefData.hardlinkMode;
397 ** If preferences don't get saved, ask the user on exit whether to save
399 void MarkPrefsChanged(void)
401 diff --quilt old/source/preferences.h new/source/preferences.h
402 --- old/source/preferences.h
403 +++ new/source/preferences.h
404 @@ -213,7 +213,9 @@ Boolean GetPrefUndoModifiesSelection(voi
405 Boolean GetPrefFocusOnRaise(void);
406 Boolean GetPrefHonorSymlinks(void);
407 Boolean GetPrefForceOSConversion(void);
408 void SetPrefFocusOnRaise(Boolean);
409 Boolean GetPrefShowScrolltip(void);
410 +void SetPrefHardlinkMode(int mode);
411 +int GetPrefHardlinkMode(void);
413 #endif /* NEDIT_PREFERENCES_H_INCLUDED */
414 diff --quilt old/source/server.c new/source/server.c
415 --- old/source/server.c
416 +++ new/source/server.c
417 @@ -344,10 +344,11 @@ static void processServerCommandString(c
418 int lineNum, createFlag, readFlag, iconicFlag, lastIconic = 0, tabbed = -1;
419 int fileLen, doLen, lmLen, geomLen, charsRead, itemsRead;
420 WindowInfo *window, *lastFile = NULL;
421 long currentDesktop = QueryCurrentDesktop(TheDisplay,
422 RootWindow(TheDisplay, DefaultScreen(TheDisplay)));
423 + WindowInfo *window_ino;
425 /* If the command string is empty, put up an empty, Untitled window
426 (or just pop one up if it already exists) */
427 if (string[0] == '\0') {
428 for (window=WindowList; window!=NULL; window=window->next)
429 @@ -471,10 +472,15 @@ static void processServerCommandString(c
430 macros to execute on. */
431 window = EditExistingFile(findWindowOnDesktop(tabbed, currentDesktop),
432 filename, pathname, editFlags, geometry, iconicFlag,
433 lmLen == 0 ? NULL : langMode,
434 tabbed == -1? GetPrefOpenInTab() : tabbed, True);
435 + window_ino = FindWindowWithInode(filename, pathname);
436 + if (window != NULL && window == window_ino) {
437 + deleteFileOpenProperty2(filename, pathname);
438 + /* deleteFileClosedProperty2(filename, pathname); */
441 if (window) {
442 CleanUpTabBarExposeQueue(window);
443 if (lastFile && window->shell != lastFile->shell) {
444 CleanUpTabBarExposeQueue(lastFile);
445 diff --quilt old/source/window.c new/source/window.c
446 --- old/source/window.c
447 +++ new/source/window.c
448 @@ -331,10 +331,11 @@ WindowInfo *CreateWindow(const char *nam
449 window->findLastLiteralCase = FALSE;
450 window->tab = NULL;
451 window->device = 0;
452 window->inode = 0;
453 window->inMacroHook = 0;
454 + window->hardlinkDontPromptAgain = False;
456 /* If window geometry was specified, split it apart into a window position
457 component and a window size component. Create a new geometry string
458 containing the position component only. Rows and cols are stripped off
459 because we can't easily calculate the size in pixels from them until the
460 @@ -1200,10 +1201,41 @@ WindowInfo *FindWindowWithFile(const cha
462 return NULL;
466 +** Check if there is already a window open for a given inode
468 +WindowInfo *FindWindowWithInode(const char *name, const char *path)
470 + WindowInfo *w;
471 + char fullname[MAXPATHLEN];
472 + struct stat statbuf;
473 + ino_t ino;
474 + dev_t dev;
476 + strcpy(fullname, path);
477 + strcat(fullname, name);
479 + if (stat(fullname, &statbuf) < 0) {
480 + return NULL;
483 + ino = statbuf.st_ino;
484 + dev = statbuf.st_dev;
486 + for (w = WindowList; w != NULL; w = w->next) {
487 + strcpy(fullname, w->path);
488 + strcat(fullname, w->filename);
489 + if (w->inode == ino && w->device == dev) {
490 + return w;
493 + return NULL;
497 ** Add another independently scrollable pane to the current document,
498 ** splitting the pane which currently has keyboard focus.
500 void SplitPane(WindowInfo *window)
502 @@ -4530,10 +4562,11 @@ static void cloneDocument(WindowInfo *wi
503 window->findLastLiteralCase = orgWin->findLastLiteralCase;
504 window->device = orgWin->device;
505 window->inode = orgWin->inode;
506 window->fileClosedAtom = orgWin->fileClosedAtom;
507 orgWin->fileClosedAtom = None;
508 + window->hardlinkDontPromptAgain = orgWin->hardlinkDontPromptAgain;
510 /* copy the text/split panes settings, cursor pos & selection */
511 cloneTextPanes(window, orgWin);
513 /* copy undo & redo list */
514 diff --quilt old/source/window.h new/source/window.h
515 --- old/source/window.h
516 +++ new/source/window.h
517 @@ -45,10 +45,11 @@ void UpdateMinPaneHeights(WindowInfo *wi
518 void UpdateNewOppositeMenu(WindowInfo *window, int openInTab);
519 void SetWindowModified(WindowInfo *window, int modified);
520 void MakeSelectionVisible(WindowInfo *window, Widget textPane);
521 int GetSimpleSelection(textBuffer *buf, int *left, int *right);
522 WindowInfo *FindWindowWithFile(const char *name, const char *path);
523 +WindowInfo *FindWindowWithInode(const char *name, const char *path);
524 void SetAutoIndent(WindowInfo *window, int state);
525 void SetShowMatching(WindowInfo *window, int state);
526 void SetFonts(WindowInfo *window, const char *fontName, const char *italicName,
527 const char *boldName, const char *boldItalicName);
528 void SetColors(WindowInfo *window, const char *textFg, const char *textBg,
529 diff --quilt old/doc/help.etx new/doc/help.etx
530 --- old/doc/help.etx
531 +++ new/doc/help.etx
532 @@ -4109,10 +4109,29 @@ Preferences
534 **Show Tooltips**
535 Show file name and path in a tooltip when moving the mouse pointer over a tab.
536 (See Tabbed_Editing_.)
538 +**Hardlinks**
539 + On UNIX systems it is possible to have a physical same file with different
540 + filenames. Because it can be dangerous to change a file which is accessible
541 + from different filenames, NEdit can warn the user either when the user writes
542 + to a file with multiple hardlinks, or if the file is already opened with a
543 + different name.
545 + ~Ignore~
546 + Ignore any hardlink informations.
548 + ~Prompt~
549 + Prompt the user in thes two cases. For the open case the user can choose to
550 + open the other window with the file, or make the file to open a destine copy
551 + of the original file. For the save case the user can save the file under the
552 + name but a new destine file or ignore it.
554 + ~Unlink~
555 + Always make a destine copy of the file.
557 **Show Cursorline**
558 Background the current line with a colored bar. Use the color dialog to
559 change the background color.
561 **Terminate with Line Break on Save**