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-2019
   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 /*** file scope variables ************************************************************************/
  79 
  80 /*** file scope functions ************************************************************************/
  81 /* --------------------------------------------------------------------------------------------- */
  82 /** \fn static char * lock_build_name (void)
  83  *  \brief builds user@host.domain.pid string (need to be freed)
  84  *  \return a pointer to lock filename
  85  */
  86 
  87 static char *
  88 lock_build_name (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
  89 {
  90     char host[BUF_SIZE];
  91     const char *user = NULL;
  92     struct passwd *pw;
  93 
  94     pw = getpwuid (getuid ());
  95     if (pw)
  96         user = pw->pw_name;
  97     if (!user)
  98         user = getenv ("USER");
  99     if (!user)
 100         user = getenv ("USERNAME");
 101     if (!user)
 102         user = getenv ("LOGNAME");
 103     if (!user)
 104         user = "";
 105 
 106     /** \todo Use FQDN, no clean interface, so requires lot of code */
 107     if (gethostname (host, BUF_SIZE - 1) == -1)
 108         *host = '\0';
 109 
 110     return g_strdup_printf ("%s@%s.%d", user, host, (int) getpid ());
 111 }
 112 
 113 /* --------------------------------------------------------------------------------------------- */
 114 
 115 static char *
 116 lock_build_symlink_name (const vfs_path_t * fname_vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
 117 {
 118     const char *elpath;
 119     char *str_filename, *str_dirname, *symlink_name;
 120 
 121     /* get first path piece */
 122     elpath = vfs_path_get_by_index (fname_vpath, 0)->path;
 123 
 124     str_filename = g_path_get_basename (elpath);
 125     str_dirname = g_path_get_dirname (elpath);
 126     symlink_name = g_strconcat (str_dirname, PATH_SEP_STR ".#", str_filename, (char *) NULL);
 127     g_free (str_dirname);
 128     g_free (str_filename);
 129 
 130     return symlink_name;
 131 }
 132 
 133 /* --------------------------------------------------------------------------------------------- */
 134 /**
 135  * Extract pid from user@host.domain.pid string
 136  */
 137 
 138 static lock_s *
 139 lock_extract_info (const char *str)
     /* [previous][next][first][last][top][bottom][index][help]  */
 140 {
 141     size_t i, len;
 142     const char *p, *s;
 143     static char pid[PID_BUF_SIZE], who[BUF_SIZE];
 144     static lock_s lock;
 145 
 146     len = strlen (str);
 147 
 148     for (p = str + len - 1; p >= str; p--)
 149         if (*p == '.')
 150             break;
 151 
 152     /* Everything before last '.' is user@host */
 153     i = 0;
 154     for (s = str; i < BUF_SIZE && s < p; s++)
 155         who[i++] = *s;
 156     if (i == BUF_SIZE)
 157         i--;
 158     who[i] = '\0';
 159 
 160     /* Treat text between '.' and ':' or '\0' as pid */
 161     i = 0;
 162     for (p = p + 1; i < PID_BUF_SIZE && p < str + len && *p != ':'; p++)
 163         pid[i++] = *p;
 164     if (i == PID_BUF_SIZE)
 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, BUF_SIZE - 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     struct stat statbuf;
 283     const char *elpath, *lock;
 284 
 285     if (fname_vpath == NULL)
 286         return 0;
 287 
 288     elpath = vfs_path_get_by_index (fname_vpath, 0)->path;
 289     /* Just to be sure (and don't lock new file) */
 290     if (*elpath == '\0')
 291         return 0;
 292 
 293     lockfname = lock_build_symlink_name (fname_vpath);
 294 
 295     if (lockfname == NULL)
 296         return 0;
 297 
 298     /* Check if lock exists */
 299     if (lstat (lockfname, &statbuf) == -1)
 300         goto ret;
 301 
 302     lock = lock_get_info (lockfname);
 303     if (lock != NULL)
 304     {
 305         /* Don't touch if lock is not ours */
 306         if (lock_extract_info (lock)->pid != getpid ())
 307             goto ret;
 308     }
 309 
 310     /* Remove lock */
 311     unlink (lockfname);
 312 
 313   ret:
 314     g_free (lockfname);
 315     return 0;
 316 }
 317 
 318 /* --------------------------------------------------------------------------------------------- */

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