1 /*
2  *  hook.c
3  *
4  *  Copyright (c) 2015-2018 Pacman Development Team <pacman-dev@archlinux.org>
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <ctype.h>
21 #include <dirent.h>
22 #include <errno.h>
23 #include <limits.h>
24 #include <string.h>
25 
26 #include "handle.h"
27 #include "hook.h"
28 #include "ini.h"
29 #include "log.h"
30 #include "trans.h"
31 #include "util.h"
32 
33 enum _alpm_hook_op_t {
34 	ALPM_HOOK_OP_INSTALL = (1 << 0),
35 	ALPM_HOOK_OP_UPGRADE = (1 << 1),
36 	ALPM_HOOK_OP_REMOVE = (1 << 2),
37 };
38 
39 enum _alpm_trigger_type_t {
40 	ALPM_HOOK_TYPE_PACKAGE = 1,
41 	ALPM_HOOK_TYPE_FILE,
42 };
43 
44 struct _alpm_trigger_t {
45 	enum _alpm_hook_op_t op;
46 	enum _alpm_trigger_type_t type;
47 	alpm_list_t *targets;
48 };
49 
50 struct _alpm_hook_t {
51 	char *name;
52 	char *desc;
53 	alpm_list_t *triggers;
54 	alpm_list_t *depends;
55 	char **cmd;
56 	alpm_list_t *matches;
57 	alpm_hook_when_t when;
58 	int abort_on_fail, needs_targets;
59 };
60 
61 struct _alpm_hook_cb_ctx {
62 	alpm_handle_t *handle;
63 	struct _alpm_hook_t *hook;
64 };
65 
_alpm_trigger_free(struct _alpm_trigger_t * trigger)66 static void _alpm_trigger_free(struct _alpm_trigger_t *trigger)
67 {
68 	if(trigger) {
69 		FREELIST(trigger->targets);
70 		free(trigger);
71 	}
72 }
73 
_alpm_wordsplit_free(char ** ws)74 static void _alpm_wordsplit_free(char **ws)
75 {
76 	if(ws) {
77 		char **c;
78 		for(c = ws; *c; c++) {
79 			free(*c);
80 		}
81 		free(ws);
82 	}
83 }
84 
_alpm_hook_free(struct _alpm_hook_t * hook)85 static void _alpm_hook_free(struct _alpm_hook_t *hook)
86 {
87 	if(hook) {
88 		free(hook->name);
89 		free(hook->desc);
90 		_alpm_wordsplit_free(hook->cmd);
91 		alpm_list_free_inner(hook->triggers, (alpm_list_fn_free) _alpm_trigger_free);
92 		alpm_list_free(hook->triggers);
93 		alpm_list_free(hook->matches);
94 		FREELIST(hook->depends);
95 		free(hook);
96 	}
97 }
98 
_alpm_trigger_validate(alpm_handle_t * handle,struct _alpm_trigger_t * trigger,const char * file)99 static int _alpm_trigger_validate(alpm_handle_t *handle,
100 		struct _alpm_trigger_t *trigger, const char *file)
101 {
102 	int ret = 0;
103 
104 	if(trigger->targets == NULL) {
105 		ret = -1;
106 		_alpm_log(handle, ALPM_LOG_ERROR,
107 				_("Missing trigger targets in hook: %s\n"), file);
108 	}
109 
110 	if(trigger->type == 0) {
111 		ret = -1;
112 		_alpm_log(handle, ALPM_LOG_ERROR,
113 				_("Missing trigger type in hook: %s\n"), file);
114 	}
115 
116 	if(trigger->op == 0) {
117 		ret = -1;
118 		_alpm_log(handle, ALPM_LOG_ERROR,
119 				_("Missing trigger operation in hook: %s\n"), file);
120 	}
121 
122 	return ret;
123 }
124 
_alpm_hook_validate(alpm_handle_t * handle,struct _alpm_hook_t * hook,const char * file)125 static int _alpm_hook_validate(alpm_handle_t *handle,
126 		struct _alpm_hook_t *hook, const char *file)
127 {
128 	alpm_list_t *i;
129 	int ret = 0;
130 
131 	if(hook->triggers == NULL) {
132 		/* special case: allow triggerless hooks as a way of creating dummy
133 		 * hooks that can be used to mask lower priority hooks */
134 		return 0;
135 	}
136 
137 	for(i = hook->triggers; i; i = i->next) {
138 		if(_alpm_trigger_validate(handle, i->data, file) != 0) {
139 			ret = -1;
140 		}
141 	}
142 
143 	if(hook->cmd == NULL) {
144 		ret = -1;
145 		_alpm_log(handle, ALPM_LOG_ERROR,
146 				_("Missing Exec option in hook: %s\n"), file);
147 	}
148 
149 	if(hook->when == 0) {
150 		ret = -1;
151 		_alpm_log(handle, ALPM_LOG_ERROR,
152 				_("Missing When option in hook: %s\n"), file);
153 	} else if(hook->when != ALPM_HOOK_PRE_TRANSACTION && hook->abort_on_fail) {
154 		_alpm_log(handle, ALPM_LOG_WARNING,
155 				_("AbortOnFail set for PostTransaction hook: %s\n"), file);
156 	}
157 
158 	return ret;
159 }
160 
_alpm_wordsplit(char * str)161 static char **_alpm_wordsplit(char *str)
162 {
163 	char *c = str, *end;
164 	char **out = NULL, **outsave;
165 	size_t count = 0;
166 
167 	if(str == NULL) {
168 		errno = EINVAL;
169 		return NULL;
170 	}
171 
172 	for(c = str; isspace(*c); c++);
173 	while(*c) {
174 		size_t wordlen = 0;
175 
176 		/* extend our array */
177 		outsave = out;
178 		if((out = realloc(out, (count + 1) * sizeof(char*))) == NULL) {
179 			out = outsave;
180 			goto error;
181 		}
182 
183 		/* calculate word length and check for unbalanced quotes */
184 		for(end = c; *end && !isspace(*end); end++) {
185 			if(*end == '\'' || *end == '"') {
186 				char quote = *end;
187 				while(*(++end) && *end != quote) {
188 					if(*end == '\\' && *(end + 1) == quote) {
189 						end++;
190 					}
191 					wordlen++;
192 				}
193 				if(*end != quote) {
194 					errno = EINVAL;
195 					goto error;
196 				}
197 			} else {
198 				if(*end == '\\' && (end[1] == '\'' || end[1] == '"')) {
199 					end++; /* skip the '\\' */
200 				}
201 				wordlen++;
202 			}
203 		}
204 
205 		if(wordlen == (size_t) (end - c)) {
206 			/* no internal quotes or escapes, copy it the easy way */
207 			if((out[count++] = strndup(c, wordlen)) == NULL) {
208 				goto error;
209 			}
210 		} else {
211 			/* manually copy to remove quotes and escapes */
212 			char *dest = out[count++] = malloc(wordlen + 1);
213 			if(dest == NULL) { goto error; }
214 			while(c < end) {
215 				if(*c == '\'' || *c == '"') {
216 					char quote = *c;
217 					/* we know there must be a matching end quote,
218 					 * no need to check for '\0' */
219 					for(c++; *c != quote; c++) {
220 						if(*c == '\\' && *(c + 1) == quote) {
221 							c++;
222 						}
223 						*(dest++) = *c;
224 					}
225 					c++;
226 				} else {
227 					if(*c == '\\' && (c[1] == '\'' || c[1] == '"')) {
228 						c++; /* skip the '\\' */
229 					}
230 					*(dest++) = *(c++);
231 				}
232 			}
233 			*dest = '\0';
234 		}
235 
236 		if(*end == '\0') {
237 			break;
238 		} else {
239 			for(c = end + 1; isspace(*c); c++);
240 		}
241 	}
242 
243 	outsave = out;
244 	if((out = realloc(out, (count + 1) * sizeof(char*))) == NULL) {
245 		out = outsave;
246 		goto error;
247 	}
248 
249 	out[count++] = NULL;
250 
251 	return out;
252 
253 error:
254 	/* can't use wordsplit_free here because NULL has not been appended */
255 	while(count) {
256 		free(out[--count]);
257 	}
258 	free(out);
259 	return NULL;
260 }
261 
_alpm_hook_parse_cb(const char * file,int line,const char * section,char * key,char * value,void * data)262 static int _alpm_hook_parse_cb(const char *file, int line,
263 		const char *section, char *key, char *value, void *data)
264 {
265 	struct _alpm_hook_cb_ctx *ctx = data;
266 	alpm_handle_t *handle = ctx->handle;
267 	struct _alpm_hook_t *hook = ctx->hook;
268 
269 #define error(...) _alpm_log(handle, ALPM_LOG_ERROR, __VA_ARGS__); return 1;
270 #define warning(...) _alpm_log(handle, ALPM_LOG_WARNING, __VA_ARGS__);
271 
272 	if(!section && !key) {
273 		error(_("error while reading hook %s: %s\n"), file, strerror(errno));
274 	} else if(!section) {
275 		error(_("hook %s line %d: invalid option %s\n"), file, line, key);
276 	} else if(!key) {
277 		/* beginning a new section */
278 		if(strcmp(section, "Trigger") == 0) {
279 			struct _alpm_trigger_t *t;
280 			CALLOC(t, sizeof(struct _alpm_trigger_t), 1, return 1);
281 			hook->triggers = alpm_list_add(hook->triggers, t);
282 		} else if(strcmp(section, "Action") == 0) {
283 			/* no special processing required */
284 		} else {
285 			error(_("hook %s line %d: invalid section %s\n"), file, line, section);
286 		}
287 	} else if(strcmp(section, "Trigger") == 0) {
288 		struct _alpm_trigger_t *t = hook->triggers->prev->data;
289 		if(strcmp(key, "Operation") == 0) {
290 			if(strcmp(value, "Install") == 0) {
291 				t->op |= ALPM_HOOK_OP_INSTALL;
292 			} else if(strcmp(value, "Upgrade") == 0) {
293 				t->op |= ALPM_HOOK_OP_UPGRADE;
294 			} else if(strcmp(value, "Remove") == 0) {
295 				t->op |= ALPM_HOOK_OP_REMOVE;
296 			} else {
297 				error(_("hook %s line %d: invalid value %s\n"), file, line, value);
298 			}
299 		} else if(strcmp(key, "Type") == 0) {
300 			if(t->type != 0) {
301 				warning(_("hook %s line %d: overwriting previous definition of %s\n"), file, line, "Type");
302 			}
303 			if(strcmp(value, "Package") == 0) {
304 				t->type = ALPM_HOOK_TYPE_PACKAGE;
305 			} else if(strcmp(value, "File") == 0) {
306 				t->type = ALPM_HOOK_TYPE_FILE;
307 			} else {
308 				error(_("hook %s line %d: invalid value %s\n"), file, line, value);
309 			}
310 		} else if(strcmp(key, "Target") == 0) {
311 			char *val;
312 			STRDUP(val, value, return 1);
313 			t->targets = alpm_list_add(t->targets, val);
314 		} else {
315 			error(_("hook %s line %d: invalid option %s\n"), file, line, key);
316 		}
317 	} else if(strcmp(section, "Action") == 0) {
318 		if(strcmp(key, "When") == 0) {
319 			if(hook->when != 0) {
320 				warning(_("hook %s line %d: overwriting previous definition of %s\n"), file, line, "When");
321 			}
322 			if(strcmp(value, "PreTransaction") == 0) {
323 				hook->when = ALPM_HOOK_PRE_TRANSACTION;
324 			} else if(strcmp(value, "PostTransaction") == 0) {
325 				hook->when = ALPM_HOOK_POST_TRANSACTION;
326 			} else {
327 				error(_("hook %s line %d: invalid value %s\n"), file, line, value);
328 			}
329 		} else if(strcmp(key, "Description") == 0) {
330 			if(hook->desc != NULL) {
331 				warning(_("hook %s line %d: overwriting previous definition of %s\n"), file, line, "Description");
332 				FREE(hook->desc);
333 			}
334 			STRDUP(hook->desc, value, return 1);
335 		} else if(strcmp(key, "Depends") == 0) {
336 			char *val;
337 			STRDUP(val, value, return 1);
338 			hook->depends = alpm_list_add(hook->depends, val);
339 		} else if(strcmp(key, "AbortOnFail") == 0) {
340 			hook->abort_on_fail = 1;
341 		} else if(strcmp(key, "NeedsTargets") == 0) {
342 			hook->needs_targets = 1;
343 		} else if(strcmp(key, "Exec") == 0) {
344 			if(hook->cmd != NULL) {
345 				warning(_("hook %s line %d: overwriting previous definition of %s\n"), file, line, "Exec");
346 				_alpm_wordsplit_free(hook->cmd);
347 			}
348 			if((hook->cmd = _alpm_wordsplit(value)) == NULL) {
349 				if(errno == EINVAL) {
350 					error(_("hook %s line %d: invalid value %s\n"), file, line, value);
351 				} else {
352 					error(_("hook %s line %d: unable to set option (%s)\n"),
353 							file, line, strerror(errno));
354 				}
355 			}
356 		} else {
357 			error(_("hook %s line %d: invalid option %s\n"), file, line, key);
358 		}
359 	}
360 
361 #undef error
362 #undef warning
363 
364 	return 0;
365 }
366 
_alpm_hook_trigger_match_file(alpm_handle_t * handle,struct _alpm_hook_t * hook,struct _alpm_trigger_t * t)367 static int _alpm_hook_trigger_match_file(alpm_handle_t *handle,
368 		struct _alpm_hook_t *hook, struct _alpm_trigger_t *t)
369 {
370 	alpm_list_t *i, *j, *install = NULL, *upgrade = NULL, *remove = NULL;
371 	size_t isize = 0, rsize = 0;
372 	int ret = 0;
373 
374 	/* check if file will be installed */
375 	for(i = handle->trans->add; i; i = i->next) {
376 		alpm_pkg_t *pkg = i->data;
377 		alpm_filelist_t filelist = pkg->files;
378 		size_t f;
379 		for(f = 0; f < filelist.count; f++) {
380 			if(alpm_option_match_noextract(handle, filelist.files[f].name) == 0) {
381 				continue;
382 			}
383 			if(_alpm_fnmatch_patterns(t->targets, filelist.files[f].name) == 0) {
384 				install = alpm_list_add(install, filelist.files[f].name);
385 				isize++;
386 			}
387 		}
388 	}
389 
390 	/* check if file will be removed due to package upgrade */
391 	for(i = handle->trans->add; i; i = i->next) {
392 		alpm_pkg_t *spkg = i->data;
393 		alpm_pkg_t *pkg = spkg->oldpkg;
394 		if(pkg) {
395 			alpm_filelist_t filelist = pkg->files;
396 			size_t f;
397 			for(f = 0; f < filelist.count; f++) {
398 				if(_alpm_fnmatch_patterns(t->targets, filelist.files[f].name) == 0) {
399 					remove = alpm_list_add(remove, filelist.files[f].name);
400 					rsize++;
401 				}
402 			}
403 		}
404 	}
405 
406 	/* check if file will be removed due to package removal */
407 	for(i = handle->trans->remove; i; i = i->next) {
408 		alpm_pkg_t *pkg = i->data;
409 		alpm_filelist_t filelist = pkg->files;
410 		size_t f;
411 		for(f = 0; f < filelist.count; f++) {
412 			if(_alpm_fnmatch_patterns(t->targets, filelist.files[f].name) == 0) {
413 				remove = alpm_list_add(remove, filelist.files[f].name);
414 				rsize++;
415 			}
416 		}
417 	}
418 
419 	i = install = alpm_list_msort(install, isize, (alpm_list_fn_cmp)strcmp);
420 	j = remove = alpm_list_msort(remove, rsize, (alpm_list_fn_cmp)strcmp);
421 	while(i) {
422 		while(j && strcmp(i->data, j->data) > 0) {
423 			j = j->next;
424 		}
425 		if(j == NULL) {
426 			break;
427 		}
428 		if(strcmp(i->data, j->data) == 0) {
429 			char *path = i->data;
430 			upgrade = alpm_list_add(upgrade, path);
431 			while(i && strcmp(i->data, path) == 0) {
432 				alpm_list_t *next = i->next;
433 				install = alpm_list_remove_item(install, i);
434 				free(i);
435 				i = next;
436 			}
437 			while(j && strcmp(j->data, path) == 0) {
438 				alpm_list_t *next = j->next;
439 				remove = alpm_list_remove_item(remove, j);
440 				free(j);
441 				j = next;
442 			}
443 		} else {
444 			i = i->next;
445 		}
446 	}
447 
448 	ret = (t->op & ALPM_HOOK_OP_INSTALL && install)
449 			|| (t->op & ALPM_HOOK_OP_UPGRADE && upgrade)
450 			|| (t->op & ALPM_HOOK_OP_REMOVE && remove);
451 
452 	if(hook->needs_targets) {
453 #define _save_matches(_op, _matches) \
454 	if(t->op & _op && _matches) { \
455 		hook->matches = alpm_list_join(hook->matches, _matches); \
456 	} else { \
457 		alpm_list_free(_matches); \
458 	}
459 		_save_matches(ALPM_HOOK_OP_INSTALL, install);
460 		_save_matches(ALPM_HOOK_OP_UPGRADE, upgrade);
461 		_save_matches(ALPM_HOOK_OP_REMOVE, remove);
462 #undef _save_matches
463 	} else {
464 		alpm_list_free(install);
465 		alpm_list_free(upgrade);
466 		alpm_list_free(remove);
467 	}
468 
469 	return ret;
470 }
471 
_alpm_hook_trigger_match_pkg(alpm_handle_t * handle,struct _alpm_hook_t * hook,struct _alpm_trigger_t * t)472 static int _alpm_hook_trigger_match_pkg(alpm_handle_t *handle,
473 		struct _alpm_hook_t *hook, struct _alpm_trigger_t *t)
474 {
475 	alpm_list_t *install = NULL, *upgrade = NULL, *remove = NULL;
476 
477 	if(t->op & ALPM_HOOK_OP_INSTALL || t->op & ALPM_HOOK_OP_UPGRADE) {
478 		alpm_list_t *i;
479 		for(i = handle->trans->add; i; i = i->next) {
480 			alpm_pkg_t *pkg = i->data;
481 			if(_alpm_fnmatch_patterns(t->targets, pkg->name) == 0) {
482 				if(pkg->oldpkg) {
483 					if(t->op & ALPM_HOOK_OP_UPGRADE) {
484 						if(hook->needs_targets) {
485 							upgrade = alpm_list_add(upgrade, pkg->name);
486 						} else {
487 							return 1;
488 						}
489 					}
490 				} else {
491 					if(t->op & ALPM_HOOK_OP_INSTALL) {
492 						if(hook->needs_targets) {
493 							install = alpm_list_add(install, pkg->name);
494 						} else {
495 							return 1;
496 						}
497 					}
498 				}
499 			}
500 		}
501 	}
502 
503 	if(t->op & ALPM_HOOK_OP_REMOVE) {
504 		alpm_list_t *i;
505 		for(i = handle->trans->remove; i; i = i->next) {
506 			alpm_pkg_t *pkg = i->data;
507 			if(pkg && _alpm_fnmatch_patterns(t->targets, pkg->name) == 0) {
508 				if(!alpm_list_find(handle->trans->add, pkg, _alpm_pkg_cmp)) {
509 					if(hook->needs_targets) {
510 						remove = alpm_list_add(remove, pkg->name);
511 					} else {
512 						return 1;
513 					}
514 				}
515 			}
516 		}
517 	}
518 
519 	/* if we reached this point we either need the target lists or we didn't
520 	 * match anything and the following calls will all be no-ops */
521 	hook->matches = alpm_list_join(hook->matches, install);
522 	hook->matches = alpm_list_join(hook->matches, upgrade);
523 	hook->matches = alpm_list_join(hook->matches, remove);
524 
525 	return install || upgrade || remove;
526 }
527 
_alpm_hook_trigger_match(alpm_handle_t * handle,struct _alpm_hook_t * hook,struct _alpm_trigger_t * t)528 static int _alpm_hook_trigger_match(alpm_handle_t *handle,
529 		struct _alpm_hook_t *hook, struct _alpm_trigger_t *t)
530 {
531 	return t->type == ALPM_HOOK_TYPE_PACKAGE
532 		? _alpm_hook_trigger_match_pkg(handle, hook, t)
533 		: _alpm_hook_trigger_match_file(handle, hook, t);
534 }
535 
_alpm_hook_triggered(alpm_handle_t * handle,struct _alpm_hook_t * hook)536 static int _alpm_hook_triggered(alpm_handle_t *handle, struct _alpm_hook_t *hook)
537 {
538 	alpm_list_t *i;
539 	int ret = 0;
540 	for(i = hook->triggers; i; i = i->next) {
541 		if(_alpm_hook_trigger_match(handle, hook, i->data)) {
542 			if(!hook->needs_targets) {
543 				return 1;
544 			} else {
545 				ret = 1;
546 			}
547 		}
548 	}
549 	return ret;
550 }
551 
_alpm_hook_cmp(struct _alpm_hook_t * h1,struct _alpm_hook_t * h2)552 static int _alpm_hook_cmp(struct _alpm_hook_t *h1, struct _alpm_hook_t *h2)
553 {
554 	return strcmp(h1->name, h2->name);
555 }
556 
find_hook(alpm_list_t * haystack,const void * needle)557 static alpm_list_t *find_hook(alpm_list_t *haystack, const void *needle)
558 {
559 	while(haystack) {
560 		struct _alpm_hook_t *h = haystack->data;
561 		if(h && strcmp(h->name, needle) == 0) {
562 			return haystack;
563 		}
564 		haystack = haystack->next;
565 	}
566 	return NULL;
567 }
568 
_alpm_hook_feed_targets(char * buf,ssize_t needed,alpm_list_t ** pos)569 static ssize_t _alpm_hook_feed_targets(char *buf, ssize_t needed, alpm_list_t **pos)
570 {
571 	size_t remaining = needed, written = 0;;
572 	size_t len;
573 
574 	while(*pos && (len = strlen((*pos)->data)) + 1 <= remaining) {
575 		memcpy(buf, (*pos)->data, len);
576 		buf[len++] = '\n';
577 		*pos = (*pos)->next;
578 		buf += len;
579 		remaining -= len;
580 		written += len;
581 	}
582 
583 	if(*pos && remaining) {
584 		memcpy(buf, (*pos)->data, remaining);
585 		(*pos)->data = (char*) (*pos)->data + remaining;
586 		written += remaining;
587 	}
588 
589 	return written;
590 }
591 
_alpm_strlist_dedup(alpm_list_t * list)592 static alpm_list_t *_alpm_strlist_dedup(alpm_list_t *list)
593 {
594 	alpm_list_t *i = list;
595 	while(i) {
596 		alpm_list_t *next = i->next;
597 		while(next && strcmp(i->data, next->data) == 0) {
598 			list = alpm_list_remove_item(list, next);
599 			free(next);
600 			next = i->next;
601 		}
602 		i = next;
603 	}
604 	return list;
605 }
606 
_alpm_hook_run_hook(alpm_handle_t * handle,struct _alpm_hook_t * hook)607 static int _alpm_hook_run_hook(alpm_handle_t *handle, struct _alpm_hook_t *hook)
608 {
609 	alpm_list_t *i, *pkgs = _alpm_db_get_pkgcache(handle->db_local);
610 
611 	for(i = hook->depends; i; i = i->next) {
612 		if(!alpm_find_satisfier(pkgs, i->data)) {
613 			_alpm_log(handle, ALPM_LOG_ERROR, _("unable to run hook %s: %s\n"),
614 					hook->name, _("could not satisfy dependencies"));
615 			return -1;
616 		}
617 	}
618 
619 	if(hook->needs_targets) {
620 		alpm_list_t *ctx;
621 		hook->matches = alpm_list_msort(hook->matches,
622 				alpm_list_count(hook->matches), (alpm_list_fn_cmp)strcmp);
623 		/* hooks with multiple triggers could have duplicate matches */
624 		ctx = hook->matches = _alpm_strlist_dedup(hook->matches);
625 		return _alpm_run_chroot(handle, hook->cmd[0], hook->cmd,
626 				(_alpm_cb_io) _alpm_hook_feed_targets, &ctx);
627 	} else {
628 		return _alpm_run_chroot(handle, hook->cmd[0], hook->cmd, NULL, NULL);
629 	}
630 }
631 
_alpm_hook_run(alpm_handle_t * handle,alpm_hook_when_t when)632 int _alpm_hook_run(alpm_handle_t *handle, alpm_hook_when_t when)
633 {
634 	alpm_event_hook_t event = { .when = when };
635 	alpm_event_hook_run_t hook_event;
636 	alpm_list_t *i, *hooks = NULL, *hooks_triggered = NULL;
637 	const char *suffix = ".hook";
638 	size_t suflen = strlen(suffix), triggered = 0;
639 	int ret = 0;
640 
641 	for(i = alpm_list_last(handle->hookdirs); i; i = alpm_list_previous(i)) {
642 		char path[PATH_MAX];
643 		size_t dirlen;
644 		struct dirent *entry;
645 		DIR *d;
646 
647 		if((dirlen = strlen(i->data)) >= PATH_MAX) {
648 			_alpm_log(handle, ALPM_LOG_ERROR, _("could not open directory: %s: %s\n"),
649 					(char *)i->data, strerror(ENAMETOOLONG));
650 			ret = -1;
651 			continue;
652 		}
653 		memcpy(path, i->data, dirlen + 1);
654 
655 		if(!(d = opendir(path))) {
656 			if(errno == ENOENT) {
657 				continue;
658 			} else {
659 				_alpm_log(handle, ALPM_LOG_ERROR,
660 						_("could not open directory: %s: %s\n"), path, strerror(errno));
661 				ret = -1;
662 				continue;
663 			}
664 		}
665 
666 		while((errno = 0, entry = readdir(d))) {
667 			struct _alpm_hook_cb_ctx ctx = { handle, NULL };
668 			struct stat buf;
669 			size_t name_len;
670 
671 			if(strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
672 				continue;
673 			}
674 
675 			if((name_len = strlen(entry->d_name)) >= PATH_MAX - dirlen) {
676 				_alpm_log(handle, ALPM_LOG_ERROR, _("could not open file: %s%s: %s\n"),
677 						path, entry->d_name, strerror(ENAMETOOLONG));
678 				ret = -1;
679 				continue;
680 			}
681 			memcpy(path + dirlen, entry->d_name, name_len + 1);
682 
683 			if(name_len < suflen
684 					|| strcmp(entry->d_name + name_len - suflen, suffix) != 0) {
685 				_alpm_log(handle, ALPM_LOG_DEBUG, "skipping non-hook file %s\n", path);
686 				continue;
687 			}
688 
689 			if(find_hook(hooks, entry->d_name)) {
690 				_alpm_log(handle, ALPM_LOG_DEBUG, "skipping overridden hook %s\n", path);
691 				continue;
692 			}
693 
694 			if(stat(path, &buf) != 0) {
695 				_alpm_log(handle, ALPM_LOG_ERROR,
696 						_("could not stat file %s: %s\n"), path, strerror(errno));
697 				ret = -1;
698 				continue;
699 			}
700 
701 			if(S_ISDIR(buf.st_mode)) {
702 				_alpm_log(handle, ALPM_LOG_DEBUG, "skipping directory %s\n", path);
703 				continue;
704 			}
705 
706 			CALLOC(ctx.hook, sizeof(struct _alpm_hook_t), 1,
707 					ret = -1; closedir(d); goto cleanup);
708 
709 			_alpm_log(handle, ALPM_LOG_DEBUG, "parsing hook file %s\n", path);
710 			if(parse_ini(path, _alpm_hook_parse_cb, &ctx) != 0
711 					|| _alpm_hook_validate(handle, ctx.hook, path)) {
712 				_alpm_log(handle, ALPM_LOG_DEBUG, "parsing hook file %s failed\n", path);
713 				_alpm_hook_free(ctx.hook);
714 				ret = -1;
715 				continue;
716 			}
717 
718 			STRDUP(ctx.hook->name, entry->d_name, ret = -1; closedir(d); goto cleanup);
719 			hooks = alpm_list_add(hooks, ctx.hook);
720 		}
721 		if(errno != 0) {
722 			_alpm_log(handle, ALPM_LOG_ERROR, _("could not read directory: %s: %s\n"),
723 					(char *) i->data, strerror(errno));
724 			ret = -1;
725 		}
726 
727 		closedir(d);
728 	}
729 
730 	if(ret != 0 && when == ALPM_HOOK_PRE_TRANSACTION) {
731 		goto cleanup;
732 	}
733 
734 	hooks = alpm_list_msort(hooks, alpm_list_count(hooks),
735 			(alpm_list_fn_cmp)_alpm_hook_cmp);
736 
737 	for(i = hooks; i; i = i->next) {
738 		struct _alpm_hook_t *hook = i->data;
739 		if(hook && hook->when == when && _alpm_hook_triggered(handle, hook)) {
740 			hooks_triggered = alpm_list_add(hooks_triggered, hook);
741 			triggered++;
742 		}
743 	}
744 
745 	if(hooks_triggered != NULL) {
746 		event.type = ALPM_EVENT_HOOK_START;
747 		EVENT(handle, (void *)&event);
748 
749 		hook_event.position = 1;
750 		hook_event.total = triggered;
751 
752 		for(i = hooks_triggered; i; i = i->next, hook_event.position++) {
753 			struct _alpm_hook_t *hook = i->data;
754 			alpm_logaction(handle, ALPM_CALLER_PREFIX, "running '%s'...\n", hook->name);
755 
756 			hook_event.type = ALPM_EVENT_HOOK_RUN_START;
757 			hook_event.name = hook->name;
758 			hook_event.desc = hook->desc;
759 			EVENT(handle, &hook_event);
760 
761 			if(_alpm_hook_run_hook(handle, hook) != 0 && hook->abort_on_fail) {
762 				ret = -1;
763 			}
764 
765 			hook_event.type = ALPM_EVENT_HOOK_RUN_DONE;
766 			EVENT(handle, &hook_event);
767 
768 			if(ret != 0 && when == ALPM_HOOK_PRE_TRANSACTION) {
769 				break;
770 			}
771 		}
772 
773 		alpm_list_free(hooks_triggered);
774 
775 		event.type = ALPM_EVENT_HOOK_DONE;
776 		EVENT(handle, (void *)&event);
777 	}
778 
779 cleanup:
780 	alpm_list_free_inner(hooks, (alpm_list_fn_free) _alpm_hook_free);
781 	alpm_list_free(hooks);
782 
783 	return ret;
784 }
785