Fixed <dst_dev> size requirements.
[badblock-guess.git] / badblock-guess.c
index 56ed478..eb1642c 100644 (file)
@@ -1,5 +1,23 @@
 /* $Id$ */
 
+/*
+ * badblock-guess: Quickly recover most of the data from a damaged disk
+ * Copyright (C) 2002  Jan Kratochvil <short@ucw.cz>
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; exactly version 2 of the License required
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
 
 #define _LARGEFILE_SOURCE
 #define _LARGEFILE64_SOURCE
@@ -38,8 +56,8 @@ static void finish(void) G_GNUC_NORETURN;
 
 static int src_fd=-1,dst_fd=-1;
 static const char *src_name,*dst_name;
-static ext2_loff_t src_len;    /* in blocks! */
-static blk_t src_lenblk;       /* ==src_len, just a different type */
+static ext2_loff_t src_len,dst_len;    /* in blocks! */
+static blk_t src_lenblk,dst_lenblk;    /* ==src_len,==dst_len, just a different type */
 
 static ext2_loff_t stat_lastread,stat_todo,stat_bads,stat_largest,stat_todo_hunks;
 
@@ -157,13 +175,13 @@ struct range *key,*neigh_left,*neigh_right;
        if (start>=end)
                return;
        key=node_build(start,end);
