This is an addition to @Sagar's answer about getting mounts from /proc
. Note the use of /proc/self/mountinfo instead of /proc/mountinfo or /proc/mounts. You can read more about format of /proc/self/mountinfo
in man 5 procfs
. While the following code technically parses files, it is safe to run on the main thread (because /proc
is in-memory filesystem).
private static final int SANE_SIZE_LIMIT = 200 * 1024 * 1024;
// some hashmap for mapping long values to objects
// personally I am using HPPC maps, the HashMap class is for simplicity
public final HashMap<String> mountMap = new HashMap<>();
public void parse() {
mountMap.clear();
CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
parseMounts(decoder, true);
}
private int measure(FileChannel fc) throws IOException {
final ByteBuffer buffer = ByteBuffer.allocate(1024 * 4);
int totalRead = 0, lastRead;
do {
buffer.clear();
lastRead = fc.read(buffer);
totalRead += lastRead;
if (totalRead > SANE_SIZE_LIMIT) {
throw new IOException("/proc/ file appears to be too big!!");
}
} while (lastRead != -1);
fc.position(0);
return totalRead;
}
private void parseMounts(CharsetDecoder d, boolean force) {
File file = new File("/proc/self/mountinfo");
int mode = ParcelFileDescriptor.MODE_READ_ONLY;
try (ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, mode));
FileChannel fc = new FileInputStream(pfd.getFileDescriptor()).getChannel()) {
// Measure size of file before reading from it.
// Virtual files in /proc/ appear to be zero-sized (because
// their contents are dynamic), but we want to attempt
// reading it in single read() call to avoid inconsistencies
final int totalRead = measure(fc);
try (FileInputStream fis = new FileInputStream(pfd.getFileDescriptor());
Reader r = Channels.newReader(fis.getChannel(), d, totalRead);
Scanner scanner = new Scanner(r)) {
while (scanner.hasNextLine()) {
scanner.nextInt();
scanner.nextInt();
final String[] mm = scanner.next().split(":");
final int major = Integer.parseInt(mm[0]);
final int minor = Integer.parseInt(mm[1]);
final long dev_t = makedev(major, minor);
final String source = scanner.next();
// ignore bind-mounts for now
if ("/".equals(source)) {
final String location = scanner.next();
// skip optional parts
scanner.skip("(.+ -)");
// type of file system (such as ext4)
// most useful filesystems can be distinguished by type
// but "fuse" is problematic (because most Android
// distributions implement dynamic permissions on
// external SD card via custom FUSE filesystem).
// To make matters worse, that SD card FUSE filesystem is
// often mounted in several places at once. The code below
// will throw away duplicate mounts by placing them in
// HashMap, keyed by uniqie filesystem type ID,
// but you can do it more cleverly be checking whether
// a mountpoint directory is accessible (via File#list).
// You can throw away rest of useless filesystems (such as
// /mnt/secure/asec) by permission checks and blacklisting
// well-known paths.
final String fsType = scanner.next().intern();
final String subject = scanner.next().intern();
String created = location + subject + fsType;
String prev = mountMap.put(dev_t, created);
if (prev != null) {
created.next = prev;
}
}
scanner.nextLine();
}
return;
} catch (NumberFormatException | NoSuchElementException nse) {
// oops.. either a new row type was introduced (not a big deal)
// or the file changed below our feet (because someone has mounted
// something). Let's retry once more
parseMounts(d, false);
} catch (IOException e) {
throw new WrappedIOException(e);
}
}
You can find more useful information (such as common path to useless filesystem), in this answer. Note, that formats of /proc/mounts and /proc/mountinfo are different, later was introduced after former to improve upon it's format without breaking backwards compatibility.
The code above is not golden bullet — it does not really tell you anything about individual filesystems, just their paths and filesystem name. You can be reasonable confident, that "vfat" and "ext4" are useful filesystems, and "procfs" is useless, but something like "fuse" is going to remain mysterious. You can augment output of code above by using android.os.storage.StorageManager
to get more user-friendly filesystem names (like "SD Card") when they are available (match by mount paths). You can also use StatFs to obtain available free space on partition — useless virtual filesystems typically will return zero free space and zero available space when queried. Finally, if you are so inclined, you can consider filesystem mount options when deciding whether to show filesystem to user. E.g. ro
vs rw
, — read-only filesystem mounts typically will be a lot less useful for your users.
When I explain this method to people, they are often concerned with it's robustness… Will it work on some random junkphone? Will it remain available in future OS versions? Here is my take on it: this method is still better than many reflection-based advises, — in the worst case, reading from /proc/ file will return you IOException. It will not crash your app or result in unpredictable behavior like some reflection-based hacks.
/proc
filesystem is official Linux API, maintained by Linux kernel developers. It is not possible to remove it by specifying different kernel build options (e.g. it is a mandatory part of OS kernel). It has been available for many years and retains better backwards compatibility than most Android APIs. In particular /proc/self/mountinfo was created over 10 years ago and will be available in most existing Android versions except most ancient.
Android developers do not officially support Linux-specific APIs. But they don't go out of their way to break them either. Some of recent SELinux changes in post-Lollipop Android have restricted access to some files in /proc/
, — because they allowed applications to covertly spy on other applications. Those changes specifically kept /proc/self
accessible, because /proc/self is designed to expose only applications own information (including information about file systems, available to the application).
If Google ever transitions from Linux to Fuchensa or some other homegrown BSD fork, the /proc/ and other Linux-specifc APIs will probably break. Do I care? Not really.