File: lock_linux.cc

package info (click to toggle)
chromium 138.0.7204.92-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,071,576 kB
  • sloc: cpp: 34,933,512; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,956; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (149 lines) | stat: -rw-r--r-- 4,576 bytes parent folder | download | duplicates (4)
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
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/named_system_lock/lock.h"

#include <aio.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>

#include <memory>
#include <string>
#include <utility>

#include "base/files/file.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/time/time.h"

namespace named_system_lock {

// A global preferences lock for Linux implemented with pthread mutexes in
// shared memory. Note that the shared memory region is leaked once per lock
// name for reasons described in the README.md. The size of the leaked region is
// ~40 bytes.
class ScopedLockImpl {
 public:
  ScopedLockImpl(const ScopedLockImpl&) = delete;
  ScopedLockImpl& operator=(const ScopedLockImpl&) = delete;
  ~ScopedLockImpl();

  static std::unique_ptr<ScopedLockImpl> TryCreate(
      const std::string& shared_mem_name,
      base::TimeDelta timeout);

 private:
  ScopedLockImpl(pthread_mutex_t* mutex, int shm_fd)
      : mutex_(mutex), shm_fd_(shm_fd) {}

  // RAW_PTR_EXCLUSION: Never allocated by PartitionAlloc (always mmap'ed), so
  // there is no benefit to using a raw_ptr, only cost.
  RAW_PTR_EXCLUSION pthread_mutex_t* mutex_;
  int shm_fd_;
};

std::unique_ptr<ScopedLockImpl> ScopedLockImpl::TryCreate(
    const std::string& shared_mem_name,
    base::TimeDelta timeout) {
  bool should_init_mutex = false;
  int shm_fd = shm_open(shared_mem_name.c_str(), O_RDWR, S_IRUSR | S_IWUSR);
  if (shm_fd < 0 && errno == ENOENT) {
    shm_fd =
        shm_open(shared_mem_name.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    should_init_mutex = true;
  }

  if (shm_fd < 0) {
    return nullptr;
  }

  base::stat_wrapper_t shm_stat;
  if (base::File::Fstat(shm_fd, &shm_stat) < 0) {
    VPLOG(1) << "Cannot stat shared memory " << shared_mem_name;
    return nullptr;
  }
  if (shm_stat.st_uid != getuid() ||
      (shm_stat.st_mode & 0777) != (S_IRUSR | S_IWUSR)) {
    VLOG(1) << "Refusing to use shared memory region " << shared_mem_name
            << " with incorrect permissions";
    return nullptr;
  }

  if (ftruncate(shm_fd, sizeof(pthread_mutex_t))) {
    return nullptr;
  }

  void* addr = mmap(nullptr, sizeof(pthread_mutex_t), PROT_READ | PROT_WRITE,
                    MAP_SHARED, shm_fd, 0);
  if (addr == MAP_FAILED) {
    return nullptr;
  }
  pthread_mutex_t* mutex = static_cast<pthread_mutex_t*>(addr);

  // Note that the mutex is configured with the "robust" attribute. This ensures
  // that even if a process crashes while holding the mutex, the mutex is
  // recoverable.
  if (should_init_mutex) {
    pthread_mutexattr_t attr = {};
    if (pthread_mutexattr_init(&attr) ||
        pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED) ||
        pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST) ||
        pthread_mutex_init(mutex, &attr)) {
      munmap(addr, sizeof(pthread_mutex_t));
      return nullptr;
    }
  }

  const base::Time start = base::Time::NowFromSystemTime();
  do {
    switch (pthread_mutex_trylock(mutex)) {
      case EOWNERDEAD:
        // A process holding the mutex died, try to recover it.
        if (pthread_mutex_consistent(mutex)) {
          return nullptr;
        }
        // The mutex is restored. It is in the locked state.
        [[fallthrough]];
      case 0:
        // The lock was acquired.
        return base::WrapUnique<ScopedLockImpl>(
            new ScopedLockImpl(mutex, shm_fd));
      case EBUSY:
        // The mutex is held by another process.
        continue;
      default:
        // An error occurred.
        return nullptr;
    }
  } while (base::Time::NowFromSystemTime() - start < timeout);

  // The lock was not acquired before the timeout.
  return nullptr;
}

ScopedLockImpl::~ScopedLockImpl() {
  if (mutex_) {
    pthread_mutex_unlock(mutex_);
    munmap(mutex_, sizeof(pthread_mutex_t));
    close(shm_fd_);
  }
}

ScopedLock::ScopedLock(std::unique_ptr<ScopedLockImpl> impl)
    : impl_(std::move(impl)) {}
ScopedLock::~ScopedLock() = default;

// static
std::unique_ptr<ScopedLock> ScopedLock::Create(const std::string& name,
                                               base::TimeDelta timeout) {
  std::unique_ptr<ScopedLockImpl> impl =
      ScopedLockImpl::TryCreate(name, timeout);
  return impl ? std::make_unique<ScopedLock>(std::move(impl)) : nullptr;
}

}  // namespace named_system_lock