summaryrefslogtreecommitdiff
path: root/examples/dte/lock.c
blob: 74cf3a40d8ae3e4815b7407dc2bbf0cccafd9ff7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
#include <errno.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include "lock.h"
#include "error.h"
#include "util/debug.h"
#include "util/log.h"
#include "util/path.h"
#include "util/readfile.h"
#include "util/str-util.h"
#include "util/string-view.h"
#include "util/strtonum.h"
#include "util/xmalloc.h"
#include "util/xreadwrite.h"
#include "util/xsnprintf.h"

// These are initialized during early startup and then never changed,
// so they're deemed an "acceptable" use of globals:
static const char *file_locks;
static const char *file_locks_lock;
static mode_t file_locks_mode = 0666;
static pid_t editor_pid;

void init_file_locks_context(const char *fallback_dir, pid_t pid)
{
    BUG_ON(file_locks);
    const char *dir = xgetenv("XDG_RUNTIME_DIR");
    if (!dir) {
        LOG_INFO("$XDG_RUNTIME_DIR not set");
        dir = fallback_dir;
    } else if (unlikely(!path_is_absolute(dir))) {
        LOG_WARNING("$XDG_RUNTIME_DIR invalid (not an absolute path)");
        dir = fallback_dir;
    } else {
        // Set sticky bit (see XDG Base Directory Specification)
        #ifdef S_ISVTX
            file_locks_mode |= S_ISVTX;
        #endif
    }

    file_locks = path_join(dir, "dte-locks");
    file_locks_lock = path_join(dir, "dte-locks.lock");
    editor_pid = pid;
    LOG_INFO("locks file: %s", file_locks);
}

static bool process_exists(pid_t pid)
{
    return !kill(pid, 0);
}

static pid_t rewrite_lock_file(char *buf, size_t *sizep, const char *filename)
{
    const size_t filename_len = strlen(filename);
    size_t size = *sizep;
    pid_t other_pid = 0;

    for (size_t pos = 0, bol = 0; pos < size; bol = pos) {
        StringView line = buf_slice_next_line(buf, &pos, size);
        uintmax_t num;
        size_t numlen = buf_parse_uintmax(line.data, line.length, &num);
        if (unlikely(numlen == 0 || num != (pid_t)num)) {
            goto remove_line;
        }

        strview_remove_prefix(&line, numlen);
        if (unlikely(!strview_has_prefix(&line, " /"))) {
            goto remove_line;
        }
        strview_remove_prefix(&line, 1);

        bool same = strview_equal_strn(&line, filename, filename_len);
        pid_t pid = (pid_t)num;
        if (pid == editor_pid) {
            if (same) {
                goto remove_line;
            }
            continue;
        } else if (process_exists(pid)) {
            if (same) {
                other_pid = pid;
            }
            continue;
        }

        remove_line:
        memmove(buf + bol, buf + pos, size - pos);
        size -= pos - bol;
        pos = bol;
    }

    *sizep = size;
    return other_pid;
}

static bool lock_or_unlock(const char *filename, bool lock)
{
    BUG_ON(!file_locks);
    if (streq(filename, file_locks) || streq(filename, file_locks_lock)) {
        return true;
    }

    mode_t mode = file_locks_mode;
    int tries = 0;
    int wfd;
    while (1) {
        wfd = xopen(file_locks_lock, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, mode);
        if (wfd >= 0) {
            break;
        }

        if (errno != EEXIST) {
            return error_msg("Error creating %s: %s", file_locks_lock, strerror(errno));
        }
        if (++tries == 3) {
            if (unlink(file_locks_lock)) {
                return error_msg (
                    "Error removing stale lock file %s: %s",
                    file_locks_lock,
                    strerror(errno)
                );
            }
            error_msg("Stale lock file %s removed", file_locks_lock);
        } else {
            const struct timespec req = {
                .tv_sec = 0,
                .tv_nsec = 100 * 1000000,
            };
            nanosleep(&req, NULL);
        }
    }

    char *buf = NULL;
    ssize_t ssize = read_file(file_locks, &buf);
    if (ssize < 0) {
        if (errno != ENOENT) {
            error_msg("Error reading %s: %s", file_locks, strerror(errno));
            goto error;
        }
        ssize = 0;
    }

    size_t size = (size_t)ssize;
    pid_t pid = rewrite_lock_file(buf, &size, filename);
    if (lock) {
        if (pid == 0) {
            intmax_t p = (intmax_t)editor_pid;
            size_t n = strlen(filename) + DECIMAL_STR_MAX(pid) + 4;
            xrenew(buf, size + n);
            size += xsnprintf(buf + size, n, "%jd %s\n", p, filename);
        } else {
            intmax_t p = (intmax_t)pid;
            error_msg("File is locked (%s) by process %jd", file_locks, p);
        }
    }

    if (xwrite_all(wfd, buf, size) < 0) {
        error_msg("Error writing %s: %s", file_locks_lock, strerror(errno));
        goto error;
    }

    int r = xclose(wfd);
    wfd = -1;
    if (r != 0) {
        error_msg("Error closing %s: %s", file_locks_lock, strerror(errno));
        goto error;
    }

    if (rename(file_locks_lock, file_locks)) {
        const char *err = strerror(errno);
        error_msg("Renaming %s to %s: %s", file_locks_lock, file_locks, err);
        goto error;
    }

    free(buf);
    return (pid == 0);

error:
    unlink(file_locks_lock);
    free(buf);
    if (wfd >= 0) {
        xclose(wfd);
    }
    return false;
}

bool lock_file(const char *filename)
{
    return lock_or_unlock(filename, true);
}

void unlock_file(const char *filename)
{
    lock_or_unlock(filename, false);
}