-
Notifications
You must be signed in to change notification settings - Fork 10
/
file.go
250 lines (206 loc) · 7.47 KB
/
file.go
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
// Implements Node and Handler methods for files
package memfs
import (
"os"
"time"
"bazil.org/fuse"
"golang.org/x/net/context"
)
//===========================================================================
// Dir Type and Constructor
//===========================================================================
// File implements Node and Handler interfaces for file (data containing)
// objects in MemFs. Data is allocated directly in the file object, and is
// not chunked or broken up until transport.
type File struct {
Node
Data []byte // Actual data contained by the File
dirty bool // If data has been written but not flushed
}
// Init the file and create the data array
func (f *File) Init(name string, mode os.FileMode, parent *Dir, memfs *FileSystem) {
// Init the embedded node.
f.Node.Init(name, mode, parent, memfs)
// Make the data array
f.Data = make([]byte, 0, 0)
}
//===========================================================================
// File Methods
//===========================================================================
// GetNode returns a pointer to the embedded Node object
func (f *File) GetNode() *Node {
return &f.Node
}
//===========================================================================
// File fuse.Node* Interface
//===========================================================================
// Setattr sets the standard metadata for the receiver.
//
// Note, this is also used to communicate changes in the size of
// the file, outside of Writes.
//
// req.Valid is a bitmask of what fields are actually being set.
// For example, the method should not change the mode of the file
// unless req.Valid.Mode() is true.
//
// https://godoc.org/bazil.org/fuse/fs#NodeSetattrer
func (f *File) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) error {
if f.IsArchive() || f.fs.readonly {
return fuse.EPERM
}
// If size is set, this represents a truncation for a file (for a dir?)
if req.Valid.Size() {
f.fs.Lock() // Only lock if we're going to change the size.
f.Attrs.Size = req.Size
f.Attrs.Blocks = Blocks(f.Attrs.Size)
f.Data = f.Data[:req.Size] // If size > len(f.Data) then panic!
logger.Debug("truncate size from %d to %d on file %d", f.Attrs.Size, req.Size, f.ID)
f.fs.Unlock() // Must unlock before Node.Setattr is called!
}
// Now use the embedded Node's Setattr method.
return f.Node.Setattr(ctx, req, resp)
}
// Fsync must be defined or edting with vim or emacs fails.
// Implements NodeFsyncer, which has no associated documentation.
//
// https://godoc.org/bazil.org/fuse/fs#NodeFsyncer
func (f *File) Fsync(ctx context.Context, req *fuse.FsyncRequest) error {
f.fs.Lock()
defer f.fs.Unlock()
logger.Debug("fsync on file %d", f.ID)
return nil
}
//===========================================================================
// File fuse.Handle* Interface
//===========================================================================
// Flush is called each time the file or directory is closed. Because there
// can be multiple file descriptors referring to a single opened file, Flush
// can be called multiple times.
//
// Because this is an in-memory system, Flush is basically ignored.
//
// https://godoc.org/bazil.org/fuse/fs#HandleFlusher
func (f *File) Flush(ctx context.Context, req *fuse.FlushRequest) error {
logger.Info("flush file %d (dirty: %t, contains %d bytes with size %d)", f.ID, f.dirty, len(f.Data), f.Attrs.Size)
if f.IsArchive() || f.fs.readonly {
return fuse.EPERM
}
f.fs.Lock()
defer f.fs.Unlock()
if !f.dirty {
return nil
}
f.Attrs.Atime = time.Now()
f.Attrs.Mtime = f.Attrs.Atime
f.dirty = false
return nil
}
// ReadAll the data from a file. Implements HandleReadAller which has no
// associated documentation.
//
// Note that if ReadAll is implemented it supersedes Read() and should only
// be implemented as a convenience for applications that do not need to do
// offset reads.
//
// https://godoc.org/bazil.org/fuse/fs#HandleReadAller
// NOTE: Do not implement
// func (f *File) ReadAll(ctx context.Context) ([]byte, error) {
// f.fs.Lock()
// defer f.fs.Unlock()
//
// // Set the access time on the file.
// f.Attrs.Atime = time.Now()
//
// // Return the data with no error.
// logger.Debug("read all file %d", f.ID)
// return f.Data, nil
// }
// Read requests to read data from the handle.
//
// There is a page cache in the kernel that normally submits only page-aligned
// reads spanning one or more pages. However, you should not rely on this. To
// see individual requests as submitted by the file system clients, set
// OpenDirectIO.
//
// NOTE: that reads beyond the size of the file as reported by Attr are not
// even attempted (except in OpenDirectIO mode).
//
// https://godoc.org/bazil.org/fuse/fs#HandleReader
func (f *File) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
f.fs.Lock()
defer f.fs.Unlock()
// Find the end of the data slice to return.
to := uint64(req.Offset) + uint64(req.Size)
if to > f.Attrs.Size {
to = f.Attrs.Size
}
// Set the access time on the file.
f.Attrs.Atime = time.Now()
// Set the data on the response object.
resp.Data = f.Data[req.Offset:to]
logger.Debug("read %d bytes from offset %d in file %d", req.Size, req.Offset, f.ID)
return nil
}
// Release the handle to the file. No associated documentation.
//
// https://godoc.org/bazil.org/fuse/fs#HandleReleaser
// func (f *File) Release(ctx context.Context, req *fuse.ReleaseRequest) error {
// logger.Debug("release handle on file %d", f.ID)
// return nil
// }
// Write requests to write data into the handle at the given offset.
// Store the amount of data written in resp.Size.
//
// There is a writeback page cache in the kernel that normally submits only
// page-aligned writes spanning one or more pages. However, you should not
// rely on this. To see individual requests as submitted by the file system
// clients, set OpenDirectIO.
//
// Writes that grow the file are expected to update the file size (as seen
// through Attr). Note that file size changes are communicated also through
// Setattr.
//
// https://godoc.org/bazil.org/fuse/fs#HandleWriter
func (f *File) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error {
if f.IsArchive() || f.fs.readonly {
return fuse.EPERM
}
f.fs.Lock()
defer f.fs.Unlock()
olen := uint64(len(f.Data)) // original data length
wlen := uint64(len(req.Data)) // data write length
off := uint64(req.Offset) // offset of the write
lim := off + wlen // The final length of the data
// Ensure the original size is the same as the set size (debugging)
if olen != f.Attrs.Size {
msg := "bad size match: %d vs %d"
logger.Error(msg, olen, f.Attrs.Size)
}
// If the amount of data being written is greater than the amount of data
// currently being stored, allocate a new array with sufficient size and
// copy the original data to that buffer.
if lim > olen {
buf := make([]byte, lim)
var to uint64
if off < olen {
to = off
} else {
to = olen
}
copy(buf[0:to], f.Data[0:to])
f.Data = buf
// Update the size attributes of the file
f.Attrs.Size = lim
f.Attrs.Blocks = Blocks(f.Attrs.Size)
// Update the file system state
f.fs.nbytes += lim - olen
}
// Copy the data from the request into our data buffer
copy(f.Data[off:lim], req.Data[:])
// Set the attributes on the response
resp.Size = int(wlen)
// Mark the file as dirty
f.dirty = true
logger.Debug("wrote %d bytes offset by %d to file %d", wlen, off, f.ID)
return nil
}