From d06290d0cb50d875daa0e9f53a103cf294fb3ee6 Mon Sep 17 00:00:00 2001 From: Wesley Hershberger Date: Mon, 16 Sep 2024 16:31:21 -0500 Subject: [PATCH 1/2] lxd: Refactor image post Attempt to reduce code duplication. This will allow me to acquire the lock in one place instead of two (next commit). Signed-off-by: Wesley Hershberger --- lxd/images.go | 95 +++++++++++++++++++++++---------------------------- 1 file changed, 42 insertions(+), 53 deletions(-) diff --git a/lxd/images.go b/lxd/images.go index c874423c961c..65cf6e09767c 100644 --- a/lxd/images.go +++ b/lxd/images.go @@ -689,6 +689,8 @@ func getImgPostInfo(s *state.State, r *http.Request, builddir string, project st sha256 := sha256.New() var size int64 + var imageTmpFilename string + var rootfsTmpFilename string if ctype == "multipart/form-data" { // Create a temporary file for the image tarball @@ -699,6 +701,8 @@ func getImgPostInfo(s *state.State, r *http.Request, builddir string, project st defer func() { _ = os.Remove(imageTarf.Name()) }() + imageTmpFilename = imageTarf.Name() + // Parse the POST data _, err = post.Seek(0, io.SeekStart) if err != nil { @@ -750,6 +754,8 @@ func getImgPostInfo(s *state.State, r *http.Request, builddir string, project st defer func() { _ = os.Remove(rootfsTarf.Name()) }() + rootfsTmpFilename = rootfsTarf.Name() + size, err = io.Copy(io.MultiWriter(rootfsTarf, sha256), part) info.Size += size @@ -760,39 +766,6 @@ func getImgPostInfo(s *state.State, r *http.Request, builddir string, project st } info.Filename = part.FileName() - info.Fingerprint = fmt.Sprintf("%x", sha256.Sum(nil)) - - expectedFingerprint := r.Header.Get("X-LXD-fingerprint") - if expectedFingerprint != "" && info.Fingerprint != expectedFingerprint { - err = fmt.Errorf("fingerprints don't match, got %s expected %s", info.Fingerprint, expectedFingerprint) - return nil, err - } - - imageMeta, _, err = getImageMetadata(imageTarf.Name()) - if err != nil { - l.Error("Failed to get image metadata", logger.Ctx{"err": err}) - return nil, err - } - - imgfname := shared.VarPath("images", info.Fingerprint) - err = shared.FileMove(imageTarf.Name(), imgfname) - if err != nil { - l.Error("Failed to move the image tarfile", logger.Ctx{ - "err": err, - "source": imageTarf.Name(), - "dest": imgfname}) - return nil, err - } - - rootfsfname := shared.VarPath("images", info.Fingerprint+".rootfs") - err = shared.FileMove(rootfsTarf.Name(), rootfsfname) - if err != nil { - l.Error("Failed to move the rootfs tarfile", logger.Ctx{ - "err": err, - "source": rootfsTarf.Name(), - "dest": imgfname}) - return nil, err - } } else { _, err = post.Seek(0, io.SeekStart) if err != nil { @@ -808,32 +781,48 @@ func getImgPostInfo(s *state.State, r *http.Request, builddir string, project st info.Size = size info.Filename = r.Header.Get("X-LXD-filename") - info.Fingerprint = fmt.Sprintf("%x", sha256.Sum(nil)) - - expectedFingerprint := r.Header.Get("X-LXD-fingerprint") - if expectedFingerprint != "" && info.Fingerprint != expectedFingerprint { - l.Error("Fingerprints don't match", logger.Ctx{ - "got": info.Fingerprint, - "expected": expectedFingerprint}) - err = fmt.Errorf("fingerprints don't match, got %s expected %s", info.Fingerprint, expectedFingerprint) - return nil, err - } - var imageType string - imageMeta, imageType, err = getImageMetadata(post.Name()) - if err != nil { - l.Error("Failed to get image metadata", logger.Ctx{"err": err}) - return nil, err - } + imageTmpFilename = post.Name() + } + + info.Fingerprint = fmt.Sprintf("%x", sha256.Sum(nil)) + + expectedFingerprint := r.Header.Get("X-LXD-fingerprint") + if expectedFingerprint != "" && info.Fingerprint != expectedFingerprint { + l.Error("Fingerprints don't match", logger.Ctx{ + "got": info.Fingerprint, + "expected": expectedFingerprint}) + err = fmt.Errorf("Fingerprints don't match, got %s expected %s", info.Fingerprint, expectedFingerprint) + return nil, err + } + imageMeta, imageType, err := getImageMetadata(imageTmpFilename) + if err != nil { + l.Error("Failed to get image metadata", logger.Ctx{"err": err}) + return nil, err + } + + if info.Type == "" { info.Type = imageType + } - imgfname := shared.VarPath("images", info.Fingerprint) - err = shared.FileMove(post.Name(), imgfname) + imgfname := shared.VarPath("images", info.Fingerprint) + err = shared.FileMove(imageTmpFilename, imgfname) + if err != nil { + l.Error("Failed to move the image tarfile", logger.Ctx{ + "err": err, + "source": imageTmpFilename, + "dest": imgfname}) + return nil, err + } + + if rootfsTmpFilename != "" { + rootfsfname := shared.VarPath("images", info.Fingerprint+".rootfs") + err = shared.FileMove(rootfsTmpFilename, rootfsfname) if err != nil { - l.Error("Failed to move the tarfile", logger.Ctx{ + l.Error("Failed to move the rootfs tarfile", logger.Ctx{ "err": err, - "source": post.Name(), + "source": rootfsTmpFilename, "dest": imgfname}) return nil, err } From 8a59f7fd1a21f078d6f47cb7b7bfbfd35bd5bb2a Mon Sep 17 00:00:00 2001 From: Wesley Hershberger Date: Mon, 16 Sep 2024 16:37:08 -0500 Subject: [PATCH 2/2] lxd/image: Acquire image lock for uploaded images Fixes #13855 This needs to happen late in the upload process because we need to verify the uploaded image's fingerprint before acquiring a lock. Signed-off-by: Wesley Hershberger --- lxd/images.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lxd/images.go b/lxd/images.go index 65cf6e09767c..32d6a4738633 100644 --- a/lxd/images.go +++ b/lxd/images.go @@ -796,6 +796,13 @@ func getImgPostInfo(s *state.State, r *http.Request, builddir string, project st return nil, err } + unlock, err := imageOperationLock(info.Fingerprint) + if err != nil { + return nil, err + } + + defer unlock() + imageMeta, imageType, err := getImageMetadata(imageTmpFilename) if err != nil { l.Error("Failed to get image metadata", logger.Ctx{"err": err})