summaryrefslogtreecommitdiffstats
path: root/cpukit/shttpd/io_file.c
blob: d2b3fb66aac1a9c8778378c495aff85c00250a73 (plain) (blame)
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/*
 * Copyright (c) 2004-2005 Sergey Lyubka <valenok@gmail.com>
 * All rights reserved
 *
 * "THE BEER-WARE LICENSE" (Revision 42):
 * Sergey Lyubka wrote this file.  As long as you retain this notice you
 * can do whatever you want with this stuff. If we meet some day, and you think
 * this stuff is worth it, you can buy me a beer in return.
 */

#include "defs.h"

static int
write_file(struct stream *stream, const void *buf, size_t len)
{
	struct stat	st;
	struct stream	*rem = &stream->conn->rem;
	int		n, fd = stream->chan.fd;

	assert(fd != -1);
	n = write(fd, buf, len);

	DBG(("put_file(%p, %d): %d bytes", (void *) stream, len, n));

	if (n <= 0 || (rem->io.total >= (big_int_t) rem->headers_len)) {
		(void) fstat(fd, &st);
		stream->io.head = stream->headers_len =
		    my_snprintf(stream->io.buf,
		    stream->io.size, "HTTP/1.1 %d OK\r\n"
		    "Content-Length: %lu\r\nConnection: close\r\n\r\n",
		    stream->conn->status, st.st_size);
		stop_stream(stream);
	}

	return (n);
}

static int
read_file(struct stream *stream, void *buf, size_t len)
{
#ifdef USE_SENDFILE
	struct	iovec	vec;
	struct	sf_hdtr	hd = {&vec, 1, NULL, 0}, *hdp = &hd;
	int		sock, fd, n;
	size_t		nbytes;
	off_t		sent;

	sock = stream->conn->rem.chan.sock;
	fd = stream->chan.fd;

	/* If this is the first call for this file, send the headers */
	vec.iov_base = stream->io.buf;
	vec.iov_len = stream->headers_len;
	if (stream->io.total > 0)
		hdp = NULL;

	nbytes = stream->content_len - stream->io.total;
	n = sendfile(fd, sock, lseek(fd, 0, SEEK_CUR), nbytes, hdp, &sent, 0);

	if (n == -1 && ERRNO != EAGAIN) {
		stream->flags &= ~FLAG_DONT_CLOSE;
		return (n);
	}

	stream->conn->ctx->out += sent;

	/* If we have sent the HTTP headers in this turn, clear them off */
	if (stream->io.total == 0) {
		assert(sent >= stream->headers_len);
		sent -= stream->headers_len;
		io_clear(&stream->io);
	}

	(void) lseek(fd, sent, SEEK_CUR);
	stream->io.total += sent;
	stream->flags |= FLAG_DONT_CLOSE;

	return (0);
#endif /* USE_SENDFILE */

	assert(stream->chan.fd != -1);
	return (read(stream->chan.fd, buf, len));
}

static void
close_file(struct stream *stream)
{
	assert(stream->chan.fd != -1);
	(void) close(stream->chan.fd);
}

void
get_file(struct conn *c, struct stat *stp)
{
	char		date[64], lm[64], etag[64], range[64] = "";
	size_t		n, status = 200;
	unsigned long	r1, r2;
	const char	*fmt = "%a, %d %b %Y %H:%M:%S GMT", *msg = "OK";
	big_int_t	cl; /* Content-Length */

	if (c->mime_type == NULL)
		c->mime_type = get_mime_type(c->ctx, c->uri, strlen(c->uri));
	cl = (big_int_t) stp->st_size;

	/* If Range: header specified, act accordingly */
	if (c->ch.range.v_vec.len > 0 &&
	    (n = sscanf(c->ch.range.v_vec.ptr,"bytes=%lu-%lu",&r1, &r2)) > 0) {
		status = 206;
		(void) lseek(c->loc.chan.fd, r1, SEEK_SET);
		cl = n == 2 ? r2 - r1 + 1: cl - r1;
		(void) my_snprintf(range, sizeof(range),
		    "Content-Range: bytes %lu-%lu/%lu\r\n",
		    r1, r1 + cl - 1, (unsigned long) stp->st_size);
		msg = "Partial Content";
	}

	/* Prepare Etag, Date, Last-Modified headers */
	(void) strftime(date, sizeof(date), fmt, localtime(&current_time));
	(void) strftime(lm, sizeof(lm), fmt, localtime(&stp->st_mtime));
	(void) my_snprintf(etag, sizeof(etag), "%lx.%lx",
	    (unsigned long) stp->st_mtime, (unsigned long) stp->st_size);

	/*
	 * We do not do io_inc_head here, because it will increase 'total'
	 * member in io. We want 'total' to be equal to the content size,
	 * and exclude the headers length from it.
	 */
	c->loc.io.head = c->loc.headers_len = my_snprintf(c->loc.io.buf,
	    c->loc.io.size,
	    "HTTP/1.1 %d %s\r\n"
	    "Date: %s\r\n"
	    "Last-Modified: %s\r\n"
	    "Etag: \"%s\"\r\n"
	    "Content-Type: %s\r\n"
	    "Content-Length: %lu\r\n"
	    "Connection: close\r\n"
	    "%s\r\n",
	    status, msg, date, lm, etag, c->mime_type, cl, range);

	c->status = status;
	c->loc.content_len = cl;
	c->loc.io_class = &io_file;
	c->loc.flags |= FLAG_R | FLAG_ALWAYS_READY;

	if (c->method == METHOD_HEAD)
		stop_stream(&c->loc);
}

const struct io_class	io_file =  {
	"file",
	read_file,
	write_file,
	close_file
};