Coverage Report

Created: 2026-07-25 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/main/io/php_io.c
Line
Count
Source
1
/*
2
   +----------------------------------------------------------------------+
3
   | Copyright © The PHP Group and Contributors.                          |
4
   +----------------------------------------------------------------------+
5
   | This source file is subject to the Modified BSD License that is      |
6
   | bundled with this package in the file LICENSE, and is available      |
7
   | through the World Wide Web at <https://www.php.net/license/>.        |
8
   |                                                                      |
9
   | SPDX-License-Identifier: BSD-3-Clause                                |
10
   +----------------------------------------------------------------------+
11
   | Authors: Jakub Zelenka <bukka@php.net>                               |
12
   +----------------------------------------------------------------------+
13
*/
14
15
#include "php.h"
16
#include "php_io.h"
17
#include "php_io_internal.h"
18
19
#include <errno.h>
20
21
#ifdef PHP_WIN32
22
#include <io.h>
23
#include <winsock2.h>
24
#else
25
#include <unistd.h>
26
#endif
27
28
static php_io php_io_instance = {
29
  .copy = PHP_IO_PLATFORM_COPY,
30
  .platform_name = PHP_IO_PLATFORM_NAME,
31
};
32
33
PHPAPI php_io *php_io_get(void)
34
0
{
35
0
  return &php_io_instance;
36
0
}
37
38
PHPAPI zend_result php_io_copy(php_io_fd *src, php_io_fd *dest, size_t maxlen, size_t *copied)
39
0
{
40
0
  return php_io_get()->copy(src, dest, maxlen, copied);
41
0
}
42
43
zend_result php_io_generic_copy_fallback(int src_fd, int dest_fd, size_t maxlen, size_t *copied)
44
0
{
45
0
  char buf[PHP_IO_COPY_BUFSIZE];
46
0
  size_t total_copied = 0;
47
0
  size_t remaining = (maxlen == PHP_IO_COPY_ALL) ? SIZE_MAX : maxlen;
48
0
  zend_result result = SUCCESS;
49
50
0
  while (remaining > 0) {
51
0
    size_t to_read = (remaining < sizeof(buf)) ? remaining : sizeof(buf);
52
0
    ssize_t bytes_read;
53
0
    do {
54
0
      bytes_read = read(src_fd, buf, to_read);
55
0
    } while (bytes_read < 0 && errno == EINTR);
56
57
0
    if (bytes_read < 0) {
58
0
      result = FAILURE;
59
0
      break;
60
0
    } else if (bytes_read == 0) {
61
0
      break;
62
0
    }
63
64
0
    char *writeptr = buf;
65
0
    size_t to_write = (size_t) bytes_read;
66
67
0
    while (to_write > 0) {
68
0
      ssize_t bytes_written;
69
0
      do {
70
0
        bytes_written = write(dest_fd, writeptr, to_write);
71
0
      } while (bytes_written < 0 && errno == EINTR);
72
0
      if (bytes_written <= 0) {
73
0
        result = FAILURE;
74
0
        break;
75
0
      }
76
0
      total_copied += bytes_written;
77
0
      writeptr += bytes_written;
78
0
      to_write -= bytes_written;
79
0
    }
80
0
    if (result == FAILURE) {
81
0
      break;
82
0
    }
83
84
0
    if (maxlen != PHP_IO_COPY_ALL) {
85
0
      remaining -= bytes_read;
86
0
    }
87
0
  }
88
89
0
  *copied = total_copied;
90
0
  return result;
91
0
}
92
93
/* For a blocking socket source, wait until data is available (or the configured
94
 * timeout elapses) before reading. Mirrors the per-platform wait_for_data
95
 * helpers so the generic copy path honours stream timeouts on systems without a
96
 * kernel offload (e.g. Haiku). Returns >0 ready, 0 timeout, <0 error. */
97
#ifndef PHP_WIN32
98
static int php_io_generic_wait_for_data(php_io_fd *fd)
99
0
{
100
0
  if (fd->fd_type != PHP_IO_FD_SOCKET || !fd->is_blocked) {
101
0
    return 1;
102
0
  }
103
104
0
  int timeout_ms = (fd->timeout.tv_sec == -1)
105
0
    ? -1
106
0
    : (int) (fd->timeout.tv_sec * 1000 + fd->timeout.tv_usec / 1000);
107
108
0
  int ret;
109
0
  do {
110
0
    ret = php_pollfd_for_ms(fd->fd, POLLIN, timeout_ms);
111
0
  } while (ret == -1 && errno == EINTR);
112
113
0
  return ret;
114
0
}
115
#else
116
static int php_io_generic_wait_for_data(php_io_fd *fd)
117
{
118
  (void) fd;
119
  return 1;
120
}
121
#endif
122
123
zend_result php_io_generic_copy(php_io_fd *src, php_io_fd *dest, size_t maxlen, size_t *copied)
124
0
{
125
0
  char buf[PHP_IO_COPY_BUFSIZE];
126
0
  size_t total_copied = 0;
127
0
  size_t remaining = (maxlen == PHP_IO_COPY_ALL) ? SIZE_MAX : maxlen;
128
0
  zend_result result = SUCCESS;
129
130
0
  while (remaining > 0) {
131
0
    int ready = php_io_generic_wait_for_data(src);
132
0
    if (ready == 0) {
133
      /* timeout */
134
0
      break;
135
0
    } else if (ready < 0) {
136
0
      result = FAILURE;
137
0
      break;
138
0
    }
139
140
0
    size_t to_read = (remaining < sizeof(buf)) ? remaining : sizeof(buf);
141
0
    ssize_t bytes_read;
142
0
    do {
143
0
      bytes_read = read(src->fd, buf, to_read);
144
0
    } while (bytes_read < 0 && errno == EINTR);
145
146
0
    if (bytes_read < 0) {
147
0
      result = FAILURE;
148
0
      break;
149
0
    } else if (bytes_read == 0) {
150
0
      break;
151
0
    }
152
153
0
    char *writeptr = buf;
154
0
    size_t to_write = (size_t) bytes_read;
155
156
0
    while (to_write > 0) {
157
0
      ssize_t bytes_written;
158
0
      do {
159
0
        bytes_written = write(dest->fd, writeptr, to_write);
160
0
      } while (bytes_written < 0 && errno == EINTR);
161
0
      if (bytes_written <= 0) {
162
0
        result = FAILURE;
163
0
        break;
164
0
      }
165
0
      total_copied += bytes_written;
166
0
      writeptr += bytes_written;
167
0
      to_write -= bytes_written;
168
0
    }
169
0
    if (result == FAILURE) {
170
0
      break;
171
0
    }
172
173
0
    if (maxlen != PHP_IO_COPY_ALL) {
174
0
      remaining -= bytes_read;
175
0
    }
176
0
  }
177
178
0
  *copied = total_copied;
179
0
  return result;
180
0
}