sbin/hammer: Minor cleanup for hammer recover
[dragonfly.git] / sbin / hammer / cmd_recover.c
blob9900a3a7b63309cc95e17c70a2a10bb8eaf40b26
1 /*
2 * Copyright (c) 2010 The DragonFly Project. All rights reserved.
4 * This code is derived from software contributed to The DragonFly Project
5 * by Matthew Dillon <dillon@backplane.com>
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in
15 * the documentation and/or other materials provided with the
16 * distribution.
17 * 3. Neither the name of The DragonFly Project nor the names of its
18 * contributors may be used to endorse or promote products derived
19 * from this software without specific, prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
35 #include "hammer.h"
37 struct recover_dict {
38 struct recover_dict *next;
39 struct recover_dict *parent;
40 int64_t obj_id;
41 uint8_t obj_type;
42 uint8_t flags;
43 uint16_t pfs_id;
44 int64_t size;
45 char *name;
48 #define DICTF_MADEDIR 0x01
49 #define DICTF_MADEFILE 0x02
50 #define DICTF_PARENT 0x04 /* parent attached for real */
51 #define DICTF_TRAVERSED 0x80
53 static void recover_top(char *ptr, hammer_off_t offset);
54 static void recover_elm(hammer_btree_leaf_elm_t leaf);
55 static struct recover_dict *get_dict(int64_t obj_id, uint16_t pfs_id);
56 static char *recover_path(struct recover_dict *dict);
57 static void sanitize_string(char *str);
59 static const char *TargetDir;
60 static int CachedFd = -1;
61 static char *CachedPath;
64 * XXX There is a hidden bug here while iterating zone-2 offset as
65 * shown in an example below.
67 * If a volume was once used as HAMMER filesystem which consists of
68 * multiple volumes whose usage has reached beyond the first volume,
69 * and then later re-formatted only using 1 volume, hammer recover is
70 * likely to hit assertion in get_buffer() due to having access to
71 * invalid volume (vol1,2,...) from old filesystem data.
73 * |-----vol0-----|-----vol1-----|-----vol2-----| old filesystem
74 * <-----------------------> used by old filesystem
76 * |-----vol0-----| new filesystem
77 * <-----> used by new filesystem
78 * <-------> unused, invalid data from old filesystem
79 * <-> B-Tree nodes likely to point to vol1
82 void
83 hammer_cmd_recover(const char *target_dir)
85 struct buffer_info *data_buffer;
86 struct volume_info *volume;
87 hammer_off_t off;
88 hammer_off_t off_end;
89 char *ptr;
90 int i;
92 TargetDir = target_dir;
94 if (mkdir(TargetDir, 0777) == -1) {
95 if (errno != EEXIST) {
96 perror("mkdir");
97 exit(1);
101 printf("Running raw scan of HAMMER image, recovering to %s\n",
102 TargetDir);
104 data_buffer = NULL;
105 for (i = 0; i < HAMMER_MAX_VOLUMES; i++) {
106 volume = get_volume(i);
107 if (volume == NULL)
108 continue;
109 printf("Scanning volume %d size %s\n",
110 volume->vol_no, sizetostr(volume->size));
111 off = HAMMER_ENCODE_RAW_BUFFER(volume->vol_no, 0);
112 off_end = off + HAMMER_VOL_BUF_SIZE(volume->ondisk);
115 * It should somehow implement reverse mapping from
116 * zone-2 to zone-X, so the command doesn't just try
117 * every zone-2 offset assuming it's a B-Tree node.
118 * Since we know most big-blocks are not for zone-8,
119 * we don't want to spend extra I/O for other zones
120 * as well as possible misinterpretation of nodes.
122 while (off < off_end) {
123 ptr = get_buffer_data(off, &data_buffer, 0);
124 if (ptr)
125 recover_top(ptr, off);
126 off += HAMMER_BUFSIZE;
129 rel_buffer(data_buffer);
131 if (CachedPath) {
132 free(CachedPath);
133 close(CachedFd);
134 CachedPath = NULL;
135 CachedFd = -1;
140 * Top level recovery processor. Assume the data is a B-Tree node.
141 * If the CRC is good we attempt to process the node, building the
142 * object space and creating the dictionary as we go.
144 static void
145 recover_top(char *ptr, hammer_off_t offset)
147 hammer_node_ondisk_t node;
148 hammer_btree_elm_t elm;
149 int maxcount;
150 int i;
151 int isnode;
152 char buf[HAMMER_BTREE_LEAF_ELMS + 1];
154 for (node = (void *)ptr; (char *)node < ptr + HAMMER_BUFSIZE; ++node) {
155 isnode = hammer_crc_test_btree(node);
156 maxcount = hammer_node_max_elements(node->type);
158 if (DebugOpt) {
159 for (i = 0; i < node->count && i < maxcount; ++i)
160 buf[i] = hammer_elm_btype(&node->elms[i]);
161 buf[i] = '\0';
162 if (!isnode && DebugOpt > 1)
163 printf("%016jx -\n", offset);
164 if (isnode)
165 printf("%016jx %c %d %s\n",
166 offset, node->type, node->count, buf);
168 offset += sizeof(*node);
170 if (isnode && node->type == HAMMER_BTREE_TYPE_LEAF) {
171 for (i = 0; i < node->count && i < maxcount; ++i) {
172 elm = &node->elms[i];
173 if (elm->base.btype == HAMMER_BTREE_TYPE_RECORD)
174 recover_elm(&elm->leaf);
180 static void
181 recover_elm(hammer_btree_leaf_elm_t leaf)
183 struct buffer_info *data_buffer = NULL;
184 struct recover_dict *dict;
185 struct recover_dict *dict2;
186 hammer_data_ondisk_t ondisk;
187 hammer_off_t data_offset;
188 struct stat st;
189 int chunk;
190 int len;
191 int zfill;
192 int64_t file_offset;
193 uint16_t pfs_id;
194 size_t nlen;
195 int fd;
196 char *name;
197 char *path1;
198 char *path2;
201 * Ignore deleted records
203 if (leaf->delete_ts)
204 return;
205 if ((data_offset = leaf->data_offset) != 0)
206 ondisk = get_buffer_data(data_offset, &data_buffer, 0);
207 else
208 ondisk = NULL;
209 if (ondisk == NULL)
210 goto done;
212 len = leaf->data_len;
213 chunk = HAMMER_BUFSIZE - ((int)data_offset & HAMMER_BUFMASK);
214 if (chunk > len)
215 chunk = len;
217 if (len < 0 || len > HAMMER_XBUFSIZE || len > chunk)
218 goto done;
220 pfs_id = lo_to_pfs(leaf->base.localization);
222 dict = get_dict(leaf->base.obj_id, pfs_id);
224 switch(leaf->base.rec_type) {
225 case HAMMER_RECTYPE_INODE:
227 * We found an inode which also tells us where the file
228 * or directory is in the directory hierarchy.
230 if (VerboseOpt) {
231 printf("file %016jx:%05d inode found\n",
232 (uintmax_t)leaf->base.obj_id, pfs_id);
234 path1 = recover_path(dict);
237 * Attach the inode to its parent. This isn't strictly
238 * necessary because the information is also in the
239 * directory entries, but if we do not find the directory
240 * entry this ensures that the files will still be
241 * reasonably well organized in their proper directories.
243 if ((dict->flags & DICTF_PARENT) == 0 &&
244 dict->obj_id != HAMMER_OBJID_ROOT &&
245 ondisk->inode.parent_obj_id != 0) {
246 dict->flags |= DICTF_PARENT;
247 dict->parent = get_dict(ondisk->inode.parent_obj_id,
248 pfs_id);
249 if (dict->parent &&
250 (dict->parent->flags & DICTF_MADEDIR) == 0) {
251 dict->parent->flags |= DICTF_MADEDIR;
252 path2 = recover_path(dict->parent);
253 printf("mkdir %s\n", path2);
254 mkdir(path2, 0777);
255 free(path2);
256 path2 = NULL;
259 if (dict->obj_type == 0)
260 dict->obj_type = ondisk->inode.obj_type;
261 dict->size = ondisk->inode.size;
262 path2 = recover_path(dict);
264 if (lstat(path1, &st) == 0) {
265 if (ondisk->inode.obj_type == HAMMER_OBJTYPE_REGFILE) {
266 truncate(path1, dict->size);
267 /* chmod(path1, 0666); */
269 if (strcmp(path1, path2)) {
270 printf("Rename %s -> %s\n", path1, path2);
271 rename(path1, path2);
273 } else if (ondisk->inode.obj_type == HAMMER_OBJTYPE_REGFILE) {
274 printf("mkinode (file) %s\n", path2);
275 fd = open(path2, O_RDWR|O_CREAT, 0666);
276 if (fd > 0)
277 close(fd);
278 } else if (ondisk->inode.obj_type == HAMMER_OBJTYPE_DIRECTORY) {
279 printf("mkinode (dir) %s\n", path2);
280 mkdir(path2, 0777);
281 dict->flags |= DICTF_MADEDIR;
283 free(path1);
284 free(path2);
285 break;
286 case HAMMER_RECTYPE_DATA:
288 * File record data
290 if (leaf->base.obj_id == 0)
291 break;
292 if (VerboseOpt) {
293 printf("file %016jx:%05d data %016jx,%d\n",
294 (uintmax_t)leaf->base.obj_id,
295 pfs_id,
296 (uintmax_t)leaf->base.key - len,
297 len);
301 * Update the dictionary entry
303 if (dict->obj_type == 0)
304 dict->obj_type = HAMMER_OBJTYPE_REGFILE;
307 * If the parent directory has not been created we
308 * have to create it (typically a PFS%05d)
310 if (dict->parent &&
311 (dict->parent->flags & DICTF_MADEDIR) == 0) {
312 dict->parent->flags |= DICTF_MADEDIR;
313 path2 = recover_path(dict->parent);
314 printf("mkdir %s\n", path2);
315 mkdir(path2, 0777);
316 free(path2);
317 path2 = NULL;
321 * Create the file if necessary, report file creations
323 path1 = recover_path(dict);
324 if (CachedPath && strcmp(CachedPath, path1) == 0) {
325 fd = CachedFd;
326 } else {
327 fd = open(path1, O_CREAT|O_RDWR, 0666);
329 if (fd < 0) {
330 printf("Unable to create %s: %s\n",
331 path1, strerror(errno));
332 free(path1);
333 break;
335 if ((dict->flags & DICTF_MADEFILE) == 0) {
336 dict->flags |= DICTF_MADEFILE;
337 printf("mkfile %s\n", path1);
341 * And write the record. A HAMMER data block is aligned
342 * and may contain trailing zeros after the file EOF. The
343 * inode record is required to get the actual file size.
345 * However, when the inode record is not available
346 * we can do a sparse write and that will get it right
347 * most of the time even if the inode record is never
348 * found.
350 file_offset = (int64_t)leaf->base.key - len;
351 lseek(fd, (off_t)file_offset, SEEK_SET);
352 while (len) {
353 if (dict->size == -1) {
354 for (zfill = chunk - 1; zfill >= 0; --zfill) {
355 if (((char *)ondisk)[zfill])
356 break;
358 ++zfill;
359 } else {
360 zfill = chunk;
363 if (zfill)
364 write(fd, ondisk, zfill);
365 if (zfill < chunk)
366 lseek(fd, chunk - zfill, SEEK_CUR);
368 len -= chunk;
369 data_offset += chunk;
370 file_offset += chunk;
371 ondisk = get_buffer_data(data_offset, &data_buffer, 0);
372 if (ondisk == NULL)
373 break;
374 chunk = HAMMER_BUFSIZE -
375 ((int)data_offset & HAMMER_BUFMASK);
376 if (chunk > len)
377 chunk = len;
379 if (dict->size >= 0 && file_offset > dict->size) {
380 ftruncate(fd, dict->size);
381 /* fchmod(fd, 0666); */
384 if (fd == CachedFd) {
385 free(path1);
386 } else if (CachedPath) {
387 free(CachedPath);
388 close(CachedFd);
389 CachedPath = path1;
390 CachedFd = fd;
391 } else {
392 CachedPath = path1;
393 CachedFd = fd;
395 break;
396 case HAMMER_RECTYPE_DIRENTRY:
397 nlen = len - HAMMER_ENTRY_NAME_OFF;
398 if ((int)nlen < 0) /* illegal length */
399 break;
400 if (ondisk->entry.obj_id == 0 ||
401 ondisk->entry.obj_id == HAMMER_OBJID_ROOT)
402 break;
403 name = malloc(nlen + 1);
404 bcopy(ondisk->entry.name, name, nlen);
405 name[nlen] = 0;
406 sanitize_string(name);
409 * We can't deal with hardlinks so if the object already
410 * has a name assigned to it we just keep using that name.
412 dict2 = get_dict(ondisk->entry.obj_id, pfs_id);
413 path1 = recover_path(dict2);
415 if (dict2->name == NULL)
416 dict2->name = name;
417 else
418 free(name);
421 * Attach dict2 to its directory (dict), create the
422 * directory (dict) if necessary. We must ensure
423 * that the directory entry exists in order to be
424 * able to properly rename() the file without creating
425 * a namespace conflict.
427 if ((dict2->flags & DICTF_PARENT) == 0) {
428 dict2->flags |= DICTF_PARENT;
429 dict2->parent = dict;
430 if ((dict->flags & DICTF_MADEDIR) == 0) {
431 dict->flags |= DICTF_MADEDIR;
432 path2 = recover_path(dict);
433 printf("mkdir %s\n", path2);
434 mkdir(path2, 0777);
435 free(path2);
436 path2 = NULL;
439 path2 = recover_path(dict2);
440 if (strcmp(path1, path2) != 0 && lstat(path1, &st) == 0) {
441 printf("Rename %s -> %s\n", path1, path2);
442 rename(path1, path2);
444 free(path1);
445 free(path2);
447 printf("dir %016jx:%05d entry %016jx \"%s\"\n",
448 (uintmax_t)leaf->base.obj_id,
449 pfs_id,
450 (uintmax_t)ondisk->entry.obj_id,
451 name);
452 break;
453 default:
455 * Ignore any other record types
457 break;
459 done:
460 rel_buffer(data_buffer);
463 #define RD_HSIZE 32768
464 #define RD_HMASK (RD_HSIZE - 1)
466 struct recover_dict *RDHash[RD_HSIZE];
468 static
469 struct recover_dict *
470 get_dict(int64_t obj_id, uint16_t pfs_id)
472 struct recover_dict *dict;
473 int i;
475 if (obj_id == 0)
476 return(NULL);
478 i = crc32(&obj_id, sizeof(obj_id)) & RD_HMASK;
479 for (dict = RDHash[i]; dict; dict = dict->next) {
480 if (dict->obj_id == obj_id &&
481 dict->pfs_id == pfs_id) {
482 break;
485 if (dict == NULL) {
486 dict = malloc(sizeof(*dict));
487 bzero(dict, sizeof(*dict));
488 dict->obj_id = obj_id;
489 dict->pfs_id = pfs_id;
490 dict->next = RDHash[i];
491 dict->size = -1;
492 RDHash[i] = dict;
495 * Always connect dangling dictionary entries to object 1
496 * (the root of the PFS).
498 * DICTF_PARENT will not be set until we know what the
499 * real parent directory object is.
501 if (dict->obj_id != HAMMER_OBJID_ROOT)
502 dict->parent = get_dict(1, pfs_id);
504 return(dict);
507 struct path_info {
508 enum { PI_FIGURE, PI_LOAD } state;
509 uint16_t pfs_id;
510 char *base;
511 char *next;
512 int len;
515 static void recover_path_helper(struct recover_dict *, struct path_info *);
517 static
518 char *
519 recover_path(struct recover_dict *dict)
521 struct path_info info;
523 /* Find info.len first */
524 bzero(&info, sizeof(info));
525 info.state = PI_FIGURE;
526 recover_path_helper(dict, &info);
528 /* Fill in the path */
529 info.pfs_id = dict->pfs_id;
530 info.base = malloc(info.len);
531 info.next = info.base;
532 info.state = PI_LOAD;
533 recover_path_helper(dict, &info);
535 /* Return the path */
536 return(info.base);
539 #define STRLEN_OBJID 22 /* "obj_0x%016jx" */
540 #define STRLEN_PFSID 8 /* "PFS%05d" */
542 static
543 void
544 recover_path_helper(struct recover_dict *dict, struct path_info *info)
547 * Calculate path element length
549 dict->flags |= DICTF_TRAVERSED;
551 switch(info->state) {
552 case PI_FIGURE:
553 if (dict->obj_id == HAMMER_OBJID_ROOT)
554 info->len += STRLEN_PFSID;
555 else if (dict->name)
556 info->len += strlen(dict->name);
557 else
558 info->len += STRLEN_OBJID;
559 ++info->len;
561 if (dict->parent &&
562 (dict->parent->flags & DICTF_TRAVERSED) == 0) {
563 recover_path_helper(dict->parent, info);
564 } else {
565 info->len += strlen(TargetDir) + 1;
567 break;
568 case PI_LOAD:
569 if (dict->parent &&
570 (dict->parent->flags & DICTF_TRAVERSED) == 0) {
571 recover_path_helper(dict->parent, info);
572 } else {
573 strcpy(info->next, TargetDir);
574 info->next += strlen(info->next);
577 *info->next++ = '/';
578 if (dict->obj_id == HAMMER_OBJID_ROOT) {
579 snprintf(info->next, STRLEN_PFSID + 1,
580 "PFS%05d", info->pfs_id);
581 } else if (dict->name) {
582 strcpy(info->next, dict->name);
583 } else {
584 snprintf(info->next, STRLEN_OBJID + 1,
585 "obj_0x%016jx", (uintmax_t)dict->obj_id);
587 info->next += strlen(info->next);
588 break;
590 dict->flags &= ~DICTF_TRAVERSED;
593 static
594 void
595 sanitize_string(char *str)
597 while (*str) {
598 if (!isprint(*str))
599 *str = 'x';
600 ++str;