pax_global_header00006660000000000000000000000064142004066620014512gustar00rootroot0000000000000052 comment=3c5541177824be03e632447bd997fe263ee6bc6c duf-0.8.1/000077500000000000000000000000001420040666200122765ustar00rootroot00000000000000duf-0.8.1/.github/000077500000000000000000000000001420040666200136365ustar00rootroot00000000000000duf-0.8.1/.github/FUNDING.yml000066400000000000000000000000171420040666200154510ustar00rootroot00000000000000github: muesli duf-0.8.1/.github/dependabot.yml000066400000000000000000000004231420040666200164650ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "gomod" directory: "/" schedule: interval: "daily" labels: - "dependencies" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" labels: - "dependencies" duf-0.8.1/.github/workflows/000077500000000000000000000000001420040666200156735ustar00rootroot00000000000000duf-0.8.1/.github/workflows/build.yml000066400000000000000000000011171420040666200175150ustar00rootroot00000000000000name: build on: [push, pull_request] jobs: build: strategy: matrix: go-version: [~1.12, ^1] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} env: GO111MODULE: "on" steps: - name: Install Go uses: actions/setup-go@v2 with: go-version: ${{ matrix.go-version }} - name: Checkout code uses: actions/checkout@v2 - name: Download Go modules run: go mod download - name: Build run: go build -v ./... - name: Test run: go test ./... duf-0.8.1/.github/workflows/goreleaser.yml000066400000000000000000000006761420040666200205570ustar00rootroot00000000000000name: goreleaser on: pull_request: push: jobs: goreleaser: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 with: fetch-depth: 0 - name: Set up Go uses: actions/setup-go@v2 - name: Run GoReleaser uses: goreleaser/goreleaser-action@v2 with: version: latest args: release --snapshot --skip-publish --skip-sign --rm-dist duf-0.8.1/.github/workflows/lint-soft.yml000066400000000000000000000011731420040666200203370ustar00rootroot00000000000000name: lint-soft on: push: 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@v2 - name: golangci-lint uses: golangci/golangci-lint-action@v2 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 duf-0.8.1/.github/workflows/lint.yml000066400000000000000000000011011420040666200173550ustar00rootroot00000000000000name: lint on: push: 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@v2 - name: golangci-lint uses: golangci/golangci-lint-action@v2 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 duf-0.8.1/.github/workflows/manpage.yml000066400000000000000000000014771420040666200200370ustar00rootroot00000000000000name: manpage on: push: branches: - master jobs: manpage: runs-on: ubuntu-latest steps: - name: Install Go uses: actions/setup-go@v2 with: go-version: 1.17 - name: Checkout code uses: actions/checkout@v2 - 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 🤖 duf-0.8.1/.gitignore000066400000000000000000000004311420040666200142640ustar00rootroot00000000000000# 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/ duf-0.8.1/.golangci-soft.yml000066400000000000000000000012701420040666200156330ustar00rootroot00000000000000run: tests: false issues: include: - EXC0001 - EXC0005 - EXC0011 - EXC0012 - EXC0013 max-issues-per-linter: 0 max-same-issues: 0 linters: enable: # - dupl - exhaustive # - exhaustivestruct - goconst - godot - godox - gomnd - gomoddirectives - goprintffuncname - ifshort # - lll - misspell - nakedret - nestif - noctx - nolintlint - prealloc - wrapcheck # disable default linters, they are already enabled in .golangci.yml disable: - deadcode - errcheck - gosimple - govet - ineffassign - staticcheck - structcheck - typecheck - unused - varcheck duf-0.8.1/.golangci.yml000066400000000000000000000006051420040666200146630ustar00rootroot00000000000000run: tests: false issues: include: - EXC0001 - EXC0005 - EXC0011 - EXC0012 - EXC0013 max-issues-per-linter: 0 max-same-issues: 0 linters: enable: - bodyclose - exportloopref - goimports - gosec - nilerr - predeclared - revive - rowserrcheck - sqlclosecheck - tparallel - unconvert - unparam - whitespace duf-0.8.1/.goreleaser.yml000066400000000000000000000024701420040666200152320ustar00rootroot00000000000000env: - GO111MODULE=on - 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 archives: - format_overrides: - goos: windows format: zip replacements: windows: Windows darwin: Darwin 386: i386 amd64: x86_64 nfpms: - builds: - duf vendor: muesli homepage: "https://fribbledom.com/" maintainer: "Christian Muehlhaeuser " description: "Disk Usage/Free Utility" license: MIT formats: - apk - deb - rpm bindir: /usr/bin brews: - goarm: 6 tap: owner: muesli name: homebrew-tap commit_author: name: "Christian Muehlhaeuser" email: "muesli@gmail.com" homepage: "https://fribbledom.com/" description: "Disk Usage/Free Utility" # skip_upload: true signs: - artifacts: checksum checksum: name_template: "checksums.txt" snapshot: name_template: "{{ .Tag }}-next" changelog: sort: asc filters: exclude: - "^docs:" - "^test:" duf-0.8.1/LICENSE000066400000000000000000000052551420040666200133120ustar00rootroot00000000000000MIT 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. duf-0.8.1/README.md000066400000000000000000000076241420040666200135660ustar00rootroot00000000000000# duf [![Latest Release](https://img.shields.io/github/release/muesli/duf.svg?style=for-the-badge)](https://github.com/muesli/duf/releases) [![Go Doc](https://img.shields.io/badge/godoc-reference-blue.svg?style=for-the-badge)](https://pkg.go.dev/github.com/muesli/duf) [![Software License](https://img.shields.io/badge/license-MIT-blue.svg?style=for-the-badge)](/LICENSE) [![Build Status](https://img.shields.io/github/workflow/status/muesli/duf/build?style=for-the-badge)](https://github.com/muesli/duf/actions) [![Go ReportCard](https://goreportcard.com/badge/github.com/muesli/duf?style=for-the-badge)](https://goreportcard.com/report/muesli/duf) Disk Usage/Free Utility (Linux, BSD, macOS & Windows) ![duf](/duf.png) ## 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` - Debian/Ubuntu: `apt install duf` - Nix: `nix-env -iA nixpkgs.duf` - Void Linux: `xbps-install -S 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.12 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) duf-0.8.1/duf.1000066400000000000000000000077651420040666200131550ustar00rootroot00000000000000.TH DUF 1 "2022-02-08" "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-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 .SH COPYRIGHT Copyright (C) 2020-2022 Christian Muehlhaeuser .PP Released under MIT license\&. duf-0.8.1/duf.png000066400000000000000000003664531420040666200136030ustar00rootroot00000000000000PNG  IHDRb 5gAMA a cHRMz&u0`:pQ<caNvKK=(bKGDCtIME +)^?LIDATxg|ǗoPPPEY$0AI Y"scLd&pggݽw wS-VOMu9/{ZD!B!#Xjohz"M2cnB!B!B|0񹉭&7QDP"0S.Va[yr!B!pDO1&LDgB^2"Ƞz/U!B!B!n|e:&j~)h2 Sf1_CB!B!D}I&^zhL"B!B!1-L40R~ycPp#K!B!bY^ 2&L4FICb ШB!B!Nav&2TIRiLB!B!L7D'_j*1NbcB!B!-Q`]_*Ar;4"!B!Bnvg͗bLaJAq$B!B&c SB$jJ6!Jf&#B!B1 {1HՔu% G!B!bM31D%)x+FB!B!0ߛxr(D \1pB!B!7GMl11D/b(-g G!B!b9{M01 Md9kRQ!B!BJGLlz$Ẅ7JnlExbi0B!B!RsiL,2QFHOJ|djm$DB!B!;&.xexLdHyz$ 1B!B!eCo,e]FIZ&4!&#B!B)5L\7q썒<13L 7D {)B!B!L|`℉&fQ&(B!B!xGo$=eb&FQrRBL&`B!B!c7(Y9i&Lt{/5 1B!B!ebn8cb&MdhB!B!B!|]BLBL<B!B! 1g]1b!B!B(B!B!P!B!B!b!B!BBYH!B!B!BL }voo!B!TqΟ?悜 1[8B!Bk悜 1LNN&G I$B!I!C 1 4I&<B!RObV\I 1 o>B!By8t9W A(T4QN۷Y~6<-DEEիW1ЭǏ[ 18_޽{I\\jkN?~|JT΃v>]SwqΝ;g|jߧ~B! 1P)e˖b&N Agee4o\ϗ@%dffZ ҥjJ fyS6[ 1邐.*?ɓemyf]&Vt=z죧= 䤥Q9B!B! 1yA$=,,܅?P}n޲eܿ_Bp൲zj 1()**2p*xX 1jl={O.n2zڃ:숲 !BCxB!B8$b(B!B| 1B!B(B!7!B! C!B!B !bxB!B|PC!B!AB(B!BCxB!B8$B !B! 1PG!B!AB!ƛB!B!' 1b!B!P!I.6 iCBC^PGxiCڐ6$?!V  1.mHhCڐ!*!bxq-hCڐ6$?J[ 1;mHڐ!*!b\B҆!!ڇZ%B o*!b*֭;vLoڴIOII1ի\pA>S9qℴl;!CX:u,Y7|wϼ/??_6lؠTgh3ڱWnwYjtر6vwwnvv:uJ=z$W^իWK͚5l|uY}ر\6ssEmQFgeeY6n(_5c1!wO<)+VPHO*cǎ_~E/,,[XC} m-wӦMvr:r効wWmc犼/*ÆW`?33SVpAqFGGGϵ L>T֘ШQ#y\tI>}jp =l Bw٣gO\q/ZH]&3@ET_fUgϞ=q7n=_y B N>-={Tf̘ޖ[pj@ꫯdΨN ʧ9OKpN0þ>}WUpq 8: dz`G~ƍ>UYc犼/*چfp3g;~}wjj-]wV)Đ2&o '=5ysfQϖYwn.k׸wjՔsr{H!%ʖwG}zΗg<띧>K 1>+ʮ]Ծ{)%[?ܹs\o߾mX_5Tzn_UQD}sR ې }ȨB;W}Q6>|BCBB-vYv멇B !cB6mTHBnQp?aF'oel޽{1?vWK!F*RASZj>zܝ/#~ey3O}g B!Ƨl# -&p57| .ToK# 6*VK+j¹}v5뵏޾WUp㈷B{ 4ie=B F{Ubع!~i9[[Q`4Vcii=P!"xbL.]HeJ<yJ^V/FPp$c/gblACqg#lΗz^pE ѣG,B̀T;bB@[ף$ۗCg0GSIF\/#x"4<1%bÀ ۖcpŏ[t:-6X  GE 18ٳguGoߍup%I# ͛Pc犺/*چ.pb1m߂o\?H r3#P!˘0~xׯWoqãUV<yLq5:ScO>SBy c>K 1>/Xb;]e?jWQF[5͆#"""BM`YB ~6:HOߍup0oG9 IB{G+("i   䴸qUYĎ#< JU PuxbN(Tq!4lFjB,A(TY!Hօ8r$>w7h&=x!HL8ѡ`mۦFA< hڄx7rH5߿=}7ү6K#^_üe3Bp~mƃ r T"ys `w=Wmc犼/*ʆpyFݐ!Cw B+}Mk;s̙RZ| vt UHAA'o^%x[O豏qC2ROY)YPyL .m3_얭`/=LlPLhٲeGj9Qv+FmFUܭ[ʋ/ݲ"LBegh"6ssEeu)Α`'䳰 z]Ďp5 16v5=O>x=3n9_F)<>K 1UV1 yQt%22R7n\ɝ7뀋7IIIn^>?ZqwxO ;xjk 18ڐ6 !CxB!7y iCBB8>Z%B KhCڐ6$pC^PGxiCڐ6$?!V  1.mHhCڐ!*!bxq%!mHBڇZ%B oZ%b(! iCBڇ^P iCڐB8>*mA(#Z%B o<mH҆!Vi B!7y iCBB8>Z%B KhCڰ*p[]Ċ<3+mIV-hJ%&f=::F&$IXX*WvQ#RZ紲1B(T}i1x/2rHtD:$+ձ/6mݻwe˖-6Ye}*5j{Cy\vM &oҥKֆ\,Y`oVBl8{lYz:o#FPΏ?h% u] oJѰҵU=Y=}$CUVM&9=Zʙ5 &C "ru8\vj, %c{Um`u]MSeia/a 6܄=c m|\\bX:"f%0Z/_%5BJDFɵoB2=ާ+7ʠ!9en sZi _Jj4#ubSB! LȳWaB&'N5geW~IƏoXzٻw<~XBBB3?Z;w?S=nC[ŋ2u*eCG@09W1Wm# &ݞeXLectlQb湢T86UHw(=̃xŶ߭(!R  5 k)3'ADDtۡ'pԵiTS_&:2~f_*=jr-%@t1{;4̜KV89}%m}@.\aU;Nԙ G91 jּu}7r껞zrFVQf%xV>2;*$B(Ę -4[y8P ҽ{w$ׯW^z$N:6c ń |GkҤI*gKhhm;R>3~_MΫ ]Mbe ȱ`4uEУWaʗաYb:5O1w.{ʲqYeUԃ/-e֭^ZkbLU3N/fɀ7)*_oRkb VĨ{FIr?*t"yvlW 1X]+h6l?[,652,S2UY1:`%5lXHL51Q%yԎ$/#dT Y0ڮ,!f}V0R 1EIAnL?S =VYg-xW`rn#Ē]Xe9r2mrQc& !ڣmz^c{FW}S,4w6TB !bJF[ McJ>X1J߽[n޼i ~,TX;rJkxA2Ys{ "B۶mձѾ9}tk yq~Zr?Vw`ۄ2ho!Čg= [_/-Umv>c)k*[l7U%ed=TwlA$ wY~! 8V\Ɵ;&ڜլ~sWRvރ'Kc^GIxi:~^l 1 PIj;`+>X1h;-G֖p^n,lpFn$rȪO%`]}T=is=zOz\dz/( 1UT6wұp)O< ylzz qBflEp$7n_q^k?Z!oPfu!MO(;{j2x(2l^XLByg0@n RB_*5zcR 1FOF_ ڳDbRԎY7@X1(9%4glTP8Y藘$u]AR륩i#W{Ӳ'^tP,eZ%b(! iCBڇ^P!p mH҆!V 7^hT- Ë.mHhC/)}eٻSJے*͂㭮AhãcnBd^߸ڵ\bY_l3eB(xݍ}i1xKɰW*_; V]yAy6l;v̊]v9,/B):ȹsO>QuիWYnTi&?%%+lm1|4ѣGe֬Y?n1dAV7n*'Fdzgi4#"dy2{KЩlguS]*zڣV8Ya F!Py}Z-eޣzM+UI7',!ٳg_ :0Mnݻeu)㎴4{!NOJ%oZվT駟q[h?Z ,~ͩsyF<}N݆O]A V"C>1M.S2 SoV=CC/ '3̎2uy'gĄy=m?,?-]%~~%qlM;SٰUZ%$V3mmr# vʑ2f*C7rb ~켼P.~SW(8P,l%{?G;+e-L['Q%XrzZ!?\-iM|j6~49|MDr i _Jj4b<x9yQ[cB!bW]BLjX"S?I$8-={Tf̘!j!ƖΝ;˟)v >\y={Bs6mF ,W&Lbl Wɬ}KSHxPGΓ@_˥Oj߼1>!Ĭ92]z +p JSE WWB £VJ#ĠG0B[UP/_IVݢ{U#F!sذ-o;XLJ$D '15Ku.'22Jv\ks(;9,gQC+Dխ's?/IhN}\fG1a'd#>WUO)qW&FYBL@@8vQ"K͚5E=ep5k֨lذA;>|JD5 gРA>%ݻW>f>ZyYqvNՆZ~ iXvg̑vMmw[yjB4oBLDL `ӭ2{xxչ22w(DDPBLdLl;?ߩx$֏+)=d p(t|_ bckɽGϥuu~#{flx;1kwz#Uw>=Ys(!A uUXVٳw?w˙ 7D ءh\ ~cUD\F΅XU] 9UbOTb7(P!bz#y{1*Z̻" {h y%ia>04℘_U7oJFFzD 1ժUSl}@.\a'd/z%mߍ7>>LhKeGOP]3f/T;d̹qS;2"TU{ yTS^+ݺ6f#PCk+ĄFזYM%u$Lվ~JG ?WBdКӆPޫW/߿4jHw.׮]~AԩSBLppKAAj dJ(} uʷKB̤ITBCCwE +z 寋K!E΄Ȑfc:İ>AM16"X?(=˴Y [Dö%7pEb)j@T4{ &#gKu.\ga3x8돞69b(Z 1]UB C:D22P 13[_~Q9C*ZS}7n6ڳyf0bUI&>%Ġ/[n>S2dy_iwl __,9-_E MФx@9_8rd{,$qՉj! ٨6aחHW!B {o,.^yFSB̲KkEdR86J#:Il]XWQc&sל#q&`㵛v*!,{[_b&NziBXұ!]4Q6F|fHi΅haXHS 1bymp~I_΄Rd`>2b*o}l߾…W_… W%ܑ#GTZ[aK+BL۶mձ p*ɓf6;:fno &ejt۵j8{jtۊ5!{c!mOf/f!o1s>[ 1BLr*4H}Hm[O6}e⊊T{|^ /^ȴitwʆΩ/QhMbŝ?":z8e+>9EϚ2m!3-%ii=(Ĩ񴸿J&#+-1uu=˰L!/HQ@`Y{NiXw\&B{My٨gjNn=?_:j£pouƻN;-#,XZ=S ɔUXؽsH6m߫o_p4,'i$J|;g][䃹v둴mAX-YVCBO{2ڴWhBMf.=jssפisF(-E( 1UVi1xL}o?(ĥ3dEuWG d:>s:hiN{՞?}ղLDDU*i&8jӢEp& ,We!f̘1*(..B+;*ܜ2M /:^Q3k R z9o2\<AEY}dR"2ڤTe4&a2! _זJq/$F8  _GCƝ jkx@A/^&9#GJi׾c! 1ܫo}/{>4p i ڐ6$p!Jx6 iCBCkPC8҆6 !hBxB!p%!mHBڇZ%Box!Q5%,/! І z &Y?p0mK4'Gs[]*aؚ$aa5o\u*-ne{d%cPӷH]Orϋ XСCRXXhYndr{l޼Yʍ9R.]$=SNIVVVҡC9w|'MsM6Ul")))^kCejIqjmRi2,Se<M)[$K+TVM&u,7Lg/z1OHo ʓg̑OfΕ]FHb)=7fC~շigkDpWmOw]FN9Ae}ޢi^ 1PVzK%^S 1U|{z0۲~%竲O>(s?X 7k֬Qgf{NKKYGJjx "ڵk2l0Ζ~[.]6SS6bɂ ~bц OkR)Q''SodS+˔byBHyzt\jfUۃP2"s(9'8b 5kl:>2[z\i=%Qj];.ޫvg"GbR$$&#~نnGڣ眺kSx&H|DUwK!mNNIuh}ޠC[%%jSppB!5b֓$d'ČySn$>W[o+Q<J3A=]{ߴ̼ϒy2dEp;}ǭ~4m]U!ϋso5kT/μ9, U{xnX?~ܪ./6#$ @6lؠ?^>\"̙~W3gOPOO.YBzFb! z;J&vLb evn7+!fX ِ(rGɖCDo.Xeʽ/ 1]Ї6gM)G V&Pfv^-O*2 !-̡YM*W)B̮Gd3O&/1ɼW8hU]\#29b|3끸c xZtr{oyV#drgOer +1*>W{>IA%o_ZvQKi_MRڼ5C -MK)QLjf Ӱp:K5E)!'UIyxDr7i;g9wB X 4i gVモɓ'Kǎeʕ_Hݺu 搜-Z;vPiPiӦY:+ '1VKN+7poaYh^W=5O&A:6OfJݚӑ$5N,l3B >lF>o.US:ua)2(& ~O.Sg:1#&4L[QG)*$ ;BK%!am;+JCfy[MDe ""RuГ84}*Iɩ%T@:*7nݣvZ(.Rb &3f/T6#rG&sԪ'ޑm܅+~ :s9zQr_͚RryFW}G!h -x†L4I~GE2doa&Ldn^ {H[: 1%+!Sq?W&ae#9Aa.kI&NgHp@ˢ>+/f9֪QC$*qE7+[a5+-]&{P]£*Ry`j P!gB &з?Q.KBc_yb+1RXauFLGΚ=D6 13Roz*% $LB"cvD\EmP'TaJ8+GDd9NN\,ue\o]YB֝azb)0p$zTZ6#L;v߃g&F`yn6qE%d?S9hè1wsNGԃ2}7r껻6xYi6:mb r+&k[d_b*.+I|;Z5DYR`*Kl Mr'Ĩ ͵?%@DVH[~1޽(@5j8ѣIJcXPKeIj Vhr7۰aI~嗋Kb+5ކ~(VN!^ A*(_ ˔wۤ4quM)SQBZjE$91j a\ֺ½<4u G%+ !b]˾ve1v;B%QQ*ԥyb<1>C8{馝G" glspp{^*I&%1ɆۣiQÓ}s뻞6Ëe΂eVew*4ɝc_b*,Iѕ%#g2z$-I&Yo_CB LҮy;Jcyqz~0iDضLċ%k2X{`IMM5x.J$mjs~U>}T"`Tߴ4sԠ r`eARezllX;m4{ =ƒҩ%o=3&Фؕ:}ja,_}h[jqjW6H//9ij 1`QﭼjY?eXh.Le*BIYSy`z1 bJ+2=Jۤd30 2;oܬ_p+-Y&h]m=R\#ѦBhgvW?A΁5,'i$JX;g]Zv둴m<28x7*c=zΩ54y27n4AR],όH2I={˜4aFxh_~#rÇ7|mjM6SƓ6D^$snoHDJޞ3T %e7Trz?a3[cv9,|ub:m7 1X7Mɲ-R GYe*Ρ9Jh[2!tV/B>9OcAGaYۇ 7ܯof[MgQ1x*kj,ϻIRXr8t:[BL>T;: -*eՊV 2AAs%ae%W۪&K8#O bdUΧ_0ޮW*buҴG9='w=mvc|!9C(TA!V?Xc %57WwRzM%2>ռ5uUX+KonQ*ƑaYO_)< zTIHzbM q^/IqQ W7V'Ī:+ӆb䍖5cqx+Iq]a5s54 ?_+,yt)P/V JNI5M*+NB!1IIKS/9ei]OVEBݲ)R!b|G4F4雧剧C7m%_i[RA>k~n^ pOһ@7þ^&M"piCڂ6 !CxB!7y iCڐB8>Z%b(! iCBڇ^P!p mH҆!V & UCjJXl2׃:+Ye|\}3**Fu/]6!ef5|7Xaa:AUr֡SWi٪rXw]S<}d̩Kqbs=}ޢi2׻pycG!PyMZ-eޣzM+Uo^:.-~yJ=ok'@pdobn߾-Bϗ{ӧO%**J}>h 1ݫaN: ={febSm҄m3-7']"m-K { P}K !Fo{")!FcbU'e+U&~R?@bSHjUPlJcV3*D':D%9b8ǎOQŠP~1H$/&Nj7h%QebǗΝ;˟)8cbNJJR0c[W^곴*#fݸqBj6 5GseebUVB%m5UBLڕnCBuc$w+ /x P1"U~L㢭a( бgH`bkQL-Wnz 42B̐QX}e˶d>',_iR$!1ɥՕ잕w/󾿿Wzv׉ u@q* 0_gquݹbulp.+ I2 1E!PyMdG% -:Yy̼ϒy2dEp;}ǭieA"V&8<\L:J U!IU쮃eڍ-s?qU}w/MdևJtʠ+OPtΰ>Ua\OhhtyTU!& @BBB*fÈJ mI CVKb*!F }?_,o*k&?,-ӲeLyMP1Ξyuv,hC۱ ure Wmϕ;?=W\Qz爬XJt9t,X<W8hu(pd_7Q٧y ٳ|pSru +!Ɲcck=Ӿ>"_.^PpԞ&M6hYi8{Mn?x"KWnx%v.Wo~OTYsX=/Y^n!'7}5bk?sEׄv3r. Zi=wT[3A=fbB 1> o݇Yвyf9] T$*̀DS&msg鞵C&_Yjƫ>[~ҰGS!HnSJb4nݴ(҄9-WO_Jj %7-yhڕǐ>>FGG˯*999e+;;[ 1M4y-Wydddk=U͆U~[ߡ*11*M>1Wm#3,󚖡c7.ktK)x61{[ 1z / WX΁2u|c&Go*h!w>5 hGFLWS͔(o%K(կCe]b.^P&NU"""[ކKS_U(bѪMܛY=>dH/ 1Bk ĄFזYMwPY~3`!;(S$`Տͭvs- B VWJ+ڄ M+YB1]ºx l.=VR<\X?F-Mɨ~`t]OXX 59] .ؖ 5(jgBLPP U^b;AXlұs7NWvF9iè1k!!2*W_r:nHH ök#l aI8.#K,Hl%4tMe\cb4oҜ shM{> !ylw-w( 1UX W@q:W*KR3bnXc}J ,WŠ~&bֹb,ثbܶm۪R_rYzK.Ā˭ 14l !ޝbrZ3(zi p>c׷ q- obbOc!WWcݔrlyL*Ju+t&&NUKcH݃'²U 1mګ>&({j^&׽ًx΄ф#v^~7IS ǝ$Z &h{D V"ˮc'7z;|6YcH`*W1/ 1BLbf)t\S:]8H\){&! [4=bSfڍ#/B X:4݇CnRc鱏/;vG# Pa3>RF*Ѭo )Zt!ub$5D= L,R!a>)պ<9c#μ|N|!mh^;w4nRbl=z/((pn>RaE" <{,s1RFo1΅!^,s,*cC,a~Q!bJșsB LҮy;J*2Yo;&Un{uT2ඹU.WsuX,8kFU0^!tKIkҨ&~#oznxxxBMr6|{M6_-YYY*"ܹsq&)))j)$mڴ!+B4hxh yJ@=zJ9yd笒+h2gsA #Ok)SQB B~8PjR[B%N3oJ$5p˒;&JGkSrZ\Hڶ`^YVVV! tKjMS?K=D8**վd9-Vz,K֫ tQA؂%4SV|m"^$-IH>72zNs.1hJke^G,{U>ڋ<:KB! 1-OA!(r 1.VBI2GJ܅cD[zڳJp$hmChʯrKw †0f:/B ԏՔ~9[6Zmg1c?81[#F}ss,ٸqc>3>?yDѨ SlD[v_Bb_!KRm?7UzIgbc@%sWTSпvy8NV__eIuI3_ lVjZ4/oN a؆r?{՜xWXb}MA>h(szHЖ^n._Ζ_Rm^!]ʓa:.# <,Ek3/U,5=eݝ\b 蠿XMIkv,ӎ%76F)B! 1JPBŋCT"SK[fO/͚5ToPo=+,R;!J]bVT"F̉z5I2֎I KS‹jOYs;_xFTAd]N|rկ"~e#9%U-c^2w51+K!_[O;bXФo*Ī/!mH:|3WmIo,9i^eL{Tt{4e|7 Cp@hCڐ6$?OMB UB(#ڐ6 !C^/!sBڃPCp@҆6$p}C‡B҆!!p}B(ĔQ iCڐB8>5yB) 1( iCڐB8>֭[:=tA(T$+W4P+6 iCBC~ut J ؾ};І!mH!hRC44iB 1 B!BHsAN JtMCl'H!B!j I"b!B!BB!B! 1B!B!b(B!B!P!B!BC!B!B(B!B!P!B!BC!B!B!B !B!BB!B!B!B!B!B !B!B 1B!B!b!B!B(B!B!*.Ąモ!B!Bq畖@!ܺu!B!Bt-BL)11nMB!BCV4}ci"L&MxaB!B!)4!fʕbe#B!Bs!s 1b^ċB!B!?~< 1!B!8sPC!B! 1b!B!R/YšB !B!B%zB!B!Tm(P!B!B 13aaa\.u׭[W"""ʭ<B!B(xU&ree߾}R'W^*f1b˥O>DNZnm/..HG!B!B7nڴiO?ɓo߾riyԨQRѣ)Ĕ 5kB!B!bp_,[*~cO$oGeӴiS]gAAA 1 ի;mC@@վZjcEFF:k%&&JzzX΄|6חTB3hf7wD_իmk;=K#::ZIHHF!B!b(8 ;|pڲeѣG_~Q>^A!,Joho-;vp؆7o'*9{РANYYYvB ĈE)&t(ĴjJȔ)S z͚5%>bܹsyf]&V;Zy_7{effZ 1zڃp6\Ciiif%KB!BC!Ƃ{O_.={߿oXA_[ARha2}tun!0OݰaC9tZ퇰Ю];ه~(7n.z. 12f̘R-**2P)xOnn9 "1!86pܝc=?+!B!PF)/` #̙3Vnܸ8?adΜ9bvF !hA\¹Y&6B!BC!p$bU9s R8kÉ'ԤJDB;}dɒ%2b=5B 1Z@w"3 N 犥8c> Puc#G Vϐg]h<1B!B!B0G"ǏJ 2b_,ݺuSBVGiWU}[._!* lcqzsb@ƍUhФI 6Y/xl?;!(p`"pL}hڇ}'PD@>!pa E!!Cþ±B &Bڇ}!^B(P!p0}hڇ pa E!!cC>?;!B &Bڇ}!N/ 1}hB-b80B8&>C!8B!B !)C>!N/ 1nnرcR\\lӦMjJJy_^… 駟ʉ'e˖Vٷo 2jԩSeɒ%Q+~~~}a]TT8C̘1?׎UzuȪUcǎ!Uq0ΖSNɣGիzjY=>ܹ<((齾cs=woP +z}Fڕer!ԭ[WUk Od"""=Nzsrrg&&&ѣGCo\/˗/f͚Y}~۶m=o޾ wxjnedH8w<믿/55U~'Ug-ԾN:!֭}Ç~$s]?̙3Ǫ~ Η/_k+1m&>Tۃ +ݫB(qF_bޯ=yXB~b0j#=}w/B`:vX_}뾰PΟ?o`УG9DJnڴ< ^`\b3aEu0[T .h])S-={Tf̘ެZT @ꫯԃQ!^%ϝ`/OKKs8}'NMN&L`އa_>} HOߍ􋐪0h޳4nXBVXI\3gD76w;xkգCAg3%&"ﻫglxP5 @d޽ m\ժUSbݻw[bQlYS]Ϙ\y+Jp@!Ơ+vRݻS?,sεsŽ}a!Կyp(!dž5y>'OǏKddzfVo,;q]=b11<Pϝ Fq!-cg+Ā+WB #cb\yrlS !&**J[i޼ 6n<o( 18&&˖-+s5PZҠA<GH )r|ev֬Ye 1u[w 1zlXQc태Zh;vZχ됐7r zC|EަMi(!!O?NA޽x֫Wϥy`bݚ#܃j*!]>G;F23g=ʱBL)l# -&Apo7| .To(K# 71PVK+0l߾] 2zFEHULC7"ñ 4i=B Fw_bذ:OBYϭĨ|0 ܯ1컞z(_b۷ !l[:g 1}*LA+ׯ_LGYVx>1!ո(Gv{[O>GSB9#Q-bJ)Xb{S7e?j9Q}nZA)w$XDDDo!pa E!>CþB(p`"pL}hڇB8PC`J>!pa E!!cC>?;!B &BSڇ}!N/ 1}hBB!!cC>}'B BSڇ!!N/ 1!hڇ!pa !b80B8>C!wBxB !B!BQ(P!B!B&B^H>CþB &Bڇ}!wBL CB_b!LiB>?;![(p`"pL}hڇB8p%B BSڇ}h B_b80B8&>CþB(p`"pL}hڇB8C`Jڇ!pa B!!cC>c/!b(xډT#n@p[=Q$,)ϣ+ܕS֐8rL}h*oou+̬߇%UFX]û]||ڵ=FGH݄$ &6ؚ. >큻6{PȱBL2\[  羗o?j_!-32Hζ+Z6{f dG ƄnݺݻΝ;r]ټy$$$8,/Roȑr%y葜:uJ̟ɱcǜ2dȐJ3=))I=*ffbɞIɝ6p]1#eʊbs}T A!!= tQ)9-9zlhd2apn0Af/.CofCţ}SeިvpX`0]/A>7YU(?e5kSGƇ%{pOzʔƆ [v}e޻rG~oGO=Eյ1BL1\2Kl*7m/ zM\SBFy{*\qd 1`zmٿ޽{S~PX[l)ձP!JtXf;3d3h ?b޽X6l0ԩSF~yF<}.>ț> 1ᑡJ~q}tn+IxTSVcqA}nP}K+SSBK+\qd 1b(ĔU''ժT&RZuXU>Œ0w@Ӝ 1fՁcGƧ(?(xLTW`;2>$4_kY0}vA])cDqgg3+5i|6I$8F>|pdzgϖJs.>yfK5,yVBOtk+>zHnVjoU2[Tz<>,8+6XMύqɸ s$GIր獷tο?Vb[3Wd|u 9b>zڌ+7q IE3D!wel-ƌRn!bp> 0 WI$e?CV]K\k'ʔ+IWyz!/,5bՍsUG q0\v7iiLiVZw}''O;ʕ+/u;weJDO 1΅ 1#:ltθG{#Mg(%gr_o'}EUa/7jde sKb ږJ0\VӘ`-Ġܯ2ڃ?F~T&AZoJP!1Q ISp)$,3m? 6VqJ@@tҲU;}d8sJIJ.uM-v{Te ȋ5kJ :u5dCDyk!jn|kՎWȈ;!")3'JDDs>zڜ3r|pSiҬKqN2;*c)4jL|$o)s 1b2޿ىffj%6&(!`G2QʫQL$Et}$=fn/Mp9k|g=Z]+vAc!F5!fK}s^p21 ISbxVxVqЬ^XjU=Sya~':*KBL.]T.0 1΅ 1 ˩6% _ +'MI%N]ILSdC@Y67xqRB +=@fHp:UүC#å{F\c*c|..C^2! ZMgaFMҒב#Ur^uVOB.cz:,ݙ\ 1fQA .\!GO]77&v'Xe #Qjisxb pM^b\D} 1؞6ib݋IA$/7t1죧𦙳`U MrW2G ^Zg=.zr@!Ƴ }3qVlB LҮykh6p쫸z{0YQ"a+r`jiɚQE~b\ǜe[sR[ /=eo{PVX.z=f$ .I&o_CelWMYAۑ~4^~Zi-8zpLشiʒ 3X:R Cd8|vDv۶m=WXǒZ:^x0 F'4ɕ} _bBk'[d؄^.M];!=ߦVcKT9t9bu,f; :}Ӛe*IA/H愹;kqJm%ԉw S{W ˤwgЎ vsD߱~.2=7(I[8RT~ d?JrX:8āG+!a"@gvDi *eW<&䕩!ƕ Z[-2{U8|bj%ڭGҶ]uqQ!=f$ Vx6Jk顣I+9ܮC(xpX1K;0"@XA$SŪ>݂\M߃|gHچė){+7wHyQ!U{ X gͨyժ**d[|@-QcYzcg=Bi3i[ZG[!F #)5P7[\PuY&&?nÒ7DDD8,Cb>1bg j)h|DXhϞ=CpEh0w=B+9*Y.|C%uUnʊJ@j{S%s2ys( 1+=[m$&ej! 2B^_ EY}vhJ[~-,=?[h*PuԊJ`Kcd얯+! Wxbz/qp  5/Xؕ2FW6`vN{yg,\zuCe, RKTX^ i-33%`!!F}|~z 1o 5)s 13nLԱsΘk*業LIj-=PؔGܵǜ-J`zh!W1F lS2n^O9&KxM{‘-Uh^?qVfVK)-|0Ed eU$pMbF[;&bɵr_2F(a$5>F8V#j݄$IHL5$4TljOT=R;X٨prʔW6LLJq.LacAj4uN]&/ئ,e*:܁B I~yߪt1U~>'ҴCXrzӫFѶJ<;δ ;}RuE!2!cC>?;!B G`J>!pa E!>CþB(p`"pL}hڇB8PSNt46 8T}-t[Q z|>Liڇq̷ʉyfVCے*́E#[.^>>Hc*LeY٦*6!Vzs+[Yg? 1VtEZ P!p{i?jɼk=a2S.*3l{¯%=qv*ʶOySIlյDŽnݺݻΝ;r]ټy$$$8,/Roȑr%y葜:uJ̟ɱcǜ2dȐJ3KS=zTf͚sqŒ=;mڑ2eH}u칾Tfm*0/c=c.2*>XQҳmmc"2apn0Af/.CofCţ}Su긘pXjwM`EmPgl@Yeik$<*L}ީo+1l#)0k>҄m3-7']"m-K {!xۄrbCC32_t h~ ,FѰ_$ʋdl&MNWt)]%Rǹa|aezOM4m.]?y>qA}nP}K+SSBK+\hkb*>eА 1UE^uZ0VMĦ6jժ0ؔRIБtblY8vd|BDu?#S%n=_kYd1}vA])cDhpgg3+5i|=h#9ۯc8ݠG}kmd]ƃ׼kL ĴVd*1!&&ΣYz~ʅ ƍʓB̹sĉVtο?Vb[3Wd|u 9b>g 3Ng.ܰbܵ'6^Zg~ejb~/]AT4:zzYq<*+l6 &~BݴSNm\ 8AC) nv!93aj;u$"sr`R@R)W~"C&_YMq1Cga q9N#mO (yQq[uF6IyQО;*:" >7<&*GhcgM)tVyGH GӞvݒ ޵{njm>mޚD%yKc%`_U948׼k̛B켥I&NgJ+ĴjJ;^/9:|;+BLLD {AOU' ,=e(x*-`ɹ2.1ZA_/.Qe>0yziLzH./u_C RcND!J(RHXJ=hO^F1ob<('O4^zZYn|駒\2YnF~G3f 1O+Us.|Uue5.)ae^DX i"b VZ&l~Xwmj^ye !߯Dȳڏ,V;Tг#WS*nVE6rvt )I.#dT Y0ڮlL(+BAx&) O$,Uo=0v9 &[w w 1@}Ce XRm@oɋJ4b) !X;wαuYDCX&3J0/wB ,:m%؞ 쥛f!^,*_rj[HH9L 9e5F! 1jؤIidv+dے̾.'EW~7,NDV^&ɶI:1PwB;[ 1,.ysuVN|)-+UJ#YWW+!fM!fk5MB ~A߱Mz}h\X&Võ\6\c 1ׯK5ʔ'A,@5j8L:N{ 1o3e+նME!f|s?O,@n;o{:8g@SZW""GKemU {T>Hk5YcG!4IK_GTy@zr,a̗L1 zVGO]77&v'X- BTJ'c/D x0uW$ٕ !@ ;wP39N"mA}zw؆<(mht4nRJ/wʜˬ]h΅IeWf#D.G9(fLu[1{Ii˼/j PU\l䯽ftWQQV9C_MB&1FU+IDZxf)c~Ks rg=v#豏=8o c&dwAܒdmK֋Zk55(UR޴iʒ 3X:R Cd8|vDv۶m=WXǒZ:^x0 F'4ɕ} _bBk'[d؄^.M];!=ߦV{T9t9bu,f; :}Ӛe*IA/H愹;kqJm%ԉw S{W ˤwgЎ vsD߱~.2= (&^.U\M%M֫bk$oQ.Bxdb2ڴWh]Ԅ~ZJ{ᝃI발212IveC-6Zzf)ѽg_ʃUu,yFkIv,hV>Hkw"dzۃ0aBFqg$Uz6Jk饢%EْdT؝Wf#rԠ,N._B Vs:02)EHd ^PA[yڳjRHچɻakSV2 Fx(I@dAΚQ>1/Me-'zʼ ٨.P"X^Y>zΗ(EKV+zk>Ǖ} z}z-_ c̑',;b>\c{:ve._sslܸ\&""a:و#>KHHPKAs$Ŋ@{q-Bb\GϹU!r*ɮrSVTJ7P3vޛ*A/ _GC|Uyo]Rm#g6,e46TvxΰL1ɔ:"%l(`5& 2C3TkaA%@BSNլ~z$!D,qIXr"ūԲ8e|"Hw>=mU*._=`╪?XQ>|pþZ=q9X];z=m6r[\DŽ\ Xcb1TS:=11yLaV(8XBjVHܵǜ-J2۸2ZȕLJ;{9_eiOiqexԬ\+Lˏɐ KLJ#Qn_FOezx sa?Zͫ B󊴏/&xȩ yUPIJcxbVT"FsT5).ejoTBa&RE;|RTX\gu$!1ɼ*+d4$4Tj7){PwLA2) l23LJyb:9sb\[u]?_=Ǖ}Upl?vł,=wC{h$I`)俜oQ- ώ5?=3wտe):GbxQL CB8PÁprs}!wB,AGL(p`"pL}hڇ}'PD@>!pa E!k0il@pk\\+q}=eyTB}vSڇ}1rbEжJs`k~x!Rg f^&W6 =15]~_O|we+ ؀I9PI[  羗o?j_!-32Hζ+Zcg竪RIlLu&w;wݻwe͒l||:tH K}#GʥKѣGr)2VTT$ǎsʐ!C*mw,M}Q5kυ7KLrH늩)SVW˞Ke֦2C {:=2 RO%=ۦ+6v,7H)g $Ok29f6T<7\ "yXޞ3jiY貑Vf4Yά^|aa^C|n;P2XKrǒ=xOwuf=eJcÅKȆ-o2]i {%ofЩlՆB Jo)kZBL|ұpU{<%h…y݄i|sk5޾}[߯|w<}T0#"g8֖-[Ju,Hqq֬Y, 4Hϟػw:ֆ :uT)c9sF^xO o;"@y6BLxd쿳R_\ },ݲʴ5y>Էs6IF5k^iB̶ݖMv.6M͖XƋ}mB 1Q!١r{dݯ jnIiV~zdWhXG/jbEyZh&'Wek.k\0V02=ާ&KM6.ˌ7IKo(>RR륕Lim)!Fc.&\P ɡC!8٫NZ ժT&RZuXU>V0wZ@ӜN챢:p%~ lGƧJTzub.삺JSƈ8g8WjX'reKUI$8<1`tIigu>Mmky|ٳgK%9f<\z%t<\+!a'm5UBLڕnBuc$w+7*`-C*_A_cCAz,xH8{d\v{9ӣ$k@[$11YSR?;lMrsիەRq^ۄ9@glM4=V3lCMdwSꩺJSƈ g!L8sw\xuLGT R$!1ɥ+ IR]hvw%%ڏp1gre=];b ϭBL2&s?/Ihd}d!/ʄ3W8nu3dM,LL-'zPMeV{LjP:X2QknU@߅dКӪIJ=Ȓ})8j\!s*غչCuCı<44XQǘߪq{?T444TOb)@Y(qV>sYx z-Ϟ=R 1zEUb୲4~a.4*+'Qt~=wOLL˖3YƋPX.!Idlϒ締BL2z t=SJέ-P& ܃~2d-}(w>>7?ZMNᨎ}XiǿJ"]QD{^bAcJ(V]4c-vMDcL{cr{Xyx3Lgaϋ:>{z*8l's{oh]Ne,YE+n wѭOx @As>M=K@j"}+=4_ӵ[@Tl4.+} ',,v;Nw>>tu+"]j#5vfkxb.*kSk I֟u;lT~HCOI] .B]_E8w;ͺՍ8pvnZ tH| ]_GV]mNTfÈF^QL /dw|Ӹ"C2*D5"G߂uM5<~$qmN>٠E'sx֏> u:B@ -̤ilt^rlnaDLǎŋ4k,ٳ'ZkjԨO ƾcZ|9{q6J 7[=LeSR&Y~2g};+DLtx dx.2##Ċw1?/dWLcw'Ӝ}L{5/,eOLA#A{?_\F3AK˨N? xi٘Cp#DgD&~.œwyVlNQLfRmYڀ!.sX}׻o6$=q~{oH]qK9t(_v A^#:ɢ'ux! Vpz妧RØ0D_ )C{.S|UhР ׏SFOCn!Mi&c1y%%Y!bFK7=a ozA'˞"8{3xY qC-ɮEKװ"HEyi36DWJ(Ν:',>A^!WHxYD lRAA6ȼ;#b<}_SC se$`7 8uBgaAo٘\ Ӭ`BS`$#L% }<. ~ݎ0Sڐ+"FEz"&yW/y_of>+V:4:Έ\Μ[aNKzjf0A9q%.DѣG`6;w~&ODLPPI_D\**ZmcbeDr_1J+{hT6+"=2!bj3!rlerruc2,CfgQjkgtH8F; [obR^t~dZ1u0iڀ&MEqu?0P%D s`~rU  yN o^%b* ɸ"bPR}x۹ς'.0iR),)u[ pYغrۊW>MOZ*D X8^B-88:,12ΈOB:ʥ0rxV=ݩa_x/w$+0UǥbW)Wd\1*z1=&=ehQ?_×1LL>DQ\FU$#}^ ^F>'2yp& q]~'|'(| %bz> M4|s>g˛ _$bZvLbr9i1meU"oaxWh'pNg㢑#RרdZώ2H6_:Yk6'??s x]MF|UEQ1e+W ]k=q7Eu!!l\ᶌ+㿲DE""F#⃹У[6D̤)3֡#";.VU{sYe" Ucd]UM}!DL b@ C$cwEWS:TBcTD2ors\{p۰ XSzGuUJĸҏ|dC>|`C?Ǽ-V@u"\#^?NY?՝'{<.8@׮]uV*O'Zli: 1wY"m J:usDLɺ\}͹AG ʾ_D Y/ؖO,hoZf\:p ;.W4- "9|$XZi!G!4IK:&pr^:IS6y]RH9d̒U_ftԮ="#8D|{Abxé+#3dnv!ɨwXµ? "ƕ~@* 犞Thy=jVfϷFUwOB'bFfjf؊0p̾u0:N^_a7Y/4D]q.F*Ӏ2Ve:.X2-xu"*28 C}l=1*Q/$5'V!Yk5mz>YoY Zb;3JĨȼOx˂dX:Fd51R?O 콑Ft >p!>Dv֭ܟ\[+v~1%%="˗/AUBGe.| Dl9+^q=ls Km׼tH3Oߣa}|:GLiN?1(ΰk hxoHIdA؟/,, sv4Es*1ɓĆQuRCҭ9rռ4۷gа-ATܜ5zRŵ8hdSϙy5xd:#bR;wch{}mܼZ^x!@S)#3jx5]KīQ|0/|'Wo>.]{XJ:Cn؈ц 9U%6zigWVxۢek^g'#%bTcd޷>Ȳ Y*}!D1һ<=ِcKQF4rgt l:Z9Qΰlq[ {L^2j;H䬙 KijT*2C6jв\zQѳ c Ϟ=ǦM,2ve$6%@PD #D̠!ù2kZ$U=v傝y1ȝ$ )9pR%F>XTK0Z\/L$eG7W^GOĀxӟ I~1^`[hQяyoպ ;ֵIk'@65><4RM%,K9A#'/ҧOSƨq>e͞*2p񲵴qNWm=U~oG(gj{[?9v!b|PqS:ЂK1MTqצ_5rO1/]D~MḒ[h߾}L0ݻwӧiZ[lqZh_"&֮]wn6xFI .dٳqFgz=O>M/_dbsqí;{}la"&,"^E.,_F*YCa}knL~f.ϲ|ֶ[J1[r)Wӏv3i,x@ oƈȰ`sMڙƦ3k#)&k?=e,nӎg96?Ou҂~-%5M:aeUN4ȈЏ1BFքAZH(&5ժUtb[Q~6#tFБ&)qC?0GmqL 6>.j3pDx+?t.ܑ1B43 sFcL?aC (yG 6XaoO#sG1~TsޠILm<:О#00Ο?Oׯ_gOwgǭ>{ YFvРA܏jՏ7x6vݛ۩AÄ(8Եй_&b%V~Ts'S|7*T?v=Vj!CɠnqSճJi%45oq&e0` ي=oϏ7N$;8߭XSNm֮m{l8mB]d;Oqsk֡F܄Ħܖ;2F`.`^g TE$R|&NɈJiޒY:mͪ΅~GqxrF>S15Hq0~߾'Ȼr>E7]_м{F<ڰ50i%i7ہq+eim)D3Ͻ2na"H](*w4М[ Mko\@4r)ƃ׷,ܣC_qYRC?vAEU]CQ/\7cqn-Xo~օho0g2*q2v_~Qp{3>SLCBB~c=A2^z.rرcV}WTVVDأo D U.1|޶(oШP#Z5}se1vPrRA3Fu/bŻ;f)ou1~eTBni= foֈZYB1celU6`0])~n:tbe "F6:m%alg8l's{o v]Ne,YE+n wѭOx0ϐ*:ɲsaw4}| ,'TdTp|ӜMn}Rc/Ӹ\6pڽ8#׭\w٪> ڙY3oШ1Yv;wR 2*qw|`c.0>E-{")nΞ6!90j Ӣ8>n);ɞ2Z;5/Tp:/0 q^E/xF5 do ߰Uvǥ0iԮ'$z 7,BKtWdHFhPѳF^chj!Wskc~ ݜ|dn T•~\GuG:i/ 6[n0|]"cǎ 5kٓVZE_55j'gc߱c-_P!"\*0l2UNKd9ʜn1!|Ϗcy|`mx+Eta_2 ߝOs1 D ~e>1y\|q|O_.-:|_ =ecIAOޥ-Z8uF}TбGڀ!.s}dO$C F%:D^mLאA+xG ɘlC2*Drܸe6sWnSVv nF p8g3{b:{&g̙Ǡ'#2'LbuAv7)ۻ.H)-չpW.ysaL ah잾/! wՈ,z1=cCEσyض,ЛH]s"ً^ ZdBNh۔ǥ:%6{TQ!T1}Wg_p=ٻ ѳ/imy gq2#=SS<իWEFG/X $ACDL>}8KhhLjWsDȼtrK]U3rt^7mdW:6I0&wsl,A˧/T?"N/Tjՙ@4;ehåj2]<ρ굂B`{z-i#$_u?0\$ 2D̈Qc'%M6~3UdgZ}vU't%h0"B411o:;|Ɔ`J7ٹS^Cׇ^?+}1€nԩ&pWM}AǨd"Cc}!DL Q<'`V" ЂɈPKb cǿeV L^#ڇ7Q^ *o٘G( E*K3_l/DLv`_kC2=뉘r]uث畗<6 /=& H0ҼR̅3"UT֏qK*KĨ̅1;//5I<?&V}D5 }AuTZ 6SDT^xW c+s>Gnsaz6;S_+?+WE1r}U&ӊM4qh*-4ct*!b# TݿM(+ks1ہ{d\ ()>` օ_Ѡ_]"sA8Bn=z1 >ա#2;.V5͇Ե[OΑ7.9Gݚ D?ڽ|9HīP`d잾/D hUk1ocRׁ'zcQhrz}6ŏ >Ka)˨?Zؑ ;̐%ɨwXWEϪD+i,:V@޻czwLF=M 75?Z8EgS=k׮Qݺu+'@-[{_~m;w,6V@u{qE:ҏѹE"d].ܠe_W/۬pl˧} 47S3.P8xLwqRT>Hke-ʴ#Œ%Ue89/{r$)<Ю )$&d̒UlذtVGdd{{/H $uJƈL Y`ۄ=fHd;J{]˟J? FO M*]<,PM1=u[TY4׭pW\4>z!-Y=}_D Mjf؊D CAHvX9$<'@gdӐs9C^!LK M,q5ǒU]K"^g-,ң/g"FE?*$*$M3dyy'F™~T~TƞyC#2P^^iiijJ'A2:t%ݺu+'7 ?~LIIfKyPh02v$gQ _%bBѩ'[hAN02x\\NĠJ J0B`! roF(6O$ugve!t9\"@긌9G&RՂj3$:d6 -9Tp-}=1*Q/:?ƣPaK_˄wY^\8ӏJT֏[ 6O{³g,mi&Lxx]%]Vm)x.vm7[#cW!bGe.|:7n;'u&Wr(߱_fʂw8A/ϷӬYP_%b>ZGY;1"2^,cy[4vxΈL1)#t%Ll,n' Hf9םq- Ȟ?]ls"m\Q 1 xSU7,嫿)_ "r!b e~q\'2c,(GEƈL YsdρԨTvmC2gEe9 7IbQ+~!?FVyX?z2 ƣPK_(k$'52Ia ?qt622vOB(Qgbaأ:ND\VdCíSE?(_}jD%ѥ}yz4}Tw>"rt/8yz{hah]HcJKIkGGOŽumI<&MM㢭 Tz sR|wNeիOGN^O>>Q|)=eUdeki*۬m{ޮ~r O@ /4mSDL\n3׌YM/_dbsqí;{}la"&,"^E.,_F*YCa}knL~f.ϲ|ֶ[J1[r)Wӏv3i,x@ oƈȰ`sMڙƦ3k#)&k?=e,nӎg96?Ou҂~-%5M:aeUNTV?UVB2V& j"?@@1IV>؊j9aF0ꝅD5iN1A8j׎KdNqPϟ㈸$lԔ[\W,s厌"ƕ1g+6&8;>׮1MAe޵yjBщ-zvO@{zϏ@:<]~=Y%bΞ=KǏɓ'fAq?U?F>n83g"bTuoNoc :PF@~mi[Q%bέLك;1޸ë@dWFPD X q$>TZMWύ}臏Ԍnř95g+g~???j8?ks݊5jlڶ~l6&aO̞ר陷aj$ MHlm#c[7QU{~o0ioNDԌBPvޞjҳAjW7ŷeܽMh4+YTi%i7ہq7¢,1h湗VD uhE%?s럸Awٽ H_F=xp= vT%{w+_~QO6$+=-+RƝ&7ӐkEOW{mKĀ8vg_}y-Ѡ:(o(%%m"Fe.jo5K "ʛg&4"&&"VM\? 9~dЌQEƋd7ǻ;f)u1~eTBni= foֈZYB1c߯ZOtWtgԡS+a쵱i+ e[=-_aK>OwMO+~@_w2,`*Zv mؼnTteôiYRCOȨ1[9b;N_qY| ;?8x|rw| +A\e=-[J55]CGE*k^ DW(@Ξ6"0#M:Q|>l ~u# ]cS_KnY~1q| ۫2FB`~ѰUvǥ iԮ'$z 7,BKtWdHFQѳFĀ[BPPx45?BĀ89 -̬iŕ{*nsh!/ 6[n0|]"cǎ 5kٓVZE_55j'gc߱c-_=E8 _%bFtTWU ҩt),G?@"&:<^o||2cm"b~:_ɰnƄŅ9k"r?_X2?F~fӃ'/QAӲ1$~GRvL \ 'ROذ:̞;Rm qw<`}$?+~@Z[2 CEڶI^} $6$B ʍ_Z~l:w6ee1 ,f̡ p8g"7 wLk k2Dqg5/1ժX{mH ­׬-ErH aC$fyD]s$835s0p3:V^` ?2מ/1}\.#b\ͅ1#/- vU ӹC2{q޴]w&UjIh-:XdXFPD +8rSaLWga=ZrteT> 4_ yhGa)'V{wH!Bܦ4|ձ>r,1#FPXXW=۠U8QOj>r&M5C-ɮEKװ7"śV$01o:;|Ɔ`qu16Kaa c;DLFּBT9MBl$ZBINh87,?-/ZXda>9AL'a3Cqi}vHup*׆d\1*z1iotUkέ00wz 1 F8*fU!!ĉxhO1ׯGQBBXܙ~KĠ *h)Q}X*̈́ȱ9V#7չʰ`=G}]ѕ"lyKEc{ iԦi84Mձ^rC1aGU H0lsRE{;ڐ8(S)t{d\1()>` ᶌ P"s \ѭGo"fҔntYWDAjQh1ȋF=,uzqO(4)nߦ"xCKD[Ӱeeq-/vŽ>3$cI*U\0UO{[ƀIg{:lyqgxSG1:HĔ˥ל~Ep;m@1ujepj91|.NCb bIrErQB$:l'e@\0eڕqz!C0A,X+ɋjA^l:+M튈#22'{/H3o8u%cqC, lŽ3$cy]R0kw)/ D31E[@ӡqg#k^l!b ^)b+S2?PbAH~vĔqn^Qi ^d$!T(x"Eu\Fg$}xגwl![Yn!UO{Y/ Z|0F41ۨސcw>\%4~]zvO(//g4JMM9HC$[nz^Ǐ))6^|i7 FƮL?*sDLH :d >ȩ\&PagX*}l+-:$љѰ>>#4y g5E{d47{${ $2D lRI;K9mIb(`XN:q ֜ri9 j vۍ3hXϖL \ ezIinN\?UZl^R@99GLgDLjn `ޣ͒W W̜J!bPKī%+Oh?0a>QZ_`|H]ܰ 1J Eּ>?TYqg#k^l!b\q< VoIYQ8rt l:ZQ0a΋r[ {L^"2J8H䬙 Kijxw!ih~.QksYԼ$B%|~JdˌǼͪz h,onOx왥 =6md +dʲ.>>KA{$EEݻw UgQ _%bNMIvJwWN 4kE%WQ|ğ:hT4vxΈL1)#t%Ll,n' Hf9םq- Ȟ?]lk_m5JogOea.R 0)b4d8\up \ յb,(GEL YPyρ( #2z`Qj.Qks;uA[o+y 1JĀ$B%|~ogkȚA*SnLT Xg1MPD\ı{Ydm"I3B#b[Qpd*X5inId01rU#ƈlk~8J,A&:SrLEh+2#cʹꁷ<8(JIITѻ,:xìnx5InhRu1x _?2Q7X$pm Rd &"IJonF΋aZ$rv_6ń31!E;ɵqƾ( 7nb)) Hu)!1dשV{gUJ2pM-lAEF{cy6ARdyg ,;Ǒyy㊃:=֤TS%!-~Rȿ?ЂS'n5ȳ_3{^d>VFmG/!bdS'~D?@ ] #~D?@ G.%D@ T ~?2v@ DlL@яG#!bB646 (~:OHd} i||ՀLp%-@*_lяt|e+  j4/ɲZxLjwQQ\kIMQS*2\z "%"%bBGMO?G6q&.׌Y-=Hc7_vzL;J[?~h_N;M~Ѯ]tڼy3ەR~~כ0a]x>|H'O4wtQ=zt홪cGi#Gܹs=>ެgX{]d 7Vtl@khnyZ-ASk&.).͛ԣ飺q3x1ӻ`<[FW7ME#Bz/cUZzLZ;:|v}ǭkuo@N1ljm}xhKXH3'ӽ/s*GN^O>>Q|)=eUdeki*۬m{vG? h_{K:vVW\RL6JĵF=y͸SD vW9~g$KIM+%=EhpY1DhL_}e>aMԪE~T?bZSZ9L'&ժgs>Œ`; WjҜ"';$b,pā`2?(QSos]B΅^Ж;2FWzƜahlp4ځ4qJ`Lщ-CPlP b:-oT~O@{zϏ@:<]~=Y%bΞ=KǏɓ'fAq?U?F>n83g"bTuoNoc :PF@~mi[Q%bέLك;1޸ë@dWFPD X q$>TZMW<}臏Ԍnř95g+g~???j8?{|bM9Z"[vm0H6cyo'fxk\˰5&$6ܑ1Bԭs(, 5ͩ3~{'b0&ZlJiޒꆅ91㲝Cg ŁS|^V Jt~A?kbtålyfGn=KdX9<Ҋ  vؾCsn7h.2.D@*QHQޡ?, g~Kq{J*kh}!XqFooE7Lh DLLD>2zZ;s(zy9m)ɠow17w̤S8%bʨz@; |g1#zfcF>Ϋ'm`pS)ݸ]uŊ8A8{m=tJFajutt乏 ծ}'2 VB6[A*mdٹ0;|>{c*2*D V8{iοk>qDαӗi\V._CҕhݦtGʵ-䕦v;-2v`GO -[ 5]+RF܎@Q=mBr`Eq|ܤSwCbp# c-\d΋0pvu'Ǜy۹10[ tH| ]_GV]mNTF"G^QL Odw|Ӹ"C2*D5"ʯԷ`{GSc D 9k6^( ڎbÇCGN. s6VMI݇ M2Ҏl`ug%b:vH/^YfQϞ=iժU_SF|hp6;vSDU"aDgJGu5Qe:/ O2r 9+5_!bC8&'>v."e iL|3iO&b ,?c:hh;=}rri d9-sHw(l¥x.U4oъ ѩ3) 9ݾCM~ ic]i`M -v}x-!@mudyJ$ 6$B W͍_Z~l:w6ee1 fasH? bq'~RO*`}F2BiG6SlWի"#Tܣ,Xya~Gړ%"O>%44cDU"fd^:e%*X9a:wHf/Nڛ6+n$JFEƋeOĀӋ(7=Ƅqu&&*MNڣ%p韚LW6Os z@󵐇m8~/ƞ2zbw$r !mH3W #'/) #'U6~U$NTegZ}vUʙ4Ր$-]C7$BĜx dbtw !.ݲz!d:ھ+zGAٹ_"Ƥo$#Dvv" {$`2" 8!uB#LECo|’ ӬaB`#, aE t}<.KXVM:%ckmHg=V\kDL> #:dϟW@1N:cL=@Άp'NXzY~==zr?ɓ} 1+UQ _%bv^^BEkx~L,cՐV+Y"UPiE3p|ORyEdGF=D_m&ḎYNulTs>OZ|\ hdOlT]*ۋoL+6Mġh>F6 bۏ8D,Uo=`;IJjv 9F퀈=C2G0i8|)U$b KJ+C}8y:xÐuS# DL(.0$ ?T. 3VgIӝ1b F HqUL4kM"St ɸ"bT'bzL.s{-D̎2cD!\eGfZ=!0Ƀ+W0 nxsZO?;A7n,ӻwoi￧ϟ1<[\"Ӳc#ǾHS+䎯1x{xB?s?CqFu%#zvI@z\]Ե9s[l5*J)[J- ^0 |4𦨮7$$ɇC3ܖW$#1-_ 0ztۆ4eFtD}vҡ=e,WY"Q;b;S-!/}V#D +u`9N{&!ڷd2p)6:.DhaGv!K"ruUJĸ&O@yӐq3d!E>Rb^fеkרnݺʓb~ -[ڽ/B6D̝;w|A~+ҽ{N\"S.v_snQm >Kƛ֩EENdKbU8 {eA$ɵ2eZGaFMҒ zrMhWd$o`?#'/{4+"F( nO 1ו"ƙA ;{̐ab R^L/7W>m&mݵߐqSh{ ):ݼ9FA7Yl?+"<%o%vd)yĮHΫO;hzG-!'=s6B$M,G2J8%+W罡"cyK<0í?їVѳ -Y/kzg+|sn\g^Hd}Fڑ=7(55N :nݺkyΏ?$Gx<(B4Jh3̅1!uԓ-4v rkr@X0Nj`h\C5\ZN1=f1vvc ֳ%?B~8f*U\VL18\7؎G3"&s7fwчIfͫew 91B8ӡW#Dz  s''* f#՛K<{1*ђ"9YoKO{Шs6VXH;b;SCŠƮ"7 + w MC+_=j61Z:yQk<`|3r[Du\Fgɂ5a)MzAEuFmZKTZ\/*zV!bT"O,/?lgaZa47lWhG6SgϞYcӦMp2Ƭّ[nGcbj# DL*7&*\3 m(".R=qD6V VYJ?oMscgh!W=b蹲"fyؒkT)˥^v?>VqLxfσꞈK!я>hzxuYCjp$b~d(#n]5HڤAALDe>1tOHd gb$).C*3v\ky}Q|o܄]#kpH[׉$^ZT˟U)7IqNWў)*zX$5M9Qww"fݦyyLf-i֟J+zĸӎBT`N5i1qU~,DL6SяG1O-ꉢ[AL bbp[шљ-B1~D?я@ G.P1r #x:ݰ(lяGt!d @N{aQ~D?я@ G.I Ȟ ~@@ D1oa&  IP'4Ud}d|3+YA[c'~D?o~ߗ̱`![A%YVk~\/ߟ)6VY4**0Ɋ4LS*2\ ԁy}!{1n-~*n|mZy͸g҃4ve\޽󯔹*_KU9U\c5a3ׯڵn߾Mwܡ͛7S||]ٸ8:x } &ŋÇtIJKK|WXXHGuѣGW۞:v>r͝;sqgEvpmEFP JYXZ@բ>f" >!ܼI=>;wjO9C:<{ ýo|u0T4.{1VǤ#'XawܺV6 tz$ȦqV߇Qi9c){b>;2(M|E3c5Χ읢]YE.^6ny2]/߶Ǜqk3i[1Bĸ)hmHkӍz/q9돧 r"O޺uCnn.ݽ{>}JeW***baڵfgȑpBƞ={Z7n|֫Wj3K&F<=ުWș= GG϶&b"BhU"Jߛet9JhFtw;h,gmTu(r9h<3V^Ao /Ρ[;gѤilZ;ӽ6Lk2s±= _Y| <6dЀtbU.l&]Z&86SR6(/SiJTN;)-RRJɸCO1-\VDLeD}!D1n#c k V-Ԛjժ!1Vm?YhMT8١J=kG%2q_'LqD\E6jxXHӹ rGJϘ3 Η3iGeޡ)ؒ۵݀bwJkLumuSfm-=!::ƣ=?tudq9{,?~'OК5kld HNNVqxgΜqQ oӏ;h׽941L~C]a"QRlG9~2ex^A1hc2đ Pk7_=D>ZBS3gRs[qJHL"s읏s݊5jlڶAl6&aO̞רaj$ MHlm#cA QU{m0ioN]PWQYC)[RݰJqDo!jNU{ՏRj D'$7ŷe5ܽMh4+&FL )d3ͻ4?r;0wPX9OwMO+~` _w2,`*Zv mؼn>T!H{Ӿç_g H =٠"BĠokvsH;}e5ajG?*䗭{3] ӝϾO<=}2*kKOn?ΙL׿'DNĀs&|&a`LI4Їگnۀݸc_mL.ٯ" f]8o;W[-d:4q./ƣa.|6C'*K3a#/ Ө]O&Hn? Y]0y4Ȑ gѷ`{GSc G4hщ{*3G;s8kc~}xj'ܻN{sFdddߺuk31;v/^ЬYgϞj*믩QF>A48;h|)"\*0l2UNKd9ʜn 1!|Ϗcy|`mx+Eta_2 ߝOs1 D ~e>1y\|q|O_.-:|_ =ecIAOޥ-Z:uF1=wH;ۼO0e. πzt8ׯ`mi#n؆kۮ#$=z5C,z !2&3ې &7~i ܕ۔gCXQ0L#Ι3jG?*k5s$&NZm1k^3{b:{&̙۩jNU{O]358}'D[NĄDŲQYЈ,(_a#A1a=o1{Xu b/kDBXLh۔ǥ:%6{fQ!bT}yN*`}J1O11~MؿD^WvPq^`HkO֗>}p.P1Wygb!8ioȮtmC`L*5$X4O,2^,#~"D?^D0&30Wiw-9Kdy*yk<4h#h}0;$[!nFI`(9yIIjeƲ oozA'"q*8{g'LZ]ga #2*D H"Ӿgl]D3CJ1xdNy2\FTemlݵAyˉxD <%MNh87,?-Zda>'@T'!(CqivVM:%cȀkmHg=!V\'#V87B;蕷^3$cINU\0UWQiG?T?Ф! ?M$gS\c/ҸYM׉x\8p]Fu֭TXlu~*..!bܹD XՕݻuH?Fus?}]n~PZ-V(Y"&4޴N͸ .B-2t|8w"]2"iH[,SD r I!(B8 3BhWu\M?8'!Lv_^H!AL1KV3J:rmнJS"b􈌌pwxbq ^d1tMcd,/KJ&ֽp.trCQѡvTVUShg=j74Y@"oqlNUTЧ1o)H,[A^7FӶ#^!q>y;&R K<џVeQFu\Fg$}xגW"cyK<0í?їXVѳ avTrXCjJ&9"9Yo zyxj xA^陿G:S^^iiijJ' s c{Idn͵BiǏSR#m|no!]%4ə~TWAt;}S59L 6ϰTV.F$:=ǧsĔs1 xfdo!DR>ɒ0aGStq1cZGY;1"2^,cq//F;VFmsȂ/`ڵ?-Zm Mnn<98PAV#dGosX $k r #%խo).{<̉~D?@ ] Irb"~D?@@ 11 D?я@ GY1Bxa&ET|-tT i||ULp%+PCHT,볲Ud3~ϻ96Dt+ؿ$j͏^3ƪ=FEEseViMtJ?1"{,~B8D[UgH&.q9ϰiNWAw+enג#45wSJz(w4f}Vz i~h׮]tmsm޼)??M0.^H>'ORZZB:zC=Lձ4#Ghܹ o֏3,=.Kn+:6 VN]Whe4 ;ֵIk'@65><4RM%,K9W>9y>}2F){h|}VqG[wz BTH?=z;>UY!b~JӴM1qmQe^3.gaUN8O'KޙSJk(3,o ֭[o>&rssݻS~ p-[u-믿RQQk׮w7<#G 2ڸq^zU˞idO/_21VBVȝ=>z0BnmQ/ ͡PWzG5J7O A3gY>k-ڈsF+G;ɴ Z?HdXFNcDLdX0}yp9& LcڙY~zx.P8k5 9N2uͤK˄|צR|Xjݦr*3mV :~ 9{'9WIj\)wu)"Fü˪qљ>7Uz"FX}š0U$ӉIl{+0#Bk4 kG%2qblی˄~ G%Qdv+߸ꏅd1 -wd19\0|9ӏ6f1Z؎ʵ\͗\R7hbw#3ji}~:)ittG{~.sY:~gO<5k4\12qϜ92ަwЮ{s:|;5hc9`~hܯ 1b]?D̹){pG>wxA+!dЇ^ ^7Y%´э߷82ClŞ7G'PBb`|V)UXdQ,`mUM+Þ t=c)Qco\P#YpnBbSn#DLݺa<B0soq|M{sL?ژAČpm\ 7kD=rDu.\C#:"FoL/B9yPއTh 4 wш^ڦEV}0f-aQvK4K+"F6B4e`G͹Oޠ, _e\@4r)ƃѷ,ܣC_qYRC?v"FERJZ߳?qnTCR/凹 7?Ye/DқaMLCBB~c=A2^z.rرcV}WTVVDأo D U.1|޶(oШP#Z5}se1vPrRA3Fu/bŻ;f)ou1~eTBni= foֈZYB1c{߯ZOtWtgԡS+4쵱i+ 0ZeqǥO]ߓ>&W ,XVB6[AWUteôiYRCOȨ1[9ҡb;N_qY| ;?80bCݷ?MgK2*Pu={ZBcCo0-&(C*)PYr m_KnY~1qUgD\#@ 0hت _ЉҌ4jד D1,zc^Ki\!"FEPxW[BPPx45?֐~TC\]KeT\tB 3njј@UÚfddߺuk31;v/^ЬYgϞj*믩QF>A48;h|)"\*0l2UNKd9ʜn1!|Ϗcy|`mx+Eta_2 ߝOs1 D ~e>1y\|q|O_.-:|_ =ecIAOޥ-Z9uF1~wH/C\戁! 4Ȟ& fp\_^bׇGܒ"׶]G&HňA}xY /ɘlC2*D rܸe6sWnSVv nFsJB\] }Mҝ jᐡ:t߄"֨X{mH׬-Er8aBK߂I?yDW10xU"L {?nSfO?5_dWвcDFQѳF"LnQR#q_Y戈kf xu^-2B=zd y=Y_"bù\BCC=Fĸ _%bF_[" Пsd⤽i#ҹ__M1ԜdcZ>ux! Vpz妧RØ0D_ )C{.S|UhР ׏SFOCn!Mi&cz%%Y!bFK7=pzAE"q*8{gRΤt%hڽ!"ś4]&M}ؐ?1*-(mv=F%s #z" xr$`2" 8<( cǿeE KL^#ڇ'Q A0I_4?kuckmHg=V\%b]KeTkd͜[!LX7dy:)`ĉ\O1ׯGQBBXܙ~KĠ *h)Q}X*̈́ȱ9V#7չʰ`=G}]ѕ"lyKEc{ iԦi84Mձ@\rC1ρG:Ui HZs3kڂs1ΝB>gH}>qCl*%ѡ Z(|$!h╆:t߄"ub06B[P$b&X{XŞz$uOwJ^a"^ Ձ+0Uǥ #DLѵ2$㊈QѳD1y7J8|‘0^$ {^=DuXS7S$)yp& q]~'|'(| %bz> M4|s>g˛ _$bZvLbr9i"eU"oaxWh'pNg㢑#RרdZώ2H-qеA]9_eȦY{ԨZ뫄@5ao{CBBp o1*"㿢DE""FWp4̅z!&MQi1ή*$Zˁ΅:t=C!b!U1J໢Q)K&9{&%uT1݌rs\{1]Ngd,Ԋ&Uѳ*J?z^.k)ΗDc/o$O&n8p]v֭[<)jٲ/T\\lCܹsg)((+ݻw+ԑ~΅/1%ri5%8(j-f`[>>PDLiiq\Zd.pDƻd*&_EӐX*A\+lQqf$-ɯ/yyp WOB.ʿ8B! c GN^T bY)_WDQn O 1\ו"ƙA ;{̐YaR R^L/7HܱFZ ^9vCWo<%׹5*z"vjf؊D rl̾u51u\}l=*0;'JA|EB 8jc(~r{CDtXS7rHKKT P:Y}СC|l/֭[?W(cJJ2{D/_̓-D&9ӏ\*R7N=Bcr*W& zJ=It{4,O)G;c^v D 29C:L51Ի}%a.ŽbN[%2y0>X0Nj`h\C5\ZN1=f1vvc ֳ%?B~RO/|Uf'8cq9nXtfv =0),y9063R2Fg:jdW_GEFCD>}l2z!uڃsa#F"Ta'JA*tyK #z" RR<=1 VTQ@ˣIp<=գ֝ac 3 f p,ȁ2q(',Td^3ih~.kYDPяҔ:pTٵTKe.Z 6ƯOJxJ5u3}왥 =6md +dʲ.>>KA{$EEݻw UgQ _%bNMIvJwWN 4kE%WQ|ğ:hT4vxΈL1)#t%Ll,n' Hf9םq- Ȟ?]lk_m5JogOea._MjgQ8 +a1\ eq\ׂ7Eu ;Q1H YeρVm",Td϶Vsye\K!o ֢r gJn4C$~,csTٵvu6qtL12F֡) #DբBeuaӴ E%YJ[۸5N6} ?4D1(8^U,Iߚ4N+h!<=by( 'ekcщ-HʮU_m{VDTX ~dXAg7ꆇP䆆+/U'#Cuk6 \4;gQ"s: \ݗ1L$EsHQeƎktr-oܟ6oB񍛰+y$+  q:qq񔐘d2Tj:y*%S8!6szLQ#b i2ωʼ{B?8ϞGjĦUuX Db2soMz|ѐ~D?@)ߟZhE ѯٙE/?WAL N+#6C#1) D?я@ G. LE?я@ #c"F f*~D?@@ "F6&@ {G#d1BT!bMyK?'$>4tz op%m0:_] {GSt|e+  j4/ɲZxLjϢQQ\ IMQ㼒 W{$ "1goWI#unЂk?Ö/;=_ṶWZzS12Gv۷oӝ;whoW6..|_mOoJ?pk3iB\"~JZ)ij%bt˼f\)"Fy"Ʒ[nѾ}`ͥwӧO)223<õlֵJEEEL:]l9.\سg_kƍzU-{>}^|Ĉ[ 9[!wVDLXD]XD{S.T6"C^-(<.n\emj#bR4g&ʋ3hY b>:1a9tk,43MkgFRsM~zx.P8k5 9N2uͤK˄|צR|Xjݦr*3mV :~ 9{'9[Jj\)wu)"Fü˪y"FX}š0U$ZjsJLb+U|wZդ9E6NvhY \;".:eBm?#(QSos]B΅^Ж;2FWzƜahlpUKeUOhtmޑ"e|{BttG{~.sY:~gO<5k4\12qϜ92ަwЮ{s:|;5hc9`6:kDLj׏*sndܑ^"2'bJ-d#ׂ /M?4b1,d3ͻ4?r;0AXbâ,1h湗VypD hE%?s럸AwY6qȵDߎJ@dp}g H UdT=[SSjV _C{wS7&$a m]BGeUƅU:s?7[^67o^+zdDzk]"ıcǬ>ꫯkձGEE7|C)))n1*sQx9\bmQ<3Q'(F11!jƏch̡崥$f.2^$#9E1Nh~> s(rkLl(0{]FČz~ղ L~J<w+7>X'`N[h=.깮l:[}k{^j׾aKVʵ[h]t7C,;}OǷ`zbLEFA g7t<.(9v2kh}aey ZH}g'wOnEĨ Vm`CffỲ }M _xô?tJ}#q}7Plc* u~a4Ol8o;W3^*-d:4q./ƣa.|6C'*K#b`#/ Ө]O&Hn? Y]0y4Ȑ g)+-X$AԼXC&/곉>sҏʼ9Lĵ1?jL..ߨ\F>.ӱcGz͚5zIV5jDر/_Ǟ"bͅ1#:V?q*yYT~IoYvN a/7|>F>1޶ow1?/dWLcw'Ӝ}L{5/,eOLA#A{?_\F3AK˨N? xi٘Cp#DgD&~.œwyVlNQLfRC6^i]i`M -v}x-1!@mudGtEO^y^dfQ!b/-\?rlt3 Ixx9s+>`q*~w!̞^ux! Vpz妧RØ0D_ )C{.S|UhР ׏SFOCn!Mi&cp%%Y!bFK7=aozA'"q*8{g qC-ɮEKװ"FE/yi36D HpxhoA-[5xdNy6z= 9 $x5~,D(0t+HAIhv@p%$%A8?k FfFX%"Ur Š`Wp6.~Is1P| ɸ"bT'bҊݞwzЭFĆryW{(jҁDz r6$8qi"f#JHH0˝;?@'OY"&((ǤXTY"Fe.|yy 6N1|u?TCZgTWA`=E4oK1~92sf9:ױQ̡g?k3rU$-w7?Quhl/:a2:4m@ҢI:KWn"' ,>m+*Ko<'Ud@ļcM1JTlåqEĠ8sO\`"SXRZ)zHW:Wו"Fu\ ҇i"Kot*DLqh#{S_ 1@$_:.Hx!b!WDDLenϻw֟+$^S{ϻ1C9M{B` W\aݵ觟~wr7nY"wDOϟ?cxE"e$&Gם}.cX6_%bF=;.w&}r<.9(uJFh)*hl㹜56ks3ŀ4ktWUU Sb}1Z8 r0)o{CBBH<4m1 1C\T b. b|,G\ѭGo"fҔ [-) 8'!Lv_^H!AL1KVGN^T bCYijWDQZnO 1\ו"ƙA ;{̐gIĺH? "FE}q^VQi0zrbhʸ1S%{Jh1^btabY}( Kk$m;u|ꓶn^Qiɀd"!I .?@iśXxTeqK"Ww-xu*28$C}Yh=1*5og!@2*B^sn&=7(55N :nݺkyΏ?$Gx<(B4Jh3̅1!uԓ-4v rkr@X0Nj`h\C5\ZN1=f1vvc ֳ%?B~RO/|U'cq99?GLgDLjn `ޣ͒W xT̩"ƙDZ]-{CEFCD9g}YhMz!uڃsa#F"bTk0By.I֫2.$dmY% $1բ8T *oz2!bp  r̹}Ң7jUcAmg>*2Fg:ɲ|9pReϪ2Ee3 vQ™KB~Dy 1*ѰlU\͋=VzXWH!QNBxⰀQ%gba4mvj۩sM'*dz N`$zU2.W$}kҜ;;_EF cDU9*QwqD֧zudAbσꞈK!я>hzuYCjp$b~d(#n]5HڤAALDe>1?tOHd gb$).C*3v\ky}Q|oR58$ĭjNBbxS*ϪLeL$Rl8h=b,9Hjs2ޠцq) qMuJP9&-:.я iяGc BZ=Qt+@)a?WAL N+#6C#1) D?я@ G. LE?я@ #cD̹sXq͓$aE#d%z!DLe(Q@ яG#d U"1ByX~D?@ 1ByX~D?@ ] "F233e1 yX~D?@@ p "D(O ÊG#d͛|o>;ڵkS-h@q6mJ)))`}:u,pF&M?oi֬%%%9%bz7{]Esl@ 11pmZl;#"\d;/ w_=zd%k.:tݺu~G={ зb $AFF={~n2pzb@Ӎ#yGֆ &bytœ9B?"b\!dk^ڞ6m}=]E*s2@ @@W?ہ1RZ R}rr2H#F!bpӧ߶m[&%&L`A^/_Z82k,Lv7wnE"h#GtӇ;vdBY&b֮]!` S_~={6olիWiՆƮC̻@ @ "F "U ӟD'N 2  O?#1_osYYQVVfuٳ͛lBSH 91Bp൲f1(),,#z"FeL! # dd*:Tʼ @ !b!b?s[`8z(:uP[ϙA xT$bN>mٜ9s諯㰰0ϧ~JgΜݻLd=@VevF OqRY"$0x`]1c9h wcwC̻@ @ "F "^xaւU=`L2Wc"\). eЎ"vYi"\pj^:/^do b :v`Hii[cwC#q5@ @@W1^پ}8Ie%|! `"4I-$EgmcE(JKKcTLD ^0ȣ}*O]}@=9S14Ie9NNxlzdd:T폫y@ BBļ;V{wܙC_ rπ_["bAc  td *t*/D?>9TL 2WPPyYBCC-ߡ϶m۸B("Ħb; l\d\z׭[gWe ƒ '8P?*.@ %a#oa8*3f T'Ո$EW|5O*^d 2Ț 5UH( yy.PRuV2v!cD?H |5_nSZe{1c:U2@ @@;yLZhmPA D »2Β|i]/11z-KttрbLdTnƮCWq5@ @@jF"@ B##D@ @ 'S3n`$u7G @ "F @ b B@ @@@ @х@ D@ @ ĎX @ X @ b B@ @@@ @х@D@ @ | b B@ @@qiE?@ ̅@T "Fn`H~D?Cя@ G s!9d~D@ GB s*#7@~$D?ѡG #9]BD?Cя@ #s!9X kL#:?2S@#!E?@̅@T "F 5&~@ ̩@ DD@ @BTt!#E?@ ̅@T "Fn`1яP#\dN!b|ׯ=z>///- 4Ο?O=ǏSٻw/=bZt\ڸq##hO<ڵj׮mlzjٳ:v5vW 5dddɓ'ÇtZf իWJw}:u۷[q~|GTTsU_ĉ_iiiV5;wGv=H/s۴iجEz|WWDUA=Wi޻wCyݹ Ƅ;**JuxtMȃ-[7|C/^Oڼ *u0\"Qݻ9J{>ތudz*ol<6b>ڵV7oT6\)Bnw@%"sNgs ߃] 0D JО+^(ONNǥӧO|!Cґ؍K|47?!y/BZR^?5QsU_!xw#s>x79x߮Q]Bj֌8Ozz ޮgguX`! mqZj1sΝ7^Sxo\Q*k3de UI~!blڹs'v]f a?͟?-֭[7?ec*">[U?c72.!bxpP1bn>T"FEUuyZf͢ǏSDD {2C~;rU;B|C82\;"*by:BH I?ꊤ_E"Xjs!b|Q٣TֆgHO1&}1DDFFOڵ H{# A|M%bpMk˗/3УyN ENJ&1/^j0SIF #x"4ə$1lLpP963l~7+{(d06*\̙3Qq {TUݸqEM&bTA=WIbqb?Ӏ*XH 2U_H;Bޖ}ڴi6l7X(ر̇1#brļwS5D'Tnw@D7bC|V]&\{Ӭw#[ {Exx8Bxh "?:xӍDFt2v#"Fx7c$t"FTsU_!!6`рׯ_E<7“0uV;BޖbYeee2B#Dg?#xqf7@ DLj!9bQI(779p13f%,[g1K&xHۺ &z=*Tnd\B8h@cs<*yFn_Sp$lSY?#r*/On8"6zhnAhڎ>}ڭv\CCuh&"!AAA"'!DW1}OƩ̅6SB@*'b`x\dM8l~(k`@`CtDXtm"^1< E$ hTG*c72.!bޣ/_Z+7ZeoIjꏨ=窼<3gOYT\k`5ҎuHgF< ~T}69'"OD~TBeRYFک}!D@ DL`X_ԪUJeCX倷M4KaяqQ "=@̅(yX ?P#d\dN!bYcѡG dȜ B , яPt(d.dN!bd~D@ BB s*#7@֘Gt(d.21r GB#~? ̩@ D@ kL#:?2S@#!E?@̩̅@ D11яP#d\dN!bE?@ ̅@T "Fn`H~Cя@ G s!9j6#@ ߂1@ @ "F "F\D?Cя@ #s!9X ?яP#d\dN!bYcѡG dȜ B , я@t(d.dNE11яP#\dN!bE?@ ̅@T "Fn`H~D?Cя@ G s!9d~D@ GB s*#7@~$D?ѡG #9]BD?Cя@ #s!98,1թ! F~$D?ѡG ؆TnXSTT45oBue.ނw@7rڏTf?R eC2oرcѣVعs[iРÇ{n dݻGwܡ-[PbbB=ٳ_5mkZib jժUh4~]hVa[׉hו2}m-ϣ:6}KhۅET:6'kL DɏQΤo7W_бs^tE3c5N- !b!b<~O@ /4m#Dck,<<޽… o _'22}TCÂiǥtw;}taԁƽ#BhULO }0EﭥM'=thr(Yc@7l_FάLUB:ud.ޢ"F "7p4ve~MSddD •@4Vh1:\Ps~$tuPqD<܊4={Ǐ)88!:Z~DMvDD4m4+"f׮]Vݸq>ja@?=<6*|_YQp}td `MHFՕ5&J"qDoĩSY!?z\VZ`?%&5󃂂)!E~~~}RdmgsmW)U\KHvd.|s. ٨))}/σ¢(wC*}_1h湗VD 6ȴ47ouch&N*Q~jC1c`C۷--کS!?p$a.6nȟ;w:䔈|2>|ZtDLDtm=)gC(oٳ^; ~5&gpڽ8#ڰy>3dttZ6zԧ:vj{\V.]v n.F۴>hYWJ?e6Z]r۴nXv3C^ ~\ex.}rp25`.] "ƣ7)g}N.d 6ӂϊ6qߨqGsn$- uɞk&/~c>q4`VYxoۤ3"_+RSS 6ΝK1ӻo^}6mCH s0k,j߾=ھ};\yD42d-]u?pjס3"Gn-gPt*?]|eJno3칋ślrμl$&#KK. ܕ۔zoߵ/^iS a`H0;vR}n歘46bM ){cݷ?$!LRe.j\@ DnX{ߩYu2ݢ'b@-Xou^Ŕw+2QWzD\: [u1 aÆQ˖-O5lPm{EkJY$ńm0b0`jۀ9`CY m[Q~`oho9L" 7:#AMJHկ" 2`BD|:7[Ude~,ړ,nV9Q{|$RH3A zDdo'< en𲻇fѥY[\B+MOHB?KE 1@ixx~ ~E]tj$KB&gkc"ex̽r|!"f(_4bO|Zʫ.:1~غɁcsf+] }x;gX/:K)>9shǢ\2LԽ{wv>}l=m7GzKRDC tsMG dl =}x;gX:`>'<2TWf S sk '~,z.n+Gā<:{64g^f> eu{XWPZ&\/3gXmE0 y=BL5B:o, /҄d9Ÿ xdJo޼-[x'jը->,ZBׯ_Ӛ5kZԘqvEj(FLff􃳳sbG8)~欞.o]Vǫa!3*iE"z%Ƣ Ĵ nGZdaiep;q mqk܍cJ,{D ƽg.:1]dPJi".LG;r䈎A9%3ÜCzcy).N.pZdξQQQ2+VHJiu&ڻwK{-&NH? ┛Ãݒ~Wq{J, %|뷂P!ݐx|bcc%NjZY,b Wݟ'ˁᾚ2^>d`K=hl UF#m.V- GM}ci\0MvK獖J,l!8 Nآzb9[ixMZ}deva +{LRL29@2@lb? Agp07$Ƣ 4IˣE@V wiWTgz9 I\hz]{DS qu0M<|PSٳg:۞?.| WVV&e7p[JKKu2,|WJ%vZC[гlR7 _.`O-^ΊM:ێ,| ՗iI8+'J^`XpY1SqYIL@'/^뮄5@GNhR&oy2y^~SRV1Ξ5WU6:czVe_7eI̩";O$[[p6"`;bӬyА ƙ %Wӽ-ّ\LJzMAAA_6QxxxY|+%Cvkj&$sCOo7 ^բk6<ŁC{Mטv#ߠ ^PtoǷ20(8T34ն0VEH@`P|>?E 1@i lJQcx}QO1 )bp\c!achٍc!705A!70KAx!柊sf͋={K>D(Ck]5} :4չR_:݃| c!o%;)&-2yOfjs4=u0e쾎 ڌ1NO]YYIzۦLBNa߾}M:7уtA.s}{.ܹ;K~6Dڿ?ݹsG Sk q9knKiZjZ%z{YٗG.BD zI+&F9%|GF1M_2ڲ|d-6M—o^Jos+HNvf 0bo'}B>~E7?}J)iX@Bybi㿓gpY1ƶBy_c...t='uZ^|)Ypqssϟ00k,JOO={ ^WB 6hcl oߦ#G-xB֜'{/+GCe7]HPsY_=^>Ty)h辴t$r;frib"S^M>6K&Gv,LI~rE`t}K@UJ% e}h|g^R'eJ]![[;|a, !})[Ҕˆ {[vWb[P׮decgiۿA!'<#K׮(-lʚ<vI]-,q#5kjj͛bRۛV/$'M_:DO>%{{{%jL?C~Ӷ1>m'd 1잰"iP=E9aFܝ^~9EiVlU Ӳ܋j &wa8j`Kgجaw9tlOJmaaR|YYYQ`P|S@` ---e{Pp(u;uoZFKvKWW7 7XOGX@Bn_Lz;gw>w\vW#8iq9-_QXN>\Nqi_ 5l[tR/b2* -?ioASE*ɹkRǔw -4nxrX[4$X[[MMiV걵:KԘ~n 믿j,̱Y`ÙvWWqvs_{x7Ki.MS ƚ5֐DsVO^94o]ҺhkJƳ@iAXzbI$[i/PD+v1lvx@wrlm٪''gA&*TEEw?GG/l/J< wsڴJJ+ةRǦm">~Xt\W\"2n<{U_c 1@1xʃ4-1-aC[4c=*c3p0?рe2Sݢ1?ԩ{ƑЈ&'tx=+yߖֶ+yML\+E0qq b&<9]%I9[-؀bxr?˱jkk)..e˖ץ.]*hY?vmI!ݎXj}ؐS}1 +Ai?B9x,yY'Hij@i!XeVmb,lILj5lX~A)K,&y,p; OyI#Fr߸Gw 䟷O!ߢa"E47a^ljR2N %&kB bvpNB?Te` - 79j翯Ykhߙ9eLj+-uSJ-^b[إ=q33rH7nEDDPrroG3_1c1bXT;>^$$(כB!//BLA$vϖ-[4̽bbRRpmX=BE=(L3yʘ?ZHhB:̱lx9Ta-=+#[{RJ/'T(y콴VGX1牭ӖL{,8$LK=<5.¬-.&M'M[ b`, !o%{n[ ! tֳZut2?=8ŕW;ڣ Iq;|]*tc̬|&vmpiˆ%zdrolK ٳ:9qqR>3,}b$!!'-nӰשּׁ"SgQJR￲?fB gW` /k+@Q™)r0ޣwȳdYAqB玤jEј.Lesk /eO$n/úV9yTIIūtg|?M9G2@X@BL6"DHO)FQCmEi]'&l<%,$)6=jOZT&[ڰS7kRy8!Gn6bs#S捎m6I,ZBL݀~UUUtއ BLհ e-cLÿ:3fP}yrdBL=fC6<}U_xb8ډЛ0/98H\{ٍp@R4YZ*ߐA-#Ē/w@{К;]7{]i_) Q;t{@X@BLîEV1`řIp7~F+i5Pa&G\n81bڷcgg'.F!s@_va2GYǩ/^<Ν;ש_;F׮]79!!ƫU_yYUc]em)\K35:hɿ6nn$=&X1HanXToΫwzz9̒#5} f]f汧MjO@Z +!1ĞYN|qYVeVXAHcp`XҞlIĉ駟~C'Rg3+S^^c٘>,++IIINcm}qX%Iy[> &-:]~ ÿVsjy%P&4qsbi IbxTuz?(2PƜM)y#,ytkQߋ0_\)ׅݗG3Su-ZCɶvcS&wf,Uqi56Ť\Avp07$Wc, ! nI_VR%Fr$.1\\WصGD?I$[ C1kES6>ԔyζϟK_CPe233 n߸q8o߾T' {+r]W5W^\cru}Md ipALZKT#&)ʠ91'!f촡ݵr Kg\'k$S[}uGўbǬjw\dT<]-)pEMdN}=y"z 3ʞݩ& 1@i$4$wq&=C'aZ[{ H6=]-, 6$|||wdemmaؼՍԭ'$$BCC5#!_<[,7uon-!hlrt8?PeںzgEs ]X|.[O Xx_RZ!zԕ'p=6m\)jt:@;JpZ3jtt!th85Ϯ|VO1 )b:k?C`X/ xHcvX`,0@!X)b5A!70KЇ?c| &oAf޹:|R@K'{>{?9::a,0S Ę d'Ťb2z=~<`iC$&&Ν;t]*//'???r*u})sN 4(rss n/ғ'O\pppP=ǧN upc]cTp?X@-Eڤ: }'ѭT(gztc;P P`68} b77С|Ј>7<}utPrlE"S ;fҞմTyy>W)<6 >a8*}O>}n~R=󾁻'h\ ^nnnsEf͚Egrw7mzm:rt=zԯ.̫Wի4eJIIІ %"bPzl1b;wNɴ_g?ӗ_~IVVV.(({i[LLy^cɳBEfOSTrW7[ ʝ0GRJ/N.+sh8"i$Z?{d 1KLm*uh,EHzm{GĒիh\?}8B{/z]?ZP彭Tr}B͇:)U{tL { c!1Nr_[oieC]Z('=30ZXj6v:vuV/o0u^j*؛*CoiɡCӧdoo?n޼)BHm{Uđsta!o{' ѣf-ĬL_."'{]c.VZnbfB]gmSw_1t!&4:xmHbO~&H4Mw}Gv󤏿+ŒK̡4(~T,?8ͿRzo;;Zn`M[Z*+Cޱ{2\SXڸQhXz:B bfadţ&}EXD=1kS:̬|F){K .sbAu%*s\=3GʠŖ]iI0an 1t2!Z܇LJF2؝!C,eZ-sVOs_܋`N,znM6 }pH,;9;,{xj&]8XiBu99; F B bfKӯBLҢf 17 )׉_ Y4%pڦqqqr,p/ 1|܆9shΑs( elԣ-dffJ82g!Bđӛ?Yݨ ?OYZ5q*gI 4 )Z#ΰ͆C hbqY &=&~CΞGjn4Wp ET}]}Db0bӺ7㷚FMbÏD[X!/ `ᄳh4ԆD"QEDZcڵkf`bKNф'OJP]^fe׬כԇգ-hcB 'G~xr f%EU]ͤ;ԛ-ennB (21̓༼b gObSM:/~KRecf.Kms͕49#Yk'x`w<]Ķ$ |c! 1ֶTpghWƄs_;"NPÿAK(&NH? :T߿?:i޽[ړ?˜;))I\kpkh,|9. gh(zcB LsҜ(ѣ@섆ԇ^ihLIWݵ$UZm ;_!5*|d ;+*[.E[ĨxAv_\ɽ%/*ȷ畿q*xXB}"WL-vUSYsz>s=dؔ?5K 6B\ZFM1iv[?L i0bc70g0Ztdek,!N2.qJjdƮ;J7!%a47n ۷oE(--Չ-p{8>Im^ҴWKꫯ8 /uW2qqq1x^>)vZCX,0X_C}X=Ua猒UdGx jJN!&cD޶'˶p]$gVq^dBLi2 L8ePݗ1!Fbh,M.:y9% 4*omuY<8O23e {?9::a,0S Ę d'Ť͠[{f=ijn8cIϾi[wRνg?9$FMǎ{|||rss^VV&Ǻ{.ܹ۴kN;}IZl޶iӦ'|B?sQRRRO/^'OH58Sd̴6lfy؋ͤth2EvoxyQE}ֻtwn[@S({]czх?1Z>u>hDm:(m9H"O@J=HNvf0bot'}B>~E7?}J)i阴c, !Ƽona38,Ӝ6s3_Ƭ9G@8-g?2ܠK?s1i֭M"|MG\\\D6Rwz^JSL:pmذ6ksD ^}/… )11m&<ؤ۷o#FAŋRڙNh0`ʕ+[6m8mCGtaL}&#.v"rXk+8ϡI}hBB$/DglS!fqI6^SR^MߠTb-ܒEqâhImhעP彭Tr}B;͇:)U{tL aҎql9KS/뭷]-I]-,5yzfb: K+]}7tjr{9{WOF\a8z0s7ֶ͖uSY.1(Č^}HeF>O\e%akk+bâELڏ͛7*Đq!z) am?ƴYMzzTUUUzB ?sκϟeJ{R/rOs5(DGG~;ڴiSo.SL/kK~?ΣbB]Bֺט:iAqt If$h=̡4^XZp~g;,wLWw+ o;;{  ֔ax;x{Pp(u;uoZFKvKWW7 7XOGc, !Lo`FV?u֙vGbkkkkڱcǏ 1ܖJě!T!b\V|=!}[g%sB vB K'+kK_)_,eKB̐}5Yl139l珓3 RqЍ#gTRT]ӆ;4uߴJJ+ةRǦmNR9|cD\e.x@' 1@1xʃ4æ翦+r&;Cą'$aI1XӨ_MGm^fЌb)T[/DtBL׮7|l8lhJ}E˖1ӗOnjC2g OO&gH` >cǚ=ؐw^*..eCBL߾}͛7`2dm޼~ߒ_~g#&&F\X;w1,ְ eט%N#bb,htuhr} oqY;8B bZz$SH?ռ k+Ԭnzc%wvQ=0l)1e,5axtH1J 8X_'-} ) d+ P;OzT6< I 1@i-58H}3b~kr ʤsԹs|Vgf54&}ؐcek/6}'-cdݩF/.-Y\_3O| Ah\4Baa܄ˢcB ha]k\f͚Eɲ1uo.2B mbۻ]|:ډЛ wVSڰŞ2$!&2EWZ9L:B LKc~C#eǝo ݉b#c`4vb L< }pH,;9;,{xj&]-zߵ%TqҤ)tsrvvA Ĵ _ňEeb2+n(SbR{9džaѧ'w."R12j`dʹۇ 1s|2ØҜ_hZd3g%\2Li dyv!2zP/c 1DI U^ctx?X@/O-@'bbE%/;9Hǽ M;JNRIGc2(0EuϮ)̖<᷷w!“]{ufQ'&M޳ҍ;4}!c!1~s6bD|:cc̱Hl" -/bjX:oц-IL9wcA$ ?4l6E]N4;Y7ICeKcKKEm񡽞cј3tPraC_M_~,slU3q$}O&״fƼĉ n3fcҹ--*p bfM ʱh 9cP"b-}XQiAnz8hhsZ.2-#e)w@{К; 9{]i_) Q;t{@X@BLW:QS%;)# >"4c6MhvYxW1F1br*'1 -3%lo2%"!!cDDD4͇5G{b8K~~?-TxJ8Dus]ɓY_a9H 1 evucHX2ݚ$0Qv)p0ON2(ʘ< +?9J#2syIeJ=٧d(,\f+wirFV&GrX#8Jÿ,cFNJLx0Kjw"2{Omҹf| gRDzݞa8thFRn2` ~7o믔WvҞl5RðXV5NKN9. fvi+!Ƙ6ץlqS[l1- $ohhhB A/5zWRJs޼bʝ0P%b2}t?RL|`rdPwX\Խ$cƊ !f0*=ePݗ1!f`ro$'G}|Ɯ)ȳzŏ2ʚ˴㇔9$m4ƦL̬Y2jlIuäސ^fҎ39w b83g bERFMcc̱"GgQl-ّXG/vy;Sf&1murդֆg齗7`,(cN/K._},vaqФ/Lff6YCKOO|L+VƄiy_uALCekZ_~tLVu nGF2ѯ2? GO^ԤLdž549z<]DLg=SM1b70 l1$wC˴G1iv Okb+.Ѱ-e-۶p'dmO/cN/ gggqa! ԭ'$$D,=Կt( dXjjxr뇈xucV/n-pYP'yJm?,Mn΂q~ Gie\<4Um.Ĉc#[{f1󙿛0'%M@`P}|[[N;iX@BLl5foonmK>|tTæ ѷCsk>}hK;~>' 15 \c!0X`,~B n`}?<$qCn 1^>D 1B ?CS n/l]pC?{?9::a,0S Ę d'Ť͠N]{5xߴH;K*rF5iOV3gSfۈ^t1g.-ML٧br5t )oVi3S>y+SJIKX`, 󾁻'h3lV=l;w֗ą モI۷oiΝ4b:wx񂜜L>~g/J/_!sm{.իٳ/аa5vi"oXn/JiLAϙOon$g[[>k -dmi)B<+;vQ_Pto7RꤌViX`, ql9KS/f)b]N7kW 1=V6R/oKvA8Ʒs"a!7TƩy;_v Hmv6޿n\: +ki/cQɘs7DzzXbTUU5Ia˖Ǐ?\]]Ed;wFo{ɺBc\W[[K~_cY5MbNΠ+֐yjo/"K~0nN̝O#VB+G3r :D0<[ߊq$0(D]) 0XS햖Pw,޸6;6nnB bZv 7m?ЊC>Fٝ EbL=ƶgf3ZoJm6XLxr:\}P~: DOԭ(JW_S\b{mPp{翯Sǘ5)cu.7a)rNV=,ΘKݝ^|IaaaMbؕfiǎ?#deeѾ}dݽ{(55B}Xse:qY_cu({& 1T6I]vORX_W@f13:]=]&TE''gA&N2Q}3tK*)KgioN%tGT{scӶre`X,:h+.WK7=窯c,0bv7i DW ֶ+y53뚮uoX1cڣmthcyAdR4bQϾ5ezϡeL~1C/1E0$İ3u|ۄ`܄IzucC'KA򷵵 %&X`, !mn``;.-V-A!&>gIBՇDhiH9\_[[gl%1tP;jm--xK+5pc~֬Y#5O*,[n4uva!߇ňQ [fׇ!aFT3gJf#^1۰6'5_^/.̚# 1" /C@1}CkQх&M.0¹`qU_CWjc!1{W Mܐ\qw$M۝lR=ƶ1!%5f"*5TOtt*u*İUU&}P5)yN>] K\.cĮIBe/ ___ׁ/;ՙ$sوu!&''l?GI:p#beui5fiIQ= U3G3OgO[NbŚ+wirFV&Z2I"İ0uq ,>]w2*n 1]97[t!Lî0]V~ADwb>T 1駟ąS^s< 읜! x1}Ә"m"diDwYT<]-)pEMdN}=y"z 3ʞX@BLb]x#UL]}#0A|R_1=F_ r !dae]oՌʥ(`{nT^!}ֱ(]y|+^_ˉ#n,>xv>䠼szjv_r5DфoOm+ cIid  jVPW>c[5c lJQcm[jHQ Zic,LSNXbTC1xI%>D@x' 15?pdNuѰLťhIJD 1B ?CS xI'3 !wd-Ь;WOJ~dgww'GG'c 󿁓줘>>TYYIzۦLBNa߾}M:7уtA.s}{.ܹK411Owܑs*//'??,\Pg?O^ߔ~l>vϩĔ| hq7imڞ"i ?|Sqt@rp3o۷uIr:>O>t|a, !Ƽona38Zob'Eɸн{=n_j|RWSwo nnnsf͚EgrwwהaQիWtURRRaÆv}69rDl/^Hd=ӧOKuAAAK?ĴIc5W{E'aj&y8:Б;E\3uTy[.ۇKJgQNQZ 1KLm*u7,6.w]#ϹStӭo6ߙo>Is,gjc 1@i7pʖ4:ðoJoN,evIVE]ZyF+[XZvz\quG֯7oJ}B oon[7}=t=}u<zz[hsNciFac 1@17VV3m&İB3^ՠcem'ž4kҲP=, ]|5k B vB KLYb/[bO3:ckV}899' 2!U**ݸzI%e{LU9mؼCSM۩j}.ulV. Eu̥s;B b M?&â [Ä'˲$FE*0j!\"{l\x%ѐ jkk)..e˖ץ.]*o}t;vl (QQQ-ZZaW%>}Г'O$6L[&iHa/΋+Wl|[vH9픱` !1l~r)$ڟj^W`# -YOxl$.J`.xl0s?O#i(Y7bbX32W&Lҫ0C:s ݺuV^mB :-%;E9^f1f6{>\K35牺7$ɿ6nn$=&X1H%._e8KM'})B = k&Xo?5j5سK髏?.AdWXA(zN1ͱ]8D{niOvu3qD駟hP5BI;3%qYv{J,{!LRw'%%iӦac&pj׵d90k8 #^Ts *NQzۆ^>{]id/+(}h ɢ!sGioeE[ zߓrߓoPw˔z]ի(~dRD髍bX9\z| :KES6>ԔyζϟK.I m߸q8o߾T/V ꫯd;uݕKիWߔ>lsbeRWkIgۑ;%7M2>{D  OlԸ,%'/^n$eP=1!Fbh,Mф?/䴁M:/ca#$[ư1Շ*OWDndc', =yQ2SEc'#GwHvgwI;B bf;-bI;j$ G`9yK❋ݛ ̵-l Yy蚷QxxxqR mVP5;5&^bߐMw?rrq dko@?[o`7krtM=wAPIsb[ 150X=|oHm+ cIid  jVPW>ñ:B bur1hFE[΢3jtt!thݢsͳ >Փc Ԛ@KЇ?c,@i-wk[qiE?< c1BY1r/_@?9S:73ŏAhB @/~x`, @xЇc !L >D? `p|O1t!71>D? `ܺuKƳ@{b6oެy0*ŏA0_xΦOd 1کϘ={ ^ >D? `.Ij/G!aE`oڱ6qcC!c{% "@b @!B !B 1bbVC!Lj 1b8{!fbzbvL_UBL *R0]AȺBt|ూ )أ`i +PblB/:d^*x3 v)X S0*!I[?o >W@5)(T`0ݴk FOSpYA,T0Q ! TڋZqP:Q (F 6)S0^A@b)pS̠3ܒ8> IU0KyJsSi0],('tQSZ_t2>.@1IEXun*ߥT=2SVETnIvyF;P1*%{ U3йz".0wU0'TBATڊJkWi/j!J=s[R`/:@ܑEvIPQ.lI*m[-J-tUhgO P)7#(s_@'#q`*w$s]A])96LJKQi+ْ,UӲ S0P89*똟1M7])YTHT" Vq*-%LNFCV1 JA4sRP:Y@;*.>UY*JKdPѶ+tSְ2U*ZUW5n=]1e `Ui{TFJؤ<4l&2BD4nDDg SpN*T0KA])T"Z2TZrPuRi*-$^/vbS~Md#z^RBT0^ 8MtZ`LWi*mcJ>^*m[8&0u6%` BDjTLT LIRiU<"UJPi$]eGj-hgSR[ǨwT=Ŧ9 "T 0s"UZFJQi=UG7`V0ّ,c,[n*5K0o=T)jBgtSi*Eث4kV0adlT P[88̨_u G-[O`dԢ0glDa]GxC$Ph%tEXtdate:create2020-10-10T08:43:41+00:00͍U%tEXtdate:modify2020-10-10T08:43:41+00:000IENDB`duf-0.8.1/filesystems.go000066400000000000000000000024141420040666200151750ustar00rootroot00000000000000package 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" ] duf-0.8.1/filesystems_darwin.go000066400000000000000000000004531420040666200165420ustar00rootroot00000000000000//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 } duf-0.8.1/filesystems_freebsd.go000066400000000000000000000010051420040666200166620ustar00rootroot00000000000000//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 } duf-0.8.1/filesystems_linux.go000066400000000000000000000310571420040666200164210ustar00rootroot00000000000000//go:build linux // +build linux package main import "strings" //nolint:revive,deadcode 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 } duf-0.8.1/filesystems_openbsd.go000066400000000000000000000004551420040666200167120ustar00rootroot00000000000000//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 } duf-0.8.1/filesystems_windows.go000066400000000000000000000020631420040666200167470ustar00rootroot00000000000000//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 } duf-0.8.1/go.mod000066400000000000000000000010431420040666200134020ustar00rootroot00000000000000module github.com/muesli/duf go 1.15 require ( github.com/IGLOU-EU/go-wildcard v1.0.3 github.com/jedib0t/go-pretty/v6 v6.2.5 github.com/kr/pretty v0.2.0 // indirect github.com/mattn/go-runewidth v0.0.13 github.com/muesli/mango v0.1.0 github.com/muesli/roff v0.1.0 github.com/muesli/termenv v0.11.0 golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 golang.org/x/term v0.0.0-20210406210042-72f3dc4e9b72 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) duf-0.8.1/go.sum000066400000000000000000000100131420040666200134240ustar00rootroot00000000000000github.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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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.2.5 h1:4faq6Fne+0du3qZAPOJcBFpAnt4AlxUJAKa1vAdvfrQ= github.com/jedib0t/go-pretty/v6 v6.2.5/go.mod h1:FMkOpgGD3EZ91cW8g/96RfxoV7bdeJyzXPYgz1L1ln0= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/muesli/mango v0.1.0 h1:DZQK45d2gGbql1arsYA4vfg4d7I9Hfx5rX/GCmzsAvI= github.com/muesli/mango v0.1.0/go.mod h1:5XFpbC8jY5UUv89YQciiXNlbi+iJgt29VDC5xbzrLL4= 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.11.0 h1:fwNUbu2mfWlgicwG7qYzs06aOI8Z/zKPAv8J4uKbT+o= github.com/muesli/termenv v0.11.0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= 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.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20210406210042-72f3dc4e9b72 h1:VqE9gduFZ4dbR7XoL77lHFp0/DyDUBKSXK7CMFkVcV0= golang.org/x/term v0.0.0-20210406210042-72f3dc4e9b72/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= duf-0.8.1/groups.go000066400000000000000000000062061420040666200141500ustar00rootroot00000000000000package 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) } } } duf-0.8.1/main.go000066400000000000000000000173531420040666200135620ustar00rootroot00000000000000package main import ( "encoding/json" "flag" "fmt" "os" "strconv" "strings" wildcard "github.com/IGLOU-EU/go-wildcard" "github.com/jedib0t/go-pretty/v6/table" "github.com/muesli/termenv" "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") 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 main() { flag.Parse() if *version { if len(CommitSHA) > 7 { CommitSHA = CommitSHA[:7] } if Version == "" { Version = "(built from source)" } fmt.Printf("duf %s", Version) if len(CommitSHA) > 0 { fmt.Printf(" (%s)", CommitSHA) } fmt.Println() 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 { err := renderJSON(m) if 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 for _, v := range flag.Args() { fm, err := findMounts(m, v) if err != nil { fmt.Println(err) os.Exit(1) } mounts = append(mounts, fm...) } 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 _, thresold := range availbilityThresholds { _, err = stringToSize(thresold) 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 _, thresold := range usageThresholds { _, err = strconv.ParseFloat(thresold, 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, }) } duf-0.8.1/man.go000066400000000000000000000051221420040666200134000ustar00rootroot00000000000000//go:build mango // +build mango package main import ( "flag" "fmt" "os" "github.com/muesli/mango" "github.com/muesli/mango/mflag" "github.com/muesli/roff" ) 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 "). WithSection("Copyright", "Copyright (C) 2020-2022 Christian Muehlhaeuser \n"+ "Released under MIT license.") flag.VisitAll(mflag.FlagVisitor(manPage)) fmt.Println(manPage.Build(roff.NewDocument())) os.Exit(0) } duf-0.8.1/mounts.go000066400000000000000000000034341420040666200141560ustar00rootroot00000000000000package 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:deadcode,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:deadcode,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]) } duf-0.8.1/mounts_darwin.go000066400000000000000000000037731420040666200155300ustar00rootroot00000000000000//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 } duf-0.8.1/mounts_freebsd.go000066400000000000000000000045101420040666200156440ustar00rootroot00000000000000//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 } duf-0.8.1/mounts_linux.go000066400000000000000000000110411420040666200153660ustar00rootroot00000000000000//go:build linux // +build linux package main import ( "fmt" "os" "path/filepath" "regexp" "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 { return nil, nil, 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 } // 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 } var i int for _, f := range strings.Fields(line) { // when parsing the optional fields, loop until we find the separator 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 != "-" { if fields[i] == "" { fields[i] += f } else { fields[i] += " " + f } // keep reading until we reach the separator continue } // separator found, continue parsing i++ } switch i { case mountinfoMountPoint: fallthrough case mountinfoMountSource: fallthrough case mountinfoFsType: fields[i] = unescapeFstab(f) default: fields[i] = f } i++ } return i, fields } duf-0.8.1/mounts_linux_test.go000066400000000000000000000115771420040666200164430ustar00rootroot00000000000000//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"}, }, } 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) } } } duf-0.8.1/mounts_openbsd.go000066400000000000000000000036241420040666200156710ustar00rootroot00000000000000//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 := intToString(stat.F_mntfromname[:]) mountPoint := intToString(stat.F_mntonname[:]) fsType := intToString(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 } duf-0.8.1/mounts_windows.go000066400000000000000000000260731420040666200157340ustar00rootroot00000000000000//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 } duf-0.8.1/style.go000066400000000000000000000006121420040666200137640ustar00rootroot00000000000000package 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" } duf-0.8.1/table.go000066400000000000000000000226441420040666200137240ustar00rootroot00000000000000package main import ( "fmt" "os" "regexp" "strconv" "strings" "github.com/jedib0t/go-pretty/v6/table" "github.com/jedib0t/go-pretty/v6/text" "github.com/muesli/termenv" ) // TableOptions contains all options for the table. type TableOptions struct { Columns []int SortBy int Style table.Style } // Column defines a column. type Column struct { ID string Name string SortIndex int Width int } var ( // "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 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}, } ) // printTable prints an individual table of mounts. func printTable(title string, m []Mount, opts TableOptions) { tab := table.NewWriter() tab.SetAllowedRowLength(int(*width)) tab.SetOutputMirror(os.Stdout) tab.Style().Options.SeparateColumns = true tab.SetStyle(opts.Style) if barWidth() > 0 { columns[4].Width = barWidth() + 7 columns[8].Width = barWidth() + 7 } twidth := tableWidth(opts.Columns, tab.Style().Options.SeparateColumns) tab.SetColumnConfigs([]table.ColumnConfig{ {Number: 1, Hidden: !inColumns(opts.Columns, 1), WidthMax: int(float64(twidth) * 0.4)}, {Number: 2, Hidden: !inColumns(opts.Columns, 2), Transformer: sizeTransformer, Align: text.AlignRight, AlignHeader: text.AlignRight}, {Number: 3, Hidden: !inColumns(opts.Columns, 3), Transformer: sizeTransformer, Align: text.AlignRight, AlignHeader: text.AlignRight}, {Number: 4, Hidden: !inColumns(opts.Columns, 4), Transformer: spaceTransformer, Align: text.AlignRight, AlignHeader: text.AlignRight}, {Number: 5, Hidden: !inColumns(opts.Columns, 5), Transformer: barTransformer, AlignHeader: text.AlignCenter}, {Number: 6, Hidden: !inColumns(opts.Columns, 6), Align: text.AlignRight, AlignHeader: text.AlignRight}, {Number: 7, Hidden: !inColumns(opts.Columns, 7), Align: text.AlignRight, AlignHeader: text.AlignRight}, {Number: 8, Hidden: !inColumns(opts.Columns, 8), Align: text.AlignRight, AlignHeader: text.AlignRight}, {Number: 9, Hidden: !inColumns(opts.Columns, 9), Transformer: barTransformer, AlignHeader: text.AlignCenter}, {Number: 10, Hidden: !inColumns(opts.Columns, 10), WidthMax: int(float64(twidth) * 0.2)}, {Number: 11, Hidden: !inColumns(opts.Columns, 11), WidthMax: int(float64(twidth) * 0.4)}, {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 }) headers := table.Row{} for _, v := range columns { headers = append(headers, v.Name) } tab.AppendHeader(headers) for _, v := range m { // spew.Dump(v) 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 }) } if tab.Length() == 0 { return } 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) var 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() } // barTransformer transforms a percentage into a progress-bar. func barTransformer(val interface{}) string { usage := val.(float64) s := termenv.String() if usage > 0 { if barWidth() > 0 { bw := barWidth() - 2 s = termenv.String(fmt.Sprintf("[%s%s] %5.1f%%", strings.Repeat("#", int(usage*float64(bw))), strings.Repeat(".", bw-int(usage*float64(bw))), usage*100, )) } else { s = termenv.String(fmt.Sprintf("%5.1f%%", usage*100)) } } // apply color to progress-bar redUsage, _ := strconv.ParseFloat(strings.Split(*usageThreshold, ",")[1], 64) yellowUsage, _ := strconv.ParseFloat(strings.Split(*usageThreshold, ",")[0], 64) switch { case usage >= redUsage: s = s.Foreground(theme.colorRed) case usage >= yellowUsage: 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 } // barWidth returns the width of progress-bars for the given render width. func barWidth() int { switch { case *width < 100: return 0 case *width < 120: return 12 default: return 22 } } // tableWidth returns the required minimum table width for the given columns. func tableWidth(cols []int, separators bool) int { var sw int if separators { sw = 1 } twidth := int(*width) for i := 0; i < len(columns); i++ { if inColumns(cols, i+1) { twidth -= 2 + sw + columns[i].Width } } return twidth } // 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 { case prefix == "K": size = num << 10 case prefix == "M": size = num << 20 case prefix == "G": size = num << 30 case prefix == "T": size = num << 40 case prefix == "P": size = num << 50 case prefix == "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 } duf-0.8.1/themes.go000066400000000000000000000027321420040666200141160ustar00rootroot00000000000000package 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 } 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"), } 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"), } 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"), } if _, ok := themes[theme]; !ok { return Theme{}, fmt.Errorf("Unknown theme: %s", theme) } return themes[theme], nil }