Skip to content
Snippets Groups Projects
fs_fat32dirent.c 72.5 KiB
Newer Older
  if (*path == '\0')
    {
      dirinfo->fd_root = true;
      return OK;
    }

  /* Otherwise, loop until 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
        {
          /* Fixed size FAT12/16 root directory is at fixxed 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
      sector = sector;
      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
  uint16_t diroffset;
patacongo's avatar
patacongo committed
  /* Set it to the cluster containing the "last" LFN entry (that appears
   * first on the media).
   */

  dirinfo->dir.fd_startcluster = dirinfo->fd_seq.fd_lfncluster;
  dirinfo->dir.fd_currcluster  = dirinfo->fd_seq.fd_lfnsector;
  dirinfo->dir.fd_index        = dirinfo->fd_seq.fd_lfnoffset / DIR_SIZE;

  /* 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, dirinfo->dir.fd_currsector);
      if (ret < 0)
        {
          return ret;
        }

      /* Get a pointer to the directory entry */

      diroffset = (dirinfo->dir.fd_index & DIRSEC_NDXMASK(fs)) * DIR_SIZE;
      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 (dirinfo->dir.fd_currsector == dirinfo->fd_seq.fd_sector &&
          diroffset == dirinfo->fd_seq.fd_sector)
        {
          /* Yes.. then we are finished. flush anything remaining in the
           * cache and return, probably successfully.
           */

          return fat_fscacheflush(fs);
        }

      /* There are moe entries to go.. Try the next directory entry */

      ret = fat_nextdirentry(fs, &dirinfo->dir);
      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 fs_dirent_s *dir, uint8_t *direntry)
#ifdef CONFIG_FAT_LFN
  uint8_t attribute = DIR_GETATTRIBUTES(direntry);
  /* Does this entry refer to the last entry of a long file name? */
  if (((*direntry & LDIR0_LAST) != 0 && attribute == LDDIR_LFNATTR))
    {
      /* Yes.. Get the name from a sequence of long file name directory
       * entries.
       */
 
      return fat_getlfname(dir);
    }
  else
    {
      /* 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)
{
  /* Is this a long file name? */

#ifdef CONFIG_FAT_LFN
  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)
{
  /* Does this directory entry have a long file name? */
#ifdef CONFIG_FAT_LFN
  if (dirinfo.fd_lfname[0] != '\0')
    {
      /* Yes.. write the long file name directory entries. (This function
       * also creates the short file name alias). */
 
      ret = fat_putsfdirentry(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.
   */
  /* 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;
}