...

Source file src/k8s.io/kubernetes/pkg/util/procfs/procfs_linux.go

Documentation: k8s.io/kubernetes/pkg/util/procfs

     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  }
   163  

View as plain text