re-fresh
[nedit-bw.git] / handle_hardlink.patch
blob002aad0232706efbafd40436b6b49018db7385f3
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:
6 * ignore it
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.
12 ---
14 doc/help.etx | 19 +++++++++
15 source/file.c | 107 +++++++++++++++++++++++++++++++++++++++++++++------
16 source/menu.c | 51 +++++++++++++++++++++++-
17 source/nedit.h | 6 ++
18 source/preferences.c | 20 +++++++++
19 source/preferences.h | 2
20 source/server.c | 8 +++
21 source/window.c | 31 ++++++++++++++
22 source/window.h | 1
23 9 files changed, 232 insertions(+), 13 deletions(-)
25 diff --quilt old/source/file.c new/source/file.c
26 --- old/source/file.c
27 +++ new/source/file.c
28 @@ -64,6 +64,7 @@ static const char CVSID[] = "$Id: file.c
29 #else
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 +#include <utime.h>
33 #ifndef __MVS__
34 #include <sys/param.h>
35 #endif
36 @@ -207,6 +208,8 @@ WindowInfo *EditExistingFile(WindowInfo
38 WindowInfo *window;
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
47 goto out;
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) {
59 + response = 0;
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);
69 + }
71 + if (response == 3)
72 + return NULL;
73 + else if (response == 2) {
74 + RaiseShellWindow(window->shell, True);
75 + return window;
76 + } else if (response == 1 || GetPrefHardlinkMode() == HARDLINK_UNLINK) {
77 + doUnlink = 1;
78 + }
79 + }
80 + }
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);
95 + if (doUnlink) {
96 + if (unlink(fullname) != 0) {
97 + DialogF(DF_ERR, window->shell, 1,
98 + "Error open File",
99 + "Error can't unlink %s.\n"
100 + "Changes are made to multiple opended files.",
101 + "Continue", fullname);
102 + } else {
103 + struct utimbuf utimbuf;
104 + doSave(window);
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 out:
114 PostOpenHook(window);
116 @@ -912,8 +962,14 @@ int CloseFileAndWindow(WindowInfo *windo
118 int SaveWindow(WindowInfo *window)
120 - int stat;
122 + int response;
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 @@ -930,7 +986,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 @@ -939,7 +995,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);
146 - if (stat == 2)
147 + if (response == 2)
149 /* Cancel and mark file as externally modified */
150 window->lastModTime = 0;
151 @@ -947,18 +1003,45 @@ int SaveWindow(WindowInfo *window)
152 return FALSE;
157 + response = 0;
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) {
171 + return FALSE;
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);
184 #ifdef VMS
185 RemoveBackupFile(window);
186 - stat = doSave(window);
187 + response = doSave(window);
188 #else
189 if (writeBckVersion(window))
190 return FALSE;
191 - stat = doSave(window);
192 - if (stat)
193 + response = doSave(window);
194 + if (response)
195 RemoveBackupFile(window);
196 #endif /*VMS*/
197 - return stat;
198 + return response;
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 @@ -142,6 +142,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 @@ -1019,7 +1022,19 @@ Widget CreateMenuBar(Widget parent, Wind
216 "Incremental Backup", 'B', autoSaveDefCB, window, GetPrefAutoSave(),
217 SHORT);
220 + /* Hardlink mode default sub menu */
221 + subSubPane = createMenu(subPane, "hardlinkMode", "Hardlinks", 'H',
222 + NULL, FULL);
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',
235 NULL, FULL);
236 @@ -1863,6 +1878,40 @@ static void preserveDefCB(Widget w, Wind
240 +static void setHardlinkModeMenu(enum hardlinkMode mode)
242 + WindowInfo *win;
243 + int i;
245 + if (mode >= N_HARDLINK_MODES) {
246 + return;
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
281 in preferences.c */
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,
286 + N_HARDLINK_MODES};
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;
297 } WindowInfo;
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 @@ -167,6 +167,13 @@ static char* TruncSubstitutionModes[] =
304 #define DEFAULT_TAB_DIST -1
305 #define DEFAULT_EM_TAB_DIST -1
307 +static char *HardlinkModes[] = {
308 + "Ignore",
309 + "Prompt",
310 + "Unlink",
311 + NULL
314 /* list of available language modes and language specific preferences */
315 static int NLanguageModes = 0;
316 typedef struct {
317 @@ -337,6 +344,7 @@ static struct prefData {
318 int truncSubstitution;
319 Boolean forceOSConversion;
320 Boolean showScrolltip;
321 + int hardlinkMode;
322 } PrefData;
324 /* Temporary storage for preferences strings which are discarded after being
325 @@ -1167,6 +1175,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 @@ -2310,6 +2320,16 @@ Boolean GetPrefShowScrolltip(void)
335 return PrefData.showScrolltip;
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 @@ -458,6 +458,7 @@ static void processServerCommandString(c
367 window = FindWindowWithFile(filename, pathname);
368 if (window == NULL) {
369 + WindowInfo *window_ino = FindWindowWithInode(filename, pathname);
370 /* Files are opened in background to improve opening speed
371 by defering certain time consuiming task such as syntax
372 highlighting. At the end of the file-opening loop, the
373 @@ -468,6 +469,13 @@ static void processServerCommandString(c
374 filename, pathname, editFlags, geometry, iconicFlag,
375 lmLen == 0 ? NULL : langMode,
376 tabbed == -1? GetPrefOpenInTab() : tabbed, True);
377 + if (window && window == window_ino) {
378 + deleteFileOpenProperty2(requestname);
379 + /* we opened a window with the same inode but another path
380 + but the client may waits for closing this path,
381 + we do it, but it's not fair */
382 + deleteFileClosedProperty2(requestname);
385 if (window) {
386 CleanUpTabBarExposeQueue(window);
387 diff --quilt old/source/window.c new/source/window.c
388 --- old/source/window.c
389 +++ new/source/window.c
390 @@ -334,6 +334,7 @@ WindowInfo *CreateWindow(const char *nam
391 window->device = 0;
392 window->inode = 0;
393 window->inMacroHook = 0;
394 + window->hardlinkDontPromptAgain = False;
396 /* If window geometry was specified, split it apart into a window position
397 component and a window size component. Create a new geometry string
398 @@ -1201,6 +1202,35 @@ WindowInfo *FindWindowWithFile(const cha
402 +** Check if there is already a window open for a given inode
404 +WindowInfo *FindWindowWithInode(const char *name, const char *path)
406 + WindowInfo *w;
407 + char fullname[MAXPATHLEN];
408 + struct stat statbuf;
409 + ino_t ino;
410 + dev_t dev;
412 + strcpy(fullname, path);
413 + strcat(fullname, name);
415 + if (stat(fullname, &statbuf) < 0) {
416 + return NULL;
419 + ino = statbuf.st_ino;
420 + dev = statbuf.st_dev;
422 + for (w = WindowList; w != NULL; w = w->next) {
423 + if (w->inode == ino && w->device == dev) {
424 + return w;
427 + return NULL;
431 ** Add another independently scrollable pane to the current document,
432 ** splitting the pane which currently has keyboard focus.
434 @@ -4523,6 +4553,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
454 --- old/doc/help.etx
455 +++ new/doc/help.etx
456 @@ -4136,6 +4136,25 @@ Preferences
457 Show file name and path in a tooltip when moving the mouse pointer over a tab.
458 (See Tabbed_Editing_.)
460 +**Hardlinks**
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
465 + different name.
467 + ~Ignore~
468 + Ignore any hardlink informations.
470 + ~Prompt~
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.
476 + ~Unlink~
477 + Always make a destine copy of the file.
479 **Show Cursorline**
480 Background the current line with a colored bar. Use the color dialog to
481 change the background color.