Blame libglnx/glnx-lockfile.c

Packit Service 2a3f3d
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
Packit Service 2a3f3d
Packit Service 2a3f3d
/***
Packit Service 2a3f3d
  This file is part of systemd.
Packit Service 2a3f3d
  Now copied into libglnx:
Packit Service 2a3f3d
    - Use GError
Packit Service 2a3f3d
Packit Service 2a3f3d
  Copyright 2010 Lennart Poettering
Packit Service 2a3f3d
  Copyright 2015 Colin Walters <walters@verbum.org>
Packit Service 2a3f3d
Packit Service 2a3f3d
  systemd is free software; you can redistribute it and/or modify it
Packit Service 2a3f3d
  under the terms of the GNU Lesser General Public License as published by
Packit Service 2a3f3d
  the Free Software Foundation; either version 2.1 of the License, or
Packit Service 2a3f3d
  (at your option) any later version.
Packit Service 2a3f3d
Packit Service 2a3f3d
  systemd is distributed in the hope that it will be useful, but
Packit Service 2a3f3d
  WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service 2a3f3d
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Packit Service 2a3f3d
  Lesser General Public License for more details.
Packit Service 2a3f3d
Packit Service 2a3f3d
  You should have received a copy of the GNU Lesser General Public License
Packit Service 2a3f3d
  along with systemd; If not, see <http://www.gnu.org/licenses/>.
Packit Service 2a3f3d
***/
Packit Service 2a3f3d
Packit Service 2a3f3d
#include "config.h"
Packit Service 2a3f3d
Packit Service 2a3f3d
#include <stdlib.h>
Packit Service 2a3f3d
#include <stdbool.h>
Packit Service 2a3f3d
#include <errno.h>
Packit Service 2a3f3d
#include <string.h>
Packit Service 2a3f3d
#include <stdio.h>
Packit Service 2a3f3d
#include <limits.h>
Packit Service 2a3f3d
#include <unistd.h>
Packit Service 2a3f3d
#include <sys/types.h>
Packit Service 2a3f3d
#include <sys/file.h>
Packit Service 2a3f3d
#include <sys/stat.h>
Packit Service 2a3f3d
#include <fcntl.h>
Packit Service 2a3f3d
Packit Service 2a3f3d
#include "glnx-lockfile.h"
Packit Service 2a3f3d
#include "glnx-errors.h"
Packit Service 2a3f3d
#include "glnx-fdio.h"
Packit Service 2a3f3d
#include "glnx-backport-autocleanups.h"
Packit Service 2a3f3d
#include "glnx-local-alloc.h"
Packit Service 2a3f3d
Packit Service 2a3f3d
#define newa(t, n) ((t*) alloca(sizeof(t)*(n)))
Packit Service 2a3f3d
Packit Service 2a3f3d
/**
Packit Service 2a3f3d
 * glnx_make_lock_file:
Packit Service 2a3f3d
 * @dfd: Directory file descriptor (if not `AT_FDCWD`, must have lifetime `>=` @out_lock)
Packit Service 2a3f3d
 * @p: Path
Packit Service 2a3f3d
 * @operation: one of `LOCK_SH`, `LOCK_EX`, `LOCK_UN`, as passed to flock()
Packit Service 2a3f3d
 * @out_lock: (out) (caller allocates): Return location for lock
Packit Service 2a3f3d
 * @error: Error
Packit Service 2a3f3d
 *
Packit Service 2a3f3d
 * Block until a lock file named @p (relative to @dfd) can be created,
Packit Service 2a3f3d
 * using the flags in @operation, returning the lock data in the
Packit Service 2a3f3d
 * caller-allocated location @out_lock.
Packit Service 2a3f3d
 *
Packit Service 2a3f3d
 * This API wraps new-style process locking if available, otherwise
Packit Service 2a3f3d
 * falls back to BSD locks.
Packit Service 2a3f3d
 */
