|
// https://github.com/util-linux/util-linux/blob/master/sys-utils/mountpoint.c |
|
|
|
import { access, dirname, open, realpath, stat } from 'fs'; |
|
|
|
function _is_dm_devname(canonical) { |
|
|
|
let dmpath = null; |
|
|
|
const spos = rindex(canonical, "/"); |
|
if (spos != null) { |
|
dmpath = substr(canonical, spos + 1); |
|
} else { |
|
return { |
|
error: true, |
|
dmpath: null, |
|
error_message: "Not a device path" |
|
}; |
|
} |
|
|
|
const can_stat = stat(canonical); |
|
|
|
if (can_stat == null) { |
|
return { |
|
error: true, |
|
dmpath: null, |
|
error_message: sprintf("Unable to access path %s", canonical) |
|
}; |
|
} |
|
|
|
if ( |
|
(length(dmpath) < 5) || |
|
(substr(dmpath, 0, 4) != "/dm-") || |
|
(int(substr(dmpath, 4)) == NaN) || |
|
(can_stat.type != "block") |
|
) { |
|
return { |
|
error: false, |
|
dmpath: null, |
|
error_message: null |
|
}; |
|
} |
|
|
|
return { |
|
error: false, |
|
dmpath: dmpath, |
|
error_message: null |
|
}; |
|
} |
|
|
|
function _canonicalize_dm_name(path) { |
|
|
|
if (path == null) { |
|
return { |
|
error: true, |
|
dmpath: null, |
|
error_message: "No input path" |
|
}; |
|
} |
|
|
|
const fpath = sprintf("/sys/block/%s/dm/name", path); |
|
const f = open(fpath, "r"); |
|
if (f == null) { |
|
return { |
|
error: true, |
|
dmpath: null, |
|
error_message: sprintf("Unable to open '%s'", fpath) |
|
}; |
|
} |
|
|
|
const dmname = f.read("\n"); |
|
f.close(); |
|
|
|
if (dmname == null) { |
|
return { |
|
error: true, |
|
dmpath: null, |
|
error_message: sprintf("Failed to read '%s'", fpath) |
|
}; |
|
} |
|
|
|
const dmpath = sprintf("/dev/mapper/%s", dmname); |
|
if (!access(dmpath)) { |
|
return { |
|
error: true, |
|
dmpath: null, |
|
error_message: sprintf("Unable to access '%s'", dmpath) |
|
}; |
|
} |
|
|
|
return { |
|
error: false, |
|
dmpath: dmpath, |
|
error_message: null |
|
}; |
|
} |
|
|
|
function _canonicalize_device_path(path) { |
|
|
|
if (!path) { |
|
return { |
|
error: true, |
|
canonical: null, |
|
error_message: "No input path" |
|
}; |
|
} |
|
|
|
let canonical = realpath(path); |
|
if (!canonical) { |
|
return { |
|
error: true, |
|
canonical: null, |
|
error_message: sprintf("'%s' has no canonical path", path) |
|
}; |
|
} |
|
|
|
const dmname_result = _is_dm_devname(canonical); |
|
if (!dmname_result.error) { |
|
if (dmname_result.dmname != null) { |
|
const dmcanonical_result = _canonicalize_dm_name(dmname_result.dmname); |
|
if (dmcanonical_result.dmname != null) { |
|
canonical = dmcanonical_result.dmname; |
|
} else if (dmcanonical_result.error) { |
|
return { |
|
error: true, |
|
canonical: null, |
|
error_message: sprintf("'%s' could not be canonicalized: %s", path, dmcanonical_result.error_message) |
|
}; |
|
} |
|
} |
|
} else if (dmname_result.error_message != "Not a device path") { |
|
return { |
|
error: true, |
|
canonical: null, |
|
error_message: sprintf("'%s' could not be read: %s", canonical, dmname_result.error_message) |
|
}; |
|
} |
|
return { |
|
error: false, |
|
canonical: canonical, |
|
error_message: null |
|
}; |
|
} |
|
|
|
function _dir_to_device(in_stat, in_path) { |
|
|
|
if (in_path == null) { |
|
return { |
|
error: true, |
|
device: null, |
|
error_message: "No input path" |
|
}; |
|
} |
|
|
|
const canonical_result = _canonicalize_device_path(in_path); |
|
|
|
if (canonical_result.error) { |
|
return { |
|
error: true, |
|
device: null, |
|
error_message: canonical_result.error_message |
|
}; |
|
} |
|
|
|
const canstat_result = stat(canonical_result.canonical); |
|
|
|
if (canstat_result == null) { |
|
return { |
|
error: true, |
|
device: null, |
|
error_message: sprintf("Could not stat '%s' (canonical path for '%s')", canonical_result.canonical, in_path) |
|
}; |
|
} |
|
|
|
let parent_path = sprintf("%s/..", canonical_result.canonical); |
|
|
|
if (canstat_result.type != "directory") { |
|
parent_path = dirname(canonical_result.canonical); |
|
} |
|
|
|
const parent_stat = stat(parent_path); |
|
|
|
if (parent_stat == null) { |
|
return { |
|
error: true, |
|
device: null, |
|
error_message: sprintf("Could not stat parent directory of '%s' (canonical path for '%s')", canonical_result.canonical, in_path) |
|
}; |
|
} |
|
|
|
if ((in_stat.dev.major != parent_stat.dev.major) || (in_stat.dev.minor != parent_stat.dev.minor) || (in_stat.inode == parent_stat.inode)) { |
|
return { |
|
error: false, |
|
device: in_stat.dev, |
|
error_message: null |
|
}; |
|
} |
|
|
|
return { |
|
error: false, |
|
device: null, |
|
error_message: null |
|
}; |
|
} |
|
|
|
function _is_mountpoint(path) { |
|
|
|
const path_stat = stat(path); |
|
if (path_stat == null) { |
|
return { |
|
error: true, |
|
is_mountpoint: null, |
|
device: null, |
|
error_message: sprintf("Could not stat '%s'", path) |
|
}; |
|
} |
|
|
|
const path_device_result = _dir_to_device(path_stat, path); |
|
|
|
if (path_device_result.error) { |
|
return { |
|
error: true, |
|
is_mountpoint: null, |
|
device: null, |
|
error_message: sprintf("Could not get device for '%s': %s", path, path_device_result.error_message) |
|
}; |
|
} else { |
|
return { |
|
error: false, |
|
is_mountpoint: (path_device_result.device != null), |
|
device: path_device_result.device, |
|
error_message: null |
|
} |
|
} |
|
} |
|
|
|
function is_mountpoint(path, show_error) { |
|
const is_mp_result = _is_mountpoint(path); |
|
if (is_mp_result.error) { |
|
if (show_error) { |
|
printf("%s\n", is_mp_result.error_message); |
|
} |
|
return -1; |
|
|
|
} else { |
|
return (is_mp_result.is_mountpoint ? 0 : 1); |
|
} |
|
} |
|
|
|
function print_is_mountpoint(path, show_message, show_error) { |
|
const is_mp = is_mountpoint(path, show_error); |
|
|
|
if (is_mp == -1) { |
|
exit(-1); |
|
} |
|
|
|
if (is_mp == 0) { |
|
if (show_message) { |
|
printf("%s is a mountpoint\n", path); |
|
} |
|
exit(0); |
|
} else { |
|
if (show_message) { |
|
printf("%s is not a mountpoint\n", path); |
|
} |
|
exit(1); |
|
} |
|
} |
|
|
|
export { is_mountpoint, print_is_mountpoint }; |