github.com/iDigitalFlame/xmt@v0.5.4/com/pipe/pipe_nix.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  	"net"
    25  	"os"
    26  	"strconv"
    27  	"strings"
    28  	"time"
    29  
    30  	"github.com/iDigitalFlame/xmt/com"
    31  	"github.com/iDigitalFlame/xmt/util"
    32  	"github.com/iDigitalFlame/xmt/util/xerr"
    33  )
    34  
    35  var dialer = new(net.Dialer)
    36  
    37  type listener struct {
    38  	_ [0]func()
    39  	net.Listener
    40  	p string
    41  }
    42  
    43  func (l *listener) Close() error {
    44  	if err := l.Listener.Close(); err != nil {
    45  		return err
    46  	}
    47  	return os.Remove(l.p)
    48  }
    49  
    50  // Dial connects to the specified Pipe path. This function will return a 'net.Conn'
    51  // instance or any errors that may occur during the connection attempt.
    52  //
    53  // Pipe names are in the form of "/<path>".
    54  //
    55  // This function blocks indefinitely. Use the DialTimeout or DialContext to specify
    56  // a control method.
    57  func Dial(path string) (net.Conn, error) {
    58  	return dialer.Dial(com.NameUnix, path)
    59  }
    60  
    61  // Listen returns a 'net.Listener' that will listen for new connections on the
    62  // Named Pipe path specified or any errors that may occur during listener
    63  // creation.
    64  //
    65  // Pipe names are in the form of "/<path>".
    66  func Listen(path string) (net.Listener, error) {
    67  	return ListenPermsContext(context.Background(), path, "")
    68  }
    69  func stringToDec(s string) (os.FileMode, error) {
    70  	if v, err := strconv.ParseInt(s, 8, 32); err == nil {
    71  		return os.FileMode(v), nil
    72  	}
    73  	var p os.FileMode
    74  	for i, c := range s {
    75  		switch {
    76  		case i < 3 && (c == 'u' || c == 'U'):
    77  			p |= os.ModeSetuid
    78  		case i < 3 && (c == 'g' || c == 'G'):
    79  			p |= os.ModeSetgid
    80  		case i == 0 && (c == 't' || c == 'T'):
    81  			p |= os.ModeSticky
    82  		case i < 3 && (c == 'r' || c == 'R'):
    83  			p |= 0400
    84  		case i < 3 && (c == 'w' || c == 'W'):
    85  			p |= 0200
    86  		case i < 3 && (c == 'x' || c == 'X'):
    87  			p |= 0100
    88  		case i >= 3 && i < 6 && (c == 'r' || c == 'R'):
    89  			p |= 0040
    90  		case i >= 3 && i < 6 && (c == 'w' || c == 'W'):
    91  			p |= 0020
    92  		case i >= 3 && i < 6 && (c == 'x' || c == 'X'):
    93  			p |= 0010
    94  		case i >= 6 && (c == 'r' || c == 'R'):
    95  			p |= 0004
    96  		case i >= 6 && (c == 'w' || c == 'W'):
    97  			p |= 0002
    98  		case i >= 6 && (c == 'x' || c == 'X'):
    99  			p |= 0001
   100  		case c == '-' || c == ' ':
   101  		case c != 'r' && c != 'R' && c != 'x' && c != 'X' && c != 'w' && c != 'W':
   102  			if xerr.ExtendedInfo {
   103  				return 0, xerr.Sub(`invalid permission "`+s+`"`, 0x2E)
   104  			}
   105  			return 0, xerr.Sub("invalid permissions", 0x2E)
   106  		}
   107  	}
   108  	return p, nil
   109  }
   110  func getPerms(s string) (os.FileMode, int, int, error) {
   111  	if i := strings.IndexByte(s, ';'); i == -1 {
   112  		p, err := stringToDec(s)
   113  		return p, -1, -1, err
   114  	}
   115  	v := strings.Split(s, ";")
   116  	if len(v) > 3 {
   117  		if xerr.ExtendedInfo {
   118  			return 0, -1, -1, xerr.Sub(`invalid permission "`+s+`" size `+util.Uitoa(uint64(len(v))), 0x2F)
   119  		}
   120  		return 0, -1, -1, xerr.Sub("invalid permission size", 0x2F)
   121  	}
   122  	var (
   123  		u, g   = -1, -1
   124  		p, err = stringToDec(v[0])
   125  	)
   126  	if err != nil {
   127  		return 0, -1, -1, err
   128  	}
   129  	if len(v) == 3 && len(v[2]) > 0 {
   130  		if g, err = strconv.Atoi(v[2]); err != nil {
   131  			return 0, -1, -1, xerr.Wrap("invalid GID", err)
   132  		}
   133  	}
   134  	if len(v) >= 2 && len(v[1]) > 0 {
   135  		if u, err = strconv.Atoi(v[1]); err != nil {
   136  			return 0, -1, -1, xerr.Wrap("invalid UID", err)
   137  		}
   138  	}
   139  	return p, u, g, nil
   140  }
   141  
   142  // ListenPerms returns a Listener that will listen for new connections on the
   143  // Named Pipe path specified or any errors that may occur during listener
   144  // creation.
   145  //
   146  // Pipe names are in the form of "/<path>".
   147  //
   148  // This function allows for specifying a Linux permissions string used to set the
   149  // permissions of the listening Pipe.
   150  func ListenPerms(path, perms string) (net.Listener, error) {
   151  	return ListenPermsContext(context.Background(), path, perms)
   152  }
   153  
   154  // DialTimeout connects to the specified Pipe path. This function will return a
   155  // net.Conn instance or any errors that may occur during the connection attempt.
   156  //
   157  // Pipe names are in the form of "/<path>".
   158  //
   159  // This function blocks for the specified amount of time and will return 'ErrTimeout'
   160  // if the timeout is reached.
   161  func DialTimeout(path string, t time.Duration) (net.Conn, error) {
   162  	return net.DialTimeout(com.NameUnix, path, t)
   163  }
   164  
   165  // DialContext connects to the specified Pipe path. This function will return a
   166  // net.Conn instance or any errors that may occur during the connection attempt.
   167  //
   168  // Pipe names are in the form of "/<path>".
   169  //
   170  // This function blocks until the supplied context is canceled and will return the
   171  // context's Err() if the cancel occurs before the connection.
   172  func DialContext(x context.Context, path string) (net.Conn, error) {
   173  	return dialer.DialContext(x, com.NameUnix, path)
   174  }
   175  
   176  // ListenContext returns a 'net.Listener' that will listen for new connections
   177  // on the Named Pipe path specified or any errors that may occur during listener
   178  // creation.
   179  //
   180  // Pipe names are in the form of "/<path>".
   181  //
   182  // The provided Context can be used to cancel the Listener.
   183  func ListenContext(x context.Context, path string) (net.Listener, error) {
   184  	return ListenPermsContext(x, path, "")
   185  }
   186  
   187  // ListenPermsContext returns a Listener that will listen for new connections on
   188  // the Named Pipe path specified or any errors that may occur during listener
   189  // creation.
   190  //
   191  // Pipe names are in the form of "/".
   192  //
   193  // This function allows for specifying a Linux permissions string used to set the
   194  // permissions of the listening Pipe.
   195  //
   196  // The provided Context can be used to cancel the Listener.
   197  func ListenPermsContext(x context.Context, path, perms string) (net.Listener, error) {
   198  	l, err := com.ListenConfig.Listen(x, com.NameUnix, path)
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  	if len(perms) == 0 {
   203  		return &listener{Listener: l, p: path}, err
   204  	}
   205  	m, u, g, err := getPerms(perms)
   206  	if err != nil {
   207  		l.Close()
   208  		return nil, err
   209  	}
   210  	if m > 0 {
   211  		if err := os.Chmod(path, m); err != nil {
   212  			l.Close()
   213  			return nil, err
   214  		}
   215  	}
   216  	if err := os.Chown(path, u, g); err != nil {
   217  		l.Close()
   218  		return nil, err
   219  	}
   220  	return &listener{Listener: l, p: path}, nil
   221  }