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 package localdisk
31
32 import (
33 "bytes"
34 "fmt"
35 "log"
36 "os"
37 "path/filepath"
38
39 "perkeep.org/internal/osutil"
40 "perkeep.org/pkg/blob"
41 "perkeep.org/pkg/blobserver"
42 "perkeep.org/pkg/blobserver/files"
43 "perkeep.org/pkg/blobserver/local"
44
45 "go4.org/jsonconfig"
46 "go4.org/syncutil"
47 )
48
49
50
51
52
53 type DiskStorage struct {
54 blobserver.Storage
55 blob.SubFetcher
56
57 root string
58
59
60 gen *local.Generationer
61 }
62
63
64 var (
65 _ blobserver.Storage = (*DiskStorage)(nil)
66 _ blob.SubFetcher = (*DiskStorage)(nil)
67 )
68
69 func (ds *DiskStorage) String() string {
70 return fmt.Sprintf("\"filesystem\" file-per-blob at %s", ds.root)
71 }
72
73
74 func IsDir(root string) (bool, error) {
75 if osutil.DirExists(filepath.Join(root, "sha1")) {
76 return true, nil
77 }
78 if osutil.DirExists(filepath.Join(root, blob.RefFromString("").HashName())) {
79 return true, nil
80 }
81 return false, nil
82 }
83
84 const (
85
86
87
88 minFDLimit = 100
89 recommendedFDLimit = 1024
90 )
91
92
93
94 func New(root string) (*DiskStorage, error) {
95
96 fi, err := os.Stat(root)
97 if os.IsNotExist(err) {
98
99 if filepath.Base(root) == "packed" {
100 if err := os.Mkdir(root, 0700); err != nil {
101 return nil, fmt.Errorf("failed to mkdir packed directory: %v", err)
102 }
103 fi, err = os.Stat(root)
104 } else {
105 return nil, fmt.Errorf("Storage root %q doesn't exist", root)
106 }
107 }
108 if err != nil {
109 return nil, fmt.Errorf("Failed to stat directory %q: %v", root, err)
110 }
111 if !fi.IsDir() {
112 return nil, fmt.Errorf("storage root %q exists but is not a directory", root)
113 }
114 fileSto := files.NewStorage(files.OSFS(), root)
115 ds := &DiskStorage{
116 Storage: fileSto,
117 SubFetcher: fileSto,
118 root: root,
119 gen: local.NewGenerationer(root),
120 }
121 if _, _, err := ds.StorageGeneration(); err != nil {
122 return nil, fmt.Errorf("Error initialization generation for %q: %v", root, err)
123 }
124 ul, err := osutil.MaxFD()
125 if err != nil {
126 if err == osutil.ErrNotSupported {
127
128 return ds, nil
129 }
130 return nil, err
131 }
132 if ul < minFDLimit {
133 return nil, fmt.Errorf("the max number of open file descriptors on your system (ulimit -n) is too low. Please fix it with 'ulimit -S -n X' with X being at least %d", recommendedFDLimit)
134 }
135
136
137 fileSto.SetNewFileGate(syncutil.NewGate(int(ul * 80 / 100)))
138
139 err = ds.checkFS()
140 if err != nil {
141 return nil, err
142 }
143 return ds, nil
144 }
145
146 func newFromConfig(_ blobserver.Loader, config jsonconfig.Obj) (storage blobserver.Storage, err error) {
147 path := config.RequiredString("path")
148 if err := config.Validate(); err != nil {
149 return nil, err
150 }
151 return New(path)
152 }
153
154 func init() {
155 blobserver.RegisterStorageConstructor("filesystem", blobserver.StorageConstructor(newFromConfig))
156 }
157
158
159
160
161
162 func (ds *DiskStorage) checkFS() (ret error) {
163 tempdir, err := os.MkdirTemp(ds.root, "")
164 if err != nil {
165 return fmt.Errorf("localdisk check: unable to create tempdir in %s, err=%v", ds.root, err)
166 }
167 defer func() {
168 err := os.RemoveAll(tempdir)
169 if err != nil {
170 cleanErr := fmt.Errorf("localdisk check: unable to clean temp dir: %v", err)
171 if ret == nil {
172 ret = cleanErr
173 } else {
174 log.Printf("WARNING: %v", cleanErr)
175 }
176 }
177 }()
178
179 tempfile := filepath.Join(tempdir, "FILE.tmp")
180 filename := filepath.Join(tempdir, "FILE")
181 data := []byte("perkeep rocks")
182 err = os.WriteFile(tempfile, data, 0644)
183 if err != nil {
184 return fmt.Errorf("localdisk check: unable to write into %s, err=%v", ds.root, err)
185 }
186
187 out, err := os.ReadFile(tempfile)
188 if err != nil {
189 return fmt.Errorf("localdisk check: unable to read from %s, err=%v", tempfile, err)
190 }
191 if bytes.Compare(out, data) != 0 {
192 return fmt.Errorf("localdisk check: tempfile contents didn't match, got=%q", out)
193 }
194 if _, err := os.Lstat(filename); !os.IsNotExist(err) {
195 return fmt.Errorf("localdisk check: didn't expect file to exist, Lstat had other error, err=%v", err)
196 }
197 if err := os.Rename(tempfile, filename); err != nil {
198 return fmt.Errorf("localdisk check: rename failed, err=%v", err)
199 }
200 if _, err := os.Lstat(filename); err != nil {
201 return fmt.Errorf("localdisk check: after rename passed Lstat had error, err=%v", err)
202 }
203 return nil
204 }