New option chdir (cd)
diff --git a/CHANGES b/CHANGES
index bb22299..21e9a39 100644
--- a/CHANGES
+++ b/CHANGES
@@ -143,6 +143,10 @@
Added option res-nsaddr that overrides /etc/resolv.conf nameserver
address based on an undocumented resolver feature.
+ New option chdir changes the working directory of the address to the
+ given path, only during the open stage.
+ Tests: CHDIR_ON_CREATE CHDIR_ON_SYSTEM
+
Option umask now applies only during opening of its very address, not
for the lifetime of the process; the original umask is restored
afterwards.
diff --git a/VERSION b/VERSION
index f09dcc3..ceaf471 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-"1.7.4.5+20230721"
+"1.7.4.5+"
diff --git a/doc/socat.yo b/doc/socat.yo
index ad5e1b8..572d4a1 100644
--- a/doc/socat.yo
+++ b/doc/socat.yo
@@ -1990,6 +1990,10 @@
properties that are restored after opening the address.
startdit()
+label(OPTION_CHDIR)dit(bf(tt(chdir=<filename>))) dit(bf(tt(cd=<filename>)))
+ Changes the working directory. After opening the address the master process
+ changes back to the original working directory. Sub processes inherit the
+ temporary setting.
label(OPTION_UMASK)dit(bf(tt(umask=<mode>)))
Sets the umask of the process to <mode> [link(mode_t)(TYPE_MODE_T)] before
opening the address. Useful when file system entries are created or a shell
diff --git a/test.sh b/test.sh
index b9ff555..6f79bb8 100755
--- a/test.sh
+++ b/test.sh
@@ -1283,7 +1283,7 @@
[ "$timeout" ] || timeout=5
while [ $timeout -gt 0 ]; do
case "$UNAME" in
- Linux) if false && [ "$SS" ]; then
+ Linux) if [ "$SS" ]; then
l=$($SS -4 -n 2>/dev/null |grep "^sctp.*LISTEN .*:$port\>")
else
l=$(netstat -n -a |grep '^sctp .*[0-9*]:'$port' .* LISTEN')
@@ -7851,7 +7851,8 @@
else
tf="$td/test$N.stout"
te="$td/test$N.stderr"
-CMD="$TRACE $SOCAT $opts -d -d /dev/null pty,end-close"
+# -t must be longer than 0.1 on OpenBSD
+CMD="$TRACE $SOCAT $opts -d -d -t 0.5 /dev/null pty,end-close"
printf "test $F_n $TEST... " $N
# AIX reports the pty writeable for select() only when its slave side has been
# opened, therefore we run this process in background and check its NOTICE
@@ -7860,7 +7861,7 @@
waitfile "${te}"
psleep 0.1
PTY=$(grep "N PTY is " $te |sed 's/.*N PTY is //')
-[ -e "$PTY" ] && cat $PTY >/dev/null
+[ -e "$PTY" ] && cat $PTY >/dev/null 2>/dev/null
rc=$(cat "$td/test$N.rc0")
if [ "$rc" = 0 ]; then
$PRINTF "$OK\n"
@@ -17355,6 +17356,171 @@
listFAIL="$listFAIL $N"
namesFAIL="$namesFAIL $NAME"
fi
+fi # NUMCOND
+ ;;
+esac
+N=$((N+1))
+
+
+# Some of the following tests need absolute path of Socat
+case "$SOCAT" in
+ /*) absSOCAT="$SOCAT" ;;
+ *) absSOCAT="$PWD/$SOCAT" ;;
+esac
+
+# Test the chdir option, in particular if chdir with the first address
+# (CREATE) does not affect pwd of second address, i.e. original pwd is
+# recovered
+NAME=CHDIR_ON_CREATE
+case "$TESTS" in
+*%$N%*|*%functions%*|*%creat%*|*%system%*|*%chdir%*|*%$NAME%*)
+TEST="$NAME: restore of pwd after CREAT with chdir option"
+# Run Socat with first address CREAT with modified chdir,
+# and second address SYSTEM (shell) with pwd command
+# Check if the file is created with modified pwd but shell has original pwd
+if ! eval $NUMCOND; then :;
+elif ! F=$(testfeats CREAT SYSTEM); then
+ $PRINTF "test $F_n $TEST... ${YELLOW}Feature $F not configured in $SOCAT${NORMAL}\n" $N
+ numCANT=$((numCANT+1))
+ listCANT="$listCANT $N"
+elif ! A=$(testaddrs - CREAT SYSTEM); then
+ $PRINTF "test $F_n $TEST... ${YELLOW}Address $A not available in $SOCAT${NORMAL}\n" $N
+ numCANT=$((numCANT+1))
+ listCANT="$listCANT $N"
+elif ! o=$(testoptions chdir) >/dev/null; then
+ $PRINTF "test $F_n $TEST... ${YELLOW}Option $o not available in $SOCAT${NORMAL}\n" $N
+ numCANT=$((numCANT+1))
+ listCANT="$listCANT $N"
+else
+ tf="$td/test$N.stdout"
+ te="$td/test$N.stderr"
+ tc="test$N.creat"
+ tdd="test$N.d"
+ tdiff="$td/test$N.diff"
+ tdebug="$td/test$N.debug"
+ opwd=$(pwd)
+ CMD0="$TRACE $absSOCAT $opts -U CREAT:$tc,chdir=$td SYSTEM:pwd"
+ printf "test $F_n $TEST... " $N
+ mkdir "$td/$tdd"
+ pushd "$td/$tdd" >/dev/null
+ $CMD0 >/dev/null 2>"${te}0"
+ rc0=$?
+ popd >/dev/null
+ tpwd=$(find $td -name $tc -print); tpwd=${tpwd%/*}
+ pwd2=$(cat $tpwd/$tc </dev/null)
+ echo "Original pwd: $opwd" >>$tdebug
+ echo "Temporary pwd: $tpwd" >>$tdebug
+ echo "Addr2 pwd: $pwd2" >>$tdebug
+ if [ "$rc0" -ne 0 ]; then
+ $PRINTF "$FAILED\n"
+ echo "$CMD0 &"
+ cat "${te}0" >&2
+ numFAIL=$((numFAIL+1))
+ listFAIL="$listFAIL $N"
+ namesFAIL="$namesFAIL $NAME"
+ elif [ "$tpwd" != "$td" ]; then
+ $PRINTF "$FAILED (chdir failed)\n"
+ echo "$CMD0 &"
+ cat "${te}0" >&2
+ numFAIL=$((numFAIL+1))
+ listFAIL="$listFAIL $N"
+ namesFAIL="$namesFAIL $NAME"
+ elif ! echo "$pwd2" |diff "$td/$tc" - >$tdiff; then
+ $PRINTF "$FAILED (bad pwd2)\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))
+
+# Test the chdir option, in particular if chdir with first address
+# (SHELL) does not affect pwd of second address, i.e. original pwd is
+# recovered
+NAME=CHDIR_ON_SHELL
+case "$TESTS" in
+*%$N%*|*%functions%*|*%shell%*|*%system%*|*%chdir%*|*%$NAME%*)
+TEST="$NAME: restore of pwd after SYSTEM with chdir option"
+# Run Socat with first address SYSTEM:"cat >file" with chdir,
+# and second address SYSTEM (shell) with pwd command.
+# Check if the file is created with modified pwd but shell has original pwd
+if ! eval $NUMCOND; then :;
+elif ! F=$(testfeats SHELL SYSTEM); then
+ $PRINTF "test $F_n $TEST... ${YELLOW}Feature $F not configured in $SOCAT${NORMAL}\n" $N
+ numCANT=$((numCANT+1))
+ listCANT="$listCANT $N"
+elif ! A=$(testaddrs SHELL SYSTEM); then
+ $PRINTF "test $F_n $TEST... ${YELLOW}Address $A not available in $SOCAT${NORMAL}\n" $N
+ numCANT=$((numCANT+1))
+ listCANT="$listCANT $N"
+elif ! o=$(testoptions chdir) >/dev/null; then
+ $PRINTF "test $F_n $TEST... ${YELLOW}Option $o not available in $SOCAT${NORMAL}\n" $N
+ numCANT=$((numCANT+1))
+ listCANT="$listCANT $N"
+else
+ tf="$td/test$N.stdout"
+ te="$td/test$N.stderr"
+ tc="test$N.creat"
+ tdd="test$N.d"
+ tdiff="$td/test$N.diff"
+ tdebug="$td/test$N.debug"
+ opwd=$(pwd)
+ CMD0="$TRACE $absSOCAT $opts -U SHELL:\"cat\ >$tc\",chdir=$td SYSTEM:pwd"
+ printf "test $F_n $TEST... " $N
+ mkdir "$td/$tdd"
+ pushd "$td/$tdd" >/dev/null
+ eval "$CMD0" >/dev/null 2>"${te}0"
+ rc0=$?
+ popd >/dev/null
+ tpwd=$(find $td -name $tc -print); tpwd=${tpwd%/*}
+ pwd2=$(cat $tpwd/$tc </dev/null)
+ echo "Original pwd: $opwd" >>$tdebug
+ echo "Temporary pwd: $tpwd" >>$tdebug
+ echo "Addr2 pwd: $pwd2" >>$tdebug
+ if [ "$rc0" -ne 0 ]; then
+ $PRINTF "$FAILED\n"
+ echo "$CMD0 &"
+ cat "${te}0" >&2
+ numFAIL=$((numFAIL+1))
+ listFAIL="$listFAIL $N"
+ namesFAIL="$namesFAIL $NAME"
+ elif [ "$tpwd" != "$td" ]; then
+ $PRINTF "$FAILED (chdir failed)\n"
+ echo "$CMD0 &"
+ cat "${te}0" >&2
+ numFAIL=$((numFAIL+1))
+ listFAIL="$listFAIL $N"
+ namesFAIL="$namesFAIL $NAME"
+ elif ! echo "$pwd2" |diff "$td/$tc" - >$tdiff; then
+ $PRINTF "$FAILED (bad pwd)\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))
# Test the modified umask option, in particular if umask with first address
@@ -17372,7 +17538,7 @@
$PRINTF "test $F_n $TEST... ${YELLOW}Feature $F not configured in $SOCAT${NORMAL}\n" $N
numCANT=$((numCANT+1))
listCANT="$listCANT $N"
-elif ! A=$(testaddrs - CREAT SYSTEM); then
+elif ! A=$(testaddrs CREAT SYSTEM); then
$PRINTF "test $F_n $TEST... ${YELLOW}Address $A not available in $SOCAT${NORMAL}\n" $N
numCANT=$((numCANT+1))
listCANT="$listCANT $N"
@@ -17441,7 +17607,7 @@
# recovered
NAME=UMASK_ON_SYSTEM
case "$TESTS" in
-*%$N%*|*%functions%*|*%shell%*|*%umask%*|*%socket%*|*%$NAME%*)
+*%$N%*|*%functions%*|*%shell%*|*%system%*|*%umask%*|*%socket%*|*%$NAME%*)
TEST="$NAME: test restore after SHELL with umask option"
# Run Socat with first address SHELL:"cat >file" with modified umask,
# and second address SYSTEM (shell) with umask command.
diff --git a/xio-namespaces.c b/xio-namespaces.c
index 1c2bc3d..b44ee43 100644
--- a/xio-namespaces.c
+++ b/xio-namespaces.c
@@ -44,29 +44,61 @@
return 0;
}
+int xio_apply_namespace(
+ struct opt *opts)
+{
+ int old_netfd;
+ char *netns_name;
+ char old_nspath[PATH_MAX];
+ int rc;
+
+ if (retropt_string(opts, OPT_SET_NETNS, &netns_name) < 0)
+ return 0;
+
+ /* Get path describing current namespace */
+ snprintf(old_nspath, sizeof(old_nspath)-1, "/proc/"F_pid"/ns/net",
+ Getpid());
+
+ /* Get a file descriptor to current ns for later reset */
+ old_netfd = Open(old_nspath, O_RDONLY|O_CLOEXEC, 000);
+ if (old_netfd < 0) {
+ Error2("open(%s, O_RDONLY|O_CLOEXEC): %s",
+ old_nspath, strerror(errno));
+ free(netns_name);
+ return -1;
+ }
+ if (old_netfd == 0) {
+ /* 0 means not netns option, oops */
+ Error1("%s(): INTERNAL", __func__);
+ free(netns_name);
+ Close(old_netfd);
+ return -1;
+ }
+ rc = xio_set_namespace("netns", netns_name);
+ free(netns_name);
+ if (rc < 0) {
+ Close(old_netfd);
+ return -1;
+ }
+
+ return old_netfd;
+}
+
/* Sets the given namespace to that of process 1, this is assumed to be the
systems default.
Returns 0 on success, or -1 on error. */
int xio_reset_namespace(
- const char *nstype)
+ int saved_netfd)
{
- char nspath[PATH_MAX];
- int nsfd;
int rc;
- snprintf(nspath, sizeof(nspath)-1, "/proc/1/ns/%s", nstype);
- Info("switching back to default namespace");
- nsfd = Open(nspath, O_RDONLY|O_CLOEXEC, 000);
- if (nsfd < 0) {
- Error2("open(%s, O_RDONLY|O_CLOEXEC): %s", nspath, strerror(errno));
- return -1;
- }
- rc = Setns(nsfd, CLONE_NEWNET);
+ rc = Setns(saved_netfd, CLONE_NEWNET);
if (rc < 0) {
- Error2("setns(%d, CLONE_NEWNET): %s", nsfd, strerror(errno));
- Close(nsfd);
+ Error2("xio_reset_namespace(%d): %s", saved_netfd, strerror(errno));
+ Close(saved_netfd);
+ return STAT_NORETRY;
}
- Close(nsfd);
+ Close(saved_netfd);
return 0;
}
diff --git a/xio-namespaces.h b/xio-namespaces.h
index 8aed47a..0b5d4bf 100644
--- a/xio-namespaces.h
+++ b/xio-namespaces.h
@@ -11,7 +11,8 @@
extern const struct optdesc opt_reset_netns;
extern int xio_set_namespace(const char *nstype, const char *nsname);
-extern int xio_reset_namespace(const char *nstype);
+extern int xio_apply_namespace(struct opt *opts);
+extern int xio_reset_namespace(int saved_netfd);
#endif /* WITH_NAMESPACES */
diff --git a/xiolayer.c b/xiolayer.c
index 6838e17..81c88e9 100644
--- a/xiolayer.c
+++ b/xiolayer.c
@@ -24,4 +24,39 @@
const struct optdesc opt_retry = { "retry", NULL, OPT_RETRY, GROUP_RETRY, PH_INIT, TYPE_UINT, OFUNC_EXT, XIO_OFFSETOF(retry), XIO_SIZEOF(retry) };
#endif
-const struct optdesc opt_umask = { "umask", NULL, OPT_UMASK, GROUP_ADDR, PH_INIT, TYPE_MODET, OFUNC_SPEC };
+const struct optdesc opt_chdir = { "chdir", "cd", OPT_CHDIR, GROUP_ADDR, PH_INIT, TYPE_FILENAME, OFUNC_SPEC };
+const struct optdesc opt_umask = { "umask", NULL, OPT_UMASK, GROUP_ADDR, PH_INIT, TYPE_MODET, OFUNC_SPEC };
+
+
+int xio_chdir(
+ struct opt* opts,
+ char **orig_dir)
+{
+ char *tmp_dir = NULL;
+
+ if (retropt_string(opts, OPT_CHDIR, &tmp_dir) < 0)
+ return 0;
+
+ if ((*orig_dir = Malloc(PATH_MAX)) == NULL) {
+ free(tmp_dir);
+ return -1;
+ }
+
+ if (getcwd(*orig_dir, PATH_MAX) == NULL) {
+ Error1("getcwd(<ptr>, PATH_MAX): %s", strerror(errno));
+ free(*orig_dir);
+ free(tmp_dir);
+ return -1;
+ }
+ *orig_dir = Realloc(*orig_dir, strlen(*orig_dir+1));
+
+ if (Chdir(tmp_dir) < 0) {
+ Error2("chdir(\"%s\"): %s", tmp_dir, strerror(errno));
+ free(*orig_dir);
+ free(tmp_dir);
+ return -1;
+ }
+
+ free(tmp_dir);
+ return 1;
+}
diff --git a/xiolayer.h b/xiolayer.h
index 22a52ad..a877e26 100644
--- a/xiolayer.h
+++ b/xiolayer.h
@@ -15,7 +15,9 @@
extern const struct optdesc opt_forever;
extern const struct optdesc opt_intervall;
extern const struct optdesc opt_retry;
+extern const struct optdesc opt_chdir;
extern const struct optdesc opt_umask;
-extern const struct optdesc opt_un_umask;
+
+extern int xio_chdir(struct opt* opts, char **orig_dir);
#endif /* !defined(__xiolayer_h_included) */
diff --git a/xioopen.c b/xioopen.c
index 0477824..3ffa375 100644
--- a/xioopen.c
+++ b/xioopen.c
@@ -622,6 +622,7 @@
const struct addrdesc *addrdesc;
const char *modetext[4] = { "none", "read-only", "write-only", "read-write" } ;
/* Values to be saved until xioopen() is finished */
+ char *orig_dir = NULL;
bool have_umask = false;
mode_t orig_umask, tmp_umask;
int result;
@@ -631,42 +632,10 @@
struct __res_state save_res;
#endif /* WITH_RESOLVE && HAVE_RESOLV_H */
#if WITH_NAMESPACES
- char *temp_netns;
int save_netfd = -1;
#endif
- int rc;
-
- /* Apply "temporary" process properties, save value for later restore */
-
- if (applyopts_single(sfd, sfd->opts, PH_OFFSET) < 0)
- return -1;
-
-#if WITH_NAMESPACES
- if (retropt_string(sfd->opts, OPT_SET_NETNS, &temp_netns) >= 0) {
- char nspath[PATH_MAX];
-
- snprintf(nspath, sizeof(nspath)-1, "/proc/"F_pid"/ns/net",
- Getpid());
- save_netfd = Open(nspath, O_RDONLY|O_CLOEXEC, 000);
- if (save_netfd < 0) {
- Error2("open(%s, O_RDONLY|O_CLOEXEC): %s", nspath, strerror(errno));
- return -1;
- }
-
- rc = xio_set_namespace("netns", temp_netns);
- free(temp_netns);
- if (rc < 0)
- return -1;
- }
-#endif /* WITH_NAMESPACES */
-
-#if WITH_RESOLVE && HAVE_RESOLV_H
- if ((do_res = xio_res_init(sfd, &save_res)) < 0)
- return STAT_NORETRY;
-#endif /* WITH_RESOLVE && HAVE_RESOLV_H */
addrdesc = xfd->stream.addr;
- /* Check if address supports required data directions */
if (((xioflags+1)&XIO_ACCMODE) & ~(addrdesc->directions)) {
Warn2("address is opened in %s mode but only supports %s", modetext[(xioflags+1)&XIO_ACCMODE], modetext[addrdesc->directions]);
}
@@ -682,12 +651,31 @@
xfd->stream.flags &= (~XIO_ACCMODE);
xfd->stream.flags |= (xioflags & XIO_ACCMODE);
+ /* Apply "temporary" process properties, save value for later restore */
+
+ if (applyopts_single(sfd, sfd->opts, PH_OFFSET) < 0)
+ return -1;
+
+#if WITH_NAMESPACES
+ if ((save_netfd = xio_apply_namespace(sfd->opts)) < 0)
+ return -1;
+#endif /* WITH_NAMESPACES */
+
+#if HAVE_RESOLV_H
+ if ((do_res = xio_res_init(sfd, &save_res)) < 0)
+ return STAT_NORETRY;
+#endif /* HAVE_RESOLV_H */
+
+ if (xio_chdir(sfd->opts, &orig_dir) < 0)
+ return STAT_NORETRY;
+
if (retropt_mode(xfd->stream.opts, OPT_UMASK, &tmp_umask) >= 0) {
Info1("changing umask to 0%3o", tmp_umask);
orig_umask = Umask(tmp_umask);
have_umask = true;
}
+ /* Call the specific xioopen function */
result = (*addrdesc->func)(xfd->stream.argc, xfd->stream.argv,
xfd->stream.opts, xioflags, xfd,
addrdesc);
@@ -698,19 +686,23 @@
Umask(orig_umask);
}
+ if (orig_dir != NULL) {
+ if (Chdir(orig_dir) < 0) {
+ Error2("chdir(\"%s\"): %s", orig_dir, strerror(errno));
+ free(orig_dir);
+ return STAT_NORETRY;
+ }
+ free(orig_dir);
+ }
+
#if WITH_RESOLVE && HAVE_RESOLV_H
if (do_res)
xio_res_restore(&save_res);
#endif /* WITH_RESOLVE && HAVE_RESOLV_H */
#if WITH_NAMESPACES
- if (save_netfd >= 0) {
- rc = Setns(save_netfd, CLONE_NEWNET);
- if (rc < 0) {
- Error2("setns(%d, CLONE_NEWNET): %s", save_netfd, strerror(errno));
- Close(save_netfd);
- return STAT_NORETRY;
- }
+ if (save_netfd > 0) {
+ xio_reset_namespace(save_netfd);
}
#endif /* WITH_NAMESPACES */
diff --git a/xioopts.c b/xioopts.c
index b326211..466fa92 100644
--- a/xioopts.c
+++ b/xioopts.c
@@ -314,9 +314,11 @@
IF_ANY ("bytes", &opt_readbytes)
IF_OPENSSL("cafile", &opt_openssl_cafile)
IF_OPENSSL("capath", &opt_openssl_capath)
+ IF_ANY ("cd", &opt_chdir)
IF_OPENSSL("cert", &opt_openssl_certificate)
IF_OPENSSL("certificate", &opt_openssl_certificate)
IF_TERMIOS("cfmakeraw", &opt_termios_cfmakeraw)
+ IF_ANY ("chdir", &opt_chdir)
#if WITH_LISTEN
IF_ANY ("children-shutup", &opt_children_shutup)
#endif
diff --git a/xioopts.h b/xioopts.h
index 00153ea..1140f82 100644
--- a/xioopts.h
+++ b/xioopts.h
@@ -263,6 +263,7 @@
# endif
OPT_BSDLY, /* termios.c_oflag */
#endif
+ OPT_CHDIR, /* change working directory */
OPT_CHILDREN_SHUTUP,
OPT_CHROOT, /* chroot() past file system access */
OPT_CHROOT_EARLY, /* chroot() before file system access */