From b7ebbd272e2693a71af4807842d8f101d8f0fc17 Mon Sep 17 00:00:00 2001
From: Michael Tuexen <tuexen@fh-muenster.de>
Date: Sun, 10 Jul 2016 20:56:52 +0200
Subject: [PATCH] Add support for sendfile.

---
 gtests/net/packetdrill/contrib/packetdrill.el |   2 +-
 .../net/packetdrill/contrib/packetdrill.plist |   1 +
 .../net/packetdrill/contrib/packetdrill.vim   |   2 +-
 gtests/net/packetdrill/lexer.l                |   2 +
 gtests/net/packetdrill/parser.y               |  23 +-
 gtests/net/packetdrill/run_system_call.c      | 212 ++++++++++++++++++
 gtests/net/packetdrill/script.c               |  51 ++++-
 gtests/net/packetdrill/script.h               |  16 ++
 gtests/net/packetdrill/symbols_freebsd.c      |   3 +
 9 files changed, 306 insertions(+), 6 deletions(-)

diff --git a/gtests/net/packetdrill/contrib/packetdrill.el b/gtests/net/packetdrill/contrib/packetdrill.el
index 32e653eb..655bb50e 100644
--- a/gtests/net/packetdrill/contrib/packetdrill.el
+++ b/gtests/net/packetdrill/contrib/packetdrill.el
@@ -2,7 +2,7 @@
 
 (setq packetdrill-constants '("AF_INET" "AF_INET6" "PF_INET" "PF_INET6" "SOCK_STREAM" "SOCK_DGRAM" "IPPROTO_IP" "IPPROTO_IPV6" "IPPROTO_ICMP" "IPPROTO_SCTP" "IPPROTO_TCP" "IPPROTO_UDP" "IPPROTO_UDPLITE" "SOL_SOCKET" "SOL_IP" "SOL_IPV6" "SOL_SCTP" "SOL_TCP" "SOL_UDP" "SO_ACCEPTCONN" "SO_ATTACH_FILTER" "SO_BINDTODEVICE" "SO_BROADCAST" "SO_BSDCOMPAT" "SO_DEBUG" "SO_DETACH_FILTER" "SO_DONTROUTE" "SO_ERROR" "SO_KEEPALIVE" "SO_LINGER" "SO_NO_CHECK" "SO_OOBINLINE" "SO_PASSCRED" "SO_PEERCRED" "SO_PEERNAME" "SO_PEERSEC" "SO_PRIORITY" "SO_RCVBUF" "SO_RCVLOWAT" "SO_RCVTIMEO" "SO_REUSEADDR" "SO_REUSEPORT" "SO_SECURITY_AUTHENTICATION" "SO_SECURITY_ENCRYPTION_NETWORK" "SO_SECURITY_ENCRYPTION_TRANSPORT" "SO_SNDBUF" "SO_SNDLOWAT" "SO_SNDTIMEO" "SO_TIMESTAMP" "SO_TYPE" "IP_TOS" "IP_MTU_DISCOVER" "IP_PMTUDISC_WANT" "IP_PMTUDISC_DONT" "IP_PMTUDISC_DO" "IP_PMTUDISC_PROBE" "IP_MTU" "IPV6_MTU" "SCTP_RTOINFO" "SCTP_INITMSG" "SCTP_NODELAY" "SCTP_MAXSEG" "SCTP_DELAYED_SACK" "SCTP_MAX_BURST" "TCP_NODELAY" "TCP_MAXSEG" "TCP_CORK" "TCP_KEEPIDLE" "TCP_KEEPINTVL" "TCP_KEEPCNT" "TCP_SYNCNT" "TCP_LINGER2" "TCP_DEFER_ACCEPT" "TCP_WINDOW_CLAMP" "TCP_INFO" "TCP_QUICKACK" "TCP_CONGESTION" "TCP_MD5SIG" "TCP_COOKIE_TRANSACTIONS" "TCP_THIN_LINEAR_TIMEOUTS" "TCP_THIN_DUPACK" "TCP_USER_TIMEOUT" "TCP_MIN_RTO" "TCP_INFO_EXT" "TCP_CWND" "TCP_XMIT_COMPL_SEQ" "TCP_CWND_CLAMP" "TCP_SAVE_SYN" "TCP_SAVED_SYN" "TCP_FASTOPEN" "TCP_MULTIPLE_CONNECTIONS" "UDPLITE_RECV_CSCOV" "UDPLITE_SEND_CSCOV" "O_RDONLY" "O_WRONLY" "O_RDWR" "O_ACCMODE" "O_CREAT" "O_EXCL" "O_NOCTTY" "O_TRUNC" "O_APPEND" "O_NONBLOCK" "F_DUPFD" "F_GETFD" "F_SETFD" "F_GETFL" "F_SETFL" "F_GETLK" "F_SETLK" "F_SETLKW" "F_GETOWN" "F_SETOWN" "F_SETSIG" "F_GETSIG" "F_GETOWN" "F_SETOWN" "F_SETLK" "F_SETLKW" "F_GETLK" "F_SETLK64" "F_SETLKW64" "F_GETLK64" "F_SETLEASE" "F_GETLEASE" "F_NOTIFY" "F_DUPFD_CLOEXEC" "FD_CLOEXEC" "LOCK_SH" "LOCK_EX" "LOCK_NB" "LOCK_UN" "F_RDLCK" "F_WRLCK" "F_UNLCK" "F_EXLCK" "F_SHLCK" "SEEK_SET" "SEEK_CUR" "SEEK_END" "MSG_OOB" "MSG_DONTROUTE" "MSG_PEEK" "MSG_CTRUNC" "MSG_PROXY" "MSG_EOR" "MSG_WAITALL" "MSG_TRUNC" "MSG_CTRUNC" "MSG_ERRQUEUE" "MSG_DONTWAIT" "MSG_CONFIRM" "MSG_FIN" "MSG_SYN" "MSG_RST" "MSG_NOSIGNAL" "MSG_MORE" "MSG_CMSG_CLOEXEC" "MSG_FASTOPEN" "SIOCINQ" "FIONREAD" "POLLIN" "POLLPRI" "POLLOUT" "POLLRDNORM" "POLLRDBAND" "POLLWRNORM" "POLLWRBAND" "POLLMSG" "POLLREMOVE" "POLLRDHUP" "POLLERR" "POLLHUP" "POLLNVAL" "EPERM" "ENOENT" "ESRCH" "EINTR" "EIO" "ENXIO" "E2BIG" "ENOEXEC" "EBADF" "ECHILD" "EAGAIN" "ENOMEM" "EACCES" "EFAULT" "ENOTBLK" "EBUSY" "EEXIST" "EXDEV" "ENODEV" "ENOTDIR" "EISDIR" "EINVAL" "ENFILE" "EMFILE" "ENOTTY" "ETXTBSY" "EFBIG" "ENOSPC" "ESPIPE" "EROFS" "EMLINK" "EPIPE" "EDOM" "ERANGE" "EDEADLK" "ENAMETOOLONG" "ENOLCK" "ENOSYS" "ENOTEMPTY" "ELOOP" "EWOULDBLOCK" "ENOMSG" "EIDRM" "ECHRNG" "EL2NSYNC" "EL3HLT" "EL3RST" "ELNRNG" "EUNATCH" "ENOCSI" "EL2HLT" "EBADE" "EBADR" "EXFULL" "ENOANO" "EBADRQC" "EBADSLT" "EDEADLOCK" "EBFONT" "ENOSTR" "ENODATA" "ETIME" "ENOSR" "ENONET" "ENOPKG" "EREMOTE" "ENOLINK" "EADV" "ESRMNT" "ECOMM" "EPROTO" "EMULTIHOP" "EDOTDOT" "EBADMSG" "EOVERFLOW" "ENOTUNIQ" "EBADFD" "EREMCHG" "ELIBACC" "ELIBBAD" "ELIBSCN" "ELIBMAX" "ELIBEXEC" "EILSEQ" "ERESTART" "ESTRPIPE" "EUSERS" "ENOTSOCK" "EDESTADDRREQ" "EMSGSIZE" "EPROTOTYPE" "ENOPROTOOPT" "EPROTONOSUPPORT" "ESOCKTNOSUPPORT" "EOPNOTSUPP" "EPFNOSUPPORT" "EAFNOSUPPORT" "EADDRINUSE" "EADDRNOTAVAIL" "ENETDOWN" "ENETUNREACH" "ENETRESET" "ECONNABORTED" "ECONNRESET" "ENOBUFS" "EISCONN" "ENOTCONN" "ESHUTDOWN" "ETOOMANYREFS" "ETIMEDOUT" "ECONNREFUSED" "EHOSTDOWN" "EHOSTUNREACH" "EALREADY" "EINPROGRESS" "ESTALE" "EUCLEAN" "ENOTNAM" "ENAVAIL" "EISNAM" "EREMOTEIO" "EDQUOT" "ENOMEDIUM" "EMEDIUMTYPE" "ECANCELED" "ENOKEY" "EKEYEXPIRED" "EKEYREVOKED" "EKEYREJECTED" "EOWNERDEAD" "ENOTRECOVERABLE" "ERFKILL" "POLLIN" "POLLPRI" "POLLOUT" "POLLRDNORM" "POLLRDBAND" "POLLWRNORM" "POLLWRBAND" "POLLMSG" "POLLREMOVE" "POLLRDHUP" "POLLERR" "POLLHUP" "POLLNVAL"))
 
-(setq packetdrill-functions '("accept" "bind" "close" "connect" "fcntl" "getsockopt" "ioctl" "listen" "poll" "open" "read" "readv" "recv" "recvfrom" "recvmsg" "send" "sendmsg" "sendto" "setsockopt" "shutdown" "socket" "write" "writev"))
+(setq packetdrill-functions '("accept" "bind" "close" "connect" "fcntl" "getsockopt" "ioctl" "listen" "poll" "open" "sendfile" "read" "readv" "recv" "recvfrom" "recvmsg" "send" "sendmsg" "sendto" "setsockopt" "shutdown" "socket" "write" "writev"))
 
 ;; create the regex string for each class of keywords
 (setq packetdrill-keywords-regexp (regexp-opt packetdrill-keywords 'words))
diff --git a/gtests/net/packetdrill/contrib/packetdrill.plist b/gtests/net/packetdrill/contrib/packetdrill.plist
index 9128fcff..ef9b2a58 100644
--- a/gtests/net/packetdrill/contrib/packetdrill.plist
+++ b/gtests/net/packetdrill/contrib/packetdrill.plist
@@ -101,6 +101,7 @@
 		<string>listen</string>
 		<string>poll</string>
 		<string>open</string>
+		<string>sendfile</string>
 		<string>read</string>
 		<string>readv</string>
 		<string>recv</string>
diff --git a/gtests/net/packetdrill/contrib/packetdrill.vim b/gtests/net/packetdrill/contrib/packetdrill.vim
index 66ee91c9..364503e5 100644
--- a/gtests/net/packetdrill/contrib/packetdrill.vim
+++ b/gtests/net/packetdrill/contrib/packetdrill.vim
@@ -14,7 +14,7 @@ set cpo&vim
 syn keyword     pKeyword        sa_family sin_port sin_addr msg_name msg_iov msg_flags fd events revents htons icmp sctp udp udplite inet_addr ack eol ecr mss mtu nop sack sackOK TS FO LS gcn minRTO val win wscale ect01 ect0 ect1 noecn ce pro onoff linger srto_initial srto_max srto_min sinit_num_ostreams sinit_max_instreams sinit_max_attempts sinit_max_init_timeo assoc_value sack_delay sack_freq sstat_state sstat_rwnd sstat_unackdata sstat_penddata sstat_instrms sstat_outstrms sstat_fragmentation_point sstat_primary spp_address spp_hbinterval spp_pathmaxrxt spinfo_state spinfo_cwnd spinfo_srtt spinfo_rto spinfo_mtu spp_pathmtu
 syn keyword     pConstant       AF_INET AF_INET6 PF_INET PF_INET6 SOCK_STREAM SOCK_DGRAM IPPROTO_IP IPPROTO_IPV6 IPPROTO_ICMP IPPROTO_SCTP IPPROTO_TCP IPPROTO_UDP IPPROTO_UDPLITE SOL_SOCKET SOL_IP SOL_IPV6 SOL_SCTP SOL_TCP SOL_UDP SOL_UDPLITE SO_ACCEPTCONN SO_ATTACH_FILTER SO_BINDTODEVICE SO_BROADCAST SO_BSDCOMPAT SO_DEBUG SO_DETACH_FILTER SO_DONTROUTE SO_ERROR SO_KEEPALIVE SO_LINGER SO_NO_CHECK SO_OOBINLINE SO_PASSCRED SO_PEERCRED SO_PEERNAME SO_PEERSEC SO_PRIORITY SO_RCVBUF SO_RCVLOWAT SO_RCVTIMEO SO_REUSEADDR SO_REUSEPORT SO_SECURITY_AUTHENTICATION SO_SECURITY_ENCRYPTION_NETWORK SO_SECURITY_ENCRYPTION_TRANSPORT SO_SNDBUF SO_SNDLOWAT SO_SNDTIMEO SO_TIMESTAMP SO_TYPE IP_TOS IP_MTU_DISCOVER IP_PMTUDISC_WANT IP_PMTUDISC_DONT IP_PMTUDISC_DO IP_PMTUDISC_PROBE IP_MTU IPV6_MTU SCTP_RTOINFO SCTP_INITMSG SCTP_NODELAY SCTP_MAXSEG SCTP_DELAYED_SACK SCTP_MAX_BURST TCP_NODELAY TCP_MAXSEG TCP_CORK TCP_KEEPIDLE TCP_KEEPINTVL TCP_KEEPCNT TCP_SYNCNT TCP_LINGER2 TCP_DEFER_ACCEPT TCP_WINDOW_CLAMP TCP_INFO TCP_QUICKACK TCP_CONGESTION TCP_MD5SIG TCP_COOKIE_TRANSACTIONS TCP_THIN_LINEAR_TIMEOUTS TCP_THIN_DUPACK TCP_USER_TIMEOUT TCP_MIN_RTO TCP_INFO_EXT TCP_CWND TCP_XMIT_COMPL_SEQ TCP_CWND_CLAMP TCP_SAVE_SYN TCP_SAVED_SYN TCP_FASTOPEN TCP_MULTIPLE_CONNECTIONS UDPLITE_RECV_CSCOV UDPLITE_SEND_CSCOV
 syn keyword     pConstant       O_RDONLY O_WRONLY O_RDWR O_ACCMODE O_CREAT O_EXCL O_NOCTTY O_TRUNC O_APPEND O_NONBLOCK F_DUPFD F_GETFD F_SETFD F_GETFL F_SETFL F_GETLK F_SETLK F_SETLKW F_GETOWN F_SETOWN F_SETSIG F_GETSIG F_GETOWN F_SETOWN F_SETLK F_SETLKW F_GETLK F_SETLK64 F_SETLKW64 F_GETLK64 F_SETLEASE F_GETLEASE F_NOTIFY F_DUPFD_CLOEXEC FD_CLOEXEC LOCK_SH LOCK_EX LOCK_NB LOCK_UN F_RDLCK F_WRLCK F_UNLCK F_EXLCK F_SHLCK SEEK_SET SEEK_CUR SEEK_END MSG_OOB MSG_DONTROUTE MSG_PEEK MSG_CTRUNC MSG_PROXY MSG_EOR MSG_WAITALL MSG_TRUNC MSG_CTRUNC MSG_ERRQUEUE MSG_DONTWAIT MSG_CONFIRM MSG_FIN MSG_SYN MSG_RST MSG_NOSIGNAL MSG_MORE MSG_CMSG_CLOEXEC MSG_FASTOPEN SIOCINQ FIONREAD POLLIN POLLPRI POLLOUT POLLRDNORM POLLRDBAND POLLWRNORM POLLWRBAND POLLMSG POLLREMOVE POLLRDHUP POLLERR POLLHUP POLLNVAL EPERM ENOENT ESRCH EINTR EIO ENXIO E2BIG ENOEXEC EBADF ECHILD EAGAIN ENOMEM EACCES EFAULT ENOTBLK EBUSY EEXIST EXDEV ENODEV ENOTDIR EISDIR EINVAL ENFILE EMFILE ENOTTY ETXTBSY EFBIG ENOSPC ESPIPE EROFS EMLINK EPIPE EDOM ERANGE EDEADLK ENAMETOOLONG ENOLCK ENOSYS ENOTEMPTY ELOOP EWOULDBLOCK ENOMSG EIDRM ECHRNG EL2NSYNC EL3HLT EL3RST ELNRNG EUNATCH ENOCSI EL2HLT EBADE EBADR EXFULL ENOANO EBADRQC EBADSLT EDEADLOCK EBFONT ENOSTR ENODATA ETIME ENOSR ENONET ENOPKG EREMOTE ENOLINK EADV ESRMNT ECOMM EPROTO EMULTIHOP EDOTDOT EBADMSG EOVERFLOW ENOTUNIQ EBADFD EREMCHG ELIBACC ELIBBAD ELIBSCN ELIBMAX ELIBEXEC EILSEQ ERESTART ESTRPIPE EUSERS ENOTSOCK EDESTADDRREQ EMSGSIZE EPROTOTYPE ENOPROTOOPT EPROTONOSUPPORT ESOCKTNOSUPPORT EOPNOTSUPP EPFNOSUPPORT EAFNOSUPPORT EADDRINUSE EADDRNOTAVAIL ENETDOWN ENETUNREACH ENETRESET ECONNABORTED ECONNRESET ENOBUFS EISCONN ENOTCONN ESHUTDOWN ETOOMANYREFS ETIMEDOUT ECONNREFUSED EHOSTDOWN EHOSTUNREACH EALREADY EINPROGRESS ESTALE EUCLEAN ENOTNAM ENAVAIL EISNAM EREMOTEIO EDQUOT ENOMEDIUM EMEDIUMTYPE ECANCELED ENOKEY EKEYEXPIRED EKEYREVOKED EKEYREJECTED EOWNERDEAD ENOTRECOVERABLE ERFKILL POLLIN POLLPRI POLLOUT POLLRDNORM POLLRDBAND POLLWRNORM POLLWRBAND POLLMSG POLLREMOVE POLLRDHUP POLLERR POLLHUP POLLNVAL
-syn keyword	pSyscall        accept bind close connect fcntl getsockopt ioctl listen poll open read readv recv recvfrom recvmsg send sendmsg sendto setsockopt shutdown socket write writev
+syn keyword	pSyscall        accept bind close connect fcntl getsockopt ioctl listen poll open sendfile read readv recv recvfrom recvmsg send sendmsg sendto setsockopt shutdown socket write writev
 syn keyword	pPythonCmds     contained assert print
 syn region      pPython         start='%{' end='}%' contains=pPythonCmds
 syn keyword     pShellCmds      contained sysctl
diff --git a/gtests/net/packetdrill/lexer.l b/gtests/net/packetdrill/lexer.l
index d68bf4ee..da93842b 100644
--- a/gtests/net/packetdrill/lexer.l
+++ b/gtests/net/packetdrill/lexer.l
@@ -325,6 +325,8 @@ noecn				return NO_ECN;
 ce				return CE;
 iov_base			return IOV_BASE;
 iov_len				return IOV_LEN;
+headers				return SF_HDTR_HEADERS;
+trailers			return SF_HDTR_TRAILERS;
 [.][.][.]			return ELLIPSIS;
 assoc_id			return ASSOC_ID;
 assoc_value			return ASSOC_VALUE;
diff --git a/gtests/net/packetdrill/parser.y b/gtests/net/packetdrill/parser.y
index ab426880..dc41710d 100644
--- a/gtests/net/packetdrill/parser.y
+++ b/gtests/net/packetdrill/parser.y
@@ -497,6 +497,7 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string,
 %token ELLIPSIS
 %token <reserved> SA_FAMILY SIN_PORT SIN_ADDR _HTONS_ _HTONL_ INET_ADDR
 %token <reserved> MSG_NAME MSG_IOV MSG_FLAGS MSG_CONTROL CMSG_LEN CMSG_LEVEL CMSG_TYPE CMSG_DATA
+%token <reserved> SF_HDTR_HEADERS SF_HDTR_TRAILERS
 %token <reserved> FD EVENTS REVENTS ONOFF LINGER
 %token <reserved> ACK ECR EOL MSS NOP SACK SACKOK TIMESTAMP VAL WIN WSCALE PRO
 %token <reserved> FAST_OPEN
@@ -614,7 +615,7 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string,
 %type <expression> expression binary_expression array
 %type <expression> decimal_integer hex_integer data
 %type <expression> inaddr sockaddr msghdr cmsghdr cmsg_level cmsg_type cmsg_data
-%type <expression> iovec pollfd opt_revents
+%type <expression> sf_hdtr iovec pollfd opt_revents
 %type <expression> linger l_onoff l_linger sctp_assoc_id
 %type <expression> sctp_status sstat_state sstat_rwnd sstat_unackdata sstat_penddata
 %type <expression> sstat_instrms sstat_outstrms sstat_fragmentation_point sstat_primary
@@ -2735,6 +2736,9 @@ expression
 | linger            {
 	$$ = $1;
 }
+| sf_hdtr           {
+	$$ = $1;
+}
 | sctp_rtoinfo      {
 	$$ = $1;
 }
@@ -3064,6 +3068,23 @@ linger
 }
 ;
 
+sf_hdtr
+: '{' SF_HDTR_HEADERS '(' decimal_integer ')' '=' array ','
+      SF_HDTR_TRAILERS '('decimal_integer ')' '=' array '}' {
+#if defined(__FreeBSD__)
+	struct sf_hdtr_expr *sf_hdtr_expr = calloc(1, sizeof(struct sf_hdtr_expr));
+	$$ = new_expression(EXPR_SF_HDTR);
+	$$->value.sf_hdtr = sf_hdtr_expr;
+	sf_hdtr_expr->headers	= $7;
+	sf_hdtr_expr->hdr_cnt	= $4;
+	sf_hdtr_expr->trailers	= $14;
+	sf_hdtr_expr->trl_cnt	= $11;
+#else
+	$$ = NULL;
+#endif
+}
+;
+
 srto_initial
 : SRTO_INITIAL '=' INTEGER {
 	if (!is_valid_u32($3)){
diff --git a/gtests/net/packetdrill/run_system_call.c b/gtests/net/packetdrill/run_system_call.c
index 7f896156..5e298013 100644
--- a/gtests/net/packetdrill/run_system_call.c
+++ b/gtests/net/packetdrill/run_system_call.c
@@ -51,6 +51,9 @@
 #include <sys/syscall.h>
 #include <sys/types.h>
 #include <sys/uio.h>
+#if defined(linux)
+#include <sys/sendfile.h>
+#endif
 #include <time.h>
 #include <unistd.h>
 #include "logging.h"
@@ -319,6 +322,27 @@ static int get_size_t(struct expression *expression,
 }
 #endif
 
+#if defined(__FreeBSD__)
+/* Sets the value from the expression argument, checking that it is a
+ * valid off_t, and matches the expected type. Returns STATUS_OK on
+ * success; on failure returns STATUS_ERR and sets error message.
+ */
+static int get_off_t(struct expression *expression,
+		     off_t *value, char **error)
+{
+	if (check_type(expression, EXPR_INTEGER, error))
+		return STATUS_ERR;
+	if (expression->value.num < 0) {
+		asprintf(error,
+			 "Value out of range for off_t: %lld",
+			 expression->value.num);
+		return STATUS_ERR;
+	}
+	*value = expression->value.num;
+	return STATUS_OK;
+}
+#endif
+
 #if defined(linux) || defined(__FreeBSD__)
 /* Sets the value from the expression argument, checking that it is a
  * valid sctp_assoc_t, and matches the expected type. Returns STATUS_OK on
@@ -530,6 +554,26 @@ static int u32_bracketed_arg(struct expression_list *args,
 	}
 	return get_u32(list->expression, value, error);
 }
+
+static int off_t_bracketed_arg(struct expression_list *args,
+			       int index, off_t *value, char **error)
+{
+	struct expression_list *list;
+	struct expression *expression;
+
+	expression = get_arg(args, index, error);
+	if (expression == NULL)
+		return STATUS_ERR;
+	if (check_type(expression, EXPR_LIST, error))
+		return STATUS_ERR;
+	list = expression->value.list;
+	if (expression_list_length(list) != 1) {
+		asprintf(error,
+			 "Expected [<integer>] but got multiple elements");
+		return STATUS_ERR;
+	}
+	return get_off_t(list->expression, value, error);
+}
 #endif
 
 /* Return STATUS_OK iff the argument with the given index is an
@@ -4253,6 +4297,171 @@ static int syscall_open(struct state *state, struct syscall_spec *syscall,
 	return STATUS_OK;
 }
 
+#if defined(linux)
+static int syscall_sendfile(struct state *state, struct syscall_spec *syscall,
+			    struct expression_list *args, char **error)
+{
+	int live_outfd, script_outfd;
+	int live_infd, script_infd;
+	int script_offset = 0;
+	off_t live_offset;
+	int count, result;
+	int status = STATUS_ERR;
+
+	if (check_arg_count(args, 4, error))
+		return STATUS_ERR;
+	if (s32_arg(args, 0, &script_outfd, error))
+		return STATUS_ERR;
+	if (to_live_fd(state, script_outfd, &live_outfd, error))
+		return STATUS_ERR;
+	if (s32_arg(args, 1, &script_infd, error))
+		return STATUS_ERR;
+	if (to_live_fd(state, script_infd, &live_infd, error))
+		return STATUS_ERR;
+	if (s32_bracketed_arg(args, 2, &script_offset, error))
+		return STATUS_ERR;
+	if (s32_arg(args, 3, &count, error))
+		return STATUS_ERR;
+
+	live_offset = script_offset;
+
+	begin_syscall(state, syscall);
+
+	result = sendfile(live_outfd, live_infd, &live_offset, count);
+
+	status = end_syscall(state, syscall, CHECK_EXACT, result, error);
+
+	return status;
+}
+#endif
+
+#if defined(__FreeBSD__)
+
+/* Free all the space used by the given sf_hdtr. */
+static void sf_hdtr_free(struct sf_hdtr *sf_hdtr)
+{
+	if (sf_hdtr == NULL)
+		return;
+
+	iovec_free(sf_hdtr->headers, sf_hdtr->hdr_cnt);
+	iovec_free(sf_hdtr->trailers, sf_hdtr->trl_cnt);
+	free(sf_hdtr); /* XXX */
+}
+
+/* Allocate and fill in a sf_hdtr described by the given expression. */
+static int sf_hdtr_new(struct expression *expression,
+		       struct sf_hdtr **sf_hdtr_ptr,
+		       char **error)
+{
+	int status = STATUS_ERR;
+	s32 s32_val = 0;
+	struct sf_hdtr_expr *sf_hdtr_expr;	/* input expression from script */
+	struct sf_hdtr *sf_hdtr = NULL;	/* live output */
+	size_t iov_len;
+
+	if (check_type(expression, EXPR_SF_HDTR, error))
+		goto error_out;
+	sf_hdtr_expr = expression->value.sf_hdtr;
+	sf_hdtr = calloc(1, sizeof(struct sf_hdtr));
+
+	if (sf_hdtr_expr->headers != NULL) {
+		if (iovec_new(sf_hdtr_expr->headers, &sf_hdtr->headers, &iov_len, error))
+			goto error_out;
+	}
+	if (sf_hdtr_expr->hdr_cnt != NULL) {
+		if (get_s32(sf_hdtr_expr->hdr_cnt, &s32_val, error))
+			goto error_out;
+		sf_hdtr->hdr_cnt = s32_val;
+	}
+	if (sf_hdtr->hdr_cnt != iov_len) {
+		asprintf(error,
+			 "msg_iovlen %d does not match %d-element headers array",
+			 sf_hdtr->hdr_cnt, (int)iov_len);
+		goto error_out;
+	}
+
+	if (sf_hdtr_expr->trailers != NULL) {
+		if (iovec_new(sf_hdtr_expr->trailers, &sf_hdtr->trailers, &iov_len, error))
+			goto error_out;
+	}
+	if (sf_hdtr_expr->trl_cnt != NULL) {
+		if (get_s32(sf_hdtr_expr->trl_cnt, &s32_val, error))
+			goto error_out;
+		sf_hdtr->trl_cnt = s32_val;
+	}
+	if (sf_hdtr->trl_cnt != iov_len) {
+		asprintf(error,
+			 "msg_iovlen %d does not match %d-element trailers array",
+			 sf_hdtr->trl_cnt, (int)iov_len);
+		goto error_out;
+	}
+	status = STATUS_OK;
+error_out:
+	*sf_hdtr_ptr = sf_hdtr;
+	return status;
+}
+
+static int syscall_sendfile(struct state *state, struct syscall_spec *syscall,
+			    struct expression_list *args, char **error)
+{
+	int live_fd, script_fd;
+	int live_s, script_s;
+	off_t offset;
+	size_t nbytes;
+	off_t script_sbytes, live_sbytes;
+	int flags;
+	int result;
+	int status = STATUS_ERR;
+	struct expression *expression;
+	struct sf_hdtr *sf_hdtr = NULL;
+
+	if (check_arg_count(args, 7, error))
+		goto error_out;
+	if (s32_arg(args, 0, &script_fd, error))
+		goto error_out;
+	if (to_live_fd(state, script_fd, &live_fd, error))
+		goto error_out;
+	if (s32_arg(args, 1, &script_s, error))
+		goto error_out;
+	if (to_live_fd(state, script_s, &live_s, error))
+		goto error_out;
+	if ((expression = get_arg(args, 2, error)) == NULL)
+		goto error_out;
+	if (get_off_t(expression, &offset, error))
+		goto error_out;
+	if ((expression = get_arg(args, 3, error)) == NULL)
+		goto error_out;
+	if (get_size_t(expression, &nbytes, error))
+		goto error_out;
+	if ((expression = get_arg(args, 4, error)) == NULL)
+		goto error_out;
+	if (sf_hdtr_new(expression, &sf_hdtr, error))
+		goto error_out;
+	if (off_t_bracketed_arg(args, 5, &script_sbytes, error))
+		goto error_out;
+	if (s32_arg(args, 6, &flags, error))
+		goto error_out;
+
+	begin_syscall(state, syscall);
+
+	result = sendfile(live_fd, live_s, offset, nbytes, sf_hdtr, &live_sbytes, flags);
+
+	status = end_syscall(state, syscall, CHECK_EXACT, result, error);
+	if ((status == STATUS_OK) &&
+	    (script_sbytes != live_sbytes)) {
+		asprintf(error,
+			 "Expected sbytes %lu but got %lu",
+			 script_sbytes, live_sbytes);
+		goto error_out;
+	}
+
+	status = STATUS_OK;
+error_out:
+	sf_hdtr_free(sf_hdtr);
+	return status;
+}
+#endif
+
 static int syscall_sctp_sendmsg(struct state *state, struct syscall_spec *syscall,
 			struct expression_list *args, char **error)
 {
@@ -6119,6 +6328,9 @@ struct system_call_entry system_call_table[] = {
 	{"setsockopt",      syscall_setsockopt},
 	{"poll",            syscall_poll},
 	{"open",            syscall_open},
+#if defined(linux) || defined(__FreeBSD__)
+	{"sendfile",        syscall_sendfile},
+#endif
 	{"sctp_bindx",      syscall_sctp_bindx},
 	{"sctp_peeloff",    syscall_sctp_peeloff},
 	{"sctp_getpaddrs",  syscall_sctp_getpaddrs},
diff --git a/gtests/net/packetdrill/script.c b/gtests/net/packetdrill/script.c
index 5dbe0ea2..5a0e16d7 100644
--- a/gtests/net/packetdrill/script.c
+++ b/gtests/net/packetdrill/script.c
@@ -68,6 +68,9 @@ struct expression_type_entry expression_type_table[] = {
 	{ EXPR_MSGHDR,               "msghdr" },
 	{ EXPR_CMSGHDR,              "cmsghdr"},
 	{ EXPR_POLLFD,               "pollfd" },
+#if defined(__FreeBSD__)
+	{ EXPR_SF_HDTR,              "sf_hdtr" },
+#endif
 	{ EXPR_SCTP_RTOINFO,         "sctp_rtoinfo"},
 	{ EXPR_SCTP_INITMSG,         "sctp_initmsg"},
 	{ EXPR_SCTP_ASSOC_VALUE,     "sctp_assoc_value"},
@@ -424,7 +427,7 @@ void free_expression(struct expression *expression)
 		free_expression(expression->value.sctp_event_subscribe->sctp_adaptation_layer_event);
 		free_expression(expression->value.sctp_event_subscribe->sctp_authentication_event);
 		free_expression(expression->value.sctp_event_subscribe->sctp_sender_dry_event);
-		break;		
+		break;
 	case EXPR_SCTP_SNDINFO:
 		free_expression(expression->value.sctp_sndinfo->snd_sid);
 		free_expression(expression->value.sctp_sndinfo->snd_flags);
@@ -435,7 +438,7 @@ void free_expression(struct expression *expression)
 	case EXPR_SCTP_SETPRIM:
 		free_expression(expression->value.sctp_setprim->ssp_assoc_id);
 		free_expression(expression->value.sctp_setprim->ssp_addr);
-		break;		
+		break;
 	case EXPR_SCTP_SETADAPTATION:
 		free_expression(expression->value.sctp_setadaptation->ssb_adaptation_ind);
 		break;
@@ -458,7 +461,7 @@ void free_expression(struct expression *expression)
 		free_expression(expression->value.sctp_default_prinfo->pr_policy);
 		free_expression(expression->value.sctp_default_prinfo->pr_value);
 		free_expression(expression->value.sctp_default_prinfo->pr_assoc_id);
-		break;		
+		break;
 	case EXPR_SCTP_AUTHINFO:
 		free_expression(expression->value.sctp_authinfo->auth_keynumber);
 		break;
@@ -701,6 +704,15 @@ void free_expression(struct expression *expression)
 		free_expression(expression->value.pollfd->events);
 		free_expression(expression->value.pollfd->revents);
 		break;
+#if defined(__FreeBSD__)
+	case EXPR_SF_HDTR:
+		assert(expression->value.sf_hdtr);
+		free_expression(expression->value.sf_hdtr->headers);
+		free_expression(expression->value.sf_hdtr->hdr_cnt);
+		free_expression(expression->value.sf_hdtr->trailers);
+		free_expression(expression->value.sf_hdtr->trl_cnt);
+		break;
+#endif
 	case EXPR_NONE:
 	case NUM_EXPR_TYPES:
 		break;
@@ -871,6 +883,34 @@ static int evaluate_pollfd_expression(struct expression *in,
 	return STATUS_OK;
 }
 
+#if defined(__FreeBSD__)
+static int evaluate_sf_hdtr_expression(struct expression *in,
+				       struct expression *out, char **error)
+{
+	struct sf_hdtr_expr *in_sf_hdtr;
+	struct sf_hdtr_expr *out_sf_hdtr;
+
+	assert(in->type == EXPR_SF_HDTR);
+	assert(in->value.sf_hdtr);
+	assert(out->type == EXPR_SF_HDTR);
+
+	out->value.sf_hdtr = calloc(1, sizeof(struct sf_hdtr_expr));
+
+	in_sf_hdtr = in->value.sf_hdtr;
+	out_sf_hdtr = out->value.sf_hdtr;
+
+	if (evaluate(in_sf_hdtr->headers,	&out_sf_hdtr->headers,	error))
+		return STATUS_ERR;
+	if (evaluate(in_sf_hdtr->hdr_cnt,	&out_sf_hdtr->hdr_cnt,	error))
+		return STATUS_ERR;
+	if (evaluate(in_sf_hdtr->trailers,	&out_sf_hdtr->trailers,	error))
+		return STATUS_ERR;
+	if (evaluate(in_sf_hdtr->trl_cnt,	&out_sf_hdtr->trl_cnt,	error))
+		return STATUS_ERR;
+
+	return STATUS_OK;
+}
+#endif
 static int evaluate_sctp_rtoinfo_expression(struct expression *in,
 					    struct expression *out,
 					    char **error)
@@ -2860,6 +2900,11 @@ static int evaluate(struct expression *in,
 	case EXPR_POLLFD:
 		result = evaluate_pollfd_expression(in, out, error);
 		break;
+#if defined(__FreeBSD__)
+	case EXPR_SF_HDTR:
+		result = evaluate_sf_hdtr_expression(in, out, error);
+		break;
+#endif
 	case EXPR_NONE:
 	case NUM_EXPR_TYPES:
 		break;
diff --git a/gtests/net/packetdrill/script.h b/gtests/net/packetdrill/script.h
index 0dc20025..41bf74a6 100644
--- a/gtests/net/packetdrill/script.h
+++ b/gtests/net/packetdrill/script.h
@@ -47,6 +47,9 @@ enum expression_t {
 	EXPR_MSGHDR,		  /* expression tree for a msghdr struct */
 	EXPR_CMSGHDR,             /* expression tree for a cmsghdr struct */
 	EXPR_POLLFD,		  /* expression tree for a pollfd struct */
+#if defined(__FreeBSD__)
+	EXPR_SF_HDTR,		  /* struct sf_hdtr for sendfile */
+#endif
 	EXPR_SCTP_RTOINFO,	  /* struct sctp_rtoinfo for SCTP_RTOINFO */
 	EXPR_SCTP_INITMSG,	  /* struct sctp_initmsg for SCTP_INITMSG */
 	EXPR_SCTP_ASSOC_VALUE,	  /* struct sctp_assoc_value */
@@ -113,6 +116,9 @@ struct expression {
 		struct msghdr_expr *msghdr;
 		struct cmsghdr_expr *cmsghdr;
 		struct pollfd_expr *pollfd;
+#if defined(__FreeBSD__)
+		struct sf_hdtr_expr *sf_hdtr;
+#endif
 		struct sctp_rtoinfo_expr *sctp_rtoinfo;
 		struct sctp_initmsg_expr *sctp_initmsg;
 		struct sctp_hmacalgo_expr *sctp_hmacalgo;
@@ -216,6 +222,16 @@ struct linger_expr {
 	struct expression *l_linger;
 };
 
+#if defined(__FreeBSD__)
+/* Parse tree for a sf_hdtr struct in a sendfile syscall. */
+struct sf_hdtr_expr {
+	struct expression *headers;
+	struct expression *hdr_cnt;
+	struct expression *trailers;
+	struct expression *trl_cnt;
+};
+#endif
+
 /* Parse tree for a sctp_rtoinfo struct in a [gs]etsockopt syscall. */
 struct sctp_rtoinfo_expr {
 	struct expression *srto_assoc_id;
diff --git a/gtests/net/packetdrill/symbols_freebsd.c b/gtests/net/packetdrill/symbols_freebsd.c
index 6cf0971c..577891c8 100644
--- a/gtests/net/packetdrill/symbols_freebsd.c
+++ b/gtests/net/packetdrill/symbols_freebsd.c
@@ -362,6 +362,9 @@ struct int_symbol platform_symbols_table[] = {
 	{ LOCK_EX,                          "LOCK_EX"                         },
 	{ LOCK_NB,                          "LOCK_NB"                         },
 	{ LOCK_UN,                          "LOCK_UN"                         },
+	{ SF_NODISKIO,                      "SF_NODISKIO"                     },
+	{ SF_NOCACHE,                       "SF_NOCACHE"                      },
+	{ SF_SYNC,                          "SF_SYNC"                         },
 
 	/* /usr/include/sys/unistd.h */
 	{ SEEK_SET,                         "SEEK_SET"                        },
-- 
GitLab