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