k8s.io/kubernetes@v1.29.3/pkg/util/procfs/procfs_linux.go (about) 1 //go:build linux 2 // +build linux 3 4 /* 5 Copyright 2015 The Kubernetes Authors. 6 7 Licensed under the Apache License, Version 2.0 (the "License"); 8 you may not use this file except in compliance with the License. 9 You may obtain a copy of the License at 10 11 http://www.apache.org/licenses/LICENSE-2.0 12 13 Unless required by applicable law or agreed to in writing, software 14 distributed under the License is distributed on an "AS IS" BASIS, 15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 See the License for the specific language governing permissions and 17 limitations under the License. 18 */ 19 20 package procfs 21 22 import ( 23 "bytes" 24 "fmt" 25 "io" 26 "os" 27 "path" 28 "path/filepath" 29 "regexp" 30 "strconv" 31 "strings" 32 "syscall" 33 "unicode" 34 35 utilerrors "k8s.io/apimachinery/pkg/util/errors" 36 "k8s.io/klog/v2" 37 ) 38 39 // ProcFS provides a helper for getting container name via pid. 40 type ProcFS struct{} 41 42 func containerNameFromProcCgroup(content string) (string, error) { 43 lines := strings.Split(content, "\n") 44 for _, line := range lines { 45 entries := strings.SplitN(line, ":", 3) 46 if len(entries) == 3 && entries[1] == "devices" { 47 return strings.TrimSpace(entries[2]), nil 48 } 49 } 50 return "", fmt.Errorf("could not find devices cgroup location") 51 } 52 53 // GetFullContainerName gets the container name given the root process id of the container. 54 // E.g. if the devices cgroup for the container is stored in /sys/fs/cgroup/devices/docker/nginx, 55 // return docker/nginx. Assumes that the process is part of exactly one cgroup hierarchy. 56 func (pfs *ProcFS) GetFullContainerName(pid int) (string, error) { 57 filePath := path.Join("/proc", strconv.Itoa(pid), "cgroup") 58 content, err := os.ReadFile(filePath) 59 if err != nil { 60 if os.IsNotExist(err) { 61 return "", os.ErrNotExist 62 } 63 return "", err 64 } 65 return containerNameFromProcCgroup(string(content)) 66 } 67 68 // PKill finds process(es) using a regular expression and send a specified 69 // signal to each process. 70 func PKill(name string, sig syscall.Signal) error { 71 if len(name) == 0 { 72 return fmt.Errorf("name should not be empty") 73 } 74 re, err := regexp.Compile(name) 75 if err != nil { 76 return err 77 } 78 pids := getPids(re) 79 if len(pids) == 0 { 80 return fmt.Errorf("unable to fetch pids for process name : %q", name) 81 } 82 errList := []error{} 83 for _, pid := range pids { 84 if err = syscall.Kill(pid, sig); err != nil { 85 errList = append(errList, err) 86 } 87 } 88 return utilerrors.NewAggregate(errList) 89 } 90 91 // PidOf finds process(es) with a specified name (regexp match) 92 // and return their pid(s). 93 func PidOf(name string) ([]int, error) { 94 if len(name) == 0 { 95 return []int{}, fmt.Errorf("name should not be empty") 96 } 97 re, err := regexp.Compile("(^|/)" + name + "$") 98 if err != nil { 99 return []int{}, err 100 } 101 return getPids(re), nil 102 } 103 104 func getPids(re *regexp.Regexp) []int { 105 pids := []int{} 106 107 dirFD, err := os.Open("/proc") 108 if err != nil { 109 return nil 110 } 111 defer dirFD.Close() 112 113 for { 114 // Read a small number at a time in case there are many entries, we don't want to 115 // allocate a lot here. 116 ls, err := dirFD.Readdir(10) 117 if err == io.EOF { 118 break 119 } 120 if err != nil { 121 return nil 122 } 123 124 for _, entry := range ls { 125 if !entry.IsDir() { 126 continue 127 } 128 129 // If the directory is not a number (i.e. not a PID), skip it 130 pid, err := strconv.Atoi(entry.Name()) 131 if err != nil { 132 continue 133 } 134 135 cmdline, err := os.ReadFile(filepath.Join("/proc", entry.Name(), "cmdline")) 136 if err != nil { 137 klog.V(4).Infof("Error reading file %s: %+v", filepath.Join("/proc", entry.Name(), "cmdline"), err) 138 continue 139 } 140 141 // The bytes we read have '\0' as a separator for the command line 142 parts := bytes.SplitN(cmdline, []byte{0}, 2) 143 if len(parts) == 0 { 144 continue 145 } 146 // Split the command line itself we are interested in just the first part 147 exe := strings.FieldsFunc(string(parts[0]), func(c rune) bool { 148 return unicode.IsSpace(c) || c == ':' 149 }) 150 if len(exe) == 0 { 151 continue 152 } 153 // Check if the name of the executable is what we are looking for 154 if re.MatchString(exe[0]) { 155 // Grab the PID from the directory path 156 pids = append(pids, pid) 157 } 158 } 159 } 160 161 return pids 162 }