Ticket #2762: mc ignores second directory argument.
[midnight-commander.git] / lib / lock.c
blobfaaed1ea529e407156a4128ea6712bf70a758ad7
1 /*
2 File locking
4 Copyright (C) 2003, 2004, 2005, 2006, 2007, 2011
5 The Free Software Foundation, Inc.
7 Written by:
8 Adam Byrtek, 2003
10 This file is part of the Midnight Commander.
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.
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.
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/>.
26 /** \file
27 * \brief Source: file locking
28 * \author Adam Byrtek
29 * \date 2003
31 * Locking scheme is based on a documentation found
32 * in JED editor sources. Abstract from lock.c file (by John E. Davis):
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.
43 #include <config.h>
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>
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() */
63 /*** global variables ****************************************************************************/
65 /*** file scope macro definitions ****************************************************************/
67 #define BUF_SIZE 255
68 #define PID_BUF_SIZE 10
70 /*** file scope type declarations ****************************************************************/
72 struct lock_s
74 char *who;
75 pid_t pid;
78 /*** file scope variables ************************************************************************/
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
87 static char *
88 lock_build_name (void)
90 char host[BUF_SIZE];
91 const char *user = NULL;
92 struct passwd *pw;
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 = "";
106 /** \todo Use FQDN, no clean interface, so requires lot of code */
107 if (gethostname (host, BUF_SIZE - 1) == -1)
108 *host = '\0';
110 return g_strdup_printf ("%s@%s.%d", user, host, (int) getpid ());
113 /* --------------------------------------------------------------------------------------------- */
115 static char *
116 lock_build_symlink_name (const vfs_path_t * fname_vpath)
118 const char *elpath, *str_filename;
119 char *str_dirname, *symlink_name;
121 /* get first path piece */
122 elpath = vfs_path_get_by_index (fname_vpath, 0)->path;
124 str_filename = g_basename (elpath);
125 str_dirname = g_dirname (elpath);
127 symlink_name = g_strconcat (str_dirname, PATH_SEP_STR ".#", str_filename, (char *) NULL);
128 g_free (str_dirname);
129 return symlink_name;
132 /* --------------------------------------------------------------------------------------------- */
134 * Extract pid from user@host.domain.pid string
137 static struct lock_s *
138 lock_extract_info (const char *str)
140 size_t i, len;
141 const char *p, *s;
142 static char pid[PID_BUF_SIZE], who[BUF_SIZE];
143 static struct lock_s lock;
145 len = strlen (str);
147 for (p = str + len - 1; p >= str; p--)
148 if (*p == '.')
149 break;
151 /* Everything before last '.' is user@host */
152 i = 0;
153 for (s = str; s < p && i < BUF_SIZE; s++)
154 who[i++] = *s;
155 who[i] = '\0';
157 /* Treat text between '.' and ':' or '\0' as pid */
158 i = 0;
159 for (p = p + 1; (p < str + len) && (*p != ':') && (i < PID_BUF_SIZE); p++)
160 pid[i++] = *p;
161 pid[i] = '\0';
163 lock.pid = (pid_t) atol (pid);
164 lock.who = who;
165 return &lock;
168 /* --------------------------------------------------------------------------------------------- */
170 * Extract user@host.domain.pid from lock file (static string)
173 static char *
174 lock_get_info (const char *lockfname)
176 int cnt;
177 static char buf[BUF_SIZE];
179 cnt = readlink (lockfname, buf, BUF_SIZE - 1);
180 if (cnt == -1 || *buf == '\0')
181 return NULL;
182 buf[cnt] = '\0';
183 return buf;
186 /* --------------------------------------------------------------------------------------------- */
187 /*** public functions ****************************************************************************/
188 /* --------------------------------------------------------------------------------------------- */
190 /* Tries to raise file lock
191 Returns 1 on success, 0 on failure, -1 if abort
192 Warning: Might do screen refresh and lose edit->force */
195 lock_file (const vfs_path_t * fname_vpath)
197 char *lockfname = NULL, *newlock, *msg, *lock;
198 struct stat statbuf;
199 struct lock_s *lockinfo;
200 gboolean is_local;
201 gboolean symlink_ok = FALSE;
202 const char *elpath;
204 if (fname_vpath == NULL)
205 return 0;
207 elpath = vfs_path_get_by_index (fname_vpath, 0)->path;
208 /* Just to be sure (and don't lock new file) */
209 if (*elpath == '\0')
210 return 0;
212 /* Locking on VFS is not supported */
213 is_local = vfs_file_is_local (fname_vpath);
214 if (is_local)
216 /* Check if already locked */
217 lockfname = lock_build_symlink_name (fname_vpath);
220 if (!is_local || lockfname == NULL)
221 return 0;
223 if (lstat (lockfname, &statbuf) == 0)
225 lock = lock_get_info (lockfname);
226 if (lock == NULL)
227 goto ret;
228 lockinfo = lock_extract_info (lock);
230 /* Check if locking process alive, ask user if required */
231 if (lockinfo->pid == 0 || !(kill (lockinfo->pid, 0) == -1 && errno == ESRCH))
233 msg =
234 g_strdup_printf (_
235 ("File \"%s\" is already being edited.\n"
236 "User: %s\nProcess ID: %d"), x_basename (lockfname) + 2,
237 lockinfo->who, (int) lockinfo->pid);
238 /* TODO: Implement "Abort" - needs to rewind undo stack */
239 switch (query_dialog
240 (_("File locked"), msg, D_NORMAL, 2, _("&Grab lock"), _("&Ignore lock")))
242 case 0:
243 break;
244 case 1:
245 case -1:
246 g_free (msg);
247 goto ret;
248 break; /* FIXME: unneeded? */
250 g_free (msg);
252 unlink (lockfname);
255 /* Create lock symlink */
256 newlock = lock_build_name ();
257 symlink_ok = (symlink (newlock, lockfname) != -1);
258 g_free (newlock);
260 ret:
261 g_free (lockfname);
262 return symlink_ok ? 1 : 0;
265 /* --------------------------------------------------------------------------------------------- */
267 * Lowers file lock if possible
268 * @returns Always 0
272 unlock_file (const vfs_path_t * fname_vpath)
274 char *lockfname, *lock;
275 struct stat statbuf;
276 const char *elpath;
278 if (fname_vpath == NULL)
279 return 0;
281 elpath = vfs_path_get_by_index (fname_vpath, 0)->path;
282 /* Just to be sure (and don't lock new file) */
283 if (*elpath == '\0')
284 return 0;
286 lockfname = lock_build_symlink_name (fname_vpath);
288 if (lockfname == NULL)
289 return 0;
291 /* Check if lock exists */
292 if (lstat (lockfname, &statbuf) == -1)
293 goto ret;
295 lock = lock_get_info (lockfname);
296 if (lock != NULL)
298 /* Don't touch if lock is not ours */
299 if (lock_extract_info (lock)->pid != getpid ())
300 goto ret;
303 /* Remove lock */
304 unlink (lockfname);
306 ret:
307 g_free (lockfname);
308 return 0;
311 /* --------------------------------------------------------------------------------------------- */