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

     1  package filesystem
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	userpkg "os/user"
     8  
     9  	"golang.org/x/sys/windows"
    10  
    11  	aclapi "github.com/hectane/go-acl/api"
    12  )
    13  
    14  // OwnershipSpecification is an opaque type that encodes specification of file
    15  // and/or directory ownership.
    16  type OwnershipSpecification struct {
    17  	// ownerSID encodes the Windows owner SID associated with the ownership
    18  	// specification. It may represent either a user SID or a group SID. A nil
    19  	// value indicates the absence of specification.
    20  	ownerSID *windows.SID
    21  	// groupSid encodes the Windows group SID associated with the ownership
    22  	// specification. A nil value indicates the absence of specification.
    23  	groupSID *windows.SID
    24  }
    25  
    26  // NewOwnershipSpecification parsers owner and group specifications and resolves
    27  // their system-level identifiers.
    28  func NewOwnershipSpecification(owner, group string) (*OwnershipSpecification, error) {
    29  	// Attempt to parse and look up owner, if specified. On Windows, an owner
    30  	// can be either a user or a group.
    31  	var ownerSID *windows.SID
    32  	if owner != "" {
    33  		switch kind, identifier := ParseOwnershipIdentifier(owner); kind {
    34  		case OwnershipIdentifierKindInvalid:
    35  			return nil, errors.New("invalid owner specification")
    36  		case OwnershipIdentifierKindPOSIXID:
    37  			return nil, errors.New("POSIX IDs not supported on Windows systems")
    38  		case OwnershipIdentifierKindWindowsSID:
    39  			// Verify that this SID represents either a user or a group.
    40  			var retrievedSID string
    41  			if userObject, err := userpkg.LookupId(identifier); err == nil {
    42  				retrievedSID = userObject.Uid
    43  			} else if groupObject, err := userpkg.LookupGroupId(identifier); err == nil {
    44  				retrievedSID = groupObject.Gid
    45  			} else {
    46  				return nil, errors.New("unable to find user or group with specified owner SID")
    47  			}
    48  
    49  			// Convert the retrieved SID to a string.
    50  			if s, err := windows.StringToSid(retrievedSID); err != nil {
    51  				return nil, fmt.Errorf("unable to convert SID string to object: %w", err)
    52  			} else {
    53  				ownerSID = s
    54  			}
    55  		case OwnershipIdentifierKindName:
    56  			// Verify that this name represents either a user or a group and
    57  			// retrieve the associated SID.
    58  			var retrievedSID string
    59  			if userObject, err := userpkg.Lookup(identifier); err == nil {
    60  				retrievedSID = userObject.Uid
    61  			} else if groupObject, err := userpkg.LookupGroup(identifier); err == nil {
    62  				retrievedSID = groupObject.Gid
    63  			} else {
    64  				return nil, errors.New("unable to find user or group with specified owner name")
    65  			}
    66  
    67  			// Convert the retrieved SID to a string.
    68  			if s, err := windows.StringToSid(retrievedSID); err != nil {
    69  				return nil, fmt.Errorf("unable to convert SID string to object: %w", err)
    70  			} else {
    71  				ownerSID = s
    72  			}
    73  		default:
    74  			panic("unhandled ownership identifier kind")
    75  		}
    76  	}
    77  
    78  	// Attempt to parse and look up group, if specified.
    79  	var groupSID *windows.SID
    80  	if group != "" {
    81  		switch kind, identifier := ParseOwnershipIdentifier(group); kind {
    82  		case OwnershipIdentifierKindInvalid:
    83  			return nil, errors.New("invalid group specification")
    84  		case OwnershipIdentifierKindPOSIXID:
    85  			return nil, errors.New("POSIX IDs not supported on Windows systems")
    86  		case OwnershipIdentifierKindWindowsSID:
    87  			if groupObject, err := userpkg.LookupGroupId(identifier); err != nil {
    88  				return nil, fmt.Errorf("unable to lookup group by ID: %w", err)
    89  			} else if g, err := windows.StringToSid(groupObject.Gid); err != nil {
    90  				return nil, fmt.Errorf("unable to convert SID string to object: %w", err)
    91  			} else {
    92  				groupSID = g
    93  			}
    94  		case OwnershipIdentifierKindName:
    95  			if groupObject, err := userpkg.LookupGroup(identifier); err != nil {
    96  				return nil, fmt.Errorf("unable to lookup group by ID: %w", err)
    97  			} else if g, err := windows.StringToSid(groupObject.Gid); err != nil {
    98  				return nil, fmt.Errorf("unable to convert SID string to object: %w", err)
    99  			} else {
   100  				groupSID = g
   101  			}
   102  		default:
   103  			panic("unhandled ownership identifier kind")
   104  		}
   105  	}
   106  
   107  	// Success.
   108  	return &OwnershipSpecification{
   109  		ownerSID: ownerSID,
   110  		groupSID: groupSID,
   111  	}, nil
   112  }
   113  
   114  // SetPermissionsByPath sets the permissions on the content at the specified
   115  // path. Ownership information is set first, followed by permissions extracted
   116  // from the mode using ModePermissionsMask. Ownership setting can be skipped
   117  // completely by providing a nil OwnershipSpecification or a specification with
   118  // both components unset. An OwnershipSpecification may also include only
   119  // certain components, in which case only those components will be set.
   120  // Permission setting can be skipped by providing a mode value that yields 0
   121  // after permission bit masking.
   122  func SetPermissionsByPath(path string, ownership *OwnershipSpecification, mode Mode) error {
   123  	// Set ownership information, if specified.
   124  	if ownership != nil && (ownership.ownerSID != nil || ownership.groupSID != nil) {
   125  		// Compute the information that we're going to set.
   126  		var information uint32
   127  		if ownership.ownerSID != nil {
   128  			information |= aclapi.OWNER_SECURITY_INFORMATION
   129  		}
   130  		if ownership.groupSID != nil {
   131  			information |= aclapi.GROUP_SECURITY_INFORMATION
   132  		}
   133  
   134  		// Set the information.
   135  		if err := aclapi.SetNamedSecurityInfo(
   136  			path,
   137  			aclapi.SE_FILE_OBJECT,
   138  			information,
   139  			ownership.ownerSID,
   140  			ownership.groupSID,
   141  			0,
   142  			0,
   143  		); err != nil {
   144  			return fmt.Errorf("unable to set ownership information: %w", err)
   145  		}
   146  	}
   147  
   148  	// Set permissions, if specified.
   149  	mode = mode & ModePermissionsMask
   150  	if mode != 0 {
   151  		if err := os.Chmod(path, os.FileMode(mode)); err != nil {
   152  			return fmt.Errorf("unable to set permission bits: %w", err)
   153  		}
   154  	}
   155  
   156  	// Success.
   157  	return nil
   158  }