github.com/iDigitalFlame/xmt@v0.5.4/com/pipe/pipe_windows.go (about) 1 //go:build windows 2 // +build windows 3 4 // Copyright (C) 2020 - 2023 iDigitalFlame 5 // 6 // This program is free software: you can redistribute it and/or modify 7 // it under the terms of the GNU General Public License as published by 8 // the Free Software Foundation, either version 3 of the License, or 9 // any later version. 10 // 11 // This program is distributed in the hope that it will be useful, 12 // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 // GNU General Public License for more details. 15 // 16 // You should have received a copy of the GNU General Public License 17 // along with this program. If not, see <https://www.gnu.org/licenses/>. 18 // 19 20 package pipe 21 22 import ( 23 "context" 24 "io" 25 "net" 26 "sync/atomic" 27 "time" 28 "unsafe" 29 30 "github.com/iDigitalFlame/xmt/com" 31 "github.com/iDigitalFlame/xmt/device/winapi" 32 "github.com/iDigitalFlame/xmt/util/bugtrack" 33 ) 34 35 const retry = 100 * time.Millisecond 36 37 // ErrClosed is an error returned by the 'Accept' function when the 38 // underlying Pipe was closed. 39 var ErrClosed = &errno{m: io.ErrClosedPipe.Error()} 40 41 type addr string 42 43 // Conn is a struct that implements a Windows Pipe connection. This is similar 44 // to the 'net.Conn' interface except it adds the 'Impersonate' function, which 45 // is only from the 'AcceptPipe' function. 46 type Conn struct { 47 _ [0]func() 48 read, write time.Time 49 addr addr 50 handle uintptr 51 } 52 type wait struct { 53 _ [0]func() 54 err error 55 n uint32 56 } 57 type errno struct { 58 e error 59 m string 60 t bool 61 } 62 63 // Listener is a struct that fulfils the 'net.Listener' interface, but used for 64 // Windows named pipes. 65 type Listener struct { 66 _ [0]func() 67 overlap *winapi.Overlapped 68 perms *winapi.SecurityAttributes 69 addr addr 70 active, handle uintptr 71 done uint32 72 } 73 74 // Fd returns the file descriptor handle for this PipeCon. 75 func (c *Conn) Fd() uintptr { 76 return c.handle 77 } 78 func (addr) Network() string { 79 return com.NamePipe 80 } 81 82 // Close releases the associated Pipe's resources. The connection is no longer 83 // considered valid after a call to this function. 84 func (c *Conn) Close() error { 85 winapi.CancelIoEx(c.handle, nil) 86 winapi.DisconnectNamedPipe(c.handle) 87 err := winapi.CloseHandle(c.handle) 88 c.handle = 0 89 return err 90 } 91 func (a addr) String() string { 92 return string(a) 93 } 94 func (e errno) Timeout() bool { 95 return e.t 96 } 97 func (e errno) Error() string { 98 if len(e.m) == 0 && e.e != nil { 99 return e.e.Error() 100 } 101 return e.m 102 } 103 func (e errno) Unwrap() error { 104 return e.e 105 } 106 func (e errno) Temporary() bool { 107 return e.t 108 } 109 110 // Close closes the listener. Any blocked Accept operations will be unblocked 111 // and return errors. 112 func (l *Listener) Close() error { 113 if atomic.LoadUint32(&l.done) == 1 { 114 return nil 115 } 116 var err error // We shouldn't let errors stop us from cleaning up! 117 if atomic.StoreUint32(&l.done, 1); l.handle > 0 { 118 if err = winapi.DisconnectNamedPipe(l.handle); err != nil { 119 if bugtrack.Enabled { 120 bugtrack.Track("com.(*Listener).Close(): DisconnectNamedPipe error: %s!", err.Error()) 121 } 122 } 123 if err = winapi.CloseHandle(l.handle); err != nil { 124 if bugtrack.Enabled { 125 bugtrack.Track("com.(*Listener).Close(): CloseHandle(handle) error: %s!", err.Error()) 126 } 127 } 128 l.handle = 0 129 } 130 if l.overlap != nil && l.active > 0 { 131 if err = winapi.CancelIoEx(l.active, l.overlap); err != nil { 132 if bugtrack.Enabled { 133 bugtrack.Track("com.(*Listener).Close(): CancelIoEx error: %s!", err.Error()) 134 } 135 } 136 if err = winapi.CloseHandle(l.overlap.Event); err != nil { 137 if bugtrack.Enabled { 138 bugtrack.Track("com.(*Listener).Close(): CloseHandle(event) error: %s!", err.Error()) 139 } 140 } 141 if l.active > 0 { // Extra check as it can be reset here sometimes. 142 if err = winapi.CloseHandle(l.active); err != nil { 143 if bugtrack.Enabled { 144 bugtrack.Track("com.(*Listener).Close(): CloseHandle(active) error: %s!", err.Error()) 145 } 146 } 147 } 148 l.active = 0 149 } 150 return err 151 } 152 153 // Addr returns the listener's network address. 154 func (l *Listener) Addr() net.Addr { 155 return l.addr 156 } 157 158 // Impersonate will attempt to call 'ImpersonatePipeToken' which, if successful, 159 // will set the token of this Thread to the Pipe's connected client token. 160 // 161 // A call to 'device.RevertToSelf()' will reset the token. 162 func (c *Conn) Impersonate() error { 163 if c.handle == 0 { 164 return nil 165 } 166 return winapi.ImpersonatePipeToken(c.handle) 167 } 168 169 // LocalAddr returns the Pipe's local endpoint address. 170 func (c *Conn) LocalAddr() net.Addr { 171 return c.addr 172 } 173 174 // RemoteAddr returns the Pipe's remote endpoint address. 175 func (c *Conn) RemoteAddr() net.Addr { 176 return c.addr 177 } 178 179 // Dial connects to the specified Pipe path. This function will return a 'net.Conn' 180 // instance or any errors that may occur during the connection attempt. 181 // 182 // Pipe names are in the form of "\\<computer>\pipe\<path>". 183 // 184 // This function blocks indefinitely. Use the DialTimeout or DialContext to specify 185 // a control method. 186 func Dial(path string) (net.Conn, error) { 187 return DialContext(context.Background(), path) 188 } 189 190 // Read implements the 'net.Conn' interface. 191 func (c *Conn) Read(b []byte) (int, error) { 192 if c.handle == 0 { 193 return 0, ErrClosed 194 } 195 var ( 196 a uint32 197 o = new(winapi.Overlapped) 198 err error 199 ) 200 if o.Event, err = winapi.CreateEvent(nil, true, true, ""); err != nil { 201 return 0, &errno{m: "could not create event", e: err} 202 } 203 return c.finish(winapi.ReadFile(c.handle, b, &a, o), int(a), c.read, o) 204 } 205 func (l *Listener) wait(x context.Context) { 206 <-x.Done() 207 l.Close() 208 } 209 210 // Write implements the 'net.Conn' interface. 211 func (c *Conn) Write(b []byte) (int, error) { 212 var ( 213 a uint32 214 o = new(winapi.Overlapped) 215 err error 216 ) 217 if o.Event, err = winapi.CreateEvent(nil, true, true, ""); err != nil { 218 return 0, &errno{m: "could not create event", e: err} 219 } 220 return c.finish(winapi.WriteFile(c.handle, b, &a, o), int(a), c.write, o) 221 } 222 223 // Listen returns a 'net.Listener' that will listen for new connections on the 224 // Named Pipe path specified or any errors that may occur during listener 225 // creation. 226 // 227 // Pipe names are in the form of "\\<computer>\pipe\<path>". 228 func Listen(path string) (*Listener, error) { 229 return ListenSecurityContext(context.Background(), path, nil) 230 } 231 232 // Accept waits for and returns the next connection to the listener. 233 func (l *Listener) Accept() (net.Conn, error) { 234 return l.AcceptPipe() 235 } 236 237 // SetDeadline implements the 'net.Conn' interface. 238 func (c *Conn) SetDeadline(t time.Time) error { 239 c.read, c.write = t, t 240 return nil 241 } 242 243 // AcceptPipe waits for and returns the next connection to the listener. 244 // 245 // This function returns the real type of 'Conn' that can be used with the 246 // 'Impersonate' function. 247 func (l *Listener) AcceptPipe() (*Conn, error) { 248 if atomic.LoadUint32(&l.done) == 1 { 249 return nil, ErrClosed 250 } 251 var ( 252 h uintptr 253 err error 254 ) 255 if l.handle == 0 { 256 if h, err = create(l.addr, l.perms, 50, 512, false); err != nil { 257 return nil, &errno{e: err} 258 } 259 } else { 260 h, l.handle = l.handle, 0 261 } 262 o := new(winapi.Overlapped) 263 if o.Event, err = winapi.CreateEvent(nil, true, true, ""); err != nil { 264 winapi.CloseHandle(h) 265 return nil, &errno{m: "could not create event", e: err} 266 } 267 if err = winapi.ConnectNamedPipe(h, o); err == winapi.ErrIoPending || err == winapi.ErrIoIncomplete { 268 l.overlap, l.active = o, h 269 _, err = complete(h, o) 270 } 271 if l.active = 0; atomic.LoadUint32(&l.done) == 1 { 272 winapi.CloseHandle(o.Event) 273 return nil, ErrClosed 274 } 275 winapi.CancelIoEx(l.active, l.overlap) 276 if winapi.CloseHandle(o.Event); err == winapi.ErrOperationAborted { 277 winapi.CloseHandle(h) 278 return nil, ErrClosed 279 } 280 if err == winapi.ErrNoData { 281 winapi.CloseHandle(h) 282 return nil, ErrEmptyConn 283 } 284 if err != nil && err != winapi.ErrPipeConnected { 285 winapi.CloseHandle(h) 286 return nil, &errno{m: "could not connect", e: err} 287 } 288 return &Conn{addr: l.addr, handle: h}, nil 289 } 290 291 // SetReadDeadline implements the 'net.Conn' interface. 292 func (c *Conn) SetReadDeadline(t time.Time) error { 293 c.read = t 294 return nil 295 } 296 297 // SetWriteDeadline implements the 'net.Conn' interface. 298 func (c *Conn) SetWriteDeadline(t time.Time) error { 299 c.write = t 300 return nil 301 } 302 func connect(path string, t uint32) (*Conn, error) { 303 if len(path) == 0 || len(path) > 255 { 304 return nil, &errno{m: "invalid path length"} 305 } 306 if err := winapi.WaitNamedPipe(path, t); err != nil { 307 return nil, err 308 } 309 // 0xC0000000 - FILE_FLAG_OVERLAPPED | FILE_FLAG_WRITE_THROUGH 310 // 0x3 - FILE_SHARE_READ | FILE_SHARE_WRITE 311 // 0x3 - OPEN_EXISTING 312 // 0x40000000 - FILE_FLAG_OVERLAPPED 313 h, err := winapi.CreateFile(path, 0xC0000000, 0x3, nil, 0x3, 0x40000000, 0) 314 if err != nil { 315 return nil, err 316 } 317 return &Conn{addr: addr(path), handle: h}, nil 318 } 319 320 // ListenPerms returns a Listener that will listen for new connections on the 321 // Named Pipe path specified or any errors that may occur during listener 322 // creation. 323 // 324 // Pipe names are in the form of "\\<computer>\pipe\<path>". 325 // 326 // This function allows for specifying a SDDL string used to set the permissions 327 // of the listening Pipe. 328 func ListenPerms(path, perms string) (*Listener, error) { 329 return ListenPermsContext(context.Background(), path, perms) 330 } 331 func complete(h uintptr, o *winapi.Overlapped) (uint32, error) { 332 if _, err := winapi.WaitForSingleObject(o.Event, -1); err != nil { 333 return 0, err 334 } 335 var ( 336 n uint32 337 err = winapi.GetOverlappedResult(h, o, &n, true) 338 ) 339 return n, err 340 } 341 342 // DialTimeout connects to the specified Pipe path. This function will return a 343 // net.Conn instance or any errors that may occur during the connection attempt. 344 // 345 // Pipe names are in the form of "\\<computer>\pipe\<path>". 346 // 347 // This function blocks for the specified amount of time and will return 'ErrTimeout' 348 // if the timeout is reached. 349 func DialTimeout(path string, t time.Duration) (net.Conn, error) { 350 for n, d := time.Now(), time.Now().Add(t); n.Before(d); n = time.Now() { 351 c, err := connect(path, uint32(d.Sub(n)/time.Millisecond)) 352 if err == nil { 353 return c, nil 354 } 355 if err == winapi.ErrSemTimeout { 356 return nil, ErrTimeout 357 } 358 if err == winapi.ErrBadPathname { 359 return nil, &errno{m: `invalid path "` + path + `"`, e: err} 360 } 361 if err == winapi.ErrFileNotFound || err == winapi.ErrPipeBusy { 362 if l := time.Until(d); l < retry { 363 time.Sleep(l - time.Millisecond) 364 } else { 365 time.Sleep(retry) 366 } 367 continue 368 } 369 return nil, &errno{m: err.Error(), e: err} 370 } 371 return nil, ErrTimeout 372 } 373 374 // DialContext connects to the specified Pipe path. This function will return a 375 // net.Conn instance or any errors that may occur during the connection attempt. 376 // 377 // Pipe names are in the form of "\\<computer>\pipe\<path>". 378 // 379 // This function blocks until the supplied context is canceled and will return the 380 // context's Err() if the cancel occurs before the connection. 381 func DialContext(x context.Context, path string) (net.Conn, error) { 382 for { 383 if x != nil { 384 select { 385 case <-x.Done(): 386 return nil, x.Err() 387 default: 388 } 389 } 390 c, err := connect(path, 250) 391 if err == nil { 392 return c, nil 393 } 394 if err == winapi.ErrSemTimeout { 395 return nil, ErrTimeout 396 } 397 if err == winapi.ErrBadPathname { 398 return nil, &errno{m: `invalid path "` + path + `"`, e: err} 399 } 400 if err == winapi.ErrFileNotFound || err == winapi.ErrPipeBusy { 401 time.Sleep(retry) 402 continue 403 } 404 return nil, &errno{m: err.Error(), e: err} 405 } 406 } 407 408 // ListenContext returns a 'net.Listener' that will listen for new connections 409 // on the Named Pipe path specified or any errors that may occur during listener 410 // creation. 411 // 412 // Pipe names are in the form of "\\<computer>\pipe\<path>". 413 // 414 // The provided Context can be used to cancel the Listener. 415 func ListenContext(x context.Context, path string) (*Listener, error) { 416 return ListenSecurityContext(x, path, nil) 417 } 418 func waitComplete(w chan<- wait, h uintptr, o *winapi.Overlapped, s *uint32) { 419 if n, err := complete(h, o); atomic.LoadUint32(s) == 0 { 420 w <- wait{n: n, err: err} 421 } 422 } 423 424 // ListenSecurity returns a net.Listener that will listen for new connections on 425 // the Named Pipe path specified or any errors that may occur during listener 426 // creation. 427 // 428 // Pipe names are in the form of "\\<computer>\pipe\<path>". 429 // 430 // This function allows for specifying a SecurityAttributes object used to set 431 // the permissions of the listening Pipe. 432 func ListenSecurity(path string, p *winapi.SecurityAttributes) (*Listener, error) { 433 return ListenSecurityContext(context.Background(), path, p) 434 } 435 436 // ListenPermsContext returns a Listener that will listen for new connections on 437 // the Named Pipe path specified or any errors that may occur during listener 438 // creation. 439 // 440 // Pipe names are in the form of "\\<computer>\pipe\<path>". 441 // 442 // This function allows for specifying a SDDL string used to set the permissions 443 // of the listening Pipe. 444 // 445 // The provided Context can be used to cancel the Listener. 446 func ListenPermsContext(x context.Context, path, perms string) (*Listener, error) { 447 var ( 448 s = winapi.SecurityAttributes{InheritHandle: 0} 449 err error 450 ) 451 if len(perms) > 0 { 452 if s.SecurityDescriptor, err = winapi.SecurityDescriptorFromString(perms); err != nil { 453 return nil, err 454 } 455 s.Length = uint32(unsafe.Sizeof(s)) 456 } 457 return ListenSecurityContext(x, path, &s) 458 } 459 func (c *Conn) finish(e error, a int, t time.Time, o *winapi.Overlapped) (int, error) { 460 if e == winapi.ErrBrokenPipe { 461 winapi.CloseHandle(o.Event) 462 return a, io.EOF 463 } 464 if e != winapi.ErrIoIncomplete && e != winapi.ErrIoPending { 465 winapi.CloseHandle(o.Event) 466 return a, e 467 } 468 var ( 469 z *time.Timer 470 s uint32 471 f <-chan time.Time 472 w = make(chan wait, 1) 473 ) 474 if !t.IsZero() && time.Now().Before(t) { 475 z = time.NewTimer(time.Until(t)) 476 f = z.C 477 } 478 go waitComplete(w, c.handle, o, &s) 479 select { 480 case <-f: 481 winapi.CancelIoEx(c.handle, o) 482 a, e = 0, ErrTimeout 483 case d := <-w: 484 a, e = int(d.n), d.err 485 } 486 atomic.StoreUint32(&s, 1) 487 if close(w); e == winapi.ErrBrokenPipe { 488 e = io.EOF 489 } 490 if winapi.CloseHandle(o.Event); z != nil { 491 z.Stop() 492 } 493 return a, e 494 } 495 func create(path addr, p *winapi.SecurityAttributes, t, l uint32, f bool) (uintptr, error) { 496 // 0x40040003 - PIPE_ACCESS_DUPLEX | WRITE_DAC | FILE_FLAG_OVERLAPPED 497 m := uint32(0x40040003) 498 if f { 499 // 0x80000 - FILE_FLAG_FIRST_PIPE_INSTANCE 500 m |= 0x80000 501 } 502 h, err := winapi.CreateNamedPipe(string(path), m, 0, 0xFF, l, l, t, p) 503 if err != nil { 504 return 0, err 505 } 506 return h, nil 507 } 508 509 // ListenSecurityContext returns a net.Listener that will listen for new connections 510 // on the Named Pipe path specified or any errors that may occur during listener 511 // creation. 512 // 513 // Pipe names are in the form of "\\<computer>\pipe\<path>". 514 // 515 // This function allows for specifying a SecurityAttributes object used to set 516 // the permissions of the listening Pipe. 517 // 518 // The provided Context can be used to cancel the Listener. 519 func ListenSecurityContext(x context.Context, path string, p *winapi.SecurityAttributes) (*Listener, error) { 520 var ( 521 a = addr(path) 522 l, err = create(a, p, 50, 512, true) 523 ) 524 if err != nil { 525 if err == winapi.ErrInvalidName { 526 return nil, &errno{m: `invalid path "` + path + `"`, e: err} 527 } 528 return nil, &errno{m: err.Error(), e: err} 529 } 530 n := &Listener{addr: a, handle: l, perms: p} 531 if x != context.Background() { 532 go n.wait(x) 533 } 534 return n, nil 535 }