randomid.go 2.0 KB

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