129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin#!/usr/bin/env python 229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin# 329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin# Copyright 2016 The Android Open Source Project 429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin# 529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin# Licensed under the Apache License, Version 2.0 (the "License"); 629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin# you may not use this file except in compliance with the License. 729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin# You may obtain a copy of the License at 829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin# 929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin# http://www.apache.org/licenses/LICENSE-2.0 1029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin# 1129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin# Unless required by applicable law or agreed to in writing, software 1229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin# distributed under the License is distributed on an "AS IS" BASIS, 1329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin# See the License for the specific language governing permissions and 1529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin# limitations under the License. 1629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 1729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlinimport os 1829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlinimport sys 1929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlinimport struct 2029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 2129e2b21c0ad6ff1e1c342264c6563035484e3191Casey DahlinFAT_TABLE_START = 0x200 2229e2b21c0ad6ff1e1c342264c6563035484e3191Casey DahlinDEL_MARKER = 0xe5 2329e2b21c0ad6ff1e1c342264c6563035484e3191Casey DahlinESCAPE_DEL_MARKER = 0x05 2429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 2529e2b21c0ad6ff1e1c342264c6563035484e3191Casey DahlinATTRIBUTE_READ_ONLY = 0x1 2629e2b21c0ad6ff1e1c342264c6563035484e3191Casey DahlinATTRIBUTE_HIDDEN = 0x2 2729e2b21c0ad6ff1e1c342264c6563035484e3191Casey DahlinATTRIBUTE_SYSTEM = 0x4 2829e2b21c0ad6ff1e1c342264c6563035484e3191Casey DahlinATTRIBUTE_VOLUME_LABEL = 0x8 2929e2b21c0ad6ff1e1c342264c6563035484e3191Casey DahlinATTRIBUTE_SUBDIRECTORY = 0x10 3029e2b21c0ad6ff1e1c342264c6563035484e3191Casey DahlinATTRIBUTE_ARCHIVE = 0x20 3129e2b21c0ad6ff1e1c342264c6563035484e3191Casey DahlinATTRIBUTE_DEVICE = 0x40 3229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 3329e2b21c0ad6ff1e1c342264c6563035484e3191Casey DahlinLFN_ATTRIBUTES = \ 3429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin ATTRIBUTE_VOLUME_LABEL | \ 3529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin ATTRIBUTE_SYSTEM | \ 3629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin ATTRIBUTE_HIDDEN | \ 3729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin ATTRIBUTE_READ_ONLY 3829e2b21c0ad6ff1e1c342264c6563035484e3191Casey DahlinLFN_ATTRIBUTES_BYTE = struct.pack("B", LFN_ATTRIBUTES) 3929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 4029e2b21c0ad6ff1e1c342264c6563035484e3191Casey DahlinMAX_CLUSTER_ID = 0x7FFF 4129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 4229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlindef read_le_short(f): 4329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin "Read a little-endian 2-byte integer from the given file-like object" 4429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return struct.unpack("<H", f.read(2))[0] 4529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 4629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlindef read_le_long(f): 4729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin "Read a little-endian 4-byte integer from the given file-like object" 4829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return struct.unpack("<L", f.read(4))[0] 4929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 5029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlindef read_byte(f): 5129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin "Read a 1-byte integer from the given file-like object" 5229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return struct.unpack("B", f.read(1))[0] 5329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 5429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlindef skip_bytes(f, n): 5529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin "Fast-forward the given file-like object by n bytes" 5629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin f.seek(n, os.SEEK_CUR) 5729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 5829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlindef skip_short(f): 5929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin "Fast-forward the given file-like object 2 bytes" 6029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin skip_bytes(f, 2) 6129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 6229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlindef skip_byte(f): 6329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin "Fast-forward the given file-like object 1 byte" 6429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin skip_bytes(f, 1) 6529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 6629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlindef rewind_bytes(f, n): 6729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin "Rewind the given file-like object n bytes" 6829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin skip_bytes(f, -n) 6929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 7029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlindef rewind_short(f): 7129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin "Rewind the given file-like object 2 bytes" 7229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin rewind_bytes(f, 2) 7329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 7429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlinclass fake_file(object): 7529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 7629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin Interface for python file-like objects that we use to manipulate the image. 7729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin Inheritors must have an idx member which indicates the file pointer, and a 7829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin size member which indicates the total file size. 7929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 8029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 8129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def seek(self, amount, direction=0): 8229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin "Implementation of seek from python's file-like object interface." 8329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if direction == os.SEEK_CUR: 8429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.idx += amount 8529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin elif direction == os.SEEK_END: 8629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.idx = self.size - amount 8729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin else: 8829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.idx = amount 8929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 9029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if self.idx < 0: 9129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.idx = 0 9229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if self.idx > self.size: 9329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.idx = self.size 9429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 9529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlinclass fat_file(fake_file): 9629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 9729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin A file inside of our fat image. The file may or may not have a dentry, and 9829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if it does this object knows nothing about it. All we see is a valid cluster 9929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin chain. 10029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 10129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 10229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def __init__(self, fs, cluster, size=None): 10329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 10429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin fs: The fat() object for the image this file resides in. 10529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin cluster: The first cluster of data for this file. 10629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin size: The size of this file. If not given, we use the total length of the 10729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin cluster chain that starts from the cluster argument. 10829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 10929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.fs = fs 11029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.start_cluster = cluster 11129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.size = size 11229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 11329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if self.size is None: 11429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.size = fs.get_chain_size(cluster) 11529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 11629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.idx = 0 11729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 11829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def read(self, size): 11929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin "Read method for pythonic file-like interface." 12029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if self.idx + size > self.size: 12129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin size = self.size - self.idx 12229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin got = self.fs.read_file(self.start_cluster, self.idx, size) 12329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.idx += len(got) 12429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return got 12529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 12629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def write(self, data): 12729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin "Write method for pythonic file-like interface." 12829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.fs.write_file(self.start_cluster, self.idx, data) 12929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.idx += len(data) 13029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 13129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if self.idx > self.size: 13229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.size = self.idx 13329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 13429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlindef shorten(name, index): 13529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 13629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin Create a file short name from the given long name (with the extension already 13729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin removed). The index argument gives a disambiguating integer to work into the 13829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin name to avoid collisions. 13929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 14029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin name = "".join(name.split('.')).upper() 14129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin postfix = "~" + str(index) 14229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return name[:8 - len(postfix)] + postfix 14329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 14429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlinclass fat_dir(object): 14529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin "A directory in our fat filesystem." 14629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 14729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def __init__(self, backing): 14829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 14929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin backing: A file-like object from which we can read dentry info. Should have 15029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin an fs member allowing us to get to the underlying image. 15129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 15229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.backing = backing 15329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.dentries = [] 15429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin to_read = self.backing.size / 32 15529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 15629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.backing.seek(0) 15729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 15829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin while to_read > 0: 15929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin (dent, consumed) = self.backing.fs.read_dentry(self.backing) 16029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin to_read -= consumed 16129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 16229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if dent: 16329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.dentries.append(dent) 16429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 16529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def __str__(self): 16629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return "\n".join([str(x) for x in self.dentries]) + "\n" 16729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 16829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def add_dentry(self, attributes, shortname, ext, longname, first_cluster, 16929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin size): 17029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 17129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin Add a new dentry to this directory. 17229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin attributes: Attribute flags for this dentry. See the ATTRIBUTE_ constants 17329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin above. 17429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin shortname: Short name of this file. Up to 8 characters, no dots. 17529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin ext: Extension for this file. Up to 3 characters, no dots. 17629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin longname: The long name for this file, with extension. Largely unrestricted. 17729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin first_cluster: The first cluster in the cluster chain holding the contents 17829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin of this file. 17929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin size: The size of this file. Set to 0 for subdirectories. 18029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 18129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin new_dentry = dentry(self.backing.fs, attributes, shortname, ext, 18229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin longname, first_cluster, size) 18329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin new_dentry.commit(self.backing) 18429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.dentries.append(new_dentry) 18529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return new_dentry 18629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 18729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def make_short_name(self, name): 18829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 18929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin Given a long file name, return an 8.3 short name as a tuple. Name will be 19029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin engineered not to collide with other such names in this folder. 19129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 19229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin parts = name.rsplit('.', 1) 19329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 19429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if len(parts) == 1: 19529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin parts.append('') 19629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 19729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin name = parts[0] 19829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin ext = parts[1].upper() 19929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 20029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin index = 1 20129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin shortened = shorten(name, index) 20229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 20329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin for dent in self.dentries: 20429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin assert dent.longname != name, "File must not exist" 20529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if dent.shortname == shortened: 20629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin index += 1 20729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin shortened = shorten(name, index) 20829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 20929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if len(name) <= 8 and len(ext) <= 3 and not '.' in name: 21029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return (name.upper().ljust(8), ext.ljust(3)) 21129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 21229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return (shortened.ljust(8), ext[:3].ljust(3)) 21329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 21429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def new_file(self, name, data=None): 21529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 21629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin Add a new regular file to this directory. 21729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin name: The name of the new file. 21829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin data: The contents of the new file. Given as a file-like object. 21929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 22029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin size = 0 22129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if data: 22229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin data.seek(0, os.SEEK_END) 22329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin size = data.tell() 22429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 225a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo # Empty files shouldn't have any clusters assigned. 226a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo chunk = self.backing.fs.allocate(size) if size > 0 else 0 22729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin (shortname, ext) = self.make_short_name(name) 22829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.add_dentry(0, shortname, ext, name, chunk, size) 22929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 23029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if data is None: 23129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return 23229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 23329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin data_file = fat_file(self.backing.fs, chunk, size) 23429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin data.seek(0) 23529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin data_file.write(data.read()) 23629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 2379a535b51884fe52be29925e00cc4032230ad9d66Alex Deymo def open_subdirectory(self, name): 23829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 2399a535b51884fe52be29925e00cc4032230ad9d66Alex Deymo Open a subdirectory of this directory with the given name. If the 2409a535b51884fe52be29925e00cc4032230ad9d66Alex Deymo subdirectory doesn't exist, a new one is created instead. 24129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin Returns a fat_dir(). 24229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 2439a535b51884fe52be29925e00cc4032230ad9d66Alex Deymo for dent in self.dentries: 2449a535b51884fe52be29925e00cc4032230ad9d66Alex Deymo if dent.longname == name: 2459a535b51884fe52be29925e00cc4032230ad9d66Alex Deymo return dent.open_directory() 2469a535b51884fe52be29925e00cc4032230ad9d66Alex Deymo 24729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin chunk = self.backing.fs.allocate(1) 24829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin (shortname, ext) = self.make_short_name(name) 249df71efe378937e18f8783229a50f5e010fd634c2Casey Dahlin new_dentry = self.add_dentry(ATTRIBUTE_SUBDIRECTORY, shortname, 250df71efe378937e18f8783229a50f5e010fd634c2Casey Dahlin ext, name, chunk, 0) 251df71efe378937e18f8783229a50f5e010fd634c2Casey Dahlin result = new_dentry.open_directory() 252df71efe378937e18f8783229a50f5e010fd634c2Casey Dahlin 253df71efe378937e18f8783229a50f5e010fd634c2Casey Dahlin parent_cluster = 0 254df71efe378937e18f8783229a50f5e010fd634c2Casey Dahlin 255df71efe378937e18f8783229a50f5e010fd634c2Casey Dahlin if hasattr(self.backing, 'start_cluster'): 256df71efe378937e18f8783229a50f5e010fd634c2Casey Dahlin parent_cluster = self.backing.start_cluster 257df71efe378937e18f8783229a50f5e010fd634c2Casey Dahlin 258df71efe378937e18f8783229a50f5e010fd634c2Casey Dahlin result.add_dentry(ATTRIBUTE_SUBDIRECTORY, '.', '', '', chunk, 0) 259df71efe378937e18f8783229a50f5e010fd634c2Casey Dahlin result.add_dentry(ATTRIBUTE_SUBDIRECTORY, '..', '', '', parent_cluster, 0) 260df71efe378937e18f8783229a50f5e010fd634c2Casey Dahlin 261df71efe378937e18f8783229a50f5e010fd634c2Casey Dahlin return result 26229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 26329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlindef lfn_checksum(name_data): 26429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 26529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin Given the characters of an 8.3 file name (concatenated *without* the dot), 26629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin Compute a one-byte checksum which needs to appear in corresponding long file 26729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin name entries. 26829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 26929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin assert len(name_data) == 11, "Name data should be exactly 11 characters" 27029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin name_data = struct.unpack("B" * 11, name_data) 27129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 27229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin result = 0 27329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 27429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin for char in name_data: 27529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin last_bit = (result & 1) << 7 27629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin result = (result >> 1) | last_bit 27729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin result += char 27829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin result = result & 0xFF 27929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 28029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return struct.pack("B", result) 28129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 28229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlinclass dentry(object): 28329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin "A directory entry" 28429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def __init__(self, fs, attributes, shortname, ext, longname, 28529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin first_cluster, size): 28629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 28729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin fs: The fat() object for the image we're stored in. 28829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin attributes: The attribute flags for this dentry. See the ATTRIBUTE_ flags 28929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin above. 29029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin shortname: The short name stored in this dentry. Up to 8 characters, no 29129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin dots. 29229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin ext: The file extension stored in this dentry. Up to 3 characters, no 29329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin dots. 29429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin longname: The long file name stored in this dentry. 29529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin first_cluster: The first cluster in the cluster chain backing the file 29629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin this dentry points to. 29729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin size: Size of the file this dentry points to. 0 for subdirectories. 29829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 29929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.fs = fs 30029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.attributes = attributes 30129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.shortname = shortname 30229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.ext = ext 30329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.longname = longname 30429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.first_cluster = first_cluster 30529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.size = size 30629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 30729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def name(self): 30829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin "A friendly text file name for this dentry." 30929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if self.longname: 31029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return self.longname 31129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 31229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if not self.ext or len(self.ext) == 0: 31329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return self.shortname 31429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 31529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return self.shortname + "." + self.ext 31629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 31729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def __str__(self): 31829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return self.name() + " (" + str(self.size) + \ 31929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin " bytes @ " + str(self.first_cluster) + ")" 32029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 32129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def is_directory(self): 32229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin "Return whether this dentry points to a directory." 32329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return (self.attributes & ATTRIBUTE_SUBDIRECTORY) != 0 32429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 32529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def open_file(self): 32629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin "Open the target of this dentry if it is a regular file." 32729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin assert not self.is_directory(), "Cannot open directory as file" 32829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return fat_file(self.fs, self.first_cluster, self.size) 32929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 33029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def open_directory(self): 33129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin "Open the target of this dentry if it is a directory." 33229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin assert self.is_directory(), "Cannot open file as directory" 33329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return fat_dir(fat_file(self.fs, self.first_cluster)) 33429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 33529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def longname_records(self, checksum): 33629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 33729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin Get the longname records necessary to store this dentry's long name, 33829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin packed as a series of 32-byte strings. 33929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 34029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if self.longname is None: 34129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return [] 34229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if len(self.longname) == 0: 34329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return [] 34429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 34529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin encoded_long_name = self.longname.encode('utf-16-le') 34629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin long_name_padding = "\0" * (26 - (len(encoded_long_name) % 26)) 34729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin padded_long_name = encoded_long_name + long_name_padding 34829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 34929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin chunks = [padded_long_name[i:i+26] for i in range(0, 35029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin len(padded_long_name), 26)] 35129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin records = [] 35229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin sequence_number = 1 35329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 35429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin for c in chunks: 35529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin sequence_byte = struct.pack("B", sequence_number) 35629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin sequence_number += 1 35729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin record = sequence_byte + c[:10] + LFN_ATTRIBUTES_BYTE + "\0" + \ 35829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin checksum + c[10:22] + "\0\0" + c[22:] 35929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin records.append(record) 36029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 36129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin last = records.pop() 36229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin last_seq = struct.unpack("B", last[0])[0] 36329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin last_seq = last_seq | 0x40 36429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin last = struct.pack("B", last_seq) + last[1:] 36529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin records.append(last) 36629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin records.reverse() 36729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 36829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return records 36929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 37029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def commit(self, f): 37129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 37229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin Write this dentry into the given file-like object, 37329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin which is assumed to contain a FAT directory. 37429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 37529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin f.seek(0) 37629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin padded_short_name = self.shortname.ljust(8) 37729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin padded_ext = self.ext.ljust(3) 37829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin name_data = padded_short_name + padded_ext 37929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin longname_record_data = self.longname_records(lfn_checksum(name_data)) 38029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin record = struct.pack("<11sBBBHHHHHHHL", 38129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin name_data, 38229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.attributes, 38329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 0, 38429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 0, 38529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 0, 38629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 0, 38729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 0, 38829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 0, 38929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 0, 39029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 0, 39129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.first_cluster, 39229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.size) 39329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin entry = "".join(longname_record_data + [record]) 39429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 39529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin record_count = len(longname_record_data) + 1 39629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 39729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin found_count = 0 398a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo while found_count < record_count: 39929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin record = f.read(32) 40029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 40129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if record is None or len(record) != 32: 402a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo # We reached the EOF, so we need to extend the file with a new cluster. 403a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo f.write("\0" * self.fs.bytes_per_cluster) 404a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo f.seek(-self.fs.bytes_per_cluster, os.SEEK_CUR) 405a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo record = f.read(32) 40629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 40729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin marker = struct.unpack("B", record[0])[0] 40829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 40929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if marker == DEL_MARKER or marker == 0: 41029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin found_count += 1 41129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin else: 41229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin found_count = 0 41329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 414a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo f.seek(-(record_count * 32), os.SEEK_CUR) 41529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin f.write(entry) 41629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 41729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlinclass root_dentry_file(fake_file): 41829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 41929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin File-like object for the root directory. The root directory isn't stored in a 42029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin normal file, so we can't use a normal fat_file object to create a view of it. 42129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 42229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def __init__(self, fs): 42329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.fs = fs 42429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.idx = 0 42529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.size = fs.root_entries * 32 42629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 42729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def read(self, count): 42829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin f = self.fs.f 42929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin f.seek(self.fs.data_start() + self.idx) 43029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 43129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if self.idx + count > self.size: 43229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin count = self.size - self.idx 43329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 43429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin ret = f.read(count) 43529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.idx += len(ret) 43629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return ret 43729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 43829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def write(self, data): 43929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin f = self.fs.f 44029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin f.seek(self.fs.data_start() + self.idx) 44129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 44229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if self.idx + len(data) > self.size: 44329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin data = data[:self.size - self.idx] 44429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 44529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin f.write(data) 44629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.idx += len(data) 44729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if self.idx > self.size: 44829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.size = self.idx 44929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 45029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlinclass fat(object): 45129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin "A FAT image" 45229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 45329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def __init__(self, path): 45429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 45529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin path: Path to an image file containing a FAT file system. 45629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 45729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin f = open(path, "r+b") 45829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 45929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.f = f 46029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 46129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin f.seek(0xb) 46229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin bytes_per_sector = read_le_short(f) 46329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin sectors_per_cluster = read_byte(f) 46429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 46529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.bytes_per_cluster = bytes_per_sector * sectors_per_cluster 46629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 46729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin reserved_sectors = read_le_short(f) 46829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin assert reserved_sectors == 1, \ 46929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin "Can only handle FAT with 1 reserved sector" 47029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 47129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin fat_count = read_byte(f) 47229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin assert fat_count == 2, "Can only handle FAT with 2 tables" 47329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 47429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.root_entries = read_le_short(f) 47529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 47629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin skip_short(f) # Image size. Sort of. Useless field. 47729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin skip_byte(f) # Media type. We don't care. 47829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 47929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.fat_size = read_le_short(f) * bytes_per_sector 48029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.root = fat_dir(root_dentry_file(self)) 48129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 48229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def data_start(self): 48329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 48429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin Index of the first byte after the FAT tables. 48529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 48629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return FAT_TABLE_START + self.fat_size * 2 48729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 48829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def get_chain_size(self, head_cluster): 48929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 49029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin Return how many total bytes are in the cluster chain rooted at the given 49129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin cluster. 49229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 49329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if head_cluster == 0: 49429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return 0 49529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 49629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin f = self.f 49729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin f.seek(FAT_TABLE_START + head_cluster * 2) 49829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 49929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin cluster_count = 0 50029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 50129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin while head_cluster <= MAX_CLUSTER_ID: 50229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin cluster_count += 1 50329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin head_cluster = read_le_short(f) 50429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin f.seek(FAT_TABLE_START + head_cluster * 2) 50529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 50629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return cluster_count * self.bytes_per_cluster 50729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 50829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def read_dentry(self, f=None): 50929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 51029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin Read and decode a dentry from the given file-like object at its current 51129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin seek position. 51229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 51329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin f = f or self.f 51429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin attributes = None 51529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 51629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin consumed = 1 51729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 51829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin lfn_entries = {} 51929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 52029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin while True: 52129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin skip_bytes(f, 11) 52229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin attributes = read_byte(f) 52329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin rewind_bytes(f, 12) 52429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 52529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if attributes & LFN_ATTRIBUTES != LFN_ATTRIBUTES: 52629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin break 52729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 52829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin consumed += 1 52929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 53029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin seq = read_byte(f) 53129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin chars = f.read(10) 53229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin skip_bytes(f, 3) # Various hackish nonsense 53329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin chars += f.read(12) 53429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin skip_short(f) # Lots more nonsense 53529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin chars += f.read(4) 53629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 53729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin chars = unicode(chars, "utf-16-le").encode("utf-8") 53829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 53929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin lfn_entries[seq] = chars 54029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 54129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin ind = read_byte(f) 54229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 54329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if ind == 0 or ind == DEL_MARKER: 54429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin skip_bytes(f, 31) 54529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return (None, consumed) 54629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 54729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if ind == ESCAPE_DEL_MARKER: 54829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin ind = DEL_MARKER 54929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 55029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin ind = str(unichr(ind)) 55129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 55229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if ind == '.': 55329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin skip_bytes(f, 31) 55429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return (None, consumed) 55529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 55629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin shortname = ind + f.read(7).rstrip() 55729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin ext = f.read(3).rstrip() 55829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin skip_bytes(f, 15) # Assorted flags, ctime/atime/mtime, etc. 55929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin first_cluster = read_le_short(f) 56029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin size = read_le_long(f) 56129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 56229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin lfn = lfn_entries.items() 56329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin lfn.sort(key=lambda x: x[0]) 56429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin lfn = reduce(lambda x, y: x + y[1], lfn, "") 56529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 56629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if len(lfn) == 0: 56729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin lfn = None 56829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin else: 56929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin lfn = lfn.split('\0', 1)[0] 57029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 57129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return (dentry(self, attributes, shortname, ext, lfn, first_cluster, 57229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin size), consumed) 57329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 57429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def read_file(self, head_cluster, start_byte, size): 57529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 57629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin Read from a given FAT file. 57729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin head_cluster: The first cluster in the file. 57829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin start_byte: How many bytes in to the file to begin the read. 57929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin size: How many bytes to read. 58029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 58129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin f = self.f 58229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 58329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin assert size >= 0, "Can't read a negative amount" 58429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if size == 0: 58529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return "" 58629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 58729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin got_data = "" 58829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 58929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin while True: 59029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin size_now = size 59129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if start_byte + size > self.bytes_per_cluster: 59229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin size_now = self.bytes_per_cluster - start_byte 59329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 59429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if start_byte < self.bytes_per_cluster: 59529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin size -= size_now 59629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 59729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin cluster_bytes_from_root = (head_cluster - 2) * \ 59829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.bytes_per_cluster 59929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin bytes_from_root = cluster_bytes_from_root + start_byte 60029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin bytes_from_data_start = bytes_from_root + self.root_entries * 32 60129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 60229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin f.seek(self.data_start() + bytes_from_data_start) 60329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin line = f.read(size_now) 60429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin got_data += line 60529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 60629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if size == 0: 60729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return got_data 60829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 60929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin start_byte -= self.bytes_per_cluster 61029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 61129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if start_byte < 0: 61229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin start_byte = 0 61329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 61429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin f.seek(FAT_TABLE_START + head_cluster * 2) 61529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin assert head_cluster <= MAX_CLUSTER_ID, "Out-of-bounds read" 61629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin head_cluster = read_le_short(f) 61729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin assert head_cluster > 0, "Read free cluster" 61829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 61929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return got_data 62029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 62129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def write_cluster_entry(self, entry): 62229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 62329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin Write a cluster entry to the FAT table. Assumes our backing file is already 62429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin seeked to the correct entry in the first FAT table. 62529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 62629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin f = self.f 62729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin f.write(struct.pack("<H", entry)) 62829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin skip_bytes(f, self.fat_size - 2) 62929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin f.write(struct.pack("<H", entry)) 63029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin rewind_bytes(f, self.fat_size) 63129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 63229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def allocate(self, amount): 63329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 63429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin Allocate a new cluster chain big enough to hold at least the given amount 63529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin of bytes. 63629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 637df71efe378937e18f8783229a50f5e010fd634c2Casey Dahlin assert amount > 0, "Must allocate a non-zero amount." 638df71efe378937e18f8783229a50f5e010fd634c2Casey Dahlin 63929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin f = self.f 64029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin f.seek(FAT_TABLE_START + 4) 64129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 64229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin current = None 64329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin current_size = 0 64429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin free_zones = {} 64529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 64629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin pos = 2 64729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin while pos < self.fat_size / 2: 64829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin data = read_le_short(f) 64929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 65029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if data == 0 and current is not None: 65129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin current_size += 1 65229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin elif data == 0: 65329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin current = pos 65429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin current_size = 1 65529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin elif current is not None: 65629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin free_zones[current] = current_size 65729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin current = None 65829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 65929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin pos += 1 66029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 66129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if current is not None: 66229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin free_zones[current] = current_size 66329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 66429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin free_zones = free_zones.items() 66529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin free_zones.sort(key=lambda x: x[1]) 66629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 66729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin grabbed_zones = [] 66829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin grabbed = 0 66929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 67029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin while grabbed < amount and len(free_zones) > 0: 67129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin zone = free_zones.pop() 67229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin grabbed += zone[1] * self.bytes_per_cluster 67329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin grabbed_zones.append(zone) 67429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 67529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if grabbed < amount: 67629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return None 67729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 67829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin excess = (grabbed - amount) / self.bytes_per_cluster 67929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 68029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin grabbed_zones[-1] = (grabbed_zones[-1][0], 68129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin grabbed_zones[-1][1] - excess) 68229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 68329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin out = None 68429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin grabbed_zones.reverse() 68529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 68629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin for cluster, size in grabbed_zones: 68729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin entries = range(cluster + 1, cluster + size) 68829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin entries.append(out or 0xFFFF) 68929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin out = cluster 69029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin f.seek(FAT_TABLE_START + cluster * 2) 69129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin for entry in entries: 69229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin self.write_cluster_entry(entry) 69329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 69429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin return out 69529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 69629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def extend_cluster(self, cluster, amount): 69729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 69829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin Given a cluster which is the *last* cluster in a chain, extend it to hold 69929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin at least `amount` more bytes. 70029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 701a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo if amount == 0: 702a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo return 70329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin f = self.f 704a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo entry_offset = FAT_TABLE_START + cluster * 2 705a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo f.seek(entry_offset) 70629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin assert read_le_short(f) == 0xFFFF, "Extending from middle of chain" 70729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 708a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo return_cluster = self.allocate(amount) 709a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo f.seek(entry_offset) 710a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo self.write_cluster_entry(return_cluster) 711a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo return return_cluster 71229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 71329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin def write_file(self, head_cluster, start_byte, data): 71429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 71529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin Write to a given FAT file. 71629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 71729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin head_cluster: The first cluster in the file. 71829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin start_byte: How many bytes in to the file to begin the write. 71929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin data: The data to write. 72029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 72129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin f = self.f 722a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo last_offset = start_byte + len(data) 723a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo current_offset = 0 724a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo current_cluster = head_cluster 725a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo 726a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo while current_offset < last_offset: 727a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo # Write everything that falls in the cluster starting at current_offset. 728a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo data_begin = max(0, current_offset - start_byte) 729a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo data_end = min(len(data), 730a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo current_offset + self.bytes_per_cluster - start_byte) 731a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo if data_end > data_begin: 732a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo cluster_file_offset = (self.data_start() + self.root_entries * 32 + 733a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo (current_cluster - 2) * self.bytes_per_cluster) 734a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo f.seek(cluster_file_offset + max(0, start_byte - current_offset)) 735a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo f.write(data[data_begin:data_end]) 736a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo 737a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo # Advance to the next cluster in the chain or get a new cluster if needed. 738a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo current_offset += self.bytes_per_cluster 739a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo if last_offset > current_offset: 740a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo f.seek(FAT_TABLE_START + current_cluster * 2) 741a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo next_cluster = read_le_short(f) 742a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo if next_cluster > MAX_CLUSTER_ID: 743a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo next_cluster = self.extend_cluster(current_cluster, len(data)) 744a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo current_cluster = next_cluster 745a1c977735b85097dc64a1f4d378b8284cf524899Alex Deymo assert current_cluster > 0, "Cannot write free cluster" 74629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 74729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 74829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlindef add_item(directory, item): 74929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 75029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin Copy a file into the given FAT directory. If the path given is a directory, 75129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin copy recursively. 75229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin directory: fat_dir to copy the file in to 75329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin item: Path of local file to copy 75429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin """ 75529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if os.path.isdir(item): 75629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin base = os.path.basename(item) 75729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if len(base) == 0: 75829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin base = os.path.basename(item[:-1]) 7599a535b51884fe52be29925e00cc4032230ad9d66Alex Deymo sub = directory.open_subdirectory(base) 760567c5d0f9590b101ae4660b484dfd31172c288c9Alex Deymo for next_item in sorted(os.listdir(item)): 76129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin add_item(sub, os.path.join(item, next_item)) 76229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin else: 76329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin with open(item, 'rb') as f: 76429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin directory.new_file(os.path.basename(item), f) 76529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 76629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlinif __name__ == "__main__": 76729e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin if len(sys.argv) < 3: 76829e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin print("Usage: fat16copy.py <image> <file> [<file> ...]") 76929e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin print("Files are copied into the root of the image.") 77029e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin print("Directories are copied recursively") 77129e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin sys.exit(1) 77229e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 77329e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin root = fat(sys.argv[1]).root 77429e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin 77529e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin for p in sys.argv[2:]: 77629e2b21c0ad6ff1e1c342264c6563035484e3191Casey Dahlin add_item(root, p) 777