6

I have a Python script that reads a file (typically from optical media) marking the unreadable sectors, to allow a re-attempt to read said unreadable sectors on a different optical reader.

I discovered that my script does not work with block devices (e.g. /dev/sr0), in order to create a copy of the contained ISO9660/UDF filesystem, because os.stat().st_size is zero. The algorithm currently needs to know the filesize in advance; I can change that, but the issue (of knowing the block device size) remains, and it's not answered here, so I open this question.

I am aware of the following two related SO questions:

Therefore, I'm asking: in Python, how can I get the file size of a block device file?

Community
  • 1
  • 1
tzot
  • 92,761
  • 29
  • 141
  • 204

6 Answers6

9

The “most clean” (i.e. not dependent on external volumes and most reusable) Python solution I've reached, is to open the device file and seek at the end, returning the file offset:

def get_file_size(filename):
    "Get the file size by seeking at end"
    fd= os.open(filename, os.O_RDONLY)
    try:
        return os.lseek(fd, 0, os.SEEK_END)
    finally:
        os.close(fd)
tzot
  • 92,761
  • 29
  • 141
  • 204
8

Linux-specific ioctl-based solution:

import fcntl
import struct

device_path = '/dev/sr0'

req = 0x80081272 # BLKGETSIZE64, result is bytes as unsigned 64-bit integer (uint64)
buf = b' ' * 8
fmt = 'L'

with open(device_path) as dev:
    buf = fcntl.ioctl(dev.fileno(), req, buf)
bytes = struct.unpack('L', buf)[0]

print device_path, 'is about', bytes / (1024 ** 2), 'megabytes'

Other unixes will have different values for req, buf, fmt of course.

hynekcer
  • 14,942
  • 6
  • 61
  • 99
rmsr
  • 81
  • 1
  • 3
3

Another possible solution is

def blockdev_size(path):
    """Return device size in bytes.
    """
    with open(path, 'rb') as f:
        return f.seek(0, 2) or f.tell()

or f.tell() part is there for Python 2 portability's sake — file.seek() returns None in Python 2.

Magic constant 2 may be substituted with io.SEEK_END.

vvv
  • 157
  • 2
  • 5
  • 1
    Thanks for your answer. Basically the last line can be `return f.seek(0, 2) or f.tell()` in both Python 2 and 3. You say “cleaner solution” compared to which one? – tzot Jul 22 '19 at 13:43
  • Indeed. Thanks! Compared to the [“most clean”](https://stackoverflow.com/a/2774125/136238) one (yours). – vvv Jul 23 '19 at 19:43
  • 1
    @tzot I've reworded the first sentence and applied your “or” suggestion. Thanks! – vvv Jul 23 '19 at 19:59
  • My assumption is that f.seek() returns the same as f.tell() - "returns an integer giving the file object’s current position in the file represented as number of bytes from the beginning of the file when in binary mode and an opaque number when in text mode" but that actually is not in Python 3 documentation. What is an "opaque" number? – hansonap Mar 25 '20 at 16:00
3

In Linux, there is /sys/block/${dev}/size that can be read even without sudo. To get the size of /dev/sdb simply do:

print( 512 * int(open('/sys/block/sdb/size','r').read()) )

See also https://unix.stackexchange.com/a/52219/384116

Axel Heider
  • 557
  • 4
  • 14
0

This solution uses lsblk (provided by util-linux) and does not require sudo:

import json
import subprocess

def get_device_size_bytes(device: str) -> int:
    output = subprocess.run(
        ["lsblk", device, "--json", "--bytes"], capture_output=True, check=True
    )
    dev_info = json.loads(output.stdout)
    size_bytes = dev_info["blockdevices"][0]["size"]
    return size_bytes

Shorter solution to only get size from lsblk:

import subprocess

def get_device_size_bytes(device: str) -> int:
    output = subprocess.run(
        ["lsblk", device, "--output", "SIZE", "--bytes", "--noheadings", "--nodeps"],
        capture_output=True,
        check=True,
    )
    size = int(output.stdout.decode())
    return size
Wolkenarchitekt
  • 20,170
  • 29
  • 111
  • 174
-1

Trying to adapt from the other answer:

import fcntl
c = 0x00001260 ## check man ioctl_list, BLKGETSIZE
f = open('/dev/sr0', 'ro')
s = fcntl.ioctl(f, c)
print s

I don't have a suitable computer at hand to test this. I'd be curious to know if it works :)

XTL
  • 851
  • 1
  • 8
  • 23
  • I tried it with both `f = open` and `fd = os.open` (since `fcntl.ioctl` requires a file descriptor, although it might call `.fileno()` on a Python file object). In both cases, `fcntl.ioctl` raised `IOError: [Errno 14] Bad address`. – tzot Apr 12 '12 at 17:54