pax_global_header00006660000000000000000000000064146053177500014522gustar00rootroot0000000000000052 comment=3c9576c9346a1892dee136329e7e15309e82fb4f go-winio-0.6.2/000077500000000000000000000000001460531775000132575ustar00rootroot00000000000000go-winio-0.6.2/.gitattributes000066400000000000000000000000221460531775000161440ustar00rootroot00000000000000* text=auto eol=lfgo-winio-0.6.2/.github/000077500000000000000000000000001460531775000146175ustar00rootroot00000000000000go-winio-0.6.2/.github/dependabot.yml000066400000000000000000000005641460531775000174540ustar00rootroot00000000000000# reference: # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file version: 2 updates: - package-ecosystem: "gomod" directory: "/" schedule: interval: "weekly" day: "sunday" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" go-winio-0.6.2/.github/workflows/000077500000000000000000000000001460531775000166545ustar00rootroot00000000000000go-winio-0.6.2/.github/workflows/ci.yml000066400000000000000000000077461460531775000200100ustar00rootroot00000000000000name: CI on: - push - pull_request env: GO_VERSION: "oldstable" GOTESTSUM_VERSION: "latest" GOLANGCILINT_VERSION: "latest" jobs: lint: name: Lint runs-on: windows-2022 steps: - name: Checkout uses: actions/checkout@v4 with: show-progress: false - name: Install go uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} cache: false - name: Run golangci-lint uses: golangci/golangci-lint-action@v4 with: version: ${{ env.GOLANGCILINT_VERSION }} 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-2022 steps: - name: Checkout uses: actions/checkout@v4 with: show-progress: false - name: Install go uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} # don't really need to cache Go packages, since go generate doesn't require much. # otherwise, the cache used in the `test` stage will be (basically) empty. cache: false - 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: - go-generate runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [windows-2019, windows-2022, ubuntu-latest] steps: - name: Checkout uses: actions/checkout@v4 with: show-progress: false - name: Install go uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} # avoid needing to download packages during test runs - name: Pre-fill Module Cache run: go mod download -x - name: Install gotestsum run: go install gotest.tools/gotestsum@${{ env.GOTESTSUM_VERSION }} - name: Test repo shell: pwsh run: | # `go test -race` requires mingw on windows, and there is some weirdness on windows 2019 # which causes tests to fail with `"exit status 0xc0000139"`. # Even trying to update mingw with choco still fails. # # see: https://go.dev/doc/articles/race_detector#Requirements if ( '${{ matrix.os }}' -ne 'windows-2019' ) { $race = '-race' } gotestsum --format standard-verbose --debug -- -gcflags=all=-d=checkptr $race -v ./... # !NOTE: # Fuzzing cannot be run across multiple packages, (ie, `go -fuzz "^Fuzz" ./...` fails). # If new fuzzing tests are added, exec additional runs for each package. - name: Fuzz root package run: gotestsum --format standard-verbose --debug -- -run "^#" -fuzztime 1m -fuzz "^Fuzz" build: name: Build Repo needs: - test runs-on: "windows-2022" steps: - name: Checkout uses: actions/checkout@v4 with: show-progress: false - name: Install go uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} - run: go build ./pkg/etw/sample/ - run: go build ./tools/etw-provider-gen/ - run: go build ./tools/mkwinsyscall/ - run: go build ./wim/validate/ go-winio-0.6.2/.gitignore000066400000000000000000000001111460531775000152400ustar00rootroot00000000000000.vscode/ *.exe # testing testdata # go workspaces go.work go.work.sum go-winio-0.6.2/.golangci.yml000066400000000000000000000102241460531775000156420ustar00rootroot00000000000000linters: enable: # style - containedctx # struct contains a context - dupl # duplicate code - errname # erorrs are named correctly - nolintlint # "//nolint" directives are properly explained - revive # golint replacement - 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 - nilerr # returns nil even with non-nil error - thelper # test helpers without t.Helper() - unparam # unused function params issues: exclude-dirs: - pkg/etw/sample 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) " #TODO: remove after upgrading to go1.18 # ignore comment spacing for nolint and sys directives - linters: - revive text: "^comment-spacings: no space between comment delimiter and comment text" source: "//(cspell:|nolint:|sys |todo)" # not on go 1.18 yet, so no any - linters: - revive text: "^use-any: since GO 1.18 'interface{}' can be replaced by 'any'" # allow unjustified ignores of error checks in defer statements - 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: exhaustive: default-signifies-exhaustive: true 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 nolintlint: 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 - name: unhandled-error # warns over common fmt.Print* and io.Close; rely on errcheck instead 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 go-winio-0.6.2/CODEOWNERS000066400000000000000000000000351460531775000146500ustar00rootroot00000000000000 * @microsoft/containerplat go-winio-0.6.2/LICENSE000066400000000000000000000020651460531775000142670ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015 Microsoft Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. go-winio-0.6.2/README.md000066400000000000000000000072731460531775000145470ustar00rootroot00000000000000# go-winio [![Build Status](https://github.com/microsoft/go-winio/actions/workflows/ci.yml/badge.svg)](https://github.com/microsoft/go-winio/actions/workflows/ci.yml) This repository contains utilities for efficiently performing Win32 IO operations in Go. Currently, this is focused on accessing named pipes and other file handles, and for using named pipes as a net transport. This code relies on IO completion ports to avoid blocking IO on system threads, allowing Go to reuse the thread to schedule another goroutine. This limits support to Windows Vista and newer operating systems. This is similar to the implementation of network sockets in Go's net package. 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 [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. 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 This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 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. [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 go-winio-0.6.2/SECURITY.md000066400000000000000000000053051460531775000150530ustar00rootroot00000000000000 ## Security Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. ## Reporting Security Issues **Please do not report security vulnerabilities through public GitHub issues.** Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) * Full paths of source file(s) related to the manifestation of the issue * The location of the affected source code (tag/branch/commit or direct URL) * Any special configuration required to reproduce the issue * Step-by-step instructions to reproduce the issue * Proof-of-concept or exploit code (if possible) * Impact of the issue, including how an attacker might exploit the issue This information will help us triage your report more quickly. If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. ## Preferred Languages We prefer all communications to be in English. ## Policy Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). go-winio-0.6.2/backup.go000066400000000000000000000202121460531775000150500ustar00rootroot00000000000000//go:build windows // +build windows package winio import ( "encoding/binary" "errors" "fmt" "io" "os" "runtime" "unicode/utf16" "github.com/Microsoft/go-winio/internal/fs" "golang.org/x/sys/windows" ) //sys backupRead(h windows.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupRead //sys backupWrite(h windows.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupWrite const ( BackupData = uint32(iota + 1) BackupEaData BackupSecurity BackupAlternateData BackupLink BackupPropertyData BackupObjectId //revive:disable-line:var-naming ID, not Id BackupReparseData BackupSparseBlock BackupTxfsData ) const ( StreamSparseAttributes = uint32(8) ) //nolint:revive // var-naming: ALL_CAPS const ( 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 Name string // The name of the stream (for BackupAlternateData only). Offset int64 // The offset of the stream in the file (for BackupSparseBlock only). } type win32StreamID struct { StreamID uint32 Attributes uint32 Size uint64 NameSize uint32 } // BackupStreamReader reads from a stream produced by the BackupRead Win32 API and produces a series // of BackupHeader values. type BackupStreamReader struct { r io.Reader bytesLeft int64 } // NewBackupStreamReader produces a BackupStreamReader from any io.Reader. func NewBackupStreamReader(r io.Reader) *BackupStreamReader { return &BackupStreamReader{r, 0} } // 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 { //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. if _, err := s.Seek(0, io.SeekCurrent); err == nil { if _, err = s.Seek(r.bytesLeft, io.SeekCurrent); err != nil { return nil, err } r.bytesLeft = 0 } } if _, err := io.Copy(io.Discard, r); err != nil { return nil, err } } var wsi win32StreamID if err := binary.Read(r.r, binary.LittleEndian, &wsi); err != nil { return nil, err } hdr := &BackupHeader{ Id: wsi.StreamID, Attributes: wsi.Attributes, Size: int64(wsi.Size), } if wsi.NameSize != 0 { name := make([]uint16, int(wsi.NameSize/2)) if err := binary.Read(r.r, binary.LittleEndian, name); err != nil { return nil, err } hdr.Name = windows.UTF16ToString(name) } if wsi.StreamID == BackupSparseBlock { if err := binary.Read(r.r, binary.LittleEndian, &hdr.Offset); err != nil { return nil, err } hdr.Size -= 8 } r.bytesLeft = hdr.Size return hdr, nil } // Read reads from the current backup stream. func (r *BackupStreamReader) Read(b []byte) (int, error) { if r.bytesLeft == 0 { return 0, io.EOF } if int64(len(b)) > r.bytesLeft { b = b[:r.bytesLeft] } n, err := r.r.Read(b) r.bytesLeft -= int64(n) if err == io.EOF { err = io.ErrUnexpectedEOF } else if r.bytesLeft == 0 && err == nil { err = io.EOF } return n, err } // BackupStreamWriter writes a stream compatible with the BackupWrite Win32 API. type BackupStreamWriter struct { w io.Writer bytesLeft int64 } // NewBackupStreamWriter produces a BackupStreamWriter on top of an io.Writer. func NewBackupStreamWriter(w io.Writer) *BackupStreamWriter { return &BackupStreamWriter{w, 0} } // WriteHeader writes the next backup stream header and prepares for calls to Write(). func (w *BackupStreamWriter) WriteHeader(hdr *BackupHeader) error { if w.bytesLeft != 0 { return fmt.Errorf("missing %d bytes", w.bytesLeft) } name := utf16.Encode([]rune(hdr.Name)) wsi := win32StreamID{ StreamID: hdr.Id, Attributes: hdr.Attributes, Size: uint64(hdr.Size), NameSize: uint32(len(name) * 2), } if hdr.Id == BackupSparseBlock { // Include space for the int64 block offset wsi.Size += 8 } if err := binary.Write(w.w, binary.LittleEndian, &wsi); err != nil { return err } if len(name) != 0 { if err := binary.Write(w.w, binary.LittleEndian, name); err != nil { return err } } if hdr.Id == BackupSparseBlock { if err := binary.Write(w.w, binary.LittleEndian, hdr.Offset); err != nil { return err } } w.bytesLeft = hdr.Size return nil } // Write writes to the current backup stream. func (w *BackupStreamWriter) Write(b []byte) (int, error) { if w.bytesLeft < int64(len(b)) { return 0, fmt.Errorf("too many bytes by %d", int64(len(b))-w.bytesLeft) } n, err := w.w.Write(b) w.bytesLeft -= int64(n) return n, err } // BackupFileReader provides an io.ReadCloser interface on top of the BackupRead Win32 API. type BackupFileReader struct { f *os.File includeSecurity bool ctx uintptr } // NewBackupFileReader returns a new BackupFileReader from a file handle. If includeSecurity is true, // Read will attempt to read the security descriptor of the file. func NewBackupFileReader(f *os.File, includeSecurity bool) *BackupFileReader { r := &BackupFileReader{f, includeSecurity, 0} return r } // Read reads a backup stream from the file by calling the Win32 API BackupRead(). func (r *BackupFileReader) Read(b []byte) (int, error) { var bytesRead uint32 err := backupRead(windows.Handle(r.f.Fd()), b, &bytesRead, false, r.includeSecurity, &r.ctx) if err != nil { return 0, &os.PathError{Op: "BackupRead", Path: r.f.Name(), Err: err} } runtime.KeepAlive(r.f) if bytesRead == 0 { return 0, io.EOF } return int(bytesRead), nil } // Close frees Win32 resources associated with the BackupFileReader. It does not close // the underlying file. func (r *BackupFileReader) Close() error { if r.ctx != 0 { _ = backupRead(windows.Handle(r.f.Fd()), nil, nil, true, false, &r.ctx) runtime.KeepAlive(r.f) r.ctx = 0 } return nil } // BackupFileWriter provides an io.WriteCloser interface on top of the BackupWrite Win32 API. type BackupFileWriter struct { f *os.File includeSecurity bool ctx uintptr } // NewBackupFileWriter returns a new BackupFileWriter from a file handle. If includeSecurity is true, // Write() will attempt to restore the security descriptor from the stream. func NewBackupFileWriter(f *os.File, includeSecurity bool) *BackupFileWriter { w := &BackupFileWriter{f, includeSecurity, 0} return w } // Write restores a portion of the file using the provided backup stream. func (w *BackupFileWriter) Write(b []byte) (int, error) { var bytesWritten uint32 err := backupWrite(windows.Handle(w.f.Fd()), b, &bytesWritten, false, w.includeSecurity, &w.ctx) if err != nil { return 0, &os.PathError{Op: "BackupWrite", Path: w.f.Name(), Err: err} } runtime.KeepAlive(w.f) if int(bytesWritten) != len(b) { return int(bytesWritten), errors.New("not all bytes could be written") } return len(b), nil } // Close frees Win32 resources associated with the BackupFileWriter. It does not // close the underlying file. func (w *BackupFileWriter) Close() error { if w.ctx != 0 { _ = backupWrite(windows.Handle(w.f.Fd()), nil, nil, true, false, &w.ctx) runtime.KeepAlive(w.f) w.ctx = 0 } return nil } // OpenForBackup opens a file or directory, potentially skipping access checks if the backup // or restore privileges have been acquired. // // If the file opened was a directory, it cannot be used with Readdir(). func OpenForBackup(path string, access uint32, share uint32, createmode uint32) (*os.File, error) { h, err := fs.CreateFile(path, fs.AccessMask(access), fs.FileShareMode(share), nil, fs.FileCreationDisposition(createmode), fs.FILE_FLAG_BACKUP_SEMANTICS|fs.FILE_FLAG_OPEN_REPARSE_POINT, 0, ) if err != nil { err = &os.PathError{Op: "open", Path: path, Err: err} return nil, err } return os.NewFile(uintptr(h), path), nil } go-winio-0.6.2/backup_test.go000066400000000000000000000104261460531775000161150ustar00rootroot00000000000000//go:build windows // +build windows package winio import ( "io" "os" "testing" "golang.org/x/sys/windows" ) var testFileName string func TestMain(m *testing.M) { f, err := os.CreateTemp("", "tmp") if err != nil { panic(err) } testFileName = f.Name() f.Close() defer os.Remove(testFileName) os.Exit(m.Run()) } func makeTestFile(makeADS bool) error { os.Remove(testFileName) f, err := os.Create(testFileName) if err != nil { return err } defer f.Close() _, err = f.Write([]byte("testing 1 2 3\n")) if err != nil { return err } if makeADS { a, err := os.Create(testFileName + ":ads.txt") if err != nil { return err } defer a.Close() _, err = a.Write([]byte("alternate data stream\n")) if err != nil { return err } } return nil } func TestBackupRead(t *testing.T) { err := makeTestFile(true) if err != nil { t.Fatal(err) } f, err := os.Open(testFileName) if err != nil { t.Fatal(err) } defer f.Close() r := NewBackupFileReader(f, false) defer r.Close() b, err := io.ReadAll(r) if err != nil { t.Fatal(err) } if len(b) == 0 { t.Fatal("no data") } } func TestBackupStreamRead(t *testing.T) { err := makeTestFile(true) if err != nil { t.Fatal(err) } f, err := os.Open(testFileName) if err != nil { t.Fatal(err) } defer f.Close() r := NewBackupFileReader(f, false) defer r.Close() br := NewBackupStreamReader(r) gotData := false gotAltData := false for { hdr, err := br.Next() if err == io.EOF { //nolint:errorlint break } if err != nil { t.Fatal(err) } switch hdr.Id { case BackupData: if gotData { t.Fatal("duplicate data") } if hdr.Name != "" { t.Fatalf("unexpected name %s", hdr.Name) } b, err := io.ReadAll(br) if err != nil { t.Fatal(err) } if string(b) != "testing 1 2 3\n" { t.Fatalf("incorrect data %v", b) } gotData = true case BackupAlternateData: if gotAltData { t.Fatal("duplicate alt data") } if hdr.Name != ":ads.txt:$DATA" { t.Fatalf("incorrect name %s", hdr.Name) } b, err := io.ReadAll(br) if err != nil { t.Fatal(err) } if string(b) != "alternate data stream\n" { t.Fatalf("incorrect data %v", b) } gotAltData = true default: t.Fatalf("unknown stream ID %d", hdr.Id) } } if !gotData || !gotAltData { t.Fatal("missing stream") } } func TestBackupStreamWrite(t *testing.T) { f, err := os.Create(testFileName) if err != nil { t.Fatal(err) } defer f.Close() w := NewBackupFileWriter(f, false) defer w.Close() data := "testing 1 2 3\n" altData := "alternate stream\n" br := NewBackupStreamWriter(w) err = br.WriteHeader(&BackupHeader{Id: BackupData, Size: int64(len(data))}) if err != nil { t.Fatal(err) } n, err := br.Write([]byte(data)) if err != nil { t.Fatal(err) } if n != len(data) { t.Fatal("short write") } err = br.WriteHeader(&BackupHeader{Id: BackupAlternateData, Size: int64(len(altData)), Name: ":ads.txt:$DATA"}) if err != nil { t.Fatal(err) } n, err = br.Write([]byte(altData)) if err != nil { t.Fatal(err) } if n != len(altData) { t.Fatal("short write") } f.Close() b, err := os.ReadFile(testFileName) if err != nil { t.Fatal(err) } if string(b) != data { t.Fatalf("wrong data %v", b) } b, err = os.ReadFile(testFileName + ":ads.txt") if err != nil { t.Fatal(err) } if string(b) != altData { t.Fatalf("wrong data %v", b) } } func makeSparseFile() error { os.Remove(testFileName) f, err := os.Create(testFileName) if err != nil { return err } defer f.Close() err = windows.DeviceIoControl(windows.Handle(f.Fd()), windows.FSCTL_SET_SPARSE, nil, 0, nil, 0, nil, nil) if err != nil { return err } _, err = f.Write([]byte("testing 1 2 3\n")) if err != nil { return err } _, err = f.Seek(1000000, 0) if err != nil { return err } _, err = f.Write([]byte("more data later\n")) if err != nil { return err } return nil } func TestBackupSparseFile(t *testing.T) { err := makeSparseFile() if err != nil { t.Fatal(err) } f, err := os.Open(testFileName) if err != nil { t.Fatal(err) } defer f.Close() r := NewBackupFileReader(f, false) defer r.Close() br := NewBackupStreamReader(r) for { hdr, err := br.Next() if err == io.EOF { //nolint:errorlint break } if err != nil { t.Fatal(err) } t.Log(hdr) } } go-winio-0.6.2/backuptar/000077500000000000000000000000001460531775000152335ustar00rootroot00000000000000go-winio-0.6.2/backuptar/doc.go000066400000000000000000000001261460531775000163260ustar00rootroot00000000000000// This file only exists to allow go get on non-Windows platforms. package backuptar go-winio-0.6.2/backuptar/strconv.go000066400000000000000000000040321460531775000172570ustar00rootroot00000000000000//go:build windows package backuptar import ( "archive/tar" "fmt" "strconv" "strings" "time" ) // Functions copied from https://github.com/golang/go/blob/master/src/archive/tar/strconv.go // as we need to manage the LIBARCHIVE.creationtime PAXRecord manually. // Idea taken from containerd which did the same thing. // parsePAXTime takes a string of the form %d.%d as described in the PAX // specification. Note that this implementation allows for negative timestamps, // which is allowed for by the PAX specification, but not always portable. func parsePAXTime(s string) (time.Time, error) { const maxNanoSecondDigits = 9 // Split string into seconds and sub-seconds parts. ss, sn := s, "" if pos := strings.IndexByte(s, '.'); pos >= 0 { ss, sn = s[:pos], s[pos+1:] } // Parse the seconds. secs, err := strconv.ParseInt(ss, 10, 64) if err != nil { return time.Time{}, tar.ErrHeader } if len(sn) == 0 { return time.Unix(secs, 0), nil // No sub-second values } // Parse the nanoseconds. if strings.Trim(sn, "0123456789") != "" { return time.Time{}, tar.ErrHeader } if len(sn) < maxNanoSecondDigits { sn += strings.Repeat("0", maxNanoSecondDigits-len(sn)) // Right pad } else { sn = sn[:maxNanoSecondDigits] // Right truncate } nsecs, _ := strconv.ParseInt(sn, 10, 64) // Must succeed if len(ss) > 0 && ss[0] == '-' { return time.Unix(secs, -1*nsecs), nil // Negative correction } return time.Unix(secs, nsecs), nil } // formatPAXTime converts ts into a time of the form %d.%d as described in the // PAX specification. This function is capable of negative timestamps. func formatPAXTime(ts time.Time) (s string) { secs, nsecs := ts.Unix(), ts.Nanosecond() if nsecs == 0 { return strconv.FormatInt(secs, 10) } // If seconds is negative, then perform correction. sign := "" if secs < 0 { sign = "-" // Remember sign secs = -(secs + 1) // Add a second to secs nsecs = -(nsecs - 1e9) // Take that second away from nsecs } return strings.TrimRight(fmt.Sprintf("%s%d.%09d", sign, secs, nsecs), "0") } go-winio-0.6.2/backuptar/tar.go000066400000000000000000000372051460531775000163570ustar00rootroot00000000000000//go:build windows // +build windows package backuptar import ( "archive/tar" "encoding/base64" "fmt" "io" "path/filepath" "strconv" "strings" "time" "github.com/Microsoft/go-winio" "golang.org/x/sys/windows" ) //nolint:deadcode,varcheck // keep unused constants for potential future use const ( 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 ( hdrFileAttributes = "MSWINDOWS.fileattr" hdrSecurityDescriptor = "MSWINDOWS.sd" hdrRawSecurityDescriptor = "MSWINDOWS.rawsd" hdrMountPoint = "MSWINDOWS.mountpoint" hdrEaPrefix = "MSWINDOWS.xattr." hdrCreationTime = "LIBARCHIVE.creationtime" ) // zeroReader is an io.Reader that always returns 0s. type zeroReader struct{} func (zeroReader) Read(b []byte) (int, error) { for i := range b { b[i] = 0 } return len(b), nil } func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error { curOffset := int64(0) for { bhdr, err := br.Next() if err == io.EOF { //nolint:errorlint err = io.ErrUnexpectedEOF } if err != nil { return err } if bhdr.Id != winio.BackupSparseBlock { return fmt.Errorf("unexpected stream %d", bhdr.Id) } // We can't seek backwards, since we have already written that data to the tar.Writer. if bhdr.Offset < curOffset { return fmt.Errorf("cannot seek back from %d to %d", curOffset, bhdr.Offset) } // 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: %w", bhdr.Offset, err) } if bhdr.Size == 0 { // A sparse block with size = 0 is used to mark the end of the sparse blocks. break } n, err := io.Copy(t, br) if err != nil { return err } if n != bhdr.Size { return fmt.Errorf("copied %d bytes instead of %d at offset %d", n, bhdr.Size, bhdr.Offset) } curOffset = bhdr.Offset + n } return nil } // BasicInfoHeader creates a tar header from basic file information. func BasicInfoHeader(name string, size int64, fileInfo *winio.FileBasicInfo) *tar.Header { hdr := &tar.Header{ Format: tar.FormatPAX, Name: filepath.ToSlash(name), Size: size, Typeflag: tar.TypeReg, ModTime: time.Unix(0, fileInfo.LastWriteTime.Nanoseconds()), ChangeTime: time.Unix(0, fileInfo.ChangeTime.Nanoseconds()), AccessTime: time.Unix(0, fileInfo.LastAccessTime.Nanoseconds()), PAXRecords: make(map[string]string), } hdr.PAXRecords[hdrFileAttributes] = fmt.Sprintf("%d", fileInfo.FileAttributes) hdr.PAXRecords[hdrCreationTime] = formatPAXTime(time.Unix(0, fileInfo.CreationTime.Nanoseconds())) if (fileInfo.FileAttributes & windows.FILE_ATTRIBUTE_DIRECTORY) != 0 { hdr.Mode |= cISDIR hdr.Size = 0 hdr.Typeflag = tar.TypeDir } return hdr } // SecurityDescriptorFromTarHeader reads the SDDL associated with the header of the current file // from the tar header and returns the security descriptor into a byte slice. func SecurityDescriptorFromTarHeader(hdr *tar.Header) ([]byte, error) { if sdraw, ok := hdr.PAXRecords[hdrRawSecurityDescriptor]; ok { sd, err := base64.StdEncoding.DecodeString(sdraw) if err != nil { // Not returning sd as-is in the error-case, as base64.DecodeString // may return partially decoded data (not nil or empty slice) in case // of a failure: https://github.com/golang/go/blob/go1.17.7/src/encoding/base64/base64.go#L382-L387 return nil, err } return sd, nil } // Maintaining old SDDL-based behavior for backward compatibility. All new // tar headers written by this library will have raw binary for the security // descriptor. if sddl, ok := hdr.PAXRecords[hdrSecurityDescriptor]; ok { return winio.SddlToSecurityDescriptor(sddl) } return nil, nil } // 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 //nolint:prealloc // len(eas) <= len(hdr.PAXRecords); prealloc is wasteful for k, v := range hdr.PAXRecords { if !strings.HasPrefix(k, hdrEaPrefix) { continue } data, err := base64.StdEncoding.DecodeString(v) if err != nil { return nil, err } eas = append(eas, winio.ExtendedAttribute{ Name: k[len(hdrEaPrefix):], Value: data, }) } var eaData []byte var err error if len(eas) != 0 { eaData, err = winio.EncodeExtendedAttributes(eas) if err != nil { return nil, err } } return eaData, nil } // EncodeReparsePointFromTarHeader reads the ReparsePoint structure from the tar header // and encodes it into a byte slice. The file for which this function is called must be a // symlink. func EncodeReparsePointFromTarHeader(hdr *tar.Header) []byte { _, isMountPoint := hdr.PAXRecords[hdrMountPoint] rp := winio.ReparsePoint{ Target: filepath.FromSlash(hdr.Linkname), IsMountPoint: isMountPoint, } return winio.EncodeReparsePoint(&rp) } // WriteTarFileFromBackupStream writes a file to a tar writer using data from a Win32 backup stream. // // This encodes Win32 metadata as tar pax vendor extensions starting with MSWINDOWS. // // 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) 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) // If r can be seeked, then this function is two-pass: pass 1 collects the // tar header data, and pass 2 copies the data stream. If r cannot be // seeked, then some header data (in particular EAs) will be silently lost. var ( restartPos int64 err error ) sr, readTwice := r.(io.Seeker) if readTwice { if restartPos, err = sr.Seek(0, io.SeekCurrent); err != nil { readTwice = false } } br := winio.NewBackupStreamReader(r) var dataHdr *winio.BackupHeader for dataHdr == nil { bhdr, err := br.Next() if err == io.EOF { //nolint:errorlint break } if err != nil { return err } switch bhdr.Id { case winio.BackupData: hdr.Mode |= cISREG if !readTwice { dataHdr = bhdr } case winio.BackupSecurity: sd, err := io.ReadAll(br) if err != nil { return err } hdr.PAXRecords[hdrRawSecurityDescriptor] = base64.StdEncoding.EncodeToString(sd) case winio.BackupReparseData: hdr.Mode |= cISLNK hdr.Typeflag = tar.TypeSymlink reparseBuffer, _ := io.ReadAll(br) rp, err := winio.DecodeReparsePoint(reparseBuffer) if err != nil { return err } if rp.IsMountPoint { hdr.PAXRecords[hdrMountPoint] = "1" } hdr.Linkname = rp.Target case winio.BackupEaData: eab, err := io.ReadAll(br) if err != nil { return err } eas, err := winio.DecodeExtendedAttributes(eab) if err != nil { return err } for _, ea := range eas { // Use base64 encoding for the binary value. Note that there // is no way to encode the EA's flags, since their use doesn't // make any sense for persisted EAs. hdr.PAXRecords[hdrEaPrefix+ea.Name] = base64.StdEncoding.EncodeToString(ea.Value) } case winio.BackupAlternateData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData: // ignore these streams default: return fmt.Errorf("%s: unknown stream ID %d", name, bhdr.Id) } } err = t.WriteHeader(hdr) if err != nil { return err } if readTwice { // Get back to the data stream. if _, err = sr.Seek(restartPos, io.SeekStart); err != nil { return err } for dataHdr == nil { bhdr, err := br.Next() if err == io.EOF { //nolint:errorlint break } if err != nil { return err } if bhdr.Id == winio.BackupData { dataHdr = bhdr } } } // The logic for copying file contents is fairly complicated due to the need for handling sparse files, // and the weird ways they are represented by BackupRead. A normal file will always either have a data stream // with size and content, or no data stream at all (if empty). However, for a sparse file, the content can also // be represented using a series of sparse block streams following the data stream. Additionally, the way sparse // files are handled by BackupRead has changed in the OS recently. The specifics of the representation are described // in the list at the bottom of this block comment. // // Sparse files can be represented in four different ways, based on the specifics of the file. // - Size = 0: // Previously: BackupRead yields no data stream and no sparse block streams. // Recently: BackupRead yields a data stream with size = 0. There are no following sparse block streams. // - Size > 0, no allocated ranges: // BackupRead yields a data stream with size = 0. Following is a single sparse block stream with // size = 0 and offset = . // - Size > 0, one allocated range: // BackupRead yields a data stream with size = containing the file contents. There are no // sparse block streams. This is the case if you take a normal file with contents and simply set the // sparse flag on it. // - Size > 0, multiple allocated ranges: // BackupRead yields a data stream with size = 0. Following are sparse block streams for each allocated // range of the file containing the range contents. Finally there is a sparse block stream with // size = 0 and offset = . 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 { if size != dataHdr.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: %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: %w", name, err) } } } // Look for streams after the data stream. The only ones we handle are alternate data streams. // Other streams may have metadata that could be serialized, but the tar header has already // been written. In practice, this means that we don't get EA or TXF metadata. for { bhdr, err := br.Next() if err == io.EOF { //nolint:errorlint break } if err != nil { return err } switch bhdr.Id { case winio.BackupAlternateData: 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: return fmt.Errorf("%s: unknown stream ID %d after data", name, bhdr.Id) } } return nil } // FileInfoFromHeader retrieves basic Win32 file information from a tar header, using the additional metadata written by // WriteTarFileFromBackupStream. func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *winio.FileBasicInfo, err error) { name = hdr.Name if hdr.Typeflag == tar.TypeReg { size = hdr.Size } fileInfo = &winio.FileBasicInfo{ LastAccessTime: windows.NsecToFiletime(hdr.AccessTime.UnixNano()), LastWriteTime: windows.NsecToFiletime(hdr.ModTime.UnixNano()), ChangeTime: windows.NsecToFiletime(hdr.ChangeTime.UnixNano()), // Default to ModTime, we'll pull hdrCreationTime below if present CreationTime: windows.NsecToFiletime(hdr.ModTime.UnixNano()), } if attrStr, ok := hdr.PAXRecords[hdrFileAttributes]; ok { attr, err := strconv.ParseUint(attrStr, 10, 32) if err != nil { return "", 0, nil, err } fileInfo.FileAttributes = uint32(attr) } else { if hdr.Typeflag == tar.TypeDir { fileInfo.FileAttributes |= windows.FILE_ATTRIBUTE_DIRECTORY } } if creationTimeStr, ok := hdr.PAXRecords[hdrCreationTime]; ok { creationTime, err := parsePAXTime(creationTimeStr) if err != nil { return "", 0, nil, err } fileInfo.CreationTime = windows.NsecToFiletime(creationTime.UnixNano()) } return name, size, fileInfo, err } // WriteBackupStreamFromTarFile writes a Win32 backup stream from the current tar file. Since this function may process multiple // tar file entries in order to collect all the alternate data streams for the file, it returns the next // tar file that was not processed, or io.EOF is there are no more. func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (*tar.Header, error) { bw := winio.NewBackupStreamWriter(w) sd, err := SecurityDescriptorFromTarHeader(hdr) if err != nil { return nil, err } if len(sd) != 0 { bhdr := winio.BackupHeader{ Id: winio.BackupSecurity, Size: int64(len(sd)), } err := bw.WriteHeader(&bhdr) if err != nil { return nil, err } _, err = bw.Write(sd) if err != nil { return nil, err } } eadata, err := ExtendedAttributesFromTarHeader(hdr) if err != nil { return nil, err } if len(eadata) != 0 { bhdr := winio.BackupHeader{ Id: winio.BackupEaData, Size: int64(len(eadata)), } err = bw.WriteHeader(&bhdr) if err != nil { return nil, err } _, err = bw.Write(eadata) if err != nil { return nil, err } } if hdr.Typeflag == tar.TypeSymlink { reparse := EncodeReparsePointFromTarHeader(hdr) bhdr := winio.BackupHeader{ Id: winio.BackupReparseData, Size: int64(len(reparse)), } err := bw.WriteHeader(&bhdr) if err != nil { return nil, err } _, err = bw.Write(reparse) if err != nil { return nil, err } } if hdr.Typeflag == tar.TypeReg { bhdr := winio.BackupHeader{ Id: winio.BackupData, Size: hdr.Size, } err := bw.WriteHeader(&bhdr) if err != nil { return nil, err } _, err = io.Copy(bw, t) if err != nil { return nil, err } } // Copy all the alternate data streams and return the next non-ADS header. for { ahdr, err := t.Next() if err != nil { return nil, err } if ahdr.Typeflag != tar.TypeReg || !strings.HasPrefix(ahdr.Name, hdr.Name+":") { return ahdr, nil } bhdr := winio.BackupHeader{ Id: winio.BackupAlternateData, Size: ahdr.Size, Name: ahdr.Name[len(hdr.Name):] + ":$DATA", } err = bw.WriteHeader(&bhdr) if err != nil { return nil, err } _, err = io.Copy(bw, t) if err != nil { return nil, err } } } go-winio-0.6.2/backuptar/tar_test.go000066400000000000000000000145021460531775000174110ustar00rootroot00000000000000//go:build windows // +build windows package backuptar import ( "archive/tar" "bytes" "io" "os" "path/filepath" "reflect" "testing" "github.com/Microsoft/go-winio" "golang.org/x/sys/windows" ) func ensurePresent(t *testing.T, m map[string]string, keys ...string) { t.Helper() for _, k := range keys { if _, ok := m[k]; !ok { t.Error(k, "not present in tar header") } } } func setSparse(t *testing.T, f *os.File) { t.Helper() if err := windows.DeviceIoControl(windows.Handle(f.Fd()), windows.FSCTL_SET_SPARSE, nil, 0, nil, 0, nil, nil); err != nil { t.Fatal(err) } } // compareReaders validates that two readers contain the exact same data. func compareReaders(t *testing.T, rActual io.Reader, rExpected io.Reader) { t.Helper() const size = 8 * 1024 var bufExpected, bufActual [size]byte var readCount int64 // Loop, first reading from rExpected, then reading the same amount from rActual. // For each set of reads, compare the bytes to make sure they are identical. // When we run out of data in rExpected, exit the loop. for { // Do a read from rExpected and see how many bytes we get. nExpected, err := rExpected.Read(bufExpected[:]) if err == io.EOF && nExpected == 0 { break } else if err != nil && err != io.EOF { t.Fatalf("Failed reading from rExpected at %d: %s", readCount, err) } // Do a ReadFull from rActual for the same number of bytes we got from rExpected. if nActual, err := io.ReadFull(rActual, bufActual[:nExpected]); err != nil { t.Fatalf("Only read %d bytes out of %d from rActual at %d: %s", nActual, nExpected, readCount, err) } readCount += int64(nExpected) for i, bExpected := range bufExpected[:nExpected] { if bExpected != bufActual[i] { t.Fatalf("Mismatched bytes at %d. got 0x%x, expected 0x%x", i, bufActual[i], bExpected) } } } // Now we just need to make sure there isn't any further data in rActual. var b [1]byte if n, err := rActual.Read(b[:]); n != 0 || err != io.EOF { t.Fatalf("rActual didn't return EOF at expected end. Read %d bytes with error %s", n, err) } } 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 { t.Helper() path := filepath.Join(t.TempDir(), "foo.txt") if err := os.WriteFile(path, []byte("testing 1 2 3\n"), 0644); err != nil { t.Fatal(err) } return path }, "normalFileEmpty": func(t *testing.T) string { t.Helper() path := filepath.Join(t.TempDir(), "foo.txt") f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) if err != nil { t.Fatal(err) } defer f.Close() return path }, "sparseFileEmpty": func(t *testing.T) string { t.Helper() path := filepath.Join(t.TempDir(), "foo.txt") f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) if err != nil { t.Fatal(err) } defer f.Close() setSparse(t, f) return path }, "sparseFileWithNoAllocatedRanges": func(t *testing.T) string { t.Helper() path := filepath.Join(t.TempDir(), "foo.txt") f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) if err != nil { t.Fatal(err) } defer f.Close() setSparse(t, f) // Set file size without writing data to produce a file with size > 0 // but no allocated ranges. if err := f.Truncate(1000000); err != nil { t.Fatal(err) } return path }, "sparseFileWithOneAllocatedRange": func(t *testing.T) string { t.Helper() path := filepath.Join(t.TempDir(), "foo.txt") f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) if err != nil { t.Fatal(err) } defer f.Close() setSparse(t, f) if _, err := f.WriteString("test sparse data"); err != nil { t.Fatal(err) } return path }, "sparseFileWithMultipleAllocatedRanges": func(t *testing.T) string { t.Helper() path := filepath.Join(t.TempDir(), "foo.txt") f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) if err != nil { t.Fatal(err) } defer f.Close() setSparse(t, f) if _, err = f.Write([]byte("testing 1 2 3\n")); err != nil { t.Fatal(err) } // The documentation talks about FSCTL_SET_ZERO_DATA, but seeking also // seems to create a hole. if _, err = f.Seek(1000000, 0); err != nil { t.Fatal(err) } if _, err = f.Write([]byte("more data later\n")); err != nil { t.Fatal(err) } return path }, } { t.Run(name, func(t *testing.T) { path := setup(t) f, err := os.Open(path) if err != nil { t.Fatal(err) } defer f.Close() fi, err := f.Stat() if err != nil { t.Fatal(err) } bi, err := winio.GetFileBasicInfo(f) if err != nil { t.Fatal(err) } br := winio.NewBackupFileReader(f, true) defer br.Close() var buf bytes.Buffer tw := tar.NewWriter(&buf) err = WriteTarFileFromBackupStream(tw, br, f.Name(), fi.Size(), bi) if err != nil { t.Fatal(err) } tr := tar.NewReader(&buf) hdr, err := tr.Next() if err != nil { t.Fatal(err) } name, size, bi2, err := FileInfoFromHeader(hdr) if err != nil { t.Fatal(err) } if name != filepath.ToSlash(f.Name()) { t.Errorf("got name %s, expected %s", name, filepath.ToSlash(f.Name())) } if size != fi.Size() { t.Errorf("got size %d, expected %d", size, fi.Size()) } if !reflect.DeepEqual(*bi2, *bi) { t.Errorf("got %#v, expected %#v", *bi2, *bi) } ensurePresent(t, hdr.PAXRecords, "MSWINDOWS.fileattr", "MSWINDOWS.rawsd") // Reset file position so we can compare file contents. // The file contents of the actual file should match what we get from the tar. if _, err := f.Seek(0, 0); err != nil { t.Fatal(err) } compareReaders(t, tr, f) }) } } func TestZeroReader(t *testing.T) { const size = 512 var b [size]byte var bExpected [size]byte var r zeroReader n, err := r.Read(b[:]) if err != nil { t.Fatalf("Unexpected read error: %s", err) } if n != size { t.Errorf("Wrong read size. got %d, expected %d", n, size) } for i := range b { if b[i] != bExpected[i] { t.Errorf("Wrong content at index %d. got %d, expected %d", i, b[i], bExpected[i]) } } } go-winio-0.6.2/doc.go000066400000000000000000000020641460531775000143550ustar00rootroot00000000000000// 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 go-winio-0.6.2/ea.go000066400000000000000000000061521460531775000141770ustar00rootroot00000000000000package winio import ( "bytes" "encoding/binary" "errors" ) type fileFullEaInformation struct { NextEntryOffset uint32 Flags uint8 NameLength uint8 ValueLength uint16 } var ( fileFullEaInformationSize = binary.Size(&fileFullEaInformation{}) errInvalidEaBuffer = errors.New("invalid extended attribute buffer") errEaNameTooLarge = errors.New("extended attribute name too large") errEaValueTooLarge = errors.New("extended attribute value too large") ) // ExtendedAttribute represents a single Windows EA. type ExtendedAttribute struct { Name string Value []byte Flags uint8 } func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) { var info fileFullEaInformation err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info) if err != nil { err = errInvalidEaBuffer return ea, nb, err } nameOffset := fileFullEaInformationSize nameLen := int(info.NameLength) valueOffset := nameOffset + int(info.NameLength) + 1 valueLen := int(info.ValueLength) nextOffset := int(info.NextEntryOffset) if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) { err = errInvalidEaBuffer return ea, nb, err } ea.Name = string(b[nameOffset : nameOffset+nameLen]) ea.Value = b[valueOffset : valueOffset+valueLen] ea.Flags = info.Flags if info.NextEntryOffset != 0 { nb = b[info.NextEntryOffset:] } return ea, nb, err } // DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION // buffer retrieved from BackupRead, ZwQueryEaFile, etc. func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) { for len(b) != 0 { ea, nb, err := parseEa(b) if err != nil { return nil, err } eas = append(eas, ea) b = nb } return eas, err } func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error { if int(uint8(len(ea.Name))) != len(ea.Name) { return errEaNameTooLarge } if int(uint16(len(ea.Value))) != len(ea.Value) { return errEaValueTooLarge } entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value)) withPadding := (entrySize + 3) &^ 3 nextOffset := uint32(0) if !last { nextOffset = withPadding } info := fileFullEaInformation{ NextEntryOffset: nextOffset, Flags: ea.Flags, NameLength: uint8(len(ea.Name)), ValueLength: uint16(len(ea.Value)), } err := binary.Write(buf, binary.LittleEndian, &info) if err != nil { return err } _, err = buf.Write([]byte(ea.Name)) if err != nil { return err } err = buf.WriteByte(0) if err != nil { return err } _, err = buf.Write(ea.Value) if err != nil { return err } _, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize]) if err != nil { return err } return nil } // EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION // buffer for use with BackupWrite, ZwSetEaFile, etc. func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) { var buf bytes.Buffer for i := range eas { last := false if i == len(eas)-1 { last = true } err := writeEa(&buf, &eas[i], last) if err != nil { return nil, err } } return buf.Bytes(), nil } go-winio-0.6.2/ea_test.go000066400000000000000000000042021460531775000152300ustar00rootroot00000000000000//go:build windows // +build windows package winio import ( "os" "reflect" "testing" "unsafe" "golang.org/x/sys/windows" ) var ( testEas = []ExtendedAttribute{ {Name: "foo", Value: []byte("bar")}, {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} testEasNotPadded = testEasEncoded[0 : len(testEasEncoded)-3] testEasTruncated = testEasEncoded[0:20] ) func Test_RoundTripEas(t *testing.T) { b, err := EncodeExtendedAttributes(testEas) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(testEasEncoded, b) { t.Fatalf("encoded mismatch %v %v", testEasEncoded, b) } eas, err := DecodeExtendedAttributes(b) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(testEas, eas) { t.Fatalf("mismatch %+v %+v", testEas, eas) } } func Test_EasDontNeedPaddingAtEnd(t *testing.T) { eas, err := DecodeExtendedAttributes(testEasNotPadded) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(testEas, eas) { t.Fatalf("mismatch %+v %+v", testEas, eas) } } func Test_TruncatedEasFailCorrectly(t *testing.T) { _, err := DecodeExtendedAttributes(testEasTruncated) if err == nil { t.Fatal("expected error") } } func Test_NilEasEncodeAndDecodeAsNil(t *testing.T) { b, err := EncodeExtendedAttributes(nil) if err != nil { t.Fatal(err) } if len(b) != 0 { t.Fatal("expected empty") } eas, err := DecodeExtendedAttributes(nil) if err != nil { t.Fatal(err) } if len(eas) != 0 { t.Fatal("expected empty") } } // Test_SetFileEa makes sure that the test buffer is actually parsable by NtSetEaFile. func Test_SetFileEa(t *testing.T) { f, err := os.CreateTemp("", "winio") if err != nil { t.Fatal(err) } defer os.Remove(f.Name()) defer f.Close() ntdll := windows.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))) if r != 0 { t.Fatalf("NtSetEaFile failed with %08x", r) } } go-winio-0.6.2/file.go000066400000000000000000000177041460531775000145360ustar00rootroot00000000000000//go:build windows // +build windows package winio import ( "errors" "io" "runtime" "sync" "sync/atomic" "syscall" "time" "golang.org/x/sys/windows" ) //sys cancelIoEx(file windows.Handle, o *windows.Overlapped) (err error) = CancelIoEx //sys createIoCompletionPort(file windows.Handle, port windows.Handle, key uintptr, threadCount uint32) (newport windows.Handle, err error) = CreateIoCompletionPort //sys getQueuedCompletionStatus(port windows.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus //sys setFileCompletionNotificationModes(h windows.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes //sys wsaGetOverlappedResult(h windows.Handle, o *windows.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult var ( ErrFileClosed = errors.New("file has already been closed") ErrTimeout = &timeoutError{} ) type timeoutError struct{} func (*timeoutError) Error() string { return "i/o timeout" } func (*timeoutError) Timeout() bool { return true } func (*timeoutError) Temporary() bool { return true } type timeoutChan chan struct{} var ioInitOnce sync.Once var ioCompletionPort windows.Handle // ioResult contains the result of an asynchronous IO operation. type ioResult struct { bytes uint32 err error } // ioOperation represents an outstanding asynchronous Win32 IO. type ioOperation struct { o windows.Overlapped ch chan ioResult } func initIO() { h, err := createIoCompletionPort(windows.InvalidHandle, 0, 0, 0xffffffff) if err != nil { panic(err) } ioCompletionPort = h go ioCompletionProcessor(h) } // win32File implements Reader, Writer, and Closer on a Win32 handle without blocking in a syscall. // It takes ownership of this handle and will close it if it is garbage collected. type win32File struct { handle windows.Handle wg sync.WaitGroup wgLock sync.RWMutex closing atomic.Bool socket bool readDeadline deadlineHandler writeDeadline deadlineHandler } type deadlineHandler struct { setLock sync.Mutex channel timeoutChan channelLock sync.RWMutex timer *time.Timer timedout atomic.Bool } // makeWin32File makes a new win32File from an existing file handle. func makeWin32File(h windows.Handle) (*win32File, error) { f := &win32File{handle: h} ioInitOnce.Do(initIO) _, err := createIoCompletionPort(h, ioCompletionPort, 0, 0xffffffff) if err != nil { return nil, err } err = setFileCompletionNotificationModes(h, windows.FILE_SKIP_COMPLETION_PORT_ON_SUCCESS|windows.FILE_SKIP_SET_EVENT_ON_HANDLE) if err != nil { return nil, err } f.readDeadline.channel = make(timeoutChan) f.writeDeadline.channel = make(timeoutChan) return f, nil } // Deprecated: use NewOpenFile instead. func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) { return NewOpenFile(windows.Handle(h)) } func NewOpenFile(h windows.Handle) (io.ReadWriteCloser, error) { // If we return the result of makeWin32File directly, it can result in an // interface-wrapped nil, rather than a nil interface value. f, err := makeWin32File(h) if err != nil { return nil, err } return f, nil } // 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) f.wg.Wait() // at this point, no new IO can start windows.Close(f.handle) f.handle = 0 } else { f.wgLock.Unlock() } } // Close closes a win32File. func (f *win32File) Close() error { f.closeHandle() return nil } // IsClosed checks if the file has been closed. func (f *win32File) IsClosed() bool { return f.closing.Load() } // 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) { f.wgLock.RLock() if f.closing.Load() { f.wgLock.RUnlock() return nil, ErrFileClosed } f.wg.Add(1) f.wgLock.RUnlock() c := &ioOperation{} c.ch = make(chan ioResult) return c, nil } // ioCompletionProcessor processes completed async IOs forever. func ioCompletionProcessor(h windows.Handle) { for { var bytes uint32 var key uintptr var op *ioOperation err := getQueuedCompletionStatus(h, &bytes, &key, &op, windows.INFINITE) if op == nil { panic(err) } op.ch <- ioResult{bytes, err} } } // todo: helsaawy - create an asyncIO version that takes a context // 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 != windows.ERROR_IO_PENDING { //nolint:errorlint // err is Errno return int(bytes), err } if f.closing.Load() { _ = cancelIoEx(f.handle, &c.o) } var timeout timeoutChan if d != nil { d.channelLock.Lock() timeout = d.channel d.channelLock.Unlock() } var r ioResult select { case r = <-c.ch: err = r.err if err == windows.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno if f.closing.Load() { err = ErrFileClosed } } else if err != nil && f.socket { // err is from Win32. Query the overlapped structure to get the winsock error. var bytes, flags uint32 err = wsaGetOverlappedResult(f.handle, &c.o, &bytes, false, &flags) } case <-timeout: _ = cancelIoEx(f.handle, &c.o) r = <-c.ch err = r.err if err == windows.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno err = ErrTimeout } } // runtime.KeepAlive is needed, as c is passed via native // code to ioCompletionProcessor, c must remain alive // until the channel read is complete. // todo: (de)allocate *ioOperation via win32 heap functions, instead of needing to KeepAlive? runtime.KeepAlive(c) return int(r.bytes), err } // Read reads from a file handle. func (f *win32File) Read(b []byte) (int, error) { c, err := f.prepareIO() if err != nil { return 0, err } defer f.wg.Done() if f.readDeadline.timedout.Load() { return 0, ErrTimeout } var bytes uint32 err = windows.ReadFile(f.handle, b, &bytes, &c.o) 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 == windows.ERROR_BROKEN_PIPE { //nolint:errorlint // err is Errno return 0, io.EOF } return n, err } // Write writes to a file handle. func (f *win32File) Write(b []byte) (int, error) { c, err := f.prepareIO() if err != nil { return 0, err } defer f.wg.Done() if f.writeDeadline.timedout.Load() { return 0, ErrTimeout } var bytes uint32 err = windows.WriteFile(f.handle, b, &bytes, &c.o) n, err := f.asyncIO(c, &f.writeDeadline, bytes, err) runtime.KeepAlive(b) return n, err } func (f *win32File) SetReadDeadline(deadline time.Time) error { return f.readDeadline.set(deadline) } func (f *win32File) SetWriteDeadline(deadline time.Time) error { return f.writeDeadline.set(deadline) } func (f *win32File) Flush() error { return windows.FlushFileBuffers(f.handle) } func (f *win32File) Fd() uintptr { return uintptr(f.handle) } func (d *deadlineHandler) set(deadline time.Time) error { d.setLock.Lock() defer d.setLock.Unlock() if d.timer != nil { if !d.timer.Stop() { <-d.channel } d.timer = nil } d.timedout.Store(false) select { case <-d.channel: d.channelLock.Lock() d.channel = make(chan struct{}) d.channelLock.Unlock() default: } if deadline.IsZero() { return nil } timeoutIO := func() { d.timedout.Store(true) close(d.channel) } now := time.Now() duration := deadline.Sub(now) if deadline.After(now) { // Deadline is in the future, set a timer to wait d.timer = time.AfterFunc(duration, timeoutIO) } else { // Deadline is in the past. Cancel all pending IO now. timeoutIO() } return nil } go-winio-0.6.2/fileinfo.go000066400000000000000000000070431460531775000154050ustar00rootroot00000000000000//go:build windows // +build windows package winio import ( "os" "runtime" "unsafe" "golang.org/x/sys/windows" ) // FileBasicInfo contains file access time and file attributes information. type FileBasicInfo struct { CreationTime, LastAccessTime, LastWriteTime, ChangeTime windows.Filetime FileAttributes uint32 _ uint32 // padding } // alignedFileBasicInfo is a FileBasicInfo, but aligned to uint64 by containing // uint64 rather than windows.Filetime. Filetime contains two uint32s. uint64 // alignment is necessary to pass this as FILE_BASIC_INFO. type alignedFileBasicInfo struct { CreationTime, LastAccessTime, LastWriteTime, ChangeTime uint64 FileAttributes uint32 _ uint32 // padding } // GetFileBasicInfo retrieves times and attributes for a file. func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) { bi := &alignedFileBasicInfo{} 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) // Reinterpret the alignedFileBasicInfo as a FileBasicInfo so it matches the // public API of this module. The data may be unnecessarily aligned. return (*FileBasicInfo)(unsafe.Pointer(bi)), nil } // SetFileBasicInfo sets times and attributes for a file. func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error { // Create an alignedFileBasicInfo based on a FileBasicInfo. The copy is // suitable to pass to GetFileInformationByHandleEx. biAligned := *(*alignedFileBasicInfo)(unsafe.Pointer(bi)) if err := windows.SetFileInformationByHandle( windows.Handle(f.Fd()), windows.FileBasicInfo, (*byte)(unsafe.Pointer(&biAligned)), uint32(unsafe.Sizeof(biAligned)), ); err != nil { return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err} } runtime.KeepAlive(f) return nil } // FileStandardInfo contains extended information for the file. // FILE_STANDARD_INFO in WinBase.h // https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_standard_info type FileStandardInfo struct { AllocationSize, EndOfFile int64 NumberOfLinks uint32 DeletePending, Directory bool } // 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 { return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err} } runtime.KeepAlive(f) return si, nil } // FileIDInfo contains the volume serial number and file ID for a file. This pair should be // unique on a system. type FileIDInfo struct { VolumeSerialNumber uint64 FileID [16]byte } // 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 { return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err} } runtime.KeepAlive(f) return fileID, nil } go-winio-0.6.2/fileinfo_test.go000066400000000000000000000124251460531775000164440ustar00rootroot00000000000000//go:build windows // +build windows package winio import ( "os" "testing" "unsafe" "golang.org/x/sys/windows" ) // Checks if current matches expected. Note that AllocationSize is filesystem-specific, // so we check that the current.AllocationSize is >= expected.AllocationSize. // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/5afa7f66-619c-48f3-955f-68c4ece704ae func checkFileStandardInfo(t *testing.T, current, expected *FileStandardInfo) { t.Helper() if current.AllocationSize < expected.AllocationSize { t.Fatalf("FileStandardInfo unexpectedly had AllocationSize %d, expecting >=%d", current.AllocationSize, expected.AllocationSize) } if current.EndOfFile != expected.EndOfFile { t.Fatalf("FileStandardInfo unexpectedly had EndOfFile %d, expecting %d", current.EndOfFile, expected.EndOfFile) } if current.NumberOfLinks != expected.NumberOfLinks { t.Fatalf("FileStandardInfo unexpectedly had NumberOfLinks %d, expecting %d", current.NumberOfLinks, expected.NumberOfLinks) } if current.DeletePending != expected.DeletePending { if current.DeletePending { t.Fatalf("FileStandardInfo unexpectedly DeletePending") } else { t.Fatalf("FileStandardInfo unexpectedly not DeletePending") } } if current.Directory != expected.Directory { if current.Directory { t.Fatalf("FileStandardInfo unexpectedly Directory") } else { t.Fatalf("FileStandardInfo unexpectedly not Directory") } } } func TestGetFileStandardInfo_File(t *testing.T) { f, err := os.CreateTemp("", "tst") if err != nil { t.Fatal(err) } defer f.Close() defer os.Remove(f.Name()) expectedFileInfo := &FileStandardInfo{ AllocationSize: 0, EndOfFile: 0, NumberOfLinks: 1, DeletePending: false, Directory: false, } info, err := GetFileStandardInfo(f) if err != nil { t.Fatal(err) } checkFileStandardInfo(t, info, expectedFileInfo) bytesWritten, err := f.Write([]byte("0123456789")) if err != nil { t.Fatal(err) } expectedFileInfo.EndOfFile = int64(bytesWritten) expectedFileInfo.AllocationSize = int64(bytesWritten) info, err = GetFileStandardInfo(f) if err != nil { t.Fatal(err) } checkFileStandardInfo(t, info, expectedFileInfo) linkName := f.Name() + ".link" if err = os.Link(f.Name(), linkName); err != nil { t.Fatal(err) } defer os.Remove(linkName) expectedFileInfo.NumberOfLinks = 2 info, err = GetFileStandardInfo(f) if err != nil { t.Fatal(err) } checkFileStandardInfo(t, info, expectedFileInfo) os.Remove(linkName) expectedFileInfo.NumberOfLinks = 1 info, err = GetFileStandardInfo(f) if err != nil { t.Fatal(err) } checkFileStandardInfo(t, info, expectedFileInfo) } func TestGetFileStandardInfo_Directory(t *testing.T) { 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) if err != nil { t.Fatal(err) } defer f.Close() expectedFileInfo := &FileStandardInfo{ AllocationSize: 0, EndOfFile: 0, NumberOfLinks: 1, DeletePending: false, Directory: true, } info, err := GetFileStandardInfo(f) if err != nil { t.Fatal(err) } checkFileStandardInfo(t, info, expectedFileInfo) } // TestFileInfoStructAlignment checks that the alignment of Go fileinfo structs // match what is expected by the Windows API. func TestFileInfoStructAlignment(t *testing.T) { //nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. const ( // The alignment of various types, as named in the Windows APIs. When // deciding on an expectedAlignment for a struct's test case, use the // type of the largest field in the struct as written in the Windows // docs. This is intended to help reviewers by allowing them to first // check that a new align* const is correct, then independently check // that the test case is correct, rather than all at once. alignLARGE_INTEGER = unsafe.Alignof(uint64(0)) alignULONGLONG = unsafe.Alignof(uint64(0)) ) tests := []struct { name string actualAlign uintptr actualSize uintptr expectedAlignment uintptr }{ { // alignedFileBasicInfo is passed to the Windows API rather than FileBasicInfo. "alignedFileBasicInfo", unsafe.Alignof(alignedFileBasicInfo{}), unsafe.Sizeof(alignedFileBasicInfo{}), // https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_basic_info alignLARGE_INTEGER, }, { "FileStandardInfo", unsafe.Alignof(FileStandardInfo{}), unsafe.Sizeof(FileStandardInfo{}), // https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_standard_info alignLARGE_INTEGER, }, { "FileIDInfo", unsafe.Alignof(FileIDInfo{}), unsafe.Sizeof(FileIDInfo{}), // https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_id_info alignULONGLONG, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.actualAlign != tt.expectedAlignment { t.Errorf("alignment mismatch: actual %d, expected %d", tt.actualAlign, tt.expectedAlignment) } if r := tt.actualSize % tt.expectedAlignment; r != 0 { t.Errorf( "size is not a multiple of alignment: size %% alignment (%d %% %d) is %d, expected 0", tt.actualSize, tt.expectedAlignment, r) } }) } } go-winio-0.6.2/go.mod000066400000000000000000000003021460531775000143600ustar00rootroot00000000000000module github.com/Microsoft/go-winio go 1.21 require ( github.com/sirupsen/logrus v1.9.3 golang.org/x/sys v0.10.0 golang.org/x/tools v0.11.0 ) require golang.org/x/mod v0.12.0 // indirect go-winio-0.6.2/go.sum000066400000000000000000000035621460531775000144200ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= go-winio-0.6.2/hvsock.go000066400000000000000000000371141460531775000151110ustar00rootroot00000000000000//go:build windows // +build windows package winio import ( "context" "errors" "fmt" "io" "net" "os" "time" "unsafe" "golang.org/x/sys/windows" "github.com/Microsoft/go-winio/internal/socket" "github.com/Microsoft/go-winio/pkg/guid" ) 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 // HvsockGUIDWildcard is the wildcard VmId for accepting connections from all partitions. func HvsockGUIDWildcard() guid.GUID { // 00000000-0000-0000-0000-000000000000 return guid.GUID{} } // 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, Data2: 0xffff, Data3: 0xffff, Data4: [8]uint8{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, } } // HvsockGUIDLoopback is the Loopback VmId for accepting connections to the same partition as the connector. func HvsockGUIDLoopback() guid.GUID { // e0e16197-dd56-4a10-9195-5ee7a155a838 return guid.GUID{ Data1: 0xe0e16197, Data2: 0xdd56, Data3: 0x4a10, Data4: [8]uint8{0x91, 0x95, 0x5e, 0xe7, 0xa1, 0x55, 0xa8, 0x38}, } } // 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. func HvsockGUIDSiloHost() guid.GUID { // 36bd0c5c-7276-4223-88ba-7d03b654c568 return guid.GUID{ Data1: 0x36bd0c5c, Data2: 0x7276, Data3: 0x4223, Data4: [8]byte{0x88, 0xba, 0x7d, 0x03, 0xb6, 0x54, 0xc5, 0x68}, } } // HvsockGUIDChildren is the wildcard VmId for accepting connections from the connector's child partitions. func HvsockGUIDChildren() guid.GUID { // 90db8b89-0d35-4f79-8ce9-49ea0ac8b7cd return guid.GUID{ Data1: 0x90db8b89, Data2: 0xd35, Data3: 0x4f79, Data4: [8]uint8{0x8c, 0xe9, 0x49, 0xea, 0xa, 0xc8, 0xb7, 0xcd}, } } // 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. func HvsockGUIDParent() guid.GUID { // a42e7cda-d03f-480c-9cc2-a4de20abb878 return guid.GUID{ Data1: 0xa42e7cda, Data2: 0xd03f, Data3: 0x480c, Data4: [8]uint8{0x9c, 0xc2, 0xa4, 0xde, 0x20, 0xab, 0xb8, 0x78}, } } // hvsockVsockServiceTemplate is the Service GUID used for the VSOCK protocol. func hvsockVsockServiceTemplate() guid.GUID { // 00000000-facb-11e6-bd58-64006a7986d3 return guid.GUID{ Data2: 0xfacb, Data3: 0x11e6, Data4: [8]uint8{0xbd, 0x58, 0x64, 0x00, 0x6a, 0x79, 0x86, 0xd3}, } } // An HvsockAddr is an address for a AF_HYPERV socket. type HvsockAddr struct { VMID guid.GUID ServiceID guid.GUID } type rawHvsockAddr struct { Family uint16 _ uint16 VMID guid.GUID ServiceID guid.GUID } var _ socket.RawSockaddr = &rawHvsockAddr{} // Network returns the address's network name, "hvsock". func (*HvsockAddr) Network() string { return "hvsock" } func (addr *HvsockAddr) String() string { return fmt.Sprintf("%s:%s", &addr.VMID, &addr.ServiceID) } // VsockServiceID returns an hvsock service ID corresponding to the specified AF_VSOCK port. func VsockServiceID(port uint32) guid.GUID { g := hvsockVsockServiceTemplate() // make a copy g.Data1 = port return g } func (addr *HvsockAddr) raw() rawHvsockAddr { return rawHvsockAddr{ Family: afHVSock, VMID: addr.VMID, ServiceID: addr.ServiceID, } } func (addr *HvsockAddr) fromRaw(raw *rawHvsockAddr) { addr.VMID = raw.VMID addr.ServiceID = raw.ServiceID } // 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]. 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()`. func (r *rawHvsockAddr) FromBytes(b []byte) error { n := int(unsafe.Sizeof(rawHvsockAddr{})) if len(b) < n { return fmt.Errorf("got %d, want %d: %w", len(b), n, socket.ErrBufferSize) } 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) } return nil } // HvsockListener is a socket listener for the AF_HYPERV address family. type HvsockListener struct { sock *win32File addr HvsockAddr } var _ net.Listener = &HvsockListener{} // HvsockConn is a connected socket of the AF_HYPERV address family. type HvsockConn struct { sock *win32File local, remote HvsockAddr } var _ net.Conn = &HvsockConn{} func newHVSocket() (*win32File, error) { fd, err := windows.Socket(afHVSock, windows.SOCK_STREAM, 1) if err != nil { return nil, os.NewSyscallError("socket", err) } f, err := makeWin32File(fd) if err != nil { windows.Close(fd) return nil, err } f.socket = true return f, nil } // ListenHvsock listens for connections on the specified hvsock address. func ListenHvsock(addr *HvsockAddr) (_ *HvsockListener, err error) { l := &HvsockListener{addr: *addr} var sock *win32File sock, err = newHVSocket() if err != nil { return nil, l.opErr("listen", err) } defer func() { if err != nil { _ = sock.Close() } }() sa := addr.raw() err = socket.Bind(sock.handle, &sa) if err != nil { return nil, l.opErr("listen", os.NewSyscallError("socket", err)) } err = windows.Listen(sock.handle, 16) if err != nil { return nil, l.opErr("listen", os.NewSyscallError("listen", err)) } return &HvsockListener{sock: sock, addr: *addr}, nil } func (l *HvsockListener) opErr(op string, err error) error { return &net.OpError{Op: op, Net: "hvsock", Addr: &l.addr, Err: err} } // Addr returns the listener's network address. func (l *HvsockListener) Addr() net.Addr { return &l.addr } // Accept waits for the next connection and returns it. func (l *HvsockListener) Accept() (_ net.Conn, err error) { sock, err := newHVSocket() if err != nil { return nil, l.opErr("accept", err) } defer func() { if sock != nil { sock.Close() } }() 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. // // 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 = windows.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 { return nil, l.opErr("accept", os.NewSyscallError("acceptex", err)) } conn := &HvsockConn{ sock: sock, } // The local address returned in the AcceptEx buffer is the same as the Listener socket's // address. However, the service GUID reported by GetSockName is different from the Listeners // socket, and is sometimes the same as the local address of the socket that dialed the // address, with the service GUID.Data1 incremented, but othertimes is different. // todo: does the local address matter? is the listener's address or the actual address appropriate? conn.local.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[0]))) conn.remote.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[addrlen]))) // initialize the accepted socket and update its properties with those of the listening socket if err = windows.Setsockopt(sock.handle, windows.SOL_SOCKET, windows.SO_UPDATE_ACCEPT_CONTEXT, (*byte)(unsafe.Pointer(&l.sock.handle)), int32(unsafe.Sizeof(l.sock.handle))); err != nil { return nil, conn.opErr("accept", os.NewSyscallError("setsockopt", err)) } sock = nil return conn, nil } // Close closes the listener, causing any pending Accept calls to fail. func (l *HvsockListener) Close() error { return l.sock.Close() } // HvsockDialer configures and dials a Hyper-V Socket (ie, [HvsockConn]). type HvsockDialer struct { // Deadline is the time the Dial operation must connect before erroring. Deadline time.Time // Retries is the number of additional connects to try if the connection times out, is refused, // or the host is unreachable Retries uint // RetryWait is the time to wait after a connection error to retry RetryWait time.Duration rt *time.Timer // redial wait timer } // Dial the Hyper-V socket at addr. // // See [HvsockDialer.Dial] for more information. func Dial(ctx context.Context, addr *HvsockAddr) (conn *HvsockConn, err error) { return (&HvsockDialer{}).Dial(ctx, addr) } // 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" // create the conn early to use opErr() conn = &HvsockConn{ remote: *addr, } if !d.Deadline.IsZero() { var cancel context.CancelFunc ctx, cancel = context.WithDeadline(ctx, d.Deadline) defer cancel() } // preemptive timeout/cancellation check if err = ctx.Err(); err != nil { return nil, conn.opErr(op, err) } sock, err := newHVSocket() if err != nil { return nil, conn.opErr(op, err) } defer func() { if sock != nil { sock.Close() } }() sa := addr.raw() err = socket.Bind(sock.handle, &sa) if err != nil { return nil, conn.opErr(op, os.NewSyscallError("bind", err)) } c, err := sock.prepareIO() if err != nil { return nil, conn.opErr(op, err) } defer sock.wg.Done() var bytes uint32 for i := uint(0); i <= d.Retries; i++ { err = socket.ConnectEx( sock.handle, &sa, nil, // sendBuf 0, // sendDataLen &bytes, (*windows.Overlapped)(unsafe.Pointer(&c.o))) _, err = sock.asyncIO(c, nil, bytes, err) if i < d.Retries && canRedial(err) { if err = d.redialWait(ctx); err == nil { continue } } break } if err != nil { return nil, conn.opErr(op, os.NewSyscallError("connectex", err)) } // update the connection properties, so shutdown can be used if err = windows.Setsockopt( sock.handle, windows.SOL_SOCKET, windows.SO_UPDATE_CONNECT_CONTEXT, nil, // optvalue 0, // optlen ); err != nil { return nil, conn.opErr(op, os.NewSyscallError("setsockopt", err)) } // get the local name var sal rawHvsockAddr err = socket.GetSockName(sock.handle, &sal) if err != nil { return nil, conn.opErr(op, os.NewSyscallError("getsockname", err)) } conn.local.fromRaw(&sal) // one last check for timeout, since asyncIO doesn't check the context if err = ctx.Err(); err != nil { return nil, conn.opErr(op, err) } conn.sock = sock sock = nil return conn, nil } // 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 } if d.rt == nil { d.rt = time.NewTimer(d.RetryWait) } else { // should already be stopped and drained d.rt.Reset(d.RetryWait) } select { case <-ctx.Done(): case <-d.rt.C: return nil } // stop and drain the timer if !d.rt.Stop() { <-d.rt.C } return ctx.Err() } // assumes error is a plain, unwrapped windows.Errno provided by direct syscall. func canRedial(err error) bool { //nolint:errorlint // guaranteed to be an Errno switch err { case windows.WSAECONNREFUSED, windows.WSAENETUNREACH, windows.WSAETIMEDOUT, windows.ERROR_CONNECTION_REFUSED, windows.ERROR_CONNECTION_UNAVAIL: return true default: return false } } func (conn *HvsockConn) opErr(op string, err error) error { // translate from "file closed" to "socket closed" if errors.Is(err, ErrFileClosed) { err = socket.ErrSocketClosed } return &net.OpError{Op: op, Net: "hvsock", Source: &conn.local, Addr: &conn.remote, Err: err} } func (conn *HvsockConn) Read(b []byte) (int, error) { c, err := conn.sock.prepareIO() if err != nil { return 0, conn.opErr("read", err) } defer conn.sock.wg.Done() buf := windows.WSABuf{Buf: &b[0], Len: uint32(len(b))} var flags, bytes uint32 err = windows.WSARecv(conn.sock.handle, &buf, 1, &bytes, &flags, &c.o, nil) n, err := conn.sock.asyncIO(c, &conn.sock.readDeadline, bytes, err) if err != nil { var eno windows.Errno if errors.As(err, &eno) { err = os.NewSyscallError("wsarecv", eno) } return 0, conn.opErr("read", err) } else if n == 0 { err = io.EOF } return n, err } func (conn *HvsockConn) Write(b []byte) (int, error) { t := 0 for len(b) != 0 { n, err := conn.write(b) if err != nil { return t + n, err } t += n b = b[n:] } return t, nil } func (conn *HvsockConn) write(b []byte) (int, error) { c, err := conn.sock.prepareIO() if err != nil { return 0, conn.opErr("write", err) } defer conn.sock.wg.Done() buf := windows.WSABuf{Buf: &b[0], Len: uint32(len(b))} var bytes uint32 err = windows.WSASend(conn.sock.handle, &buf, 1, &bytes, 0, &c.o, nil) n, err := conn.sock.asyncIO(c, &conn.sock.writeDeadline, bytes, err) if err != nil { var eno windows.Errno if errors.As(err, &eno) { err = os.NewSyscallError("wsasend", eno) } return 0, conn.opErr("write", err) } return n, err } // Close closes the socket connection, failing any pending read or write calls. func (conn *HvsockConn) Close() error { return conn.sock.Close() } func (conn *HvsockConn) IsClosed() bool { return conn.sock.IsClosed() } // shutdown disables sending or receiving on a socket. func (conn *HvsockConn) shutdown(how int) error { if conn.IsClosed() { return socket.ErrSocketClosed } err := windows.Shutdown(conn.sock.handle, how) if err != nil { // If the connection was closed, shutdowns fail with "not connected" if errors.Is(err, windows.WSAENOTCONN) || errors.Is(err, windows.WSAESHUTDOWN) { err = socket.ErrSocketClosed } return os.NewSyscallError("shutdown", err) } return nil } // CloseRead shuts down the read end of the socket, preventing future read operations. func (conn *HvsockConn) CloseRead() error { err := conn.shutdown(windows.SHUT_RD) if err != nil { return conn.opErr("closeread", err) } return nil } // CloseWrite shuts down the write end of the socket, preventing future write operations and // notifying the other endpoint that no more data will be written. func (conn *HvsockConn) CloseWrite() error { err := conn.shutdown(windows.SHUT_WR) if err != nil { return conn.opErr("closewrite", err) } return nil } // LocalAddr returns the local address of the connection. func (conn *HvsockConn) LocalAddr() net.Addr { return &conn.local } // RemoteAddr returns the remote address of the connection. func (conn *HvsockConn) RemoteAddr() net.Addr { return &conn.remote } // SetDeadline implements the net.Conn SetDeadline method. func (conn *HvsockConn) SetDeadline(t time.Time) error { // todo: implement `SetDeadline` for `win32File` if err := conn.SetReadDeadline(t); err != nil { return fmt.Errorf("set read deadline: %w", err) } if err := conn.SetWriteDeadline(t); err != nil { return fmt.Errorf("set write deadline: %w", err) } return nil } // SetReadDeadline implements the net.Conn SetReadDeadline method. func (conn *HvsockConn) SetReadDeadline(t time.Time) error { return conn.sock.SetReadDeadline(t) } // SetWriteDeadline implements the net.Conn SetWriteDeadline method. func (conn *HvsockConn) SetWriteDeadline(t time.Time) error { return conn.sock.SetWriteDeadline(t) } go-winio-0.6.2/hvsock_go118_test.go000066400000000000000000000053531460531775000170670ustar00rootroot00000000000000//go:build windows && go1.18 package winio import ( "errors" "fmt" "testing" "time" "golang.org/x/sys/windows" ) func FuzzHvSockRxTx(f *testing.F) { // fuzzing fails on windows 2019 for some reason, even though tests pass if _, _, build := windows.RtlGetNtVersionNumbers(); build <= 17763 { f.Skipf("build (%d) must be > %d", build, 17763) } 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 ws2019, its expected that the caller passes a vhd handle // that can be obtained from the virtdisk APIs. func FormatWritableLayerVHD(vhdHandle windows.Handle) (err error) { err = hcsFormatWritableLayerVhd(vhdHandle) if err != nil { return fmt.Errorf("failed to format writable layer vhd: %w", err) } return nil } // https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsgetlayervhdmountpath // //sys hcsGetLayerVhdMountPath(vhdHandle windows.Handle, mountPath **uint16) (hr error) = computestorage.HcsGetLayerVhdMountPath? // GetLayerVHDMountPath returns the volume path for a virtual disk of a writable container layer. func GetLayerVHDMountPath(vhdHandle windows.Handle) (path string, err error) { var mountPath *uint16 err = hcsGetLayerVhdMountPath(vhdHandle, &mountPath) if err != nil { return "", fmt.Errorf("failed to get vhd mount path: %w", err) } path = interop.ConvertAndFreeCoTaskMemString(mountPath) return path, nil } go-winio-0.6.2/internal/computestorage/doc.go000066400000000000000000000000271460531775000212270ustar00rootroot00000000000000package computestorage go-winio-0.6.2/internal/computestorage/zsyscall_windows.go000066400000000000000000000032311460531775000241000ustar00rootroot00000000000000//go:build windows // Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT. package computestorage import ( "syscall" "unsafe" "golang.org/x/sys/windows" ) var _ unsafe.Pointer // Do the interface allocations only once for common // Errno values. const ( errnoERROR_IO_PENDING = 997 ) var ( errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) errERROR_EINVAL error = syscall.EINVAL ) // errnoErr returns common boxed Errno values, to prevent // allocations at runtime. func errnoErr(e syscall.Errno) error { switch e { case 0: return errERROR_EINVAL case errnoERROR_IO_PENDING: return errERROR_IO_PENDING } return e } var ( modcomputestorage = windows.NewLazySystemDLL("computestorage.dll") procHcsFormatWritableLayerVhd = modcomputestorage.NewProc("HcsFormatWritableLayerVhd") procHcsGetLayerVhdMountPath = modcomputestorage.NewProc("HcsGetLayerVhdMountPath") ) func hcsFormatWritableLayerVhd(handle windows.Handle) (hr error) { hr = procHcsFormatWritableLayerVhd.Find() if hr != nil { return } r0, _, _ := syscall.SyscallN(procHcsFormatWritableLayerVhd.Addr(), uintptr(handle)) if int32(r0) < 0 { if r0&0x1fff0000 == 0x00070000 { r0 &= 0xffff } hr = syscall.Errno(r0) } return } func hcsGetLayerVhdMountPath(vhdHandle windows.Handle, mountPath **uint16) (hr error) { hr = procHcsGetLayerVhdMountPath.Find() if hr != nil { return } r0, _, _ := syscall.SyscallN(procHcsGetLayerVhdMountPath.Addr(), uintptr(vhdHandle), uintptr(unsafe.Pointer(mountPath))) if int32(r0) < 0 { if r0&0x1fff0000 == 0x00070000 { r0 &= 0xffff } hr = syscall.Errno(r0) } return } go-winio-0.6.2/internal/fs/000077500000000000000000000000001460531775000155035ustar00rootroot00000000000000go-winio-0.6.2/internal/fs/doc.go000066400000000000000000000001041460531775000165720ustar00rootroot00000000000000// This package contains Win32 filesystem functionality. package fs go-winio-0.6.2/internal/fs/fs.go000066400000000000000000000235351460531775000164520ustar00rootroot00000000000000//go:build windows package fs import ( "golang.org/x/sys/windows" "github.com/Microsoft/go-winio/internal/stringbuffer" ) //go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go fs.go // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew //sys CreateFile(name string, access AccessMask, mode FileShareMode, sa *windows.SecurityAttributes, createmode FileCreationDisposition, attrs FileFlagOrAttribute, templatefile windows.Handle) (handle windows.Handle, err error) [failretval==windows.InvalidHandle] = CreateFileW const NullHandle windows.Handle = 0 // AccessMask defines standard, specific, and generic rights. // // Used with CreateFile and NtCreateFile (and co.). // // Bitmask: // 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 // 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 // +---------------+---------------+-------------------------------+ // |G|G|G|G|Resvd|A| StandardRights| SpecificRights | // |R|W|E|A| |S| | | // +-+-------------+---------------+-------------------------------+ // // GR Generic Read // GW Generic Write // GE Generic Exectue // GA Generic All // Resvd Reserved // AS Access Security System // // https://learn.microsoft.com/en-us/windows/win32/secauthz/access-mask // // https://learn.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights // // https://learn.microsoft.com/en-us/windows/win32/fileio/file-access-rights-constants type AccessMask = windows.ACCESS_MASK //nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. const ( // Not actually any. // // For CreateFile: "query certain metadata such as file, directory, or device attributes without accessing that file or device" // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew#parameters FILE_ANY_ACCESS AccessMask = 0 GENERIC_READ AccessMask = 0x8000_0000 GENERIC_WRITE AccessMask = 0x4000_0000 GENERIC_EXECUTE AccessMask = 0x2000_0000 GENERIC_ALL AccessMask = 0x1000_0000 ACCESS_SYSTEM_SECURITY AccessMask = 0x0100_0000 // Specific Object Access // from ntioapi.h FILE_READ_DATA AccessMask = (0x0001) // file & pipe FILE_LIST_DIRECTORY AccessMask = (0x0001) // directory FILE_WRITE_DATA AccessMask = (0x0002) // file & pipe FILE_ADD_FILE AccessMask = (0x0002) // directory FILE_APPEND_DATA AccessMask = (0x0004) // file FILE_ADD_SUBDIRECTORY AccessMask = (0x0004) // directory FILE_CREATE_PIPE_INSTANCE AccessMask = (0x0004) // named pipe FILE_READ_EA AccessMask = (0x0008) // file & directory FILE_READ_PROPERTIES AccessMask = FILE_READ_EA FILE_WRITE_EA AccessMask = (0x0010) // file & directory FILE_WRITE_PROPERTIES AccessMask = FILE_WRITE_EA FILE_EXECUTE AccessMask = (0x0020) // file FILE_TRAVERSE AccessMask = (0x0020) // directory FILE_DELETE_CHILD AccessMask = (0x0040) // directory FILE_READ_ATTRIBUTES AccessMask = (0x0080) // all FILE_WRITE_ATTRIBUTES AccessMask = (0x0100) // all FILE_ALL_ACCESS AccessMask = (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF) FILE_GENERIC_READ AccessMask = (STANDARD_RIGHTS_READ | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE) FILE_GENERIC_WRITE AccessMask = (STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | SYNCHRONIZE) FILE_GENERIC_EXECUTE AccessMask = (STANDARD_RIGHTS_EXECUTE | FILE_READ_ATTRIBUTES | FILE_EXECUTE | SYNCHRONIZE) SPECIFIC_RIGHTS_ALL AccessMask = 0x0000FFFF // Standard Access // from ntseapi.h DELETE AccessMask = 0x0001_0000 READ_CONTROL AccessMask = 0x0002_0000 WRITE_DAC AccessMask = 0x0004_0000 WRITE_OWNER AccessMask = 0x0008_0000 SYNCHRONIZE AccessMask = 0x0010_0000 STANDARD_RIGHTS_REQUIRED AccessMask = 0x000F_0000 STANDARD_RIGHTS_READ AccessMask = READ_CONTROL STANDARD_RIGHTS_WRITE AccessMask = READ_CONTROL STANDARD_RIGHTS_EXECUTE AccessMask = READ_CONTROL STANDARD_RIGHTS_ALL AccessMask = 0x001F_0000 ) type FileShareMode uint32 //nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. const ( FILE_SHARE_NONE FileShareMode = 0x00 FILE_SHARE_READ FileShareMode = 0x01 FILE_SHARE_WRITE FileShareMode = 0x02 FILE_SHARE_DELETE FileShareMode = 0x04 FILE_SHARE_VALID_FLAGS FileShareMode = 0x07 ) type FileCreationDisposition uint32 //nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. const ( // from winbase.h CREATE_NEW FileCreationDisposition = 0x01 CREATE_ALWAYS FileCreationDisposition = 0x02 OPEN_EXISTING FileCreationDisposition = 0x03 OPEN_ALWAYS FileCreationDisposition = 0x04 TRUNCATE_EXISTING FileCreationDisposition = 0x05 ) // Create disposition values for NtCreate* type NTFileCreationDisposition uint32 //nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. const ( // From ntioapi.h FILE_SUPERSEDE NTFileCreationDisposition = 0x00 FILE_OPEN NTFileCreationDisposition = 0x01 FILE_CREATE NTFileCreationDisposition = 0x02 FILE_OPEN_IF NTFileCreationDisposition = 0x03 FILE_OVERWRITE NTFileCreationDisposition = 0x04 FILE_OVERWRITE_IF NTFileCreationDisposition = 0x05 FILE_MAXIMUM_DISPOSITION NTFileCreationDisposition = 0x05 ) // CreateFile and co. take flags or attributes together as one parameter. // Define alias until we can use generics to allow both // // https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants type FileFlagOrAttribute uint32 //nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. const ( // from winnt.h FILE_FLAG_WRITE_THROUGH FileFlagOrAttribute = 0x8000_0000 FILE_FLAG_OVERLAPPED FileFlagOrAttribute = 0x4000_0000 FILE_FLAG_NO_BUFFERING FileFlagOrAttribute = 0x2000_0000 FILE_FLAG_RANDOM_ACCESS FileFlagOrAttribute = 0x1000_0000 FILE_FLAG_SEQUENTIAL_SCAN FileFlagOrAttribute = 0x0800_0000 FILE_FLAG_DELETE_ON_CLOSE FileFlagOrAttribute = 0x0400_0000 FILE_FLAG_BACKUP_SEMANTICS FileFlagOrAttribute = 0x0200_0000 FILE_FLAG_POSIX_SEMANTICS FileFlagOrAttribute = 0x0100_0000 FILE_FLAG_OPEN_REPARSE_POINT FileFlagOrAttribute = 0x0020_0000 FILE_FLAG_OPEN_NO_RECALL FileFlagOrAttribute = 0x0010_0000 FILE_FLAG_FIRST_PIPE_INSTANCE FileFlagOrAttribute = 0x0008_0000 ) // NtCreate* functions take a dedicated CreateOptions parameter. // // https://learn.microsoft.com/en-us/windows/win32/api/Winternl/nf-winternl-ntcreatefile // // https://learn.microsoft.com/en-us/windows/win32/devnotes/nt-create-named-pipe-file type NTCreateOptions uint32 //nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. const ( // From ntioapi.h FILE_DIRECTORY_FILE NTCreateOptions = 0x0000_0001 FILE_WRITE_THROUGH NTCreateOptions = 0x0000_0002 FILE_SEQUENTIAL_ONLY NTCreateOptions = 0x0000_0004 FILE_NO_INTERMEDIATE_BUFFERING NTCreateOptions = 0x0000_0008 FILE_SYNCHRONOUS_IO_ALERT NTCreateOptions = 0x0000_0010 FILE_SYNCHRONOUS_IO_NONALERT NTCreateOptions = 0x0000_0020 FILE_NON_DIRECTORY_FILE NTCreateOptions = 0x0000_0040 FILE_CREATE_TREE_CONNECTION NTCreateOptions = 0x0000_0080 FILE_COMPLETE_IF_OPLOCKED NTCreateOptions = 0x0000_0100 FILE_NO_EA_KNOWLEDGE NTCreateOptions = 0x0000_0200 FILE_DISABLE_TUNNELING NTCreateOptions = 0x0000_0400 FILE_RANDOM_ACCESS NTCreateOptions = 0x0000_0800 FILE_DELETE_ON_CLOSE NTCreateOptions = 0x0000_1000 FILE_OPEN_BY_FILE_ID NTCreateOptions = 0x0000_2000 FILE_OPEN_FOR_BACKUP_INTENT NTCreateOptions = 0x0000_4000 FILE_NO_COMPRESSION NTCreateOptions = 0x0000_8000 ) type FileSQSFlag = FileFlagOrAttribute //nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. const ( // from winbase.h SECURITY_ANONYMOUS FileSQSFlag = FileSQSFlag(SecurityAnonymous << 16) SECURITY_IDENTIFICATION FileSQSFlag = FileSQSFlag(SecurityIdentification << 16) SECURITY_IMPERSONATION FileSQSFlag = FileSQSFlag(SecurityImpersonation << 16) SECURITY_DELEGATION FileSQSFlag = FileSQSFlag(SecurityDelegation << 16) SECURITY_SQOS_PRESENT FileSQSFlag = 0x0010_0000 SECURITY_VALID_SQOS_FLAGS FileSQSFlag = 0x001F_0000 ) // GetFinalPathNameByHandle flags // // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew#parameters type GetFinalPathFlag uint32 //nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. const ( GetFinalPathDefaultFlag GetFinalPathFlag = 0x0 FILE_NAME_NORMALIZED GetFinalPathFlag = 0x0 FILE_NAME_OPENED GetFinalPathFlag = 0x8 VOLUME_NAME_DOS GetFinalPathFlag = 0x0 VOLUME_NAME_GUID GetFinalPathFlag = 0x1 VOLUME_NAME_NT GetFinalPathFlag = 0x2 VOLUME_NAME_NONE GetFinalPathFlag = 0x4 ) // getFinalPathNameByHandle facilitates calling the Windows API GetFinalPathNameByHandle // with the given handle and flags. It transparently takes care of creating a buffer of the // correct size for the call. // // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew func GetFinalPathNameByHandle(h windows.Handle, flags GetFinalPathFlag) (string, error) { b := stringbuffer.NewWString() //TODO: can loop infinitely if Win32 keeps returning the same (or a larger) n? for { n, err := windows.GetFinalPathNameByHandle(h, b.Pointer(), b.Cap(), uint32(flags)) if err != nil { return "", err } // If the buffer wasn't large enough, n will be the total size needed (including null terminator). // Resize and try again. if n > b.Cap() { b.ResizeTo(n) continue } // If the buffer is large enough, n will be the size not including the null terminator. // Convert to a Go string and return. return b.String(), nil } } go-winio-0.6.2/internal/fs/fs_test.go000066400000000000000000000015711460531775000175050ustar00rootroot00000000000000//go:build windows package fs import ( "os" "path/filepath" "strings" "testing" "golang.org/x/sys/windows" ) func Test_GetFinalPathNameByHandle(t *testing.T) { d := t.TempDir() // open f via a relative path name := t.Name() + ".txt" fullPath := filepath.Join(d, name) w, err := os.Getwd() if err != nil { t.Fatalf("could not get working directory: %v", err) } if err := os.Chdir(d); err != nil { t.Fatalf("could not chdir to %s: %v", d, err) } defer os.Chdir(w) //nolint:errcheck f, err := os.Create(name) if err != nil { t.Fatalf("could not open %s: %v", fullPath, err) } defer f.Close() path, err := GetFinalPathNameByHandle(windows.Handle(f.Fd()), GetFinalPathDefaultFlag) if err != nil { t.Fatalf("could not get final path for %s: %v", fullPath, err) } if strings.EqualFold(fullPath, path) { t.Fatalf("expected %s, got %s", fullPath, path) } } go-winio-0.6.2/internal/fs/security.go000066400000000000000000000007251460531775000177050ustar00rootroot00000000000000package fs // https://learn.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-security_impersonation_level type SecurityImpersonationLevel int32 // C default enums underlying type is `int`, which is Go `int32` // Impersonation levels const ( SecurityAnonymous SecurityImpersonationLevel = 0 SecurityIdentification SecurityImpersonationLevel = 1 SecurityImpersonation SecurityImpersonationLevel = 2 SecurityDelegation SecurityImpersonationLevel = 3 ) go-winio-0.6.2/internal/fs/zsyscall_windows.go000066400000000000000000000033321460531775000214510ustar00rootroot00000000000000//go:build windows // Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT. package fs import ( "syscall" "unsafe" "golang.org/x/sys/windows" ) var _ unsafe.Pointer // Do the interface allocations only once for common // Errno values. const ( errnoERROR_IO_PENDING = 997 ) var ( errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) errERROR_EINVAL error = syscall.EINVAL ) // errnoErr returns common boxed Errno values, to prevent // allocations at runtime. func errnoErr(e syscall.Errno) error { switch e { case 0: return errERROR_EINVAL case errnoERROR_IO_PENDING: return errERROR_IO_PENDING } return e } var ( modkernel32 = windows.NewLazySystemDLL("kernel32.dll") procCreateFileW = modkernel32.NewProc("CreateFileW") ) func CreateFile(name string, access AccessMask, mode FileShareMode, sa *windows.SecurityAttributes, createmode FileCreationDisposition, attrs FileFlagOrAttribute, templatefile windows.Handle) (handle windows.Handle, err error) { var _p0 *uint16 _p0, err = syscall.UTF16PtrFromString(name) if err != nil { return } return _CreateFile(_p0, access, mode, sa, createmode, attrs, templatefile) } func _CreateFile(name *uint16, access AccessMask, mode FileShareMode, sa *windows.SecurityAttributes, createmode FileCreationDisposition, attrs FileFlagOrAttribute, templatefile windows.Handle) (handle windows.Handle, err error) { r0, _, e1 := syscall.SyscallN(procCreateFileW.Addr(), uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile)) handle = windows.Handle(r0) if handle == windows.InvalidHandle { err = errnoErr(e1) } return } go-winio-0.6.2/internal/interop/000077500000000000000000000000001460531775000165535ustar00rootroot00000000000000go-winio-0.6.2/internal/interop/doc.go000066400000000000000000000000201460531775000176370ustar00rootroot00000000000000package interop go-winio-0.6.2/internal/interop/interop.go000066400000000000000000000011411460531775000205570ustar00rootroot00000000000000//go:build windows package interop import ( "syscall" "unsafe" ) //go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go interop.go //sys coTaskMemFree(buffer unsafe.Pointer) = api_ms_win_core_com_l1_1_0.CoTaskMemFree func ConvertAndFreeCoTaskMemString(buffer *uint16) string { str := syscall.UTF16ToString((*[1 << 29]uint16)(unsafe.Pointer(buffer))[:]) coTaskMemFree(unsafe.Pointer(buffer)) return str } func Win32FromHresult(hr uintptr) syscall.Errno { if hr&0x1fff0000 == 0x00070000 { return syscall.Errno(hr & 0xffff) } return syscall.Errno(hr) } go-winio-0.6.2/internal/interop/zsyscall_windows.go000066400000000000000000000017421460531775000225240ustar00rootroot00000000000000//go:build windows // Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT. package interop import ( "syscall" "unsafe" "golang.org/x/sys/windows" ) var _ unsafe.Pointer // Do the interface allocations only once for common // Errno values. const ( errnoERROR_IO_PENDING = 997 ) var ( errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) errERROR_EINVAL error = syscall.EINVAL ) // errnoErr returns common boxed Errno values, to prevent // allocations at runtime. func errnoErr(e syscall.Errno) error { switch e { case 0: return errERROR_EINVAL case errnoERROR_IO_PENDING: return errERROR_IO_PENDING } return e } var ( modapi_ms_win_core_com_l1_1_0 = windows.NewLazySystemDLL("api-ms-win-core-com-l1-1-0.dll") procCoTaskMemFree = modapi_ms_win_core_com_l1_1_0.NewProc("CoTaskMemFree") ) func coTaskMemFree(buffer unsafe.Pointer) { syscall.SyscallN(procCoTaskMemFree.Addr(), uintptr(buffer)) return } go-winio-0.6.2/internal/socket/000077500000000000000000000000001460531775000163635ustar00rootroot00000000000000go-winio-0.6.2/internal/socket/rawaddr.go000066400000000000000000000013231460531775000203350ustar00rootroot00000000000000package socket import ( "unsafe" ) // RawSockaddr allows structs to be used with [Bind] and [ConnectEx]. The // struct must meet the Win32 sockaddr requirements specified here: // https://docs.microsoft.com/en-us/windows/win32/winsock/sockaddr-2 // // Specifically, the struct size must be least larger than an int16 (unsigned short) // for the address family. type RawSockaddr interface { // Sockaddr returns a pointer to the RawSockaddr and its struct size, allowing // for the RawSockaddr's data to be overwritten by syscalls (if necessary). // // It is the callers responsibility to validate that the values are valid; invalid // pointers or size can cause a panic. Sockaddr() (unsafe.Pointer, int32, error) } go-winio-0.6.2/internal/socket/socket.go000066400000000000000000000115301460531775000202020ustar00rootroot00000000000000//go:build windows package socket import ( "errors" "fmt" "net" "sync" "syscall" "unsafe" "github.com/Microsoft/go-winio/pkg/guid" "golang.org/x/sys/windows" ) //go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go socket.go //sys getsockname(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) [failretval==socketError] = ws2_32.getsockname //sys getpeername(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) [failretval==socketError] = ws2_32.getpeername //sys bind(s windows.Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socketError] = ws2_32.bind const socketError = uintptr(^uint32(0)) var ( // todo(helsaawy): create custom error types to store the desired vs actual size and addr family? ErrBufferSize = errors.New("buffer size") ErrAddrFamily = errors.New("address family") ErrInvalidPointer = errors.New("invalid pointer") ErrSocketClosed = fmt.Errorf("socket closed: %w", net.ErrClosed) ) // todo(helsaawy): replace these with generics, ie: GetSockName[S RawSockaddr](s windows.Handle) (S, error) // GetSockName writes the local address of socket s to the [RawSockaddr] rsa. // If rsa is not large enough, the [windows.WSAEFAULT] is returned. func GetSockName(s windows.Handle, rsa RawSockaddr) error { ptr, l, err := rsa.Sockaddr() if err != nil { return fmt.Errorf("could not retrieve socket pointer and size: %w", err) } // although getsockname returns WSAEFAULT if the buffer is too small, it does not set // &l to the correct size, so--apart from doubling the buffer repeatedly--there is no remedy return getsockname(s, ptr, &l) } // GetPeerName returns the remote address the socket is connected to. // // See [GetSockName] for more information. func GetPeerName(s windows.Handle, rsa RawSockaddr) error { ptr, l, err := rsa.Sockaddr() if err != nil { return fmt.Errorf("could not retrieve socket pointer and size: %w", err) } return getpeername(s, ptr, &l) } func Bind(s windows.Handle, rsa RawSockaddr) (err error) { ptr, l, err := rsa.Sockaddr() if err != nil { return fmt.Errorf("could not retrieve socket pointer and size: %w", err) } return bind(s, ptr, l) } // "golang.org/x/sys/windows".ConnectEx and .Bind only accept internal implementations of the // their sockaddr interface, so they cannot be used with HvsockAddr // Replicate functionality here from // https://cs.opensource.google/go/x/sys/+/master:windows/syscall_windows.go // The function pointers to `AcceptEx`, `ConnectEx` and `GetAcceptExSockaddrs` must be loaded at // runtime via a WSAIoctl call: // https://docs.microsoft.com/en-us/windows/win32/api/Mswsock/nc-mswsock-lpfn_connectex#remarks type runtimeFunc struct { id guid.GUID once sync.Once addr uintptr err error } func (f *runtimeFunc) Load() error { f.once.Do(func() { var s windows.Handle s, f.err = windows.Socket(windows.AF_INET, windows.SOCK_STREAM, windows.IPPROTO_TCP) if f.err != nil { return } defer windows.CloseHandle(s) //nolint:errcheck var n uint32 f.err = windows.WSAIoctl(s, windows.SIO_GET_EXTENSION_FUNCTION_POINTER, (*byte)(unsafe.Pointer(&f.id)), uint32(unsafe.Sizeof(f.id)), (*byte)(unsafe.Pointer(&f.addr)), uint32(unsafe.Sizeof(f.addr)), &n, nil, // overlapped 0, // completionRoutine ) }) return f.err } var ( // todo: add `AcceptEx` and `GetAcceptExSockaddrs` WSAID_CONNECTEX = guid.GUID{ //revive:disable-line:var-naming ALL_CAPS Data1: 0x25a207b9, Data2: 0xddf3, Data3: 0x4660, Data4: [8]byte{0x8e, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e}, } connectExFunc = runtimeFunc{id: WSAID_CONNECTEX} ) func ConnectEx( fd windows.Handle, rsa RawSockaddr, sendBuf *byte, sendDataLen uint32, bytesSent *uint32, overlapped *windows.Overlapped, ) error { if err := connectExFunc.Load(); err != nil { return fmt.Errorf("failed to load ConnectEx function pointer: %w", err) } ptr, n, err := rsa.Sockaddr() if err != nil { return err } return connectEx(fd, ptr, n, sendBuf, sendDataLen, bytesSent, overlapped) } // BOOL LpfnConnectex( // [in] SOCKET s, // [in] const sockaddr *name, // [in] int namelen, // [in, optional] PVOID lpSendBuffer, // [in] DWORD dwSendDataLength, // [out] LPDWORD lpdwBytesSent, // [in] LPOVERLAPPED lpOverlapped // ) func connectEx( s windows.Handle, name unsafe.Pointer, namelen int32, sendBuf *byte, sendDataLen uint32, bytesSent *uint32, overlapped *windows.Overlapped, ) (err error) { r1, _, e1 := syscall.SyscallN(connectExFunc.addr, uintptr(s), uintptr(name), uintptr(namelen), uintptr(unsafe.Pointer(sendBuf)), uintptr(sendDataLen), uintptr(unsafe.Pointer(bytesSent)), uintptr(unsafe.Pointer(overlapped)), ) if r1 == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return err } go-winio-0.6.2/internal/socket/zsyscall_windows.go000066400000000000000000000031761460531775000223370ustar00rootroot00000000000000//go:build windows // Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT. package socket import ( "syscall" "unsafe" "golang.org/x/sys/windows" ) var _ unsafe.Pointer // Do the interface allocations only once for common // Errno values. const ( errnoERROR_IO_PENDING = 997 ) var ( errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) errERROR_EINVAL error = syscall.EINVAL ) // errnoErr returns common boxed Errno values, to prevent // allocations at runtime. func errnoErr(e syscall.Errno) error { switch e { case 0: return errERROR_EINVAL case errnoERROR_IO_PENDING: return errERROR_IO_PENDING } return e } var ( modws2_32 = windows.NewLazySystemDLL("ws2_32.dll") procbind = modws2_32.NewProc("bind") procgetpeername = modws2_32.NewProc("getpeername") procgetsockname = modws2_32.NewProc("getsockname") ) func bind(s windows.Handle, name unsafe.Pointer, namelen int32) (err error) { r1, _, e1 := syscall.SyscallN(procbind.Addr(), uintptr(s), uintptr(name), uintptr(namelen)) if r1 == socketError { err = errnoErr(e1) } return } func getpeername(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) { r1, _, e1 := syscall.SyscallN(procgetpeername.Addr(), uintptr(s), uintptr(name), uintptr(unsafe.Pointer(namelen))) if r1 == socketError { err = errnoErr(e1) } return } func getsockname(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) { r1, _, e1 := syscall.SyscallN(procgetsockname.Addr(), uintptr(s), uintptr(name), uintptr(unsafe.Pointer(namelen))) if r1 == socketError { err = errnoErr(e1) } return } go-winio-0.6.2/internal/stringbuffer/000077500000000000000000000000001460531775000175735ustar00rootroot00000000000000go-winio-0.6.2/internal/stringbuffer/wstring.go000066400000000000000000000066171460531775000216310ustar00rootroot00000000000000package stringbuffer import ( "sync" "unicode/utf16" ) // TODO: worth exporting and using in mkwinsyscall? // Uint16BufferSize is the buffer size in the pool, chosen somewhat arbitrarily to accommodate // large path strings: // MAX_PATH (260) + size of volume GUID prefix (49) + null terminator = 310. const MinWStringCap = 310 // use *[]uint16 since []uint16 creates an extra allocation where the slice header // is copied to heap and then referenced via pointer in the interface header that sync.Pool // stores. var pathPool = sync.Pool{ // if go1.18+ adds Pool[T], use that to store []uint16 directly New: func() interface{} { b := make([]uint16, MinWStringCap) return &b }, } func newBuffer() []uint16 { return *(pathPool.Get().(*[]uint16)) } // freeBuffer copies the slice header data, and puts a pointer to that in the pool. // This avoids taking a pointer to the slice header in WString, which can be set to nil. func freeBuffer(b []uint16) { pathPool.Put(&b) } // WString is a wide string buffer ([]uint16) meant for storing UTF-16 encoded strings // for interacting with Win32 APIs. // Sizes are specified as uint32 and not int. // // It is not thread safe. type WString struct { // type-def allows casting to []uint16 directly, use struct to prevent that and allow adding fields in the future. // raw buffer b []uint16 } // NewWString returns a [WString] allocated from a shared pool with an // initial capacity of at least [MinWStringCap]. // Since the buffer may have been previously used, its contents are not guaranteed to be empty. // // The buffer should be freed via [WString.Free] func NewWString() *WString { return &WString{ b: newBuffer(), } } func (b *WString) Free() { if b.empty() { return } freeBuffer(b.b) b.b = nil } // ResizeTo grows the buffer to at least c and returns the new capacity, freeing the // previous buffer back into pool. func (b *WString) ResizeTo(c uint32) uint32 { // already sufficient (or n is 0) if c <= b.Cap() { return b.Cap() } if c <= MinWStringCap { c = MinWStringCap } // allocate at-least double buffer size, as is done in [bytes.Buffer] and other places if c <= 2*b.Cap() { c = 2 * b.Cap() } b2 := make([]uint16, c) if !b.empty() { copy(b2, b.b) freeBuffer(b.b) } b.b = b2 return c } // Buffer returns the underlying []uint16 buffer. func (b *WString) Buffer() []uint16 { if b.empty() { return nil } return b.b } // Pointer returns a pointer to the first uint16 in the buffer. // If the [WString.Free] has already been called, the pointer will be nil. func (b *WString) Pointer() *uint16 { if b.empty() { return nil } return &b.b[0] } // String returns the returns the UTF-8 encoding of the UTF-16 string in the buffer. // // It assumes that the data is null-terminated. func (b *WString) String() string { // Using [windows.UTF16ToString] would require importing "golang.org/x/sys/windows" // and would make this code Windows-only, which makes no sense. // So copy UTF16ToString code into here. // If other windows-specific code is added, switch to [windows.UTF16ToString] s := b.b for i, v := range s { if v == 0 { s = s[:i] break } } return string(utf16.Decode(s)) } // Cap returns the underlying buffer capacity. func (b *WString) Cap() uint32 { if b.empty() { return 0 } return b.cap() } func (b *WString) cap() uint32 { return uint32(cap(b.b)) } func (b *WString) empty() bool { return b == nil || b.cap() == 0 } go-winio-0.6.2/internal/stringbuffer/wstring_test.go000066400000000000000000000017401460531775000226600ustar00rootroot00000000000000//go:build windows package stringbuffer import "testing" func Test_BufferCapacity(t *testing.T) { b := NewWString() c := b.Cap() if c < MinWStringCap { t.Fatalf("expected capacity >= %d, got %d", MinWStringCap, c) } if l := len(b.b); l != int(c) { t.Fatalf("buffer length (%d) and capacity (%d) mismatch", l, c) } n := uint32(1.5 * MinWStringCap) nn := b.ResizeTo(n) if len(b.b) != int(nn) { t.Fatalf("resized buffer should be %d, was %d", nn, len(b.b)) } if n > nn { t.Fatalf("resized to a value smaller than requested") } } func Test_BufferFree(t *testing.T) { // make sure free-ing doesn't set pooled buffer to nil as well for i := 0; i < 256; i++ { // try allocating and freeing repeatedly since pool does not guarantee item reuse b := NewWString() b.Free() if b.b != nil { t.Fatalf("freed buffer is not nil") } b = NewWString() c := b.Cap() if c < MinWStringCap { t.Fatalf("expected capacity >= %d, got %d", MinWStringCap, c) } } } go-winio-0.6.2/pipe.go000066400000000000000000000401651460531775000145510ustar00rootroot00000000000000//go:build windows // +build windows package winio import ( "context" "errors" "fmt" "io" "net" "os" "runtime" "time" "unsafe" "golang.org/x/sys/windows" "github.com/Microsoft/go-winio/internal/fs" ) //sys connectNamedPipe(pipe windows.Handle, o *windows.Overlapped) (err error) = ConnectNamedPipe //sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *windows.SecurityAttributes) (handle windows.Handle, err error) [failretval==windows.InvalidHandle] = CreateNamedPipeW //sys disconnectNamedPipe(pipe windows.Handle) (err error) = DisconnectNamedPipe //sys getNamedPipeInfo(pipe windows.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo //sys getNamedPipeHandleState(pipe windows.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW //sys ntCreateNamedPipeFile(pipe *windows.Handle, access ntAccessMask, oa *objectAttributes, iosb *ioStatusBlock, share ntFileShareMode, disposition ntFileCreationDisposition, options ntFileOptions, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntStatus) = ntdll.NtCreateNamedPipeFile //sys rtlNtStatusToDosError(status ntStatus) (winerr error) = ntdll.RtlNtStatusToDosErrorNoTeb //sys rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntStatus) = ntdll.RtlDosPathNameToNtPathName_U //sys rtlDefaultNpAcl(dacl *uintptr) (status ntStatus) = ntdll.RtlDefaultNpAcl type PipeConn interface { net.Conn Disconnect() error Flush() error } // type aliases for mkwinsyscall code type ( ntAccessMask = fs.AccessMask ntFileShareMode = fs.FileShareMode ntFileCreationDisposition = fs.NTFileCreationDisposition ntFileOptions = fs.NTCreateOptions ) type ioStatusBlock struct { Status, Information uintptr } // typedef struct _OBJECT_ATTRIBUTES { // ULONG Length; // HANDLE RootDirectory; // PUNICODE_STRING ObjectName; // ULONG Attributes; // PVOID SecurityDescriptor; // PVOID SecurityQualityOfService; // } OBJECT_ATTRIBUTES; // // https://learn.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-_object_attributes type objectAttributes struct { Length uintptr RootDirectory uintptr ObjectName *unicodeString Attributes uintptr SecurityDescriptor *securityDescriptor SecurityQoS uintptr } type unicodeString struct { Length uint16 MaximumLength uint16 Buffer uintptr } // typedef struct _SECURITY_DESCRIPTOR { // BYTE Revision; // BYTE Sbz1; // SECURITY_DESCRIPTOR_CONTROL Control; // PSID Owner; // PSID Group; // PACL Sacl; // PACL Dacl; // } SECURITY_DESCRIPTOR, *PISECURITY_DESCRIPTOR; // // https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-security_descriptor type securityDescriptor struct { Revision byte Sbz1 byte Control uint16 Owner uintptr Group uintptr Sacl uintptr //revive:disable-line:var-naming SACL, not Sacl Dacl uintptr //revive:disable-line:var-naming DACL, not Dacl } type ntStatus int32 func (status ntStatus) Err() error { if status >= 0 { return nil } return rtlNtStatusToDosError(status) } var ( // ErrPipeListenerClosed is returned for pipe operations on listeners that have been closed. ErrPipeListenerClosed = net.ErrClosed errPipeWriteClosed = errors.New("pipe has been closed for write") ) type win32Pipe struct { *win32File path string } var _ PipeConn = (*win32Pipe)(nil) type win32MessageBytePipe struct { win32Pipe writeClosed bool readEOF bool } type pipeAddress string func (f *win32Pipe) LocalAddr() net.Addr { return pipeAddress(f.path) } func (f *win32Pipe) RemoteAddr() net.Addr { return pipeAddress(f.path) } func (f *win32Pipe) SetDeadline(t time.Time) error { if err := f.SetReadDeadline(t); err != nil { return err } return f.SetWriteDeadline(t) } func (f *win32Pipe) Disconnect() error { return disconnectNamedPipe(f.win32File.handle) } // CloseWrite closes the write side of a message pipe in byte mode. func (f *win32MessageBytePipe) CloseWrite() error { if f.writeClosed { return errPipeWriteClosed } err := f.win32File.Flush() if err != nil { return err } _, err = f.win32File.Write(nil) if err != nil { return err } f.writeClosed = true return nil } // Write writes bytes to a message pipe in byte mode. Zero-byte writes are ignored, since // they are used to implement CloseWrite(). func (f *win32MessageBytePipe) Write(b []byte) (int, error) { if f.writeClosed { return 0, errPipeWriteClosed } if len(b) == 0 { return 0, nil } return f.win32File.Write(b) } // Read reads bytes from a message pipe in byte mode. A read of a zero-byte message on a message // mode pipe will return io.EOF, as will all subsequent reads. func (f *win32MessageBytePipe) Read(b []byte) (int, error) { if f.readEOF { return 0, io.EOF } n, err := f.win32File.Read(b) 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 == windows.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. err = nil } return n, err } func (pipeAddress) Network() string { return "pipe" } func (s pipeAddress) String() string { return string(s) } // tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout. func tryDialPipe(ctx context.Context, path *string, access fs.AccessMask, impLevel PipeImpLevel) (windows.Handle, error) { for { select { case <-ctx.Done(): return windows.Handle(0), ctx.Err() default: h, err := fs.CreateFile(*path, access, 0, // mode nil, // security attributes fs.OPEN_EXISTING, fs.FILE_FLAG_OVERLAPPED|fs.SECURITY_SQOS_PRESENT|fs.FileSQSFlag(impLevel), 0, // template file handle ) if err == nil { return h, nil } 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 // view, as we always try each 10 milliseconds. time.Sleep(10 * time.Millisecond) } } } // DialPipe connects to a named pipe by path, timing out if the connection // takes longer than the specified duration. If timeout is nil, then we use // a default timeout of 2 seconds. (We do not use WaitNamedPipe.) func DialPipe(path string, timeout *time.Duration) (net.Conn, error) { var absTimeout time.Time if timeout != nil { absTimeout = time.Now().Add(*timeout) } else { absTimeout = time.Now().Add(2 * time.Second) } ctx, cancel := context.WithDeadline(context.Background(), absTimeout) defer cancel() conn, err := DialPipeContext(ctx, path) if errors.Is(err, context.DeadlineExceeded) { return nil, ErrTimeout } return conn, err } // DialPipeContext attempts to connect to a named pipe by `path` until `ctx` // cancellation or timeout. func DialPipeContext(ctx context.Context, path string) (net.Conn, error) { return DialPipeAccess(ctx, path, uint32(fs.GENERIC_READ|fs.GENERIC_WRITE)) } // PipeImpLevel is an enumeration of impersonation levels that may be set // when calling DialPipeAccessImpersonation. type PipeImpLevel uint32 const ( PipeImpLevelAnonymous = PipeImpLevel(fs.SECURITY_ANONYMOUS) PipeImpLevelIdentification = PipeImpLevel(fs.SECURITY_IDENTIFICATION) PipeImpLevelImpersonation = PipeImpLevel(fs.SECURITY_IMPERSONATION) PipeImpLevelDelegation = PipeImpLevel(fs.SECURITY_DELEGATION) ) // DialPipeAccess attempts to connect to a named pipe by `path` with `access` until `ctx` // cancellation or timeout. func DialPipeAccess(ctx context.Context, path string, access uint32) (net.Conn, error) { return DialPipeAccessImpLevel(ctx, path, access, PipeImpLevelAnonymous) } // DialPipeAccessImpLevel attempts to connect to a named pipe by `path` with // `access` at `impLevel` until `ctx` cancellation or timeout. The other // DialPipe* implementations use PipeImpLevelAnonymous. func DialPipeAccessImpLevel(ctx context.Context, path string, access uint32, impLevel PipeImpLevel) (net.Conn, error) { var err error var h windows.Handle h, err = tryDialPipe(ctx, &path, fs.AccessMask(access), impLevel) if err != nil { return nil, err } var flags uint32 err = getNamedPipeInfo(h, &flags, nil, nil, nil) if err != nil { return nil, err } f, err := makeWin32File(h) if err != nil { windows.Close(h) return nil, err } // If the pipe is in message mode, return a message byte pipe, which // supports CloseWrite(). if flags&windows.PIPE_TYPE_MESSAGE != 0 { return &win32MessageBytePipe{ win32Pipe: win32Pipe{win32File: f, path: path}, }, nil } return &win32Pipe{win32File: f, path: path}, nil } type acceptResponse struct { f *win32File err error } type win32PipeListener struct { firstHandle windows.Handle path string config PipeConfig acceptCh chan (chan acceptResponse) closeCh chan int doneCh chan int } func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (windows.Handle, error) { path16, err := windows.UTF16FromString(path) if err != nil { return 0, &os.PathError{Op: "open", Path: path, Err: err} } var oa objectAttributes oa.Length = unsafe.Sizeof(oa) var ntPath unicodeString if err := rtlDosPathNameToNtPathName(&path16[0], &ntPath, 0, 0, ).Err(); err != nil { return 0, &os.PathError{Op: "open", Path: path, Err: err} } defer windows.LocalFree(windows.Handle(ntPath.Buffer)) //nolint:errcheck oa.ObjectName = &ntPath oa.Attributes = windows.OBJ_CASE_INSENSITIVE // The security descriptor is only needed for the first pipe. if first { if sd != nil { //todo: does `sdb` need to be allocated on the heap, or can go allocate it? l := uint32(len(sd)) sdb, err := windows.LocalAlloc(0, l) if err != nil { return 0, fmt.Errorf("LocalAlloc for security descriptor with of length %d: %w", l, err) } defer windows.LocalFree(windows.Handle(sdb)) //nolint:errcheck copy((*[0xffff]byte)(unsafe.Pointer(sdb))[:], sd) oa.SecurityDescriptor = (*securityDescriptor)(unsafe.Pointer(sdb)) } else { // 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: %w", err) } defer windows.LocalFree(windows.Handle(dacl)) //nolint:errcheck sdb := &securityDescriptor{ Revision: 1, Control: windows.SE_DACL_PRESENT, Dacl: dacl, } oa.SecurityDescriptor = sdb } } typ := uint32(windows.FILE_PIPE_REJECT_REMOTE_CLIENTS) if c.MessageMode { typ |= windows.FILE_PIPE_MESSAGE_TYPE } disposition := fs.FILE_OPEN access := fs.GENERIC_READ | fs.GENERIC_WRITE | fs.SYNCHRONIZE if first { disposition = fs.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. access = fs.SYNCHRONIZE } timeout := int64(-50 * 10000) // 50ms var ( h windows.Handle iosb ioStatusBlock ) err = ntCreateNamedPipeFile(&h, access, &oa, &iosb, fs.FILE_SHARE_READ|fs.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} } runtime.KeepAlive(ntPath) return h, nil } func (l *win32PipeListener) makeServerPipe() (*win32File, error) { h, err := makeServerPipeHandle(l.path, nil, &l.config, false) if err != nil { return nil, err } f, err := makeWin32File(h) if err != nil { windows.Close(h) return nil, err } return f, nil } func (l *win32PipeListener) makeConnectedServerPipe() (*win32File, error) { p, err := l.makeServerPipe() if err != nil { return nil, err } // Wait for the client to connect. ch := make(chan error) go func(p *win32File) { ch <- connectPipe(p) }(p) select { case err = <-ch: if err != nil { p.Close() p = nil } case <-l.closeCh: // Abort the connect request by closing the handle. p.Close() p = nil err = <-ch if err == nil || err == ErrFileClosed { //nolint:errorlint // err is Errno err = ErrPipeListenerClosed } } return p, err } func (l *win32PipeListener) listenerRoutine() { closed := false for !closed { select { case <-l.closeCh: closed = true case responseCh := <-l.acceptCh: var ( p *win32File err error ) for { p, err = l.makeConnectedServerPipe() // If the connection was immediately closed by the client, try // again. if err != windows.ERROR_NO_DATA { //nolint:errorlint // err is Errno break } } responseCh <- acceptResponse{p, err} closed = err == ErrPipeListenerClosed //nolint:errorlint // err is Errno } } windows.Close(l.firstHandle) l.firstHandle = 0 // Notify Close() and Accept() callers that the handle has been closed. close(l.doneCh) } // PipeConfig contain configuration for the pipe listener. type PipeConfig struct { // SecurityDescriptor contains a Windows security descriptor in SDDL format. SecurityDescriptor string // MessageMode determines whether the pipe is in byte or message mode. In either // case the pipe is read in byte mode by default. The only practical difference in // this implementation is that CloseWrite() is only supported for message mode pipes; // CloseWrite() is implemented as a zero-byte write, but zero-byte writes are only // transferred to the reader (and returned as io.EOF in this implementation) // when the pipe is in message mode. MessageMode bool // InputBufferSize specifies the size of the input buffer, in bytes. InputBufferSize int32 // OutputBufferSize specifies the size of the output buffer, in bytes. OutputBufferSize int32 } // ListenPipe creates a listener on a Windows named pipe path, e.g. \\.\pipe\mypipe. // The pipe must not already exist. func ListenPipe(path string, c *PipeConfig) (net.Listener, error) { var ( sd []byte err error ) if c == nil { c = &PipeConfig{} } if c.SecurityDescriptor != "" { sd, err = SddlToSecurityDescriptor(c.SecurityDescriptor) if err != nil { return nil, err } } h, err := makeServerPipeHandle(path, sd, c, true) if err != nil { return nil, err } l := &win32PipeListener{ firstHandle: h, path: path, config: *c, acceptCh: make(chan (chan acceptResponse)), closeCh: make(chan int), doneCh: make(chan int), } go l.listenerRoutine() return l, nil } func connectPipe(p *win32File) error { 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 != windows.ERROR_PIPE_CONNECTED { //nolint:errorlint // err is Errno return err } return nil } func (l *win32PipeListener) Accept() (net.Conn, error) { ch := make(chan acceptResponse) select { case l.acceptCh <- ch: response := <-ch err := response.err if err != nil { return nil, err } if l.config.MessageMode { return &win32MessageBytePipe{ win32Pipe: win32Pipe{win32File: response.f, path: l.path}, }, nil } return &win32Pipe{win32File: response.f, path: l.path}, nil case <-l.doneCh: return nil, ErrPipeListenerClosed } } func (l *win32PipeListener) Close() error { select { case l.closeCh <- 1: <-l.doneCh case <-l.doneCh: } return nil } func (l *win32PipeListener) Addr() net.Addr { return pipeAddress(l.path) } go-winio-0.6.2/pipe_test.go000066400000000000000000000275741460531775000156210ustar00rootroot00000000000000//go:build windows // +build windows package winio import ( "bufio" "bytes" "context" "errors" "io" "net" "sync" "testing" "time" "unsafe" "golang.org/x/sys/windows" ) var testPipeName = `\\.\pipe\winiotestpipe` var aLongTimeAgo = time.Unix(1, 0) func TestDialUnknownFailsImmediately(t *testing.T) { _, err := DialPipe(testPipeName, nil) if !errors.Is(err, windows.ERROR_FILE_NOT_FOUND) { t.Fatalf("expected ERROR_FILE_NOT_FOUND got %v", err) } } func TestDialListenerTimesOut(t *testing.T) { l, err := ListenPipe(testPipeName, nil) if err != nil { t.Fatal(err) } defer l.Close() var d = 10 * time.Millisecond _, err = DialPipe(testPipeName, &d) if !errors.Is(err, ErrTimeout) { t.Fatalf("expected ErrTimeout, got %v", err) } } func TestDialContextListenerTimesOut(t *testing.T) { l, err := ListenPipe(testPipeName, nil) if err != nil { t.Fatal(err) } defer l.Close() var d = 10 * time.Millisecond ctx, cancel := context.WithTimeout(context.Background(), d) defer cancel() _, err = DialPipeContext(ctx, testPipeName) if !errors.Is(err, context.DeadlineExceeded) { t.Fatalf("expected context.DeadlineExceeded, got %v", err) } } func TestDialListenerGetsCancelled(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) l, err := ListenPipe(testPipeName, nil) if err != nil { t.Fatal(err) } ch := make(chan error) defer l.Close() go func(ctx context.Context, ch chan error) { _, err := DialPipeContext(ctx, testPipeName) ch <- err }(ctx, ch) time.Sleep(time.Millisecond * 30) cancel() err = <-ch if !errors.Is(err, context.Canceled) { t.Fatalf("expected context.Canceled, got %v", err) } } func TestDialAccessDeniedWithRestrictedSD(t *testing.T) { c := PipeConfig{ SecurityDescriptor: "D:P(A;;0x1200FF;;;WD)", } l, err := ListenPipe(testPipeName, &c) if err != nil { t.Fatal(err) } defer l.Close() _, err = DialPipe(testPipeName, nil) if !errors.Is(err, windows.ERROR_ACCESS_DENIED) { t.Fatalf("expected ERROR_ACCESS_DENIED, got %v", err) } } func getConnection(cfg *PipeConfig) (client net.Conn, server net.Conn, err error) { l, err := ListenPipe(testPipeName, cfg) if err != nil { return nil, nil, err } defer l.Close() type response struct { c net.Conn err error } ch := make(chan response) go func() { c, err := l.Accept() ch <- response{c, err} }() c, err := DialPipe(testPipeName, nil) if err != nil { return client, server, err } r := <-ch if err = r.err; err != nil { c.Close() return nil, nil, err } return c, r.c, nil } func TestReadTimeout(t *testing.T) { c, s, err := getConnection(nil) if err != nil { t.Fatal(err) } defer c.Close() defer s.Close() _ = c.SetReadDeadline(time.Now().Add(10 * time.Millisecond)) buf := make([]byte, 10) _, err = c.Read(buf) if !errors.Is(err, ErrTimeout) { t.Fatalf("expected ErrTimeout, got %v", err) } } func server(l net.Listener, ch chan int) { c, err := l.Accept() if err != nil { panic(err) } rw := bufio.NewReadWriter(bufio.NewReader(c), bufio.NewWriter(c)) s, err := rw.ReadString('\n') if err != nil { panic(err) } _, err = rw.WriteString("got " + s) if err != nil { panic(err) } err = rw.Flush() if err != nil { panic(err) } c.Close() ch <- 1 } func TestFullListenDialReadWrite(t *testing.T) { l, err := ListenPipe(testPipeName, nil) if err != nil { t.Fatal(err) } defer l.Close() ch := make(chan int) go server(l, ch) c, err := DialPipe(testPipeName, nil) if err != nil { t.Fatal(err) } defer c.Close() rw := bufio.NewReadWriter(bufio.NewReader(c), bufio.NewWriter(c)) _, err = rw.WriteString("hello world\n") if err != nil { t.Fatal(err) } err = rw.Flush() if err != nil { t.Fatal(err) } s, err := rw.ReadString('\n') if err != nil { t.Fatal(err) } ms := "got hello world\n" if s != ms { t.Errorf("expected '%s', got '%s'", ms, s) } <-ch } func TestCloseAbortsListen(t *testing.T) { l, err := ListenPipe(testPipeName, nil) if err != nil { t.Fatal(err) } ch := make(chan error) go func() { _, err := l.Accept() ch <- err }() time.Sleep(30 * time.Millisecond) l.Close() err = <-ch if !errors.Is(err, ErrPipeListenerClosed) { t.Fatalf("expected ErrPipeListenerClosed, got %v", err) } } func ensureEOFOnClose(t *testing.T, r io.Reader, w io.Closer) { t.Helper() b := make([]byte, 10) w.Close() n, err := r.Read(b) if n > 0 { t.Errorf("unexpected byte count %d", n) } if err != io.EOF { t.Errorf("expected EOF: %v", err) } } func TestCloseClientEOFServer(t *testing.T) { c, s, err := getConnection(nil) if err != nil { t.Fatal(err) } defer c.Close() defer s.Close() ensureEOFOnClose(t, c, s) } func TestCloseServerEOFClient(t *testing.T) { c, s, err := getConnection(nil) if err != nil { t.Fatal(err) } defer c.Close() defer s.Close() ensureEOFOnClose(t, s, c) } func TestCloseWriteEOF(t *testing.T) { cfg := &PipeConfig{ MessageMode: true, } c, s, err := getConnection(cfg) if err != nil { t.Fatal(err) } defer c.Close() defer s.Close() type closeWriter interface { CloseWrite() error } err = c.(closeWriter).CloseWrite() if err != nil { t.Fatal(err) } b := make([]byte, 10) _, err = s.Read(b) if !errors.Is(err, io.EOF) { t.Fatal(err) } } func TestAcceptAfterCloseFails(t *testing.T) { l, err := ListenPipe(testPipeName, nil) if err != nil { t.Fatal(err) } l.Close() _, err = l.Accept() if !errors.Is(err, ErrPipeListenerClosed) { t.Fatalf("expected ErrPipeListenerClosed, got %v", err) } } func TestDialTimesOutByDefault(t *testing.T) { l, err := ListenPipe(testPipeName, nil) if err != nil { t.Fatal(err) } defer l.Close() _, err = DialPipe(testPipeName, nil) if !errors.Is(err, ErrTimeout) { t.Fatalf("expected ErrTimeout, got %v", err) } } func TestTimeoutPendingRead(t *testing.T) { l, err := ListenPipe(testPipeName, nil) if err != nil { t.Fatal(err) } defer l.Close() serverDone := make(chan struct{}) go func() { s, err := l.Accept() if err != nil { t.Error(err) return } time.Sleep(1 * time.Second) s.Close() close(serverDone) }() client, err := DialPipe(testPipeName, nil) if err != nil { t.Fatal(err) } defer client.Close() clientErr := make(chan error) go func() { buf := make([]byte, 10) _, err = client.Read(buf) clientErr <- err }() time.Sleep(100 * time.Millisecond) // make *sure* the pipe is reading before we set the deadline _ = client.SetReadDeadline(aLongTimeAgo) select { case err = <-clientErr: if !errors.Is(err, ErrTimeout) { t.Fatalf("expected ErrTimeout, got %v", err) } case <-time.After(100 * time.Millisecond): t.Fatalf("timed out while waiting for read to cancel") <-clientErr } <-serverDone } func TestTimeoutPendingWrite(t *testing.T) { l, err := ListenPipe(testPipeName, nil) if err != nil { t.Fatal(err) } defer l.Close() serverDone := make(chan struct{}) go func() { s, err := l.Accept() if err != nil { t.Error(err) return } time.Sleep(1 * time.Second) s.Close() close(serverDone) }() client, err := DialPipe(testPipeName, nil) if err != nil { t.Fatal(err) } defer client.Close() clientErr := make(chan error) go func() { _, err = client.Write([]byte("this should timeout")) clientErr <- err }() time.Sleep(100 * time.Millisecond) // make *sure* the pipe is writing before we set the deadline _ = client.SetWriteDeadline(aLongTimeAgo) select { case err = <-clientErr: if !errors.Is(err, ErrTimeout) { t.Fatalf("expected ErrTimeout, got %v", err) } case <-time.After(100 * time.Millisecond): t.Fatalf("timed out while waiting for write to cancel") <-clientErr } <-serverDone } func TestDisconnectPipe(t *testing.T) { l, err := ListenPipe(testPipeName, nil) if err != nil { t.Fatal(err) } defer l.Close() const testData = "foo" serverDone := make(chan struct{}) go func() { s, err := l.Accept() if err != nil { t.Error(err) return } defer func() { s.Close() close(serverDone) }() if _, err := s.Write([]byte(testData)); err != nil { t.Error(err) return } if err := s.(PipeConn).Flush(); err != nil { t.Error(err) return } if err := s.(PipeConn).Disconnect(); err != nil { t.Error(err) return } }() client, err := DialPipe(testPipeName, nil) if err != nil { t.Fatal(err) } defer client.Close() buf := make([]byte, len(testData)) if _, err = client.Read(buf); err != nil { t.Fatal(err) } dataRead := string(buf) if dataRead != testData { t.Fatalf("incorrect data read %q", dataRead) } if _, err = client.Read(buf); err == nil { t.Fatal("read should fail") } <-serverDone } type CloseWriter interface { CloseWrite() error } func TestEchoWithMessaging(t *testing.T) { c := PipeConfig{ MessageMode: true, // Use message mode so that CloseWrite() is supported InputBufferSize: 65536, // Use 64KB buffers to improve performance OutputBufferSize: 65536, } l, err := ListenPipe(testPipeName, &c) if err != nil { t.Fatal(err) } defer l.Close() listenerDone := make(chan bool) clientDone := make(chan bool) go func() { // server echo conn, e := l.Accept() if e != nil { 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() close(listenerDone) }() timeout := 1 * time.Second client, err := DialPipe(testPipeName, &timeout) if err != nil { t.Fatal(err) } defer client.Close() go func() { // client read back bytes := make([]byte, 2) n, e := client.Read(bytes) if e != nil { t.Error(err) return } if n != 2 { t.Errorf("expected 2 bytes, got %v", n) return } close(clientDone) }() payload := make([]byte, 2) payload[0] = 0 payload[1] = 1 n, err := client.Write(payload) if err != nil { t.Fatal(err) } if n != 2 { t.Fatalf("expected 2 bytes, got %v", n) } _ = client.(CloseWriter).CloseWrite() <-listenerDone <-clientDone } func TestConnectRace(t *testing.T) { l, err := ListenPipe(testPipeName, nil) if err != nil { t.Fatal(err) } defer l.Close() go func() { for { s, err := l.Accept() if errors.Is(err, ErrPipeListenerClosed) { return } if err != nil { t.Error(err) return } s.Close() } }() for i := 0; i < 1000; i++ { c, err := DialPipe(testPipeName, nil) if err != nil { t.Fatal(err) } c.Close() } } func TestMessageReadMode(t *testing.T) { var wg sync.WaitGroup defer wg.Wait() l, err := ListenPipe(testPipeName, &PipeConfig{MessageMode: true}) if err != nil { t.Fatal(err) } defer l.Close() msg := ([]byte)("hello world") wg.Add(1) go func() { defer wg.Done() s, err := l.Accept() if err != nil { t.Error(err) return } _, err = s.Write(msg) if err != nil { t.Error(err) return } s.Close() }() c, err := DialPipe(testPipeName, nil) if err != nil { t.Fatal(err) } defer c.Close() setNamedPipeHandleState := windows.NewLazyDLL("kernel32.dll").NewProc("SetNamedPipeHandleState") p, ok := c.(*win32MessageBytePipe) if !ok { t.Fatalf("expected type %T; got %T", new(win32MessageBytePipe), c) } 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) } ch := make([]byte, 1) var vmsg []byte for { n, err := c.Read(ch) if err == io.EOF { //nolint:errorlint break } if err != nil { t.Fatal(err) } if n != 1 { t.Fatal("expected 1: ", n) } vmsg = append(vmsg, ch[0]) } if !bytes.Equal(msg, vmsg) { t.Fatalf("expected %s: %s", msg, vmsg) } } func TestListenConnectRace(t *testing.T) { for i := 0; i < 50 && !t.Failed(); i++ { var wg sync.WaitGroup wg.Add(1) go func() { c, err := DialPipe(testPipeName, nil) if err == nil { c.Close() } wg.Done() }() s, err := ListenPipe(testPipeName, nil) if err != nil { t.Error(i, err) } else { s.Close() } wg.Wait() } } go-winio-0.6.2/pkg/000077500000000000000000000000001460531775000140405ustar00rootroot00000000000000go-winio-0.6.2/pkg/bindfilter/000077500000000000000000000000001460531775000161625ustar00rootroot00000000000000go-winio-0.6.2/pkg/bindfilter/bind_filter.go000066400000000000000000000231711460531775000207760ustar00rootroot00000000000000//go:build windows // +build windows package bindfilter import ( "bytes" "encoding/binary" "errors" "fmt" "os" "path/filepath" "strings" "unsafe" "golang.org/x/sys/windows" ) //go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go ./bind_filter.go //sys bfSetupFilter(jobHandle windows.Handle, flags uint32, virtRootPath string, virtTargetPath string, virtExceptions **uint16, virtExceptionPathCount uint32) (hr error) = bindfltapi.BfSetupFilter? //sys bfRemoveMapping(jobHandle windows.Handle, virtRootPath string) (hr error) = bindfltapi.BfRemoveMapping? //sys bfGetMappings(flags uint32, jobHandle windows.Handle, virtRootPath *uint16, sid *windows.SID, bufferSize *uint32, outBuffer *byte) (hr error) = bindfltapi.BfGetMappings? // BfSetupFilter flags. See: // https://github.com/microsoft/BuildXL/blob/a6dce509f0d4f774255e5fbfb75fa6d5290ed163/Public/Src/Utilities/Native/Processes/Windows/NativeContainerUtilities.cs#L193-L240 // //nolint:revive // var-naming: ALL_CAPS const ( BINDFLT_FLAG_READ_ONLY_MAPPING uint32 = 0x00000001 // Tells bindflt to fail mapping with STATUS_INVALID_PARAMETER if a mapping produces // multiple targets. BINDFLT_FLAG_NO_MULTIPLE_TARGETS uint32 = 0x00000040 ) //nolint:revive // var-naming: ALL_CAPS const ( BINDFLT_GET_MAPPINGS_FLAG_VOLUME uint32 = 0x00000001 BINDFLT_GET_MAPPINGS_FLAG_SILO uint32 = 0x00000002 BINDFLT_GET_MAPPINGS_FLAG_USER uint32 = 0x00000004 ) // ApplyFileBinding creates a global mount of the source in root, with an optional // read only flag. // The bind filter allows us to create mounts of directories and volumes. By default it allows // us to mount multiple sources inside a single root, acting as an overlay. Files from the // second source will superscede the first source that was mounted. // This function disables this behavior and sets the BINDFLT_FLAG_NO_MULTIPLE_TARGETS flag // on the mount. func ApplyFileBinding(root, source string, readOnly bool) error { // The parent directory needs to exist for the bind to work. MkdirAll stats and // returns nil if the directory exists internally so we should be fine to mkdirall // every time. if err := os.MkdirAll(filepath.Dir(root), 0); err != nil { return err } if strings.Contains(source, "Volume{") && !strings.HasSuffix(source, "\\") { // Add trailing slash to volumes, otherwise we get an error when binding it to // a folder. source = source + "\\" } flags := BINDFLT_FLAG_NO_MULTIPLE_TARGETS if readOnly { flags |= BINDFLT_FLAG_READ_ONLY_MAPPING } // Set the job handle to 0 to create a global mount. if err := bfSetupFilter( 0, flags, root, source, nil, 0, ); err != nil { return fmt.Errorf("failed to bind target %q to root %q: %w", source, root, err) } return nil } // RemoveFileBinding removes a mount from the root path. func RemoveFileBinding(root string) error { if err := bfRemoveMapping(0, root); err != nil { return fmt.Errorf("removing file binding: %w", err) } return nil } // GetBindMappings returns a list of bind mappings that have their root on a // particular volume. The volumePath parameter can be any path that exists on // a volume. For example, if a number of mappings are created in C:\ProgramData\test, // to get a list of those mappings, the volumePath parameter would have to be set to // C:\ or the VOLUME_NAME_GUID notation of C:\ (\\?\Volume{GUID}\), or any child // path that exists. func GetBindMappings(volumePath string) ([]BindMapping, error) { rootPtr, err := windows.UTF16PtrFromString(volumePath) if err != nil { return nil, err } flags := BINDFLT_GET_MAPPINGS_FLAG_VOLUME // allocate a large buffer for results var outBuffSize uint32 = 256 * 1024 buf := make([]byte, outBuffSize) if err := bfGetMappings(flags, 0, rootPtr, nil, &outBuffSize, &buf[0]); err != nil { return nil, err } if outBuffSize < 12 { return nil, fmt.Errorf("invalid buffer returned") } result := buf[:outBuffSize] // The first 12 bytes are the three uint32 fields in getMappingsResponseHeader{} headerBuffer := result[:12] // The alternative to using unsafe and casting it to the above defined structures, is to manually // parse the fields. Not too terrible, but not sure it'd worth the trouble. header := *(*getMappingsResponseHeader)(unsafe.Pointer(&headerBuffer[0])) if header.MappingCount == 0 { // no mappings return []BindMapping{}, nil } mappingsBuffer := result[12 : int(unsafe.Sizeof(mappingEntry{}))*int(header.MappingCount)] // Get a pointer to the first mapping in the slice mappingsPointer := (*mappingEntry)(unsafe.Pointer(&mappingsBuffer[0])) // Get slice of mappings mappings := unsafe.Slice(mappingsPointer, header.MappingCount) mappingEntries := make([]BindMapping, header.MappingCount) for i := 0; i < int(header.MappingCount); i++ { bindMapping, err := getBindMappingFromBuffer(result, mappings[i]) if err != nil { return nil, fmt.Errorf("fetching bind mappings: %w", err) } mappingEntries[i] = bindMapping } return mappingEntries, nil } // mappingEntry holds information about where in the response buffer we can // find information about the virtual root (the mount point) and the targets (sources) // that get mounted, as well as the flags used to bind the targets to the virtual root. type mappingEntry struct { VirtRootLength uint32 VirtRootOffset uint32 Flags uint32 NumberOfTargets uint32 TargetEntriesOffset uint32 } type mappingTargetEntry struct { TargetRootLength uint32 TargetRootOffset uint32 } // getMappingsResponseHeader represents the first 12 bytes of the BfGetMappings() response. // It gives us the size of the buffer, the status of the call and the number of mappings. // A response type getMappingsResponseHeader struct { Size uint32 Status uint32 MappingCount uint32 } type BindMapping struct { MountPoint string Flags uint32 Targets []string } func decodeEntry(buffer []byte) (string, error) { name := make([]uint16, len(buffer)/2) err := binary.Read(bytes.NewReader(buffer), binary.LittleEndian, &name) if err != nil { return "", fmt.Errorf("decoding name: %w", err) } return windows.UTF16ToString(name), nil } func getTargetsFromBuffer(buffer []byte, offset, count int) ([]string, error) { if len(buffer) < offset+count*6 { return nil, fmt.Errorf("invalid buffer") } targets := make([]string, count) for i := 0; i < count; i++ { entryBuf := buffer[offset+i*8 : offset+i*8+8] tgt := *(*mappingTargetEntry)(unsafe.Pointer(&entryBuf[0])) if len(buffer) < int(tgt.TargetRootOffset)+int(tgt.TargetRootLength) { return nil, fmt.Errorf("invalid buffer") } decoded, err := decodeEntry(buffer[tgt.TargetRootOffset : tgt.TargetRootOffset+tgt.TargetRootLength]) if err != nil { return nil, fmt.Errorf("decoding name: %w", err) } decoded, err = getFinalPath(decoded) if err != nil { return nil, fmt.Errorf("fetching final path: %w", err) } targets[i] = decoded } return targets, nil } func getFinalPath(pth string) (string, error) { // BfGetMappings returns VOLUME_NAME_NT paths like \Device\HarddiskVolume2\ProgramData. // These can be accessed by prepending \\.\GLOBALROOT to the path. We use this to get the // DOS paths for these files. if strings.HasPrefix(pth, `\Device`) { pth = `\\.\GLOBALROOT` + pth } han, err := openPath(pth) if err != nil { return "", fmt.Errorf("fetching file handle: %w", err) } defer func() { _ = windows.CloseHandle(han) }() buf := make([]uint16, 100) var flags uint32 = 0x0 for { n, err := windows.GetFinalPathNameByHandle(han, &buf[0], uint32(len(buf)), flags) if err != nil { // if we mounted a volume that does not also have a drive letter assigned, attempting to // fetch the VOLUME_NAME_DOS will fail with os.ErrNotExist. Attempt to get the VOLUME_NAME_GUID. if errors.Is(err, os.ErrNotExist) && flags != 0x1 { flags = 0x1 continue } return "", fmt.Errorf("getting final path name: %w", err) } if n < uint32(len(buf)) { break } buf = make([]uint16, n) } finalPath := windows.UTF16ToString(buf) // We got VOLUME_NAME_DOS, we need to strip away some leading slashes. // Leave unchanged if we ended up requesting VOLUME_NAME_GUID if len(finalPath) > 4 && finalPath[:4] == `\\?\` && flags == 0x0 { finalPath = finalPath[4:] if len(finalPath) > 3 && finalPath[:3] == `UNC` { // return path like \\server\share\... finalPath = `\` + finalPath[3:] } } return finalPath, nil } func getBindMappingFromBuffer(buffer []byte, entry mappingEntry) (BindMapping, error) { if len(buffer) < int(entry.VirtRootOffset)+int(entry.VirtRootLength) { return BindMapping{}, fmt.Errorf("invalid buffer") } src, err := decodeEntry(buffer[entry.VirtRootOffset : entry.VirtRootOffset+entry.VirtRootLength]) if err != nil { return BindMapping{}, fmt.Errorf("decoding entry: %w", err) } targets, err := getTargetsFromBuffer(buffer, int(entry.TargetEntriesOffset), int(entry.NumberOfTargets)) if err != nil { return BindMapping{}, fmt.Errorf("fetching targets: %w", err) } src, err = getFinalPath(src) if err != nil { return BindMapping{}, fmt.Errorf("fetching final path: %w", err) } return BindMapping{ Flags: entry.Flags, Targets: targets, MountPoint: src, }, nil } func openPath(path string) (windows.Handle, error) { u16, err := windows.UTF16PtrFromString(path) if err != nil { return 0, err } h, err := windows.CreateFile( u16, 0, windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE, nil, windows.OPEN_EXISTING, windows.FILE_FLAG_BACKUP_SEMANTICS, // Needed to open a directory handle. 0) if err != nil { return 0, &os.PathError{ Op: "CreateFile", Path: path, Err: err, } } return h, nil } go-winio-0.6.2/pkg/bindfilter/bind_filter_test.go000066400000000000000000000166441460531775000220440ustar00rootroot00000000000000//go:build windows // +build windows package bindfilter import ( "errors" "fmt" "os" "path/filepath" "strings" "testing" "golang.org/x/sys/windows" ) func TestApplyFileBinding(t *testing.T) { requireElevated(t) source := t.TempDir() destination := t.TempDir() fileName := "testFile.txt" srcFile := filepath.Join(source, fileName) dstFile := filepath.Join(destination, fileName) err := ApplyFileBinding(destination, source, false) if err != nil { t.Fatal(err) } defer removeFileBinding(t, destination) data := []byte("bind filter test") if err := os.WriteFile(srcFile, data, 0600); err != nil { t.Fatal(err) } readData, err := os.ReadFile(dstFile) if err != nil { t.Fatal(err) } if string(readData) != string(data) { t.Fatalf("source and destination file contents differ. Expected: %s, got: %s", string(data), string(readData)) } // Remove the file on the mount point. The mount is not read-only, this should work. if err := os.Remove(dstFile); err != nil { t.Fatalf("failed to remove file from mount point: %s", err) } // Check that it's gone from the source as well. if _, err := os.Stat(srcFile); err == nil { t.Fatalf("expected file %s to be gone but is not", srcFile) } } func removeFileBinding(t *testing.T, mountpoint string) { t.Helper() if err := RemoveFileBinding(mountpoint); err != nil { t.Logf("failed to remove file binding from %s: %q", mountpoint, err) } } func TestApplyFileBindingReadOnly(t *testing.T) { requireElevated(t) source := t.TempDir() destination := t.TempDir() fileName := "testFile.txt" srcFile := filepath.Join(source, fileName) dstFile := filepath.Join(destination, fileName) err := ApplyFileBinding(destination, source, true) if err != nil { t.Fatal(err) } defer removeFileBinding(t, destination) data := []byte("bind filter test") if err := os.WriteFile(srcFile, data, 0600); err != nil { t.Fatal(err) } readData, err := os.ReadFile(dstFile) if err != nil { t.Fatal(err) } if string(readData) != string(data) { t.Fatalf("source and destination file contents differ. Expected: %s, got: %s", string(data), string(readData)) } // Attempt to remove the file on the mount point err = os.Remove(dstFile) if err == nil { t.Fatalf("should not be able to remove a file from a read-only mount") } if !errors.Is(err, os.ErrPermission) { t.Fatalf("expected an access denied error, got: %q", err) } // Attempt to write on the read-only mount point. err = os.WriteFile(dstFile, []byte("something else"), 0600) if err == nil { t.Fatalf("should not be able to overwrite a file from a read-only mount") } if !errors.Is(err, os.ErrPermission) { t.Fatalf("expected an access denied error, got: %q", err) } } func TestEnsureOnlyOneTargetCanBeMounted(t *testing.T) { requireElevated(t) requireBuild(t, RS5+1) // support added after RS5 source := t.TempDir() secondarySource := t.TempDir() destination := t.TempDir() err := ApplyFileBinding(destination, source, false) if err != nil { t.Fatal(err) } defer removeFileBinding(t, destination) err = ApplyFileBinding(destination, secondarySource, false) if err == nil { removeFileBinding(t, destination) t.Fatalf("we should not be able to mount multiple targets in the same destination") } } func checkSourceIsMountedOnDestination(src, dst string) (bool, error) { mappings, err := GetBindMappings(dst) if err != nil { return false, err } found := false // There may be pre-existing mappings on the system. for _, mapping := range mappings { if mapping.MountPoint == dst { found = true if len(mapping.Targets) != 1 { return false, fmt.Errorf("expected only one target, got: %s", strings.Join(mapping.Targets, ", ")) } if mapping.Targets[0] != src { return false, fmt.Errorf("expected target to be %s, got %s", src, mapping.Targets[0]) } break } } return found, nil } func TestGetBindMappings(t *testing.T) { requireElevated(t) requireBuild(t, RS5+1) // support added after RS5 // GetBindMappings will expand short paths like ADMINI~1 and PROGRA~1 to their // full names. In order to properly match the names later, we expand them here. srcShort := t.TempDir() source, err := getFinalPath(srcShort) if err != nil { t.Fatalf("failed to get long path") } dstShort := t.TempDir() destination, err := getFinalPath(dstShort) if err != nil { t.Fatalf("failed to get long path") } err = ApplyFileBinding(destination, source, false) if err != nil { t.Fatal(err) } defer removeFileBinding(t, destination) hasMapping, err := checkSourceIsMountedOnDestination(source, destination) if err != nil { t.Fatal(err) } if !hasMapping { t.Fatalf("expected to find %s mounted on %s, but could not", source, destination) } } func TestRemoveFileBinding(t *testing.T) { requireElevated(t) srcShort := t.TempDir() source, err := getFinalPath(srcShort) if err != nil { t.Fatalf("failed to get long path") } dstShort := t.TempDir() destination, err := getFinalPath(dstShort) if err != nil { t.Fatalf("failed to get long path") } fileName := "testFile.txt" srcFile := filepath.Join(source, fileName) dstFile := filepath.Join(destination, fileName) data := []byte("bind filter test") if err := os.WriteFile(srcFile, data, 0600); err != nil { t.Fatal(err) } err = ApplyFileBinding(destination, source, false) if err != nil { t.Fatal(err) } if _, err := os.Stat(dstFile); err != nil { removeFileBinding(t, destination) t.Fatalf("expected to find %s, but could not", dstFile) } if err := RemoveFileBinding(destination); err != nil { t.Fatal(err) } if _, err := os.Stat(dstFile); err == nil { t.Fatalf("expected %s to be gone, but it is not", dstFile) } } func TestGetBindMappingsSymlinks(t *testing.T) { requireElevated(t) requireBuild(t, RS5+1) // support added after RS5 srcShort := t.TempDir() sourceNested := filepath.Join(srcShort, "source") if err := os.MkdirAll(sourceNested, 0600); err != nil { t.Fatalf("failed to create folder: %s", err) } simlinkSource := filepath.Join(srcShort, "symlink") if err := os.Symlink(sourceNested, simlinkSource); err != nil { t.Fatalf("failed to create symlink: %s", err) } // We'll need the long form of the source folder, as we expect bfSetupFilter() // to resolve the symlink and create a mapping to the actual source the symlink // points to. source, err := getFinalPath(sourceNested) if err != nil { t.Fatalf("failed to get long path") } dstShort := t.TempDir() destination, err := getFinalPath(dstShort) if err != nil { t.Fatalf("failed to get long path") } // Use the symlink as a source for the mapping. err = ApplyFileBinding(destination, simlinkSource, false) if err != nil { t.Fatal(err) } defer removeFileBinding(t, destination) // We expect the mapping to point to the folder the symlink points to, not to the // actual symlink. hasMapping, err := checkSourceIsMountedOnDestination(source, destination) if err != nil { t.Fatal(err) } if !hasMapping { t.Fatalf("expected to find %s mounted on %s, but could not", source, destination) } } func requireElevated(tb testing.TB) { tb.Helper() if !windows.GetCurrentProcessToken().IsElevated() { tb.Skip("requires elevated privileges") } } const RS5 = 17763 //todo: also check that `bindfltapi.dll` exists // require current build to be >= build func requireBuild(tb testing.TB, build uint32) { tb.Helper() _, _, b := windows.RtlGetNtVersionNumbers() if b < build { tb.Skipf("requires build %d+; current build is %d", build, b) } } go-winio-0.6.2/pkg/bindfilter/zsyscall_windows.go000066400000000000000000000061421460531775000221320ustar00rootroot00000000000000//go:build windows // Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT. package bindfilter import ( "syscall" "unsafe" "golang.org/x/sys/windows" ) var _ unsafe.Pointer // Do the interface allocations only once for common // Errno values. const ( errnoERROR_IO_PENDING = 997 ) var ( errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) errERROR_EINVAL error = syscall.EINVAL ) // errnoErr returns common boxed Errno values, to prevent // allocations at runtime. func errnoErr(e syscall.Errno) error { switch e { case 0: return errERROR_EINVAL case errnoERROR_IO_PENDING: return errERROR_IO_PENDING } return e } var ( modbindfltapi = windows.NewLazySystemDLL("bindfltapi.dll") procBfGetMappings = modbindfltapi.NewProc("BfGetMappings") procBfRemoveMapping = modbindfltapi.NewProc("BfRemoveMapping") procBfSetupFilter = modbindfltapi.NewProc("BfSetupFilter") ) func bfGetMappings(flags uint32, jobHandle windows.Handle, virtRootPath *uint16, sid *windows.SID, bufferSize *uint32, outBuffer *byte) (hr error) { hr = procBfGetMappings.Find() if hr != nil { return } r0, _, _ := syscall.SyscallN(procBfGetMappings.Addr(), uintptr(flags), uintptr(jobHandle), uintptr(unsafe.Pointer(virtRootPath)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(bufferSize)), uintptr(unsafe.Pointer(outBuffer))) if int32(r0) < 0 { if r0&0x1fff0000 == 0x00070000 { r0 &= 0xffff } hr = syscall.Errno(r0) } return } func bfRemoveMapping(jobHandle windows.Handle, virtRootPath string) (hr error) { var _p0 *uint16 _p0, hr = syscall.UTF16PtrFromString(virtRootPath) if hr != nil { return } return _bfRemoveMapping(jobHandle, _p0) } func _bfRemoveMapping(jobHandle windows.Handle, virtRootPath *uint16) (hr error) { hr = procBfRemoveMapping.Find() if hr != nil { return } r0, _, _ := syscall.SyscallN(procBfRemoveMapping.Addr(), uintptr(jobHandle), uintptr(unsafe.Pointer(virtRootPath))) if int32(r0) < 0 { if r0&0x1fff0000 == 0x00070000 { r0 &= 0xffff } hr = syscall.Errno(r0) } return } func bfSetupFilter(jobHandle windows.Handle, flags uint32, virtRootPath string, virtTargetPath string, virtExceptions **uint16, virtExceptionPathCount uint32) (hr error) { var _p0 *uint16 _p0, hr = syscall.UTF16PtrFromString(virtRootPath) if hr != nil { return } var _p1 *uint16 _p1, hr = syscall.UTF16PtrFromString(virtTargetPath) if hr != nil { return } return _bfSetupFilter(jobHandle, flags, _p0, _p1, virtExceptions, virtExceptionPathCount) } func _bfSetupFilter(jobHandle windows.Handle, flags uint32, virtRootPath *uint16, virtTargetPath *uint16, virtExceptions **uint16, virtExceptionPathCount uint32) (hr error) { hr = procBfSetupFilter.Find() if hr != nil { return } r0, _, _ := syscall.SyscallN(procBfSetupFilter.Addr(), uintptr(jobHandle), uintptr(flags), uintptr(unsafe.Pointer(virtRootPath)), uintptr(unsafe.Pointer(virtTargetPath)), uintptr(unsafe.Pointer(virtExceptions)), uintptr(virtExceptionPathCount)) if int32(r0) < 0 { if r0&0x1fff0000 == 0x00070000 { r0 &= 0xffff } hr = syscall.Errno(r0) } return } go-winio-0.6.2/pkg/etw/000077500000000000000000000000001460531775000146375ustar00rootroot00000000000000go-winio-0.6.2/pkg/etw/doc.go000066400000000000000000000007411460531775000157350ustar00rootroot00000000000000// 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 go-winio-0.6.2/pkg/etw/eventdata.go000066400000000000000000000041261460531775000171440ustar00rootroot00000000000000//go:build windows // +build windows package etw import ( "bytes" "encoding/binary" "golang.org/x/sys/windows" ) // eventData maintains a buffer which builds up the data for an ETW event. It // needs to be paired with EventMetadata which describes the event. type eventData struct { buffer bytes.Buffer } // 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) 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) } // writeInt8 appends a int8 to the buffer. func (ed *eventData) writeInt8(value int8) { _ = 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) } // writeInt32 appends a int32 to the buffer. func (ed *eventData) writeInt32(value int32) { _ = 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) } // writeUint8 appends a uint8 to the buffer. func (ed *eventData) writeUint8(value uint8) { _ = ed.buffer.WriteByte(value) } // writeUint16 appends a uint16 to the buffer. func (ed *eventData) writeUint16(value uint16) { _ = 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) } // writeUint64 appends a uint64 to the buffer. func (ed *eventData) writeUint64(value uint64) { _ = binary.Write(&ed.buffer, binary.LittleEndian, value) } // writeFiletime appends a FILETIME to the buffer. func (ed *eventData) writeFiletime(value windows.Filetime) { _ = binary.Write(&ed.buffer, binary.LittleEndian, value) } go-winio-0.6.2/pkg/etw/eventdatadescriptor.go000066400000000000000000000011561460531775000212430ustar00rootroot00000000000000//go:build windows package etw import ( "unsafe" ) type eventDataDescriptorType uint8 const ( eventDataDescriptorTypeUserData eventDataDescriptorType = iota eventDataDescriptorTypeEventMetadata eventDataDescriptorTypeProviderMetadata ) type eventDataDescriptor struct { ptr ptr64 size uint32 dataType eventDataDescriptorType _ uint8 _ uint16 } func newEventDataDescriptor(dataType eventDataDescriptorType, buffer []byte) eventDataDescriptor { return eventDataDescriptor{ ptr: ptr64{ptr: unsafe.Pointer(&buffer[0])}, size: uint32(len(buffer)), dataType: dataType, } } go-winio-0.6.2/pkg/etw/eventdescriptor.go000066400000000000000000000056461460531775000204210ustar00rootroot00000000000000package etw import "fmt" // Channel represents the ETW logging channel that is used. It can be used by // event consumers to give an event special treatment. type Channel uint8 const ( // ChannelTraceLogging is the default channel for TraceLogging events. It is // not required to be used for TraceLogging, but will prevent decoding // issues for these events on older operating systems. ChannelTraceLogging Channel = 11 ) // Level represents the ETW logging level. There are several predefined levels // that are commonly used, but technically anything from 0-255 is allowed. // Lower levels indicate more important events, and 0 indicates an event that // will always be collected. type Level uint8 var _ fmt.Stringer = Level(0) // Predefined ETW log levels from winmeta.xml in the Windows SDK. // //go:generate go run golang.org/x/tools/cmd/stringer -type=Level -trimprefix=Level const ( LevelAlways Level = iota LevelCritical LevelError LevelWarning LevelInfo LevelVerbose ) // Opcode represents the operation that the event indicates is being performed. type Opcode uint8 var _ fmt.Stringer = Opcode(0) // Predefined ETW opcodes from winmeta.xml in the Windows SDK. // //go:generate go run golang.org/x/tools/cmd/stringer -type=Opcode -trimprefix=Opcode const ( // OpcodeInfo indicates an informational event. OpcodeInfo Opcode = iota // OpcodeStart indicates the start of an operation. OpcodeStart // OpcodeStop indicates the end of an operation. OpcodeStop // OpcodeDCStart indicates the start of a provider capture state operation. OpcodeDCStart // OpcodeDCStop indicates the end of a provider capture state operation. OpcodeDCStop ) // eventDescriptor represents various metadata for an ETW event. type eventDescriptor struct { id uint16 version uint8 channel Channel level Level opcode Opcode task uint16 keyword uint64 } // newEventDescriptor returns an EventDescriptor initialized for use with // TraceLogging. func newEventDescriptor() *eventDescriptor { // Standard TraceLogging events default to the TraceLogging channel, and // verbose level. return &eventDescriptor{ channel: ChannelTraceLogging, level: LevelVerbose, } } // identity returns the identity of the event. If the identity is not 0, it // 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) } // setIdentity sets the identity of the event. If the identity is not 0, it // 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) } go-winio-0.6.2/pkg/etw/eventmetadata.go000066400000000000000000000134521460531775000200150ustar00rootroot00000000000000//go:build windows package etw import ( "bytes" "encoding/binary" ) // inType indicates the type of data contained in the ETW event. 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 inTypeANSIString inTypeInt8 inTypeUint8 inTypeInt16 inTypeUint16 inTypeInt32 inTypeUint32 inTypeInt64 inTypeUint64 inTypeFloat inTypeDouble inTypeBool32 inTypeBinary inTypeGUID inTypePointerUnsupported inTypeFileTime inTypeSystemTime inTypeSID inTypeHexInt32 inTypeHexInt64 inTypeCountedString inTypeCountedANSIString inTypeStruct inTypeCountedBinary inTypeCountedArray inType = 32 inTypeArray inType = 64 ) // outType specifies a hint to the event decoder for how the value should be // formatted. 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. outTypeDefault outType = iota outTypeNoPrint outTypeString outTypeBoolean outTypeHex outTypePID outTypeTID outTypePort outTypeIPv4 outTypeIPv6 outTypeSocketAddress outTypeXML outTypeJSON outTypeWin32Error outTypeNTStatus outTypeHResult outTypeFileTime outTypeSigned outTypeUnsigned outTypeUTF8 outType = 35 outTypePKCS7WithTypeInfo outType = 36 outTypeCodePointer outType = 37 outTypeDateTimeUTC outType = 38 ) // eventMetadata maintains a buffer which builds up the metadata for an ETW // event. It needs to be paired with EventData which describes the event. type eventMetadata struct { buffer bytes.Buffer } // 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) 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())) return em.buffer.Bytes() } // 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 em.writeTags(tags) em.buffer.WriteString(name) em.buffer.WriteByte(0) // Null terminator for name } func (em *eventMetadata) writeFieldInner(name string, inType inType, outType outType, tags uint32, arrSize uint16) { em.buffer.WriteString(name) em.buffer.WriteByte(0) // Null terminator for name if outType == outTypeDefault && tags == 0 { em.buffer.WriteByte(byte(inType)) } else { em.buffer.WriteByte(byte(inType | 128)) if tags == 0 { em.buffer.WriteByte(byte(outType)) } else { em.buffer.WriteByte(byte(outType | 128)) em.writeTags(tags) } } if arrSize != 0 { _ = binary.Write(&em.buffer, binary.LittleEndian, arrSize) } } // writeTags writes out the tags value to the event metadata. Tags is a 28-bit // value, interpreted as bit flags, which are only relevant to the event // consumer. The event consumer may choose to attribute special meaning to tags // (e.g. 0x4 could mean the field contains PII). Tags are written as a series of // bytes, each containing 7 bits of tag value, with the high bit set if there is // more tag data in the following byte. This allows for a more compact // representation when not all of the tag bits are needed. func (em *eventMetadata) writeTags(tags uint32) { // Only use the top 28 bits of the tags value. tags &= 0xfffffff for { // Tags are written with the most significant bits (e.g. 21-27) first. val := tags >> 21 if tags&0x1fffff == 0 { // If there is no more data to write after this, write this value // without the high bit set, and return. em.buffer.WriteByte(byte(val & 0x7f)) return } em.buffer.WriteByte(byte(val | 0x80)) tags <<= 7 } } // 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 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) } // 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) } // writeStruct writes the metadata for a nested struct to the buffer. The struct // contains the next N fields in the metadata, where N is specified by the // fieldCount argument. func (em *eventMetadata) writeStruct(name string, fieldCount uint8, tags uint32) { em.writeFieldInner(name, inTypeStruct, outType(fieldCount), tags, 0) } go-winio-0.6.2/pkg/etw/eventopt.go000066400000000000000000000040471460531775000170370ustar00rootroot00000000000000//go:build windows // +build windows package etw import ( "github.com/Microsoft/go-winio/pkg/guid" ) type eventOptions struct { descriptor *eventDescriptor activityID guid.GUID relatedActivityID guid.GUID tags uint32 } // EventOpt defines the option function type that can be passed to // Provider.WriteEvent to specify general event options, such as level and // keyword. type EventOpt func(options *eventOptions) // WithEventOpts returns the variadic arguments as a single slice. func WithEventOpts(opts ...EventOpt) []EventOpt { return opts } // WithLevel specifies the level of the event to be written. func WithLevel(level Level) EventOpt { return func(options *eventOptions) { options.descriptor.level = level } } // WithKeyword specifies the keywords of the event to be written. Multiple uses // of this option are OR'd together. func WithKeyword(keyword uint64) EventOpt { return func(options *eventOptions) { options.descriptor.keyword |= keyword } } // WithChannel specifies the channel of the event to be written. func WithChannel(channel Channel) EventOpt { return func(options *eventOptions) { options.descriptor.channel = channel } } // WithOpcode specifies the opcode of the event to be written. func WithOpcode(opcode Opcode) EventOpt { return func(options *eventOptions) { options.descriptor.opcode = opcode } } // WithTags specifies the tags of the event to be written. Tags is a 28-bit // value (top 4 bits are ignored) which are interpreted by the event consumer. func WithTags(newTags uint32) EventOpt { return func(options *eventOptions) { options.tags |= newTags } } // WithActivityID specifies the activity ID of the event to be written. func WithActivityID(activityID guid.GUID) EventOpt { return func(options *eventOptions) { options.activityID = activityID } } // WithRelatedActivityID specifies the parent activity ID of the event to be written. func WithRelatedActivityID(activityID guid.GUID) EventOpt { return func(options *eventOptions) { options.relatedActivityID = activityID } } go-winio-0.6.2/pkg/etw/fieldopt.go000066400000000000000000000353341460531775000170040ustar00rootroot00000000000000//go:build windows // +build windows package etw import ( "fmt" "math" "reflect" "time" "unsafe" "golang.org/x/sys/windows" ) // FieldOpt defines the option function type that can be passed to // Provider.WriteEvent to add fields to the event. type FieldOpt func(em *eventMetadata, ed *eventData) // WithFields returns the variadic arguments as a single slice. func WithFields(opts ...FieldOpt) []FieldOpt { return opts } // BoolField adds a single bool field to the event. func BoolField(name string, value bool) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeField(name, inTypeUint8, outTypeBoolean, 0) bool8 := uint8(0) if value { bool8 = uint8(1) } ed.writeUint8(bool8) } } // BoolArray adds an array of bool to the event. func BoolArray(name string, values []bool) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeArray(name, inTypeUint8, outTypeBoolean, 0) ed.writeUint16(uint16(len(values))) for _, v := range values { bool8 := uint8(0) if v { bool8 = uint8(1) } ed.writeUint8(bool8) } } } // StringField adds a single string field to the event. func StringField(name string, value string) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeField(name, inTypeANSIString, outTypeUTF8, 0) ed.writeString(value) } } // JSONStringField adds a JSON-encoded string field to the event. func JSONStringField(name string, value string) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeField(name, inTypeANSIString, outTypeJSON, 0) ed.writeString(value) } } // StringArray adds an array of string to the event. func StringArray(name string, values []string) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeArray(name, inTypeANSIString, outTypeUTF8, 0) ed.writeUint16(uint16(len(values))) for _, v := range values { ed.writeString(v) } } } // IntField adds a single int field to the event. func IntField(name string, value int) FieldOpt { switch unsafe.Sizeof(value) { case 4: return Int32Field(name, int32(value)) case 8: return Int64Field(name, int64(value)) default: panic("Unsupported int size") } } // IntArray adds an array of int to the event. func IntArray(name string, values []int) FieldOpt { inType := inTypeNull var writeItem func(*eventData, int) switch unsafe.Sizeof(values[0]) { case 4: inType = inTypeInt32 writeItem = func(ed *eventData, item int) { ed.writeInt32(int32(item)) } case 8: inType = inTypeInt64 writeItem = func(ed *eventData, item int) { ed.writeInt64(int64(item)) } default: panic("Unsupported int size") } return func(em *eventMetadata, ed *eventData) { em.writeArray(name, inType, outTypeDefault, 0) ed.writeUint16(uint16(len(values))) for _, v := range values { writeItem(ed, v) } } } // Int8Field adds a single int8 field to the event. func Int8Field(name string, value int8) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeField(name, inTypeInt8, outTypeDefault, 0) ed.writeInt8(value) } } // Int8Array adds an array of int8 to the event. func Int8Array(name string, values []int8) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeArray(name, inTypeInt8, outTypeDefault, 0) ed.writeUint16(uint16(len(values))) for _, v := range values { ed.writeInt8(v) } } } // Int16Field adds a single int16 field to the event. func Int16Field(name string, value int16) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeField(name, inTypeInt16, outTypeDefault, 0) ed.writeInt16(value) } } // Int16Array adds an array of int16 to the event. func Int16Array(name string, values []int16) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeArray(name, inTypeInt16, outTypeDefault, 0) ed.writeUint16(uint16(len(values))) for _, v := range values { ed.writeInt16(v) } } } // Int32Field adds a single int32 field to the event. func Int32Field(name string, value int32) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeField(name, inTypeInt32, outTypeDefault, 0) ed.writeInt32(value) } } // Int32Array adds an array of int32 to the event. func Int32Array(name string, values []int32) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeArray(name, inTypeInt32, outTypeDefault, 0) ed.writeUint16(uint16(len(values))) for _, v := range values { ed.writeInt32(v) } } } // Int64Field adds a single int64 field to the event. func Int64Field(name string, value int64) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeField(name, inTypeInt64, outTypeDefault, 0) ed.writeInt64(value) } } // Int64Array adds an array of int64 to the event. func Int64Array(name string, values []int64) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeArray(name, inTypeInt64, outTypeDefault, 0) ed.writeUint16(uint16(len(values))) for _, v := range values { ed.writeInt64(v) } } } // UintField adds a single uint field to the event. func UintField(name string, value uint) FieldOpt { switch unsafe.Sizeof(value) { case 4: return Uint32Field(name, uint32(value)) case 8: return Uint64Field(name, uint64(value)) default: panic("Unsupported uint size") } } // UintArray adds an array of uint to the event. func UintArray(name string, values []uint) FieldOpt { inType := inTypeNull var writeItem func(*eventData, uint) switch unsafe.Sizeof(values[0]) { case 4: inType = inTypeUint32 writeItem = func(ed *eventData, item uint) { ed.writeUint32(uint32(item)) } case 8: inType = inTypeUint64 writeItem = func(ed *eventData, item uint) { ed.writeUint64(uint64(item)) } default: panic("Unsupported uint size") } return func(em *eventMetadata, ed *eventData) { em.writeArray(name, inType, outTypeDefault, 0) ed.writeUint16(uint16(len(values))) for _, v := range values { writeItem(ed, v) } } } // Uint8Field adds a single uint8 field to the event. func Uint8Field(name string, value uint8) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeField(name, inTypeUint8, outTypeDefault, 0) ed.writeUint8(value) } } // Uint8Array adds an array of uint8 to the event. func Uint8Array(name string, values []uint8) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeArray(name, inTypeUint8, outTypeDefault, 0) ed.writeUint16(uint16(len(values))) for _, v := range values { ed.writeUint8(v) } } } // Uint16Field adds a single uint16 field to the event. func Uint16Field(name string, value uint16) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeField(name, inTypeUint16, outTypeDefault, 0) ed.writeUint16(value) } } // Uint16Array adds an array of uint16 to the event. func Uint16Array(name string, values []uint16) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeArray(name, inTypeUint16, outTypeDefault, 0) ed.writeUint16(uint16(len(values))) for _, v := range values { ed.writeUint16(v) } } } // Uint32Field adds a single uint32 field to the event. func Uint32Field(name string, value uint32) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeField(name, inTypeUint32, outTypeDefault, 0) ed.writeUint32(value) } } // Uint32Array adds an array of uint32 to the event. func Uint32Array(name string, values []uint32) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeArray(name, inTypeUint32, outTypeDefault, 0) ed.writeUint16(uint16(len(values))) for _, v := range values { ed.writeUint32(v) } } } // Uint64Field adds a single uint64 field to the event. func Uint64Field(name string, value uint64) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeField(name, inTypeUint64, outTypeDefault, 0) ed.writeUint64(value) } } // Uint64Array adds an array of uint64 to the event. func Uint64Array(name string, values []uint64) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeArray(name, inTypeUint64, outTypeDefault, 0) ed.writeUint16(uint16(len(values))) for _, v := range values { ed.writeUint64(v) } } } // UintptrField adds a single uintptr field to the event. func UintptrField(name string, value uintptr) FieldOpt { inType := inTypeNull var writeItem func(*eventData, uintptr) switch unsafe.Sizeof(value) { case 4: inType = inTypeHexInt32 writeItem = func(ed *eventData, item uintptr) { ed.writeUint32(uint32(item)) } case 8: inType = inTypeHexInt64 writeItem = func(ed *eventData, item uintptr) { ed.writeUint64(uint64(item)) } default: panic("Unsupported uintptr size") } return func(em *eventMetadata, ed *eventData) { em.writeField(name, inType, outTypeDefault, 0) writeItem(ed, value) } } // UintptrArray adds an array of uintptr to the event. func UintptrArray(name string, values []uintptr) FieldOpt { inType := inTypeNull var writeItem func(*eventData, uintptr) switch unsafe.Sizeof(values[0]) { case 4: inType = inTypeHexInt32 writeItem = func(ed *eventData, item uintptr) { ed.writeUint32(uint32(item)) } case 8: inType = inTypeHexInt64 writeItem = func(ed *eventData, item uintptr) { ed.writeUint64(uint64(item)) } default: panic("Unsupported uintptr size") } return func(em *eventMetadata, ed *eventData) { em.writeArray(name, inType, outTypeDefault, 0) ed.writeUint16(uint16(len(values))) for _, v := range values { writeItem(ed, v) } } } // Float32Field adds a single float32 field to the event. func Float32Field(name string, value float32) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeField(name, inTypeFloat, outTypeDefault, 0) ed.writeUint32(math.Float32bits(value)) } } // Float32Array adds an array of float32 to the event. func Float32Array(name string, values []float32) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeArray(name, inTypeFloat, outTypeDefault, 0) ed.writeUint16(uint16(len(values))) for _, v := range values { ed.writeUint32(math.Float32bits(v)) } } } // Float64Field adds a single float64 field to the event. func Float64Field(name string, value float64) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeField(name, inTypeDouble, outTypeDefault, 0) ed.writeUint64(math.Float64bits(value)) } } // Float64Array adds an array of float64 to the event. func Float64Array(name string, values []float64) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeArray(name, inTypeDouble, outTypeDefault, 0) ed.writeUint16(uint16(len(values))) for _, v := range values { ed.writeUint64(math.Float64bits(v)) } } } // Struct adds a nested struct to the event, the FieldOpts in the opts argument // are used to specify the fields of the struct. func Struct(name string, opts ...FieldOpt) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeStruct(name, uint8(len(opts)), 0) for _, opt := range opts { opt(em, ed) } } } // Time adds a time to the event. func Time(name string, value time.Time) FieldOpt { return func(em *eventMetadata, ed *eventData) { em.writeField(name, inTypeFileTime, outTypeDateTimeUTC, 0) ed.writeFiletime(windows.NsecToFiletime(value.UTC().UnixNano())) } } // Currently, we support logging basic builtin types (int, string, etc), slices // of basic builtin types, error, types derived from the basic types (e.g. "type // foo int"), and structs (recursively logging their fields). We do not support // slices of derived types (e.g. "[]foo"). // // For types that we don't support, the value is formatted via fmt.Sprint, and // we also log a message that the type is unsupported along with the formatted // type. The intent of this is to make it easier to see which types are not // supported in traces, so we can evaluate adding support for more types in the // future. func SmartField(name string, v interface{}) FieldOpt { switch v := v.(type) { case bool: return BoolField(name, v) case []bool: return BoolArray(name, v) case string: return StringField(name, v) case []string: return StringArray(name, v) case int: return IntField(name, v) case []int: return IntArray(name, v) case int8: return Int8Field(name, v) case []int8: return Int8Array(name, v) case int16: return Int16Field(name, v) case []int16: return Int16Array(name, v) case int32: return Int32Field(name, v) case []int32: return Int32Array(name, v) case int64: return Int64Field(name, v) case []int64: return Int64Array(name, v) case uint: return UintField(name, v) case []uint: return UintArray(name, v) case uint8: return Uint8Field(name, v) case []uint8: return Uint8Array(name, v) case uint16: return Uint16Field(name, v) case []uint16: return Uint16Array(name, v) case uint32: return Uint32Field(name, v) case []uint32: return Uint32Array(name, v) case uint64: return Uint64Field(name, v) case []uint64: return Uint64Array(name, v) case uintptr: return UintptrField(name, v) case []uintptr: return UintptrArray(name, v) case float32: return Float32Field(name, v) case []float32: return Float32Array(name, v) case float64: return Float64Field(name, v) case []float64: return Float64Array(name, v) case error: return StringField(name, v.Error()) case time.Time: return Time(name, v) default: switch rv := reflect.ValueOf(v); rv.Kind() { case reflect.Bool: return SmartField(name, rv.Bool()) case reflect.Int: return SmartField(name, int(rv.Int())) case reflect.Int8: return SmartField(name, int8(rv.Int())) case reflect.Int16: return SmartField(name, int16(rv.Int())) case reflect.Int32: return SmartField(name, int32(rv.Int())) case reflect.Int64: return SmartField(name, int64(rv.Int())) //nolint:unconvert // make look consistent case reflect.Uint: return SmartField(name, uint(rv.Uint())) case reflect.Uint8: return SmartField(name, uint8(rv.Uint())) case reflect.Uint16: return SmartField(name, uint16(rv.Uint())) case reflect.Uint32: return SmartField(name, uint32(rv.Uint())) case reflect.Uint64: 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())) case reflect.String: return SmartField(name, rv.String()) case reflect.Struct: fields := make([]FieldOpt, 0, rv.NumField()) for i := 0; i < rv.NumField(); i++ { field := rv.Field(i) if field.CanInterface() { fields = append(fields, SmartField(name, field.Interface())) } } 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: } } return StringField(name, fmt.Sprintf("(Unsupported: %T) %v", v, v)) } go-winio-0.6.2/pkg/etw/level_string.go000066400000000000000000000013321460531775000176620ustar00rootroot00000000000000// Code generated by "stringer -type=Level -trimprefix=Level"; DO NOT EDIT. package etw import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[LevelAlways-0] _ = x[LevelCritical-1] _ = x[LevelError-2] _ = x[LevelWarning-3] _ = x[LevelInfo-4] _ = x[LevelVerbose-5] } const _Level_name = "AlwaysCriticalErrorWarningInfoVerbose" var _Level_index = [...]uint8{0, 6, 14, 19, 26, 30, 37} func (i Level) String() string { if i >= Level(len(_Level_index)-1) { return "Level(" + strconv.FormatInt(int64(i), 10) + ")" } return _Level_name[_Level_index[i]:_Level_index[i+1]] } go-winio-0.6.2/pkg/etw/newprovider.go000066400000000000000000000045541460531775000175420ustar00rootroot00000000000000//go:build windows && (amd64 || arm64 || 386) // +build windows // +build amd64 arm64 386 package etw import ( "bytes" "encoding/binary" "unsafe" "github.com/Microsoft/go-winio/pkg/guid" "golang.org/x/sys/windows" ) // NewProviderWithOptions creates and registers a new ETW provider, allowing // the provider ID and Group to be manually specified. This is most useful when // there is an existing provider ID that must be used to conform to existing // diagnostic infrastructure. func NewProviderWithOptions(name string, options ...ProviderOpt) (provider *Provider, err error) { var opts providerOpts for _, opt := range options { opt(&opts) } if opts.id == (guid.GUID{}) { opts.id = providerIDFromName(name) } providerCallbackOnce.Do(func() { globalProviderCallback = windows.NewCallback(providerCallbackAdapter) }) provider = providers.newProvider() defer func(provider *Provider) { if err != nil { providers.removeProvider(provider) } }(provider) provider.ID = opts.id provider.callback = opts.callback if err := eventRegister((*windows.GUID)(&provider.ID), globalProviderCallback, uintptr(provider.index), &provider.handle); err != nil { return nil, err } 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 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) metadata.WriteString(name) metadata.WriteByte(0) // Null terminator for name _, _ = 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() if err := eventSetInformation( provider.handle, eventInfoClassProviderSetTraits, uintptr(unsafe.Pointer(&provider.metadata[0])), uint32(len(provider.metadata)), ); err != nil { return nil, err } return provider, nil } go-winio-0.6.2/pkg/etw/newprovider_unsupported.go000066400000000000000000000003721460531775000222040ustar00rootroot00000000000000//go:build windows && arm // +build windows,arm package etw // NewProviderWithID returns a nil provider on unsupported platforms. func NewProviderWithOptions(name string, options ...ProviderOpt) (provider *Provider, err error) { return nil, nil } go-winio-0.6.2/pkg/etw/opcode_string.go000066400000000000000000000012771460531775000200340ustar00rootroot00000000000000// Code generated by "stringer -type=Opcode -trimprefix=Opcode"; DO NOT EDIT. package etw import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[OpcodeInfo-0] _ = x[OpcodeStart-1] _ = x[OpcodeStop-2] _ = x[OpcodeDCStart-3] _ = x[OpcodeDCStop-4] } const _Opcode_name = "InfoStartStopDCStartDCStop" var _Opcode_index = [...]uint8{0, 4, 9, 13, 20, 26} func (i Opcode) String() string { if i >= Opcode(len(_Opcode_index)-1) { return "Opcode(" + strconv.FormatInt(int64(i), 10) + ")" } return _Opcode_name[_Opcode_index[i]:_Opcode_index[i+1]] } go-winio-0.6.2/pkg/etw/provider.go000066400000000000000000000221241460531775000170210ustar00rootroot00000000000000//go:build windows // +build windows package etw import ( "crypto/sha1" //nolint:gosec // not used for secure application "encoding/binary" "strings" "unicode/utf16" "github.com/Microsoft/go-winio/pkg/guid" "golang.org/x/sys/windows" ) // Provider represents an ETW event provider. It is identified by a provider // name and ID (GUID), which should always have a 1:1 mapping to each other // (e.g. don't use multiple provider names with the same ID, or vice versa). type Provider struct { ID guid.GUID handle providerHandle metadata []byte callback EnableCallback index uint enabled bool level Level keywordAny uint64 keywordAll uint64 } // String returns the `provider`.ID as a string. func (provider *Provider) String() string { if provider == nil { return "" } return provider.ID.String() } type providerHandle uint64 // ProviderState informs the provider EnableCallback what action is being // performed. type ProviderState uint32 const ( // ProviderStateDisable indicates the provider is being disabled. ProviderStateDisable ProviderState = iota // ProviderStateEnable indicates the provider is being enabled. ProviderStateEnable // ProviderStateCaptureState indicates the provider is having its current // state snap-shotted. ProviderStateCaptureState ) type eventInfoClass uint32 //nolint:deadcode,varcheck // keep unused constants for potential future use const ( eventInfoClassProviderBinaryTrackInfo eventInfoClass = iota eventInfoClassProviderSetReserved1 eventInfoClassProviderSetTraits eventInfoClassProviderUseDescriptorType ) // EnableCallback is the form of the callback function that receives provider // 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, ) { provider := providers.getProvider(uint(i)) switch state { case ProviderStateCaptureState: case ProviderStateDisable: provider.enabled = false case ProviderStateEnable: provider.enabled = true provider.level = level provider.keywordAny = matchAnyKeyword provider.keywordAll = matchAllKeyword } if provider.callback != nil { provider.callback(sourceID, state, level, matchAnyKeyword, matchAllKeyword, filterData) } } // providerIDFromName generates a provider ID based on the provider name. It // uses the same algorithm as used by .NET's EventSource class, which is based // on RFC 4122. More information on the algorithm can be found here: // https://blogs.msdn.microsoft.com/dcook/2015/09/08/etw-provider-names-and-guids/ // // 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. func providerIDFromName(name string) guid.GUID { 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)))) sum := buffer.Sum(nil) sum[7] = (sum[7] & 0xf) | 0x50 a := [16]byte{} copy(a[:], sum) return guid.FromWindowsArray(a) } type providerOpts struct { callback EnableCallback id guid.GUID group guid.GUID } // ProviderOpt allows the caller to specify provider options to // NewProviderWithOptions. type ProviderOpt func(*providerOpts) // 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. func WithID(id guid.GUID) ProviderOpt { return func(opts *providerOpts) { opts.id = id } } // WithGroup is used to provide a provider group option to NewProviderWithOptions. func WithGroup(group guid.GUID) ProviderOpt { return func(opts *providerOpts) { opts.group = group } } // NewProviderWithID creates and registers a new ETW provider, allowing the // provider ID to be manually specified. This is most useful when there is an // existing provider ID that must be used to conform to existing diagnostic // infrastructure. func NewProviderWithID(name string, id guid.GUID, callback EnableCallback) (provider *Provider, err error) { return NewProviderWithOptions(name, WithID(id), WithCallback(callback)) } // NewProvider creates and registers a new ETW provider. The provider ID is // generated based on the provider name. func NewProvider(name string, callback EnableCallback) (provider *Provider, err error) { return NewProviderWithOptions(name, WithCallback(callback)) } // Close unregisters the provider. func (provider *Provider) Close() error { if provider == nil { return nil } providers.removeProvider(provider) return eventUnregister(provider.handle) } // IsEnabled calls IsEnabledForLevelAndKeywords with LevelAlways and all // keywords set. func (provider *Provider) IsEnabled() bool { return provider.IsEnabledForLevelAndKeywords(LevelAlways, ^uint64(0)) } // IsEnabledForLevel calls IsEnabledForLevelAndKeywords with the specified level // and all keywords set. func (provider *Provider) IsEnabledForLevel(level Level) bool { return provider.IsEnabledForLevelAndKeywords(level, ^uint64(0)) } // IsEnabledForLevelAndKeywords allows event producer code to check if there are // any event sessions that are interested in an event, based on the event level // and keywords. Although this check happens automatically in the ETW // infrastructure, it can be useful to check if an event will actually be // consumed before doing expensive work to build the event data. func (provider *Provider) IsEnabledForLevelAndKeywords(level Level, keywords uint64) bool { if provider == nil { return false } if !provider.enabled { return false } // ETW automatically sets the level to 255 if it is specified as 0, so we // don't need to worry about the level=0 (all events) case. if level > provider.level { return false } if keywords != 0 && (keywords&provider.keywordAny == 0 || keywords&provider.keywordAll != provider.keywordAll) { return false } return true } // WriteEvent writes a single ETW event from the provider. The event is // constructed based on the EventOpt and FieldOpt values that are passed as // opts. func (provider *Provider) WriteEvent(name string, eventOpts []EventOpt, fieldOpts []FieldOpt) error { if provider == nil { return nil } options := eventOptions{descriptor: newEventDescriptor()} em := &eventMetadata{} ed := &eventData{} // We need to evaluate the EventOpts first since they might change tags, and // we write out the tags before evaluating FieldOpts. for _, opt := range eventOpts { opt(&options) } if !provider.IsEnabledForLevelAndKeywords(options.descriptor.level, options.descriptor.keyword) { return nil } em.writeEventHeader(name, options.tags) for _, opt := range fieldOpts { opt(em, ed) } // Don't pass a data blob if there is no event data. There will always be // event metadata (e.g. for the name) so we don't need to do this check for // the metadata. dataBlobs := [][]byte{} if len(ed.toBytes()) > 0 { dataBlobs = [][]byte{ed.toBytes()} } 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 // less abstracted than WriteEvent, and presents a fairly direct interface to // the event writing functionality. It expects a series of event metadata and // event data blobs to be passed in, which must conform to the TraceLogging // schema. The functions on EventMetadata and EventData can help with creating // these blobs. The blobs of each type are effectively concatenated together by // the ETW infrastructure. func (provider *Provider) writeEventRaw( descriptor *eventDescriptor, activityID guid.GUID, 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)) for _, blob := range metadataBlobs { dataDescriptors = append(dataDescriptors, newEventDataDescriptor(eventDataDescriptorTypeEventMetadata, blob)) } for _, blob := range dataBlobs { dataDescriptors = append(dataDescriptors, newEventDataDescriptor(eventDataDescriptorTypeUserData, blob)) } return eventWriteTransfer(provider.handle, descriptor, (*windows.GUID)(&activityID), (*windows.GUID)(&relatedActivityID), dataDescriptorCount, &dataDescriptors[0]) } go-winio-0.6.2/pkg/etw/provider_test.go000066400000000000000000000016071460531775000200630ustar00rootroot00000000000000//go:build windows // +build windows package etw import ( "testing" "github.com/Microsoft/go-winio/pkg/guid" ) func mustGUIDFromString(t *testing.T, s string) guid.GUID { t.Helper() g, err := guid.FromString(s) if err != nil { t.Fatal(err) } return g } func Test_ProviderIDFromName(t *testing.T) { type testCase struct { name string g guid.GUID } testCases := []testCase{ {"wincni", mustGUIDFromString(t, "c822b598-f4cc-5a72-7933-ce2a816d033f")}, {"Moby", mustGUIDFromString(t, "6996f090-c5de-5082-a81e-5841acc3a635")}, {"ContainerD", mustGUIDFromString(t, "2acb92c0-eb9b-571a-69cf-8f3410f383ad")}, {"Microsoft.Virtualization.RunHCS", mustGUIDFromString(t, "0B52781F-B24D-5685-DDF6-69830ED40EC3")}, } for _, tc := range testCases { g := providerIDFromName(tc.name) if g != tc.g { t.Fatalf("Incorrect provider GUID.\nExpected: %s\nActual: %s", tc.g, g) } } } go-winio-0.6.2/pkg/etw/providerglobal.go000066400000000000000000000021001460531775000201720ustar00rootroot00000000000000//go:build windows // +build windows package etw import ( "sync" ) // Because the provider callback function needs to be able to access the // provider data when it is invoked by ETW, we need to keep provider data stored // in a global map based on an index. The index is passed as the callback // context to ETW. type providerMap struct { m map[uint]*Provider i uint lock sync.Mutex } var providers = providerMap{ m: make(map[uint]*Provider), } func (p *providerMap) newProvider() *Provider { p.lock.Lock() defer p.lock.Unlock() i := p.i p.i++ provider := &Provider{ index: i, } p.m[i] = provider return provider } func (p *providerMap) removeProvider(provider *Provider) { p.lock.Lock() defer p.lock.Unlock() delete(p.m, provider.index) } func (p *providerMap) getProvider(index uint) *Provider { p.lock.Lock() defer p.lock.Unlock() 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 go-winio-0.6.2/pkg/etw/ptr64_32.go000066400000000000000000000006331460531775000164530ustar00rootroot00000000000000//go:build windows && (386 || arm) // +build windows // +build 386 arm package etw import ( "unsafe" ) // byteptr64 defines a struct containing a pointer. The struct is guaranteed to // be 64 bits, regardless of the actual size of a pointer on the platform. This // is intended for use with certain Windows APIs that expect a pointer as a // ULONGLONG. type ptr64 struct { ptr unsafe.Pointer _ uint32 } go-winio-0.6.2/pkg/etw/ptr64_64.go000066400000000000000000000006271460531775000164630ustar00rootroot00000000000000//go:build windows && (amd64 || arm64) // +build windows // +build amd64 arm64 package etw import ( "unsafe" ) // byteptr64 defines a struct containing a pointer. The struct is guaranteed to // be 64 bits, regardless of the actual size of a pointer on the platform. This // is intended for use with certain Windows APIs that expect a pointer as a // ULONGLONG. type ptr64 struct { ptr unsafe.Pointer } go-winio-0.6.2/pkg/etw/sample/000077500000000000000000000000001460531775000161205ustar00rootroot00000000000000go-winio-0.6.2/pkg/etw/sample/main_other.go000066400000000000000000000001051460531775000205700ustar00rootroot00000000000000//go:build !windows // +build !windows package main func main() {} go-winio-0.6.2/pkg/etw/sample/main_windows.go000066400000000000000000000044541460531775000211540ustar00rootroot00000000000000//go:build windows // +build windows // Shows a sample usage of the ETW logging package. package main import ( "bufio" "fmt" "log" "os" "runtime" "github.com/Microsoft/go-winio/pkg/etw" "github.com/Microsoft/go-winio/pkg/guid" ) func callback(sourceID guid.GUID, state etw.ProviderState, level etw.Level, matchAnyKeyword uint64, matchAllKeyword uint64, filterData uintptr) { fmt.Printf("Callback: isEnabled=%d, level=%d, matchAnyKeyword=%d\n", state, level, matchAnyKeyword) } func main() { fmt.Printf("Running on %s/%s\n", runtime.GOOS, runtime.GOARCH) group, err := guid.FromString("12341234-abcd-abcd-abcd-123412341234") if err != nil { log.Fatal(err) } provider, err := etw.NewProvider("TestProvider", callback) if err != nil { log.Fatal(err) } defer func() { if err := provider.Close(); err != nil { log.Fatal(err) } }() providerWithGroup, err := etw.NewProviderWithOptions("TestProviderWithGroup", etw.WithGroup(group), etw.WithCallback(callback)) if err != nil { log.Fatal(err) } defer func() { if err := providerWithGroup.Close(); err != nil { log.Fatal(err) } }() fmt.Printf("Provider ID: %s\n", provider) fmt.Printf("Provider w/ Group ID: %s\n", providerWithGroup) reader := bufio.NewReader(os.Stdin) fmt.Println("Press enter to log events") reader.ReadString('\n') if err := provider.WriteEvent( "TestEvent", etw.WithEventOpts( etw.WithLevel(etw.LevelInfo), etw.WithKeyword(0x140), ), etw.WithFields( etw.StringField("TestField", "Foo"), etw.StringField("TestField2", "Bar"), etw.Struct("TestStruct", etw.StringField("Field1", "Value1"), etw.StringField("Field2", "Value2")), etw.StringArray("TestArray", []string{ "Item1", "Item2", "Item3", "Item4", "Item5", })), ); err != nil { log.Fatal(err) } if err := providerWithGroup.WriteEvent( "TestEventWithGroup", etw.WithEventOpts( etw.WithLevel(etw.LevelInfo), etw.WithKeyword(0x140), ), etw.WithFields( etw.StringField("TestField", "Foo"), etw.StringField("TestField2", "Bar"), etw.Struct("TestStruct", etw.StringField("Field1", "Value1"), etw.StringField("Field2", "Value2")), etw.StringArray("TestArray", []string{ "Item1", "Item2", "Item3", "Item4", "Item5", })), ); err != nil { log.Fatal(err) } } go-winio-0.6.2/pkg/etw/syscall.go000066400000000000000000000026301460531775000166410ustar00rootroot00000000000000//go:build windows package etw //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 //sys eventUnregister_64(providerHandle providerHandle) (win32err error) = advapi32.EventUnregister //sys eventWriteTransfer_64(providerHandle providerHandle, descriptor *eventDescriptor, activityID *windows.GUID, relatedActivityID *windows.GUID, dataDescriptorCount uint32, dataDescriptors *eventDataDescriptor) (win32err error) = advapi32.EventWriteTransfer //sys eventSetInformation_64(providerHandle providerHandle, class eventInfoClass, information uintptr, length uint32) (win32err error) = advapi32.EventSetInformation //sys eventUnregister_32(providerHandle_low uint32, providerHandle_high uint32) (win32err error) = advapi32.EventUnregister //sys eventWriteTransfer_32(providerHandle_low uint32, providerHandle_high uint32, descriptor *eventDescriptor, activityID *windows.GUID, relatedActivityID *windows.GUID, dataDescriptorCount uint32, dataDescriptors *eventDataDescriptor) (win32err error) = advapi32.EventWriteTransfer //sys eventSetInformation_32(providerHandle_low uint32, providerHandle_high uint32, class eventInfoClass, information uintptr, length uint32) (win32err error) = advapi32.EventSetInformation go-winio-0.6.2/pkg/etw/wrapper_32.go000066400000000000000000000041401460531775000171510ustar00rootroot00000000000000//go:build windows && (386 || arm) // +build windows // +build 386 arm package etw import ( "github.com/Microsoft/go-winio/pkg/guid" "golang.org/x/sys/windows" ) func low(v providerHandle) uint32 { return uint32(v & 0xffffffff) } func high(v providerHandle) uint32 { return low(v >> 32) } func eventUnregister(providerHandle providerHandle) (win32err error) { return eventUnregister_32(low(providerHandle), high(providerHandle)) } func eventWriteTransfer( providerHandle providerHandle, descriptor *eventDescriptor, activityID *windows.GUID, relatedActivityID *windows.GUID, dataDescriptorCount uint32, dataDescriptors *eventDataDescriptor) (win32err error) { return eventWriteTransfer_32( low(providerHandle), high(providerHandle), descriptor, activityID, relatedActivityID, dataDescriptorCount, dataDescriptors) } func eventSetInformation( providerHandle providerHandle, class eventInfoClass, information uintptr, length uint32) (win32err error) { return eventSetInformation_32( low(providerHandle), high(providerHandle), class, information, length) } // providerCallbackAdapter acts as the first-level callback from the C/ETW side // 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. // For x86, the matchAny and matchAll keywords need to be assembled from two // 32-bit integers, because the max size of an argument is uintptr, but those // two arguments are actually 64-bit integers. func providerCallbackAdapter(sourceID *guid.GUID, state uint32, level uint32, matchAnyKeyword_low uint32, matchAnyKeyword_high uint32, matchAllKeyword_low uint32, matchAllKeyword_high uint32, filterData uintptr, i uintptr) uintptr { matchAnyKeyword := uint64(matchAnyKeyword_high)<<32 | uint64(matchAnyKeyword_low) matchAllKeyword := uint64(matchAllKeyword_high)<<32 | uint64(matchAllKeyword_low) providerCallback(*sourceID, ProviderState(state), Level(level), uint64(matchAnyKeyword), uint64(matchAllKeyword), filterData, i) return 0 } go-winio-0.6.2/pkg/etw/wrapper_64.go000066400000000000000000000027661460531775000171720ustar00rootroot00000000000000//go:build windows && (amd64 || arm64) // +build windows // +build amd64 arm64 package etw import ( "github.com/Microsoft/go-winio/pkg/guid" "golang.org/x/sys/windows" ) func eventUnregister(providerHandle providerHandle) (win32err error) { return eventUnregister_64(providerHandle) } func eventWriteTransfer( providerHandle providerHandle, descriptor *eventDescriptor, activityID *windows.GUID, relatedActivityID *windows.GUID, dataDescriptorCount uint32, dataDescriptors *eventDataDescriptor) (win32err error) { return eventWriteTransfer_64( providerHandle, descriptor, activityID, relatedActivityID, dataDescriptorCount, dataDescriptors) } func eventSetInformation( providerHandle providerHandle, class eventInfoClass, information uintptr, length uint32) (win32err error) { return eventSetInformation_64( providerHandle, class, information, length) } // providerCallbackAdapter acts as the first-level callback from the C/ETW side // 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) return 0 } go-winio-0.6.2/pkg/etw/zsyscall_windows.go000066400000000000000000000073541460531775000206150ustar00rootroot00000000000000//go:build windows // Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT. package etw import ( "syscall" "unsafe" "golang.org/x/sys/windows" ) var _ unsafe.Pointer // Do the interface allocations only once for common // Errno values. const ( errnoERROR_IO_PENDING = 997 ) var ( errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) errERROR_EINVAL error = syscall.EINVAL ) // errnoErr returns common boxed Errno values, to prevent // allocations at runtime. func errnoErr(e syscall.Errno) error { switch e { case 0: return errERROR_EINVAL case errnoERROR_IO_PENDING: return errERROR_IO_PENDING } return e } var ( modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") procEventRegister = modadvapi32.NewProc("EventRegister") procEventSetInformation = modadvapi32.NewProc("EventSetInformation") procEventUnregister = modadvapi32.NewProc("EventUnregister") procEventWriteTransfer = modadvapi32.NewProc("EventWriteTransfer") ) func eventRegister(providerId *windows.GUID, callback uintptr, callbackContext uintptr, providerHandle *providerHandle) (win32err error) { r0, _, _ := syscall.SyscallN(procEventRegister.Addr(), uintptr(unsafe.Pointer(providerId)), uintptr(callback), uintptr(callbackContext), uintptr(unsafe.Pointer(providerHandle))) if r0 != 0 { win32err = syscall.Errno(r0) } return } func eventSetInformation_64(providerHandle providerHandle, class eventInfoClass, information uintptr, length uint32) (win32err error) { r0, _, _ := syscall.SyscallN(procEventSetInformation.Addr(), uintptr(providerHandle), uintptr(class), uintptr(information), uintptr(length)) if r0 != 0 { win32err = syscall.Errno(r0) } return } func eventSetInformation_32(providerHandle_low uint32, providerHandle_high uint32, class eventInfoClass, information uintptr, length uint32) (win32err error) { r0, _, _ := syscall.SyscallN(procEventSetInformation.Addr(), uintptr(providerHandle_low), uintptr(providerHandle_high), uintptr(class), uintptr(information), uintptr(length)) if r0 != 0 { win32err = syscall.Errno(r0) } return } func eventUnregister_64(providerHandle providerHandle) (win32err error) { r0, _, _ := syscall.SyscallN(procEventUnregister.Addr(), uintptr(providerHandle)) if r0 != 0 { win32err = syscall.Errno(r0) } return } func eventUnregister_32(providerHandle_low uint32, providerHandle_high uint32) (win32err error) { r0, _, _ := syscall.SyscallN(procEventUnregister.Addr(), uintptr(providerHandle_low), uintptr(providerHandle_high)) if r0 != 0 { win32err = syscall.Errno(r0) } return } func eventWriteTransfer_64(providerHandle providerHandle, descriptor *eventDescriptor, activityID *windows.GUID, relatedActivityID *windows.GUID, dataDescriptorCount uint32, dataDescriptors *eventDataDescriptor) (win32err error) { r0, _, _ := syscall.SyscallN(procEventWriteTransfer.Addr(), uintptr(providerHandle), uintptr(unsafe.Pointer(descriptor)), uintptr(unsafe.Pointer(activityID)), uintptr(unsafe.Pointer(relatedActivityID)), uintptr(dataDescriptorCount), uintptr(unsafe.Pointer(dataDescriptors))) if r0 != 0 { win32err = syscall.Errno(r0) } return } func eventWriteTransfer_32(providerHandle_low uint32, providerHandle_high uint32, descriptor *eventDescriptor, activityID *windows.GUID, relatedActivityID *windows.GUID, dataDescriptorCount uint32, dataDescriptors *eventDataDescriptor) (win32err error) { r0, _, _ := syscall.SyscallN(procEventWriteTransfer.Addr(), uintptr(providerHandle_low), uintptr(providerHandle_high), uintptr(unsafe.Pointer(descriptor)), uintptr(unsafe.Pointer(activityID)), uintptr(unsafe.Pointer(relatedActivityID)), uintptr(dataDescriptorCount), uintptr(unsafe.Pointer(dataDescriptors))) if r0 != 0 { win32err = syscall.Errno(r0) } return } go-winio-0.6.2/pkg/etwlogrus/000077500000000000000000000000001460531775000160735ustar00rootroot00000000000000go-winio-0.6.2/pkg/etwlogrus/HookTest.wprp000066400000000000000000000012151460531775000205440ustar00rootroot00000000000000 go-winio-0.6.2/pkg/etwlogrus/hook.go000066400000000000000000000107771460531775000173760ustar00rootroot00000000000000//go:build windows // +build windows package etwlogrus import ( "errors" "sort" "github.com/sirupsen/logrus" "github.com/Microsoft/go-winio/pkg/etw" ) 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. type HookOpt func(*Hook) error // Hook is a Logrus hook which logs received events to ETW. type Hook struct { provider *etw.Provider closeProvider bool // allows setting the entry name getName func(*logrus.Entry) string // returns additional options to add to the event 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. 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. func NewHookFromProvider(provider *etw.Provider, opts ...HookOpt) (*Hook, error) { opts = append(opts, WithExistingETWProvider(provider)) return NewHookFromOpts(opts...) } // NewHookFromOpts creates a new hook with the provided options. // An error is returned if the hook does not have a valid provider. func NewHookFromOpts(opts ...HookOpt) (*Hook, error) { h := defaultHook() for _, o := range opts { if err := o(h); err != nil { return nil, err } } return h, h.validate() } func defaultHook() *Hook { h := &Hook{} return h } func (h *Hook) validate() error { if h.provider == nil { return ErrNoProvider } return nil } // Levels returns the set of levels that this hook wants to receive log entries // for. func (*Hook) Levels() []logrus.Level { return logrus.AllLevels } var logrusToETWLevelMap = map[logrus.Level]etw.Level{ logrus.PanicLevel: etw.LevelAlways, logrus.FatalLevel: etw.LevelCritical, logrus.ErrorLevel: etw.LevelError, logrus.WarnLevel: etw.LevelWarning, logrus.InfoLevel: etw.LevelInfo, logrus.DebugLevel: etw.LevelVerbose, logrus.TraceLevel: etw.LevelVerbose, } // Fire receives each Logrus entry as it is logged, and logs it to ETW. func (h *Hook) Fire(e *logrus.Entry) error { // Logrus defines more levels than ETW typically uses, but analysis is // easiest when using a consistent set of levels across ETW providers, so we // map the Logrus levels to ETW levels. level := logrusToETWLevelMap[e.Level] if !h.provider.IsEnabledForLevel(level) { return nil } name := defaultEventName if h.getName != nil { if n := h.getName(e); n != "" { name = n } } // extra room for two more options in addition to log level to avoid repeated reallocations // if the user also provides options opts := make([]etw.EventOpt, 0, 3) opts = append(opts, etw.WithLevel(level)) if h.getEventsOpts != nil { opts = append(opts, h.getEventsOpts(e)...) } // Sort the fields by name so they are consistent in each instance // of an event. Otherwise, the fields don't line up in WPA. names := make([]string, 0, len(e.Data)) hasError := false for k := range e.Data { if k == logrus.ErrorKey { // Always put the error last because it is optional in some events. hasError = true } else { names = append(names, k) } } sort.Strings(names) // Reserve extra space for the message and time fields. fields := make([]etw.FieldOpt, 0, len(e.Data)+2) fields = append(fields, etw.StringField("Message", e.Message)) fields = append(fields, etw.Time("Time", e.Time)) for _, k := range names { fields = append(fields, etw.SmartField(k, e.Data[k])) } if hasError { fields = append(fields, etw.SmartField(logrus.ErrorKey, e.Data[logrus.ErrorKey])) } // Firing an ETW event is essentially best effort, as the event write can // fail for reasons completely out of the control of the event writer (such // 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) return nil } // Close cleans up the hook and closes the ETW provider. If the provder was // registered by etwlogrus, it will be closed as part of `Close`. If the // provider was passed in, it will not be closed. func (h *Hook) Close() error { if h.closeProvider { return h.provider.Close() } return nil } go-winio-0.6.2/pkg/etwlogrus/hook_test.go000066400000000000000000000060501460531775000204220ustar00rootroot00000000000000//go:build windows // +build windows package etwlogrus import ( "testing" "github.com/sirupsen/logrus" ) func fireEvent(name string, value interface{}) { logrus.WithField("Field", value).Info(name) } // The purpose of this test is to log lots of different field types, to test the // logic that converts them to ETW. Because we don't have a way to // programatically validate the ETW events, this test has two main purposes: (1) // validate nothing causes a panic while logging (2) allow manual validation that // the data is logged correctly (through a tool like WPA). func TestFieldLogging(t *testing.T) { // Sample WPRP to collect this provider is included in HookTest.wprp. // // Start collection: // wpr -start HookTest.wprp -filemode // // Stop collection: // wpr -stop HookTest.etl h, err := NewHook("HookTest") if err != nil { t.Fatal(err) } logrus.AddHook(h) fireEvent("Bool", true) fireEvent("BoolSlice", []bool{true, false, true}) fireEvent("EmptyBoolSlice", []bool{}) fireEvent("String", "teststring") fireEvent("StringSlice", []string{"sstr1", "sstr2", "sstr3"}) fireEvent("EmptyStringSlice", []string{}) fireEvent("Int", int(1)) fireEvent("IntSlice", []int{2, 3, 4}) fireEvent("EmptyIntSlice", []int{}) fireEvent("Int8", int8(5)) fireEvent("Int8Slice", []int8{6, 7, 8}) fireEvent("EmptyInt8Slice", []int8{}) fireEvent("Int16", int16(9)) fireEvent("Int16Slice", []int16{10, 11, 12}) fireEvent("EmptyInt16Slice", []int16{}) fireEvent("Int32", int32(13)) fireEvent("Int32Slice", []int32{14, 15, 16}) fireEvent("EmptyInt32Slice", []int32{}) fireEvent("Int64", int64(17)) fireEvent("Int64Slice", []int64{18, 19, 20}) fireEvent("EmptyInt64Slice", []int64{}) fireEvent("Uint", uint(21)) fireEvent("UintSlice", []uint{22, 23, 24}) fireEvent("EmptyUintSlice", []uint{}) fireEvent("Uint8", uint8(25)) fireEvent("Uint8Slice", []uint8{26, 27, 28}) fireEvent("EmptyUint8Slice", []uint8{}) fireEvent("Uint16", uint16(29)) fireEvent("Uint16Slice", []uint16{30, 31, 32}) fireEvent("EmptyUint16Slice", []uint16{}) fireEvent("Uint32", uint32(33)) fireEvent("Uint32Slice", []uint32{34, 35, 36}) fireEvent("EmptyUint32Slice", []uint32{}) fireEvent("Uint64", uint64(37)) fireEvent("Uint64Slice", []uint64{38, 39, 40}) fireEvent("EmptyUint64Slice", []uint64{}) fireEvent("Uintptr", uintptr(41)) fireEvent("UintptrSlice", []uintptr{42, 43, 44}) fireEvent("EmptyUintptrSlice", []uintptr{}) fireEvent("Float32", float32(45.46)) fireEvent("Float32Slice", []float32{47.48, 49.50, 51.52}) fireEvent("EmptyFloat32Slice", []float32{}) fireEvent("Float64", float64(53.54)) fireEvent("Float64Slice", []float64{55.56, 57.58, 59.60}) fireEvent("EmptyFloat64Slice", []float64{}) type struct1 struct { A float32 priv int B []uint } type struct2 struct { A int B int } type struct3 struct { struct2 A int B string priv string C struct1 D uint16 } // Unexported fields, and fields in embedded structs, should not log. fireEvent("Struct", struct3{struct2{-1, -2}, 1, "2s", "-3s", struct1{3.4, -4, []uint{5, 6, 7}}, 8}) } go-winio-0.6.2/pkg/etwlogrus/opts.go000066400000000000000000000024061460531775000174110ustar00rootroot00000000000000//go:build windows package etwlogrus import ( "github.com/sirupsen/logrus" "github.com/Microsoft/go-winio/pkg/etw" ) // etw provider // WithNewETWProvider registers a new ETW provider and sets the hook to log using it. // The provider will be closed when the hook is closed. func WithNewETWProvider(n string) HookOpt { return func(h *Hook) error { provider, err := etw.NewProvider(n, nil) if err != nil { return err } h.provider = provider h.closeProvider = true return nil } } // WithExistingETWProvider configures the hook to use an existing ETW provider. // The provider will not be closed when the hook is closed. func WithExistingETWProvider(p *etw.Provider) HookOpt { return func(h *Hook) error { h.provider = p h.closeProvider = false return nil } } // WithGetName sets the ETW EventName of an event to the value returned by f // If the name is empty, the default event name will be used. func WithGetName(f func(*logrus.Entry) string) HookOpt { return func(h *Hook) error { h.getName = f return nil } } // 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 return nil } } go-winio-0.6.2/pkg/fs/000077500000000000000000000000001460531775000144505ustar00rootroot00000000000000go-winio-0.6.2/pkg/fs/doc.go000066400000000000000000000001041460531775000155370ustar00rootroot00000000000000// This package contains Win32 filesystem functionality. package fs go-winio-0.6.2/pkg/fs/fs_windows.go000066400000000000000000000016031460531775000171610ustar00rootroot00000000000000package fs import ( "errors" "path/filepath" "golang.org/x/sys/windows" "github.com/Microsoft/go-winio/internal/stringbuffer" ) var ( // ErrInvalidPath is returned when the location of a file path doesn't begin with a driver letter. ErrInvalidPath = errors.New("the path provided to GetFileSystemType must start with a drive letter") ) // GetFileSystemType obtains the type of a file system through GetVolumeInformation. // // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumeinformationw func GetFileSystemType(path string) (fsType string, err error) { drive := filepath.VolumeName(path) if len(drive) != 2 { return "", ErrInvalidPath } buf := stringbuffer.NewWString() defer buf.Free() drive += `\` err = windows.GetVolumeInformation(windows.StringToUTF16Ptr(drive), nil, 0, nil, nil, nil, buf.Pointer(), buf.Cap()) return buf.String(), err } go-winio-0.6.2/pkg/fs/fs_windows_test.go000066400000000000000000000021501460531775000202160ustar00rootroot00000000000000package fs import ( "errors" "os" "testing" ) func TestGetFSTypeOfKnownDrive(t *testing.T) { fsType, err := GetFileSystemType("C:\\") if err != nil { t.Fatal(err) } if fsType == "" { t.Fatal("No filesystem type name returned") } } func TestGetFSTypeOfInvalidPath(t *testing.T) { // [filepath.VolumeName] doesn't mandate that the drive letters matches [a-zA-Z]. // Instead, use non-character drive. _, err := GetFileSystemType(`No:\`) if !errors.Is(err, ErrInvalidPath) { t.Fatalf("Expected `ErrInvalidPath`, got %v", err) } } func TestGetFSTypeOfValidButAbsentDrive(t *testing.T) { drive := "" for _, letter := range "abcdefghijklmnopqrstuvwxyz" { possibleDrive := string(letter) + ":\\" if _, err := os.Stat(possibleDrive); os.IsNotExist(err) { drive = possibleDrive break } } if drive == "" { t.Skip("Every possible drive exists") } _, err := GetFileSystemType(drive) if err == nil { t.Fatalf("GetFileSystemType %s unexpectedly succeeded", drive) } if !os.IsNotExist(err) { t.Fatalf("GetFileSystemType %s failed with %v, expected 'ErrNotExist' or similar", drive, err) } } go-winio-0.6.2/pkg/fs/resolve.go000066400000000000000000000142171460531775000164630ustar00rootroot00000000000000//go:build windows package fs import ( "errors" "os" "strings" "golang.org/x/sys/windows" "github.com/Microsoft/go-winio/internal/fs" ) // ResolvePath returns the final path to a file or directory represented, resolving symlinks, // handling mount points, etc. // The resolution works by using the Windows API GetFinalPathNameByHandle, which takes a // handle and returns the final path to that file. // // It is intended to address short-comings of [filepath.EvalSymlinks], which does not work // well on Windows. func ResolvePath(path string) (string, error) { h, err := openMetadata(path) if err != nil { return "", err } defer windows.CloseHandle(h) //nolint:errcheck // We use the Windows API GetFinalPathNameByHandle to handle path resolution. GetFinalPathNameByHandle // returns a resolved path name for a file or directory. The returned path can be in several different // formats, based on the flags passed. There are several goals behind the design here: // - Do as little manual path manipulation as possible. Since Windows path formatting can be quite // complex, we try to just let the Windows APIs handle that for us. // - Retain as much compatibility with existing Go path functions as we can. In particular, we try to // ensure paths returned from resolvePath can be passed to EvalSymlinks. // // First, we query for the VOLUME_NAME_GUID path of the file. This will return a path in the form // "\\?\Volume{8a25748f-cf34-4ac6-9ee2-c89400e886db}\dir\file.txt". If the path is a UNC share // (e.g. "\\server\share\dir\file.txt"), then the VOLUME_NAME_GUID query will fail with ERROR_PATH_NOT_FOUND. // In this case, we will next try a VOLUME_NAME_DOS query. This query will return a path for a UNC share // in the form "\\?\UNC\server\share\dir\file.txt". This path will work with most functions, but EvalSymlinks // fails on it. Therefore, we rewrite the path to the form "\\server\share\dir\file.txt" before returning it. // This path rewrite may not be valid in all cases (see the notes in the next paragraph), but those should // be very rare edge cases, and this case wouldn't have worked with EvalSymlinks anyways. // // The "\\?\" prefix indicates that no path parsing or normalization should be performed by Windows. // Instead the path is passed directly to the object manager. The lack of parsing means that "." and ".." are // interpreted literally and "\"" must be used as a path separator. Additionally, because normalization is // not done, certain paths can only be represented in this format. For instance, "\\?\C:\foo." (with a trailing .) // cannot be written as "C:\foo.", because path normalization will remove the trailing ".". // // FILE_NAME_NORMALIZED can fail on some UNC paths based on access restrictions. // Attempt to query with FILE_NAME_NORMALIZED, and then fall back on FILE_NAME_OPENED if access is denied. // // Querying for VOLUME_NAME_DOS first instead of VOLUME_NAME_GUID would yield a "nicer looking" path in some cases. // For instance, it could return "\\?\C:\dir\file.txt" instead of "\\?\Volume{8a25748f-cf34-4ac6-9ee2-c89400e886db}\dir\file.txt". // However, we query for VOLUME_NAME_GUID first for two reasons: // - The volume GUID path is more stable. A volume's mount point can change when it is remounted, but its // volume GUID should not change. // - If the volume is mounted at a non-drive letter path (e.g. mounted to "C:\mnt"), then VOLUME_NAME_DOS // will return the mount path. EvalSymlinks fails on a path like this due to a bug. // // References: // - GetFinalPathNameByHandle: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlea // - Naming Files, Paths, and Namespaces: https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file // - Naming a Volume: https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-volume normalize := true guid := true rPath := "" for i := 1; i <= 4; i++ { // maximum of 4 different cases to try var flags fs.GetFinalPathFlag if normalize { flags |= fs.FILE_NAME_NORMALIZED // nop; for clarity } else { flags |= fs.FILE_NAME_OPENED } if guid { flags |= fs.VOLUME_NAME_GUID } else { flags |= fs.VOLUME_NAME_DOS // nop; for clarity } rPath, err = fs.GetFinalPathNameByHandle(h, flags) switch { case guid && errors.Is(err, windows.ERROR_PATH_NOT_FOUND): // ERROR_PATH_NOT_FOUND is returned from the VOLUME_NAME_GUID query if the path is a // network share (UNC path). In this case, query for the DOS name instead. guid = false continue case normalize && errors.Is(err, windows.ERROR_ACCESS_DENIED): // normalization failed when accessing individual components along path for SMB share normalize = false continue default: } break } if err == nil && strings.HasPrefix(rPath, `\\?\UNC\`) { // Convert \\?\UNC\server\share -> \\server\share. The \\?\UNC syntax does not work with // some Go filepath functions such as EvalSymlinks. In the future if other components // move away from EvalSymlinks and use GetFinalPathNameByHandle instead, we could remove // this path munging. rPath = `\\` + rPath[len(`\\?\UNC\`):] } return rPath, err } // openMetadata takes a path, opens it with only meta-data access, and returns the resulting handle. // It works for both file and directory paths. func openMetadata(path string) (windows.Handle, error) { // We are not able to use builtin Go functionality for opening a directory path: // - os.Open on a directory returns a os.File where Fd() is a search handle from FindFirstFile. // - syscall.Open does not provide a way to specify FILE_FLAG_BACKUP_SEMANTICS, which is needed to // open a directory. // // We could use os.Open if the path is a file, but it's easier to just use the same code for both. // Therefore, we call windows.CreateFile directly. h, err := fs.CreateFile( path, fs.FILE_ANY_ACCESS, fs.FILE_SHARE_READ|fs.FILE_SHARE_WRITE|fs.FILE_SHARE_DELETE, nil, // security attributes fs.OPEN_EXISTING, fs.FILE_FLAG_BACKUP_SEMANTICS, // Needed to open a directory handle. fs.NullHandle, ) if err != nil { return 0, &os.PathError{ Op: "CreateFile", Path: path, Err: err, } } return h, nil } go-winio-0.6.2/pkg/fs/resolve_test.go000066400000000000000000000166121460531775000175230ustar00rootroot00000000000000//go:build windows package fs import ( "os" "path/filepath" "strings" "syscall" "testing" "golang.org/x/sys/windows" "github.com/Microsoft/go-winio/internal/computestorage" "github.com/Microsoft/go-winio/internal/fs" "github.com/Microsoft/go-winio/vhd" ) func getWindowsBuildNumber() uint32 { // RtlGetVersion ignores manifest requirements vex := windows.RtlGetVersion() return vex.BuildNumber } func makeSymlink(t *testing.T, oldName string, newName string) { t.Helper() t.Logf("make symlink: %s -> %s", oldName, newName) if _, err := os.Lstat(oldName); err != nil { t.Fatalf("could not open file %q: %v", oldName, err) } if err := os.Symlink(oldName, newName); err != nil { t.Fatalf("creating symlink: %s", err) } if _, err := os.Lstat(newName); err != nil { t.Fatalf("could not open file %q: %v", newName, err) } } func getVolumeGUIDPath(t *testing.T, path string) string { t.Helper() h, err := openMetadata(path) if err != nil { t.Fatal(err) } defer windows.CloseHandle(h) //nolint:errcheck final, err := fs.GetFinalPathNameByHandle(h, fs.FILE_NAME_OPENED|fs.VOLUME_NAME_GUID) if err != nil { t.Fatal(err) } return final } func openDisk(path string) (windows.Handle, error) { h, err := fs.CreateFile( path, windows.GENERIC_READ|windows.GENERIC_WRITE, fs.FILE_SHARE_READ|fs.FILE_SHARE_WRITE, nil, // security attributes fs.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL|fs.FILE_FLAG_NO_BUFFERING, fs.NullHandle) if err != nil { return 0, &os.PathError{ Op: "CreateFile", Path: path, Err: err, } } return h, nil } func formatVHD(vhdHandle windows.Handle) error { h := vhdHandle // Pre-19H1 HcsFormatWritableLayerVhd expects a disk handle. // On newer builds it expects a VHD handle instead. // Open a handle to the VHD's disk object if needed. // Windows Server 1903, aka 19H1 if getWindowsBuildNumber() < 18362 { diskPath, err := vhd.GetVirtualDiskPhysicalPath(syscall.Handle(h)) if err != nil { return err } diskHandle, err := openDisk(diskPath) if err != nil { return err } defer windows.CloseHandle(diskHandle) //nolint:errcheck // cleanup code h = diskHandle } // Formatting a disk directly in Windows is a pain, so we use FormatWritableLayerVhd to do it. // It has a side effect of creating a sandbox directory on the formatted volume, but it's safe // to just ignore that for our purposes here. return computestorage.FormatWritableLayerVHD(h) } // Creates a VHD with a NTFS volume. Returns the volume path. func setupVHDVolume(t *testing.T, vhdPath string) string { t.Helper() vhdHandle, err := vhd.CreateVirtualDisk(vhdPath, vhd.VirtualDiskAccessNone, vhd.CreateVirtualDiskFlagNone, &vhd.CreateVirtualDiskParameters{ Version: 2, Version2: vhd.CreateVersion2{ MaximumSize: 5 * 1024 * 1024 * 1024, // 5GB, thin provisioned BlockSizeInBytes: 1 * 1024 * 1024, // 1MB }, }) if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = windows.CloseHandle(windows.Handle(vhdHandle)) }) if err := vhd.AttachVirtualDisk(vhdHandle, vhd.AttachVirtualDiskFlagNone, &vhd.AttachVirtualDiskParameters{Version: 1}); err != nil { t.Fatal(err) } t.Cleanup(func() { if err := vhd.DetachVirtualDisk(vhdHandle); err != nil { t.Fatal(err) } }) if err := formatVHD(windows.Handle(vhdHandle)); err != nil { t.Fatalf("failed to format VHD: %s", err) } // Get the path for the volume that was just created on the disk. volumePath, err := computestorage.GetLayerVHDMountPath(windows.Handle(vhdHandle)) if err != nil { t.Fatal(err) } return volumePath } func writeFile(t *testing.T, path string, content []byte) { t.Helper() if err := os.WriteFile(path, content, 0644); err != nil { //nolint:gosec // test file, can have permissive mode t.Fatal(err) } } func mountVolume(t *testing.T, volumePath string, mountPoint string) { t.Helper() // Create the mount point directory. if err := os.Mkdir(mountPoint, 0644); err != nil { t.Fatal(err) } t.Cleanup(func() { if err := os.Remove(mountPoint); err != nil { t.Fatal(err) } }) // Volume path must end in a slash. if !strings.HasSuffix(volumePath, `\`) { volumePath += `\` } volumePathU16, err := windows.UTF16PtrFromString(volumePath) if err != nil { t.Fatal(err) } // Mount point must end in a slash. if !strings.HasSuffix(mountPoint, `\`) { mountPoint += `\` } mountPointU16, err := windows.UTF16PtrFromString(mountPoint) if err != nil { t.Fatal(err) } if err := windows.SetVolumeMountPoint(mountPointU16, volumePathU16); err != nil { t.Fatalf("failed to mount %s onto %s: %s", volumePath, mountPoint, err) } t.Cleanup(func() { if err := windows.DeleteVolumeMountPoint(mountPointU16); err != nil { t.Fatalf("failed to delete mount on %s: %s", mountPoint, err) } }) } func TestResolvePath(t *testing.T) { if !windows.GetCurrentProcessToken().IsElevated() { t.Skip("requires elevated privileges") } // Set up some data to be used by the test cases. volumePathC := getVolumeGUIDPath(t, `C:\`) dir := t.TempDir() makeSymlink(t, `C:\windows`, filepath.Join(dir, "lnk1")) makeSymlink(t, `\\localhost\c$\windows`, filepath.Join(dir, "lnk2")) volumePathVHD1 := setupVHDVolume(t, filepath.Join(dir, "foo.vhdx")) writeFile(t, filepath.Join(volumePathVHD1, "data.txt"), []byte("test content 1")) makeSymlink(t, filepath.Join(volumePathVHD1, "data.txt"), filepath.Join(dir, "lnk3")) volumePathVHD2 := setupVHDVolume(t, filepath.Join(dir, "bar.vhdx")) writeFile(t, filepath.Join(volumePathVHD2, "data.txt"), []byte("test content 2")) makeSymlink(t, filepath.Join(volumePathVHD2, "data.txt"), filepath.Join(dir, "lnk4")) mountVolume(t, volumePathVHD2, filepath.Join(dir, "mnt")) for _, tc := range []struct { input string expected string description string }{ {`C:\windows`, volumePathC + `Windows`, "local path"}, {filepath.Join(dir, "lnk1"), volumePathC + `Windows`, "symlink to local path"}, {`\\localhost\c$\windows`, `\\localhost\c$\Windows`, "UNC path"}, {filepath.Join(dir, "lnk2"), `\\localhost\c$\Windows`, "symlink to UNC path"}, {filepath.Join(volumePathVHD1, "data.txt"), filepath.Join(volumePathVHD1, "data.txt"), "volume with no mount point"}, {filepath.Join(dir, "lnk3"), filepath.Join(volumePathVHD1, "data.txt"), "symlink to volume with no mount point"}, {filepath.Join(dir, "mnt", "data.txt"), filepath.Join(volumePathVHD2, "data.txt"), "volume with mount point"}, {filepath.Join(dir, "lnk4"), filepath.Join(volumePathVHD2, "data.txt"), "symlink to volume with mount point"}, } { t.Run(tc.description, func(t *testing.T) { t.Logf("resolving: %s -> %s", tc.input, tc.expected) actual, err := ResolvePath(tc.input) if err != nil { t.Fatalf("ResolvePath should return no error, but: %v", err) } if actual != tc.expected { t.Fatalf("expected %v but got %v", tc.expected, actual) } // Make sure EvalSymlinks works with the resolved path, as an extra safety measure. t.Logf("filepath.EvalSymlinks(%s)", actual) p, err := filepath.EvalSymlinks(actual) if err != nil { t.Fatalf("EvalSymlinks should return no error, but %v", err) } // As an extra-extra safety, check that resolvePath(x) == EvalSymlinks(resolvePath(x)). // EvalSymlinks normalizes UNC path casing, but resolvePath may not, so compare with // case-insensitivity here. if !strings.EqualFold(actual, p) { t.Fatalf("EvalSymlinks should resolve to the same path. Expected %v but got %v", actual, p) } }) } } go-winio-0.6.2/pkg/guid/000077500000000000000000000000001460531775000147705ustar00rootroot00000000000000go-winio-0.6.2/pkg/guid/guid.go000066400000000000000000000140661460531775000162560ustar00rootroot00000000000000// Package guid provides a GUID type. The backing structure for a GUID is // identical to that used by the golang.org/x/sys/windows GUID type. // There are two main binary encodings used for a GUID, the big-endian encoding, // and the Windows (mixed-endian) encoding. See here for details: // https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding package guid import ( "crypto/rand" "crypto/sha1" //nolint:gosec // not used for secure application "encoding" "encoding/binary" "fmt" "strconv" ) //go:generate go run golang.org/x/tools/cmd/stringer -type=Variant -trimprefix=Variant -linecomment // Variant specifies which GUID variant (or "type") of the GUID. It determines // how the entirety of the rest of the GUID is interpreted. type Variant uint8 // The variants specified by RFC 4122 section 4.1.1. const ( // VariantUnknown specifies a GUID variant which does not conform to one of // the variant encodings specified in RFC 4122. VariantUnknown Variant = iota VariantNCS VariantRFC4122 // RFC 4122 VariantMicrosoft VariantFuture ) // Version specifies how the bits in the GUID were generated. For instance, a // version 4 GUID is randomly generated, and a version 5 is generated from the // hash of an input string. type Version uint8 func (v Version) String() string { return strconv.FormatUint(uint64(v), 10) } var _ = (encoding.TextMarshaler)(GUID{}) var _ = (encoding.TextUnmarshaler)(&GUID{}) // NewV4 returns a new version 4 (pseudorandom) GUID, as defined by RFC 4122. func NewV4() (GUID, error) { var b [16]byte if _, err := rand.Read(b[:]); err != nil { return GUID{}, err } g := FromArray(b) g.setVersion(4) // Version 4 means randomly generated. g.setVariant(VariantRFC4122) return g, nil } // NewV5 returns a new version 5 (generated from a string via SHA-1 hashing) // GUID, as defined by RFC 4122. The RFC is unclear on the encoding of the name, // and the sample code treats it as a series of bytes, so we do the same here. // // Some implementations, such as those found on Windows, treat the name as a // 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() //nolint:gosec // not used for secure application namespaceBytes := namespace.ToArray() b.Write(namespaceBytes[:]) b.Write(name) a := [16]byte{} copy(a[:], b.Sum(nil)) g := FromArray(a) g.setVersion(5) // Version 5 means generated from a string. g.setVariant(VariantRFC4122) return g, nil } func fromArray(b [16]byte, order binary.ByteOrder) GUID { var g GUID g.Data1 = order.Uint32(b[0:4]) g.Data2 = order.Uint16(b[4:6]) g.Data3 = order.Uint16(b[6:8]) copy(g.Data4[:], b[8:16]) return g } func (g GUID) toArray(order binary.ByteOrder) [16]byte { b := [16]byte{} order.PutUint32(b[0:4], g.Data1) order.PutUint16(b[4:6], g.Data2) order.PutUint16(b[6:8], g.Data3) copy(b[8:16], g.Data4[:]) return b } // FromArray constructs a GUID from a big-endian encoding array of 16 bytes. func FromArray(b [16]byte) GUID { return fromArray(b, binary.BigEndian) } // ToArray returns an array of 16 bytes representing the GUID in big-endian // encoding. func (g GUID) ToArray() [16]byte { return g.toArray(binary.BigEndian) } // FromWindowsArray constructs a GUID from a Windows encoding array of bytes. func FromWindowsArray(b [16]byte) GUID { return fromArray(b, binary.LittleEndian) } // ToWindowsArray returns an array of 16 bytes representing the GUID in Windows // encoding. func (g GUID) ToWindowsArray() [16]byte { return g.toArray(binary.LittleEndian) } func (g GUID) String() string { return fmt.Sprintf( "%08x-%04x-%04x-%04x-%012x", g.Data1, g.Data2, g.Data3, g.Data4[:2], g.Data4[2:]) } // FromString parses a string containing a GUID and returns the GUID. The only // format currently supported is the `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` // format. func FromString(s string) (GUID, error) { if len(s) != 36 { return GUID{}, fmt.Errorf("invalid GUID %q", s) } if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { return GUID{}, fmt.Errorf("invalid GUID %q", s) } var g GUID data1, err := strconv.ParseUint(s[0:8], 16, 32) if err != nil { return GUID{}, fmt.Errorf("invalid GUID %q", s) } g.Data1 = uint32(data1) data2, err := strconv.ParseUint(s[9:13], 16, 16) if err != nil { return GUID{}, fmt.Errorf("invalid GUID %q", s) } g.Data2 = uint16(data2) data3, err := strconv.ParseUint(s[14:18], 16, 16) if err != nil { return GUID{}, fmt.Errorf("invalid GUID %q", s) } g.Data3 = uint16(data3) for i, x := range []int{19, 21, 24, 26, 28, 30, 32, 34} { v, err := strconv.ParseUint(s[x:x+2], 16, 8) if err != nil { return GUID{}, fmt.Errorf("invalid GUID %q", s) } g.Data4[i] = uint8(v) } return g, nil } func (g *GUID) setVariant(v Variant) { d := g.Data4[0] switch v { case VariantNCS: d = (d & 0x7f) case VariantRFC4122: d = (d & 0x3f) | 0x80 case VariantMicrosoft: d = (d & 0x1f) | 0xc0 case VariantFuture: d = (d & 0x0f) | 0xe0 case VariantUnknown: fallthrough default: panic(fmt.Sprintf("invalid variant: %d", v)) } g.Data4[0] = d } // Variant returns the GUID variant, as defined in RFC 4122. func (g GUID) Variant() Variant { b := g.Data4[0] if b&0x80 == 0 { return VariantNCS } else if b&0xc0 == 0x80 { return VariantRFC4122 } else if b&0xe0 == 0xc0 { return VariantMicrosoft } else if b&0xe0 == 0xe0 { return VariantFuture } return VariantUnknown } func (g *GUID) setVersion(v Version) { g.Data3 = (g.Data3 & 0x0fff) | (uint16(v) << 12) } // Version returns the GUID version, as defined in RFC 4122. func (g GUID) Version() Version { return Version((g.Data3 & 0xF000) >> 12) } // MarshalText returns the textual representation of the GUID. func (g GUID) MarshalText() ([]byte, error) { return []byte(g.String()), nil } // UnmarshalText takes the textual representation of a GUID, and unmarhals it // into this GUID. func (g *GUID) UnmarshalText(text []byte) error { g2, err := FromString(string(text)) if err != nil { return err } *g = g2 return nil } go-winio-0.6.2/pkg/guid/guid_nonwindows.go000066400000000000000000000006741460531775000205430ustar00rootroot00000000000000//go:build !windows // +build !windows package guid // GUID represents a GUID/UUID. It has the same structure as // golang.org/x/sys/windows.GUID so that it can be used with functions expecting // that type. It is defined as its own type as that is only available to builds // targeted at `windows`. The representation matches that used by native Windows // code. type GUID struct { Data1 uint32 Data2 uint16 Data3 uint16 Data4 [8]byte } go-winio-0.6.2/pkg/guid/guid_test.go000066400000000000000000000160521460531775000173120ustar00rootroot00000000000000package guid import ( "encoding/json" "fmt" "testing" ) func mustNewV4(t *testing.T) GUID { t.Helper() g, err := NewV4() if err != nil { t.Fatal(err) } return g } func mustNewV5(t *testing.T, namespace GUID, name []byte) GUID { t.Helper() g, err := NewV5(namespace, name) if err != nil { t.Fatal(err) } return g } func mustFromString(t *testing.T, s string) GUID { t.Helper() g, err := FromString(s) if err != nil { t.Fatal(err) } return g } func Test_Variant(t *testing.T) { type testCase struct { g GUID v Variant } testCases := []testCase{ {mustFromString(t, "f5cbc1a9-4cba-45a0-0fdd-b6761fc7dcc0"), VariantNCS}, {mustFromString(t, "f5cbc1a9-4cba-45a0-7fdd-b6761fc7dcc0"), VariantNCS}, {mustFromString(t, "f5cbc1a9-4cba-45a0-bfdd-b6761fc7dcc0"), VariantRFC4122}, {mustFromString(t, "f5cbc1a9-4cba-45a0-9fdd-b6761fc7dcc0"), VariantRFC4122}, {mustFromString(t, "f5cbc1a9-4cba-45a0-cfdd-b6761fc7dcc0"), VariantMicrosoft}, {mustFromString(t, "f5cbc1a9-4cba-45a0-dfdd-b6761fc7dcc0"), VariantMicrosoft}, {mustFromString(t, "f5cbc1a9-4cba-45a0-efdd-b6761fc7dcc0"), VariantFuture}, {mustFromString(t, "f5cbc1a9-4cba-45a0-ffdd-b6761fc7dcc0"), VariantFuture}, } for _, tc := range testCases { t.Run(tc.v.String()+"/"+tc.g.String(), func(t *testing.T) { actualVariant := tc.g.Variant() if actualVariant != tc.v { t.Fatalf("Variant is not correct.\nExpected: %d\nActual: %d\nGUID: %s", tc.v, actualVariant, tc.g) } }) } } func Test_SetVariant(t *testing.T) { g := mustFromString(t, "f5cbc1a9-4cba-45a0-bfdd-b6761fc7dcc0") for i := 0; i < len(_Variant_index)-1; i++ { v := Variant(i) if v == VariantUnknown { // Unknown is not a valid variant continue } t.Run(v.String(), func(t *testing.T) { g.setVariant(v) if g.Variant() != v { t.Fatalf("Variant is incorrect.\nExpected: %d\nActual: %d", v, g.Variant()) } }) } } func Test_Version(t *testing.T) { type testCase struct { g GUID v Version } testCases := []testCase{ {mustFromString(t, "f5cbc1a9-4cba-15a0-0fdd-b6761fc7dcc0"), 1}, {mustFromString(t, "f5cbc1a9-4cba-25a0-0fdd-b6761fc7dcc0"), 2}, {mustFromString(t, "f5cbc1a9-4cba-35a0-0fdd-b6761fc7dcc0"), 3}, {mustFromString(t, "f5cbc1a9-4cba-45a0-0fdd-b6761fc7dcc0"), 4}, {mustFromString(t, "f5cbc1a9-4cba-55a0-0fdd-b6761fc7dcc0"), 5}, } for _, tc := range testCases { t.Run(tc.v.String()+"-"+tc.g.String(), func(t *testing.T) { actualVersion := tc.g.Version() if actualVersion != tc.v { t.Fatalf("Version is not correct.\nExpected: %d\nActual: %d\nGUID: %s", tc.v, actualVersion, tc.g) } }) } } func Test_SetVersion(t *testing.T) { g := mustFromString(t, "f5cbc1a9-4cba-45a0-bfdd-b6761fc7dcc0") for tc := 0; tc < 16; tc++ { v := Version(tc) t.Run(v.String(), func(t *testing.T) { g.setVersion(v) if g.Version() != v { t.Fatalf("Version is incorrect.\nExpected: %d\nActual: %d", v, g.Version()) } }) } } func Test_NewV4IsUnique(t *testing.T) { g := mustNewV4(t) g2 := mustNewV4(t) if g == g2 { t.Fatalf("GUIDs are equal: %s, %s", g, g2) } } func Test_V4HasCorrectVersionAndVariant(t *testing.T) { g := mustNewV4(t) if g.Version() != 4 { t.Fatalf("Version is not 4: %s", g) } if g.Variant() != VariantRFC4122 { t.Fatalf("Variant is not RFC4122: %s", g) } } func Test_V5HasCorrectVersionAndVariant(t *testing.T) { namespace := mustFromString(t, "f5cbc1a9-4cba-45a0-bfdd-b6761fc7dcc0") g := mustNewV5(t, namespace, []byte("Foo")) if g.Version() != 5 { t.Fatalf("Version is not 5: %s", g) } if g.Variant() != VariantRFC4122 { t.Fatalf("Variant is not RFC4122: %s", g) } } func Test_V5KnownValues(t *testing.T) { type testCase struct { ns GUID name string g GUID } testCases := []testCase{ { mustFromString(t, "6ba7b810-9dad-11d1-80b4-00c04fd430c8"), "www.sample.com", mustFromString(t, "4e4463eb-b0e8-54fa-8c28-12d1ab1d45b3"), }, { mustFromString(t, "6ba7b811-9dad-11d1-80b4-00c04fd430c8"), "https://www.sample.com/test", mustFromString(t, "9e44625a-0d85-5e0a-99bc-8e8a77df5ea2"), }, { mustFromString(t, "6ba7b812-9dad-11d1-80b4-00c04fd430c8"), "1.3.6.1.4.1.343", mustFromString(t, "6aab0456-7392-582a-b92a-ba5a7096945d"), }, { mustFromString(t, "6ba7b814-9dad-11d1-80b4-00c04fd430c8"), "CN=John Smith, ou=People, o=FakeCorp, L=Seattle, S=Washington, C=US", mustFromString(t, "badff8dd-c869-5b64-a260-00092e66be00"), }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { g := mustNewV5(t, tc.ns, []byte(tc.name)) if g != tc.g { t.Fatalf("GUIDs are not equal.\nExpected: %s\nActual: %s", tc.g, g) } }) } } func Test_ToArray(t *testing.T) { g := mustFromString(t, "73c39589-192e-4c64-9acf-6c5d0aa18528") b := g.ToArray() expected := [16]byte{0x73, 0xc3, 0x95, 0x89, 0x19, 0x2e, 0x4c, 0x64, 0x9a, 0xcf, 0x6c, 0x5d, 0x0a, 0xa1, 0x85, 0x28} if b != expected { t.Fatalf("GUID does not match array form: %x, %x", expected, b) } } func Test_FromArrayAndBack(t *testing.T) { b := [16]byte{0x73, 0xc3, 0x95, 0x89, 0x19, 0x2e, 0x4c, 0x64, 0x9a, 0xcf, 0x6c, 0x5d, 0x0a, 0xa1, 0x85, 0x28} b2 := FromArray(b).ToArray() if b != b2 { t.Fatalf("Arrays do not match: %x, %x", b, b2) } } func Test_ToWindowsArray(t *testing.T) { g := mustFromString(t, "73c39589-192e-4c64-9acf-6c5d0aa18528") b := g.ToWindowsArray() expected := [16]byte{0x89, 0x95, 0xc3, 0x73, 0x2e, 0x19, 0x64, 0x4c, 0x9a, 0xcf, 0x6c, 0x5d, 0x0a, 0xa1, 0x85, 0x28} if b != expected { t.Fatalf("GUID does not match array form: %x, %x", expected, b) } } func Test_FromWindowsArrayAndBack(t *testing.T) { b := [16]byte{0x73, 0xc3, 0x95, 0x89, 0x19, 0x2e, 0x4c, 0x64, 0x9a, 0xcf, 0x6c, 0x5d, 0x0a, 0xa1, 0x85, 0x28} b2 := FromWindowsArray(b).ToWindowsArray() if b != b2 { t.Fatalf("Arrays do not match: %x, %x", b, b2) } } func Test_FromString(t *testing.T) { orig := "8e35239e-2084-490e-a3db-ab18ee0744cb" g := mustFromString(t, orig) s := g.String() if orig != s { t.Fatalf("GUIDs not equal: %s, %s", orig, s) } } func Test_MarshalJSON(t *testing.T) { g := mustNewV4(t) j, err := json.Marshal(g) if err != nil { t.Fatal(err) } gj := fmt.Sprintf("\"%s\"", g.String()) if string(j) != gj { t.Fatalf("JSON not equal: %s, %s", j, gj) } } func Test_MarshalJSON_Nested(t *testing.T) { type test struct { G GUID } g := mustNewV4(t) t1 := test{g} j, err := json.Marshal(t1) if err != nil { t.Fatal(err) } gj := fmt.Sprintf("{\"G\":\"%s\"}", g.String()) if string(j) != gj { t.Fatalf("JSON not equal: %s, %s", j, gj) } } func Test_UnmarshalJSON(t *testing.T) { g := mustNewV4(t) j, err := json.Marshal(g) if err != nil { t.Fatal(err) } var g2 GUID if err := json.Unmarshal(j, &g2); err != nil { t.Fatal(err) } if g != g2 { t.Fatalf("GUIDs not equal: %s, %s", g, g2) } } func Test_UnmarshalJSON_Nested(t *testing.T) { type test struct { G GUID } g := mustNewV4(t) t1 := test{g} j, err := json.Marshal(t1) if err != nil { t.Fatal(err) } var t2 test if err := json.Unmarshal(j, &t2); err != nil { t.Fatal(err) } if t1.G != t2.G { t.Fatalf("GUIDs not equal: %v, %v", t1.G, t2.G) } } go-winio-0.6.2/pkg/guid/guid_windows.go000066400000000000000000000006441460531775000200250ustar00rootroot00000000000000//go:build windows // +build windows package guid import "golang.org/x/sys/windows" // GUID represents a GUID/UUID. It has the same structure as // golang.org/x/sys/windows.GUID so that it can be used with functions expecting // that type. It is defined as its own type so that stringification and // marshaling can be supported. The representation matches that used by native // Windows code. type GUID windows.GUID go-winio-0.6.2/pkg/guid/variant_string.go000066400000000000000000000013531460531775000203530ustar00rootroot00000000000000// Code generated by "stringer -type=Variant -trimprefix=Variant -linecomment"; DO NOT EDIT. package guid import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[VariantUnknown-0] _ = x[VariantNCS-1] _ = x[VariantRFC4122-2] _ = x[VariantMicrosoft-3] _ = x[VariantFuture-4] } const _Variant_name = "UnknownNCSRFC 4122MicrosoftFuture" var _Variant_index = [...]uint8{0, 7, 10, 18, 27, 33} func (i Variant) String() string { if i >= Variant(len(_Variant_index)-1) { return "Variant(" + strconv.FormatInt(int64(i), 10) + ")" } return _Variant_name[_Variant_index[i]:_Variant_index[i+1]] } go-winio-0.6.2/pkg/process/000077500000000000000000000000001460531775000155165ustar00rootroot00000000000000go-winio-0.6.2/pkg/process/process.go000066400000000000000000000055361460531775000175340ustar00rootroot00000000000000//go:build windows // +build windows package process import ( "unsafe" "golang.org/x/sys/windows" ) // EnumProcesses returns a slice containing the process IDs of all processes // currently running on the system. func EnumProcesses() ([]uint32, error) { count := 256 uint32Size := unsafe.Sizeof(uint32(0)) for { buf := make([]uint32, count) bufferSize := uint32(len(buf) * int(uint32Size)) retBufferSize := uint32(0) if err := enumProcesses(&buf[0], bufferSize, &retBufferSize); err != nil { return nil, err } if retBufferSize == bufferSize { count = count * 2 continue } actualCount := retBufferSize / uint32(uint32Size) return buf[:actualCount], nil } } // 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 PeakWorkingSetSize uint WorkingSetSize uint QuotaPeakPagedPoolUsage uint QuotaPagedPoolUsage uint QuotaPeakNonPagedPoolUsage uint QuotaNonPagedPoolUsage uint PagefileUsage uint PeakPagefileUsage uint PrivateUsage uint } // GetProcessMemoryInfo returns the memory usage information for the given // process. The process handle must have the PROCESS_QUERY_INFORMATION or // PROCESS_QUERY_LIMITED_INFORMATION, and the PROCESS_VM_READ access rights. func GetProcessMemoryInfo(process windows.Handle) (*ProcessMemoryCountersEx, error) { memCounters := &ProcessMemoryCountersEx{} size := unsafe.Sizeof(*memCounters) if err := getProcessMemoryInfo(process, memCounters, uint32(size)); err != nil { return nil, err } return memCounters, nil } // These constants are used with QueryFullProcessImageName's flags. const ( // ImageNameFormatWin32Path indicates to format the name as a Win32 path. ImageNameFormatWin32Path = iota // ImageNameFormatNTPath indicates to format the name as a NT path. ImageNameFormatNTPath ) // QueryFullProcessImageName returns the full process image name for the given // process. The process handle must have the PROCESS_QUERY_INFORMATION or // PROCESS_QUERY_LIMITED_INFORMATION access right. The flags can be either // `ImageNameFormatWin32Path` or `ImageNameFormatNTPath`. func QueryFullProcessImageName(process windows.Handle, flags uint32) (string, error) { bufferSize := uint32(256) for { b := make([]uint16, bufferSize) err := queryFullProcessImageName(process, flags, &b[0], &bufferSize) if err == windows.ERROR_INSUFFICIENT_BUFFER { //nolint:errorlint // err is Errno bufferSize = bufferSize * 2 continue } if err != nil { return "", err } return windows.UTF16ToString(b[:bufferSize]), nil } } go-winio-0.6.2/pkg/process/syscall.go000066400000000000000000000012001460531775000175100ustar00rootroot00000000000000//go:build windows // +build windows package process import ( "golang.org/x/sys/windows" ) //go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go syscall.go //sys enumProcesses(pids *uint32, bufferSize uint32, retBufferSize *uint32) (err error) = kernel32.K32EnumProcesses //sys getProcessMemoryInfo(process handle, memCounters *ProcessMemoryCountersEx, size uint32) (err error) = kernel32.K32GetProcessMemoryInfo //sys queryFullProcessImageName(process handle, flags uint32, buffer *uint16, bufferSize *uint32) (err error) = kernel32.QueryFullProcessImageNameW type handle = windows.Handle go-winio-0.6.2/pkg/process/zsyscall_windows.go000066400000000000000000000036031460531775000214650ustar00rootroot00000000000000//go:build windows // Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT. package process import ( "syscall" "unsafe" "golang.org/x/sys/windows" ) var _ unsafe.Pointer // Do the interface allocations only once for common // Errno values. const ( errnoERROR_IO_PENDING = 997 ) var ( errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) errERROR_EINVAL error = syscall.EINVAL ) // errnoErr returns common boxed Errno values, to prevent // allocations at runtime. func errnoErr(e syscall.Errno) error { switch e { case 0: return errERROR_EINVAL case errnoERROR_IO_PENDING: return errERROR_IO_PENDING } return e } var ( modkernel32 = windows.NewLazySystemDLL("kernel32.dll") procK32EnumProcesses = modkernel32.NewProc("K32EnumProcesses") procK32GetProcessMemoryInfo = modkernel32.NewProc("K32GetProcessMemoryInfo") procQueryFullProcessImageNameW = modkernel32.NewProc("QueryFullProcessImageNameW") ) func enumProcesses(pids *uint32, bufferSize uint32, retBufferSize *uint32) (err error) { r1, _, e1 := syscall.SyscallN(procK32EnumProcesses.Addr(), uintptr(unsafe.Pointer(pids)), uintptr(bufferSize), uintptr(unsafe.Pointer(retBufferSize))) if r1 == 0 { err = errnoErr(e1) } return } func getProcessMemoryInfo(process handle, memCounters *ProcessMemoryCountersEx, size uint32) (err error) { r1, _, e1 := syscall.SyscallN(procK32GetProcessMemoryInfo.Addr(), uintptr(process), uintptr(unsafe.Pointer(memCounters)), uintptr(size)) if r1 == 0 { err = errnoErr(e1) } return } func queryFullProcessImageName(process handle, flags uint32, buffer *uint16, bufferSize *uint32) (err error) { r1, _, e1 := syscall.SyscallN(procQueryFullProcessImageNameW.Addr(), uintptr(process), uintptr(flags), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(bufferSize))) if r1 == 0 { err = errnoErr(e1) } return } go-winio-0.6.2/pkg/security/000077500000000000000000000000001460531775000157075ustar00rootroot00000000000000go-winio-0.6.2/pkg/security/grantvmgroupaccess.go000066400000000000000000000116331460531775000221570ustar00rootroot00000000000000//go:build windows // +build windows package security import ( "fmt" "os" "unsafe" "golang.org/x/sys/windows" ) type ( accessMask uint32 accessMode uint32 desiredAccess uint32 inheritMode uint32 objectType uint32 shareMode uint32 securityInformation uint32 trusteeForm uint32 trusteeType uint32 explicitAccess struct { accessPermissions accessMask accessMode accessMode inheritance inheritMode trustee trustee } trustee struct { multipleTrustee *trustee multipleTrusteeOperation int32 trusteeForm trusteeForm trusteeType trusteeType name uintptr } ) const ( accessMaskDesiredPermission accessMask = 1 << 31 // GENERIC_READ accessModeGrant accessMode = 1 desiredAccessReadControl desiredAccess = 0x20000 desiredAccessWriteDac desiredAccess = 0x40000 //cspell:disable-next-line gvmga = "GrantVmGroupAccess:" inheritModeNoInheritance inheritMode = 0x0 inheritModeSubContainersAndObjectsInherit inheritMode = 0x3 objectTypeFileObject objectType = 0x1 securityInformationDACL securityInformation = 0x4 shareModeRead shareMode = 0x1 shareModeWrite shareMode = 0x2 sidVMGroup = "S-1-5-83-0" trusteeFormIsSID trusteeForm = 0 trusteeTypeWellKnownGroup trusteeType = 5 ) // GrantVMGroupAccess sets the DACL for a specified file or directory to // 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) if err != nil { return fmt.Errorf("%s os.Stat %s: %w", gvmga, name, err) } // Get a handle to the file/directory. Must defer Close on success. fd, err := createFile(name, s.IsDir()) if err != nil { return err // Already wrapped } defer windows.CloseHandle(fd) //nolint:errcheck // Get the current DACL and Security Descriptor. Must defer LocalFree on success. ot := objectTypeFileObject si := securityInformationDACL sd := uintptr(0) origDACL := uintptr(0) 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 windows.LocalFree(windows.Handle(sd)) //nolint:errcheck // Generate a new DACL which is the current DACL with the required ACEs added. // Must defer LocalFree on success. newDACL, err := generateDACLWithAcesAdded(name, s.IsDir(), origDACL) if err != nil { return err // Already wrapped } defer windows.LocalFree(windows.Handle(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 { return fmt.Errorf("%s SetSecurityInfo %s: %w", gvmga, name, err) } return nil } // createFile is a helper function to call [Nt]CreateFile to get a handle to // the file or directory. func createFile(name string, isDir bool) (windows.Handle, error) { namep, err := windows.UTF16FromString(name) if err != nil { return windows.InvalidHandle, fmt.Errorf("could not convernt name to UTF-16: %w", err) } da := uint32(desiredAccessReadControl | desiredAccessWriteDac) sm := uint32(shareModeRead | shareModeWrite) fa := uint32(windows.FILE_ATTRIBUTE_NORMAL) if isDir { fa |= windows.FILE_FLAG_BACKUP_SEMANTICS } fd, err := windows.CreateFile(&namep[0], da, sm, nil, windows.OPEN_EXISTING, fa, 0) if err != nil { return windows.InvalidHandle, fmt.Errorf("%s windows.CreateFile %s: %w", gvmga, name, err) } return fd, nil } // generateDACLWithAcesAdded generates a new DACL with the two needed ACEs added. // 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 := windows.StringToSid(sidVMGroup) if err != nil { return 0, fmt.Errorf("%s windows.StringToSid %s %s: %w", gvmga, name, sidVMGroup, err) } inheritance := inheritModeNoInheritance if isDir { inheritance = inheritModeSubContainersAndObjectsInherit } eaArray := []explicitAccess{ { accessPermissions: accessMaskDesiredPermission, accessMode: accessModeGrant, inheritance: inheritance, trustee: trustee{ trusteeForm: trusteeFormIsSID, trusteeType: trusteeTypeWellKnownGroup, name: uintptr(unsafe.Pointer(sid)), }, }, } modifiedDACL := uintptr(0) if err := setEntriesInAcl(uintptr(uint32(1)), uintptr(unsafe.Pointer(&eaArray[0])), origDACL, &modifiedDACL); err != nil { return 0, fmt.Errorf("%s SetEntriesInAcl %s: %w", gvmga, name, err) } return modifiedDACL, nil } go-winio-0.6.2/pkg/security/grantvmgroupaccess_test.go000066400000000000000000000056541460531775000232240ustar00rootroot00000000000000//go:build windows // +build windows package security import ( "os" "path/filepath" "regexp" "strings" "testing" exec "golang.org/x/sys/execabs" ) const ( vmAccountName = `NT VIRTUAL MACHINE\\Virtual Machines` vmAccountSID = "S-1-5-83-0" ) // TestGrantVmGroupAccess verifies for the three case of a file, a directory, // and a file in a directory that the appropriate ACEs are set, including // inheritance in the second two examples. These are the expected ACES. Is // verified by running icacls and comparing output. // // File: // S-1-15-3-1024-2268835264-3721307629-241982045-173645152-1490879176-104643441-2915960892-1612460704:(R,W) // S-1-5-83-1-3166535780-1122986932-343720105-43916321:(R,W) // // Directory: // S-1-15-3-1024-2268835264-3721307629-241982045-173645152-1490879176-104643441-2915960892-1612460704:(OI)(CI)(R,W) // S-1-5-83-1-3166535780-1122986932-343720105-43916321:(OI)(CI)(R,W) // // File in directory (inherited): // S-1-15-3-1024-2268835264-3721307629-241982045-173645152-1490879176-104643441-2915960892-1612460704:(I)(R,W) // S-1-5-83-1-3166535780-1122986932-343720105-43916321:(I)(R,W) func TestGrantVmGroupAccess(t *testing.T) { f, err := os.CreateTemp("", "gvmgafile") if err != nil { t.Fatal(err) } defer func() { f.Close() os.Remove(f.Name()) }() 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) } if err := GrantVmGroupAccess(d); err != nil { t.Fatal(err) } verifyVMAccountDACLs(t, f.Name(), []string{`(R)`}, ) // Two items here: // - One explicit read only. // - Other applies to this folder, subfolders and files // (OI): object inherit // (CI): container inherit // (IO): inherit only // (GR): generic read // // In properties for the directory, advanced security settings, this will // show as a single line "Allow/Virtual Machines/Read/Inherited from none/This folder, subfolder and files verifyVMAccountDACLs(t, d, []string{`(R)`, `(OI)(CI)(IO)(GR)`}, ) verifyVMAccountDACLs(t, find.Name(), []string{`(I)(R)`}, ) } func verifyVMAccountDACLs(t *testing.T, name string, permissions []string) { t.Helper() cmd := exec.Command("icacls", name) outb, err := cmd.CombinedOutput() if err != nil { t.Fatal(err) } out := string(outb) for _, p := range permissions { // Avoid '(' and ')' being part of match groups p = strings.Replace(p, "(", "\\(", -1) p = strings.Replace(p, ")", "\\)", -1) nameToCheck := vmAccountName + ":" + p sidToCheck := vmAccountSID + ":" + p rxName := regexp.MustCompile(nameToCheck) rxSID := regexp.MustCompile(sidToCheck) matchesName := rxName.FindAllStringIndex(out, -1) matchesSID := rxSID.FindAllStringIndex(out, -1) if len(matchesName) != 1 && len(matchesSID) != 1 { t.Fatalf("expected one match for %s or %s\n%s", nameToCheck, sidToCheck, out) } } } go-winio-0.6.2/pkg/security/syscall_windows.go000066400000000000000000000012561460531775000214660ustar00rootroot00000000000000package security //go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go syscall_windows.go //sys getSecurityInfo(handle windows.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (win32err error) = advapi32.GetSecurityInfo //sys setSecurityInfo(handle windows.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (win32err error) = advapi32.SetSecurityInfo //sys setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (win32err error) = advapi32.SetEntriesInAclW go-winio-0.6.2/pkg/security/zsyscall_windows.go000066400000000000000000000042641460531775000216620ustar00rootroot00000000000000//go:build windows // Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT. package security import ( "syscall" "unsafe" "golang.org/x/sys/windows" ) var _ unsafe.Pointer // Do the interface allocations only once for common // Errno values. const ( errnoERROR_IO_PENDING = 997 ) var ( errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) errERROR_EINVAL error = syscall.EINVAL ) // errnoErr returns common boxed Errno values, to prevent // allocations at runtime. func errnoErr(e syscall.Errno) error { switch e { case 0: return errERROR_EINVAL case errnoERROR_IO_PENDING: return errERROR_IO_PENDING } return e } var ( modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") procGetSecurityInfo = modadvapi32.NewProc("GetSecurityInfo") procSetEntriesInAclW = modadvapi32.NewProc("SetEntriesInAclW") procSetSecurityInfo = modadvapi32.NewProc("SetSecurityInfo") ) func getSecurityInfo(handle windows.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (win32err error) { r0, _, _ := syscall.SyscallN(procGetSecurityInfo.Addr(), uintptr(handle), uintptr(objectType), uintptr(si), uintptr(unsafe.Pointer(ppsidOwner)), uintptr(unsafe.Pointer(ppsidGroup)), uintptr(unsafe.Pointer(ppDacl)), uintptr(unsafe.Pointer(ppSacl)), uintptr(unsafe.Pointer(ppSecurityDescriptor))) if r0 != 0 { win32err = syscall.Errno(r0) } return } func setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (win32err error) { r0, _, _ := syscall.SyscallN(procSetEntriesInAclW.Addr(), uintptr(count), uintptr(pListOfEEs), uintptr(oldAcl), uintptr(unsafe.Pointer(newAcl))) if r0 != 0 { win32err = syscall.Errno(r0) } return } func setSecurityInfo(handle windows.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (win32err error) { r0, _, _ := syscall.SyscallN(procSetSecurityInfo.Addr(), uintptr(handle), uintptr(objectType), uintptr(si), uintptr(psidOwner), uintptr(psidGroup), uintptr(pDacl), uintptr(pSacl)) if r0 != 0 { win32err = syscall.Errno(r0) } return } go-winio-0.6.2/privilege.go000066400000000000000000000126751460531775000156070ustar00rootroot00000000000000//go:build windows // +build windows package winio import ( "bytes" "encoding/binary" "fmt" "runtime" "sync" "unicode/utf16" "golang.org/x/sys/windows" ) //sys adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) [true] = advapi32.AdjustTokenPrivileges //sys impersonateSelf(level uint32) (err error) = advapi32.ImpersonateSelf //sys revertToSelf() (err error) = advapi32.RevertToSelf //sys openThreadToken(thread windows.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) = advapi32.OpenThreadToken //sys getCurrentThread() (h windows.Handle) = GetCurrentThread //sys lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) = advapi32.LookupPrivilegeValueW //sys lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) = advapi32.LookupPrivilegeNameW //sys lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) = advapi32.LookupPrivilegeDisplayNameW const ( //revive:disable-next-line:var-naming ALL_CAPS SE_PRIVILEGE_ENABLED = windows.SE_PRIVILEGE_ENABLED //revive:disable-next-line:var-naming ALL_CAPS ERROR_NOT_ALL_ASSIGNED windows.Errno = windows.ERROR_NOT_ALL_ASSIGNED SeBackupPrivilege = "SeBackupPrivilege" SeRestorePrivilege = "SeRestorePrivilege" SeSecurityPrivilege = "SeSecurityPrivilege" ) var ( privNames = make(map[string]uint64) privNameMutex sync.Mutex ) // PrivilegeError represents an error enabling privileges. type PrivilegeError struct { privileges []uint64 } func (e *PrivilegeError) Error() string { s := "Could not enable privilege " if len(e.privileges) > 1 { s = "Could not enable privileges " } for i, p := range e.privileges { if i != 0 { s += ", " } s += `"` s += getPrivilegeName(p) s += `"` } return s } // RunWithPrivilege enables a single privilege for a function call. func RunWithPrivilege(name string, fn func() error) error { return RunWithPrivileges([]string{name}, fn) } // RunWithPrivileges enables privileges for a function call. func RunWithPrivileges(names []string, fn func() error) error { privileges, err := mapPrivileges(names) if err != nil { return err } runtime.LockOSThread() defer runtime.UnlockOSThread() token, err := newThreadToken() if err != nil { return err } defer releaseThreadToken(token) err = adjustPrivileges(token, privileges, SE_PRIVILEGE_ENABLED) if err != nil { return err } return fn() } func mapPrivileges(names []string) ([]uint64, error) { privileges := make([]uint64, 0, len(names)) privNameMutex.Lock() defer privNameMutex.Unlock() for _, name := range names { p, ok := privNames[name] if !ok { err := lookupPrivilegeValue("", name, &p) if err != nil { return nil, err } privNames[name] = p } privileges = append(privileges, p) } return privileges, nil } // EnableProcessPrivileges enables privileges globally for the process. func EnableProcessPrivileges(names []string) error { return enableDisableProcessPrivilege(names, SE_PRIVILEGE_ENABLED) } // DisableProcessPrivileges disables privileges globally for the process. func DisableProcessPrivileges(names []string) error { return enableDisableProcessPrivilege(names, 0) } func enableDisableProcessPrivilege(names []string, action uint32) error { privileges, err := mapPrivileges(names) if err != nil { return err } p := windows.CurrentProcess() var token windows.Token err = windows.OpenProcessToken(p, windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, &token) if err != nil { return err } defer token.Close() return adjustPrivileges(token, privileges, action) } func adjustPrivileges(token windows.Token, privileges []uint64, action uint32) error { var b bytes.Buffer _ = binary.Write(&b, binary.LittleEndian, uint32(len(privileges))) for _, p := range privileges { _ = binary.Write(&b, binary.LittleEndian, p) _ = binary.Write(&b, binary.LittleEndian, action) } prevState := make([]byte, b.Len()) reqSize := uint32(0) success, err := adjustTokenPrivileges(token, false, &b.Bytes()[0], uint32(len(prevState)), &prevState[0], &reqSize) if !success { return err } if err == ERROR_NOT_ALL_ASSIGNED { //nolint:errorlint // err is Errno return &PrivilegeError{privileges} } return nil } func getPrivilegeName(luid uint64) string { var nameBuffer [256]uint16 bufSize := uint32(len(nameBuffer)) err := lookupPrivilegeName("", &luid, &nameBuffer[0], &bufSize) if err != nil { return fmt.Sprintf("", luid) } var displayNameBuffer [256]uint16 displayBufSize := uint32(len(displayNameBuffer)) var langID uint32 err = lookupPrivilegeDisplayName("", &nameBuffer[0], &displayNameBuffer[0], &displayBufSize, &langID) if err != nil { return fmt.Sprintf("", string(utf16.Decode(nameBuffer[:bufSize]))) } return string(utf16.Decode(displayNameBuffer[:displayBufSize])) } func newThreadToken() (windows.Token, error) { err := impersonateSelf(windows.SecurityImpersonation) if err != nil { return 0, err } var token windows.Token err = openThreadToken(getCurrentThread(), windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, false, &token) if err != nil { rerr := revertToSelf() if rerr != nil { panic(rerr) } return 0, err } return token, nil } func releaseThreadToken(h windows.Token) { err := revertToSelf() if err != nil { panic(err) } h.Close() } go-winio-0.6.2/privileges_test.go000066400000000000000000000007321460531775000170200ustar00rootroot00000000000000//go:build windows // +build windows package winio import ( "errors" "testing" ) func TestRunWithUnavailablePrivilege(t *testing.T) { err := RunWithPrivilege("SeCreateTokenPrivilege", func() error { return nil }) var perr *PrivilegeError if !errors.As(err, &perr) { t.Fatal("expected PrivilegeError") } } func TestRunWithPrivileges(t *testing.T) { err := RunWithPrivilege("SeShutdownPrivilege", func() error { return nil }) if err != nil { t.Fatal(err) } } go-winio-0.6.2/reparse.go000066400000000000000000000067051460531775000152570ustar00rootroot00000000000000//go:build windows // +build windows package winio import ( "bytes" "encoding/binary" "fmt" "strings" "unicode/utf16" "unsafe" ) const ( reparseTagMountPoint = 0xA0000003 reparseTagSymlink = 0xA000000C ) type reparseDataBuffer struct { ReparseTag uint32 ReparseDataLength uint16 Reserved uint16 SubstituteNameOffset uint16 SubstituteNameLength uint16 PrintNameOffset uint16 PrintNameLength uint16 } // ReparsePoint describes a Win32 symlink or mount point. type ReparsePoint struct { Target string IsMountPoint bool } // UnsupportedReparsePointError is returned when trying to decode a non-symlink or // mount point reparse point. type UnsupportedReparsePointError struct { Tag uint32 } func (e *UnsupportedReparsePointError) Error() string { return fmt.Sprintf("unsupported reparse point %x", e.Tag) } // DecodeReparsePoint decodes a Win32 REPARSE_DATA_BUFFER structure containing either a symlink // or a mount point. func DecodeReparsePoint(b []byte) (*ReparsePoint, error) { tag := binary.LittleEndian.Uint32(b[0:4]) return DecodeReparsePointData(tag, b[8:]) } func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) { isMountPoint := false switch tag { case reparseTagMountPoint: isMountPoint = true case reparseTagSymlink: default: return nil, &UnsupportedReparsePointError{tag} } nameOffset := 8 + binary.LittleEndian.Uint16(b[4:6]) if !isMountPoint { nameOffset += 4 } nameLength := binary.LittleEndian.Uint16(b[6:8]) name := make([]uint16, nameLength/2) err := binary.Read(bytes.NewReader(b[nameOffset:nameOffset+nameLength]), binary.LittleEndian, &name) if err != nil { return nil, err } return &ReparsePoint{string(utf16.Decode(name)), isMountPoint}, nil } func isDriveLetter(c byte) bool { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') } // EncodeReparsePoint encodes a Win32 REPARSE_DATA_BUFFER structure describing a symlink or // mount point. func EncodeReparsePoint(rp *ReparsePoint) []byte { // Generate an NT path and determine if this is a relative path. var ntTarget string relative := false if strings.HasPrefix(rp.Target, `\\?\`) { ntTarget = `\??\` + rp.Target[4:] } else if strings.HasPrefix(rp.Target, `\\`) { ntTarget = `\??\UNC\` + rp.Target[2:] } else if len(rp.Target) >= 2 && isDriveLetter(rp.Target[0]) && rp.Target[1] == ':' { ntTarget = `\??\` + rp.Target } else { ntTarget = rp.Target relative = true } // The paths must be NUL-terminated even though they are counted strings. target16 := utf16.Encode([]rune(rp.Target + "\x00")) ntTarget16 := utf16.Encode([]rune(ntTarget + "\x00")) size := int(unsafe.Sizeof(reparseDataBuffer{})) - 8 size += len(ntTarget16)*2 + len(target16)*2 tag := uint32(reparseTagMountPoint) if !rp.IsMountPoint { tag = reparseTagSymlink size += 4 // Add room for symlink flags } data := reparseDataBuffer{ ReparseTag: tag, ReparseDataLength: uint16(size), SubstituteNameOffset: 0, SubstituteNameLength: uint16((len(ntTarget16) - 1) * 2), PrintNameOffset: uint16(len(ntTarget16) * 2), PrintNameLength: uint16((len(target16) - 1) * 2), } var b bytes.Buffer _ = 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, ntTarget16) _ = binary.Write(&b, binary.LittleEndian, target16) return b.Bytes() } go-winio-0.6.2/sd.go000066400000000000000000000104201460531775000142110ustar00rootroot00000000000000//go:build windows // +build windows package winio import ( "errors" "fmt" "unsafe" "golang.org/x/sys/windows" ) //sys lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) = advapi32.LookupAccountNameW //sys lookupAccountSid(systemName *uint16, sid *byte, name *uint16, nameSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) = advapi32.LookupAccountSidW //sys convertSidToStringSid(sid *byte, str **uint16) (err error) = advapi32.ConvertSidToStringSidW //sys convertStringSidToSid(str *uint16, sid **byte) (err error) = advapi32.ConvertStringSidToSidW type AccountLookupError struct { Name string Err error } func (e *AccountLookupError) Error() string { if e.Name == "" { return "lookup account: empty account name specified" } var s string switch { case errors.Is(e.Err, windows.ERROR_INVALID_SID): s = "the security ID structure is invalid" case errors.Is(e.Err, windows.ERROR_NONE_MAPPED): s = "not found" default: s = e.Err.Error() } return "lookup account " + e.Name + ": " + s } func (e *AccountLookupError) Unwrap() error { return e.Err } type SddlConversionError struct { Sddl string Err error } 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, windows.ERROR_NONE_MAPPED} } var sidSize, sidNameUse, refDomainSize uint32 err = lookupAccountName(nil, name, nil, &sidSize, nil, &refDomainSize, &sidNameUse) if err != nil && err != windows.ERROR_INSUFFICIENT_BUFFER { //nolint:errorlint // err is Errno return "", &AccountLookupError{name, err} } sidBuffer := make([]byte, sidSize) refDomainBuffer := make([]uint16, refDomainSize) err = lookupAccountName(nil, name, &sidBuffer[0], &sidSize, &refDomainBuffer[0], &refDomainSize, &sidNameUse) if err != nil { return "", &AccountLookupError{name, err} } var strBuffer *uint16 err = convertSidToStringSid(&sidBuffer[0], &strBuffer) if err != nil { return "", &AccountLookupError{name, err} } sid = windows.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(strBuffer))[:]) _, _ = windows.LocalFree(windows.Handle(unsafe.Pointer(strBuffer))) return sid, nil } // 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, windows.ERROR_NONE_MAPPED} } sidBuffer, err := windows.UTF16PtrFromString(sid) if err != nil { return "", &AccountLookupError{sid, err} } var sidPtr *byte if err = convertStringSidToSid(sidBuffer, &sidPtr); err != nil { return "", &AccountLookupError{sid, err} } defer windows.LocalFree(windows.Handle(unsafe.Pointer(sidPtr))) //nolint:errcheck var nameSize, refDomainSize, sidNameUse uint32 err = lookupAccountSid(nil, sidPtr, nil, &nameSize, nil, &refDomainSize, &sidNameUse) if err != nil && err != windows.ERROR_INSUFFICIENT_BUFFER { //nolint:errorlint // err is Errno return "", &AccountLookupError{sid, err} } nameBuffer := make([]uint16, nameSize) refDomainBuffer := make([]uint16, refDomainSize) err = lookupAccountSid(nil, sidPtr, &nameBuffer[0], &nameSize, &refDomainBuffer[0], &refDomainSize, &sidNameUse) if err != nil { return "", &AccountLookupError{sid, err} } name = windows.UTF16ToString(nameBuffer) return name, nil } func SddlToSecurityDescriptor(sddl string) ([]byte, error) { sd, err := windows.SecurityDescriptorFromString(sddl) if err != nil { return nil, &SddlConversionError{Sddl: sddl, Err: err} } b := unsafe.Slice((*byte)(unsafe.Pointer(sd)), sd.Length()) return b, nil } func SecurityDescriptorToSddl(sd []byte) (string, error) { if l := int(unsafe.Sizeof(windows.SECURITY_DESCRIPTOR{})); len(sd) < l { return "", fmt.Errorf("SecurityDescriptor (%d) smaller than expected (%d): %w", len(sd), l, windows.ERROR_INCORRECT_SIZE) } s := (*windows.SECURITY_DESCRIPTOR)(unsafe.Pointer(&sd[0])) return s.String(), nil } go-winio-0.6.2/sd_test.go000066400000000000000000000023441460531775000152560ustar00rootroot00000000000000//go:build windows // +build windows package winio import ( "errors" "testing" "golang.org/x/sys/windows" ) func TestLookupInvalidSid(t *testing.T) { _, err := LookupSidByName(".\\weoifjdsklfj") 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") 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) } } func TestLookupValidSid(t *testing.T) { everyone := "S-1-1-0" name, err := LookupNameBySid(everyone) if err != nil { t.Fatalf("expected a valid account name, got %v", err) } sid, err := LookupSidByName(name) if err != nil || sid != everyone { t.Fatalf("expected %s, got %s, %s", everyone, sid, err) } } func TestLookupEmptyNameFails(t *testing.T) { _, err := LookupSidByName("") 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) } } go-winio-0.6.2/syscall.go000066400000000000000000000002141460531775000152550ustar00rootroot00000000000000//go:build windows package winio //go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go ./*.go go-winio-0.6.2/tools/000077500000000000000000000000001460531775000144175ustar00rootroot00000000000000go-winio-0.6.2/tools/etw-provider-gen/000077500000000000000000000000001460531775000176155ustar00rootroot00000000000000go-winio-0.6.2/tools/etw-provider-gen/main_others.go000066400000000000000000000001051460531775000224500ustar00rootroot00000000000000//go:build !windows // +build !windows package main func main() {} go-winio-0.6.2/tools/etw-provider-gen/main_windows.go000066400000000000000000000011071460531775000226410ustar00rootroot00000000000000//go:build windows // +build windows package main import ( "flag" "fmt" "os" "github.com/Microsoft/go-winio/pkg/etw" ) func main() { var pn = flag.String("provider-name", "", "The human readable ETW provider name to be converted into GUID format") flag.Parse() if pn == nil || *pn == "" { fmt.Fprint(os.Stderr, "--provider-name is required") os.Exit(1) } p, err := etw.NewProvider(*pn, nil) if err != nil { fmt.Fprintf(os.Stderr, "failed to convert provider-name: '%s' with err: '%s", *pn, err) os.Exit(1) } defer p.Close() fmt.Fprintf(os.Stdout, "%s", p) } go-winio-0.6.2/tools/mkwinsyscall/000077500000000000000000000000001460531775000171375ustar00rootroot00000000000000go-winio-0.6.2/tools/mkwinsyscall/doc.go000066400000000000000000000040071460531775000202340ustar00rootroot00000000000000/* mkwinsyscall generates windows system call bodies It parses all files specified on command line containing function prototypes (like syscall_windows.go) and prints system call bodies to standard output. The prototypes are marked by lines beginning with "//sys" and read like func declarations if //sys is replaced by func, but: - The parameter lists must give a name for each argument. This includes return parameters. - The parameter lists must give a type for each argument: the (x, y, z int) shorthand is not allowed. - If the return parameter is an error number, it must be named err. - 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- fatal, and an error will be returned instead of panicking. Usage: 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 go-winio-0.6.2/tools/mkwinsyscall/mkwinsyscall.go000066400000000000000000000576511460531775000222240ustar00rootroot00000000000000//go:build windows // Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "bufio" "bytes" "errors" "flag" "fmt" "go/format" "go/parser" "go/token" "io" "log" "os" "path/filepath" "runtime" "sort" "strings" "text/template" "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") systemDLL = flag.Bool("systemdll", true, "whether all DLLs should be loaded from the Windows system directory") winio = flag.Bool("winio", false, `import this package ("github.com/Microsoft/go-winio")`) utf16 = flag.Bool("utf16", true, "encode string arguments as UTF-16 for syscalls not ending in 'A' or 'W'") sortdecls = flag.Bool("sort", true, "sort DLL and function declarations") ) func trim(s string) string { return strings.Trim(s, " \t") } func endsIn(s string, c byte) bool { return len(s) >= 1 && s[len(s)-1] == c } var packageName string func packagename() string { return packageName } func windowsdot() string { if packageName == pkgWindows { return "" } return pkgWindows + "." } func syscalldot() string { if packageName == pkgSyscall { return "" } return pkgSyscall + "." } // Param is function parameter. type Param struct { Name string Type string fn *Fn tmpVarIdx int } // tmpVar returns temp variable name that will be used to represent p during syscall. func (p *Param) tmpVar() string { if p.tmpVarIdx < 0 { p.tmpVarIdx = p.fn.curTmpVarIdx p.fn.curTmpVarIdx++ } return fmt.Sprintf("_p%d", p.tmpVarIdx) } // BoolTmpVarCode returns source code for bool temp variable. func (p *Param) BoolTmpVarCode() string { const code = `var %[1]s uint32 if %[2]s { %[1]s = 1 }` return fmt.Sprintf(code, p.tmpVar(), p.Name) } // BoolPointerTmpVarCode returns source code for bool temp variable. func (p *Param) BoolPointerTmpVarCode() string { const code = `var %[1]s uint32 if *%[2]s { %[1]s = 1 }` return fmt.Sprintf(code, p.tmpVar(), p.Name) } // SliceTmpVarCode returns source code for slice temp variable. func (p *Param) SliceTmpVarCode() string { const code = `var %s *%s if len(%s) > 0 { %s = &%s[0] }` tmp := p.tmpVar() return fmt.Sprintf(code, tmp, p.Type[2:], p.Name, tmp, p.Name) } // StringTmpVarCode returns source code for string temp variable. func (p *Param) StringTmpVarCode() string { errvar := p.fn.Rets.ErrorVarName() if errvar == "" { errvar = "_" } tmp := p.tmpVar() const code = `var %s %s %s, %s = %s(%s)` s := fmt.Sprintf(code, tmp, p.fn.StrconvType(), tmp, errvar, p.fn.StrconvFunc(), p.Name) if errvar == "-" { return s } const morecode = ` if %s != nil { return }` return s + fmt.Sprintf(morecode, errvar) } // TmpVarCode returns source code for temp variable. func (p *Param) TmpVarCode() string { switch { case p.Type == tBool: return p.BoolTmpVarCode() case p.Type == tBoolPtr: return p.BoolPointerTmpVarCode() case strings.HasPrefix(p.Type, "[]"): return p.SliceTmpVarCode() default: return "" } } // TmpVarReadbackCode returns source code for reading back the temp variable into the original variable. func (p *Param) TmpVarReadbackCode() string { switch { case p.Type == tBoolPtr: return fmt.Sprintf("*%s = %s != 0", p.Name, p.tmpVar()) default: return "" } } // TmpVarHelperCode returns source code for helper's temp variable. func (p *Param) TmpVarHelperCode() string { if p.Type != "string" { return "" } return p.StringTmpVarCode() } // SyscallArgList returns source code fragments representing p parameter // in syscall. Slices are translated into 2 syscall parameters: pointer to // the first element and length. func (p *Param) SyscallArgList() []string { t := p.HelperType() var s string switch { case t == tBoolPtr: s = fmt.Sprintf("unsafe.Pointer(&%s)", p.tmpVar()) case t[0] == '*': s = fmt.Sprintf("unsafe.Pointer(%s)", p.Name) case t == tBool: s = p.tmpVar() case strings.HasPrefix(t, "[]"): return []string{ fmt.Sprintf("uintptr(unsafe.Pointer(%s))", p.tmpVar()), fmt.Sprintf("uintptr(len(%s))", p.Name), } default: s = p.Name } return []string{fmt.Sprintf("uintptr(%s)", s)} } // IsError determines if p parameter is used to return error. func (p *Param) IsError() bool { 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 == tString { return p.fn.StrconvType() } return p.Type } // join concatenates parameters ps into a string with sep separator. // Each parameter is converted into string by applying fn to it // before conversion. func join(ps []*Param, fn func(*Param) string, sep string) string { if len(ps) == 0 { return "" } a := make([]string, 0) for _, p := range ps { a = append(a, fn(p)) } return strings.Join(a, sep) } // Rets describes function return parameters. type Rets struct { Name string Type string ReturnsError bool FailCond string fnMaybeAbsent bool } // ErrorVarName returns error variable name for r. func (r *Rets) ErrorVarName() string { if r.ReturnsError { return varErr } if r.Type == tError { return r.Name } return "" } // ToParams converts r into slice of *Param. func (r *Rets) ToParams() []*Param { ps := make([]*Param, 0) if len(r.Name) > 0 { ps = append(ps, &Param{Name: r.Name, Type: r.Type}) } if r.ReturnsError { ps = append(ps, &Param{Name: varErr, Type: tError}) } return ps } // List returns source code of syscall return parameters. func (r *Rets) List() string { s := join(r.ToParams(), func(p *Param) string { return p.Name + " " + p.Type }, ", ") if len(s) > 0 { s = "(" + s + ")" } else if r.fnMaybeAbsent { s = "(err error)" } return s } // PrintList returns source code of trace printing part correspondent // to syscall return values. func (r *Rets) PrintList() string { return join(r.ToParams(), func(p *Param) string { return fmt.Sprintf(`"%s=", %s, `, p.Name, p.Name) }, `", ", `) } // SetReturnValuesCode returns source code that accepts syscall return values. func (r *Rets) SetReturnValuesCode() string { if r.Name == "" && !r.ReturnsError { return "" } retvar := "r0" if r.Name == "" { retvar = "r1" } errvar := "_" if r.ReturnsError { errvar = "e1" } return fmt.Sprintf("%s, _, %s := ", retvar, errvar) } func (r *Rets) useLongHandleErrorCode(retvar string) string { const code = `if %s { err = errnoErr(e1) }` cond := retvar + " == 0" if r.FailCond != "" { cond = strings.Replace(r.FailCond, "failretval", retvar, 1) } return fmt.Sprintf(code, cond) } // SetErrorCode returns source code that sets return parameters. func (r *Rets) SetErrorCode() string { const code = `if r0 != 0 { %s = %sErrno(r0) }` const ntStatus = `if r0 != 0 { %s = %sNTStatus(r0) }` const hrCode = `if int32(r0) < 0 { if r0&0x1fff0000 == 0x00070000 { r0 &= 0xffff } %s = %sErrno(r0) }` if r.Name == "" && !r.ReturnsError { return "" } if r.Name == "" { return r.useLongHandleErrorCode("r1") } if r.Type == tError { switch r.Name { 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()) } } var s string switch { case r.Type[0] == '*': s = fmt.Sprintf("%s = (%s)(unsafe.Pointer(r0))", r.Name, r.Type) case r.Type == tBool: s = fmt.Sprintf("%s = r0 != 0", r.Name) default: s = fmt.Sprintf("%s = %s(r0)", r.Name, r.Type) } if !r.ReturnsError { return s } return s + "\n\t" + r.useLongHandleErrorCode(r.Name) } // Fn describes syscall function. type Fn struct { Name string Params []*Param Rets *Rets PrintTrace bool dllname string dllfuncname string src string // TODO: get rid of this field and just use parameter index instead curTmpVarIdx int // insure tmp variables have uniq names } // extractParams parses s to extract function parameters. func extractParams(s string, f *Fn) ([]*Param, error) { s = trim(s) if s == "" { return nil, nil } a := strings.Split(s, ",") ps := make([]*Param, len(a)) for i := range ps { s2 := trim(a[i]) b := strings.Split(s2, " ") if len(b) != 2 { b = strings.Split(s2, "\t") if len(b) != 2 { return nil, errors.New("Could not extract function parameter from \"" + s2 + "\"") } } ps[i] = &Param{ Name: trim(b[0]), Type: trim(b[1]), fn: f, tmpVarIdx: -1, } } return ps, nil } // extractSection extracts text out of string s starting after start // and ending just before end. found return value will indicate success, // and prefix, body and suffix will contain correspondent parts of string s. func extractSection(s string, start, end rune) (prefix, body, suffix string, found bool) { s = trim(s) if strings.HasPrefix(s, string(start)) { // no prefix body = s[1:] } else { a := strings.SplitN(s, string(start), 2) if len(a) != 2 { return "", "", s, false } prefix = a[0] body = a[1] } a := strings.SplitN(body, string(end), 2) if len(a) != 2 { return "", "", "", false } return prefix, a[0], a[1], true } // newFn parses string s and return created function Fn. func newFn(s string) (*Fn, error) { s = trim(s) f := &Fn{ Rets: &Rets{}, src: s, PrintTrace: *printTraceFlag, } // function name and args prefix, body, s, found := extractSection(s, '(', ')') if !found || prefix == "" { return nil, errors.New("Could not extract function name and parameters from \"" + f.src + "\"") } f.Name = prefix var err error f.Params, err = extractParams(body, f) if err != nil { return nil, err } // return values _, body, s, found = extractSection(s, '(', ')') if found { r, err := extractParams(body, f) if err != nil { return nil, err } switch len(r) { case 0: case 1: if r[0].IsError() { f.Rets.ReturnsError = true } else { f.Rets.Name = r[0].Name f.Rets.Type = r[0].Type } case 2: if !r[1].IsError() { return nil, errors.New("Only last windows error is allowed as second return value in \"" + f.src + "\"") } f.Rets.ReturnsError = true f.Rets.Name = r[0].Name f.Rets.Type = r[0].Type default: return nil, errors.New("Too many return values in \"" + f.src + "\"") } } // fail condition _, body, s, found = extractSection(s, '[', ']') if found { f.Rets.FailCond = body } // dll and dll function names s = trim(s) if s == "" { return f, nil } if !strings.HasPrefix(s, "=") { return nil, errors.New("Could not extract dll name from \"" + f.src + "\"") } s = trim(s[1:]) if i := strings.LastIndex(s, "."); i >= 0 { f.dllname = s[:i] f.dllfuncname = s[i+1:] } else { f.dllfuncname = s } if f.dllfuncname == "" { return nil, fmt.Errorf("function name is not specified in %q", s) } if n := f.dllfuncname; endsIn(n, '?') { f.dllfuncname = n[:len(n)-1] f.Rets.fnMaybeAbsent = true } return f, nil } // DLLName returns DLL name for function f. func (f *Fn) DLLName() string { if f.dllname == "" { return "kernel32" } return f.dllname } // DLLVar returns a valid Go identifier that represents DLLName. func (f *Fn) DLLVar() string { id := strings.Map(func(r rune) rune { switch r { case '.', '-': return '_' default: return r } }, f.DLLName()) if !token.IsIdentifier(id) { panic(fmt.Errorf("could not create Go identifier for DLLName %q", f.DLLName())) } return id } // DLLFuncName returns DLL function name for function f. func (f *Fn) DLLFuncName() string { if f.dllfuncname == "" { return f.Name } return f.dllfuncname } // ParamList returns source code for function f parameters. func (f *Fn) ParamList() string { return join(f.Params, func(p *Param) string { return p.Name + " " + p.Type }, ", ") } // HelperParamList returns source code for helper function f parameters. func (f *Fn) HelperParamList() string { return join(f.Params, func(p *Param) string { return p.Name + " " + p.HelperType() }, ", ") } // ParamPrintList returns source code of trace printing part correspondent // to syscall input parameters. func (f *Fn) ParamPrintList() string { return join(f.Params, func(p *Param) string { return fmt.Sprintf(`"%s=", %s, `, p.Name, p.Name) }, `", ", `) } // Syscall determines which SyscallX function to use for function f. func (f *Fn) Syscall() string { return syscalldot() + "SyscallN" } // SyscallParamList returns source code for SyscallN parameters for function f. func (f *Fn) SyscallParamList() string { a := make([]string, 0, len(f.Params)) for _, p := range f.Params { a = append(a, p.SyscallArgList()...) } return strings.Join(a, ", ") } // HelperCallParamList returns source code of call into function f helper. func (f *Fn) HelperCallParamList() string { a := make([]string, 0, len(f.Params)) for _, p := range f.Params { s := p.Name if p.Type == tString { s = p.tmpVar() } a = append(a, s) } return strings.Join(a, ", ") } // MaybeAbsent returns source code for handling functions that are possibly unavailable. func (f *Fn) MaybeAbsent() string { if !f.Rets.fnMaybeAbsent { return "" } const code = `%[1]s = proc%[2]s.Find() if %[1]s != nil { return }` errorVar := f.Rets.ErrorVarName() if errorVar == "" { errorVar = varErr } return fmt.Sprintf(code, errorVar, f.DLLFuncName()) } // IsUTF16 is true, if f is W (UTF-16) function and false for all A (ASCII) functions. // Functions ending in neither will default to UTF-16, unless the `-utf16` flag is set // to `false`. func (f *Fn) IsUTF16() bool { s := f.DLLFuncName() return endsIn(s, 'W') || (*utf16 && !endsIn(s, 'A')) } // StrconvFunc returns name of Go string to OS string function for f. func (f *Fn) StrconvFunc() string { if f.IsUTF16() { return syscalldot() + "UTF16PtrFromString" } return syscalldot() + "BytePtrFromString" } // StrconvType returns Go type name used for OS string for f. func (f *Fn) StrconvType() string { if f.IsUTF16() { return "*uint16" } return "*byte" } // HasStringParam is true, if f has at least one string parameter. // Otherwise it is false. func (f *Fn) HasStringParam() bool { for _, p := range f.Params { if p.Type == tString { return true } } return false } // HelperName returns name of function f helper. func (f *Fn) HelperName() string { if !f.HasStringParam() { return f.Name } return "_" + f.Name } // DLL is a DLL's filename and a string that is valid in a Go identifier that should be used when // naming a variable that refers to the DLL. type DLL struct { Name string Var string } // Source files and functions. type Source struct { Funcs []*Fn DLLFuncNames []*Fn Files []string StdLibImports []string ExternalImports []string } func (src *Source) Import(pkg string) { src.StdLibImports = append(src.StdLibImports, pkg) sort.Strings(src.StdLibImports) } func (src *Source) ExternalImport(pkg string) { src.ExternalImports = append(src.ExternalImports, pkg) sort.Strings(src.ExternalImports) } // ParseFiles parses files listed in fs and extracts all syscall // functions listed in sys comments. It returns source files // and functions collection *Source if successful. func ParseFiles(fs []string) (*Source, error) { src := &Source{ Funcs: make([]*Fn, 0), Files: make([]string, 0), StdLibImports: []string{ "unsafe", }, ExternalImports: make([]string, 0), } for _, file := range fs { if err := src.ParseFile(file); err != nil { return nil, err } } src.DLLFuncNames = make([]*Fn, 0, len(src.Funcs)) uniq := make(map[string]bool, len(src.Funcs)) for _, fn := range src.Funcs { name := fn.DLLFuncName() if !uniq[name] { src.DLLFuncNames = append(src.DLLFuncNames, fn) uniq[name] = true } } return src, nil } // DLLs return dll names for a source set src. func (src *Source) DLLs() []DLL { uniq := make(map[string]bool) r := make([]DLL, 0) for _, f := range src.Funcs { id := f.DLLVar() if _, found := uniq[id]; !found { uniq[id] = true r = append(r, DLL{f.DLLName(), id}) } } if *sortdecls { sort.Slice(r, func(i, j int) bool { return r[i].Var < r[j].Var }) } return r } // ParseFile adds additional file (or files, if path is a glob pattern) path to a source set src. func (src *Source) ParseFile(path string) error { file, err := os.Open(path) if err == nil { defer file.Close() return src.parseFile(file) } else if !(errors.Is(err, os.ErrNotExist) || errors.Is(err, windows.ERROR_INVALID_NAME)) { return err } paths, err := filepath.Glob(path) if err != nil { return err } for _, path := range paths { file, err := os.Open(path) if err != nil { return err } err = src.parseFile(file) file.Close() if err != nil { return err } } return nil } func (src *Source) parseFile(file *os.File) error { s := bufio.NewScanner(file) for s.Scan() { t := trim(s.Text()) if len(t) < 7 { continue } if !strings.HasPrefix(t, "//sys") { continue } t = t[5:] if !(t[0] == ' ' || t[0] == '\t') { continue } f, err := newFn(t[1:]) if err != nil { return err } src.Funcs = append(src.Funcs, f) } if err := s.Err(); err != nil { return err } src.Files = append(src.Files, file.Name()) if *sortdecls { sort.Slice(src.Funcs, func(i, j int) bool { fi, fj := src.Funcs[i], src.Funcs[j] if fi.DLLName() == fj.DLLName() { return fi.DLLFuncName() < fj.DLLFuncName() } return fi.DLLName() < fj.DLLName() }) } // get package name fset := token.NewFileSet() _, err := file.Seek(0, 0) if err != nil { return err } pkg, err := parser.ParseFile(fset, "", file, parser.PackageClauseOnly) if err != nil { return err } packageName = pkg.Name.Name return nil } // IsStdRepo reports whether src is part of standard library. func (src *Source) IsStdRepo() (bool, error) { if len(src.Files) == 0 { return false, errors.New("no input files provided") } abspath, err := filepath.Abs(src.Files[0]) if err != nil { return false, err } goroot := runtime.GOROOT() if runtime.GOOS == "windows" { abspath = strings.ToLower(abspath) goroot = strings.ToLower(goroot) } sep := string(os.PathSeparator) if !strings.HasSuffix(goroot, sep) { goroot += sep } return strings.HasPrefix(abspath, goroot), nil } // Generate output source file from a source set src. func (src *Source) Generate(w io.Writer) error { const ( pkgStd = iota // any package in std library pkgXSysWindows // x/sys/windows package pkgOther ) isStdRepo, err := src.IsStdRepo() if err != nil { return err } var pkgtype int switch { case isStdRepo: pkgtype = pkgStd case packageName == "windows": // TODO: this needs better logic than just using package name pkgtype = pkgXSysWindows default: pkgtype = pkgOther } if *systemDLL { switch pkgtype { case pkgStd: src.Import("internal/syscall/windows/sysdll") case pkgXSysWindows: default: src.ExternalImport("golang.org/x/sys/windows") } } if *winio { src.ExternalImport("github.com/Microsoft/go-winio") } if packageName != "syscall" { src.Import("syscall") } funcMap := template.FuncMap{ "packagename": packagename, "syscalldot": syscalldot, "newlazydll": func(dll string) string { arg := "\"" + dll + ".dll\"" if !*systemDLL { return syscalldot() + "NewLazyDLL(" + arg + ")" } if strings.HasPrefix(dll, "api_") || strings.HasPrefix(dll, "ext_") { arg = strings.Replace(arg, "_", "-", -1) } switch pkgtype { case pkgStd: return syscalldot() + "NewLazyDLL(sysdll.Add(" + arg + "))" case pkgXSysWindows: return "NewLazySystemDLL(" + arg + ")" default: return "windows.NewLazySystemDLL(" + arg + ")" } }, } t := template.Must(template.New("main").Funcs(funcMap).Parse(srcTemplate)) err = t.Execute(w, src) if err != nil { return errors.New("Failed to execute template: " + err.Error()) } return nil } func writeTempSourceFile(data []byte) (string, error) { f, err := os.CreateTemp("", "mkwinsyscall-generated-*.go") if err != nil { return "", err } _, err = f.Write(data) if closeErr := f.Close(); err == nil { err = closeErr } if err != nil { os.Remove(f.Name()) // best effort return "", err } return f.Name(), nil } func usage() { fmt.Fprintf(os.Stderr, "usage: mkwinsyscall [flags] [path ...]\n") flag.PrintDefaults() os.Exit(1) } func main() { flag.Usage = usage flag.Parse() if len(flag.Args()) <= 0 { fmt.Fprintf(os.Stderr, "no files to parse provided\n") usage() } src, err := ParseFiles(flag.Args()) if err != nil { log.Fatal(err) } var buf bytes.Buffer if err := src.Generate(&buf); err != nil { log.Fatal(err) } data, err := format.Source(buf.Bytes()) if err != nil { log.Printf("failed to format source: %v", err) f, err := writeTempSourceFile(buf.Bytes()) if err != nil { log.Fatalf("failed to write unformatted source to file: %v", err) } log.Fatalf("for diagnosis, wrote unformatted source to %v", f) } if *filename == "" { _, err = os.Stdout.Write(data) } else { //nolint:gosec // G306: code file, no need for wants 0600 err = os.WriteFile(*filename, data, 0644) } if err != nil { log.Fatal(err) } } const srcTemplate = ` {{define "main"}} //go:build windows // Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT. package {{packagename}} import ( {{range .StdLibImports}}"{{.}}" {{end}} {{range .ExternalImports}}"{{.}}" {{end}} ) var _ unsafe.Pointer // Do the interface allocations only once for common // Errno values. const ( errnoERROR_IO_PENDING = 997 ) var ( errERROR_IO_PENDING error = {{syscalldot}}Errno(errnoERROR_IO_PENDING) errERROR_EINVAL error = {{syscalldot}}EINVAL ) // errnoErr returns common boxed Errno values, to prevent // allocations at runtime. func errnoErr(e {{syscalldot}}Errno) error { switch e { case 0: return errERROR_EINVAL case errnoERROR_IO_PENDING: return errERROR_IO_PENDING } {{- /* TODO: add more here, after collecting data on the common error values see on Windows. (perhaps when running all.bat? */}} return e } var ( {{template "dlls" .}} {{template "funcnames" .}}) {{range .Funcs}}{{if .HasStringParam}}{{template "helperbody" .}}{{end}}{{template "funcbody" .}}{{end}} {{end}} {{/* help functions */}} {{define "dlls"}}{{range .DLLs}} mod{{.Var}} = {{newlazydll .Name}} {{end}}{{end}} {{define "funcnames"}}{{range .DLLFuncNames}} proc{{.DLLFuncName}} = mod{{.DLLVar}}.NewProc("{{.DLLFuncName}}") {{end}}{{end}} {{define "helperbody"}} func {{.Name}}({{.ParamList}}) {{template "results" .}}{ {{template "helpertmpvars" .}} return {{.HelperName}}({{.HelperCallParamList}}) } {{end}} {{define "funcbody"}} func {{.HelperName}}({{.HelperParamList}}) {{template "results" .}}{ {{template "maybeabsent" .}} {{template "tmpvars" .}} {{template "syscall" .}} {{template "tmpvarsreadback" .}} {{template "seterror" .}}{{template "printtrace" .}} return } {{end}} {{define "helpertmpvars"}}{{range .Params}}{{if .TmpVarHelperCode}} {{.TmpVarHelperCode}} {{end}}{{end}}{{end}} {{define "maybeabsent"}}{{if .MaybeAbsent}}{{.MaybeAbsent}} {{end}}{{end}} {{define "tmpvars"}}{{range .Params}}{{if .TmpVarCode}} {{.TmpVarCode}} {{end}}{{end}}{{end}} {{define "results"}}{{if .Rets.List}}{{.Rets.List}} {{end}}{{end}} {{define "syscall"}}{{.Rets.SetReturnValuesCode}}{{.Syscall}}(proc{{.DLLFuncName}}.Addr(), {{.SyscallParamList}}){{end}} {{define "tmpvarsreadback"}}{{range .Params}}{{if .TmpVarReadbackCode}} {{.TmpVarReadbackCode}}{{end}}{{end}}{{end}} {{define "seterror"}}{{if .Rets.SetErrorCode}} {{.Rets.SetErrorCode}} {{end}}{{end}} {{define "printtrace"}}{{if .PrintTrace}} print("SYSCALL: {{.Name}}(", {{.ParamPrintList}}") (", {{.Rets.PrintList}}")\n") {{end}}{{end}} ` go-winio-0.6.2/tools/tools.go000066400000000000000000000010201460531775000160770ustar00rootroot00000000000000//go:build tools // This package contains imports to various tools used (eg, via `//go:generate`) within this repo. // // Calls to `go run ` (or `//go:generate go run `) for go executables // included here will use the version specified in `go.mod` and build the executable from vendored code. // // Based on golang [guidance]. // // [guidance]: https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module package tools import _ "golang.org/x/tools/cmd/stringer" go-winio-0.6.2/vhd/000077500000000000000000000000001460531775000140405ustar00rootroot00000000000000go-winio-0.6.2/vhd/vhd.go000066400000000000000000000320011460531775000151440ustar00rootroot00000000000000//go:build windows // +build windows package vhd import ( "fmt" "syscall" "github.com/Microsoft/go-winio/pkg/guid" "golang.org/x/sys/windows" ) //go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zvhd_windows.go vhd.go //sys createVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (win32err error) = virtdisk.CreateVirtualDisk //sys openVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *openVirtualDiskParameters, handle *syscall.Handle) (win32err error) = virtdisk.OpenVirtualDisk //sys attachVirtualDisk(handle syscall.Handle, securityDescriptor *uintptr, attachVirtualDiskFlag uint32, providerSpecificFlags uint32, parameters *AttachVirtualDiskParameters, overlapped *syscall.Overlapped) (win32err error) = virtdisk.AttachVirtualDisk //sys detachVirtualDisk(handle syscall.Handle, detachVirtualDiskFlags uint32, providerSpecificFlags uint32) (win32err error) = virtdisk.DetachVirtualDisk //sys getVirtualDiskPhysicalPath(handle syscall.Handle, diskPathSizeInBytes *uint32, buffer *uint16) (win32err error) = virtdisk.GetVirtualDiskPhysicalPath type ( CreateVirtualDiskFlag uint32 VirtualDiskFlag uint32 AttachVirtualDiskFlag uint32 DetachVirtualDiskFlag uint32 VirtualDiskAccessMask uint32 ) type VirtualStorageType struct { DeviceID uint32 VendorID guid.GUID } type CreateVersion2 struct { UniqueID guid.GUID MaximumSize uint64 BlockSizeInBytes uint32 SectorSizeInBytes uint32 PhysicalSectorSizeInByte uint32 ParentPath *uint16 // string SourcePath *uint16 // string OpenFlags uint32 ParentVirtualStorageType VirtualStorageType SourceVirtualStorageType VirtualStorageType ResiliencyGUID guid.GUID } type CreateVirtualDiskParameters struct { Version uint32 // Must always be set to 2 Version2 CreateVersion2 } type OpenVersion2 struct { GetInfoOnly bool ReadOnly bool ResiliencyGUID guid.GUID } type OpenVirtualDiskParameters struct { Version uint32 // Must always be set to 2 Version2 OpenVersion2 } // 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 readOnly int32 resiliencyGUID guid.GUID } type openVirtualDiskParameters struct { version uint32 version2 openVersion2 } type AttachVersion2 struct { RestrictedOffset uint64 RestrictedLength uint64 } type AttachVirtualDiskParameters struct { Version uint32 Version2 AttachVersion2 } const ( //revive:disable-next-line:var-naming ALL_CAPS VIRTUAL_STORAGE_TYPE_DEVICE_VHDX = 0x3 // Access Mask for opening a VHD. VirtualDiskAccessNone VirtualDiskAccessMask = 0x00000000 VirtualDiskAccessAttachRO VirtualDiskAccessMask = 0x00010000 VirtualDiskAccessAttachRW VirtualDiskAccessMask = 0x00020000 VirtualDiskAccessDetach VirtualDiskAccessMask = 0x00040000 VirtualDiskAccessGetInfo VirtualDiskAccessMask = 0x00080000 VirtualDiskAccessCreate VirtualDiskAccessMask = 0x00100000 VirtualDiskAccessMetaOps VirtualDiskAccessMask = 0x00200000 VirtualDiskAccessRead VirtualDiskAccessMask = 0x000d0000 VirtualDiskAccessAll VirtualDiskAccessMask = 0x003f0000 VirtualDiskAccessWritable VirtualDiskAccessMask = 0x00320000 // Flags for creating a VHD. CreateVirtualDiskFlagNone CreateVirtualDiskFlag = 0x0 CreateVirtualDiskFlagFullPhysicalAllocation CreateVirtualDiskFlag = 0x1 CreateVirtualDiskFlagPreventWritesToSourceDisk CreateVirtualDiskFlag = 0x2 CreateVirtualDiskFlagDoNotCopyMetadataFromParent CreateVirtualDiskFlag = 0x4 CreateVirtualDiskFlagCreateBackingStorage CreateVirtualDiskFlag = 0x8 CreateVirtualDiskFlagUseChangeTrackingSourceLimit CreateVirtualDiskFlag = 0x10 CreateVirtualDiskFlagPreserveParentChangeTrackingState CreateVirtualDiskFlag = 0x20 CreateVirtualDiskFlagVhdSetUseOriginalBackingStorage CreateVirtualDiskFlag = 0x40 //revive:disable-line:var-naming VHD, not Vhd CreateVirtualDiskFlagSparseFile CreateVirtualDiskFlag = 0x80 CreateVirtualDiskFlagPmemCompatible CreateVirtualDiskFlag = 0x100 //revive:disable-line:var-naming PMEM, not Pmem CreateVirtualDiskFlagSupportCompressedVolumes CreateVirtualDiskFlag = 0x200 // Flags for opening a VHD. OpenVirtualDiskFlagNone VirtualDiskFlag = 0x00000000 OpenVirtualDiskFlagNoParents VirtualDiskFlag = 0x00000001 OpenVirtualDiskFlagBlankFile VirtualDiskFlag = 0x00000002 OpenVirtualDiskFlagBootDrive VirtualDiskFlag = 0x00000004 OpenVirtualDiskFlagCachedIO VirtualDiskFlag = 0x00000008 OpenVirtualDiskFlagCustomDiffChain VirtualDiskFlag = 0x00000010 OpenVirtualDiskFlagParentCachedIO VirtualDiskFlag = 0x00000020 OpenVirtualDiskFlagVhdsetFileOnly VirtualDiskFlag = 0x00000040 OpenVirtualDiskFlagIgnoreRelativeParentLocator VirtualDiskFlag = 0x00000080 OpenVirtualDiskFlagNoWriteHardening VirtualDiskFlag = 0x00000100 OpenVirtualDiskFlagSupportCompressedVolumes VirtualDiskFlag = 0x00000200 // Flags for attaching a VHD. AttachVirtualDiskFlagNone AttachVirtualDiskFlag = 0x00000000 AttachVirtualDiskFlagReadOnly AttachVirtualDiskFlag = 0x00000001 AttachVirtualDiskFlagNoDriveLetter AttachVirtualDiskFlag = 0x00000002 AttachVirtualDiskFlagPermanentLifetime AttachVirtualDiskFlag = 0x00000004 AttachVirtualDiskFlagNoLocalHost AttachVirtualDiskFlag = 0x00000008 AttachVirtualDiskFlagNoSecurityDescriptor AttachVirtualDiskFlag = 0x00000010 AttachVirtualDiskFlagBypassDefaultEncryptionPolicy AttachVirtualDiskFlag = 0x00000020 AttachVirtualDiskFlagNonPnp AttachVirtualDiskFlag = 0x00000040 AttachVirtualDiskFlagRestrictedRange AttachVirtualDiskFlag = 0x00000080 AttachVirtualDiskFlagSinglePartition AttachVirtualDiskFlag = 0x00000100 AttachVirtualDiskFlagRegisterVolume AttachVirtualDiskFlag = 0x00000200 // 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, Version2: CreateVersion2{ MaximumSize: uint64(maxSizeInGb) * 1024 * 1024 * 1024, BlockSizeInBytes: blockSizeInMb * 1024 * 1024, }, } handle, err := CreateVirtualDisk(path, VirtualDiskAccessNone, CreateVirtualDiskFlagNone, ¶ms) if err != nil { return err } return syscall.CloseHandle(handle) } // DetachVirtualDisk detaches a virtual hard disk by handle. func DetachVirtualDisk(handle syscall.Handle) (err error) { if err := detachVirtualDisk(handle, 0, 0); err != nil { return fmt.Errorf("failed to detach virtual disk: %w", err) } return nil } // 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, VirtualDiskAccessNone, OpenVirtualDiskFlagCachedIO|OpenVirtualDiskFlagIgnoreRelativeParentLocator, ) if err != nil { return err } 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) { // Supports both version 1 and 2 of the attach parameters as version 2 wasn't present in RS5. if err := attachVirtualDisk( handle, nil, uint32(attachVirtualDiskFlag), 0, parameters, nil, ); err != nil { return fmt.Errorf("failed to attach virtual disk: %w", err) } return nil } // 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, VirtualDiskAccessNone, OpenVirtualDiskFlagCachedIO|OpenVirtualDiskFlagIgnoreRelativeParentLocator, ) if err != nil { return err } defer syscall.CloseHandle(handle) //nolint:errcheck params := AttachVirtualDiskParameters{Version: 2} if err := AttachVirtualDisk( handle, AttachVirtualDiskFlagNone, ¶ms, ); err != nil { return fmt.Errorf("failed to attach virtual disk: %w", err) } return nil } // 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) { parameters := OpenVirtualDiskParameters{Version: 2} handle, err := OpenVirtualDiskWithParameters( vhdPath, virtualDiskAccessMask, openVirtualDiskFlags, ¶meters, ) if err != nil { return 0, err } return handle, nil } // 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) { var ( handle syscall.Handle defaultType VirtualStorageType getInfoOnly int32 readOnly int32 ) if parameters.Version != 2 { return handle, fmt.Errorf("only version 2 VHDs are supported, found version: %d", parameters.Version) } if parameters.Version2.GetInfoOnly { getInfoOnly = 1 } if parameters.Version2.ReadOnly { readOnly = 1 } params := &openVirtualDiskParameters{ version: parameters.Version, version2: openVersion2{ getInfoOnly, readOnly, parameters.Version2.ResiliencyGUID, }, } if err := openVirtualDisk( &defaultType, vhdPath, uint32(virtualDiskAccessMask), uint32(openVirtualDiskFlags), params, &handle, ); err != nil { return 0, fmt.Errorf("failed to open virtual disk: %w", err) } return handle, nil } // 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) { var ( handle syscall.Handle defaultType VirtualStorageType ) if parameters.Version != 2 { return handle, fmt.Errorf("only version 2 VHDs are supported, found version: %d", parameters.Version) } if err := createVirtualDisk( &defaultType, path, uint32(virtualDiskAccessMask), nil, uint32(createVirtualDiskFlags), 0, parameters, nil, &handle, ); err != nil { return handle, fmt.Errorf("failed to create virtual disk: %w", err) } return handle, nil } // GetVirtualDiskPhysicalPath takes a handle to a virtual hard disk and returns the physical // path of the disk on the machine. This path is in the form \\.\PhysicalDriveX where X is an integer // that represents the particular enumeration of the physical disk on the caller's system. func GetVirtualDiskPhysicalPath(handle syscall.Handle) (_ string, err error) { var ( diskPathSizeInBytes uint32 = 256 * 2 // max path length 256 wide chars diskPhysicalPathBuf [256]uint16 ) if err := getVirtualDiskPhysicalPath( handle, &diskPathSizeInBytes, &diskPhysicalPathBuf[0], ); err != nil { return "", fmt.Errorf("failed to get disk physical path: %w", err) } return windows.UTF16ToString(diskPhysicalPathBuf[:]), nil } // 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{ Version: 2, Version2: CreateVersion2{ ParentPath: windows.StringToUTF16Ptr(baseVhdPath), BlockSizeInBytes: blockSizeInMB * 1024 * 1024, OpenFlags: uint32(OpenVirtualDiskFlagCachedIO), }, } vhdHandle, err := CreateVirtualDisk( diffVhdPath, VirtualDiskAccessNone, CreateVirtualDiskFlagNone, createParams, ) if err != nil { return fmt.Errorf("failed to create differencing vhd: %w", err) } if err := syscall.CloseHandle(vhdHandle); err != nil { return fmt.Errorf("failed to close differencing vhd handle: %w", err) } return nil } go-winio-0.6.2/vhd/zvhd_windows.go000066400000000000000000000111151460531775000171130ustar00rootroot00000000000000//go:build windows // Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT. package vhd import ( "syscall" "unsafe" "golang.org/x/sys/windows" ) var _ unsafe.Pointer // Do the interface allocations only once for common // Errno values. const ( errnoERROR_IO_PENDING = 997 ) var ( errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) errERROR_EINVAL error = syscall.EINVAL ) // errnoErr returns common boxed Errno values, to prevent // allocations at runtime. func errnoErr(e syscall.Errno) error { switch e { case 0: return errERROR_EINVAL case errnoERROR_IO_PENDING: return errERROR_IO_PENDING } return e } var ( modvirtdisk = windows.NewLazySystemDLL("virtdisk.dll") procAttachVirtualDisk = modvirtdisk.NewProc("AttachVirtualDisk") procCreateVirtualDisk = modvirtdisk.NewProc("CreateVirtualDisk") procDetachVirtualDisk = modvirtdisk.NewProc("DetachVirtualDisk") procGetVirtualDiskPhysicalPath = modvirtdisk.NewProc("GetVirtualDiskPhysicalPath") procOpenVirtualDisk = modvirtdisk.NewProc("OpenVirtualDisk") ) func attachVirtualDisk(handle syscall.Handle, securityDescriptor *uintptr, attachVirtualDiskFlag uint32, providerSpecificFlags uint32, parameters *AttachVirtualDiskParameters, overlapped *syscall.Overlapped) (win32err error) { r0, _, _ := syscall.SyscallN(procAttachVirtualDisk.Addr(), uintptr(handle), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(attachVirtualDiskFlag), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(overlapped))) if r0 != 0 { win32err = syscall.Errno(r0) } return } func createVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (win32err error) { var _p0 *uint16 _p0, win32err = syscall.UTF16PtrFromString(path) if win32err != nil { return } return _createVirtualDisk(virtualStorageType, _p0, virtualDiskAccessMask, securityDescriptor, createVirtualDiskFlags, providerSpecificFlags, parameters, overlapped, handle) } func _createVirtualDisk(virtualStorageType *VirtualStorageType, path *uint16, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (win32err error) { r0, _, _ := syscall.SyscallN(procCreateVirtualDisk.Addr(), uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(createVirtualDiskFlags), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(handle))) if r0 != 0 { win32err = syscall.Errno(r0) } return } func detachVirtualDisk(handle syscall.Handle, detachVirtualDiskFlags uint32, providerSpecificFlags uint32) (win32err error) { r0, _, _ := syscall.SyscallN(procDetachVirtualDisk.Addr(), uintptr(handle), uintptr(detachVirtualDiskFlags), uintptr(providerSpecificFlags)) if r0 != 0 { win32err = syscall.Errno(r0) } return } func getVirtualDiskPhysicalPath(handle syscall.Handle, diskPathSizeInBytes *uint32, buffer *uint16) (win32err error) { r0, _, _ := syscall.SyscallN(procGetVirtualDiskPhysicalPath.Addr(), uintptr(handle), uintptr(unsafe.Pointer(diskPathSizeInBytes)), uintptr(unsafe.Pointer(buffer))) if r0 != 0 { win32err = syscall.Errno(r0) } return } func openVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *openVirtualDiskParameters, handle *syscall.Handle) (win32err error) { var _p0 *uint16 _p0, win32err = syscall.UTF16PtrFromString(path) if win32err != nil { return } return _openVirtualDisk(virtualStorageType, _p0, virtualDiskAccessMask, openVirtualDiskFlags, parameters, handle) } func _openVirtualDisk(virtualStorageType *VirtualStorageType, path *uint16, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *openVirtualDiskParameters, handle *syscall.Handle) (win32err error) { r0, _, _ := syscall.SyscallN(procOpenVirtualDisk.Addr(), uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(openVirtualDiskFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(handle))) if r0 != 0 { win32err = syscall.Errno(r0) } return } go-winio-0.6.2/wim/000077500000000000000000000000001460531775000140535ustar00rootroot00000000000000go-winio-0.6.2/wim/decompress.go000066400000000000000000000051471460531775000165550ustar00rootroot00000000000000//go:build windows || linux // +build windows linux package wim import ( "encoding/binary" "io" "github.com/Microsoft/go-winio/wim/lzx" ) const chunkSize = 32768 // Compressed resource chunk size type compressedReader struct { r *io.SectionReader d io.ReadCloser chunks []int64 curChunk int originalSize int64 } func newCompressedReader(r *io.SectionReader, originalSize int64, offset int64) (*compressedReader, error) { nchunks := (originalSize + chunkSize - 1) / chunkSize var base int64 chunks := make([]int64, nchunks) if originalSize <= 0xffffffff { // 32-bit chunk offsets base = (nchunks - 1) * 4 chunks32 := make([]uint32, nchunks-1) err := binary.Read(r, binary.LittleEndian, chunks32) if err != nil { return nil, err } for i, n := range chunks32 { chunks[i+1] = int64(n) } } else { // 64-bit chunk offsets base = (nchunks - 1) * 8 err := binary.Read(r, binary.LittleEndian, chunks[1:]) if err != nil { return nil, err } } for i, c := range chunks { chunks[i] = c + base } cr := &compressedReader{ r: r, chunks: chunks, originalSize: originalSize, } err := cr.reset(int(offset / chunkSize)) if err != nil { return nil, err } suboff := offset % chunkSize if suboff != 0 { _, err := io.CopyN(io.Discard, cr.d, suboff) if err != nil { return nil, err } } return cr, nil } func (r *compressedReader) chunkOffset(n int) int64 { if n == len(r.chunks) { return r.r.Size() } return r.chunks[n] } func (r *compressedReader) chunkSize(n int) int { return int(r.chunkOffset(n+1) - r.chunkOffset(n)) } func (r *compressedReader) uncompressedSize(n int) int { if n < len(r.chunks)-1 { return chunkSize } size := int(r.originalSize % chunkSize) if size == 0 { size = chunkSize } return size } func (r *compressedReader) reset(n int) error { if n >= len(r.chunks) { return io.EOF } if r.d != nil { r.d.Close() } r.curChunk = n size := r.chunkSize(n) uncompressedSize := r.uncompressedSize(n) section := io.NewSectionReader(r.r, r.chunkOffset(n), int64(size)) if size != uncompressedSize { d, err := lzx.NewReader(section, uncompressedSize) if err != nil { return err } r.d = d } else { r.d = io.NopCloser(section) } return nil } func (r *compressedReader) Read(b []byte) (int, error) { for { n, err := r.d.Read(b) if err != io.EOF { //nolint:errorlint return n, err } err = r.reset(r.curChunk + 1) if err != nil { return n, err } } } func (r *compressedReader) Close() error { var err error if r.d != nil { err = r.d.Close() r.d = nil } return err } go-winio-0.6.2/wim/lzx/000077500000000000000000000000001460531775000146705ustar00rootroot00000000000000go-winio-0.6.2/wim/lzx/lzx.go000066400000000000000000000341041460531775000160360ustar00rootroot00000000000000// Package lzx implements a decompressor for the the WIM variant of the // LZX compression algorithm. // // The LZX algorithm is an earlier variant of LZX DELTA, which is documented // at https://msdn.microsoft.com/en-us/library/cc483133(v=exchg.80).aspx. package lzx import ( "bytes" "encoding/binary" "errors" "io" ) const ( maincodecount = 496 maincodesplit = 256 lencodecount = 249 lenshift = 9 codemask = 0x1ff tablebits = 9 tablesize = 1 << tablebits maxBlockSize = 32768 windowSize = 32768 maxTreePathLen = 16 e8filesize = 12000000 maxe8offset = 0x3fffffff verbatimBlock = 1 alignedOffsetBlock = 2 uncompressedBlock = 3 ) var footerBits = [...]byte{ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, } var basePosition = [...]uint16{ 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576, 32768, } var ( errCorrupt = errors.New("LZX data corrupt") ) // Reader is an interface used by the decompressor to access // the input stream. If the provided io.Reader does not implement // Reader, then a bufio.Reader is used. type Reader interface { io.Reader io.ByteReader } type decompressor struct { r io.Reader err error unaligned bool nbits byte c uint32 lru [3]uint16 uncompressed int windowReader *bytes.Reader mainlens [maincodecount]byte lenlens [lencodecount]byte window [windowSize]byte b []byte bv int bo int } //go:noinline func (f *decompressor) fail(err error) { if f.err == nil { f.err = err } f.bo = 0 f.bv = 0 } func (f *decompressor) ensureAtLeast(n int) error { if f.bv-f.bo >= n { return nil } if f.err != nil { return f.err } if f.bv != f.bo { copy(f.b[:f.bv-f.bo], f.b[f.bo:f.bv]) } n, err := io.ReadAtLeast(f.r, f.b[f.bv-f.bo:], n) if err != nil { if err == io.EOF { //nolint:errorlint err = io.ErrUnexpectedEOF } else { f.fail(err) } return err } f.bv = f.bv - f.bo + n f.bo = 0 return nil } // feed retrieves another 16-bit word from the stream and consumes // it into f.c. It returns false if there are no more bytes available. // Otherwise, on error, it sets f.err. func (f *decompressor) feed() bool { err := f.ensureAtLeast(2) 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 f.bo += 2 return true } // getBits retrieves the next n bits from the byte stream. n // must be <= 16. It sets f.err on error. func (f *decompressor) getBits(n byte) uint16 { if f.nbits < n { if !f.feed() { f.fail(io.ErrUnexpectedEOF) } } c := uint16(f.c >> (32 - n)) f.c <<= n f.nbits -= n return c } type huffman struct { extra [][]uint16 maxbits byte table [tablesize]uint16 } // buildTable builds a huffman decoding table from a slice of code lengths, // one per code, in order. Each code length must be <= maxTreePathLen. // See https://en.wikipedia.org/wiki/Canonical_Huffman_code. func buildTable(codelens []byte) *huffman { // Determine the number of codes of each length, and the // maximum length. var count [maxTreePathLen + 1]uint var max byte for _, cl := range codelens { count[cl]++ if max < cl { max = cl } } if max == 0 { return &huffman{} } // Determine the first code of each length. var first [maxTreePathLen + 1]uint code := uint(0) for i := byte(1); i <= max; i++ { code <<= 1 first[i] = code code += count[i] } if code != 1< tablebits, split long codes into additional tables // of suffixes of max-tablebits length. h := &huffman{maxbits: max} if max > tablebits { core := first[tablebits+1] / 2 // Number of codes that fit without extra tables nextra := 1<> (cl - tablebits) suffix := code & (1<<(cl-tablebits) - 1) extendedCode := suffix << (max - cl) for j := uint(0); j < 1<<(max-cl); j++ { h.extra[h.table[prefix]][extendedCode+j] = v } } } } return h } // getCode retrieves the next code using the provided // huffman tree. It sets f.err on error. func (f *decompressor) getCode(h *huffman) uint16 { if h.maxbits > 0 { if f.nbits < maxTreePathLen { f.feed() } // For codes with length < tablebits, it doesn't matter // what the remainder of the bits used for table lookup // are, since entries with all possible suffixes were // added to the table. c := h.table[f.c>>(32-tablebits)] if !(c >= 1<>(32-(h.maxbits-tablebits))] } n := byte(c >> lenshift) if f.nbits >= n { // Only consume the length of the code, not the maximum // code length. f.c <<= n f.nbits -= n return c & codemask } f.fail(io.ErrUnexpectedEOF) return 0 } // This is an empty tree. It should not be used. f.fail(errCorrupt) return 0 } // readTree updates the huffman tree path lengths in lens by // reading and decoding lengths from the byte stream. lens // should be prepopulated with the previous block's tree's path // lengths. For the first block, lens should be zero. func (f *decompressor) readTree(lens []byte) error { // Get the pre-tree for the main tree. var pretreeLen [20]byte for i := range pretreeLen { pretreeLen[i] = byte(f.getBits(4)) } if f.err != nil { return f.err } h := buildTable(pretreeLen[:]) // The lengths are encoded as a series of huffman codes // encoded by the pre-tree. for i := 0; i < len(lens); { c := byte(f.getCode(h)) if f.err != nil { return f.err } switch { case c <= 16: // length is delta from previous length lens[i] = (lens[i] + 17 - c) % 17 i++ case c == 17: // next n + 4 lengths are zero zeroes := int(f.getBits(4)) + 4 if i+zeroes > len(lens) { return errCorrupt } for j := 0; j < zeroes; j++ { lens[i+j] = 0 } i += zeroes case c == 18: // next n + 20 lengths are zero zeroes := int(f.getBits(5)) + 20 if i+zeroes > len(lens) { return errCorrupt } for j := 0; j < zeroes; j++ { lens[i+j] = 0 } i += zeroes case c == 19: // next n + 4 lengths all have the same value same := int(f.getBits(1)) + 4 if i+same > len(lens) { return errCorrupt } c = byte(f.getCode(h)) if c > 16 { return errCorrupt } l := (lens[i] + 17 - c) % 17 for j := 0; j < same; j++ { lens[i+j] = l } i += same default: return errCorrupt } } if f.err != nil { return f.err } return nil } func (f *decompressor) readBlockHeader() (byte, uint16, error) { // If the previous block was an unaligned uncompressed block, restore // 2-byte alignment. if f.unaligned { err := f.ensureAtLeast(1) if err != nil { return 0, 0, err } f.bo++ f.unaligned = false } blockType := f.getBits(3) full := f.getBits(1) var blockSize uint16 if full != 0 { blockSize = maxBlockSize } else { blockSize = f.getBits(16) if blockSize > maxBlockSize { return 0, 0, errCorrupt } } if f.err != nil { return 0, 0, f.err } switch blockType { case verbatimBlock, alignedOffsetBlock: // The caller will read the huffman trees. case uncompressedBlock: if f.nbits > 16 { panic("impossible: more than one 16-bit word remains") } // Drop the remaining bits in the current 16-bit word // If there are no bits left, discard a full 16-bit word. n := f.nbits if n == 0 { n = 16 } f.getBits(n) // Read the LRU values for the next block. err := f.ensureAtLeast(12) if err != nil { return 0, 0, err } f.lru[0] = uint16(binary.LittleEndian.Uint32(f.b[f.bo : f.bo+4])) f.lru[1] = uint16(binary.LittleEndian.Uint32(f.b[f.bo+4 : f.bo+8])) f.lru[2] = uint16(binary.LittleEndian.Uint32(f.b[f.bo+8 : f.bo+12])) f.bo += 12 default: return 0, 0, errCorrupt } return byte(blockType), blockSize, nil } // readTrees reads the two or three huffman trees for the current block. // readAligned specifies whether to read the aligned offset tree. func (f *decompressor) readTrees(readAligned bool) (main *huffman, length *huffman, aligned *huffman, err error) { // Aligned offset blocks start with a small aligned offset tree. if readAligned { var alignedLen [8]byte for i := range alignedLen { alignedLen[i] = byte(f.getBits(3)) } aligned = buildTable(alignedLen[:]) if aligned == nil { 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 main, length, aligned, err } err = f.readTree(f.mainlens[maincodesplit:]) if err != nil { return main, length, aligned, err } main = buildTable(f.mainlens[:]) if main == nil { 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 main, length, aligned, err } length = buildTable(f.lenlens[:]) if length == nil { return main, length, aligned, errors.New("corrupt") } return main, length, aligned, f.err } // readCompressedBlock decodes a compressed block, writing into the window // starting at start and ending at end, and using the provided huffman trees. func (f *decompressor) readCompressedBlock(start, end uint16, hmain, hlength, haligned *huffman) (int, error) { i := start for i < end { main := f.getCode(hmain) if f.err != nil { break } if main < 256 { // Literal byte. f.window[i] = byte(main) i++ continue } // This is a match backward in the window. Determine // the offset and dlength. matchlen := (main - 256) % 8 slot := (main - 256) / 8 // The length is either the low bits of the code, // or if this is 7, is encoded with the length tree. if matchlen == 7 { matchlen += f.getCode(hlength) } matchlen += 2 var matchoffset uint16 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] f.lru[0] = matchoffset } else { // The offset is encoded as a combination of the // slot and more bits from the bit stream. offsetbits := footerBits[slot] var verbatimbits, alignedbits uint16 if offsetbits > 0 { if haligned != nil && offsetbits >= 3 { // This is an aligned offset block. Combine // the bits written verbatim with the aligned // offset tree code. verbatimbits = f.getBits(offsetbits-3) * 8 alignedbits = f.getCode(haligned) } else { // There are no aligned offset bits to read, // only verbatim bits. verbatimbits = f.getBits(offsetbits) alignedbits = 0 } } matchoffset = basePosition[slot] + verbatimbits + alignedbits - 2 // Update the LRU cache. f.lru[2] = f.lru[1] f.lru[1] = f.lru[0] f.lru[0] = matchoffset } if !(matchoffset <= i && matchlen <= end-i) { f.fail(errCorrupt) break } copyend := i + matchlen for ; i < copyend; i++ { f.window[i] = f.window[i-matchoffset] } } return int(i - start), f.err } // readBlock decodes the current block and returns the number of uncompressed bytes. func (f *decompressor) readBlock(start uint16) (int, error) { blockType, size, err := f.readBlockHeader() if err != nil { return 0, err } if blockType == uncompressedBlock { if size%2 == 1 { // Remember to realign the byte stream at the next block. f.unaligned = true } copied := 0 if f.bo < f.bv { copied = int(size) s := int(start) if copied > f.bv-f.bo { copied = f.bv - f.bo } copy(f.window[s:s+copied], f.b[f.bo:f.bo+copied]) f.bo += copied } n, err := io.ReadFull(f.r, f.window[start+uint16(copied):start+size]) return copied + n, err } hmain, hlength, haligned, err := f.readTrees(blockType == alignedOffsetBlock) if err != nil { return 0, err } return f.readCompressedBlock(start, start+size, hmain, hlength, haligned) } // decodeE8 reverses the 0xe8 x86 instruction encoding that was performed // to the uncompressed data before it was compressed. func decodeE8(b []byte, off int64) { if off > maxe8offset || len(b) < 10 { return } for i := 0; i < len(b)-10; i++ { if b[i] == 0xe8 { currentPtr := int32(off) + int32(i) abs := int32(binary.LittleEndian.Uint32(b[i+1 : i+5])) if abs >= -currentPtr && abs < e8filesize { var rel int32 if abs >= 0 { rel = abs - currentPtr } else { rel = abs + e8filesize } binary.LittleEndian.PutUint32(b[i+1:i+5], uint32(rel)) } i += 4 } } } func (f *decompressor) Read(b []byte) (int, error) { // Read and uncompress everything. if f.windowReader == nil { n := 0 for n < f.uncompressed { k, err := f.readBlock(uint16(n)) if err != nil { return 0, err } n += k } decodeE8(f.window[:f.uncompressed], 0) f.windowReader = bytes.NewReader(f.window[:f.uncompressed]) } // Just read directly from the window. return f.windowReader.Read(b) } func (*decompressor) Close() error { return nil } // NewReader returns a new io.ReadCloser that decompresses a // WIM LZX stream until uncompressedSize bytes have been returned. func NewReader(r io.Reader, uncompressedSize int) (io.ReadCloser, error) { if uncompressedSize > windowSize { return nil, errors.New("uncompressed size is limited to 32KB") } f := &decompressor{ lru: [3]uint16{1, 1, 1}, uncompressed: uncompressedSize, b: make([]byte, 4096), r: r, } return f, nil } go-winio-0.6.2/wim/validate/000077500000000000000000000000001460531775000156445ustar00rootroot00000000000000go-winio-0.6.2/wim/validate/main_other.go000066400000000000000000000001051460531775000203140ustar00rootroot00000000000000//go:build !windows // +build !windows package main func main() {} go-winio-0.6.2/wim/validate/main_windows.go000066400000000000000000000013331460531775000206710ustar00rootroot00000000000000//go:build windows // +build windows package main import ( "flag" "fmt" "os" "github.com/Microsoft/go-winio/wim" ) func main() { flag.Parse() f, err := os.Open(flag.Arg(0)) if err != nil { panic(err) } w, err := wim.NewReader(f) if err != nil { panic(err) } fmt.Printf("%#v\n%#v\n", w.Image[0], w.Image[0].Windows) dir, err := w.Image[0].Open() if err != nil { panic(err) } err = recur(dir) if err != nil { panic(err) } } func recur(d *wim.File) error { files, err := d.Readdir() if err != nil { 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: %w", f.Name, err) } } } return nil } go-winio-0.6.2/wim/wim.go000066400000000000000000000535401460531775000152050ustar00rootroot00000000000000//go:build windows || linux // +build windows linux // Package wim implements a WIM file parser. // // WIM files are used to distribute Windows file system and container images. // They are documented at https://msdn.microsoft.com/en-us/library/windows/desktop/dd861280.aspx. package wim import ( "bytes" "crypto/sha1" //nolint:gosec // not used for secure application "encoding/binary" "encoding/xml" "errors" "fmt" "io" "strconv" "sync" "time" "unicode/utf16" ) // File attribute constants from Windows. // //nolint:revive // var-naming: ALL_CAPS const ( FILE_ATTRIBUTE_READONLY = 0x00000001 FILE_ATTRIBUTE_HIDDEN = 0x00000002 FILE_ATTRIBUTE_SYSTEM = 0x00000004 FILE_ATTRIBUTE_DIRECTORY = 0x00000010 FILE_ATTRIBUTE_ARCHIVE = 0x00000020 FILE_ATTRIBUTE_DEVICE = 0x00000040 FILE_ATTRIBUTE_NORMAL = 0x00000080 FILE_ATTRIBUTE_TEMPORARY = 0x00000100 FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200 FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400 FILE_ATTRIBUTE_COMPRESSED = 0x00000800 FILE_ATTRIBUTE_OFFLINE = 0x00001000 FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000 FILE_ATTRIBUTE_ENCRYPTED = 0x00004000 FILE_ATTRIBUTE_INTEGRITY_STREAM = 0x00008000 FILE_ATTRIBUTE_VIRTUAL = 0x00010000 FILE_ATTRIBUTE_NO_SCRUB_DATA = 0x00020000 FILE_ATTRIBUTE_EA = 0x00040000 ) // Windows processor architectures. // //nolint:revive // var-naming: ALL_CAPS const ( PROCESSOR_ARCHITECTURE_INTEL = 0 PROCESSOR_ARCHITECTURE_MIPS = 1 PROCESSOR_ARCHITECTURE_ALPHA = 2 PROCESSOR_ARCHITECTURE_PPC = 3 PROCESSOR_ARCHITECTURE_SHX = 4 PROCESSOR_ARCHITECTURE_ARM = 5 PROCESSOR_ARCHITECTURE_IA64 = 6 PROCESSOR_ARCHITECTURE_ALPHA64 = 7 PROCESSOR_ARCHITECTURE_MSIL = 8 PROCESSOR_ARCHITECTURE_AMD64 = 9 PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 = 10 PROCESSOR_ARCHITECTURE_NEUTRAL = 11 PROCESSOR_ARCHITECTURE_ARM64 = 12 ) 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 Data3 uint16 Data4 [8]byte } 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]) } type resourceDescriptor struct { FlagsAndCompressedSize uint64 Offset int64 OriginalSize int64 } type resFlag byte //nolint:deadcode,varcheck // need unused variables for iota to work const ( resFlagFree resFlag = 1 << iota resFlagMetadata resFlagCompressed resFlagSpanned ) const validate = false const supportedResFlags = resFlagMetadata | resFlagCompressed func (r *resourceDescriptor) Flags() resFlag { return resFlag(r.FlagsAndCompressedSize >> 56) } func (r *resourceDescriptor) CompressedSize() int64 { return int64(r.FlagsAndCompressedSize & 0xffffffffffffff) } func (r *resourceDescriptor) String() string { s := fmt.Sprintf("%d bytes at %d", r.CompressedSize(), r.Offset) if r.Flags()&4 != 0 { s += fmt.Sprintf(" (uncompresses to %d)", r.OriginalSize) } return s } // SHA1Hash contains the SHA1 hash of a file or stream. type SHA1Hash [20]byte type streamDescriptor struct { resourceDescriptor PartNumber uint16 RefCount uint32 Hash SHA1Hash } type hdrFlag uint32 //nolint:deadcode,varcheck // need unused variables for iota to work const ( hdrFlagReserved hdrFlag = 1 << iota hdrFlagCompressed hdrFlagReadOnly hdrFlagSpanned hdrFlagResourceOnly hdrFlagMetadataOnly hdrFlagWriteInProgress hdrFlagRpFix ) //nolint:deadcode,varcheck // need unused variables for iota to work const ( hdrFlagCompressReserved hdrFlag = 1 << (iota + 16) hdrFlagCompressXpress hdrFlagCompressLzx ) const supportedHdrFlags = hdrFlagRpFix | hdrFlagReadOnly | hdrFlagCompressed | hdrFlagCompressLzx type wimHeader struct { ImageTag [8]byte Size uint32 Version uint32 Flags hdrFlag CompressionSize uint32 WIMGuid guid PartNumber uint16 TotalParts uint16 ImageCount uint32 OffsetTable resourceDescriptor XMLData resourceDescriptor BootMetadata resourceDescriptor BootIndex uint32 Padding uint32 Integrity resourceDescriptor Unused [60]byte } type securityblockDisk struct { TotalLength uint32 NumEntries uint32 } const securityblockDiskSize = 8 type direntry struct { Attributes uint32 SecurityID uint32 SubdirOffset int64 Unused1, Unused2 int64 CreationTime Filetime LastAccessTime Filetime LastWriteTime Filetime Hash SHA1Hash Padding uint32 ReparseHardLink int64 StreamCount uint16 ShortNameLength uint16 FileNameLength uint16 } var direntrySize = int64(binary.Size(direntry{}) + 8) // includes an 8-byte length prefix type streamentry struct { Unused int64 Hash SHA1Hash NameLength int16 } var streamentrySize = int64(binary.Size(streamentry{}) + 8) // includes an 8-byte length prefix // Filetime represents a Windows time. type Filetime struct { LowDateTime uint32 HighDateTime uint32 } // Time returns the time as time.Time. func (ft *Filetime) Time() time.Time { // 100-nanosecond intervals since January 1, 1601 nsec := int64(ft.HighDateTime)<<32 + int64(ft.LowDateTime) // change starting time to the Epoch (00:00:00 UTC, January 1, 1970) nsec -= 116444736000000000 // convert into nanoseconds nsec *= 100 return time.Unix(0, nsec) } // UnmarshalXML unmarshalls the time from a WIM XML blob. func (ft *Filetime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { type Time struct { Low string `xml:"LOWPART"` High string `xml:"HIGHPART"` } var t Time err := d.DecodeElement(&t, &start) if err != nil { return err } low, err := strconv.ParseUint(t.Low, 0, 32) if err != nil { return err } high, err := strconv.ParseUint(t.High, 0, 32) if err != nil { return err } ft.LowDateTime = uint32(low) ft.HighDateTime = uint32(high) return nil } type info struct { Image []ImageInfo `xml:"IMAGE"` } // ImageInfo contains information about the image. type ImageInfo struct { Name string `xml:"NAME"` Index int `xml:"INDEX,attr"` CreationTime Filetime `xml:"CREATIONTIME"` ModTime Filetime `xml:"LASTMODIFICATIONTIME"` Windows *WindowsInfo `xml:"WINDOWS"` } // WindowsInfo contains information about the Windows installation in the image. type WindowsInfo struct { Arch byte `xml:"ARCH"` ProductName string `xml:"PRODUCTNAME"` EditionID string `xml:"EDITIONID"` InstallationType string `xml:"INSTALLATIONTYPE"` ProductType string `xml:"PRODUCTTYPE"` Languages []string `xml:"LANGUAGES>LANGUAGE"` DefaultLanguage string `xml:"LANGUAGES>DEFAULT"` Version Version `xml:"VERSION"` SystemRoot string `xml:"SYSTEMROOT"` } // Version represents a Windows build version. type Version struct { Major int `xml:"MAJOR"` Minor int `xml:"MINOR"` Build int `xml:"BUILD"` SPBuild int `xml:"SPBUILD"` SPLevel int `xml:"SPLEVEL"` } // ParseError is returned when the WIM cannot be parsed. type ParseError struct { Oper string Path string Err error } func (e *ParseError) Error() string { if e.Path == "" { return "WIM parse error at " + e.Oper + ": " + e.Err.Error() } 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 r io.ReaderAt fileData map[SHA1Hash]resourceDescriptor XMLInfo string // The XML information about the WIM. Image []*Image // The WIM's images. } // Image represents an image within a WIM file. type Image struct { wim *Reader offset resourceDescriptor sds [][]byte rootOffset int64 r io.ReadCloser curOffset int64 m sync.Mutex ImageInfo } // StreamHeader contains alternate data stream metadata. type StreamHeader struct { Name string Hash SHA1Hash Size int64 } // Stream represents an alternate data stream or reparse point data stream. type Stream struct { StreamHeader wim *Reader offset resourceDescriptor } // FileHeader contains file metadata. type FileHeader struct { Name string ShortName string Attributes uint32 SecurityDescriptor []byte CreationTime Filetime LastAccessTime Filetime LastWriteTime Filetime Hash SHA1Hash Size int64 LinkID int64 ReparseTag uint32 ReparseReserved uint32 } // File represents a file or directory in a WIM image. type File struct { FileHeader Streams []*Stream offset resourceDescriptor img *Image subdirOffset int64 } // NewReader returns a Reader that can be used to read WIM file data. func NewReader(f io.ReaderAt) (*Reader, error) { r := &Reader{r: f} section := io.NewSectionReader(f, 0, 0xffff) err := binary.Read(section, binary.LittleEndian, &r.hdr) if err != nil { return nil, err } if r.hdr.ImageTag != wimImageTag { return nil, &ParseError{Oper: "image tag", Err: errors.New("not a WIM file")} } if r.hdr.Flags&^supportedHdrFlags != 0 { return nil, fmt.Errorf("unsupported WIM flags %x", r.hdr.Flags&^supportedHdrFlags) } if r.hdr.CompressionSize != 0x8000 { return nil, fmt.Errorf("unsupported compression size %d", r.hdr.CompressionSize) } if r.hdr.TotalParts != 1 { return nil, errors.New("multi-part WIM not supported") } fileData, images, err := r.readOffsetTable(&r.hdr.OffsetTable) if err != nil { return nil, err } xmlinfo, err := r.readXML() if err != nil { return nil, err } 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 inf.Image { if imgInfo.Index == i+1 { img.ImageInfo = imgInfo break } } } r.fileData = fileData r.Image = images r.XMLInfo = xmlinfo return r, nil } // Close releases resources associated with the Reader. func (r *Reader) Close() error { for _, img := range r.Image { img.reset() } return nil } func (r *Reader) resourceReader(hdr *resourceDescriptor) (io.ReadCloser, error) { return r.resourceReaderWithOffset(hdr, 0) } func (r *Reader) resourceReaderWithOffset(hdr *resourceDescriptor, offset int64) (io.ReadCloser, error) { var sr io.ReadCloser section := io.NewSectionReader(r.r, hdr.Offset, hdr.CompressedSize()) if hdr.Flags()&resFlagCompressed == 0 { _, _ = section.Seek(offset, 0) sr = io.NopCloser(section) } else { cr, err := newCompressedReader(section, hdr.OriginalSize, offset) if err != nil { return nil, err } sr = cr } return sr, nil } func (r *Reader) readResource(hdr *resourceDescriptor) ([]byte, error) { rsrc, err := r.resourceReader(hdr) if err != nil { return nil, err } defer rsrc.Close() return io.ReadAll(rsrc) } func (r *Reader) readXML() (string, error) { if r.hdr.XMLData.CompressedSize() == 0 { return "", nil } rsrc, err := r.resourceReader(&r.hdr.XMLData) if err != nil { return "", err } defer rsrc.Close() 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 { return "", &ParseError{Oper: "XML data", Err: errors.New("invalid BOM")} } return string(utf16.Decode(xmlData[1:])), nil } func (r *Reader) readOffsetTable(res *resourceDescriptor) (map[SHA1Hash]resourceDescriptor, []*Image, error) { fileData := make(map[SHA1Hash]resourceDescriptor) var images []*Image offsetTable, err := r.readResource(res) if err != nil { return nil, nil, &ParseError{Oper: "offset table", Err: err} } br := bytes.NewReader(offsetTable) for i := 0; ; i++ { var res streamDescriptor err := binary.Read(br, binary.LittleEndian, &res) if err == io.EOF { //nolint:errorlint break } if err != nil { return nil, nil, &ParseError{Oper: "offset table", Err: err} } if res.Flags()&^supportedResFlags != 0 { return nil, nil, &ParseError{Oper: "offset table", Err: errors.New("unsupported resource flag")} } // Validation for ad-hoc testing if validate { sec, err := r.resourceReader(&res.resourceDescriptor) if err != nil { panic(fmt.Sprint(i, err)) } hash := sha1.New() //nolint:gosec // not used for secure application _, err = io.Copy(hash, sec) sec.Close() if err != nil { panic(fmt.Sprint(i, err)) } var cmphash SHA1Hash copy(cmphash[:], hash.Sum(nil)) if cmphash != res.Hash { panic(fmt.Sprint(i, "hash mismatch")) } } if res.Flags()&resFlagMetadata != 0 { image := &Image{ wim: r, offset: res.resourceDescriptor, } images = append(images, image) } else { fileData[res.Hash] = res.resourceDescriptor } } if len(images) != int(r.hdr.ImageCount) { return nil, nil, &ParseError{Oper: "offset table", Err: errors.New("mismatched image count")} } return fileData, images, nil } 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 { return sds, 0, &ParseError{Oper: "security table", Err: err} } n += securityblockDiskSize secSizes := make([]int64, secBlock.NumEntries) err = binary.Read(rsrc, binary.LittleEndian, &secSizes) if err != nil { return sds, n, &ParseError{Oper: "security table sizes", Err: err} } n += int64(secBlock.NumEntries * 8) sds = make([][]byte, secBlock.NumEntries) for i, size := range secSizes { sd := make([]byte, size&0xffffffff) _, err = io.ReadFull(rsrc, sd) if err != nil { return sds, n, &ParseError{Oper: "security descriptor", Err: err} } n += int64(len(sd)) sds[i] = sd } secsize := int64((secBlock.TotalLength + 7) &^ 7) if n > secsize { return sds, n, &ParseError{Oper: "security descriptor", Err: errors.New("security descriptor table too small")} } _, err = io.CopyN(io.Discard, rsrc, secsize-n) if err != nil { return sds, n, err } n = secsize return sds, n, nil } // Open parses the image and returns the root directory. func (img *Image) Open() (*File, error) { if img.sds == nil { rsrc, err := img.wim.resourceReaderWithOffset(&img.offset, img.rootOffset) if err != nil { return nil, err } sds, n, err := img.wim.readSecurityDescriptors(rsrc) if err != nil { rsrc.Close() return nil, err } img.sds = sds img.r = rsrc img.rootOffset = n img.curOffset = n } f, err := img.readdir(img.rootOffset) if err != nil { return nil, err } if len(f) != 1 { return nil, &ParseError{Oper: "root directory", Err: errors.New("expected exactly 1 root directory entry")} } return f[0], err } func (img *Image) reset() { if img.r != nil { img.r.Close() img.r = nil } img.curOffset = -1 } func (img *Image) readdir(offset int64) ([]*File, error) { img.m.Lock() defer img.m.Unlock() if offset < img.curOffset || offset > img.curOffset+chunkSize { // Reset to seek backward or to seek forward very far. img.reset() } if img.r == nil { rsrc, err := img.wim.resourceReaderWithOffset(&img.offset, offset) if err != nil { return nil, err } img.r = rsrc img.curOffset = offset } if offset > img.curOffset { _, err := io.CopyN(io.Discard, img.r, offset-img.curOffset) if err != nil { img.reset() if err == io.EOF { //nolint:errorlint err = io.ErrUnexpectedEOF } return nil, err } } var entries []*File for { e, n, err := img.readNextEntry(img.r) img.curOffset += n if err == io.EOF { //nolint:errorlint break } if err != nil { img.reset() return nil, err } entries = append(entries, e) } return entries, nil } func (img *Image) readNextEntry(r io.Reader) (*File, int64, error) { var length int64 err := binary.Read(r, binary.LittleEndian, &length) if err != nil { return nil, 0, &ParseError{Oper: "directory length check", Err: err} } if length == 0 { return nil, 8, io.EOF } left := length if left < direntrySize { return nil, 0, &ParseError{Oper: "directory entry", Err: errors.New("size too short")} } var dentry direntry err = binary.Read(r, binary.LittleEndian, &dentry) if err != nil { return nil, 0, &ParseError{Oper: "directory entry", Err: err} } left -= direntrySize namesLen := int64(dentry.FileNameLength + 2 + dentry.ShortNameLength) if left < namesLen { return nil, 0, &ParseError{Oper: "directory entry", Err: errors.New("size too short for names")} } names := make([]uint16, namesLen/2) err = binary.Read(r, binary.LittleEndian, names) if err != nil { return nil, 0, &ParseError{Oper: "file name", Err: err} } left -= namesLen var name, shortName string if dentry.FileNameLength > 0 { name = string(utf16.Decode(names[:dentry.FileNameLength/2])) } if dentry.ShortNameLength > 0 { shortName = string(utf16.Decode(names[dentry.FileNameLength/2+1:])) } var offset resourceDescriptor zerohash := SHA1Hash{} if dentry.Hash != zerohash { 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), } } } f := &File{ FileHeader: FileHeader{ Attributes: dentry.Attributes, CreationTime: dentry.CreationTime, LastAccessTime: dentry.LastAccessTime, LastWriteTime: dentry.LastWriteTime, Hash: dentry.Hash, Size: offset.OriginalSize, Name: name, ShortName: shortName, }, offset: offset, img: img, subdirOffset: dentry.SubdirOffset, } isDir := false if dentry.Attributes&FILE_ATTRIBUTE_REPARSE_POINT == 0 { f.LinkID = dentry.ReparseHardLink if dentry.Attributes&FILE_ATTRIBUTE_DIRECTORY != 0 { isDir = true } } else { f.ReparseTag = uint32(dentry.ReparseHardLink) f.ReparseReserved = uint32(dentry.ReparseHardLink >> 32) } if isDir && f.subdirOffset == 0 { return nil, 0, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("no subdirectory data for directory")} } else if !isDir && f.subdirOffset != 0 { return nil, 0, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("unexpected subdirectory data for non-directory")} } if dentry.SecurityID != 0xffffffff { f.SecurityDescriptor = img.sds[dentry.SecurityID] } _, err = io.CopyN(io.Discard, r, left) if err != nil { if err == io.EOF { //nolint:errorlint err = io.ErrUnexpectedEOF } return nil, 0, err } if dentry.StreamCount > 0 { var streams []*Stream for i := uint16(0); i < dentry.StreamCount; i++ { s, n, err := img.readNextStream(r) length += n if err != nil { return nil, 0, err } // The first unnamed stream should be treated as the file stream. if i == 0 && s.Name == "" { f.Hash = s.Hash f.Size = s.Size f.offset = s.offset } else if s.Name != "" { streams = append(streams, s) } } f.Streams = streams } 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 f, length, nil } 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 { //nolint:errorlint err = io.ErrUnexpectedEOF } return nil, 0, &ParseError{Oper: "stream length check", Err: err} } left := length if left < streamentrySize { return nil, 0, &ParseError{Oper: "stream entry", Err: errors.New("size too short")} } var sentry streamentry err = binary.Read(r, binary.LittleEndian, &sentry) if err != nil { return nil, 0, &ParseError{Oper: "stream entry", Err: err} } left -= streamentrySize if left < int64(sentry.NameLength) { return nil, 0, &ParseError{Oper: "stream entry", Err: errors.New("size too short for name")} } names := make([]uint16, sentry.NameLength/2) err = binary.Read(r, binary.LittleEndian, names) if err != nil { return nil, 0, &ParseError{Oper: "file name", Err: err} } left -= int64(sentry.NameLength) name := string(utf16.Decode(names)) var offset resourceDescriptor if sentry.Hash != (SHA1Hash{}) { 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), } } } s := &Stream{ StreamHeader: StreamHeader{ Hash: sentry.Hash, Size: offset.OriginalSize, Name: name, }, wim: img.wim, offset: offset, } _, err = io.CopyN(io.Discard, r, left) if err != nil { if err == io.EOF { //nolint:errorlint err = io.ErrUnexpectedEOF } return nil, 0, err } return s, length, nil } // Open returns an io.ReadCloser that can be used to read the stream's contents. func (s *Stream) Open() (io.ReadCloser, error) { return s.wim.resourceReader(&s.offset) } // Open returns an io.ReadCloser that can be used to read the file's contents. func (f *File) Open() (io.ReadCloser, error) { return f.img.wim.resourceReader(&f.offset) } // Readdir reads the directory entries. func (f *File) Readdir() ([]*File, error) { if !f.IsDir() { return nil, errors.New("not a directory") } return f.img.readdir(f.subdirOffset) } // IsDir returns whether the given file is a directory. It returns false when it // is a directory reparse point. func (f *FileHeader) IsDir() bool { return f.Attributes&(FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_DIRECTORY } go-winio-0.6.2/zsyscall_windows.go000066400000000000000000000350571460531775000172360ustar00rootroot00000000000000//go:build windows // Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT. package winio import ( "syscall" "unsafe" "golang.org/x/sys/windows" ) var _ unsafe.Pointer // Do the interface allocations only once for common // Errno values. const ( errnoERROR_IO_PENDING = 997 ) var ( errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) errERROR_EINVAL error = syscall.EINVAL ) // errnoErr returns common boxed Errno values, to prevent // allocations at runtime. func errnoErr(e syscall.Errno) error { switch e { case 0: return errERROR_EINVAL case errnoERROR_IO_PENDING: return errERROR_IO_PENDING } return e } var ( modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") modkernel32 = windows.NewLazySystemDLL("kernel32.dll") modntdll = windows.NewLazySystemDLL("ntdll.dll") modws2_32 = windows.NewLazySystemDLL("ws2_32.dll") procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges") procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW") procConvertStringSidToSidW = modadvapi32.NewProc("ConvertStringSidToSidW") procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf") procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW") procLookupAccountSidW = modadvapi32.NewProc("LookupAccountSidW") procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW") procLookupPrivilegeNameW = modadvapi32.NewProc("LookupPrivilegeNameW") procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW") procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken") procRevertToSelf = modadvapi32.NewProc("RevertToSelf") procBackupRead = modkernel32.NewProc("BackupRead") procBackupWrite = modkernel32.NewProc("BackupWrite") procCancelIoEx = modkernel32.NewProc("CancelIoEx") procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe") procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort") procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW") procDisconnectNamedPipe = modkernel32.NewProc("DisconnectNamedPipe") procGetCurrentThread = modkernel32.NewProc("GetCurrentThread") procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW") procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo") procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus") procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes") procNtCreateNamedPipeFile = modntdll.NewProc("NtCreateNamedPipeFile") procRtlDefaultNpAcl = modntdll.NewProc("RtlDefaultNpAcl") procRtlDosPathNameToNtPathName_U = modntdll.NewProc("RtlDosPathNameToNtPathName_U") procRtlNtStatusToDosErrorNoTeb = modntdll.NewProc("RtlNtStatusToDosErrorNoTeb") procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult") ) func adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) { var _p0 uint32 if releaseAll { _p0 = 1 } r0, _, e1 := syscall.SyscallN(procAdjustTokenPrivileges.Addr(), uintptr(token), uintptr(_p0), uintptr(unsafe.Pointer(input)), uintptr(outputSize), uintptr(unsafe.Pointer(output)), uintptr(unsafe.Pointer(requiredSize))) success = r0 != 0 if true { err = errnoErr(e1) } return } func convertSidToStringSid(sid *byte, str **uint16) (err error) { r1, _, e1 := syscall.SyscallN(procConvertSidToStringSidW.Addr(), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(str))) if r1 == 0 { err = errnoErr(e1) } return } func convertStringSidToSid(str *uint16, sid **byte) (err error) { r1, _, e1 := syscall.SyscallN(procConvertStringSidToSidW.Addr(), uintptr(unsafe.Pointer(str)), uintptr(unsafe.Pointer(sid))) if r1 == 0 { err = errnoErr(e1) } return } func impersonateSelf(level uint32) (err error) { r1, _, e1 := syscall.SyscallN(procImpersonateSelf.Addr(), uintptr(level)) if r1 == 0 { err = errnoErr(e1) } return } func lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) { var _p0 *uint16 _p0, err = syscall.UTF16PtrFromString(accountName) if err != nil { return } return _lookupAccountName(systemName, _p0, sid, sidSize, refDomain, refDomainSize, sidNameUse) } func _lookupAccountName(systemName *uint16, accountName *uint16, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) { r1, _, e1 := syscall.SyscallN(procLookupAccountNameW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(accountName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(sidSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse))) if r1 == 0 { err = errnoErr(e1) } return } func lookupAccountSid(systemName *uint16, sid *byte, name *uint16, nameSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) { r1, _, e1 := syscall.SyscallN(procLookupAccountSidW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(nameSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse))) if r1 == 0 { err = errnoErr(e1) } return } func lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) { var _p0 *uint16 _p0, err = syscall.UTF16PtrFromString(systemName) if err != nil { return } return _lookupPrivilegeDisplayName(_p0, name, buffer, size, languageId) } func _lookupPrivilegeDisplayName(systemName *uint16, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) { r1, _, e1 := syscall.SyscallN(procLookupPrivilegeDisplayNameW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), uintptr(unsafe.Pointer(languageId))) if r1 == 0 { err = errnoErr(e1) } return } func lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) { var _p0 *uint16 _p0, err = syscall.UTF16PtrFromString(systemName) if err != nil { return } return _lookupPrivilegeName(_p0, luid, buffer, size) } func _lookupPrivilegeName(systemName *uint16, luid *uint64, buffer *uint16, size *uint32) (err error) { r1, _, e1 := syscall.SyscallN(procLookupPrivilegeNameW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(luid)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size))) if r1 == 0 { err = errnoErr(e1) } return } func lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) { var _p0 *uint16 _p0, err = syscall.UTF16PtrFromString(systemName) if err != nil { return } var _p1 *uint16 _p1, err = syscall.UTF16PtrFromString(name) if err != nil { return } return _lookupPrivilegeValue(_p0, _p1, luid) } func _lookupPrivilegeValue(systemName *uint16, name *uint16, luid *uint64) (err error) { r1, _, e1 := syscall.SyscallN(procLookupPrivilegeValueW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid))) if r1 == 0 { err = errnoErr(e1) } return } func openThreadToken(thread windows.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) { var _p0 uint32 if openAsSelf { _p0 = 1 } r1, _, e1 := syscall.SyscallN(procOpenThreadToken.Addr(), uintptr(thread), uintptr(accessMask), uintptr(_p0), uintptr(unsafe.Pointer(token))) if r1 == 0 { err = errnoErr(e1) } return } func revertToSelf() (err error) { r1, _, e1 := syscall.SyscallN(procRevertToSelf.Addr()) if r1 == 0 { err = errnoErr(e1) } return } func backupRead(h windows.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) { var _p0 *byte if len(b) > 0 { _p0 = &b[0] } var _p1 uint32 if abort { _p1 = 1 } var _p2 uint32 if processSecurity { _p2 = 1 } r1, _, e1 := syscall.SyscallN(procBackupRead.Addr(), uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesRead)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context))) if r1 == 0 { err = errnoErr(e1) } return } func backupWrite(h windows.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) { var _p0 *byte if len(b) > 0 { _p0 = &b[0] } var _p1 uint32 if abort { _p1 = 1 } var _p2 uint32 if processSecurity { _p2 = 1 } r1, _, e1 := syscall.SyscallN(procBackupWrite.Addr(), uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesWritten)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context))) if r1 == 0 { err = errnoErr(e1) } return } func cancelIoEx(file windows.Handle, o *windows.Overlapped) (err error) { r1, _, e1 := syscall.SyscallN(procCancelIoEx.Addr(), uintptr(file), uintptr(unsafe.Pointer(o))) if r1 == 0 { err = errnoErr(e1) } return } func connectNamedPipe(pipe windows.Handle, o *windows.Overlapped) (err error) { r1, _, e1 := syscall.SyscallN(procConnectNamedPipe.Addr(), uintptr(pipe), uintptr(unsafe.Pointer(o))) if r1 == 0 { err = errnoErr(e1) } return } func createIoCompletionPort(file windows.Handle, port windows.Handle, key uintptr, threadCount uint32) (newport windows.Handle, err error) { r0, _, e1 := syscall.SyscallN(procCreateIoCompletionPort.Addr(), uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount)) newport = windows.Handle(r0) if newport == 0 { err = errnoErr(e1) } return } func createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *windows.SecurityAttributes) (handle windows.Handle, err error) { var _p0 *uint16 _p0, err = syscall.UTF16PtrFromString(name) if err != nil { return } return _createNamedPipe(_p0, flags, pipeMode, maxInstances, outSize, inSize, defaultTimeout, sa) } func _createNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *windows.SecurityAttributes) (handle windows.Handle, err error) { r0, _, e1 := syscall.SyscallN(procCreateNamedPipeW.Addr(), uintptr(unsafe.Pointer(name)), uintptr(flags), uintptr(pipeMode), uintptr(maxInstances), uintptr(outSize), uintptr(inSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa))) handle = windows.Handle(r0) if handle == windows.InvalidHandle { err = errnoErr(e1) } return } func disconnectNamedPipe(pipe windows.Handle) (err error) { r1, _, e1 := syscall.SyscallN(procDisconnectNamedPipe.Addr(), uintptr(pipe)) if r1 == 0 { err = errnoErr(e1) } return } func getCurrentThread() (h windows.Handle) { r0, _, _ := syscall.SyscallN(procGetCurrentThread.Addr()) h = windows.Handle(r0) return } func getNamedPipeHandleState(pipe windows.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) { r1, _, e1 := syscall.SyscallN(procGetNamedPipeHandleStateW.Addr(), uintptr(pipe), uintptr(unsafe.Pointer(state)), uintptr(unsafe.Pointer(curInstances)), uintptr(unsafe.Pointer(maxCollectionCount)), uintptr(unsafe.Pointer(collectDataTimeout)), uintptr(unsafe.Pointer(userName)), uintptr(maxUserNameSize)) if r1 == 0 { err = errnoErr(e1) } return } func getNamedPipeInfo(pipe windows.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) { r1, _, e1 := syscall.SyscallN(procGetNamedPipeInfo.Addr(), uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances))) if r1 == 0 { err = errnoErr(e1) } return } func getQueuedCompletionStatus(port windows.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) { r1, _, e1 := syscall.SyscallN(procGetQueuedCompletionStatus.Addr(), uintptr(port), uintptr(unsafe.Pointer(bytes)), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(o)), uintptr(timeout)) if r1 == 0 { err = errnoErr(e1) } return } func setFileCompletionNotificationModes(h windows.Handle, flags uint8) (err error) { r1, _, e1 := syscall.SyscallN(procSetFileCompletionNotificationModes.Addr(), uintptr(h), uintptr(flags)) if r1 == 0 { err = errnoErr(e1) } return } func ntCreateNamedPipeFile(pipe *windows.Handle, access ntAccessMask, oa *objectAttributes, iosb *ioStatusBlock, share ntFileShareMode, disposition ntFileCreationDisposition, options ntFileOptions, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntStatus) { r0, _, _ := syscall.SyscallN(procNtCreateNamedPipeFile.Addr(), 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))) status = ntStatus(r0) return } func rtlDefaultNpAcl(dacl *uintptr) (status ntStatus) { r0, _, _ := syscall.SyscallN(procRtlDefaultNpAcl.Addr(), uintptr(unsafe.Pointer(dacl))) status = ntStatus(r0) return } func rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntStatus) { r0, _, _ := syscall.SyscallN(procRtlDosPathNameToNtPathName_U.Addr(), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(ntName)), uintptr(filePart), uintptr(reserved)) status = ntStatus(r0) return } func rtlNtStatusToDosError(status ntStatus) (winerr error) { r0, _, _ := syscall.SyscallN(procRtlNtStatusToDosErrorNoTeb.Addr(), uintptr(status)) if r0 != 0 { winerr = syscall.Errno(r0) } return } func wsaGetOverlappedResult(h windows.Handle, o *windows.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) { var _p0 uint32 if wait { _p0 = 1 } r1, _, e1 := syscall.SyscallN(procWSAGetOverlappedResult.Addr(), uintptr(h), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(bytes)), uintptr(_p0), uintptr(unsafe.Pointer(flags))) if r1 == 0 { err = errnoErr(e1) } return }