diff options
author | Ken Brown <kbrown@cornell.edu> | 2021-05-11 17:34:51 +0300 |
---|---|---|
committer | Ken Brown <kbrown@cornell.edu> | 2021-06-04 18:19:28 +0300 |
commit | 315920326a0b8eb66a16ac576022b777097865a1 (patch) | |
tree | 62e13e359b921650d9bf24ce438f299d08910805 | |
parent | dfe5988f961ff97d283a9c460e75499db168163a (diff) |
Cygwin: AF_UNIX: add socket tests
69 files changed, 4808 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore index 9bff2ff60..28cd86649 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ .#* *# +*.exe *.flt *.gmo *.info @@ -47,3 +48,6 @@ core !core/ lost+found + +ename.c.inc +libafunix.a diff --git a/winsup/cygwin/socket_tests/Makefile b/winsup/cygwin/socket_tests/Makefile new file mode 100644 index 000000000..18fe2a96b --- /dev/null +++ b/winsup/cygwin/socket_tests/Makefile @@ -0,0 +1,62 @@ +include Makefile.inc + +CFLAGS += -Ilib + +AF_UNIX_LIB = libafunix.a + +AF_UNIX_HDR = lib/af_unix_hdr.h + +EXE = ud_ucase_sv ud_ucase_cl \ + us_xfr_cl us_xfr_sv \ + us_xfr_v2_cl us_xfr_v2_sv \ + scm_cred_recv scm_cred_send \ + scm_rights_recv scm_rights_send \ + scm_multi_recv scm_multi_send \ + us_abstract_bind \ + waitall_sv waitall_cl \ + readv_socket writev_socket \ + msg_peek_sv msg_peek_cl \ + fork_socketpair \ + select_sv select_cl \ + is_seqnum_v2_sv is_seqnum_v3_sv is_seqnum_v2_cl \ + recv_pty_slave send_pty_slave \ + recv_pty_master send_pty_master + +all: ${EXE} + +${EXE}: ${AF_UNIX_LIB} # True as a rough approximation + +${AF_UNIX_LIB}: + cd lib; ${MAKE} + +*.o: ${AF_UNIX_HDR} + +scm_cred_recv.o scm_cred_send.o: scm_cred.h + +scm_rights_recv.o scm_rights_send.o: scm_rights.h + +scm_multi_recv.o scm_multi_send.o: scm_multi.h + +us_xfr_sv.o us_xfr_cl.o: us_xfr.h + +us_xfr_v2_sv.o us_xfr_v2_cl.o: us_xfr_v2.h + +ud_ucase_sv.o ud_ucase_cl.o: ud_ucase.h + +waitall_sv.o waitall_cl.o: waitall.h + +readv_socket.o writev_socket.o: scatter_gather.h + +msg_peek_sv.o msg_peek_cl.o: msg_peek.h + +select_sv.o select_cl.o: select_test.h + +is_seqnum_v2_sv.o is_seqnum_v3_sv.o is_seqnum_v2_cl.o: is_seqnum_v2.h + +recv_pty_slave.o send_pty_slave.o: pty_slave.h + +recv_pty_master.o send_pty_master.o: pty_master.h + +clean: + cd lib; ${MAKE} clean + ${RM} *.exe *.o ${AF_UNIX_LIB} diff --git a/winsup/cygwin/socket_tests/Makefile.inc b/winsup/cygwin/socket_tests/Makefile.inc new file mode 100644 index 000000000..cd5db5328 --- /dev/null +++ b/winsup/cygwin/socket_tests/Makefile.inc @@ -0,0 +1,11 @@ +CC = gcc + +CFLAGS = -D_XOPEN_SOURCE=600 \ + -D_DEFAULT_SOURCE \ + -g -O0 -I. \ + -pedantic \ + -Wall \ + -Wextra \ + -Wmissing-prototypes \ + -Wno-sign-compare \ + -Wno-unused-parameter diff --git a/winsup/cygwin/socket_tests/README.txt b/winsup/cygwin/socket_tests/README.txt new file mode 100644 index 000000000..274473173 --- /dev/null +++ b/winsup/cygwin/socket_tests/README.txt @@ -0,0 +1,279 @@ +0. make + +1. Server-client using read/write. + + $ cat *.c > a + + $ ./us_xfr_sv.exe > b& + + $ ./us_xfr_cl.exe < a + + $ kill %1 + + $ + [1]+ Terminated ./us_xfr_sv.exe > b + + $ diff a b + + $ rm a b + + Should be able to do same test with v2 versions. + +2. Datagram server-client using sendto/recvfrom. + + $ ./ud_ucase_sv.exe & + + $ ./ud_ucase_cl.exe long message + Server received 4 bytes from /tmp/ud_ucase_cl.925 + Response 1: LONG + Server received 7 bytes from /tmp/ud_ucase_cl.925 + Response 2: MESSAGE + + $ ./ud_ucase_cl.exe 'long message' + Server received 10 bytes from /tmp/ud_ucase_cl.926 + Response 1: LONG MESSA + + $ kill %1 + +3. MSG_WAITALL test. In two terminals: + + # Terminal 1: + $ ./waitall_sv.exe + + # Terminal 2: + $ ./waitall_cl.exe + abcd + abcd + + [Should see this echoed in Terminal 1 after both lines have been + typed. Kill both programs with Ctrl-C.] + +4. scatter-gather test. In two terminals: + + # Terminal 1: + $ ./readv_socket.exe + + # Terminal 2: + $ ./writev_socket.exe + wrote 148 bytes + + # Terminal 1 should now show: + $ ./readv_socket.exe + read 148 bytes + 0: The term buccaneer comes from the word boucan. + 1: A boucan is a wooden frame used for cooking meat. + 2: Buccaneer is the West Indies name for a pirate. + +5. MSG_PEEK test. In two terminals: + + # Terminal 1: + $ ./msg_peek_sv.exe + peeking... + + # Terminal 2: + $ ./msg_peek_cl.exe + hello + + # Terminal 1 should now show: + $ ./msg_peek_sv.exe + peeking... + reading would yield 6 bytes: hello + + [After 1 second delay] + + reading... + read 6 bytes: hello + + [Need to kill msg_peek_cl.] + +6. fork/socketpair test. + + $ ./fork_socketpair.exe + count = 0 + count = 1 + count = 2 + count = 3 + count = 4 + count = 5 + count = 6 + count = 7 + count = 8 + count = 9 + +7. select test. In two terminals: + + # Terminal 1: + $ ./select_sv + waiting for connection request... + + # Terminal 2: + $ ./select_cl + waiting for socket to be ready for write... + ready for write, writing until buffer full + buffer full + wrote 262108 bytes + waiting for write ready again... + ready for write, writing once more + wrote 65527 more bytes for a total of 327635 + + # Terminal 1 should now show: + $ ./select_sv + waiting for connection request... + connection request received; accepting + slowly reading from socket... + read 327635 bytes + +8. Ancillary data test (SCM_CREDENTIALS). In two terminals: + + # Terminal 1: + $ ./scm_cred_recv.exe + + # Terminal 2: + $ ./scm_cred_send.exe + Sending data = 12345 + Send credentials pid=234, uid=197609, gid=197121 + sendmsg() returned 4 + + # Terminal 1 should now show: + $ ./scm_cred_recv.exe + recvmsg() returned 4 + Received data = 12345 + Received credentials pid=234, uid=197609, gid=197121 + Credentials from SO_PEERCRED: pid=234, euid=197609, egid=197121 + + If use -d option in both programs to use datagrams, the last line + instead reads: + + ERROR [EINVAL Invalid argument] getsockopt + + I think this is correct. According to + https://man7.org/linux/man-pages/man7/unix.7.html, SO_PEERCRED is + not supported for datagram sockets unless they are created using + socketpair. + + If we use -n in the send program, credentials will be sent even + though the caller didn't specify control message data. + + scm_cred_send can also specify credentials: + + $ ./scm_cred_send.exe data 1 3 5 + + This should fail with EPERM if the specified credentials are not + the actual credentials of the sender, unless the sender is an + administrator. In that case the specified pid must be the pid of + an existing process, but the uid and gid can be arbitrary. + +9. Ancillary data test (SCM_RIGHTS, disk file descriptor). + In two terminals: + + # Terminal 1: + $ ./scm_rights_recv.exe + + # Terminal 2: + $ ./scm_rights_send.exe <some disk file> + Sending data = 12345 + Sending FD 3 + sendmsg() returned 4 + + # Terminal 1 should now show: + recvmsg() returned 4 + Received data = 12345 + Received FD 5 + <contents of some disk file> + +10. Ancillary data test (SCM_RIGHTS, socket descriptor). + + $ ./is_seqnum_v3_sv.exe & + [1] 8880 + + $ ./is_seqnum_v2_cl.exe localhost + Connection from (<host>, <port>) + Sending fd 4 to child + Sequence number: 0 + +11. Ancillary data test (SCM_RIGHTS, pty slave descriptor). + send_pty_slave creates pty pair and a shell subprocess connected + to the slave. It sends the slave descriptor over an AF_UNIX + socket to recv_pty_slave. It then monitors its stdin and the pty + master for input. Anything it reads from stdin is written to the + pty master (and so read by the shell). Anything it reads from the + pty master is written to stdout. This is normally just the shell + output. But recv_pty_slave writes "hello" to the slave and so is + read by send_pty_slave and written to stdout as though it were + written by the shell. + + In two terminals: + + # Terminal 1: + $ ./recv_pty_slave.exe + Waiting for sender to connect and send descriptor... + + # Terminal 2: + $ ./send_pty_slave.exe + hello + + #Terminal 1 now shows: + $ ./recv_pty_slave.exe + Waiting for sender to connect and send descriptor... + Received descriptor 5. + Writing "hello" to that descriptor. + This should appear in the other terminal as though it were output by the shell. + + Can now exit the shell in terminal 2. + + To test all this when the pty is connected to a pseudo terminal, + set SHELL=cmd before running send_pty_slave. Terminal 2 then + looks like this: + + $ SHELL=cmd ./send_pty_slave.exe + hello + Microsoft Windows [Version 10.0.18363.1256] + (c) 2019 Microsoft Corporation. All rights reserved. + + C:\Users\kbrown\src\cygdll\af_unix\winsup\cygwin\socket_tests>exit + +12. Ancillary data test (SCM_RIGHTS, pty master descriptor). + send_pty_master creates pty pair and a shell subprocess connected + to the slave. It then does the same as in 11, except that it + sends the master descriptor instead of the slave descriptor. + recv_pty_master writes "ps\n" to the received master fd. The + shell created by send_pty_master reads and executes this. + + In two terminals: + + # Terminal 1: + $ ./recv_pty_master.exe + Waiting for sender to connect and send descriptor... + + # Terminal 2: + $ ./send_pty_master.exe + ps + +$ ps + PID PPID PGID WINPID TTY UID STIME COMMAND + 934 933 934 138392 pty2 197609 13:47:22 /usr/bin/bash + 937 934 937 109052 pty2 197609 13:47:22 /usr/bin/ps + 887 886 887 51496 pty1 197609 13:11:25 /usr/bin/bash + 875 874 875 30396 pty0 197609 13:11:21 /usr/bin/bash + 874 1 874 23516 ? 197609 13:11:21 /usr/bin/mintty + 886 1 886 118428 ? 197609 13:11:25 /usr/bin/mintty + 933 887 933 59856 pty1 197609 13:47:22 /home/kbrown/src/cygdll/af_unix/winsup/cygwin/socket_tests/send_pty_master + 932 875 932 115304 pty0 197609 13:46:30 /home/kbrown/src/cygdll/af_unix/winsup/cygwin/socket_tests/recv_pty_master + + [Why is ps echoed twice?] + + + #Terminal 1 now shows: + $ ./recv_pty_master.exe + Waiting for sender to connect and send descriptor... + Received descriptor 5. + Writing "ps" to that descriptor. + This should appear in the other terminal + and be executed by the shell running there. + Waiting for sender to finish... + + Can now exit the shell in terminal 2 and both programs exit. + + This doesn't work if we use SHELL=cmd in terminal 2. "ps" gets + echoed but not executed. I'm not sure if we should expect it to + work. diff --git a/winsup/cygwin/socket_tests/fork_socketpair.c b/winsup/cygwin/socket_tests/fork_socketpair.c new file mode 100644 index 000000000..5d9fd14c0 --- /dev/null +++ b/winsup/cygwin/socket_tests/fork_socketpair.c @@ -0,0 +1,72 @@ +/* Adapted from the code for debug/backlog.c in Stevens, Unix Network + Programming. */ + +#include "af_unix_hdr.h" + +int pipefd[2]; +#define pfd pipefd[1] /* parent's end */ +#define cfd pipefd[0] /* child's end */ + +/* function prototypes */ +void do_parent(void); +void do_child(void); + +int +main (int argc, char **argv) +{ + pid_t pid; + + if (socketpair (AF_UNIX, SOCK_STREAM, 0, pipefd) < 0) + errExit ("socketpair"); + if ((pid = fork ()) < 0) + errExit ("fork"); + else if (pid == 0) + do_child(); + else + do_parent(); +} + +void +do_parent (void) +{ + int count, junk; + + if (close (cfd) < 0) + errExit ("close"); + + for (count = 0; count < 10; count++) + { + printf ("count = %d\n", count); + /* tell child value */ + if (write (pfd, &count, sizeof (int)) != sizeof (int)) + errExit ("write"); + /* wait for child */ + if (read (pfd, &junk, sizeof (int)) < 0) + errExit ("read"); + sleep (1); + } + count = -1; /* tell child we're all done */ + if (write (pfd, &count, sizeof (int)) != sizeof (int)) + errExit ("write"); +} + +void +do_child(void) +{ + int count, junk; + + if (close (pfd) < 0) + errExit ("close"); + /* wait for parent */ + if (read (cfd, &count, sizeof (int)) < 0) + errExit ("read"); + while (count >= 0) + { + /* tell parent */ + if (write (cfd, &junk, sizeof (int)) != sizeof (int)) + errExit ("write"); + /* wait for parent */ + if (read (cfd, &count, sizeof (int)) < 0) + errExit ("read"); + } +} diff --git a/winsup/cygwin/socket_tests/is_seqnum_v2.h b/winsup/cygwin/socket_tests/is_seqnum_v2.h new file mode 100644 index 000000000..495553aee --- /dev/null +++ b/winsup/cygwin/socket_tests/is_seqnum_v2.h @@ -0,0 +1,27 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either version 3 or (at your option) any * +* later version. This program is distributed without any warranty. See * +* the file COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Solution for Exercise 59-2:a */ + +/* is_seqnum_v2.h + + Header file for is_seqnum_v2_sv.c and is_seqnum_v2_cl.c. +*/ +#include "af_unix_hdr.h" +#include <netinet/in.h> +#include <sys/socket.h> +#include <signal.h> +#include "inet_sockets.h" /* Declares our socket functions */ +#include "read_line.h" /* Declaration of readLine() */ + +#define PORT_NUM_STR "50000" /* Port number for server */ + +#define INT_LEN 30 /* Size of string able to hold largest + integer (including terminating '\n') */ diff --git a/winsup/cygwin/socket_tests/is_seqnum_v2_cl.c b/winsup/cygwin/socket_tests/is_seqnum_v2_cl.c new file mode 100644 index 000000000..a6df5d0a3 --- /dev/null +++ b/winsup/cygwin/socket_tests/is_seqnum_v2_cl.c @@ -0,0 +1,56 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either version 3 or (at your option) any * +* later version. This program is distributed without any warranty. See * +* the file COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Solution for Exercise 59-2:c */ + +/* is_seqnum_v2_cl.c + + A simple Internet stream socket client. This server obtains a sequence + number from the server. + + The program is the same as is_seqnum_cl.c, except that it uses the + functions in our inet_sockets.c library to simplify the creation of a + socket that connects to the server's socket. + + See also is_seqnum_v2_sv.c. +*/ +#include "is_seqnum_v2.h" + +int +main(int argc, char *argv[]) +{ + char *reqLenStr; /* Requested length of sequence */ + char seqNumStr[INT_LEN]; /* Start of granted sequence */ + int cfd; + ssize_t numRead; + + if (argc < 2 || strcmp(argv[1], "--help") == 0) + usageErr("%s server-host [sequence-len]\n", argv[0]); + + cfd = inetConnect(argv[1], PORT_NUM_STR, SOCK_STREAM); + if (cfd == -1) + fatal("inetConnect() failed"); + + reqLenStr = (argc > 2) ? argv[2] : "1"; + if (write(cfd, reqLenStr, strlen(reqLenStr)) != strlen(reqLenStr)) + fatal("Partial/failed write (reqLenStr)"); + if (write(cfd, "\n", 1) != 1) + fatal("Partial/failed write (newline)"); + + numRead = readLine(cfd, seqNumStr, INT_LEN); + if (numRead == -1) + errExit("readLine"); + if (numRead == 0) + fatal("Unexpected EOF from server"); + + printf("Sequence number: %s", seqNumStr); /* Includes '\n' */ + + exit(EXIT_SUCCESS); /* Closes 'cfd' */ +} diff --git a/winsup/cygwin/socket_tests/is_seqnum_v2_sv.c b/winsup/cygwin/socket_tests/is_seqnum_v2_sv.c new file mode 100644 index 000000000..3d1c37b5c --- /dev/null +++ b/winsup/cygwin/socket_tests/is_seqnum_v2_sv.c @@ -0,0 +1,94 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either version 3 or (at your option) any * +* later version. This program is distributed without any warranty. See * +* the file COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Solution for Exercise 59-2:b */ + +/* is_seqnum_v2_sv.c + + A simple Internet stream socket server. Our service is to provide unique + sequence numbers to the client. + + This program is the same as is_seqnum_cl.c, except that it uses the functions + in our inet_sockets.c library to simplify set up of the server's socket. + + Usage: is_seqnum_sv [init-seq-num] (default = 0) + + See also is_seqnum_v2_cl.c. +*/ +#include "is_seqnum_v2.h" + +int +main(int argc, char *argv[]) +{ + uint32_t seqNum; + char reqLenStr[INT_LEN]; /* Length of requested sequence */ + char seqNumStr[INT_LEN]; /* Start of granted sequence */ + struct sockaddr *claddr; + int lfd, cfd, reqLen; + socklen_t addrlen, alen; + char addrStr[IS_ADDR_STR_LEN]; + + if (argc > 1 && strcmp(argv[1], "--help") == 0) + usageErr("%s [init-seq-num]\n", argv[0]); + + seqNum = (argc > 1) ? getInt(argv[1], 0, "init-seq-num") : 0; + + /* Ignore the SIGPIPE signal, so that we find out about broken connection + errors via a failure from write(). */ + + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) errExit("signal"); + + lfd = inetListen(PORT_NUM_STR, 5, &addrlen); + if (lfd == -1) + fatal("inetListen() failed"); + + /* Allocate a buffer large enough to hold the client's socket address */ + + claddr = malloc(addrlen); + if (claddr == NULL) + errExit("malloc"); + + for (;;) { /* Handle clients iteratively */ + + /* Accept a client connection, obtaining client's address */ + + alen = addrlen; + cfd = accept(lfd, (struct sockaddr *) claddr, &alen); + if (cfd == -1) { + errMsg("accept"); + continue; + } + + printf("Connection from %s\n", inetAddressStr(claddr, alen, + addrStr, IS_ADDR_STR_LEN)); + + /* Read client request, send sequence number back */ + + if (readLine(cfd, reqLenStr, INT_LEN) <= 0) { + close(cfd); + continue; /* Failed read; skip request */ + } + + reqLen = atoi(reqLenStr); + if (reqLen <= 0) { /* Watch for misbehaving clients */ + close(cfd); + continue; /* Bad request; skip it */ + } + + snprintf(seqNumStr, INT_LEN, "%d\n", seqNum); + if (write(cfd, seqNumStr, strlen(seqNumStr)) != strlen(seqNumStr)) + fprintf(stderr, "Error on write"); + + seqNum += reqLen; /* Update sequence number */ + + if (close(cfd) == -1) /* Close connection */ + errMsg("close"); + } +} diff --git a/winsup/cygwin/socket_tests/is_seqnum_v3_sv.c b/winsup/cygwin/socket_tests/is_seqnum_v3_sv.c new file mode 100644 index 000000000..f52a90d44 --- /dev/null +++ b/winsup/cygwin/socket_tests/is_seqnum_v3_sv.c @@ -0,0 +1,158 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either version 3 or (at your option) any * +* later version. This program is distributed without any warranty. See * +* the file COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* is_seqnum_v3_sv.c (KB) + + A simple Internet stream socket server. Our service is to provide unique + sequence numbers to the client. + + This program is the same as is_seqnum_v2.c, except that it forks a + subprocess to do the work, and it sends the connection fd to the + child over an AF_UNIX socket. Invoke it with --debug to allow time + to attach gdb to the child. (KB) + + Usage: is_seqnum_sv [init-seq-num] (default = 0) + + See also is_seqnum_v2_cl.c. +*/ +#include "af_unix_hdr.h" +#include "is_seqnum_v2.h" + +int +main(int argc, char *argv[]) +{ + uint32_t seqNum; + char reqLenStr[INT_LEN]; /* Length of requested sequence */ + char seqNumStr[INT_LEN]; /* Start of granted sequence */ + struct sockaddr *claddr; + int lfd, reqLen; + socklen_t addrlen, alen; + char addrStr[IS_ADDR_STR_LEN]; + Boolean debug = FALSE; + pid_t pid; + int pipefd[2]; + int pfd; /* parent's end */ + int cfd; /* child's end */ + + if (argc > 1 && strcmp (argv[1], "--help") == 0) + usageErr ("%s [--debug] [init-seq-num]\n", argv[0]); + if (argc > 1 && strcmp (argv[1], "--debug") == 0) + debug = TRUE; + + if (!debug) + seqNum = (argc > 1) ? getInt (argv[1], 0, "init-seq-num") : 0; + else + seqNum = (argc > 2) ? getInt (argv[2], 0, "init-seq-num") : 0; + + /* Ignore the SIGPIPE signal, so that we find out about broken connection + errors via a failure from write(). */ + + if (signal (SIGPIPE, SIG_IGN) == SIG_ERR) + errExit("signal"); + + lfd = inetListen (PORT_NUM_STR, 5, &addrlen); + if (lfd == -1) + fatal ("inetListen() failed"); + + /* Allocate a buffer large enough to hold the client's socket address */ + + claddr = malloc (addrlen); + if (claddr == NULL) + errExit ("malloc"); + + /* Fork a child to handle client request. */ + + if (socketpair (AF_UNIX, SOCK_STREAM, 0, pipefd) < 0) + errExit ("socketpair"); + pfd = pipefd[1]; + cfd = pipefd[0]; + + if ((pid = fork ()) < 0) + errExit ("fork"); + else if (pid > 0) /* parent */ + { + int connfd, junk; + + if (close (cfd) < 0) + errExit ("close"); + if (debug) + { + printf ("parent pid %d, child pid %d, sleeping...\n", getpid (), pid); + sleep (30); + } + + /* Accept a client connection, obtaining client's address */ + + alen = addrlen; + connfd = accept (lfd, (struct sockaddr *) claddr, &alen); + if (connfd == -1) + errExit ("accept"); + printf ("Connection from %s\n", inetAddressStr (claddr, alen, addrStr, + IS_ADDR_STR_LEN)); + printf ("Sending fd %d to child\n", connfd); + if (sendfd (pfd, connfd) < 0) + errExit ("sendfd"); + if (close (connfd) < 0) + errExit ("close"); + /* Wait for child. */ + if (read (pfd, &junk, sizeof junk) != sizeof junk) + errMsg ("read"); + if (close (pfd) < 0) + errMsg ("close"); + if (close (lfd) < 0) + errExit ("close"); + } + else /* child */ + { + int connfd, junk; + + if (close (pfd) < 0) + errExit ("close"); + if (close (lfd) < 0) + errExit ("close"); + if (debug) + sleep (30); + + /* Get connection fd from parent. */ + + connfd = recvfd (cfd); + if (connfd < 0) + errExit ("recvfd"); + + /* Read client request, send sequence number back */ + + if (readLine (connfd, reqLenStr, INT_LEN) <= 0) + { + close (connfd); + errExit ("readLine"); + } + + reqLen = atoi (reqLenStr); + if (reqLen <= 0) + { + close(cfd); + errExit ("Bad request"); + } + + snprintf (seqNumStr, INT_LEN, "%d\n", seqNum); + if (write (connfd, seqNumStr, strlen (seqNumStr)) != strlen(seqNumStr)) + errExit ("write"); + + seqNum += reqLen; /* Update sequence number */ + + if (close (connfd) == -1) /* Close connection */ + errMsg ("close"); + /* Tell parent we're done. */ + if (write (cfd, &junk, sizeof junk) != sizeof junk) + errMsg ("write"); + if (close (cfd) < 0) + errExit ("close"); + } +} diff --git a/winsup/cygwin/socket_tests/lib/Build_ename.sh b/winsup/cygwin/socket_tests/lib/Build_ename.sh new file mode 100644 index 000000000..d2eb014c8 --- /dev/null +++ b/winsup/cygwin/socket_tests/lib/Build_ename.sh @@ -0,0 +1,53 @@ +#!/bin/sh +# +# Create a new version of the file ename.c.inc by parsing symbolic +# error names defined in errno.h +# +echo '#include <errno.h>' | cpp -dM | +sed -n -e '/#define *E/s/#define *//p' |sort -k2n | +awk ' +BEGIN { + entries_per_line = 4 + line_len = 68; + last = 0; + varname =" enames"; + print "static char *ename[] = {"; + line = " /* 0 */ \"\""; +} + +{ + if ($2 ~ /^E[A-Z0-9]*$/) { # These entries are sorted at top + synonym[$1] = $2; + } else { + while (last + 1 < $2) { + last++; + line = line ", "; + if (length(line ename) > line_len || last == 1) { + print line; + line = " /* " last " */ "; + line = sprintf(" /* %3d */ ", last); + } + line = line "\"" "\"" ; + } + last = $2; + ename = $1; + for (k in synonym) + if (synonym[k] == $1) ename = ename "/" k; + + line = line ", "; + if (length(line ename) > line_len || last == 1) { + print line; + line = " /* " last " */ "; + line = sprintf(" /* %3d */ ", last);; + } + line = line "\"" ename "\"" ; + } +} +END { + print line; + print "};" + print ""; + print "#define MAX_ENAME " last; +} +' + diff --git a/winsup/cygwin/socket_tests/lib/Makefile b/winsup/cygwin/socket_tests/lib/Makefile new file mode 100644 index 000000000..a94e4b53b --- /dev/null +++ b/winsup/cygwin/socket_tests/lib/Makefile @@ -0,0 +1,31 @@ +include ../Makefile.inc + +AF_UNIX_LIB = ../libafunix.a + +all: ${AF_UNIX_LIB} + +${AF_UNIX_LIB}: *.c *.h ename.c.inc + ${CC} -c ${CFLAGS} *.c + ${RM} ${AF_UNIX_LIB} + ${AR} rs ${AF_UNIX_LIB} *.o + +ename.c.inc: + sh Build_ename.sh > ename.c.inc + echo 1>&2 "ename.c.inc built" + +*.o: af_unix_hdr.h + +error_functions.o: error_functions.h + +scm_functions.o: scm_functions.h + +unix_sockets.o: unix_sockets.h + +inet_sockets.o: inet_sockets.h + +read_line.o: read_line.h + +get_num.o: get_num.h + +clean: + ${RM} *.o ename.c.inc ${AF_UNIX_LIB} diff --git a/winsup/cygwin/socket_tests/lib/af_unix_hdr.h b/winsup/cygwin/socket_tests/lib/af_unix_hdr.h new file mode 100644 index 000000000..bfbee9c53 --- /dev/null +++ b/winsup/cygwin/socket_tests/lib/af_unix_hdr.h @@ -0,0 +1,55 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU Lesser General Public License as published * +* by the Free Software Foundation, either version 3 or (at your option) * +* any later version. This program is distributed without any warranty. * +* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Listing 3-1 */ + +/* af_unix_hdr.h + + Standard header file used by nearly all of our example programs. +*/ +#ifndef AF_UNIX_HDR_H +#define AF_UNIX_HDR_H /* Prevent accidental double inclusion */ + +#include <sys/types.h> /* Type definitions used by many programs */ +#include <stdio.h> /* Standard I/O functions */ +#include <stdlib.h> /* Prototypes of commonly used library functions, + plus EXIT_SUCCESS and EXIT_FAILURE constants */ +#include <unistd.h> /* Prototypes for many system calls */ +#include <errno.h> /* Declares errno and defines error constants */ +#include <string.h> /* Commonly used string-handling functions */ + +#include "get_num.h" /* Declares our functions for handling numeric + arguments (getInt(), getLong()) */ + +#include "error_functions.h" /* Declares our error-handling functions */ + +#include <sys/socket.h> +#include <sys/un.h> +#include "unix_sockets.h" /* Declares our socket functions */ +#include "scm_functions.h" +/* #include "unp.h" /\* Stevens, Unix Network Programming *\/ */ + +#undef AF_UNIX +#define AF_UNIX 31 + +#ifdef TRUE +#undef TRUE +#endif + +#ifdef FALSE +#undef FALSE +#endif + +typedef enum { FALSE, TRUE } Boolean; + +#define min(m,n) ((m) < (n) ? (m) : (n)) +#define max(m,n) ((m) > (n) ? (m) : (n)) + +#endif diff --git a/winsup/cygwin/socket_tests/lib/error_functions.c b/winsup/cygwin/socket_tests/lib/error_functions.c new file mode 100644 index 000000000..62e4961da --- /dev/null +++ b/winsup/cygwin/socket_tests/lib/error_functions.c @@ -0,0 +1,201 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU Lesser General Public License as published * +* by the Free Software Foundation, either version 3 or (at your option) * +* any later version. This program is distributed without any warranty. * +* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Listing 3-3 */ + +/* error_functions.c + + Some standard error handling routines used by various programs. +*/ +#include <stdarg.h> +#include "error_functions.h" +#include "af_unix_hdr.h" +#include "ename.c.inc" /* Defines ename and MAX_ENAME */ + +#ifdef __GNUC__ /* Prevent 'gcc -Wall' complaining */ +__attribute__ ((__noreturn__)) /* if we call this function as last */ +#endif /* statement in a non-void function */ +static void +terminate(Boolean useExit3) +{ + char *s; + + /* Dump core if EF_DUMPCORE environment variable is defined and + is a nonempty string; otherwise call exit(3) or _exit(2), + depending on the value of 'useExit3'. */ + + s = getenv("EF_DUMPCORE"); + + if (s != NULL && *s != '\0') + abort(); + else if (useExit3) + exit(EXIT_FAILURE); + else + _exit(EXIT_FAILURE); +} + +/* Diagnose 'errno' error by: + + * outputting a string containing the error name (if available + in 'ename' array) corresponding to the value in 'err', along + with the corresponding error message from strerror(), and + + * outputting the caller-supplied error message specified in + 'format' and 'ap'. */ + +static void +outputError(Boolean useErr, int err, Boolean flushStdout, + const char *format, va_list ap) +{ +#define BUF_SIZE 500 + char buf[BUF_SIZE], userMsg[BUF_SIZE], errText[BUF_SIZE]; + + vsnprintf(userMsg, BUF_SIZE, format, ap); + + if (useErr) + snprintf(errText, BUF_SIZE, " [%s %s]", + (err > 0 && err <= MAX_ENAME) ? + ename[err] : "?UNKNOWN?", strerror(err)); + else + snprintf(errText, BUF_SIZE, ":"); + + snprintf(buf, BUF_SIZE, "ERROR%s %s\n", errText, userMsg); + + if (flushStdout) + fflush(stdout); /* Flush any pending stdout */ + fputs(buf, stderr); + fflush(stderr); /* In case stderr is not line-buffered */ +} + +/* Display error message including 'errno' diagnostic, and + return to caller */ + +void +errMsg(const char *format, ...) +{ + va_list argList; + int savedErrno; + + savedErrno = errno; /* In case we change it here */ + + va_start(argList, format); + outputError(TRUE, errno, TRUE, format, argList); + va_end(argList); + + errno = savedErrno; +} + +/* Display error message including 'errno' diagnostic, and + terminate the process */ + +void +errExit(const char *format, ...) +{ + va_list argList; + + va_start(argList, format); + outputError(TRUE, errno, TRUE, format, argList); + va_end(argList); + + terminate(TRUE); +} + +/* Display error message including 'errno' diagnostic, and + terminate the process by calling _exit(). + + The relationship between this function and errExit() is analogous + to that between _exit(2) and exit(3): unlike errExit(), this + function does not flush stdout and calls _exit(2) to terminate the + process (rather than exit(3), which would cause exit handlers to be + invoked). + + These differences make this function especially useful in a library + function that creates a child process that must then terminate + because of an error: the child must terminate without flushing + stdio buffers that were partially filled by the caller and without + invoking exit handlers that were established by the caller. */ + +void +err_exit(const char *format, ...) +{ + va_list argList; + + va_start(argList, format); + outputError(TRUE, errno, FALSE, format, argList); + va_end(argList); + + terminate(FALSE); +} + +/* The following function does the same as errExit(), but expects + the error number in 'errnum' */ + +void +errExitEN(int errnum, const char *format, ...) +{ + va_list argList; + + va_start(argList, format); + outputError(TRUE, errnum, TRUE, format, argList); + va_end(argList); + + terminate(TRUE); +} + +/* Print an error message (without an 'errno' diagnostic) */ + +void +fatal(const char *format, ...) +{ + va_list argList; + + va_start(argList, format); + outputError(FALSE, 0, TRUE, format, argList); + va_end(argList); + + terminate(TRUE); +} + +/* Print a command usage error message and terminate the process */ + +void +usageErr(const char *format, ...) +{ + va_list argList; + + fflush(stdout); /* Flush any pending stdout */ + + fprintf(stderr, "Usage: "); + va_start(argList, format); + vfprintf(stderr, format, argList); + va_end(argList); + + fflush(stderr); /* In case stderr is not line-buffered */ + exit(EXIT_FAILURE); +} + +/* Diagnose an error in command-line arguments and + terminate the process */ + +void +cmdLineErr(const char *format, ...) +{ + va_list argList; + + fflush(stdout); /* Flush any pending stdout */ + + fprintf(stderr, "Command-line usage error: "); + va_start(argList, format); + vfprintf(stderr, format, argList); + va_end(argList); + + fflush(stderr); /* In case stderr is not line-buffered */ + exit(EXIT_FAILURE); +} diff --git a/winsup/cygwin/socket_tests/lib/error_functions.h b/winsup/cygwin/socket_tests/lib/error_functions.h new file mode 100644 index 000000000..8954fa95e --- /dev/null +++ b/winsup/cygwin/socket_tests/lib/error_functions.h @@ -0,0 +1,47 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU Lesser General Public License as published * +* by the Free Software Foundation, either version 3 or (at your option) * +* any later version. This program is distributed without any warranty. * +* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Listing 3-2 */ + +/* error_functions.h + + Header file for error_functions.c. +*/ +#ifndef ERROR_FUNCTIONS_H +#define ERROR_FUNCTIONS_H + +/* Error diagnostic routines */ + +void errMsg(const char *format, ...); + +#ifdef __GNUC__ + + /* This macro stops 'gcc -Wall' complaining that "control reaches + end of non-void function" if we use the following functions to + terminate main() or some other non-void function. */ + +#define NORETURN __attribute__ ((__noreturn__)) +#else +#define NORETURN +#endif + +void errExit(const char *format, ...) NORETURN ; + +void err_exit(const char *format, ...) NORETURN ; + +void errExitEN(int errnum, const char *format, ...) NORETURN ; + +void fatal(const char *format, ...) NORETURN ; + +void usageErr(const char *format, ...) NORETURN ; + +void cmdLineErr(const char *format, ...) NORETURN ; + +#endif diff --git a/winsup/cygwin/socket_tests/lib/get_num.c b/winsup/cygwin/socket_tests/lib/get_num.c new file mode 100644 index 000000000..e8e1ad225 --- /dev/null +++ b/winsup/cygwin/socket_tests/lib/get_num.c @@ -0,0 +1,103 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU Lesser General Public License as published * +* by the Free Software Foundation, either version 3 or (at your option) * +* any later version. This program is distributed without any warranty. * +* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Listing 3-6 */ + +/* get_num.c + + Functions to process numeric command-line arguments. +*/ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include <errno.h> +#include "get_num.h" + +/* Print a diagnostic message that contains a function name ('fname'), + the value of a command-line argument ('arg'), the name of that + command-line argument ('name'), and a diagnostic error message ('msg'). */ + +static void +gnFail(const char *fname, const char *msg, const char *arg, const char *name) +{ + fprintf(stderr, "%s error", fname); + if (name != NULL) + fprintf(stderr, " (in %s)", name); + fprintf(stderr, ": %s\n", msg); + if (arg != NULL && *arg != '\0') + fprintf(stderr, " offending text: %s\n", arg); + + exit(EXIT_FAILURE); +} + +/* Convert a numeric command-line argument ('arg') into a long integer, + returned as the function result. 'flags' is a bit mask of flags controlling + how the conversion is done and what diagnostic checks are performed on the + numeric result; see get_num.h for details. + + 'fname' is the name of our caller, and 'name' is the name associated with + the command-line argument 'arg'. 'fname' and 'name' are used to print a + diagnostic message in case an error is detected when processing 'arg'. */ + +static long +getNum(const char *fname, const char *arg, int flags, const char *name) +{ + long res; + char *endptr; + int base; + + if (arg == NULL || *arg == '\0') + gnFail(fname, "null or empty string", arg, name); + + base = (flags & GN_ANY_BASE) ? 0 : (flags & GN_BASE_8) ? 8 : + (flags & GN_BASE_16) ? 16 : 10; + + errno = 0; + res = strtol(arg, &endptr, base); + if (errno != 0) + gnFail(fname, "strtol() failed", arg, name); + + if (*endptr != '\0') + gnFail(fname, "nonnumeric characters", arg, name); + + if ((flags & GN_NONNEG) && res < 0) + gnFail(fname, "negative value not allowed", arg, name); + + if ((flags & GN_GT_0) && res <= 0) + gnFail(fname, "value must be > 0", arg, name); + + return res; +} + +/* Convert a numeric command-line argument string to a long integer. See the + comments for getNum() for a description of the arguments to this function. */ + +long +getLong(const char *arg, int flags, const char *name) +{ + return getNum("getLong", arg, flags, name); +} + +/* Convert a numeric command-line argument string to an integer. See the + comments for getNum() for a description of the arguments to this function. */ + +int +getInt(const char *arg, int flags, const char *name) +{ + long res; + + res = getNum("getInt", arg, flags, name); + + if (res > INT_MAX || res < INT_MIN) + gnFail("getInt", "integer out of range", arg, name); + + return (int) res; +} diff --git a/winsup/cygwin/socket_tests/lib/get_num.h b/winsup/cygwin/socket_tests/lib/get_num.h new file mode 100644 index 000000000..7b5a0b1e4 --- /dev/null +++ b/winsup/cygwin/socket_tests/lib/get_num.h @@ -0,0 +1,32 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU Lesser General Public License as published * +* by the Free Software Foundation, either version 3 or (at your option) * +* any later version. This program is distributed without any warranty. * +* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Listing 3-5 */ + +/* get_num.h + + Header file for get_num.c. +*/ +#ifndef GET_NUM_H +#define GET_NUM_H + +#define GN_NONNEG 01 /* Value must be >= 0 */ +#define GN_GT_0 02 /* Value must be > 0 */ + + /* By default, integers are decimal */ +#define GN_ANY_BASE 0100 /* Can use any base - like strtol(3) */ +#define GN_BASE_8 0200 /* Value is expressed in octal */ +#define GN_BASE_16 0400 /* Value is expressed in hexadecimal */ + +long getLong(const char *arg, int flags, const char *name); + +int getInt(const char *arg, int flags, const char *name); + +#endif diff --git a/winsup/cygwin/socket_tests/lib/inet_sockets.c b/winsup/cygwin/socket_tests/lib/inet_sockets.c new file mode 100644 index 000000000..396dbf906 --- /dev/null +++ b/winsup/cygwin/socket_tests/lib/inet_sockets.c @@ -0,0 +1,188 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU Lesser General Public License as published * +* by the Free Software Foundation, either version 3 or (at your option) * +* any later version. This program is distributed without any warranty. * +* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Listing 59-9 */ + +/* inet_sockets.c + + A package of useful routines for Internet domain sockets. +*/ +#define _BSD_SOURCE /* To get NI_MAXHOST and NI_MAXSERV + definitions from <netdb.h> */ +#include "af_unix_hdr.h" +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include "inet_sockets.h" /* Declares functions defined here */ + +/* The following arguments are common to several of the routines + below: + + 'host': NULL for loopback IP address, or + a host name or numeric IP address + 'service': either a name or a port number + 'type': either SOCK_STREAM or SOCK_DGRAM +*/ + +/* Create socket and connect it to the address specified by + 'host' + 'service'/'type'. Return socket descriptor on success, + or -1 on error */ + +int +inetConnect(const char *host, const char *service, int type) +{ + struct addrinfo hints; + struct addrinfo *result, *rp; + int sfd, s; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_canonname = NULL; + hints.ai_addr = NULL; + hints.ai_next = NULL; + hints.ai_family = AF_UNSPEC; /* Allows IPv4 or IPv6 */ + hints.ai_socktype = type; + + s = getaddrinfo(host, service, &hints, &result); + if (s != 0) { + errno = ENOSYS; + return -1; + } + + /* Walk through returned list until we find an address structure + that can be used to successfully connect a socket */ + + for (rp = result; rp != NULL; rp = rp->ai_next) { + sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (sfd == -1) + continue; /* On error, try next address */ + + if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) + break; /* Success */ + + /* Connect failed: close this socket and try next address */ + + close(sfd); + } + + freeaddrinfo(result); + + return (rp == NULL) ? -1 : sfd; +} + +/* Create an Internet domain socket and bind it to the address + { wildcard-IP-address + 'service'/'type' }. + If 'doListen' is TRUE, then make this a listening socket (by + calling listen() with 'backlog'), with the SO_REUSEADDR option set. + If 'addrLen' is not NULL, then use it to return the size of the + address structure for the address family for this socket. + Return the socket descriptor on success, or -1 on error. */ + +static int /* Public interfaces: inetBind() and inetListen() */ +inetPassiveSocket(const char *service, int type, socklen_t *addrlen, + Boolean doListen, int backlog) +{ + struct addrinfo hints; + struct addrinfo *result, *rp; + int sfd, optval, s; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_canonname = NULL; + hints.ai_addr = NULL; + hints.ai_next = NULL; + hints.ai_socktype = type; + hints.ai_family = AF_UNSPEC; /* Allows IPv4 or IPv6 */ + hints.ai_flags = AI_PASSIVE; /* Use wildcard IP address */ + + s = getaddrinfo(NULL, service, &hints, &result); + if (s != 0) + return -1; + + /* Walk through returned list until we find an address structure + that can be used to successfully create and bind a socket */ + + optval = 1; + for (rp = result; rp != NULL; rp = rp->ai_next) { + sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (sfd == -1) + continue; /* On error, try next address */ + + if (doListen) { + if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &optval, + sizeof(optval)) == -1) { + close(sfd); + freeaddrinfo(result); + return -1; + } + } + + if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) + break; /* Success */ + + /* bind() failed: close this socket and try next address */ + + close(sfd); + } + + if (rp != NULL && doListen) { + if (listen(sfd, backlog) == -1) { + freeaddrinfo(result); + return -1; + } + } + + if (rp != NULL && addrlen != NULL) + *addrlen = rp->ai_addrlen; /* Return address structure size */ + + freeaddrinfo(result); + + return (rp == NULL) ? -1 : sfd; +} + +/* Create stream socket, bound to wildcard IP address + port given in + 'service'. Make the socket a listening socket, with the specified + 'backlog'. Return socket descriptor on success, or -1 on error. */ + +int +inetListen(const char *service, int backlog, socklen_t *addrlen) +{ + return inetPassiveSocket(service, SOCK_STREAM, addrlen, TRUE, backlog); +} + +/* Create socket bound to wildcard IP address + port given in + 'service'. Return socket descriptor on success, or -1 on error. */ + +int +inetBind(const char *service, int type, socklen_t *addrlen) +{ + return inetPassiveSocket(service, type, addrlen, FALSE, 0); +} + +/* Given a socket address in 'addr', whose length is specified in + 'addrlen', return a null-terminated string containing the host and + service names in the form "(hostname, port#)". The string is + returned in the buffer pointed to by 'addrStr', and this value is + also returned as the function result. The caller must specify the + size of the 'addrStr' buffer in 'addrStrLen'. */ + +char * +inetAddressStr(const struct sockaddr *addr, socklen_t addrlen, + char *addrStr, int addrStrLen) +{ + char host[NI_MAXHOST], service[NI_MAXSERV]; + + if (getnameinfo(addr, addrlen, host, NI_MAXHOST, + service, NI_MAXSERV, NI_NUMERICSERV) == 0) + snprintf(addrStr, addrStrLen, "(%s, %s)", host, service); + else + snprintf(addrStr, addrStrLen, "(?UNKNOWN?)"); + + return addrStr; +} diff --git a/winsup/cygwin/socket_tests/lib/inet_sockets.h b/winsup/cygwin/socket_tests/lib/inet_sockets.h new file mode 100644 index 000000000..1aa263050 --- /dev/null +++ b/winsup/cygwin/socket_tests/lib/inet_sockets.h @@ -0,0 +1,36 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU Lesser General Public License as published * +* by the Free Software Foundation, either version 3 or (at your option) * +* any later version. This program is distributed without any warranty. * +* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Listing 59-8 */ + +/* inet_sockets.h + + Header file for inet_sockets.c. +*/ +#ifndef INET_SOCKETS_H +#define INET_SOCKETS_H /* Prevent accidental double inclusion */ + +#include <sys/socket.h> +#include <netdb.h> + +int inetConnect(const char *host, const char *service, int type); + +int inetListen(const char *service, int backlog, socklen_t *addrlen); + +int inetBind(const char *service, int type, socklen_t *addrlen); + +char *inetAddressStr(const struct sockaddr *addr, socklen_t addrlen, + char *addrStr, int addrStrLen); + +#define IS_ADDR_STR_LEN 4096 + /* Suggested length for string buffer that caller + should pass to inetAddressStr(). Must be greater + than (NI_MAXHOST + NI_MAXSERV + 4) */ +#endif diff --git a/winsup/cygwin/socket_tests/lib/pty_fork.c b/winsup/cygwin/socket_tests/lib/pty_fork.c new file mode 100644 index 000000000..4e78c9e9b --- /dev/null +++ b/winsup/cygwin/socket_tests/lib/pty_fork.c @@ -0,0 +1,117 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU Lesser General Public License as published * +* by the Free Software Foundation, either version 3 or (at your option) * +* any later version. This program is distributed without any warranty. * +* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Listing 64-2 */ + +/* pty_fork.c + + Implements ptyFork(), a function that creates a child process connected to + the parent (i.e., the calling process) via a pseudoterminal (pty). The child + is placed in a new session, with the pty slave as its controlling terminal, + and its standard input, output, and error connected to the pty slave. + + In the parent, 'masterFd' is used to return the file descriptor for the + pty master. + + If 'slaveName' is non-NULL, then it is used to return the name of the pty + slave. If 'slaveName' is not NULL, then 'snLen' should be set to indicate + the size of the buffer pointed to by 'slaveName'. + + If 'slaveTermios' and 'slaveWS' are non-NULL, then they are used respectively + to set the terminal attributes and window size of the pty slave. + + Returns: + in child: 0 + in parent: PID of child or -1 on error +*/ +#include <fcntl.h> +#include <termios.h> +#include <sys/ioctl.h> +#include "pty_master_open.h" +#include "pty_fork.h" /* Declares ptyFork() */ +#include "af_unix_hdr.h" + +#define MAX_SNAME 1000 /* Maximum size for pty slave name */ + +pid_t +ptyFork(int *masterFd, char *slaveName, size_t snLen, + const struct termios *slaveTermios, const struct winsize *slaveWS) +{ + int mfd, slaveFd, savedErrno; + pid_t childPid; + char slname[MAX_SNAME]; + + mfd = ptyMasterOpen(slname, MAX_SNAME); + if (mfd == -1) + return -1; + + if (slaveName != NULL) { /* Return slave name to caller */ + if (strlen(slname) < snLen) { + strncpy(slaveName, slname, snLen); + + } else { /* 'slaveName' was too small */ + close(mfd); + errno = EOVERFLOW; + return -1; + } + } + + childPid = fork(); + + if (childPid == -1) { /* fork() failed */ + savedErrno = errno; /* close() might change 'errno' */ + close(mfd); /* Don't leak file descriptors */ + errno = savedErrno; + return -1; + } + + if (childPid != 0) { /* Parent */ + *masterFd = mfd; /* Only parent gets master fd */ + return childPid; /* Like parent of fork() */ + } + + /* Child falls through to here */ + + if (setsid() == -1) /* Start a new session */ + err_exit("ptyFork:setsid"); + + close(mfd); /* Not needed in child */ + + slaveFd = open(slname, O_RDWR); /* Becomes controlling tty */ + if (slaveFd == -1) + err_exit("ptyFork:open-slave"); + +/* #ifdef TIOCSCTTY /\* Acquire controlling tty on BSD *\/ */ +/* if (ioctl(slaveFd, TIOCSCTTY, 0) == -1) */ +/* err_exit("ptyFork:ioctl-TIOCSCTTY"); */ +/* #endif */ + + if (slaveTermios != NULL) /* Set slave tty attributes */ + if (tcsetattr(slaveFd, TCSANOW, slaveTermios) == -1) + err_exit("ptyFork:tcsetattr"); + + if (slaveWS != NULL) /* Set slave tty window size */ + if (ioctl(slaveFd, TIOCSWINSZ, slaveWS) == -1) + err_exit("ptyFork:ioctl-TIOCSWINSZ"); + + /* Duplicate pty slave to be child's stdin, stdout, and stderr */ + + if (dup2(slaveFd, STDIN_FILENO) != STDIN_FILENO) + err_exit("ptyFork:dup2-STDIN_FILENO"); + if (dup2(slaveFd, STDOUT_FILENO) != STDOUT_FILENO) + err_exit("ptyFork:dup2-STDOUT_FILENO"); + if (dup2(slaveFd, STDERR_FILENO) != STDERR_FILENO) + err_exit("ptyFork:dup2-STDERR_FILENO"); + + if (slaveFd > STDERR_FILENO) /* Safety check */ + close(slaveFd); /* No longer need this fd */ + + return 0; /* Like child of fork() */ +} diff --git a/winsup/cygwin/socket_tests/lib/pty_fork.h b/winsup/cygwin/socket_tests/lib/pty_fork.h new file mode 100644 index 000000000..99e0c0683 --- /dev/null +++ b/winsup/cygwin/socket_tests/lib/pty_fork.h @@ -0,0 +1,27 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU Lesser General Public License as published * +* by the Free Software Foundation, either version 3 or (at your option) * +* any later version. This program is distributed without any warranty. * +* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Header file for Listing 64-2 */ + +/* pty_fork.h + + Header file for pty_fork.c. +*/ +#ifndef FORK_PTY_H +#define FORK_PTY_H + +#include <termios.h> +#include <sys/ioctl.h> +#include <sys/types.h> + +pid_t ptyFork(int *masterFd, char *slaveName, size_t snLen, + const struct termios *slaveTermios, const struct winsize *slaveWS); + +#endif diff --git a/winsup/cygwin/socket_tests/lib/pty_master_open.c b/winsup/cygwin/socket_tests/lib/pty_master_open.c new file mode 100644 index 000000000..b0de3a1fb --- /dev/null +++ b/winsup/cygwin/socket_tests/lib/pty_master_open.c @@ -0,0 +1,93 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU Lesser General Public License as published * +* by the Free Software Foundation, either version 3 or (at your option) * +* any later version. This program is distributed without any warranty. * +* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Listing 64-1 */ + +/* pty_master_open.c + + Implement our ptyMasterOpen() function, based on UNIX 98 pseudoterminals. + See comments below. + + See also pty_master_open_bsd.c. +*/ +#if ! defined(__sun) + /* Prevents ptsname() declaration being visible on Solaris 8 */ +#if ! defined(_XOPEN_SOURCE) || _XOPEN_SOURCE < 600 +#define _XOPEN_SOURCE 600 +#endif +#endif +#include <stdlib.h> +#include <fcntl.h> +#include "pty_master_open.h" /* Declares ptyMasterOpen() */ +#include "af_unix_hdr.h" + +/* Some implementations don't have posix_openpt() */ + +#if defined(__sun) /* Not on Solaris 8 */ +#define NO_POSIX_OPENPT +#endif + +#ifdef NO_POSIX_OPENPT + +static int +posix_openpt(int flags) +{ + return open("/dev/ptmx", flags); +} + +#endif + +/* Open a pty master, returning file descriptor, or -1 on error. + On successful completion, the name of the corresponding pty + slave is returned in 'slaveName'. 'snLen' should be set to + indicate the size of the buffer pointed to by 'slaveName'. */ + +int +ptyMasterOpen(char *slaveName, size_t snLen) +{ + int masterFd, savedErrno; + char *p; + + masterFd = posix_openpt(O_RDWR | O_NOCTTY); /* Open pty master */ + if (masterFd == -1) + return -1; + + if (grantpt(masterFd) == -1) { /* Grant access to slave pty */ + savedErrno = errno; + close(masterFd); /* Might change 'errno' */ + errno = savedErrno; + return -1; + } + + if (unlockpt(masterFd) == -1) { /* Unlock slave pty */ + savedErrno = errno; + close(masterFd); /* Might change 'errno' */ + errno = savedErrno; + return -1; + } + + p = ptsname(masterFd); /* Get slave pty name */ + if (p == NULL) { + savedErrno = errno; + close(masterFd); /* Might change 'errno' */ + errno = savedErrno; + return -1; + } + + if (strlen(p) < snLen) { + strncpy(slaveName, p, snLen); + } else { /* Return an error if buffer too small */ + close(masterFd); + errno = EOVERFLOW; + return -1; + } + + return masterFd; +} diff --git a/winsup/cygwin/socket_tests/lib/pty_master_open.h b/winsup/cygwin/socket_tests/lib/pty_master_open.h new file mode 100644 index 000000000..5f5c7b991 --- /dev/null +++ b/winsup/cygwin/socket_tests/lib/pty_master_open.h @@ -0,0 +1,24 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU Lesser General Public License as published * +* by the Free Software Foundation, either version 3 or (at your option) * +* any later version. This program is distributed without any warranty. * +* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Header file for Listing 64-1 */ + +/* pty_open.h + + Header file for pty_open.c (and pty_master_open_bsd.c). +*/ +#ifndef PTY_MASTER_OPEN_H +#define PTY_MASTER_OPEN_H + +#include <sys/types.h> + +int ptyMasterOpen(char *slaveName, size_t snLen); + +#endif diff --git a/winsup/cygwin/socket_tests/lib/read_line.c b/winsup/cygwin/socket_tests/lib/read_line.c new file mode 100644 index 000000000..f6c5f96c1 --- /dev/null +++ b/winsup/cygwin/socket_tests/lib/read_line.c @@ -0,0 +1,73 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU Lesser General Public License as published * +* by the Free Software Foundation, either version 3 or (at your option) * +* any later version. This program is distributed without any warranty. * +* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Listing 59-1 */ + +/* read_line.c + + Implementation of readLine(). +*/ +#include <unistd.h> +#include <errno.h> +#include "read_line.h" /* Declaration of readLine() */ + +/* Read characters from 'fd' until a newline is encountered. If a newline + character is not encountered in the first (n - 1) bytes, then the excess + characters are discarded. The returned string placed in 'buf' is + null-terminated and includes the newline character if it was read in the + first (n - 1) bytes. The function return value is the number of bytes + placed in buffer (which includes the newline character if encountered, + but excludes the terminating null byte). */ + +ssize_t +readLine(int fd, void *buffer, size_t n) +{ + ssize_t numRead; /* # of bytes fetched by last read() */ + size_t totRead; /* Total bytes read so far */ + char *buf; + char ch; + + if (n <= 0 || buffer == NULL) { + errno = EINVAL; + return -1; + } + + buf = buffer; /* No pointer arithmetic on "void *" */ + + totRead = 0; + for (;;) { + numRead = read(fd, &ch, 1); + + if (numRead == -1) { + if (errno == EINTR) /* Interrupted --> restart read() */ + continue; + else + return -1; /* Some other error */ + + } else if (numRead == 0) { /* EOF */ + if (totRead == 0) /* No bytes read; return 0 */ + return 0; + else /* Some bytes read; add '\0' */ + break; + + } else { /* 'numRead' must be 1 if we get here */ + if (totRead < n - 1) { /* Discard > (n - 1) bytes */ + totRead++; + *buf++ = ch; + } + + if (ch == '\n') + break; + } + } + + *buf = '\0'; + return totRead; +} diff --git a/winsup/cygwin/socket_tests/lib/read_line.h b/winsup/cygwin/socket_tests/lib/read_line.h new file mode 100644 index 000000000..1da5b874e --- /dev/null +++ b/winsup/cygwin/socket_tests/lib/read_line.h @@ -0,0 +1,24 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU Lesser General Public License as published * +* by the Free Software Foundation, either version 3 or (at your option) * +* any later version. This program is distributed without any warranty. * +* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Header file for Listing 59-1 */ + +/* read_line.h + + Header file for read_line.c. +*/ +#ifndef READ_LINE_H +#define READ_LINE_H + +#include <sys/types.h> + +ssize_t readLine(int fd, void *buffer, size_t n); + +#endif diff --git a/winsup/cygwin/socket_tests/lib/scm_functions.c b/winsup/cygwin/socket_tests/lib/scm_functions.c new file mode 100644 index 000000000..8dbbca108 --- /dev/null +++ b/winsup/cygwin/socket_tests/lib/scm_functions.c @@ -0,0 +1,146 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU Lesser General Public License as published * +* by the Free Software Foundation, either version 3 or (at your option) * +* any later version. This program is distributed without any warranty. * +* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Supplementary program for Chapter 61 */ + +/* scm_functions.c + + Functions to exchange ancillary data over UNIX domain sockets. + These functions are simplistic, in that they ignore the "real" data + content on the assumption that the sockets are being used only for + the purposes of exchanging ancillary data. In many real-world + applications, the application makes use of both the "real" data + channel and the ancillary data, with some kind of protocol that + determines how the "real" and ancillary data are used together. +*/ +#include "scm_functions.h" + +/* Send the file descriptor 'fd' over the connected UNIX domain + socket 'sockfd' */ + +int +sendfd(int sockfd, int fd) +{ + struct msghdr msgh; + struct iovec iov; + int data; + struct cmsghdr *cmsgp; + + /* Allocate a char array of suitable size to hold the ancillary data. + However, since this buffer is in reality a 'struct cmsghdr', use a + union to ensure that it is aligned as required for that structure. + Alternatively, we could allocate the buffer using malloc(), which + returns a buffer that satisfies the strictest alignment requirements + of any type. However, if we employ that approach, we must ensure + that we free() the buffer on all return paths from this function. */ + union { + char buf[CMSG_SPACE(sizeof(int))]; + /* Space large enough to hold an 'int' */ + struct cmsghdr align; + } controlMsg; + + /* The 'msg_name' field can be used to specify the address of the + destination socket when sending a datagram. However, we do not + need to use this field because we presume that 'sockfd' is a + connected socket. */ + + msgh.msg_name = NULL; + msgh.msg_namelen = 0; + + /* On Linux, we must transmit at least one byte of real data in + order to send ancillary data. We transmit an arbitrary integer + whose value is ignored by recvfd(). */ + + msgh.msg_iov = &iov; + msgh.msg_iovlen = 1; + iov.iov_base = &data; + iov.iov_len = sizeof(int); + data = 12345; + + /* Set 'msghdr' fields that describe ancillary data */ + + msgh.msg_control = controlMsg.buf; + msgh.msg_controllen = sizeof(controlMsg.buf); + + /* Set up ancillary data describing file descriptor to send */ + + cmsgp = CMSG_FIRSTHDR(&msgh); + cmsgp->cmsg_level = SOL_SOCKET; + cmsgp->cmsg_type = SCM_RIGHTS; + cmsgp->cmsg_len = CMSG_LEN(sizeof(int)); + *((int *) CMSG_DATA(cmsgp)) = fd; + + /* Send real plus ancillary data */ + + if (sendmsg(sockfd, &msgh, 0) == -1) + return -1; + + return 0; +} + +/* Receive a file descriptor on a connected UNIX domain socket. + The received file descriptor is returned as the function result. */ + +int +recvfd(int sockfd) +{ + struct msghdr msgh; + struct iovec iov; + int data; + ssize_t nr; + + /* Allocate a char buffer for the ancillary data. See the comments + in sendfd() */ + union { + char buf[CMSG_SPACE(sizeof(int))]; + struct cmsghdr align; + } controlMsg; + struct cmsghdr *cmsgp; + + /* The 'msg_name' field can be used to obtain the address of the + sending socket. However, we do not need this information. */ + + msgh.msg_name = NULL; + msgh.msg_namelen = 0; + + /* Specify buffer for receiving real data */ + + msgh.msg_iov = &iov; + msgh.msg_iovlen = 1; + iov.iov_base = &data; /* Real data is an 'int' */ + iov.iov_len = sizeof(int); + + /* Set 'msghdr' fields that describe ancillary data */ + + msgh.msg_control = controlMsg.buf; + msgh.msg_controllen = sizeof(controlMsg.buf); + + /* Receive real plus ancillary data; content of real data is ignored */ + + nr = recvmsg(sockfd, &msgh, 0); + if (nr == -1) + return -1; + + cmsgp = CMSG_FIRSTHDR(&msgh); + + /* Check the validity of the 'cmsghdr' */ + + if (cmsgp == NULL || + cmsgp->cmsg_len != CMSG_LEN(sizeof(int)) || + cmsgp->cmsg_level != SOL_SOCKET || + cmsgp->cmsg_type != SCM_RIGHTS) { + errno = EINVAL; + return -1; + } + + /* Return the received file descriptor to our caller */ + + return *((int *) CMSG_DATA(cmsgp)); +} diff --git a/winsup/cygwin/socket_tests/lib/scm_functions.h b/winsup/cygwin/socket_tests/lib/scm_functions.h new file mode 100644 index 000000000..a94129186 --- /dev/null +++ b/winsup/cygwin/socket_tests/lib/scm_functions.h @@ -0,0 +1,26 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU Lesser General Public License as published * +* by the Free Software Foundation, either version 3 or (at your option) * +* any later version. This program is distributed without any warranty. * +* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Supplementary program for Chapter 61 */ + +/* scm_functions.h + + Functions to exchange ancillary data over a UNIX domain socket. +*/ +#ifndef SCM_FUNCTIONS_H +#define SCM_FUNCTIONS_H /* Prevent accidental double inclusion */ + +#include "af_unix_hdr.h" + +int sendfd(int sockfd, int fd); + +int recvfd(int sockfd); + +#endif diff --git a/winsup/cygwin/socket_tests/lib/tty_functions.c b/winsup/cygwin/socket_tests/lib/tty_functions.c new file mode 100644 index 000000000..61b024e0b --- /dev/null +++ b/winsup/cygwin/socket_tests/lib/tty_functions.c @@ -0,0 +1,89 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU Lesser General Public License as published * +* by the Free Software Foundation, either version 3 or (at your option) * +* any later version. This program is distributed without any warranty. * +* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Listing 62-3 */ + +/* tty_functions.c + + Implement ttySetCbreak() and ttySetRaw(). +*/ +#include <termios.h> +#include <unistd.h> +#include "tty_functions.h" /* Declares functions defined here */ + +/* Place terminal referred to by 'fd' in cbreak mode (noncanonical mode + with echoing turned off). This function assumes that the terminal is + currently in cooked mode (i.e., we shouldn't call it if the terminal + is currently in raw mode, since it does not undo all of the changes + made by the ttySetRaw() function below). Return 0 on success, or -1 + on error. If 'prevTermios' is non-NULL, then use the buffer to which + it points to return the previous terminal settings. */ + +int +ttySetCbreak(int fd, struct termios *prevTermios) +{ + struct termios t; + + if (tcgetattr(fd, &t) == -1) + return -1; + + if (prevTermios != NULL) + *prevTermios = t; + + t.c_lflag &= ~(ICANON | ECHO); + t.c_lflag |= ISIG; + + t.c_iflag &= ~ICRNL; + + t.c_cc[VMIN] = 1; /* Character-at-a-time input */ + t.c_cc[VTIME] = 0; /* with blocking */ + + if (tcsetattr(fd, TCSAFLUSH, &t) == -1) + return -1; + + return 0; +} + +/* Place terminal referred to by 'fd' in raw mode (noncanonical mode + with all input and output processing disabled). Return 0 on success, + or -1 on error. If 'prevTermios' is non-NULL, then use the buffer to + which it points to return the previous terminal settings. */ + +int +ttySetRaw(int fd, struct termios *prevTermios) +{ + struct termios t; + + if (tcgetattr(fd, &t) == -1) + return -1; + + if (prevTermios != NULL) + *prevTermios = t; + + t.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO); + /* Noncanonical mode, disable signals, extended + input processing, and echoing */ + + t.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | + INPCK | ISTRIP | IXON | PARMRK); + /* Disable special handling of CR, NL, and BREAK. + No 8th-bit stripping or parity error handling. + Disable START/STOP output flow control. */ + + t.c_oflag &= ~OPOST; /* Disable all output processing */ + + t.c_cc[VMIN] = 1; /* Character-at-a-time input */ + t.c_cc[VTIME] = 0; /* with blocking */ + + if (tcsetattr(fd, TCSAFLUSH, &t) == -1) + return -1; + + return 0; +} diff --git a/winsup/cygwin/socket_tests/lib/tty_functions.h b/winsup/cygwin/socket_tests/lib/tty_functions.h new file mode 100644 index 000000000..4ed7aaca8 --- /dev/null +++ b/winsup/cygwin/socket_tests/lib/tty_functions.h @@ -0,0 +1,26 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU Lesser General Public License as published * +* by the Free Software Foundation, either version 3 or (at your option) * +* any later version. This program is distributed without any warranty. * +* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Header file for Listing 62-3 */ + +/* tty_functions.h + + Header file for tty_functions.c. +*/ +#ifndef TTY_FUNCTIONS_H +#define TTY_FUNCTIONS_H + +#include <termios.h> + +int ttySetCbreak(int fd, struct termios *prevTermios); + +int ttySetRaw(int fd, struct termios *prevTermios); + +#endif diff --git a/winsup/cygwin/socket_tests/lib/unix_sockets.c b/winsup/cygwin/socket_tests/lib/unix_sockets.c new file mode 100644 index 000000000..cc93b3ac6 --- /dev/null +++ b/winsup/cygwin/socket_tests/lib/unix_sockets.c @@ -0,0 +1,94 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU Lesser General Public License as published * +* by the Free Software Foundation, either version 3 or (at your option) * +* any later version. This program is distributed without any warranty. * +* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Solution for Exercise 59-3:b */ + +/* unix_sockets.c + + A package of useful routines for UNIX domain sockets. +*/ +#include "unix_sockets.h" /* Declares functions defined here */ +#include "af_unix_hdr.h" + +/* Build a UNIX domain socket address structure for 'path', returning + it in 'addr'. Returns -1 on success, or 0 on error. */ + +int +unixBuildAddress(const char *path, struct sockaddr_un *addr) +{ + if (addr == NULL || path == NULL) { + errno = EINVAL; + return -1; + } + + memset(addr, 0, sizeof(struct sockaddr_un)); + addr->sun_family = AF_UNIX; + if (strlen(path) < sizeof(addr->sun_path)) { + strncpy(addr->sun_path, path, sizeof(addr->sun_path) - 1); + return 0; + } else { + errno = ENAMETOOLONG; + return -1; + } +} + +/* Create a UNIX domain socket of type 'type' and connect it + to the remote address specified by the 'path'. + Return the socket descriptor on success, or -1 on error */ + +int +unixConnect(const char *path, int type) +{ + int sd, savedErrno; + struct sockaddr_un addr; + + if (unixBuildAddress(path, &addr) == -1) + return -1; + + sd = socket(AF_UNIX, type, 0); + if (sd == -1) + return -1; + + if (connect(sd, (struct sockaddr *) &addr, + sizeof(struct sockaddr_un)) == -1) { + savedErrno = errno; + close(sd); /* Might change 'errno' */ + errno = savedErrno; + return -1; + } + + return sd; +} + +/* Create a UNIX domain socket and bind it to 'path'. + Return the socket descriptor on success, or -1 on error. */ + +int +unixBind(const char *path, int type) +{ + int sd, savedErrno; + struct sockaddr_un addr; + + if (unixBuildAddress(path, &addr) == -1) + return -1; + + sd = socket(AF_UNIX, type, 0); + if (sd == -1) + return -1; + + if (bind(sd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) == -1) { + savedErrno = errno; + close(sd); /* Might change 'errno' */ + errno = savedErrno; + return -1; + } + + return sd; +} diff --git a/winsup/cygwin/socket_tests/lib/unix_sockets.h b/winsup/cygwin/socket_tests/lib/unix_sockets.h new file mode 100644 index 000000000..5ad394317 --- /dev/null +++ b/winsup/cygwin/socket_tests/lib/unix_sockets.h @@ -0,0 +1,28 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU Lesser General Public License as published * +* by the Free Software Foundation, either version 3 or (at your option) * +* any later version. This program is distributed without any warranty. * +* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Solution for Exercise 59-3:a */ + +/* unix_sockets.h + + Header file for unix_sockets.c. +*/ +#ifndef UNIX_SOCKETS_H +#define UNIX_SOCKETS_H /* Prevent accidental double inclusion */ + +#include "af_unix_hdr.h" + +int unixBuildAddress(const char *path, struct sockaddr_un *addr); + +int unixConnect(const char *path, int type); + +int unixBind(const char *path, int type); + +#endif diff --git a/winsup/cygwin/socket_tests/msg_peek.h b/winsup/cygwin/socket_tests/msg_peek.h new file mode 100644 index 000000000..ae2b7c962 --- /dev/null +++ b/winsup/cygwin/socket_tests/msg_peek.h @@ -0,0 +1,9 @@ +/* Header for msg_peek_sv.c and msg_peek_cl.c */ + +#include "af_unix_hdr.h" + +#define SV_SOCK_PATH "/tmp/peek" + +#define BUF_SIZE 100 + +#define BACKLOG 5 diff --git a/winsup/cygwin/socket_tests/msg_peek_cl.c b/winsup/cygwin/socket_tests/msg_peek_cl.c new file mode 100644 index 000000000..db3c52c5f --- /dev/null +++ b/winsup/cygwin/socket_tests/msg_peek_cl.c @@ -0,0 +1,20 @@ +#include "msg_peek.h" + +int +main () +{ + int sfd; + ssize_t nread; + char buf[BUF_SIZE]; + + if ((sfd = unixConnect (SV_SOCK_PATH, SOCK_STREAM)) < 0) + errExit ("unixConnect"); + + /* Copy stdin to socket. */ + while ((nread = read (STDIN_FILENO, buf, BUF_SIZE)) > 0) + if (write (sfd, buf, nread) != nread) + errExit ("partial/failed write"); + + if (nread < 0) + errExit("read"); +} diff --git a/winsup/cygwin/socket_tests/msg_peek_sv.c b/winsup/cygwin/socket_tests/msg_peek_sv.c new file mode 100644 index 000000000..3303f05a6 --- /dev/null +++ b/winsup/cygwin/socket_tests/msg_peek_sv.c @@ -0,0 +1,36 @@ +#include "msg_peek.h" + +int +main () +{ + int sfd, cfd; + ssize_t nread; + char buf[BUF_SIZE]; + + if (remove (SV_SOCK_PATH) < 0 && errno != ENOENT) + errExit ("remove"); + + if ((sfd = unixBind (SV_SOCK_PATH, SOCK_STREAM)) < 0) + errExit ("unixBind"); + + if (listen (sfd, BACKLOG) < 0) + errExit ("listen"); + + cfd = accept (sfd, NULL, NULL); + if (cfd < 0) + errExit ("accept"); + + printf ("peeking...\n"); + if ((nread = recv (cfd, buf, BUF_SIZE - 1, MSG_PEEK)) < 0) + errExit ("recv"); + buf[nread] = '\0'; + printf ("reading would yield %zd bytes: %s\n", nread, buf); + sleep (1); + printf ("reading...\n"); + if ((nread = read (cfd, buf, BUF_SIZE)) < 0) + errExit ("read"); + buf[nread] = '\0'; + printf ("read %zd bytes: %s\n", nread, buf); + if (close (cfd) < 0) + errExit ("close"); +} diff --git a/winsup/cygwin/socket_tests/pty_master.h b/winsup/cygwin/socket_tests/pty_master.h new file mode 100644 index 000000000..4a30752a0 --- /dev/null +++ b/winsup/cygwin/socket_tests/pty_master.h @@ -0,0 +1,5 @@ +/* pty_slave.h + + Header file for send_pty_master.c and recv_pty_master.c +*/ +#define SOCK_PATH "/tmp/pty_master" diff --git a/winsup/cygwin/socket_tests/pty_slave.h b/winsup/cygwin/socket_tests/pty_slave.h new file mode 100644 index 000000000..232440eba --- /dev/null +++ b/winsup/cygwin/socket_tests/pty_slave.h @@ -0,0 +1,5 @@ +/* pty_slave.h + + Header file for send_pty_slave.c and recv_pty_slave.c +*/ +#define SOCK_PATH "/tmp/pty_slave" diff --git a/winsup/cygwin/socket_tests/readv_socket.c b/winsup/cygwin/socket_tests/readv_socket.c new file mode 100644 index 000000000..dceec2f62 --- /dev/null +++ b/winsup/cygwin/socket_tests/readv_socket.c @@ -0,0 +1,41 @@ +/* Adapted from https://www.oreilly.com/library/view/linux-system-programming/0596009585/ch04.html */ + +#include "scatter_gather.h" + +int main () +{ + char foo[48], bar[51], baz[49]; + struct iovec iov[3]; + ssize_t nr; + int sfd, cfd; + + if (remove (SV_SOCK_PATH) < 0 && errno != ENOENT) + errExit ("remove"); + + if ((sfd = unixBind (SV_SOCK_PATH, SOCK_STREAM)) < 0) + errExit ("unixBind"); + + if (listen (sfd, BACKLOG) < 0) + errExit ("listen"); + + cfd = accept (sfd, NULL, NULL); + if (cfd < 0) + errExit ("accept"); + + iov[0].iov_base = foo; + iov[0].iov_len = sizeof (foo); + iov[1].iov_base = bar; + iov[1].iov_len = sizeof (bar); + iov[2].iov_base = baz; + iov[2].iov_len = sizeof (baz); + + nr = readv (cfd, iov, 3); + if (nr < 0) + errExit ("readv"); + printf ("read %zd bytes\n", nr); + for (int i = 0; i < 3; i++) + printf ("%d: %s", i, (char *) iov[i].iov_base); + + if (close (cfd) < 0) + errExit ("close"); +} diff --git a/winsup/cygwin/socket_tests/rec_pty_slave.c b/winsup/cygwin/socket_tests/rec_pty_slave.c new file mode 100644 index 000000000..b5197a80e --- /dev/null +++ b/winsup/cygwin/socket_tests/rec_pty_slave.c @@ -0,0 +1,3 @@ +#include "af_unix_hdr.h" +#include "pty_slave.h" + diff --git a/winsup/cygwin/socket_tests/recv_pty_master.c b/winsup/cygwin/socket_tests/recv_pty_master.c new file mode 100644 index 000000000..81e16b4f2 --- /dev/null +++ b/winsup/cygwin/socket_tests/recv_pty_master.c @@ -0,0 +1,40 @@ +#include "af_unix_hdr.h" +#include "pty_master.h" +#define BUF_SIZE 100 + +int +main (int argc, char *argv[]) +{ + int lfd, connfd, ptyfd, junk; + + if (remove (SOCK_PATH) == -1 && errno != ENOENT) + errExit ("remove-%s", SOCK_PATH); + + lfd = unixBind (SOCK_PATH, SOCK_STREAM); + if (lfd < 0) + errExit ("unixBind"); + printf ("Waiting for sender to connect and send descriptor...\n"); + if (listen (lfd, 5) < 0) + errExit ("listen"); + connfd = accept (lfd, NULL, NULL); + if (connfd < 0) + errExit ("accept"); + ptyfd = recvfd (connfd); + if (ptyfd < 0) + errExit ("recvfd"); + printf ("Received descriptor %d.\n", ptyfd); + printf ("Writing \"ps\" to that descriptor.\n" + "This should appear in the other terminal\n" + "and be executed by the shell running there.\n"); + if (write (ptyfd, "ps\n", 3) != 3) + errExit ("write"); + printf ("Waiting for sender to finish...\n"); + if (read (connfd, &junk, sizeof junk) < 0) + errExit ("read"); + if (close (ptyfd) < 0) + errMsg ("close"); + if (close (lfd) < 0) + errMsg ("close"); + if (close (connfd) < 0) + errMsg ("close"); +} diff --git a/winsup/cygwin/socket_tests/recv_pty_slave.c b/winsup/cygwin/socket_tests/recv_pty_slave.c new file mode 100644 index 000000000..6095d302a --- /dev/null +++ b/winsup/cygwin/socket_tests/recv_pty_slave.c @@ -0,0 +1,37 @@ +#include "af_unix_hdr.h" +#include "pty_slave.h" +#define BUF_SIZE 100 + +int +main (int argc, char *argv[]) +{ + int lfd, connfd, ptyfd; + + if (remove (SOCK_PATH) == -1 && errno != ENOENT) + errExit ("remove-%s", SOCK_PATH); + + lfd = unixBind (SOCK_PATH, SOCK_STREAM); + if (lfd < 0) + errExit ("unixBind"); + printf ("Waiting for sender to connect and send descriptor...\n"); + if (listen (lfd, 5) < 0) + errExit ("listen"); + connfd = accept (lfd, NULL, NULL); + if (connfd < 0) + errExit ("accept"); + ptyfd = recvfd (connfd); + if (ptyfd < 0) + errExit ("recvfd"); + printf ("Received descriptor %d.\n", ptyfd); + printf ("Writing \"hello\" to that descriptor.\n" + "This should appear in the other terminal " + "as though it were output by the shell.\n"); + if (write (ptyfd, "hello\n", 6) != 6) + errExit ("write"); + if (close (ptyfd) < 0) + errMsg ("close"); + if (close (lfd) < 0) + errMsg ("close"); + if (close (connfd) < 0) + errMsg ("close"); +} diff --git a/winsup/cygwin/socket_tests/scatter_gather.h b/winsup/cygwin/socket_tests/scatter_gather.h new file mode 100644 index 000000000..99502222c --- /dev/null +++ b/winsup/cygwin/socket_tests/scatter_gather.h @@ -0,0 +1,7 @@ +/* Header for readv_socket.c and writev_socket.c */ + +#include "af_unix_hdr.h" + +#define SV_SOCK_PATH "/tmp/scatter" + +#define BACKLOG 5 diff --git a/winsup/cygwin/socket_tests/scm_cred.h b/winsup/cygwin/socket_tests/scm_cred.h new file mode 100644 index 000000000..26858a678 --- /dev/null +++ b/winsup/cygwin/socket_tests/scm_cred.h @@ -0,0 +1,21 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either version 3 or (at your option) any * +* later version. This program is distributed without any warranty. See * +* the file COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Supplementary program for Chapter 61 */ + +/* scm_cred.h + + Header file used by scm_cred_send.c and scm_cred_recv.c. +*/ +#define _GNU_SOURCE /* To get SCM_CREDENTIALS definition from + <sys/socket.h> */ +#include "af_unix_hdr.h" + +#define SOCK_PATH "/tmp/scm_cred" diff --git a/winsup/cygwin/socket_tests/scm_cred_recv.c b/winsup/cygwin/socket_tests/scm_cred_recv.c new file mode 100644 index 000000000..121b472a4 --- /dev/null +++ b/winsup/cygwin/socket_tests/scm_cred_recv.c @@ -0,0 +1,173 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either version 3 or (at your option) any * +* later version. This program is distributed without any warranty. See * +* the file COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Supplementary program for Chapter 61 */ + +/* scm_cred_recv.c + + Used in conjunction with scm_cred_send.c to demonstrate passing of + process credentials via a UNIX domain socket. + + This program receives credentials sent to a UNIX domain socket. + + Usage is as shown in the usageErr() call below. + + Credentials can be exchanged over stream or datagram sockets. This program + uses stream sockets by default; the "-d" command-line option specifies + that datagram sockets should be used instead. + + This program is Linux-specific. + + See also scm_multi_recv.c. +*/ +#include "scm_cred.h" + +int +main(int argc, char *argv[]) +{ + int data, lfd, sfd, optval, opt; + ssize_t nr; + Boolean useDatagramSocket; + struct msghdr msgh; + struct iovec iov; + struct ucred *ucredp, ucred; + + /* Allocate a char array of suitable size to hold the ancillary data. + However, since this buffer is in reality a 'struct cmsghdr', use a + union to ensure that it is aligned as required for that structure. + Alternatively, we could allocate the buffer using malloc(), which + returns a buffer that satisfies the strictest alignment + requirements of any type */ + + union { + char buf[CMSG_SPACE(sizeof(struct ucred))]; + /* Space large enough to hold a 'ucred' structure */ + struct cmsghdr align; + } controlMsg; + struct cmsghdr *cmsgp; /* Pointer used to iterate through + headers in ancillary data */ + socklen_t len; + + /* Parse command-line options */ + + useDatagramSocket = FALSE; + + while ((opt = getopt(argc, argv, "d")) != -1) { + switch (opt) { + case 'd': + useDatagramSocket = TRUE; + break; + + default: + usageErr("%s [-d]\n" + " -d use datagram socket\n", argv[0]); + } + } + + /* Create socket bound to a well-known address. In the case where + we are using stream sockets, also make the socket a listening + socket and accept a connection on the socket. */ + + if (remove(SOCK_PATH) == -1 && errno != ENOENT) + errExit("remove-%s", SOCK_PATH); + + if (useDatagramSocket) { + sfd = unixBind(SOCK_PATH, SOCK_DGRAM); + if (sfd == -1) + errExit("unixBind"); + + } else { + lfd = unixBind(SOCK_PATH, SOCK_STREAM); + if (lfd == -1) + errExit("unixBind"); + + if (listen(lfd, 5) == -1) + errExit("listen"); + + sfd = accept(lfd, NULL, NULL); + if (sfd == -1) + errExit("accept"); + } + + /* We must set the SO_PASSCRED socket option in order to receive + credentials */ + + optval = 1; + if (setsockopt(sfd, SOL_SOCKET, SO_PASSCRED, &optval, sizeof(optval)) == -1) + errExit("setsockopt"); + + /* The 'msg_name' field can be set to point to a buffer where the + kernel will place the address of the peer socket. However, we don't + need the address of the peer, so we set this field to NULL. */ + + msgh.msg_name = NULL; + msgh.msg_namelen = 0; + + /* Set fields of 'msgh' to point to buffer used to receive (real) + data read by recvmsg() */ + + msgh.msg_iov = &iov; + msgh.msg_iovlen = 1; + iov.iov_base = &data; + iov.iov_len = sizeof(int); + + /* Set 'msgh' fields to describe the ancillary data buffer */ + + msgh.msg_control = controlMsg.buf; + msgh.msg_controllen = sizeof(controlMsg.buf); + + /* Receive real plus ancillary data */ + + nr = recvmsg(sfd, &msgh, 0); + if (nr == -1) + errExit("recvmsg"); + printf("recvmsg() returned %ld\n", (long) nr); + + if (nr > 0) + printf("Received data = %d\n", data); + + /* Get the address of the first 'cmsghdr' in the received + ancillary data */ + + cmsgp = CMSG_FIRSTHDR(&msgh); + + /* Check the validity of the 'cmsghdr' */ + + if (cmsgp == NULL || cmsgp->cmsg_len != CMSG_LEN(sizeof(struct ucred))) + fatal("bad cmsg header / message length"); + if (cmsgp->cmsg_level != SOL_SOCKET) + fatal("cmsg_level != SOL_SOCKET"); + if (cmsgp->cmsg_type != SCM_CREDENTIALS) + fatal("cmsg_type != SCM_CREDENTIALS"); + + /* The data area of the 'cmsghdr' is a 'struct ucred', so assign + the address of the data area to a suitable pointer */ + + ucredp = (struct ucred *) CMSG_DATA(cmsgp); + + /* Display the credentials from the received data area */ + + printf("Received credentials pid=%ld, uid=%ld, gid=%ld\n", + (long) ucredp->pid, (long) ucredp->uid, (long) ucredp->gid); + + /* The Linux-specific, read-only SO_PEERCRED socket option returns + credential information about the peer, as described in socket(7). + This operation can be performed on UNIX domain stream sockets and on + UNIX domain sockets (stream or datagram) created with socketpair(). */ + + len = sizeof(struct ucred); + if (getsockopt(sfd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1) + errExit("getsockopt"); + + printf("Credentials from SO_PEERCRED: pid=%ld, euid=%ld, egid=%ld\n", + (long) ucred.pid, (long) ucred.uid, (long) ucred.gid); + + exit(EXIT_SUCCESS); +} diff --git a/winsup/cygwin/socket_tests/scm_cred_send.c b/winsup/cygwin/socket_tests/scm_cred_send.c new file mode 100644 index 000000000..d023ace67 --- /dev/null +++ b/winsup/cygwin/socket_tests/scm_cred_send.c @@ -0,0 +1,171 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either version 3 or (at your option) any * +* later version. This program is distributed without any warranty. See * +* the file COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Supplementary program for Chapter 61 */ + +/* scm_cred_send.c + + Used in conjunction with scm_cred_recv.c to demonstrate passing of + process credentials via a UNIX domain socket. + + This program sends credentials to a UNIX domain socket. + + Usage is as shown in the usageErr() call below. + + Credentials can be exchanged over stream or datagram sockets. This program + uses stream sockets by default; the "-d" command-line option specifies + that datagram sockets should be used instead. + + This program is Linux-specific. + + See also scm_multi_send.c. +*/ +#include "scm_cred.h" + +int +main(int argc, char *argv[]) +{ + int data, sfd, opt; + ssize_t ns; + Boolean useDatagramSocket, noExplicitCreds; + struct msghdr msgh; + struct iovec iov; + + /* Allocate a char array of suitable size to hold the ancillary data. + However, since this buffer is in reality a 'struct cmsghdr', use a + union to ensure that it is aligned as required for that structure. + Alternatively, we could allocate the buffer using malloc(), which + returns a buffer that satisfies the strictest alignment + requirements of any type */ + + union { + char buf[CMSG_SPACE(sizeof(struct ucred))]; + /* Space large enough to hold a ucred structure */ + struct cmsghdr align; + } controlMsg; + struct cmsghdr *cmsgp; /* Pointer used to iterate through + headers in ancillary data */ + + /* Parse command-line options */ + + useDatagramSocket = FALSE; + noExplicitCreds = FALSE; + + while ((opt = getopt(argc, argv, "dn")) != -1) { + switch (opt) { + case 'd': + useDatagramSocket = TRUE; + break; + + case 'n': + noExplicitCreds = TRUE; + break; + + default: + usageErr("%s [-d] [-n] [data [PID [UID [GID]]]]\n" + " -d use datagram socket\n" + " -n don't construct explicit " + "credentials structure\n", argv[0]); + } + } + + /* The 'msg_name' field can be used to specify the address of the + destination socket when sending a datagram. However, we do not + need to use this field because we use connect() below, which sets + a default outgoing address for datagrams. */ + + msgh.msg_name = NULL; + msgh.msg_namelen = 0; + + /* On Linux, we must transmit at least 1 byte of real data in + order to send ancillary data */ + + msgh.msg_iov = &iov; + msgh.msg_iovlen = 1; + iov.iov_base = &data; + iov.iov_len = sizeof(int); + + /* Data is optionally taken from command line */ + + data = (argc > optind) ? atoi(argv[optind]) : 12345; + fprintf(stderr, "Sending data = %d\n", data); + + if (noExplicitCreds) { + + /* Don't construct an explicit credentials structure. (It is not + necessary to do so, if we just want the receiver to receive + our real credentials.) */ + + printf("Not explicitly sending a credentials structure\n"); + msgh.msg_control = NULL; + msgh.msg_controllen = 0; + + } else { + struct ucred *ucredp; + + /* Set 'msgh' fields to describe the ancillary data buffer */ + + msgh.msg_control = controlMsg.buf; + msgh.msg_controllen = sizeof(controlMsg.buf); + + /* The control message buffer must be zero-initialized in order for the + CMSG_NXTHDR() macro to work correctly. Although we don't need to use + CMSG_NXTHDR() in this example (because there is only one block of + ancillary data), we show this step to demonstrate best practice */ + + memset(controlMsg.buf, 0, sizeof(controlMsg.buf)); + + /* Set message header to describe the ancillary data that + we want to send */ + + cmsgp = CMSG_FIRSTHDR(&msgh); + cmsgp->cmsg_len = CMSG_LEN(sizeof(struct ucred)); + cmsgp->cmsg_level = SOL_SOCKET; + cmsgp->cmsg_type = SCM_CREDENTIALS; + + /* Set 'ucredp' to point to the data area in the 'cmsghdr' */ + + ucredp = (struct ucred *) CMSG_DATA(cmsgp); + + /* Use sender's own PID, real UID, and real GID, unless + alternate values were supplied on the command line */ + + ucredp->pid = getpid(); + if (argc > optind + 1 && strcmp(argv[optind + 1], "-") != 0) + ucredp->pid = atoi(argv[optind + 1]); + + ucredp->uid = getuid(); + if (argc > optind + 2 && strcmp(argv[optind + 2], "-") != 0) + ucredp->uid = atoi(argv[optind + 2]); + + ucredp->gid = getgid(); + if (argc > optind + 3 && strcmp(argv[optind + 3], "-") != 0) + ucredp->gid = atoi(argv[optind + 3]); + + printf("Send credentials pid=%ld, uid=%ld, gid=%ld\n", + (long) ucredp->pid, (long) ucredp->uid, (long) ucredp->gid); + } + + /* Connect to the peer socket */ + + sfd = unixConnect(SOCK_PATH, useDatagramSocket ? SOCK_DGRAM : SOCK_STREAM); + if (sfd == -1) + errExit("unixConnect"); + + /* Send real plus ancillary data */ + + ns = sendmsg(sfd, &msgh, 0); + if (ns == -1) + errExit("sendmsg"); + + printf("sendmsg() returned %ld\n", (long) ns); + + exit(EXIT_SUCCESS); +} diff --git a/winsup/cygwin/socket_tests/scm_multi.h b/winsup/cygwin/socket_tests/scm_multi.h new file mode 100644 index 000000000..af3d503b3 --- /dev/null +++ b/winsup/cygwin/socket_tests/scm_multi.h @@ -0,0 +1,26 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either version 3 or (at your option) any * +* later version. This program is distributed without any warranty. See * +* the file COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Supplementary program for Chapter 61 */ + +/* scm_multi.h + + Header file used by scm_multi_send.c and scm_multi_recv.c. +*/ +#define _GNU_SOURCE /* To get SCM_CREDENTIALS definition from + <sys/socket.h> */ +#include <sys/stat.h> +#include <fcntl.h> +#include "unix_sockets.h" /* Declares our socket functions */ +#include "af_unix_hdr.h" + +#define SOCK_PATH "/tmp/scm_multi" +#define MAX_FDS 1024 /* Maximum number of file descriptors we'll + attempt to exchange in ancillary data */ diff --git a/winsup/cygwin/socket_tests/scm_multi_recv.c b/winsup/cygwin/socket_tests/scm_multi_recv.c new file mode 100644 index 000000000..e861c0976 --- /dev/null +++ b/winsup/cygwin/socket_tests/scm_multi_recv.c @@ -0,0 +1,249 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either version 3 or (at your option) any * +* later version. This program is distributed without any warranty. See * +* the file COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Supplementary program for Chapter 61 */ + +/* scm_multi_recv.c + + Used in conjunction with scm_multi_send.c to demonstrate passing of + ancillary data containing multiple 'msghdr' structures on a UNIX + domain socket. + + Usage is as shown in the usageErr() call below. + + This program uses stream sockets by default; the "-d" command-line option + specifies that datagram sockets should be used instead. + + This program is Linux-specific. +*/ +#define _GNU_SOURCE +#include "scm_multi.h" + +#define BUF_SIZE 100 + +int +main(int argc, char *argv[]) +{ + int data, lfd, sfd, opt, optval, j; + ssize_t NumReceived; + Boolean useDatagramSocket; + int optControlMsgSize; + struct msghdr msgh; + struct iovec iov; + char *controlMsg; /* Ancillary data (control message) */ + size_t controlMsgSize; /* Size of ancillary data */ + struct cmsghdr *cmsgp; /* Pointer used to iterate through + headers in ancillary data */ + struct ucred *ucredp; /* Pointer to data area of a 'cmsghdr' that + contains credentials */ + int *fdList; /* Pointer to data area of a 'cmsghdr' that + contains a list of file descriptors */ + int fdCnt; /* Number of FDs in ancillary data */ + + /* Allocate a buffer of suitable size to hold the ancillary data. + This buffer is in reality treated as a 'struct cmsghdr', + and so needs to be suitably aligned: malloc() provides a block + with suitable alignment. */ + + controlMsgSize = CMSG_SPACE(sizeof(int[MAX_FDS])) + + CMSG_SPACE(sizeof(struct ucred)); + controlMsg = malloc(controlMsgSize); + if (controlMsg == NULL) + errExit("malloc"); + + /* Parse command-line options */ + + useDatagramSocket = FALSE; + optControlMsgSize = -1; + + while ((opt = getopt(argc, argv, "dn:")) != -1) { + switch (opt) { + case 'd': + useDatagramSocket = TRUE; + break; + + case 'n': + optControlMsgSize = atoi(optarg); + break; + + default: + usageErr("%s [-d]\n" + " -d use datagram socket\n" + " -n nbytes limit on size of received " + "ancillary data\n", argv[0]); + } + } + + /* Create socket bound to a well-known address. In the case where + we are using stream sockets, also make the socket a listening + socket and accept a connection on the socket. */ + + if (remove(SOCK_PATH) == -1 && errno != ENOENT) + errExit("remove-%s", SOCK_PATH); + + if (useDatagramSocket) { + sfd = unixBind(SOCK_PATH, SOCK_DGRAM); + if (sfd == -1) + errExit("unixBind"); + + } else { + lfd = unixBind(SOCK_PATH, SOCK_STREAM); + if (lfd == -1) + errExit("unixBind"); + + if (listen(lfd, 5) == -1) + errExit("listen"); + + sfd = accept(lfd, NULL, NULL); + if (sfd == -1) + errExit("accept"); + } + + /* We must set the SO_PASSCRED socket option in order to receive + credentials */ + + optval = 1; + if (setsockopt(sfd, SOL_SOCKET, SO_PASSCRED, &optval, sizeof(optval)) == -1) + errExit("setsockopt"); + + /* The 'msg_name' field can be set to point to a buffer where the + kernel will place the address of the peer socket. However, we don't + need the address of the peer, so we set this field to NULL. */ + + msgh.msg_name = NULL; + msgh.msg_namelen = 0; + + /* Set fields of 'msgh' to point to a buffer used to receive + the (real) data read by recvmsg() */ + + msgh.msg_iov = &iov; + msgh.msg_iovlen = 1; + iov.iov_base = &data; + iov.iov_len = sizeof(int); + + /* Set 'msgh' fields to describe the ancillary data buffer. + + The 'optControlMsgSize' value (specified as a command-line option) + can be used to artifically limit the size of the received ancillary + data. This can be used to demonstrate that when the buffer size is + too small, the list of received file descriptors is truncated, and + the excess file descriptors are automatically closed. */ + + msgh.msg_control = controlMsg; + msgh.msg_controllen = (optControlMsgSize == -1) ? + controlMsgSize : optControlMsgSize; + + /* Receive real plus ancillary data */ + + NumReceived = recvmsg(sfd, &msgh, 0); + if (NumReceived == -1) + errExit("recvmsg"); + + printf("recvmsg() returned %ld\n", (long) NumReceived); + + if (NumReceived > 0) + printf("Received data = %d\n", data); + + if (optControlMsgSize != -1) { + char cbuf[1000]; + + /* Display this process's set of open file descriptors via */ + /* /proc/PID/fd */ + + printf("=================================\n"); + snprintf(cbuf, sizeof(cbuf), "ls -l /proc/%ld/fd", (long) getpid()); + system(cbuf); + printf("=================================\n"); + } + + /* Check to see if the ancillary data was truncated */ + + if (msgh.msg_flags & MSG_CTRUNC) + printf("********** Ancillary data was truncated!!! **********\n"); + + /* Walk through the series of headers in the ancillary data */ + + for (cmsgp = CMSG_FIRSTHDR(&msgh); + cmsgp != NULL; + cmsgp = CMSG_NXTHDR(&msgh, cmsgp)) { + + printf("=================================\n"); + printf("cmsg_len: %ld\n", (long) cmsgp->cmsg_len); + + /* Check that 'cmsg_level' is as expected */ + + if (cmsgp->cmsg_level != SOL_SOCKET) + fatal("cmsg_level != SOL_SOCKET"); + + switch (cmsgp->cmsg_type) { + + case SCM_RIGHTS: /* Header containing file descriptors */ + + /* The number of file descriptors is the size of the control + message block minus the size that would be allocated for + a zero-length data block (i.e., the size of the 'cmsghdr' + structure plus padding), divided by the size of a file + descriptor */ + + fdCnt = (cmsgp->cmsg_len - CMSG_LEN(0)) / sizeof(int); + printf("SCM_RIGHTS: received %d file descriptors\n", fdCnt); + + /* Set 'fdList' to point to the first descriptor in the + control message data */ + + fdList = ((int *) CMSG_DATA(cmsgp)); + + /* For each of the received file descriptors, display the file + descriptor number and read and display the file content */ + + for (j = 0; j < fdCnt; j++) { + printf("--- [%d] Received FD %d\n", j, fdList[j]); + + for (;;) { + char buf[BUF_SIZE]; + ssize_t numRead; + + numRead = read(fdList[j], buf, BUF_SIZE); + if (numRead == -1) + errExit("read"); + + if (numRead == 0) + break; + + write(STDOUT_FILENO, buf, numRead); + } + + if (close(fdList[j]) == -1) + errExit("close"); + } + break; + + case SCM_CREDENTIALS: /* Header containing credentials */ + + /* Check validity of the 'cmsghdr' */ + + if (cmsgp->cmsg_len != CMSG_LEN(sizeof(struct ucred))) + fatal("cmsg data has incorrect size"); + + /* The data in this control message block is a 'struct ucred' */ + + ucredp = (struct ucred *) CMSG_DATA(cmsgp); + printf("SCM_CREDENTIALS: pid=%ld, uid=%ld, gid=%ld\n", + (long) ucredp->pid, (long) ucredp->uid, + (long) ucredp->gid); + break; + + default: + fatal("Bad cmsg_type (%d)", cmsgp->cmsg_type); + } + } + + exit(EXIT_SUCCESS); +} diff --git a/winsup/cygwin/socket_tests/scm_multi_send.c b/winsup/cygwin/socket_tests/scm_multi_send.c new file mode 100644 index 000000000..21151a86f --- /dev/null +++ b/winsup/cygwin/socket_tests/scm_multi_send.c @@ -0,0 +1,226 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either version 3 or (at your option) any * +* later version. This program is distributed without any warranty. See * +* the file COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Supplementary program for Chapter 61 */ + +/* scm_multi_send.c + + Used in conjunction with scm_multi_recv.c to demonstrate passing of + ancillary data containing multiple 'msghdr' structures on a UNIX + domain socket. + + This program sends ancillary data consisting of two blocks. One block + contains process credentials (SCM_CREDENTIALS) and the other contains + one or more file descriptors (SCM_RIGHTS). + + Usage is as shown below in usageError(). + + This program uses stream sockets by default; the "-d" command-line option + specifies that datagram sockets should be used instead. + + This program is Linux-specific. +*/ +#define _GNU_SOURCE +#include "scm_multi.h" + +static void +usageError(char *pname) +{ + fprintf(stderr, "Usage: %s [options] file...\n", pname); + fprintf(stderr, " Options:\n"); + fprintf(stderr, "\t-d Use datagram (instead of stream) socket\n"); + fprintf(stderr, "\t-n Don't send any real data with the " + "ancillary data\n"); + fprintf(stderr, "\t-p <pid> Use this PID when sending credentials\n"); + fprintf(stderr, "\t-u <uid> Use this UID when sending credentials\n"); + fprintf(stderr, "\t-g <gid> Use this GID when sending credentials\n"); + fprintf(stderr, " If any of any of -p/-u/-g is absent, the " + "corresponding real\n credential is used.\n"); + exit(EXIT_FAILURE); +} + +int +main(int argc, char *argv[]) +{ + int data, sfd, opt, j; + pid_t pid; + uid_t uid; + gid_t gid; + ssize_t numSent; + Boolean useDatagramSocket, sendData; + struct msghdr msgh; + struct iovec iov; + struct ucred *ucredp; /* Pointer to data area of a 'cmsghdr' that + contains credentials */ + int *fdList; /* Pointer to data area of a 'cmsghdr' that + contains a list of file descriptors */ + int fdCnt; /* Number of FDs in ancillary data */ + char *controlMsg; /* Ancillary data (control message) */ + size_t controlMsgSize; /* Size of ancillary data */ + struct cmsghdr *cmsgp; /* Pointer used to iterate through + headers in ancillary data */ + + /* By default, this program sends an SCM_CREDENTIALS message containing + the process's real credentials. This can be altered via command-line + options. */ + + pid = getpid(); + uid = getuid(); + gid = getgid(); + + /* Parse command-line options */ + + useDatagramSocket = FALSE; + sendData = TRUE; + + while ((opt = getopt(argc, argv, "dnp:u:g:")) != -1) { + switch (opt) { + case 'd': + useDatagramSocket = TRUE; + break; + + case 'n': + sendData = FALSE; + break; + + case 'p': + pid = atoi(optarg); + break; + + case 'u': + uid = atoi(optarg); + break; + + case 'g': + gid = atoi(optarg); + break; + + default: + usageError(argv[0]); + } + } + + fdCnt = argc - optind; + if (fdCnt <= 0) + usageError(argv[0]); + + /* Allocate a buffer of suitable size to hold the ancillary data. + This buffer is in reality treated as a 'struct cmsghdr', + and so needs to be suitably aligned: malloc() provides a block + with suitable alignment. */ + + controlMsgSize = CMSG_SPACE(sizeof(int) * fdCnt) + + CMSG_SPACE(sizeof(struct ucred)); + controlMsg = malloc(controlMsgSize); + if (controlMsg == NULL) + errExit("malloc"); + + /* The control message buffer must be zero-initialized in order for + the CMSG_NXTHDR() macro to work correctly */ + + memset(controlMsg, 0, controlMsgSize); + + /* The 'msg_name' field can be used to specify the address of the + destination socket when sending a datagram. However, we do not + need to use this field because we use connect() below, which sets + a default outgoing address for datagrams. */ + + msgh.msg_name = NULL; + msgh.msg_namelen = 0; + + /* On Linux, we must transmit at least 1 byte of real data in + order to send ancillary data, at least when using stream sockets. + The following allows for testing the results if no real data is + sent with the ancillary data. */ + + if (sendData) { + msgh.msg_iov = &iov; + msgh.msg_iovlen = 1; + iov.iov_base = &data; + iov.iov_len = sizeof(int); + data = 12345; + } else { + msgh.msg_iov = NULL; + msgh.msg_iovlen = 0; + } + + /* Place a pointer to the ancillary data, and size of that data, + in the 'msghdr' structure that will be passed to sendmsg() */ + + msgh.msg_control = controlMsg; + msgh.msg_controllen = controlMsgSize; + + /* Set message header to describe the ancillary data that + we want to send */ + + /* First, the file descriptor list */ + + cmsgp = CMSG_FIRSTHDR(&msgh); + cmsgp->cmsg_level = SOL_SOCKET; + cmsgp->cmsg_type = SCM_RIGHTS; + + /* The ancillary message must include space for the required number + of file descriptors */ + + cmsgp->cmsg_len = CMSG_LEN(sizeof(int) * fdCnt); + printf("cmsg_len 1: %ld\n", (long) cmsgp->cmsg_len); + + /* Set 'fdList' pointing to the data area of this ancillary message block. + The file descriptrs are placed into the data block by the loop below. */ + + fdList = (int *) CMSG_DATA(cmsgp); + + /* Next, the credentials */ + + cmsgp = CMSG_NXTHDR(&msgh, cmsgp); + cmsgp->cmsg_level = SOL_SOCKET; + cmsgp->cmsg_type = SCM_CREDENTIALS; + + /* The ancillary message must include space for a 'struct ucred' */ + + cmsgp->cmsg_len = CMSG_LEN(sizeof(struct ucred)); + printf("cmsg_len 2: %ld\n", (long) cmsgp->cmsg_len); + + /* Set 'ucredp' pointing to the data area of this ancillary message block. + The credentials are placed into the data area by code below. */ + + ucredp = (struct ucred *) CMSG_DATA(cmsgp); + + /* Initialize the credentials inside the ancillary data */ + + ucredp->pid = pid; + ucredp->uid = uid; + ucredp->gid = gid; + + /* Open the files named on the command line, placing the returned file + descriptors into the ancillary data */ + + for (j = 0; j < fdCnt; j++) { + fdList[j] = open(argv[optind + j], O_RDONLY); + if (fdList[j] == -1) + errExit("open"); + } + + /* Connect to the peer socket */ + + sfd = unixConnect(SOCK_PATH, useDatagramSocket ? SOCK_DGRAM : SOCK_STREAM); + if (sfd == -1) + errExit("unixConnect"); + + /* Send the data plus ancillary data */ + + numSent = sendmsg(sfd, &msgh, 0); + if (numSent == -1) + errExit("sendmsg"); + + printf("sendmsg() returned %ld\n", (long) numSent); + + exit(EXIT_SUCCESS); +} diff --git a/winsup/cygwin/socket_tests/scm_rights.h b/winsup/cygwin/socket_tests/scm_rights.h new file mode 100644 index 000000000..fb779537a --- /dev/null +++ b/winsup/cygwin/socket_tests/scm_rights.h @@ -0,0 +1,21 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either version 3 or (at your option) any * +* later version. This program is distributed without any warranty. See * +* the file COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Supplementary program for Chapter 61 */ + +/* scm_rights.h + + Header file used by scm_rights_send.c and scm_rights_recv.c. +*/ +#include <fcntl.h> +#include "unix_sockets.h" /* Declares our unix*() socket functions */ +#include "af_unix_hdr.h" + +#define SOCK_PATH "/tmp/scm_rights" diff --git a/winsup/cygwin/socket_tests/scm_rights_recv.c b/winsup/cygwin/socket_tests/scm_rights_recv.c new file mode 100644 index 000000000..4a23a2d99 --- /dev/null +++ b/winsup/cygwin/socket_tests/scm_rights_recv.c @@ -0,0 +1,169 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either version 3 or (at your option) any * +* later version. This program is distributed without any warranty. See * +* the file COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Supplementary program for Chapter 61 */ + +/* scm_rights_recv.c + + Used in conjunction with scm_rights_send.c to demonstrate passing of + file descriptors via a UNIX domain socket. + + This program receives a file descriptor sent to a UNIX domain socket. + + Usage is as shown in the usageErr() call below. + + File descriptors can be exchanged over stream or datagram sockets. This + program uses stream sockets by default; the "-d" command-line option + specifies that datagram sockets should be used instead. + + This program is Linux-specific. + + See also scm_multi_recv.c. +*/ +#include "scm_rights.h" + +#define BUF_SIZE 100 + +int +main(int argc, char *argv[]) +{ + int data, lfd, sfd, fd, opt; + ssize_t nr; + Boolean useDatagramSocket; + struct msghdr msgh; + struct iovec iov; + + /* Allocate a char array of suitable size to hold the ancillary data. + However, since this buffer is in reality a 'struct cmsghdr', use a + union to ensure that it is aligned as required for that structure. + Alternatively, we could allocate the buffer using malloc(), which + returns a buffer that satisfies the strictest alignment + requirements of any type */ + + union { + char buf[CMSG_SPACE(sizeof(int))]; + /* Space large enough to hold an 'int' */ + struct cmsghdr align; + } controlMsg; + struct cmsghdr *cmsgp; /* Pointer used to iterate through + headers in ancillary data */ + + /* Parse command-line options */ + + useDatagramSocket = FALSE; + + while ((opt = getopt(argc, argv, "d")) != -1) { + switch (opt) { + case 'd': + useDatagramSocket = TRUE; + break; + + default: + usageErr("%s [-d]\n" + " -d use datagram socket\n", argv[0]); + } + } + + /* Create socket bound to a well-known address. In the case where + we are using stream sockets, also make the socket a listening + socket and accept a connection on the socket. */ + + if (remove(SOCK_PATH) == -1 && errno != ENOENT) + errExit("remove-%s", SOCK_PATH); + + if (useDatagramSocket) { + sfd = unixBind(SOCK_PATH, SOCK_DGRAM); + if (sfd == -1) + errExit("unixBind"); + + } else { + lfd = unixBind(SOCK_PATH, SOCK_STREAM); + if (lfd == -1) + errExit("unixBind"); + + if (listen(lfd, 5) == -1) + errExit("listen"); + + sfd = accept(lfd, NULL, NULL); + if (sfd == -1) + errExit("accept"); + } + + /* The 'msg_name' field can be set to point to a buffer where the + kernel will place the address of the peer socket. However, we don't + need the address of the peer, so we set this field to NULL. */ + + msgh.msg_name = NULL; + msgh.msg_namelen = 0; + + /* Set fields of 'msgh' to point to buffer used to receive the (real) + data read by recvmsg() */ + + msgh.msg_iov = &iov; + msgh.msg_iovlen = 1; + iov.iov_base = &data; + iov.iov_len = sizeof(int); + + /* Set 'msgh' fields to describe the ancillary data buffer */ + + msgh.msg_control = controlMsg.buf; + msgh.msg_controllen = sizeof(controlMsg.buf); + + /* Receive real plus ancillary data */ + + nr = recvmsg(sfd, &msgh, 0); + if (nr == -1) + errExit("recvmsg"); + fprintf(stderr, "recvmsg() returned %ld\n", (long) nr); + + if (nr > 0) + fprintf(stderr, "Received data = %d\n", data); + + /* Get the address of the first 'cmsghdr' in the received + ancillary data */ + + cmsgp = CMSG_FIRSTHDR(&msgh); + + /* Check the validity of the 'cmsghdr' */ + + if (cmsgp == NULL || cmsgp->cmsg_len != CMSG_LEN(sizeof(int))) + fatal("bad cmsg header / message length"); + if (cmsgp->cmsg_level != SOL_SOCKET) + fatal("cmsg_level != SOL_SOCKET"); + if (cmsgp->cmsg_type != SCM_RIGHTS) + fatal("cmsg_type != SCM_RIGHTS"); + + /* The data area of the 'cmsghdr' is an 'int', so assign + the address of the data area to a suitable pointer. The data + is the received file descriptor (which is typically a different + file descriptor number than was used in the sending process). */ + + fd = *((int *) CMSG_DATA(cmsgp)); + fprintf(stderr, "Received FD %d\n", fd); + + /* Having obtained the file descriptor, read the file's contents and + print them on standard output */ + + for (;;) { + char buf[BUF_SIZE]; + ssize_t numRead; + + numRead = read(fd, buf, BUF_SIZE); + if (numRead == -1) + errExit("read"); + + if (numRead == 0) + break; + + write(STDOUT_FILENO, buf, numRead); + } + + exit(EXIT_SUCCESS); +} diff --git a/winsup/cygwin/socket_tests/scm_rights_send.c b/winsup/cygwin/socket_tests/scm_rights_send.c new file mode 100644 index 000000000..a0ca76672 --- /dev/null +++ b/winsup/cygwin/socket_tests/scm_rights_send.c @@ -0,0 +1,138 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either version 3 or (at your option) any * +* later version. This program is distributed without any warranty. See * +* the file COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Supplementary program for Chapter 61 */ + +/* scm_rights_send.c + + Used in conjunction with scm_rights_recv.c to demonstrate passing of + file descriptors via a UNIX domain socket. + + This program sends a file descriptor to a UNIX domain socket. + + Usage is as shown in the usageErr() call below. + + File descriptors can be exchanged over stream or datagram sockets. This + program uses stream sockets by default; the "-d" command-line option + specifies that datagram sockets should be used instead. + + This program is Linux-specific. + + See also scm_multi_recv.c. +*/ +#include "scm_rights.h" + +int +main(int argc, char *argv[]) +{ + int data, sfd, opt, fd; + ssize_t ns; + Boolean useDatagramSocket; + struct msghdr msgh; + struct iovec iov; + + /* Allocate a char array of suitable size to hold the ancillary data. + However, since this buffer is in reality a 'struct cmsghdr', use a + union to ensure that it is aligned as required for that structure. + Alternatively, we could allocate the buffer using malloc(), which + returns a buffer that satisfies the strictest alignment + requirements of any type. */ + + union { + char buf[CMSG_SPACE(sizeof(int))]; + /* Space large enough to hold an 'int' */ + struct cmsghdr align; + } controlMsg; + struct cmsghdr *cmsgp; /* Pointer used to iterate through + headers in ancillary data */ + + /* Parse command-line options */ + + useDatagramSocket = FALSE; + + while ((opt = getopt(argc, argv, "d")) != -1) { + switch (opt) { + case 'd': + useDatagramSocket = TRUE; + break; + + default: + usageErr("%s [-d] file\n" + " -d use datagram socket\n", argv[0]); + } + } + + if (argc != optind + 1) + usageErr("%s [-d] file\n", argv[0]); + + /* Open the file named on the command line */ + + fd = open(argv[optind], O_RDONLY); + if (fd == -1) + errExit("open"); + + /* The 'msg_name' field can be used to specify the address of the + destination socket when sending a datagram. However, we do not + need to use this field because we use connect() below, which sets + a default outgoing address for datagrams. */ + + msgh.msg_name = NULL; + msgh.msg_namelen = 0; + + /* On Linux, we must transmit at least 1 byte of real data in + order to send ancillary data */ + + msgh.msg_iov = &iov; + msgh.msg_iovlen = 1; + iov.iov_base = &data; + iov.iov_len = sizeof(int); + data = 12345; + fprintf(stderr, "Sending data = %d\n", data); + + /* Set 'msgh' fields to describe the ancillary data buffer */ + + msgh.msg_control = controlMsg.buf; + msgh.msg_controllen = sizeof(controlMsg.buf); + + /* The control message buffer must be zero-initialized in order + for the CMSG_NXTHDR() macro to work correctly. Although we + don't need to use CMSG_NXTHDR() in this example (because + there is only one block of ancillary data), we show this + step to demonstrate best practice */ + + memset(controlMsg.buf, 0, sizeof(controlMsg.buf)); + + /* Set message header to describe the ancillary data that + we want to send */ + + cmsgp = CMSG_FIRSTHDR(&msgh); + cmsgp->cmsg_len = CMSG_LEN(sizeof(int)); + cmsgp->cmsg_level = SOL_SOCKET; + cmsgp->cmsg_type = SCM_RIGHTS; + *((int *) CMSG_DATA(cmsgp)) = fd; + + /* Connect to the peer socket */ + + sfd = unixConnect(SOCK_PATH, useDatagramSocket ? SOCK_DGRAM : SOCK_STREAM); + if (sfd == -1) + errExit("unixConnect"); + + fprintf(stderr, "Sending FD %d\n", fd); + + /* Send real plus ancillary data */ + + ns = sendmsg(sfd, &msgh, 0); + if (ns == -1) + errExit("sendmsg"); + + fprintf(stderr, "sendmsg() returned %ld\n", (long) ns); + + exit(EXIT_SUCCESS); +} diff --git a/winsup/cygwin/socket_tests/select_cl.c b/winsup/cygwin/socket_tests/select_cl.c new file mode 100644 index 000000000..44aebd45c --- /dev/null +++ b/winsup/cygwin/socket_tests/select_cl.c @@ -0,0 +1,54 @@ +#include "select_test.h" + +int +main () +{ + int sfd, flags; + fd_set writefds; + size_t nwritten = 0; + ssize_t nw; + char buf[BUF_SIZE]; + + if ((sfd = unixConnect (SV_SOCK_PATH, SOCK_STREAM)) < 0) + errExit ("unixConnect"); + flags = fcntl (sfd, F_GETFL); + if (fcntl (sfd, F_SETFL, flags | O_NONBLOCK) < 0) + errExit ("fcntl"); + + printf ("waiting for socket to be ready for write...\n"); + FD_ZERO (&writefds); + FD_SET (sfd, &writefds); + if (select (sfd + 1, NULL, &writefds, NULL, NULL) < 0) + errExit ("select"); + if (FD_ISSET (sfd, &writefds)) + printf ("ready for write, writing until buffer full\n"); + else + errExit ("something's wrong"); + while (1) + { + nw = write (sfd, buf, BUF_SIZE); + if (nw < 0) + { + if (errno == EAGAIN) + { + printf ("buffer full\n"); + break; + } + else + errExit ("write"); + } + nwritten += nw; + } + printf ("wrote %zu bytes\n", nwritten); + printf ("waiting for write ready again...\n"); + FD_ZERO (&writefds); + FD_SET (sfd, &writefds); + if (select (sfd + 1, NULL, &writefds, NULL, NULL) < 0) + errExit ("select"); + if (FD_ISSET (sfd, &writefds)) + printf ("ready for write, writing once more\n"); + if ((nw = write (sfd, buf, BUF_SIZE)) < 0) + errExit ("write"); + nwritten += nw; + printf ("wrote %zd more bytes for a total of %zu\n", nw, nwritten); +} diff --git a/winsup/cygwin/socket_tests/select_sv.c b/winsup/cygwin/socket_tests/select_sv.c new file mode 100644 index 000000000..e9cf6dc8c --- /dev/null +++ b/winsup/cygwin/socket_tests/select_sv.c @@ -0,0 +1,59 @@ +#include "select_test.h" + +int +main () +{ + int sfd, cfd, flags; + fd_set readfds; + size_t nread = 0; + char buf[BUF_SIZE]; + + if (remove (SV_SOCK_PATH) < 0 && errno != ENOENT) + errExit ("remove"); + + if ((sfd = unixBind (SV_SOCK_PATH, SOCK_STREAM)) < 0) + errExit ("unixBind"); + + if (listen (sfd, BACKLOG) < 0) + errExit ("listen"); + + printf ("waiting for connection request...\n"); + FD_ZERO (&readfds); + FD_SET (sfd, &readfds); + if (select (sfd + 1, &readfds, NULL, NULL, NULL) < 0) + errExit ("select"); + if (FD_ISSET (sfd, &readfds)) + printf ("connection request received; accepting\n"); + else + errExit ("something's wrong"); + cfd = accept (sfd, NULL, NULL); + if (cfd < 0) + errExit ("accept"); + + flags = fcntl (cfd, F_GETFL); + if (fcntl (cfd, F_SETFL, flags | O_NONBLOCK) < 0) + errExit ("fcntl"); + + printf ("slowly reading from socket...\n"); + while (1) + { + FD_ZERO (&readfds); + FD_SET (cfd, &readfds); + if (select (cfd + 1, &readfds, NULL, NULL, NULL) < 0) + errExit ("select"); + if (!FD_ISSET (cfd, &readfds)) + errExit ("something's wrong"); + ssize_t nr = read (cfd, buf, 10); + if (nr < 0) + { + if (errno == EPIPE) + break; + else + errExit ("read"); + } + else if (nr == 0) + break; + nread += nr; + } + printf ("read %zu bytes\n", nread); +} diff --git a/winsup/cygwin/socket_tests/select_test.h b/winsup/cygwin/socket_tests/select_test.h new file mode 100644 index 000000000..67a85ca37 --- /dev/null +++ b/winsup/cygwin/socket_tests/select_test.h @@ -0,0 +1,11 @@ +/* Header for select_sv.c and select_cl.c */ + +#include "af_unix_hdr.h" +#include <sys/select.h> +#include <fcntl.h> + +#define SV_SOCK_PATH "/tmp/select" + +#define BUF_SIZE 65527 /* MAX_AF_PKT_LEN - sizeof (af_unix_pkt_hdr_t) */ + +#define BACKLOG 5 diff --git a/winsup/cygwin/socket_tests/send_pty_master.c b/winsup/cygwin/socket_tests/send_pty_master.c new file mode 100644 index 000000000..b1595e67f --- /dev/null +++ b/winsup/cygwin/socket_tests/send_pty_master.c @@ -0,0 +1,143 @@ +/* Adapted from Kerrisk's script.c by Ken Brown */ + +/* + Create a pty pair and fork/exec a shell running on the slave. Send + the master fd across an AF_UNIX socket to a process running + recv_pty_slave. +*/ + +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either version 3 or (at your option) any * +* later version. This program is distributed without any warranty. See * +* the file COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Listing 64-3 */ + +/* script.c + + A simple version of script(1). +*/ +#include <sys/stat.h> +#include <fcntl.h> +#include <libgen.h> +#include <termios.h> +#if ! defined(__hpux) +/* HP-UX 11 doesn't have this header file */ +#include <sys/select.h> +#endif +#include "pty_fork.h" /* Declaration of ptyFork() */ +#include "tty_functions.h" /* Declaration of ttySetRaw() */ +#include "af_unix_hdr.h" +#include "pty_master.h" + +#define BUF_SIZE 256 +#define MAX_SNAME 1000 + +struct termios ttyOrig; + +static void /* Reset terminal mode on program exit */ +ttyReset(void) +{ + if (tcsetattr(STDIN_FILENO, TCSANOW, &ttyOrig) == -1) + errExit("tcsetattr"); +} + +int +main(int argc, char *argv[]) +{ + char slaveName[MAX_SNAME]; + char *shell; + int masterFd, connFd, junk; + struct winsize ws; + fd_set inFds; + char buf[BUF_SIZE]; + ssize_t numRead; + pid_t childPid; + + /* Retrieve the attributes of terminal on which we are started */ + + if (tcgetattr(STDIN_FILENO, &ttyOrig) == -1) + errExit("tcgetattr"); + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0) + errExit("ioctl-TIOCGWINSZ"); + + /* Create a child process, with parent and child connected via a + pty pair. The child is connected to the pty slave and its terminal + attributes are set to be the same as those retrieved above. */ + + childPid = ptyFork(&masterFd, slaveName, MAX_SNAME, &ttyOrig, &ws); + if (childPid == -1) + errExit("ptyFork"); + + if (childPid == 0) { /* Child: execute a shell on pty slave */ + + /* If the SHELL variable is set, use its value to determine + the shell execed in child. Otherwise use /bin/sh. */ + + shell = getenv("SHELL"); + if (shell == NULL || *shell == '\0') + shell = "/bin/sh"; + + execlp(shell, shell, (char *) NULL); + errExit("execlp"); /* If we get here, something went wrong */ + } + + /* Parent */ + + if ((connFd = unixConnect (SOCK_PATH, SOCK_STREAM)) < 0) + errExit ("unixConnect"); + + /* Send master fd across the socket. */ + if (sendfd (connFd, masterFd) < 0) + errExit ("sendfd"); + + /* Place terminal in raw mode so that we can pass all terminal + input to the pseudoterminal master untouched */ + + ttySetRaw(STDIN_FILENO, &ttyOrig); + + if (atexit(ttyReset) != 0) + errExit("atexit"); + + /* Loop monitoring terminal and pty master for input. If the + terminal is ready for input, then read some bytes and write + them to the pty master. If the pty master is ready for input, + then read some bytes and write them to the terminal. */ + + for (;;) { + FD_ZERO(&inFds); + FD_SET(STDIN_FILENO, &inFds); + FD_SET(masterFd, &inFds); + + if (select(masterFd + 1, &inFds, NULL, NULL, NULL) == -1) + errExit("select"); + + if (FD_ISSET(STDIN_FILENO, &inFds)) { /* stdin --> pty */ + numRead = read(STDIN_FILENO, buf, BUF_SIZE); + if (numRead <= 0) + break; + + if (write(masterFd, buf, numRead) != numRead) + fatal("partial/failed write (masterFd)"); + } + + if (FD_ISSET(masterFd, &inFds)) { /* pty --> stdout */ + numRead = read(masterFd, buf, BUF_SIZE); + if (numRead <= 0) + break; + + if (write(STDOUT_FILENO, buf, numRead) != numRead) + fatal("partial/failed write (STDOUT_FILENO)"); + } + } + /* Notify receiver that we're done. */ + if (write (connFd, &junk, sizeof junk) != sizeof junk) + errMsg ("write"); + if (close (connFd) < 0) + errMsg ("close"); +} diff --git a/winsup/cygwin/socket_tests/send_pty_slave.c b/winsup/cygwin/socket_tests/send_pty_slave.c new file mode 100644 index 000000000..606b8a529 --- /dev/null +++ b/winsup/cygwin/socket_tests/send_pty_slave.c @@ -0,0 +1,144 @@ +/* Adapted from Kerrisk's script.c by Ken Brown */ + +/* + Create a pty pair and fork/exec a shell running on the slave. Send + the slave fd across an AF_UNIX socket to a process running + recv_pty_slave. +*/ + +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either version 3 or (at your option) any * +* later version. This program is distributed without any warranty. See * +* the file COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Listing 64-3 */ + +/* script.c + + A simple version of script(1). +*/ +#include <sys/stat.h> +#include <fcntl.h> +#include <libgen.h> +#include <termios.h> +#if ! defined(__hpux) +/* HP-UX 11 doesn't have this header file */ +#include <sys/select.h> +#endif +#include "pty_fork.h" /* Declaration of ptyFork() */ +#include "tty_functions.h" /* Declaration of ttySetRaw() */ +#include "af_unix_hdr.h" +#include "pty_slave.h" + +#define BUF_SIZE 256 +#define MAX_SNAME 1000 + +struct termios ttyOrig; + +static void /* Reset terminal mode on program exit */ +ttyReset(void) +{ + if (tcsetattr(STDIN_FILENO, TCSANOW, &ttyOrig) == -1) + errExit("tcsetattr"); +} + +int +main(int argc, char *argv[]) +{ + char slaveName[MAX_SNAME]; + char *shell; + int masterFd, slaveFd, connFd; + struct winsize ws; + fd_set inFds; + char buf[BUF_SIZE]; + ssize_t numRead; + pid_t childPid; + + /* Retrieve the attributes of terminal on which we are started */ + + if (tcgetattr(STDIN_FILENO, &ttyOrig) == -1) + errExit("tcgetattr"); + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0) + errExit("ioctl-TIOCGWINSZ"); + + /* Create a child process, with parent and child connected via a + pty pair. The child is connected to the pty slave and its terminal + attributes are set to be the same as those retrieved above. */ + + childPid = ptyFork(&masterFd, slaveName, MAX_SNAME, &ttyOrig, &ws); + if (childPid == -1) + errExit("ptyFork"); + + if (childPid == 0) { /* Child: execute a shell on pty slave */ + + /* If the SHELL variable is set, use its value to determine + the shell execed in child. Otherwise use /bin/sh. */ + + shell = getenv("SHELL"); + if (shell == NULL || *shell == '\0') + shell = "/bin/sh"; + + execlp(shell, shell, (char *) NULL); + errExit("execlp"); /* If we get here, something went wrong */ + } + + /* Parent */ + + if ((connFd = unixConnect (SOCK_PATH, SOCK_STREAM)) < 0) + errExit ("unixConnect"); + + /* Open slave and send its fd across the socket. */ + if ((slaveFd = open (slaveName, O_RDWR | O_NOCTTY)) < 0) + errExit ("open"); + if (sendfd (connFd, slaveFd) < 0) + errExit ("sendfd"); + if (close (slaveFd) < 0) + errMsg ("close"); + if (close (connFd) < 0) + errMsg ("close"); + + /* Place terminal in raw mode so that we can pass all terminal + input to the pseudoterminal master untouched */ + + ttySetRaw(STDIN_FILENO, &ttyOrig); + + if (atexit(ttyReset) != 0) + errExit("atexit"); + + /* Loop monitoring terminal and pty master for input. If the + terminal is ready for input, then read some bytes and write + them to the pty master. If the pty master is ready for input, + then read some bytes and write them to the terminal. */ + + for (;;) { + FD_ZERO(&inFds); + FD_SET(STDIN_FILENO, &inFds); + FD_SET(masterFd, &inFds); + + if (select(masterFd + 1, &inFds, NULL, NULL, NULL) == -1) + errExit("select"); + + if (FD_ISSET(STDIN_FILENO, &inFds)) { /* stdin --> pty */ + numRead = read(STDIN_FILENO, buf, BUF_SIZE); + if (numRead <= 0) + exit(EXIT_SUCCESS); + + if (write(masterFd, buf, numRead) != numRead) + fatal("partial/failed write (masterFd)"); + } + + if (FD_ISSET(masterFd, &inFds)) { /* pty --> stdout */ + numRead = read(masterFd, buf, BUF_SIZE); + if (numRead <= 0) + exit(EXIT_SUCCESS); + + if (write(STDOUT_FILENO, buf, numRead) != numRead) + fatal("partial/failed write (STDOUT_FILENO)"); + } + } +} diff --git a/winsup/cygwin/socket_tests/send_pty_slave_fork.c b/winsup/cygwin/socket_tests/send_pty_slave_fork.c new file mode 100644 index 000000000..a88715035 --- /dev/null +++ b/winsup/cygwin/socket_tests/send_pty_slave_fork.c @@ -0,0 +1,102 @@ +/* + Fork a subprocess, open a pty pair, and send the pty slave file + descriptor to the subprocess over an AF_UNIX socket. Invoke with + --debug to allow time to attach gdb to the child. +*/ + +#include "af_unix_hdr.h" +#include "pty_master_open.h" +#include "read_line.h" +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#define BUF_SIZE 100 +#define MAX_SNAME 100 /* Maximum size for pty slave name */ + +int +main(int argc, char *argv[]) +{ + Boolean debug = FALSE; + pid_t pid; + int pipefd[2]; + int pfd; /* parent's end */ + int cfd; /* child's end */ + + if (argc > 1 && strcmp (argv[1], "--debug") == 0) + debug = TRUE; + + if (socketpair (AF_UNIX, SOCK_STREAM, 0, pipefd) < 0) + errExit ("socketpair"); + pfd = pipefd[1]; + cfd = pipefd[0]; + + if ((pid = fork ()) < 0) + errExit ("fork"); + else if (pid > 0) /* parent */ + { + int mfd, sfd, junk; + char slname[MAX_SNAME]; + + if (close (cfd) < 0) + errExit ("close"); + if ((mfd = ptyMasterOpen (slname, MAX_SNAME)) < 0) + errExit ("ptyMasterOpen"); + if ((sfd = open (slname, O_RDWR | O_NOCTTY)) < 0) + errExit ("open"); + if (debug) + { + printf ("parent pid %d, child pid %d, sleeping...\n", getpid (), pid); + sleep (30); + } + + printf ("parent sending descriptor %d for %s to child\n", sfd, slname); + if (sendfd (pfd, sfd) < 0) + errExit ("sendfd"); + if (close (sfd) < 0) + errMsg ("close"); + if (write (mfd, "hello\n", 6) < 0) + errExit ("write"); + /* Wait for child. */ + if (read (pfd, &junk, sizeof junk) != sizeof junk) + errMsg ("read"); + if (close (pfd) < 0) + errMsg ("close"); + if (close (mfd) < 0) + errMsg ("close"); + } + else /* child */ + { + int fd, junk; + ssize_t nr; + char buf[BUF_SIZE]; + + if (close (pfd) < 0) + errExit ("close"); + if (debug) + sleep (30); + + /* Read fd from parent. */ + fd = recvfd (cfd); + if (fd < 0) + errExit ("recvfd"); + + /* Read a line from fd. */ + if ((nr = readLine (fd, buf, BUF_SIZE)) < 0) + { + close (fd); + errExit ("readLine"); + } + + /* Kill newline. */ + buf[nr - 1] = '\0'; + printf ("child read %zd bytes (including newline) from fd %d: %s\n", nr, + fd, buf); + if (close (fd) == -1) + errMsg ("close"); + /* Tell parent we're done. */ + if (write (cfd, &junk, sizeof junk) != sizeof junk) + errMsg ("write"); + if (close (cfd) < 0) + errExit ("close"); + } +} diff --git a/winsup/cygwin/socket_tests/ud_ucase.h b/winsup/cygwin/socket_tests/ud_ucase.h new file mode 100644 index 000000000..55cac451a --- /dev/null +++ b/winsup/cygwin/socket_tests/ud_ucase.h @@ -0,0 +1,32 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either version 3 or (at your option) any * +* later version. This program is distributed without any warranty. See * +* the file COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Listing 57-5 */ + +/* ud_ucase.h + + Header file for ud_ucase_sv.c and ud_ucase_cl.c. + + These programs employ sockets in /tmp. This makes it easy to compile + and run the programs. However, for a security reasons, a real-world + application should never create sensitive files in /tmp. (As a simple of + example of the kind of security problems that can result, a malicious + user could create a file using the name defined in SV_SOCK_PATH, and + thereby cause a denial of service attack against this application. + See Section 38.7 of "The Linux Programming Interface" for more details + on this subject.) +*/ +#include <ctype.h> +#include "af_unix_hdr.h" + +#define BUF_SIZE 10 /* Maximum size of messages exchanged + between client and server */ + +#define SV_SOCK_PATH "/tmp/ud_ucase" diff --git a/winsup/cygwin/socket_tests/ud_ucase_cl.c b/winsup/cygwin/socket_tests/ud_ucase_cl.c new file mode 100644 index 000000000..7d60f4077 --- /dev/null +++ b/winsup/cygwin/socket_tests/ud_ucase_cl.c @@ -0,0 +1,71 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either version 3 or (at your option) any * +* later version. This program is distributed without any warranty. See * +* the file COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Listing 57-7 */ + +/* ud_ucase_cl.c + + A UNIX domain client that communicates with the server in ud_ucase_sv.c. + This client sends each command-line argument as a datagram to the server, + and then displays the contents of the server's response datagram. +*/ +#include "ud_ucase.h" + +int +main(int argc, char *argv[]) +{ + struct sockaddr_un svaddr, claddr; + int sfd, j; + size_t msgLen; + ssize_t numBytes; + char resp[BUF_SIZE]; + + if (argc < 2 || strcmp(argv[1], "--help") == 0) + usageErr("%s msg...\n", argv[0]); + + /* Create client socket; bind to unique pathname (based on PID) */ + + sfd = socket(AF_UNIX, SOCK_DGRAM, 0); + if (sfd == -1) + errExit("socket"); + + memset(&claddr, 0, sizeof(struct sockaddr_un)); + claddr.sun_family = AF_UNIX; + snprintf(claddr.sun_path, sizeof(claddr.sun_path), + "/tmp/ud_ucase_cl.%ld", (long) getpid()); + + if (bind(sfd, (struct sockaddr *) &claddr, sizeof(struct sockaddr_un)) == -1) + errExit("bind"); + + /* Construct address of server */ + + memset(&svaddr, 0, sizeof(struct sockaddr_un)); + svaddr.sun_family = AF_UNIX; + strncpy(svaddr.sun_path, SV_SOCK_PATH, sizeof(svaddr.sun_path) - 1); + + /* Send messages to server; echo responses on stdout */ + + for (j = 1; j < argc; j++) { + msgLen = strlen(argv[j]); /* May be longer than BUF_SIZE */ + if (sendto(sfd, argv[j], msgLen, 0, (struct sockaddr *) &svaddr, + sizeof(struct sockaddr_un)) != msgLen) + fatal("sendto"); + + numBytes = recvfrom(sfd, resp, BUF_SIZE, 0, NULL, NULL); + /* Or equivalently: numBytes = recv(sfd, resp, BUF_SIZE, 0); + or: numBytes = read(sfd, resp, BUF_SIZE); */ + if (numBytes == -1) + errExit("recvfrom"); + printf("Response %d: %.*s\n", j, (int) numBytes, resp); + } + + remove(claddr.sun_path); /* Remove client socket pathname */ + exit(EXIT_SUCCESS); +} diff --git a/winsup/cygwin/socket_tests/ud_ucase_sv.c b/winsup/cygwin/socket_tests/ud_ucase_sv.c new file mode 100644 index 000000000..7c4987b35 --- /dev/null +++ b/winsup/cygwin/socket_tests/ud_ucase_sv.c @@ -0,0 +1,72 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either version 3 or (at your option) any * +* later version. This program is distributed without any warranty. See * +* the file COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Listing 57-6 */ + +/* ud_ucase_sv.c + + A server that uses a UNIX domain datagram socket to receive datagrams, + convert their contents to uppercase, and then return them to the senders. + + See also ud_ucase_cl.c. +*/ +#include "ud_ucase.h" + +int +main(int argc, char *argv[]) +{ + struct sockaddr_un svaddr, claddr; + int sfd, j; + ssize_t numBytes; + socklen_t len; + char buf[BUF_SIZE]; + + sfd = socket(AF_UNIX, SOCK_DGRAM, 0); /* Create server socket */ + if (sfd == -1) + errExit("socket"); + + /* Construct well-known address and bind server socket to it */ + + /* For an explanation of the following check, see the erratum note for + page 1168 at http://www.man7.org/tlpi/errata/. */ + + if (strlen(SV_SOCK_PATH) > sizeof(svaddr.sun_path) - 1) + fatal("Server socket path too long: %s", SV_SOCK_PATH); + + if (remove(SV_SOCK_PATH) == -1 && errno != ENOENT) + errExit("remove-%s", SV_SOCK_PATH); + + memset(&svaddr, 0, sizeof(struct sockaddr_un)); + svaddr.sun_family = AF_UNIX; + strncpy(svaddr.sun_path, SV_SOCK_PATH, sizeof(svaddr.sun_path) - 1); + + if (bind(sfd, (struct sockaddr *) &svaddr, sizeof(struct sockaddr_un)) == -1) + errExit("bind"); + + /* Receive messages, convert to uppercase, and return to client */ + + for (;;) { + len = sizeof(struct sockaddr_un); + numBytes = recvfrom(sfd, buf, BUF_SIZE, 0, + (struct sockaddr *) &claddr, &len); + if (numBytes == -1) + errExit("recvfrom"); + + printf("Server received %ld bytes from %s\n", (long) numBytes, + claddr.sun_path); + + for (j = 0; j < numBytes; j++) + buf[j] = toupper((unsigned char) buf[j]); + + if (sendto(sfd, buf, numBytes, 0, (struct sockaddr *) &claddr, len) != + numBytes) + fatal("sendto"); + } +} diff --git a/winsup/cygwin/socket_tests/us_abstract_bind.c b/winsup/cygwin/socket_tests/us_abstract_bind.c new file mode 100644 index 000000000..897072ec3 --- /dev/null +++ b/winsup/cygwin/socket_tests/us_abstract_bind.c @@ -0,0 +1,61 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either version 3 or (at your option) any * +* later version. This program is distributed without any warranty. See * +* the file COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Listing 57-8 */ + +/* us_abstract_bind.c + + Demonstrate how to bind a UNIX domain socket to a name in the + Linux-specific abstract namespace. + + This program is Linux-specific. + + The first printing of the book used slightly different code. The code was + correct, but could have been better (to understand why, see the errata + for page 1176 of the book). The old code is shown in comments below. +*/ +#include "af_unix_hdr.h" + +int +main(int argc, char *argv[]) +{ + int sockfd; + struct sockaddr_un addr; + char *str; + + memset(&addr, 0, sizeof(struct sockaddr_un)); /* Clear address structure */ + addr.sun_family = AF_UNIX; /* UNIX domain address */ + + /* addr.sun_path[0] has already been set to 0 by memset() */ + + str = "xyz"; /* Abstract name is "\0xyz" */ + strncpy(&addr.sun_path[1], str, strlen(str)); + + // In early printings of the book, the above two lines were instead: + // + // strncpy(&addr.sun_path[1], "xyz", sizeof(addr.sun_path) - 2); + // /* Abstract name is "xyz" followed by null bytes */ + + sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sockfd == -1) + errExit("socket"); + + if (bind(sockfd, (struct sockaddr *) &addr, + sizeof(sa_family_t) + strlen(str) + 1) == -1) + errExit("bind"); + + // In early printings of the book, the final part of the bind() call + // above was instead: + // sizeof(struct sockaddr_un)) == -1) + + sleep(60); + + exit(EXIT_SUCCESS); +} diff --git a/winsup/cygwin/socket_tests/us_xfr.h b/winsup/cygwin/socket_tests/us_xfr.h new file mode 100644 index 000000000..5ac855a6e --- /dev/null +++ b/winsup/cygwin/socket_tests/us_xfr.h @@ -0,0 +1,30 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either version 3 or (at your option) any * +* later version. This program is distributed without any warranty. See * +* the file COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Listing 57-2 */ + +/* us_xfr.h + + Header file for us_xfr_sv.c and us_xfr_cl.c. + + These programs employ a socket in /tmp. This makes it easy to compile + and run the programs. However, for a security reasons, a real-world + application should never create sensitive files in /tmp. (As a simple of + example of the kind of security problems that can result, a malicious + user could create a file using the name defined in SV_SOCK_PATH, and + thereby cause a denial of service attack against this application. + See Section 38.7 of "The Linux Programming Interface" for more details + on this subject.) +*/ +#include "af_unix_hdr.h" + +#define SV_SOCK_PATH "/tmp/us_xfr" + +#define BUF_SIZE 100 diff --git a/winsup/cygwin/socket_tests/us_xfr_cl.c b/winsup/cygwin/socket_tests/us_xfr_cl.c new file mode 100644 index 000000000..249765952 --- /dev/null +++ b/winsup/cygwin/socket_tests/us_xfr_cl.c @@ -0,0 +1,55 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either version 3 or (at your option) any * +* later version. This program is distributed without any warranty. See * +* the file COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Listing 57-4 */ + +/* us_xfr_cl.c + + An example UNIX domain stream socket client. This client transmits contents + of stdin to a server socket. + + See also us_xfr_sv.c. +*/ + +#include "us_xfr.h" + +int +main(int argc, char *argv[]) +{ + struct sockaddr_un addr; + int sfd; + ssize_t numRead; + char buf[BUF_SIZE]; + + sfd = socket(AF_UNIX, SOCK_STREAM, 0); /* Create client socket */ + if (sfd == -1) + errExit("socket"); + + /* Construct server address, and make the connection */ + + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, SV_SOCK_PATH, sizeof(addr.sun_path) - 1); + + if (connect(sfd, (struct sockaddr *) &addr, + sizeof(struct sockaddr_un)) == -1) + errExit("connect"); + + /* Copy stdin to socket */ + + while ((numRead = read(STDIN_FILENO, buf, BUF_SIZE)) > 0) + if (write(sfd, buf, numRead) != numRead) + fatal("partial/failed write"); + + if (numRead == -1) + errExit("read"); + + exit(EXIT_SUCCESS); /* Closes our socket; server sees EOF */ +} diff --git a/winsup/cygwin/socket_tests/us_xfr_sv.c b/winsup/cygwin/socket_tests/us_xfr_sv.c new file mode 100644 index 000000000..a2193dad7 --- /dev/null +++ b/winsup/cygwin/socket_tests/us_xfr_sv.c @@ -0,0 +1,79 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either version 3 or (at your option) any * +* later version. This program is distributed without any warranty. See * +* the file COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Listing 57-3 */ + +/* us_xfr_sv.c + + An example UNIX stream socket server. Accepts incoming connections + and copies data sent from clients to stdout. + + See also us_xfr_cl.c. +*/ +#include "us_xfr.h" +#define BACKLOG 5 + +int +main(int argc, char *argv[]) +{ + struct sockaddr_un addr; + int sfd, cfd; + ssize_t numRead; + char buf[BUF_SIZE]; + + sfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sfd == -1) + errExit("socket"); + + /* Construct server socket address, bind socket to it, + and make this a listening socket */ + + /* For an explanation of the following check, see the errata notes for + pages 1168 and 1172 at http://www.man7.org/tlpi/errata/. */ + + if (strlen(SV_SOCK_PATH) > sizeof(addr.sun_path) - 1) + fatal("Server socket path too long: %s", SV_SOCK_PATH); + + if (remove(SV_SOCK_PATH) == -1 && errno != ENOENT) + errExit("remove-%s", SV_SOCK_PATH); + + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, SV_SOCK_PATH, sizeof(addr.sun_path) - 1); + + if (bind(sfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) == -1) + errExit("bind"); + + if (listen(sfd, BACKLOG) == -1) + errExit("listen"); + + for (;;) { /* Handle client connections iteratively */ + + /* Accept a connection. The connection is returned on a new + socket, 'cfd'; the listening socket ('sfd') remains open + and can be used to accept further connections. */ + + cfd = accept(sfd, NULL, NULL); + if (cfd == -1) + errExit("accept"); + + /* Transfer data from connected socket to stdout until EOF */ + + while ((numRead = read(cfd, buf, BUF_SIZE)) > 0) + if (write(STDOUT_FILENO, buf, numRead) != numRead) + fatal("partial/failed write"); + + if (numRead == -1) + errExit("read"); + + if (close(cfd) == -1) + errMsg("close"); + } +} diff --git a/winsup/cygwin/socket_tests/us_xfr_v2.h b/winsup/cygwin/socket_tests/us_xfr_v2.h new file mode 100644 index 000000000..aef7e2435 --- /dev/null +++ b/winsup/cygwin/socket_tests/us_xfr_v2.h @@ -0,0 +1,22 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either version 3 or (at your option) any * +* later version. This program is distributed without any warranty. See * +* the file COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Solution for Exercise 59-3:c */ + +/* us_xfr_v2.h + + Header file for us_xfr_sv.c and us_xfr_cl.c. +*/ +#include "unix_sockets.h" /* Declares our socket functions */ +#include "af_unix_hdr.h" + +#define SV_SOCK_PATH "/tmp/us_xfr_v2" + +#define BUF_SIZE 100 diff --git a/winsup/cygwin/socket_tests/us_xfr_v2_cl.c b/winsup/cygwin/socket_tests/us_xfr_v2_cl.c new file mode 100644 index 000000000..da2b27a0d --- /dev/null +++ b/winsup/cygwin/socket_tests/us_xfr_v2_cl.c @@ -0,0 +1,44 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either version 3 or (at your option) any * +* later version. This program is distributed without any warranty. See * +* the file COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Solution for Exercise 59-3:e */ + +/* us_xfr_v2_cl.c + + An example UNIX domain stream socket client. This client transmits contents + of stdin to a server socket. This program is similar to us_xfr_cl.c, except + that it uses the functions in unix_sockets.c to simplify working with UNIX + domain sockets. + + See also us_xfr_v2_sv.c. +*/ +#include "us_xfr_v2.h" + +int +main(int argc, char *argv[]) +{ + int sfd; + ssize_t numRead; + char buf[BUF_SIZE]; + + sfd = unixConnect(SV_SOCK_PATH, SOCK_STREAM); + if (sfd == -1) + errExit("unixConnect"); + + /* Copy stdin to socket */ + + while ((numRead = read(STDIN_FILENO, buf, BUF_SIZE)) > 0) + if (write(sfd, buf, numRead) != numRead) + fatal("partial/failed write"); + + if (numRead == -1) + errExit("read"); + exit(EXIT_SUCCESS); /* Closes our socket; server sees EOF */ +} diff --git a/winsup/cygwin/socket_tests/us_xfr_v2_sv.c b/winsup/cygwin/socket_tests/us_xfr_v2_sv.c new file mode 100644 index 000000000..517129a4b --- /dev/null +++ b/winsup/cygwin/socket_tests/us_xfr_v2_sv.c @@ -0,0 +1,57 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2018. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either version 3 or (at your option) any * +* later version. This program is distributed without any warranty. See * +* the file COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* Solution for Exercise 59-3:d */ + +/* us_xfr_v2_sv.c + + An example UNIX stream socket server. Accepts incoming connections and + copies data sent from clients to stdout. This program is similar to + us_xfr_sv.c, except that it uses the functions in unix_sockets.c to + simplify working with UNIX domain sockets. + + See also us_xfr_v2_cl.c. +*/ +#include "us_xfr_v2.h" + +int +main(int argc, char *argv[]) +{ + int sfd, cfd; + ssize_t numRead; + char buf[BUF_SIZE]; + + if (remove (SV_SOCK_PATH) < 0 && errno != ENOENT) + errExit ("remove"); + + sfd = unixBind(SV_SOCK_PATH, SOCK_STREAM); + if (sfd == -1) + errExit("unixBind"); + + if (listen(sfd, 5) == -1) + errExit("listen"); + + for (;;) { /* Handle client connections iteratively */ + cfd = accept(sfd, NULL, NULL); + if (cfd == -1) + errExit("accept"); + + /* Transfer data from connected socket to stdout until EOF */ + + while ((numRead = read(cfd, buf, BUF_SIZE)) > 0) + if (write(STDOUT_FILENO, buf, numRead) != numRead) + fatal("partial/failed write"); + + if (numRead == -1) + errExit("read"); + if (close(cfd) == -1) + errMsg("close"); + } +} diff --git a/winsup/cygwin/socket_tests/waitall.h b/winsup/cygwin/socket_tests/waitall.h new file mode 100644 index 000000000..73aad8490 --- /dev/null +++ b/winsup/cygwin/socket_tests/waitall.h @@ -0,0 +1,7 @@ +/* Header for waitall_sv.c and waitall_cl.c */ + +#include "af_unix_hdr.h" + +#define SV_SOCK_PATH "/tmp/waitall" + +#define BACKLOG 5 diff --git a/winsup/cygwin/socket_tests/waitall_cl.c b/winsup/cygwin/socket_tests/waitall_cl.c new file mode 100644 index 000000000..49aba3e87 --- /dev/null +++ b/winsup/cygwin/socket_tests/waitall_cl.c @@ -0,0 +1,22 @@ +#include "waitall.h" + +#define BUF_SIZE 100 + +int +main () +{ + int sfd; + ssize_t nread; + char buf[BUF_SIZE]; + + if ((sfd = unixConnect (SV_SOCK_PATH, SOCK_STREAM)) < 0) + errExit ("unixConnect"); + + /* Copy stdin to socket. */ + while ((nread = read (STDIN_FILENO, buf, BUF_SIZE)) > 0) + if (write (sfd, buf, nread) != nread) + errExit ("partial/failed write"); + + if (nread < 0) + errExit ("read"); +} diff --git a/winsup/cygwin/socket_tests/waitall_sv.c b/winsup/cygwin/socket_tests/waitall_sv.c new file mode 100644 index 000000000..175ca4337 --- /dev/null +++ b/winsup/cygwin/socket_tests/waitall_sv.c @@ -0,0 +1,38 @@ +#include "waitall.h" + +#define BUF_SIZE 10 + +int +main () +{ + int sfd, cfd; + ssize_t nread; + char buf[BUF_SIZE]; + + if (remove (SV_SOCK_PATH) < 0 && errno != ENOENT) + errExit ("remove"); + + if ((sfd = unixBind (SV_SOCK_PATH, SOCK_STREAM)) < 0) + errExit ("unixBind"); + + if (listen (sfd, BACKLOG) < 0) + errExit ("listen"); + + while (1) + { + cfd = accept (sfd, NULL, NULL); + if (cfd < 0) + errExit ("accept"); + + /* Transfer data from connected socket to stdout until EOF. */ + while ((nread = recv (cfd, buf, BUF_SIZE, MSG_WAITALL)) > 0) + if (write (STDOUT_FILENO, buf, nread) != nread) + errExit ("partial/failed write"); + + if (nread < 0) + errExit ("read"); + + if (close (cfd) < 0) + errExit ("close"); + } +} diff --git a/winsup/cygwin/socket_tests/writev_socket.c b/winsup/cygwin/socket_tests/writev_socket.c new file mode 100644 index 000000000..793857222 --- /dev/null +++ b/winsup/cygwin/socket_tests/writev_socket.c @@ -0,0 +1,32 @@ +/* Adapted from https://www.oreilly.com/library/view/linux-system-programming/0596009585/ch04.html */ + +#include "scatter_gather.h" + +int main ( ) +{ + struct iovec iov[3]; + ssize_t nr; + int sfd; + + char *buf[] = { + "The term buccaneer comes from the word boucan.\n", + "A boucan is a wooden frame used for cooking meat.\n", + "Buccaneer is the West Indies name for a pirate.\n" + }; + + if ((sfd = unixConnect (SV_SOCK_PATH, SOCK_STREAM)) < 0) + errExit ("unixConnect"); + + for (int i = 0; i < 3; i++) + { + iov[i].iov_base = buf[i]; + iov[i].iov_len = strlen (buf[i]) + 1; + } + + nr = writev (sfd, iov, 3); + if (nr < 0) + errExit ("writev"); + printf ("wrote %zd bytes\n", nr); + if (close (sfd) < 0) + errExit ("close"); +} |