From 0a4922f5713bce52abca95784960e62110bd7d76 Mon Sep 17 00:00:00 2001 From: Bryan Frimin Date: Tue, 26 May 2026 09:34:51 -0700 Subject: [PATCH] Share copyFile helper across Unix and Windows updaters Move the duplicated file-copy logic into update/copy.go so both platform installers stay in sync during maintenance. Signed-off-by: Bryan Frimin --- pkg/deviceagent/update/copy.go | 57 +++++++++++++++++++++++ pkg/deviceagent/update/install_unix.go | 37 --------------- pkg/deviceagent/update/install_windows.go | 38 --------------- 3 files changed, 57 insertions(+), 75 deletions(-) create mode 100644 pkg/deviceagent/update/copy.go diff --git a/pkg/deviceagent/update/copy.go b/pkg/deviceagent/update/copy.go new file mode 100644 index 000000000..80c533230 --- /dev/null +++ b/pkg/deviceagent/update/copy.go @@ -0,0 +1,57 @@ +// Copyright (c) 2025-2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +package update + +import ( + "fmt" + "io" + "os" + "path/filepath" +) + +func copyFile(src, dst string) error { + in, err := os.Open(src) + if err != nil { + return fmt.Errorf("cannot open %s: %w", src, err) + } + defer func() { _ = in.Close() }() + + if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil { + return fmt.Errorf("cannot ensure %s: %w", filepath.Dir(dst), err) + } + + out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o755) + if err != nil { + return fmt.Errorf("cannot create %s: %w", dst, err) + } + + if _, err := io.Copy(out, in); err != nil { + _ = out.Close() + _ = os.Remove(dst) + return fmt.Errorf("cannot copy to %s: %w", dst, err) + } + if err := out.Sync(); err != nil { + _ = out.Close() + _ = os.Remove(dst) + return fmt.Errorf("cannot fsync %s: %w", dst, err) + } + + if err := out.Close(); err != nil { + _ = os.Remove(dst) + return fmt.Errorf("cannot close %s: %w", dst, err) + } + + return nil +} diff --git a/pkg/deviceagent/update/install_unix.go b/pkg/deviceagent/update/install_unix.go index 7e9d5d8dc..a62335474 100644 --- a/pkg/deviceagent/update/install_unix.go +++ b/pkg/deviceagent/update/install_unix.go @@ -18,9 +18,7 @@ package update import ( "fmt" - "io" "os" - "path/filepath" ) // replaceBinary replaces the file at dst with src. @@ -61,41 +59,6 @@ func replaceBinary(dst, src string) error { return nil } -func copyFile(src, dst string) error { - in, err := os.Open(src) - if err != nil { - return fmt.Errorf("cannot open %s: %w", src, err) - } - defer func() { _ = in.Close() }() - - if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil { - return fmt.Errorf("cannot ensure %s: %w", filepath.Dir(dst), err) - } - - out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o755) - if err != nil { - return fmt.Errorf("cannot create %s: %w", dst, err) - } - - if _, err := io.Copy(out, in); err != nil { - _ = out.Close() - _ = os.Remove(dst) - return fmt.Errorf("cannot copy to %s: %w", dst, err) - } - if err := out.Sync(); err != nil { - _ = out.Close() - _ = os.Remove(dst) - return fmt.Errorf("cannot fsync %s: %w", dst, err) - } - - if err := out.Close(); err != nil { - _ = os.Remove(dst) - return fmt.Errorf("cannot close %s: %w", dst, err) - } - - return nil -} - // CleanupAfterRestart removes any leftover .old binary from a // previous Windows-style swap. On Unix this is a no-op. func CleanupAfterRestart(_ string) {} diff --git a/pkg/deviceagent/update/install_windows.go b/pkg/deviceagent/update/install_windows.go index ab75e9bff..88c6da331 100644 --- a/pkg/deviceagent/update/install_windows.go +++ b/pkg/deviceagent/update/install_windows.go @@ -19,9 +19,7 @@ package update import ( "errors" "fmt" - "io" "os" - "path/filepath" ) const oldSuffix = ".old" @@ -63,42 +61,6 @@ func replaceBinary(dst, src string) error { return nil } -func copyFile(src, dst string) error { - in, err := os.Open(src) - if err != nil { - return fmt.Errorf("cannot open %s: %w", src, err) - } - defer func() { _ = in.Close() }() - - if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil { - return fmt.Errorf("cannot ensure %s: %w", filepath.Dir(dst), err) - } - - out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o755) - if err != nil { - return fmt.Errorf("cannot create %s: %w", dst, err) - } - - if _, err := io.Copy(out, in); err != nil { - _ = out.Close() - _ = os.Remove(dst) - return fmt.Errorf("cannot copy to %s: %w", dst, err) - } - - if err := out.Sync(); err != nil { - _ = out.Close() - _ = os.Remove(dst) - return fmt.Errorf("cannot fsync %s: %w", dst, err) - } - - if err := out.Close(); err != nil { - _ = os.Remove(dst) - return fmt.Errorf("cannot close %s: %w", dst, err) - } - - return nil -} - // CleanupAfterRestart removes the previous-version binary left behind // by replaceBinary. Best-effort: callers ignore errors, so a still-locked // `.old` is fine and will be retried on the next boot.