Remove a file or directory tree recursively. Cross-platform rm -rf. */
| 111 | |
| 112 | /* Remove a file or directory tree recursively. Cross-platform rm -rf. */ |
| 113 | static inline int th_rmtree(const char *path) { |
| 114 | /* The platform wrappers preserve UTF-8 and add the extended-length prefix |
| 115 | * on Windows; narrow CRT stat() silently treats paths beyond MAX_PATH as |
| 116 | * absent and leaves their parents nonempty. */ |
| 117 | if (!cbm_file_exists(path)) { |
| 118 | return 0; /* doesn't exist — success */ |
| 119 | } |
| 120 | |
| 121 | if (!cbm_is_dir(path)) { |
| 122 | return th_unlink_force(path); |
| 123 | } |
| 124 | |
| 125 | /* Directory — recurse into children */ |
| 126 | cbm_dir_t *d = cbm_opendir(path); |
| 127 | if (!d) { |
| 128 | return -1; |
| 129 | } |
| 130 | |
| 131 | cbm_dirent_t *entry; |
| 132 | int rc = 0; |
| 133 | while ((entry = cbm_readdir(d)) != NULL) { |
| 134 | if (strcmp(entry->name, ".") == 0 || strcmp(entry->name, "..") == 0) { |
| 135 | continue; |
| 136 | } |
| 137 | char child[1024]; |
| 138 | snprintf(child, sizeof(child), "%s/%s", path, entry->name); |
| 139 | if (entry->is_dir) { |
| 140 | if (th_rmtree(child) != 0) { |
| 141 | rc = -1; |
| 142 | } |
| 143 | } else { |
| 144 | if (th_unlink_force(child) != 0) { |
| 145 | rc = -1; |
| 146 | } |
| 147 | } |
| 148 | } |
| 149 | cbm_closedir(d); |
| 150 | if (cbm_rmdir(path) != 0) { |
| 151 | rc = -1; |
| 152 | } |
| 153 | return rc; |
| 154 | } |
| 155 | |
| 156 | /* ── Temp directory creation ──────────────────────────────────── */ |
| 157 |
no test coverage detected