// To reduce the number of string copies, this function calling pattern works as follows: // 1) The managed code calls GetDirentSize() to get the platform-specific // size of the dirent struct. // 2) The managed code creates a byte[] buffer of the size of the native dirent // and passes a pointer to this buffer to this function. // 3) This function passes input byte[] buffer to the OS to fill with dirent // data which makes the 1st strcpy. // 4) The ConvertDirent function will fill DirectoryEntry outputEntry with // pointers from byte[] buffer. // 5) The managed code uses DirectoryEntry outputEntry to find start of d_name // and the value of d_namelen, if avalable, to copy the name from // byte[] buffer into a managed string that the caller can use; this makes // the 2nd and final strcpy. extern "C" int32_t SystemNative_ReadDirR(DIR* dir, void* buffer, int32_t bufferSize, DirectoryEntry* outputEntry) { assert(buffer != nullptr); assert(dir != nullptr); assert(outputEntry != nullptr); if (bufferSize < static_cast<int32_t>(sizeof(dirent))) { assert(false && "Buffer size too small; use GetDirentSize to get required buffer size"); return ERANGE; } dirent* result = nullptr; dirent* entry = static_cast<dirent*>(buffer); #if HAVE_READDIR_R int error = readdir_r(dir, entry, &result); // positive error number returned -> failure if (error != 0) { assert(error > 0); *outputEntry = {}; // managed out param must be initialized return error; } // 0 returned with null result -> end-of-stream if (result == nullptr) { *outputEntry = {}; // managed out param must be initialized return -1; // shim convention for end-of-stream } // 0 returned with non-null result (guaranteed to be set to entry arg) -> success assert(result == entry); #else errno = 0; result = readdir(dir); // 0 returned with null result -> end-of-stream if (result == nullptr) { *outputEntry = {}; // managed out param must be initialized // kernel set errno -> failure if (errno != 0) { assert(errno == EBADF); // Invalid directory stream descriptor dir. return errno; } return -1; } assert(result->d_reclen <= bufferSize); memcpy(entry, result, static_cast<size_t>(result->d_reclen)); #endif ConvertDirent(*entry, outputEntry); return 0; }
int32_t ReadDirR(DIR* dir, void* buffer, int32_t bufferSize, DirectoryEntry* outputEntry) { assert(buffer != nullptr); assert(dir != nullptr); assert(outputEntry != nullptr); if (bufferSize < sizeof(dirent)) { assert(!"Buffer size too small; use GetDirentSize to get required buffer size"); return ERANGE; } // On successful cal to readdir_r, result and &entry should point to the same // data; a NULL temp pointer but return of 0 means that we reached the end of the // directory stream; finally, a NULL temp pointer with a positive return value // means an error occurred. dirent* result = nullptr; dirent* entry = (dirent*)buffer; int ret = readdir_r(dir, entry, &result); if (ret == 0) { if (result != nullptr) { assert(result == entry); ConvertDirent(*entry, outputEntry); } else { ret = -1; // errno values are positive so signal the end-of-stream with a non-error value *outputEntry = { }; } } else { *outputEntry = { }; } return ret; }
// To reduce the number of string copies, this function calling pattern works as follows: // 1) The managed code calls GetDirentSize() to get the platform-specific // size of the dirent struct. // 2) The managed code creates a byte[] buffer of the size of the native dirent // and passes a pointer to this buffer to this function. // 3) This function passes input byte[] buffer to the OS to fill with dirent data // which makes the 1st strcpy. // 4) The ConvertDirent function will set a pointer to the start of the inode name // in the byte[] buffer so the managed code and find it and copy it out of the // buffer into a managed string that the caller of the framework can use, making // the 2nd and final strcpy. extern "C" int32_t ReadDirR(DIR* dir, void* buffer, int32_t bufferSize, DirectoryEntry* outputEntry) { assert(buffer != nullptr); assert(dir != nullptr); assert(outputEntry != nullptr); if (bufferSize < static_cast<int32_t>(sizeof(dirent))) { assert(false && "Buffer size too small; use GetDirentSize to get required buffer size"); return ERANGE; } dirent* result = nullptr; dirent* entry = static_cast<dirent*>(buffer); int error = readdir_r(dir, entry, &result); // positive error number returned -> failure if (error != 0) { assert(error > 0); *outputEntry = {}; // managed out param must be initialized return error; } // 0 returned with null result -> end-of-stream if (result == nullptr) { *outputEntry = {}; // managed out param must be initialized return -1; // shim convention for end-of-stream } // 0 returned with non-null result (guaranteed to be set to entry arg) -> success assert(result == entry); ConvertDirent(*entry, outputEntry); return 0; }
// To reduce the number of string copies, the caller of this function is responsible to ensure the memory // referenced by outputEntry remains valid until it is read. // If the platform supports readdir_r, the caller provides a buffer into which the data is read. // If the platform uses readdir, the caller must ensure no calls are made to readdir/closedir since those will invalidate // the current dirent. We assume the platform supports concurrent readdir calls to different DIRs. int32_t SystemNative_ReadDirR(DIR* dir, uint8_t* buffer, int32_t bufferSize, DirectoryEntry* outputEntry) { assert(dir != NULL); assert(outputEntry != NULL); #if HAVE_READDIR_R assert(buffer != NULL); // align to dirent struct dirent* entry = (struct dirent*)((size_t)(buffer + dirent_alignment - 1) & ~(dirent_alignment - 1)); // check there is dirent size available at entry if ((buffer + bufferSize) < ((uint8_t*)entry + sizeof(struct dirent))) { assert(false && "Buffer size too small; use GetReadDirRBufferSize to get required buffer size"); return ERANGE; } struct dirent* result = NULL; #ifdef _AIX // AIX returns 0 on success, but bizarrely, it returns 9 for both error and // end-of-directory. result is NULL for both cases. The API returns the // same thing for EOD/error, so disambiguation between the two is nearly // impossible without clobbering errno for yourself and seeing if the API // changed it. See: // https://www.ibm.com/support/knowledgecenter/ssw_aix_71/com.ibm.aix.basetrf2/readdir_r.htm errno = 0; // create a success condition for the API to clobber int error = readdir_r(dir, entry, &result); if (error == 9) { memset(outputEntry, 0, sizeof(*outputEntry)); // managed out param must be initialized return errno == 0 ? -1 : errno; } #else int error = readdir_r(dir, entry, &result); // positive error number returned -> failure if (error != 0) { assert(error > 0); memset(outputEntry, 0, sizeof(*outputEntry)); // managed out param must be initialized return error; } // 0 returned with null result -> end-of-stream if (result == NULL) { memset(outputEntry, 0, sizeof(*outputEntry)); // managed out param must be initialized return -1; // shim convention for end-of-stream } #endif // 0 returned with non-null result (guaranteed to be set to entry arg) -> success assert(result == entry); #else (void)buffer; // unused (void)bufferSize; // unused errno = 0; struct dirent* entry = readdir(dir); // 0 returned with null result -> end-of-stream if (entry == NULL) { memset(outputEntry, 0, sizeof(*outputEntry)); // managed out param must be initialized // kernel set errno -> failure if (errno != 0) { assert_err(errno == EBADF, "Invalid directory stream descriptor dir", errno); return errno; } return -1; } #endif ConvertDirent(entry, outputEntry); return 0; }