Skip to content
Snippets Groups Projects
fs_fat32dirent.c 74.9 KiB
Newer Older
/****************************************************************************
 * Name: fat_finddirentry
 *
 * Desciption: Given a path to something that may or may not be in the file
 *   system, return the description of the directory entry of the requested
 *   item.
 *
 * NOTE: As a side effect, this function returns with the sector containing
 *   the short file name directory entry in the cache.
 *
 ****************************************************************************/

int fat_finddirentry(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo,
                     const char *path)
{
  off_t    cluster;
  uint8_t *direntry;
  char     terminator;
  int      ret;

  /* Initialize to traverse the chain.  Set it to the cluster of the root
   * directory
   */

  cluster = fs->fs_rootbase;
  if (fs->fs_type == FSTYPE_FAT32)
    {
      /* For FAT32, the root directory is variable sized and is a cluster
       * chain like any other directory.  fs_rootbase holds the first
       * cluster of the root directory.
       */

      dirinfo->dir.fd_startcluster = cluster;
      dirinfo->dir.fd_currcluster  = cluster;
      dirinfo->dir.fd_currsector   = fat_cluster2sector(fs, cluster);
    }
  else
    {
      /* For FAT12/16, the first sector of the root directory is a sector
       * relative to the first sector of the fat volume.
       */

      dirinfo->dir.fd_startcluster = 0;
      dirinfo->dir.fd_currcluster  = 0;
      dirinfo->dir.fd_currsector   = cluster;
    }

  /* fd_index is the index into the current directory table */

  dirinfo->dir.fd_index = 0;

  /* If no path was provided, then the root directory must be exactly what
   * the caller is looking for.
   */

  if (*path == '\0')
    {
      dirinfo->fd_root = true;
      return OK;
    }

patacongo's avatar
patacongo committed
  /* This is not the root directory */

  dirinfo->fd_root = false;

  /* Now loop until the directory entry corresponding to the path is found */

  for (;;)
    {
      /* Convert the next the path segment name into the kind of name that
       * we would see in the directory entry.
       */

      ret = fat_path2dirname(&path, dirinfo, &terminator);
      if (ret < 0)
        {
          /* ERROR:  The filename contains invalid characters or is
           * too long.
           */

          return ret;
        }

      /* Is this a path segment a long or a short file.  Was a long file
       * name parsed?
       */

#ifdef CONFIG_FAT_LFN
      if (dirinfo->fd_lfname[0] != '\0')
        {
          /* Yes.. Search for the sequence of long file name directory
           * entries. NOTE: As a side effect, this function returns with
           * the sector containing the short file name directory entry
           * in the cache.
           */
 
          ret = fat_findlfnentry(fs, dirinfo);
        }
      else
#endif
        {
          /* No.. Search for the single short file name directory entry */

          ret = fat_findsfnentry(fs, dirinfo);
        }

      /* Did we find the directory entries? */

      if (ret < 0)
        {
          return ret;
        }

      /* If the terminator character in the path was the end of the string
       * then we have successfully found the directory entry that describes
       * the path.
       */

      if (!terminator)
        {
          /* Return success meaning that the description the matching
           * directory entry is in dirinfo.
           */

          return OK;
        }

      /* No.. then we have found one of the intermediate directories on
       * the way to the final path target.  In this case, make sure
       * the thing that we found is, indeed, a directory.
       */

      direntry = &fs->fs_buffer[dirinfo->fd_seq.ds_offset];
      if (!(DIR_GETATTRIBUTES(direntry) & FATATTR_DIRECTORY))
        {
          /* Ooops.. we found something else */

          return -ENOTDIR;
        }

      /* Get the cluster number of this directory */

      cluster =
          ((uint32_t)DIR_GETFSTCLUSTHI(direntry) << 16) |
          DIR_GETFSTCLUSTLO(direntry);

      /* Then restart scanning at the new directory */

      dirinfo->dir.fd_currcluster = dirinfo->dir.fd_startcluster = cluster;
      dirinfo->dir.fd_currsector  = fat_cluster2sector(fs, cluster);
      dirinfo->dir.fd_index       = 2;
    }
}

/****************************************************************************
 * Name: fat_allocatedirentry
 *
 * Desciption: Find a free directory entry
 *
 ****************************************************************************/

