]> git.proxmox.com Git - ceph.git/blob - ceph/src/tools/rbd/action/MergeDiff.cc
update sources to v12.1.2
[ceph.git] / ceph / src / tools / rbd / action / MergeDiff.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3
4 #define _LARGEFILE64_SOURCE
5 #include <sys/types.h>
6 #include <unistd.h>
7
8 #include "include/compat.h"
9 #include "tools/rbd/ArgumentTypes.h"
10 #include "tools/rbd/Shell.h"
11 #include "tools/rbd/Utils.h"
12 #include "common/safe_io.h"
13 #include "common/debug.h"
14 #include "common/errno.h"
15 #include <iostream>
16 #include <boost/program_options.hpp>
17
18 #define dout_context g_ceph_context
19 #define dout_subsys ceph_subsys_rbd
20
21 namespace rbd {
22 namespace action {
23 namespace merge_diff {
24
25 namespace at = argument_types;
26 namespace po = boost::program_options;
27
28 static int parse_diff_header(int fd, __u8 *tag, string *from, string *to, uint64_t *size)
29 {
30 int r;
31
32 {//header
33 char buf[utils::RBD_DIFF_BANNER.size() + 1];
34 r = safe_read_exact(fd, buf, utils::RBD_DIFF_BANNER.size());
35 if (r < 0)
36 return r;
37
38 buf[utils::RBD_DIFF_BANNER.size()] = '\0';
39 if (strcmp(buf, utils::RBD_DIFF_BANNER.c_str())) {
40 std::cerr << "invalid banner '" << buf << "', expected '"
41 << utils::RBD_DIFF_BANNER << "'" << std::endl;
42 return -EINVAL;
43 }
44 }
45
46 while (true) {
47 r = safe_read_exact(fd, tag, 1);
48 if (r < 0)
49 return r;
50
51 if (*tag == RBD_DIFF_FROM_SNAP) {
52 r = utils::read_string(fd, 4096, from); // 4k limit to make sure we don't get a garbage string
53 if (r < 0)
54 return r;
55 dout(2) << " from snap " << *from << dendl;
56 } else if (*tag == RBD_DIFF_TO_SNAP) {
57 r = utils::read_string(fd, 4096, to); // 4k limit to make sure we don't get a garbage string
58 if (r < 0)
59 return r;
60 dout(2) << " to snap " << *to << dendl;
61 } else if (*tag == RBD_DIFF_IMAGE_SIZE) {
62 char buf[8];
63 r = safe_read_exact(fd, buf, 8);
64 if (r < 0)
65 return r;
66
67 bufferlist bl;
68 bl.append(buf, 8);
69 bufferlist::iterator p = bl.begin();
70 ::decode(*size, p);
71 } else {
72 break;
73 }
74 }
75
76 return 0;
77 }
78
79 static int parse_diff_body(int fd, __u8 *tag, uint64_t *offset, uint64_t *length)
80 {
81 int r;
82
83 if (!(*tag)) {
84 r = safe_read_exact(fd, tag, 1);
85 if (r < 0)
86 return r;
87 }
88
89 if (*tag == RBD_DIFF_END) {
90 offset = 0;
91 length = 0;
92 return 0;
93 }
94
95 if (*tag != RBD_DIFF_WRITE && *tag != RBD_DIFF_ZERO)
96 return -ENOTSUP;
97
98 char buf[16];
99 r = safe_read_exact(fd, buf, 16);
100 if (r < 0)
101 return r;
102
103 bufferlist bl;
104 bl.append(buf, 16);
105 bufferlist::iterator p = bl.begin();
106 ::decode(*offset, p);
107 ::decode(*length, p);
108
109 if (!(*length))
110 return -ENOTSUP;
111
112 return 0;
113 }
114
115 /*
116 * fd: the diff file to read from
117 * pd: the diff file to be written into
118 */
119 static int accept_diff_body(int fd, int pd, __u8 tag, uint64_t offset, uint64_t length)
120 {
121 if (tag == RBD_DIFF_END)
122 return 0;
123
124 bufferlist bl;
125 ::encode(tag, bl);
126 ::encode(offset, bl);
127 ::encode(length, bl);
128 int r;
129 r = bl.write_fd(pd);
130 if (r < 0)
131 return r;
132
133 if (tag == RBD_DIFF_WRITE) {
134 bufferptr bp = buffer::create(length);
135 r = safe_read_exact(fd, bp.c_str(), length);
136 if (r < 0)
137 return r;
138 bufferlist data;
139 data.append(bp);
140 r = data.write_fd(pd);
141 if (r < 0)
142 return r;
143 }
144
145 return 0;
146 }
147
148 /*
149 * Merge two diff files into one single file
150 * Note: It does not do the merging work if
151 * either of the source diff files is stripped,
152 * since which complicates the process and is
153 * rarely used
154 */
155 static int do_merge_diff(const char *first, const char *second,
156 const char *path, bool no_progress)
157 {
158 utils::ProgressContext pc("Merging image diff", no_progress);
159 int fd = -1, sd = -1, pd = -1, r;
160
161 string f_from, f_to;
162 string s_from, s_to;
163 uint64_t f_size = 0;
164 uint64_t s_size = 0;
165 uint64_t pc_size;
166
167 __u8 f_tag = 0, s_tag = 0;
168 uint64_t f_off = 0, f_len = 0;
169 uint64_t s_off = 0, s_len = 0;
170 bool f_end = false, s_end = false;
171
172 bool first_stdin = !strcmp(first, "-");
173 if (first_stdin) {
174 fd = STDIN_FILENO;
175 } else {
176 fd = open(first, O_RDONLY);
177 if (fd < 0) {
178 r = -errno;
179 std::cerr << "rbd: error opening " << first << std::endl;
180 goto done;
181 }
182 }
183
184 sd = open(second, O_RDONLY);
185 if (sd < 0) {
186 r = -errno;
187 std::cerr << "rbd: error opening " << second << std::endl;
188 goto done;
189 }
190
191 if (strcmp(path, "-") == 0) {
192 pd = 1;
193 } else {
194 pd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0644);
195 if (pd < 0) {
196 r = -errno;
197 std::cerr << "rbd: error create " << path << std::endl;
198 goto done;
199 }
200 }
201
202 //We just handle the case like 'banner, [ftag], [ttag], stag, [wztag]*,etag',
203 // and the (offset,length) in wztag must be ascending order.
204 r = parse_diff_header(fd, &f_tag, &f_from, &f_to, &f_size);
205 if (r < 0) {
206 std::cerr << "rbd: failed to parse first diff header" << std::endl;
207 goto done;
208 }
209
210 r = parse_diff_header(sd, &s_tag, &s_from, &s_to, &s_size);
211 if (r < 0) {
212 std::cerr << "rbd: failed to parse second diff header" << std::endl;
213 goto done;
214 }
215
216 if (f_to != s_from) {
217 r = -EINVAL;
218 std::cerr << "The first TO snapshot must be equal with the second FROM "
219 << "snapshot, aborting" << std::endl;
220 goto done;
221 }
222
223 {
224 // header
225 bufferlist bl;
226 bl.append(utils::RBD_DIFF_BANNER);
227
228 __u8 tag;
229 if (f_from.size()) {
230 tag = RBD_DIFF_FROM_SNAP;
231 ::encode(tag, bl);
232 ::encode(f_from, bl);
233 }
234
235 if (s_to.size()) {
236 tag = RBD_DIFF_TO_SNAP;
237 ::encode(tag, bl);
238 ::encode(s_to, bl);
239 }
240
241 tag = RBD_DIFF_IMAGE_SIZE;
242 ::encode(tag, bl);
243 ::encode(s_size, bl);
244
245 r = bl.write_fd(pd);
246 if (r < 0) {
247 std::cerr << "rbd: failed to write merged diff header" << std::endl;
248 goto done;
249 }
250 }
251 if (f_size > s_size)
252 pc_size = f_size << 1;
253 else
254 pc_size = s_size << 1;
255
256 //data block
257 while (!f_end || !s_end) {
258 // progress through input
259 pc.update_progress(f_off + s_off, pc_size);
260
261 if (!f_end && !f_len) {
262 uint64_t last_off = f_off;
263
264 r = parse_diff_body(fd, &f_tag, &f_off, &f_len);
265 dout(2) << "first diff data chunk: tag=" << f_tag << ", "
266 << "off=" << f_off << ", "
267 << "len=" << f_len << dendl;
268 if (r < 0) {
269 std::cerr << "rbd: failed to read first diff data chunk header"
270 << std::endl;
271 goto done;
272 }
273
274 if (f_tag == RBD_DIFF_END) {
275 f_end = true;
276 f_tag = RBD_DIFF_ZERO;
277 f_off = f_size;
278 if (f_size < s_size)
279 f_len = s_size - f_size;
280 else
281 f_len = 0;
282 }
283
284 if (last_off > f_off) {
285 r = -ENOTSUP;
286 std::cerr << "rbd: out-of-order offset from first diff ("
287 << last_off << " > " << f_off << ")" << std::endl;
288 goto done;
289 }
290 }
291
292 if (!s_end && !s_len) {
293 uint64_t last_off = s_off;
294
295 r = parse_diff_body(sd, &s_tag, &s_off, &s_len);
296 dout(2) << "second diff data chunk: tag=" << s_tag << ", "
297 << "off=" << s_off << ", "
298 << "len=" << s_len << dendl;
299 if (r < 0) {
300 std::cerr << "rbd: failed to read second diff data chunk header"
301 << std::endl;
302 goto done;
303 }
304
305 if (s_tag == RBD_DIFF_END) {
306 s_end = true;
307 s_off = s_size;
308 if (s_size < f_size)
309 s_len = f_size - s_size;
310 else
311 s_len = 0;
312 }
313
314 if (last_off > s_off) {
315 r = -ENOTSUP;
316 std::cerr << "rbd: out-of-order offset from second diff ("
317 << last_off << " > " << s_off << ")" << std::endl;
318 goto done;
319 }
320 }
321
322 if (f_off < s_off && f_len) {
323 uint64_t delta = s_off - f_off;
324 if (delta > f_len)
325 delta = f_len;
326 r = accept_diff_body(fd, pd, f_tag, f_off, delta);
327 if (r < 0) {
328 std::cerr << "rbd: failed to merge diff chunk" << std::endl;
329 goto done;
330 }
331 f_off += delta;
332 f_len -= delta;
333
334 if (!f_len) {
335 f_tag = 0;
336 continue;
337 }
338 }
339 assert(f_off >= s_off);
340
341 if (f_off < s_off + s_len && f_len) {
342 uint64_t delta = s_off + s_len - f_off;
343 if (delta > f_len)
344 delta = f_len;
345 if (f_tag == RBD_DIFF_WRITE) {
346 if (first_stdin) {
347 bufferptr bp = buffer::create(delta);
348 r = safe_read_exact(fd, bp.c_str(), delta);
349 } else {
350 off64_t l = lseek64(fd, delta, SEEK_CUR);
351 r = l < 0 ? -errno : 0;
352 }
353 if (r < 0) {
354 std::cerr << "rbd: failed to skip first diff data" << std::endl;
355 goto done;
356 }
357 }
358 f_off += delta;
359 f_len -= delta;
360
361 if (!f_len) {
362 f_tag = 0;
363 continue;
364 }
365 }
366 assert(f_off >= s_off + s_len);
367 if (s_len) {
368 r = accept_diff_body(sd, pd, s_tag, s_off, s_len);
369 if (r < 0) {
370 std::cerr << "rbd: failed to merge diff chunk" << std::endl;
371 goto done;
372 }
373 s_off += s_len;
374 s_len = 0;
375 s_tag = 0;
376 } else {
377 assert(f_end && s_end);
378 }
379 continue;
380 }
381
382 {//tail
383 __u8 tag = RBD_DIFF_END;
384 bufferlist bl;
385 ::encode(tag, bl);
386 r = bl.write_fd(pd);
387 }
388
389 done:
390 if (pd > 2)
391 close(pd);
392 if (sd > 2)
393 close(sd);
394 if (fd > 2)
395 close(fd);
396
397 if(r < 0) {
398 pc.fail();
399 if (pd > 2)
400 unlink(path);
401 } else
402 pc.finish();
403
404 return r;
405 }
406
407 void get_arguments(po::options_description *positional,
408 po::options_description *options) {
409 positional->add_options()
410 ("diff1-path", "path to first diff (or '-' for stdin)")
411 ("diff2-path", "path to second diff");
412 at::add_path_options(positional, options,
413 "path to merged diff (or '-' for stdout)");
414 at::add_no_progress_option(options);
415 }
416
417 int execute(const po::variables_map &vm) {
418 std::string first_diff = utils::get_positional_argument(vm, 0);
419 if (first_diff.empty()) {
420 std::cerr << "rbd: first diff was not specified" << std::endl;
421 return -EINVAL;
422 }
423
424 std::string second_diff = utils::get_positional_argument(vm, 1);
425 if (second_diff.empty()) {
426 std::cerr << "rbd: second diff was not specified" << std::endl;
427 return -EINVAL;
428 }
429
430 std::string path;
431 int r = utils::get_path(vm, utils::get_positional_argument(vm, 2),
432 &path);
433 if (r < 0) {
434 return r;
435 }
436
437 r = do_merge_diff(first_diff.c_str(), second_diff.c_str(), path.c_str(),
438 vm[at::NO_PROGRESS].as<bool>());
439 if (r < 0) {
440 cerr << "rbd: merge-diff error" << std::endl;
441 return -r;
442 }
443
444 return 0;
445 }
446
447 Shell::Action action(
448 {"merge-diff"}, {}, "Merge two diff exports together.", "",
449 &get_arguments, &execute);
450
451 } // namespace merge_diff
452 } // namespace action
453 } // namespace rbd