Packit Service 2a3f3d
gboolean
Packit Service 2a3f3d
glnx_make_lock_file(int dfd, const char *p, int operation, GLnxLockFile *out_lock, GError **error) {
Packit Service 2a3f3d
        glnx_autofd int fd = -1;
Packit Service 2a3f3d
        g_autofree char *t = NULL;
Packit Service 2a3f3d
        int r;
Packit Service 2a3f3d
Packit Service 2a3f3d
        /*
Packit Service 2a3f3d
         * We use UNPOSIX locks if they are available. They have nice
Packit Service 2a3f3d
         * semantics, and are mostly compatible with NFS. However,
Packit Service 2a3f3d
         * they are only available on new kernels. When we detect we
Packit Service 2a3f3d
         * are running on an older kernel, then we fall back to good
Packit Service 2a3f3d
         * old BSD locks. They also have nice semantics, but are
Packit Service 2a3f3d
         * slightly problematic on NFS, where they are upgraded to
Packit Service 2a3f3d
         * POSIX locks, even though locally they are orthogonal to
Packit Service 2a3f3d
         * POSIX locks.
Packit Service 2a3f3d
         */
Packit Service 2a3f3d
Packit Service 2a3f3d
        t = g_strdup(p);
Packit Service 2a3f3d
Packit Service 2a3f3d
        for (;;) {
Packit Service 2a3f3d
#ifdef F_OFD_SETLK
Packit Service 2a3f3d
                struct flock fl = {
Packit Service 2a3f3d
                        .l_type = (operation & ~LOCK_NB) == LOCK_EX ? F_WRLCK : F_RDLCK,
Packit Service 2a3f3d
                        .l_whence = SEEK_SET,
Packit Service 2a3f3d
                };
Packit Service 2a3f3d
#endif
Packit Service 2a3f3d
                struct stat st;
Packit Service 2a3f3d
Packit Service 2a3f3d
                fd = openat(dfd, p, O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, 0600);
Packit Service 2a3f3d
                if (fd < 0)
Packit Service 2a3f3d
                        return glnx_throw_errno(error);
Packit Service 2a3f3d
Packit Service 2a3f3d
                /* Unfortunately, new locks are not in RHEL 7.1 glibc */
Packit Service 2a3f3d
#ifdef F_OFD_SETLK
Packit Service 2a3f3d
                r = fcntl(fd, (operation & LOCK_NB) ? F_OFD_SETLK : F_OFD_SETLKW, &fl);
Packit Service 2a3f3d
#else
Packit Service 2a3f3d
                r = -1;
Packit Service 2a3f3d
                errno = EINVAL;
Packit Service 2a3f3d
#endif
Packit Service 2a3f3d
                if (r < 0) {
Packit Service 2a3f3d
Packit Service 2a3f3d
                        /* If the kernel is too old, use good old BSD locks */
Packit Service 2a3f3d
                        if (errno == EINVAL)
Packit Service 2a3f3d
                                r = flock(fd, operation);
Packit Service 2a3f3d
Packit Service 2a3f3d
                        if (r < 0)
Packit Service 2a3f3d
                                return glnx_throw_errno_prefix (error, "flock");
Packit Service 2a3f3d
                }
Packit Service 2a3f3d
Packit Service 2a3f3d
                /* If we acquired the lock, let's check if the file
Packit Service 2a3f3d
                 * still exists in the file system. If not, then the
Packit Service 2a3f3d
                 * previous exclusive owner removed it and then closed
Packit Service 2a3f3d
                 * it. In such a case our acquired lock is worthless,
Packit Service 2a3f3d
                 * hence try again. */
Packit Service 2a3f3d
Packit Service 2a3f3d
                if (!glnx_fstat (fd, &st, error))
Packit Service 2a3f3d
                        return FALSE;
Packit Service 2a3f3d
                if (st.st_nlink > 0)
Packit Service 2a3f3d
                        break;
Packit Service 2a3f3d
Packit Service 2a3f3d
                glnx_close_fd (&fd;;
Packit Service 2a3f3d
        }
Packit Service 2a3f3d
Packit Service 2a3f3d
        /* Note that if this is not AT_FDCWD, the caller takes responsibility
Packit Service 2a3f3d
         * for the fd's lifetime being >= that of the lock.
Packit Service 2a3f3d
         */
Packit Service 2a3f3d
        out_lock->initialized = TRUE;
Packit Service 2a3f3d
        out_lock->dfd = dfd;
Packit Service 2a3f3d
        out_lock->path = g_steal_pointer (&t);
Packit Service 2a3f3d
        out_lock->fd = glnx_steal_fd (&fd;;
Packit Service 2a3f3d
        out_lock->operation = operation;
Packit Service 2a3f3d
        return TRUE;
Packit Service 2a3f3d
}
Packit Service 2a3f3d
Packit Service 2a3f3d
void glnx_release_lock_file(GLnxLockFile *f) {
Packit Service 2a3f3d
        int r;
Packit Service 2a3f3d
Packit Service 2a3f3d
        if (!(f && f->initialized))
Packit Service 2a3f3d
                return;
Packit Service 2a3f3d
Packit Service 2a3f3d
        if (f->path) {
Packit Service 2a3f3d
Packit Service 2a3f3d
                /* If we are the exclusive owner we can safely delete
Packit Service 2a3f3d
                 * the lock file itself. If we are not the exclusive
Packit Service 2a3f3d
                 * owner, we can try becoming it. */
Packit Service 2a3f3d
Packit Service 2a3f3d
                if (f->fd >= 0 &&
Packit Service 2a3f3d
                    (f->operation & ~LOCK_NB) == LOCK_SH) {
Packit Service 2a3f3d
#ifdef F_OFD_SETLK
Packit Service 2a3f3d
                        static const struct flock fl = {
Packit Service 2a3f3d
                                .l_type = F_WRLCK,
Packit Service 2a3f3d
                                .l_whence = SEEK_SET,
Packit Service 2a3f3d
                        };
Packit Service 2a3f3d
Packit Service 2a3f3d
                        r = fcntl(f->fd, F_OFD_SETLK, &fl);
Packit Service 2a3f3d
#else
Packit Service 2a3f3d
                        r = -1;
Packit Service 2a3f3d
                        errno = EINVAL;
Packit Service 2a3f3d
#endif
Packit Service 2a3f3d
                        if (r < 0 && errno == EINVAL)
Packit Service 2a3f3d
                                r = flock(f->fd, LOCK_EX|LOCK_NB);
Packit Service 2a3f3d
Packit Service 2a3f3d
                        if (r >= 0)
Packit Service 2a3f3d
                                f->operation = LOCK_EX|LOCK_NB;
Packit Service 2a3f3d
                }
Packit Service 2a3f3d
Packit Service 2a3f3d
                if ((f->operation & ~LOCK_NB) == LOCK_EX) {
Packit Service 2a3f3d
                        (void) unlinkat(f->dfd, f->path, 0);
Packit Service 2a3f3d
                }
Packit Service 2a3f3d
Packit Service 2a3f3d
                g_free(f->path);
Packit Service 2a3f3d
                f->path = NULL;
Packit Service 2a3f3d
        }
Packit Service 2a3f3d
Packit Service 2a3f3d
        glnx_close_fd (&f->fd);
Packit Service 2a3f3d
        f->operation = 0;
Packit Service 2a3f3d
        f->initialized = FALSE;
Packit Service 2a3f3d
}