linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* Re: [PATCH v9 1/2] fs: New zonefs file system
@ 2020-01-28 16:24 Markus Elfring
  2020-01-29  4:14 ` Damien Le Moal
  0 siblings, 1 reply; 14+ messages in thread
From: Markus Elfring @ 2020-01-28 16:24 UTC (permalink / raw)
  To: Damien Le Moal, linux-fsdevel, linux-xfs
  Cc: linux-kernel, Darrick J. Wong, Hannes Reinecke,
	Johannes Thumshirn, Linus Torvalds, Naohiro Aota

…
> +++ b/fs/zonefs/Kconfig
> +	help
> +	  zonefs is a simple File System which exposes zones of a zoned block

Does the capitalisation matter here?
Would the spelling “Zonefs is a simple file system which …” be appropriate?

Regards,
Markus

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v9 1/2] fs: New zonefs file system
  2020-01-28 16:24 [PATCH v9 1/2] fs: New zonefs file system Markus Elfring
@ 2020-01-29  4:14 ` Damien Le Moal
  0 siblings, 0 replies; 14+ messages in thread
From: Damien Le Moal @ 2020-01-29  4:14 UTC (permalink / raw)
  To: Markus.Elfring, linux-fsdevel, linux-xfs
  Cc: darrick.wong, torvalds, jth, linux-kernel, hare, Naohiro Aota

On Tue, 2020-01-28 at 17:24 +0100, Markus Elfring wrote:
> …
> > +++ b/fs/zonefs/Kconfig
> …
> > +	help
> > +	  zonefs is a simple File System which exposes zones of a zoned block
> 
> Does the capitalisation matter here?
> Would the spelling “Zonefs is a simple file system which …” be appropriate?

Fixed. Thanks !

> 
> Regards,
> Markus

-- 
Damien Le Moal
Western Digital Research

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v9 1/2] fs: New zonefs file system
  2020-01-30  3:00         ` Damien Le Moal
@ 2020-01-30 22:59           ` Dave Chinner
  0 siblings, 0 replies; 14+ messages in thread
From: Dave Chinner @ 2020-01-30 22:59 UTC (permalink / raw)
  To: Damien Le Moal
  Cc: hare, Naohiro Aota, linux-fsdevel, linux-kernel, torvalds,
	darrick.wong, jth, linux-xfs

On Thu, Jan 30, 2020 at 03:00:32AM +0000, Damien Le Moal wrote:
> On Thu, 2020-01-30 at 08:33 +1100, Dave Chinner wrote:
> > On Wed, Jan 29, 2020 at 01:06:29PM +0000, Damien Le Moal wrote:
> > > Exactly. This is how the ZBC & ZAC (and upcoming ZNS) specifications
> > > define the write pointer behavior. That makes error recovery a lot
> > > easier and does not result in stale data accesses. Just notice the one-
> > > off difference for the WP position from your example as WP will be
> > > pointing at the error location, not the last written location. Indexing
> > > from 0, we get (wp - zone start) always being isize with all written
> > > and readable data in the sector range between zone start and zone write
> > > pointer.
> > 
> > Ok, I'm going throw a curve ball here: volatile device caches.
> > 
> > How does the write pointer updates interact with device write
> > caches? i.e.  the first write could be sitting in the device write
> > cache, and the OS write pointer has been advanced. Then another write
> > occurs, the device decides to write both to physical media, and it
> > gets a write error in the area of the first write that only hit the
> > volatile cache.
> > 
> > So does this mean that, from the POV of the OS, the device zone
> > write pointer has gone backwards?
> 
> You are absolutely correct. Forgot to consider this case.
> Nice pitching :)

Potentially adverse IO ordering interactions with volatile device
caches are never that far from the mind of filesystem engineers...
:)

> > Unless there's some other magic that ensures device cached writes
> > that have been signalled as successfully completed to the OS
> > can never fail or that sequential zone writes are never cached in
> > volatile memory in drives, I can't see how the above guarantees
> > can be provided.
> 
> There not, at least from the standards point of view. Such guarantees
> would be device implementation dependent and so we cannot rely on
> anything in this regard. The write pointer ending up below the position
> of the last issue direct IO is thus a possibility and not necessarily
> indicative of an external action (and we actually cannot distinguish
> which case it really is).

*nod*

> > > It is hard to decide on the best action to take here considering the
> > > simple nature of zonefs (i.e. another better interface to do raw block
> > > device file accesses). Including your comments on mount options, I cam
> > > up with these actions that the user can choose with mount options:
> > > * repair: Truncate the inode size only, nothing else
> > > * remount-ro (default): Truncate the inode size and remount read-only
> > > * zone-ro: Truncate the inode size and set the inode read-only
> > > * zone-offline: Truncate the inode size to 0 and assume that its zone 
> > > is offline (no reads nor writes possible).
> > > 
> > > This gives I think a good range of possible behaviors that the user may
> > > want, from almost nothing (repair) to extreme to avoid accessing bad
> > > data (zone-offline).
> > 
> > I would suggest that this is something that can be added later as it
> > is not critical to supporting the underlying functionality.  Right
> > now I'd just pick the safest option: shutdown to protect what data
> > is on the storage right now and then let the user take action to
> > recover/fix the issue.
> 
> By shutdown, do you mean remounting read-only ? Or do you mean
> something more aggressive like preventing all accesses and changes to
> files, i.e. assuming all zones are offline ? The former is already
> there and is the default.

"shutdown" in this context means "do whatever is necessary to
prevent the problem getting worse". So, at minimum, it would be to
prevent further writes to the zone that has gone bad.

If there's potential for other zones to be affected, then moving to
a global read-only state is the right thing to do.

If there's potential for the error to expose stale data, propagate
the error further into currently good on-disk structures, or walk
off the end of corrupt structures (kernel crash and/or memory
corruption), then an aggressive "error out as early as possible"
shutdown is the right solution....

I suspect that zonefs really only needs to go as far as remounting
read-only as long as the hardware write pointers prevent reading the
zone beyond that point....

Cheers,

Dave.
-- 
Dave Chinner
david@fromorbit.com

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v9 1/2] fs: New zonefs file system
  2020-01-29 21:33       ` Dave Chinner
@ 2020-01-30  3:00         ` Damien Le Moal
  2020-01-30 22:59           ` Dave Chinner
  0 siblings, 1 reply; 14+ messages in thread
From: Damien Le Moal @ 2020-01-30  3:00 UTC (permalink / raw)
  To: david
  Cc: hare, Naohiro Aota, linux-fsdevel, linux-kernel, torvalds,
	darrick.wong, jth, linux-xfs

On Thu, 2020-01-30 at 08:33 +1100, Dave Chinner wrote:
> On Wed, Jan 29, 2020 at 01:06:29PM +0000, Damien Le Moal wrote:
> > On Tue, 2020-01-28 at 09:46 -0800, Darrick J. Wong wrote:
> > > > +static int zonefs_io_err_cb(struct blk_zone *zone, unsigned int idx, void *data)
> > > > +{
> > > > +	struct zonefs_ioerr_data *ioerr = data;
> > > > +	struct inode *inode = ioerr->inode;
> > > > +	struct zonefs_inode_info *zi = ZONEFS_I(inode);
> > > > +	struct super_block *sb = inode->i_sb;
> > > > +	loff_t isize, wp_ofst;
> > > > +
> > > > +	/*
> > > > +	 * The condition of the zone may have change. Fix the file access
> > > > +	 * permissions if necessary.
> > > > +	 */
> > > > +	zonefs_update_file_perm(inode, zone);
> > > > +
> > > > +	/*
> > > > +	 * There is no write pointer on conventional zones and read operations
> > > > +	 * do not change a zone write pointer. So there is nothing more to do
> > > > +	 * for these two cases.
> > > > +	 */
> > > > +	if (zi->i_ztype == ZONEFS_ZTYPE_CNV || !ioerr->write)
> > > > +		return 0;
> > > > +
> > > > +	/*
> > > > +	 * For sequential zones write, make sure that the zone write pointer
> > > > +	 * position is as expected, that is, in sync with the inode size.
> > > > +	 */
> > > > +	wp_ofst = (zone->wp - zone->start) << SECTOR_SHIFT;
> > > > +	zi->i_wpoffset = wp_ofst;
> > > > +	isize = i_size_read(inode);
> > > > +
> > > > +	if (isize == wp_ofst)
> > > /> +		return 0;
> > > > +
> > > > +	/*
> > > > +	 * The inode size and the zone write pointer are not in sync.
> > > > +	 * If the inode size is below the zone write pointer, then data was
> > > 
> > > I'm a little confused about what events these states reflect.
> > > 
> > > "inode size is below the zone wp" -- let's say we have a partially
> > > written sequential zone:
> > > 
> > >     isize
> > > ----v---------------
> > > DDDDD
> > > ----^---------------
> > >     WP
> > > 
> > > Then we tried to write to the end of the sequential zone:
> > > 
> > >     isize
> > > ----v---------------
> > > DDDDDWWWW
> > > ----^---------------
> > >     WP
> > > 
> > > Then an error happens so we didn't update the isize, and now we see that
> > > the write pointer is beyond isize (pretend the write failed to the '?'
> > > area):
> > > 
> > >     isize
> > > ----v---------------
> > > DDDDDD?DD
> > > --------^-----------
> > >         WP
> > 
> > If the write failed at the "?" location, then the zone write pointer
> > points to that location since nothing after that location can be
> > written unless that location itself is first written.
> > 
> > So with your example, the drive will give back:
> > 
> >     isize
> > ----v---------------
> > DDDDDD?XX
> > ------^-------------
> >       WP
> > 
> > With XX denoting the unwritten part of the issued write.
> > 
> > > So if we increase isize to match the WP, what happens when userspace
> > > tries to read the question-mark area?  Do they get read errors?  Stale
> > > contents?
> > 
> > Nope, see above: the write pointer always point to the sector following
> > the last sector correctly written. So increasing isize to the write
> > pointer location only exposes the data that actually was written and is
> > readable. No stale data.
> > > Or am I misunderstanding SMR firmware, and the drive only advances the
> > > write pointer once it has written a block?  i.e. if a write fails in
> > > the middle, the drive ends up in this state, not the one I drew above:
> > > 
> > >     isize
> > > ----v---------------
> > > DDDDDD?
> > > -----^--------------
> > >      WP
> > > 
> > > In which case it would be fine to push isize up to the write pointer?
> > 
> > Exactly. This is how the ZBC & ZAC (and upcoming ZNS) specifications
> > define the write pointer behavior. That makes error recovery a lot
> > easier and does not result in stale data accesses. Just notice the one-
> > off difference for the WP position from your example as WP will be
> > pointing at the error location, not the last written location. Indexing
> > from 0, we get (wp - zone start) always being isize with all written
> > and readable data in the sector range between zone start and zone write
> > pointer.
> 
> Ok, I'm going throw a curve ball here: volatile device caches.
> 
> How does the write pointer updates interact with device write
> caches? i.e.  the first write could be sitting in the device write
> cache, and the OS write pointer has been advanced. Then another write
> occurs, the device decides to write both to physical media, and it
> gets a write error in the area of the first write that only hit the
> volatile cache.
> 
> So does this mean that, from the POV of the OS, the device zone
> write pointer has gone backwards?

You are absolutely correct. Forgot to consider this case.
Nice pitching :)

> Unless there's some other magic that ensures device cached writes
> that have been signalled as successfully completed to the OS
> can never fail or that sequential zone writes are never cached in
> volatile memory in drives, I can't see how the above guarantees
> can be provided.

There not, at least from the standards point of view. Such guarantees
would be device implementation dependent and so we cannot rely on
anything in this regard. The write pointer ending up below the position
of the last issue direct IO is thus a possibility and not necessarily
indicative of an external action (and we actually cannot distinguish
which case it really is).

And looking at the code again, I need to add error processing in fsync
to catch this case.

> > It is hard to decide on the best action to take here considering the
> > simple nature of zonefs (i.e. another better interface to do raw block
> > device file accesses). Including your comments on mount options, I cam
> > up with these actions that the user can choose with mount options:
> > * repair: Truncate the inode size only, nothing else
> > * remount-ro (default): Truncate the inode size and remount read-only
> > * zone-ro: Truncate the inode size and set the inode read-only
> > * zone-offline: Truncate the inode size to 0 and assume that its zone 
> > is offline (no reads nor writes possible).
> > 
> > This gives I think a good range of possible behaviors that the user may
> > want, from almost nothing (repair) to extreme to avoid accessing bad
> > data (zone-offline).
> 
> I would suggest that this is something that can be added later as it
> is not critical to supporting the underlying functionality.  Right
> now I'd just pick the safest option: shutdown to protect what data
> is on the storage right now and then let the user take action to
> recover/fix the issue.

By shutdown, do you mean remounting read-only ? Or do you mean
something more aggressive like preventing all accesses and changes to
files, i.e. assuming all zones are offline ? The former is already
there and is the default.

> 
> > > > +	 * BIO allocations for the same device. The former case may end up in
> > > > +	 * a deadlock on the inode truncate mutex, while the latter may prevent
> > > > +	 * forward progress with BIO allocations as we are potentially still
> > > > +	 * holding the failed BIO. Executing the report zones under GFP_NOIO
> > > > +	 * avoids both problems.
> > > > +	 */
> > > > +	noio_flag = memalloc_noio_save();
> > > 
> > > Don't you still need memalloc_nofs_ here too?
> > 
> > noio implies nofs, doesn't it ? Or rather, noio is more restrictive
> > than nofs here. Which is safer since we need a struct request to be
> > able to execute blkdev_report_zones().
> 
> Correct, noio implies nofs.
> 
> Cheers,
> 
> Dave.

Thanks !


-- 
Damien Le Moal
Western Digital Research

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v9 1/2] fs: New zonefs file system
  2020-01-29 13:06     ` Damien Le Moal
@ 2020-01-29 21:33       ` Dave Chinner
  2020-01-30  3:00         ` Damien Le Moal
  0 siblings, 1 reply; 14+ messages in thread
From: Dave Chinner @ 2020-01-29 21:33 UTC (permalink / raw)
  To: Damien Le Moal
  Cc: darrick.wong, torvalds, jth, linux-fsdevel, linux-xfs,
	linux-kernel, Naohiro Aota, hare

On Wed, Jan 29, 2020 at 01:06:29PM +0000, Damien Le Moal wrote:
> On Tue, 2020-01-28 at 09:46 -0800, Darrick J. Wong wrote:
> > > +static int zonefs_io_err_cb(struct blk_zone *zone, unsigned int idx, void *data)
> > > +{
> > > +	struct zonefs_ioerr_data *ioerr = data;
> > > +	struct inode *inode = ioerr->inode;
> > > +	struct zonefs_inode_info *zi = ZONEFS_I(inode);
> > > +	struct super_block *sb = inode->i_sb;
> > > +	loff_t isize, wp_ofst;
> > > +
> > > +	/*
> > > +	 * The condition of the zone may have change. Fix the file access
> > > +	 * permissions if necessary.
> > > +	 */
> > > +	zonefs_update_file_perm(inode, zone);
> > > +
> > > +	/*
> > > +	 * There is no write pointer on conventional zones and read operations
> > > +	 * do not change a zone write pointer. So there is nothing more to do
> > > +	 * for these two cases.
> > > +	 */
> > > +	if (zi->i_ztype == ZONEFS_ZTYPE_CNV || !ioerr->write)
> > > +		return 0;
> > > +
> > > +	/*
> > > +	 * For sequential zones write, make sure that the zone write pointer
> > > +	 * position is as expected, that is, in sync with the inode size.
> > > +	 */
> > > +	wp_ofst = (zone->wp - zone->start) << SECTOR_SHIFT;
> > > +	zi->i_wpoffset = wp_ofst;
> > > +	isize = i_size_read(inode);
> > > +
> > > +	if (isize == wp_ofst)
> > /> +		return 0;
> > > +
> > > +	/*
> > > +	 * The inode size and the zone write pointer are not in sync.
> > > +	 * If the inode size is below the zone write pointer, then data was
> > 
> > I'm a little confused about what events these states reflect.
> > 
> > "inode size is below the zone wp" -- let's say we have a partially
> > written sequential zone:
> > 
> >     isize
> > ----v---------------
> > DDDDD
> > ----^---------------
> >     WP
> > 
> > Then we tried to write to the end of the sequential zone:
> > 
> >     isize
> > ----v---------------
> > DDDDDWWWW
> > ----^---------------
> >     WP
> > 
> > Then an error happens so we didn't update the isize, and now we see that
> > the write pointer is beyond isize (pretend the write failed to the '?'
> > area):
> > 
> >     isize
> > ----v---------------
> > DDDDDD?DD
> > --------^-----------
> >         WP
> 
> If the write failed at the "?" location, then the zone write pointer
> points to that location since nothing after that location can be
> written unless that location itself is first written.
> 
> So with your example, the drive will give back:
> 
>     isize
> ----v---------------
> DDDDDD?XX
> ------^-------------
>       WP
> 
> With XX denoting the unwritten part of the issued write.
> 
> > So if we increase isize to match the WP, what happens when userspace
> > tries to read the question-mark area?  Do they get read errors?  Stale
> > contents?
> 
> Nope, see above: the write pointer always point to the sector following
> the last sector correctly written. So increasing isize to the write
> pointer location only exposes the data that actually was written and is
> readable. No stale data.
> > Or am I misunderstanding SMR firmware, and the drive only advances the
> > write pointer once it has written a block?  i.e. if a write fails in
> > the middle, the drive ends up in this state, not the one I drew above:
> > 
> >     isize
> > ----v---------------
> > DDDDDD?
> > -----^--------------
> >      WP
> > 
> > In which case it would be fine to push isize up to the write pointer?
> 
> Exactly. This is how the ZBC & ZAC (and upcoming ZNS) specifications
> define the write pointer behavior. That makes error recovery a lot
> easier and does not result in stale data accesses. Just notice the one-
> off difference for the WP position from your example as WP will be
> pointing at the error location, not the last written location. Indexing
> from 0, we get (wp - zone start) always being isize with all written
> and readable data in the sector range between zone start and zone write
> pointer.

Ok, I'm going throw a curve ball here: volatile device caches.

How does the write pointer updates interact with device write
caches? i.e.  the first write could be sitting in the device write
cache, and the OS write pointer has been advanced. Then another write
occurs, the device decides to write both to physical media, and it
gets a write error in the area of the first write that only hit the
volatile cache.

So does this mean that, from the POV of the OS, the device zone
write pointer has gone backwards?

Unless there's some other magic that ensures device cached writes
that have been signalled as successfully completed to the OS
can never fail or that sequential zone writes are never cached in
volatile memory in drives, I can't see how the above guarantees
can be provided.

> It is hard to decide on the best action to take here considering the
> simple nature of zonefs (i.e. another better interface to do raw block
> device file accesses). Including your comments on mount options, I cam
> up with these actions that the user can choose with mount options:
> * repair: Truncate the inode size only, nothing else
> * remount-ro (default): Truncate the inode size and remount read-only
> * zone-ro: Truncate the inode size and set the inode read-only
> * zone-offline: Truncate the inode size to 0 and assume that its zone 
> is offline (no reads nor writes possible).
> 
> This gives I think a good range of possible behaviors that the user may
> want, from almost nothing (repair) to extreme to avoid accessing bad
> data (zone-offline).

I would suggest that this is something that can be added later as it
is not critical to supporting the underlying functionality.  Right
now I'd just pick the safest option: shutdown to protect what data
is on the storage right now and then let the user take action to
recover/fix the issue.

> > > +	 * BIO allocations for the same device. The former case may end up in
> > > +	 * a deadlock on the inode truncate mutex, while the latter may prevent
> > > +	 * forward progress with BIO allocations as we are potentially still
> > > +	 * holding the failed BIO. Executing the report zones under GFP_NOIO
> > > +	 * avoids both problems.
> > > +	 */
> > > +	noio_flag = memalloc_noio_save();
> > 
> > Don't you still need memalloc_nofs_ here too?
> 
> noio implies nofs, doesn't it ? Or rather, noio is more restrictive
> than nofs here. Which is safer since we need a struct request to be
> able to execute blkdev_report_zones().

Correct, noio implies nofs.

Cheers,

Dave.
-- 
Dave Chinner
david@fromorbit.com

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v9 1/2] fs: New zonefs file system
  2020-01-28 17:46   ` Darrick J. Wong
@ 2020-01-29 13:06     ` Damien Le Moal
  2020-01-29 21:33       ` Dave Chinner
  0 siblings, 1 reply; 14+ messages in thread
From: Damien Le Moal @ 2020-01-29 13:06 UTC (permalink / raw)
  To: darrick.wong
  Cc: torvalds, jth, linux-fsdevel, linux-xfs, linux-kernel,
	Naohiro Aota, hare

Hi Darrick,

On Tue, 2020-01-28 at 09:46 -0800, Darrick J. Wong wrote:
[...]
> > +/*
> > + * Update a file inode access permissions based on the file zone condition.
> > + */
> > +static void zonefs_update_file_perm(struct inode *inode, struct blk_zone *zone)
> > +{
> > +	if (zone->cond == BLK_ZONE_COND_OFFLINE) {
> > +		/*
> > +		 * Dead zone: make the inode immutable, disable all accesses
> > +		 * and set the file size to 0 (zone wp set to zone start).
> > +		 */
> > +		inode->i_flags |= S_IMMUTABLE;
> 
> One annoying nit about setting S_IMMUTABLE: the generic vfs write
> routines do not check S_IMMUTABLE, which means that zonefs will have to
> do that on its own.
> 
> I tried to fix it last year, but there were complaints that it could
> break existing workloads (open O_TMPFILE for write, mark it immutable,
> link it into the filesystem, continue to write it since you're the only
> writer...)

OK. Understood. Adding checks where appropriate.

> > +		inode->i_mode &= ~0777;
> > +		zone->wp = zone->start;
> > +	} else if (zone->cond == BLK_ZONE_COND_READONLY) {
> > +		/* Do not allow writes in read-only zones */
> > +		inode->i_flags |= S_IMMUTABLE;
> > +		inode->i_mode &= ~0222;
> > +	}
> > +}
> > +
> > +struct zonefs_ioerr_data {
> > +	struct inode	*inode;
> > +	bool		write;
> > +};
> > +
> > +static int zonefs_io_err_cb(struct blk_zone *zone, unsigned int idx, void *data)
> > +{
> > +	struct zonefs_ioerr_data *ioerr = data;
> > +	struct inode *inode = ioerr->inode;
> > +	struct zonefs_inode_info *zi = ZONEFS_I(inode);
> > +	struct super_block *sb = inode->i_sb;
> > +	loff_t isize, wp_ofst;
> > +
> > +	/*
> > +	 * The condition of the zone may have change. Fix the file access
> > +	 * permissions if necessary.
> > +	 */
> > +	zonefs_update_file_perm(inode, zone);
> > +
> > +	/*
> > +	 * There is no write pointer on conventional zones and read operations
> > +	 * do not change a zone write pointer. So there is nothing more to do
> > +	 * for these two cases.
> > +	 */
> > +	if (zi->i_ztype == ZONEFS_ZTYPE_CNV || !ioerr->write)
> > +		return 0;
> > +
> > +	/*
> > +	 * For sequential zones write, make sure that the zone write pointer
> > +	 * position is as expected, that is, in sync with the inode size.
> > +	 */
> > +	wp_ofst = (zone->wp - zone->start) << SECTOR_SHIFT;
> > +	zi->i_wpoffset = wp_ofst;
> > +	isize = i_size_read(inode);
> > +
> > +	if (isize == wp_ofst)
> /> +		return 0;
> > +
> > +	/*
> > +	 * The inode size and the zone write pointer are not in sync.
> > +	 * If the inode size is below the zone write pointer, then data was
> 
> I'm a little confused about what events these states reflect.
> 
> "inode size is below the zone wp" -- let's say we have a partially
> written sequential zone:
> 
>     isize
> ----v---------------
> DDDDD
> ----^---------------
>     WP
> 
> Then we tried to write to the end of the sequential zone:
> 
>     isize
> ----v---------------
> DDDDDWWWW
> ----^---------------
>     WP
> 
> Then an error happens so we didn't update the isize, and now we see that
> the write pointer is beyond isize (pretend the write failed to the '?'
> area):
> 
>     isize
> ----v---------------
> DDDDDD?DD
> --------^-----------
>         WP

If the write failed at the "?" location, then the zone write pointer
points to that location since nothing after that location can be
written unless that location itself is first written.

So with your example, the drive will give back:

    isize
----v---------------
DDDDDD?XX
------^-------------
      WP

With XX denoting the unwritten part of the issued write.

> So if we increase isize to match the WP, what happens when userspace
> tries to read the question-mark area?  Do they get read errors?  Stale
> contents?

Nope, see above: the write pointer always point to the sector following
the last sector correctly written. So increasing isize to the write
pointer location only exposes the data that actually was written and is
readable. No stale data.

> Or am I misunderstanding SMR firmware, and the drive only advances the
> write pointer once it has written a block?  i.e. if a write fails in
> the middle, the drive ends up in this state, not the one I drew above:
> 
>     isize
> ----v---------------
> DDDDDD?
> -----^--------------
>      WP
> 
> In which case it would be fine to push isize up to the write pointer?

Exactly. This is how the ZBC & ZAC (and upcoming ZNS) specifications
define the write pointer behavior. That makes error recovery a lot
easier and does not result in stale data accesses. Just notice the one-
off difference for the WP position from your example as WP will be
pointing at the error location, not the last written location. Indexing
from 0, we get (wp - zone start) always being isize with all written
and readable data in the sector range between zone start and zone write
pointer.

> Aha, you /did/ say exactly this in the v8 thread.
> 
> > +	 * writen at the end of the file. This can happen in the case of a
> > +	 * partial failure of a large multi-bio DIO. No data is lost. Simply fix
> > +	 * the inode size to reflect the partial write.

Yes. I further improved this comment to make it, I hope this time,
super easy to understand.

> > +	 * On the other hand, if the inode size is over the zone write pointer,
> > +	 * then there was an external corruption, e.g. an application reset the
> > +	 * file zone directly, or the device has a problem.
> 
> So I guess this case "isize is greater than WP" means we start with
> this appending write to what we think is the end of the zone:
> 
>     isize
> ----v---------------
> DDDDDWWWW
> --------------------
> 
> (The position of the WP is irrelevant here)
> 
> Then we get a disk error, so we query the WP and discover that it's
> actually below isize:
> 
>     isize
> ----v---------------
> DDDDDDD
> -^------------------
>  WP
> 
> So now we conclude that either the drive is broken or someone is messing
> with the zones behind our back, so we'd rather just shut down and let
> the sysadmin figure it out?  Because while we could truncate the zone
> file down to the WP, this is a sign that something could be seriously
> broken?

Yes. Exactly. The figure for the file after such error would be:

    isize
----v---------------
DDXXX
-^------------------
 WP

With the XXX sectors being garbage data since read accesses to sectors
after a zone write pointer returns zeroes, or the drive format pattern
if it is set.

Which also means that the "DD" data above cannot be trusted since if we
started with isize after WP, it means that we saw WP == isize on mount.
And with SMR specifications, the only way to get into the situation
above is if the zone is reset and rewritten behind our back.

It is hard to decide on the best action to take here considering the
simple nature of zonefs (i.e. another better interface to do raw block
device file accesses). Including your comments on mount options, I cam
up with these actions that the user can choose with mount options:
* repair: Truncate the inode size only, nothing else
* remount-ro (default): Truncate the inode size and remount read-only
* zone-ro: Truncate the inode size and set the inode read-only
* zone-offline: Truncate the inode size to 0 and assume that its zone 
is offline (no reads nor writes possible).

This gives I think a good range of possible behaviors that the user may
want, from almost nothing (repair) to extreme to avoid accessing bad
data (zone-offline).

> (Oh, you said this in the v8 thread too.)
> 
> > +	 */
> > +	zonefs_warn(sb, "inode %lu: size %lld should be %lld\n",
> > +		    inode->i_ino, isize, wp_ofst);
> > +	if (isize > wp_ofst) {
> > +		struct zonefs_sb_info *sbi = ZONEFS_SB(sb);
> > +
> > +		if ((sbi->s_mount_opts & ZONEFS_MNTOPT_ERRORS_RO) &&
> 
> Mount options?  Hey, wait a minute, this didn't exist in v8...

Yes, improvement in v9 to better handle all error cases (indirectly
suggested by Dave who pointed out deficiencies in that area).

> > +		    !sb_rdonly(sb)) {
> > +			zonefs_warn(sb,
> > +				"Zone %lu corruption detected, remounting fs read-only\n",
> > +				inode->i_ino);
> > +			sb->s_flags |= SB_RDONLY;
> > +			return 0;
> > +		} else if (sbi->s_mount_opts & ZONEFS_MNTOPT_ERRORS_CONT) {
> > +			zonefs_warn(sb,
> > +				"Zone %lu corruption detected, continuing\n",
> > +				inode->i_ino);
> 
> I'm frankly not sure errors=continue makes sense for a filesystem.  It
> exists for ext* as a crutch for the root fs to help users stumble
> towards /sbin/reboot and a full fsck afterwards.

Good point.

> 
> Also wondering if you should have an errors=zone-ro that will set
> S_IMMUTABLE on the zone file?  That would enable the intact zones to
> keep operating.

Done. And as noted above, I also added "errors=zone-offline" and
"error=repair".

> (Or I guess if you really want a "continue" mode you could truncate the
> zone...)

That is the errors=repair option now. It is clearer this way I think.

> > +		} else if (sbi->s_mount_opts & ZONEFS_MNTOPT_ERRORS_PANIC) {
> 
> I don't think it's a good idea to crash the entire kernel on zone
> corruption.

I have dropped this one.

> > +			zonefs_panic(sb,
> > +				"Zone %lu corruption detected\n",
> > +				inode->i_ino);
> > +		}
> > +	}
> > +
> > +	zonefs_update_stats(inode, wp_ofst);
> > +	i_size_write(inode, wp_ofst);
> > +
> > +	return 0;
> > +}
> > +
> > +/*
> > + * When an IO error occurs, check the target zone to see if there is a change
> > + * in the zone condition (e.g. offline or read-only). For a failed write to a
> > + * sequential zone, the zone write pointer position must also be checked to
> > + * eventually correct the file size and zonefs inode write pointer offset
> > + * (which can be out of sync with the drive due to partial write failures).
> > + */
> > +static void zonefs_io_error(struct inode *inode, bool write)
> > +{
> > +	struct zonefs_inode_info *zi = ZONEFS_I(inode);
> > +	struct super_block *sb = inode->i_sb;
> > +	struct zonefs_sb_info *sbi = ZONEFS_SB(sb);
> > +	unsigned int noio_flag;
> > +	unsigned int nr_zones =
> > +		zi->i_max_size >> (sbi->s_zone_sectors_shift + SECTOR_SHIFT);
> > +	struct zonefs_ioerr_data ioerr = {
> > +		.inode = inode,
> > +		.write = write
> > +	};
> > +	int ret;
> > +
> > +	mutex_lock(&zi->i_truncate_mutex);
> > +
> > +	/*
> > +	 * Memory allocations in blkdev_report_zones() can trigger a memory
> > +	 * reclaim which may in turn cause a recursion into zonefs as well as
> > +	 * BIO allocations for the same device. The former case may end up in
> > +	 * a deadlock on the inode truncate mutex, while the latter may prevent
> > +	 * forward progress with BIO allocations as we are potentially still
> > +	 * holding the failed BIO. Executing the report zones under GFP_NOIO
> > +	 * avoids both problems.
> > +	 */
> > +	noio_flag = memalloc_noio_save();
> 
> Don't you still need memalloc_nofs_ here too?

noio implies nofs, doesn't it ? Or rather, noio is more restrictive
than nofs here. Which is safer since we need a struct request to be
able to execute blkdev_report_zones().

> > +	ret = blkdev_report_zones(sb->s_bdev, zi->i_zsector, nr_zones,
> > +				  zonefs_io_err_cb, &ioerr);
> > +	if (ret != nr_zones)
> > +		zonefs_err(sb, "Get inode %lu zone information failed %d\n",
> > +			   inode->i_ino, ret);
> > +	memalloc_noio_restore(noio_flag);
> > +
> > +	mutex_unlock(&zi->i_truncate_mutex);
> > +}
> > +
> > +static int zonefs_file_write_dio_end_io(struct kiocb *iocb, ssize_t size,
> > +					int error, unsigned int flags)
> > +{
> > +	struct inode *inode = file_inode(iocb->ki_filp);
> > +	struct zonefs_inode_info *zi = ZONEFS_I(inode);
> > +
> > +	if (error) {
> > +		zonefs_io_error(inode, true);
> > +		return error;
> > +	}
> > +
> > +	if (size && zi->i_ztype != ZONEFS_ZTYPE_CNV) {
> > +		mutex_lock(&zi->i_truncate_mutex);
> > +		if (i_size_read(inode) < iocb->ki_pos + size) {
> > +			zonefs_update_stats(inode, iocb->ki_pos + size);
> > +			i_size_write(inode, iocb->ki_pos + size);
> > +		}
> > +		mutex_unlock(&zi->i_truncate_mutex);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct iomap_dio_ops zonefs_write_dio_ops = {
> > +	.end_io			= zonefs_file_write_dio_end_io,
> > +};

Unrelated to your other comments, I discovered that the end_io
operation is called with the flags argument being dio->flags. Since the
flags for that are the IOMAP_DIO_XXX flags defined in fs/iomap/direct-
io.c, the flags values are not visible by the implementation and the
end_io() callback function cannot determine if the dio is a read or a
write. This can be worked around by defining one end_io op for reads
and another for writes (which I did here, see
zonefs_file_read_dio_end_io()).

But we could allow code simplification by simply adding the IOMAP_XXX
flags passed to iomap_begin() into the dio->flags (theses two set of
flags do not collide as mentioned in fs/iomap/direct-io.c). That would
keep the interface in include/linux/iomap.h clean (no new flags) and
give more information to the end_io() callback. With that, I could get
rid of the zonefs_file_read_dio_end_io() function and change
zonefs_file_write_dio_end_io() into zonefs_file_dio_end_io() for both
read and write operations. Less code. Thoughts ?

> > +
> > +/*
> > + * Handle direct writes. For sequential zone files, this is the only possible
> > + * write path. For these files, check that the user is issuing writes
> > + * sequentially from the end of the file. This code assumes that the block layer
> > + * delivers write requests to the device in sequential order. This is always the
> > + * case if a block IO scheduler implementing the ELEVATOR_F_ZBD_SEQ_WRITE
> 
> Is there any way for zonefs to detect that it's talking to an io
> scheduler that doesn't support ZBD_SEQ_WRITE and react accordingly (log
> message, refuse to mount, etc.)?

Not really. It can be done if zonefs sits directly on the bdev of a
real device, but if the block device comes from a BIO-based device
mapper target (e.g. dm-linear), then there is no scheduler for that
device. Scheduling is on the backend device(s) in that case and that is
invisible from the top bdev interface. Not to mention that target may
be using several devices...

Furthermore, I am trying to limit as much as possible dependencies on
the block layer implementation of "sequential write guarantees" as we
are still trying to evolve that into something that works for any
scheduler.

> > + * elevator feature is being used (e.g. mq-deadline). The block layer always
> > + * automatically select such an elevator for zoned block devices during the
> > + * device initialization.
> 
> Or is the case that the block layer knows when it's dealing with a zoned
> block device and will not allow the assignment of an ioscheduler that
> does not support ZBD_SEQ_WRITE?

Currently, for zoned block devices, the block layer will only allow
setting a scheduler that has the ZBD_SEQ_WRITE feature. The only one
that does for now is mq-deadline. Other schedulers without this feature
support will not even be shown in /sys/block/xxx/queue/scheduler. The
only exception to this is "none", which is always allowed.

> > [...]
> > +static ssize_t zonefs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
> > +{
> > +	struct inode *inode = file_inode(iocb->ki_filp);
> > +
> > +	/* Write operations beyond the zone size are not allowed */
> > +	if (iocb->ki_pos >= ZONEFS_I(inode)->i_max_size)
> > +		return -EFBIG;
> 
> This needs a check for IS_IMMUTABLE so that userspace can't write to
> zones which zonefs has decided are no longer writable, even if the
> program has a writeable file descriptor.

Done, with another additional checks in zonefs_file_read_iter() for
offline zones (immutable + no reads allowed).

> > [...]
> > +/*
> > + * Check that the device is zoned. If it is, get the list of zones and create
> > + * sub-directories and files according to the device zone configuration and
> > + * format options.
> > + */
> > +static int zonefs_fill_super(struct super_block *sb, void *data, int silent)
> > +{
> > +	struct zonefs_zone_data zd;
> > +	struct zonefs_sb_info *sbi;
> > +	struct inode *inode;
> > +	enum zonefs_ztype t;
> > +	int ret;
> > +
> > +	if (!bdev_is_zoned(sb->s_bdev)) {
> > +		zonefs_err(sb, "Not a zoned block device\n");
> > +		return -EINVAL;
> > +	}
> > +
> > +	/*
> > +	 * Initialize super block information: the maximum file size is updated
> > +	 * when the zone files are created so that the format option
> > +	 * ZONEFS_F_AGGRCNV which increases the maximum file size of a file
> > +	 * beyond the zone size is taken into account.
> > +	 */
> > +	sbi = kzalloc(sizeof(*sbi), GFP_KERNEL);
> > +	if (!sbi)
> > +		return -ENOMEM;
> > +
> > +	spin_lock_init(&sbi->s_lock);
> > +	sb->s_fs_info = sbi;
> > +	sb->s_magic = ZONEFS_MAGIC;
> > +	sb->s_maxbytes = 0;
> > +	sb->s_op = &zonefs_sops;
> > +	sb->s_time_gran	= 1;
> > +
> > +	/*
> > +	 * The block size is set to the device physical sector size to ensure
> > +	 * that write operations on 512e devices (512B logical block and 4KB
> > +	 * physical block) are always aligned to the device physical blocks,
> > +	 * as mandated by the ZBC/ZAC specifications.
> > +	 */
> > +	sb_set_blocksize(sb, bdev_physical_block_size(sb->s_bdev));
> > +	sbi->s_blocksize_mask = sb->s_blocksize - 1;
> > +	sbi->s_zone_sectors_shift = ilog2(bdev_zone_sectors(sb->s_bdev));
> > +	sbi->s_uid = GLOBAL_ROOT_UID;
> > +	sbi->s_gid = GLOBAL_ROOT_GID;
> > +	sbi->s_perm = 0640;
> > +	sbi->s_mount_opts = ZONEFS_MNTOPT_ERRORS_RO;
> > +
> > +	ret = zonefs_read_super(sb);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = zonefs_parse_options(sb, data);
> > +	if (ret)
> > +		return ret;
> > +
> > +	memset(&zd, 0, sizeof(struct zonefs_zone_data));
> > +	zd.sb = sb;
> > +	ret = zonefs_get_zone_info(&zd);
> > +	if (ret)
> > +		goto out;
> > +
> 
> It might be a good idea to spit out an EXPERIMENTAL warning at mount
> time for the first 6 months while you, uh, seek out advanced bleeding
> edge testers to really give this code a thorough workout.
> 
> zonefs_warn(sb, "EXPERIMENTAL filesystem in use; use at your own risk");

Yes, I thought about this too but I am still wondering if it is the
right thing to do. See below.

> Or something like that to manage peoples' expectations in case you find
> a really nasty data-chomping bug. :)

Well, my view is that since zonefs does not have any run-time changing
on-disk metadata, it is not worse that the raw block device file use
case in terms of reliability. Unmount zonefs, ignoring the first zone
of the device that has the superblock, using the zones directly through
the raw block device file open/close/read/write/ioctl will give the
same level of confidence about data in the zones. If anything, zonefs
improves on that with the various checks it adds for writes and IO
errors (fs/block-dev.c does not have anything like that for zoned block
devices).

Of course I do not mean that zonefs is bug free. But I still consider
the likeliness of loosing data equivalent to the raw block device file
case: it mostly will depend on the application doing the right thing.
The value of zonefs is in the file access interface simplification, and
not in strong additional guarantees about data loss or corruption
detection. So warning about the experimental status may be too scary
and discourage users from using it and start developing for the block
device file access use case. I would rather encourage people to start
using zonefs now, especially considering the fact that the upcoming
NVMe ZNS will need some additional zone specific handling (zone
resource control for writes) that are fairly easy to handle with a one-
file-per-zone in-kernel FS interface. That simplifies even more the
application implementation.

But I do not have strong feeling about it either, and I will add the
warning if you or others insist :)

> (Or as a lever to convince people to stop running old code some day...)

I am still trying to convince a lot of SMR users to move away from
SG_IO and use the kernel block layer instead. But a lot of deployments
still use enterprise distros with kernels that do not have SMR support.
Getting zonefs into the kernel and I will definitely push for its use
in place of the raw block device file interface as that also simplifies
support for various application programming languages (e.g. SMR drive
handling directly from JAVA or python).

Thank you for all your comments.

-- 
Damien Le Moal
Western Digital Research

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v9 1/2] fs: New zonefs file system
  2020-01-28 15:34 Markus Elfring
@ 2020-01-29  4:13 ` Damien Le Moal
  0 siblings, 0 replies; 14+ messages in thread
From: Damien Le Moal @ 2020-01-29  4:13 UTC (permalink / raw)
  To: Markus.Elfring, linux-fsdevel, linux-xfs
  Cc: darrick.wong, torvalds, jth, linux-kernel, hare, Naohiro Aota

On Tue, 2020-01-28 at 16:34 +0100, Markus Elfring wrote:
> …
> > +++ b/fs/zonefs/super.c
> …
> > +out:
> > +	kunmap(page);
> > +out_free:
> > +	__free_page(page);
> 
> Would you like to reconsider your name selection for such labels?
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/process/coding-style.rst?id=b0be0eff1a5ab77d588b76bd8b1c92d5d17b3f73#n460
> 
> Change possibility:
> 
> +unmap:
> +	kunmap(page);
> +free_page:
> +	__free_page(page);
> 

Fixed. Thanks !

> 
> Regards,
> Markus

