hash_update.c 9.09 KB
Newer Older
1
/* hash_update.c - functions to update a crc file */
2

3
#include <errno.h>
4
#include <stdio.h>
5
#include <stdlib.h>
6
#include <string.h>
7 8 9
#ifndef _WIN32
# include <dirent.h>
#endif
10

11 12 13
#include "calc_sums.h"
#include "common_func.h"
#include "file.h"
14 15
#include "file_set.h"
#include "file_mask.h"
16
#include "hash_print.h"
17
#include "hash_update.h"
18 19 20 21
#include "output.h"
#include "parse_cmdline.h"
#include "rhash_main.h"
#include "win_utils.h"
22 23

/* first define some internal functions, implemented later in this file */
24 25 26
static int add_new_crc_entries(file_t* file, file_set *crc_entries);
static int file_set_load_from_crc_file(file_set *set, file_t* file);
static int fix_sfv_header(file_t* file);
27 28

/**
29
 * Update given crc file, by adding to it hashes of files from the same
30 31
 * directory, but which the crc file doesn't contain yet.
 *
32
 * @param file the file containing hash sums
33
 * @return 0 on success, -1 on fail
34
 */
35
int update_hash_file(file_t* file)
36
{
37 38 39 40
	file_set* crc_entries;
	timedelta_t timer;
	int res;

41
	if (opt.flags & OPT_VERBOSE) {
42
		log_msg(_("Updating: %s\n"), file->path);
43 44 45
	}

	crc_entries = file_set_new();
46
	res = file_set_load_from_crc_file(crc_entries, file);
47

48
	if (opt.flags & OPT_SPEED) rsh_timer_start(&timer);
49 50 51
	rhash_data.total_size = 0;
	rhash_data.processed  = 0;

52
	if (res == 0) {
53
		/* add the crc file itself to the set of excluded from re-calculation files */
54
		file_set_add_name(crc_entries, get_basename(file->path));
55 56 57
		file_set_sort(crc_entries);

		/* update crc file with sums of files not present in the crc_entries */
58
		res = add_new_crc_entries(file, crc_entries);
59 60 61
	}
	file_set_free(crc_entries);

62 63
	if (opt.flags & OPT_SPEED && rhash_data.processed > 0) {
		double time = rsh_timer_stop(&timer);
64 65 66 67
		print_time_stats(time, rhash_data.total_size, 1);
	}

	return res;
68 69
}

70
/**
71 72 73
 * Load a set of files from given crc file.
 *
 * @param set the file set to store loaded files
74
 * @param file the file containing hash sums to load
75
 * @return 0 on success, -1 on fail with error code in errno
76
 */
77
static int file_set_load_from_crc_file(file_set *set, file_t* file)
78
{
79 80 81 82 83
	FILE *fd;
	int line_num;
	char buf[2048];
	hash_check hc;

84
	if ( !(fd = file_fopen(file, FOpenRead | FOpenBin) )) {
85 86 87
		/* if file not exist, it will be created */
		return (errno == ENOENT ? 0 : -1);
	}
88
	for (line_num = 0; fgets(buf, 2048, fd); line_num++) {
89 90 91
		char* line = buf;

		/* skip unicode BOM */
92
		if (line_num == 0 && buf[0] == (char)0xEF && buf[1] == (char)0xBB && buf[2] == (char)0xBF) line += 3;
93

94
		if (*line == 0) continue; /* skip empty lines */
95

96 97
		if (is_binary_string(line)) {
			log_error(_("skipping binary file %s\n"), file->path);
98 99 100 101
			fclose(fd);
			return -1;
		}

102
		if (IS_COMMENT(*line) || *line == '\r' || *line == '\n') continue;
103 104

		/* parse a hash file line */
105
		if (hash_check_parse_line(line, &hc, !feof(fd))) {
106
			/* store file info to the file set */
107
			if (hc.file_path) file_set_add_name(set, hc.file_path);
108 109 110 111
		}
	}
	fclose(fd);
	return 0;
112 113 114 115 116 117 118
}

