Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
L
linux
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
linux
Commits
016b9c5f
Commit
016b9c5f
authored
Jun 30, 2002
by
Anton Altaparmakov
Browse files
Options
Browse Files
Download
Plain Diff
Merge cantab.net:/usr/src/tng-2.0.11 into cantab.net:/usr/src/tng-2.0.13
parents
59b1ec78
8d9eae15
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
459 additions
and
416 deletions
+459
-416
Documentation/filesystems/ntfs.txt
Documentation/filesystems/ntfs.txt
+6
-0
fs/ntfs/ChangeLog
fs/ntfs/ChangeLog
+35
-6
fs/ntfs/Makefile
fs/ntfs/Makefile
+1
-1
fs/ntfs/aops.c
fs/ntfs/aops.c
+115
-297
fs/ntfs/inode.c
fs/ntfs/inode.c
+227
-27
fs/ntfs/inode.h
fs/ntfs/inode.h
+4
-2
fs/ntfs/mft.c
fs/ntfs/mft.c
+2
-3
fs/ntfs/namei.c
fs/ntfs/namei.c
+14
-10
fs/ntfs/super.c
fs/ntfs/super.c
+50
-58
fs/ntfs/volume.h
fs/ntfs/volume.h
+5
-12
No files found.
Documentation/filesystems/ntfs.txt
View file @
016b9c5f
...
...
@@ -247,6 +247,12 @@ ChangeLog
Note, a technical ChangeLog aimed at kernel hackers is in fs/ntfs/ChangeLog.
2.0.13:
- Internal changes towards using iget5_locked() in preparation for
fake inodes and small cleanups to ntfs_volume structure.
2.0.12:
- Internal cleanups in address space operations made possible by the
changes introduced in the previous release.
2.0.11:
- Internal updates and cleanups introducing the first step towards
fake inode based attribute i/o.
...
...
fs/ntfs/ChangeLog
View file @
016b9c5f
...
...
@@ -6,7 +6,7 @@ ToDo:
user open()s a file with i_size > s_maxbytes? Should read_inode()
truncate the visible i_size? Will the user just get -E2BIG (or
whatever) on open()? Or will (s)he be able to open() but lseek() and
read() will fail when s_maxbytes is reached? -> Investigate this
!
read() will fail when s_maxbytes is reached? -> Investigate this
.
- Implement/allow non-resident index bitmaps in dir.c::ntfs_readdir()
and then also consider initialized_size w.r.t. the bitmaps, etc.
- vcn_to_lcn() should somehow return the correct pointer within the
...
...
@@ -17,14 +17,43 @@ ToDo:
- Consider if ntfs_file_read_compressed_block() shouldn't be coping
with initialized_size < data_size. I don't think it can happen but
it requires more careful consideration.
- CLEANUP: Modularise and reuse code in aops.c. At the moment we have
several copies of almost identicall functions and the functions are
quite big. Modularising them a bit, e.g. a-la get_block(), will make
them cleaner and make code reuse easier.
- CLEANUP: At the moment we have two copies of almost identical
functions in aops.c, can merge them once fake inode address space
based attribute i/o is further developed.
- CLEANUP: Modularising code in aops.c a bit, e.g. a-la get_block(),
will be cleaner and make code reuse easier.
- Modify ntfs_read_locked_inode() to return an error code and update
callers, i.e. ntfs_iget(), to pass that error code up instead of just
using -EIO.
- Enable NFS exporting of NTFS.
- Use iget5_locked() and friends instead of conventional iget().
- Use fake inodes for address space i/o.
2.0.13 - Use iget5_locked() in preparation for fake inodes and small cleanups.
- Remove nr_mft_bits and the now superfluous union with nr_mft_records
from ntfs_volume structure.
- Remove nr_lcn_bits and the now superfluous union with nr_clusters
from ntfs_volume structure.
- Use iget5_locked() and friends instead of conventional iget(). Wrap
the call in fs/ntfs/inode.c::ntfs_iget() and update callers of iget()
to use ntfs_iget(). Leave only one iget() call at mount time so we
don't need an ntfs_iget_mount().
- Change fs/ntfs/inode.c::ntfs_new_extent_inode() to take mft_no as an
additional argument.
2.0.12 - Initial cleanup of address space operations following 2.0.11 changes.
- Merge fs/ntfs/aops.c::end_buffer_read_mst_async() and
fs/ntfs/aops.c::end_buffer_read_file_async() into one function
fs/ntfs/aops.c::end_buffer_read_attr_async() using NInoMstProtected()
to determine whether to apply mst fixups or not.
- Above change allows merging fs/ntfs/aops.c::ntfs_file_read_block()
and fs/ntfs/aops.c::ntfs_mst_readpage() into one function
fs/ntfs/aops.c::ntfs_attr_read_block(). Also, create a tiny wrapper
fs/ntfs/aops.c::ntfs_mst_readpage() to transform the parameters from
the VFS readpage function prototype to the ntfs_attr_read_block()
function prototype.
2.0.11 - Initial preparations for fake inode based attribute i/o.
- Move definition of ntfs_inode_state_bits to fs/ntfs/inode.h and
...
...
fs/ntfs/Makefile
View file @
016b9c5f
...
...
@@ -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
\
mst.o namei.o super.o sysctl.o time.o unistr.o upcase.o
EXTRA_CFLAGS
=
-DNTFS_VERSION
=
\"
2.0.1
1
\"
EXTRA_CFLAGS
=
-DNTFS_VERSION
=
\"
2.0.1
3
\"
ifeq
($(CONFIG_NTFS_DEBUG),y)
EXTRA_CFLAGS
+=
-DDEBUG
...
...
fs/ntfs/aops.c
View file @
016b9c5f
...
...
@@ -3,7 +3,7 @@
* Part of the Linux-NTFS project.
*
* Copyright (c) 2001,2002 Anton Altaparmakov.
* Copyright (
C
) 2002 Richard Russon.
* Copyright (
c
) 2002 Richard Russon.
*
* This program/include file is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published
...
...
@@ -30,31 +30,43 @@
#include "ntfs.h"
/**
* end_buffer_read_file_async -
* end_buffer_read_attr_async - async io completion for reading attributes
* @bh: buffer head on which io is completed
* @uptodate: whether @bh is now uptodate or not
*
* Async io completion handler for accessing files. Adapted from
* end_buffer_read_mst_async().
* Asynchronous I/O completion handler for reading pages belonging to the
* attribute address space of an inode. The inodes can either be files or
* directories or they can be fake inodes describing some attribute.
*
* If NInoMstProtected(), perform the post read mst fixups when all IO on the
* page has been completed and mark the page uptodate or set the error bit on
* the page. To determine the size of the records that need fixing up, we cheat
* a little bit by setting the index_block_size in ntfs_inode to the ntfs
* record size, and index_block_size_bits, to the log(base 2) of the ntfs
* record size.
*/
static
void
end_buffer_read_
file
_async
(
struct
buffer_head
*
bh
,
int
uptodate
)
static
void
end_buffer_read_
attr
_async
(
struct
buffer_head
*
bh
,
int
uptodate
)
{
static
spinlock_t
page_uptodate_lock
=
SPIN_LOCK_UNLOCKED
;
unsigned
long
flags
;
struct
buffer_head
*
tmp
;
struct
page
*
page
;
ntfs_inode
*
ni
;
if
(
uptodate
)
if
(
likely
(
uptodate
)
)
set_buffer_uptodate
(
bh
);
else
clear_buffer_uptodate
(
bh
);
page
=
bh
->
b_page
;
ni
=
NTFS_I
(
page
->
mapping
->
host
);
if
(
likely
(
uptodate
))
{
s64
file_ofs
;
ntfs_inode
*
ni
=
NTFS_I
(
page
->
mapping
->
host
);
file_ofs
=
(
page
->
index
<<
PAGE_CACHE_SHIFT
)
+
bh_offset
(
bh
);
/* Check for the current buffer head overflowing. */
if
(
file_ofs
+
bh
->
b_size
>
ni
->
initialized_size
)
{
char
*
addr
;
int
ofs
=
0
;
...
...
@@ -82,10 +94,47 @@ static void end_buffer_read_file_async(struct buffer_head *bh, int uptodate)
SetPageError
(
page
);
tmp
=
tmp
->
b_this_page
;
}
spin_unlock_irqrestore
(
&
page_uptodate_lock
,
flags
);
if
(
!
PageError
(
page
))
SetPageUptodate
(
page
);
/*
* If none of the buffers had errors then we can set the page uptodate,
* but we first have to perform the post read mst fixups, if the
* attribute is mst protected, i.e. if NInoMstProteced(ni) is true.
*/
if
(
!
NInoMstProtected
(
ni
))
{
if
(
likely
(
!
PageError
(
page
)))
SetPageUptodate
(
page
);
unlock_page
(
page
);
return
;
}
else
{
char
*
addr
;
unsigned
int
i
,
recs
,
nr_err
;
u32
rec_size
;
rec_size
=
ni
->
_IDM
(
index_block_size
);
recs
=
PAGE_CACHE_SIZE
/
rec_size
;
addr
=
kmap_atomic
(
page
,
KM_BIO_SRC_IRQ
);
for
(
i
=
nr_err
=
0
;
i
<
recs
;
i
++
)
{
if
(
likely
(
!
post_read_mst_fixup
((
NTFS_RECORD
*
)(
addr
+
i
*
rec_size
),
rec_size
)))
continue
;
nr_err
++
;
ntfs_error
(
ni
->
vol
->
sb
,
"post_read_mst_fixup() failed, "
"corrupt %s record 0x%Lx. Run chkdsk."
,
ni
->
mft_no
?
"index"
:
"mft"
,
(
long
long
)(((
s64
)
page
->
index
<<
PAGE_CACHE_SHIFT
>>
ni
->
_IDM
(
index_block_size_bits
))
+
i
));
}
flush_dcache_page
(
page
);
kunmap_atomic
(
addr
,
KM_BIO_SRC_IRQ
);
if
(
likely
(
!
nr_err
&&
recs
))
SetPageUptodate
(
page
);
else
{
ntfs_error
(
ni
->
vol
->
sb
,
"Setting page error, index "
"0x%lx."
,
page
->
index
);
SetPageError
(
page
);
}
}
unlock_page
(
page
);
return
;
still_busy:
...
...
@@ -94,11 +143,20 @@ static void end_buffer_read_file_async(struct buffer_head *bh, int uptodate)
}
/**
* ntfs_file_read_block -
* ntfs_attr_read_block - fill a @page of an address space with data
* @page: page cache page to fill with data
*
* NTFS version of block_read_full_page(). Adapted from ntfs_mst_readpage().
* 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
* completion handler end_buffer_read_attr_async(), if required, automatically
* applies the mst fixups to the page before finally marking it uptodate and
* unlocking it.
*
* Return 0 on success and -errno on error.
*
* Contains an adapted version of fs/buffer.c::block_read_full_page().
*/
static
int
ntfs_
file
_read_block
(
struct
page
*
page
)
static
int
ntfs_
attr
_read_block
(
struct
page
*
page
)
{
VCN
vcn
;
LCN
lcn
;
...
...
@@ -119,7 +177,7 @@ static int ntfs_file_read_block(struct page *page)
if
(
!
page_has_buffers
(
page
))
create_empty_buffers
(
page
,
blocksize
,
0
);
bh
=
head
=
page_buffers
(
page
);
if
(
!
bh
)
if
(
unlikely
(
!
bh
)
)
return
-
ENOMEM
;
blocks
=
PAGE_CACHE_SIZE
>>
blocksize_bits
;
...
...
@@ -128,11 +186,9 @@ static int ntfs_file_read_block(struct page *page)
zblock
=
(
ni
->
initialized_size
+
blocksize
-
1
)
>>
blocksize_bits
;
#ifdef DEBUG
if
(
unlikely
(
!
ni
->
mft_no
))
{
ntfs_error
(
vol
->
sb
,
"NTFS: Attempt to access $MFT! This is a "
"very serious bug! Denying access..."
);
return
-
EACCES
;
}
if
(
unlikely
(
!
ni
->
run_list
.
rl
&&
!
ni
->
mft_no
))
panic
(
"NTFS: $MFT/$DATA run list has been unmapped! This is a "
"very serious bug! Cannot continue..."
);
#endif
/* Loop through all the buffers in the page. */
...
...
@@ -211,7 +267,7 @@ static int ntfs_file_read_block(struct page *page)
for
(
i
=
0
;
i
<
nr
;
i
++
)
{
struct
buffer_head
*
tbh
=
arr
[
i
];
lock_buffer
(
tbh
);
tbh
->
b_end_io
=
end_buffer_read_
file
_async
;
tbh
->
b_end_io
=
end_buffer_read_
attr
_async
;
set_buffer_async_read
(
tbh
);
}
/* Finally, start i/o on the buffers. */
...
...
@@ -220,7 +276,7 @@ static int ntfs_file_read_block(struct page *page)
return
0
;
}
/* No i/o was scheduled on any of the buffers. */
if
(
!
PageError
(
page
))
if
(
likely
(
!
PageError
(
page
)
))
SetPageUptodate
(
page
);
else
/* Signal synchronous i/o error. */
nr
=
-
EIO
;
...
...
@@ -234,17 +290,17 @@ static int ntfs_file_read_block(struct page *page)
* @page: page cache page to fill with data
*
* For non-resident attributes, ntfs_file_readpage() fills the @page of the open
* file @file by calling the
generic block_read_full_page() function provided by
*
the kernel which in turn invokes our ntfs_file_get_block() callback in order
*
to create and read
in the buffers associated with the page asynchronously.
* 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
*
creates and reads
in the buffers associated with the page asynchronously.
*
* For resident attributes, OTOH, ntfs_file_readpage() fills @page by copying
* the data from the mft record (which at this stage is most likely in memory)
* and fills the remainder with zeroes. Thus, in this case I/O is synchronous,
* and fills the remainder with zeroes. Thus, in this case
,
I/O is synchronous,
* as even if the mft record is not cached at this point in time, we need to
* wait for it to be read in before we can do the copy.
*
* Return
zero
on success or -errno on error.
* Return
0
on success or -errno on error.
*/
static
int
ntfs_file_readpage
(
struct
file
*
file
,
struct
page
*
page
)
{
...
...
@@ -256,43 +312,43 @@ static int ntfs_file_readpage(struct file *file, struct page *page)
u32
attr_len
;
int
err
=
0
;
if
(
!
PageLocked
(
page
))
if
(
unlikely
(
!
PageLocked
(
page
)
))
PAGE_BUG
(
page
);
ni
=
NTFS_I
(
page
->
mapping
->
host
);
/* Is the unnamed $DATA attribute resident? */
if
(
test_bit
(
NI_NonResident
,
&
ni
->
state
))
{
if
(
NInoNonResident
(
ni
))
{
/* Attribute is not resident. */
/* If the file is encrypted, we deny access, just like NT4. */
if
(
test_bit
(
NI_Encrypted
,
&
ni
->
state
))
{
if
(
NInoEncrypted
(
ni
))
{
err
=
-
EACCES
;
goto
unl_err_out
;
}
/* Compressed data stream. Handled in compress.c. */
if
(
test_bit
(
NI_Compressed
,
&
ni
->
state
))
if
(
NInoCompressed
(
ni
))
return
ntfs_file_read_compressed_block
(
page
);
/* Normal data stream. */
return
ntfs_
file
_read_block
(
page
);
return
ntfs_
attr
_read_block
(
page
);
}
/* Attribute is resident, implying it is not compressed or encrypted. */
/* Map, pin and lock the mft record for reading. */
mrec
=
map_mft_record
(
READ
,
ni
);
if
(
IS_ERR
(
mrec
))
{
if
(
unlikely
(
IS_ERR
(
mrec
)
))
{
err
=
PTR_ERR
(
mrec
);
goto
unl_err_out
;
}
ctx
=
get_attr_search_ctx
(
ni
,
mrec
);
if
(
!
ctx
)
{
if
(
unlikely
(
!
ctx
)
)
{
err
=
-
ENOMEM
;
goto
unm_unl_err_out
;
}
/* Find the data attribute in the mft record. */
if
(
!
lookup_attr
(
AT_DATA
,
NULL
,
0
,
0
,
0
,
NULL
,
0
,
ctx
))
{
if
(
unlikely
(
!
lookup_attr
(
AT_DATA
,
NULL
,
0
,
0
,
0
,
NULL
,
0
,
ctx
)
))
{
err
=
-
ENOENT
;
goto
put_unm_unl_err_out
;
}
...
...
@@ -330,6 +386,25 @@ static int ntfs_file_readpage(struct file *file, struct page *page)
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 -
*
...
...
@@ -343,7 +418,7 @@ static void end_buffer_read_mftbmp_async(struct buffer_head *bh, int uptodate)
struct
buffer_head
*
tmp
;
struct
page
*
page
;
if
(
uptodate
)
if
(
likely
(
uptodate
)
)
set_buffer_uptodate
(
bh
);
else
clear_buffer_uptodate
(
bh
);
...
...
@@ -386,7 +461,7 @@ static void end_buffer_read_mftbmp_async(struct buffer_head *bh, int uptodate)
}
spin_unlock_irqrestore
(
&
page_uptodate_lock
,
flags
);
if
(
!
PageError
(
page
))
if
(
likely
(
!
PageError
(
page
)
))
SetPageUptodate
(
page
);
unlock_page
(
page
);
return
;
...
...
@@ -410,7 +485,7 @@ static int ntfs_mftbmp_readpage(ntfs_volume *vol, struct page *page)
int
nr
,
i
;
unsigned
char
blocksize_bits
;
if
(
!
PageLocked
(
page
))
if
(
unlikely
(
!
PageLocked
(
page
)
))
PAGE_BUG
(
page
);
blocksize
=
vol
->
sb
->
s_blocksize
;
...
...
@@ -419,7 +494,7 @@ static int ntfs_mftbmp_readpage(ntfs_volume *vol, struct page *page)
if
(
!
page_has_buffers
(
page
))
create_empty_buffers
(
page
,
blocksize
,
0
);
bh
=
head
=
page_buffers
(
page
);
if
(
!
bh
)
if
(
unlikely
(
!
bh
)
)
return
-
ENOMEM
;
blocks
=
PAGE_CACHE_SIZE
>>
blocksize_bits
;
...
...
@@ -503,264 +578,7 @@ static int ntfs_mftbmp_readpage(ntfs_volume *vol, struct page *page)
return
0
;
}
/* No i/o was scheduled on any of the buffers. */
if
(
!
PageError
(
page
))
SetPageUptodate
(
page
);
else
/* Signal synchronous i/o error. */
nr
=
-
EIO
;
unlock_page
(
page
);
return
nr
;
}
/**
* end_buffer_read_mst_async - async io completion for reading index records
* @bh: buffer head on which io is completed
* @uptodate: whether @bh is now uptodate or not
*
* Asynchronous I/O completion handler for reading pages belonging to the
* index allocation attribute address space of directory inodes.
*
* Perform the post read mst fixups when all IO on the page has been completed
* and marks the page uptodate or sets the error bit on the page.
*
* Adapted from fs/buffer.c.
*
* NOTE: We use this function as async io completion handler for reading pages
* belonging to the mft data attribute address space, too as this saves
* duplicating an almost identical function. We do this by cheating a little
* bit in setting the index_block_size in the mft ntfs_inode to the mft record
* size of the volume (vol->mft_record_size), and index_block_size_bits to
* mft_record_size_bits, respectively.
*/
static
void
end_buffer_read_mst_async
(
struct
buffer_head
*
bh
,
int
uptodate
)
{
static
spinlock_t
page_uptodate_lock
=
SPIN_LOCK_UNLOCKED
;
unsigned
long
flags
;
struct
buffer_head
*
tmp
;
struct
page
*
page
;
ntfs_inode
*
ni
;
if
(
uptodate
)
set_buffer_uptodate
(
bh
);
else
clear_buffer_uptodate
(
bh
);
page
=
bh
->
b_page
;
ni
=
NTFS_I
(
page
->
mapping
->
host
);
if
(
likely
(
uptodate
))
{
s64
file_ofs
;
file_ofs
=
(
page
->
index
<<
PAGE_CACHE_SHIFT
)
+
bh_offset
(
bh
);
/* Check for the current buffer head overflowing. */
if
(
file_ofs
+
bh
->
b_size
>
ni
->
initialized_size
)
{
char
*
addr
;
int
ofs
=
0
;
if
(
file_ofs
<
ni
->
initialized_size
)
ofs
=
ni
->
initialized_size
-
file_ofs
;
addr
=
kmap_atomic
(
page
,
KM_BIO_SRC_IRQ
);
memset
(
addr
+
bh_offset
(
bh
)
+
ofs
,
0
,
bh
->
b_size
-
ofs
);
flush_dcache_page
(
page
);
kunmap_atomic
(
addr
,
KM_BIO_SRC_IRQ
);
}
}
else
SetPageError
(
page
);
spin_lock_irqsave
(
&
page_uptodate_lock
,
flags
);
clear_buffer_async_read
(
bh
);
unlock_buffer
(
bh
);
tmp
=
bh
->
b_this_page
;
while
(
tmp
!=
bh
)
{
if
(
buffer_locked
(
tmp
))
{
if
(
buffer_async_read
(
tmp
))
goto
still_busy
;
}
else
if
(
!
buffer_uptodate
(
tmp
))
SetPageError
(
page
);
tmp
=
tmp
->
b_this_page
;
}
spin_unlock_irqrestore
(
&
page_uptodate_lock
,
flags
);
/*
* If none of the buffers had errors then we can set the page uptodate,
* but we first have to perform the post read mst fixups.
*/
if
(
!
PageError
(
page
))
{
char
*
addr
;
unsigned
int
i
,
recs
,
nr_err
=
0
;
u32
rec_size
;
rec_size
=
ni
->
_IDM
(
index_block_size
);
recs
=
PAGE_CACHE_SIZE
/
rec_size
;
addr
=
kmap_atomic
(
page
,
KM_BIO_SRC_IRQ
);
for
(
i
=
0
;
i
<
recs
;
i
++
)
{
if
(
!
post_read_mst_fixup
((
NTFS_RECORD
*
)(
addr
+
i
*
rec_size
),
rec_size
))
continue
;
nr_err
++
;
ntfs_error
(
ni
->
vol
->
sb
,
"post_read_mst_fixup() failed, "
"corrupt %s record 0x%Lx. Run chkdsk."
,
ni
->
mft_no
?
"index"
:
"mft"
,
(
long
long
)((
page
->
index
<<
PAGE_CACHE_SHIFT
>>
ni
->
_IDM
(
index_block_size_bits
))
+
i
));
}
flush_dcache_page
(
page
);
kunmap_atomic
(
addr
,
KM_BIO_SRC_IRQ
);
if
(
likely
(
!
nr_err
&&
recs
))
SetPageUptodate
(
page
);
else
{
ntfs_error
(
ni
->
vol
->
sb
,
"Setting page error, index "
"0x%lx."
,
page
->
index
);
SetPageError
(
page
);
}
}
unlock_page
(
page
);
return
;
still_busy:
spin_unlock_irqrestore
(
&
page_uptodate_lock
,
flags
);
return
;
}
/**
* ntfs_mst_readpage - fill a @page of the mft or a directory with data
* @file: open file/directory to which the page @page belongs or NULL
* @page: page cache page to fill with data
*
* Readpage method for the VFS address space operations.
*
* Fill the page @page of the $MFT or the open directory @dir. We read each
* buffer asynchronously and when all buffers are read in our io completion
* handler end_buffer_read_mst_async() automatically applies the mst fixups to
* the page before finally marking it uptodate and unlocking it.
*
* Contains an adapted version of fs/buffer.c::block_read_full_page().
*/
int
ntfs_mst_readpage
(
struct
file
*
dir
,
struct
page
*
page
)
{
VCN
vcn
;
LCN
lcn
;
ntfs_inode
*
ni
;
ntfs_volume
*
vol
;
struct
buffer_head
*
bh
,
*
head
,
*
arr
[
MAX_BUF_PER_PAGE
];
sector_t
iblock
,
lblock
,
zblock
;
unsigned
int
blocksize
,
blocks
,
vcn_ofs
;
int
i
,
nr
;
unsigned
char
blocksize_bits
;
if
(
!
PageLocked
(
page
))
PAGE_BUG
(
page
);
ni
=
NTFS_I
(
page
->
mapping
->
host
);
vol
=
ni
->
vol
;
blocksize_bits
=
VFS_I
(
ni
)
->
i_blkbits
;
blocksize
=
1
<<
blocksize_bits
;
if
(
!
page_has_buffers
(
page
))
create_empty_buffers
(
page
,
blocksize
,
0
);
bh
=
head
=
page_buffers
(
page
);
if
(
!
bh
)
return
-
ENOMEM
;
blocks
=
PAGE_CACHE_SIZE
>>
blocksize_bits
;
iblock
=
page
->
index
<<
(
PAGE_CACHE_SHIFT
-
blocksize_bits
);
lblock
=
(
ni
->
allocated_size
+
blocksize
-
1
)
>>
blocksize_bits
;
zblock
=
(
ni
->
initialized_size
+
blocksize
-
1
)
>>
blocksize_bits
;
#ifdef DEBUG
if
(
unlikely
(
!
ni
->
run_list
.
rl
&&
!
ni
->
mft_no
))
panic
(
"NTFS: $MFT/$DATA run list has been unmapped! This is a "
"very serious bug! Cannot continue..."
);
#endif
/* Loop through all the buffers in the page. */
nr
=
i
=
0
;
do
{
if
(
unlikely
(
buffer_uptodate
(
bh
)))
continue
;
if
(
unlikely
(
buffer_mapped
(
bh
)))
{
arr
[
nr
++
]
=
bh
;
continue
;
}
bh
->
b_bdev
=
vol
->
sb
->
s_bdev
;
/* Is the block within the allowed limits? */
if
(
iblock
<
lblock
)
{
BOOL
is_retry
=
FALSE
;
/* Convert iblock into corresponding vcn and offset. */
vcn
=
(
VCN
)
iblock
<<
blocksize_bits
>>
vol
->
cluster_size_bits
;
vcn_ofs
=
((
VCN
)
iblock
<<
blocksize_bits
)
&
vol
->
cluster_size_mask
;
retry_remap:
/* Convert the vcn to the corresponding lcn. */
down_read
(
&
ni
->
run_list
.
lock
);
lcn
=
vcn_to_lcn
(
ni
->
run_list
.
rl
,
vcn
);
up_read
(
&
ni
->
run_list
.
lock
);
/* Successful remap. */
if
(
lcn
>=
0
)
{
/* Setup buffer head to correct block. */
bh
->
b_blocknr
=
((
lcn
<<
vol
->
cluster_size_bits
)
+
vcn_ofs
)
>>
blocksize_bits
;
set_buffer_mapped
(
bh
);
/* Only read initialized data blocks. */
if
(
iblock
<
zblock
)
{
arr
[
nr
++
]
=
bh
;
continue
;
}
/* Fully non-initialized data block, zero it. */
goto
handle_zblock
;
}
/* It is a hole, need to zero it. */
if
(
lcn
==
LCN_HOLE
)
goto
handle_hole
;
/* If first try and run list unmapped, map and retry. */
if
(
!
is_retry
&&
lcn
==
LCN_RL_NOT_MAPPED
)
{
is_retry
=
TRUE
;
if
(
!
map_run_list
(
ni
,
vcn
))
goto
retry_remap
;
}
/* Hard error, zero out region. */
SetPageError
(
page
);
ntfs_error
(
vol
->
sb
,
"vcn_to_lcn(vcn = 0x%Lx) failed "
"with error code 0x%Lx%s."
,
(
long
long
)
vcn
,
(
long
long
)
-
lcn
,
is_retry
?
" even after retrying"
:
""
);
// FIXME: Depending on vol->on_errors, do something.
}
/*
* Either iblock was outside lblock limits or vcn_to_lcn()
* returned error. Just zero that portion of the page and set
* the buffer uptodate.
*/
handle_hole:
bh
->
b_blocknr
=
-
1UL
;
clear_buffer_mapped
(
bh
);
handle_zblock:
memset
(
kmap
(
page
)
+
i
*
blocksize
,
0
,
blocksize
);
flush_dcache_page
(
page
);
kunmap
(
page
);
set_buffer_uptodate
(
bh
);
}
while
(
i
++
,
iblock
++
,
(
bh
=
bh
->
b_this_page
)
!=
head
);
/* Check we have at least one buffer ready for i/o. */
if
(
nr
)
{
/* Lock the buffers. */
for
(
i
=
0
;
i
<
nr
;
i
++
)
{
struct
buffer_head
*
tbh
=
arr
[
i
];
lock_buffer
(
tbh
);
tbh
->
b_end_io
=
end_buffer_read_mst_async
;
set_buffer_async_read
(
tbh
);
}
/* Finally, start i/o on the buffers. */
for
(
i
=
0
;
i
<
nr
;
i
++
)
submit_bh
(
READ
,
arr
[
i
]);
return
0
;
}
/* No i/o was scheduled on any of the buffers. */
if
(
!
PageError
(
page
))
if
(
likely
(
!
PageError
(
page
)))
SetPageUptodate
(
page
);
else
/* Signal synchronous i/o error. */
nr
=
-
EIO
;
...
...
fs/ntfs/inode.c
View file @
016b9c5f
...
...
@@ -24,6 +24,178 @@
#include "ntfs.h"
#include "dir.h"
#include "inode.h"
#include "attrib.h"
/**
* ntfs_attr - ntfs in memory attribute structure
* @mft_no: mft record number of the base mft record of this attribute
* @name: Unicode name of the attribute (NULL if unnamed)
* @name_len: length of @name in Unicode characters (0 if unnamed)
* @type: attribute type (see layout.h)
*
* This structure exists only to provide a small structure for the
* ntfs_iget()/ntfs_test_inode()/ntfs_init_locked_inode() mechanism.
*
* NOTE: Elements are ordered by size to make the structure as compact as
* possible on all architectures.
*/
typedef
struct
{
unsigned
long
mft_no
;
uchar_t
*
name
;
u32
name_len
;
ATTR_TYPES
type
;
}
ntfs_attr
;
/**
* ntfs_test_inode - compare two (possibly fake) inodes for equality
* @vi: vfs inode which to test
* @na: ntfs attribute which is being tested with
*
* Compare the ntfs attribute embedded in the ntfs specific part of the vfs
* inode @vi for equality with the ntfs attribute @na.
*
* If searching for the normal file/directory inode, set @na->type to AT_UNUSED.
* @na->name and @na->name_len are then ignored.
*
* Return 1 if the attributes match and 0 if not.
*
* NOTE: This function runs with the inode_lock spin lock held so it is not
* allowed to sleep.
*/
static
int
ntfs_test_inode
(
struct
inode
*
vi
,
ntfs_attr
*
na
)
{
ntfs_inode
*
ni
;
if
(
vi
->
i_ino
!=
na
->
mft_no
)
return
0
;
ni
=
NTFS_I
(
vi
);
/* If !NInoAttr(ni), @vi is a normal file or directory inode. */
if
(
likely
(
!
NInoAttr
(
ni
)))
{
/* If not looking for a normal inode this is a mismatch. */
if
(
unlikely
(
na
->
type
!=
AT_UNUSED
))
return
0
;
}
else
{
/* A fake inode describing an attribute. */
if
(
ni
->
type
!=
na
->
type
)
return
0
;
if
(
ni
->
name_len
!=
na
->
name_len
)
return
0
;
if
(
na
->
name_len
&&
memcmp
(
ni
->
name
,
na
->
name
,
na
->
name_len
*
sizeof
(
uchar_t
)))
return
0
;
}
/* Match! */
return
1
;
}
/**
* ntfs_init_locked_inode - initialize an inode
* @vi: vfs inode to initialize
* @na: ntfs attribute which to initialize @vi to
*
* Initialize the vfs inode @vi with the values from the ntfs attribute @na in
* order to enable ntfs_test_inode() to do its work.
*
* If initializing the normal file/directory inode, set @na->type to AT_UNUSED.
* In that case, @na->name and @na->name_len should be set to NULL and 0,
* respectively. Although that is not strictly necessary as
* ntfs_read_inode_locked() will fill them in later.
*
* Return 0 on success and -errno on error.
*
* NOTE: This function runs with the inode_lock spin lock held so it is not
* allowed to sleep. (Hence the GFP_ATOMIC allocation.)
*/
static
int
ntfs_init_locked_inode
(
struct
inode
*
vi
,
ntfs_attr
*
na
)
{
ntfs_inode
*
ni
=
NTFS_I
(
vi
);
vi
->
i_ino
=
na
->
mft_no
;
ni
->
type
=
na
->
type
;
ni
->
name
=
na
->
name
;
ni
->
name_len
=
na
->
name_len
;
/* If initializing a normal inode, we are done. */
if
(
likely
(
na
->
type
==
AT_UNUSED
))
return
0
;
/* It is a fake inode. */
NInoSetAttr
(
ni
);
/*
* 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
* allocation but that is ok. And most attributes are unnamed anyway,
* thus the fraction of named attributes with name != I30 is actually
* absolutely tiny.
*/
if
(
na
->
name
&&
na
->
name_len
&&
na
->
name
!=
I30
)
{
unsigned
int
i
;
i
=
na
->
name_len
*
sizeof
(
uchar_t
);
ni
->
name
=
(
uchar_t
*
)
kmalloc
(
i
+
sizeof
(
uchar_t
),
GFP_ATOMIC
);
if
(
!
ni
->
name
)
return
-
ENOMEM
;
memcpy
(
ni
->
name
,
na
->
name
,
i
);
ni
->
name
[
i
]
=
cpu_to_le16
(
'\0'
);
}
return
0
;
}
typedef
int
(
*
test_t
)(
struct
inode
*
,
void
*
);
typedef
int
(
*
set_t
)(
struct
inode
*
,
void
*
);
static
void
ntfs_read_locked_inode
(
struct
inode
*
vi
);
/**
* ntfs_iget - obtain a struct inode corresponding to a specific normal inode
* @sb: super block of mounted volume
* @mft_no: mft record number / inode number to obtain
*
* Obtain the struct inode corresponding to a specific normal inode (i.e. a
* file or directory).
*
* If the 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_inode() is called to read in the inode and
* fill in the remainder of the inode structure.
*
* Return the struct 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_iget
(
struct
super_block
*
sb
,
unsigned
long
mft_no
)
{
struct
inode
*
vi
;
ntfs_attr
na
;
na
.
mft_no
=
mft_no
;
na
.
type
=
AT_UNUSED
;
na
.
name
=
NULL
;
na
.
name_len
=
0
;
vi
=
iget5_locked
(
sb
,
mft_no
,
(
test_t
)
ntfs_test_inode
,
(
set_t
)
ntfs_init_locked_inode
,
&
na
);
if
(
!
vi
)
return
ERR_PTR
(
-
ENOMEM
);
/* If this is a freshly allocated inode, need to read it now. */
if
(
vi
->
i_state
&
I_NEW
)
{
ntfs_read_locked_inode
(
vi
);
unlock_new_inode
(
vi
);
}
#if 0
// TODO: Enable this and do the follow up cleanup, i.e. remove all the
// bad inode checks. -- BUT: Do we actually want to do this? -- It may
// result in repeated attemps to read a bad inode which is not
// desirable. (AIA)
/*
* There is no point in keeping bad inodes around. This also simplifies
* things in that we never need to check for bad inodes elsewhere.
*/
if (is_bad_inode(vi)) {
iput(vi);
vi = ERR_PTR(-EIO);
}
#endif
return
vi
;
}
struct
inode
*
ntfs_alloc_big_inode
(
struct
super_block
*
sb
)
{
...
...
@@ -32,12 +204,12 @@ struct inode *ntfs_alloc_big_inode(struct super_block *sb)
ntfs_debug
(
"Entering."
);
ni
=
(
ntfs_inode
*
)
kmem_cache_alloc
(
ntfs_big_inode_cache
,
SLAB_NOFS
);
if
(
!
ni
)
{
ntfs_error
(
sb
,
"Allocation of NTFS big inode structure "
"failed."
);
return
NULL
;
if
(
likely
(
ni
!=
NULL
))
{
ni
->
state
=
0
;
return
VFS_I
(
ni
);
}
return
VFS_I
(
ni
);
ntfs_error
(
sb
,
"Allocation of NTFS big inode structure failed."
);
return
NULL
;
}
void
ntfs_destroy_big_inode
(
struct
inode
*
inode
)
...
...
@@ -49,14 +221,17 @@ void ntfs_destroy_big_inode(struct inode *inode)
kmem_cache_free
(
ntfs_big_inode_cache
,
NTFS_I
(
inode
));
}
ntfs_inode
*
ntfs_alloc_extent_inode
(
void
)
static
ntfs_inode
*
ntfs_alloc_extent_inode
(
void
)
{
ntfs_inode
*
ni
=
(
ntfs_inode
*
)
kmem_cache_alloc
(
ntfs_inode_cache
,
SLAB_NOFS
);
ntfs_debug
(
"Entering."
);
if
(
unlikely
(
!
ni
))
ntfs_error
(
NULL
,
"Allocation of NTFS inode structure failed."
);
return
ni
;
if
(
likely
(
ni
!=
NULL
))
{
ni
->
state
=
0
;
return
ni
;
}
ntfs_error
(
NULL
,
"Allocation of NTFS inode structure failed."
);
return
NULL
;
}
void
ntfs_destroy_extent_inode
(
ntfs_inode
*
ni
)
...
...
@@ -68,27 +243,42 @@ void ntfs_destroy_extent_inode(ntfs_inode *ni)
/**
* __ntfs_init_inode - initialize ntfs specific part of an inode
* @sb: super block of mounted volume
* @ni: freshly allocated ntfs inode which to initialize
*
* Initialize an ntfs inode to defaults.
*
* NOTE: ni->mft_no, ni->state, ni->type, ni->name, and ni->name_len are left
* untouched. Make sure to initialize them elsewhere.
*
* Return zero on success and -ENOMEM on error.
*/
static
void
__ntfs_init_inode
(
struct
super_block
*
sb
,
ntfs_inode
*
ni
)
{
ntfs_debug
(
"Entering."
);
memset
(
ni
,
0
,
sizeof
(
ntfs_inode
));
ni
->
initialized_size
=
ni
->
allocated_size
=
0
;
ni
->
seq_no
=
0
;
atomic_set
(
&
ni
->
count
,
1
);
ni
->
vol
=
N
ULL
;
ni
->
vol
=
N
TFS_SB
(
sb
)
;
init_run_list
(
&
ni
->
run_list
);
init_rwsem
(
&
ni
->
mrec_lock
);
atomic_set
(
&
ni
->
mft_count
,
0
);
ni
->
page
=
NULL
;
ni
->
page_ofs
=
0
;
ni
->
attr_list_size
=
0
;
ni
->
attr_list
=
NULL
;
init_run_list
(
&
ni
->
attr_list_rl
);
ni
->
_IDM
(
index_block_size
)
=
0
;
ni
->
_IDM
(
index_vcn_size
)
=
0
;
ni
->
_IDM
(
bmp_size
)
=
0
;
ni
->
_IDM
(
bmp_initialized_size
)
=
0
;
ni
->
_IDM
(
bmp_allocated_size
)
=
0
;
init_run_list
(
&
ni
->
_IDM
(
bmp_rl
));
ni
->
_IDM
(
index_block_size_bits
)
=
0
;
ni
->
_IDM
(
index_vcn_size_bits
)
=
0
;
init_MUTEX
(
&
ni
->
extent_lock
);
ni
->
nr_extents
=
0
;
ni
->
_INE
(
base_ntfs_ino
)
=
NULL
;
ni
->
vol
=
NTFS_SB
(
sb
);
return
;
}
...
...
@@ -102,13 +292,18 @@ static void ntfs_init_big_inode(struct inode *vi)
return
;
}
ntfs_inode
*
ntfs_new_extent_inode
(
struct
super_block
*
sb
)
ntfs_inode
*
ntfs_new_extent_inode
(
struct
super_block
*
sb
,
unsigned
long
mft_no
)
{
ntfs_inode
*
ni
=
ntfs_alloc_extent_inode
();
ntfs_debug
(
"Entering."
);
if
(
ni
)
if
(
likely
(
ni
!=
NULL
))
{
__ntfs_init_inode
(
sb
,
ni
);
ni
->
mft_no
=
mft_no
;
ni
->
type
=
AT_UNUSED
;
ni
->
name
=
NULL
;
ni
->
name_len
=
0
;
}
return
ni
;
}
...
...
@@ -189,18 +384,20 @@ static int ntfs_is_extended_system_file(attr_search_context *ctx)
}
/**
* ntfs_read_inode - read an inode from its device
* ntfs_read_
locked_
inode - read an inode from its device
* @vi: inode to read
*
* ntfs_read_
inode() is called from the VFS iget() function
to read the inode
* ntfs_read_
locked_inode() is called from the ntfs_iget()
to read the inode
* described by @vi into memory from the device.
*
* The only fields in @vi that we need to/can look at when the function is
* called are i_sb, pointing to the mounted device's super block, and i_ino,
* the number of the inode to load.
* the number of the inode to load. If this is a fake inode, i.e. NInoAttr(),
* then the fields type, name, and name_len are also valid, and describe the
* attribute which this fake inode represents.
*
* ntfs_read_
inode() maps, pins and locks the mft record number i_ino for
* reading and sets up the necessary @vi fields as well as initializing
* ntfs_read_
locked_inode() maps, pins and locks the mft record number i_ino
*
for
reading and sets up the necessary @vi fields as well as initializing
* the ntfs inode.
*
* Q: What locks are held when the function is called?
...
...
@@ -209,9 +406,9 @@ static int ntfs_is_extended_system_file(attr_search_context *ctx)
* i_flags is set to 0 and we have no business touching it. Only an ioctl()
* is allowed to write to them. We should of course be honouring them but
* we need to do that using the IS_* macros defined in include/linux/fs.h.
* In any case ntfs_read_
inode() has nothing to do with i_flags at all
.
* In any case ntfs_read_
locked_inode() has nothing to do with i_flags
.
*/
void
ntfs_rea
d_inode
(
struct
inode
*
vi
)
static
void
ntfs_read_locke
d_inode
(
struct
inode
*
vi
)
{
ntfs_volume
*
vol
=
NTFS_SB
(
vi
->
i_sb
);
ntfs_inode
*
ni
;
...
...
@@ -661,7 +858,7 @@ void ntfs_read_inode(struct inode *vi)
vi
->
i_fop
=
&
ntfs_dir_ops
;
vi
->
i_mapping
->
a_ops
=
&
ntfs_dir_aops
;
}
else
{
/* It is a file
: find first extent of unnamed data attribute
. */
/* It is a file. */
reinit_attr_search_ctx
(
ctx
);
/* Setup the data attribute, even if not present. */
...
...
@@ -669,6 +866,7 @@ void ntfs_read_inode(struct inode *vi)
ni
->
name
=
NULL
;
ni
->
name_len
=
0
;
/* Find first extent of the unnamed data attribute. */
if
(
!
lookup_attr
(
AT_DATA
,
NULL
,
0
,
0
,
0
,
NULL
,
0
,
ctx
))
{
vi
->
i_size
=
ni
->
initialized_size
=
ni
->
allocated_size
=
0LL
;
...
...
@@ -877,15 +1075,17 @@ void ntfs_read_inode_mount(struct inode *vi)
ntfs_debug
(
"Entering."
);
/* Initialize the ntfs specific part of @vi. */
ntfs_init_big_inode
(
vi
);
ni
=
NTFS_I
(
vi
);
if
(
vi
->
i_ino
!=
FILE_MFT
)
{
ntfs_error
(
sb
,
"Called for inode 0x%lx but only inode %d "
"allowed."
,
vi
->
i_ino
,
FILE_MFT
);
goto
err_out
;
}
/* Initialize the ntfs specific part of @vi. */
ntfs_init_big_inode
(
vi
);
ni
=
NTFS_I
(
vi
);
/* Setup the data attribute. It is special as it is mst protected. */
NInoSetNonResident
(
ni
);
NInoSetMstProtected
(
ni
);
...
...
@@ -1158,7 +1358,7 @@ void ntfs_read_inode_mount(struct inode *vi)
ntfs_error
(
sb
,
"$MFT is too big! Aborting."
);
goto
put_err_out
;
}
vol
->
_VMM
(
nr_mft_records
)
=
ll
;
vol
->
nr_mft_records
=
ll
;
/*
* We have got the first extent of the run_list for
* $MFT which means it is now relatively safe to call
...
...
@@ -1184,7 +1384,7 @@ void ntfs_read_inode_mount(struct inode *vi)
* ntfs_read_inode() on extents of $MFT/$DATA. But lets
* hope this never happens...
*/
ntfs_read_inode
(
vi
);
ntfs_read_
locked_
inode
(
vi
);
if
(
is_bad_inode
(
vi
))
{
ntfs_error
(
sb
,
"ntfs_read_inode() of $MFT "
"failed. BUG or corrupt $MFT. "
...
...
fs/ntfs/inode.h
View file @
016b9c5f
...
...
@@ -231,14 +231,16 @@ static inline struct inode *VFS_I(ntfs_inode *ni)
return
&
((
big_ntfs_inode
*
)
ni
)
->
vfs_inode
;
}
extern
struct
inode
*
ntfs_iget
(
struct
super_block
*
sb
,
unsigned
long
mft_no
);
extern
struct
inode
*
ntfs_alloc_big_inode
(
struct
super_block
*
sb
);
extern
void
ntfs_destroy_big_inode
(
struct
inode
*
inode
);
extern
void
ntfs_clear_big_inode
(
struct
inode
*
vi
);
extern
ntfs_inode
*
ntfs_new_extent_inode
(
struct
super_block
*
sb
);
extern
ntfs_inode
*
ntfs_new_extent_inode
(
struct
super_block
*
sb
,
unsigned
long
mft_no
);
extern
void
ntfs_clear_extent_inode
(
ntfs_inode
*
ni
);
extern
void
ntfs_read_inode
(
struct
inode
*
vi
);
extern
void
ntfs_read_inode_mount
(
struct
inode
*
vi
);
extern
void
ntfs_dirty_inode
(
struct
inode
*
vi
);
...
...
fs/ntfs/mft.c
View file @
016b9c5f
...
...
@@ -102,7 +102,7 @@ extern int ntfs_mst_readpage(struct file *, struct page *);
* ntfs_mft_aops - address space operations for access to $MFT
*
* Address space operations for access to $MFT. This allows us to simply use
*
read_cache_page() in map_mft_record
().
*
ntfs_map_page() in map_mft_record_page
().
*/
struct
address_space_operations
ntfs_mft_aops
=
{
writepage:
NULL
,
/* Write dirty page to disk. */
...
...
@@ -417,14 +417,13 @@ MFT_RECORD *map_extent_mft_record(ntfs_inode *base_ni, MFT_REF mref,
return
m
;
}
/* Record wasn't there. Get a new ntfs inode and initialize it. */
ni
=
ntfs_new_extent_inode
(
base_ni
->
vol
->
sb
);
ni
=
ntfs_new_extent_inode
(
base_ni
->
vol
->
sb
,
mft_no
);
if
(
!
ni
)
{
up
(
&
base_ni
->
extent_lock
);
atomic_dec
(
&
base_ni
->
count
);
return
ERR_PTR
(
-
ENOMEM
);
}
ni
->
vol
=
base_ni
->
vol
;
ni
->
mft_no
=
mft_no
;
ni
->
seq_no
=
seq_no
;
ni
->
nr_extents
=
-
1
;
ni
->
_INE
(
base_ntfs_ino
)
=
base_ni
;
...
...
fs/ntfs/namei.c
View file @
016b9c5f
...
...
@@ -38,8 +38,8 @@
* supplying the name of the inode in @dent->d_name.name. ntfs_lookup()
* converts the name to Unicode and walks the contents of the directory inode
* @dir_ino looking for the converted Unicode name. If the name is found in the
* directory, the corresponding inode is loaded by calling
iget() on its inode
* number and the inode is associated with the dentry @dent via a call to
* directory, the corresponding inode is loaded by calling
ntfs_iget() on its
*
inode
number and the inode is associated with the dentry @dent via a call to
* d_add().
*
* If the name is not found in the directory, a NULL inode is inserted into the
...
...
@@ -111,9 +111,9 @@ static struct dentry *ntfs_lookup(struct inode *dir_ino, struct dentry *dent)
kmem_cache_free
(
ntfs_name_cache
,
uname
);
if
(
!
IS_ERR_MREF
(
mref
))
{
dent_ino
=
MREF
(
mref
);
ntfs_debug
(
"Found inode 0x%lx. Calling iget."
,
dent_ino
);
dent_inode
=
iget
(
vol
->
sb
,
dent_ino
);
if
(
dent_inode
)
{
ntfs_debug
(
"Found inode 0x%lx. Calling
ntfs_
iget."
,
dent_ino
);
dent_inode
=
ntfs_
iget
(
vol
->
sb
,
dent_ino
);
if
(
likely
(
!
IS_ERR
(
dent_inode
))
)
{
/* Consistency check. */
if
(
MSEQNO
(
mref
)
==
NTFS_I
(
dent_inode
)
->
seq_no
||
dent_ino
==
FILE_MFT
)
{
...
...
@@ -132,16 +132,19 @@ static struct dentry *ntfs_lookup(struct inode *dir_ino, struct dentry *dent)
ntfs_error
(
vol
->
sb
,
"Found stale reference to inode "
"0x%lx (reference sequence number = "
"0x%x, inode sequence number = 0x%x, "
"returning -E
ACCES
. Run chkdsk."
,
"returning -E
IO
. Run chkdsk."
,
dent_ino
,
MSEQNO
(
mref
),
NTFS_I
(
dent_inode
)
->
seq_no
);
iput
(
dent_inode
);
dent_inode
=
ERR_PTR
(
-
EIO
);
}
else
ntfs_error
(
vol
->
sb
,
"iget(0x%lx) failed, returning "
"-EACCES."
,
dent_ino
);
ntfs_error
(
vol
->
sb
,
"ntfs_iget(0x%lx) failed with "
"error code %li."
,
dent_ino
,
PTR_ERR
(
dent_inode
));
if
(
name
)
kfree
(
name
);
return
ERR_PTR
(
-
EACCES
);
/* Return the error code. */
return
(
struct
dentry
*
)
dent_inode
;
}
/* It is guaranteed that name is no longer allocated at this point. */
if
(
MREF_ERR
(
mref
)
==
-
ENOENT
)
{
...
...
@@ -256,7 +259,8 @@ static struct dentry *ntfs_lookup(struct inode *dir_ino, struct dentry *dent)
BUG_ON
(
real_dent
->
d_inode
!=
dent_inode
);
/*
* Already have the inode and the dentry attached, decrement
* the reference count to balance the iget() we did earlier on.
* the reference count to balance the ntfs_iget() we did
* earlier on.
*/
iput
(
dent_inode
);
return
real_dent
;
...
...
fs/ntfs/super.c
View file @
016b9c5f
...
...
@@ -605,17 +605,17 @@ static BOOL parse_ntfs_boot_sector(ntfs_volume *vol, const NTFS_BOOT_SECTOR *b)
sizeof
(
unsigned
long
)
*
4
);
return
FALSE
;
}
vol
->
_VCL
(
nr_clusters
)
=
ll
;
ntfs_debug
(
"vol->nr_clusters = 0x%Lx"
,
(
long
long
)
vol
->
_VCL
(
nr_clusters
)
);
vol
->
nr_clusters
=
ll
;
ntfs_debug
(
"vol->nr_clusters = 0x%Lx"
,
(
long
long
)
vol
->
nr_clusters
);
ll
=
sle64_to_cpu
(
b
->
mft_lcn
);
if
(
ll
>=
vol
->
_VCL
(
nr_clusters
)
)
{
if
(
ll
>=
vol
->
nr_clusters
)
{
ntfs_error
(
vol
->
sb
,
"MFT LCN is beyond end of volume. Weird."
);
return
FALSE
;
}
vol
->
mft_lcn
=
ll
;
ntfs_debug
(
"vol->mft_lcn = 0x%Lx"
,
(
long
long
)
vol
->
mft_lcn
);
ll
=
sle64_to_cpu
(
b
->
mftmirr_lcn
);
if
(
ll
>=
vol
->
_VCL
(
nr_clusters
)
)
{
if
(
ll
>=
vol
->
nr_clusters
)
{
ntfs_error
(
vol
->
sb
,
"MFTMirr LCN is beyond end of volume. "
"Weird."
);
return
FALSE
;
...
...
@@ -629,7 +629,7 @@ static BOOL parse_ntfs_boot_sector(ntfs_volume *vol, const NTFS_BOOT_SECTOR *b)
* Determine MFT zone size. This is not strictly the right place to do
* this, but I am too lazy to create a function especially for it...
*/
vol
->
mft_zone_end
=
vol
->
_VCL
(
nr_clusters
)
;
vol
->
mft_zone_end
=
vol
->
nr_clusters
;
switch
(
vol
->
mft_zone_multiplier
)
{
/* % of volume size in clusters */
case
4
:
vol
->
mft_zone_end
=
vol
->
mft_zone_end
>>
1
;
/* 50% */
...
...
@@ -678,9 +678,9 @@ static BOOL load_and_init_upcase(ntfs_volume *vol)
ntfs_debug
(
"Entering."
);
/* Read upcase table and setup vol->upcase and vol->upcase_len. */
ino
=
iget
(
sb
,
FILE_UpCase
);
if
(
!
ino
||
is_bad_inode
(
ino
))
{
if
(
ino
)
ino
=
ntfs_
iget
(
sb
,
FILE_UpCase
);
if
(
IS_ERR
(
ino
)
||
is_bad_inode
(
ino
))
{
if
(
!
IS_ERR
(
ino
)
)
iput
(
ino
);
goto
upcase_failed
;
}
...
...
@@ -848,7 +848,7 @@ static BOOL load_system_files(ntfs_volume *vol)
vol
->
mftbmp_allocated_size
=
sle64_to_cpu
(
attr
->
_ANR
(
allocated_size
));
/* Consistency check. */
if
(
vol
->
mftbmp_size
<
(
vol
->
_VMM
(
nr_mft_records
)
+
7
)
>>
3
)
{
if
(
vol
->
mftbmp_size
<
(
vol
->
nr_mft_records
+
7
)
>>
3
)
{
ntfs_error
(
sb
,
"$MFT/$BITMAP is too short to "
"contain a complete mft "
"bitmap: impossible. $MFT is "
...
...
@@ -914,9 +914,9 @@ static BOOL load_system_files(ntfs_volume *vol)
// volume read-write...
/* Get mft mirror inode. */
vol
->
mftmirr_ino
=
iget
(
sb
,
FILE_MFTMirr
);
if
(
!
vol
->
mftmirr_ino
||
is_bad_inode
(
vol
->
mftmirr_ino
))
{
if
(
is_bad_inode
(
vol
->
mftmirr_ino
))
vol
->
mftmirr_ino
=
ntfs_
iget
(
sb
,
FILE_MFTMirr
);
if
(
IS_ERR
(
vol
->
mftmirr_ino
)
||
is_bad_inode
(
vol
->
mftmirr_ino
))
{
if
(
!
IS_ERR
(
vol
->
mftmirr_ino
))
iput
(
vol
->
mftmirr_ino
);
ntfs_error
(
sb
,
"Failed to load $MFTMirr."
);
return
FALSE
;
...
...
@@ -932,13 +932,13 @@ static BOOL load_system_files(ntfs_volume *vol)
* need for any locking at this stage as we are already running
* exclusively as we are mount in progress task.
*/
vol
->
lcnbmp_ino
=
iget
(
sb
,
FILE_Bitmap
);
if
(
!
vol
->
lcnbmp_ino
||
is_bad_inode
(
vol
->
lcnbmp_ino
))
{
if
(
is_bad_inode
(
vol
->
lcnbmp_ino
))
vol
->
lcnbmp_ino
=
ntfs_
iget
(
sb
,
FILE_Bitmap
);
if
(
IS_ERR
(
vol
->
lcnbmp_ino
)
||
is_bad_inode
(
vol
->
lcnbmp_ino
))
{
if
(
!
IS_ERR
(
vol
->
lcnbmp_ino
))
iput
(
vol
->
lcnbmp_ino
);
goto
bitmap_failed
;
}
if
((
vol
->
_VCL
(
nr_lcn_bits
)
+
7
)
>>
3
>
vol
->
lcnbmp_ino
->
i_size
)
{
if
((
vol
->
nr_clusters
+
7
)
>>
3
>
vol
->
lcnbmp_ino
->
i_size
)
{
iput
(
vol
->
lcnbmp_ino
);
bitmap_failed:
ntfs_error
(
sb
,
"Failed to load $Bitmap."
);
...
...
@@ -948,9 +948,9 @@ static BOOL load_system_files(ntfs_volume *vol)
* Get the volume inode and setup our cache of the volume flags and
* version.
*/
vol
->
vol_ino
=
iget
(
sb
,
FILE_Volume
);
if
(
!
vol
->
vol_ino
||
is_bad_inode
(
vol
->
vol_ino
))
{
if
(
is_bad_inode
(
vol
->
vol_ino
))
vol
->
vol_ino
=
ntfs_
iget
(
sb
,
FILE_Volume
);
if
(
IS_ERR
(
vol
->
vol_ino
)
||
is_bad_inode
(
vol
->
vol_ino
))
{
if
(
!
IS_ERR
(
vol
->
vol_ino
))
iput
(
vol
->
vol_ino
);
volume_failed:
ntfs_error
(
sb
,
"Failed to load $Volume."
);
...
...
@@ -993,9 +993,9 @@ static BOOL load_system_files(ntfs_volume *vol)
* Get the inode for the logfile and empty it if this is a read-write
* mount.
*/
tmp_ino
=
iget
(
sb
,
FILE_LogFile
);
if
(
!
tmp_ino
||
is_bad_inode
(
tmp_ino
))
{
if
(
is_bad_inode
(
tmp_ino
))
tmp_ino
=
ntfs_
iget
(
sb
,
FILE_LogFile
);
if
(
IS_ERR
(
tmp_ino
)
||
is_bad_inode
(
tmp_ino
))
{
if
(
!
IS_ERR
(
tmp_ino
))
iput
(
tmp_ino
);
ntfs_error
(
sb
,
"Failed to load $LogFile."
);
// FIMXE: We only want to empty the thing so pointless bailing
...
...
@@ -1010,9 +1010,9 @@ static BOOL load_system_files(ntfs_volume *vol)
* Get the inode for the attribute definitions file and parse the
* attribute definitions.
*/
tmp_ino
=
iget
(
sb
,
FILE_AttrDef
);
if
(
!
tmp_ino
||
is_bad_inode
(
tmp_ino
))
{
if
(
is_bad_inode
(
tmp_ino
))
tmp_ino
=
ntfs_
iget
(
sb
,
FILE_AttrDef
);
if
(
IS_ERR
(
tmp_ino
)
||
is_bad_inode
(
tmp_ino
))
{
if
(
!
IS_ERR
(
tmp_ino
))
iput
(
tmp_ino
);
ntfs_error
(
sb
,
"Failed to load $AttrDef."
);
goto
iput_vol_bmp_mirr_err_out
;
...
...
@@ -1020,9 +1020,9 @@ static BOOL load_system_files(ntfs_volume *vol)
// FIXME: Parse the attribute definitions.
iput
(
tmp_ino
);
/* Get the root directory inode. */
vol
->
root_ino
=
iget
(
sb
,
FILE_root
);
if
(
!
vol
->
root_ino
||
is_bad_inode
(
vol
->
root_ino
))
{
if
(
is_bad_inode
(
vol
->
root_ino
))
vol
->
root_ino
=
ntfs_
iget
(
sb
,
FILE_root
);
if
(
IS_ERR
(
vol
->
root_ino
)
||
is_bad_inode
(
vol
->
root_ino
))
{
if
(
!
IS_ERR
(
vol
->
root_ino
))
iput
(
vol
->
root_ino
);
ntfs_error
(
sb
,
"Failed to load root directory."
);
goto
iput_vol_bmp_mirr_err_out
;
...
...
@@ -1032,18 +1032,18 @@ static BOOL load_system_files(ntfs_volume *vol)
return
TRUE
;
/* NTFS 3.0+ specific initialization. */
/* Get the security descriptors inode. */
vol
->
secure_ino
=
iget
(
sb
,
FILE_Secure
);
if
(
!
vol
->
secure_ino
||
is_bad_inode
(
vol
->
secure_ino
))
{
if
(
is_bad_inode
(
vol
->
secure_ino
))
vol
->
secure_ino
=
ntfs_
iget
(
sb
,
FILE_Secure
);
if
(
IS_ERR
(
vol
->
secure_ino
)
||
is_bad_inode
(
vol
->
secure_ino
))
{
if
(
!
IS_ERR
(
vol
->
secure_ino
))
iput
(
vol
->
secure_ino
);
ntfs_error
(
sb
,
"Failed to load $Secure."
);
goto
iput_root_vol_bmp_mirr_err_out
;
}
// FIXME: Initialize security.
/* Get the extended system files' directory inode. */
tmp_ino
=
iget
(
sb
,
FILE_Extend
);
if
(
!
tmp_ino
||
is_bad_inode
(
tmp_ino
))
{
if
(
is_bad_inode
(
tmp_ino
))
tmp_ino
=
ntfs_
iget
(
sb
,
FILE_Extend
);
if
(
IS_ERR
(
tmp_ino
)
||
is_bad_inode
(
tmp_ino
))
{
if
(
!
IS_ERR
(
tmp_ino
))
iput
(
tmp_ino
);
ntfs_error
(
sb
,
"Failed to load $Extend."
);
goto
iput_sec_root_vol_bmp_mirr_err_out
;
...
...
@@ -1051,8 +1051,8 @@ static BOOL load_system_files(ntfs_volume *vol)
// FIXME: Do something. E.g. want to delete the $UsnJrnl if exists.
// Note we might be doing this at the wrong level; we might want to
// d_alloc_root() and then do a "normal" open(2) of $Extend\$UsnJrnl
// rather than using
iget here, as we don't know the inode number fo
r
// the files in $Extend directory.
// rather than using
ntfs_iget here, as we don't know the inode numbe
r
//
for
the files in $Extend directory.
iput
(
tmp_ino
);
return
TRUE
;
iput_sec_root_vol_bmp_mirr_err_out:
...
...
@@ -1172,7 +1172,7 @@ s64 get_nr_free_clusters(ntfs_volume *vol)
* Convert the number of bits into bytes rounded up, then convert into
* multiples of PAGE_CACHE_SIZE.
*/
max_index
=
(
vol
->
_VCL
(
nr_clusters
)
+
7
)
>>
(
3
+
PAGE_CACHE_SHIFT
);
max_index
=
(
vol
->
nr_clusters
+
7
)
>>
(
3
+
PAGE_CACHE_SHIFT
);
/* Use multiples of 4 bytes. */
max_size
=
PAGE_CACHE_SIZE
>>
2
;
ntfs_debug
(
"Reading $BITMAP, max_index = 0x%lx, max_size = 0x%x."
,
...
...
@@ -1211,7 +1211,7 @@ s64 get_nr_free_clusters(ntfs_volume *vol)
* Get the multiples of 4 bytes in use in the final partial
* page.
*/
max_size
=
((((
vol
->
_VCL
(
nr_clusters
)
+
7
)
>>
3
)
&
~
PAGE_CACHE_MASK
)
max_size
=
((((
vol
->
nr_clusters
+
7
)
>>
3
)
&
~
PAGE_CACHE_MASK
)
+
3
)
>>
2
;
/* If there is a partial page go back and do it. */
if
(
max_size
)
{
...
...
@@ -1254,7 +1254,7 @@ unsigned long get_nr_free_mft_records(ntfs_volume *vol)
* Convert the number of bits into bytes rounded up, then convert into
* multiples of PAGE_CACHE_SIZE.
*/
max_index
=
(
vol
->
_VMM
(
nr_mft_records
)
+
7
)
>>
(
3
+
PAGE_CACHE_SHIFT
);
max_index
=
(
vol
->
nr_mft_records
+
7
)
>>
(
3
+
PAGE_CACHE_SHIFT
);
/* Use multiples of 4 bytes. */
max_size
=
PAGE_CACHE_SIZE
>>
2
;
ntfs_debug
(
"Reading $MFT/$BITMAP, max_index = 0x%lx, max_size = "
...
...
@@ -1293,12 +1293,12 @@ unsigned long get_nr_free_mft_records(ntfs_volume *vol)
* Get the multiples of 4 bytes in use in the final partial
* page.
*/
max_size
=
((((
vol
->
_VMM
(
nr_mft_records
)
+
7
)
>>
3
)
&
max_size
=
((((
vol
->
nr_mft_records
+
7
)
>>
3
)
&
~
PAGE_CACHE_MASK
)
+
3
)
>>
2
;
/* If there is a partial page go back and do it. */
if
(
max_size
)
{
/* Compensate for out of bounds zero bits. */
if
((
i
=
vol
->
_VMM
(
nr_mft_records
)
&
31
))
if
((
i
=
vol
->
nr_mft_records
&
31
))
nr_free
-=
32
-
i
;
ntfs_debug
(
"Handling partial page, max_size = 0x%x"
,
max_size
);
...
...
@@ -1345,7 +1345,7 @@ int ntfs_statfs(struct super_block *sb, struct statfs *sfs)
* inodes are also stored in data blocs ($MFT is a file) this is just
* the total clusters.
*/
sfs
->
f_blocks
=
vol
->
_VCL
(
nr_clusters
)
<<
vol
->
cluster_size_bits
>>
sfs
->
f_blocks
=
vol
->
nr_clusters
<<
vol
->
cluster_size_bits
>>
PAGE_CACHE_SHIFT
;
/* Free data blocks in file system in units of f_bsize. */
size
=
get_nr_free_clusters
(
vol
)
<<
vol
->
cluster_size_bits
>>
...
...
@@ -1394,8 +1394,6 @@ struct super_operations ntfs_mount_sops = {
struct
super_operations
ntfs_sops
=
{
alloc_inode:
ntfs_alloc_big_inode
,
/* VFS: Allocate a new inode. */
destroy_inode:
ntfs_destroy_big_inode
,
/* VFS: Deallocate an inode. */
read_inode:
ntfs_read_inode
,
/* VFS: Load inode from disk,
called from iget(). */
dirty_inode:
ntfs_dirty_inode
,
/* VFS: Called from
__mark_inode_dirty(). */
//write_inode: NULL, /* VFS: Write dirty inode to disk. */
...
...
@@ -1575,9 +1573,9 @@ static int ntfs_fill_super(struct super_block *sb, void *opt, const int silent)
/*
* Now load the metadata required for the page cache and our address
* space operations to function. We do this by setting up a specialised
* read_inode method and then just calling
iget() to obtain the inode
*
for $MFT which is sufficient to allow our normal inode operations
* and associated address space operations to function.
* read_inode method and then just calling
the normal iget() to obtain
*
the inode for $MFT which is sufficient to allow our normal inode
*
operations
and associated address space operations to function.
*/
/*
* Poison vol->mft_ino so we know whether iget() called into our
...
...
@@ -1601,9 +1599,7 @@ static int ntfs_fill_super(struct super_block *sb, void *opt, const int silent)
* Note: sb->s_op has already been set to &ntfs_sops by our specialized
* ntfs_read_inode_mount() method when it was invoked by iget().
*/
down
(
&
ntfs_lock
);
/*
* The current mount is a compression user if the cluster size is
* less than or equal 4kiB.
...
...
@@ -1618,7 +1614,6 @@ static int ntfs_fill_super(struct super_block *sb, void *opt, const int silent)
goto
iput_tmp_ino_err_out_now
;
}
}
/*
* Increment the number of mounts and generate the global default
* upcase table if necessary. Also temporarily increment the number of
...
...
@@ -1629,12 +1624,10 @@ static int ntfs_fill_super(struct super_block *sb, void *opt, const int silent)
ntfs_nr_upcase_users
++
;
up
(
&
ntfs_lock
);
/*
* From now on, ignore @silent parameter. If we fail below this line,
* it will be due to a corrupt fs or a system error, so we report it.
*/
/*
* Open the system files with normal access functions and complete
* setting up the ntfs super block.
...
...
@@ -1643,9 +1636,8 @@ static int ntfs_fill_super(struct super_block *sb, void *opt, const int silent)
ntfs_error
(
sb
,
"Failed to load system files."
);
goto
unl_upcase_iput_tmp_ino_err_out_now
;
}
if
((
sb
->
s_root
=
d_alloc_root
(
vol
->
root_ino
)))
{
/* We increment i_count simulating an iget(). */
/* We increment i_count simulating an
ntfs_
iget(). */
atomic_inc
(
&
vol
->
root_ino
->
i_count
);
ntfs_debug
(
"Exiting, status successful."
);
/* Release the default upcase if it has no users. */
...
...
@@ -1710,10 +1702,10 @@ static int ntfs_fill_super(struct super_block *sb, void *opt, const int silent)
#undef OGIN
/*
* This is needed to get ntfs_clear_extent_inode() called for each
* inode we have ever called
iget()/iput() on, otherwise we A) leak
* resources and B) a subsequent mount fails automatically due to
*
iget() never calling down into our ntfs_read_inode{_mount}() methods
* again...
* inode we have ever called
ntfs_iget()/iput() on, otherwise we A)
*
leak
resources and B) a subsequent mount fails automatically due to
*
ntfs_iget() never calling down into our ntfs_read_locked_inode()
*
method
again...
*/
if
(
invalidate_inodes
(
sb
))
{
ntfs_error
(
sb
,
"Busy inodes left. This is most likely a NTFS "
...
...
fs/ntfs/volume.h
View file @
016b9c5f
...
...
@@ -3,7 +3,7 @@
* of the Linux-NTFS project.
*
* Copyright (c) 2001,2002 Anton Altaparmakov.
* Copyright (
C
) 2002 Richard Russon.
* Copyright (
c
) 2002 Richard Russon.
*
* This program/include file is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published
...
...
@@ -89,10 +89,8 @@ typedef struct {
u32
index_record_size
;
/* in bytes */
u32
index_record_size_mask
;
/* index_record_size - 1 */
u8
index_record_size_bits
;
/* log2(index_record_size) */
union
{
LCN
nr_clusters
;
/* Volume size in clusters. */
LCN
nr_lcn_bits
;
/* Number of bits in lcn bitmap. */
}
SN
(
vcl
);
LCN
nr_clusters
;
/* Volume size in clusters == number of
bits in lcn bitmap. */
LCN
mft_lcn
;
/* Cluster location of mft data. */
LCN
mftmirr_lcn
;
/* Cluster location of copy of mft. */
u64
serial_no
;
/* The volume serial number. */
...
...
@@ -104,10 +102,8 @@ typedef struct {
struct
inode
*
mft_ino
;
/* The VFS inode of $MFT. */
struct
rw_semaphore
mftbmp_lock
;
/* Lock for serializing accesses to the
mft record bitmap ($MFT/$BITMAP). */
union
{
unsigned
long
nr_mft_records
;
/* Number of mft records. */
unsigned
long
nr_mft_bits
;
/* Number of bits in mft bitmap. */
}
SN
(
vmm
);
unsigned
long
nr_mft_records
;
/* Number of mft records == number of
bits in mft bitmap. */
struct
address_space
mftbmp_mapping
;
/* Page cache for $MFT/$BITMAP. */
run_list
mftbmp_rl
;
/* Run list for $MFT/$BITMAP. */
s64
mftbmp_size
;
/* Data size of $MFT/$BITMAP. */
...
...
@@ -128,8 +124,5 @@ typedef struct {
struct
nls_table
*
nls_map
;
}
ntfs_volume
;
#define _VCL(X) SC(vcl,X)
#define _VMM(X) SC(vmm,X)
#endif
/* _LINUX_NTFS_VOLUME_H */
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment