Query size of block device file in Python
Asked Answered
A

6

8

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?

Acrogen answered 5/5, 2010 at 13:30 Comment(0)
A
11

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)
Acrogen answered 5/5, 2010 at 14:32 Comment(2)
Since there were no other answers after 2 weeks, I picked my own answer.Acrogen
This requires sudo, which should not be necessary for such an operationPiloting
P
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.

Pepin answered 17/10, 2012 at 0:8 Comment(1)
This requires sudo, which should not be necessary for such an operationPiloting
L
5

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.

Lacerta answered 21/7, 2019 at 21:46 Comment(5)
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?Acrogen
Indeed. Thanks! Compared to the “most clean” one (yours).Lacerta
@Acrogen I've reworded the first sentence and applied your “or” suggestion. Thanks!Lacerta
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?Flyte
The or in the last line is should be a comment, f.seek is quite enough. The f.seek(0, os.SEEK_END) is much clearer / easier to read.Birthstone
L
4

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

Labdanum answered 22/2, 2021 at 2:30 Comment(0)
P
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
Piloting answered 16/8, 2023 at 9:13 Comment(0)
M
-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 :)

Mulvey answered 12/4, 2012 at 8:16 Comment(1)
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.Acrogen

© 2022 - 2024 — McMap. All rights reserved.