-- 
Damien Le Moal
Western Digital Research

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v9 1/2] fs: New zonefs file system
  2020-01-27 10:05 ` [PATCH v9 1/2] fs: " Damien Le Moal
@ 2020-01-28 17:46   ` Darrick J. Wong
  2020-01-29 13:06     ` Damien Le Moal
  0 siblings, 1 reply; 14+ messages in thread
From: Darrick J. Wong @ 2020-01-28 17:46 UTC (permalink / raw)
  To: Damien Le Moal
  Cc: linux-fsdevel, linux-xfs, linux-kernel, Linus Torvalds,
	Johannes Thumshirn, Naohiro Aota, Hannes Reinecke

On Mon, Jan 27, 2020 at 07:05:20PM +0900, Damien Le Moal wrote:
> zonefs is a very simple file system exposing each zone of a zoned block
> device as a file. Unlike a regular file system with zoned block device
> support (e.g. f2fs), zonefs does not hide the sequential write
> constraint of zoned block devices to the user. Files representing
> sequential write zones of the device must be written sequentially
> starting from the end of the file (append only writes).
> 
> As such, zonefs is in essence closer to a raw block device access
> interface than to a full featured POSIX file system. The goal of zonefs
> is to simplify the implementation of zoned block device support in
> applications by replacing raw block device file accesses with a richer
> file API, avoiding relying on direct block device file ioctls which may
> be more obscure to developers. One example of this approach is the
> implementation of LSM (log-structured merge) tree structures (such as
> used in RocksDB and LevelDB) on zoned block devices by allowing SSTables
> to be stored in a zone file similarly to a regular file system rather
> than as a range of sectors of a zoned device. The introduction of the
> higher level construct "one file is one zone" can help reducing the
> amount of changes needed in the application as well as introducing
> support for different application programming languages.
> 
> Zonefs on-disk metadata is reduced to an immutable super block to
> persistently store a magic number and optional feature flags and
> values. On mount, zonefs uses blkdev_report_zones() to obtain the device
> zone configuration and populates the mount point with a static file tree
> solely based on this information. E.g. file sizes come from the device
> zone type and write pointer offset managed by the device itself.
> 
> The zone files created on mount have the following characteristics.
> 1) Files representing zones of the same type are grouped together
>    under a common sub-directory:
>      * For conventional zones, the sub-directory "cnv" is used.
>      * For sequential write zones, the sub-directory "seq" is used.
>   These two directories are the only directories that exist in zonefs.
>   Users cannot create other directories and cannot rename nor delete
>   the "cnv" and "seq" sub-directories.
> 2) The name of zone files is the number of the file within the zone
>    type sub-directory, in order of increasing zone start sector.
> 3) The size of conventional zone files is fixed to the device zone size.
>    Conventional zone files cannot be truncated.
> 4) The size of sequential zone files represent the file's zone write
>    pointer position relative to the zone start sector. Truncating these
>    files is allowed only down to 0, in which case, the zone is reset to
>    rewind the zone write pointer position to the start of the zone, or
>    up to the zone size, in which case the file's zone is transitioned
>    to the FULL state (finish zone operation).
> 5) All read and write operations to files are not allowed beyond the
>    file zone size. Any access exceeding the zone size is failed with
>    the -EFBIG error.
> 6) Creating, deleting, renaming or modifying any attribute of files and
>    sub-directories is not allowed.
> 7) There are no restrictions on the type of read and write operations
>    that can be issued to conventional zone files. Buffered, direct and
>    mmap read & write operations are accepted. For sequential zone files,
>    there are no restrictions on read operations, but all write
>    operations must be direct IO append writes. mmap write of sequential
>    files is not allowed.
> 
> Several optional features of zonefs can be enabled at format time.
> * Conventional zone aggregation: ranges of contiguous conventional
>   zones can be aggregated into a single larger file instead of the
>   default one file per zone.
> * File ownership: The owner UID and GID of zone files is by default 0
>   (root) but can be changed to any valid UID/GID.
> * File access permissions: the default 640 access permissions can be
>   changed.
> 
> The mkzonefs tool is used to format zoned block devices for use with
> zonefs. This tool is available on Github at:
> 
> git@github.com:damien-lemoal/zonefs-tools.git.
> 
> zonefs-tools also includes a test suite which can be run against any
> zoned block device, including null_blk block device created with zoned
> mode.
> 
> Example: the following formats a 15TB host-managed SMR HDD with 256 MB
> zones with the conventional zones aggregation feature enabled.
> 
> $ sudo mkzonefs -o aggr_cnv /dev/sdX
> $ sudo mount -t zonefs /dev/sdX /mnt
> $ ls -l /mnt/
> total 0
> dr-xr-xr-x 2 root root     1 Nov 25 13:23 cnv
> dr-xr-xr-x 2 root root 55356 Nov 25 13:23 seq
> 
> The size of the zone files sub-directories indicate the number of files
> existing for each type of zones. In this example, there is only one
> conventional zone file (all conventional zones are aggregated under a
> single file).
> 
> $ ls -l /mnt/cnv
> total 137101312
> -rw-r----- 1 root root 140391743488 Nov 25 13:23 0
> 
> This aggregated conventional zone file can be used as a regular file.
> 
> $ sudo mkfs.ext4 /mnt/cnv/0
> $ sudo mount -o loop /mnt/cnv/0 /data
> 
> The "seq" sub-directory grouping files for sequential write zones has
> in this example 55356 zones.
> 
> $ ls -lv /mnt/seq
> total 14511243264
> -rw-r----- 1 root root 0 Nov 25 13:23 0
> -rw-r----- 1 root root 0 Nov 25 13:23 1
> -rw-r----- 1 root root 0 Nov 25 13:23 2
> ...
> -rw-r----- 1 root root 0 Nov 25 13:23 55354
> -rw-r----- 1 root root 0 Nov 25 13:23 55355
> 
> For sequential write zone files, the file size changes as data is
> appended at the end of the file, similarly to any regular file system.
> 
> $ dd if=/dev/zero of=/mnt/seq/0 bs=4K count=1 conv=notrunc oflag=direct
> 1+0 records in
> 1+0 records out
> 4096 bytes (4.1 kB, 4.0 KiB) copied, 0.000452219 s, 9.1 MB/s
> 
> $ ls -l /mnt/seq/0
> -rw-r----- 1 root root 4096 Nov 25 13:23 /mnt/seq/0
> 
> The written file can be truncated to the zone size, preventing any
> further write operation.
> 
> $ truncate -s 268435456 /mnt/seq/0
> $ ls -l /mnt/seq/0
> -rw-r----- 1 root root 268435456 Nov 25 13:49 /mnt/seq/0
> 
> Truncation to 0 size allows freeing the file zone storage space and
> restart append-writes to the file.
> 
> $ truncate -s 0 /mnt/seq/0
> $ ls -l /mnt/seq/0
> -rw-r----- 1 root root 0 Nov 25 13:49 /mnt/seq/0
> 
> Since files are statically mapped to zones on the disk, the number of
> blocks of a file as reported by stat() and fstat() indicates the size
> of the file zone.
> 
> $ stat /mnt/seq/0
>   File: /mnt/seq/0
>   Size: 0       Blocks: 524288     IO Block: 4096   regular empty file
> Device: 870h/2160d      Inode: 50431       Links: 1
> Access: (0640/-rw-r-----)  Uid: (    0/    root)   Gid: (    0/  root)
> Access: 2019-11-25 13:23:57.048971997 +0900
> Modify: 2019-11-25 13:52:25.553805765 +0900
> Change: 2019-11-25 13:52:25.553805765 +0900
>  Birth: -
> 
> The number of blocks of the file ("Blocks") in units of 512B blocks
> gives the maximum file size of 524288 * 512 B = 256 MB, corresponding
> to the device zone size in this example. Of note is that the "IO block"
> field always indicates the minimum IO size for writes and corresponds
> to the device physical sector size.
> 
> This code contains contributions from:
> * Johannes Thumshirn <jthumshirn@suse.de>,
> * Darrick J. Wong <darrick.wong@oracle.com>,
> * Christoph Hellwig <hch@lst.de>,
> * Chaitanya Kulkarni <chaitanya.kulkarni@wdc.com> and
> * Ting Yao <tingyao@hust.edu.cn>.
> 
> Signed-off-by: Damien Le Moal <damien.lemoal@wdc.com>
> ---
>  MAINTAINERS                |    9 +
>  fs/Kconfig                 |    1 +
>  fs/Makefile                |    1 +
>  fs/zonefs/Kconfig          |    9 +
>  fs/zonefs/Makefile         |    4 +
>  fs/zonefs/super.c          | 1366 ++++++++++++++++++++++++++++++++++++
>  fs/zonefs/zonefs.h         |  187 +++++
>  include/uapi/linux/magic.h |    1 +
>  8 files changed, 1578 insertions(+)
>  create mode 100644 fs/zonefs/Kconfig
>  create mode 100644 fs/zonefs/Makefile
>  create mode 100644 fs/zonefs/super.c
>  create mode 100644 fs/zonefs/zonefs.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 56765f542244..089fd879632a 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -18303,6 +18303,15 @@ L:	linux-kernel@vger.kernel.org
>  S:	Maintained
>  F:	arch/x86/kernel/cpu/zhaoxin.c
>  
> +ZONEFS FILESYSTEM
> +M:	Damien Le Moal <damien.lemoal@wdc.com>
> +M:	Naohiro Aota <naohiro.aota@wdc.com>
> +R:	Johannes Thumshirn <jth@kernel.org>
> +L:	linux-fsdevel@vger.kernel.org
> +T:	git git://git.kernel.org/pub/scm/linux/kernel/git/dlemoal/zonefs.git
> +S:	Maintained
> +F:	fs/zonefs/
> +
>  ZPOOL COMPRESSED PAGE STORAGE API
>  M:	Dan Streetman <ddstreet@ieee.org>
>  L:	linux-mm@kvack.org
> diff --git a/fs/Kconfig b/fs/Kconfig
> index 7b623e9fc1b0..a3f97ca2bd46 100644
> --- a/fs/Kconfig
> +++ b/fs/Kconfig
> @@ -40,6 +40,7 @@ source "fs/ocfs2/Kconfig"
>  source "fs/btrfs/Kconfig"
>  source "fs/nilfs2/Kconfig"
>  source "fs/f2fs/Kconfig"
> +source "fs/zonefs/Kconfig"
>  
>  config FS_DAX
>  	bool "Direct Access (DAX) support"
> diff --git a/fs/Makefile b/fs/Makefile
> index 1148c555c4d3..527f228a5e8a 100644
> --- a/fs/Makefile
> +++ b/fs/Makefile
> @@ -133,3 +133,4 @@ obj-$(CONFIG_CEPH_FS)		+= ceph/
>  obj-$(CONFIG_PSTORE)		+= pstore/
>  obj-$(CONFIG_EFIVAR_FS)		+= efivarfs/
>  obj-$(CONFIG_EROFS_FS)		+= erofs/
> +obj-$(CONFIG_ZONEFS_FS)		+= zonefs/
> diff --git a/fs/zonefs/Kconfig b/fs/zonefs/Kconfig
> new file mode 100644
> index 000000000000..03a4ef80f975
> --- /dev/null
> +++ b/fs/zonefs/Kconfig
> @@ -0,0 +1,9 @@
> +config ZONEFS_FS
> +	tristate "zonefs filesystem support"
> +	depends on BLOCK
> +	depends on BLK_DEV_ZONED
> +	help
> +	  zonefs is a simple File System which exposes zones of a zoned block
> +	  device (e.g. host-managed or host-aware SMR disk drives) as files.
> +
> +	  If unsure, say N.
> diff --git a/fs/zonefs/Makefile b/fs/zonefs/Makefile
> new file mode 100644
> index 000000000000..75a380aa1ae1
> --- /dev/null
> +++ b/fs/zonefs/Makefile
> @@ -0,0 +1,4 @@
> +# SPDX-License-Identifier: GPL-2.0
> +obj-$(CONFIG_ZONEFS_FS) += zonefs.o
> +
> +zonefs-y	:= super.o
> diff --git a/fs/zonefs/super.c b/fs/zonefs/super.c
> new file mode 100644
> index 000000000000..bef6193e0a70
> --- /dev/null
> +++ b/fs/zonefs/super.c
> @@ -0,0 +1,1366 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Simple file system for zoned block devices exposing zones as files.
> + *
> + * Copyright (C) 2019 Western Digital Corporation or its affiliates.
> + */
> +#include <linux/module.h>
> +#include <linux/fs.h>
> +#include <linux/magic.h>
> +#include <linux/iomap.h>
> +#include <linux/init.h>
> +#include <linux/slab.h>
> +#include <linux/blkdev.h>
> +#include <linux/statfs.h>
> +#include <linux/writeback.h>
> +#include <linux/quotaops.h>
> +#include <linux/seq_file.h>
> +#include <linux/parser.h>
> +#include <linux/uio.h>
> +#include <linux/mman.h>
> +#include <linux/sched/mm.h>
> +#include <linux/crc32.h>
> +
> +#include "zonefs.h"
> +
> +static int zonefs_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
> +			      unsigned int flags, struct iomap *iomap,
> +			      struct iomap *srcmap)
> +{
> +	struct zonefs_sb_info *sbi = ZONEFS_SB(inode->i_sb);
> +	struct zonefs_inode_info *zi = ZONEFS_I(inode);
> +	loff_t isize;
> +
> +	/* All I/Os should always be within the file maximum size */
> +	if (WARN_ON_ONCE(offset + length > zi->i_max_size))
> +		return -EIO;
> +
> +	/*
> +	 * Sequential zones can only accept direct writes. This is already
> +	 * checked when writes are issued, so warn about writeback operations.
> +	 */
> +	if (WARN_ON_ONCE(zi->i_ztype == ZONEFS_ZTYPE_SEQ &&
> +			 (flags & IOMAP_WRITE) && !(flags & IOMAP_DIRECT)))
> +		return -EIO;
> +
> +	/*
> +	 * For conventional zones, all blocks are always mapped.
> +	 * For sequential zones, all blocks after always mapped below the
> +	 * inode size (zone write pointer) and unwriten beyond.
> +	 */
> +	mutex_lock(&zi->i_truncate_mutex);
> +	isize = i_size_read(inode);
> +	if (offset >= isize)
> +		iomap->type = IOMAP_UNWRITTEN;
> +	else
> +		iomap->type = IOMAP_MAPPED;
> +	if (flags & IOMAP_WRITE)
> +		length = zi->i_max_size - offset;
> +	else
> +		length = min(length, isize - offset);
> +	mutex_unlock(&zi->i_truncate_mutex);
> +
> +	iomap->offset = offset & (~sbi->s_blocksize_mask);
> +	iomap->length = ((offset + length + sbi->s_blocksize_mask) &
> +			 (~sbi->s_blocksize_mask)) - iomap->offset;
> +	iomap->bdev = inode->i_sb->s_bdev;
> +	iomap->addr = (zi->i_zsector << SECTOR_SHIFT) + iomap->offset;
> +
> +	return 0;
> +}
> +
> +static const struct iomap_ops zonefs_iomap_ops = {
> +	.iomap_begin	= zonefs_iomap_begin,
> +};
> +
> +static int zonefs_readpage(struct file *unused, struct page *page)
> +{
> +	return iomap_readpage(page, &zonefs_iomap_ops);
> +}
> +
> +static int zonefs_readpages(struct file *unused, struct address_space *mapping,
> +			    struct list_head *pages, unsigned int nr_pages)
> +{
> +	return iomap_readpages(mapping, pages, nr_pages, &zonefs_iomap_ops);
> +}
> +
> +/*
> + * Map blocks for page writeback. This is used only on conventional zone files,
> + * which implies that the page range can only be within the fixed inode size.
> + */
> +static int zonefs_map_blocks(struct iomap_writepage_ctx *wpc,
> +			     struct inode *inode, loff_t offset)
> +{
> +	struct zonefs_inode_info *zi = ZONEFS_I(inode);
> +
> +	if (WARN_ON_ONCE(zi->i_ztype != ZONEFS_ZTYPE_CNV))
> +		return -EIO;
> +	if (WARN_ON_ONCE(offset >= i_size_read(inode)))
> +		return -EIO;
> +
> +	/* If the mapping is already OK, nothing needs to be done */
> +	if (offset >= wpc->iomap.offset &&
> +	    offset < wpc->iomap.offset + wpc->iomap.length)
> +		return 0;
> +
> +	return zonefs_iomap_begin(inode, offset, zi->i_max_size - offset,
> +				  IOMAP_WRITE, &wpc->iomap, NULL);
> +}
> +
> +static const struct iomap_writeback_ops zonefs_writeback_ops = {
> +	.map_blocks		= zonefs_map_blocks,
> +};
> +
> +static int zonefs_writepage(struct page *page, struct writeback_control *wbc)
> +{
> +	struct iomap_writepage_ctx wpc = { };
> +
> +	return iomap_writepage(page, wbc, &wpc, &zonefs_writeback_ops);
> +}
> +
> +static int zonefs_writepages(struct address_space *mapping,
> +			     struct writeback_control *wbc)
> +{
> +	struct iomap_writepage_ctx wpc = { };
> +
> +	return iomap_writepages(mapping, wbc, &wpc, &zonefs_writeback_ops);
> +}
> +
> +static const struct address_space_operations zonefs_file_aops = {
> +	.readpage		= zonefs_readpage,
> +	.readpages		= zonefs_readpages,
> +	.writepage		= zonefs_writepage,
> +	.writepages		= zonefs_writepages,
> +	.set_page_dirty		= iomap_set_page_dirty,
> +	.releasepage		= iomap_releasepage,
> +	.invalidatepage		= iomap_invalidatepage,
> +	.migratepage		= iomap_migrate_page,
> +	.is_partially_uptodate	= iomap_is_partially_uptodate,
> +	.error_remove_page	= generic_error_remove_page,
> +	.direct_IO		= noop_direct_IO,
> +};
> +
> +static void zonefs_update_stats(struct inode *inode, loff_t new_isize)
> +{
> +	struct super_block *sb = inode->i_sb;
> +	struct zonefs_sb_info *sbi = ZONEFS_SB(sb);
> +	loff_t old_isize = i_size_read(inode);
> +	loff_t nr_blocks;
> +
> +	if (new_isize == old_isize)
> +		return;
> +
> +	spin_lock(&sbi->s_lock);
> +
> +	/*
> +	 * This may be called for an IO error recovery update.
> +	 * So beware of the values seen.
> +	 */
> +	if (new_isize < old_isize) {
> +		nr_blocks = (old_isize - new_isize) >> sb->s_blocksize_bits;
> +		if (sbi->s_used_blocks > nr_blocks)
> +			sbi->s_used_blocks -= nr_blocks;
> +		else
> +			sbi->s_used_blocks = 0;
> +	} else {
> +		sbi->s_used_blocks +=
> +			(new_isize - old_isize) >> sb->s_blocksize_bits;
> +		if (sbi->s_used_blocks > sbi->s_blocks)
> +			sbi->s_used_blocks = sbi->s_blocks;
> +	}
> +
> +	spin_unlock(&sbi->s_lock);
> +}
> +
> +static int zonefs_file_truncate(struct inode *inode, loff_t isize)
> +{
> +	struct zonefs_inode_info *zi = ZONEFS_I(inode);
> +	loff_t old_isize;
> +	enum req_opf op;
> +	int ret = 0;
> +
> +	/*
> +	 * Only sequential zone files can be truncated and Truncation is allowed
> +	 * only down to a 0 size, which is equivalent to a zone reset, and to
> +	 * the maximum file size, which is equivalent to a zone finish.
> +	 */
> +	if (zi->i_ztype != ZONEFS_ZTYPE_SEQ)
> +		return -EPERM;
> +
> +	if (!isize)
> +		op = REQ_OP_ZONE_RESET;
> +	else if (isize == zi->i_max_size)
> +		op = REQ_OP_ZONE_FINISH;
> +	else
> +		return -EPERM;
> +
> +	inode_dio_wait(inode);
> +
> +	/* Serialize against page faults */
> +	down_write(&zi->i_mmap_sem);
> +
> +	/* Serialize against zonefs_iomap_begin() */
> +	mutex_lock(&zi->i_truncate_mutex);
> +
> +	old_isize = i_size_read(inode);
> +	if (isize == old_isize)
> +		goto unlock;
> +
> +	ret = blkdev_zone_mgmt(inode->i_sb->s_bdev, op, zi->i_zsector,
> +			       zi->i_max_size >> SECTOR_SHIFT, GFP_NOFS);
> +	if (ret) {
> +		zonefs_err(inode->i_sb,
> +			   "Zone management operation at %llu failed %d",
> +			   zi->i_zsector, ret);
> +		goto unlock;
> +	}
> +
> +	zonefs_update_stats(inode, isize);
> +	truncate_setsize(inode, isize);
> +	zi->i_wpoffset = isize;
> +
> +unlock:
> +	mutex_unlock(&zi->i_truncate_mutex);
> +	up_write(&zi->i_mmap_sem);
> +
> +	return ret;
> +}
> +
> +static int zonefs_inode_setattr(struct dentry *dentry, struct iattr *iattr)
> +{
> +	struct inode *inode = d_inode(dentry);
> +	int ret;
> +
> +	ret = setattr_prepare(dentry, iattr);
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * Since files and directories cannot be created nor deleted, do not
> +	 * allow setting any write attributes on the zone types sub-directories.
> +	 */
> +	if ((iattr->ia_valid & ATTR_MODE) && S_ISDIR(inode->i_mode) &&
> +	    (iattr->ia_mode & 0222))
> +		return -EPERM;
> +
> +	if (((iattr->ia_valid & ATTR_UID) &&
> +	     !uid_eq(iattr->ia_uid, inode->i_uid)) ||
> +	    ((iattr->ia_valid & ATTR_GID) &&
> +	     !gid_eq(iattr->ia_gid, inode->i_gid))) {
> +		ret = dquot_transfer(inode, iattr);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (iattr->ia_valid & ATTR_SIZE) {
> +		ret = zonefs_file_truncate(inode, iattr->ia_size);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	setattr_copy(inode, iattr);
> +
> +	return 0;
> +}
> +
> +static const struct inode_operations zonefs_file_inode_operations = {
> +	.setattr	= zonefs_inode_setattr,
> +};
> +
> +static int zonefs_file_fsync(struct file *file, loff_t start, loff_t end,
> +			     int datasync)
> +{
> +	struct inode *inode = file_inode(file);
> +	int ret = 0;
> +
> +	/*
> +	 * Since only direct writes are allowed in sequential files, page cache
> +	 * flush is needed only for conventional zone files.
> +	 */
> +	if (ZONEFS_I(inode)->i_ztype == ZONEFS_ZTYPE_CNV) {
> +		ret = file_write_and_wait_range(file, start, end);
> +		if (ret)
> +			return ret;
> +		ret = file_check_and_advance_wb_err(file);
> +	}
> +
> +	if (ret == 0)
> +		ret = blkdev_issue_flush(inode->i_sb->s_bdev, GFP_KERNEL, NULL);
> +
> +	return ret;
> +}
> +
> +static vm_fault_t zonefs_filemap_fault(struct vm_fault *vmf)
> +{
> +	struct zonefs_inode_info *zi = ZONEFS_I(file_inode(vmf->vma->vm_file));
> +	vm_fault_t ret;
> +
> +	down_read(&zi->i_mmap_sem);
> +	ret = filemap_fault(vmf);
> +	up_read(&zi->i_mmap_sem);
> +
> +	return ret;
> +}
> +
> +static vm_fault_t zonefs_filemap_page_mkwrite(struct vm_fault *vmf)
> +{
> +	struct inode *inode = file_inode(vmf->vma->vm_file);
> +	struct zonefs_inode_info *zi = ZONEFS_I(inode);
> +	vm_fault_t ret;
> +
> +	/*
> +	 * Sanity check: only conventional zone files can have shared
> +	 * writeable mappings.
> +	 */
> +	if (WARN_ON_ONCE(zi->i_ztype != ZONEFS_ZTYPE_CNV))
> +		return VM_FAULT_NOPAGE;
> +
> +	sb_start_pagefault(inode->i_sb);
> +	file_update_time(vmf->vma->vm_file);
> +
> +	/* Serialize against truncates */
> +	down_read(&zi->i_mmap_sem);
> +	ret = iomap_page_mkwrite(vmf, &zonefs_iomap_ops);
> +	up_read(&zi->i_mmap_sem);
> +
> +	sb_end_pagefault(inode->i_sb);
> +	return ret;
> +}
> +
> +static const struct vm_operations_struct zonefs_file_vm_ops = {
> +	.fault		= zonefs_filemap_fault,
> +	.map_pages	= filemap_map_pages,
> +	.page_mkwrite	= zonefs_filemap_page_mkwrite,
> +};
> +
> +static int zonefs_file_mmap(struct file *file, struct vm_area_struct *vma)
> +{
> +	/*
> +	 * Conventional zones accept random writes, so their files can support
> +	 * shared writable mappings. For sequential zone files, only read
> +	 * mappings are possible since there are no guarantees for write
> +	 * ordering with msync() and page cache writeback.
> +	 */
> +	if (ZONEFS_I(file_inode(file))->i_ztype == ZONEFS_ZTYPE_SEQ &&
> +	    (vma->vm_flags & VM_SHARED) && (vma->vm_flags & VM_MAYWRITE))
> +		return -EINVAL;
> +
> +	file_accessed(file);
> +	vma->vm_ops = &zonefs_file_vm_ops;
> +
> +	return 0;
> +}
> +
> +static loff_t zonefs_file_llseek(struct file *file, loff_t offset, int whence)
> +{
> +	loff_t isize = i_size_read(file_inode(file));
> +
> +	/*
> +	 * Seeks are limited to below the zone size for conventional zones
> +	 * and below the zone write pointer for sequential zones. In both
> +	 * cases, this limit is the inode size.
> +	 */
> +	return generic_file_llseek_size(file, offset, whence, isize, isize);
> +}
> +
> +/*
> + * Update a file inode access permissions based on the file zone condition.
> + */
> +static void zonefs_update_file_perm(struct inode *inode, struct blk_zone *zone)
> +{
> +	if (zone->cond == BLK_ZONE_COND_OFFLINE) {
> +		/*
> +		 * Dead zone: make the inode immutable, disable all accesses
> +		 * and set the file size to 0 (zone wp set to zone start).
> +		 */
> +		inode->i_flags |= S_IMMUTABLE;

One annoying nit about setting S_IMMUTABLE: the generic vfs write
routines do not check S_IMMUTABLE, which means that zonefs will have to
do that on its own.

I tried to fix it last year, but there were complaints that it could
break existing workloads (open O_TMPFILE for write, mark it immutable,
link it into the filesystem, continue to write it since you're the only
writer...)

> +		inode->i_mode &= ~0777;
> +		zone->wp = zone->start;
> +	} else if (zone->cond == BLK_ZONE_COND_READONLY) {
> +		/* Do not allow writes in read-only zones */
> +		inode->i_flags |= S_IMMUTABLE;
> +		inode->i_mode &= ~0222;
> +	}
> +}
> +
> +struct zonefs_ioerr_data {
> +	struct inode	*inode;
> +	bool		write;
> +};
> +
> +static int zonefs_io_err_cb(struct blk_zone *zone, unsigned int idx, void *data)
> +{
> +	struct zonefs_ioerr_data *ioerr = data;
> +	struct inode *inode = ioerr->inode;
> +	struct zonefs_inode_info *zi = ZONEFS_I(inode);
> +	struct super_block *sb = inode->i_sb;
> +	loff_t isize, wp_ofst;
> +
> +	/*
> +	 * The condition of the zone may have change. Fix the file access
> +	 * permissions if necessary.
> +	 */
> +	zonefs_update_file_perm(inode, zone);
> +
> +	/*
> +	 * There is no write pointer on conventional zones and read operations
> +	 * do not change a zone write pointer. So there is nothing more to do
> +	 * for these two cases.
> +	 */
> +	if (zi->i_ztype == ZONEFS_ZTYPE_CNV || !ioerr->write)
> +		return 0;
> +
> +	/*
> +	 * For sequential zones write, make sure that the zone write pointer
> +	 * position is as expected, that is, in sync with the inode size.
> +	 */
> +	wp_ofst = (zone->wp - zone->start) << SECTOR_SHIFT;
> +	zi->i_wpoffset = wp_ofst;
> +	isize = i_size_read(inode);
> +
> +	if (isize == wp_ofst)
/> +		return 0;
> +
> +	/*
> +	 * The inode size and the zone write pointer are not in sync.
> +	 * If the inode size is below the zone write pointer, then data was

I'm a little confused about what events these states reflect.

"inode size is below the zone wp" -- let's say we have a partially
written sequential zone:

    isize
----v---------------
DDDDD
----^---------------
    WP

Then we tried to write to the end of the sequential zone:

    isize
----v---------------
DDDDDWWWW
----^---------------
    WP

Then an error happens so we didn't update the isize, and now we see that
the write pointer is beyond isize (pretend the write failed to the '?'
area):

    isize
----v---------------
DDDDDD?DD
--------^-----------
        WP

So if we increase isize to match the WP, what happens when userspace
tries to read the question-mark area?  Do they get read errors?  Stale
contents?

Or am I misunderstanding SMR firmware, and the drive only advances the
write pointer once it has written a block?  i.e. if a write fails in
the middle, the drive ends up in this state, not the one I drew above:

    isize
----v---------------
DDDDDD?
-----^--------------
     WP

In which case it would be fine to push isize up to the write pointer?

Aha, you /did/ say exactly this in the v8 thread.

> +	 * writen at the end of the file. This can happen in the case of a
> +	 * partial failure of a large multi-bio DIO. No data is lost. Simply fix
> +	 * the inode size to reflect the partial write.
> +	 * On the other hand, if the inode size is over the zone write pointer,
> +	 * then there was an external corruption, e.g. an application reset the
> +	 * file zone directly, or the device has a problem.

So I guess this case "isize is greater than WP" means we start with
this appending write to what we think is the end of the zone:

    isize
----v---------------
DDDDDWWWW
--------------------

(The position of the WP is irrelevant here)

Then we get a disk error, so we query the WP and discover that it's
actually below isize:

    isize
----v---------------
DDDDDDD
-^------------------
 WP

So now we conclude that either the drive is broken or someone is messing
with the zones behind our back, so we'd rather just shut down and let
the sysadmin figure it out?  Because while we could truncate the zone
file down to the WP, this is a sign that something could be seriously
broken?

(Oh, you said this in the v8 thread too.)

> +	 */
> +	zonefs_warn(sb, "inode %lu: size %lld should be %lld\n",
> +		    inode->i_ino, isize, wp_ofst);
> +	if (isize > wp_ofst) {
> +		struct zonefs_sb_info *sbi = ZONEFS_SB(sb);
> +
> +		if ((sbi->s_mount_opts & ZONEFS_MNTOPT_ERRORS_RO) &&

Mount options?  Hey, wait a minute, this didn't exist in v8...

> +		    !sb_rdonly(sb)) {
> +			zonefs_warn(sb,
> +				"Zone %lu corruption detected, remounting fs read-only\n",
> +				inode->i_ino);
> +			sb->s_flags |= SB_RDONLY;
> +			return 0;
> +		} else if (sbi->s_mount_opts & ZONEFS_MNTOPT_ERRORS_CONT) {
> +			zonefs_warn(sb,
> +				"Zone %lu corruption detected, continuing\n",
> +				inode->i_ino);

I'm frankly not sure errors=continue makes sense for a filesystem.  It
exists for ext* as a crutch for the root fs to help users stumble
towards /sbin/reboot and a full fsck afterwards.

Also wondering if you should have an errors=zone-ro that will set
S_IMMUTABLE on the zone file?  That would enable the intact zones to
keep operating.

(Or I guess if you really want a "continue" mode you could truncate the
zone...)

> +		} else if (sbi->s_mount_opts & ZONEFS_MNTOPT_ERRORS_PANIC) {

I don't think it's a good idea to crash the entire kernel on zone
corruption.

> +			zonefs_panic(sb,
> +				"Zone %lu corruption detected\n",
> +				inode->i_ino);
> +		}
> +	}
> +
> +	zonefs_update_stats(inode, wp_ofst);
> +	i_size_write(inode, wp_ofst);
> +
> +	return 0;
> +}
> +
> +/*
> + * When an IO error occurs, check the target zone to see if there is a change
> + * in the zone condition (e.g. offline or read-only). For a failed write to a
> + * sequential zone, the zone write pointer position must also be checked to
> + * eventually correct the file size and zonefs inode write pointer offset
> + * (which can be out of sync with the drive due to partial write failures).
> + */
> +static void zonefs_io_error(struct inode *inode, bool write)
> +{
> +	struct zonefs_inode_info *zi = ZONEFS_I(inode);
> +	struct super_block *sb = inode->i_sb;
> +	struct zonefs_sb_info *sbi = ZONEFS_SB(sb);
> +	unsigned int noio_flag;
> +	unsigned int nr_zones =
> +		zi->i_max_size >> (sbi->s_zone_sectors_shift + SECTOR_SHIFT);
> +	struct zonefs_ioerr_data ioerr = {
> +		.inode = inode,
> +		.write = write
> +	};
> +	int ret;
> +
> +	mutex_lock(&zi->i_truncate_mutex);
> +
> +	/*
> +	 * Memory allocations in blkdev_report_zones() can trigger a memory
> +	 * reclaim which may in turn cause a recursion into zonefs as well as
> +	 * BIO allocations for the same device. The former case may end up in
> +	 * a deadlock on the inode truncate mutex, while the latter may prevent
> +	 * forward progress with BIO allocations as we are potentially still
> +	 * holding the failed BIO. Executing the report zones under GFP_NOIO
> +	 * avoids both problems.
> +	 */
> +	noio_flag = memalloc_noio_save();

Don't you still need memalloc_nofs_ here too?

> +	ret = blkdev_report_zones(sb->s_bdev, zi->i_zsector, nr_zones,
> +				  zonefs_io_err_cb, &ioerr);
> +	if (ret != nr_zones)
> +		zonefs_err(sb, "Get inode %lu zone information failed %d\n",
> +			   inode->i_ino, ret);
> +	memalloc_noio_restore(noio_flag);
> +
> +	mutex_unlock(&zi->i_truncate_mutex);
> +}
> +
> +static int zonefs_file_write_dio_end_io(struct kiocb *iocb, ssize_t size,
> +					int error, unsigned int flags)
> +{
> +	struct inode *inode = file_inode(iocb->ki_filp);
> +	struct zonefs_inode_info *zi = ZONEFS_I(inode);
> +
> +	if (error) {
> +		zonefs_io_error(inode, true);
> +		return error;
> +	}
> +
> +	if (size && zi->i_ztype != ZONEFS_ZTYPE_CNV) {
> +		mutex_lock(&zi->i_truncate_mutex);
> +		if (i_size_read(inode) < iocb->ki_pos + size) {
> +			zonefs_update_stats(inode, iocb->ki_pos + size);
> +			i_size_write(inode, iocb->ki_pos + size);
> +		}
> +		mutex_unlock(&zi->i_truncate_mutex);
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct iomap_dio_ops zonefs_write_dio_ops = {
> +	.end_io			= zonefs_file_write_dio_end_io,
> +};
> +
> +/*
> + * Handle direct writes. For sequential zone files, this is the only possible
> + * write path. For these files, check that the user is issuing writes
> + * sequentially from the end of the file. This code assumes that the block layer
> + * delivers write requests to the device in sequential order. This is always the
> + * case if a block IO scheduler implementing the ELEVATOR_F_ZBD_SEQ_WRITE

Is there any way for zonefs to detect that it's talking to an io
scheduler that doesn't support ZBD_SEQ_WRITE and react accordingly (log
message, refuse to mount, etc.)?

> + * elevator feature is being used (e.g. mq-deadline). The block layer always
> + * automatically select such an elevator for zoned block devices during the
> + * device initialization.

Or is the case that the block layer knows when it's dealing with a zoned
block device and will not allow the assignment of an ioscheduler that
does not support ZBD_SEQ_WRITE?

> + */
> +static ssize_t zonefs_file_dio_write(struct kiocb *iocb, struct iov_iter *from)
> +{
> +	struct inode *inode = file_inode(iocb->ki_filp);
> +	struct zonefs_sb_info *sbi = ZONEFS_SB(inode->i_sb);
> +	struct zonefs_inode_info *zi = ZONEFS_I(inode);
> +	size_t count;
> +	ssize_t ret;
> +
> +	/*
> +	 * For async direct IOs to sequential zone files, ignore IOCB_NOWAIT
> +	 * as this can cause write reordering (e.g. the first aio gets EAGAIN
> +	 * on the inode lock but the second goes through but is now unaligned).
> +	 */
> +	if (zi->i_ztype == ZONEFS_ZTYPE_SEQ && !is_sync_kiocb(iocb)
> +	    && (iocb->ki_flags & IOCB_NOWAIT))
> +		iocb->ki_flags &= ~IOCB_NOWAIT;
> +
> +	if (iocb->ki_flags & IOCB_NOWAIT) {
> +		if (!inode_trylock(inode))
> +			return -EAGAIN;
> +	} else {
> +		inode_lock(inode);
> +	}
> +
> +	ret = generic_write_checks(iocb, from);
> +	if (ret <= 0)
> +		goto out;
> +
> +	iov_iter_truncate(from, zi->i_max_size - iocb->ki_pos);
> +	count = iov_iter_count(from);
> +
> +	if ((iocb->ki_pos | count) & sbi->s_blocksize_mask) {
> +		ret = -EINVAL;
> +		goto out;
> +	}
> +
> +	/* Enforce sequential writes (append only) in sequential zones */
> +	mutex_lock(&zi->i_truncate_mutex);
> +	if (zi->i_ztype == ZONEFS_ZTYPE_SEQ && iocb->ki_pos != zi->i_wpoffset) {
> +		zonefs_err(inode->i_sb,
> +			   "Unaligned direct write at %llu + %zu (wp %llu)\n",
> +			   iocb->ki_pos, count,
> +			   zi->i_wpoffset);
> +		mutex_unlock(&zi->i_truncate_mutex);
> +		ret = -EINVAL;
> +		goto out;
> +	}
> +	mutex_unlock(&zi->i_truncate_mutex);
> +
> +	ret = iomap_dio_rw(iocb, from, &zonefs_iomap_ops,
> +			   &zonefs_write_dio_ops, is_sync_kiocb(iocb));
> +	if (zi->i_ztype == ZONEFS_ZTYPE_SEQ &&
> +	    (ret > 0 || ret == -EIOCBQUEUED)) {
> +		if (ret > 0)
> +			count = ret;
> +		mutex_lock(&zi->i_truncate_mutex);
> +		zi->i_wpoffset += count;
> +		mutex_unlock(&zi->i_truncate_mutex);
> +	}
> +
> +out:
> +	inode_unlock(inode);
> +
> +	return ret;
> +}
> +
> +static ssize_t zonefs_file_buffered_write(struct kiocb *iocb,
> +					  struct iov_iter *from)
> +{
> +	struct inode *inode = file_inode(iocb->ki_filp);
> +	struct zonefs_inode_info *zi = ZONEFS_I(inode);
> +	ssize_t ret;
> +
> +	/*
> +	 * Direct IO writes are mandatory for sequential zones so that the
> +	 * write IO order is preserved.
> +	 */
> +	if (zi->i_ztype != ZONEFS_ZTYPE_CNV)
> +		return -EIO;
> +
> +	if (iocb->ki_flags & IOCB_NOWAIT) {
> +		if (!inode_trylock(inode))
> +			return -EAGAIN;
> +	} else {
> +		inode_lock(inode);
> +	}
> +
> +	ret = generic_write_checks(iocb, from);
> +	if (ret <= 0)
> +		goto out;
> +
> +	iov_iter_truncate(from, zi->i_max_size - iocb->ki_pos);
> +
> +	ret = iomap_file_buffered_write(iocb, from, &zonefs_iomap_ops);
> +	if (ret > 0)
> +		iocb->ki_pos += ret;
> +	else if (ret == -EIO)
> +		zonefs_io_error(inode, false);
> +
> +out:
> +	inode_unlock(inode);
> +	if (ret > 0)
> +		ret = generic_write_sync(iocb, ret);
> +
> +	return ret;
> +}
> +
> +static ssize_t zonefs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
> +{
> +	struct inode *inode = file_inode(iocb->ki_filp);
> +
> +	/* Write operations beyond the zone size are not allowed */
> +	if (iocb->ki_pos >= ZONEFS_I(inode)->i_max_size)
> +		return -EFBIG;

This needs a check for IS_IMMUTABLE so that userspace can't write to
zones which zonefs has decided are no longer writable, even if the
program has a writeable file descriptor.

> +
> +	if (iocb->ki_flags & IOCB_DIRECT)
> +		return zonefs_file_dio_write(iocb, from);
> +
> +	return zonefs_file_buffered_write(iocb, from);
> +}
> +
> +static int zonefs_file_read_dio_end_io(struct kiocb *iocb, ssize_t size,
> +				       int error, unsigned int flags)
> +{
> +	if (error) {
> +		zonefs_io_error(file_inode(iocb->ki_filp), false);
> +		return error;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct iomap_dio_ops zonefs_read_dio_ops = {
> +	.end_io			= zonefs_file_read_dio_end_io,
> +};
> +
> +static ssize_t zonefs_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
> +{
> +	struct inode *inode = file_inode(iocb->ki_filp);
> +	struct zonefs_sb_info *sbi = ZONEFS_SB(inode->i_sb);
> +	struct zonefs_inode_info *zi = ZONEFS_I(inode);
> +	loff_t isize;
> +	ssize_t ret;
> +
> +	if (iocb->ki_pos >= zi->i_max_size)
> +		return 0;
> +
> +	if (iocb->ki_flags & IOCB_NOWAIT) {
> +		if (!inode_trylock_shared(inode))
> +			return -EAGAIN;
> +	} else {
> +		inode_lock_shared(inode);
> +	}
> +
> +	/* Limit read operations to written data */
> +	mutex_lock(&zi->i_truncate_mutex);
> +	isize = i_size_read(inode);
> +	if (iocb->ki_pos >= isize) {
> +		mutex_unlock(&zi->i_truncate_mutex);
> +		ret = 0;
> +		goto out;
> +	}
> +	iov_iter_truncate(to, isize - iocb->ki_pos);
> +	mutex_unlock(&zi->i_truncate_mutex);
> +
> +	if (iocb->ki_flags & IOCB_DIRECT) {
> +		size_t count = iov_iter_count(to);
> +
> +		if ((iocb->ki_pos | count) & sbi->s_blocksize_mask) {
> +			ret = -EINVAL;
> +			goto out;
> +		}
> +		file_accessed(iocb->ki_filp);
> +		ret = iomap_dio_rw(iocb, to, &zonefs_iomap_ops,
> +				   &zonefs_read_dio_ops, is_sync_kiocb(iocb));
> +	} else {
> +		ret = generic_file_read_iter(iocb, to);
> +		if (ret == -EIO)
> +			zonefs_io_error(inode, false);
> +	}
> +
> +out:
> +	inode_unlock_shared(inode);
> +
> +	return ret;
> +}
> +
> +static const struct file_operations zonefs_file_operations = {
> +	.open		= generic_file_open,
> +	.fsync		= zonefs_file_fsync,
> +	.mmap		= zonefs_file_mmap,
> +	.llseek		= zonefs_file_llseek,
> +	.read_iter	= zonefs_file_read_iter,
> +	.write_iter	= zonefs_file_write_iter,
> +	.splice_read	= generic_file_splice_read,
> +	.splice_write	= iter_file_splice_write,
> +	.iopoll		= iomap_dio_iopoll,
> +};
> +
> +static struct kmem_cache *zonefs_inode_cachep;
> +
> +static struct inode *zonefs_alloc_inode(struct super_block *sb)
> +{
> +	struct zonefs_inode_info *zi;
> +
> +	zi = kmem_cache_alloc(zonefs_inode_cachep, GFP_KERNEL);
> +	if (!zi)
> +		return NULL;
> +
> +	inode_init_once(&zi->i_vnode);
> +	mutex_init(&zi->i_truncate_mutex);
> +	init_rwsem(&zi->i_mmap_sem);
> +
> +	return &zi->i_vnode;
> +}
> +
> +static void zonefs_free_inode(struct inode *inode)
> +{
> +	kmem_cache_free(zonefs_inode_cachep, ZONEFS_I(inode));
> +}
> +
> +/*
> + * File system stat.
> + */
> +static int zonefs_statfs(struct dentry *dentry, struct kstatfs *buf)
> +{
> +	struct super_block *sb = dentry->d_sb;
> +	struct zonefs_sb_info *sbi = ZONEFS_SB(sb);
> +	enum zonefs_ztype t;
> +	u64 fsid;
> +
> +	buf->f_type = ZONEFS_MAGIC;
> +	buf->f_bsize = sb->s_blocksize;
> +	buf->f_namelen = ZONEFS_NAME_MAX;
> +
> +	spin_lock(&sbi->s_lock);
> +
> +	buf->f_blocks = sbi->s_blocks;
> +	if (WARN_ON(sbi->s_used_blocks > sbi->s_blocks))
> +		buf->f_bfree = 0;
> +	else
> +		buf->f_bfree = buf->f_blocks - sbi->s_used_blocks;
> +	buf->f_bavail = buf->f_bfree;
> +
> +	for (t = 0; t < ZONEFS_ZTYPE_MAX; t++) {
> +		if (sbi->s_nr_files[t])
> +			buf->f_files += sbi->s_nr_files[t] + 1;
> +	}
> +	buf->f_ffree = 0;
> +
> +	spin_unlock(&sbi->s_lock);
> +
> +	fsid = le64_to_cpup((void *)sbi->s_uuid.b) ^
> +		le64_to_cpup((void *)sbi->s_uuid.b + sizeof(u64));
> +	buf->f_fsid.val[0] = (u32)fsid;
> +	buf->f_fsid.val[1] = (u32)(fsid >> 32);
> +
> +	return 0;
> +}
> +
> +enum {
> +	Opt_errors_cont, Opt_errors_panic, Opt_errors_ro,
> +	Opt_err,
> +};
> +
> +static const match_table_t tokens = {
> +	{ Opt_errors_cont,	"errors=continue"},
> +	{ Opt_errors_panic,	"errors=panic"},
> +	{ Opt_errors_ro,	"errors=remount-ro"},
> +	{ Opt_err,		NULL}
> +};
> +
> +static int zonefs_parse_options(struct super_block *sb, char *options)
> +{
> +	struct zonefs_sb_info *sbi = ZONEFS_SB(sb);
> +	substring_t args[MAX_OPT_ARGS];
> +	char *p;
> +
> +	if (!options)
> +		return 0;
> +
> +	while ((p = strsep(&options, ",")) != NULL) {
> +		int token;
> +
> +		if (!*p)
> +			continue;
> +
> +		token = match_token(p, tokens, args);
> +		switch (token) {
> +		case Opt_errors_cont:
> +			sbi->s_mount_opts &= ~ZONEFS_MNTOPT_ERRORS_RO;
> +			sbi->s_mount_opts &= ~ZONEFS_MNTOPT_ERRORS_PANIC;
> +			sbi->s_mount_opts |= ZONEFS_MNTOPT_ERRORS_CONT;
> +			break;
> +		case Opt_errors_ro:
> +			sbi->s_mount_opts &= ~ZONEFS_MNTOPT_ERRORS_CONT;
> +			sbi->s_mount_opts &= ~ZONEFS_MNTOPT_ERRORS_PANIC;
> +			sbi->s_mount_opts |= ZONEFS_MNTOPT_ERRORS_RO;
> +			break;
> +		case Opt_errors_panic:
> +			sbi->s_mount_opts &= ~ZONEFS_MNTOPT_ERRORS_RO;
> +			sbi->s_mount_opts &= ~ZONEFS_MNTOPT_ERRORS_CONT;
> +			sbi->s_mount_opts |= ZONEFS_MNTOPT_ERRORS_PANIC;
> +			break;
> +		default:
> +			return -EINVAL;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int zonefs_show_options(struct seq_file *seq, struct dentry *root)
> +{
> +	struct zonefs_sb_info *sbi = ZONEFS_SB(root->d_sb);
> +
> +	if (sbi->s_mount_opts & ZONEFS_MNTOPT_ERRORS_CONT)
> +		seq_puts(seq, ",errors=continue");
> +	if (sbi->s_mount_opts & ZONEFS_MNTOPT_ERRORS_RO)
> +		seq_puts(seq, ",errors=ro");
> +	if (sbi->s_mount_opts & ZONEFS_MNTOPT_ERRORS_PANIC)
> +		seq_puts(seq, ",errors=panic");
> +
> +	return 0;
> +}
> +
> +static int zonefs_remount(struct super_block *sb, int *flags, char *data)
> +{
> +	sync_filesystem(sb);
> +
> +	return zonefs_parse_options(sb, data);
> +}
> +
> +static const struct super_operations zonefs_sops = {
> +	.alloc_inode	= zonefs_alloc_inode,
> +	.free_inode	= zonefs_free_inode,
> +	.statfs		= zonefs_statfs,
> +	.remount_fs	= zonefs_remount,
> +	.show_options	= zonefs_show_options,
> +};
> +
> +static const struct inode_operations zonefs_dir_inode_operations = {
> +	.lookup		= simple_lookup,
> +	.setattr	= zonefs_inode_setattr,
> +};
> +
> +static void zonefs_init_dir_inode(struct inode *parent, struct inode *inode,
> +				  enum zonefs_ztype type)
> +{
> +	struct super_block *sb = parent->i_sb;
> +
> +	inode->i_ino = blkdev_nr_zones(sb->s_bdev->bd_disk) + type + 1;
> +	inode_init_owner(inode, parent, S_IFDIR | 0555);
> +	inode->i_op = &zonefs_dir_inode_operations;
> +	inode->i_fop = &simple_dir_operations;
> +	set_nlink(inode, 2);
> +	inc_nlink(parent);
> +}
> +
> +static void zonefs_init_file_inode(struct inode *inode, struct blk_zone *zone,
> +				   enum zonefs_ztype type)
> +{
> +	struct super_block *sb = inode->i_sb;
> +	struct zonefs_sb_info *sbi = ZONEFS_SB(sb);
> +	struct zonefs_inode_info *zi = ZONEFS_I(inode);
> +
> +	inode->i_ino = zone->start >> sbi->s_zone_sectors_shift;
> +	inode->i_mode = S_IFREG | sbi->s_perm;
> +	zonefs_update_file_perm(inode, zone);
> +
> +	zi->i_ztype = type;
> +	zi->i_zsector = zone->start;
> +	zi->i_max_size = min_t(loff_t, MAX_LFS_FILESIZE,
> +			       zone->len << SECTOR_SHIFT);
> +	if (zi->i_ztype == ZONEFS_ZTYPE_CNV)
> +		zi->i_wpoffset = zi->i_max_size;
> +	else
> +		zi->i_wpoffset = (zone->wp - zone->start) << SECTOR_SHIFT;
> +
> +	inode->i_uid = sbi->s_uid;
> +	inode->i_gid = sbi->s_gid;
> +	inode->i_size = zi->i_wpoffset;
> +	inode->i_blocks = zone->len;
> +
> +	inode->i_op = &zonefs_file_inode_operations;
> +	inode->i_fop = &zonefs_file_operations;
> +	inode->i_mapping->a_ops = &zonefs_file_aops;
> +
> +	sb->s_maxbytes = max(zi->i_max_size, sb->s_maxbytes);
> +	sbi->s_blocks += zi->i_max_size >> sb->s_blocksize_bits;
> +	sbi->s_used_blocks += zi->i_wpoffset >> sb->s_blocksize_bits;
> +}
> +
> +static struct dentry *zonefs_create_inode(struct dentry *parent,
> +					const char *name, struct blk_zone *zone,
> +					enum zonefs_ztype type)
> +{
> +	struct inode *dir = d_inode(parent);
> +	struct dentry *dentry;
> +	struct inode *inode;
> +
> +	dentry = d_alloc_name(parent, name);
> +	if (!dentry)
> +		return NULL;
> +
> +	inode = new_inode(parent->d_sb);
> +	if (!inode)
> +		goto out;
> +
> +	inode->i_ctime = inode->i_mtime = inode->i_atime = dir->i_ctime;
> +	if (zone)
> +		zonefs_init_file_inode(inode, zone, type);
> +	else
> +		zonefs_init_dir_inode(dir, inode, type);
> +	d_add(dentry, inode);
> +	dir->i_size++;
> +
> +	return dentry;
> +
> +out:
> +	dput(dentry);
> +
> +	return NULL;
> +}
> +
> +static char *zgroups_name[ZONEFS_ZTYPE_MAX] = { "cnv", "seq" };
> +
> +struct zonefs_zone_data {
> +	struct super_block *sb;
> +	unsigned int nr_zones[ZONEFS_ZTYPE_MAX];
> +	struct blk_zone *zones;
> +};
> +
> +/*
> + * Create a zone group and populate it with zone files.
> + */
> +static int zonefs_create_zgroup(struct zonefs_zone_data *zd,
> +				enum zonefs_ztype type)
> +{
> +	struct super_block *sb = zd->sb;
> +	struct zonefs_sb_info *sbi = ZONEFS_SB(sb);
> +	struct blk_zone *zone, *next, *end;
> +	char name[ZONEFS_NAME_MAX];
> +	struct dentry *dir;
> +	unsigned int n = 0;
> +
> +	/* If the group is empty, there is nothing to do */
> +	if (!zd->nr_zones[type])
> +		return 0;
> +
> +	dir = zonefs_create_inode(sb->s_root, zgroups_name[type], NULL, type);
> +	if (!dir)
> +		return -ENOMEM;
> +
> +	/*
> +	 * The first zone contains the super block: skip it.
> +	 */
> +	end = zd->zones + blkdev_nr_zones(sb->s_bdev->bd_disk);
> +	for (zone = &zd->zones[1]; zone < end; zone = next) {
> +
> +		next = zone + 1;
> +		if (zonefs_zone_type(zone) != type)
> +			continue;
> +
> +		/*
> +		 * For conventional zones, contiguous zones can be aggregated
> +		 * together to form larger files.
> +		 * Note that this overwrites the length of the first zone of
> +		 * the set of contiguous zones aggregated together.
> +		 * Only zones with the same condition can be agreggated so that
> +		 * offline zones are excluded and readonly zones are aggregated
> +		 * together into a read only file.
> +		 */
> +		if (type == ZONEFS_ZTYPE_CNV &&
> +		    (sbi->s_features & ZONEFS_F_AGGRCNV)) {
> +			for (; next < end; next++) {
> +				if (zonefs_zone_type(next) != type ||
> +				    next->cond != zone->cond)
> +					break;
> +				zone->len += next->len;
> +			}
> +		}
> +
> +		/*
> +		 * Use the file number within its group as file name.
> +		 */
> +		snprintf(name, ZONEFS_NAME_MAX - 1, "%u", n);
> +		if (!zonefs_create_inode(dir, name, zone, type))
> +			return -ENOMEM;
> +
> +		n++;
> +	}
> +
> +	zonefs_info(sb, "Zone group \"%s\" has %u file%s\n",
> +		    zgroups_name[type], n, n > 1 ? "s" : "");
> +
> +	sbi->s_nr_files[type] = n;
> +
> +	return 0;
> +}
> +
> +static int zonefs_get_zone_info_cb(struct blk_zone *zone, unsigned int idx,
> +				   void *data)
> +{
> +	struct zonefs_zone_data *zd = data;
> +
> +	/*
> +	 * Count the number of usable zones: the first zone at index 0 contains
> +	 * the super block and is ignored.
> +	 */
> +	switch (zone->type) {
> +	case BLK_ZONE_TYPE_CONVENTIONAL:
> +		zone->wp = zone->start + zone->len;
> +		if (idx)
> +			zd->nr_zones[ZONEFS_ZTYPE_CNV]++;
> +		break;
> +	case BLK_ZONE_TYPE_SEQWRITE_REQ:
> +	case BLK_ZONE_TYPE_SEQWRITE_PREF:
> +		if (idx)
> +			zd->nr_zones[ZONEFS_ZTYPE_SEQ]++;
> +		break;
> +	default:
> +		zonefs_err(zd->sb, "Unsupported zone type 0x%x\n",
> +			   zone->type);
> +		return -EIO;
> +	}
> +
> +	memcpy(&zd->zones[idx], zone, sizeof(struct blk_zone));
> +
> +	return 0;
> +}
> +
> +static int zonefs_get_zone_info(struct zonefs_zone_data *zd)
> +{
> +	struct block_device *bdev = zd->sb->s_bdev;
> +	int ret;
> +
> +	zd->zones = kvcalloc(blkdev_nr_zones(bdev->bd_disk),
> +			     sizeof(struct blk_zone), GFP_KERNEL);
> +	if (!zd->zones)
> +		return -ENOMEM;
> +
> +	/* Get zones information */
> +	ret = blkdev_report_zones(bdev, 0, BLK_ALL_ZONES,
> +				  zonefs_get_zone_info_cb, zd);
> +	if (ret < 0) {
> +		zonefs_err(zd->sb, "Zone report failed %d\n", ret);
> +		return ret;
> +	}
> +
> +	if (ret != blkdev_nr_zones(bdev->bd_disk)) {
> +		zonefs_err(zd->sb, "Invalid zone report (%d/%u zones)\n",
> +			   ret, blkdev_nr_zones(bdev->bd_disk));
> +		return -EIO;
> +	}
> +
> +	return 0;
> +}
> +
> +static inline void zonefs_cleanup_zone_info(struct zonefs_zone_data *zd)
> +{
> +	kvfree(zd->zones);
> +}
> +
> +/*
> + * Read super block information from the device.
> + */
> +static int zonefs_read_super(struct super_block *sb)
> +{
> +	struct zonefs_sb_info *sbi = ZONEFS_SB(sb);
> +	struct zonefs_super *super;
> +	u32 crc, stored_crc;
> +	struct page *page;
> +	struct bio_vec bio_vec;
> +	struct bio bio;
> +	int ret;
> +
> +	page = alloc_page(GFP_KERNEL);
> +	if (!page)
> +		return -ENOMEM;
> +
> +	bio_init(&bio, &bio_vec, 1);
> +	bio.bi_iter.bi_sector = 0;
> +	bio.bi_opf = REQ_OP_READ;
> +	bio_set_dev(&bio, sb->s_bdev);
> +	bio_add_page(&bio, page, PAGE_SIZE, 0);
> +
> +	ret = submit_bio_wait(&bio);
> +	if (ret)
> +		goto out_free;
> +
> +	super = kmap(page);
> +
> +	ret = -EINVAL;
> +	if (le32_to_cpu(super->s_magic) != ZONEFS_MAGIC)
> +		goto out;
> +
> +	stored_crc = le32_to_cpu(super->s_crc);
> +	super->s_crc = 0;
> +	crc = crc32(~0U, (unsigned char *)super, sizeof(struct zonefs_super));
> +	if (crc != stored_crc) {
> +		zonefs_err(sb, "Invalid checksum (Expected 0x%08x, got 0x%08x)",
> +			   crc, stored_crc);
> +		goto out;
> +	}
> +
> +	sbi->s_features = le64_to_cpu(super->s_features);
> +	if (sbi->s_features & ~ZONEFS_F_DEFINED_FEATURES) {
> +		zonefs_err(sb, "Unknown features set 0x%llx\n",
> +			   sbi->s_features);
> +		goto out;
> +	}
> +
> +	if (sbi->s_features & ZONEFS_F_UID) {
> +		sbi->s_uid = make_kuid(current_user_ns(),
> +				       le32_to_cpu(super->s_uid));
> +		if (!uid_valid(sbi->s_uid)) {
> +			zonefs_err(sb, "Invalid UID feature\n");
> +			goto out;
> +		}
> +	}
> +
> +	if (sbi->s_features & ZONEFS_F_GID) {
> +		sbi->s_gid = make_kgid(current_user_ns(),
> +				       le32_to_cpu(super->s_gid));
> +		if (!gid_valid(sbi->s_gid)) {
> +			zonefs_err(sb, "Invalid GID feature\n");
> +			goto out;
> +		}
> +	}
> +
> +	if (sbi->s_features & ZONEFS_F_PERM)
> +		sbi->s_perm = le32_to_cpu(super->s_perm);
> +
> +	if (memchr_inv(super->s_reserved, 0, sizeof(super->s_reserved))) {
> +		zonefs_err(sb, "Reserved area is being used\n");
> +		goto out;
> +	}
> +
> +	uuid_copy(&sbi->s_uuid, (uuid_t *)super->s_uuid);
> +	ret = 0;
> +
> +out:
> +	kunmap(page);
> +out_free:
> +	__free_page(page);
> +
> +	return ret;
> +}
> +
> +/*
> + * Check that the device is zoned. If it is, get the list of zones and create
> + * sub-directories and files according to the device zone configuration and
> + * format options.
> + */
> +static int zonefs_fill_super(struct super_block *sb, void *data, int silent)
> +{
> +	struct zonefs_zone_data zd;
> +	struct zonefs_sb_info *sbi;
> +	struct inode *inode;
> +	enum zonefs_ztype t;
> +	int ret;
> +
> +	if (!bdev_is_zoned(sb->s_bdev)) {
> +		zonefs_err(sb, "Not a zoned block device\n");
> +		return -EINVAL;
> +	}
> +
> +	/*
> +	 * Initialize super block information: the maximum file size is updated
> +	 * when the zone files are created so that the format option
> +	 * ZONEFS_F_AGGRCNV which increases the maximum file size of a file
> +	 * beyond the zone size is taken into account.
> +	 */
> +	sbi = kzalloc(sizeof(*sbi), GFP_KERNEL);
> +	if (!sbi)
> +		return -ENOMEM;
> +
> +	spin_lock_init(&sbi->s_lock);
> +	sb->s_fs_info = sbi;
> +	sb->s_magic = ZONEFS_MAGIC;
> +	sb->s_maxbytes = 0;
> +	sb->s_op = &zonefs_sops;
> +	sb->s_time_gran	= 1;
> +
> +	/*
> +	 * The block size is set to the device physical sector size to ensure
> +	 * that write operations on 512e devices (512B logical block and 4KB
> +	 * physical block) are always aligned to the device physical blocks,
> +	 * as mandated by the ZBC/ZAC specifications.
> +	 */
> +	sb_set_blocksize(sb, bdev_physical_block_size(sb->s_bdev));
> +	sbi->s_blocksize_mask = sb->s_blocksize - 1;
> +	sbi->s_zone_sectors_shift = ilog2(bdev_zone_sectors(sb->s_bdev));
> +	sbi->s_uid = GLOBAL_ROOT_UID;
> +	sbi->s_gid = GLOBAL_ROOT_GID;
> +	sbi->s_perm = 0640;
> +	sbi->s_mount_opts = ZONEFS_MNTOPT_ERRORS_RO;
> +
> +	ret = zonefs_read_super(sb);
> +	if (ret)
> +		return ret;
> +
> +	ret = zonefs_parse_options(sb, data);
> +	if (ret)
> +		return ret;
> +
> +	memset(&zd, 0, sizeof(struct zonefs_zone_data));
> +	zd.sb = sb;
> +	ret = zonefs_get_zone_info(&zd);
> +	if (ret)
> +		goto out;
> +

It might be a good idea to spit out an EXPERIMENTAL warning at mount
time for the first 6 months while you, uh, seek out advanced bleeding
edge testers to really give this code a thorough workout.

zonefs_warn(sb, "EXPERIMENTAL filesystem in use; use at your own risk");

Or something like that to manage peoples' expectations in case you find
a really nasty data-chomping bug. :)

(Or as a lever to convince people to stop running old code some day...)

--D

> +	zonefs_info(sb, "Mounting %u zones",
> +		    blkdev_nr_zones(sb->s_bdev->bd_disk));
> +
> +	/* Create root directory inode */
> +	ret = -ENOMEM;
> +	inode = new_inode(sb);
> +	if (!inode)
> +		goto out;
> +
> +	inode->i_ino = blkdev_nr_zones(sb->s_bdev->bd_disk);
> +	inode->i_mode = S_IFDIR | 0555;
> +	inode->i_ctime = inode->i_mtime = inode->i_atime = current_time(inode);
> +	inode->i_op = &zonefs_dir_inode_operations;
> +	inode->i_fop = &simple_dir_operations;
> +	set_nlink(inode, 2);
> +
> +	sb->s_root = d_make_root(inode);
> +	if (!sb->s_root)
> +		goto out;
> +
> +	/* Create and populate files in zone groups directories */
> +	for (t = 0; t < ZONEFS_ZTYPE_MAX; t++) {
> +		ret = zonefs_create_zgroup(&zd, t);
> +		if (ret)
> +			break;
> +	}
> +
> +out:
> +	zonefs_cleanup_zone_info(&zd);
> +
> +	return ret;
> +}
> +
> +static struct dentry *zonefs_mount(struct file_system_type *fs_type,
> +				   int flags, const char *dev_name, void *data)
> +{
> +	return mount_bdev(fs_type, flags, dev_name, data, zonefs_fill_super);
> +}
> +
> +static void zonefs_kill_super(struct super_block *sb)
> +{
> +	struct zonefs_sb_info *sbi = ZONEFS_SB(sb);
> +
> +	if (sb->s_root)
> +		d_genocide(sb->s_root);
> +	kill_block_super(sb);
> +	kfree(sbi);
> +}
> +
> +/*
> + * File system definition and registration.
> + */
> +static struct file_system_type zonefs_type = {
> +	.owner		= THIS_MODULE,
> +	.name		= "zonefs",
> +	.mount		= zonefs_mount,
> +	.kill_sb	= zonefs_kill_super,
> +	.fs_flags	= FS_REQUIRES_DEV,
> +};
> +
> +static int __init zonefs_init_inodecache(void)
> +{
> +	zonefs_inode_cachep = kmem_cache_create("zonefs_inode_cache",
> +			sizeof(struct zonefs_inode_info), 0,
> +			(SLAB_RECLAIM_ACCOUNT | SLAB_MEM_SPREAD | SLAB_ACCOUNT),
> +			NULL);
> +	if (zonefs_inode_cachep == NULL)
> +		return -ENOMEM;
> +	return 0;
> +}
> +
> +static void zonefs_destroy_inodecache(void)
> +{
> +	/*
> +	 * Make sure all delayed rcu free inodes are flushed before we
> +	 * destroy the inode cache.
> +	 */
> +	rcu_barrier();
> +	kmem_cache_destroy(zonefs_inode_cachep);
> +}
> +
> +static int __init zonefs_init(void)
> +{
> +	int ret;
> +
> +	BUILD_BUG_ON(sizeof(struct zonefs_super) != ZONEFS_SUPER_SIZE);
> +
> +	ret = zonefs_init_inodecache();
> +	if (ret)
> +		return ret;
> +
> +	ret = register_filesystem(&zonefs_type);
> +	if (ret) {
> +		zonefs_destroy_inodecache();
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void __exit zonefs_exit(void)
> +{
> +	zonefs_destroy_inodecache();
> +	unregister_filesystem(&zonefs_type);
> +}
> +
> +MODULE_AUTHOR("Damien Le Moal");
> +MODULE_DESCRIPTION("Zone file system for zoned block devices");
> +MODULE_LICENSE("GPL");
> +module_init(zonefs_init);
> +module_exit(zonefs_exit);
> diff --git a/fs/zonefs/zonefs.h b/fs/zonefs/zonefs.h
> new file mode 100644
> index 000000000000..5625aecac1ad
> --- /dev/null
> +++ b/fs/zonefs/zonefs.h
> @@ -0,0 +1,187 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Simple zone file system for zoned block devices.
> + *
> + * Copyright (C) 2019 Western Digital Corporation or its affiliates.
> + */
> +#ifndef __ZONEFS_H__
> +#define __ZONEFS_H__
> +
> +#include <linux/fs.h>
> +#include <linux/magic.h>
> +#include <linux/uuid.h>
> +#include <linux/mutex.h>
> +#include <linux/rwsem.h>
> +
> +/*
> + * Maximum length of file names: this only needs to be large enough to fit
> + * the zone group directory names and a decimal zone number for file names.
> + * 16 characters is plenty.
> + */
> +#define ZONEFS_NAME_MAX		16
> +
> +/*
> + * Zone types: ZONEFS_ZTYPE_SEQ is used for all sequential zone types
> + * defined in linux/blkzoned.h, that is, BLK_ZONE_TYPE_SEQWRITE_REQ and
> + * BLK_ZONE_TYPE_SEQWRITE_PREF.
> + */
> +enum zonefs_ztype {
> +	ZONEFS_ZTYPE_CNV,
> +	ZONEFS_ZTYPE_SEQ,
> +	ZONEFS_ZTYPE_MAX,
> +};
> +
> +static inline enum zonefs_ztype zonefs_zone_type(struct blk_zone *zone)
> +{
> +	if (zone->type == BLK_ZONE_TYPE_CONVENTIONAL)
> +		return ZONEFS_ZTYPE_CNV;
> +	return ZONEFS_ZTYPE_SEQ;
> +}
> +
> +/*
> + * In-memory inode data.
> + */
> +struct zonefs_inode_info {
> +	struct inode		i_vnode;
> +
> +	/* File zone type */
> +	enum zonefs_ztype	i_ztype;
> +
> +	/* File zone start sector (512B unit) */
> +	sector_t		i_zsector;
> +
> +	/* File zone write pointer position (sequential zones only) */
> +	loff_t			i_wpoffset;
> +
> +	/* File maximum size */
> +	loff_t			i_max_size;
> +
> +	/*
> +	 * To serialise fully against both syscall and mmap based IO and
> +	 * sequential file truncation, two locks are used. For serializing
> +	 * zonefs_seq_file_truncate() against zonefs_iomap_begin(), that is,
> +	 * file truncate operations against block mapping, i_truncate_mutex is
> +	 * used. i_truncate_mutex also protects against concurrent accesses
> +	 * and changes to the inode private data, and in particular changes to
> +	 * a sequential file size on completion of direct IO writes.
> +	 * Serialization of mmap read IOs with truncate and syscall IO
> +	 * operations is done with i_mmap_sem in addition to i_truncate_mutex.
> +	 * Only zonefs_seq_file_truncate() takes both lock (i_mmap_sem first,
> +	 * i_truncate_mutex second).
> +	 */
> +	struct mutex		i_truncate_mutex;
> +	struct rw_semaphore	i_mmap_sem;
> +};
> +
> +static inline struct zonefs_inode_info *ZONEFS_I(struct inode *inode)
> +{
> +	return container_of(inode, struct zonefs_inode_info, i_vnode);
> +}
> +
> +/*
> + * On-disk super block (block 0).
> + */
> +#define ZONEFS_LABEL_LEN	64
> +#define ZONEFS_UUID_SIZE	16
> +#define ZONEFS_SUPER_SIZE	4096
> +
> +struct zonefs_super {
> +
> +	/* Magic number */
> +	__le32		s_magic;
> +
> +	/* Checksum */
> +	__le32		s_crc;
> +
> +	/* Volume label */
> +	char		s_label[ZONEFS_LABEL_LEN];
> +
> +	/* 128-bit uuid */
> +	__u8		s_uuid[ZONEFS_UUID_SIZE];
> +
> +	/* Features */
> +	__le64		s_features;
> +
> +	/* UID/GID to use for files */
> +	__le32		s_uid;
> +	__le32		s_gid;
> +
> +	/* File permissions */
> +	__le32		s_perm;
> +
> +	/* Padding to ZONEFS_SUPER_SIZE bytes */
> +	__u8		s_reserved[3988];
> +
> +} __packed;
> +
> +/*
> + * Feature flags: used on disk in the s_features field of struct zonefs_super
> + * and in-memory in the s_feartures field of struct zonefs_sb_info.
> + */
> +enum zonefs_features {
> +	/*
> +	 * Aggregate contiguous conventional zones into a single file.
> +	 */
> +	ZONEFS_F_AGGRCNV = 1ULL << 0,
> +	/*
> +	 * Use super block specified UID for files instead of default.
> +	 */
> +	ZONEFS_F_UID = 1ULL << 1,
> +	/*
> +	 * Use super block specified GID for files instead of default.
> +	 */
> +	ZONEFS_F_GID = 1ULL << 2,
> +	/*
> +	 * Use super block specified file permissions instead of default 640.
> +	 */
> +	ZONEFS_F_PERM = 1ULL << 3,
> +};
> +
> +#define ZONEFS_F_DEFINED_FEATURES \
> +	(ZONEFS_F_AGGRCNV | ZONEFS_F_UID | ZONEFS_F_GID | ZONEFS_F_PERM)
> +
> +/*
> + * Mount options for error handling.
> + */
> +#define ZONEFS_MNTOPT_ERRORS_CONT	(1 << 0)
> +#define ZONEFS_MNTOPT_ERRORS_RO		(1 << 1)
> +#define ZONEFS_MNTOPT_ERRORS_PANIC	(1 << 2)
> +
> +/*
> + * In-memory Super block information.
> + */
> +struct zonefs_sb_info {
> +
> +	unsigned long		s_mount_opts;
> +
> +	spinlock_t		s_lock;
> +
> +	unsigned long long	s_features;
> +	kuid_t			s_uid;
> +	kgid_t			s_gid;
> +	umode_t			s_perm;
> +	uuid_t			s_uuid;
> +	loff_t			s_blocksize_mask;
> +	unsigned int		s_zone_sectors_shift;
> +
> +	unsigned int		s_nr_files[ZONEFS_ZTYPE_MAX];
> +
> +	loff_t			s_blocks;
> +	loff_t			s_used_blocks;
> +};
> +
> +static inline struct zonefs_sb_info *ZONEFS_SB(struct super_block *sb)
> +{
> +	return sb->s_fs_info;
> +}
> +
> +#define zonefs_info(sb, format, args...)	\
> +	pr_info("zonefs (%s): " format, sb->s_id, ## args)
> +#define zonefs_err(sb, format, args...)		\
> +	pr_err("zonefs (%s) ERROR: " format, sb->s_id, ## args)
> +#define zonefs_warn(sb, format, args...)	\
> +	pr_warn("zonefs (%s) WARNING: " format, sb->s_id, ## args)
> +#define zonefs_panic(sb, format, args...)	\
> +	panic("zonefs (%s) PANIC: " format, sb->s_id, ## args)
> +
> +#endif
> diff --git a/include/uapi/linux/magic.h b/include/uapi/linux/magic.h
> index 3ac436376d79..d78064007b17 100644
> --- a/include/uapi/linux/magic.h
> +++ b/include/uapi/linux/magic.h
> @@ -87,6 +87,7 @@
>  #define NSFS_MAGIC		0x6e736673
>  #define BPF_FS_MAGIC		0xcafe4a11
>  #define AAFS_MAGIC		0x5a3c69f0
> +#define ZONEFS_MAGIC		0x5a4f4653
>  
>  /* Since UDF 2.01 is ISO 13346 based... */
>  #define UDF_SUPER_MAGIC		0x15013346
> -- 
> 2.24.1
> 

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v9 1/2] fs: New zonefs file system
@ 2020-01-28 15:34 Markus Elfring
  2020-01-29  4:13 ` Damien Le Moal
  0 siblings, 1 reply; 14+ messages in thread
