Branch data Line data Source code
1 : : /* Copyright (c) 2009, 2010, 2011, 2012, 2013 Nicira, Inc.
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 : :
18 : : #include "log.h"
19 : :
20 : : #include <errno.h>
21 : : #include <fcntl.h>
22 : : #include <stdlib.h>
23 : : #include <string.h>
24 : : #include <sys/stat.h>
25 : : #include <unistd.h>
26 : :
27 : : #include "openvswitch/json.h"
28 : : #include "lockfile.h"
29 : : #include "ovsdb.h"
30 : : #include "ovsdb-error.h"
31 : : #include "sha1.h"
32 : : #include "socket-util.h"
33 : : #include "transaction.h"
34 : : #include "util.h"
35 : :
36 : : enum ovsdb_log_mode {
37 : : OVSDB_LOG_READ,
38 : : OVSDB_LOG_WRITE
39 : : };
40 : :
41 : : struct ovsdb_log {
42 : : off_t prev_offset;
43 : : off_t offset;
44 : : char *name;
45 : : struct lockfile *lockfile;
46 : : FILE *stream;
47 : : struct ovsdb_error *read_error;
48 : : bool write_error;
49 : : enum ovsdb_log_mode mode;
50 : : };
51 : :
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 : : */
60 : : struct ovsdb_error *
61 : 2812 : ovsdb_log_open(const char *name, enum ovsdb_log_open_mode open_mode,
62 : : int locking, struct ovsdb_log **filep)
63 : : {
64 : : struct lockfile *lockfile;
65 : : struct ovsdb_error *error;
66 : : struct ovsdb_log *file;
67 : : struct stat s;
68 : : FILE *stream;
69 : : int flags;
70 : : int fd;
71 : :
72 : 2812 : *filep = NULL;
73 : :
74 [ + + ][ + - ]: 2812 : ovs_assert(locking == -1 || locking == false || locking == true);
[ - + ][ # # ]
75 [ + + ]: 2812 : if (locking < 0) {
76 : 2808 : locking = open_mode != OVSDB_LOG_READ_ONLY;
77 : : }
78 [ + + ]: 2812 : if (locking) {
79 : 2790 : int retval = lockfile_lock(name, &lockfile);
80 [ - + ]: 2790 : if (retval) {
81 : 0 : error = ovsdb_io_error(retval, "%s: failed to lock lockfile",
82 : : name);
83 : 2790 : goto error;
84 : : }
85 : : } else {
86 : 22 : lockfile = NULL;
87 : : }
88 : :
89 [ + + ]: 2812 : if (open_mode == OVSDB_LOG_READ_ONLY) {
90 : 18 : flags = O_RDONLY;
91 [ + + ]: 2794 : } else if (open_mode == OVSDB_LOG_READ_WRITE) {
92 : 1521 : flags = O_RDWR;
93 [ + - ]: 1273 : } else if (open_mode == OVSDB_LOG_CREATE) {
94 : : #ifndef _WIN32
95 [ + + ][ + - ]: 1273 : if (stat(name, &s) == -1 && errno == ENOENT
96 [ + + ][ + - ]: 1272 : && 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 : 2 : flags = O_RDWR | O_CREAT;
102 : : } else {
103 : 1273 : flags = O_RDWR | O_CREAT | O_EXCL;
104 : : }
105 : : #else
106 : : flags = O_RDWR | O_CREAT | O_EXCL;
107 : : #endif
108 : : } else {
109 : 0 : OVS_NOT_REACHED();
110 : : }
111 : : #ifdef _WIN32
112 : : flags = flags | O_BINARY;
113 : : #endif
114 : 2812 : fd = open(name, flags, 0666);
115 [ + + ]: 2812 : if (fd < 0) {
116 [ + + ]: 2 : const char *op = open_mode == OVSDB_LOG_CREATE ? "create" : "open";
117 : 2 : error = ovsdb_io_error(errno, "%s: %s failed", name, op);
118 : 2 : goto error_unlock;
119 : : }
120 : :
121 [ + - ][ + + ]: 2810 : 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. */
124 : 1273 : fsync_parent_dir(name);
125 : : }
126 : :
127 [ + + ]: 2810 : stream = fdopen(fd, open_mode == OVSDB_LOG_READ_ONLY ? "rb" : "w+b");
128 [ - + ]: 2810 : if (!stream) {
129 : 0 : error = ovsdb_io_error(errno, "%s: fdopen failed", name);
130 : 0 : goto error_close;
131 : : }
132 : :
133 : 2810 : file = xmalloc(sizeof *file);
134 : 2810 : file->name = xstrdup(name);
135 : 2810 : file->lockfile = lockfile;
136 : 2810 : file->stream = stream;
137 : 2810 : file->prev_offset = 0;
138 : 2810 : file->offset = 0;
139 : 2810 : file->read_error = NULL;
140 : 2810 : file->write_error = false;
141 : 2810 : file->mode = OVSDB_LOG_READ;
142 : 2810 : *filep = file;
143 : 2810 : return NULL;
144 : :
145 : : error_close:
146 : 0 : close(fd);
147 : : error_unlock:
148 : 2 : lockfile_unlock(lockfile);
149 : : error:
150 : 2812 : return error;
151 : : }
152 : :
153 : : void
154 : 2811 : ovsdb_log_close(struct ovsdb_log *file)
155 : : {
156 [ + + ]: 2811 : if (file) {
157 : 2808 : free(file->name);
158 : 2808 : fclose(file->stream);
159 : 2808 : lockfile_unlock(file->lockfile);
160 : 2808 : ovsdb_error_destroy(file->read_error);
161 : 2808 : free(file);
162 : : }
163 : 2811 : }
164 : :
165 : : static const char magic[] = "OVSDB JSON ";
166 : :
167 : : static bool
168 : 2186 : 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 [ + + ]: 2186 : if (strncmp(header, magic, strlen(magic))) {
175 : 3 : return false;
176 : : }
177 : :
178 : : /* ...followed by a length in bytes... */
179 : 2183 : *length = strtoul(header + strlen(magic), &p, 10);
180 [ + - ][ + - ]: 2183 : if (!*length || *length == ULONG_MAX || *p != ' ') {
[ - + ]
181 : 0 : return false;
182 : : }
183 : 2183 : p++;
184 : :
185 : : /* ...followed by a SHA-1 hash... */
186 [ - + ]: 2183 : if (!sha1_from_hex(sha1, p)) {
187 : 0 : return false;
188 : : }
189 : 2183 : p += SHA1_HEX_DIGEST_LEN;
190 : :
191 : : /* ...and ended by a new-line. */
192 [ - + ]: 2183 : if (*p != '\n') {
193 : 0 : return false;
194 : : }
195 : :
196 : 2186 : return true;
197 : : }
198 : :
199 : : static struct ovsdb_error *
200 : 2183 : parse_body(struct ovsdb_log *file, off_t offset, unsigned long int length,
201 : : uint8_t sha1[SHA1_DIGEST_SIZE], struct json **jsonp)
202 : : {
203 : : struct json_parser *parser;
204 : : struct sha1_ctx ctx;
205 : :
206 : 2183 : sha1_init(&ctx);
207 : 2183 : parser = json_parser_create(JSPF_TRAILER);
208 : :
209 [ + + ]: 5084 : while (length > 0) {
210 : : char input[BUFSIZ];
211 : : int chunk;
212 : :
213 : 2902 : chunk = MIN(length, sizeof input);
214 [ + + ]: 2902 : if (fread(input, 1, chunk, file->stream) != chunk) {
215 : 1 : json_parser_abort(parser);
216 [ - + ]: 1 : 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 : 2901 : sha1_update(&ctx, input, chunk);
222 : 2901 : json_parser_feed(parser, input, chunk);
223 : 2901 : length -= chunk;
224 : : }
225 : :
226 : 2182 : sha1_final(&ctx, sha1);
227 : 2182 : *jsonp = json_parser_finish(parser);
228 : 2183 : return NULL;
229 : : }
230 : :
231 : : struct ovsdb_error *
232 : 3710 : ovsdb_log_read(struct ovsdb_log *file, struct json **jsonp)
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 : 3710 : *jsonp = json = NULL;
243 : :
244 [ - + ]: 3710 : if (file->read_error) {
245 : 0 : return ovsdb_error_clone(file->read_error);
246 [ - + ]: 3710 : } else if (file->mode == OVSDB_LOG_WRITE) {
247 : 0 : return OVSDB_BUG("reading file in write mode");
248 : : }
249 : :
250 [ + + ]: 3710 : if (!fgets(header, sizeof header, file->stream)) {
251 [ + - ]: 1524 : if (feof(file->stream)) {
252 : 1524 : error = NULL;
253 : : } else {
254 : 0 : error = ovsdb_io_error(errno, "%s: read failed", file->name);
255 : : }
256 : 1524 : goto error;
257 : : }
258 : :
259 [ + + ]: 2186 : if (!parse_header(header, &data_length, expected_sha1)) {
260 : 3 : error = ovsdb_syntax_error(NULL, NULL, "%s: parse error at offset "
261 : : "%lld in header line \"%.*s\"",
262 : 3 : file->name, (long long int) file->offset,
263 : 3 : (int) strcspn(header, "\n"), header);
264 : 3 : goto error;
265 : : }
266 : :
267 : 2183 : data_offset = file->offset + strlen(header);
268 : 2183 : error = parse_body(file, data_offset, data_length, actual_sha1, &json);
269 [ + + ]: 2183 : if (error) {
270 : 1 : goto error;
271 : : }
272 : :
273 [ + + ]: 2182 : if (memcmp(expected_sha1, actual_sha1, SHA1_DIGEST_SIZE)) {
274 : 1 : 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 : 20 : SHA1_ARGS(actual_sha1),
280 : 20 : SHA1_ARGS(expected_sha1));
281 : 1 : goto error;
282 : : }
283 : :
284 [ + + ]: 2181 : if (json->type == JSON_STRING) {
285 : 1 : 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 : 1 : json->u.string);
290 : 1 : goto error;
291 : : }
292 : :
293 : 2180 : file->prev_offset = file->offset;
294 : 2180 : file->offset = data_offset + data_length;
295 : 2180 : *jsonp = json;
296 : 2180 : return NULL;
297 : :
298 : : error:
299 : 1530 : file->read_error = ovsdb_error_clone(error);
300 : 1530 : json_destroy(json);
301 : 3710 : return error;
302 : : }
303 : :
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 : 1 : ovsdb_log_unread(struct ovsdb_log *file)
315 : : {
316 [ - + ]: 1 : ovs_assert(file->mode == OVSDB_LOG_READ);
317 : 1 : file->offset = file->prev_offset;
318 : 1 : }
319 : :
320 : : struct ovsdb_error *
321 : 11704 : ovsdb_log_write(struct ovsdb_log *file, struct json *json)
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 : 11704 : json_string = NULL;
330 : :
331 [ + + ][ - + ]: 11704 : if (file->mode == OVSDB_LOG_READ || file->write_error) {
332 : 2614 : file->mode = OVSDB_LOG_WRITE;
333 : 2614 : file->write_error = false;
334 [ - + ]: 2614 : if (fseeko(file->stream, file->offset, SEEK_SET)) {
335 : 0 : error = ovsdb_io_error(errno, "%s: cannot seek to offset %lld",
336 : 0 : file->name, (long long int) file->offset);
337 : 0 : goto error;
338 : : }
339 [ - + ]: 2614 : if (ftruncate(fileno(file->stream), file->offset)) {
340 : 0 : error = ovsdb_io_error(errno, "%s: cannot truncate to length %lld",
341 : 0 : file->name, (long long int) file->offset);
342 : 0 : goto error;
343 : : }
344 : : }
345 : :
346 [ + + ][ - + ]: 11704 : if (json->type != JSON_OBJECT && json->type != JSON_ARRAY) {
347 : 0 : error = OVSDB_BUG("bad JSON type");
348 : 0 : 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 : 11704 : json_string = json_to_string(json, 0);
354 : 11704 : length = strlen(json_string) + 1;
355 : 11704 : json_string[length - 1] = '\n';
356 : :
357 : : /* Compose header. */
358 : 11704 : sha1_bytes(json_string, length, sha1);
359 : 234080 : snprintf(header, sizeof header, "%s%"PRIuSIZE" "SHA1_FMT"\n",
360 : 234080 : magic, length, SHA1_ARGS(sha1));
361 : :
362 : : /* Write. */
363 [ + - ]: 11704 : if (fwrite(header, strlen(header), 1, file->stream) != 1
364 [ + - ]: 11704 : || fwrite(json_string, length, 1, file->stream) != 1
365 [ - + ]: 11704 : || fflush(file->stream))
366 : : {
367 : 0 : 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. */
371 : 0 : ignore(ftruncate(fileno(file->stream), file->offset));
372 : :
373 : 0 : goto error;
374 : : }
375 : :
376 : 11704 : file->offset += strlen(header) + length;
377 : 11704 : free(json_string);
378 : 11704 : return NULL;
379 : :
380 : : error:
381 : 0 : file->write_error = true;
382 : 0 : free(json_string);
383 : 11704 : return error;
384 : : }
385 : :
386 : : struct ovsdb_error *
387 : 1268 : ovsdb_log_commit(struct ovsdb_log *file)
388 : : {
389 [ - + ]: 1268 : if (fsync(fileno(file->stream))) {
390 : 0 : return ovsdb_io_error(errno, "%s: fsync failed", file->name);
391 : : }
392 : 1268 : return NULL;
393 : : }
394 : :
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 : 10686 : ovsdb_log_get_offset(const struct ovsdb_log *log)
400 : : {
401 : 10686 : return log->offset;
402 : : }
|