Skip to content

Commit 0d94df1

Browse files
committed
added separate copy_range function
1 parent 659511d commit 0d94df1

File tree

7 files changed

+203
-65
lines changed

7 files changed

+203
-65
lines changed

erts/emulator/nifs/common/prim_file_nif.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ static ERL_NIF_TERM open_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]
103103
static ERL_NIF_TERM close_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
104104

105105
static ERL_NIF_TERM copy_file_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
106+
static ERL_NIF_TERM copy_range_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
106107

107108
static ERL_NIF_TERM file_desc_to_ref_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
108109

@@ -188,6 +189,7 @@ static ErlNifFunc nif_funcs[] = {
188189
{"advise_nif", 4, advise_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
189190
{"read_handle_info_nif", 1, read_handle_info_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
190191
{"copy_file_nif", 2, copy_file_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
192+
{"copy_range_nif", 3, copy_range_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
191193

192194
/* Filesystem ops */
193195
{"make_hard_link_nif", 2, make_hard_link_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
@@ -1423,3 +1425,33 @@ static ERL_NIF_TERM copy_file_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM a
14231425

14241426
return am_ok;
14251427
}
1428+
1429+
static ERL_NIF_TERM copy_range_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
1430+
posix_errno_t posix_errno;
1431+
efile_path_t file_path_in, file_path_out;
1432+
off64_t length;
1433+
1434+
ASSERT(argc == 3);
1435+
1436+
if ((posix_errno = efile_marshal_path(env, argv[0], &file_path_in))) {
1437+
return posix_error_to_tuple(env, posix_errno);
1438+
}
1439+
1440+
if ((posix_errno = efile_marshal_path(env, argv[1], &file_path_out))) {
1441+
return posix_error_to_tuple(env, posix_errno);
1442+
}
1443+
1444+
if(!enif_is_number(env, argv[2])) {
1445+
return enif_make_badarg(env);
1446+
}
1447+
1448+
if(!enif_get_int64(env, argv[2], &length) || length < 0) {
1449+
return posix_error_to_tuple(env, EINVAL);
1450+
}
1451+
1452+
if ((posix_errno = efile_copy_range(&file_path_in, &file_path_out, length))) {
1453+
return posix_error_to_tuple(env, posix_errno);
1454+
}
1455+
1456+
return am_ok;
1457+
}

erts/emulator/nifs/common/prim_file_nif.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,6 @@ posix_errno_t efile_from_fd(int fd,
177177
ErlNifResourceType *nif_type,
178178
efile_data_t **d);
179179

180-
posix_errno_t efile_copy_file(const efile_path_t *source, const efile_path_t *destination);
181-
182180
/** @brief Closes a file. The file must have entered the CLOSED state prior to
183181
* calling this to prevent double close.
184182
*
@@ -263,3 +261,12 @@ posix_errno_t efile_get_device_cwd(ErlNifEnv *env, int device_index, ERL_NIF_TER
263261
/** @brief A Windows-specific function for returning the 8.3-name of a given
264262
* file or directory. */
265263
posix_errno_t efile_altname(ErlNifEnv *env, const efile_path_t *path, ERL_NIF_TERM *result);
264+
265+
/** @brief This function an implementation of copy_file_range for Unix systems and CopyFileW for Windows,
266+
* providing the capability to copy data efficiently between file descriptors.
267+
* If the system supports the copy_file_range syscall, it performs a direct data transfer between file descriptors.
268+
* However, if the syscall isn't available, it may default to a standard read and write operation.
269+
*/
270+
posix_errno_t efile_copy_range_int(int fd_in, int fd_out, off64_t length);
271+
posix_errno_t efile_copy_file(const efile_path_t *file_path_in, const efile_path_t *file_path_out);
272+
posix_errno_t efile_copy_range(const efile_path_t *file_path_in, const efile_path_t *file_path_out, off64_t length);

erts/emulator/nifs/unix/unix_prim_file.c

Lines changed: 77 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,10 +1086,53 @@ posix_errno_t efile_altname(ErlNifEnv *env, const efile_path_t *path, ERL_NIF_TE
10861086
return ENOTSUP;
10871087
}
10881088

1089+
posix_errno_t efile_copy_range_int(int fd_in, int fd_out, off64_t length) {
1090+
off64_t ret;
1091+
1092+
#ifdef HAVE_COPY_FILE_RANGE
1093+
ret = syscall(SYS_copy_file_range, fd_in, NULL, fd_out, NULL, length, 0);
1094+
1095+
if (ret < 0) {
1096+
return errno;
1097+
}
1098+
#else
1099+
char buffer[4096];
1100+
off64_t bytes_copied = 0;
1101+
ssize_t bytes_read, bytes_written;
1102+
1103+
while (bytes_copied < length && (bytes_read = read(fd_in, buffer, sizeof(buffer))) > 0) {
1104+
ssize_t total_bytes_written = 0;
1105+
1106+
do {
1107+
bytes_written = write(fd_out, buffer + total_bytes_written, bytes_read - total_bytes_written);
1108+
if (bytes_written >= 0) {
1109+
total_bytes_written += bytes_written;
1110+
bytes_copied += bytes_written;
1111+
} else {
1112+
if (errno != EINTR) {
1113+
return errno;
1114+
}
1115+
}
1116+
} while (total_bytes_written < bytes_read && bytes_copied < length);
1117+
1118+
if (bytes_written < 0) {
1119+
return errno;
1120+
}
1121+
}
1122+
1123+
if (bytes_read < 0) {
1124+
return errno;
1125+
}
1126+
#endif
1127+
1128+
return 0;
1129+
}
1130+
10891131
posix_errno_t efile_copy_file(const efile_path_t *file_path_in, const efile_path_t *file_path_out) {
10901132
int fd_in, fd_out;
10911133
struct stat file_stat;
1092-
off64_t ret, length;
1134+
off64_t length;
1135+
posix_errno_t result;
10931136

10941137
do {
10951138
fd_in = open((const char*)file_path_in->data, O_RDONLY);
@@ -1108,71 +1151,53 @@ posix_errno_t efile_copy_file(const efile_path_t *file_path_in, const efile_path
11081151
return errno;
11091152
}
11101153

1111-
#ifdef HAVE_COPY_FILE_RANGE
1112-
/* Copy file contents using unix SYS_copy_file_range syscall
1113-
* First we should check check for FSTAT to define Source length */
1114-
1115-
#ifdef HAVE_FSTAT
1116-
if (fstat(fd_in, &file_stat) < 0) {
1117-
close(fd_in);
1118-
close(fd_out);
1119-
return errno;
1120-
}
1154+
#ifdef HAVE_FSTAT
1155+
if (fstat(fd_in, &file_stat) < 0) {
1156+
close(fd_in);
1157+
close(fd_out);
1158+
return errno;
1159+
}
11211160

1122-
length = file_stat.st_size;
1161+
length = file_stat.st_size;
1162+
#else
1163+
close(fd_in);
1164+
close(fd_out);
1165+
return ENOTSUP;
1166+
#endif
11231167

1124-
ret = syscall(SYS_copy_file_range, fd_in, NULL, fd_out, NULL, length, 0);
1168+
result = efile_copy_range_int(fd_in, fd_out, length);
11251169

1126-
close(fd_in);
1127-
close(fd_out);
1170+
close(fd_in);
1171+
close(fd_out);
11281172

1129-
if (ret < 0) {
1130-
return errno;
1131-
}
1132-
#else
1133-
close(fd_in);
1134-
close(fd_out);
1135-
return ENOTSUP;
1136-
#endif
1173+
return result;
1174+
}
11371175

1138-
#else
1139-
/* As a fallback for when copy_file_range(2) is unavailable we could do a simple read(2)+write(2) loop. */
1140-
char buffer[4096];
1141-
ssize_t bytes_read, bytes_written;
1142-
1143-
while ((bytes_read = read(fd_in, buffer, sizeof(buffer))) > 0) {
1144-
ssize_t total_bytes_written = 0;
1176+
posix_errno_t efile_copy_range(const efile_path_t *file_path_in, const efile_path_t *file_path_out, off64_t length) {
1177+
int fd_in, fd_out;
1178+
posix_errno_t result;
11451179

1146-
do {
1147-
bytes_written = write(fd_out, buffer + total_bytes_written, bytes_read - total_bytes_written);
1148-
if (bytes_written >= 0) {
1149-
total_bytes_written += bytes_written;
1150-
} else {
1151-
if (errno != EINTR) {
1152-
close(fd_in);
1153-
close(fd_out);
1154-
return errno;
1155-
}
1156-
}
1157-
} while (total_bytes_written < bytes_read);
1180+
do {
1181+
fd_in = open((const char*)file_path_in->data, O_RDONLY);
1182+
} while (fd_in == -1 && errno == EINTR);
11581183

1159-
if (bytes_written < 0) {
1160-
close(fd_in);
1161-
close(fd_out);
1162-
return errno;
1163-
}
1184+
if (fd_in == -1) {
1185+
return errno;
11641186
}
11651187

1166-
if (bytes_read < 0) {
1188+
do {
1189+
fd_out = open((const char*)file_path_out->data, O_CREAT | O_WRONLY | O_TRUNC, FILE_MODE);
1190+
} while (fd_out == -1 && errno == EINTR);
1191+
1192+
if (fd_out == -1) {
11671193
close(fd_in);
1168-
close(fd_out);
11691194
return errno;
11701195
}
11711196

1197+
result = efile_copy_range_int(fd_in, fd_out, length);
1198+
11721199
close(fd_in);
11731200
close(fd_out);
1174-
#endif
11751201

1176-
return 0;
1202+
return result;
11771203
}
1178-

erts/emulator/nifs/win32/win_prim_file.c

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1595,18 +1595,67 @@ posix_errno_t efile_copy_file(const efile_path_t *file_path_in, const efile_path
15951595
ASSERT_PATH_FORMAT(file_path_in);
15961596
ASSERT_PATH_FORMAT(file_path_out);
15971597

1598-
/* If this parameter is TRUE and the new file specified by lpNewFileName already exists, the function fails.
1599-
* If this parameter is FALSE and the new file already exists, the function overwrites the existing file and succeeds.
1600-
* For safety reason, we using TRUE as default value.
1601-
*/
1598+
if(!CopyFileW((WCHAR*)file_path_in->data, (WCHAR*)file_path_out->data, FALSE)) {
1599+
return windows_to_posix_errno(GetLastError());
1600+
}
1601+
1602+
return 0;
1603+
}
1604+
1605+
posix_errno_t efile_copy_range(const efile_path_t *file_path_in, const efile_path_t *file_path_out, off64_t length) {
1606+
HANDLE hInFile, hOutFile;
16021607

1603-
if(!CopyFileW((WCHAR*)file_path_in->data, (WCHAR*)file_path_out->data, TRUE)) {
1608+
char buffer[4096];
1609+
DWORD totalBytesRead;
1610+
DWORD bytesRead, bytesWritten;
1611+
LARGE_INTEGER li;
1612+
1613+
ASSERT_PATH_FORMAT(file_path_in);
1614+
ASSERT_PATH_FORMAT(file_path_out);
1615+
1616+
hInFile = CreateFileW((WCHAR*)file_path_in->data, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1617+
if (hInFile == INVALID_HANDLE_VALUE) {
16041618
return windows_to_posix_errno(GetLastError());
16051619
}
16061620

1621+
hOutFile = CreateFileW((WCHAR*)file_path_out->data, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
1622+
if (hOutFile == INVALID_HANDLE_VALUE) {
1623+
CloseHandle(hInFile);
1624+
return windows_to_posix_errno(GetLastError());
1625+
}
1626+
1627+
li.QuadPart = length;
1628+
if (!SetFilePointerEx(hInFile, li, NULL, FILE_BEGIN)) {
1629+
CloseHandle(hInFile);
1630+
CloseHandle(hOutFile);
1631+
return windows_to_posix_errno(GetLastError());
1632+
}
1633+
1634+
totalBytesRead = 0;
1635+
1636+
while (totalBytesRead < length) {
1637+
if (!ReadFile(hInFile, buffer, sizeof(buffer), &bytesRead, NULL) || bytesRead == 0) {
1638+
break;
1639+
}
1640+
1641+
if (!WriteFile(hOutFile, buffer, bytesRead, &bytesWritten, NULL)) {
1642+
break;
1643+
}
1644+
1645+
totalBytesRead += bytesRead;
1646+
}
1647+
1648+
CloseHandle(hInFile);
1649+
CloseHandle(hOutFile);
1650+
1651+
if (totalBytesRead < length) {
1652+
return errno;
1653+
}
1654+
16071655
return 0;
16081656
}
16091657

1658+
16101659
static int windows_to_posix_errno(DWORD last_error) {
16111660
switch(last_error) {
16121661
case ERROR_SUCCESS:

erts/preloaded/ebin/prim_file.beam

340 Bytes
Binary file not shown.

erts/preloaded/src/prim_file.erl

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
sync/1, datasync/1, truncate/1, advise/4, allocate/3,
2626
read_line/1, read/2, write/2, position/2,
2727
pread/2, pread/3, pwrite/2, pwrite/3,
28-
copy_file/2]).
28+
copy_file/2, copy_range/3]).
2929

3030
%% OTP internal.
3131

@@ -74,7 +74,7 @@
7474
del_dir_nif/1, get_device_cwd_nif/1, set_cwd_nif/1, get_cwd_nif/0,
7575
ipread_s32bu_p32bu_nif/3, read_file_nif/1,
7676
get_handle_nif/1, delayed_close_nif/1, altname_nif/1,
77-
file_desc_to_ref_nif/1, copy_file_nif/2]).
77+
file_desc_to_ref_nif/1, copy_file_nif/2, copy_range_nif/3]).
7878

7979
-type prim_file_name() :: string() | unicode:unicode_binary().
8080
-type prim_file_name_error() :: 'error' | 'ignore' | 'warning'.
@@ -127,10 +127,13 @@ copy(#file_descriptor{module = ?MODULE} = Source,
127127
%% XXX Should be moved down to the driver for optimization.
128128
file:copy_opened(Source, Dest, Length).
129129

130-
%% Returns {error, Reason} | {ok, BytesCopied}
130+
%% Returns {error, Reason} | ok
131131
%% Using unix syscall copy_file_range(2)
132132
copy_file(Source, Destination) ->
133133
copy_file_nif(encode_path(Source), encode_path(Destination)).
134+
copy_range(Source, Destination, Length)
135+
when is_integer(Length), Length >= 0 ->
136+
copy_range_nif(encode_path(Source), encode_path(Destination), Length).
134137

135138
open(Name, Modes) ->
136139
%% The try/catch pattern seen here is used throughout the file to adhere to
@@ -537,6 +540,8 @@ read_handle_info_nif(_FileRef) ->
537540
erlang:nif_error(undef).
538541
copy_file_nif(_Name, _Dest) ->
539542
erlang:nif_error(undef).
543+
copy_range_nif(_Name, _Dest, _Length) ->
544+
erlang:nif_error(undef).
540545

541546
%%
542547
%% Quality-of-life helpers

0 commit comments

Comments
 (0)