/**
 * Add hash sums of files from given file-set to a specified hash-file.
 * A specified directory path will be prepended to the path of added files,
 * if it is not a current directory.
 *
119
 * @param file the hash file to add the hash sums to
120 121 122 123
 * @param dir_path the directory path to prepend
 * @param files_to_add the set of files to hash and add
 * @return 0 on success, -1 on error
 */
124
static int add_sums_to_file(file_t* file, char* dir_path, file_set *files_to_add)
125
{
126 127 128 129 130 131
	FILE* fd;
	unsigned i;
	int ch;

	/* SFV banner will be printed only in SFV mode and only for empty crc files */
	int print_banner = (opt.fmt == FMT_SFV);
132 133

	file->size = 0;
134
	if (file_stat(file, 0) == 0) {
135
		if (print_banner && file->size > 0) print_banner = 0;
136 137
	}

138
	/* open the hash file for writing */
139 140
	if ( !(fd = file_fopen(file, FOpenRead | FOpenWrite) )) {
		log_file_t_error(file);
141 142 143 144
		return -1;
	}
	rhash_data.upd_fd = fd;

145
	if (file->size > 0) {
146
		/* read the last character of the file to check if it is EOL */
147
		if (fseek(fd, -1, SEEK_END) != 0) {
148
			log_file_t_error(file);
149 150 151 152 153
			return -1;
		}
		ch = fgetc(fd);

		/* somehow writing doesn't work without seeking */
154
		if (fseek(fd, 0, SEEK_END) != 0) {
155
			log_file_t_error(file);
156 157 158 159
			return -1;
		}

		/* write EOL if it wasn't present */
160
		if (ch != '\n' && ch != '\r') {
161
			/* fputc('\n', fd); */
162
			rsh_fprintf(fd, "\n");
163 164 165 166
		}
	}

	/* append hash sums to the updated crc file */
167
	for (i = 0; i < files_to_add->size; i++, rhash_data.processed++) {
168 169
		file_t file;
		char *print_path = file_set_get(files_to_add, i)->filepath;
170
		memset(&file, 0, sizeof(file));
171

172
		if (dir_path[0] != '.' || dir_path[1] != 0) {
173
			/* prepend the file path by directory path */
174
			file_init(&file, make_path(dir_path, print_path), 0);
175
		} else {
176
			file_init(&file, print_path, FILE_OPT_DONT_FREE_PATH);
177
		}
178

179 180
		if (opt.fmt == FMT_SFV) {
			if (print_banner) {
181 182 183 184
				print_sfv_banner(fd);
				print_banner = 0;
			}
		}
185
		file_stat(&file, 0);
186 187

		/* print hash sums to the crc file */
188
		calculate_and_print_sums(fd, &file, print_path);
189

190
		file_cleanup(&file);
191

192
		if (rhash_data.interrupted) {
193 194 195
			fclose(fd);
			return 0;
		}
196 197
	}
	fclose(fd);
198
	log_msg(_("Updated: %s\n"), file->path);
199
	return 0;
200 201 202
}

/**
203
 * Read a directory and load files not present in the crc_entries file-set
204 205 206
 * into the files_to_add file-set.
 *
 * @param dir_path the path of the directory to load files from
207
 * @param crc_entries file-set of files which should be skipped
208 209 210 211 212
 * @param files_to_add file-set to load the list of files into
 * @return 0 on success, -1 on error (and errno is set)
 */
static int load_filtered_dir(const char* dir_path, file_set *crc_entries, file_set *files_to_add)
{
213 214 215 216 217
	DIR *dp;
	struct dirent *de;

	/* read directory */
	dp = opendir(dir_path);
218
	if (!dp) return -1;
219

220
	while ((de = readdir(dp)) != NULL) {
221
		char *path;
222
		unsigned is_regular;
223 224

		/* skip "." and ".." directories */
225
		if (de->d_name[0] == '.' && (de->d_name[1] == 0 ||
226 227 228 229
				(de->d_name[1] == '.' && de->d_name[2] == 0))) {
					continue;
		}

230
		/* check if the file is a regular one */
231
		path = make_path(dir_path, de->d_name);
232
		is_regular = is_regular_file(path);
233 234
		free(path);

235
		/* skip non-regular files (directories, device files, e.t.c.),
236 237
		 * as well as files not accepted by current file filter
		 * and files already present in the crc_entries file set */
238 239 240 241
		if (!is_regular || !file_mask_match(opt.files_accept, de->d_name) ||
			(opt.files_exclude && file_mask_match(opt.files_exclude, de->d_name)) ||
			file_set_exist(crc_entries, de->d_name))
		{
242 243 244 245 246 247
			continue;
		}

		file_set_add_name(files_to_add, de->d_name);
	}
	return 0;
248 249 250 251
}

