EXT2Err EXT2Rm()

in lsvmutils/ext2.c [3804:4060]


EXT2Err EXT2Rm(
    EXT2* ext2,
    const char* path)
{
    EXT2_DECLARE_ERR(err);
    char dirname[EXT2_PATH_MAX];
    char filename[EXT2_PATH_MAX];
    void* blocks = NULL;
    UINT32 blocks_size = 0;
    void* new_blocks = NULL;
    UINT32 new_blocks_size = 0;
    BufU32 blknos = BUF_U32_INITIALIZER;
    EXT2Ino ino;
    EXT2Inode inode;
    const EXT2DirEntry* ent = NULL;
#if !defined(BUILD_EFI)
    (void)_DumpDirectoryEntries;
#endif /* !defined(BUILD_EFI) */

    /* Check parameters */
    if (!EXT2Valid(ext2) && !path)
    {
        err = EXT2_ERR_INVALID_PARAMETER;
        GOTO(done);
    }

    /* Truncate the file first */
    if (EXT2_IFERR(err = EXT2Trunc(ext2, path)))
    {
        GOTO(done);
    }

    /* Split the path */
    if (EXT2_IFERR(err = _SplitFullPath(path, dirname, filename)))
    {
        GOTO(done);
    }

    /* Load the directory inode */
    if (EXT2_IFERR(err = EXT2PathToInode(ext2, dirname, &ino, &inode)))
    {
        GOTO(done);
    }

    /* Load the directory file */
    if (EXT2_IFERR(err = EXT2LoadFileFromInode(
        ext2, 
        &inode, 
        &blocks, 
        &blocks_size)))
    {
        GOTO(done);
    }

    /* Load the block numbers (including the block blocks) */
    if (EXT2_IFERR(err = _LoadBlockNumbersFromInode(
        ext2,
        &inode,
        1, /* include_block_blocks */
        &blknos)))
    {
        GOTO(done);
    }

    /* Find 'filename' within this directory */
    if (!(ent = _FindDirectoryEntry(filename, blocks, blocks_size)))
    {
        GOTO(done);
    }

    /* Allow removal of empty directories only */
    if (ent->file_type == EXT2_FT_DIR)
    {
        EXT2Ino dir_ino;
        EXT2Inode dir_inode;
        UINT32 count;

        /* Find the inode of the filename */
        if (EXT2_IFERR(err = EXT2PathToInode(
            ext2, filename, &dir_ino, &dir_inode)))
        {
            GOTO(done);
        }

        /* Disallow removal if directory is non empty */
        if (EXT2_IFERR(err = _CountDirectoryEntriesIno(ext2, dir_ino, &count)))
        {
            GOTO(done);
        }

        /* Expect just "." and ".." entries */
        if (count != 2)
        {
            GOTO(done);
        }
    }

    /* Convert from 'indexed' to 'linked list' directory format */
    {
        new_blocks_size = blocks_size;
        const char* src = (const char*)blocks;
        const char* src_end = (const char*)blocks + inode.i_size;
        char* dest = NULL;

        /* Allocate a buffer to hold 'linked list' directory */
        if (!(new_blocks = Calloc(new_blocks_size, 1)))
        {
            GOTO(done);
        }

        /* Set current and end pointers to new buffer */
        dest = (char*)new_blocks;

        /* Copy over directory entries (skipping removed entry) */
        {
            EXT2DirEntry* prev = NULL;

            while (src < src_end)
            {
                const EXT2DirEntry* curr_ent = 
                    (const EXT2DirEntry*)src;
                UINT32 rec_len;
                UINT32 offset;

                /* Skip the removed directory entry */
                if (curr_ent == ent || !ent->name)
                {
                    src += curr_ent->rec_len;
                    continue;
                }

                /* Compute size of the new directory entry */
                rec_len = sizeof(*curr_ent) - EXT2_PATH_MAX + curr_ent->name_len;
                rec_len = _NextMult(rec_len, 4);

                /* Compute byte offset into current block */
                offset = (dest - (char*)new_blocks) % ext2->block_size;

                /* If new entry would overflow the block */
                if (offset + rec_len > ext2->block_size)
                {
                    UINT32 rem = ext2->block_size - offset;

                    if (!prev)
                    {
                        GOTO(done);
                    }

                    /* Adjust previous entry to point to next block */
                    prev->rec_len += rem;
                    dest += rem;
                }

                /* Copy this entry into new buffer */
                {
                    EXT2DirEntry* new_ent = 
                        (EXT2DirEntry*)dest;
                    Memset(new_ent, 0, rec_len);
                    Memcpy(new_ent, curr_ent, 
                        sizeof(*curr_ent) + curr_ent->name_len);

                    new_ent->rec_len = rec_len;
                    prev = new_ent;
                    dest += rec_len;
                }

                src += curr_ent->rec_len;
            }

            /* Set final entry to point to end of the block */
            if (prev)
            {
                UINT32 offset;
                UINT32 rem;

                /* Compute byte offset into current block */
                offset = (dest - (char*)new_blocks) % ext2->block_size;

                /* Compute remaining bytes */
                rem = ext2->block_size - offset;

                /* Set record length of final entry to end of block */
                prev->rec_len += rem;

                /* Advance dest to block boundary */
                dest += rem;
            }

            /* Size down the new blocks size */
            new_blocks_size = (UINT32)(dest - (char*)new_blocks);

            if (EXT2_IFERR(err = _CheckDirectoryEntries(
                ext2, 
                new_blocks, 
                new_blocks_size)))
            {
                GOTO(done);
            }
        }
    }

    /* Count directory entries before and after */
    {
        UINT32 count;
        UINT32 new_count;

        if (EXT2_IFERR(err = _CountDirectoryEntries(
            ext2, 
            blocks, 
            blocks_size, 
            &count)))
        {
            GOTO(done);
        }

        if (EXT2_IFERR(err = _CountDirectoryEntries(
            ext2, 
            new_blocks, 
            new_blocks_size, 
            &new_count)))
        {
            GOTO(done);
        }
    }

    /* Return all directory blocks to the free list */
    if (EXT2_IFERR(err = _PutBlocks(ext2, blknos.data, blknos.size)))
    {
        GOTO(done);
    }

    /* Update the inode blocks */
    if (EXT2_IFERR(err = _UpdateInodeDataBlocks(
        ext2,
        ino,
        &inode,
        new_blocks,
        new_blocks_size,
        1))) /* is_dir */
    {
        GOTO(done);
    }

    err = EXT2_ERR_NONE;

done:

    if (blocks)
        Free(blocks);

    if (new_blocks)
        Free(new_blocks);

    BufU32Release(&blknos);

    return err;
}