Pull request 2294: AGDNS-2455 Windows permissions
Closes #7314. Squashed commit of the following: commit f8b6ffeec2f0f96c947cf896c75d05efaca77caf Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Tue Oct 29 14:14:41 2024 +0300 all: fix chlog commit9417b7dc51
Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Mon Oct 28 19:41:30 2024 +0300 aghos: imp doc commitb91f0e72a7
Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Mon Oct 28 19:26:15 2024 +0300 all: rm bin commit9008ee93b1
Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Mon Oct 28 18:23:54 2024 +0300 all: revert permcheck commitbcc85d50f5
Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Mon Oct 28 17:48:55 2024 +0300 all: use aghos more commit993e351712
Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Mon Oct 28 16:24:56 2024 +0300 all: fix more bugs commita22b0d265e
Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Fri Oct 25 18:30:52 2024 +0300 all: fix bugs commita2309f812a
Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Fri Oct 25 17:05:08 2024 +0300 all: fix chlog, imp api commit42c3f8e91c
Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Fri Oct 25 16:04:47 2024 +0300 scripts: fix docs commit9e781ff18d
Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Fri Oct 25 16:03:19 2024 +0300 scripts: imp docs commit1dbc784982
Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Fri Oct 25 15:55:16 2024 +0300 all: use new functions, add tests commitdcbabaf4e3
Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Fri Oct 25 13:23:50 2024 +0300 aghos: add stat commit72d7c0f881
Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Thu Oct 24 17:10:30 2024 +0300 aghos: add windows functions
This commit is contained in:
parent
e529d29e8a
commit
e77de2e67d
|
@ -25,6 +25,10 @@ See also the [v0.107.54 GitHub milestone][ms-v0.107.54].
|
||||||
NOTE: Add new changes BELOW THIS COMMENT.
|
NOTE: Add new changes BELOW THIS COMMENT.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Incorrect handling of sensitive files permissions on Windows ([#7314]).
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Improved filtering performance ([#6818]).
|
- Improved filtering performance ([#6818]).
|
||||||
|
@ -38,6 +42,7 @@ NOTE: Add new changes BELOW THIS COMMENT.
|
||||||
|
|
||||||
[#6818]: https://github.com/AdguardTeam/AdGuardHome/issues/6818
|
[#6818]: https://github.com/AdguardTeam/AdGuardHome/issues/6818
|
||||||
[#7250]: https://github.com/AdguardTeam/AdGuardHome/issues/7250
|
[#7250]: https://github.com/AdguardTeam/AdGuardHome/issues/7250
|
||||||
|
[#7314]: https://github.com/AdguardTeam/AdGuardHome/issues/7314
|
||||||
[#7315]: https://github.com/AdguardTeam/AdGuardHome/issues/7315
|
[#7315]: https://github.com/AdguardTeam/AdGuardHome/issues/7315
|
||||||
[#7338]: https://github.com/AdguardTeam/AdGuardHome/issues/7338
|
[#7338]: https://github.com/AdguardTeam/AdGuardHome/issues/7338
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
package aghos
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(e.burkov): Add platform-independent tests.
|
||||||
|
|
||||||
|
// Chmod is an extension for [os.Chmod] that properly handles Windows access
|
||||||
|
// rights.
|
||||||
|
func Chmod(name string, perm fs.FileMode) (err error) {
|
||||||
|
return chmod(name, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mkdir is an extension for [os.Mkdir] that properly handles Windows access
|
||||||
|
// rights.
|
||||||
|
func Mkdir(name string, perm fs.FileMode) (err error) {
|
||||||
|
return mkdir(name, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MkdirAll is an extension for [os.MkdirAll] that properly handles Windows
|
||||||
|
// access rights.
|
||||||
|
func MkdirAll(path string, perm fs.FileMode) (err error) {
|
||||||
|
return mkdirAll(path, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteFile is an extension for [os.WriteFile] that properly handles Windows
|
||||||
|
// access rights.
|
||||||
|
func WriteFile(filename string, data []byte, perm fs.FileMode) (err error) {
|
||||||
|
return writeFile(filename, data, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenFile is an extension for [os.OpenFile] that properly handles Windows
|
||||||
|
// access rights.
|
||||||
|
func OpenFile(name string, flag int, perm fs.FileMode) (file *os.File, err error) {
|
||||||
|
return openFile(name, flag, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat is an extension for [os.Stat] that properly handles Windows access
|
||||||
|
// rights.
|
||||||
|
//
|
||||||
|
// Note that on Windows the "other" permission bits combines the access rights
|
||||||
|
// of any trustee that is neither the owner nor the owning group for the file.
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): Inspect the behavior for the World (everyone) well-known
|
||||||
|
// SID and, perhaps, use it.
|
||||||
|
func Stat(name string) (fi fs.FileInfo, err error) {
|
||||||
|
return stat(name)
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
//go:build unix
|
||||||
|
|
||||||
|
package aghos
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/google/renameio/v2/maybe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// chmod is a Unix implementation of [Chmod].
|
||||||
|
func chmod(name string, perm fs.FileMode) (err error) {
|
||||||
|
return os.Chmod(name, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mkdir is a Unix implementation of [Mkdir].
|
||||||
|
func mkdir(name string, perm fs.FileMode) (err error) {
|
||||||
|
return os.Mkdir(name, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mkdirAll is a Unix implementation of [MkdirAll].
|
||||||
|
func mkdirAll(path string, perm fs.FileMode) (err error) {
|
||||||
|
return os.MkdirAll(path, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeFile is a Unix implementation of [WriteFile].
|
||||||
|
func writeFile(filename string, data []byte, perm fs.FileMode) (err error) {
|
||||||
|
return maybe.WriteFile(filename, data, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// openFile is a Unix implementation of [OpenFile].
|
||||||
|
func openFile(name string, flag int, perm fs.FileMode) (file *os.File, err error) {
|
||||||
|
// #nosec G304 -- This function simply wraps the [os.OpenFile] function, so
|
||||||
|
// the security concerns should be addressed to the [OpenFile] calls.
|
||||||
|
return os.OpenFile(name, flag, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stat is a Unix implementation of [Stat].
|
||||||
|
func stat(name string) (fi os.FileInfo, err error) {
|
||||||
|
return os.Stat(name)
|
||||||
|
}
|
|
@ -0,0 +1,392 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package aghos
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
|
"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 data
|
||||||
|
// 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) {
|
||||||
|
absName, err := filepath.Abs(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("computing absolute path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err = os.Stat(absName)
|
||||||
|
if err != nil {
|
||||||
|
// Don't wrap the error, since it's informative enough as is.
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dacl, owner, group, err := retrieveDACL(absName)
|
||||||
|
if err != nil {
|
||||||
|
// Don't wrap the error, since it's informative enough as is.
|
||||||
|
return nil, 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mode := fi.Mode()
|
||||||
|
perm := masksToPerm(ownerMask, groupMask, otherMask, mode.IsDir())
|
||||||
|
|
||||||
|
return &fileInfo{
|
||||||
|
FileInfo: fi,
|
||||||
|
// Use the file mode from the security descriptor, but use the
|
||||||
|
// calculated permission bits.
|
||||||
|
mode: perm | mode&^fs.FileMode(0o777),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// retrieveDACL retrieves the discretionary access control list, owner, and
|
||||||
|
// group from the security descriptor of the file with the specified absolute
|
||||||
|
// name.
|
||||||
|
func retrieveDACL(absName string) (dacl *windows.ACL, owner, group *windows.SID, err error) {
|
||||||
|
// desiredSecInfo defines the parts of a security descriptor to retrieve.
|
||||||
|
const desiredSecInfo windows.SECURITY_INFORMATION = windows.OWNER_SECURITY_INFORMATION |
|
||||||
|
windows.GROUP_SECURITY_INFORMATION |
|
||||||
|
windows.DACL_SECURITY_INFORMATION |
|
||||||
|
windows.PROTECTED_DACL_SECURITY_INFORMATION |
|
||||||
|
windows.UNPROTECTED_DACL_SECURITY_INFORMATION
|
||||||
|
|
||||||
|
sd, err := windows.GetNamedSecurityInfo(absName, windows.SE_FILE_OBJECT, desiredSecInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, fmt.Errorf("getting security descriptor: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dacl, _, err = sd.DACL()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, fmt.Errorf("getting discretionary access control list: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
owner, _, err = sd.Owner()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, fmt.Errorf("getting owner sid: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
group, _, err = sd.Group()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, fmt.Errorf("getting group sid: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dacl, owner, group, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// chmod is a Windows implementation of [Chmod].
|
||||||
|
func chmod(name string, perm fs.FileMode) (err error) {
|
||||||
|
fi, err := os.Stat(name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("getting file info: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
entries := make([]windows.EXPLICIT_ACCESS, 0, 3)
|
||||||
|
creatorMask, groupMask, worldMask := permToMasks(perm, fi.IsDir())
|
||||||
|
|
||||||
|
sidMasks := []struct {
|
||||||
|
Key windows.WELL_KNOWN_SID_TYPE
|
||||||
|
Value windows.ACCESS_MASK
|
||||||
|
}{{
|
||||||
|
Key: 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 {
|
||||||
|
errs = append(errs, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
entries = append(entries, windows.EXPLICIT_ACCESS{
|
||||||
|
AccessPermissions: sidMask.Value,
|
||||||
|
AccessMode: windows.GRANT_ACCESS,
|
||||||
|
Inheritance: windows.NO_INHERITANCE,
|
||||||
|
Trustee: trustee,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = errors.Join(errs...); err != nil {
|
||||||
|
return fmt.Errorf("creating access control entries: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
acl, err := windows.ACLFromEntries(entries, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating access control list: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// secInfo defines the parts of a security descriptor to set.
|
||||||
|
const secInfo windows.SECURITY_INFORMATION = windows.DACL_SECURITY_INFORMATION |
|
||||||
|
windows.PROTECTED_DACL_SECURITY_INFORMATION
|
||||||
|
|
||||||
|
err = windows.SetNamedSecurityInfo(name, windows.SE_FILE_OBJECT, secInfo, nil, nil, acl, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("setting security descriptor: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
name, err = filepath.Abs(name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("computing absolute path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Mkdir(name, perm)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
err = errors.WithDeferred(err, os.Remove(name))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return chmod(name, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mkdirAll is a Windows implementation of [MkdirAll].
|
||||||
|
func mkdirAll(path string, perm os.FileMode) (err error) {
|
||||||
|
parent, _ := filepath.Split(path)
|
||||||
|
|
||||||
|
if parent != "" {
|
||||||
|
err = os.MkdirAll(parent, perm)
|
||||||
|
if err != nil && !errors.Is(err, os.ErrExist) {
|
||||||
|
return fmt.Errorf("creating parent directories: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mkdir(path, perm)
|
||||||
|
if errors.Is(err, os.ErrExist) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeFile is a Windows implementation of [WriteFile].
|
||||||
|
func writeFile(filename string, data []byte, perm os.FileMode) (err error) {
|
||||||
|
file, err := openFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, perm)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("opening file: %w", err)
|
||||||
|
}
|
||||||
|
defer func() { err = errors.WithDeferred(err, file.Close()) }()
|
||||||
|
|
||||||
|
_, err = file.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("writing data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// openFile is a Windows implementation of [OpenFile].
|
||||||
|
func openFile(name string, flag int, perm os.FileMode) (file *os.File, err error) {
|
||||||
|
// Only change permissions if the file not yet exists, but should be
|
||||||
|
// created.
|
||||||
|
if flag&os.O_CREATE == 0 {
|
||||||
|
return os.OpenFile(name, flag, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = stat(name)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
defer func() { err = errors.WithDeferred(err, chmod(name, perm)) }()
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("getting file info: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.OpenFile(name, flag, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newWellKnownTrustee returns a trustee for a well-known SID.
|
||||||
|
func newWellKnownTrustee(stype windows.WELL_KNOWN_SID_TYPE) (t windows.TRUSTEE, err error) {
|
||||||
|
sid, err := windows.CreateWellKnownSid(stype)
|
||||||
|
if err != nil {
|
||||||
|
return windows.TRUSTEE{}, fmt.Errorf("creating sid for type %d: %w", stype, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return windows.TRUSTEE{
|
||||||
|
TrusteeForm: windows.TRUSTEE_IS_SID,
|
||||||
|
TrusteeValue: windows.TrusteeValueFromSID(sid),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UNIX file mode permission bits.
|
||||||
|
const (
|
||||||
|
permRead = 0b100
|
||||||
|
permWrite = 0b010
|
||||||
|
permExecute = 0b001
|
||||||
|
)
|
||||||
|
|
||||||
|
// Windows access masks for appropriate UNIX file mode permission bits and
|
||||||
|
// file types.
|
||||||
|
const (
|
||||||
|
fileReadRights windows.ACCESS_MASK = windows.READ_CONTROL |
|
||||||
|
windows.FILE_READ_DATA |
|
||||||
|
windows.FILE_READ_ATTRIBUTES |
|
||||||
|
windows.FILE_READ_EA |
|
||||||
|
windows.SYNCHRONIZE |
|
||||||
|
windows.ACCESS_SYSTEM_SECURITY
|
||||||
|
|
||||||
|
fileWriteRights windows.ACCESS_MASK = windows.WRITE_DAC |
|
||||||
|
windows.WRITE_OWNER |
|
||||||
|
windows.FILE_WRITE_DATA |
|
||||||
|
windows.FILE_WRITE_ATTRIBUTES |
|
||||||
|
windows.FILE_WRITE_EA |
|
||||||
|
windows.DELETE |
|
||||||
|
windows.FILE_APPEND_DATA |
|
||||||
|
windows.SYNCHRONIZE |
|
||||||
|
windows.ACCESS_SYSTEM_SECURITY
|
||||||
|
|
||||||
|
fileExecuteRights windows.ACCESS_MASK = windows.FILE_EXECUTE
|
||||||
|
|
||||||
|
dirReadRights windows.ACCESS_MASK = windows.READ_CONTROL |
|
||||||
|
windows.FILE_LIST_DIRECTORY |
|
||||||
|
windows.FILE_READ_EA |
|
||||||
|
windows.FILE_READ_ATTRIBUTES<<1 |
|
||||||
|
windows.SYNCHRONIZE |
|
||||||
|
windows.ACCESS_SYSTEM_SECURITY
|
||||||
|
|
||||||
|
dirWriteRights windows.ACCESS_MASK = windows.WRITE_DAC |
|
||||||
|
windows.WRITE_OWNER |
|
||||||
|
windows.DELETE |
|
||||||
|
windows.FILE_WRITE_DATA |
|
||||||
|
windows.FILE_APPEND_DATA |
|
||||||
|
windows.FILE_WRITE_EA |
|
||||||
|
windows.FILE_WRITE_ATTRIBUTES<<1 |
|
||||||
|
windows.SYNCHRONIZE |
|
||||||
|
windows.ACCESS_SYSTEM_SECURITY
|
||||||
|
|
||||||
|
dirExecuteRights windows.ACCESS_MASK = windows.FILE_TRAVERSE
|
||||||
|
)
|
||||||
|
|
||||||
|
// permToMasks converts a UNIX file mode permissions to the corresponding
|
||||||
|
// Windows access masks. The [isDir] argument is used to set specific access
|
||||||
|
// bits for directories.
|
||||||
|
func permToMasks(fm os.FileMode, isDir bool) (owner, group, world windows.ACCESS_MASK) {
|
||||||
|
mask := fm.Perm()
|
||||||
|
|
||||||
|
owner = permToMask(byte((mask>>6)&0b111), isDir)
|
||||||
|
group = permToMask(byte((mask>>3)&0b111), isDir)
|
||||||
|
world = permToMask(byte(mask&0b111), isDir)
|
||||||
|
|
||||||
|
return owner, group, world
|
||||||
|
}
|
||||||
|
|
||||||
|
// permToMask converts a UNIX file mode permission bits within p byte to the
|
||||||
|
// corresponding Windows access mask. The [isDir] argument is used to set
|
||||||
|
// specific access bits for directories.
|
||||||
|
func permToMask(p byte, isDir bool) (mask windows.ACCESS_MASK) {
|
||||||
|
readRights, writeRights, executeRights := fileReadRights, fileWriteRights, fileExecuteRights
|
||||||
|
if isDir {
|
||||||
|
readRights, writeRights, executeRights = dirReadRights, dirWriteRights, dirExecuteRights
|
||||||
|
}
|
||||||
|
|
||||||
|
if p&permRead != 0 {
|
||||||
|
mask |= readRights
|
||||||
|
}
|
||||||
|
if p&permWrite != 0 {
|
||||||
|
mask |= writeRights
|
||||||
|
}
|
||||||
|
if p&permExecute != 0 {
|
||||||
|
mask |= executeRights
|
||||||
|
}
|
||||||
|
|
||||||
|
return mask
|
||||||
|
}
|
||||||
|
|
||||||
|
// masksToPerm converts Windows access masks to the corresponding UNIX file
|
||||||
|
// mode permission bits.
|
||||||
|
func masksToPerm(u, g, o windows.ACCESS_MASK, isDir bool) (perm fs.FileMode) {
|
||||||
|
perm |= fs.FileMode(maskToPerm(u, isDir)) << 6
|
||||||
|
perm |= fs.FileMode(maskToPerm(g, isDir)) << 3
|
||||||
|
perm |= fs.FileMode(maskToPerm(o, isDir))
|
||||||
|
|
||||||
|
return perm
|
||||||
|
}
|
||||||
|
|
||||||
|
// maskToPerm converts a Windows access mask to the corresponding UNIX file
|
||||||
|
// mode permission bits.
|
||||||
|
func maskToPerm(mask windows.ACCESS_MASK, isDir bool) (perm byte) {
|
||||||
|
readMask, writeMask, executeMask := fileReadRights, fileWriteRights, fileExecuteRights
|
||||||
|
if isDir {
|
||||||
|
readMask, writeMask, executeMask = dirReadRights, dirWriteRights, dirExecuteRights
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove common bits to avoid false positive detection of unset rights.
|
||||||
|
readMask ^= windows.SYNCHRONIZE | windows.ACCESS_SYSTEM_SECURITY
|
||||||
|
writeMask ^= windows.SYNCHRONIZE | windows.ACCESS_SYSTEM_SECURITY
|
||||||
|
|
||||||
|
if mask&readMask != 0 {
|
||||||
|
perm |= permRead
|
||||||
|
}
|
||||||
|
if mask&writeMask != 0 {
|
||||||
|
perm |= permWrite
|
||||||
|
}
|
||||||
|
if mask&executeMask != 0 {
|
||||||
|
perm |= permExecute
|
||||||
|
}
|
||||||
|
|
||||||
|
return perm
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package aghos
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPermToMasks(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
perm fs.FileMode
|
||||||
|
wantUser windows.ACCESS_MASK
|
||||||
|
wantGroup windows.ACCESS_MASK
|
||||||
|
wantOther windows.ACCESS_MASK
|
||||||
|
isDir bool
|
||||||
|
}{{
|
||||||
|
name: "all",
|
||||||
|
perm: 0b111_111_111,
|
||||||
|
wantUser: fileReadRights | fileWriteRights | fileExecuteRights,
|
||||||
|
wantGroup: fileReadRights | fileWriteRights | fileExecuteRights,
|
||||||
|
wantOther: fileReadRights | fileWriteRights | fileExecuteRights,
|
||||||
|
isDir: false,
|
||||||
|
}, {
|
||||||
|
name: "user_write",
|
||||||
|
perm: 0b010_000_000,
|
||||||
|
wantUser: fileWriteRights,
|
||||||
|
wantGroup: 0,
|
||||||
|
wantOther: 0,
|
||||||
|
isDir: false,
|
||||||
|
}, {
|
||||||
|
name: "group_read",
|
||||||
|
perm: 0b000_100_000,
|
||||||
|
wantUser: 0,
|
||||||
|
wantGroup: fileReadRights,
|
||||||
|
wantOther: 0,
|
||||||
|
isDir: false,
|
||||||
|
}, {
|
||||||
|
name: "all_dir",
|
||||||
|
perm: 0b111_111_111,
|
||||||
|
wantUser: dirReadRights | dirWriteRights | dirExecuteRights,
|
||||||
|
wantGroup: dirReadRights | dirWriteRights | dirExecuteRights,
|
||||||
|
wantOther: dirReadRights | dirWriteRights | dirExecuteRights,
|
||||||
|
isDir: true,
|
||||||
|
}, {
|
||||||
|
name: "user_write_dir",
|
||||||
|
perm: 0b010_000_000,
|
||||||
|
wantUser: dirWriteRights,
|
||||||
|
wantGroup: 0,
|
||||||
|
wantOther: 0,
|
||||||
|
isDir: true,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
user, group, other := permToMasks(tc.perm, tc.isDir)
|
||||||
|
assert.Equal(t, tc.wantUser, user)
|
||||||
|
assert.Equal(t, tc.wantGroup, group)
|
||||||
|
assert.Equal(t, tc.wantOther, other)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMasksToPerm(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
user windows.ACCESS_MASK
|
||||||
|
group windows.ACCESS_MASK
|
||||||
|
other windows.ACCESS_MASK
|
||||||
|
wantPerm fs.FileMode
|
||||||
|
isDir bool
|
||||||
|
}{{
|
||||||
|
name: "all",
|
||||||
|
user: fileReadRights | fileWriteRights | fileExecuteRights,
|
||||||
|
group: fileReadRights | fileWriteRights | fileExecuteRights,
|
||||||
|
other: fileReadRights | fileWriteRights | fileExecuteRights,
|
||||||
|
wantPerm: 0b111_111_111,
|
||||||
|
isDir: false,
|
||||||
|
}, {
|
||||||
|
name: "user_write",
|
||||||
|
user: fileWriteRights,
|
||||||
|
group: 0,
|
||||||
|
other: 0,
|
||||||
|
wantPerm: 0b010_000_000,
|
||||||
|
isDir: false,
|
||||||
|
}, {
|
||||||
|
name: "group_read",
|
||||||
|
user: 0,
|
||||||
|
group: fileReadRights,
|
||||||
|
other: 0,
|
||||||
|
wantPerm: 0b000_100_000,
|
||||||
|
isDir: false,
|
||||||
|
}, {
|
||||||
|
name: "no_access",
|
||||||
|
user: 0,
|
||||||
|
group: 0,
|
||||||
|
other: 0,
|
||||||
|
wantPerm: 0,
|
||||||
|
isDir: false,
|
||||||
|
}, {
|
||||||
|
name: "all_dir",
|
||||||
|
user: dirReadRights | dirWriteRights | dirExecuteRights,
|
||||||
|
group: dirReadRights | dirWriteRights | dirExecuteRights,
|
||||||
|
other: dirReadRights | dirWriteRights | dirExecuteRights,
|
||||||
|
wantPerm: 0b111_111_111,
|
||||||
|
isDir: true,
|
||||||
|
}, {
|
||||||
|
name: "user_write_dir",
|
||||||
|
user: dirWriteRights,
|
||||||
|
group: 0,
|
||||||
|
other: 0,
|
||||||
|
wantPerm: 0b010_000_000,
|
||||||
|
isDir: true,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Don't call [fs.FileMode.Perm] since the result is expected to
|
||||||
|
// contain only the permission bits.
|
||||||
|
assert.Equal(t, tc.wantPerm, masksToPerm(tc.user, tc.group, tc.other, tc.isDir))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -62,7 +63,7 @@ func newPendingFile(filePath string, mode fs.FileMode) (f PendingFile, err error
|
||||||
return nil, fmt.Errorf("opening pending file: %w", err)
|
return nil, fmt.Errorf("opening pending file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = file.Chmod(mode)
|
err = aghos.Chmod(file.Name(), mode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("preparing pending file: %w", err)
|
return nil, fmt.Errorf("preparing pending file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1057,7 +1057,7 @@ func New(c *Config, blockFilters []Filter) (d *DNSFilter, err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.MkdirAll(filepath.Join(d.conf.DataDir, filterDir), aghos.DefaultPermDir)
|
err = aghos.MkdirAll(filepath.Join(d.conf.DataDir, filterDir), aghos.DefaultPermDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.Close()
|
d.Close()
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
@ -25,7 +26,7 @@ func (d *DNSFilter) validateFilterURL(urlStr string) (err error) {
|
||||||
|
|
||||||
if filepath.IsAbs(urlStr) {
|
if filepath.IsAbs(urlStr) {
|
||||||
urlStr = filepath.Clean(urlStr)
|
urlStr = filepath.Clean(urlStr)
|
||||||
_, err = os.Stat(urlStr)
|
_, err = aghos.Stat(urlStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Don't wrap the error since it's informative enough as is.
|
// Don't wrap the error since it's informative enough as is.
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -90,7 +90,11 @@ func InitAuth(
|
||||||
trustedProxies: trustedProxies,
|
trustedProxies: trustedProxies,
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
a.db, err = bbolt.Open(dbFilename, aghos.DefaultPermFile, nil)
|
|
||||||
|
opts := *bbolt.DefaultOptions
|
||||||
|
opts.OpenFile = aghos.OpenFile
|
||||||
|
|
||||||
|
a.db, err = bbolt.Open(dbFilename, aghos.DefaultPermFile, &opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("auth: open DB: %s: %s", dbFilename, err)
|
log.Error("auth: open DB: %s: %s", dbFilename, err)
|
||||||
if err.Error() == "invalid argument" {
|
if err.Error() == "invalid argument" {
|
||||||
|
|
|
@ -708,7 +708,7 @@ func (c *configuration) write() (err error) {
|
||||||
return fmt.Errorf("generating config file: %w", err)
|
return fmt.Errorf("generating config file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = maybe.WriteFile(confPath, buf.Bytes(), aghos.DefaultPermFile)
|
err = aghos.WriteFile(confPath, buf.Bytes(), aghos.DefaultPermFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("writing config file: %w", err)
|
return fmt.Errorf("writing config file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -643,7 +643,7 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
dataDir := Context.getDataDir()
|
dataDir := Context.getDataDir()
|
||||||
err = os.MkdirAll(dataDir, aghos.DefaultPermDir)
|
err = aghos.MkdirAll(dataDir, aghos.DefaultPermDir)
|
||||||
fatalOnError(errors.Annotate(err, "creating DNS data dir at %s: %w", dataDir))
|
fatalOnError(errors.Annotate(err, "creating DNS data dir at %s: %w", dataDir))
|
||||||
|
|
||||||
GLMode = opts.glinetMode
|
GLMode = opts.glinetMode
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/next/configmgr"
|
"github.com/AdguardTeam/AdGuardHome/internal/next/configmgr"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/AdguardTeam/golibs/osutil"
|
"github.com/AdguardTeam/golibs/osutil"
|
||||||
"github.com/google/renameio/v2/maybe"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// signalHandler processes incoming signals and shuts services down.
|
// signalHandler processes incoming signals and shuts services down.
|
||||||
|
@ -142,7 +141,7 @@ func (h *signalHandler) writePID() {
|
||||||
data = strconv.AppendInt(data, int64(os.Getpid()), 10)
|
data = strconv.AppendInt(data, int64(os.Getpid()), 10)
|
||||||
data = append(data, '\n')
|
data = append(data, '\n')
|
||||||
|
|
||||||
err := maybe.WriteFile(h.pidFile, data, 0o644)
|
err := aghos.WriteFile(h.pidFile, data, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("sighdlr: writing pidfile: %s", err)
|
log.Error("sighdlr: writing pidfile: %s", err)
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ import (
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/AdguardTeam/golibs/timeutil"
|
"github.com/AdguardTeam/golibs/timeutil"
|
||||||
"github.com/google/renameio/v2/maybe"
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -183,7 +182,7 @@ func (m *Manager) write() (err error) {
|
||||||
return fmt.Errorf("encoding: %w", err)
|
return fmt.Errorf("encoding: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = maybe.WriteFile(m.fileName, b, aghos.DefaultPermFile)
|
err = aghos.WriteFile(m.fileName, b, aghos.DefaultPermFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("writing: %w", err)
|
return fmt.Errorf("writing: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
//
|
//
|
||||||
// TODO(a.garipov): Consider ways to detect this better.
|
// TODO(a.garipov): Consider ways to detect this better.
|
||||||
func NeedsMigration(confFilePath string) (ok bool) {
|
func NeedsMigration(confFilePath string) (ok bool) {
|
||||||
s, err := os.Stat(confFilePath)
|
s, err := aghos.Stat(confFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
// Likely a first run. Don't check.
|
// Likely a first run. Don't check.
|
||||||
|
@ -70,7 +70,7 @@ func chmodFile(filePath string) {
|
||||||
// chmodPath changes the permissions of a single filesystem entity. The results
|
// chmodPath changes the permissions of a single filesystem entity. The results
|
||||||
// are logged at the appropriate level.
|
// are logged at the appropriate level.
|
||||||
func chmodPath(entPath, fileType string, fm fs.FileMode) {
|
func chmodPath(entPath, fileType string, fm fs.FileMode) {
|
||||||
err := os.Chmod(entPath, fm)
|
err := aghos.Chmod(entPath, fm)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
log.Info("permcheck: changed permissions for %s %q", fileType, entPath)
|
log.Info("permcheck: changed permissions for %s %q", fileType, entPath)
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ func checkFile(filePath string) {
|
||||||
// checkPath checks the permissions of a single filesystem entity. The results
|
// checkPath checks the permissions of a single filesystem entity. The results
|
||||||
// are logged at the appropriate level.
|
// are logged at the appropriate level.
|
||||||
func checkPath(entPath, fileType string, want fs.FileMode) {
|
func checkPath(entPath, fileType string, want fs.FileMode) {
|
||||||
s, err := os.Stat(entPath)
|
s, err := aghos.Stat(entPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logFunc := log.Error
|
logFunc := log.Error
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
|
|
@ -57,6 +57,7 @@ type qLogFile struct {
|
||||||
|
|
||||||
// newQLogFile initializes a new instance of the qLogFile.
|
// newQLogFile initializes a new instance of the qLogFile.
|
||||||
func newQLogFile(path string) (qf *qLogFile, err error) {
|
func newQLogFile(path string) (qf *qLogFile, err error) {
|
||||||
|
// Don't use [aghos.OpenFile] here, because the file is expected to exist.
|
||||||
f, err := os.OpenFile(path, os.O_RDONLY, aghos.DefaultPermFile)
|
f, err := os.OpenFile(path, os.O_RDONLY, aghos.DefaultPermFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -71,7 +71,7 @@ func (l *queryLog) flushToFile(b *bytes.Buffer) (err error) {
|
||||||
|
|
||||||
filename := l.logFile
|
filename := l.logFile
|
||||||
|
|
||||||
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, aghos.DefaultPermFile)
|
f, err := aghos.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, aghos.DefaultPermFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("creating file %q: %w", filename, err)
|
return fmt.Errorf("creating file %q: %w", filename, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -384,7 +384,13 @@ func (s *StatsCtx) openDB() (err error) {
|
||||||
s.logger.Debug("opening database")
|
s.logger.Debug("opening database")
|
||||||
|
|
||||||
var db *bbolt.DB
|
var db *bbolt.DB
|
||||||
db, err = bbolt.Open(s.filename, aghos.DefaultPermFile, nil)
|
|
||||||
|
opts := *bbolt.DefaultOptions
|
||||||
|
// Use the custom OpenFile function to properly handle access rights on
|
||||||
|
// Windows.
|
||||||
|
opts.OpenFile = aghos.OpenFile
|
||||||
|
|
||||||
|
db, err = bbolt.Open(s.filename, aghos.DefaultPermFile, &opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "invalid argument" {
|
if err.Error() == "invalid argument" {
|
||||||
const lines = `AdGuard Home cannot be initialized due to an incompatible file system.
|
const lines = `AdGuard Home cannot be initialized due to an incompatible file system.
|
||||||
|
|
|
@ -264,7 +264,7 @@ func (u *Updater) check() (err error) {
|
||||||
// ignores the configuration file if firstRun is true.
|
// ignores the configuration file if firstRun is true.
|
||||||
func (u *Updater) backup(firstRun bool) (err error) {
|
func (u *Updater) backup(firstRun bool) (err error) {
|
||||||
log.Debug("updater: backing up current configuration")
|
log.Debug("updater: backing up current configuration")
|
||||||
_ = os.Mkdir(u.backupDir, aghos.DefaultPermDir)
|
_ = aghos.Mkdir(u.backupDir, aghos.DefaultPermDir)
|
||||||
if !firstRun {
|
if !firstRun {
|
||||||
err = copyFile(u.confName, filepath.Join(u.backupDir, "AdGuardHome.yaml"))
|
err = copyFile(u.confName, filepath.Join(u.backupDir, "AdGuardHome.yaml"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -338,12 +338,12 @@ func (u *Updater) downloadPackageFile() (err error) {
|
||||||
return fmt.Errorf("io.ReadAll() failed: %w", err)
|
return fmt.Errorf("io.ReadAll() failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = os.Mkdir(u.updateDir, aghos.DefaultPermDir)
|
_ = aghos.Mkdir(u.updateDir, aghos.DefaultPermDir)
|
||||||
|
|
||||||
log.Debug("updater: saving package to file")
|
log.Debug("updater: saving package to file")
|
||||||
err = os.WriteFile(u.packageName, body, aghos.DefaultPermFile)
|
err = aghos.WriteFile(u.packageName, body, aghos.DefaultPermFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("os.WriteFile() failed: %w", err)
|
return fmt.Errorf("writing package file: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -360,15 +360,15 @@ func tarGzFileUnpackOne(outDir string, tr *tar.Reader, hdr *tar.Header) (name st
|
||||||
if name == "AdGuardHome" {
|
if name == "AdGuardHome" {
|
||||||
// Top-level AdGuardHome/. Skip it.
|
// Top-level AdGuardHome/. Skip it.
|
||||||
//
|
//
|
||||||
// TODO(a.garipov): This whole package needs to be
|
// TODO(a.garipov): This whole package needs to be rewritten and
|
||||||
// rewritten and covered in more integration tests. It
|
// covered in more integration tests. It has weird assumptions and
|
||||||
// has weird assumptions and file mode issues.
|
// file mode issues.
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.Mkdir(outputName, os.FileMode(hdr.Mode&0o755))
|
err = aghos.Mkdir(outputName, os.FileMode(hdr.Mode&0o755))
|
||||||
if err != nil && !errors.Is(err, os.ErrExist) {
|
if err != nil && !errors.Is(err, os.ErrExist) {
|
||||||
return "", fmt.Errorf("os.Mkdir(%q): %w", outputName, err)
|
return "", fmt.Errorf("creating directory %q: %w", outputName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("updater: created directory %q", outputName)
|
log.Debug("updater: created directory %q", outputName)
|
||||||
|
@ -383,7 +383,7 @@ func tarGzFileUnpackOne(outDir string, tr *tar.Reader, hdr *tar.Header) (name st
|
||||||
}
|
}
|
||||||
|
|
||||||
var wc io.WriteCloser
|
var wc io.WriteCloser
|
||||||
wc, err = os.OpenFile(
|
wc, err = aghos.OpenFile(
|
||||||
outputName,
|
outputName,
|
||||||
os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
|
os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
|
||||||
os.FileMode(hdr.Mode&0o755),
|
os.FileMode(hdr.Mode&0o755),
|
||||||
|
@ -464,14 +464,13 @@ func zipFileUnpackOne(outDir string, zf *zip.File) (name string, err error) {
|
||||||
if name == "AdGuardHome" {
|
if name == "AdGuardHome" {
|
||||||
// Top-level AdGuardHome/. Skip it.
|
// Top-level AdGuardHome/. Skip it.
|
||||||
//
|
//
|
||||||
// TODO(a.garipov): See the similar todo in
|
// TODO(a.garipov): See the similar todo in tarGzFileUnpack.
|
||||||
// tarGzFileUnpack.
|
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.Mkdir(outputName, fi.Mode())
|
err = aghos.Mkdir(outputName, fi.Mode())
|
||||||
if err != nil && !errors.Is(err, os.ErrExist) {
|
if err != nil && !errors.Is(err, os.ErrExist) {
|
||||||
return "", fmt.Errorf("os.Mkdir(%q): %w", outputName, err)
|
return "", fmt.Errorf("creating directory %q: %w", outputName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("updater: created directory %q", outputName)
|
log.Debug("updater: created directory %q", outputName)
|
||||||
|
@ -480,7 +479,7 @@ func zipFileUnpackOne(outDir string, zf *zip.File) (name string, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var wc io.WriteCloser
|
var wc io.WriteCloser
|
||||||
wc, err = os.OpenFile(outputName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fi.Mode())
|
wc, err = aghos.OpenFile(outputName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fi.Mode())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("os.OpenFile(): %w", err)
|
return "", fmt.Errorf("os.OpenFile(): %w", err)
|
||||||
}
|
}
|
||||||
|
@ -523,15 +522,19 @@ func zipFileUnpack(zipfile, outDir string) (files []string, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy file on disk
|
// Copy file on disk
|
||||||
func copyFile(src, dst string) error {
|
func copyFile(src, dst string) (err error) {
|
||||||
d, e := os.ReadFile(src)
|
d, err := os.ReadFile(src)
|
||||||
if e != nil {
|
if err != nil {
|
||||||
return e
|
// Don't wrap the error, since it's informative enough as is.
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
e = os.WriteFile(dst, d, aghos.DefaultPermFile)
|
|
||||||
if e != nil {
|
err = aghos.WriteFile(dst, d, aghos.DefaultPermFile)
|
||||||
return e
|
if err != nil {
|
||||||
|
// Don't wrap the error, since it's informative enough as is.
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,9 +54,12 @@ set -f -u
|
||||||
#
|
#
|
||||||
# * Package unsafe is… unsafe.
|
# * Package unsafe is… unsafe.
|
||||||
#
|
#
|
||||||
# Currently, the only standard exception are files generated from protobuf
|
# If your project needs more exceptions, add and document them. Currently,
|
||||||
# schemas, which use package reflect. If your project needs more exceptions,
|
# there are only two standard exceptions:
|
||||||
# add and document them.
|
#
|
||||||
|
# * Files generated from protobuf schemas, which use package reflect.
|
||||||
|
#
|
||||||
|
# * Windows-specific code caused by golang.org/x/sys/windows API design.
|
||||||
#
|
#
|
||||||
# TODO(a.garipov): Add golibs/log.
|
# TODO(a.garipov): Add golibs/log.
|
||||||
#
|
#
|
||||||
|
@ -75,6 +78,7 @@ blocklist_imports() {
|
||||||
-n\
|
-n\
|
||||||
-- '*.go'\
|
-- '*.go'\
|
||||||
':!*.pb.go'\
|
':!*.pb.go'\
|
||||||
|
':!./internal/aghos/permission_windows.go'\
|
||||||
| sed -e 's/^\([^[:space:]]\+\)\(.*\)$/\1 blocked import:\2/'\
|
| sed -e 's/^\([^[:space:]]\+\)\(.*\)$/\1 blocked import:\2/'\
|
||||||
|| exit 0
|
|| exit 0
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue