Bug 15872

Summary: Y2k bug in at(1)
Product: Base System Reporter: Crist J. Clark <cjc>
Component: binAssignee: Sheldon Hearn <sheldonh>
Status: Closed FIXED    
Severity: Affects Only Me    
Priority: Normal    
Version: 3.4-STABLE   
Hardware: Any   
OS: Any   

Description Crist J. Clark 2000-01-04 03:00:01 UTC
	There is a Y2k bug in at(1). The following portion of code
can cause valid time specifications to return 'garbled time' errors,

/*
 * assign_date() assigns a date, wrapping to next year if needed
 */
static void
assign_date(struct tm *tm, long mday, long mon, long year)
{
    if (year > 99) {
	if (year > 1899)
	    year -= 1900;
	else
	    panic("garbled time");
    } ...

When this function is passed the current year in tm_year format, 100,
it will complain it is a garbled time. However, it is a legal value.

Fix: 

The parsetime.c code, which is excerpted above, is a _very_
complex piece of work. I hesitate to write a patch to fix this small
problem since it might create greater ones. I leave it to someone with
more familiarity with the code. (However, I am not sure why the 
'year > 99' check is needed at all.)

	In the mean time, the previous command line can will work if
you phrase it,

% at 10:30 + 2 days

You may need to try other convoluted constructions to work around
other formats that might trigger the bug.
How-To-Repeat: 
	Try to queue an atjob in the following manner,

% at 10:30am wed
at: garbled time

However, that is a legal command line.
Comment 1 Sheldon Hearn 2000-01-04 10:55:34 UTC
On Mon, 03 Jan 2000 21:54:28 EST, "Crist J. Clark" wrote:

>     if (year > 99) {
> 	if (year > 1899)
> 	    year -= 1900;
> 	else
> 	    panic("garbled time");
>     } ...

What odd code. :-)

I'd fix this by making proper tm_year adjustments before those calls to
assign_date() which pass it a tm_year value.

Have you chatted to the authors?  Are they unreachable?

Ciao,
Sheldon.

Index: parsetime.c
===================================================================
RCS file: /home/ncvs/src/usr.bin/at/parsetime.c,v
retrieving revision 1.19
diff -u -d -r1.19 parsetime.c
--- parsetime.c	1999/12/05 19:57:14	1.19
+++ parsetime.c	2000/01/04 10:54:54
@@ -495,7 +495,7 @@
 
 	    tm->tm_wday = wday;
 
-	    assign_date(tm, mday, tm->tm_mon, tm->tm_year);
+	    assign_date(tm, mday, tm->tm_mon, 1900 + tm->tm_year);
 	    break;
 
     case NUMBER:
