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