Skip to content

Commit

Permalink
Since POSIX removed rand_r(3), provide fallback (re: af6a32d)
Browse files Browse the repository at this point in the history
As of the referenced commit, the rand_r(3) function is used for
$RANDOM instead of rand(3). This is necessary to keep repeatable
seeded $RANDOM sequences working in the shell -- particularly in
combination with virtual subshells, which require us to save and
restore the intermediate seed value. Only rand_r will allow us to
access and control that seed value so we can save it.

However, POSIX removed rand_r in the 2024 edition, so we cannot
count on operating systems continuing to provide it forever.

src/lib/libast/features/{lib,sys}:
- Add lib test for rand_r, checking if it exists in OS libraries.
  Defines _lib_rand_r as 1 if found.
- Add extern test for rand_r, checking if the extern declaration is
  found in the system headers and emitting one if not. This is
  needed because some operating systems (e.g., NetBSD) keep
  deprecated functions in their libraries for ABI compatibility
  purposes while hiding or removing their header declarations.

src/cmd/ksh93/sh/{init,subshell}.c:
- Add rand_r fallback implementation (nicked from FreeBSD libc) if
  !_lib_rand_r (i.e., rand_r is not found in system libraries).
- Since this is a static function (no need for extern here as it's
  only used in init.c), use a #define to rename it to _ksh_rand_r
  to avoid a conflict with the extern declaration.
- Eliminate pointless srand(3) calls. It seeds rand(3) sequences;
  it's irrelevant to rand_r(3) because we give it a pointer to our
  own seed variable! All we need to do is assign to that.
- Eliminate an init-time loop that calls rand(3) to check whether
  it returns large values. This is not inefficient, and now invalid
  in case we provide our own rand_r whose range may differ from the
  OS's rand. Determine this at compile time instead, using the
  RAND_MAX macro (which we redefine when providing our own rand_r).

Resolves: #806
  • Loading branch information
McDutchie committed Jan 7, 2025
1 parent 3c6d352 commit 1b6157e
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 16 deletions.
56 changes: 43 additions & 13 deletions src/cmd/ksh93/sh/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,6 @@ static Init_t *nv_init(void);
#if SHOPT_STATS
static void stat_init(void);
#endif
static int rand_shift;

/*
* Exception callback routine for stk(3) and sh_*alloc wrappers.
Expand Down Expand Up @@ -624,6 +623,39 @@ static Sfdouble_t nget_seconds(Namval_t* np, Namfun_t *fp)
return dtime(&tp) - offset;
}

#if !_lib_rand_r
#undef RAND_MAX
#define RAND_MAX 0x7fffffff
#define rand_r _ksh_rand_r
/*
* rand_r(3) fallback nicked from FreeBSD libc.
* License: BSD 3-clause. See the COPYRIGHT file.
* Not to be confused with actual randomness!
*
* Compute x = (7^5 * x) mod (2^31 - 1)
* without overflowing 31 bits:
* (2^31 - 1) = 127773 * (7^5) + 2836
* From "Random number generators: good ones are hard to find",
* Park and Miller, Communications of the ACM, vol. 31, no. 10,
* October 1988, p. 1195.
*/
static int rand_r(unsigned int *seed)
{
long hi, lo, x;
/* Transform to [1, 0x7ffffffe] range. */
x = (*seed % 0x7ffffffe) + 1;
hi = x / 127773;
lo = x % 127773;
x = 16807 * lo - 2836 * hi;
if (x < 0)
x += 0x7fffffff;
/* Transform to [0, 0x7ffffffd] range. */
x--;
*seed = x;
return x;
}
#endif /* !_lib_rand_r */

/*
* These four functions are used to get and set the RANDOM variable
*/
Expand All @@ -644,7 +676,7 @@ static void put_rand(Namval_t* np,const char *val,int flags,Namfun_t *fp)
n = *(Sfdouble_t*)val;
else
n = sh_arith(val);
srand(rp->rand_seed = (unsigned int)n);
rp->rand_seed = (unsigned int)n;
rp->rand_last = -1;
if(!np->nvalue)
np->nvalue = &rp->rand_last;
Expand All @@ -662,7 +694,14 @@ static Sfdouble_t nget_rand(Namval_t* np, Namfun_t *fp)
int32_t last = *lp;
sh_save_rand_seed(rp, 1);
do
cur = (rand_r(&rp->rand_seed)>>rand_shift)&RANDMASK;
#if RAND_MAX > (RANDMASK << 3)
/* don't use lower bits when rand_r() generates large numbers */
cur = (rand_r(&rp->rand_seed) >> 3) & RANDMASK;
#elif RAND_MAX > RANDMASK
cur = rand_r(&rp->rand_seed) & RANDMASK;
#else
cur = rand_r(&rp->rand_seed);
#endif
while(cur==last);
*lp = cur;
return (Sfdouble_t)cur;
Expand All @@ -676,7 +715,7 @@ static char* get_rand(Namval_t* np, Namfun_t *fp)

void sh_reseed_rand(struct rand *rp)
{
srand(rp->rand_seed = arc4random());
rp->rand_seed = arc4random();
rp->rand_last = -1;
}

Expand Down Expand Up @@ -1254,15 +1293,6 @@ Shell_t *sh_init(int argc,char *argv[], Shinit_f userinit)
error_info.catalog = e_dict;
sh.cpipe[0] = -1;
sh.coutpipe = -1;
for(n=0;n < 10; n++)
{
/* don't use lower bits when rand() generates large numbers */
if(rand() > RANDMASK)
{
rand_shift = 3;
break;
}
}
sh_ioinit();
/* initialize signal handling */
sh_siginit();
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/ksh93/sh/subshell.c
Original file line number Diff line number Diff line change
Expand Up @@ -886,7 +886,7 @@ Sfio_t *sh_subshell(Shnode_t *t, volatile int flags, int comsub)
rp = (struct rand*)RANDNOD->nvfun;
if(sp->rand_state)
{
srand(rp->rand_seed = sp->rand_seed);
rp->rand_seed = sp->rand_seed;
rp->rand_last = sp->rand_last;
}
/* restore $SRANDOM upper bound */
Expand Down
1 change: 1 addition & 0 deletions src/lib/libast/features/lib
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ lib glob,iswblank,iswctype,killpg,link,localeconv,madvise
lib mbtowc,mbrtowc,memalign,memdup
lib mkdir,mkfifo,mktemp,mktime
lib mount,opendir,openat,pathconf
lib rand_r
lib readlink,remove,rename,rewinddir,rmdir,setlocale
lib setpgrp,setpgrp2,setreuid,setuid
lib socketpair
Expand Down
3 changes: 1 addition & 2 deletions src/lib/libast/features/sys
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ extern pause int (void)
extern pipe int (int[])
extern pvalloc void* (size_t)
extern qsort void (void*, size_t, size_t, int(*)(const void*, const void*))
extern rand int (void)
extern rand_r int (unsigned int*)
extern read ssize_t (int, void*, size_t)
extern realloc void* (void*, size_t)
extern realpath char* (const char*, char*)
Expand All @@ -187,7 +187,6 @@ extern setpgid int (pid_t, pid_t)
extern setsid pid_t (void)
extern setuid int (uid_t)
extern sleep unsigned (unsigned int)
extern srand void (unsigned int)
extern strcasecmp int (const char*, const char*)
extern strcoll int (const char*, const char*)
extern strcspn size_t (const char*, const char*)
Expand Down

0 comments on commit 1b6157e

Please sign in to comment.