2005-04-16 16:20:36 -06:00
|
|
|
/*
|
2005-11-01 20:58:39 -07:00
|
|
|
* Copyright (c) 2000-2005 Silicon Graphics, Inc.
|
|
|
|
* All Rights Reserved.
|
2005-04-16 16:20:36 -06:00
|
|
|
*
|
2005-11-01 20:58:39 -07:00
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License as
|
2005-04-16 16:20:36 -06:00
|
|
|
* published by the Free Software Foundation.
|
|
|
|
*
|
2005-11-01 20:58:39 -07:00
|
|
|
* This program is distributed in the hope that it would 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.
|
2005-04-16 16:20:36 -06:00
|
|
|
*
|
2005-11-01 20:58:39 -07:00
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write the Free Software Foundation,
|
|
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
2005-04-16 16:20:36 -06:00
|
|
|
*/
|
|
|
|
#include "xfs.h"
|
2005-11-01 20:38:42 -07:00
|
|
|
#include "xfs_fs.h"
|
2005-04-16 16:20:36 -06:00
|
|
|
#include "xfs_types.h"
|
2005-11-01 20:38:42 -07:00
|
|
|
#include "xfs_bit.h"
|
2005-04-16 16:20:36 -06:00
|
|
|
#include "xfs_log.h"
|
2005-11-01 20:38:42 -07:00
|
|
|
#include "xfs_inum.h"
|
2005-04-16 16:20:36 -06:00
|
|
|
#include "xfs_trans.h"
|
|
|
|
#include "xfs_sb.h"
|
2005-11-01 20:38:42 -07:00
|
|
|
#include "xfs_ag.h"
|
2005-04-16 16:20:36 -06:00
|
|
|
#include "xfs_dir2.h"
|
|
|
|
#include "xfs_dmapi.h"
|
|
|
|
#include "xfs_mount.h"
|
2005-11-01 20:38:42 -07:00
|
|
|
#include "xfs_da_btree.h"
|
2005-04-16 16:20:36 -06:00
|
|
|
#include "xfs_bmap_btree.h"
|
|
|
|
#include "xfs_ialloc_btree.h"
|
|
|
|
#include "xfs_alloc_btree.h"
|
|
|
|
#include "xfs_dir2_sf.h"
|
2005-11-01 20:38:42 -07:00
|
|
|
#include "xfs_attr_sf.h"
|
2005-04-16 16:20:36 -06:00
|
|
|
#include "xfs_dinode.h"
|
|
|
|
#include "xfs_inode.h"
|
2005-11-01 20:38:42 -07:00
|
|
|
#include "xfs_inode_item.h"
|
|
|
|
#include "xfs_btree.h"
|
|
|
|
#include "xfs_alloc.h"
|
|
|
|
#include "xfs_ialloc.h"
|
|
|
|
#include "xfs_quota.h"
|
2005-04-16 16:20:36 -06:00
|
|
|
#include "xfs_error.h"
|
|
|
|
#include "xfs_bmap.h"
|
|
|
|
#include "xfs_rw.h"
|
|
|
|
#include "xfs_refcache.h"
|
|
|
|
#include "xfs_buf_item.h"
|
2005-11-01 20:38:42 -07:00
|
|
|
#include "xfs_log_priv.h"
|
2005-04-16 16:20:36 -06:00
|
|
|
#include "xfs_dir2_trace.h"
|
2005-11-01 20:38:42 -07:00
|
|
|
#include "xfs_extfree_item.h"
|
2005-04-16 16:20:36 -06:00
|
|
|
#include "xfs_acl.h"
|
|
|
|
#include "xfs_attr.h"
|
|
|
|
#include "xfs_clnt.h"
|
2007-07-10 19:09:12 -06:00
|
|
|
#include "xfs_mru_cache.h"
|
|
|
|
#include "xfs_filestream.h"
|
2006-01-10 21:30:08 -07:00
|
|
|
#include "xfs_fsops.h"
|
2007-08-28 18:58:01 -06:00
|
|
|
#include "xfs_vnodeops.h"
|
2007-08-30 01:20:31 -06:00
|
|
|
#include "xfs_vfsops.h"
|
2007-08-28 18:58:01 -06:00
|
|
|
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
int
|
|
|
|
xfs_init(void)
|
|
|
|
{
|
|
|
|
extern kmem_zone_t *xfs_bmap_free_item_zone;
|
|
|
|
extern kmem_zone_t *xfs_btree_cur_zone;
|
|
|
|
extern kmem_zone_t *xfs_trans_zone;
|
|
|
|
extern kmem_zone_t *xfs_buf_item_zone;
|
|
|
|
extern kmem_zone_t *xfs_dabuf_zone;
|
|
|
|
#ifdef XFS_DABUF_DEBUG
|
2007-10-11 01:41:21 -06:00
|
|
|
extern spinlock_t xfs_dabuf_global_lock;
|
2007-10-11 01:43:56 -06:00
|
|
|
spin_lock_init(&xfs_dabuf_global_lock);
|
2005-04-16 16:20:36 -06:00
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Initialize all of the zone allocators we use.
|
|
|
|
*/
|
|
|
|
xfs_bmap_free_item_zone = kmem_zone_init(sizeof(xfs_bmap_free_item_t),
|
|
|
|
"xfs_bmap_free_item");
|
|
|
|
xfs_btree_cur_zone = kmem_zone_init(sizeof(xfs_btree_cur_t),
|
|
|
|
"xfs_btree_cur");
|
|
|
|
xfs_trans_zone = kmem_zone_init(sizeof(xfs_trans_t), "xfs_trans");
|
|
|
|
xfs_da_state_zone =
|
|
|
|
kmem_zone_init(sizeof(xfs_da_state_t), "xfs_da_state");
|
|
|
|
xfs_dabuf_zone = kmem_zone_init(sizeof(xfs_dabuf_t), "xfs_dabuf");
|
2006-03-13 19:18:19 -07:00
|
|
|
xfs_ifork_zone = kmem_zone_init(sizeof(xfs_ifork_t), "xfs_ifork");
|
|
|
|
xfs_acl_zone_init(xfs_acl_zone, "xfs_acl");
|
2007-07-10 19:09:12 -06:00
|
|
|
xfs_mru_cache_init();
|
|
|
|
xfs_filestream_init();
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
/*
|
|
|
|
* The size of the zone allocated buf log item is the maximum
|
|
|
|
* size possible under XFS. This wastes a little bit of memory,
|
|
|
|
* but it is much faster.
|
|
|
|
*/
|
|
|
|
xfs_buf_item_zone =
|
|
|
|
kmem_zone_init((sizeof(xfs_buf_log_item_t) +
|
|
|
|
(((XFS_MAX_BLOCKSIZE / XFS_BLI_CHUNK) /
|
|
|
|
NBWORD) * sizeof(int))),
|
|
|
|
"xfs_buf_item");
|
2006-03-13 19:18:19 -07:00
|
|
|
xfs_efd_zone =
|
|
|
|
kmem_zone_init((sizeof(xfs_efd_log_item_t) +
|
|
|
|
((XFS_EFD_MAX_FAST_EXTENTS - 1) *
|
|
|
|
sizeof(xfs_extent_t))),
|
2005-04-16 16:20:36 -06:00
|
|
|
"xfs_efd_item");
|
2006-03-13 19:18:19 -07:00
|
|
|
xfs_efi_zone =
|
|
|
|
kmem_zone_init((sizeof(xfs_efi_log_item_t) +
|
|
|
|
((XFS_EFI_MAX_FAST_EXTENTS - 1) *
|
|
|
|
sizeof(xfs_extent_t))),
|
2005-04-16 16:20:36 -06:00
|
|
|
"xfs_efi_item");
|
2006-03-13 19:18:19 -07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* These zones warrant special memory allocator hints
|
|
|
|
*/
|
|
|
|
xfs_inode_zone =
|
|
|
|
kmem_zone_init_flags(sizeof(xfs_inode_t), "xfs_inode",
|
|
|
|
KM_ZONE_HWALIGN | KM_ZONE_RECLAIM |
|
|
|
|
KM_ZONE_SPREAD, NULL);
|
|
|
|
xfs_ili_zone =
|
|
|
|
kmem_zone_init_flags(sizeof(xfs_inode_log_item_t), "xfs_ili",
|
|
|
|
KM_ZONE_SPREAD, NULL);
|
2007-08-27 22:00:13 -06:00
|
|
|
xfs_icluster_zone =
|
|
|
|
kmem_zone_init_flags(sizeof(xfs_icluster_t), "xfs_icluster",
|
2006-03-13 19:18:19 -07:00
|
|
|
KM_ZONE_SPREAD, NULL);
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Allocate global trace buffers.
|
|
|
|
*/
|
|
|
|
#ifdef XFS_ALLOC_TRACE
|
|
|
|
xfs_alloc_trace_buf = ktrace_alloc(XFS_ALLOC_TRACE_SIZE, KM_SLEEP);
|
|
|
|
#endif
|
|
|
|
#ifdef XFS_BMAP_TRACE
|
|
|
|
xfs_bmap_trace_buf = ktrace_alloc(XFS_BMAP_TRACE_SIZE, KM_SLEEP);
|
|
|
|
#endif
|
|
|
|
#ifdef XFS_BMBT_TRACE
|
|
|
|
xfs_bmbt_trace_buf = ktrace_alloc(XFS_BMBT_TRACE_SIZE, KM_SLEEP);
|
|
|
|
#endif
|
|
|
|
#ifdef XFS_ATTR_TRACE
|
|
|
|
xfs_attr_trace_buf = ktrace_alloc(XFS_ATTR_TRACE_SIZE, KM_SLEEP);
|
|
|
|
#endif
|
|
|
|
#ifdef XFS_DIR2_TRACE
|
|
|
|
xfs_dir2_trace_buf = ktrace_alloc(XFS_DIR2_GTRACE_SIZE, KM_SLEEP);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
xfs_dir_startup();
|
|
|
|
|
|
|
|
#if (defined(DEBUG) || defined(INDUCE_IO_ERROR))
|
|
|
|
xfs_error_test_init();
|
|
|
|
#endif /* DEBUG || INDUCE_IO_ERROR */
|
|
|
|
|
|
|
|
xfs_init_procfs();
|
|
|
|
xfs_sysctl_register();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xfs_cleanup(void)
|
|
|
|
{
|
|
|
|
extern kmem_zone_t *xfs_bmap_free_item_zone;
|
|
|
|
extern kmem_zone_t *xfs_btree_cur_zone;
|
|
|
|
extern kmem_zone_t *xfs_inode_zone;
|
|
|
|
extern kmem_zone_t *xfs_trans_zone;
|
|
|
|
extern kmem_zone_t *xfs_da_state_zone;
|
|
|
|
extern kmem_zone_t *xfs_dabuf_zone;
|
|
|
|
extern kmem_zone_t *xfs_efd_zone;
|
|
|
|
extern kmem_zone_t *xfs_efi_zone;
|
|
|
|
extern kmem_zone_t *xfs_buf_item_zone;
|
2007-08-27 22:00:13 -06:00
|
|
|
extern kmem_zone_t *xfs_icluster_zone;
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
xfs_cleanup_procfs();
|
|
|
|
xfs_sysctl_unregister();
|
|
|
|
xfs_refcache_destroy();
|
2007-07-10 19:09:12 -06:00
|
|
|
xfs_filestream_uninit();
|
|
|
|
xfs_mru_cache_uninit();
|
2005-04-16 16:20:36 -06:00
|
|
|
xfs_acl_zone_destroy(xfs_acl_zone);
|
|
|
|
|
|
|
|
#ifdef XFS_DIR2_TRACE
|
|
|
|
ktrace_free(xfs_dir2_trace_buf);
|
|
|
|
#endif
|
|
|
|
#ifdef XFS_ATTR_TRACE
|
|
|
|
ktrace_free(xfs_attr_trace_buf);
|
|
|
|
#endif
|
|
|
|
#ifdef XFS_BMBT_TRACE
|
|
|
|
ktrace_free(xfs_bmbt_trace_buf);
|
|
|
|
#endif
|
|
|
|
#ifdef XFS_BMAP_TRACE
|
|
|
|
ktrace_free(xfs_bmap_trace_buf);
|
|
|
|
#endif
|
|
|
|
#ifdef XFS_ALLOC_TRACE
|
|
|
|
ktrace_free(xfs_alloc_trace_buf);
|
|
|
|
#endif
|
|
|
|
|
2006-03-21 18:47:28 -07:00
|
|
|
kmem_zone_destroy(xfs_bmap_free_item_zone);
|
|
|
|
kmem_zone_destroy(xfs_btree_cur_zone);
|
|
|
|
kmem_zone_destroy(xfs_inode_zone);
|
|
|
|
kmem_zone_destroy(xfs_trans_zone);
|
|
|
|
kmem_zone_destroy(xfs_da_state_zone);
|
|
|
|
kmem_zone_destroy(xfs_dabuf_zone);
|
|
|
|
kmem_zone_destroy(xfs_buf_item_zone);
|
|
|
|
kmem_zone_destroy(xfs_efd_zone);
|
|
|
|
kmem_zone_destroy(xfs_efi_zone);
|
|
|
|
kmem_zone_destroy(xfs_ifork_zone);
|
|
|
|
kmem_zone_destroy(xfs_ili_zone);
|
2007-08-27 22:00:13 -06:00
|
|
|
kmem_zone_destroy(xfs_icluster_zone);
|
2005-04-16 16:20:36 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* xfs_start_flags
|
|
|
|
*
|
|
|
|
* This function fills in xfs_mount_t fields based on mount args.
|
|
|
|
* Note: the superblock has _not_ yet been read in.
|
|
|
|
*/
|
|
|
|
STATIC int
|
|
|
|
xfs_start_flags(
|
|
|
|
struct xfs_mount_args *ap,
|
|
|
|
struct xfs_mount *mp)
|
|
|
|
{
|
|
|
|
/* Values are in BBs */
|
|
|
|
if ((ap->flags & XFSMNT_NOALIGN) != XFSMNT_NOALIGN) {
|
|
|
|
/*
|
|
|
|
* At this point the superblock has not been read
|
|
|
|
* in, therefore we do not know the block size.
|
|
|
|
* Before the mount call ends we will convert
|
|
|
|
* these to FSBs.
|
|
|
|
*/
|
|
|
|
mp->m_dalign = ap->sunit;
|
|
|
|
mp->m_swidth = ap->swidth;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ap->logbufs != -1 &&
|
|
|
|
ap->logbufs != 0 &&
|
|
|
|
(ap->logbufs < XLOG_MIN_ICLOGS ||
|
|
|
|
ap->logbufs > XLOG_MAX_ICLOGS)) {
|
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: invalid logbufs value: %d [not %d-%d]",
|
|
|
|
ap->logbufs, XLOG_MIN_ICLOGS, XLOG_MAX_ICLOGS);
|
|
|
|
return XFS_ERROR(EINVAL);
|
|
|
|
}
|
|
|
|
mp->m_logbufs = ap->logbufs;
|
|
|
|
if (ap->logbufsize != -1 &&
|
2005-11-01 21:12:04 -07:00
|
|
|
ap->logbufsize != 0 &&
|
2007-08-16 00:25:33 -06:00
|
|
|
(ap->logbufsize < XLOG_MIN_RECORD_BSIZE ||
|
|
|
|
ap->logbufsize > XLOG_MAX_RECORD_BSIZE ||
|
|
|
|
!is_power_of_2(ap->logbufsize))) {
|
2005-04-16 16:20:36 -06:00
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: invalid logbufsize: %d [not 16k,32k,64k,128k or 256k]",
|
|
|
|
ap->logbufsize);
|
|
|
|
return XFS_ERROR(EINVAL);
|
|
|
|
}
|
|
|
|
mp->m_logbsize = ap->logbufsize;
|
|
|
|
mp->m_fsname_len = strlen(ap->fsname) + 1;
|
|
|
|
mp->m_fsname = kmem_alloc(mp->m_fsname_len, KM_SLEEP);
|
|
|
|
strcpy(mp->m_fsname, ap->fsname);
|
2005-11-01 17:44:33 -07:00
|
|
|
if (ap->rtname[0]) {
|
|
|
|
mp->m_rtname = kmem_alloc(strlen(ap->rtname) + 1, KM_SLEEP);
|
|
|
|
strcpy(mp->m_rtname, ap->rtname);
|
|
|
|
}
|
|
|
|
if (ap->logname[0]) {
|
|
|
|
mp->m_logname = kmem_alloc(strlen(ap->logname) + 1, KM_SLEEP);
|
|
|
|
strcpy(mp->m_logname, ap->logname);
|
|
|
|
}
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
if (ap->flags & XFSMNT_WSYNC)
|
|
|
|
mp->m_flags |= XFS_MOUNT_WSYNC;
|
|
|
|
#if XFS_BIG_INUMS
|
|
|
|
if (ap->flags & XFSMNT_INO64) {
|
|
|
|
mp->m_flags |= XFS_MOUNT_INO64;
|
|
|
|
mp->m_inoadd = XFS_INO64_OFFSET;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
if (ap->flags & XFSMNT_RETERR)
|
|
|
|
mp->m_flags |= XFS_MOUNT_RETERR;
|
|
|
|
if (ap->flags & XFSMNT_NOALIGN)
|
|
|
|
mp->m_flags |= XFS_MOUNT_NOALIGN;
|
|
|
|
if (ap->flags & XFSMNT_SWALLOC)
|
|
|
|
mp->m_flags |= XFS_MOUNT_SWALLOC;
|
|
|
|
if (ap->flags & XFSMNT_OSYNCISOSYNC)
|
|
|
|
mp->m_flags |= XFS_MOUNT_OSYNCISOSYNC;
|
|
|
|
if (ap->flags & XFSMNT_32BITINODES)
|
2005-11-01 21:11:45 -07:00
|
|
|
mp->m_flags |= XFS_MOUNT_32BITINODES;
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
if (ap->flags & XFSMNT_IOSIZE) {
|
|
|
|
if (ap->iosizelog > XFS_MAX_IO_LOG ||
|
|
|
|
ap->iosizelog < XFS_MIN_IO_LOG) {
|
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: invalid log iosize: %d [not %d-%d]",
|
|
|
|
ap->iosizelog, XFS_MIN_IO_LOG,
|
|
|
|
XFS_MAX_IO_LOG);
|
|
|
|
return XFS_ERROR(EINVAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
mp->m_flags |= XFS_MOUNT_DFLT_IOSIZE;
|
|
|
|
mp->m_readio_log = mp->m_writeio_log = ap->iosizelog;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ap->flags & XFSMNT_IDELETE)
|
|
|
|
mp->m_flags |= XFS_MOUNT_IDELETE;
|
|
|
|
if (ap->flags & XFSMNT_DIRSYNC)
|
|
|
|
mp->m_flags |= XFS_MOUNT_DIRSYNC;
|
2006-01-10 21:32:01 -07:00
|
|
|
if (ap->flags & XFSMNT_ATTR2)
|
|
|
|
mp->m_flags |= XFS_MOUNT_ATTR2;
|
2005-11-01 16:33:05 -07:00
|
|
|
|
2005-11-01 21:09:22 -07:00
|
|
|
if (ap->flags2 & XFSMNT2_COMPAT_IOSIZE)
|
|
|
|
mp->m_flags |= XFS_MOUNT_COMPAT_IOSIZE;
|
|
|
|
|
2005-04-16 16:20:36 -06:00
|
|
|
/*
|
|
|
|
* no recovery flag requires a read-only mount
|
|
|
|
*/
|
|
|
|
if (ap->flags & XFSMNT_NORECOVERY) {
|
2007-08-30 01:21:12 -06:00
|
|
|
if (!(mp->m_flags & XFS_MOUNT_RDONLY)) {
|
2005-04-16 16:20:36 -06:00
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: tried to mount a FS read-write without recovery!");
|
|
|
|
return XFS_ERROR(EINVAL);
|
|
|
|
}
|
|
|
|
mp->m_flags |= XFS_MOUNT_NORECOVERY;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ap->flags & XFSMNT_NOUUID)
|
|
|
|
mp->m_flags |= XFS_MOUNT_NOUUID;
|
2005-11-01 16:26:59 -07:00
|
|
|
if (ap->flags & XFSMNT_BARRIER)
|
|
|
|
mp->m_flags |= XFS_MOUNT_BARRIER;
|
2006-01-10 21:27:18 -07:00
|
|
|
else
|
|
|
|
mp->m_flags &= ~XFS_MOUNT_BARRIER;
|
2005-04-16 16:20:36 -06:00
|
|
|
|
2007-07-10 19:09:12 -06:00
|
|
|
if (ap->flags2 & XFSMNT2_FILESTREAMS)
|
|
|
|
mp->m_flags |= XFS_MOUNT_FILESTREAMS;
|
|
|
|
|
2007-08-28 19:59:36 -06:00
|
|
|
if (ap->flags & XFSMNT_DMAPI)
|
2007-08-30 01:21:12 -06:00
|
|
|
mp->m_flags |= XFS_MOUNT_DMAPI;
|
2005-04-16 16:20:36 -06:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This function fills in xfs_mount_t fields based on mount args.
|
|
|
|
* Note: the superblock _has_ now been read in.
|
|
|
|
*/
|
|
|
|
STATIC int
|
|
|
|
xfs_finish_flags(
|
|
|
|
struct xfs_mount_args *ap,
|
|
|
|
struct xfs_mount *mp)
|
|
|
|
{
|
2007-08-30 01:21:12 -06:00
|
|
|
int ronly = (mp->m_flags & XFS_MOUNT_RDONLY);
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
/* Fail a mount where the logbuf is smaller then the log stripe */
|
|
|
|
if (XFS_SB_VERSION_HASLOGV2(&mp->m_sb)) {
|
2006-01-10 21:35:32 -07:00
|
|
|
if ((ap->logbufsize <= 0) &&
|
2005-04-16 16:20:36 -06:00
|
|
|
(mp->m_sb.sb_logsunit > XLOG_BIG_RECORD_BSIZE)) {
|
|
|
|
mp->m_logbsize = mp->m_sb.sb_logsunit;
|
2006-01-10 21:35:32 -07:00
|
|
|
} else if (ap->logbufsize > 0 &&
|
|
|
|
ap->logbufsize < mp->m_sb.sb_logsunit) {
|
2005-04-16 16:20:36 -06:00
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: logbuf size must be greater than or equal to log stripe size");
|
|
|
|
return XFS_ERROR(EINVAL);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* Fail a mount if the logbuf is larger than 32K */
|
|
|
|
if (ap->logbufsize > XLOG_BIG_RECORD_BSIZE) {
|
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: logbuf size for version 1 logs must be 16K or 32K");
|
|
|
|
return XFS_ERROR(EINVAL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-01-10 21:32:01 -07:00
|
|
|
if (XFS_SB_VERSION_HASATTR2(&mp->m_sb)) {
|
|
|
|
mp->m_flags |= XFS_MOUNT_ATTR2;
|
|
|
|
}
|
|
|
|
|
2005-04-16 16:20:36 -06:00
|
|
|
/*
|
|
|
|
* prohibit r/w mounts of read-only filesystems
|
|
|
|
*/
|
|
|
|
if ((mp->m_sb.sb_flags & XFS_SBF_READONLY) && !ronly) {
|
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: cannot mount a read-only filesystem as read-write");
|
|
|
|
return XFS_ERROR(EROFS);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* check for shared mount.
|
|
|
|
*/
|
|
|
|
if (ap->flags & XFSMNT_SHARED) {
|
|
|
|
if (!XFS_SB_VERSION_HASSHARED(&mp->m_sb))
|
|
|
|
return XFS_ERROR(EINVAL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* For IRIX 6.5, shared mounts must have the shared
|
|
|
|
* version bit set, have the persistent readonly
|
|
|
|
* field set, must be version 0 and can only be mounted
|
|
|
|
* read-only.
|
|
|
|
*/
|
|
|
|
if (!ronly || !(mp->m_sb.sb_flags & XFS_SBF_READONLY) ||
|
|
|
|
(mp->m_sb.sb_shared_vn != 0))
|
|
|
|
return XFS_ERROR(EINVAL);
|
|
|
|
|
|
|
|
mp->m_flags |= XFS_MOUNT_SHARED;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Shared XFS V0 can't deal with DMI. Return EINVAL.
|
|
|
|
*/
|
|
|
|
if (mp->m_sb.sb_shared_vn == 0 && (ap->flags & XFSMNT_DMAPI))
|
|
|
|
return XFS_ERROR(EINVAL);
|
|
|
|
}
|
|
|
|
|
2007-08-30 01:19:57 -06:00
|
|
|
if (ap->flags & XFSMNT_UQUOTA) {
|
|
|
|
mp->m_qflags |= (XFS_UQUOTA_ACCT | XFS_UQUOTA_ACTIVE);
|
|
|
|
if (ap->flags & XFSMNT_UQUOTAENF)
|
|
|
|
mp->m_qflags |= XFS_UQUOTA_ENFD;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ap->flags & XFSMNT_GQUOTA) {
|
|
|
|
mp->m_qflags |= (XFS_GQUOTA_ACCT | XFS_GQUOTA_ACTIVE);
|
|
|
|
if (ap->flags & XFSMNT_GQUOTAENF)
|
|
|
|
mp->m_qflags |= XFS_OQUOTA_ENFD;
|
|
|
|
} else if (ap->flags & XFSMNT_PQUOTA) {
|
|
|
|
mp->m_qflags |= (XFS_PQUOTA_ACCT | XFS_PQUOTA_ACTIVE);
|
|
|
|
if (ap->flags & XFSMNT_PQUOTAENF)
|
|
|
|
mp->m_qflags |= XFS_OQUOTA_ENFD;
|
|
|
|
}
|
|
|
|
|
2005-04-16 16:20:36 -06:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* xfs_mount
|
|
|
|
*
|
|
|
|
* The file system configurations are:
|
|
|
|
* (1) device (partition) with data and internal log
|
|
|
|
* (2) logical volume with data and log subvolumes.
|
|
|
|
* (3) logical volume with data, log, and realtime subvolumes.
|
|
|
|
*
|
|
|
|
* We only have to handle opening the log and realtime volumes here if
|
|
|
|
* they are present. The data subvolume has already been opened by
|
|
|
|
* get_sb_bdev() and is stored in vfsp->vfs_super->s_bdev.
|
|
|
|
*/
|
2007-08-30 01:20:31 -06:00
|
|
|
int
|
2005-04-16 16:20:36 -06:00
|
|
|
xfs_mount(
|
2007-08-30 01:20:31 -06:00
|
|
|
struct xfs_mount *mp,
|
2005-04-16 16:20:36 -06:00
|
|
|
struct xfs_mount_args *args,
|
|
|
|
cred_t *credp)
|
|
|
|
{
|
|
|
|
struct block_device *ddev, *logdev, *rtdev;
|
|
|
|
int flags = 0, error;
|
|
|
|
|
2007-08-30 01:21:30 -06:00
|
|
|
ddev = mp->m_super->s_bdev;
|
2005-04-16 16:20:36 -06:00
|
|
|
logdev = rtdev = NULL;
|
|
|
|
|
2007-08-28 19:59:36 -06:00
|
|
|
error = xfs_dmops_get(mp, args);
|
2007-08-30 01:19:57 -06:00
|
|
|
if (error)
|
|
|
|
return error;
|
|
|
|
error = xfs_qmops_get(mp, args);
|
2007-08-28 19:59:36 -06:00
|
|
|
if (error)
|
|
|
|
return error;
|
|
|
|
|
2006-03-30 20:04:17 -07:00
|
|
|
if (args->flags & XFSMNT_QUIET)
|
|
|
|
flags |= XFS_MFSI_QUIET;
|
|
|
|
|
2005-04-16 16:20:36 -06:00
|
|
|
/*
|
|
|
|
* Open real time and log devices - order is important.
|
|
|
|
*/
|
|
|
|
if (args->logname[0]) {
|
|
|
|
error = xfs_blkdev_get(mp, args->logname, &logdev);
|
|
|
|
if (error)
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
if (args->rtname[0]) {
|
|
|
|
error = xfs_blkdev_get(mp, args->rtname, &rtdev);
|
|
|
|
if (error) {
|
|
|
|
xfs_blkdev_put(logdev);
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rtdev == ddev || rtdev == logdev) {
|
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: Cannot mount filesystem with identical rtdev and ddev/logdev.");
|
|
|
|
xfs_blkdev_put(logdev);
|
|
|
|
xfs_blkdev_put(rtdev);
|
|
|
|
return EINVAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Setup xfs_mount buffer target pointers
|
|
|
|
*/
|
|
|
|
error = ENOMEM;
|
|
|
|
mp->m_ddev_targp = xfs_alloc_buftarg(ddev, 0);
|
|
|
|
if (!mp->m_ddev_targp) {
|
|
|
|
xfs_blkdev_put(logdev);
|
|
|
|
xfs_blkdev_put(rtdev);
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
if (rtdev) {
|
|
|
|
mp->m_rtdev_targp = xfs_alloc_buftarg(rtdev, 1);
|
2007-08-16 00:25:42 -06:00
|
|
|
if (!mp->m_rtdev_targp) {
|
|
|
|
xfs_blkdev_put(logdev);
|
|
|
|
xfs_blkdev_put(rtdev);
|
2005-04-16 16:20:36 -06:00
|
|
|
goto error0;
|
2007-08-16 00:25:42 -06:00
|
|
|
}
|
2005-04-16 16:20:36 -06:00
|
|
|
}
|
|
|
|
mp->m_logdev_targp = (logdev && logdev != ddev) ?
|
|
|
|
xfs_alloc_buftarg(logdev, 1) : mp->m_ddev_targp;
|
2007-08-16 00:25:42 -06:00
|
|
|
if (!mp->m_logdev_targp) {
|
|
|
|
xfs_blkdev_put(logdev);
|
|
|
|
xfs_blkdev_put(rtdev);
|
2005-04-16 16:20:36 -06:00
|
|
|
goto error0;
|
2007-08-16 00:25:42 -06:00
|
|
|
}
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Setup flags based on mount(2) options and then the superblock
|
|
|
|
*/
|
2007-08-30 01:21:30 -06:00
|
|
|
error = xfs_start_flags(args, mp);
|
2005-04-16 16:20:36 -06:00
|
|
|
if (error)
|
|
|
|
goto error1;
|
2006-03-30 20:04:17 -07:00
|
|
|
error = xfs_readsb(mp, flags);
|
2005-04-16 16:20:36 -06:00
|
|
|
if (error)
|
|
|
|
goto error1;
|
2007-08-30 01:21:30 -06:00
|
|
|
error = xfs_finish_flags(args, mp);
|
2005-04-16 16:20:36 -06:00
|
|
|
if (error)
|
|
|
|
goto error2;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Setup xfs_mount buffer target pointers based on superblock
|
|
|
|
*/
|
|
|
|
error = xfs_setsize_buftarg(mp->m_ddev_targp, mp->m_sb.sb_blocksize,
|
|
|
|
mp->m_sb.sb_sectsize);
|
|
|
|
if (!error && logdev && logdev != ddev) {
|
|
|
|
unsigned int log_sector_size = BBSIZE;
|
|
|
|
|
|
|
|
if (XFS_SB_VERSION_HASSECTOR(&mp->m_sb))
|
|
|
|
log_sector_size = mp->m_sb.sb_logsectsize;
|
|
|
|
error = xfs_setsize_buftarg(mp->m_logdev_targp,
|
|
|
|
mp->m_sb.sb_blocksize,
|
|
|
|
log_sector_size);
|
|
|
|
}
|
|
|
|
if (!error && rtdev)
|
|
|
|
error = xfs_setsize_buftarg(mp->m_rtdev_targp,
|
|
|
|
mp->m_sb.sb_blocksize,
|
|
|
|
mp->m_sb.sb_sectsize);
|
|
|
|
if (error)
|
|
|
|
goto error2;
|
|
|
|
|
2006-07-28 01:05:13 -06:00
|
|
|
if (mp->m_flags & XFS_MOUNT_BARRIER)
|
2006-01-10 21:28:56 -07:00
|
|
|
xfs_mountfs_check_barriers(mp);
|
|
|
|
|
2007-07-10 19:09:12 -06:00
|
|
|
if ((error = xfs_filestream_mount(mp)))
|
|
|
|
goto error2;
|
|
|
|
|
2007-10-11 01:34:33 -06:00
|
|
|
error = xfs_mountfs(mp, flags);
|
2005-11-01 16:26:59 -07:00
|
|
|
if (error)
|
|
|
|
goto error2;
|
|
|
|
|
2007-08-28 19:59:36 -06:00
|
|
|
XFS_SEND_MOUNT(mp, DM_RIGHT_NULL, args->mtpt, args->fsname);
|
|
|
|
|
2005-11-01 16:26:59 -07:00
|
|
|
return 0;
|
|
|
|
|
2005-04-16 16:20:36 -06:00
|
|
|
error2:
|
|
|
|
if (mp->m_sb_bp)
|
|
|
|
xfs_freesb(mp);
|
|
|
|
error1:
|
|
|
|
xfs_binval(mp->m_ddev_targp);
|
|
|
|
if (logdev && logdev != ddev)
|
|
|
|
xfs_binval(mp->m_logdev_targp);
|
|
|
|
if (rtdev)
|
|
|
|
xfs_binval(mp->m_rtdev_targp);
|
|
|
|
error0:
|
|
|
|
xfs_unmountfs_close(mp, credp);
|
2007-08-30 01:19:57 -06:00
|
|
|
xfs_qmops_put(mp);
|
2007-08-28 19:59:36 -06:00
|
|
|
xfs_dmops_put(mp);
|
2005-04-16 16:20:36 -06:00
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
2007-08-30 01:20:31 -06:00
|
|
|
int
|
2005-04-16 16:20:36 -06:00
|
|
|
xfs_unmount(
|
2007-08-30 01:20:31 -06:00
|
|
|
xfs_mount_t *mp,
|
2005-04-16 16:20:36 -06:00
|
|
|
int flags,
|
|
|
|
cred_t *credp)
|
|
|
|
{
|
|
|
|
xfs_inode_t *rip;
|
2006-06-09 01:00:52 -06:00
|
|
|
bhv_vnode_t *rvp;
|
2005-04-16 16:20:36 -06:00
|
|
|
int unmount_event_wanted = 0;
|
|
|
|
int unmount_event_flags = 0;
|
|
|
|
int xfs_unmountfs_needed = 0;
|
|
|
|
int error;
|
|
|
|
|
|
|
|
rip = mp->m_rootip;
|
|
|
|
rvp = XFS_ITOV(rip);
|
|
|
|
|
2007-08-23 00:19:57 -06:00
|
|
|
#ifdef HAVE_DMAPI
|
2007-08-30 01:21:12 -06:00
|
|
|
if (mp->m_flags & XFS_MOUNT_DMAPI) {
|
2007-08-30 01:21:30 -06:00
|
|
|
error = XFS_SEND_PREUNMOUNT(mp,
|
2005-04-16 16:20:36 -06:00
|
|
|
rvp, DM_RIGHT_NULL, rvp, DM_RIGHT_NULL,
|
|
|
|
NULL, NULL, 0, 0,
|
|
|
|
(mp->m_dmevmask & (1<<DM_EVENT_PREUNMOUNT))?
|
|
|
|
0:DM_FLAGS_UNWANTED);
|
|
|
|
if (error)
|
|
|
|
return XFS_ERROR(error);
|
|
|
|
unmount_event_wanted = 1;
|
|
|
|
unmount_event_flags = (mp->m_dmevmask & (1<<DM_EVENT_UNMOUNT))?
|
|
|
|
0 : DM_FLAGS_UNWANTED;
|
|
|
|
}
|
2007-08-23 00:19:57 -06:00
|
|
|
#endif
|
2005-04-16 16:20:36 -06:00
|
|
|
/*
|
|
|
|
* First blow any referenced inode from this file system
|
|
|
|
* out of the reference cache, and delete the timer.
|
|
|
|
*/
|
|
|
|
xfs_refcache_purge_mp(mp);
|
|
|
|
|
2007-07-10 19:09:12 -06:00
|
|
|
/*
|
|
|
|
* Blow away any referenced inode in the filestreams cache.
|
|
|
|
* This can and will cause log traffic as inodes go inactive
|
|
|
|
* here.
|
|
|
|
*/
|
|
|
|
xfs_filestream_unmount(mp);
|
|
|
|
|
2005-04-16 16:20:36 -06:00
|
|
|
XFS_bflush(mp->m_ddev_targp);
|
|
|
|
error = xfs_unmount_flush(mp, 0);
|
|
|
|
if (error)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
ASSERT(vn_count(rvp) == 1);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Drop the reference count
|
|
|
|
*/
|
|
|
|
VN_RELE(rvp);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we're forcing a shutdown, typically because of a media error,
|
|
|
|
* we want to make sure we invalidate dirty pages that belong to
|
|
|
|
* referenced vnodes as well.
|
|
|
|
*/
|
|
|
|
if (XFS_FORCED_SHUTDOWN(mp)) {
|
2007-08-30 01:20:31 -06:00
|
|
|
error = xfs_sync(mp, SYNC_WAIT | SYNC_CLOSE);
|
2005-04-16 16:20:36 -06:00
|
|
|
ASSERT(error != EFSCORRUPTED);
|
|
|
|
}
|
|
|
|
xfs_unmountfs_needed = 1;
|
|
|
|
|
|
|
|
out:
|
|
|
|
/* Send DMAPI event, if required.
|
|
|
|
* Then do xfs_unmountfs() if needed.
|
|
|
|
* Then return error (or zero).
|
|
|
|
*/
|
|
|
|
if (unmount_event_wanted) {
|
|
|
|
/* Note: mp structure must still exist for
|
|
|
|
* XFS_SEND_UNMOUNT() call.
|
|
|
|
*/
|
2007-08-30 01:21:30 -06:00
|
|
|
XFS_SEND_UNMOUNT(mp, error == 0 ? rvp : NULL,
|
2005-04-16 16:20:36 -06:00
|
|
|
DM_RIGHT_NULL, 0, error, unmount_event_flags);
|
|
|
|
}
|
|
|
|
if (xfs_unmountfs_needed) {
|
|
|
|
/*
|
|
|
|
* Call common unmount function to flush to disk
|
|
|
|
* and free the super block buffer & mount structures.
|
|
|
|
*/
|
|
|
|
xfs_unmountfs(mp, credp);
|
2007-08-30 01:19:57 -06:00
|
|
|
xfs_qmops_put(mp);
|
2007-08-28 19:59:36 -06:00
|
|
|
xfs_dmops_put(mp);
|
2007-08-28 19:53:22 -06:00
|
|
|
kmem_free(mp, sizeof(xfs_mount_t));
|
2005-04-16 16:20:36 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return XFS_ERROR(error);
|
|
|
|
}
|
|
|
|
|
2005-06-20 23:40:48 -06:00
|
|
|
STATIC int
|
|
|
|
xfs_quiesce_fs(
|
|
|
|
xfs_mount_t *mp)
|
|
|
|
{
|
|
|
|
int count = 0, pincount;
|
2006-03-21 18:47:28 -07:00
|
|
|
|
2005-06-20 23:40:48 -06:00
|
|
|
xfs_refcache_purge_mp(mp);
|
|
|
|
xfs_flush_buftarg(mp->m_ddev_targp, 0);
|
|
|
|
xfs_finish_reclaim_all(mp, 0);
|
|
|
|
|
|
|
|
/* This loop must run at least twice.
|
|
|
|
* The first instance of the loop will flush
|
|
|
|
* most meta data but that will generate more
|
|
|
|
* meta data (typically directory updates).
|
|
|
|
* Which then must be flushed and logged before
|
|
|
|
* we can write the unmount record.
|
2006-03-21 18:47:28 -07:00
|
|
|
*/
|
2005-06-20 23:40:48 -06:00
|
|
|
do {
|
2007-06-18 00:50:48 -06:00
|
|
|
xfs_syncsub(mp, SYNC_INODE_QUIESCE, NULL);
|
2005-06-20 23:40:48 -06:00
|
|
|
pincount = xfs_flush_buftarg(mp->m_ddev_targp, 1);
|
|
|
|
if (!pincount) {
|
|
|
|
delay(50);
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
} while (count < 2);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2005-04-16 16:20:36 -06:00
|
|
|
|
2007-06-18 00:50:48 -06:00
|
|
|
/*
|
|
|
|
* Second stage of a quiesce. The data is already synced, now we have to take
|
|
|
|
* care of the metadata. New transactions are already blocked, so we need to
|
|
|
|
* wait for any remaining transactions to drain out before proceding.
|
|
|
|
*/
|
2007-10-11 02:11:14 -06:00
|
|
|
void
|
2007-06-18 00:50:48 -06:00
|
|
|
xfs_attr_quiesce(
|
|
|
|
xfs_mount_t *mp)
|
|
|
|
{
|
|
|
|
/* wait for all modifications to complete */
|
|
|
|
while (atomic_read(&mp->m_active_trans) > 0)
|
|
|
|
delay(100);
|
|
|
|
|
|
|
|
/* flush inodes and push all remaining buffers out to disk */
|
|
|
|
xfs_quiesce_fs(mp);
|
|
|
|
|
|
|
|
ASSERT_ALWAYS(atomic_read(&mp->m_active_trans) == 0);
|
|
|
|
|
|
|
|
/* Push the superblock and write an unmount record */
|
|
|
|
xfs_log_sbcount(mp, 1);
|
|
|
|
xfs_log_unmount_write(mp);
|
|
|
|
xfs_unmountfs_writesb(mp);
|
|
|
|
}
|
|
|
|
|
2007-08-30 01:20:31 -06:00
|
|
|
int
|
2005-04-16 16:20:36 -06:00
|
|
|
xfs_mntupdate(
|
2007-08-30 01:20:31 -06:00
|
|
|
struct xfs_mount *mp,
|
2005-04-16 16:20:36 -06:00
|
|
|
int *flags,
|
|
|
|
struct xfs_mount_args *args)
|
|
|
|
{
|
2006-05-08 03:51:28 -06:00
|
|
|
if (!(*flags & MS_RDONLY)) { /* rw/ro -> rw */
|
2007-08-30 01:21:12 -06:00
|
|
|
if (mp->m_flags & XFS_MOUNT_RDONLY)
|
|
|
|
mp->m_flags &= ~XFS_MOUNT_RDONLY;
|
2006-05-08 03:51:28 -06:00
|
|
|
if (args->flags & XFSMNT_BARRIER) {
|
|
|
|
mp->m_flags |= XFS_MOUNT_BARRIER;
|
2005-11-01 16:26:59 -07:00
|
|
|
xfs_mountfs_check_barriers(mp);
|
2006-05-08 03:51:28 -06:00
|
|
|
} else {
|
|
|
|
mp->m_flags &= ~XFS_MOUNT_BARRIER;
|
|
|
|
}
|
2007-08-30 01:21:12 -06:00
|
|
|
} else if (!(mp->m_flags & XFS_MOUNT_RDONLY)) { /* rw -> ro */
|
2007-07-10 19:09:12 -06:00
|
|
|
xfs_filestream_flush(mp);
|
2007-08-30 01:20:39 -06:00
|
|
|
xfs_sync(mp, SYNC_DATA_QUIESCE);
|
2007-06-18 00:50:48 -06:00
|
|
|
xfs_attr_quiesce(mp);
|
2007-08-30 01:21:12 -06:00
|
|
|
mp->m_flags |= XFS_MOUNT_RDONLY;
|
2005-04-16 16:20:36 -06:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* xfs_unmount_flush implements a set of flush operation on special
|
|
|
|
* inodes, which are needed as a separate set of operations so that
|
|
|
|
* they can be called as part of relocation process.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
xfs_unmount_flush(
|
|
|
|
xfs_mount_t *mp, /* Mount structure we are getting
|
|
|
|
rid of. */
|
|
|
|
int relocation) /* Called from vfs relocation. */
|
|
|
|
{
|
|
|
|
xfs_inode_t *rip = mp->m_rootip;
|
|
|
|
xfs_inode_t *rbmip;
|
|
|
|
xfs_inode_t *rsumip = NULL;
|
2006-06-09 01:00:52 -06:00
|
|
|
bhv_vnode_t *rvp = XFS_ITOV(rip);
|
2005-04-16 16:20:36 -06:00
|
|
|
int error;
|
|
|
|
|
2007-05-07 21:50:19 -06:00
|
|
|
xfs_ilock(rip, XFS_ILOCK_EXCL | XFS_ILOCK_PARENT);
|
2005-04-16 16:20:36 -06:00
|
|
|
xfs_iflock(rip);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Flush out the real time inodes.
|
|
|
|
*/
|
|
|
|
if ((rbmip = mp->m_rbmip) != NULL) {
|
|
|
|
xfs_ilock(rbmip, XFS_ILOCK_EXCL);
|
|
|
|
xfs_iflock(rbmip);
|
|
|
|
error = xfs_iflush(rbmip, XFS_IFLUSH_SYNC);
|
|
|
|
xfs_iunlock(rbmip, XFS_ILOCK_EXCL);
|
|
|
|
|
|
|
|
if (error == EFSCORRUPTED)
|
|
|
|
goto fscorrupt_out;
|
|
|
|
|
|
|
|
ASSERT(vn_count(XFS_ITOV(rbmip)) == 1);
|
|
|
|
|
|
|
|
rsumip = mp->m_rsumip;
|
|
|
|
xfs_ilock(rsumip, XFS_ILOCK_EXCL);
|
|
|
|
xfs_iflock(rsumip);
|
|
|
|
error = xfs_iflush(rsumip, XFS_IFLUSH_SYNC);
|
|
|
|
xfs_iunlock(rsumip, XFS_ILOCK_EXCL);
|
|
|
|
|
|
|
|
if (error == EFSCORRUPTED)
|
|
|
|
goto fscorrupt_out;
|
|
|
|
|
|
|
|
ASSERT(vn_count(XFS_ITOV(rsumip)) == 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Synchronously flush root inode to disk
|
|
|
|
*/
|
|
|
|
error = xfs_iflush(rip, XFS_IFLUSH_SYNC);
|
|
|
|
if (error == EFSCORRUPTED)
|
|
|
|
goto fscorrupt_out2;
|
|
|
|
|
|
|
|
if (vn_count(rvp) != 1 && !relocation) {
|
|
|
|
xfs_iunlock(rip, XFS_ILOCK_EXCL);
|
|
|
|
return XFS_ERROR(EBUSY);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Release dquot that rootinode, rbmino and rsumino might be holding,
|
|
|
|
* flush and purge the quota inodes.
|
|
|
|
*/
|
|
|
|
error = XFS_QM_UNMOUNT(mp);
|
|
|
|
if (error == EFSCORRUPTED)
|
|
|
|
goto fscorrupt_out2;
|
|
|
|
|
|
|
|
if (rbmip) {
|
|
|
|
VN_RELE(XFS_ITOV(rbmip));
|
|
|
|
VN_RELE(XFS_ITOV(rsumip));
|
|
|
|
}
|
|
|
|
|
|
|
|
xfs_iunlock(rip, XFS_ILOCK_EXCL);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
fscorrupt_out:
|
|
|
|
xfs_ifunlock(rip);
|
|
|
|
|
|
|
|
fscorrupt_out2:
|
|
|
|
xfs_iunlock(rip, XFS_ILOCK_EXCL);
|
|
|
|
|
|
|
|
return XFS_ERROR(EFSCORRUPTED);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* xfs_root extracts the root vnode from a vfs.
|
|
|
|
*
|
|
|
|
* vfsp -- the vfs struct for the desired file system
|
|
|
|
* vpp -- address of the caller's vnode pointer which should be
|
|
|
|
* set to the desired fs root vnode
|
|
|
|
*/
|
2007-08-30 01:20:31 -06:00
|
|
|
int
|
2005-04-16 16:20:36 -06:00
|
|
|
xfs_root(
|
2007-08-30 01:20:31 -06:00
|
|
|
xfs_mount_t *mp,
|
2006-06-09 01:00:52 -06:00
|
|
|
bhv_vnode_t **vpp)
|
2005-04-16 16:20:36 -06:00
|
|
|
{
|
2006-06-09 01:00:52 -06:00
|
|
|
bhv_vnode_t *vp;
|
2005-04-16 16:20:36 -06:00
|
|
|
|
2007-08-30 01:20:31 -06:00
|
|
|
vp = XFS_ITOV(mp->m_rootip);
|
2005-04-16 16:20:36 -06:00
|
|
|
VN_HOLD(vp);
|
|
|
|
*vpp = vp;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* xfs_sync flushes any pending I/O to file system vfsp.
|
|
|
|
*
|
|
|
|
* This routine is called by vfs_sync() to make sure that things make it
|
|
|
|
* out to disk eventually, on sync() system calls to flush out everything,
|
|
|
|
* and when the file system is unmounted. For the vfs_sync() case, all
|
|
|
|
* we really need to do is sync out the log to make all of our meta-data
|
|
|
|
* updates permanent (except for timestamps). For calls from pflushd(),
|
|
|
|
* dirty pages are kept moving by calling pdflush() on the inodes
|
|
|
|
* containing them. We also flush the inodes that we can lock without
|
|
|
|
* sleeping and the superblock if we can lock it without sleeping from
|
|
|
|
* vfs_sync() so that items at the tail of the log are always moving out.
|
|
|
|
*
|
|
|
|
* Flags:
|
|
|
|
* SYNC_BDFLUSH - We're being called from vfs_sync() so we don't want
|
|
|
|
* to sleep if we can help it. All we really need
|
|
|
|
* to do is ensure that the log is synced at least
|
|
|
|
* periodically. We also push the inodes and
|
|
|
|
* superblock if we can lock them without sleeping
|
|
|
|
* and they are not pinned.
|
|
|
|
* SYNC_ATTR - We need to flush the inodes. If SYNC_BDFLUSH is not
|
|
|
|
* set, then we really want to lock each inode and flush
|
|
|
|
* it.
|
|
|
|
* SYNC_WAIT - All the flushes that take place in this call should
|
|
|
|
* be synchronous.
|
|
|
|
* SYNC_DELWRI - This tells us to push dirty pages associated with
|
|
|
|
* inodes. SYNC_WAIT and SYNC_BDFLUSH are used to
|
|
|
|
* determine if they should be flushed sync, async, or
|
|
|
|
* delwri.
|
|
|
|
* SYNC_CLOSE - This flag is passed when the system is being
|
2006-03-28 15:55:14 -07:00
|
|
|
* unmounted. We should sync and invalidate everything.
|
2005-04-16 16:20:36 -06:00
|
|
|
* SYNC_FSDATA - This indicates that the caller would like to make
|
|
|
|
* sure the superblock is safe on disk. We can ensure
|
2006-03-28 15:55:14 -07:00
|
|
|
* this by simply making sure the log gets flushed
|
2005-04-16 16:20:36 -06:00
|
|
|
* if SYNC_BDFLUSH is set, and by actually writing it
|
|
|
|
* out otherwise.
|
2007-02-10 00:37:22 -07:00
|
|
|
* SYNC_IOWAIT - The caller wants us to wait for all data I/O to complete
|
|
|
|
* before we return (including direct I/O). Forms the drain
|
|
|
|
* side of the write barrier needed to safely quiesce the
|
|
|
|
* filesystem.
|
2005-04-16 16:20:36 -06:00
|
|
|
*
|
|
|
|
*/
|
2007-08-30 01:20:31 -06:00
|
|
|
int
|
2005-04-16 16:20:36 -06:00
|
|
|
xfs_sync(
|
2007-08-30 01:20:31 -06:00
|
|
|
xfs_mount_t *mp,
|
|
|
|
int flags)
|
2005-04-16 16:20:36 -06:00
|
|
|
{
|
2007-08-30 01:19:57 -06:00
|
|
|
int error;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get the Quota Manager to flush the dquots.
|
|
|
|
*
|
|
|
|
* If XFS quota support is not enabled or this filesystem
|
|
|
|
* instance does not use quotas XFS_QM_DQSYNC will always
|
|
|
|
* return zero.
|
|
|
|
*/
|
|
|
|
error = XFS_QM_DQSYNC(mp, flags);
|
|
|
|
if (error) {
|
|
|
|
/*
|
|
|
|
* If we got an IO error, we will be shutting down.
|
|
|
|
* So, there's nothing more for us to do here.
|
|
|
|
*/
|
|
|
|
ASSERT(error != EIO || XFS_FORCED_SHUTDOWN(mp));
|
|
|
|
if (XFS_FORCED_SHUTDOWN(mp))
|
|
|
|
return XFS_ERROR(error);
|
|
|
|
}
|
2005-04-16 16:20:36 -06:00
|
|
|
|
2007-07-10 19:09:12 -06:00
|
|
|
if (flags & SYNC_IOWAIT)
|
|
|
|
xfs_filestream_flush(mp);
|
|
|
|
|
2007-02-10 00:36:40 -07:00
|
|
|
return xfs_syncsub(mp, flags, NULL);
|
2005-04-16 16:20:36 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* xfs sync routine for internal use
|
|
|
|
*
|
2006-06-09 00:48:30 -06:00
|
|
|
* This routine supports all of the flags defined for the generic vfs_sync
|
2007-02-10 00:35:33 -07:00
|
|
|
* interface as explained above under xfs_sync.
|
2005-04-16 16:20:36 -06:00
|
|
|
*
|
|
|
|
*/
|
2005-11-01 16:32:38 -07:00
|
|
|
int
|
2005-04-16 16:20:36 -06:00
|
|
|
xfs_sync_inodes(
|
|
|
|
xfs_mount_t *mp,
|
|
|
|
int flags,
|
|
|
|
int *bypassed)
|
|
|
|
{
|
|
|
|
xfs_inode_t *ip = NULL;
|
2006-06-09 01:00:52 -06:00
|
|
|
bhv_vnode_t *vp = NULL;
|
2005-04-16 16:20:36 -06:00
|
|
|
int error;
|
|
|
|
int last_error;
|
|
|
|
uint64_t fflag;
|
|
|
|
uint lock_flags;
|
|
|
|
uint base_lock_flags;
|
|
|
|
boolean_t mount_locked;
|
|
|
|
boolean_t vnode_refed;
|
|
|
|
int preempt;
|
|
|
|
xfs_iptr_t *ipointer;
|
|
|
|
#ifdef DEBUG
|
|
|
|
boolean_t ipointer_in = B_FALSE;
|
|
|
|
|
|
|
|
#define IPOINTER_SET ipointer_in = B_TRUE
|
|
|
|
#define IPOINTER_CLR ipointer_in = B_FALSE
|
|
|
|
#else
|
|
|
|
#define IPOINTER_SET
|
|
|
|
#define IPOINTER_CLR
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
/* Insert a marker record into the inode list after inode ip. The list
|
|
|
|
* must be locked when this is called. After the call the list will no
|
|
|
|
* longer be locked.
|
|
|
|
*/
|
|
|
|
#define IPOINTER_INSERT(ip, mp) { \
|
|
|
|
ASSERT(ipointer_in == B_FALSE); \
|
|
|
|
ipointer->ip_mnext = ip->i_mnext; \
|
|
|
|
ipointer->ip_mprev = ip; \
|
|
|
|
ip->i_mnext = (xfs_inode_t *)ipointer; \
|
|
|
|
ipointer->ip_mnext->i_mprev = (xfs_inode_t *)ipointer; \
|
|
|
|
preempt = 0; \
|
|
|
|
XFS_MOUNT_IUNLOCK(mp); \
|
|
|
|
mount_locked = B_FALSE; \
|
|
|
|
IPOINTER_SET; \
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Remove the marker from the inode list. If the marker was the only item
|
|
|
|
* in the list then there are no remaining inodes and we should zero out
|
|
|
|
* the whole list. If we are the current head of the list then move the head
|
|
|
|
* past us.
|
|
|
|
*/
|
|
|
|
#define IPOINTER_REMOVE(ip, mp) { \
|
|
|
|
ASSERT(ipointer_in == B_TRUE); \
|
|
|
|
if (ipointer->ip_mnext != (xfs_inode_t *)ipointer) { \
|
|
|
|
ip = ipointer->ip_mnext; \
|
|
|
|
ip->i_mprev = ipointer->ip_mprev; \
|
|
|
|
ipointer->ip_mprev->i_mnext = ip; \
|
|
|
|
if (mp->m_inodes == (xfs_inode_t *)ipointer) { \
|
|
|
|
mp->m_inodes = ip; \
|
|
|
|
} \
|
|
|
|
} else { \
|
|
|
|
ASSERT(mp->m_inodes == (xfs_inode_t *)ipointer); \
|
|
|
|
mp->m_inodes = NULL; \
|
|
|
|
ip = NULL; \
|
|
|
|
} \
|
|
|
|
IPOINTER_CLR; \
|
|
|
|
}
|
|
|
|
|
|
|
|
#define XFS_PREEMPT_MASK 0x7f
|
|
|
|
|
2007-09-13 23:21:08 -06:00
|
|
|
ASSERT(!(flags & SYNC_BDFLUSH));
|
|
|
|
|
2005-04-16 16:20:36 -06:00
|
|
|
if (bypassed)
|
|
|
|
*bypassed = 0;
|
2007-08-30 01:21:12 -06:00
|
|
|
if (mp->m_flags & XFS_MOUNT_RDONLY)
|
2005-04-16 16:20:36 -06:00
|
|
|
return 0;
|
|
|
|
error = 0;
|
|
|
|
last_error = 0;
|
|
|
|
preempt = 0;
|
|
|
|
|
|
|
|
/* Allocate a reference marker */
|
|
|
|
ipointer = (xfs_iptr_t *)kmem_zalloc(sizeof(xfs_iptr_t), KM_SLEEP);
|
|
|
|
|
|
|
|
fflag = XFS_B_ASYNC; /* default is don't wait */
|
2007-09-13 23:21:08 -06:00
|
|
|
if (flags & SYNC_DELWRI)
|
2005-04-16 16:20:36 -06:00
|
|
|
fflag = XFS_B_DELWRI;
|
|
|
|
if (flags & SYNC_WAIT)
|
|
|
|
fflag = 0; /* synchronous overrides all */
|
|
|
|
|
|
|
|
base_lock_flags = XFS_ILOCK_SHARED;
|
|
|
|
if (flags & (SYNC_DELWRI | SYNC_CLOSE)) {
|
|
|
|
/*
|
|
|
|
* We need the I/O lock if we're going to call any of
|
|
|
|
* the flush/inval routines.
|
|
|
|
*/
|
|
|
|
base_lock_flags |= XFS_IOLOCK_SHARED;
|
|
|
|
}
|
|
|
|
|
|
|
|
XFS_MOUNT_ILOCK(mp);
|
|
|
|
|
|
|
|
ip = mp->m_inodes;
|
|
|
|
|
|
|
|
mount_locked = B_TRUE;
|
|
|
|
vnode_refed = B_FALSE;
|
|
|
|
|
|
|
|
IPOINTER_CLR;
|
|
|
|
|
|
|
|
do {
|
|
|
|
ASSERT(ipointer_in == B_FALSE);
|
|
|
|
ASSERT(vnode_refed == B_FALSE);
|
|
|
|
|
|
|
|
lock_flags = base_lock_flags;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* There were no inodes in the list, just break out
|
|
|
|
* of the loop.
|
|
|
|
*/
|
|
|
|
if (ip == NULL) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We found another sync thread marker - skip it
|
|
|
|
*/
|
|
|
|
if (ip->i_mount == NULL) {
|
|
|
|
ip = ip->i_mnext;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
vp = XFS_ITOV_NULL(ip);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the vnode is gone then this is being torn down,
|
|
|
|
* call reclaim if it is flushed, else let regular flush
|
|
|
|
* code deal with it later in the loop.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (vp == NULL) {
|
|
|
|
/* Skip ones already in reclaim */
|
|
|
|
if (ip->i_flags & XFS_IRECLAIM) {
|
|
|
|
ip = ip->i_mnext;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (xfs_ilock_nowait(ip, XFS_ILOCK_EXCL) == 0) {
|
|
|
|
ip = ip->i_mnext;
|
|
|
|
} else if ((xfs_ipincount(ip) == 0) &&
|
|
|
|
xfs_iflock_nowait(ip)) {
|
|
|
|
IPOINTER_INSERT(ip, mp);
|
|
|
|
|
|
|
|
xfs_finish_reclaim(ip, 1,
|
|
|
|
XFS_IFLUSH_DELWRI_ELSE_ASYNC);
|
|
|
|
|
|
|
|
XFS_MOUNT_ILOCK(mp);
|
|
|
|
mount_locked = B_TRUE;
|
|
|
|
IPOINTER_REMOVE(ip, mp);
|
|
|
|
} else {
|
|
|
|
xfs_iunlock(ip, XFS_ILOCK_EXCL);
|
|
|
|
ip = ip->i_mnext;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (VN_BAD(vp)) {
|
|
|
|
ip = ip->i_mnext;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (XFS_FORCED_SHUTDOWN(mp) && !(flags & SYNC_CLOSE)) {
|
|
|
|
XFS_MOUNT_IUNLOCK(mp);
|
|
|
|
kmem_free(ipointer, sizeof(xfs_iptr_t));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Try to lock without sleeping. We're out of order with
|
|
|
|
* the inode list lock here, so if we fail we need to drop
|
|
|
|
* the mount lock and try again. If we're called from
|
|
|
|
* bdflush() here, then don't bother.
|
|
|
|
*
|
|
|
|
* The inode lock here actually coordinates with the
|
|
|
|
* almost spurious inode lock in xfs_ireclaim() to prevent
|
|
|
|
* the vnode we handle here without a reference from
|
|
|
|
* being freed while we reference it. If we lock the inode
|
|
|
|
* while it's on the mount list here, then the spurious inode
|
|
|
|
* lock in xfs_ireclaim() after the inode is pulled from
|
|
|
|
* the mount list will sleep until we release it here.
|
|
|
|
* This keeps the vnode from being freed while we reference
|
2005-09-02 00:24:19 -06:00
|
|
|
* it.
|
2005-04-16 16:20:36 -06:00
|
|
|
*/
|
|
|
|
if (xfs_ilock_nowait(ip, lock_flags) == 0) {
|
2007-09-13 23:21:08 -06:00
|
|
|
if (vp == NULL) {
|
2005-04-16 16:20:36 -06:00
|
|
|
ip = ip->i_mnext;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2005-09-02 00:24:19 -06:00
|
|
|
vp = vn_grab(vp);
|
2005-04-16 16:20:36 -06:00
|
|
|
if (vp == NULL) {
|
2005-09-02 00:24:19 -06:00
|
|
|
ip = ip->i_mnext;
|
2005-04-16 16:20:36 -06:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2005-09-02 00:24:19 -06:00
|
|
|
IPOINTER_INSERT(ip, mp);
|
2005-04-16 16:20:36 -06:00
|
|
|
xfs_ilock(ip, lock_flags);
|
|
|
|
|
|
|
|
ASSERT(vp == XFS_ITOV(ip));
|
|
|
|
ASSERT(ip->i_mount == mp);
|
|
|
|
|
|
|
|
vnode_refed = B_TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* From here on in the loop we may have a marker record
|
|
|
|
* in the inode list.
|
|
|
|
*/
|
|
|
|
|
2007-05-14 02:24:09 -06:00
|
|
|
/*
|
|
|
|
* If we have to flush data or wait for I/O completion
|
|
|
|
* we need to drop the ilock that we currently hold.
|
|
|
|
* If we need to drop the lock, insert a marker if we
|
|
|
|
* have not already done so.
|
|
|
|
*/
|
|
|
|
if ((flags & (SYNC_CLOSE|SYNC_IOWAIT)) ||
|
|
|
|
((flags & SYNC_DELWRI) && VN_DIRTY(vp))) {
|
|
|
|
if (mount_locked) {
|
|
|
|
IPOINTER_INSERT(ip, mp);
|
2005-04-16 16:20:36 -06:00
|
|
|
}
|
2007-05-14 02:24:09 -06:00
|
|
|
xfs_iunlock(ip, XFS_ILOCK_SHARED);
|
2005-04-16 16:20:36 -06:00
|
|
|
|
2007-05-14 02:24:09 -06:00
|
|
|
if (flags & SYNC_CLOSE) {
|
|
|
|
/* Shutdown case. Flush and invalidate. */
|
|
|
|
if (XFS_FORCED_SHUTDOWN(mp))
|
2007-08-28 18:58:01 -06:00
|
|
|
xfs_tosspages(ip, 0, -1,
|
|
|
|
FI_REMAPF);
|
2007-05-14 02:24:09 -06:00
|
|
|
else
|
2007-08-28 18:58:01 -06:00
|
|
|
error = xfs_flushinval_pages(ip,
|
|
|
|
0, -1, FI_REMAPF);
|
2007-05-14 02:24:09 -06:00
|
|
|
} else if ((flags & SYNC_DELWRI) && VN_DIRTY(vp)) {
|
2007-08-28 18:58:01 -06:00
|
|
|
error = xfs_flush_pages(ip, 0,
|
2006-06-09 01:00:52 -06:00
|
|
|
-1, fflag, FI_NONE);
|
2005-04-16 16:20:36 -06:00
|
|
|
}
|
|
|
|
|
2007-05-14 02:24:09 -06:00
|
|
|
/*
|
|
|
|
* When freezing, we need to wait ensure all I/O (including direct
|
|
|
|
* I/O) is complete to ensure no further data modification can take
|
|
|
|
* place after this point
|
|
|
|
*/
|
|
|
|
if (flags & SYNC_IOWAIT)
|
2007-08-28 19:46:28 -06:00
|
|
|
vn_iowait(ip);
|
2007-05-14 02:24:09 -06:00
|
|
|
|
|
|
|
xfs_ilock(ip, XFS_ILOCK_SHARED);
|
2005-04-16 16:20:36 -06:00
|
|
|
}
|
|
|
|
|
2007-09-13 23:21:08 -06:00
|
|
|
if ((flags & SYNC_ATTR) &&
|
|
|
|
(ip->i_update_core ||
|
|
|
|
(ip->i_itemp && ip->i_itemp->ili_format.ilf_fields))) {
|
|
|
|
if (mount_locked)
|
|
|
|
IPOINTER_INSERT(ip, mp);
|
2005-04-16 16:20:36 -06:00
|
|
|
|
2007-09-13 23:21:08 -06:00
|
|
|
if (flags & SYNC_WAIT) {
|
|
|
|
xfs_iflock(ip);
|
|
|
|
error = xfs_iflush(ip, XFS_IFLUSH_SYNC);
|
2005-04-16 16:20:36 -06:00
|
|
|
|
2007-09-13 23:21:08 -06:00
|
|
|
/*
|
|
|
|
* If we can't acquire the flush lock, then the inode
|
|
|
|
* is already being flushed so don't bother waiting.
|
|
|
|
*
|
|
|
|
* If we can lock it then do a delwri flush so we can
|
|
|
|
* combine multiple inode flushes in each disk write.
|
|
|
|
*/
|
|
|
|
} else if (xfs_iflock_nowait(ip)) {
|
|
|
|
error = xfs_iflush(ip, XFS_IFLUSH_DELWRI);
|
|
|
|
} else if (bypassed) {
|
|
|
|
(*bypassed)++;
|
2005-04-16 16:20:36 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lock_flags != 0) {
|
|
|
|
xfs_iunlock(ip, lock_flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (vnode_refed) {
|
|
|
|
/*
|
|
|
|
* If we had to take a reference on the vnode
|
|
|
|
* above, then wait until after we've unlocked
|
|
|
|
* the inode to release the reference. This is
|
|
|
|
* because we can be already holding the inode
|
|
|
|
* lock when VN_RELE() calls xfs_inactive().
|
|
|
|
*
|
|
|
|
* Make sure to drop the mount lock before calling
|
|
|
|
* VN_RELE() so that we don't trip over ourselves if
|
|
|
|
* we have to go for the mount lock again in the
|
|
|
|
* inactive code.
|
|
|
|
*/
|
|
|
|
if (mount_locked) {
|
|
|
|
IPOINTER_INSERT(ip, mp);
|
|
|
|
}
|
|
|
|
|
|
|
|
VN_RELE(vp);
|
|
|
|
|
|
|
|
vnode_refed = B_FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
last_error = error;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* bail out if the filesystem is corrupted.
|
|
|
|
*/
|
|
|
|
if (error == EFSCORRUPTED) {
|
|
|
|
if (!mount_locked) {
|
|
|
|
XFS_MOUNT_ILOCK(mp);
|
|
|
|
IPOINTER_REMOVE(ip, mp);
|
|
|
|
}
|
|
|
|
XFS_MOUNT_IUNLOCK(mp);
|
|
|
|
ASSERT(ipointer_in == B_FALSE);
|
|
|
|
kmem_free(ipointer, sizeof(xfs_iptr_t));
|
|
|
|
return XFS_ERROR(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Let other threads have a chance at the mount lock
|
|
|
|
* if we have looped many times without dropping the
|
|
|
|
* lock.
|
|
|
|
*/
|
|
|
|
if ((++preempt & XFS_PREEMPT_MASK) == 0) {
|
|
|
|
if (mount_locked) {
|
|
|
|
IPOINTER_INSERT(ip, mp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mount_locked == B_FALSE) {
|
|
|
|
XFS_MOUNT_ILOCK(mp);
|
|
|
|
mount_locked = B_TRUE;
|
|
|
|
IPOINTER_REMOVE(ip, mp);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT(ipointer_in == B_FALSE);
|
|
|
|
ip = ip->i_mnext;
|
|
|
|
|
|
|
|
} while (ip != mp->m_inodes);
|
|
|
|
|
|
|
|
XFS_MOUNT_IUNLOCK(mp);
|
|
|
|
|
|
|
|
ASSERT(ipointer_in == B_FALSE);
|
|
|
|
|
|
|
|
kmem_free(ipointer, sizeof(xfs_iptr_t));
|
|
|
|
return XFS_ERROR(last_error);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* xfs sync routine for internal use
|
|
|
|
*
|
2006-06-09 00:48:30 -06:00
|
|
|
* This routine supports all of the flags defined for the generic vfs_sync
|
2007-02-10 00:35:33 -07:00
|
|
|
* interface as explained above under xfs_sync.
|
2005-04-16 16:20:36 -06:00
|
|
|
*
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
xfs_syncsub(
|
|
|
|
xfs_mount_t *mp,
|
|
|
|
int flags,
|
|
|
|
int *bypassed)
|
|
|
|
{
|
|
|
|
int error = 0;
|
|
|
|
int last_error = 0;
|
|
|
|
uint log_flags = XFS_LOG_FORCE;
|
|
|
|
xfs_buf_t *bp;
|
|
|
|
xfs_buf_log_item_t *bip;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Sync out the log. This ensures that the log is periodically
|
|
|
|
* flushed even if there is not enough activity to fill it up.
|
|
|
|
*/
|
|
|
|
if (flags & SYNC_WAIT)
|
|
|
|
log_flags |= XFS_LOG_SYNC;
|
|
|
|
|
|
|
|
xfs_log_force(mp, (xfs_lsn_t)0, log_flags);
|
|
|
|
|
|
|
|
if (flags & (SYNC_ATTR|SYNC_DELWRI)) {
|
|
|
|
if (flags & SYNC_BDFLUSH)
|
|
|
|
xfs_finish_reclaim_all(mp, 1);
|
|
|
|
else
|
2007-02-10 00:35:33 -07:00
|
|
|
error = xfs_sync_inodes(mp, flags, bypassed);
|
2005-04-16 16:20:36 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Flushing out dirty data above probably generated more
|
|
|
|
* log activity, so if this isn't vfs_sync() then flush
|
|
|
|
* the log again.
|
|
|
|
*/
|
|
|
|
if (flags & SYNC_DELWRI) {
|
|
|
|
xfs_log_force(mp, (xfs_lsn_t)0, log_flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (flags & SYNC_FSDATA) {
|
|
|
|
/*
|
|
|
|
* If this is vfs_sync() then only sync the superblock
|
|
|
|
* if we can lock it without sleeping and it is not pinned.
|
|
|
|
*/
|
|
|
|
if (flags & SYNC_BDFLUSH) {
|
|
|
|
bp = xfs_getsb(mp, XFS_BUF_TRYLOCK);
|
|
|
|
if (bp != NULL) {
|
|
|
|
bip = XFS_BUF_FSPRIVATE(bp,xfs_buf_log_item_t*);
|
|
|
|
if ((bip != NULL) &&
|
|
|
|
xfs_buf_item_dirty(bip)) {
|
|
|
|
if (!(XFS_BUF_ISPINNED(bp))) {
|
|
|
|
XFS_BUF_ASYNC(bp);
|
|
|
|
error = xfs_bwrite(mp, bp);
|
|
|
|
} else {
|
|
|
|
xfs_buf_relse(bp);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
xfs_buf_relse(bp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
bp = xfs_getsb(mp, 0);
|
|
|
|
/*
|
|
|
|
* If the buffer is pinned then push on the log so
|
|
|
|
* we won't get stuck waiting in the write for
|
|
|
|
* someone, maybe ourselves, to flush the log.
|
|
|
|
* Even though we just pushed the log above, we
|
|
|
|
* did not have the superblock buffer locked at
|
|
|
|
* that point so it can become pinned in between
|
|
|
|
* there and here.
|
|
|
|
*/
|
|
|
|
if (XFS_BUF_ISPINNED(bp))
|
|
|
|
xfs_log_force(mp, (xfs_lsn_t)0, XFS_LOG_FORCE);
|
|
|
|
if (flags & SYNC_WAIT)
|
|
|
|
XFS_BUF_UNASYNC(bp);
|
|
|
|
else
|
|
|
|
XFS_BUF_ASYNC(bp);
|
|
|
|
error = xfs_bwrite(mp, bp);
|
|
|
|
}
|
|
|
|
if (error) {
|
|
|
|
last_error = error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If this is the periodic sync, then kick some entries out of
|
|
|
|
* the reference cache. This ensures that idle entries are
|
|
|
|
* eventually kicked out of the cache.
|
|
|
|
*/
|
|
|
|
if (flags & SYNC_REFCACHE) {
|
2005-09-04 19:47:01 -06:00
|
|
|
if (flags & SYNC_WAIT)
|
|
|
|
xfs_refcache_purge_mp(mp);
|
|
|
|
else
|
|
|
|
xfs_refcache_purge_some(mp);
|
2005-04-16 16:20:36 -06:00
|
|
|
}
|
|
|
|
|
[XFS] Lazy Superblock Counters
When we have a couple of hundred transactions on the fly at once, they all
typically modify the on disk superblock in some way.
create/unclink/mkdir/rmdir modify inode counts, allocation/freeing modify
free block counts.
When these counts are modified in a transaction, they must eventually lock
the superblock buffer and apply the mods. The buffer then remains locked
until the transaction is committed into the incore log buffer. The result
of this is that with enough transactions on the fly the incore superblock
buffer becomes a bottleneck.
The result of contention on the incore superblock buffer is that
transaction rates fall - the more pressure that is put on the superblock
buffer, the slower things go.
The key to removing the contention is to not require the superblock fields
in question to be locked. We do that by not marking the superblock dirty
in the transaction. IOWs, we modify the incore superblock but do not
modify the cached superblock buffer. In short, we do not log superblock
modifications to critical fields in the superblock on every transaction.
In fact we only do it just before we write the superblock to disk every
sync period or just before unmount.
This creates an interesting problem - if we don't log or write out the
fields in every transaction, then how do the values get recovered after a
crash? the answer is simple - we keep enough duplicate, logged information
in other structures that we can reconstruct the correct count after log
recovery has been performed.
It is the AGF and AGI structures that contain the duplicate information;
after recovery, we walk every AGI and AGF and sum their individual
counters to get the correct value, and we do a transaction into the log to
correct them. An optimisation of this is that if we have a clean unmount
record, we know the value in the superblock is correct, so we can avoid
the summation walk under normal conditions and so mount/recovery times do
not change under normal operation.
One wrinkle that was discovered during development was that the blocks
used in the freespace btrees are never accounted for in the AGF counters.
This was once a valid optimisation to make; when the filesystem is full,
the free space btrees are empty and consume no space. Hence when it
matters, the "accounting" is correct. But that means the when we do the
AGF summations, we would not have a correct count and xfs_check would
complain. Hence a new counter was added to track the number of blocks used
by the free space btrees. This is an *on-disk format change*.
As a result of this, lazy superblock counters are a mkfs option and at the
moment on linux there is no way to convert an old filesystem. This is
possible - xfs_db can be used to twiddle the right bits and then
xfs_repair will do the format conversion for you. Similarly, you can
convert backwards as well. At some point we'll add functionality to
xfs_admin to do the bit twiddling easily....
SGI-PV: 964999
SGI-Modid: xfs-linux-melb:xfs-kern:28652a
Signed-off-by: David Chinner <dgc@sgi.com>
Signed-off-by: Christoph Hellwig <hch@infradead.org>
Signed-off-by: Tim Shimmin <tes@sgi.com>
2007-05-23 23:26:31 -06:00
|
|
|
/*
|
|
|
|
* If asked, update the disk superblock with incore counter values if we
|
|
|
|
* are using non-persistent counters so that they don't get too far out
|
|
|
|
* of sync if we crash or get a forced shutdown. We don't want to force
|
|
|
|
* this to disk, just get a transaction into the iclogs....
|
|
|
|
*/
|
|
|
|
if (flags & SYNC_SUPER)
|
|
|
|
xfs_log_sbcount(mp, 0);
|
|
|
|
|
2005-04-16 16:20:36 -06:00
|
|
|
/*
|
|
|
|
* Now check to see if the log needs a "dummy" transaction.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (!(flags & SYNC_REMOUNT) && xfs_log_need_covered(mp)) {
|
|
|
|
xfs_trans_t *tp;
|
|
|
|
xfs_inode_t *ip;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Put a dummy transaction in the log to tell
|
|
|
|
* recovery that all others are OK.
|
|
|
|
*/
|
|
|
|
tp = xfs_trans_alloc(mp, XFS_TRANS_DUMMY1);
|
|
|
|
if ((error = xfs_trans_reserve(tp, 0,
|
|
|
|
XFS_ICHANGE_LOG_RES(mp),
|
|
|
|
0, 0, 0))) {
|
|
|
|
xfs_trans_cancel(tp, 0);
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
ip = mp->m_rootip;
|
|
|
|
xfs_ilock(ip, XFS_ILOCK_EXCL);
|
|
|
|
|
|
|
|
xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL);
|
|
|
|
xfs_trans_ihold(tp, ip);
|
|
|
|
xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
|
2007-05-07 21:48:42 -06:00
|
|
|
error = xfs_trans_commit(tp, 0);
|
2005-04-16 16:20:36 -06:00
|
|
|
xfs_iunlock(ip, XFS_ILOCK_EXCL);
|
|
|
|
xfs_log_force(mp, (xfs_lsn_t)0, log_flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* When shutting down, we need to insure that the AIL is pushed
|
|
|
|
* to disk or the filesystem can appear corrupt from the PROM.
|
|
|
|
*/
|
|
|
|
if ((flags & (SYNC_CLOSE|SYNC_WAIT)) == (SYNC_CLOSE|SYNC_WAIT)) {
|
|
|
|
XFS_bflush(mp->m_ddev_targp);
|
|
|
|
if (mp->m_rtdev_targp) {
|
|
|
|
XFS_bflush(mp->m_rtdev_targp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return XFS_ERROR(last_error);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* xfs_vget - called by DMAPI and NFSD to get vnode from file handle
|
|
|
|
*/
|
2007-08-30 01:20:31 -06:00
|
|
|
int
|
2005-04-16 16:20:36 -06:00
|
|
|
xfs_vget(
|
2007-08-30 01:20:31 -06:00
|
|
|
xfs_mount_t *mp,
|
2006-06-09 01:00:52 -06:00
|
|
|
bhv_vnode_t **vpp,
|
2007-09-13 23:22:37 -06:00
|
|
|
xfs_fid_t *xfid)
|
2005-04-16 16:20:36 -06:00
|
|
|
{
|
|
|
|
xfs_inode_t *ip;
|
|
|
|
int error;
|
|
|
|
xfs_ino_t ino;
|
|
|
|
unsigned int igen;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Invalid. Since handles can be created in user space and passed in
|
|
|
|
* via gethandle(), this is not cause for a panic.
|
|
|
|
*/
|
2007-09-13 23:22:37 -06:00
|
|
|
if (xfid->fid_len != sizeof(*xfid) - sizeof(xfid->fid_len))
|
2005-04-16 16:20:36 -06:00
|
|
|
return XFS_ERROR(EINVAL);
|
|
|
|
|
2007-09-13 23:22:37 -06:00
|
|
|
ino = xfid->fid_ino;
|
|
|
|
igen = xfid->fid_gen;
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
/*
|
|
|
|
* NFS can sometimes send requests for ino 0. Fail them gracefully.
|
|
|
|
*/
|
|
|
|
if (ino == 0)
|
|
|
|
return XFS_ERROR(ESTALE);
|
|
|
|
|
|
|
|
error = xfs_iget(mp, NULL, ino, 0, XFS_ILOCK_SHARED, &ip, 0);
|
|
|
|
if (error) {
|
|
|
|
*vpp = NULL;
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ip == NULL) {
|
|
|
|
*vpp = NULL;
|
|
|
|
return XFS_ERROR(EIO);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ip->i_d.di_mode == 0 || ip->i_d.di_gen != igen) {
|
|
|
|
xfs_iput_new(ip, XFS_ILOCK_SHARED);
|
|
|
|
*vpp = NULL;
|
|
|
|
return XFS_ERROR(ENOENT);
|
|
|
|
}
|
|
|
|
|
|
|
|
*vpp = XFS_ITOV(ip);
|
|
|
|
xfs_iunlock(ip, XFS_ILOCK_SHARED);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#define MNTOPT_LOGBUFS "logbufs" /* number of XFS log buffers */
|
|
|
|
#define MNTOPT_LOGBSIZE "logbsize" /* size of XFS log buffers */
|
|
|
|
#define MNTOPT_LOGDEV "logdev" /* log device */
|
|
|
|
#define MNTOPT_RTDEV "rtdev" /* realtime I/O device */
|
|
|
|
#define MNTOPT_BIOSIZE "biosize" /* log2 of preferred buffered io size */
|
|
|
|
#define MNTOPT_WSYNC "wsync" /* safe-mode nfs compatible mount */
|
|
|
|
#define MNTOPT_INO64 "ino64" /* force inodes into 64-bit range */
|
|
|
|
#define MNTOPT_NOALIGN "noalign" /* turn off stripe alignment */
|
|
|
|
#define MNTOPT_SWALLOC "swalloc" /* turn on stripe width allocation */
|
|
|
|
#define MNTOPT_SUNIT "sunit" /* data volume stripe unit */
|
|
|
|
#define MNTOPT_SWIDTH "swidth" /* data volume stripe width */
|
|
|
|
#define MNTOPT_NOUUID "nouuid" /* ignore filesystem UUID */
|
|
|
|
#define MNTOPT_MTPT "mtpt" /* filesystem mount point */
|
2005-09-02 00:42:26 -06:00
|
|
|
#define MNTOPT_GRPID "grpid" /* group-ID from parent directory */
|
|
|
|
#define MNTOPT_NOGRPID "nogrpid" /* group-ID from current process */
|
|
|
|
#define MNTOPT_BSDGROUPS "bsdgroups" /* group-ID from parent directory */
|
|
|
|
#define MNTOPT_SYSVGROUPS "sysvgroups" /* group-ID from current process */
|
2005-05-05 14:28:29 -06:00
|
|
|
#define MNTOPT_ALLOCSIZE "allocsize" /* preferred allocation size */
|
2005-04-16 16:20:36 -06:00
|
|
|
#define MNTOPT_NORECOVERY "norecovery" /* don't run XFS recovery */
|
2005-11-01 16:26:59 -07:00
|
|
|
#define MNTOPT_BARRIER "barrier" /* use writer barriers for log write and
|
2005-11-01 16:34:53 -07:00
|
|
|
* unwritten extent conversion */
|
2006-01-10 21:27:18 -07:00
|
|
|
#define MNTOPT_NOBARRIER "nobarrier" /* .. disable */
|
2005-04-16 16:20:36 -06:00
|
|
|
#define MNTOPT_OSYNCISOSYNC "osyncisosync" /* o_sync is REALLY o_sync */
|
|
|
|
#define MNTOPT_64BITINODE "inode64" /* inodes can be allocated anywhere */
|
|
|
|
#define MNTOPT_IKEEP "ikeep" /* do not free empty inode clusters */
|
|
|
|
#define MNTOPT_NOIKEEP "noikeep" /* free empty inode clusters */
|
2005-11-01 16:33:05 -07:00
|
|
|
#define MNTOPT_LARGEIO "largeio" /* report large I/O sizes in stat() */
|
|
|
|
#define MNTOPT_NOLARGEIO "nolargeio" /* do not report large I/O sizes
|
|
|
|
* in stat(). */
|
2005-11-01 16:34:53 -07:00
|
|
|
#define MNTOPT_ATTR2 "attr2" /* do use attr2 attribute format */
|
|
|
|
#define MNTOPT_NOATTR2 "noattr2" /* do not use attr2 attribute format */
|
2007-07-10 19:09:12 -06:00
|
|
|
#define MNTOPT_FILESTREAM "filestreams" /* use filestreams allocator */
|
2007-08-30 01:19:57 -06:00
|
|
|
#define MNTOPT_QUOTA "quota" /* disk quotas (user) */
|
|
|
|
#define MNTOPT_NOQUOTA "noquota" /* no quotas */
|
|
|
|
#define MNTOPT_USRQUOTA "usrquota" /* user quota enabled */
|
|
|
|
#define MNTOPT_GRPQUOTA "grpquota" /* group quota enabled */
|
|
|
|
#define MNTOPT_PRJQUOTA "prjquota" /* project quota enabled */
|
|
|
|
#define MNTOPT_UQUOTA "uquota" /* user quota (IRIX variant) */
|
|
|
|
#define MNTOPT_GQUOTA "gquota" /* group quota (IRIX variant) */
|
|
|
|
#define MNTOPT_PQUOTA "pquota" /* project quota (IRIX variant) */
|
|
|
|
#define MNTOPT_UQUOTANOENF "uqnoenforce"/* user quota limit enforcement */
|
|
|
|
#define MNTOPT_GQUOTANOENF "gqnoenforce"/* group quota limit enforcement */
|
|
|
|
#define MNTOPT_PQUOTANOENF "pqnoenforce"/* project quota limit enforcement */
|
|
|
|
#define MNTOPT_QUOTANOENF "qnoenforce" /* same as uqnoenforce */
|
2007-08-28 19:59:36 -06:00
|
|
|
#define MNTOPT_DMAPI "dmapi" /* DMI enabled (DMAPI / XDSM) */
|
|
|
|
#define MNTOPT_XDSM "xdsm" /* DMI enabled (DMAPI / XDSM) */
|
|
|
|
#define MNTOPT_DMI "dmi" /* DMI enabled (DMAPI / XDSM) */
|
2005-04-16 16:20:36 -06:00
|
|
|
|
2005-05-05 14:28:29 -06:00
|
|
|
STATIC unsigned long
|
2006-06-09 01:13:15 -06:00
|
|
|
suffix_strtoul(char *s, char **endp, unsigned int base)
|
2005-05-05 14:28:29 -06:00
|
|
|
{
|
|
|
|
int last, shift_left_factor = 0;
|
2006-06-09 01:13:15 -06:00
|
|
|
char *value = s;
|
2005-05-05 14:28:29 -06:00
|
|
|
|
|
|
|
last = strlen(value) - 1;
|
|
|
|
if (value[last] == 'K' || value[last] == 'k') {
|
|
|
|
shift_left_factor = 10;
|
|
|
|
value[last] = '\0';
|
|
|
|
}
|
|
|
|
if (value[last] == 'M' || value[last] == 'm') {
|
|
|
|
shift_left_factor = 20;
|
|
|
|
value[last] = '\0';
|
|
|
|
}
|
|
|
|
if (value[last] == 'G' || value[last] == 'g') {
|
|
|
|
shift_left_factor = 30;
|
|
|
|
value[last] = '\0';
|
|
|
|
}
|
|
|
|
|
2006-06-09 01:13:15 -06:00
|
|
|
return simple_strtoul((const char *)s, endp, base) << shift_left_factor;
|
2005-05-05 14:28:29 -06:00
|
|
|
}
|
2005-04-16 16:20:36 -06:00
|
|
|
|
2007-08-30 01:20:31 -06:00
|
|
|
int
|
2005-04-16 16:20:36 -06:00
|
|
|
xfs_parseargs(
|
2007-08-30 01:20:31 -06:00
|
|
|
struct xfs_mount *mp,
|
2005-04-16 16:20:36 -06:00
|
|
|
char *options,
|
|
|
|
struct xfs_mount_args *args,
|
|
|
|
int update)
|
|
|
|
{
|
|
|
|
char *this_char, *value, *eov;
|
|
|
|
int dsunit, dswidth, vol_dsunit, vol_dswidth;
|
|
|
|
int iosize;
|
2007-09-13 23:23:44 -06:00
|
|
|
int ikeep = 0;
|
2007-08-27 22:00:28 -06:00
|
|
|
|
2006-03-30 20:04:56 -07:00
|
|
|
args->flags |= XFSMNT_BARRIER;
|
|
|
|
args->flags2 |= XFSMNT2_COMPAT_IOSIZE;
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
if (!options)
|
2005-11-03 15:49:07 -07:00
|
|
|
goto done;
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
iosize = dsunit = dswidth = vol_dsunit = vol_dswidth = 0;
|
|
|
|
|
|
|
|
while ((this_char = strsep(&options, ",")) != NULL) {
|
|
|
|
if (!*this_char)
|
|
|
|
continue;
|
|
|
|
if ((value = strchr(this_char, '=')) != NULL)
|
|
|
|
*value++ = 0;
|
|
|
|
|
|
|
|
if (!strcmp(this_char, MNTOPT_LOGBUFS)) {
|
|
|
|
if (!value || !*value) {
|
2006-06-08 23:29:40 -06:00
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: %s option requires an argument",
|
2005-05-05 14:28:29 -06:00
|
|
|
this_char);
|
2005-04-16 16:20:36 -06:00
|
|
|
return EINVAL;
|
|
|
|
}
|
|
|
|
args->logbufs = simple_strtoul(value, &eov, 10);
|
|
|
|
} else if (!strcmp(this_char, MNTOPT_LOGBSIZE)) {
|
|
|
|
if (!value || !*value) {
|
2006-06-08 23:29:40 -06:00
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: %s option requires an argument",
|
2005-05-05 14:28:29 -06:00
|
|
|
this_char);
|
2005-04-16 16:20:36 -06:00
|
|
|
return EINVAL;
|
|
|
|
}
|
2005-05-05 14:28:29 -06:00
|
|
|
args->logbufsize = suffix_strtoul(value, &eov, 10);
|
2005-04-16 16:20:36 -06:00
|
|
|
} else if (!strcmp(this_char, MNTOPT_LOGDEV)) {
|
|
|
|
if (!value || !*value) {
|
2006-06-08 23:29:40 -06:00
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: %s option requires an argument",
|
2005-05-05 14:28:29 -06:00
|
|
|
this_char);
|
2005-04-16 16:20:36 -06:00
|
|
|
return EINVAL;
|
|
|
|
}
|
|
|
|
strncpy(args->logname, value, MAXNAMELEN);
|
|
|
|
} else if (!strcmp(this_char, MNTOPT_MTPT)) {
|
|
|
|
if (!value || !*value) {
|
2006-06-08 23:29:40 -06:00
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: %s option requires an argument",
|
2005-05-05 14:28:29 -06:00
|
|
|
this_char);
|
2005-04-16 16:20:36 -06:00
|
|
|
return EINVAL;
|
|
|
|
}
|
|
|
|
strncpy(args->mtpt, value, MAXNAMELEN);
|
|
|
|
} else if (!strcmp(this_char, MNTOPT_RTDEV)) {
|
|
|
|
if (!value || !*value) {
|
2006-06-08 23:29:40 -06:00
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: %s option requires an argument",
|
2005-05-05 14:28:29 -06:00
|
|
|
this_char);
|
2005-04-16 16:20:36 -06:00
|
|
|
return EINVAL;
|
|
|
|
}
|
|
|
|
strncpy(args->rtname, value, MAXNAMELEN);
|
|
|
|
} else if (!strcmp(this_char, MNTOPT_BIOSIZE)) {
|
|
|
|
if (!value || !*value) {
|
2006-06-08 23:29:40 -06:00
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: %s option requires an argument",
|
2005-05-05 14:28:29 -06:00
|
|
|
this_char);
|
2005-04-16 16:20:36 -06:00
|
|
|
return EINVAL;
|
|
|
|
}
|
|
|
|
iosize = simple_strtoul(value, &eov, 10);
|
|
|
|
args->flags |= XFSMNT_IOSIZE;
|
|
|
|
args->iosizelog = (uint8_t) iosize;
|
2005-05-05 14:28:29 -06:00
|
|
|
} else if (!strcmp(this_char, MNTOPT_ALLOCSIZE)) {
|
|
|
|
if (!value || !*value) {
|
2006-06-08 23:29:40 -06:00
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: %s option requires an argument",
|
2005-05-05 14:28:29 -06:00
|
|
|
this_char);
|
|
|
|
return EINVAL;
|
|
|
|
}
|
|
|
|
iosize = suffix_strtoul(value, &eov, 10);
|
|
|
|
args->flags |= XFSMNT_IOSIZE;
|
|
|
|
args->iosizelog = ffs(iosize) - 1;
|
2005-09-02 00:42:26 -06:00
|
|
|
} else if (!strcmp(this_char, MNTOPT_GRPID) ||
|
|
|
|
!strcmp(this_char, MNTOPT_BSDGROUPS)) {
|
2007-08-30 01:21:12 -06:00
|
|
|
mp->m_flags |= XFS_MOUNT_GRPID;
|
2005-09-02 00:42:26 -06:00
|
|
|
} else if (!strcmp(this_char, MNTOPT_NOGRPID) ||
|
|
|
|
!strcmp(this_char, MNTOPT_SYSVGROUPS)) {
|
2007-08-30 01:21:12 -06:00
|
|
|
mp->m_flags &= ~XFS_MOUNT_GRPID;
|
2005-04-16 16:20:36 -06:00
|
|
|
} else if (!strcmp(this_char, MNTOPT_WSYNC)) {
|
|
|
|
args->flags |= XFSMNT_WSYNC;
|
|
|
|
} else if (!strcmp(this_char, MNTOPT_OSYNCISOSYNC)) {
|
|
|
|
args->flags |= XFSMNT_OSYNCISOSYNC;
|
|
|
|
} else if (!strcmp(this_char, MNTOPT_NORECOVERY)) {
|
|
|
|
args->flags |= XFSMNT_NORECOVERY;
|
|
|
|
} else if (!strcmp(this_char, MNTOPT_INO64)) {
|
|
|
|
args->flags |= XFSMNT_INO64;
|
|
|
|
#if !XFS_BIG_INUMS
|
2006-06-08 23:29:40 -06:00
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: %s option not allowed on this system",
|
2005-05-05 14:28:29 -06:00
|
|
|
this_char);
|
2005-04-16 16:20:36 -06:00
|
|
|
return EINVAL;
|
|
|
|
#endif
|
|
|
|
} else if (!strcmp(this_char, MNTOPT_NOALIGN)) {
|
|
|
|
args->flags |= XFSMNT_NOALIGN;
|
|
|
|
} else if (!strcmp(this_char, MNTOPT_SWALLOC)) {
|
|
|
|
args->flags |= XFSMNT_SWALLOC;
|
|
|
|
} else if (!strcmp(this_char, MNTOPT_SUNIT)) {
|
|
|
|
if (!value || !*value) {
|
2006-06-08 23:29:40 -06:00
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: %s option requires an argument",
|
2005-05-05 14:28:29 -06:00
|
|
|
this_char);
|
2005-04-16 16:20:36 -06:00
|
|
|
return EINVAL;
|
|
|
|
}
|
|
|
|
dsunit = simple_strtoul(value, &eov, 10);
|
|
|
|
} else if (!strcmp(this_char, MNTOPT_SWIDTH)) {
|
|
|
|
if (!value || !*value) {
|
2006-06-08 23:29:40 -06:00
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: %s option requires an argument",
|
2005-05-05 14:28:29 -06:00
|
|
|
this_char);
|
2005-04-16 16:20:36 -06:00
|
|
|
return EINVAL;
|
|
|
|
}
|
|
|
|
dswidth = simple_strtoul(value, &eov, 10);
|
|
|
|
} else if (!strcmp(this_char, MNTOPT_64BITINODE)) {
|
|
|
|
args->flags &= ~XFSMNT_32BITINODES;
|
|
|
|
#if !XFS_BIG_INUMS
|
2006-06-08 23:29:40 -06:00
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: %s option not allowed on this system",
|
2005-05-05 14:28:29 -06:00
|
|
|
this_char);
|
2005-04-16 16:20:36 -06:00
|
|
|
return EINVAL;
|
|
|
|
#endif
|
|
|
|
} else if (!strcmp(this_char, MNTOPT_NOUUID)) {
|
|
|
|
args->flags |= XFSMNT_NOUUID;
|
2005-11-01 16:26:59 -07:00
|
|
|
} else if (!strcmp(this_char, MNTOPT_BARRIER)) {
|
|
|
|
args->flags |= XFSMNT_BARRIER;
|
2006-01-10 21:27:18 -07:00
|
|
|
} else if (!strcmp(this_char, MNTOPT_NOBARRIER)) {
|
|
|
|
args->flags &= ~XFSMNT_BARRIER;
|
2005-04-16 16:20:36 -06:00
|
|
|
} else if (!strcmp(this_char, MNTOPT_IKEEP)) {
|
2007-09-13 23:23:44 -06:00
|
|
|
ikeep = 1;
|
2005-04-16 16:20:36 -06:00
|
|
|
args->flags &= ~XFSMNT_IDELETE;
|
|
|
|
} else if (!strcmp(this_char, MNTOPT_NOIKEEP)) {
|
|
|
|
args->flags |= XFSMNT_IDELETE;
|
2005-11-01 16:33:05 -07:00
|
|
|
} else if (!strcmp(this_char, MNTOPT_LARGEIO)) {
|
2005-11-01 21:09:22 -07:00
|
|
|
args->flags2 &= ~XFSMNT2_COMPAT_IOSIZE;
|
2005-11-01 16:33:05 -07:00
|
|
|
} else if (!strcmp(this_char, MNTOPT_NOLARGEIO)) {
|
2005-11-01 21:09:22 -07:00
|
|
|
args->flags2 |= XFSMNT2_COMPAT_IOSIZE;
|
2005-11-01 16:34:53 -07:00
|
|
|
} else if (!strcmp(this_char, MNTOPT_ATTR2)) {
|
2006-01-10 21:32:01 -07:00
|
|
|
args->flags |= XFSMNT_ATTR2;
|
2005-11-01 16:34:53 -07:00
|
|
|
} else if (!strcmp(this_char, MNTOPT_NOATTR2)) {
|
2006-01-10 21:32:01 -07:00
|
|
|
args->flags &= ~XFSMNT_ATTR2;
|
2007-07-10 19:09:12 -06:00
|
|
|
} else if (!strcmp(this_char, MNTOPT_FILESTREAM)) {
|
|
|
|
args->flags2 |= XFSMNT2_FILESTREAMS;
|
2007-08-30 01:19:57 -06:00
|
|
|
} else if (!strcmp(this_char, MNTOPT_NOQUOTA)) {
|
|
|
|
args->flags &= ~(XFSMNT_UQUOTAENF|XFSMNT_UQUOTA);
|
|
|
|
args->flags &= ~(XFSMNT_GQUOTAENF|XFSMNT_GQUOTA);
|
|
|
|
} else if (!strcmp(this_char, MNTOPT_QUOTA) ||
|
|
|
|
!strcmp(this_char, MNTOPT_UQUOTA) ||
|
|
|
|
!strcmp(this_char, MNTOPT_USRQUOTA)) {
|
|
|
|
args->flags |= XFSMNT_UQUOTA | XFSMNT_UQUOTAENF;
|
|
|
|
} else if (!strcmp(this_char, MNTOPT_QUOTANOENF) ||
|
|
|
|
!strcmp(this_char, MNTOPT_UQUOTANOENF)) {
|
|
|
|
args->flags |= XFSMNT_UQUOTA;
|
|
|
|
args->flags &= ~XFSMNT_UQUOTAENF;
|
|
|
|
} else if (!strcmp(this_char, MNTOPT_PQUOTA) ||
|
|
|
|
!strcmp(this_char, MNTOPT_PRJQUOTA)) {
|
|
|
|
args->flags |= XFSMNT_PQUOTA | XFSMNT_PQUOTAENF;
|
|
|
|
} else if (!strcmp(this_char, MNTOPT_PQUOTANOENF)) {
|
|
|
|
args->flags |= XFSMNT_PQUOTA;
|
|
|
|
args->flags &= ~XFSMNT_PQUOTAENF;
|
|
|
|
} else if (!strcmp(this_char, MNTOPT_GQUOTA) ||
|
|
|
|
!strcmp(this_char, MNTOPT_GRPQUOTA)) {
|
|
|
|
args->flags |= XFSMNT_GQUOTA | XFSMNT_GQUOTAENF;
|
|
|
|
} else if (!strcmp(this_char, MNTOPT_GQUOTANOENF)) {
|
|
|
|
args->flags |= XFSMNT_GQUOTA;
|
|
|
|
args->flags &= ~XFSMNT_GQUOTAENF;
|
2007-08-28 19:59:36 -06:00
|
|
|
} else if (!strcmp(this_char, MNTOPT_DMAPI)) {
|
|
|
|
args->flags |= XFSMNT_DMAPI;
|
|
|
|
} else if (!strcmp(this_char, MNTOPT_XDSM)) {
|
|
|
|
args->flags |= XFSMNT_DMAPI;
|
|
|
|
} else if (!strcmp(this_char, MNTOPT_DMI)) {
|
|
|
|
args->flags |= XFSMNT_DMAPI;
|
2007-08-27 22:00:13 -06:00
|
|
|
} else if (!strcmp(this_char, "ihashsize")) {
|
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: ihashsize no longer used, option is deprecated.");
|
2005-04-16 16:20:36 -06:00
|
|
|
} else if (!strcmp(this_char, "osyncisdsync")) {
|
|
|
|
/* no-op, this is now the default */
|
2006-06-08 23:29:40 -06:00
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: osyncisdsync is now the default, option is deprecated.");
|
2005-04-16 16:20:36 -06:00
|
|
|
} else if (!strcmp(this_char, "irixsgid")) {
|
2006-06-08 23:29:40 -06:00
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: irixsgid is now a sysctl(2) variable, option is deprecated.");
|
2005-04-16 16:20:36 -06:00
|
|
|
} else {
|
2006-06-08 23:29:40 -06:00
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: unknown mount option [%s].", this_char);
|
2005-04-16 16:20:36 -06:00
|
|
|
return EINVAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (args->flags & XFSMNT_NORECOVERY) {
|
2007-08-30 01:21:12 -06:00
|
|
|
if ((mp->m_flags & XFS_MOUNT_RDONLY) == 0) {
|
2006-06-08 23:29:40 -06:00
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: no-recovery mounts must be read-only.");
|
2005-04-16 16:20:36 -06:00
|
|
|
return EINVAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((args->flags & XFSMNT_NOALIGN) && (dsunit || dswidth)) {
|
2006-06-08 23:29:40 -06:00
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: sunit and swidth options incompatible with the noalign option");
|
2005-04-16 16:20:36 -06:00
|
|
|
return EINVAL;
|
|
|
|
}
|
|
|
|
|
2007-08-30 01:19:57 -06:00
|
|
|
if ((args->flags & XFSMNT_GQUOTA) && (args->flags & XFSMNT_PQUOTA)) {
|
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: cannot mount with both project and group quota");
|
|
|
|
return EINVAL;
|
|
|
|
}
|
|
|
|
|
2007-08-28 19:59:36 -06:00
|
|
|
if ((args->flags & XFSMNT_DMAPI) && *args->mtpt == '\0') {
|
|
|
|
printk("XFS: %s option needs the mount point option as well\n",
|
|
|
|
MNTOPT_DMAPI);
|
|
|
|
return EINVAL;
|
|
|
|
}
|
|
|
|
|
2005-04-16 16:20:36 -06:00
|
|
|
if ((dsunit && !dswidth) || (!dsunit && dswidth)) {
|
2006-06-08 23:29:40 -06:00
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: sunit and swidth must be specified together");
|
2005-04-16 16:20:36 -06:00
|
|
|
return EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dsunit && (dswidth % dsunit != 0)) {
|
2006-06-08 23:29:40 -06:00
|
|
|
cmn_err(CE_WARN,
|
|
|
|
"XFS: stripe width (%d) must be a multiple of the stripe unit (%d)",
|
2005-04-16 16:20:36 -06:00
|
|
|
dswidth, dsunit);
|
|
|
|
return EINVAL;
|
|
|
|
}
|
|
|
|
|
2007-09-13 23:23:44 -06:00
|
|
|
/*
|
|
|
|
* Applications using DMI filesystems often expect the
|
|
|
|
* inode generation number to be monotonically increasing.
|
|
|
|
* If we delete inode chunks we break this assumption, so
|
|
|
|
* keep unused inode chunks on disk for DMI filesystems
|
|
|
|
* until we come up with a better solution.
|
|
|
|
* Note that if "ikeep" or "noikeep" mount options are
|
|
|
|
* supplied, then they are honored.
|
|
|
|
*/
|
|
|
|
if (!(args->flags & XFSMNT_DMAPI) && !ikeep)
|
|
|
|
args->flags |= XFSMNT_IDELETE;
|
|
|
|
|
2005-04-16 16:20:36 -06:00
|
|
|
if ((args->flags & XFSMNT_NOALIGN) != XFSMNT_NOALIGN) {
|
|
|
|
if (dsunit) {
|
|
|
|
args->sunit = dsunit;
|
|
|
|
args->flags |= XFSMNT_RETERR;
|
|
|
|
} else {
|
|
|
|
args->sunit = vol_dsunit;
|
|
|
|
}
|
|
|
|
dswidth ? (args->swidth = dswidth) :
|
|
|
|
(args->swidth = vol_dswidth);
|
|
|
|
} else {
|
|
|
|
args->sunit = args->swidth = 0;
|
|
|
|
}
|
|
|
|
|
2005-11-03 15:49:07 -07:00
|
|
|
done:
|
2005-11-01 21:11:45 -07:00
|
|
|
if (args->flags & XFSMNT_32BITINODES)
|
2007-08-30 01:21:12 -06:00
|
|
|
mp->m_flags |= XFS_MOUNT_SMALL_INUMS;
|
2005-11-01 21:09:22 -07:00
|
|
|
if (args->flags2)
|
|
|
|
args->flags |= XFSMNT_FLAGS2;
|
2005-04-16 16:20:36 -06:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-08-30 01:20:31 -06:00
|
|
|
int
|
2005-04-16 16:20:36 -06:00
|
|
|
xfs_showargs(
|
2007-08-30 01:20:31 -06:00
|
|
|
struct xfs_mount *mp,
|
2005-04-16 16:20:36 -06:00
|
|
|
struct seq_file *m)
|
|
|
|
{
|
|
|
|
static struct proc_xfs_info {
|
|
|
|
int flag;
|
|
|
|
char *str;
|
|
|
|
} xfs_info[] = {
|
|
|
|
/* the few simple ones we can get from the mount struct */
|
|
|
|
{ XFS_MOUNT_WSYNC, "," MNTOPT_WSYNC },
|
|
|
|
{ XFS_MOUNT_INO64, "," MNTOPT_INO64 },
|
|
|
|
{ XFS_MOUNT_NOALIGN, "," MNTOPT_NOALIGN },
|
|
|
|
{ XFS_MOUNT_SWALLOC, "," MNTOPT_SWALLOC },
|
|
|
|
{ XFS_MOUNT_NOUUID, "," MNTOPT_NOUUID },
|
|
|
|
{ XFS_MOUNT_NORECOVERY, "," MNTOPT_NORECOVERY },
|
|
|
|
{ XFS_MOUNT_OSYNCISOSYNC, "," MNTOPT_OSYNCISOSYNC },
|
|
|
|
{ 0, NULL }
|
|
|
|
};
|
|
|
|
struct proc_xfs_info *xfs_infop;
|
|
|
|
|
|
|
|
for (xfs_infop = xfs_info; xfs_infop->flag; xfs_infop++) {
|
|
|
|
if (mp->m_flags & xfs_infop->flag)
|
|
|
|
seq_puts(m, xfs_infop->str);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mp->m_flags & XFS_MOUNT_DFLT_IOSIZE)
|
2005-11-01 21:12:04 -07:00
|
|
|
seq_printf(m, "," MNTOPT_ALLOCSIZE "=%dk",
|
|
|
|
(int)(1 << mp->m_writeio_log) >> 10);
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
if (mp->m_logbufs > 0)
|
|
|
|
seq_printf(m, "," MNTOPT_LOGBUFS "=%d", mp->m_logbufs);
|
|
|
|
if (mp->m_logbsize > 0)
|
2005-11-01 21:12:04 -07:00
|
|
|
seq_printf(m, "," MNTOPT_LOGBSIZE "=%dk", mp->m_logbsize >> 10);
|
2005-04-16 16:20:36 -06:00
|
|
|
|
2005-11-01 17:44:33 -07:00
|
|
|
if (mp->m_logname)
|
|
|
|
seq_printf(m, "," MNTOPT_LOGDEV "=%s", mp->m_logname);
|
|
|
|
if (mp->m_rtname)
|
|
|
|
seq_printf(m, "," MNTOPT_RTDEV "=%s", mp->m_rtname);
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
if (mp->m_dalign > 0)
|
|
|
|
seq_printf(m, "," MNTOPT_SUNIT "=%d",
|
|
|
|
(int)XFS_FSB_TO_BB(mp, mp->m_dalign));
|
|
|
|
if (mp->m_swidth > 0)
|
|
|
|
seq_printf(m, "," MNTOPT_SWIDTH "=%d",
|
|
|
|
(int)XFS_FSB_TO_BB(mp, mp->m_swidth));
|
|
|
|
|
2006-03-21 18:47:52 -07:00
|
|
|
if (!(mp->m_flags & XFS_MOUNT_IDELETE))
|
|
|
|
seq_printf(m, "," MNTOPT_IKEEP);
|
2005-11-01 21:00:48 -07:00
|
|
|
if (!(mp->m_flags & XFS_MOUNT_COMPAT_IOSIZE))
|
|
|
|
seq_printf(m, "," MNTOPT_LARGEIO);
|
|
|
|
|
2007-08-30 01:21:12 -06:00
|
|
|
if (!(mp->m_flags & XFS_MOUNT_SMALL_INUMS))
|
2005-11-01 21:11:45 -07:00
|
|
|
seq_printf(m, "," MNTOPT_64BITINODE);
|
2007-08-30 01:21:12 -06:00
|
|
|
if (mp->m_flags & XFS_MOUNT_GRPID)
|
2005-09-02 00:42:26 -06:00
|
|
|
seq_printf(m, "," MNTOPT_GRPID);
|
|
|
|
|
2007-08-30 01:19:57 -06:00
|
|
|
if (mp->m_qflags & XFS_UQUOTA_ACCT) {
|
|
|
|
if (mp->m_qflags & XFS_UQUOTA_ENFD)
|
|
|
|
seq_puts(m, "," MNTOPT_USRQUOTA);
|
|
|
|
else
|
|
|
|
seq_puts(m, "," MNTOPT_UQUOTANOENF);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mp->m_qflags & XFS_PQUOTA_ACCT) {
|
|
|
|
if (mp->m_qflags & XFS_OQUOTA_ENFD)
|
|
|
|
seq_puts(m, "," MNTOPT_PRJQUOTA);
|
|
|
|
else
|
|
|
|
seq_puts(m, "," MNTOPT_PQUOTANOENF);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mp->m_qflags & XFS_GQUOTA_ACCT) {
|
|
|
|
if (mp->m_qflags & XFS_OQUOTA_ENFD)
|
|
|
|
seq_puts(m, "," MNTOPT_GRPQUOTA);
|
|
|
|
else
|
|
|
|
seq_puts(m, "," MNTOPT_GQUOTANOENF);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(mp->m_qflags & XFS_ALL_QUOTA_ACCT))
|
|
|
|
seq_puts(m, "," MNTOPT_NOQUOTA);
|
|
|
|
|
2007-08-30 01:21:12 -06:00
|
|
|
if (mp->m_flags & XFS_MOUNT_DMAPI)
|
2007-08-28 19:59:36 -06:00
|
|
|
seq_puts(m, "," MNTOPT_DMAPI);
|
2005-04-16 16:20:36 -06:00
|
|
|
return 0;
|
|
|
|
}
|