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  }