🐙

GNU CoreUtils の sleep は infinity が指定できる

2021/11/12に公開

https://twitter.com/mattn_jp/status/1459142544831311880

というのを見つけたので、ほんとかよと思いながらソースを調べた。まず調べる前に

$ sleep info
sleep: invalid time interval ‘info’
Try 'sleep --help' for more information.

なるほど意図的ぽい。

$ sleep inf

どうやら infinfinity の両方が指定可能らしい。CoreUtils のソースを適当に検索してみようと GitHub から検索してみたけど、それっぽいものが引っかからない。

https://github.com/coreutils/coreutils

こりゃ調べないと分からないなと思って git clone した。こういうの気になりだすと、ちゃんと分かるまで気になって眠れなくなる病気です。

スタート地点は coreutils/src/sleep.c

    for (int i = optind; i < argc; i++)
      {
        double s;
        char const *p;
        if (! (xstrtod (argv[i], &p, &s, cl_strtod) || errno == ERANGE)
            /* Nonnegative interval.  */
            || ! (0 <= s)
            /* No extra chars after the number and an optional s,m,h,d char.  */
            || (*p && *(p+1))
            /* Check any suffix char and update S based on the suffix.  */
            || ! apply_suffix (&s, *p))
          {
            error (0, 0, _("invalid time interval %s"), quote (argv[i]));
            ok = false;
          }

        seconds += s;
      }

どうやら xstrtod で引数を解析してる。xstrtod は引数に渡した cl_strtod をコールバック的に渡してるだけ。git submodule の gnulib の中。gnulib/lib/xstrtod.c

DOUBLE                                                                
CL_STRTOD (char const *nptr, char **restrict endptr)                  
{                                                                     
  char *end;                                                          
  DOUBLE d = STRTOD (nptr, &end);                                     
  if (*end)                                                           
    {                                                                 
      int strtod_errno = errno;                                       
      char *c_end;                                                    
      DOUBLE c = C_STRTOD (nptr, &c_end);                             
      if (end < c_end)                                                
        d = c, end = c_end;                                           
      else                                                            
        errno = strtod_errno;                                         
    }                                                                 
  if (endptr)                                                         
    *endptr = end;                                                    
  return d;                                                           
}                                                                     

という事で STRTOD (strtod) が本体。strtod.c の中

/* Check for infinities and NaNs.  */             
else if (c_tolower (*s) == 'i'                    
         && c_tolower (s[1]) == 'n'               
         && c_tolower (s[2]) == 'f')              
  {                                               
    s += 3;                                       
    if (c_tolower (*s) == 'i'                     
        && c_tolower (s[1]) == 'n'                
        && c_tolower (s[2]) == 'i'                
        && c_tolower (s[3]) == 't'                
        && c_tolower (s[4]) == 'y')               
      s += 5;                                     
    num = HUGE_VAL;                               
    errno = saved_errno;                          
  }                                               
else if (c_tolower (*s) == 'n'                    
         && c_tolower (s[1]) == 'a'               
         && c_tolower (s[2]) == 'n')              
  {                                               
    s += 3;                                       
    if (*s == '(')                                
      {                                           
        const char *p = s + 1;                    

なるほど。gnulib の strtodinfinfinity の場合には double の最大の数 HUGE_VAL が得られると。nanNAN を返す様に判定してるけどこの関数では nan が戻り値として判定され、結局エラーになる。

else if (c_tolower (*s) == 'n'                                       
         && c_tolower (s[1]) == 'a'                                  
         && c_tolower (s[2]) == 'n')                                 
  {                                                                  
    s += 3;                                                          
    if (*s == '(')                                                   
      {                                                              
        const char *p = s + 1;                                       
        while (c_isalnum (*p))                                       
          p++;                                                       
        if (*p == ')')                                               
          s = p + 1;                                                 
      }                                                              
                                                                     
    /* If the underlying implementation misparsed the NaN, assume    
       its result is incorrect, and return a NaN.  Normally it's     
       better to use the underlying implementation's result, since a 
       nice implementation populates the bits of the NaN according   
       to interpreting n-char-sequence as a hexadecimal number.  */  
    if (s != end || num == num)                                      
      num = NAN;                                                     
    errno = saved_errno;                                             
  }                                                                  
else                                                                 
  {                                                                 

という事でまとめ。

Discussion