Commit 77417942 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'vfs-6.9.ntfs' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs

Pull ntfs update from Christian Brauner:
 "This removes the old ntfs driver. The new ntfs3 driver is a full
  replacement that was merged over two years ago. We've went through
  various userspace and either they use ntfs3 or they use the fuse
  version of ntfs and thus build neither ntfs nor ntfs3. I think that's
  a clear sign that we should risk removing the legacy ntfs driver.

  Quoting from Arch Linux and Debian:

   - Debian does neither build the legacy ntfs nor the new ntfs3:

     "Not currently built with Debian's kernel packages, 'ntfs' has been
      symlinked to 'ntfs-3g' as it relates to fstab and mount commands.

      Debian kernels are built without support of the ntfs3 driver
      developed by Paragon Software."  (cf. [2])

   - Archlinux provides ntfs3 as their default since 5.15:

     "All officially supported kernels with versions 5.15 or newer are
      built with CONFIG_NTFS3_FS=m and thus support it. Before 5.15,
      NTFS read and write support is provided by the NTFS-3G FUSE file
      system."  (cf. [1]).

  It's unmaintained apart from various odd fixes as well. Worst case we
  have to reintroduce it if someone really has a valid dependency on it.
  But it's worth trying to see whether we can remove it"

Link: https://wiki.archlinux.org/title/NTFS [1]
Link: https://wiki.debian.org/NTFS [2]

* tag 'vfs-6.9.ntfs' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs:
  fs: remove NTFS classic from docum. index
  fs: Remove NTFS classic
