Skip to content

Commit de05686

Browse files
jpnurmiclaude
andauthored
feat(path): add sentry__path_copy for large attachments (#1680)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 74c1028 commit de05686

6 files changed

Lines changed: 140 additions & 0 deletions

File tree

CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,15 @@ endif()
422422
include(CheckTypeSize)
423423
check_type_size("long" CMAKE_SIZEOF_LONG)
424424

425+
if(LINUX)
426+
include(CheckSymbolExists)
427+
list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE)
428+
check_symbol_exists(copy_file_range "unistd.h" HAVE_COPY_FILE_RANGE)
429+
if(HAVE_COPY_FILE_RANGE)
430+
target_compile_definitions(sentry PRIVATE SENTRY_HAVE_COPY_FILE_RANGE)
431+
endif()
432+
endif()
433+
425434
# https://gitlab.kitware.com/cmake/cmake/issues/18393
426435
if(SENTRY_BUILD_SHARED_LIBS)
427436
if(APPLE)

src/path/sentry_path_unix.c

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <unistd.h>
2222

2323
#ifdef SENTRY_PLATFORM_DARWIN
24+
# include <copyfile.h>
2425
# include <mach-o/dyld.h>
2526
#endif
2627

@@ -333,6 +334,71 @@ sentry__path_rename(const sentry_path_t *src, const sentry_path_t *dst)
333334
return status == 0 ? 0 : 1;
334335
}
335336

337+
int
338+
sentry__path_copy(const sentry_path_t *src, const sentry_path_t *dst)
339+
{
340+
#ifdef SENTRY_PLATFORM_DARWIN
341+
return copyfile(src->path, dst->path, NULL, COPYFILE_DATA) == 0 ? 0 : 1;
342+
#else
343+
int src_fd = open(src->path, O_RDONLY);
344+
if (src_fd < 0) {
345+
return 1;
346+
}
347+
int dst_fd = open(dst->path, O_WRONLY | O_CREAT | O_TRUNC,
348+
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
349+
if (dst_fd < 0) {
350+
close(src_fd);
351+
return 1;
352+
}
353+
354+
int rv = 0;
355+
356+
# ifdef SENTRY_HAVE_COPY_FILE_RANGE
357+
while (true) {
358+
ssize_t n
359+
= copy_file_range(src_fd, NULL, dst_fd, NULL, SIZE_MAX / 2, 0);
360+
if (n > 0) {
361+
continue;
362+
} else if (n == 0) {
363+
goto done;
364+
} else if (errno == EAGAIN || errno == EINTR) {
365+
continue;
366+
} else if (errno == ENOSYS || errno == EXDEV || errno == EOPNOTSUPP
367+
|| errno == EINVAL) {
368+
break;
369+
} else {
370+
rv = 1;
371+
goto done;
372+
}
373+
}
374+
# endif
375+
376+
{
377+
char buf[16384];
378+
while (true) {
379+
ssize_t n = read(src_fd, buf, sizeof(buf));
380+
if (n < 0 && (errno == EAGAIN || errno == EINTR)) {
381+
continue;
382+
} else if (n <= 0) {
383+
rv = n < 0;
384+
break;
385+
}
386+
if (write_loop(dst_fd, buf, (size_t)n) != 0) {
387+
rv = 1;
388+
break;
389+
}
390+
}
391+
}
392+
393+
# ifdef SENTRY_HAVE_COPY_FILE_RANGE
394+
done:
395+
# endif
396+
close(src_fd);
397+
close(dst_fd);
398+
return rv;
399+
#endif
400+
}
401+
336402
int
337403
sentry__path_create_dir_all(const sentry_path_t *path)
338404
{

src/path/sentry_path_windows.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,15 @@ sentry__path_rename(const sentry_path_t *src, const sentry_path_t *dst)
524524
return MoveFileExW(src_w, dst_w, MOVEFILE_REPLACE_EXISTING) ? 0 : 1;
525525
}
526526

527+
int
528+
sentry__path_copy(const sentry_path_t *src, const sentry_path_t *dst)
529+
{
530+
if (!src->path_w || !dst->path_w) {
531+
return 1;
532+
}
533+
return CopyFileW(src->path_w, dst->path_w, FALSE) ? 0 : 1;
534+
}
535+
527536
int
528537
sentry__path_create_dir_all(const sentry_path_t *path)
529538
{

src/sentry_path.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,12 @@ int sentry__path_remove_all(const sentry_path_t *path);
167167
*/
168168
int sentry__path_rename(const sentry_path_t *src, const sentry_path_t *dst);
169169

170+
/**
171+
* Copy the file from `src` to `dst`.
172+
* Returns 0 on success.
173+
*/
174+
int sentry__path_copy(const sentry_path_t *src, const sentry_path_t *dst);
175+
170176
/**
171177
* This will create the directory referred to by `path`, and any non-existing
172178
* parent directory.

tests/unit/test_path.c

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,3 +393,52 @@ SENTRY_TEST(path_basename)
393393
TEST_CHECK(!sentry__path_basename(path, ".tar.gz"));
394394
sentry__path_free(path);
395395
}
396+
397+
SENTRY_TEST(path_copy)
398+
{
399+
#if defined(SENTRY_PLATFORM_NX)
400+
SKIP_TEST();
401+
#endif
402+
sentry_path_t *src
403+
= sentry__path_from_str(SENTRY_TEST_PATH_PREFIX ".copy-src");
404+
TEST_ASSERT(!!src);
405+
sentry_path_t *dst
406+
= sentry__path_from_str(SENTRY_TEST_PATH_PREFIX ".copy-dst");
407+
TEST_ASSERT(!!dst);
408+
409+
// cleanup
410+
sentry__path_remove_all(src);
411+
sentry__path_remove_all(dst);
412+
413+
// copy file with content preserved
414+
sentry__path_write_buffer(src, "hello", 5);
415+
TEST_CHECK(sentry__path_copy(src, dst) == 0);
416+
TEST_CHECK(sentry__path_is_file(src));
417+
TEST_CHECK(sentry__path_is_file(dst));
418+
size_t len = 0;
419+
char *buf = sentry__path_read_to_buffer(dst, &len);
420+
TEST_ASSERT(!!buf);
421+
TEST_CHECK(len == 5);
422+
TEST_CHECK(memcmp(buf, "hello", 5) == 0);
423+
sentry_free(buf);
424+
sentry__path_remove(dst);
425+
426+
// overwrite existing dst
427+
sentry__path_write_buffer(dst, "dst-data", 8);
428+
TEST_CHECK(sentry__path_copy(src, dst) == 0);
429+
TEST_CHECK(sentry__path_is_file(src));
430+
buf = sentry__path_read_to_buffer(dst, &len);
431+
TEST_ASSERT(!!buf);
432+
TEST_CHECK(len == 5);
433+
TEST_CHECK(memcmp(buf, "hello", 5) == 0);
434+
sentry_free(buf);
435+
sentry__path_remove(dst);
436+
437+
// copy nonexistent src
438+
sentry__path_remove(src);
439+
TEST_CHECK(sentry__path_copy(src, dst) != 0);
440+
441+
sentry__path_remove_all(dst);
442+
sentry__path_free(dst);
443+
sentry__path_free(src);
444+
}

tests/unit/tests.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ XX(overflow_spans)
189189
XX(page_allocator)
190190
XX(path_basename)
191191
XX(path_basics)
192+
XX(path_copy)
192193
XX(path_current_exe)
193194
XX(path_directory)
194195
XX(path_from_str_n_wo_null_termination)

0 commit comments

Comments
 (0)