[PATCH] Editor file locking

Adam Byrtek / alpha alpha at student.uci.agh.edu.pl
Wed Apr 2 11:37:24 UTC 2003


As discussed earlier, patch to implement file locking in editor. Try
and tell me about any problems. Locking scheme described in
edit/editlock.c

TODO: 'Abort' option would be very useful, but it requires some more
hacking in the undo stack. I'll do this when this patch will be
considered stable.

(Patch also sent to BTS)

Regards
Adam

-- 

  _.|._ |_  _.   :  Adam Byrtek /alpha               alpha at debian.org
 (_|||_)| |(_|   :  http://krakow.linux.org.pl/        pgp 0xB25952C0
     |           
-------------- next part --------------
Index: edit/Makefile.am
===================================================================
RCS file: /cvs/gnome/mc/edit/Makefile.am,v
retrieving revision 1.5
diff -u -r1.5 Makefile.am
--- edit/Makefile.am	23 Dec 2002 10:13:35 -0000	1.5
+++ edit/Makefile.am	2 Apr 2003 11:17:41 -0000
@@ -9,6 +9,6 @@
 libedit_a_SOURCES = \
 	bookmark.c edit.c editcmd.c editwidget.c editdraw.c editkeys.c \
 	editmenu.c editoptions.c editcmddef.h edit.h edit-widget.h \
-	syntax.c wordproc.c
+	syntax.c wordproc.c editlock.c editlock.h
 
 EXTRA_DIST = ChangeLog
Index: edit/edit-widget.h
===================================================================
RCS file: /cvs/gnome/mc/edit/edit-widget.h,v
retrieving revision 1.15
diff -u -r1.15 edit-widget.h
--- edit/edit-widget.h	12 Mar 2003 07:07:28 -0000	1.15
+++ edit/edit-widget.h	2 Apr 2003 11:17:41 -0000
@@ -56,6 +56,7 @@
     unsigned char overwrite;
     unsigned char modified;	/* has the file been changed?: 1 if char inserted or
 				   deleted at all since last load or save */
+    unsigned char locked;	/* 1 if lock is held on current file */
     unsigned char screen_modified;	/* has the file been changed since the last screen draw? */
     int delete_file;		/* Has the file been created by the editor?  Delete
 				   it at end of editing when it hasn't been modified 
Index: edit/edit.c
===================================================================
RCS file: /cvs/gnome/mc/edit/edit.c,v
retrieving revision 1.73
diff -u -r1.73 edit.c
--- edit/edit.c	23 Dec 2002 10:13:35 -0000	1.73
+++ edit/edit.c	2 Apr 2003 11:17:41 -0000
@@ -22,6 +22,7 @@
 
 #include <config.h>
 #include "edit.h"
+#include "editlock.h"
 #include "edit-widget.h"
 #include "editcmddef.h"
 
@@ -554,6 +555,7 @@
 	return 0;
     }
     edit->modified = 0;
+    edit->locked = 0;
     edit_load_syntax (edit, 0, 0);
     {
 	int color;
@@ -811,8 +813,12 @@
 static inline void edit_modification (WEdit * edit)
 {
     edit->caches_valid = 0;
-    edit->modified = 1;
     edit->screen_modified = 1;
+    
+    /* raise lock when file modified */
+    if (!edit->modified && !edit->delete_file)
+	edit->locked = edit_lock_file (edit->filename);
+    edit->modified = 1;
 }
 
 /*
Index: edit/editcmd.c
===================================================================
RCS file: /cvs/gnome/mc/edit/editcmd.c,v
retrieving revision 1.75
diff -u -r1.75 editcmd.c
--- edit/editcmd.c	19 Dec 2002 13:01:34 -0000	1.75
+++ edit/editcmd.c	2 Apr 2003 11:17:43 -0000
@@ -27,6 +27,7 @@
 #include <ctype.h>
 
 #include "edit.h"
+#include "editlock.h"
 #include "editcmddef.h"
 #include "edit-widget.h"
 
@@ -184,9 +185,6 @@
     doupdate();
 }
 
-/* "Oleg Yu. Repin" <repin at ssd.sscc.ru> added backup filenames
-    ...thanks -paul */
-
 /*  If 0 (quick save) then  a) create/truncate <filename> file,
 			    b) save to <filename>;
     if 1 (safe save) then   a) save to <tempnam>,
@@ -438,6 +436,7 @@
 {
     /* This heads the 'Save As' dialog box */
     char *exp = 0;
+    int save_lock = 0;
     int different_filename = 0;
 
     exp = edit_get_save_file (edit->filename, _(" Save As "));
@@ -465,8 +464,25 @@
 			return 0;
 		    }
 		}
+		save_lock = edit_lock_file (exp);
+	    } else {
+		/* filenames equal, check if already locked */
+		if (!edit->locked && !edit->delete_file)
+		    save_lock = edit_lock_file (exp);
 	    }