From: Markus Elfring @ 2020-01-28 15:34 UTC (permalink / raw)
  To: Damien Le Moal, linux-fsdevel, linux-xfs
  Cc: linux-kernel, Darrick J. Wong, Hannes Reinecke,
	Johannes Thumshirn, Linus Torvalds, Naohiro Aota

…
> +++ b/fs/zonefs/super.c
> +out:
> +	kunmap(page);
> +out_free:
> +	__free_page(page);


Would you like to reconsider your name selection for such labels?
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/process/coding-style.rst?id=b0be0eff1a5ab77d588b76bd8b1c92d5d17b3f73#n460

Change possibility:

+unmap:
+	kunmap(page);
+free_page:
+	__free_page(page);


Regards,
Markus

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v9 1/2] fs: New zonefs file system
  2020-01-28  1:28   ` Linus Torvalds
@ 2020-01-28  1:34     ` Damien Le Moal
  0 siblings, 0 replies; 14+ messages in thread
From: Damien Le Moal @ 2020-01-28  1:34 UTC (permalink / raw)
  To: Linus Torvalds
  Cc: Markus Elfring, linux-fsdevel, linux-xfs, linux-kernel,
	Darrick J . Wong, Hannes Reinecke, Johannes Thumshirn,
	Naohiro Aota

