1 /*
2  * Copyright (C) 2011 Andrea Mazzoleni
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "portable.h"
19 
20 #include "elem.h"
21 #include "support.h"
22 #include "handle.h"
23 
24 /****************************************************************************/
25 /* handle */
26 
handle_create(struct snapraid_handle * handle,struct snapraid_file * file,int mode)27 int handle_create(struct snapraid_handle* handle, struct snapraid_file* file, int mode)
28 {
29 	int ret;
30 	int flags;
31 
32 	/* if it's the same file, and already opened, nothing to do */
33 	if (handle->file == file && handle->f != -1) {
34 		return 0;
35 	}
36 
37 	advise_init(&handle->advise, mode);
38 	pathprint(handle->path, sizeof(handle->path), "%s%s", handle->disk->dir, file->sub);
39 
40 	ret = mkancestor(handle->path);
41 	if (ret != 0) {
42 		/* LCOV_EXCL_START */
43 		return -1;
44 		/* LCOV_EXCL_STOP */
45 	}
46 
47 	/* initial values, changed later if required */
48 	handle->created = 0;
49 
50 	/* flags for opening */
51 	/* O_BINARY: open as binary file (Windows only) */
52 	/* O_NOFOLLOW: do not follow links to ensure to open the real file */
53 	flags = O_BINARY | O_NOFOLLOW | advise_flags(&handle->advise);
54 
55 	/* open for read write */
56 	handle->f = open(handle->path, flags | O_RDWR);
57 
58 	/* if failed for missing write permission */
59 	if (handle->f == -1 && (errno == EACCES || errno == EROFS)) {
60 		/* open for real-only */
61 		handle->f = open(handle->path, flags | O_RDONLY);
62 	}
63 
64 	/* if failed for missing file */
65 	if (handle->f == -1 && errno == ENOENT) {
66 		char path_from[PATH_MAX];
67 
68 		/* check if exists a .unrecoverable copy, and rename to the real one */
69 		pathprint(path_from, sizeof(path_from), "%s.unrecoverable", handle->path);
70 
71 		if (rename(path_from, handle->path) == 0) {
72 			/* open for read write */
73 			handle->f = open(handle->path, flags | O_RDWR);
74 		} else {
75 			/* create it */
76 			handle->f = open(handle->path, flags | O_RDWR | O_CREAT, 0600);
77 			if (handle->f != -1) {
78 				/* mark it as created if really done */
79 				handle->created = 1;
80 			}
81 		}
82 	}
83 
84 	if (handle->f == -1) {
85 		/* LCOV_EXCL_START */
86 		/* invalidate for error */
87 		handle->file = 0;
88 		handle->f = -1;
89 		handle->valid_size = 0;
90 
91 		log_fatal("Error opening file '%s'. %s.\n", handle->path, strerror(errno));
92 		return -1;
93 		/* LCOV_EXCL_STOP */
94 	}
95 
96 	/* just opened */
97 	handle->file = file;
98 
99 	/* get the stat info */
100 	ret = fstat(handle->f, &handle->st);
101 	if (ret != 0) {
102 		/* LCOV_EXCL_START */
103 		log_fatal("Error accessing file '%s'. %s.\n", handle->path, strerror(errno));
104 		return -1;
105 		/* LCOV_EXCL_STOP */
106 	}
107 
108 	/* get the size of the existing data */
109 	handle->valid_size = handle->st.st_size;
110 
111 	ret = advise_open(&handle->advise, handle->f);
112 	if (ret != 0) {
113 		/* LCOV_EXCL_START */
114 		log_fatal("Error advising file '%s'. %s.\n", handle->path, strerror(errno));
115 		return -1;
116 		/* LCOV_EXCL_STOP */
117 	}
118 
119 	return 0;
120 }
121 
handle_truncate(struct snapraid_handle * handle,struct snapraid_file * file)122 int handle_truncate(struct snapraid_handle* handle, struct snapraid_file* file)
123 {
124 	int ret;
125 
126 	ret = ftruncate(handle->f, file->size);
127 	if (ret != 0) {
128 		/* LCOV_EXCL_START */
129 		if (errno == EACCES) {
130 			log_fatal("Failed to truncate file '%s' for missing write permission.\n", handle->path);
131 		} else {
132 			log_fatal("Error truncating file '%s'. %s.\n", handle->path, strerror(errno));
133 		}
134 		return -1;
135 		/* LCOV_EXCL_STOP */
136 	}
137 
138 	/* adjust the size to the truncated size */
139 	handle->valid_size = file->size;
140 
141 	return 0;
142 }
143 
handle_open(struct snapraid_handle * handle,struct snapraid_file * file,int mode,fptr * out,fptr * out_missing)144 int handle_open(struct snapraid_handle* handle, struct snapraid_file* file, int mode, fptr* out, fptr* out_missing)
145 {
146 	int ret;
147 	int flags;
148 
149 	if (!out_missing)
150 		out_missing = out;
151 
152 	/* if already opened, nothing to do */
153 	if (handle->file == file && handle->file != 0 && handle->f != -1) {
154 		return 0;
155 	}
156 
157 	advise_init(&handle->advise, mode);
158 	pathprint(handle->path, sizeof(handle->path), "%s%s", handle->disk->dir, file->sub);
159 
160 	/* for sure not created */
161 	handle->created = 0;
162 
163 	/* flags for opening */
164 	/* O_BINARY: open as binary file (Windows only) */
165 	/* O_NOFOLLOW: do not follow links to ensure to open the real file */
166 	flags = O_BINARY | O_NOFOLLOW | advise_flags(&handle->advise);
167 
168 	/* open for read */
169 	handle->f = open_noatime(handle->path, flags | O_RDONLY);
170 	if (handle->f == -1) {
171 		/* invalidate for error */
172 		handle->file = 0;
173 		handle->f = -1;
174 		handle->valid_size = 0;
175 
176 		if (errno == ENOENT)
177 			out_missing("Missing file '%s'.\n", handle->path);
178 		else
179 			out("Error opening file '%s'. %s.\n", handle->path, strerror(errno));
180 		return -1;
181 	}
182 
183 	/* just opened */
184 	handle->file = file;
185 
186 	/* get the stat info */
187 	ret = fstat(handle->f, &handle->st);
188 	if (ret != 0) {
189 		/* LCOV_EXCL_START */
190 		out("Error accessing file '%s'. %s.\n", handle->path, strerror(errno));
191 		return -1;
192 		/* LCOV_EXCL_STOP */
193 	}
194 
195 	/* get the size of the existing data */
196 	handle->valid_size = handle->st.st_size;
197 
198 	ret = advise_open(&handle->advise, handle->f);
199 	if (ret != 0) {
200 		/* LCOV_EXCL_START */
201 		out("Error advising file '%s'. %s.\n", handle->path, strerror(errno));
202 		return -1;
203 		/* LCOV_EXCL_STOP */
204 	}
205 
206 	return 0;
207 }
208 
handle_close(struct snapraid_handle * handle)209 int handle_close(struct snapraid_handle* handle)
210 {
211 	int ret;
212 
213 	/* close if open */
214 	if (handle->f != -1) {
215 		ret = close(handle->f);
216 		if (ret != 0) {
217 			/* LCOV_EXCL_START */
218 			log_fatal("Error closing file '%s'. %s.\n", handle->file->sub, strerror(errno));
219 
220 			/* invalidate for error */
221 			handle->file = 0;
222 			handle->f = -1;
223 			handle->valid_size = 0;
224 			return -1;
225 			/* LCOV_EXCL_STOP */
226 		}
227 	}
228 
229 	/* reset the descriptor */
230 	handle->file = 0;
231 	handle->f = -1;
232 	handle->valid_size = 0;
233 
234 	return 0;
235 }
236 
handle_read(struct snapraid_handle * handle,block_off_t file_pos,unsigned char * block_buffer,unsigned block_size,fptr * out,fptr * out_missing)237 int handle_read(struct snapraid_handle* handle, block_off_t file_pos, unsigned char* block_buffer, unsigned block_size, fptr* out, fptr* out_missing)
238 {
239 	ssize_t read_ret;
240 	data_off_t offset;
241 	unsigned read_size;
242 	unsigned count;
243 	int ret;
244 
245 	offset = file_pos * (data_off_t)block_size;
246 
247 	if (!out_missing)
248 		out_missing = out;
249 
250 	/* check if we are going to read only not initialized data */
251 	if (offset >= handle->valid_size) {
252 		/* if the file is missing, it's at 0 size, or it's rebuilt while reading */
253 		if (offset == handle->valid_size || handle->valid_size == 0)
254 			out_missing("Reading data from missing file '%s' at offset %" PRIu64 ".\n", handle->path, offset);
255 		else
256 			out("Reading missing data from file '%s' at offset %" PRIu64 ".\n", handle->path, offset);
257 		return -1;
258 	}
259 
260 	read_size = file_block_size(handle->file, file_pos, block_size);
261 
262 	count = 0;
263 	do {
264 		/* read the full block to support O_DIRECT */
265 		read_ret = pread(handle->f, block_buffer + count, block_size - count, offset + count);
266 		if (read_ret < 0) {
267 			/* LCOV_EXCL_START */
268 			out("Error reading file '%s' at offset %" PRIu64 " for size %u. %s.\n", handle->path, offset + count, block_size - count, strerror(errno));
269 			return -1;
270 			/* LCOV_EXCL_STOP */
271 		}
272 		if (read_ret == 0) {
273 			out("Unexpected end of file '%s' at offset %" PRIu64 ". %s.\n", handle->path, offset, strerror(errno));
274 			return -1;
275 		}
276 
277 		count += read_ret;
278 	} while (count < read_size);
279 
280 	/* pad with 0 */
281 	if (read_size < block_size) {
282 		memset(block_buffer + read_size, 0, block_size - read_size);
283 	}
284 
285 	ret = advise_read(&handle->advise, handle->f, offset, block_size);
286 	if (ret != 0) {
287 		/* LCOV_EXCL_START */
288 		out("Error advising file '%s'. %s.\n", handle->path, strerror(errno));
289 		return -1;
290 		/* LCOV_EXCL_STOP */
291 	}
292 
293 	return read_size;
294 }
295 
handle_write(struct snapraid_handle * handle,block_off_t file_pos,unsigned char * block_buffer,unsigned block_size)296 int handle_write(struct snapraid_handle* handle, block_off_t file_pos, unsigned char* block_buffer, unsigned block_size)
297 {
298 	ssize_t write_ret;
299 	data_off_t offset;
300 	unsigned write_size;
301 	int ret;
302 
303 	offset = file_pos * (data_off_t)block_size;
304 
305 	write_size = file_block_size(handle->file, file_pos, block_size);
306 
307 	write_ret = pwrite(handle->f, block_buffer, write_size, offset);
308 	if (write_ret != (ssize_t)write_size) { /* conversion is safe because block_size is always small */
309 		/* LCOV_EXCL_START */
310 		log_fatal("Error writing file '%s'. %s.\n", handle->path, strerror(errno));
311 		return -1;
312 		/* LCOV_EXCL_STOP */
313 	}
314 
315 	/* adjust the size of the valid data */
316 	if (handle->valid_size < offset + write_size) {
317 		handle->valid_size = offset + write_size;
318 	}
319 
320 	ret = advise_write(&handle->advise, handle->f, offset, block_size);
321 	if (ret != 0) {
322 		/* LCOV_EXCL_START */
323 		log_fatal("Error advising file '%s'. %s.\n", handle->path, strerror(errno));
324 		return -1;
325 		/* LCOV_EXCL_STOP */
326 	}
327 
328 	return 0;
329 }
330 
handle_utime(struct snapraid_handle * handle)331 int handle_utime(struct snapraid_handle* handle)
332 {
333 	int ret;
334 
335 	/* do nothing if not opened */
336 	if (handle->f == -1)
337 		return 0;
338 
339 	ret = fmtime(handle->f, handle->file->mtime_sec, handle->file->mtime_nsec);
340 
341 	if (ret != 0) {
342 		/* LCOV_EXCL_START */
343 		log_fatal("Error timing file '%s'. %s.\n", handle->file->sub, strerror(errno));
344 		return -1;
345 		/* LCOV_EXCL_STOP */
346 	}
347 
348 	return 0;
349 }
350 
handle_mapping(struct snapraid_state * state,unsigned * handlemax)351 struct snapraid_handle* handle_mapping(struct snapraid_state* state, unsigned* handlemax)
352 {
353 	tommy_node* i;
354 	unsigned j;
355 	unsigned size = 0;
356 	struct snapraid_handle* handle;
357 
358 	/* get the size of the mapping */
359 	size = 0;
360 	for (i = state->maplist; i != 0; i = i->next) {
361 		struct snapraid_map* map = i->data;
362 		if (map->position > size)
363 			size = map->position;
364 	}
365 	++size; /* size is one more than the max */
366 
367 	handle = malloc_nofail(size * sizeof(struct snapraid_handle));
368 
369 	for (j = 0; j < size; ++j) {
370 		/* default for empty position */
371 		handle[j].disk = 0;
372 		handle[j].file = 0;
373 		handle[j].f = -1;
374 		handle[j].valid_size = 0;
375 	}
376 
377 	/* set the vector */
378 	for (i = state->disklist; i != 0; i = i->next) {
379 		struct snapraid_map* map;
380 		struct snapraid_disk* disk = i->data;
381 		tommy_node* k;
382 
383 		/* search the mapping for this disk */
384 		map = 0;
385 		for (k = state->maplist; k != 0; k = k->next) {
386 			map = k->data;
387 			if (strcmp(disk->name, map->name) == 0)
388 				break;
389 		}
390 		if (!map) {
391 			/* LCOV_EXCL_START */
392 			log_fatal("Internal error for inconsistent disk mapping.\n");
393 			os_abort();
394 			/* LCOV_EXCL_STOP */
395 		}
396 
397 		handle[map->position].disk = disk;
398 	}
399 
400 	*handlemax = size;
401 	return handle;
402 }
403 
404