22

For example, I've got a file with the following path:

/media/my_mountpoint/path/to/file.txt

I've got the whole path and want to get:

/media/my_mountpoint

How can I do this? Preferably in Python and without using external libraries / tools. (Both are not a requirement.)

rbrito
  • 2,398
  • 2
  • 21
  • 24
Georg Schölly
  • 124,188
  • 49
  • 220
  • 267

8 Answers8

23

You may either call the mount command and parse its output to find the longest common prefix with your path, or use the stat system call to get the device a file resides on and go up the tree until you get to a different device.

In Python, stat may be used as follows (untested and may have to be extended to handle symlinks and exotic stuff like union mounts):

def find_mount_point(path):
    path = os.path.abspath(path)
    orig_dev = os.stat(path).st_dev

    while path != '/':
        dir = os.path.dirname(path)
        if os.stat(dir).st_dev != orig_dev:
            # we crossed the device border
            break
        path = dir
    return path

Edit: I didn't know about os.path.ismount until just now. This simplifies things greatly.

def find_mount_point(path):
    path = os.path.abspath(path)
    while not os.path.ismount(path):
        path = os.path.dirname(path)
    return path
Fred Foo
  • 355,277
  • 75
  • 744
  • 836
  • 3
    That's almost what I'm using at the moment, except that I'm using [`os.path.ismount()`](http://docs.python.org/library/os.path.html#os.path.ismount). I thought there might be something more direct than walking the path. – Georg Schölly Dec 15 '10 at 19:27
  • 2
    @Georg: I didn't even know about that function. I've edited my answer. – Fred Foo Dec 15 '10 at 23:09
  • 6
    To make your code work with symlinks, e.g. /var/run -> ../run, replace `os.path.abspath()` with `os.path.realpath()` or `find_mount_point()` will return "/". – insecure May 01 '15 at 11:42
  • Very good answer! I have implemented this in Golang which was very helpfull. – Jerry Jacobs Dec 23 '15 at 20:59
7

Since python is not a requirement:

df "$filename" | awk 'NR==1 {next} {print $6; exit}'

The NR==1 {next} is to skip the header line that df outputs. $6 is the mount point. exit is to make sure we output only one line.

camh
  • 40,988
  • 13
  • 62
  • 70
  • 2
    For me on Gentoo this only worked when additionally using the `-P` flag for posix-compatible output. For regular output the filesystem column was on its own line if the path was too long and all the other column values came in the next line. – David Leuschner Jan 28 '13 at 15:52
6

Since nowadays we can't really reliably parse the contents of mount in systems where a filesystem was mounted by UUID or LABEL, as the output can contain something like:

(...)
/dev/disk/by-uuid/00000000-0000-0000-0000-000000000000 on / type ext4 (rw,relatime,errors=remount-ro,data=ordered)
(...)

we need a more robust solution (e.g., think about what "chopping" parts of a path like the above may lead to, and if we would want something like that).

One such solution (which, by the way, tries not to reinvent the wheel) is to simply use the stat command to discover the mountpoint where a file resides, as in:

$ stat --printf "%h:%m:%i\n" Talks
6:/media/lattes:461246

In the output above, we can see that:

  • the number of hardlinks (%h) in Talks is 6
  • the mountpoint (%m) is /media/lattes
  • its inode number (%i) is 461246.

Just for the record, this is with the version of stat from GNU coreutils, which means that some other versions (e.g., the BSDs) may not have it by default (but you can always install it with your preferred package manager).

rbrito
  • 2,398
  • 2
  • 21
  • 24
2

@larsmans Very good answer, this was very helpfull! I have implemented this in Golang where I needed it.

For people who are interested in the code (this has been tested for OS X and Linux):

package main

import (
    "os"
    "fmt"
    "syscall"
    "path/filepath"
)

func Mountpoint(path string) string {
    pi, err := os.Stat(path)
    if err != nil {
        return ""
    }

    odev := pi.Sys().(*syscall.Stat_t).Dev

    for path != "/" {
        _path := filepath.Dir(path)

        in, err := os.Stat(_path)
        if err != nil {
            return ""
        }

        if odev != in.Sys().(*syscall.Stat_t).Dev {
            break
        }

        path = _path
    }

    return path
}

func main() {
    path, _ := filepath.Abs("./")
    dir := filepath.Dir(path)
    fmt.Println("Path", path)
    fmt.Println("Dir", dir)
    fmt.Println("Mountpoint", Mountpoint(path))
}
Jerry Jacobs
  • 195
  • 9
2

I was working on a GTK+ 3 file manager in Python and came across the same need when looping through files.

The computer I was working on has Linux and OS X partitions. When the file manager application (running on the root Linux partition) would attempt to index the files on the OS X partition, it would quickly come across an absolute symlink from "/media/mac-hd/User Guides And Information" to "/Library/Documentation/User Guides and Information.localized" and choke. The problem was that the file manager was looking for the absolute target of that link on it's own file system where it does not exist instead of the OS X partition mounted at /media/mac-hd. So, I needed a way to identify that a file was on a different mount point and prepend that mount point to the absolute target of the link.

I began with the edited solution in Fred Foo's answer. It seemed to help provide a solution to the specific error I was trying to work around. When I would call find_mount_point('/media/mac-hd/User Guides And Information'), it would return /media/mac-hd. Great, I thought.

I noticed insecure's comment below the answer about making it work with symlinks and also noticed he was correct about /var/run:

To make your code work with symlinks, e.g. /var/run -> ../run, replace os.path.abspath() with os.path.realpath() or find_mount_point() will return "/".

When I tried replacing os.path.abspath() with os.path.realpath(), I would get the correct return value of /run for /var/run. However I also noticed that I would no longer get the value I wanted when calling find_mount_point('/media/mac-hd/User Guides And Information') because it now returned /.

The following is the solution I ended up using. Perhaps it can be simplified:

def find_mount_point(path):
    if not os.path.islink(path):
        path = os.path.abspath(path)
    elif os.path.islink(path) and os.path.lexists(os.readlink(path)):
        path = os.path.realpath(path)
    while not os.path.ismount(path):
        path = os.path.dirname(path)
        if os.path.islink(path) and os.path.lexists(os.readlink(path)):
            path = os.path.realpath(path)
    return path
KTB
  • 21
  • 1
1

My python is rusty, however you can use something like this with perl :

export PATH_TO_LOOK_FOR="/media/path";
perl -ne '@p = split /\s+/; print "$p[1]\n" if "'$PATH_TO_LOOK_FOR'" =~ m@^$p[1]/@' < /proc/mounts

notice the " ' ' " around $PATH_TO_LOOK_FOR otherwise it won't work.

//edit : python solution :

def find_mountpoint(path):
    for l in open("/proc/mounts", "r"):
        mp = l.split(" ")[1]
        if(mp != "/" and path.find(mp)==0): return mp

    return None
OneOfOne
  • 95,033
  • 20
  • 184
  • 185
-3
/bin/mountpoint [-q] [-d] /path/to/directory
Andrew Sledge
  • 10,163
  • 2
  • 29
  • 30
-4
import os

def find_mount_point(path):
    while not os.path.ismount(path):
        path=os.path.dirname(path)
    return path