2005-04-17 02:20:36 +04:00
/*
2005-11-02 06:58:39 +03:00
* Copyright ( c ) 2000 - 2002 , 2005 Silicon Graphics , Inc .
* All Rights Reserved .
2005-04-17 02:20:36 +04:00
*
2005-11-02 06:58:39 +03: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-17 02:20:36 +04:00
* published by the Free Software Foundation .
*
2005-11-02 06:58:39 +03: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-17 02:20:36 +04:00
*
2005-11-02 06:58:39 +03: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-17 02:20:36 +04:00
*/
# include "xfs.h"
2005-11-02 06:38:42 +03:00
# include "xfs_fs.h"
2005-04-17 02:20:36 +04:00
# include "xfs_types.h"
2005-11-02 06:38:42 +03:00
# include "xfs_bit.h"
2005-04-17 02:20:36 +04:00
# include "xfs_log.h"
2005-11-02 06:38:42 +03:00
# include "xfs_inum.h"
2005-04-17 02:20:36 +04:00
# include "xfs_trans.h"
# include "xfs_sb.h"
2005-11-02 06:38:42 +03:00
# include "xfs_ag.h"
2005-04-17 02:20:36 +04:00
# include "xfs_dir2.h"
# include "xfs_dmapi.h"
# include "xfs_mount.h"
# include "xfs_bmap_btree.h"
# include "xfs_dir2_sf.h"
2005-11-02 06:38:42 +03:00
# include "xfs_attr_sf.h"
2005-04-17 02:20:36 +04:00
# include "xfs_dinode.h"
# include "xfs_inode.h"
2005-11-02 06:38:42 +03:00
# include "xfs_inode_item.h"
2005-04-17 02:20:36 +04:00
# include "xfs_bmap.h"
# include "xfs_error.h"
# include "xfs_quota.h"
# include "xfs_rw.h"
# include "xfs_itable.h"
# include "xfs_utils.h"
/*
* Allocates a new inode from disk and return a pointer to the
* incore copy . This routine will internally commit the current
* transaction and allocate a new one if the Space Manager needed
* to do an allocation to replenish the inode free - list .
*
* This routine is designed to be called from xfs_create and
* xfs_create_dir .
*
*/
int
xfs_dir_ialloc (
xfs_trans_t * * tpp , /* input: current transaction;
output : may be a new transaction . */
xfs_inode_t * dp , /* directory within whose allocate
the inode . */
mode_t mode ,
2005-05-06 00:25:00 +04:00
xfs_nlink_t nlink ,
2005-04-17 02:20:36 +04:00
xfs_dev_t rdev ,
cred_t * credp ,
prid_t prid , /* project id */
int okalloc , /* ok to allocate new space */
xfs_inode_t * * ipp , /* pointer to inode; it will be
locked . */
int * committed )
{
xfs_trans_t * tp ;
xfs_trans_t * ntp ;
xfs_inode_t * ip ;
xfs_buf_t * ialloc_context = NULL ;
boolean_t call_again = B_FALSE ;
int code ;
uint log_res ;
uint log_count ;
void * dqinfo ;
uint tflags ;
tp = * tpp ;
ASSERT ( tp - > t_flags & XFS_TRANS_PERM_LOG_RES ) ;
/*
* xfs_ialloc will return a pointer to an incore inode if
* the Space Manager has an available inode on the free
* list . Otherwise , it will do an allocation and replenish
* the freelist . Since we can only do one allocation per
* transaction without deadlocks , we will need to commit the
* current transaction and start a new one . We will then
* need to call xfs_ialloc again to get the inode .
*
* If xfs_ialloc did an allocation to replenish the freelist ,
* it returns the bp containing the head of the freelist as
* ialloc_context . We will hold a lock on it across the
* transaction commit so that no other process can steal
* the inode ( s ) that we ' ve just allocated .
*/
code = xfs_ialloc ( tp , dp , mode , nlink , rdev , credp , prid , okalloc ,
& ialloc_context , & call_again , & ip ) ;
/*
* Return an error if we were unable to allocate a new inode .
* This should only happen if we run out of space on disk or
* encounter a disk error .
*/
if ( code ) {
* ipp = NULL ;
return code ;
}
if ( ! call_again & & ( ip = = NULL ) ) {
* ipp = NULL ;
return XFS_ERROR ( ENOSPC ) ;
}
/*
* If call_again is set , then we were unable to get an
* inode in one operation . We need to commit the current
* transaction and call xfs_ialloc ( ) again . It is guaranteed
* to succeed the second time .
*/
if ( call_again ) {
/*
* Normally , xfs_trans_commit releases all the locks .
* We call bhold to hang on to the ialloc_context across
* the commit . Holding this buffer prevents any other
* processes from doing any allocations in this
* allocation group .
*/
xfs_trans_bhold ( tp , ialloc_context ) ;
/*
* Save the log reservation so we can use
* them in the next transaction .
*/
log_res = xfs_trans_get_log_res ( tp ) ;
log_count = xfs_trans_get_log_count ( tp ) ;
/*
* We want the quota changes to be associated with the next
* transaction , NOT this one . So , detach the dqinfo from this
* and attach it to the next transaction .
*/
dqinfo = NULL ;
tflags = 0 ;
if ( tp - > t_dqinfo ) {
dqinfo = ( void * ) tp - > t_dqinfo ;
tp - > t_dqinfo = NULL ;
tflags = tp - > t_flags & XFS_TRANS_DQ_DIRTY ;
tp - > t_flags & = ~ ( XFS_TRANS_DQ_DIRTY ) ;
}
ntp = xfs_trans_dup ( tp ) ;
2007-05-08 07:48:42 +04:00
code = xfs_trans_commit ( tp , 0 ) ;
2005-04-17 02:20:36 +04:00
tp = ntp ;
if ( committed ! = NULL ) {
* committed = 1 ;
}
/*
* If we get an error during the commit processing ,
* release the buffer that is still held and return
* to the caller .
*/
if ( code ) {
xfs_buf_relse ( ialloc_context ) ;
if ( dqinfo ) {
tp - > t_dqinfo = dqinfo ;
2009-06-08 17:33:32 +04:00
xfs_trans_free_dqinfo ( tp ) ;
2005-04-17 02:20:36 +04:00
}
* tpp = ntp ;
* ipp = NULL ;
return code ;
}
2008-11-17 09:37:10 +03:00
/*
* transaction commit worked ok so we can drop the extra ticket
* reference that we gained in xfs_trans_dup ( )
*/
xfs_log_ticket_put ( tp - > t_ticket ) ;
2005-04-17 02:20:36 +04:00
code = xfs_trans_reserve ( tp , 0 , log_res , 0 ,
XFS_TRANS_PERM_LOG_RES , log_count ) ;
/*
* Re - attach the quota info that we detached from prev trx .
*/
if ( dqinfo ) {
tp - > t_dqinfo = dqinfo ;
tp - > t_flags | = tflags ;
}
if ( code ) {
xfs_buf_relse ( ialloc_context ) ;
* tpp = ntp ;
* ipp = NULL ;
return code ;
}
xfs_trans_bjoin ( tp , ialloc_context ) ;
/*
* Call ialloc again . Since we ' ve locked out all
* other allocations in this allocation group ,
* this call should always succeed .
*/
code = xfs_ialloc ( tp , dp , mode , nlink , rdev , credp , prid ,
okalloc , & ialloc_context , & call_again , & ip ) ;
/*
* If we get an error at this point , return to the caller
* so that the current transaction can be aborted .
*/
if ( code ) {
* tpp = tp ;
* ipp = NULL ;
return code ;
}
ASSERT ( ( ! call_again ) & & ( ip ! = NULL ) ) ;
} else {
if ( committed ! = NULL ) {
* committed = 0 ;
}
}
* ipp = ip ;
* tpp = tp ;
return 0 ;
}
/*
* Decrement the link count on an inode & log the change .
* If this causes the link count to go to zero , initiate the
* logging activity required to truncate a file .
*/
int /* error */
xfs_droplink (
xfs_trans_t * tp ,
xfs_inode_t * ip )
{
int error ;
xfs_ichgtime ( ip , XFS_ICHGTIME_CHG ) ;
ASSERT ( ip - > i_d . di_nlink > 0 ) ;
ip - > i_d . di_nlink - - ;
2008-08-13 09:45:15 +04:00
drop_nlink ( VFS_I ( ip ) ) ;
2005-04-17 02:20:36 +04:00
xfs_trans_log_inode ( tp , ip , XFS_ILOG_CORE ) ;
error = 0 ;
if ( ip - > i_d . di_nlink = = 0 ) {
/*
* We ' re dropping the last link to this file .
* Move the on - disk inode to the AGI unlinked list .
* From xfs_inactive ( ) we will pull the inode from
* the list and free it .
*/
error = xfs_iunlink ( tp , ip ) ;
}
return error ;
}
/*
* This gets called when the inode ' s version needs to be changed from 1 to 2.
* Currently this happens when the nlink field overflows the old 16 - bit value
* or when chproj is called to change the project for the first time .
* As a side effect the superblock version will also get rev ' d
* to contain the NLINK bit .
*/
void
xfs_bump_ino_vers2 (
xfs_trans_t * tp ,
xfs_inode_t * ip )
{
xfs_mount_t * mp ;
2008-04-22 11:34:00 +04:00
ASSERT ( xfs_isilocked ( ip , XFS_ILOCK_EXCL ) ) ;
2008-11-28 06:23:39 +03:00
ASSERT ( ip - > i_d . di_version = = 1 ) ;
2005-04-17 02:20:36 +04:00
2008-11-28 06:23:39 +03:00
ip - > i_d . di_version = 2 ;
2005-04-17 02:20:36 +04:00
ip - > i_d . di_onlink = 0 ;
memset ( & ( ip - > i_d . di_pad [ 0 ] ) , 0 , sizeof ( ip - > i_d . di_pad ) ) ;
mp = tp - > t_mountp ;
2008-03-06 05:44:28 +03:00
if ( ! xfs_sb_version_hasnlink ( & mp - > m_sb ) ) {
2007-10-11 11:42:32 +04:00
spin_lock ( & mp - > m_sb_lock ) ;
2008-03-06 05:44:28 +03:00
if ( ! xfs_sb_version_hasnlink ( & mp - > m_sb ) ) {
xfs_sb_version_addnlink ( & mp - > m_sb ) ;
2007-10-11 11:42:32 +04:00
spin_unlock ( & mp - > m_sb_lock ) ;
2005-04-17 02:20:36 +04:00
xfs_mod_sb ( tp , XFS_SB_VERSIONNUM ) ;
} else {
2007-10-11 11:42:32 +04:00
spin_unlock ( & mp - > m_sb_lock ) ;
2005-04-17 02:20:36 +04:00
}
}
/* Caller must log the inode */
}
/*
* Increment the link count on an inode & log the change .
*/
int
xfs_bumplink (
xfs_trans_t * tp ,
xfs_inode_t * ip )
{
if ( ip - > i_d . di_nlink > = XFS_MAXLINK )
return XFS_ERROR ( EMLINK ) ;
xfs_ichgtime ( ip , XFS_ICHGTIME_CHG ) ;
ASSERT ( ip - > i_d . di_nlink > 0 ) ;
ip - > i_d . di_nlink + + ;
2008-08-13 09:45:15 +04:00
inc_nlink ( VFS_I ( ip ) ) ;
2008-11-28 06:23:39 +03:00
if ( ( ip - > i_d . di_version = = 1 ) & &
2005-04-17 02:20:36 +04:00
( ip - > i_d . di_nlink > XFS_MAXLINK_1 ) ) {
/*
* The inode has increased its number of links beyond
* what can fit in an old format inode . It now needs
* to be converted to a version 2 inode with a 32 bit
* link count . If this is the first inode in the file
* system to do this , then we need to bump the superblock
* version number as well .
*/
xfs_bump_ino_vers2 ( tp , ip ) ;
}
xfs_trans_log_inode ( tp , ip , XFS_ILOG_CORE ) ;
return 0 ;
}
/*
* Try to truncate the given file to 0 length . Currently called
* only out of xfs_remove when it has to truncate a file to free
* up space for the remove to proceed .
*/
int
xfs_truncate_file (
xfs_mount_t * mp ,
xfs_inode_t * ip )
{
xfs_trans_t * tp ;
int error ;
# ifdef QUOTADEBUG
/*
* This is called to truncate the quotainodes too .
*/
if ( XFS_IS_UQUOTA_ON ( mp ) ) {
if ( ip - > i_ino ! = mp - > m_sb . sb_uquotino )
ASSERT ( ip - > i_udquot ) ;
}
2005-06-21 09:38:48 +04:00
if ( XFS_IS_OQUOTA_ON ( mp ) ) {
2005-04-17 02:20:36 +04:00
if ( ip - > i_ino ! = mp - > m_sb . sb_gquotino )
ASSERT ( ip - > i_gdquot ) ;
}
# endif
/*
* Make the call to xfs_itruncate_start before starting the
* transaction , because we cannot make the call while we ' re
* in a transaction .
*/
xfs_ilock ( ip , XFS_IOLOCK_EXCL ) ;
2007-05-08 07:49:27 +04:00
error = xfs_itruncate_start ( ip , XFS_ITRUNC_DEFINITE , ( xfs_fsize_t ) 0 ) ;
if ( error ) {
xfs_iunlock ( ip , XFS_IOLOCK_EXCL ) ;
return error ;
}
2005-04-17 02:20:36 +04:00
tp = xfs_trans_alloc ( mp , XFS_TRANS_TRUNCATE_FILE ) ;
if ( ( error = xfs_trans_reserve ( tp , 0 , XFS_ITRUNCATE_LOG_RES ( mp ) , 0 ,
XFS_TRANS_PERM_LOG_RES ,
XFS_ITRUNCATE_LOG_COUNT ) ) ) {
xfs_trans_cancel ( tp , 0 ) ;
xfs_iunlock ( ip , XFS_IOLOCK_EXCL ) ;
return error ;
}
/*
* Follow the normal truncate locking protocol . Since we
2009-03-29 11:55:42 +04:00
* hold the inode in the transaction , we know that its number
2005-04-17 02:20:36 +04:00
* of references will stay constant .
*/
xfs_ilock ( ip , XFS_ILOCK_EXCL ) ;
xfs_trans_ijoin ( tp , ip , XFS_ILOCK_EXCL | XFS_IOLOCK_EXCL ) ;
xfs_trans_ihold ( tp , ip ) ;
/*
* Signal a sync xaction . The only case where that isn ' t
* the case is if we ' re truncating an already unlinked file
* on a wsync fs . In that case , we know the blocks can ' t
* reappear in the file because the links to file are
* permanently toast . Currently , we ' re always going to
* want a sync transaction because this code is being
* called from places where nlink is guaranteed to be 1
* but I ' m leaving the tests in to protect against future
* changes - - rcc .
*/
error = xfs_itruncate_finish ( & tp , ip , ( xfs_fsize_t ) 0 ,
XFS_DATA_FORK ,
( ( ip - > i_d . di_nlink ! = 0 | |
! ( mp - > m_flags & XFS_MOUNT_WSYNC ) )
? 1 : 0 ) ) ;
if ( error ) {
xfs_trans_cancel ( tp , XFS_TRANS_RELEASE_LOG_RES |
XFS_TRANS_ABORT ) ;
} else {
xfs_ichgtime ( ip , XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG ) ;
2007-05-08 07:48:42 +04:00
error = xfs_trans_commit ( tp , XFS_TRANS_RELEASE_LOG_RES ) ;
2005-04-17 02:20:36 +04:00
}
xfs_iunlock ( ip , XFS_ILOCK_EXCL | XFS_IOLOCK_EXCL ) ;
return error ;
}