mirror of
https://github.com/wiidev/usbloadergx.git
synced 2024-11-15 16:05:10 +01:00
1077 lines
24 KiB
C
1077 lines
24 KiB
C
/**
|
|
* ext2_internal.c - Internal support routines for EXT2-based devices.
|
|
*
|
|
* Copyright (c) 2006 Michael "Chishm" Chisholm
|
|
* Copyright (c) 2009 Rhys "Shareese" Koedijk
|
|
* Copyright (c) 2010 Dimok
|
|
*
|
|
* This program/include file is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as published
|
|
* by the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program/include file is distributed in the hope that it will be
|
|
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include "ext2_internal.h"
|
|
#include "ext2dir.h"
|
|
#include "ext2file.h"
|
|
#include "gekko_io.h"
|
|
|
|
// EXT2 device driver devoptab
|
|
static const devoptab_t devops_ext2 =
|
|
{
|
|
NULL, /* Device name */
|
|
sizeof(ext2_file_state),
|
|
ext2_open_r,
|
|
ext2_close_r,
|
|
ext2_write_r,
|
|
ext2_read_r,
|
|
ext2_seek_r,
|
|
ext2_fstat_r,
|
|
ext2_stat_r,
|
|
ext2_link_r,
|
|
ext2_unlink_r,
|
|
ext2_chdir_r,
|
|
ext2_rename_r,
|
|
ext2_mkdir_r,
|
|
sizeof(ext2_dir_state),
|
|
ext2_diropen_r,
|
|
ext2_dirreset_r,
|
|
ext2_dirnext_r,
|
|
ext2_dirclose_r,
|
|
ext2_statvfs_r,
|
|
ext2_ftruncate_r,
|
|
ext2_fsync_r,
|
|
NULL /* Device data */
|
|
};
|
|
|
|
|
|
const devoptab_t *ext2GetDevOpTab()
|
|
{
|
|
return &devops_ext2;
|
|
}
|
|
|
|
int ext2AddDevice (const char *name, void *deviceData)
|
|
{
|
|
const devoptab_t *devoptab_ext2 = ext2GetDevOpTab();
|
|
devoptab_t *dev = NULL;
|
|
char *devname = NULL;
|
|
int i;
|
|
|
|
// Sanity check
|
|
if (!name || !deviceData || !devoptab_ext2) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
// Allocate a devoptab for this device
|
|
dev = (devoptab_t *) mem_alloc(sizeof(devoptab_t) + strlen(name) + 1);
|
|
if (!dev) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
// Use the space allocated at the end of the devoptab for storing the device name
|
|
devname = (char*)(dev + 1);
|
|
strcpy(devname, name);
|
|
|
|
// Setup the devoptab
|
|
memcpy(dev, devoptab_ext2, sizeof(devoptab_t));
|
|
dev->name = devname;
|
|
dev->deviceData = deviceData;
|
|
|
|
// Add the device to the devoptab table (if there is a free slot)
|
|
for (i = 0; i < STD_MAX; i++) {
|
|
if (devoptab_list[i] == devoptab_list[0] && i != 0) {
|
|
devoptab_list[i] = dev;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// If we reach here then there are no free slots in the devoptab table for this device
|
|
errno = EADDRNOTAVAIL;
|
|
return -1;
|
|
}
|
|
|
|
void ext2RemoveDevice (const char *path)
|
|
{
|
|
const devoptab_t *devoptab = NULL;
|
|
char name[128] = {0};
|
|
int i;
|
|
|
|
// Get the device name from the path
|
|
strncpy(name, path, 127);
|
|
strtok(name, ":/");
|
|
|
|
// Find and remove the specified device from the devoptab table
|
|
// NOTE: We do this manually due to a 'bug' in RemoveDevice
|
|
// which ignores names with suffixes
|
|
for (i = 0; i < STD_MAX; i++) {
|
|
devoptab = devoptab_list[i];
|
|
if (devoptab && devoptab->name) {
|
|
if (strcmp(name, devoptab->name) == 0) {
|
|
devoptab_list[i] = devoptab_list[0];
|
|
mem_free((devoptab_t*)devoptab);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
const devoptab_t *ext2GetDevice (const char *path)
|
|
{
|
|
const devoptab_t *devoptab = NULL;
|
|
char name[128] = {0};
|
|
int i;
|
|
|
|
// Get the device name from the path
|
|
strncpy(name, path, 127);
|
|
strtok(name, ":/");
|
|
|
|
// Search the devoptab table for the specified device name
|
|
// NOTE: We do this manually due to a 'bug' in GetDeviceOpTab
|
|
// which ignores names with suffixes
|
|
for (i = 0; i < STD_MAX; i++) {
|
|
devoptab = devoptab_list[i];
|
|
if (devoptab && devoptab->name) {
|
|
if (strcmp(name, devoptab->name) == 0) {
|
|
return devoptab;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
ext2_vd *ext2GetVolume (const char *path)
|
|
{
|
|
// Get the volume descriptor from the paths associated devoptab (if found)
|
|
const devoptab_t *devoptab_ext2 = ext2GetDevOpTab();
|
|
const devoptab_t *devoptab = ext2GetDevice(path);
|
|
if (devoptab && devoptab_ext2 && (devoptab->open_r == devoptab_ext2->open_r))
|
|
return (ext2_vd*)devoptab->deviceData;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int ext2InitVolume (ext2_vd *vd)
|
|
{
|
|
// Sanity check
|
|
if (!vd) {
|
|
errno = ENODEV;
|
|
return -1;
|
|
}
|
|
|
|
// Reset the volumes data
|
|
memset(vd, 0, sizeof(ext2_vd));
|
|
|
|
// Initialise the volume lock
|
|
LWP_MutexInit(&vd->lock, false);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ext2DeinitVolume (ext2_vd *vd)
|
|
{
|
|
// Sanity check
|
|
if (!vd) {
|
|
errno = ENODEV;
|
|
return;
|
|
}
|
|
|
|
// Lock
|
|
ext2Lock(vd);
|
|
|
|
// Close any directories which are still open (lazy programmers!)
|
|
ext2_dir_state *nextDir = vd->firstOpenDir;
|
|
while (nextDir) {
|
|
ext2CloseDir(nextDir);
|
|
nextDir = nextDir->nextOpenDir;
|
|
}
|
|
|
|
// Close any files which are still open (lazy programmers!)
|
|
ext2_file_state *nextFile = vd->firstOpenFile;
|
|
while (nextFile) {
|
|
ext2CloseFile(nextFile);
|
|
nextFile = nextFile->nextOpenFile;
|
|
}
|
|
|
|
// Reset open directory and file stats
|
|
vd->openDirCount = 0;
|
|
vd->openFileCount = 0;
|
|
vd->firstOpenDir = NULL;
|
|
vd->firstOpenFile = NULL;
|
|
|
|
// Force the underlying device to sync
|
|
ext2Sync(vd, NULL);
|
|
|
|
// Unlock
|
|
ext2Unlock(vd);
|
|
|
|
// Deinitialise the volume lock
|
|
LWP_MutexDestroy(vd->lock);
|
|
}
|
|
|
|
static ext2_ino_t ext2PathToInode(ext2_vd *vd, const char * path)
|
|
{
|
|
//Sanity check
|
|
if(!vd || !path)
|
|
return 0;
|
|
|
|
char filename[EXT2_NAME_LEN];
|
|
errcode_t errorcode = 0;
|
|
ext2_ino_t ino = 0, parent = vd->cwd_ni && *path != '/' && *path != '\0' ? vd->cwd_ni->ino : vd->root;
|
|
const char * ptr = path;
|
|
int i;
|
|
|
|
while(*ptr == '/') ++ptr;
|
|
|
|
if(*ptr == '\0')
|
|
return parent;
|
|
|
|
while(*ptr != '\0')
|
|
{
|
|
for(i = 0; *ptr != '\0' && *ptr != '/' && (i < EXT2_NAME_LEN-1); ++ptr, ++i)
|
|
filename[i] = *ptr;
|
|
|
|
filename[i] = '\0';
|
|
|
|
errorcode = ext2fs_namei_follow(vd->fs, vd->root, parent, filename, &ino);
|
|
if(errorcode != EXT2_ET_OK)
|
|
return 0;
|
|
|
|
parent = ino;
|
|
|
|
while(*ptr == '/') ++ptr;
|
|
|
|
}
|
|
|
|
|
|
return ino;
|
|
}
|
|
|
|
ext2_inode_t *ext2OpenEntry (ext2_vd *vd, const char *path)
|
|
{
|
|
errcode_t errorcode = 0;
|
|
ext2_inode_t * ni = 0;
|
|
|
|
// Sanity check
|
|
if (!vd) {
|
|
errno = ENODEV;
|
|
return NULL;
|
|
}
|
|
|
|
// Get the actual path of the entry
|
|
path = ext2RealPath(path);
|
|
if (!path)
|
|
{
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
|
|
ni = mem_alloc(sizeof(ext2_inode_t));
|
|
if(!ni)
|
|
{
|
|
errno = ENOMEM;
|
|
return NULL;
|
|
}
|
|
|
|
memset(ni, 0, sizeof(ext2_inode_t));
|
|
|
|
// Find the entry, taking into account our current directory (if any)
|
|
ni->ino = ext2PathToInode(vd, path);
|
|
if(ni->ino == 0)
|
|
{
|
|
mem_free(ni);
|
|
return NULL;
|
|
}
|
|
|
|
errorcode = ext2fs_read_inode(vd->fs, ni->ino, &ni->ni);
|
|
if(errorcode)
|
|
{
|
|
mem_free(ni);
|
|
return NULL;
|
|
}
|
|
|
|
return ni;
|
|
}
|
|
|
|
void ext2CloseEntry(ext2_vd *vd, ext2_inode_t * ni)
|
|
{
|
|
// Sanity check
|
|
if (!vd || !ni) {
|
|
errno = ENODEV;
|
|
return;
|
|
}
|
|
|
|
// Lock
|
|
ext2Lock(vd);
|
|
|
|
// Sync the entry (if it is dirty)
|
|
if(ni && ni->dirty)
|
|
ext2fs_write_inode(vd->fs, ni->ino, &ni->ni);
|
|
|
|
// Close the entry
|
|
if(ni)
|
|
mem_free(ni);
|
|
|
|
// Unlock
|
|
ext2Unlock(vd);
|
|
|
|
return;
|
|
}
|
|
|
|
static ext2_ino_t ext2CreateSymlink(ext2_vd *vd, const char *path, const char * targetdir, const char * name, mode_t type)
|
|
{
|
|
ext2_inode_t *target_ni = NULL;
|
|
ext2_ino_t newentry = 0;
|
|
ext2_ino_t ino = 0;
|
|
|
|
// Check if it does exist
|
|
target_ni = ext2OpenEntry(vd, targetdir);
|
|
if (!target_ni)
|
|
goto cleanup;
|
|
|
|
int err = ext2fs_new_inode(vd->fs, target_ni->ino, type, 0, &ino);
|
|
if (err)
|
|
goto cleanup;
|
|
|
|
do
|
|
{
|
|
err = ext2fs_link(vd->fs, target_ni->ino, name, ino, EXT2_FT_SYMLINK);
|
|
if (err == EXT2_ET_DIR_NO_SPACE)
|
|
{
|
|
err = ext2fs_expand_dir(vd->fs, target_ni->ino);
|
|
if (err)
|
|
goto cleanup;
|
|
}
|
|
}
|
|
while(err == EXT2_ET_DIR_NO_SPACE);
|
|
|
|
ext2fs_inode_alloc_stats2(vd->fs, ino, +1, 0);
|
|
|
|
struct ext2_inode inode;
|
|
memset(&inode, 0, sizeof(inode));
|
|
inode.i_mode = type;
|
|
inode.i_atime = inode.i_ctime = inode.i_mtime = time(NULL);
|
|
inode.i_links_count = 1;
|
|
inode.i_size = strlen(path); //initial size of file
|
|
inode.i_uid = target_ni->ni.i_uid;
|
|
inode.i_gid = target_ni->ni.i_gid;
|
|
|
|
if (strlen(path) <= sizeof(inode.i_block))
|
|
{
|
|
/* fast symlink */
|
|
strncpy((char *)&(inode.i_block[0]),path,sizeof(inode.i_blocks));
|
|
}
|
|
else
|
|
{
|
|
/* slow symlink */
|
|
char * buffer = mem_alloc(vd->fs->blocksize);
|
|
if (buffer)
|
|
{
|
|
blk_t blk;
|
|
strncpy(buffer, path, vd->fs->blocksize);
|
|
err = ext2fs_new_block(vd->fs, 0, 0, &blk);
|
|
if (!err)
|
|
{
|
|
inode.i_block[0] = blk;
|
|
inode.i_blocks = vd->fs->blocksize / 512;
|
|
vd->fs->io->manager->write_blk(vd->fs->io, blk, 1, buffer);
|
|
ext2fs_block_alloc_stats(vd->fs, blk, +1);
|
|
}
|
|
mem_free(buffer);
|
|
}
|
|
}
|
|
|
|
if(ext2fs_write_new_inode(vd->fs, ino, &inode) != 0)
|
|
newentry = ino;
|
|
|
|
cleanup:
|
|
|
|
if(target_ni)
|
|
ext2CloseEntry(vd, target_ni);
|
|
|
|
return newentry;
|
|
}
|
|
|
|
static ext2_ino_t ext2CreateMkDir(ext2_vd *vd, ext2_inode_t * parent, int type, const char * name)
|
|
{
|
|
ext2_ino_t newentry = 0;
|
|
ext2_ino_t existing;
|
|
|
|
if(ext2fs_namei(vd->fs, vd->root, parent->ino, name, &existing) == 0)
|
|
return 0;
|
|
|
|
errcode_t err = ext2fs_new_inode(vd->fs, parent->ino, type, 0, &newentry);
|
|
if(err != EXT2_ET_OK)
|
|
return 0;
|
|
|
|
do
|
|
{
|
|
err = ext2fs_mkdir(vd->fs, parent->ino, newentry, name);
|
|
if(err == EXT2_ET_DIR_NO_SPACE)
|
|
{
|
|
if(ext2fs_expand_dir(vd->fs, parent->ino) != 0)
|
|
return 0;
|
|
}
|
|
}
|
|
while(err == EXT2_ET_DIR_NO_SPACE);
|
|
|
|
if(err != EXT2_ET_OK)
|
|
return 0;
|
|
|
|
struct ext2_inode inode;
|
|
if(ext2fs_read_inode(vd->fs, newentry, &inode) == EXT2_ET_OK)
|
|
{
|
|
inode.i_mode = type;
|
|
inode.i_uid = parent->ni.i_uid;
|
|
inode.i_gid = parent->ni.i_gid;
|
|
ext2fs_write_new_inode(vd->fs, newentry, &inode);
|
|
}
|
|
|
|
return newentry;
|
|
}
|
|
|
|
|
|
static ext2_ino_t ext2CreateFile(ext2_vd *vd, ext2_inode_t * parent, int type, const char * name)
|
|
{
|
|
errcode_t retval = -1;
|
|
ext2_ino_t newfile = 0;
|
|
ext2_ino_t existing;
|
|
|
|
if(ext2fs_namei(vd->fs, vd->root, parent->ino, name, &existing) == 0)
|
|
return 0;
|
|
|
|
retval = ext2fs_new_inode(vd->fs, parent->ino, type, 0, &newfile);
|
|
if (retval)
|
|
return 0;
|
|
|
|
do
|
|
{
|
|
retval = ext2fs_link(vd->fs, parent->ino, name, newfile, EXT2_FT_REG_FILE);
|
|
if (retval == EXT2_ET_DIR_NO_SPACE)
|
|
{
|
|
if (ext2fs_expand_dir(vd->fs, parent->ino) != 0)
|
|
return 0;
|
|
}
|
|
}
|
|
while(retval == EXT2_ET_DIR_NO_SPACE);
|
|
|
|
if (retval)
|
|
return 0;
|
|
|
|
ext2fs_inode_alloc_stats2(vd->fs, newfile, +1, 0);
|
|
|
|
struct ext2_inode inode;
|
|
memset(&inode, 0, sizeof(inode));
|
|
inode.i_mode = type;
|
|
inode.i_atime = inode.i_ctime = inode.i_mtime = time(0);
|
|
inode.i_links_count = 1;
|
|
inode.i_size = 0;
|
|
inode.i_uid = parent->ni.i_uid;
|
|
inode.i_gid = parent->ni.i_gid;
|
|
|
|
if (ext2fs_write_new_inode(vd->fs, newfile, &inode) != 0)
|
|
return 0;
|
|
|
|
return newfile;
|
|
}
|
|
|
|
ext2_inode_t *ext2Create(ext2_vd *vd, const char *path, mode_t type, const char *target)
|
|
{
|
|
ext2_inode_t *dir_ni = NULL, *ni = NULL;
|
|
char *dir = NULL;
|
|
char *targetdir = NULL;
|
|
char *name = NULL;
|
|
ext2_ino_t newentry = 0;
|
|
|
|
// Sanity check
|
|
if (!vd || !vd->fs) {
|
|
errno = ENODEV;
|
|
return NULL;
|
|
}
|
|
|
|
if(!(vd->fs->flags & EXT2_FLAG_RW))
|
|
return NULL;
|
|
|
|
// You cannot link between devices
|
|
if(target) {
|
|
if(vd != ext2GetVolume(target)) {
|
|
errno = EXDEV;
|
|
return NULL;
|
|
}
|
|
// Check if existing
|
|
dir_ni = ext2OpenEntry(vd, target);
|
|
if (dir_ni) {
|
|
goto cleanup;
|
|
}
|
|
ext2CloseEntry(vd, dir_ni);
|
|
dir_ni = NULL;
|
|
targetdir = strdup(target);
|
|
if (!targetdir) {
|
|
errno = EINVAL;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
// Get the actual paths of the entry
|
|
path = ext2RealPath(path);
|
|
target = ext2RealPath(target);
|
|
if (!path) {
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
|
|
// Lock
|
|
ext2Lock(vd);
|
|
|
|
// Clean me
|
|
// NOTE: this looks horrible right now and need a cleanup
|
|
dir = strdup(path);
|
|
if (!dir) {
|
|
errno = EINVAL;
|
|
goto cleanup;
|
|
}
|
|
|
|
char * tmp_path = (targetdir && (type == S_IFLNK)) ? targetdir : dir;
|
|
if (strrchr(tmp_path, '/') != NULL)
|
|
{
|
|
char * ptr = strrchr(tmp_path, '/');
|
|
name = strdup(ptr+1);
|
|
*ptr = '\0';
|
|
}
|
|
else
|
|
name = strdup(tmp_path);
|
|
|
|
// Open the entries parent directory
|
|
dir_ni = ext2OpenEntry(vd, dir);
|
|
if (!dir_ni) {
|
|
goto cleanup;
|
|
}
|
|
|
|
// If not yet read, read the inode and block bitmap
|
|
if(!vd->fs->inode_map || !vd->fs->block_map)
|
|
ext2fs_read_bitmaps(vd->fs);
|
|
|
|
// Symbolic link
|
|
if(type == S_IFLNK)
|
|
{
|
|
if (!target) {
|
|
errno = EINVAL;
|
|
goto cleanup;
|
|
}
|
|
|
|
newentry = ext2CreateSymlink(vd, path, targetdir, name, type);
|
|
}
|
|
// Directory
|
|
else if(type == S_IFDIR)
|
|
{
|
|
newentry = ext2CreateMkDir(vd, dir_ni, LINUX_S_IFDIR | (0755 & ~vd->fs->umask), name);
|
|
}
|
|
// File
|
|
else if(type == S_IFREG)
|
|
{
|
|
newentry = ext2CreateFile(vd, dir_ni, LINUX_S_IFREG | (0755 & ~vd->fs->umask), name);
|
|
}
|
|
|
|
// If the entry was created
|
|
if (newentry != 0)
|
|
{
|
|
// Sync the entry to disc
|
|
ext2Sync(vd, NULL);
|
|
|
|
ni = ext2OpenEntry(vd, target ? target : path);
|
|
}
|
|
|
|
cleanup:
|
|
|
|
if(dir_ni)
|
|
ext2CloseEntry(vd, dir_ni);
|
|
|
|
if(name)
|
|
mem_free(name);
|
|
|
|
if(dir)
|
|
mem_free(dir);
|
|
|
|
if(targetdir)
|
|
mem_free(targetdir);
|
|
|
|
// Unlock
|
|
ext2Unlock(vd);
|
|
|
|
return ni;
|
|
}
|
|
|
|
/*
|
|
* Given a mode, return the ext2 file type
|
|
*/
|
|
static int ext2_file_type(unsigned int mode)
|
|
{
|
|
if (LINUX_S_ISREG(mode))
|
|
return EXT2_FT_REG_FILE;
|
|
|
|
if (LINUX_S_ISDIR(mode))
|
|
return EXT2_FT_DIR;
|
|
|
|
if (LINUX_S_ISCHR(mode))
|
|
return EXT2_FT_CHRDEV;
|
|
|
|
if (LINUX_S_ISBLK(mode))
|
|
return EXT2_FT_BLKDEV;
|
|
|
|
if (LINUX_S_ISLNK(mode))
|
|
return EXT2_FT_SYMLINK;
|
|
|
|
if (LINUX_S_ISFIFO(mode))
|
|
return EXT2_FT_FIFO;
|
|
|
|
if (LINUX_S_ISSOCK(mode))
|
|
return EXT2_FT_SOCK;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ext2Link(ext2_vd *vd, const char *old_path, const char *new_path)
|
|
{
|
|
ext2_inode_t *dir_ni = NULL, *ni = NULL;
|
|
char *dir = NULL;
|
|
char *name = NULL;
|
|
errcode_t err = 0;
|
|
|
|
// Sanity check
|
|
if (!vd || !vd->fs) {
|
|
errno = ENODEV;
|
|
return -1;
|
|
}
|
|
|
|
if(!(vd->fs->flags & EXT2_FLAG_RW))
|
|
return -1;
|
|
|
|
// You cannot link between devices
|
|
if(vd != ext2GetVolume(new_path)) {
|
|
errno = EXDEV;
|
|
return -1;
|
|
}
|
|
|
|
// Get the actual paths of the entry
|
|
old_path = ext2RealPath(old_path);
|
|
new_path = ext2RealPath(new_path);
|
|
if (!old_path || !new_path) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
// Lock
|
|
ext2Lock(vd);
|
|
|
|
//check for existing in new path
|
|
ni = ext2OpenEntry(vd, new_path);
|
|
if (ni) {
|
|
ext2CloseEntry(vd, ni);
|
|
ni = NULL;
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
dir = strdup(new_path);
|
|
if (!dir) {
|
|
errno = EINVAL;
|
|
err = -1;
|
|
goto cleanup;
|
|
}
|
|
char * ptr = strrchr(dir, '/');
|
|
if (ptr)
|
|
{
|
|
name = strdup(ptr+1);
|
|
*ptr = 0;
|
|
}
|
|
else
|
|
name = strdup(dir);
|
|
|
|
// Find the entry
|
|
ni = ext2OpenEntry(vd, old_path);
|
|
if (!ni) {
|
|
errno = ENOENT;
|
|
err = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
// Open the entries new parent directory
|
|
dir_ni = ext2OpenEntry(vd, dir);
|
|
if (!dir_ni) {
|
|
errno = ENOENT;
|
|
err = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
do
|
|
{
|
|
// Link the entry to its new parent
|
|
err = ext2fs_link(vd->fs, dir_ni->ino, name, ni->ino, ext2_file_type(ni->ni.i_mode));
|
|
if (err == EXT2_ET_DIR_NO_SPACE)
|
|
{
|
|
if (ext2fs_expand_dir(vd->fs, dir_ni->ino) != 0)
|
|
goto cleanup;
|
|
}
|
|
else if(err != 0)
|
|
{
|
|
errno = ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
while(err == EXT2_ET_DIR_NO_SPACE);
|
|
|
|
ni->ni.i_links_count++;
|
|
|
|
// Update entry times
|
|
ext2UpdateTimes(vd, ni, EXT2_UPDATE_MCTIME);
|
|
|
|
// Sync the entry to disc
|
|
ext2Sync(vd, ni);
|
|
|
|
cleanup:
|
|
|
|
if(dir_ni)
|
|
ext2CloseEntry(vd, dir_ni);
|
|
|
|
if(ni)
|
|
ext2CloseEntry(vd, ni);
|
|
|
|
if(dir)
|
|
mem_free(dir);
|
|
|
|
if(name)
|
|
mem_free(name);
|
|
|
|
// Unlock
|
|
ext2Unlock(vd);
|
|
|
|
return err;
|
|
}
|
|
|
|
typedef struct _rd_struct
|
|
{
|
|
ext2_ino_t parent;
|
|
int empty;
|
|
} rd_struct;
|
|
|
|
static int release_blocks_proc(ext2_filsys fs, blk_t *blocknr, int blockcnt EXT2FS_ATTR((unused)), void *private EXT2FS_ATTR((unused)))
|
|
{
|
|
blk_t block;
|
|
|
|
block = *blocknr;
|
|
ext2fs_block_alloc_stats(fs, block, -1);
|
|
*blocknr = 0;
|
|
return 0;
|
|
}
|
|
|
|
static int unlink_proc(ext2_ino_t dir EXT2FS_ATTR((unused)), int entry EXT2FS_ATTR((unused)),
|
|
struct ext2_dir_entry *dirent, int offset EXT2FS_ATTR((unused)),
|
|
int blocksize EXT2FS_ATTR((unused)), char *buf EXT2FS_ATTR((unused)),
|
|
void *private_data)
|
|
{
|
|
rd_struct *rds = (rd_struct *) private_data;
|
|
|
|
if (dirent->inode == 0)
|
|
return 0;
|
|
if (((dirent->name_len & 0xFF) == 1) && (dirent->name[0] == '.'))
|
|
return 0;
|
|
if (((dirent->name_len & 0xFF) == 2) && (dirent->name[0] == '.') &&
|
|
(dirent->name[1] == '.')) {
|
|
rds->parent = dirent->inode;
|
|
return 0;
|
|
}
|
|
|
|
rds->empty = 0;
|
|
return 0;
|
|
}
|
|
|
|
int ext2Unlink (ext2_vd *vd, const char *path)
|
|
{
|
|
ext2_inode_t *dir_ni = NULL, *ni = NULL;
|
|
char *dir = NULL;
|
|
char *name = NULL;
|
|
errcode_t err = -1;
|
|
|
|
// Sanity check
|
|
if (!vd || !vd->fs) {
|
|
errno = ENODEV;
|
|
return -1;
|
|
}
|
|
|
|
if(!(vd->fs->flags & EXT2_FLAG_RW))
|
|
return -1;
|
|
|
|
// Get the actual path of the entry
|
|
path = ext2RealPath(path);
|
|
if (!path) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
// Lock
|
|
ext2Lock(vd);
|
|
|
|
dir = strdup(path);
|
|
if (!dir) {
|
|
errno = EINVAL;
|
|
goto cleanup;
|
|
}
|
|
char * ptr = strrchr(dir, '/');
|
|
if (ptr)
|
|
{
|
|
name = strdup(ptr+1);
|
|
*ptr = 0;
|
|
}
|
|
else
|
|
name = dir;
|
|
|
|
// Find the entry
|
|
ni = ext2OpenEntry(vd, path);
|
|
if (!ni) {
|
|
errno = ENOENT;
|
|
goto cleanup;
|
|
}
|
|
|
|
// Open the entries parent directory
|
|
dir_ni = ext2OpenEntry(vd, dir);
|
|
if (!dir_ni) {
|
|
errno = ENOENT;
|
|
goto cleanup;
|
|
}
|
|
|
|
// Directory
|
|
if(LINUX_S_ISDIR(ni->ni.i_mode))
|
|
{
|
|
rd_struct rds;
|
|
rds.parent = 0;
|
|
rds.empty = 1;
|
|
|
|
if (ext2fs_dir_iterate2(vd->fs, ni->ino, 0, 0, unlink_proc, &rds) != 0)
|
|
goto cleanup;
|
|
|
|
if(!rds.empty)
|
|
goto cleanup;
|
|
|
|
if (rds.parent)
|
|
{
|
|
struct ext2_inode inode;
|
|
if (ext2fs_read_inode(vd->fs, rds.parent, &inode) == 0)
|
|
{
|
|
if(inode.i_links_count > 1)
|
|
inode.i_links_count--;
|
|
ext2fs_write_inode(vd->fs, rds.parent, &inode);
|
|
}
|
|
}
|
|
|
|
// set link count 0
|
|
ni->ni.i_links_count = 0;
|
|
}
|
|
// File
|
|
else
|
|
{
|
|
ni->ni.i_links_count--;
|
|
}
|
|
|
|
if(ni->ni.i_links_count <= 0)
|
|
{
|
|
ni->ni.i_size = 0;
|
|
ni->ni.i_size_high = 0;
|
|
ni->ni.i_links_count = 0;
|
|
ni->ni.i_dtime = (u32) time(0);
|
|
}
|
|
|
|
ext2fs_write_inode(vd->fs, ni->ino, &ni->ni);
|
|
|
|
// Unlink the entry from its parent
|
|
if(ext2fs_unlink(vd->fs, dir_ni->ino, name, 0, 0) != 0)
|
|
goto cleanup;
|
|
|
|
if (ext2fs_inode_has_valid_blocks(&ni->ni))
|
|
{
|
|
ext2fs_block_iterate(vd->fs, ni->ino, 0, NULL, release_blocks_proc, NULL);
|
|
ext2fs_inode_alloc_stats2(vd->fs, ni->ino, -1, LINUX_S_ISDIR(ni->ni.i_mode));
|
|
}
|
|
|
|
if(ni->ni.i_links_count == 0)
|
|
{
|
|
// It's odd that i have to do this on my own and the lib is not doing that for me
|
|
blk64_t truncate_block = ((vd->fs->blocksize - 1) >> EXT2_BLOCK_SIZE_BITS(vd->fs->super)) + 1;
|
|
ext2fs_punch(vd->fs, ni->ino, &ni->ni, 0, truncate_block, ~0ULL);
|
|
}
|
|
|
|
// Sync the entry to disc
|
|
ext2Sync(vd, NULL);
|
|
|
|
err = 0;
|
|
|
|
cleanup:
|
|
|
|
if(dir_ni)
|
|
ext2CloseEntry(vd, dir_ni);
|
|
|
|
if(ni)
|
|
ext2CloseEntry(vd, ni);
|
|
|
|
if(name)
|
|
mem_free(name);
|
|
|
|
if(dir)
|
|
mem_free(dir);
|
|
|
|
// Unlock
|
|
ext2Unlock(vd);
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
int ext2Sync(ext2_vd *vd, ext2_inode_t *ni)
|
|
{
|
|
errcode_t res = 0;
|
|
|
|
// Sanity check
|
|
if (!vd || !vd->fs) {
|
|
errno = ENODEV;
|
|
return -1;
|
|
}
|
|
|
|
if(!(vd->fs->flags & EXT2_FLAG_RW))
|
|
return -1;
|
|
|
|
// Lock
|
|
ext2Lock(vd);
|
|
|
|
if(ni && ni->dirty)
|
|
{
|
|
ext2fs_write_inode(vd->fs, ni->ino, &ni->ni);
|
|
ni->dirty = false;
|
|
}
|
|
|
|
// Sync the entry
|
|
res = ext2fs_flush(vd->fs);
|
|
|
|
// Force the underlying device to sync
|
|
vd->io->manager->flush(vd->io);
|
|
|
|
// Unlock
|
|
ext2Unlock(vd);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
int ext2Stat (ext2_vd *vd, ext2_inode_t *ni_main, struct stat *st)
|
|
{
|
|
int res = 0;
|
|
|
|
// Sanity check
|
|
if (!vd) {
|
|
errno = ENODEV;
|
|
return -1;
|
|
}
|
|
|
|
struct ext2_inode * ni = ni_main ? &ni_main->ni : 0;
|
|
|
|
// Sanity check
|
|
if (!ni) {
|
|
errno = ENOENT;
|
|
return -1;
|
|
}
|
|
|
|
// Short circuit cases were we don't actually have to do anything
|
|
if (!st)
|
|
return 0;
|
|
|
|
// Lock
|
|
ext2Lock(vd);
|
|
|
|
// Zero out the stat buffer
|
|
memset(st, 0, sizeof(struct stat));
|
|
|
|
if(LINUX_S_ISDIR(ni->i_mode))
|
|
{
|
|
st->st_nlink = 1;
|
|
st->st_size = ni->i_size;
|
|
}
|
|
else
|
|
{
|
|
st->st_nlink = ni->i_links_count;
|
|
st->st_size = EXT2_I_SIZE(ni);
|
|
}
|
|
|
|
st->st_mode = ni->i_mode;
|
|
st->st_blocks = ni->i_blocks;
|
|
st->st_blksize = vd->fs->blocksize;
|
|
|
|
// Fill in the generic entry stats
|
|
st->st_dev = (dev_t) ((long) vd->fs);
|
|
st->st_uid = ni->i_uid | (((u32) ni->osd2.linux2.l_i_uid_high) << 16);
|
|
st->st_gid = ni->i_gid | (((u32) ni->osd2.linux2.l_i_gid_high) << 16);
|
|
st->st_ino = ni_main->ino;
|
|
st->st_atime = ni->i_atime;
|
|
st->st_ctime = ni->i_ctime;
|
|
st->st_mtime = ni->i_mtime;
|
|
|
|
// Update entry times
|
|
ext2UpdateTimes(vd, ni_main, EXT2_UPDATE_ATIME);
|
|
|
|
// Unlock
|
|
ext2Unlock(vd);
|
|
|
|
return res;
|
|
}
|
|
|
|
void ext2UpdateTimes(ext2_vd *vd, ext2_inode_t *ni, ext2_time_update_flags mask)
|
|
{
|
|
// Sanity check
|
|
if(!ni || !mask)
|
|
return;
|
|
|
|
if(!(vd->fs->flags & EXT2_FLAG_RW))
|
|
return;
|
|
|
|
u32 now = (u32) time(0);
|
|
|
|
if(mask & EXT2_UPDATE_ATIME)
|
|
ni->ni.i_atime = now;
|
|
if(mask & EXT2_UPDATE_MTIME)
|
|
ni->ni.i_mtime = now;
|
|
if(mask & EXT2_UPDATE_CTIME)
|
|
ni->ni.i_ctime = now;
|
|
|
|
ni->dirty = true;
|
|
}
|
|
|
|
const char *ext2RealPath (const char *path)
|
|
{
|
|
// Sanity check
|
|
if (!path)
|
|
return NULL;
|
|
|
|
// Move the path pointer to the start of the actual path
|
|
if (strchr(path, ':') != NULL) {
|
|
path = strchr(path, ':')+1;
|
|
}
|
|
if (strchr(path, ':') != NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return path;
|
|
}
|