-       if ((neigh_left =g_tree_search(bad_tree,(GSearchFunc)search_end  ,&start))) {
+       if ((neigh_left =g_tree_search(bad_tree,(GCompareFunc)search_end  ,&start))) {
                g_assert(neigh_left->end==key->start);
                key->start=neigh_left->start;
                g_tree_remove(bad_tree,neigh_left);
                g_mem_chunk_free(node_memchunk,neigh_left);
                }
-       if ((neigh_right=g_tree_search(bad_tree,(GSearchFunc)search_start,&end  ))) {
+       if ((neigh_right=g_tree_search(bad_tree,(GCompareFunc)search_start,&end  ))) {
                g_assert(neigh_right->start==key->end);
                key->end=neigh_right->end;
                g_tree_remove(bad_tree,neigh_right);
@@ -256,6 +274,13 @@ ext2_loff_t gotext2_loff;
                exit(EXIT_FAILURE);
                }
        if (len!=(gotssize=write(dst_fd,buf,len))) {
+               /* see README/"Linux kernel flaw" */
+               /* no "start==src_len-1" checking here, we may write larger blocks! */
+               if (gotssize==((src_len&~1)-start)*BLOCK
+                               /* -1 instead of 0 is given when no bytes were written */
+                               || (gotssize==-1 && start==src_len-1 && end==src_len))
+                       return; /* suppress error message */
+
                fprintf(stderr,"write(\"%s\",@%llu-@%llu=%llu=%lluB)=%lld: %m\n",dst_name,
                                (unsigned long long)start,
                                (unsigned long long)end,
@@ -266,11 +291,29 @@ ext2_loff_t gotext2_loff;
                }
 }
 
+static void range_zero(ext2_loff_t start,ext2_loff_t end)
+{
+static const unsigned char buf_zero[BUF_ZERO_BLOCKS*BLOCK];
+
+       g_return_if_fail(start<end);
+
+       while (start<end) {
+               ext2_loff_t mid=((end-start)>BUF_ZERO_BLOCKS ? start+BUF_ZERO_BLOCKS : end);
+               g_assert(mid>=start);
+               g_assert(mid<=end);
+
+               write_dst(start,mid,buf_zero);
+
+               start=mid;
+               }
+}
+
 static void process(ext2_loff_t start,ext2_loff_t end)
 {
 unsigned char block_buf[BLOCK];
 ext2_loff_t bads;
 ext2_loff_t gotext2_loff;
+ssize_t gotssize;
 
        g_return_if_fail(start<end);
        g_return_if_fail(end<=src_len);
@@ -290,9 +333,18 @@ restart:   /* continues when the block was successfuly read */
                exit(EXIT_FAILURE);
                }
        stat_todo--;    /* for the forthcoming read() */
-       if (!simulate_bads && sizeof(block_buf)==read(src_fd,block_buf,sizeof(block_buf))) {
+       if (!simulate_bads && (sizeof(block_buf)==(gotssize=read(src_fd,block_buf,sizeof(block_buf)))
+                                       /* see README/"Linux kernel flaw" */
+                                       || (start==src_len-1 && gotssize==((src_len&~1)-start)*BLOCK))
+                       ) {
                /* success read */
-               write_dst(start,start+1,block_buf);
+               if (gotssize==sizeof(block_buf))
+                       write_dst(start,start+1,block_buf);
+               else {
+                       /* handle README/"Linux kernel flaw" */
+                       fprintf(stderr,"WARNING: Ignored disk-last sector read failure; see README/\"Linux kernel flaw\"\n");
+                       range_zero(start,start+1);
+                       }
                start++;
                if (start>=end) /* finished */
                        return;
@@ -319,23 +371,6 @@ ext2_loff_t mid=(start+end)/2;
                }
 }
 
-static void range_zero(ext2_loff_t start,ext2_loff_t end)
-{
-static const unsigned char buf_zero[BUF_ZERO_BLOCKS*BLOCK];
-
-       g_return_if_fail(start<end);
-
-       while (start<end) {
-               ext2_loff_t mid=((end-start)>BUF_ZERO_BLOCKS ? start+BUF_ZERO_BLOCKS : end);
-               g_assert(mid>=start);
-               g_assert(mid<=end);
-
-               write_dst(start,mid,buf_zero);
-
-               start=mid;
-               }
-}
-
 static void finish(void)
 {
 struct range *todo_linear=tree_linearize(todo_tree),*todol;
@@ -390,7 +425,15 @@ int main(int argc,char **argv)
        setlinebuf(stderr);
 
        if (argc!=2 && argc!=3) {
-               fprintf(stderr,"Syntax: badblock-guess <src_dev> [<dst_dev (OVERWRITTEN & DESTROYED!!!)>]\n");
+               fprintf(stderr,"\
+badblock-guess, Copyright (C) 2002 Jan Kratochvil <short@ucw.cz>\n\
+$Id$\n\
+badblock-guess comes with ABSOLUTELY NO WARRANTY.\n\
+This is free software, and you are welcome to redistribute it\n\
+under certain conditions.\n\
+\n\
+Syntax: badblock-guess <src_dev> [<dst_dev (OVERWRITTEN & DESTROYED!!!)>]\n\
+\n");
                exit(EXIT_FAILURE);
                }
        if (-1==(src_fd=open64((src_name=argv[1]),O_RDONLY|O_BINARY))) {
@@ -427,6 +470,23 @@ struct stat src_stat,dst_stat;
                fprintf(stderr,"\"%s\" length %llu <=0\n",src_name,(unsigned long long)src_len);
                exit(EXIT_FAILURE);
                }
+       if (dst_fd!=-1) {
+               if (ext2fs_get_device_size(dst_name,BLOCK,&dst_lenblk)) {
+                       fprintf(stderr,"ext2fs_get_device_size(\"%s\",%d,...): %m\n",dst_name,BLOCK);
+                       exit(EXIT_FAILURE);
+                       }
+               dst_len=dst_lenblk;
+               if (dst_len<=0) {
+                       fprintf(stderr,"\"%s\" length %llu <=0\n",dst_name,(unsigned long long)dst_len);
+                       exit(EXIT_FAILURE);
+                       }
+               if (dst_len<src_len) {
+                       fprintf(stderr,"WARNING: Ignoring exceeding trailing data of \"%s\" from %llu up to its end %llu!\n",
+                                       src_name,(unsigned long long)dst_len,(unsigned long long)src_len);
+                       src_len=dst_len;
+                       src_lenblk=dst_lenblk;
+                       }
+               }
 
        node_memchunk=g_mem_chunk_new("node_memchunk",sizeof(struct range)/*atom_size*/,
                        0x1000 /*are_size: 64KB*/,G_ALLOC_AND_FREE);