mirror of
https://git.savannah.gnu.org/git/coreutils.git
synced 2025-09-10 07:59:52 +02:00
Compare commits
15 Commits
master
...
fiemap-cop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f25181d32c | ||
|
|
be5548445e | ||
|
|
98b2a24d7f | ||
|
|
484903dc41 | ||
|
|
20c1eeec11 | ||
|
|
f9daf7e7ed | ||
|
|
1a2b6d0938 | ||
|
|
e955826e4c | ||
|
|
50975af729 | ||
|
|
912e7d26c6 | ||
|
|
e62e22b798 | ||
|
|
578db289bb | ||
|
|
9cc9dbaf5b | ||
|
|
e3dca50c2a | ||
|
|
1ba1e9ae94 |
2
cfg.mk
2
cfg.mk
@@ -80,7 +80,7 @@ sc_root_tests:
|
||||
@if test -d tests \
|
||||
&& grep check-root tests/Makefile.am>/dev/null 2>&1; then \
|
||||
t1=sc-root.expected; t2=sc-root.actual; \
|
||||
grep -nl '^require_root_$$' \
|
||||
grep -nl '^ *require_root_$$' \
|
||||
$$($(VC_LIST) tests) |sed s,tests/,, |sort > $$t1; \
|
||||
sed -n '/^root_tests =[ ]*\\$$/,/[^\]$$/p' \
|
||||
$(srcdir)/tests/Makefile.am \
|
||||
|
||||
@@ -145,6 +145,7 @@ noinst_HEADERS = \
|
||||
copy.h \
|
||||
cp-hash.h \
|
||||
dircolors.h \
|
||||
fiemap.h \
|
||||
fs.h \
|
||||
group-list.h \
|
||||
ls.h \
|
||||
|
||||
171
src/copy.c
171
src/copy.c
@@ -63,6 +63,10 @@
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#ifndef HAVE_FIEMAP
|
||||
# include "fiemap.h"
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_FCHOWN
|
||||
# define HAVE_FCHOWN false
|
||||
# define fchown(fd, uid, gid) (-1)
|
||||
@@ -149,6 +153,153 @@ clone_file (int dest_fd, int src_fd)
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
# ifndef FS_IOC_FIEMAP
|
||||
# define FS_IOC_FIEMAP _IOWR ('f', 11, struct fiemap)
|
||||
# endif
|
||||
/* Perform a FIEMAP copy, if possible.
|
||||
Call ioctl(2) with FS_IOC_FIEMAP (available in linux 2.6.27) to
|
||||
obtain a map of file extents excluding holes. This avoids the
|
||||
overhead of detecting holes in a hole-introducing/preserving copy,
|
||||
and thus makes copying sparse files much more efficient. Upon a
|
||||
successful copy, return true. If the initial ioctl fails, set
|
||||
*NORMAL_COPY_REQUIRED to true and return false. Upon any other
|
||||
failure, set *NORMAL_COPY_REQUIRED to false and return false. */
|
||||
static bool
|
||||
fiemap_copy (int src_fd, int dest_fd, size_t buf_size,
|
||||
off_t src_total_size, char const *src_name,
|
||||
char const *dst_name, bool *normal_copy_required)
|
||||
{
|
||||
bool last = false;
|
||||
union { struct fiemap f; char c[4096]; } fiemap_buf;
|
||||
struct fiemap *fiemap = &fiemap_buf.f;
|
||||
struct fiemap_extent *fm_ext = &fiemap->fm_extents[0];
|
||||
enum { count = (sizeof fiemap_buf - sizeof *fiemap) / sizeof *fm_ext };
|
||||
verify (count != 0);
|
||||
|
||||
off_t last_ext_logical = 0;
|
||||
uint64_t last_ext_len = 0;
|
||||
uint64_t last_read_size = 0;
|
||||
unsigned int i = 0;
|
||||
*normal_copy_required = false;
|
||||
|
||||
/* This is required at least to initialize fiemap->fm_start,
|
||||
but also serves (in mid 2010) to appease valgrind, which
|
||||
appears not to know the semantics of the FIEMAP ioctl. */
|
||||
memset (&fiemap_buf, 0, sizeof fiemap_buf);
|
||||
|
||||
do
|
||||
{
|
||||
fiemap->fm_length = FIEMAP_MAX_OFFSET;
|
||||
fiemap->fm_flags = FIEMAP_FLAG_SYNC;
|
||||
fiemap->fm_extent_count = count;
|
||||
|
||||
/* When ioctl(2) fails, fall back to the normal copy only if it
|
||||
is the first time we met. */
|
||||
if (ioctl (src_fd, FS_IOC_FIEMAP, fiemap) < 0)
|
||||
{
|
||||
/* If the first ioctl fails, tell the caller that it is
|
||||
ok to proceed with a normal copy. */
|
||||
if (i == 0)
|
||||
*normal_copy_required = true;
|
||||
else
|
||||
{
|
||||
/* If the second or subsequent ioctl fails, diagnose it,
|
||||
since it ends up causing the entire copy/cp to fail. */
|
||||
error (0, errno, _("%s: FIEMAP ioctl failed"), quote (src_name));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If 0 extents are returned, then more ioctls are not needed. */
|
||||
if (fiemap->fm_mapped_extents == 0)
|
||||
break;
|
||||
|
||||
for (i = 0; i < fiemap->fm_mapped_extents; i++)
|
||||
{
|
||||
assert (fm_ext[i].fe_logical <= OFF_T_MAX);
|
||||
|
||||
off_t ext_logical = fm_ext[i].fe_logical;
|
||||
uint64_t ext_len = fm_ext[i].fe_length;
|
||||
|
||||
if (lseek (src_fd, ext_logical, SEEK_SET) < 0)
|
||||
{
|
||||
error (0, errno, _("cannot lseek %s"), quote (src_name));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lseek (dest_fd, ext_logical, SEEK_SET) < 0)
|
||||
{
|
||||
error (0, errno, _("cannot lseek %s"), quote (dst_name));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fm_ext[i].fe_flags & FIEMAP_EXTENT_LAST)
|
||||
{
|
||||
last_ext_logical = ext_logical;
|
||||
last_ext_len = ext_len;
|
||||
last = true;
|
||||
}
|
||||
|
||||
while (ext_len)
|
||||
{
|
||||
char buf[buf_size];
|
||||
|
||||
/* Avoid reading into the holes if the left extent
|
||||
length is shorter than the buffer size. */
|
||||
if (ext_len < buf_size)
|
||||
buf_size = ext_len;
|
||||
|
||||
ssize_t n_read = read (src_fd, buf, buf_size);
|
||||
if (n_read < 0)
|
||||
{
|
||||
#ifdef EINTR
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
#endif
|
||||
error (0, errno, _("reading %s"), quote (src_name));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (n_read == 0)
|
||||
{
|
||||
/* Figure out how many bytes read from the last extent. */
|
||||
last_read_size = last_ext_len - ext_len;
|
||||
break;
|
||||
}
|
||||
|
||||
if (full_write (dest_fd, buf, n_read) != n_read)
|
||||
{
|
||||
error (0, errno, _("writing %s"), quote (dst_name));
|
||||
return false;
|
||||
}
|
||||
|
||||
ext_len -= n_read;
|
||||
}
|
||||
}
|
||||
|
||||
fiemap->fm_start = fm_ext[i - 1].fe_logical + fm_ext[i - 1].fe_length;
|
||||
|
||||
} while (! last);
|
||||
|
||||
/* If a file ends up with holes, the sum of the last extent logical offset
|
||||
and the read-returned size will be shorter than the actual size of the
|
||||
file. Use ftruncate to extend the length of the destination file. */
|
||||
if (last_ext_logical + last_read_size < src_total_size)
|
||||
{
|
||||
if (ftruncate (dest_fd, src_total_size) < 0)
|
||||
{
|
||||
error (0, errno, _("failed to extend %s"), quote (dst_name));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
static bool fiemap_copy (ignored) { errno == ENOTSUP; return false; }
|
||||
#endif
|
||||
|
||||
/* FIXME: describe */
|
||||
/* FIXME: rewrite this to use a hash table so we avoid the quadratic
|
||||
performance hit that's probably noticeable only on trees deeper
|
||||
@@ -679,6 +830,25 @@ copy_reg (char const *src_name, char const *dst_name,
|
||||
#endif
|
||||
}
|
||||
|
||||
if (make_holes)
|
||||
{
|
||||
bool require_normal_copy;
|
||||
/* Perform efficient FIEMAP copy for sparse files, fall back to the
|
||||
standard copy only if the ioctl(2) fails. */
|
||||
if (fiemap_copy (source_desc, dest_desc, buf_size,
|
||||
src_open_sb.st_size, src_name,
|
||||
dst_name, &require_normal_copy))
|
||||
goto preserve_metadata;
|
||||
else
|
||||
{
|
||||
if (! require_normal_copy)
|
||||
{
|
||||
return_val = false;
|
||||
goto close_src_and_dst_desc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If not making a sparse file, try to use a more-efficient
|
||||
buffer size. */
|
||||
if (! make_holes)
|
||||
@@ -807,6 +977,7 @@ copy_reg (char const *src_name, char const *dst_name,
|
||||
}
|
||||
}
|
||||
|
||||
preserve_metadata:
|
||||
if (x->preserve_timestamps)
|
||||
{
|
||||
struct timespec timespec[2];
|
||||
|
||||
102
src/fiemap.h
Normal file
102
src/fiemap.h
Normal file
@@ -0,0 +1,102 @@
|
||||
/* FS_IOC_FIEMAP ioctl infrastructure.
|
||||
Some portions copyright (C) 2007 Cluster File Systems, Inc
|
||||
Authors: Mark Fasheh <mfasheh@suse.com>
|
||||
Kalpak Shah <kalpak.shah@sun.com>
|
||||
Andreas Dilger <adilger@sun.com>. */
|
||||
|
||||
/* Copy from kernel, modified to respect GNU code style by Jie Liu. */
|
||||
|
||||
#ifndef _LINUX_FIEMAP_H
|
||||
# define _LINUX_FIEMAP_H
|
||||
|
||||
# include <stdint.h>
|
||||
|
||||
struct fiemap_extent
|
||||
{
|
||||
/* Logical offset in bytes for the start of the extent
|
||||
from the beginning of the file. */
|
||||
uint64_t fe_logical;
|
||||
|
||||
/* Physical offset in bytes for the start of the extent
|
||||
from the beginning of the disk. */
|
||||
uint64_t fe_physical;
|
||||
|
||||
/* Length in bytes for this extent. */
|
||||
uint64_t fe_length;
|
||||
|
||||
uint64_t fe_reserved64[2];
|
||||
|
||||
/* FIEMAP_EXTENT_* flags for this extent. */
|
||||
uint32_t fe_flags;
|
||||
|
||||
uint32_t fe_reserved[3];
|
||||
};
|
||||
|
||||
struct fiemap
|
||||
{
|
||||
/* Logical offset(inclusive) at which to start mapping(in). */
|
||||
uint64_t fm_start;
|
||||
|
||||
/* Logical length of mapping which userspace wants(in). */
|
||||
uint64_t fm_length;
|
||||
|
||||
/* FIEMAP_FLAG_* flags for request(in/out). */
|
||||
uint32_t fm_flags;
|
||||
|
||||
/* Number of extents that were mapped(out). */
|
||||
uint32_t fm_mapped_extents;
|
||||
|
||||
/* Size of fm_extents array(in). */
|
||||
uint32_t fm_extent_count;
|
||||
|
||||
uint32_t fm_reserved;
|
||||
|
||||
/* Array of mapped extents(out). */
|
||||
struct fiemap_extent fm_extents[0];
|
||||
};
|
||||
|
||||
/* The maximum offset can be mapped for a file. */
|
||||
# define FIEMAP_MAX_OFFSET (~0ULL)
|
||||
|
||||
/* Sync file data before map. */
|
||||
# define FIEMAP_FLAG_SYNC 0x00000001
|
||||
|
||||
/* Map extented attribute tree. */
|
||||
# define FIEMAP_FLAG_XATTR 0x00000002
|
||||
|
||||
# define FIEMAP_FLAGS_COMPAT (FIEMAP_FLAG_SYNC | FIEMAP_FLAG_XATTR)
|
||||
|
||||
/* Last extent in file. */
|
||||
# define FIEMAP_EXTENT_LAST 0x00000001
|
||||
|
||||
/* Data location unknown. */
|
||||
# define FIEMAP_EXTENT_UNKNOWN 0x00000002
|
||||
|
||||
/* Location still pending, Sets EXTENT_UNKNOWN. */
|
||||
# define FIEMAP_EXTENT_DELALLOC 0x00000004
|
||||
|
||||
/* Data can not be read while fs is unmounted. */
|
||||
# define FIEMAP_EXTENT_ENCODED 0x00000008
|
||||
|
||||
/* Data is encrypted by fs. Sets EXTENT_NO_BYPASS. */
|
||||
# define FIEMAP_EXTENT_DATA_ENCRYPTED 0x00000080
|
||||
|
||||
/* Extent offsets may not be block aligned. */
|
||||
# define FIEMAP_EXTENT_NOT_ALIGNED 0x00000100
|
||||
|
||||
/* Data mixed with metadata. Sets EXTENT_NOT_ALIGNED. */
|
||||
# define FIEMAP_EXTENT_DATA_INLINE 0x00000200
|
||||
|
||||
/* Multiple files in block. Set EXTENT_NOT_ALIGNED. */
|
||||
# define FIEMAP_EXTENT_DATA_TAIL 0x00000400
|
||||
|
||||
/* Space allocated, but not data (i.e. zero). */
|
||||
# define FIEMAP_EXTENT_UNWRITTEN 0x00000800
|
||||
|
||||
/* File does not natively support extents. Result merged for efficiency. */
|
||||
# define FIEMAP_EXTENT_MERGED 0x00001000
|
||||
|
||||
/* Space shared with other files. */
|
||||
# define FIEMAP_EXTENT_SHARED 0x00002000
|
||||
|
||||
#endif
|
||||
@@ -10,6 +10,7 @@ EXTRA_DIST = \
|
||||
CuTmpdir.pm \
|
||||
check.mk \
|
||||
envvar-check \
|
||||
filefrag-extent-compare \
|
||||
init.sh \
|
||||
lang-default \
|
||||
other-fs-tmpdir \
|
||||
@@ -25,6 +26,7 @@ root_tests = \
|
||||
cp/special-bits \
|
||||
cp/cp-mv-enotsup-xattr \
|
||||
cp/capability \
|
||||
cp/sparse-fiemap \
|
||||
dd/skip-seek-past-dev \
|
||||
install/install-C-root \
|
||||
ls/capability \
|
||||
|
||||
119
tests/cp/sparse-fiemap
Executable file
119
tests/cp/sparse-fiemap
Executable file
@@ -0,0 +1,119 @@
|
||||
#!/bin/sh
|
||||
# Test cp --sparse=always through fiemap copy
|
||||
|
||||
# Copyright (C) 2010 Free Software Foundation, Inc.
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
if test "$VERBOSE" = yes; then
|
||||
set -x
|
||||
cp --version
|
||||
fi
|
||||
|
||||
. "${srcdir=.}/init.sh"; path_prepend_ ../src
|
||||
|
||||
if df -T -t btrfs -t xfs -t ext4 -t ocfs2 . ; then
|
||||
: # Current dir is on a partition with working extents. Good!
|
||||
else
|
||||
# It's not; we need to create one, hence we need root access.
|
||||
require_root_
|
||||
|
||||
cwd=$PWD
|
||||
cleanup_() { cd /; umount "$cwd/mnt"; }
|
||||
|
||||
skip=0
|
||||
# Create an ext4 loopback file system
|
||||
dd if=/dev/zero of=blob bs=32k count=1000 || skip=1
|
||||
mkdir mnt
|
||||
mkfs -t ext4 -F blob ||
|
||||
skip_test_ "failed to create ext4 file system"
|
||||
mount -oloop blob mnt || skip=1
|
||||
cd mnt || skip=1
|
||||
echo test > f || skip=1
|
||||
test -s f || skip=1
|
||||
|
||||
test $skip = 1 &&
|
||||
skip_test_ "insufficient mount/ext4 support"
|
||||
fi
|
||||
|
||||
# Create a 1TiB sparse file
|
||||
dd if=/dev/zero of=sparse bs=1k count=1 seek=1G || framework_failure
|
||||
|
||||
# It takes many minutes to copy this sparse file using the old method.
|
||||
# By contrast, it takes far less than 1 second using FIEMAP-copy.
|
||||
timeout 10 cp --sparse=always sparse fiemap || fail=1
|
||||
|
||||
# Ensure that the sparse file copied through fiemap has the same size
|
||||
# in bytes as the original.
|
||||
test $(stat --printf %s sparse) = $(stat --printf %s fiemap) || fail=1
|
||||
|
||||
# =================================================
|
||||
# Ensure that we exercise the FIEMAP-copying code enough
|
||||
# to provoke at least two iterations of the do...while loop
|
||||
# in which it calls ioctl (fd, FS_IOC_FIEMAP,...
|
||||
# This also verifies that non-trivial extents are preserved.
|
||||
|
||||
$PERL -e 1 || skip_test_ 'skipping part of this test; you lack perl'
|
||||
|
||||
# Extract logical block number and length pairs from filefrag -v output.
|
||||
# The initial sed is to remove the "eof" from the normally-empty "flags" field.
|
||||
# Similarly, remove flags values like "unknown,delalloc,eof".
|
||||
# That is required when that final extent has no number in the "expected" field.
|
||||
f()
|
||||
{
|
||||
sed 's/ [a-z,][a-z,]*$//' $@ \
|
||||
| awk '/^ *[0-9]/ {printf "%d %d ", $2 ,NF < 5 ? $NF : $5 } END {print ""}'
|
||||
}
|
||||
|
||||
for i in $(seq 1 2 21); do
|
||||
for j in 1 2 31 100; do
|
||||
$PERL -e 'BEGIN { $n = '$i' * 1024; *F = *STDOUT }' \
|
||||
-e 'for (1..'$j') { sysseek (*F, $n, 1)' \
|
||||
-e '&& syswrite (*F, chr($_)x$n) or die "$!"}' > j1 || fail=1
|
||||
# sync
|
||||
cp --sparse=always j1 j2 || fail=1
|
||||
# sync
|
||||
# Technically we may need the 'sync' uses above, but
|
||||
# uncommenting them makes this test take much longer.
|
||||
|
||||
cmp j1 j2 || fail=1
|
||||
filefrag -v j1 | grep extent \
|
||||
|| skip_test_ 'skipping part of this test; you lack filefrag'
|
||||
|
||||
# Here is sample filefrag output:
|
||||
# $ perl -e 'BEGIN{$n=16*1024; *F=*STDOUT}' \
|
||||
# -e 'for (1..5) { sysseek(*F,$n,1)' \
|
||||
# -e '&& syswrite *F,"."x$n or die "$!"}' > j
|
||||
# $ filefrag -v j
|
||||
# File system type is: ef53
|
||||
# File size of j is 163840 (40 blocks, blocksize 4096)
|
||||
# ext logical physical expected length flags
|
||||
# 0 4 6258884 4
|
||||
# 1 12 6258892 6258887 4
|
||||
# 2 20 6258900 6258895 4
|
||||
# 3 28 6258908 6258903 4
|
||||
# 4 36 6258916 6258911 4 eof
|
||||
# j: 6 extents found
|
||||
|
||||
# exclude the physical block numbers; they always differ
|
||||
filefrag -v j1 > ff1 || fail=1
|
||||
filefrag -v j2 > ff2 || fail=1
|
||||
{ f ff1; f ff2; } \
|
||||
| $PERL $abs_top_srcdir/tests/filefrag-extent-compare \
|
||||
|| { fail=1; break; }
|
||||
done
|
||||
test $fail = 1 && break
|
||||
done
|
||||
|
||||
Exit $fail
|
||||
68
tests/filefrag-extent-compare
Normal file
68
tests/filefrag-extent-compare
Normal file
@@ -0,0 +1,68 @@
|
||||
eval '(exit $?0)' && eval 'exec perl -wS "$0" ${1+"$@"}'
|
||||
& eval 'exec perl -wS "$0" $argv:q'
|
||||
if 0;
|
||||
# Determine whether two files have the same extents by comparing
|
||||
# the logical block numbers and lengths from filefrag -v for each.
|
||||
|
||||
# Invoke like this:
|
||||
# This helper function, f, extracts logical block number and lengths.
|
||||
# f() { awk '/^ *[0-9]/ {printf "%d %d ",$2,NF<5?$NF:$5} END {print ""}'; }
|
||||
# { filefrag -v j1 | f; filefrag -v j2 | f; } | ./filefrag-extent-compare
|
||||
|
||||
use warnings;
|
||||
use strict;
|
||||
(my $ME = $0) =~ s|.*/||;
|
||||
|
||||
my @line = <>;
|
||||
my $n_lines = @line;
|
||||
$n_lines == 2
|
||||
or die "$ME: expected exactly two input lines; got $n_lines\n";
|
||||
|
||||
my @A = split ' ', $line[0];
|
||||
my @B = split ' ', $line[1];
|
||||
@A % 2 || @B % 2
|
||||
and die "$ME: unexpected input: odd number of numbers; expected even\n";
|
||||
|
||||
my @a;
|
||||
my @b;
|
||||
foreach my $i (0..@A/2-1) { $a[$i] = { L_BLK => $A[2*$i], LEN => $A[2*$i+1] } };
|
||||
foreach my $i (0..@B/2-1) { $b[$i] = { L_BLK => $B[2*$i], LEN => $B[2*$i+1] } };
|
||||
|
||||
my $i = 0;
|
||||
my $j = 0;
|
||||
while (1)
|
||||
{
|
||||
!defined $a[$i] && !defined $b[$j]
|
||||
and exit 0;
|
||||
defined $a[$i] && defined $b[$j]
|
||||
or die "\@a and \@b have different lengths, even after adjustment\n";
|
||||
($a[$i]->{L_BLK} == $b[$j]->{L_BLK}
|
||||
&& $a[$i]->{LEN} == $b[$j]->{LEN})
|
||||
and next;
|
||||
($a[$i]->{LEN} < $b[$j]->{LEN}
|
||||
&& exists $a[$i+1] && $a[$i]->{LEN} + $a[$i+1]->{LEN} == $b[$j]->{LEN})
|
||||
and ++$i, next;
|
||||
exists $b[$j+1] && $a[$i]->{LEN} == $b[$i]->{LEN} + $b[$i+1]->{LEN}
|
||||
and ++$j, next;
|
||||
die "differing extent:\n"
|
||||
. " [$i]=$a[$i]->{L_BLK} $a[$i]->{LEN}\n"
|
||||
. " [$j]=$b[$j]->{L_BLK} $b[$j]->{LEN}\n"
|
||||
}
|
||||
continue
|
||||
{
|
||||
++$i;
|
||||
++$j;
|
||||
}
|
||||
|
||||
### Setup "GNU" style for perl-mode and cperl-mode.
|
||||
## Local Variables:
|
||||
## mode: perl
|
||||
## perl-indent-level: 2
|
||||
## perl-continued-statement-offset: 2
|
||||
## perl-continued-brace-offset: 0
|
||||
## perl-brace-offset: 0
|
||||
## perl-brace-imaginary-offset: 0
|
||||
## perl-label-offset: -2
|
||||
## perl-extra-newline-before-brace: t
|
||||
## perl-merge-trailing-else: nil
|
||||
## End:
|
||||
Reference in New Issue
Block a user