aboutsummaryrefslogtreecommitdiff
path: root/internal/inomap/inomap.go
blob: b4dbf27a100e646a15873b2f45e9ccf44b62f024 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// inomap translates (Dev, Tag, Ino) tuples to unique uint64
// inode numbers.
//
// Format of the returned inode numbers:
//
//	[spill bit = 0][15 bit namespace id][48 bit passthru inode number] = 64 bit translated inode number
//	[spill bit = 1][63 bit counter                                   ] = 64 bit spill inode number
//
// Each (Dev, Tag) tuple gets a namespace id assigned. The original inode
// number is then passed through in the lower 48 bits.
//
// If namespace ids are exhausted, or the original id is larger than 48 bits,
// the whole (Dev, Tag, Ino) tuple gets mapped in the spill map, and the
// spill bit is set to 1.
package inomap

import (
	"log"
	"math"
	"sync"
	"sync/atomic"
	"syscall"

	"github.com/rfjakob/gocryptfs/v2/internal/tlog"
)

const (
	// max value of 15 bit namespace id
	maxNamespaceId = 1<<15 - 1
	// max value of 48 bit passthru inode number
	maxPassthruIno = 1<<48 - 1
	// the spill inode number space starts at 0b10000...0.
	spillSpaceStart = 1 << 63
)

// InoMap stores the maps using for inode number translation.
// See package comment for details.
type InoMap struct {
	sync.Mutex
	// namespaceMap keeps the mapping of (Dev,Flags) tuples to
	// 15-bit identifiers (stored in an uint16 with the high bit always zero)
	namespaceMap map[namespaceData]uint16
	// spillNext is the next free namespace number in the namespaces map
	namespaceNext uint16
	// spill is used once the namespaces map is full
	spillMap map[QIno]uint64
	// spillNext is the next free inode number in the spill map
	spillNext uint64
}

// New returns a new InoMap.
// Inode numbers on device `rootDev` will be passed through as-is.
// If `rootDev` is zero, the first Translate() call decides the effective
// rootDev.
func New(rootDev uint64) *InoMap {
	m := &InoMap{
		namespaceMap:  make(map[namespaceData]uint16),
		namespaceNext: 0,
		spillMap:      make(map[QIno]uint64),
		spillNext:     spillSpaceStart,
	}
	if rootDev > 0 {
		// Reserve namespace 0 for rootDev
		m.namespaceMap[namespaceData{rootDev, 0}] = 0
		m.namespaceNext = 1
	}
	return m
}

var spillWarn sync.Once

// NextSpillIno returns a fresh inode number from the spill pool without adding it to
// spillMap.
// Reverse mode NextSpillIno() for gocryptfs.longname.*.name files where a stable
// mapping is not needed.
func (m *InoMap) NextSpillIno() (out uint64) {
	if m.spillNext == math.MaxUint64 {
		log.Panicf("spillMap overflow: spillNext = 0x%x", m.spillNext)
	}
	return atomic.AddUint64(&m.spillNext, 1) - 1
}

func (m *InoMap) spill(in QIno) (out uint64) {
	spillWarn.Do(func() { tlog.Warn.Printf("InoMap: opening spillMap for %#v", in) })

	out, found := m.spillMap[in]
	if found {
		return out
	}

	out = m.NextSpillIno()
	m.spillMap[in] = out

	return out
}

// Translate maps the passed-in (device, tag, inode) tuple to a unique inode number.
func (m *InoMap) Translate(in QIno) (out uint64) {
	m.Lock()
	defer m.Unlock()

	if in.Ino > maxPassthruIno {
		out = m.spill(in)
		return out
	}
	ns, found := m.namespaceMap[in.namespaceData]
	// Use existing namespace
	if found {
		out = uint64(ns)<<48 | in.Ino
		return out
	}
	// No free namespace slots?
	if m.namespaceNext >= maxNamespaceId {
		out = m.spill(in)
		return out
	}
	ns = m.namespaceNext
	m.namespaceNext++
	m.namespaceMap[in.namespaceData] = ns
	out = uint64(ns)<<48 | in.Ino
	return out
}

// TranslateStat translates (device, ino) pair contained in "st" into a unique
// inode number and overwrites the ino in "st" with it.
// Convenience wrapper around Translate().
func (m *InoMap) TranslateStat(st *syscall.Stat_t) {
	in := QInoFromStat(st)
	st.Ino = m.Translate(in)
}