int fat_allocatedirentry(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo)
{
  int32_t  cluster;
  off_t    sector;
  int      ret;
  int      i;

  /* Re-initialize directory object */

  cluster = dirinfo->dir.fd_startcluster;

  /* Loop until we successfully allocate the sequence of directory entries
   * or until to fail to extend the directory cluster chain.
   */

  for (;;)
    {
      /* Can this cluster chain be extended */
patacongo's avatar
patacongo committed
      if (cluster)
patacongo's avatar
patacongo committed
         /* Cluster chain can be extended */
patacongo's avatar
patacongo committed
          dirinfo->dir.fd_currcluster = cluster;
          dirinfo->dir.fd_currsector  = fat_cluster2sector(fs, cluster);
        }
      else
        {
patacongo's avatar
patacongo committed
          /* Fixed size FAT12/16 root directory is at fixed offset/size */
patacongo's avatar
patacongo committed
          dirinfo->dir.fd_currsector = fs->fs_rootbase;
        }
    dirinfo->dir.fd_index = 0;
patacongo's avatar
patacongo committed
      /* Is this a path segment a long or a short file.  Was a long file
       * name parsed?
       */
patacongo's avatar
patacongo committed
#ifdef CONFIG_FAT_LFN
      if (dirinfo->fd_lfname[0] != '\0')
patacongo's avatar
patacongo committed
          /* Yes.. Allocate for the sequence of long file name directory
           * entries plus a short file name directory entry.
           */
 
          ret = fat_allocatelfnentry(fs, dirinfo);
        }
patacongo's avatar
patacongo committed
      /* No.. Allocate only a short file name directory entry */

      else
#endif
        {
          ret = fat_allocatesfnentry(fs, dirinfo);
patacongo's avatar
patacongo committed
      /* Did we successfully allocate the directory entries?  If the error
       * value is -ENOSPC, then we can try to extend the directory cluster
       * (we can't handle other return values)
       */

      if (ret == OK || ret != -ENOSPC)
patacongo's avatar
patacongo committed
      /* If we get here, then we have reached the end of the directory table
       * in this sector without finding a free directory entry.
       *
       * It this is a fixed size dirctory entry, then this is an error.
       * Otherwise, we can try to extend the directory cluster chain to
       * make space for the new directory entry.
       */
patacongo's avatar
patacongo committed
      if (!cluster)
        {
          /* The size is fixed */
patacongo's avatar
patacongo committed
          return -ENOSPC;
        }
patacongo's avatar
patacongo committed
      /* Try to extend the cluster chain for this directory */
patacongo's avatar
patacongo committed
      cluster = fat_extendchain(fs, dirinfo->dir.fd_currcluster);
      if (cluster < 0)
        {
          return cluster;
        }
patacongo's avatar
patacongo committed
     /* Flush out any cached data in fs_buffer.. we are going to use
      * it to initialize the new directory cluster.
      */

      ret = fat_fscacheflush(fs);
      if (ret < 0)
        {
          return ret;
        }
patacongo's avatar
patacongo committed
      /* Clear all sectors comprising the new directory cluster */
patacongo's avatar
patacongo committed
      fs->fs_currentsector = fat_cluster2sector(fs, cluster);
      memset(fs->fs_buffer, 0, fs->fs_hwsectorsize);
patacongo's avatar
patacongo committed
      for (i = fs->fs_fatsecperclus; i; i--)
patacongo's avatar
patacongo committed
          ret = fat_hwwrite(fs, fs->fs_buffer, sector, 1);
          if ( ret < 0)
            {
              return ret;
            }
          sector++;
        }
    }
}

/****************************************************************************
 * Name: fat_freedirentry
 *
 * Desciption:  Free the directory entry.
 *
 * NOTE: As a side effect, this function returns with the sector containing
 *   the deleted short file name directory entry in the cache.
 *
 ****************************************************************************/

int fat_freedirentry(struct fat_mountpt_s *fs, struct fat_dirseq_s *seq)
patacongo's avatar
patacongo committed
#ifdef CONFIG_FAT_LFN
  struct fs_fatdir_s dir;
patacongo's avatar
patacongo committed
  uint16_t diroffset;
patacongo's avatar
patacongo committed
  /* Set it to the cluster containing the "last" LFN entry (that appears
   * first on the media).
   */

  dir.fd_currcluster = seq->ds_lfncluster;
  dir.fd_currsector  = seq->ds_lfnsector;
  dir.fd_index       = seq->ds_lfnoffset / DIR_SIZE;
