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]](../icons/n_left.png)
![[next]](../icons/right.png)
![[first]](../icons/n_first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/n_right.png)
![[first]](../icons/first.png)
![[last]](../icons/n_last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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 /* --------------------------------------------------------------------------------------------- */