File: passwd.go

package info (click to toggle)
singularity-container 4.0.3%2Bds1-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 21,672 kB
  • sloc: asm: 3,857; sh: 2,125; ansic: 1,677; awk: 414; makefile: 110; python: 99
file content (100 lines) | stat: -rw-r--r-- 3,148 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// Copyright (c) 2018-2023, Sylabs Inc. All rights reserved.
// Copyright (c) Contributors to the Apptainer project, established as
//   Apptainer a Series of LF Projects LLC.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE.md file distributed with the sources of this project regarding your
// rights to use or distribute this software.

package files

import (
	"bufio"
	"fmt"
	"os"
	"strings"

	pwd "github.com/astromechza/etcpwdparse"

	"github.com/sylabs/singularity/v4/internal/pkg/util/fs"
	"github.com/sylabs/singularity/v4/internal/pkg/util/user"
	"github.com/sylabs/singularity/v4/pkg/sylog"
)

type UserGroupLookup interface {
	GetPwUID(uint32) (*user.User, error)
	GetGrGID(uint32) (*user.Group, error)
	Getgroups() ([]int, error)
}

// Passwd creates a passwd template based on content of file provided in path,
// updates content with current user information and returns content.
func Passwd(path string, home string, uid int, customLookup UserGroupLookup) (content []byte, err error) {
	sylog.Verbosef("Checking for template passwd file: %s", path)
	if !fs.IsFile(path) {
		return content, fmt.Errorf("passwd file doesn't exist in container, not updating")
	}

	sylog.Verbosef("Creating passwd content")
	file, err := os.Open(path)
	if err != nil {
		return content, fmt.Errorf("error opening passwd file %#v for reading: %v", path, err)
	}
	scanner := bufio.NewScanner(file)
	scanner.Split(bufio.ScanLines)
	lines := []string{}
	for scanner.Scan() {
		lines = append(lines, scanner.Text())
	}
	file.Close()

	getPwUID := user.GetPwUID
	if customLookup != nil {
		getPwUID = customLookup.GetPwUID
	}

	pwInfo, err := getPwUID(uint32(uid))
	if err != nil {
		return content, err
	}

	homeDir := pwInfo.Dir
	if home != "" {
		homeDir = home
	}
	userInfo := makePasswdLine(pwInfo.Name, pwInfo.UID, pwInfo.GID, pwInfo.Gecos, homeDir, pwInfo.Shell)

	sylog.Verbosef("Creating template passwd file and injecting user data: %s", path)
	userExists := false
	for i, line := range lines {
		if line == "" {
			continue
		}

		entry, err := pwd.ParsePasswdLine(line)
		if err != nil {
			return content, fmt.Errorf("failed to parse this /etc/passwd line in container: %#v (%s)", line, err)
		}
		if entry.Uid() == uid {
			// If user already exists in container, rebuild their passwd info preserving their original shell value
			userExists = true
			origLine := lines[i]
			revisedLine := makePasswdLine(pwInfo.Name, pwInfo.UID, pwInfo.GID, pwInfo.Gecos, homeDir, entry.Shell())
			sylog.Debugf("replacing line in passwd file: %q instead of %q", revisedLine, origLine)
			lines[i] = revisedLine
			break
		}
	}
	if !userExists {
		sylog.Debugf("appending new line to passwd file: %q", userInfo)
		lines = append(lines, userInfo)
	}

	// Add this so that the following strings.Join call will result in text that ends in a newline
	lines = append(lines, "")

	return []byte(strings.Join(lines, "\n")), nil
}

func makePasswdLine(name string, uid uint32, gid uint32, gecos string, homedir string, shell string) string {
	return fmt.Sprintf("%s:x:%d:%d:%s:%s:%s", name, uid, gid, gecos, homedir, shell)
}