How to call statvfs on a filesystem mounted using Arduino_UnifiedStorage?

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?

What did you try?

The examples look like this

  if (0 == mount(DEV_USB, FS_FAT, MNT_DEFAULT))
  {
    fp = fopen("/usb/testfile.txt", "w");

And the docs/README.md says

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.

So try just

  struct statvfs vfs;
  if (statvfs("/usb/", &vfs)) {
    Serial.print("statvfs failed: ");
    Serial.println(errno);
  }
  Serial.print("file system block size: ");
  Serial.println(vfs.f_bsize);
  Serial.print("fragment size: ");
  Serial.println(vfs.f_frsize);
  Serial.print("total size: * ");
  Serial.print(vfs.f_blocks);
  Serial.print(" = ");
  uint64_t total = vfs.f_frsize * vfs.f_blocks;
  Serial.println(total);
  Serial.print("free blocks: * ");
  Serial.print(vfs.f_bfree);
  Serial.print(" = ");
  uint64_t free = vfs.f_bfree * vfs.f_bsize;
  Serial.println(free);
  Serial.print("used: 0x");
  Serial.println(total - free, HEX);

I happen to have this vfs code handy, testing a custom block device. Works on R4, but I haven't tried the POSIXStorage library.

Hi Kenb4

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

struct statvfs vfsbuf;

uint64_t totalSize(const char *ld) {
  statvfs(ld, &vfsbuf);
  return (uint64_t)(vfsbuf.f_blocks * vfsbuf.f_bsize);
}

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

uint64_t giantStackOfFloppies(uint32_t count, uint32_t size) {
  return static_cast<uint64_t>(count) * size;
} 
  • 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

and without the cast

auto giantStackOfFloppies(uint32_t count, uint32_t size) {
  return count * size;
} 

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

uint64_t giantStackOfCDs(uint32_t count) {
  return count * 650'000'000ULL;
}

Here, the second factor is hard-coded to be long long

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...

References work?

struct Animal {
  virtual String talk() = 0;
};

struct Dog : Animal {
  String talk() override { return "woof"; }
} dog;

struct Cat : Animal {
  String talk() override { return "meow"; }
} cat;

struct Cow : Animal {
  String talk() override { return "moo"; }
} cow;

void setup() {
  Serial.begin(115200);
}

void talkWith(Animal &a) {
  Serial.println(a.talk());
}

void loop() {
  switch (millis() % 3) {
  case 2:
    talkWith(dog);
    break;
  case 1:
    talkWith(cat);
    break;
  default:
    talkWith(cow);
  }
  delay(random(1000, 1200));
}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.