forked from 4store/4store
-
Notifications
You must be signed in to change notification settings - Fork 0
/
lockable.c
212 lines (190 loc) · 7.01 KB
/
lockable.c
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
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <errno.h>
#include "lockable.h"
#include "common/error.h"
#ifdef __linux__
#define st_mtimespec st_mtim
#endif
/* flush any cached data to disc after writing out the required metadata */
static int fs_lockable_sync(fs_lockable_t *hf)
{
/* make sure we have a write lock */
assert(hf->locktype & LOCK_EX);
/* write out any necessary metadata */
if (hf->write_metadata && (hf->write_metadata)(hf))
return -1;
/* flush data to disc */
if (hf->mmap_addr > 0) {
if (msync(hf->mmap_addr, hf->mmap_size, MS_ASYNC)) {
fs_error(LOG_ERR, "msync(%s): %s", hf->filename, strerror(errno));
return -1;
}
} else if (fs_fsync(hf->fd)) {
fs_error(LOG_ERR, "fsync(%s): %s", hf->filename, strerror(errno));
return -1;
}
return 0;
}
int fs_lockable_lock_debug(fs_lockable_t *hf, int operation, const char *file, int line)
{
/* this should not happen */
fs_assert(hf);
//fs_error(LOG_INFO, "%s:%d fs_lockable_lock(%s): %01x", file, line, hf->filename, operation);
/* It is an error to try to upgrade / downgrade locks */
if ( (operation & LOCK_EX && hf->locktype & LOCK_SH) ||
(operation & LOCK_SH && hf->locktype & LOCK_EX) ) {
fs_error(LOG_ERR, "%s:%d fs_lockable_lock(%s): up/downgrading lock not permitted",
file, line, hf->filename);
return -1;
}
/* It is an error to request a lock while holding one already */
if ( (operation & hf->locktype) & (LOCK_SH|LOCK_EX) ) {
fs_error(LOG_ERR, "%s:%d fs_lockable_lock(%s): double lock",
file, line, hf->filename);
return -1;
}
return (hf->lock)(hf, operation);
}
static int fs_lockable_do_lock(fs_lockable_t *hf, int operation)
{
struct stat stat;
/* if we are unlocking while holding a write lock, flush data */
if ( (hf->locktype & LOCK_EX) && (operation & LOCK_UN) ) {
if (fs_lockable_sync(hf))
return -1;
/* update the mtime before releasing the lock */
if (fstat(hf->fd, &stat) < 0) {
fs_error(LOG_ERR, "fstat(%s): %s", hf->filename, strerror(errno));
return -1;
}
memcpy(&hf->mtime, &stat.st_mtimespec, sizeof(struct timespec));
}
/* release or acquire the lock */
if (flock(hf->fd, operation)) {
fs_error(LOG_ERR, "flock(%s): %s", hf->filename, strerror(errno));
return -1;
}
hf->locktype = operation;
/* if we are acquiring the lock, read any metadata if necessary */
if ( hf->read_metadata && (operation & (LOCK_EX|LOCK_SH)) ) {
if (fstat(hf->fd, &stat) < 0) {
fs_error(LOG_ERR, "fstat(%s): %s", hf->filename, strerror(errno));
return -1;
}
/* for mmaped files, let them figure out if they should remap */
if (hf->mmap_addr > 0) {
if ( (hf->read_metadata)(hf) )
return -1;
}
/* block files we need to check the mtime to see if we should update */
else if ( (stat.st_mtimespec.tv_sec > hf->mtime.tv_sec) ||
( (stat.st_mtimespec.tv_sec == hf->mtime.tv_sec) &&
(stat.st_mtimespec.tv_nsec > hf->mtime.tv_nsec) ) ) {
if ( (hf->read_metadata)(hf) )
return -1;
}
}
return 0;
}
/*
* Initialize the hashfile, reading or writing any header
* or metadata as appropriate. Handles locking. Returns
* 0 on success, -1 on error. T
*/
int fs_lockable_init(fs_lockable_t *hf)
{
struct stat stat;
int file_length;
/* read or create the file header metadata */
if ( (hf->flags & O_TRUNC) ) {
/* we have truncated the file, so write a header */
if (flock(hf->fd, LOCK_EX)) {
fs_error(LOG_ERR, "flock(%s): %s", hf->filename, strerror(errno));
return -1;
}
if ( hf->write_metadata && (hf->write_metadata)(hf) ) {
if (flock(hf->fd, LOCK_UN))
fs_error(LOG_ERR, "flock(%s): %s", hf->filename, strerror(errno));
return -1;
}
/* flush data to disc */
fs_fsync(hf->fd);
/* downgrade the lock */
if (flock(hf->fd, LOCK_SH)) {
fs_error(LOG_ERR, "flock(%s): %s", hf->filename, strerror(errno));
if (flock(hf->fd, LOCK_UN))
fs_error(LOG_ERR, "flock(%s): %s", hf->filename, strerror(errno));
return -1;
}
} else {
/* check if the file is empty, we have created it,
* don't take exclusive lock yet so we don't
* unnecessarily block */
if (flock(hf->fd, LOCK_SH)) {
fs_error(LOG_ERR, "flock(%s): %s", hf->filename, strerror(errno));
return -1;
}
file_length = lseek(hf->fd, 0, SEEK_END);
if (file_length < 0) {
fs_error(LOG_ERR, "lseek(%s, SEEK_END): %s", hf->filename, strerror(errno));
return -1;
}
if (file_length == 0) {
/* empty file, check again with an upgraded lock */
flock(hf->fd, LOCK_EX);
file_length = lseek(hf->fd, 0, SEEK_END);
if (file_length < 0) {
fs_error(LOG_ERR, "lseek(%s, SEEK_END): %s", hf->filename, strerror(errno));
return -1;
}
if (file_length == 0) {
if ( hf->write_metadata && (hf->write_metadata)(hf) ) {
flock(hf->fd, LOCK_UN);
return -1;
}
}
/* flush data to disc */
if (fs_fsync(hf->fd)) {
fs_error(LOG_ERR, "fsync(%s): %s", hf->filename, strerror(errno));
return -1;
}
/* downgrade the lock */
if (flock(hf->fd, LOCK_SH)) {
fs_error(LOG_ERR, "flock(%s): %s", hf->filename, strerror(errno));
if (flock(hf->fd, LOCK_UN))
fs_error(LOG_ERR, "flock(%s): %s", hf->filename, strerror(errno));
return -1;
}
}
}
/* we are now holding a read lock, read in the header */
if ( hf->read_metadata && (hf->read_metadata)(hf) ) {
if (flock(hf->fd, LOCK_UN))
fs_error(LOG_ERR, "flock(%s): %s", hf->filename, strerror(errno));
return -1;
}
/* update the mtime */
if (fstat(hf->fd, &stat)) {
fs_error(LOG_ERR, "fstat(%s): %s", hf->filename, strerror(errno));
if (flock(hf->fd, LOCK_UN))
fs_error(LOG_ERR, "flock(%s): %s", hf->filename, strerror(errno));
return -1;
}
memcpy(&hf->mtime, &stat.st_mtimespec, sizeof(struct timespec));
/* done, we have consistent state and can release the lock */
if (flock(hf->fd, LOCK_UN)) {
fs_error(LOG_ERR, "flock(%s): %s", hf->filename, strerror(errno));
return -1;
}
/* set our flag to keep track of the lock type */
hf->locktype = LOCK_UN;
/* and the pointer to the lock function */
hf->lock = fs_lockable_do_lock;
return 0;
}