/* ----------------------------------------------------------------------- *
*
* Copyright 2012 Intel Corporation; All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston MA 02110-1301, USA; either version 2 of the License, or
* (at your option) any later version; incorporated herein by reference.
*
* ----------------------------------------------------------------------- */
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include "mountinfo.h"
/*
* Parse /proc/self/mountinfo
*/
static int get_string(FILE *f, char *string_buf, size_t string_len, char *ec)
{
int ch;
char *p = string_buf;
for (;;) {
if (!string_len)
return -2; /* String too long */
ch = getc(f);
if (ch == EOF) {
return -1; /* Got EOF */
} else if (ch == ' ' || ch == '\t' || ch == '\n') {
*ec = ch;
*p = '\0';
return p - string_buf;
} else if (ch == '\\') {
/* Should always be followed by 3 octal digits in 000..377 */
int oc = 0;
int i;
for (i = 0; i < 3; i++) {
ch = getc(f);
if (ch < '0' || ch > '7' || (i == 0 && ch > '3'))
return -1; /* Bad escape sequence */
oc = (oc << 3) + (ch - '0');
}
if (!oc)
return -1; /* We can't handle \000 */
*p++ = oc;
string_len--;
} else {
*p++ = ch;
string_len--;
}
}
}
static void free_mountinfo(struct mountinfo *m)
{
struct mountinfo *nx;
while (m) {
free((char *)m->root);
free((char *)m->path);
free((char *)m->fstype);
free((char *)m->devpath);
free((char *)m->mountopt);
nx = m->next;
free(m);
m = nx;
}
}
static struct mountinfo *head = NULL, **tail = &head;
static void parse_mountinfo(void)
{
FILE *f;
struct mountinfo *m, *mm;
char string_buf[PATH_MAX*8];
int n;
char ec, *ep;
unsigned int ma, mi;
f = fopen("/proc/self/mountinfo", "r");
if (!f)
return;
for (;;) {
m = malloc(sizeof(struct mountinfo));
if (!m)
break;
memset(m, 0, sizeof *m);
n = get_string(f, string_buf, sizeof string_buf, &ec);
if (n < 0 || ec == '\n')
break;
m->mountid = strtoul(string_buf, &ep, 10);
if (*ep)
break;
n = get_string(f, string_buf, sizeof string_buf, &ec);
if (n < 0 || ec == '\n')
break;
m->parentid = strtoul(string_buf, &ep, 10);
if (*ep)
break;
n = get_string(f, string_buf, sizeof string_buf, &ec);
if (n < 0 || ec == '\n')
break;
if (sscanf(string_buf, "%u:%u", &ma, &mi) != 2)
break;
m->dev = makedev(ma, mi);
n = get_string(f, string_buf, sizeof string_buf, &ec);
if (n < 1 || ec == '\n' || string_buf[0] != '/')
break;
m->root = strdup(string_buf);
if (!m->root)
break;
n = get_string(f, string_buf, sizeof string_buf, &ec);
if (n < 1 || ec == '\n' || string_buf[0] != '/')
break;
m->path = strdup(string_buf);
m->pathlen = (n == 1) ? 0 : n; /* Treat / as empty */
/* Skip tagged attributes */
do {
n = get_string(f, string_buf, sizeof string_buf, &ec);
if (n < 0 || ec == '\n')
goto quit;
} while (n != 1 || string_buf[0] != '-');
n = get_string(f, string_buf, sizeof string_buf, &ec);
if (n < 0 || ec == '\n')
break;
m->fstype = strdup(string_buf);
if (!m->fstype)
break;
n = get_string(f, string_buf, sizeof string_buf, &ec);
if (n < 0 || ec == '\n')
break;
m->devpath = strdup(string_buf);
if (!m->devpath)
break;
n = get_string(f, string_buf, sizeof string_buf, &ec);
if (n < 0)
break;
m->mountopt = strdup(string_buf);
if (!m->mountopt)
break;
/* Skip any previously unknown fields */
while (ec != '\n' && ec != EOF)
ec = getc(f);
*tail = m;
tail = &m->next;
}
quit:
fclose(f);
free_mountinfo(m);
/* Create parent links */
for (m = head; m; m = m->next) {
for (mm = head; mm; mm = mm->next) {
if (m->parentid == mm->mountid) {
m->parent = mm;
if (!strcmp(m->path, mm->path))
mm->hidden = 1; /* Hidden under another mount */
break;
}
}
}
}
const struct mountinfo *find_mount(const char *path, char **subpath)
{
static int done_init;
char *real_path;
const struct mountinfo *m, *best;
struct stat st;
int len, matchlen;
if (!done_init) {
parse_mountinfo();
done_init = 1;
}
if (stat(path, &st))
return NULL;
real_path = realpath(path, NULL);
if (!real_path)
return NULL;
/*
* Tricky business: we need the longest matching subpath
* which isn't a parent of the same subpath.
*/
len = strlen(real_path);
matchlen = 0;
best = NULL;
for (m = head; m; m = m->next) {
if (m->hidden)
continue; /* Hidden underneath another mount */
if (m->pathlen > len)
continue; /* Cannot possibly match */
if (m->pathlen < matchlen)
continue; /* No point in testing this one */
if (st.st_dev == m->dev &&
!memcmp(m->path, real_path, m->pathlen) &&
(real_path[m->pathlen] == '/' || real_path[m->pathlen] == '\0')) {
matchlen = m->pathlen;
best = m;
}
}
if (best && subpath) {
if (real_path[best->pathlen] == '\0')
*subpath = strdup("/");
else
*subpath = strdup(real_path + best->pathlen);
}
return best;
}
#ifdef TEST
int main(int argc, char *argv[])
{
int i;
const struct mountinfo *m;
char *subpath;
parse_mountinfo();
for (i = 1; i < argc; i++) {
m = find_mount(argv[i], &subpath);
if (!m) {
printf("%s: %s\n", argv[i], strerror(errno));
continue;
}
printf("%s -> %s @ %s(%u,%u):%s %s %s\n",
argv[i], subpath, m->devpath, major(m->dev), minor(m->dev),
m->root, m->fstype, m->mountopt);
printf("Usable device: %s\n", find_device(m->dev, m->devpath));
free(subpath);
}
return 0;
}
#endif