On 2020/01/28 10:28, Linus Torvalds wrote:
> On Mon, Jan 27, 2020 at 5:26 PM Damien Le Moal <Damien.LeMoal@wdc.com> wrote:
>>
>> Yes, good catch. Furthermore, since this array is used only in
>> zonefs_create_zgroup(), I moved its declaration on-stack in that function.
> 
> What?
> 
> Making it _local_ to that function makes sense, but not on stack.
> Please keep it "static const char *[]" so that it isn't copied onto
> the stack.

Done. Thanks.

> 
>                Linus
> 
-- 
Damien Le Moal
Western Digital Research

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v9 1/2] fs: New zonefs file system
  2020-01-28  1:26 ` Damien Le Moal
@ 2020-01-28  1:28   ` Linus Torvalds
  2020-01-28  1:34     ` Damien Le Moal
  0 siblings, 1 reply; 14+ messages in thread
From: Linus Torvalds @ 2020-01-28  1:28 UTC (permalink / raw)
  To: Damien Le Moal
  Cc: Markus Elfring, linux-fsdevel, linux-xfs, linux-kernel,
	Darrick J . Wong, Hannes Reinecke, Johannes Thumshirn,
	Naohiro Aota

On Mon, Jan 27, 2020 at 5:26 PM Damien Le Moal <Damien.LeMoal@wdc.com> wrote:
>
> Yes, good catch. Furthermore, since this array is used only in
> zonefs_create_zgroup(), I moved its declaration on-stack in that function.

What?

Making it _local_ to that function makes sense, but not on stack.
Please keep it "static const char *[]" so that it isn't copied onto
the stack.

               Linus

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v9 1/2] fs: New zonefs file system
  2020-01-27 12:49 Markus Elfring
@ 2020-01-28  1:26 ` Damien Le Moal
  2020-01-28  1:28   ` Linus Torvalds
  0 siblings, 1 reply; 14+ messages in thread
From: Damien Le Moal @ 2020-01-28  1:26 UTC (permalink / raw)
  To: Markus Elfring, linux-fsdevel, linux-xfs
  Cc: linux-kernel, Darrick J . Wong, Hannes Reinecke,
	Johannes Thumshirn, Linus Torvalds, Naohiro Aota

On 2020/01/27 21:49, Markus Elfring wrote:
> …
>> +++ b/fs/zonefs/super.c
> …
>> +static char *zgroups_name[ZONEFS_ZTYPE_MAX] = { "cnv", "seq" };
> 
> Would you like to keep this array as mutable?
> How do you think about to mark such data structures as “const”?

Yes, good catch. Furthermore, since this array is used only in
zonefs_create_zgroup(), I moved its declaration on-stack in that function.

Thanks.

> 
> Regards,
> Markus
> 


-- 
Damien Le Moal
Western Digital Research

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v9 1/2] fs: New zonefs file system
@ 2020-01-27 12:49 Markus Elfring
  2020-01-28  1:26 ` Damien Le Moal
  0 siblings, 1 reply; 14+ messages in thread
