1 package keystore 2 3 import ( 4 "encoding/base64" 5 "fmt" 6 "io" 7 "os" 8 "path/filepath" 9 "sort" 10 "strings" 11 ) 12 13 // FileStore implements the Store interface and provides a file backed keystore 14 type FileStore struct { 15 baseDir, tmpDir string 16 mangler Mangler 17 } 18 19 // NewFileStore creates a file backed key-value store 20 func NewFileStore(baseDir, tmpDir string, mangler Mangler) (*FileStore, error) { 21 fs := new(FileStore) 22 if err := fs.init(baseDir, tmpDir, mangler); err != nil { 23 return nil, err 24 } 25 return fs, nil 26 } 27 28 func (fs *FileStore) init(baseDir, tmpDir string, mangler Mangler) error { 29 if err := os.MkdirAll(baseDir, 0o700); err != nil { 30 return fmt.Errorf("error creating data dir: %w", err) 31 } 32 if mangler == nil { 33 mangler = base64Mangler{} 34 } 35 if tmpDir != "" { 36 if err := os.MkdirAll(tmpDir, 0o700); err != nil { 37 return fmt.Errorf("error creating temp dir: %w", err) 38 } 39 } 40 fs.baseDir = baseDir 41 fs.tmpDir = tmpDir 42 fs.mangler = mangler 43 return nil 44 } 45 46 // Get retrieves the key data from the filesystem 47 func (fs *FileStore) Get(key string, r io.ReaderFrom) error { 48 key = fs.mangleKey(key, false) 49 f, err := os.Open(filepath.Join(fs.baseDir, key)) 50 if err != nil { 51 if os.IsNotExist(err) { 52 return ErrUnknownKey 53 } 54 return fmt.Errorf("error opening key file: %w", err) 55 } 56 _, err = r.ReadFrom(f) 57 f.Close() 58 return err 59 } 60 61 // Set stores the key data on the filesystem 62 func (fs *FileStore) Set(key string, w io.WriterTo) error { 63 key = fs.mangleKey(key, true) 64 var ( 65 f *os.File 66 err error 67 ) 68 if fs.tmpDir != "" { 69 f, err = os.CreateTemp(fs.tmpDir, "keystore") 70 } else { 71 f, err = os.Create(filepath.Join(fs.baseDir, key)) 72 } 73 if err != nil { 74 return fmt.Errorf("error opening file for writing: %w", err) 75 } 76 if _, err = w.WriteTo(f); err != nil && err != io.EOF { 77 f.Close() 78 return fmt.Errorf("error writing to file: %w", err) 79 } else if err = f.Close(); err != nil { 80 return fmt.Errorf("error closing file: %w", err) 81 } 82 if fs.tmpDir != "" { 83 fp := f.Name() 84 if err = os.Rename(fp, filepath.Join(fs.baseDir, key)); err != nil { 85 os.Remove(fp) 86 return fmt.Errorf("error moving tmp file: %w", err) 87 } 88 } 89 return nil 90 } 91 92 // Remove deletes the key data from the filesystem 93 func (fs *FileStore) Remove(key string) error { 94 key = fs.mangleKey(key, false) 95 if os.IsNotExist((os.Remove(filepath.Join(fs.baseDir, key)))) { 96 return ErrUnknownKey 97 } 98 return nil 99 } 100 101 // Keys returns a sorted slice of all of the keys 102 func (fs *FileStore) Keys() []string { 103 s := fs.getDirContents("") 104 sort.Strings(s) 105 return s 106 } 107 108 // Stat returns the FileInfo of the file relatining to the given key 109 func (fs *FileStore) Stat(key string) (os.FileInfo, error) { 110 return os.Stat(filepath.Join(fs.baseDir, fs.mangleKey(key, false))) 111 } 112 113 // Exists returns true when the key exists within the store 114 func (fs *FileStore) Exists(key string) bool { 115 _, err := os.Stat(filepath.Join(fs.baseDir, fs.mangleKey(key, false))) 116 return err == nil 117 } 118 119 // Rename moves data from an existing key to a new, unused key 120 func (fs *FileStore) Rename(oldkey, newkey string) error { 121 return os.Rename(filepath.Join(fs.baseDir, fs.mangleKey(oldkey, false)), filepath.Join(fs.baseDir, fs.mangleKey(newkey, true))) 122 } 123 124 func (fs *FileStore) mangleKey(key string, prepare bool) string { 125 parts := fs.mangler.Encode(key) 126 if len(parts) == 0 { 127 return "" 128 } else if len(parts) == 1 { 129 return parts[0] 130 } else if prepare { 131 os.MkdirAll(filepath.Join(append([]string{fs.baseDir}, parts...)...), 0o700) 132 } 133 return filepath.Clean("/" + strings.Join(parts, string(filepath.Separator)))[1:] 134 } 135 136 func (fs *FileStore) getDirContents(dir string) []string { 137 d, err := os.Open(filepath.Join(fs.baseDir, dir)) 138 if err != nil { 139 return nil 140 } 141 files, err := d.Readdir(-1) 142 if err != nil { 143 return nil 144 } 145 names := make([]string, 0, len(files)) 146 for _, file := range files { 147 if file.IsDir() { 148 names = append(names, fs.getDirContents(filepath.Join(dir, file.Name()))...) 149 } else { 150 name, err := fs.mangler.Decode(strings.Split(filepath.Join(dir, file.Name()), string(filepath.Separator))) 151 if err != nil { 152 continue 153 } 154 names = append(names, name) 155 } 156 } 157 return names 158 } 159 160 // Mangler is an interface for the methods required to un/mangle a key 161 type Mangler interface { 162 Encode(string) []string 163 Decode([]string) (string, error) 164 } 165 166 type base64Mangler struct{} 167 168 func (base64Mangler) Encode(name string) []string { 169 return []string{base64.URLEncoding.EncodeToString([]byte(name))} 170 } 171 172 func (base64Mangler) Decode(parts []string) (string, error) { 173 if len(parts) != 1 { 174 return "", ErrInvalidKey 175 } 176 b, err := base64.URLEncoding.DecodeString(parts[0]) 177 if err != nil { 178 return "", err 179 } 180 return string(b), nil 181 } 182 183 type noMangle struct{} 184 185 func (noMangle) Encode(name string) []string { 186 return strings.Split(name, string(filepath.Separator)) 187 } 188 189 func (noMangle) Decode(parts []string) (string, error) { 190 return strings.Join(parts, string(filepath.Separator)), nil 191 } 192 193 // Base64Mangler represents the default Mangler that simple base64 encodes the 194 // key 195 var Base64Mangler Mangler = base64Mangler{} 196 197 // NoMangle is a mangler that performs no mangling. This should only be used 198 // when you are certain that there are no filesystem special characters in the 199 // key name 200 var NoMangle Mangler = noMangle{} 201