unison

Fork of Unison, a bi-directional file synchronization tool
git clone git://git.laack.co/unison.git
Log | Files | Refs | README | LICENSE

copy_stubs.c (12912B)


      1 /* Unison file synchronizer: src/copy_stubs.c */
      2 /* Copyright 2021-2025, Tõivo Leedjärv
      3 
      4     This program is free software: you can redistribute it and/or modify
      5     it under the terms of the GNU General Public License as published by
      6     the Free Software Foundation, either version 3 of the License, or
      7     (at your option) any later version.
      8 
      9     This program is distributed in the hope that it will be useful,
     10     but WITHOUT ANY WARRANTY; without even the implied warranty of
     11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12     GNU General Public License for more details.
     13 
     14     You should have received a copy of the GNU General Public License
     15     along with this program.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17 
     18 #include <caml/memory.h>
     19 #include <caml/threads.h>
     20 #include <caml/unixsupport.h>
     21 #include <caml/version.h>
     22 
     23 #if OCAML_VERSION_MAJOR < 5
     24 #define caml_unix_error unix_error
     25 #define caml_uerror uerror
     26 #define caml_win32_maperr win32_maperr
     27 #endif
     28 
     29 
     30 #include <errno.h>
     31 
     32 
     33 /* ----------------------------------------------- */
     34 /* Clone a file given source and destination paths */
     35 /* It must fully complete or fully fail.
     36 
     37    The function must not raise any exceptions.
     38 
     39    Return true for success and false for failure
     40    or if the operation is not supported.           */
     41 
     42 #if defined(__APPLE__)
     43 
     44 
     45 #include <AvailabilityMacros.h>
     46 
     47 #if defined(MAC_OS_X_VERSION_10_12)
     48 #include <string.h>
     49 #include <sys/attr.h>
     50 #include <sys/clonefile.h>
     51 
     52 CAMLprim value unison_clone_path(value src, value dst)
     53 {
     54   CAMLparam2(src, dst);
     55   char *srcn, *dstn;
     56   int status;
     57 
     58   srcn = strdup(String_val(src));
     59   dstn = strdup(String_val(dst));
     60   caml_release_runtime_system();
     61   status = clonefile(srcn, dstn, CLONE_NOFOLLOW | CLONE_NOOWNERCOPY);
     62   free(srcn);
     63   free(dstn);
     64   caml_acquire_runtime_system();
     65 
     66   /* Don't raise an exception, just return false in case of errors */
     67   CAMLreturn(Val_bool(status == 0));
     68 }
     69 #else /* MAC_OS_X_VERSION_10_12 */
     70 CAMLprim value unison_clone_path(value src, value dst)
     71 {
     72   CAMLparam2(src, dst);
     73   CAMLreturn(Val_false);
     74 }
     75 #endif /* MAC_OS_X_VERSION_10_12 */
     76 
     77 
     78 #else /* defined(__APPLE__) */
     79 
     80 
     81 CAMLprim value unison_clone_path(value src, value dst)
     82 {
     83   CAMLparam2(src, dst);
     84   CAMLreturn(Val_false);
     85 }
     86 
     87 /* Regarding Windows API: The CopyFile function in Windows API can do file
     88  * clones under right conditions. Nevertheless, we can't use that function
     89  * since it is not possible to explicitly request cloning (it is possible to
     90  * check whether block cloning is supported (see [unison_copy_file]) but it is
     91  * not a guarantee that the CopyFile function will actually result in a
     92  * cloning operation) and the function will fall back to a normal copy, which
     93  * we do not want in this case (even though performance-wise it would be more
     94  * like sendfile). */
     95 
     96 #endif /* defined(__APPLE__) */
     97 
     98 
     99 /* ----------------------------------------------- */
    100 /* Clone a file given input and output fd          */
    101 /* It must fully complete or fully fail.
    102 
    103    The function must not raise any exceptions.
    104 
    105    Return true for success and false for failure
    106    or if the operation is not supported.           */
    107 
    108 #if defined(__linux__) || defined(__linux)
    109 
    110 
    111 #include <sys/ioctl.h>
    112 
    113 #if !defined(FICLONE) && defined(_IOW)
    114 #define FICLONE _IOW(0x94, 9, int)
    115 #endif
    116 
    117 CAMLprim value unison_clone_file(value in_fd, value out_fd)
    118 {
    119   CAMLparam2(in_fd, out_fd);
    120 
    121 #ifdef FICLONE
    122   caml_release_runtime_system();
    123   int status = ioctl(Int_val(out_fd), FICLONE, Int_val(in_fd));
    124   caml_acquire_runtime_system();
    125 
    126   /* Don't raise an exception, just return false in case of errors */
    127   CAMLreturn(Val_bool(status == 0));
    128 #else /* defined(FICLONE) */
    129   CAMLreturn(Val_false);
    130 #endif
    131 }
    132 
    133 
    134 #else /* defined(__linux__) */
    135 
    136 
    137 CAMLprim value unison_clone_file(value in_fd, value out_fd)
    138 {
    139   CAMLparam2(in_fd, out_fd);
    140   CAMLreturn(Val_false);
    141 }
    142 
    143 
    144 #endif /* defined(__linux__) */
    145 
    146 
    147 /* --------------------------------------------------------- */
    148 /* Copy, or possibly clone, a file given input and output fd */
    149 /* If operation is not supported by the OS or the filesystem
    150    then file offsets must not have been changed at failure.
    151    Output file offset must be changed on success.
    152 
    153    The function must return the number of bytes copied.
    154 
    155    On any error, raise a Unix exception based on errno.
    156    Raise ENOSYS if the operation is not supported.           */
    157 
    158 #if defined(__linux__) || defined(__linux)
    159 
    160 
    161 #include <unistd.h>
    162 #include <sys/syscall.h>
    163 #include <sys/sendfile.h>
    164 
    165 CAMLprim value unison_copy_file(value in_fd, value out_fd, value in_offs, value len)
    166 {
    167   CAMLparam4(in_fd, out_fd, in_offs, len);
    168   off_t off_i = Int64_val(in_offs);
    169   ssize_t ret;
    170 
    171   caml_release_runtime_system();
    172 #ifdef __NR_copy_file_range
    173   /* First, try copy_file_range() */
    174   /* Using off_i prevents changing in_fd file offset */
    175   ret = syscall(__NR_copy_file_range, Int_val(in_fd), &off_i, Int_val(out_fd), NULL, Long_val(len), 0);
    176   if (ret == -1 && (errno == ENOSYS || errno == EBADF || errno == EXDEV))
    177 #endif /* defined(__NR_copy_file_range) */
    178   {
    179     /* Second, try sendfile(); this one changes out_fd file offset */
    180     ret = sendfile(Int_val(out_fd), Int_val(in_fd), &off_i, Long_val(len));
    181   }
    182   caml_acquire_runtime_system();
    183   if (ret == -1) caml_uerror("copy_file", Nothing);
    184 
    185   CAMLreturn(Val_long(ret));
    186 }
    187 
    188 
    189 #elif defined(__FreeBSD__)
    190 
    191 #include <sys/param.h>
    192 #include <sys/types.h>
    193 #include <unistd.h>
    194 
    195 CAMLprim value unison_copy_file(value in_fd, value out_fd, value in_offs, value len)
    196 {
    197   CAMLparam4(in_fd, out_fd, in_offs, len);
    198 #if __FreeBSD_version >= 1300037
    199   off_t off_i = Int64_val(in_offs);
    200   ssize_t ret;
    201 
    202   caml_release_runtime_system();
    203   /* Using off_i prevents changing in_fd file offset */
    204   ret = copy_file_range(Int_val(in_fd), &off_i, Int_val(out_fd), NULL, Long_val(len), 0);
    205   caml_acquire_runtime_system();
    206   if (ret == -1) caml_uerror("copy_file", Nothing);
    207 
    208   CAMLreturn(Val_long(ret));
    209 #else /* __FreeBSD_version >= 1300037 */
    210   caml_unix_error(ENOSYS, "copy_file", Nothing);
    211   CAMLreturn(Val_long(0));
    212 #endif /* __FreeBSD_version >= 1300037 */
    213 }
    214 
    215 
    216 #elif defined(__sun) || defined(sun)
    217 
    218 
    219 #include <sys/sendfile.h>
    220 
    221 CAMLprim value unison_copy_file(value in_fd, value out_fd, value in_offs, value len)
    222 {
    223   CAMLparam4(in_fd, out_fd, in_offs, len);
    224   off_t off_orig;
    225   off_t off = off_orig = Int64_val(in_offs);
    226   ssize_t ret;
    227 
    228   caml_release_runtime_system();
    229   /* This one changes out_fd file offset */
    230   ret = sendfile(Int_val(out_fd), Int_val(in_fd), &off, Long_val(len));
    231   caml_acquire_runtime_system();
    232   if (ret == -1) {
    233     if (off > off_orig) {
    234       ret = off - off_orig;
    235     } else {
    236       caml_uerror("copy_file", Nothing);
    237     }
    238   }
    239 
    240   CAMLreturn(Val_long(ret));
    241 }
    242 
    243 
    244 #elif defined(_WIN32)
    245 
    246 
    247 #ifndef UNICODE
    248 #define UNICODE
    249 #endif
    250 #ifndef _UNICODE
    251 #define _UNICODE
    252 #endif
    253 
    254 #include <windows.h>
    255 #include <winioctl.h>
    256 
    257 #if defined(__MINGW32__)
    258 
    259 /* FIXME: As of January 2025, mingw has does not include type definitions for
    260  * FSCTL_GET_INTEGRITY_INFORMATION_BUFFER and DUPLICATE_EXTENTS_DATA (but does
    261  * include the definitions of FSCTL constants). If you get a compilation error
    262  * about "type redefinition" or similar, just remove the typedefs below. */
    263 
    264 #ifndef FILE_SUPPORTS_BLOCK_REFCOUNTING
    265 #define FILE_SUPPORTS_BLOCK_REFCOUNTING 0x08000000
    266 #endif
    267 
    268 #ifndef FSCTL_GET_INTEGRITY_INFORMATION
    269 #define FSCTL_GET_INTEGRITY_INFORMATION 0x9027c
    270 #endif
    271 
    272 typedef struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER {
    273   WORD  ChecksumAlgorithm;
    274   WORD  Reserved;
    275   DWORD Flags;
    276   DWORD ChecksumChunkSizeInBytes;
    277   DWORD ClusterSizeInBytes;
    278 } FSCTL_GET_INTEGRITY_INFORMATION_BUFFER;
    279 
    280 #ifndef FSCTL_DUPLICATE_EXTENTS_TO_FILE
    281 #define FSCTL_DUPLICATE_EXTENTS_TO_FILE 0x98344
    282 #endif
    283 
    284 #if !defined(__MINGW64_VERSION_MAJOR) || __MINGW64_VERSION_MAJOR < 13
    285 
    286 /* This typedef is included in mingw since version 13.0.0 */
    287 typedef struct _DUPLICATE_EXTENTS_DATA {
    288   HANDLE        FileHandle;
    289   LARGE_INTEGER SourceFileOffset;
    290   LARGE_INTEGER TargetFileOffset;
    291   LARGE_INTEGER ByteCount;
    292 } DUPLICATE_EXTENTS_DATA;
    293 
    294 #endif /* __MINGW64_VERSION_MAJOR < 13 */
    295 
    296 #endif /* defined(__MINGW32__) */
    297 
    298 void unsn_copy_file_error(void)
    299 {
    300   caml_win32_maperr(GetLastError());
    301   caml_uerror("copy_file", Nothing);
    302 }
    303 
    304 BOOL unsn_copy_file_supports_cloning(HANDLE h)
    305 {
    306   DWORD flags = 0;
    307   return
    308     (GetVolumeInformationByHandleW(h, NULL, 0, NULL, NULL, &flags, NULL, 0)
    309       && (flags & FILE_SUPPORTS_BLOCK_REFCOUNTING));
    310  }
    311 
    312 void unsn_copy_file_set_sparse(HANDLE h, BOOL sparse)
    313 {
    314   FILE_SET_SPARSE_BUFFER sp;
    315   sp.SetSparse = sparse;
    316   if (!DeviceIoControl(h, FSCTL_SET_SPARSE, &sp, sizeof(sp),
    317         NULL, 0, NULL, NULL)) {
    318     unsn_copy_file_error();
    319   }
    320 }
    321 
    322 BOOL unsn_copy_file_get_sparse(HANDLE h)
    323 {
    324   FILE_BASIC_INFO info;
    325   if (!GetFileInformationByHandleEx(h, FileBasicInfo, &info, sizeof(info))) {
    326     unsn_copy_file_error();
    327   }
    328   return (info.FileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) != 0;
    329 }
    330 
    331 CAMLprim value unison_copy_file(value in_fd, value out_fd, value in_offs, value len)
    332 {
    333   CAMLparam4(in_fd, out_fd, in_offs, len);
    334 
    335   HANDLE hin = Handle_val(in_fd);
    336   HANDLE hout = Handle_val(out_fd);
    337 
    338   if (!unsn_copy_file_supports_cloning(hout)
    339       || !unsn_copy_file_supports_cloning(hin)) {
    340     caml_unix_error(ENOSYS, "copy_file", Nothing);
    341   }
    342 
    343   /* INTEGRITY_INFORMATION contains information about fs cluster size. We make
    344    * the assumption that both the source and destination have the same cluster
    345    * size (From MSDN: The source and destination files must be on the same ReFS
    346    * volume.). */
    347   FSCTL_GET_INTEGRITY_INFORMATION_BUFFER intgrty;
    348   if (!DeviceIoControl(hin, FSCTL_GET_INTEGRITY_INFORMATION,
    349         NULL, 0, &intgrty, sizeof(intgrty), NULL, NULL)) {
    350     unsn_copy_file_error();
    351   }
    352 
    353   DUPLICATE_EXTENTS_DATA dupl;
    354   dupl.FileHandle = hin;
    355   dupl.SourceFileOffset.QuadPart = Int64_val(in_offs);
    356   if (!SetFilePointerEx(hout, (LARGE_INTEGER){0}, &dupl.TargetFileOffset,
    357         FILE_CURRENT)) {
    358     unsn_copy_file_error();
    359   }
    360 
    361   /* From MSDN: The source and destination regions must begin and end at
    362    * a cluster boundary. */
    363   if ((dupl.SourceFileOffset.QuadPart % intgrty.ClusterSizeInBytes != 0)
    364       || (dupl.TargetFileOffset.QuadPart % intgrty.ClusterSizeInBytes != 0)) {
    365     /* FSCTL_DUPLICATE_EXTENTS_TO_FILE would fail anyway if this didn't hold,
    366      * but this way we can bail out earlier. */
    367     caml_unix_error(EINVAL, "copy_file", Nothing);
    368   }
    369   dupl.ByteCount.QuadPart = Long_val(len) +
    370     intgrty.ClusterSizeInBytes - (Long_val(len) % intgrty.ClusterSizeInBytes);
    371 
    372   /* From MSDN: If the source file is sparse, the destination file must also be
    373    * sparse. */
    374   BOOL in_is_sparse = unsn_copy_file_get_sparse(hin);
    375   BOOL out_is_sparse = unsn_copy_file_get_sparse(hout);
    376   /* Based on the wording in MSDN, it is ok if the destination file is sparse
    377    * and the source is not, the opposite is not. */
    378   if (in_is_sparse && !out_is_sparse) {
    379     unsn_copy_file_set_sparse(hout, TRUE);
    380   }
    381 
    382   /* From MSDN: The destination region must not extend past the end of file. */
    383   LARGE_INTEGER newTargetLen;
    384   newTargetLen.QuadPart = dupl.TargetFileOffset.QuadPart + Long_val(len);
    385   FILE_STANDARD_INFO fi;
    386   if (!GetFileInformationByHandleEx(hout, FileStandardInfo, &fi, sizeof(fi))) {
    387     unsn_copy_file_error();
    388   }
    389   if (fi.EndOfFile.QuadPart < newTargetLen.QuadPart) {
    390     /* We want to avoid allocating on disk when extending the file. Make the
    391      * destination file temporarily sparse, if it's not already. */
    392     if (!out_is_sparse && !in_is_sparse) {
    393       unsn_copy_file_set_sparse(hout, TRUE);
    394     }
    395     FILE_END_OF_FILE_INFO eofi;
    396     eofi.EndOfFile = newTargetLen;
    397     if (!SetFileInformationByHandle(hout, FileEndOfFileInfo, &eofi, sizeof(eofi))) {
    398       unsn_copy_file_error();
    399     }
    400     /* The destination file was set to sparse when extending it; revert the
    401      * sparse status. */
    402     if (!out_is_sparse && !in_is_sparse) {
    403       unsn_copy_file_set_sparse(hout, FALSE);
    404     }
    405   }
    406 
    407   caml_release_runtime_system();
    408   BOOL res = DeviceIoControl(hout, FSCTL_DUPLICATE_EXTENTS_TO_FILE,
    409                              &dupl, sizeof(dupl), NULL, 0, NULL, NULL);
    410   caml_acquire_runtime_system();
    411   if (!res) {
    412     unsn_copy_file_error();
    413   } else {
    414     /* FSCTL_DUPLICATE_EXTENTS_TO_FILE does not advance the destination file
    415      * offset. */
    416     LARGE_INTEGER copied;
    417     copied.QuadPart = Long_val(len);
    418     if (!SetFilePointerEx(hout, copied, NULL, FILE_CURRENT)) {
    419       unsn_copy_file_error();
    420     }
    421   }
    422 
    423   CAMLreturn(len);
    424 }
    425 
    426 #else /* defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(_WIN32) */
    427 
    428 
    429 CAMLprim value unison_copy_file(value in_fd, value out_fd, value in_offs, value len)
    430 {
    431   CAMLparam4(in_fd, out_fd, in_offs, len);
    432   caml_unix_error(ENOSYS, "copy_file", Nothing);
    433   CAMLreturn(Val_long(0));
    434 }
    435 
    436 
    437 #endif /* defined(__linux__) || defined(__FreeBSD__) || defined (__sun) || defined(_WIN32) */