1 #include "network_backends.h"
2 
3 #ifdef USE_FREEBSD_SENDFILE
4 
5 #include "network.h"
6 #include "fdevent.h"
7 #include "log.h"
8 #include "stat_cache.h"
9 
10 #include <sys/types.h>
11 #include <sys/socket.h>
12 #include <sys/stat.h>
13 #include <sys/time.h>
14 #include <sys/resource.h>
15 
16 #include <netinet/in.h>
17 #include <netinet/tcp.h>
18 
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <unistd.h>
22 #include <netdb.h>
23 #include <string.h>
24 #include <stdlib.h>
25 
26 
27 #ifndef UIO_MAXIOV
28 # if defined(__FreeBSD__) || defined(__DragonFly__)
29 /* FreeBSD 4.7, 4.9 defined it in sys/uio.h only if _KERNEL is specified */
30 #  define UIO_MAXIOV 1024
31 # endif
32 #endif
33 
network_write_chunkqueue_freebsdsendfile(server * srv,connection * con,int fd,chunkqueue * cq,off_t max_bytes)34 int network_write_chunkqueue_freebsdsendfile(server *srv, connection *con, int fd, chunkqueue *cq, off_t max_bytes) {
35 	chunk *c;
36 
37 	for(c = cq->first; (max_bytes > 0) && (NULL != c); c = c->next) {
38 		int chunk_finished = 0;
39 
40 		switch(c->type) {
41 		case MEM_CHUNK: {
42 			char * offset;
43 			off_t toSend;
44 			ssize_t r;
45 
46 			size_t num_chunks, i;
47 			struct iovec chunks[UIO_MAXIOV];
48 			chunk *tc;
49 			size_t num_bytes = 0;
50 
51 			/* build writev list
52 			 *
53 			 * 1. limit: num_chunks < UIO_MAXIOV
54 			 * 2. limit: num_bytes < max_bytes
55 			 */
56 			for(num_chunks = 0, tc = c; tc && tc->type == MEM_CHUNK && num_chunks < UIO_MAXIOV; num_chunks++, tc = tc->next);
57 
58 			for(tc = c, i = 0; i < num_chunks; tc = tc->next, i++) {
59 				if (tc->mem->used == 0) {
60 					chunks[i].iov_base = tc->mem->ptr;
61 					chunks[i].iov_len  = 0;
62 				} else {
63 					offset = tc->mem->ptr + tc->offset;
64 					toSend = tc->mem->used - 1 - tc->offset;
65 
66 					chunks[i].iov_base = offset;
67 
68 					/* protect the return value of writev() */
69 					if (toSend > max_bytes ||
70 					    (off_t) num_bytes + toSend > max_bytes) {
71 						chunks[i].iov_len = max_bytes - num_bytes;
72 
73 						num_chunks = i + 1;
74 						break;
75 					} else {
76 						chunks[i].iov_len = toSend;
77 					}
78 
79 					num_bytes += toSend;
80 				}
81 			}
82 
83 			if ((r = writev(fd, chunks, num_chunks)) < 0) {
84 				switch (errno) {
85 				case EAGAIN:
86 				case EINTR:
87 					r = 0;
88 					break;
89 				case ENOTCONN:
90 				case EPIPE:
91 				case ECONNRESET:
92 					return -2;
93 				default:
94 					log_error_write(srv, __FILE__, __LINE__, "ssd",
95 							"writev failed:", strerror(errno), fd);
96 
97 					return -1;
98 				}
99 
100 				r = 0;
101 			}
102 
103 			/* check which chunks have been written */
104 			cq->bytes_out += r;
105 			max_bytes -= r;
106 
107 			for(i = 0, tc = c; i < num_chunks; i++, tc = tc->next) {
108 				if (r >= (ssize_t)chunks[i].iov_len) {
109 					/* written */
110 					r -= chunks[i].iov_len;
111 					tc->offset += chunks[i].iov_len;
112 
113 					if (chunk_finished) {
114 						/* skip the chunks from further touches */
115 						c = c->next;
116 					} else {
117 						/* chunks_written + c = c->next is done in the for()*/
118 						chunk_finished = 1;
119 					}
120 				} else {
121 					/* partially written */
122 
123 					tc->offset += r;
124 					chunk_finished = 0;
125 
126 					break;
127 				}
128 			}
129 
130 			break;
131 		}
132 		case FILE_CHUNK: {
133 			off_t offset, r;
134 			off_t toSend;
135 			stat_cache_entry *sce = NULL;
136 
137 			if (HANDLER_ERROR == stat_cache_get_entry(srv, con, c->file.name, &sce)) {
138 				log_error_write(srv, __FILE__, __LINE__, "sb",
139 						strerror(errno), c->file.name);
140 				return -1;
141 			}
142 
143 			offset = c->file.start + c->offset;
144 			toSend = c->file.length - c->offset;
145 			if (toSend > max_bytes) toSend = max_bytes;
146 
147 			if (-1 == c->file.fd) {
148 				if (-1 == (c->file.fd = open(c->file.name->ptr, O_RDONLY))) {
149 					log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno));
150 
151 					return -1;
152 				}
153 
154 #ifdef FD_CLOEXEC
155 				fcntl(c->file.fd, F_SETFD, FD_CLOEXEC);
156 #endif
157 			}
158 
159 			r = 0;
160 
161 			/* FreeBSD sendfile() */
162 			if (-1 == sendfile(c->file.fd, fd, offset, toSend, NULL, &r, 0)) {
163 				switch(errno) {
164 				case EAGAIN:
165 				case EINTR:
166 					/* for EAGAIN/EINTR r still contains the sent bytes */
167 					break; /* try again later */
168 				case EPIPE:
169 				case ENOTCONN:
170 					return -2;
171 				default:
172 					log_error_write(srv, __FILE__, __LINE__, "ssd", "sendfile: ", strerror(errno), errno);
173 					return -1;
174 				}
175 			} else if (r == 0) {
176 				/* We got an event to write but we wrote nothing
177 				 *
178 				 * - the file shrinked -> error
179 				 * - the remote side closed inbetween -> remote-close */
180 
181 				if (HANDLER_ERROR == stat_cache_get_entry(srv, con, c->file.name, &sce)) {
182 					/* file is gone ? */
183 					return -1;
184 				}
185 
186 				if (offset >= sce->st.st_size) {
187 					/* file shrinked, close the connection */
188 					return -1;
189 				}
190 
191 				return -2;
192 			}
193 
194 			c->offset += r;
195 			cq->bytes_out += r;
196 			max_bytes -= r;
197 
198 			if (c->offset == c->file.length) {
199 				chunk_finished = 1;
200 			}
201 
202 			break;
203 		}
204 		default:
205 
206 			log_error_write(srv, __FILE__, __LINE__, "ds", c, "type not known");
207 
208 			return -1;
209 		}
210 
211 		if (!chunk_finished) {
212 			/* not finished yet */
213 
214 			break;
215 		}
216 	}
217 
218 	return 0;
219 }
220 
221 #endif
222