Skip to content

Instantly share code, notes, and snippets.

@chrrrisw
Last active April 3, 2016 21:47
Show Gist options
  • Save chrrrisw/765ee229d046c12b0310 to your computer and use it in GitHub Desktop.
Save chrrrisw/765ee229d046c12b0310 to your computer and use it in GitHub Desktop.
Getting short filenames (8.3 format) from a FAT partition on Linux in Python
#include <fcntl.h>
#include <linux/msdos_fs.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stddef.h>
/*
* This file prints the constants for both VFAT_IOCTL_READDIR_BOTH
* and VFAT_IOCTL_READDIR_SHORT, as well as the buffer length required
* for the __fat_dirent structure (multiplied by 2).
* The output is suitable for direct inclusion into a python module.
* Also output is a format string suitable for use by struct.unpack().
*
* The accompanying Makefile builds this C file into an executable and
* generates the vfat_ioctl.py Python module from it. This is then
* imported into the test_short.py program for use in determining
* the short (8.3 format) filenames.
*/
int main(int argc, char *argv[])
{
struct __fat_dirent entry;
printf("VFAT_IOCTL_READDIR_BOTH = %ld\n", VFAT_IOCTL_READDIR_BOTH);
printf("VFAT_IOCTL_READDIR_SHORT = %ld\n", VFAT_IOCTL_READDIR_SHORT);
long int buffer_size = sizeof(struct __fat_dirent);
printf("BUFFER_SIZE = %ld\n", buffer_size * 2);
long int d_reclen_offset = offsetof(struct __fat_dirent, d_reclen);
long int d_reclen_size = sizeof(entry.d_reclen);
char d_reclen_type;
switch (d_reclen_size) {
case 1:
d_reclen_type = 'B';
break;
case 2:
d_reclen_type = 'H';
break;
case 4:
d_reclen_type = 'I';
break;
}
long int d_name_offset = offsetof(struct __fat_dirent, d_name);
long int d_name_size = sizeof(entry.d_name);
if (d_reclen_offset + d_reclen_size != d_name_offset) {
printf("Oops!\n");
}
long int end_padding_size = buffer_size - (d_name_offset + d_name_size);
printf("BUFFER_FORMAT = '=%ldx%c%lds%ldx%ldx%c%lds%ldx'\n",
d_reclen_offset, d_reclen_type, d_name_size, end_padding_size,
d_reclen_offset, d_reclen_type, d_name_size, end_padding_size);
exit(EXIT_SUCCESS);
}
all: generate_ioctl vfat_ioctl.py
generate_ioctl: generate_ioctl.c
vfat_ioctl.py: generate_ioctl
./generate_ioctl > vfat_ioctl.py
#!/usr/bin/env /usr/bin/python3
import argparse
import os
import fcntl
import struct
import vfat_ioctl
'''
Prints the directory tree below the given FAT partition path.
Outputs both the long and short (8.3 format) file and directory names.
'''
class FATParser(object):
def __init__(self, topdir):
# Store the top directory
self.topdir = topdir
# Create an empty dictionary to store the paths
self.paths = {}
# A buffer to hold the IOCTL data
self.buffer = bytearray(vfat_ioctl.BUFFER_SIZE)
# Walk the directory tree
for root, dirs, files, rootfd in os.fwalk(topdir):
self.get_directory_entries(root, rootfd)
def get_directory_entries(self, directory_name, directory_fd):
# Get the path relative to the top level directory
relative_path = os.path.relpath(directory_name, self.topdir)
# If we don't have it, it's the top level directory so we
# seed the paths dictionary. If we have it, get the shortname
# for the directory (collected one level up).
if relative_path not in self.paths:
self.paths[relative_path] = {
'shortname': relative_path,
'files': []}
current_path_shortname = relative_path
else:
current_path_shortname = self.paths[relative_path]['shortname']
while True:
# Get both names for the next directory entry using a ioctl call.
result = fcntl.ioctl(
directory_fd,
vfat_ioctl.VFAT_IOCTL_READDIR_BOTH,
self.buffer)
# Have we finished?
if result < 1:
break
# Interpret the resultant bytearray
# sl = length of shortname
# sn = shortname
# ll = length of longname
# ln = longname
sl, sn, ll, ln = struct.unpack(
vfat_ioctl.BUFFER_FORMAT,
self.buffer)
# Decode the bytearrays into strings
# If longname has zero length, use shortname
shortname = sn[:sl].decode()
if ll > 0:
filename = ln[:ll].decode()
else:
filename = shortname
# Don't process . or ..
if (filename != '.') and (filename != '..'):
# Check whether it's a directory
fullname = os.path.join(directory_name, filename)
if os.path.isdir(fullname):
# Create the paths entry
self.paths[os.path.relpath(fullname, self.topdir)] = {
'shortname': os.path.join(
current_path_shortname, shortname),
'files': []}
else:
self.paths[relative_path]['files'].append({
'shortname': shortname,
'fullname': fullname,
'filename': filename})
def display(self):
for path in self.paths:
print('{} --> {}'.format(path, self.paths[path]['shortname']))
for f in self.paths[path]['files']:
print('\t {} --> {}'.format(f['filename'], f['shortname']))
def main():
parser = argparse.ArgumentParser(description="List a FAT directory")
parser.add_argument(
dest="inputdir",
action="store",
default=None, help="specify input directory")
args = parser.parse_args()
if os.path.isdir(args.inputdir):
fat_parser = FATParser(args.inputdir)
fat_parser.display()
else:
print('Please enter a directory name on a FAT partition')
if __name__ == '__main__':
main()
VFAT_IOCTL_READDIR_BOTH = 2184212993
VFAT_IOCTL_READDIR_SHORT = 2184212994
BUFFER_SIZE = 560
BUFFER_FORMAT = '=16xH256s6x16xH256s6x'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment