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  }