1 Subject: handle hardlinks
3 Gives the user a choice what happend if he open a file which is currently
4 opened with an other path:
7 * ask the user how to handle it
8 * automatic re-save the to-be-open file to a new inode
10 Similar when you save a file that has more than one hardlink.
14 doc/help.etx | 19 +++++++++
15 source/file.c | 107 +++++++++++++++++++++++++++++++++++++++++++++------
16 source/menu.c | 51 +++++++++++++++++++++++-
18 source/preferences.c | 20 +++++++++
19 source/preferences.h | 2
20 source/server.c | 6 ++
21 source/window.c | 33 +++++++++++++++
23 9 files changed, 232 insertions(+), 13 deletions(-)
25 diff --quilt old/source/file.c new/source/file.c
28 @@ -64,6 +64,7 @@ static const char CVSID[] = "$Id: file.c
30 #include <sys/types.h>
34 #include <sys/param.h>
36 @@ -207,6 +208,8 @@ WindowInfo *EditExistingFile(WindowInfo
39 char fullname[MAXPATHLEN];
40 + struct stat statbuf;
41 + int response, doUnlink = 0;
43 /* first look to see if file is already displayed in a window */
44 window = FindWindowWithFile(name, path);
45 @@ -219,7 +222,38 @@ WindowInfo *EditExistingFile(WindowInfo
51 + /* Get the full name of the file */
52 + strcpy(fullname, path);
53 + strcat(fullname, name);
55 + if (GetPrefHardlinkMode() != HARDLINK_IGNORE
56 + && stat(fullname, &statbuf) == 0
57 + && statbuf.st_nlink > 1) {
60 + window = FindWindowWithInode(name, path);
61 + if (window != NULL) {
62 + if (GetPrefHardlinkMode() == HARDLINK_PROMPT) {
63 + response = DialogF(DF_INF, window->shell, 3, "Open File",
64 + "%s has more than one hardlink\n"
65 + "and is already opened in an other window.\n\n"
66 + "Unlink this file after open (make this file uniq)\n"
67 + "or show the other window?",
68 + "Open & Unlink", "Show other", "Cancel", fullname);
73 + else if (response == 2) {
74 + RaiseShellWindow(window->shell, True);
76 + } else if (response == 1 || GetPrefHardlinkMode() == HARDLINK_UNLINK) {
82 /* If an existing window isn't specified; or the window is already
83 in use (not Untitled or Untitled and modified), or is currently
84 busy running a macro; create the window */
85 @@ -276,12 +310,28 @@ WindowInfo *EditExistingFile(WindowInfo
86 UpdateStatsLine(window);
88 /* Add the name to the convenience menu of previously opened files */
89 - strcpy(fullname, path);
90 - strcat(fullname, name);
91 if(GetPrefAlwaysCheckRelTagsSpecs())
92 AddRelTagsFile(GetPrefTagFile(), path, TAG);
93 AddToPrevOpenMenu(fullname);
96 + if (unlink(fullname) != 0) {
97 + DialogF(DF_ERR, window->shell, 1,
99 + "Error can't unlink %s.\n"
100 + "Changes are made to multiple opended files.",
101 + "Continue", fullname);
103 + struct utimbuf utimbuf;
105 + chmod(fullname, statbuf.st_mode);
106 + chown(fullname, statbuf.st_uid, statbuf.st_gid);
107 + utimbuf.actime = statbuf.st_atime;
108 + utimbuf.modtime = statbuf.st_mtime;
109 + utime(fullname, &utimbuf);
113 MacroApplyHook(window, "post_open_hook", 0, NULL, NULL);
116 @@ -911,8 +961,14 @@ int CloseFileAndWindow(WindowInfo *windo
118 int SaveWindow(WindowInfo *window)
123 + struct stat statbuf;
124 + char fullname[MAXPATHLEN];
126 + /* Get the full name of the file */
127 + strcpy(fullname, window->path);
128 + strcat(fullname, window->filename);
130 /* Try to ensure our information is up-to-date */
131 CheckForChangesToFile(window);
133 @@ -929,7 +985,7 @@ int SaveWindow(WindowInfo *window)
134 /* Check for external modifications and warn the user */
135 if (GetPrefWarnFileMods() && fileWasModifiedExternally(window))
137 - stat = DialogF(DF_WARN, window->shell, 2, "Save File",
138 + response = DialogF(DF_WARN, window->shell, 2, "Save File",
139 "%s has been modified by another program.\n\n"
140 "Continuing this operation will overwrite any external\n"
141 "modifications to the file since it was opened in NEdit,\n"
142 @@ -938,7 +994,7 @@ int SaveWindow(WindowInfo *window)
143 "use Save As... to save this file under a different name,\n"
144 "or Revert to Saved to revert to the modified version.",
145 "Continue", "Cancel", window->filename);
149 /* Cancel and mark file as externally modified */
150 window->lastModTime = 0;
151 @@ -946,18 +1002,45 @@ int SaveWindow(WindowInfo *window)
158 + if (GetPrefHardlinkMode() != HARDLINK_IGNORE
159 + && stat(fullname, &statbuf) == 0
160 + && statbuf.st_nlink > 1) {
162 + if (GetPrefHardlinkMode() == HARDLINK_PROMPT
163 + && window->hardlinkDontPromptAgain == False) {
164 + response = DialogF(DF_QUES, window->shell, 3, "Save File",
165 + "%s has more than one hardlink.\n\n"
166 + "Unlink this file before saving (make this file uniq)?",
167 + "Unlink & Save", "Save", "Cancel", fullname);
170 + if (response == 3) {
172 + } else if (response == 2) {
173 + window->hardlinkDontPromptAgain = True;
174 + } else if (GetPrefHardlinkMode() == HARDLINK_UNLINK || response == 1) {
175 + if (unlink(fullname) != 0) {
176 + DialogF(DF_ERR, window->shell, 1,
177 + "Error saving File",
178 + "Error can't unlink %s",
179 + "Cancel", fullname);
185 RemoveBackupFile(window);
186 - stat = doSave(window);
187 + response = doSave(window);
189 if (writeBckVersion(window))
191 - stat = doSave(window);
193 + response = doSave(window);
195 RemoveBackupFile(window);
201 int SaveWindowAs(WindowInfo *window, const char *newName, int addWrap)
202 diff --quilt old/source/menu.c new/source/menu.c
203 --- old/source/menu.c
204 +++ new/source/menu.c
205 @@ -141,6 +141,9 @@ static void autoIndentDefCB(Widget w, Wi
206 static void smartIndentDefCB(Widget w, WindowInfo *window, caddr_t callData);
207 static void autoSaveDefCB(Widget w, WindowInfo *window, caddr_t callData);
208 static void preserveDefCB(Widget w, WindowInfo *window, caddr_t callData);
209 +static void ignoreHardlinkDefCB(Widget w, WindowInfo *window, caddr_t callData);
210 +static void promptHardlinkDefCB(Widget w, WindowInfo *window, caddr_t callData);
211 +static void unlinkHardlinkDefCB(Widget w, WindowInfo *window, caddr_t callData);
212 static void noWrapDefCB(Widget w, WindowInfo *window, caddr_t callData);
213 static void newlineWrapDefCB(Widget w, WindowInfo *window, caddr_t callData);
214 static void contWrapDefCB(Widget w, WindowInfo *window, caddr_t callData);
215 @@ -1018,7 +1021,19 @@ Widget CreateMenuBar(Widget parent, Wind
216 "Incremental Backup", 'B', autoSaveDefCB, window, GetPrefAutoSave(),
220 + /* Hardlink mode default sub menu */
221 + subSubPane = createMenu(subPane, "hardlinkMode", "Hardlinks", 'H',
223 + window->hardlinkDefItem[HARDLINK_IGNORE] = createMenuRadioToggle(
224 + subSubPane, "ignore", "Ignore", 'I', ignoreHardlinkDefCB, window,
225 + GetPrefHardlinkMode() == HARDLINK_IGNORE, SHORT);
226 + window->hardlinkDefItem[HARDLINK_PROMPT] = createMenuRadioToggle(
227 + subSubPane, "prompt", "Prompt", 'P', promptHardlinkDefCB, window,
228 + GetPrefHardlinkMode() == HARDLINK_PROMPT, SHORT);
229 + window->hardlinkDefItem[HARDLINK_UNLINK] = createMenuRadioToggle(
230 + subSubPane, "unlink", "Unlink", 'U', unlinkHardlinkDefCB, window,
231 + GetPrefHardlinkMode() == HARDLINK_UNLINK, SHORT);
233 /* Show Matching sub menu */
234 subSubPane = createMenu(subPane, "showMatching", "Show Matching (..)", 'M',
236 @@ -1862,6 +1877,40 @@ static void preserveDefCB(Widget w, Wind
240 +static void setHardlinkModeMenu(enum hardlinkMode mode)
245 + if (mode >= N_HARDLINK_MODES) {
249 + /* Set the preference and make the other windows' menus agree */
250 + SetPrefHardlinkMode(mode);
251 + for (win = WindowList; win != NULL; win = win->next) {
252 + for (i = 0; i < N_HARDLINK_MODES; i++) {
253 + XmToggleButtonSetState(win->hardlinkDefItem[i],
254 + mode == i ? True : False, False);
259 +static void ignoreHardlinkDefCB(Widget w, WindowInfo *window, caddr_t callData)
261 + setHardlinkModeMenu(HARDLINK_IGNORE);
264 +static void promptHardlinkDefCB(Widget w, WindowInfo *window, caddr_t callData)
266 + setHardlinkModeMenu(HARDLINK_PROMPT);
269 +static void unlinkHardlinkDefCB(Widget w, WindowInfo *window, caddr_t callData)
271 + setHardlinkModeMenu(HARDLINK_UNLINK);
274 static void fontDefCB(Widget w, WindowInfo *window, caddr_t callData)
276 HidePointerOnKeyedEvent(WidgetToWindow(MENU_WIDGET(w))->lastFocus,
277 diff --quilt old/source/nedit.h new/source/nedit.h
278 --- old/source/nedit.h
279 +++ new/source/nedit.h
280 @@ -113,6 +113,10 @@ enum showWrapMarginEnums {SHOW_WRAP_MARG
282 enum truncSubstitution {TRUNCSUBST_SILENT, TRUNCSUBST_FAIL, TRUNCSUBST_WARN, TRUNCSUBST_IGNORE};
284 +/* This enum must be kept in sync with HardlinkModes[] in in preferences.c */
285 +enum hardlinkMode {HARDLINK_IGNORE, HARDLINK_PROMPT, HARDLINK_UNLINK,
288 #define NO_FLASH_STRING "off"
289 #define FLASH_DELIMIT_STRING "delimiter"
290 #define FLASH_RANGE_STRING "range"
291 @@ -570,6 +574,8 @@ typedef struct _WindowInfo {
292 "tabbed" documents, while each document
293 has its own background menu. */
294 int inMacroHook; /* to protect GC in MacroApplyHook() */
295 + Widget hardlinkDefItem[N_HARDLINK_MODES];
296 + Boolean hardlinkDontPromptAgain;
299 extern WindowInfo *WindowList;
300 diff --quilt old/source/preferences.c new/source/preferences.c
301 --- old/source/preferences.c
302 +++ new/source/preferences.c
303 @@ -169,6 +169,13 @@ static char* TruncSubstitutionModes[] =
304 #define DEFAULT_TAB_DIST -1
305 #define DEFAULT_EM_TAB_DIST -1
307 +static char *HardlinkModes[] = {
314 /* list of available language modes and language specific preferences */
315 static int NLanguageModes = 0;
317 @@ -339,6 +346,7 @@ static struct prefData {
318 int truncSubstitution;
319 Boolean forceOSConversion;
320 Boolean showScrolltip;
324 /* Temporary storage for preferences strings which are discarded after being
325 @@ -1169,6 +1177,8 @@ static PrefDescripRec PrefDescrip[] = {
326 &PrefData.showCursorline, NULL, True},
327 // {"showScrolltip", "ShowScrolltip", PREF_BOOLEAN, "True",
328 // &PrefData.showScrolltip, NULL, False},
329 + {"hardlinkMode", "HardlinkMode", PREF_ENUM, "Ignore",
330 + &PrefData.hardlinkMode, HardlinkModes, True},
333 static XrmOptionDescRec OpTable[] = {
334 @@ -2314,6 +2324,16 @@ Boolean GetPrefShowScrolltip(void)
338 +void SetPrefHardlinkMode(int mode)
340 + setIntPref(&PrefData.hardlinkMode, mode);
343 +int GetPrefHardlinkMode(void)
345 + return PrefData.hardlinkMode;
349 ** If preferences don't get saved, ask the user on exit whether to save
351 diff --quilt old/source/preferences.h new/source/preferences.h
352 --- old/source/preferences.h
353 +++ new/source/preferences.h
354 @@ -216,5 +216,7 @@ Boolean GetPrefHonorSymlinks(void);
355 Boolean GetPrefForceOSConversion(void);
356 void SetPrefFocusOnRaise(Boolean);
357 Boolean GetPrefShowScrolltip(void);
358 +void SetPrefHardlinkMode(int mode);
359 +int GetPrefHardlinkMode(void);
361 #endif /* NEDIT_PREFERENCES_H_INCLUDED */
362 diff --quilt old/source/server.c new/source/server.c
363 --- old/source/server.c
364 +++ new/source/server.c
365 @@ -346,6 +346,7 @@ static void processServerCommandString(c
366 WindowInfo *window, *lastFile = NULL;
367 long currentDesktop = QueryCurrentDesktop(TheDisplay,
368 RootWindow(TheDisplay, DefaultScreen(TheDisplay)));
369 + WindowInfo *window_ino;
371 /* If the command string is empty, put up an empty, Untitled window
372 (or just pop one up if it already exists) */
373 @@ -473,6 +474,11 @@ static void processServerCommandString(c
374 filename, pathname, editFlags, geometry, iconicFlag,
375 lmLen == 0 ? NULL : langMode,
376 tabbed == -1? GetPrefOpenInTab() : tabbed, True);
377 + window_ino = FindWindowWithInode(filename, pathname);
378 + if (window != NULL && window == window_ino) {
379 + deleteFileOpenProperty2(filename, pathname);
380 + /* deleteFileClosedProperty2(filename, pathname); */
384 CleanUpTabBarExposeQueue(window);
385 diff --quilt old/source/window.c new/source/window.c
386 --- old/source/window.c
387 +++ new/source/window.c
388 @@ -333,6 +333,7 @@ WindowInfo *CreateWindow(const char *nam
391 window->inMacroHook = 0;
392 + window->hardlinkDontPromptAgain = False;
394 /* If window geometry was specified, split it apart into a window position
395 component and a window size component. Create a new geometry string
396 @@ -1202,6 +1203,37 @@ WindowInfo *FindWindowWithFile(const cha
400 +** Check if there is already a window open for a given inode
402 +WindowInfo *FindWindowWithInode(const char *name, const char *path)
405 + char fullname[MAXPATHLEN];
406 + struct stat statbuf;
410 + strcpy(fullname, path);
411 + strcat(fullname, name);
413 + if (stat(fullname, &statbuf) < 0) {
417 + ino = statbuf.st_ino;
418 + dev = statbuf.st_dev;
420 + for (w = WindowList; w != NULL; w = w->next) {
421 + strcpy(fullname, w->path);
422 + strcat(fullname, w->filename);
423 + if (w->inode == ino && w->device == dev) {
431 ** Add another independently scrollable pane to the current document,
432 ** splitting the pane which currently has keyboard focus.
434 @@ -4532,6 +4564,7 @@ static void cloneDocument(WindowInfo *wi
435 window->inode = orgWin->inode;
436 window->fileClosedAtom = orgWin->fileClosedAtom;
437 orgWin->fileClosedAtom = None;
438 + window->hardlinkDontPromptAgain = orgWin->hardlinkDontPromptAgain;
440 /* copy the text/split panes settings, cursor pos & selection */
441 cloneTextPanes(window, orgWin);
442 diff --quilt old/source/window.h new/source/window.h
443 --- old/source/window.h
444 +++ new/source/window.h
445 @@ -47,6 +47,7 @@ void SetWindowModified(WindowInfo *windo
446 void MakeSelectionVisible(WindowInfo *window, Widget textPane);
447 int GetSimpleSelection(textBuffer *buf, int *left, int *right);
448 WindowInfo *FindWindowWithFile(const char *name, const char *path);
449 +WindowInfo *FindWindowWithInode(const char *name, const char *path);
450 void SetAutoIndent(WindowInfo *window, int state);
451 void SetShowMatching(WindowInfo *window, int state);
452 void SetFonts(WindowInfo *window, const char *fontName, const char *italicName,
453 diff --quilt old/doc/help.etx new/doc/help.etx
456 @@ -4133,6 +4133,25 @@ Preferences
457 Show file name and path in a tooltip when moving the mouse pointer over a tab.
458 (See Tabbed_Editing_.)
461 + On UNIX systems it is possible to have a physical same file with different
462 + filenames. Because it can be dangerous to change a file which is accessible
463 + from different filenames, NEdit can warn the user either when the user writes
464 + to a file with multiple hardlinks, or if the file is already opened with a
468 + Ignore any hardlink informations.
471 + Prompt the user in thes two cases. For the open case the user can choose to
472 + open the other window with the file, or make the file to open a destine copy
473 + of the original file. For the save case the user can save the file under the
474 + name but a new destine file or ignore it.
477 + Always make a destine copy of the file.
480 Background the current line with a colored bar. Use the color dialog to
481 change the background color.