/* -*- mode: C; mode: fold; -*- */ /* Copyright (c) 1999, 2000, 2002, 2003, 2004, 2005, 2006 John E. Davis * This file is part of JED editor library source. * * You may distribute this file under the terms the GNU General Public * License. See the file COPYING for more information. */ #include "config.h" #include "jed-feat.h" /*{{{ Include Files */ #include #include #ifdef HAVE_STDLIB_H # include #endif #ifdef HAVE_UNISTD_H # include #endif #include #include #include #include #include #include "jdmacros.h" #include "buffer.h" #include "file.h" #include "userinfo.h" #include "ledit.h" #include "misc.h" /*}}}*/ #if JED_HAS_EMACS_LOCKING && !defined(HAVE_SYMLINK) # undef JED_HAS_EMACS_LOCKING # define JED_HAS_EMACS_LOCKING 0 #endif #if !JED_HAS_EMACS_LOCKING /*{{{*/ int jed_lock_buffer_file (Buffer *b) { (void) b; return 0; } int jed_unlock_buffer_file (Buffer *b) { (void) b; return 0; } int jed_lock_file (char *file) { (void) file; return 0; } int jed_unlock_file (char *file) { (void) file; return 0; } /*}}}*/ #else /* Rest of file */ /* 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. * * Here are the scenerios requiring a lock: * * 1. When a buffer attached to a file becomes modified. * 2. When appending or writing to a file. * * Suppose that a buffer has not been modified but one appends a * region of the buffer to some file. Then while that file is being * written, it should be locked and then released when finished. * However, suppose another buffer has that file locked. Then in * order to write to it, either the lock must be stolen or ignored. * In either of these cases, the user is responsible to what happens * to the text in the other buffer. In fact, if the user elects to * steal the lock from the other buffer, then that lock will be * deleted after the file has been modfied. Of course these same comments * apply if another process owns the lock. * * Now consider the case when a buffer is modified and has the file locked. * When the buffer is saved, the file will already be locked and there is no * need to lock it again. */ typedef struct { char *host; char *user; int pid; } Lock_Info_Type; static char *make_lockfile_name (char *file) { unsigned int len; char *buf, *dir, *name; file = jed_expand_link (file); if (file == NULL) return NULL; if (-1 == jed_dirfile_to_dir_file (file, &dir, &name)) { SLfree (file); return NULL; } SLfree (file); len = strlen (dir) + strlen (name) + 3; if (NULL != (buf = SLmalloc (len))) sprintf (buf, "%s.#%s", dir, name); SLfree (dir); SLfree (name); return buf; } static void free_lock_info (Lock_Info_Type *l) { SLang_free_slstring (l->host); SLang_free_slstring (l->user); } static int create_lock_info (Lock_Info_Type *l) { char *host, *user; memset ((char *) l, 0, sizeof (Lock_Info_Type)); if (NULL == (host = jed_get_hostname ())) return -1; if (NULL == (user = jed_get_username ())) { SLang_free_slstring (host); return -1; } l->host = host; l->user = user; l->pid = getpid (); return 0; } /* * If 0 is returned, then proceed with no lock. If 1, then force * the lock, if -1 then abort with no lock. */ static int ask_about_lock (char *file, Lock_Info_Type *l) { char *buf; unsigned int len; if (Batch) { jed_verror ("%s is locked by %s@%s.d.", file, l->user, l->host, l->pid); return -1; } len = 64 + strlen (file) + strlen (l->host) + strlen (l->user); if (NULL == (buf = SLmalloc (len))) return -1; sprintf (buf, "%s is locked by %s@%s.%d. (S)teal, (P)roceed, (A)bort?", file, l->user, l->host, l->pid); while (1) { switch (jed_get_mini_response (buf)) { case 's': case 'S': SLfree (buf); return 1; case 'P': case 'p': case 'q': case 'Q': SLfree (buf); return 0; case 'A': case 'a': SLfree (buf); jed_verror ("%s is locked by another process", file); return -1; } if (SLang_get_error ()) { SLfree (buf); return -1; } jed_beep (); } } /* Returns 0 if file does not exist, or 1 with info, or -1 for error */ static int get_lock_info (char *lockfile, Lock_Info_Type *l) { struct stat st; char buf[1024]; char *b; char *user, *host; int n; memset ((char *) l, 0, sizeof (Lock_Info_Type)); if (-1 == lstat (lockfile, &st)) { if (errno == ENOENT) return 0; return -1; } if (((st.st_mode & S_IFMT) & S_IFLNK) == 0) return -1; n = readlink (lockfile, buf, sizeof (buf)-1); if ((n == -1) || (n >= (int)sizeof(buf)-1)) return -1; buf[n] = 0; /* The format is: username@host.pid:boot_time */ b = strchr (buf, '@'); if (b == NULL) return -1; *b++ = 0; user = SLang_create_slstring (buf); if (user == NULL) return -1; host = b; b += strlen (b); while (b > host) { b--; if (*b == '.') break; } if (b == host) { SLang_free_slstring (user); return -1; } *b++ = 0; if (NULL == (host = SLang_create_slstring (host))) { SLang_free_slstring (user); return -1; } l->pid = atoi (b); l->host = host; l->user = user; return 1; } /* Returns 1 upon success, 0 is lock exists, or -1 upon failure. If force * is non-zero, then this function will not return 0. */ static int perform_lock (char *lockfile, Lock_Info_Type *l, int force) { unsigned int len; char *buf; int status; len = 32 + strlen (l->host) + strlen (l->user); if (NULL == (buf = SLmalloc (len))) return -1; sprintf (buf, "%s@%s.%d", l->user, l->host, l->pid); if (0 == symlink (buf, lockfile)) { SLfree (buf); return 1; } # ifdef EPERM if (errno == EPERM) /* filesystem does not support links */ { jed_vmessage (0, "File system does not support symbolic links: file not locked."); SLfree (buf); return 1; } # endif # ifdef EACCES if (errno == EACCES) { jed_vmessage (0, "No permission to lock file"); SLfree (buf); return 1; } #endif if (force == 0) { SLfree (buf); if (errno == EEXIST) return 0; return -1; } (void) unlink (lockfile); status = symlink (buf, lockfile); if (status == -1) jed_vmessage (0, "Unable to create lockfile %s", lockfile); else status = 1; SLfree (buf); return status; } /* Return 0 if same, -1 if same host, 1 if different host */ static int compare_lock_info (Lock_Info_Type *a, Lock_Info_Type *b) { if (a->host != b->host) return 1; if ((a->pid != b->pid) || (a->user != b->user)) return -1; return 0; } int jed_lock_file (char *file) { char *lockfile; Lock_Info_Type l0; int force; int status; lockfile = make_lockfile_name (file); if (lockfile == NULL) return -1; if (-1 == create_lock_info (&l0)) { SLfree (lockfile); return -1; } force = 0; while (1) { Lock_Info_Type l1; status = perform_lock (lockfile, &l0, force); if (status == 1) /* lock succeeded */ break; if (status == -1) /* error */ break; /* Lock already exists. Let's see who owns it */ status = get_lock_info (lockfile, &l1); if (status == -1) break; if (status == 0) /* lock nolonger exists, try again */ continue; status = compare_lock_info (&l0, &l1); if (status == 0) { /* We already own this lock */ free_lock_info (&l1); status = 1; break; } if (status == -1) { /* Some process on this machine owns it. See if the process is * alive. */ if (l1.pid <= 0) status = 2; else if (-1 == kill (l1.pid, 0)) { # ifdef ESRCH if (errno == ESRCH) /* Doesn't exist */ status = 2; # endif } } if (status != 2) status = ask_about_lock (file, &l1); free_lock_info (&l1); if (status <= 0) break; force = 1; } SLfree (lockfile); free_lock_info (&l0); if (status == -1) return -1; return 0; } int jed_unlock_file (char *file) { Lock_Info_Type l0, l1; char *lockfile; int status; lockfile = make_lockfile_name (file); if (lockfile == NULL) return -1; if (-1 == create_lock_info (&l0)) { SLfree (lockfile); return -1; } status = get_lock_info (lockfile, &l1); if (status <= 0) { free_lock_info (&l0); SLfree (lockfile); return status; } if (0 == compare_lock_info (&l0, &l1)) (void) unlink (lockfile); free_lock_info (&l0); free_lock_info (&l1); SLfree (lockfile); return 0; } int jed_lock_buffer_file (Buffer *b) { int status; char *file; if ((b->file[0] == 0) || (b->flags & BUFFER_NON_LOCKING)) return 0; file = jed_dir_file_merge (b->dir, b->file); status = jed_lock_file (file); SLfree (file); return status; } int jed_unlock_buffer_file (Buffer *b) { int status; char *file; if ((b->file[0] == 0) || (b->flags & BUFFER_NON_LOCKING)) return 0; file = jed_dir_file_merge (b->dir, b->file); status = jed_unlock_file (file); SLfree (file); return status; } int jed_unlock_buffer_files (void) { int status; Buffer *b; status = 0; b = CBuf; do { b = b->next; if (-1 == jed_unlock_buffer_file (b)) status = -1; } while (b != CBuf); return status; } #endif /* JED_HAS_EMACS_LOCKING */