vfs.c
1 // Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include <stdlib.h> 16 #include <string.h> 17 #include <assert.h> 18 #include <sys/errno.h> 19 #include <sys/fcntl.h> 20 #include <sys/ioctl.h> 21 #include <sys/unistd.h> 22 #include <sys/lock.h> 23 #include <sys/param.h> 24 #include <dirent.h> 25 #include "freertos/FreeRTOS.h" 26 #include "freertos/semphr.h" 27 #include "esp_vfs.h" 28 #include "sdkconfig.h" 29 30 #ifdef CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT 31 #define LOG_LOCAL_LEVEL ESP_LOG_NONE 32 #endif //CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT 33 #include "esp_log.h" 34 35 static const char *TAG = "vfs"; 36 37 #define VFS_MAX_COUNT 8 /* max number of VFS entries (registered filesystems) */ 38 #define LEN_PATH_PREFIX_IGNORED SIZE_MAX /* special length value for VFS which is never recognised by open() */ 39 #define FD_TABLE_ENTRY_UNUSED (fd_table_t) { .permanent = false, .vfs_index = -1, .local_fd = -1 } 40 41 typedef uint8_t local_fd_t; 42 _Static_assert((1 << (sizeof(local_fd_t)*8)) >= MAX_FDS, "file descriptor type too small"); 43 44 typedef int8_t vfs_index_t; 45 _Static_assert((1 << (sizeof(vfs_index_t)*8)) >= VFS_MAX_COUNT, "VFS index type too small"); 46 _Static_assert(((vfs_index_t) -1) < 0, "vfs_index_t must be a signed type"); 47 48 typedef struct { 49 bool permanent; 50 vfs_index_t vfs_index; 51 local_fd_t local_fd; 52 } fd_table_t; 53 54 typedef struct vfs_entry_ { 55 esp_vfs_t vfs; // contains pointers to VFS functions 56 char path_prefix[ESP_VFS_PATH_MAX]; // path prefix mapped to this VFS 57 size_t path_prefix_len; // micro-optimization to avoid doing extra strlen 58 void* ctx; // optional pointer which can be passed to VFS 59 int offset; // index of this structure in s_vfs array 60 } vfs_entry_t; 61 62 typedef struct { 63 bool isset; // none or at least one bit is set in the following 3 fd sets 64 fd_set readfds; 65 fd_set writefds; 66 fd_set errorfds; 67 } fds_triple_t; 68 69 static vfs_entry_t* s_vfs[VFS_MAX_COUNT] = { 0 }; 70 static size_t s_vfs_count = 0; 71 72 static fd_table_t s_fd_table[MAX_FDS] = { [0 ... MAX_FDS-1] = FD_TABLE_ENTRY_UNUSED }; 73 static _lock_t s_fd_table_lock; 74 75 static esp_err_t esp_vfs_register_common(const char* base_path, size_t len, const esp_vfs_t* vfs, void* ctx, int *vfs_index) 76 { 77 if (len != LEN_PATH_PREFIX_IGNORED) { 78 /* empty prefix is allowed, "/" is not allowed */ 79 if ((len == 1) || (len > ESP_VFS_PATH_MAX)) { 80 return ESP_ERR_INVALID_ARG; 81 } 82 /* prefix has to start with "/" and not end with "/" */ 83 if (len >= 2 && ((base_path[0] != '/') || (base_path[len - 1] == '/'))) { 84 return ESP_ERR_INVALID_ARG; 85 } 86 } 87 vfs_entry_t *entry = (vfs_entry_t*) malloc(sizeof(vfs_entry_t)); 88 if (entry == NULL) { 89 return ESP_ERR_NO_MEM; 90 } 91 size_t index; 92 for (index = 0; index < s_vfs_count; ++index) { 93 if (s_vfs[index] == NULL) { 94 break; 95 } 96 } 97 if (index == s_vfs_count) { 98 if (s_vfs_count >= VFS_MAX_COUNT) { 99 free(entry); 100 return ESP_ERR_NO_MEM; 101 } 102 ++s_vfs_count; 103 } 104 s_vfs[index] = entry; 105 if (len != LEN_PATH_PREFIX_IGNORED) { 106 strcpy(entry->path_prefix, base_path); // we have already verified argument length 107 } else { 108 bzero(entry->path_prefix, sizeof(entry->path_prefix)); 109 } 110 memcpy(&entry->vfs, vfs, sizeof(esp_vfs_t)); 111 entry->path_prefix_len = len; 112 entry->ctx = ctx; 113 entry->offset = index; 114 115 if (vfs_index) { 116 *vfs_index = index; 117 } 118 119 return ESP_OK; 120 } 121 122 esp_err_t esp_vfs_register(const char* base_path, const esp_vfs_t* vfs, void* ctx) 123 { 124 return esp_vfs_register_common(base_path, strlen(base_path), vfs, ctx, NULL); 125 } 126 127 esp_err_t esp_vfs_register_fd_range(const esp_vfs_t *vfs, void *ctx, int min_fd, int max_fd) 128 { 129 if (min_fd < 0 || max_fd < 0 || min_fd > MAX_FDS || max_fd > MAX_FDS || min_fd > max_fd) { 130 ESP_LOGD(TAG, "Invalid arguments: esp_vfs_register_fd_range(0x%x, 0x%x, %d, %d)", (int) vfs, (int) ctx, min_fd, max_fd); 131 return ESP_ERR_INVALID_ARG; 132 } 133 134 int index = -1; 135 esp_err_t ret = esp_vfs_register_common("", LEN_PATH_PREFIX_IGNORED, vfs, ctx, &index); 136 137 if (ret == ESP_OK) { 138 _lock_acquire(&s_fd_table_lock); 139 for (int i = min_fd; i < max_fd; ++i) { 140 if (s_fd_table[i].vfs_index != -1) { 141 free(s_vfs[i]); 142 s_vfs[i] = NULL; 143 for (int j = min_fd; j < i; ++j) { 144 if (s_fd_table[j].vfs_index == index) { 145 s_fd_table[j] = FD_TABLE_ENTRY_UNUSED; 146 } 147 } 148 _lock_release(&s_fd_table_lock); 149 ESP_LOGD(TAG, "esp_vfs_register_fd_range cannot set fd %d (used by other VFS)", i); 150 return ESP_ERR_INVALID_ARG; 151 } 152 s_fd_table[i].permanent = true; 153 s_fd_table[i].vfs_index = index; 154 s_fd_table[i].local_fd = i; 155 } 156 _lock_release(&s_fd_table_lock); 157 } 158 159 ESP_LOGD(TAG, "esp_vfs_register_fd_range is successful for range <%d; %d) and VFS ID %d", min_fd, max_fd, index); 160 161 return ret; 162 } 163 164 esp_err_t esp_vfs_register_with_id(const esp_vfs_t *vfs, void *ctx, esp_vfs_id_t *vfs_id) 165 { 166 if (vfs_id == NULL) { 167 return ESP_ERR_INVALID_ARG; 168 } 169 170 *vfs_id = -1; 171 return esp_vfs_register_common("", LEN_PATH_PREFIX_IGNORED, vfs, ctx, vfs_id); 172 } 173 174 esp_err_t esp_vfs_unregister(const char* base_path) 175 { 176 const size_t base_path_len = strlen(base_path); 177 for (size_t i = 0; i < s_vfs_count; ++i) { 178 vfs_entry_t* vfs = s_vfs[i]; 179 if (vfs == NULL) { 180 continue; 181 } 182 if (base_path_len == vfs->path_prefix_len && 183 memcmp(base_path, vfs->path_prefix, vfs->path_prefix_len) == 0) { 184 free(vfs); 185 s_vfs[i] = NULL; 186 187 _lock_acquire(&s_fd_table_lock); 188 // Delete all references from the FD lookup-table 189 for (int j = 0; j < MAX_FDS; ++j) { 190 if (s_fd_table[j].vfs_index == i) { 191 s_fd_table[j] = FD_TABLE_ENTRY_UNUSED; 192 } 193 } 194 _lock_release(&s_fd_table_lock); 195 196 return ESP_OK; 197 } 198 } 199 return ESP_ERR_INVALID_STATE; 200 } 201 202 esp_err_t esp_vfs_register_fd(esp_vfs_id_t vfs_id, int *fd) 203 { 204 if (vfs_id < 0 || vfs_id >= s_vfs_count || fd == NULL) { 205 ESP_LOGD(TAG, "Invalid arguments for esp_vfs_register_fd(%d, 0x%x)", vfs_id, (int) fd); 206 return ESP_ERR_INVALID_ARG; 207 } 208 209 esp_err_t ret = ESP_ERR_NO_MEM; 210 _lock_acquire(&s_fd_table_lock); 211 for (int i = 0; i < MAX_FDS; ++i) { 212 if (s_fd_table[i].vfs_index == -1) { 213 s_fd_table[i].permanent = true; 214 s_fd_table[i].vfs_index = vfs_id; 215 s_fd_table[i].local_fd = i; 216 *fd = i; 217 ret = ESP_OK; 218 break; 219 } 220 } 221 _lock_release(&s_fd_table_lock); 222 223 ESP_LOGD(TAG, "esp_vfs_register_fd(%d, 0x%x) finished with %s", vfs_id, (int) fd, esp_err_to_name(ret)); 224 225 return ret; 226 } 227 228 esp_err_t esp_vfs_unregister_fd(esp_vfs_id_t vfs_id, int fd) 229 { 230 esp_err_t ret = ESP_ERR_INVALID_ARG; 231 232 if (vfs_id < 0 || vfs_id >= s_vfs_count || fd < 0 || fd >= MAX_FDS) { 233 ESP_LOGD(TAG, "Invalid arguments for esp_vfs_unregister_fd(%d, %d)", vfs_id, fd); 234 return ret; 235 } 236 237 _lock_acquire(&s_fd_table_lock); 238 fd_table_t *item = s_fd_table + fd; 239 if (item->permanent == true && item->vfs_index == vfs_id && item->local_fd == fd) { 240 *item = FD_TABLE_ENTRY_UNUSED; 241 ret = ESP_OK; 242 } 243 _lock_release(&s_fd_table_lock); 244 245 ESP_LOGD(TAG, "esp_vfs_unregister_fd(%d, %d) finished with %s", vfs_id, fd, esp_err_to_name(ret)); 246 247 return ret; 248 } 249 250 static inline const vfs_entry_t *get_vfs_for_index(int index) 251 { 252 if (index < 0 || index >= s_vfs_count) { 253 return NULL; 254 } else { 255 return s_vfs[index]; 256 } 257 } 258 259 static inline bool fd_valid(int fd) 260 { 261 return (fd < MAX_FDS) && (fd >= 0); 262 } 263 264 static const vfs_entry_t *get_vfs_for_fd(int fd) 265 { 266 const vfs_entry_t *vfs = NULL; 267 if (fd_valid(fd)) { 268 const int index = s_fd_table[fd].vfs_index; // single read -> no locking is required 269 vfs = get_vfs_for_index(index); 270 } 271 return vfs; 272 } 273 274 static inline int get_local_fd(const vfs_entry_t *vfs, int fd) 275 { 276 int local_fd = -1; 277 278 if (vfs && fd_valid(fd)) { 279 local_fd = s_fd_table[fd].local_fd; // single read -> no locking is required 280 } 281 282 return local_fd; 283 } 284 285 static const char* translate_path(const vfs_entry_t* vfs, const char* src_path) 286 { 287 assert(strncmp(src_path, vfs->path_prefix, vfs->path_prefix_len) == 0); 288 if (strlen(src_path) == vfs->path_prefix_len) { 289 // special case when src_path matches the path prefix exactly 290 return "/"; 291 } 292 return src_path + vfs->path_prefix_len; 293 } 294 295 static const vfs_entry_t* get_vfs_for_path(const char* path) 296 { 297 const vfs_entry_t* best_match = NULL; 298 ssize_t best_match_prefix_len = -1; 299 size_t len = strlen(path); 300 for (size_t i = 0; i < s_vfs_count; ++i) { 301 const vfs_entry_t* vfs = s_vfs[i]; 302 if (!vfs || vfs->path_prefix_len == LEN_PATH_PREFIX_IGNORED) { 303 continue; 304 } 305 // match path prefix 306 if (len < vfs->path_prefix_len || 307 memcmp(path, vfs->path_prefix, vfs->path_prefix_len) != 0) { 308 continue; 309 } 310 // this is the default VFS and we don't have a better match yet. 311 if (vfs->path_prefix_len == 0 && !best_match) { 312 best_match = vfs; 313 continue; 314 } 315 // if path is not equal to the prefix, expect to see a path separator 316 // i.e. don't match "/data" prefix for "/data1/foo.txt" path 317 if (len > vfs->path_prefix_len && 318 path[vfs->path_prefix_len] != '/') { 319 continue; 320 } 321 // Out of all matching path prefixes, select the longest one; 322 // i.e. if "/dev" and "/dev/uart" both match, for "/dev/uart/1" path, 323 // choose "/dev/uart", 324 // This causes all s_vfs_count VFS entries to be scanned when opening 325 // a file by name. This can be optimized by introducing a table for 326 // FS search order, sorted so that longer prefixes are checked first. 327 if (best_match_prefix_len < (ssize_t) vfs->path_prefix_len) { 328 best_match_prefix_len = (ssize_t) vfs->path_prefix_len; 329 best_match = vfs; 330 } 331 } 332 return best_match; 333 } 334 335 /* 336 * Using huge multi-line macros is never nice, but in this case 337 * the only alternative is to repeat this chunk of code (with different function names) 338 * for each syscall being implemented. Given that this define is contained within a single 339 * file, this looks like a good tradeoff. 340 * 341 * First we check if syscall is implemented by VFS (corresponding member is not NULL), 342 * then call the right flavor of the method (e.g. open or open_p) depending on 343 * ESP_VFS_FLAG_CONTEXT_PTR flag. If ESP_VFS_FLAG_CONTEXT_PTR is set, context is passed 344 * in as first argument and _p variant is used for the call. 345 * It is enough to check just one of them for NULL, as both variants are part of a union. 346 */ 347 #define CHECK_AND_CALL(ret, r, pvfs, func, ...) \ 348 if (pvfs->vfs.func == NULL) { \ 349 __errno_r(r) = ENOSYS; \ 350 return -1; \ 351 } \ 352 if (pvfs->vfs.flags & ESP_VFS_FLAG_CONTEXT_PTR) { \ 353 ret = (*pvfs->vfs.func ## _p)(pvfs->ctx, __VA_ARGS__); \ 354 } else { \ 355 ret = (*pvfs->vfs.func)(__VA_ARGS__);\ 356 } 357 358 359 #define CHECK_AND_CALLV(r, pvfs, func, ...) \ 360 if (pvfs->vfs.func == NULL) { \ 361 __errno_r(r) = ENOSYS; \ 362 return; \ 363 } \ 364 if (pvfs->vfs.flags & ESP_VFS_FLAG_CONTEXT_PTR) { \ 365 (*pvfs->vfs.func ## _p)(pvfs->ctx, __VA_ARGS__); \ 366 } else { \ 367 (*pvfs->vfs.func)(__VA_ARGS__);\ 368 } 369 370 #define CHECK_AND_CALLP(ret, r, pvfs, func, ...) \ 371 if (pvfs->vfs.func == NULL) { \ 372 __errno_r(r) = ENOSYS; \ 373 return NULL; \ 374 } \ 375 if (pvfs->vfs.flags & ESP_VFS_FLAG_CONTEXT_PTR) { \ 376 ret = (*pvfs->vfs.func ## _p)(pvfs->ctx, __VA_ARGS__); \ 377 } else { \ 378 ret = (*pvfs->vfs.func)(__VA_ARGS__);\ 379 } 380 381 int esp_vfs_open(struct _reent *r, const char * path, int flags, int mode) 382 { 383 const vfs_entry_t *vfs = get_vfs_for_path(path); 384 if (vfs == NULL) { 385 __errno_r(r) = ENOENT; 386 return -1; 387 } 388 const char *path_within_vfs = translate_path(vfs, path); 389 int fd_within_vfs; 390 CHECK_AND_CALL(fd_within_vfs, r, vfs, open, path_within_vfs, flags, mode); 391 if (fd_within_vfs >= 0) { 392 _lock_acquire(&s_fd_table_lock); 393 for (int i = 0; i < MAX_FDS; ++i) { 394 if (s_fd_table[i].vfs_index == -1) { 395 s_fd_table[i].permanent = false; 396 s_fd_table[i].vfs_index = vfs->offset; 397 s_fd_table[i].local_fd = fd_within_vfs; 398 _lock_release(&s_fd_table_lock); 399 return i; 400 } 401 } 402 _lock_release(&s_fd_table_lock); 403 int ret; 404 CHECK_AND_CALL(ret, r, vfs, close, fd_within_vfs); 405 (void) ret; // remove "set but not used" warning 406 __errno_r(r) = ENOMEM; 407 return -1; 408 } 409 __errno_r(r) = ENOENT; 410 return -1; 411 } 412 413 ssize_t esp_vfs_write(struct _reent *r, int fd, const void * data, size_t size) 414 { 415 const vfs_entry_t* vfs = get_vfs_for_fd(fd); 416 const int local_fd = get_local_fd(vfs, fd); 417 if (vfs == NULL || local_fd < 0) { 418 __errno_r(r) = EBADF; 419 return -1; 420 } 421 ssize_t ret; 422 CHECK_AND_CALL(ret, r, vfs, write, local_fd, data, size); 423 return ret; 424 } 425 426 off_t esp_vfs_lseek(struct _reent *r, int fd, off_t size, int mode) 427 { 428 const vfs_entry_t* vfs = get_vfs_for_fd(fd); 429 const int local_fd = get_local_fd(vfs, fd); 430 if (vfs == NULL || local_fd < 0) { 431 __errno_r(r) = EBADF; 432 return -1; 433 } 434 off_t ret; 435 CHECK_AND_CALL(ret, r, vfs, lseek, local_fd, size, mode); 436 return ret; 437 } 438 439 ssize_t esp_vfs_read(struct _reent *r, int fd, void * dst, size_t size) 440 { 441 const vfs_entry_t* vfs = get_vfs_for_fd(fd); 442 const int local_fd = get_local_fd(vfs, fd); 443 if (vfs == NULL || local_fd < 0) { 444 __errno_r(r) = EBADF; 445 return -1; 446 } 447 ssize_t ret; 448 CHECK_AND_CALL(ret, r, vfs, read, local_fd, dst, size); 449 return ret; 450 } 451 452 ssize_t esp_vfs_pread(int fd, void *dst, size_t size, off_t offset) 453 { 454 struct _reent *r = __getreent(); 455 const vfs_entry_t* vfs = get_vfs_for_fd(fd); 456 const int local_fd = get_local_fd(vfs, fd); 457 if (vfs == NULL || local_fd < 0) { 458 __errno_r(r) = EBADF; 459 return -1; 460 } 461 ssize_t ret; 462 CHECK_AND_CALL(ret, r, vfs, pread, local_fd, dst, size, offset); 463 return ret; 464 } 465 466 ssize_t esp_vfs_pwrite(int fd, const void *src, size_t size, off_t offset) 467 { 468 struct _reent *r = __getreent(); 469 const vfs_entry_t* vfs = get_vfs_for_fd(fd); 470 const int local_fd = get_local_fd(vfs, fd); 471 if (vfs == NULL || local_fd < 0) { 472 __errno_r(r) = EBADF; 473 return -1; 474 } 475 ssize_t ret; 476 CHECK_AND_CALL(ret, r, vfs, pwrite, local_fd, src, size, offset); 477 return ret; 478 } 479 480 int esp_vfs_close(struct _reent *r, int fd) 481 { 482 const vfs_entry_t* vfs = get_vfs_for_fd(fd); 483 const int local_fd = get_local_fd(vfs, fd); 484 if (vfs == NULL || local_fd < 0) { 485 __errno_r(r) = EBADF; 486 return -1; 487 } 488 int ret; 489 CHECK_AND_CALL(ret, r, vfs, close, local_fd); 490 491 _lock_acquire(&s_fd_table_lock); 492 if (!s_fd_table[fd].permanent) { 493 s_fd_table[fd] = FD_TABLE_ENTRY_UNUSED; 494 } 495 _lock_release(&s_fd_table_lock); 496 return ret; 497 } 498 499 int esp_vfs_fstat(struct _reent *r, int fd, struct stat * st) 500 { 501 const vfs_entry_t* vfs = get_vfs_for_fd(fd); 502 const int local_fd = get_local_fd(vfs, fd); 503 if (vfs == NULL || local_fd < 0) { 504 __errno_r(r) = EBADF; 505 return -1; 506 } 507 int ret; 508 CHECK_AND_CALL(ret, r, vfs, fstat, local_fd, st); 509 return ret; 510 } 511 512 int esp_vfs_fcntl_r(struct _reent *r, int fd, int cmd, int arg) 513 { 514 const vfs_entry_t* vfs = get_vfs_for_fd(fd); 515 const int local_fd = get_local_fd(vfs, fd); 516 if (vfs == NULL || local_fd < 0) { 517 __errno_r(r) = EBADF; 518 return -1; 519 } 520 int ret; 521 CHECK_AND_CALL(ret, r, vfs, fcntl, local_fd, cmd, arg); 522 return ret; 523 } 524 525 int esp_vfs_ioctl(int fd, int cmd, ...) 526 { 527 const vfs_entry_t* vfs = get_vfs_for_fd(fd); 528 const int local_fd = get_local_fd(vfs, fd); 529 struct _reent* r = __getreent(); 530 if (vfs == NULL || local_fd < 0) { 531 __errno_r(r) = EBADF; 532 return -1; 533 } 534 int ret; 535 va_list args; 536 va_start(args, cmd); 537 CHECK_AND_CALL(ret, r, vfs, ioctl, local_fd, cmd, args); 538 va_end(args); 539 return ret; 540 } 541 542 int esp_vfs_fsync(int fd) 543 { 544 const vfs_entry_t* vfs = get_vfs_for_fd(fd); 545 const int local_fd = get_local_fd(vfs, fd); 546 struct _reent* r = __getreent(); 547 if (vfs == NULL || local_fd < 0) { 548 __errno_r(r) = EBADF; 549 return -1; 550 } 551 int ret; 552 CHECK_AND_CALL(ret, r, vfs, fsync, local_fd); 553 return ret; 554 } 555 556 #ifdef CONFIG_VFS_SUPPORT_DIR 557 558 int esp_vfs_stat(struct _reent *r, const char * path, struct stat * st) 559 { 560 const vfs_entry_t* vfs = get_vfs_for_path(path); 561 if (vfs == NULL) { 562 __errno_r(r) = ENOENT; 563 return -1; 564 } 565 const char* path_within_vfs = translate_path(vfs, path); 566 int ret; 567 CHECK_AND_CALL(ret, r, vfs, stat, path_within_vfs, st); 568 return ret; 569 } 570 571 int esp_vfs_utime(const char *path, const struct utimbuf *times) 572 { 573 int ret; 574 const vfs_entry_t* vfs = get_vfs_for_path(path); 575 struct _reent* r = __getreent(); 576 if (vfs == NULL) { 577 __errno_r(r) = ENOENT; 578 return -1; 579 } 580 const char* path_within_vfs = translate_path(vfs, path); 581 CHECK_AND_CALL(ret, r, vfs, utime, path_within_vfs, times); 582 return ret; 583 } 584 585 int esp_vfs_link(struct _reent *r, const char* n1, const char* n2) 586 { 587 const vfs_entry_t* vfs = get_vfs_for_path(n1); 588 if (vfs == NULL) { 589 __errno_r(r) = ENOENT; 590 return -1; 591 } 592 const vfs_entry_t* vfs2 = get_vfs_for_path(n2); 593 if (vfs != vfs2) { 594 __errno_r(r) = EXDEV; 595 return -1; 596 } 597 const char* path1_within_vfs = translate_path(vfs, n1); 598 const char* path2_within_vfs = translate_path(vfs, n2); 599 int ret; 600 CHECK_AND_CALL(ret, r, vfs, link, path1_within_vfs, path2_within_vfs); 601 return ret; 602 } 603 604 int esp_vfs_unlink(struct _reent *r, const char *path) 605 { 606 const vfs_entry_t* vfs = get_vfs_for_path(path); 607 if (vfs == NULL) { 608 __errno_r(r) = ENOENT; 609 return -1; 610 } 611 const char* path_within_vfs = translate_path(vfs, path); 612 int ret; 613 CHECK_AND_CALL(ret, r, vfs, unlink, path_within_vfs); 614 return ret; 615 } 616 617 int esp_vfs_rename(struct _reent *r, const char *src, const char *dst) 618 { 619 const vfs_entry_t* vfs = get_vfs_for_path(src); 620 if (vfs == NULL) { 621 __errno_r(r) = ENOENT; 622 return -1; 623 } 624 const vfs_entry_t* vfs_dst = get_vfs_for_path(dst); 625 if (vfs != vfs_dst) { 626 __errno_r(r) = EXDEV; 627 return -1; 628 } 629 const char* src_within_vfs = translate_path(vfs, src); 630 const char* dst_within_vfs = translate_path(vfs, dst); 631 int ret; 632 CHECK_AND_CALL(ret, r, vfs, rename, src_within_vfs, dst_within_vfs); 633 return ret; 634 } 635 636 DIR* esp_vfs_opendir(const char* name) 637 { 638 const vfs_entry_t* vfs = get_vfs_for_path(name); 639 struct _reent* r = __getreent(); 640 if (vfs == NULL) { 641 __errno_r(r) = ENOENT; 642 return NULL; 643 } 644 const char* path_within_vfs = translate_path(vfs, name); 645 DIR* ret; 646 CHECK_AND_CALLP(ret, r, vfs, opendir, path_within_vfs); 647 if (ret != NULL) { 648 ret->dd_vfs_idx = vfs->offset; 649 } 650 return ret; 651 } 652 653 struct dirent* esp_vfs_readdir(DIR* pdir) 654 { 655 const vfs_entry_t* vfs = get_vfs_for_index(pdir->dd_vfs_idx); 656 struct _reent* r = __getreent(); 657 if (vfs == NULL) { 658 __errno_r(r) = EBADF; 659 return NULL; 660 } 661 struct dirent* ret; 662 CHECK_AND_CALLP(ret, r, vfs, readdir, pdir); 663 return ret; 664 } 665 666 int esp_vfs_readdir_r(DIR* pdir, struct dirent* entry, struct dirent** out_dirent) 667 { 668 const vfs_entry_t* vfs = get_vfs_for_index(pdir->dd_vfs_idx); 669 struct _reent* r = __getreent(); 670 if (vfs == NULL) { 671 errno = EBADF; 672 return -1; 673 } 674 int ret; 675 CHECK_AND_CALL(ret, r, vfs, readdir_r, pdir, entry, out_dirent); 676 return ret; 677 } 678 679 long esp_vfs_telldir(DIR* pdir) 680 { 681 const vfs_entry_t* vfs = get_vfs_for_index(pdir->dd_vfs_idx); 682 struct _reent* r = __getreent(); 683 if (vfs == NULL) { 684 errno = EBADF; 685 return -1; 686 } 687 long ret; 688 CHECK_AND_CALL(ret, r, vfs, telldir, pdir); 689 return ret; 690 } 691 692 void esp_vfs_seekdir(DIR* pdir, long loc) 693 { 694 const vfs_entry_t* vfs = get_vfs_for_index(pdir->dd_vfs_idx); 695 struct _reent* r = __getreent(); 696 if (vfs == NULL) { 697 errno = EBADF; 698 return; 699 } 700 CHECK_AND_CALLV(r, vfs, seekdir, pdir, loc); 701 } 702 703 void esp_vfs_rewinddir(DIR* pdir) 704 { 705 seekdir(pdir, 0); 706 } 707 708 int esp_vfs_closedir(DIR* pdir) 709 { 710 const vfs_entry_t* vfs = get_vfs_for_index(pdir->dd_vfs_idx); 711 struct _reent* r = __getreent(); 712 if (vfs == NULL) { 713 errno = EBADF; 714 return -1; 715 } 716 int ret; 717 CHECK_AND_CALL(ret, r, vfs, closedir, pdir); 718 return ret; 719 } 720 721 int esp_vfs_mkdir(const char* name, mode_t mode) 722 { 723 const vfs_entry_t* vfs = get_vfs_for_path(name); 724 struct _reent* r = __getreent(); 725 if (vfs == NULL) { 726 __errno_r(r) = ENOENT; 727 return -1; 728 } 729 const char* path_within_vfs = translate_path(vfs, name); 730 int ret; 731 CHECK_AND_CALL(ret, r, vfs, mkdir, path_within_vfs, mode); 732 return ret; 733 } 734 735 int esp_vfs_rmdir(const char* name) 736 { 737 const vfs_entry_t* vfs = get_vfs_for_path(name); 738 struct _reent* r = __getreent(); 739 if (vfs == NULL) { 740 __errno_r(r) = ENOENT; 741 return -1; 742 } 743 const char* path_within_vfs = translate_path(vfs, name); 744 int ret; 745 CHECK_AND_CALL(ret, r, vfs, rmdir, path_within_vfs); 746 return ret; 747 } 748 749 int esp_vfs_access(const char *path, int amode) 750 { 751 int ret; 752 const vfs_entry_t* vfs = get_vfs_for_path(path); 753 struct _reent* r = __getreent(); 754 if (vfs == NULL) { 755 __errno_r(r) = ENOENT; 756 return -1; 757 } 758 const char* path_within_vfs = translate_path(vfs, path); 759 CHECK_AND_CALL(ret, r, vfs, access, path_within_vfs, amode); 760 return ret; 761 } 762 763 int esp_vfs_truncate(const char *path, off_t length) 764 { 765 int ret; 766 const vfs_entry_t* vfs = get_vfs_for_path(path); 767 struct _reent* r = __getreent(); 768 if (vfs == NULL) { 769 __errno_r(r) = ENOENT; 770 return -1; 771 } 772 const char* path_within_vfs = translate_path(vfs, path); 773 CHECK_AND_CALL(ret, r, vfs, truncate, path_within_vfs, length); 774 return ret; 775 } 776 777 #endif // CONFIG_VFS_SUPPORT_DIR 778 779 #ifdef CONFIG_VFS_SUPPORT_SELECT 780 781 static void call_end_selects(int end_index, const fds_triple_t *vfs_fds_triple, void **driver_args) 782 { 783 for (int i = 0; i < end_index; ++i) { 784 const vfs_entry_t *vfs = get_vfs_for_index(i); 785 const fds_triple_t *item = &vfs_fds_triple[i]; 786 if (vfs && vfs->vfs.end_select && item->isset) { 787 esp_err_t err = vfs->vfs.end_select(driver_args[i]); 788 if (err != ESP_OK) { 789 ESP_LOGD(TAG, "end_select failed: %s", esp_err_to_name(err)); 790 } 791 } 792 } 793 } 794 795 static inline bool esp_vfs_safe_fd_isset(int fd, const fd_set *fds) 796 { 797 return fds && FD_ISSET(fd, fds); 798 } 799 800 static int set_global_fd_sets(const fds_triple_t *vfs_fds_triple, int size, fd_set *readfds, fd_set *writefds, fd_set *errorfds) 801 { 802 int ret = 0; 803 804 for (int i = 0; i < size; ++i) { 805 const fds_triple_t *item = &vfs_fds_triple[i]; 806 if (item->isset) { 807 for (int fd = 0; fd < MAX_FDS; ++fd) { 808 const int local_fd = s_fd_table[fd].local_fd; // single read -> no locking is required 809 if (readfds && esp_vfs_safe_fd_isset(local_fd, &item->readfds)) { 810 ESP_LOGD(TAG, "FD %d in readfds was set from VFS ID %d", fd, i); 811 FD_SET(fd, readfds); 812 ++ret; 813 } 814 if (writefds && esp_vfs_safe_fd_isset(local_fd, &item->writefds)) { 815 ESP_LOGD(TAG, "FD %d in writefds was set from VFS ID %d", fd, i); 816 FD_SET(fd, writefds); 817 ++ret; 818 } 819 if (errorfds && esp_vfs_safe_fd_isset(local_fd, &item->errorfds)) { 820 ESP_LOGD(TAG, "FD %d in errorfds was set from VFS ID %d", fd, i); 821 FD_SET(fd, errorfds); 822 ++ret; 823 } 824 } 825 } 826 } 827 828 return ret; 829 } 830 831 static void esp_vfs_log_fd_set(const char *fds_name, const fd_set *fds) 832 { 833 if (fds_name && fds) { 834 ESP_LOGD(TAG, "FDs in %s =", fds_name); 835 for (int i = 0; i < MAX_FDS; ++i) { 836 if (esp_vfs_safe_fd_isset(i, fds)) { 837 ESP_LOGD(TAG, "%d", i); 838 } 839 } 840 } 841 } 842 843 int esp_vfs_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout) 844 { 845 // NOTE: Please see the "Synchronous input/output multiplexing" section of the ESP-IDF Programming Guide 846 // (API Reference -> Storage -> Virtual Filesystem) for a general overview of the implementation of VFS select(). 847 int ret = 0; 848 struct _reent* r = __getreent(); 849 850 ESP_LOGD(TAG, "esp_vfs_select starts with nfds = %d", nfds); 851 if (timeout) { 852 ESP_LOGD(TAG, "timeout is %lds + %ldus", (long)timeout->tv_sec, timeout->tv_usec); 853 } 854 esp_vfs_log_fd_set("readfds", readfds); 855 esp_vfs_log_fd_set("writefds", writefds); 856 esp_vfs_log_fd_set("errorfds", errorfds); 857 858 if (nfds > MAX_FDS || nfds < 0) { 859 ESP_LOGD(TAG, "incorrect nfds"); 860 __errno_r(r) = EINVAL; 861 return -1; 862 } 863 864 // Capture s_vfs_count to a local variable in case a new driver is registered or removed during this actual select() 865 // call. s_vfs_count cannot be protected with a mutex during a select() call (which can be one without a timeout) 866 // because that could block the registration of new driver. 867 const size_t vfs_count = s_vfs_count; 868 fds_triple_t *vfs_fds_triple; 869 if ((vfs_fds_triple = calloc(vfs_count, sizeof(fds_triple_t))) == NULL) { 870 __errno_r(r) = ENOMEM; 871 ESP_LOGD(TAG, "calloc is unsuccessful"); 872 return -1; 873 } 874 875 esp_vfs_select_sem_t sel_sem = { 876 .is_sem_local = false, 877 .sem = NULL, 878 }; 879 880 int (*socket_select)(int, fd_set *, fd_set *, fd_set *, struct timeval *) = NULL; 881 for (int fd = 0; fd < nfds; ++fd) { 882 _lock_acquire(&s_fd_table_lock); 883 const bool is_socket_fd = s_fd_table[fd].permanent; 884 const int vfs_index = s_fd_table[fd].vfs_index; 885 const int local_fd = s_fd_table[fd].local_fd; 886 _lock_release(&s_fd_table_lock); 887 888 if (vfs_index < 0) { 889 continue; 890 } 891 892 if (is_socket_fd) { 893 if (!socket_select) { 894 // no socket_select found yet so take a look 895 if (esp_vfs_safe_fd_isset(fd, readfds) || 896 esp_vfs_safe_fd_isset(fd, writefds) || 897 esp_vfs_safe_fd_isset(fd, errorfds)) { 898 const vfs_entry_t *vfs = s_vfs[vfs_index]; 899 socket_select = vfs->vfs.socket_select; 900 sel_sem.sem = vfs->vfs.get_socket_select_semaphore(); 901 } 902 } 903 continue; 904 } 905 906 fds_triple_t *item = &vfs_fds_triple[vfs_index]; // FD sets for VFS which belongs to fd 907 if (esp_vfs_safe_fd_isset(fd, readfds)) { 908 item->isset = true; 909 FD_SET(local_fd, &item->readfds); 910 FD_CLR(fd, readfds); 911 ESP_LOGD(TAG, "removing %d from readfds and adding as local FD %d to fd_set of VFS ID %d", fd, local_fd, vfs_index); 912 } 913 if (esp_vfs_safe_fd_isset(fd, writefds)) { 914 item->isset = true; 915 FD_SET(local_fd, &item->writefds); 916 FD_CLR(fd, writefds); 917 ESP_LOGD(TAG, "removing %d from writefds and adding as local FD %d to fd_set of VFS ID %d", fd, local_fd, vfs_index); 918 } 919 if (esp_vfs_safe_fd_isset(fd, errorfds)) { 920 item->isset = true; 921 FD_SET(local_fd, &item->errorfds); 922 FD_CLR(fd, errorfds); 923 ESP_LOGD(TAG, "removing %d from errorfds and adding as local FD %d to fd_set of VFS ID %d", fd, local_fd, vfs_index); 924 } 925 } 926 927 // all non-socket VFSs have their FD sets in vfs_fds_triple 928 // the global readfds, writefds and errorfds contain only socket FDs (if 929 // there any) 930 931 if (!socket_select) { 932 // There is no socket VFS registered or select() wasn't called for 933 // any socket. Therefore, we will use our own signalization. 934 sel_sem.is_sem_local = true; 935 if ((sel_sem.sem = xSemaphoreCreateBinary()) == NULL) { 936 free(vfs_fds_triple); 937 __errno_r(r) = ENOMEM; 938 ESP_LOGD(TAG, "cannot create select semaphore"); 939 return -1; 940 } 941 } 942 943 void **driver_args = calloc(vfs_count, sizeof(void *)); 944 945 if (driver_args == NULL) { 946 free(vfs_fds_triple); 947 __errno_r(r) = ENOMEM; 948 ESP_LOGD(TAG, "calloc is unsuccessful for driver args"); 949 return -1; 950 } 951 952 for (int i = 0; i < vfs_count; ++i) { 953 const vfs_entry_t *vfs = get_vfs_for_index(i); 954 fds_triple_t *item = &vfs_fds_triple[i]; 955 956 if (vfs && vfs->vfs.start_select && item->isset) { 957 // call start_select for all non-socket VFSs with has at least one FD set in readfds, writefds, or errorfds 958 // note: it can point to socket VFS but item->isset will be false for that 959 ESP_LOGD(TAG, "calling start_select for VFS ID %d with the following local FDs", i); 960 esp_vfs_log_fd_set("readfds", &item->readfds); 961 esp_vfs_log_fd_set("writefds", &item->writefds); 962 esp_vfs_log_fd_set("errorfds", &item->errorfds); 963 esp_err_t err = vfs->vfs.start_select(nfds, &item->readfds, &item->writefds, &item->errorfds, sel_sem, 964 driver_args + i); 965 966 if (err != ESP_OK) { 967 call_end_selects(i, vfs_fds_triple, driver_args); 968 (void) set_global_fd_sets(vfs_fds_triple, vfs_count, readfds, writefds, errorfds); 969 if (sel_sem.is_sem_local && sel_sem.sem) { 970 vSemaphoreDelete(sel_sem.sem); 971 sel_sem.sem = NULL; 972 } 973 free(vfs_fds_triple); 974 free(driver_args); 975 __errno_r(r) = EINTR; 976 ESP_LOGD(TAG, "start_select failed: %s", esp_err_to_name(err)); 977 return -1; 978 } 979 } 980 } 981 982 if (socket_select) { 983 ESP_LOGD(TAG, "calling socket_select with the following FDs"); 984 esp_vfs_log_fd_set("readfds", readfds); 985 esp_vfs_log_fd_set("writefds", writefds); 986 esp_vfs_log_fd_set("errorfds", errorfds); 987 ret = socket_select(nfds, readfds, writefds, errorfds, timeout); 988 ESP_LOGD(TAG, "socket_select returned %d and the FDs are the following", ret); 989 esp_vfs_log_fd_set("readfds", readfds); 990 esp_vfs_log_fd_set("writefds", writefds); 991 esp_vfs_log_fd_set("errorfds", errorfds); 992 } else { 993 if (readfds) { 994 FD_ZERO(readfds); 995 } 996 if (writefds) { 997 FD_ZERO(writefds); 998 } 999 if (errorfds) { 1000 FD_ZERO(errorfds); 1001 } 1002 1003 TickType_t ticks_to_wait = portMAX_DELAY; 1004 if (timeout) { 1005 uint32_t timeout_ms = timeout->tv_sec * 1000 + timeout->tv_usec / 1000; 1006 ticks_to_wait = timeout_ms / portTICK_PERIOD_MS; 1007 ESP_LOGD(TAG, "timeout is %dms", timeout_ms); 1008 } 1009 ESP_LOGD(TAG, "waiting without calling socket_select"); 1010 xSemaphoreTake(sel_sem.sem, ticks_to_wait); 1011 } 1012 1013 call_end_selects(vfs_count, vfs_fds_triple, driver_args); // for VFSs for start_select was called before 1014 if (ret >= 0) { 1015 ret += set_global_fd_sets(vfs_fds_triple, vfs_count, readfds, writefds, errorfds); 1016 } 1017 if (sel_sem.is_sem_local && sel_sem.sem) { 1018 vSemaphoreDelete(sel_sem.sem); 1019 sel_sem.sem = NULL; 1020 } 1021 free(vfs_fds_triple); 1022 free(driver_args); 1023 1024 ESP_LOGD(TAG, "esp_vfs_select returns %d", ret); 1025 esp_vfs_log_fd_set("readfds", readfds); 1026 esp_vfs_log_fd_set("writefds", writefds); 1027 esp_vfs_log_fd_set("errorfds", errorfds); 1028 return ret; 1029 } 1030 1031 void esp_vfs_select_triggered(esp_vfs_select_sem_t sem) 1032 { 1033 if (sem.is_sem_local) { 1034 xSemaphoreGive(sem.sem); 1035 } else { 1036 // Another way would be to go through s_fd_table and find the VFS 1037 // which has a permanent FD. But in order to avoid to lock 1038 // s_fd_table_lock we go through the VFS table. 1039 for (int i = 0; i < s_vfs_count; ++i) { 1040 // Note: s_vfs_count could have changed since the start of vfs_select() call. However, that change doesn't 1041 // matter here stop_socket_select() will be called for only valid VFS drivers. 1042 const vfs_entry_t *vfs = s_vfs[i]; 1043 if (vfs != NULL && vfs->vfs.stop_socket_select != NULL) { 1044 vfs->vfs.stop_socket_select(sem.sem); 1045 break; 1046 } 1047 } 1048 } 1049 } 1050 1051 void esp_vfs_select_triggered_isr(esp_vfs_select_sem_t sem, BaseType_t *woken) 1052 { 1053 if (sem.is_sem_local) { 1054 xSemaphoreGiveFromISR(sem.sem, woken); 1055 } else { 1056 // Another way would be to go through s_fd_table and find the VFS 1057 // which has a permanent FD. But in order to avoid to lock 1058 // s_fd_table_lock we go through the VFS table. 1059 for (int i = 0; i < s_vfs_count; ++i) { 1060 // Note: s_vfs_count could have changed since the start of vfs_select() call. However, that change doesn't 1061 // matter here stop_socket_select() will be called for only valid VFS drivers. 1062 const vfs_entry_t *vfs = s_vfs[i]; 1063 if (vfs != NULL && vfs->vfs.stop_socket_select_isr != NULL) { 1064 vfs->vfs.stop_socket_select_isr(sem.sem, woken); 1065 break; 1066 } 1067 } 1068 } 1069 } 1070 1071 #endif // CONFIG_VFS_SUPPORT_SELECT 1072 1073 #ifdef CONFIG_VFS_SUPPORT_TERMIOS 1074 1075 int tcgetattr(int fd, struct termios *p) 1076 { 1077 const vfs_entry_t* vfs = get_vfs_for_fd(fd); 1078 const int local_fd = get_local_fd(vfs, fd); 1079 struct _reent* r = __getreent(); 1080 if (vfs == NULL || local_fd < 0) { 1081 __errno_r(r) = EBADF; 1082 return -1; 1083 } 1084 int ret; 1085 CHECK_AND_CALL(ret, r, vfs, tcgetattr, local_fd, p); 1086 return ret; 1087 } 1088 1089 int tcsetattr(int fd, int optional_actions, const struct termios *p) 1090 { 1091 const vfs_entry_t* vfs = get_vfs_for_fd(fd); 1092 const int local_fd = get_local_fd(vfs, fd); 1093 struct _reent* r = __getreent(); 1094 if (vfs == NULL || local_fd < 0) { 1095 __errno_r(r) = EBADF; 1096 return -1; 1097 } 1098 int ret; 1099 CHECK_AND_CALL(ret, r, vfs, tcsetattr, local_fd, optional_actions, p); 1100 return ret; 1101 } 1102 1103 int tcdrain(int fd) 1104 { 1105 const vfs_entry_t* vfs = get_vfs_for_fd(fd); 1106 const int local_fd = get_local_fd(vfs, fd); 1107 struct _reent* r = __getreent(); 1108 if (vfs == NULL || local_fd < 0) { 1109 __errno_r(r) = EBADF; 1110 return -1; 1111 } 1112 int ret; 1113 CHECK_AND_CALL(ret, r, vfs, tcdrain, local_fd); 1114 return ret; 1115 } 1116 1117 int tcflush(int fd, int select) 1118 { 1119 const vfs_entry_t* vfs = get_vfs_for_fd(fd); 1120 const int local_fd = get_local_fd(vfs, fd); 1121 struct _reent* r = __getreent(); 1122 if (vfs == NULL || local_fd < 0) { 1123 __errno_r(r) = EBADF; 1124 return -1; 1125 } 1126 int ret; 1127 CHECK_AND_CALL(ret, r, vfs, tcflush, local_fd, select); 1128 return ret; 1129 } 1130 1131 int tcflow(int fd, int action) 1132 { 1133 const vfs_entry_t* vfs = get_vfs_for_fd(fd); 1134 const int local_fd = get_local_fd(vfs, fd); 1135 struct _reent* r = __getreent(); 1136 if (vfs == NULL || local_fd < 0) { 1137 __errno_r(r) = EBADF; 1138 return -1; 1139 } 1140 int ret; 1141 CHECK_AND_CALL(ret, r, vfs, tcflow, local_fd, action); 1142 return ret; 1143 } 1144 1145 pid_t tcgetsid(int fd) 1146 { 1147 const vfs_entry_t* vfs = get_vfs_for_fd(fd); 1148 const int local_fd = get_local_fd(vfs, fd); 1149 struct _reent* r = __getreent(); 1150 if (vfs == NULL || local_fd < 0) { 1151 __errno_r(r) = EBADF; 1152 return -1; 1153 } 1154 int ret; 1155 CHECK_AND_CALL(ret, r, vfs, tcgetsid, local_fd); 1156 return ret; 1157 } 1158 1159 int tcsendbreak(int fd, int duration) 1160 { 1161 const vfs_entry_t* vfs = get_vfs_for_fd(fd); 1162 const int local_fd = get_local_fd(vfs, fd); 1163 struct _reent* r = __getreent(); 1164 if (vfs == NULL || local_fd < 0) { 1165 __errno_r(r) = EBADF; 1166 return -1; 1167 } 1168 int ret; 1169 CHECK_AND_CALL(ret, r, vfs, tcsendbreak, local_fd, duration); 1170 return ret; 1171 } 1172 #endif // CONFIG_VFS_SUPPORT_TERMIOS 1173 1174 1175 /* Create aliases for newlib syscalls 1176 1177 These functions are also available in ROM as stubs which use the syscall table, but linking them 1178 directly here saves an additional function call when a software function is linked to one, and 1179 makes linking with -stdlib easier. 1180 */ 1181 #ifdef CONFIG_VFS_SUPPORT_IO 1182 int _open_r(struct _reent *r, const char * path, int flags, int mode) 1183 __attribute__((alias("esp_vfs_open"))); 1184 int _close_r(struct _reent *r, int fd) 1185 __attribute__((alias("esp_vfs_close"))); 1186 ssize_t _read_r(struct _reent *r, int fd, void * dst, size_t size) 1187 __attribute__((alias("esp_vfs_read"))); 1188 ssize_t _write_r(struct _reent *r, int fd, const void * data, size_t size) 1189 __attribute__((alias("esp_vfs_write"))); 1190 ssize_t pread(int fd, void *dst, size_t size, off_t offset) 1191 __attribute__((alias("esp_vfs_pread"))); 1192 ssize_t pwrite(int fd, const void *src, size_t size, off_t offset) 1193 __attribute__((alias("esp_vfs_pwrite"))); 1194 off_t _lseek_r(struct _reent *r, int fd, off_t size, int mode) 1195 __attribute__((alias("esp_vfs_lseek"))); 1196 int _fcntl_r(struct _reent *r, int fd, int cmd, int arg) 1197 __attribute__((alias("esp_vfs_fcntl_r"))); 1198 int _fstat_r(struct _reent *r, int fd, struct stat * st) 1199 __attribute__((alias("esp_vfs_fstat"))); 1200 int fsync(int fd) 1201 __attribute__((alias("esp_vfs_fsync"))); 1202 int ioctl(int fd, int cmd, ...) 1203 __attribute__((alias("esp_vfs_ioctl"))); 1204 #endif // CONFIG_VFS_SUPPORT_IO 1205 1206 #ifdef CONFIG_VFS_SUPPORT_SELECT 1207 int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout) 1208 __attribute__((alias("esp_vfs_select"))); 1209 #endif // CONFIG_VFS_SUPPORT_SELECT 1210 1211 #ifdef CONFIG_VFS_SUPPORT_DIR 1212 int _stat_r(struct _reent *r, const char * path, struct stat * st) 1213 __attribute__((alias("esp_vfs_stat"))); 1214 int _link_r(struct _reent *r, const char* n1, const char* n2) 1215 __attribute__((alias("esp_vfs_link"))); 1216 int _unlink_r(struct _reent *r, const char *path) 1217 __attribute__((alias("esp_vfs_unlink"))); 1218 int _rename_r(struct _reent *r, const char *src, const char *dst) 1219 __attribute__((alias("esp_vfs_rename"))); 1220 int truncate(const char *path, off_t length) 1221 __attribute__((alias("esp_vfs_truncate"))); 1222 int access(const char *path, int amode) 1223 __attribute__((alias("esp_vfs_access"))); 1224 int utime(const char *path, const struct utimbuf *times) 1225 __attribute__((alias("esp_vfs_utime"))); 1226 int rmdir(const char* name) 1227 __attribute__((alias("esp_vfs_rmdir"))); 1228 int mkdir(const char* name, mode_t mode) 1229 __attribute__((alias("esp_vfs_mkdir"))); 1230 DIR* opendir(const char* name) 1231 __attribute__((alias("esp_vfs_opendir"))); 1232 int closedir(DIR* pdir) 1233 __attribute__((alias("esp_vfs_closedir"))); 1234 int readdir_r(DIR* pdir, struct dirent* entry, struct dirent** out_dirent) 1235 __attribute__((alias("esp_vfs_readdir_r"))); 1236 struct dirent* readdir(DIR* pdir) 1237 __attribute__((alias("esp_vfs_readdir"))); 1238 long telldir(DIR* pdir) 1239 __attribute__((alias("esp_vfs_telldir"))); 1240 void seekdir(DIR* pdir, long loc) 1241 __attribute__((alias("esp_vfs_seekdir"))); 1242 void rewinddir(DIR* pdir) 1243 __attribute__((alias("esp_vfs_rewinddir"))); 1244 #endif // CONFIG_VFS_SUPPORT_DIR 1245 1246 void vfs_include_syscalls_impl(void) 1247 { 1248 // Linker hook function, exists to make the linker examine this fine 1249 }