aboutsummaryrefslogtreecommitdiff
path: root/io/copy_file_range-compat.c
blob: 58dbeef3e92669a855eccc5f559d978011b4a854 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/* Emulation of copy_file_range.
   Copyright (C) 2017-2019 Free Software Foundation, Inc.
   This file is part of the GNU C Library.

   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   The GNU C Library 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, see
   <http://www.gnu.org/licenses/>.  */

/* The following macros should be defined before including this
   file:

   COPY_FILE_RANGE_DECL   Declaration specifiers for the function below.
   COPY_FILE_RANGE        Name of the function to define.  */

#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

COPY_FILE_RANGE_DECL
ssize_t
COPY_FILE_RANGE (int infd, __off64_t *pinoff,
                 int outfd, __off64_t *poutoff,
                 size_t length, unsigned int flags)
{
  if (flags != 0)
    {
      __set_errno (EINVAL);
      return -1;
    }

  {
    struct stat64 instat;
    struct stat64 outstat;
    if (fstat64 (infd, &instat) != 0 || fstat64 (outfd, &outstat) != 0)
      return -1;
    if (S_ISDIR (instat.st_mode) || S_ISDIR (outstat.st_mode))
      {
        __set_errno (EISDIR);
        return -1;
      }
    if (!S_ISREG (instat.st_mode) || !S_ISREG (outstat.st_mode))
      {
        /* We need a regular input file so that the we can seek
           backwards in case of a write failure.  */
        __set_errno (EINVAL);
        return -1;
      }
    if (instat.st_dev != outstat.st_dev)
      {
        /* Cross-device copies are not supported.  */
        __set_errno (EXDEV);
        return -1;
      }
  }

  /* The output descriptor must not have O_APPEND set.  */
  {
    int flags = __fcntl (outfd, F_GETFL);
    if (flags & O_APPEND)
      {
        __set_errno (EBADF);
        return -1;
      }
  }

  /* Avoid an overflow in the result.  */
  if (length > SSIZE_MAX)
    length = SSIZE_MAX;

  /* Main copying loop.  The buffer size is arbitrary and is a
     trade-off between stack size consumption, cache usage, and
     amortization of system call overhead.  */
  size_t copied = 0;
  char buf[8192];
  while (length > 0)
    {
      size_t to_read = length;
      if (to_read > sizeof (buf))
        to_read = sizeof (buf);

      /* Fill the buffer.  */
      ssize_t read_count;
      if (pinoff == NULL)
        read_count = read (infd, buf, to_read);
      else
        read_count = __libc_pread64 (infd, buf, to_read, *pinoff);
      if (read_count == 0)
        /* End of file reached prematurely.  */
        return copied;
      if (read_count < 0)
        {
          if (copied > 0)
            /* Report the number of bytes copied so far.  */
            return copied;
          return -1;
        }
      if (pinoff != NULL)
        *pinoff += read_count;

      /* Write the buffer part which was read to the destination.  */
      char *end = buf + read_count;
      for (char *p = buf; p < end; )
        {
          ssize_t write_count;
          if (poutoff == NULL)
            write_count = write (outfd, p, end - p);
          else
            write_count = __libc_pwrite64 (outfd, p, end - p, *poutoff);
          if (write_count < 0)
            {
              /* Adjust the input read position to match what we have
                 written, so that the caller can pick up after the
                 error.  */
              size_t written = p - buf;
              /* NB: This needs to be signed so that we can form the
                 negative value below.  */
              ssize_t overread = read_count - written;
              if (pinoff == NULL)
                {
                  if (overread > 0)
                    {
                      /* We are on an error recovery path, so we
                         cannot deal with failure here.  */
                      int save_errno = errno;
                      (void) __libc_lseek64 (infd, -overread, SEEK_CUR);
                      __set_errno (save_errno);
                    }
                }
              else /* pinoff != NULL */
                *pinoff -= overread;

              if (copied + written > 0)
                /* Report the number of bytes copied so far.  */
                return copied + written;
              return -1;
            }
          p += write_count;
          if (poutoff != NULL)
            *poutoff += write_count;
        } /* Write loop.  */

      copied += read_count;
      length -= read_count;
    }
  return copied;
}