2019-07-15 08:50:58 -07:00
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright ( C ) 2017 Red Hat , Inc .
* Copyright ( c ) 2018 Christoph Hellwig .
*/
# include <linux/module.h>
# include <linux/compiler.h>
# include <linux/fs.h>
# include <linux/iomap.h>
# include <linux/pagemap.h>
# include <linux/pagevec.h>
/*
* Seek for SEEK_DATA / SEEK_HOLE within @ page , starting at @ lastoff .
* Returns true if found and updates @ lastoff to the offset in file .
*/
static bool
page_seek_hole_data ( struct inode * inode , struct page * page , loff_t * lastoff ,
int whence )
{
const struct address_space_operations * ops = inode - > i_mapping - > a_ops ;
unsigned int bsize = i_blocksize ( inode ) , off ;
bool seek_data = whence = = SEEK_DATA ;
loff_t poff = page_offset ( page ) ;
if ( WARN_ON_ONCE ( * lastoff > = poff + PAGE_SIZE ) )
return false ;
if ( * lastoff < poff ) {
/*
* Last offset smaller than the start of the page means we found
* a hole :
*/
if ( whence = = SEEK_HOLE )
return true ;
* lastoff = poff ;
}
/*
* Just check the page unless we can and should check block ranges :
*/
if ( bsize = = PAGE_SIZE | | ! ops - > is_partially_uptodate )
return PageUptodate ( page ) = = seek_data ;
lock_page ( page ) ;
if ( unlikely ( page - > mapping ! = inode - > i_mapping ) )
goto out_unlock_not_found ;
for ( off = 0 ; off < PAGE_SIZE ; off + = bsize ) {
if ( offset_in_page ( * lastoff ) > = off + bsize )
continue ;
if ( ops - > is_partially_uptodate ( page , off , bsize ) = = seek_data ) {
unlock_page ( page ) ;
return true ;
}
* lastoff = poff + off + bsize ;
}
out_unlock_not_found :
unlock_page ( page ) ;
return false ;
}
/*
* Seek for SEEK_DATA / SEEK_HOLE in the page cache .
*
* Within unwritten extents , the page cache determines which parts are holes
* and which are data : uptodate buffer heads count as data ; everything else
* counts as a hole .
*
* Returns the resulting offset on successs , and - ENOENT otherwise .
*/
static loff_t
page_cache_seek_hole_data ( struct inode * inode , loff_t offset , loff_t length ,
int whence )
{
pgoff_t index = offset > > PAGE_SHIFT ;
pgoff_t end = DIV_ROUND_UP ( offset + length , PAGE_SIZE ) ;
loff_t lastoff = offset ;
struct pagevec pvec ;
if ( length < = 0 )
return - ENOENT ;
pagevec_init ( & pvec ) ;
do {
unsigned nr_pages , i ;
nr_pages = pagevec_lookup_range ( & pvec , inode - > i_mapping , & index ,
end - 1 ) ;
if ( nr_pages = = 0 )
break ;
for ( i = 0 ; i < nr_pages ; i + + ) {
struct page * page = pvec . pages [ i ] ;
if ( page_seek_hole_data ( inode , page , & lastoff , whence ) )
goto check_range ;
lastoff = page_offset ( page ) + PAGE_SIZE ;
}
pagevec_release ( & pvec ) ;
} while ( index < end ) ;
/* When no page at lastoff and we are not done, we found a hole. */
if ( whence ! = SEEK_HOLE )
goto not_found ;
check_range :
if ( lastoff < offset + length )
goto out ;
not_found :
lastoff = - ENOENT ;
out :
pagevec_release ( & pvec ) ;
return lastoff ;
}
static loff_t
iomap_seek_hole_actor ( struct inode * inode , loff_t offset , loff_t length ,
2019-10-18 16:44:10 -07:00
void * data , struct iomap * iomap , struct iomap * srcmap )
2019-07-15 08:50:58 -07:00
{
switch ( iomap - > type ) {
case IOMAP_UNWRITTEN :
offset = page_cache_seek_hole_data ( inode , offset , length ,
SEEK_HOLE ) ;
if ( offset < 0 )
return length ;
2020-08-23 17:36:59 -05:00
fallthrough ;
2019-07-15 08:50:58 -07:00
case IOMAP_HOLE :
* ( loff_t * ) data = offset ;
return 0 ;
default :
return length ;
}
}
loff_t
iomap_seek_hole ( struct inode * inode , loff_t offset , const struct iomap_ops * ops )
{
loff_t size = i_size_read ( inode ) ;
loff_t length = size - offset ;
loff_t ret ;
/* Nothing to be found before or beyond the end of the file. */
if ( offset < 0 | | offset > = size )
return - ENXIO ;
while ( length > 0 ) {
ret = iomap_apply ( inode , offset , length , IOMAP_REPORT , ops ,
& offset , iomap_seek_hole_actor ) ;
if ( ret < 0 )
return ret ;
if ( ret = = 0 )
break ;
offset + = ret ;
length - = ret ;
}
return offset ;
}
EXPORT_SYMBOL_GPL ( iomap_seek_hole ) ;
static loff_t
iomap_seek_data_actor ( struct inode * inode , loff_t offset , loff_t length ,
2019-10-18 16:44:10 -07:00
void * data , struct iomap * iomap , struct iomap * srcmap )
2019-07-15 08:50:58 -07:00
{
switch ( iomap - > type ) {
case IOMAP_HOLE :
return length ;
case IOMAP_UNWRITTEN :
offset = page_cache_seek_hole_data ( inode , offset , length ,
SEEK_DATA ) ;
if ( offset < 0 )
return length ;
2020-08-23 17:36:59 -05:00
fallthrough ;
2019-07-15 08:50:58 -07:00
default :
* ( loff_t * ) data = offset ;
return 0 ;
}
}
loff_t
iomap_seek_data ( struct inode * inode , loff_t offset , const struct iomap_ops * ops )
{
loff_t size = i_size_read ( inode ) ;
loff_t length = size - offset ;
loff_t ret ;
/* Nothing to be found before or beyond the end of the file. */
if ( offset < 0 | | offset > = size )
return - ENXIO ;
while ( length > 0 ) {
ret = iomap_apply ( inode , offset , length , IOMAP_REPORT , ops ,
& offset , iomap_seek_data_actor ) ;
if ( ret < 0 )
return ret ;
if ( ret = = 0 )
break ;
offset + = ret ;
length - = ret ;
}
if ( length < = 0 )
return - ENXIO ;
return offset ;
}
EXPORT_SYMBOL_GPL ( iomap_seek_data ) ;