github.com/containerd/nerdctl@v1.7.7/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/identifiers" 26 "github.com/containerd/errdefs" 27 "github.com/containerd/nerdctl/pkg/inspecttypes/native" 28 "github.com/containerd/nerdctl/pkg/lockutil" 29 "github.com/containerd/nerdctl/pkg/strutil" 30 ) 31 32 // Path returns a string like `/var/lib/nerdctl/1935db59/volumes/default`. 33 func Path(dataStore, ns string) (string, error) { 34 if dataStore == "" || ns == "" { 35 return "", errdefs.ErrInvalidArgument 36 } 37 volStore := filepath.Join(dataStore, "volumes", ns) 38 return volStore, nil 39 } 40 41 // New returns a VolumeStore 42 func New(dataStore, ns string) (VolumeStore, error) { 43 volStoreDir, err := Path(dataStore, ns) 44 if err != nil { 45 return nil, err 46 } 47 if err := os.MkdirAll(volStoreDir, 0700); err != nil { 48 return nil, err 49 } 50 vs := &volumeStore{ 51 dir: volStoreDir, 52 } 53 return vs, nil 54 } 55 56 // DataDirName is "_data" 57 const DataDirName = "_data" 58 59 const volumeJSONFileName = "volume.json" 60 61 type VolumeStore interface { 62 Dir() string 63 Create(name string, labels []string) (*native.Volume, error) 64 // Get may return ErrNotFound 65 Get(name string, size bool) (*native.Volume, error) 66 List(size bool) (map[string]native.Volume, error) 67 Remove(names []string) (removedNames []string, err error) 68 } 69 70 type volumeStore struct { 71 // dir is a string like `/var/lib/nerdctl/1935db59/volumes/default`. 72 // dir is guaranteed to exist. 73 dir string 74 } 75 76 func (vs *volumeStore) Dir() string { 77 return vs.dir 78 } 79 80 func (vs *volumeStore) Create(name string, labels []string) (*native.Volume, error) { 81 if err := identifiers.Validate(name); err != nil { 82 return nil, fmt.Errorf("malformed name %s: %w", name, err) 83 } 84 volPath := filepath.Join(vs.dir, name) 85 volDataPath := filepath.Join(volPath, DataDirName) 86 fn := func() error { 87 if err := os.Mkdir(volPath, 0700); err != nil { 88 return err 89 } 90 if err := os.Mkdir(volDataPath, 0755); err != nil { 91 return err 92 } 93 94 type volumeOpts struct { 95 Labels map[string]string `json:"labels"` 96 } 97 98 labelsMap := strutil.ConvertKVStringsToMap(labels) 99 100 volOpts := volumeOpts{ 101 Labels: labelsMap, 102 } 103 104 labelsJSON, err := json.MarshalIndent(volOpts, "", " ") 105 if err != nil { 106 return err 107 } 108 109 volFilePath := filepath.Join(volPath, volumeJSONFileName) 110 return os.WriteFile(volFilePath, labelsJSON, 0644) 111 } 112 113 if err := lockutil.WithDirLock(vs.dir, fn); err != nil { 114 return nil, err 115 } 116 117 vol := &native.Volume{ 118 Name: name, 119 Mountpoint: volDataPath, 120 } 121 return vol, nil 122 } 123 124 func (vs *volumeStore) Get(name string, size bool) (*native.Volume, error) { 125 if err := identifiers.Validate(name); err != nil { 126 return nil, fmt.Errorf("malformed name %s: %w", name, err) 127 } 128 dataPath := filepath.Join(vs.dir, name, DataDirName) 129 if _, err := os.Stat(dataPath); err != nil { 130 if os.IsNotExist(err) { 131 return nil, fmt.Errorf("volume %q not found: %w", name, errdefs.ErrNotFound) 132 } 133 return nil, err 134 } 135 136 volFilePath := filepath.Join(vs.dir, name, volumeJSONFileName) 137 volumeDataBytes, err := os.ReadFile(volFilePath) 138 if err != nil { 139 if !os.IsNotExist(err) { 140 return nil, err 141 } // on else, volume.json does not exists should not be blocking for inspect operation 142 } 143 144 entry := native.Volume{ 145 Name: name, 146 Mountpoint: dataPath, 147 Labels: Labels(volumeDataBytes), 148 } 149 if size { 150 entry.Size, err = Size(&entry) 151 if err != nil { 152 return nil, err 153 } 154 } 155 return &entry, nil 156 } 157 158 func (vs *volumeStore) List(size bool) (map[string]native.Volume, error) { 159 dEnts, err := os.ReadDir(vs.dir) 160 if err != nil { 161 return nil, err 162 } 163 164 res := make(map[string]native.Volume, len(dEnts)) 165 for _, dEnt := range dEnts { 166 name := dEnt.Name() 167 vol, err := vs.Get(name, size) 168 if err != nil { 169 return res, err 170 } 171 res[name] = *vol 172 } 173 return res, nil 174 } 175 176 func (vs *volumeStore) Remove(names []string) ([]string, error) { 177 var removed []string 178 fn := func() error { 179 for _, name := range names { 180 if err := identifiers.Validate(name); err != nil { 181 return fmt.Errorf("malformed name %s: %w", name, err) 182 } 183 dir := filepath.Join(vs.dir, name) 184 if err := os.RemoveAll(dir); err != nil { 185 return err 186 } 187 removed = append(removed, name) 188 } 189 return nil 190 } 191 err := lockutil.WithDirLock(vs.dir, fn) 192 return removed, err 193 } 194 195 func Labels(b []byte) *map[string]string { 196 type volumeOpts struct { 197 Labels *map[string]string `json:"labels,omitempty"` 198 } 199 var vo volumeOpts 200 if err := json.Unmarshal(b, &vo); err != nil { 201 return nil 202 } 203 return vo.Labels 204 } 205 206 func Size(volume *native.Volume) (int64, error) { 207 var size int64 208 var walkFn = func(_ string, info os.FileInfo, err error) error { 209 if err != nil { 210 return err 211 } 212 if !info.IsDir() { 213 size += info.Size() 214 } 215 return err 216 } 217 var err = filepath.Walk(volume.Mountpoint, walkFn) 218 if err != nil { 219 return 0, err 220 } 221 return size, nil 222 }