pastebin - collaborative debugging tool
rovema.kpaste.net RSS


msnfs41client: Patches for |FSCTL_DUPLICATE_EXTENTS_TO_FILE| to duplicate sparse files correctly, 2025-04-28
Posted by Anonymous on Mon 28th Apr 2025 18:44
raw | new post

  1. From 98384f6e213c19baa112e095242e3399c2345489 Mon Sep 17 00:00:00 2001
  2. From: Roland Mainz <roland.mainz@nrubsig.org>
  3. Date: Mon, 28 Apr 2025 19:25:17 +0200
  4. Subject: [PATCH] daemon: |FSCTL_DUPLICATE_EXTENTS_TO_FILE| should duplicate
  5.  data+hole ranges correctly
  6.  
  7. |FSCTL_DUPLICATE_EXTENTS_TO_FILE| should duplicate data+hole ranges
  8. in sparse files correctly.
  9.  
  10. Signed-off-by: Cedric Blancher <cedric.blancher@gmail.com>
  11. ---
  12. daemon/fsctl.c | 305 ++++++++++++++++++++++++++++++++++---------------
  13.  1 file changed, 216 insertions(+), 89 deletions(-)
  14.  
  15. diff --git a/daemon/fsctl.c b/daemon/fsctl.c
  16. index c191f39..80a4e02 100644
  17. --- a/daemon/fsctl.c
  18. +++ b/daemon/fsctl.c
  19. @@ -466,18 +466,177 @@ out:
  20.      return status;
  21.  }
  22.  
  23. -/*
  24. - * CLONE_PUNCH_HOLE_IF_CLONESIZE_BIGGER_THAN_SRCFILESIZE - Punch
  25. - * a hole at the end of the file if the clone range is larger than
  26. - * the source file's size (Linux 6.6 nfsd will return
  27. - * |NFS4ERR_INVAL| in such cases)
  28. - *
  29. - * ToDo:
  30. - * - This should be replaced by a NFS SEEK loop to enumerate
  31. - * data ranges and hole ranges - Data ranges are handled via NFS
  32. - * CLONE, hole ranges via NFS DEALLOCATE
  33. - */
  34. -#define CLONE_PUNCH_HOLE_IF_CLONESIZE_BIGGER_THAN_SRCFILESIZE 1
  35. +static
  36. +int duplicate_sparsefile(nfs41_open_state *src_state,
  37. +    nfs41_open_state *dst_state,
  38. +    uint64_t srcfileoffset,
  39. +    uint64_t destfileoffset,
  40. +    uint64_t bytecount,
  41. +    nfs41_file_info *info)
  42. +{
  43. +    int status = NO_ERROR;
  44. +    nfs41_session *session = src_state->session;
  45. +    uint64_t next_offset;
  46. +    uint64_t end_offset;
  47. +    uint64_t data_size;
  48. +    int data_seek_status;
  49. +    bool_t data_seek_sr_eof;
  50. +    uint64_t data_seek_sr_offset;
  51. +    int hole_seek_status;
  52. +    bool_t hole_seek_sr_eof;
  53. +    uint64_t hole_seek_sr_offset;
  54. +    size_t i;
  55. +
  56. +    nfs41_path_fh *src_file = &src_state->file;
  57. +    nfs41_path_fh *dst_file = &dst_state->file;
  58. +    stateid_arg src_stateid;
  59. +    stateid_arg dst_stateid;
  60. +
  61. +    (void)memset(info, 0, sizeof(info));
  62. +
  63. +    DPRINTF(DDLVL,
  64. +        ("--> duplicate_sparsefile(src_state->path.path='%s')\n",
  65. +        src_state->path.path));
  66. +
  67. +    nfs41_open_stateid_arg(src_state, &src_stateid);
  68. +    nfs41_open_stateid_arg(dst_state, &dst_stateid);
  69. +
  70. +    /*
  71. +     * First punch a hole into the destination to make sure that any
  72. +     * data ranges in the destination from |destfileoffset| to
  73. +     * |destfileoffset+bytecount| are gone
  74. +     */
  75. +    status = nfs42_deallocate(session,
  76. +        dst_file,
  77. +        &dst_stateid,
  78. +        destfileoffset,
  79. +        bytecount,
  80. +        info);
  81. +    if (status) {
  82. +        DPRINTF(0/*DDLVL*/,
  83. +            ("duplicate_sparsefile("
  84. +            "src_state->path.path='%s' "
  85. +            "dst_state->path.path='%s'): "
  86. +            "DEALLOCATE failed with '%s'\n",
  87. +            src_state->path.path,
  88. +            dst_state->path.path,
  89. +            nfs_error_string(status)));
  90. +        status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP);
  91. +        goto out;
  92. +    }
  93. +
  94. +    next_offset = srcfileoffset;
  95. +    end_offset = srcfileoffset + bytecount;
  96. +
  97. +    for (i=0 ; ; i++) {
  98. +        data_seek_status = nfs42_seek(session,
  99. +            &src_state->file,
  100. +            &src_stateid,
  101. +            next_offset,
  102. +            NFS4_CONTENT_DATA,
  103. +            &data_seek_sr_eof,
  104. +            &data_seek_sr_offset);
  105. +
  106. +#ifdef LINUX_NFSD_SEEK_NXIO_BUG_WORKAROUND
  107. +        if (data_seek_status == NFS4ERR_NXIO) {
  108. +            DPRINTF(QARLVL, ("SEEK_DATA failed with NFS4ERR_NXIO\n"));
  109. +            goto out;
  110. +        }
  111. +#endif /* LINUX_NFSD_SEEK_NXIO_BUG_WORKAROUND */
  112. +        if (data_seek_status) {
  113. +            status = nfs_to_windows_error(data_seek_status,
  114. +                ERROR_INVALID_PARAMETER);
  115. +            DPRINTF(DDLVL,
  116. +                ("SEEK_DATA failed "
  117. +                "OP_SEEK(sa_offset=%llu,sa_what=SEEK_DATA) "
  118. +                "failed with %d(='%s')\n",
  119. +                next_offset,
  120. +                data_seek_status,
  121. +                nfs_error_string(data_seek_status)));
  122. +            goto out;
  123. +        }
  124. +
  125. +        next_offset = data_seek_sr_offset;
  126. +
  127. +        hole_seek_status = nfs42_seek(session,
  128. +            &src_state->file,
  129. +            &src_stateid,
  130. +            next_offset,
  131. +            NFS4_CONTENT_HOLE,
  132. +            &hole_seek_sr_eof,
  133. +            &hole_seek_sr_offset);
  134. +        if (hole_seek_status) {
  135. +            status = nfs_to_windows_error(hole_seek_status,
  136. +                ERROR_INVALID_PARAMETER);
  137. +            DPRINTF(DDLVL,
  138. +                ("SEEK_HOLE failed "
  139. +                "OP_SEEK(sa_offset=%llu,sa_what=SEEK_HOLE) "
  140. +                "failed with %d(='%s')\n",
  141. +                next_offset,
  142. +                hole_seek_status,
  143. +                nfs_error_string(hole_seek_status)));
  144. +            goto out;
  145. +        }
  146. +
  147. +        next_offset = hole_seek_sr_offset;
  148. +
  149. +        data_size = hole_seek_sr_offset - data_seek_sr_offset;
  150. +
  151. +        DPRINTF(DDLVL,
  152. +            ("data_section: from "
  153. +            "%llu to %llu, size=%llu (data_eof=%d, hole_eof=%d)\n",
  154. +            data_seek_sr_offset,
  155. +            hole_seek_sr_offset,
  156. +            data_size,
  157. +            (int)data_seek_sr_eof,
  158. +            (int)hole_seek_sr_eof));
  159. +
  160. +        status = nfs42_clone(session,
  161. +            src_file,
  162. +            dst_file,
  163. +            &src_stateid,
  164. +            &dst_stateid,
  165. +            data_seek_sr_offset,
  166. +            destfileoffset + (data_seek_sr_offset-srcfileoffset),
  167. +            data_size,
  168. +            info);
  169. +        if (status) {
  170. +            DPRINTF(0/*DDLVL*/,
  171. +                ("duplicate_sparsefile("
  172. +                "src_state->path.path='%s' "
  173. +                "dst_state->path.path='%s'): "
  174. +                "CLONE failed with '%s'\n",
  175. +                src_state->path.path,
  176. +                dst_state->path.path,
  177. +                nfs_error_string(status)));
  178. +            status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP);
  179. +            goto out;
  180. +        }
  181. +
  182. +        if (data_seek_sr_offset > end_offset) {
  183. +            DPRINTF(DDLVL,
  184. +                ("end offset reached, "
  185. +                "i=%d, data_seek_sr_offset(=%lld) > end_offset(=%lld)\n",
  186. +                (int)i,
  187. +                (long long)data_seek_sr_offset,
  188. +                (long long)end_offset));
  189. +            break;
  190. +        }
  191. +
  192. +        if (data_seek_sr_eof || hole_seek_sr_eof) {
  193. +            DPRINTF(DDLVL,
  194. +                ("EOF reached (data_seek_sr_eof=%d, hole_seek_sr_eof=%d)\n",
  195. +                (int)data_seek_sr_eof,
  196. +                (int)hole_seek_sr_eof));
  197. +            break;
  198. +        }
  199. +    }
  200. +
  201. +out:
  202. +    DPRINTF(DDLVL, ("<-- duplicate_sparsefile(), status=0x%x\n",
  203. +        status));
  204. +    return status;
  205. +}
  206.  
  207.  static
  208.  int handle_duplicatedata(void *daemon_context,
  209. @@ -488,7 +647,6 @@ int handle_duplicatedata(void *daemon_context,
  210.      nfs41_open_state *src_state = args->src_state;
  211.      nfs41_open_state *dst_state = upcall->state_ref;
  212.      nfs41_session *session = dst_state->session;
  213. -    nfs41_path_fh *src_file = &src_state->file;
  214.      nfs41_path_fh *dst_file = &dst_state->file;
  215.      nfs41_file_info info;
  216.      stateid_arg src_stateid;
  217. @@ -504,36 +662,44 @@ int handle_duplicatedata(void *daemon_context,
  218.              dst_state->path.path,
  219.              src_state->path.path));
  220.  
  221. +    /* NFS SEEK supported ? */
  222. +    if (session->client->root->supports_nfs42_seek == false) {
  223. +        status = ERROR_NOT_SUPPORTED;
  224. +        goto out;
  225. +    }
  226.      /* NFS CLONE supported ? */
  227.      if (session->client->root->supports_nfs42_clone == false) {
  228. -        DPRINTF(0,
  229. -            ("handle_duplicatedata: NFS CLONE not supported\n"));
  230.          status = ERROR_NOT_SUPPORTED;
  231.          goto out;
  232.      }
  233. -
  234. -#ifdef CLONE_PUNCH_HOLE_IF_CLONESIZE_BIGGER_THAN_SRCFILESIZE
  235.      /* NFS DEALLOCATE supported ? */
  236.      if (session->client->root->supports_nfs42_deallocate == false) {
  237. -        DPRINTF(0,
  238. -            ("handle_duplicatedata: NFS DEALLOCATE not supported\n"));
  239.          status = ERROR_NOT_SUPPORTED;
  240.          goto out;
  241.      }
  242. -#endif /* CLONE_PUNCH_HOLE_IF_CLONESIZE_BIGGER_THAN_SRCFILESIZE */
  243.  
  244.      nfs41_open_stateid_arg(src_state, &src_stateid);
  245.      nfs41_open_stateid_arg(dst_state, &dst_stateid);
  246.  
  247. -#ifdef CLONE_PUNCH_HOLE_IF_CLONESIZE_BIGGER_THAN_SRCFILESIZE
  248. -    int64_t src_file_size;
  249. +    /*
  250. +     * Get destination file size
  251. +     * Callers will set the file size before calling
  252. +     * |FSCTL_DUPLICATE_EXTENTS_TO_FILE| because the fsctl is
  253. +     * not allows to allocate disk space(=increase the destination
  254. +     * file size).
  255. +     * But since |DUPLICATE_EXTENTS_DATA.ByteCount| might be rounded
  256. +     * up to the filesystem's cluster size and NFSv4.2 CLONE can
  257. +     * grow the file's size we have to get the destination file's
  258. +     * size to clamp |args->bytecount|.
  259. +     */
  260. +    int64_t dst_file_size;
  261.      bitmap4 attr_request = {
  262.          .count = 3,
  263.          .arr[0] = FATTR4_WORD0_SIZE,
  264.          .arr[1] = 0UL,
  265.          .arr[2] = FATTR4_WORD2_CLONE_BLKSIZE
  266.      };
  267. -    status = nfs41_getattr(session, src_file, &attr_request, &info);
  268. +    status = nfs41_getattr(session, dst_file, &attr_request, &info);
  269.      if (status) {
  270.          eprintf("handle_duplicatedata: "
  271.              "nfs41_getattr() failed with '%s'\n",
  272. @@ -543,101 +709,62 @@ int handle_duplicatedata(void *daemon_context,
  273.      }
  274.  
  275.      EASSERT(bitmap_isset(&info.attrmask, 0, FATTR4_WORD0_SIZE));
  276. +
  277.      if (bitmap_isset(&info.attrmask, 2, FATTR4_WORD2_CLONE_BLKSIZE)) {
  278.          DPRINTF(DDLVL,
  279.              ("handle_duplicatedata: "
  280. -            "srcfile size=%lld, clone_blksize=%lu\n",
  281. +            "dstfile size=%lld, clone_blksize=%lu\n",
  282.              (long long)info.size,
  283.              (unsigned long)info.clone_blksize));
  284.      }
  285.      else {
  286.          DPRINTF(DDLVL,
  287. -            ("handle_duplicatedata: srcfile size=%lld\n",
  288. +            ("handle_duplicatedata: dstfile size=%lld\n",
  289.              (long long)info.size));
  290.      }
  291.  
  292. -    src_file_size = info.size;
  293. +    dst_file_size = info.size;
  294.  
  295. -    if ((args->srcfileoffset+args->bytecount) > src_file_size)
  296. -        bytecount = src_file_size-args->srcfileoffset;
  297. -    else
  298. -        bytecount = args->bytecount;
  299. -#else
  300.      /*
  301. -     * FIXME: We should validate against |FATTR4_WORD2_CLONE_BLKSIZE|
  302. -     * (if supported by NFSv4.2 server) in this codepath
  303. +     * Clamp bytecount so everything will fit into the destination
  304. +     * file
  305.       */
  306. -    bytecount = args->bytecount;
  307. -#endif /* CLONE_PUNCH_HOLE_IF_CLONESIZE_BIGGER_THAN_SRCFILESIZE */
  308. +    if ((args->destfileoffset+args->bytecount) > dst_file_size) {
  309. +        bytecount = dst_file_size-args->destfileoffset;
  310. +    }
  311. +    else {
  312. +        bytecount = args->bytecount;
  313. +    }
  314.  
  315. -    EASSERT(bytecount > 0);
  316. -    if (bytecount <= 0) {
  317. -        DPRINTF(0/*DDLVL*/,
  318. -            ("handle_duplicatedata("
  319. +    if (bytecount == 0) {
  320. +        DPRINTF(DDLVL,
  321. +            ("bytecount == 0, returning ERROR_SUCCESS\n"));
  322. +        status = ERROR_SUCCESS;
  323. +        goto out;
  324. +    }
  325. +
  326. +    if (bytecount < 0) {
  327. +        eprintf("handle_duplicatedata("
  328.              "src_state->path.path='%s' "
  329.              "dst_state->path.path='%s'): "
  330.              "Negative bytecount %lld\n",
  331.              src_state->path.path,
  332.              dst_state->path.path,
  333. -            bytecount));
  334. +            bytecount);
  335.          status = ERROR_INVALID_PARAMETER;
  336.          goto out;
  337.      }
  338.  
  339. -    status = nfs42_clone(session,
  340. -        src_file,
  341. -        dst_file,
  342. -        &src_stateid,
  343. -        &dst_stateid,
  344. +    (void)memset(&info, 0, sizeof(info));
  345. +
  346. +    status = duplicate_sparsefile(src_state,
  347. +        dst_state,
  348.          args->srcfileoffset,
  349.          args->destfileoffset,
  350.          bytecount,
  351.          &info);
  352. -    if (status) {
  353. -        DPRINTF(0/*DDLVL*/,
  354. -            ("handle_duplicatedata("
  355. -            "src_state->path.path='%s' "
  356. -            "dst_state->path.path='%s'): "
  357. -            "CLONE failed with '%s'\n",
  358. -            src_state->path.path,
  359. -            dst_state->path.path,
  360. -            nfs_error_string(status)));
  361. -        status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP);
  362. +    if (status)
  363.          goto out;
  364. -    }
  365. -
  366. -#ifdef CLONE_PUNCH_HOLE_IF_CLONESIZE_BIGGER_THAN_SRCFILESIZE
  367. -    if ((args->srcfileoffset+args->bytecount) > src_file_size) {
  368. -        DPRINTF(DDLVL,
  369. -            ("handle_duplicatedata(): "
  370. -            "Clone range (offset(=%lld)+bytecount(=%lld)=%lld) "
  371. -            "bigger than source file size (%lld), "
  372. -            "adding hole at the end of dest file.\n",
  373. -            args->srcfileoffset,
  374. -            args->bytecount,
  375. -            (args->srcfileoffset+args->bytecount),
  376. -            src_file_size));
  377. -
  378. -        status = nfs42_deallocate(session,
  379. -            dst_file,
  380. -            &dst_stateid,
  381. -            args->destfileoffset+src_file_size,
  382. -            ((args->srcfileoffset+args->bytecount) - src_file_size),
  383. -            &info);
  384. -        if (status) {
  385. -            DPRINTF(0/*DDLVL*/,
  386. -                ("handle_duplicatedata("
  387. -                "src_state->path.path='%s' "
  388. -                "dst_state->path.path='%s'): "
  389. -                "DEALLOCATE failed with '%s'\n",
  390. -                src_state->path.path,
  391. -                dst_state->path.path,
  392. -                nfs_error_string(status)));
  393. -            status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP);
  394. -            goto out;
  395. -        }
  396. -    }
  397. -#endif /* CLONE_PUNCH_HOLE_IF_CLONESIZE_BIGGER_THAN_SRCFILESIZE */
  398.  
  399.      /* Update ctime on success */
  400.      EASSERT(bitmap_isset(&info.attrmask, 0, FATTR4_WORD0_CHANGE));
  401. --
  402. 2.45.1

Submit a correction or amendment below (click here to make a fresh posting)
After submitting an amendment, you'll be able to view the differences between the old and new posts easily.

Syntax highlighting:

To highlight particular lines, prefix each line with {%HIGHLIGHT}




All content is user-submitted.
The administrators of this site (kpaste.net) are not responsible for their content.
Abuse reports should be emailed to us at