On Sat, 09 Jul 2022 12:53:17 +0100, Stuart Henderson wrote:
> I'm trying to teach ftp(1) to do something like gui web browsers do
> and reduce the HTTP/HTTPS connection timeout from the default (75 seconds)
> if there are multiple addresses behind a hostname.
>
> There's an existing connection timeout mechanism that I thought it
> might make sense to reuse...
>
> -w seconds
> For URL format connections to HTTP/HTTPS servers, abort a slow
> connection after seconds.
Here's a diff to implement timed_connect(). It replaces the alarm
used for slow connections with non-blocking connect(2) and ppoll(2).
I've also replaced the other connect(2) + connect_wait() calls with
timed_connect() so the -w option now works for more that just http.
- todd
Index: usr.bin/ftp/extern.h
===================================================================
RCS file: /cvs/src/usr.bin/ftp/extern.h,v
retrieving revision 1.52
diff -u -p -u -r1.52 extern.h
--- usr.bin/ftp/extern.h 2 Feb 2021 12:58:42 -0000 1.52
+++ usr.bin/ftp/extern.h 13 Sep 2022 16:43:30 -0000
@@ -62,6 +62,7 @@
*/
#include <sys/types.h>
+#include <sys/socket.h>
void abort_remote(FILE *);
void abortpt(int);
@@ -75,7 +76,6 @@ void cmdabort(int);
void cmdscanner(int);
int command(const char *, ...);
int confirm(const char *, const char *);
-int connect_wait(int);
FILE *dataconn(const char *);
int foregroundproc(void);
int fileindir(const char *, const char *);
@@ -109,6 +109,7 @@ void sethash(int, char **);
void setpeer(int, char **);
void setttywidth(int);
char *slurpstring(void);
+int timed_connect(int s, const struct sockaddr *, socklen_t, int);
__dead void usage(void);
Index: usr.bin/ftp/fetch.c
===================================================================
RCS file: /cvs/src/usr.bin/ftp/fetch.c,v
retrieving revision 1.209
diff -u -p -u -r1.209 fetch.c
--- usr.bin/ftp/fetch.c 8 Sep 2022 11:12:44 -0000 1.209
+++ usr.bin/ftp/fetch.c 13 Sep 2022 16:40:00 -0000
@@ -173,14 +173,6 @@ url_encode(const char *path)
return (epath);
}
-/* ARGSUSED */
-static void
-tooslow(int signo)
-{
- dprintf(STDERR_FILENO, "%s: connect taking too long\n", __progname);
- _exit(2);
-}
-
/*
* Copy a local file (used by the OpenBSD installer).
* Returns -1 on failure, 0 on success
@@ -604,14 +596,8 @@ noslash:
}
#endif /* !SMALL */
- if (connect_timeout) {
- (void)signal(SIGALRM, tooslow);
- alarmtimer(connect_timeout);
- }
-
- for (error = connect(fd, res->ai_addr, res->ai_addrlen);
- error != 0 && errno == EINTR; error = connect_wait(fd))
- continue;
+ error = timed_connect(fd, res->ai_addr, res->ai_addrlen,
+ connect_timeout);
if (error != 0) {
save_errno = errno;
close(fd);
@@ -700,11 +686,6 @@ noslash:
}
#endif
- if (connect_timeout) {
- signal(SIGALRM, SIG_DFL);
- alarmtimer(0);
- }
-
/*
* Construct and send the request. Proxy requests don't want leading /.
*/
@@ -1242,7 +1223,6 @@ aborthttp(int signo)
{
const char errmsg[] = "\nfetch aborted.\n";
- alarmtimer(0);
write(fileno(ttyout), errmsg, sizeof(errmsg) - 1);
longjmp(httpabort, 1);
}
Index: usr.bin/ftp/ftp.1
===================================================================
RCS file: /cvs/src/usr.bin/ftp/ftp.1,v
retrieving revision 1.123
diff -u -p -u -r1.123 ftp.1
--- usr.bin/ftp/ftp.1 27 Mar 2022 20:09:12 -0000 1.123
+++ usr.bin/ftp/ftp.1 13 Sep 2022 16:49:38 -0000
@@ -320,9 +320,9 @@ Forces
to show all responses from the remote server, as well
as report on data transfer statistics.
.It Fl w Ar seconds
-For URL format connections to HTTP/HTTPS servers, abort a
-slow connection after
-.Ar seconds .
+Wait for
+.Ar seconds
+for the remote server to connect before giving up.
.El
.Pp
The host with which
Index: usr.bin/ftp/ftp.c
===================================================================
RCS file: /cvs/src/usr.bin/ftp/ftp.c,v
retrieving revision 1.107
diff -u -p -u -r1.107 ftp.c
--- usr.bin/ftp/ftp.c 18 Nov 2019 04:37:35 -0000 1.107
+++ usr.bin/ftp/ftp.c 13 Sep 2022 16:43:03 -0000
@@ -212,9 +212,8 @@ hookup(char *host, char *port)
}
}
#endif /* !SMALL */
- for (error = connect(s, res->ai_addr, res->ai_addrlen);
- error != 0 && errno == EINTR; error = connect_wait(s))
- continue;
+ error = timed_connect(s, res->ai_addr, res->ai_addrlen,
+ connect_timeout);
if (error != 0) {
/* this "if" clause is to prevent print warning twice */
if (verbose && res->ai_next) {
@@ -1509,10 +1508,8 @@ reinit:
} else
goto bad;
- for (error = connect(data, &data_addr.sa, data_addr.sa.sa_len);
- error != 0 && errno == EINTR;
- error = connect_wait(data))
- continue;
+ error = timed_connect(data, &data_addr.sa, data_addr.sa.sa_len,
+ connect_timeout);
if (error != 0) {
if (activefallback) {
(void)close(data);
Index: usr.bin/ftp/util.c
===================================================================
RCS file: /cvs/src/usr.bin/ftp/util.c,v
retrieving revision 1.95
diff -u -p -u -r1.95 util.c
--- usr.bin/ftp/util.c 2 Feb 2021 12:58:42 -0000 1.95
+++ usr.bin/ftp/util.c 13 Sep 2022 16:46:26 -0000
@@ -1077,6 +1077,84 @@ controlediting(void)
#endif /* !SMALL */
/*
+ * connect(2) with an optional timeout if secs > 0.
+ */
+int
+timed_connect(int s, const struct sockaddr *name, socklen_t namelen, int secs)
+{
+ struct timespec now, target, timebuf, *timeout = NULL;
+ int flags, nready, optval, ret = -1;
+ socklen_t optlen;
+ struct pollfd pfd;
+
+ if (secs > 0) {
+ timebuf.tv_sec = secs;
+ timebuf.tv_nsec = 0;
+ timeout = &timebuf;
+ clock_gettime(CLOCK_MONOTONIC, &target);
+ timespecadd(&target, timeout, &target);
+ }
+
+ flags = fcntl(s, F_GETFL, 0);
+ if (flags == -1) {
+ warn("fcntl(F_GETFL)");
+ return -1;
+ }
+ if (!(flags & O_NONBLOCK)) {
+ if (fcntl(s, F_SETFL, flags | O_NONBLOCK) == -1) {
+ warn("fcntl(F_SETFL)");
+ return -1;
+ }
+ }
+
+ ret = connect(s, name, namelen);
+ if (ret == 0 || errno != EINPROGRESS)
+ goto done;
+
+ for (;;) {
+ pfd.fd = s;
+ pfd.events = POLLOUT;
+ nready = ppoll(&pfd, 1, timeout, NULL);
+ switch (nready) {
+ case -1:
+ if (errno != EINTR && errno != EAGAIN) {
+ warn("ppoll");
+ goto done;
+ }
+ if (timeout == NULL)
+ continue;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ timespecsub(&now, &target, timeout);
+ if (timeout->tv_sec >= 0)
+ continue;
+ /* FALLTHROUGH */
+ case 0:
+ errno = ETIMEDOUT;
+ goto done;
+ default:
+ optlen = sizeof(optval);
+ ret = getsockopt(s, SOL_SOCKET, SO_ERROR, &optval,
+ &optlen);
+ if (ret == 0 && optval != 0) {
+ ret = -1;
+ errno = optval;
+ }
+ goto done;
+ }
+ }
+
+done:
+ if (!(flags & O_NONBLOCK)) {
+ if (fcntl(s, F_SETFL, flags) == -1) {
+ warn("fcntl(F_SETFL)");
+ ret = -1;
+ }
+ }
+
+ return ret;
+}
+
+/*
* Wait for an asynchronous connect(2) attempt to finish.
*/
int