Commit db05cffc authored by Anton Altaparmakov's avatar Anton Altaparmakov

NTFS: 2.0.15 - Fake inodes based attribute i/o via the pagecache, fixes, cleanups.

- Fix silly bug in fs/ntfs/super.c::parse_options() which was causing
  remounts to fail when the partition had an entry in /etc/fstab and
  the entry specified the nls= option.
- Apply same macro magic used in fs/ntfs/inode.h to fs/ntfs/volume.h to
  expand all the helper functions NVolFoo(), NVolSetFoo(), and
  NVolClearFoo().
- Move copyright statement from driver initialisation message to
  module description (fs/super.c). This makes the initialisation
  message fit on one line and fits in better with rest of kernel.
- Update fs/ntfs/attrib.c::map_run_list() to work on both real and
  attribute inodes, and both for files and directories.
- Implement fake attribute inodes allowing all attribute i/o to go via
  the page cache and to use all the normal vfs/mm functionality:
  - Add ntfs_attr_iget() and its helper ntfs_read_locked_attr_inode()
    to fs/ntfs/inode.c.
  - Add needed cleanup code to ntfs_clear_big_inode().
- Merge address space operations for files and directories (aops.c),
  now just have ntfs_aops:
  - Rename:
        end_buffer_read_attr_async() -> ntfs_end_buffer_read_async(),
        ntfs_attr_read_block()       -> ntfs_read_block(),
        ntfs_file_read_page()        -> ntfs_readpage().
  - Rewrite fs/ntfs/aops.c::ntfs_readpage() to work on both real and
    attribute inodes, and both for files and directories.
  - Remove obsolete fs/ntfs/aops.c::ntfs_mst_readpage().
