There is a difference between the size of the block device a filesystem is on, and the size of that filesystem itself. Given your task is to operate on the superblocks within the filesystem itself, I'll presume that you are more interested in the latter. If you're more interested in the actual device size, then the answer by @that-other-guy is correct.
Assuming that you are working with ext4
for the filesystem, and based on the information here, the full size of the filesystem would be the total block count multiplied by the block size. In the structure of the superblock, the relevant fields are:
s_blocks_count_lo
is straightforward, but s_log_block_size
needs a bit of processing, as the value stored means:
Block size is 2 ^ (10 + s_log_block_size).
Putting all that together, you can do something like:
uintmax_t get_filesystem_size(const char *device) {
int fd;
if((fd = open(device, O_RDONLY)) < 0) {
perror(device);
exit(1);
}
if (lseek(fd, 1024, SEEK_SET) < 0) {
perror("lseek");
exit(1);
}
uint8_t block0[1024];
if (read(fd, &block0, 1024) < 0) {
perror("read");
exit(1);
}
if (s_magic(block0) != 0xef53) {
fprintf(stderr, "bad magic\n");
exit(1);
}
close(fd);
return s_blocks_count_lo(block0) * s_block_size(block0);
}
with the following ext4 superblock specific helper functions:
uint16_t s_magic(const uint8_t *buffer) {
return le16(buffer + 0x38);
}
uint32_t s_blocks_count_lo(const uint8_t *buffer) {
return le32(buffer + 0x4);
}
uintmax_t s_block_size(const uint8_t *buffer) {
return 1 << (10 + le32(buffer + 0x18));
}
and the following general endianness helper functions:
uint16_t le16(const uint8_t *buffer) {
int result = 0;
for (int i = 1; i >= 0; i--) {
result *= 256;
result += buffer[i];
}
return result;
}
uint32_t le32(const uint8_t *buffer) {
int result = 0;
for (int i = 3; i >= 0; i--) {
result *= 256;
result += buffer[i];
}
return result;
}