github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/filesystem/permissions_posix.go (about)

     1  //go:build !windows
     2  
     3  package filesystem
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  	"os"
     9  	userpkg "os/user"
    10  	"strconv"
    11  )
    12  
    13  // OwnershipSpecification is an opaque type that encodes specification of file
    14  // and/or directory ownership.
    15  type OwnershipSpecification struct {
    16  	// ownerID encodes the POSIX user ID associated with the ownership
    17  	// specification. A value of -1 indicates the absence of specification. The
    18  	// availability of -1 as a sentinel value for omission is guaranteed by the
    19  	// POSIX definition of chmod.
    20  	ownerID int
    21  	// groupID encodes the POSIX user ID associated with the ownership
    22  	// specification. A value of -1 indicates the absence of specification. The
    23  	// availability of -1 as a sentinel value for omission is guaranteed by the
    24  	// POSIX definition of chmod.
    25  	groupID int
    26  }
    27  
    28  // NewOwnershipSpecification parsers owner and group specifications and resolves
    29  // their system-level identifiers.
    30  func NewOwnershipSpecification(owner, group string) (*OwnershipSpecification, error) {
    31  	// Attempt to parse and look up owner user, if specified.
    32  	ownerID := -1
    33  	if owner != "" {
    34  		switch kind, identifier := ParseOwnershipIdentifier(owner); kind {
    35  		case OwnershipIdentifierKindInvalid:
    36  			return nil, errors.New("invalid user specification")
    37  		case OwnershipIdentifierKindPOSIXID:
    38  			if u, err := strconv.Atoi(identifier); err != nil {
    39  				return nil, fmt.Errorf("unable to convert user ID to numeric value: %w", err)
    40  			} else if u < 0 {
    41  				return nil, errors.New("negative user ID")
    42  			} else {
    43  				ownerID = u
    44  			}
    45  		case OwnershipIdentifierKindWindowsSID:
    46  			return nil, errors.New("Windows SIDs not supported on POSIX systems")
    47  		case OwnershipIdentifierKindName:
    48  			if userObject, err := userpkg.Lookup(identifier); err != nil {
    49  				return nil, fmt.Errorf("unable to lookup user by ID: %w", err)
    50  			} else if u, err := strconv.Atoi(userObject.Uid); err != nil {
    51  				return nil, fmt.Errorf("unable to convert user ID to numeric value: %w", err)
    52  			} else if u < 0 {
    53  				return nil, errors.New("negative user ID retrieved")
    54  			} else {
    55  				ownerID = u
    56  			}
    57  		default:
    58  			panic("unhandled ownership identifier kind")
    59  		}
    60  	}
    61  
    62  	// Attempt to parse and look up group, if specified.
    63  	groupID := -1
    64  	if group != "" {
    65  		switch kind, identifier := ParseOwnershipIdentifier(group); kind {
    66  		case OwnershipIdentifierKindInvalid:
    67  			return nil, errors.New("invalid group specification")
    68  		case OwnershipIdentifierKindPOSIXID:
    69  			if g, err := strconv.Atoi(identifier); err != nil {
    70  				return nil, fmt.Errorf("unable to convert group ID to numeric value: %w", err)
    71  			} else if g < 0 {
    72  				return nil, errors.New("negative group ID")
    73  			} else {
    74  				groupID = g
    75  			}
    76  		case OwnershipIdentifierKindWindowsSID:
    77  			return nil, errors.New("Windows SIDs not supported on POSIX systems")
    78  		case OwnershipIdentifierKindName:
    79  			if groupObject, err := userpkg.LookupGroup(identifier); err != nil {
    80  				return nil, fmt.Errorf("unable to lookup group by ID: %w", err)
    81  			} else if g, err := strconv.Atoi(groupObject.Gid); err != nil {
    82  				return nil, fmt.Errorf("unable to convert group ID to numeric value: %w", err)
    83  			} else if g < 0 {
    84  				return nil, errors.New("negative group ID retrieved")
    85  			} else {
    86  				groupID = g
    87  			}
    88  		default:
    89  			panic("unhandled ownership identifier kind")
    90  		}
    91  	}
    92  
    93  	// Success.
    94  	return &OwnershipSpecification{
    95  		ownerID: ownerID,
    96  		groupID: groupID,
    97  	}, nil
    98  }
    99  
   100  // SetPermissionsByPath sets the permissions on the content at the specified
   101  // path. Ownership information is set first, followed by permissions extracted
   102  // from the mode using ModePermissionsMask. Ownership setting can be skipped
   103  // completely by providing a nil OwnershipSpecification or a specification with
   104  // both components unset. An OwnershipSpecification may also include only
   105  // certain components, in which case only those components will be set.
   106  // Permission setting can be skipped by providing a mode value that yields 0
   107  // after permission bit masking.
   108  func SetPermissionsByPath(path string, ownership *OwnershipSpecification, mode Mode) error {
   109  	// Set ownership information, if specified.
   110  	if ownership != nil && (ownership.ownerID != -1 || ownership.groupID != -1) {
   111  		if err := os.Chown(path, ownership.ownerID, ownership.groupID); err != nil {
   112  			return fmt.Errorf("unable to set ownership information: %w", err)
   113  		}
   114  	}
   115  
   116  	// Set permissions, if specified.
   117  	mode = mode & ModePermissionsMask
   118  	if mode != 0 {
   119  		if err := os.Chmod(path, os.FileMode(mode)); err != nil {
   120  			return fmt.Errorf("unable to set permission bits: %w", err)
   121  		}
   122  	}
   123  
   124  	// Success.
   125  	return nil
   126  }