github.com/containerd/nerdctl/v2@v2.0.0-beta.5.0.20240520001846-b5758f54fa28/pkg/dnsutil/hostsstore/hostsstore.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 hostsstore provides the interface for /var/lib/nerdctl/<ADDRHASH>/etchosts . 18 // Prioritize simplicity over scalability. 19 package hostsstore 20 21 import ( 22 "encoding/json" 23 "errors" 24 "os" 25 "path/filepath" 26 27 "github.com/containerd/containerd/errdefs" 28 "github.com/containerd/nerdctl/v2/pkg/lockutil" 29 types100 "github.com/containernetworking/cni/pkg/types/100" 30 ) 31 32 const ( 33 // hostsDirBasename is the base name of /var/lib/nerdctl/<ADDRHASH>/etchosts 34 hostsDirBasename = "etchosts" 35 // metaJSON is stored as /var/lib/nerdctl/<ADDRHASH>/etchosts/<NS>/<ID>/meta.json 36 metaJSON = "meta.json" 37 ) 38 39 // HostsPath returns "/var/lib/nerdctl/<ADDRHASH>/etchosts/<NS>/<ID>/hosts" 40 func HostsPath(dataStore, ns, id string) string { 41 if dataStore == "" || ns == "" || id == "" { 42 panic(errdefs.ErrInvalidArgument) 43 } 44 return filepath.Join(dataStore, hostsDirBasename, ns, id, "hosts") 45 } 46 47 // ensureFile ensures a file with permission 0644. 48 // The file is initialized with no content. 49 // The dir (if not exists) is created with permission 0700. 50 func ensureFile(path string) error { 51 if path == "" { 52 return errdefs.ErrInvalidArgument 53 } 54 dir := filepath.Dir(path) 55 if err := os.MkdirAll(dir, 0700); err != nil { 56 return err 57 } 58 return os.WriteFile(path, []byte{}, 0644) 59 } 60 61 // AllocHostsFile is used for creating mount-bindable /etc/hosts file. 62 // The file is initialized with no content. 63 func AllocHostsFile(dataStore, ns, id string) (string, error) { 64 lockDir := filepath.Join(dataStore, hostsDirBasename) 65 if err := os.MkdirAll(lockDir, 0700); err != nil { 66 return "", err 67 } 68 path := HostsPath(dataStore, ns, id) 69 fn := func() error { 70 return ensureFile(path) 71 } 72 err := lockutil.WithDirLock(lockDir, fn) 73 return path, err 74 } 75 76 func DeallocHostsFile(dataStore, ns, id string) error { 77 lockDir := filepath.Join(dataStore, hostsDirBasename) 78 if err := os.MkdirAll(lockDir, 0700); err != nil { 79 return err 80 } 81 dirToBeRemoved := filepath.Dir(HostsPath(dataStore, ns, id)) 82 fn := func() error { 83 return os.RemoveAll(dirToBeRemoved) 84 } 85 return lockutil.WithDirLock(lockDir, fn) 86 } 87 88 func NewStore(dataStore string) (Store, error) { 89 store := &store{ 90 dataStore: dataStore, 91 hostsD: filepath.Join(dataStore, hostsDirBasename), 92 } 93 return store, os.MkdirAll(store.hostsD, 0700) 94 } 95 96 type Meta struct { 97 Namespace string 98 ID string 99 Networks map[string]*types100.Result 100 Hostname string 101 ExtraHosts map[string]string // host:ip 102 Name string 103 } 104 105 type Store interface { 106 Acquire(Meta) error 107 Release(ns, id string) error 108 Update(ns, id, newName string) error 109 } 110 111 type store struct { 112 // dataStore is /var/lib/nerdctl/<ADDRHASH> 113 dataStore string 114 // hostsD is /var/lib/nerdctl/<ADDRHASH>/etchosts 115 hostsD string 116 } 117 118 func (x *store) Acquire(meta Meta) error { 119 fn := func() error { 120 hostsPath := HostsPath(x.dataStore, meta.Namespace, meta.ID) 121 if err := ensureFile(hostsPath); err != nil { 122 return err 123 } 124 metaB, err := json.Marshal(meta) 125 if err != nil { 126 return err 127 } 128 metaPath := filepath.Join(x.hostsD, meta.Namespace, meta.ID, metaJSON) 129 if err := os.WriteFile(metaPath, metaB, 0644); err != nil { 130 return err 131 } 132 return newUpdater(meta.ID, x.hostsD).update() 133 } 134 return lockutil.WithDirLock(x.hostsD, fn) 135 } 136 137 // Release is triggered by Poststop hooks. 138 // It is called after the containerd task is deleted but before the delete operation returns. 139 func (x *store) Release(ns, id string) error { 140 fn := func() error { 141 metaPath := filepath.Join(x.hostsD, ns, id, metaJSON) 142 if _, err := os.Stat(metaPath); errors.Is(err, os.ErrNotExist) { 143 return nil 144 } 145 // We remove "meta.json" but we still retain the "hosts" file 146 // because it is needed for restarting. The "hosts" is removed on 147 // `nerdctl rm`. 148 // https://github.com/rootless-containers/rootlesskit/issues/220#issuecomment-783224610 149 if err := os.RemoveAll(metaPath); err != nil { 150 return err 151 } 152 return newUpdater(id, x.hostsD).update() 153 } 154 return lockutil.WithDirLock(x.hostsD, fn) 155 } 156 157 func (x *store) Update(ns, id, newName string) error { 158 fn := func() error { 159 metaPath := filepath.Join(x.hostsD, ns, id, metaJSON) 160 metaB, err := os.ReadFile(metaPath) 161 if err != nil { 162 return err 163 } 164 meta := &Meta{} 165 if err := json.Unmarshal(metaB, meta); err != nil { 166 return err 167 } 168 meta.Name = newName 169 metaB, err = json.Marshal(meta) 170 if err != nil { 171 return err 172 } 173 if err := os.WriteFile(metaPath, metaB, 0644); err != nil { 174 return err 175 } 176 return newUpdater(meta.ID, x.hostsD).update() 177 } 178 return lockutil.WithDirLock(x.hostsD, fn) 179 }