summaryrefslogtreecommitdiff
path: root/chromium/sql/vfs_wrapper_fuchsia.cc
blob: bd4d03b825ce682223877c94c6d85d0141e46ebb (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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "sql/vfs_wrapper_fuchsia.h"

#include "base/containers/fixed_flat_set.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/synchronization/lock.h"
#include "base/thread_annotations.h"
#include "sql/vfs_wrapper.h"

namespace sql {

namespace {

struct FileLock {
  int lock_level;
  // Used to track the pointers to different VfsFile instances that hold shared
  // locks on the same underlying file. The pointer is only used as a unique id
  // for the VfsFile instance. The contents are never accessed.
  base::flat_set<VfsFile*> readers = {};
  // Used to track a VfsFile instance that holds a reserved/pending/exclusive
  // lock for writing. The pointer is only used as a unique id for the VfsFile
  // instance. The contents are never accessed.
  VfsFile* writer = nullptr;
};

// Singleton that stores and mutates state as described in
// https://www.sqlite.org/lockingv3.html
class FuchsiaFileLockManager {
 public:
  FuchsiaFileLockManager() = default;

  // Returns lock manager for the current process.
  static FuchsiaFileLockManager* Instance() {
    static base::NoDestructor<FuchsiaFileLockManager> lock_manager;
    return lock_manager.get();
  }

  int Lock(VfsFile* vfs_file, int requested_lock) {
    DCHECK_GT(requested_lock, SQLITE_LOCK_NONE)
        << "SQLITE_LOCK_NONE can only be set via Unlock";
    base::AutoLock lock(lock_);
    const auto file_lock_state = GetFileLockStateLocked(vfs_file);

    // Allow any lock level since the lock isn't held.
    if (file_lock_state.readers.empty() && file_lock_state.writer == nullptr) {
      if (requested_lock == SQLITE_LOCK_SHARED) {
        locked_files_[vfs_file->file_name] = {.lock_level = requested_lock,
                                              .readers = {vfs_file}};
      } else {
        locked_files_[vfs_file->file_name] = {.lock_level = requested_lock,
                                              .writer = vfs_file};
      }

      return SQLITE_OK;
    }

    if (requested_lock == SQLITE_LOCK_SHARED) {
      if (file_lock_state.lock_level >= SQLITE_LOCK_PENDING) {
        DVLOG(1) << "lock for file " << vfs_file->file_name
                 << " is held by a writer and cannot be shared.";
        return SQLITE_BUSY;
      }

      locked_files_[vfs_file->file_name].readers.insert(vfs_file);
      return SQLITE_OK;
    }

    if (file_lock_state.writer != nullptr &&
        file_lock_state.writer != vfs_file) {
      DVLOG(1) << "lock for file " << vfs_file->file_name
               << " is already held by another writer.";
      return SQLITE_BUSY;
    }

    if (requested_lock == SQLITE_LOCK_EXCLUSIVE &&
        (file_lock_state.readers.size() > 1 ||
         (file_lock_state.readers.size() == 1 &&
          !file_lock_state.readers.contains(vfs_file)))) {
      DVLOG(1) << "lock for file " << vfs_file->file_name
               << " is held by readers and can't yet be upgraded to exclusive.";
      return SQLITE_BUSY;
    }

    DCHECK(file_lock_state.writer == nullptr ||
           file_lock_state.writer == vfs_file);
    locked_files_[vfs_file->file_name].lock_level = requested_lock;
    locked_files_[vfs_file->file_name].writer = vfs_file;
    locked_files_[vfs_file->file_name].readers.erase(vfs_file);
    DCHECK(locked_files_[vfs_file->file_name].lock_level <
               SQLITE_LOCK_EXCLUSIVE ||
           locked_files_[vfs_file->file_name].readers.empty());
    return SQLITE_OK;
  }

  int Unlock(VfsFile* vfs_file, int requested_lock) {
    base::AutoLock lock(lock_);
    const auto file_lock_state = GetFileLockStateLocked(vfs_file);

    DCHECK_LE(requested_lock, file_lock_state.lock_level)
        << "Attempted to unlock to a higher lock level, unlock can only "
           "decrement.";

    // Shortcut if the caller doesn't currently hold a lock.
    if (!file_lock_state.readers.contains(vfs_file) &&
        file_lock_state.writer != vfs_file) {
      DVLOG(1) << "caller can't unlock because it doesn't currently "
               << "hold a lock for file " << vfs_file->file_name;
      return SQLITE_OK;
    }

    if (requested_lock == SQLITE_LOCK_NONE) {
      locked_files_[vfs_file->file_name].readers.erase(vfs_file);
    } else if (requested_lock == SQLITE_LOCK_SHARED) {
      locked_files_[vfs_file->file_name].readers.insert(vfs_file);
    }

    if (requested_lock < SQLITE_LOCK_RESERVED &&
        file_lock_state.writer == vfs_file) {
      locked_files_[vfs_file->file_name].writer = nullptr;
    }

    // Check that `vfs_file` is correctly tracked given the `requested_lock`.
    DCHECK(requested_lock == SQLITE_LOCK_SHARED ||
           !locked_files_[vfs_file->file_name].readers.contains(vfs_file));
    DCHECK_EQ(requested_lock > SQLITE_LOCK_SHARED,
              locked_files_[vfs_file->file_name].writer == vfs_file);

    // Mark lock level as shared if there are only shared usages.
    if (!file_lock_state.readers.empty() && file_lock_state.writer == nullptr) {
      locked_files_[vfs_file->file_name].lock_level = SQLITE_LOCK_SHARED;
      return SQLITE_OK;
    }

    // Remove lock if there are no usages left.
    if (file_lock_state.readers.empty() && file_lock_state.writer == nullptr) {
      DCHECK_EQ(requested_lock, SQLITE_LOCK_NONE);
      locked_files_.erase(vfs_file->file_name);
      return SQLITE_OK;
    }

    if (file_lock_state.writer != vfs_file) {
      DCHECK_GE(file_lock_state.lock_level, SQLITE_LOCK_RESERVED);
      DCHECK_LE(requested_lock, SQLITE_LOCK_SHARED);
      return SQLITE_OK;
    }

    locked_files_[vfs_file->file_name].lock_level = requested_lock;
    return SQLITE_OK;
  }

  int CheckReservedLock(VfsFile* vfs_file, int* result) {
    base::AutoLock lock(lock_);
    const auto file_lock_state = GetFileLockStateLocked(vfs_file);

    switch (file_lock_state.lock_level) {
      case SQLITE_LOCK_NONE:
      case SQLITE_LOCK_SHARED:
        *result = 0;
        return SQLITE_OK;
      case SQLITE_LOCK_RESERVED:
      case SQLITE_LOCK_PENDING:
      case SQLITE_LOCK_EXCLUSIVE:
        *result = 1;
        return SQLITE_OK;
      default:
        return SQLITE_IOERR_CHECKRESERVEDLOCK;
    }
  }

 private:
  ~FuchsiaFileLockManager() = delete;

  const FileLock& GetFileLockStateLocked(VfsFile* vfs_file)
      EXCLUSIVE_LOCKS_REQUIRED(lock_) {
    static const FileLock kUnlockedFileLock = {.lock_level = SQLITE_LOCK_NONE};
    const auto file_lock_state_iter = locked_files_.find(vfs_file->file_name);
    if (file_lock_state_iter == locked_files_.end()) {
      return kUnlockedFileLock;
    }

    return file_lock_state_iter->second;
  }

  base::Lock lock_;

  // Set of all currently locked files.
  base::flat_map<std::string, FileLock> locked_files_ GUARDED_BY(lock_);
};

}  // namespace

int Lock(sqlite3_file* sqlite_file, int file_lock) {
  DCHECK(file_lock == SQLITE_LOCK_SHARED || file_lock == SQLITE_LOCK_RESERVED ||
         file_lock == SQLITE_LOCK_PENDING ||
         file_lock == SQLITE_LOCK_EXCLUSIVE);

  auto* vfs_file = reinterpret_cast<VfsFile*>(sqlite_file);
  return FuchsiaFileLockManager::Instance()->Lock(vfs_file, file_lock);
}

int Unlock(sqlite3_file* sqlite_file, int file_lock) {
  auto* vfs_file = reinterpret_cast<VfsFile*>(sqlite_file);
  return FuchsiaFileLockManager::Instance()->Unlock(vfs_file, file_lock);
}

int CheckReservedLock(sqlite3_file* sqlite_file, int* result) {
  auto* vfs_file = reinterpret_cast<VfsFile*>(sqlite_file);
  return FuchsiaFileLockManager::Instance()->CheckReservedLock(vfs_file,
                                                               result);
}

}  // namespace sql