summaryrefslogtreecommitdiffstats
path: root/src/lib/sendfile-util.c
blob: 662285b14d35e2b19a429db2b0e2426c0bd75dc5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */

/* kludge a bit to remove _FILE_OFFSET_BITS definition from config.h.
   It's required to be able to include sys/sendfile.h with Linux. */
#include "config.h"
#undef HAVE_CONFIG_H

#ifdef HAVE_LINUX_SENDFILE
#  undef _FILE_OFFSET_BITS
#endif

#include "lib.h"
#include "sendfile-util.h"

#ifdef HAVE_LINUX_SENDFILE

#include <sys/sendfile.h>

ssize_t safe_sendfile(int out_fd, int in_fd, uoff_t *offset, size_t count)
{
	/* REMEMBER: uoff_t and off_t may not be of same size. */
	off_t safe_offset;
	ssize_t ret;

	i_assert(count > 0);

	/* make sure given offset fits into off_t */
	if (sizeof(off_t) * CHAR_BIT == 32) {
		/* 32bit off_t */
		if (*offset >= 2147483647L) {
			errno = EINVAL;
			return -1;
		}
		if (count > 2147483647L - *offset)
			count = 2147483647L - *offset;
	} else {
		/* they're most likely the same size. if not, fix this
		   code later */
		i_assert(sizeof(off_t) == sizeof(uoff_t));

		if (*offset >= OFF_T_MAX) {
			errno = EINVAL;
			return -1;
		}
		if (count > OFF_T_MAX - *offset)
			count = OFF_T_MAX - *offset;
	}

	safe_offset = (off_t)*offset;
	ret = sendfile(out_fd, in_fd, &safe_offset, count);
	/* ret=0 : trying to read past EOF */
	*offset = (uoff_t)safe_offset;
	return ret;
}

#elif defined(HAVE_FREEBSD_SENDFILE)

#include <sys/socket.h>
#include <sys/uio.h>

ssize_t safe_sendfile(int out_fd, int in_fd, uoff_t *offset, size_t count)
{
	struct sf_hdtr hdtr;
	off_t sbytes;
	int ret;

	/* if count=0 is passed to sendfile(), it sends everything
	   from in_fd until EOF. We don't want that. */
	i_assert(count > 0);
	i_assert(count <= SSIZE_T_MAX);

	i_zero(&hdtr);
	ret = sendfile(in_fd, out_fd, *offset, count, &hdtr, &sbytes, 0);

	*offset += sbytes;

	if (ret == 0 || (ret < 0 && errno == EAGAIN && sbytes > 0))
		return (ssize_t)sbytes;
	else {
		if (errno == ENOTSOCK) {
			/* out_fd wasn't a socket. behave as if sendfile()
			   wasn't supported at all. */
			errno = EINVAL;
		}
		return -1;
	}
}

#elif defined (HAVE_SOLARIS_SENDFILE)

#include <sys/sendfile.h>
#include "net.h"

ssize_t safe_sendfile(int out_fd, int in_fd, uoff_t *offset, size_t count)
{
	ssize_t ret;
	off_t s_offset;

	i_assert(count > 0);
	i_assert(count <= SSIZE_T_MAX);

	/* NOTE: if outfd is not a socket, some Solaris versions will
	   kernel panic */

	s_offset = (off_t)*offset;
	ret = sendfile(out_fd, in_fd, &s_offset, count);

	if (ret < 0) {
		/* if remote is gone, EPIPE is returned */
		if (errno == EINVAL) {
			/* most likely trying to read past EOF */
			ret = 0;
		} else if (errno == EAFNOSUPPORT || errno == EOPNOTSUPP) {
			/* not supported, return Linux-like EINVAL so caller
			   sees only consistent errnos. */
			errno = EINVAL;
		} else if (s_offset != (off_t)*offset) {
			/* some data was sent, return it */
			i_assert(s_offset > (off_t)*offset);
			ret = s_offset - (off_t)*offset;
		}
	}
	*offset = (uoff_t)s_offset;
	i_assert(ret < 0 || (size_t)ret <= count);
	return ret;
}

#else
ssize_t safe_sendfile(int out_fd ATTR_UNUSED, int in_fd ATTR_UNUSED,
		      uoff_t *offset ATTR_UNUSED,
		      size_t count ATTR_UNUSED)
{
	errno = EINVAL;
	return -1;
}

#endif