nuttx/fs/mount/fs_automount.c
Xiang Xiao 66057a4612 fs: Add the relative path support
all functions which accept the path argument should support the relative path:
https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html

Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
2020-09-24 03:13:27 -07:00

612 lines
16 KiB
C

/****************************************************************************
* fs/mount/fs_automount.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#if defined(CONFIG_FS_AUTOMOUNTER_DEBUG) && !defined(CONFIG_DEBUG_FS)
# define CONFIG_DEBUG_FS 1
#endif
#include <sys/mount.h>
#include <stdbool.h>
#include <errno.h>
#include <assert.h>
#include <debug.h>
#include <nuttx/wdog.h>
#include <nuttx/kmalloc.h>
#include <nuttx/wqueue.h>
#include <nuttx/fs/automount.h>
#include "inode/inode.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Configuration ************************************************************
*
* CONFIG_FS_AUTOMOUNTER - Enables AUTOMOUNT support
*/
/* Pre-requisites */
#ifndef CONFIG_SCHED_WORKQUEUE
# error Work queue support is required (CONFIG_SCHED_WORKQUEUE)
#endif
/* Return Values */
#define OK_EXIST 0
#define OK_NOENT 1
/****************************************************************************
* Private Types
****************************************************************************/
/* This structure describes the state of the automounter */
struct automounter_state_s
{
FAR const struct automount_lower_s *lower; /* Board level interfaces */
struct work_s work; /* Work queue support */
struct wdog_s wdog; /* Delay to retry un-mounts */
bool mounted; /* True: Volume has been mounted */
bool inserted; /* True: Media has been inserted */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static int automount_findinode(FAR const char *path);
static void automount_mount(FAR struct automounter_state_s *priv);
static int automount_unmount(FAR struct automounter_state_s *priv);
static void automount_timeout(wdparm_t arg);
static void automount_worker(FAR void *arg);
static int automount_interrupt(FAR const struct automount_lower_s *lower,
FAR void *arg, bool inserted);
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: automount_findinode
*
* Description:
* Find the mountpoint inode in the inode tree.
*
* Input Parameters:
* mntpath - Mountpoint path
*
* Returned Value:
* OK_EXIST if the inode exists
* OK_NOENT if the inode does not exist
* Negated errno if some failure occurs
*
****************************************************************************/
static int automount_findinode(FAR const char *path)
{
struct inode_search_s desc;
int ret;
/* Make sure that we were given a path */
DEBUGASSERT(path != NULL);
/* Get exclusive access to the in-memory inode tree. */
ret = inode_semtake();
if (ret < 0)
{
return ret;
}
/* Find the inode */
SETUP_SEARCH(&desc, path, false);
ret = inode_search(&desc);
/* Did we find it? */
if (ret < 0)
{
/* No.. Not found */
ret = OK_NOENT;
}
/* Yes.. is it a mount point? */
else if (INODE_IS_MOUNTPT(desc.node))
{
/* Yes.. we found a mountpoint at this path */
ret = OK_EXIST;
}
else
{
/* No.. then something is in the way */
ret = -ENOTDIR;
}
/* Relinquish our exclusive access to the inode try and return the result */
inode_semgive();
RELEASE_SEARCH(&desc);
return ret;
}
/****************************************************************************
* Name: automount_mount
*
* Description:
* Media has been inserted, mount the volume.
*
* Input Parameters:
* priv - A reference to out private state structure
*
* Returned Value:
* None
*
****************************************************************************/
static void automount_mount(FAR struct automounter_state_s *priv)
{
FAR const struct automount_lower_s *lower = priv->lower;
int ret;
finfo("Mounting %s\n", lower->mountpoint);
/* Check if the something is already mounted at the mountpoint. */
ret = automount_findinode(lower->mountpoint);
switch (ret)
{
case OK_EXIST:
/* REVISIT: What should we do in this case? I think that this would
* happen only if a previous unmount failed? I suppose that we should
* try to unmount again because the mount might be stale.
*/
fwarn("WARNING: Mountpoint %s already exists\n", lower->mountpoint);
ret = automount_unmount(priv);
if (ret < 0)
{
/* We failed to unmount (again?). Complain and abort. */
ferr("ERROR: automount_unmount failed: %d\n", ret);
return;
}
/* We successfully unmounted the file system. Fall through to
* mount it again.
*/
case OK_NOENT:
/* If we get here, then the volume must not be mounted */
DEBUGASSERT(!priv->mounted);
/* Mount the file system */
ret = mount(lower->blockdev, lower->mountpoint, lower->fstype,
0, NULL);
if (ret < 0)
{
int errcode = get_errno();
DEBUGASSERT(errcode > 0);
ferr("ERROR: Mount failed: %d\n", errcode);
UNUSED(errcode);
return;
}
/* Indicate that the volume is mounted */
priv->mounted = true;
break;
default:
ferr("ERROR: automount_findinode failed: %d\n", ret);
break;
}
}
/****************************************************************************
* Name: automount_unmount
*
* Description:
* Media has been removed, unmount the volume.
*
* Input Parameters:
* priv - A reference to out private state structure
*
* Returned Value:
* OK if the volume was successfully mounted. A negated errno value
* otherwise.
*
****************************************************************************/
static int automount_unmount(FAR struct automounter_state_s *priv)
{
FAR const struct automount_lower_s *lower = priv->lower;
int ret;
finfo("Unmounting %s\n", lower->mountpoint);
/* Check if the something is already mounted at the mountpoint. */
ret = automount_findinode(lower->mountpoint);
switch (ret)
{
case OK_EXIST:
/* If we get here, then the volume must be mounted */
DEBUGASSERT(priv->mounted);
/* Un-mount the volume */
ret = umount2(lower->mountpoint, MNT_FORCE);
if (ret < 0)
{
int errcode = get_errno();
DEBUGASSERT(errcode > 0);
/* We expect the error to be EBUSY meaning that the volume could
* not be unmounted because there are currently reference via open
* files or directories.
*/
if (errcode == EBUSY)
{
finfo("WARNING: Volume is busy, try again later\n");
/* Start a timer to retry the umount2 after a delay */
ret = wd_start(&priv->wdog, lower->udelay,
automount_timeout, (wdparm_t)priv);
if (ret < 0)
{
ferr("ERROR: wd_start failed: %d\n", ret);
return ret;
}
}
/* Other errors are fatal */
else
{
ferr("ERROR: umount2 failed: %d\n", errcode);
return -errcode;
}
}
/* Fall through */
case OK_NOENT:
/* The mountpoint is not present. This is normal behavior in the
* case where the user manually un-mounted the volume before removing
* media. Nice job, Mr. user.
*/
priv->mounted = false;
return OK;
default:
ferr("ERROR: automount_findinode failed: %d\n", ret);
return ret;
}
}
/****************************************************************************
* Name: automount_timeout
*
* Description:
* A previous unmount failed because the volume was busy... busy meaning
* the volume could not be unmounted because there are open references
* the files or directories in the volume. When this failure occurred,
* the unmount logic setup a delay and this function is called as a result
* of that delay timeout.
*
* This function will attempt the unmount again.
*
* Input Parameters:
* Standard wdog timeout parameters
*
* Returned Value:
* None
*
****************************************************************************/
static void automount_timeout(wdparm_t arg)
{
FAR struct automounter_state_s *priv =
(FAR struct automounter_state_s *)arg;
int ret;
finfo("Timeout!\n");
DEBUGASSERT(priv);
/* Check the state of things. This timeout at the interrupt level and
* will cancel the timeout if there is any change in the insertion
* state. So we should still have the saved state as NOT inserted and
* there should be no pending work.
*/
finfo("inserted=%d\n", priv->inserted);
DEBUGASSERT(!priv->inserted && work_available(&priv->work));
/* Queue work to occur immediately. */
ret = work_queue(LPWORK, &priv->work, automount_worker, priv, 0);
if (ret < 0)
{
/* NOTE: Currently, work_queue only returns success */
ferr("ERROR: Failed to schedule work: %d\n", ret);
}
}
/****************************************************************************
* Name: automount_worker
*
* Description:
* Performs auto-mount actions on the worker thread.
*
* Input Parameters:
* arg - Work argument set by work_queue()
*
* Returned Value:
* None
*
****************************************************************************/
static void automount_worker(FAR void *arg)
{
FAR struct automounter_state_s *priv =
(FAR struct automounter_state_s *)arg;
FAR const struct automount_lower_s *lower;
DEBUGASSERT(priv && priv->lower);
lower = priv->lower;
/* Disable interrupts. We are commit now and everything must remain
* stable.
*/
AUTOMOUNT_DISABLE(lower);
/* Are we mounting or unmounting? */
if (priv->inserted)
{
/* We are mounting */
automount_mount(priv);
}
else
{
/* We are unmounting */
automount_unmount(priv);
}
/* Re-enable interrupts */
AUTOMOUNT_ENABLE(lower);
}
/****************************************************************************
* Name: automount_interrupt
*
* Description:
* Called (probably from the interrupt level) when a media change event
* has been detected.
*
* Input Parameters:
* lower - Persistent board configuration data
* arg - Data associated with the auto-mounter
* inserted - True: Media has been inserted. False: media has been removed
*
* Returned Value:
* OK is returned on success; a negated errno value is returned on failure.
*
* Assumptions:
* Interrupts are disabled so that there is no race condition with the
* timer expiry.
*
****************************************************************************/
static int automount_interrupt(FAR const struct automount_lower_s *lower,
FAR void *arg, bool inserted)
{
FAR struct automounter_state_s *priv =
(FAR struct automounter_state_s *)arg;
int ret;
DEBUGASSERT(lower && priv && priv->lower == lower);
finfo("inserted=%d\n", inserted);
/* Cancel any pending work. We could get called multiple times if, for
* example there is bounce in the detection mechanism. Work is performed
* the low priority work queue if it is available.
*
* NOTE: The return values are ignored. The error -ENOENT means that
* there is no work to be canceled. No other errors are expected.
*/
work_cancel(LPWORK, &priv->work);
/* Set the media insertion/removal state */
priv->inserted = inserted;
/* Queue work to occur after a delay. The delays performs debouncing:
* If the insertion/removal detection logic has "chatter", then we may
* receive this interrupt numerous times. Each time, the previous work
* will be canceled (above) and the new work will scheduled with the
* delay. So the final mount operation will not be performed until the
* insertion state is stable for that delay.
*/
ret = work_queue(LPWORK, &priv->work, automount_worker, priv,
priv->lower->ddelay);
if (ret < 0)
{
/* NOTE: Currently, work_queue only returns success */
ferr("ERROR: Failed to schedule work: %d\n", ret);
}
else
{
/* Cancel any retry delays */
wd_cancel(&priv->wdog);
}
return OK;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: automount_initialize
*
* Description:
* Configure the auto mounter.
*
* Input Parameters:
* lower - Persistent board configuration data
*
* Returned Value:
* A void* handle. The only use for this handle is with
* automount_uninitialize(). NULL is returned on any failure.
*
****************************************************************************/
FAR void *automount_initialize(FAR const struct automount_lower_s *lower)
{
FAR struct automounter_state_s *priv;
int ret;
finfo("lower=%p\n", lower);
DEBUGASSERT(lower);
/* Allocate an auto-mounter state structure */
priv = (FAR struct automounter_state_s *)
kmm_zalloc(sizeof(struct automounter_state_s));
if (!priv)
{
ferr("ERROR: Failed to allocate state structure\n");
return NULL;
}
/* Initialize the automounter state structure */
priv->lower = lower;
/* Handle the initial state of the mount on the caller's thread */
priv->inserted = AUTOMOUNT_INSERTED(lower);
/* Set up the first action at a delay from the initialization time (to
* allow time for any extended block driver initialization to complete.
*/
ret = work_queue(LPWORK, &priv->work, automount_worker, priv,
priv->lower->ddelay);
if (ret < 0)
{
/* NOTE: Currently, work_queue only returns success */
ferr("ERROR: Failed to schedule work: %d\n", ret);
}
/* Attach and enable automounter interrupts */
ret = AUTOMOUNT_ATTACH(lower, automount_interrupt, priv);
if (ret < 0)
{
ferr("ERROR: Failed to attach automount interrupt: %d\n", ret);
automount_uninitialize(priv);
return NULL;
}
AUTOMOUNT_ENABLE(lower);
return priv;
}
/****************************************************************************
* Name: automount_uninitialize
*
* Description:
* Stop the automounter and free resources that it used. NOTE that the
* mount is left in its last state mounted/unmounted state.
*
* Input Parameters:
* handle - The value previously returned by automount_initialize();
*
* Returned Value:
* None
*
****************************************************************************/
void automount_uninitialize(FAR void *handle)
{
FAR struct automounter_state_s *priv =
(FAR struct automounter_state_s *)handle;
FAR const struct automount_lower_s *lower;
DEBUGASSERT(priv && priv->lower);
lower = priv->lower;
/* Disable and detach interrupts */
AUTOMOUNT_DISABLE(lower);
AUTOMOUNT_DETACH(lower);
/* Cancel the watchdog timer */
wd_cancel(&priv->wdog);
/* And free the state structure */
kmm_free(priv);
}