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