/**
 * Calculate and add to the given hash-file the hash-sums for all files
252 253
 * from the same directory as the hash-file, but absent from given
 * crc_entries file-set.
254 255 256
 *
 * <p/>If SFV format was specified by a command line switch, the after adding
 * hash sums SFV header of the file is fixed by moving all lines starting
257
 * with a semicolon before other lines. So an SFV-formatted hash-file
258 259
 * will remain correct.
 *
260
 * @param file the hash-file to add sums into
261 262 263
 * @param crc_entries file-set of files to omit from adding
 * @return 0 on success, -1 on error
 */
264
static int add_new_crc_entries(file_t* file, file_set *crc_entries)
265
{
266 267 268 269
	file_set* files_to_add;
	char* dir_path;
	int res = 0;

270
	dir_path = get_dirname(file->path);
271 272 273 274 275
	files_to_add = file_set_new();

	/* load into files_to_add files from directory not present in the crc_entries */
	load_filtered_dir(dir_path, crc_entries, files_to_add);

276
	if (files_to_add->size > 0) {
277 278 279 280
		/* sort files by path */
		file_set_sort_by_path(files_to_add);

		/* calculate and write crc sums to the file */
281
		res = add_sums_to_file(file, dir_path, files_to_add);
282

283
		if (res == 0 && opt.fmt == FMT_SFV && !rhash_data.interrupted) {
284
			/* move SFV header from the end of updated file to its head */
285
			res = fix_sfv_header(file);
286 287 288 289 290 291
		}
	}

	file_set_free(files_to_add);
	free(dir_path);
	return res;
292 293 294 295 296
}

/**
 * Move all SFV header lines (i.e. all lines starting with a semicolon)
 * from the end of updated file to its head.
297 298
 *
 * @param file the hash file requiring fixing of its SFV header
299
 */
300
static int fix_sfv_header(file_t* file)
301
{
302 303 304
	FILE* in;
	FILE* out;
	char line[2048];
305
	file_t new_file;
306 307
	int err = 0;

308 309
	if ( !(in = file_fopen(file, FOpenRead))) {
		log_file_t_error(file);
310 311 312
		return -1;
	}

313 314 315 316 317
	/* open a temporary file for writing */
	file_path_append(&new_file, file, ".new");
	if ( !(out = file_fopen(&new_file, FOpenWrite) )) {
		log_file_t_error(&new_file);
		file_cleanup(&new_file);
318 319 320 321 322
		fclose(in);
		return -1;
	}

	/* The first, output all commented lines to the file header */
323 324 325
	while (fgets(line, 2048, in)) {
		if (*line == ';') {
			if (fputs(line, out) < 0) break;
326 327
		}
	}
328
	if (!ferror(out) && !ferror(in)) {
329 330
		fseek(in, 0, SEEK_SET);
		/* The second, output non-commented lines */
331 332 333
		while (fgets(line, 2048, in)) {
			if (*line != ';') {
				if (fputs(line, out) < 0) break;
334 335 336
			}
		}
	}
337
	if (ferror(in)) {
338
		log_file_t_error(file);
339 340
		err = 1;
	}
341
	if (ferror(out)) {
342
		log_file_t_error(&new_file);
343 344 345 346 347 348
		err = 1;
	}

	fclose(in);
	fclose(out);

349
	/* overwrite the hash file with a new one */
350 351 352
	if (!err && file_rename(&new_file, file) < 0) {
		log_error(_("can't move %s to %s: %s\n"),
			new_file.path, file->path, strerror(errno));
353
	}
354
	file_cleanup(&new_file);
355
	return (err ? -1 : 0);
356
}