github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/ipc/ipc_windows.go (about) 1 package ipc 2 3 import ( 4 "context" 5 "fmt" 6 "net" 7 "os" 8 "os/user" 9 10 "github.com/google/uuid" 11 12 "github.com/Microsoft/go-winio" 13 ) 14 15 // DialContext attempts to establish an IPC connection, timing out if the 16 // provided context expires. 17 func DialContext(ctx context.Context, path string) (net.Conn, error) { 18 // Read the pipe name. 19 pipeNameBytes, err := os.ReadFile(path) 20 if err != nil { 21 return nil, fmt.Errorf("unable to read pipe name: %w", err) 22 } 23 pipeName := string(pipeNameBytes) 24 25 // Attempt to connect. 26 return winio.DialPipeContext(ctx, pipeName) 27 } 28 29 // listener implements net.Listener but provides additional cleanup facilities 30 // on top of those provided by the underlying named pipe listener. 31 type listener struct { 32 // Listener is the underlying named pipe listener. 33 net.Listener 34 // path is the path to the file where the named pipe name is stored. 35 path string 36 } 37 38 // Close closes the listener and removes the pipe name record. 39 func (l *listener) Close() error { 40 // Remove the pipe name record. 41 if err := os.Remove(l.path); err != nil { 42 l.Listener.Close() 43 return fmt.Errorf("unable to remove pipe name record: %w", err) 44 } 45 46 // Close the underlying listener. 47 return l.Listener.Close() 48 } 49 50 // NewListener creates a new IPC listener. 51 func NewListener(path string) (net.Listener, error) { 52 // Create a unique pipe name. 53 randomUUID, err := uuid.NewRandom() 54 if err != nil { 55 return nil, fmt.Errorf("unable to generate UUID for named pipe: %w", err) 56 } 57 pipeName := fmt.Sprintf(`\\.\pipe\mutagen-ipc-%s`, randomUUID.String()) 58 59 // Compute the SID of the user. 60 user, err := user.Current() 61 if err != nil { 62 return nil, fmt.Errorf("unable to look up current user: %w", err) 63 } 64 sid := user.Uid 65 66 // Create the security descriptor for the pipe. This is constructed using 67 // the Security Descriptor Definition Language (SDDL) (the Discretionary 68 // Access Control List (DACL) format), where the value in parentheses is an 69 // Access Control Entry (ACE) string. The P flag in the DACL prevents 70 // inherited permissions. The ACE string in this case grants "Generic All" 71 // (GA) permissions to its associated SID. More information can be found 72 // here: 73 // SDDL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa379570(v=vs.85).aspx 74 // ACEs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa374928(v=vs.85).aspx 75 // SIDs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa379602(v=vs.85).aspx 76 securityDescriptor := fmt.Sprintf("D:P(A;;GA;;;%s)", sid) 77 78 // Create the pipe configuration. 79 configuration := &winio.PipeConfig{ 80 SecurityDescriptor: securityDescriptor, 81 } 82 83 // Attempt to create (and open) the endpoint path where we will record the 84 // underlying named pipe name. In order to match the semantics of UNIX 85 // domain sockets, we enforce that the file doesn't exist. We do this before 86 // attempt to create the named pipe to avoid unnecessary overhead. 87 file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600) 88 if err != nil { 89 if os.IsExist(err) { 90 return nil, err 91 } 92 return nil, fmt.Errorf("unable to open endpoint: %w", err) 93 } 94 95 // Defer closure of the endpoint file when we're done, along with removal in 96 // the event of failure. 97 var successful bool 98 defer func() { 99 file.Close() 100 if !successful { 101 os.Remove(path) 102 } 103 }() 104 105 // Create the named pipe listener. 106 rawListener, err := winio.ListenPipe(pipeName, configuration) 107 if err != nil { 108 return nil, err 109 } 110 111 // Write the pipe name. This isn't 100% atomic since the name could be 112 // partially written, but MoveFileEx isn't guaranteed to be atomic either, 113 // so renaming a file into place here doesn't help much. 114 if _, err := file.Write([]byte(pipeName)); err != nil { 115 return nil, fmt.Errorf("unable to write pipe name: %w", err) 116 } 117 118 // Mark ourselves as successful. 119 successful = true 120 121 // Success. 122 return &listener{rawListener, path}, nil 123 }