forked from RepoMirrors/btrfs
decode root_item; more tests; uuid search wip
This commit is contained in:
parent
08f5a6b26a
commit
54573d1c9d
@ -5,6 +5,8 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -74,6 +76,70 @@ func TestIsSubvolume(t *testing.T) {
|
||||
mksub("d1/v2/v3")
|
||||
}
|
||||
|
||||
func TestSubvolumes(t *testing.T) {
|
||||
dir, closer := btrfstest.New(t, sizeDef)
|
||||
defer closer()
|
||||
fs, err := Open(dir, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer fs.Close()
|
||||
|
||||
mksub := func(in string, path string) {
|
||||
if in != "" {
|
||||
path = filepath.Join(dir, in, path)
|
||||
} else {
|
||||
path = filepath.Join(dir, path)
|
||||
}
|
||||
if err := CreateSubVolume(path); err != nil {
|
||||
t.Fatalf("cannot create subvolume %v: %v", path, err)
|
||||
}
|
||||
}
|
||||
delsub := func(path string) {
|
||||
path = filepath.Join(dir, path)
|
||||
if err := DeleteSubVolume(path); err != nil {
|
||||
t.Fatalf("cannot delete subvolume %v: %v", path, err)
|
||||
}
|
||||
}
|
||||
expect := func(exp []string) {
|
||||
subs, err := fs.ListSubvolumes(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var got []string
|
||||
for _, s := range subs {
|
||||
if s.UUID.IsZero() {
|
||||
t.Fatalf("zero uuid in %+v", s)
|
||||
}
|
||||
if s.Name != "" {
|
||||
got = append(got, s.Name)
|
||||
}
|
||||
}
|
||||
sort.Strings(got)
|
||||
sort.Strings(exp)
|
||||
if !reflect.DeepEqual(got, exp) {
|
||||
t.Fatalf("list failed:\ngot: %v\nvs\nexp: %v", got, exp)
|
||||
}
|
||||
}
|
||||
|
||||
names := []string{"foo", "bar", "baz"}
|
||||
for _, name := range names {
|
||||
mksub("", name)
|
||||
}
|
||||
for _, name := range names {
|
||||
mksub(names[0], name)
|
||||
}
|
||||
expect([]string{
|
||||
"foo", "bar", "baz",
|
||||
"foo", "bar", "baz",
|
||||
})
|
||||
delsub("foo/bar")
|
||||
expect([]string{
|
||||
"foo", "bar", "baz",
|
||||
"foo", "baz",
|
||||
})
|
||||
}
|
||||
|
||||
func TestCompression(t *testing.T) {
|
||||
dir, closer := btrfstest.New(t, sizeDef)
|
||||
defer closer()
|
||||
|
206
btrfs_tree.go
206
btrfs_tree.go
@ -37,11 +37,6 @@ func asUint16(p []byte) uint16 {
|
||||
return *(*uint16)(unsafe.Pointer(&p[0]))
|
||||
}
|
||||
|
||||
func asTime(p []byte) time.Time {
|
||||
sec, nsec := asUint64(p[0:]), asUint32(p[8:])
|
||||
return time.Unix(int64(sec), int64(nsec))
|
||||
}
|
||||
|
||||
func asRootRef(p []byte) rootRef {
|
||||
const sz = 18
|
||||
// assuming that it is highly unsafe to have sizeof(struct) > len(data)
|
||||
@ -55,3 +50,204 @@ func asRootRef(p []byte) rootRef {
|
||||
}
|
||||
return ref
|
||||
}
|
||||
|
||||
// btrfs_disk_key_raw is a raw bytes for btrfs_disk_key structure
|
||||
type btrfs_disk_key_raw [17]byte
|
||||
|
||||
func (p btrfs_disk_key_raw) Decode() diskKey {
|
||||
return diskKey{
|
||||
ObjectID: asUint64(p[0:]),
|
||||
Type: p[8],
|
||||
Offset: asUint64(p[9:]),
|
||||
}
|
||||
}
|
||||
|
||||
type diskKey struct {
|
||||
ObjectID uint64
|
||||
Type byte
|
||||
Offset uint64
|
||||
}
|
||||
|
||||
// btrfs_timespec_raw is a raw bytes for btrfs_timespec structure.
|
||||
type btrfs_timespec_raw [12]byte
|
||||
|
||||
func (t btrfs_timespec_raw) Decode() time.Time {
|
||||
sec, nsec := asUint64(t[0:]), asUint32(t[8:])
|
||||
return time.Unix(int64(sec), int64(nsec))
|
||||
}
|
||||
|
||||
// timeBlock is a raw set of bytes for 4 time fields:
|
||||
// atime btrfs_timespec
|
||||
// ctime btrfs_timespec
|
||||
// mtime btrfs_timespec
|
||||
// otime btrfs_timespec
|
||||
// It is used to keep correct alignment when accessing structures from btrfs.
|
||||
type timeBlock [4]btrfs_timespec_raw
|
||||
|
||||
type btrfs_inode_item_raw struct {
|
||||
generation uint64
|
||||
transid uint64
|
||||
size uint64
|
||||
nbytes uint64
|
||||
block_group uint64
|
||||
nlink uint32
|
||||
uid uint32
|
||||
gid uint32
|
||||
mode uint32
|
||||
rdev uint64
|
||||
flags uint64
|
||||
sequence uint64
|
||||
_ [4]uint64 // reserved
|
||||
// atime btrfs_timespec
|
||||
// ctime btrfs_timespec
|
||||
// mtime btrfs_timespec
|
||||
// otime btrfs_timespec
|
||||
times timeBlock
|
||||
}
|
||||
|
||||
func (v btrfs_inode_item_raw) Decode() inodeItem {
|
||||
return inodeItem{
|
||||
Gen: v.generation,
|
||||
TransID: v.transid,
|
||||
Size: v.size,
|
||||
NBytes: v.nbytes,
|
||||
BlockGroup: v.block_group,
|
||||
NLink: v.nlink,
|
||||
UID: v.uid,
|
||||
GID: v.gid,
|
||||
Mode: v.mode,
|
||||
RDev: v.rdev,
|
||||
Flags: v.flags,
|
||||
Sequence: v.sequence,
|
||||
ATime: v.times[0].Decode(),
|
||||
CTime: v.times[1].Decode(),
|
||||
MTime: v.times[2].Decode(),
|
||||
OTime: v.times[3].Decode(),
|
||||
}
|
||||
}
|
||||
|
||||
type inodeItem struct {
|
||||
Gen uint64 // nfs style generation number
|
||||
TransID uint64 // transid that last touched this inode
|
||||
Size uint64
|
||||
NBytes uint64
|
||||
BlockGroup uint64
|
||||
NLink uint32
|
||||
UID uint32
|
||||
GID uint32
|
||||
Mode uint32
|
||||
RDev uint64
|
||||
Flags uint64
|
||||
Sequence uint64 // modification sequence number for NFS
|
||||
ATime time.Time
|
||||
CTime time.Time
|
||||
MTime time.Time
|
||||
OTime time.Time
|
||||
}
|
||||
|
||||
func asRootItem(p []byte) *btrfs_root_item_raw {
|
||||
return (*btrfs_root_item_raw)(unsafe.Pointer(&p[0]))
|
||||
}
|
||||
|
||||
type btrfs_root_item_raw [439]byte
|
||||
|
||||
func (p btrfs_root_item_raw) Decode() rootItem {
|
||||
const (
|
||||
off2 = unsafe.Sizeof(btrfs_root_item_raw_p1{})
|
||||
off3 = off2 + 23
|
||||
)
|
||||
p1 := (*btrfs_root_item_raw_p1)(unsafe.Pointer(&p[0]))
|
||||
p2 := p[off2 : off2+23]
|
||||
p2_k := (*btrfs_disk_key_raw)(unsafe.Pointer(&p[off2+4]))
|
||||
p2_b := p2[4+17:]
|
||||
p3 := (*btrfs_root_item_raw_p3)(unsafe.Pointer(&p[off3]))
|
||||
return rootItem{
|
||||
Inode: p1.inode.Decode(),
|
||||
Gen: p1.generation,
|
||||
RootDirID: p1.root_dirid,
|
||||
ByteNr: p1.bytenr,
|
||||
ByteLimit: p1.byte_limit,
|
||||
BytesUsed: p1.bytes_used,
|
||||
LastSnapshot: p1.last_snapshot,
|
||||
Flags: p1.flags,
|
||||
// from here, Go structure become misaligned with C structure
|
||||
Refs: asUint32(p2[0:]),
|
||||
DropProgress: p2_k.Decode(),
|
||||
DropLevel: p2_b[0],
|
||||
Level: p2_b[1],
|
||||
// these fields are still misaligned by 1 bytes
|
||||
// TODO(dennwc): it's a copy of Gen to check structure version; hide it maybe?
|
||||
GenV2: p3.generation_v2,
|
||||
UUID: p3.uuid,
|
||||
ParentUUID: p3.parent_uuid,
|
||||
ReceivedUUID: p3.received_uuid,
|
||||
CTransID: p3.ctransid,
|
||||
OTransID: p3.otransid,
|
||||
STransID: p3.stransid,
|
||||
RTransID: p3.rtransid,
|
||||
CTime: p3.times[0].Decode(),
|
||||
OTime: p3.times[1].Decode(),
|
||||
STime: p3.times[2].Decode(),
|
||||
RTime: p3.times[3].Decode(),
|
||||
}
|
||||
}
|
||||
|
||||
type rootItem struct {
|
||||
Inode inodeItem
|
||||
Gen uint64
|
||||
RootDirID uint64
|
||||
ByteNr uint64
|
||||
ByteLimit uint64
|
||||
BytesUsed uint64
|
||||
LastSnapshot uint64
|
||||
Flags uint64
|
||||
Refs uint32
|
||||
DropProgress diskKey
|
||||
DropLevel uint8
|
||||
Level uint8
|
||||
GenV2 uint64
|
||||
UUID UUID
|
||||
ParentUUID UUID
|
||||
ReceivedUUID UUID
|
||||
CTransID uint64
|
||||
OTransID uint64
|
||||
STransID uint64
|
||||
RTransID uint64
|
||||
CTime time.Time
|
||||
OTime time.Time
|
||||
STime time.Time
|
||||
RTime time.Time
|
||||
}
|
||||
|
||||
type btrfs_root_item_raw_p1 struct {
|
||||
inode btrfs_inode_item_raw
|
||||
generation uint64
|
||||
root_dirid uint64
|
||||
bytenr uint64
|
||||
byte_limit uint64
|
||||
bytes_used uint64
|
||||
last_snapshot uint64
|
||||
flags uint64
|
||||
}
|
||||
type btrfs_root_item_raw_p2 struct {
|
||||
refs uint32
|
||||
drop_progress btrfs_disk_key_raw
|
||||
drop_level uint8
|
||||
level uint8
|
||||
}
|
||||
type btrfs_root_item_raw_p3 struct {
|
||||
generation_v2 uint64
|
||||
uuid UUID
|
||||
parent_uuid UUID
|
||||
received_uuid UUID
|
||||
ctransid uint64
|
||||
otransid uint64
|
||||
stransid uint64
|
||||
rtransid uint64
|
||||
// ctime btrfs_timespec
|
||||
// otime btrfs_timespec
|
||||
// stime btrfs_timespec
|
||||
// rtime btrfs_timespec
|
||||
times timeBlock
|
||||
_ [8]uint64 // reserved
|
||||
}
|
||||
|
10
errors.go
10
errors.go
@ -1,6 +1,9 @@
|
||||
package btrfs
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type ErrNotBtrfs struct {
|
||||
Path string
|
||||
@ -42,3 +45,8 @@ var errorString = map[ErrCode]string{
|
||||
ErrDevOnlyWritable: "unable to remove the only writeable device",
|
||||
ErrDevExclRunInProgress: "add/delete/balance/replace/resize operation in progress",
|
||||
}
|
||||
|
||||
var (
|
||||
ErrNotFound = errors.New("not found")
|
||||
errNotImplemented = errors.New("not implemented")
|
||||
)
|
||||
|
@ -3,6 +3,7 @@ package btrfs
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var caseSizes = []struct {
|
||||
@ -48,15 +49,21 @@ var caseSizes = []struct {
|
||||
{obj: btrfs_ioctl_received_subvol_args{}, size: 200},
|
||||
{obj: btrfs_ioctl_send_args{}, size: 72},
|
||||
|
||||
//{obj:btrfs_timespec{},size:12},
|
||||
//{obj:btrfs_root_ref{},size:18},
|
||||
//{obj:btrfs_root_item{},size:439},
|
||||
{obj: btrfs_root_item_raw{}, size: 439},
|
||||
{obj: btrfs_root_item_raw_p1{}, size: 439 - 23 - int(unsafe.Sizeof(btrfs_root_item_raw_p3{}))},
|
||||
{obj: btrfs_root_item_raw_p3{}, size: 439 - 23 - int(unsafe.Sizeof(btrfs_root_item_raw_p1{}))},
|
||||
//{obj:btrfs_inode_item{},size:160},
|
||||
{obj: btrfs_inode_item_raw{}, size: 160},
|
||||
{obj: timeBlock{}, size: 4 * 12},
|
||||
}
|
||||
|
||||
func TestSizes(t *testing.T) {
|
||||
for _, c := range caseSizes {
|
||||
if sz := int(reflect.ValueOf(c.obj).Type().Size()); sz != c.size {
|
||||
t.Fatalf("unexpected size of %T: %d", c.obj, sz)
|
||||
t.Errorf("unexpected size of %T: %d (exp: %d)", c.obj, sz, c.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
79
subvolume.go
79
subvolume.go
@ -146,6 +146,23 @@ func SnapshotSubVolume(subvol, dst string, ro bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsReadOnly(path string) (bool, error) {
|
||||
f, err := GetFlags(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return f.ReadOnly(), nil
|
||||
}
|
||||
|
||||
func GetFlags(path string) (SubvolFlags, error) {
|
||||
fs, err := Open(path, true)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer fs.Close()
|
||||
return fs.GetFlags()
|
||||
}
|
||||
|
||||
type Subvolume struct {
|
||||
ObjectID uint64
|
||||
TransID uint64
|
||||
@ -205,18 +222,15 @@ func listSubVolumes(f *os.File) (map[uint64]Subvolume, error) {
|
||||
o := m[obj.ObjectID]
|
||||
o.TransID = obj.TransID
|
||||
o.ObjectID = obj.ObjectID
|
||||
// TODO: decode whole object?
|
||||
o.Gen = asUint64(obj.Data[160:]) // size of btrfs_inode_item
|
||||
o.Flags = asUint64(obj.Data[160+6*8:])
|
||||
const sz = 439
|
||||
const toff = sz - 8*8 - 4*12
|
||||
o.CTime = asTime(obj.Data[toff+0*12:])
|
||||
o.OTime = asTime(obj.Data[toff+1*12:])
|
||||
o.OGen = asUint64(obj.Data[toff-3*8:])
|
||||
const uoff = toff - 4*8 - 3*UUIDSize
|
||||
copy(o.UUID[:], obj.Data[uoff+0*UUIDSize:])
|
||||
copy(o.ParentUUID[:], obj.Data[uoff+1*UUIDSize:])
|
||||
copy(o.ReceivedUUID[:], obj.Data[uoff+2*UUIDSize:])
|
||||
robj := asRootItem(obj.Data).Decode()
|
||||
o.Gen = robj.Gen
|
||||
o.Flags = robj.Flags
|
||||
o.CTime = robj.CTime
|
||||
o.OTime = robj.OTime
|
||||
o.OGen = robj.GenV2
|
||||
o.UUID = robj.UUID
|
||||
o.ParentUUID = robj.ParentUUID
|
||||
o.ReceivedUUID = robj.ReceivedUUID
|
||||
m[obj.ObjectID] = o
|
||||
}
|
||||
}
|
||||
@ -243,3 +257,44 @@ func listSubVolumes(f *os.File) (map[uint64]Subvolume, error) {
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
type subvolInfo struct {
|
||||
RootID uint64
|
||||
|
||||
UUID UUID
|
||||
ParentUUID UUID
|
||||
ReceivedUUID UUID
|
||||
|
||||
CTransID uint64
|
||||
OTransID uint64
|
||||
STransID uint64
|
||||
RTransID uint64
|
||||
|
||||
Path string
|
||||
}
|
||||
|
||||
func subvolSearchByUUID(mnt *os.File, uuid UUID) (*subvolInfo, error) {
|
||||
id, err := lookupUUIDSubvolItem(mnt, uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return subvolSearchByRootID(mnt, id)
|
||||
}
|
||||
|
||||
func subvolSearchByReceivedUUID(mnt *os.File, uuid UUID) (*subvolInfo, error) {
|
||||
id, err := lookupUUIDReceivedSubvolItem(mnt, uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return subvolSearchByRootID(mnt, id)
|
||||
}
|
||||
|
||||
func subvolSearchByPath(mnt *os.File, path string) (*subvolInfo, error) {
|
||||
var id uint64
|
||||
panic("not implemented")
|
||||
return subvolSearchByRootID(mnt, id)
|
||||
}
|
||||
|
||||
func subvolSearchByRootID(mnt *os.File, rootID uint64) (*subvolInfo, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
27
utils.go
27
utils.go
@ -18,19 +18,6 @@ func isBtrfs(path string) (bool, error) {
|
||||
return stfs.Type == SuperMagic, nil
|
||||
}
|
||||
|
||||
func IsReadOnly(path string) (bool, error) {
|
||||
fs, err := Open(path, true)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer fs.Close()
|
||||
f, err := fs.GetFlags()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return f.ReadOnly(), nil
|
||||
}
|
||||
|
||||
func findMountRoot(path string) (string, error) {
|
||||
mounts, err := mtab.Mounts()
|
||||
if err != nil {
|
||||
@ -77,7 +64,7 @@ func openDir(path string) (*os.File, error) {
|
||||
return file, nil
|
||||
}
|
||||
|
||||
type rawItem struct {
|
||||
type searchResult struct {
|
||||
TransID uint64
|
||||
ObjectID uint64
|
||||
Type uint32
|
||||
@ -85,24 +72,24 @@ type rawItem struct {
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func treeSearchRaw(f *os.File, key btrfs_ioctl_search_key) (out []rawItem, _ error) {
|
||||
func treeSearchRaw(mnt *os.File, key btrfs_ioctl_search_key) (out []searchResult, _ error) {
|
||||
args := btrfs_ioctl_search_args{
|
||||
key: key,
|
||||
}
|
||||
if err := iocTreeSearch(f, &args); err != nil {
|
||||
if err := iocTreeSearch(mnt, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = make([]rawItem, 0, args.key.nr_items)
|
||||
out = make([]searchResult, 0, args.key.nr_items)
|
||||
buf := args.buf[:]
|
||||
for i := 0; i < int(args.key.nr_items); i++ {
|
||||
h := (*btrfs_ioctl_search_header)(unsafe.Pointer(&buf[0]))
|
||||
buf = buf[unsafe.Sizeof(btrfs_ioctl_search_header{}):]
|
||||
out = append(out, rawItem{
|
||||
out = append(out, searchResult{
|
||||
TransID: h.transid,
|
||||
ObjectID: h.objectid,
|
||||
Type: h.typ,
|
||||
Offset: h.offset,
|
||||
Data: buf[:h.len], // TODO: reallocate?
|
||||
Type: h.typ,
|
||||
Data: buf[:h.len:h.len], // TODO: reallocate?
|
||||
})
|
||||
buf = buf[h.len:]
|
||||
}
|
||||
|
49
uuid_tree.go
Normal file
49
uuid_tree.go
Normal file
@ -0,0 +1,49 @@
|
||||
package btrfs
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func lookupUUIDSubvolItem(f *os.File, uuid UUID) (uint64, error) {
|
||||
return uuidTreeLookupAny(f, uuid, uuidKeySubvol)
|
||||
}
|
||||
|
||||
func lookupUUIDReceivedSubvolItem(f *os.File, uuid UUID) (uint64, error) {
|
||||
return uuidTreeLookupAny(f, uuid, uuidKeyReceivedSubvol)
|
||||
}
|
||||
|
||||
func (id UUID) toKey() (objID, off uint64) {
|
||||
objID = binary.LittleEndian.Uint64(id[:8])
|
||||
off = binary.LittleEndian.Uint64(id[8:16])
|
||||
return
|
||||
}
|
||||
|
||||
// uuidTreeLookupAny searches uuid tree for a given uuid in specified field.
|
||||
// It returns ErrNotFound if object was not found.
|
||||
func uuidTreeLookupAny(f *os.File, uuid UUID, typ uint32) (uint64, error) {
|
||||
objId, off := uuid.toKey()
|
||||
args := btrfs_ioctl_search_key{
|
||||
tree_id: uuidTreeObjectid,
|
||||
min_objectid: objId,
|
||||
max_objectid: objId,
|
||||
min_type: typ,
|
||||
max_type: typ,
|
||||
min_offset: off,
|
||||
max_offset: off,
|
||||
max_transid: maxUint64,
|
||||
nr_items: 1,
|
||||
}
|
||||
res, err := treeSearchRaw(f, args)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if len(res) < 1 {
|
||||
return 0, ErrNotFound
|
||||
}
|
||||
out := res[0]
|
||||
if len(out.Data) != 8 {
|
||||
return 0, fmt.Errorf("btrfs: uuid item with illegal size %d", len(out.Data))
|
||||
}
|
||||
return binary.LittleEndian.Uint64(out.Data), nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user