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 */