VBoxFUSE.cpp revision 64f96761c69b9dd974a26629390802a91bb2ac85
/* $Id$ */
/** @file
* VBoxFUSE - Disk Image Flattening FUSE Program.
*/
/*
* Copyright (C) 2009 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#endif
#define FUSE_USE_VERSION 27
#include <fuse.h>
#include <errno.h>
#include <fcntl.h>
#ifndef EDOOFUS
# ifdef EBADMACHO
//# elif defined(EXYZ)
//# define EDOOFUS EXYZ
# else
# error "Choose an unlikely and (if possible) fun error number for EDOOFUS."
# endif
#endif
#include <iprt/critsect.h>
#include <iprt/initterm.h>
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/**
* Node type.
*/
typedef enum VBOXFUSETYPE
{
VBOXFUSETYPE_INVALID = 0,
} VBOXFUSETYPE;
/**
* Stuff common to both directories and files.
*/
typedef struct VBOXFUSENODE
{
/** The directory name. */
const char *pszName;
/** The name length. */
/** The node type. */
/** The number of references.
* The directory linking this node will always retain one. */
/** Critical section serializing access to the node data. */
/** Pointer to the directory (parent). */
struct VBOXFUSEDIR *pDir;
/** The mode mask. */
/** The User ID of the directory owner. */
/** The Group ID of the directory. */
/** The link count. */
/** The inode number. */
/** The size of the primary stream. */
} VBOXFUSENODE;
typedef VBOXFUSENODE *PVBOXFUSENODE;
/**
* A flat image file.
*/
typedef struct VBOXFUSEFLATIMAGE
{
/** The standard bits. */
/** The virtual disk container. */
/** The format name. */
char *pszFormat;
/** The number of readers.
* Read only images will have this set to INT32_MAX/2 on creation. */
/** The number of writers. (Just 1 or 0 really.) */
typedef VBOXFUSEFLATIMAGE *PVBOXFUSEFLATIMAGE;
/**
* A control pipe (file).
*/
typedef struct VBOXFUSECTRLPIPE
{
/** The standard bits. */
typedef VBOXFUSECTRLPIPE *PVBOXFUSECTRLPIPE;
/**
* A Directory.
*
* This is just a container of files and subdirectories, nothing special.
*/
typedef struct VBOXFUSEDIR
{
/** The standard bits. */
/** The number of directory entries. */
/** Array of pointers to directory entries.
* Whether what's being pointed to is a file, directory or something else can be
* determined by the enmType field. */
} VBOXFUSEDIR;
typedef VBOXFUSEDIR *PVBOXFUSEDIR;
/** The number of elements to grow VBOXFUSEDIR::paEntries by. */
/*******************************************************************************
* Global Variables *
*******************************************************************************/
/** The root of the file hierarchy. */
static VBOXFUSEDIR *g_pTreeRoot;
/** The next inode number. */
/*******************************************************************************
* Internal Functions *
*******************************************************************************/
static int vboxfuseTreeLookupParent(const char *pszPath, const char **ppszName, PVBOXFUSEDIR *ppDir);
static int vboxfuseTreeLookupParentForInsert(const char *pszPath, const char **ppszName, PVBOXFUSEDIR *ppDir);
/**
* Node destructor.
*
* @returns true.
* @param pNode The node.
* @param fLocked Whether it's locked.
*/
{
/*
* Type specific cleanups.
*/
{
case VBOXFUSETYPE_DIRECTORY:
{
break;
}
case VBOXFUSETYPE_FLAT_IMAGE:
{
if (pFlatImage->pDisk)
{
}
break;
}
break;
default:
break;
}
/*
* Generic cleanup.
*/
/*
* Unlock and destroy the lock, before we finally frees the node.
*/
if (fLocked)
return true;
}
/**
* Locks a FUSE node.
*
* @param pNode The node.
*/
{
}
/**
* Unlocks a FUSE node.
*
* @param pNode The node.
*/
{
}
/**
* Retain a VBoxFUSE node.
*
* @param pNode The node.
*/
{
}
/**
* Releases a VBoxFUSE node reference.
*
* @returns true if deleted, false if not.
* @param pNode The node.
*/
{
return false;
}
/**
* Locks and retains a VBoxFUSE node.
*
* @param pNode The node.
*/
{
}
/**
* Releases a VBoxFUSE node reference and unlocks it.
*
* @returns true if deleted, false if not.
* @param pNode The node.
*/
{
return false;
}
/**
* Creates stat info for a locked node.
*
* @param pNode The node (locked).
*/
{
/** @todo file times */
// pStat->st_atimensec = 0;
// pStat->st_mtimensec = 0;
// pStat->st_ctimensec = 0;
#ifndef RT_OS_LINUX
#endif
}
/**
* Allocates a new node and initialize the node part of it.
*
* The returned node has one reference.
*
* @returns VBox status code.
*
* @param cbNode The size of the node.
* @param pszName The name of the node.
* @param enmType The node type.
* @param pDir The directory (parent).
* @param ppNode Where to return the pointer to the node.
*/
static int vboxfuseNodeAlloc(size_t cbNode, const char *pszName, VBOXFUSETYPE enmType, PVBOXFUSEDIR pDir,
{
/*
* Allocate the memory for it and init the critical section.
*/
if (!pNode)
return VERR_NO_MEMORY;
if (RT_FAILURE(rc))
{
return rc;
}
/*
* Initialize the members.
*/
#if 0
#else
#endif
return VINF_SUCCESS;
}
/**
* Inserts a node into a directory
*
* The caller has locked and referneced the directory as well as checked that
* the name doesn't already exist within it. On success both the reference and
* and link counters will be incremented.
*
* @returns VBox status code.
*
* @param pDir The parent directory. Can be NULL when creating the root
* directory.
* @param pNode The node to insert.
*/
{
if (!pDir)
{
/*
* Special case: Root Directory.
*/
}
else
{
/*
* Common case.
*/
{
void *pvNew = RTMemRealloc(pDir->paEntries, sizeof(*pDir->paEntries) * (pDir->cEntries + VBOXFUSE_DIR_GROW_BY));
if (!pvNew)
return VERR_NO_MEMORY;
}
}
return VINF_SUCCESS;
}
/**
* Create a directory node.
*
* @returns VBox status code.
* @param pszPath The path to the directory.
* @param ppDir Optional, where to return the new directory locked and
* referenced (making cRefs == 2).
*/
{
/*
* Figure out where the directory is going.
*/
const char *pszName;
if (RT_FAILURE(rc))
return rc;
/*
* Allocate and initialize the new directory node.
*/
rc = vboxfuseNodeAlloc(sizeof(*pNewDir), pszName, VBOXFUSETYPE_DIRECTORY, pParent, (PVBOXFUSENODE *)&pNewDir);
if (RT_SUCCESS(rc))
{
/*
* Insert it.
*/
if ( RT_SUCCESS(rc)
&& ppDir)
{
}
}
if (pParent)
return rc;
}
/**
* Creates a flattened image
*
* @returns VBox status code.
* @param pszPath Where to create the flattened file in the FUSE file
* system.
* @param pszImage The image to flatten.
* @param ppFile Where to return the pointer to the instance.
* Optional.
*/
static int vboxfuseFlatImageCreate(const char *pszPath, const char *pszImage, PVBOXFUSEFLATIMAGE *ppFile)
{
/*
* Check that we can create this file.
*/
const char *pszName;
if (RT_FAILURE(rc))
return rc;
if (pParent)
/*
* Try open the image file (without holding any locks).
*/
char *pszFormat;
if (RT_FAILURE(rc))
{
return rc;
}
if (RT_SUCCESS(rc))
{
if (RT_FAILURE(rc))
{
}
}
else
if (RT_FAILURE(rc))
{
return rc;
}
/*
* Allocate and initialize the new directory node.
*/
if (RT_SUCCESS(rc))
{
rc = vboxfuseNodeAlloc(sizeof(*pNewFlatImage), pszName, VBOXFUSETYPE_FLAT_IMAGE, pParent, (PVBOXFUSENODE *)&pNewFlatImage);
if (RT_SUCCESS(rc))
{
pNewFlatImage->cWriters = 0;
/*
* Insert it.
*/
if ( RT_SUCCESS(rc)
&& ppFile)
{
*ppFile = pNewFlatImage;
}
}
if (pParent)
}
return rc;
}
//static int vboxfuseTreeMkCtrlPipe(const char *pszPath, PVBOXFUSECTRLPIPE *ppPipe)
//{
//}
/**
* Looks up a file in the tree.
*
* Upon successfull return the returned node is both referenced and locked. The
* call will have to release and unlock it.
*
* @returns VBox status code
* @param pszPath The path to the file.
* @param ppNode Where to return the node.
*/
{
/*
* Root first.
*/
if (*psz != '/')
return VERR_FILE_NOT_FOUND;
do psz++;
while (*psz == '/');
if (!*psz)
{
/* looking for the root. */
return VINF_SUCCESS;
}
/*
* Take it bit by bit from here on.
*/
for (;;)
{
/*
* Find the length of the current directory entry and check if it must be file.
*/
if (!psz)
while (*psz == '/')
psz++;
/*
* Look it up.
* This is safe as the directory will hold a refernece to each node
* so the nodes cannot possibly be destroyed while we're searching them.
*/
while (i-- > 0)
{
{
break;
}
}
if (!pNode)
if ( fMustBeDir
{
return VERR_NOT_A_DIRECTORY;
}
/*
* Are we done?
*/
if (!*psz)
{
return VINF_SUCCESS;
}
/* advance */
}
}
/**
* Errno convertsion wrapper around vboxfuseTreeLookup().
*
* @returns 0 on success, negated errno on failure.
* @param pszPath The path to the file.
* @param ppNode Where to return the node.
*/
{
if (RT_SUCCESS(rc))
return 0;
return -RTErrConvertToErrno(rc);
}
/**
* Looks up a parent directory in the tree.
*
* Upon successfull return the returned directory is both referenced and locked.
* The call will have to release and unlock it.
*
* @returns VBox status code.
*
* @param pszPath The path to the file which parent we seek.
* @param ppszName Where to return the pointer to the child's name within
* pszPath.
* @param ppDir Where to return the parent directory.
*/
static int vboxfuseTreeLookupParent(const char *pszPath, const char **ppszName, PVBOXFUSEDIR *ppDir)
{
/*
* Root first.
*/
if (*psz != '/')
return VERR_INVALID_PARAMETER;
do psz++;
while (*psz == '/');
if (!*psz)
{
/* looking for the root. */
return VINF_SUCCESS;
}
/*
* Take it bit by bit from here on.
*/
for (;;)
{
/*
* Find the length of the current directory entry and check if it must be file.
*/
if (!psz)
{
/* that's all folks.*/
return VINF_SUCCESS;
}
while (*psz == '/')
psz++;
/* Trailing slashes are not allowed (because it's simpler without them). */
if (!*psz)
return VERR_INVALID_PARAMETER;
/*
* Look it up.
* This is safe as the directory will hold a refernece to each node
* so the nodes cannot possibly be destroyed while we're searching them.
*/
while (i-- > 0)
{
{
break;
}
}
if (!pNode)
return VERR_FILE_NOT_FOUND;
if ( fMustBeDir
{
return VERR_PATH_NOT_FOUND;
}
/* advance */
}
}
/**
* Looks up a parent directory in the tree and checking that the specified child
* doesn't already exist.
*
* Upon successfull return the returned directory is both referenced and locked.
* The call will have to release and unlock it.
*
* @returns VBox status code.
*
* @param pszPath The path to the file which parent we seek.
* @param ppszName Where to return the pointer to the child's name within
* pszPath.
* @param ppDir Where to return the parent directory.
*/
static int vboxfuseTreeLookupParentForInsert(const char *pszPath, const char **ppszName, PVBOXFUSEDIR *ppDir)
{
/*
* Lookup the parent directory using vboxfuseTreeLookupParent first.
*/
const char *pszName;
if (RT_SUCCESS(rc))
{
/*
* Check that it doesn't exist already
*/
if (pDir)
{
while (i-- > 0)
{
{
break;
}
}
}
if (RT_SUCCESS(rc))
{
}
}
return rc;
}
/** @copydoc fuse_operations::getattr */
{
if (!rc)
{
}
return rc;
}
/** @copydoc fuse_operations::opendir */
{
if (!rc)
{
/*
* Check that it's a directory and that the caller should see it.
*/
/** @todo access checks. */
else
{
/** @todo update the accessed TS? */
/*
* Put a reference to the node in the fuse_file_info::fh member so
* we don't have to parse the path in readdir.
*/
}
/* cleanup */
if (rc)
}
return rc;
}
/** @copydoc fuse_operations::readdir */
{
#define VBOXFUSE_FAKE_DIRENT_SIZE 512
/*
* First the mandatory dot and dot-dot entries.
*/
int rc = 0;
if (!offDir)
{
}
if ( offDir == VBOXFUSE_FAKE_DIRENT_SIZE
&& !rc)
{
}
/*
* The entries only needs locking since the directory already has a reference
* to each of them.
*/
while ( !rc
{
/* next */
i++;
}
return 0;
}
/** @copydoc fuse_operations::releasedir */
{
return 0;
}
/** @copydoc fuse_operations::symlink */
{
/*
* "Interface" for mounting a image.
*/
if (RT_SUCCESS(rc))
{
return 0;
}
return -RTErrConvertToErrno(rc);
}
/** @copydoc fuse_operations::open */
{
/*
* Validate incoming flags.
*/
#ifdef RT_OS_DARWIN
return -EINVAL;
return -EINVAL;
#elif defined(RT_OS_LINUX)
/* | O_SYNC ? */))
return -EINVAL;
return -EINVAL;
#else
# error "Port me"
#endif
if (!rc)
{
/*
* Check flags and stuff.
*/
{
/* not expected here? */
case VBOXFUSETYPE_DIRECTORY:
AssertFailed();
break;
case VBOXFUSETYPE_FLAT_IMAGE:
{
#ifdef O_DIRECTORY
#endif
{
if ( pFlatImage->cWriters == 0
&& pFlatImage->cReaders == 0)
pFlatImage->cWriters++;
else
}
{
if (pFlatImage->cWriters == 0)
{
? INT32_MAX / 4
pFlatImage->cReaders++;
else
}
else
}
break;
}
break;
default:
break;
}
if (!rc)
{
/*
* Put a reference to the node in the fuse_file_info::fh member so
* we don't have to parse the path in the other file methods.
*/
}
else
{
/* cleanup */
}
}
return rc;
}
/** @copydoc fuse_operations::release */
{
{
case VBOXFUSETYPE_DIRECTORY:
/* nothing to do */
break;
case VBOXFUSETYPE_FLAT_IMAGE:
{
{
pFlatImage->cWriters--;
}
{
pFlatImage->cReaders--;
}
else
AssertFailed();
break;
}
/* nothing to do yet */
break;
default:
return -EDOOFUS;
}
return 0;
}
#define VBOXFUSE_MIN_SIZE 512
/** Offset mask corresponding to VBOXFUSE_MIN_SIZE. */
#define VBOXFUSE_MIN_SIZE_MASK_OFF (0x1ff)
/** Block mask corresponding to VBOXFUSE_MIN_SIZE. */
/** @copydoc fuse_operations::read */
{
/* paranoia */
{
case VBOXFUSETYPE_DIRECTORY:
return -ENOTSUP;
case VBOXFUSETYPE_FLAT_IMAGE:
{
LogFlow(("vboxfuseOp_read: offFile=%#llx cbBuf=%#zx pszPath=\"%s\"\n", (uint64_t)offFile, cbBuf, pszPath));
int rc;
rc = 0;
else if (!cbBuf)
rc = 0;
else
{
/* Adjust for EOF. */
/*
* Aligned read?
*/
int rc2;
if ( !(offFile & VBOXFUSE_MIN_SIZE_MASK_OFF)
&& !(cbBuf & VBOXFUSE_MIN_SIZE_MASK_OFF))
else
{
/*
* Unaligned read - lots of extra work.
*/
{
/* a single partial block. */
if (RT_SUCCESS(rc2))
}
else
{
/* read unaligned head. */
rc2 = VINF_SUCCESS;
{
if (RT_SUCCESS(rc2))
{
}
}
/* read the middle. */
{
if (RT_SUCCESS(rc2))
{
}
}
/* unaligned tail read. */
{
if (RT_SUCCESS(rc2))
}
}
}
/* convert the return code */
if (RT_SUCCESS(rc2))
else
}
return rc;
}
return -ENOTSUP;
default:
return -EDOOFUS;
}
}
/** @copydoc fuse_operations::write */
{
/* paranoia */
{
case VBOXFUSETYPE_DIRECTORY:
return -ENOTSUP;
case VBOXFUSETYPE_FLAT_IMAGE:
{
LogFlow(("vboxfuseOp_write: offFile=%#llx cbBuf=%#zx pszPath=\"%s\"\n", (uint64_t)offFile, cbBuf, pszPath));
int rc;
rc = 0;
else if (!cbBuf)
rc = 0;
else
{
/* Adjust for EOF. */
/*
* Aligned write?
*/
int rc2;
if ( !(offFile & VBOXFUSE_MIN_SIZE_MASK_OFF)
&& !(cbBuf & VBOXFUSE_MIN_SIZE_MASK_OFF))
else
{
/*
* Unaligned write - lots of extra work.
*/
{
/* a single partial block. */
if (RT_SUCCESS(rc2))
{
/* Update the block */
}
}
else
{
/* read unaligned head. */
rc2 = VINF_SUCCESS;
{
if (RT_SUCCESS(rc2))
{
}
}
/* write the middle. */
{
if (RT_SUCCESS(rc2))
{
}
}
/* unaligned tail write. */
{
if (RT_SUCCESS(rc2))
{
}
}
}
}
/* convert the return code */
if (RT_SUCCESS(rc2))
else
}
return rc;
}
return -ENOTSUP;
default:
return -EDOOFUS;
}
}
/**
* The FUSE operations.
*
* @remarks We'll initialize this manually since we cannot use C99 style
* initialzer designations in C++ (yet).
*/
static struct fuse_operations g_vboxfuseOps;
{
/*
* Initialize the runtime and VD.
*/
if (RT_FAILURE(rc))
{
return 1;
}
RTPrintf("VBoxFUSE: Hello...\n");
if (RT_FAILURE(rc))
{
return 1;
}
/*
* Initializes the globals and populate the file hierarchy.
*/
if (RT_SUCCESS(rc))
if (RT_FAILURE(rc))
{
return 1;
}
/*
* Initialize the g_vboxfuseOps. (C++ sucks!)
*/
/*
* Hand control over to libfuse.
*/
#if 0
/** @todo multithreaded fun. */
#else
#endif
return rc;
}