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.
Files changed (26) hide show
  1. {flashpod-0.2.4 → flashpod-0.2.5}/PKG-INFO +1 -1
  2. {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/__init__.py +1 -1
  3. {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/cli.py +19 -10
  4. {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/platform/windows.py +62 -16
  5. {flashpod-0.2.4 → flashpod-0.2.5}/flashpod.egg-info/PKG-INFO +1 -1
  6. {flashpod-0.2.4 → flashpod-0.2.5}/pyproject.toml +1 -1
  7. {flashpod-0.2.4 → flashpod-0.2.5}/LICENSE +0 -0
  8. {flashpod-0.2.4 → flashpod-0.2.5}/README.md +0 -0
  9. {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/__main__.py +0 -0
  10. {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/contrib/99-flashpod-firewire-ipod.rules +0 -0
  11. {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/fat32.py +0 -0
  12. {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/fatfs.py +0 -0
  13. {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/firmware/firmware.json +0 -0
  14. {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/ipod_flash.py +0 -0
  15. {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/itunesdb.py +0 -0
  16. {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/platform/__init__.py +0 -0
  17. {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/platform/base.py +0 -0
  18. {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/platform/linux.py +0 -0
  19. {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/platform/macos.py +0 -0
  20. {flashpod-0.2.4 → flashpod-0.2.5}/flashpod/resources.py +0 -0
  21. {flashpod-0.2.4 → flashpod-0.2.5}/flashpod.egg-info/SOURCES.txt +0 -0
  22. {flashpod-0.2.4 → flashpod-0.2.5}/flashpod.egg-info/dependency_links.txt +0 -0
  23. {flashpod-0.2.4 → flashpod-0.2.5}/flashpod.egg-info/entry_points.txt +0 -0
  24. {flashpod-0.2.4 → flashpod-0.2.5}/flashpod.egg-info/requires.txt +0 -0
  25. {flashpod-0.2.4 → flashpod-0.2.5}/flashpod.egg-info/top_level.txt +0 -0
  26. {flashpod-0.2.4 → flashpod-0.2.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flashpod
3
- Version: 0.2.4
3
+ Version: 0.2.5
4
4
  Summary: Command-line iPod sync + card-flashing tooling for early FireWire-era iPods, pure Python
5
5
  Author: David Barnhart
6
6
  License-Expression: MIT
@@ -4,4 +4,4 @@
4
4
  Pure Python, no libgpod. See the README for usage.
5
5
  """
6
6
 
7
- __version__ = "0.2.0"
7
+ __version__ = "0.2.5"
@@ -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 os.name == "nt" or not sys.stdin.isatty():
358
- return # can't prompt — caller handles it
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 rootelevate via sudo (prompts for the
2428
- # password on a terminal) rather than just bailing, like the raw
2429
- # data commands do.
2430
- if os.name != "nt" and sys.stdin.isatty():
2431
- print("flashpod flash: writing to a disk needs root — "
2432
- "elevating via sudo...", file=sys.stderr)
2437
+ # Writing to a disk needs elevationtry 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
- if os.name != "nt": # offer the exact sudo rerun on POSIX
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 | Where-Object DriveLetter | "
218
- "ForEach-Object { $_.DriveLetter }" % num)
219
- return [("%s:" % c.strip(), "%s:\\" % c.strip())
220
- for c in txt.splitlines() if c.strip()]
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 unmount_all(self, dev, dry):
236
- if dry:
237
- return
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("\\\\.\\%s" % vol.rstrip("\\"), write=True)
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
- finally:
245
- ctypes.windll.kernel32.CloseHandle(h)
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
- # FSCTL_LOCK_VOLUME only holds while the locking handle is open, so
291
- # the dismount done before write_layout is already undone by now:
292
- # writing the new MBR makes Windows rescan and re-mount the data
293
- # partition, and it then refuses writes into those sectors. Dismount
294
- # again immediately before each write open.
295
- self.unmount_all(dev, False)
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))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flashpod
3
- Version: 0.2.4
3
+ Version: 0.2.5
4
4
  Summary: Command-line iPod sync + card-flashing tooling for early FireWire-era iPods, pure Python
5
5
  Author: David Barnhart
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "flashpod"
7
- version = "0.2.4"
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