| //go:build linux | |
| package overlayutils // import "github.com/docker/docker/daemon/graphdriver/overlayutils" | |
| import ( | |
| "crypto/rand" | |
| "encoding/base32" | |
| "fmt" | |
| "io" | |
| "os" | |
| "syscall" | |
| "time" | |
| "github.com/containerd/log" | |
| "golang.org/x/sys/unix" | |
| ) | |
| // GenerateID creates a new random string identifier with the given length | |
| func GenerateID(l int, logger *log.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 | |
| } |