Module: Meshtastic::Admin::Firmware::UF2

Defined in:
lib/meshtastic/admin/firmware/uf2.rb

Overview

UF2 mass-storage submission is not a flash verification protocol.

Constant Summary collapse

MAX_IMAGE =
32 * 1024 * 1024

Class Method Summary collapse

Class Method Details

.authorsObject



142
143
144
# File 'lib/meshtastic/admin/firmware/uf2.rb', line 142

public_class_method def self.authors
  "AUTHOR(S):\n        0day Inc. <[email protected]>\n      "
end

.helpObject



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/meshtastic/admin/firmware/uf2.rb', line 146

public_class_method def self.help
  puts "USAGE:
    # Validate and submit firmware through UF2 mass storage.
    #{self}.install(
      protocol: 'required - explicit :uf2 protocol selection',
      bytes: 'optional - complete original UF2 image bytes instead of firmware',
      firmware: 'optional - regular UF2 image file instead of bytes',
      mount: 'required - absolute bootloader directory explicitly selected by the operator',
      family_id: 'required - expected numeric UF2 MCU family identifier',
      board_id: 'required - exact Board-ID from the selected bootloader INFO_UF2.TXT',
      flash_size: 'optional - required for RP2040 only; installed flash capacity in bytes, 4096 aligned, at most 16 MiB'
    )

    # Return module author information.
    #{self}.authors
  "
end

.install(opts = {}) ⇒ Object

Raises:

  • (ArgumentError)


12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/meshtastic/admin/firmware/uf2.rb', line 12

public_class_method def self.install(opts = {})
  raise ArgumentError, 'protocol must be explicitly :uf2' unless opts[:protocol] == :uf2
  raise ArgumentError, 'Unsupported UF2 install options' unless (opts.keys - %i[protocol firmware bytes mount family_id board_id flash_size]).empty?
  raise ArgumentError, 'Supply exactly one of firmware or bytes' unless opts.key?(:firmware) ^ opts.key?(:bytes)

  bytes = image_bytes(opts.merge({}))
  validate_image(opts.merge(bytes: bytes))
  mount = opts[:mount]
  raise ArgumentError, 'mount must be an explicit canonical absolute bootloader directory' unless mount.is_a?(String) && mount.start_with?('/') && mount != '/' && File.expand_path(mount) == mount && File.directory?(mount) && File.realpath(mount) == mount
  raise NotImplementedError, 'Safe UF2 directory pinning requires Linux procfs and O_NOFOLLOW' unless File.directory?('/proc/self/fd') && File.const_defined?(:NOFOLLOW)

  destination = File.join(mount, 'FIRMWARE.UF2')
  File.open(mount, File::RDONLY | File::NOFOLLOW | File::NONBLOCK) do |directory|
    raise ArgumentError, 'mount must be a directory' unless directory.stat.directory?

    # Use the opened directory, never re-resolve the mount path for a
    # write: an unplug/unmount must not redirect data onto the host disk.
    anchor = "/proc/self/fd/#{directory.fileno}"
    validate_target(opts.merge(anchor: anchor))
    current = File.stat(mount)
    raise IOError, 'UF2 mount changed before submission' unless current.dev == directory.stat.dev && current.ino == directory.stat.ino && File.realpath(mount) == mount

    File.open(File.join(anchor, 'FIRMWARE.UF2'), File::WRONLY | File::CREAT | File::EXCL | File::NOFOLLOW, 0o600) do |file|
      file.binmode
      written = file.write(bytes)
      raise IOError, 'Incomplete UF2 submission; do not automatically retry' unless written == bytes.bytesize

      file.flush
      file.fsync
    end
  end
  { status: :copied, protocol: :uf2, bytes: bytes.bytesize, sha256: Digest::SHA256.hexdigest(bytes),
    family_id: opts.fetch(:family_id), board_id: opts.fetch(:board_id), destination: destination,
    flash_verified: false, reboot_verified: false }
end