//go:build linux // +build linux package overlayutils // import "github.com/docker/docker/daemon/graphdriver/overlayutils" import ( "crypto/rand" "encoding/base32" "fmt" "io" "os" "syscall" "time" "github.com/sirupsen/logrus" "golang.org/x/sys/unix" ) // GenerateID creates a new random string identifier with the given length func GenerateID(l int, logger *logrus.Entry) string { const ( // ensures we backoff for less than 450ms total. Use the following to // select new value, in units of 10ms: // n*(n+1)/2 = d -> n^2 + n - 2d -> n = (sqrt(8d + 1) - 1)/2 maxretries = 9 backoff = time.Millisecond * 10 ) var ( totalBackoff time.Duration count int retries int size = (l*5 + 7) / 8 u = make([]byte, size) ) // TODO: Include time component, counter component, random component for { // This should never block but the read may fail. Because of this, // we just try to read the random number generator until we get // something. This is a very rare condition but may happen. b := time.Duration(retries) * backoff time.Sleep(b) totalBackoff += b n, err := io.ReadFull(rand.Reader, u[count:]) if err != nil { if retryOnError(err) && retries < maxretries { count += n retries++ logger.Errorf("error generating version 4 uuid, retrying: %v", err) continue } // Any other errors represent a system problem. What did someone // do to /dev/urandom? panic(fmt.Errorf("error reading random number generator, retried for %v: %v", totalBackoff.String(), err)) } break } s := base32.StdEncoding.EncodeToString(u) return s[:l] } // retryOnError tries to detect whether or not retrying would be fruitful. func retryOnError(err error) bool { switch err := err.(type) { case *os.PathError: return retryOnError(err.Err) // unpack the target error case syscall.Errno: if err == unix.EPERM { // EPERM represents an entropy pool exhaustion, a condition under // which we backoff and retry. return true } } return false }