From e268c11e27607f25b97bcb14e9d01af70c2c0f52 Mon Sep 17 00:00:00 2001 From: Hamza El-Saawy <84944216+helsaawy@users.noreply.github.com> Date: Tue, 23 Aug 2022 15:05:05 -0400 Subject: [PATCH] Add lint and go generate steps to CI (#254) * Add lint and go generate stages to CI Add CI step to verify `go generate` was run on repo. Add linter stage to CI along with linter config file, `.golangci.yml`. Will likely prefer revive over static-check. Updated README Contributing section on linting requirements. Added sequence ordering to make sure lint and go generate stages run before tests and build. This way, build and tests are not run on code that could potentially: 1. not build due to `gofmt` issues; 2. contain bugs; 3. have to be re-submitted after issues are fixed; or 4. contain outdated Win32 syscall or other auto-generated files. Signed-off-by: Hamza El-Saawy * Fixed linter issues Code changes to satisfy linters: - Ran `gofmt -s -w` on repo. - Broke up long lines. - When possible, changed names with incorrect initialism formatting - Added exceptions for exported variables. - Added exceptions for ALL_CAPS_WITH_UNDERSCORES code. - Switched to using `windows` or `syscall` definitions if possible; especially if some constants were unused. - Added `_ =` to satisfy error linter, and acknowledge that errors are being ignored. - Switched to using `errors.Is` and `As` in places, elsewhere added exceptions if error value was known to be `syscall.Errno`. - Removed bare returns. - Prevented variables from being overshadowed in certain places (ignoring cases of overshadowing `err`). - Renamed variables and functions (eg, `len`, `eventMetadata.bytes`) to prevent shadowing pre-built functions and imported pacakges. - Removed unused method receivers. - Added exceptions to certain unused (unexported) constants and functions. - Deleted unused `once` from `pkg/etw.providerMap`. - Renamed `noop.go` files to `main_other.go` or `doc.go`, to better fit style recommendations. - Added exceptions for non-secure use of SHA1 and weak crypto libraries. - Replaced `ioutil` with `io` and `os` (and `t.TempDir` in tests). - Added fully exhaustive checks for `switch` statements in `pkg/etw`. - Defined constant strings for `tools/mkwinsyscall`. - Removed unnecessary conversions. - Made sure `context.Cancel` was called. Additionally, added `//go:build windows" constraints on files with unexported code, since linter will complain about unused code on non-Windows platforms. Added a stub `main() {}` for `mkwinsyscall` for non-Windows builds, just in case `//go:generate` directives are added to OS-agnostic files. Signed-off-by: Hamza El-Saawy * PR: spelling, constants, fuzzing Moved HVSocket fuzzing tests to separate file with go 1.18 build constraint. Signed-off-by: Hamza El-Saawy Signed-off-by: Hamza El-Saawy --- .gitattributes | 1 + .github/workflows/ci.yml | 77 ++++++++-- .golangci.yml | 144 ++++++++++++++++++ README.md | 72 +++++++-- backup.go | 47 +++--- backup_test.go | 26 ++-- backuptar/{noop.go => doc.go} | 1 - backuptar/strconv.go | 2 + backuptar/tar.go | 121 +++++++-------- backuptar/tar_test.go | 9 +- doc.go | 22 +++ ea.go | 8 +- ea_test.go | 11 +- file.go | 57 ++++--- fileinfo.go | 28 +++- fileinfo_test.go | 10 +- hvsock.go | 72 ++++----- hvsock_go118_test.go | 88 +++++++++++ hvsock_test.go | 78 ---------- internal/socket/socket.go | 36 ++++- pipe.go | 120 ++++++++------- pipe_test.go | 87 ++++++----- pkg/etw/doc.go | 8 + pkg/etw/eventdata.go | 27 ++-- pkg/etw/eventdatadescriptor.go | 12 +- pkg/etw/eventdescriptor.go | 8 + pkg/etw/eventmetadata.go | 22 ++- pkg/etw/eventopt.go | 1 + pkg/etw/fieldopt.go | 10 +- pkg/etw/newprovider.go | 15 +- pkg/etw/newprovider_unsupported.go | 4 +- pkg/etw/provider.go | 76 ++++++--- pkg/etw/provider_test.go | 1 + pkg/etw/providerglobal.go | 4 +- pkg/etw/ptr64_32.go | 2 + pkg/etw/ptr64_64.go | 2 + .../noop.go => pkg/etw/sample/main_other.go | 1 + pkg/etw/sample/{sample.go => main_windows.go} | 1 + pkg/etw/{etw.go => syscall.go} | 11 +- pkg/etw/wrapper_32.go | 1 + pkg/etw/wrapper_64.go | 21 ++- pkg/etwlogrus/hook.go | 14 +- pkg/etwlogrus/hook_test.go | 1 + pkg/etwlogrus/opts.go | 2 +- pkg/fs/fs_windows.go | 2 +- pkg/fs/fs_windows_test.go | 3 +- pkg/guid/guid.go | 4 +- pkg/process/process.go | 6 +- pkg/security/grantvmgroupaccess.go | 33 ++-- pkg/security/grantvmgroupaccess_test.go | 15 +- privilege.go | 31 ++-- privileges_test.go | 8 +- reparse.go | 8 +- sd.go | 28 ++-- sd_test.go | 19 ++- syscall.go | 2 + .../etw-provider-gen/main_others.go | 1 + .../{main.go => main_windows.go} | 1 + tools/mkwinsyscall/doc.go | 37 +++-- tools/mkwinsyscall/mkwinsyscall.go | 75 +++++---- vhd/vhd.go | 57 +++++-- wim/decompress.go | 9 +- wim/lzx/lzx.go | 35 ++--- wim/validate/{noop.go => main_other.go} | 1 + wim/validate/{validate.go => main_windows.go} | 6 +- wim/wim.go | 112 +++++++++----- zsyscall_windows.go | 14 +- 67 files changed, 1193 insertions(+), 675 deletions(-) create mode 100644 .gitattributes create mode 100644 .golangci.yml rename backuptar/{noop.go => doc.go} (81%) create mode 100644 doc.go create mode 100644 hvsock_go118_test.go create mode 100644 pkg/etw/doc.go rename tools/etw-provider-gen/noop.go => pkg/etw/sample/main_other.go (71%) rename pkg/etw/sample/{sample.go => main_windows.go} (99%) rename pkg/etw/{etw.go => syscall.go} (72%) rename pkg/etw/sample/noop.go => tools/etw-provider-gen/main_others.go (71%) rename tools/etw-provider-gen/{main.go => main_windows.go} (96%) rename wim/validate/{noop.go => main_other.go} (71%) rename wim/validate/{validate.go => main_windows.go} (85%) diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..94f480de --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 63a15efe..2797ea89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,27 +3,86 @@ on: - push - pull_request +env: + GO_VERSION: "1.17" + jobs: + lint: + name: Lint + runs-on: windows-2019 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: ${{ env.GO_VERSION }} + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + args: >- + --verbose + --timeout=5m + --config=.golangci.yml + --max-issues-per-linter=0 + --max-same-issues=0 + --modules-download-mode=readonly + + go-generate: + name: Go Generate + runs-on: windows-2019 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: ${{ env.GO_VERSION }} + - name: Run go generate + shell: pwsh + run: | + Write-Output "::group::go generate" + go generate -x ./... + Write-Output "::endgroup::" + if ($LASTEXITCODE -ne 0) { + Write-Output "::error title=Go Generate::Error running go generate." + exit $LASTEXITCODE + } + - name: Diff + shell: pwsh + run: | + git add -N . + Write-Output "::group::git diff" + git diff --stat --exit-code + Write-Output "::endgroup::" + if ($LASTEXITCODE -ne 0) { + Write-Output "::error ::Generated files are not up to date. Please run ``go generate ./...``." + exit $LASTEXITCODE + } + test: + name: Run Tests + needs: + - lint + - go-generate runs-on: ${{ matrix.os }} strategy: matrix: os: [windows-2019, windows-2022, ubuntu-latest] steps: - - uses: actions/checkout@v2 - - uses: actions/setup-go@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 with: - go-version: '^1.17.0' + go-version: ${{ env.GO_VERSION }} - run: go test -gcflags=all=-d=checkptr -v ./... build: - runs-on: 'windows-2019' + name: Build Repo + needs: + - test + runs-on: "windows-2019" steps: - - uses: actions/checkout@v2 - - uses: actions/setup-go@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 with: - go-version: '^1.17.0' - + go-version: ${{ env.GO_VERSION }} - run: go build ./pkg/etw/sample/ - run: go build ./tools/etw-provider-gen/ - - run: go build ./wim/validate/ \ No newline at end of file + - run: go build ./tools/mkwinsyscall/ + - run: go build ./wim/validate/ diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000..f016bcc8 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,144 @@ +run: + skip-dirs: + - pkg/etw/sample + +linters: + enable: + # style + - containedctx # struct contains a context + - dupl # duplicate code + - errname # erorrs are named correctly + - goconst # strings that should be constants + - godot # comments end in a period + - misspell + - nolintlint # "//nolint" directives are properly explained + - revive # golint replacement + - stylecheck # golint replacement, less configurable than revive + - unconvert # unnecessary conversions + - wastedassign + + # bugs, performance, unused, etc ... + - contextcheck # function uses a non-inherited context + - errorlint # errors not wrapped for 1.13 + - exhaustive # check exhaustiveness of enum switch statements + - gofmt # files are gofmt'ed + - gosec # security + - nestif # deeply nested ifs + - nilerr # returns nil even with non-nil error + - prealloc # slices that can be pre-allocated + - structcheck # unused struct fields + - unparam # unused function params + +issues: + exclude-rules: + # err is very often shadowed in nested scopes + - linters: + - govet + text: '^shadow: declaration of "err" shadows declaration' + + # ignore long lines for skip autogen directives + - linters: + - revive + text: "^line-length-limit: " + source: "^//(go:generate|sys) " + + # allow unjustified ignores of error checks in defer statments + - linters: + - nolintlint + text: "^directive `//nolint:errcheck` should provide explanation" + source: '^\s*defer ' + + # allow unjustified ignores of error lints for io.EOF + - linters: + - nolintlint + text: "^directive `//nolint:errorlint` should provide explanation" + source: '[=|!]= io.EOF' + + +linters-settings: + govet: + enable-all: true + disable: + # struct order is often for Win32 compat + # also, ignore pointer bytes/GC issues for now until performance becomes an issue + - fieldalignment + check-shadowing: true + nolintlint: + allow-leading-space: false + require-explanation: true + require-specific: true + revive: + # revive is more configurable than static check, so likely the preferred alternative to static-check + # (once the perf issue is solved: https://github.com/golangci/golangci-lint/issues/2997) + enable-all-rules: + true + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md + rules: + # rules with required arguments + - name: argument-limit + disabled: true + - name: banned-characters + disabled: true + - name: cognitive-complexity + disabled: true + - name: cyclomatic + disabled: true + - name: file-header + disabled: true + - name: function-length + disabled: true + - name: function-result-limit + disabled: true + - name: max-public-structs + disabled: true + # geneally annoying rules + - name: add-constant # complains about any and all strings and integers + disabled: true + - name: confusing-naming # we frequently use "Foo()" and "foo()" together + disabled: true + - name: flag-parameter # excessive, and a common idiom we use + disabled: true + # general config + - name: line-length-limit + arguments: + - 140 + - name: var-naming + arguments: + - [] + - - CID + - CRI + - CTRD + - DACL + - DLL + - DOS + - ETW + - FSCTL + - GCS + - GMSA + - HCS + - HV + - IO + - LCOW + - LDAP + - LPAC + - LTSC + - MMIO + - NT + - OCI + - PMEM + - PWSH + - RX + - SACl + - SID + - SMB + - TX + - VHD + - VHDX + - VMID + - VPCI + - WCOW + - WIM + stylecheck: + checks: + - "all" + - "-ST1003" # use revive's var naming diff --git a/README.md b/README.md index 683be1dc..7474b4f0 100644 --- a/README.md +++ b/README.md @@ -13,16 +13,60 @@ Please see the LICENSE file for licensing information. ## Contributing -This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) -declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com. +This project welcomes contributions and suggestions. +Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that +you have the right to, and actually do, grant us the rights to use your contribution. +For details, visit [Microsoft CLA](https://cla.microsoft.com). -When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR -appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA. +When you submit a pull request, a CLA-bot will automatically determine whether you need to +provide a CLA and decorate the PR appropriately (e.g., label, comment). +Simply follow the instructions provided by the bot. +You will only need to do this once across all repos using our CLA. -We also require that contributors sign their commits using git commit -s or git commit --signoff to certify they either authored the work themselves -or otherwise have permission to use it in this project. Please see https://developercertificate.org/ for more info, as well as to make sure that you can -attest to the rules listed. Our CI uses the DCO Github app to ensure that all commits in a given PR are signed-off. +Additionally, the pull request pipeline requires the following steps to be performed before +mergining. +### Code Sign-Off + +We require that contributors sign their commits using [`git commit --signoff`][git-commit-s] +to certify they either authored the work themselves or otherwise have permission to use it in this project. + +A range of commits can be signed off using [`git rebase --signoff`][git-rebase-s]. + +Please see [the developer certificate](https://developercertificate.org) for more info, +as well as to make sure that you can attest to the rules listed. +Our CI uses the DCO Github app to ensure that all commits in a given PR are signed-off. + +### Linting + +Code must pass a linting stage, which uses [`golangci-lint`][lint]. +The linting settings are stored in [`.golangci.yaml`](./.golangci.yaml), and can be run +automatically with VSCode by adding the following to your workspace or folder settings: + +```json + "go.lintTool": "golangci-lint", + "go.lintOnSave": "package", +``` + +Additional editor [integrations options are also available][lint-ide]. + +Alternatively, `golangci-lint` can be [installed locally][lint-install] and run from the repo root: + +```shell +# use . or specify a path to only lint a package +# to show all lint errors, use flags "--max-issues-per-linter=0 --max-same-issues=0" +> golangci-lint run ./... +``` + +### Go Generate + +The pipeline checks that auto-generated code, via `go generate`, are up to date. + +This can be done for the entire repo: + +```shell +> go generate ./... +``` ## Code of Conduct @@ -30,8 +74,16 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. +## Special Thanks +Thanks to [natefinch][natefinch] for the inspiration for this library. +See [npipe](https://github.com/natefinch/npipe) for another named pipe implementation. -## Special Thanks -Thanks to natefinch for the inspiration for this library. See https://github.com/natefinch/npipe -for another named pipe implementation. +[lint]: https://golangci-lint.run/ +[lint-ide]: https://golangci-lint.run/usage/integrations/#editor-integration +[lint-install]: https://golangci-lint.run/usage/install/#local-installation + +[git-commit-s]: https://git-scm.com/docs/git-commit#Documentation/git-commit.txt--s +[git-rebase-s]: https://git-scm.com/docs/git-rebase#Documentation/git-rebase.txt---signoff + +[natefinch]: https://github.com/natefinch diff --git a/backup.go b/backup.go index 6b3f121f..09621c88 100644 --- a/backup.go +++ b/backup.go @@ -8,11 +8,12 @@ import ( "errors" "fmt" "io" - "io/ioutil" "os" "runtime" "syscall" "unicode/utf16" + + "golang.org/x/sys/windows" ) //sys backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupRead @@ -25,7 +26,7 @@ const ( BackupAlternateData BackupLink BackupPropertyData - BackupObjectId + BackupObjectId //revive:disable-line:var-naming ID, not Id BackupReparseData BackupSparseBlock BackupTxfsData @@ -35,14 +36,16 @@ const ( StreamSparseAttributes = uint32(8) ) +//nolint:revive // var-naming: ALL_CAPS const ( - WRITE_DAC = 0x40000 - WRITE_OWNER = 0x80000 - ACCESS_SYSTEM_SECURITY = 0x1000000 + WRITE_DAC = windows.WRITE_DAC + WRITE_OWNER = windows.WRITE_OWNER + ACCESS_SYSTEM_SECURITY = windows.ACCESS_SYSTEM_SECURITY ) // BackupHeader represents a backup stream of a file. type BackupHeader struct { + //revive:disable-next-line:var-naming ID, not Id Id uint32 // The backup stream ID Attributes uint32 // Stream attributes Size int64 // The size of the stream in bytes @@ -50,8 +53,8 @@ type BackupHeader struct { Offset int64 // The offset of the stream in the file (for BackupSparseBlock only). } -type win32StreamId struct { - StreamId uint32 +type win32StreamID struct { + StreamID uint32 Attributes uint32 Size uint64 NameSize uint32 @@ -72,7 +75,7 @@ func NewBackupStreamReader(r io.Reader) *BackupStreamReader { // Next returns the next backup stream and prepares for calls to Read(). It skips the remainder of the current stream if // it was not completely read. func (r *BackupStreamReader) Next() (*BackupHeader, error) { - if r.bytesLeft > 0 { + if r.bytesLeft > 0 { //nolint:nestif // todo: flatten this if s, ok := r.r.(io.Seeker); ok { // Make sure Seek on io.SeekCurrent sometimes succeeds // before trying the actual seek. @@ -83,16 +86,16 @@ func (r *BackupStreamReader) Next() (*BackupHeader, error) { r.bytesLeft = 0 } } - if _, err := io.Copy(ioutil.Discard, r); err != nil { + if _, err := io.Copy(io.Discard, r); err != nil { return nil, err } } - var wsi win32StreamId + var wsi win32StreamID if err := binary.Read(r.r, binary.LittleEndian, &wsi); err != nil { return nil, err } hdr := &BackupHeader{ - Id: wsi.StreamId, + Id: wsi.StreamID, Attributes: wsi.Attributes, Size: int64(wsi.Size), } @@ -103,7 +106,7 @@ func (r *BackupStreamReader) Next() (*BackupHeader, error) { } hdr.Name = syscall.UTF16ToString(name) } - if wsi.StreamId == BackupSparseBlock { + if wsi.StreamID == BackupSparseBlock { if err := binary.Read(r.r, binary.LittleEndian, &hdr.Offset); err != nil { return nil, err } @@ -148,8 +151,8 @@ func (w *BackupStreamWriter) WriteHeader(hdr *BackupHeader) error { return fmt.Errorf("missing %d bytes", w.bytesLeft) } name := utf16.Encode([]rune(hdr.Name)) - wsi := win32StreamId{ - StreamId: hdr.Id, + wsi := win32StreamID{ + StreamID: hdr.Id, Attributes: hdr.Attributes, Size: uint64(hdr.Size), NameSize: uint32(len(name) * 2), @@ -204,7 +207,7 @@ func (r *BackupFileReader) Read(b []byte) (int, error) { var bytesRead uint32 err := backupRead(syscall.Handle(r.f.Fd()), b, &bytesRead, false, r.includeSecurity, &r.ctx) if err != nil { - return 0, &os.PathError{"BackupRead", r.f.Name(), err} + return 0, &os.PathError{Op: "BackupRead", Path: r.f.Name(), Err: err} } runtime.KeepAlive(r.f) if bytesRead == 0 { @@ -217,7 +220,7 @@ func (r *BackupFileReader) Read(b []byte) (int, error) { // the underlying file. func (r *BackupFileReader) Close() error { if r.ctx != 0 { - backupRead(syscall.Handle(r.f.Fd()), nil, nil, true, false, &r.ctx) + _ = backupRead(syscall.Handle(r.f.Fd()), nil, nil, true, false, &r.ctx) runtime.KeepAlive(r.f) r.ctx = 0 } @@ -243,7 +246,7 @@ func (w *BackupFileWriter) Write(b []byte) (int, error) { var bytesWritten uint32 err := backupWrite(syscall.Handle(w.f.Fd()), b, &bytesWritten, false, w.includeSecurity, &w.ctx) if err != nil { - return 0, &os.PathError{"BackupWrite", w.f.Name(), err} + return 0, &os.PathError{Op: "BackupWrite", Path: w.f.Name(), Err: err} } runtime.KeepAlive(w.f) if int(bytesWritten) != len(b) { @@ -256,7 +259,7 @@ func (w *BackupFileWriter) Write(b []byte) (int, error) { // close the underlying file. func (w *BackupFileWriter) Close() error { if w.ctx != 0 { - backupWrite(syscall.Handle(w.f.Fd()), nil, nil, true, false, &w.ctx) + _ = backupWrite(syscall.Handle(w.f.Fd()), nil, nil, true, false, &w.ctx) runtime.KeepAlive(w.f) w.ctx = 0 } @@ -272,7 +275,13 @@ func OpenForBackup(path string, access uint32, share uint32, createmode uint32) if err != nil { return nil, err } - h, err := syscall.CreateFile(&winPath[0], access, share, nil, createmode, syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OPEN_REPARSE_POINT, 0) + h, err := syscall.CreateFile(&winPath[0], + access, + share, + nil, + createmode, + syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OPEN_REPARSE_POINT, + 0) if err != nil { err = &os.PathError{Op: "open", Path: path, Err: err} return nil, err diff --git a/backup_test.go b/backup_test.go index eaa8a2e9..112726e6 100644 --- a/backup_test.go +++ b/backup_test.go @@ -5,16 +5,17 @@ package winio import ( "io" - "io/ioutil" "os" "syscall" "testing" + + "golang.org/x/sys/windows" ) var testFileName string func TestMain(m *testing.M) { - f, err := ioutil.TempFile("", "tmp") + f, err := os.CreateTemp("", "tmp") if err != nil { panic(err) } @@ -62,7 +63,7 @@ func TestBackupRead(t *testing.T) { defer f.Close() r := NewBackupFileReader(f, false) defer r.Close() - b, err := ioutil.ReadAll(r) + b, err := io.ReadAll(r) if err != nil { t.Fatal(err) } @@ -90,7 +91,7 @@ func TestBackupStreamRead(t *testing.T) { gotAltData := false for { hdr, err := br.Next() - if err == io.EOF { + if err == io.EOF { //nolint:errorlint break } if err != nil { @@ -105,7 +106,7 @@ func TestBackupStreamRead(t *testing.T) { if hdr.Name != "" { t.Fatalf("unexpected name %s", hdr.Name) } - b, err := ioutil.ReadAll(br) + b, err := io.ReadAll(br) if err != nil { t.Fatal(err) } @@ -120,7 +121,7 @@ func TestBackupStreamRead(t *testing.T) { if hdr.Name != ":ads.txt:$DATA" { t.Fatalf("incorrect name %s", hdr.Name) } - b, err := ioutil.ReadAll(br) + b, err := io.ReadAll(br) if err != nil { t.Fatal(err) } @@ -176,7 +177,7 @@ func TestBackupStreamWrite(t *testing.T) { f.Close() - b, err := ioutil.ReadFile(testFileName) + b, err := os.ReadFile(testFileName) if err != nil { t.Fatal(err) } @@ -184,7 +185,7 @@ func TestBackupStreamWrite(t *testing.T) { t.Fatalf("wrong data %v", b) } - b, err = ioutil.ReadFile(testFileName + ":ads.txt") + b, err = os.ReadFile(testFileName + ":ads.txt") if err != nil { t.Fatal(err) } @@ -201,12 +202,7 @@ func makeSparseFile() error { } defer f.Close() - const ( - FSCTL_SET_SPARSE = 0x000900c4 - FSCTL_SET_ZERO_DATA = 0x000980c8 - ) - - err = syscall.DeviceIoControl(syscall.Handle(f.Fd()), FSCTL_SET_SPARSE, nil, 0, nil, 0, nil, nil) + err = syscall.DeviceIoControl(syscall.Handle(f.Fd()), windows.FSCTL_SET_SPARSE, nil, 0, nil, 0, nil, nil) if err != nil { return err } @@ -246,7 +242,7 @@ func TestBackupSparseFile(t *testing.T) { br := NewBackupStreamReader(r) for { hdr, err := br.Next() - if err == io.EOF { + if err == io.EOF { //nolint:errorlint break } if err != nil { diff --git a/backuptar/noop.go b/backuptar/doc.go similarity index 81% rename from backuptar/noop.go rename to backuptar/doc.go index d39eccf0..965d52ab 100644 --- a/backuptar/noop.go +++ b/backuptar/doc.go @@ -1,4 +1,3 @@ -// +build !windows // This file only exists to allow go get on non-Windows platforms. package backuptar diff --git a/backuptar/strconv.go b/backuptar/strconv.go index 34160966..455fd798 100644 --- a/backuptar/strconv.go +++ b/backuptar/strconv.go @@ -1,3 +1,5 @@ +//go:build windows + package backuptar import ( diff --git a/backuptar/tar.go b/backuptar/tar.go index 038caff3..6b3b0cd5 100644 --- a/backuptar/tar.go +++ b/backuptar/tar.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package backuptar @@ -7,7 +8,6 @@ import ( "encoding/base64" "fmt" "io" - "io/ioutil" "path/filepath" "strconv" "strings" @@ -18,17 +18,18 @@ import ( "golang.org/x/sys/windows" ) +//nolint:deadcode,varcheck // keep unused constants for potential future use const ( - c_ISUID = 04000 // Set uid - c_ISGID = 02000 // Set gid - c_ISVTX = 01000 // Save text (sticky bit) - c_ISDIR = 040000 // Directory - c_ISFIFO = 010000 // FIFO - c_ISREG = 0100000 // Regular file - c_ISLNK = 0120000 // Symbolic link - c_ISBLK = 060000 // Block special file - c_ISCHR = 020000 // Character special file - c_ISSOCK = 0140000 // Socket + cISUID = 0004000 // Set uid + cISGID = 0002000 // Set gid + cISVTX = 0001000 // Save text (sticky bit) + cISDIR = 0040000 // Directory + cISFIFO = 0010000 // FIFO + cISREG = 0100000 // Regular file + cISLNK = 0120000 // Symbolic link + cISBLK = 0060000 // Block special file + cISCHR = 0020000 // Character special file + cISSOCK = 0140000 // Socket ) const ( @@ -44,7 +45,7 @@ const ( // zeroReader is an io.Reader that always returns 0s. type zeroReader struct{} -func (zr zeroReader) Read(b []byte) (int, error) { +func (zeroReader) Read(b []byte) (int, error) { for i := range b { b[i] = 0 } @@ -55,7 +56,7 @@ func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error { curOffset := int64(0) for { bhdr, err := br.Next() - if err == io.EOF { + if err == io.EOF { //nolint:errorlint err = io.ErrUnexpectedEOF } if err != nil { @@ -71,8 +72,8 @@ func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error { } // archive/tar does not support writing sparse files // so just write zeroes to catch up to the current offset. - if _, err := io.CopyN(t, zeroReader{}, bhdr.Offset-curOffset); err != nil { - return fmt.Errorf("seek to offset %d: %s", bhdr.Offset, err) + if _, err = io.CopyN(t, zeroReader{}, bhdr.Offset-curOffset); err != nil { + return fmt.Errorf("seek to offset %d: %w", bhdr.Offset, err) } if bhdr.Size == 0 { // A sparse block with size = 0 is used to mark the end of the sparse blocks. @@ -106,7 +107,7 @@ func BasicInfoHeader(name string, size int64, fileInfo *winio.FileBasicInfo) *ta hdr.PAXRecords[hdrCreationTime] = formatPAXTime(time.Unix(0, fileInfo.CreationTime.Nanoseconds())) if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 { - hdr.Mode |= c_ISDIR + hdr.Mode |= cISDIR hdr.Size = 0 hdr.Typeflag = tar.TypeDir } @@ -138,9 +139,7 @@ func SecurityDescriptorFromTarHeader(hdr *tar.Header) ([]byte, error) { // ExtendedAttributesFromTarHeader reads the EAs associated with the header of the // current file from the tar header and returns it as a byte slice. func ExtendedAttributesFromTarHeader(hdr *tar.Header) ([]byte, error) { - var eas []winio.ExtendedAttribute - var eadata []byte - var err error + var eas []winio.ExtendedAttribute //nolint:prealloc // len(eas) <= len(hdr.PAXRecords); prealloc is wasteful for k, v := range hdr.PAXRecords { if !strings.HasPrefix(k, hdrEaPrefix) { continue @@ -154,13 +153,15 @@ func ExtendedAttributesFromTarHeader(hdr *tar.Header) ([]byte, error) { Value: data, }) } + var eaData []byte + var err error if len(eas) != 0 { - eadata, err = winio.EncodeExtendedAttributes(eas) + eaData, err = winio.EncodeExtendedAttributes(eas) if err != nil { return nil, err } } - return eadata, nil + return eaData, nil } // EncodeReparsePointFromTarHeader reads the ReparsePoint structure from the tar header @@ -181,11 +182,9 @@ func EncodeReparsePointFromTarHeader(hdr *tar.Header) []byte { // // The additional Win32 metadata is: // -// MSWINDOWS.fileattr: The Win32 file attributes, as a decimal value -// -// MSWINDOWS.rawsd: The Win32 security descriptor, in raw binary format -// -// MSWINDOWS.mountpoint: If present, this is a mount point and not a symlink, even though the type is '2' (symlink) +// - MSWINDOWS.fileattr: The Win32 file attributes, as a decimal value +// - MSWINDOWS.rawsd: The Win32 security descriptor, in raw binary format +// - MSWINDOWS.mountpoint: If present, this is a mount point and not a symlink, even though the type is '2' (symlink) func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size int64, fileInfo *winio.FileBasicInfo) error { name = filepath.ToSlash(name) hdr := BasicInfoHeader(name, size, fileInfo) @@ -208,7 +207,7 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size var dataHdr *winio.BackupHeader for dataHdr == nil { bhdr, err := br.Next() - if err == io.EOF { + if err == io.EOF { //nolint:errorlint break } if err != nil { @@ -216,21 +215,21 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size } switch bhdr.Id { case winio.BackupData: - hdr.Mode |= c_ISREG + hdr.Mode |= cISREG if !readTwice { dataHdr = bhdr } case winio.BackupSecurity: - sd, err := ioutil.ReadAll(br) + sd, err := io.ReadAll(br) if err != nil { return err } hdr.PAXRecords[hdrRawSecurityDescriptor] = base64.StdEncoding.EncodeToString(sd) case winio.BackupReparseData: - hdr.Mode |= c_ISLNK + hdr.Mode |= cISLNK hdr.Typeflag = tar.TypeSymlink - reparseBuffer, err := ioutil.ReadAll(br) + reparseBuffer, _ := io.ReadAll(br) rp, err := winio.DecodeReparsePoint(reparseBuffer) if err != nil { return err @@ -241,7 +240,7 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size hdr.Linkname = rp.Target case winio.BackupEaData: - eab, err := ioutil.ReadAll(br) + eab, err := io.ReadAll(br) if err != nil { return err } @@ -275,7 +274,7 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size } for dataHdr == nil { bhdr, err := br.Next() - if err == io.EOF { + if err == io.EOF { //nolint:errorlint break } if err != nil { @@ -310,7 +309,7 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size // range of the file containing the range contents. Finally there is a sparse block stream with // size = 0 and offset = . - if dataHdr != nil { + if dataHdr != nil { //nolint:nestif // todo: reduce nesting complexity // A data stream was found. Copy the data. // We assume that we will either have a data stream size > 0 XOR have sparse block streams. if dataHdr.Size > 0 || (dataHdr.Attributes&winio.StreamSparseAttributes) == 0 { @@ -318,13 +317,13 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size return fmt.Errorf("%s: mismatch between file size %d and header size %d", name, size, dataHdr.Size) } if _, err = io.Copy(t, br); err != nil { - return fmt.Errorf("%s: copying contents from data stream: %s", name, err) + return fmt.Errorf("%s: copying contents from data stream: %w", name, err) } } else if size > 0 { // As of a recent OS change, BackupRead now returns a data stream for empty sparse files. // These files have no sparse block streams, so skip the copySparse call if file size = 0. if err = copySparse(t, br); err != nil { - return fmt.Errorf("%s: copying contents from sparse block stream: %s", name, err) + return fmt.Errorf("%s: copying contents from sparse block stream: %w", name, err) } } } @@ -334,7 +333,7 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size // been written. In practice, this means that we don't get EA or TXF metadata. for { bhdr, err := br.Next() - if err == io.EOF { + if err == io.EOF { //nolint:errorlint break } if err != nil { @@ -342,35 +341,30 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size } switch bhdr.Id { case winio.BackupAlternateData: - altName := bhdr.Name - if strings.HasSuffix(altName, ":$DATA") { - altName = altName[:len(altName)-len(":$DATA")] - } - if (bhdr.Attributes & winio.StreamSparseAttributes) == 0 { - hdr = &tar.Header{ - Format: hdr.Format, - Name: name + altName, - Mode: hdr.Mode, - Typeflag: tar.TypeReg, - Size: bhdr.Size, - ModTime: hdr.ModTime, - AccessTime: hdr.AccessTime, - ChangeTime: hdr.ChangeTime, - } - err = t.WriteHeader(hdr) - if err != nil { - return err - } - _, err = io.Copy(t, br) - if err != nil { - return err - } - - } else { + if (bhdr.Attributes & winio.StreamSparseAttributes) != 0 { // Unsupported for now, since the size of the alternate stream is not present // in the backup stream until after the data has been read. return fmt.Errorf("%s: tar of sparse alternate data streams is unsupported", name) } + altName := strings.TrimSuffix(bhdr.Name, ":$DATA") + hdr = &tar.Header{ + Format: hdr.Format, + Name: name + altName, + Mode: hdr.Mode, + Typeflag: tar.TypeReg, + Size: bhdr.Size, + ModTime: hdr.ModTime, + AccessTime: hdr.AccessTime, + ChangeTime: hdr.ChangeTime, + } + err = t.WriteHeader(hdr) + if err != nil { + return err + } + _, err = io.Copy(t, br) + if err != nil { + return err + } case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData: // ignore these streams default: @@ -412,7 +406,7 @@ func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *win } fileInfo.CreationTime = windows.NsecToFiletime(creationTime.UnixNano()) } - return + return name, size, fileInfo, err } // WriteBackupStreamFromTarFile writes a Win32 backup stream from the current tar file. Since this function may process multiple @@ -473,7 +467,6 @@ func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) ( if err != nil { return nil, err } - } if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA { diff --git a/backuptar/tar_test.go b/backuptar/tar_test.go index cc2cbf09..8984c596 100644 --- a/backuptar/tar_test.go +++ b/backuptar/tar_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package backuptar @@ -6,7 +7,6 @@ import ( "archive/tar" "bytes" "io" - "io/ioutil" "os" "path/filepath" "reflect" @@ -25,8 +25,7 @@ func ensurePresent(t *testing.T, m map[string]string, keys ...string) { } func setSparse(t *testing.T, f *os.File) { - const FSCTL_SET_SPARSE uint32 = 0x900c4 - if err := windows.DeviceIoControl(windows.Handle(f.Fd()), FSCTL_SET_SPARSE, nil, 0, nil, 0, nil, nil); err != nil { + if err := windows.DeviceIoControl(windows.Handle(f.Fd()), windows.FSCTL_SET_SPARSE, nil, 0, nil, 0, nil, nil); err != nil { t.Fatal(err) } } @@ -68,10 +67,12 @@ func compareReaders(t *testing.T, rActual io.Reader, rExpected io.Reader) { func TestRoundTrip(t *testing.T) { // Each test case is a name mapped to a function which must create a file and return its path. // The test then round-trips that file through backuptar, and validates the output matches the input. + // + //nolint:gosec // G306: Expect WriteFile permissions to be 0600 or less for name, setup := range map[string]func(*testing.T) string{ "normalFile": func(t *testing.T) string { path := filepath.Join(t.TempDir(), "foo.txt") - if err := ioutil.WriteFile(path, []byte("testing 1 2 3\n"), 0644); err != nil { + if err := os.WriteFile(path, []byte("testing 1 2 3\n"), 0644); err != nil { t.Fatal(err) } return path diff --git a/doc.go b/doc.go new file mode 100644 index 00000000..1f5bfe2d --- /dev/null +++ b/doc.go @@ -0,0 +1,22 @@ +// This package provides utilities for efficiently performing Win32 IO operations in Go. +// Currently, this package is provides support for genreal IO and management of +// - named pipes +// - files +// - [Hyper-V sockets] +// +// This code is similar to Go's [net] package, and uses IO completion ports to avoid +// blocking IO on system threads, allowing Go to reuse the thread to schedule other goroutines. +// +// This limits support to Windows Vista and newer operating systems. +// +// Additionally, this package provides support for: +// - creating and managing GUIDs +// - writing to [ETW] +// - opening and manageing VHDs +// - parsing [Windows Image files] +// - auto-generating Win32 API code +// +// [Hyper-V sockets]: https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/make-integration-service +// [ETW]: https://docs.microsoft.com/en-us/windows-hardware/drivers/devtest/event-tracing-for-windows--etw- +// [Windows Image files]: https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/work-with-windows-images +package winio diff --git a/ea.go b/ea.go index 4051c1b3..e104dbdf 100644 --- a/ea.go +++ b/ea.go @@ -33,7 +33,7 @@ func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) { err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info) if err != nil { err = errInvalidEaBuffer - return + return ea, nb, err } nameOffset := fileFullEaInformationSize @@ -43,7 +43,7 @@ func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) { nextOffset := int(info.NextEntryOffset) if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) { err = errInvalidEaBuffer - return + return ea, nb, err } ea.Name = string(b[nameOffset : nameOffset+nameLen]) @@ -52,7 +52,7 @@ func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) { if info.NextEntryOffset != 0 { nb = b[info.NextEntryOffset:] } - return + return ea, nb, err } // DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION @@ -67,7 +67,7 @@ func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) { eas = append(eas, ea) b = nb } - return + return eas, err } func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error { diff --git a/ea_test.go b/ea_test.go index 3e32076f..5591edb0 100644 --- a/ea_test.go +++ b/ea_test.go @@ -4,7 +4,6 @@ package winio import ( - "io/ioutil" "os" "reflect" "syscall" @@ -18,7 +17,8 @@ var ( {Name: "fizz", Value: []byte("buzz")}, } - testEasEncoded = []byte{16, 0, 0, 0, 0, 3, 3, 0, 102, 111, 111, 0, 98, 97, 114, 0, 0, 0, 0, 0, 0, 4, 4, 0, 102, 105, 122, 122, 0, 98, 117, 122, 122, 0, 0, 0} + testEasEncoded = []byte{16, 0, 0, 0, 0, 3, 3, 0, 102, 111, 111, 0, 98, 97, 114, 0, 0, + 0, 0, 0, 0, 4, 4, 0, 102, 105, 122, 122, 0, 98, 117, 122, 122, 0, 0, 0} testEasNotPadded = testEasEncoded[0 : len(testEasEncoded)-3] testEasTruncated = testEasEncoded[0:20] ) @@ -76,7 +76,7 @@ func Test_NilEasEncodeAndDecodeAsNil(t *testing.T) { // Test_SetFileEa makes sure that the test buffer is actually parsable by NtSetEaFile. func Test_SetFileEa(t *testing.T) { - f, err := ioutil.TempFile("", "winio") + f, err := os.CreateTemp("", "winio") if err != nil { t.Fatal(err) } @@ -85,7 +85,10 @@ func Test_SetFileEa(t *testing.T) { ntdll := syscall.MustLoadDLL("ntdll.dll") ntSetEaFile := ntdll.MustFindProc("NtSetEaFile") var iosb [2]uintptr - r, _, _ := ntSetEaFile.Call(f.Fd(), uintptr(unsafe.Pointer(&iosb[0])), uintptr(unsafe.Pointer(&testEasEncoded[0])), uintptr(len(testEasEncoded))) + r, _, _ := ntSetEaFile.Call(f.Fd(), + uintptr(unsafe.Pointer(&iosb[0])), + uintptr(unsafe.Pointer(&testEasEncoded[0])), + uintptr(len(testEasEncoded))) if r != 0 { t.Fatalf("NtSetEaFile failed with %08x", r) } diff --git a/file.go b/file.go index a88a1269..175a99d3 100644 --- a/file.go +++ b/file.go @@ -11,6 +11,8 @@ import ( "sync/atomic" "syscall" "time" + + "golang.org/x/sys/windows" ) //sys cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) = CancelIoEx @@ -24,6 +26,8 @@ type atomicBool int32 func (b *atomicBool) isSet() bool { return atomic.LoadInt32((*int32)(b)) != 0 } func (b *atomicBool) setFalse() { atomic.StoreInt32((*int32)(b), 0) } func (b *atomicBool) setTrue() { atomic.StoreInt32((*int32)(b), 1) } + +//revive:disable-next-line:predeclared Keep "new" to maintain consistency with "atomic" pkg func (b *atomicBool) swap(new bool) bool { var newInt int32 if new { @@ -32,11 +36,6 @@ func (b *atomicBool) swap(new bool) bool { return atomic.SwapInt32((*int32)(b), newInt) == 1 } -const ( - cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 1 - cFILE_SKIP_SET_EVENT_ON_HANDLE = 2 -) - var ( ErrFileClosed = errors.New("file has already been closed") ErrTimeout = &timeoutError{} @@ -53,19 +52,19 @@ type timeoutChan chan struct{} var ioInitOnce sync.Once var ioCompletionPort syscall.Handle -// ioResult contains the result of an asynchronous IO operation +// ioResult contains the result of an asynchronous IO operation. type ioResult struct { bytes uint32 err error } -// ioOperation represents an outstanding asynchronous Win32 IO +// ioOperation represents an outstanding asynchronous Win32 IO. type ioOperation struct { o syscall.Overlapped ch chan ioResult } -func initIo() { +func initIO() { h, err := createIoCompletionPort(syscall.InvalidHandle, 0, 0, 0xffffffff) if err != nil { panic(err) @@ -94,15 +93,15 @@ type deadlineHandler struct { timedout atomicBool } -// makeWin32File makes a new win32File from an existing file handle +// makeWin32File makes a new win32File from an existing file handle. func makeWin32File(h syscall.Handle) (*win32File, error) { f := &win32File{handle: h} - ioInitOnce.Do(initIo) + ioInitOnce.Do(initIO) _, err := createIoCompletionPort(h, ioCompletionPort, 0, 0xffffffff) if err != nil { return nil, err } - err = setFileCompletionNotificationModes(h, cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS|cFILE_SKIP_SET_EVENT_ON_HANDLE) + err = setFileCompletionNotificationModes(h, windows.FILE_SKIP_COMPLETION_PORT_ON_SUCCESS|windows.FILE_SKIP_SET_EVENT_ON_HANDLE) if err != nil { return nil, err } @@ -121,14 +120,14 @@ func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) { return f, nil } -// closeHandle closes the resources associated with a Win32 handle +// closeHandle closes the resources associated with a Win32 handle. func (f *win32File) closeHandle() { f.wgLock.Lock() // Atomically set that we are closing, releasing the resources only once. if !f.closing.swap(true) { f.wgLock.Unlock() // cancel all IO and wait for it to complete - cancelIoEx(f.handle, nil) + _ = cancelIoEx(f.handle, nil) f.wg.Wait() // at this point, no new IO can start syscall.Close(f.handle) @@ -144,14 +143,14 @@ func (f *win32File) Close() error { return nil } -// IsClosed checks if the file has been closed +// IsClosed checks if the file has been closed. func (f *win32File) IsClosed() bool { return f.closing.isSet() } -// prepareIo prepares for a new IO operation. +// prepareIO prepares for a new IO operation. // The caller must call f.wg.Done() when the IO is finished, prior to Close() returning. -func (f *win32File) prepareIo() (*ioOperation, error) { +func (f *win32File) prepareIO() (*ioOperation, error) { f.wgLock.RLock() if f.closing.isSet() { f.wgLock.RUnlock() @@ -164,7 +163,7 @@ func (f *win32File) prepareIo() (*ioOperation, error) { return c, nil } -// ioCompletionProcessor processes completed async IOs forever +// ioCompletionProcessor processes completed async IOs forever. func ioCompletionProcessor(h syscall.Handle) { for { var bytes uint32 @@ -180,15 +179,15 @@ func ioCompletionProcessor(h syscall.Handle) { // todo: helsaawy - create an asyncIO version that takes a context -// asyncIo processes the return value from ReadFile or WriteFile, blocking until +// asyncIO processes the return value from ReadFile or WriteFile, blocking until // the operation has actually completed. -func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, err error) (int, error) { - if err != syscall.ERROR_IO_PENDING { +func (f *win32File) asyncIO(c *ioOperation, d *deadlineHandler, bytes uint32, err error) (int, error) { + if err != syscall.ERROR_IO_PENDING { //nolint:errorlint // err is Errno return int(bytes), err } if f.closing.isSet() { - cancelIoEx(f.handle, &c.o) + _ = cancelIoEx(f.handle, &c.o) } var timeout timeoutChan @@ -202,7 +201,7 @@ func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, er select { case r = <-c.ch: err = r.err - if err == syscall.ERROR_OPERATION_ABORTED { + if err == syscall.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno if f.closing.isSet() { err = ErrFileClosed } @@ -212,10 +211,10 @@ func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, er err = wsaGetOverlappedResult(f.handle, &c.o, &bytes, false, &flags) } case <-timeout: - cancelIoEx(f.handle, &c.o) + _ = cancelIoEx(f.handle, &c.o) r = <-c.ch err = r.err - if err == syscall.ERROR_OPERATION_ABORTED { + if err == syscall.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno err = ErrTimeout } } @@ -230,7 +229,7 @@ func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, er // Read reads from a file handle. func (f *win32File) Read(b []byte) (int, error) { - c, err := f.prepareIo() + c, err := f.prepareIO() if err != nil { return 0, err } @@ -242,13 +241,13 @@ func (f *win32File) Read(b []byte) (int, error) { var bytes uint32 err = syscall.ReadFile(f.handle, b, &bytes, &c.o) - n, err := f.asyncIo(c, &f.readDeadline, bytes, err) + n, err := f.asyncIO(c, &f.readDeadline, bytes, err) runtime.KeepAlive(b) // Handle EOF conditions. if err == nil && n == 0 && len(b) != 0 { return 0, io.EOF - } else if err == syscall.ERROR_BROKEN_PIPE { + } else if err == syscall.ERROR_BROKEN_PIPE { //nolint:errorlint // err is Errno return 0, io.EOF } else { return n, err @@ -257,7 +256,7 @@ func (f *win32File) Read(b []byte) (int, error) { // Write writes to a file handle. func (f *win32File) Write(b []byte) (int, error) { - c, err := f.prepareIo() + c, err := f.prepareIO() if err != nil { return 0, err } @@ -269,7 +268,7 @@ func (f *win32File) Write(b []byte) (int, error) { var bytes uint32 err = syscall.WriteFile(f.handle, b, &bytes, &c.o) - n, err := f.asyncIo(c, &f.writeDeadline, bytes, err) + n, err := f.asyncIO(c, &f.writeDeadline, bytes, err) runtime.KeepAlive(b) return n, err } diff --git a/fileinfo.go b/fileinfo.go index 350c1096..702950e7 100644 --- a/fileinfo.go +++ b/fileinfo.go @@ -15,13 +15,18 @@ import ( type FileBasicInfo struct { CreationTime, LastAccessTime, LastWriteTime, ChangeTime windows.Filetime FileAttributes uint32 - pad uint32 // padding + _ uint32 // padding } // GetFileBasicInfo retrieves times and attributes for a file. func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) { bi := &FileBasicInfo{} - if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil { + if err := windows.GetFileInformationByHandleEx( + windows.Handle(f.Fd()), + windows.FileBasicInfo, + (*byte)(unsafe.Pointer(bi)), + uint32(unsafe.Sizeof(*bi)), + ); err != nil { return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err} } runtime.KeepAlive(f) @@ -30,7 +35,12 @@ func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) { // SetFileBasicInfo sets times and attributes for a file. func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error { - if err := windows.SetFileInformationByHandle(windows.Handle(f.Fd()), windows.FileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil { + if err := windows.SetFileInformationByHandle( + windows.Handle(f.Fd()), + windows.FileBasicInfo, + (*byte)(unsafe.Pointer(bi)), + uint32(unsafe.Sizeof(*bi)), + ); err != nil { return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err} } runtime.KeepAlive(f) @@ -49,7 +59,10 @@ type FileStandardInfo struct { // GetFileStandardInfo retrieves ended information for the file. func GetFileStandardInfo(f *os.File) (*FileStandardInfo, error) { si := &FileStandardInfo{} - if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileStandardInfo, (*byte)(unsafe.Pointer(si)), uint32(unsafe.Sizeof(*si))); err != nil { + if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), + windows.FileStandardInfo, + (*byte)(unsafe.Pointer(si)), + uint32(unsafe.Sizeof(*si))); err != nil { return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err} } runtime.KeepAlive(f) @@ -66,7 +79,12 @@ type FileIDInfo struct { // GetFileID retrieves the unique (volume, file ID) pair for a file. func GetFileID(f *os.File) (*FileIDInfo, error) { fileID := &FileIDInfo{} - if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileIdInfo, (*byte)(unsafe.Pointer(fileID)), uint32(unsafe.Sizeof(*fileID))); err != nil { + if err := windows.GetFileInformationByHandleEx( + windows.Handle(f.Fd()), + windows.FileIdInfo, + (*byte)(unsafe.Pointer(fileID)), + uint32(unsafe.Sizeof(*fileID)), + ); err != nil { return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err} } runtime.KeepAlive(f) diff --git a/fileinfo_test.go b/fileinfo_test.go index 8c97f506..bdb87edd 100644 --- a/fileinfo_test.go +++ b/fileinfo_test.go @@ -4,7 +4,6 @@ package winio import ( - "io/ioutil" "os" "testing" @@ -45,7 +44,7 @@ func checkFileStandardInfo(t *testing.T, current, expected *FileStandardInfo) { } func TestGetFileStandardInfo_File(t *testing.T) { - f, err := ioutil.TempFile("", "tst") + f, err := os.CreateTemp("", "tst") if err != nil { t.Fatal(err) } @@ -107,12 +106,7 @@ func TestGetFileStandardInfo_File(t *testing.T) { } func TestGetFileStandardInfo_Directory(t *testing.T) { - tempDir, err := ioutil.TempDir("", "tst") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tempDir) - + tempDir := t.TempDir() // os.Open returns the Search Handle, not the Directory Handle // See https://github.com/golang/go/issues/13738 f, err := OpenForBackup(tempDir, windows.GENERIC_READ, 0, windows.OPEN_EXISTING) diff --git a/hvsock.go b/hvsock.go index 8b56f96b..52f1c280 100644 --- a/hvsock.go +++ b/hvsock.go @@ -20,7 +20,7 @@ import ( "github.com/Microsoft/go-winio/pkg/guid" ) -const afHvSock = 34 // AF_HYPERV +const afHVSock = 34 // AF_HYPERV // Well known Service and VM IDs //https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/make-integration-service#vmid-wildcards @@ -30,7 +30,7 @@ func HvsockGUIDWildcard() guid.GUID { // 00000000-0000-0000-0000-000000000000 return guid.GUID{} } -// HvsockGUIDBroadcast is the wildcard VmId for broadcasting sends to all partitions +// HvsockGUIDBroadcast is the wildcard VmId for broadcasting sends to all partitions. func HvsockGUIDBroadcast() guid.GUID { //ffffffff-ffff-ffff-ffff-ffffffffffff return guid.GUID{ Data1: 0xffffffff, @@ -51,8 +51,8 @@ func HvsockGUIDLoopback() guid.GUID { // e0e16197-dd56-4a10-9195-5ee7a155a838 } // HvsockGUIDSiloHost is the address of a silo's host partition: -// - The silo host of a hosted silo is the utility VM. -// - The silo host of a silo on a physical host is the physical host. +// - The silo host of a hosted silo is the utility VM. +// - The silo host of a silo on a physical host is the physical host. func HvsockGUIDSiloHost() guid.GUID { // 36bd0c5c-7276-4223-88ba-7d03b654c568 return guid.GUID{ Data1: 0x36bd0c5c, @@ -74,10 +74,10 @@ func HvsockGUIDChildren() guid.GUID { // 90db8b89-0d35-4f79-8ce9-49ea0ac8b7cd // HvsockGUIDParent is the wildcard VmId for accepting connections from the connector's parent partition. // Listening on this VmId accepts connection from: -// - Inside silos: silo host partition. -// - Inside hosted silo: host of the VM. -// - Inside VM: VM host. -// - Physical host: Not supported. +// - Inside silos: silo host partition. +// - Inside hosted silo: host of the VM. +// - Inside VM: VM host. +// - Physical host: Not supported. func HvsockGUIDParent() guid.GUID { // a42e7cda-d03f-480c-9cc2-a4de20abb878 return guid.GUID{ Data1: 0xa42e7cda, @@ -87,7 +87,7 @@ func HvsockGUIDParent() guid.GUID { // a42e7cda-d03f-480c-9cc2-a4de20abb878 } } -// hvsockVsockServiceTemplate is the Service GUID used for the VSOCK protocol +// hvsockVsockServiceTemplate is the Service GUID used for the VSOCK protocol. func hvsockVsockServiceTemplate() guid.GUID { // 00000000-facb-11e6-bd58-64006a7986d3 return guid.GUID{ Data2: 0xfacb, @@ -112,7 +112,7 @@ type rawHvsockAddr struct { var _ socket.RawSockaddr = &rawHvsockAddr{} // Network returns the address's network name, "hvsock". -func (addr *HvsockAddr) Network() string { +func (*HvsockAddr) Network() string { return "hvsock" } @@ -129,7 +129,7 @@ func VsockServiceID(port uint32) guid.GUID { func (addr *HvsockAddr) raw() rawHvsockAddr { return rawHvsockAddr{ - Family: afHvSock, + Family: afHVSock, VMID: addr.VMID, ServiceID: addr.ServiceID, } @@ -143,12 +143,12 @@ func (addr *HvsockAddr) fromRaw(raw *rawHvsockAddr) { // Sockaddr returns a pointer to and the size of this struct. // // Implements the [socket.RawSockaddr] interface, and allows use in -// [socket.Bind()] and [socket.ConnectEx()] +// [socket.Bind] and [socket.ConnectEx]. func (r *rawHvsockAddr) Sockaddr() (unsafe.Pointer, int32, error) { return unsafe.Pointer(r), int32(unsafe.Sizeof(rawHvsockAddr{})), nil } -// Sockaddr interface allows use with `sockets.Bind()` and `.ConnectEx()` +// Sockaddr interface allows use with `sockets.Bind()` and `.ConnectEx()`. func (r *rawHvsockAddr) FromBytes(b []byte) error { n := int(unsafe.Sizeof(rawHvsockAddr{})) @@ -157,8 +157,8 @@ func (r *rawHvsockAddr) FromBytes(b []byte) error { } copy(unsafe.Slice((*byte)(unsafe.Pointer(r)), n), b[:n]) - if r.Family != afHvSock { - return fmt.Errorf("got %d, want %d: %w", r.Family, afHvSock, socket.ErrAddrFamily) + if r.Family != afHVSock { + return fmt.Errorf("got %d, want %d: %w", r.Family, afHVSock, socket.ErrAddrFamily) } return nil @@ -180,8 +180,8 @@ type HvsockConn struct { var _ net.Conn = &HvsockConn{} -func newHvSocket() (*win32File, error) { - fd, err := syscall.Socket(afHvSock, syscall.SOCK_STREAM, 1) +func newHVSocket() (*win32File, error) { + fd, err := syscall.Socket(afHVSock, syscall.SOCK_STREAM, 1) if err != nil { return nil, os.NewSyscallError("socket", err) } @@ -197,7 +197,7 @@ func newHvSocket() (*win32File, error) { // ListenHvsock listens for connections on the specified hvsock address. func ListenHvsock(addr *HvsockAddr) (_ *HvsockListener, err error) { l := &HvsockListener{addr: *addr} - sock, err := newHvSocket() + sock, err := newHVSocket() if err != nil { return nil, l.opErr("listen", err) } @@ -224,7 +224,7 @@ func (l *HvsockListener) Addr() net.Addr { // Accept waits for the next connection and returns it. func (l *HvsockListener) Accept() (_ net.Conn, err error) { - sock, err := newHvSocket() + sock, err := newHVSocket() if err != nil { return nil, l.opErr("accept", err) } @@ -233,20 +233,21 @@ func (l *HvsockListener) Accept() (_ net.Conn, err error) { sock.Close() } }() - c, err := l.sock.prepareIo() + c, err := l.sock.prepareIO() if err != nil { return nil, l.opErr("accept", err) } defer l.sock.wg.Done() - // AcceptEx, per documentation, requires an extra 16 bytes per address: + // AcceptEx, per documentation, requires an extra 16 bytes per address. + // // https://docs.microsoft.com/en-us/windows/win32/api/mswsock/nf-mswsock-acceptex const addrlen = uint32(16 + unsafe.Sizeof(rawHvsockAddr{})) var addrbuf [addrlen * 2]byte var bytes uint32 err = syscall.AcceptEx(l.sock.handle, sock.handle, &addrbuf[0], 0 /*rxdatalen*/, addrlen, addrlen, &bytes, &c.o) - if _, err = l.sock.asyncIo(c, nil, bytes, err); err != nil { + if _, err = l.sock.asyncIO(c, nil, bytes, err); err != nil { return nil, l.opErr("accept", os.NewSyscallError("acceptex", err)) } @@ -294,7 +295,7 @@ type HvsockDialer struct { // Dial the Hyper-V socket at addr. // -// See (*HvsockDialer).Dial for more information. +// See [HvsockDialer.Dial] for more information. func Dial(ctx context.Context, addr *HvsockAddr) (conn *HvsockConn, err error) { return (&HvsockDialer{}).Dial(ctx, addr) } @@ -302,6 +303,7 @@ func Dial(ctx context.Context, addr *HvsockAddr) (conn *HvsockConn, err error) { // Dial attempts to connect to the Hyper-V socket at addr, and returns a connection if successful. // Will attempt (HvsockDialer).Retries if dialing fails, waiting (HvsockDialer).RetryWait between // retries. +// // Dialing can be cancelled either by providing (HvsockDialer).Deadline, or cancelling ctx. func (d *HvsockDialer) Dial(ctx context.Context, addr *HvsockAddr) (conn *HvsockConn, err error) { op := "dial" @@ -321,7 +323,7 @@ func (d *HvsockDialer) Dial(ctx context.Context, addr *HvsockAddr) (conn *Hvsock return nil, conn.opErr(op, err) } - sock, err := newHvSocket() + sock, err := newHVSocket() if err != nil { return nil, conn.opErr(op, err) } @@ -337,7 +339,7 @@ func (d *HvsockDialer) Dial(ctx context.Context, addr *HvsockAddr) (conn *Hvsock return nil, conn.opErr(op, os.NewSyscallError("bind", err)) } - c, err := sock.prepareIo() + c, err := sock.prepareIO() if err != nil { return nil, conn.opErr(op, err) } @@ -351,7 +353,7 @@ func (d *HvsockDialer) Dial(ctx context.Context, addr *HvsockAddr) (conn *Hvsock 0, // sendDataLen &bytes, (*windows.Overlapped)(unsafe.Pointer(&c.o))) - _, err = sock.asyncIo(c, nil, bytes, err) + _, err = sock.asyncIO(c, nil, bytes, err) if i < d.Retries && canRedial(err) { if err = d.redialWait(ctx); err == nil { continue @@ -382,7 +384,7 @@ func (d *HvsockDialer) Dial(ctx context.Context, addr *HvsockAddr) (conn *Hvsock } conn.local.fromRaw(&sal) - // one last check for timeout, since asyncIO doesnt check the context + // one last check for timeout, since asyncIO doesn't check the context if err = ctx.Err(); err != nil { return nil, conn.opErr(op, err) } @@ -393,7 +395,7 @@ func (d *HvsockDialer) Dial(ctx context.Context, addr *HvsockAddr) (conn *Hvsock return conn, nil } -// redialWait waits before attempting to redial, resetting the timer as appropriate +// redialWait waits before attempting to redial, resetting the timer as appropriate. func (d *HvsockDialer) redialWait(ctx context.Context) (err error) { if d.RetryWait == 0 { return nil @@ -419,9 +421,9 @@ func (d *HvsockDialer) redialWait(ctx context.Context) (err error) { return ctx.Err() } -// assumes error is a plain, unwrapped syscall.Errno provided by direct syscall +// assumes error is a plain, unwrapped syscall.Errno provided by direct syscall. func canRedial(err error) bool { - // nolint:errorlint + //nolint:errorlint // guaranteed to be an Errno switch err { case windows.WSAECONNREFUSED, windows.WSAENETUNREACH, windows.WSAETIMEDOUT, windows.ERROR_CONNECTION_REFUSED, windows.ERROR_CONNECTION_UNAVAIL: @@ -440,7 +442,7 @@ func (conn *HvsockConn) opErr(op string, err error) error { } func (conn *HvsockConn) Read(b []byte) (int, error) { - c, err := conn.sock.prepareIo() + c, err := conn.sock.prepareIO() if err != nil { return 0, conn.opErr("read", err) } @@ -448,7 +450,7 @@ func (conn *HvsockConn) Read(b []byte) (int, error) { buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))} var flags, bytes uint32 err = syscall.WSARecv(conn.sock.handle, &buf, 1, &bytes, &flags, &c.o, nil) - n, err := conn.sock.asyncIo(c, &conn.sock.readDeadline, bytes, err) + n, err := conn.sock.asyncIO(c, &conn.sock.readDeadline, bytes, err) if err != nil { var eno windows.Errno if errors.As(err, &eno) { @@ -475,7 +477,7 @@ func (conn *HvsockConn) Write(b []byte) (int, error) { } func (conn *HvsockConn) write(b []byte) (int, error) { - c, err := conn.sock.prepareIo() + c, err := conn.sock.prepareIO() if err != nil { return 0, conn.opErr("write", err) } @@ -483,7 +485,7 @@ func (conn *HvsockConn) write(b []byte) (int, error) { buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))} var bytes uint32 err = syscall.WSASend(conn.sock.handle, &buf, 1, &bytes, 0, &c.o, nil) - n, err := conn.sock.asyncIo(c, &conn.sock.writeDeadline, bytes, err) + n, err := conn.sock.asyncIO(c, &conn.sock.writeDeadline, bytes, err) if err != nil { var eno windows.Errno if errors.As(err, &eno) { @@ -503,7 +505,7 @@ func (conn *HvsockConn) IsClosed() bool { return conn.sock.IsClosed() } -// shutdown disables sending or receiving on a socket +// shutdown disables sending or receiving on a socket. func (conn *HvsockConn) shutdown(how int) error { if conn.IsClosed() { return socket.ErrSocketClosed diff --git a/hvsock_go118_test.go b/hvsock_go118_test.go new file mode 100644 index 00000000..dd529146 --- /dev/null +++ b/hvsock_go118_test.go @@ -0,0 +1,88 @@ +//go:build windows && go1.18 + +package winio + +import ( + "errors" + "fmt" + "testing" + "time" +) + +func FuzzHvSockRxTx(f *testing.F) { + for _, b := range [][]byte{ + []byte("hello?"), + []byte("This is a really long string that should be a good example of the really long " + + "payloads that may be sent over hvsockets when really long inputs are being used, tautologically. " + + "That means that we will have to test with really long input sequences, which means that " + + "we need to include really long byte sequences or strings in our testing so that we know that " + + "the sockets can deal with really long inputs. Look at this key mashing: " + + "sdflhsdfgkjdhskljjsad;kljfasd;lfkjsadl ;fasdjfopiwej09q34iur092\"i4o[piwajfliasdkf-012ior]-" + + "01oi3;'lSD= 0 { return nil } return rtlNtStatusToDosError(status) } -const ( - cERROR_PIPE_BUSY = syscall.Errno(231) - cERROR_NO_DATA = syscall.Errno(232) - cERROR_PIPE_CONNECTED = syscall.Errno(535) - cERROR_SEM_TIMEOUT = syscall.Errno(121) - - cSECURITY_SQOS_PRESENT = 0x100000 - cSECURITY_ANONYMOUS = 0 - - cPIPE_TYPE_MESSAGE = 4 - - cPIPE_READMODE_MESSAGE = 2 - - cFILE_OPEN = 1 - cFILE_CREATE = 2 - - cFILE_PIPE_MESSAGE_TYPE = 1 - cFILE_PIPE_REJECT_REMOTE_CLIENTS = 2 - - cSE_DACL_PRESENT = 4 -) - var ( // ErrPipeListenerClosed is returned for pipe operations on listeners that have been closed. ErrPipeListenerClosed = net.ErrClosed @@ -116,9 +96,10 @@ func (f *win32Pipe) RemoteAddr() net.Addr { } func (f *win32Pipe) SetDeadline(t time.Time) error { - f.SetReadDeadline(t) - f.SetWriteDeadline(t) - return nil + if err := f.SetReadDeadline(t); err != nil { + return err + } + return f.SetWriteDeadline(t) } // CloseWrite closes the write side of a message pipe in byte mode. @@ -157,14 +138,14 @@ func (f *win32MessageBytePipe) Read(b []byte) (int, error) { return 0, io.EOF } n, err := f.win32File.Read(b) - if err == io.EOF { + if err == io.EOF { //nolint:errorlint // If this was the result of a zero-byte read, then // it is possible that the read was due to a zero-size // message. Since we are simulating CloseWrite with a // zero-byte message, ensure that all future Read() calls // also return EOF. f.readEOF = true - } else if err == syscall.ERROR_MORE_DATA { + } else if err == syscall.ERROR_MORE_DATA { //nolint:errorlint // err is Errno // ERROR_MORE_DATA indicates that the pipe's read mode is message mode // and the message still has more bytes. Treat this as a success, since // this package presents all named pipes as byte streams. @@ -173,7 +154,7 @@ func (f *win32MessageBytePipe) Read(b []byte) (int, error) { return n, err } -func (s pipeAddress) Network() string { +func (pipeAddress) Network() string { return "pipe" } @@ -184,16 +165,21 @@ func (s pipeAddress) String() string { // tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout. func tryDialPipe(ctx context.Context, path *string, access uint32) (syscall.Handle, error) { for { - select { case <-ctx.Done(): return syscall.Handle(0), ctx.Err() default: - h, err := createFile(*path, access, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0) + h, err := createFile(*path, + access, + 0, + nil, + syscall.OPEN_EXISTING, + windows.FILE_FLAG_OVERLAPPED|windows.SECURITY_SQOS_PRESENT|windows.SECURITY_ANONYMOUS, + 0) if err == nil { return h, nil } - if err != cERROR_PIPE_BUSY { + if err != windows.ERROR_PIPE_BUSY { //nolint:errorlint // err is Errno return h, &os.PathError{Err: err, Op: "open", Path: *path} } // Wait 10 msec and try again. This is a rather simplistic @@ -213,9 +199,10 @@ func DialPipe(path string, timeout *time.Duration) (net.Conn, error) { } else { absTimeout = time.Now().Add(2 * time.Second) } - ctx, _ := context.WithDeadline(context.Background(), absTimeout) + ctx, cancel := context.WithDeadline(context.Background(), absTimeout) + defer cancel() conn, err := DialPipeContext(ctx, path) - if err == context.DeadlineExceeded { + if errors.Is(err, context.DeadlineExceeded) { return nil, ErrTimeout } return conn, err @@ -251,7 +238,7 @@ func DialPipeAccess(ctx context.Context, path string, access uint32) (net.Conn, // If the pipe is in message mode, return a message byte pipe, which // supports CloseWrite(). - if flags&cPIPE_TYPE_MESSAGE != 0 { + if flags&windows.PIPE_TYPE_MESSAGE != 0 { return &win32MessageBytePipe{ win32Pipe: win32Pipe{win32File: f, path: path}, }, nil @@ -283,7 +270,11 @@ func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (sy oa.Length = unsafe.Sizeof(oa) var ntPath unicodeString - if err := rtlDosPathNameToNtPathName(&path16[0], &ntPath, 0, 0).Err(); err != nil { + if err := rtlDosPathNameToNtPathName(&path16[0], + &ntPath, + 0, + 0, + ).Err(); err != nil { return 0, &os.PathError{Op: "open", Path: path, Err: err} } defer localFree(ntPath.Buffer) @@ -292,8 +283,8 @@ func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (sy // The security descriptor is only needed for the first pipe. if first { if sd != nil { - len := uint32(len(sd)) - sdb := localAlloc(0, len) + l := uint32(len(sd)) + sdb := localAlloc(0, l) defer localFree(sdb) copy((*[0xffff]byte)(unsafe.Pointer(sdb))[:], sd) oa.SecurityDescriptor = (*securityDescriptor)(unsafe.Pointer(sdb)) @@ -301,28 +292,28 @@ func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (sy // Construct the default named pipe security descriptor. var dacl uintptr if err := rtlDefaultNpAcl(&dacl).Err(); err != nil { - return 0, fmt.Errorf("getting default named pipe ACL: %s", err) + return 0, fmt.Errorf("getting default named pipe ACL: %w", err) } defer localFree(dacl) sdb := &securityDescriptor{ Revision: 1, - Control: cSE_DACL_PRESENT, + Control: windows.SE_DACL_PRESENT, Dacl: dacl, } oa.SecurityDescriptor = sdb } } - typ := uint32(cFILE_PIPE_REJECT_REMOTE_CLIENTS) + typ := uint32(windows.FILE_PIPE_REJECT_REMOTE_CLIENTS) if c.MessageMode { - typ |= cFILE_PIPE_MESSAGE_TYPE + typ |= windows.FILE_PIPE_MESSAGE_TYPE } - disposition := uint32(cFILE_OPEN) + disposition := uint32(windows.FILE_OPEN) access := uint32(syscall.GENERIC_READ | syscall.GENERIC_WRITE | syscall.SYNCHRONIZE) if first { - disposition = cFILE_CREATE + disposition = windows.FILE_CREATE // By not asking for read or write access, the named pipe file system // will put this pipe into an initially disconnected state, blocking // client connections until the next call with first == false. @@ -335,7 +326,20 @@ func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (sy h syscall.Handle iosb ioStatusBlock ) - err = ntCreateNamedPipeFile(&h, access, &oa, &iosb, syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE, disposition, 0, typ, 0, 0, 0xffffffff, uint32(c.InputBufferSize), uint32(c.OutputBufferSize), &timeout).Err() + err = ntCreateNamedPipeFile(&h, + access, + &oa, + &iosb, + syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE, + disposition, + 0, + typ, + 0, + 0, + 0xffffffff, + uint32(c.InputBufferSize), + uint32(c.OutputBufferSize), + &timeout).Err() if err != nil { return 0, &os.PathError{Op: "open", Path: path, Err: err} } @@ -380,7 +384,7 @@ func (l *win32PipeListener) makeConnectedServerPipe() (*win32File, error) { p.Close() p = nil err = <-ch - if err == nil || err == ErrFileClosed { + if err == nil || err == ErrFileClosed { //nolint:errorlint // err is Errno err = ErrPipeListenerClosed } } @@ -402,12 +406,12 @@ func (l *win32PipeListener) listenerRoutine() { p, err = l.makeConnectedServerPipe() // If the connection was immediately closed by the client, try // again. - if err != cERROR_NO_DATA { + if err != windows.ERROR_NO_DATA { //nolint:errorlint // err is Errno break } } responseCh <- acceptResponse{p, err} - closed = err == ErrPipeListenerClosed + closed = err == ErrPipeListenerClosed //nolint:errorlint // err is Errno } } syscall.Close(l.firstHandle) @@ -469,15 +473,15 @@ func ListenPipe(path string, c *PipeConfig) (net.Listener, error) { } func connectPipe(p *win32File) error { - c, err := p.prepareIo() + c, err := p.prepareIO() if err != nil { return err } defer p.wg.Done() err = connectNamedPipe(p.handle, &c.o) - _, err = p.asyncIo(c, nil, 0, err) - if err != nil && err != cERROR_PIPE_CONNECTED { + _, err = p.asyncIO(c, nil, 0, err) + if err != nil && err != windows.ERROR_PIPE_CONNECTED { //nolint:errorlint // err is Errno return err } return nil diff --git a/pipe_test.go b/pipe_test.go index aa0eb77c..c07c06be 100644 --- a/pipe_test.go +++ b/pipe_test.go @@ -7,14 +7,16 @@ import ( "bufio" "bytes" "context" + "errors" "io" "net" - "os" "sync" "syscall" "testing" "time" "unsafe" + + "golang.org/x/sys/windows" ) var testPipeName = `\\.\pipe\winiotestpipe` @@ -23,7 +25,7 @@ var aLongTimeAgo = time.Unix(1, 0) func TestDialUnknownFailsImmediately(t *testing.T) { _, err := DialPipe(testPipeName, nil) - if err.(*os.PathError).Err != syscall.ENOENT { + if !errors.Is(err, syscall.ENOENT) { t.Fatalf("expected ENOENT got %v", err) } } @@ -34,9 +36,9 @@ func TestDialListenerTimesOut(t *testing.T) { t.Fatal(err) } defer l.Close() - var d = time.Duration(10 * time.Millisecond) + var d = 10 * time.Millisecond _, err = DialPipe(testPipeName, &d) - if err != ErrTimeout { + if !errors.Is(err, ErrTimeout) { t.Fatalf("expected ErrTimeout, got %v", err) } } @@ -47,10 +49,11 @@ func TestDialContextListenerTimesOut(t *testing.T) { t.Fatal(err) } defer l.Close() - var d = time.Duration(10 * time.Millisecond) - ctx, _ := context.WithTimeout(context.Background(), d) + var d = 10 * time.Millisecond + ctx, cancel := context.WithTimeout(context.Background(), d) + defer cancel() _, err = DialPipeContext(ctx, testPipeName) - if err != context.DeadlineExceeded { + if !errors.Is(err, context.DeadlineExceeded) { t.Fatalf("expected context.DeadlineExceeded, got %v", err) } } @@ -70,7 +73,7 @@ func TestDialListenerGetsCancelled(t *testing.T) { time.Sleep(time.Millisecond * 30) cancel() err = <-ch - if err != context.Canceled { + if !errors.Is(err, context.Canceled) { t.Fatalf("expected context.Canceled, got %v", err) } } @@ -85,7 +88,7 @@ func TestDialAccessDeniedWithRestrictedSD(t *testing.T) { } defer l.Close() _, err = DialPipe(testPipeName, nil) - if err.(*os.PathError).Err != syscall.ERROR_ACCESS_DENIED { + if !errors.Is(err, syscall.ERROR_ACCESS_DENIED) { t.Fatalf("expected ERROR_ACCESS_DENIED, got %v", err) } } @@ -93,7 +96,7 @@ func TestDialAccessDeniedWithRestrictedSD(t *testing.T) { func getConnection(cfg *PipeConfig) (client net.Conn, server net.Conn, err error) { l, err := ListenPipe(testPipeName, cfg) if err != nil { - return + return nil, nil, err } defer l.Close() @@ -109,18 +112,16 @@ func getConnection(cfg *PipeConfig) (client net.Conn, server net.Conn, err error c, err := DialPipe(testPipeName, nil) if err != nil { - return + return client, server, err } r := <-ch if err = r.err; err != nil { c.Close() - return + return nil, nil, err } - client = c - server = r.c - return + return c, r.c, nil } func TestReadTimeout(t *testing.T) { @@ -131,11 +132,11 @@ func TestReadTimeout(t *testing.T) { defer c.Close() defer s.Close() - c.SetReadDeadline(time.Now().Add(10 * time.Millisecond)) + _ = c.SetReadDeadline(time.Now().Add(10 * time.Millisecond)) buf := make([]byte, 10) _, err = c.Read(buf) - if err != ErrTimeout { + if !errors.Is(err, ErrTimeout) { t.Fatalf("expected ErrTimeout, got %v", err) } } @@ -216,7 +217,7 @@ func TestCloseAbortsListen(t *testing.T) { l.Close() err = <-ch - if err != ErrPipeListenerClosed { + if !errors.Is(err, ErrPipeListenerClosed) { t.Fatalf("expected ErrPipeListenerClosed, got %v", err) } } @@ -275,7 +276,7 @@ func TestCloseWriteEOF(t *testing.T) { b := make([]byte, 10) _, err = s.Read(b) - if err != io.EOF { + if !errors.Is(err, io.EOF) { t.Fatal(err) } } @@ -287,7 +288,7 @@ func TestAcceptAfterCloseFails(t *testing.T) { } l.Close() _, err = l.Accept() - if err != ErrPipeListenerClosed { + if !errors.Is(err, ErrPipeListenerClosed) { t.Fatalf("expected ErrPipeListenerClosed, got %v", err) } } @@ -299,7 +300,7 @@ func TestDialTimesOutByDefault(t *testing.T) { } defer l.Close() _, err = DialPipe(testPipeName, nil) - if err != ErrTimeout { + if !errors.Is(err, ErrTimeout) { t.Fatalf("expected ErrTimeout, got %v", err) } } @@ -316,7 +317,8 @@ func TestTimeoutPendingRead(t *testing.T) { go func() { s, err := l.Accept() if err != nil { - t.Fatal(err) + t.Error(err) + return } time.Sleep(1 * time.Second) s.Close() @@ -337,11 +339,11 @@ func TestTimeoutPendingRead(t *testing.T) { }() time.Sleep(100 * time.Millisecond) // make *sure* the pipe is reading before we set the deadline - client.SetReadDeadline(aLongTimeAgo) + _ = client.SetReadDeadline(aLongTimeAgo) select { case err = <-clientErr: - if err != ErrTimeout { + if !errors.Is(err, ErrTimeout) { t.Fatalf("expected ErrTimeout, got %v", err) } case <-time.After(100 * time.Millisecond): @@ -363,7 +365,8 @@ func TestTimeoutPendingWrite(t *testing.T) { go func() { s, err := l.Accept() if err != nil { - t.Fatal(err) + t.Error(err) + return } time.Sleep(1 * time.Second) s.Close() @@ -383,11 +386,11 @@ func TestTimeoutPendingWrite(t *testing.T) { }() time.Sleep(100 * time.Millisecond) // make *sure* the pipe is writing before we set the deadline - client.SetWriteDeadline(aLongTimeAgo) + _ = client.SetWriteDeadline(aLongTimeAgo) select { case err = <-clientErr: - if err != ErrTimeout { + if !errors.Is(err, ErrTimeout) { t.Fatalf("expected ErrTimeout, got %v", err) } case <-time.After(100 * time.Millisecond): @@ -419,13 +422,14 @@ func TestEchoWithMessaging(t *testing.T) { // server echo conn, e := l.Accept() if e != nil { - t.Fatal(e) + t.Error(err) + return } defer conn.Close() time.Sleep(500 * time.Millisecond) // make *sure* we don't begin to read before eof signal is sent - io.Copy(conn, conn) - conn.(CloseWriter).CloseWrite() + _, _ = io.Copy(conn, conn) + _ = conn.(CloseWriter).CloseWrite() close(listenerDone) }() timeout := 1 * time.Second @@ -440,10 +444,12 @@ func TestEchoWithMessaging(t *testing.T) { bytes := make([]byte, 2) n, e := client.Read(bytes) if e != nil { - t.Fatal(e) + t.Error(err) + return } if n != 2 { - t.Fatalf("expected 2 bytes, got %v", n) + t.Errorf("expected 2 bytes, got %v", n) + return } close(clientDone) }() @@ -459,7 +465,7 @@ func TestEchoWithMessaging(t *testing.T) { if n != 2 { t.Fatalf("expected 2 bytes, got %v", n) } - client.(CloseWriter).CloseWrite() + _ = client.(CloseWriter).CloseWrite() <-listenerDone <-clientDone } @@ -473,12 +479,13 @@ func TestConnectRace(t *testing.T) { go func() { for { s, err := l.Accept() - if err == ErrPipeListenerClosed { + if errors.Is(err, ErrPipeListenerClosed) { return } if err != nil { - t.Fatal(err) + t.Error(err) + return } s.Close() } @@ -510,11 +517,13 @@ func TestMessageReadMode(t *testing.T) { defer wg.Done() s, err := l.Accept() if err != nil { - t.Fatal(err) + t.Error(err) + return } _, err = s.Write(msg) if err != nil { - t.Fatal(err) + t.Error(err) + return } s.Close() }() @@ -528,7 +537,7 @@ func TestMessageReadMode(t *testing.T) { setNamedPipeHandleState := syscall.NewLazyDLL("kernel32.dll").NewProc("SetNamedPipeHandleState") p := c.(*win32MessageBytePipe) - mode := uint32(cPIPE_READMODE_MESSAGE) + mode := uint32(windows.PIPE_READMODE_MESSAGE) if s, _, err := setNamedPipeHandleState.Call(uintptr(p.handle), uintptr(unsafe.Pointer(&mode)), 0, 0); s == 0 { t.Fatal(err) } @@ -537,7 +546,7 @@ func TestMessageReadMode(t *testing.T) { var vmsg []byte for { n, err := c.Read(ch) - if err == io.EOF { + if err == io.EOF { //nolint:errorlint break } if err != nil { diff --git a/pkg/etw/doc.go b/pkg/etw/doc.go new file mode 100644 index 00000000..888def77 --- /dev/null +++ b/pkg/etw/doc.go @@ -0,0 +1,8 @@ +// Package etw provides support for TraceLogging-based ETW (Event Tracing +// for Windows). TraceLogging is a format of ETW events that are self-describing +// (the event contains information on its own schema). This allows them to be +// decoded without needing a separate manifest with event information. The +// implementation here is based on the information found in +// TraceLoggingProvider.h in the Windows SDK, which implements TraceLogging as a +// set of C macros. +package etw diff --git a/pkg/etw/eventdata.go b/pkg/etw/eventdata.go index abf16803..a6354754 100644 --- a/pkg/etw/eventdata.go +++ b/pkg/etw/eventdata.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package etw @@ -14,60 +15,60 @@ type eventData struct { buffer bytes.Buffer } -// bytes returns the raw binary data containing the event data. The returned +// toBytes returns the raw binary data containing the event data. The returned // value is not copied from the internal buffer, so it can be mutated by the // eventData object after it is returned. -func (ed *eventData) bytes() []byte { +func (ed *eventData) toBytes() []byte { return ed.buffer.Bytes() } // writeString appends a string, including the null terminator, to the buffer. func (ed *eventData) writeString(data string) { - ed.buffer.WriteString(data) - ed.buffer.WriteByte(0) + _, _ = ed.buffer.WriteString(data) + _ = ed.buffer.WriteByte(0) } // writeInt8 appends a int8 to the buffer. func (ed *eventData) writeInt8(value int8) { - ed.buffer.WriteByte(uint8(value)) + _ = ed.buffer.WriteByte(uint8(value)) } // writeInt16 appends a int16 to the buffer. func (ed *eventData) writeInt16(value int16) { - binary.Write(&ed.buffer, binary.LittleEndian, value) + _ = binary.Write(&ed.buffer, binary.LittleEndian, value) } // writeInt32 appends a int32 to the buffer. func (ed *eventData) writeInt32(value int32) { - binary.Write(&ed.buffer, binary.LittleEndian, value) + _ = binary.Write(&ed.buffer, binary.LittleEndian, value) } // writeInt64 appends a int64 to the buffer. func (ed *eventData) writeInt64(value int64) { - binary.Write(&ed.buffer, binary.LittleEndian, value) + _ = binary.Write(&ed.buffer, binary.LittleEndian, value) } // writeUint8 appends a uint8 to the buffer. func (ed *eventData) writeUint8(value uint8) { - ed.buffer.WriteByte(value) + _ = ed.buffer.WriteByte(value) } // writeUint16 appends a uint16 to the buffer. func (ed *eventData) writeUint16(value uint16) { - binary.Write(&ed.buffer, binary.LittleEndian, value) + _ = binary.Write(&ed.buffer, binary.LittleEndian, value) } // writeUint32 appends a uint32 to the buffer. func (ed *eventData) writeUint32(value uint32) { - binary.Write(&ed.buffer, binary.LittleEndian, value) + _ = binary.Write(&ed.buffer, binary.LittleEndian, value) } // writeUint64 appends a uint64 to the buffer. func (ed *eventData) writeUint64(value uint64) { - binary.Write(&ed.buffer, binary.LittleEndian, value) + _ = binary.Write(&ed.buffer, binary.LittleEndian, value) } // writeFiletime appends a FILETIME to the buffer. func (ed *eventData) writeFiletime(value syscall.Filetime) { - binary.Write(&ed.buffer, binary.LittleEndian, value) + _ = binary.Write(&ed.buffer, binary.LittleEndian, value) } diff --git a/pkg/etw/eventdatadescriptor.go b/pkg/etw/eventdatadescriptor.go index 8b0ad481..9cbef490 100644 --- a/pkg/etw/eventdatadescriptor.go +++ b/pkg/etw/eventdatadescriptor.go @@ -1,3 +1,5 @@ +//go:build windows + package etw import ( @@ -13,11 +15,11 @@ const ( ) type eventDataDescriptor struct { - ptr ptr64 - size uint32 - dataType eventDataDescriptorType - reserved1 uint8 - reserved2 uint16 + ptr ptr64 + size uint32 + dataType eventDataDescriptorType + _ uint8 + _ uint16 } func newEventDataDescriptor(dataType eventDataDescriptorType, buffer []byte) eventDataDescriptor { diff --git a/pkg/etw/eventdescriptor.go b/pkg/etw/eventdescriptor.go index cc41f159..0dd11b45 100644 --- a/pkg/etw/eventdescriptor.go +++ b/pkg/etw/eventdescriptor.go @@ -1,3 +1,5 @@ +//go:build windows + package etw // Channel represents the ETW logging channel that is used. It can be used by @@ -45,6 +47,8 @@ const ( ) // EventDescriptor represents various metadata for an ETW event. +// +//nolint:structcheck // task is currently unused type eventDescriptor struct { id uint16 version uint8 @@ -70,6 +74,8 @@ func newEventDescriptor() *eventDescriptor { // should uniquely identify the other event metadata (contained in // EventDescriptor, and field metadata). Only the lower 24 bits of this value // are relevant. +// +//nolint:unused // keep for future use func (ed *eventDescriptor) identity() uint32 { return (uint32(ed.version) << 16) | uint32(ed.id) } @@ -78,6 +84,8 @@ func (ed *eventDescriptor) identity() uint32 { // should uniquely identify the other event metadata (contained in // EventDescriptor, and field metadata). Only the lower 24 bits of this value // are relevant. +// +//nolint:unused // keep for future use func (ed *eventDescriptor) setIdentity(identity uint32) { ed.id = uint16(identity) ed.version = uint8(identity >> 16) diff --git a/pkg/etw/eventmetadata.go b/pkg/etw/eventmetadata.go index 6fdc126c..a2e151a5 100644 --- a/pkg/etw/eventmetadata.go +++ b/pkg/etw/eventmetadata.go @@ -1,3 +1,5 @@ +//go:build windows + package etw import ( @@ -10,6 +12,8 @@ type inType byte // Various inType definitions for TraceLogging. These must match the definitions // found in TraceLoggingProvider.h in the Windows SDK. +// +//nolint:deadcode,varcheck // keep unused constants for potential future use const ( inTypeNull inType = iota inTypeUnicodeString @@ -47,6 +51,8 @@ type outType byte // Various outType definitions for TraceLogging. These must match the // definitions found in TraceLoggingProvider.h in the Windows SDK. +// +//nolint:deadcode,varcheck // keep unused constants for potential future use const ( // outTypeDefault indicates that the default formatting for the inType will // be used by the event decoder. @@ -81,11 +87,11 @@ type eventMetadata struct { buffer bytes.Buffer } -// bytes returns the raw binary data containing the event metadata. Before being +// toBytes returns the raw binary data containing the event metadata. Before being // returned, the current size of the buffer is written to the start of the // buffer. The returned value is not copied from the internal buffer, so it can // be mutated by the eventMetadata object after it is returned. -func (em *eventMetadata) bytes() []byte { +func (em *eventMetadata) toBytes() []byte { // Finalize the event metadata buffer by filling in the buffer length at the // beginning. binary.LittleEndian.PutUint16(em.buffer.Bytes(), uint16(em.buffer.Len())) @@ -95,7 +101,7 @@ func (em *eventMetadata) bytes() []byte { // writeEventHeader writes the metadata for the start of an event to the buffer. // This specifies the event name and tags. func (em *eventMetadata) writeEventHeader(name string, tags uint32) { - binary.Write(&em.buffer, binary.LittleEndian, uint16(0)) // Length placeholder + _ = binary.Write(&em.buffer, binary.LittleEndian, uint16(0)) // Length placeholder em.writeTags(tags) em.buffer.WriteString(name) em.buffer.WriteByte(0) // Null terminator for name @@ -118,7 +124,7 @@ func (em *eventMetadata) writeFieldInner(name string, inType inType, outType out } if arrSize != 0 { - binary.Write(&em.buffer, binary.LittleEndian, arrSize) + _ = binary.Write(&em.buffer, binary.LittleEndian, arrSize) } } @@ -151,13 +157,17 @@ func (em *eventMetadata) writeTags(tags uint32) { } // writeField writes the metadata for a simple field to the buffer. +// +//nolint:unparam // tags is currently always 0, may change in the future func (em *eventMetadata) writeField(name string, inType inType, outType outType, tags uint32) { em.writeFieldInner(name, inType, outType, tags, 0) } // writeArray writes the metadata for an array field to the buffer. The number // of elements in the array must be written as a uint16 in the event data, -// immediately preceeding the event data. +// immediately preceding the event data. +// +//nolint:unparam // tags is currently always 0, may change in the future func (em *eventMetadata) writeArray(name string, inType inType, outType outType, tags uint32) { em.writeFieldInner(name, inType|inTypeArray, outType, tags, 0) } @@ -165,6 +175,8 @@ func (em *eventMetadata) writeArray(name string, inType inType, outType outType, // writeCountedArray writes the metadata for an array field to the buffer. The // size of a counted array is fixed, and the size is written into the metadata // directly. +// +//nolint:unused // keep for future use func (em *eventMetadata) writeCountedArray(name string, count uint16, inType inType, outType outType, tags uint32) { em.writeFieldInner(name, inType|inTypeCountedArray, outType, tags, count) } diff --git a/pkg/etw/eventopt.go b/pkg/etw/eventopt.go index eaace688..73403220 100644 --- a/pkg/etw/eventopt.go +++ b/pkg/etw/eventopt.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package etw diff --git a/pkg/etw/fieldopt.go b/pkg/etw/fieldopt.go index b5ea80a4..b769c896 100644 --- a/pkg/etw/fieldopt.go +++ b/pkg/etw/fieldopt.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package etw @@ -481,7 +482,7 @@ func SmartField(name string, v interface{}) FieldOpt { case reflect.Int32: return SmartField(name, int32(rv.Int())) case reflect.Int64: - return SmartField(name, int64(rv.Int())) + return SmartField(name, int64(rv.Int())) //nolint:unconvert // make look consistent case reflect.Uint: return SmartField(name, uint(rv.Uint())) case reflect.Uint8: @@ -491,13 +492,13 @@ func SmartField(name string, v interface{}) FieldOpt { case reflect.Uint32: return SmartField(name, uint32(rv.Uint())) case reflect.Uint64: - return SmartField(name, uint64(rv.Uint())) + return SmartField(name, uint64(rv.Uint())) //nolint:unconvert // make look consistent case reflect.Uintptr: return SmartField(name, uintptr(rv.Uint())) case reflect.Float32: return SmartField(name, float32(rv.Float())) case reflect.Float64: - return SmartField(name, float64(rv.Float())) + return SmartField(name, float64(rv.Float())) //nolint:unconvert // make look consistent case reflect.String: return SmartField(name, rv.String()) case reflect.Struct: @@ -509,6 +510,9 @@ func SmartField(name string, v interface{}) FieldOpt { } } return Struct(name, fields...) + case reflect.Array, reflect.Chan, reflect.Complex128, reflect.Complex64, + reflect.Func, reflect.Interface, reflect.Invalid, reflect.Map, reflect.Ptr, + reflect.Slice, reflect.UnsafePointer: } } diff --git a/pkg/etw/newprovider.go b/pkg/etw/newprovider.go index 581ef595..3669b4f7 100644 --- a/pkg/etw/newprovider.go +++ b/pkg/etw/newprovider.go @@ -1,3 +1,4 @@ +//go:build windows && (amd64 || arm64 || 386) // +build windows // +build amd64 arm64 386 @@ -45,18 +46,18 @@ func NewProviderWithOptions(name string, options ...ProviderOpt) (provider *Prov trait := &bytes.Buffer{} if opts.group != (guid.GUID{}) { - binary.Write(trait, binary.LittleEndian, uint16(0)) // Write empty size for buffer (update later) - binary.Write(trait, binary.LittleEndian, uint8(1)) // EtwProviderTraitTypeGroup - traitArray := opts.group.ToWindowsArray() // Append group guid + _ = binary.Write(trait, binary.LittleEndian, uint16(0)) // Write empty size for buffer (update later) + _ = binary.Write(trait, binary.LittleEndian, uint8(1)) // EtwProviderTraitTypeGroup + traitArray := opts.group.ToWindowsArray() // Append group guid trait.Write(traitArray[:]) binary.LittleEndian.PutUint16(trait.Bytes(), uint16(trait.Len())) // Update size } metadata := &bytes.Buffer{} - binary.Write(metadata, binary.LittleEndian, uint16(0)) // Write empty size for buffer (to update later) + _ = binary.Write(metadata, binary.LittleEndian, uint16(0)) // Write empty size for buffer (to update later) metadata.WriteString(name) metadata.WriteByte(0) // Null terminator for name - trait.WriteTo(metadata) // Add traits if applicable + _, _ = trait.WriteTo(metadata) // Add traits if applicable binary.LittleEndian.PutUint16(metadata.Bytes(), uint16(metadata.Len())) // Update the size at the beginning of the buffer provider.metadata = metadata.Bytes() @@ -64,8 +65,8 @@ func NewProviderWithOptions(name string, options ...ProviderOpt) (provider *Prov provider.handle, eventInfoClassProviderSetTraits, uintptr(unsafe.Pointer(&provider.metadata[0])), - uint32(len(provider.metadata))); err != nil { - + uint32(len(provider.metadata)), + ); err != nil { return nil, err } diff --git a/pkg/etw/newprovider_unsupported.go b/pkg/etw/newprovider_unsupported.go index 5a05c134..e0057cfe 100644 --- a/pkg/etw/newprovider_unsupported.go +++ b/pkg/etw/newprovider_unsupported.go @@ -1,5 +1,5 @@ -// +build windows -// +build arm +//go:build windows && arm +// +build windows,arm package etw diff --git a/pkg/etw/provider.go b/pkg/etw/provider.go index a5b90d03..8174bff1 100644 --- a/pkg/etw/provider.go +++ b/pkg/etw/provider.go @@ -1,9 +1,10 @@ +//go:build windows // +build windows package etw import ( - "crypto/sha1" + "crypto/sha1" //nolint:gosec // not used for secure application "encoding/binary" "strings" "unicode/utf16" @@ -27,7 +28,7 @@ type Provider struct { keywordAll uint64 } -// String returns the `provider`.ID as a string +// String returns the `provider`.ID as a string. func (provider *Provider) String() string { if provider == nil { return "" @@ -54,6 +55,7 @@ const ( type eventInfoClass uint32 +//nolint:deadcode,varcheck // keep unused constants for potential future use const ( eventInfoClassProviderBinaryTrackInfo eventInfoClass = iota eventInfoClassProviderSetReserved1 @@ -65,10 +67,19 @@ const ( // enable/disable notifications from ETW. type EnableCallback func(guid.GUID, ProviderState, Level, uint64, uint64, uintptr) -func providerCallback(sourceID guid.GUID, state ProviderState, level Level, matchAnyKeyword uint64, matchAllKeyword uint64, filterData uintptr, i uintptr) { +func providerCallback( + sourceID guid.GUID, + state ProviderState, + level Level, + matchAnyKeyword uint64, + matchAllKeyword uint64, + filterData uintptr, + i uintptr, +) { provider := providers.getProvider(uint(i)) switch state { + case ProviderStateCaptureState: case ProviderStateDisable: provider.enabled = false case ProviderStateEnable: @@ -90,17 +101,22 @@ func providerCallback(sourceID guid.GUID, state ProviderState, level Level, matc // // The algorithm is roughly the RFC 4122 algorithm for a V5 UUID, but differs in // the following ways: -// - The input name is first upper-cased, UTF16-encoded, and converted to -// big-endian. -// - No variant is set on the result UUID. -// - The result UUID is treated as being in little-endian format, rather than -// big-endian. +// - The input name is first upper-cased, UTF16-encoded, and converted to +// big-endian. +// - No variant is set on the result UUID. +// - The result UUID is treated as being in little-endian format, rather than +// big-endian. func providerIDFromName(name string) guid.GUID { - buffer := sha1.New() - namespace := guid.GUID{0x482C2DB2, 0xC390, 0x47C8, [8]byte{0x87, 0xF8, 0x1A, 0x15, 0xBF, 0xC1, 0x30, 0xFB}} + buffer := sha1.New() //nolint:gosec // not used for secure application + namespace := guid.GUID{ + Data1: 0x482C2DB2, + Data2: 0xC390, + Data3: 0x47C8, + Data4: [8]byte{0x87, 0xF8, 0x1A, 0x15, 0xBF, 0xC1, 0x30, 0xFB}, + } namespaceBytes := namespace.ToArray() buffer.Write(namespaceBytes[:]) - binary.Write(buffer, binary.BigEndian, utf16.Encode([]rune(strings.ToUpper(name)))) + _ = binary.Write(buffer, binary.BigEndian, utf16.Encode([]rune(strings.ToUpper(name)))) sum := buffer.Sum(nil) sum[7] = (sum[7] & 0xf) | 0x50 @@ -117,25 +133,24 @@ type providerOpts struct { } // ProviderOpt allows the caller to specify provider options to -// NewProviderWithOptions +// NewProviderWithOptions. type ProviderOpt func(*providerOpts) -// WithCallback is used to provide a callback option to NewProviderWithOptions +// WithCallback is used to provide a callback option to NewProviderWithOptions. func WithCallback(callback EnableCallback) ProviderOpt { return func(opts *providerOpts) { opts.callback = callback } } -// WithID is used to provide a provider ID option to NewProviderWithOptions +// WithID is used to provide a provider ID option to NewProviderWithOptions. func WithID(id guid.GUID) ProviderOpt { return func(opts *providerOpts) { opts.id = id } } -// WithGroup is used to provide a provider group option to -// NewProviderWithOptions +// WithGroup is used to provide a provider group option to NewProviderWithOptions. func WithGroup(group guid.GUID) ProviderOpt { return func(opts *providerOpts) { opts.group = group @@ -237,11 +252,17 @@ func (provider *Provider) WriteEvent(name string, eventOpts []EventOpt, fieldOpt // event metadata (e.g. for the name) so we don't need to do this check for // the metadata. dataBlobs := [][]byte{} - if len(ed.bytes()) > 0 { - dataBlobs = [][]byte{ed.bytes()} + if len(ed.toBytes()) > 0 { + dataBlobs = [][]byte{ed.toBytes()} } - return provider.writeEventRaw(options.descriptor, options.activityID, options.relatedActivityID, [][]byte{em.bytes()}, dataBlobs) + return provider.writeEventRaw( + options.descriptor, + options.activityID, + options.relatedActivityID, + [][]byte{em.toBytes()}, + dataBlobs, + ) } // writeEventRaw writes a single ETW event from the provider. This function is @@ -257,17 +278,24 @@ func (provider *Provider) writeEventRaw( relatedActivityID guid.GUID, metadataBlobs [][]byte, dataBlobs [][]byte) error { - dataDescriptorCount := uint32(1 + len(metadataBlobs) + len(dataBlobs)) dataDescriptors := make([]eventDataDescriptor, 0, dataDescriptorCount) - dataDescriptors = append(dataDescriptors, newEventDataDescriptor(eventDataDescriptorTypeProviderMetadata, provider.metadata)) + dataDescriptors = append(dataDescriptors, + newEventDataDescriptor(eventDataDescriptorTypeProviderMetadata, provider.metadata)) for _, blob := range metadataBlobs { - dataDescriptors = append(dataDescriptors, newEventDataDescriptor(eventDataDescriptorTypeEventMetadata, blob)) + dataDescriptors = append(dataDescriptors, + newEventDataDescriptor(eventDataDescriptorTypeEventMetadata, blob)) } for _, blob := range dataBlobs { - dataDescriptors = append(dataDescriptors, newEventDataDescriptor(eventDataDescriptorTypeUserData, blob)) + dataDescriptors = append(dataDescriptors, + newEventDataDescriptor(eventDataDescriptorTypeUserData, blob)) } - return eventWriteTransfer(provider.handle, descriptor, (*windows.GUID)(&activityID), (*windows.GUID)(&relatedActivityID), dataDescriptorCount, &dataDescriptors[0]) + return eventWriteTransfer(provider.handle, + descriptor, + (*windows.GUID)(&activityID), + (*windows.GUID)(&relatedActivityID), + dataDescriptorCount, + &dataDescriptors[0]) } diff --git a/pkg/etw/provider_test.go b/pkg/etw/provider_test.go index 6b7c5ef8..1a98a1af 100644 --- a/pkg/etw/provider_test.go +++ b/pkg/etw/provider_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package etw diff --git a/pkg/etw/providerglobal.go b/pkg/etw/providerglobal.go index ce3d3057..0a1d90dd 100644 --- a/pkg/etw/providerglobal.go +++ b/pkg/etw/providerglobal.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package etw @@ -14,7 +15,6 @@ type providerMap struct { m map[uint]*Provider i uint lock sync.Mutex - once sync.Once } var providers = providerMap{ @@ -50,5 +50,7 @@ func (p *providerMap) getProvider(index uint) *Provider { return p.m[index] } +//todo: combine these into struct, so that "globalProviderCallback" is guaranteed to be initialized through method access + var providerCallbackOnce sync.Once var globalProviderCallback uintptr diff --git a/pkg/etw/ptr64_32.go b/pkg/etw/ptr64_32.go index d1a76125..26c9f194 100644 --- a/pkg/etw/ptr64_32.go +++ b/pkg/etw/ptr64_32.go @@ -1,3 +1,5 @@ +//go:build windows && (386 || arm) +// +build windows // +build 386 arm package etw diff --git a/pkg/etw/ptr64_64.go b/pkg/etw/ptr64_64.go index b86c8f2b..1524c643 100644 --- a/pkg/etw/ptr64_64.go +++ b/pkg/etw/ptr64_64.go @@ -1,3 +1,5 @@ +//go:build windows && (amd64 || arm64) +// +build windows // +build amd64 arm64 package etw diff --git a/tools/etw-provider-gen/noop.go b/pkg/etw/sample/main_other.go similarity index 71% rename from tools/etw-provider-gen/noop.go rename to pkg/etw/sample/main_other.go index bf98ceb4..7c3e856c 100644 --- a/tools/etw-provider-gen/noop.go +++ b/pkg/etw/sample/main_other.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package main diff --git a/pkg/etw/sample/sample.go b/pkg/etw/sample/main_windows.go similarity index 99% rename from pkg/etw/sample/sample.go rename to pkg/etw/sample/main_windows.go index fd315965..c4e4e858 100644 --- a/pkg/etw/sample/sample.go +++ b/pkg/etw/sample/main_windows.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows // Shows a sample usage of the ETW logging package. diff --git a/pkg/etw/etw.go b/pkg/etw/syscall.go similarity index 72% rename from pkg/etw/etw.go rename to pkg/etw/syscall.go index 0bf6e1aa..16f3bb13 100644 --- a/pkg/etw/etw.go +++ b/pkg/etw/syscall.go @@ -1,13 +1,8 @@ -// Package etw provides support for TraceLogging-based ETW (Event Tracing -// for Windows). TraceLogging is a format of ETW events that are self-describing -// (the event contains information on its own schema). This allows them to be -// decoded without needing a separate manifest with event information. The -// implementation here is based on the information found in -// TraceLoggingProvider.h in the Windows SDK, which implements TraceLogging as a -// set of C macros. +//go:build windows + package etw -//go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go etw.go +//go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go syscall.go //sys eventRegister(providerId *windows.GUID, callback uintptr, callbackContext uintptr, providerHandle *providerHandle) (win32err error) = advapi32.EventRegister diff --git a/pkg/etw/wrapper_32.go b/pkg/etw/wrapper_32.go index 6867a1f8..14c49984 100644 --- a/pkg/etw/wrapper_32.go +++ b/pkg/etw/wrapper_32.go @@ -1,3 +1,4 @@ +//go:build windows && (386 || arm) // +build windows // +build 386 arm diff --git a/pkg/etw/wrapper_64.go b/pkg/etw/wrapper_64.go index fe83df2b..8cfe2e8c 100644 --- a/pkg/etw/wrapper_64.go +++ b/pkg/etw/wrapper_64.go @@ -1,3 +1,4 @@ +//go:build windows && (amd64 || arm64) // +build windows // +build amd64 arm64 @@ -19,7 +20,6 @@ func eventWriteTransfer( relatedActivityID *windows.GUID, dataDescriptorCount uint32, dataDescriptors *eventDataDescriptor) (win32err error) { - return eventWriteTransfer_64( providerHandle, descriptor, @@ -34,7 +34,6 @@ func eventSetInformation( class eventInfoClass, information uintptr, length uint32) (win32err error) { - return eventSetInformation_64( providerHandle, class, @@ -46,7 +45,21 @@ func eventSetInformation( // for provider notifications. Because Go has trouble with callback arguments of // different size, it has only pointer-sized arguments, which are then cast to // the appropriate types when calling providerCallback. -func providerCallbackAdapter(sourceID *guid.GUID, state uintptr, level uintptr, matchAnyKeyword uintptr, matchAllKeyword uintptr, filterData uintptr, i uintptr) uintptr { - providerCallback(*sourceID, ProviderState(state), Level(level), uint64(matchAnyKeyword), uint64(matchAllKeyword), filterData, i) +func providerCallbackAdapter( + sourceID *guid.GUID, + state uintptr, + level uintptr, + matchAnyKeyword uintptr, + matchAllKeyword uintptr, + filterData uintptr, + i uintptr, +) uintptr { + providerCallback(*sourceID, + ProviderState(state), + Level(level), + uint64(matchAnyKeyword), + uint64(matchAllKeyword), + filterData, + i) return 0 } diff --git a/pkg/etwlogrus/hook.go b/pkg/etwlogrus/hook.go index 8cf7baa1..76f6239a 100644 --- a/pkg/etwlogrus/hook.go +++ b/pkg/etwlogrus/hook.go @@ -17,7 +17,7 @@ const defaultEventName = "LogrusEntry" // ErrNoProvider is returned when a hook is created without a provider being configured. var ErrNoProvider = errors.New("no ETW registered provider") -// HookOpt is an option to change the behavior of the Logrus ETW hook +// HookOpt is an option to change the behavior of the Logrus ETW hook. type HookOpt func(*Hook) error // Hook is a Logrus hook which logs received events to ETW. @@ -30,16 +30,16 @@ type Hook struct { getEventsOpts func(*logrus.Entry) []etw.EventOpt } -// NewHook registers a new ETW provider and returns a hook to log from it. The -// provider will be closed when the hook is closed. +// NewHook registers a new ETW provider and returns a hook to log from it. +// The provider will be closed when the hook is closed. func NewHook(providerName string, opts ...HookOpt) (*Hook, error) { opts = append(opts, WithNewETWProvider(providerName)) return NewHookFromOpts(opts...) } -// NewHookFromProvider creates a new hook based on an existing ETW provider. The -// provider will not be closed when the hook is closed. +// NewHookFromProvider creates a new hook based on an existing ETW provider. +// The provider will not be closed when the hook is closed. func NewHookFromProvider(provider *etw.Provider, opts ...HookOpt) (*Hook, error) { opts = append(opts, WithExistingETWProvider(provider)) @@ -73,7 +73,7 @@ func (h *Hook) validate() error { // Levels returns the set of levels that this hook wants to receive log entries // for. -func (h *Hook) Levels() []logrus.Level { +func (*Hook) Levels() []logrus.Level { return logrus.AllLevels } @@ -142,7 +142,7 @@ func (h *Hook) Fire(e *logrus.Entry) error { // as a session listening for the event having no available space in its // buffers). Therefore, we don't return the error from WriteEvent, as it is // just noise in many cases. - h.provider.WriteEvent(name, opts, fields) + _ = h.provider.WriteEvent(name, opts, fields) return nil } diff --git a/pkg/etwlogrus/hook_test.go b/pkg/etwlogrus/hook_test.go index 6f3e7ae7..f6e24bde 100644 --- a/pkg/etwlogrus/hook_test.go +++ b/pkg/etwlogrus/hook_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package etwlogrus diff --git a/pkg/etwlogrus/opts.go b/pkg/etwlogrus/opts.go index ea3f2e4f..499fca87 100644 --- a/pkg/etwlogrus/opts.go +++ b/pkg/etwlogrus/opts.go @@ -44,7 +44,7 @@ func WithGetName(f func(*logrus.Entry) string) HookOpt { } } -// WithAdditionalEventOpts allows additional ETW event properties (keywords, tags, etc.) to be specified +// WithEventOpts allows additional ETW event properties (keywords, tags, etc.) to be specified. func WithEventOpts(f func(*logrus.Entry) []etw.EventOpt) HookOpt { return func(h *Hook) error { h.getEventsOpts = f diff --git a/pkg/fs/fs_windows.go b/pkg/fs/fs_windows.go index 53bc797e..7a73aafb 100644 --- a/pkg/fs/fs_windows.go +++ b/pkg/fs/fs_windows.go @@ -27,5 +27,5 @@ func GetFileSystemType(path string) (fsType string, err error) { drive += `\` err = windows.GetVolumeInformation(windows.StringToUTF16Ptr(drive), nil, 0, nil, nil, nil, &buf[0], size) fsType = windows.UTF16ToString(buf) - return + return fsType, err } diff --git a/pkg/fs/fs_windows_test.go b/pkg/fs/fs_windows_test.go index 512b2352..f5e4c6ea 100644 --- a/pkg/fs/fs_windows_test.go +++ b/pkg/fs/fs_windows_test.go @@ -1,6 +1,7 @@ package fs import ( + "errors" "os" "testing" ) @@ -18,7 +19,7 @@ func TestGetFSTypeOfKnownDrive(t *testing.T) { func TestGetFSTypeOfInvalidPath(t *testing.T) { _, err := GetFileSystemType("7:\\") - if err != ErrInvalidPath { + if !errors.Is(err, ErrInvalidPath) { t.Fatalf("Expected `ErrInvalidPath`, got %v", err) } } diff --git a/pkg/guid/guid.go b/pkg/guid/guid.go index 6e5526bd..7e8e60b1 100644 --- a/pkg/guid/guid.go +++ b/pkg/guid/guid.go @@ -7,7 +7,7 @@ package guid import ( "crypto/rand" - "crypto/sha1" + "crypto/sha1" //nolint:gosec // not used for secure application "encoding" "encoding/binary" "fmt" @@ -59,7 +59,7 @@ func NewV4() (GUID, error) { // big-endian UTF16 stream of bytes. If that is desired, the string can be // encoded as such before being passed to this function. func NewV5(namespace GUID, name []byte) (GUID, error) { - b := sha1.New() + b := sha1.New() //nolint:gosec // not used for secure application namespaceBytes := namespace.ToArray() b.Write(namespaceBytes[:]) b.Write(name) diff --git a/pkg/process/process.go b/pkg/process/process.go index 85c70fe6..873d24e9 100644 --- a/pkg/process/process.go +++ b/pkg/process/process.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package process @@ -32,6 +33,8 @@ func EnumProcesses() ([]uint32, error) { // ProcessMemoryCountersEx is the PROCESS_MEMORY_COUNTERS_EX struct from // Windows: // https://docs.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-process_memory_counters_ex +// +//nolint:revive // process.ProcessMemoryCountersEx stutters, too late to change it type ProcessMemoryCountersEx struct { Cb uint32 PageFaultCount uint32 @@ -75,7 +78,7 @@ func QueryFullProcessImageName(process windows.Handle, flags uint32) (string, er for { b := make([]uint16, bufferSize) err := queryFullProcessImageName(process, flags, &b[0], &bufferSize) - if err == windows.ERROR_INSUFFICIENT_BUFFER { + if err == windows.ERROR_INSUFFICIENT_BUFFER { //nolint:errorlint // err is Errno bufferSize = bufferSize * 2 continue } @@ -84,5 +87,4 @@ func QueryFullProcessImageName(process windows.Handle, flags uint32) (string, er } return windows.UTF16ToString(b[:bufferSize]), nil } - } diff --git a/pkg/security/grantvmgroupaccess.go b/pkg/security/grantvmgroupaccess.go index 60292078..6df87b74 100644 --- a/pkg/security/grantvmgroupaccess.go +++ b/pkg/security/grantvmgroupaccess.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package security @@ -20,6 +21,7 @@ type ( trusteeForm uint32 trusteeType uint32 + //nolint:structcheck // structcheck thinks fields are unused, but the are used to pass data to OS explicitAccess struct { accessPermissions accessMask accessMode accessMode @@ -27,6 +29,7 @@ type ( trustee trustee } + //nolint:structcheck,unused // structcheck thinks fields are unused, but the are used to pass data to OS trustee struct { multipleTrustee *trustee multipleTrusteeOperation int32 @@ -44,6 +47,7 @@ const ( desiredAccessReadControl desiredAccess = 0x20000 desiredAccessWriteDac desiredAccess = 0x40000 + //cspell:disable-next-line gvmga = "GrantVmGroupAccess:" inheritModeNoInheritance inheritMode = 0x0 @@ -56,9 +60,9 @@ const ( shareModeRead shareMode = 0x1 shareModeWrite shareMode = 0x2 - sidVmGroup = "S-1-5-83-0" + sidVMGroup = "S-1-5-83-0" - trusteeFormIsSid trusteeForm = 0 + trusteeFormIsSID trusteeForm = 0 trusteeTypeWellKnownGroup trusteeType = 5 ) @@ -67,6 +71,8 @@ const ( // include Grant ACE entries for the VM Group SID. This is a golang re- // implementation of the same function in vmcompute, just not exported in // RS5. Which kind of sucks. Sucks a lot :/ +// +//revive:disable-next-line:var-naming VM, not Vm func GrantVmGroupAccess(name string) error { // Stat (to determine if `name` is a directory). s, err := os.Stat(name) @@ -79,7 +85,7 @@ func GrantVmGroupAccess(name string) error { if err != nil { return err // Already wrapped } - defer syscall.CloseHandle(fd) + defer syscall.CloseHandle(fd) //nolint:errcheck // Get the current DACL and Security Descriptor. Must defer LocalFree on success. ot := objectTypeFileObject @@ -89,7 +95,7 @@ func GrantVmGroupAccess(name string) error { if err := getSecurityInfo(fd, uint32(ot), uint32(si), nil, nil, &origDACL, nil, &sd); err != nil { return fmt.Errorf("%s GetSecurityInfo %s: %w", gvmga, name, err) } - defer syscall.LocalFree((syscall.Handle)(unsafe.Pointer(sd))) + defer syscall.LocalFree((syscall.Handle)(unsafe.Pointer(sd))) //nolint:errcheck // Generate a new DACL which is the current DACL with the required ACEs added. // Must defer LocalFree on success. @@ -97,7 +103,7 @@ func GrantVmGroupAccess(name string) error { if err != nil { return err // Already wrapped } - defer syscall.LocalFree((syscall.Handle)(unsafe.Pointer(newDACL))) + defer syscall.LocalFree((syscall.Handle)(unsafe.Pointer(newDACL))) //nolint:errcheck // And finally use SetSecurityInfo to apply the updated DACL. if err := setSecurityInfo(fd, uint32(ot), uint32(si), uintptr(0), uintptr(0), newDACL, uintptr(0)); err != nil { @@ -110,16 +116,19 @@ func GrantVmGroupAccess(name string) error { // createFile is a helper function to call [Nt]CreateFile to get a handle to // the file or directory. func createFile(name string, isDir bool) (syscall.Handle, error) { - namep := syscall.StringToUTF16(name) + namep, err := syscall.UTF16FromString(name) + if err != nil { + return syscall.InvalidHandle, fmt.Errorf("could not convernt name to UTF-16: %w", err) + } da := uint32(desiredAccessReadControl | desiredAccessWriteDac) sm := uint32(shareModeRead | shareModeWrite) fa := uint32(syscall.FILE_ATTRIBUTE_NORMAL) if isDir { - fa = uint32(fa | syscall.FILE_FLAG_BACKUP_SEMANTICS) + fa |= syscall.FILE_FLAG_BACKUP_SEMANTICS } fd, err := syscall.CreateFile(&namep[0], da, sm, nil, syscall.OPEN_EXISTING, fa, 0) if err != nil { - return 0, fmt.Errorf("%s syscall.CreateFile %s: %w", gvmga, name, err) + return syscall.InvalidHandle, fmt.Errorf("%s syscall.CreateFile %s: %w", gvmga, name, err) } return fd, nil } @@ -128,9 +137,9 @@ func createFile(name string, isDir bool) (syscall.Handle, error) { // The caller is responsible for LocalFree of the returned DACL on success. func generateDACLWithAcesAdded(name string, isDir bool, origDACL uintptr) (uintptr, error) { // Generate pointers to the SIDs based on the string SIDs - sid, err := syscall.StringToSid(sidVmGroup) + sid, err := syscall.StringToSid(sidVMGroup) if err != nil { - return 0, fmt.Errorf("%s syscall.StringToSid %s %s: %w", gvmga, name, sidVmGroup, err) + return 0, fmt.Errorf("%s syscall.StringToSid %s %s: %w", gvmga, name, sidVMGroup, err) } inheritance := inheritModeNoInheritance @@ -139,12 +148,12 @@ func generateDACLWithAcesAdded(name string, isDir bool, origDACL uintptr) (uintp } eaArray := []explicitAccess{ - explicitAccess{ + { accessPermissions: accessMaskDesiredPermission, accessMode: accessModeGrant, inheritance: inheritance, trustee: trustee{ - trusteeForm: trusteeFormIsSid, + trusteeForm: trusteeFormIsSID, trusteeType: trusteeTypeWellKnownGroup, name: uintptr(unsafe.Pointer(sid)), }, diff --git a/pkg/security/grantvmgroupaccess_test.go b/pkg/security/grantvmgroupaccess_test.go index 2dffaa12..bd648935 100644 --- a/pkg/security/grantvmgroupaccess_test.go +++ b/pkg/security/grantvmgroupaccess_test.go @@ -1,9 +1,9 @@ -//+build windows +//go:build windows +// +build windows package security import ( - "io/ioutil" "os" "path/filepath" "regexp" @@ -36,7 +36,7 @@ const ( // S-1-5-83-1-3166535780-1122986932-343720105-43916321:(I)(R,W) func TestGrantVmGroupAccess(t *testing.T) { - f, err := ioutil.TempFile("", "gvmgafile") + f, err := os.CreateTemp("", "gvmgafile") if err != nil { t.Fatal(err) } @@ -45,16 +45,12 @@ func TestGrantVmGroupAccess(t *testing.T) { os.Remove(f.Name()) }() - d, err := ioutil.TempDir("", "gvmgadir") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(d) - + d := t.TempDir() find, err := os.Create(filepath.Join(d, "find.txt")) if err != nil { t.Fatal(err) } + defer find.Close() if err := GrantVmGroupAccess(f.Name()); err != nil { t.Fatal(err) @@ -88,7 +84,6 @@ func TestGrantVmGroupAccess(t *testing.T) { find.Name(), []string{`(I)(R)`}, ) - } func verifyVMAccountDACLs(t *testing.T, name string, permissions []string) { diff --git a/privilege.go b/privilege.go index 120b6055..0ff9dac9 100644 --- a/privilege.go +++ b/privilege.go @@ -25,22 +25,17 @@ import ( //sys lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) = advapi32.LookupPrivilegeDisplayNameW const ( - SE_PRIVILEGE_ENABLED = 2 + //revive:disable-next-line:var-naming ALL_CAPS + SE_PRIVILEGE_ENABLED = windows.SE_PRIVILEGE_ENABLED - ERROR_NOT_ALL_ASSIGNED syscall.Errno = 1300 + //revive:disable-next-line:var-naming ALL_CAPS + ERROR_NOT_ALL_ASSIGNED syscall.Errno = windows.ERROR_NOT_ALL_ASSIGNED SeBackupPrivilege = "SeBackupPrivilege" SeRestorePrivilege = "SeRestorePrivilege" SeSecurityPrivilege = "SeSecurityPrivilege" ) -const ( - securityAnonymous = iota - securityIdentification - securityImpersonation - securityDelegation -) - var ( privNames = make(map[string]uint64) privNameMutex sync.Mutex @@ -52,11 +47,9 @@ type PrivilegeError struct { } func (e *PrivilegeError) Error() string { - s := "" + s := "Could not enable privilege " if len(e.privileges) > 1 { s = "Could not enable privileges " - } else { - s = "Could not enable privilege " } for i, p := range e.privileges { if i != 0 { @@ -95,7 +88,7 @@ func RunWithPrivileges(names []string, fn func() error) error { } func mapPrivileges(names []string) ([]uint64, error) { - var privileges []uint64 + privileges := make([]uint64, 0, len(names)) privNameMutex.Lock() defer privNameMutex.Unlock() for _, name := range names { @@ -128,7 +121,7 @@ func enableDisableProcessPrivilege(names []string, action uint32) error { return err } - p, _ := windows.GetCurrentProcess() + p := windows.CurrentProcess() var token windows.Token err = windows.OpenProcessToken(p, windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, &token) if err != nil { @@ -141,10 +134,10 @@ func enableDisableProcessPrivilege(names []string, action uint32) error { func adjustPrivileges(token windows.Token, privileges []uint64, action uint32) error { var b bytes.Buffer - binary.Write(&b, binary.LittleEndian, uint32(len(privileges))) + _ = binary.Write(&b, binary.LittleEndian, uint32(len(privileges))) for _, p := range privileges { - binary.Write(&b, binary.LittleEndian, p) - binary.Write(&b, binary.LittleEndian, action) + _ = binary.Write(&b, binary.LittleEndian, p) + _ = binary.Write(&b, binary.LittleEndian, action) } prevState := make([]byte, b.Len()) reqSize := uint32(0) @@ -152,7 +145,7 @@ func adjustPrivileges(token windows.Token, privileges []uint64, action uint32) e if !success { return err } - if err == ERROR_NOT_ALL_ASSIGNED { + if err == ERROR_NOT_ALL_ASSIGNED { //nolint:errorlint // err is Errno return &PrivilegeError{privileges} } return nil @@ -178,7 +171,7 @@ func getPrivilegeName(luid uint64) string { } func newThreadToken() (windows.Token, error) { - err := impersonateSelf(securityImpersonation) + err := impersonateSelf(windows.SecurityImpersonation) if err != nil { return 0, err } diff --git a/privileges_test.go b/privileges_test.go index 0d2f492b..2e2175bf 100644 --- a/privileges_test.go +++ b/privileges_test.go @@ -3,11 +3,15 @@ package winio -import "testing" +import ( + "errors" + "testing" +) func TestRunWithUnavailablePrivilege(t *testing.T) { err := RunWithPrivilege("SeCreateTokenPrivilege", func() error { return nil }) - if _, ok := err.(*PrivilegeError); err == nil || !ok { + var perr *PrivilegeError + if !errors.As(err, &perr) { t.Fatal("expected PrivilegeError") } } diff --git a/reparse.go b/reparse.go index 5f2f3a47..67d1a104 100644 --- a/reparse.go +++ b/reparse.go @@ -116,16 +116,16 @@ func EncodeReparsePoint(rp *ReparsePoint) []byte { } var b bytes.Buffer - binary.Write(&b, binary.LittleEndian, &data) + _ = binary.Write(&b, binary.LittleEndian, &data) if !rp.IsMountPoint { flags := uint32(0) if relative { flags |= 1 } - binary.Write(&b, binary.LittleEndian, flags) + _ = binary.Write(&b, binary.LittleEndian, flags) } - binary.Write(&b, binary.LittleEndian, ntTarget16) - binary.Write(&b, binary.LittleEndian, target16) + _ = binary.Write(&b, binary.LittleEndian, ntTarget16) + _ = binary.Write(&b, binary.LittleEndian, target16) return b.Bytes() } diff --git a/sd.go b/sd.go index 48d8557f..5550ef6b 100644 --- a/sd.go +++ b/sd.go @@ -4,6 +4,7 @@ package winio import ( + "errors" "syscall" "unsafe" @@ -19,11 +20,6 @@ import ( //sys localFree(mem uintptr) = LocalFree //sys getSecurityDescriptorLength(sd uintptr) (len uint32) = advapi32.GetSecurityDescriptorLength -const ( - cERROR_NONE_MAPPED = syscall.Errno(1332) - cERROR_INVALID_SID = syscall.Errno(1337) -) - type AccountLookupError struct { Name string Err error @@ -34,10 +30,10 @@ func (e *AccountLookupError) Error() string { return "lookup account: empty account name specified" } var s string - switch e.Err { - case cERROR_INVALID_SID: + switch { + case errors.Is(e.Err, windows.ERROR_INVALID_SID): s = "the security ID structure is invalid" - case cERROR_NONE_MAPPED: + case errors.Is(e.Err, windows.ERROR_NONE_MAPPED): s = "not found" default: s = e.Err.Error() @@ -45,6 +41,8 @@ func (e *AccountLookupError) Error() string { return "lookup account " + e.Name + ": " + s } +func (e *AccountLookupError) Unwrap() error { return e.Err } + type SddlConversionError struct { Sddl string Err error @@ -54,15 +52,19 @@ func (e *SddlConversionError) Error() string { return "convert " + e.Sddl + ": " + e.Err.Error() } +func (e *SddlConversionError) Unwrap() error { return e.Err } + // LookupSidByName looks up the SID of an account by name +// +//revive:disable-next-line:var-naming SID, not Sid func LookupSidByName(name string) (sid string, err error) { if name == "" { - return "", &AccountLookupError{name, cERROR_NONE_MAPPED} + return "", &AccountLookupError{name, windows.ERROR_NONE_MAPPED} } var sidSize, sidNameUse, refDomainSize uint32 err = lookupAccountName(nil, name, nil, &sidSize, nil, &refDomainSize, &sidNameUse) - if err != nil && err != syscall.ERROR_INSUFFICIENT_BUFFER { + if err != nil && err != syscall.ERROR_INSUFFICIENT_BUFFER { //nolint:errorlint // err is Errno return "", &AccountLookupError{name, err} } sidBuffer := make([]byte, sidSize) @@ -82,9 +84,11 @@ func LookupSidByName(name string) (sid string, err error) { } // LookupNameBySid looks up the name of an account by SID +// +//revive:disable-next-line:var-naming SID, not Sid func LookupNameBySid(sid string) (name string, err error) { if sid == "" { - return "", &AccountLookupError{sid, cERROR_NONE_MAPPED} + return "", &AccountLookupError{sid, windows.ERROR_NONE_MAPPED} } sidBuffer, err := windows.UTF16PtrFromString(sid) @@ -100,7 +104,7 @@ func LookupNameBySid(sid string) (name string, err error) { var nameSize, refDomainSize, sidNameUse uint32 err = lookupAccountSid(nil, sidPtr, nil, &nameSize, nil, &refDomainSize, &sidNameUse) - if err != nil && err != windows.ERROR_INSUFFICIENT_BUFFER { + if err != nil && err != windows.ERROR_INSUFFICIENT_BUFFER { //nolint:errorlint // err is Errno return "", &AccountLookupError{sid, err} } diff --git a/sd_test.go b/sd_test.go index 9a6aab8a..c72bcbf4 100644 --- a/sd_test.go +++ b/sd_test.go @@ -3,20 +3,25 @@ package winio -import "testing" +import ( + "errors" + "testing" + + "golang.org/x/sys/windows" +) func TestLookupInvalidSid(t *testing.T) { _, err := LookupSidByName(".\\weoifjdsklfj") - aerr, ok := err.(*AccountLookupError) - if !ok || aerr.Err != cERROR_NONE_MAPPED { + var aerr *AccountLookupError + if !errors.As(err, &aerr) || !errors.Is(err, windows.ERROR_NONE_MAPPED) { t.Fatalf("expected AccountLookupError with ERROR_NONE_MAPPED, got %s", err) } } func TestLookupInvalidName(t *testing.T) { _, err := LookupNameBySid("notasid") - aerr, ok := err.(*AccountLookupError) - if !ok || aerr.Err != cERROR_INVALID_SID { + var aerr *AccountLookupError + if !errors.As(err, &aerr) || !errors.Is(aerr.Err, windows.ERROR_INVALID_SID) { t.Fatalf("expected AccountLookupError with ERROR_INVALID_SID got %s", err) } } @@ -36,8 +41,8 @@ func TestLookupValidSid(t *testing.T) { func TestLookupEmptyNameFails(t *testing.T) { _, err := LookupSidByName("") - aerr, ok := err.(*AccountLookupError) - if !ok || aerr.Err != cERROR_NONE_MAPPED { + var aerr *AccountLookupError + if !errors.As(err, &aerr) || !errors.Is(aerr.Err, windows.ERROR_NONE_MAPPED) { t.Fatalf("expected AccountLookupError with ERROR_NONE_MAPPED, got %s", err) } } diff --git a/syscall.go b/syscall.go index ca0de234..a6ca111b 100644 --- a/syscall.go +++ b/syscall.go @@ -1,3 +1,5 @@ +//go:build windows + package winio //go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go ./*.go diff --git a/pkg/etw/sample/noop.go b/tools/etw-provider-gen/main_others.go similarity index 71% rename from pkg/etw/sample/noop.go rename to tools/etw-provider-gen/main_others.go index bf98ceb4..7c3e856c 100644 --- a/pkg/etw/sample/noop.go +++ b/tools/etw-provider-gen/main_others.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package main diff --git a/tools/etw-provider-gen/main.go b/tools/etw-provider-gen/main_windows.go similarity index 96% rename from tools/etw-provider-gen/main.go rename to tools/etw-provider-gen/main_windows.go index 9e6df9d6..9d316fbe 100644 --- a/tools/etw-provider-gen/main.go +++ b/tools/etw-provider-gen/main_windows.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package main diff --git a/tools/mkwinsyscall/doc.go b/tools/mkwinsyscall/doc.go index 3d638e41..20b172cc 100644 --- a/tools/mkwinsyscall/doc.go +++ b/tools/mkwinsyscall/doc.go @@ -18,13 +18,16 @@ like func declarations if //sys is replaced by func, but: - If go func name needs to be different from its winapi dll name, the winapi name could be specified at the end, after "=" sign, like + //sys LoadLibrary(libname string) (handle uint32, err error) = LoadLibraryA - Each function that returns err needs to supply a condition, that return value of winapi will be tested against to detect failure. This would set err to windows "last-error", otherwise it will be nil. The value can be provided at end of //sys declaration, like + //sys LoadLibrary(libname string) (handle uint32, err error) [failretval==-1] = LoadLibraryA + and is [failretval==0] by default. - If the function name ends in a "?", then the function not existing is non- @@ -32,21 +35,23 @@ like func declarations if //sys is replaced by func, but: Usage: - mkwinsyscall [flags] [path ...] - -The flags are: - - -output string - Specify output file name (standard output if omitted). - -sort - Sort DLL and Function declarations (default true). Setting to false is intended to maintain - compatibility with older versions of mkwinsyscall so that diffs are easier to read and understand. - -systemdll - Specify that all DLLs should be loaded from the Windows system directory (default true). - -trace - Generate print statement after every syscall. - winio - Import this package ("github.com/Microsoft/go-winio"). - + mkwinsyscall [flags] [path ...] + +Flags + + -output string + Output file name (standard output if omitted). + -sort + Sort DLL and function declarations (default true). + Intended to help transition from older versions of mkwinsyscall by making diffs + easier to read and understand. + -systemdll + Whether all DLLs should be loaded from the Windows system directory (default true). + -trace + Generate print statement after every syscall. + -utf16 + Encode string arguments as UTF-16 for syscalls not ending in 'A' or 'W' (default true). + -winio + Import this package ("github.com/Microsoft/go-winio"). */ package main diff --git a/tools/mkwinsyscall/mkwinsyscall.go b/tools/mkwinsyscall/mkwinsyscall.go index de362c6a..e72be313 100644 --- a/tools/mkwinsyscall/mkwinsyscall.go +++ b/tools/mkwinsyscall/mkwinsyscall.go @@ -16,7 +16,6 @@ import ( "go/parser" "go/token" "io" - "io/ioutil" "log" "os" "path/filepath" @@ -29,6 +28,24 @@ import ( "golang.org/x/sys/windows" ) +const ( + pkgSyscall = "syscall" + pkgWindows = "windows" + + // common types. + + tBool = "bool" + tBoolPtr = "*bool" + tError = "error" + tString = "string" + + // error variable names. + + varErr = "err" + varErrNTStatus = "ntStatus" + varErrHR = "hr" +) + var ( filename = flag.String("output", "", "output file name (standard output if omitted)") printTraceFlag = flag.Bool("trace", false, "generate print statement after every syscall") @@ -53,20 +70,20 @@ func packagename() string { } func windowsdot() string { - if packageName == "windows" { + if packageName == pkgWindows { return "" } - return "windows." + return pkgWindows + "." } func syscalldot() string { - if packageName == "syscall" { + if packageName == pkgSyscall { return "" } - return "syscall." + return pkgSyscall + "." } -// Param is function parameter +// Param is function parameter. type Param struct { Name string Type string @@ -134,9 +151,9 @@ func (p *Param) StringTmpVarCode() string { // TmpVarCode returns source code for temp variable. func (p *Param) TmpVarCode() string { switch { - case p.Type == "bool": + case p.Type == tBool: return p.BoolTmpVarCode() - case p.Type == "*bool": + case p.Type == tBoolPtr: return p.BoolPointerTmpVarCode() case strings.HasPrefix(p.Type, "[]"): return p.SliceTmpVarCode() @@ -148,7 +165,7 @@ func (p *Param) TmpVarCode() string { // TmpVarReadbackCode returns source code for reading back the temp variable into the original variable. func (p *Param) TmpVarReadbackCode() string { switch { - case p.Type == "*bool": + case p.Type == tBoolPtr: return fmt.Sprintf("*%s = %s != 0", p.Name, p.tmpVar()) default: return "" @@ -170,11 +187,11 @@ func (p *Param) SyscallArgList() []string { t := p.HelperType() var s string switch { - case t == "*bool": + case t == tBoolPtr: s = fmt.Sprintf("unsafe.Pointer(&%s)", p.tmpVar()) case t[0] == '*': s = fmt.Sprintf("unsafe.Pointer(%s)", p.Name) - case t == "bool": + case t == tBool: s = p.tmpVar() case strings.HasPrefix(t, "[]"): return []string{ @@ -189,12 +206,12 @@ func (p *Param) SyscallArgList() []string { // IsError determines if p parameter is used to return error. func (p *Param) IsError() bool { - return p.Name == "err" && p.Type == "error" + return p.Name == varErr && p.Type == tError } // HelperType returns type of parameter p used in helper function. func (p *Param) HelperType() string { - if p.Type == "string" { + if p.Type == tString { return p.fn.StrconvType() } return p.Type @@ -226,9 +243,9 @@ type Rets struct { // ErrorVarName returns error variable name for r. func (r *Rets) ErrorVarName() string { if r.ReturnsError { - return "err" + return varErr } - if r.Type == "error" { + if r.Type == tError { return r.Name } return "" @@ -241,7 +258,7 @@ func (r *Rets) ToParams() []*Param { ps = append(ps, &Param{Name: r.Name, Type: r.Type}) } if r.ReturnsError { - ps = append(ps, &Param{Name: "err", Type: "error"}) + ps = append(ps, &Param{Name: varErr, Type: tError}) } return ps } @@ -295,8 +312,8 @@ func (r *Rets) SetErrorCode() string { const code = `if r0 != 0 { %s = %sErrno(r0) }` - const ntstatus = `if r0 != 0 { - ntstatus = %sNTStatus(r0) + const ntStatus = `if r0 != 0 { + %s = %sNTStatus(r0) }` const hrCode = `if int32(r0) < 0 { if r0&0x1fff0000 == 0x00070000 { @@ -311,22 +328,22 @@ func (r *Rets) SetErrorCode() string { if r.Name == "" { return r.useLongHandleErrorCode("r1") } - if r.Type == "error" { + if r.Type == tError { switch r.Name { - case "ntstatus": - return fmt.Sprintf(ntstatus, windowsdot()) - case "hr": + case varErrNTStatus, strings.ToLower(varErrNTStatus): // allow ntstatus to work + return fmt.Sprintf(ntStatus, r.Name, windowsdot()) + case varErrHR: return fmt.Sprintf(hrCode, r.Name, syscalldot()) default: return fmt.Sprintf(code, r.Name, syscalldot()) } } - s := "" + var s string switch { case r.Type[0] == '*': s = fmt.Sprintf("%s = (%s)(unsafe.Pointer(r0))", r.Name, r.Type) - case r.Type == "bool": + case r.Type == tBool: s = fmt.Sprintf("%s = r0 != 0", r.Name) default: s = fmt.Sprintf("%s = %s(r0)", r.Name, r.Type) @@ -564,7 +581,7 @@ func (f *Fn) HelperCallParamList() string { a := make([]string, 0, len(f.Params)) for _, p := range f.Params { s := p.Name - if p.Type == "string" { + if p.Type == tString { s = p.tmpVar() } a = append(a, s) @@ -583,7 +600,7 @@ func (f *Fn) MaybeAbsent() string { }` errorVar := f.Rets.ErrorVarName() if errorVar == "" { - errorVar = "err" + errorVar = varErr } return fmt.Sprintf(code, errorVar, f.DLLFuncName()) } @@ -616,7 +633,7 @@ func (f *Fn) StrconvType() string { // Otherwise it is false. func (f *Fn) HasStringParam() bool { for _, p := range f.Params { - if p.Type == "string" { + if p.Type == tString { return true } } @@ -892,7 +909,8 @@ func main() { if *filename == "" { _, err = os.Stdout.Write(data) } else { - err = ioutil.WriteFile(*filename, data, 0644) + //nolint:gosec // G306: code file, no need for wants 0600 + err = os.WriteFile(*filename, data, 0644) } if err != nil { log.Fatal(err) @@ -900,6 +918,7 @@ func main() { } // TODO: use println instead to print in the following template + const srcTemplate = ` {{define "main"}} //go:build windows diff --git a/vhd/vhd.go b/vhd/vhd.go index 46f69984..b54cad11 100644 --- a/vhd/vhd.go +++ b/vhd/vhd.go @@ -62,8 +62,8 @@ type OpenVirtualDiskParameters struct { Version2 OpenVersion2 } -// The higher level `OpenVersion2` struct uses bools to refer to `GetInfoOnly` and `ReadOnly` for ease of use. However, -// the internal windows structure uses `BOOLS` aka int32s for these types. `openVersion2` is used for translating +// The higher level `OpenVersion2` struct uses `bool`s to refer to `GetInfoOnly` and `ReadOnly` for ease of use. However, +// the internal windows structure uses `BOOL`s aka int32s for these types. `openVersion2` is used for translating // `OpenVersion2` fields to the correct windows internal field types on the `Open____` methods. type openVersion2 struct { getInfoOnly int32 @@ -87,9 +87,10 @@ type AttachVirtualDiskParameters struct { } const ( + //revive:disable-next-line:var-naming ALL_CAPS VIRTUAL_STORAGE_TYPE_DEVICE_VHDX = 0x3 - // Access Mask for opening a VHD + // Access Mask for opening a VHD. VirtualDiskAccessNone VirtualDiskAccessMask = 0x00000000 VirtualDiskAccessAttachRO VirtualDiskAccessMask = 0x00010000 VirtualDiskAccessAttachRW VirtualDiskAccessMask = 0x00020000 @@ -101,7 +102,7 @@ const ( VirtualDiskAccessAll VirtualDiskAccessMask = 0x003f0000 VirtualDiskAccessWritable VirtualDiskAccessMask = 0x00320000 - // Flags for creating a VHD + // Flags for creating a VHD. CreateVirtualDiskFlagNone CreateVirtualDiskFlag = 0x0 CreateVirtualDiskFlagFullPhysicalAllocation CreateVirtualDiskFlag = 0x1 CreateVirtualDiskFlagPreventWritesToSourceDisk CreateVirtualDiskFlag = 0x2 @@ -109,12 +110,12 @@ const ( CreateVirtualDiskFlagCreateBackingStorage CreateVirtualDiskFlag = 0x8 CreateVirtualDiskFlagUseChangeTrackingSourceLimit CreateVirtualDiskFlag = 0x10 CreateVirtualDiskFlagPreserveParentChangeTrackingState CreateVirtualDiskFlag = 0x20 - CreateVirtualDiskFlagVhdSetUseOriginalBackingStorage CreateVirtualDiskFlag = 0x40 + CreateVirtualDiskFlagVhdSetUseOriginalBackingStorage CreateVirtualDiskFlag = 0x40 //revive:disable-line:var-naming VHD, not Vhd CreateVirtualDiskFlagSparseFile CreateVirtualDiskFlag = 0x80 - CreateVirtualDiskFlagPmemCompatible CreateVirtualDiskFlag = 0x100 + CreateVirtualDiskFlagPmemCompatible CreateVirtualDiskFlag = 0x100 //revive:disable-line:var-naming PMEM, not Pmem CreateVirtualDiskFlagSupportCompressedVolumes CreateVirtualDiskFlag = 0x200 - // Flags for opening a VHD + // Flags for opening a VHD. OpenVirtualDiskFlagNone VirtualDiskFlag = 0x00000000 OpenVirtualDiskFlagNoParents VirtualDiskFlag = 0x00000001 OpenVirtualDiskFlagBlankFile VirtualDiskFlag = 0x00000002 @@ -127,7 +128,7 @@ const ( OpenVirtualDiskFlagNoWriteHardening VirtualDiskFlag = 0x00000100 OpenVirtualDiskFlagSupportCompressedVolumes VirtualDiskFlag = 0x00000200 - // Flags for attaching a VHD + // Flags for attaching a VHD. AttachVirtualDiskFlagNone AttachVirtualDiskFlag = 0x00000000 AttachVirtualDiskFlagReadOnly AttachVirtualDiskFlag = 0x00000001 AttachVirtualDiskFlagNoDriveLetter AttachVirtualDiskFlag = 0x00000002 @@ -140,12 +141,14 @@ const ( AttachVirtualDiskFlagSinglePartition AttachVirtualDiskFlag = 0x00000100 AttachVirtualDiskFlagRegisterVolume AttachVirtualDiskFlag = 0x00000200 - // Flags for detaching a VHD + // Flags for detaching a VHD. DetachVirtualDiskFlagNone DetachVirtualDiskFlag = 0x0 ) // CreateVhdx is a helper function to create a simple vhdx file at the given path using // default values. +// +//revive:disable-next-line:var-naming VHDX, not Vhdx func CreateVhdx(path string, maxSizeInGb, blockSizeInMb uint32) error { params := CreateVirtualDiskParameters{ Version: 2, @@ -172,6 +175,8 @@ func DetachVirtualDisk(handle syscall.Handle) (err error) { } // DetachVhd detaches a vhd found at `path`. +// +//revive:disable-next-line:var-naming VHD, not Vhd func DetachVhd(path string) error { handle, err := OpenVirtualDisk( path, @@ -181,12 +186,16 @@ func DetachVhd(path string) error { if err != nil { return err } - defer syscall.CloseHandle(handle) + defer syscall.CloseHandle(handle) //nolint:errcheck return DetachVirtualDisk(handle) } // AttachVirtualDisk attaches a virtual hard disk for use. -func AttachVirtualDisk(handle syscall.Handle, attachVirtualDiskFlag AttachVirtualDiskFlag, parameters *AttachVirtualDiskParameters) (err error) { +func AttachVirtualDisk( + handle syscall.Handle, + attachVirtualDiskFlag AttachVirtualDiskFlag, + parameters *AttachVirtualDiskParameters, +) (err error) { // Supports both version 1 and 2 of the attach parameters as version 2 wasn't present in RS5. if err := attachVirtualDisk( handle, @@ -203,6 +212,8 @@ func AttachVirtualDisk(handle syscall.Handle, attachVirtualDiskFlag AttachVirtua // AttachVhd attaches a virtual hard disk at `path` for use. Attaches using version 2 // of the ATTACH_VIRTUAL_DISK_PARAMETERS. +// +//revive:disable-next-line:var-naming VHD, not Vhd func AttachVhd(path string) (err error) { handle, err := OpenVirtualDisk( path, @@ -213,7 +224,7 @@ func AttachVhd(path string) (err error) { return err } - defer syscall.CloseHandle(handle) + defer syscall.CloseHandle(handle) //nolint:errcheck params := AttachVirtualDiskParameters{Version: 2} if err := AttachVirtualDisk( handle, @@ -226,7 +237,11 @@ func AttachVhd(path string) (err error) { } // OpenVirtualDisk obtains a handle to a VHD opened with supplied access mask and flags. -func OpenVirtualDisk(vhdPath string, virtualDiskAccessMask VirtualDiskAccessMask, openVirtualDiskFlags VirtualDiskFlag) (syscall.Handle, error) { +func OpenVirtualDisk( + vhdPath string, + virtualDiskAccessMask VirtualDiskAccessMask, + openVirtualDiskFlags VirtualDiskFlag, +) (syscall.Handle, error) { parameters := OpenVirtualDiskParameters{Version: 2} handle, err := OpenVirtualDiskWithParameters( vhdPath, @@ -241,7 +256,12 @@ func OpenVirtualDisk(vhdPath string, virtualDiskAccessMask VirtualDiskAccessMask } // OpenVirtualDiskWithParameters obtains a handle to a VHD opened with supplied access mask, flags and parameters. -func OpenVirtualDiskWithParameters(vhdPath string, virtualDiskAccessMask VirtualDiskAccessMask, openVirtualDiskFlags VirtualDiskFlag, parameters *OpenVirtualDiskParameters) (syscall.Handle, error) { +func OpenVirtualDiskWithParameters( + vhdPath string, + virtualDiskAccessMask VirtualDiskAccessMask, + openVirtualDiskFlags VirtualDiskFlag, + parameters *OpenVirtualDiskParameters, +) (syscall.Handle, error) { var ( handle syscall.Handle defaultType VirtualStorageType @@ -279,7 +299,12 @@ func OpenVirtualDiskWithParameters(vhdPath string, virtualDiskAccessMask Virtual } // CreateVirtualDisk creates a virtual harddisk and returns a handle to the disk. -func CreateVirtualDisk(path string, virtualDiskAccessMask VirtualDiskAccessMask, createVirtualDiskFlags CreateVirtualDiskFlag, parameters *CreateVirtualDiskParameters) (syscall.Handle, error) { +func CreateVirtualDisk( + path string, + virtualDiskAccessMask VirtualDiskAccessMask, + createVirtualDiskFlags CreateVirtualDiskFlag, + parameters *CreateVirtualDiskParameters, +) (syscall.Handle, error) { var ( handle syscall.Handle defaultType VirtualStorageType @@ -323,6 +348,8 @@ func GetVirtualDiskPhysicalPath(handle syscall.Handle) (_ string, err error) { } // CreateDiffVhd is a helper function to create a differencing virtual disk. +// +//revive:disable-next-line:var-naming VHD, not Vhd func CreateDiffVhd(diffVhdPath, baseVhdPath string, blockSizeInMB uint32) error { // Setting `ParentPath` is how to signal to create a differencing disk. createParams := &CreateVirtualDiskParameters{ diff --git a/wim/decompress.go b/wim/decompress.go index 01fe01cb..993a4131 100644 --- a/wim/decompress.go +++ b/wim/decompress.go @@ -1,3 +1,4 @@ +//go:build windows || linux // +build windows linux package wim @@ -5,7 +6,6 @@ package wim import ( "encoding/binary" "io" - "io/ioutil" "github.com/Microsoft/go-winio/wim/lzx" ) @@ -35,7 +35,6 @@ func newCompressedReader(r *io.SectionReader, originalSize int64, offset int64) for i, n := range chunks32 { chunks[i+1] = int64(n) } - } else { // 64-bit chunk offsets base = (nchunks - 1) * 8 @@ -62,7 +61,7 @@ func newCompressedReader(r *io.SectionReader, originalSize int64, offset int64) suboff := offset % chunkSize if suboff != 0 { - _, err := io.CopyN(ioutil.Discard, cr.d, suboff) + _, err := io.CopyN(io.Discard, cr.d, suboff) if err != nil { return nil, err } @@ -110,7 +109,7 @@ func (r *compressedReader) reset(n int) error { } r.d = d } else { - r.d = ioutil.NopCloser(section) + r.d = io.NopCloser(section) } return nil @@ -119,7 +118,7 @@ func (r *compressedReader) reset(n int) error { func (r *compressedReader) Read(b []byte) (int, error) { for { n, err := r.d.Read(b) - if err != io.EOF { + if err != io.EOF { //nolint:errorlint return n, err } diff --git a/wim/lzx/lzx.go b/wim/lzx/lzx.go index 4deb0df7..f7cf69a9 100644 --- a/wim/lzx/lzx.go +++ b/wim/lzx/lzx.go @@ -100,7 +100,7 @@ func (f *decompressor) ensureAtLeast(n int) error { } n, err := io.ReadAtLeast(f.r, f.b[f.bv-f.bo:], n) if err != nil { - if err == io.EOF { + if err == io.EOF { //nolint:errorlint err = io.ErrUnexpectedEOF } else { f.fail(err) @@ -117,10 +117,8 @@ func (f *decompressor) ensureAtLeast(n int) error { // Otherwise, on error, it sets f.err. func (f *decompressor) feed() bool { err := f.ensureAtLeast(2) - if err != nil { - if err == io.ErrUnexpectedEOF { - return false - } + if err == io.ErrUnexpectedEOF { //nolint:errorlint // returns io.ErrUnexpectedEOF by contract + return false } f.c |= (uint32(f.b[f.bo+1])<<8 | uint32(f.b[f.bo])) << (16 - f.nbits) f.nbits += 16 @@ -232,9 +230,8 @@ func (f *decompressor) getCode(h *huffman) uint16 { // are, since entries with all possible suffixes were // added to the table. c := h.table[f.c>>(32-tablebits)] - if c >= 1<= 1<>(32-(h.maxbits-tablebits))] } @@ -399,41 +396,37 @@ func (f *decompressor) readTrees(readAligned bool) (main *huffman, length *huffm } aligned = buildTable(alignedLen[:]) if aligned == nil { - err = errors.New("corrupt") - return + return main, length, aligned, errors.New("corrupt") } } // The main tree is encoded in two parts. err = f.readTree(f.mainlens[:maincodesplit]) if err != nil { - return + return main, length, aligned, err } err = f.readTree(f.mainlens[maincodesplit:]) if err != nil { - return + return main, length, aligned, err } main = buildTable(f.mainlens[:]) if main == nil { - err = errors.New("corrupt") - return + return main, length, aligned, errors.New("corrupt") } // The length tree is encoding in a single part. err = f.readTree(f.lenlens[:]) if err != nil { - return + return main, length, aligned, err } length = buildTable(f.lenlens[:]) if length == nil { - err = errors.New("corrupt") - return + return main, length, aligned, errors.New("corrupt") } - err = f.err - return + return main, length, aligned, f.err } // readCompressedBlock decodes a compressed block, writing into the window @@ -465,7 +458,7 @@ func (f *decompressor) readCompressedBlock(start, end uint16, hmain, hlength, ha matchlen += 2 var matchoffset uint16 - if slot < 3 { + if slot < 3 { //nolint:nestif // todo: simplify nested complexity // The offset is one of the LRU values. matchoffset = f.lru[slot] f.lru[slot] = f.lru[0] @@ -586,7 +579,7 @@ func (f *decompressor) Read(b []byte) (int, error) { return f.windowReader.Read(b) } -func (f *decompressor) Close() error { +func (*decompressor) Close() error { return nil } diff --git a/wim/validate/noop.go b/wim/validate/main_other.go similarity index 71% rename from wim/validate/noop.go rename to wim/validate/main_other.go index bf98ceb4..7c3e856c 100644 --- a/wim/validate/noop.go +++ b/wim/validate/main_other.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package main diff --git a/wim/validate/validate.go b/wim/validate/main_windows.go similarity index 85% rename from wim/validate/validate.go rename to wim/validate/main_windows.go index 2536c002..b7329fff 100644 --- a/wim/validate/validate.go +++ b/wim/validate/main_windows.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package main @@ -20,7 +21,6 @@ func main() { w, err := wim.NewReader(f) if err != nil { panic(err) - } fmt.Printf("%#v\n%#v\n", w.Image[0], w.Image[0].Windows) @@ -39,13 +39,13 @@ func main() { func recur(d *wim.File) error { files, err := d.Readdir() if err != nil { - return fmt.Errorf("%s: %s", d.Name, err) + return fmt.Errorf("%s: %w", d.Name, err) } for _, f := range files { if f.IsDir() { err = recur(f) if err != nil { - return fmt.Errorf("%s: %s", f.Name, err) + return fmt.Errorf("%s: %w", f.Name, err) } } } diff --git a/wim/wim.go b/wim/wim.go index 45e0c515..8f272f3d 100644 --- a/wim/wim.go +++ b/wim/wim.go @@ -1,3 +1,4 @@ +//go:build windows || linux // +build windows linux // Package wim implements a WIM file parser. @@ -8,13 +9,12 @@ package wim import ( "bytes" - "crypto/sha1" + "crypto/sha1" //nolint:gosec // not used for secure application "encoding/binary" "encoding/xml" "errors" "fmt" "io" - "io/ioutil" "strconv" "sync" "time" @@ -22,6 +22,8 @@ import ( ) // File attribute constants from Windows. +// +//nolint:revive // var-naming: ALL_CAPS const ( FILE_ATTRIBUTE_READONLY = 0x00000001 FILE_ATTRIBUTE_HIDDEN = 0x00000002 @@ -44,6 +46,8 @@ const ( ) // Windows processor architectures. +// +//nolint:revive // var-naming: ALL_CAPS const ( PROCESSOR_ARCHITECTURE_INTEL = 0 PROCESSOR_ARCHITECTURE_MIPS = 1 @@ -62,6 +66,8 @@ const ( var wimImageTag = [...]byte{'M', 'S', 'W', 'I', 'M', 0, 0, 0} +// todo: replace this with pkg/guid.GUID (and add tests to make sure nothing breaks) + type guid struct { Data1 uint32 Data2 uint16 @@ -70,7 +76,18 @@ type guid struct { } func (g guid) String() string { - return fmt.Sprintf("%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", g.Data1, g.Data2, g.Data3, g.Data4[0], g.Data4[1], g.Data4[2], g.Data4[3], g.Data4[4], g.Data4[5], g.Data4[6], g.Data4[7]) + return fmt.Sprintf("%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + g.Data1, + g.Data2, + g.Data3, + g.Data4[0], + g.Data4[1], + g.Data4[2], + g.Data4[3], + g.Data4[4], + g.Data4[5], + g.Data4[6], + g.Data4[7]) } type resourceDescriptor struct { @@ -81,6 +98,7 @@ type resourceDescriptor struct { type resFlag byte +//nolint:deadcode,varcheck // need unused variables for iota to work const ( resFlagFree resFlag = 1 << iota resFlagMetadata @@ -120,6 +138,7 @@ type streamDescriptor struct { type hdrFlag uint32 +//nolint:deadcode,varcheck // need unused variables for iota to work const ( hdrFlagReserved hdrFlag = 1 << iota hdrFlagCompressed @@ -131,6 +150,7 @@ const ( hdrFlagRpFix ) +//nolint:deadcode,varcheck // need unused variables for iota to work const ( hdrFlagCompressReserved hdrFlag = 1 << (iota + 16) hdrFlagCompressXpress @@ -208,13 +228,13 @@ func (ft *Filetime) Time() time.Time { return time.Unix(0, nsec) } -// UnmarshalXML unmarshals the time from a WIM XML blob. +// UnmarshalXML unmarshalls the time from a WIM XML blob. func (ft *Filetime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { - type time struct { + type Time struct { Low string `xml:"LOWPART"` High string `xml:"HIGHPART"` } - var t time + var t Time err := d.DecodeElement(&t, &start) if err != nil { return err @@ -283,6 +303,8 @@ func (e *ParseError) Error() string { return fmt.Sprintf("WIM parse error: %s %s: %s", e.Oper, e.Path, e.Err.Error()) } +func (e *ParseError) Unwrap() error { return e.Err } + // Reader provides functions to read a WIM file. type Reader struct { hdr wimHeader @@ -380,14 +402,14 @@ func NewReader(f io.ReaderAt) (*Reader, error) { return nil, err } - var info info - err = xml.Unmarshal([]byte(xmlinfo), &info) + var inf info + err = xml.Unmarshal([]byte(xmlinfo), &inf) if err != nil { return nil, &ParseError{Oper: "XML info", Err: err} } for i, img := range images { - for _, imgInfo := range info.Image { + for _, imgInfo := range inf.Image { if imgInfo.Index == i+1 { img.ImageInfo = imgInfo break @@ -417,8 +439,8 @@ func (r *Reader) resourceReaderWithOffset(hdr *resourceDescriptor, offset int64) var sr io.ReadCloser section := io.NewSectionReader(r.r, hdr.Offset, hdr.CompressedSize()) if hdr.Flags()&resFlagCompressed == 0 { - section.Seek(offset, 0) - sr = ioutil.NopCloser(section) + _, _ = section.Seek(offset, 0) + sr = io.NopCloser(section) } else { cr, err := newCompressedReader(section, hdr.OriginalSize, offset) if err != nil { @@ -436,7 +458,7 @@ func (r *Reader) readResource(hdr *resourceDescriptor) ([]byte, error) { return nil, err } defer rsrc.Close() - return ioutil.ReadAll(rsrc) + return io.ReadAll(rsrc) } func (r *Reader) readXML() (string, error) { @@ -449,17 +471,17 @@ func (r *Reader) readXML() (string, error) { } defer rsrc.Close() - XMLData := make([]uint16, r.hdr.XMLData.OriginalSize/2) - err = binary.Read(rsrc, binary.LittleEndian, XMLData) + xmlData := make([]uint16, r.hdr.XMLData.OriginalSize/2) + err = binary.Read(rsrc, binary.LittleEndian, xmlData) if err != nil { return "", &ParseError{Oper: "XML data", Err: err} } // The BOM will always indicate little-endian UTF-16. - if XMLData[0] != 0xfeff { + if xmlData[0] != 0xfeff { return "", &ParseError{Oper: "XML data", Err: errors.New("invalid BOM")} } - return string(utf16.Decode(XMLData[1:])), nil + return string(utf16.Decode(xmlData[1:])), nil } func (r *Reader) readOffsetTable(res *resourceDescriptor) (map[SHA1Hash]resourceDescriptor, []*Image, error) { @@ -475,7 +497,7 @@ func (r *Reader) readOffsetTable(res *resourceDescriptor) (map[SHA1Hash]resource for i := 0; ; i++ { var res streamDescriptor err := binary.Read(br, binary.LittleEndian, &res) - if err == io.EOF { + if err == io.EOF { //nolint:errorlint break } if err != nil { @@ -491,7 +513,7 @@ func (r *Reader) readOffsetTable(res *resourceDescriptor) (map[SHA1Hash]resource if err != nil { panic(fmt.Sprint(i, err)) } - hash := sha1.New() + hash := sha1.New() //nolint:gosec // not used for secure application _, err = io.Copy(hash, sec) sec.Close() if err != nil { @@ -522,12 +544,11 @@ func (r *Reader) readOffsetTable(res *resourceDescriptor) (map[SHA1Hash]resource return fileData, images, nil } -func (r *Reader) readSecurityDescriptors(rsrc io.Reader) (sds [][]byte, n int64, err error) { +func (*Reader) readSecurityDescriptors(rsrc io.Reader) (sds [][]byte, n int64, err error) { var secBlock securityblockDisk err = binary.Read(rsrc, binary.LittleEndian, &secBlock) if err != nil { - err = &ParseError{Oper: "security table", Err: err} - return + return sds, 0, &ParseError{Oper: "security table", Err: err} } n += securityblockDiskSize @@ -535,8 +556,7 @@ func (r *Reader) readSecurityDescriptors(rsrc io.Reader) (sds [][]byte, n int64, secSizes := make([]int64, secBlock.NumEntries) err = binary.Read(rsrc, binary.LittleEndian, &secSizes) if err != nil { - err = &ParseError{Oper: "security table sizes", Err: err} - return + return sds, n, &ParseError{Oper: "security table sizes", Err: err} } n += int64(secBlock.NumEntries * 8) @@ -546,8 +566,7 @@ func (r *Reader) readSecurityDescriptors(rsrc io.Reader) (sds [][]byte, n int64, sd := make([]byte, size&0xffffffff) _, err = io.ReadFull(rsrc, sd) if err != nil { - err = &ParseError{Oper: "security descriptor", Err: err} - return + return sds, n, &ParseError{Oper: "security descriptor", Err: err} } n += int64(len(sd)) sds[i] = sd @@ -555,17 +574,16 @@ func (r *Reader) readSecurityDescriptors(rsrc io.Reader) (sds [][]byte, n int64, secsize := int64((secBlock.TotalLength + 7) &^ 7) if n > secsize { - err = &ParseError{Oper: "security descriptor", Err: errors.New("security descriptor table too small")} - return + return sds, n, &ParseError{Oper: "security descriptor", Err: errors.New("security descriptor table too small")} } - _, err = io.CopyN(ioutil.Discard, rsrc, secsize-n) + _, err = io.CopyN(io.Discard, rsrc, secsize-n) if err != nil { - return + return sds, n, err } n = secsize - return + return sds, n, nil } // Open parses the image and returns the root directory. @@ -621,10 +639,10 @@ func (img *Image) readdir(offset int64) ([]*File, error) { img.curOffset = offset } if offset > img.curOffset { - _, err := io.CopyN(ioutil.Discard, img.r, offset-img.curOffset) + _, err := io.CopyN(io.Discard, img.r, offset-img.curOffset) if err != nil { img.reset() - if err == io.EOF { + if err == io.EOF { //nolint:errorlint err = io.ErrUnexpectedEOF } return nil, err @@ -635,7 +653,7 @@ func (img *Image) readdir(offset int64) ([]*File, error) { for { e, n, err := img.readNextEntry(img.r) img.curOffset += n - if err == io.EOF { + if err == io.EOF { //nolint:errorlint break } if err != nil { @@ -699,7 +717,11 @@ func (img *Image) readNextEntry(r io.Reader) (*File, int64, error) { var ok bool offset, ok = img.wim.fileData[dentry.Hash] if !ok { - return nil, 0, &ParseError{Oper: "directory entry", Path: name, Err: fmt.Errorf("could not find file data matching hash %#v", dentry)} + return nil, 0, &ParseError{ + Oper: "directory entry", + Path: name, + Err: fmt.Errorf("could not find file data matching hash %#v", dentry), + } } } @@ -742,9 +764,9 @@ func (img *Image) readNextEntry(r io.Reader) (*File, int64, error) { f.SecurityDescriptor = img.sds[dentry.SecurityID] } - _, err = io.CopyN(ioutil.Discard, r, left) + _, err = io.CopyN(io.Discard, r, left) if err != nil { - if err == io.EOF { + if err == io.EOF { //nolint:errorlint err = io.ErrUnexpectedEOF } return nil, 0, err @@ -771,7 +793,11 @@ func (img *Image) readNextEntry(r io.Reader) (*File, int64, error) { } if dentry.Attributes&FILE_ATTRIBUTE_REPARSE_POINT != 0 && f.Size == 0 { - return nil, 0, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("reparse point is missing reparse stream")} + return nil, 0, &ParseError{ + Oper: "directory entry", + Path: name, + Err: errors.New("reparse point is missing reparse stream"), + } } return f, length, nil @@ -781,7 +807,7 @@ func (img *Image) readNextStream(r io.Reader) (*Stream, int64, error) { var length int64 err := binary.Read(r, binary.LittleEndian, &length) if err != nil { - if err == io.EOF { + if err == io.EOF { //nolint:errorlint err = io.ErrUnexpectedEOF } return nil, 0, &ParseError{Oper: "stream length check", Err: err} @@ -818,7 +844,11 @@ func (img *Image) readNextStream(r io.Reader) (*Stream, int64, error) { var ok bool offset, ok = img.wim.fileData[sentry.Hash] if !ok { - return nil, 0, &ParseError{Oper: "stream entry", Path: name, Err: fmt.Errorf("could not find file data matching hash %v", sentry.Hash)} + return nil, 0, &ParseError{ + Oper: "stream entry", + Path: name, + Err: fmt.Errorf("could not find file data matching hash %v", sentry.Hash), + } } } @@ -832,9 +862,9 @@ func (img *Image) readNextStream(r io.Reader) (*Stream, int64, error) { offset: offset, } - _, err = io.CopyN(ioutil.Discard, r, left) + _, err = io.CopyN(io.Discard, r, left) if err != nil { - if err == io.EOF { + if err == io.EOF { //nolint:errorlint err = io.ErrUnexpectedEOF } return nil, 0, err diff --git a/zsyscall_windows.go b/zsyscall_windows.go index 1e921f40..83f45a13 100644 --- a/zsyscall_windows.go +++ b/zsyscall_windows.go @@ -399,25 +399,25 @@ func setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err erro return } -func ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) { +func ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntStatus) { r0, _, _ := syscall.Syscall15(procNtCreateNamedPipeFile.Addr(), 14, uintptr(unsafe.Pointer(pipe)), uintptr(access), uintptr(unsafe.Pointer(oa)), uintptr(unsafe.Pointer(iosb)), uintptr(share), uintptr(disposition), uintptr(options), uintptr(typ), uintptr(readMode), uintptr(completionMode), uintptr(maxInstances), uintptr(inboundQuota), uintptr(outputQuota), uintptr(unsafe.Pointer(timeout)), 0) - status = ntstatus(r0) + status = ntStatus(r0) return } -func rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) { +func rtlDefaultNpAcl(dacl *uintptr) (status ntStatus) { r0, _, _ := syscall.Syscall(procRtlDefaultNpAcl.Addr(), 1, uintptr(unsafe.Pointer(dacl)), 0, 0) - status = ntstatus(r0) + status = ntStatus(r0) return } -func rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) { +func rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntStatus) { r0, _, _ := syscall.Syscall6(procRtlDosPathNameToNtPathName_U.Addr(), 4, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(ntName)), uintptr(filePart), uintptr(reserved), 0, 0) - status = ntstatus(r0) + status = ntStatus(r0) return } -func rtlNtStatusToDosError(status ntstatus) (winerr error) { +func rtlNtStatusToDosError(status ntStatus) (winerr error) { r0, _, _ := syscall.Syscall(procRtlNtStatusToDosErrorNoTeb.Addr(), 1, uintptr(status), 0, 0) if r0 != 0 { winerr = syscall.Errno(r0)