github.com/cilium/cilium@v1.16.2/pkg/endpoint/directory.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package endpoint
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  
    11  	"github.com/sirupsen/logrus"
    12  	"golang.org/x/sys/unix"
    13  
    14  	"github.com/cilium/cilium/pkg/logging"
    15  	"github.com/cilium/cilium/pkg/option"
    16  )
    17  
    18  const (
    19  	nextDirectorySuffix       = "_next"
    20  	nextFailedDirectorySuffix = "_next_fail"
    21  	backupDirectorySuffix     = "_stale"
    22  )
    23  
    24  // DirectoryPath returns the directory name for this endpoint bpf program.
    25  func (e *Endpoint) DirectoryPath() string {
    26  	return filepath.Join(".", fmt.Sprintf("%d", e.ID))
    27  }
    28  
    29  // FailedDirectoryPath returns the directory name for this endpoint bpf program
    30  // failed builds.
    31  func (e *Endpoint) FailedDirectoryPath() string {
    32  	return filepath.Join(".", fmt.Sprintf("%d%s", e.ID, nextFailedDirectorySuffix))
    33  }
    34  
    35  // StateDirectoryPath returns the directory name for this endpoint bpf program.
    36  func (e *Endpoint) StateDirectoryPath() string {
    37  	return filepath.Join(option.Config.StateDir, e.StringID())
    38  }
    39  
    40  // NextDirectoryPath returns the directory name for this endpoint bpf program
    41  // next bpf builds.
    42  func (e *Endpoint) NextDirectoryPath() string {
    43  	return filepath.Join(".", fmt.Sprintf("%d%s", e.ID, nextDirectorySuffix))
    44  }
    45  
    46  // copyExistingState copies all files, that do not exist in newDir, from oldDir.
    47  // It assumes that oldDir and newDir are an endpoint's old and new state
    48  // directories (see synchronizeDirectories below).
    49  func copyExistingState(oldDir, newDir string) error {
    50  	oldDirFile, err := os.Open(oldDir)
    51  	if err != nil {
    52  		return fmt.Errorf("failed to open old endpoint state dir: %w", err)
    53  	}
    54  	defer oldDirFile.Close()
    55  
    56  	oldFiles, err := oldDirFile.Readdirnames(-1)
    57  	if err != nil {
    58  		return fmt.Errorf("failed to list old endpoint state dir: %w", err)
    59  	}
    60  
    61  	newDirFile, err := os.Open(newDir)
    62  	if err != nil {
    63  		return fmt.Errorf("failed to open new endpoint state dir: %w", err)
    64  	}
    65  	defer newDirFile.Close()
    66  
    67  	newFiles, err := newDirFile.Readdirnames(-1)
    68  	if err != nil {
    69  		return fmt.Errorf("failed to list new endpoint state dir: %w", err)
    70  	}
    71  
    72  	newFilesHash := make(map[string]struct{}, len(newFiles))
    73  	for _, f := range newFiles {
    74  		newFilesHash[f] = struct{}{}
    75  	}
    76  
    77  	var ok bool
    78  
    79  	for _, oldFile := range oldFiles {
    80  		if _, ok = newFilesHash[oldFile]; !ok {
    81  			if err := os.Link(filepath.Join(oldDir, oldFile), filepath.Join(newDir, oldFile)); err != nil {
    82  				return fmt.Errorf("failed to move endpoint state file: %w", err)
    83  			}
    84  		}
    85  	}
    86  
    87  	return nil
    88  }
    89  
    90  // synchronizeDirectories moves the files related to endpoint BPF program
    91  // compilation to their according directories if compilation of BPF was
    92  // necessary for the endpoint.
    93  // Returns the original regenerationError if regenerationError was non-nil,
    94  // or if any updates to directories for the endpoint's directories fails.
    95  // Must be called with endpoint.mutex Lock()ed.
    96  func (e *Endpoint) synchronizeDirectories(origDir string) error {
    97  	scopedLog := e.getLogger()
    98  	debugLogEnabled := logging.CanLogAt(scopedLog.Logger, logrus.DebugLevel)
    99  
   100  	scopedLog.Debug("synchronizing directories")
   101  
   102  	tmpDir := e.NextDirectoryPath()
   103  
   104  	// Check if an existing endpoint directory exists, e.g.
   105  	// /var/run/cilium/state/1111
   106  	_, err := os.Stat(origDir)
   107  	switch {
   108  
   109  	// An endpoint directory already exists. We need to back it up before attempting
   110  	// to move the new directory in its place so we can attempt recovery.
   111  	case !os.IsNotExist(err):
   112  		if err := copyExistingState(origDir, tmpDir); err != nil {
   113  			scopedLog.WithError(err).Debugf("unable to copy state "+
   114  				"from %s into the new directory %s.", tmpDir, origDir)
   115  		}
   116  
   117  		// Atomically exchange the two directories.
   118  		if err := unix.Renameat2(unix.AT_FDCWD, tmpDir, unix.AT_FDCWD, origDir, unix.RENAME_EXCHANGE); err != nil {
   119  			return fmt.Errorf("unable to exchange %s with %s: %w", origDir, tmpDir, err)
   120  		}
   121  
   122  	// No existing endpoint directory, synchronizing the directory is a
   123  	// simple move
   124  	default:
   125  		// Make temporary directory the new endpoint directory
   126  		if debugLogEnabled {
   127  			scopedLog.WithFields(logrus.Fields{
   128  				"temporaryDirectory": tmpDir,
   129  				"originalDirectory":  origDir,
   130  			}).Debug("attempting to make temporary directory new directory for endpoint programs")
   131  		}
   132  
   133  		if err := os.Rename(tmpDir, origDir); err != nil {
   134  			return fmt.Errorf("atomic endpoint directory move failed: %w", err)
   135  		}
   136  	}
   137  
   138  	// The build succeeded and is in place, any eventual existing failure
   139  	// directory can be removed.
   140  	e.removeDirectory(e.FailedDirectoryPath())
   141  
   142  	return nil
   143  }
   144  
   145  func (e *Endpoint) removeDirectory(path string) error {
   146  	if logger := e.getLogger(); logging.CanLogAt(logger.Logger, logrus.DebugLevel) {
   147  		logger.WithField("directory", path).Debug("removing directory")
   148  	}
   149  	return os.RemoveAll(path)
   150  }
   151  
   152  func (e *Endpoint) removeDirectories() {
   153  	e.removeDirectory(e.DirectoryPath())
   154  	e.removeDirectory(e.FailedDirectoryPath())
   155  	e.removeDirectory(e.NextDirectoryPath())
   156  }