parent 5a597e77
...@@ -247,6 +247,11 @@ ChangeLog ...@@ -247,6 +247,11 @@ ChangeLog
Note, a technical ChangeLog aimed at kernel hackers is in fs/ntfs/ChangeLog. Note, a technical ChangeLog aimed at kernel hackers is in fs/ntfs/ChangeLog.
2.0.15:
- Bug fix in parsing of remount options.
- Internal changes implementing attribute (fake) inodes allowing all
attribute i/o to go via the page cache and to use all the normal
vfs/mm functionality.
2.0.14: 2.0.14:
- Internal changes improving run list merging code and minor locking - Internal changes improving run list merging code and minor locking
change to not rely on BKL in ntfs_statfs(). change to not rely on BKL in ntfs_statfs().
......
...@@ -26,7 +26,34 @@ ToDo: ...@@ -26,7 +26,34 @@ ToDo:
callers, i.e. ntfs_iget(), to pass that error code up instead of just callers, i.e. ntfs_iget(), to pass that error code up instead of just
using -EIO. using -EIO.
- Enable NFS exporting of NTFS. - Enable NFS exporting of NTFS.
- Use fake inodes for address space i/o.
2.0.15 - Fake inodes based attribute i/o via the pagecache, fixes and cleanups.
- Fix silly bug in fs/ntfs/super.c::parse_options() which was causing
remounts to fail when the partition had an entry in /etc/fstab and
the entry specified the nls= option.
- Apply same macro magic used in fs/ntfs/inode.h to fs/ntfs/volume.h to
expand all the helper functions NVolFoo(), NVolSetFoo(), and
NVolClearFoo().
- Move copyright statement from driver initialisation message to
module description (fs/super.c). This makes the initialisation
message fit on one line and fits in better with rest of kernel.
- Update fs/ntfs/attrib.c::map_run_list() to work on both real and
attribute inodes, and both for files and directories.
- Implement fake attribute inodes allowing all attribute i/o to go via
the page cache and to use all the normal vfs/mm functionality:
- Add ntfs_attr_iget() and its helper ntfs_read_locked_attr_inode()
to fs/ntfs/inode.c.
- Add needed cleanup code to ntfs_clear_big_inode().
- Merge address space operations for files and directories (aops.c),
now just have ntfs_aops:
- Rename:
end_buffer_read_attr_async() -> ntfs_end_buffer_read_async(),
ntfs_attr_read_block() -> ntfs_read_block(),
ntfs_file_read_page() -> ntfs_readpage().
- Rewrite fs/ntfs/aops.c::ntfs_readpage() to work on both real and
attribute inodes, and both for files and directories.
- Remove obsolete fs/ntfs/aops.c::ntfs_mst_readpage().
2.0.14 - Run list merging code cleanup, minor locking changes, typo fixes. 2.0.14 - Run list merging code cleanup, minor locking changes, typo fixes.
......
...@@ -5,7 +5,7 @@ obj-$(CONFIG_NTFS_FS) += ntfs.o ...@@ -5,7 +5,7 @@ obj-$(CONFIG_NTFS_FS) += ntfs.o
ntfs-objs := aops.o attrib.o compress.o debug.o dir.o file.o inode.o mft.o \ ntfs-objs := aops.o attrib.o compress.o debug.o dir.o file.o inode.o mft.o \
mst.o namei.o super.o sysctl.o time.o unistr.o upcase.o mst.o namei.o super.o sysctl.o time.o unistr.o upcase.o
EXTRA_CFLAGS = -DNTFS_VERSION=\"2.0.14\" EXTRA_CFLAGS = -DNTFS_VERSION=\"2.0.15\"
ifeq ($(CONFIG_NTFS_DEBUG),y) ifeq ($(CONFIG_NTFS_DEBUG),y)
EXTRA_CFLAGS += -DDEBUG EXTRA_CFLAGS += -DDEBUG
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
#include "ntfs.h" #include "ntfs.h"
/** /**
* end_buffer_read_attr_async - async io completion for reading attributes * ntfs_end_buffer_read_async - async io completion for reading attributes
* @bh: buffer head on which io is completed * @bh: buffer head on which io is completed
* @uptodate: whether @bh is now uptodate or not * @uptodate: whether @bh is now uptodate or not
* *
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
* record size, and index_block_size_bits, to the log(base 2) of the ntfs * record size, and index_block_size_bits, to the log(base 2) of the ntfs
* record size. * record size.
*/ */
static void end_buffer_read_attr_async(struct buffer_head *bh, int uptodate) static void ntfs_end_buffer_read_async(struct buffer_head *bh, int uptodate)
{ {
static spinlock_t page_uptodate_lock = SPIN_LOCK_UNLOCKED; static spinlock_t page_uptodate_lock = SPIN_LOCK_UNLOCKED;
unsigned long flags; unsigned long flags;
...@@ -143,12 +143,12 @@ static void end_buffer_read_attr_async(struct buffer_head *bh, int uptodate) ...@@ -143,12 +143,12 @@ static void end_buffer_read_attr_async(struct buffer_head *bh, int uptodate)
} }
/** /**
* ntfs_attr_read_block - fill a @page of an address space with data * ntfs_read_block - fill a @page of an address space with data
* @page: page cache page to fill with data * @page: page cache page to fill with data
* *
* Fill the page @page of the address space belonging to the @page->host inode. * Fill the page @page of the address space belonging to the @page->host inode.
* We read each buffer asynchronously and when all buffers are read in, our io * We read each buffer asynchronously and when all buffers are read in, our io
* completion handler end_buffer_read_attr_async(), if required, automatically * completion handler ntfs_end_buffer_read_async(), if required, automatically
* applies the mst fixups to the page before finally marking it uptodate and * applies the mst fixups to the page before finally marking it uptodate and
* unlocking it. * unlocking it.
* *
...@@ -156,7 +156,7 @@ static void end_buffer_read_attr_async(struct buffer_head *bh, int uptodate) ...@@ -156,7 +156,7 @@ static void end_buffer_read_attr_async(struct buffer_head *bh, int uptodate)
* *
* Contains an adapted version of fs/buffer.c::block_read_full_page(). * Contains an adapted version of fs/buffer.c::block_read_full_page().
*/ */
static int ntfs_attr_read_block(struct page *page) static int ntfs_read_block(struct page *page)
{ {
VCN vcn; VCN vcn;
LCN lcn; LCN lcn;
...@@ -267,7 +267,7 @@ static int ntfs_attr_read_block(struct page *page) ...@@ -267,7 +267,7 @@ static int ntfs_attr_read_block(struct page *page)
for (i = 0; i < nr; i++) { for (i = 0; i < nr; i++) {
struct buffer_head *tbh = arr[i]; struct buffer_head *tbh = arr[i];
lock_buffer(tbh); lock_buffer(tbh);
tbh->b_end_io = end_buffer_read_attr_async; tbh->b_end_io = ntfs_end_buffer_read_async;
set_buffer_async_read(tbh); set_buffer_async_read(tbh);
} }
/* Finally, start i/o on the buffers. */ /* Finally, start i/o on the buffers. */
...@@ -285,27 +285,27 @@ static int ntfs_attr_read_block(struct page *page) ...@@ -285,27 +285,27 @@ static int ntfs_attr_read_block(struct page *page)
} }
/** /**
* ntfs_file_readpage - fill a @page of a @file with data from the device * ntfs_readpage - fill a @page of a @file with data from the device
* @file: open file to which the page @page belongs or NULL * @file: open file to which the page @page belongs or NULL
* @page: page cache page to fill with data * @page: page cache page to fill with data
* *
* For non-resident attributes, ntfs_file_readpage() fills the @page of the open * For non-resident attributes, ntfs_readpage() fills the @page of the open
* file @file by calling the ntfs version of the generic block_read_full_page() * file @file by calling the ntfs version of the generic block_read_full_page()
* function provided by the kernel, ntfs_attr_read_block(), which in turn * function, ntfs_read_block(), which in turn creates and reads in the buffers
* creates and reads in the buffers associated with the page asynchronously. * associated with the page asynchronously.
* *
* For resident attributes, OTOH, ntfs_file_readpage() fills @page by copying * For resident attributes, OTOH, ntfs_readpage() fills @page by copying the
* the data from the mft record (which at this stage is most likely in memory) * data from the mft record (which at this stage is most likely in memory) and
* and fills the remainder with zeroes. Thus, in this case, I/O is synchronous, * fills the remainder with zeroes. Thus, in this case, I/O is synchronous, as
* as even if the mft record is not cached at this point in time, we need to * even if the mft record is not cached at this point in time, we need to wait
* wait for it to be read in before we can do the copy. * for it to be read in before we can do the copy.
* *
* Return 0 on success or -errno on error. * Return 0 on success and -errno on error.
*/ */
static int ntfs_file_readpage(struct file *file, struct page *page) int ntfs_readpage(struct file *file, struct page *page)
{ {
s64 attr_pos; s64 attr_pos;
ntfs_inode *ni; ntfs_inode *ni, *base_ni;
char *addr; char *addr;
attr_search_context *ctx; attr_search_context *ctx;
MFT_RECORD *mrec; MFT_RECORD *mrec;
...@@ -317,40 +317,45 @@ static int ntfs_file_readpage(struct file *file, struct page *page) ...@@ -317,40 +317,45 @@ static int ntfs_file_readpage(struct file *file, struct page *page)
ni = NTFS_I(page->mapping->host); ni = NTFS_I(page->mapping->host);
/* Is the unnamed $DATA attribute resident? */
if (NInoNonResident(ni)) { if (NInoNonResident(ni)) {
/* Attribute is not resident. */ /*
* Only unnamed $DATA attributes can be compressed or
/* If the file is encrypted, we deny access, just like NT4. */ * encrypted.
*/
if (ni->type == AT_DATA && !ni->name_len) {
/* If file is encrypted, deny access, just like NT4. */
if (NInoEncrypted(ni)) { if (NInoEncrypted(ni)) {
err = -EACCES; err = -EACCES;
goto unl_err_out; goto err_out;
} }
/* Compressed data stream. Handled in compress.c. */ /* Compressed data streams are handled in compress.c. */
if (NInoCompressed(ni)) if (NInoCompressed(ni))
return ntfs_file_read_compressed_block(page); return ntfs_file_read_compressed_block(page);
}
/* Normal data stream. */ /* Normal data stream. */
return ntfs_attr_read_block(page); return ntfs_read_block(page);
} }
/* Attribute is resident, implying it is not compressed or encrypted. */ /* Attribute is resident, implying it is not compressed or encrypted. */
if (!NInoAttr(ni))
base_ni = ni;
else
base_ni = ni->_INE(base_ntfs_ino);
/* Map, pin and lock the mft record for reading. */ /* Map, pin and lock the mft record for reading. */
mrec = map_mft_record(READ, ni); mrec = map_mft_record(READ, base_ni);
if (unlikely(IS_ERR(mrec))) { if (unlikely(IS_ERR(mrec))) {
err = PTR_ERR(mrec); err = PTR_ERR(mrec);
goto unl_err_out; goto err_out;
} }
ctx = get_attr_search_ctx(base_ni, mrec);
ctx = get_attr_search_ctx(ni, mrec);
if (unlikely(!ctx)) { if (unlikely(!ctx)) {
err = -ENOMEM; err = -ENOMEM;
goto unm_unl_err_out; goto unm_err_out;
} }
if (unlikely(!lookup_attr(ni->type, ni->name, ni->name_len,
/* Find the data attribute in the mft record. */ IGNORE_CASE, 0, NULL, 0, ctx))) {
if (unlikely(!lookup_attr(AT_DATA, NULL, 0, 0, 0, NULL, 0, ctx))) {
err = -ENOENT; err = -ENOENT;
goto put_unm_unl_err_out; goto put_unm_err_out;
} }
/* Starting position of the page within the attribute value. */ /* Starting position of the page within the attribute value. */
...@@ -377,34 +382,15 @@ static int ntfs_file_readpage(struct file *file, struct page *page) ...@@ -377,34 +382,15 @@ static int ntfs_file_readpage(struct file *file, struct page *page)
kunmap(page); kunmap(page);
SetPageUptodate(page); SetPageUptodate(page);
put_unm_unl_err_out: put_unm_err_out:
put_attr_search_ctx(ctx); put_attr_search_ctx(ctx);
unm_unl_err_out: unm_err_out:
unmap_mft_record(READ, ni); unmap_mft_record(READ, base_ni);
unl_err_out: err_out:
unlock_page(page); unlock_page(page);
return err; return err;
} }
/**
* ntfs_mst_readpage - fill a @page of the mft or a directory with data
* @file: open file/directory to which the @page belongs or NULL
* @page: page cache page to fill with data
*
* Readpage method for the VFS address space operations of directory inodes
* and the $MFT/$DATA attribute.
*
* We just call ntfs_attr_read_block() here, in fact we only need this wrapper
* because of the difference in function parameters.
*/
int ntfs_mst_readpage(struct file *file, struct page *page)
{
if (unlikely(!PageLocked(page)))
PAGE_BUG(page);
return ntfs_attr_read_block(page);
}
/** /**
* end_buffer_read_mftbmp_async - * end_buffer_read_mftbmp_async -
* *
...@@ -473,7 +459,7 @@ static void end_buffer_read_mftbmp_async(struct buffer_head *bh, int uptodate) ...@@ -473,7 +459,7 @@ static void end_buffer_read_mftbmp_async(struct buffer_head *bh, int uptodate)
/** /**
* ntfs_mftbmp_readpage - * ntfs_mftbmp_readpage -
* *
* Readpage for accessing mft bitmap. Adapted from ntfs_mst_readpage(). * Readpage for accessing mft bitmap.
*/ */
static int ntfs_mftbmp_readpage(ntfs_volume *vol, struct page *page) static int ntfs_mftbmp_readpage(ntfs_volume *vol, struct page *page)
{ {
...@@ -587,11 +573,11 @@ static int ntfs_mftbmp_readpage(ntfs_volume *vol, struct page *page) ...@@ -587,11 +573,11 @@ static int ntfs_mftbmp_readpage(ntfs_volume *vol, struct page *page)
} }
/** /**
* ntfs_file_aops - address space operations for accessing normal file data * ntfs_aops - general address space operations for inodes and attributes
*/ */
struct address_space_operations ntfs_file_aops = { struct address_space_operations ntfs_aops = {
writepage: NULL, /* Write dirty page to disk. */ writepage: NULL, /* Write dirty page to disk. */
readpage: ntfs_file_readpage, /* Fill page with data. */ readpage: ntfs_readpage, /* Fill page with data. */
sync_page: block_sync_page, /* Currently, just unplugs the sync_page: block_sync_page, /* Currently, just unplugs the
disk request queue. */ disk request queue. */
prepare_write: NULL, /* . */ prepare_write: NULL, /* . */
...@@ -613,20 +599,3 @@ struct address_space_operations ntfs_mftbmp_aops = { ...@@ -613,20 +599,3 @@ struct address_space_operations ntfs_mftbmp_aops = {
commit_write: NULL, /* . */ commit_write: NULL, /* . */
}; };
/**
* ntfs_dir_aops -
*
* Address space operations for accessing normal directory data (i.e. index
* allocation attribute). We can't just use the same operations as for files
* because 1) the attribute is different and even more importantly 2) the index
* records have to be multi sector transfer deprotected (i.e. fixed-up).
*/
struct address_space_operations ntfs_dir_aops = {
writepage: NULL, /* Write dirty page to disk. */
readpage: ntfs_mst_readpage, /* Fill page with data. */
sync_page: block_sync_page, /* Currently, just unplugs the
disk request queue. */
prepare_write: NULL, /* . */
commit_write: NULL, /* . */
};
...@@ -935,78 +935,51 @@ run_list_element *decompress_mapping_pairs(const ntfs_volume *vol, ...@@ -935,78 +935,51 @@ run_list_element *decompress_mapping_pairs(const ntfs_volume *vol,
*/ */
int map_run_list(ntfs_inode *ni, VCN vcn) int map_run_list(ntfs_inode *ni, VCN vcn)
{ {
ntfs_inode *base_ni;
attr_search_context *ctx; attr_search_context *ctx;
MFT_RECORD *mrec; MFT_RECORD *mrec;
const uchar_t *name;
u32 name_len;
ATTR_TYPES at;
int err = 0; int err = 0;
ntfs_debug("Mapping run list part containing vcn 0x%Lx.", ntfs_debug("Mapping run list part containing vcn 0x%Lx.",
(long long)vcn); (long long)vcn);
/* Map, pin and lock the mft record for reading. */ if (!NInoAttr(ni))
mrec = map_mft_record(READ, ni); base_ni = ni;
else
base_ni = ni->_INE(base_ntfs_ino);
mrec = map_mft_record(READ, base_ni);
if (IS_ERR(mrec)) if (IS_ERR(mrec))
return PTR_ERR(mrec); return PTR_ERR(mrec);
ctx = get_attr_search_ctx(base_ni, mrec);
ctx = get_attr_search_ctx(ni, mrec);
if (!ctx) { if (!ctx) {
err = -ENOMEM; err = -ENOMEM;
goto unm_err_out; goto err_out;
}
/* The attribute type is determined from the inode type. */
if (S_ISDIR(VFS_I(ni)->i_mode)) {
at = AT_INDEX_ALLOCATION;
name = I30;
name_len = 4;
} else {
at = AT_DATA;
name = NULL;
name_len = 0;
} }
if (!lookup_attr(ni->type, ni->name, ni->name_len, IGNORE_CASE, vcn,
/* Find the attribute in the mft record. */ NULL, 0, ctx)) {
if (!lookup_attr(at, name, name_len, CASE_SENSITIVE, vcn, NULL, 0,
ctx)) {
put_attr_search_ctx(ctx); put_attr_search_ctx(ctx);
err = -ENOENT; err = -ENOENT;
goto unm_err_out; goto err_out;
} }
/* Lock the run list. */
down_write(&ni->run_list.lock); down_write(&ni->run_list.lock);
/* Make sure someone else didn't do the work while we were spinning. */ /* Make sure someone else didn't do the work while we were spinning. */
if (likely(vcn_to_lcn(ni->run_list.rl, vcn) <= LCN_RL_NOT_MAPPED)) { if (likely(vcn_to_lcn(ni->run_list.rl, vcn) <= LCN_RL_NOT_MAPPED)) {
run_list_element *rl; run_list_element *rl;
/* Decode the run list. */
rl = decompress_mapping_pairs(ni->vol, ctx->attr, rl = decompress_mapping_pairs(ni->vol, ctx->attr,
ni->run_list.rl); ni->run_list.rl);
/* Flag any errors or set the run list if successful. */
if (unlikely(IS_ERR(rl))) if (unlikely(IS_ERR(rl)))
err = PTR_ERR(rl); err = PTR_ERR(rl);
else else
ni->run_list.rl = rl; ni->run_list.rl = rl;
} }
/* Unlock the run list. */
up_write(&ni->run_list.lock); up_write(&ni->run_list.lock);
put_attr_search_ctx(ctx); put_attr_search_ctx(ctx);
err_out:
/* Unlock, unpin and release the mft record. */ unmap_mft_record(READ, base_ni);
unmap_mft_record(READ, ni);
/* If an error occured, return it. */
ntfs_debug("Done.");
return err;
unm_err_out:
unmap_mft_record(READ, ni);
return err; return err;
} }
......
...@@ -462,6 +462,11 @@ int ntfs_file_read_compressed_block(struct page *page) ...@@ -462,6 +462,11 @@ int ntfs_file_read_compressed_block(struct page *page)
ntfs_debug("Entering, page->index = 0x%lx, cb_size = 0x%x, nr_pages = " ntfs_debug("Entering, page->index = 0x%lx, cb_size = 0x%x, nr_pages = "
"%i.", index, cb_size, nr_pages); "%i.", index, cb_size, nr_pages);
/*
* Bad things happen if we get here for anything that is not an
* unnamed $DATA attribute.
*/
BUG_ON(ni->type != AT_DATA || ni->name_len);
pages = kmalloc(nr_pages * sizeof(struct page *), GFP_NOFS); pages = kmalloc(nr_pages * sizeof(struct page *), GFP_NOFS);
......
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
* @type: attribute type (see layout.h) * @type: attribute type (see layout.h)
* *
* This structure exists only to provide a small structure for the * This structure exists only to provide a small structure for the
* ntfs_iget()/ntfs_test_inode()/ntfs_init_locked_inode() mechanism. * ntfs_{attr_}iget()/ntfs_test_inode()/ntfs_init_locked_inode() mechanism.
* *
* NOTE: Elements are ordered by size to make the structure as compact as * NOTE: Elements are ordered by size to make the structure as compact as
* possible on all architectures. * possible on all architectures.
...@@ -112,14 +112,21 @@ static int ntfs_init_locked_inode(struct inode *vi, ntfs_attr *na) ...@@ -112,14 +112,21 @@ static int ntfs_init_locked_inode(struct inode *vi, ntfs_attr *na)
ntfs_inode *ni = NTFS_I(vi); ntfs_inode *ni = NTFS_I(vi);
vi->i_ino = na->mft_no; vi->i_ino = na->mft_no;
ni->type = na->type; ni->type = na->type;
if (na->type == AT_INDEX_ALLOCATION)
NInoSetMstProtected(ni);
ni->name = na->name; ni->name = na->name;
ni->name_len = na->name_len; ni->name_len = na->name_len;
/* If initializing a normal inode, we are done. */ /* If initializing a normal inode, we are done. */
if (likely(na->type == AT_UNUSED)) if (likely(na->type == AT_UNUSED))
return 0; return 0;
/* It is a fake inode. */ /* It is a fake inode. */
NInoSetAttr(ni); NInoSetAttr(ni);
/* /*
* We have I30 global constant as an optimization as it is the name * We have I30 global constant as an optimization as it is the name
* in >99.9% of named attributes! The other <0.1% incur a GFP_ATOMIC * in >99.9% of named attributes! The other <0.1% incur a GFP_ATOMIC
...@@ -143,6 +150,7 @@ static int ntfs_init_locked_inode(struct inode *vi, ntfs_attr *na) ...@@ -143,6 +150,7 @@ static int ntfs_init_locked_inode(struct inode *vi, ntfs_attr *na)
typedef int (*test_t)(struct inode *, void *); typedef int (*test_t)(struct inode *, void *);
typedef int (*set_t)(struct inode *, void *); typedef int (*set_t)(struct inode *, void *);
static void ntfs_read_locked_inode(struct inode *vi); static void ntfs_read_locked_inode(struct inode *vi);
static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi);
/** /**
* ntfs_iget - obtain a struct inode corresponding to a specific normal inode * ntfs_iget - obtain a struct inode corresponding to a specific normal inode
...@@ -197,6 +205,62 @@ struct inode *ntfs_iget(struct super_block *sb, unsigned long mft_no) ...@@ -197,6 +205,62 @@ struct inode *ntfs_iget(struct super_block *sb, unsigned long mft_no)
return vi; return vi;
} }
/**
* ntfs_attr_iget - obtain a struct inode corresponding to an attribute
* @base_vi: vfs base inode containing the attribute
* @type: attribute type
* @name: Unicode name of the attribute (NULL if unnamed)
* @name_len: length of @name in Unicode characters (0 if unnamed)
*
* Obtain the (fake) struct inode corresponding to the attribute specified by
* @type, @name, and @name_len, which is present in the base mft record
* specified by the vfs inode @base_vi.
*
* If the attribute inode is in the cache, it is just returned with an
* increased reference count. Otherwise, a new struct inode is allocated and
* initialized, and finally ntfs_read_locked_attr_inode() is called to read the
* attribute and fill in the inode structure.
*
* Return the struct inode of the attribute inode on success. Check the return
* value with IS_ERR() and if true, the function failed and the error code is
* obtained from PTR_ERR().
*/
struct inode *ntfs_attr_iget(struct inode *base_vi, ATTR_TYPES type,
uchar_t *name, u32 name_len)
{
struct inode *vi;
ntfs_attr na;
int err;
na.mft_no = base_vi->i_ino;
na.type = type;
na.name = name;
na.name_len = name_len;
vi = iget5_locked(base_vi->i_sb, na.mft_no, (test_t)ntfs_test_inode,
(set_t)ntfs_init_locked_inode, &na);
if (!vi)
return ERR_PTR(-ENOMEM);
err = 0;
/* If this is a freshly allocated inode, need to read it now. */
if (vi->i_state & I_NEW) {
err = ntfs_read_locked_attr_inode(base_vi, vi);
unlock_new_inode(vi);
}
/*
* There is no point in keeping bad attribute inodes around. This also
* simplifies things in that we never need to check for bad attribute
* inodes elsewhere.
*/
if (err) {
iput(vi);
vi = ERR_PTR(-EIO);
}
return vi;
}
struct inode *ntfs_alloc_big_inode(struct super_block *sb) struct inode *ntfs_alloc_big_inode(struct super_block *sb)
{ {
ntfs_inode *ni; ntfs_inode *ni;
...@@ -685,9 +749,8 @@ static void ntfs_read_locked_inode(struct inode *vi) ...@@ -685,9 +749,8 @@ static void ntfs_read_locked_inode(struct inode *vi)
goto put_unm_err_out; goto put_unm_err_out;
} }
if (ir->type != AT_FILE_NAME) { if (ir->type != AT_FILE_NAME) {
ntfs_error(vi->i_sb, __FUNCTION__ "(): Indexed " ntfs_error(vi->i_sb, "Indexed attribute is not "
"attribute is not $FILE_NAME. Not " "$FILE_NAME. Not allowed.");
"allowed.");
goto put_unm_err_out; goto put_unm_err_out;
} }
if (ir->collation_rule != COLLATION_FILE_NAME) { if (ir->collation_rule != COLLATION_FILE_NAME) {
...@@ -856,7 +919,7 @@ static void ntfs_read_locked_inode(struct inode *vi) ...@@ -856,7 +919,7 @@ static void ntfs_read_locked_inode(struct inode *vi)
/* Setup the operations for this inode. */ /* Setup the operations for this inode. */
vi->i_op = &ntfs_dir_inode_ops; vi->i_op = &ntfs_dir_inode_ops;
vi->i_fop = &ntfs_dir_ops; vi->i_fop = &ntfs_dir_ops;
vi->i_mapping->a_ops = &ntfs_dir_aops; vi->i_mapping->a_ops = &ntfs_aops;
} else { } else {
/* It is a file. */ /* It is a file. */
reinit_attr_search_ctx(ctx); reinit_attr_search_ctx(ctx);
...@@ -995,7 +1058,7 @@ static void ntfs_read_locked_inode(struct inode *vi) ...@@ -995,7 +1058,7 @@ static void ntfs_read_locked_inode(struct inode *vi)
/* Setup the operations for this inode. */ /* Setup the operations for this inode. */
vi->i_op = &ntfs_file_inode_ops; vi->i_op = &ntfs_file_inode_ops;
vi->i_fop = &ntfs_file_ops; vi->i_fop = &ntfs_file_ops;
vi->i_mapping->a_ops = &ntfs_file_aops; vi->i_mapping->a_ops = &ntfs_aops;
} }
/* /*
* The number of 512-byte blocks used on disk (for stat). This is in so * The number of 512-byte blocks used on disk (for stat). This is in so
...@@ -1033,6 +1096,245 @@ static void ntfs_read_locked_inode(struct inode *vi) ...@@ -1033,6 +1096,245 @@ static void ntfs_read_locked_inode(struct inode *vi)
return; return;
} }
/**
* ntfs_read_locked_attr_inode - read an attribute inode from its base inode
* @base_vi: base inode
* @vi: attribute inode to read
*
* ntfs_read_locked_attr_inode() is called from the ntfs_attr_iget() to read
* the attribute inode described by @vi into memory from the base mft record
* described by @base_ni.
*
* ntfs_read_locked_attr_inode() maps, pins and locks the base inode for
* reading and looks up the attribute described by @vi before setting up the
* necessary fields in @vi as well as initializing the ntfs inode.
*
* Q: What locks are held when the function is called?
* A: i_state has I_LOCK set, hence the inode is locked, also
* i_count is set to 1, so it is not going to go away
*/
static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi)
{
ntfs_volume *vol = NTFS_SB(vi->i_sb);
ntfs_inode *ni, *base_ni;
MFT_RECORD *m;
attr_search_context *ctx;
int err = 0;
ntfs_debug("Entering for i_ino 0x%lx.", vi->i_ino);
ntfs_init_big_inode(vi);
ni = NTFS_I(vi);
base_ni = NTFS_I(base_vi);
/* Just mirror the values from the base inode. */
vi->i_blksize = base_vi->i_blksize;
vi->i_version = base_vi->i_version;
vi->i_uid = base_vi->i_uid;
vi->i_gid = base_vi->i_gid;
vi->i_nlink = base_vi->i_nlink;
vi->i_mtime = base_vi->i_mtime;
vi->i_ctime = base_vi->i_ctime;
vi->i_atime = base_vi->i_atime;
ni->seq_no = base_ni->seq_no;
/* Set inode type to zero but preserve permissions. */
vi->i_mode = base_vi->i_mode & ~S_IFMT;
m = map_mft_record(READ, base_ni);
if (IS_ERR(m)) {
err = PTR_ERR(m);
goto err_out;
}
ctx = get_attr_search_ctx(base_ni, m);
if (!ctx) {
err = -ENOMEM;
goto unm_err_out;
}
/* Find the attribute. */
if (!lookup_attr(ni->type, ni->name, ni->name_len, IGNORE_CASE, 0,
NULL, 0, ctx))
goto unm_err_out;
if (!ctx->attr->non_resident) {
if (NInoMstProtected(ni) || ctx->attr->flags) {
ntfs_error(vi->i_sb, "Found mst protected attribute "
"or attribute with non-zero flags but "
"the attribute is resident (mft_no "
"0x%lx, type 0x%x, name_len %i). "
"Please report you saw this message "
"to linux-ntfs-dev@lists.sf.net",
vi->i_ino, ni->type, ni->name_len);
goto unm_err_out;
}
/*
* Resident attribute. Make all sizes equal for simplicity in
* read code paths.
*/
vi->i_size = ni->initialized_size = ni->allocated_size =
le32_to_cpu(ctx->attr->_ARA(value_length));
} else {
NInoSetNonResident(ni);
if (ctx->attr->flags & ATTR_COMPRESSION_MASK) {
if (NInoMstProtected(ni)) {
ntfs_error(vi->i_sb, "Found mst protected "
"attribute but the attribute "
"is compressed (mft_no 0x%lx, "
"type 0x%x, name_len %i). "
"Please report you saw this "
"message to linux-ntfs-dev@"
"lists.sf.net", vi->i_ino,
ni->type, ni->name_len);
goto unm_err_out;
}
NInoSetCompressed(ni);
if ((ni->type != AT_DATA) || (ni->type == AT_DATA &&
ni->name_len)) {
ntfs_error(vi->i_sb, "Found compressed non-"
"data or named data attribute "
"(mft_no 0x%lx, type 0x%x, "
"name_len %i). Please report "
"you saw this message to "
"linux-ntfs-dev@lists.sf.net",
vi->i_ino, ni->type,
ni->name_len);
goto unm_err_out;
}
if (vol->cluster_size > 4096) {
ntfs_error(vi->i_sb, "Found "
"compressed attribute but "
"compression is disabled due "
"to cluster size (%i) > 4kiB.",
vol->cluster_size);
goto unm_err_out;
}
if ((ctx->attr->flags & ATTR_COMPRESSION_MASK)
!= ATTR_IS_COMPRESSED) {
ntfs_error(vi->i_sb, "Found unknown "
"compression method or "
"corrupt file.");
goto unm_err_out;
}
ni->_ICF(compression_block_clusters) = 1U <<
ctx->attr->_ANR(compression_unit);
if (ctx->attr->_ANR(compression_unit) != 4) {
ntfs_error(vi->i_sb, "Found "
"nonstandard compression unit "
"(%u instead of 4). Cannot "
"handle this. This might "
"indicate corruption so you "
"should run chkdsk.",
ctx->attr->_ANR(compression_unit));
err = -EOPNOTSUPP;
goto unm_err_out;
}
ni->_ICF(compression_block_size) = 1U << (
ctx->attr->_ANR(
compression_unit) +
vol->cluster_size_bits);
ni->_ICF(compression_block_size_bits) = ffs(
ni->_ICF(compression_block_size)) - 1;
}
if (ctx->attr->flags & ATTR_IS_ENCRYPTED) {
if (ctx->attr->flags & ATTR_COMPRESSION_MASK) {
ntfs_error(vi->i_sb, "Found encrypted "
"and compressed data.");
goto unm_err_out;
}
if (NInoMstProtected(ni)) {
ntfs_error(vi->i_sb, "Found mst protected "
"attribute but the attribute "
"is encrypted (mft_no 0x%lx, "
"type 0x%x, name_len %i). "
"Please report you saw this "
"message to linux-ntfs-dev@"
"lists.sf.net", vi->i_ino,
ni->type, ni->name_len);
goto unm_err_out;
}
NInoSetEncrypted(ni);
}
if (ctx->attr->flags & ATTR_IS_SPARSE) {
if (NInoMstProtected(ni)) {
ntfs_error(vi->i_sb, "Found mst protected "
"attribute but the attribute "
"is sparse (mft_no 0x%lx, "
"type 0x%x, name_len %i). "
"Please report you saw this "
"message to linux-ntfs-dev@"
"lists.sf.net", vi->i_ino,
ni->type, ni->name_len);
goto unm_err_out;
}
NInoSetSparse(ni);
}
if (ctx->attr->_ANR(lowest_vcn)) {
ntfs_error(vi->i_sb, "First extent of attribute has "
"non-zero lowest_vcn. Inode is "
"corrupt. You should run chkdsk.");
goto unm_err_out;
}
/* Setup all the sizes. */
vi->i_size = sle64_to_cpu(ctx->attr->_ANR(data_size));
ni->initialized_size = sle64_to_cpu(
ctx->attr->_ANR(initialized_size));
ni->allocated_size = sle64_to_cpu(
ctx->attr->_ANR(allocated_size));
if (NInoCompressed(ni)) {
ni->_ICF(compressed_size) = sle64_to_cpu(
ctx->attr->_ANR(compressed_size));
if (vi->i_size != ni->initialized_size)
ntfs_warning(vi->i_sb, "Compressed attribute "
"with data_size unequal to "
"initialized size found. This "
"will probably cause problems "
"when trying to access the "
"file. Please notify "
"linux-ntfs-dev@ lists.sf.net "
"that you saw this message.");
}
}
/* Setup the operations for this attribute inode. */
vi->i_op = NULL;
vi->i_fop = NULL;
vi->i_mapping->a_ops = &ntfs_aops;
if (!NInoCompressed(ni))
vi->i_blocks = ni->allocated_size >> 9;
else
vi->i_blocks = ni->_ICF(compressed_size) >> 9;
/*
* Make sure the base inode doesn't go away and attach it to the
* attribute inode.
*/
igrab(base_vi);
ni->_INE(base_ntfs_ino) = base_ni;
ni->nr_extents = -1;
put_attr_search_ctx(ctx);
unmap_mft_record(READ, ni);
ntfs_debug("Done.");
return 0;
unm_err_out:
if (!err)
err = -EIO;
if (ctx)
put_attr_search_ctx(ctx);
unmap_mft_record(READ, base_ni);
err_out:
ntfs_error(vi->i_sb, "Failed with error code %i while reading "
"attribute inode (mft_no 0x%lx, type 0x%x, name_len "
"%i.", -err, vi->i_ino, ni->type, ni->name_len);
make_bad_inode(vi);
return err;
}
/** /**
* ntfs_read_inode_mount - special read_inode for mount time use only * ntfs_read_inode_mount - special read_inode for mount time use only
* @vi: inode to read * @vi: inode to read
...@@ -1590,6 +1892,13 @@ void ntfs_clear_big_inode(struct inode *vi) ...@@ -1590,6 +1892,13 @@ void ntfs_clear_big_inode(struct inode *vi)
if (ni->_IDM(bmp_rl).rl) if (ni->_IDM(bmp_rl).rl)
ntfs_free(ni->_IDM(bmp_rl).rl); ntfs_free(ni->_IDM(bmp_rl).rl);
up_write(&ni->_IDM(bmp_rl).lock); up_write(&ni->_IDM(bmp_rl).lock);
} else if (NInoAttr(ni)) {
/* Release the base inode if we are holding it. */
if (ni->nr_extents == -1) {
iput(VFS_I(ni->_INE(base_ntfs_ino)));
ni->nr_extents = 0;
ni->_INE(base_ntfs_ino) = NULL;
}
} }
return; return;
} }
......
...@@ -95,8 +95,10 @@ int format_mft_record(ntfs_inode *ni, MFT_RECORD *mft_rec) ...@@ -95,8 +95,10 @@ int format_mft_record(ntfs_inode *ni, MFT_RECORD *mft_rec)
return 0; return 0;
} }
/* From fs/ntfs/aops.c */ /**
extern int ntfs_mst_readpage(struct file *, struct page *); * From fs/ntfs/aops.c
*/
extern int ntfs_readpage(struct file *, struct page *);
/** /**
* ntfs_mft_aops - address space operations for access to $MFT * ntfs_mft_aops - address space operations for access to $MFT
...@@ -106,7 +108,7 @@ extern int ntfs_mst_readpage(struct file *, struct page *); ...@@ -106,7 +108,7 @@ extern int ntfs_mst_readpage(struct file *, struct page *);
*/ */
struct address_space_operations ntfs_mft_aops = { struct address_space_operations ntfs_mft_aops = {
writepage: NULL, /* Write dirty page to disk. */ writepage: NULL, /* Write dirty page to disk. */
readpage: ntfs_mst_readpage, /* Fill page with data. */ readpage: ntfs_readpage, /* Fill page with data. */
sync_page: block_sync_page, /* Currently, just unplugs the sync_page: block_sync_page, /* Currently, just unplugs the
disk request queue. */ disk request queue. */
prepare_write: NULL, /* . */ prepare_write: NULL, /* . */
...@@ -214,11 +216,11 @@ static inline void unmap_mft_record_page(ntfs_inode *ni) ...@@ -214,11 +216,11 @@ static inline void unmap_mft_record_page(ntfs_inode *ni)
* necessary, increments the use count on the page so that it cannot disappear * necessary, increments the use count on the page so that it cannot disappear
* under us and returns a reference to the page cache page). * under us and returns a reference to the page cache page).
* *
* If read_cache_page() invokes ntfs_mst_readpage() to load the page from disk, * If read_cache_page() invokes ntfs_readpage() to load the page from disk, it
* it sets PG_locked and clears PG_uptodate on the page. Once I/O has * sets PG_locked and clears PG_uptodate on the page. Once I/O has completed
* completed and the post-read mst fixups on each mft record in the page have * and the post-read mst fixups on each mft record in the page have been
* been performed, the page gets PG_uptodate set and PG_locked cleared (this is * performed, the page gets PG_uptodate set and PG_locked cleared (this is done
* done in our asynchronous I/O completion handler end_buffer_read_mft_async()). * in our asynchronous I/O completion handler end_buffer_read_mft_async()).
* ntfs_map_page() waits for PG_locked to become clear and checks if * ntfs_map_page() waits for PG_locked to become clear and checks if
* PG_uptodate is set and returns an error code if not. This provides * PG_uptodate is set and returns an error code if not. This provides
* sufficient protection against races when reading/using the page. * sufficient protection against races when reading/using the page.
......
...@@ -62,18 +62,21 @@ extern kmem_cache_t *ntfs_big_inode_cache; ...@@ -62,18 +62,21 @@ extern kmem_cache_t *ntfs_big_inode_cache;
extern kmem_cache_t *ntfs_attr_ctx_cache; extern kmem_cache_t *ntfs_attr_ctx_cache;
/* The various operations structs defined throughout the driver files. */ /* The various operations structs defined throughout the driver files. */
extern struct super_operations ntfs_mount_sops;
extern struct super_operations ntfs_sops; extern struct super_operations ntfs_sops;
extern struct super_operations ntfs_mount_sops;
extern struct address_space_operations ntfs_aops;
extern struct address_space_operations ntfs_mft_aops;
extern struct address_space_operations ntfs_mftbmp_aops;
extern struct file_operations ntfs_file_ops; extern struct file_operations ntfs_file_ops;
extern struct inode_operations ntfs_file_inode_ops; extern struct inode_operations ntfs_file_inode_ops;
extern struct address_space_operations ntfs_file_aops;
extern struct file_operations ntfs_dir_ops; extern struct file_operations ntfs_dir_ops;
extern struct inode_operations ntfs_dir_inode_ops; extern struct inode_operations ntfs_dir_inode_ops;
extern struct address_space_operations ntfs_dir_aops;
extern struct file_operations ntfs_empty_file_ops; extern struct file_operations ntfs_empty_file_ops;
extern struct inode_operations ntfs_empty_inode_ops; extern struct inode_operations ntfs_empty_inode_ops;
extern struct address_space_operations ntfs_mft_aops;
extern struct address_space_operations ntfs_mftbmp_aops;
/* Generic macro to convert pointers to values for comparison purposes. */ /* Generic macro to convert pointers to values for comparison purposes. */
#ifndef p2n #ifndef p2n
......
...@@ -135,6 +135,7 @@ static BOOL parse_options(ntfs_volume *vol, char *opt) ...@@ -135,6 +135,7 @@ static BOOL parse_options(ntfs_volume *vol, char *opt)
} }
if (!opt || !*opt) if (!opt || !*opt)
goto no_mount_options; goto no_mount_options;
ntfs_debug("Entering with mount options string: %s", opt);
while ((p = strsep(&opt, ","))) { while ((p = strsep(&opt, ","))) {
if ((v = strchr(p, '='))) if ((v = strchr(p, '=')))
*v++ = '\0'; *v++ = '\0';
...@@ -217,7 +218,7 @@ static BOOL parse_options(ntfs_volume *vol, char *opt) ...@@ -217,7 +218,7 @@ static BOOL parse_options(ntfs_volume *vol, char *opt)
} }
} }
if (nls_map) { if (nls_map) {
if (vol->nls_map) { if (vol->nls_map && vol->nls_map != nls_map) {
ntfs_error(vol->sb, "Cannot change NLS character set " ntfs_error(vol->sb, "Cannot change NLS character set "
"on remount."); "on remount.");
return FALSE; return FALSE;
...@@ -249,8 +250,8 @@ static BOOL parse_options(ntfs_volume *vol, char *opt) ...@@ -249,8 +250,8 @@ static BOOL parse_options(ntfs_volume *vol, char *opt)
mft_zone_multiplier = 1; mft_zone_multiplier = 1;
} }
vol->mft_zone_multiplier = mft_zone_multiplier; vol->mft_zone_multiplier = mft_zone_multiplier;
} if (!vol->mft_zone_multiplier) }
/* Not specified and it is the first mount, so set default. */ if (!vol->mft_zone_multiplier)
vol->mft_zone_multiplier = 1; vol->mft_zone_multiplier = 1;
if (on_errors != -1) if (on_errors != -1)
vol->on_errors = on_errors; vol->on_errors = on_errors;
...@@ -304,7 +305,7 @@ static int ntfs_remount(struct super_block *sb, int *flags, char *opt) ...@@ -304,7 +305,7 @@ static int ntfs_remount(struct super_block *sb, int *flags, char *opt)
{ {
ntfs_volume *vol = NTFS_SB(sb); ntfs_volume *vol = NTFS_SB(sb);
ntfs_debug("Entering."); ntfs_debug("Entering with remount options string: %s", opt);
// FIXME/TODO: If left like this we will have problems with rw->ro and // FIXME/TODO: If left like this we will have problems with rw->ro and
// ro->rw, as well as with sync->async and vice versa remounts. // ro->rw, as well as with sync->async and vice versa remounts.
...@@ -1799,7 +1800,7 @@ static int __init init_ntfs_fs(void) ...@@ -1799,7 +1800,7 @@ static int __init init_ntfs_fs(void)
#ifdef MODULE #ifdef MODULE
" MODULE" " MODULE"
#endif #endif
"]. Copyright (c) 2001,2002 Anton Altaparmakov.\n"); "].\n");
ntfs_debug("Debug messages are enabled."); ntfs_debug("Debug messages are enabled.");
...@@ -1899,7 +1900,7 @@ static void __exit exit_ntfs_fs(void) ...@@ -1899,7 +1900,7 @@ static void __exit exit_ntfs_fs(void)
} }
MODULE_AUTHOR("Anton Altaparmakov <aia21@cantab.net>"); MODULE_AUTHOR("Anton Altaparmakov <aia21@cantab.net>");
MODULE_DESCRIPTION("NTFS 1.2/3.x driver"); MODULE_DESCRIPTION("NTFS 1.2/3.x driver - Copyright (c) 2001-2002 Anton Altaparmakov");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
#ifdef DEBUG #ifdef DEBUG
MODULE_PARM(debug_msgs, "i"); MODULE_PARM(debug_msgs, "i");
......
...@@ -26,31 +26,6 @@ ...@@ -26,31 +26,6 @@
#include "types.h" #include "types.h"
/*
* Defined bits for the flags field in the ntfs_volume structure.
*/
typedef enum {
NV_ShowSystemFiles, /* 1: Return system files in ntfs_readdir(). */
NV_CaseSensitive, /* 1: Treat file names as case sensitive and
create filenames in the POSIX namespace.
Otherwise be case insensitive and create
file names in WIN32 namespace. */
} ntfs_volume_flags;
#define NVolShowSystemFiles(n_vol) test_bit(NV_ShowSystemFiles, \
&(n_vol)->flags)
#define NVolSetShowSystemFiles(n_vol) set_bit(NV_ShowSystemFiles, \
&(n_vol)->flags)
#define NVolClearShowSystemFiles(n_vol) clear_bit(NV_ShowSystemFiles, \
&(n_vol)->flags)
#define NVolCaseSensitive(n_vol) test_bit(NV_CaseSensitive, \
&(n_vol)->flags)
#define NVolSetCaseSensitive(n_vol) set_bit(NV_CaseSensitive, \
&(n_vol)->flags)
#define NVolClearCaseSensitive(n_vol) clear_bit(NV_CaseSensitive, \
&(n_vol)->flags)
/* /*
* The NTFS in memory super block structure. * The NTFS in memory super block structure.
*/ */
...@@ -124,5 +99,38 @@ typedef struct { ...@@ -124,5 +99,38 @@ typedef struct {
struct nls_table *nls_map; struct nls_table *nls_map;
} ntfs_volume; } ntfs_volume;
/*
* Defined bits for the flags field in the ntfs_volume structure.
*/
typedef enum {
NV_ShowSystemFiles, /* 1: Return system files in ntfs_readdir(). */
NV_CaseSensitive, /* 1: Treat file names as case sensitive and
create filenames in the POSIX namespace.
Otherwise be case insensitive and create
file names in WIN32 namespace. */
} ntfs_volume_flags;
/*
* Macro tricks to expand the NVolFoo(), NVolSetFoo(), and NVolClearFoo()
* functions.
*/
#define NVOL_FNS(flag) \
static inline int NVol##flag(ntfs_volume *vol) \
{ \
return test_bit(NV_##flag, &(vol)->flags); \
} \
static inline void NVolSet##flag(ntfs_volume *vol) \
{ \
set_bit(NV_##flag, &(vol)->flags); \
} \
static inline void NVolClear##flag(ntfs_volume *vol) \
{ \
clear_bit(NV_##flag, &(vol)->flags); \
}
/* Emit the ntfs volume bitops functions. */
NVOL_FNS(ShowSystemFiles)
NVOL_FNS(CaseSensitive)
#endif /* _LINUX_NTFS_VOLUME_H */ #endif /* _LINUX_NTFS_VOLUME_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment