diff mbox series

[v3] ctime.3: Document how to check errors from mktime(3)

Message ID 795308497fb950b9f8602a2eedf4fc749ff6e50e.1724446443.git.alx@kernel.org
State New
Headers show
Series [v3] ctime.3: Document how to check errors from mktime(3) | expand

Commit Message

Alejandro Colomar Aug. 23, 2024, 8:56 p.m. UTC
-1 is a valid successful time_t, for one second before the Epoch.  And
mktime(3) is allowed (like most libc calls) to set errno on success.
This makes it impossible to determine errors from the return value or
errno.

ISO C specifies that tp->tm_wday is unmodified after a failed call, and
puts an example where this is used to determine errors.  It is indeed
the only way to check for errors from this call.

Document this detail in the RETURN VALUE section, add a CAVEATS section
that warns about this, and write an example program that shows how to
properly call this function.

All the code I've been able to find in several search engines either
doesn't check for errors after mktime(3), or checks them incorrectly, so
this documentation should help fix those.

This is guaranteed since ISO C23 and POSIX.1-2024.  Prior to those
standards, there was no standard way to check for errors.  However,
there are no known implementations that do not conform to this,
according to WG14 (which themselves refer to the Austin Group for having
researched that).

Link: <https://lore.kernel.org/linux-man/20240823131024.GD2713@cventin.lip.ens-lyon.fr/T/#t>
Link: <https://lore.kernel.org/linux-man/6un6baaq5tez23irtycuvzqtuh7a4sdrf2px7tnyb3y6iqoxmq@2ofln4cd27ep/T/#t>
Link: <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3147.txt>
Link: <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3148.doc>
Link: <https://austingroupbugs.net/view.php?id=1614>
Link: <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf#subsubsection.7.29.2.3>
Reported-by: Paul Eggert <eggert@cs.ucla.edu>
Cc: Vincent Lefevre <vincent@vinc17.net>
Cc: DJ Delorie <dj@redhat.com>
Cc: Carlos O'Donell <carlos@redhat.com>
Cc: Xi Ruoyao <xry111@xry111.site>
Cc: Brian Inglis <Brian.Inglis@SystematicSW.ab.ca>
Cc: "Robert C. Seacord" <rcseacord@gmail.com>
Cc: Jens Gustedt <jens.gustedt@inria.fr>
Cc: Robert Elz <kre@munnari.oz.au>
Cc: Andrew Josey <ajosey@opengroup.org>
Cc: Geoff Clare <gwc@opengroup.org>
Cc: GNU C Library <libc-alpha@sourceware.org>
Cc: Austin Group <austin-group-l@opengroup.org>
Cc: Hans Åberg <haberg-1@telia.com>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---

Hi!

v3 changes:

-  Typos in the example runs (s/2024/2023/)
-  s/if/whether/  [Paul]
-  Respect the sign of time_t.  [Paul]
-  Remove unused include.  [IWYU]
-  Add links and CCs.

Cheers,
Alex

