]>
Commit | Line | Data |
---|---|---|
a7bf837f | 1 | /* Copyright (c) 2009, 2010, 2011, 2012, 2013 Nicira, Inc. |
f85f8ebb BP |
2 | * |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at: | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | #include <config.h> | |
17 | ||
41709ccc | 18 | #include "log.h" |
f85f8ebb | 19 | |
f85f8ebb BP |
20 | #include <errno.h> |
21 | #include <fcntl.h> | |
22 | #include <stdlib.h> | |
23 | #include <string.h> | |
3762274e | 24 | #include <sys/stat.h> |
f85f8ebb BP |
25 | #include <unistd.h> |
26 | ||
27 | #include "json.h" | |
28 | #include "lockfile.h" | |
41709ccc | 29 | #include "ovsdb.h" |
f85f8ebb BP |
30 | #include "ovsdb-error.h" |
31 | #include "sha1.h" | |
8e71cf88 | 32 | #include "socket-util.h" |
41709ccc | 33 | #include "transaction.h" |
f85f8ebb | 34 | #include "util.h" |
5136ce49 | 35 | |
41709ccc BP |
36 | enum ovsdb_log_mode { |
37 | OVSDB_LOG_READ, | |
38 | OVSDB_LOG_WRITE | |
f85f8ebb BP |
39 | }; |
40 | ||
41709ccc | 41 | struct ovsdb_log { |
43675e26 | 42 | off_t prev_offset; |
f85f8ebb BP |
43 | off_t offset; |
44 | char *name; | |
45 | struct lockfile *lockfile; | |
46 | FILE *stream; | |
47 | struct ovsdb_error *read_error; | |
a7bf837f | 48 | bool write_error; |
41709ccc | 49 | enum ovsdb_log_mode mode; |
f85f8ebb BP |
50 | }; |
51 | ||
7446f148 BP |
52 | /* Attempts to open 'name' with the specified 'open_mode'. On success, stores |
53 | * the new log into '*filep' and returns NULL; otherwise returns NULL and | |
54 | * stores NULL into '*filep'. | |
55 | * | |
56 | * Whether the file will be locked using lockfile_lock() depends on 'locking': | |
57 | * use true to lock it, false not to lock it, or -1 to lock it only if | |
58 | * 'open_mode' is a mode that allows writing. | |
59 | */ | |
f85f8ebb | 60 | struct ovsdb_error * |
7446f148 BP |
61 | ovsdb_log_open(const char *name, enum ovsdb_log_open_mode open_mode, |
62 | int locking, struct ovsdb_log **filep) | |
f85f8ebb BP |
63 | { |
64 | struct lockfile *lockfile; | |
65 | struct ovsdb_error *error; | |
41709ccc | 66 | struct ovsdb_log *file; |
f85f8ebb BP |
67 | struct stat s; |
68 | FILE *stream; | |
7446f148 | 69 | int flags; |
f85f8ebb BP |
70 | int fd; |
71 | ||
72 | *filep = NULL; | |
73 | ||
cb22974d | 74 | ovs_assert(locking == -1 || locking == false || locking == true); |
7446f148 BP |
75 | if (locking < 0) { |
76 | locking = open_mode != OVSDB_LOG_READ_ONLY; | |
77 | } | |
78 | if (locking) { | |
4770e795 | 79 | int retval = lockfile_lock(name, &lockfile); |
f85f8ebb BP |
80 | if (retval) { |
81 | error = ovsdb_io_error(retval, "%s: failed to lock lockfile", | |
82 | name); | |
83 | goto error; | |
84 | } | |
85 | } else { | |
86 | lockfile = NULL; | |
87 | } | |
88 | ||
7446f148 BP |
89 | if (open_mode == OVSDB_LOG_READ_ONLY) { |
90 | flags = O_RDONLY; | |
91 | } else if (open_mode == OVSDB_LOG_READ_WRITE) { | |
92 | flags = O_RDWR; | |
93 | } else if (open_mode == OVSDB_LOG_CREATE) { | |
7470e8e6 | 94 | #ifndef _WIN32 |
01ca539f BP |
95 | if (stat(name, &s) == -1 && errno == ENOENT |
96 | && lstat(name, &s) == 0 && S_ISLNK(s.st_mode)) { | |
97 | /* 'name' is a dangling symlink. We want to create the file that | |
98 | * the symlink points to, but POSIX says that open() with O_EXCL | |
99 | * must fail with EEXIST if the named file is a symlink. So, we | |
100 | * have to leave off O_EXCL and accept the race. */ | |
101 | flags = O_RDWR | O_CREAT; | |
102 | } else { | |
103 | flags = O_RDWR | O_CREAT | O_EXCL; | |
104 | } | |
7470e8e6 GS |
105 | #else |
106 | flags = O_RDWR | O_CREAT | O_EXCL; | |
107 | #endif | |
7446f148 | 108 | } else { |
428b2edd | 109 | OVS_NOT_REACHED(); |
7446f148 | 110 | } |
3e94784c GS |
111 | #ifdef _WIN32 |
112 | flags = flags | O_BINARY; | |
113 | #endif | |
f85f8ebb BP |
114 | fd = open(name, flags, 0666); |
115 | if (fd < 0) { | |
7446f148 | 116 | const char *op = open_mode == OVSDB_LOG_CREATE ? "create" : "open"; |
f85f8ebb BP |
117 | error = ovsdb_io_error(errno, "%s: %s failed", op, name); |
118 | goto error_unlock; | |
119 | } | |
120 | ||
121 | if (!fstat(fd, &s) && s.st_size == 0) { | |
122 | /* It's (probably) a new file so fsync() its parent directory to ensure | |
123 | * that its directory entry is committed to disk. */ | |
8e71cf88 | 124 | fsync_parent_dir(name); |
f85f8ebb BP |
125 | } |
126 | ||
7446f148 | 127 | stream = fdopen(fd, open_mode == OVSDB_LOG_READ_ONLY ? "rb" : "w+b"); |
f85f8ebb BP |
128 | if (!stream) { |
129 | error = ovsdb_io_error(errno, "%s: fdopen failed", name); | |
130 | goto error_close; | |
131 | } | |
132 | ||
133 | file = xmalloc(sizeof *file); | |
134 | file->name = xstrdup(name); | |
135 | file->lockfile = lockfile; | |
136 | file->stream = stream; | |
43675e26 | 137 | file->prev_offset = 0; |
f85f8ebb BP |
138 | file->offset = 0; |
139 | file->read_error = NULL; | |
a7bf837f | 140 | file->write_error = false; |
41709ccc | 141 | file->mode = OVSDB_LOG_READ; |
f85f8ebb BP |
142 | *filep = file; |
143 | return NULL; | |
144 | ||
145 | error_close: | |
146 | close(fd); | |
147 | error_unlock: | |
148 | lockfile_unlock(lockfile); | |
149 | error: | |
150 | return error; | |
151 | } | |
152 | ||
153 | void | |
41709ccc | 154 | ovsdb_log_close(struct ovsdb_log *file) |
f85f8ebb BP |
155 | { |
156 | if (file) { | |
157 | free(file->name); | |
158 | fclose(file->stream); | |
159 | lockfile_unlock(file->lockfile); | |
160 | ovsdb_error_destroy(file->read_error); | |
f85f8ebb BP |
161 | free(file); |
162 | } | |
163 | } | |
164 | ||
165 | static const char magic[] = "OVSDB JSON "; | |
166 | ||
167 | static bool | |
168 | parse_header(char *header, unsigned long int *length, | |
169 | uint8_t sha1[SHA1_DIGEST_SIZE]) | |
170 | { | |
171 | char *p; | |
172 | ||
173 | /* 'header' must consist of a magic string... */ | |
174 | if (strncmp(header, magic, strlen(magic))) { | |
175 | return false; | |
176 | } | |
177 | ||
178 | /* ...followed by a length in bytes... */ | |
179 | *length = strtoul(header + strlen(magic), &p, 10); | |
180 | if (!*length || *length == ULONG_MAX || *p != ' ') { | |
181 | return false; | |
182 | } | |
183 | p++; | |
184 | ||
185 | /* ...followed by a SHA-1 hash... */ | |
186 | if (!sha1_from_hex(sha1, p)) { | |
187 | return false; | |
188 | } | |
189 | p += SHA1_HEX_DIGEST_LEN; | |
190 | ||
191 | /* ...and ended by a new-line. */ | |
192 | if (*p != '\n') { | |
193 | return false; | |
194 | } | |
195 | ||
196 | return true; | |
197 | } | |
198 | ||
41709ccc | 199 | struct ovsdb_log_read_cbdata { |
f85f8ebb | 200 | char input[4096]; |
41709ccc | 201 | struct ovsdb_log *file; |
f85f8ebb BP |
202 | int error; |
203 | unsigned long length; | |
204 | }; | |
205 | ||
206 | static struct ovsdb_error * | |
41709ccc | 207 | parse_body(struct ovsdb_log *file, off_t offset, unsigned long int length, |
f85f8ebb BP |
208 | uint8_t sha1[SHA1_DIGEST_SIZE], struct json **jsonp) |
209 | { | |
f85f8ebb BP |
210 | struct json_parser *parser; |
211 | struct sha1_ctx ctx; | |
212 | ||
213 | sha1_init(&ctx); | |
214 | parser = json_parser_create(JSPF_TRAILER); | |
215 | ||
f85f8ebb BP |
216 | while (length > 0) { |
217 | char input[BUFSIZ]; | |
218 | int chunk; | |
219 | ||
220 | chunk = MIN(length, sizeof input); | |
221 | if (fread(input, 1, chunk, file->stream) != chunk) { | |
222 | json_parser_abort(parser); | |
223 | return ovsdb_io_error(ferror(file->stream) ? errno : EOF, | |
224 | "%s: error reading %lu bytes " | |
225 | "starting at offset %lld", file->name, | |
226 | length, (long long int) offset); | |
227 | } | |
228 | sha1_update(&ctx, input, chunk); | |
229 | json_parser_feed(parser, input, chunk); | |
230 | length -= chunk; | |
231 | } | |
232 | ||
233 | sha1_final(&ctx, sha1); | |
234 | *jsonp = json_parser_finish(parser); | |
235 | return NULL; | |
236 | } | |
237 | ||
238 | struct ovsdb_error * | |
41709ccc | 239 | ovsdb_log_read(struct ovsdb_log *file, struct json **jsonp) |
f85f8ebb BP |
240 | { |
241 | uint8_t expected_sha1[SHA1_DIGEST_SIZE]; | |
242 | uint8_t actual_sha1[SHA1_DIGEST_SIZE]; | |
243 | struct ovsdb_error *error; | |
244 | off_t data_offset; | |
245 | unsigned long data_length; | |
246 | struct json *json; | |
247 | char header[128]; | |
248 | ||
249 | *jsonp = json = NULL; | |
250 | ||
251 | if (file->read_error) { | |
252 | return ovsdb_error_clone(file->read_error); | |
41709ccc | 253 | } else if (file->mode == OVSDB_LOG_WRITE) { |
f85f8ebb BP |
254 | return OVSDB_BUG("reading file in write mode"); |
255 | } | |
256 | ||
257 | if (!fgets(header, sizeof header, file->stream)) { | |
258 | if (feof(file->stream)) { | |
259 | error = NULL; | |
260 | } else { | |
261 | error = ovsdb_io_error(errno, "%s: read failed", file->name); | |
262 | } | |
263 | goto error; | |
264 | } | |
265 | ||
266 | if (!parse_header(header, &data_length, expected_sha1)) { | |
267 | error = ovsdb_syntax_error(NULL, NULL, "%s: parse error at offset " | |
268 | "%lld in header line \"%.*s\"", | |
269 | file->name, (long long int) file->offset, | |
270 | (int) strcspn(header, "\n"), header); | |
271 | goto error; | |
272 | } | |
273 | ||
274 | data_offset = file->offset + strlen(header); | |
275 | error = parse_body(file, data_offset, data_length, actual_sha1, &json); | |
276 | if (error) { | |
277 | goto error; | |
278 | } | |
279 | ||
280 | if (memcmp(expected_sha1, actual_sha1, SHA1_DIGEST_SIZE)) { | |
281 | error = ovsdb_syntax_error(NULL, NULL, "%s: %lu bytes starting at " | |
282 | "offset %lld have SHA-1 hash "SHA1_FMT" " | |
283 | "but should have hash "SHA1_FMT, | |
284 | file->name, data_length, | |
285 | (long long int) data_offset, | |
286 | SHA1_ARGS(actual_sha1), | |
287 | SHA1_ARGS(expected_sha1)); | |
288 | goto error; | |
289 | } | |
290 | ||
291 | if (json->type == JSON_STRING) { | |
292 | error = ovsdb_syntax_error(NULL, NULL, "%s: %lu bytes starting at " | |
293 | "offset %lld are not valid JSON (%s)", | |
294 | file->name, data_length, | |
295 | (long long int) data_offset, | |
296 | json->u.string); | |
297 | goto error; | |
298 | } | |
299 | ||
43675e26 | 300 | file->prev_offset = file->offset; |
f85f8ebb BP |
301 | file->offset = data_offset + data_length; |
302 | *jsonp = json; | |
e3c17733 | 303 | return NULL; |
f85f8ebb BP |
304 | |
305 | error: | |
306 | file->read_error = ovsdb_error_clone(error); | |
307 | json_destroy(json); | |
308 | return error; | |
309 | } | |
310 | ||
43675e26 BP |
311 | /* Causes the log record read by the previous call to ovsdb_log_read() to be |
312 | * effectively discarded. The next call to ovsdb_log_write() will overwrite | |
313 | * that previously read record. | |
314 | * | |
315 | * Calling this function more than once has no additional effect. | |
316 | * | |
317 | * This function is useful when ovsdb_log_read() successfully reads a record | |
318 | * but that record does not make sense at a higher level (e.g. it specifies an | |
319 | * invalid transaction). */ | |
320 | void | |
321 | ovsdb_log_unread(struct ovsdb_log *file) | |
322 | { | |
cb22974d | 323 | ovs_assert(file->mode == OVSDB_LOG_READ); |
43675e26 BP |
324 | file->offset = file->prev_offset; |
325 | } | |
326 | ||
f85f8ebb | 327 | struct ovsdb_error * |
41709ccc | 328 | ovsdb_log_write(struct ovsdb_log *file, struct json *json) |
f85f8ebb BP |
329 | { |
330 | uint8_t sha1[SHA1_DIGEST_SIZE]; | |
331 | struct ovsdb_error *error; | |
332 | char *json_string; | |
333 | char header[128]; | |
334 | size_t length; | |
335 | ||
336 | json_string = NULL; | |
337 | ||
a7bf837f | 338 | if (file->mode == OVSDB_LOG_READ || file->write_error) { |
41709ccc | 339 | file->mode = OVSDB_LOG_WRITE; |
a7bf837f | 340 | file->write_error = false; |
f85f8ebb BP |
341 | if (fseeko(file->stream, file->offset, SEEK_SET)) { |
342 | error = ovsdb_io_error(errno, "%s: cannot seek to offset %lld", | |
343 | file->name, (long long int) file->offset); | |
344 | goto error; | |
345 | } | |
346 | if (ftruncate(fileno(file->stream), file->offset)) { | |
347 | error = ovsdb_io_error(errno, "%s: cannot truncate to length %lld", | |
348 | file->name, (long long int) file->offset); | |
349 | goto error; | |
350 | } | |
351 | } | |
352 | ||
353 | if (json->type != JSON_OBJECT && json->type != JSON_ARRAY) { | |
354 | error = OVSDB_BUG("bad JSON type"); | |
355 | goto error; | |
356 | } | |
357 | ||
358 | /* Compose content. Add a new-line (replacing the null terminator) to make | |
359 | * the file easier to read, even though it has no semantic value. */ | |
360 | json_string = json_to_string(json, 0); | |
361 | length = strlen(json_string) + 1; | |
362 | json_string[length - 1] = '\n'; | |
363 | ||
364 | /* Compose header. */ | |
365 | sha1_bytes(json_string, length, sha1); | |
34582733 | 366 | snprintf(header, sizeof header, "%s%"PRIuSIZE" "SHA1_FMT"\n", |
f85f8ebb BP |
367 | magic, length, SHA1_ARGS(sha1)); |
368 | ||
369 | /* Write. */ | |
370 | if (fwrite(header, strlen(header), 1, file->stream) != 1 | |
371 | || fwrite(json_string, length, 1, file->stream) != 1 | |
372 | || fflush(file->stream)) | |
373 | { | |
374 | error = ovsdb_io_error(errno, "%s: write failed", file->name); | |
375 | ||
376 | /* Remove any partially written data, ignoring errors since there is | |
377 | * nothing further we can do. */ | |
18b9283b | 378 | ignore(ftruncate(fileno(file->stream), file->offset)); |
f85f8ebb BP |
379 | |
380 | goto error; | |
381 | } | |
382 | ||
383 | file->offset += strlen(header) + length; | |
384 | free(json_string); | |
e3c17733 | 385 | return NULL; |
f85f8ebb BP |
386 | |
387 | error: | |
a7bf837f | 388 | file->write_error = true; |
f85f8ebb BP |
389 | free(json_string); |
390 | return error; | |
391 | } | |
392 | ||
393 | struct ovsdb_error * | |
41709ccc | 394 | ovsdb_log_commit(struct ovsdb_log *file) |
f85f8ebb BP |
395 | { |
396 | if (fsync(fileno(file->stream))) { | |
397 | return ovsdb_io_error(errno, "%s: fsync failed", file->name); | |
398 | } | |
e3c17733 | 399 | return NULL; |
f85f8ebb | 400 | } |
41709ccc | 401 | |
ada496b5 BP |
402 | /* Returns the current offset into the file backing 'log', in bytes. This |
403 | * reflects the number of bytes that have been read or written in the file. If | |
404 | * the whole file has been read, this is the file size. */ | |
405 | off_t | |
406 | ovsdb_log_get_offset(const struct ovsdb_log *log) | |
407 | { | |
408 | return log->offset; | |
409 | } |