patacongo's avatar
patacongo committed

  /* Free all of the directory entries used for the sequence of long file name
   * and for the single short file name entry.
   */

  for (;;)
    {
      /* Read the directory sector into the sector cache */

      ret = fat_fscacheread(fs, dir.fd_currsector);
patacongo's avatar
patacongo committed
      if (ret < 0)
        {
          return ret;
        }

      /* Get a pointer to the directory entry */

      diroffset = (dir.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE;
patacongo's avatar
patacongo committed
      direntry  = &fs->fs_buffer[diroffset];

      /* Then mark the entry as deleted */

      direntry[DIR_NAME] = DIR0_EMPTY;
      fs->fs_dirty       = true;

      /* Did we just free the single short file name entry? */

      if (dir.fd_currsector == seq->ds_sector &&
          diroffset == seq->ds_offset)
patacongo's avatar
patacongo committed
        {
          /* Yes.. then we are finished. flush anything remaining in the
           * cache and return, probably successfully.
           */

          return fat_fscacheflush(fs);
        }

      /* There are more entries to go.. Try the next directory entry */
      ret = fat_nextdirentry(fs, &dir);
patacongo's avatar
patacongo committed
      if (ret < 0)
        {
          return ret;
        }
    }

#else
  uint8_t *direntry;
  int      ret;

  /* Free the single short file name entry.
   *
   * Make sure that the sector containing the directory entry is in the
   * cache.
   */

  ret = fat_fscacheread(fs, seq->ds_sector);
  if (ret == OK)
    {
      /* Then mark the entry as deleted */

      direntry           = &fs->fs_buffer[seq->ds_offset];
      direntry[DIR_NAME] = DIR0_EMPTY;
      fs->fs_dirty       = true;
    }

  return ret;
patacongo's avatar
patacongo committed
#endif
}

/****************************************************************************
 * Name: fat_dirname2path
 *
 * Desciption:  Convert a filename in a raw directory entry into a user
 *    filename.  This is essentially the inverse operation of that performed
 *    by fat_path2dirname.  See that function for more details.
 *
 ****************************************************************************/

int fat_dirname2path(struct fat_mountpt_s *fs, struct fs_dirent_s *dir)
  uint16_t diroffset;
  uint8_t *direntry;
#ifdef CONFIG_FAT_LFN
  uint8_t attribute;