Range-diff against v2:
1:  664cd54a8 ! 1:  795308497 ctime.3: Document how to check errors from mktime(3)
    @@ Commit message
         according to WG14 (which themselves refer to the Austin Group for having
         researched that).
     
    +    Link: <https://lore.kernel.org/linux-man/20240823131024.GD2713@cventin.lip.ens-lyon.fr/T/#t>
    +    Link: <https://lore.kernel.org/linux-man/6un6baaq5tez23irtycuvzqtuh7a4sdrf2px7tnyb3y6iqoxmq@2ofln4cd27ep/T/#t>
         Link: <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3147.txt>
         Link: <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3148.doc>
         Link: <https://austingroupbugs.net/view.php?id=1614>
    @@ Commit message
         Cc: Carlos O'Donell <carlos@redhat.com>
         Cc: Xi Ruoyao <xry111@xry111.site>
         Cc: Brian Inglis <Brian.Inglis@SystematicSW.ab.ca>
    +    Cc: "Robert C. Seacord" <rcseacord@gmail.com>
    +    Cc: Jens Gustedt <jens.gustedt@inria.fr>
    +    Cc: Robert Elz <kre@munnari.oz.au>
    +    Cc: Andrew Josey <ajosey@opengroup.org>
    +    Cc: Geoff Clare <gwc@opengroup.org>
         Cc: GNU C Library <libc-alpha@sourceware.org>
    +    Cc: Austin Group <austin-group-l@opengroup.org>
    +    Cc: Hans Åberg <haberg-1@telia.com>
         Signed-off-by: Alejandro Colomar <alx@kernel.org>
     
      ## man/man3/ctime.3 ##
    @@ man/man3/ctime.3: .SH NOTES
     +.I (time_t) \-1
     +can represent a valid time
     +(one second before the Epoch).
    -+To determine if
    ++To determine whether
     +.BR mktime ()
     +failed,
     +one must use the
    @@ man/man3/ctime.3: .SH NOTES
     +.RB $\~ "./a.out 2024 02 23 00 17 53 1" ;
     +1708640273
     +$
    -+.RB $\~ "./a.out 2024 03 26 02 17 53 \-1" ;
    ++.RB $\~ "./a.out 2023 03 26 02 17 53 \-1" ;
     +1679793473
     +$
    -+.RB $\~ "./a.out 2024 10 29 02 17 53 \-1" ;
    ++.RB $\~ "./a.out 2023 10 29 02 17 53 \-1" ;
     +1698542273
    -+.RB $\~ "./a.out 2024 10 29 02 17 53 0" ;
    ++.RB $\~ "./a.out 2023 10 29 02 17 53 0" ;
     +1698542273
    -+.RB $\~ "./a.out 2024 10 29 02 17 53 1" ;
    ++.RB $\~ "./a.out 2023 10 29 02 17 53 1" ;
     +1698538673
     +$
    -+.RB $\~ "./a.out 2024 02 29 12 00 00 \-1" ;
    ++.RB $\~ "./a.out 2023 02 29 12 00 00 \-1" ;
     +1677668400
     +.EE
     +.SS Program source: mktime.c
    @@ man/man3/ctime.3: .SH NOTES
     +.\" SRC BEGIN (mktime.c)
     +.EX
     +#include <err.h>
    -+#include <errno.h>
     +#include <stdint.h>
     +#include <stdio.h>
     +#include <stdlib.h>
     +#include <time.h>
     +\&
    ++#define is_signed(T)  ((T) \-1 < 1)
    ++\&
     +int
     +main(int argc, char *argv[])
     +{
    @@ man/man3/ctime.3: .SH NOTES
     +    if (tm.tm_wday == \-1)
     +        err(EXIT_FAILURE, "mktime");
     +\&
    -+    printf("%jd\[rs]n", (intmax_t) t);
    ++    if (is_signed(time_t))
    ++        printf("%jd\[rs]n", (intmax_t) t);
    ++    else
    ++        printf("%ju\[rs]n", (uintmax_t) t);
    ++\&
     +    exit(EXIT_SUCCESS);
     +}
     +.EE

 man/man3/ctime.3 | 104 ++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 103 insertions(+), 1 deletion(-)


base-commit: 0813c125d8bf754c40015aa1b31f23e0650584ac

Comments

Alejandro Colomar Aug. 23, 2024, 10:24 p.m. UTC | #1
Hi all,

On Fri, Aug 23, 2024 at 10:56:40PM GMT, Alejandro Colomar wrote:
> -1 is a valid successful time_t, for one second before the Epoch.  And
> mktime(3) is allowed (like most libc calls) to set errno on success.
> This makes it impossible to determine errors from the return value or
> errno.
> 
> ISO C specifies that tp->tm_wday is unmodified after a failed call, and
> puts an example where this is used to determine errors.  It is indeed
> the only way to check for errors from this call.
> 
> Document this detail in the RETURN VALUE section, add a CAVEATS section
> that warns about this, and write an example program that shows how to
> properly call this function.
> 
> All the code I've been able to find in several search engines either
> doesn't check for errors after mktime(3), or checks them incorrectly, so
> this documentation should help fix those.
> 
> This is guaranteed since ISO C23 and POSIX.1-2024.  Prior to those
> standards, there was no standard way to check for errors.  However,
> there are no known implementations that do not conform to this,
> according to WG14 (which themselves refer to the Austin Group for having
> researched that).
> 
> Link: <https://lore.kernel.org/linux-man/20240823131024.GD2713@cventin.lip.ens-lyon.fr/T/#t>
> Link: <https://lore.kernel.org/linux-man/6un6baaq5tez23irtycuvzqtuh7a4sdrf2px7tnyb3y6iqoxmq@2ofln4cd27ep/T/#t>
> Link: <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3147.txt>
> Link: <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3148.doc>
> Link: <https://austingroupbugs.net/view.php?id=1614>
> Link: <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf#subsubsection.7.29.2.3>
> Reported-by: Paul Eggert <eggert@cs.ucla.edu>
> Cc: Vincent Lefevre <vincent@vinc17.net>
> Cc: DJ Delorie <dj@redhat.com>
> Cc: Carlos O'Donell <carlos@redhat.com>
> Cc: Xi Ruoyao <xry111@xry111.site>
> Cc: Brian Inglis <Brian.Inglis@SystematicSW.ab.ca>
> Cc: "Robert C. Seacord" <rcseacord@gmail.com>
> Cc: Jens Gustedt <jens.gustedt@inria.fr>
> Cc: Robert Elz <kre@munnari.oz.au>
> Cc: Andrew Josey <ajosey@opengroup.org>
> Cc: Geoff Clare <gwc@opengroup.org>
> Cc: GNU C Library <libc-alpha@sourceware.org>
> Cc: Austin Group <austin-group-l@opengroup.org>
> Cc: Hans Åberg <haberg-1@telia.com>
> Signed-off-by: Alejandro Colomar <alx@kernel.org>
> ---

I've finally applied this patch.  Thank you all for your feedback!
Especially Paul for finding so many problems in my code!  :)

Have a lovely night!
Alex

