Hi, On Fri, 14 May 2010 20:24:02 -0400, Paul L wrote: > I have my home directory mounted as a nilfs2 partition. Today what > happened was that first I noticed google-chrome reporting it cannot > load user profile, I initially thought it was a google-chrome error. > At the time I was still able to view and modify my home directory. But > then after rebooting the system, my home partition no longer mounts. > I'm using nilfs-2.0.19 and nilfs-utils-2.0.18 with Linux kernel > 2.6.28. > > Here is the error message from dmesg (after turning on debugging > message for nilfs2): > > NILFS nilfs_fill_super: start(silent=0) > NILFS(recovery) nilfs_search_super_root: looking segment > (seg_start=1607680, seg_end=1609727, segnum=785, seg_seq=307637) > NILFS(recovery) load_segment_summary: checking segment > (pseg_start=1608334, full_check=0) > NILFS(recovery) load_segment_summary: done (ret=3) > NILFS(recovery) nilfs_search_super_root: strayed: scan_newer=0, ret=3 > NILFS warning: Segment magic number invalid > NILFS: error searching super root. > NILFS nilfs_fill_super: aborted > NILFS put_nilfs: the_nilfs on bdev mmcblk0p1 was freed > > I then dumped the first and last (backup) copy of the nilfs2 super > block, they are identical, and given below: > > 00000400 02 00 00 00 00 00 34 34 00 01 00 00 A1 6A E9 71 ......44.....j.q > 00000410 A3 F1 DD BE 02 00 00 00 AF 07 00 00 00 00 00 00 ................ > 00000420 00 E0 BF D7 03 00 00 00 01 00 00 00 00 00 00 00 ................ > 00000430 00 08 00 00 05 00 00 00 7C 3D 0A 00 00 00 00 00 ........|=...... > 00000440 8E 8A 18 00 00 00 00 00 B5 B1 04 00 00 00 00 00 ................ > 00000450 00 B8 23 00 00 00 00 00 B9 AF F3 4A 00 00 00 00 ..#........J.... > 00000460 D9 E1 D6 4B 00 00 00 00 49 8F ED 4B 00 00 00 00 ...K....I..K.... > 00000470 37 00 32 00 03 00 01 00 B9 AF F3 4A 00 00 00 00 7.2........J....elp > 00000480 00 4E ED 00 00 00 00 00 00 00 00 00 0B 00 00 00 .N.............. > 00000490 80 00 20 00 C0 00 10 00 13 1C FC 11 D7 43 4C 09 .. ..........CL. > 000004A0 81 64 93 0A F4 54 CF 5E 48 4F 4D 45 00 00 00 00 .d...T.^HOME.... > > > I wonder if there is a fsck tool to help me recover the file system. > Any help is greatly appreciated! > > PS: last time I had a different problem of losing partition info, and > later successfully recovered with the help from people on the list. So > thanks! Now I'm actually backing up my files every two weeks, but > it'll still be great if it can recover and even better if we can trace > the problem. Your filesystem seems to have lost the latest log according to the report. The attached patch may help to recover it. It is revised scan tool for nilfs-utils-2.0.18. After compiling the tool, you can use it like: # cd nilfs-utils-2.0.18 # sbin/fsck/fsck0 <device> The tool will confirm whether to update super blocks if it finds the latest log. You may need to do $ aclocal && autoheader && libtoolize -c --foce && automake -a -c && autoconf $ ./configure before build the tool. With regards, Ryusuke Konishi
>From b1a403ed4a7cb6987052729075255d5d893bebdb Mon Sep 17 00:00:00 2001 From: Ryusuke Konishi <konishi.ryusuke@xxxxxxxxxxxxx> Date: Sat, 15 May 2010 12:33:27 +0900 Subject: [PATCH] nilfs2-utils: add test tool to correct log pointer in super block Signed-off-by: Ryusuke Konishi <konishi.ryusuke@xxxxxxxxxxxxx> --- configure.ac | 1 + sbin/Makefile.am | 2 +- sbin/fsck/Makefile.am | 7 + sbin/fsck/fsck0.nilfs2.c | 1150 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1159 insertions(+), 1 deletions(-) create mode 100644 sbin/fsck/Makefile.am create mode 100644 sbin/fsck/fsck0.nilfs2.c diff --git a/configure.ac b/configure.ac index 2b2dfd7..30e91eb 100644 --- a/configure.ac +++ b/configure.ac @@ -86,6 +86,7 @@ AC_CONFIG_FILES([Makefile man/Makefile sbin/Makefile sbin/cleanerd/Makefile + sbin/fsck/Makefile sbin/mkfs/Makefile sbin/mount/Makefile]) AC_OUTPUT diff --git a/sbin/Makefile.am b/sbin/Makefile.am index 6038b8f..7f2c6db 100644 --- a/sbin/Makefile.am +++ b/sbin/Makefile.am @@ -1,3 +1,3 @@ ## Makefile.am -SUBDIRS = cleanerd mkfs mount +SUBDIRS = cleanerd mkfs mount fsck diff --git a/sbin/fsck/Makefile.am b/sbin/fsck/Makefile.am new file mode 100644 index 0000000..4a2f380 --- /dev/null +++ b/sbin/fsck/Makefile.am @@ -0,0 +1,7 @@ +## Makefile.am + +sbin_PROGRAMS = fsck0.nilfs2 + +fsck0_nilfs2_SOURCES = fsck0.nilfs2.c ../../lib/crc32.c ../mkfs/mkfs.h +fsck0_nilfs2_CFLAGS = -Wall +fsck0_nilfs2_CPPFLAGS = -I$(top_srcdir)/include diff --git a/sbin/fsck/fsck0.nilfs2.c b/sbin/fsck/fsck0.nilfs2.c new file mode 100644 index 0000000..2d3a435 --- /dev/null +++ b/sbin/fsck/fsck0.nilfs2.c @@ -0,0 +1,1150 @@ +/* + * fsck0.nilfs2.c - correct inconsistencies of nilfs2 volume + * + * Licensed under GPLv2: the complete text of the GNU General Public License + * can be found in COPYING file of the nilfs-utils package. + * + * Copyright (C) 2008-2010 Nippon Telegraph and Telephone Corporation. + * Written by Ryusuke Konishi <ryusuke@xxxxxxxx> + */ +#define _LARGEFILE64_SOURCE +#define _XOPEN_SOURCE 600 + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#if HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif /* HAVE_SYS_TYPES_H */ + +#if HAVE_LINUX_TYPES_H +#include <linux/types.h> +#endif /* HAVE_LINUX_TYPES_H */ + +#include <stdio.h> + +#if HAVE_STDLIB_H +#include <stdlib.h> +#endif /* HAVE_STDLIB_H */ + +#if HAVE_FCNTL_H +#include <fcntl.h> +#endif /* HAVE_FCNTL_H */ + +#include <errno.h> + +#if HAVE_UNISTD_H +#include <unistd.h> +#endif /* HAVE_UNISTD_H */ + +#include <malloc.h> + +#if HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif /* HAVE_SYS_IOCTL_H */ + +#if HAVE_STRINGS_H +#include <strings.h> +#endif /* HAVE_SYS_STRINGS_H */ + +#if HAVE_STRING_H +#include <string.h> +#endif /* HAVE_SYS_STRING_H */ + +#include <endian.h> +#include <byteswap.h> + +#include <stdarg.h> +#include <time.h> +#include <assert.h> + +#include "../mkfs/mkfs.h" + +#define MOUNTS "/etc/mtab" +#define LINE_BUFFER_SIZE 256 /* Line buffer size for reading mtab */ +#define MAX_SCAN_SEGMENT 50 /* Maximum number of segments which are + tested for the latest segment search */ +#define SCAN_INDICATOR_SPEED 3 /* Indicator speed (smaller value for + higher speed) */ +#define SCAN_SEGMENT_MASK ((1U << SCAN_INDICATOR_SPEED) - 1) + +#define NILFS_MAX_SB_SIZE 1024 /* Maximum size of super block in bytes */ +#define NILFS_SB_BLOCK_SIZE_SHIFT 10 + +/* fsck return codes */ +#define EXIT_OK 0 +#define EXIT_NONDESTRUCT 1 +#define EXIT_DESTRUCT 2 +#define EXIT_UNCORRECTED 4 +#define EXIT_ERROR 8 +#define EXIT_USAGE 16 +#define EXIT_CANCEL 32 +#define EXIT_LIBRARY 128 + +#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) + +char *progname = NULL; + +struct nilfs_log_ref { + __u64 blocknr; /* start blocknumber */ + __u64 seqnum; /* sequence number */ + __u64 cno; /* checkpoint number */ + __u64 ctime; /* creation time */ +}; + +static int show_version_only = 0; +static int force = 0; +static int verbose = 0; + +static int devfd = -1; +static int blocksize; +static __u32 crc_seed; +static __u32 blocks_per_segment; +static __u64 first_data_block; +static __u64 nsegments; +static __u16 checkpoint_size; +static __u16 sb_bytes; + +static int first_checkpoint_offset; +static int ncheckpoints_per_block; + +/* + * Generic routines + */ +void die(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + fprintf(stderr, "%s: ", progname); + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); + va_end(args); + + if (devfd >= 0) + close(devfd); + exit(EXIT_ERROR); +} + +static void (*nilfs_shrink)(void) = NULL; + +void *nilfs_malloc(size_t size) +{ + void *p = malloc(size); + if (!p) { + if (nilfs_shrink) + nilfs_shrink(); + p = malloc(size); + if (!p) + die("memory allocation failure"); + } + return p; +} + +static inline void *nilfs_zalloc(size_t size) +{ + void *p = nilfs_malloc(size); + memset(p, 0, size); + return p; +} + +/* + * Block buffer + */ +static void *block_buffer = NULL; + +static void destroy_block_buffer(void) +{ + if (block_buffer) { + free(block_buffer); + block_buffer = NULL; + } +} + +static void init_block_buffer(void) +{ + block_buffer = nilfs_malloc(blocksize); + atexit(destroy_block_buffer); +} + +static void read_block(int fd, __u64 blocknr, void *buf, + unsigned long size) +{ + if (lseek64(fd, blocknr * blocksize, SEEK_SET) < 0 || + read(fd, buf, size) < size) + die("cannot read block (blocknr = %llu)", + (unsigned long long)blocknr); +} + +static inline __u64 segment_start_blocknr(unsigned long segnum) +{ + return segnum > 0 ? blocks_per_segment * segnum : first_data_block; +} + +static int log_is_valid(int fd, __u64 log_start, + struct nilfs_segment_summary *ss) +{ + __u32 crc, sum; + int offset = sizeof(ss->ss_datasum); + int nblocks = le32_to_cpu(ss->ss_nblocks); + __u64 blocknr = log_start; + + if (le32_to_cpu(ss->ss_magic) != NILFS_SEGSUM_MAGIC) + return 0; + + if (nblocks == 0 || nblocks > blocks_per_segment) + return 0; + + sum = le32_to_cpu(ss->ss_datasum); + + read_block(fd, blocknr++, block_buffer, blocksize); + crc = nilfs_crc32(crc_seed, block_buffer + offset, blocksize - offset); + while (--nblocks > 0) { + read_block(fd, blocknr++, block_buffer, blocksize); + crc = nilfs_crc32(crc, block_buffer, blocksize); + } + return crc == sum; +} + +/* + * Routines to handle log (partial segment) list + */ +struct nilfs_list { /* use struct list_head in kernel land */ + struct nilfs_list *prev; + struct nilfs_list *next; +}; + +static inline void nilfs_list_init(struct nilfs_list *list) +{ + list->prev = list->next = list; +} + +static inline int nilfs_list_empty(struct nilfs_list *list) +{ + return list->next == list; +} + +static inline void nilfs_list_del(struct nilfs_list *list) +{ + struct nilfs_list *p = list->prev, *n = list->next; + + p->next = n; + n->prev = p; + list->prev = list->next = list; +} + +static inline void nilfs_list_add(struct nilfs_list *list, + struct nilfs_list *item) +{ + struct nilfs_list *p = list->prev; + + item->prev = p; + item->next = list; + p->next = list->prev = item; +} + +/* log (partial segment) information */ +struct nilfs_log_info { + struct nilfs_list list; + __u64 log_start; /* start blocknr */ + __u32 nblocks; + struct nilfs_segment_summary segsum; /* on-disk log header */ + __u16 flags; +}; + +static inline struct nilfs_log_info *nilfs_log_list_entry(struct nilfs_list *p) +{ + return (void *)p - offsetof(struct nilfs_log_info, list); +} + +struct nilfs_log_info *new_log_info(__u64 blocknr) +{ + struct nilfs_log_info *loginfo = nilfs_zalloc(sizeof(*loginfo)); + + loginfo->log_start = blocknr; + nilfs_list_init(&loginfo->list); + return loginfo; +} + +static void dispose_log_list(struct nilfs_list *list) +{ + struct nilfs_list *p, *n; + + for (p = list->next; n = p->next, p != list; p = n) { + nilfs_list_del(p); + free(nilfs_log_list_entry(p)); + } +} + +/* + * Segment information + */ +struct nilfs_segment_info { + struct nilfs_list list; + struct nilfs_list log_list; /* partial segment list */ + __u64 seg_start; /* start blocknr of the segment */ + __u64 next; /* pointer to the next segment */ + __u64 segseq; /* sequence number of the segment */ + unsigned long segnum; /* the number of the segment */ + int nlogs; /* number of logs */ + int refcnt; +}; + +static struct nilfs_list segment_cache; + +static struct nilfs_segment_info * +nilfs_segment_list_entry(struct nilfs_list *p) +{ + return (void *)p - offsetof(struct nilfs_segment_info, list); +} + +struct nilfs_segment_info *new_segment_info(unsigned long segnum) +{ + struct nilfs_segment_info *seginfo; + + seginfo = nilfs_zalloc(sizeof(*seginfo)); + seginfo->segnum = segnum; + seginfo->seg_start = segment_start_blocknr(segnum); + seginfo->refcnt = 1; + + nilfs_list_init(&seginfo->log_list); + nilfs_list_add(&segment_cache, &seginfo->list); + return seginfo; +} + +void destroy_segment_info(struct nilfs_segment_info *seginfo) +{ + nilfs_list_del(&seginfo->list); + dispose_log_list(&seginfo->log_list); + free(seginfo); +} + +static inline struct nilfs_segment_info * +get_segment_info(struct nilfs_segment_info *seginfo) +{ + seginfo->refcnt++; + return seginfo; +} + +static inline void put_segment_info(struct nilfs_segment_info *seginfo) +{ + assert(seginfo->refcnt > 0); + seginfo->refcnt--; +} + +/* + * Segment cache + */ +void destroy_segment_cache(void) +{ + struct nilfs_list *p, *n; + + for (p = segment_cache.next; n = p->next, p != &segment_cache; p = n) { + destroy_segment_info(nilfs_segment_list_entry(p)); + } +} + +void shrink_segment_cache(void) +{ + struct nilfs_list *p, *n; + struct nilfs_segment_info *seginfo; + + for (p = segment_cache.next; n = p->next, p != &segment_cache; p = n) { + seginfo = nilfs_segment_list_entry(p); + if (seginfo->refcnt == 0) + destroy_segment_info(seginfo); + } +} + +void init_segment_cache(void) +{ + nilfs_list_init(&segment_cache); + nilfs_shrink = shrink_segment_cache; + atexit(destroy_segment_cache); +} + +struct nilfs_segment_info *lookup_segment(unsigned long segnum) +{ + struct nilfs_segment_info *seginfo; + struct nilfs_list *p; + + for (p = segment_cache.next; p != &segment_cache; p = p->next) { + seginfo = nilfs_segment_list_entry(p); + if (seginfo->segnum == segnum) { + get_segment_info(seginfo); + return seginfo; + } + } + return NULL; +} + +struct nilfs_segment_info *load_segment(int fd, unsigned long segnum) +{ + struct nilfs_segment_info *seginfo; + struct nilfs_log_info *loginfo; + struct nilfs_segment_summary *ss; + __u64 blocknr, end; + + seginfo = lookup_segment(segnum); + if (seginfo) + return seginfo; + + seginfo = new_segment_info(segnum); + blocknr = seginfo->seg_start; + + posix_fadvise(fd, blocknr * blocksize, blocks_per_segment * blocksize, + POSIX_FADV_WILLNEED); + + loginfo = new_log_info(blocknr); + nilfs_list_add(&seginfo->log_list, &loginfo->list); + + ss = &loginfo->segsum; + read_block(fd, blocknr, ss, sizeof(*ss)); + + if (!log_is_valid(fd, blocknr, ss)) { + put_segment_info(seginfo); + fprintf(stderr, "empty or bad segment: " + "segnum = %lu, blocknr = %llu\n", segnum, + (unsigned long long)segment_start_blocknr(segnum)); + return NULL; /* no valid partial segment found */ + } + + seginfo->segseq = le64_to_cpu(ss->ss_seq); + seginfo->next = le64_to_cpu(ss->ss_next); + + end = blocknr + blocks_per_segment; + do { + seginfo->nlogs++; + + loginfo->nblocks = le32_to_cpu(ss->ss_nblocks); + loginfo->flags = le16_to_cpu(ss->ss_flags); + + blocknr += loginfo->nblocks; + if (blocknr >= end) + return seginfo; + + loginfo = new_log_info(blocknr); + nilfs_list_add(&seginfo->log_list, &loginfo->list); + + ss = &loginfo->segsum; + read_block(fd, blocknr, ss, sizeof(*ss)); + + } while (log_is_valid(fd, blocknr, ss) && + le64_to_cpu(ss->ss_seq) == seginfo->segseq); + + nilfs_list_del(&loginfo->list); + free(loginfo); + + return seginfo; +} + +/* + * Operations on segment_info structure + */ +struct nilfs_log_info *lookup_log(struct nilfs_segment_info *seginfo, + __u64 blocknr) +{ + struct nilfs_log_info *loginfo; + struct nilfs_list *p; + + for (p = seginfo->log_list.next; p != &seginfo->log_list; + p = p->next) { + loginfo = nilfs_log_list_entry(p); + if (loginfo->log_start == blocknr) + return loginfo; + } + return NULL; +} + +struct nilfs_log_info *first_log(struct nilfs_segment_info *seginfo) +{ + return nilfs_list_empty(&seginfo->log_list) ? NULL : + nilfs_log_list_entry(seginfo->log_list.next); +} + +struct nilfs_log_info *last_log(struct nilfs_segment_info *seginfo) +{ + return nilfs_list_empty(&seginfo->log_list) ? NULL : + nilfs_log_list_entry(seginfo->log_list.prev); +} + +struct nilfs_log_info *next_log(struct nilfs_segment_info *seginfo, + struct nilfs_log_info *loginfo) +{ + return loginfo->list.next == &seginfo->log_list ? NULL : + nilfs_log_list_entry(loginfo->list.next); +} + +struct nilfs_log_info *prev_log(struct nilfs_segment_info *seginfo, + struct nilfs_log_info *loginfo) +{ + return loginfo->list.prev == &seginfo->log_list ? NULL : + nilfs_log_list_entry(loginfo->list.prev); +} + +struct nilfs_log_info * +lookup_last_super_root(struct nilfs_segment_info *seginfo) +{ + struct nilfs_log_info *loginfo; + + for (loginfo = last_log(seginfo); loginfo != NULL; + loginfo = prev_log(seginfo, loginfo)) { + if (loginfo->flags & NILFS_SS_SR) + return loginfo; + } + return NULL; +} + +unsigned long log_length(struct nilfs_segment_info *seginfo) +{ + return nilfs_list_empty(&seginfo->log_list) ? 0 : + nilfs_log_list_entry(seginfo->log_list.prev)->log_start - + seginfo->seg_start + + nilfs_log_list_entry(seginfo->log_list.prev)->nblocks; +} + +/* + * Routines to get latest checkpoint number + */ +static __u64 find_latest_checkpoint(int fd, __u64 cpblocknr, __u64 blkoff) +{ + struct nilfs_checkpoint *cp; + int i, ncp; + __u64 cno = 0; + + read_block(fd, cpblocknr, block_buffer, blocksize); + if (blkoff == 0) { + cp = block_buffer + first_checkpoint_offset * checkpoint_size; + ncp = ncheckpoints_per_block - first_checkpoint_offset; + } else { + cp = block_buffer; + ncp = ncheckpoints_per_block; + } + + for (i = 0; i < ncp; i++, cp = (void *)cp + checkpoint_size) { + if (!nilfs_checkpoint_invalid(cp) && + le64_to_cpu(cp->cp_cno) > cno) + cno = le64_to_cpu(cp->cp_cno); + } + return cno; +} + +static void *next_ss_entry(int fd, __u64 *blocknrp, + unsigned *offsetp, unsigned entry_size) +{ + void *p; + + if (*offsetp + entry_size > blocksize) { + (*blocknrp)++; + read_block(fd, *blocknrp, block_buffer, blocksize); + *offsetp = 0; + } + p = block_buffer + *offsetp; + (*offsetp) += entry_size; + return p; +} + +static __u64 get_latest_cno(int fd, __u64 log_start) +{ + struct nilfs_segment_summary *ss; + struct nilfs_finfo *finfo; + __u32 nfinfo; + __u32 nblocks, ndatablk, nnodeblk; + __u64 ino; + __u64 latest_cno = 0, cno; + __u64 blocknr = log_start, fblocknr; + unsigned offset; + int i, j; + + read_block(fd, blocknr, block_buffer, blocksize); + ss = block_buffer; + nfinfo = le32_to_cpu(ss->ss_nfinfo); + offset = le16_to_cpu(ss->ss_bytes); + fblocknr = blocknr + DIV_ROUND_UP(le32_to_cpu(ss->ss_sumbytes), + blocksize); + + for (i = 0; i < nfinfo; i++) { + finfo = next_ss_entry(fd, &blocknr, &offset, sizeof(*finfo)); + + nblocks = le32_to_cpu(finfo->fi_nblocks); + ndatablk = le32_to_cpu(finfo->fi_ndatablk); + nnodeblk = nblocks - ndatablk; + ino = le64_to_cpu(finfo->fi_ino); + + if (ino == NILFS_DAT_INO) { + __le64 *blkoff; + struct nilfs_binfo_dat *binfo_dat; + + for (j = 0; j < ndatablk; j++, fblocknr++) { + blkoff = next_ss_entry(fd, &blocknr, + &offset, + sizeof(*blkoff)); + } + for (j = 0; j < nnodeblk; j++, fblocknr++) { + binfo_dat = next_ss_entry(fd, &blocknr, + &offset, + sizeof(*binfo_dat)); + } + } else { + struct nilfs_binfo_v *binfo_v; + __le64 *vblocknr; + + for (j = 0; j < ndatablk; j++, fblocknr++) { + binfo_v = next_ss_entry(fd, &blocknr, + &offset, + sizeof(*binfo_v)); + } + if (ino == NILFS_CPFILE_INO && ndatablk > 0) { + cno = find_latest_checkpoint( + fd, fblocknr - 1, + le64_to_cpu(binfo_v->bi_blkoff)); + if (cno > latest_cno) + latest_cno = cno; + } + for (j = 0; j < nnodeblk; j++, fblocknr++) { + vblocknr = next_ss_entry(fd, &blocknr, + &offset, + sizeof(*vblocknr)); + } + } + } + + return latest_cno; +} + +__u64 find_latest_cno_in_logical_segment(int fd, + struct nilfs_segment_info *seginfo, + struct nilfs_log_info *start) +{ + struct nilfs_log_info *loginfo = start ? : last_log(seginfo); + __u64 cno, latest_cno = 0; + __u64 seq; + int i = 0; + + if (loginfo == NULL) + return 0; + + get_segment_info(seginfo); + do { + cno = get_latest_cno(fd, loginfo->log_start); + if (cno > latest_cno) + latest_cno = cno; + + if (loginfo->flags & NILFS_SS_LOGBGN) + break; + + loginfo = prev_log(seginfo, loginfo); + if (loginfo == NULL) { + unsigned long segnum = seginfo->segnum; + + if (++i > MAX_SCAN_SEGMENT) + break; + segnum = (segnum == 0) ? nsegments - 1 : segnum - 1; + seq = seginfo->segseq; + + put_segment_info(seginfo); + seginfo = load_segment(fd, segnum); + + if (!seginfo || seginfo->segseq != seq - 1) + break; + loginfo = last_log(seginfo); + } + } while (loginfo != NULL && !(loginfo->flags & NILFS_SS_LOGEND)); + + if (seginfo) + put_segment_info(seginfo); + return latest_cno; +} + +void print_log_message(const struct nilfs_log_ref *log_ref, + const char *fmt, ...) +{ + const char *cp; + va_list args; + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + fprintf(stderr, ": blocknr = %llu\n", + (unsigned long long)log_ref->blocknr); + + for (cp = fmt; *cp == ' '; cp++) + fputc(' ', stderr); + fprintf(stderr, " segnum = %lu, seq = %llu, cno=%llu\n", + (unsigned long)log_ref->blocknr / blocks_per_segment, + (unsigned long long)log_ref->seqnum, + (unsigned long long)log_ref->cno); + if (log_ref->ctime) { + char tmbuf[LINE_BUFFER_SIZE]; + struct tm tm; + time_t t = (time_t)le64_to_cpu(log_ref->ctime); + + localtime_r(&t, &tm); + strftime(tmbuf, LINE_BUFFER_SIZE, "%F %T", &tm); + for (cp = fmt; *cp == ' '; cp++) + fputc(' ', stderr); + fprintf(stderr, " creation time = %s\n", tmbuf); + } + va_end(args); +} + +struct nilfs_log_info * +find_latest_super_root(int fd, unsigned long segnum, __u64 blocknr, + struct nilfs_segment_info **seginfop) +{ + struct nilfs_segment_info *seginfo; + struct nilfs_segment_info *seginfo_sr = NULL; + /* seginfo which has the last super root */ + struct nilfs_log_info *log_sr = NULL; + int cont = 0, invert = 0; + int i; + + seginfo = load_segment(fd, segnum); + if (seginfo) { + log_sr = lookup_last_super_root(seginfo); + if (log_sr) + seginfo_sr = get_segment_info(seginfo); + + if (blocknr < seginfo->seg_start + log_length(seginfo)) + cont = 1; + } + + for (i = 0; i < MAX_SCAN_SEGMENT; i++) { + struct nilfs_segment_info *seginfo2; + + /* + * Look into the previous segment. + * + * This code depends on the current GC policy; discontinuously + * allocated segments are not supported. + */ + if (!(i & SCAN_SEGMENT_MASK)) + fputc('.', stderr); + segnum = (segnum == 0) ? nsegments - 1 : segnum - 1; + + seginfo2 = load_segment(fd, segnum); + if (!seginfo2) { + if (log_sr && cont) { + log_sr = NULL; + put_segment_info(seginfo_sr); + seginfo_sr = NULL; + } + cont = 0; + if (seginfo) { + put_segment_info(seginfo); + seginfo = NULL; + } + continue; + } + + if (!seginfo) { + seginfo = seginfo2; + seginfo2 = NULL; + + if (log_sr) + put_segment_info(seginfo_sr); + log_sr = lookup_last_super_root(seginfo); + if (log_sr) + seginfo_sr = get_segment_info(seginfo); + continue; + } + + if (seginfo2->segseq + 1 != seginfo->segseq) + cont = 0; + + if (seginfo2->segseq > seginfo->segseq) { + invert++; + if (log_sr) { + log_sr = NULL; + put_segment_info(seginfo_sr); + seginfo_sr = NULL; + } + } + if (invert && !log_sr) { + log_sr = lookup_last_super_root(seginfo2); + if (log_sr) { + put_segment_info(seginfo); + *seginfop = seginfo2; + fputc('\n', stderr); + return log_sr; /* latest segment was found */ + } + } + + if (!cont && !log_sr) { + log_sr = lookup_last_super_root(seginfo2); + if (log_sr) + seginfo_sr = get_segment_info(seginfo2); + } + + put_segment_info(seginfo); + seginfo = seginfo2; + seginfo2 = NULL; + } + fputc('\n', stderr); + if (seginfo) + put_segment_info(seginfo); + + if (log_sr && !cont) { + *seginfop = seginfo_sr; + return log_sr; /* regard second-ranking candidate + as the latest segment */ + } + if (seginfo_sr) + put_segment_info(seginfo_sr); + return NULL; +} + +static void check_mount(int fd, const char *device) +{ + FILE *fp; + char line[LINE_BUFFER_SIZE]; + + fp = fopen(MOUNTS, "r"); + if (fp == NULL) + die("cannot open %s!", MOUNTS); + + while (fgets(line, LINE_BUFFER_SIZE, fp) != NULL) { + if (strncmp(strtok(line, " "), device, strlen(device)) == 0) { + fclose(fp); + die("%s is currently mounted.", device); + } + } + fclose(fp); +} + +static int nilfs_sb_is_valid(struct nilfs_super_block *sbp, int check_crc) +{ + __le32 sum; + __u32 seed, crc; + + if (le16_to_cpu(sbp->s_magic) != NILFS_SUPER_MAGIC) + return 0; + if (le16_to_cpu(sbp->s_bytes) > NILFS_MAX_SB_SIZE) + return 0; + if (!check_crc) + return 1; + + seed = le32_to_cpu(sbp->s_crc_seed); + sum = sbp->s_sum; + sbp->s_sum = 0; + crc = crc32_le(seed, (unsigned char *)sbp, le16_to_cpu(sbp->s_bytes)); + sbp->s_sum = sum; + return crc == le32_to_cpu(sum); +} + +static struct nilfs_super_block *nilfs_read_super_block(int fd) +{ + struct nilfs_super_block *sbp[2]; + __u64 devsize, sb2_offset; + + sbp[0] = malloc(NILFS_MAX_SB_SIZE); + sbp[1] = malloc(NILFS_MAX_SB_SIZE); + if (sbp[0] == NULL || sbp[1] == NULL) + goto failed; + + if (ioctl(fd, BLKGETSIZE64, &devsize) != 0) + goto failed; + + if (lseek64(fd, NILFS_SB_OFFSET_BYTES, SEEK_SET) < 0 || + read(fd, sbp[0], NILFS_MAX_SB_SIZE) < 0 || + !nilfs_sb_is_valid(sbp[0], 0)) { + free(sbp[0]); + sbp[0] = NULL; + } + + sb2_offset = NILFS_SB2_OFFSET_BYTES(devsize); + if (lseek64(fd, sb2_offset, SEEK_SET) < 0 || + read(fd, sbp[1], NILFS_MAX_SB_SIZE) < 0 || + !nilfs_sb_is_valid(sbp[1], 0)) + goto sb2_failed; + + if (sb2_offset < + (le64_to_cpu(sbp[1]->s_nsegments) * + le32_to_cpu(sbp[1]->s_blocks_per_segment)) << + (le32_to_cpu(sbp[1]->s_log_block_size) + + NILFS_SB_BLOCK_SIZE_SHIFT)) + goto sb2_failed; + + sb2_done: + if (!sbp[0]) { + sbp[0] = sbp[1]; + sbp[1] = NULL; + } + + if (sbp[1] && + le64_to_cpu(sbp[1]->s_last_cno) > le64_to_cpu(sbp[0]->s_last_cno)) { + free(sbp[0]); + return sbp[1]; + } else if (sbp[0]) { + free(sbp[1]); + return sbp[0]; + } + + failed: + free(sbp[0]); /* free(NULL) is just ignored */ + free(sbp[1]); + return NULL; + + sb2_failed: + free(sbp[1]); + sbp[1] = NULL; + goto sb2_done; +} + +static void read_sb_info(struct nilfs_super_block *sbp) +{ + char tmbuf[LINE_BUFFER_SIZE]; + struct tm tm; + time_t t; + + fprintf(stderr, "Super-block:\n"); + + crc_seed = le32_to_cpu(sbp->s_crc_seed); + + fprintf(stderr, " revision = %d.%d\n", + le32_to_cpu(sbp->s_rev_level), + le16_to_cpu(sbp->s_minor_rev_level)); + + blocksize = 1 << (le32_to_cpu(sbp->s_log_block_size) + 10); + blocks_per_segment = le32_to_cpu(sbp->s_blocks_per_segment); + first_data_block = le64_to_cpu(sbp->s_first_data_block); + nsegments = le64_to_cpu(sbp->s_nsegments); + checkpoint_size = le16_to_cpu(sbp->s_checkpoint_size); + sb_bytes = le16_to_cpu(sbp->s_bytes); + + first_checkpoint_offset = + DIV_ROUND_UP(sizeof(struct nilfs_cpfile_header), + checkpoint_size); + ncheckpoints_per_block = blocksize / checkpoint_size; + + t = (time_t)le64_to_cpu(sbp->s_wtime); + localtime_r(&t, &tm); + strftime(tmbuf, LINE_BUFFER_SIZE, "%F %T", &tm); + + fprintf(stderr, " blocksize = %d\n", blocksize); + fprintf(stderr, " write time = %s\n", tmbuf); +} + +static void commit_super_block(struct nilfs_super_block *sbp, + const struct nilfs_log_ref *log_ref) +{ + __u32 sbsum; + + sbp->s_last_pseg = cpu_to_le64(log_ref->blocknr); + sbp->s_last_seq = cpu_to_le64(log_ref->seqnum); + sbp->s_last_cno = cpu_to_le64(log_ref->cno); + + sbp->s_wtime = cpu_to_le64(time(NULL)); + sbp->s_state = cpu_to_le16(le16_to_cpu(sbp->s_state) & ~NILFS_VALID_FS); + + /* fill in crc */ + sbp->s_sum = 0; + sbsum = nilfs_crc32(crc_seed, (unsigned char *)sbp, sb_bytes); + sbp->s_sum = cpu_to_le32(sbsum); +} + +static int nilfs_write_super_block(int fd, struct nilfs_super_block *sbp) +{ + __u64 devsize, sb2_offset; + int ret = -1; + + if (ioctl(fd, BLKGETSIZE64, &devsize) != 0) + return -1; + + if (lseek64(fd, NILFS_SB_OFFSET_BYTES, SEEK_SET) < 0 || + write(fd, sbp, sb_bytes) < sb_bytes || + fsync(fd) < 0) + fprintf(stderr, "failed to write primary super block"); + else + ret = 0; + + sb2_offset = NILFS_SB2_OFFSET_BYTES(devsize); + if (sb2_offset < (__u64)nsegments * blocks_per_segment * blocksize) + return ret; + + if (lseek64(fd, sb2_offset, SEEK_SET) < 0 || + write(fd, sbp, sb_bytes) < sb_bytes || + fsync(fd) < 0) + fprintf(stderr, + "failed to write secondary super block"); + else + ret = 0; + + return ret; +} + +static int test_latest_log(int fd, struct nilfs_log_ref *log_ref) +{ + struct nilfs_segment_info *seginfo; + struct nilfs_log_info *loginfo; + unsigned long segnum; + int ret = -1; + + /* + * check the log the super block points to + */ + segnum = log_ref->blocknr / blocks_per_segment; + seginfo = load_segment(fd, segnum); + if (seginfo) { + loginfo = lookup_log(seginfo, log_ref->blocknr); + if (loginfo && seginfo->segseq == log_ref->seqnum && + loginfo->flags & NILFS_SS_SR) { + log_ref->ctime = + le64_to_cpu(loginfo->segsum.ss_create); + print_log_message(log_ref, + "A valid log is pointed to by " + "superblock (No change needed)"); + ret = 0; + } + } + put_segment_info(seginfo); + return ret; +} + +static void nilfs_do_rollback(int fd, struct nilfs_log_ref *log_ref) +{ + struct nilfs_segment_info *seginfo; + struct nilfs_log_info *loginfo; + unsigned long segnum; + + /* + * check logs in the current and prior full segments. + */ + segnum = log_ref->blocknr / blocks_per_segment; + loginfo = find_latest_super_root(fd, segnum, log_ref->blocknr, + &seginfo); + if (!loginfo) + die("Cannot find super root"); + + log_ref->blocknr = loginfo->log_start; + log_ref->seqnum = seginfo->segseq; + log_ref->ctime = le64_to_cpu(loginfo->segsum.ss_create); + + fprintf(stderr, "Searching the latest checkpoint.\n"); + log_ref->cno = find_latest_cno_in_logical_segment(fd, seginfo, loginfo); + if (log_ref->cno == 0) + die("Cannot identify the latest checkpoint"); + + print_log_message(log_ref, "Selected log"); +} + +static void nilfs_fsck(const char *device) +{ + struct nilfs_super_block *sbp; + struct nilfs_log_ref log_ref; + int clean, ret; + int c; + + if ((devfd = open(device, O_RDONLY | O_LARGEFILE)) < 0) + die("cannot open device %s", device); + + check_mount(devfd, device); + + sbp = nilfs_read_super_block(devfd); + if (!sbp) + die("cannot read super block (device=%s)", device); + + read_sb_info(sbp); + + log_ref.blocknr = le64_to_cpu(sbp->s_last_pseg); + log_ref.seqnum = le64_to_cpu(sbp->s_last_seq); + log_ref.cno = le64_to_cpu(sbp->s_last_cno); + log_ref.ctime = 0; + print_log_message(&log_ref, " indicated log"); + fputc('\n', stderr); + + if (le16_to_cpu(sbp->s_state) & NILFS_VALID_FS) { + fprintf(stderr, "Clean FS.\n"); + clean = 1; + } else { + fprintf(stderr, "Unclean FS.\n"); + clean = 0; + } + + init_block_buffer(); + init_segment_cache(); + + ret = test_latest_log(devfd, &log_ref); + if (ret < 0) { + fprintf(stderr, "The latest log is lost. " + "Trying rollback recovery..\n"); + clean = 0; + nilfs_do_rollback(devfd, &log_ref); + } + destroy_segment_cache(); + destroy_block_buffer(); + + if (!ret) + goto out; + + /* + * Reopen device to update superblock + */ + close(devfd); + devfd = -1; + if ((devfd = open(device, O_RDWR | O_LARGEFILE)) < 0) + die("cannot open device %s in read/write mode", device); + + fprintf(stderr, "Do you wish to overwrite super block (y/N)? "); + if ((c = getchar()) == 'y' || c == 'Y') { + commit_super_block(sbp, &log_ref); + if (nilfs_write_super_block(devfd, sbp) < 0) + die("couldn't update super block (device=%s)", device); + } + out: + if (!clean) + fprintf(stderr, "Recovery will complete on mount.\n"); + free(sbp); + close(devfd); +} + +static void usage(void) +{ + fprintf(stderr, "Usage: %s [-fv] device\n", progname); + exit(EXIT_USAGE); +} + +static void parse_options(int argc, char *argv[]) +{ + int c; + + while ((c = getopt(argc, argv, "fvV")) != EOF) { + switch (c) { + case 'f': + force = 1; + break; + case 'v': + verbose = 1; + break; + case 'V': + show_version_only = 1; + break; + default: + usage(); + } + } + if (show_version_only) + return; + if (optind == argc) + usage(); +} + +int main(int argc, char *argv[]) +{ + char *device; + + if ((progname = strrchr(argv[0], '/')) != NULL) + progname++; + else + progname = argv[0]; + + parse_options(argc, argv); + if (show_version_only) { + fprintf(stderr, "%s version %s\n", progname, PACKAGE_VERSION); + exit(EXIT_OK); + } + device = argv[optind]; + nilfs_fsck(device); + + exit(EXIT_OK); +} -- 1.6.3.4