Fix some bugs when testing opensds ansible
[stor4nfv.git] / src / 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