build: lookup the right git binary on WSL
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>pull/1477/head
parent
df8e7d0a9a
commit
19417e76e7
@ -0,0 +1,42 @@
|
|||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package gitutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/moby/sys/mountinfo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func gitPath(wd string) (string, error) {
|
||||||
|
// On WSL2 we need to check if the current working directory is mounted on
|
||||||
|
// a Windows drive and if so, we need to use the Windows git executable.
|
||||||
|
if os.Getenv("WSL_DISTRO_NAME") != "" && wd != "" {
|
||||||
|
// ensure any symlinks are resolved
|
||||||
|
wdPath, err := filepath.EvalSymlinks(wd)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
mi, err := mountinfo.GetMounts(mountinfo.ParentsFilter(wdPath))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// find the longest mount point
|
||||||
|
var idx, maxlen int
|
||||||
|
for i := range mi {
|
||||||
|
if len(mi[i].Mountpoint) > maxlen {
|
||||||
|
maxlen = len(mi[i].Mountpoint)
|
||||||
|
idx = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mi[idx].FSType == "9p" {
|
||||||
|
if p, err := exec.LookPath("git.exe"); err == nil {
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return exec.LookPath("git")
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package gitutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func gitPath(wd string) (string, error) {
|
||||||
|
return exec.LookPath("git.exe")
|
||||||
|
}
|
@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
@ -0,0 +1,44 @@
|
|||||||
|
// Package mountinfo provides a set of functions to retrieve information about OS mounts.
|
||||||
|
//
|
||||||
|
// Currently it supports Linux. For historical reasons, there is also some support for FreeBSD and OpenBSD,
|
||||||
|
// and a shallow implementation for Windows, but in general this is Linux-only package, so
|
||||||
|
// the rest of the document only applies to Linux, unless explicitly specified otherwise.
|
||||||
|
//
|
||||||
|
// In Linux, information about mounts seen by the current process is available from
|
||||||
|
// /proc/self/mountinfo. Note that due to mount namespaces, different processes can
|
||||||
|
// see different mounts. A per-process mountinfo table is available from /proc/<PID>/mountinfo,
|
||||||
|
// where <PID> is a numerical process identifier.
|
||||||
|
//
|
||||||
|
// In general, /proc is not a very efficient interface, and mountinfo is not an exception.
|
||||||
|
// For example, there is no way to get information about a specific mount point (i.e. it
|
||||||
|
// is all-or-nothing). This package tries to hide the /proc ineffectiveness by using
|
||||||
|
// parse filters while reading mountinfo. A filter can skip some entries, or stop
|
||||||
|
// processing the rest of the file once the needed information is found.
|
||||||
|
//
|
||||||
|
// For mountinfo filters that accept path as an argument, the path must be absolute,
|
||||||
|
// having all symlinks resolved, and being cleaned (i.e. no extra slashes or dots).
|
||||||
|
// One way to achieve all of the above is to employ filepath.Abs followed by
|
||||||
|
// filepath.EvalSymlinks (the latter calls filepath.Clean on the result so
|
||||||
|
// there is no need to explicitly call filepath.Clean).
|
||||||
|
//
|
||||||
|
// NOTE that in many cases there is no need to consult mountinfo at all. Here are some
|
||||||
|
// of the cases where mountinfo should not be parsed:
|
||||||
|
//
|
||||||
|
// 1. Before performing a mount. Usually, this is not needed, but if required (say to
|
||||||
|
// prevent over-mounts), to check whether a directory is mounted, call os.Lstat
|
||||||
|
// on it and its parent directory, and compare their st.Sys().(*syscall.Stat_t).Dev
|
||||||
|
// fields -- if they differ, then the directory is the mount point. NOTE this does
|
||||||
|
// not work for bind mounts. Optionally, the filesystem type can also be checked
|
||||||
|
// by calling unix.Statfs and checking the Type field (i.e. filesystem type).
|
||||||
|
//
|
||||||
|
// 2. After performing a mount. If there is no error returned, the mount succeeded;
|
||||||
|
// checking the mount table for a new mount is redundant and expensive.
|
||||||
|
//
|
||||||
|
// 3. Before performing an unmount. It is more efficient to do an unmount and ignore
|
||||||
|
// a specific error (EINVAL) which tells the directory is not mounted.
|
||||||
|
//
|
||||||
|
// 4. After performing an unmount. If there is no error returned, the unmount succeeded.
|
||||||
|
//
|
||||||
|
// 5. To find the mount point root of a specific directory. You can perform os.Stat()
|
||||||
|
// on the directory and traverse up until the Dev field of a parent directory differs.
|
||||||
|
package mountinfo
|
@ -0,0 +1,101 @@
|
|||||||
|
package mountinfo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MountedFast is a method of detecting a mount point without reading
|
||||||
|
// mountinfo from procfs. A caller can only trust the result if no error
|
||||||
|
// and sure == true are returned. Otherwise, other methods (e.g. parsing
|
||||||
|
// /proc/mounts) have to be used. If unsure, use Mounted instead (which
|
||||||
|
// uses MountedFast, but falls back to parsing mountinfo if needed).
|
||||||
|
//
|
||||||
|
// If a non-existent path is specified, an appropriate error is returned.
|
||||||
|
// In case the caller is not interested in this particular error, it should
|
||||||
|
// be handled separately using e.g. errors.Is(err, fs.ErrNotExist).
|
||||||
|
//
|
||||||
|
// This function is only available on Linux. When available (since kernel
|
||||||
|
// v5.6), openat2(2) syscall is used to reliably detect all mounts. Otherwise,
|
||||||
|
// the implementation falls back to using stat(2), which can reliably detect
|
||||||
|
// normal (but not bind) mounts.
|
||||||
|
func MountedFast(path string) (mounted, sure bool, err error) {
|
||||||
|
// Root is always mounted.
|
||||||
|
if path == string(os.PathSeparator) {
|
||||||
|
return true, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
path, err = normalizePath(path)
|
||||||
|
if err != nil {
|
||||||
|
return false, false, err
|
||||||
|
}
|
||||||
|
mounted, sure, err = mountedFast(path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// mountedByOpenat2 is a method of detecting a mount that works for all kinds
|
||||||
|
// of mounts (incl. bind mounts), but requires a recent (v5.6+) linux kernel.
|
||||||
|
func mountedByOpenat2(path string) (bool, error) {
|
||||||
|
dir, last := filepath.Split(path)
|
||||||
|
|
||||||
|
dirfd, err := unix.Openat2(unix.AT_FDCWD, dir, &unix.OpenHow{
|
||||||
|
Flags: unix.O_PATH | unix.O_CLOEXEC,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return false, &os.PathError{Op: "openat2", Path: dir, Err: err}
|
||||||
|
}
|
||||||
|
fd, err := unix.Openat2(dirfd, last, &unix.OpenHow{
|
||||||
|
Flags: unix.O_PATH | unix.O_CLOEXEC | unix.O_NOFOLLOW,
|
||||||
|
Resolve: unix.RESOLVE_NO_XDEV,
|
||||||
|
})
|
||||||
|
_ = unix.Close(dirfd)
|
||||||
|
switch err { //nolint:errorlint // unix errors are bare
|
||||||
|
case nil: // definitely not a mount
|
||||||
|
_ = unix.Close(fd)
|
||||||
|
return false, nil
|
||||||
|
case unix.EXDEV: // definitely a mount
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
// not sure
|
||||||
|
return false, &os.PathError{Op: "openat2", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mountedFast is similar to MountedFast, except it expects a normalized path.
|
||||||
|
func mountedFast(path string) (mounted, sure bool, err error) {
|
||||||
|
// Root is always mounted.
|
||||||
|
if path == string(os.PathSeparator) {
|
||||||
|
return true, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try a fast path, using openat2() with RESOLVE_NO_XDEV.
|
||||||
|
mounted, err = mountedByOpenat2(path)
|
||||||
|
if err == nil {
|
||||||
|
return mounted, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Another fast path: compare st.st_dev fields.
|
||||||
|
mounted, err = mountedByStat(path)
|
||||||
|
// This does not work for bind mounts, so false negative
|
||||||
|
// is possible, therefore only trust if return is true.
|
||||||
|
if mounted && err == nil {
|
||||||
|
return true, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func mounted(path string) (bool, error) {
|
||||||
|
path, err := normalizePath(path)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
mounted, sure, err := mountedFast(path)
|
||||||
|
if sure && err == nil {
|
||||||
|
return mounted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to parsing mountinfo.
|
||||||
|
return mountedByMountinfo(path)
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
//go:build linux || freebsd || openbsd || darwin
|
||||||
|
// +build linux freebsd openbsd darwin
|
||||||
|
|
||||||
|
package mountinfo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func mountedByStat(path string) (bool, error) {
|
||||||
|
var st unix.Stat_t
|
||||||
|
|
||||||
|
if err := unix.Lstat(path, &st); err != nil {
|
||||||
|
return false, &os.PathError{Op: "stat", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
dev := st.Dev
|
||||||
|
parent := filepath.Dir(path)
|
||||||
|
if err := unix.Lstat(parent, &st); err != nil {
|
||||||
|
return false, &os.PathError{Op: "stat", Path: parent, Err: err}
|
||||||
|
}
|
||||||
|
if dev != st.Dev {
|
||||||
|
// Device differs from that of parent,
|
||||||
|
// so definitely a mount point.
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
// NB: this does not detect bind mounts on Linux.
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizePath(path string) (realPath string, err error) {
|
||||||
|
if realPath, err = filepath.Abs(path); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if realPath, err = filepath.EvalSymlinks(realPath); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(realPath); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return realPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mountedByMountinfo(path string) (bool, error) {
|
||||||
|
entries, err := GetMounts(SingleEntryFilter(path))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(entries) > 0, nil
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
package mountinfo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetMounts retrieves a list of mounts for the current running process,
|
||||||
|
// with an optional filter applied (use nil for no filter).
|
||||||
|
func GetMounts(f FilterFunc) ([]*Info, error) {
|
||||||
|
return parseMountTable(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mounted determines if a specified path is a mount point. In case of any
|
||||||
|
// error, false (and an error) is returned.
|
||||||
|
//
|
||||||
|
// If a non-existent path is specified, an appropriate error is returned.
|
||||||
|
// In case the caller is not interested in this particular error, it should
|
||||||
|
// be handled separately using e.g. errors.Is(err, fs.ErrNotExist).
|
||||||
|
func Mounted(path string) (bool, error) {
|
||||||
|
// root is always mounted
|
||||||
|
if path == string(os.PathSeparator) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return mounted(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info reveals information about a particular mounted filesystem. This
|
||||||
|
// struct is populated from the content in the /proc/<pid>/mountinfo file.
|
||||||
|
type Info struct {
|
||||||
|
// ID is a unique identifier of the mount (may be reused after umount).
|
||||||
|
ID int
|
||||||
|
|
||||||
|
// Parent is the ID of the parent mount (or of self for the root
|
||||||
|
// of this mount namespace's mount tree).
|
||||||
|
Parent int
|
||||||
|
|
||||||
|
// Major and Minor are the major and the minor components of the Dev
|
||||||
|
// field of unix.Stat_t structure returned by unix.*Stat calls for
|
||||||
|
// files on this filesystem.
|
||||||
|
Major, Minor int
|
||||||
|
|
||||||
|
// Root is the pathname of the directory in the filesystem which forms
|
||||||
|
// the root of this mount.
|
||||||
|
Root string
|
||||||
|
|
||||||
|
// Mountpoint is the pathname of the mount point relative to the
|
||||||
|
// process's root directory.
|
||||||
|
Mountpoint string
|
||||||
|
|
||||||
|
// Options is a comma-separated list of mount options.
|
||||||
|
Options string
|
||||||
|
|
||||||
|
// Optional are zero or more fields of the form "tag[:value]",
|
||||||
|
// separated by a space. Currently, the possible optional fields are
|
||||||
|
// "shared", "master", "propagate_from", and "unbindable". For more
|
||||||
|
// information, see mount_namespaces(7) Linux man page.
|
||||||
|
Optional string
|
||||||
|
|
||||||
|
// FSType is the filesystem type in the form "type[.subtype]".
|
||||||
|
FSType string
|
||||||
|
|
||||||
|
// Source is filesystem-specific information, or "none".
|
||||||
|
Source string
|
||||||
|
|
||||||
|
// VFSOptions is a comma-separated list of superblock options.
|
||||||
|
VFSOptions string
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
//go:build freebsd || openbsd || darwin
|
||||||
|
// +build freebsd openbsd darwin
|
||||||
|
|
||||||
|
package mountinfo
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
// parseMountTable returns information about mounted filesystems
|
||||||
|
func parseMountTable(filter FilterFunc) ([]*Info, error) {
|
||||||
|
count, err := unix.Getfsstat(nil, unix.MNT_WAIT)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
entries := make([]unix.Statfs_t, count)
|
||||||
|
_, err = unix.Getfsstat(entries, unix.MNT_WAIT)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var out []*Info
|
||||||
|
for _, entry := range entries {
|
||||||
|
var skip, stop bool
|
||||||
|
mountinfo := getMountinfo(&entry)
|
||||||
|
|
||||||
|
if filter != nil {
|
||||||
|
// filter out entries we're not interested in
|
||||||
|
skip, stop = filter(mountinfo)
|
||||||
|
if skip {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out = append(out, mountinfo)
|
||||||
|
if stop {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mounted(path string) (bool, error) {
|
||||||
|
path, err := normalizePath(path)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
// Fast path: compare st.st_dev fields.
|
||||||
|
// This should always work for FreeBSD and OpenBSD.
|
||||||
|
mounted, err := mountedByStat(path)
|
||||||
|
if err == nil {
|
||||||
|
return mounted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to parsing mountinfo
|
||||||
|
return mountedByMountinfo(path)
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
package mountinfo
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// FilterFunc is a type defining a callback function for GetMount(),
|
||||||
|
// used to filter out mountinfo entries we're not interested in,
|
||||||
|
// and/or stop further processing if we found what we wanted.
|
||||||
|
//
|
||||||
|
// It takes a pointer to the Info struct (fully populated with all available
|
||||||
|
// fields on the GOOS platform), and returns two booleans:
|
||||||
|
//
|
||||||
|
// skip: true if the entry should be skipped;
|
||||||
|
//
|
||||||
|
// stop: true if parsing should be stopped after the entry.
|
||||||
|
type FilterFunc func(*Info) (skip, stop bool)
|
||||||
|
|
||||||
|
// PrefixFilter discards all entries whose mount points do not start with, or
|
||||||
|
// are equal to the path specified in prefix. The prefix path must be absolute,
|
||||||
|
// have all symlinks resolved, and cleaned (i.e. no extra slashes or dots).
|
||||||
|
//
|
||||||
|
// PrefixFilter treats prefix as a path, not a partial prefix, which means that
|
||||||
|
// given "/foo", "/foo/bar" and "/foobar" entries, PrefixFilter("/foo") returns
|
||||||
|
// "/foo" and "/foo/bar", and discards "/foobar".
|
||||||
|
func PrefixFilter(prefix string) FilterFunc {
|
||||||
|
return func(m *Info) (bool, bool) {
|
||||||
|
skip := !strings.HasPrefix(m.Mountpoint+"/", prefix+"/")
|
||||||
|
return skip, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SingleEntryFilter looks for a specific entry.
|
||||||
|
func SingleEntryFilter(mp string) FilterFunc {
|
||||||
|
return func(m *Info) (bool, bool) {
|
||||||
|
if m.Mountpoint == mp {
|
||||||
|
return false, true // don't skip, stop now
|
||||||
|
}
|
||||||
|
return true, false // skip, keep going
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParentsFilter returns all entries whose mount points
|
||||||
|
// can be parents of a path specified, discarding others.
|
||||||
|
//
|
||||||
|
// For example, given /var/lib/docker/something, entries
|
||||||
|
// like /var/lib/docker, /var and / are returned.
|
||||||
|
func ParentsFilter(path string) FilterFunc {
|
||||||
|
return func(m *Info) (bool, bool) {
|
||||||
|
skip := !strings.HasPrefix(path, m.Mountpoint)
|
||||||
|
return skip, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FSTypeFilter returns all entries that match provided fstype(s).
|
||||||
|
func FSTypeFilter(fstype ...string) FilterFunc {
|
||||||
|
return func(m *Info) (bool, bool) {
|
||||||
|
for _, t := range fstype {
|
||||||
|
if m.FSType == t {
|
||||||
|
return false, false // don't skip, keep going
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, false // skip, keep going
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
//go:build freebsd || darwin
|
||||||
|
// +build freebsd darwin
|
||||||
|
|
||||||
|
package mountinfo
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
func getMountinfo(entry *unix.Statfs_t) *Info {
|
||||||
|
return &Info{
|
||||||
|
Mountpoint: unix.ByteSliceToString(entry.Mntonname[:]),
|
||||||
|
FSType: unix.ByteSliceToString(entry.Fstypename[:]),
|
||||||
|
Source: unix.ByteSliceToString(entry.Mntfromname[:]),
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,214 @@
|
|||||||
|
package mountinfo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetMountsFromReader retrieves a list of mounts from the
|
||||||
|
// reader provided, with an optional filter applied (use nil
|
||||||
|
// for no filter). This can be useful in tests or benchmarks
|
||||||
|
// that provide fake mountinfo data, or when a source other
|
||||||
|
// than /proc/self/mountinfo needs to be read from.
|
||||||
|
//
|
||||||
|
// This function is Linux-specific.
|
||||||
|
func GetMountsFromReader(r io.Reader, filter FilterFunc) ([]*Info, error) {
|
||||||
|
s := bufio.NewScanner(r)
|
||||||
|
out := []*Info{}
|
||||||
|
for s.Scan() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
/*
|
||||||
|
See http://man7.org/linux/man-pages/man5/proc.5.html
|
||||||
|
|
||||||
|
36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
|
||||||
|
(1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11)
|
||||||
|
|
||||||
|
(1) mount ID: unique identifier of the mount (may be reused after umount)
|
||||||
|
(2) parent ID: ID of parent (or of self for the top of the mount tree)
|
||||||
|
(3) major:minor: value of st_dev for files on filesystem
|
||||||
|
(4) root: root of the mount within the filesystem
|
||||||
|
(5) mount point: mount point relative to the process's root
|
||||||
|
(6) mount options: per mount options
|
||||||
|
(7) optional fields: zero or more fields of the form "tag[:value]"
|
||||||
|
(8) separator: marks the end of the optional fields
|
||||||
|
(9) filesystem type: name of filesystem of the form "type[.subtype]"
|
||||||
|
(10) mount source: filesystem specific information or "none"
|
||||||
|
(11) super options: per super block options
|
||||||
|
|
||||||
|
In other words, we have:
|
||||||
|
* 6 mandatory fields (1)..(6)
|
||||||
|
* 0 or more optional fields (7)
|
||||||
|
* a separator field (8)
|
||||||
|
* 3 mandatory fields (9)..(11)
|
||||||
|
*/
|
||||||
|
|
||||||
|
text := s.Text()
|
||||||
|
fields := strings.Split(text, " ")
|
||||||
|
numFields := len(fields)
|
||||||
|
if numFields < 10 {
|
||||||
|
// should be at least 10 fields
|
||||||
|
return nil, fmt.Errorf("parsing '%s' failed: not enough fields (%d)", text, numFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
// separator field
|
||||||
|
sepIdx := numFields - 4
|
||||||
|
// In Linux <= 3.9 mounting a cifs with spaces in a share
|
||||||
|
// name (like "//srv/My Docs") _may_ end up having a space
|
||||||
|
// in the last field of mountinfo (like "unc=//serv/My Docs").
|
||||||
|
// Since kernel 3.10-rc1, cifs option "unc=" is ignored,
|
||||||
|
// so spaces should not appear.
|
||||||
|
//
|
||||||
|
// Check for a separator, and work around the spaces bug
|
||||||
|
for fields[sepIdx] != "-" {
|
||||||
|
sepIdx--
|
||||||
|
if sepIdx == 5 {
|
||||||
|
return nil, fmt.Errorf("parsing '%s' failed: missing - separator", text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &Info{}
|
||||||
|
|
||||||
|
p.Mountpoint, err = unescape(fields[4])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parsing '%s' failed: mount point: %w", fields[4], err)
|
||||||
|
}
|
||||||
|
p.FSType, err = unescape(fields[sepIdx+1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parsing '%s' failed: fstype: %w", fields[sepIdx+1], err)
|
||||||
|
}
|
||||||
|
p.Source, err = unescape(fields[sepIdx+2])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parsing '%s' failed: source: %w", fields[sepIdx+2], err)
|
||||||
|
}
|
||||||
|
p.VFSOptions = fields[sepIdx+3]
|
||||||
|
|
||||||
|
// ignore any numbers parsing errors, as there should not be any
|
||||||
|
p.ID, _ = strconv.Atoi(fields[0])
|
||||||
|
p.Parent, _ = strconv.Atoi(fields[1])
|
||||||
|
mm := strings.SplitN(fields[2], ":", 3)
|
||||||
|
if len(mm) != 2 {
|
||||||
|
return nil, fmt.Errorf("parsing '%s' failed: unexpected major:minor pair %s", text, mm)
|
||||||
|
}
|
||||||
|
p.Major, _ = strconv.Atoi(mm[0])
|
||||||
|
p.Minor, _ = strconv.Atoi(mm[1])
|
||||||
|
|
||||||
|
p.Root, err = unescape(fields[3])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parsing '%s' failed: root: %w", fields[3], err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Options = fields[5]
|
||||||
|
|
||||||
|
// zero or more optional fields
|
||||||
|
p.Optional = strings.Join(fields[6:sepIdx], " ")
|
||||||
|
|
||||||
|
// Run the filter after parsing all fields.
|
||||||
|
var skip, stop bool
|
||||||
|
if filter != nil {
|
||||||
|
skip, stop = filter(p)
|
||||||
|
if skip {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out = append(out, p)
|
||||||
|
if stop {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseMountTable(filter FilterFunc) ([]*Info, error) {
|
||||||
|
f, err := os.Open("/proc/self/mountinfo")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
return GetMountsFromReader(f, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PidMountInfo retrieves the list of mounts from a given process' mount
|
||||||
|
// namespace. Unless there is a need to get mounts from a mount namespace
|
||||||
|
// different from that of a calling process, use GetMounts.
|
||||||
|
//
|
||||||
|
// This function is Linux-specific.
|
||||||
|
//
|
||||||
|
// Deprecated: this will be removed before v1; use GetMountsFromReader with
|
||||||
|
// opened /proc/<pid>/mountinfo as an argument instead.
|
||||||
|
func PidMountInfo(pid int) ([]*Info, error) {
|
||||||
|
f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
return GetMountsFromReader(f, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A few specific characters in mountinfo path entries (root and mountpoint)
|
||||||
|
// are escaped using a backslash followed by a character's ascii code in octal.
|
||||||
|
//
|
||||||
|
// space -- as \040
|
||||||
|
// tab (aka \t) -- as \011
|
||||||
|
// newline (aka \n) -- as \012
|
||||||
|
// backslash (aka \\) -- as \134
|
||||||
|
//
|
||||||
|
// This function converts path from mountinfo back, i.e. it unescapes the above sequences.
|
||||||
|
func unescape(path string) (string, error) {
|
||||||
|
// try to avoid copying
|
||||||
|
if strings.IndexByte(path, '\\') == -1 {
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following code is UTF-8 transparent as it only looks for some
|
||||||
|
// specific characters (backslash and 0..7) with values < utf8.RuneSelf,
|
||||||
|
// and everything else is passed through as is.
|
||||||
|
buf := make([]byte, len(path))
|
||||||
|
bufLen := 0
|
||||||
|
for i := 0; i < len(path); i++ {
|
||||||
|
if path[i] != '\\' {
|
||||||
|
buf[bufLen] = path[i]
|
||||||
|
bufLen++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s := path[i:]
|
||||||
|
if len(s) < 4 {
|
||||||
|
// too short
|
||||||
|
return "", fmt.Errorf("bad escape sequence %q: too short", s)
|
||||||
|
}
|
||||||
|
c := s[1]
|
||||||
|
switch c {
|
||||||
|
case '0', '1', '2', '3', '4', '5', '6', '7':
|
||||||
|
v := c - '0'
|
||||||
|
for j := 2; j < 4; j++ { // one digit already; two more
|
||||||
|
if s[j] < '0' || s[j] > '7' {
|
||||||
|
return "", fmt.Errorf("bad escape sequence %q: not a digit", s[:3])
|
||||||
|
}
|
||||||
|
x := s[j] - '0'
|
||||||
|
v = (v << 3) | x
|
||||||
|
}
|
||||||
|
if v > 255 {
|
||||||
|
return "", fmt.Errorf("bad escape sequence %q: out of range" + s[:3])
|
||||||
|
}
|
||||||
|
buf[bufLen] = v
|
||||||
|
bufLen++
|
||||||
|
i += 3
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("bad escape sequence %q: not a digit" + s[:3])
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(buf[:bufLen]), nil
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package mountinfo
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
func getMountinfo(entry *unix.Statfs_t) *Info {
|
||||||
|
return &Info{
|
||||||
|
Mountpoint: unix.ByteSliceToString(entry.F_mntonname[:]),
|
||||||
|
FSType: unix.ByteSliceToString(entry.F_fstypename[:]),
|
||||||
|
Source: unix.ByteSliceToString(entry.F_mntfromname[:]),
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
//go:build !windows && !linux && !freebsd && !openbsd && !darwin
|
||||||
|
// +build !windows,!linux,!freebsd,!openbsd,!darwin
|
||||||
|
|
||||||
|
package mountinfo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errNotImplemented = fmt.Errorf("not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
|
||||||
|
func parseMountTable(_ FilterFunc) ([]*Info, error) {
|
||||||
|
return nil, errNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func mounted(path string) (bool, error) {
|
||||||
|
return false, errNotImplemented
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package mountinfo
|
||||||
|
|
||||||
|
func parseMountTable(_ FilterFunc) ([]*Info, error) {
|
||||||
|
// Do NOT return an error!
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mounted(_ string) (bool, error) {
|
||||||
|
return false, nil
|
||||||
|
}
|
Loading…
Reference in New Issue