@@ -527,7 +527,7 @@
 	    }
 	    else if (tlen == 6 || tlen == 8) {
 		if (tlen == 8) {
-		    year = (mon % 10000) - 1900;
+		    year = mon % 10000;
 		    mon /= 10000;
 		}
 		else {
Comment 2 Sergey N. Voronkov 2000-01-21 05:28:10 UTC
> >     if (year > 99) {
> >      if (year > 1899)
> >          year -= 1900;
> >      else
> >          panic("garbled time");
> >     } ...
>  
> What odd code. :-)
> 
> I'd fix this by making proper tm_year adjustments before those calls to
> assign_date() which pass it a tm_year value.
>     
> Have you chatted to the authors?  Are they unreachable?
>     
> Ciao,
> Sheldon.
>     
> Index: parsetime.c
> ===================================================================
> RCS file: /home/ncvs/src/usr.bin/at/parsetime.c,v
> retrieving revision 1.19
[...]

It's another way to do this right. I'v adapted OpenBSD Team patch
.
And, Mr. Sheldon, can you, please, make one of two changes in STABLE branch ?

Best Regards,
Sergey N. Voronkov.

--- /usr/src/usr.bin/at/parsetime.c	Sun Aug 29 21:25:26 1999
+++ parsetime.c	Fri Jan 21 10:21:56 2000
@@ -417,27 +417,30 @@
 static void
 assign_date(struct tm *tm, long mday, long mon, long year)
 {
-    if (year > 99) {
-	if (year > 1899)
-	    year -= 1900;
-	else
-	    panic("garbled time");
-    } else if (year != -1) {
+
+    /*
+     * Convert year into tm_year format (year - 1900).
+     * We may be given the year in 2 digit, 4 digit, or tm_year format.
+     */
+#define TM_YEAR_BASE 1900
+    if (year != -1) {
 	struct tm *lt;
 	time_t now;
 
 	time(&now);
 	lt = localtime(&now);
 
-	/*
-	 * check if the specified year is in the next century.
-	 * allow for one year of user error as many people will
-	 * enter n - 1 at the start of year n.
-	 */
-	if (year < (lt->tm_year % 100) - 1)
-	    year += 100;
-	/* adjust for the year 2000 and beyond */
-	year += lt->tm_year - (lt->tm_year % 100);
+        if (year >= TM_YEAR_BASE)
+                year -= TM_YEAR_BASE;   /* convert from 4 digit year */
+        else if (year < 100) {
+                /* Convert to tm_year assuming current century */
+                year += (lt->tm_year / 100) * 100;
+
+                if (year == lt->tm_year - 1)
+                       year++;         /* Common off by one error */
+                else if (year < lt->tm_year)
+                       year += 100;    /* must be in next century */
+        }
     }
 
     if (year < 0 &&
Comment 3 Sergey N. Voronkov 2000-01-27 13:53:59 UTC
Hello Nick!

As a last person deeply touched /usr/src/usr.bin/at/parsetime.c, 
could you, please, review and apply this patch to at (PR/15872).
Patch is based on OpenBSD code.

Sorry, I test it only on my 3.4-STABLE system. In CURRENT may be
need to make some changes in patch header. assign_date() has some bug
in both versions.

Serg N. Voronkov

--- parsetime.c.orig	Mon Aug 30 00:42:00 1999
+++ parsetime.c	Thu Jan 27 18:50:48 2000
@@ -417,27 +417,28 @@
 static void
 assign_date(struct tm *tm, long mday, long mon, long year)
 {
-    if (year > 99) {
-	if (year > 1899)
-	    year -= 1900;
-	else
-	    panic("garbled time");
-    } else if (year != -1) {
-	struct tm *lt;
-	time_t now;
 
-	time(&now);
-	lt = localtime(&now);
+   /*
+    * Convert year into tm_year format (year - 1900).
+    */
+    if (year != -1) {
+	if (year >= 1900)
+		year -= 1900;   /* convert from 4 digit year */
+	else if (year < 100) {
+		/* convert from 2 digit year */
+		struct tm *lt;
+		time_t now;
 
-	/*
-	 * check if the specified year is in the next century.
-	 * allow for one year of user error as many people will
-	 * enter n - 1 at the start of year n.
-	 */
-	if (year < (lt->tm_year % 100) - 1)
-	    year += 100;
-	/* adjust for the year 2000 and beyond */
-	year += lt->tm_year - (lt->tm_year % 100);
+		time(&now);
+		lt = localtime(&now);
+
+		/* Convert to tm_year assuming current century */
+		year += (lt->tm_year / 100) * 100;
+
+		if (year == lt->tm_year - 1) year++;
+		else if (year < lt->tm_year)
+			year += 100;    /* must be in next century */
+	}
     }
 
     if (year < 0 &&
Comment 4 Sheldon Hearn 2000-02-19 15:59:53 UTC
On Fri, 21 Jan 2000 10:28:10 +0500, "Sergey N. Voronkov" wrote:

> It's another way to do this right. I'v adapted OpenBSD Team patch.
> And, Mr. Sheldon, can you, please, make one of two changes in STABLE
> branch ?

Hi Sergey,

I'm sorry I've taken so long to get back to you on this one.  Work
pressure picked up unexpectedly.

It looks like this one will have to wait 'til after 4.0-RELEASE.

Apologies,
Sheldon.
Comment 5 Sheldon Hearn freebsd_committer freebsd_triage 2000-02-19 16:00:33 UTC
Responsible Changed
From-To: freebsd-bugs->sheldonh

Reminder for me to tackle this after 4.0-RELEASE if nobody else has 
done so by then. 

Comment 6 Sheldon Hearn freebsd_committer freebsd_triage 2000-03-27 10:32:44 UTC
State Changed
From-To: open->analyzed

Committed in rev 1.20 of src/usr.bin/at/parsetime.c .  We'll give it 
some time in HEAD before merging onto RELENG_4 and RELENG_3 branches. 
Thanks! 
Comment 7 Sheldon Hearn freebsd_committer freebsd_triage 2000-06-22 13:36:03 UTC
State Changed
From-To: analyzed->closed

Merged onto RELENG_4 branch in rev 1.19.2.1 of parsetime.c and 
onto RELENG_3 branch in rev 1.16.2.2, both on Fri Apr 14 2000.