randomid.go 1.9 KB

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