xref: /dragonfly/usr.bin/dsynth/subs.c (revision 335b9e93)
1 /*
2  * Copyright (c) 2019 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Matthew Dillon <dillon@backplane.com>
6  *
7  * This code uses concepts and configuration based on 'synth', by
8  * John R. Marino <draco@marino.st>, which was written in ada.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  *
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in
18  *    the documentation and/or other materials provided with the
19  *    distribution.
20  * 3. Neither the name of The DragonFly Project nor the names of its
21  *    contributors may be used to endorse or promote products derived
22  *    from this software without specific, prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
27  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
28  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
30  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
32  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
33  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
34  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35  * SUCH DAMAGE.
36  */
37 
38 #include "dsynth.h"
39 
40 buildenv_t *BuildEnv;
41 static buildenv_t **BuildEnvTail = &BuildEnv;
42 
43 extern char **environ;
44 
45 __dead2 void
46 _dfatal(const char *file __unused, int line __unused, const char *func,
47 	int do_errno, const char *ctl, ...)
48 {
49 	va_list va;
50 
51 	fprintf(stderr, "%s: ", func);
52 	va_start(va, ctl);
53 	vfprintf(stderr, ctl, va);
54 	va_end(va);
55 	if (do_errno & 1)
56 		fprintf(stderr, ": %s", strerror(errno));
57 	fprintf(stderr, "\n");
58 	fflush(stderr);
59 
60 	if (do_errno & 2)
61 		kill(getpid(), SIGQUIT);
62 	exit(1);
63 }
64 
65 void
66 _ddprintf(int tab, const char *ctl, ...)
67 {
68 	va_list va;
69 
70 	if (tab)
71 		printf("%*.*s", tab, tab, "");
72 	va_start(va, ctl);
73 	vfprintf(stdout, ctl, va);
74 	va_end(va);
75 }
76 
77 char *
78 strdup_or_null(char *str)
79 {
80 	if (str && str[0])
81 		return(strdup(str));
82 	return NULL;
83 }
84 
85 static const char *DLogNames[] = {
86 	"00_last_results.log",
87 	"01_success_list.log",
88 	"02_failure_list.log",
89 	"03_ignored_list.log",
90 	"04_skipped_list.log",
91 	"05_abnormal_command_output.log",
92 	"06_obsolete_packages.log",
93 	"07_debug.log",
94 };
95 
96 static int DLogFd[DLOG_COUNT];
97 static pthread_mutex_t DLogFdMutex;
98 
99 #define arysize(ary)	(sizeof((ary)) / sizeof((ary)[0]))
100 
101 static int
102 dlogfd(int which, int modes)
103 {
104 	char *path;
105 	int fd;
106 
107 	which &= DLOG_MASK;
108 	if ((fd = DLogFd[which]) > 0)
109 		return fd;
110 	pthread_mutex_lock(&DLogFdMutex);
111 	if ((fd = DLogFd[which]) <= 0) {
112 		asprintf(&path, "%s/%s", LogsPath, DLogNames[which]);
113 		fd = open(path, modes, 0666);
114 		DLogFd[which] = fd;
115 		free(path);
116 	}
117 	pthread_mutex_unlock(&DLogFdMutex);
118 
119 	return fd;
120 }
121 
122 
123 void
124 dlogreset(void)
125 {
126 	int i;
127 
128 	ddassert(DLOG_COUNT == arysize(DLogNames));
129 	for (i = 0; i < DLOG_COUNT; ++i) {
130 		if (DLogFd[i] > 0) {
131 			close(DLogFd[i]);
132 			DLogFd[i] = -1;
133 		}
134 		(void)dlogfd(i, O_RDWR|O_CREAT|O_TRUNC|O_APPEND);
135 	}
136 }
137 
138 void
139 _dlog(int which, const char *ctl, ...)
140 {
141 	va_list va;
142 	char *buf;
143 	char *ptr;
144 	size_t len;
145 	int fd;
146 	int filter;
147 
148 	filter = which;
149 	which &= DLOG_MASK;
150 
151 	ddassert((uint)which < DLOG_COUNT);
152 	va_start(va, ctl);
153 	vasprintf(&buf, ctl, va);
154 	va_end(va);
155 	len = strlen(buf);
156 
157 	/*
158 	 * The special sequence ## right-justfies the text after the ##.
159 	 *
160 	 * NOTE: Alignment test includes \n so use 80 instead of 79 to
161 	 *	 leave one char unused on a 80-column terminal.
162 	 */
163 	if ((ptr = strstr(buf, "##")) != NULL) {
164 		size_t l2;
165 		size_t l1;
166 		char *b2;
167 		int spc;
168 
169 		l1 = (int)(ptr - buf);
170 		l2 = len - l1 - 2;
171 		if (l1 <= 80 - l2) {
172 			spc = 80 - l1 - l2;
173 			buf[l1] = 0;
174 			asprintf(&b2, "%s%*.*s%s",
175 				 buf, spc, spc, "", ptr + 2);
176 		} else {
177 			buf[l1] = 0;
178 			asprintf(&b2, "%s%s", buf, ptr + 2);
179 		}
180 		len = strlen(b2);
181 		free(buf);
182 		buf = b2;
183 	}
184 
185 	/*
186 	 * All logs also go to log 00.
187 	 */
188 	if (which != DLOG_ALL) {
189 		fd = dlogfd(DLOG_ALL, O_RDWR|O_CREAT|O_APPEND);
190 		if (fd > 0)
191 			write(fd, buf, len);
192 	}
193 
194 	/*
195 	 * Nominal log target
196 	 */
197 	fd = dlogfd(which, O_RDWR|O_CREAT|O_APPEND);
198 	write(fd, buf, len);
199 
200 	/*
201 	 * If ncurses is not being used, all log output also goes
202 	 * to stdout, unless filtered.
203 	 */
204 	if ((UseNCurses == 0 || (filter & DLOG_STDOUT)) &&
205 	    (filter & DLOG_FILTER) == 0) {
206 		if (ColorOpt) {
207 			if (filter & DLOG_GRN)
208 				write(1, "\x1b[0;32m", 7);
209 			if (filter & DLOG_RED)
210 				write(1, "\x1b[0;31m", 7);
211 		}
212 		write(1, buf, len);
213 		if (ColorOpt && (filter & (DLOG_GRN|DLOG_RED))) {
214 			write(1, "\x1b[0;39m", 7);
215 		}
216 	}
217 	free(buf);
218 }
219 
220 int
221 dlog00_fd(void)
222 {
223 	return(dlogfd(DLOG_ALL, O_RDWR|O_CREAT|O_APPEND));
224 }
225 
226 /*
227  * Bulk and Build environment control.  These routines are only called
228  * unthreaded or when dsynth threads are idle.
229  */
230 void
231 addbuildenv(const char *label, const char *data, int type)
232 {
233 	buildenv_t *env;
234 
235 	env = calloc(1, sizeof(*env));
236 	env->a1 = strdup(label);
237 	env->a2 = strdup(data);
238 	env->label = env->a1;
239 	env->data = env->a2;
240 	env->type = type;
241 	*BuildEnvTail = env;
242 	BuildEnvTail = &env->next;
243 }
244 
245 void
246 delbuildenv(const char *label)
247 {
248 	buildenv_t **envp;
249 	buildenv_t *env;
250 
251 	envp = &BuildEnv;
252 	while ((env = *envp) != NULL) {
253 		if (strcmp(env->label, label) == 0) {
254 			*envp = env->next;
255 			if (env->a1)
256 				free(env->a1);
257 			if (env->a2)
258 				free(env->a2);
259 			free(env);
260 		} else {
261 			envp = &env->next;
262 		}
263 	}
264 	BuildEnvTail = envp;
265 }
266 
267 void
268 freestrp(char **strp)
269 {
270 	if (*strp) {
271 		free(*strp);
272 		*strp = NULL;
273 	}
274 }
275 
276 void
277 dupstrp(char **strp)
278 {
279 	if (*strp)
280 		*strp = strdup(*strp);
281 }
282 
283 int
284 ipcreadmsg(int fd, wmsg_t *msg)
285 {
286 	size_t res;
287 	ssize_t r;
288 	char *ptr;
289 
290 	res = sizeof(*msg);
291 	ptr = (char *)(void *)msg;
292 	while (res) {
293 		r = read(fd, ptr, res);
294 		if (r <= 0) {
295 			if (errno == EINTR)
296 				continue;
297 			return -1;
298 		}
299 		res -= (size_t)r;
300 		ptr += r;
301 	}
302 	return 0;
303 }
304 
305 int
306 ipcwritemsg(int fd, wmsg_t *msg)
307 {
308 	size_t res;
309 	ssize_t r;
310 	char *ptr;
311 
312 	res = sizeof(*msg);
313 	ptr = (char *)(void *)msg;
314 	while (res) {
315 		r = write(fd, ptr, res);
316 		if (r < 0) {
317 			if (errno == EINTR)
318 				continue;
319 			return -1;
320 		}
321 		res -= (size_t)r;
322 		ptr += r;
323 	}
324 	return 0;
325 }
326 
327 int
328 askyn(const char *ctl, ...)
329 {
330 	va_list va;
331 	char buf[256];
332 	int res = 0;
333 
334 	if (YesOpt)
335 		return 1;
336 
337 	for (;;) {
338 		va_start(va, ctl);
339 		vprintf(ctl, va);
340 		va_end(va);
341 		fflush(stdout);
342 		if (fgets(buf, sizeof(buf), stdin) == NULL)
343 			break;
344 		if (buf[0] == 'y' || buf[0] == 'Y') {
345 			res = 1;
346 			break;
347 		}
348 		if (buf[0] == 'n' || buf[0] == 'N') {
349 			res = 0;
350 			break;
351 		}
352 		printf("Please type y/n\n");
353 	}
354 	return res;
355 }
356 
357 /*
358  * Get swap% used 0.0-1.0.
359  *
360  * NOTE: This routine is intended to return quickly.
361  *
362  * NOTE: swap_cache (caching for hard drives) is persistent and should
363  *	 not be counted for our purposes.
364  */
365 double
366 getswappct(int *noswapp)
367 {
368 	long swap_size = 0;
369 	long swap_anon = 0;
370 	long swap_cache __unused = 0;
371 	size_t len;
372 	double dswap;
373 
374 	len = sizeof(swap_size);
375 	sysctlbyname("vm.swap_size", &swap_size, &len, NULL, 0);
376 	len = sizeof(swap_size);
377 	sysctlbyname("vm.swap_anon_use", &swap_anon, &len, NULL, 0);
378 	len = sizeof(swap_size);
379 	sysctlbyname("vm.swap_cache_use", &swap_cache, &len, NULL, 0);
380 	if (swap_size) {
381 		dswap = (double)(swap_anon /*+swap_cache*/) / (double)swap_size;
382 		*noswapp = 0;
383 	} else {
384 		dswap = 0.0;
385 		*noswapp = 1;
386 	}
387 	return dswap;
388 }
389 
390 /*
391  * dexec_open()/fgets/dexec_close()
392  *
393  * Similar to popen() but directly exec()s the argument list (cav[0] must
394  * be an absolute path).
395  *
396  * If xenv is non-NULL its an array of local buildenv_t's to be used.
397  * The array is terminated with a NULL xenv->label.
398  *
399  * If with_env is non-zero the configured environment is included.
400  *
401  * If with_mvars is non-zero the make environment is passed as VAR=DATA
402  * elements on the command line.
403  */
404 FILE *
405 dexec_open(const char **cav, int cac, pid_t *pidp, buildenv_t *xenv,
406 	   int with_env, int with_mvars)
407 {
408 	buildenv_t *benv;
409 	const char **cenv;
410 	char *allocary[MAXCAC*2];
411 	int env_basei;
412 	int envi;
413 	int alloci;
414 	int nullfd;
415 	int fds[2];
416 	pid_t pid;
417 	FILE *fp;
418 
419 	env_basei = 0;
420 	while (environ[env_basei])
421 		++env_basei;
422 	cenv = calloc(env_basei + MAXCAC, sizeof(char *));
423 	env_basei = 0;
424 	for (envi = 0; envi < env_basei; ++envi)
425 		cenv[envi] = environ[envi];
426 
427 	alloci = 0;
428 	for (benv = BuildEnv; benv; benv = benv->next) {
429 		if (with_mvars &&
430 		    (benv->type & BENV_CMDMASK) == BENV_MAKECONF) {
431 			asprintf(&allocary[alloci], "%s=%s",
432 				 benv->label, benv->data);
433 			cav[cac++] = allocary[alloci];
434 			++alloci;
435 		}
436 		if (with_env &&
437 		    (benv->type & BENV_PKGLIST) &&
438 		    (benv->type & BENV_CMDMASK) == BENV_ENVIRONMENT) {
439 			asprintf(&allocary[alloci], "%s=%s",
440 				 benv->label, benv->data);
441 			cenv[envi++] = allocary[alloci];
442 			++alloci;
443 		}
444 		ddassert(cac < MAXCAC && envi - env_basei < MAXCAC);
445 	}
446 
447 	/*
448 	 * Extra environment specific to this particular dexec
449 	 */
450 	while (xenv && xenv->label) {
451 		asprintf(&allocary[alloci], "%s=%s",
452 			 xenv->label, xenv->data);
453 		cenv[envi++] = allocary[alloci];
454 		++alloci;
455 		++xenv;
456 	}
457 
458 	cav[cac] = NULL;
459 	cenv[envi] = NULL;
460 
461 	if (pipe(fds) < 0)
462 		dfatal_errno("pipe");
463 	nullfd = open("/dev/null", O_RDWR);
464 	if (nullfd < 0)
465 		dfatal_errno("open(\"/dev/null\")");
466 
467 	/*
468 	 * We have to be very careful using vfork(), do only the bare
469 	 * minimum necessary in the child to set it up and exec it.
470 	 */
471 	pid = vfork();
472 	if (pid == 0) {
473 #if 0
474 		int i;
475 		printf("%s", cav[0]);
476 		for (i = 0; cav[i]; ++i)
477 			printf(" %s", cav[i]);
478 		printf("\n");
479 		printf("ENV: ");
480 		for (i = 0; cenv[i]; ++i)
481 			printf(" %s", cenv[i]);
482 #endif
483 
484 		if (fds[1] != 1) {
485 			dup2(fds[1], 1);
486 			close(fds[1]);
487 		}
488 		close(fds[0]);		/* safety */
489 		dup2(nullfd, 0);	/* no questions! */
490 		closefrom(3);		/* be nice */
491 
492 		/*
493 		 * Self-nice to be nice (ignore any error)
494 		 */
495 		if (NiceOpt)
496 			setpriority(PRIO_PROCESS, 0, NiceOpt);
497 
498 		execve(cav[0], (void *)cav, (void *)cenv);
499 		write(2, "EXEC FAILURE\n", 13);
500 		_exit(1);
501 	}
502 	close(nullfd);
503 	close(fds[1]);
504 	if (pid < 0) {
505 		close(fds[0]);
506 		dfatal_errno("vfork failed");
507 	}
508 	fp = fdopen(fds[0], "r");
509 	*pidp = pid;
510 
511 	while (--alloci >= 0)
512 		free(allocary[alloci]);
513 	free(cenv);
514 
515 	return fp;
516 }
517 
518 int
519 dexec_close(FILE *fp, pid_t pid)
520 {
521 	pid_t rpid;
522 	int status;
523 
524 	fclose(fp);
525 	while ((rpid = waitpid(pid, &status, 0)) != pid) {
526 		if (rpid < 0) {
527 			if (errno == EINTR)
528 				continue;
529 			return 1;
530 		}
531 	}
532 	return (WEXITSTATUS(status));
533 }
534 
535 const char *
536 getphasestr(worker_phase_t phaseid)
537 {
538 	const char *phase;
539 
540 	switch(phaseid) {
541 	case PHASE_PENDING:
542 		phase = "pending";
543 		break;
544 	case PHASE_INSTALL_PKGS:
545 		phase = "install-pkgs";
546 		break;
547 	case PHASE_CHECK_SANITY:
548 		phase = "check-sanity";
549 		break;
550 	case PHASE_PKG_DEPENDS:
551 		phase = "pkg-depends";
552 		break;
553 	case PHASE_FETCH_DEPENDS:
554 		phase = "fetch-depends";
555 		break;
556 	case PHASE_FETCH:
557 		phase = "fetch";
558 		break;
559 	case PHASE_CHECKSUM:
560 		phase = "checksum";
561 		break;
562 	case PHASE_EXTRACT_DEPENDS:
563 		phase = "extract-depends";
564 		break;
565 	case PHASE_EXTRACT:
566 		phase = "extract";
567 		break;
568 	case PHASE_PATCH_DEPENDS:
569 		phase = "patch-depends";
570 		break;
571 	case PHASE_PATCH:
572 		phase = "patch";
573 		break;
574 	case PHASE_BUILD_DEPENDS:
575 		phase = "build-depends";
576 		break;
577 	case PHASE_LIB_DEPENDS:
578 		phase = "lib-depends";
579 		break;
580 	case PHASE_CONFIGURE:
581 		phase = "configure";
582 		break;
583 	case PHASE_BUILD:
584 		phase = "build";
585 		break;
586 	case PHASE_RUN_DEPENDS:
587 		phase = "run-depends";
588 		break;
589 	case PHASE_STAGE:
590 		phase = "stage";
591 		break;
592 	case PHASE_TEST:
593 		phase = "test";
594 		break;
595 	case PHASE_CHECK_PLIST:
596 		phase = "check-plist";
597 		break;
598 	case PHASE_PACKAGE:
599 		phase = "package";
600 		break;
601 	case PHASE_INSTALL_MTREE:
602 		phase = "install-mtree";
603 		break;
604 	case PHASE_INSTALL:
605 		phase = "install";
606 		break;
607 	case PHASE_DEINSTALL:
608 		phase = "deinstall";
609 		break;
610 	default:
611 		phase = "Run-Unknown";
612 		break;
613 	}
614 	return phase;
615 }
616 
617 int
618 readlogline(monitorlog_t *log, char **bufp)
619 {
620 	int r;
621 	int n;
622 
623 	/*
624 	 * Reset buffer as an optimization to avoid unnecessary
625 	 * shifts.
626 	 */
627 	*bufp = NULL;
628 	if (log->buf_beg == log->buf_end) {
629 		log->buf_beg = 0;
630 		log->buf_end = 0;
631 		log->buf_scan = 0;
632 	}
633 
634 	/*
635 	 * Look for newline, handle discard mode
636 	 */
637 again:
638 	for (n = log->buf_scan; n < log->buf_end; ++n) {
639 		if (log->buf[n] == '\n') {
640 			*bufp = log->buf + log->buf_beg;
641 			r = n - log->buf_beg;
642 			log->buf_beg = n + 1;
643 			log->buf_scan = n + 1;
644 
645 			if (log->buf_discard_mode == 0)
646 				return r;
647 			log->buf_discard_mode = 0;
648 			goto again;
649 		}
650 	}
651 
652 	/*
653 	 * Handle overflow
654 	 */
655 	if (n == sizeof(log->buf)) {
656 		if (log->buf_beg) {
657 			/*
658 			 * Shift the buffer to make room and read more data.
659 			 */
660 			bcopy(log->buf + log->buf_beg,
661 			      log->buf,
662 			      n - log->buf_beg);
663 			log->buf_end -= log->buf_beg;
664 			log->buf_scan -= log->buf_beg;
665 			n -= log->buf_beg;
666 			log->buf_beg = 0;
667 		} else if (log->buf_discard_mode) {
668 			/*
669 			 * Overflow.  If in discard mode just throw it all
670 			 * away.  Stay in discard mode.
671 			 */
672 			log->buf_beg = 0;
673 			log->buf_end = 0;
674 			log->buf_scan = 0;
675 		} else {
676 			/*
677 			 * Overflow.  If not in discard mode return a truncated
678 			 * line and enter discard mode.
679 			 *
680 			 * The caller will temporarily set ptr[r] = 0 so make
681 			 * sure that does not overflow our buffer as we are not
682 			 * at a newline.
683 			 *
684 			 * (log->buf_beg is 0);
685 			 */
686 			*bufp = log->buf + log->buf_beg;
687 			r = n - 1;
688 			log->buf_beg = n;
689 			log->buf_scan = n;
690 			log->buf_discard_mode = 1;
691 
692 			return r;
693 		}
694 	}
695 
696 	/*
697 	 * Read more data.  If there is no data pending then return -1,
698 	 * otherwise loop up to see if more complete line(s) are available.
699 	 */
700 	r = pread(log->fd,
701 		  log->buf + log->buf_end,
702 		  sizeof(log->buf) - log->buf_end,
703 		  log->offset);
704 	if (r <= 0)
705 		return -1;
706 	log->offset += r;
707 	log->buf_end += r;
708 	goto again;
709 }
710 
711 uint32_t
712 crcDirTree(const char *path)
713 {
714 	FTS *fts;
715 	FTSENT *fen;
716 	struct stat *st;
717 	char *pav[2];
718 	uint32_t crc;
719 	uint32_t val;
720 
721 	crc = 0;
722 	pav[0] = strdup(path);
723 	pav[1] = NULL;
724 
725 	fts = fts_open(pav, FTS_PHYSICAL | FTS_NOCHDIR, NULL);
726 	if (fts == NULL)
727 		goto failed;
728 	while ((fen = fts_read(fts)) != NULL) {
729 		if (fen->fts_info != FTS_F && fen->fts_info != FTS_SL)
730 			continue;
731 		if (fen->fts_namelen >= 5 &&
732 		    !strcmp(fen->fts_name + fen->fts_namelen - 5, ".core")) {
733 			continue;
734 		}
735 
736 		st = fen->fts_statp;
737 
738 		val = iscsi_crc32(&st->st_mtime, sizeof(st->st_mtime));
739 		val = iscsi_crc32_ext(&st->st_size, sizeof(st->st_size), val);
740 		val = iscsi_crc32_ext(fen->fts_path, fen->fts_pathlen, val);
741 		crc ^= val;
742 	}
743 	fts_close(fts);
744 failed:
745 	free(pav[0]);
746 
747 	return crc;
748 }
749