aboutsummaryrefslogtreecommitdiffstats
path: root/Godeps/_workspace/src/github.com/microsoft/go-winio/file.go
blob: fd16f007550077ad248df1ae1af8afb0eafc652a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
package winio

import (
    "errors"
    "io"
    "runtime"
    "sync"
    "syscall"
    "time"
)

//sys cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) = CancelIoEx
//sys createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) = CreateIoCompletionPort
//sys getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus
//sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
//sys timeBeginPeriod(period uint32) (n int32) = winmm.timeBeginPeriod

const (
    cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 1
    cFILE_SKIP_SET_EVENT_ON_HANDLE        = 2
)

var (
    ErrFileClosed = errors.New("file has already been closed")
    ErrTimeout    = &timeoutError{}
)

type timeoutError struct{}

func (e *timeoutError) Error() string   { return "i/o timeout" }
func (e *timeoutError) Timeout() bool   { return true }
func (e *timeoutError) Temporary() bool { return true }

var ioInitOnce sync.Once
var ioCompletionPort syscall.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  syscall.Overlapped
    ch chan ioResult
}

func initIo() {
    h, err := createIoCompletionPort(syscall.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        syscall.Handle
    wg            sync.WaitGroup
    closing       bool
    readDeadline  time.Time
    writeDeadline time.Time
}

// makeWin32File makes a new win32File from an existing file handle
func makeWin32File(h syscall.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, cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS|cFILE_SKIP_SET_EVENT_ON_HANDLE)
    if err != nil {
        return nil, err
    }
    runtime.SetFinalizer(f, (*win32File).closeHandle)
    return f, nil
}

func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
    return makeWin32File(h)
}

// closeHandle closes the resources associated with a Win32 handle
func (f *win32File) closeHandle() {
    if !f.closing {
        // cancel all IO and wait for it to complete
        f.closing = true
        cancelIoEx(f.handle, nil)
        f.wg.Wait()
        // at this point, no new IO can start
        syscall.Close(f.handle)
        f.handle = 0
    }
}

// Close closes a win32File.
func (f *win32File) Close() error {
    f.closeHandle()
    runtime.SetFinalizer(f, nil)
    return nil
}

// prepareIo prepares for a new IO operation
func (f *win32File) prepareIo() (*ioOperation, error) {
    f.wg.Add(1)
    if f.closing {
        return nil, ErrFileClosed
    }
    c := &ioOperation{}
    c.ch = make(chan ioResult)
    return c, nil
}

// ioCompletionProcessor processes completed async IOs forever
func ioCompletionProcessor(h syscall.Handle) {
    // Set the timer resolution to 1. This fixes a performance regression in golang 1.6.
    timeBeginPeriod(1)
    for {
        var bytes uint32
        var key uintptr
        var op *ioOperation
        err := getQueuedCompletionStatus(h, &bytes, &key, &op, syscall.INFINITE)
        if op == nil {
            panic(err)
        }
        op.ch <- ioResult{bytes, err}
    }
}

// asyncIo processes the return value from ReadFile or WriteFile, blocking until
// the operation has actually completed.
func (f *win32File) asyncIo(c *ioOperation, deadline time.Time, bytes uint32, err error) (int, error) {
    if err != syscall.ERROR_IO_PENDING {
        f.wg.Done()
        return int(bytes), err
    } else {
        var r ioResult
        wait := true
        timedout := false
        if f.closing {
            cancelIoEx(f.handle, &c.o)
        } else if !deadline.IsZero() {
            now := time.Now()
            if !deadline.After(now) {
                timedout = true
            } else {
                timeout := time.After(deadline.Sub(now))
                select {
                case r = <-c.ch:
                    wait = false
                case <-timeout:
                    timedout = true
                }
            }
        }
        if timedout {
            cancelIoEx(f.handle, &c.o)
        }
        if wait {
            r = <-c.ch
        }
        err = r.err
        if err == syscall.ERROR_OPERATION_ABORTED {
            if f.closing {
                err = ErrFileClosed
            } else if timedout {
                err = ErrTimeout
            }
        }
        f.wg.Done()
        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
    }
    var bytes uint32
    err = syscall.ReadFile(f.handle, b, &bytes, &c.o)
    n, err := f.asyncIo(c, f.readDeadline, bytes, err)

    // Handle EOF conditions.
    if err == nil && n == 0 && len(b) != 0 {
        return 0, io.EOF
    } else if err == syscall.ERROR_BROKEN_PIPE {
        return 0, io.EOF
    } else {
        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
    }
    var bytes uint32
    err = syscall.WriteFile(f.handle, b, &bytes, &c.o)
    return f.asyncIo(c, f.writeDeadline, bytes, err)
}

func (f *win32File) SetReadDeadline(t time.Time) error {
    f.readDeadline = t
    return nil
}

func (f *win32File) SetWriteDeadline(t time.Time) error {
    f.writeDeadline = t
    return nil
}