Cygwin: AF_UNIX: add socket tests

This commit is contained in:
Ken Brown 2021-05-11 10:34:51 -04:00
parent dfe5988f96
commit 315920326a
69 changed files with 4808 additions and 0 deletions

4
.gitignore vendored
View File

@ -7,6 +7,7 @@
.#*
*#
*.exe
*.flt
*.gmo
*.info
@ -47,3 +48,6 @@ core
!core/
lost+found
ename.c.inc
libafunix.a

View File

@ -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}

View File

@ -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

View File

@ -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.

View File

@ -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");
}
}

View File

@ -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') */

View File

@ -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' */
}

View File

@ -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");
}
}

View File

@ -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");
}
}

View File

@ -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;
}
'

View File

@ -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}

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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() */
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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));
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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");
}

View File

@ -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");
}

View File

@ -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"

View File

@ -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"

View File

@ -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");
}

View File

@ -0,0 +1,3 @@
#include "af_unix_hdr.h"
#include "pty_slave.h"

View File

@ -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");
}

View File

@ -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");
}

View File

@ -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

View File

@ -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"

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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 */

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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"

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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

View File

@ -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");
}

View File

@ -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)");
}
}
}

View File

@ -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");
}
}

View File

@ -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"

View File

@ -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);
}

View File

@ -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");
}
}

View File

@ -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);
}

View File

@ -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

View File

@ -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 */
}

View File

@ -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");
}
}

View File

@ -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

View File

@ -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 */
}

View File

@ -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");
}
}

View File

@ -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

View File

@ -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");
}

View File

@ -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");
}
}

View File

@ -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");
}