-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathFilesystemKVS.cpp
216 lines (198 loc) · 7.02 KB
/
FilesystemKVS.cpp
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
// System
#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <unistd.h>
// C++
#include <stdexcept>
// Local
#include "Log.h"
#include "utils.h"
// Self
#include "FilesystemKVS.h"
// \brief Instantiate FilesystemKVS
// \param root Directory to use as root of store. Should already exist
FilesystemKVS::FilesystemKVS(const char *root) : m_root(root) {
if (m_root == "" || m_root[m_root.size()-1] == '/')
throw std::runtime_error("store path " + std::string(root) + " shouldn't end with '/'");
if (!filename_exists(root))
throw std::runtime_error(std::string(root) + " does not exist");
if (m_verbose) log_f("FilesystemKVS: opening %s", root);
}
/// \brief Check if key exists
/// \param key
/// \return Returns true if found, false if not
bool FilesystemKVS::has_key(const std::string &key) const {
struct stat statbuf;
int ret= stat(value_key_to_path(key).c_str(), &statbuf);
return (ret == 0);
}
/// \brief Set key to value
/// \param key
/// \param value
void FilesystemKVS::set(const std::string &key, const std::string &value) {
std::string path = value_key_to_path(key);
FILE *out = fopen(path.c_str(), "wb");
if (!out) {
make_parent_directories(path);
out = fopen(path.c_str(), "wb");
if (!out) throw std::runtime_error("fopen " + path);
}
if (value.size()) {
if (1 != fwrite(&value[0], value.size(), 1, out)) {
fclose(out);
throw std::runtime_error("fwrite " + path);
}
}
if (m_verbose) log_f("FilesystemKVS::set(%s) wrote %zd bytes to %s", key.c_str(), value.length(), path.c_str());
fclose(out);
}
/// \brief Get value
/// \param key
/// \param value if found, returns value in this parameter
/// \return Returns true if found, false if not
///
/// See FilesystemKVS class description for the mapping between datastore and filesystem.
bool FilesystemKVS::get(const std::string &key, std::string &value) const {
std::string path = value_key_to_path(key);
FILE *in = fopen(path.c_str(), "rb");
if (!in) {
if (m_verbose) log_f("FilesystemKVS::get(%s) found no file at %s, returning false", key.c_str(), path.c_str());
return false;
}
struct stat statbuf;
if (0 != fstat(fileno(in), &statbuf)) {
fclose(in);
throw std::runtime_error("fstat " + path);
}
value.resize(statbuf.st_size);
if (statbuf.st_size) {
if (1 != fread(&value[0], statbuf.st_size, 1, in)) {
fclose(in);
throw std::runtime_error("fread " + path);
}
}
fclose(in);
if (m_verbose) log_f("FilesystemKVS::get(%s) read %zd bytes from %s", key.c_str(), value.length(), path.c_str());
return true;
}
/// Delete key if present
/// \param key
/// \return Returns true if deleted, false if not present
bool FilesystemKVS::del(const std::string &key) {
std::string path = value_key_to_path(key);
return unlink(path.c_str()) == 0;
}
/// Get subkeys
/// \param key
/// \param nlevels: 1=only return immediate children; 2=children and grandchildren; (unsigned int) -1: all children
/// \return All subkeys, recursively
void FilesystemKVS::get_subkeys(const std::string &key, std::vector<std::string> &keys,
unsigned int nlevels, bool (*subdir_filter)(const char *subdirname)) const {
std::string path = directory_key_to_path(key);
std::string prefix = (key == "") ? "" : key+".";
DIR *dir = opendir(path.c_str());
if (!dir) return;
while (1) {
struct dirent *ent = readdir(dir);
if (!ent) break;
if (!strcmp(ent->d_name, ".")) continue;
if (!strcmp(ent->d_name, "..")) continue;
if (filename_suffix(ent->d_name) == "val") {
keys.push_back(prefix+filename_sans_suffix(ent->d_name));
} else if (nlevels > 1 && (!subdir_filter || (*subdir_filter)(ent->d_name))) {
// If it's a directory, recurse, honoring symlinks
bool is_directory = false;
std::string fullpath = path+"/"+ent->d_name;
switch (ent->d_type) {
case DT_DIR:
is_directory = true;
break;
case DT_UNKNOWN:
case DT_LNK: {
struct stat statbuf;
int ret = stat(fullpath.c_str(), &statbuf);
if (ret != 0) {
perror(("stat " + fullpath).c_str());
} else {
is_directory = S_ISDIR(statbuf.st_mode);
}
}
}
if (is_directory) get_subkeys(prefix+ent->d_name, keys, nlevels-1);
}
}
closedir(dir);
}
/// Lock key. Do not call this directly; instead, use KVSLocker to create a scoped lock.
/// \param key
///
/// Uses flock on file that contains value.
void *FilesystemKVS::lock(const std::string &key) {
std::string path = value_key_to_path(key);
FILE *f = fopen(path.c_str(), "ab");
if (!f) {
make_parent_directories(path);
f = fopen(path.c_str(), "a");
if (!f) throw std::runtime_error("fopen " + path);
}
int fd= fileno(f);
if (m_verbose) log_f("FilesystemKVS::lock(%s) about to lock %s (fd=%d)", key.c_str(), path.c_str(), fd);
if (-1 == flock(fd, LOCK_EX)) {
fclose(f);
throw std::runtime_error("flock " + path);
}
if (m_verbose) log_f("FilesystemKVS::lock(%s) locked %s (fd=%d)", key.c_str(), path.c_str(), fd);
return (void*)f;
}
/// Unlock key after it has been locked with lock. Do not call this directly; instead, use KVSLocker to create a scoped lock
/// \param key
void FilesystemKVS::unlock(void *lock) {
FILE *f = (FILE*)lock;
int fd = fileno(f);
int ret = flock(fd, LOCK_UN);
fclose(f);
if (ret == -1) throw std::runtime_error("funlock");
if (m_verbose) log_f("FilesystemKVS::unlock unlocked fd %d", fd);
}
/// Return path to file that stores the value associated with key
/// \param key Key
/// \return Returns path to file that stores value associated with key
std::string FilesystemKVS::value_key_to_path(const std::string &key) const {
// Validate key
if (key == "") throw std::runtime_error("Invalid key '" + key + "'");
return directory_key_to_path(key) + ".val";
}
/// Return path to directory that stores subkey children of key
/// \param key Key
/// \return Returns path to directory that stores subkey children of key
std::string FilesystemKVS::directory_key_to_path(const std::string &key) const {
if (key == "") return m_root;
if (key[0] == '.' || key[key.size()-1] == '.' || key.find("..") != std::string::npos) {
throw std::runtime_error("Invalid key '" + key + "'");
}
std::string path = key;
for (size_t i = 0; i < key.size(); i++) {
if (key[i] == '.') {
path[i] = '/';
} else if (!isalnum(key[i]) && key[i] != '_' && key[i] != '-') {
throw std::runtime_error("Invalid key '" + key + "'");
}
}
return m_root + "/" + path;
}
void FilesystemKVS::make_parent_directories(const std::string &path) {
size_t lastDirDelim= path.rfind('/');
if (lastDirDelim == std::string::npos) throw std::runtime_error("make_parent_directories " + path);
std::string subpath = path.substr(0, lastDirDelim);
if (0 == mkdir(subpath.c_str(), 0777) || errno == EEXIST) return;
if (errno == ENOENT) {
make_parent_directories(subpath);
if (0 == mkdir(subpath.c_str(), 0777) || errno == EEXIST) return;
}
throw std::runtime_error("make_parent_directories " + path);
}