github.com/apptainer/singularity@v3.1.1+incompatible/pkg/util/fs/proc/proc.go (about)

     1  // Copyright (c) 2018, Sylabs Inc. All rights reserved.
     2  // This software is licensed under a 3-clause BSD license. Please consult the
     3  // LICENSE.md file distributed with the sources of this project regarding your
     4  // rights to use or distribute this software.
     5  
     6  package proc
     7  
     8  import (
     9  	"bufio"
    10  	"fmt"
    11  	"os"
    12  	"path/filepath"
    13  	"strconv"
    14  	"strings"
    15  	"syscall"
    16  )
    17  
    18  // HasFilesystem returns whether kernel support filesystem or not
    19  func HasFilesystem(fs string) (bool, error) {
    20  	p, err := os.Open("/proc/filesystems")
    21  	if err != nil {
    22  		return false, fmt.Errorf("can't open /proc/filesystems: %s", err)
    23  	}
    24  	defer p.Close()
    25  
    26  	suffix := "\t" + fs
    27  	scanner := bufio.NewScanner(p)
    28  	for scanner.Scan() {
    29  		if strings.HasSuffix(scanner.Text(), suffix) {
    30  			return true, nil
    31  		}
    32  	}
    33  	return false, nil
    34  }
    35  
    36  // ParseMountInfo parses mountinfo pointing to path and returns a map
    37  // of parent mount points with associated child mount points
    38  func ParseMountInfo(path string) (map[string][]string, error) {
    39  	mp := make(map[string][]string)
    40  	mountlist := make(map[string][]string)
    41  
    42  	p, err := os.Open(path)
    43  	if err != nil {
    44  		return mp, fmt.Errorf("can't open %s: %s", path, err)
    45  	}
    46  	defer p.Close()
    47  
    48  	scanner := bufio.NewScanner(p)
    49  	for scanner.Scan() {
    50  		fields := strings.Fields(scanner.Text())
    51  		mountlist[fields[0]] = fields
    52  	}
    53  	for k := range mountlist {
    54  		if i, ok := mountlist[mountlist[k][1]]; ok {
    55  			if mountlist[k][4] != i[4] {
    56  				mp[i[4]] = append(mp[i[4]], mountlist[k][4])
    57  			}
    58  		}
    59  	}
    60  	return mp, nil
    61  }
    62  
    63  // ParentMount parses mountinfo and return the path of parent
    64  // mount point for which the provided path is mounted in
    65  func ParentMount(path string) (string, error) {
    66  	var mountPoints []string
    67  	parent := "/"
    68  
    69  	resolved, err := filepath.EvalSymlinks(path)
    70  	if err != nil {
    71  		return parent, err
    72  	}
    73  
    74  	p, err := os.Open("/proc/self/mountinfo")
    75  	if err != nil {
    76  		return parent, fmt.Errorf("can't open /proc/self/mountinfo: %s", err)
    77  	}
    78  	defer p.Close()
    79  
    80  	scanner := bufio.NewScanner(p)
    81  	for scanner.Scan() {
    82  		fields := strings.Fields(scanner.Text())
    83  		mountPoints = append(mountPoints, fields[4])
    84  	}
    85  
    86  	for resolved != "/" {
    87  		for _, point := range mountPoints {
    88  			if point == resolved {
    89  				return point, nil
    90  			}
    91  		}
    92  		resolved = filepath.Dir(resolved)
    93  	}
    94  
    95  	return parent, nil
    96  }
    97  
    98  // ExtractPid returns a pid extracted from path of type "/proc/1"
    99  func ExtractPid(path string) (pid uint, err error) {
   100  	n, err := fmt.Sscanf(path, "/proc/%d", &pid)
   101  	if n != 1 {
   102  		return 0, fmt.Errorf("can't extract PID from %s: %s", path, err)
   103  	}
   104  	return
   105  }
   106  
   107  // CountChilds returns the number of child processes for a given process id
   108  func CountChilds(pid int) (int, error) {
   109  	childs := 0
   110  
   111  	parentProc := fmt.Sprintf("/proc/%d", pid)
   112  	if _, err := os.Stat(parentProc); os.IsNotExist(err) {
   113  		return 0, fmt.Errorf("pid %d doesn't exists", pid)
   114  	}
   115  
   116  	parentLine := fmt.Sprintf("PPid:\t%d", pid)
   117  	pattern := filepath.Join("/proc", "[0-9]*")
   118  
   119  	matches, _ := filepath.Glob(pattern)
   120  	for _, path := range matches {
   121  		r, err := os.Open(filepath.Join(path, "status"))
   122  		if err != nil {
   123  			continue
   124  		}
   125  		scanner := bufio.NewScanner(r)
   126  		for scanner.Scan() {
   127  			if scanner.Text() == parentLine {
   128  				childs++
   129  				break
   130  			}
   131  		}
   132  		r.Close()
   133  	}
   134  	return childs, nil
   135  }
   136  
   137  // ReadIDMap reads uid_map or gid_map and returns both container ID
   138  // and host ID
   139  func ReadIDMap(path string) (uint32, uint32, error) {
   140  	r, err := os.Open(path)
   141  	if err != nil {
   142  		return 0, 0, err
   143  	}
   144  	defer r.Close()
   145  
   146  	scanner := bufio.NewScanner(r)
   147  	scanner.Scan()
   148  	fields := strings.Fields(scanner.Text())
   149  
   150  	containerID, err := strconv.ParseUint(fields[0], 10, 32)
   151  	if err != nil {
   152  		return 0, 0, err
   153  	}
   154  	hostID, err := strconv.ParseUint(fields[1], 10, 32)
   155  	if err != nil {
   156  		return 0, 0, err
   157  	}
   158  
   159  	return uint32(containerID), uint32(hostID), nil
   160  }
   161  
   162  // SetOOMScoreAdj sets OOM score for process with pid
   163  func SetOOMScoreAdj(pid int, score *int) error {
   164  	if score != nil {
   165  		path := fmt.Sprintf("/proc/%d/oom_score_adj", pid)
   166  
   167  		f, err := os.OpenFile(path, os.O_WRONLY, 0)
   168  		if err != nil {
   169  			return fmt.Errorf("failed to open oom_score_adj: %s", err)
   170  		}
   171  		if _, err := fmt.Fprintf(f, "%d", *score); err != nil {
   172  			return fmt.Errorf("failed to set oom_score_adj: %s", err)
   173  		}
   174  
   175  		f.Close()
   176  	}
   177  	return nil
   178  }
   179  
   180  // HasNamespace checks if host namespace and container namespace
   181  // are different.
   182  func HasNamespace(pid int, nstype string) (bool, error) {
   183  	var st1 syscall.Stat_t
   184  	var st2 syscall.Stat_t
   185  
   186  	has := false
   187  
   188  	processOne := fmt.Sprintf("/proc/%d/ns/%s", pid, nstype)
   189  	processTwo := fmt.Sprintf("/proc/self/ns/%s", nstype)
   190  
   191  	if err := syscall.Stat(processOne, &st1); err != nil {
   192  		if os.IsNotExist(err) {
   193  			return has, nil
   194  		}
   195  		return has, err
   196  	}
   197  	if err := syscall.Stat(processTwo, &st2); err != nil {
   198  		if os.IsNotExist(err) {
   199  			return has, nil
   200  		}
   201  		return has, err
   202  	}
   203  
   204  	if st1.Ino != st2.Ino {
   205  		has = true
   206  	}
   207  
   208  	return has, nil
   209  }