From: Benjamin Lee McQueen Subject: ftp(1): parse fractional seconds in MDTM responses To: tech@openbsd.org Date: Wed, 11 Feb 2026 20:22:29 -0600 hello tech@, index: util.c cmds.c ftp.c small.c extern.h this diff implements the TODO to parse optional fractional seconds from FTP MDTM timestamp response. the diff changes remotemodtime() to return struct timespec instead of time_t so fractional part isn't lost. also updates all callers to use .tv_sec. tested with custom ftp server using fractional timestamp. diff: --- util.c.orig Wed Feb 11 19:45:14 2026 +++ /usr/src/usr.bin/ftp/util.c Wed Feb 11 19:29:46 2026 @@ -602,12 +602,11 @@ remotesize(const char *file, int noisy) /* * determine last modification time (in GMT) of remote file */ -time_t +struct timespec remotemodtime(const char *file, int noisy) { - int overbose; + struct timespec result; - time_t rtime; - int ocode; + int ocode, overbose; overbose = verbose; ocode = code; - rtime = -1; + result.tv_sec = -1; #ifndef SMALL if (!debug) #endif /* !SMALL */ verbose = -1; if (command("MDTM %s", file) == COMPLETE) { struct tm timebuf; int yy, mo, day, hour, min, sec; + char *cp, *ep; + long frac, nsec; + int i, multiplier, num_digits; /* * time-val = 14DIGIT [ "." 1*DIGIT ] * YYYYMMDDHHMMSS[.sss] * mdtm-response = "213" SP time-val CRLF / error-response */ - /* TODO: parse .sss as well, use timespecs. */ char *timestr = reply_string; /* Repair `19%02d' bug on server side */ @@ -649,15 +653,31 @@ remotemodtime(const char *file, int noisy) timebuf.tm_mon = mo - 1; timebuf.tm_year = yy - 1900; timebuf.tm_isdst = -1; - rtime = mktime(&timebuf); - if (rtime == -1 && (noisy + + cp = strchr(reply_string, '.'); + if (cp != NULL) { + cp++; + frac = strtol(cp, &ep, 10); + if (*ep != '\0' && !isspace((unsigned char)*ep)) + nsec = 0; + else { + num_digits = ep - cp; + multiplier = 1; + for (i = 0; i < (9 - num_digits); i++) + multiplier *= 10; + nsec = frac * multiplier; + } + } else + nsec = 0; + + result.tv_sec = mktime(&timebuf); + result.tv_nsec = nsec; + if (result.tv_sec == -1 && (noisy #ifndef SMALL || debug #endif /* !SMALL */ )) fprintf(ttyout, "Can't convert %s to a time.\n", reply_string); else - rtime += timebuf.tm_gmtoff; /* conv. local -> GMT */ + result.tv_sec += timebuf.tm_gmtoff; /* conv. local -> GMT */ } else if (noisy #ifndef SMALL && !debug @@ -667,9 +687,9 @@ remotemodtime(const char *file, int noisy) fputc('\n', ttyout); } verbose = overbose; - if (rtime == -1) + if (result.tv_sec == -1) code = ocode; - return (rtime); + return (result); } /* --- cmds.c.orig Wed Feb 11 19:45:15 2026 +++ /usr/src/usr.bin/ftp/cmds.c Wed Feb 11 18:57:19 2026 @@ -1594,7 +1594,7 @@ modtime(int argc, char *argv[]) code = -1; return; } - mtime = remotemodtime(argv[1], 1); + mtime = remotemodtime(argv[1], 1).tv_sec; if (mtime != -1) fprintf(ttyout, "%s\t%s", argv[1], asctime(localtime(&mtime))); code = mtime; --- ftp.c.orig Wed Feb 11 19:45:16 2026 +++ /usr/src/usr.bin/ftp/ftp.c Wed Feb 11 18:56:48 2026 @@ -1204,7 +1204,7 @@ break2: if (bytes > 0) ptransfer(0); if (preserve && (closefunc == fclose)) { - mtime = remotemodtime(remote, 0); + mtime = remotemodtime(remote, 0).tv_sec; if (mtime != -1) { struct timespec times[2]; --- small.c.orig Wed Feb 11 19:45:17 2026 +++ /usr/src/usr.bin/ftp/small.c Wed Feb 11 18:56:17 2026 @@ -269,7 +269,7 @@ usage: if (ret == 0) { time_t mtime; - mtime = remotemodtime(argv[1], 0); + mtime = remotemodtime(argv[1], 0).tv_sec; if (mtime == -1) goto freegetit; if (stbuf.st_mtime >= mtime) { --- extern.h.orig Wed Feb 11 19:45:17 2026 +++ /usr/src/usr.bin/ftp/extern.h Wed Feb 11 19:42:14 2026 @@ -101,7 +101,7 @@ void recvrequest(const char *, const char *, const cha const char *, int, int); char *remglob(char **, int, char **); off_t remotesize(const char *, int); -time_t remotemodtime(const char *, int); +struct timespec remotemodtime(const char *, int); void reset(int, char **); void rmthelp(int, char **); void sethash(int, char **); ok? --- util.c.orig Wed Feb 11 19:45:14 2026 +++ /usr/src/usr.bin/ftp/util.c Wed Feb 11 19:29:46 2026 @@ -602,12 +602,11 @@ remotesize(const char *file, int noisy) /* * determine last modification time (in GMT) of remote file */ -time_t +struct timespec remotemodtime(const char *file, int noisy) { - int overbose; + struct timespec result; - time_t rtime; - int ocode; + int ocode, overbose; overbose = verbose; ocode = code; - rtime = -1; + result.tv_sec = -1; #ifndef SMALL if (!debug) #endif /* !SMALL */ verbose = -1; if (command("MDTM %s", file) == COMPLETE) { struct tm timebuf; int yy, mo, day, hour, min, sec; + char *cp, *ep; + long frac, nsec; + int i, multiplier, num_digits; /* * time-val = 14DIGIT [ "." 1*DIGIT ] * YYYYMMDDHHMMSS[.sss] * mdtm-response = "213" SP time-val CRLF / error-response */ - /* TODO: parse .sss as well, use timespecs. */ char *timestr = reply_string; /* Repair `19%02d' bug on server side */ @@ -649,15 +653,31 @@ remotemodtime(const char *file, int noisy) timebuf.tm_mon = mo - 1; timebuf.tm_year = yy - 1900; timebuf.tm_isdst = -1; - rtime = mktime(&timebuf); - if (rtime == -1 && (noisy + + cp = strchr(reply_string, '.'); + if (cp != NULL) { + cp++; + frac = strtol(cp, &ep, 10); + if (*ep != '\0' && !isspace((unsigned char)*ep)) + nsec = 0; + else { + num_digits = ep - cp; + multiplier = 1; + for (i = 0; i < (9 - num_digits); i++) + multiplier *= 10; + nsec = frac * multiplier; + } + } else + nsec = 0; + + result.tv_sec = mktime(&timebuf); + result.tv_nsec = nsec; + if (result.tv_sec == -1 && (noisy #ifndef SMALL || debug #endif /* !SMALL */ )) fprintf(ttyout, "Can't convert %s to a time.\n", reply_string); else - rtime += timebuf.tm_gmtoff; /* conv. local -> GMT */ + result.tv_sec += timebuf.tm_gmtoff; /* conv. local -> GMT */ } else if (noisy #ifndef SMALL && !debug @@ -667,9 +687,9 @@ remotemodtime(const char *file, int noisy) fputc('\n', ttyout); } verbose = overbose; - if (rtime == -1) + if (result.tv_sec == -1) code = ocode; - return (rtime); + return (result); } /* --- cmds.c.orig Wed Feb 11 19:45:15 2026 +++ /usr/src/usr.bin/ftp/cmds.c Wed Feb 11 18:57:19 2026 @@ -1594,7 +1594,7 @@ modtime(int argc, char *argv[]) code = -1; return; } - mtime = remotemodtime(argv[1], 1); + mtime = remotemodtime(argv[1], 1).tv_sec; if (mtime != -1) fprintf(ttyout, "%s\t%s", argv[1], asctime(localtime(&mtime))); code = mtime; --- ftp.c.orig Wed Feb 11 19:45:16 2026 +++ /usr/src/usr.bin/ftp/ftp.c Wed Feb 11 18:56:48 2026 @@ -1204,7 +1204,7 @@ break2: if (bytes > 0) ptransfer(0); if (preserve && (closefunc == fclose)) { - mtime = remotemodtime(remote, 0); + mtime = remotemodtime(remote, 0).tv_sec; if (mtime != -1) { struct timespec times[2]; --- small.c.orig Wed Feb 11 19:45:17 2026 +++ /usr/src/usr.bin/ftp/small.c Wed Feb 11 18:56:17 2026 @@ -269,7 +269,7 @@ usage: if (ret == 0) { time_t mtime; - mtime = remotemodtime(argv[1], 0); + mtime = remotemodtime(argv[1], 0).tv_sec; if (mtime == -1) goto freegetit; if (stbuf.st_mtime >= mtime) { --- extern.h.orig Wed Feb 11 19:45:17 2026 +++ /usr/src/usr.bin/ftp/extern.h Wed Feb 11 19:42:14 2026 @@ -101,7 +101,7 @@ void recvrequest(const char *, const char *, const cha const char *, int, int); char *remglob(char **, int, char **); off_t remotesize(const char *, int); -time_t remotemodtime(const char *, int); +struct timespec remotemodtime(const char *, int); void reset(int, char **); void rmthelp(int, char **); void sethash(int, char **);