From: Markus Elfring @ 2020-01-27 12:49 UTC (permalink / raw)
  To: Damien Le Moal, linux-fsdevel, linux-xfs
  Cc: linux-kernel, Darrick J . Wong, Hannes Reinecke,
	Johannes Thumshirn, Linus Torvalds, Naohiro Aota

…
> +++ b/fs/zonefs/super.c
> +static char *zgroups_name[ZONEFS_ZTYPE_MAX] = { "cnv", "seq" };

Would you like to keep this array as mutable?
How do you think about to mark such data structures as “const”?

Regards,
Markus

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v9 1/2] fs: New zonefs file system
  2020-01-27 10:05 [PATCH v9 0/2] " Damien Le Moal
@ 2020-01-27 10:05 ` Damien Le Moal
  2020-01-28 17:46   ` Darrick J. Wong
  0 siblings, 1 reply; 14+ messages in thread
From: Damien Le Moal @ 2020-01-27 10:05 UTC (permalink / raw)
  To: linux-fsdevel, linux-xfs, linux-kernel, Linus Torvalds
  Cc: Johannes Thumshirn, Naohiro Aota, Darrick J . Wong, Hannes Reinecke

zonefs is a very simple file system exposing each zone of a zoned block
device as a file. Unlike a regular file system with zoned block device
support (e.g. f2fs), zonefs does not hide the sequential write
constraint of zoned block devices to the user. Files representing
sequential write zones of the device must be written sequentially
starting from the end of the file (append only writes).

As such, zonefs is in essence closer to a raw block device access
interface than to a full featured POSIX file system. The goal of zonefs
is to simplify the implementation of zoned block device support in
applications by replacing raw block device file accesses with a richer
file API, avoiding relying on direct block device file ioctls which may
be more obscure to developers. One example of this approach is the
implementation of LSM (log-structured merge) tree structures (such as
used in RocksDB and LevelDB) on zoned block devices by allowing SSTables
to be stored in a zone file similarly to a regular file system rather
than as a range of sectors of a zoned device. The introduction of the
higher level construct "one file is one zone" can help reducing the
amount of changes needed in the application as well as introducing
support for different application programming languages.

Zonefs on-disk metadata is reduced to an immutable super block to
persistently store a magic number and optional feature flags and
values. On mount, zonefs uses blkdev_report_zones() to obtain the device
zone configuration and populates the mount point with a static file tree
solely based on this information. E.g. file sizes come from the device
zone type and write pointer offset managed by the device itself.

The zone files created on mount have the following characteristics.
1) Files representing zones of the same type are grouped together
   under a common sub-directory:
     * For conventional zones, the sub-directory "cnv" is used.
     * For sequential write zones, the sub-directory "seq" is used.
  These two directories are the only directories that exist in zonefs.
  Users cannot create other directories and cannot rename nor delete
  the "cnv" and "seq" sub-directories.
2) The name of zone files is the number of the file within the zone
   type sub-directory, in order of increasing zone start sector.
3) The size of conventional zone files is fixed to the device zone size.
   Conventional zone files cannot be truncated.
4) The size of sequential zone files represent the file's zone write
   pointer position relative to the zone start sector. Truncating these
   files is allowed only down to 0, in which case, the zone is reset to
   rewind the zone write pointer position to the start of the zone, or
   up to the zone size, in which case the file's zone is transitioned
   to the FULL state (finish zone operation).
5) All read and write operations to files are not allowed beyond the
   file zone size. Any access exceeding the zone size is failed with
   the -EFBIG error.
6) Creating, deleting, renaming or modifying any attribute of files and
   sub-directories is not allowed.
7) There are no restrictions on the type of read and write operations
   that can be issued to conventional zone files. Buffered, direct and
   mmap read & write operations are accepted. For sequential zone files,
   there are no restrictions on read operations, but all write
   operations must be direct IO append writes. mmap write of sequential
   files is not allowed.

Several optional features of zonefs can be enabled at format time.
* Conventional zone aggregation: ranges of contiguous conventional
  zones can be aggregated into a single larger file instead of the
  default one file per zone.
* File ownership: The owner UID and GID of zone files is by default 0
  (root) but can be changed to any valid UID/GID.
* File access permissions: the default 640 access permissions can be
  changed.

The mkzonefs tool is used to format zoned block devices for use with
zonefs. This tool is available on Github at:

git@github.com:damien-lemoal/zonefs-tools.git.

zonefs-tools also includes a test suite which can be run against any
zoned block device, including null_blk block device created with zoned
mode.

Example: the following formats a 15TB host-managed SMR HDD with 256 MB
zones with the conventional zones aggregation feature enabled.

$ sudo mkzonefs -o aggr_cnv /dev/sdX
$ sudo mount -t zonefs /dev/sdX /mnt
$ ls -l /mnt/
total 0
dr-xr-xr-x 2 root root     1 Nov 25 13:23 cnv
dr-xr-xr-x 2 root root 55356 Nov 25 13:23 seq

The size of the zone files sub-directories indicate the number of files
existing for each type of zones. In this example, there is only one
conventional zone file (all conventional zones are aggregated under a
single file).

$ ls -l /mnt/cnv
total 137101312
-rw-r----- 1 root root 140391743488 Nov 25 13:23 0

This aggregated conventional zone file can be used as a regular file.

$ sudo mkfs.ext4 /mnt/cnv/0
$ sudo mount -o loop /mnt/cnv/0 /data

The "seq" sub-directory grouping files for sequential write zones has
in this example 55356 zones.

$ ls -lv /mnt/seq
total 14511243264
-rw-r----- 1 root root 0 Nov 25 13:23 0
-rw-r----- 1 root root 0 Nov 25 13:23 1
-rw-r----- 1 root root 0 Nov 25 13:23 2
...
-rw-r----- 1 root root 0 Nov 25 13:23 55354
-rw-r----- 1 root root 0 Nov 25 13:23 55355

For sequential write zone files, the file size changes as data is
appended at the end of the file, similarly to any regular file system.

$ dd if=/dev/zero of=/mnt/seq/0 bs=4K count=1 conv=notrunc oflag=direct
1+0 records in
1+0 records out
4096 bytes (4.1 kB, 4.0 KiB) copied, 0.000452219 s, 9.1 MB/s

$ ls -l /mnt/seq/0
-rw-r----- 1 root root 4096 Nov 25 13:23 /mnt/seq/0

The written file can be truncated to the zone size, preventing any
further write operation.

$ truncate -s 268435456 /mnt/seq/0
$ ls -l /mnt/seq/0
-rw-r----- 1 root root 268435456 Nov 25 13:49 /mnt/seq/0

Truncation to 0 size allows freeing the file zone storage space and
restart append-writes to the file.

$ truncate -s 0 /mnt/seq/0
$ ls -l /mnt/seq/0
-rw-r----- 1 root root 0 Nov 25 13:49 /mnt/seq/0

Since files are statically mapped to zones on the disk, the number of
blocks of a file as reported by stat() and fstat() indicates the size
of the file zone.

$ stat /mnt/seq/0
  File: /mnt/seq/0
  Size: 0       Blocks: 524288     IO Block: 4096   regular empty file
Device: 870h/2160d      Inode: 50431       Links: 1
Access: (0640/-rw-r-----)  Uid: (    0/    root)   Gid: (    0/  root)
Access: 2019-11-25 13:23:57.048971997 +0900
Modify: 2019-11-25 13:52:25.553805765 +0900
Change: 2019-11-25 13:52:25.553805765 +0900
 Birth: -

The number of blocks of the file ("Blocks") in units of 512B blocks
gives the maximum file size of 524288 * 512 B = 256 MB, corresponding
to the device zone size in this example. Of note is that the "IO block"
field always indicates the minimum IO size for writes and corresponds
to the device physical sector size.

This code contains contributions from:
* Johannes Thumshirn <jthumshirn@suse.de>,
* Darrick J. Wong <darrick.wong@oracle.com>,
* Christoph Hellwig <hch@lst.de>,
* Chaitanya Kulkarni <chaitanya.kulkarni@wdc.com> and
* Ting Yao <tingyao@hust.edu.cn>.

Signed-off-by: Damien Le Moal <damien.lemoal@wdc.com>
---
 MAINTAINERS                |    9 +
 fs/Kconfig                 |    1 +
 fs/Makefile                |    1 +
 fs/zonefs/Kconfig          |    9 +
 fs/zonefs/Makefile         |    4 +
 fs/zonefs/super.c          | 1366 ++++++++++++++++++++++++++++++++++++
 fs/zonefs/zonefs.h         |  187 +++++
 include/uapi/linux/magic.h |    1 +
 8 files changed, 1578 insertions(+)
 create mode 100644 fs/zonefs/Kconfig
 create mode 100644 fs/zonefs/Makefile
 create mode 100644 fs/zonefs/super.c
 create mode 100644 fs/zonefs/zonefs.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 56765f542244..089fd879632a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18303,6 +18303,15 @@ L:	linux-kernel@vger.kernel.org
 S:	Maintained
 F:	arch/x86/kernel/cpu/zhaoxin.c
 
+ZONEFS FILESYSTEM
+M:	Damien Le Moal <damien.lemoal@wdc.com>
+M:	Naohiro Aota <naohiro.aota@wdc.com>
+R:	Johannes Thumshirn <jth@kernel.org>
+L:	linux-fsdevel@vger.kernel.org
+T:	git git://git.kernel.org/pub/scm/linux/kernel/git/dlemoal/zonefs.git
+S:	Maintained
+F:	fs/zonefs/
+
 ZPOOL COMPRESSED PAGE STORAGE API
 M:	Dan Streetman <ddstreet@ieee.org>
 L:	linux-mm@kvack.org
diff --git a/fs/Kconfig b/fs/Kconfig
index 7b623e9fc1b0..a3f97ca2bd46 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -40,6 +40,7 @@ source "fs/ocfs2/Kconfig"
 source "fs/btrfs/Kconfig"
 source "fs/nilfs2/Kconfig"
 source "fs/f2fs/Kconfig"
+source "fs/zonefs/Kconfig"
 
 config FS_DAX
 	bool "Direct Access (DAX) support"
diff --git a/fs/Makefile b/fs/Makefile
index 1148c555c4d3..527f228a5e8a 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -133,3 +133,4 @@ obj-$(CONFIG_CEPH_FS)		+= ceph/
 obj-$(CONFIG_PSTORE)		+= pstore/
 obj-$(CONFIG_EFIVAR_FS)		+= efivarfs/
 obj-$(CONFIG_EROFS_FS)		+= erofs/
