Index | Thread | Search

From:
Benjamin Lee McQueen <mcq@disroot.org>
Subject:
ftp(1): parse fractional seconds in MDTM responses
To:
tech@openbsd.org
Date:
Wed, 11 Feb 2026 20:22:29 -0600

Download raw body.

Thread
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 **);