Files
wincmd/ntfs/mft/output.go
T

212 lines
5.2 KiB
Go
Raw Permalink Normal View History

2021-11-16 15:58:13 +08:00
package mft
import (
"b612.me/wincmd/ntfs/bootsect"
"b612.me/wincmd/ntfs/fragment"
"bytes"
"fmt"
"io"
"os"
"runtime"
)
const supportedOemId = "NTFS "
const isWin = runtime.GOOS == "windows"
func GetMFTFileBytes(volume string) ([]byte, error) {
reader, length, err := GetMFTFileReader(volume)
2021-11-16 15:58:13 +08:00
if err != nil {
return nil, err
}
defer reader.Close()
bfio := bytes.NewBuffer(make([]byte, 0, length))
2021-11-16 15:58:13 +08:00
written, err := copyBytes(bfio, reader, length)
if err != nil {
return nil, err
}
2021-11-16 15:58:13 +08:00
if written != length {
return nil, fmt.Errorf("Write Not Ok,Should %d got %d", length, written)
}
return bfio.Bytes(), nil
}
func DumpMFTFile(volume, filepath string, fn func(int64, int64, float64)) error {
reader, length, err := GetMFTFileReader(volume)
2021-11-16 15:58:13 +08:00
if err != nil {
return err
}
defer reader.Close()
2021-11-16 15:58:13 +08:00
out, err := os.Create(filepath)
if err != nil {
return err
}
defer out.Close()
written, err := copyFiles(out, reader, length, fn)
if err != nil {
return err
}
2021-11-16 15:58:13 +08:00
if written != length {
return fmt.Errorf("Write Not Ok,Should %d got %d", length, written)
}
return nil
}
func GetMFTFile(volume string) (io.Reader, int64, error) {
reader, length, err := GetMFTFileReader(volume)
if err != nil {
return nil, 0, err
}
return reader, length, nil
}
func GetMFTFileReader(volume string) (io.ReadCloser, int64, error) {
reader, length, _, err := openMFTFile(volume)
if err != nil {
return nil, 0, err
}
return reader, length, nil
}
func openMFTFile(volume string) (io.ReadCloser, int64, int64, error) {
2021-11-16 15:58:13 +08:00
if isWin {
volume = `\\.\` + volume[:len(volume)-1]
}
in, err := os.Open(volume)
if err != nil {
return nil, 0, 0, err
2021-11-16 15:58:13 +08:00
}
success := false
defer func() {
if !success {
in.Close()
}
}()
2021-11-16 15:58:13 +08:00
bootSectorData := make([]byte, 512)
_, err = io.ReadFull(in, bootSectorData)
if err != nil {
return nil, 0, 0, fmt.Errorf("Unable to read boot sector: %v", err)
2021-11-16 15:58:13 +08:00
}
bootSector, err := bootsect.Parse(bootSectorData)
if err != nil {
return nil, 0, 0, fmt.Errorf("Unable to parse boot sector data: %v", err)
2021-11-16 15:58:13 +08:00
}
if bootSector.OemId != supportedOemId {
return nil, 0, 0, fmt.Errorf("Unknown OemId (file system type) %q (expected %q)", bootSector.OemId, supportedOemId)
2021-11-16 15:58:13 +08:00
}
bytesPerCluster := bootSector.BytesPerSector * bootSector.SectorsPerCluster
if bytesPerCluster <= 0 {
return nil, 0, 0, fmt.Errorf("Invalid bytes per cluster %d", bytesPerCluster)
}
2021-11-16 15:58:13 +08:00
mftPosInBytes := int64(bootSector.MftClusterNumber) * int64(bytesPerCluster)
_, err = in.Seek(mftPosInBytes, 0)
if err != nil {
return nil, 0, 0, fmt.Errorf("Unable to seek to MFT position: %v", err)
2021-11-16 15:58:13 +08:00
}
mftSizeInBytes := bootSector.FileRecordSegmentSizeInBytes
if mftSizeInBytes <= 0 {
return nil, 0, 0, fmt.Errorf("Invalid MFT record size %d", mftSizeInBytes)
}
2021-11-16 15:58:13 +08:00
mftData := make([]byte, mftSizeInBytes)
_, err = io.ReadFull(in, mftData)
if err != nil {
return nil, 0, 0, fmt.Errorf("Unable to read $MFT record: %v", err)
2021-11-16 15:58:13 +08:00
}
2022-03-09 13:42:01 +08:00
record, err := ParseRecord(mftData)
2021-11-16 15:58:13 +08:00
if err != nil {
return nil, 0, 0, fmt.Errorf("Unable to parse $MFT record: %v", err)
2021-11-16 15:58:13 +08:00
}
2022-03-09 13:42:01 +08:00
dataAttributes := record.FindAttributes(AttributeTypeData)
2021-11-16 15:58:13 +08:00
if len(dataAttributes) == 0 {
return nil, 0, 0, fmt.Errorf("No $DATA attribute found in $MFT record")
2021-11-16 15:58:13 +08:00
}
if len(dataAttributes) > 1 {
return nil, 0, 0, fmt.Errorf("More than 1 $DATA attribute found in $MFT record")
2021-11-16 15:58:13 +08:00
}
dataAttribute := dataAttributes[0]
if dataAttribute.Resident {
return nil, 0, 0, fmt.Errorf("Don't know how to handle resident $DATA attribute in $MFT record")
2021-11-16 15:58:13 +08:00
}
dataRuns, err := ParseDataRuns(dataAttribute.Data)
if err != nil {
return nil, 0, 0, fmt.Errorf("Unable to parse dataruns in $MFT $DATA record: %v", err)
2021-11-16 15:58:13 +08:00
}
if len(dataRuns) == 0 {
return nil, 0, 0, fmt.Errorf("No dataruns found in $MFT $DATA record")
2021-11-16 15:58:13 +08:00
}
fragments := DataRunsToFragments(dataRuns, bytesPerCluster)
totalLength := int64(0)
for _, frag := range fragments {
totalLength += int64(frag.Length)
}
success = true
return fragment.NewReader(in, fragments), totalLength, int64(mftSizeInBytes), nil
2021-11-16 15:58:13 +08:00
}
func copyBytes(dst io.Writer, src io.Reader, totalLength int64) (written int64, err error) {
return copyWithProgress(dst, src, totalLength, nil)
2021-11-16 15:58:13 +08:00
}
func copyFiles(dst io.Writer, src io.Reader, totalLength int64, fn func(int64, int64, float64)) (written int64, err error) {
return copyWithProgress(dst, src, totalLength, fn)
}
func copyWithProgress(dst io.Writer, src io.Reader, totalLength int64, fn func(int64, int64, float64)) (written int64, err error) {
2021-11-16 15:58:13 +08:00
buf := make([]byte, 1024*1024)
// Below copied from io.copyBuffer (https://golang.org/src/io/io.go?s=12796:12856#L380)
for {
reportCopyProgress(fn, written, totalLength)
2021-11-16 15:58:13 +08:00
nr, er := src.Read(buf)
if nr > 0 {
nw, ew := dst.Write(buf[0:nr])
if nw > 0 {
written += int64(nw)
}
if ew != nil {
err = ew
break
}
if nr != nw {
err = io.ErrShortWrite
break
}
}
if er != nil {
if er != io.EOF {
err = er
}
break
}
}
reportCopyProgress(fn, written, totalLength)
2021-11-16 15:58:13 +08:00
return written, err
}
func reportCopyProgress(fn func(int64, int64, float64), written int64, totalLength int64) {
if fn == nil {
return
}
if totalLength <= 0 {
fn(written, totalLength, 100)
return
}
fn(written, totalLength, float64(written)/float64(totalLength)*100)
}