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 }