]>
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 | ||
ee89ea7b | 27 | #include "openvswitch/json.h" |
f85f8ebb | 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"; |
a6be657b | 117 | error = ovsdb_io_error(errno, "%s: %s failed", name, op); |
f85f8ebb BP |
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 | ||
f85f8ebb | 199 | static struct ovsdb_error * |
41709ccc | 200 | parse_body(struct ovsdb_log *file, off_t offset, unsigned long int length, |
f85f8ebb BP |
201 | uint8_t sha1[SHA1_DIGEST_SIZE], struct json **jsonp) |
202 | { | |
f85f8ebb BP |
203 | struct json_parser *parser; |
204 | struct sha1_ctx ctx; | |
205 | ||
206 | sha1_init(&ctx); | |
207 | parser = json_parser_create(JSPF_TRAILER); | |
208 | ||
f85f8ebb BP |
209 | while (length > 0) { |
210 | char input[BUFSIZ]; | |
211 | int chunk; | |
212 | ||
213 | chunk = MIN(length, sizeof input); | |
214 | if (fread(input, 1, chunk, file->stream) != chunk) { | |
215 | json_parser_abort(parser); | |
216 | return ovsdb_io_error(ferror(file->stream) ? errno : EOF, | |
217 | "%s: error reading %lu bytes " | |
218 | "starting at offset %lld", file->name, | |
219 | length, (long long int) offset); | |
220 | } | |
221 | sha1_update(&ctx, input, chunk); | |
222 | json_parser_feed(parser, input, chunk); | |
223 | length -= chunk; | |
224 | } | |
225 | ||
226 | sha1_final(&ctx, sha1); | |
227 | *jsonp = json_parser_finish(parser); | |
228 | return NULL; | |
229 | } | |
230 | ||
231 | struct ovsdb_error * | |
41709ccc | 232 | ovsdb_log_read(struct ovsdb_log *file, struct json **jsonp) |
f85f8ebb BP |
233 | { |
234 | uint8_t expected_sha1[SHA1_DIGEST_SIZE]; | |
235 | uint8_t actual_sha1[SHA1_DIGEST_SIZE]; | |
236 | struct ovsdb_error *error; | |
237 | off_t data_offset; | |
238 | unsigned long data_length; | |
239 | struct json *json; | |
240 | char header[128]; | |
241 | ||
242 | *jsonp = json = NULL; | |
243 | ||
244 | if (file->read_error) { | |
245 | return ovsdb_error_clone(file->read_error); | |
41709ccc | 246 | } else if (file->mode == OVSDB_LOG_WRITE) { |
f85f8ebb BP |
247 | return OVSDB_BUG("reading file in write mode"); |
248 | } | |
249 | ||
250 | if (!fgets(header, sizeof header, file->stream)) { | |
251 | if (feof(file->stream)) { | |
252 | error = NULL; | |
253 | } else { | |
254 | error = ovsdb_io_error(errno, "%s: read failed", file->name); | |
255 | } | |
256 | goto error; | |
257 | } | |
258 | ||
259 | if (!parse_header(header, &data_length, expected_sha1)) { | |
260 | error = ovsdb_syntax_error(NULL, NULL, "%s: parse error at offset " | |
261 | "%lld in header line \"%.*s\"", | |
262 | file->name, (long long int) file->offset, | |
263 | (int) strcspn(header, "\n"), header); | |
264 | goto error; | |
265 | } | |
266 | ||
267 | data_offset = file->offset + strlen(header); | |
268 | error = parse_body(file, data_offset, data_length, actual_sha1, &json); | |
269 | if (error) { | |
270 | goto error; | |
271 | } | |
272 | ||
273 | if (memcmp(expected_sha1, actual_sha1, SHA1_DIGEST_SIZE)) { | |
274 | error = ovsdb_syntax_error(NULL, NULL, "%s: %lu bytes starting at " | |
275 | "offset %lld have SHA-1 hash "SHA1_FMT" " | |
276 | "but should have hash "SHA1_FMT, | |
277 | file->name, data_length, | |
278 | (long long int) data_offset, | |
279 | SHA1_ARGS(actual_sha1), | |
280 | SHA1_ARGS(expected_sha1)); | |
281 | goto error; | |
282 | } | |
283 | ||
284 | if (json->type == JSON_STRING) { | |
285 | error = ovsdb_syntax_error(NULL, NULL, "%s: %lu bytes starting at " | |
286 | "offset %lld are not valid JSON (%s)", | |
287 | file->name, data_length, | |
288 | (long long int) data_offset, | |
289 | json->u.string); | |
290 | goto error; | |
291 | } | |
292 | ||
43675e26 | 293 | file->prev_offset = file->offset; |
f85f8ebb BP |
294 | file->offset = data_offset + data_length; |
295 | *jsonp = json; | |
e3c17733 | 296 | return NULL; |
f85f8ebb BP |
297 | |
298 | error: | |
299 | file->read_error = ovsdb_error_clone(error); | |
300 | json_destroy(json); | |
301 | return error; | |
302 | } | |
303 | ||
43675e26 BP |
304 | /* Causes the log record read by the previous call to ovsdb_log_read() to be |
305 | * effectively discarded. The next call to ovsdb_log_write() will overwrite | |
306 | * that previously read record. | |
307 | * | |
308 | * Calling this function more than once has no additional effect. | |
309 | * | |
310 | * This function is useful when ovsdb_log_read() successfully reads a record | |
311 | * but that record does not make sense at a higher level (e.g. it specifies an | |
312 | * invalid transaction). */ | |
313 | void | |
314 | ovsdb_log_unread(struct ovsdb_log *file) | |
315 | { | |
cb22974d | 316 | ovs_assert(file->mode == OVSDB_LOG_READ); |
43675e26 BP |
317 | file->offset = file->prev_offset; |
318 | } | |
319 | ||
f85f8ebb | 320 | struct ovsdb_error * |
41709ccc | 321 | ovsdb_log_write(struct ovsdb_log *file, struct json *json) |
f85f8ebb BP |
322 | { |
323 | uint8_t sha1[SHA1_DIGEST_SIZE]; | |
324 | struct ovsdb_error *error; | |
325 | char *json_string; | |
326 | char header[128]; | |
327 | size_t length; | |
328 | ||
329 | json_string = NULL; | |
330 | ||
a7bf837f | 331 | if (file->mode == OVSDB_LOG_READ || file->write_error) { |
41709ccc | 332 | file->mode = OVSDB_LOG_WRITE; |
a7bf837f | 333 | file->write_error = false; |
f85f8ebb BP |
334 | if (fseeko(file->stream, file->offset, SEEK_SET)) { |
335 | error = ovsdb_io_error(errno, "%s: cannot seek to offset %lld", | |
336 | file->name, (long long int) file->offset); | |
337 | goto error; | |
338 | } | |
339 | if (ftruncate(fileno(file->stream), file->offset)) { | |
340 | error = ovsdb_io_error(errno, "%s: cannot truncate to length %lld", | |
341 | file->name, (long long int) file->offset); | |
342 | goto error; | |
343 | } | |
344 | } | |
345 | ||
346 | if (json->type != JSON_OBJECT && json->type != JSON_ARRAY) { | |
347 | error = OVSDB_BUG("bad JSON type"); | |
348 | goto error; | |
349 | } | |
350 | ||
351 | /* Compose content. Add a new-line (replacing the null terminator) to make | |
352 | * the file easier to read, even though it has no semantic value. */ | |
353 | json_string = json_to_string(json, 0); | |
354 | length = strlen(json_string) + 1; | |
355 | json_string[length - 1] = '\n'; | |
356 | ||
357 | /* Compose header. */ | |
358 | sha1_bytes(json_string, length, sha1); | |
34582733 | 359 | snprintf(header, sizeof header, "%s%"PRIuSIZE" "SHA1_FMT"\n", |
f85f8ebb BP |
360 | magic, length, SHA1_ARGS(sha1)); |
361 | ||
362 | /* Write. */ | |
363 | if (fwrite(header, strlen(header), 1, file->stream) != 1 | |
364 | || fwrite(json_string, length, 1, file->stream) != 1 | |
365 | || fflush(file->stream)) | |
366 | { | |
367 | error = ovsdb_io_error(errno, "%s: write failed", file->name); | |
368 | ||
369 | /* Remove any partially written data, ignoring errors since there is | |
370 | * nothing further we can do. */ | |
18b9283b | 371 | ignore(ftruncate(fileno(file->stream), file->offset)); |
f85f8ebb BP |
372 | |
373 | goto error; | |
374 | } | |
375 | ||
376 | file->offset += strlen(header) + length; | |
377 | free(json_string); | |
e3c17733 | 378 | return NULL; |
f85f8ebb BP |
379 | |
380 | error: | |
a7bf837f | 381 | file->write_error = true; |
f85f8ebb BP |
382 | free(json_string); |
383 | return error; | |
384 | } | |
385 | ||
386 | struct ovsdb_error * | |
41709ccc | 387 | ovsdb_log_commit(struct ovsdb_log *file) |
f85f8ebb BP |
388 | { |
389 | if (fsync(fileno(file->stream))) { | |
390 | return ovsdb_io_error(errno, "%s: fsync failed", file->name); | |
391 | } | |
e3c17733 | 392 | return NULL; |
f85f8ebb | 393 | } |
41709ccc | 394 | |
ada496b5 BP |
395 | /* Returns the current offset into the file backing 'log', in bytes. This |
396 | * reflects the number of bytes that have been read or written in the file. If | |
397 | * the whole file has been read, this is the file size. */ | |
398 | off_t | |
399 | ovsdb_log_get_offset(const struct ovsdb_log *log) | |
400 | { | |
401 | return log->offset; | |
402 | } |