> 
> Hi!
> 
> v3 changes:
> 
> -  Typos in the example runs (s/2024/2023/)
> -  s/if/whether/  [Paul]
> -  Respect the sign of time_t.  [Paul]
> -  Remove unused include.  [IWYU]
> -  Add links and CCs.
> 
> Cheers,
> Alex
> 
> Range-diff against v2:
> 1:  664cd54a8 ! 1:  795308497 ctime.3: Document how to check errors from mktime(3)
>     @@ Commit message
>          according to WG14 (which themselves refer to the Austin Group for having
>          researched that).
>      
>     +    Link: <https://lore.kernel.org/linux-man/20240823131024.GD2713@cventin.lip.ens-lyon.fr/T/#t>
>     +    Link: <https://lore.kernel.org/linux-man/6un6baaq5tez23irtycuvzqtuh7a4sdrf2px7tnyb3y6iqoxmq@2ofln4cd27ep/T/#t>
>          Link: <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3147.txt>
>          Link: <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3148.doc>
>          Link: <https://austingroupbugs.net/view.php?id=1614>
>     @@ Commit message
>          Cc: Carlos O'Donell <carlos@redhat.com>
>          Cc: Xi Ruoyao <xry111@xry111.site>
>          Cc: Brian Inglis <Brian.Inglis@SystematicSW.ab.ca>
>     +    Cc: "Robert C. Seacord" <rcseacord@gmail.com>
>     +    Cc: Jens Gustedt <jens.gustedt@inria.fr>
>     +    Cc: Robert Elz <kre@munnari.oz.au>
>     +    Cc: Andrew Josey <ajosey@opengroup.org>
>     +    Cc: Geoff Clare <gwc@opengroup.org>
>          Cc: GNU C Library <libc-alpha@sourceware.org>
>     +    Cc: Austin Group <austin-group-l@opengroup.org>
>     +    Cc: Hans Åberg <haberg-1@telia.com>
>          Signed-off-by: Alejandro Colomar <alx@kernel.org>
>      
>       ## man/man3/ctime.3 ##
>     @@ man/man3/ctime.3: .SH NOTES
>      +.I (time_t) \-1
>      +can represent a valid time
>      +(one second before the Epoch).
>     -+To determine if
>     ++To determine whether
>      +.BR mktime ()
>      +failed,
>      +one must use the
>     @@ man/man3/ctime.3: .SH NOTES
>      +.RB $\~ "./a.out 2024 02 23 00 17 53 1" ;
>      +1708640273
>      +$
>     -+.RB $\~ "./a.out 2024 03 26 02 17 53 \-1" ;
>     ++.RB $\~ "./a.out 2023 03 26 02 17 53 \-1" ;
>      +1679793473
>      +$
>     -+.RB $\~ "./a.out 2024 10 29 02 17 53 \-1" ;
>     ++.RB $\~ "./a.out 2023 10 29 02 17 53 \-1" ;
>      +1698542273
>     -+.RB $\~ "./a.out 2024 10 29 02 17 53 0" ;
>     ++.RB $\~ "./a.out 2023 10 29 02 17 53 0" ;
>      +1698542273
>     -+.RB $\~ "./a.out 2024 10 29 02 17 53 1" ;
>     ++.RB $\~ "./a.out 2023 10 29 02 17 53 1" ;
>      +1698538673
>      +$
>     -+.RB $\~ "./a.out 2024 02 29 12 00 00 \-1" ;
>     ++.RB $\~ "./a.out 2023 02 29 12 00 00 \-1" ;
>      +1677668400
>      +.EE
>      +.SS Program source: mktime.c
>     @@ man/man3/ctime.3: .SH NOTES
>      +.\" SRC BEGIN (mktime.c)
>      +.EX
>      +#include <err.h>
>     -+#include <errno.h>
>      +#include <stdint.h>
>      +#include <stdio.h>
>      +#include <stdlib.h>
>      +#include <time.h>
>      +\&
>     ++#define is_signed(T)  ((T) \-1 < 1)
>     ++\&
>      +int
>      +main(int argc, char *argv[])
>      +{
>     @@ man/man3/ctime.3: .SH NOTES
>      +    if (tm.tm_wday == \-1)
>      +        err(EXIT_FAILURE, "mktime");
>      +\&
>     -+    printf("%jd\[rs]n", (intmax_t) t);
>     ++    if (is_signed(time_t))
>     ++        printf("%jd\[rs]n", (intmax_t) t);
>     ++    else
>     ++        printf("%ju\[rs]n", (uintmax_t) t);
>     ++\&
>      +    exit(EXIT_SUCCESS);
>      +}
>      +.EE
> 
>  man/man3/ctime.3 | 104 ++++++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 103 insertions(+), 1 deletion(-)
> 
> diff --git a/man/man3/ctime.3 b/man/man3/ctime.3
> index 5aec51b79..84d140c71 100644
> --- a/man/man3/ctime.3
> +++ b/man/man3/ctime.3
> @@ -241,7 +241,10 @@ .SH RETURN VALUE
>  On error,
>  .BR mktime ()
>  returns the value
> -.IR "(time_t)\ \-1" .
> +.IR "(time_t)\ \-1" ,
> +and leaves the
> +.I tm->tm_wday
> +member unmodified.
>  The remaining functions return NULL on error.
>  On error,
>  .I errno
> @@ -412,6 +415,105 @@ .SH NOTES
>  object types may overwrite the information in any object of the same type
>  pointed to by the value returned from any previous call to any of them."
>  This can occur in the glibc implementation.
> +.SH CAVEATS
> +.SS mktime()
> +.I (time_t) \-1
> +can represent a valid time
> +(one second before the Epoch).
> +To determine whether
> +.BR mktime ()
> +failed,
> +one must use the
> +.I tm->tm_wday
> +field.
> +See the example program in EXAMPLES.
> +.SH EXAMPLES
> +The following shell session shows sample runs of the program:
> +.P
> +.in +4n
> +.EX
> +.RB $\~ "TZ=UTC ./a.out 1969 12 31 23 59 59 0" ;
> +\-1
> +$
> +.RB $\~ "export TZ=Europe/Madrid" ;
> +$
> +.RB $\~ "./a.out 2147483647 2147483647 00 00 00 00 -1" ;
> +a.out: mktime: Value too large for defined data type
> +$
> +.RB $\~ "./a.out 2024 08 23 00 17 53 \-1" ;
> +1724365073
> +.RB $\~ "./a.out 2024 08 23 00 17 53 0" ;
> +1724368673
> +.RB $\~ "./a.out 2024 08 23 00 17 53 1" ;
> +1724365073
> +$
> +.RB $\~ "./a.out 2024 02 23 00 17 53 \-1" ;
> +1708643873
> +.RB $\~ "./a.out 2024 02 23 00 17 53 0" ;
> +1708643873
> +.RB $\~ "./a.out 2024 02 23 00 17 53 1" ;
> +1708640273
> +$
> +.RB $\~ "./a.out 2023 03 26 02 17 53 \-1" ;
> +1679793473
> +$
> +.RB $\~ "./a.out 2023 10 29 02 17 53 \-1" ;
> +1698542273
> +.RB $\~ "./a.out 2023 10 29 02 17 53 0" ;
> +1698542273
> +.RB $\~ "./a.out 2023 10 29 02 17 53 1" ;
> +1698538673
> +$
> +.RB $\~ "./a.out 2023 02 29 12 00 00 \-1" ;
> +1677668400
> +.EE
> +.SS Program source: mktime.c
> +\&
> +.\" SRC BEGIN (mktime.c)
> +.EX
> +#include <err.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <time.h>
> +\&
> +#define is_signed(T)  ((T) \-1 < 1)
> +\&
> +int
> +main(int argc, char *argv[])
> +{
> +    char       **p;
> +    time_t     t;
> +    struct tm  tm;
> +\&
> +    if (argc != 8) {
> +        fprintf(stderr, "Usage: %s yyyy mm dd HH MM SS isdst\[rs]n", argv[0]);
> +        exit(EXIT_FAILURE);
> +    }
> +\&
> +    p = &argv[1];
> +    tm.tm_year  = atoi(*p++) \- 1900;
> +    tm.tm_mon   = atoi(*p++) \- 1;
> +    tm.tm_mday  = atoi(*p++);
> +    tm.tm_hour  = atoi(*p++);
> +    tm.tm_min   = atoi(*p++);
> +    tm.tm_sec   = atoi(*p++);
> +    tm.tm_isdst = atoi(*p++);
> +\&
> +    tm.tm_wday = \-1;
> +    t = mktime(&tm);
> +    if (tm.tm_wday == \-1)
> +        err(EXIT_FAILURE, "mktime");
> +\&
> +    if (is_signed(time_t))
> +        printf("%jd\[rs]n", (intmax_t) t);
> +    else
> +        printf("%ju\[rs]n", (uintmax_t) t);
> +\&
> +    exit(EXIT_SUCCESS);
> +}
> +.EE
> +.\" SRC END
>  .SH SEE ALSO
>  .BR date (1),
>  .BR gettimeofday (2),
> 
> base-commit: 0813c125d8bf754c40015aa1b31f23e0650584ac
> -- 
> 2.45.2
>
diff mbox series

