Repository: muesli/duf
Branch: master
Commit: 4636deb4a7b7
Files: 35
Total size: 101.4 KB
Directory structure:
gitextract_nb2n7rrk/
├── .github/
│ ├── FUNDING.yml
│ ├── dependabot.yml
│ └── workflows/
│ ├── build.yml
│ ├── goreleaser.yml
│ ├── lint-soft.yml
│ ├── lint.yml
│ └── manpage.yml
├── .gitignore
├── .golangci-soft.yml
├── .golangci.yml
├── .goreleaser.yml
├── LICENSE
├── README.md
├── duf.1
├── filesystems.go
├── filesystems_darwin.go
├── filesystems_freebsd.go
├── filesystems_linux.go
├── filesystems_openbsd.go
├── filesystems_windows.go
├── go.mod
├── go.sum
├── groups.go
├── main.go
├── man.go
├── mounts.go
├── mounts_darwin.go
├── mounts_freebsd.go
├── mounts_linux.go
├── mounts_linux_test.go
├── mounts_openbsd.go
├── mounts_windows.go
├── style.go
├── table.go
└── themes.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
github: muesli
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "daily"
labels:
- "dependencies"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
labels:
- "dependencies"
================================================
FILE: .github/workflows/build.yml
================================================
name: build
on: [push, pull_request]
jobs:
build:
strategy:
matrix:
go-version: [~1.23, ^1]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
env:
GO111MODULE: "on"
steps:
- name: Install Go
uses: actions/setup-go@v6.0.0
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v5
- name: Download Go modules
run: go mod download
- name: Build
run: go build -v ./...
- name: Test
run: go test ./...
================================================
FILE: .github/workflows/goreleaser.yml
================================================
name: goreleaser
on:
pull_request:
push:
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v6.0.0
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
version: latest
args: release --snapshot --skip publish --skip sign --clean
================================================
FILE: .github/workflows/lint-soft.yml
================================================
name: lint-soft
on:
push:
branches:
- master
pull_request:
permissions:
contents: read
# Optional: allow read access to pull request. Use with `only-new-issues` option.
pull-requests: read
jobs:
golangci:
name: lint-soft
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-go@v6.0.0
with:
go-version: stable
- name: golangci-lint
uses: golangci/golangci-lint-action@v8
with:
# Optional: golangci-lint command line arguments.
args: --config .golangci-soft.yml --issues-exit-code=0
# Optional: show only new issues if it's a pull request. The default value is `false`.
only-new-issues: true
================================================
FILE: .github/workflows/lint.yml
================================================
name: lint
on:
push:
branches:
- master
pull_request:
permissions:
contents: read
# Optional: allow read access to pull request. Use with `only-new-issues` option.
pull-requests: read
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-go@v6.0.0
with:
go-version: stable
- name: golangci-lint
uses: golangci/golangci-lint-action@v8
with:
# Optional: golangci-lint command line arguments.
#args:
# Optional: show only new issues if it's a pull request. The default value is `false`.
only-new-issues: true
================================================
FILE: .github/workflows/manpage.yml
================================================
name: manpage
on:
push:
branches:
- master
jobs:
manpage:
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v6.0.0
with:
go-version: 1.23
- name: Checkout code
uses: actions/checkout@v5
- name: Download Go modules
run: go mod download
- name: Build
run: go build -v -tags mango
- name: Generate man-page
run: ./duf > duf.1
- name: Commit
uses: stefanzweifel/git-auto-commit-action@v4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
commit_message: "docs: update man page"
branch: master
commit_user_name: mango 🤖
commit_user_email: actions@github.com
commit_author: mango 🤖 <actions@github.com>
================================================
FILE: .gitignore
================================================
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
duf
dist/
================================================
FILE: .golangci-soft.yml
================================================
version: "2"
run:
tests: false
linters:
enable:
- exhaustive
- goconst
- godot
- godox
- gomoddirectives
- goprintffuncname
- misspell
- mnd
- nakedret
- nestif
- noctx
- nolintlint
- prealloc
- wrapcheck
disable:
- errcheck
- govet
- ineffassign
- staticcheck
- unused
exclusions:
generated: lax
presets:
- common-false-positives
issues:
max-issues-per-linter: 0
max-same-issues: 0
formatters:
exclusions:
generated: lax
================================================
FILE: .golangci.yml
================================================
version: "2"
run:
tests: false
linters:
enable:
- bodyclose
- gosec
- nilerr
- predeclared
- revive
- rowserrcheck
- sqlclosecheck
- tparallel
- unconvert
- unparam
- whitespace
exclusions:
generated: lax
presets:
- common-false-positives
issues:
max-issues-per-linter: 0
max-same-issues: 0
formatters:
enable:
- goimports
exclusions:
generated: lax
================================================
FILE: .goreleaser.yml
================================================
version: 2
env:
- CGO_ENABLED=0
before:
hooks:
- go mod tidy
builds:
- binary: duf
flags:
- -trimpath
ldflags: -s -w -X main.Version={{ .Version }} -X main.CommitSHA={{ .Commit }}
goos:
- linux
- freebsd
- openbsd
- darwin
- windows
goarch:
- amd64
- arm64
- 386
- arm
- ppc64le
goarm:
- 6
- 7
ignore:
- goos: windows
goarm: "6"
- goos: windows
goarm: "7"
archives:
- format_overrides:
- goos: windows
formats: ['zip']
name_template: >-
{{- .ProjectName }}_
{{- .Version }}_
{{- .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end -}}
files:
- duf.1
nfpms:
- ids:
- duf
vendor: muesli
homepage: "https://fribbledom.com/"
maintainer: "Christian Muehlhaeuser <muesli@gmail.com>"
description: "Disk Usage/Free Utility"
license: MIT
formats:
- apk
- deb
- rpm
bindir: /usr/bin
homebrew_casks:
- repository:
owner: muesli
name: homebrew-tap
commit_author:
name: "Christian Muehlhaeuser"
email: "muesli@gmail.com"
homepage: "https://fribbledom.com/"
description: "Disk Usage/Free Utility"
manpages:
- duf.1
skip_upload: true
signs:
- artifacts: checksum
checksum:
name_template: "checksums.txt"
snapshot:
version_template: "{{ .Tag }}-next"
changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020 Christian Muehlhaeuser
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
Portions of duf's code are copied and modified from
https://github.com/shirou/gopsutil.
gopsutil is distributed under BSD license reproduced below.
Copyright (c) 2014, WAKAYAMA Shirou
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the gopsutil authors nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
# duf
[](https://github.com/muesli/duf/releases)
[](https://pkg.go.dev/github.com/muesli/duf)
[](/LICENSE)
[](https://github.com/muesli/duf/actions)
[](https://goreportcard.com/report/muesli/duf)
Disk Usage/Free Utility (Linux, BSD, macOS & Windows)

## Features
- [x] User-friendly, colorful output
- [x] Adjusts to your terminal's theme & width
- [x] Sort the results according to your needs
- [x] Groups & filters devices
- [x] Can conveniently output JSON
## Installation
### Packages
#### Linux
- Arch Linux: `pacman -S duf`
- Ubuntu (22.04 and later) / Debian (12 and later): `apt install duf`
- Fedora Linux: `dnf install duf`
- Nix: `nix-env -iA nixpkgs.duf`
- Void Linux: `xbps-install -S duf`
- Gentoo Linux: `emerge sys-fs/duf`
- Solus: `eopkg it duf`
- [Packages](https://github.com/muesli/duf/releases) in Alpine, Debian & RPM formats
#### BSD
- FreeBSD: `pkg install duf`
- OpenBSD: `pkg_add duf`
#### macOS
- with [Homebrew](https://brew.sh/): `brew install duf`
- with [MacPorts](https://www.macports.org): `sudo port selfupdate && sudo port install duf`
#### Windows
- with [Chocolatey](https://chocolatey.org/): `choco install duf`
- with [scoop](https://scoop.sh/): `scoop install duf`
#### Android
- Android (via termux): `pkg install duf`
### Binaries
- [Binaries](https://github.com/muesli/duf/releases) for Linux, FreeBSD, OpenBSD, macOS, Windows
### From source
Make sure you have a working Go environment (Go 1.23 or higher is required).
See the [install instructions](https://golang.org/doc/install.html).
Compiling duf is easy, simply run:
git clone https://github.com/muesli/duf.git
cd duf
go build
## Usage
You can simply start duf without any command-line arguments:
duf
If you supply arguments, duf will only list specific devices & mount points:
duf /home /some/file
If you want to list everything (including pseudo, duplicate, inaccessible file systems):
duf --all
### Filtering
You can show and hide specific tables:
duf --only local,network,fuse,special,loops,binds
duf --hide local,network,fuse,special,loops,binds
You can also show and hide specific filesystems:
duf --only-fs tmpfs,vfat
duf --hide-fs tmpfs,vfat
...or specific mount points:
duf --only-mp /,/home,/dev
duf --hide-mp /,/home,/dev
Wildcards inside quotes work:
duf --only-mp '/sys/*,/dev/*'
### Display options
Sort the output:
duf --sort size
Valid keys are: `mountpoint`, `size`, `used`, `avail`, `usage`, `inodes`,
`inodes_used`, `inodes_avail`, `inodes_usage`, `type`, `filesystem`.
Show or hide specific columns:
duf --output mountpoint,size,usage
Valid keys are: `mountpoint`, `size`, `used`, `avail`, `usage`, `inodes`,
`inodes_used`, `inodes_avail`, `inodes_usage`, `type`, `filesystem`.
List inode information instead of block usage:
duf --inodes
If duf doesn't detect your terminal's colors correctly, you can set a theme:
duf --theme light
### Color-coding & Thresholds
duf highlights the availability & usage columns in red, green, or yellow,
depending on how much space is still available. You can set your own thresholds:
duf --avail-threshold="10G,1G"
duf --usage-threshold="0.5,0.9"
### Bonus
If you prefer your output as JSON:
duf --json
## Troubleshooting
Users of `oh-my-zsh` should be aware that it already defines an alias called
`duf`, which you will have to remove in order to use `duf`:
unalias duf
## Feedback
Got some feedback or suggestions? Please open an issue or drop me a note!
* [Twitter](https://twitter.com/mueslix)
* [The Fediverse](https://mastodon.social/@fribbledom)
================================================
FILE: duf.1
================================================
.TH DUF 1 "2025-09-30" "duf" "Disk Usage/Free Utility"
.SH NAME
duf - Disk Usage/Free Utility
.SH SYNOPSIS
\fBduf\fP [\fIoptions\&.\&.\&.\fP] [\fIargument\&.\&.\&.\fP]
.SH DESCRIPTION
Simple Disk Usage/Free Utility\&.
.PP
Features:
.PP
.RS
.IP \(bu 3
User-friendly, colorful output\&.
.IP \(bu 3
Adjusts to your terminal's theme & width\&.
.IP \(bu 3
Sort the results according to your needs\&.
.IP \(bu 3
Groups & filters devices\&.
.IP \(bu 3
Can conveniently output JSON\&.
.SH OPTIONS
.TP
\fB--all\fP
include pseudo, duplicate, inaccessible file systems
.TP
\fB--avail-threshold\fP
specifies the coloring threshold (yellow, red) of the avail column, must be integer with optional SI prefixes
.TP
\fB--hide\fP
hide specific devices, separated with commas: local, network, fuse, special, loops, binds
.TP
\fB--hide-fs\fP
hide specific filesystems, separated with commas
.TP
\fB--hide-mp\fP
hide specific mount points, separated with commas (supports wildcards)
.TP
\fB-h, --human-readable\fP
ignored, just for df compatibility
.TP
\fB--inodes\fP
list inode information instead of block usage
.TP
\fB--json\fP
output all devices in JSON format
.TP
\fB--only\fP
show only specific devices, separated with commas: local, network, fuse, special, loops, binds
.TP
\fB--only-fs\fP
only specific filesystems, separated with commas
.TP
\fB--only-mp\fP
only specific mount points, separated with commas (supports wildcards)
.TP
\fB--output\fP
output fields: mountpoint, size, used, avail, usage, inodes, inodes_used, inodes_avail, inodes_usage, type, filesystem
.TP
\fB--sort\fP
sort output by: mountpoint, size, used, avail, usage, inodes, inodes_used, inodes_avail, inodes_usage, type, filesystem
.TP
\fB--style\fP
style: unicode, ascii
.TP
\fB--theme\fP
color themes: dark, light, ansi
.TP
\fB--usage-threshold\fP
specifies the coloring threshold (yellow, red) of the usage bars as a floating point number from 0 to 1
.TP
\fB--version\fP
display version
.TP
\fB--warnings\fP
output all warnings to STDERR
.TP
\fB--width\fP
max output width
.SH USAGE
You can simply start duf without any command-line arguments:
.PP
.PP
$ duf
.PP
.PP
If you supply arguments, duf will only list specific devices & mount points:
.PP
.PP
$ duf /home /some/file
.PP
.PP
If you want to list everything (including pseudo, duplicate, inaccessible file systems):
.PP
.PP
$ duf --all
.PP
.PP
You can show and hide specific tables:
.PP
.PP
$ duf --only local,network,fuse,special,loops,binds
.PP
$ duf --hide local,network,fuse,special,loops,binds
.PP
.PP
You can also show and hide specific filesystems:
.PP
.PP
$ duf --only-fs tmpfs,vfat
.PP
$ duf --hide-fs tmpfs,vfat
.PP
.PP
\&.\&.\&.or specific mount points:
.PP
.PP
$ duf --only-mp /,/home,/dev
.PP
$ duf --hide-mp /,/home,/dev
.PP
.PP
Wildcards inside quotes work:
.PP
.PP
$ duf --only-mp '/sys/*,/dev/*'
.PP
.PP
Sort the output:
.PP
.PP
$ duf --sort size
.PP
.PP
Valid keys are: mountpoint, size, used, avail, usage, inodes, inodes_used, inodes_avail, inodes_usage, type, filesystem\&.
.PP
.PP
Show or hide specific columns:
.PP
.PP
$ duf --output mountpoint,size,usage
.PP
.PP
Valid keys are: mountpoint, size, used, avail, usage, inodes, inodes_used, inodes_avail, inodes_usage, type, filesystem\&.
.PP
.PP
List inode information instead of block usage:
.PP
.PP
$ duf --inodes
.PP
.PP
If duf doesn't detect your terminal's colors correctly, you can set a theme:
.PP
.PP
$ duf --theme light
.PP
.PP
duf highlights the availability & usage columns in red, green, or yellow, depending on how much space is still available\&. You can set your own thresholds:
.PP
.PP
$ duf --avail-threshold="10G,1G"
.PP
$ duf --usage-threshold="0\&.5,0\&.9"
.PP
.PP
If you prefer your output as JSON:
.PP
.PP
$ duf --json
.PP
.SH NOTES
Portions of duf's code are copied and modified from https://github\&.com/shirou/gopsutil\&.
.PP
gopsutil was written by WAKAYAMA Shirou and is distributed under BSD-3-Clause\&.
.SH AUTHORS
duf was written by Christian Muehlhaeuser <https://github\&.com/muesli/duf>
.SH COPYRIGHT
Copyright (C) 2020-2022 Christian Muehlhaeuser <https://github\&.com/muesli>
.PP
Released under MIT license\&.
================================================
FILE: filesystems.go
================================================
package main
import (
"os"
"path/filepath"
"strings"
)
func findMounts(mounts []Mount, path string) ([]Mount, error) {
var err error
path, err = filepath.Abs(path)
if err != nil {
return nil, err
}
path, err = filepath.EvalSymlinks(path)
if err != nil {
return nil, err
}
_, err = os.Stat(path)
if err != nil {
return nil, err
}
var m []Mount
for _, v := range mounts {
if path == v.Device {
return []Mount{v}, nil
}
if strings.HasPrefix(path, v.Mountpoint) {
var nm []Mount
// keep all entries that are as close or closer to the target
for _, mv := range m {
if len(mv.Mountpoint) >= len(v.Mountpoint) {
nm = append(nm, mv)
}
}
m = nm
// add entry only if we didn't already find something closer
if len(nm) == 0 || len(v.Mountpoint) >= len(nm[0].Mountpoint) {
m = append(m, v)
}
}
}
return m, nil
}
func deviceType(m Mount) string {
if isNetworkFs(m) {
return networkDevice
}
if isSpecialFs(m) {
return specialDevice
}
if isFuseFs(m) {
return fuseDevice
}
return localDevice
}
// remote: [ "nfs", "smbfs", "cifs", "ncpfs", "afs", "coda", "ftpfs", "mfs", "sshfs", "fuse.sshfs", "nfs4" ]
// special: [ "tmpfs", "devpts", "devtmpfs", "proc", "sysfs", "usbfs", "devfs", "fdescfs", "linprocfs" ]
================================================
FILE: filesystems_darwin.go
================================================
//go:build darwin
// +build darwin
package main
func isFuseFs(m Mount) bool {
//FIXME: implement
return false
}
func isNetworkFs(m Mount) bool {
//FIXME: implement
return false
}
func isSpecialFs(m Mount) bool {
return m.Fstype == "devfs"
}
func isHiddenFs(m Mount) bool {
return false
}
================================================
FILE: filesystems_freebsd.go
================================================
//go:build freebsd
// +build freebsd
package main
func isFuseFs(m Mount) bool {
//FIXME: implement
return false
}
func isNetworkFs(m Mount) bool {
fs := []string{"nfs", "smbfs"}
for _, v := range fs {
if m.Fstype == v {
return true
}
}
return false
}
func isSpecialFs(m Mount) bool {
fs := []string{"devfs", "tmpfs", "linprocfs", "linsysfs", "fdescfs", "procfs"}
for _, v := range fs {
if m.Fstype == v {
return true
}
}
return false
}
func isHiddenFs(m Mount) bool {
return false
}
================================================
FILE: filesystems_linux.go
================================================
//go:build linux
// +build linux
package main
import "strings"
//nolint:revive
const (
// man statfs
ADFS_SUPER_MAGIC = 0xadf5
AFFS_SUPER_MAGIC = 0xADFF
AUTOFS_SUPER_MAGIC = 0x0187
BDEVFS_MAGIC = 0x62646576
BEFS_SUPER_MAGIC = 0x42465331
BFS_MAGIC = 0x1BADFACE
BINFMTFS_MAGIC = 0x42494e4d
BPF_FS_MAGIC = 0xcafe4a11
BTRFS_SUPER_MAGIC = 0x9123683E
CGROUP_SUPER_MAGIC = 0x27e0eb
CGROUP2_SUPER_MAGIC = 0x63677270
CIFS_MAGIC_NUMBER = 0xFF534D42
CODA_SUPER_MAGIC = 0x73757245
COH_SUPER_MAGIC = 0x012FF7B7
CONFIGFS_MAGIC = 0x62656570
CRAMFS_MAGIC = 0x28cd3d45
DEBUGFS_MAGIC = 0x64626720
DEVFS_SUPER_MAGIC = 0x1373
DEVPTS_SUPER_MAGIC = 0x1cd1
EFIVARFS_MAGIC = 0xde5e81e4
EFS_SUPER_MAGIC = 0x00414A53
EXT_SUPER_MAGIC = 0x137D
EXT2_OLD_SUPER_MAGIC = 0xEF51
EXT2_SUPER_MAGIC = 0xEF53
EXT3_SUPER_MAGIC = 0xEF53
EXT4_SUPER_MAGIC = 0xEF53
FUSE_SUPER_MAGIC = 0x65735546
FUTEXFS_SUPER_MAGIC = 0xBAD1DEA
HFS_SUPER_MAGIC = 0x4244
HFSPLUS_SUPER_MAGIC = 0x482b
HOSTFS_SUPER_MAGIC = 0x00c0ffee
HPFS_SUPER_MAGIC = 0xF995E849
HUGETLBFS_MAGIC = 0x958458f6
ISOFS_SUPER_MAGIC = 0x9660
JFFS2_SUPER_MAGIC = 0x72b6
JFS_SUPER_MAGIC = 0x3153464a
MINIX_SUPER_MAGIC = 0x137F /* orig. minix */
MINIX_SUPER_MAGIC2 = 0x138F /* 30 char minix */
MINIX2_SUPER_MAGIC = 0x2468 /* minix V2 */
MINIX2_SUPER_MAGIC2 = 0x2478 /* minix V2, 30 char names */
MINIX3_SUPER_MAGIC = 0x4d5a /* minix V3 fs, 60 char names */
MQUEUE_MAGIC = 0x19800202
MSDOS_SUPER_MAGIC = 0x4d44
NCP_SUPER_MAGIC = 0x564c
NFS_SUPER_MAGIC = 0x6969
NILFS_SUPER_MAGIC = 0x3434
NTFS_SB_MAGIC = 0x5346544e
OCFS2_SUPER_MAGIC = 0x7461636f
OPENPROM_SUPER_MAGIC = 0x9fa1
PIPEFS_MAGIC = 0x50495045
PROC_SUPER_MAGIC = 0x9fa0
PSTOREFS_MAGIC = 0x6165676C
QNX4_SUPER_MAGIC = 0x002f
QNX6_SUPER_MAGIC = 0x68191122
RAMFS_MAGIC = 0x858458f6
REISERFS_SUPER_MAGIC = 0x52654973
ROMFS_MAGIC = 0x7275
SELINUX_MAGIC = 0xf97cff8c
SMACK_MAGIC = 0x43415d53
SMB_SUPER_MAGIC = 0x517B
SMB2_MAGIC_NUMBER = 0xfe534d42
SOCKFS_MAGIC = 0x534F434B
SQUASHFS_MAGIC = 0x73717368
SYSFS_MAGIC = 0x62656572
SYSV2_SUPER_MAGIC = 0x012FF7B6
SYSV4_SUPER_MAGIC = 0x012FF7B5
TMPFS_MAGIC = 0x01021994
TRACEFS_MAGIC = 0x74726163
UDF_SUPER_MAGIC = 0x15013346
UFS_MAGIC = 0x00011954
USBDEVICE_SUPER_MAGIC = 0x9fa2
V9FS_MAGIC = 0x01021997
VXFS_SUPER_MAGIC = 0xa501FCF5
XENFS_SUPER_MAGIC = 0xabba1974
XENIX_SUPER_MAGIC = 0x012FF7B4
XFS_SUPER_MAGIC = 0x58465342
_XIAFS_SUPER_MAGIC = 0x012FD16D
AFS_SUPER_MAGIC = 0x5346414F
AUFS_SUPER_MAGIC = 0x61756673
ANON_INODE_FS_SUPER_MAGIC = 0x09041934
CEPH_SUPER_MAGIC = 0x00C36400
ECRYPTFS_SUPER_MAGIC = 0xF15F
FAT_SUPER_MAGIC = 0x4006
FHGFS_SUPER_MAGIC = 0x19830326
FUSEBLK_SUPER_MAGIC = 0x65735546
FUSECTL_SUPER_MAGIC = 0x65735543
GFS_SUPER_MAGIC = 0x1161970
GPFS_SUPER_MAGIC = 0x47504653
MTD_INODE_FS_SUPER_MAGIC = 0x11307854
INOTIFYFS_SUPER_MAGIC = 0x2BAD1DEA
ISOFS_R_WIN_SUPER_MAGIC = 0x4004
ISOFS_WIN_SUPER_MAGIC = 0x4000
JFFS_SUPER_MAGIC = 0x07C0
KAFS_SUPER_MAGIC = 0x6B414653
LUSTRE_SUPER_MAGIC = 0x0BD00BD0
NFSD_SUPER_MAGIC = 0x6E667364
PANFS_SUPER_MAGIC = 0xAAD7AAEA
RPC_PIPEFS_SUPER_MAGIC = 0x67596969
SECURITYFS_SUPER_MAGIC = 0x73636673
UFS_BYTESWAPPED_SUPER_MAGIC = 0x54190100
VMHGFS_SUPER_MAGIC = 0xBACBACBC
VZFS_SUPER_MAGIC = 0x565A4653
ZFS_SUPER_MAGIC = 0x2FC12FC1
)
// coreutils/src/stat.c
var fsTypeMap = map[int64]string{
ADFS_SUPER_MAGIC: "adfs", /* 0xADF5 local */
AFFS_SUPER_MAGIC: "affs", /* 0xADFF local */
AFS_SUPER_MAGIC: "afs", /* 0x5346414F remote */
ANON_INODE_FS_SUPER_MAGIC: "anon-inode FS", /* 0x09041934 local */
AUFS_SUPER_MAGIC: "aufs", /* 0x61756673 remote */
AUTOFS_SUPER_MAGIC: "autofs", /* 0x0187 local */
BEFS_SUPER_MAGIC: "befs", /* 0x42465331 local */
BDEVFS_MAGIC: "bdevfs", /* 0x62646576 local */
BFS_MAGIC: "bfs", /* 0x1BADFACE local */
BINFMTFS_MAGIC: "binfmt_misc", /* 0x42494E4D local */
BTRFS_SUPER_MAGIC: "btrfs", /* 0x9123683E local */
CEPH_SUPER_MAGIC: "ceph", /* 0x00C36400 remote */
CGROUP_SUPER_MAGIC: "cgroupfs", /* 0x0027E0EB local */
CIFS_MAGIC_NUMBER: "cifs", /* 0xFF534D42 remote */
CODA_SUPER_MAGIC: "coda", /* 0x73757245 remote */
COH_SUPER_MAGIC: "coh", /* 0x012FF7B7 local */
CRAMFS_MAGIC: "cramfs", /* 0x28CD3D45 local */
DEBUGFS_MAGIC: "debugfs", /* 0x64626720 local */
DEVFS_SUPER_MAGIC: "devfs", /* 0x1373 local */
DEVPTS_SUPER_MAGIC: "devpts", /* 0x1CD1 local */
ECRYPTFS_SUPER_MAGIC: "ecryptfs", /* 0xF15F local */
EFS_SUPER_MAGIC: "efs", /* 0x00414A53 local */
EXT_SUPER_MAGIC: "ext", /* 0x137D local */
EXT2_SUPER_MAGIC: "ext2/ext3", /* 0xEF53 local */
EXT2_OLD_SUPER_MAGIC: "ext2", /* 0xEF51 local */
FAT_SUPER_MAGIC: "fat", /* 0x4006 local */
FHGFS_SUPER_MAGIC: "fhgfs", /* 0x19830326 remote */
FUSEBLK_SUPER_MAGIC: "fuseblk", /* 0x65735546 remote */
FUSECTL_SUPER_MAGIC: "fusectl", /* 0x65735543 remote */
FUTEXFS_SUPER_MAGIC: "futexfs", /* 0x0BAD1DEA local */
GFS_SUPER_MAGIC: "gfs/gfs2", /* 0x1161970 remote */
GPFS_SUPER_MAGIC: "gpfs", /* 0x47504653 remote */
HFS_SUPER_MAGIC: "hfs", /* 0x4244 local */
HFSPLUS_SUPER_MAGIC: "hfsplus", /* 0x482b local */
HPFS_SUPER_MAGIC: "hpfs", /* 0xF995E849 local */
HUGETLBFS_MAGIC: "hugetlbfs", /* 0x958458F6 local */
MTD_INODE_FS_SUPER_MAGIC: "inodefs", /* 0x11307854 local */
INOTIFYFS_SUPER_MAGIC: "inotifyfs", /* 0x2BAD1DEA local */
ISOFS_SUPER_MAGIC: "isofs", /* 0x9660 local */
ISOFS_R_WIN_SUPER_MAGIC: "isofs", /* 0x4004 local */
ISOFS_WIN_SUPER_MAGIC: "isofs", /* 0x4000 local */
JFFS_SUPER_MAGIC: "jffs", /* 0x07C0 local */
JFFS2_SUPER_MAGIC: "jffs2", /* 0x72B6 local */
JFS_SUPER_MAGIC: "jfs", /* 0x3153464A local */
KAFS_SUPER_MAGIC: "k-afs", /* 0x6B414653 remote */
LUSTRE_SUPER_MAGIC: "lustre", /* 0x0BD00BD0 remote */
MINIX_SUPER_MAGIC: "minix", /* 0x137F local */
MINIX_SUPER_MAGIC2: "minix (30 char.)", /* 0x138F local */
MINIX2_SUPER_MAGIC: "minix v2", /* 0x2468 local */
MINIX2_SUPER_MAGIC2: "minix v2 (30 char.)", /* 0x2478 local */
MINIX3_SUPER_MAGIC: "minix3", /* 0x4D5A local */
MQUEUE_MAGIC: "mqueue", /* 0x19800202 local */
MSDOS_SUPER_MAGIC: "msdos", /* 0x4D44 local */
NCP_SUPER_MAGIC: "novell", /* 0x564C remote */
NFS_SUPER_MAGIC: "nfs", /* 0x6969 remote */
NFSD_SUPER_MAGIC: "nfsd", /* 0x6E667364 remote */
NILFS_SUPER_MAGIC: "nilfs", /* 0x3434 local */
NTFS_SB_MAGIC: "ntfs", /* 0x5346544E local */
OPENPROM_SUPER_MAGIC: "openprom", /* 0x9FA1 local */
OCFS2_SUPER_MAGIC: "ocfs2", /* 0x7461636f remote */
PANFS_SUPER_MAGIC: "panfs", /* 0xAAD7AAEA remote */
PIPEFS_MAGIC: "pipefs", /* 0x50495045 remote */
PROC_SUPER_MAGIC: "proc", /* 0x9FA0 local */
PSTOREFS_MAGIC: "pstorefs", /* 0x6165676C local */
QNX4_SUPER_MAGIC: "qnx4", /* 0x002F local */
QNX6_SUPER_MAGIC: "qnx6", /* 0x68191122 local */
RAMFS_MAGIC: "ramfs", /* 0x858458F6 local */
REISERFS_SUPER_MAGIC: "reiserfs", /* 0x52654973 local */
ROMFS_MAGIC: "romfs", /* 0x7275 local */
RPC_PIPEFS_SUPER_MAGIC: "rpc_pipefs", /* 0x67596969 local */
SECURITYFS_SUPER_MAGIC: "securityfs", /* 0x73636673 local */
SELINUX_MAGIC: "selinux", /* 0xF97CFF8C local */
SMB_SUPER_MAGIC: "smb", /* 0x517B remote */
SMB2_MAGIC_NUMBER: "smb2", /* 0xfe534d42 remote */
SOCKFS_MAGIC: "sockfs", /* 0x534F434B local */
SQUASHFS_MAGIC: "squashfs", /* 0x73717368 local */
SYSFS_MAGIC: "sysfs", /* 0x62656572 local */
SYSV2_SUPER_MAGIC: "sysv2", /* 0x012FF7B6 local */
SYSV4_SUPER_MAGIC: "sysv4", /* 0x012FF7B5 local */
TMPFS_MAGIC: "tmpfs", /* 0x01021994 local */
UDF_SUPER_MAGIC: "udf", /* 0x15013346 local */
UFS_MAGIC: "ufs", /* 0x00011954 local */
UFS_BYTESWAPPED_SUPER_MAGIC: "ufs", /* 0x54190100 local */
USBDEVICE_SUPER_MAGIC: "usbdevfs", /* 0x9FA2 local */
V9FS_MAGIC: "v9fs", /* 0x01021997 local */
VMHGFS_SUPER_MAGIC: "vmhgfs", /* 0xBACBACBC remote */
VXFS_SUPER_MAGIC: "vxfs", /* 0xA501FCF5 local */
VZFS_SUPER_MAGIC: "vzfs", /* 0x565A4653 local */
XENFS_SUPER_MAGIC: "xenfs", /* 0xABBA1974 local */
XENIX_SUPER_MAGIC: "xenix", /* 0x012FF7B4 local */
XFS_SUPER_MAGIC: "xfs", /* 0x58465342 local */
_XIAFS_SUPER_MAGIC: "xia", /* 0x012FD16D local */
ZFS_SUPER_MAGIC: "zfs", /* 0x2FC12FC1 local */
}
/*
var localMap = map[int64]bool{
AFS_SUPER_MAGIC: true,
BTRFS_SUPER_MAGIC: true,
EXT_SUPER_MAGIC: true,
EXT2_OLD_SUPER_MAGIC: true,
EXT2_SUPER_MAGIC: true,
FAT_SUPER_MAGIC: true,
HPFS_SUPER_MAGIC: true,
MSDOS_SUPER_MAGIC: true,
NTFS_SB_MAGIC: true,
REISERFS_SUPER_MAGIC: true,
UDF_SUPER_MAGIC: true,
XFS_SUPER_MAGIC: true,
ZFS_SUPER_MAGIC: true,
}
*/
var networkMap = map[int64]bool{
CIFS_MAGIC_NUMBER: true,
NFS_SUPER_MAGIC: true,
SMB_SUPER_MAGIC: true,
SMB2_MAGIC_NUMBER: true,
}
var specialMap = map[int64]bool{
AUTOFS_SUPER_MAGIC: true,
BINFMTFS_MAGIC: true,
BPF_FS_MAGIC: true,
CGROUP_SUPER_MAGIC: true,
CGROUP2_SUPER_MAGIC: true,
CONFIGFS_MAGIC: true,
DEBUGFS_MAGIC: true,
DEVPTS_SUPER_MAGIC: true,
EFIVARFS_MAGIC: true,
FUSECTL_SUPER_MAGIC: true,
HUGETLBFS_MAGIC: true,
MQUEUE_MAGIC: true,
PROC_SUPER_MAGIC: true,
PSTOREFS_MAGIC: true,
SECURITYFS_SUPER_MAGIC: true,
SYSFS_MAGIC: true,
TMPFS_MAGIC: true,
TRACEFS_MAGIC: true,
}
/*
func isLocalFs(m Mount) bool {
return localMap[int64(m.Stat().Type)] //nolint:unconvert
}
*/
func isFuseFs(m Mount) bool {
return m.Stat().Type == FUSEBLK_SUPER_MAGIC ||
m.Stat().Type == FUSE_SUPER_MAGIC
}
func isNetworkFs(m Mount) bool {
return networkMap[int64(m.Stat().Type)] //nolint:unconvert
}
func isSpecialFs(m Mount) bool {
if m.Device == "nsfs" {
return true
}
return specialMap[int64(m.Stat().Type)] //nolint:unconvert
}
func isHiddenFs(m Mount) bool {
switch m.Device {
case "shm":
return true
case "overlay":
return true
}
switch m.Fstype {
case "autofs":
return true
case "squashfs":
if strings.HasPrefix(m.Mountpoint, "/snap") {
return true
}
}
return false
}
================================================
FILE: filesystems_openbsd.go
================================================
//go:build openbsd
// +build openbsd
package main
func isFuseFs(m Mount) bool {
//FIXME: implement
return false
}
func isNetworkFs(m Mount) bool {
//FIXME: implement
return false
}
func isSpecialFs(m Mount) bool {
return m.Fstype == "devfs"
}
func isHiddenFs(m Mount) bool {
return false
}
================================================
FILE: filesystems_windows.go
================================================
//go:build windows
// +build windows
package main
import (
"golang.org/x/sys/windows/registry"
)
const (
WindowsSandboxMountPointRegistryPath = `Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\CPC\LocalMOF`
)
var windowsSandboxMountPoints = loadRegisteredWindowsSandboxMountPoints()
func loadRegisteredWindowsSandboxMountPoints() (ret map[string]struct{}) {
ret = make(map[string]struct{})
key, err := registry.OpenKey(registry.CURRENT_USER, WindowsSandboxMountPointRegistryPath, registry.READ)
if err != nil {
return
}
keyInfo, err := key.Stat()
if err != nil {
return
}
mountPoints, err := key.ReadValueNames(int(keyInfo.ValueCount))
if err != nil {
return
}
for _, val := range mountPoints {
ret[val] = struct{}{}
}
return ret
}
func isFuseFs(m Mount) bool {
//FIXME: implement
return false
}
func isNetworkFs(m Mount) bool {
_, ok := m.Metadata.(*NetResource)
return ok
}
func isSpecialFs(m Mount) bool {
_, ok := windowsSandboxMountPoints[m.Mountpoint]
return ok
}
func isHiddenFs(m Mount) bool {
return false
}
================================================
FILE: go.mod
================================================
module github.com/muesli/duf
go 1.23.0
require (
github.com/IGLOU-EU/go-wildcard v1.0.3
github.com/jedib0t/go-pretty/v6 v6.6.8
github.com/mattn/go-runewidth v0.0.19
github.com/muesli/mango v0.2.0
github.com/muesli/mango-pflag v0.2.0
github.com/muesli/roff v0.1.0
github.com/muesli/termenv v0.16.0
github.com/spf13/pflag v1.0.10
golang.org/x/sys v0.35.0
golang.org/x/term v0.34.0
)
require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
golang.org/x/text v0.22.0 // indirect
)
================================================
FILE: go.sum
================================================
github.com/IGLOU-EU/go-wildcard v1.0.3 h1:r8T46+8/9V1STciXJomTWRpPEv4nGJATDbJkdU0Nou0=
github.com/IGLOU-EU/go-wildcard v1.0.3/go.mod h1:/qeV4QLmydCbwH0UMQJmXDryrFKJknWi/jjO8IiuQfY=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/jedib0t/go-pretty/v6 v6.6.8 h1:JnnzQeRz2bACBobIaa/r+nqjvws4yEhcmaZ4n1QzsEc=
github.com/jedib0t/go-pretty/v6 v6.6.8/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/muesli/mango v0.2.0 h1:iNNc0c5VLQ6fsMgAqGQofByNUBH2Q2nEbD6TaI+5yyQ=
github.com/muesli/mango v0.2.0/go.mod h1:5XFpbC8jY5UUv89YQciiXNlbi+iJgt29VDC5xbzrLL4=
github.com/muesli/mango-pflag v0.2.0 h1:QViokgKDZQCzKhYe1zH8D+UlPJzBSGoP9yx0hBG0t5k=
github.com/muesli/mango-pflag v0.2.0/go.mod h1:X9LT1p/pbGA1wjvEbtwnixujKErkP0jVmrxwrw3fL0Y=
github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8=
github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: groups.go
================================================
package main
import (
"strings"
)
const (
localDevice = "local"
networkDevice = "network"
fuseDevice = "fuse"
specialDevice = "special"
loopsDevice = "loops"
bindsMount = "binds"
)
// FilterOptions contains all filters.
type FilterOptions struct {
HiddenDevices map[string]struct{}
OnlyDevices map[string]struct{}
HiddenFilesystems map[string]struct{}
OnlyFilesystems map[string]struct{}
HiddenMountPoints map[string]struct{}
OnlyMountPoints map[string]struct{}
}
// renderTables renders all tables.
func renderTables(m []Mount, filters FilterOptions, opts TableOptions) {
deviceMounts := make(map[string][]Mount)
hasOnlyDevices := len(filters.OnlyDevices) != 0
_, hideLocal := filters.HiddenDevices[localDevice]
_, hideNetwork := filters.HiddenDevices[networkDevice]
_, hideFuse := filters.HiddenDevices[fuseDevice]
_, hideSpecial := filters.HiddenDevices[specialDevice]
_, hideLoops := filters.HiddenDevices[loopsDevice]
_, hideBinds := filters.HiddenDevices[bindsMount]
_, onlyLocal := filters.OnlyDevices[localDevice]
_, onlyNetwork := filters.OnlyDevices[networkDevice]
_, onlyFuse := filters.OnlyDevices[fuseDevice]
_, onlySpecial := filters.OnlyDevices[specialDevice]
_, onlyLoops := filters.OnlyDevices[loopsDevice]
_, onlyBinds := filters.OnlyDevices[bindsMount]
// sort/filter devices
for _, v := range m {
if len(filters.OnlyFilesystems) != 0 {
// skip not onlyFs
if _, ok := filters.OnlyFilesystems[strings.ToLower(v.Fstype)]; !ok {
continue
}
} else {
// skip hideFs
if _, ok := filters.HiddenFilesystems[strings.ToLower(v.Fstype)]; ok {
continue
}
}
// skip hidden devices
if isHiddenFs(v) && !*all {
continue
}
// skip bind-mounts
if strings.Contains(v.Opts, "bind") {
if (hasOnlyDevices && !onlyBinds) || (hideBinds && !*all) {
continue
}
}
// skip loop devices
if strings.HasPrefix(v.Device, "/dev/loop") {
if (hasOnlyDevices && !onlyLoops) || (hideLoops && !*all) {
continue
}
}
// skip special devices
if v.Blocks == 0 && !*all {
continue
}
// skip zero size devices
if v.BlockSize == 0 && !*all {
continue
}
// skip not only mount point
if len(filters.OnlyMountPoints) != 0 {
if !findInKey(v.Mountpoint, filters.OnlyMountPoints) {
continue
}
}
// skip hidden mount point
if len(filters.HiddenMountPoints) != 0 {
if findInKey(v.Mountpoint, filters.HiddenMountPoints) {
continue
}
}
t := deviceType(v)
deviceMounts[t] = append(deviceMounts[t], v)
}
// print tables
for _, devType := range groups {
mounts := deviceMounts[devType]
shouldPrint := *all
if !shouldPrint {
switch devType {
case localDevice:
shouldPrint = (hasOnlyDevices && onlyLocal) || (!hasOnlyDevices && !hideLocal)
case networkDevice:
shouldPrint = (hasOnlyDevices && onlyNetwork) || (!hasOnlyDevices && !hideNetwork)
case fuseDevice:
shouldPrint = (hasOnlyDevices && onlyFuse) || (!hasOnlyDevices && !hideFuse)
case specialDevice:
shouldPrint = (hasOnlyDevices && onlySpecial) || (!hasOnlyDevices && !hideSpecial)
}
}
if shouldPrint {
printTable(devType, mounts, opts)
}
}
}
================================================
FILE: main.go
================================================
package main
import (
"encoding/json"
"fmt"
"os"
"runtime/debug"
"strconv"
"strings"
"time"
wildcard "github.com/IGLOU-EU/go-wildcard"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/muesli/termenv"
flag "github.com/spf13/pflag"
"golang.org/x/term"
)
var (
// Version contains the application version number. It's set via ldflags
// when building.
Version = ""
// CommitSHA contains the SHA of the commit that this application was built
// against. It's set via ldflags when building.
CommitSHA = ""
env = termenv.EnvColorProfile()
theme Theme
groups = []string{localDevice, networkDevice, fuseDevice, specialDevice, loopsDevice, bindsMount}
allowedValues = strings.Join(groups, ", ")
all = flag.Bool("all", false, "include pseudo, duplicate, inaccessible file systems")
hideDevices = flag.String("hide", "", "hide specific devices, separated with commas:\n"+allowedValues)
hideFs = flag.String("hide-fs", "", "hide specific filesystems, separated with commas")
hideMp = flag.String("hide-mp", "", "hide specific mount points, separated with commas (supports wildcards)")
onlyDevices = flag.String("only", "", "show only specific devices, separated with commas:\n"+allowedValues)
onlyFs = flag.String("only-fs", "", "only specific filesystems, separated with commas")
onlyMp = flag.String("only-mp", "", "only specific mount points, separated with commas (supports wildcards)")
output = flag.String("output", "", "output fields: "+strings.Join(columnIDs(), ", "))
sortBy = flag.String("sort", "mountpoint", "sort output by: "+strings.Join(columnIDs(), ", "))
width = flag.Uint("width", 0, "max output width")
themeOpt = flag.String("theme", defaultThemeName(), "color themes: dark, light, ansi")
styleOpt = flag.String("style", defaultStyleName(), "style: unicode, ascii")
availThreshold = flag.String("avail-threshold", "10G,1G", "specifies the coloring threshold (yellow, red) of the avail column, must be integer with optional SI prefixes")
usageThreshold = flag.String("usage-threshold", "0.5,0.9", "specifies the coloring threshold (yellow, red) of the usage bars as a floating point number from 0 to 1")
_ = flag.BoolP("human-readable", "h", false, "ignored, just for df compatibility")
inodes = flag.Bool("inodes", false, "list inode information instead of block usage")
jsonOutput = flag.Bool("json", false, "output all devices in JSON format")
warns = flag.Bool("warnings", false, "output all warnings to STDERR")
version = flag.Bool("version", false, "display version")
)
// renderJSON encodes the JSON output and prints it.
func renderJSON(m []Mount) error {
output, err := json.MarshalIndent(m, "", " ")
if err != nil {
return fmt.Errorf("error formatting the json output: %s", err)
}
fmt.Println(string(output))
return nil
}
// parseColumns parses the supplied output flag into a slice of column indices.
func parseColumns(cols string) ([]int, error) {
var i []int
s := strings.Split(cols, ",")
for _, v := range s {
v = strings.TrimSpace(v)
if len(v) == 0 {
continue
}
col, err := stringToColumn(v)
if err != nil {
return nil, err
}
i = append(i, col)
}
return i, nil
}
// parseStyle converts user-provided style option into a table.Style.
func parseStyle(styleOpt string) (table.Style, error) {
switch styleOpt {
case "unicode":
return table.StyleRounded, nil
case "ascii":
return table.StyleDefault, nil
default:
return table.Style{}, fmt.Errorf("unknown style option: %s", styleOpt)
}
}
// parseCommaSeparatedValues parses comma separated string into a map.
func parseCommaSeparatedValues(values string) map[string]struct{} {
m := make(map[string]struct{})
for _, v := range strings.Split(values, ",") {
v = strings.TrimSpace(v)
if len(v) == 0 {
continue
}
v = strings.ToLower(v)
m[v] = struct{}{}
}
return m
}
// validateGroups validates the parsed group maps.
func validateGroups(m map[string]struct{}) error {
for k := range m {
found := false
for _, g := range groups {
if g == k {
found = true
break
}
}
if !found {
return fmt.Errorf("unknown device group: %s", k)
}
}
return nil
}
// findInKey parse a slice of pattern to match the given key.
func findInKey(str string, km map[string]struct{}) bool {
for p := range km {
if wildcard.Match(p, str) {
return true
}
}
return false
}
func printVersion() {
info, ok := debug.ReadBuildInfo()
var buildTime time.Time
var modified bool
if ok {
if len(Version) == 0 {
vs := strings.Split(info.Main.Version, "-")
if len(vs) >= 1 {
Version = vs[0]
}
}
for _, setting := range info.Settings {
switch setting.Key {
case "vcs.revision":
if len(CommitSHA) == 0 {
CommitSHA = setting.Value
if len(CommitSHA) > 12 {
CommitSHA = CommitSHA[:12]
}
}
case "vcs.time":
buildTime, _ = time.Parse(time.RFC3339, setting.Value)
case "vcs.modified":
modified, _ = strconv.ParseBool(setting.Value)
}
}
}
if Version == "" || Version == "(devel)" {
Version = "(built from source)"
}
fmt.Printf("duf %s", Version)
if len(CommitSHA) > 0 {
if modified {
CommitSHA += "+modified"
}
fmt.Printf(" (%s)", CommitSHA)
}
if !buildTime.IsZero() {
fmt.Printf(" (built on %s)", buildTime.Format("2006-01-02"))
}
fmt.Println()
}
func main() {
// hide -h from help, it's just for df compatibility
_ = flag.CommandLine.MarkHidden("human-readable")
flag.Parse()
if *version {
printVersion()
os.Exit(0)
}
// read mount table
m, warnings, err := mounts()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// print JSON
if *jsonOutput {
if err = renderJSON(m); err != nil {
fmt.Fprintln(os.Stderr, err)
}
return
}
// validate theme
theme, err = loadTheme(*themeOpt)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if env == termenv.ANSI {
// enforce ANSI theme for limited color support
theme, err = loadTheme("ansi")
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
// validate style
style, err := parseStyle(*styleOpt)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// validate output columns
columns, err := parseColumns(*output)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if len(columns) == 0 {
// no columns supplied, use defaults
if *inodes {
columns = []int{1, 6, 7, 8, 9, 10, 11}
} else {
columns = []int{1, 2, 3, 4, 5, 10, 11}
}
}
// validate sort column
sortCol, err := stringToSortIndex(*sortBy)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// validate filters
filters := FilterOptions{
HiddenDevices: parseCommaSeparatedValues(*hideDevices),
OnlyDevices: parseCommaSeparatedValues(*onlyDevices),
HiddenFilesystems: parseCommaSeparatedValues(*hideFs),
OnlyFilesystems: parseCommaSeparatedValues(*onlyFs),
HiddenMountPoints: parseCommaSeparatedValues(*hideMp),
OnlyMountPoints: parseCommaSeparatedValues(*onlyMp),
}
err = validateGroups(filters.HiddenDevices)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = validateGroups(filters.OnlyDevices)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// validate arguments
if len(flag.Args()) > 0 {
var mounts []Mount
vis := map[string]struct{}{}
for _, v := range flag.Args() {
var fm []Mount
fm, err = findMounts(m, v)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// de-duplicate
for _, v := range fm {
if _, ok := vis[v.Mountpoint]; !ok {
mounts = append(mounts, v)
vis[v.Mountpoint] = struct{}{}
}
}
}
m = mounts
}
// validate availability thresholds
availbilityThresholds := strings.Split(*availThreshold, ",")
if len(availbilityThresholds) != 2 {
fmt.Fprintln(os.Stderr, fmt.Errorf("error parsing avail-threshold: invalid option '%s'", *availThreshold))
os.Exit(1)
}
for _, threshold := range availbilityThresholds {
_, err = stringToSize(threshold)
if err != nil {
fmt.Fprintln(os.Stderr, "error parsing avail-threshold:", err)
os.Exit(1)
}
}
// validate usage thresholds
usageThresholds := strings.Split(*usageThreshold, ",")
if len(usageThresholds) != 2 {
fmt.Fprintln(os.Stderr, fmt.Errorf("error parsing usage-threshold: invalid option '%s'", *usageThreshold))
os.Exit(1)
}
for _, threshold := range usageThresholds {
_, err = strconv.ParseFloat(threshold, 64)
if err != nil {
fmt.Fprintln(os.Stderr, "error parsing usage-threshold:", err)
os.Exit(1)
}
}
// print out warnings
if *warns {
for _, warning := range warnings {
fmt.Fprintln(os.Stderr, warning)
}
}
// detect terminal width
isTerminal := term.IsTerminal(int(os.Stdout.Fd()))
if isTerminal && *width == 0 {
w, _, err := term.GetSize(int(os.Stdout.Fd()))
if err == nil {
*width = uint(w)
}
}
if *width == 0 {
*width = 80
}
// print tables
renderTables(m, filters, TableOptions{
Columns: columns,
SortBy: sortCol,
Style: style,
StyleName: *styleOpt,
})
}
================================================
FILE: man.go
================================================
//go:build mango
// +build mango
package main
import (
"fmt"
"os"
"github.com/muesli/mango"
mpflag "github.com/muesli/mango-pflag"
"github.com/muesli/roff"
flag "github.com/spf13/pflag"
)
func init() {
usage := `You can simply start duf without any command-line arguments:
$ duf
If you supply arguments, duf will only list specific devices & mount points:
$ duf /home /some/file
If you want to list everything (including pseudo, duplicate, inaccessible file systems):
$ duf --all
You can show and hide specific tables:
$ duf --only local,network,fuse,special,loops,binds
$ duf --hide local,network,fuse,special,loops,binds
You can also show and hide specific filesystems:
$ duf --only-fs tmpfs,vfat
$ duf --hide-fs tmpfs,vfat
...or specific mount points:
$ duf --only-mp /,/home,/dev
$ duf --hide-mp /,/home,/dev
Wildcards inside quotes work:
$ duf --only-mp '/sys/*,/dev/*'
Sort the output:
$ duf --sort size
Valid keys are: mountpoint, size, used, avail, usage, inodes, inodes_used, inodes_avail, inodes_usage, type, filesystem.
Show or hide specific columns:
$ duf --output mountpoint,size,usage
Valid keys are: mountpoint, size, used, avail, usage, inodes, inodes_used, inodes_avail, inodes_usage, type, filesystem.
List inode information instead of block usage:
$ duf --inodes
If duf doesn't detect your terminal's colors correctly, you can set a theme:
$ duf --theme light
duf highlights the availability & usage columns in red, green, or yellow, depending on how much space is still available. You can set your own thresholds:
$ duf --avail-threshold="10G,1G"
$ duf --usage-threshold="0.5,0.9"
If you prefer your output as JSON:
$ duf --json
`
manPage := mango.NewManPage(1, "duf", "Disk Usage/Free Utility").
WithLongDescription("Simple Disk Usage/Free Utility.\n"+
"Features:\n"+
"* User-friendly, colorful output.\n"+
"* Adjusts to your terminal's theme & width.\n"+
"* Sort the results according to your needs.\n"+
"* Groups & filters devices.\n"+
"* Can conveniently output JSON.").
WithSection("Usage", usage).
WithSection("Notes", "Portions of duf's code are copied and modified from https://github.com/shirou/gopsutil.\n"+
"gopsutil was written by WAKAYAMA Shirou and is distributed under BSD-3-Clause.").
WithSection("Authors", "duf was written by Christian Muehlhaeuser <https://github.com/muesli/duf>").
WithSection("Copyright", "Copyright (C) 2020-2022 Christian Muehlhaeuser <https://github.com/muesli>\n"+
"Released under MIT license.")
flag.VisitAll(mpflag.PFlagVisitor(manPage))
fmt.Println(manPage.Build(roff.NewDocument()))
os.Exit(0)
}
================================================
FILE: mounts.go
================================================
package main
import (
"bufio"
"os"
"strconv"
)
// Mount contains all metadata for a single filesystem mount.
type Mount struct {
Device string `json:"device"`
DeviceType string `json:"device_type"`
Mountpoint string `json:"mount_point"`
Fstype string `json:"fs_type"`
Type string `json:"type"`
Opts string `json:"opts"`
Total uint64 `json:"total"`
Free uint64 `json:"free"`
Used uint64 `json:"used"`
Inodes uint64 `json:"inodes"`
InodesFree uint64 `json:"inodes_free"`
InodesUsed uint64 `json:"inodes_used"`
Blocks uint64 `json:"blocks"`
BlockSize uint64 `json:"block_size"`
Metadata interface{} `json:"-"`
}
func readLines(filename string) ([]string, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close() //nolint:errcheck // ignore error
scanner := bufio.NewScanner(file)
var s []string
for scanner.Scan() {
s = append(s, scanner.Text())
}
return s, scanner.Err()
}
func unescapeFstab(path string) string {
escaped, err := strconv.Unquote(`"` + path + `"`)
if err != nil {
return path
}
return escaped
}
//nolint:unused // used on BSD
func byteToString(orig []byte) string {
n := -1
l := -1
for i, b := range orig {
// skip left side null
if l == -1 && b == 0 {
continue
}
if l == -1 {
l = i
}
if b == 0 {
break
}
n = i + 1
}
if n == -1 {
return string(orig)
}
return string(orig[l:n])
}
//nolint:unused // used on OpenBSD
func intToString(orig []int8) string {
ret := make([]byte, len(orig))
size := -1
for i, o := range orig {
if o == 0 {
size = i
break
}
ret[i] = byte(o)
}
if size == -1 {
size = len(orig)
}
return string(ret[0:size])
}
================================================
FILE: mounts_darwin.go
================================================
//go:build darwin
// +build darwin
package main
import (
"golang.org/x/sys/unix"
)
func (m *Mount) Stat() unix.Statfs_t {
return m.Metadata.(unix.Statfs_t)
}
func mounts() ([]Mount, []string, error) {
var ret []Mount
var warnings []string
count, err := unix.Getfsstat(nil, unix.MNT_WAIT)
if err != nil {
return nil, nil, err
}
fs := make([]unix.Statfs_t, count)
if _, err = unix.Getfsstat(fs, unix.MNT_WAIT); err != nil {
return nil, nil, err
}
for _, stat := range fs {
opts := "rw"
if stat.Flags&unix.MNT_RDONLY != 0 {
opts = "ro"
}
if stat.Flags&unix.MNT_SYNCHRONOUS != 0 {
opts += ",sync"
}
if stat.Flags&unix.MNT_NOEXEC != 0 {
opts += ",noexec"
}
if stat.Flags&unix.MNT_NOSUID != 0 {
opts += ",nosuid"
}
if stat.Flags&unix.MNT_UNION != 0 {
opts += ",union"
}
if stat.Flags&unix.MNT_ASYNC != 0 {
opts += ",async"
}
if stat.Flags&unix.MNT_DONTBROWSE != 0 {
opts += ",nobrowse"
}
if stat.Flags&unix.MNT_AUTOMOUNTED != 0 {
opts += ",automounted"
}
if stat.Flags&unix.MNT_JOURNALED != 0 {
opts += ",journaled"
}
if stat.Flags&unix.MNT_MULTILABEL != 0 {
opts += ",multilabel"
}
if stat.Flags&unix.MNT_NOATIME != 0 {
opts += ",noatime"
}
if stat.Flags&unix.MNT_NODEV != 0 {
opts += ",nodev"
}
device := byteToString(stat.Mntfromname[:])
mountPoint := byteToString(stat.Mntonname[:])
fsType := byteToString(stat.Fstypename[:])
if len(device) == 0 {
continue
}
d := Mount{
Device: device,
Mountpoint: mountPoint,
Fstype: fsType,
Type: fsType,
Opts: opts,
Metadata: stat,
Total: stat.Blocks * uint64(stat.Bsize),
Free: stat.Bavail * uint64(stat.Bsize),
Used: (stat.Blocks - stat.Bfree) * uint64(stat.Bsize),
Inodes: stat.Files,
InodesFree: stat.Ffree,
InodesUsed: stat.Files - stat.Ffree,
Blocks: stat.Blocks,
BlockSize: uint64(stat.Bsize),
}
d.DeviceType = deviceType(d)
ret = append(ret, d)
}
return ret, warnings, nil
}
================================================
FILE: mounts_freebsd.go
================================================
//go:build freebsd
// +build freebsd
package main
import (
"golang.org/x/sys/unix"
)
func (m *Mount) Stat() unix.Statfs_t {
return m.Metadata.(unix.Statfs_t)
}
func mounts() ([]Mount, []string, error) {
var ret []Mount
var warnings []string
count, err := unix.Getfsstat(nil, unix.MNT_WAIT)
if err != nil {
return nil, nil, err
}
fs := make([]unix.Statfs_t, count)
if _, err = unix.Getfsstat(fs, unix.MNT_WAIT); err != nil {
return nil, nil, err
}
for _, stat := range fs {
opts := "rw"
if stat.Flags&unix.MNT_RDONLY != 0 {
opts = "ro"
}
if stat.Flags&unix.MNT_SYNCHRONOUS != 0 {
opts += ",sync"
}
if stat.Flags&unix.MNT_NOEXEC != 0 {
opts += ",noexec"
}
if stat.Flags&unix.MNT_NOSUID != 0 {
opts += ",nosuid"
}
if stat.Flags&unix.MNT_UNION != 0 {
opts += ",union"
}
if stat.Flags&unix.MNT_ASYNC != 0 {
opts += ",async"
}
if stat.Flags&unix.MNT_SUIDDIR != 0 {
opts += ",suiddir"
}
if stat.Flags&unix.MNT_SOFTDEP != 0 {
opts += ",softdep"
}
if stat.Flags&unix.MNT_NOSYMFOLLOW != 0 {
opts += ",nosymfollow"
}
if stat.Flags&unix.MNT_GJOURNAL != 0 {
opts += ",gjournal"
}
if stat.Flags&unix.MNT_MULTILABEL != 0 {
opts += ",multilabel"
}
if stat.Flags&unix.MNT_ACLS != 0 {
opts += ",acls"
}
if stat.Flags&unix.MNT_NOATIME != 0 {
opts += ",noatime"
}
if stat.Flags&unix.MNT_NOCLUSTERR != 0 {
opts += ",noclusterr"
}
if stat.Flags&unix.MNT_NOCLUSTERW != 0 {
opts += ",noclusterw"
}
if stat.Flags&unix.MNT_NFS4ACLS != 0 {
opts += ",nfsv4acls"
}
device := byteToString(stat.Mntfromname[:])
mountPoint := byteToString(stat.Mntonname[:])
fsType := byteToString(stat.Fstypename[:])
if len(device) == 0 {
continue
}
d := Mount{
Device: device,
Mountpoint: mountPoint,
Fstype: fsType,
Type: fsType,
Opts: opts,
Metadata: stat,
Total: (uint64(stat.Blocks) * uint64(stat.Bsize)),
Free: (uint64(stat.Bavail) * uint64(stat.Bsize)),
Used: (uint64(stat.Blocks) - uint64(stat.Bfree)) * uint64(stat.Bsize),
Inodes: stat.Files,
InodesFree: uint64(stat.Ffree),
InodesUsed: stat.Files - uint64(stat.Ffree),
Blocks: uint64(stat.Blocks),
BlockSize: uint64(stat.Bsize),
}
d.DeviceType = deviceType(d)
ret = append(ret, d)
}
return ret, warnings, nil
}
================================================
FILE: mounts_linux.go
================================================
//go:build linux
// +build linux
package main
import (
"fmt"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"golang.org/x/sys/unix"
)
const (
// A line of self/mountinfo has the following structure:
// 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
// (0) (1) (2) (3) (4) (5) (6) (7) (8) (9) (10)
//
// (0) mount ID: unique identifier of the mount (may be reused after umount).
//mountinfoMountID = 0
// (1) parent ID: ID of parent (or of self for the top of the mount tree).
//mountinfoParentID = 1
// (2) major:minor: value of st_dev for files on filesystem.
//mountinfoMajorMinor = 2
// (3) root: root of the mount within the filesystem.
//mountinfoRoot = 3
// (4) mount point: mount point relative to the process's root.
mountinfoMountPoint = 4
// (5) mount options: per mount options.
mountinfoMountOpts = 5
// (6) optional fields: zero or more fields terminated by "-".
mountinfoOptionalFields = 6
// (7) separator between optional fields.
//mountinfoSeparator = 7
// (8) filesystem type: name of filesystem of the form.
mountinfoFsType = 8
// (9) mount source: filesystem specific information or "none".
mountinfoMountSource = 9
// (10) super options: per super block options.
mountinfoSuperOptions = 10
)
// Stat returns the mountpoint's stat information.
func (m *Mount) Stat() unix.Statfs_t {
return m.Metadata.(unix.Statfs_t)
}
func mounts() ([]Mount, []string, error) {
var warnings []string
filename := "/proc/self/mountinfo"
lines, err := readLines(filename)
if err != nil {
// wrapcheck: add context to the error.
return nil, nil, fmt.Errorf("reading mountinfo %q: %w", filename, err)
}
ret := make([]Mount, 0, len(lines))
for _, line := range lines {
nb, fields := parseMountInfoLine(line)
if nb == 0 {
continue
}
// if the number of fields does not match the structure of mountinfo,
// emit a warning and ignore the line.
if nb < 10 || nb > 11 {
warnings = append(warnings, fmt.Sprintf("found invalid mountinfo line: %s", line))
continue
}
// blockDeviceID := fields[mountinfoMountID]
mountPoint := fields[mountinfoMountPoint]
mountOpts := fields[mountinfoMountOpts]
fstype := fields[mountinfoFsType]
device := fields[mountinfoMountSource]
var stat unix.Statfs_t
err := unix.Statfs(mountPoint, &stat)
if err != nil {
if err != os.ErrPermission {
warnings = append(warnings, fmt.Sprintf("%s: %s", mountPoint, err))
continue
}
stat = unix.Statfs_t{}
}
d := Mount{
Device: device,
Mountpoint: mountPoint,
Fstype: fstype,
Type: fsTypeMap[int64(stat.Type)], //nolint:unconvert
Opts: mountOpts,
Metadata: stat,
Total: (uint64(stat.Blocks) * uint64(stat.Bsize)), //nolint:unconvert
Free: (uint64(stat.Bavail) * uint64(stat.Bsize)), //nolint:unconvert
Used: (uint64(stat.Blocks) - uint64(stat.Bfree)) * uint64(stat.Bsize), //nolint:unconvert
Inodes: stat.Files,
InodesFree: stat.Ffree,
InodesUsed: stat.Files - stat.Ffree,
Blocks: uint64(stat.Blocks), //nolint:unconvert
BlockSize: uint64(stat.Bsize),
}
d.DeviceType = deviceType(d)
// Resolve /dev/mapper/* device names.
if strings.HasPrefix(d.Device, "/dev/mapper/") {
re := regexp.MustCompile(`^/dev/mapper/(.*)-(.*)`)
match := re.FindAllStringSubmatch(d.Device, -1)
if len(match) > 0 && len(match[0]) == 3 {
d.Device = filepath.Join("/dev", match[0][1], match[0][2])
}
}
ret = append(ret, d)
}
return ret, warnings, nil
}
// splitMountInfoFields splits a mountinfo line into its fields.
// It treats spaces and tabs as field separators and decodes certain octal escapes.
func splitMountInfoFields(line string) []string {
var fields []string
var buf strings.Builder
for i := 0; i < len(line); i++ {
c := line[i]
// Treat both space and tab as separators
if c == ' ' || c == '\t' {
if buf.Len() > 0 {
fields = append(fields, buf.String())
buf.Reset()
}
continue
}
if c == '\\' && i+3 < len(line) {
oct := line[i+1 : i+4]
if v, err := strconv.ParseInt(oct, 8, 0); err == nil {
switch byte(v) {
case ' ', '\t', '\n':
buf.WriteByte(byte(v))
i += 3
continue
default:
// keep unknown escapes as-is
buf.WriteString("\\" + oct)
i += 3
continue
}
}
}
buf.WriteByte(c)
}
if buf.Len() > 0 {
fields = append(fields, buf.String())
}
return fields
}
// parseMountInfoLine parses a line of /proc/self/mountinfo and returns the
// amount of parsed fields and their values.
func parseMountInfoLine(line string) (int, [11]string) {
var fields [11]string
if len(line) == 0 || line[0] == '#' {
// ignore comments and empty lines
return 0, fields
}
all := splitMountInfoFields(line)
var i int
sawSep := false
sawSup := false
for _, f := range all {
if i >= len(fields) {
break
}
if i == mountinfoOptionalFields {
// (6) optional fields: zero or more fields of the form "tag[:value]"; see below.
// (7) separator: the end of the optional fields is marked by a single hyphen.
if f != "-" {
// Join tokens with spaces for mountinfoOptionalFields.
fields[i] = strings.TrimSpace(fields[i] + " " + f)
continue
}
// Found separator.
sawSep = true
i++
fields[i] = f
i++
continue
}
if i == mountinfoSuperOptions {
// join tokens with spaces for WSL2 path=... they are splitted around spaces.
fields[i] = strings.TrimSpace(fields[i] + " " + f)
sawSup = true
continue
}
// Default case: copy with unescape for certain fields
switch i {
case mountinfoMountPoint, mountinfoMountSource, mountinfoFsType:
fields[i] = unescapeFstab(f)
default:
fields[i] = f
}
i++
}
// Handle malformed line (no "-" found).
if !sawSep && len(all) > mountinfoOptionalFields {
i = mountinfoOptionalFields
}
// When super options are present, the index is one less than 11.
if sawSup {
i++
}
// clear trailing empties.
for j := i + 1; j < len(fields); j++ {
fields[j] = ""
}
return i, fields
}
================================================
FILE: mounts_linux_test.go
================================================
//go:build linux
// +build linux
package main
import (
"reflect"
"testing"
)
func TestGetFields(t *testing.T) {
var tt = []struct {
input string
number int
expected [11]string
}{
// Empty lines
{
input: "",
number: 0,
},
{
input: " ",
number: 0,
},
{
input: " ",
number: 0,
},
{
input: " ",
number: 0,
},
// Comments
{
input: "#",
number: 0,
},
{
input: "# ",
number: 0,
},
{
input: "# ",
number: 0,
},
{
input: "# I'm a lazy dog",
number: 0,
},
// Bad fields
{
input: "1 2",
number: 2,
expected: [11]string{"1", "2"},
},
{
input: "1 2",
number: 2,
expected: [11]string{"1", "2"},
},
{
input: "1 2 3",
number: 3,
expected: [11]string{"1", "2", "3"},
},
{
input: "1 2 3 4",
number: 4,
expected: [11]string{"1", "2", "3", "4"},
},
// No optional separator or no options
{
input: "1 2 3 4 5 6 7 NotASeparator 9 10 11",
number: 6,
expected: [11]string{"1", "2", "3", "4", "5", "6", "7 NotASeparator 9 10 11"},
},
{
input: "1 2 3 4 5 6 7 8 9 10 11",
number: 6,
expected: [11]string{"1", "2", "3", "4", "5", "6", "7 8 9 10 11"},
},
{
input: "1 2 3 4 5 6 - 9 10 11",
number: 11,
expected: [11]string{"1", "2", "3", "4", "5", "6", "", "-", "9", "10", "11"},
},
// Normal mount table line
{
input: "22 27 0:21 / /proc rw,nosuid,nodev,noexec,relatime shared:5 - proc proc rw",
number: 11,
expected: [11]string{"22", "27", "0:21", "/", "/proc", "rw,nosuid,nodev,noexec,relatime", "shared:5", "-", "proc", "proc", "rw"},
},
{
input: "31 23 0:27 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime shared:9 - cgroup2 cgroup2 rw,nsdelegate,memory_recursiveprot",
number: 11,
expected: [11]string{"31", "23", "0:27", "/", "/sys/fs/cgroup", "rw,nosuid,nodev,noexec,relatime", "shared:9", "-", "cgroup2", "cgroup2", "rw,nsdelegate,memory_recursiveprot"},
},
{
input: "40 27 0:33 / /tmp rw,nosuid,nodev shared:18 - tmpfs tmpfs",
number: 10,
expected: [11]string{"40", "27", "0:33", "/", "/tmp", "rw,nosuid,nodev", "shared:18", "-", "tmpfs", "tmpfs"},
},
{
input: "40 27 0:33 / /tmp rw,nosuid,nodev shared:18 shared:22 - tmpfs tmpfs",
number: 10,
expected: [11]string{"40", "27", "0:33", "/", "/tmp", "rw,nosuid,nodev", "shared:18 shared:22", "-", "tmpfs", "tmpfs"},
},
{
input: "50 27 0:33 / /tmp rw,nosuid,nodev - tmpfs tmpfs",
number: 10,
expected: [11]string{"50", "27", "0:33", "/", "/tmp", "rw,nosuid,nodev", "", "-", "tmpfs", "tmpfs"},
},
// Exceptional mount table lines
{
input: "328 27 0:73 / /mnt/a rw,relatime shared:206 - tmpfs - rw,inode64",
number: 11,
expected: [11]string{"328", "27", "0:73", "/", "/mnt/a", "rw,relatime", "shared:206", "-", "tmpfs", "-", "rw,inode64"},
},
{
input: "330 27 0:73 / /mnt/a rw,relatime shared:206 - tmpfs 👾 rw,inode64",
number: 11,
expected: [11]string{"330", "27", "0:73", "/", "/mnt/a", "rw,relatime", "shared:206", "-", "tmpfs", "👾", "rw,inode64"},
},
{
input: "335 27 0:73 / /mnt/👾 rw,relatime shared:206 - tmpfs 👾 rw,inode64",
number: 11,
expected: [11]string{"335", "27", "0:73", "/", "/mnt/👾", "rw,relatime", "shared:206", "-", "tmpfs", "👾", "rw,inode64"},
},
{
input: "509 27 0:78 / /mnt/- rw,relatime shared:223 - tmpfs 👾 rw,inode64",
number: 11,
expected: [11]string{"509", "27", "0:78", "/", "/mnt/-", "rw,relatime", "shared:223", "-", "tmpfs", "👾", "rw,inode64"},
},
{
input: "362 27 0:76 / /mnt/a\\040b rw,relatime shared:215 - tmpfs 👾 rw,inode64",
number: 11,
expected: [11]string{"362", "27", "0:76", "/", "/mnt/a b", "rw,relatime", "shared:215", "-", "tmpfs", "👾", "rw,inode64"},
},
{
input: "1 2 3:3 / /mnt/\\011 rw shared:7 - tmpfs - rw,inode64",
number: 11,
expected: [11]string{"1", "2", "3:3", "/", "/mnt/\t", "rw", "shared:7", "-", "tmpfs", "-", "rw,inode64"},
},
{
input: "11 2 3:3 / /mnt/a\\012b rw shared:7 - tmpfs - rw,inode64",
number: 11,
expected: [11]string{"11", "2", "3:3", "/", "/mnt/a\nb", "rw", "shared:7", "-", "tmpfs", "-", "rw,inode64"},
},
{
input: "111 2 3:3 / /mnt/a\\134b rw shared:7 - tmpfs - rw,inode64",
number: 11,
expected: [11]string{"111", "2", "3:3", "/", "/mnt/a\\b", "rw", "shared:7", "-", "tmpfs", "-", "rw,inode64"},
},
{
input: "1111 2 3:3 / /mnt/a\\042b rw shared:7 - tmpfs - rw,inode64",
number: 11,
expected: [11]string{"1111", "2", "3:3", "/", "/mnt/a\"b", "rw", "shared:7", "-", "tmpfs", "-", "rw,inode64"},
},
// WSL2 9p mount table line.
{
input: `380 383 0:33 / /usr/lib/wsl/drivers ro,nosuid,nodev,noatime - 9p drivers ro,dirsync,aname=drivers;fmask=222;dmask=222,mmap,access=client,msize=65536,trans=fd,rfd=8,wfd=8`,
number: 11,
expected: [11]string{"380", "383", "0:33", "/", "/usr/lib/wsl/drivers", "ro,nosuid,nodev,noatime", "", "-", "9p", "drivers", "ro,dirsync,aname=drivers;fmask=222;dmask=222,mmap,access=client,msize=65536,trans=fd,rfd=8,wfd=8"},
},
{
input: `488 383 0:128 / /mnt/c rw,noatime - 9p C:\134 rw,dirsync,aname=drvfs;path=C:\;uid=1000;gid=1000;symlinkroot=/mnt/,mmap,access=client,msize=65536,trans=fd,rfd=5,wfd=5`,
number: 11,
expected: [11]string{"488", "383", "0:128", "/", "/mnt/c", "rw,noatime", "", "-", "9p", "C:\\", "rw,dirsync,aname=drvfs;path=C:\\;uid=1000;gid=1000;symlinkroot=/mnt/,mmap,access=client,msize=65536,trans=fd,rfd=5,wfd=5"},
},
{
input: `516 78 0:136 / /Docker/host rw,noatime - 9p C:\134Program\040Files\134Docker\134Docker\134resources rw,dirsync,aname=drvfs;path=C:\Program Files\Docker\Docker\resources;symlinkroot=/mnt/,mmap,access=client,msize=65536,trans=fd,rfd=3,wfd=3`,
number: 11,
expected: [11]string{"516", "78", "0:136", "/", "/Docker/host", "rw,noatime", "", "-", "9p", "C:\\Program Files\\Docker\\Docker\\resources", "rw,dirsync,aname=drvfs;path=C:\\Program Files\\Docker\\Docker\\resources;symlinkroot=/mnt/,mmap,access=client,msize=65536,trans=fd,rfd=3,wfd=3"},
},
}
for _, tc := range tt {
nb, actual := parseMountInfoLine(tc.input)
if nb != tc.number || !reflect.DeepEqual(actual, tc.expected) {
t.Errorf("\nparseMountInfoLine(%q) == \n(%d) %q, \nexpected (%d) %q", tc.input, nb, actual, tc.number, tc.expected)
}
}
}
================================================
FILE: mounts_openbsd.go
================================================
//go:build openbsd
// +build openbsd
package main
import (
"golang.org/x/sys/unix"
)
func (m *Mount) Stat() unix.Statfs_t {
return m.Metadata.(unix.Statfs_t)
}
func mounts() ([]Mount, []string, error) {
var ret []Mount
var warnings []string
count, err := unix.Getfsstat(nil, unix.MNT_WAIT)
if err != nil {
return nil, nil, err
}
fs := make([]unix.Statfs_t, count)
if _, err = unix.Getfsstat(fs, unix.MNT_WAIT); err != nil {
return nil, nil, err
}
for _, stat := range fs {
opts := "rw"
if stat.F_flags&unix.MNT_RDONLY != 0 {
opts = "ro"
}
if stat.F_flags&unix.MNT_SYNCHRONOUS != 0 {
opts += ",sync"
}
if stat.F_flags&unix.MNT_NOEXEC != 0 {
opts += ",noexec"
}
if stat.F_flags&unix.MNT_NOSUID != 0 {
opts += ",nosuid"
}
if stat.F_flags&unix.MNT_NODEV != 0 {
opts += ",nodev"
}
if stat.F_flags&unix.MNT_ASYNC != 0 {
opts += ",async"
}
if stat.F_flags&unix.MNT_SOFTDEP != 0 {
opts += ",softdep"
}
if stat.F_flags&unix.MNT_NOATIME != 0 {
opts += ",noatime"
}
if stat.F_flags&unix.MNT_WXALLOWED != 0 {
opts += ",wxallowed"
}
device := byteToString(stat.F_mntfromname[:])
mountPoint := byteToString(stat.F_mntonname[:])
fsType := byteToString(stat.F_fstypename[:])
if len(device) == 0 {
continue
}
d := Mount{
Device: device,
Mountpoint: mountPoint,
Fstype: fsType,
Type: fsType,
Opts: opts,
Metadata: stat,
Total: (uint64(stat.F_blocks) * uint64(stat.F_bsize)),
Free: (uint64(stat.F_bavail) * uint64(stat.F_bsize)),
Used: (uint64(stat.F_blocks) - uint64(stat.F_bfree)) * uint64(stat.F_bsize),
Inodes: stat.F_files,
InodesFree: uint64(stat.F_ffree),
InodesUsed: stat.F_files - uint64(stat.F_ffree),
Blocks: uint64(stat.F_blocks),
BlockSize: uint64(stat.F_bsize),
}
d.DeviceType = deviceType(d)
ret = append(ret, d)
}
return ret, warnings, nil
}
================================================
FILE: mounts_windows.go
================================================
//go:build windows
// +build windows
package main
import (
"fmt"
"golang.org/x/sys/windows"
"math"
"path/filepath"
"strings"
"syscall"
"unsafe"
)
// Local devices
const (
guidBufLen = windows.MAX_PATH + 1
volumeNameBufLen = windows.MAX_PATH + 1
rootPathBufLen = windows.MAX_PATH + 1
fileSystemBufLen = windows.MAX_PATH + 1
)
func getMountPoint(guidBuf []uint16) (mountPoint string, err error) {
var rootPathLen uint32
rootPathBuf := make([]uint16, rootPathBufLen)
err = windows.GetVolumePathNamesForVolumeName(&guidBuf[0], &rootPathBuf[0], rootPathBufLen*2, &rootPathLen)
if err != nil && err.(windows.Errno) == windows.ERROR_MORE_DATA {
// Retry if buffer size is too small
rootPathBuf = make([]uint16, (rootPathLen+1)/2)
err = windows.GetVolumePathNamesForVolumeName(
&guidBuf[0], &rootPathBuf[0], rootPathLen, &rootPathLen)
}
return windows.UTF16ToString(rootPathBuf), err
}
func getVolumeInfo(guidOrMountPointBuf []uint16) (volumeName string, fsType string, err error) {
volumeNameBuf := make([]uint16, volumeNameBufLen)
fsTypeBuf := make([]uint16, fileSystemBufLen)
err = windows.GetVolumeInformation(&guidOrMountPointBuf[0], &volumeNameBuf[0], volumeNameBufLen*2,
nil, nil, nil,
&fsTypeBuf[0], fileSystemBufLen*2)
return windows.UTF16ToString(volumeNameBuf), windows.UTF16ToString(fsTypeBuf), err
}
func getSpaceInfo(guidOrMountPointBuf []uint16) (totalBytes uint64, freeBytes uint64, err error) {
err = windows.GetDiskFreeSpaceEx(&guidOrMountPointBuf[0], nil, &totalBytes, &freeBytes)
return
}
func getClusterInfo(guidOrMountPointBuf []uint16) (totalClusters uint32, clusterSize uint32, err error) {
var sectorsPerCluster uint32
var bytesPerSector uint32
err = GetDiskFreeSpace(&guidOrMountPointBuf[0], §orsPerCluster, &bytesPerSector, nil, &totalClusters)
clusterSize = bytesPerSector * sectorsPerCluster
return
}
func getMount(guidOrMountPointBuf []uint16, isGUID bool) (m Mount, skip bool, warnings []string) {
var err error
guidOrMountPoint := windows.UTF16ToString(guidOrMountPointBuf)
mountPoint := guidOrMountPoint
if isGUID {
mountPoint, err = getMountPoint(guidOrMountPointBuf)
if err != nil {
warnings = append(warnings, fmt.Sprintf("%s: %s", guidOrMountPoint, err))
}
// Skip unmounted volumes
if len(mountPoint) == 0 {
skip = true
return
}
}
// Get volume name & filesystem type
volumeName, fsType, err := getVolumeInfo(guidOrMountPointBuf)
if err != nil {
warnings = append(warnings, fmt.Sprintf("%s: %s", guidOrMountPoint, err))
}
// Get space info
totalBytes, freeBytes, err := getSpaceInfo(guidOrMountPointBuf)
if err != nil {
warnings = append(warnings, fmt.Sprintf("%s: %s", guidOrMountPoint, err))
}
// Get cluster info
totalClusters, clusterSize, err := getClusterInfo(guidOrMountPointBuf)
if err != nil {
warnings = append(warnings, fmt.Sprintf("%s: %s", guidOrMountPoint, err))
}
m = Mount{
Device: volumeName,
Mountpoint: mountPoint,
Fstype: fsType,
Type: fsType,
Opts: "",
Total: totalBytes,
Free: freeBytes,
Used: totalBytes - freeBytes,
Blocks: uint64(totalClusters),
BlockSize: uint64(clusterSize),
}
m.DeviceType = deviceType(m)
return
}
func getMountFromGUID(guidBuf []uint16) (m Mount, skip bool, warnings []string) {
m, skip, warnings = getMount(guidBuf, true)
// Use GUID as volume name if no label was set
if len(m.Device) == 0 {
m.Device = windows.UTF16ToString(guidBuf)
}
return
}
func getMountFromMountPoint(mountPointBuf []uint16) (m Mount, warnings []string) {
m, _, warnings = getMount(mountPointBuf, false)
// Use mount point as volume name if no label was set
if len(m.Device) == 0 {
m.Device = windows.UTF16ToString(mountPointBuf)
}
return m, warnings
}
func appendLocalMounts(mounts []Mount, warnings []string) ([]Mount, []string, error) {
guidBuf := make([]uint16, guidBufLen)
hFindVolume, err := windows.FindFirstVolume(&guidBuf[0], guidBufLen*2)
if err != nil {
return mounts, warnings, err
}
VolumeLoop:
for ; ; err = windows.FindNextVolume(hFindVolume, &guidBuf[0], guidBufLen*2) {
if err != nil {
switch err.(windows.Errno) {
case windows.ERROR_NO_MORE_FILES:
break VolumeLoop
default:
warnings = append(warnings, fmt.Sprintf("%s: %s", windows.UTF16ToString(guidBuf), err))
continue VolumeLoop
}
}
if m, skip, w := getMountFromGUID(guidBuf); !skip {
mounts = append(mounts, m)
warnings = append(warnings, w...)
}
}
if err = windows.FindVolumeClose(hFindVolume); err != nil {
warnings = append(warnings, fmt.Sprintf("%s", err))
}
return mounts, warnings, nil
}
// Network devices
func getMountFromNetResource(netResource NetResource) (m Mount, warnings []string) {
mountPoint := windows.UTF16PtrToString(netResource.LocalName)
if !strings.HasSuffix(mountPoint, string(filepath.Separator)) {
mountPoint += string(filepath.Separator)
}
mountPointBuf := windows.StringToUTF16(mountPoint)
m, _, warnings = getMount(mountPointBuf, false)
// Use remote name as volume name if no label was set
if len(m.Device) == 0 {
m.Device = windows.UTF16PtrToString(netResource.RemoteName)
}
return
}
func appendNetworkMounts(mounts []Mount, warnings []string) ([]Mount, []string, error) {
hEnumResource, err := WNetOpenEnum(RESOURCE_CONNECTED, RESOURCETYPE_DISK, RESOURCEUSAGE_CONNECTABLE, nil)
if err != nil {
return mounts, warnings, err
}
EnumLoop:
for {
// Reference: https://docs.microsoft.com/en-us/windows/win32/wnet/enumerating-network-resources
var nrBuf [16384]byte
count := uint32(math.MaxUint32)
size := uint32(len(nrBuf))
if err := WNetEnumResource(hEnumResource, &count, &nrBuf[0], &size); err != nil {
switch err.(windows.Errno) {
case windows.ERROR_NO_MORE_ITEMS:
break EnumLoop
default:
warnings = append(warnings, err.Error())
break EnumLoop
}
}
for i := uint32(0); i < count; i++ {
nr := (*NetResource)(unsafe.Pointer(&nrBuf[uintptr(i)*NetResourceSize]))
m, w := getMountFromNetResource(*nr)
mounts = append(mounts, m)
warnings = append(warnings, w...)
}
}
if err = WNetCloseEnum(hEnumResource); err != nil {
warnings = append(warnings, fmt.Sprintf("%s", err))
}
return mounts, warnings, nil
}
func mountPointAlreadyPresent(mounts []Mount, mountPoint string) bool {
for _, m := range mounts {
if m.Mountpoint == mountPoint {
return true
}
}
return false
}
func appendLogicalDrives(mounts []Mount, warnings []string) ([]Mount, []string) {
driveBitmap, err := windows.GetLogicalDrives()
if err != nil {
warnings = append(warnings, fmt.Sprintf("GetLogicalDrives(): %s", err))
return mounts, warnings
}
for drive := 'A'; drive <= 'Z'; drive, driveBitmap = drive+1, driveBitmap>>1 {
if driveBitmap&0x1 == 0 {
continue
}
mountPoint := fmt.Sprintf("%c:\\", drive)
if mountPointAlreadyPresent(mounts, mountPoint) {
continue
}
mountPointBuf := windows.StringToUTF16(mountPoint)
m, w := getMountFromMountPoint(mountPointBuf)
mounts = append(mounts, m)
warnings = append(warnings, w...)
}
return mounts, warnings
}
func mounts() (ret []Mount, warnings []string, err error) {
ret = make([]Mount, 0)
// Local devices
if ret, warnings, err = appendLocalMounts(ret, warnings); err != nil {
return
}
// Network devices
if ret, warnings, err = appendNetworkMounts(ret, warnings); err != nil {
return
}
// Logical devices (from GetLogicalDrives bitflag)
// Check any possible logical drives, in case of some special virtual devices, such as RAM disk
ret, warnings = appendLogicalDrives(ret, warnings)
return ret, warnings, nil
}
// Windows API
const (
// Windows Networking const
// Reference: https://docs.microsoft.com/en-us/windows/win32/api/winnetwk/nf-winnetwk-wnetopenenumw
RESOURCE_CONNECTED = 0x00000001
RESOURCE_GLOBALNET = 0x00000002
RESOURCE_REMEMBERED = 0x00000003
RESOURCE_RECENT = 0x00000004
RESOURCE_CONTEXT = 0x00000005
RESOURCETYPE_ANY = 0x00000000
RESOURCETYPE_DISK = 0x00000001
RESOURCETYPE_PRINT = 0x00000002
RESOURCETYPE_RESERVED = 0x00000008
RESOURCETYPE_UNKNOWN = 0xFFFFFFFF
RESOURCEUSAGE_CONNECTABLE = 0x00000001
RESOURCEUSAGE_CONTAINER = 0x00000002
RESOURCEUSAGE_NOLOCALDEVICE = 0x00000004
RESOURCEUSAGE_SIBLING = 0x00000008
RESOURCEUSAGE_ATTACHED = 0x00000010
RESOURCEUSAGE_ALL = RESOURCEUSAGE_CONNECTABLE | RESOURCEUSAGE_CONTAINER | RESOURCEUSAGE_ATTACHED
RESOURCEUSAGE_RESERVED = 0x80000000
)
var (
// Windows syscall
modmpr = windows.NewLazySystemDLL("mpr.dll")
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
procWNetOpenEnumW = modmpr.NewProc("WNetOpenEnumW")
procWNetCloseEnum = modmpr.NewProc("WNetCloseEnum")
procWNetEnumResourceW = modmpr.NewProc("WNetEnumResourceW")
procGetDiskFreeSpaceW = modkernel32.NewProc("GetDiskFreeSpaceW")
NetResourceSize = unsafe.Sizeof(NetResource{})
)
// Reference: https://docs.microsoft.com/en-us/windows/win32/api/winnetwk/ns-winnetwk-netresourcew
type NetResource struct {
Scope uint32
Type uint32
DisplayType uint32
Usage uint32
LocalName *uint16
RemoteName *uint16
Comment *uint16
Provider *uint16
}
// Reference: https://docs.microsoft.com/en-us/windows/win32/api/winnetwk/nf-winnetwk-wnetopenenumw
func WNetOpenEnum(scope uint32, resourceType uint32, usage uint32, resource *NetResource) (handle windows.Handle, err error) {
r1, _, e1 := syscall.Syscall6(procWNetOpenEnumW.Addr(), 5, uintptr(scope), uintptr(resourceType), uintptr(usage), uintptr(unsafe.Pointer(resource)), uintptr(unsafe.Pointer(&handle)), 0)
if r1 != windows.NO_ERROR {
if e1 != 0 {
err = e1
} else {
err = syscall.EINVAL
}
}
return
}
// Reference: https://docs.microsoft.com/en-us/windows/win32/api/winnetwk/nf-winnetwk-wnetenumresourcew
func WNetEnumResource(enumResource windows.Handle, count *uint32, buffer *byte, bufferSize *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procWNetEnumResourceW.Addr(), 4, uintptr(enumResource), uintptr(unsafe.Pointer(count)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(bufferSize)), 0, 0)
if r1 != windows.NO_ERROR {
if e1 != 0 {
err = e1
} else {
err = syscall.EINVAL
}
}
return
}
// Reference: https://docs.microsoft.com/en-us/windows/win32/api/winnetwk/nf-winnetwk-wnetcloseenum
func WNetCloseEnum(enumResource windows.Handle) (err error) {
r1, _, e1 := syscall.Syscall(procWNetCloseEnum.Addr(), 1, uintptr(enumResource), 0, 0)
if r1 != windows.NO_ERROR {
if e1 != 0 {
err = e1
} else {
err = syscall.EINVAL
}
}
return
}
// Reference: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getdiskfreespacew
func GetDiskFreeSpace(directoryName *uint16, sectorsPerCluster *uint32, bytesPerSector *uint32, numberOfFreeClusters *uint32, totalNumberOfClusters *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procGetDiskFreeSpaceW.Addr(), 5, uintptr(unsafe.Pointer(directoryName)), uintptr(unsafe.Pointer(sectorsPerCluster)), uintptr(unsafe.Pointer(bytesPerSector)), uintptr(unsafe.Pointer(numberOfFreeClusters)), uintptr(unsafe.Pointer(totalNumberOfClusters)), 0)
if r1 == 0 {
if e1 != 0 {
err = e1
} else {
err = syscall.EINVAL
}
}
return
}
================================================
FILE: style.go
================================================
package main
import "github.com/mattn/go-runewidth"
func defaultStyleName() string {
/*
Due to a bug in github.com/mattn/go-runewidth v0.0.9, the width of unicode rune(such as '╭') could not be correctly
calculated. Degrade to ascii to prevent broken table structure. Remove this once the bug is fixed.
*/
if runewidth.RuneWidth('╭') > 1 {
return "ascii"
}
return "unicode"
}
================================================
FILE: table.go
================================================
package main
import (
"fmt"
"os"
"regexp"
"strconv"
"strings"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/mattn/go-runewidth"
"github.com/muesli/termenv"
)
// TableOptions contains all options for the table.
type TableOptions struct {
Columns []int
SortBy int
Style table.Style
StyleName string
}
// Column defines a column.
type Column struct {
ID string
Name string
SortIndex int
Width int
}
// "Mounted on", "Size", "Used", "Avail", "Use%", "Inodes", "IUsed", "IAvail", "IUse%", "Type", "Filesystem"
// mountpoint, size, used, avail, usage, inodes, inodes_used, inodes_avail, inodes_usage, type, filesystem
var columns = []Column{
{ID: "mountpoint", Name: "Mounted on", SortIndex: 1},
{ID: "size", Name: "Size", SortIndex: 12, Width: 7},
{ID: "used", Name: "Used", SortIndex: 13, Width: 7},
{ID: "avail", Name: "Avail", SortIndex: 14, Width: 7},
{ID: "usage", Name: "Use%", SortIndex: 15, Width: 6},
{ID: "inodes", Name: "Inodes", SortIndex: 16, Width: 7},
{ID: "inodes_used", Name: "IUsed", SortIndex: 17, Width: 7},
{ID: "inodes_avail", Name: "IAvail", SortIndex: 18, Width: 7},
{ID: "inodes_usage", Name: "IUse%", SortIndex: 19, Width: 6},
{ID: "type", Name: "Type", SortIndex: 10},
{ID: "filesystem", Name: "Filesystem", SortIndex: 11},
}
// initializeTable sets up the table writer with initial configurations.
func initializeTable(tab table.Writer, opts TableOptions) {
tab.SetAllowedRowLength(int(*width))
tab.SetOutputMirror(os.Stdout)
tab.Style().Options.SeparateColumns = true
tab.SetStyle(opts.Style)
}
// appendHeaders adds the header row to the table.
func appendHeaders(tab table.Writer) {
headers := table.Row{}
for _, v := range columns {
headers = append(headers, v.Name)
}
tab.AppendHeader(headers)
}
// appendRows adds data rows to the table for each mount.
func appendRows(tab table.Writer, m []Mount) {
for _, v := range m {
var usage, inodeUsage float64
if v.Total > 0 {
usage = float64(v.Used) / float64(v.Total)
if usage > 1.0 {
usage = 1.0
}
}
if v.Inodes > 0 {
inodeUsage = float64(v.InodesUsed) / float64(v.Inodes)
if inodeUsage > 1.0 {
inodeUsage = 1.0
}
}
tab.AppendRow([]interface{}{
termenv.String(v.Mountpoint).Foreground(theme.colorBlue), // mounted on
v.Total, // size
v.Used, // used
v.Free, // avail
usage, // use%
v.Inodes, // inodes
v.InodesUsed, // inodes used
v.InodesFree, // inodes avail
inodeUsage, // inodes use%
termenv.String(v.Fstype).Foreground(theme.colorGray), // type
termenv.String(v.Device).Foreground(theme.colorGray), // filesystem
v.Total, // size sorting helper
v.Used, // used sorting helper
v.Free, // avail sorting helper
usage, // use% sorting helper
v.Inodes, // inodes sorting helper
v.InodesUsed, // inodes used sorting helper
v.InodesFree, // inodes avail sorting helper
inodeUsage, // inodes use% sorting helper
})
}
}
// computeMaxContentWidths calculates the maximum content width for each visible column.
func computeMaxContentWidths(m []Mount, opts TableOptions) map[int]int {
visibleCols := append([]int{}, opts.Columns...)
maxColContent := map[int]int{}
// Seed with headers
for _, ci := range visibleCols {
maxColContent[ci] = runewidth.StringWidth(columns[ci-1].Name)
}
for _, v := range m {
if inColumns(opts.Columns, 1) {
if w := runewidth.StringWidth(v.Mountpoint); w > maxColContent[1] {
maxColContent[1] = w
}
}
if inColumns(opts.Columns, 2) {
if w := runewidth.StringWidth(sizeToString(v.Total)); w > maxColContent[2] {
maxColContent[2] = w
}
}
if inColumns(opts.Columns, 3) {
if w := runewidth.StringWidth(sizeToString(v.Used)); w > maxColContent[3] {
maxColContent[3] = w
}
}
if inColumns(opts.Columns, 4) {
if w := runewidth.StringWidth(sizeToString(v.Free)); w > maxColContent[4] {
maxColContent[4] = w
}
}
if inColumns(opts.Columns, 5) {
var usage float64
if v.Total > 0 {
usage = float64(v.Used) / float64(v.Total)
if usage > 1.0 {
usage = 1.0
}
}
percentStr := fmt.Sprintf("%.1f%%", usage*100)
if w := runewidth.StringWidth(percentStr); w > maxColContent[5] {
maxColContent[5] = w
}
}
if inColumns(opts.Columns, 6) {
if w := runewidth.StringWidth(strconv.FormatUint(v.Inodes, 10)); w > maxColContent[6] {
maxColContent[6] = w
}
}
if inColumns(opts.Columns, 7) {
if w := runewidth.StringWidth(strconv.FormatUint(v.InodesUsed, 10)); w > maxColContent[7] {
maxColContent[7] = w
}
}
if inColumns(opts.Columns, 8) {
if w := runewidth.StringWidth(strconv.FormatUint(v.InodesFree, 10)); w > maxColContent[8] {
maxColContent[8] = w
}
}
if inColumns(opts.Columns, 9) {
var usage float64
if v.Inodes > 0 {
usage = float64(v.InodesUsed) / float64(v.Inodes)
if usage > 1.0 {
usage = 1.0
}
}
percentStr := fmt.Sprintf("%.1f%%", usage*100)
if w := runewidth.StringWidth(percentStr); w > maxColContent[9] {
maxColContent[9] = w
}
}
if inColumns(opts.Columns, 10) {
if w := runewidth.StringWidth(v.Fstype); w > maxColContent[10] {
maxColContent[10] = w
}
}
if inColumns(opts.Columns, 11) {
if w := runewidth.StringWidth(v.Device); w > maxColContent[11] {
maxColContent[11] = w
}
}
}
return maxColContent
}
// computeAssignedWidths computes the assigned widths for dynamic columns (1, 10, 11).
func computeAssignedWidths(maxColContent map[int]int, opts TableOptions) (map[int]int, int) {
visibleCols := append([]int{}, opts.Columns...)
nVis := len(visibleCols)
// Non-content overhead
sepWidth := 1
paddingPerCol := 2
overhead := (nVis+1)*sepWidth + nVis*paddingPerCol
totalAllowed := int(*width)
// Determine targets and their need
targets := []int{}
weights := map[int]float64{1: 0.4, 10: 0.2, 11: 0.4}
weightSum := 0.0
for _, t := range []int{1, 10, 11} {
if inColumns(opts.Columns, t) {
targets = append(targets, t)
weightSum += weights[t]
}
}
// Sum fixed widths of non-target visible columns
fixedContentWidth := 0
for _, ci := range visibleCols {
if ci == 1 || ci == 10 || ci == 11 {
continue
}
fixedContentWidth += maxColContent[ci]
}
availableContent := totalAllowed - overhead - fixedContentWidth
if availableContent < 0 {
availableContent = 0
}
// Cap target allocations by their max content need
assigned := map[int]int{}
used := 0
if availableContent > 0 && len(targets) > 0 {
for _, t := range targets {
share := int(float64(availableContent) * (weights[t] / weightSum))
if share > maxColContent[t] {
share = maxColContent[t]
}
assigned[t] = share
used += share
}
// remainder distribution
remainder := availableContent - used
for remainder > 0 {
bestCol := 0
bestNeed := 0
for _, t := range targets {
need := maxColContent[t] - assigned[t]
if need > bestNeed {
bestNeed = need
bestCol = t
}
}
if bestNeed <= 0 {
break
}
take := remainder
if take > bestNeed {
take = bestNeed
}
assigned[bestCol] += take
remainder -= take
}
}
// Calculate final slack
predictedTotal := overhead + fixedContentWidth
for _, t := range targets {
predictedTotal += assigned[t]
}
slack := totalAllowed - predictedTotal
return assigned, slack
}
// setColumnConfigs configures the columns for the table.
func setColumnConfigs(tab table.Writer, maxColContent map[int]int, assigned map[int]int, opts TableOptions, barTransformerFunc func(interface{}) string) {
cfgs := []table.ColumnConfig{
{Number: 1, Hidden: !inColumns(opts.Columns, 1), WidthMax: assigned[1]},
{Number: 2, Hidden: !inColumns(opts.Columns, 2), Transformer: sizeTransformer, Align: text.AlignRight, AlignHeader: text.AlignRight, WidthMax: maxColContent[2]},
{Number: 3, Hidden: !inColumns(opts.Columns, 3), Transformer: sizeTransformer, Align: text.AlignRight, AlignHeader: text.AlignRight, WidthMax: maxColContent[3]},
{Number: 4, Hidden: !inColumns(opts.Columns, 4), Transformer: spaceTransformer, Align: text.AlignRight, AlignHeader: text.AlignRight, WidthMax: maxColContent[4]},
{Number: 5, Hidden: !inColumns(opts.Columns, 5), Transformer: barTransformerFunc, AlignHeader: text.AlignCenter, WidthMax: maxColContent[5]},
{Number: 6, Hidden: !inColumns(opts.Columns, 6), Align: text.AlignRight, AlignHeader: text.AlignRight, WidthMax: maxColContent[6]},
{Number: 7, Hidden: !inColumns(opts.Columns, 7), Align: text.AlignRight, AlignHeader: text.AlignRight, WidthMax: maxColContent[7]},
{Number: 8, Hidden: !inColumns(opts.Columns, 8), Align: text.AlignRight, AlignHeader: text.AlignRight, WidthMax: maxColContent[8]},
{Number: 9, Hidden: !inColumns(opts.Columns, 9), Transformer: barTransformerFunc, AlignHeader: text.AlignCenter, WidthMax: maxColContent[9]},
{Number: 10, Hidden: !inColumns(opts.Columns, 10), WidthMax: assigned[10]},
{Number: 11, Hidden: !inColumns(opts.Columns, 11), WidthMax: assigned[11]},
{Number: 12, Hidden: true}, // sortBy helper for size
{Number: 13, Hidden: true}, // sortBy helper for used
{Number: 14, Hidden: true}, // sortBy helper for avail
{Number: 15, Hidden: true}, // sortBy helper for usage
{Number: 16, Hidden: true}, // sortBy helper for inodes size
{Number: 17, Hidden: true}, // sortBy helper for inodes used
{Number: 18, Hidden: true}, // sortBy helper for inodes avail
{Number: 19, Hidden: true}, // sortBy helper for inodes usage
}
tab.SetColumnConfigs(cfgs)
}
// printTable prints an individual table of mounts.
func printTable(title string, m []Mount, opts TableOptions) {
tab := table.NewWriter()
initializeTable(tab, opts)
appendHeaders(tab)
appendRows(tab, m)
if tab.Length() == 0 {
return
}
maxColContent := computeMaxContentWidths(m, opts)
assigned, slack := computeAssignedWidths(maxColContent, opts)
origPercentWidth5 := maxColContent[5]
origPercentWidth9 := maxColContent[9]
percentWidth := origPercentWidth5
if origPercentWidth9 > percentWidth {
percentWidth = origPercentWidth9
}
barWidth := 0
numBars := 0
if inColumns(opts.Columns, 5) {
numBars++
}
if inColumns(opts.Columns, 9) {
numBars++
}
if numBars > 0 && slack >= 6 {
// Each bar consumes: barWidth + 1 (for space)
// So for numBars, total consumption is: numBars * (barWidth + 1)
maxBarWidth := min((slack/numBars)-1, 20)
if maxBarWidth > 0 {
barWidth = maxBarWidth
if inColumns(opts.Columns, 5) {
maxColContent[5] = barWidth + 1 + percentWidth
}
if inColumns(opts.Columns, 9) {
maxColContent[9] = barWidth + 1 + percentWidth
}
}
}
// Define barTransformerFunc
barTransformerFunc := func(val interface{}) string {
usage := val.(float64)
if barWidth <= 0 {
s := fmt.Sprintf("%*s", percentWidth, fmt.Sprintf("%.1f%%", usage*100))
return termenv.String(s).String()
}
bw := barWidth
var filledChar, halfChar, emptyChar string
if opts.StyleName == "unicode" {
filledChar = "█"
halfChar = "▌"
emptyChar = " "
} else {
bw -= 2
filledChar = "#"
halfChar = "#"
emptyChar = "."
}
filled := int(usage * float64(bw))
partial := usage*float64(bw) - float64(filled)
empty := bw - filled
var filledStr, emptyStr string
filledStr = strings.Repeat(filledChar, filled)
// If we have a sufficiently large partial, render a half block.
if partial >= 0.5 {
filledStr += halfChar
empty--
}
if empty < 0 {
empty = 0
}
emptyStr = strings.Repeat(emptyChar, empty)
var format string
if opts.StyleName == "unicode" {
format = "%s%s %*s"
} else {
format = "[%s%s] %*s"
}
// Apply colors
redUsage, _ := strconv.ParseFloat(strings.Split(*usageThreshold, ",")[1], 64)
yellowUsage, _ := strconv.ParseFloat(strings.Split(*usageThreshold, ",")[0], 64)
var fgColor termenv.Color
switch {
case usage >= redUsage:
fgColor = theme.colorRed
case usage >= yellowUsage:
fgColor = theme.colorYellow
default:
fgColor = theme.colorGreen
}
filledPart := termenv.String(filledStr).Foreground(fgColor)
emptyPart := termenv.String(emptyStr)
if opts.StyleName == "unicode" {
// Add background to filled part to prevent black spaces in half blocks
// Use a background color that complements the foreground
var bgColor termenv.Color
switch {
case usage >= redUsage:
bgColor = theme.colorBgRed
case usage >= yellowUsage:
bgColor = theme.colorBgYellow
default:
bgColor = theme.colorBgGreen
}
filledPart = filledPart.Background(bgColor).Foreground(fgColor)
// Use a neutral background for empty areas
emptyPart = emptyPart.Background(bgColor)
}
s := fmt.Sprintf(format, filledPart, emptyPart, percentWidth, fmt.Sprintf("%.1f%%", usage*100))
return termenv.String(s).String()
}
setColumnConfigs(tab, maxColContent, assigned, opts, barTransformerFunc)
suffix := "device"
if tab.Length() > 1 {
suffix = "devices"
}
tab.SetTitle("%d %s %s", tab.Length(), title, suffix)
// tab.AppendFooter(table.Row{fmt.Sprintf("%d %s", tab.Length(), title)})
sortMode := table.Asc
if opts.SortBy >= 12 {
sortMode = table.AscNumeric
}
tab.SortBy([]table.SortBy{{Number: opts.SortBy, Mode: sortMode}})
tab.Render()
}
// sizeTransformer makes a size human-readable.
func sizeTransformer(val interface{}) string {
return sizeToString(val.(uint64))
}
// spaceTransformer makes a size human-readable and applies a color coding.
func spaceTransformer(val interface{}) string {
free := val.(uint64)
s := termenv.String(sizeToString(free))
redAvail, _ := stringToSize(strings.Split(*availThreshold, ",")[1])
yellowAvail, _ := stringToSize(strings.Split(*availThreshold, ",")[0])
switch {
case free < redAvail:
s = s.Foreground(theme.colorRed)
case free < yellowAvail:
s = s.Foreground(theme.colorYellow)
default:
s = s.Foreground(theme.colorGreen)
}
return s.String()
}
// inColumns return true if the column with index i is in the slice of visible
// columns cols.
func inColumns(cols []int, i int) bool {
for _, v := range cols {
if v == i {
return true
}
}
return false
}
// sizeToString prettifies sizes.
func sizeToString(size uint64) (str string) {
b := float64(size)
switch {
case size >= 1<<60:
str = fmt.Sprintf("%.1fE", b/(1<<60))
case size >= 1<<50:
str = fmt.Sprintf("%.1fP", b/(1<<50))
case size >= 1<<40:
str = fmt.Sprintf("%.1fT", b/(1<<40))
case size >= 1<<30:
str = fmt.Sprintf("%.1fG", b/(1<<30))
case size >= 1<<20:
str = fmt.Sprintf("%.1fM", b/(1<<20))
case size >= 1<<10:
str = fmt.Sprintf("%.1fK", b/(1<<10))
default:
str = fmt.Sprintf("%dB", size)
}
return
}
// stringToSize transforms an SI size into a number.
func stringToSize(s string) (size uint64, err error) {
regex := regexp.MustCompile(`^(\d+)([KMGTPE]?)$`)
matches := regex.FindStringSubmatch(s)
if len(matches) == 0 {
return 0, fmt.Errorf("'%s' is not valid, must have integer with optional SI prefix", s)
}
num, err := strconv.ParseUint(matches[1], 10, 64)
if err != nil {
return 0, err
}
if matches[2] != "" {
prefix := matches[2]
switch prefix {
case "K":
size = num << 10
case "M":
size = num << 20
case "G":
size = num << 30
case "T":
size = num << 40
case "P":
size = num << 50
case "E":
size = num << 60
default:
err = fmt.Errorf("prefix '%s' not allowed, valid prefixes are K, M, G, T, P, E", prefix)
return
}
} else {
size = num
}
return
}
// stringToColumn converts a column name to its index.
func stringToColumn(s string) (int, error) {
s = strings.ToLower(s)
for i, v := range columns {
if v.ID == s {
return i + 1, nil
}
}
return 0, fmt.Errorf("unknown column: %s (valid: %s)", s, strings.Join(columnIDs(), ", "))
}
// stringToSortIndex converts a column name to its sort index.
func stringToSortIndex(s string) (int, error) {
s = strings.ToLower(s)
for _, v := range columns {
if v.ID == s {
return v.SortIndex, nil
}
}
return 0, fmt.Errorf("unknown column: %s (valid: %s)", s, strings.Join(columnIDs(), ", "))
}
// columnsIDs returns a slice of all column IDs.
func columnIDs() []string {
s := make([]string, len(columns))
for i, v := range columns {
s[i] = v.ID
}
return s
}
================================================
FILE: themes.go
================================================
package main
import (
"fmt"
"github.com/muesli/termenv"
)
// Theme defines a color theme used for printing tables.
type Theme struct {
colorRed termenv.Color
colorYellow termenv.Color
colorGreen termenv.Color
colorBlue termenv.Color
colorGray termenv.Color
colorMagenta termenv.Color
colorCyan termenv.Color
colorBgRed termenv.Color
colorBgYellow termenv.Color
colorBgGreen termenv.Color
}
func defaultThemeName() string {
if !termenv.HasDarkBackground() {
return "light"
}
return "dark"
}
func loadTheme(theme string) (Theme, error) {
themes := make(map[string]Theme)
themes["dark"] = Theme{
colorRed: env.Color("#E88388"),
colorYellow: env.Color("#DBAB79"),
colorGreen: env.Color("#A8CC8C"),
colorBlue: env.Color("#71BEF2"),
colorGray: env.Color("#B9BFCA"),
colorMagenta: env.Color("#D290E4"),
colorCyan: env.Color("#66C2CD"),
colorBgRed: env.Color("#2d1b1b"),
colorBgYellow: env.Color("#2d2d1b"),
colorBgGreen: env.Color("#1b2d1b"),
}
themes["light"] = Theme{
colorRed: env.Color("#D70000"),
colorYellow: env.Color("#FFAF00"),
colorGreen: env.Color("#005F00"),
colorBlue: env.Color("#000087"),
colorGray: env.Color("#303030"),
colorMagenta: env.Color("#AF00FF"),
colorCyan: env.Color("#0087FF"),
colorBgRed: env.Color("#ffdede"),
colorBgYellow: env.Color("#fff4d0"),
colorBgGreen: env.Color("#e6ffe6"),
}
themes["ansi"] = Theme{
colorRed: env.Color("9"),
colorYellow: env.Color("11"),
colorGreen: env.Color("10"),
colorBlue: env.Color("12"),
colorGray: env.Color("7"),
colorMagenta: env.Color("13"),
colorCyan: env.Color("8"),
colorBgRed: env.Color("1"),
colorBgYellow: env.Color("3"),
colorBgGreen: env.Color("2"),
}
if _, ok := themes[theme]; !ok {
return Theme{}, fmt.Errorf("unknown theme: %s", theme)
}
return themes[theme], nil
}
gitextract_nb2n7rrk/ ├── .github/ │ ├── FUNDING.yml │ ├── dependabot.yml │ └── workflows/ │ ├── build.yml │ ├── goreleaser.yml │ ├── lint-soft.yml │ ├── lint.yml │ └── manpage.yml ├── .gitignore ├── .golangci-soft.yml ├── .golangci.yml ├── .goreleaser.yml ├── LICENSE ├── README.md ├── duf.1 ├── filesystems.go ├── filesystems_darwin.go ├── filesystems_freebsd.go ├── filesystems_linux.go ├── filesystems_openbsd.go ├── filesystems_windows.go ├── go.mod ├── go.sum ├── groups.go ├── main.go ├── man.go ├── mounts.go ├── mounts_darwin.go ├── mounts_freebsd.go ├── mounts_linux.go ├── mounts_linux_test.go ├── mounts_openbsd.go ├── mounts_windows.go ├── style.go ├── table.go └── themes.go
SYMBOL INDEX (226 symbols across 19 files)
FILE: filesystems.go
function findMounts (line 9) | func findMounts(mounts []Mount, path string) ([]Mount, error) {
function deviceType (line 53) | func deviceType(m Mount) string {
FILE: filesystems_darwin.go
function isFuseFs (line 6) | func isFuseFs(m Mount) bool {
function isNetworkFs (line 11) | func isNetworkFs(m Mount) bool {
function isSpecialFs (line 16) | func isSpecialFs(m Mount) bool {
function isHiddenFs (line 20) | func isHiddenFs(m Mount) bool {
FILE: filesystems_freebsd.go
function isFuseFs (line 6) | func isFuseFs(m Mount) bool {
function isNetworkFs (line 11) | func isNetworkFs(m Mount) bool {
function isSpecialFs (line 23) | func isSpecialFs(m Mount) bool {
function isHiddenFs (line 35) | func isHiddenFs(m Mount) bool {
FILE: filesystems_linux.go
constant ADFS_SUPER_MAGIC (line 11) | ADFS_SUPER_MAGIC = 0xadf5
constant AFFS_SUPER_MAGIC (line 12) | AFFS_SUPER_MAGIC = 0xADFF
constant AUTOFS_SUPER_MAGIC (line 13) | AUTOFS_SUPER_MAGIC = 0x0187
constant BDEVFS_MAGIC (line 14) | BDEVFS_MAGIC = 0x62646576
constant BEFS_SUPER_MAGIC (line 15) | BEFS_SUPER_MAGIC = 0x42465331
constant BFS_MAGIC (line 16) | BFS_MAGIC = 0x1BADFACE
constant BINFMTFS_MAGIC (line 17) | BINFMTFS_MAGIC = 0x42494e4d
constant BPF_FS_MAGIC (line 18) | BPF_FS_MAGIC = 0xcafe4a11
constant BTRFS_SUPER_MAGIC (line 19) | BTRFS_SUPER_MAGIC = 0x9123683E
constant CGROUP_SUPER_MAGIC (line 20) | CGROUP_SUPER_MAGIC = 0x27e0eb
constant CGROUP2_SUPER_MAGIC (line 21) | CGROUP2_SUPER_MAGIC = 0x63677270
constant CIFS_MAGIC_NUMBER (line 22) | CIFS_MAGIC_NUMBER = 0xFF534D42
constant CODA_SUPER_MAGIC (line 23) | CODA_SUPER_MAGIC = 0x73757245
constant COH_SUPER_MAGIC (line 24) | COH_SUPER_MAGIC = 0x012FF7B7
constant CONFIGFS_MAGIC (line 25) | CONFIGFS_MAGIC = 0x62656570
constant CRAMFS_MAGIC (line 26) | CRAMFS_MAGIC = 0x28cd3d45
constant DEBUGFS_MAGIC (line 27) | DEBUGFS_MAGIC = 0x64626720
constant DEVFS_SUPER_MAGIC (line 28) | DEVFS_SUPER_MAGIC = 0x1373
constant DEVPTS_SUPER_MAGIC (line 29) | DEVPTS_SUPER_MAGIC = 0x1cd1
constant EFIVARFS_MAGIC (line 30) | EFIVARFS_MAGIC = 0xde5e81e4
constant EFS_SUPER_MAGIC (line 31) | EFS_SUPER_MAGIC = 0x00414A53
constant EXT_SUPER_MAGIC (line 32) | EXT_SUPER_MAGIC = 0x137D
constant EXT2_OLD_SUPER_MAGIC (line 33) | EXT2_OLD_SUPER_MAGIC = 0xEF51
constant EXT2_SUPER_MAGIC (line 34) | EXT2_SUPER_MAGIC = 0xEF53
constant EXT3_SUPER_MAGIC (line 35) | EXT3_SUPER_MAGIC = 0xEF53
constant EXT4_SUPER_MAGIC (line 36) | EXT4_SUPER_MAGIC = 0xEF53
constant FUSE_SUPER_MAGIC (line 37) | FUSE_SUPER_MAGIC = 0x65735546
constant FUTEXFS_SUPER_MAGIC (line 38) | FUTEXFS_SUPER_MAGIC = 0xBAD1DEA
constant HFS_SUPER_MAGIC (line 39) | HFS_SUPER_MAGIC = 0x4244
constant HFSPLUS_SUPER_MAGIC (line 40) | HFSPLUS_SUPER_MAGIC = 0x482b
constant HOSTFS_SUPER_MAGIC (line 41) | HOSTFS_SUPER_MAGIC = 0x00c0ffee
constant HPFS_SUPER_MAGIC (line 42) | HPFS_SUPER_MAGIC = 0xF995E849
constant HUGETLBFS_MAGIC (line 43) | HUGETLBFS_MAGIC = 0x958458f6
constant ISOFS_SUPER_MAGIC (line 44) | ISOFS_SUPER_MAGIC = 0x9660
constant JFFS2_SUPER_MAGIC (line 45) | JFFS2_SUPER_MAGIC = 0x72b6
constant JFS_SUPER_MAGIC (line 46) | JFS_SUPER_MAGIC = 0x3153464a
constant MINIX_SUPER_MAGIC (line 47) | MINIX_SUPER_MAGIC = 0x137F
constant MINIX_SUPER_MAGIC2 (line 48) | MINIX_SUPER_MAGIC2 = 0x138F
constant MINIX2_SUPER_MAGIC (line 49) | MINIX2_SUPER_MAGIC = 0x2468
constant MINIX2_SUPER_MAGIC2 (line 50) | MINIX2_SUPER_MAGIC2 = 0x2478
constant MINIX3_SUPER_MAGIC (line 51) | MINIX3_SUPER_MAGIC = 0x4d5a
constant MQUEUE_MAGIC (line 52) | MQUEUE_MAGIC = 0x19800202
constant MSDOS_SUPER_MAGIC (line 53) | MSDOS_SUPER_MAGIC = 0x4d44
constant NCP_SUPER_MAGIC (line 54) | NCP_SUPER_MAGIC = 0x564c
constant NFS_SUPER_MAGIC (line 55) | NFS_SUPER_MAGIC = 0x6969
constant NILFS_SUPER_MAGIC (line 56) | NILFS_SUPER_MAGIC = 0x3434
constant NTFS_SB_MAGIC (line 57) | NTFS_SB_MAGIC = 0x5346544e
constant OCFS2_SUPER_MAGIC (line 58) | OCFS2_SUPER_MAGIC = 0x7461636f
constant OPENPROM_SUPER_MAGIC (line 59) | OPENPROM_SUPER_MAGIC = 0x9fa1
constant PIPEFS_MAGIC (line 60) | PIPEFS_MAGIC = 0x50495045
constant PROC_SUPER_MAGIC (line 61) | PROC_SUPER_MAGIC = 0x9fa0
constant PSTOREFS_MAGIC (line 62) | PSTOREFS_MAGIC = 0x6165676C
constant QNX4_SUPER_MAGIC (line 63) | QNX4_SUPER_MAGIC = 0x002f
constant QNX6_SUPER_MAGIC (line 64) | QNX6_SUPER_MAGIC = 0x68191122
constant RAMFS_MAGIC (line 65) | RAMFS_MAGIC = 0x858458f6
constant REISERFS_SUPER_MAGIC (line 66) | REISERFS_SUPER_MAGIC = 0x52654973
constant ROMFS_MAGIC (line 67) | ROMFS_MAGIC = 0x7275
constant SELINUX_MAGIC (line 68) | SELINUX_MAGIC = 0xf97cff8c
constant SMACK_MAGIC (line 69) | SMACK_MAGIC = 0x43415d53
constant SMB_SUPER_MAGIC (line 70) | SMB_SUPER_MAGIC = 0x517B
constant SMB2_MAGIC_NUMBER (line 71) | SMB2_MAGIC_NUMBER = 0xfe534d42
constant SOCKFS_MAGIC (line 72) | SOCKFS_MAGIC = 0x534F434B
constant SQUASHFS_MAGIC (line 73) | SQUASHFS_MAGIC = 0x73717368
constant SYSFS_MAGIC (line 74) | SYSFS_MAGIC = 0x62656572
constant SYSV2_SUPER_MAGIC (line 75) | SYSV2_SUPER_MAGIC = 0x012FF7B6
constant SYSV4_SUPER_MAGIC (line 76) | SYSV4_SUPER_MAGIC = 0x012FF7B5
constant TMPFS_MAGIC (line 77) | TMPFS_MAGIC = 0x01021994
constant TRACEFS_MAGIC (line 78) | TRACEFS_MAGIC = 0x74726163
constant UDF_SUPER_MAGIC (line 79) | UDF_SUPER_MAGIC = 0x15013346
constant UFS_MAGIC (line 80) | UFS_MAGIC = 0x00011954
constant USBDEVICE_SUPER_MAGIC (line 81) | USBDEVICE_SUPER_MAGIC = 0x9fa2
constant V9FS_MAGIC (line 82) | V9FS_MAGIC = 0x01021997
constant VXFS_SUPER_MAGIC (line 83) | VXFS_SUPER_MAGIC = 0xa501FCF5
constant XENFS_SUPER_MAGIC (line 84) | XENFS_SUPER_MAGIC = 0xabba1974
constant XENIX_SUPER_MAGIC (line 85) | XENIX_SUPER_MAGIC = 0x012FF7B4
constant XFS_SUPER_MAGIC (line 86) | XFS_SUPER_MAGIC = 0x58465342
constant _XIAFS_SUPER_MAGIC (line 87) | _XIAFS_SUPER_MAGIC = 0x012FD16D
constant AFS_SUPER_MAGIC (line 89) | AFS_SUPER_MAGIC = 0x5346414F
constant AUFS_SUPER_MAGIC (line 90) | AUFS_SUPER_MAGIC = 0x61756673
constant ANON_INODE_FS_SUPER_MAGIC (line 91) | ANON_INODE_FS_SUPER_MAGIC = 0x09041934
constant CEPH_SUPER_MAGIC (line 92) | CEPH_SUPER_MAGIC = 0x00C36400
constant ECRYPTFS_SUPER_MAGIC (line 93) | ECRYPTFS_SUPER_MAGIC = 0xF15F
constant FAT_SUPER_MAGIC (line 94) | FAT_SUPER_MAGIC = 0x4006
constant FHGFS_SUPER_MAGIC (line 95) | FHGFS_SUPER_MAGIC = 0x19830326
constant FUSEBLK_SUPER_MAGIC (line 96) | FUSEBLK_SUPER_MAGIC = 0x65735546
constant FUSECTL_SUPER_MAGIC (line 97) | FUSECTL_SUPER_MAGIC = 0x65735543
constant GFS_SUPER_MAGIC (line 98) | GFS_SUPER_MAGIC = 0x1161970
constant GPFS_SUPER_MAGIC (line 99) | GPFS_SUPER_MAGIC = 0x47504653
constant MTD_INODE_FS_SUPER_MAGIC (line 100) | MTD_INODE_FS_SUPER_MAGIC = 0x11307854
constant INOTIFYFS_SUPER_MAGIC (line 101) | INOTIFYFS_SUPER_MAGIC = 0x2BAD1DEA
constant ISOFS_R_WIN_SUPER_MAGIC (line 102) | ISOFS_R_WIN_SUPER_MAGIC = 0x4004
constant ISOFS_WIN_SUPER_MAGIC (line 103) | ISOFS_WIN_SUPER_MAGIC = 0x4000
constant JFFS_SUPER_MAGIC (line 104) | JFFS_SUPER_MAGIC = 0x07C0
constant KAFS_SUPER_MAGIC (line 105) | KAFS_SUPER_MAGIC = 0x6B414653
constant LUSTRE_SUPER_MAGIC (line 106) | LUSTRE_SUPER_MAGIC = 0x0BD00BD0
constant NFSD_SUPER_MAGIC (line 107) | NFSD_SUPER_MAGIC = 0x6E667364
constant PANFS_SUPER_MAGIC (line 108) | PANFS_SUPER_MAGIC = 0xAAD7AAEA
constant RPC_PIPEFS_SUPER_MAGIC (line 109) | RPC_PIPEFS_SUPER_MAGIC = 0x67596969
constant SECURITYFS_SUPER_MAGIC (line 110) | SECURITYFS_SUPER_MAGIC = 0x73636673
constant UFS_BYTESWAPPED_SUPER_MAGIC (line 111) | UFS_BYTESWAPPED_SUPER_MAGIC = 0x54190100
constant VMHGFS_SUPER_MAGIC (line 112) | VMHGFS_SUPER_MAGIC = 0xBACBACBC
constant VZFS_SUPER_MAGIC (line 113) | VZFS_SUPER_MAGIC = 0x565A4653
constant ZFS_SUPER_MAGIC (line 114) | ZFS_SUPER_MAGIC = 0x2FC12FC1
function isFuseFs (line 266) | func isFuseFs(m Mount) bool {
function isNetworkFs (line 271) | func isNetworkFs(m Mount) bool {
function isSpecialFs (line 275) | func isSpecialFs(m Mount) bool {
function isHiddenFs (line 283) | func isHiddenFs(m Mount) bool {
FILE: filesystems_openbsd.go
function isFuseFs (line 6) | func isFuseFs(m Mount) bool {
function isNetworkFs (line 11) | func isNetworkFs(m Mount) bool {
function isSpecialFs (line 16) | func isSpecialFs(m Mount) bool {
function isHiddenFs (line 20) | func isHiddenFs(m Mount) bool {
FILE: filesystems_windows.go
constant WindowsSandboxMountPointRegistryPath (line 11) | WindowsSandboxMountPointRegistryPath = `Software\Microsoft\Windows\Curre...
function loadRegisteredWindowsSandboxMountPoints (line 16) | func loadRegisteredWindowsSandboxMountPoints() (ret map[string]struct{}) {
function isFuseFs (line 39) | func isFuseFs(m Mount) bool {
function isNetworkFs (line 44) | func isNetworkFs(m Mount) bool {
function isSpecialFs (line 49) | func isSpecialFs(m Mount) bool {
function isHiddenFs (line 54) | func isHiddenFs(m Mount) bool {
FILE: groups.go
constant localDevice (line 8) | localDevice = "local"
constant networkDevice (line 9) | networkDevice = "network"
constant fuseDevice (line 10) | fuseDevice = "fuse"
constant specialDevice (line 11) | specialDevice = "special"
constant loopsDevice (line 12) | loopsDevice = "loops"
constant bindsMount (line 13) | bindsMount = "binds"
type FilterOptions (line 17) | type FilterOptions struct
function renderTables (line 29) | func renderTables(m []Mount, filters FilterOptions, opts TableOptions) {
FILE: main.go
function renderJSON (line 59) | func renderJSON(m []Mount) error {
function parseColumns (line 70) | func parseColumns(cols string) ([]int, error) {
function parseStyle (line 92) | func parseStyle(styleOpt string) (table.Style, error) {
function parseCommaSeparatedValues (line 104) | func parseCommaSeparatedValues(values string) map[string]struct{} {
function validateGroups (line 119) | func validateGroups(m map[string]struct{}) error {
function findInKey (line 138) | func findInKey(str string, km map[string]struct{}) bool {
function printVersion (line 148) | func printVersion() {
function main (line 195) | func main() {
FILE: man.go
function init (line 16) | func init() {
FILE: mounts.go
type Mount (line 10) | type Mount struct
function readLines (line 28) | func readLines(filename string) ([]string, error) {
function unescapeFstab (line 44) | func unescapeFstab(path string) string {
function byteToString (line 53) | func byteToString(orig []byte) string {
function intToString (line 77) | func intToString(orig []int8) string {
FILE: mounts_darwin.go
method Stat (line 10) | func (m *Mount) Stat() unix.Statfs_t {
function mounts (line 14) | func mounts() ([]Mount, []string, error) {
FILE: mounts_freebsd.go
method Stat (line 10) | func (m *Mount) Stat() unix.Statfs_t {
function mounts (line 14) | func mounts() ([]Mount, []string, error) {
FILE: mounts_linux.go
constant mountinfoMountPoint (line 31) | mountinfoMountPoint = 4
constant mountinfoMountOpts (line 33) | mountinfoMountOpts = 5
constant mountinfoOptionalFields (line 35) | mountinfoOptionalFields = 6
constant mountinfoFsType (line 39) | mountinfoFsType = 8
constant mountinfoMountSource (line 41) | mountinfoMountSource = 9
constant mountinfoSuperOptions (line 43) | mountinfoSuperOptions = 10
method Stat (line 47) | func (m *Mount) Stat() unix.Statfs_t {
function mounts (line 51) | func mounts() ([]Mount, []string, error) {
function splitMountInfoFields (line 127) | func splitMountInfoFields(line string) []string {
function parseMountInfoLine (line 172) | func parseMountInfoLine(line string) (int, [11]string) {
FILE: mounts_linux_test.go
function TestGetFields (line 11) | func TestGetFields(t *testing.T) {
FILE: mounts_openbsd.go
method Stat (line 10) | func (m *Mount) Stat() unix.Statfs_t {
function mounts (line 14) | func mounts() ([]Mount, []string, error) {
FILE: mounts_windows.go
constant guidBufLen (line 18) | guidBufLen = windows.MAX_PATH + 1
constant volumeNameBufLen (line 19) | volumeNameBufLen = windows.MAX_PATH + 1
constant rootPathBufLen (line 20) | rootPathBufLen = windows.MAX_PATH + 1
constant fileSystemBufLen (line 21) | fileSystemBufLen = windows.MAX_PATH + 1
function getMountPoint (line 24) | func getMountPoint(guidBuf []uint16) (mountPoint string, err error) {
function getVolumeInfo (line 38) | func getVolumeInfo(guidOrMountPointBuf []uint16) (volumeName string, fsT...
function getSpaceInfo (line 49) | func getSpaceInfo(guidOrMountPointBuf []uint16) (totalBytes uint64, free...
function getClusterInfo (line 54) | func getClusterInfo(guidOrMountPointBuf []uint16) (totalClusters uint32,...
function getMount (line 62) | func getMount(guidOrMountPointBuf []uint16, isGUID bool) (m Mount, skip ...
function getMountFromGUID (line 113) | func getMountFromGUID(guidBuf []uint16) (m Mount, skip bool, warnings []...
function getMountFromMountPoint (line 124) | func getMountFromMountPoint(mountPointBuf []uint16) (m Mount, warnings [...
function appendLocalMounts (line 135) | func appendLocalMounts(mounts []Mount, warnings []string) ([]Mount, []st...
function getMountFromNetResource (line 168) | func getMountFromNetResource(netResource NetResource) (m Mount, warnings...
function appendNetworkMounts (line 185) | func appendNetworkMounts(mounts []Mount, warnings []string) ([]Mount, []...
function mountPointAlreadyPresent (line 221) | func mountPointAlreadyPresent(mounts []Mount, mountPoint string) bool {
function appendLogicalDrives (line 231) | func appendLogicalDrives(mounts []Mount, warnings []string) ([]Mount, []...
function mounts (line 257) | func mounts() (ret []Mount, warnings []string, err error) {
constant RESOURCE_CONNECTED (line 281) | RESOURCE_CONNECTED = 0x00000001
constant RESOURCE_GLOBALNET (line 282) | RESOURCE_GLOBALNET = 0x00000002
constant RESOURCE_REMEMBERED (line 283) | RESOURCE_REMEMBERED = 0x00000003
constant RESOURCE_RECENT (line 284) | RESOURCE_RECENT = 0x00000004
constant RESOURCE_CONTEXT (line 285) | RESOURCE_CONTEXT = 0x00000005
constant RESOURCETYPE_ANY (line 287) | RESOURCETYPE_ANY = 0x00000000
constant RESOURCETYPE_DISK (line 288) | RESOURCETYPE_DISK = 0x00000001
constant RESOURCETYPE_PRINT (line 289) | RESOURCETYPE_PRINT = 0x00000002
constant RESOURCETYPE_RESERVED (line 290) | RESOURCETYPE_RESERVED = 0x00000008
constant RESOURCETYPE_UNKNOWN (line 291) | RESOURCETYPE_UNKNOWN = 0xFFFFFFFF
constant RESOURCEUSAGE_CONNECTABLE (line 293) | RESOURCEUSAGE_CONNECTABLE = 0x00000001
constant RESOURCEUSAGE_CONTAINER (line 294) | RESOURCEUSAGE_CONTAINER = 0x00000002
constant RESOURCEUSAGE_NOLOCALDEVICE (line 295) | RESOURCEUSAGE_NOLOCALDEVICE = 0x00000004
constant RESOURCEUSAGE_SIBLING (line 296) | RESOURCEUSAGE_SIBLING = 0x00000008
constant RESOURCEUSAGE_ATTACHED (line 297) | RESOURCEUSAGE_ATTACHED = 0x00000010
constant RESOURCEUSAGE_ALL (line 298) | RESOURCEUSAGE_ALL = RESOURCEUSAGE_CONNECTABLE | RESOURCEUSAGE_...
constant RESOURCEUSAGE_RESERVED (line 299) | RESOURCEUSAGE_RESERVED = 0x80000000
type NetResource (line 316) | type NetResource struct
function WNetOpenEnum (line 328) | func WNetOpenEnum(scope uint32, resourceType uint32, usage uint32, resou...
function WNetEnumResource (line 341) | func WNetEnumResource(enumResource windows.Handle, count *uint32, buffer...
function WNetCloseEnum (line 354) | func WNetCloseEnum(enumResource windows.Handle) (err error) {
function GetDiskFreeSpace (line 367) | func GetDiskFreeSpace(directoryName *uint16, sectorsPerCluster *uint32, ...
FILE: style.go
function defaultStyleName (line 5) | func defaultStyleName() string {
FILE: table.go
type TableOptions (line 17) | type TableOptions struct
type Column (line 25) | type Column struct
function initializeTable (line 49) | func initializeTable(tab table.Writer, opts TableOptions) {
function appendHeaders (line 57) | func appendHeaders(tab table.Writer) {
function appendRows (line 66) | func appendRows(tab table.Writer, m []Mount) {
function computeMaxContentWidths (line 107) | func computeMaxContentWidths(m []Mount, opts TableOptions) map[int]int {
function computeAssignedWidths (line 191) | func computeAssignedWidths(maxColContent map[int]int, opts TableOptions)...
function setColumnConfigs (line 272) | func setColumnConfigs(tab table.Writer, maxColContent map[int]int, assig...
function printTable (line 298) | func printTable(title string, m []Mount, opts TableOptions) {
function sizeTransformer (line 444) | func sizeTransformer(val interface{}) string {
function spaceTransformer (line 449) | func spaceTransformer(val interface{}) string {
function inColumns (line 469) | func inColumns(cols []int, i int) bool {
function sizeToString (line 480) | func sizeToString(size uint64) (str string) {
function stringToSize (line 504) | func stringToSize(s string) (size uint64, err error) {
function stringToColumn (line 541) | func stringToColumn(s string) (int, error) {
function stringToSortIndex (line 554) | func stringToSortIndex(s string) (int, error) {
function columnIDs (line 567) | func columnIDs() []string {
FILE: themes.go
type Theme (line 10) | type Theme struct
function defaultThemeName (line 24) | func defaultThemeName() string {
function loadTheme (line 31) | func loadTheme(theme string) (Theme, error) {
Condensed preview — 35 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (115K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 15,
"preview": "github: muesli\n"
},
{
"path": ".github/dependabot.yml",
"chars": 275,
"preview": "version: 2\nupdates:\n - package-ecosystem: \"gomod\"\n directory: \"/\"\n schedule:\n interval: \"daily\"\n labels:\n"
},
{
"path": ".github/workflows/build.yml",
"chars": 595,
"preview": "name: build\non: [push, pull_request]\n\njobs:\n build:\n strategy:\n matrix:\n go-version: [~1.23, ^1]\n "
},
{
"path": ".github/workflows/goreleaser.yml",
"chars": 448,
"preview": "name: goreleaser\n\non:\n pull_request:\n push:\n\njobs:\n goreleaser:\n runs-on: ubuntu-latest\n steps:\n - name: C"
},
{
"path": ".github/workflows/lint-soft.yml",
"chars": 745,
"preview": "name: lint-soft\non:\n push:\n branches:\n - master\n pull_request:\n\npermissions:\n contents: read\n # Optional: al"
},
{
"path": ".github/workflows/lint.yml",
"chars": 687,
"preview": "name: lint\non:\n push:\n branches:\n - master\n pull_request:\n\npermissions:\n contents: read\n # Optional: allow r"
},
{
"path": ".github/workflows/manpage.yml",
"chars": 829,
"preview": "name: manpage\n\non:\n push:\n branches:\n - master\n\njobs:\n manpage:\n runs-on: ubuntu-latest\n steps:\n - "
},
{
"path": ".gitignore",
"chars": 281,
"preview": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, built with `go test -c`\n*.test\n\n# Ou"
},
{
"path": ".golangci-soft.yml",
"chars": 530,
"preview": "version: \"2\"\nrun:\n tests: false\nlinters:\n enable:\n - exhaustive\n - goconst\n - godot\n - godox\n - gomoddi"
},
{
"path": ".golangci.yml",
"chars": 430,
"preview": "version: \"2\"\nrun:\n tests: false\nlinters:\n enable:\n - bodyclose\n - gosec\n - nilerr\n - predeclared\n - rev"
},
{
"path": ".goreleaser.yml",
"chars": 1635,
"preview": "version: 2\n\nenv:\n - CGO_ENABLED=0\n\nbefore:\n hooks:\n - go mod tidy\n\nbuilds:\n - binary: duf\n flags:\n - -trim"
},
{
"path": "LICENSE",
"chars": 2733,
"preview": "MIT License\n\nCopyright (c) 2020 Christian Muehlhaeuser\n\nPermission is hereby granted, free of charge, to any person obta"
},
{
"path": "README.md",
"chars": 4143,
"preview": "# duf\n\n[](https://github.com/"
},
{
"path": "duf.1",
"chars": 4169,
"preview": ".TH DUF 1 \"2025-09-30\" \"duf\" \"Disk Usage/Free Utility\"\n.SH NAME\nduf - Disk Usage/Free Utility\n.SH SYNOPSIS\n\\fBduf\\fP [\\f"
},
{
"path": "filesystems.go",
"chars": 1292,
"preview": "package main\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\nfunc findMounts(mounts []Mount, path string) ([]Mount, error"
},
{
"path": "filesystems_darwin.go",
"chars": 299,
"preview": "//go:build darwin\n// +build darwin\n\npackage main\n\nfunc isFuseFs(m Mount) bool {\n\t//FIXME: implement\n\treturn false\n}\n\nfun"
},
{
"path": "filesystems_freebsd.go",
"chars": 517,
"preview": "//go:build freebsd\n// +build freebsd\n\npackage main\n\nfunc isFuseFs(m Mount) bool {\n\t//FIXME: implement\n\treturn false\n}\n\nf"
},
{
"path": "filesystems_linux.go",
"chars": 12838,
"preview": "//go:build linux\n// +build linux\n\npackage main\n\nimport \"strings\"\n\n//nolint:revive\nconst (\n\t// man statfs\n\tADFS_SUPER_MAG"
},
{
"path": "filesystems_openbsd.go",
"chars": 301,
"preview": "//go:build openbsd\n// +build openbsd\n\npackage main\n\nfunc isFuseFs(m Mount) bool {\n\t//FIXME: implement\n\treturn false\n}\n\nf"
},
{
"path": "filesystems_windows.go",
"chars": 1075,
"preview": "//go:build windows\n// +build windows\n\npackage main\n\nimport (\n\t\"golang.org/x/sys/windows/registry\"\n)\n\nconst (\n\tWindowsSan"
},
{
"path": "go.mod",
"chars": 700,
"preview": "module github.com/muesli/duf\n\ngo 1.23.0\n\nrequire (\n\tgithub.com/IGLOU-EU/go-wildcard v1.0.3\n\tgithub.com/jedib0t/go-pretty"
},
{
"path": "go.sum",
"chars": 3493,
"preview": "github.com/IGLOU-EU/go-wildcard v1.0.3 h1:r8T46+8/9V1STciXJomTWRpPEv4nGJATDbJkdU0Nou0=\ngithub.com/IGLOU-EU/go-wildcard v"
},
{
"path": "groups.go",
"chars": 3206,
"preview": "package main\n\nimport (\n\t\"strings\"\n)\n\nconst (\n\tlocalDevice = \"local\"\n\tnetworkDevice = \"network\"\n\tfuseDevice = \"fuse\""
},
{
"path": "main.go",
"chars": 9113,
"preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"runtime/debug\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\twildcard \"github.c"
},
{
"path": "man.go",
"chars": 2674,
"preview": "//go:build mango\n// +build mango\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/muesli/mango\"\n\tmpflag \"github.com/mu"
},
{
"path": "mounts.go",
"chars": 1802,
"preview": "package main\n\nimport (\n\t\"bufio\"\n\t\"os\"\n\t\"strconv\"\n)\n\n// Mount contains all metadata for a single filesystem mount.\ntype M"
},
{
"path": "mounts_darwin.go",
"chars": 2043,
"preview": "//go:build darwin\n// +build darwin\n\npackage main\n\nimport (\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc (m *Mount) Stat() unix.Statf"
},
{
"path": "mounts_freebsd.go",
"chars": 2376,
"preview": "//go:build freebsd\n// +build freebsd\n\npackage main\n\nimport (\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc (m *Mount) Stat() unix.Sta"
},
{
"path": "mounts_linux.go",
"chars": 6185,
"preview": "//go:build linux\n// +build linux\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n"
},
{
"path": "mounts_linux_test.go",
"chars": 6444,
"preview": "//go:build linux\n// +build linux\n\npackage main\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestGetFields(t *testing.T) {\n\tva"
},
{
"path": "mounts_openbsd.go",
"chars": 1943,
"preview": "//go:build openbsd\n// +build openbsd\n\npackage main\n\nimport (\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc (m *Mount) Stat() unix.Sta"
},
{
"path": "mounts_windows.go",
"chars": 11323,
"preview": "//go:build windows\n// +build windows\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"golang.org/x/sys/windows\"\n\t\"math\"\n\t\"path/filepath\""
},
{
"path": "style.go",
"chars": 390,
"preview": "package main\n\nimport \"github.com/mattn/go-runewidth\"\n\nfunc defaultStyleName() string {\n\t/*\n\t\tDue to a bug in github.com/"
},
{
"path": "table.go",
"chars": 16374,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.c"
},
{
"path": "themes.go",
"chars": 1940,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/muesli/termenv\"\n)\n\n// Theme defines a color theme used for printing tables.\n"
}
]
About this extraction
This page contains the full source code of the muesli/duf GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 35 files (101.4 KB), approximately 33.9k tokens, and a symbol index with 226 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.