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