Coverage Report

Created: 2025-06-13 06:55

/src/ostree/libglnx/glnx-lockfile.c
Line
Count
Source (jump to first uncovered line)
1
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3
/***
4
  This file is part of systemd.
5
  Now copied into libglnx:
6
    - Use GError
7
8
  Copyright 2010 Lennart Poettering
9
  Copyright 2015 Colin Walters <walters@verbum.org>
10
  SPDX-License-Identifier: LGPL-2.1-or-later
11
12
  systemd is free software; you can redistribute it and/or modify it
13
  under the terms of the GNU Lesser General Public License as published by
14
  the Free Software Foundation; either version 2.1 of the License, or
15
  (at your option) any later version.
16
17
  systemd is distributed in the hope that it will be useful, but
18
  WITHOUT ANY WARRANTY; without even the implied warranty of
19
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20
  Lesser General Public License for more details.
21
22
  You should have received a copy of the GNU Lesser General Public License
23
  along with systemd; If not, see <http://www.gnu.org/licenses/>.
24
***/
25
26
#include "libglnx-config.h"
27
28
#include <stdlib.h>
29
#include <stdbool.h>
30
#include <errno.h>
31
#include <string.h>
32
#include <stdio.h>
33
#include <limits.h>
34
#include <unistd.h>
35
#include <sys/types.h>
36
#include <sys/file.h>
37
#include <sys/stat.h>
38
#include <fcntl.h>
39
40
#include "glnx-lockfile.h"
41
#include "glnx-errors.h"
42
#include "glnx-fdio.h"
43
#include "glnx-backport-autocleanups.h"
44
#include "glnx-local-alloc.h"
45
46
#define newa(t, n) ((t*) alloca(sizeof(t)*(n)))
47
48
/**
49
 * glnx_make_lock_file:
50
 * @dfd: Directory file descriptor (if not `AT_FDCWD`, must have lifetime `>=` @out_lock)
51
 * @p: Path
52
 * @operation: one of `LOCK_SH`, `LOCK_EX`, `LOCK_UN`, as passed to flock()
53
 * @out_lock: (out) (caller allocates): Return location for lock
54
 * @error: Error
55
 *
56
 * Block until a lock file named @p (relative to @dfd) can be created,
57
 * using the flags in @operation, returning the lock data in the
58
 * caller-allocated location @out_lock.
59
 *
60
 * This API wraps new-style process locking if available, otherwise
61
 * falls back to BSD locks.
62
 */
63
gboolean
64
0
glnx_make_lock_file(int dfd, const char *p, int operation, GLnxLockFile *out_lock, GError **error) {
65
0
        glnx_autofd int fd = -1;
66
0
        g_autofree char *t = NULL;
67
0
        int r;
68
69
        /*
70
         * We use UNPOSIX locks if they are available. They have nice
71
         * semantics, and are mostly compatible with NFS. However,
72
         * they are only available on new kernels. When we detect we
73
         * are running on an older kernel, then we fall back to good
74
         * old BSD locks. They also have nice semantics, but are
75
         * slightly problematic on NFS, where they are upgraded to
76
         * POSIX locks, even though locally they are orthogonal to
77
         * POSIX locks.
78
         */
79
80
0
        t = g_strdup(p);
81
82
0
        for (;;) {
83
0
#ifdef F_OFD_SETLK
84
0
                struct flock fl = {
85
0
                        .l_type = (operation & ~LOCK_NB) == LOCK_EX ? F_WRLCK : F_RDLCK,
86
0
                        .l_whence = SEEK_SET,
87
0
                };
88
0
#endif
89
0
                struct stat st;
90
91
0
                fd = openat(dfd, p, O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, 0600);
92
0
                if (fd < 0)
93
0
                        return glnx_throw_errno(error);
94
95
                /* Unfortunately, new locks are not in RHEL 7.1 glibc */
96
0
#ifdef F_OFD_SETLK
97
0
                r = fcntl(fd, (operation & LOCK_NB) ? F_OFD_SETLK : F_OFD_SETLKW, &fl);
98
#else
99
                r = -1;
100
                errno = EINVAL;
101
#endif
102
0
                if (r < 0) {
103
104
                        /* If the kernel is too old, use good old BSD locks */
105
0
                        if (errno == EINVAL)
106
0
                                r = flock(fd, operation);
107
108
0
                        if (r < 0)
109
0
                                return glnx_throw_errno_prefix (error, "flock");
110
0
                }
111
112
                /* If we acquired the lock, let's check if the file
113
                 * still exists in the file system. If not, then the
114
                 * previous exclusive owner removed it and then closed
115
                 * it. In such a case our acquired lock is worthless,
116
                 * hence try again. */
117
118
0
                if (!glnx_fstat (fd, &st, error))
119
0
                        return FALSE;
120
0
                if (st.st_nlink > 0)
121
0
                        break;
122
123
0
                glnx_close_fd (&fd);
124
0
        }
125
126
        /* Note that if this is not AT_FDCWD, the caller takes responsibility
127
         * for the fd's lifetime being >= that of the lock.
128
         */
129
0
        out_lock->initialized = TRUE;
130
0
        out_lock->dfd = dfd;
131
0
        out_lock->path = g_steal_pointer (&t);
132
0
        out_lock->fd = g_steal_fd (&fd);
133
0
        out_lock->operation = operation;
134
0
        return TRUE;
135
0
}
136
137
845
void glnx_release_lock_file(GLnxLockFile *f) {
138
845
        int r;
139
140
845
        if (!(f && f->initialized))
141
845
                return;
142
143
0
        if (f->path) {
144
145
                /* If we are the exclusive owner we can safely delete
146
                 * the lock file itself. If we are not the exclusive
147
                 * owner, we can try becoming it. */
148
149
0
                if (f->fd >= 0 &&
150
0
                    (f->operation & ~LOCK_NB) == LOCK_SH) {
151
0
#ifdef F_OFD_SETLK
152
0
                        static const struct flock fl = {
153
0
                                .l_type = F_WRLCK,
154
0
                                .l_whence = SEEK_SET,
155
0
                        };
156
157
0
                        r = fcntl(f->fd, F_OFD_SETLK, &fl);
158
#else
159
                        r = -1;
160
                        errno = EINVAL;
161
#endif
162
0
                        if (r < 0 && errno == EINVAL)
163
0
                                r = flock(f->fd, LOCK_EX|LOCK_NB);
164
165
0
                        if (r >= 0)
166
0
                                f->operation = LOCK_EX|LOCK_NB;
167
0
                }
168
169
0
                if ((f->operation & ~LOCK_NB) == LOCK_EX) {
170
0
                        (void) unlinkat(f->dfd, f->path, 0);
171
0
                }
172
173
0
                g_free(f->path);
174
0
                f->path = NULL;
175
0
        }
176
177
0
        glnx_close_fd (&f->fd);
178
0
        f->operation = 0;
179
0
        f->initialized = FALSE;
180
0
}