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
25 * \brief Source: editor file locking
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.
43 #include <signal.h> /* kill() */
46 #include <sys/types.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"
63 #include "src/wtools.h" /* edit_query_dialog () */
66 #define PID_BUF_SIZE 10
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
78 lock_build_name (void)
81 const char *user
= NULL
;
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");
91 /** \todo Use FQDN, no clean interface, so requires lot of code */
92 if (gethostname (host
, BUF_SIZE
- 1) == -1)
95 return g_strdup_printf ("%s@%s.%d", user
, host
, (int) getpid ());
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
)
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
);
116 /* Extract pid from user@host.domain.pid string */
117 static struct lock_s
*
118 lock_extract_info (const char *str
)
122 static char pid
[PID_BUF_SIZE
], who
[BUF_SIZE
];
123 static struct lock_s lock
;
127 for (p
= str
+ len
- 1; p
>= str
; p
--)
131 /* Everything before last '.' is user@host */
133 for (s
= str
; s
< p
&& i
< BUF_SIZE
; s
++)
137 /* Treat text between '.' and ':' or '\0' as pid */
139 for (p
= p
+ 1; (p
< str
+ len
) && (*p
!= ':') && (i
< PID_BUF_SIZE
); p
++)
143 lock
.pid
= (pid_t
) atol (pid
);
148 /* Extract user@host.domain.pid from lock file (static string) */
150 lock_get_info (const char *lockfname
)
153 static char buf
[BUF_SIZE
];
155 cnt
= readlink (lockfname
, buf
, BUF_SIZE
- 1);
156 if (cnt
== -1 || *buf
== '\0')
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
;
171 struct lock_s
*lockinfo
;
173 /* Just to be sure (and don't lock new file) */
174 if (!fname
|| !*fname
)
177 /* Locking on VFS is not supported */
178 if (!vfs_file_is_local (fname
))
181 /* Check if already locked */
182 lockfname
= lock_build_symlink_name (fname
);
183 if (lockfname
== NULL
)
185 if (lstat (lockfname
, &statbuf
) == 0) {
186 lock
= lock_get_info (lockfname
);
191 lockinfo
= lock_extract_info (lock
);
193 /* Check if locking process alive, ask user if required */
195 || !(kill (lockinfo
->pid
, 0) == -1 && errno
== ESRCH
)) {
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"))) {
218 /* Create lock symlink */
219 newlock
= lock_build_name ();
220 if (symlink (newlock
, lockfname
) == -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
;
239 /* Just to be sure */
240 if (!fname
|| !*fname
)
243 lockfname
= lock_build_symlink_name (fname
);
244 if (lockfname
== NULL
)
247 /* Check if lock exists */
248 if (lstat (lockfname
, &statbuf
) == -1) {
253 lock
= lock_get_info (lockfname
);
255 /* Don't touch if lock is not ours */
256 if (lock_extract_info (lock
)->pid
!= getpid ()) {