Hi,
Is there a reason that 'install --reflink' does not exist? I was curious
myself, and saw someone asked the same thing a few years ago [1]. As far
as I can tell they didn't get a response, but the lists.gnu.org
interface isn't great at tracking things across months so I could have
missed it.
I can't immediately think of why it shouldn't be supported. Both cp and
install have --debug, though I suppose that is more useful for
--reflink=auto and copying items which may go to a different file
system.
Anyways, I attached a example patch since it isn't that difficult to
share the code. Here is the output with --debug:
$ ./src/ginstall --debug a ../
'a' -> '../a'
copy offload: unknown, reflink: yes, sparse detection: unknown
$ ./src/ginstall --debug a /tmp
'a' -> '/tmp/a'
copy offload: unsupported, reflink: unsupported, sparse detection: no
$ ./src/ginstall --debug --reflink=never a ../
removed '../a'
'a' -> '../a'
copy offload: avoided, reflink: no, sparse detection: no
$ ./src/ginstall --debug --reflink=always a /tmp
removed '/tmp/a'
'a' -> '/tmp/a'
ginstall: failed to clone '/tmp/a' from 'a': Invalid cross-device link
copy offload: unknown, reflink: unsupported, sparse detection: unknown
Missing documentation and tests, of course.
Collin
[1] https://lists.gnu.org/archive/html/coreutils/2019-11/msg00002.html
>From b99529b9b38b3f382e8d4b3844365b7e81de1c1d Mon Sep 17 00:00:00 2001
Message-ID: <b99529b9b38b3f382e8d4b3844365b7e81de1c1d.1770267426.git.collin.fu...@gmail.com>
From: Collin Funk <[email protected]>
Date: Wed, 4 Feb 2026 20:37:41 -0800
Subject: [PATCH] install: add the --reflink option
* src/reflink-option.h: New file, based on src/cp.c.
* src/local.mk (noinst_HEADERS): Add the new file.
* src/cp.c: Include reflink-option.h.
(reflink_type, reflink_type_string): Moved to src/reflink-option.h.
(long_opts, usage, main): Use the definitions from src/reflink-option.h.
* src/install.c: Include reflink-option.h.
(REFLINK_OPTION, long_options, usage, main): Add the --reflink option.
---
src/cp.c | 34 ++++--------------------
src/install.c | 6 +++++
src/local.mk | 1 +
src/reflink-option.h | 63 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 75 insertions(+), 29 deletions(-)
create mode 100644 src/reflink-option.h
diff --git a/src/cp.c b/src/cp.c
index e17484b5d..80e386862 100644
--- a/src/cp.c
+++ b/src/cp.c
@@ -32,6 +32,7 @@
#include "ignore-value.h"
#include "quote.h"
#include "stat-time.h"
+#include "reflink-option.h"
#include "targetdir.h"
#include "utimens.h"
#include "acl.h"
@@ -92,16 +93,6 @@ static enum Sparse_type const sparse_type[] =
};
ARGMATCH_VERIFY (sparse_type_string, sparse_type);
-static char const *const reflink_type_string[] =
-{
- "auto", "always", "never", NULL
-};
-static enum Reflink_type const reflink_type[] =
-{
- REFLINK_AUTO, REFLINK_ALWAYS, REFLINK_NEVER
-};
-ARGMATCH_VERIFY (reflink_type_string, reflink_type);
-
static char const *const update_type_string[] =
{
"all", "none", "none-fail", "older", NULL
@@ -134,7 +125,7 @@ static struct option const long_opts[] =
{"recursive", no_argument, NULL, 'R'},
{"remove-destination", no_argument, NULL, UNLINK_DEST_BEFORE_OPENING},
{"sparse", required_argument, NULL, SPARSE_OPTION},
- {"reflink", optional_argument, NULL, REFLINK_OPTION},
+ {GETOPT_REFLINK_OPTION_DECL},
{"strip-trailing-slashes", no_argument, NULL,
STRIP_TRAILING_SLASHES_OPTION},
{"suffix", required_argument, NULL, 'S'},
@@ -250,10 +241,7 @@ Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.\n\
-R, -r, --recursive\n\
copy directories recursively\n\
"));
- oputs (_("\
- --reflink[=WHEN]\n\
- control clone/CoW copies. See below\n\
-"));
+ oputs (REFLINK_OPTION_DESCRIPTION);
oputs (_("\
--remove-destination\n\
remove each existing destination file before attempting to open it\n\
@@ -328,13 +316,7 @@ file whenever the SOURCE file contains a long enough sequence of zero bytes.\n\
Use --sparse=never to inhibit creation of sparse files.\n\
"), stdout);
emit_update_parameters_note ();
- fputs (_("\
-\n\
-By default or with --reflink=auto, cp will try a lightweight copy, where the\n\
-data blocks are copied only when modified, falling back to a standard copy\n\
-if this is not possible. With --reflink[=always] cp will fail if CoW is not\n\
-supported, while --reflink=never ensures a standard copy is performed.\n\
-"), stdout);
+ emit_reflink_note (PROGRAM_NAME);
emit_backup_suffix_note ();
fputs (_("\
\n\
@@ -1060,13 +1042,7 @@ main (int argc, char **argv)
sparse_type_string, sparse_type);
break;
- case REFLINK_OPTION:
- if (optarg == NULL)
- x.reflink_mode = REFLINK_ALWAYS;
- else
- x.reflink_mode = XARGMATCH ("--reflink", optarg,
- reflink_type_string, reflink_type);
- break;
+ case_GETOPT_REFLINK_OPTION;
case 'a':
/* Like -dR --preserve=all with reduced failure diagnostics. */
diff --git a/src/install.c b/src/install.c
index 359eb657e..f064c8738 100644
--- a/src/install.c
+++ b/src/install.c
@@ -38,6 +38,7 @@
#include "modechange.h"
#include "prog-fprintf.h"
#include "quote.h"
+#include "reflink-option.h"
#include "savewd.h"
#include "selinux.h"
#include "stat-time.h"
@@ -108,6 +109,7 @@ enum
{
DEBUG_OPTION = CHAR_MAX + 1,
PRESERVE_CONTEXT_OPTION,
+ REFLINK_OPTION,
STRIP_PROGRAM_OPTION
};
@@ -124,6 +126,7 @@ static struct option const long_options[] =
{"owner", required_argument, NULL, 'o'},
{"preserve-timestamps", no_argument, NULL, 'p'},
{"preserve-context", no_argument, NULL, PRESERVE_CONTEXT_OPTION},
+ {GETOPT_REFLINK_OPTION_DECL},
{"strip", no_argument, NULL, 's'},
{"strip-program", required_argument, NULL, STRIP_PROGRAM_OPTION},
{"suffix", required_argument, NULL, 'S'},
@@ -659,6 +662,7 @@ In the 4th form, create all components of the given DIRECTORY(ies).\n\
apply access/modification times of SOURCE files\n\
to corresponding destination files\n\
"));
+ oputs (REFLINK_OPTION_DESCRIPTION);
oputs (_("\
-s, --strip\n\
strip symbol tables\n\
@@ -700,6 +704,7 @@ In the 4th form, create all components of the given DIRECTORY(ies).\n\
oputs (HELP_OPTION_DESCRIPTION);
oputs (VERSION_OPTION_DESCRIPTION);
+ emit_reflink_note (PROGRAM_NAME);
emit_backup_suffix_note ();
emit_ancillary_info (PROGRAM_NAME);
}
@@ -887,6 +892,7 @@ main (int argc, char **argv)
signal (SIGCHLD, SIG_DFL);
#endif
break;
+ case_GETOPT_REFLINK_OPTION;
case DEBUG_OPTION:
x.debug = x.verbose = true;
break;
diff --git a/src/local.mk b/src/local.mk
index 29f07f254..c0401db2d 100644
--- a/src/local.mk
+++ b/src/local.mk
@@ -58,6 +58,7 @@ noinst_HEADERS = \
src/octhexdigits.h \
src/operand2sig.h \
src/prog-fprintf.h \
+ src/reflink-option.h \
src/remove.h \
src/set-fields.h \
src/show-date.h \
diff --git a/src/reflink-option.h b/src/reflink-option.h
new file mode 100644
index 000000000..9dc4a886c
--- /dev/null
+++ b/src/reflink-option.h
@@ -0,0 +1,63 @@
+/* Common definitions for handling the --reflink option.
+ Copyright (C) 1989-2026 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 <https://www.gnu.org/licenses/>. */
+
+#ifndef REFLINK_OPTION_H
+# define REFLINK_OPTION_H
+
+# include "argmatch.h"
+
+static char const *const reflink_type_string[] =
+{
+ "auto", "always", "never", NULL
+};
+static enum Reflink_type const reflink_type[] =
+{
+ REFLINK_AUTO, REFLINK_ALWAYS, REFLINK_NEVER
+};
+ARGMATCH_VERIFY (reflink_type_string, reflink_type);
+
+# define REFLINK_OPTION_DESCRIPTION \
+ _("\
+ --reflink[=WHEN]\n\
+ control clone/CoW copies. See below\n\
+")
+
+static inline void
+emit_reflink_note (char const *program)
+{
+ printf (_("\
+\n\
+By default or with --reflink=auto, %s will try a lightweight copy, where\n\
+the data blocks are copied only when modified, falling back to a standard copy\
+\n\
+if this is not possible. With --reflink[=always] %s will fail if CoW is\n\
+not supported, while --reflink=never ensures a standard copy is performed.\n\
+"), program, program);
+}
+
+#define GETOPT_REFLINK_OPTION_DECL \
+ "reflink", optional_argument, NULL, REFLINK_OPTION
+
+#define case_GETOPT_REFLINK_OPTION \
+ case REFLINK_OPTION: \
+ if (optarg == NULL) \
+ x.reflink_mode = REFLINK_ALWAYS; \
+ else \
+ x.reflink_mode = XARGMATCH ("--reflink", optarg, \
+ reflink_type_string, reflink_type); \
+ break;
+
+#endif
--
2.52.0