Patch

diff --git a/man/man3/ctime.3 b/man/man3/ctime.3
index 5aec51b79..84d140c71 100644
--- a/man/man3/ctime.3
+++ b/man/man3/ctime.3
@@ -241,7 +241,10 @@  .SH RETURN VALUE
 On error,
 .BR mktime ()
 returns the value
-.IR "(time_t)\ \-1" .
+.IR "(time_t)\ \-1" ,
+and leaves the
+.I tm->tm_wday
+member unmodified.
 The remaining functions return NULL on error.
 On error,
 .I errno
@@ -412,6 +415,105 @@  .SH NOTES
 object types may overwrite the information in any object of the same type
 pointed to by the value returned from any previous call to any of them."
 This can occur in the glibc implementation.
+.SH CAVEATS
+.SS mktime()
+.I (time_t) \-1
+can represent a valid time
+(one second before the Epoch).
+To determine whether
+.BR mktime ()
+failed,
+one must use the
+.I tm->tm_wday
+field.
+See the example program in EXAMPLES.
+.SH EXAMPLES
+The following shell session shows sample runs of the program:
+.P
+.in +4n
+.EX
+.RB $\~ "TZ=UTC ./a.out 1969 12 31 23 59 59 0" ;
+\-1
+$
+.RB $\~ "export TZ=Europe/Madrid" ;
+$
+.RB $\~ "./a.out 2147483647 2147483647 00 00 00 00 -1" ;
+a.out: mktime: Value too large for defined data type
+$
+.RB $\~ "./a.out 2024 08 23 00 17 53 \-1" ;
+1724365073
+.RB $\~ "./a.out 2024 08 23 00 17 53 0" ;
+1724368673
+.RB $\~ "./a.out 2024 08 23 00 17 53 1" ;
+1724365073
+$
+.RB $\~ "./a.out 2024 02 23 00 17 53 \-1" ;
+1708643873
+.RB $\~ "./a.out 2024 02 23 00 17 53 0" ;
+1708643873
+.RB $\~ "./a.out 2024 02 23 00 17 53 1" ;
+1708640273
+$
+.RB $\~ "./a.out 2023 03 26 02 17 53 \-1" ;
+1679793473
+$
+.RB $\~ "./a.out 2023 10 29 02 17 53 \-1" ;
+1698542273
+.RB $\~ "./a.out 2023 10 29 02 17 53 0" ;
+1698542273
+.RB $\~ "./a.out 2023 10 29 02 17 53 1" ;
+1698538673
+$
+.RB $\~ "./a.out 2023 02 29 12 00 00 \-1" ;
+1677668400
+.EE
+.SS Program source: mktime.c
+\&
+.\" SRC BEGIN (mktime.c)
+.EX
+#include <err.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+\&
+#define is_signed(T)  ((T) \-1 < 1)
+\&
+int
+main(int argc, char *argv[])
+{
+    char       **p;
+    time_t     t;
+    struct tm  tm;
+\&
+    if (argc != 8) {
+        fprintf(stderr, "Usage: %s yyyy mm dd HH MM SS isdst\[rs]n", argv[0]);
+        exit(EXIT_FAILURE);
+    }
+\&
+    p = &argv[1];
+    tm.tm_year  = atoi(*p++) \- 1900;
+    tm.tm_mon   = atoi(*p++) \- 1;
+    tm.tm_mday  = atoi(*p++);
+    tm.tm_hour  = atoi(*p++);
+    tm.tm_min   = atoi(*p++);
+    tm.tm_sec   = atoi(*p++);
+    tm.tm_isdst = atoi(*p++);
+\&
+    tm.tm_wday = \-1;
+    t = mktime(&tm);
+    if (tm.tm_wday == \-1)
+        err(EXIT_FAILURE, "mktime");
+\&
+    if (is_signed(time_t))
+        printf("%jd\[rs]n", (intmax_t) t);
+    else
+        printf("%ju\[rs]n", (uintmax_t) t);
+\&
+    exit(EXIT_SUCCESS);
+}
+.EE
+.\" SRC END
 .SH SEE ALSO
 .BR date (1),
 .BR gettimeofday (2),