I'd like to get the size of the unused storage on a USB drive mounted using Arduino_UnifiedStorage. However it seems the mounted filesystem is held as a private member of the Arduino_POSIXStorage library, and thus I can't get a call to statvfs to return sane values.
Is there a recommended approach to getting the file system statistics (especially free space) of a USB stick using Arduino_UnifiedStorage?
and that statvfs is one of the many POSIX functions that work "more or less". Through typical C++ shenanigans, just plain statvfs should be sufficient; no need to specifically call (for example) FATFileSystem::statvfs through the -- in this case -- private object.
Thanks for the confirmation and clarifications. You helped isolate where my problem might be.
It turned out that my conundrum came from my using the wrong data type specifier in printf and also my alternate logging tools not handling the long long values properly. Once I sorted out my bad data logging I could see the simplest naked call to statvfs was actually working OK just as you said and I didn't need a specific filesystem instance after all.
Here's what I used in case it helps someone else... for a USB drive pass in "/usb/" as the ld parameter.
struct statvfs vfsbuf;
void Dump_statvfs(const char *ld) {
int ret;
ret = statvfs(ld, &vfsbuf); // This naked call to statvfs works OK
if (ret != 0) printf("statvfs ERROR: %d\n", ret);
printf("f_bsize: %8.8lu\n", vfsbuf.f_bsize); // unsigned long f_bsize; ///< Filesystem block size
printf("f_frsize:%8.8lu\n", vfsbuf.f_frsize); // unsigned long f_frsize; ///< Fragment size (block size)
printf("f_blocks:%16.16llu\n", vfsbuf.f_blocks); // fsblkcnt_t f_blocks; ///< Number of blocks
printf("f_bfree: %16.16llu\n", vfsbuf.f_bfree); // fsblkcnt_t f_bfree; ///< Number of free blocks
printf("f_bavail:%16.16llu\n", vfsbuf.f_bavail); // fsblkcnt_t f_bavail; ///< Number of free blocks for unprivileged users
printf("f_fsid: %8.8lu\n", vfsbuf.f_fsid); // unsigned long f_fsid; ///< Filesystem ID
printf("f_namemax:%8.8lu\n", vfsbuf.f_namemax); // unsigned long f_namemax; ///< Maximum filename length
printf("fsTotalSize:%llu fsUsedSize:%llu fsFreeBytes:%llu\n", totalSize(ld), usedSize(ld), bytesFree(ld));
}
//******************************************************
// Total byte capacity of the volume.
//******************************************************
uint64_t totalSize(const char *ld) {
statvfs(ld, &vfsbuf);
return (uint64_t)(vfsbuf.f_blocks * vfsbuf.f_bsize);
}
//********************************************************
// How much space in bytes currently used on the volume. *
//********************************************************
uint64_t usedSize(const char *ld) {
statvfs(ld, &vfsbuf);
return (uint64_t)((vfsbuf.f_blocks * vfsbuf.f_bsize) - (vfsbuf.f_bfree * vfsbuf.f_bsize));
}
//******************************************************
// How much space is left on a volume (Logical Drive). *
//******************************************************
uint64_t bytesFree(const char *ld) {
statvfs(ld, &vfsbuf);
return (uint64_t)(vfsbuf.f_bfree * vfsbuf.f_bsize);
}
Ah, printf. While lacking in the alignment and padding options, one benefit of Serial.print is that it takes advantage of C++ function overloading, not available with plain old C: the size of the integer argument selects the appropriate variant.
Note that the cast at the end of what you're using
does not help, and is effectively a no-op. First, it's not necessary as it matches the declared return type. C/C++ is happy to pointlessly widen, or dangerously chop, numeric types without a peep. More importantly, the cast happens after the multiplication. It so happens that with those fields
struct statvfs {
unsigned long f_bsize; ///< Filesystem block size
unsigned long f_frsize; ///< Fragment size (block size)
fsblkcnt_t f_blocks; ///< Number of blocks
fsblkcnt_t f_bfree; ///< Number of free blocks
fsblkcnt_t f_bavail; ///< Number of free blocks for unprivileged users
you've already got
typedef unsigned long long fsblkcnt_t; ///< Count of file system blocks
one the factors in the multiplication as long long, so you don't need any kind of cast at all.
In the case you do need a cast, it should be done more like this
cast one of the operands -- might as well be the first
Use a C++ _cast instead of the C-style one
Use the appropriate kind -- in this case static_cast
As a hint, if you make the return type auto, and hover over the function name, the code insight feature will tell you the result. (Same trick applies to variables.) For example, the difference between the above
But you should NOT leave the return type as auto -- function signatures should be explicit with their types, for people eyeballing your code (including yourself).
You also don't need a cast in a slightly different case like
Thanks for the further insights. FWIW The cast was there from the statvfs code code I previously borrowed for use with the internal FLASH storage. And I agree it didn't seem necessary based on the declared return type, but it seemed like it might help with being just a bit more explicit with the mixed type calculation.
I'm still experimenting to find the best C++ practices that suit my preference for higher level languages. So my current project has 3 types of strings, 4-5 types of logging and I avoid pointers and new if at all possible. The Arduino_UnifiedStorage library elevates things is just enough to be worth while for my tastes. eg class references and dot notation vs function calls and passing pointers.
And nakedly calling things like fopen() still seems like a very unnatural act and is very unclear about what exact function is called and within which context and scope????? I much prefer theCSVfile.open() etc and if (theCSVfile.exists)
I'm glad I got Arduino_UnifiedStorage working on the GIGA, although I've only been using the USBstorage class so far.
BTW I'm disappointed that Arduino_UnifiedStorage is only a virtual superclass to USBStorage, SDStorage and InternalStorage. I'd wanted to write code that could take an instance of any of the three subclasses, but apparently C++ requires pointer use for subclasses of virtual classes. So they handle that by passing enums and using switch statements. Ugh...