github.com/containerd/nerdctl@v1.7.7/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/errdefs" 28 "github.com/containerd/nerdctl/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 f, err := os.OpenFile(path, os.O_CREATE, 0644) 59 if err != nil { 60 f.Close() 61 } 62 return err 63 } 64 65 // AllocHostsFile is used for creating mount-bindable /etc/hosts file. 66 // The file is initialized with no content. 67 func AllocHostsFile(dataStore, ns, id string) (string, error) { 68 lockDir := filepath.Join(dataStore, hostsDirBasename) 69 if err := os.MkdirAll(lockDir, 0700); err != nil { 70 return "", err 71 } 72 path := HostsPath(dataStore, ns, id) 73 fn := func() error { 74 return ensureFile(path) 75 } 76 err := lockutil.WithDirLock(lockDir, fn) 77 return path, err 78 } 79 80 func DeallocHostsFile(dataStore, ns, id string) error { 81 lockDir := filepath.Join(dataStore, hostsDirBasename) 82 if err := os.MkdirAll(lockDir, 0700); err != nil { 83 return err 84 } 85 dirToBeRemoved := filepath.Dir(HostsPath(dataStore, ns, id)) 86 fn := func() error { 87 return os.RemoveAll(dirToBeRemoved) 88 } 89 return lockutil.WithDirLock(lockDir, fn) 90 } 91 92 func NewStore(dataStore string) (Store, error) { 93 store := &store{ 94 dataStore: dataStore, 95 hostsD: filepath.Join(dataStore, hostsDirBasename), 96 } 97 return store, os.MkdirAll(store.hostsD, 0700) 98 } 99 100 type Meta struct { 101 Namespace string 102 ID string 103 Networks map[string]*types100.Result 104 Hostname string 105 ExtraHosts map[string]string // host:ip 106 Name string 107 } 108 109 type Store interface { 110 Acquire(Meta) error 111 Release(ns, id string) error 112 Update(ns, id, newName string) error 113 } 114 115 type store struct { 116 // dataStore is /var/lib/nerdctl/<ADDRHASH> 117 dataStore string 118 // hostsD is /var/lib/nerdctl/<ADDRHASH>/etchosts 119 hostsD string 120 } 121 122 func (x *store) Acquire(meta Meta) error { 123 fn := func() error { 124 hostsPath := HostsPath(x.dataStore, meta.Namespace, meta.ID) 125 if err := ensureFile(hostsPath); err != nil { 126 return err 127 } 128 metaB, err := json.Marshal(meta) 129 if err != nil { 130 return err 131 } 132 metaPath := filepath.Join(x.hostsD, meta.Namespace, meta.ID, metaJSON) 133 if err := os.WriteFile(metaPath, metaB, 0644); err != nil { 134 return err 135 } 136 return newUpdater(meta.ID, x.hostsD).update() 137 } 138 return lockutil.WithDirLock(x.hostsD, fn) 139 } 140 141 // Release is triggered by Poststop hooks. 142 // It is called after the containerd task is deleted but before the delete operation returns. 143 func (x *store) Release(ns, id string) error { 144 fn := func() error { 145 metaPath := filepath.Join(x.hostsD, ns, id, metaJSON) 146 if _, err := os.Stat(metaPath); errors.Is(err, os.ErrNotExist) { 147 return nil 148 } 149 // We remove "meta.json" but we still retain the "hosts" file 150 // because it is needed for restarting. The "hosts" is removed on 151 // `nerdctl rm`. 152 // https://github.com/rootless-containers/rootlesskit/issues/220#issuecomment-783224610 153 if err := os.RemoveAll(metaPath); err != nil { 154 return err 155 } 156 return newUpdater(id, x.hostsD).update() 157 } 158 return lockutil.WithDirLock(x.hostsD, fn) 159 } 160 161 func (x *store) Update(ns, id, newName string) error { 162 fn := func() error { 163 metaPath := filepath.Join(x.hostsD, ns, id, metaJSON) 164 metaB, err := os.ReadFile(metaPath) 165 if err != nil { 166 return err 167 } 168 meta := &Meta{} 169 if err := json.Unmarshal(metaB, meta); err != nil { 170 return err 171 } 172 meta.Name = newName 173 metaB, err = json.Marshal(meta) 174 if err != nil { 175 return err 176 } 177 if err := os.WriteFile(metaPath, metaB, 0644); err != nil { 178 return err 179 } 180 return newUpdater(meta.ID, x.hostsD).update() 181 } 182 return lockutil.WithDirLock(x.hostsD, fn) 183 }