|
1 |
| -// Copyright (c) 2016-2019, Jan Cajthaml <[email protected]> |
| 1 | +// Copyright (c) 2016-2020, Jan Cajthaml <[email protected]> |
2 | 2 | //
|
3 | 3 | // Licensed under the Apache License, Version 2.0 (the "License");
|
4 | 4 | // you may not use this file except in compliance with the License.
|
|
14 | 14 |
|
15 | 15 | package storage
|
16 | 16 |
|
17 |
| -import ( |
18 |
| - "bytes" |
19 |
| - "crypto/aes" |
20 |
| - "crypto/cipher" |
21 |
| - "crypto/rand" |
22 |
| - "fmt" |
23 |
| - "io" |
24 |
| - "os" |
25 |
| - "path/filepath" |
26 |
| - "reflect" |
27 |
| - "runtime" |
28 |
| - "sort" |
29 |
| - "syscall" |
30 |
| - "unsafe" |
31 |
| -) |
32 |
| - |
33 |
| -func nameFromDirent(dirent *syscall.Dirent) []byte { |
34 |
| - reg := int(uint64(dirent.Reclen) - uint64(unsafe.Offsetof(syscall.Dirent{}.Name))) |
35 |
| - |
36 |
| - var name []byte |
37 |
| - header := (*reflect.SliceHeader)(unsafe.Pointer(&name)) |
38 |
| - header.Cap = reg |
39 |
| - header.Len = reg |
40 |
| - header.Data = uintptr(unsafe.Pointer(&dirent.Name[0])) |
41 |
| - |
42 |
| - if index := bytes.IndexByte(name, 0); index >= 0 { |
43 |
| - header.Cap = index |
44 |
| - header.Len = index |
45 |
| - } |
46 |
| - |
47 |
| - return name |
48 |
| -} |
49 |
| - |
50 |
| -// Storage is a fascade to access storage |
51 |
| -type Storage struct { |
52 |
| - Root string |
53 |
| - encryptionKey []byte |
54 |
| - bufferSize int |
55 |
| -} |
56 |
| - |
57 |
| -// NewStorage returns new storage over given root |
58 |
| -func NewStorage(root string) Storage { |
59 |
| - if root == "" || os.MkdirAll(filepath.Clean(root), os.ModePerm) != nil { |
60 |
| - panic("unable to assert root storage directory") |
61 |
| - } |
62 |
| - return Storage{ |
63 |
| - Root: root, |
64 |
| - bufferSize: 8192, |
65 |
| - } |
66 |
| -} |
67 |
| - |
68 |
| -// SetEncryptionKey sets AES encryption key for data encryption and decryption |
69 |
| -func (storage *Storage) SetEncryptionKey(key []byte) { |
70 |
| - if storage == nil { |
71 |
| - return |
72 |
| - } |
73 |
| - storage.encryptionKey = key |
74 |
| -} |
75 |
| - |
76 |
| -// ListDirectory returns sorted slice of item names in given absolute path |
77 |
| -// default sorting is ascending |
78 |
| -func (storage Storage) ListDirectory(path string, ascending bool) (result []string, err error) { |
79 |
| - var ( |
80 |
| - n int |
81 |
| - dh *os.File |
82 |
| - de *syscall.Dirent |
83 |
| - ) |
84 |
| - |
85 |
| - dh, err = os.Open(filepath.Clean(storage.Root + "/" + path)) |
86 |
| - if err != nil { |
87 |
| - return |
88 |
| - } |
89 |
| - |
90 |
| - fd := int(dh.Fd()) |
91 |
| - result = make([]string, 0) |
92 |
| - |
93 |
| - scratchBuffer := make([]byte, storage.bufferSize) |
94 |
| - |
95 |
| - for { |
96 |
| - n, err = syscall.ReadDirent(fd, scratchBuffer) |
97 |
| - runtime.KeepAlive(dh) |
98 |
| - if err != nil { |
99 |
| - if r := dh.Close(); r != nil { |
100 |
| - err = r |
101 |
| - } |
102 |
| - return |
103 |
| - } |
104 |
| - if n <= 0 { |
105 |
| - break |
106 |
| - } |
107 |
| - buf := scratchBuffer[:n] |
108 |
| - for len(buf) > 0 { |
109 |
| - de = (*syscall.Dirent)(unsafe.Pointer(&buf[0])) |
110 |
| - buf = buf[de.Reclen:] |
111 |
| - |
112 |
| - if de.Ino == 0 { |
113 |
| - continue |
114 |
| - } |
115 |
| - |
116 |
| - nameSlice := nameFromDirent(de) |
117 |
| - switch len(nameSlice) { |
118 |
| - case 0: |
119 |
| - continue |
120 |
| - case 1: |
121 |
| - if nameSlice[0] == '.' { |
122 |
| - continue |
123 |
| - } |
124 |
| - case 2: |
125 |
| - if nameSlice[0] == '.' && nameSlice[1] == '.' { |
126 |
| - continue |
127 |
| - } |
128 |
| - } |
129 |
| - result = append(result, string(nameSlice)) |
130 |
| - } |
131 |
| - } |
132 |
| - |
133 |
| - if r := dh.Close(); r != nil { |
134 |
| - err = r |
135 |
| - return |
136 |
| - } |
137 |
| - |
138 |
| - if ascending { |
139 |
| - sort.Slice(result, func(i, j int) bool { |
140 |
| - return result[i] < result[j] |
141 |
| - }) |
142 |
| - } else { |
143 |
| - sort.Slice(result, func(i, j int) bool { |
144 |
| - return result[i] > result[j] |
145 |
| - }) |
146 |
| - } |
147 |
| - |
148 |
| - return |
149 |
| -} |
150 |
| - |
151 |
| -// CountFiles returns number of items in directory |
152 |
| -func (storage Storage) CountFiles(path string) (result int, err error) { |
153 |
| - var ( |
154 |
| - n int |
155 |
| - dh *os.File |
156 |
| - de *syscall.Dirent |
157 |
| - ) |
158 |
| - |
159 |
| - dh, err = os.Open(filepath.Clean(storage.Root + "/" + path)) |
160 |
| - if err != nil { |
161 |
| - return |
162 |
| - } |
163 |
| - |
164 |
| - fd := int(dh.Fd()) |
165 |
| - |
166 |
| - scratchBuffer := make([]byte, storage.bufferSize) |
167 |
| - |
168 |
| - for { |
169 |
| - n, err = syscall.ReadDirent(fd, scratchBuffer) |
170 |
| - runtime.KeepAlive(dh) |
171 |
| - if err != nil { |
172 |
| - if r := dh.Close(); r != nil { |
173 |
| - err = r |
174 |
| - } |
175 |
| - return |
176 |
| - } |
177 |
| - if n <= 0 { |
178 |
| - break |
179 |
| - } |
180 |
| - buf := scratchBuffer[:n] |
181 |
| - for len(buf) > 0 { |
182 |
| - de = (*syscall.Dirent)(unsafe.Pointer(&buf[0])) |
183 |
| - buf = buf[de.Reclen:] |
184 |
| - if de.Ino == 0 || de.Type != syscall.DT_REG { |
185 |
| - continue |
186 |
| - } |
187 |
| - result++ |
188 |
| - } |
189 |
| - } |
190 |
| - |
191 |
| - if r := dh.Close(); r != nil { |
192 |
| - err = r |
193 |
| - } |
194 |
| - |
195 |
| - return |
196 |
| -} |
197 |
| - |
198 |
| -// Exists returns true if absolute path exists |
199 |
| -func (storage Storage) Exists(path string) (bool, error) { |
200 |
| - var ( |
201 |
| - trusted = new(syscall.Stat_t) |
202 |
| - cleaned = filepath.Clean(storage.Root + "/" + path) |
203 |
| - err error |
204 |
| - ) |
205 |
| - err = syscall.Stat(cleaned, trusted) |
206 |
| - if err == nil { |
207 |
| - return true, nil |
208 |
| - } else if os.IsNotExist(err) { |
209 |
| - return false, nil |
210 |
| - } else { |
211 |
| - return false, err |
212 |
| - } |
213 |
| -} |
214 |
| - |
215 |
| -// TouchFile creates files given absolute path if file does not already exist |
216 |
| -func (storage Storage) TouchFile(path string) error { |
217 |
| - cleanedPath := filepath.Clean(storage.Root + "/" + path) |
218 |
| - if err := os.MkdirAll(filepath.Dir(cleanedPath), os.ModePerm); err != nil { |
219 |
| - return err |
220 |
| - } |
221 |
| - f, err := os.OpenFile(cleanedPath, os.O_RDONLY|os.O_CREATE|os.O_EXCL, os.ModePerm) |
222 |
| - if err != nil { |
223 |
| - return err |
224 |
| - } |
225 |
| - defer f.Close() |
226 |
| - return nil |
227 |
| -} |
228 |
| - |
229 |
| -// GetFileReader creates file io.Reader |
230 |
| -func (storage Storage) GetFileReader(path string) (*fileReader, error) { |
231 |
| - f, err := os.OpenFile(filepath.Clean(storage.Root+"/"+path), os.O_RDONLY, os.ModePerm) |
232 |
| - if err != nil { |
233 |
| - return nil, err |
234 |
| - } |
235 |
| - |
236 |
| - reader := new(fileReader) |
237 |
| - reader.source = f |
238 |
| - |
239 |
| - return reader, nil |
240 |
| -} |
241 |
| - |
242 |
| -// ReadFileFully reads whole file given absolute path |
243 |
| -func (storage Storage) ReadFileFully(path string) ([]byte, error) { |
244 |
| - f, err := os.OpenFile(filepath.Clean(storage.Root+"/"+path), os.O_RDONLY, os.ModePerm) |
245 |
| - if err != nil { |
246 |
| - return nil, err |
247 |
| - } |
248 |
| - defer f.Close() |
249 |
| - fi, err := f.Stat() |
250 |
| - if err != nil { |
251 |
| - return nil, err |
252 |
| - } |
253 |
| - buf := make([]byte, fi.Size()) |
254 |
| - _, err = f.Read(buf) |
255 |
| - if err != nil && err != io.EOF { |
256 |
| - return nil, err |
257 |
| - } |
258 |
| - return buf, nil |
259 |
| -} |
260 |
| - |
261 |
| -// WriteFile writes data given absolute path to a file if that file does not |
262 |
| -// already exists |
263 |
| -func (storage Storage) WriteFile(path string, data []byte) error { |
264 |
| - cleanedPath := filepath.Clean(storage.Root + "/" + path) |
265 |
| - if err := os.MkdirAll(filepath.Dir(cleanedPath), os.ModePerm); err != nil { |
266 |
| - return err |
267 |
| - } |
268 |
| - f, err := os.OpenFile(cleanedPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, os.ModePerm) |
269 |
| - if err != nil { |
270 |
| - return err |
271 |
| - } |
272 |
| - defer f.Close() |
273 |
| - if _, err := f.Write(data); err != nil { |
274 |
| - return err |
275 |
| - } |
276 |
| - return nil |
277 |
| -} |
278 |
| - |
279 |
| -// DeleteFile removes file given absolute path if that file does exists |
280 |
| -func (storage Storage) DeleteFile(path string) error { |
281 |
| - return os.Remove(filepath.Clean(storage.Root + "/" + path)) |
282 |
| -} |
283 |
| - |
284 |
| -// UpdateFile rewrite file with data given absolute path to a file if that file |
285 |
| -// exist |
286 |
| -func (storage Storage) UpdateFile(path string, data []byte) (err error) { |
287 |
| - cleanedPath := filepath.Clean(storage.Root + "/" + path) |
288 |
| - var f *os.File |
289 |
| - f, err = os.OpenFile(cleanedPath, os.O_WRONLY|os.O_TRUNC, os.ModePerm) |
290 |
| - if err != nil { |
291 |
| - return |
292 |
| - } |
293 |
| - defer f.Close() |
294 |
| - _, err = f.Write(data) |
295 |
| - return |
296 |
| -} |
297 |
| - |
298 |
| -// AppendFile appens data given absolute path to a file, creates it if it does |
299 |
| -// not exist |
300 |
| -func (storage Storage) AppendFile(path string, data []byte) (err error) { |
301 |
| - cleanedPath := filepath.Clean(storage.Root + "/" + path) |
302 |
| - err = os.MkdirAll(filepath.Dir(cleanedPath), os.ModePerm) |
303 |
| - if err != nil { |
304 |
| - return err |
305 |
| - } |
306 |
| - var f *os.File |
307 |
| - f, err = os.OpenFile(cleanedPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, os.ModePerm) |
308 |
| - if err != nil { |
309 |
| - return |
310 |
| - } |
311 |
| - defer f.Close() |
312 |
| - _, err = f.Write(data) |
313 |
| - return |
314 |
| -} |
315 |
| - |
316 |
| -// Encrypt data with encryption key |
317 |
| -func (storage Storage) Encrypt(data []byte) ([]byte, error) { |
318 |
| - if len(storage.encryptionKey) == 0 { |
319 |
| - return nil, fmt.Errorf("no encryption key setup") |
320 |
| - } |
321 |
| - block, err := aes.NewCipher(storage.encryptionKey) |
322 |
| - if err != nil { |
323 |
| - return nil, err |
324 |
| - } |
325 |
| - ciphertext := make([]byte, aes.BlockSize+len(data)) |
326 |
| - iv := ciphertext[:aes.BlockSize] |
327 |
| - if _, err := io.ReadFull(rand.Reader, iv); err != nil { |
328 |
| - return nil, err |
329 |
| - } |
330 |
| - cfb := cipher.NewCFBEncrypter(block, iv) |
331 |
| - cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(data)) |
332 |
| - return ciphertext, nil |
333 |
| -} |
334 |
| - |
335 |
| -// Decrypt data with encryption key |
336 |
| -func (storage Storage) Decrypt(data []byte) ([]byte, error) { |
337 |
| - if len(storage.encryptionKey) == 0 { |
338 |
| - return nil, fmt.Errorf("no encryption key setup") |
339 |
| - } |
340 |
| - block, err := aes.NewCipher(storage.encryptionKey) |
341 |
| - if err != nil { |
342 |
| - return nil, err |
343 |
| - } |
344 |
| - if len(data) < aes.BlockSize { |
345 |
| - return nil, fmt.Errorf("invalid blocksize expected %d but actual is %d", aes.BlockSize, len(data)) |
346 |
| - } |
347 |
| - |
348 |
| - plaintext := make([]byte, len(data)) |
349 |
| - copy(plaintext, data) |
350 |
| - iv := plaintext[:aes.BlockSize] |
351 |
| - plaintext = plaintext[aes.BlockSize:] |
352 |
| - cfb := cipher.NewCFBDecrypter(block, iv) |
353 |
| - cfb.XORKeyStream(plaintext, plaintext) |
354 |
| - return plaintext, nil |
| 17 | +// Storage hold contract for storage API |
| 18 | +type Storage interface { |
| 19 | + ListDirectory(string) ([]string, error) |
| 20 | + CountFiles(string) (int, error) |
| 21 | + Exists(string) (bool, error) |
| 22 | + TouchFile(string) error |
| 23 | + ReadFileFully(string) ([]byte, error) |
| 24 | + WriteFile(string, []byte) error |
| 25 | + DeleteFile(string) error |
| 26 | + UpdateFile(string, []byte) error |
| 27 | + AppendFile(string, []byte) error |
355 | 28 | }
|
0 commit comments