Commit 69a9759d authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #46744 from karataliu/wincri4

Automatic merge from submit-queue Support windows in dockershim **What this PR does / why we need it**: This is the 2nd part for https://github.com/kubernetes/kubernetes/issues/45927 . The non-cri implementation dockertools was removed from kubelet v1.7 . Part of previous work for supporting windows container lies in v1.6 dockertools, this PR is to port them to dockershim. Main reference file in v1.6 dockertools windows support: https://github.com/kubernetes/kubernetes/blob/v1.6.4/pkg/kubelet/dockertools/docker_manager_windows.go **Which issue this PR fixes** 45927, for now catching up the implementation of v1.6 **Special notes for your reviewer**: The code change includes 4 parts, put them together as we discussed in https://github.com/kubernetes/kubernetes/pull/46089 1. Update go-winio package to a newer version 'go-winio' package is used by docker client. This change is to bring the support for Go v1.8, specifically included in the PR: https://github.com/Microsoft/go-winio/pull/48 Otherwise it will produce a lot of error like in: https://github.com/fsouza/go-dockerclient/issues/648 2. Add os dependent getSecurityOpts helper method. seccomp not supported on windows Corresponding code in v1.6: https://github.com/kubernetes/kubernetes/blob/v1.6.4/pkg/kubelet/dockertools/docker_manager_windows.go#L78 3. Add updateCreateConfig. Allow user specified network mode setting. This is to be compatible with what kube-proxy package does on Windows. Also, there is a Linux section in both sandbox config and container config: LinuxPodSandboxConfig, LinuxContainerConfig. And that section later goes to Config and HostConfig section under docker container createConfig. Ideally hostconfig section should be dependent on host os, while config should depend on container image os. To simplify the case, here it assumes that windows host supports windows type container image only. It needs to be updated when kubernetes is to support windows host running linux container image or the like. Corresponding code in v1.6: https://github.com/kubernetes/kubernetes/blob/v1.6.4/pkg/kubelet/dockertools/docker_manager_windows.go#L57 4. Add podIpCache in dockershim. For v1.6 windows implementation, it still does not use sandbox, thus only allow single container to be exposed. Here added a cache for saving container IP, to get adapted to the new CRI api. Corresponding code in v1.6: No sandbox: https://github.com/kubernetes/kubernetes/blob/v1.6.4/pkg/kubelet/dockertools/docker_manager_windows.go#L66 Use container id as pod ip: https://github.com/kubernetes/kubernetes/blob/v1.6.4/pkg/kubelet/dockertools/docker_manager.go#L2727 **Release note**:
parents d16d64f6 5936e81b
...@@ -92,8 +92,8 @@ ...@@ -92,8 +92,8 @@
}, },
{ {
"ImportPath": "github.com/Microsoft/go-winio", "ImportPath": "github.com/Microsoft/go-winio",
"Comment": "v0.1.0", "Comment": "v0.4.2",
"Rev": "8f9387ea7efabb228a981b9c381142be7667967f" "Rev": "f533f7a102197536779ea3a8cb881d639e21ec5a"
}, },
{ {
"ImportPath": "github.com/PuerkitoBio/purell", "ImportPath": "github.com/PuerkitoBio/purell",
...@@ -2701,6 +2701,10 @@ ...@@ -2701,6 +2701,10 @@
"Rev": "8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9" "Rev": "8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9"
}, },
{ {
"ImportPath": "golang.org/x/sys/windows",
"Rev": "8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9"
},
{
"ImportPath": "golang.org/x/text/cases", "ImportPath": "golang.org/x/text/cases",
"Rev": "2910a502d2bf9e43193af9d68ca516529614eed3" "Rev": "2910a502d2bf9e43193af9d68ca516529614eed3"
}, },
......
...@@ -83345,6 +83345,41 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ...@@ -83345,6 +83345,41 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================================================ ================================================================================
= vendor/golang.org/x/sys/windows licensed under: =
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
= vendor/golang.org/x/sys/LICENSE 5d4950ecb7b26d2c5e4e7b4e0dd74707 -
================================================================================
================================================================================
= vendor/golang.org/x/text/cases licensed under: = = vendor/golang.org/x/text/cases licensed under: =
Copyright (c) 2009 The Go Authors. All rights reserved. Copyright (c) 2009 The Go Authors. All rights reserved.
...@@ -132,47 +132,12 @@ func (ds *dockerService) CreateContainer(podSandboxID string, config *runtimeapi ...@@ -132,47 +132,12 @@ func (ds *dockerService) CreateContainer(podSandboxID string, config *runtimeapi
StdinOnce: config.StdinOnce, StdinOnce: config.StdinOnce,
Tty: config.Tty, Tty: config.Tty,
}, },
} HostConfig: &dockercontainer.HostConfig{
// Fill the HostConfig.
hc := &dockercontainer.HostConfig{
Binds: generateMountBindings(config.GetMounts()), Binds: generateMountBindings(config.GetMounts()),
},
} }
// Apply Linux-specific options if applicable. hc := createConfig.HostConfig
if lc := config.GetLinux(); lc != nil {
// TODO: Check if the units are correct.
// TODO: Can we assume the defaults are sane?
rOpts := lc.GetResources()
if rOpts != nil {
hc.Resources = dockercontainer.Resources{
Memory: rOpts.MemoryLimitInBytes,
MemorySwap: DefaultMemorySwap(),
CPUShares: rOpts.CpuShares,
CPUQuota: rOpts.CpuQuota,
CPUPeriod: rOpts.CpuPeriod,
}
hc.OomScoreAdj = int(rOpts.OomScoreAdj)
}
// Note: ShmSize is handled in kube_docker_client.go
// Apply security context.
if err = applyContainerSecurityContext(lc, podSandboxID, createConfig.Config, hc, securityOptSep); err != nil {
return "", fmt.Errorf("failed to apply container security context for container %q: %v", config.Metadata.Name, err)
}
modifyPIDNamespaceOverrides(ds.disableSharedPID, apiVersion, hc)
}
// Apply cgroupsParent derived from the sandbox config.
if lc := sandboxConfig.GetLinux(); lc != nil {
// Apply Cgroup options.
cgroupParent, err := ds.GenerateExpectedCgroupParent(lc.CgroupParent)
if err != nil {
return "", fmt.Errorf("failed to generate cgroup parent in expected syntax for container %q: %v", config.Metadata.Name, err)
}
hc.CgroupParent = cgroupParent
}
// Set devices for container. // Set devices for container.
devices := make([]dockercontainer.DeviceMapping, len(config.Devices)) devices := make([]dockercontainer.DeviceMapping, len(config.Devices))
for i, device := range config.Devices { for i, device := range config.Devices {
...@@ -183,15 +148,15 @@ func (ds *dockerService) CreateContainer(podSandboxID string, config *runtimeapi ...@@ -183,15 +148,15 @@ func (ds *dockerService) CreateContainer(podSandboxID string, config *runtimeapi
} }
} }
hc.Resources.Devices = devices hc.Resources.Devices = devices
ds.updateCreateConfig(&createConfig, config, sandboxConfig, podSandboxID, securityOptSep, apiVersion)
// Apply seccomp options. securityOpts, err := ds.getSecurityOpts(config.Metadata.Name, sandboxConfig, securityOptSep)
seccompSecurityOpts, err := getSeccompSecurityOpts(config.Metadata.Name, sandboxConfig, ds.seccompProfileRoot, securityOptSep)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to generate seccomp security options for container %q: %v", config.Metadata.Name, err) return "", fmt.Errorf("failed to generate security options for container %q: %v", config.Metadata.Name, err)
} }
hc.SecurityOpt = append(hc.SecurityOpt, seccompSecurityOpts...)
createConfig.HostConfig = hc hc.SecurityOpt = append(hc.SecurityOpt, securityOpts...)
createResp, err := ds.client.CreateContainer(createConfig) createResp, err := ds.client.CreateContainer(createConfig)
if err != nil { if err != nil {
createResp, err = recoverFromCreationConflictIfNeeded(ds.client, createConfig, err) createResp, err = recoverFromCreationConflictIfNeeded(ds.client, createConfig, err)
......
...@@ -317,10 +317,16 @@ func (ds *dockerService) PodSandboxStatus(podSandboxID string) (*runtimeapi.PodS ...@@ -317,10 +317,16 @@ func (ds *dockerService) PodSandboxStatus(podSandboxID string) (*runtimeapi.PodS
if r.State.Running { if r.State.Running {
state = runtimeapi.PodSandboxState_SANDBOX_READY state = runtimeapi.PodSandboxState_SANDBOX_READY
} }
IP, err := ds.getIP(r)
var IP string
// TODO: Remove this when sandbox is available on windows
// This is a workaround for windows, where sandbox is not in use, and pod IP is determined through containers belonging to the Pod.
if IP = ds.determinePodIPBySandboxID(podSandboxID); IP == "" {
IP, err = ds.getIP(r)
if err != nil { if err != nil {
return nil, err return nil, err
} }
}
network := &runtimeapi.PodSandboxNetworkStatus{Ip: IP} network := &runtimeapi.PodSandboxNetworkStatus{Ip: IP}
hostNetwork := sharesHostNetwork(r) hostNetwork := sharesHostNetwork(r)
...@@ -537,7 +543,7 @@ func (ds *dockerService) makeSandboxDockerConfig(c *runtimeapi.PodSandboxConfig, ...@@ -537,7 +543,7 @@ func (ds *dockerService) makeSandboxDockerConfig(c *runtimeapi.PodSandboxConfig,
} }
// Set security options. // Set security options.
securityOpts, err := getSeccompSecurityOpts(sandboxContainerName, c, ds.seccompProfileRoot, securityOptSep) securityOpts, err := ds.getSecurityOpts(sandboxContainerName, c, securityOptSep)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to generate sandbox security options for sandbox %q: %v", c.Metadata.Name, err) return nil, fmt.Errorf("failed to generate sandbox security options for sandbox %q: %v", c.Metadata.Name, err)
} }
......
...@@ -18,6 +18,71 @@ limitations under the License. ...@@ -18,6 +18,71 @@ limitations under the License.
package dockershim package dockershim
import (
"fmt"
"github.com/blang/semver"
dockertypes "github.com/docker/engine-api/types"
dockercontainer "github.com/docker/engine-api/types/container"
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1"
)
func DefaultMemorySwap() int64 { func DefaultMemorySwap() int64 {
return 0 return 0
} }
func (ds *dockerService) getSecurityOpts(containerName string, sandboxConfig *runtimeapi.PodSandboxConfig, separator rune) ([]string, error) {
// Apply seccomp options.
seccompSecurityOpts, err := getSeccompSecurityOpts(containerName, sandboxConfig, ds.seccompProfileRoot, separator)
if err != nil {
return nil, fmt.Errorf("failed to generate seccomp security options for container %q: %v", containerName, err)
}
return seccompSecurityOpts, nil
}
func (ds *dockerService) updateCreateConfig(
createConfig *dockertypes.ContainerCreateConfig,
config *runtimeapi.ContainerConfig,
sandboxConfig *runtimeapi.PodSandboxConfig,
podSandboxID string, securityOptSep rune, apiVersion *semver.Version) error {
// Apply Linux-specific options if applicable.
if lc := config.GetLinux(); lc != nil {
// TODO: Check if the units are correct.
// TODO: Can we assume the defaults are sane?
rOpts := lc.GetResources()
if rOpts != nil {
createConfig.HostConfig.Resources = dockercontainer.Resources{
Memory: rOpts.MemoryLimitInBytes,
MemorySwap: DefaultMemorySwap(),
CPUShares: rOpts.CpuShares,
CPUQuota: rOpts.CpuQuota,
CPUPeriod: rOpts.CpuPeriod,
}
createConfig.HostConfig.OomScoreAdj = int(rOpts.OomScoreAdj)
}
// Note: ShmSize is handled in kube_docker_client.go
// Apply security context.
if err := applyContainerSecurityContext(lc, podSandboxID, createConfig.Config, createConfig.HostConfig, securityOptSep); err != nil {
return fmt.Errorf("failed to apply container security context for container %q: %v", config.Metadata.Name, err)
}
modifyPIDNamespaceOverrides(ds.disableSharedPID, apiVersion, createConfig.HostConfig)
}
// Apply cgroupsParent derived from the sandbox config.
if lc := sandboxConfig.GetLinux(); lc != nil {
// Apply Cgroup options.
cgroupParent, err := ds.GenerateExpectedCgroupParent(lc.CgroupParent)
if err != nil {
return fmt.Errorf("failed to generate cgroup parent in expected syntax for container %q: %v", config.Metadata.Name, err)
}
createConfig.HostConfig.CgroupParent = cgroupParent
}
return nil
}
func (ds *dockerService) determinePodIPBySandboxID(uid string) string {
return ""
}
...@@ -18,6 +18,32 @@ limitations under the License. ...@@ -18,6 +18,32 @@ limitations under the License.
package dockershim package dockershim
import (
"github.com/blang/semver"
dockertypes "github.com/docker/engine-api/types"
"github.com/golang/glog"
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1"
)
func DefaultMemorySwap() int64 { func DefaultMemorySwap() int64 {
return -1 return -1
} }
func (ds *dockerService) getSecurityOpts(containerName string, sandboxConfig *runtimeapi.PodSandboxConfig, separator rune) ([]string, error) {
glog.Warningf("getSecurityOpts is unsupported in this build")
return nil, nil
}
func (ds *dockerService) updateCreateConfig(
createConfig *dockertypes.ContainerCreateConfig,
config *runtimeapi.ContainerConfig,
sandboxConfig *runtimeapi.PodSandboxConfig,
podSandboxID string, securityOptSep rune, apiVersion *semver.Version) error {
glog.Warningf("updateCreateConfig is unsupported in this build")
return nil
}
func (ds *dockerService) determinePodIPBySandboxID(uid string) string {
glog.Warningf("determinePodIPBySandboxID is unsupported in this build")
return ""
}
...@@ -18,6 +18,84 @@ limitations under the License. ...@@ -18,6 +18,84 @@ limitations under the License.
package dockershim package dockershim
import (
"os"
"github.com/blang/semver"
dockertypes "github.com/docker/engine-api/types"
dockercontainer "github.com/docker/engine-api/types/container"
dockerfilters "github.com/docker/engine-api/types/filters"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api/v1"
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1"
)
func DefaultMemorySwap() int64 { func DefaultMemorySwap() int64 {
return 0 return 0
} }
func (ds *dockerService) getSecurityOpts(containerName string, sandboxConfig *runtimeapi.PodSandboxConfig, separator rune) ([]string, error) {
hasSeccompSetting := false
annotations := sandboxConfig.GetAnnotations()
if _, ok := annotations[v1.SeccompContainerAnnotationKeyPrefix+containerName]; !ok {
_, hasSeccompSetting = annotations[v1.SeccompPodAnnotationKey]
} else {
hasSeccompSetting = true
}
if hasSeccompSetting {
glog.Warningf("seccomp annotations found, but it is not supported on windows")
}
return nil, nil
}
func (ds *dockerService) updateCreateConfig(
createConfig *dockertypes.ContainerCreateConfig,
config *runtimeapi.ContainerConfig,
sandboxConfig *runtimeapi.PodSandboxConfig,
podSandboxID string, securityOptSep rune, apiVersion *semver.Version) error {
if networkMode := os.Getenv("CONTAINER_NETWORK"); networkMode != "" {
createConfig.HostConfig.NetworkMode = dockercontainer.NetworkMode(networkMode)
}
return nil
}
func (ds *dockerService) determinePodIPBySandboxID(sandboxID string) string {
opts := dockertypes.ContainerListOptions{
All: true,
Filter: dockerfilters.NewArgs(),
}
f := newDockerFilter(&opts.Filter)
f.AddLabel(containerTypeLabelKey, containerTypeLabelContainer)
f.AddLabel(sandboxIDLabelKey, sandboxID)
containers, err := ds.client.ListContainers(opts)
if err != nil {
return ""
}
for _, c := range containers {
r, err := ds.client.InspectContainer(c.ID)
if err != nil {
continue
}
if containerIP := getContainerIP(r); containerIP != "" {
return containerIP
}
}
return ""
}
func getContainerIP(container *dockertypes.ContainerJSON) string {
if container.NetworkSettings != nil {
for _, network := range container.NetworkSettings.Networks {
if network.IPAddress != "" {
return network.IPAddress
}
}
}
return ""
}
...@@ -337,6 +337,7 @@ filegroup( ...@@ -337,6 +337,7 @@ filegroup(
"//vendor/golang.org/x/net/websocket:all-srcs", "//vendor/golang.org/x/net/websocket:all-srcs",
"//vendor/golang.org/x/oauth2:all-srcs", "//vendor/golang.org/x/oauth2:all-srcs",
"//vendor/golang.org/x/sys/unix:all-srcs", "//vendor/golang.org/x/sys/unix:all-srcs",
"//vendor/golang.org/x/sys/windows:all-srcs",
"//vendor/golang.org/x/text/cases:all-srcs", "//vendor/golang.org/x/text/cases:all-srcs",
"//vendor/golang.org/x/text/encoding:all-srcs", "//vendor/golang.org/x/text/encoding:all-srcs",
"//vendor/golang.org/x/text/internal/tag:all-srcs", "//vendor/golang.org/x/text/internal/tag:all-srcs",
......
...@@ -10,15 +10,8 @@ load( ...@@ -10,15 +10,8 @@ load(
go_library( go_library(
name = "go_default_library", name = "go_default_library",
srcs = [ srcs = [
"backup.go",
"file.go",
"fileinfo.go",
"pipe.go",
"privilege.go",
"reparse.go", "reparse.go",
"sd.go",
"syscall.go", "syscall.go",
"zsyscall.go",
], ],
tags = ["automanaged"], tags = ["automanaged"],
) )
......
...@@ -11,5 +11,12 @@ package. ...@@ -11,5 +11,12 @@ package.
Please see the LICENSE file for licensing information. Please see the LICENSE file for licensing information.
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.
Thanks to natefinch for the inspiration for this library. See https://github.com/natefinch/npipe Thanks to natefinch for the inspiration for this library. See https://github.com/natefinch/npipe
for another named pipe implementation. for another named pipe implementation.
// +build windows
package winio package winio
import ( import (
...@@ -26,10 +28,18 @@ const ( ...@@ -26,10 +28,18 @@ const (
BackupReparseData BackupReparseData
BackupSparseBlock BackupSparseBlock
BackupTxfsData BackupTxfsData
)
const (
StreamSparseAttributes = uint32(8) StreamSparseAttributes = uint32(8)
) )
const (
WRITE_DAC = 0x40000
WRITE_OWNER = 0x80000
ACCESS_SYSTEM_SECURITY = 0x1000000
)
// BackupHeader represents a backup stream of a file. // BackupHeader represents a backup stream of a file.
type BackupHeader struct { type BackupHeader struct {
Id uint32 // The backup stream ID Id uint32 // The backup stream ID
...@@ -175,7 +185,6 @@ type BackupFileReader struct { ...@@ -175,7 +185,6 @@ type BackupFileReader struct {
// Read will attempt to read the security descriptor of the file. // Read will attempt to read the security descriptor of the file.
func NewBackupFileReader(f *os.File, includeSecurity bool) *BackupFileReader { func NewBackupFileReader(f *os.File, includeSecurity bool) *BackupFileReader {
r := &BackupFileReader{f, includeSecurity, 0} r := &BackupFileReader{f, includeSecurity, 0}
runtime.SetFinalizer(r, func(r *BackupFileReader) { r.Close() })
return r return r
} }
...@@ -186,6 +195,7 @@ func (r *BackupFileReader) Read(b []byte) (int, error) { ...@@ -186,6 +195,7 @@ func (r *BackupFileReader) Read(b []byte) (int, error) {
if err != nil { if err != nil {
return 0, &os.PathError{"BackupRead", r.f.Name(), err} return 0, &os.PathError{"BackupRead", r.f.Name(), err}
} }
runtime.KeepAlive(r.f)
if bytesRead == 0 { if bytesRead == 0 {
return 0, io.EOF return 0, io.EOF
} }
...@@ -197,6 +207,7 @@ func (r *BackupFileReader) Read(b []byte) (int, error) { ...@@ -197,6 +207,7 @@ func (r *BackupFileReader) Read(b []byte) (int, error) {
func (r *BackupFileReader) Close() error { func (r *BackupFileReader) Close() error {
if r.ctx != 0 { if r.ctx != 0 {
backupRead(syscall.Handle(r.f.Fd()), nil, nil, true, false, &r.ctx) backupRead(syscall.Handle(r.f.Fd()), nil, nil, true, false, &r.ctx)
runtime.KeepAlive(r.f)
r.ctx = 0 r.ctx = 0
} }
return nil return nil
...@@ -213,7 +224,6 @@ type BackupFileWriter struct { ...@@ -213,7 +224,6 @@ type BackupFileWriter struct {
// Write() will attempt to restore the security descriptor from the stream. // Write() will attempt to restore the security descriptor from the stream.
func NewBackupFileWriter(f *os.File, includeSecurity bool) *BackupFileWriter { func NewBackupFileWriter(f *os.File, includeSecurity bool) *BackupFileWriter {
w := &BackupFileWriter{f, includeSecurity, 0} w := &BackupFileWriter{f, includeSecurity, 0}
runtime.SetFinalizer(w, func(w *BackupFileWriter) { w.Close() })
return w return w
} }
...@@ -224,6 +234,7 @@ func (w *BackupFileWriter) Write(b []byte) (int, error) { ...@@ -224,6 +234,7 @@ func (w *BackupFileWriter) Write(b []byte) (int, error) {
if err != nil { if err != nil {
return 0, &os.PathError{"BackupWrite", w.f.Name(), err} return 0, &os.PathError{"BackupWrite", w.f.Name(), err}
} }
runtime.KeepAlive(w.f)
if int(bytesWritten) != len(b) { if int(bytesWritten) != len(b) {
return int(bytesWritten), errors.New("not all bytes could be written") return int(bytesWritten), errors.New("not all bytes could be written")
} }
...@@ -235,7 +246,25 @@ func (w *BackupFileWriter) Write(b []byte) (int, error) { ...@@ -235,7 +246,25 @@ func (w *BackupFileWriter) Write(b []byte) (int, error) {
func (w *BackupFileWriter) Close() error { func (w *BackupFileWriter) Close() error {
if w.ctx != 0 { if w.ctx != 0 {
backupWrite(syscall.Handle(w.f.Fd()), nil, nil, true, false, &w.ctx) backupWrite(syscall.Handle(w.f.Fd()), nil, nil, true, false, &w.ctx)
runtime.KeepAlive(w.f)
w.ctx = 0 w.ctx = 0
} }
return nil 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) {
winPath, err := syscall.UTF16FromString(path)
if err != nil {
return nil, err
}
h, err := syscall.CreateFile(&winPath[0], access, share, nil, createmode, syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OPEN_REPARSE_POINT, 0)
if err != nil {
err = &os.PathError{Op: "open", Path: path, Err: err}
return nil, err
}
return os.NewFile(uintptr(h), path), nil
}
// +build windows
package winio package winio
import ( import (
...@@ -5,6 +7,7 @@ import ( ...@@ -5,6 +7,7 @@ import (
"io" "io"
"runtime" "runtime"
"sync" "sync"
"sync/atomic"
"syscall" "syscall"
"time" "time"
) )
...@@ -15,6 +18,12 @@ import ( ...@@ -15,6 +18,12 @@ import (
//sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes //sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
//sys timeBeginPeriod(period uint32) (n int32) = winmm.timeBeginPeriod //sys timeBeginPeriod(period uint32) (n int32) = winmm.timeBeginPeriod
type atomicBool int32
func (b *atomicBool) isSet() bool { return atomic.LoadInt32((*int32)(b)) != 0 }
func (b *atomicBool) setFalse() { atomic.StoreInt32((*int32)(b), 0) }
func (b *atomicBool) setTrue() { atomic.StoreInt32((*int32)(b), 1) }
const ( const (
cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 1 cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 1
cFILE_SKIP_SET_EVENT_ON_HANDLE = 2 cFILE_SKIP_SET_EVENT_ON_HANDLE = 2
...@@ -31,6 +40,8 @@ func (e *timeoutError) Error() string { return "i/o timeout" } ...@@ -31,6 +40,8 @@ func (e *timeoutError) Error() string { return "i/o timeout" }
func (e *timeoutError) Timeout() bool { return true } func (e *timeoutError) Timeout() bool { return true }
func (e *timeoutError) Temporary() bool { return true } func (e *timeoutError) Temporary() bool { return true }
type timeoutChan chan struct{}
var ioInitOnce sync.Once var ioInitOnce sync.Once
var ioCompletionPort syscall.Handle var ioCompletionPort syscall.Handle
...@@ -61,8 +72,16 @@ type win32File struct { ...@@ -61,8 +72,16 @@ type win32File struct {
handle syscall.Handle handle syscall.Handle
wg sync.WaitGroup wg sync.WaitGroup
closing bool closing bool
readDeadline time.Time readDeadline deadlineHandler
writeDeadline time.Time writeDeadline deadlineHandler
}
type deadlineHandler struct {
setLock sync.Mutex
channel timeoutChan
channelLock sync.RWMutex
timer *time.Timer
timedout atomicBool
} }
// makeWin32File makes a new win32File from an existing file handle // makeWin32File makes a new win32File from an existing file handle
...@@ -77,7 +96,8 @@ func makeWin32File(h syscall.Handle) (*win32File, error) { ...@@ -77,7 +96,8 @@ func makeWin32File(h syscall.Handle) (*win32File, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
runtime.SetFinalizer(f, (*win32File).closeHandle) f.readDeadline.channel = make(timeoutChan)
f.writeDeadline.channel = make(timeoutChan)
return f, nil return f, nil
} }
...@@ -101,11 +121,11 @@ func (f *win32File) closeHandle() { ...@@ -101,11 +121,11 @@ func (f *win32File) closeHandle() {
// Close closes a win32File. // Close closes a win32File.
func (f *win32File) Close() error { func (f *win32File) Close() error {
f.closeHandle() f.closeHandle()
runtime.SetFinalizer(f, nil)
return nil return nil
} }
// prepareIo prepares for a new IO operation // prepareIo prepares for a new IO operation.
// The caller must call f.wg.Done() when the IO is finished, prior to Close() returning.
func (f *win32File) prepareIo() (*ioOperation, error) { func (f *win32File) prepareIo() (*ioOperation, error) {
f.wg.Add(1) f.wg.Add(1)
if f.closing { if f.closing {
...@@ -134,47 +154,45 @@ func ioCompletionProcessor(h syscall.Handle) { ...@@ -134,47 +154,45 @@ func ioCompletionProcessor(h syscall.Handle) {
// asyncIo processes the return value from ReadFile or WriteFile, blocking until // asyncIo processes the return value from ReadFile or WriteFile, blocking until
// the operation has actually completed. // the operation has actually completed.
func (f *win32File) asyncIo(c *ioOperation, deadline time.Time, bytes uint32, err error) (int, error) { func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, err error) (int, error) {
if err != syscall.ERROR_IO_PENDING { if err != syscall.ERROR_IO_PENDING {
f.wg.Done()
return int(bytes), err return int(bytes), err
} else { }
var r ioResult
wait := true
timedout := false
if f.closing { if f.closing {
cancelIoEx(f.handle, &c.o) cancelIoEx(f.handle, &c.o)
} else if !deadline.IsZero() { }
now := time.Now()
if !deadline.After(now) { var timeout timeoutChan
timedout = true if d != nil {
} else { d.channelLock.Lock()
timeout := time.After(deadline.Sub(now)) timeout = d.channel
d.channelLock.Unlock()
}
var r ioResult
select { select {
case r = <-c.ch: case r = <-c.ch:
wait = false err = r.err
case <-timeout: if err == syscall.ERROR_OPERATION_ABORTED {
timedout = true if f.closing {
} err = ErrFileClosed
} }
} }
if timedout { case <-timeout:
cancelIoEx(f.handle, &c.o) cancelIoEx(f.handle, &c.o)
}
if wait {
r = <-c.ch r = <-c.ch
}
err = r.err err = r.err
if err == syscall.ERROR_OPERATION_ABORTED { if err == syscall.ERROR_OPERATION_ABORTED {
if f.closing {
err = ErrFileClosed
} else if timedout {
err = ErrTimeout err = ErrTimeout
} }
} }
f.wg.Done()
// runtime.KeepAlive is needed, as c is passed via native
// code to ioCompletionProcessor, c must remain alive
// until the channel read is complete.
runtime.KeepAlive(c)
return int(r.bytes), err return int(r.bytes), err
}
} }
// Read reads from a file handle. // Read reads from a file handle.
...@@ -183,9 +201,16 @@ func (f *win32File) Read(b []byte) (int, error) { ...@@ -183,9 +201,16 @@ func (f *win32File) Read(b []byte) (int, error) {
if err != nil { if err != nil {
return 0, err return 0, err
} }
defer f.wg.Done()
if f.readDeadline.timedout.isSet() {
return 0, ErrTimeout
}
var bytes uint32 var bytes uint32
err = syscall.ReadFile(f.handle, b, &bytes, &c.o) err = syscall.ReadFile(f.handle, b, &bytes, &c.o)
n, err := f.asyncIo(c, f.readDeadline, bytes, err) n, err := f.asyncIo(c, &f.readDeadline, bytes, err)
runtime.KeepAlive(b)
// Handle EOF conditions. // Handle EOF conditions.
if err == nil && n == 0 && len(b) != 0 { if err == nil && n == 0 && len(b) != 0 {
...@@ -203,17 +228,68 @@ func (f *win32File) Write(b []byte) (int, error) { ...@@ -203,17 +228,68 @@ func (f *win32File) Write(b []byte) (int, error) {
if err != nil { if err != nil {
return 0, err return 0, err
} }
defer f.wg.Done()
if f.writeDeadline.timedout.isSet() {
return 0, ErrTimeout
}
var bytes uint32 var bytes uint32
err = syscall.WriteFile(f.handle, b, &bytes, &c.o) err = syscall.WriteFile(f.handle, b, &bytes, &c.o)
return f.asyncIo(c, f.writeDeadline, bytes, err) n, err := f.asyncIo(c, &f.writeDeadline, bytes, err)
runtime.KeepAlive(b)
return n, err
} }
func (f *win32File) SetReadDeadline(t time.Time) error { func (f *win32File) SetReadDeadline(deadline time.Time) error {
f.readDeadline = t return f.readDeadline.set(deadline)
return nil }
func (f *win32File) SetWriteDeadline(deadline time.Time) error {
return f.writeDeadline.set(deadline)
} }
func (f *win32File) SetWriteDeadline(t time.Time) error { func (f *win32File) Flush() error {
f.writeDeadline = t return syscall.FlushFileBuffers(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.setFalse()
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.setTrue()
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 return nil
} }
// +build windows
package winio package winio
import ( import (
"os" "os"
"runtime"
"syscall" "syscall"
"unsafe" "unsafe"
) )
...@@ -9,22 +12,49 @@ import ( ...@@ -9,22 +12,49 @@ import (
//sys getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = GetFileInformationByHandleEx //sys getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = GetFileInformationByHandleEx
//sys setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = SetFileInformationByHandle //sys setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = SetFileInformationByHandle
const (
fileBasicInfo = 0
fileIDInfo = 0x12
)
// FileBasicInfo contains file access time and file attributes information.
type FileBasicInfo struct { type FileBasicInfo struct {
CreationTime, LastAccessTime, LastWriteTime, ChangeTime syscall.Filetime CreationTime, LastAccessTime, LastWriteTime, ChangeTime syscall.Filetime
FileAttributes uintptr // includes padding FileAttributes uintptr // includes padding
} }
// GetFileBasicInfo retrieves times and attributes for a file.
func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) { func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
bi := &FileBasicInfo{} bi := &FileBasicInfo{}
if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), 0, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil { if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), fileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
return nil, &os.PathError{"GetFileInformationByHandleEx", f.Name(), err} return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
} }
runtime.KeepAlive(f)
return bi, nil return bi, nil
} }
// SetFileBasicInfo sets times and attributes for a file.
func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error { func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error {
if err := setFileInformationByHandle(syscall.Handle(f.Fd()), 0, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil { if err := setFileInformationByHandle(syscall.Handle(f.Fd()), fileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
return &os.PathError{"SetFileInformationByHandle", f.Name(), err} return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err}
} }
runtime.KeepAlive(f)
return nil return 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 := getFileInformationByHandleEx(syscall.Handle(f.Fd()), 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
}
// +build windows
package winio package winio
import ( import (
...@@ -11,17 +13,12 @@ import ( ...@@ -11,17 +13,12 @@ import (
) )
//sys connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) = ConnectNamedPipe //sys connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) = ConnectNamedPipe
//sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *securityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW //sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW
//sys createFile(name string, access uint32, mode uint32, sa *securityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateFileW //sys createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateFileW
//sys waitNamedPipe(name string, timeout uint32) (err error) = WaitNamedPipeW //sys waitNamedPipe(name string, timeout uint32) (err error) = WaitNamedPipeW
//sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo //sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo
//sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW //sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW
//sys localAlloc(uFlags uint32, length uint32) (ptr uintptr) = LocalAlloc
type securityAttributes struct {
Length uint32
SecurityDescriptor *byte
InheritHandle uint32
}
const ( const (
cERROR_PIPE_BUSY = syscall.Errno(231) cERROR_PIPE_BUSY = syscall.Errno(231)
...@@ -85,7 +82,11 @@ func (f *win32MessageBytePipe) CloseWrite() error { ...@@ -85,7 +82,11 @@ func (f *win32MessageBytePipe) CloseWrite() error {
if f.writeClosed { if f.writeClosed {
return errPipeWriteClosed return errPipeWriteClosed
} }
_, err := f.win32File.Write(nil) err := f.win32File.Flush()
if err != nil {
return err
}
_, err = f.win32File.Write(nil)
if err != nil { if err != nil {
return err return err
} }
...@@ -225,12 +226,15 @@ func makeServerPipeHandle(path string, securityDescriptor []byte, c *PipeConfig, ...@@ -225,12 +226,15 @@ func makeServerPipeHandle(path string, securityDescriptor []byte, c *PipeConfig,
mode |= cPIPE_TYPE_MESSAGE mode |= cPIPE_TYPE_MESSAGE
} }
var sa securityAttributes sa := &syscall.SecurityAttributes{}
sa.Length = uint32(unsafe.Sizeof(sa)) sa.Length = uint32(unsafe.Sizeof(*sa))
if securityDescriptor != nil { if securityDescriptor != nil {
sa.SecurityDescriptor = &securityDescriptor[0] len := uint32(len(securityDescriptor))
sa.SecurityDescriptor = localAlloc(0, len)
defer localFree(sa.SecurityDescriptor)
copy((*[0xffff]byte)(unsafe.Pointer(sa.SecurityDescriptor))[:], securityDescriptor)
} }
h, err := createNamedPipe(path, flags, mode, cPIPE_UNLIMITED_INSTANCES, uint32(c.OutputBufferSize), uint32(c.InputBufferSize), 0, &sa) h, err := createNamedPipe(path, flags, mode, cPIPE_UNLIMITED_INSTANCES, uint32(c.OutputBufferSize), uint32(c.InputBufferSize), 0, sa)
if err != nil { if err != nil {
return 0, &os.PathError{Op: "open", Path: path, Err: err} return 0, &os.PathError{Op: "open", Path: path, Err: err}
} }
...@@ -356,8 +360,10 @@ func connectPipe(p *win32File) error { ...@@ -356,8 +360,10 @@ func connectPipe(p *win32File) error {
if err != nil { if err != nil {
return err return err
} }
defer p.wg.Done()
err = connectNamedPipe(p.handle, &c.o) err = connectNamedPipe(p.handle, &c.o)
_, err = p.asyncIo(c, time.Time{}, 0, err) _, err = p.asyncIo(c, nil, 0, err)
if err != nil && err != cERROR_PIPE_CONNECTED { if err != nil && err != cERROR_PIPE_CONNECTED {
return err return err
} }
......
// +build windows
package winio package winio
import ( import (
...@@ -5,14 +7,17 @@ import ( ...@@ -5,14 +7,17 @@ import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"runtime" "runtime"
"sync"
"syscall" "syscall"
"unicode/utf16" "unicode/utf16"
"golang.org/x/sys/windows"
) )
//sys adjustTokenPrivileges(token syscall.Handle, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) [true] = advapi32.AdjustTokenPrivileges //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 impersonateSelf(level uint32) (err error) = advapi32.ImpersonateSelf
//sys revertToSelf() (err error) = advapi32.RevertToSelf //sys revertToSelf() (err error) = advapi32.RevertToSelf
//sys openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *syscall.Handle) (err error) = advapi32.OpenThreadToken //sys openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) = advapi32.OpenThreadToken
//sys getCurrentThread() (h syscall.Handle) = GetCurrentThread //sys getCurrentThread() (h syscall.Handle) = GetCurrentThread
//sys lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) = advapi32.LookupPrivilegeValueW //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 lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) = advapi32.LookupPrivilegeNameW
...@@ -34,6 +39,12 @@ const ( ...@@ -34,6 +39,12 @@ const (
securityDelegation securityDelegation
) )
var (
privNames = make(map[string]uint64)
privNameMutex sync.Mutex
)
// PrivilegeError represents an error enabling privileges.
type PrivilegeError struct { type PrivilegeError struct {
privileges []uint64 privileges []uint64
} }
...@@ -56,20 +67,17 @@ func (e *PrivilegeError) Error() string { ...@@ -56,20 +67,17 @@ func (e *PrivilegeError) Error() string {
return s return s
} }
// RunWithPrivilege enables a single privilege for a function call.
func RunWithPrivilege(name string, fn func() error) error { func RunWithPrivilege(name string, fn func() error) error {
return RunWithPrivileges([]string{name}, fn) return RunWithPrivileges([]string{name}, fn)
} }
// RunWithPrivileges enables privileges for a function call.
func RunWithPrivileges(names []string, fn func() error) error { func RunWithPrivileges(names []string, fn func() error) error {
var privileges []uint64 privileges, err := mapPrivileges(names)
for _, name := range names {
p := uint64(0)
err := lookupPrivilegeValue("", name, &p)
if err != nil { if err != nil {
return err return err
} }
privileges = append(privileges, p)
}
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
token, err := newThreadToken() token, err := newThreadToken()
...@@ -77,19 +85,64 @@ func RunWithPrivileges(names []string, fn func() error) error { ...@@ -77,19 +85,64 @@ func RunWithPrivileges(names []string, fn func() error) error {
return err return err
} }
defer releaseThreadToken(token) defer releaseThreadToken(token)
err = adjustPrivileges(token, privileges) err = adjustPrivileges(token, privileges, SE_PRIVILEGE_ENABLED)
if err != nil { if err != nil {
return err return err
} }
return fn() return fn()
} }
func adjustPrivileges(token syscall.Handle, privileges []uint64) error { func mapPrivileges(names []string) ([]uint64, error) {
var privileges []uint64
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.GetCurrentProcess()
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 var b bytes.Buffer
binary.Write(&b, binary.LittleEndian, uint32(len(privileges))) binary.Write(&b, binary.LittleEndian, uint32(len(privileges)))
for _, p := range privileges { for _, p := range privileges {
binary.Write(&b, binary.LittleEndian, p) binary.Write(&b, binary.LittleEndian, p)
binary.Write(&b, binary.LittleEndian, uint32(SE_PRIVILEGE_ENABLED)) binary.Write(&b, binary.LittleEndian, action)
} }
prevState := make([]byte, b.Len()) prevState := make([]byte, b.Len())
reqSize := uint32(0) reqSize := uint32(0)
...@@ -113,23 +166,22 @@ func getPrivilegeName(luid uint64) string { ...@@ -113,23 +166,22 @@ func getPrivilegeName(luid uint64) string {
var displayNameBuffer [256]uint16 var displayNameBuffer [256]uint16
displayBufSize := uint32(len(displayNameBuffer)) displayBufSize := uint32(len(displayNameBuffer))
var langId uint32 var langID uint32
err = lookupPrivilegeDisplayName("", &nameBuffer[0], &displayNameBuffer[0], &displayBufSize, &langId) err = lookupPrivilegeDisplayName("", &nameBuffer[0], &displayNameBuffer[0], &displayBufSize, &langID)
if err != nil { if err != nil {
return fmt.Sprintf("<unknown privilege %s>", utf16.Decode(nameBuffer[:bufSize])) return fmt.Sprintf("<unknown privilege %s>", string(utf16.Decode(nameBuffer[:bufSize])))
} }
return string(utf16.Decode(displayNameBuffer[:displayBufSize])) return string(utf16.Decode(displayNameBuffer[:displayBufSize]))
} }
func newThreadToken() (syscall.Handle, error) { func newThreadToken() (windows.Token, error) {
err := impersonateSelf(securityImpersonation) err := impersonateSelf(securityImpersonation)
if err != nil { if err != nil {
panic(err)
return 0, err return 0, err
} }
var token syscall.Handle var token windows.Token
err = openThreadToken(getCurrentThread(), syscall.TOKEN_ADJUST_PRIVILEGES|syscall.TOKEN_QUERY, false, &token) err = openThreadToken(getCurrentThread(), syscall.TOKEN_ADJUST_PRIVILEGES|syscall.TOKEN_QUERY, false, &token)
if err != nil { if err != nil {
rerr := revertToSelf() rerr := revertToSelf()
...@@ -141,10 +193,10 @@ func newThreadToken() (syscall.Handle, error) { ...@@ -141,10 +193,10 @@ func newThreadToken() (syscall.Handle, error) {
return token, nil return token, nil
} }
func releaseThreadToken(h syscall.Handle) { func releaseThreadToken(h windows.Token) {
err := revertToSelf() err := revertToSelf()
if err != nil { if err != nil {
panic(err) panic(err)
} }
syscall.Close(h) h.Close()
} }
...@@ -43,8 +43,12 @@ func (e *UnsupportedReparsePointError) Error() string { ...@@ -43,8 +43,12 @@ func (e *UnsupportedReparsePointError) Error() string {
// DecodeReparsePoint decodes a Win32 REPARSE_DATA_BUFFER structure containing either a symlink // DecodeReparsePoint decodes a Win32 REPARSE_DATA_BUFFER structure containing either a symlink
// or a mount point. // or a mount point.
func DecodeReparsePoint(b []byte) (*ReparsePoint, error) { func DecodeReparsePoint(b []byte) (*ReparsePoint, error) {
isMountPoint := false
tag := binary.LittleEndian.Uint32(b[0:4]) tag := binary.LittleEndian.Uint32(b[0:4])
return DecodeReparsePointData(tag, b[8:])
}
func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) {
isMountPoint := false
switch tag { switch tag {
case reparseTagMountPoint: case reparseTagMountPoint:
isMountPoint = true isMountPoint = true
...@@ -52,11 +56,11 @@ func DecodeReparsePoint(b []byte) (*ReparsePoint, error) { ...@@ -52,11 +56,11 @@ func DecodeReparsePoint(b []byte) (*ReparsePoint, error) {
default: default:
return nil, &UnsupportedReparsePointError{tag} return nil, &UnsupportedReparsePointError{tag}
} }
nameOffset := 16 + binary.LittleEndian.Uint16(b[12:14]) nameOffset := 8 + binary.LittleEndian.Uint16(b[4:6])
if !isMountPoint { if !isMountPoint {
nameOffset += 4 nameOffset += 4
} }
nameLength := binary.LittleEndian.Uint16(b[14:16]) nameLength := binary.LittleEndian.Uint16(b[6:8])
name := make([]uint16, nameLength/2) name := make([]uint16, nameLength/2)
err := binary.Read(bytes.NewReader(b[nameOffset:nameOffset+nameLength]), binary.LittleEndian, &name) err := binary.Read(bytes.NewReader(b[nameOffset:nameOffset+nameLength]), binary.LittleEndian, &name)
if err != nil { if err != nil {
...@@ -76,7 +80,7 @@ func EncodeReparsePoint(rp *ReparsePoint) []byte { ...@@ -76,7 +80,7 @@ func EncodeReparsePoint(rp *ReparsePoint) []byte {
var ntTarget string var ntTarget string
relative := false relative := false
if strings.HasPrefix(rp.Target, `\\?\`) { if strings.HasPrefix(rp.Target, `\\?\`) {
ntTarget = rp.Target ntTarget = `\??\` + rp.Target[4:]
} else if strings.HasPrefix(rp.Target, `\\`) { } else if strings.HasPrefix(rp.Target, `\\`) {
ntTarget = `\??\UNC\` + rp.Target[2:] ntTarget = `\??\UNC\` + rp.Target[2:]
} else if len(rp.Target) >= 2 && isDriveLetter(rp.Target[0]) && rp.Target[1] == ':' { } else if len(rp.Target) >= 2 && isDriveLetter(rp.Target[0]) && rp.Target[1] == ':' {
......
// +build windows
package winio package winio
import ( import (
......
package winio package winio
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go //go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["mksyscall.go"],
tags = ["automanaged"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
// Copyright 2009 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.
//
// System calls for 386, Windows are implemented in runtime/syscall_windows.goc
//
TEXT ·getprocaddress(SB), 7, $0-8
JMP syscall·getprocaddress(SB)
TEXT ·loadlibrary(SB), 7, $0-4
JMP syscall·loadlibrary(SB)
// Copyright 2009 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.
//
// System calls for amd64, Windows are implemented in runtime/syscall_windows.goc
//
TEXT ·getprocaddress(SB), 7, $0-32
JMP syscall·getprocaddress(SB)
TEXT ·loadlibrary(SB), 7, $0-8
JMP syscall·loadlibrary(SB)
// Copyright 2014 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.
// +build windows
// +build go1.4
package windows
import "syscall"
func Unsetenv(key string) error {
// This was added in Go 1.4.
return syscall.Unsetenv(key)
}
// Copyright 2010 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.
// Windows environment variables.
package windows
import "syscall"
func Getenv(key string) (value string, found bool) {
return syscall.Getenv(key)
}
func Setenv(key, value string) error {
return syscall.Setenv(key, value)
}
func Clearenv() {
syscall.Clearenv()
}
func Environ() []string {
return syscall.Environ()
}
// Copyright 2012 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.
// +build windows
package windows
const (
EVENTLOG_SUCCESS = 0
EVENTLOG_ERROR_TYPE = 1
EVENTLOG_WARNING_TYPE = 2
EVENTLOG_INFORMATION_TYPE = 4
EVENTLOG_AUDIT_SUCCESS = 8
EVENTLOG_AUDIT_FAILURE = 16
)
//sys RegisterEventSource(uncServerName *uint16, sourceName *uint16) (handle Handle, err error) [failretval==0] = advapi32.RegisterEventSourceW
//sys DeregisterEventSource(handle Handle) (err error) = advapi32.DeregisterEventSource
//sys ReportEvent(log Handle, etype uint16, category uint16, eventId uint32, usrSId uintptr, numStrings uint16, dataSize uint32, strings **uint16, rawData *byte) (err error) = advapi32.ReportEventW
// Copyright 2009 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.
// Fork, exec, wait, etc.
package windows
// EscapeArg rewrites command line argument s as prescribed
// in http://msdn.microsoft.com/en-us/library/ms880421.
// This function returns "" (2 double quotes) if s is empty.
// Alternatively, these transformations are done:
// - every back slash (\) is doubled, but only if immediately
// followed by double quote (");
// - every double quote (") is escaped by back slash (\);
// - finally, s is wrapped with double quotes (arg -> "arg"),
// but only if there is space or tab inside s.
func EscapeArg(s string) string {
if len(s) == 0 {
return "\"\""
}
n := len(s)
hasSpace := false
for i := 0; i < len(s); i++ {
switch s[i] {
case '"', '\\':
n++
case ' ', '\t':
hasSpace = true
}
}
if hasSpace {
n += 2
}
if n == len(s) {
return s
}
qs := make([]byte, n)
j := 0
if hasSpace {
qs[j] = '"'
j++
}
slashes := 0
for i := 0; i < len(s); i++ {
switch s[i] {
default:
slashes = 0
qs[j] = s[i]
case '\\':
slashes++
qs[j] = s[i]
case '"':
for ; slashes > 0; slashes-- {
qs[j] = '\\'
j++
}
qs[j] = '\\'
j++
qs[j] = s[i]
}
j++
}
if hasSpace {
for ; slashes > 0; slashes-- {
qs[j] = '\\'
j++
}
qs[j] = '"'
j++
}
return string(qs[:j])
}
func CloseOnExec(fd Handle) {
SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0)
}
// FullPath retrieves the full path of the specified file.
func FullPath(name string) (path string, err error) {
p, err := UTF16PtrFromString(name)
if err != nil {
return "", err
}
n := uint32(100)
for {
buf := make([]uint16, n)
n, err = GetFullPathName(p, uint32(len(buf)), &buf[0], nil)
if err != nil {
return "", err
}
if n <= uint32(len(buf)) {
return UTF16ToString(buf[:n]), nil
}
}
}
// Copyright 2009 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 windows
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go eventlog.go service.go syscall_windows.go security_windows.go
// Copyright 2012 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.
// +build windows,race
package windows
import (
"runtime"
"unsafe"
)
const raceenabled = true
func raceAcquire(addr unsafe.Pointer) {
runtime.RaceAcquire(addr)
}
func raceReleaseMerge(addr unsafe.Pointer) {
runtime.RaceReleaseMerge(addr)
}
func raceReadRange(addr unsafe.Pointer, len int) {
runtime.RaceReadRange(addr, len)
}
func raceWriteRange(addr unsafe.Pointer, len int) {
runtime.RaceWriteRange(addr, len)
}
// Copyright 2012 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.
// +build windows,!race
package windows
import (
"unsafe"
)
const raceenabled = false
func raceAcquire(addr unsafe.Pointer) {
}
func raceReleaseMerge(addr unsafe.Pointer) {
}
func raceReadRange(addr unsafe.Pointer, len int) {
}
func raceWriteRange(addr unsafe.Pointer, len int) {
}
// Copyright 2012 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.
// +build windows
package windows
const (
SC_MANAGER_CONNECT = 1
SC_MANAGER_CREATE_SERVICE = 2
SC_MANAGER_ENUMERATE_SERVICE = 4
SC_MANAGER_LOCK = 8
SC_MANAGER_QUERY_LOCK_STATUS = 16
SC_MANAGER_MODIFY_BOOT_CONFIG = 32
SC_MANAGER_ALL_ACCESS = 0xf003f
)
//sys OpenSCManager(machineName *uint16, databaseName *uint16, access uint32) (handle Handle, err error) [failretval==0] = advapi32.OpenSCManagerW
const (
SERVICE_KERNEL_DRIVER = 1
SERVICE_FILE_SYSTEM_DRIVER = 2
SERVICE_ADAPTER = 4
SERVICE_RECOGNIZER_DRIVER = 8
SERVICE_WIN32_OWN_PROCESS = 16
SERVICE_WIN32_SHARE_PROCESS = 32
SERVICE_WIN32 = SERVICE_WIN32_OWN_PROCESS | SERVICE_WIN32_SHARE_PROCESS
SERVICE_INTERACTIVE_PROCESS = 256
SERVICE_DRIVER = SERVICE_KERNEL_DRIVER | SERVICE_FILE_SYSTEM_DRIVER | SERVICE_RECOGNIZER_DRIVER
SERVICE_TYPE_ALL = SERVICE_WIN32 | SERVICE_ADAPTER | SERVICE_DRIVER | SERVICE_INTERACTIVE_PROCESS
SERVICE_BOOT_START = 0
SERVICE_SYSTEM_START = 1
SERVICE_AUTO_START = 2
SERVICE_DEMAND_START = 3
SERVICE_DISABLED = 4
SERVICE_ERROR_IGNORE = 0
SERVICE_ERROR_NORMAL = 1
SERVICE_ERROR_SEVERE = 2
SERVICE_ERROR_CRITICAL = 3
SC_STATUS_PROCESS_INFO = 0
SERVICE_STOPPED = 1
SERVICE_START_PENDING = 2
SERVICE_STOP_PENDING = 3
SERVICE_RUNNING = 4
SERVICE_CONTINUE_PENDING = 5
SERVICE_PAUSE_PENDING = 6
SERVICE_PAUSED = 7
SERVICE_NO_CHANGE = 0xffffffff
SERVICE_ACCEPT_STOP = 1
SERVICE_ACCEPT_PAUSE_CONTINUE = 2
SERVICE_ACCEPT_SHUTDOWN = 4
SERVICE_ACCEPT_PARAMCHANGE = 8
SERVICE_ACCEPT_NETBINDCHANGE = 16
SERVICE_ACCEPT_HARDWAREPROFILECHANGE = 32
SERVICE_ACCEPT_POWEREVENT = 64
SERVICE_ACCEPT_SESSIONCHANGE = 128
SERVICE_CONTROL_STOP = 1
SERVICE_CONTROL_PAUSE = 2
SERVICE_CONTROL_CONTINUE = 3
SERVICE_CONTROL_INTERROGATE = 4
SERVICE_CONTROL_SHUTDOWN = 5
SERVICE_CONTROL_PARAMCHANGE = 6
SERVICE_CONTROL_NETBINDADD = 7
SERVICE_CONTROL_NETBINDREMOVE = 8
SERVICE_CONTROL_NETBINDENABLE = 9
SERVICE_CONTROL_NETBINDDISABLE = 10
SERVICE_CONTROL_DEVICEEVENT = 11
SERVICE_CONTROL_HARDWAREPROFILECHANGE = 12
SERVICE_CONTROL_POWEREVENT = 13
SERVICE_CONTROL_SESSIONCHANGE = 14
SERVICE_ACTIVE = 1
SERVICE_INACTIVE = 2
SERVICE_STATE_ALL = 3
SERVICE_QUERY_CONFIG = 1
SERVICE_CHANGE_CONFIG = 2
SERVICE_QUERY_STATUS = 4
SERVICE_ENUMERATE_DEPENDENTS = 8
SERVICE_START = 16
SERVICE_STOP = 32
SERVICE_PAUSE_CONTINUE = 64
SERVICE_INTERROGATE = 128
SERVICE_USER_DEFINED_CONTROL = 256
SERVICE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS | SERVICE_ENUMERATE_DEPENDENTS | SERVICE_START | SERVICE_STOP | SERVICE_PAUSE_CONTINUE | SERVICE_INTERROGATE | SERVICE_USER_DEFINED_CONTROL
SERVICE_RUNS_IN_SYSTEM_PROCESS = 1
SERVICE_CONFIG_DESCRIPTION = 1
SERVICE_CONFIG_FAILURE_ACTIONS = 2
NO_ERROR = 0
)
type SERVICE_STATUS struct {
ServiceType uint32
CurrentState uint32
ControlsAccepted uint32
Win32ExitCode uint32
ServiceSpecificExitCode uint32
CheckPoint uint32
WaitHint uint32
}
type SERVICE_TABLE_ENTRY struct {
ServiceName *uint16
ServiceProc uintptr
}
type QUERY_SERVICE_CONFIG struct {
ServiceType uint32
StartType uint32
ErrorControl uint32
BinaryPathName *uint16
LoadOrderGroup *uint16
TagId uint32
Dependencies *uint16
ServiceStartName *uint16
DisplayName *uint16
}
type SERVICE_DESCRIPTION struct {
Description *uint16
}
//sys CloseServiceHandle(handle Handle) (err error) = advapi32.CloseServiceHandle
//sys CreateService(mgr Handle, serviceName *uint16, displayName *uint16, access uint32, srvType uint32, startType uint32, errCtl uint32, pathName *uint16, loadOrderGroup *uint16, tagId *uint32, dependencies *uint16, serviceStartName *uint16, password *uint16) (handle Handle, err error) [failretval==0] = advapi32.CreateServiceW
//sys OpenService(mgr Handle, serviceName *uint16, access uint32) (handle Handle, err error) [failretval==0] = advapi32.OpenServiceW
//sys DeleteService(service Handle) (err error) = advapi32.DeleteService
//sys StartService(service Handle, numArgs uint32, argVectors **uint16) (err error) = advapi32.StartServiceW
//sys QueryServiceStatus(service Handle, status *SERVICE_STATUS) (err error) = advapi32.QueryServiceStatus
//sys ControlService(service Handle, control uint32, status *SERVICE_STATUS) (err error) = advapi32.ControlService
//sys StartServiceCtrlDispatcher(serviceTable *SERVICE_TABLE_ENTRY) (err error) = advapi32.StartServiceCtrlDispatcherW
//sys SetServiceStatus(service Handle, serviceStatus *SERVICE_STATUS) (err error) = advapi32.SetServiceStatus
//sys ChangeServiceConfig(service Handle, serviceType uint32, startType uint32, errorControl uint32, binaryPathName *uint16, loadOrderGroup *uint16, tagId *uint32, dependencies *uint16, serviceStartName *uint16, password *uint16, displayName *uint16) (err error) = advapi32.ChangeServiceConfigW
//sys QueryServiceConfig(service Handle, serviceConfig *QUERY_SERVICE_CONFIG, bufSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceConfigW
//sys ChangeServiceConfig2(service Handle, infoLevel uint32, info *byte) (err error) = advapi32.ChangeServiceConfig2W
//sys QueryServiceConfig2(service Handle, infoLevel uint32, buff *byte, buffSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceConfig2W
// Copyright 2009 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.
// +build windows
package windows
func itoa(val int) string { // do it here rather than with fmt to avoid dependency
if val < 0 {
return "-" + itoa(-val)
}
var buf [32]byte // big enough for int64
i := len(buf) - 1
for val >= 10 {
buf[i] = byte(val%10 + '0')
i--
val /= 10
}
buf[i] = byte(val + '0')
return string(buf[i:])
}
// Copyright 2009 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.
// +build windows
// Package windows contains an interface to the low-level operating system
// primitives. OS details vary depending on the underlying system, and
// by default, godoc will display the OS-specific documentation for the current
// system. If you want godoc to display syscall documentation for another
// system, set $GOOS and $GOARCH to the desired system. For example, if
// you want to view documentation for freebsd/arm on linux/amd64, set $GOOS
// to freebsd and $GOARCH to arm.
// The primary use of this package is inside other packages that provide a more
// portable interface to the system, such as "os", "time" and "net". Use
// those packages rather than this one if you can.
// For details of the functions and data types in this package consult
// the manuals for the appropriate operating system.
// These calls return err == nil to indicate success; otherwise
// err represents an operating system error describing the failure and
// holds a value of type syscall.Errno.
package windows
import (
"syscall"
)
// ByteSliceFromString returns a NUL-terminated slice of bytes
// containing the text of s. If s contains a NUL byte at any
// location, it returns (nil, syscall.EINVAL).
func ByteSliceFromString(s string) ([]byte, error) {
for i := 0; i < len(s); i++ {
if s[i] == 0 {
return nil, syscall.EINVAL
}
}
a := make([]byte, len(s)+1)
copy(a, s)
return a, nil
}
// BytePtrFromString returns a pointer to a NUL-terminated array of
// bytes containing the text of s. If s contains a NUL byte at any
// location, it returns (nil, syscall.EINVAL).
func BytePtrFromString(s string) (*byte, error) {
a, err := ByteSliceFromString(s)
if err != nil {
return nil, err
}
return &a[0], nil
}
// Single-word zero for use when we need a valid pointer to 0 bytes.
// See mksyscall.pl.
var _zero uintptr
func (ts *Timespec) Unix() (sec int64, nsec int64) {
return int64(ts.Sec), int64(ts.Nsec)
}
func (tv *Timeval) Unix() (sec int64, nsec int64) {
return int64(tv.Sec), int64(tv.Usec) * 1000
}
func (ts *Timespec) Nano() int64 {
return int64(ts.Sec)*1e9 + int64(ts.Nsec)
}
func (tv *Timeval) Nano() int64 {
return int64(tv.Sec)*1e9 + int64(tv.Usec)*1000
}
// Copyright 2011 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 windows
type WSAData struct {
Version uint16
HighVersion uint16
Description [WSADESCRIPTION_LEN + 1]byte
SystemStatus [WSASYS_STATUS_LEN + 1]byte
MaxSockets uint16
MaxUdpDg uint16
VendorInfo *byte
}
type Servent struct {
Name *byte
Aliases **byte
Port uint16
Proto *byte
}
// Copyright 2011 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 windows
type WSAData struct {
Version uint16
HighVersion uint16
MaxSockets uint16
MaxUdpDg uint16
VendorInfo *byte
Description [WSADESCRIPTION_LEN + 1]byte
SystemStatus [WSASYS_STATUS_LEN + 1]byte
}
type Servent struct {
Name *byte
Aliases **byte
Proto *byte
Port uint16
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment