1 #include <fcntl.h>
2 #include <signal.h>
3 #include <stdio.h>
4 #include <sys/stat.h>
5 #include <time.h>
6 #include <unistd.h>
7 #include "lock.h"
8 #include "buffer.h"
9 #include "editor.h"
10 #include "error.h"
11 #include "util/ascii.h"
12 #include "util/readfile.h"
13 #include "util/str-util.h"
14 #include "util/xmalloc.h"
15 #include "util/xreadwrite.h"
16 #include "util/xsnprintf.h"
17
18 static char *file_locks;
19 static char *file_locks_lock;
20
process_exists(pid_t pid)21 static bool process_exists(pid_t pid)
22 {
23 return !kill(pid, 0);
24 }
25
rewrite_lock_file(char * buf,ssize_t * sizep,const char * filename)26 static pid_t rewrite_lock_file(char *buf, ssize_t *sizep, const char *filename)
27 {
28 size_t filename_len = strlen(filename);
29 pid_t my_pid = getpid();
30 ssize_t size = *sizep;
31 ssize_t pos = 0;
32 pid_t other_pid = 0;
33
34 while (pos < size) {
35 ssize_t bol = pos;
36 bool remove_line = false;
37 pid_t pid = 0;
38
39 while (pos < size && ascii_isdigit(buf[pos])) {
40 pid *= 10;
41 pid += buf[pos++] - '0';
42 }
43 while (pos < size && (buf[pos] == ' ' || buf[pos] == '\t')) {
44 pos++;
45 }
46 char *nl = memchr(buf + pos, '\n', size - pos);
47 ssize_t next_bol = nl - buf + 1;
48
49 bool same =
50 filename_len == next_bol - 1 - pos
51 && !memcmp(buf + pos, filename, filename_len)
52 ;
53 if (pid == my_pid) {
54 if (same) {
55 // lock = 1 => pid conflict. lock must be stale
56 // lock = 0 => normal unlock case
57 remove_line = true;
58 }
59 } else if (process_exists(pid)) {
60 if (same) {
61 other_pid = pid;
62 }
63 } else {
64 // Release lock from dead process
65 remove_line = true;
66 }
67
68 if (remove_line) {
69 memmove(buf + bol, buf + next_bol, size - next_bol);
70 size -= next_bol - bol;
71 pos = bol;
72 } else {
73 pos = next_bol;
74 }
75 }
76 *sizep = size;
77 return other_pid;
78 }
79
lock_or_unlock(const char * filename,bool lock)80 static int lock_or_unlock(const char *filename, bool lock)
81 {
82 if (!file_locks) {
83 file_locks = editor_file("file-locks");
84 file_locks_lock = editor_file("file-locks.lock");
85 }
86
87 if (streq(filename, file_locks) || streq(filename, file_locks_lock)) {
88 return 0;
89 }
90
91 int tries = 0;
92 int wfd;
93 while (1) {
94 wfd = open(file_locks_lock, O_WRONLY | O_CREAT | O_EXCL, 0666);
95 if (wfd >= 0) {
96 break;
97 }
98
99 if (errno != EEXIST) {
100 error_msg (
101 "Error creating %s: %s",
102 file_locks_lock,
103 strerror(errno)
104 );
105 return -1;
106 }
107 if (++tries == 3) {
108 if (unlink(file_locks_lock)) {
109 error_msg (
110 "Error removing stale lock file %s: %s",
111 file_locks_lock,
112 strerror(errno)
113 );
114 return -1;
115 }
116 error_msg("Stale lock file %s removed.", file_locks_lock);
117 } else {
118 const struct timespec req = {
119 .tv_sec = 0,
120 .tv_nsec = 100 * 1000000,
121 };
122 nanosleep(&req, NULL);
123 }
124 }
125
126 char *buf = NULL;
127 ssize_t size = read_file(file_locks, &buf);
128 if (size < 0) {
129 if (errno != ENOENT) {
130 error_msg("Error reading %s: %s", file_locks, strerror(errno));
131 goto error;
132 }
133 size = 0;
134 }
135 if (size > 0 && buf[size - 1] != '\n') {
136 buf[size++] = '\n';
137 }
138 pid_t pid = rewrite_lock_file(buf, &size, filename);
139 if (lock) {
140 if (pid == 0) {
141 const size_t n = strlen(filename) + 32;
142 xrenew(buf, size + n);
143 xsnprintf(buf + size, n, "%d %s\n", getpid(), filename);
144 size += strlen(buf + size);
145 } else {
146 error_msg("File is locked (%s) by process %d", file_locks, pid);
147 }
148 }
149 if (xwrite(wfd, buf, size) < 0) {
150 error_msg("Error writing %s: %s", file_locks_lock, strerror(errno));
151 goto error;
152 }
153 if (close(wfd)) {
154 error_msg("Error closing %s: %s", file_locks_lock, strerror(errno));
155 goto error;
156 }
157 if (rename(file_locks_lock, file_locks)) {
158 error_msg (
159 "Renaming %s to %s: %s",
160 file_locks_lock,
161 file_locks,
162 strerror(errno)
163 );
164 goto error;
165 }
166 free(buf);
167 return pid == 0 ? 0 : -1;
168 error:
169 unlink(file_locks_lock);
170 free(buf);
171 close(wfd);
172 return -1;
173 }
174
lock_file(const char * filename)175 int lock_file(const char *filename)
176 {
177 return lock_or_unlock(filename, true);
178 }
179
unlock_file(const char * filename)180 void unlock_file(const char *filename)
181 {
182 lock_or_unlock(filename, false);
183 }
184