github.com/containerd/nerdctl/v2@v2.0.0-beta.5.0.20240520001846-b5758f54fa28/pkg/mountutil/volumestore/volumestore.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package volumestore 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "os" 23 "path/filepath" 24 25 "github.com/containerd/containerd/errdefs" 26 "github.com/containerd/containerd/identifiers" 27 "github.com/containerd/log" 28 "github.com/containerd/nerdctl/v2/pkg/inspecttypes/native" 29 "github.com/containerd/nerdctl/v2/pkg/lockutil" 30 "github.com/containerd/nerdctl/v2/pkg/strutil" 31 ) 32 33 // Path returns a string like `/var/lib/nerdctl/1935db59/volumes/default`. 34 func Path(dataStore, ns string) (string, error) { 35 if dataStore == "" || ns == "" { 36 return "", errdefs.ErrInvalidArgument 37 } 38 volStore := filepath.Join(dataStore, "volumes", ns) 39 return volStore, nil 40 } 41 42 // New returns a VolumeStore 43 func New(dataStore, ns string) (VolumeStore, error) { 44 volStoreDir, err := Path(dataStore, ns) 45 if err != nil { 46 return nil, err 47 } 48 if err := os.MkdirAll(volStoreDir, 0700); err != nil { 49 return nil, err 50 } 51 vs := &volumeStore{ 52 dir: volStoreDir, 53 } 54 return vs, nil 55 } 56 57 // DataDirName is "_data" 58 const DataDirName = "_data" 59 60 const volumeJSONFileName = "volume.json" 61 62 type VolumeStore interface { 63 Dir() string 64 Create(name string, labels []string) (*native.Volume, error) 65 // Get may return ErrNotFound 66 Get(name string, size bool) (*native.Volume, error) 67 List(size bool) (map[string]native.Volume, error) 68 Remove(names []string) (removedNames []string, err error) 69 } 70 71 type volumeStore struct { 72 // dir is a string like `/var/lib/nerdctl/1935db59/volumes/default`. 73 // dir is guaranteed to exist. 74 dir string 75 } 76 77 func (vs *volumeStore) Dir() string { 78 return vs.dir 79 } 80 81 func (vs *volumeStore) Create(name string, labels []string) (*native.Volume, error) { 82 if err := identifiers.Validate(name); err != nil { 83 return nil, fmt.Errorf("malformed name %s: %w", name, err) 84 } 85 volPath := filepath.Join(vs.dir, name) 86 volDataPath := filepath.Join(volPath, DataDirName) 87 volFilePath := filepath.Join(volPath, volumeJSONFileName) 88 fn := func() (err error) { 89 if err := os.Mkdir(volPath, 0700); err != nil { 90 return err 91 } 92 defer func() { 93 if err != nil { 94 os.Remove(volPath) 95 } 96 }() 97 if err := os.Mkdir(volDataPath, 0755); err != nil { 98 return err 99 } 100 defer func() { 101 if err != nil { 102 os.Remove(volDataPath) 103 } 104 }() 105 106 type volumeOpts struct { 107 Labels map[string]string `json:"labels"` 108 } 109 110 labelsMap := strutil.ConvertKVStringsToMap(labels) 111 112 volOpts := volumeOpts{ 113 Labels: labelsMap, 114 } 115 116 labelsJSON, err := json.MarshalIndent(volOpts, "", " ") 117 if err != nil { 118 return err 119 } 120 121 defer func() { 122 if err != nil { 123 if _, statErr := os.Stat(volFilePath); statErr != nil && !os.IsNotExist(statErr) { 124 log.L.Warnf("failed to stat volume file: %v", statErr) 125 return 126 } else if statErr == nil { 127 os.Remove(volFilePath) 128 } 129 } 130 }() 131 return os.WriteFile(volFilePath, labelsJSON, 0644) 132 } 133 134 if err := lockutil.WithDirLock(vs.dir, fn); err != nil { 135 return nil, err 136 } 137 138 // If other new actions that might fail are added below, we should move the cleanup function out of fn. 139 140 vol := &native.Volume{ 141 Name: name, 142 Mountpoint: volDataPath, 143 } 144 return vol, nil 145 } 146 147 func (vs *volumeStore) Get(name string, size bool) (*native.Volume, error) { 148 if err := identifiers.Validate(name); err != nil { 149 return nil, fmt.Errorf("malformed name %s: %w", name, err) 150 } 151 dataPath := filepath.Join(vs.dir, name, DataDirName) 152 if _, err := os.Stat(dataPath); err != nil { 153 if os.IsNotExist(err) { 154 return nil, fmt.Errorf("volume %q not found: %w", name, errdefs.ErrNotFound) 155 } 156 return nil, err 157 } 158 159 volFilePath := filepath.Join(vs.dir, name, volumeJSONFileName) 160 volumeDataBytes, err := os.ReadFile(volFilePath) 161 if err != nil { 162 if !os.IsNotExist(err) { 163 return nil, err 164 } // on else, volume.json does not exists should not be blocking for inspect operation 165 } 166 167 entry := native.Volume{ 168 Name: name, 169 Mountpoint: dataPath, 170 Labels: Labels(volumeDataBytes), 171 } 172 if size { 173 entry.Size, err = Size(&entry) 174 if err != nil { 175 return nil, err 176 } 177 } 178 return &entry, nil 179 } 180 181 func (vs *volumeStore) List(size bool) (map[string]native.Volume, error) { 182 dEnts, err := os.ReadDir(vs.dir) 183 if err != nil { 184 return nil, err 185 } 186 187 res := make(map[string]native.Volume, len(dEnts)) 188 for _, dEnt := range dEnts { 189 name := dEnt.Name() 190 vol, err := vs.Get(name, size) 191 if err != nil { 192 return res, err 193 } 194 res[name] = *vol 195 } 196 return res, nil 197 } 198 199 func (vs *volumeStore) Remove(names []string) ([]string, error) { 200 var removed []string 201 fn := func() error { 202 for _, name := range names { 203 if err := identifiers.Validate(name); err != nil { 204 return fmt.Errorf("malformed name %s: %w", name, err) 205 } 206 dir := filepath.Join(vs.dir, name) 207 if err := os.RemoveAll(dir); err != nil { 208 return err 209 } 210 removed = append(removed, name) 211 } 212 return nil 213 } 214 err := lockutil.WithDirLock(vs.dir, fn) 215 return removed, err 216 } 217 218 func Labels(b []byte) *map[string]string { 219 type volumeOpts struct { 220 Labels *map[string]string `json:"labels,omitempty"` 221 } 222 var vo volumeOpts 223 if err := json.Unmarshal(b, &vo); err != nil { 224 return nil 225 } 226 return vo.Labels 227 } 228 229 func Size(volume *native.Volume) (int64, error) { 230 var size int64 231 var walkFn = func(_ string, info os.FileInfo, err error) error { 232 if err != nil { 233 return err 234 } 235 if !info.IsDir() { 236 size += info.Size() 237 } 238 return err 239 } 240 var err = filepath.Walk(volume.Mountpoint, walkFn) 241 if err != nil { 242 return 0, err 243 } 244 return size, nil 245 }