1 /*
2  *
3  * Copyright (c) 2007-2016 The University of Waikato, Hamilton, New Zealand.
4  * All rights reserved.
5  *
6  * This file is part of libwandio.
7  *
8  * This code has been developed by the University of Waikato WAND
9  * research group. For further information please see http://www.wand.net.nz/
10  *
11  * libwandio is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License as published by
13  * the Free Software Foundation; either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * libwandio is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23  *
24  *
25  */
26 
27 
28 #define _GNU_SOURCE 1
29 #include "config.h"
30 #include "wandio_internal.h"
31 #include "wandio.h"
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <sys/uio.h>
35 #include <fcntl.h>
36 #include <stdlib.h>
37 #include <unistd.h>
38 #include <string.h>
39 #include <assert.h>
40 
41 /* Libwandio IO module implementing a standard IO writer, i.e. no decompression
42  */
43 
44 enum { MIN_WRITE_SIZE = 4096 };
45 
46 struct stdiow_t {
47 	char buffer[MIN_WRITE_SIZE];
48 	int offset;
49 	int fd;
50 };
51 
52 extern iow_source_t stdio_wsource;
53 
54 #define DATA(iow) ((struct stdiow_t *)((iow)->data))
55 
safe_open(const char * filename,int flags)56 static int safe_open(const char *filename, int flags)
57 {
58 	int fd = -1;
59 	uid_t userid = 0;
60 	gid_t groupid = 0;
61 	char *sudoenv = NULL;
62 
63 /* Try opening with O_DIRECT */
64 #ifdef O_DIRECT
65 	fd = open(filename,
66 		flags
67 		|O_WRONLY
68 		|O_CREAT
69 		|O_TRUNC
70 		|(force_directio_write?O_DIRECT:0),
71 		0666);
72 #endif
73 /* If that failed (or we don't support O_DIRECT) try opening without */
74 	if (fd == -1) {
75 		fd = open(filename,
76 			flags
77 			|O_WRONLY
78 			|O_CREAT
79 			|O_TRUNC,
80 			0666);
81 	}
82 
83 	if (fd == -1)
84 		return fd;
85 
86 	/* If we're running via sudo, we want to write files owned by the
87 	 * original user rather than root.
88 	 *
89 	 * TODO: make this some sort of config option */
90 
91 	sudoenv = getenv("SUDO_UID");
92 	if (sudoenv != NULL) {
93 		userid = strtol(sudoenv, NULL, 10);
94 	}
95 	sudoenv = getenv("SUDO_GID");
96 	if (sudoenv != NULL) {
97 		groupid = strtol(sudoenv, NULL, 10);
98 	}
99 
100 	if (userid != 0 && fchown(fd, userid, groupid) == -1) {
101 		perror("fchown");
102 		return -1;
103 	}
104 
105 	return fd;
106 }
107 
stdio_wopen(const char * filename,int flags)108 iow_t *stdio_wopen(const char *filename,int flags)
109 {
110 	iow_t *iow = malloc(sizeof(iow_t));
111 	iow->source = &stdio_wsource;
112 	iow->data = malloc(sizeof(struct stdiow_t));
113 
114 	if (strcmp(filename,"-") == 0)
115 		DATA(iow)->fd = 1; /* STDOUT */
116 	else {
117 		DATA(iow)->fd = safe_open(filename, flags);
118 	}
119 
120 	if (DATA(iow)->fd == -1) {
121 		free(iow);
122 		return NULL;
123 	}
124 
125 	DATA(iow)->offset = 0;
126 
127 	return iow;
128 }
129 
130 #define min(a,b) ((a)<(b) ? (a) : (b))
131 #define max(a,b) ((a)>(b) ? (a) : (b))
132 /* Round A Down to the nearest multiple of B */
133 #define rounddown(a,b) ((a)-((a)%b)
134 
135 /* When doing directio (O_DIRECT) we need to make sure that we write multiples of MIN_WRITE_SIZE.
136  * So we accumulate data into DATA(iow)->buffer, and write it out when we get at least MIN_WRITE_SIZE.
137  *
138  * Since most writes are likely to be larger than MIN_WRITE_SIZE optimise for that case.
139  */
stdio_wwrite(iow_t * iow,const char * buffer,int64_t len)140 static int64_t stdio_wwrite(iow_t *iow, const char *buffer, int64_t len)
141 {
142 	int towrite = len;
143 	/* Round down size to the nearest multiple of MIN_WRITE_SIZE */
144 
145 	assert(towrite >= 0);
146 
147 	while (DATA(iow)->offset + towrite >= MIN_WRITE_SIZE) {
148 		int err;
149 		struct iovec iov[2];
150 		int total = (DATA(iow)->offset+towrite);
151 		int amount;
152 		int count=0;
153 		/* Round down to the nearest multiple */
154 		total = total - (total % MIN_WRITE_SIZE);
155 		amount = total;
156 		if (DATA(iow)->offset) {
157 			iov[count].iov_base = DATA(iow)->buffer;
158 			iov[count].iov_len = min(DATA(iow)->offset,amount);
159 			amount -= iov[count].iov_len;
160 			++count;
161 		}
162 		/* How much to write from this buffer? */
163 		if (towrite) {
164 			iov[count].iov_base = (void*)buffer; 	/* cast away constness, which is safe
165 								 * here
166 								 */
167 			iov[count].iov_len = amount;
168 			amount -= iov[count].iov_len;
169 			++count;
170 		}
171 		assert(amount == 0);
172 		err=writev(DATA(iow)->fd, iov, count);
173 		if (err==-1)
174 			return -1;
175 
176 		/* Drop off "err" bytes from the beginning of the buffers */
177 		amount = min(DATA(iow)->offset, err); /* How much we took out of the buffer */
178 		memmove(DATA(iow)->buffer,
179 			DATA(iow)->buffer+amount,
180 			DATA(iow)->offset-amount);
181 		DATA(iow)->offset -= amount;
182 
183 		err -= amount; /* How much was written */
184 
185 		assert(err <= towrite);
186 
187 		buffer += err;
188 		towrite -= err;
189 
190 		assert(DATA(iow)->offset == 0);
191 	}
192 
193 	/* Make sure we're not going to overflow the buffer.  The above writev should assure
194  	 * that this is true
195  	 */
196 	assert(DATA(iow)->offset + towrite <= MIN_WRITE_SIZE);
197 	assert(towrite >= 0);
198 
199 	if (towrite > 0) {
200 		/* Copy the remainder into the buffer to write next time. */
201 		memcpy(DATA(iow)->buffer + DATA(iow)->offset, buffer, towrite);
202 		DATA(iow)->offset += towrite;
203 	}
204 
205 	return len;
206 }
207 
stdio_wflush(iow_t * iow)208 static int stdio_wflush(iow_t *iow) {
209 
210 	int err;
211 	/* Now, there might be some non multiple of the direct filesize left over, if so turn off
212  	 * O_DIRECT and write the final chunk.
213  	 */
214 #ifdef O_DIRECT
215 	err=fcntl(DATA(iow)->fd, F_GETFL);
216 	if (err != -1 && (err & O_DIRECT) != 0) {
217 		fcntl(DATA(iow)->fd,F_SETFL, err & ~O_DIRECT);
218 	} else if (err < 0) {
219                 return err;
220         }
221 #endif
222 	err=write(DATA(iow)->fd, DATA(iow)->buffer, DATA(iow)->offset);
223 	DATA(iow)->offset = 0;
224         return err;
225 
226 }
227 
stdio_wclose(iow_t * iow)228 static void stdio_wclose(iow_t *iow)
229 {
230         stdio_wflush(iow);
231 	close(DATA(iow)->fd);
232 	free(iow->data);
233 	free(iow);
234 }
235 
236 iow_source_t stdio_wsource = {
237 	"stdiow",
238 	stdio_wwrite,
239         stdio_wflush,
240 	stdio_wclose
241 };
242