parents 7ea65c89 06b8db3a
...@@ -63,6 +63,11 @@ D: dosfs, LILO, some fd features, ATM, various other hacks here and there ...@@ -63,6 +63,11 @@ D: dosfs, LILO, some fd features, ATM, various other hacks here and there
S: Buenos Aires S: Buenos Aires
S: Argentina S: Argentina
NTFS FILESYSTEM
N: Anton Altaparmakov
E: anton@tuxera.com
D: NTFS filesystem
N: Tim Alpaerts N: Tim Alpaerts
E: tim_alpaerts@toyota-motor-europe.com E: tim_alpaerts@toyota-motor-europe.com
D: 802.2 class II logical link control layer, D: 802.2 class II logical link control layer,
......
...@@ -98,7 +98,6 @@ Documentation for filesystem implementations. ...@@ -98,7 +98,6 @@ Documentation for filesystem implementations.
isofs isofs
nilfs2 nilfs2
nfs/index nfs/index
ntfs
ntfs3 ntfs3
ocfs2 ocfs2
ocfs2-online-filecheck ocfs2-online-filecheck
......
This diff is collapsed.
...@@ -15589,16 +15589,6 @@ W: https://github.com/davejiang/linux/wiki ...@@ -15589,16 +15589,6 @@ W: https://github.com/davejiang/linux/wiki
T: git https://github.com/davejiang/linux.git T: git https://github.com/davejiang/linux.git
F: drivers/ntb/hw/intel/ F: drivers/ntb/hw/intel/
NTFS FILESYSTEM
M: Anton Altaparmakov <anton@tuxera.com>
R: Namjae Jeon <linkinjeon@kernel.org>
L: linux-ntfs-dev@lists.sourceforge.net
S: Supported
W: http://www.tuxera.com/
T: git git://git.kernel.org/pub/scm/linux/kernel/git/aia21/ntfs.git
F: Documentation/filesystems/ntfs.rst
F: fs/ntfs/
NTFS3 FILESYSTEM NTFS3 FILESYSTEM
M: Konstantin Komarov <almaz.alexandrovich@paragon-software.com> M: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
L: ntfs3@lists.linux.dev L: ntfs3@lists.linux.dev
......
...@@ -162,7 +162,6 @@ menu "DOS/FAT/EXFAT/NT Filesystems" ...@@ -162,7 +162,6 @@ menu "DOS/FAT/EXFAT/NT Filesystems"
source "fs/fat/Kconfig" source "fs/fat/Kconfig"
source "fs/exfat/Kconfig" source "fs/exfat/Kconfig"
source "fs/ntfs/Kconfig"
source "fs/ntfs3/Kconfig" source "fs/ntfs3/Kconfig"
endmenu endmenu
......
...@@ -91,7 +91,6 @@ obj-y += unicode/ ...@@ -91,7 +91,6 @@ obj-y += unicode/
obj-$(CONFIG_SYSV_FS) += sysv/ obj-$(CONFIG_SYSV_FS) += sysv/
obj-$(CONFIG_SMBFS) += smb/ obj-$(CONFIG_SMBFS) += smb/
obj-$(CONFIG_HPFS_FS) += hpfs/ obj-$(CONFIG_HPFS_FS) += hpfs/
obj-$(CONFIG_NTFS_FS) += ntfs/
obj-$(CONFIG_NTFS3_FS) += ntfs3/ obj-$(CONFIG_NTFS3_FS) += ntfs3/
obj-$(CONFIG_UFS_FS) += ufs/ obj-$(CONFIG_UFS_FS) += ufs/
obj-$(CONFIG_EFS_FS) += efs/ obj-$(CONFIG_EFS_FS) += efs/
......
# SPDX-License-Identifier: GPL-2.0-only
config NTFS_FS
tristate "NTFS file system support"
select BUFFER_HEAD
select NLS
help
NTFS is the file system of Microsoft Windows NT, 2000, XP and 2003.
Saying Y or M here enables read support. There is partial, but
safe, write support available. For write support you must also
say Y to "NTFS write support" below.
There are also a number of user-space tools available, called
ntfsprogs. These include ntfsundelete and ntfsresize, that work
without NTFS support enabled in the kernel.
This is a rewrite from scratch of Linux NTFS support and replaced
the old NTFS code starting with Linux 2.5.11. A backport to
the Linux 2.4 kernel series is separately available as a patch
from the project web site.
For more information see <file:Documentation/filesystems/ntfs.rst>
and <http://www.linux-ntfs.org/>.
To compile this file system support as a module, choose M here: the
module will be called ntfs.
If you are not using Windows NT, 2000, XP or 2003 in addition to
Linux on your computer it is safe to say N.
config NTFS_DEBUG
bool "NTFS debugging support"
depends on NTFS_FS
help
If you are experiencing any problems with the NTFS file system, say
Y here. This will result in additional consistency checks to be
performed by the driver as well as additional debugging messages to
be written to the system log. Note that debugging messages are
disabled by default. To enable them, supply the option debug_msgs=1
at the kernel command line when booting the kernel or as an option
to insmod when loading the ntfs module. Once the driver is active,
you can enable debugging messages by doing (as root):
echo 1 > /proc/sys/fs/ntfs-debug
Replacing the "1" with "0" would disable debug messages.
If you leave debugging messages disabled, this results in little
overhead, but enabling debug messages results in very significant
slowdown of the system.
When reporting bugs, please try to have available a full dump of
debugging messages while the misbehaviour was occurring.
config NTFS_RW
bool "NTFS write support"
depends on NTFS_FS
depends on PAGE_SIZE_LESS_THAN_64KB
help
This enables the partial, but safe, write support in the NTFS driver.
The only supported operation is overwriting existing files, without
changing the file length. No file or directory creation, deletion or
renaming is possible. Note only non-resident files can be written to
so you may find that some very small files (<500 bytes or so) cannot
be written to.
While we cannot guarantee that it will not damage any data, we have
so far not received a single report where the driver would have
damaged someones data so we assume it is perfectly safe to use.
Note: While write support is safe in this version (a rewrite from
scratch of the NTFS support), it should be noted that the old NTFS
write support, included in Linux 2.5.10 and before (since 1997),
is not safe.
This is currently useful with TopologiLinux. TopologiLinux is run
on top of any DOS/Microsoft Windows system without partitioning your
hard disk. Unlike other Linux distributions TopologiLinux does not
need its own partition. For more information see
<http://topologi-linux.sourceforge.net/>
It is perfectly safe to say N here.
# SPDX-License-Identifier: GPL-2.0
# Rules for making the NTFS driver.
obj-$(CONFIG_NTFS_FS) += ntfs.o
ntfs-y := aops.o attrib.o collate.o compress.o debug.o dir.o file.o \
index.o inode.o mft.o mst.o namei.o runlist.o super.o sysctl.o \
unistr.o upcase.o
ntfs-$(CONFIG_NTFS_RW) += bitmap.o lcnalloc.o logfile.o quota.o usnjrnl.o
ccflags-y := -DNTFS_VERSION=\"2.1.32\"
ccflags-$(CONFIG_NTFS_DEBUG) += -DDEBUG
ccflags-$(CONFIG_NTFS_RW) += -DNTFS_RW
This diff is collapsed.
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* aops.h - Defines for NTFS kernel address space operations and page cache
* handling. Part of the Linux-NTFS project.
*
* Copyright (c) 2001-2004 Anton Altaparmakov
* Copyright (c) 2002 Richard Russon
*/
#ifndef _LINUX_NTFS_AOPS_H
#define _LINUX_NTFS_AOPS_H
#include <linux/mm.h>
#include <linux/highmem.h>
#include <linux/pagemap.h>
#include <linux/fs.h>
#include "inode.h"
/**
* ntfs_unmap_page - release a page that was mapped using ntfs_map_page()
* @page: the page to release
*
* Unpin, unmap and release a page that was obtained from ntfs_map_page().
*/
static inline void ntfs_unmap_page(struct page *page)
{
kunmap(page);
put_page(page);
}
/**
* ntfs_map_page - map a page into accessible memory, reading it if necessary
* @mapping: address space for which to obtain the page
* @index: index into the page cache for @mapping of the page to map
*
* Read a page from the page cache of the address space @mapping at position
* @index, where @index is in units of PAGE_SIZE, and not in bytes.
*
* If the page is not in memory it is loaded from disk first using the
* read_folio method defined in the address space operations of @mapping
* and the page is added to the page cache of @mapping in the process.
*
* If the page belongs to an mst protected attribute and it is marked as such
* in its ntfs inode (NInoMstProtected()) the mst fixups are applied but no
* error checking is performed. This means the caller has to verify whether
* the ntfs record(s) contained in the page are valid or not using one of the
* ntfs_is_XXXX_record{,p}() macros, where XXXX is the record type you are
* expecting to see. (For details of the macros, see fs/ntfs/layout.h.)
*
* If the page is in high memory it is mapped into memory directly addressible
* by the kernel.
*
* Finally the page count is incremented, thus pinning the page into place.
*
* The above means that page_address(page) can be used on all pages obtained
* with ntfs_map_page() to get the kernel virtual address of the page.
*
* When finished with the page, the caller has to call ntfs_unmap_page() to
* unpin, unmap and release the page.
*
* Note this does not grant exclusive access. If such is desired, the caller
* must provide it independently of the ntfs_{un}map_page() calls by using
* a {rw_}semaphore or other means of serialization. A spin lock cannot be
* used as ntfs_map_page() can block.
*
* The unlocked and uptodate page is returned on success or an encoded error
* on failure. Caller has to test for error using the IS_ERR() macro on the
* return value. If that evaluates to 'true', the negative error code can be
* obtained using PTR_ERR() on the return value of ntfs_map_page().
*/
static inline struct page *ntfs_map_page(struct address_space *mapping,
unsigned long index)
{
struct page *page = read_mapping_page(mapping, index, NULL);
if (!IS_ERR(page))
kmap(page);
return page;
}
#ifdef NTFS_RW
extern void mark_ntfs_record_dirty(struct page *page, const unsigned int ofs);
#endif /* NTFS_RW */
#endif /* _LINUX_NTFS_AOPS_H */
This diff is collapsed.
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* attrib.h - Defines for attribute handling in NTFS Linux kernel driver.
* Part of the Linux-NTFS project.
*
* Copyright (c) 2001-2005 Anton Altaparmakov
* Copyright (c) 2002 Richard Russon
*/
#ifndef _LINUX_NTFS_ATTRIB_H
#define _LINUX_NTFS_ATTRIB_H
#include "endian.h"
#include "types.h"
#include "layout.h"
#include "inode.h"
#include "runlist.h"
#include "volume.h"
/**
* ntfs_attr_search_ctx - used in attribute search functions
* @mrec: buffer containing mft record to search
* @attr: attribute record in @mrec where to begin/continue search
* @is_first: if true ntfs_attr_lookup() begins search with @attr, else after
*
* Structure must be initialized to zero before the first call to one of the
* attribute search functions. Initialize @mrec to point to the mft record to
* search, and @attr to point to the first attribute within @mrec (not necessary
* if calling the _first() functions), and set @is_first to 'true' (not necessary
* if calling the _first() functions).
*
* If @is_first is 'true', the search begins with @attr. If @is_first is 'false',
* the search begins after @attr. This is so that, after the first call to one
* of the search attribute functions, we can call the function again, without
* any modification of the search context, to automagically get the next
* matching attribute.
*/
typedef struct {
MFT_RECORD *mrec;
ATTR_RECORD *attr;
bool is_first;
ntfs_inode *ntfs_ino;
ATTR_LIST_ENTRY *al_entry;
ntfs_inode *base_ntfs_ino;
MFT_RECORD *base_mrec;
ATTR_RECORD *base_attr;
} ntfs_attr_search_ctx;
extern int ntfs_map_runlist_nolock(ntfs_inode *ni, VCN vcn,
ntfs_attr_search_ctx *ctx);
extern int ntfs_map_runlist(ntfs_inode *ni, VCN vcn);
extern LCN ntfs_attr_vcn_to_lcn_nolock(ntfs_inode *ni, const VCN vcn,
const bool write_locked);
extern runlist_element *ntfs_attr_find_vcn_nolock(ntfs_inode *ni,
const VCN vcn, ntfs_attr_search_ctx *ctx);
int ntfs_attr_lookup(const ATTR_TYPE type, const ntfschar *name,
const u32 name_len, const IGNORE_CASE_BOOL ic,
const VCN lowest_vcn, const u8 *val, const u32 val_len,
ntfs_attr_search_ctx *ctx);
extern int load_attribute_list(ntfs_volume *vol, runlist *rl, u8 *al_start,
const s64 size, const s64 initialized_size);
static inline s64 ntfs_attr_size(const ATTR_RECORD *a)
{
if (!a->non_resident)
return (s64)le32_to_cpu(a->data.resident.value_length);
return sle64_to_cpu(a->data.non_resident.data_size);
}
extern void ntfs_attr_reinit_search_ctx(ntfs_attr_search_ctx *ctx);
extern ntfs_attr_search_ctx *ntfs_attr_get_search_ctx(ntfs_inode *ni,
MFT_RECORD *mrec);
extern void ntfs_attr_put_search_ctx(ntfs_attr_search_ctx *ctx);
#ifdef NTFS_RW
extern int ntfs_attr_size_bounds_check(const ntfs_volume *vol,
const ATTR_TYPE type, const s64 size);
extern int ntfs_attr_can_be_non_resident(const ntfs_volume *vol,
const ATTR_TYPE type);
extern int ntfs_attr_can_be_resident(const ntfs_volume *vol,
const ATTR_TYPE type);
extern int ntfs_attr_record_resize(MFT_RECORD *m, ATTR_RECORD *a, u32 new_size);
extern int ntfs_resident_attr_value_resize(MFT_RECORD *m, ATTR_RECORD *a,
const u32 new_size);
extern int ntfs_attr_make_non_resident(ntfs_inode *ni, const u32 data_size);
extern s64 ntfs_attr_extend_allocation(ntfs_inode *ni, s64 new_alloc_size,
const s64 new_data_size, const s64 data_start);
extern int ntfs_attr_set(ntfs_inode *ni, const s64 ofs, const s64 cnt,
const u8 val);
#endif /* NTFS_RW */
#endif /* _LINUX_NTFS_ATTRIB_H */
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* bitmap.c - NTFS kernel bitmap handling. Part of the Linux-NTFS project.
*
* Copyright (c) 2004-2005 Anton Altaparmakov
*/
#ifdef NTFS_RW
#include <linux/pagemap.h>
#include "bitmap.h"
#include "debug.h"
#include "aops.h"
#include "ntfs.h"
/**
* __ntfs_bitmap_set_bits_in_run - set a run of bits in a bitmap to a value
* @vi: vfs inode describing the bitmap
* @start_bit: first bit to set
* @count: number of bits to set
* @value: value to set the bits to (i.e. 0 or 1)
* @is_rollback: if 'true' this is a rollback operation
*
* Set @count bits starting at bit @start_bit in the bitmap described by the
* vfs inode @vi to @value, where @value is either 0 or 1.
*
* @is_rollback should always be 'false', it is for internal use to rollback
* errors. You probably want to use ntfs_bitmap_set_bits_in_run() instead.
*
* Return 0 on success and -errno on error.
*/
int __ntfs_bitmap_set_bits_in_run(struct inode *vi, const s64 start_bit,
const s64 count, const u8 value, const bool is_rollback)
{
s64 cnt = count;
pgoff_t index, end_index;
struct address_space *mapping;
struct page *page;
u8 *kaddr;
int pos, len;
u8 bit;
BUG_ON(!vi);
ntfs_debug("Entering for i_ino 0x%lx, start_bit 0x%llx, count 0x%llx, "
"value %u.%s", vi->i_ino, (unsigned long long)start_bit,
(unsigned long long)cnt, (unsigned int)value,
is_rollback ? " (rollback)" : "");
BUG_ON(start_bit < 0);
BUG_ON(cnt < 0);
BUG_ON(value > 1);
/*
* Calculate the indices for the pages containing the first and last
* bits, i.e. @start_bit and @start_bit + @cnt - 1, respectively.
*/
index = start_bit >> (3 + PAGE_SHIFT);
end_index = (start_bit + cnt - 1) >> (3 + PAGE_SHIFT);
/* Get the page containing the first bit (@start_bit). */
mapping = vi->i_mapping;
page = ntfs_map_page(mapping, index);
if (IS_ERR(page)) {
if (!is_rollback)
ntfs_error(vi->i_sb, "Failed to map first page (error "
"%li), aborting.", PTR_ERR(page));
return PTR_ERR(page);
}
kaddr = page_address(page);
/* Set @pos to the position of the byte containing @start_bit. */
pos = (start_bit >> 3) & ~PAGE_MASK;
/* Calculate the position of @start_bit in the first byte. */
bit = start_bit & 7;
/* If the first byte is partial, modify the appropriate bits in it. */
if (bit) {
u8 *byte = kaddr + pos;
while ((bit & 7) && cnt) {
cnt--;
if (value)
*byte |= 1 << bit++;
else
*byte &= ~(1 << bit++);
}
/* If we are done, unmap the page and return success. */
if (!cnt)
goto done;
/* Update @pos to the new position. */
pos++;
}
/*
* Depending on @value, modify all remaining whole bytes in the page up
* to @cnt.
*/
len = min_t(s64, cnt >> 3, PAGE_SIZE - pos);
memset(kaddr + pos, value ? 0xff : 0, len);
cnt -= len << 3;
/* Update @len to point to the first not-done byte in the page. */
if (cnt < 8)
len += pos;
/* If we are not in the last page, deal with all subsequent pages. */
while (index < end_index) {
BUG_ON(cnt <= 0);
/* Update @index and get the next page. */
flush_dcache_page(page);
set_page_dirty(page);
ntfs_unmap_page(page);
page = ntfs_map_page(mapping, ++index);
if (IS_ERR(page))
goto rollback;
kaddr = page_address(page);
/*
* Depending on @value, modify all remaining whole bytes in the
* page up to @cnt.
*/
len = min_t(s64, cnt >> 3, PAGE_SIZE);
memset(kaddr, value ? 0xff : 0, len);
cnt -= len << 3;
}
/*
* The currently mapped page is the last one. If the last byte is
* partial, modify the appropriate bits in it. Note, @len is the
* position of the last byte inside the page.
*/
if (cnt) {
u8 *byte;
BUG_ON(cnt > 7);
bit = cnt;
byte = kaddr + len;
while (bit--) {
if (value)
*byte |= 1 << bit;
else
*byte &= ~(1 << bit);
}
}
done:
/* We are done. Unmap the page and return success. */
flush_dcache_page(page);
set_page_dirty(page);
ntfs_unmap_page(page);
ntfs_debug("Done.");
return 0;
rollback:
/*
* Current state:
* - no pages are mapped
* - @count - @cnt is the number of bits that have been modified
*/
if (is_rollback)
return PTR_ERR(page);
if (count != cnt)
pos = __ntfs_bitmap_set_bits_in_run(vi, start_bit, count - cnt,
value ? 0 : 1, true);
else
pos = 0;
if (!pos) {
/* Rollback was successful. */
ntfs_error(vi->i_sb, "Failed to map subsequent page (error "
"%li), aborting.", PTR_ERR(page));
} else {
/* Rollback failed. */
ntfs_error(vi->i_sb, "Failed to map subsequent page (error "
"%li) and rollback failed (error %i). "
"Aborting and leaving inconsistent metadata. "
"Unmount and run chkdsk.", PTR_ERR(page), pos);
NVolSetErrors(NTFS_SB(vi->i_sb));
}
return PTR_ERR(page);
}
#endif /* NTFS_RW */
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* bitmap.h - Defines for NTFS kernel bitmap handling. Part of the Linux-NTFS
* project.
*
* Copyright (c) 2004 Anton Altaparmakov
*/
#ifndef _LINUX_NTFS_BITMAP_H
#define _LINUX_NTFS_BITMAP_H
#ifdef NTFS_RW
#include <linux/fs.h>
#include "types.h"
extern int __ntfs_bitmap_set_bits_in_run(struct inode *vi, const s64 start_bit,
const s64 count, const u8 value, const bool is_rollback);
/**
* ntfs_bitmap_set_bits_in_run - set a run of bits in a bitmap to a value
* @vi: vfs inode describing the bitmap
* @start_bit: first bit to set
* @count: number of bits to set
* @value: value to set the bits to (i.e. 0 or 1)
*
* Set @count bits starting at bit @start_bit in the bitmap described by the
* vfs inode @vi to @value, where @value is either 0 or 1.
*
* Return 0 on success and -errno on error.
*/
static inline int ntfs_bitmap_set_bits_in_run(struct inode *vi,
const s64 start_bit, const s64 count, const u8 value)
{
return __ntfs_bitmap_set_bits_in_run(vi, start_bit, count, value,
false);
}
/**
* ntfs_bitmap_set_run - set a run of bits in a bitmap
* @vi: vfs inode describing the bitmap
* @start_bit: first bit to set
* @count: number of bits to set
*
* Set @count bits starting at bit @start_bit in the bitmap described by the
* vfs inode @vi.
*
* Return 0 on success and -errno on error.
*/
static inline int ntfs_bitmap_set_run(struct inode *vi, const s64 start_bit,
const s64 count)
{
return ntfs_bitmap_set_bits_in_run(vi, start_bit, count, 1);
}
/**
* ntfs_bitmap_clear_run - clear a run of bits in a bitmap
* @vi: vfs inode describing the bitmap
* @start_bit: first bit to clear
* @count: number of bits to clear
*
* Clear @count bits starting at bit @start_bit in the bitmap described by the
* vfs inode @vi.
*
* Return 0 on success and -errno on error.
*/
static inline int ntfs_bitmap_clear_run(struct inode *vi, const s64 start_bit,
const s64 count)
{
return ntfs_bitmap_set_bits_in_run(vi, start_bit, count, 0);
}
/**
* ntfs_bitmap_set_bit - set a bit in a bitmap
* @vi: vfs inode describing the bitmap
* @bit: bit to set
*
* Set bit @bit in the bitmap described by the vfs inode @vi.
*
* Return 0 on success and -errno on error.
*/
static inline int ntfs_bitmap_set_bit(struct inode *vi, const s64 bit)
{
return ntfs_bitmap_set_run(vi, bit, 1);
}
/**
* ntfs_bitmap_clear_bit - clear a bit in a bitmap
* @vi: vfs inode describing the bitmap
* @bit: bit to clear
*
* Clear bit @bit in the bitmap described by the vfs inode @vi.
*
* Return 0 on success and -errno on error.
*/
static inline int ntfs_bitmap_clear_bit(struct inode *vi, const s64 bit)
{
return ntfs_bitmap_clear_run(vi, bit, 1);
}
#endif /* NTFS_RW */
#endif /* defined _LINUX_NTFS_BITMAP_H */
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* collate.c - NTFS kernel collation handling. Part of the Linux-NTFS project.
*
* Copyright (c) 2004 Anton Altaparmakov
*/
#include "collate.h"
#include "debug.h"
#include "ntfs.h"
static int ntfs_collate_binary(ntfs_volume *vol,
const void *data1, const int data1_len,
const void *data2, const int data2_len)
{
int rc;
ntfs_debug("Entering.");
rc = memcmp(data1, data2, min(data1_len, data2_len));
if (!rc && (data1_len != data2_len)) {
if (data1_len < data2_len)
rc = -1;
else
rc = 1;
}
ntfs_debug("Done, returning %i", rc);
return rc;
}
static int ntfs_collate_ntofs_ulong(ntfs_volume *vol,
const void *data1, const int data1_len,
const void *data2, const int data2_len)
{
int rc;
u32 d1, d2;
ntfs_debug("Entering.");
// FIXME: We don't really want to bug here.
BUG_ON(data1_len != data2_len);
BUG_ON(data1_len != 4);
d1 = le32_to_cpup(data1);
d2 = le32_to_cpup(data2);
if (d1 < d2)
rc = -1;
else {
if (d1 == d2)
rc = 0;
else
rc = 1;
}
ntfs_debug("Done, returning %i", rc);
return rc;
}
typedef int (*ntfs_collate_func_t)(ntfs_volume *, const void *, const int,
const void *, const int);
static ntfs_collate_func_t ntfs_do_collate0x0[3] = {
ntfs_collate_binary,
NULL/*ntfs_collate_file_name*/,
NULL/*ntfs_collate_unicode_string*/,
};
static ntfs_collate_func_t ntfs_do_collate0x1[4] = {
ntfs_collate_ntofs_ulong,
NULL/*ntfs_collate_ntofs_sid*/,
NULL/*ntfs_collate_ntofs_security_hash*/,
NULL/*ntfs_collate_ntofs_ulongs*/,
};
/**
* ntfs_collate - collate two data items using a specified collation rule
* @vol: ntfs volume to which the data items belong
* @cr: collation rule to use when comparing the items
* @data1: first data item to collate
* @data1_len: length in bytes of @data1
* @data2: second data item to collate
* @data2_len: length in bytes of @data2
*
* Collate the two data items @data1 and @data2 using the collation rule @cr
* and return -1, 0, ir 1 if @data1 is found, respectively, to collate before,
* to match, or to collate after @data2.
*
* For speed we use the collation rule @cr as an index into two tables of
* function pointers to call the appropriate collation function.
*/
int ntfs_collate(ntfs_volume *vol, COLLATION_RULE cr,
const void *data1, const int data1_len,
const void *data2, const int data2_len) {
int i;
ntfs_debug("Entering.");
/*
* FIXME: At the moment we only support COLLATION_BINARY and
* COLLATION_NTOFS_ULONG, so we BUG() for everything else for now.
*/
BUG_ON(cr != COLLATION_BINARY && cr != COLLATION_NTOFS_ULONG);
i = le32_to_cpu(cr);
BUG_ON(i < 0);
if (i <= 0x02)
return ntfs_do_collate0x0[i](vol, data1, data1_len,
data2, data2_len);
BUG_ON(i < 0x10);
i -= 0x10;
if (likely(i <= 3))
return ntfs_do_collate0x1[i](vol, data1, data1_len,
data2, data2_len);
BUG();
return 0;
}
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* collate.h - Defines for NTFS kernel collation handling. Part of the
* Linux-NTFS project.
*
* Copyright (c) 2004 Anton Altaparmakov
*/
#ifndef _LINUX_NTFS_COLLATE_H
#define _LINUX_NTFS_COLLATE_H
#include "types.h"
#include "volume.h"
static inline bool ntfs_is_collation_rule_supported(COLLATION_RULE cr) {
int i;
/*
* FIXME: At the moment we only support COLLATION_BINARY and
* COLLATION_NTOFS_ULONG, so we return false for everything else for
* now.
*/
if (unlikely(cr != COLLATION_BINARY && cr != COLLATION_NTOFS_ULONG))
return false;
i = le32_to_cpu(cr);
if (likely(((i >= 0) && (i <= 0x02)) ||
((i >= 0x10) && (i <= 0x13))))
return true;
return false;
}
extern int ntfs_collate(ntfs_volume *vol, COLLATION_RULE cr,
const void *data1, const int data1_len,
const void *data2, const int data2_len);
#endif /* _LINUX_NTFS_COLLATE_H */
This diff is collapsed.
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* debug.c - NTFS kernel debug support. Part of the Linux-NTFS project.
*
* Copyright (c) 2001-2004 Anton Altaparmakov
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include "debug.h"
/**
* __ntfs_warning - output a warning to the syslog
* @function: name of function outputting the warning
* @sb: super block of mounted ntfs filesystem
* @fmt: warning string containing format specifications
* @...: a variable number of arguments specified in @fmt
*
* Outputs a warning to the syslog for the mounted ntfs filesystem described
* by @sb.
*
* @fmt and the corresponding @... is printf style format string containing
* the warning string and the corresponding format arguments, respectively.
*
* @function is the name of the function from which __ntfs_warning is being
* called.
*
* Note, you should be using debug.h::ntfs_warning(@sb, @fmt, @...) instead
* as this provides the @function parameter automatically.
*/
void __ntfs_warning(const char *function, const struct super_block *sb,
const char *fmt, ...)
{
struct va_format vaf;
va_list args;
int flen = 0;
#ifndef DEBUG
if (!printk_ratelimit())
return;
#endif
if (function)
flen = strlen(function);
va_start(args, fmt);
vaf.fmt = fmt;
vaf.va = &args;
if (sb)
pr_warn("(device %s): %s(): %pV\n",
sb->s_id, flen ? function : "", &vaf);
else
pr_warn("%s(): %pV\n", flen ? function : "", &vaf);
va_end(args);
}
/**
* __ntfs_error - output an error to the syslog
* @function: name of function outputting the error
* @sb: super block of mounted ntfs filesystem
* @fmt: error string containing format specifications
* @...: a variable number of arguments specified in @fmt
*
* Outputs an error to the syslog for the mounted ntfs filesystem described
* by @sb.
*
* @fmt and the corresponding @... is printf style format string containing
* the error string and the corresponding format arguments, respectively.
*
* @function is the name of the function from which __ntfs_error is being
* called.
*
* Note, you should be using debug.h::ntfs_error(@sb, @fmt, @...) instead
* as this provides the @function parameter automatically.
*/
void __ntfs_error(const char *function, const struct super_block *sb,
const char *fmt, ...)
{
struct va_format vaf;
va_list args;
int flen = 0;
#ifndef DEBUG
if (!printk_ratelimit())
return;
#endif
if (function)
flen = strlen(function);
va_start(args, fmt);
vaf.fmt = fmt;
vaf.va = &args;
if (sb)
pr_err("(device %s): %s(): %pV\n",
sb->s_id, flen ? function : "", &vaf);
else
pr_err("%s(): %pV\n", flen ? function : "", &vaf);
va_end(args);
}
#ifdef DEBUG
/* If 1, output debug messages, and if 0, don't. */
int debug_msgs = 0;
void __ntfs_debug(const char *file, int line, const char *function,
const char *fmt, ...)
{
struct va_format vaf;
va_list args;
int flen = 0;
if (!debug_msgs)
return;
if (function)
flen = strlen(function);
va_start(args, fmt);
vaf.fmt = fmt;
vaf.va = &args;
pr_debug("(%s, %d): %s(): %pV", file, line, flen ? function : "", &vaf);
va_end(args);
}
/* Dump a runlist. Caller has to provide synchronisation for @rl. */
void ntfs_debug_dump_runlist(const runlist_element *rl)
{
int i;
const char *lcn_str[5] = { "LCN_HOLE ", "LCN_RL_NOT_MAPPED",
"LCN_ENOENT ", "LCN_unknown " };
if (!debug_msgs)
return;
pr_debug("Dumping runlist (values in hex):\n");
if (!rl) {
pr_debug("Run list not present.\n");
return;
}
pr_debug("VCN LCN Run length\n");
for (i = 0; ; i++) {
LCN lcn = (rl + i)->lcn;
if (lcn < (LCN)0) {
int index = -lcn - 1;
if (index > -LCN_ENOENT - 1)
index = 3;
pr_debug("%-16Lx %s %-16Lx%s\n",
(long long)(rl + i)->vcn, lcn_str[index],
(long long)(rl + i)->length,
(rl + i)->length ? "" :
" (runlist end)");
} else
pr_debug("%-16Lx %-16Lx %-16Lx%s\n",
(long long)(rl + i)->vcn,
(long long)(rl + i)->lcn,
(long long)(rl + i)->length,
(rl + i)->length ? "" :
" (runlist end)");
if (!(rl + i)->length)
break;
}
}
#endif
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* debug.h - NTFS kernel debug support. Part of the Linux-NTFS project.
*
* Copyright (c) 2001-2004 Anton Altaparmakov
*/
#ifndef _LINUX_NTFS_DEBUG_H
#define _LINUX_NTFS_DEBUG_H
#include <linux/fs.h>
#include "runlist.h"
#ifdef DEBUG
extern int debug_msgs;
extern __printf(4, 5)
void __ntfs_debug(const char *file, int line, const char *function,
const char *format, ...);
/**
* ntfs_debug - write a debug level message to syslog
* @f: a printf format string containing the message
* @...: the variables to substitute into @f
*
* ntfs_debug() writes a DEBUG level message to the syslog but only if the
* driver was compiled with -DDEBUG. Otherwise, the call turns into a NOP.
*/
#define ntfs_debug(f, a...) \
__ntfs_debug(__FILE__, __LINE__, __func__, f, ##a)
extern void ntfs_debug_dump_runlist(const runlist_element *rl);
#else /* !DEBUG */
#define ntfs_debug(fmt, ...) \
do { \
if (0) \
no_printk(fmt, ##__VA_ARGS__); \
} while (0)
#define ntfs_debug_dump_runlist(rl) do {} while (0)
#endif /* !DEBUG */
extern __printf(3, 4)
void __ntfs_warning(const char *function, const struct super_block *sb,
const char *fmt, ...);
#define ntfs_warning(sb, f, a...) __ntfs_warning(__func__, sb, f, ##a)
extern __printf(3, 4)
void __ntfs_error(const char *function, const struct super_block *sb,
const char *fmt, ...);
#define ntfs_error(sb, f, a...) __ntfs_error(__func__, sb, f, ##a)
#endif /* _LINUX_NTFS_DEBUG_H */
This diff is collapsed.
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* dir.h - Defines for directory handling in NTFS Linux kernel driver. Part of
* the Linux-NTFS project.
*
* Copyright (c) 2002-2004 Anton Altaparmakov
*/
#ifndef _LINUX_NTFS_DIR_H
#define _LINUX_NTFS_DIR_H
#include "layout.h"
#include "inode.h"
#include "types.h"
/*
* ntfs_name is used to return the file name to the caller of
* ntfs_lookup_inode_by_name() in order for the caller (namei.c::ntfs_lookup())
* to be able to deal with dcache aliasing issues.
*/
typedef struct {
MFT_REF mref;
FILE_NAME_TYPE_FLAGS type;
u8 len;
ntfschar name[0];
} __attribute__ ((__packed__)) ntfs_name;
/* The little endian Unicode string $I30 as a global constant. */
extern ntfschar I30[5];
extern MFT_REF ntfs_lookup_inode_by_name(ntfs_inode *dir_ni,
const ntfschar *uname, const int uname_len, ntfs_name **res);
#endif /* _LINUX_NTFS_FS_DIR_H */
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* endian.h - Defines for endianness handling in NTFS Linux kernel driver.
* Part of the Linux-NTFS project.
*
* Copyright (c) 2001-2004 Anton Altaparmakov
*/
#ifndef _LINUX_NTFS_ENDIAN_H
#define _LINUX_NTFS_ENDIAN_H
#include <asm/byteorder.h>
#include "types.h"
/*
* Signed endianness conversion functions.
*/
static inline s16 sle16_to_cpu(sle16 x)
{
return le16_to_cpu((__force le16)x);
}
static inline s32 sle32_to_cpu(sle32 x)
{
return le32_to_cpu((__force le32)x);
}
static inline s64 sle64_to_cpu(sle64 x)
{
return le64_to_cpu((__force le64)x);
}
static inline s16 sle16_to_cpup(sle16 *x)
{
return le16_to_cpu(*(__force le16*)x);
}
static inline s32 sle32_to_cpup(sle32 *x)
{
return le32_to_cpu(*(__force le32*)x);
}
static inline s64 sle64_to_cpup(sle64 *x)
{
return le64_to_cpu(*(__force le64*)x);
}
static inline sle16 cpu_to_sle16(s16 x)
{
return (__force sle16)cpu_to_le16(x);
}
static inline sle32 cpu_to_sle32(s32 x)
{
return (__force sle32)cpu_to_le32(x);
}
static inline sle64 cpu_to_sle64(s64 x)
{
return (__force sle64)cpu_to_le64(x);
}
static inline sle16 cpu_to_sle16p(s16 *x)
{
return (__force sle16)cpu_to_le16(*x);
}
static inline sle32 cpu_to_sle32p(s32 *x)
{
return (__force sle32)cpu_to_le32(*x);
}
static inline sle64 cpu_to_sle64p(s64 *x)
{
return (__force sle64)cpu_to_le64(*x);
}
#endif /* _LINUX_NTFS_ENDIAN_H */
This diff is collapsed.
This diff is collapsed.
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* index.h - Defines for NTFS kernel index handling. Part of the Linux-NTFS
* project.
*
* Copyright (c) 2004 Anton Altaparmakov
*/
#ifndef _LINUX_NTFS_INDEX_H
#define _LINUX_NTFS_INDEX_H
#include <linux/fs.h>
#include "types.h"
#include "layout.h"
#include "inode.h"
#include "attrib.h"
#include "mft.h"
#include "aops.h"
/**
* @idx_ni: index inode containing the @entry described by this context
* @entry: index entry (points into @ir or @ia)
* @data: index entry data (points into @entry)
* @data_len: length in bytes of @data
* @is_in_root: 'true' if @entry is in @ir and 'false' if it is in @ia
* @ir: index root if @is_in_root and NULL otherwise
* @actx: attribute search context if @is_in_root and NULL otherwise
* @base_ni: base inode if @is_in_root and NULL otherwise
* @ia: index block if @is_in_root is 'false' and NULL otherwise
* @page: page if @is_in_root is 'false' and NULL otherwise
*
* @idx_ni is the index inode this context belongs to.
*
* @entry is the index entry described by this context. @data and @data_len
* are the index entry data and its length in bytes, respectively. @data
* simply points into @entry. This is probably what the user is interested in.
*
* If @is_in_root is 'true', @entry is in the index root attribute @ir described
* by the attribute search context @actx and the base inode @base_ni. @ia and
* @page are NULL in this case.
*
* If @is_in_root is 'false', @entry is in the index allocation attribute and @ia
* and @page point to the index allocation block and the mapped, locked page it
* is in, respectively. @ir, @actx and @base_ni are NULL in this case.
*
* To obtain a context call ntfs_index_ctx_get().
*
* We use this context to allow ntfs_index_lookup() to return the found index
* @entry and its @data without having to allocate a buffer and copy the @entry
* and/or its @data into it.
*
* When finished with the @entry and its @data, call ntfs_index_ctx_put() to
* free the context and other associated resources.
*
* If the index entry was modified, call flush_dcache_index_entry_page()
* immediately after the modification and either ntfs_index_entry_mark_dirty()
* or ntfs_index_entry_write() before the call to ntfs_index_ctx_put() to
* ensure that the changes are written to disk.
*/
typedef struct {
ntfs_inode *idx_ni;
INDEX_ENTRY *entry;
void *data;
u16 data_len;
bool is_in_root;
INDEX_ROOT *ir;
ntfs_attr_search_ctx *actx;
ntfs_inode *base_ni;
INDEX_ALLOCATION *ia;
struct page *page;
} ntfs_index_context;
extern ntfs_index_context *ntfs_index_ctx_get(ntfs_inode *idx_ni);
extern void ntfs_index_ctx_put(ntfs_index_context *ictx);
extern int ntfs_index_lookup(const void *key, const int key_len,
ntfs_index_context *ictx);
#ifdef NTFS_RW
/**
* ntfs_index_entry_flush_dcache_page - flush_dcache_page() for index entries
* @ictx: ntfs index context describing the index entry
*
* Call flush_dcache_page() for the page in which an index entry resides.
*
* This must be called every time an index entry is modified, just after the
* modification.
*
* If the index entry is in the index root attribute, simply flush the page
* containing the mft record containing the index root attribute.
*
* If the index entry is in an index block belonging to the index allocation
* attribute, simply flush the page cache page containing the index block.
*/
static inline void ntfs_index_entry_flush_dcache_page(ntfs_index_context *ictx)
{
if (ictx->is_in_root)
flush_dcache_mft_record_page(ictx->actx->ntfs_ino);
else
flush_dcache_page(ictx->page);
}
/**
* ntfs_index_entry_mark_dirty - mark an index entry dirty
* @ictx: ntfs index context describing the index entry
*
* Mark the index entry described by the index entry context @ictx dirty.
*
* If the index entry is in the index root attribute, simply mark the mft
* record containing the index root attribute dirty. This ensures the mft
* record, and hence the index root attribute, will be written out to disk
* later.
*
* If the index entry is in an index block belonging to the index allocation
* attribute, mark the buffers belonging to the index record as well as the
* page cache page the index block is in dirty. This automatically marks the
* VFS inode of the ntfs index inode to which the index entry belongs dirty,
* too (I_DIRTY_PAGES) and this in turn ensures the page buffers, and hence the
* dirty index block, will be written out to disk later.
*/
static inline void ntfs_index_entry_mark_dirty(ntfs_index_context *ictx)
{
if (ictx->is_in_root)
mark_mft_record_dirty(ictx->actx->ntfs_ino);
else
mark_ntfs_record_dirty(ictx->page,
(u8*)ictx->ia - (u8*)page_address(ictx->page));
}
#endif /* NTFS_RW */
#endif /* _LINUX_NTFS_INDEX_H */
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* lcnalloc.h - Exports for NTFS kernel cluster (de)allocation. Part of the
* Linux-NTFS project.
*
* Copyright (c) 2004-2005 Anton Altaparmakov
*/
#ifndef _LINUX_NTFS_LCNALLOC_H
#define _LINUX_NTFS_LCNALLOC_H
#ifdef NTFS_RW
#include <linux/fs.h>
#include "attrib.h"
#include "types.h"
#include "inode.h"
#include "runlist.h"
#include "volume.h"
typedef enum {
FIRST_ZONE = 0, /* For sanity checking. */
MFT_ZONE = 0, /* Allocate from $MFT zone. */
DATA_ZONE = 1, /* Allocate from $DATA zone. */
LAST_ZONE = 1, /* For sanity checking. */
} NTFS_CLUSTER_ALLOCATION_ZONES;
extern runlist_element *ntfs_cluster_alloc(ntfs_volume *vol,
const VCN start_vcn, const s64 count, const LCN start_lcn,
const NTFS_CLUSTER_ALLOCATION_ZONES zone,
const bool is_extension);
extern s64 __ntfs_cluster_free(ntfs_inode *ni, const VCN start_vcn,
s64 count, ntfs_attr_search_ctx *ctx, const bool is_rollback);
/**
* ntfs_cluster_free - free clusters on an ntfs volume
* @ni: ntfs inode whose runlist describes the clusters to free
* @start_vcn: vcn in the runlist of @ni at which to start freeing clusters
* @count: number of clusters to free or -1 for all clusters
* @ctx: active attribute search context if present or NULL if not
*
* Free @count clusters starting at the cluster @start_vcn in the runlist
* described by the ntfs inode @ni.
*
* If @count is -1, all clusters from @start_vcn to the end of the runlist are
* deallocated. Thus, to completely free all clusters in a runlist, use
* @start_vcn = 0 and @count = -1.
*
* If @ctx is specified, it is an active search context of @ni and its base mft
* record. This is needed when ntfs_cluster_free() encounters unmapped runlist
* fragments and allows their mapping. If you do not have the mft record
* mapped, you can specify @ctx as NULL and ntfs_cluster_free() will perform
* the necessary mapping and unmapping.
*
* Note, ntfs_cluster_free() saves the state of @ctx on entry and restores it
* before returning. Thus, @ctx will be left pointing to the same attribute on
* return as on entry. However, the actual pointers in @ctx may point to
* different memory locations on return, so you must remember to reset any
* cached pointers from the @ctx, i.e. after the call to ntfs_cluster_free(),
* you will probably want to do:
* m = ctx->mrec;
* a = ctx->attr;
* Assuming you cache ctx->attr in a variable @a of type ATTR_RECORD * and that
* you cache ctx->mrec in a variable @m of type MFT_RECORD *.
*
* Note, ntfs_cluster_free() does not modify the runlist, so you have to remove
* from the runlist or mark sparse the freed runs later.
*
* Return the number of deallocated clusters (not counting sparse ones) on
* success and -errno on error.
*
* WARNING: If @ctx is supplied, regardless of whether success or failure is
* returned, you need to check IS_ERR(@ctx->mrec) and if 'true' the @ctx
* is no longer valid, i.e. you need to either call
* ntfs_attr_reinit_search_ctx() or ntfs_attr_put_search_ctx() on it.
* In that case PTR_ERR(@ctx->mrec) will give you the error code for
* why the mapping of the old inode failed.
*
* Locking: - The runlist described by @ni must be locked for writing on entry
* and is locked on return. Note the runlist may be modified when
* needed runlist fragments need to be mapped.
* - The volume lcn bitmap must be unlocked on entry and is unlocked
* on return.
* - This function takes the volume lcn bitmap lock for writing and
* modifies the bitmap contents.
* - If @ctx is NULL, the base mft record of @ni must not be mapped on
* entry and it will be left unmapped on return.
* - If @ctx is not NULL, the base mft record must be mapped on entry
* and it will be left mapped on return.
*/
static inline s64 ntfs_cluster_free(ntfs_inode *ni, const VCN start_vcn,
s64 count, ntfs_attr_search_ctx *ctx)
{
return __ntfs_cluster_free(ni, start_vcn, count, ctx, false);
}
extern int ntfs_cluster_free_from_rl_nolock(ntfs_volume *vol,
const runlist_element *rl);
/**
* ntfs_cluster_free_from_rl - free clusters from runlist
* @vol: mounted ntfs volume on which to free the clusters
* @rl: runlist describing the clusters to free
*
* Free all the clusters described by the runlist @rl on the volume @vol. In
* the case of an error being returned, at least some of the clusters were not
* freed.
*
* Return 0 on success and -errno on error.
*
* Locking: - This function takes the volume lcn bitmap lock for writing and
* modifies the bitmap contents.
* - The caller must have locked the runlist @rl for reading or
* writing.
*/
static inline int ntfs_cluster_free_from_rl(ntfs_volume *vol,
const runlist_element *rl)
{
int ret;
down_write(&vol->lcnbmp_lock);
ret = ntfs_cluster_free_from_rl_nolock(vol, rl);
up_write(&vol->lcnbmp_lock);
return ret;
}
#endif /* NTFS_RW */
#endif /* defined _LINUX_NTFS_LCNALLOC_H */
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* quota.h - Defines for NTFS kernel quota ($Quota) handling. Part of the
* Linux-NTFS project.
*
* Copyright (c) 2004 Anton Altaparmakov
*/
#ifndef _LINUX_NTFS_QUOTA_H
#define _LINUX_NTFS_QUOTA_H
#ifdef NTFS_RW
#include "types.h"
#include "volume.h"
extern bool ntfs_mark_quotas_out_of_date(ntfs_volume *vol);
#endif /* NTFS_RW */
#endif /* _LINUX_NTFS_QUOTA_H */
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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