/src/openvswitch/lib/ovs-replay.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2021, Red Hat, Inc. |
3 | | * |
4 | | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | | * you may not use this file except in compliance with the License. |
6 | | * You may obtain a copy of the License at: |
7 | | * |
8 | | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | | * |
10 | | * Unless required by applicable law or agreed to in writing, software |
11 | | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | | * See the License for the specific language governing permissions and |
14 | | * limitations under the License. |
15 | | */ |
16 | | |
17 | | #include <config.h> |
18 | | #include <ctype.h> |
19 | | #include <errno.h> |
20 | | #include <stdlib.h> |
21 | | #include <string.h> |
22 | | #include <sys/socket.h> |
23 | | #include <sys/types.h> |
24 | | #include <unistd.h> |
25 | | #include "dirs.h" |
26 | | #include "ovs-atomic.h" |
27 | | #include "ovs-replay.h" |
28 | | #include "util.h" |
29 | | #include "openvswitch/vlog.h" |
30 | | |
31 | | VLOG_DEFINE_THIS_MODULE(ovs_replay); |
32 | | |
33 | | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 25); |
34 | | |
35 | | static struct ovs_mutex replay_mutex = OVS_MUTEX_INITIALIZER; |
36 | | static int replay_seqno OVS_GUARDED_BY(replay_mutex) = 0; |
37 | | static atomic_int replay_state = OVS_REPLAY_NONE; |
38 | | |
39 | | static char *dirname = NULL; |
40 | | |
41 | | void |
42 | | ovs_replay_set_state(enum ovs_replay_state state) |
43 | 0 | { |
44 | 0 | atomic_store_relaxed(&replay_state, state); |
45 | 0 | } |
46 | | |
47 | | enum ovs_replay_state |
48 | | ovs_replay_get_state(void) |
49 | 0 | { |
50 | 0 | int state; |
51 | |
|
52 | 0 | atomic_read_relaxed(&replay_state, &state); |
53 | 0 | return state; |
54 | 0 | } |
55 | | |
56 | | void |
57 | | ovs_replay_set_dirname(const char *new_dirname) |
58 | 0 | { |
59 | 0 | if (new_dirname) { |
60 | 0 | free(dirname); |
61 | 0 | dirname = xstrdup(new_dirname); |
62 | 0 | } |
63 | 0 | } |
64 | | |
65 | | void |
66 | | ovs_replay_lock(void) |
67 | | OVS_ACQUIRES(replay_mutex) |
68 | 0 | { |
69 | 0 | ovs_mutex_lock(&replay_mutex); |
70 | 0 | } |
71 | | |
72 | | void |
73 | | ovs_replay_unlock(void) |
74 | | OVS_RELEASES(replay_mutex) |
75 | 0 | { |
76 | 0 | ovs_mutex_unlock(&replay_mutex); |
77 | 0 | } |
78 | | |
79 | | int |
80 | | ovs_replay_seqno(void) |
81 | | OVS_REQUIRES(replay_mutex) |
82 | 0 | { |
83 | 0 | return replay_seqno; |
84 | 0 | } |
85 | | |
86 | | static char * |
87 | | ovs_replay_file_name(const char *name, int seqno) |
88 | 0 | { |
89 | 0 | char *local_name = xstrdup(name); |
90 | 0 | char *filename, *p, *c; |
91 | 0 | bool skip = false; |
92 | | |
93 | | /* Replace all the numbers and special symbols with single underscore. |
94 | | * Numbers might be PIDs or port numbers that could change between record |
95 | | * and replay phases, special symbols might be not good as a filename. |
96 | | * We have a unique seuqence number as part of the name, so we don't care |
97 | | * keeping too much information. */ |
98 | 0 | for (c = p = local_name; *p; p++) { |
99 | 0 | if (!isalpha((unsigned char) *p)) { |
100 | 0 | if (!skip) { |
101 | 0 | *c++ = '_'; |
102 | 0 | skip = true; |
103 | 0 | } |
104 | 0 | } else { |
105 | 0 | *c++ = *p; |
106 | 0 | skip = false; |
107 | 0 | } |
108 | 0 | } |
109 | 0 | if (skip) { |
110 | 0 | c--; |
111 | 0 | } |
112 | 0 | *c = '\0'; |
113 | 0 | filename = xasprintf("%s/replay_%s_%d", dirname ? dirname : "", |
114 | 0 | local_name, seqno); |
115 | 0 | VLOG_DBG("Constructing replay filename: '%s' --> '%s' --> '%s'.", |
116 | 0 | name, local_name, filename); |
117 | 0 | free(local_name); |
118 | |
|
119 | 0 | return filename; |
120 | 0 | } |
121 | | |
122 | | int |
123 | | ovs_replay_file_open(const char *name, replay_file_t *f, int *seqno) |
124 | | OVS_REQUIRES(replay_mutex) |
125 | 0 | { |
126 | 0 | char *file_path, *filename; |
127 | 0 | int state = ovs_replay_get_state(); |
128 | |
|
129 | 0 | ovs_assert(state != OVS_REPLAY_NONE); |
130 | |
|
131 | 0 | filename = ovs_replay_file_name(name, replay_seqno); |
132 | 0 | if (filename[0] != '/') { |
133 | 0 | file_path = abs_file_name(ovs_rundir(), filename); |
134 | 0 | free(filename); |
135 | 0 | } else { |
136 | 0 | file_path = filename; |
137 | 0 | } |
138 | |
|
139 | 0 | *f = fopen(file_path, state == OVS_REPLAY_WRITE ? "wb" : "rb"); |
140 | 0 | if (!*f) { |
141 | 0 | VLOG_ERR_RL(&rl, "%s: fopen failed: %s", |
142 | 0 | file_path, ovs_strerror(errno)); |
143 | 0 | free(file_path); |
144 | 0 | return errno; |
145 | 0 | } |
146 | 0 | free(file_path); |
147 | |
|
148 | 0 | if (state == OVS_REPLAY_READ |
149 | 0 | && fread(seqno, sizeof *seqno, 1, *f) != 1) { |
150 | 0 | VLOG_INFO("%s: failed to read seqno: replay might be empty.", name); |
151 | 0 | *seqno = INT_MAX; |
152 | 0 | } |
153 | 0 | replay_seqno++; /* New file opened. */ |
154 | 0 | return 0; |
155 | 0 | } |
156 | | |
157 | | void |
158 | | ovs_replay_file_close(replay_file_t f) |
159 | 0 | { |
160 | 0 | fclose(f); |
161 | 0 | } |
162 | | |
163 | | int |
164 | | ovs_replay_write(replay_file_t f, const void *buffer, int n, bool is_read) |
165 | | OVS_EXCLUDED(replay_mutex) |
166 | 0 | { |
167 | 0 | int state = ovs_replay_get_state(); |
168 | 0 | int seqno_to_write; |
169 | 0 | int retval = 0; |
170 | |
|
171 | 0 | if (OVS_LIKELY(state != OVS_REPLAY_WRITE)) { |
172 | 0 | return 0; |
173 | 0 | } |
174 | | |
175 | 0 | ovs_replay_lock(); |
176 | |
|
177 | 0 | seqno_to_write = is_read ? replay_seqno : -replay_seqno; |
178 | 0 | if (fwrite(&seqno_to_write, sizeof seqno_to_write, 1, f) != 1) { |
179 | 0 | VLOG_ERR_RL(&rl, "Failed to write seqno."); |
180 | 0 | retval = -1; |
181 | 0 | goto out; |
182 | 0 | } |
183 | 0 | if (fwrite(&n, sizeof n, 1, f) != 1) { |
184 | 0 | VLOG_ERR_RL(&rl, "Failed to write length."); |
185 | 0 | retval = -1; |
186 | 0 | goto out; |
187 | 0 | } |
188 | 0 | if (n > 0 && is_read && fwrite(buffer, 1, n, f) != n) { |
189 | 0 | VLOG_ERR_RL(&rl, "Failed to write data."); |
190 | 0 | retval = -1; |
191 | 0 | } |
192 | 0 | out: |
193 | 0 | replay_seqno++; /* Write completed. */ |
194 | 0 | ovs_replay_unlock(); |
195 | 0 | fflush(f); |
196 | 0 | return retval; |
197 | 0 | } |
198 | | |
199 | | int |
200 | | ovs_replay_read(replay_file_t f, void *buffer, int buffer_size, |
201 | | int *len, int *seqno, bool is_read) |
202 | | OVS_REQUIRES(replay_mutex) |
203 | 0 | { |
204 | 0 | int retval = EINVAL; |
205 | |
|
206 | 0 | if (fread(len, sizeof *len, 1, f) != 1) { |
207 | 0 | VLOG_ERR_RL(&rl, "Failed to read replay length."); |
208 | 0 | goto out; |
209 | 0 | } |
210 | | |
211 | 0 | if (is_read && *len > buffer_size) { |
212 | 0 | VLOG_ERR_RL(&rl, "Failed to read replay buffer: " |
213 | 0 | "insufficient buffer size: provided %d, needed %d.", |
214 | 0 | buffer_size, *len); |
215 | 0 | goto out; |
216 | 0 | } |
217 | | |
218 | 0 | if (*len > 0 && is_read && fread(buffer, 1, *len, f) != *len) { |
219 | 0 | VLOG_ERR_RL(&rl, "Failed to read replay buffer."); |
220 | 0 | goto out; |
221 | 0 | } |
222 | | |
223 | 0 | if (fread(seqno, sizeof *seqno, 1, f) != 1) { |
224 | 0 | *seqno = INT_MAX; /* Most likely EOF. */ |
225 | 0 | if (ferror(f)) { |
226 | 0 | VLOG_INFO("Failed to read replay seqno."); |
227 | 0 | goto out; |
228 | 0 | } |
229 | 0 | } |
230 | | |
231 | 0 | retval = 0; |
232 | 0 | out: |
233 | 0 | replay_seqno++; /* Read completed. */ |
234 | 0 | return retval; |
235 | 0 | } |
236 | | |
237 | | void |
238 | | ovs_replay_usage(void) |
239 | 0 | { |
240 | 0 | printf("\nReplay options:\n" |
241 | 0 | " --record[=DIR] turn on writing replay files\n" |
242 | 0 | " --replay[=DIR] run from replay files\n"); |
243 | 0 | } |