(root)/
glibc-2.38/
support/
support_copy_file_range.c
       1  /* Simplified copy_file_range with cross-device copy.
       2     Copyright (C) 2018-2023 Free Software Foundation, Inc.
       3     This file is part of the GNU C Library.
       4  
       5     The GNU C Library is free software; you can redistribute it and/or
       6     modify it under the terms of the GNU Lesser General Public
       7     License as published by the Free Software Foundation; either
       8     version 2.1 of the License, or (at your option) any later version.
       9  
      10     The GNU C Library is distributed in the hope that it will be useful,
      11     but WITHOUT ANY WARRANTY; without even the implied warranty of
      12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      13     Lesser General Public License for more details.
      14  
      15     You should have received a copy of the GNU Lesser General Public
      16     License along with the GNU C Library; if not, see
      17     <https://www.gnu.org/licenses/>.  */
      18  
      19  #include <errno.h>
      20  #include <fcntl.h>
      21  #include <inttypes.h>
      22  #include <limits.h>
      23  #include <sys/stat.h>
      24  #include <sys/types.h>
      25  #include <unistd.h>
      26  #include <support/support.h>
      27  
      28  ssize_t
      29  support_copy_file_range (int infd, __off64_t *pinoff,
      30  			 int outfd, __off64_t *poutoff,
      31  			 size_t length, unsigned int flags)
      32  {
      33    if (flags != 0)
      34      {
      35        errno = EINVAL;
      36        return -1;
      37      }
      38  
      39    struct stat64 instat;
      40    struct stat64 outstat;
      41    if (fstat64 (infd, &instat) != 0 || fstat64 (outfd, &outstat) != 0)
      42      return -1;
      43    if (S_ISDIR (instat.st_mode) || S_ISDIR (outstat.st_mode))
      44      {
      45        errno = EISDIR;
      46        return -1;
      47      }
      48    if (!S_ISREG (instat.st_mode) || !S_ISREG (outstat.st_mode))
      49      {
      50        /* We need a regular input file so that the we can seek
      51  	 backwards in case of a write failure.  */
      52        errno = EINVAL;
      53        return -1;
      54      }
      55  
      56    /* The output descriptor must not have O_APPEND set.  */
      57    if (fcntl (outfd, F_GETFL) & O_APPEND)
      58      {
      59        errno = EBADF;
      60        return -1;
      61      }
      62  
      63    /* Avoid an overflow in the result.  */
      64    if (length > SSIZE_MAX)
      65      length = SSIZE_MAX;
      66  
      67    /* Main copying loop.  The buffer size is arbitrary and is a
      68       trade-off between stack size consumption, cache usage, and
      69       amortization of system call overhead.  */
      70    size_t copied = 0;
      71    char buf[8192];
      72    while (length > 0)
      73      {
      74        size_t to_read = length;
      75        if (to_read > sizeof (buf))
      76  	to_read = sizeof (buf);
      77  
      78        /* Fill the buffer.  */
      79        ssize_t read_count;
      80        if (pinoff == NULL)
      81  	read_count = read (infd, buf, to_read);
      82        else
      83  	read_count = pread64 (infd, buf, to_read, *pinoff);
      84        if (read_count == 0)
      85  	/* End of file reached prematurely.  */
      86  	return copied;
      87        if (read_count < 0)
      88  	{
      89  	  if (copied > 0)
      90  	    /* Report the number of bytes copied so far.  */
      91  	    return copied;
      92  	  return -1;
      93  	}
      94        if (pinoff != NULL)
      95  	*pinoff += read_count;
      96  
      97        /* Write the buffer part which was read to the destination.  */
      98        char *end = buf + read_count;
      99        for (char *p = buf; p < end; )
     100  	{
     101  	  ssize_t write_count;
     102  	  if (poutoff == NULL)
     103  	    write_count = write (outfd, p, end - p);
     104  	  else
     105  	    write_count = pwrite64 (outfd, p, end - p, *poutoff);
     106  	  if (write_count < 0)
     107  	    {
     108  	      /* Adjust the input read position to match what we have
     109  		 written, so that the caller can pick up after the
     110  		 error.  */
     111  	      size_t written = p - buf;
     112  	      /* NB: This needs to be signed so that we can form the
     113  		 negative value below.  */
     114  	      ssize_t overread = read_count - written;
     115  	      if (pinoff == NULL)
     116  		{
     117  		  if (overread > 0)
     118  		    {
     119  		      /* We are on an error recovery path, so we
     120  			 cannot deal with failure here.  */
     121  		      int save_errno = errno;
     122  		      (void) lseek64 (infd, -overread, SEEK_CUR);
     123  		      errno = save_errno;
     124  		    }
     125  		}
     126  	      else /* pinoff != NULL */
     127  		*pinoff -= overread;
     128  
     129  	      if (copied + written > 0)
     130  		/* Report the number of bytes copied so far.  */
     131  		return copied + written;
     132  	      return -1;
     133  	    }
     134  	  p += write_count;
     135  	  if (poutoff != NULL)
     136  	    *poutoff += write_count;
     137  	} /* Write loop.  */
     138  
     139        copied += read_count;
     140        length -= read_count;
     141      }
     142    return copied;
     143  }