root/lib/lock.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. lock_build_name
  2. lock_build_symlink_name
  3. lock_extract_info
  4. lock_get_info
  5. lock_file
  6. unlock_file

   1 /*
   2    File locking
   3 
   4    Copyright (C) 2003-2024
   5    Free Software Foundation, Inc.
   6 
   7    Written by:
   8    Adam Byrtek, 2003
   9 
  10    This file is part of the Midnight Commander.
  11 
  12    The Midnight Commander is free software: you can redistribute it
  13    and/or modify it under the terms of the GNU General Public License as
  14    published by the Free Software Foundation, either version 3 of the License,
  15    or (at your option) any later version.
  16 
  17    The Midnight Commander is distributed in the hope that it will be useful,
  18    but WITHOUT ANY WARRANTY; without even the implied warranty of
  19    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  20    GNU General Public License for more details.
  21 
  22    You should have received a copy of the GNU General Public License
  23    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  24  */
  25 
  26 /** \file
  27  *  \brief Source: file locking
  28  *  \author Adam Byrtek
  29  *  \date 2003
  30  *
  31  *  Locking scheme is based on a documentation found
  32  *  in JED editor sources. Abstract from lock.c file (by John E. Davis):
  33  *
  34  *  The basic idea here is quite simple.  Whenever a buffer is attached to
  35  *  a file, and that buffer is modified, then attempt to lock the
  36  *  file. Moreover, before writing to a file for any reason, lock the
  37  *  file. The lock is really a protocol respected and not a real lock.
  38  *  The protocol is this: If in the directory of the file is a
  39  *  symbolic link with name ".#FILE", the FILE is considered to be locked
  40  *  by the process specified by the link.
  41  */
  42 
  43 #include <config.h>
  44 
  45 #include <signal.h>             /* kill() */
  46 #include <stdio.h>
  47 #include <stdarg.h>
  48 #include <sys/types.h>
  49 #include <unistd.h>
  50 #include <string.h>
  51 #include <ctype.h>
  52 #include <errno.h>
  53 #include <sys/stat.h>
  54 #include <pwd.h>
  55 #include <stdlib.h>
  56 
  57 #include "lib/global.h"
  58 #include "lib/vfs/vfs.h"
  59 #include "lib/util.h"           /* tilde_expand() */
  60 #include "lib/lock.h"
  61 #include "lib/widget.h"         /* query_dialog() */
  62 
  63 /*** global variables ****************************************************************************/
  64 
  65 /*** file scope macro definitions ****************************************************************/
  66 
  67 #define BUF_SIZE 255
  68 #define PID_BUF_SIZE 10
  69 
  70 /*** file scope type declarations ****************************************************************/
  71 
  72 typedef struct
  73 {
  74     char *who;
  75     pid_t pid;
  76 } lock_s;
  77 
  78 /*** forward declarations (file scope functions) *************************************************/
  79 
  80 /*** file scope variables ************************************************************************/
  81 
  82 /* --------------------------------------------------------------------------------------------- */
  83 /*** file scope functions ************************************************************************/
  84 /* --------------------------------------------------------------------------------------------- */
  85 /** \fn static char * lock_build_name (void)
  86  *  \brief builds user@host.domain.pid string (need to be freed)
  87  *  \return a pointer to lock filename
  88  */
  89 
  90 static char *
  91 lock_build_name (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
  92 {
  93     char host[BUF_SIZE];
  94     const char *user = NULL;
  95     struct passwd *pw;
  96 
  97     pw = getpwuid (getuid ());
  98     if (pw != NULL)
  99         user = pw->pw_name;
 100     if (user == NULL)
 101         user = getenv ("USER");
 102     if (user == NULL)
 103         user = getenv ("USERNAME");
 104     if (user == NULL)
 105         user = getenv ("LOGNAME");
 106     if (user == NULL)
 107         user = "";
 108 
 109     /** \todo Use FQDN, no clean interface, so requires lot of code */
 110     if (gethostname (host, sizeof (host) - 1) == -1)
 111         *host = '\0';
 112 
 113     return g_strdup_printf ("%s@%s.%d", user, host, (int) getpid ());
 114 }
 115 
 116 /* --------------------------------------------------------------------------------------------- */
 117 
 118 static char *
 119 lock_build_symlink_name (const vfs_path_t * fname_vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
 120 {
 121     const char *elpath;
 122     char *str_filename, *str_dirname, *symlink_name;
 123 
 124     /* get first path piece */
 125     elpath = vfs_path_get_by_index (fname_vpath, 0)->path;
 126 
 127     str_filename = g_path_get_basename (elpath);
 128     str_dirname = g_path_get_dirname (elpath);
 129     symlink_name = g_strconcat (str_dirname, PATH_SEP_STR ".#", str_filename, (char *) NULL);
 130     g_free (str_dirname);
 131     g_free (str_filename);
 132 
 133     return symlink_name;
 134 }
 135 
 136 /* --------------------------------------------------------------------------------------------- */
 137 /**
 138  * Extract pid from user@host.domain.pid string
 139  */
 140 
 141 static lock_s *
 142 lock_extract_info (const char *str)
     /* [previous][next][first][last][top][bottom][index][help]  */
 143 {
 144     size_t i, len;
 145     const char *p, *s;
 146     static char pid[PID_BUF_SIZE], who[BUF_SIZE];
 147     static lock_s lock;
 148 
 149     len = strlen (str);
 150 
 151     for (p = str + len - 1; p >= str && *p != '.'; p--)
 152         ;
 153 
 154     /* Everything before last '.' is user@host */
 155     for (i = 0, s = str; i < sizeof (who) && s < p; i++, s++)
 156         who[i] = *s;
 157     if (i == sizeof (who))
 158         i--;
 159     who[i] = '\0';
 160 
 161     /* Treat text between '.' and ':' or '\0' as pid */
 162     for (i = 0, p++, s = str + len; i < sizeof (pid) && p < s && *p != ':'; i++, p++)
 163         pid[i] = *p;
 164     if (i == sizeof (pid))
 165         i--;
 166     pid[i] = '\0';
 167 
 168     lock.pid = (pid_t) atol (pid);
 169     lock.who = who;
 170     return &lock;
 171 }
 172 
 173 /* --------------------------------------------------------------------------------------------- */
 174 /**
 175  * Extract user@host.domain.pid from lock file (static string)
 176  */
 177 
 178 static const char *
 179 lock_get_info (const char *lockfname)
     /* [previous][next][first][last][top][bottom][index][help]  */
 180 {
 181     ssize_t cnt;
 182     static char buf[BUF_SIZE];
 183 
 184     cnt = readlink (lockfname, buf, sizeof (buf) - 1);
 185     if (cnt == -1 || *buf == '\0')
 186         return NULL;
 187     buf[cnt] = '\0';
 188     return buf;
 189 }
 190 
 191 /* --------------------------------------------------------------------------------------------- */
 192 /*** public functions ****************************************************************************/
 193 /* --------------------------------------------------------------------------------------------- */
 194 
 195 /* Tries to raise file lock
 196    Returns 1 on success,  0 on failure, -1 if abort
 197    Warning: Might do screen refresh and lose edit->force */
 198 
 199 int
 200 lock_file (const vfs_path_t * fname_vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
 201 {
 202     char *lockfname = NULL, *newlock, *msg;
 203     struct stat statbuf;
 204     lock_s *lockinfo;
 205     gboolean is_local;
 206     gboolean symlink_ok = FALSE;
 207     const char *elpath;
 208 
 209     if (fname_vpath == NULL)
 210         return 0;
 211 
 212     elpath = vfs_path_get_by_index (fname_vpath, 0)->path;
 213     /* Just to be sure (and don't lock new file) */
 214     if (*elpath == '\0')
 215         return 0;
 216 
 217     /* Locking on VFS is not supported */
 218     is_local = vfs_file_is_local (fname_vpath);
 219     if (is_local)
 220     {
 221         /* Check if already locked */
 222         lockfname = lock_build_symlink_name (fname_vpath);
 223     }
 224 
 225     if (!is_local || lockfname == NULL)
 226         return 0;
 227 
 228     if (lstat (lockfname, &statbuf) == 0)
 229     {
 230         const char *lock;
 231 
 232         lock = lock_get_info (lockfname);
 233         if (lock == NULL)
 234             goto ret;
 235         lockinfo = lock_extract_info (lock);
 236 
 237         /* Check if locking process alive, ask user if required */
 238         if (lockinfo->pid == 0 || !(kill (lockinfo->pid, 0) == -1 && errno == ESRCH))
 239         {
 240             msg =
 241                 g_strdup_printf (_
 242                                  ("File \"%s\" is already being edited.\n"
 243                                   "User: %s\nProcess ID: %d"), x_basename (lockfname) + 2,
 244                                  lockinfo->who, (int) lockinfo->pid);
 245             /* TODO: Implement "Abort" - needs to rewind undo stack */
 246             switch (query_dialog
 247                     (_("File locked"), msg, D_NORMAL, 2, _("&Grab lock"), _("&Ignore lock")))
 248             {
 249             case 0:
 250                 break;
 251             case 1:
 252             case -1:
 253             default:           /* Esc Esc */
 254                 g_free (msg);
 255                 goto ret;
 256             }
 257             g_free (msg);
 258         }
 259         unlink (lockfname);
 260     }
 261 
 262     /* Create lock symlink */
 263     newlock = lock_build_name ();
 264     symlink_ok = (symlink (newlock, lockfname) != -1);
 265     g_free (newlock);
 266 
 267   ret:
 268     g_free (lockfname);
 269     return symlink_ok ? 1 : 0;
 270 }
 271 
 272 /* --------------------------------------------------------------------------------------------- */
 273 /**
 274  * Lowers file lock if possible
 275  * @return  Always 0
 276  */
 277 
 278 int
 279 unlock_file (const vfs_path_t * fname_vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
 280 {
 281     char *lockfname;
 282     const char *elpath;
 283 
 284     if (fname_vpath == NULL)
 285         return 0;
 286 
 287     elpath = vfs_path_get_by_index (fname_vpath, 0)->path;
 288     /* Just to be sure (and don't lock new file) */
 289     if (*elpath == '\0')
 290         return 0;
 291 
 292     lockfname = lock_build_symlink_name (fname_vpath);
 293     if (lockfname != NULL)
 294     {
 295         struct stat statbuf;
 296 
 297         /* Check if lock exists */
 298         if (lstat (lockfname, &statbuf) != -1)
 299         {
 300             const char *lock;
 301 
 302             lock = lock_get_info (lockfname);
 303             /* Don't touch if lock is not ours */
 304             if (lock == NULL || lock_extract_info (lock)->pid == getpid ())
 305                 unlink (lockfname);
 306         }
 307 
 308         g_free (lockfname);
 309     }
 310 
 311     return 0;
 312 }
 313 
 314 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */