2021-01-13 16:18:51 +03:00
|
|
|
// Package updater provides an updater for AdGuardHome.
|
|
|
|
package updater
|
2020-07-20 21:14:07 +03:00
|
|
|
|
|
|
|
import (
|
2020-07-21 18:50:35 +03:00
|
|
|
"archive/tar"
|
|
|
|
"archive/zip"
|
|
|
|
"compress/gzip"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
2024-11-05 19:21:29 +03:00
|
|
|
"io/fs"
|
2020-07-21 18:50:35 +03:00
|
|
|
"net/http"
|
2020-07-20 21:14:07 +03:00
|
|
|
"os"
|
2020-07-21 18:50:35 +03:00
|
|
|
"os/exec"
|
2020-07-20 21:14:07 +03:00
|
|
|
"path/filepath"
|
2020-07-21 18:50:35 +03:00
|
|
|
"strings"
|
2021-01-13 16:18:51 +03:00
|
|
|
"sync"
|
2020-07-21 12:17:23 +03:00
|
|
|
"time"
|
2020-07-21 18:50:35 +03:00
|
|
|
|
2024-10-02 21:00:15 +03:00
|
|
|
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
2021-01-13 16:18:51 +03:00
|
|
|
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
2021-05-24 17:28:11 +03:00
|
|
|
"github.com/AdguardTeam/golibs/errors"
|
2023-10-06 16:45:48 +03:00
|
|
|
"github.com/AdguardTeam/golibs/ioutil"
|
2020-07-21 18:50:35 +03:00
|
|
|
"github.com/AdguardTeam/golibs/log"
|
2020-07-20 21:14:07 +03:00
|
|
|
)
|
|
|
|
|
2021-01-13 16:18:51 +03:00
|
|
|
// Updater is the AdGuard Home updater.
|
2020-07-20 21:14:07 +03:00
|
|
|
type Updater struct {
|
2021-01-13 16:18:51 +03:00
|
|
|
client *http.Client
|
2020-07-20 21:14:07 +03:00
|
|
|
|
2021-01-13 16:18:51 +03:00
|
|
|
version string
|
|
|
|
channel string
|
|
|
|
goarch string
|
|
|
|
goos string
|
|
|
|
goarm string
|
|
|
|
gomips string
|
|
|
|
|
|
|
|
workDir string
|
|
|
|
confName string
|
2023-09-21 17:07:57 +03:00
|
|
|
execPath string
|
2021-01-13 16:18:51 +03:00
|
|
|
versionCheckURL string
|
|
|
|
|
|
|
|
// mu protects all fields below.
|
|
|
|
mu *sync.RWMutex
|
|
|
|
|
|
|
|
// TODO(a.garipov): See if all of these fields actually have to be in
|
|
|
|
// this struct.
|
2020-07-21 18:50:35 +03:00
|
|
|
currentExeName string // current binary executable
|
2021-01-13 16:18:51 +03:00
|
|
|
updateDir string // "workDir/agh-update-v0.103.0"
|
|
|
|
packageName string // "workDir/agh-update-v0.103.0/pkg_name.tar.gz"
|
|
|
|
backupDir string // "workDir/agh-backup"
|
|
|
|
backupExeName string // "workDir/agh-backup/AdGuardHome[.exe]"
|
|
|
|
updateExeName string // "workDir/agh-update-v0.103.0/AdGuardHome[.exe]"
|
2020-07-21 18:50:35 +03:00
|
|
|
unpackedFiles []string
|
2020-07-21 12:17:23 +03:00
|
|
|
|
2021-01-13 16:18:51 +03:00
|
|
|
newVersion string
|
|
|
|
packageURL string
|
|
|
|
|
|
|
|
// Cached fields to prevent too many API requests.
|
|
|
|
prevCheckError error
|
|
|
|
prevCheckTime time.Time
|
|
|
|
prevCheckResult VersionInfo
|
2020-07-20 21:14:07 +03:00
|
|
|
}
|
|
|
|
|
2021-01-13 16:18:51 +03:00
|
|
|
// Config is the AdGuard Home updater configuration.
|
2020-07-22 20:27:20 +03:00
|
|
|
type Config struct {
|
|
|
|
Client *http.Client
|
|
|
|
|
2021-01-13 16:18:51 +03:00
|
|
|
Version string
|
|
|
|
Channel string
|
|
|
|
GOARCH string
|
|
|
|
GOOS string
|
|
|
|
GOARM string
|
|
|
|
GOMIPS string
|
|
|
|
|
|
|
|
// ConfName is the name of the current configuration file. Typically,
|
|
|
|
// "AdGuardHome.yaml".
|
|
|
|
ConfName string
|
2023-09-21 17:07:57 +03:00
|
|
|
|
2021-01-13 16:18:51 +03:00
|
|
|
// WorkDir is the working directory that is used for temporary files.
|
|
|
|
WorkDir string
|
2023-09-21 17:07:57 +03:00
|
|
|
|
|
|
|
// ExecPath is path to the executable file.
|
|
|
|
ExecPath string
|
|
|
|
|
|
|
|
// VersionCheckURL is url to the latest version announcement.
|
|
|
|
VersionCheckURL string
|
2020-07-22 20:27:20 +03:00
|
|
|
}
|
|
|
|
|
2021-01-13 16:18:51 +03:00
|
|
|
// NewUpdater creates a new Updater.
|
|
|
|
func NewUpdater(conf *Config) *Updater {
|
2020-07-22 20:27:20 +03:00
|
|
|
return &Updater{
|
2021-01-13 16:18:51 +03:00
|
|
|
client: conf.Client,
|
|
|
|
|
|
|
|
version: conf.Version,
|
|
|
|
channel: conf.Channel,
|
|
|
|
goarch: conf.GOARCH,
|
|
|
|
goos: conf.GOOS,
|
|
|
|
goarm: conf.GOARM,
|
|
|
|
gomips: conf.GOMIPS,
|
|
|
|
|
|
|
|
confName: conf.ConfName,
|
|
|
|
workDir: conf.WorkDir,
|
2023-09-21 17:07:57 +03:00
|
|
|
execPath: conf.ExecPath,
|
|
|
|
versionCheckURL: conf.VersionCheckURL,
|
2021-01-13 16:18:51 +03:00
|
|
|
|
|
|
|
mu: &sync.RWMutex{},
|
2020-07-20 21:14:07 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-09 13:38:31 +03:00
|
|
|
// Update performs the auto-update. It returns an error if the update failed.
|
|
|
|
// If firstRun is true, it assumes the configuration file doesn't exist.
|
|
|
|
func (u *Updater) Update(firstRun bool) (err error) {
|
2021-01-13 16:18:51 +03:00
|
|
|
u.mu.Lock()
|
|
|
|
defer u.mu.Unlock()
|
|
|
|
|
2022-06-09 17:47:05 +03:00
|
|
|
log.Info("updater: updating")
|
2023-01-09 13:38:31 +03:00
|
|
|
defer func() {
|
|
|
|
if err != nil {
|
2023-09-11 16:42:41 +03:00
|
|
|
log.Info("updater: failed")
|
2023-01-09 13:38:31 +03:00
|
|
|
} else {
|
2023-09-11 16:42:41 +03:00
|
|
|
log.Info("updater: finished successfully")
|
2023-01-09 13:38:31 +03:00
|
|
|
}
|
|
|
|
}()
|
2022-06-09 17:47:05 +03:00
|
|
|
|
2023-09-21 17:07:57 +03:00
|
|
|
err = u.prepare()
|
2020-07-21 18:50:35 +03:00
|
|
|
if err != nil {
|
2023-01-09 13:38:31 +03:00
|
|
|
return fmt.Errorf("preparing: %w", err)
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
defer u.clean()
|
|
|
|
|
2023-01-09 13:38:31 +03:00
|
|
|
err = u.downloadPackageFile()
|
2020-07-21 18:50:35 +03:00
|
|
|
if err != nil {
|
2023-01-09 13:38:31 +03:00
|
|
|
return fmt.Errorf("downloading package file: %w", err)
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
err = u.unpack()
|
|
|
|
if err != nil {
|
2023-01-09 13:38:31 +03:00
|
|
|
return fmt.Errorf("unpacking: %w", err)
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
|
|
|
|
2023-01-09 13:38:31 +03:00
|
|
|
if !firstRun {
|
|
|
|
err = u.check()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("checking config: %w", err)
|
|
|
|
}
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
|
|
|
|
2023-01-09 13:38:31 +03:00
|
|
|
err = u.backup(firstRun)
|
2020-07-21 18:50:35 +03:00
|
|
|
if err != nil {
|
2023-01-09 13:38:31 +03:00
|
|
|
return fmt.Errorf("making backup: %w", err)
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
err = u.replace()
|
|
|
|
if err != nil {
|
2023-01-09 13:38:31 +03:00
|
|
|
return fmt.Errorf("replacing: %w", err)
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-01-13 16:18:51 +03:00
|
|
|
// NewVersion returns the available new version.
|
|
|
|
func (u *Updater) NewVersion() (nv string) {
|
|
|
|
u.mu.RLock()
|
|
|
|
defer u.mu.RUnlock()
|
|
|
|
|
|
|
|
return u.newVersion
|
|
|
|
}
|
|
|
|
|
|
|
|
// VersionCheckURL returns the version check URL.
|
|
|
|
func (u *Updater) VersionCheckURL() (vcu string) {
|
|
|
|
u.mu.RLock()
|
|
|
|
defer u.mu.RUnlock()
|
|
|
|
|
|
|
|
return u.versionCheckURL
|
|
|
|
}
|
|
|
|
|
2022-07-07 19:49:47 +03:00
|
|
|
// prepare fills all necessary fields in Updater object.
|
2023-09-21 17:07:57 +03:00
|
|
|
func (u *Updater) prepare() (err error) {
|
2021-01-13 16:18:51 +03:00
|
|
|
u.updateDir = filepath.Join(u.workDir, fmt.Sprintf("agh-update-%s", u.newVersion))
|
2020-07-21 18:50:35 +03:00
|
|
|
|
2021-01-13 16:18:51 +03:00
|
|
|
_, pkgNameOnly := filepath.Split(u.packageURL)
|
|
|
|
if pkgNameOnly == "" {
|
2022-12-28 15:14:08 +03:00
|
|
|
return fmt.Errorf("invalid PackageURL: %q", u.packageURL)
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
2021-01-13 16:18:51 +03:00
|
|
|
|
2020-07-21 18:50:35 +03:00
|
|
|
u.packageName = filepath.Join(u.updateDir, pkgNameOnly)
|
2021-01-13 16:18:51 +03:00
|
|
|
u.backupDir = filepath.Join(u.workDir, "agh-backup")
|
2020-07-21 18:50:35 +03:00
|
|
|
|
2022-07-07 19:49:47 +03:00
|
|
|
updateExeName := "AdGuardHome"
|
2021-01-13 16:18:51 +03:00
|
|
|
if u.goos == "windows" {
|
2022-07-07 19:49:47 +03:00
|
|
|
updateExeName = "AdGuardHome.exe"
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
|
|
|
|
2023-09-21 17:07:57 +03:00
|
|
|
u.backupExeName = filepath.Join(u.backupDir, filepath.Base(u.execPath))
|
2022-07-07 19:49:47 +03:00
|
|
|
u.updateExeName = filepath.Join(u.updateDir, updateExeName)
|
2020-07-22 14:20:14 +03:00
|
|
|
|
2022-06-09 17:47:05 +03:00
|
|
|
log.Debug(
|
|
|
|
"updater: updating from %s to %s using url: %s",
|
|
|
|
version.Version(),
|
|
|
|
u.newVersion,
|
|
|
|
u.packageURL,
|
|
|
|
)
|
2020-07-22 14:20:14 +03:00
|
|
|
|
2023-09-21 17:07:57 +03:00
|
|
|
u.currentExeName = u.execPath
|
2021-03-15 14:19:04 +03:00
|
|
|
_, err = os.Stat(u.currentExeName)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("checking %q: %w", u.currentExeName, err)
|
2020-07-22 14:20:14 +03:00
|
|
|
}
|
2021-01-13 16:18:51 +03:00
|
|
|
|
2020-07-21 18:50:35 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-01-09 13:38:31 +03:00
|
|
|
// unpack extracts the files from the downloaded archive.
|
2020-07-21 18:50:35 +03:00
|
|
|
func (u *Updater) unpack() error {
|
|
|
|
var err error
|
2021-01-13 16:18:51 +03:00
|
|
|
_, pkgNameOnly := filepath.Split(u.packageURL)
|
2020-07-21 18:50:35 +03:00
|
|
|
|
2022-06-09 17:47:05 +03:00
|
|
|
log.Debug("updater: unpacking package")
|
2020-07-21 18:50:35 +03:00
|
|
|
if strings.HasSuffix(pkgNameOnly, ".zip") {
|
|
|
|
u.unpackedFiles, err = zipFileUnpack(u.packageName, u.updateDir)
|
|
|
|
if err != nil {
|
2020-11-05 15:20:57 +03:00
|
|
|
return fmt.Errorf(".zip unpack failed: %w", err)
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
} else if strings.HasSuffix(pkgNameOnly, ".tar.gz") {
|
|
|
|
u.unpackedFiles, err = tarGzFileUnpack(u.packageName, u.updateDir)
|
|
|
|
if err != nil {
|
2020-11-05 15:20:57 +03:00
|
|
|
return fmt.Errorf(".tar.gz unpack failed: %w", err)
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("unknown package extension")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-01-09 13:38:31 +03:00
|
|
|
// check returns an error if the configuration file couldn't be used with the
|
|
|
|
// version of AdGuard Home just downloaded.
|
2023-09-11 16:42:41 +03:00
|
|
|
func (u *Updater) check() (err error) {
|
2020-07-21 18:50:35 +03:00
|
|
|
log.Debug("updater: checking configuration")
|
2023-01-09 13:38:31 +03:00
|
|
|
|
2024-11-05 19:21:29 +03:00
|
|
|
err = copyFile(u.confName, filepath.Join(u.updateDir, "AdGuardHome.yaml"), aghos.DefaultPermFile)
|
2020-07-21 18:50:35 +03:00
|
|
|
if err != nil {
|
2020-11-05 15:20:57 +03:00
|
|
|
return fmt.Errorf("copyFile() failed: %w", err)
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
2023-01-09 13:38:31 +03:00
|
|
|
|
2023-09-11 16:42:41 +03:00
|
|
|
const format = "executing configuration check command: %w %d:\n" +
|
|
|
|
"below is the output of configuration check:\n" +
|
|
|
|
"%s" +
|
|
|
|
"end of the output"
|
|
|
|
|
2020-07-21 18:50:35 +03:00
|
|
|
cmd := exec.Command(u.updateExeName, "--check-config")
|
2023-09-11 16:42:41 +03:00
|
|
|
out, err := cmd.CombinedOutput()
|
|
|
|
code := cmd.ProcessState.ExitCode()
|
|
|
|
if err != nil || code != 0 {
|
|
|
|
return fmt.Errorf(format, err, code, out)
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
2023-01-09 13:38:31 +03:00
|
|
|
|
2020-07-21 18:50:35 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-01-09 13:38:31 +03:00
|
|
|
// backup makes a backup of the current configuration and supporting files. It
|
|
|
|
// ignores the configuration file if firstRun is true.
|
|
|
|
func (u *Updater) backup(firstRun bool) (err error) {
|
2022-06-09 17:47:05 +03:00
|
|
|
log.Debug("updater: backing up current configuration")
|
2024-10-29 14:28:59 +03:00
|
|
|
_ = aghos.Mkdir(u.backupDir, aghos.DefaultPermDir)
|
2023-01-09 13:38:31 +03:00
|
|
|
if !firstRun {
|
2024-11-05 19:21:29 +03:00
|
|
|
err = copyFile(u.confName, filepath.Join(u.backupDir, "AdGuardHome.yaml"), aghos.DefaultPermFile)
|
2023-01-09 13:38:31 +03:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("copyFile() failed: %w", err)
|
|
|
|
}
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
|
|
|
|
2021-01-13 16:18:51 +03:00
|
|
|
wd := u.workDir
|
|
|
|
err = copySupportingFiles(u.unpackedFiles, wd, u.backupDir)
|
2020-07-21 18:50:35 +03:00
|
|
|
if err != nil {
|
2023-02-27 17:07:31 +03:00
|
|
|
return fmt.Errorf("copySupportingFiles(%s, %s) failed: %w", wd, u.backupDir, err)
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-01-09 13:38:31 +03:00
|
|
|
// replace moves the current executable with the updated one and also copies the
|
|
|
|
// supporting files.
|
2020-07-21 18:50:35 +03:00
|
|
|
func (u *Updater) replace() error {
|
2021-01-13 16:18:51 +03:00
|
|
|
err := copySupportingFiles(u.unpackedFiles, u.updateDir, u.workDir)
|
2020-07-21 18:50:35 +03:00
|
|
|
if err != nil {
|
2023-02-27 17:07:31 +03:00
|
|
|
return fmt.Errorf("copySupportingFiles(%s, %s) failed: %w", u.updateDir, u.workDir, err)
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
|
|
|
|
2022-06-09 17:47:05 +03:00
|
|
|
log.Debug("updater: renaming: %s to %s", u.currentExeName, u.backupExeName)
|
2020-07-21 18:50:35 +03:00
|
|
|
err = os.Rename(u.currentExeName, u.backupExeName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-01-13 16:18:51 +03:00
|
|
|
if u.goos == "windows" {
|
2024-11-05 19:21:29 +03:00
|
|
|
// Use copy, since renaming fails with "File in use" error.
|
|
|
|
err = copyFile(u.updateExeName, u.currentExeName, aghos.DefaultPermExe)
|
2020-07-21 18:50:35 +03:00
|
|
|
} else {
|
|
|
|
err = os.Rename(u.updateExeName, u.currentExeName)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-01-13 16:18:51 +03:00
|
|
|
|
2022-06-09 17:47:05 +03:00
|
|
|
log.Debug("updater: renamed: %s to %s", u.updateExeName, u.currentExeName)
|
2021-01-13 16:18:51 +03:00
|
|
|
|
2020-07-21 18:50:35 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-01-09 13:38:31 +03:00
|
|
|
// clean removes the temporary directory itself and all it's contents.
|
2020-07-21 18:50:35 +03:00
|
|
|
func (u *Updater) clean() {
|
|
|
|
_ = os.RemoveAll(u.updateDir)
|
|
|
|
}
|
|
|
|
|
2024-02-12 18:45:51 +03:00
|
|
|
// MaxPackageFileSize is a maximum package file length in bytes. The largest
|
2020-11-23 14:14:08 +03:00
|
|
|
// package whose size is limited by this constant currently has the size of
|
|
|
|
// approximately 9 MiB.
|
2023-03-01 14:35:27 +03:00
|
|
|
const MaxPackageFileSize = 32 * 1024 * 1024
|
2020-11-23 14:14:08 +03:00
|
|
|
|
2020-07-21 18:50:35 +03:00
|
|
|
// Download package file and save it to disk
|
2023-01-09 13:38:31 +03:00
|
|
|
func (u *Updater) downloadPackageFile() (err error) {
|
2021-05-24 17:28:11 +03:00
|
|
|
var resp *http.Response
|
2023-01-09 13:38:31 +03:00
|
|
|
resp, err = u.client.Get(u.packageURL)
|
2020-07-21 18:50:35 +03:00
|
|
|
if err != nil {
|
2020-11-05 15:20:57 +03:00
|
|
|
return fmt.Errorf("http request failed: %w", err)
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
2021-05-24 17:28:11 +03:00
|
|
|
defer func() { err = errors.WithDeferred(err, resp.Body.Close()) }()
|
2020-11-23 14:14:08 +03:00
|
|
|
|
2023-10-06 16:45:48 +03:00
|
|
|
r := ioutil.LimitReader(resp.Body, MaxPackageFileSize)
|
2020-07-21 18:50:35 +03:00
|
|
|
|
2022-06-09 17:47:05 +03:00
|
|
|
log.Debug("updater: reading http body")
|
2020-11-23 14:14:08 +03:00
|
|
|
// This use of ReadAll is now safe, because we limited body's Reader.
|
2021-05-24 17:28:11 +03:00
|
|
|
body, err := io.ReadAll(r)
|
2020-07-21 18:50:35 +03:00
|
|
|
if err != nil {
|
2021-05-21 14:55:42 +03:00
|
|
|
return fmt.Errorf("io.ReadAll() failed: %w", err)
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
|
|
|
|
2024-10-29 14:28:59 +03:00
|
|
|
_ = aghos.Mkdir(u.updateDir, aghos.DefaultPermDir)
|
2020-07-21 18:50:35 +03:00
|
|
|
|
|
|
|
log.Debug("updater: saving package to file")
|
2024-10-29 14:28:59 +03:00
|
|
|
err = aghos.WriteFile(u.packageName, body, aghos.DefaultPermFile)
|
2020-07-21 18:50:35 +03:00
|
|
|
if err != nil {
|
2024-10-29 14:28:59 +03:00
|
|
|
return fmt.Errorf("writing package file: %w", err)
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-05-24 17:28:11 +03:00
|
|
|
func tarGzFileUnpackOne(outDir string, tr *tar.Reader, hdr *tar.Header) (name string, err error) {
|
|
|
|
name = filepath.Base(hdr.Name)
|
|
|
|
if name == "" {
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
outputName := filepath.Join(outDir, name)
|
|
|
|
|
|
|
|
if hdr.Typeflag == tar.TypeDir {
|
|
|
|
if name == "AdGuardHome" {
|
|
|
|
// Top-level AdGuardHome/. Skip it.
|
|
|
|
//
|
2024-10-29 14:28:59 +03:00
|
|
|
// TODO(a.garipov): This whole package needs to be rewritten and
|
|
|
|
// covered in more integration tests. It has weird assumptions and
|
|
|
|
// file mode issues.
|
2021-05-24 17:28:11 +03:00
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
2024-10-29 14:28:59 +03:00
|
|
|
err = aghos.Mkdir(outputName, os.FileMode(hdr.Mode&0o755))
|
2021-05-24 17:28:11 +03:00
|
|
|
if err != nil && !errors.Is(err, os.ErrExist) {
|
2024-10-29 14:28:59 +03:00
|
|
|
return "", fmt.Errorf("creating directory %q: %w", outputName, err)
|
2021-05-24 17:28:11 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
log.Debug("updater: created directory %q", outputName)
|
|
|
|
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if hdr.Typeflag != tar.TypeReg {
|
2022-06-09 17:47:05 +03:00
|
|
|
log.Info("updater: %s: unknown file type %d, skipping", name, hdr.Typeflag)
|
2021-05-24 17:28:11 +03:00
|
|
|
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var wc io.WriteCloser
|
2024-10-29 14:28:59 +03:00
|
|
|
wc, err = aghos.OpenFile(
|
2021-05-24 17:28:11 +03:00
|
|
|
outputName,
|
|
|
|
os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
|
2021-05-28 18:45:11 +03:00
|
|
|
os.FileMode(hdr.Mode&0o755),
|
2021-05-24 17:28:11 +03:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("os.OpenFile(%s): %w", outputName, err)
|
|
|
|
}
|
|
|
|
defer func() { err = errors.WithDeferred(err, wc.Close()) }()
|
|
|
|
|
|
|
|
_, err = io.Copy(wc, tr)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("io.Copy(): %w", err)
|
|
|
|
}
|
|
|
|
|
2022-06-09 17:47:05 +03:00
|
|
|
log.Debug("updater: created file %q", outputName)
|
2021-05-24 17:28:11 +03:00
|
|
|
|
|
|
|
return name, nil
|
|
|
|
}
|
|
|
|
|
2020-07-21 18:50:35 +03:00
|
|
|
// Unpack all files from .tar.gz file to the specified directory
|
|
|
|
// Existing files are overwritten
|
2021-05-24 17:28:11 +03:00
|
|
|
// All files are created inside outDir, subdirectories are not created
|
2020-07-21 18:50:35 +03:00
|
|
|
// Return the list of files (not directories) written
|
2021-05-24 17:28:11 +03:00
|
|
|
func tarGzFileUnpack(tarfile, outDir string) (files []string, err error) {
|
2020-07-21 18:50:35 +03:00
|
|
|
f, err := os.Open(tarfile)
|
|
|
|
if err != nil {
|
2020-11-05 15:20:57 +03:00
|
|
|
return nil, fmt.Errorf("os.Open(): %w", err)
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
2021-05-24 17:28:11 +03:00
|
|
|
defer func() { err = errors.WithDeferred(err, f.Close()) }()
|
2020-07-21 18:50:35 +03:00
|
|
|
|
|
|
|
gzReader, err := gzip.NewReader(f)
|
|
|
|
if err != nil {
|
2020-11-05 15:20:57 +03:00
|
|
|
return nil, fmt.Errorf("gzip.NewReader(): %w", err)
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
2021-05-24 17:28:11 +03:00
|
|
|
defer func() { err = errors.WithDeferred(err, gzReader.Close()) }()
|
2020-07-21 18:50:35 +03:00
|
|
|
|
|
|
|
tarReader := tar.NewReader(gzReader)
|
|
|
|
for {
|
2021-05-24 17:28:11 +03:00
|
|
|
var hdr *tar.Header
|
|
|
|
hdr, err = tarReader.Next()
|
|
|
|
if errors.Is(err, io.EOF) {
|
|
|
|
err = nil
|
|
|
|
|
2020-07-21 18:50:35 +03:00
|
|
|
break
|
2021-05-24 17:28:11 +03:00
|
|
|
} else if err != nil {
|
|
|
|
err = fmt.Errorf("tarReader.Next(): %w", err)
|
2020-07-21 18:50:35 +03:00
|
|
|
|
2021-05-24 17:28:11 +03:00
|
|
|
break
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
|
|
|
|
2021-05-24 17:28:11 +03:00
|
|
|
var name string
|
|
|
|
name, err = tarGzFileUnpackOne(outDir, tarReader, hdr)
|
2020-07-21 18:50:35 +03:00
|
|
|
|
2021-05-24 17:28:11 +03:00
|
|
|
if name != "" {
|
|
|
|
files = append(files, name)
|
|
|
|
}
|
|
|
|
}
|
2021-02-02 20:32:44 +03:00
|
|
|
|
2021-05-24 17:28:11 +03:00
|
|
|
return files, err
|
|
|
|
}
|
2021-03-15 14:19:04 +03:00
|
|
|
|
2021-05-24 17:28:11 +03:00
|
|
|
func zipFileUnpackOne(outDir string, zf *zip.File) (name string, err error) {
|
|
|
|
var rc io.ReadCloser
|
|
|
|
rc, err = zf.Open()
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("zip file Open(): %w", err)
|
|
|
|
}
|
|
|
|
defer func() { err = errors.WithDeferred(err, rc.Close()) }()
|
2021-03-15 14:19:04 +03:00
|
|
|
|
2021-05-24 17:28:11 +03:00
|
|
|
fi := zf.FileInfo()
|
|
|
|
name = fi.Name()
|
|
|
|
if name == "" {
|
|
|
|
return "", nil
|
|
|
|
}
|
2021-03-15 14:19:04 +03:00
|
|
|
|
2021-05-24 17:28:11 +03:00
|
|
|
outputName := filepath.Join(outDir, name)
|
|
|
|
if fi.IsDir() {
|
|
|
|
if name == "AdGuardHome" {
|
|
|
|
// Top-level AdGuardHome/. Skip it.
|
|
|
|
//
|
2024-10-29 14:28:59 +03:00
|
|
|
// TODO(a.garipov): See the similar todo in tarGzFileUnpack.
|
2021-05-24 17:28:11 +03:00
|
|
|
return "", nil
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
|
|
|
|
2024-10-29 14:28:59 +03:00
|
|
|
err = aghos.Mkdir(outputName, fi.Mode())
|
2021-05-24 17:28:11 +03:00
|
|
|
if err != nil && !errors.Is(err, os.ErrExist) {
|
2024-10-29 14:28:59 +03:00
|
|
|
return "", fmt.Errorf("creating directory %q: %w", outputName, err)
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
|
|
|
|
2022-06-09 17:47:05 +03:00
|
|
|
log.Debug("updater: created directory %q", outputName)
|
2021-05-24 17:28:11 +03:00
|
|
|
|
|
|
|
return "", nil
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
|
|
|
|
2021-05-24 17:28:11 +03:00
|
|
|
var wc io.WriteCloser
|
2024-10-29 14:28:59 +03:00
|
|
|
wc, err = aghos.OpenFile(outputName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fi.Mode())
|
2021-05-24 17:28:11 +03:00
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("os.OpenFile(): %w", err)
|
|
|
|
}
|
|
|
|
defer func() { err = errors.WithDeferred(err, wc.Close()) }()
|
|
|
|
|
|
|
|
_, err = io.Copy(wc, rc)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("io.Copy(): %w", err)
|
|
|
|
}
|
|
|
|
|
2022-06-09 17:47:05 +03:00
|
|
|
log.Debug("updater: created file %q", outputName)
|
2021-05-24 17:28:11 +03:00
|
|
|
|
|
|
|
return name, nil
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Unpack all files from .zip file to the specified directory
|
|
|
|
// Existing files are overwritten
|
2021-05-24 17:28:11 +03:00
|
|
|
// All files are created inside 'outDir', subdirectories are not created
|
2020-07-21 18:50:35 +03:00
|
|
|
// Return the list of files (not directories) written
|
2021-05-24 17:28:11 +03:00
|
|
|
func zipFileUnpack(zipfile, outDir string) (files []string, err error) {
|
|
|
|
zrc, err := zip.OpenReader(zipfile)
|
2020-07-21 18:50:35 +03:00
|
|
|
if err != nil {
|
2020-11-05 15:20:57 +03:00
|
|
|
return nil, fmt.Errorf("zip.OpenReader(): %w", err)
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
2021-05-24 17:28:11 +03:00
|
|
|
defer func() { err = errors.WithDeferred(err, zrc.Close()) }()
|
2020-07-21 18:50:35 +03:00
|
|
|
|
2021-05-24 17:28:11 +03:00
|
|
|
for _, zf := range zrc.File {
|
|
|
|
var name string
|
|
|
|
name, err = zipFileUnpackOne(outDir, zf)
|
2020-07-21 18:50:35 +03:00
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2021-05-24 17:28:11 +03:00
|
|
|
if name != "" {
|
|
|
|
files = append(files, name)
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-24 17:28:11 +03:00
|
|
|
return files, err
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
|
|
|
|
2024-11-05 19:21:29 +03:00
|
|
|
// copyFile copies a file from src to dst with the specified permissions.
|
|
|
|
func copyFile(src, dst string, perm fs.FileMode) (err error) {
|
2024-10-29 14:28:59 +03:00
|
|
|
d, err := os.ReadFile(src)
|
|
|
|
if err != nil {
|
|
|
|
// Don't wrap the error, since it's informative enough as is.
|
|
|
|
return err
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
2024-10-29 14:28:59 +03:00
|
|
|
|
2024-11-05 19:21:29 +03:00
|
|
|
err = aghos.WriteFile(dst, d, perm)
|
2024-10-29 14:28:59 +03:00
|
|
|
if err != nil {
|
|
|
|
// Don't wrap the error, since it's informative enough as is.
|
|
|
|
return err
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
2024-10-29 14:28:59 +03:00
|
|
|
|
2020-07-21 18:50:35 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-11-05 19:21:29 +03:00
|
|
|
// copySupportingFiles copies each file specified in files from srcdir to
|
|
|
|
// dstdir. If a file specified as a path, only the name of the file is used.
|
|
|
|
// It skips AdGuardHome, AdGuardHome.exe, and AdGuardHome.yaml.
|
2020-07-21 18:50:35 +03:00
|
|
|
func copySupportingFiles(files []string, srcdir, dstdir string) error {
|
|
|
|
for _, f := range files {
|
|
|
|
_, name := filepath.Split(f)
|
|
|
|
if name == "AdGuardHome" || name == "AdGuardHome.exe" || name == "AdGuardHome.yaml" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
src := filepath.Join(srcdir, name)
|
|
|
|
dst := filepath.Join(dstdir, name)
|
|
|
|
|
2024-11-05 19:21:29 +03:00
|
|
|
err := copyFile(src, dst, aghos.DefaultPermFile)
|
2021-03-15 14:19:04 +03:00
|
|
|
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
2020-07-21 18:50:35 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-06-09 17:47:05 +03:00
|
|
|
log.Debug("updater: copied: %q to %q", src, dst)
|
2020-07-21 18:50:35 +03:00
|
|
|
}
|
2021-03-15 14:19:04 +03:00
|
|
|
|
2020-07-20 21:14:07 +03:00
|
|
|
return nil
|
|
|
|
}
|