+		
 	    if (edit_save_file (edit, exp)) {
+		/* Succesful, so unlock both files */
+		if (strcmp (edit->filename, exp)) {
+		    if (save_lock)
+			edit_unlock_file (exp);
+		    if (edit->locked)
+			edit->locked = edit_unlock_file (edit->filename);
+		} else {
+		    if (edit->locked || save_lock)
+			edit->locked = edit_unlock_file (edit->filename);
+		}
+		
 		edit_set_filename (edit, exp);
 		g_free (exp);
 		edit->modified = 0;
@@ -476,6 +492,11 @@
 		edit->force |= REDRAW_COMPLETELY;
 		return 1;
 	    } else {
+		/* Failed, so maintain modify (not save) lock */
+		if (strcmp (edit->filename, exp) && save_lock)
+		    edit_unlock_file (exp);
+		if (save_lock)
+		    edit->locked = edit_unlock_file (edit->filename);
 		g_free (exp);
 		edit_error_dialog (_(" Save As "),
 				   get_sys_error (_
@@ -730,11 +751,22 @@
 /* returns 1 on success */
 int edit_save_cmd (WEdit * edit)
 {
-    if (!edit_save_file (edit, edit->filename))
+    int res, save_lock = 0;
+    
+    if (!edit->locked && !edit->delete_file)
+	save_lock = edit_lock_file (edit->filename);
+    res = edit_save_file (edit, edit->filename);
+    
+    /* Maintain modify (not save) lock on failure */
+    if ((res && edit->locked) || save_lock)
+	edit->locked = edit_unlock_file (edit->filename);
+    
+    /* On failure try 'save as', it does locking on its own */
+    if (!res) 
 	return edit_save_as_cmd (edit);
     edit->force |= REDRAW_COMPLETELY;
-    edit->modified = 0;
     edit->delete_file = 0;
+    edit->modified = 0;
 
     return 1;
 }
@@ -750,7 +782,9 @@
 	}
     }
     edit->force |= REDRAW_COMPLETELY;
-    edit->modified = 0;
+
+    if (edit->locked)
+	edit->locked = edit_unlock_file (edit->filename);
     return edit_renew (edit);	/* if this gives an error, something has really screwed up */
 }
 
@@ -758,10 +792,17 @@
 static int
 edit_load_file_from_filename (WEdit * edit, char *exp)
 {
-    if (!edit_reload (edit, exp))
+    int prev_locked = edit->locked;
+    char *prev_filename = g_strdup (edit->filename);
+    
+    if (!edit_reload (edit, exp)) {
+	g_free (prev_filename);
 	return 1;
-    edit_set_filename (edit, exp);
-    edit->modified = 0;
+    }
+
+    if (prev_locked)
+	edit_unlock_file (prev_filename);
+    g_free (prev_filename);
     return 0;
 }
 
@@ -2023,6 +2064,8 @@
 		return;
 	    break;
 	case 2:
+	    if (edit->locked)
+		edit->locked = edit_unlock_file (edit->filename);
 	    if (edit->delete_file)
 		unlink (edit->filename);
 	    break;
