From 27877ea777bf996ca0fb1438b81d65f437fb58d4 Mon Sep 17 00:00:00 2001 From: Gerhard Rieger Date: Sat, 11 Nov 2023 17:45:02 +0100 Subject: [PATCH] Procan: Try to identify controlling terminal --- CHANGES | 4 ++ VERSION | 2 +- config.h.in | 2 + configure.ac | 11 ++++ hostan.c | 8 ++- procan.c | 183 ++++++++++++++++++++++++++++++++++++++++++--------- test.sh | 140 ++++++++++++++++++++++++++++++++++++++- 7 files changed, 314 insertions(+), 36 deletions(-) diff --git a/CHANGES b/CHANGES index 8308220..4aa4980 100644 --- a/CHANGES +++ b/CHANGES @@ -193,6 +193,10 @@ Features: value of SO_PROTOCOL/SO_PROTOTYPE and some other defines, definitions of many C types, and the actual umask. + Procan tries to find the name of the controlling terminal, on Linux it + reads info from /proc/self/stat and searches for a device with matching + major and minor numbers. + Corrections: When a sub process (EXEC, SYSTEM) terminated with exit code other than 0, its last sent data might have been lost depending on timing of read/ diff --git a/VERSION b/VERSION index 77adf6d..ceaf471 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -"1.7.4.5+setpipe" +"1.7.4.5+" diff --git a/config.h.in b/config.h.in index 0b4842c..bf1a72d 100644 --- a/config.h.in +++ b/config.h.in @@ -684,6 +684,8 @@ #undef HAVE_PROC_DIR_FD #undef HAVE_PROC_DIR_PATH +#undef HAVE_DIRENT_D_TYPE + #undef HAVE_RES_RETRANS #undef HAVE_RES_RETRY #undef HAVE_RES_NSADDR_LIST diff --git a/configure.ac b/configure.ac index 33d1543..1107e5d 100644 --- a/configure.ac +++ b/configure.ac @@ -2191,6 +2191,17 @@ else AC_MSG_RESULT(no) fi +# On Solaris family there is not dirent.d_type +AC_MSG_CHECKING(for d_type in struct dirent) +AC_CACHE_VAL(sc_cv_dirent_d_type, +[AC_TRY_COMPILE([#include ],[struct dirent d; d.d_type], +[sc_cv_dirent_d_type=yes], +[sc_cv_dirent_d_type=no])]) +if test $sc_cv_dirent_d_type = yes; then + AC_DEFINE(HAVE_DIRENT_D_TYPE) +fi +AC_MSG_RESULT($sc_cv_dirent_d_type) + # Some OSes have undocumented _res.retrans, _res.retry components AC_MSG_CHECKING(for _res.retrans) AC_TRY_COMPILE([#include ], diff --git a/hostan.c b/hostan.c index c40b4e9..80ea569 100644 --- a/hostan.c +++ b/hostan.c @@ -169,6 +169,7 @@ int hostan(FILE *outfile) { fprintf(outfile, "typedef unsigned long long off_t; /* sizeof(off_t) = %u */\n", (unsigned int)sizeof(off_t)); #endif +#if HAVE_TYPE_OFF64 && defined(HAVE_BASIC_OFF64_T) && HAVE_BASIC_OFF64_T # if HAVE_BASIC_OFF64_T==1 fprintf(outfile, "typedef short off64_t; /* sizeof(off64_t) = %u */\n", (unsigned int)sizeof(off64_t)); #elif HAVE_BASIC_OFF64_T==2 @@ -186,6 +187,7 @@ int hostan(FILE *outfile) { #elif HAVE_BASIC_OFF64_T==8 fprintf(outfile, "typedef unsigned long long off64_t; /* sizeof(off64_t) = %u */\n", (unsigned int)sizeof(off64_t)); #endif +#endif /* defined(HAVE_BASIC_OFF64_T) && HAVE_BASIC_OFF64_T */ # if HAVE_BASIC_DEV_T==1 fprintf(outfile, "typedef short dev_t; /* sizeof(dev_t) = %u */\n", (unsigned int)sizeof(dev_t)); @@ -270,7 +272,7 @@ static int iffan(FILE *outfile) { int i; if ((s = Socket(PF_INET, SOCK_DGRAM, IPPROTO_IP)) < 0) { - Error1("socket(PF_INET, SOCK_DGRAM, IPPROTO_IP): %s", strerror(errno)); + Warn1("socket(PF_INET, SOCK_DGRAM, IPPROTO_IP): %s", strerror(errno)); return -1; } @@ -280,7 +282,7 @@ static int iffan(FILE *outfile) { ic.ifc_len = sizeof(buff); ic.ifc_ifcu.ifcu_buf = (caddr_t)buff; if (Ioctl(s, SIOCGIFCONF, &ic) < 0) { - Error3("ioctl(%d, SIOCGIFCONF, %p): %s", s, &ic, strerror(errno)); + Warn3("ioctl(%d, SIOCGIFCONF, %p): %s", s, &ic, strerror(errno)); return -1; } @@ -293,7 +295,7 @@ static int iffan(FILE *outfile) { #if 0 || defined(SIOCGIFINDEX) /* not NetBSD, OpenBSD */ strcpy(ifr.ifr_name, ifp->ifr_name); if (Ioctl(s, SIOCGIFINDEX, &ifr) < 0) { - Error3("ioctl(%d, SIOCGIFINDEX, {\"%s\"}): %s", + Warn3("ioctl(%d, SIOCGIFINDEX, {\"%s\"}): %s", s, ifr.ifr_name, strerror(errno)); return 1; } diff --git a/procan.c b/procan.c index 1ad8a68..22dd5e4 100644 --- a/procan.c +++ b/procan.c @@ -16,38 +16,168 @@ #include "sched.h" #include "filan.h" -#include +#include /* RLIMIT_CPU ... */ +#include /* opendir() readdir() closedir() */ #include "procan.h" +/* Search dir recursively for matching device file. + Returns 0 on success; + returns -1 when it failed to find the device file. */ +int find_devpath( + char *dirname, + unsigned int major, + unsigned int minor, + char *devname) +{ + DIR *dirp; + struct dirent *dirent; + char devpath[PATH_MAX]; + int rc; + + /* Pass 1: search dir flatly for this device entry */ + dirp = opendir(dirname); + if (dirp == NULL) { + Warn2("failed to open dir \"%s\": %s", dirname, strerror(errno)); + return -1; + } + while ((errno = 0) || (dirent = readdir(dirp))) { + struct stat statbuf; + +#if HAVE_DIRENT_D_TYPE + if (dirent->d_type != DT_CHR && dirent->d_type != DT_UNKNOWN) + continue; +#endif + snprintf(devpath, PATH_MAX, "%s/%s", dirname, dirent->d_name); + if (Stat(devpath, &statbuf) < 0) { + Warn2("failed to stat entry \"%s\": %s", devpath, strerror(errno)); + continue; + } + if ((statbuf.st_mode & S_IFMT) != S_IFCHR) + continue; + if ((statbuf.st_rdev >> 8) == major && + (statbuf.st_rdev & 0xff) == minor) { + strcpy(devname, devpath); + return 0; + } + continue; + } + closedir(dirp); + if (errno != 0) { + Warn2("failed to read dir \"%s\": %s", dirname, strerror(errno)); + snprintf(devname, PATH_MAX, "device %u, %u", major, minor); + } + + /* Pass 2: search sub dirs */ + dirp = opendir(dirname); + if (dirp == NULL) { + Warn2("failed to open dir \"%s\": %s", dirname, strerror(errno)); + return -1; + } + while ((errno = 0) || (dirent = readdir(dirp))) { + char dirpath[PATH_MAX]; +#if HAVE_DIRENT_D_TYPE + if (dirent->d_type != DT_DIR) + continue; +#else /* Solaris */ + { + struct stat statbuf; + if (Stat(dirent->d_name, &statbuf) < 0) + continue; + if ((statbuf.st_mode & S_IFMT) != S_IFDIR) + continue; + } +#endif + if (!strcmp(dirent->d_name, ".") || !strcmp(dirent->d_name, "..")) + continue; + snprintf(dirpath, PATH_MAX, "%s/%s", dirname, dirent->d_name); + rc = find_devpath(dirpath, major, minor, devname); + if (rc == 0) { + return 0; + } + } + closedir(dirp); + if (dirent == NULL) { + return 1; + } + return 0; +} + + /* Tries to determine the name of the controlling terminal. + Returns 0 on success, the name in cttyname; + returns 1 when only the device numbers are in cttyname; + returns -1 when it failed to determine ctty. */ +static int controlling_term( + FILE *outfile) +{ + char cttypath[PATH_MAX+1]; + int rc; + + { /* On Linux this just gives "/dev/tty" */ + char s[L_ctermid+1]; + fprintf(outfile, "controlling terminal by ctermid(): \"%s\"\n", ctermid(s)); + } + + { /* Check if there is a controlling terminal */ + int fd; + + if ((fd = Open("/dev/tty", O_NOCTTY, 0)) >= 0) + /* On Linux this just gives "/dev/tty" */ + fprintf(outfile, "controlling terminal by /dev/tty, ttyname(): \"%s\"\n", Ttyname(fd)); + else + fprintf(outfile, "controlling terminal by /dev/tty, ttyname(): (none)\n"); + } + +#if HAVE_PROC_DIR + do { /* Linux: derive ctty from info in /proc */ + const char procpath[] = "/proc/self/stat"; + FILE *procstat; + unsigned int dev; + int n = 0; + unsigned int maj, min; + + /* Linux: get device ids from /proc */ + if ((procstat = fopen(procpath, "r")) == NULL) { + Warn1("failed to open \"%s\" for process info", procpath); + rc = -1; + break; + } + n = fscanf(procstat, "%*s %*s %*s %*s %*s %*s %u", &dev); + if (n != 1) { + Warn1("failed to read ctty info from \"%s\"", procpath); + rc = -1; + break; + } + maj = (dev>>8)&0xff; + min = ((dev>>12)&0xfff00)|(dev&0xff); + rc = find_devpath("/dev" /* _PATH_DEV has trailing "/" */, maj, min, cttypath); + if (rc < 0) { + snprintf(cttypath, PATH_MAX, "device %u, %u", maj, min); + rc = 1; + break; + } + rc = 0; + } while (false); +#else /* !HAVE_PROC_DIR */ + rc = -1; +#endif /* !HAVE_PROC_DIR */ + if (rc >= 0) + fprintf(outfile, "controlling terminal by /proc//: \"%s\"\n", cttypath); + else + fprintf(outfile, "controlling terminal by /proc//: (none)\n"); + + return 0; +} + + int procan(FILE *outfile) { /*filan(0, outfile);*/ - /* controlling terminal */ fprintf(outfile, "process id = "F_pid"\n", Getpid()); fprintf(outfile, "process parent id = "F_pid"\n", Getppid()); - { - int fd; - if ((fd = Open("/dev/tty", O_NOCTTY, 0)) < 0) { - fprintf(outfile, "controlling terminal: -\n"); - } else { -#if 1 - fprintf(outfile, "controlling terminal: \"%s\"\n", Ttyname(fd)); -#else - char procpath[PATH_MAX], devpath[PATH_MAX+1]; - int devlen; - sprintf(procpath, "/proc/"F_pid"/fd/%d", Getpid(), 0 /*! fd*/); - if ((devlen = Readlink(procpath, devpath, sizeof(devpath))) < 0) { - ; - } else { - devpath[devlen] = '\0'; - fprintf(outfile, "controlling terminal: \"%s\"\n", devpath); - } -#endif - } - } + controlling_term(outfile); fprintf(outfile, "process group id = "F_pid"\n", Getpgrp()); #if HAVE_GETSID fprintf(outfile, "process session id = "F_pid"\n", Getsid(0)); @@ -55,15 +185,6 @@ int procan(FILE *outfile) { fprintf(outfile, "process group id if fg process / stdin = "F_pid"\n", Tcgetpgrp(0)); fprintf(outfile, "process group id if fg process / stdout = "F_pid"\n", Tcgetpgrp(1)); fprintf(outfile, "process group id if fg process / stderr = "F_pid"\n", Tcgetpgrp(2)); - { - int fd; - if ((fd = Open("/dev/tty", O_RDWR, 0600)) >= 0) { - fprintf(outfile, "process has a controlling terminal\n"); - Close(fd); - } else { - fprintf(outfile, "process does not have a controlling terminal\n"); - } - } /* process owner, groups */ fprintf(outfile, "user id = "F_uid"\n", Getuid()); diff --git a/test.sh b/test.sh index f4a7f90..c9bc3aa 100755 --- a/test.sh +++ b/test.sh @@ -1148,6 +1148,90 @@ waitip4proto () { return 1 } + +# Perform a couple of checks to make sure the test has a chance of a useful +# result: +# platform is supported, features compiled in, addresses and options +# available; needs root; is allowed to access the internet +checkconds() { + local unames="$(echo "$1")" # must be one of... exa: "Linux,FreeBSD" + local root="$2" # "root" or "" + local progs="$(echo "$3" |tr 'A-Z,' 'a-z ')" # exa: "nslookup" + local feats="$(echo "$4" |tr 'a-z,' 'A-Z ')" # list of req.features (socat -V) + local addrs="$(echo "$5" |tr 'a-z,' 'A-Z ')" # list of req.addresses (socat -h) + local opts="$(echo "$6" |tr 'A-Z,' 'a-z ')" # list of req.options (socat -hhh) + local runs="$(echo "$7" |tr , ' ')" # list of req.protocols, exa: "sctp6" + local inet="$8" # when "internet": needs allowance + local i + + if [ "$unames" ]; then + local uname="$(echo $UNAME |tr 'A-Z' 'a-z')" + for i in $unames; do + if [ "$uname" = "$(echo "$i" |tr 'A-Z,' 'a-z ')" ]; then + # good, mark as passed + i= + break; + fi + done + [ "$i" ] && { echo "Only on (one of) $unames"; return -1; } + fi + + if [ "$root" = "root" ]; then + if [ $(id -u) -ne 0 -a "$withroot" -eq 0 ]; then + echo "Must be root" + return -1; + fi + fi + + if [ "$progs" ]; then + for i in $progs; do + if ! type >/dev/null 2>&1; then + echo "Program $i not available" + return -1 + fi + done + fi + + if [ "$feats" ]; then + if ! F=$(testfeats $feats); then + echo "Feature $F not configured in $SOCAT" + return -1 + fi + fi + + if [ "$addrs" ]; then + if ! A=$(testaddrs - $addrs); then + echo "Address $A not available in $SOCAT" + return -1 + fi + fi + + if [ "$opts" ]; then + if ! o=$(testoptions $opts); then + echo "Option $o not available in $SOCAT" + return -1 + fi + fi + + if [ "$runs" ]; then + for i in $runs; do + if ! runs$i >/dev/null; then + echo "$i not available on host" + return -1; + fi + done + fi + + if [ "$inet" ]; then + if [ -z "$FOREIGN" ]; then + echo "Use test.sh option -internet" + return -1 + fi + fi + return 0 +} + + # we need this misleading function name for canonical reasons waitip4port () { waitip4proto "$1" "$2" "$3" @@ -17567,7 +17651,7 @@ tdiff="$td/test$N.diff" da="$(echo test$N $(date) $RANDOM |tr ' :' '-')" echo "$da" >"$td/test$N.da" newport udp4 # or whatever proto, or drop this line -CMD0="$TRACE $SOCAT $opts -u UDP-RECVFROM:$PORT -" +CMD0="$TRACE $SOCAT $opts -u UDP4-RECVFROM:$PORT -" CMD1="$TRACE $SOCAT $opts - TCP4:$da:0,res-nsaddr=$LOCALHOST:$PORT" printf "test $F_n $TEST... " $N $CMD0 >/dev/null 2>"${te}0" >"${tf}0" & @@ -18700,6 +18784,60 @@ UDPLITE6 UDPLITE udplite [::1] PORT shut-null " +# Test the procan controlling terminal output +NAME=PROCAN_CTTY +case "$TESTS" in +*%$N%*|*%functions%*|*%procan%*|*%$NAME%*) +TEST="$NAME: test procan controlling terminal output" +# Run procan and compare its controlling terminal output with tty (oops)" +if ! eval $NUMCOND; then : +elif ! cond=$(checkconds \ + "" \ + "" \ + "tty" \ + "" \ + "" \ + "" \ + "" ); then + $PRINTF "test $F_n $TEST... ${YELLOW}$cond${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +else + tf="$td/test$N.stdout" + te="$td/test$N.stderr" + tdiff="$td/test$N.diff" + CMD0="$TRACE $PROCAN" + printf "test $F_n $TEST... " $N + $CMD0 >"${tf}0" 2>"${te}0" + rc0=$? + if [ "$rc0" -ne 0 ]; then + $PRINTF "$FAILED\n" + echo "$CMD0" + cat "${te}0" >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" + namesFAIL="$namesFAIL $NAME" + elif ! tty |diff - <(cat ${tf}0 |grep "controlling terminal" |grep -v -e '"/dev/tty"' -e none |head -n 1 |sed -e 's/controlling terminal by .*:[[:space:]]*//' -e 's/"//g') >$tdiff; then + $PRINTF "$FAILED\n" + echo "$CMD0" + cat "${te}0" >&2 + echo "// diff:" >&2 + cat "$tdiff" >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" + namesFAIL="$namesFAIL $NAME" + else + $PRINTF "$OK\n" + if [ "$VERBOSE" ]; then echo "$CMD0"; fi + if [ "$DEBUG" ]; then cat "${te}0" >&2; fi + numOK=$((numOK+1)) + fi +fi # NUMCOND + ;; +esac +N=$((N+1)) + + # end of common tests ##################################################################################