序言/背景
上周,我的根文件系统重新安装了几次,我通过ddrescue拍摄了一个完整的快照。遗憾的是,文件系统已经损坏,一些文件丢失了。目前我试图找到我的ejabberd用户数据库,它应该在图像中的某个位置。 Testdisk找到了所需的文件(标记为已删除)但无法恢复。由于文件非常小,而且我在几个月前有备份,所以我想在整个图像上进行二分搜索。
所以现在我有一个带有损坏文件系统的64GB文件,想要提取一些包含某种模式的4kb块。
问题
如何在64GB大文件中查找数据并使用某些上下文(4kb)提取结果?
由于文件系统映像驻留在我的服务器上,我更喜欢使用linux cli工具。
答案 0 :(得分:0)
由于我无法找到符合我要求的工具,所以我自己在golang中编写了这个工具。我称之为 bima (用于二进制匹配)。它不是很漂亮但它完成了这项工作:
package main
import (
"bytes"
"encoding/hex"
"fmt"
"gopkg.in/alecthomas/kingpin.v1"
"io"
"log"
"math"
"os"
)
var (
debug = kingpin.Flag("debug", "Enable debug mode.").Short('d').Bool()
bsize = kingpin.Flag("blocksize", "Blocksize").Short('b').Default("126976").Int()
debugDetail = kingpin.Flag("debugdetail", "Debug Detail").Short('v').Default("10").Int()
matchCommand = kingpin.Command("match", "Match a value")
matchCommandValue = matchCommand.Arg("value", "The value (Hex Encoded e.g.: 616263 == abc)").Required().String()
matchCommandFile = matchCommand.Arg("file", "The file").Required().String()
)
func main() {
kingpin.Version("0.1")
mode := kingpin.Parse()
if *bsize <= 0 {
log.Fatal("The blocksize has to be larger than 0")
}
if *debugDetail <= 0 {
log.Fatal("The Debug Detail has to be larger than 0")
}
if mode == "match" {
searchBytes, err := hex.DecodeString(*matchCommandValue)
if err != nil {
log.Fatal(err)
}
scanFile(searchBytes, *matchCommandFile)
}
}
func scanFile(search []byte, path string) {
searchLength := len(search)
blocksize := *bsize
f, err := os.Open(path)
if err != nil {
log.Fatal(err)
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
log.Fatal(err)
}
filesize := fi.Size()
expectedRounds := int(math.Ceil(float64(filesize-int64(searchLength))/float64(blocksize)) + 1)
if expectedRounds <= 0 {
expectedRounds = 1
}
data := make([]byte, 0, blocksize+searchLength-1)
data2 := make([]byte, 0, blocksize+searchLength-1)
offset := make([]byte, searchLength-1)
//reading the len of the slice or less (but not the cap)
readCount, err := f.Read(offset)
if err == io.EOF {
fmt.Println("The files seems to be empty")
return
} else if err != nil {
log.Fatal(err)
}
data = append(data, offset...)
buffer := make([]byte, blocksize)
var blockpos int
var idx int
blockpos = 0
lastLevel := -1
roundLevel := 0
idxOffset := 0
for round := 0; ; round++ {
if *debug {
roundLevel = ((round * 100) / expectedRounds)
if (roundLevel%*debugDetail == 0) && (roundLevel > lastLevel) {
lastLevel = roundLevel
fmt.Fprintln(os.Stderr, "Starting round", round+1, "of", expectedRounds, "--", ((round * 100) / expectedRounds))
}
}
//At EOF, the count will be zero and err will be io.EOF
readCount, err = f.Read(buffer)
if err != nil {
if err == io.EOF {
if *debug {
fmt.Fprintln(os.Stderr, "Done - Found EOF")
}
break
}
fmt.Println(err)
return
}
data = append(data, buffer[:readCount]...)
data2 = data
idxOffset = 0
for {
idx = bytes.Index(data2, search)
if idx >= 0 {
fmt.Println(blockpos + idxOffset + idx)
if idx+searchLength < len(data2) {
data2 = data2[idx+searchLength:]
idxOffset += idx
} else {
break
}
} else {
break
}
}
data = data[readCount:]
blockpos += readCount
}
}
为了完整起见,我来了解决问题的方法:
首先我使用hexedit来查明所有db文件都有相同的标题。以十六进制编码,它看起来像这样:0102030463584d0b0000004b62574c41
所以我使用我的工具查找我的sda.image文件中的所有事件:
./bima match 0102030463584d0b0000004b62574c41 ./sda.image >DBfiles.txt
对于64GB这需要大约8分钟,我认为硬盘驱动器是限制因素。
使用dd从图像中提取的约1200次出现的结果。由于我不知道文件的确切大小,我只是提取了20.000字节的块:
for f in $(cat DBfiles.txt); do
dd if=sda.image of=$f.dunno bs=1 ibs=1 skip=$f count=20000
done
现在我有大约1200个文件,必须找到合适的文件。在第一步中,我搜索passwd文件(passwd.DCD和passwd.DCL)。后来我为名册文件做了同样的事情。由于文件的标题包含名称,我只是为了passwd:
for f in *.dunno; do
if [ "$(cat $f | head -c 200 | grep "passwd" | wc -l)" == "1" ]; then
echo "$f" | sed 's/\.$//g' >> passwd_files.list
fi
done
因为块大于我必须手动找到每个文件末尾的文件。我用Curses Hexedit进行了更正。
在此过程中,我可以看到每个文件的头部都包含dcl_logk
或dcd_logk
。所以我知道哪些文件是DCL文件,哪些是DCD文件。
最后我将每个文件最多放十次,并且必须决定我想要使用哪个版本。一般来说,我拿了最大的文件。将文件放入新ejabberd服务器的DB目录并重新启动后,所有帐户都会再次返回。 : - )