Commit | Line | Data |
---|---|---|
46bdd3e0 PP |
1 | /* |
2 | * Copyright 2019 Philippe Proulx <pproulx@efficios.com> | |
3 | * | |
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
5 | * of this software and associated documentation files (the "Software"), to deal | |
6 | * in the Software without restriction, including without limitation the rights | |
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
8 | * copies of the Software, and to permit persons to whom the Software is | |
9 | * furnished to do so, subject to the following conditions: | |
10 | * | |
11 | * The above copyright notice and this permission notice shall be included in | |
12 | * all copies or substantial portions of the Software. | |
13 | * | |
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
20 | * SOFTWARE. | |
21 | */ | |
22 | ||
23 | #define BT_LOG_TAG "PLUGIN-CTF-FS-SINK-TRACE" | |
24 | #include "logging.h" | |
25 | ||
26 | #include <babeltrace/babeltrace.h> | |
27 | #include <stdio.h> | |
28 | #include <stdbool.h> | |
29 | #include <glib.h> | |
30 | #include <babeltrace/assert-internal.h> | |
31 | #include <babeltrace/ctfser-internal.h> | |
32 | ||
33 | #include "translate-trace-ir-to-ctf-ir.h" | |
34 | #include "translate-ctf-ir-to-tsdl.h" | |
35 | #include "fs-sink.h" | |
36 | #include "fs-sink-trace.h" | |
37 | #include "fs-sink-stream.h" | |
38 | ||
39 | /* | |
40 | * Sanitizes `path` so as to: | |
41 | * | |
42 | * * Replace `.` subdirectories with `_`. | |
43 | * * Replace `..` subdirectories with `__`. | |
44 | * * Remove trailing slashes. | |
45 | */ | |
46 | static | |
47 | GString *sanitize_trace_path(const char *path) | |
48 | { | |
49 | GString *san_path = g_string_new(NULL); | |
50 | const char *ch = path; | |
51 | bool dir_start = true; | |
52 | ||
53 | BT_ASSERT(san_path); | |
54 | BT_ASSERT(path); | |
55 | ||
56 | while (*ch != '\0') { | |
57 | switch (*ch) { | |
58 | case '/': | |
59 | /* Start of directory */ | |
60 | dir_start = true; | |
61 | g_string_append_c(san_path, *ch); | |
62 | ch++; | |
63 | continue; | |
64 | case '.': | |
65 | if (dir_start) { | |
66 | switch (ch[1]) { | |
67 | case '\0': | |
68 | case '/': | |
69 | /* `.` -> `_` */ | |
70 | g_string_append_c(san_path, '_'); | |
71 | ch++; | |
72 | continue; | |
73 | case '.': | |
74 | switch (ch[2]) { | |
75 | case '\0': | |
76 | case '/': | |
77 | /* `..` -> `__` */ | |
78 | g_string_append(san_path, "__"); | |
79 | ch += 2; | |
80 | continue; | |
81 | default: | |
82 | break; | |
83 | } | |
84 | default: | |
85 | break; | |
86 | } | |
87 | } | |
88 | default: | |
89 | break; | |
90 | } | |
91 | ||
92 | /* Not a special character */ | |
93 | g_string_append_c(san_path, *ch); | |
94 | ch++; | |
95 | dir_start = false; | |
96 | } | |
97 | ||
98 | /* Remove trailing slashes */ | |
99 | while (san_path->len > 0 && | |
100 | san_path->str[san_path->len - 1] == '/') { | |
101 | /* Remove trailing slash */ | |
102 | g_string_set_size(san_path, san_path->len - 1); | |
103 | } | |
104 | ||
105 | if (san_path->len == 0) { | |
106 | /* Looks like there's nothing left: just use `trace` */ | |
107 | g_string_assign(san_path, "trace"); | |
108 | } | |
109 | ||
110 | return san_path; | |
111 | } | |
112 | ||
113 | static | |
114 | GString *make_unique_trace_path(struct fs_sink_comp *fs_sink, | |
115 | const char *output_dir_path, const char *base) | |
116 | { | |
117 | GString *path = g_string_new(output_dir_path); | |
118 | GString *san_base = NULL; | |
119 | unsigned int suffix = 0; | |
120 | ||
121 | if (fs_sink->assume_single_trace) { | |
122 | /* Use output directory directly */ | |
123 | goto end; | |
124 | } | |
125 | ||
126 | san_base = sanitize_trace_path(base); | |
127 | g_string_append_printf(path, "/%s", san_base->str); | |
128 | ||
129 | while (g_file_test(path->str, G_FILE_TEST_IS_DIR)) { | |
130 | g_string_assign(path, output_dir_path); | |
131 | g_string_append_printf(path, "/%s%u", san_base->str, suffix); | |
132 | suffix++; | |
133 | } | |
134 | ||
135 | end: | |
136 | if (san_base) { | |
137 | g_string_free(san_base, TRUE); | |
138 | } | |
139 | ||
140 | return path; | |
141 | } | |
142 | ||
143 | BT_HIDDEN | |
144 | void fs_sink_trace_destroy(struct fs_sink_trace *trace) | |
145 | { | |
146 | GString *tsdl = NULL; | |
147 | FILE *fh = NULL; | |
148 | size_t len; | |
149 | ||
150 | if (!trace) { | |
151 | goto end; | |
152 | } | |
153 | ||
154 | if (trace->ir_trace_destruction_listener_id != UINT64_C(-1)) { | |
155 | /* | |
156 | * Remove the destruction listener, otherwise it could | |
157 | * be called in the future, and its private data is this | |
158 | * CTF FS sink trace object which won't exist anymore. | |
159 | */ | |
160 | (void) bt_trace_remove_destruction_listener(trace->ir_trace, | |
161 | trace->ir_trace_destruction_listener_id); | |
162 | trace->ir_trace_destruction_listener_id = UINT64_C(-1); | |
163 | } | |
164 | ||
165 | if (trace->streams) { | |
166 | g_hash_table_destroy(trace->streams); | |
167 | trace->streams = NULL; | |
168 | } | |
169 | ||
170 | tsdl = g_string_new(NULL); | |
171 | BT_ASSERT(tsdl); | |
172 | translate_trace_class_ctf_ir_to_tsdl(trace->tc, tsdl); | |
173 | fh = fopen(trace->metadata_path->str, "wb"); | |
174 | if (!fh) { | |
175 | BT_LOGF_ERRNO("In trace destruction listener: " | |
176 | "cannot open metadata file for writing: ", | |
177 | ": path=\"%s\"", trace->metadata_path->str); | |
178 | abort(); | |
179 | } | |
180 | ||
181 | len = fwrite(tsdl->str, sizeof(*tsdl->str), tsdl->len, fh); | |
182 | if (len != tsdl->len) { | |
183 | BT_LOGF_ERRNO("In trace destruction listener: " | |
184 | "cannot write metadata file: ", | |
185 | ": path=\"%s\"", trace->metadata_path->str); | |
186 | abort(); | |
187 | } | |
188 | ||
189 | if (!trace->fs_sink->quiet) { | |
190 | printf("Created CTF trace `%s`.\n", trace->path->str); | |
191 | } | |
192 | ||
193 | if (trace->path) { | |
194 | g_string_free(trace->path, TRUE); | |
195 | trace->path = NULL; | |
196 | } | |
197 | ||
198 | if (trace->metadata_path) { | |
199 | g_string_free(trace->metadata_path, TRUE); | |
200 | trace->metadata_path = NULL; | |
201 | } | |
202 | ||
203 | fs_sink_ctf_trace_class_destroy(trace->tc); | |
204 | trace->tc = NULL; | |
205 | g_free(trace); | |
206 | ||
207 | end: | |
208 | if (fh) { | |
209 | int ret = fclose(fh); | |
210 | ||
211 | if (ret != 0) { | |
212 | BT_LOGW_ERRNO("In trace destruction listener: " | |
213 | "cannot close metadata file: ", | |
214 | ": path=\"%s\"", trace->metadata_path->str); | |
215 | } | |
216 | } | |
217 | ||
218 | if (tsdl) { | |
219 | g_string_free(tsdl, TRUE); | |
220 | } | |
221 | ||
222 | return; | |
223 | } | |
224 | ||
225 | static | |
226 | void ir_trace_destruction_listener(const bt_trace *ir_trace, void *data) | |
227 | { | |
228 | struct fs_sink_trace *trace = data; | |
229 | ||
230 | /* | |
231 | * Prevent bt_trace_remove_destruction_listener() from being | |
232 | * called in fs_sink_trace_destroy(), which is called by | |
233 | * g_hash_table_remove() below. | |
234 | */ | |
235 | trace->ir_trace_destruction_listener_id = UINT64_C(-1); | |
236 | g_hash_table_remove(trace->fs_sink->traces, ir_trace); | |
237 | } | |
238 | ||
239 | BT_HIDDEN | |
240 | struct fs_sink_trace *fs_sink_trace_create(struct fs_sink_comp *fs_sink, | |
241 | const bt_trace *ir_trace) | |
242 | { | |
243 | int ret; | |
244 | struct fs_sink_trace *trace = g_new0(struct fs_sink_trace, 1); | |
245 | const char *trace_name = bt_trace_get_name(ir_trace); | |
246 | bt_trace_status trace_status; | |
247 | ||
248 | if (!trace) { | |
249 | goto end; | |
250 | } | |
251 | ||
252 | if (!trace_name) { | |
253 | trace_name = "trace"; | |
254 | } | |
255 | ||
256 | trace->fs_sink = fs_sink; | |
257 | trace->ir_trace = ir_trace; | |
258 | trace->ir_trace_destruction_listener_id = UINT64_C(-1); | |
259 | trace->tc = translate_trace_class_trace_ir_to_ctf_ir( | |
260 | bt_trace_borrow_class_const(ir_trace)); | |
261 | if (!trace->tc) { | |
262 | goto error; | |
263 | } | |
264 | ||
265 | trace->path = make_unique_trace_path(fs_sink, | |
266 | fs_sink->output_dir_path->str, trace_name); | |
267 | BT_ASSERT(trace->path); | |
268 | ret = g_mkdir_with_parents(trace->path->str, 0755); | |
269 | if (ret) { | |
270 | BT_LOGE_ERRNO("Cannot create directories for trace directory", | |
271 | ": path=\"%s\"", trace->path->str); | |
272 | goto error; | |
273 | } | |
274 | ||
275 | trace->metadata_path = g_string_new(trace->path->str); | |
276 | BT_ASSERT(trace->metadata_path); | |
277 | g_string_append(trace->metadata_path, "/metadata"); | |
278 | trace->streams = g_hash_table_new_full(g_direct_hash, g_direct_equal, | |
279 | NULL, (GDestroyNotify) fs_sink_stream_destroy); | |
280 | BT_ASSERT(trace->streams); | |
281 | trace_status = bt_trace_add_destruction_listener(ir_trace, | |
282 | ir_trace_destruction_listener, trace, | |
283 | &trace->ir_trace_destruction_listener_id); | |
284 | if (trace_status) { | |
285 | goto error; | |
286 | } | |
287 | ||
288 | g_hash_table_insert(fs_sink->traces, (gpointer) ir_trace, trace); | |
289 | goto end; | |
290 | ||
291 | error: | |
292 | fs_sink_trace_destroy(trace); | |
293 | trace = NULL; | |
294 | ||
295 | end: | |
296 | return trace; | |
297 | } |