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

Reply via email to