diff options
Diffstat (limited to 'lib/libc')
-rw-r--r-- | lib/libc/gen/fdopendir.c | 10 | ||||
-rw-r--r-- | lib/libc/net/gethostbydns.c | 6 | ||||
-rw-r--r-- | lib/libc/net/res_config.h | 4 | ||||
-rw-r--r-- | lib/libc/resolv/res_debug.h | 2 | ||||
-rw-r--r-- | lib/libc/resolv/res_init.c | 6 | ||||
-rw-r--r-- | lib/libc/resolv/res_mkquery.c | 5 | ||||
-rw-r--r-- | lib/libc/resolv/res_mkupdate.c | 4 | ||||
-rw-r--r-- | lib/libc/resolv/res_query.c | 5 | ||||
-rw-r--r-- | lib/libc/resolv/res_send.c | 49 | ||||
-rw-r--r-- | lib/libc/stdio/fdopen.c | 18 | ||||
-rw-r--r-- | lib/libc/stdio/freopen.c | 10 | ||||
-rw-r--r-- | lib/libc/stdtime/Makefile.inc | 1 | ||||
-rw-r--r-- | lib/libc/stdtime/Symbol.map | 6 | ||||
-rw-r--r-- | lib/libc/string/memchr.3 | 16 | ||||
-rw-r--r-- | lib/libc/tests/stdtime/Makefile | 5 | ||||
-rw-r--r-- | lib/libc/tests/stdtime/detect_tz_changes_test.c | 281 |
16 files changed, 352 insertions, 76 deletions
diff --git a/lib/libc/gen/fdopendir.c b/lib/libc/gen/fdopendir.c index df6709fbcb85..9393cbe28f85 100644 --- a/lib/libc/gen/fdopendir.c +++ b/lib/libc/gen/fdopendir.c @@ -48,8 +48,16 @@ DIR * fdopendir(int fd) { + int flags, rc; - if (_fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) + flags = _fcntl(fd, F_GETFD, 0); + if (flags == -1) return (NULL); + + if ((flags & FD_CLOEXEC) == 0) { + rc = _fcntl(fd, F_SETFD, flags | FD_CLOEXEC); + if (rc == -1) + return (NULL); + } return (__opendir_common(fd, DTF_HIDEW | DTF_NODUP, true)); } diff --git a/lib/libc/net/gethostbydns.c b/lib/libc/net/gethostbydns.c index b29fa1cdd845..216fc9bcf9a4 100644 --- a/lib/libc/net/gethostbydns.c +++ b/lib/libc/net/gethostbydns.c @@ -74,8 +74,10 @@ #define SPRINTF(x) ((size_t)sprintf x) +#ifdef DEBUG static const char AskedForGot[] = "gethostby*.gethostanswer: asked for \"%s\", got \"%s\""; +#endif #ifdef RESOLVSORT static void addrsort(char **, int, res_state); @@ -299,8 +301,10 @@ gethostanswer(const querybuf *answer, int anslen, const char *qname, int qtype, switch (type) { case T_PTR: if (strcasecmp(tname, bp) != 0) { +#ifdef DEBUG syslog(LOG_NOTICE|LOG_AUTH, AskedForGot, qname, bp); +#endif cp += n; continue; /* XXX - had_error++ ? */ } @@ -347,8 +351,10 @@ gethostanswer(const querybuf *answer, int anslen, const char *qname, int qtype, case T_A: case T_AAAA: if (strcasecmp(he->h_name, bp) != 0) { +#ifdef DEBUG syslog(LOG_NOTICE|LOG_AUTH, AskedForGot, he->h_name, bp); +#endif cp += n; continue; /* XXX - had_error++ ? */ } diff --git a/lib/libc/net/res_config.h b/lib/libc/net/res_config.h index f049d6817b7a..39a1b5f1486f 100644 --- a/lib/libc/net/res_config.h +++ b/lib/libc/net/res_config.h @@ -1,5 +1,5 @@ -#define DEBUG 1 /* enable debugging code (needed for dig) */ +//#define DEBUG /* enable debugging code */ #define RESOLVSORT /* allow sorting of addresses in gethostbyname */ -#undef SUNSECURITY /* verify gethostbyaddr() calls - WE DON'T NEED IT */ +//#define SUNSECURITY /* verify gethostbyaddr() calls */ #define MULTI_PTRS_ARE_ALIASES 1 /* fold multiple PTR records into aliases */ diff --git a/lib/libc/resolv/res_debug.h b/lib/libc/resolv/res_debug.h index dd048116fb49..ccae03e625aa 100644 --- a/lib/libc/resolv/res_debug.h +++ b/lib/libc/resolv/res_debug.h @@ -23,7 +23,7 @@ #ifndef DEBUG # define Dprint(cond, args) /*empty*/ # define DprintQ(cond, args, query, size) /*empty*/ -# define Aerror(statp, file, string, error, address) /*empty*/ +# define Aerror(statp, file, string, error, address, alen) /*empty*/ # define Perror(statp, file, string, error) /*empty*/ #else # define Dprint(cond, args) if (cond) {fprintf args;} else {} diff --git a/lib/libc/resolv/res_init.c b/lib/libc/resolv/res_init.c index 70d6bc6d3bf2..71ab2dcb7038 100644 --- a/lib/libc/resolv/res_init.c +++ b/lib/libc/resolv/res_init.c @@ -108,12 +108,6 @@ #include "res_private.h" -/*% Options. Should all be left alone. */ -#define RESOLVSORT -#ifndef DEBUG -#define DEBUG -#endif - #ifdef SOLARIS2 #include <sys/systeminfo.h> #endif diff --git a/lib/libc/resolv/res_mkquery.c b/lib/libc/resolv/res_mkquery.c index 0c15def5d117..f6767a92375c 100644 --- a/lib/libc/resolv/res_mkquery.c +++ b/lib/libc/resolv/res_mkquery.c @@ -76,11 +76,6 @@ #include <string.h> #include "port_after.h" -/* Options. Leave them on. */ -#ifndef DEBUG -#define DEBUG -#endif - extern const char *_res_opcodes[]; /*% diff --git a/lib/libc/resolv/res_mkupdate.c b/lib/libc/resolv/res_mkupdate.c index e5a3cb702cda..3f595dc4ec08 100644 --- a/lib/libc/resolv/res_mkupdate.c +++ b/lib/libc/resolv/res_mkupdate.c @@ -48,10 +48,6 @@ #include "port_after.h" -/* Options. Leave them on. */ -#ifndef DEBUG -#define DEBUG -#endif #define MAXPORT 1024 static int getnum_str(u_char **, u_char *); diff --git a/lib/libc/resolv/res_query.c b/lib/libc/resolv/res_query.c index e9c628ad8d47..f26d59e522b4 100644 --- a/lib/libc/resolv/res_query.c +++ b/lib/libc/resolv/res_query.c @@ -81,11 +81,6 @@ #include <unistd.h> #include "port_after.h" -/* Options. Leave them on. */ -#ifndef DEBUG -#define DEBUG -#endif - #if PACKETSZ > 1024 #define MAXPACKET PACKETSZ #else diff --git a/lib/libc/resolv/res_send.c b/lib/libc/resolv/res_send.c index 3fb627b83d55..08c3aed7f934 100644 --- a/lib/libc/resolv/res_send.c +++ b/lib/libc/resolv/res_send.c @@ -112,10 +112,6 @@ #include "un-namespace.h" -/* Options. Leave them on. */ -#ifndef DEBUG -#define DEBUG -#endif #include "res_debug.h" #include "res_private.h" @@ -138,15 +134,12 @@ static int send_dg(res_state, const u_char *, int, u_char *, int, int *, int, int, int *, int *); +#ifdef DEBUG static void Aerror(const res_state, FILE *, const char *, int, const struct sockaddr *, int); static void Perror(const res_state, FILE *, const char *, int); -static int sock_eq(struct sockaddr *, struct sockaddr *); -#if defined(NEED_PSELECT) && !defined(USE_POLL) && !defined(USE_KQUEUE) -static int pselect(int, void *, void *, void *, - struct timespec *, - const sigset_t *); #endif +static int sock_eq(struct sockaddr *, struct sockaddr *); void res_pquery(const res_state, const u_char *, int, FILE *); static const int niflags = NI_NUMERICHOST | NI_NUMERICSERV; @@ -302,7 +295,9 @@ res_nsend(res_state statp, #ifdef USE_KQUEUE int kq; #endif +#ifdef DEBUG char abuf[NI_MAXHOST]; +#endif /* No name servers or res_init() failure */ if (statp->nscount == 0 || EXT(statp).ext == NULL) { @@ -418,10 +413,10 @@ res_nsend(res_state statp, */ for (tries = 0; tries < statp->retry; tries++) { for (ns = 0; ns < statp->nscount; ns++) { - struct sockaddr *nsap; - int nsaplen; - nsap = get_nsaddr(statp, ns); - nsaplen = get_salen(nsap); + struct sockaddr *nsap = get_nsaddr(statp, ns); +#ifdef DEBUG + int nsaplen = get_salen(nsap); +#endif statp->_flags &= ~RES_F_LASTMASK; statp->_flags |= (ns << RES_F_LASTSHIFT); same_ns: @@ -1088,6 +1083,7 @@ send_dg(res_state statp, return (resplen); } +#ifdef DEBUG static void Aerror(const res_state statp, FILE *file, const char *string, int error, const struct sockaddr *address, int alen) @@ -1119,6 +1115,7 @@ Perror(const res_state statp, FILE *file, const char *string, int error) { string, strerror(error)); errno = save; } +#endif static int sock_eq(struct sockaddr *a, struct sockaddr *b) { @@ -1145,29 +1142,3 @@ sock_eq(struct sockaddr *a, struct sockaddr *b) { return 0; } } - -#if defined(NEED_PSELECT) && !defined(USE_POLL) && !defined(USE_KQUEUE) -/* XXX needs to move to the porting library. */ -static int -pselect(int nfds, void *rfds, void *wfds, void *efds, - struct timespec *tsp, const sigset_t *sigmask) -{ - struct timeval tv, *tvp; - sigset_t sigs; - int n; - - if (tsp) { - tvp = &tv; - tv = evTimeVal(*tsp); - } else - tvp = NULL; - if (sigmask) - sigprocmask(SIG_SETMASK, sigmask, &sigs); - n = select(nfds, rfds, wfds, efds, tvp); - if (sigmask) - sigprocmask(SIG_SETMASK, &sigs, NULL); - if (tsp) - *tsp = evTimeSpec(tv); - return (n); -} -#endif diff --git a/lib/libc/stdio/fdopen.c b/lib/libc/stdio/fdopen.c index a0d7b71df782..49ec97eda39d 100644 --- a/lib/libc/stdio/fdopen.c +++ b/lib/libc/stdio/fdopen.c @@ -46,7 +46,7 @@ FILE * fdopen(int fd, const char *mode) { FILE *fp; - int flags, oflags, fdflags, tmp; + int flags, oflags, fdflags, rc, tmp; /* * File descriptors are a full int, but _file is only a short. @@ -76,9 +76,19 @@ fdopen(int fd, const char *mode) if ((fp = __sfp()) == NULL) return (NULL); - if ((oflags & O_CLOEXEC) && _fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) { - fp->_flags = 0; - return (NULL); + if ((oflags & O_CLOEXEC) != 0) { + tmp = _fcntl(fd, F_GETFD, 0); + if (tmp == -1) { + fp->_flags = 0; + return (NULL); + } + if ((tmp & FD_CLOEXEC) == 0) { + rc = _fcntl(fd, F_SETFD, tmp | FD_CLOEXEC); + if (rc == -1) { + fp->_flags = 0; + return (NULL); + } + } } fp->_flags = flags; diff --git a/lib/libc/stdio/freopen.c b/lib/libc/stdio/freopen.c index f0732b6d6741..048fd30b3193 100644 --- a/lib/libc/stdio/freopen.c +++ b/lib/libc/stdio/freopen.c @@ -55,7 +55,7 @@ freopen(const char * __restrict file, const char * __restrict mode, FILE * __restrict fp) { int f; - int dflags, flags, isopen, oflags, sverrno, wantfd; + int dflags, fdflags, flags, isopen, oflags, sverrno, wantfd; if ((flags = __sflags(mode, &oflags)) == 0) { sverrno = errno; @@ -113,8 +113,12 @@ freopen(const char * __restrict file, const char * __restrict mode, (void) ftruncate(fp->_file, (off_t)0); if (!(oflags & O_APPEND)) (void) _sseek(fp, (fpos_t)0, SEEK_SET); - if (oflags & O_CLOEXEC) - (void) _fcntl(fp->_file, F_SETFD, FD_CLOEXEC); + if ((oflags & O_CLOEXEC) != 0) { + fdflags = _fcntl(fp->_file, F_GETFD, 0); + if (fdflags != -1 && (fdflags & FD_CLOEXEC) == 0) + (void) _fcntl(fp->_file, F_SETFD, + fdflags | FD_CLOEXEC); + } f = fp->_file; isopen = 0; wantfd = -1; diff --git a/lib/libc/stdtime/Makefile.inc b/lib/libc/stdtime/Makefile.inc index 5d0ce7765b63..647cbe6f40ba 100644 --- a/lib/libc/stdtime/Makefile.inc +++ b/lib/libc/stdtime/Makefile.inc @@ -18,6 +18,7 @@ CFLAGS.${src}+= -I${LIBC_SRCTOP}/stdtime CFLAGS.localtime.c+= -DALL_STATE -DTHREAD_SAFE .if ${MK_DETECT_TZ_CHANGES} != "no" CFLAGS.localtime.c+= -DDETECT_TZ_CHANGES +CFLAGS.Version.map+= -DDETECT_TZ_CHANGES .endif MAN+= ctime.3 strftime.3 strptime.3 time2posix.3 tzset.3 diff --git a/lib/libc/stdtime/Symbol.map b/lib/libc/stdtime/Symbol.map index 669d4d47907b..6a34cd3ea590 100644 --- a/lib/libc/stdtime/Symbol.map +++ b/lib/libc/stdtime/Symbol.map @@ -35,3 +35,9 @@ FBSD_1.8 { daylight; timezone; }; + +FBSDprivate_1.0 { +#ifdef DETECT_TZ_CHANGES + __tz_change_interval; +#endif +}; diff --git a/lib/libc/string/memchr.3 b/lib/libc/string/memchr.3 index f5d1fe5d5c7f..65617a117371 100644 --- a/lib/libc/string/memchr.3 +++ b/lib/libc/string/memchr.3 @@ -52,7 +52,10 @@ locates the first occurrence of (converted to an .Vt "unsigned char" ) in string -.Fa b . +.Fa b , +limited to at most +.Fa len +characters. .Pp The .Fn memrchr @@ -61,15 +64,18 @@ function behaves like except that it locates the last occurrence of .Fa c in string -.Fa b . +.Fa b , +limited to the first +.Fa len +characters. .Sh RETURN VALUES The .Fn memchr and .Fn memrchr -functions -return a pointer to the byte located, -or NULL if no such byte exists within +functions return a pointer to the byte located, or +.Dv NULL +if no such byte exists within .Fa len bytes. .Sh SEE ALSO diff --git a/lib/libc/tests/stdtime/Makefile b/lib/libc/tests/stdtime/Makefile index c7a7f5b9436f..adb883cc5b9a 100644 --- a/lib/libc/tests/stdtime/Makefile +++ b/lib/libc/tests/stdtime/Makefile @@ -1,6 +1,9 @@ -.include <bsd.own.mk> +.include <src.opts.mk> ATF_TESTS_C+= strptime_test +.if ${MK_DETECT_TZ_CHANGES} != "no" +ATF_TESTS_C+= detect_tz_changes_test +.endif TESTSDIR:= ${TESTSBASE}/${RELDIR:C/libc\/tests/libc/} diff --git a/lib/libc/tests/stdtime/detect_tz_changes_test.c b/lib/libc/tests/stdtime/detect_tz_changes_test.c new file mode 100644 index 000000000000..9722546747fd --- /dev/null +++ b/lib/libc/tests/stdtime/detect_tz_changes_test.c @@ -0,0 +1,281 @@ +/*- + * Copyright (c) 2025 Klara, Inc. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/stat.h> +#include <sys/wait.h> + +#include <dlfcn.h> +#include <fcntl.h> +#include <limits.h> +#include <poll.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +#include <atf-c.h> + +static const time_t then = 1751328000; /* 2025-07-01 00:00:00 UTC */ +static const char *tz_change_interval_sym = "__tz_change_interval"; +static int *tz_change_interval_p; +static const int tz_change_interval = 3; +static int tz_change_timeout = 90; + +static bool debugging; + +static void +debug(const char *fmt, ...) +{ + va_list ap; + + if (debugging) { + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fputc('\n', stderr); + } +} + +static void +change_tz(const char *tzn) +{ + static const char *zfn = "/usr/share/zoneinfo"; + static const char *tfn = "root/etc/.localtime"; + static const char *dfn = "root/etc/localtime"; + ssize_t clen; + int zfd, sfd, dfd; + + ATF_REQUIRE((zfd = open(zfn, O_DIRECTORY | O_SEARCH)) >= 0); + ATF_REQUIRE((sfd = openat(zfd, tzn, O_RDONLY)) >= 0); + ATF_REQUIRE((dfd = open(tfn, O_CREAT | O_TRUNC | O_WRONLY)) >= 0); + do { + clen = copy_file_range(sfd, NULL, dfd, NULL, SSIZE_MAX, 0); + ATF_REQUIRE_MSG(clen != -1, "failed to copy %s/%s: %m", + zfn, tzn); + } while (clen > 0); + ATF_CHECK_EQ(0, close(dfd)); + ATF_CHECK_EQ(0, close(sfd)); + ATF_CHECK_EQ(0, close(zfd)); + ATF_REQUIRE_EQ(0, rename(tfn, dfn)); + debug("time zone %s installed", tzn); +} + +/* + * Test time zone change detection. + * + * The parent creates a chroot containing only /etc/localtime, initially + * set to UTC. It then forks a child which enters the chroot, repeatedly + * checks the current time zone, and prints it to stdout if it changes + * (including once on startup). Meanwhile, the parent waits for output + * from the child. Every time it receives a line of text from the child, + * it checks that it is as expected, then changes /etc/localtime within + * the chroot to the next case in the list. Once it reaches the end of + * the list, it closes a pipe to notify the child, which terminates. + * + * Note that ATF and / or Kyua may have set the timezone before the test + * case starts (even unintentionally). Therefore, we start the test only + * after we've received and discarded the first report from the child, + * which should come almost immediately on startup. + */ +ATF_TC(detect_tz_changes); +ATF_TC_HEAD(detect_tz_changes, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test timezone change detection"); + atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "timeout", "600"); +} +ATF_TC_BODY(detect_tz_changes, tc) +{ + static const struct tzcase { + const char *tzfn; + const char *expect; + } tzcases[] = { + /* + * A handful of time zones and the expected result of + * strftime("%z (%Z)", tm) when that time zone is active + * and tm represents a date in the summer of 2025. + */ + { "America/Vancouver", "-0700 (PDT)" }, + { "America/New_York", "-0400 (EDT)" }, + { "Europe/London", "+0100 (BST)" }, + { "Europe/Paris", "+0200 (CEST)" }, + { "Asia/Kolkata", "+0530 (IST)" }, + { "Asia/Tokyo", "+0900 (JST)" }, + { "Australia/Canberra", "+1000 (AEST)" }, + { "UTC", "+0000 (UTC)" }, + { 0 }, + }; + char obuf[1024] = ""; + char ebuf[1024] = ""; + struct pollfd fds[3]; + int opd[2], epd[2], spd[2]; + time_t changed, now; + const struct tzcase *tzcase = NULL; + struct tm *tm; + size_t olen = 0, elen = 0; + ssize_t rlen; + long curoff = LONG_MIN; + pid_t pid; + int nfds, status; + + /* speed up the test if possible */ + tz_change_interval_p = dlsym(RTLD_SELF, tz_change_interval_sym); + if (tz_change_interval_p != NULL && + *tz_change_interval_p > tz_change_interval) { + debug("reducing detection interval from %d to %d", + *tz_change_interval_p, tz_change_interval); + *tz_change_interval_p = tz_change_interval; + tz_change_timeout = tz_change_interval * 3; + } + /* prepare chroot */ + ATF_REQUIRE_EQ(0, mkdir("root", 0755)); + ATF_REQUIRE_EQ(0, mkdir("root/etc", 0755)); + change_tz("UTC"); + time(&changed); + /* output, error, sync pipes */ + if (pipe(opd) != 0 || pipe(epd) != 0 || pipe(spd) != 0) + atf_tc_fail("failed to pipe"); + /* fork child */ + if ((pid = fork()) < 0) + atf_tc_fail("failed to fork"); + if (pid == 0) { + /* child */ + dup2(opd[1], STDOUT_FILENO); + close(opd[0]); + close(opd[1]); + dup2(epd[1], STDERR_FILENO); + close(epd[0]); + close(epd[1]); + close(spd[0]); + unsetenv("TZ"); + ATF_REQUIRE_EQ(0, chroot("root")); + ATF_REQUIRE_EQ(0, chdir("/")); + fds[0].fd = spd[1]; + fds[0].events = POLLIN; + for (;;) { + ATF_REQUIRE(poll(fds, 1, 100) >= 0); + if (fds[0].revents & POLLHUP) { + /* parent closed sync pipe */ + _exit(0); + } + ATF_REQUIRE((tm = localtime(&then)) != NULL); + if (tm->tm_gmtoff == curoff) + continue; + olen = strftime(obuf, sizeof(obuf), "%z (%Z)", tm); + ATF_REQUIRE(olen > 0); + fprintf(stdout, "%s\n", obuf); + fflush(stdout); + curoff = tm->tm_gmtoff; + } + _exit(2); + } + /* parent */ + close(opd[1]); + close(epd[1]); + close(spd[1]); + /* receive output until child terminates */ + fds[0].fd = opd[0]; + fds[0].events = POLLIN; + fds[1].fd = epd[0]; + fds[1].events = POLLIN; + fds[2].fd = spd[0]; + fds[2].events = POLLIN; + nfds = 3; + for (;;) { + ATF_REQUIRE(poll(fds, 3, 1000) >= 0); + time(&now); + if (fds[0].revents & POLLIN && olen < sizeof(obuf)) { + rlen = read(opd[0], obuf + olen, sizeof(obuf) - olen); + ATF_REQUIRE(rlen >= 0); + olen += rlen; + } + if (olen > 0) { + ATF_REQUIRE_EQ('\n', obuf[olen - 1]); + obuf[--olen] = '\0'; + /* tzcase will be NULL at first */ + if (tzcase != NULL) { + debug("%s", obuf); + ATF_REQUIRE_STREQ(tzcase->expect, obuf); + debug("change to %s detected after %d s", + tzcase->tzfn, (int)(now - changed)); + if (tz_change_interval_p != NULL) { + ATF_CHECK((int)(now - changed) >= + *tz_change_interval_p - 1); + ATF_CHECK((int)(now - changed) <= + *tz_change_interval_p + 1); + } + } + olen = 0; + /* first / next test case */ + if (tzcase == NULL) + tzcase = tzcases; + else + tzcase++; + if (tzcase->tzfn == NULL) { + /* test is over */ + break; + } + change_tz(tzcase->tzfn); + changed = now; + } + if (fds[1].revents & POLLIN && elen < sizeof(ebuf)) { + rlen = read(epd[0], ebuf + elen, sizeof(ebuf) - elen); + ATF_REQUIRE(rlen >= 0); + elen += rlen; + } + if (elen > 0) { + ATF_REQUIRE_EQ(elen, fwrite(ebuf, 1, elen, stderr)); + elen = 0; + } + if (nfds > 2 && fds[2].revents & POLLHUP) { + /* child closed sync pipe */ + break; + } + /* + * The timeout for this test case is set to 10 minutes, + * because it can take that long to run with the default + * 61-second interval. However, each individual tzcase + * entry should not take much longer than the detection + * interval to test, so we can detect a problem long + * before Kyua terminates us. + */ + if ((now - changed) > tz_change_timeout) { + close(spd[0]); + if (tz_change_interval_p == NULL && + tzcase == tzcases) { + /* + * The most likely explanation in this + * case is that libc was built without + * time zone change detection. + */ + atf_tc_skip("time zone change detection " + "does not appear to be enabled"); + } + atf_tc_fail("timed out waiting for change to %s " + "to be detected", tzcase->tzfn); + } + } + close(opd[0]); + close(epd[0]); + close(spd[0]); /* this will wake up and terminate the child */ + if (olen > 0) + ATF_REQUIRE_EQ(olen, fwrite(obuf, 1, olen, stdout)); + if (elen > 0) + ATF_REQUIRE_EQ(elen, fwrite(ebuf, 1, elen, stderr)); + ATF_REQUIRE_EQ(pid, waitpid(pid, &status, 0)); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE_EQ(0, WEXITSTATUS(status)); +} + +ATF_TP_ADD_TCS(tp) +{ + debugging = !getenv("__RUNNING_INSIDE_ATF_RUN") && + isatty(STDERR_FILENO); + ATF_TP_ADD_TC(tp, detect_tz_changes); + return (atf_no_error()); +} |