aghos: add stat
This commit is contained in:
parent
72d7c0f881
commit
dcbabaf4e3
|
@ -18,6 +18,12 @@ func Mkdir(name string, perm fs.FileMode) (err error) {
|
||||||
return mkdir(name, perm)
|
return mkdir(name, perm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(e.burkov): !! add Stat.
|
// Stat is an extension for [os.Stat] that properly handles Windows access
|
||||||
|
// rights.
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): !! use.
|
||||||
|
func Stat(name string) (fi fs.FileInfo, err error) {
|
||||||
|
return stat(name)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(e.burkov): !! add tests.
|
// TODO(e.burkov): !! add tests.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build !windows
|
//go:build unix
|
||||||
|
|
||||||
package aghos
|
package aghos
|
||||||
|
|
||||||
|
@ -16,3 +16,8 @@ func chmod(name string, perm fs.FileMode) (err error) {
|
||||||
func mkdir(name string, perm fs.FileMode) (err error) {
|
func mkdir(name string, perm fs.FileMode) (err error) {
|
||||||
return os.Mkdir(name, perm)
|
return os.Mkdir(name, perm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stat is a Unix implementation of [Stat].
|
||||||
|
func stat(name string) (fi os.FileInfo, err error) {
|
||||||
|
return os.Stat(name)
|
||||||
|
}
|
||||||
|
|
|
@ -9,64 +9,134 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/container"
|
||||||
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// fileInfo is a Windows implementation of [fs.FileInfo], that contains the
|
||||||
|
// filemode converted from the security descriptor.
|
||||||
|
type fileInfo struct {
|
||||||
|
// fs.FileInfo is embedded to provide the default implementations and info
|
||||||
|
// successfully retrieved by [os.Stat].
|
||||||
|
fs.FileInfo
|
||||||
|
|
||||||
|
// mode is the file mode converted from the security descriptor.
|
||||||
|
mode fs.FileMode
|
||||||
|
}
|
||||||
|
|
||||||
|
// type check
|
||||||
|
var _ fs.FileInfo = (*fileInfo)(nil)
|
||||||
|
|
||||||
|
// Mode implements [fs.FileInfo.Mode] for [*fileInfo].
|
||||||
|
func (fi *fileInfo) Mode() (mode fs.FileMode) { return fi.mode }
|
||||||
|
|
||||||
|
// stat is a Windows implementation of [Stat].
|
||||||
|
func stat(name string) (fi os.FileInfo, err error) {
|
||||||
|
fi, err = os.Stat(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const objectType windows.SE_OBJECT_TYPE = windows.SE_FILE_OBJECT
|
||||||
|
|
||||||
|
secInfo := windows.SECURITY_INFORMATION(0 |
|
||||||
|
windows.OWNER_SECURITY_INFORMATION |
|
||||||
|
windows.GROUP_SECURITY_INFORMATION |
|
||||||
|
windows.DACL_SECURITY_INFORMATION |
|
||||||
|
windows.PROTECTED_DACL_SECURITY_INFORMATION,
|
||||||
|
)
|
||||||
|
|
||||||
|
sd, err := windows.GetNamedSecurityInfo(fi.Name(), objectType, secInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting security descriptor: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dacl, _, err := sd.DACL()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting discretionary access control list: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
owner, _, err := sd.Owner()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting owner sid: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
group, _, err := sd.Group()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting group sid: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ownerMask, groupMask, otherMask windows.ACCESS_MASK
|
||||||
|
for i := range uint32(dacl.AceCount) {
|
||||||
|
var ace *windows.ACCESS_ALLOWED_ACE
|
||||||
|
err = windows.GetAce(dacl, i, &ace)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting access control entry at index %d: %w", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
entrySid := (*windows.SID)(unsafe.Pointer(&ace.SidStart))
|
||||||
|
switch {
|
||||||
|
case entrySid.Equals(owner):
|
||||||
|
ownerMask |= ace.Mask
|
||||||
|
case entrySid.Equals(group):
|
||||||
|
groupMask |= ace.Mask
|
||||||
|
default:
|
||||||
|
otherMask = ace.Mask
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &fileInfo{
|
||||||
|
FileInfo: fi,
|
||||||
|
mode: masksToPerm(ownerMask, groupMask, otherMask),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// chmod is a Windows implementation of [Chmod].
|
// chmod is a Windows implementation of [Chmod].
|
||||||
func chmod(name string, perm fs.FileMode) (err error) {
|
func chmod(name string, perm fs.FileMode) (err error) {
|
||||||
const objectType windows.SE_OBJECT_TYPE = windows.SE_FILE_OBJECT
|
const objectType windows.SE_OBJECT_TYPE = windows.SE_FILE_OBJECT
|
||||||
|
|
||||||
entries := make([]windows.EXPLICIT_ACCESS, 0, 3)
|
entries := make([]windows.EXPLICIT_ACCESS, 0, 3)
|
||||||
creatorMask, groupMask, worldMask := modeToMasks(perm.Perm())
|
creatorMask, groupMask, worldMask := modeToMasks(perm)
|
||||||
|
|
||||||
if creatorMask > 0 {
|
sidMasks := container.KeyValues[windows.WELL_KNOWN_SID_TYPE, windows.ACCESS_MASK]{{
|
||||||
var creator *windows.TRUSTEE
|
Key: windows.WinCreatorOwnerSid,
|
||||||
creator, err = newWellKnownTrustee(windows.WinCreatorOwnerSid)
|
Value: creatorMask,
|
||||||
|
}, {
|
||||||
|
Key: windows.WinCreatorGroupSid,
|
||||||
|
Value: groupMask,
|
||||||
|
}, {
|
||||||
|
Key: windows.WinWorldSid,
|
||||||
|
Value: worldMask,
|
||||||
|
}}
|
||||||
|
|
||||||
|
var errs []error
|
||||||
|
for _, sidMask := range sidMasks {
|
||||||
|
if sidMask.Value == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var trustee *windows.TRUSTEE
|
||||||
|
trustee, err = newWellKnownTrustee(sidMask.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("creating owner trustee: %w", err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
entries = append(entries, windows.EXPLICIT_ACCESS{
|
entries = append(entries, windows.EXPLICIT_ACCESS{
|
||||||
AccessPermissions: creatorMask,
|
AccessPermissions: sidMask.Value,
|
||||||
AccessMode: windows.SET_ACCESS,
|
AccessMode: windows.GRANT_ACCESS,
|
||||||
Inheritance: windows.NO_INHERITANCE,
|
Inheritance: windows.NO_INHERITANCE,
|
||||||
Trustee: *creator,
|
Trustee: *trustee,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if groupMask > 0 {
|
if err = errors.Join(errs...); err != nil {
|
||||||
var group *windows.TRUSTEE
|
return fmt.Errorf("creating access control entries: %w", err)
|
||||||
group, err = newWellKnownTrustee(windows.WinCreatorGroupSid)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating group trustee: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
entries = append(entries, windows.EXPLICIT_ACCESS{
|
|
||||||
AccessPermissions: groupMask,
|
|
||||||
AccessMode: windows.SET_ACCESS,
|
|
||||||
Inheritance: windows.NO_INHERITANCE,
|
|
||||||
Trustee: *group,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if worldMask > 0 {
|
|
||||||
var world *windows.TRUSTEE
|
|
||||||
world, err = newWellKnownTrustee(windows.WinWorldSid)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating everyone trustee: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
entries = append(entries, windows.EXPLICIT_ACCESS{
|
|
||||||
AccessPermissions: worldMask,
|
|
||||||
AccessMode: windows.SET_ACCESS,
|
|
||||||
Inheritance: windows.NO_INHERITANCE,
|
|
||||||
Trustee: *world,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
acl, err := windows.ACLFromEntries(entries, nil)
|
acl, err := windows.ACLFromEntries(entries, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("creating acl: %w", err)
|
return fmt.Errorf("creating access control list: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
secInfo := windows.SECURITY_INFORMATION(
|
secInfo := windows.SECURITY_INFORMATION(
|
||||||
|
@ -75,164 +145,41 @@ func chmod(name string, perm fs.FileMode) (err error) {
|
||||||
|
|
||||||
err = windows.SetNamedSecurityInfo(name, objectType, secInfo, nil, nil, acl, nil)
|
err = windows.SetNamedSecurityInfo(name, objectType, secInfo, nil, nil, acl, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("setting security information: %w", err)
|
return fmt.Errorf("setting security descriptor: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// mkdir is a Windows implementation of [Mkdir].
|
// mkdir is a Windows implementation of [Mkdir].
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): Consider using [windows.CreateDirectory] instead of
|
||||||
|
// [os.Mkdir] to reduce the number of syscalls.
|
||||||
func mkdir(name string, perm os.FileMode) (err error) {
|
func mkdir(name string, perm os.FileMode) (err error) {
|
||||||
name, err = filepath.Abs(name)
|
name, err = filepath.Abs(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("computing absolute path: %w", err)
|
return fmt.Errorf("computing absolute path: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
entries := make([]windows.EXPLICIT_ACCESS, 0, 3)
|
err = os.Mkdir(name, perm)
|
||||||
creatorMask, groupMask, worldMask := modeToMasks(perm.Perm())
|
|
||||||
|
|
||||||
if creatorMask > 0 {
|
|
||||||
var creator *windows.TRUSTEE
|
|
||||||
creator, err = currentUserTrustee()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("creating owner trustee: %w", err)
|
return fmt.Errorf("creating directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
entries = append(entries, windows.EXPLICIT_ACCESS{
|
defer func() {
|
||||||
AccessPermissions: creatorMask,
|
|
||||||
AccessMode: windows.GRANT_ACCESS,
|
|
||||||
Inheritance: windows.NO_INHERITANCE,
|
|
||||||
Trustee: *creator,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if groupMask > 0 {
|
|
||||||
var group *windows.TRUSTEE
|
|
||||||
group, err = currentUserGroupTrustee()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("creating group trustee: %w", err)
|
err = errors.WithDeferred(err, os.Remove(name))
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
entries = append(entries, windows.EXPLICIT_ACCESS{
|
return chmod(name, perm)
|
||||||
AccessPermissions: groupMask,
|
|
||||||
AccessMode: windows.GRANT_ACCESS,
|
|
||||||
Inheritance: windows.NO_INHERITANCE,
|
|
||||||
Trustee: *group,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if worldMask > 0 {
|
|
||||||
var world *windows.TRUSTEE
|
|
||||||
world, err = newWellKnownTrustee(windows.WinWorldSid)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating everyone trustee: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
entries = append(entries, windows.EXPLICIT_ACCESS{
|
|
||||||
AccessPermissions: worldMask,
|
|
||||||
AccessMode: windows.GRANT_ACCESS,
|
|
||||||
Inheritance: windows.NO_INHERITANCE,
|
|
||||||
Trustee: *world,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
secAttrs, err := newSecAttr(entries)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating security attributes: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
namePntr, err := windows.UTF16PtrFromString(name)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("converting string: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return windows.CreateDirectory(namePntr, secAttrs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// newSecAttr creates a new security attributes structure with the specified
|
|
||||||
// explicit access entries.
|
|
||||||
func newSecAttr(entries []windows.EXPLICIT_ACCESS) (sa *windows.SecurityAttributes, err error) {
|
|
||||||
sd, err := windows.NewSecurityDescriptor()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create security descriptor: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(entries) > 0 {
|
|
||||||
var acl *windows.ACL
|
|
||||||
acl, err = windows.ACLFromEntries(entries, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create ACL from explicit access entries: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = sd.SetDACL(acl, true, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to configure DACL for security desctriptor: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = sd.SetControl(windows.SE_DACL_PROTECTED, windows.SE_DACL_PROTECTED)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to configure protected DACL for security descriptor: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &windows.SecurityAttributes{
|
|
||||||
Length: uint32(unsafe.Sizeof(windows.SecurityAttributes{})),
|
|
||||||
SecurityDescriptor: sd,
|
|
||||||
InheritHandle: 0,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// currentUserTrustee returns a trustee for the current user.
|
|
||||||
func currentUserTrustee() (t *windows.TRUSTEE, err error) {
|
|
||||||
token := windows.GetCurrentProcessToken()
|
|
||||||
tokenUser, err := token.GetTokenUser()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
admins, err := windows.CreateWellKnownSid(windows.WinBuiltinAdministratorsSid)
|
|
||||||
if err != nil {
|
|
||||||
// Don't wrap the error here, as we can't add any additional context.
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sid := tokenUser.User.Sid
|
|
||||||
trusteeType := windows.TRUSTEE_TYPE(windows.TRUSTEE_IS_USER)
|
|
||||||
// TODO(e.burkov): !! consider using IsElevated()
|
|
||||||
if ok, err := token.IsMember(admins); err == nil && ok {
|
|
||||||
sid = admins
|
|
||||||
trusteeType = windows.TRUSTEE_IS_GROUP
|
|
||||||
}
|
|
||||||
|
|
||||||
return &windows.TRUSTEE{
|
|
||||||
TrusteeForm: windows.TRUSTEE_IS_SID,
|
|
||||||
TrusteeType: trusteeType,
|
|
||||||
TrusteeValue: windows.TrusteeValueFromSID(sid),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// currentUserGroupTrustee returns a trustee for the current user's primary
|
|
||||||
// group.
|
|
||||||
func currentUserGroupTrustee() (t *windows.TRUSTEE, err error) {
|
|
||||||
token := windows.GetCurrentProcessToken()
|
|
||||||
group, err := token.GetTokenPrimaryGroup()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &windows.TRUSTEE{
|
|
||||||
TrusteeForm: windows.TRUSTEE_IS_SID,
|
|
||||||
TrusteeType: windows.TRUSTEE_IS_GROUP,
|
|
||||||
TrusteeValue: windows.TrusteeValueFromSID(group.PrimaryGroup),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// newWellKnownTrustee returns a trustee for a well-known SID.
|
// newWellKnownTrustee returns a trustee for a well-known SID.
|
||||||
func newWellKnownTrustee(stype windows.WELL_KNOWN_SID_TYPE) (t *windows.TRUSTEE, err error) {
|
func newWellKnownTrustee(stype windows.WELL_KNOWN_SID_TYPE) (t *windows.TRUSTEE, err error) {
|
||||||
sid, err := windows.CreateWellKnownSid(stype)
|
sid, err := windows.CreateWellKnownSid(stype)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Don't wrap the error here, as we can't add any additional context.
|
return nil, fmt.Errorf("creating sid for type %d: %w", stype, err)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &windows.TRUSTEE{
|
return &windows.TRUSTEE{
|
||||||
|
@ -241,24 +188,19 @@ func newWellKnownTrustee(stype windows.WELL_KNOWN_SID_TYPE) (t *windows.TRUSTEE,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// modeToMasks converts a UNIX file mode to the corresponding Windows access
|
|
||||||
// masks.
|
|
||||||
func modeToMasks(fm os.FileMode) (owner, group, world windows.ACCESS_MASK) {
|
|
||||||
mask := windows.ACCESS_MASK(fm.Perm())
|
|
||||||
|
|
||||||
// Constants reflecting the UNIX permission bits.
|
// Constants reflecting the UNIX permission bits.
|
||||||
const (
|
const (
|
||||||
ownerWrite = 0o200
|
ownerWrite = 0b010000000
|
||||||
groupWrite = 0o020
|
groupWrite = 0b000100000
|
||||||
worldWrite = 0o002
|
worldWrite = 0b000000100
|
||||||
|
|
||||||
ownerAll = 0o700
|
ownerAll = 0b111000000
|
||||||
groupAll = 0o070
|
groupAll = 0b000111000
|
||||||
worldAll = 0o007
|
worldAll = 0b000000111
|
||||||
)
|
)
|
||||||
|
|
||||||
// Constants reflecting the number of bits to shift the UNIX permission bits
|
// Constants reflecting the number of bits to shift the UNIX permission bits to
|
||||||
// to convert them to the generic access rights used by Windows, see
|
// convert them to the generic access rights used by Windows, see
|
||||||
// https://learn.microsoft.com/en-us/windows-hardware/drivers/ifs/access-mask.
|
// https://learn.microsoft.com/en-us/windows-hardware/drivers/ifs/access-mask.
|
||||||
const (
|
const (
|
||||||
genericOwner = 23
|
genericOwner = 23
|
||||||
|
@ -266,18 +208,32 @@ func modeToMasks(fm os.FileMode) (owner, group, world windows.ACCESS_MASK) {
|
||||||
genericWorld = 29
|
genericWorld = 29
|
||||||
)
|
)
|
||||||
|
|
||||||
// Constants reflecting the number of bits to shift the UNIX write
|
// Constants reflecting the number of bits to shift the UNIX write permission
|
||||||
// permission bits to convert them to the delete access rights used by
|
// bits to convert them to the delete access rights used by Windows.
|
||||||
// Windows.
|
|
||||||
const (
|
const (
|
||||||
deleteOwner = 9
|
deleteOwner = 9
|
||||||
deleteGroup = 12
|
deleteGroup = 12
|
||||||
deleteWorld = 15
|
deleteWorld = 15
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// modeToMasks converts a UNIX file mode to the corresponding Windows access
|
||||||
|
// masks.
|
||||||
|
func modeToMasks(fm os.FileMode) (owner, group, world windows.ACCESS_MASK) {
|
||||||
|
mask := windows.ACCESS_MASK(fm.Perm())
|
||||||
|
|
||||||
owner = ((mask & ownerAll) << genericOwner) | ((mask & ownerWrite) << deleteOwner)
|
owner = ((mask & ownerAll) << genericOwner) | ((mask & ownerWrite) << deleteOwner)
|
||||||
group = ((mask & groupAll) << genericGroup) | ((mask & groupWrite) << deleteGroup)
|
group = ((mask & groupAll) << genericGroup) | ((mask & groupWrite) << deleteGroup)
|
||||||
world = ((mask & worldAll) << genericWorld) | ((mask & worldWrite) << deleteWorld)
|
world = ((mask & worldAll) << genericWorld) | ((mask & worldWrite) << deleteWorld)
|
||||||
|
|
||||||
return owner, group, world
|
return owner, group, world
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// masksToPerm converts Windows access masks to the corresponding UNIX file
|
||||||
|
// mode.
|
||||||
|
func masksToPerm(u, g, o windows.ACCESS_MASK) (perm os.FileMode) {
|
||||||
|
perm |= os.FileMode(((u >> genericOwner) & ownerAll) | ((u >> deleteOwner) & ownerWrite))
|
||||||
|
perm |= os.FileMode(((g >> genericGroup) & groupAll) | ((g >> deleteGroup) & groupWrite))
|
||||||
|
perm |= os.FileMode(((o >> genericWorld) & worldAll) | ((o >> deleteWorld) & worldWrite))
|
||||||
|
|
||||||
|
return perm
|
||||||
|
}
|
||||||
|
|
|
@ -52,7 +52,8 @@ set -f -u
|
||||||
#
|
#
|
||||||
# * Package sort is replaced by package slices.
|
# * Package sort is replaced by package slices.
|
||||||
#
|
#
|
||||||
# * Package unsafe is… unsafe.
|
# * Package unsafe is… unsafe. The only exception is Windows-specific code
|
||||||
|
# due to golang.org/x/sys/windows API design.
|
||||||
#
|
#
|
||||||
# Currently, the only standard exception are files generated from protobuf
|
# Currently, the only standard exception are files generated from protobuf
|
||||||
# schemas, which use package reflect. If your project needs more exceptions,
|
# schemas, which use package reflect. If your project needs more exceptions,
|
||||||
|
|
Loading…
Reference in New Issue