--- /dev/null	2003-01-13 01:13:32.000000000 +0100
+++ edit/editlock.h	2003-04-02 01:05:26.000000000 +0200
@@ -0,0 +1,7 @@
+#ifndef __EDIT_LOCK_H
+#define __EDIT_LOCK_H
+
+int edit_lock_file (char *fname);
+int edit_unlock_file (char *fname);
+
+#endif /* !__EDIT_LOCK_H */
--- /dev/null	2003-01-13 01:13:32.000000000 +0100
+++ edit/editlock.c	2003-04-02 13:13:24.000000000 +0200
@@ -0,0 +1,169 @@
+/* editor file locking.
+
+   Copyright (C) 2003 the Free Software Foundation
+
+   Authors: 2003 Adam Byrtek
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+   02111-1307, USA.
+
+*/
+
+#include <config.h>
+#include "edit.h"
+#include "editlock.h"
+
+#include "src/wtools.h"	/* edit_query_dialog () */
+
+#define BUF_SIZE 255
+
+/* Locking scheme used in mcedit is based on a documentation found
+   in JED editor sources. Abstract from lock.c file (by John E. Davis):
+
+   The basic idea here is quite simple.  Whenever a buffer is attached to
+   a file, and that buffer is modified, then attempt to lock the
+   file. Moreover, before writing to a file for any reason, lock the
+   file. The lock is really a protocol respected and not a real lock.
+   The protocol is this: If in the directory of the file is a
+   symbolic link with name ".#FILE", the FILE is considered to be locked
+   by the process specified by the link.
+*/ 
+ 
+/* Build user at host.domain.pid string (need to be freed) */
+static char *lock_build_name (char *fname)
+{
+    char host [BUF_SIZE], *user;
+    
+    if (!((user = getpwuid (getuid ()) -> pw_name) ||
+	  (user = getenv ("USER")) ||
+	  (user = getenv ("USERNAME")) ||
+	  (user = getenv ("LOGNAME"))))
+	user = "";
+    
+    /* TODO: Use FQDN, no clean interface, so requires lot of code */
+    if (gethostname (host, BUF_SIZE-1) == -1)
+	*host='\0';
+    
+    return g_strdup_printf ("%s@%s.%d", user, host, getpid ());
+}
+
+/* Extract pid from user at host.domain.pid string */
+static pid_t lock_extract_pid (char *str)
+{
+    char *pid;
+
+    for (pid = str+strlen(str)-1; isdigit(*pid) && pid>=str; pid--);
+    if (!*pid)
+	return -1;
+    return (pid_t) atol (pid+1);
+}
+
+/* Extract user at host.domain.pid from lock file (static string)  */
+static char *lock_get_info (char *lockfname)
+{
+    static char buf[BUF_SIZE];
+
+    if (readlink (lockfname, buf, BUF_SIZE-1) == -1 || !buf || !*buf)
+	return NULL;
+    return buf;
+}
+
+
+/* Tries to raise file lock
+   Returns 1 on success,  0 on failure, -1 if abort */
+int edit_lock_file (char *fname)
+{
+    char *lockfname, *newlock, *msg, *lock;
+    struct stat statbuf;
+    pid_t pid;
+ 
+    /* Just to be sure (and don't lock new file) */
+    if (!fname || !*fname)
+	return 0;
+
+    /* Check if already locked */
+    lockfname = g_strconcat (".#", fname, NULL);
+    if (lstat (lockfname, &statbuf) == 0) {
+	lock = lock_get_info (lockfname);
+	if (!lock) {
+	    g_free (lockfname);
+	    return 0;
+	}
+	pid = lock_extract_pid (lock);
+	
+	/* Check if locking process alive, ask user if required */
+	if (pid <= 0 || !(kill (pid, 0) == -1 && errno == ESRCH)) {
+	    msg = g_strconcat ( fname, _(" is already locked by "), lock , NULL);
+	    /* TODO: Implement "Abort" - needs to rewind undo stack */
+	    switch (edit_query_dialog2 (_ ("File locked"), msg,
+					_ ("&Steal"), _ ("&Continue"))) {
+	    case 0 :
+		break;
+	    case 1 : case -1 :
+		g_free (lockfname);
+		g_free (msg);
+		return 0;
+	    }
+	    g_free (msg);
+	}
+	unlink (lockfname);
+    }
+    
+    /* Create lock symlink */
+    newlock = lock_build_name (fname);
+    if (symlink (newlock, lockfname) == -1) {
+	g_free (lockfname);
+	g_free (newlock);
+	return 0;
+    }
+    
+    g_free (lockfname);
+    g_free (newlock);
+    return 1;
+}
+
+/* Lowers file lock if possible 
+   Always returns 0 to make 'lock = edit_unlock_file (f)' possible */
+int edit_unlock_file (char *fname)
+{
+    char *lockfname, *lock;
+    struct stat statbuf;
+
+    /* Just to be sure */
+    if (!fname || !*fname)
+	return 0;
+    
+    lockfname = g_strconcat (".#", fname, NULL);
+    
+    /* Check if lock exists */
+    if (lstat (lockfname, &statbuf) == -1) {
+	g_free (lockfname);
+	return 0;
+    }
+    
+    lock = lock_get_info (lockfname);
+    if (lock) {
+	/* Don't touch if lock is not ours */
+	if (lock_extract_pid (lock) != getpid ()) {
+	    g_free (lockfname);
+	    return 0;
+	}
+    }
+    
+    /* Remove lock */
+    unlink (lockfname);
+    g_free (lockfname);
+    return 0;
+}
Index: edit/ChangeLog
===================================================================
RCS file: /cvs/gnome/mc/edit/ChangeLog,v
retrieving revision 1.157
diff -u -u -0 -r1.157 ChangeLog
--- edit/ChangeLog	12 Mar 2003 07:07:27 -0000	1.157
+++ edit/ChangeLog	2 Apr 2003 11:29:04 -0000
@@ -0,0 +1,14 @@
+2003-04-01  Adam Byrtek  <alpha at debian.org>
+
+	* editlock.c, editlock.h: New files. Implement file locking in
+	Emacs style, as documented in JED editor sources.
+	Makefile.am: Add those files to build tree.
+	
+	* edit-widget.c (WEdit): New property 'locked', 0 on edit_init.
+	* edit.c (edit_modification): Lock buffer on modification.
+	* editcmd.c (edit_save_cmd, edit_save_as_cmd): Handle locking during
+	file save.
+	(edit_load_file_from_filename): Unlock. Remove 2 duplicate
+	lines (handled by edit_init).
+	(edit_quit_cmd): Unlock.
+	


More information about the mc-devel mailing list