flashpod 0.2.4__tar.gz → 0.2.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {flashpod-0.2.4 → flashpod-0.2.5}/PKG-INFO +1 -1
- {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/__init__.py +1 -1
- {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/cli.py +19 -10
- {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/platform/windows.py +62 -16
- {flashpod-0.2.4 → flashpod-0.2.5}/flashpod.egg-info/PKG-INFO +1 -1
- {flashpod-0.2.4 → flashpod-0.2.5}/pyproject.toml +1 -1
- {flashpod-0.2.4 → flashpod-0.2.5}/LICENSE +0 -0
- {flashpod-0.2.4 → flashpod-0.2.5}/README.md +0 -0
- {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/__main__.py +0 -0
- {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/contrib/99-flashpod-firewire-ipod.rules +0 -0
- {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/fat32.py +0 -0
- {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/fatfs.py +0 -0
- {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/firmware/firmware.json +0 -0
- {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/ipod_flash.py +0 -0
- {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/itunesdb.py +0 -0
- {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/platform/__init__.py +0 -0
- {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/platform/base.py +0 -0
- {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/platform/linux.py +0 -0
- {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/platform/macos.py +0 -0
- {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/resources.py +0 -0
- {flashpod-0.2.4 → flashpod-0.2.5}/flashpod.egg-info/SOURCES.txt +0 -0
- {flashpod-0.2.4 → flashpod-0.2.5}/flashpod.egg-info/dependency_links.txt +0 -0
- {flashpod-0.2.4 → flashpod-0.2.5}/flashpod.egg-info/entry_points.txt +0 -0
- {flashpod-0.2.4 → flashpod-0.2.5}/flashpod.egg-info/requires.txt +0 -0
- {flashpod-0.2.4 → flashpod-0.2.5}/flashpod.egg-info/top_level.txt +0 -0
- {flashpod-0.2.4 → flashpod-0.2.5}/setup.cfg +0 -0
|
@@ -354,8 +354,18 @@ def _sudo_reexec(extra):
|
|
|
354
354
|
"""Re-exec this same flashpod under sudo with ``extra`` args appended
|
|
355
355
|
(prompting for the password on a terminal). REPLACES the process and never
|
|
356
356
|
returns on success; returns only if it can't elevate (non-tty / no sudo)."""
|
|
357
|
-
if
|
|
358
|
-
return
|
|
357
|
+
if not sys.stdin.isatty():
|
|
358
|
+
return
|
|
359
|
+
if os.name == "nt":
|
|
360
|
+
# Windows 11 24H2+ ships sudo.exe. In "Inline" mode it elevates
|
|
361
|
+
# within the same console, preserving interactive prompts — unlike
|
|
362
|
+
# ShellExecute "runas" which opens a detached window. The
|
|
363
|
+
# environment is inherited, so no env/PYTHONPATH dance needed.
|
|
364
|
+
cmd = ["sudo"] + _self_cmd() + extra
|
|
365
|
+
try:
|
|
366
|
+
sys.exit(subprocess.call(cmd))
|
|
367
|
+
except OSError:
|
|
368
|
+
return # no sudo.exe — caller handles it
|
|
359
369
|
# sudo resets the environment, so the FLASHPOD_* tuning knobs the user set
|
|
360
370
|
# would be lost across elevation. Re-assert them in the child via `env`.
|
|
361
371
|
passthru = ["%s=%s" % (k, v) for k, v in sorted(os.environ.items())
|
|
@@ -2424,16 +2434,15 @@ def main():
|
|
|
2424
2434
|
return 0
|
|
2425
2435
|
plat = platform.current()
|
|
2426
2436
|
if not opts.dry_run and not plat.is_admin():
|
|
2427
|
-
# Writing to a disk needs
|
|
2428
|
-
#
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
print("flashpod flash: writing to a disk needs
|
|
2432
|
-
"elevating via sudo...", file=sys.stderr)
|
|
2437
|
+
# Writing to a disk needs elevation — try sudo (prompts for
|
|
2438
|
+
# credentials or shows a UAC dialog) rather than just bailing.
|
|
2439
|
+
if sys.stdin.isatty():
|
|
2440
|
+
role = "Administrator" if os.name == "nt" else "root"
|
|
2441
|
+
print("flashpod flash: writing to a disk needs %s — "
|
|
2442
|
+
"elevating via sudo..." % role, file=sys.stderr)
|
|
2433
2443
|
_sudo_reexec(_cmd_args(opts)) # re-execs; returns only if sudo is missing
|
|
2434
2444
|
msg = "flashpod flash: " + plat.privilege_hint()
|
|
2435
|
-
|
|
2436
|
-
msg += "\n sudo " + " ".join(_self_cmd() + _cmd_args(opts))
|
|
2445
|
+
msg += "\n sudo " + " ".join(_self_cmd() + _cmd_args(opts))
|
|
2437
2446
|
print(msg, file=sys.stderr)
|
|
2438
2447
|
return 1
|
|
2439
2448
|
max_data_gb = None
|
|
@@ -92,6 +92,9 @@ def _drive_number(dev):
|
|
|
92
92
|
class WindowsPlatform(Platform):
|
|
93
93
|
name = "windows"
|
|
94
94
|
|
|
95
|
+
def __init__(self):
|
|
96
|
+
self._held_locks = [] # [(handle_path, win_handle), ...]
|
|
97
|
+
|
|
95
98
|
# -- privilege --------------------------------------------------------
|
|
96
99
|
def is_admin(self):
|
|
97
100
|
try:
|
|
@@ -213,11 +216,29 @@ class WindowsPlatform(Platform):
|
|
|
213
216
|
num = _drive_number(dev)
|
|
214
217
|
except ValueError:
|
|
215
218
|
return []
|
|
219
|
+
# AccessPaths covers both drive letters (D:\) and volume GUID
|
|
220
|
+
# paths (\\?\Volume{...}\). The old DriveLetter-only filter missed
|
|
221
|
+
# letterless volumes, so their sectors were never locked and raw
|
|
222
|
+
# writes failed with access-denied / bad-file-descriptor.
|
|
216
223
|
txt = _powershell(
|
|
217
|
-
"Get-Partition -DiskNumber %d
|
|
218
|
-
"ForEach-Object { $_.
|
|
219
|
-
|
|
220
|
-
|
|
224
|
+
"Get-Partition -DiskNumber %d -ErrorAction SilentlyContinue"
|
|
225
|
+
" | ForEach-Object { foreach ($a in $_.AccessPaths) { $a } }"
|
|
226
|
+
% num)
|
|
227
|
+
seen = set()
|
|
228
|
+
results = []
|
|
229
|
+
for line in txt.splitlines():
|
|
230
|
+
p = line.strip()
|
|
231
|
+
if not p or p in seen:
|
|
232
|
+
continue
|
|
233
|
+
seen.add(p)
|
|
234
|
+
if len(p) >= 2 and p[1] == ':':
|
|
235
|
+
# Drive letter: D:\ -> ("D:", "D:\")
|
|
236
|
+
results.append((p[:2], p))
|
|
237
|
+
elif p.startswith('\\\\?\\'):
|
|
238
|
+
# Volume GUID: \\?\Volume{...}\ -> ("Volume{...}", path)
|
|
239
|
+
# _lock_volumes prepends \\.\ to build \\.\Volume{...}
|
|
240
|
+
results.append((p[4:].rstrip('\\'), p))
|
|
241
|
+
return results
|
|
221
242
|
|
|
222
243
|
def validate_target(self, dev, dry_run):
|
|
223
244
|
from .. import ipod_flash
|
|
@@ -232,20 +253,44 @@ class WindowsPlatform(Platform):
|
|
|
232
253
|
sys.exit(color("refusing: PhysicalDrive%d backs the running system." % num, red))
|
|
233
254
|
|
|
234
255
|
# -- mutation around the raw write ------------------------------------
|
|
235
|
-
def
|
|
236
|
-
|
|
237
|
-
|
|
256
|
+
def _lock_volumes(self, dev):
|
|
257
|
+
"""Lock and dismount every volume on *dev*, holding the handles so
|
|
258
|
+
Windows cannot remount between write_layout and format_data.
|
|
259
|
+
|
|
260
|
+
Safe to call more than once: volumes already locked are skipped, and
|
|
261
|
+
volumes that appeared since the previous call (e.g. after an MBR
|
|
262
|
+
rewrite) are picked up."""
|
|
263
|
+
already = {v for v, _h in self._held_locks}
|
|
238
264
|
for vol, _mp in self.device_mountpoints(dev):
|
|
265
|
+
handle_path = "\\\\.\\%s" % vol.rstrip("\\")
|
|
266
|
+
if handle_path in already:
|
|
267
|
+
continue
|
|
239
268
|
try:
|
|
240
|
-
h = self._open_handle(
|
|
269
|
+
h = self._open_handle(handle_path, write=True)
|
|
241
270
|
try:
|
|
242
271
|
self._ioctl(h, FSCTL_LOCK_VOLUME)
|
|
243
272
|
self._ioctl(h, FSCTL_DISMOUNT_VOLUME)
|
|
244
|
-
|
|
245
|
-
|
|
273
|
+
self._held_locks.append((handle_path, h))
|
|
274
|
+
except OSError:
|
|
275
|
+
_k32().CloseHandle(h)
|
|
246
276
|
except OSError:
|
|
247
277
|
pass
|
|
248
278
|
|
|
279
|
+
def _release_locks(self):
|
|
280
|
+
"""Close all held volume-lock handles."""
|
|
281
|
+
k = _k32()
|
|
282
|
+
for _vol, h in self._held_locks:
|
|
283
|
+
try:
|
|
284
|
+
k.CloseHandle(h)
|
|
285
|
+
except Exception: # noqa: BLE001
|
|
286
|
+
pass
|
|
287
|
+
self._held_locks = []
|
|
288
|
+
|
|
289
|
+
def unmount_all(self, dev, dry):
|
|
290
|
+
if dry:
|
|
291
|
+
return
|
|
292
|
+
self._lock_volumes(dev)
|
|
293
|
+
|
|
249
294
|
def wipe_signatures(self, dev, dry):
|
|
250
295
|
return # raw zeroing in write_layout handles this
|
|
251
296
|
|
|
@@ -270,6 +315,7 @@ class WindowsPlatform(Platform):
|
|
|
270
315
|
pass
|
|
271
316
|
|
|
272
317
|
def eject(self, dev, dry):
|
|
318
|
+
self._release_locks()
|
|
273
319
|
if dry:
|
|
274
320
|
return
|
|
275
321
|
try:
|
|
@@ -287,12 +333,12 @@ class WindowsPlatform(Platform):
|
|
|
287
333
|
import msvcrt
|
|
288
334
|
write = ("w" in mode or "+" in mode or "a" in mode)
|
|
289
335
|
if write:
|
|
290
|
-
#
|
|
291
|
-
#
|
|
292
|
-
#
|
|
293
|
-
#
|
|
294
|
-
#
|
|
295
|
-
self.
|
|
336
|
+
# Lock (and hold) every volume on this disk so Windows cannot
|
|
337
|
+
# remount between write_layout's MBR write and format_data's
|
|
338
|
+
# FAT32 write. Handles are kept alive in self._held_locks and
|
|
339
|
+
# released in eject(). _lock_volumes is incremental — safe to
|
|
340
|
+
# call on every open.
|
|
341
|
+
self._lock_volumes(dev)
|
|
296
342
|
h = self._open_handle(dev, write=write)
|
|
297
343
|
fd = msvcrt.open_osfhandle(h, os.O_BINARY)
|
|
298
344
|
return AlignedRawIO(os.fdopen(fd, mode, buffering=0))
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "flashpod"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.5"
|
|
8
8
|
description = "Command-line iPod sync + card-flashing tooling for early FireWire-era iPods, pure Python"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.6"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|