Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions flash.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (
)

func newFlashCmd() *cobra.Command {
var forceYes bool
var forceYes, preserveUser bool
var tempDir string
appCmd := &cobra.Command{
Use: "flash",
Expand Down Expand Up @@ -68,12 +68,12 @@ NOTE: On Windows, required drivers are automatically installed with elevated pri
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
checkDriversInstalled()
runFlashCommand(cmd.Context(), args, forceYes, tempDir)
runFlashCommand(cmd.Context(), args, forceYes, preserveUser, tempDir)
},
}
appCmd.Flags().BoolVarP(&forceYes, "yes", "y", false, "Automatically confirm all prompts")
appCmd.Flags().StringVar(&tempDir, "temp-dir", "", "Path to the directory in which the image will be downloaded and extracted")
// TODO: add --clean-install flag or something similar to distinguish between keeping and purging the /home directory
appCmd.Flags().BoolVar(&preserveUser, "preserve-user", false, "Preserve user partition")

return appCmd
}
Expand All @@ -89,13 +89,13 @@ func checkDriversInstalled() {
}
}

func runFlashCommand(ctx context.Context, args []string, forceYes bool, tempDir string) {
func runFlashCommand(ctx context.Context, args []string, forceYes bool, preserveUser bool, tempDir string) {
imagePath, err := paths.New(args[0]).Abs()
if err != nil {
feedback.Fatal(i18n.Tr("could not find image absolute path: %v", err), feedback.ErrBadArgument)
}

err = updater.Flash(ctx, imagePath, args[0], forceYes, tempDir)
err = updater.Flash(ctx, imagePath, args[0], forceYes, preserveUser, tempDir)
if err != nil {
feedback.Fatal(i18n.Tr("error flashing the board: %v", err), feedback.ErrBadArgument)
}
Expand Down
23 changes: 23 additions & 0 deletions updater/artifacts/artifacts_read_xml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// This file is part of arduino-flasher-cli.
//
// Copyright 2025 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-flasher-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to license@arduino.cc.

package artifacts

import (
_ "embed"
)

//go:embed read.xml
var ReadXML []byte
4 changes: 4 additions & 0 deletions updater/artifacts/read.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" ?>
<data>
<read SECTOR_SIZE_IN_BYTES="512" filename="dump.bin" physical_partition_number="0" num_partition_sectors="34" start_sector="0"/>
</data>
81 changes: 73 additions & 8 deletions updater/flasher.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package updater

import (
"context"
"encoding/hex"
"fmt"
"runtime"
"strings"
Expand All @@ -32,8 +33,9 @@ import (
const GiB = uint64(1024 * 1024 * 1024)
const DownloadDiskSpace = uint64(12)
const ExtractDiskSpace = uint64(10)
const yesPrompt = "yes"

func Flash(ctx context.Context, imagePath *paths.Path, version string, forceYes bool, tempDir string) error {
func Flash(ctx context.Context, imagePath *paths.Path, version string, forceYes bool, preserveUser bool, tempDir string) error {
if !imagePath.Exist() {
client := NewClient()

Expand Down Expand Up @@ -61,7 +63,7 @@ func Flash(ctx context.Context, imagePath *paths.Path, version string, forceYes
if err != nil {
return false, err
}
yes := strings.ToLower(yesInput) == "yes" || strings.ToLower(yesInput) == "y"
yes := strings.ToLower(yesInput) == yesPrompt || strings.ToLower(yesInput) == "y"
return yes, nil
}, forceYes, temp)

Expand Down Expand Up @@ -116,13 +118,13 @@ func Flash(ctx context.Context, imagePath *paths.Path, version string, forceYes
if err != nil {
return false, err
}
yes := strings.ToLower(yesInput) == "yes" || strings.ToLower(yesInput) == "y"
yes := strings.ToLower(yesInput) == yesPrompt || strings.ToLower(yesInput) == "y"
return yes, nil
}, forceYes)
}, forceYes, preserveUser)
}

func FlashBoard(ctx context.Context, downloadedImagePath string, version string, upgradeConfirmCb DownloadConfirmCB, forceYes bool) error {
if !forceYes {
func FlashBoard(ctx context.Context, downloadedImagePath string, version string, upgradeConfirmCb DownloadConfirmCB, forceYes bool, preserveUser bool) error {
if !forceYes && !preserveUser {
res, err := upgradeConfirmCb(version)
if err != nil {
return err
Expand Down Expand Up @@ -168,9 +170,41 @@ func FlashBoard(ctx context.Context, downloadedImagePath string, version string,
if err != nil {
return err
}
// TODO: add logic to preserve the user partition

rawProgram := "rawprogram0.xml"
if preserveUser {
if errT := checkBoardGPTTable(ctx, qdlPath, flashDir); errT == nil && flashDir.Join("rawprogram0.nouser.xml").Exist() {
rawProgram = "rawprogram0.nouser.xml"
} else {
res, err := func(target string) (bool, error) {
warnStr := "Linux image " + target + " does not support user partition preservation"
if errT != nil {
warnStr = errT.Error()
}
feedback.Printf("\nWARNING: %s.", warnStr)
feedback.Printf("Do you want to proceed and flash %s on the board, erasing any existing data you have on it.? (yes/no)", target)

var yesInput string
_, err := fmt.Scanf("%s\n", &yesInput)
if err != nil {
return false, err
}
yes := strings.ToLower(yesInput) == yesPrompt || strings.ToLower(yesInput) == "y"
return yes, nil
}(version)
if err != nil {
return err
}
if !res {
feedback.Print(i18n.Tr("Flashing not confirmed by user, exiting"))
return nil
}
}

}

feedback.Print(i18n.Tr("Flashing with qdl"))
cmd, err := paths.NewProcess(nil, qdlPath.String(), "--allow-missing", "--storage", "emmc", "prog_firehose_ddr.elf", "rawprogram0.xml", "patch0.xml")
cmd, err := paths.NewProcess(nil, qdlPath.String(), "--allow-missing", "--storage", "emmc", "prog_firehose_ddr.elf", rawProgram, "patch0.xml")
if err != nil {
return err
}
Expand All @@ -186,3 +220,34 @@ func FlashBoard(ctx context.Context, downloadedImagePath string, version string,

return nil
}

func checkBoardGPTTable(ctx context.Context, qdlPath, flashDir *paths.Path) error {
dumpBinPath := qdlPath.Parent().Join("dump.bin")
readXMLPath := qdlPath.Parent().Join("read.xml")
err := readXMLPath.WriteFile(artifacts.ReadXML)
if err != nil {
return err
}
cmd, err := paths.NewProcess(nil, qdlPath.String(), "--storage", "emmc", flashDir.Join("prog_firehose_ddr.elf").String(), readXMLPath.String())
if err != nil {
return err
}
cmd.SetDir(qdlPath.Parent().String())
if err := cmd.RunWithinContext(ctx); err != nil {
return err
}
if !dumpBinPath.Exist() {
return fmt.Errorf("it was not possible to access the current Debian image GPT table")
}
dump, err := dumpBinPath.ReadFile()
if err != nil {
return err
}
strDump := hex.Dump(dump)

if strings.Contains(strDump, "00000250 4c 00 00 00") {
return fmt.Errorf("the current Debian image (R0) does not support user partition preservation")
}

return nil
}
Loading