diff options
Diffstat (limited to 'lib/lock.c')
-rw-r--r-- | lib/lock.c | 314 |
1 files changed, 314 insertions, 0 deletions
diff --git a/lib/lock.c b/lib/lock.c new file mode 100644 index 0000000..bae7093 --- /dev/null +++ b/lib/lock.c @@ -0,0 +1,314 @@ +/* + File locking + + Copyright (C) 2003-2023 + Free Software Foundation, Inc. + + Written by: + Adam Byrtek, 2003 + + This file is part of the Midnight Commander. + + The Midnight Commander 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 3 of the License, + or (at your option) any later version. + + The Midnight Commander 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, see <http://www.gnu.org/licenses/>. + */ + +/** \file + * \brief Source: file locking + * \author Adam Byrtek + * \date 2003 + * + * Locking scheme 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. + */ + +#include <config.h> + +#include <signal.h> /* kill() */ +#include <stdio.h> +#include <stdarg.h> +#include <sys/types.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <sys/stat.h> +#include <pwd.h> +#include <stdlib.h> + +#include "lib/global.h" +#include "lib/vfs/vfs.h" +#include "lib/util.h" /* tilde_expand() */ +#include "lib/lock.h" +#include "lib/widget.h" /* query_dialog() */ + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +#define BUF_SIZE 255 +#define PID_BUF_SIZE 10 + +/*** file scope type declarations ****************************************************************/ + +typedef struct +{ + char *who; + pid_t pid; +} lock_s; + +/*** forward declarations (file scope functions) *************************************************/ + +/*** file scope variables ************************************************************************/ + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ +/** \fn static char * lock_build_name (void) + * \brief builds user@host.domain.pid string (need to be freed) + * \return a pointer to lock filename + */ + +static char * +lock_build_name (void) +{ + char host[BUF_SIZE]; + const char *user = NULL; + struct passwd *pw; + + pw = getpwuid (getuid ()); + if (pw != NULL) + user = pw->pw_name; + if (user == NULL) + user = getenv ("USER"); + if (user == NULL) + user = getenv ("USERNAME"); + if (user == NULL) + user = getenv ("LOGNAME"); + if (user == NULL) + user = ""; + + /** \todo Use FQDN, no clean interface, so requires lot of code */ + if (gethostname (host, sizeof (host) - 1) == -1) + *host = '\0'; + + return g_strdup_printf ("%s@%s.%d", user, host, (int) getpid ()); +} + +/* --------------------------------------------------------------------------------------------- */ + +static char * +lock_build_symlink_name (const vfs_path_t * fname_vpath) +{ + const char *elpath; + char *str_filename, *str_dirname, *symlink_name; + + /* get first path piece */ + elpath = vfs_path_get_by_index (fname_vpath, 0)->path; + + str_filename = g_path_get_basename (elpath); + str_dirname = g_path_get_dirname (elpath); + symlink_name = g_strconcat (str_dirname, PATH_SEP_STR ".#", str_filename, (char *) NULL); + g_free (str_dirname); + g_free (str_filename); + + return symlink_name; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Extract pid from user@host.domain.pid string + */ + +static lock_s * +lock_extract_info (const char *str) +{ + size_t i, len; + const char *p, *s; + static char pid[PID_BUF_SIZE], who[BUF_SIZE]; + static lock_s lock; + + len = strlen (str); + + for (p = str + len - 1; p >= str && *p != '.'; p--) + ; + + /* Everything before last '.' is user@host */ + for (i = 0, s = str; i < sizeof (who) && s < p; i++, s++) + who[i] = *s; + if (i == sizeof (who)) + i--; + who[i] = '\0'; + + /* Treat text between '.' and ':' or '\0' as pid */ + for (i = 0, p++, s = str + len; i < sizeof (pid) && p < s && *p != ':'; i++, p++) + pid[i] = *p; + if (i == sizeof (pid)) + i--; + pid[i] = '\0'; + + lock.pid = (pid_t) atol (pid); + lock.who = who; + return &lock; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Extract user@host.domain.pid from lock file (static string) + */ + +static const char * +lock_get_info (const char *lockfname) +{ + ssize_t cnt; + static char buf[BUF_SIZE]; + + cnt = readlink (lockfname, buf, sizeof (buf) - 1); + if (cnt == -1 || *buf == '\0') + return NULL; + buf[cnt] = '\0'; + return buf; +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +/* Tries to raise file lock + Returns 1 on success, 0 on failure, -1 if abort + Warning: Might do screen refresh and lose edit->force */ + +int +lock_file (const vfs_path_t * fname_vpath) +{ + char *lockfname = NULL, *newlock, *msg; + struct stat statbuf; + lock_s *lockinfo; + gboolean is_local; + gboolean symlink_ok = FALSE; + const char *elpath; + + if (fname_vpath == NULL) + return 0; + + elpath = vfs_path_get_by_index (fname_vpath, 0)->path; + /* Just to be sure (and don't lock new file) */ + if (*elpath == '\0') + return 0; + + /* Locking on VFS is not supported */ + is_local = vfs_file_is_local (fname_vpath); + if (is_local) + { + /* Check if already locked */ + lockfname = lock_build_symlink_name (fname_vpath); + } + + if (!is_local || lockfname == NULL) + return 0; + + if (lstat (lockfname, &statbuf) == 0) + { + const char *lock; + + lock = lock_get_info (lockfname); + if (lock == NULL) + goto ret; + lockinfo = lock_extract_info (lock); + + /* Check if locking process alive, ask user if required */ + if (lockinfo->pid == 0 || !(kill (lockinfo->pid, 0) == -1 && errno == ESRCH)) + { + msg = + g_strdup_printf (_ + ("File \"%s\" is already being edited.\n" + "User: %s\nProcess ID: %d"), x_basename (lockfname) + 2, + lockinfo->who, (int) lockinfo->pid); + /* TODO: Implement "Abort" - needs to rewind undo stack */ + switch (query_dialog + (_("File locked"), msg, D_NORMAL, 2, _("&Grab lock"), _("&Ignore lock"))) + { + case 0: + break; + case 1: + case -1: + default: /* Esc Esc */ + g_free (msg); + goto ret; + } + g_free (msg); + } + unlink (lockfname); + } + + /* Create lock symlink */ + newlock = lock_build_name (); + symlink_ok = (symlink (newlock, lockfname) != -1); + g_free (newlock); + + ret: + g_free (lockfname); + return symlink_ok ? 1 : 0; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Lowers file lock if possible + * @return Always 0 + */ + +int +unlock_file (const vfs_path_t * fname_vpath) +{ + char *lockfname; + const char *elpath; + + if (fname_vpath == NULL) + return 0; + + elpath = vfs_path_get_by_index (fname_vpath, 0)->path; + /* Just to be sure (and don't lock new file) */ + if (*elpath == '\0') + return 0; + + lockfname = lock_build_symlink_name (fname_vpath); + if (lockfname != NULL) + { + struct stat statbuf; + + /* Check if lock exists */ + if (lstat (lockfname, &statbuf) != -1) + { + const char *lock; + + lock = lock_get_info (lockfname); + /* Don't touch if lock is not ours */ + if (lock == NULL || lock_extract_info (lock)->pid == getpid ()) + unlink (lockfname); + } + + g_free (lockfname); + } + + return 0; +} + +/* --------------------------------------------------------------------------------------------- */ |