+obj-$(CONFIG_ZONEFS_FS)		+= zonefs/
diff --git a/fs/zonefs/Kconfig b/fs/zonefs/Kconfig
new file mode 100644
index 000000000000..03a4ef80f975
--- /dev/null
+++ b/fs/zonefs/Kconfig
@@ -0,0 +1,9 @@
+config ZONEFS_FS
+	tristate "zonefs filesystem support"
+	depends on BLOCK
+	depends on BLK_DEV_ZONED
+	help
+	  zonefs is a simple File System which exposes zones of a zoned block
+	  device (e.g. host-managed or host-aware SMR disk drives) as files.
+
+	  If unsure, say N.
diff --git a/fs/zonefs/Makefile b/fs/zonefs/Makefile
new file mode 100644
index 000000000000..75a380aa1ae1
--- /dev/null
+++ b/fs/zonefs/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_ZONEFS_FS) += zonefs.o
+
+zonefs-y	:= super.o
diff --git a/fs/zonefs/super.c b/fs/zonefs/super.c
new file mode 100644
index 000000000000..bef6193e0a70
--- /dev/null
+++ b/fs/zonefs/super.c
@@ -0,0 +1,1366 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Simple file system for zoned block devices exposing zones as files.
+ *
+ * Copyright (C) 2019 Western Digital Corporation or its affiliates.
+ */
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/magic.h>
+#include <linux/iomap.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/blkdev.h>
+#include <linux/statfs.h>
+#include <linux/writeback.h>
+#include <linux/quotaops.h>
+#include <linux/seq_file.h>
+#include <linux/parser.h>
+#include <linux/uio.h>
+#include <linux/mman.h>
+#include <linux/sched/mm.h>
+#include <linux/crc32.h>
+
+#include "zonefs.h"
+
+static int zonefs_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
+			      unsigned int flags, struct iomap *iomap,
+			      struct iomap *srcmap)
+{
+	struct zonefs_sb_info *sbi = ZONEFS_SB(inode->i_sb);
+	struct zonefs_inode_info *zi = ZONEFS_I(inode);
+	loff_t isize;
+
+	/* All I/Os should always be within the file maximum size */
+	if (WARN_ON_ONCE(offset + length > zi->i_max_size))
+		return -EIO;
+
+	/*
+	 * Sequential zones can only accept direct writes. This is already
+	 * checked when writes are issued, so warn about writeback operations.
+	 */
+	if (WARN_ON_ONCE(zi->i_ztype == ZONEFS_ZTYPE_SEQ &&
+			 (flags & IOMAP_WRITE) && !(flags & IOMAP_DIRECT)))
+		return -EIO;
+
+	/*
+	 * For conventional zones, all blocks are always mapped.
+	 * For sequential zones, all blocks after always mapped below the
+	 * inode size (zone write pointer) and unwriten beyond.
+	 */
+	mutex_lock(&zi->i_truncate_mutex);
+	isize = i_size_read(inode);
+	if (offset >= isize)
+		iomap->type = IOMAP_UNWRITTEN;
+	else
+		iomap->type = IOMAP_MAPPED;
+	if (flags & IOMAP_WRITE)
+		length = zi->i_max_size - offset;
+	else
+		length = min(length, isize - offset);
+	mutex_unlock(&zi->i_truncate_mutex);
+
+	iomap->offset = offset & (~sbi->s_blocksize_mask);
+	iomap->length = ((offset + length + sbi->s_blocksize_mask) &
+			 (~sbi->s_blocksize_mask)) - iomap->offset;
+	iomap->bdev = inode->i_sb->s_bdev;
+	iomap->addr = (zi->i_zsector << SECTOR_SHIFT) + iomap->offset;
+
+	return 0;
+}
+
+static const struct iomap_ops zonefs_iomap_ops = {
+	.iomap_begin	= zonefs_iomap_begin,
+};
+
+static int zonefs_readpage(struct file *unused, struct page *page)
+{
+	return iomap_readpage(page, &zonefs_iomap_ops);
+}
+
+static int zonefs_readpages(struct file *unused, struct address_space *mapping,
+			    struct list_head *pages, unsigned int nr_pages)
+{
+	return iomap_readpages(mapping, pages, nr_pages, &zonefs_iomap_ops);
+}
+
+/*
+ * Map blocks for page writeback. This is used only on conventional zone files,
+ * which implies that the page range can only be within the fixed inode size.
+ */
+static int zonefs_map_blocks(struct iomap_writepage_ctx *wpc,
+			     struct inode *inode, loff_t offset)
+{
+	struct zonefs_inode_info *zi = ZONEFS_I(inode);
+
+	if (WARN_ON_ONCE(zi->i_ztype != ZONEFS_ZTYPE_CNV))
+		return -EIO;
+	if (WARN_ON_ONCE(offset >= i_size_read(inode)))
+		return -EIO;
+
+	/* If the mapping is already OK, nothing needs to be done */
+	if (offset >= wpc->iomap.offset &&
+	    offset < wpc->iomap.offset + wpc->iomap.length)
+		return 0;
+
+	return zonefs_iomap_begin(inode, offset, zi->i_max_size - offset,
+				  IOMAP_WRITE, &wpc->iomap, NULL);
+}
+
+static const struct iomap_writeback_ops zonefs_writeback_ops = {
+	.map_blocks		= zonefs_map_blocks,
+};
+
+static int zonefs_writepage(struct page *page, struct writeback_control *wbc)
+{
+	struct iomap_writepage_ctx wpc = { };
+
+	return iomap_writepage(page, wbc, &wpc, &zonefs_writeback_ops);
+}
+
+static int zonefs_writepages(struct address_space *mapping,
+			     struct writeback_control *wbc)
+{
+	struct iomap_writepage_ctx wpc = { };
+
+	return iomap_writepages(mapping, wbc, &wpc, &zonefs_writeback_ops);
+}
+
+static const struct address_space_operations zonefs_file_aops = {
+	.readpage		= zonefs_readpage,
+	.readpages		= zonefs_readpages,
+	.writepage		= zonefs_writepage,
+	.writepages		= zonefs_writepages,
+	.set_page_dirty		= iomap_set_page_dirty,
+	.releasepage		= iomap_releasepage,
+	.invalidatepage		= iomap_invalidatepage,
+	.migratepage		= iomap_migrate_page,
+	.is_partially_uptodate	= iomap_is_partially_uptodate,
+	.error_remove_page	= generic_error_remove_page,
+	.direct_IO		= noop_direct_IO,
+};
+
+static void zonefs_update_stats(struct inode *inode, loff_t new_isize)
+{
+	struct super_block *sb = inode->i_sb;
+	struct zonefs_sb_info *sbi = ZONEFS_SB(sb);
+	loff_t old_isize = i_size_read(inode);
+	loff_t nr_blocks;
+
+	if (new_isize == old_isize)
+		return;
+
+	spin_lock(&sbi->s_lock);
+
+	/*
+	 * This may be called for an IO error recovery update.
+	 * So beware of the values seen.
+	 */
+	if (new_isize < old_isize) {
+		nr_blocks = (old_isize - new_isize) >> sb->s_blocksize_bits;
+		if (sbi->s_used_blocks > nr_blocks)
+			sbi->s_used_blocks -= nr_blocks;
+		else
+			sbi->s_used_blocks = 0;
+	} else {
+		sbi->s_used_blocks +=
+			(new_isize - old_isize) >> sb->s_blocksize_bits;
+		if (sbi->s_used_blocks > sbi->s_blocks)
+			sbi->s_used_blocks = sbi->s_blocks;
+	}
+
+	spin_unlock(&sbi->s_lock);
+}
+
+static int zonefs_file_truncate(struct inode *inode, loff_t isize)
+{
+	struct zonefs_inode_info *zi = ZONEFS_I(inode);
+	loff_t old_isize;
+	enum req_opf op;
+	int ret = 0;
+
+	/*
+	 * Only sequential zone files can be truncated and Truncation is allowed
+	 * only down to a 0 size, which is equivalent to a zone reset, and to
+	 * the maximum file size, which is equivalent to a zone finish.
+	 */
+	if (zi->i_ztype != ZONEFS_ZTYPE_SEQ)
+		return -EPERM;
+
+	if (!isize)
+		op = REQ_OP_ZONE_RESET;
+	else if (isize == zi->i_max_size)
+		op = REQ_OP_ZONE_FINISH;
+	else
+		return -EPERM;
+
+	inode_dio_wait(inode);
+
+	/* Serialize against page faults */
+	down_write(&zi->i_mmap_sem);
+
+	/* Serialize against zonefs_iomap_begin() */
+	mutex_lock(&zi->i_truncate_mutex);
+
+	old_isize = i_size_read(inode);
+	if (isize == old_isize)
+		goto unlock;
+
+	ret = blkdev_zone_mgmt(inode->i_sb->s_bdev, op, zi->i_zsector,
+			       zi->i_max_size >> SECTOR_SHIFT, GFP_NOFS);
+	if (ret) {
+		zonefs_err(inode->i_sb,
+			   "Zone management operation at %llu failed %d",
+			   zi->i_zsector, ret);
+		goto unlock;
+	}
+
+	zonefs_update_stats(inode, isize);
+	truncate_setsize(inode, isize);
+	zi->i_wpoffset = isize;
+
+unlock:
+	mutex_unlock(&zi->i_truncate_mutex);
+	up_write(&zi->i_mmap_sem);
+
+	return ret;
+}
+
+static int zonefs_inode_setattr(struct dentry *dentry, struct iattr *iattr)
+{
+	struct inode *inode = d_inode(dentry);
+	int ret;
+
+	ret = setattr_prepare(dentry, iattr);
+	if (ret)
+		return ret;
+
+	/*
+	 * Since files and directories cannot be created nor deleted, do not
+	 * allow setting any write attributes on the zone types sub-directories.
+	 */
+	if ((iattr->ia_valid & ATTR_MODE) && S_ISDIR(inode->i_mode) &&
+	    (iattr->ia_mode & 0222))
+		return -EPERM;
+
+	if (((iattr->ia_valid & ATTR_UID) &&
+	     !uid_eq(iattr->ia_uid, inode->i_uid)) ||
+	    ((iattr->ia_valid & ATTR_GID) &&
+	     !gid_eq(iattr->ia_gid, inode->i_gid))) {
+		ret = dquot_transfer(inode, iattr);
+		if (ret)
+			return ret;
+	}
+
+	if (iattr->ia_valid & ATTR_SIZE) {
+		ret = zonefs_file_truncate(inode, iattr->ia_size);
+		if (ret)
+			return ret;
+	}
+
+	setattr_copy(inode, iattr);
+
+	return 0;
+}
+
+static const struct inode_operations zonefs_file_inode_operations = {
+	.setattr	= zonefs_inode_setattr,
+};
+
+static int zonefs_file_fsync(struct file *file, loff_t start, loff_t end,
+			     int datasync)
+{
+	struct inode *inode = file_inode(file);
+	int ret = 0;
+
+	/*
+	 * Since only direct writes are allowed in sequential files, page cache
+	 * flush is needed only for conventional zone files.
+	 */
+	if (ZONEFS_I(inode)->i_ztype == ZONEFS_ZTYPE_CNV) {
+		ret = file_write_and_wait_range(file, start, end);
+		if (ret)
+			return ret;
+		ret = file_check_and_advance_wb_err(file);
+	}
+
+	if (ret == 0)
+		ret = blkdev_issue_flush(inode->i_sb->s_bdev, GFP_KERNEL, NULL);
+
+	return ret;
+}
+
+static vm_fault_t zonefs_filemap_fault(struct vm_fault *vmf)
+{
+	struct zonefs_inode_info *zi = ZONEFS_I(file_inode(vmf->vma->vm_file));
+	vm_fault_t ret;
+
+	down_read(&zi->i_mmap_sem);
+	ret = filemap_fault(vmf);
+	up_read(&zi->i_mmap_sem);
+
+	return ret;
+}
+
+static vm_fault_t zonefs_filemap_page_mkwrite(struct vm_fault *vmf)
+{
+	struct inode *inode = file_inode(vmf->vma->vm_file);
+	struct zonefs_inode_info *zi = ZONEFS_I(inode);
+	vm_fault_t ret;
+
+	/*
+	 * Sanity check: only conventional zone files can have shared
+	 * writeable mappings.
+	 */
+	if (WARN_ON_ONCE(zi->i_ztype != ZONEFS_ZTYPE_CNV))
+		return VM_FAULT_NOPAGE;
+
+	sb_start_pagefault(inode->i_sb);
+	file_update_time(vmf->vma->vm_file);
+
+	/* Serialize against truncates */
+	down_read(&zi->i_mmap_sem);
+	ret = iomap_page_mkwrite(vmf, &zonefs_iomap_ops);
+	up_read(&zi->i_mmap_sem);
+
+	sb_end_pagefault(inode->i_sb);
+	return ret;
+}
+
+static const struct vm_operations_struct zonefs_file_vm_ops = {
+	.fault		= zonefs_filemap_fault,
+	.map_pages	= filemap_map_pages,
+	.page_mkwrite	= zonefs_filemap_page_mkwrite,
+};
+
+static int zonefs_file_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	/*
+	 * Conventional zones accept random writes, so their files can support
+	 * shared writable mappings. For sequential zone files, only read
+	 * mappings are possible since there are no guarantees for write
+	 * ordering with msync() and page cache writeback.
+	 */
+	if (ZONEFS_I(file_inode(file))->i_ztype == ZONEFS_ZTYPE_SEQ &&
+	    (vma->vm_flags & VM_SHARED) && (vma->vm_flags & VM_MAYWRITE))
+		return -EINVAL;
+
+	file_accessed(file);
+	vma->vm_ops = &zonefs_file_vm_ops;
+
+	return 0;
+}
+
+static loff_t zonefs_file_llseek(struct file *file, loff_t offset, int whence)
+{
+	loff_t isize = i_size_read(file_inode(file));
+
+	/*
+	 * Seeks are limited to below the zone size for conventional zones
+	 * and below the zone write pointer for sequential zones. In both
+	 * cases, this limit is the inode size.
+	 */
+	return generic_file_llseek_size(file, offset, whence, isize, isize);
+}
+
+/*
+ * Update a file inode access permissions based on the file zone condition.
+ */
+static void zonefs_update_file_perm(struct inode *inode, struct blk_zone *zone)
+{
+	if (zone->cond == BLK_ZONE_COND_OFFLINE) {
+		/*
+		 * Dead zone: make the inode immutable, disable all accesses
+		 * and set the file size to 0 (zone wp set to zone start).
+		 */
+		inode->i_flags |= S_IMMUTABLE;
+		inode->i_mode &= ~0777;
+		zone->wp = zone->start;
+	} else if (zone->cond == BLK_ZONE_COND_READONLY) {
+		/* Do not allow writes in read-only zones */
+		inode->i_flags |= S_IMMUTABLE;
+		inode->i_mode &= ~0222;
+	}
+}
+
+struct zonefs_ioerr_data {
+	struct inode	*inode;
+	bool		write;
+};
+
+static int zonefs_io_err_cb(struct blk_zone *zone, unsigned int idx, void *data)
+{
+	struct zonefs_ioerr_data *ioerr = data;
+	struct inode *inode = ioerr->inode;
+	struct zonefs_inode_info *zi = ZONEFS_I(inode);
+	struct super_block *sb = inode->i_sb;
+	loff_t isize, wp_ofst;
+
+	/*
+	 * The condition of the zone may have change. Fix the file access
+	 * permissions if necessary.
+	 */
+	zonefs_update_file_perm(inode, zone);
+
+	/*
+	 * There is no write pointer on conventional zones and read operations
+	 * do not change a zone write pointer. So there is nothing more to do
+	 * for these two cases.
+	 */
+	if (zi->i_ztype == ZONEFS_ZTYPE_CNV || !ioerr->write)
+		return 0;
+
+	/*
+	 * For sequential zones write, make sure that the zone write pointer
+	 * position is as expected, that is, in sync with the inode size.
+	 */
+	wp_ofst = (zone->wp - zone->start) << SECTOR_SHIFT;
+	zi->i_wpoffset = wp_ofst;
+	isize = i_size_read(inode);
+
+	if (isize == wp_ofst)
+		return 0;
+
+	/*
+	 * The inode size and the zone write pointer are not in sync.
+	 * If the inode size is below the zone write pointer, then data was
+	 * writen at the end of the file. This can happen in the case of a
+	 * partial failure of a large multi-bio DIO. No data is lost. Simply fix
+	 * the inode size to reflect the partial write.
+	 * On the other hand, if the inode size is over the zone write pointer,
+	 * then there was an external corruption, e.g. an application reset the
+	 * file zone directly, or the device has a problem.
+	 */
+	zonefs_warn(sb, "inode %lu: size %lld should be %lld\n",
+		    inode->i_ino, isize, wp_ofst);
+	if (isize > wp_ofst) {
+		struct zonefs_sb_info *sbi = ZONEFS_SB(sb);
+
+		if ((sbi->s_mount_opts & ZONEFS_MNTOPT_ERRORS_RO) &&
+		    !sb_rdonly(sb)) {
+			zonefs_warn(sb,
+				"Zone %lu corruption detected, remounting fs read-only\n",
+				inode->i_ino);
+			sb->s_flags |= SB_RDONLY;
+			return 0;
+		} else if (sbi->s_mount_opts & ZONEFS_MNTOPT_ERRORS_CONT) {
+			zonefs_warn(sb,
+				"Zone %lu corruption detected, continuing\n",
+				inode->i_ino);
+		} else if (sbi->s_mount_opts & ZONEFS_MNTOPT_ERRORS_PANIC) {
+			zonefs_panic(sb,
+				"Zone %lu corruption detected\n",
+				inode->i_ino);
+		}
+	}
+
+	zonefs_update_stats(inode, wp_ofst);
+	i_size_write(inode, wp_ofst);
+
+	return 0;
+}
+
+/*
+ * When an IO error occurs, check the target zone to see if there is a change
+ * in the zone condition (e.g. offline or read-only). For a failed write to a
+ * sequential zone, the zone write pointer position must also be checked to
+ * eventually correct the file size and zonefs inode write pointer offset
+ * (which can be out of sync with the drive due to partial write failures).
+ */
+static void zonefs_io_error(struct inode *inode, bool write)
+{
+	struct zonefs_inode_info *zi = ZONEFS_I(inode);
+	struct super_block *sb = inode->i_sb;
+	struct zonefs_sb_info *sbi = ZONEFS_SB(sb);
+	unsigned int noio_flag;
+	unsigned int nr_zones =
+		zi->i_max_size >> (sbi->s_zone_sectors_shift + SECTOR_SHIFT);
+	struct zonefs_ioerr_data ioerr = {
+		.inode = inode,
+		.write = write
+	};
+	int ret;
+
+	mutex_lock(&zi->i_truncate_mutex);
+
+	/*
+	 * Memory allocations in blkdev_report_zones() can trigger a memory
+	 * reclaim which may in turn cause a recursion into zonefs as well as
+	 * BIO allocations for the same device. The former case may end up in
+	 * a deadlock on the inode truncate mutex, while the latter may prevent
+	 * forward progress with BIO allocations as we are potentially still
+	 * holding the failed BIO. Executing the report zones under GFP_NOIO
+	 * avoids both problems.
+	 */
+	noio_flag = memalloc_noio_save();
+	ret = blkdev_report_zones(sb->s_bdev, zi->i_zsector, nr_zones,
+				  zonefs_io_err_cb, &ioerr);
+	if (ret != nr_zones)
+		zonefs_err(sb, "Get inode %lu zone information failed %d\n",
+			   inode->i_ino, ret);
+	memalloc_noio_restore(noio_flag);
+
+	mutex_unlock(&zi->i_truncate_mutex);
+}
+
+static int zonefs_file_write_dio_end_io(struct kiocb *iocb, ssize_t size,
+					int error, unsigned int flags)
+{
+	struct inode *inode = file_inode(iocb->ki_filp);
+	struct zonefs_inode_info *zi = ZONEFS_I(inode);
+
+	if (error) {
+		zonefs_io_error(inode, true);
+		return error;
+	}
+
+	if (size && zi->i_ztype != ZONEFS_ZTYPE_CNV) {
+		mutex_lock(&zi->i_truncate_mutex);
+		if (i_size_read(inode) < iocb->ki_pos + size) {
+			zonefs_update_stats(inode, iocb->ki_pos + size);
+			i_size_write(inode, iocb->ki_pos + size);
+		}
+		mutex_unlock(&zi->i_truncate_mutex);
+	}
+
+	return 0;
+}
+
+static const struct iomap_dio_ops zonefs_write_dio_ops = {
+	.end_io			= zonefs_file_write_dio_end_io,
+};
+
+/*
+ * Handle direct writes. For sequential zone files, this is the only possible
+ * write path. For these files, check that the user is issuing writes
+ * sequentially from the end of the file. This code assumes that the block layer
+ * delivers write requests to the device in sequential order. This is always the
+ * case if a block IO scheduler implementing the ELEVATOR_F_ZBD_SEQ_WRITE
+ * elevator feature is being used (e.g. mq-deadline). The block layer always
+ * automatically select such an elevator for zoned block devices during the
+ * device initialization.
+ */
+static ssize_t zonefs_file_dio_write(struct kiocb *iocb, struct iov_iter *from)
+{
+	struct inode *inode = file_inode(iocb->ki_filp);
+	struct zonefs_sb_info *sbi = ZONEFS_SB(inode->i_sb);
+	struct zonefs_inode_info *zi = ZONEFS_I(inode);
+	size_t count;
+	ssize_t ret;
+
+	/*
+	 * For async direct IOs to sequential zone files, ignore IOCB_NOWAIT
+	 * as this can cause write reordering (e.g. the first aio gets EAGAIN
+	 * on the inode lock but the second goes through but is now unaligned).
+	 */
+	if (zi->i_ztype == ZONEFS_ZTYPE_SEQ && !is_sync_kiocb(iocb)
+	    && (iocb->ki_flags & IOCB_NOWAIT))
+		iocb->ki_flags &= ~IOCB_NOWAIT;
+
+	if (iocb->ki_flags & IOCB_NOWAIT) {
+		if (!inode_trylock(inode))
+			return -EAGAIN;
+	} else {
+		inode_lock(inode);
+	}
+
+	ret = generic_write_checks(iocb, from);
+	if (ret <= 0)
+		goto out;
+
+	iov_iter_truncate(from, zi->i_max_size - iocb->ki_pos);
+	count = iov_iter_count(from);
+
+	if ((iocb->ki_pos | count) & sbi->s_blocksize_mask) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	/* Enforce sequential writes (append only) in sequential zones */
+	mutex_lock(&zi->i_truncate_mutex);
+	if (zi->i_ztype == ZONEFS_ZTYPE_SEQ && iocb->ki_pos != zi->i_wpoffset) {
+		zonefs_err(inode->i_sb,
+			   "Unaligned direct write at %llu + %zu (wp %llu)\n",
+			   iocb->ki_pos, count,
+			   zi->i_wpoffset);
+		mutex_unlock(&zi->i_truncate_mutex);
+		ret = -EINVAL;
+		goto out;
+	}
+	mutex_unlock(&zi->i_truncate_mutex);
+
+	ret = iomap_dio_rw(iocb, from, &zonefs_iomap_ops,
+			   &zonefs_write_dio_ops, is_sync_kiocb(iocb));
+	if (zi->i_ztype == ZONEFS_ZTYPE_SEQ &&
+	    (ret > 0 || ret == -EIOCBQUEUED)) {
+		if (ret > 0)
+			count = ret;
+		mutex_lock(&zi->i_truncate_mutex);
+		zi->i_wpoffset += count;
+		mutex_unlock(&zi->i_truncate_mutex);
+	}
+
+out:
+	inode_unlock(inode);
+
+	return ret;
+}
+
+static ssize_t zonefs_file_buffered_write(struct kiocb *iocb,
+					  struct iov_iter *from)
+{
+	struct inode *inode = file_inode(iocb->ki_filp);
+	struct zonefs_inode_info *zi = ZONEFS_I(inode);
+	ssize_t ret;
+
+	/*
+	 * Direct IO writes are mandatory for sequential zones so that the
+	 * write IO order is preserved.
+	 */
+	if (zi->i_ztype != ZONEFS_ZTYPE_CNV)
+		return -EIO;
+
+	if (iocb->ki_flags & IOCB_NOWAIT) {
+		if (!inode_trylock(inode))
+			return -EAGAIN;
+	} else {
+		inode_lock(inode);
+	}
+
+	ret = generic_write_checks(iocb, from);
+	if (ret <= 0)
+		goto out;
+
+	iov_iter_truncate(from, zi->i_max_size - iocb->ki_pos);
+
+	ret = iomap_file_buffered_write(iocb, from, &zonefs_iomap_ops);
+	if (ret > 0)
+		iocb->ki_pos += ret;
+	else if (ret == -EIO)
+		zonefs_io_error(inode, false);
+
+out:
+	inode_unlock(inode);
+	if (ret > 0)
+		ret = generic_write_sync(iocb, ret);
+
+	return ret;
+}
+
+static ssize_t zonefs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
+{
+	struct inode *inode = file_inode(iocb->ki_filp);
+
+	/* Write operations beyond the zone size are not allowed */
+	if (iocb->ki_pos >= ZONEFS_I(inode)->i_max_size)
+		return -EFBIG;
+
+	if (iocb->ki_flags & IOCB_DIRECT)
+		return zonefs_file_dio_write(iocb, from);
+
+	return zonefs_file_buffered_write(iocb, from);
+}
+
+static int zonefs_file_read_dio_end_io(struct kiocb *iocb, ssize_t size,
+				       int error, unsigned int flags)
+{
+	if (error) {
+		zonefs_io_error(file_inode(iocb->ki_filp), false);
+		return error;
+	}
+
+	return 0;
+}
+
+static const struct iomap_dio_ops zonefs_read_dio_ops = {
+	.end_io			= zonefs_file_read_dio_end_io,
+};
+
+static ssize_t zonefs_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
+{
+	struct inode *inode = file_inode(iocb->ki_filp);
+	struct zonefs_sb_info *sbi = ZONEFS_SB(inode->i_sb);
+	struct zonefs_inode_info *zi = ZONEFS_I(inode);
+	loff_t isize;
+	ssize_t ret;
+
+	if (iocb->ki_pos >= zi->i_max_size)
+		return 0;
+
+	if (iocb->ki_flags & IOCB_NOWAIT) {
+		if (!inode_trylock_shared(inode))
+			return -EAGAIN;
+	} else {
+		inode_lock_shared(inode);
+	}
+
+	/* Limit read operations to written data */
+	mutex_lock(&zi->i_truncate_mutex);
+	isize = i_size_read(inode);
+	if (iocb->ki_pos >= isize) {
+		mutex_unlock(&zi->i_truncate_mutex);
+		ret = 0;
+		goto out;
+	}
+	iov_iter_truncate(to, isize - iocb->ki_pos);
+	mutex_unlock(&zi->i_truncate_mutex);
+
+	if (iocb->ki_flags & IOCB_DIRECT) {
+		size_t count = iov_iter_count(to);
+
+		if ((iocb->ki_pos | count) & sbi->s_blocksize_mask) {
+			ret = -EINVAL;
+			goto out;
+		}
+		file_accessed(iocb->ki_filp);
+		ret = iomap_dio_rw(iocb, to, &zonefs_iomap_ops,
+				   &zonefs_read_dio_ops, is_sync_kiocb(iocb));
+	} else {
+		ret = generic_file_read_iter(iocb, to);
+		if (ret == -EIO)
+			zonefs_io_error(inode, false);
+	}
+
+out:
+	inode_unlock_shared(inode);
+
+	return ret;
+}
+
+static const struct file_operations zonefs_file_operations = {
+	.open		= generic_file_open,
+	.fsync		= zonefs_file_fsync,
+	.mmap		= zonefs_file_mmap,
+	.llseek		= zonefs_file_llseek,
+	.read_iter	= zonefs_file_read_iter,
+	.write_iter	= zonefs_file_write_iter,
+	.splice_read	= generic_file_splice_read,
+	.splice_write	= iter_file_splice_write,
+	.iopoll		= iomap_dio_iopoll,
+};
+
+static struct kmem_cache *zonefs_inode_cachep;
+
+static struct inode *zonefs_alloc_inode(struct super_block *sb)
+{
+	struct zonefs_inode_info *zi;
+
+	zi = kmem_cache_alloc(zonefs_inode_cachep, GFP_KERNEL);
+	if (!zi)
+		return NULL;
+
+	inode_init_once(&zi->i_vnode);
+	mutex_init(&zi->i_truncate_mutex);
+	init_rwsem(&zi->i_mmap_sem);
+
+	return &zi->i_vnode;
+}
+
+static void zonefs_free_inode(struct inode *inode)
+{
+	kmem_cache_free(zonefs_inode_cachep, ZONEFS_I(inode));
+}
+
+/*
+ * File system stat.
+ */
+static int zonefs_statfs(struct dentry *dentry, struct kstatfs *buf)
+{
+	struct super_block *sb = dentry->d_sb;
+	struct zonefs_sb_info *sbi = ZONEFS_SB(sb);
+	enum zonefs_ztype t;
+	u64 fsid;
+
+	buf->f_type = ZONEFS_MAGIC;
+	buf->f_bsize = sb->s_blocksize;
+	buf->f_namelen = ZONEFS_NAME_MAX;
+
+	spin_lock(&sbi->s_lock);
+
+	buf->f_blocks = sbi->s_blocks;
+	if (WARN_ON(sbi->s_used_blocks > sbi->s_blocks))
+		buf->f_bfree = 0;
+	else
+		buf->f_bfree = buf->f_blocks - sbi->s_used_blocks;
+	buf->f_bavail = buf->f_bfree;
+
+	for (t = 0; t < ZONEFS_ZTYPE_MAX; t++) {
+		if (sbi->s_nr_files[t])
+			buf->f_files += sbi->s_nr_files[t] + 1;
+	}
+	buf->f_ffree = 0;
+
+	spin_unlock(&sbi->s_lock);
+
+	fsid = le64_to_cpup((void *)sbi->s_uuid.b) ^
+		le64_to_cpup((void *)sbi->s_uuid.b + sizeof(u64));
+	buf->f_fsid.val[0] = (u32)fsid;
+	buf->f_fsid.val[1] = (u32)(fsid >> 32);
+
+	return 0;
+}
+
+enum {
+	Opt_errors_cont, Opt_errors_panic, Opt_errors_ro,
+	Opt_err,
+};
+
+static const match_table_t tokens = {
+	{ Opt_errors_cont,	"errors=continue"},
+	{ Opt_errors_panic,	"errors=panic"},
+	{ Opt_errors_ro,	"errors=remount-ro"},
+	{ Opt_err,		NULL}
+};
+
+static int zonefs_parse_options(struct super_block *sb, char *options)
+{
+	struct zonefs_sb_info *sbi = ZONEFS_SB(sb);
+	substring_t args[MAX_OPT_ARGS];
+	char *p;
+
+	if (!options)
+		return 0;
+
+	while ((p = strsep(&options, ",")) != NULL) {
+		int token;
+
+		if (!*p)
+			continue;
+
+		token = match_token(p, tokens, args);
+		switch (token) {
+		case Opt_errors_cont:
+			sbi->s_mount_opts &= ~ZONEFS_MNTOPT_ERRORS_RO;
+			sbi->s_mount_opts &= ~ZONEFS_MNTOPT_ERRORS_PANIC;
+			sbi->s_mount_opts |= ZONEFS_MNTOPT_ERRORS_CONT;
+			break;
+		case Opt_errors_ro:
+			sbi->s_mount_opts &= ~ZONEFS_MNTOPT_ERRORS_CONT;
+			sbi->s_mount_opts &= ~ZONEFS_MNTOPT_ERRORS_PANIC;
+			sbi->s_mount_opts |= ZONEFS_MNTOPT_ERRORS_RO;
+			break;
+		case Opt_errors_panic:
+			sbi->s_mount_opts &= ~ZONEFS_MNTOPT_ERRORS_RO;
+			sbi->s_mount_opts &= ~ZONEFS_MNTOPT_ERRORS_CONT;
+			sbi->s_mount_opts |= ZONEFS_MNTOPT_ERRORS_PANIC;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+static int zonefs_show_options(struct seq_file *seq, struct dentry *root)
+{
+	struct zonefs_sb_info *sbi = ZONEFS_SB(root->d_sb);
+
+	if (sbi->s_mount_opts & ZONEFS_MNTOPT_ERRORS_CONT)
+		seq_puts(seq, ",errors=continue");
+	if (sbi->s_mount_opts & ZONEFS_MNTOPT_ERRORS_RO)
+		seq_puts(seq, ",errors=ro");
+	if (sbi->s_mount_opts & ZONEFS_MNTOPT_ERRORS_PANIC)
+		seq_puts(seq, ",errors=panic");
+
+	return 0;
+}
+
+static int zonefs_remount(struct super_block *sb, int *flags, char *data)
+{
+	sync_filesystem(sb);
+
+	return zonefs_parse_options(sb, data);
+}
+
+static const struct super_operations zonefs_sops = {
+	.alloc_inode	= zonefs_alloc_inode,
+	.free_inode	= zonefs_free_inode,
+	.statfs		= zonefs_statfs,
+	.remount_fs	= zonefs_remount,
+	.show_options	= zonefs_show_options,
+};
+
+static const struct inode_operations zonefs_dir_inode_operations = {
+	.lookup		= simple_lookup,
+	.setattr	= zonefs_inode_setattr,
+};
+
+static void zonefs_init_dir_inode(struct inode *parent, struct inode *inode,
+				  enum zonefs_ztype type)
+{
+	struct super_block *sb = parent->i_sb;
+
+	inode->i_ino = blkdev_nr_zones(sb->s_bdev->bd_disk) + type + 1;
+	inode_init_owner(inode, parent, S_IFDIR | 0555);
+	inode->i_op = &zonefs_dir_inode_operations;
+	inode->i_fop = &simple_dir_operations;
+	set_nlink(inode, 2);
+	inc_nlink(parent);
+}
+
+static void zonefs_init_file_inode(struct inode *inode, struct blk_zone *zone,
+				   enum zonefs_ztype type)
+{
+	struct super_block *sb = inode->i_sb;
+	struct zonefs_sb_info *sbi = ZONEFS_SB(sb);
+	struct zonefs_inode_info *zi = ZONEFS_I(inode);
+
+	inode->i_ino = zone->start >> sbi->s_zone_sectors_shift;
+	inode->i_mode = S_IFREG | sbi->s_perm;
+	zonefs_update_file_perm(inode, zone);
+
+	zi->i_ztype = type;
+	zi->i_zsector = zone->start;
+	zi->i_max_size = min_t(loff_t, MAX_LFS_FILESIZE,
+			       zone->len << SECTOR_SHIFT);
+	if (zi->i_ztype == ZONEFS_ZTYPE_CNV)
+		zi->i_wpoffset = zi->i_max_size;
+	else
+		zi->i_wpoffset = (zone->wp - zone->start) << SECTOR_SHIFT;
+
+	inode->i_uid = sbi->s_uid;
+	inode->i_gid = sbi->s_gid;
+	inode->i_size = zi->i_wpoffset;
+	inode->i_blocks = zone->len;
+
+	inode->i_op = &zonefs_file_inode_operations;
+	inode->i_fop = &zonefs_file_operations;
+	inode->i_mapping->a_ops = &zonefs_file_aops;
+
+	sb->s_maxbytes = max(zi->i_max_size, sb->s_maxbytes);
+	sbi->s_blocks += zi->i_max_size >> sb->s_blocksize_bits;
+	sbi->s_used_blocks += zi->i_wpoffset >> sb->s_blocksize_bits;
+}
+
+static struct dentry *zonefs_create_inode(struct dentry *parent,
+					const char *name, struct blk_zone *zone,
+					enum zonefs_ztype type)
+{
+	struct inode *dir = d_inode(parent);
+	struct dentry *dentry;
+	struct inode *inode;
+
+	dentry = d_alloc_name(parent, name);
+	if (!dentry)
+		return NULL;
+
+	inode = new_inode(parent->d_sb);
+	if (!inode)
+		goto out;
+
+	inode->i_ctime = inode->i_mtime = inode->i_atime = dir->i_ctime;
+	if (zone)
+		zonefs_init_file_inode(inode, zone, type);
+	else
+		zonefs_init_dir_inode(dir, inode, type);
+	d_add(dentry, inode);
+	dir->i_size++;
+
+	return dentry;
+
+out:
+	dput(dentry);
+
+	return NULL;
+}
+
+static char *zgroups_name[ZONEFS_ZTYPE_MAX] = { "cnv", "seq" };
+
+struct zonefs_zone_data {
+	struct super_block *sb;
+	unsigned int nr_zones[ZONEFS_ZTYPE_MAX];
+	struct blk_zone *zones;
+};
+
+/*
+ * Create a zone group and populate it with zone files.
+ */
+static int zonefs_create_zgroup(struct zonefs_zone_data *zd,
+				enum zonefs_ztype type)
+{
+	struct super_block *sb = zd->sb;
+	struct zonefs_sb_info *sbi = ZONEFS_SB(sb);
+	struct blk_zone *zone, *next, *end;
+	char name[ZONEFS_NAME_MAX];
+	struct dentry *dir;
+	unsigned int n = 0;
+
+	/* If the group is empty, there is nothing to do */
+	if (!zd->nr_zones[type])
+		return 0;
+
+	dir = zonefs_create_inode(sb->s_root, zgroups_name[type], NULL, type);
+	if (!dir)
+		return -ENOMEM;
+
+	/*
+	 * The first zone contains the super block: skip it.
+	 */
+	end = zd->zones + blkdev_nr_zones(sb->s_bdev->bd_disk);
+	for (zone = &zd->zones[1]; zone < end; zone = next) {
+
+		next = zone + 1;
+		if (zonefs_zone_type(zone) != type)
+			continue;
+
+		/*
+		 * For conventional zones, contiguous zones can be aggregated
+		 * together to form larger files.
+		 * Note that this overwrites the length of the first zone of
+		 * the set of contiguous zones aggregated together.
+		 * Only zones with the same condition can be agreggated so that
+		 * offline zones are excluded and readonly zones are aggregated
+		 * together into a read only file.
+		 */
+		if (type == ZONEFS_ZTYPE_CNV &&
+		    (sbi->s_features & ZONEFS_F_AGGRCNV)) {
+			for (; next < end; next++) {
+				if (zonefs_zone_type(next) != type ||
+				    next->cond != zone->cond)
+					break;
+				zone->len += next->len;
+			}
+		}
+
+		/*
+		 * Use the file number within its group as file name.
+		 */
+		snprintf(name, ZONEFS_NAME_MAX - 1, "%u", n);
+		if (!zonefs_create_inode(dir, name, zone, type))
+			return -ENOMEM;
+
+		n++;
+	}
+
+	zonefs_info(sb, "Zone group \"%s\" has %u file%s\n",
+		    zgroups_name[type], n, n > 1 ? "s" : "");
+
+	sbi->s_nr_files[type] = n;
+
+	return 0;
+}
+
+static int zonefs_get_zone_info_cb(struct blk_zone *zone, unsigned int idx,
+				   void *data)
+{
+	struct zonefs_zone_data *zd = data;
+
+	/*
+	 * Count the number of usable zones: the first zone at index 0 contains
+	 * the super block and is ignored.
+	 */
+	switch (zone->type) {
+	case BLK_ZONE_TYPE_CONVENTIONAL:
+		zone->wp = zone->start + zone->len;
+		if (idx)
+			zd->nr_zones[ZONEFS_ZTYPE_CNV]++;
+		break;
+	case BLK_ZONE_TYPE_SEQWRITE_REQ:
+	case BLK_ZONE_TYPE_SEQWRITE_PREF:
+		if (idx)
+			zd->nr_zones[ZONEFS_ZTYPE_SEQ]++;
+		break;
+	default:
+		zonefs_err(zd->sb, "Unsupported zone type 0x%x\n",
+			   zone->type);
+		return -EIO;
+	}
+
+	memcpy(&zd->zones[idx], zone, sizeof(struct blk_zone));
+
+	return 0;
+}
+
+static int zonefs_get_zone_info(struct zonefs_zone_data *zd)
+{
+	struct block_device *bdev = zd->sb->s_bdev;
+	int ret;
+
+	zd->zones = kvcalloc(blkdev_nr_zones(bdev->bd_disk),
+			     sizeof(struct blk_zone), GFP_KERNEL);
+	if (!zd->zones)
+		return -ENOMEM;
+
+	/* Get zones information */
+	ret = blkdev_report_zones(bdev, 0, BLK_ALL_ZONES,
+				  zonefs_get_zone_info_cb, zd);
+	if (ret < 0) {
+		zonefs_err(zd->sb, "Zone report failed %d\n", ret);
+		return ret;
+	}
+
+	if (ret != blkdev_nr_zones(bdev->bd_disk)) {
+		zonefs_err(zd->sb, "Invalid zone report (%d/%u zones)\n",
+			   ret, blkdev_nr_zones(bdev->bd_disk));
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static inline void zonefs_cleanup_zone_info(struct zonefs_zone_data *zd)
+{
+	kvfree(zd->zones);
+}
+
+/*
+ * Read super block information from the device.
+ */
+static int zonefs_read_super(struct super_block *sb)
+{
+	struct zonefs_sb_info *sbi = ZONEFS_SB(sb);
+	struct zonefs_super *super;
+	u32 crc, stored_crc;
+	struct page *page;
+	struct bio_vec bio_vec;
+	struct bio bio;
+	int ret;
+
+	page = alloc_page(GFP_KERNEL);
+	if (!page)
+		return -ENOMEM;
+
+	bio_init(&bio, &bio_vec, 1);
+	bio.bi_iter.bi_sector = 0;
+	bio.bi_opf = REQ_OP_READ;
+	bio_set_dev(&bio, sb->s_bdev);
+	bio_add_page(&bio, page, PAGE_SIZE, 0);
+
+	ret = submit_bio_wait(&bio);
+	if (ret)
+		goto out_free;
+
+	super = kmap(page);
+
+	ret = -EINVAL;
+	if (le32_to_cpu(super->s_magic) != ZONEFS_MAGIC)
+		goto out;
+
+	stored_crc = le32_to_cpu(super->s_crc);
+	super->s_crc = 0;
+	crc = crc32(~0U, (unsigned char *)super, sizeof(struct zonefs_super));
+	if (crc != stored_crc) {
+		zonefs_err(sb, "Invalid checksum (Expected 0x%08x, got 0x%08x)",
+			   crc, stored_crc);
+		goto out;
+	}
+
+	sbi->s_features = le64_to_cpu(super->s_features);
+	if (sbi->s_features & ~ZONEFS_F_DEFINED_FEATURES) {
+		zonefs_err(sb, "Unknown features set 0x%llx\n",
+			   sbi->s_features);
+		goto out;
+	}
+
+	if (sbi->s_features & ZONEFS_F_UID) {
+		sbi->s_uid = make_kuid(current_user_ns(),
+				       le32_to_cpu(super->s_uid));
+		if (!uid_valid(sbi->s_uid)) {
+			zonefs_err(sb, "Invalid UID feature\n");
+			goto out;
+		}
+	}
+
+	if (sbi->s_features & ZONEFS_F_GID) {
+		sbi->s_gid = make_kgid(current_user_ns(),
+				       le32_to_cpu(super->s_gid));
+		if (!gid_valid(sbi->s_gid)) {
+			zonefs_err(sb, "Invalid GID feature\n");
+			goto out;
+		}
+	}
+
+	if (sbi->s_features & ZONEFS_F_PERM)
+		sbi->s_perm = le32_to_cpu(super->s_perm);
+
+	if (memchr_inv(super->s_reserved, 0, sizeof(super->s_reserved))) {
+		zonefs_err(sb, "Reserved area is being used\n");
+		goto out;
+	}
+
+	uuid_copy(&sbi->s_uuid, (uuid_t *)super->s_uuid);
+	ret = 0;
+
+out:
+	kunmap(page);
+out_free:
+	__free_page(page);
+
+	return ret;
+}
+
+/*
+ * Check that the device is zoned. If it is, get the list of zones and create
+ * sub-directories and files according to the device zone configuration and
+ * format options.
+ */
+static int zonefs_fill_super(struct super_block *sb, void *data, int silent)
+{
+	struct zonefs_zone_data zd;
+	struct zonefs_sb_info *sbi;
+	struct inode *inode;
+	enum zonefs_ztype t;
+	int ret;
+
+	if (!bdev_is_zoned(sb->s_bdev)) {
+		zonefs_err(sb, "Not a zoned block device\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * Initialize super block information: the maximum file size is updated
+	 * when the zone files are created so that the format option
+	 * ZONEFS_F_AGGRCNV which increases the maximum file size of a file
+	 * beyond the zone size is taken into account.
+	 */
+	sbi = kzalloc(sizeof(*sbi), GFP_KERNEL);
+	if (!sbi)
+		return -ENOMEM;
+
+	spin_lock_init(&sbi->s_lock);
+	sb->s_fs_info = sbi;
+	sb->s_magic = ZONEFS_MAGIC;
+	sb->s_maxbytes = 0;
+	sb->s_op = &zonefs_sops;
+	sb->s_time_gran	= 1;
+
+	/*
+	 * The block size is set to the device physical sector size to ensure
+	 * that write operations on 512e devices (512B logical block and 4KB
+	 * physical block) are always aligned to the device physical blocks,
+	 * as mandated by the ZBC/ZAC specifications.
+	 */
+	sb_set_blocksize(sb, bdev_physical_block_size(sb->s_bdev));
+	sbi->s_blocksize_mask = sb->s_blocksize - 1;
+	sbi->s_zone_sectors_shift = ilog2(bdev_zone_sectors(sb->s_bdev));
+	sbi->s_uid = GLOBAL_ROOT_UID;
+	sbi->s_gid = GLOBAL_ROOT_GID;
+	sbi->s_perm = 0640;
+	sbi->s_mount_opts = ZONEFS_MNTOPT_ERRORS_RO;
+
+	ret = zonefs_read_super(sb);
+	if (ret)
+		return ret;
+
+	ret = zonefs_parse_options(sb, data);
+	if (ret)
+		return ret;
+
+	memset(&zd, 0, sizeof(struct zonefs_zone_data));
+	zd.sb = sb;
+	ret = zonefs_get_zone_info(&zd);
+	if (ret)
+		goto out;
+
+	zonefs_info(sb, "Mounting %u zones",
+		    blkdev_nr_zones(sb->s_bdev->bd_disk));
+
+	/* Create root directory inode */
+	ret = -ENOMEM;
+	inode = new_inode(sb);
+	if (!inode)
+		goto out;
+
+	inode->i_ino = blkdev_nr_zones(sb->s_bdev->bd_disk);
+	inode->i_mode = S_IFDIR | 0555;
+	inode->i_ctime = inode->i_mtime = inode->i_atime = current_time(inode);
+	inode->i_op = &zonefs_dir_inode_operations;
+	inode->i_fop = &simple_dir_operations;
+	set_nlink(inode, 2);
+
+	sb->s_root = d_make_root(inode);
+	if (!sb->s_root)
+		goto out;
+
+	/* Create and populate files in zone groups directories */
+	for (t = 0; t < ZONEFS_ZTYPE_MAX; t++) {
+		ret = zonefs_create_zgroup(&zd, t);
+		if (ret)
+			break;
+	}
+
+out:
+	zonefs_cleanup_zone_info(&zd);
+
+	return ret;
+}
+
+static struct dentry *zonefs_mount(struct file_system_type *fs_type,
+				   int flags, const char *dev_name, void *data)
+{
+	return mount_bdev(fs_type, flags, dev_name, data, zonefs_fill_super);
+}
+
+static void zonefs_kill_super(struct super_block *sb)
+{
+	struct zonefs_sb_info *sbi = ZONEFS_SB(sb);
+
+	if (sb->s_root)
+		d_genocide(sb->s_root);
+	kill_block_super(sb);
+	kfree(sbi);
+}
+
+/*
+ * File system definition and registration.
+ */
+static struct file_system_type zonefs_type = {
+	.owner		= THIS_MODULE,
+	.name		= "zonefs",
+	.mount		= zonefs_mount,
+	.kill_sb	= zonefs_kill_super,
+	.fs_flags	= FS_REQUIRES_DEV,
+};
+
+static int __init zonefs_init_inodecache(void)
+{
+	zonefs_inode_cachep = kmem_cache_create("zonefs_inode_cache",
+			sizeof(struct zonefs_inode_info), 0,
+			(SLAB_RECLAIM_ACCOUNT | SLAB_MEM_SPREAD | SLAB_ACCOUNT),
+			NULL);
+	if (zonefs_inode_cachep == NULL)
+		return -ENOMEM;
+	return 0;
+}
+
+static void zonefs_destroy_inodecache(void)
+{
+	/*
+	 * Make sure all delayed rcu free inodes are flushed before we
+	 * destroy the inode cache.
+	 */
+	rcu_barrier();
+	kmem_cache_destroy(zonefs_inode_cachep);
+}
+
+static int __init zonefs_init(void)
+{
+	int ret;
+
+	BUILD_BUG_ON(sizeof(struct zonefs_super) != ZONEFS_SUPER_SIZE);
+
+	ret = zonefs_init_inodecache();
+	if (ret)
+		return ret;
+
+	ret = register_filesystem(&zonefs_type);
+	if (ret) {
+		zonefs_destroy_inodecache();
+		return ret;
+	}
+
+	return 0;
+}
+
+static void __exit zonefs_exit(void)
+{
+	zonefs_destroy_inodecache();
+	unregister_filesystem(&zonefs_type);
+}
+
+MODULE_AUTHOR("Damien Le Moal");
+MODULE_DESCRIPTION("Zone file system for zoned block devices");
+MODULE_LICENSE("GPL");
+module_init(zonefs_init);
+module_exit(zonefs_exit);
diff --git a/fs/zonefs/zonefs.h b/fs/zonefs/zonefs.h
new file mode 100644
index 000000000000..5625aecac1ad
--- /dev/null
+++ b/fs/zonefs/zonefs.h
@@ -0,0 +1,187 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Simple zone file system for zoned block devices.
+ *
+ * Copyright (C) 2019 Western Digital Corporation or its affiliates.
+ */
+#ifndef __ZONEFS_H__
+#define __ZONEFS_H__
+
+#include <linux/fs.h>
+#include <linux/magic.h>
+#include <linux/uuid.h>
+#include <linux/mutex.h>
+#include <linux/rwsem.h>
+
+/*
+ * Maximum length of file names: this only needs to be large enough to fit
+ * the zone group directory names and a decimal zone number for file names.
+ * 16 characters is plenty.
+ */
+#define ZONEFS_NAME_MAX		16
+
+/*
+ * Zone types: ZONEFS_ZTYPE_SEQ is used for all sequential zone types
+ * defined in linux/blkzoned.h, that is, BLK_ZONE_TYPE_SEQWRITE_REQ and
+ * BLK_ZONE_TYPE_SEQWRITE_PREF.
+ */
+enum zonefs_ztype {
+	ZONEFS_ZTYPE_CNV,
+	ZONEFS_ZTYPE_SEQ,
+	ZONEFS_ZTYPE_MAX,
+};
+
+static inline enum zonefs_ztype zonefs_zone_type(struct blk_zone *zone)
+{
+	if (zone->type == BLK_ZONE_TYPE_CONVENTIONAL)
+		return ZONEFS_ZTYPE_CNV;
+	return ZONEFS_ZTYPE_SEQ;
+}
+
+/*
+ * In-memory inode data.
+ */
+struct zonefs_inode_info {
+	struct inode		i_vnode;
+
+	/* File zone type */
+	enum zonefs_ztype	i_ztype;
+
+	/* File zone start sector (512B unit) */
+	sector_t		i_zsector;
+
+	/* File zone write pointer position (sequential zones only) */
+	loff_t			i_wpoffset;
+
+	/* File maximum size */
+	loff_t			i_max_size;
+
+	/*
+	 * To serialise fully against both syscall and mmap based IO and
+	 * sequential file truncation, two locks are used. For serializing
+	 * zonefs_seq_file_truncate() against zonefs_iomap_begin(), that is,
+	 * file truncate operations against block mapping, i_truncate_mutex is
+	 * used. i_truncate_mutex also protects against concurrent accesses
+	 * and changes to the inode private data, and in particular changes to
+	 * a sequential file size on completion of direct IO writes.
+	 * Serialization of mmap read IOs with truncate and syscall IO
+	 * operations is done with i_mmap_sem in addition to i_truncate_mutex.
+	 * Only zonefs_seq_file_truncate() takes both lock (i_mmap_sem first,
+	 * i_truncate_mutex second).
+	 */
+	struct mutex		i_truncate_mutex;
+	struct rw_semaphore	i_mmap_sem;
+};
+
+static inline struct zonefs_inode_info *ZONEFS_I(struct inode *inode)
+{
+	return container_of(inode, struct zonefs_inode_info, i_vnode);
+}
+
+/*
+ * On-disk super block (block 0).
+ */
+#define ZONEFS_LABEL_LEN	64
+#define ZONEFS_UUID_SIZE	16
+#define ZONEFS_SUPER_SIZE	4096
+
+struct zonefs_super {
+
+	/* Magic number */
+	__le32		s_magic;
+
+	/* Checksum */
+	__le32		s_crc;
+
+	/* Volume label */
+	char		s_label[ZONEFS_LABEL_LEN];
+
+	/* 128-bit uuid */
+	__u8		s_uuid[ZONEFS_UUID_SIZE];
+
+	/* Features */
+	__le64		s_features;
+
+	/* UID/GID to use for files */
+	__le32		s_uid;
+	__le32		s_gid;
+
+	/* File permissions */
+	__le32		s_perm;
+
+	/* Padding to ZONEFS_SUPER_SIZE bytes */
+	__u8		s_reserved[3988];
+
+} __packed;
+
+/*
+ * Feature flags: used on disk in the s_features field of struct zonefs_super
+ * and in-memory in the s_feartures field of struct zonefs_sb_info.
+ */
+enum zonefs_features {
+	/*
+	 * Aggregate contiguous conventional zones into a single file.
+	 */
+	ZONEFS_F_AGGRCNV = 1ULL << 0,
+	/*
+	 * Use super block specified UID for files instead of default.
+	 */
+	ZONEFS_F_UID = 1ULL << 1,
+	/*
+	 * Use super block specified GID for files instead of default.
+	 */
+	ZONEFS_F_GID = 1ULL << 2,
+	/*
+	 * Use super block specified file permissions instead of default 640.
+	 */
+	ZONEFS_F_PERM = 1ULL << 3,
+};
+
+#define ZONEFS_F_DEFINED_FEATURES \
+	(ZONEFS_F_AGGRCNV | ZONEFS_F_UID | ZONEFS_F_GID | ZONEFS_F_PERM)
+
+/*
+ * Mount options for error handling.
+ */
+#define ZONEFS_MNTOPT_ERRORS_CONT	(1 << 0)
+#define ZONEFS_MNTOPT_ERRORS_RO		(1 << 1)
+#define ZONEFS_MNTOPT_ERRORS_PANIC	(1 << 2)
+
+/*
+ * In-memory Super block information.
+ */
+struct zonefs_sb_info {
+
+	unsigned long		s_mount_opts;
+
+	spinlock_t		s_lock;
+
+	unsigned long long	s_features;
+	kuid_t			s_uid;
+	kgid_t			s_gid;
+	umode_t			s_perm;
+	uuid_t			s_uuid;
+	loff_t			s_blocksize_mask;
+	unsigned int		s_zone_sectors_shift;
+
+	unsigned int		s_nr_files[ZONEFS_ZTYPE_MAX];
+
+	loff_t			s_blocks;
+	loff_t			s_used_blocks;
+};
+
+static inline struct zonefs_sb_info *ZONEFS_SB(struct super_block *sb)
+{
+	return sb->s_fs_info;
+}
+
+#define zonefs_info(sb, format, args...)	\
+	pr_info("zonefs (%s): " format, sb->s_id, ## args)
+#define zonefs_err(sb, format, args...)		\
+	pr_err("zonefs (%s) ERROR: " format, sb->s_id, ## args)
+#define zonefs_warn(sb, format, args...)	\
+	pr_warn("zonefs (%s) WARNING: " format, sb->s_id, ## args)
+#define zonefs_panic(sb, format, args...)	\
+	panic("zonefs (%s) PANIC: " format, sb->s_id, ## args)
+
+#endif
diff --git a/include/uapi/linux/magic.h b/include/uapi/linux/magic.h
index 3ac436376d79..d78064007b17 100644
--- a/include/uapi/linux/magic.h
+++ b/include/uapi/linux/magic.h
@@ -87,6 +87,7 @@
 #define NSFS_MAGIC		0x6e736673
 #define BPF_FS_MAGIC		0xcafe4a11
 #define AAFS_MAGIC		0x5a3c69f0
+#define ZONEFS_MAGIC		0x5a4f4653
 
 /* Since UDF 2.01 is ISO 13346 based... */
 #define UDF_SUPER_MAGIC		0x15013346
-- 
2.24.1


^ permalink raw reply related	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2020-01-30 22:59 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-01-28 16:24 [PATCH v9 1/2] fs: New zonefs file system Markus Elfring
2020-01-29  4:14 ` Damien Le Moal
  -- strict thread matches above, loose matches on Subject: below --
2020-01-28 15:34 Markus Elfring
2020-01-29  4:13 ` Damien Le Moal
2020-01-27 12:49 Markus Elfring
2020-01-28  1:26 ` Damien Le Moal
2020-01-28  1:28   ` Linus Torvalds
2020-01-28  1:34     ` Damien Le Moal
2020-01-27 10:05 [PATCH v9 0/2] " Damien Le Moal
2020-01-27 10:05 ` [PATCH v9 1/2] fs: " Damien Le Moal
2020-01-28 17:46   ` Darrick J. Wong
2020-01-29 13:06     ` Damien Le Moal
2020-01-29 21:33       ` Dave Chinner
2020-01-30  3:00         ` Damien Le Moal
2020-01-30 22:59           ` Dave Chinner

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).