-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfilesystem_persistence.go
169 lines (141 loc) · 3.79 KB
/
filesystem_persistence.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
/*
Copyright 2017 Continusec Pty Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package safeadmin
import (
"encoding/hex"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"time"
"github.com/continusec/safeadmin/pb"
"github.com/golang/protobuf/proto"
"golang.org/x/net/context"
)
const (
fnamePrefix = "sdc-"
)
// FilesystemPersistence write key/values to the specified directory.
type FilesystemPersistence struct {
// Dir is a path to the directory that contains the data
Dir string
// Immutable means be read-only
Immutable bool
}
// Load returns value if found, nil otherwise. It should ignore the TTL
func (f *FilesystemPersistence) Load(ctx context.Context, key []byte) ([]byte, error) {
bo, err := ioutil.ReadFile(filepath.Join(f.Dir, fnamePrefix+hex.EncodeToString(key)))
if err != nil {
if os.IsNotExist(err) {
return nil, ErrStorageKeyNotFound
}
return nil, err
}
var rv pb.PersistedObject
err = proto.Unmarshal(bo, &rv)
if err != nil {
return nil, err
}
// Ignore what the TTL actually is though
return rv.Value, nil
}
// Save sets value, as atomically as we can. File will be saved with default umask
// This persistence layer saves the TTL as a prefix to each file
func (f *FilesystemPersistence) Save(ctx context.Context, key, value []byte, ttl time.Time) error {
if f.Immutable {
log.Println("Persistence layer is configured as immutable, yet tool is trying to write to it")
return ErrInvalidConfig
}
// Serialize
bo, err := proto.Marshal(&pb.PersistedObject{
Key: key,
Value: value,
Ttl: ttl.Unix(),
})
if err != nil {
return err
}
// Create dir if not exists
_, err = os.Stat(f.Dir)
if err != nil {
if os.IsNotExist(err) {
err = os.MkdirAll(f.Dir, 0700)
if err != nil {
return err
}
} else {
return err
}
}
// Create temp file
tf, err := ioutil.TempFile(f.Dir, "temp-")
if err != nil {
return err
}
// Write it out
_, err = tf.Write(bo)
if err != nil {
tf.Close() // ignore failure
os.Remove(tf.Name()) // ignore failure
return err
}
// Close it
err = tf.Close()
if err != nil {
os.Remove(tf.Name()) // ignore failure
return err
}
// Rename to correct filename - while not guaranteed by Golang to be atomic, apparently POSIX does on *nix systems
err = os.Rename(tf.Name(), filepath.Join(f.Dir, fnamePrefix+hex.EncodeToString(key)))
if err != nil {
os.Remove(tf.Name()) // ignore failure
return err
}
return nil
}
func deleteIfOld(fpath string, now int64) error {
bo, err := ioutil.ReadFile(fpath)
if err != nil {
return err
}
var rv pb.PersistedObject
err = proto.Unmarshal(bo, &rv)
if err != nil {
return err
}
// If too old, delete it
if rv.Ttl < now {
return os.Remove(fpath)
}
return nil
}
// Purge removes data whose TTL is older than now
func (f *FilesystemPersistence) Purge(ctx context.Context, now time.Time) error {
files, err := ioutil.ReadDir(f.Dir)
if err != nil {
return err
}
nowInt := now.Unix()
for _, fi := range files {
name := fi.Name()
if strings.HasPrefix(name, fnamePrefix) {
fileErr := deleteIfOld(filepath.Join(f.Dir, name), nowInt)
if fileErr != nil {
log.Printf("Error checking %s for deletion, continuing to next file: %s\n", name, fileErr)
err = fileErr // deliberately do not terminate, instead save error for returning later
}
}
}
return err
}