Skip to content

Commit cc766a8

Browse files
LemonBoybrammool
authored andcommitted
patch 8.2.4684: cannot open a channel on a Unix domain socket
Problem: Cannot open a channel on a Unix domain socket. Solution: Add Unix domain socket support. (closes #10062)
1 parent 4829c1c commit cc766a8

File tree

9 files changed

+286
-50
lines changed

9 files changed

+286
-50
lines changed

runtime/doc/channel.txt

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,13 @@ To open a channel: >
119119
120120
Use |ch_status()| to see if the channel could be opened.
121121

122-
{address} has the form "hostname:port". E.g., "localhost:8765".
123-
124-
When using an IPv6 address, enclose it within square brackets. E.g.,
125-
"[2001:db8::1]:8765".
122+
*channel-address*
123+
{address} can be a domain name or an IP address, followed by a port number, or
124+
a Unix-domain socket path prefixed by "unix:". E.g. >
125+
www.example.com:80 " domain + port
126+
127.0.0.1:1234 " IPv4 + port
127+
[2001:db8::1]:8765 " IPv6 + port
128+
unix:/tmp/my-socket " Unix-domain socket path
126129
127130
{options} is a dictionary with optional entries: *channel-open-options*
128131

@@ -579,10 +582,15 @@ ch_info({handle}) *ch_info()*
579582
When opened with ch_open():
580583
"hostname" the hostname of the address
581584
"port" the port of the address
585+
"path" the path of the Unix-domain socket
582586
"sock_status" "open" or "closed"
583587
"sock_mode" "NL", "RAW", "JSON" or "JS"
584588
"sock_io" "socket"
585589
"sock_timeout" timeout in msec
590+
591+
Note that "pair" is only present for Unix-domain sockets, for
592+
regular ones "hostname" and "port" are present instead.
593+
586594
When opened with job_start():
587595
"out_status" "open", "buffered" or "closed"
588596
"out_mode" "NL", "RAW", "JSON" or "JS"
@@ -641,11 +649,8 @@ ch_open({address} [, {options}]) *ch_open()*
641649
Open a channel to {address}. See |channel|.
642650
Returns a Channel. Use |ch_status()| to check for failure.
643651

644-
{address} is a String and has the form "hostname:port", e.g.,
645-
"localhost:8765".
646-
647-
When using an IPv6 address, enclose it within square brackets.
648-
E.g., "[2001:db8::1]:8765".
652+
{address} is a String, see |channel-address| for the possible
653+
accepted forms.
649654

650655
If {options} is given it must be a |Dictionary|.
651656
See |channel-open-options|.

src/channel.c

Lines changed: 113 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,18 @@
4444
# define sock_write(sd, buf, len) send((SOCKET)sd, buf, len, 0)
4545
# define sock_read(sd, buf, len) recv((SOCKET)sd, buf, len, 0)
4646
# define sock_close(sd) closesocket((SOCKET)sd)
47+
// Support for Unix-domain sockets was added in Windows SDK 17061.
48+
# define UNIX_PATH_MAX 108
49+
typedef struct sockaddr_un {
50+
ADDRESS_FAMILY sun_family;
51+
char sun_path[UNIX_PATH_MAX];
52+
} SOCKADDR_UN, *PSOCKADDR_UN;
4753
#else
4854
# include <netdb.h>
4955
# include <netinet/in.h>
5056
# include <arpa/inet.h>
5157
# include <sys/socket.h>
58+
# include <sys/un.h>
5259
# ifdef HAVE_LIBGEN_H
5360
# include <libgen.h>
5461
# endif
@@ -928,6 +935,67 @@ channel_connect(
928935
return sd;
929936
}
930937

938+
/*
939+
* Open a socket channel to the UNIX socket at "path".
940+
* Returns the channel for success.
941+
* Returns NULL for failure.
942+
*/
943+
static channel_T *
944+
channel_open_unix(
945+
const char *path,
946+
void (*nb_close_cb)(void))
947+
{
948+
channel_T *channel = NULL;
949+
int sd = -1;
950+
size_t path_len = STRLEN(path);
951+
struct sockaddr_un server;
952+
size_t server_len;
953+
int waittime = -1;
954+
955+
if (*path == NUL || path_len >= sizeof(server.sun_path))
956+
{
957+
semsg(_(e_invalid_argument_str), path);
958+
return NULL;
959+
}
960+
961+
channel = add_channel();
962+
if (channel == NULL)
963+
{
964+
ch_error(NULL, "Cannot allocate channel.");
965+
return NULL;
966+
}
967+
968+
CLEAR_FIELD(server);
969+
server.sun_family = AF_UNIX;
970+
STRNCPY(server.sun_path, path, sizeof(server.sun_path) - 1);
971+
972+
ch_log(channel, "Trying to connect to %s", path);
973+
974+
server_len = offsetof(struct sockaddr_un, sun_path) + path_len + 1;
975+
sd = channel_connect(channel, (struct sockaddr *)&server, (int)server_len,
976+
&waittime);
977+
978+
if (sd < 0)
979+
{
980+
channel_free(channel);
981+
return NULL;
982+
}
983+
984+
ch_log(channel, "Connection made");
985+
986+
channel->CH_SOCK_FD = (sock_T)sd;
987+
channel->ch_nb_close_cb = nb_close_cb;
988+
channel->ch_hostname = (char *)vim_strsave((char_u *)path);
989+
channel->ch_port = 0;
990+
channel->ch_to_be_closed |= (1U << PART_SOCK);
991+
992+
#ifdef FEAT_GUI
993+
channel_gui_register_one(channel, PART_SOCK);
994+
#endif
995+
996+
return channel;
997+
}
998+
931999
/*
9321000
* Open a socket channel to "hostname":"port".
9331001
* "waittime" is the time in msec to wait for the connection.
@@ -1301,8 +1369,9 @@ channel_open_func(typval_T *argvars)
13011369
char_u *address;
13021370
char_u *p;
13031371
char *rest;
1304-
int port;
1372+
int port = 0;
13051373
int is_ipv6 = FALSE;
1374+
int is_unix = FALSE;
13061375
jobopt_T opt;
13071376
channel_T *channel = NULL;
13081377

@@ -1319,8 +1388,18 @@ channel_open_func(typval_T *argvars)
13191388
return NULL;
13201389
}
13211390

1322-
// parse address
1323-
if (*address == '[')
1391+
if (*address == NUL)
1392+
{
1393+
semsg(_(e_invalid_argument_str), address);
1394+
return NULL;
1395+
}
1396+
1397+
if (!STRNCMP(address, "unix:", 5))
1398+
{
1399+
is_unix = TRUE;
1400+
address += 5;
1401+
}
1402+
else if (*address == '[')
13241403
{
13251404
// ipv6 address
13261405
is_ipv6 = TRUE;
@@ -1333,42 +1412,51 @@ channel_open_func(typval_T *argvars)
13331412
}
13341413
else
13351414
{
1415+
// ipv4 address
13361416
p = vim_strchr(address, ':');
13371417
if (p == NULL)
13381418
{
13391419
semsg(_(e_invalid_argument_str), address);
13401420
return NULL;
13411421
}
13421422
}
1343-
port = strtol((char *)(p + 1), &rest, 10);
1344-
if (*address == NUL || port <= 0 || port >= 65536 || *rest != NUL)
1345-
{
1346-
semsg(_(e_invalid_argument_str), address);
1347-
return NULL;
1348-
}
1349-
if (is_ipv6)
1423+
1424+
if (!is_unix)
13501425
{
1351-
// strip '[' and ']'
1352-
++address;
1353-
*(p - 1) = NUL;
1426+
port = strtol((char *)(p + 1), &rest, 10);
1427+
if (port <= 0 || port >= 65536 || *rest != NUL)
1428+
{
1429+
semsg(_(e_invalid_argument_str), address);
1430+
return NULL;
1431+
}
1432+
if (is_ipv6)
1433+
{
1434+
// strip '[' and ']'
1435+
++address;
1436+
*(p - 1) = NUL;
1437+
}
1438+
else
1439+
*p = NUL;
13541440
}
1355-
else
1356-
*p = NUL;
13571441

13581442
// parse options
13591443
clear_job_options(&opt);
13601444
opt.jo_mode = MODE_JSON;
13611445
opt.jo_timeout = 2000;
13621446
if (get_job_options(&argvars[1], &opt,
1363-
JO_MODE_ALL + JO_CB_ALL + JO_WAITTIME + JO_TIMEOUT_ALL, 0) == FAIL)
1447+
JO_MODE_ALL + JO_CB_ALL + JO_TIMEOUT_ALL
1448+
+ (is_unix? 0 : JO_WAITTIME), 0) == FAIL)
13641449
goto theend;
13651450
if (opt.jo_timeout < 0)
13661451
{
13671452
emsg(_(e_invalid_argument));
13681453
goto theend;
13691454
}
13701455

1371-
channel = channel_open((char *)address, port, opt.jo_waittime, NULL);
1456+
if (is_unix)
1457+
channel = channel_open_unix((char *)address, NULL);
1458+
else
1459+
channel = channel_open((char *)address, port, opt.jo_waittime, NULL);
13721460
if (channel != NULL)
13731461
{
13741462
opt.jo_set = JO_ALL;
@@ -3268,8 +3356,14 @@ channel_info(channel_T *channel, dict_T *dict)
32683356

32693357
if (channel->ch_hostname != NULL)
32703358
{
3271-
dict_add_string(dict, "hostname", (char_u *)channel->ch_hostname);
3272-
dict_add_number(dict, "port", channel->ch_port);
3359+
if (channel->ch_port)
3360+
{
3361+
dict_add_string(dict, "hostname", (char_u *)channel->ch_hostname);
3362+
dict_add_number(dict, "port", channel->ch_port);
3363+
}
3364+
else
3365+
// Unix-domain socket.
3366+
dict_add_string(dict, "path", (char_u *)channel->ch_hostname);
32733367
channel_part_info(channel, dict, "sock", PART_SOCK);
32743368
}
32753369
else

src/testdir/check.vim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ func CheckUnix()
9595
endif
9696
endfunc
9797

98-
" Command to check for running on Linix
98+
" Command to check for running on Linux
9999
command CheckLinux call CheckLinux()
100100
func CheckLinux()
101101
if !has('linux')

src/testdir/shared.vim

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,16 @@ func PythonProg()
1515
if has('unix')
1616
" We also need the job feature or the pkill command to make sure the server
1717
" can be stopped.
18-
if !(executable('python') && (has('job') || executable('pkill')))
18+
if !(has('job') || executable('pkill'))
1919
return ''
2020
endif
21-
let s:python = 'python'
21+
if executable('python')
22+
let s:python = 'python'
23+
elseif executable('python3')
24+
let s:python = 'python3'
25+
else
26+
return ''
27+
end
2228
elseif has('win32')
2329
" Use Python Launcher for Windows (py.exe) if available.
2430
" NOTE: if you get a "Python was not found" error, disable the Python

src/testdir/test_channel.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
2323

2424
def setup(self):
25-
self.request.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
25+
if self.server.address_family != socket.AF_UNIX:
26+
self.request.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
2627

2728
def handle(self):
2829
print("=== socket opened ===")

0 commit comments

Comments
 (0)