Line data Source code
1 : /*
2 : * Copyright (c) James Peach 2006, 2007
3 : * Copyright (c) David Losada Carballo 2007
4 : *
5 : * This program is free software; you can redistribute it and/or modify
6 : * it under the terms of the GNU General Public License as published by
7 : * the Free Software Foundation; either version 3 of the License, or
8 : * (at your option) any later version.
9 : *
10 : * This program is distributed in the hope that it will be useful,
11 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : * GNU General Public License for more details.
14 : *
15 : * You should have received a copy of the GNU General Public License
16 : * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 : */
18 :
19 : #include "includes.h"
20 : #include "system/filesys.h"
21 : #include "smbd/smbd.h"
22 : #include "lib/util/tevent_unix.h"
23 :
24 : /* Commit data module.
25 : *
26 : * The purpose of this module is to flush data to disk at regular intervals,
27 : * just like the NFS commit operation. There's two rationales for this. First,
28 : * it minimises the data loss in case of a power outage without incurring
29 : * the poor performance of synchronous I/O. Second, a steady flush rate
30 : * can produce better throughput than suddenly dumping massive amounts of
31 : * writes onto a disk.
32 : *
33 : * Tunables:
34 : *
35 : * commit: dthresh Amount of dirty data that can accumulate
36 : * before we commit (sync) it.
37 : *
38 : * commit: debug Debug level at which to emit messages.
39 : *
40 : * commit: eof mode String. Tunes how the module tries to guess when
41 : * the client has written the last bytes of the file.
42 : * Possible values (default = hinted):
43 : *
44 : * (*) = hinted Some clients (i.e. Windows Explorer) declare the
45 : * size of the file before transferring it. With this
46 : * option, we remember that hint, and commit after
47 : * writing in that file position. If the client
48 : * doesn't declare the size of file, committing on EOF
49 : * is not triggered.
50 : *
51 : * = growth Commits after a write operation has made the file
52 : * size grow. If the client declares a file size, it
53 : * refrains to commit until the file has reached it.
54 : * Useful for defeating writeback on NFS shares.
55 : *
56 : */
57 :
58 : #define MODULE "commit"
59 :
60 : static int module_debug;
61 :
62 : enum eof_mode
63 : {
64 : EOF_NONE = 0x0000,
65 : EOF_HINTED = 0x0001,
66 : EOF_GROWTH = 0x0002
67 : };
68 :
69 : struct commit_info
70 : {
71 : /* For chunk-based commits */
72 : off_t dbytes; /* Dirty (uncommitted) bytes */
73 : off_t dthresh; /* Dirty data threshold */
74 : /* For commits on EOF */
75 : enum eof_mode on_eof;
76 : off_t eof; /* Expected file size */
77 : };
78 :
79 0 : static int commit_do(
80 : struct commit_info * c,
81 : int fd)
82 : {
83 : int result;
84 :
85 0 : DEBUG(module_debug,
86 : ("%s: flushing %lu dirty bytes\n",
87 : MODULE, (unsigned long)c->dbytes));
88 :
89 : #if defined(HAVE_FDATASYNC)
90 0 : result = fdatasync(fd);
91 : #elif defined(HAVE_FSYNC)
92 : result = fsync(fd);
93 : #else
94 : DEBUG(0, ("%s: WARNING: no commit support on this platform\n",
95 : MODULE));
96 : result = 0
97 : #endif
98 0 : if (result == 0) {
99 0 : c->dbytes = 0; /* on success, no dirty bytes */
100 : }
101 0 : return result;
102 : }
103 :
104 0 : static int commit_all(
105 : struct vfs_handle_struct * handle,
106 : files_struct * fsp)
107 : {
108 : struct commit_info *c;
109 :
110 0 : if ((c = (struct commit_info *)VFS_FETCH_FSP_EXTENSION(handle, fsp))) {
111 0 : if (c->dbytes) {
112 0 : DEBUG(module_debug,
113 : ("%s: flushing %lu dirty bytes\n",
114 : MODULE, (unsigned long)c->dbytes));
115 :
116 0 : return commit_do(c, fsp_get_io_fd(fsp));
117 : }
118 : }
119 0 : return 0;
120 : }
121 :
122 0 : static int commit(
123 : struct vfs_handle_struct * handle,
124 : files_struct * fsp,
125 : off_t offset,
126 : ssize_t last_write)
127 : {
128 : struct commit_info *c;
129 :
130 0 : if ((c = (struct commit_info *)VFS_FETCH_FSP_EXTENSION(handle, fsp))
131 : == NULL) {
132 0 : return 0;
133 : }
134 :
135 0 : c->dbytes += last_write; /* dirty bytes always counted */
136 :
137 0 : if (c->dthresh && (c->dbytes > c->dthresh)) {
138 0 : return commit_do(c, fsp_get_io_fd(fsp));
139 : }
140 :
141 : /* Return if we are not in EOF mode or if we have temporarily opted
142 : * out of it.
143 : */
144 0 : if (c->on_eof == EOF_NONE || c->eof < 0) {
145 0 : return 0;
146 : }
147 :
148 : /* This write hit or went past our cache the file size. */
149 0 : if ((offset + last_write) >= c->eof) {
150 0 : if (commit_do(c, fsp_get_io_fd(fsp)) == -1) {
151 0 : return -1;
152 : }
153 :
154 : /* Hinted mode only commits the first time we hit EOF. */
155 0 : if (c->on_eof == EOF_HINTED) {
156 0 : c->eof = -1;
157 0 : } else if (c->on_eof == EOF_GROWTH) {
158 0 : c->eof = offset + last_write;
159 : }
160 : }
161 :
162 0 : return 0;
163 : }
164 :
165 0 : static int commit_connect(
166 : struct vfs_handle_struct * handle,
167 : const char * service,
168 : const char * user)
169 : {
170 0 : int ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
171 :
172 0 : if (ret < 0) {
173 0 : return ret;
174 : }
175 :
176 0 : module_debug = lp_parm_int(SNUM(handle->conn), MODULE, "debug", 100);
177 0 : return 0;
178 : }
179 :
180 0 : static int commit_openat(struct vfs_handle_struct *handle,
181 : const struct files_struct *dirfsp,
182 : const struct smb_filename *smb_fname,
183 : files_struct *fsp,
184 : const struct vfs_open_how *how)
185 : {
186 : off_t dthresh;
187 : const char *eof_mode;
188 0 : struct commit_info *c = NULL;
189 : int fd;
190 :
191 : /* Don't bother with read-only files. */
192 0 : if ((how->flags & O_ACCMODE) == O_RDONLY) {
193 0 : return SMB_VFS_NEXT_OPENAT(handle,
194 : dirfsp,
195 : smb_fname,
196 : fsp,
197 : how);
198 : }
199 :
200 : /* Read and check module configuration */
201 0 : dthresh = conv_str_size(lp_parm_const_string(SNUM(handle->conn),
202 : MODULE, "dthresh", NULL));
203 :
204 0 : eof_mode = lp_parm_const_string(SNUM(handle->conn),
205 : MODULE, "eof mode", "none");
206 :
207 0 : if (dthresh > 0 || !strequal(eof_mode, "none")) {
208 0 : c = VFS_ADD_FSP_EXTENSION(
209 : handle, fsp, struct commit_info, NULL);
210 : /* Process main tunables */
211 0 : if (c) {
212 0 : c->dthresh = dthresh;
213 0 : c->dbytes = 0;
214 0 : c->on_eof = EOF_NONE;
215 0 : c->eof = 0;
216 : }
217 : }
218 : /* Process eof_mode tunable */
219 0 : if (c) {
220 0 : if (strequal(eof_mode, "hinted")) {
221 0 : c->on_eof = EOF_HINTED;
222 0 : } else if (strequal(eof_mode, "growth")) {
223 0 : c->on_eof = EOF_GROWTH;
224 : }
225 : }
226 :
227 0 : fd = SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, how);
228 0 : if (fd == -1) {
229 0 : VFS_REMOVE_FSP_EXTENSION(handle, fsp);
230 0 : return fd;
231 : }
232 :
233 : /* EOF commit modes require us to know the initial file size. */
234 0 : if (c && (c->on_eof != EOF_NONE)) {
235 : SMB_STRUCT_STAT st;
236 : /*
237 : * Setting the fd of the FSP is a hack
238 : * but also practiced elsewhere -
239 : * needed for calling the VFS.
240 : */
241 0 : fsp_set_fd(fsp, fd);
242 0 : if (SMB_VFS_FSTAT(fsp, &st) == -1) {
243 0 : int saved_errno = errno;
244 0 : SMB_VFS_CLOSE(fsp);
245 0 : fsp_set_fd(fsp, -1);
246 0 : errno = saved_errno;
247 0 : return -1;
248 : }
249 0 : c->eof = st.st_ex_size;
250 : }
251 :
252 0 : return fd;
253 : }
254 :
255 0 : static ssize_t commit_pwrite(
256 : vfs_handle_struct * handle,
257 : files_struct * fsp,
258 : const void * data,
259 : size_t count,
260 : off_t offset)
261 : {
262 : ssize_t ret;
263 :
264 0 : ret = SMB_VFS_NEXT_PWRITE(handle, fsp, data, count, offset);
265 0 : if (ret > 0) {
266 0 : if (commit(handle, fsp, offset, ret) == -1) {
267 0 : return -1;
268 : }
269 : }
270 :
271 0 : return ret;
272 : }
273 :
274 : struct commit_pwrite_state {
275 : struct vfs_handle_struct *handle;
276 : struct files_struct *fsp;
277 : ssize_t ret;
278 : struct vfs_aio_state vfs_aio_state;
279 : };
280 :
281 : static void commit_pwrite_written(struct tevent_req *subreq);
282 :
283 0 : static struct tevent_req *commit_pwrite_send(struct vfs_handle_struct *handle,
284 : TALLOC_CTX *mem_ctx,
285 : struct tevent_context *ev,
286 : struct files_struct *fsp,
287 : const void *data,
288 : size_t n, off_t offset)
289 : {
290 : struct tevent_req *req, *subreq;
291 : struct commit_pwrite_state *state;
292 :
293 0 : req = tevent_req_create(mem_ctx, &state, struct commit_pwrite_state);
294 0 : if (req == NULL) {
295 0 : return NULL;
296 : }
297 0 : state->handle = handle;
298 0 : state->fsp = fsp;
299 :
300 0 : subreq = SMB_VFS_NEXT_PWRITE_SEND(state, ev, handle, fsp, data,
301 : n, offset);
302 0 : if (tevent_req_nomem(subreq, req)) {
303 0 : return tevent_req_post(req, ev);
304 : }
305 0 : tevent_req_set_callback(subreq, commit_pwrite_written, req);
306 0 : return req;
307 : }
308 :
309 0 : static void commit_pwrite_written(struct tevent_req *subreq)
310 : {
311 0 : struct tevent_req *req = tevent_req_callback_data(
312 : subreq, struct tevent_req);
313 0 : struct commit_pwrite_state *state = tevent_req_data(
314 : req, struct commit_pwrite_state);
315 : int commit_ret;
316 :
317 0 : state->ret = SMB_VFS_PWRITE_RECV(subreq, &state->vfs_aio_state);
318 0 : TALLOC_FREE(subreq);
319 :
320 0 : if (state->ret <= 0) {
321 0 : tevent_req_done(req);
322 0 : return;
323 : }
324 :
325 : /*
326 : * Ok, this is a sync fake. We should make the sync async as well, but
327 : * I'm too lazy for that right now -- vl
328 : */
329 0 : commit_ret = commit(state->handle,
330 0 : state->fsp,
331 0 : fh_get_pos(state->fsp->fh),
332 : state->ret);
333 :
334 0 : if (commit_ret == -1) {
335 0 : state->ret = -1;
336 : }
337 :
338 0 : tevent_req_done(req);
339 : }
340 :
341 0 : static ssize_t commit_pwrite_recv(struct tevent_req *req,
342 : struct vfs_aio_state *vfs_aio_state)
343 : {
344 : struct commit_pwrite_state *state =
345 0 : tevent_req_data(req, struct commit_pwrite_state);
346 :
347 0 : if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
348 0 : return -1;
349 : }
350 0 : *vfs_aio_state = state->vfs_aio_state;
351 0 : return state->ret;
352 : }
353 :
354 0 : static int commit_close(
355 : vfs_handle_struct * handle,
356 : files_struct * fsp)
357 : {
358 : /* Commit errors not checked, close() will find them again */
359 0 : commit_all(handle, fsp);
360 0 : return SMB_VFS_NEXT_CLOSE(handle, fsp);
361 : }
362 :
363 0 : static int commit_ftruncate(
364 : vfs_handle_struct * handle,
365 : files_struct * fsp,
366 : off_t len)
367 : {
368 : int result;
369 :
370 0 : result = SMB_VFS_NEXT_FTRUNCATE(handle, fsp, len);
371 0 : if (result == 0) {
372 : struct commit_info *c;
373 0 : if ((c = (struct commit_info *)VFS_FETCH_FSP_EXTENSION(
374 : handle, fsp))) {
375 0 : commit(handle, fsp, len, 0);
376 0 : c->eof = len;
377 : }
378 : }
379 :
380 0 : return result;
381 : }
382 :
383 : static struct vfs_fn_pointers vfs_commit_fns = {
384 : .openat_fn = commit_openat,
385 : .close_fn = commit_close,
386 : .pwrite_fn = commit_pwrite,
387 : .pwrite_send_fn = commit_pwrite_send,
388 : .pwrite_recv_fn = commit_pwrite_recv,
389 : .connect_fn = commit_connect,
390 : .ftruncate_fn = commit_ftruncate
391 : };
392 :
393 : static_decl_vfs;
394 27 : NTSTATUS vfs_commit_init(TALLOC_CTX *ctx)
395 : {
396 27 : return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, MODULE,
397 : &vfs_commit_fns);
398 : }
399 :
400 :
|