xref: /dragonfly/usr.bin/dsynth/subs.c (revision 3074866b)
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 };
94 
95 static int DLogFd[DLOG_COUNT];
96 static pthread_mutex_t DLogFdMutex;
97 
98 #define arysize(ary)	(sizeof((ary)) / sizeof((ary)[0]))
99 
100 static int
101 dlogfd(int which, int modes)
102 {
103 	char *path;
104 	int fd;
105 
106 	which &= DLOG_MASK;
107 	if ((fd = DLogFd[which]) > 0)
108 		return fd;
109 	pthread_mutex_lock(&DLogFdMutex);
110 	if ((fd = DLogFd[which]) <= 0) {
111 		asprintf(&path, "%s/%s", LogsPath, DLogNames[which]);
112 		fd = open(path, modes, 0666);
113 		DLogFd[which] = fd;
114 		free(path);
115 	}
116 	pthread_mutex_unlock(&DLogFdMutex);
117 
118 	return fd;
119 }
120 
121 
122 void
123 dlogreset(void)
124 {
125 	int i;
126 
127 	ddassert(DLOG_COUNT == arysize(DLogNames));
128 	for (i = 0; i < DLOG_COUNT; ++i) {
129 		if (DLogFd[i] > 0) {
130 			close(DLogFd[i]);
131 			DLogFd[i] = -1;
132 		}
133 		(void)dlogfd(i, O_RDWR|O_CREAT|O_TRUNC|O_APPEND);
134 	}
135 }
136 
137 void
138 _dlog(int which, const char *ctl, ...)
139 {
140 	va_list va;
141 	char *buf;
142 	char *ptr;
143 	size_t len;
144 	int fd;
145 	int filter;
146 
147 	filter = which;
148 	which &= DLOG_MASK;
149 
150 	ddassert((uint)which < DLOG_COUNT);
151 	va_start(va, ctl);
152 	vasprintf(&buf, ctl, va);
153 	va_end(va);
154 	len = strlen(buf);
155 
156 	/*
157 	 * The special sequence ## right-justfies the text after the ##.
158 	 *
159 	 * NOTE: Alignment test includes \n so use 80 instead of 79 to
160 	 *	 leave one char unused on a 80-column terminal.
161 	 */
162 	if ((ptr = strstr(buf, "##")) != NULL) {
163 		size_t l2;
164 		size_t l1;
165 		char *b2;
166 		int spc;
167 
168 		l1 = (int)(ptr - buf);
169 		l2 = len - l1 - 2;
170 		if (l1 <= 80 - l2) {
171 			spc = 80 - l1 - l2;
172 			buf[l1] = 0;
173 			asprintf(&b2, "%s%*.*s%s",
174 				 buf, spc, spc, "", ptr + 2);
175 		} else {
176 			buf[l1] = 0;
177 			asprintf(&b2, "%s%s", buf, ptr + 2);
178 		}
179 		len = strlen(b2);
180 		free(buf);
181 		buf = b2;
182 	}
183 
184 	/*
185 	 * All logs also go to log 00.
186 	 */
187 	if (which != DLOG_ALL) {
188 		fd = dlogfd(DLOG_ALL, O_RDWR|O_CREAT|O_APPEND);
189 		if (fd > 0)
190 			write(fd, buf, len);
191 	}
192 
193 	/*
194 	 * Nominal log target
195 	 */
196 	fd = dlogfd(which, O_RDWR|O_CREAT|O_APPEND);
197 	write(fd, buf, len);
198 
199 	/*
200 	 * If ncurses is not being used, all log output also goes
201 	 * to stdout, unless filtered.
202 	 */
203 	if ((UseNCurses == 0 || (filter & DLOG_STDOUT)) &&
204 	    (filter & DLOG_FILTER) == 0) {
205 		if (ColorOpt) {
206 			if (filter & DLOG_GRN)
207 				write(1, "\x1b[0;32m", 7);
208 			if (filter & DLOG_RED)
209 				write(1, "\x1b[0;31m", 7);
210 		}
211 		write(1, buf, len);
212 		if (ColorOpt && (filter & (DLOG_GRN|DLOG_RED))) {
213 			write(1, "\x1b[0;39m", 7);
214 		}
215 	}
216 	free(buf);
217 }
218 
219 int
220 dlog00_fd(void)
221 {
222 	return(dlogfd(DLOG_ALL, O_RDWR|O_CREAT|O_APPEND));
223 }
224 
225 /*
226  * Bulk and Build environment control.  These routines are only called
227  * unthreaded or when dsynth threads are idle.
228  */
229 void
230 addbuildenv(const char *label, const char *data, int type)
231 {
232 	buildenv_t *env;
233 
234 	env = calloc(1, sizeof(*env));
235 	env->a1 = strdup(label);
236 	env->a2 = strdup(data);
237 	env->label = env->a1;
238 	env->data = env->a2;
239 	env->type = type;
240 	*BuildEnvTail = env;
241 	BuildEnvTail = &env->next;
242 }
243 
244 void
245 delbuildenv(const char *label)
246 {
247 	buildenv_t **envp;
248 	buildenv_t *env;
249 
250 	envp = &BuildEnv;
251 	while ((env = *envp) != NULL) {
252 		if (strcmp(env->label, label) == 0) {
253 			*envp = env->next;
254 			if (env->a1)
255 				free(env->a1);
256 			if (env->a2)
257 				free(env->a2);
258 			free(env);
259 		} else {
260 			envp = &env->next;
261 		}
262 	}
263 	BuildEnvTail = envp;
264 }
265 
266 void
267 freestrp(char **strp)
268 {
269 	if (*strp) {
270 		free(*strp);
271 		*strp = NULL;
272 	}
273 }
274 
275 void
276 dupstrp(char **strp)
277 {
278 	if (*strp)
279 		*strp = strdup(*strp);
280 }
281 
282 int
283 ipcreadmsg(int fd, wmsg_t *msg)
284 {
285 	size_t res;
286 	ssize_t r;
287 	char *ptr;
288 
289 	res = sizeof(*msg);
290 	ptr = (char *)(void *)msg;
291 	while (res) {
292 		r = read(fd, ptr, res);
293 		if (r <= 0) {
294 			if (errno == EINTR)
295 				continue;
296 			return -1;
297 		}
298 		res -= (size_t)r;
299 		ptr += r;
300 	}
301 	return 0;
302 }
303 
304 int
305 ipcwritemsg(int fd, wmsg_t *msg)
306 {
307 	size_t res;
308 	ssize_t r;
309 	char *ptr;
310 
311 	res = sizeof(*msg);
312 	ptr = (char *)(void *)msg;
313 	while (res) {
314 		r = write(fd, ptr, res);
315 		if (r < 0) {
316 			if (errno == EINTR)
317 				continue;
318 			return -1;
319 		}
320 		res -= (size_t)r;
321 		ptr += r;
322 	}
323 	return 0;
324 }
325 
326 int
327 askyn(const char *ctl, ...)
328 {
329 	va_list va;
330 	char buf[256];
331 	int res = 0;
332 
333 	if (YesOpt)
334 		return 1;
335 
336 	for (;;) {
337 		va_start(va, ctl);
338 		vprintf(ctl, va);
339 		va_end(va);
340 		fflush(stdout);
341 		if (fgets(buf, sizeof(buf), stdin) == NULL)
342 			break;
343 		if (buf[0] == 'y' || buf[0] == 'Y') {
344 			res = 1;
345 			break;
346 		}
347 		if (buf[0] == 'n' || buf[0] == 'N') {
348 			res = 0;
349 			break;
350 		}
351 		printf("Please type y/n\n");
352 	}
353 	return res;
354 }
355 
356 /*
357  * Get swap% used 0.0-1.0.
358  *
359  * NOTE: This routine is intended to return quickly.
360  *
361  * NOTE: swap_cache (caching for hard drives) is persistent and should
362  *	 not be counted for our purposes.
363  */
364 double
365 getswappct(int *noswapp)
366 {
367 	long swap_size = 0;
368 	long swap_anon = 0;
369 	long swap_cache __unused = 0;
370 	size_t len;
371 	double dswap;
372 
373 	len = sizeof(swap_size);
374 	sysctlbyname("vm.swap_size", &swap_size, &len, NULL, 0);
375 	len = sizeof(swap_size);
376 	sysctlbyname("vm.swap_anon_use", &swap_anon, &len, NULL, 0);
377 	len = sizeof(swap_size);
378 	sysctlbyname("vm.swap_cache_use", &swap_cache, &len, NULL, 0);
379 	if (swap_size) {
380 		dswap = (double)(swap_anon /*+swap_cache*/) / (double)swap_size;
381 		*noswapp = 0;
382 	} else {
383 		dswap = 0.0;
384 		*noswapp = 1;
385 	}
386 	return dswap;
387 }
388 
389 /*
390  * dexec_open()/fgets/dexec_close()
391  *
392  * Similar to popen() but directly exec()s the argument list (cav[0] must
393  * be an absolute path).
394  *
395  * If xenv is non-NULL its an array of local buildenv_t's to be used.
396  * The array is terminated with a NULL xenv->label.
397  *
398  * If with_env is non-zero the configured environment is included.
399  *
400  * If with_mvars is non-zero the make environment is passed as VAR=DATA
401  * elements on the command line.
402  */
403 FILE *
404 dexec_open(const char **cav, int cac, pid_t *pidp, buildenv_t *xenv,
405 	   int with_env, int with_mvars)
406 {
407 	buildenv_t *benv;
408 	const char **cenv;
409 	char *allocary[MAXCAC*2];
410 	int env_basei;
411 	int envi;
412 	int alloci;
413 	int nullfd;
414 	int fds[2];
415 	pid_t pid;
416 	FILE *fp;
417 
418 	env_basei = 0;
419 	while (environ[env_basei])
420 		++env_basei;
421 	cenv = calloc(env_basei + MAXCAC, sizeof(char *));
422 	env_basei = 0;
423 	for (envi = 0; envi < env_basei; ++envi)
424 		cenv[envi] = environ[envi];
425 
426 	alloci = 0;
427 	for (benv = BuildEnv; benv; benv = benv->next) {
428 		if (with_mvars &&
429 		    (benv->type & BENV_CMDMASK) == BENV_MAKECONF) {
430 			asprintf(&allocary[alloci], "%s=%s",
431 				 benv->label, benv->data);
432 			cav[cac++] = allocary[alloci];
433 			++alloci;
434 		}
435 		if (with_env &&
436 		    (benv->type & BENV_PKGLIST) &&
437 		    (benv->type & BENV_CMDMASK) == BENV_ENVIRONMENT) {
438 			asprintf(&allocary[alloci], "%s=%s",
439 				 benv->label, benv->data);
440 			cenv[envi++] = allocary[alloci];
441 			++alloci;
442 		}
443 		ddassert(cac < MAXCAC && envi - env_basei < MAXCAC);
444 	}
445 
446 	/*
447 	 * Extra environment specific to this particular dexec
448 	 */
449 	while (xenv && xenv->label) {
450 		asprintf(&allocary[alloci], "%s=%s",
451 			 xenv->label, xenv->data);
452 		cenv[envi++] = allocary[alloci];
453 		++alloci;
454 		++xenv;
455 	}
456 
457 	cav[cac] = NULL;
458 	cenv[envi] = NULL;
459 
460 	if (pipe(fds) < 0)
461 		dfatal_errno("pipe");
462 	nullfd = open("/dev/null", O_RDWR);
463 	if (nullfd < 0)
464 		dfatal_errno("open(\"/dev/null\")");
465 
466 	/*
467 	 * We have to be very careful using vfork(), do only the bare
468 	 * minimum necessary in the child to set it up and exec it.
469 	 */
470 	pid = vfork();
471 	if (pid == 0) {
472 #if 0
473 		int i;
474 		printf("%s", cav[0]);
475 		for (i = 0; cav[i]; ++i)
476 			printf(" %s", cav[i]);
477 		printf("\n");
478 		printf("ENV: ");
479 		for (i = 0; cenv[i]; ++i)
480 			printf(" %s", cenv[i]);
481 #endif
482 
483 		if (fds[1] != 1) {
484 			dup2(fds[1], 1);
485 			close(fds[1]);
486 		}
487 		close(fds[0]);		/* safety */
488 		dup2(nullfd, 0);	/* no questions! */
489 		closefrom(3);		/* be nice */
490 
491 		execve(cav[0], (void *)cav, (void *)cenv);
492 		write(2, "EXEC FAILURE\n", 13);
493 		_exit(1);
494 	}
495 	close(nullfd);
496 	close(fds[1]);
497 	if (pid < 0) {
498 		close(fds[0]);
499 		dfatal_errno("vfork failed");
500 	}
501 	fp = fdopen(fds[0], "r");
502 	*pidp = pid;
503 
504 	while (--alloci >= 0)
505 		free(allocary[alloci]);
506 	free(cenv);
507 
508 	return fp;
509 }
510 
511 int
512 dexec_close(FILE *fp, pid_t pid)
513 {
514 	pid_t rpid;
515 	int status;
516 
517 	fclose(fp);
518 	while ((rpid = waitpid(pid, &status, 0)) != pid) {
519 		if (rpid < 0) {
520 			if (errno == EINTR)
521 				continue;
522 			return 1;
523 		}
524 	}
525 	return (WEXITSTATUS(status));
526 }
527 
528 const char *
529 getphasestr(worker_phase_t phaseid)
530 {
531 	const char *phase;
532 
533 	switch(phaseid) {
534 	case PHASE_PENDING:
535 		phase = "pending";
536 		break;
537 	case PHASE_INSTALL_PKGS:
538 		phase = "install-pkgs";
539 		break;
540 	case PHASE_CHECK_SANITY:
541 		phase = "check-sanity";
542 		break;
543 	case PHASE_PKG_DEPENDS:
544 		phase = "pkg-depends";
545 		break;
546 	case PHASE_FETCH_DEPENDS:
547 		phase = "fetch-depends";
548 		break;
549 	case PHASE_FETCH:
550 		phase = "fetch";
551 		break;
552 	case PHASE_CHECKSUM:
553 		phase = "checksum";
554 		break;
555 	case PHASE_EXTRACT_DEPENDS:
556 		phase = "extract-depends";
557 		break;
558 	case PHASE_EXTRACT:
559 		phase = "extract";
560 		break;
561 	case PHASE_PATCH_DEPENDS:
562 		phase = "patch-depends";
563 		break;
564 	case PHASE_PATCH:
565 		phase = "patch";
566 		break;
567 	case PHASE_BUILD_DEPENDS:
568 		phase = "build-depends";
569 		break;
570 	case PHASE_LIB_DEPENDS:
571 		phase = "lib-depends";
572 		break;
573 	case PHASE_CONFIGURE:
574 		phase = "configure";
575 		break;
576 	case PHASE_BUILD:
577 		phase = "build";
578 		break;
579 	case PHASE_RUN_DEPENDS:
580 		phase = "run-depends";
581 		break;
582 	case PHASE_STAGE:
583 		phase = "stage";
584 		break;
585 	case PHASE_TEST:
586 		phase = "test";
587 		break;
588 	case PHASE_CHECK_PLIST:
589 		phase = "check-plist";
590 		break;
591 	case PHASE_PACKAGE:
592 		phase = "package";
593 		break;
594 	case PHASE_INSTALL_MTREE:
595 		phase = "install-mtree";
596 		break;
597 	case PHASE_INSTALL:
598 		phase = "install";
599 		break;
600 	case PHASE_DEINSTALL:
601 		phase = "deinstall";
602 		break;
603 	default:
604 		phase = "Run-Unknown";
605 		break;
606 	}
607 	return phase;
608 }
609 
610 int
611 readlogline(monitorlog_t *log, char **bufp)
612 {
613 	int r;
614 	int n;
615 
616 	/*
617 	 * Reset buffer as an optimization to avoid unnecessary
618 	 * shifts.
619 	 */
620 	*bufp = NULL;
621 	if (log->buf_beg == log->buf_end) {
622 		log->buf_beg = 0;
623 		log->buf_end = 0;
624 		log->buf_scan = 0;
625 	}
626 
627 	/*
628 	 * Look for newline, handle discard mode
629 	 */
630 again:
631 	for (n = log->buf_scan; n < log->buf_end; ++n) {
632 		if (log->buf[n] == '\n') {
633 			*bufp = log->buf + log->buf_beg;
634 			r = n - log->buf_beg;
635 			log->buf_beg = n + 1;
636 			log->buf_scan = n + 1;
637 
638 			if (log->buf_discard_mode == 0)
639 				return r;
640 			log->buf_discard_mode = 0;
641 			goto again;
642 		}
643 	}
644 
645 	/*
646 	 * Handle overflow
647 	 */
648 	if (n == sizeof(log->buf)) {
649 		if (log->buf_beg) {
650 			/*
651 			 * Shift the buffer to make room and read more data.
652 			 */
653 			bcopy(log->buf + log->buf_beg,
654 			      log->buf,
655 			      n - log->buf_beg);
656 			log->buf_end -= log->buf_beg;
657 			log->buf_scan -= log->buf_beg;
658 			n -= log->buf_beg;
659 			log->buf_beg = 0;
660 		} else if (log->buf_discard_mode) {
661 			/*
662 			 * Overflow.  If in discard mode just throw it all
663 			 * away.  Stay in discard mode.
664 			 */
665 			log->buf_beg = 0;
666 			log->buf_end = 0;
667 			log->buf_scan = 0;
668 		} else {
669 			/*
670 			 * Overflow.  If not in discard mode return a truncated
671 			 * line and enter discard mode.
672 			 *
673 			 * The caller will temporarily set ptr[r] = 0 so make
674 			 * sure that does not overflow our buffer as we are not
675 			 * at a newline.
676 			 *
677 			 * (log->buf_beg is 0);
678 			 */
679 			*bufp = log->buf + log->buf_beg;
680 			r = n - 1;
681 			log->buf_beg = n;
682 			log->buf_scan = n;
683 			log->buf_discard_mode = 1;
684 
685 			return r;
686 		}
687 	}
688 
689 	/*
690 	 * Read more data.  If there is no data pending then return -1,
691 	 * otherwise loop up to see if more complete line(s) are available.
692 	 */
693 	r = pread(log->fd,
694 		  log->buf + log->buf_end,
695 		  sizeof(log->buf) - log->buf_end,
696 		  log->offset);
697 	if (r <= 0)
698 		return -1;
699 	log->offset += r;
700 	log->buf_end += r;
701 	goto again;
702 }
703