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-2025
   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 <https://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"
  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 = g_strdup_printf (_ ("File \"%s\" is already being edited.\n"
 241                                       "User: %s\nProcess ID: %d"),
 242                                    x_basename (lockfname) + 2, lockinfo->who, (int) lockinfo->pid);
 243             // TODO: Implement "Abort" - needs to rewind undo stack
 244             switch (query_dialog (_ ("File locked"), msg, D_NORMAL, 2, _ ("&Grab lock"),
 245                                   _ ("&Ignore lock")))
 246             {
 247             case 0:
 248                 break;
 249             case 1:
 250             case -1:
 251             default:  // Esc Esc
 252                 g_free (msg);
 253                 goto ret;
 254             }
 255             g_free (msg);
 256         }
 257         unlink (lockfname);
 258     }
 259 
 260     // Create lock symlink
 261     newlock = lock_build_name ();
 262     symlink_ok = (symlink (newlock, lockfname) != -1);
 263     g_free (newlock);
 264 
 265 ret:
 266     g_free (lockfname);
 267     return symlink_ok ? 1 : 0;
 268 }
 269 
 270 /* --------------------------------------------------------------------------------------------- */
 271 /**
 272  * Lowers file lock if possible
 273  * @return  Always 0
 274  */
 275 
 276 int
 277 unlock_file (const vfs_path_t *fname_vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
 278 {
 279     char *lockfname;
 280     const char *elpath;
 281 
 282     if (fname_vpath == NULL)
 283         return 0;
 284 
 285     elpath = vfs_path_get_by_index (fname_vpath, 0)->path;
 286     // Just to be sure (and don't lock new file)
 287     if (*elpath == '\0')
 288         return 0;
 289 
 290     lockfname = lock_build_symlink_name (fname_vpath);
 291     if (lockfname != NULL)
 292     {
 293         struct stat statbuf;
 294 
 295         // Check if lock exists
 296         if (lstat (lockfname, &statbuf) != -1)
 297         {
 298             const char *lock;
 299 
 300             lock = lock_get_info (lockfname);
 301             // Don't touch if lock is not ours
 302             if (lock == NULL || lock_extract_info (lock)->pid == getpid ())
 303                 unlink (lockfname);
 304         }
 305 
 306         g_free (lockfname);
 307     }
 308 
 309     return 0;
 310 }
 311 
 312 /* --------------------------------------------------------------------------------------------- */

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