1 /*
2 This file is part of mktorrent
3 Copyright (C) 2007, 2009 Emil Renner Berthing
4 
5 mktorrent 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 2 of the License, or
8 (at your option) any later version.
9 
10 mktorrent 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, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18 */
19 #ifndef ALLINONE
20 #include <stdlib.h>      /* exit(), malloc() */
21 #include <sys/types.h>   /* off_t */
22 #include <errno.h>       /* errno */
23 #include <string.h>      /* strerror() */
24 #include <stdio.h>       /* printf() etc. */
25 #include <fcntl.h>       /* open() */
26 #include <unistd.h>      /* access(), read(), close() */
27 #ifdef USE_OPENSSL
28 #include <openssl/sha.h> /* SHA1() */
29 #else
30 #include <inttypes.h>
31 #include "sha1.h"
32 #endif
33 #include <pthread.h>     /* pthread functions and data structures */
34 
35 #include "mktorrent.h"
36 
37 #define EXPORT
38 #endif /* ALLINONE */
39 
40 
41 #ifndef PROGRESS_PERIOD
42 #define PROGRESS_PERIOD 200000
43 #endif
44 
45 #ifndef O_BINARY
46 #define O_BINARY 0
47 #endif
48 
49 #if defined _LARGEFILE_SOURCE && defined O_LARGEFILE
50 #define OPENFLAGS (O_RDONLY | O_BINARY | O_LARGEFILE)
51 #else
52 #define OPENFLAGS (O_RDONLY | O_BINARY)
53 #endif
54 
55 struct piece_s;
56 typedef struct piece_s piece_t;
57 struct piece_s {
58 	piece_t *next;
59 	unsigned char *dest;
60 	unsigned long len;
61 	unsigned char data[1];
62 };
63 
64 struct queue_s;
65 typedef struct queue_s queue_t;
66 struct queue_s {
67 	piece_t *free;
68 	piece_t *full;
69 	unsigned int buffers_max;
70 	unsigned int buffers;
71 	pthread_mutex_t mutex_free;
72 	pthread_mutex_t mutex_full;
73 	pthread_cond_t cond_empty;
74 	pthread_cond_t cond_full;
75 	unsigned int done;
76 	unsigned int pieces;
77 	unsigned int pieces_hashed;
78 };
79 
get_free(queue_t * q,size_t piece_length)80 static piece_t *get_free(queue_t *q, size_t piece_length)
81 {
82 	piece_t *r;
83 
84 	pthread_mutex_lock(&q->mutex_free);
85 	if (q->free) {
86 		r = q->free;
87 		q->free = r->next;
88 	} else if (q->buffers < q->buffers_max) {
89 		r = malloc(sizeof(piece_t) - 1 + piece_length);
90 		if (r == NULL) {
91 			fprintf(stderr, "Out of memory.\n");
92 			exit(EXIT_FAILURE);
93 		}
94 
95 		q->buffers++;
96 	} else {
97 		while (q->free == NULL) {
98 			pthread_cond_wait(&q->cond_full, &q->mutex_free);
99 		}
100 
101 		r = q->free;
102 		q->free = r->next;
103 	}
104 	pthread_mutex_unlock(&q->mutex_free);
105 
106 	return r;
107 }
108 
get_full(queue_t * q)109 static piece_t *get_full(queue_t *q)
110 {
111 	piece_t *r;
112 
113 	pthread_mutex_lock(&q->mutex_full);
114 again:
115 	if (q->full) {
116 		r = q->full;
117 		q->full = r->next;
118 	} else if (q->done) {
119 		r = NULL;
120 	} else {
121 		pthread_cond_wait(&q->cond_empty, &q->mutex_full);
122 		goto again;
123 	}
124 	pthread_mutex_unlock(&q->mutex_full);
125 
126 	return r;
127 }
128 
put_free(queue_t * q,piece_t * p,unsigned int hashed)129 static void put_free(queue_t *q, piece_t *p, unsigned int hashed)
130 {
131 	pthread_mutex_lock(&q->mutex_free);
132 	p->next = q->free;
133 	q->free = p;
134 	q->pieces_hashed += hashed;
135 	pthread_mutex_unlock(&q->mutex_free);
136 	pthread_cond_signal(&q->cond_full);
137 }
138 
put_full(queue_t * q,piece_t * p)139 static void put_full(queue_t *q, piece_t *p)
140 {
141 	pthread_mutex_lock(&q->mutex_full);
142 	p->next = q->full;
143 	q->full = p;
144 	pthread_mutex_unlock(&q->mutex_full);
145 	pthread_cond_signal(&q->cond_empty);
146 }
147 
set_done(queue_t * q)148 static void set_done(queue_t *q)
149 {
150 	pthread_mutex_lock(&q->mutex_full);
151 	q->done = 1;
152 	pthread_mutex_unlock(&q->mutex_full);
153 	pthread_cond_broadcast(&q->cond_empty);
154 }
155 
free_buffers(queue_t * q)156 static void free_buffers(queue_t *q)
157 {
158 	piece_t *first = q->free;
159 
160 	while (first) {
161 		piece_t *p = first;
162 		first = p->next;
163 		free(p);
164 	}
165 
166 	q->free = NULL;
167 }
168 
169 /*
170  * print the progress in a thread of its own
171  */
print_progress(void * data)172 static void *print_progress(void *data)
173 {
174 	queue_t *q = data;
175 	int err;
176 
177 	err = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
178 	if (err) {
179 		fprintf(stderr, "Error setting thread cancel type: %s\n",
180 				strerror(err));
181 		exit(EXIT_FAILURE);
182 	}
183 
184 	while (1) {
185 		/* print progress and flush the buffer immediately */
186 		printf("\rHashed %u of %u pieces.", q->pieces_hashed, q->pieces);
187 		fflush(stdout);
188 		/* now sleep for PROGRESS_PERIOD microseconds */
189 		usleep(PROGRESS_PERIOD);
190 	}
191 
192 	return NULL;
193 }
194 
worker(void * data)195 static void *worker(void *data)
196 {
197 	queue_t *q = data;
198 	piece_t *p;
199 	SHA_CTX c;
200 
201 	while ((p = get_full(q))) {
202 		SHA1_Init(&c);
203 		SHA1_Update(&c, p->data, p->len);
204 		SHA1_Final(p->dest, &c);
205 		put_free(q, p, 1);
206 	}
207 
208 	return NULL;
209 }
210 
read_files(metafile_t * m,queue_t * q,unsigned char * pos)211 static void read_files(metafile_t *m, queue_t *q, unsigned char *pos)
212 {
213 	int fd;              /* file descriptor */
214 	flist_t *f;          /* pointer to a place in the file list */
215 	ssize_t r = 0;       /* number of bytes read from file(s)
216 	                        into the read buffer */
217 #ifndef NO_HASH_CHECK
218 	off_t counter = 0;   /* number of bytes hashed
219 	                        should match size when done */
220 #endif
221 	piece_t *p = get_free(q, m->piece_length);
222 
223 	/* go through all the files in the file list */
224 	for (f = m->file_list; f; f = f->next) {
225 
226 		/* open the current file for reading */
227 		if ((fd = open(f->path, OPENFLAGS)) == -1) {
228 			fprintf(stderr, "Error opening '%s' for reading: %s\n",
229 					f->path, strerror(errno));
230 			exit(EXIT_FAILURE);
231 		}
232 
233 		while (1) {
234 			ssize_t d = read(fd, p->data + r, m->piece_length - r);
235 
236 			if (d < 0) {
237 				fprintf(stderr, "Error reading from '%s': %s\n",
238 						f->path, strerror(errno));
239 				exit(EXIT_FAILURE);
240 			}
241 
242 			if (d == 0) /* end of file */
243 				break;
244 
245 			r += d;
246 
247 			if (r == m->piece_length) {
248 				p->dest = pos;
249 				p->len = m->piece_length;
250 				put_full(q, p);
251 				pos += SHA_DIGEST_LENGTH;
252 #ifndef NO_HASH_CHECK
253 				counter += r;
254 #endif
255 				r = 0;
256 				p = get_free(q, m->piece_length);
257 			}
258 		}
259 
260 		/* now close the file */
261 		if (close(fd)) {
262 			fprintf(stderr, "Error closing '%s': %s\n",
263 					f->path, strerror(errno));
264 			exit(EXIT_FAILURE);
265 		}
266 	}
267 
268 	/* finally append the hash of the last irregular piece to the hash string */
269 	if (r) {
270 		p->dest = pos;
271 		p->len = r;
272 		put_full(q, p);
273 	} else
274 		put_free(q, p, 0);
275 
276 #ifndef NO_HASH_CHECK
277 	counter += r;
278 	if (counter != m->size) {
279 		fprintf(stderr, "Counted %" PRIoff " bytes, "
280 				"but hashed %" PRIoff " bytes. "
281 				"Something is wrong...\n", m->size, counter);
282 		exit(EXIT_FAILURE);
283 	}
284 #endif
285 }
286 
make_hash(metafile_t * m)287 EXPORT unsigned char *make_hash(metafile_t *m)
288 {
289 	queue_t q = {
290 		NULL, NULL, 0, 0,
291 		PTHREAD_MUTEX_INITIALIZER,
292 		PTHREAD_MUTEX_INITIALIZER,
293 		PTHREAD_COND_INITIALIZER,
294 		PTHREAD_COND_INITIALIZER,
295 		0, 0, 0
296 	};
297 	pthread_t print_progress_thread;	/* progress printer thread */
298 	pthread_t *workers;
299 	unsigned char *hash_string;		/* the hash string */
300 	unsigned int i;
301 	int err;
302 
303 	workers = malloc(m->threads * sizeof(pthread_t));
304 	hash_string = malloc(m->pieces * SHA_DIGEST_LENGTH);
305 	if (workers == NULL || hash_string == NULL) {
306 		fprintf(stderr, "Out of memory.\n");
307 		exit(EXIT_FAILURE);
308 	}
309 
310 	q.pieces = m->pieces;
311 	q.buffers_max = 3*m->threads;
312 
313 	/* create worker threads */
314 	for (i = 0; i < m->threads; i++) {
315 		err = pthread_create(&workers[i], NULL, worker, &q);
316 		if (err) {
317 			fprintf(stderr, "Error creating thread: %s\n",
318 					strerror(err));
319 			exit(EXIT_FAILURE);
320 		}
321 	}
322 
323 	/* now set off the progress printer */
324 	err = pthread_create(&print_progress_thread, NULL, print_progress, &q);
325 	if (err) {
326 		fprintf(stderr, "Error creating thread: %s\n",
327 				strerror(err));
328 		exit(EXIT_FAILURE);
329 	}
330 
331 	/* read files and feed pieces to the workers */
332 	read_files(m, &q, hash_string);
333 
334 	/* we're done so stop printing our progress. */
335 	err = pthread_cancel(print_progress_thread);
336 	if (err) {
337 		fprintf(stderr, "Error cancelling thread: %s\n",
338 				strerror(err));
339 		exit(EXIT_FAILURE);
340 	}
341 
342 	/* inform workers we're done */
343 	set_done(&q);
344 
345 	/* wait for workers to finish */
346 	for (i = 0; i < m->threads; i++) {
347 		err = pthread_join(workers[i], NULL);
348 		if (err) {
349 			fprintf(stderr, "Error joining thread: %s\n",
350 					strerror(err));
351 			exit(EXIT_FAILURE);
352 		}
353 	}
354 
355 	free(workers);
356 
357 	/* the progress printer should be done by now too */
358 	err = pthread_join(print_progress_thread, NULL);
359 	if (err) {
360 		fprintf(stderr, "Error joining thread: %s\n",
361 				strerror(err));
362 		exit(EXIT_FAILURE);
363 	}
364 
365 	/* destroy mutexes and condition variables */
366 	pthread_mutex_destroy(&q.mutex_full);
367 	pthread_mutex_destroy(&q.mutex_free);
368 	pthread_cond_destroy(&q.cond_empty);
369 	pthread_cond_destroy(&q.cond_full);
370 
371 	/* free buffers */
372 	free_buffers(&q);
373 
374 	/* ok, let the user know we're done too */
375 	printf("\rHashed %u of %u pieces.\n", q.pieces_hashed, q.pieces);
376 
377 	return hash_string;
378 }
379