aboutsummaryrefslogtreecommitdiff
path: root/internal/fusefrontend/file_allocate_truncate.go
blob: 5be7df4dcf5fb9e3cea0aea47b5989abc4c045d3 (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
package fusefrontend

// FUSE operations Truncate and Allocate on file handles
// i.e. ftruncate and fallocate

import (
	"sync"
	"syscall"

	"github.com/hanwen/go-fuse/fuse"

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

// Only warn once
var allocateWarnOnce sync.Once

// Allocate - FUSE call, fallocate(2)
// This is not implemented yet in gocryptfs, but it is neither in EncFS. This
// suggests that the user demand is low.
func (f *file) Allocate(off uint64, sz uint64, mode uint32) fuse.Status {
	allocateWarnOnce.Do(func() {
		tlog.Warn.Printf("fallocate(2) is not supported, returning ENOSYS - see https://github.com/rfjakob/gocryptfs/issues/1")
	})
	return fuse.ENOSYS
}

// Truncate - FUSE call
func (f *file) Truncate(newSize uint64) fuse.Status {
	f.fdLock.RLock()
	defer f.fdLock.RUnlock()
	if f.released {
		// The file descriptor has been closed concurrently.
		tlog.Warn.Printf("ino%d fh%d: Truncate on released file", f.ino, f.intFd())
		return fuse.EBADF
	}
	wlock.lock(f.ino)
	defer wlock.unlock(f.ino)
	var err error
	// Common case first: Truncate to zero
	if newSize == 0 {
		err = syscall.Ftruncate(int(f.fd.Fd()), 0)
		if err != nil {
			tlog.Warn.Printf("ino%d fh%d: Ftruncate(fd, 0) returned error: %v", f.ino, f.intFd(), err)
			return fuse.ToStatus(err)
		}
		// Truncate to zero kills the file header
		f.header = nil
		return fuse.OK
	}
	// We need the old file size to determine if we are growing or shrinking
	// the file
	fi, err := f.fd.Stat()
	if err != nil {
		tlog.Warn.Printf("ino%d fh%d: Truncate: Fstat failed: %v", f.ino, f.intFd(), err)
		return fuse.ToStatus(err)
	}
	oldSize := f.contentEnc.CipherSizeToPlainSize(uint64(fi.Size()))
	{
		oldB := float32(oldSize) / float32(f.contentEnc.PlainBS())
		newB := float32(newSize) / float32(f.contentEnc.PlainBS())
		tlog.Debug.Printf("ino%d: FUSE Truncate from %.2f to %.2f blocks (%d to %d bytes)", f.ino, oldB, newB, oldSize, newSize)
	}
	// File size stays the same - nothing to do
	if newSize == oldSize {
		return fuse.OK
	}
	// File grows
	if newSize > oldSize {
		// File was empty, create new header
		if oldSize == 0 {
			err = f.createHeader()
			if err != nil {
				return fuse.ToStatus(err)
			}
		}
		// New blocks to add
		addBlocks := f.contentEnc.ExplodePlainRange(oldSize, newSize-oldSize)
		if len(addBlocks) >= 2 {
			f.zeroPad(oldSize)
		}
		lastBlock := addBlocks[len(addBlocks)-1]
		if lastBlock.IsPartial() {
			off := lastBlock.BlockPlainOff()
			_, status := f.doWrite(make([]byte, lastBlock.Length), int64(off+lastBlock.Skip))
			return status
		} else {
			off := lastBlock.BlockCipherOff()
			err = syscall.Ftruncate(f.intFd(), int64(off+f.contentEnc.CipherBS()))
			if err != nil {
				tlog.Warn.Printf("Truncate: grow Ftruncate returned error: %v", err)
			}
			return fuse.ToStatus(err)
		}
	} else {
		// File shrinks
		blockNo := f.contentEnc.PlainOffToBlockNo(newSize)
		cipherOff := f.contentEnc.BlockNoToCipherOff(blockNo)
		plainOff := f.contentEnc.BlockNoToPlainOff(blockNo)
		lastBlockLen := newSize - plainOff
		var data []byte
		if lastBlockLen > 0 {
			var status fuse.Status
			data, status = f.doRead(plainOff, lastBlockLen)
			if status != fuse.OK {
				tlog.Warn.Printf("Truncate: shrink doRead returned error: %v", err)
				return status
			}
		}
		// Truncate down to the last complete block
		err = syscall.Ftruncate(int(f.fd.Fd()), int64(cipherOff))
		if err != nil {
			tlog.Warn.Printf("Truncate: shrink Ftruncate returned error: %v", err)
			return fuse.ToStatus(err)
		}
		// Append partial block
		if lastBlockLen > 0 {
			_, status := f.doWrite(data, int64(plainOff))
			return status
		}
		return fuse.OK
	}
}