Ticket #2097: clean up before 4.7.2 release.
[midnight-commander.git] / src / editor / editlock.c
blob27d4b99513e4397d0e580cdd321210d45b149b42
1 /* editor file locking.
3 Copyright (C) 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
5 Authors: 2003 Adam Byrtek
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20 02110-1301, USA.
24 /** \file
25 * \brief Source: editor file locking
26 * \author Adam Byrtek
27 * \date 2003
29 * Locking scheme used in mcedit is based on a documentation found
30 * in JED editor sources. Abstract from lock.c file (by John E. Davis):
32 * The basic idea here is quite simple. Whenever a buffer is attached to
33 * a file, and that buffer is modified, then attempt to lock the
34 * file. Moreover, before writing to a file for any reason, lock the
35 * file. The lock is really a protocol respected and not a real lock.
36 * The protocol is this: If in the directory of the file is a
37 * symbolic link with name ".#FILE", the FILE is considered to be locked
38 * by the process specified by the link.
41 #include <config.h>
43 #include <signal.h> /* kill() */
44 #include <stdio.h>
45 #include <stdarg.h>
46 #include <sys/types.h>
47 #include <unistd.h>
48 #include <string.h>
49 #include <ctype.h>
50 #include <errno.h>
51 #include <sys/stat.h>
52 #include <pwd.h>
53 #include <stdlib.h>
55 #include "lib/global.h"
56 #include "lib/vfs/mc-vfs/vfs.h"
57 #include "lib/strutil.h" /* utf string functions */
60 #include "edit-impl.h"
61 #include "editlock.h"
63 #include "src/wtools.h" /* edit_query_dialog () */
65 #define BUF_SIZE 255
66 #define PID_BUF_SIZE 10
68 struct lock_s {
69 char *who;
70 pid_t pid;
73 /** \fn static char * lock_build_name (void)
74 * \brief builds user@host.domain.pid string (need to be freed)
75 * \return a pointer to lock filename
77 static char *
78 lock_build_name (void)
80 char host[BUF_SIZE];
81 const char *user = NULL;
82 struct passwd *pw;
84 pw = getpwuid (getuid ());
85 if (pw) user = pw->pw_name;
86 if (!user) user = getenv ("USER");
87 if (!user) user = getenv ("USERNAME");
88 if (!user) user = getenv ("LOGNAME");
89 if (!user) user = "";
91 /** \todo Use FQDN, no clean interface, so requires lot of code */
92 if (gethostname (host, BUF_SIZE - 1) == -1)
93 *host = '\0';
95 return g_strdup_printf ("%s@%s.%d", user, host, (int) getpid ());
98 static char *
99 lock_build_symlink_name (const char *fname)
101 char *fname_copy, *symlink_name;
102 char absolute_fname[PATH_MAX];
104 if (mc_realpath (fname, absolute_fname) == NULL)
105 return NULL;
107 fname = x_basename (absolute_fname);
108 fname_copy = g_strdup (fname);
109 absolute_fname[fname - absolute_fname] = '\0';
110 symlink_name = g_strconcat (absolute_fname, ".#", fname_copy, (char *) NULL);
111 g_free (fname_copy);
113 return symlink_name;
116 /* Extract pid from user@host.domain.pid string */
117 static struct lock_s *
118 lock_extract_info (const char *str)
120 size_t i, len;
121 const char *p, *s;
122 static char pid[PID_BUF_SIZE], who[BUF_SIZE];
123 static struct lock_s lock;
125 len = strlen (str);
127 for (p = str + len - 1; p >= str; p--)
128 if (*p == '.')
129 break;
131 /* Everything before last '.' is user@host */
132 i = 0;
133 for (s = str; s < p && i < BUF_SIZE; s++)
134 who[i++] = *s;
135 who[i] = '\0';
137 /* Treat text between '.' and ':' or '\0' as pid */
138 i = 0;
139 for (p = p + 1; (p < str + len) && (*p != ':') && (i < PID_BUF_SIZE); p++)
140 pid[i++] = *p;
141 pid[i] = '\0';
143 lock.pid = (pid_t) atol (pid);
144 lock.who = who;
145 return &lock;
148 /* Extract user@host.domain.pid from lock file (static string) */
149 static char *
150 lock_get_info (const char *lockfname)
152 int cnt;
153 static char buf[BUF_SIZE];
155 cnt = readlink (lockfname, buf, BUF_SIZE - 1);
156 if (cnt == -1 || *buf == '\0')
157 return NULL;
158 buf[cnt] = '\0';
159 return buf;
163 /* Tries to raise file lock
164 Returns 1 on success, 0 on failure, -1 if abort
165 Warning: Might do screen refresh and lose edit->force */
167 edit_lock_file (const char *fname)
169 char *lockfname, *newlock, *msg, *lock;
170 struct stat statbuf;
171 struct lock_s *lockinfo;
173 /* Just to be sure (and don't lock new file) */
174 if (!fname || !*fname)
175 return 0;
177 /* Locking on VFS is not supported */
178 if (!vfs_file_is_local (fname))
179 return 0;
181 /* Check if already locked */
182 lockfname = lock_build_symlink_name (fname);
183 if (lockfname == NULL)
184 return 0;
185 if (lstat (lockfname, &statbuf) == 0) {
186 lock = lock_get_info (lockfname);
187 if (!lock) {
188 g_free (lockfname);
189 return 0;
191 lockinfo = lock_extract_info (lock);
193 /* Check if locking process alive, ask user if required */
194 if (!lockinfo->pid
195 || !(kill (lockinfo->pid, 0) == -1 && errno == ESRCH)) {
196 msg =
197 g_strdup_printf (_
198 ("File \"%s\" is already being edited\n"
199 "User: %s\nProcess ID: %d"), x_basename (lockfname) + 2,
200 lockinfo->who, (int) lockinfo->pid);
201 /* TODO: Implement "Abort" - needs to rewind undo stack */
202 switch (edit_query_dialog2
203 (_("File locked"), msg, _("&Grab lock"),
204 _("&Ignore lock"))) {
205 case 0:
206 break;
207 case 1:
208 case -1:
209 g_free (lockfname);
210 g_free (msg);
211 return 0;
213 g_free (msg);
215 unlink (lockfname);
218 /* Create lock symlink */
219 newlock = lock_build_name ();
220 if (symlink (newlock, lockfname) == -1) {
221 g_free (lockfname);
222 g_free (newlock);
223 return 0;
226 g_free (lockfname);
227 g_free (newlock);
228 return 1;
231 /* Lowers file lock if possible
232 Always returns 0 to make 'lock = edit_unlock_file (f)' possible */
234 edit_unlock_file (const char *fname)
236 char *lockfname, *lock;
237 struct stat statbuf;
239 /* Just to be sure */
240 if (!fname || !*fname)
241 return 0;
243 lockfname = lock_build_symlink_name (fname);
244 if (lockfname == NULL)
245 return 0;
247 /* Check if lock exists */
248 if (lstat (lockfname, &statbuf) == -1) {
249 g_free (lockfname);
250 return 0;
253 lock = lock_get_info (lockfname);
254 if (lock) {
255 /* Don't touch if lock is not ours */
256 if (lock_extract_info (lock)->pid != getpid ()) {
257 g_free (lockfname);
258 return 0;
262 /* Remove lock */
263 unlink (lockfname);
264 g_free (lockfname);
265 return 0;