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