randomid.go 2.0 KB

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