github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/daemon/graphdriver/overlayutils/randomid.go (about)

     1  //go:build linux
     2  // +build linux
     3  
     4  package overlayutils // import "github.com/docker/docker/daemon/graphdriver/overlayutils"
     5  
     6  import (
     7  	"crypto/rand"
     8  	"encoding/base32"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  	"syscall"
    13  	"time"
    14  
    15  	"github.com/sirupsen/logrus"
    16  	"golang.org/x/sys/unix"
    17  )
    18  
    19  // GenerateID creates a new random string identifier with the given length
    20  func GenerateID(l int, logger *logrus.Entry) string {
    21  	const (
    22  		// ensures we backoff for less than 450ms total. Use the following to
    23  		// select new value, in units of 10ms:
    24  		// 	n*(n+1)/2 = d -> n^2 + n - 2d -> n = (sqrt(8d + 1) - 1)/2
    25  		maxretries = 9
    26  		backoff    = time.Millisecond * 10
    27  	)
    28  
    29  	var (
    30  		totalBackoff time.Duration
    31  		count        int
    32  		retries      int
    33  		size         = (l*5 + 7) / 8
    34  		u            = make([]byte, size)
    35  	)
    36  	// TODO: Include time component, counter component, random component
    37  
    38  	for {
    39  		// This should never block but the read may fail. Because of this,
    40  		// we just try to read the random number generator until we get
    41  		// something. This is a very rare condition but may happen.
    42  		b := time.Duration(retries) * backoff
    43  		time.Sleep(b)
    44  		totalBackoff += b
    45  
    46  		n, err := io.ReadFull(rand.Reader, u[count:])
    47  		if err != nil {
    48  			if retryOnError(err) && retries < maxretries {
    49  				count += n
    50  				retries++
    51  				logger.Errorf("error generating version 4 uuid, retrying: %v", err)
    52  				continue
    53  			}
    54  
    55  			// Any other errors represent a system problem. What did someone
    56  			// do to /dev/urandom?
    57  			panic(fmt.Errorf("error reading random number generator, retried for %v: %v", totalBackoff.String(), err))
    58  		}
    59  
    60  		break
    61  	}
    62  
    63  	s := base32.StdEncoding.EncodeToString(u)
    64  
    65  	return s[:l]
    66  }
    67  
    68  // retryOnError tries to detect whether or not retrying would be fruitful.
    69  func retryOnError(err error) bool {
    70  	switch err := err.(type) {
    71  	case *os.PathError:
    72  		return retryOnError(err.Err) // unpack the target error
    73  	case syscall.Errno:
    74  		if err == unix.EPERM {
    75  			// EPERM represents an entropy pool exhaustion, a condition under
    76  			// which we backoff and retry.
    77  			return true
    78  		}
    79  	}
    80  
    81  	return false
    82  }