gv_fopen.c 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. /// @file
  2. /// @brief C implementation of `gv_fopen`
  3. #include <assert.h>
  4. #include <errno.h>
  5. #include <fcntl.h>
  6. #include <stdbool.h>
  7. #include <stddef.h>
  8. #include <stdio.h>
  9. #include <string.h>
  10. #include <unistd.h>
  11. #include <util/gv_fopen.h>
  12. #include <util/streq.h>
  13. /// platform abstraction over `fopen`
  14. static FILE *fopen_(const char *filename, const char *mode) {
  15. assert(filename != NULL);
  16. assert(mode != NULL);
  17. #ifdef _MSC_VER
  18. {
  19. FILE *result = NULL;
  20. if (fopen_s(&result, filename, mode) != 0) {
  21. return NULL;
  22. }
  23. return result;
  24. }
  25. #else
  26. return fopen(filename, mode);
  27. #endif
  28. }
  29. FILE *gv_fopen(const char *filename, const char *mode) {
  30. assert(filename != NULL);
  31. assert(mode != NULL);
  32. assert(streq(mode, "r") || streq(mode, "rb") || streq(mode, "w") ||
  33. streq(mode, "wb"));
  34. // does `fopen` support 'e' for setting close-on-exec?
  35. bool have_e = false;
  36. // does `fopen` support 'N' for setting close-on-exec?
  37. bool have_n = false;
  38. #if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__linux__) || \
  39. defined(__NetBSD__) || defined(__OpenBSD__)
  40. have_e = true;
  41. #endif
  42. #ifdef _MSC_VER
  43. have_n = true;
  44. #endif
  45. // Attempt 1: `fopen`+'e'
  46. if (have_e) {
  47. char mode_with_cloexec[4] = {0};
  48. snprintf(mode_with_cloexec, sizeof(mode_with_cloexec), "%se", mode);
  49. return fopen_(filename, mode_with_cloexec);
  50. }
  51. // Attempt 2: `fopen`+'N'
  52. if (have_n) {
  53. char mode_with_cloexec[4] = {0};
  54. snprintf(mode_with_cloexec, sizeof(mode_with_cloexec), "%sN", mode);
  55. return fopen_(filename, mode_with_cloexec);
  56. }
  57. // Attempt 3: `open`+`O_CLOEXEC`; `fdopen` (e.g. for macOS)
  58. #ifdef O_CLOEXEC
  59. {
  60. int flags = mode[0] == 'w' ? (O_WRONLY | O_CREAT | O_TRUNC) : O_RDONLY;
  61. #ifdef O_BINARY
  62. if (strchr(mode, 'b') != NULL) {
  63. flags |= O_BINARY;
  64. }
  65. #endif
  66. #ifdef _O_BINARY
  67. if (strchr(mode, 'b') != NULL) {
  68. flags |= _O_BINARY;
  69. }
  70. #endif
  71. const int fd = open(filename, flags | O_CLOEXEC, 0666);
  72. if (fd < 0) {
  73. return NULL;
  74. }
  75. FILE *f = fdopen(fd, mode);
  76. if (f == NULL) {
  77. const int err = errno;
  78. (void)close(fd);
  79. errno = err;
  80. }
  81. return f;
  82. }
  83. #endif
  84. // Attempt 4: `fopen`; `fcntl`+`FD_CLOEXEC`
  85. // We are on a platform that supported none of the above, so we need to
  86. // fallback on an unmodified `fopen`. The platform either does not have a
  87. // concept of close-on-exec or does not support a race-free way of achieving
  88. // it.
  89. FILE *f = fopen_(filename, mode);
  90. if (f == NULL) {
  91. return NULL;
  92. }
  93. #ifdef FD_CLOEXEC
  94. {
  95. const int fd = fileno(f);
  96. const int flags = fcntl(fd, F_GETFD);
  97. if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) {
  98. const int err = errno;
  99. (void)fclose(f);
  100. errno = err;
  101. return NULL;
  102. }
  103. }
  104. #endif
  105. return f;
  106. }