#endif

  /* Get a reference to the current directory entry */

  diroffset = (dir->u.fat.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE;
  direntry = &fs->fs_buffer[diroffset];
  /* Does this entry refer to the last entry of a long file name? */
#ifdef CONFIG_FAT_LFN
  attribute = DIR_GETATTRIBUTES(direntry);
  if (((*direntry & LDIR0_LAST) != 0 && attribute == LDDIR_LFNATTR))
    {
      /* Yes.. Get the name from a sequence of long file name directory
       * entries.
       */
 
      return fat_getlfname(fs, dir);
    {
      /* No.. Get the name from a short file name directory entries */
      return fat_getsfname(direntry, dir->fd_dir.d_name, NAME_MAX+1);
    }
}

/****************************************************************************
 * Name: fat_dirnamewrite
 *
 * Desciption: Write the (possibly long) directory entry name.  This function
 *   is called only from fat_rename to write the new file name.
 * Assumption:  The directory sector containing the short file name entry
 *   is in the cache.  *NOT* the sector containing the last long file name
 *   entry!
 *
 ****************************************************************************/

int fat_dirnamewrite(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo)
{
#ifdef CONFIG_FAT_LFN
  int ret;

  /* Is this a long file name? */

  if (dirinfo->fd_lfname[0] != '\0')
    {
      /* Write the sequence of long file name directory entries (this function
       * also creates the short file name alias).
       */
      ret = fat_putlfname(fs, dirinfo);
      if (ret != OK)
        {
          return ret;
        }
    }

  /* On return, fat_lfsfname() will leave the short file name entry in the
   * cache.  So we can just fall throught to write that directory entry, perhaps
   * using the short file name alias for the long file name.
   */

  return fat_putsfname(fs, dirinfo);
}

/****************************************************************************
 * Name: fat_dirwrite
 *
 * Desciption: Write a directory entry, possibly with a long file name.
 *   Called from:
 *
 *   fat_mkdir() to write the new FAT directory entry.
 *   fat_dircreate() to create any new directory entry.
 *
 * Assumption:  The directory sector is in the cache.  The caller will write
 *   sector information.
 *
 ****************************************************************************/

int fat_dirwrite(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo,
                 uint8_t attributes, uint32_t fattime)
{
#ifdef CONFIG_FAT_LFN
  int ret;

  /* Does this directory entry have a long file name? */
  if (dirinfo->fd_lfname[0] != '\0')
      /* Write the sequence of long file name directory entries (this function
       * also creates the short file name alias).
       */
      ret = fat_putlfname(fs, dirinfo);
  /* On return, fat_lfsfname() will leave the short file name entry in the
   * cache.  So we can just fall throught to write that directory entry, perhaps
   * using the short file name alias for the long file name.
   */
#endif
  /* Put the short file name entry data */
  return fat_putsfdirentry(fs, dirinfo, attributes, fattime);
}

/****************************************************************************
 * Name: fat_dircreate
 *
 * Desciption: Create a directory entry for a new file
 *
 ****************************************************************************/

int fat_dircreate(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo)
{
  uint32_t fattime;
  int ret;

  /* Allocate a directory entry.  If long file name support is enabled, then
   * this might, in fact, allocate a sequence of directory entries.
   */

  ret = fat_allocatedirentry(fs, dirinfo);
  if (ret != OK)
    {
      /* Failed to allocate the required directory entry or entries. */
  /* Write the directory entry (or entries) with the current time and the
   * ARCHIVE attribute.
   */

  fattime = fat_systime2fattime();
  return fat_dirwrite(fs, dirinfo, FATATTR_ARCHIVE, fattime);
}

/****************************************************************************
 * Name: fat_remove
 *
 * Desciption: Remove a directory or file from the file system.  This
 *   implements both rmdir() and unlink().
 *
 ****************************************************************************/

int fat_remove(struct fat_mountpt_s *fs, const char *relpath, bool directory)
{
  struct fat_dirinfo_s dirinfo;
  uint32_t             dircluster;
  uint8_t             *direntry;
  int                  ret;

  /* Find the directory entry referring to the entry to be deleted */

  ret = fat_finddirentry(fs, &dirinfo, relpath);
  if (ret != OK)
    {
      /* No such path */

      return -ENOENT;
    }

  /* Check if this is a FAT12/16 root directory */

    {
      /* The root directory cannot be removed */

      return -EPERM;
    }

  /* The object has to have write access to be deleted */

  direntry = &fs->fs_buffer[dirinfo.fd_seq.ds_offset];
  if ((DIR_GETATTRIBUTES(direntry) & FATATTR_READONLY) != 0)
    {
      /* It is a read-only entry */

      return -EACCES;
    }

  /* Get the directory sector and cluster containing the entry to be deleted. */
      ((uint32_t)DIR_GETFSTCLUSTHI(direntry) << 16) |
      DIR_GETFSTCLUSTLO(direntry);

  /* Is this entry a directory? */

  if (DIR_GETATTRIBUTES(direntry) & FATATTR_DIRECTORY)
    {
      /* It is a sub-directory. Check if we are be asked to remove
       * a directory or a file.
       */

      if (!directory)
        {
          /* We are asked to delete a file */

          return -EISDIR;
        }

      /* We are asked to delete a directory. Check if this
       * sub-directory is empty
       */

      dirinfo.dir.fd_currcluster = dircluster;
      dirinfo.dir.fd_currsector  = fat_cluster2sector(fs, dircluster);
      dirinfo.dir.fd_index       = 2;

      /* Loop until either (1) an entry is found in the directory (error),
       * (2) the directory is found to be empty, or (3) some error occurs.
       */

      for (;;)
        {
          unsigned int subdirindex;
          uint8_t     *subdirentry;

          /* Make sure that the sector containing the of the subdirectory
           * sector is in the cache
           */

          ret = fat_fscacheread(fs, dirinfo.dir.fd_currsector);
          if (ret < 0)
            {
              return ret;
            }

          /* Get a reference to the next entry in the directory */

          subdirindex = (dirinfo.dir.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE;
          subdirentry = &fs->fs_buffer[subdirindex];

          /* Is this the last entry in the direcory? */

          if (subdirentry[DIR_NAME] == DIR0_ALLEMPTY)
            {
              /* Yes then the directory is empty.  Break out of the
               * loop and delete the directory.
               */

              break;
            }

          /* Check if the next entry refers to a file or directory */

          if (subdirentry[DIR_NAME] != DIR0_EMPTY &&
              !(DIR_GETATTRIBUTES(subdirentry) & FATATTR_VOLUMEID))
            {
              /* The directory is not empty */

              return -ENOTEMPTY;
            }

          /* Get the next directory entry */

          ret = fat_nextdirentry(fs, &dirinfo.dir);
          if (ret < 0)
            {
              return ret;
            }
        }
    }
  else
    {
      /* It is a file. Check if we are be asked to remove a directory
       * or a file.
       */

      if (directory)
        {
          /* We are asked to remove a directory */

          return -ENOTDIR;
        }
    }

  /* Mark the directory entry 'deleted'.  If long file name support is
   * enabled, then multiple directory entries may be freed.
   */
  ret = fat_freedirentry(fs, &dirinfo.fd_seq);
  if (ret < 0)
    {
      return ret;
    }

  /* And remove the cluster chain making up the subdirectory */

  ret = fat_removechain(fs, dircluster);
  if (ret < 0)
    {
      return ret;
    }

  /* Update the FSINFO sector (FAT32) */

  ret = fat_updatefsinfo(fs);
  if (ret < 0)
    {
      return ret;
    }

  return OK;
}