Repository: containerd/nydus-snapshotter
Branch: main
Commit: 389bddbab0da
Files: 271
Total size: 2.3 MB
Directory structure:
gitextract_bo46r8c0/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug-report.yml
│ │ ├── feature-request.yml
│ │ └── improvement-report.yml
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── codecov.yml
│ └── workflows/
│ ├── ci.yml
│ ├── e2e.yml
│ ├── k8s-e2e-run.yml
│ ├── k8s-e2e.yml
│ ├── optimizer.yml
│ └── release.yml
├── .gitignore
├── .golangci.yml
├── LICENSE
├── MAINTAINERS
├── Makefile
├── README.md
├── cmd/
│ ├── containerd-nydus-grpc/
│ │ ├── main.go
│ │ └── snapshotter.go
│ ├── converter/
│ │ └── main.go
│ ├── nydus-overlayfs/
│ │ ├── main.go
│ │ └── main_test.go
│ ├── optimizer-nri-plugin/
│ │ └── main.go
│ └── prefetchfiles-nri-plugin/
│ └── main.go
├── config/
│ ├── config.go
│ ├── config_test.go
│ ├── daemonconfig/
│ │ ├── daemonconfig.go
│ │ ├── daemonconfig_test.go
│ │ ├── fscache.go
│ │ ├── fuse.go
│ │ ├── mirror_select_test.go
│ │ ├── mirrors.go
│ │ └── mirrors_test.go
│ ├── default.go
│ └── global.go
├── docs/
│ ├── configure_nydus.md
│ ├── crictl_dry_run.md
│ ├── index_detection.md
│ ├── optimize_nydus_image.md
│ ├── registry_authentication.md
│ ├── run_nydus_in_kubernetes.md
│ ├── setup_snapshotter_by_daemonset.md
│ └── tarfs.md
├── export/
│ └── snapshotter/
│ └── snapshotter.go
├── go.mod
├── go.sum
├── integration/
│ ├── Dockerfile
│ └── entrypoint.sh
├── internal/
│ ├── constant/
│ │ └── values.go
│ ├── flags/
│ │ ├── flags.go
│ │ └── flags_test.go
│ └── logging/
│ ├── setup.go
│ └── setup_test.go
├── misc/
│ ├── example/
│ │ ├── 10-containerd-net.conflist
│ │ ├── README.md
│ │ ├── container.yaml
│ │ ├── containerd-config.toml
│ │ ├── containerd-test-config.toml
│ │ ├── crictl.yaml
│ │ ├── optimizer-nri-plugin.conf
│ │ └── pod.yaml
│ ├── nri-prefetch/
│ │ └── prefetchConfig.toml
│ ├── optimizer/
│ │ ├── containerd-config.toml
│ │ ├── crictl.yaml
│ │ ├── nginx.yaml
│ │ ├── sandbox.yaml
│ │ └── script/
│ │ ├── entrypoint.sh
│ │ └── file_list.txt
│ └── snapshotter/
│ ├── Dockerfile
│ ├── base/
│ │ ├── kustomization.yaml
│ │ └── nydus-snapshotter.yaml
│ ├── config-blockdev.toml
│ ├── config-proxy.toml
│ ├── config.toml
│ ├── nydus-snapshotter-rbac.yaml
│ ├── nydus-snapshotter.fscache.service
│ ├── nydus-snapshotter.fusedev.service
│ ├── nydus-snapshotter.service
│ ├── nydusd-config-localfs.json
│ ├── nydusd-config.fscache.json
│ ├── nydusd-config.fusedev.json
│ ├── overlays/
│ │ ├── k3s/
│ │ │ ├── kustomization.yaml
│ │ │ └── mount_k3s_conf.yaml
│ │ └── rke2/
│ │ ├── kustomization.yaml
│ │ └── mount_rke2_conf.yaml
│ └── snapshotter.sh
├── pkg/
│ ├── auth/
│ │ ├── cri.go
│ │ ├── cri_test.go
│ │ ├── docker.go
│ │ ├── docker_test.go
│ │ ├── keychain.go
│ │ ├── kubelet.go
│ │ ├── kubelet_test.go
│ │ ├── kubesecret.go
│ │ ├── kubesecret_test.go
│ │ ├── labels.go
│ │ ├── labels_test.go
│ │ ├── provider.go
│ │ ├── renewal.go
│ │ └── renewal_test.go
│ ├── backend/
│ │ ├── backend.go
│ │ ├── localfs.go
│ │ ├── oss.go
│ │ ├── s3.go
│ │ └── s3_test.go
│ ├── cache/
│ │ ├── manager.go
│ │ └── manager_test.go
│ ├── cgroup/
│ │ ├── cgroup.go
│ │ ├── manager.go
│ │ ├── v1/
│ │ │ └── v1.go
│ │ └── v2/
│ │ └── v2.go
│ ├── converter/
│ │ ├── constant.go
│ │ ├── convert_unix.go
│ │ ├── convert_windows.go
│ │ ├── cs_proxy_unix.go
│ │ ├── merge_unix_test.go
│ │ ├── reconvert_unix.go
│ │ ├── reconvert_unix_test.go
│ │ ├── tool/
│ │ │ ├── builder.go
│ │ │ ├── feature.go
│ │ │ └── feature_test.go
│ │ ├── types.go
│ │ └── utils.go
│ ├── daemon/
│ │ ├── client.go
│ │ ├── client_test.go
│ │ ├── command/
│ │ │ ├── command.go
│ │ │ └── command_builder_test.go
│ │ ├── config.go
│ │ ├── daemon.go
│ │ ├── daemon_test.go
│ │ ├── idgen.go
│ │ └── types/
│ │ └── types.go
│ ├── encryption/
│ │ └── encryption.go
│ ├── errdefs/
│ │ └── errors.go
│ ├── fanotify/
│ │ ├── conn/
│ │ │ └── conn.go
│ │ └── fanotify.go
│ ├── filesystem/
│ │ ├── config.go
│ │ ├── fs.go
│ │ ├── index_adaptor.go
│ │ ├── referer_adaptor.go
│ │ ├── stargz_adaptor.go
│ │ └── tarfs_adaptor.go
│ ├── index/
│ │ ├── detector.go
│ │ ├── detector_test.go
│ │ ├── manager.go
│ │ └── manager_test.go
│ ├── label/
│ │ └── label.go
│ ├── layout/
│ │ └── layout.go
│ ├── manager/
│ │ ├── daemon_adaptor.go
│ │ ├── daemon_cache.go
│ │ ├── daemon_cache_test.go
│ │ ├── daemon_event.go
│ │ ├── manager.go
│ │ ├── monitor.go
│ │ ├── monitor_test.go
│ │ └── store.go
│ ├── metrics/
│ │ ├── collector/
│ │ │ ├── cache.go
│ │ │ ├── collector.go
│ │ │ ├── daemon.go
│ │ │ ├── fs.go
│ │ │ └── snapshotter.go
│ │ ├── data/
│ │ │ ├── auth.go
│ │ │ ├── cache.go
│ │ │ ├── daemon.go
│ │ │ ├── fs.go
│ │ │ ├── labels.go
│ │ │ └── snapshotter.go
│ │ ├── listener.go
│ │ ├── registry/
│ │ │ └── registry.go
│ │ ├── serve.go
│ │ ├── tool/
│ │ │ ├── common.go
│ │ │ ├── stat.go
│ │ │ └── stat_test.go
│ │ └── types/
│ │ ├── ttl/
│ │ │ ├── gauge.go
│ │ │ └── gauge_test.go
│ │ └── types.go
│ ├── pprof/
│ │ └── listener.go
│ ├── prefetch/
│ │ └── prefetch.go
│ ├── rafs/
│ │ └── rafs.go
│ ├── referrer/
│ │ ├── manager.go
│ │ └── referrer.go
│ ├── remote/
│ │ ├── remote.go
│ │ ├── remote_test.go
│ │ ├── remotes/
│ │ │ ├── docker/
│ │ │ │ ├── auth/
│ │ │ │ │ ├── fetch.go
│ │ │ │ │ ├── fetch_test.go
│ │ │ │ │ ├── parse.go
│ │ │ │ │ └── parse_test.go
│ │ │ │ ├── authorizer.go
│ │ │ │ ├── config/
│ │ │ │ │ ├── config_unix.go
│ │ │ │ │ ├── config_windows.go
│ │ │ │ │ ├── docker_fuzzer_internal.go
│ │ │ │ │ ├── hosts.go
│ │ │ │ │ └── hosts_test.go
│ │ │ │ ├── converter.go
│ │ │ │ ├── converter_fuzz.go
│ │ │ │ ├── errcode.go
│ │ │ │ ├── errdesc.go
│ │ │ │ ├── fetcher.go
│ │ │ │ ├── fetcher_fuzz.go
│ │ │ │ ├── fetcher_test.go
│ │ │ │ ├── handler.go
│ │ │ │ ├── handler_test.go
│ │ │ │ ├── httpreadseeker.go
│ │ │ │ ├── pusher.go
│ │ │ │ ├── pusher_test.go
│ │ │ │ ├── referrers.go
│ │ │ │ ├── registry.go
│ │ │ │ ├── registry_test.go
│ │ │ │ ├── resolver.go
│ │ │ │ ├── resolver_test.go
│ │ │ │ ├── schema1/
│ │ │ │ │ └── converter.go
│ │ │ │ ├── scope.go
│ │ │ │ ├── scope_test.go
│ │ │ │ └── status.go
│ │ │ ├── errors/
│ │ │ │ └── errors.go
│ │ │ ├── handlers.go
│ │ │ ├── handlers_test.go
│ │ │ └── resolver.go
│ │ └── unpack.go
│ ├── resolve/
│ │ └── resolver.go
│ ├── signature/
│ │ └── signature.go
│ ├── snapshot/
│ │ └── storage.go
│ ├── stargz/
│ │ ├── resolver.go
│ │ ├── resolver_test.go
│ │ └── testdata/
│ │ ├── config/
│ │ │ └── nydus.json
│ │ └── stargz.index.json
│ ├── store/
│ │ ├── daemonstore.go
│ │ ├── database.go
│ │ ├── database_compat.go
│ │ └── database_test.go
│ ├── supervisor/
│ │ ├── supervisor.go
│ │ └── supervisor_test.go
│ ├── system/
│ │ ├── system.go
│ │ └── system_test.go
│ ├── tarfs/
│ │ └── tarfs.go
│ └── utils/
│ ├── display/
│ │ └── display.go
│ ├── erofs/
│ │ └── erofs.go
│ ├── file/
│ │ └── file.go
│ ├── mount/
│ │ └── mount.go
│ ├── parser/
│ │ ├── parser.go
│ │ └── parser_test.go
│ ├── registry/
│ │ ├── registry.go
│ │ └── registry_test.go
│ ├── retry/
│ │ └── retry.go
│ ├── signals/
│ │ ├── signal.go
│ │ └── signal_test.go
│ ├── signer/
│ │ └── signer.go
│ ├── sysinfo/
│ │ └── sysinfo.go
│ └── transport/
│ ├── pool.go
│ └── pool_test.go
├── snapshot/
│ ├── mount_option.go
│ ├── mount_option_test.go
│ ├── process.go
│ ├── renewal.go
│ ├── renewal_test.go
│ ├── snapshot.go
│ ├── snapshot_test.go
│ └── utils.go
├── tests/
│ ├── converter_test.go
│ ├── e2e/
│ │ └── k8s/
│ │ ├── kind.yaml
│ │ ├── snapshotter-cri.yaml
│ │ ├── snapshotter-kubeconf.yaml
│ │ └── test-pod.yaml.tpl
│ ├── helpers/
│ │ ├── helpers.sh
│ │ ├── kind.sh
│ │ └── lib.sh
│ └── nydusd.go
├── tools/
│ └── optimizer-server/
│ ├── Cargo.toml
│ ├── Makefile
│ └── src/
│ └── main.rs
└── version/
└── version.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/bug-report.yml
================================================
name: Bug report
title: "[Bug] Bug title"
description: Problems and issues with code of nydus-snapshotter
labels: [ "bug"]
body:
- type: markdown
attributes:
value: >
Thank you for taking the time to report an issue. To help us resolve it quickly, please fill out the following
information.
- type: textarea
attributes:
label: Problem Description
description: A clear and concise description of the bug you've encountered.
placeholder: >
Please clearly and concisely describe the error you encountered.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: What did you expect to happen?
placeholder: >
Explain the behavior you expected to see.
validations:
required: true
- type: textarea
attributes:
label: Actual Behavior
description: What actually happened? Please provide any relevant logs, stack traces, or screenshots.
placeholder: >
Describe the actual behavior. Include logs or screenshots if possible.
validations:
required: true
- type: textarea
attributes:
label: How to reproduce
description: Provide detailed steps to reproduce the issue.
placeholder: >
Please make sure you provide a reproducible step-by-step case of how to reproduce the problem
as minimally and precisely as possible.
value: |
1.
2.
3.
validations:
required: true
- type: textarea
id: environment-details
attributes:
label: Environment Details
description: |
Please provide your environment information.
value: |
- Nydus-snapshotter version:
- Nydus version:
- Container runtime:
- Operating System:
- Kernel version:
validations:
required: true
- type: textarea
attributes:
label: Additional Information
description: Any other context or details that could be helpful.
placeholder: >
If you have any other information that might help us diagnose the issue, please provide it here.
- type: checkboxes
attributes:
label: Are you willing to submit PR?
description: >
This is absolutely not required, but we are happy to guide you in the contribution process
especially if you already have a good understanding of how to implement the fix.
options:
- label: Yes I am willing to submit a PR!
- type: markdown
attributes:
value: "Thanks for completing our form!"
================================================
FILE: .github/ISSUE_TEMPLATE/feature-request.yml
================================================
name: Feature request
description: Suggest an idea for nydus-snapshotter
title: "[Feature] Feature title"
labels: [ "feature"]
body:
- type: markdown
attributes:
value: |
Thank you for suggesting a new feature. Please provide a clear and detailed description.
- type: textarea
attributes:
label: Feature Description
description: A clear and concise description of the feature you would like to see.
placeholder: >
Describe the feature you are requesting.
validations:
required: true
- type: textarea
attributes:
label: Problem and Use Case
description: What problem does this feature solve? Why is it important to you or other users?
placeholder: >
Describe the problem and a specific use case.
validations:
required: true
- type: textarea
attributes:
label: Related issues
description: Is there currently another issue associated with this?
- type: checkboxes
attributes:
label: Are you willing to submit PR?
description: >
This is absolutely not required, but we are happy to guide you in the contribution process
especially if you already have a good understanding of how to implement the fix.
options:
- label: Yes I am willing to submit a PR!
- type: markdown
attributes:
value: "Thanks for completing our form!"
================================================
FILE: .github/ISSUE_TEMPLATE/improvement-report.yml
================================================
name: Improvement Report
description: Suggest an improvement to an existing feature or process.
title: "[Improvement] Improvement title"
labels: ["improvement"]
assignees: []
body:
- type: markdown
attributes:
value: |
Thank you for helping us improve nydus-snapshotter! Please provide a clear and detailed description of your suggestion.
- type: textarea
id: problem-summary
attributes:
label: Problem Summary
description: A brief, high-level summary of the issue or inefficiency you've identified.
placeholder: Describe the problem you want to solve.
validations:
required: true
- type: textarea
id: proposed-improvement
attributes:
label: Proposed Improvement
description: A detailed description of your proposed change or improvement.
placeholder: Explain your proposed solution.
validations:
required: true
- type: textarea
id: additional-context
attributes:
label: Additional Context
description: Add any other context or references that would be helpful (e.g., links to discussions, related issues).
placeholder: Add any additional context here.
validations:
required: false
- type: checkboxes
attributes:
label: Are you willing to submit PR?
description: >
This is absolutely not required, but we are happy to guide you in the contribution process
especially if you already have a good understanding of how to implement the fix.
options:
- label: Yes I am willing to submit a PR!
- type: markdown
attributes:
value: "Thanks for completing our form!"
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
## Overview
_Please briefly describe the changes your pull request makes._
## Related Issues
_Please link to the relevant issue. For example: `Fix #123` or `Related #456`._
## Change Details
_Please describe your changes in detail:_
## Test Results
_If you have any relevant screenshots or videos that can help illustrate your changes, please add them here._
## Change Type
_Please select the type of change your pull request relates to:_
- [ ] Bug Fix
- [ ] Feature Addition
- [ ] Documentation Update
- [ ] Code Refactoring
- [ ] Performance Improvement
- [ ] Other (please describe)
## Self-Checklist
_Before submitting a pull request, please ensure you have completed the following:_
- [ ] I have run a code style check and addressed any warnings/errors.
- [ ] I have added appropriate comments to my code (if applicable).
- [ ] I have updated the documentation (if applicable).
- [ ] I have written appropriate unit tests.
================================================
FILE: .github/codecov.yml
================================================
coverage:
status:
patch: off
project:
default:
enabled: yes
target: auto # auto compares coverage to the previous base commit
# adjust accordingly based on how flaky your tests are
# this allows a 0.3% drop from the previous base commit coverage
threshold: 0.3%
comment:
layout: "reach, diff, flags, files"
behavior: default
require_changes: true # if true: only post the comment if coverage changes
codecov:
require_ci_to_pass: false
notify:
wait_for_ci: true
# When modifying this file, please validate using
# curl -X POST --data-binary @codecov.yml https://codecov.io/validate
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
push:
branches: ["**", "stable/**"]
pull_request:
branches: ["**", "stable/**"]
env:
CARGO_TERM_COLOR: always
jobs:
security:
name: Security scan
timeout-minutes: 10
runs-on: ubuntu-22.04
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Setup Golang
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache-dependency-path: "go.sum"
- name: govulncheck
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
"$(go env GOPATH)/bin/govulncheck" ./...
- name: OSV-Scanner
run: |
go install github.com/google/osv-scanner/cmd/osv-scanner@v1
"$(go env GOPATH)/bin/osv-scanner" -r .
build:
name: Build and Lint
timeout-minutes: 10
runs-on: ubuntu-22.04
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Setup Golang
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache-dependency-path: "go.sum"
- name: golangci-lint
uses: golangci/golangci-lint-action@v9
with:
# Prebuilt golangci-lint v2.1.x is built with Go 1.24; go.mod requires Go 1.25+.
install-mode: goinstall
version: v2.1.6
skip-cache: true
- name: Build
run: |
make
make test
build-optimizer:
name: Build optimizer
timeout-minutes: 10
runs-on: ubuntu-22.04
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Setup Golang
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache-dependency-path: "go.sum"
- name: Build
run: |
rustup component add rustfmt clippy
make build-optimizer
smoke:
name: Smoke
timeout-minutes: 10
runs-on: ubuntu-22.04
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Setup Golang
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache-dependency-path: "go.sum"
- name: Set up containerd
uses: crazy-max/ghaction-setup-containerd@v3
- name: Build
run: |
# Download nydus components
NYDUS_VER=v$(curl -fsSL --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' "https://api.github.com/repos/dragonflyoss/nydus/releases/latest" | jq -r .tag_name | sed 's/^v//')
wget -q https://github.com/dragonflyoss/nydus/releases/download/$NYDUS_VER/nydus-static-$NYDUS_VER-linux-amd64.tgz
tar xzvf nydus-static-$NYDUS_VER-linux-amd64.tgz
mkdir -p /usr/bin
sudo mv nydus-static/nydus-image nydus-static/nydusd nydus-static/nydusify /usr/bin/
export PATH=$PATH:$(go env GOPATH)/bin
make smoke
cross-build-test:
name: Cross Build Test
timeout-minutes: 10
runs-on: ubuntu-22.04
strategy:
matrix:
GOOS: ["linux", "windows", "darwin"]
GOARCH: ["amd64", "arm64"]
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Setup Golang
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache-dependency-path: "go.sum"
- name: Build
run: |
make -e GOOS=${{ matrix.GOOS }} GOARCH=${{ matrix.GOARCH }} converter
coverage:
name: Code coverage
timeout-minutes: 10
runs-on: ubuntu-22.04
needs: [build]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Golang
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache-dependency-path: "go.sum"
- name: Run unit tests.
run: make cover
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
files: ./coverage.txt
================================================
FILE: .github/workflows/e2e.yml
================================================
name: integration test
on:
push:
branches:
- "main"
tags:
- v[0-9]+.[0-9]+.[0-9]+
pull_request:
branches:
- "main"
schedule:
# Trigger test every day at 00:03 clock UTC
- cron: "3 0 * * *"
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
REGISTRY: ghcr.io
jobs:
run-e2e-for-cgroups-v1:
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Golang
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache-dependency-path: "go.sum"
- name: Log in to container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Run e2e test
run: |
TAG=$GITHUB_REF_NAME
[ "$TAG" == "main" ] && TAG="latest"
[ "$GITHUB_EVENT_NAME" == "pull_request" ] && TAG="local"
make integration
run-e2e-for-cgroups-v2:
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Golang
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache-dependency-path: "go.sum"
- name: Log in to container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Run e2e test
run: |
TAG=$GITHUB_REF_NAME
[ "$TAG" == "main" ] && TAG="latest"
[ "$GITHUB_EVENT_NAME" == "pull_request" ] && TAG="local"
make integration
================================================
FILE: .github/workflows/k8s-e2e-run.yml
================================================
name: E2E Test With Kubernetes
on:
push:
branches:
- "main"
tags:
- v[0-9]+.[0-9]+.[0-9]+
pull_request:
branches: [main]
jobs:
cri_auth:
uses: ./.github/workflows/k8s-e2e.yml
with:
auth-type: cri
kubeconf_auth:
uses: ./.github/workflows/k8s-e2e.yml
with:
auth-type: kubeconf
index_detect:
uses: ./.github/workflows/k8s-e2e.yml
with:
auth-type: kubeconf
index-detect: true
================================================
FILE: .github/workflows/k8s-e2e.yml
================================================
name: E2E Test With Kubernetes Template
on:
workflow_call:
inputs:
auth-type:
required: true
type: string
index-detect:
required: false
type: boolean
env:
DOCKER_USER: testuser
DOCKER_PASSWORD: testpassword
NAMESPACE: nydus-system
jobs:
e2e_tests_k8s:
runs-on: ubuntu-22.04
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup Golang
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache-dependency-path: "go.sum"
- name: Test
env:
AUTH_TYPE: ${{ inputs.auth-type }}
INDEX_DETECT: ${{ inputs.index-detect }}
run: |
./tests/helpers/kind.sh
- name: Dump logs
if: failure()
continue-on-error: true
run: |
log_dir="/tmp/nydus-log"
mkdir -p $log_dir
for p in `kubectl --namespace "$NAMESPACE" get pods --no-headers -o custom-columns=NAME:metadata.name`; do
kubectl --namespace "$NAMESPACE" get pod $p -o yaml >> $log_dir/nydus-pods.conf
kubectl --namespace "$NAMESPACE" describe pod $p >> $log_dir/nydus-pods.conf
kubectl --namespace "$NAMESPACE" logs $p -c nydus-snapshotter >> $log_dir/nydus-snapshotter.log || echo "failed to get snapshotter log"
done
kubectl --namespace "$NAMESPACE" get secrets -o yaml >> $log_dir/nydus-secrets.log
docker exec kind-control-plane cat /etc/containerd/config.toml >> $log_dir/containerd-config.toml
docker exec kind-control-plane containerd config dump >> $log_dir/containerd-config-dump.toml
docker exec kind-control-plane journalctl --no-pager -u containerd >> $log_dir/containerd.log
docker exec kind-control-plane journalctl --no-pager -u kubelet >> $log_dir/kubelet.log
docker exec kind-control-plane ps -ef >> $log_dir/psef.log
kubectl get pod test-pod -o yaml >> $log_dir/test-pod.log || echo "test-pod may be deleted or not created"
cat ~/.docker/config.json > $log_dir/docker.config.json || echo "~/.docker/config.json not found"
- name: Upload Logs
uses: actions/upload-artifact@v4
if: failure()
with:
name: k8s-e2e-tests-logs
path: |
/tmp/nydus-log
overwrite: true
================================================
FILE: .github/workflows/optimizer.yml
================================================
name: optimizer test
on:
push:
branches:
- "main"
tags:
- v[0-9]+.[0-9]+.[0-9]+
pull_request:
branches:
- "main"
schedule:
# Trigger test every day at 00:03 clock UTC
- cron: "3 0 * * *"
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
jobs:
run_optimizer:
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Golang
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache-dependency-path: "go.sum"
- name: cache cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
tools/optimizer-server/target/
key: ${{ runner.os }}-cargo-${{ hashFiles('tools/optimizer-server/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo
- name: containerd runc and crictl
run: |
sudo wget -q https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.26.0/crictl-v1.26.0-linux-amd64.tar.gz
sudo tar zxvf ./crictl-v1.26.0-linux-amd64.tar.gz -C /usr/local/bin
sudo install -D -m 755 misc/optimizer/crictl.yaml /etc/crictl.yaml
sudo wget -q https://github.com/containerd/containerd/releases/download/v1.7.0/containerd-static-1.7.0-linux-amd64.tar.gz
sudo systemctl stop containerd
sudo tar -zxf ./containerd-static-1.7.0-linux-amd64.tar.gz -C /usr/
sudo install -D -m 755 misc/optimizer/containerd-config.toml /etc/containerd/config.toml
sudo systemctl restart containerd
sudo wget -q https://github.com/opencontainers/runc/releases/download/v1.1.5/runc.amd64 -O /usr/bin/runc
sudo chmod +x /usr/bin/runc
- name: Setup CNI
run: |
wget -q https://github.com/containernetworking/plugins/releases/download/v1.2.0/cni-plugins-linux-amd64-v1.2.0.tgz
sudo mkdir -p /opt/cni/bin
sudo tar xzf cni-plugins-linux-amd64-v1.2.0.tgz -C /opt/cni/bin/
sudo install -D -m 755 misc/example/10-containerd-net.conflist /etc/cni/net.d/10-containerd-net.conflist
- name: Build and install optimizer
run: |
rustup component add rustfmt clippy
make optimizer
sudo chown -R $(id -un):$(id -gn) . ~/.cargo/
pwd
ls -lh bin/*optimizer*
sudo make install-optimizer
sudo install -D -m 755 misc/example/optimizer-nri-plugin.conf /etc/nri/conf.d/02-optimizer-nri-plugin.conf
sudo systemctl restart containerd
systemctl status containerd --no-pager -l
- name: Wait containerd ready
run: |
unset READY
for i in $(seq 30); do
if eval "timeout 180 ls /run/containerd/containerd.sock"; then
READY=true
break
fi
echo "Fail(${i}). Retrying..."
sleep 1
done
if [ "$READY" != "true" ];then
echo "containerd is not ready"
exit 1
fi
- name: Generate accessed files list
run: |
sed -i "s|host_path: script|host_path: $(pwd)/misc/optimizer/script|g" misc/optimizer/nginx.yaml
sudo crictl run misc/optimizer/nginx.yaml misc/optimizer/sandbox.yaml
sleep 20
sudo crictl rmp -f --all
tree /opt/nri/optimizer/results/
count=$(cat /opt/nri/optimizer/results/library/nginx:1.23.3 | wc -l)
expected=$(cat misc/optimizer/script/file_list.txt | wc -l)
echo "count: $count expected minimum value: $expected"
if [ $count -lt $expected ]; then
echo "failed to generate accessed files list for nginx:1.23.3"
cat misc/optimizer/script/file_list.txt
exit 1
fi
cat /opt/nri/optimizer/results/library/nginx:1.23.3.csv
- name: Dump logs
if: failure()
continue-on-error: true
run: |
systemctl status containerd --no-pager -l
journalctl -xeu containerd --no-pager
================================================
FILE: .github/workflows/release.yml
================================================
name: release
on:
push:
tags:
- v[0-9]+.[0-9]+.[0-9]+*
env:
CARGO_TERM_COLOR: always
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-22.04
strategy:
matrix:
include:
- build-os: linux
build-arch: amd64
build-linker: x86-64
- build-os: linux
build-arch: arm64
build-linker: aarch64
- build-os: linux
build-arch: s390x
build-linker: s390x
- build-os: linux
build-arch: ppc64le
build-linker: powerpc64le
- build-os: linux
build-arch: riscv64
build-linker: riscv64
- build-os: linux
build-arch: static
build-linker: static
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache-dependency-path: "go.sum"
- name: cache go mod
uses: actions/cache@v4
with:
path: ~/go/pkg/mod
key: ${{ matrix.build-os }}-go-${{ hashFiles('go.sum') }}
restore-keys: |
${{ matrix.build-os }}-go
- name: cache cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
tools/optimizer-server/target/
key: ${{ matrix.build-os }}-cargo-${{ hashFiles('tools/optimizer-server/Cargo.lock') }}
restore-keys: |
${{ matrix.build-os }}-cargo
- name: install gnu gcc linker
run: |
if [ "${{ matrix.build-linker }}" != "static" ]; then
sudo apt-get update && sudo apt-get install -qq gcc-${{ matrix.build-linker }}-linux-gnu
fi
- name: build nydus-snapshotter and optimizer
run: |
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.51.2
export PATH=$PATH:$(go env GOPATH)/bin
if [ "${{ matrix.build-arch }}" == "static" ]; then
make static-package
else
make package GOOS=${{ matrix.build-os }} GOARCH=${{ matrix.build-arch }}
fi
- name: upload artifacts
uses: actions/upload-artifact@v4
with:
name: release-tars-${{ matrix.build-os }}-${{ matrix.build-arch }}
path: |
package/*.tar.gz*
overwrite: true
upload:
runs-on: ubuntu-22.04
needs: [build]
steps:
- uses: actions/checkout@v4
- name: Download Artifacts
uses: actions/download-artifact@v4
with:
path: builds
- name: Release
uses: softprops/action-gh-release@v1
with:
name: "Nydus Snapshotter ${{ github.ref_name }} Release"
generate_release_notes: true
files: |
builds/release-tars-**/*
publish-image:
runs-on: ubuntu-22.04
needs: [build]
strategy:
matrix:
include:
- build-os: linux
build-arch: amd64
- build-os: linux
build-arch: arm64
- build-os: linux
build-arch: s390x
- build-os: linux
build-arch: ppc64le
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to the container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: download artifacts
uses: actions/download-artifact@v4
with:
name: release-tars-${{ matrix.build-os }}-${{ matrix.build-arch }}
path: misc/snapshotter
- name: unpack static release
run: |
cd misc/snapshotter && tar -zxf *.tar.gz && mv bin/* . && rm -rf bin
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Get the nydusd version
run: |
export NYDUS_STABLE_VER=$(curl -fsSL https://api.github.com/repos/dragonflyoss/nydus/releases/latest | jq -r .tag_name)
echo "NYDUS_STABLE_VER=$NYDUS_STABLE_VER" >> "$GITHUB_ENV"
printf 'nydus version is: %s\n' "$NYDUS_STABLE_VER"
- name: build and push nydus-snapshotter image
uses: docker/build-push-action@v5
with:
context: misc/snapshotter
file: misc/snapshotter/Dockerfile
push: true
platforms: ${{ matrix.build-os }}/${{ matrix.build-arch }}
provenance: false
tags: |
${{ fromJSON(steps.meta.outputs.json).tags[0] }}-${{ matrix.build-arch }}
${{ fromJSON(steps.meta.outputs.json).tags[1] }}-${{ matrix.build-arch }}
labels: ${{ steps.meta.outputs.labels }}
build-args: NYDUS_VER=${{ env.NYDUS_STABLE_VER }}
publish-manifest:
runs-on: ubuntu-22.04
needs: [publish-image]
steps:
- name: Log in to the container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Publish manifest for multi-arch image
run: |
IFS=',' read -ra tags <<< "$(echo "${{ steps.meta.outputs.tags }}" | tr '\n' ',')"
for tag in "${tags[@]}"; do
docker manifest create "${tag}" \
--amend "${tag}-amd64" \
--amend "${tag}-arm64" \
--amend "${tag}-s390x" \
--amend "${tag}-ppc64le"
docker manifest push "${tag}"
done
================================================
FILE: .gitignore
================================================
bin/
pkg/filesystem/stargz/testdata/db/
coverage.txt
.vscode/
tests/output/
smoke.tests
tools/optimizer-server/target
vendor/
.idea/
.DS_Store
================================================
FILE: .golangci.yml
================================================
# https://golangci-lint.run/usage/configuration#config-file
version: "2"
run:
concurrency: 4
timeout: 5m
issues-exit-code: 1
tests: true
linters:
default: none
enable:
- depguard # Checks for imports that shouldn't be used.
- staticcheck
- unconvert
- revive
- ineffassign
- govet
- unused
- misspell
- bodyclose
# - cyclop
- dogsled
- nilnil
- unparam
- nilerr
# - gosec
- gocritic
- prealloc
# - funlen
- exhaustive
- errcheck
exclusions:
paths:
- misc
# The package is ported from containerd project, let's skip it.
- pkg/remote/remotes
settings:
errcheck:
# Don't check errors for these functions (common cleanup patterns)
exclude-functions:
- (io.Closer).Close
- (*os.File).Close
- (net.Conn).Close
- (*net.UnixListener).Close
- (*net.UnixConn).Close
- (*http.Response).Body.Close
- (*io.PipeWriter).Close
- (*compress/gzip.Writer).Close
- os.RemoveAll
- os.Remove
- os.Setenv
- fmt.Fprintf
- fmt.Fprintln
- golang.org/x/sys/unix.Close
exhaustive:
# Allow default case to be considered exhaustive
default-signifies-exhaustive: true
revive:
rules:
# Disable exported comment requirements (too strict for this codebase)
- name: exported
disabled: true
- name: package-comments
disabled: true
depguard:
rules:
Main:
list-mode: lax
files:
- $all
deny:
- pkg: "github.com/containerd/containerd/errdefs"
desc: The containerd errdefs package was migrated to a separate module. Use github.com/containerd/errdefs instead.
- pkg: "github.com/containerd/containerd/log"
desc: The containerd log package was migrated to a separate module. Use github.com/containerd/log instead.
- pkg: "github.com/containerd/containerd/platforms"
desc: The containerd platforms package was migrated to a separate module. Use github.com/containerd/platforms instead.
- pkg: "github.com/containerd/containerd/reference/docker"
desc: The containerd reference package was migrated to a separate module. Use github.com/distribution/reference instead.
# govet:
# check-shadowing: true
# enable:
# - fieldalignment
funlen:
# Checks the number of lines in a function.
# If lower than 0, disable the check.
# Default: 60
lines: 100
# Checks the number of statements in a function.
# If lower than 0, disable the check.
# Default: 40
statements: 80
nilnil:
checked-types:
- ptr
- func
- iface
- map
- chan
formatters:
enable:
- gofmt
- goimports
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: MAINTAINERS
================================================
# nydus-snapshotter maintainers
#
# As a containerd sub-project, containerd maintainers are also included from https://github.com/containerd/project/blob/main/MAINTAINERS.
# See https://github.com/containerd/project/blob/main/GOVERNANCE.md for description of maintainer role
#
# COMMITTERS
# GitHub ID, Name, Email address
changweige, Changwei Ge, changweige@gmail.com
eryugey, Eryu Guan, eguan@linux.alibaba.com
imeoer, Yan Song, yansong.ys@antgroup.com
Fricounet, Baptiste Girard-Carrabin, baptiste.girardcarrabin@datadoghq.com
#
# REVIEWERS
# GitHub ID, Name, Email address
sctb512, Bin Tang, tangbin.bin@bytedance.com
BraveY, Kaiyong Yang, yangkaiyong.yky@antgroup.com
================================================
FILE: Makefile
================================================
all: clean build
optimizer: clean-optimizer build-optimizer
PKG = github.com/containerd/nydus-snapshotter
PACKAGES ?= $(shell go list ./... | grep -v /tests)
SUDO = $(shell which sudo)
GO_EXECUTABLE_PATH ?= $(shell which go)
NYDUS_BUILDER ?= /usr/bin/nydus-image
NYDUS_NYDUSD ?= /usr/bin/nydusd
GOOS ?= linux
GOARCH ?= $(shell go env GOARCH)
KERNEL_VER = $(shell uname -r)
# Used to populate variables in version package.
BUILD_TIMESTAMP=$(shell date '+%Y-%m-%dT%H:%M:%S')
VERSION=$(shell git describe --match 'v[0-9]*' --dirty='.m' --always --tags)
REVISION=$(shell git rev-parse HEAD)$(shell if ! git diff --no-ext-diff --quiet --exit-code; then echo .m; fi)
RELEASE=nydus-snapshotter-v$(VERSION:v%=%)-${GOOS}-${GOARCH}
STATIC_RELEASE=nydus-snapshotter-v$(VERSION:v%=%)-${GOOS}-static
# Relpace test target images for e2e tests.
ifdef E2E_TEST_TARGET_IMAGES_FILE
ENV_TARGET_IMAGES_FILE = --env-file ${E2E_TEST_TARGET_IMAGES_FILE}
endif
ifdef E2E_DOWNLOADS_MIRROR
BUILD_ARG_E2E_DOWNLOADS_MIRROR = --build-arg DOWNLOADS_MIRROR=${E2E_DOWNLOADS_MIRROR}
endif
ifdef GOPROXY
PROXY := GOPROXY="${GOPROXY}"
endif
ifdef FS_CACHE
FS_DRIVER = fscache
else
FS_DRIVER = fusedev
endif
SNAPSHOTTER_CONFIG=/etc/nydus/config.toml
SOURCE_SNAPSHOTTER_CONFIG=misc/snapshotter/config.toml
NYDUSD_CONFIG=/etc/nydus/nydusd-config.${FS_DRIVER}.json
SOURCE_NYDUSD_CONFIG=misc/snapshotter/nydusd-config.${FS_DRIVER}.json
SNAPSHOTTER_SYSTEMD_UNIT_SERVICE=misc/snapshotter/nydus-snapshotter.${FS_DRIVER}.service
LDFLAGS = -s -w -X ${PKG}/version.Version=${VERSION} -X ${PKG}/version.Revision=$(REVISION) -X ${PKG}/version.BuildTimestamp=$(BUILD_TIMESTAMP)
DEBUG_LDFLAGS = -X ${PKG}/version.Version=${VERSION} -X ${PKG}/version.Revision=$(REVISION) -X ${PKG}/version.BuildTimestamp=$(BUILD_TIMESTAMP)
CARGO ?= $(shell which cargo)
OPTIMIZER_SERVER = tools/optimizer-server
OPTIMIZER_SERVER_TOML = ${OPTIMIZER_SERVER}/Cargo.toml
OPTIMIZER_SERVER_BIN = ${OPTIMIZER_SERVER}/bin/optimizer-server
.PHONY: build
build:
GOOS=${GOOS} GOARCH=${GOARCH} ${PROXY} go build -ldflags "$(LDFLAGS)" -v -o bin/containerd-nydus-grpc ./cmd/containerd-nydus-grpc
GOOS=${GOOS} GOARCH=${GOARCH} ${PROXY} go build -ldflags "$(LDFLAGS)" -v -o bin/nydus-overlayfs ./cmd/nydus-overlayfs
.PHONY: static
static:
CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} ${PROXY} go build -ldflags "$(LDFLAGS) -extldflags -static" -v -o bin/containerd-nydus-grpc ./cmd/containerd-nydus-grpc
CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} ${PROXY} go build -ldflags "$(LDFLAGS) -extldflags -static" -v -o bin/nydus-overlayfs ./cmd/nydus-overlayfs
debug:
GOOS=${GOOS} GOARCH=${GOARCH} ${PROXY} go build -ldflags "$(DEBUG_LDFLAGS)" -gcflags "-N -l" -v -o bin/containerd-nydus-grpc ./cmd/containerd-nydus-grpc
GOOS=${GOOS} GOARCH=${GOARCH} ${PROXY} go build -ldflags "$(DEBUG_LDFLAGS)" -gcflags "-N -l" -v -o bin/nydus-overlayfs ./cmd/nydus-overlayfs
.PHONY: build-optimizer
build-optimizer:
GOOS=${GOOS} GOARCH=${GOARCH} ${PROXY} go build -ldflags "$(LDFLAGS)" -v -o bin/optimizer-nri-plugin ./cmd/optimizer-nri-plugin
make -C tools/optimizer-server release OS=$(GOOS) ARCH=$(GOARCH) && cp ${OPTIMIZER_SERVER_BIN} ./bin
static-release:
CGO_ENABLED=0 ${PROXY} GOOS=${GOOS} GOARCH=${GOARCH} go build -ldflags "$(LDFLAGS) -extldflags -static" -v -o bin/containerd-nydus-grpc ./cmd/containerd-nydus-grpc
CGO_ENABLED=0 ${PROXY} GOOS=${GOOS} GOARCH=${GOARCH} go build -ldflags "$(LDFLAGS) -extldflags -static" -v -o bin/nydus-overlayfs ./cmd/nydus-overlayfs
CGO_ENABLED=0 ${PROXY} GOOS=${GOOS} GOARCH=${GOARCH} go build -ldflags "$(LDFLAGS) -extldflags -static" -v -o bin/optimizer-nri-plugin ./cmd/optimizer-nri-plugin
make -C tools/optimizer-server static-release && cp ${OPTIMIZER_SERVER_BIN} ./bin
package/$(RELEASE).tar.gz: build build-optimizer
mkdir -p package
rm -rf package/$(RELEASE) package/$(RELEASE).tar.gz
tar -czf package/$(RELEASE).tar.gz bin
rm -rf package/$(RELEASE)
package/$(STATIC_RELEASE).tar.gz: static-release
@mkdir -p package
@rm -rf package/$(STATIC_RELEASE) package/$(STATIC_RELEASE).tar.gz
@tar -czf package/$(STATIC_RELEASE).tar.gz bin
@rm -rf package/$(STATIC_RELEASE)
package: package/$(RELEASE).tar.gz
cd package && sha256sum $(RELEASE).tar.gz >$(RELEASE).tar.gz.sha256sum
static-package: package/$(STATIC_RELEASE).tar.gz
@cd package && sha256sum $(STATIC_RELEASE).tar.gz >$(STATIC_RELEASE).tar.gz.sha256sum
# Majorly for cross build for converter package since it is imported by other projects
converter:
GOOS=${GOOS} GOARCH=${GOARCH} ${PROXY} go build -ldflags "$(LDFLAGS)" -v -o bin/converter ./cmd/converter
.PHONY: clean
clean:
rm -f bin/*
rm -rf _out
.PHONY: clean-optimizer
clean-optimizer:
rm -rf bin/optimizer-nri-plugin bin/optimizer-server
make -C tools/optimizer-server clean
.PHONY: install
install:
@echo "+ $@ bin/containerd-nydus-grpc"
@sudo install -D -m 755 bin/containerd-nydus-grpc /usr/local/bin/containerd-nydus-grpc
@echo "+ $@ bin/nydus-overlayfs"
@sudo install -D -m 755 bin/nydus-overlayfs /usr/local/bin/nydus-overlayfs
@if [ ! -e ${NYDUSD_CONFIG} ]; then echo "+ $@ SOURCE_NYDUSD_CONFIG"; sudo install -D -m 664 ${SOURCE_NYDUSD_CONFIG} ${NYDUSD_CONFIG}; fi
@if [ ! -e ${SNAPSHOTTER_CONFIG} ]; then echo "+ $@ ${SOURCE_SNAPSHOTTER_CONFIG}"; sudo install -D -m 664 ${SOURCE_SNAPSHOTTER_CONFIG} ${SNAPSHOTTER_CONFIG}; fi
@sudo ln -f -s /etc/nydus/nydusd-config.${FS_DRIVER}.json /etc/nydus/nydusd-config.json
@echo "+ $@ ${SNAPSHOTTER_SYSTEMD_UNIT_SERVICE}"
@sudo install -D -m 644 ${SNAPSHOTTER_SYSTEMD_UNIT_SERVICE} /etc/systemd/system/nydus-snapshotter.service
@sudo mkdir -p /etc/nydus/certs.d
@if which systemctl >/dev/null; then sudo systemctl enable /etc/systemd/system/nydus-snapshotter.service; sudo systemctl restart nydus-snapshotter; fi
install-optimizer:
sudo install -D -m 755 bin/optimizer-nri-plugin /opt/nri/plugins/02-optimizer-nri-plugin
sudo install -D -m 755 bin/optimizer-server /usr/local/bin/optimizer-server
sudo install -D -m 755 misc/example/optimizer-nri-plugin.conf /etc/nri/conf.d/02-optimizer-nri-plugin.conf
@sudo mkdir -p /opt/nri/optimizer/results
.PHONY: vet
vet:
go vet $(PACKAGES) ./tests
.PHONY: check
check: vet
golangci-lint run
.PHONY: test
test:
go test -race -v -mod=mod -cover ${PACKAGES}
.PHONY: cover
cover:
go test -v -covermode=atomic -coverprofile=coverage.txt $(PACKAGES)
go tool cover -func=coverage.txt
# make smoke TESTS=TestPack
smoke:
${GO_EXECUTABLE_PATH} test -o smoke.tests -c -race -v -cover ./tests
$(SUDO) -E NYDUS_BUILDER=${NYDUS_BUILDER} NYDUS_NYDUSD=${NYDUS_NYDUSD} ./smoke.tests -test.v -test.timeout 10m -test.parallel=8 -test.run=$(TESTS)
.PHONY: integration
integration:
CGO_ENABLED=1 ${PROXY} GOOS=${GOOS} GOARCH=${GOARCH} go build -ldflags '-X "${PKG}/version.Version=${VERSION}" -extldflags "-static"' -race -v -o bin/containerd-nydus-grpc ./cmd/containerd-nydus-grpc
CGO_ENABLED=1 ${PROXY} GOOS=${GOOS} GOARCH=${GOARCH} go build -ldflags '-X "${PKG}/version.Version=${VERSION}" -extldflags "-static"' -race -v -o bin/nydus-overlayfs ./cmd/nydus-overlayfs
$(SUDO) docker build ${BUILD_ARG_E2E_DOWNLOADS_MIRROR} -t nydus-snapshotter-e2e:0.1 -f integration/Dockerfile .
$(SUDO) docker run --cap-add SYS_ADMIN --security-opt seccomp=unconfined --cgroup-parent=system.slice --cgroupns private --name nydus-snapshotter_e2e --rm --privileged -v /root/.docker:/root/.docker -v `go env GOMODCACHE`:/go/pkg/mod \
-v `go env GOCACHE`:/root/.cache/go-build -v `pwd`:/nydus-snapshotter \
-v /usr/src/linux-headers-${KERNEL_VER}:/usr/src/linux-headers-${KERNEL_VER} \
${ENV_TARGET_IMAGES_FILE} \
nydus-snapshotter-e2e:0.1
================================================
FILE: README.md
================================================
[**[⬇️ Download]**](https://github.com/containerd/nydus-snapshotter/releases)
[**[📖 Website]**](https://nydus.dev/)
[**[☸ Quick Start (Kubernetes)**]](https://github.com/containerd/nydus-snapshotter/blob/main/docs/run_nydus_in_kubernetes.md)
[**[🤓 Quick Start (nerdctl)**]](https://github.com/containerd/nerdctl/blob/master/docs/nydus.md)
[**[❓ FAQs & Troubleshooting]**](https://github.com/dragonflyoss/nydus/wiki/FAQ)
# Nydus Snapshotter
[](https://github.com/containerd/nydus-snapshotter/releases)
[](https://github.com/containerd/nydus-snapshotter/blob/main/LICENSE)

[](https://goreportcard.com/report/github.com/containerd/nydus-snapshotter)
[](https://twitter.com/dragonfly_oss)
[](https://github.com/dragonflyoss/nydus)
Nydus-snapshotter is a **non-core** sub-project of containerd.
Nydus snapshotter is an external plugin of containerd for [Nydus image service](https://nydus.dev) which implements a chunk-based content-addressable filesystem on top of a called `RAFS (Registry Acceleration File System)` format that improves the current OCI image specification, in terms of container launching speed, image space, and network bandwidth efficiency, as well as data integrity with several runtime backends: FUSE, virtiofs and in-kernel [EROFS](https://www.kernel.org/doc/html/latest/filesystems/erofs.html).
Nydus supports lazy pulling feature since pulling image is one of the time-consuming steps in the container lifecycle. Lazy pulling here means a container can run even the image is partially available and necessary chunks of the image are fetched on-demand. Apart from that, Nydus also supports [(e)Stargz](https://github.com/containerd/stargz-snapshotter) and OCI (by using [zran](https://github.com/dragonflyoss/nydus/blob/master/docs/nydus-zran.md)) lazy pulling directly **WITHOUT** any explicit conversion.
For more details about how to build Nydus container image, please refer to [nydusify](https://github.com/dragonflyoss/nydus/blob/master/docs/nydusify.md) conversion tool and [acceld](https://github.com/goharbor/acceleration-service).
## Architecture
### Architecture Based on FUSE

### Architecture Based on Fscache/Erofs

## Building
Just invoke `make` and check out the output executable binary `./bin/containerd-nydus-grpc`
```bash
make
```
## Integrate Nydus-snapshotter into Containerd
The following document will describe how to manually configure containerd + Nydus snapshotter. If you want to run Nydus snapshotter in Kubernetes cluster, you can try to use helm or run nydus snapshotter as a container. You can refer to [this documentation](./docs/run_nydus_in_kubernetes.md).
Containerd provides a general mechanism to exploit different types of snapshotters. Please ensure your containerd's version is 1.4.0 or above.
Add Nydus as a proxy plugin into containerd's configuration file which may be located at `/etc/containerd/config.toml`.
```toml
# The `address` field specifies through which socket snapshotter and containerd communicate.
[proxy_plugins]
[proxy_plugins.nydus]
type = "snapshot"
address = "/run/containerd-nydus/containerd-nydus-grpc.sock"
```
Restart your containerd service making the change take effect. Assume that your node is systemd based, restart the service as below:
```bash
systemctl restart containerd
```
### Get Nydus Binaries
Get `nydusd` `nydus-image` and `nydusctl` binaries from [nydus releases page](https://github.com/dragonflyoss/nydus/releases).
It's suggested to install the binaries to your system path. `nydusd` is FUSE userspace daemon and a vhost-user-fs backend. Nydus-snapshotter
will fork a nydusd process when necessary.
### Configure Nydus
Please follow instructions to [configure nydus](./docs/configure_nydus.md) in order to make it work properly in your environment.
### Start Nydus Snapshotter
Nydus-snapshotter is implemented as a [proxy plugin](https://github.com/containerd/containerd/blob/04985039cede6aafbb7dfb3206c9c4d04e2f924d/PLUGINS.md#proxy-plugins) (`containerd-nydus-grpc`) for containerd.
Assume your server is systemd based, install nydus-snapshotter:
Note: `nydusd` and `nydus-image` should be found from $PATH.
```bash
make install
systemctl restart containerd
```
Or you can start nydus-snapshotter manually.
```bash
# `--nydusd` specifies the path to nydusd binary. If `nydusd` and `nydus-image` are installed, `--nydusd` and `--nydus-image`can be omitted.
# Otherwise, provide them in below command line.
# `address` is the domain socket that you configured in containerd configuration file
# `--nydusd-config` is the path to `nydusd` configuration file
# The default nydus-snapshotter work directory is located at `/var/lib/containerd/io.containerd.snapshotter.v1.nydus`
$ sudo ./containerd-nydus-grpc --config /etc/nydus/config.toml --nydusd-config /etc/nydus/nydusd-config.json --log-to-stdout
```
### Validate Nydus-snapshotter Setup
Utilize containerd's `ctr` CLI command to validate if nydus-snapshotter is set up successfully.
```bash
$ ctr -a /run/containerd/containerd.sock plugin ls
TYPE ID PLATFORMS STATUS
io.containerd.snapshotter.v1 nydus - ok
```
### Optimize Nydus Image as per Workload
Nydus usually prefetch image data to local filesystem before a real user on-demand read. It helps to improve the performance and availability. A containerd NRI plugin [container image optimizer](docs/optimize_nydus_image.md) can be used to generate nydus image building suggestions to optimize your nydus image making the nydusd runtime match your workload IO pattern. The optimized nydus image has
a better performance.
## Quickstart Container with Lazy Pulling
### Start Container on single Node
Start container using `nerdctl` (>=v0.22) which has native nydus support with `nydus-snapshotter`.
```bash
# Start container by `nerdctl`
nerdctl --snapshotter nydus run ghcr.io/dragonflyoss/image-service/nginx:nydus-latest
```
### Start Container in Kubernetes Cluster
Change containerd's CRI configuration:
```toml
[plugins."io.containerd.grpc.v1.cri".containerd]
snapshotter = "nydus"
disable_snapshot_annotations = false
```
Use `crictl` to debug starting container via Kubernetes CRI. Dry run [steps](./docs/crictl_dry_run.md) of using `crictl` can be found in [documents](./docs).
### Setup with nydus-snapshotter image
We can also use the `nydus-snapshotter` container image when we want to put Nydus stuffs inside a container. See the [nydus-snapshotter example](./misc/example/README.md) for how to setup and use it.
## Integrate with Dragonfly to Distribute Images by P2P
Nydus is a sub-project of [Dragonfly](https://github.com/dragonflyoss/dragonfly). So it closely works with Dragonfly to distribute container images in a fast and efficient P2P fashion to reduce network latency and lower the pressure on a single-point of the registry.
### Quickstart Dragonfly & Nydus in Kubernetes
We recommend using the Dragonfly P2P data distribution system to further improve the runtime performance of Nydus images.
If you want to deploy Dragonfly and Nydus at the same time, please refer to this **[Quick Start](https://github.com/dragonflyoss/helm-charts/blob/main/INSTALL.md)**.
### Config Dragonfly mode
Dragonfly supports both **mirror** mode and HTTP **proxy** mode to boost the containers startup. It is suggested to use Dragonfly mirror mode. To integrate with Dragonfly in the mirror mode, please provide registry mirror in nydusd's json configuration file in section `device.backend.mirrors`
```json
{
"mirrors": [
{
"host": "http://127.0.0.1:65001",
"headers": "https://index.docker.io/v1/"
}
]
}
```
### Hot updating mirror configurations
`nydus-snapshotter` supports hot updating mirror configurations. You can set the configuration directory in nydus-snapshotter's toml configuration file with `remote.mirrors_config.dir`. The empty `remote.mirrors_config.dir` means disabling it.
```toml
[remote.mirrors_config]
dir = "/etc/nydus/certs.d"
```
Configuration file is compatible with containerd's configuration file in toml format.
```toml
[host]
[host."http://127.0.0.1:65001"]
# Optional: health check URL. If set, the mirror is only used when this URL returns HTTP 2xx.
# If not set, the mirror is used unconditionally.
ping_url = "http://127.0.0.1:65001/ping"
```
Before each mount, the snapshotter reads the mirror configuration for the target registry host and rewrites nydusd's backend `host` to the first reachable mirror. If a mirror has no `ping_url` it is selected immediately. If all mirrors fail their health check, the original registry host is used as fallback.
## Community
Nydus aims to form a **vendor-neutral opensource** image distribution solution to all communities.
Questions, bug reports, technical discussion, feature requests and contribution are always welcomed!
We're very pleased to hear your use cases any time.
Feel free to reach/join us via Slack and/or Dingtalk.
- **Slack:** [Nydus Workspace](https://join.slack.com/t/nydusimageservice/shared_invite/zt-pz4qvl4y-WIh4itPNILGhPS8JqdFm_w)
- **Twitter:** [@dragonfly_oss](https://twitter.com/dragonfly_oss)
- **Dingtalk:** [34971767](https://qr.dingtalk.com/action/joingroup?code=v1,k1,ioWGzuDZEIO10Bf+/ohz4RcQqAkW0MtOwoG1nbbMxQg=&_dt_no_comment=1&origin=11)
- **Technical Meeting:** Every Wednesday at 06:00 UTC (Beijing, Shanghai 14:00), please see our [HackMD](https://hackmd.io/@Nydus/Bk8u2X0p9) page for more information.
## License
[](https://app.fossa.com/projects/git%2Bgithub.com%2Fcontainerd%2Fnydus-snapshotter?ref=badge_large)
================================================
FILE: cmd/containerd-nydus-grpc/main.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"fmt"
"os"
"github.com/containerd/log"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
"github.com/containerd/nydus-snapshotter/config"
"github.com/containerd/nydus-snapshotter/internal/flags"
"github.com/containerd/nydus-snapshotter/internal/logging"
"github.com/containerd/nydus-snapshotter/pkg/errdefs"
"github.com/containerd/nydus-snapshotter/version"
)
func main() {
flags := flags.NewFlags()
app := &cli.App{
Name: "containerd-nydus-grpc",
Usage: "Nydus remote snapshotter for containerd",
Version: version.Version,
Flags: flags.F,
HideVersion: true,
Action: func(_ *cli.Context) error {
if flags.Args.PrintVersion {
fmt.Println("Version: ", version.Version)
fmt.Println("Revision: ", version.Revision)
fmt.Println("Go version: ", version.GoVersion)
fmt.Println("Build time: ", version.BuildTimestamp)
return nil
}
snapshotterConfigPath := flags.Args.SnapshotterConfigPath
var defaultSnapshotterConfig config.SnapshotterConfig
var snapshotterConfig config.SnapshotterConfig
if err := defaultSnapshotterConfig.FillUpWithDefaults(); err != nil {
return errors.New("failed to generate nydus default configuration")
}
// Once snapshotter's configuration file is provided, parse it and let command line parameters override it.
if snapshotterConfigPath != "" {
if c, err := config.LoadSnapshotterConfig(snapshotterConfigPath); err == nil {
// Command line parameters override the snapshotter's configurations for backwards compatibility
if err := config.ParseParameters(flags.Args, c); err != nil {
return errors.Wrap(err, "failed to parse commandline options")
}
snapshotterConfig = *c
} else {
return errors.Wrapf(err, "failed to load snapshotter configuration from %q", snapshotterConfigPath)
}
} else {
if err := config.ParseParameters(flags.Args, &snapshotterConfig); err != nil {
return errors.Wrap(err, "failed to parse commandline options")
}
}
if err := config.MergeConfig(&snapshotterConfig, &defaultSnapshotterConfig); err != nil {
return errors.Wrap(err, "failed to merge configurations")
}
if err := config.ValidateConfig(&snapshotterConfig); err != nil {
return errors.Wrapf(err, "failed to validate configurations")
}
config.PrepareLogDir(&snapshotterConfig)
ctx := logging.WithContext()
logConfig := &snapshotterConfig.LoggingConfig
logRotateArgs := &logging.RotateLogArgs{
RotateLogMaxSize: logConfig.RotateLogMaxSize,
RotateLogMaxBackups: logConfig.RotateLogMaxBackups,
RotateLogMaxAge: logConfig.RotateLogMaxAge,
RotateLogLocalTime: logConfig.RotateLogLocalTime,
RotateLogCompress: logConfig.RotateLogCompress,
}
if err := logging.SetUp(logConfig.LogLevel, logConfig.LogToStdout, logConfig.LogDir, logRotateArgs); err != nil {
return errors.Wrap(err, "failed to setup logger")
}
log.L.Infof("Logger successfully set up. Proceeding to process nydus-snapshotter configurations")
if err := config.ProcessConfigurations(&snapshotterConfig); err != nil {
return errors.Wrap(err, "failed to process configurations")
}
if err := config.SetUpEnvironment(&snapshotterConfig); err != nil {
return errors.Wrap(err, "failed to setup environment")
}
log.L.Infof("Start nydus-snapshotter. Version: %s, PID: %d, FsDriver: %s, DaemonMode: %s",
version.Version, os.Getpid(), config.GetFsDriver(), snapshotterConfig.DaemonMode)
return Start(ctx, &snapshotterConfig)
},
}
if err := app.Run(os.Args); err != nil {
if errdefs.IsConnectionClosed(err) {
log.L.Info("nydus-snapshotter exited")
} else {
log.L.WithError(err).Fatal("failed to start nydus-snapshotter")
}
}
}
================================================
FILE: cmd/containerd-nydus-grpc/snapshotter.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"context"
"net"
"os"
"path/filepath"
"github.com/pkg/errors"
"github.com/containerd/nydus-snapshotter/config"
"github.com/containerd/nydus-snapshotter/pkg/auth"
"github.com/containerd/nydus-snapshotter/pkg/utils/signals"
"github.com/containerd/nydus-snapshotter/snapshot"
api "github.com/containerd/containerd/api/services/snapshots/v1"
"github.com/containerd/containerd/v2/contrib/snapshotservice"
"github.com/containerd/containerd/v2/core/snapshots"
"github.com/containerd/log"
"google.golang.org/grpc"
)
func Start(ctx context.Context, cfg *config.SnapshotterConfig) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
rs, err := snapshot.NewSnapshotter(ctx, cfg)
if err != nil {
return errors.Wrap(err, "failed to initialize snapshotter")
}
stopSignal := signals.SetupSignalHandler()
opt := ServeOptions{
ListeningSocketPath: cfg.Address,
ListeningSocketUID: cfg.UID,
ListeningSocketGID: cfg.GID,
EnableCRIKeychain: cfg.RemoteConfig.AuthConfig.EnableCRIKeychain,
ImageServiceAddress: cfg.RemoteConfig.AuthConfig.ImageServiceAddress,
}
if cfg.RemoteConfig.AuthConfig.EnableKubeconfigKeychain {
if err := auth.InitKubeSecretListener(ctx, cfg.RemoteConfig.AuthConfig.KubeconfigPath); err != nil {
return err
}
}
if cfg.RemoteConfig.AuthConfig.EnableKubeletCredentialProviders {
if err := auth.InitKubeletProvider(
cfg.RemoteConfig.AuthConfig.CredentialProviderConfig,
cfg.RemoteConfig.AuthConfig.CredentialProviderBinDir,
); err != nil {
return errors.Wrap(err, "failed to initialize kubelet credential provider")
}
}
return Serve(ctx, rs, opt, stopSignal)
}
type ServeOptions struct {
ListeningSocketPath string
ListeningSocketUID int
ListeningSocketGID int
EnableCRIKeychain bool
ImageServiceAddress string
}
func Serve(ctx context.Context, sn snapshots.Snapshotter, options ServeOptions, stop <-chan struct{}) error {
err := ensureSocketNotExists(options.ListeningSocketPath)
if err != nil {
return err
}
rpc := grpc.NewServer()
if rpc == nil {
return errors.New("start gRPC server")
}
api.RegisterSnapshotsServer(rpc, snapshotservice.FromSnapshotter(sn))
listener, err := net.Listen("unix", options.ListeningSocketPath)
if err != nil {
return errors.Wrapf(err, "listen socket %q", options.ListeningSocketPath)
}
if err := os.Chown(options.ListeningSocketPath, options.ListeningSocketUID, options.ListeningSocketGID); err != nil {
return errors.Wrap(err, "chown socket")
}
if options.EnableCRIKeychain {
auth.AddImageProxy(ctx, rpc, options.ImageServiceAddress)
}
go func() {
<-stop
log.L.Infof("Shutting down nydus-snapshotter!")
if err := sn.Close(); err != nil {
log.L.WithError(err).Errorf("Closing snapshotter error")
}
if err := listener.Close(); err != nil {
log.L.Errorf("Failed to close listener %s, err: %v", options.ListeningSocketPath, err)
}
}()
return rpc.Serve(listener)
}
func ensureSocketNotExists(listeningSocketPath string) error {
if err := os.MkdirAll(filepath.Dir(listeningSocketPath), 0700); err != nil {
return errors.Wrapf(err, "failed to create directory %q", filepath.Dir(listeningSocketPath))
}
finfo, err := os.Stat(listeningSocketPath)
// err is nil means listening socket path exists, remove before serve
if err == nil {
if finfo.Mode()&os.ModeSocket == 0 {
return errors.Errorf("file %s is not a socket", listeningSocketPath)
}
err := os.Remove(listeningSocketPath)
if err != nil {
return err
}
}
return nil
}
================================================
FILE: cmd/converter/main.go
================================================
package main
// Import the converter package so that it can be compiled during
// `go build` to ensure cross-compilation compatibility.
import (
_ "github.com/containerd/nydus-snapshotter/pkg/converter"
)
func main() {
}
================================================
FILE: cmd/nydus-overlayfs/main.go
================================================
package main
import (
"fmt"
"log"
"os"
"path"
"strings"
"syscall"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
"golang.org/x/sys/unix"
)
const (
// Extra mount option to pass Nydus specific information from snapshotter to runtime through containerd.
extraOptionKey = "extraoption="
// Kata virtual volume infmation passed from snapshotter to runtime through containerd, superset of `extraOptionKey`.
// Please refer to `KataVirtualVolume` in https://github.com/kata-containers/kata-containers/blob/main/src/libs/kata-types/src/mount.rs
kataVolumeOptionKey = "io.katacontainers.volume="
)
var (
Version = "v0.1"
BuildTime = "unknown"
pagesize = os.Getpagesize()
)
/*
containerd run fuse.mount format: nydus-overlayfs overlay /tmp/ctd-volume107067851
-o lowerdir=/foo/lower2:/foo/lower1,upperdir=/foo/upper,workdir=/foo/work,extraoption={...},dev,suid]
*/
type mountArgs struct {
fsType string
target string
options []string
}
func parseArgs(args []string) (*mountArgs, error) {
margs := &mountArgs{
fsType: args[0],
target: args[1],
}
if margs.fsType != "overlay" {
return nil, errors.Errorf("invalid filesystem type %s for overlayfs", margs.fsType)
}
if len(margs.target) == 0 {
return nil, errors.New("empty overlayfs mount target")
}
if args[2] == "-o" && len(args[3]) != 0 {
for _, opt := range strings.Split(args[3], ",") {
// filter Nydus specific options
if strings.HasPrefix(opt, extraOptionKey) || strings.HasPrefix(opt, kataVolumeOptionKey) {
continue
}
margs.options = append(margs.options, opt)
}
}
if len(margs.options) == 0 {
return nil, errors.New("empty overlayfs mount options")
}
return margs, nil
}
func parseOptions(options []string) (int, string) {
flagsTable := map[string]int{
"async": unix.MS_SYNCHRONOUS,
"atime": unix.MS_NOATIME,
"bind": unix.MS_BIND,
"defaults": 0,
"dev": unix.MS_NODEV,
"diratime": unix.MS_NODIRATIME,
"dirsync": unix.MS_DIRSYNC,
"exec": unix.MS_NOEXEC,
"mand": unix.MS_MANDLOCK,
"noatime": unix.MS_NOATIME,
"nodev": unix.MS_NODEV,
"nodiratime": unix.MS_NODIRATIME,
"noexec": unix.MS_NOEXEC,
"nomand": unix.MS_MANDLOCK,
"norelatime": unix.MS_RELATIME,
"nostrictatime": unix.MS_STRICTATIME,
"nosuid": unix.MS_NOSUID,
"rbind": unix.MS_BIND | unix.MS_REC,
"relatime": unix.MS_RELATIME,
"remount": unix.MS_REMOUNT,
"ro": unix.MS_RDONLY,
"rw": unix.MS_RDONLY,
"strictatime": unix.MS_STRICTATIME,
"suid": unix.MS_NOSUID,
"sync": unix.MS_SYNCHRONOUS,
}
var (
flags int
data []string
)
for _, o := range options {
if f, exist := flagsTable[o]; exist {
flags |= f
} else {
data = append(data, o)
}
}
return flags, strings.Join(data, ",")
}
// longestCommonPrefix finds the longest common prefix in the string slice.
func longestCommonPrefix(strs []string) string {
if len(strs) == 0 {
return ""
} else if len(strs) == 1 {
return strs[0]
}
min, max := strs[0], strs[0]
for _, str := range strs[1:] {
if min > str {
min = str
}
if max < str {
max = str
}
}
for i := 0; i < len(min) && i < len(max); i++ {
if min[i] != max[i] {
return min[:i]
}
}
return min
}
// compactLowerdirOption shortens lowerdir paths by extracting the longest
// common directory prefix across all lowerdirs, upperdir, and workdir. The
// caller chdir's into that prefix before calling mount so the kernel receives
// shorter, relative paths that fit within the mount-data page-size limit.
// Ported from containerd's core/mount/mount_linux.go.
func compactLowerdirOption(options []string) (string, []string) {
var lowerdirIdx = -1
for i, opt := range options {
if strings.HasPrefix(opt, "lowerdir=") {
lowerdirIdx = i
break
}
}
if lowerdirIdx == -1 {
return "", options
}
dirs := strings.Split(options[lowerdirIdx][len("lowerdir="):], ":")
if len(dirs) <= 1 {
return "", options
}
// Collect all paths that participate in the overlay: lowerdirs + upperdir + workdir.
allPaths := make([]string, 0, len(dirs)+2)
allPaths = append(allPaths, dirs...)
for _, opt := range options {
if strings.HasPrefix(opt, "upperdir=") {
allPaths = append(allPaths, opt[len("upperdir="):])
} else if strings.HasPrefix(opt, "workdir=") {
allPaths = append(allPaths, opt[len("workdir="):])
}
}
commondir := longestCommonPrefix(allPaths)
if commondir == "" {
return "", options
}
// Back up to the parent directory to avoid partial snapshot-id matches.
commondir = path.Dir(commondir)
if commondir == "/" || commondir == "." {
return "", options
}
commondir += "/"
// Rebuild options with relative paths.
newopts := make([]string, 0, len(options))
for i, opt := range options {
switch {
case i == lowerdirIdx:
newdirs := make([]string, 0, len(dirs))
for _, dir := range dirs {
if len(dir) <= len(commondir) {
return "", options
}
newdirs = append(newdirs, dir[len(commondir):])
}
newopts = append(newopts, fmt.Sprintf("lowerdir=%s", strings.Join(newdirs, ":")))
case strings.HasPrefix(opt, "upperdir="):
p := opt[len("upperdir="):]
if len(p) <= len(commondir) {
return "", options
}
newopts = append(newopts, fmt.Sprintf("upperdir=%s", p[len(commondir):]))
case strings.HasPrefix(opt, "workdir="):
p := opt[len("workdir="):]
if len(p) <= len(commondir) {
return "", options
}
newopts = append(newopts, fmt.Sprintf("workdir=%s", p[len(commondir):]))
default:
newopts = append(newopts, opt)
}
}
return commondir, newopts
}
func run(args cli.Args) error {
margs, err := parseArgs(args.Slice())
if err != nil {
return errors.Wrap(err, "parse mount options")
}
flags, data := parseOptions(margs.options)
// When the mount data string exceeds the kernel's page-size limit, compact
// overlay paths by chdir'ing into their common prefix and using relative
// paths — the same approach containerd uses for direct overlay mounts.
var chdir string
if len(data) >= pagesize-512 {
chdir, margs.options = compactLowerdirOption(margs.options)
if chdir != "" {
_, data = parseOptions(margs.options)
}
}
if chdir != "" {
if err := os.Chdir(chdir); err != nil {
return errors.Wrapf(err, "chdir to common overlay dir %s", chdir)
}
}
err = syscall.Mount(margs.fsType, margs.target, margs.fsType, uintptr(flags), data)
if err != nil {
return errors.Wrapf(err, "mount overlayfs by syscall")
}
return nil
}
func main() {
app := &cli.App{
Name: "NydusOverlayfs",
Usage: "FUSE mount helper for containerd to filter out Nydus specific options",
Version: fmt.Sprintf("%s.%s", Version, BuildTime),
UsageText: "[Usage]: nydus-overlayfs overlay -o ",
Action: func(c *cli.Context) error {
return run(c.Args())
},
Before: func(c *cli.Context) error {
if c.NArg() != 4 {
cli.ShowAppHelpAndExit(c, 1)
}
return nil
},
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
os.Exit(0)
}
================================================
FILE: cmd/nydus-overlayfs/main_test.go
================================================
/*
* Copyright (c) 2026. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLongestCommonPrefix(t *testing.T) {
tests := []struct {
name string
input []string
expected string
}{
{
name: "empty slice",
input: []string{},
expected: "",
},
{
name: "single element",
input: []string{"/var/lib/snapshots/1/fs"},
expected: "/var/lib/snapshots/1/fs",
},
{
name: "common prefix across snapshot ids",
input: []string{"/var/lib/snapshots/128/fs", "/var/lib/snapshots/21/fs", "/var/lib/snapshots/99/fs"},
expected: "/var/lib/snapshots/",
},
{
name: "no common prefix",
input: []string{"/foo/bar", "/baz/qux"},
expected: "/",
},
{
name: "identical strings",
input: []string{"/a/b/c", "/a/b/c"},
expected: "/a/b/c",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result := longestCommonPrefix(tc.input)
assert.Equal(t, tc.expected, result)
})
}
}
func TestCompactLowerdirOption(t *testing.T) {
tests := []struct {
name string
options []string
expectedChdir string
expectedOptions []string
}{
{
name: "no lowerdir option",
options: []string{"ro", "dev"},
expectedChdir: "",
expectedOptions: []string{"ro", "dev"},
},
{
name: "single lowerdir - no compaction needed",
options: []string{"lowerdir=/var/lib/snapshots/1/fs"},
expectedChdir: "",
expectedOptions: []string{"lowerdir=/var/lib/snapshots/1/fs"},
},
{
name: "two lowerdirs with common prefix",
options: []string{
"workdir=/var/lib/snapshots/3/work",
"upperdir=/var/lib/snapshots/3/fs",
"lowerdir=/var/lib/snapshots/2/fs:/var/lib/snapshots/1/fs",
},
expectedChdir: "/var/lib/snapshots/",
expectedOptions: []string{
"workdir=3/work",
"upperdir=3/fs",
"lowerdir=2/fs:1/fs",
},
},
{
name: "many lowerdirs simulating real nydus case",
options: func() []string {
lowerdirs := make([]string, 0, 108)
for i := 128; i >= 21; i-- {
lowerdirs = append(lowerdirs, fmt.Sprintf("/var/lib/nydus-snapshotter/snapshots/%d/fs", i))
}
return []string{
"workdir=/var/lib/nydus-snapshotter/snapshots/129/work",
"upperdir=/var/lib/nydus-snapshotter/snapshots/129/fs",
fmt.Sprintf("lowerdir=%s", strings.Join(lowerdirs, ":")),
"ro",
}
}(),
expectedChdir: "/var/lib/nydus-snapshotter/snapshots/",
expectedOptions: func() []string {
lowerdirs := make([]string, 0, 108)
for i := 128; i >= 21; i-- {
lowerdirs = append(lowerdirs, fmt.Sprintf("%d/fs", i))
}
return []string{
"workdir=129/work",
"upperdir=129/fs",
fmt.Sprintf("lowerdir=%s", strings.Join(lowerdirs, ":")),
"ro",
}
}(),
},
{
name: "disjoint paths - no compaction possible",
options: []string{
"workdir=/tmp/work",
"upperdir=/tmp/upper",
"lowerdir=/var/lib/snapshots/2/fs:/var/lib/snapshots/1/fs",
},
expectedChdir: "",
expectedOptions: nil, // same as input
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
chdir, newopts := compactLowerdirOption(tc.options)
assert.Equal(t, tc.expectedChdir, chdir)
if tc.expectedOptions != nil {
assert.Equal(t, tc.expectedOptions, newopts)
}
if chdir != "" {
_, newdata := parseOptions(newopts)
assert.Less(t, len(newdata), len(strings.Join(tc.options, ",")),
"compacted options should be shorter than original")
}
})
}
}
func TestCompactLowerdirOption_RealWorldSizeSavings(t *testing.T) {
lowerdirs := make([]string, 0, 108)
for i := 128; i >= 21; i-- {
lowerdirs = append(lowerdirs, fmt.Sprintf("/var/lib/nydus-snapshotter/snapshots/%d/fs", i))
}
options := []string{
"workdir=/var/lib/nydus-snapshotter/snapshots/129/work",
"upperdir=/var/lib/nydus-snapshotter/snapshots/129/fs",
fmt.Sprintf("lowerdir=%s", strings.Join(lowerdirs, ":")),
}
_, origdata := parseOptions(options)
chdir, newopts := compactLowerdirOption(options)
require.NotEmpty(t, chdir)
_, compactdata := parseOptions(newopts)
assert.Greater(t, len(origdata), 4096, "original data should exceed page size")
assert.Less(t, len(compactdata), 4096, "compacted data should fit within page size")
}
func TestParseArgs(t *testing.T) {
tests := []struct {
name string
args []string
expectedType string
expectedTarget string
expectedOptions []string
expectErr bool
}{
{
name: "basic overlay",
args: []string{"overlay", "/tmp/merged", "-o", "lowerdir=/a:/b,upperdir=/c,workdir=/d"},
expectedType: "overlay",
expectedTarget: "/tmp/merged",
expectedOptions: []string{"lowerdir=/a:/b", "upperdir=/c", "workdir=/d"},
},
{
name: "filters extraoption",
args: []string{"overlay", "/tmp/merged", "-o", "lowerdir=/a:/b,extraoption=abc123,ro"},
expectedType: "overlay",
expectedTarget: "/tmp/merged",
expectedOptions: []string{"lowerdir=/a:/b", "ro"},
},
{
name: "filters kata volume option",
args: []string{"overlay", "/tmp/merged", "-o", "lowerdir=/a:/b,io.katacontainers.volume=abc123,ro"},
expectedType: "overlay",
expectedTarget: "/tmp/merged",
expectedOptions: []string{"lowerdir=/a:/b", "ro"},
},
{
name: "invalid fs type",
args: []string{"ext4", "/tmp/merged", "-o", "lowerdir=/a"},
expectErr: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
margs, err := parseArgs(tc.args)
if tc.expectErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tc.expectedType, margs.fsType)
assert.Equal(t, tc.expectedTarget, margs.target)
assert.Equal(t, tc.expectedOptions, margs.options)
})
}
}
================================================
FILE: cmd/optimizer-nri-plugin/main.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"context"
"fmt"
"io"
"log/syslog"
"os"
"path/filepath"
"strings"
"time"
"github.com/containerd/log"
distribution "github.com/distribution/reference"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
"github.com/containerd/nri/pkg/api"
"github.com/containerd/nri/pkg/stub"
"github.com/containerd/nydus-snapshotter/pkg/errdefs"
"github.com/containerd/nydus-snapshotter/pkg/fanotify"
"github.com/containerd/nydus-snapshotter/version"
"github.com/pelletier/go-toml"
)
const (
defaultEvents = "StartContainer,StopContainer"
defaultServerPath = "/usr/local/bin/optimizer-server"
defaultPersistDir = "/opt/nri/optimizer/results"
)
type PluginConfig struct {
Events []string `toml:"events"`
ServerPath string `toml:"server_path"`
PersistDir string `toml:"persist_dir"`
Readable bool `toml:"readable"`
Timeout int `toml:"timeout"`
Overwrite bool `toml:"overwrite"`
}
type PluginArgs struct {
PluginName string
PluginIdx string
PluginEvents string
Config PluginConfig
}
type Flags struct {
Args *PluginArgs
F []cli.Flag
}
func buildFlags(args *PluginArgs) []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "name",
Usage: "plugin name to register to NRI",
Destination: &args.PluginName,
},
&cli.StringFlag{
Name: "idx",
Usage: "plugin index to register to NRI",
Destination: &args.PluginIdx,
},
&cli.StringFlag{
Name: "events",
Value: defaultEvents,
Usage: "the events that containerd subscribes to. DO NOT CHANGE THIS.",
Destination: &args.PluginEvents,
},
&cli.StringFlag{
Name: "server-path",
Value: defaultServerPath,
Usage: "the path of optimizer server binary",
Destination: &args.Config.ServerPath,
},
&cli.StringFlag{
Name: "persist-dir",
Value: defaultPersistDir,
Usage: "the directory to persist accessed files list for container",
Destination: &args.Config.PersistDir,
},
&cli.BoolFlag{
Name: "readable",
Value: false,
Usage: "whether to make the csv file human readable",
Destination: &args.Config.Readable,
},
&cli.IntFlag{
Name: "timeout",
Value: 0,
Usage: "the timeout to kill optimizer server, 0 to disable it",
Destination: &args.Config.Timeout,
},
&cli.BoolFlag{
Name: "overwrite",
Usage: "whether to overwrite the existed persistent files",
Destination: &args.Config.Overwrite,
},
}
}
func NewPluginFlags() *Flags {
var args PluginArgs
return &Flags{
Args: &args,
F: buildFlags(&args),
}
}
type plugin struct {
stub stub.Stub
mask stub.EventMask
}
var (
cfg PluginConfig
logWriter *syslog.Writer
globalFanotifyServer = make(map[string]*fanotify.Server)
_ = stub.ConfigureInterface(&plugin{})
_ = stub.StartContainerInterface(&plugin{})
_ = stub.StopContainerInterface(&plugin{})
)
const (
imageNameLabel = "io.kubernetes.cri.image-name"
)
func (p *plugin) Configure(ctx context.Context, config, runtime, version string) (stub.EventMask, error) {
log.G(ctx).Infof("got configuration data: %q from runtime %s %s", config, runtime, version)
if config == "" {
return p.mask, nil
}
tree, err := toml.Load(config)
if err != nil {
return 0, errors.Wrap(err, "parse TOML")
}
if err := tree.Unmarshal(&cfg); err != nil {
return 0, err
}
p.mask, err = api.ParseEventMask(cfg.Events...)
if err != nil {
return 0, errors.Wrap(err, "parse events in configuration")
}
log.G(ctx).Infof("configuration: %#v", cfg)
return p.mask, nil
}
func (p *plugin) StartContainer(_ context.Context, _ *api.PodSandbox, container *api.Container) error {
dir, imageName, err := GetImageName(container.Annotations)
if err != nil {
return err
}
persistDir := filepath.Join(cfg.PersistDir, dir)
if err := os.MkdirAll(persistDir, os.ModePerm); err != nil {
return err
}
persistFile := filepath.Join(persistDir, imageName)
if cfg.Timeout > 0 {
persistFile = fmt.Sprintf("%s.timeout%ds", persistFile, cfg.Timeout)
}
fanotifyServer := fanotify.NewServer(cfg.ServerPath, container.Pid, imageName, persistFile, cfg.Readable, cfg.Overwrite, time.Duration(cfg.Timeout)*time.Second, logWriter)
if err := fanotifyServer.RunServer(); err != nil {
return err
}
globalFanotifyServer[imageName] = fanotifyServer
return nil
}
func (p *plugin) StopContainer(_ context.Context, _ *api.PodSandbox, container *api.Container) ([]*api.ContainerUpdate, error) {
var update = []*api.ContainerUpdate{}
_, imageName, err := GetImageName(container.Annotations)
if err != nil {
return update, err
}
if fanotifyServer, ok := globalFanotifyServer[imageName]; ok {
fanotifyServer.StopServer()
} else {
return nil, errors.New("can not find fanotify server for container image " + imageName)
}
return update, nil
}
func GetImageName(annotations map[string]string) (string, string, error) {
named, err := distribution.ParseDockerRef(annotations[imageNameLabel])
if err != nil {
return "", "", err
}
nameTagged := named.(distribution.NamedTagged)
repo := distribution.Path(nameTagged)
dir := filepath.Dir(repo)
image := filepath.Base(repo)
imageName := image + ":" + nameTagged.Tag()
return dir, imageName, nil
}
func (p *plugin) onClose() {
for _, fanotifyServer := range globalFanotifyServer {
fanotifyServer.StopServer()
}
os.Exit(0)
}
func main() {
flags := NewPluginFlags()
app := &cli.App{
Name: "optimizer-nri-plugin",
Usage: "Optimizer client for NRI plugin to manage optimizer server",
Version: version.Version,
Flags: flags.F,
HideVersion: true,
Action: func(_ *cli.Context) error {
var (
opts []stub.Option
err error
)
cfg = flags.Args.Config
// FIXME(thaJeztah): ucontainerd's log does not set "PadLevelText: true"
_ = log.SetFormat(log.TextFormat)
ctx := log.WithLogger(context.Background(), log.L)
logWriter, err = syslog.New(syslog.LOG_INFO, "optimizer-nri-plugin")
if err == nil {
log.G(ctx).Logger.SetOutput(io.MultiWriter(os.Stdout, logWriter))
}
if flags.Args.PluginName != "" {
opts = append(opts, stub.WithPluginName(flags.Args.PluginName))
}
if flags.Args.PluginIdx != "" {
opts = append(opts, stub.WithPluginIdx(flags.Args.PluginIdx))
}
p := &plugin{}
if p.mask, err = api.ParseEventMask(flags.Args.PluginEvents); err != nil {
log.G(ctx).Fatalf("failed to parse events: %v", err)
}
cfg.Events = strings.Split(flags.Args.PluginEvents, ",")
if p.stub, err = stub.New(p, append(opts, stub.WithOnClose(p.onClose))...); err != nil {
log.G(ctx).Fatalf("failed to create plugin stub: %v", err)
}
err = p.stub.Run(context.Background())
if err != nil {
log.G(ctx).Errorf("plugin exited with error %v", err)
os.Exit(1)
}
return nil
},
}
if err := app.Run(os.Args); err != nil {
if errdefs.IsConnectionClosed(err) {
log.L.Info("optimizer NRI plugin exited")
} else {
log.L.WithError(err).Fatal("failed to start optimizer NRI plugin")
}
}
}
================================================
FILE: cmd/prefetchfiles-nri-plugin/main.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"context"
"fmt"
"io"
"log/syslog"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/containerd/log"
"github.com/containerd/nri/pkg/api"
"github.com/containerd/nri/pkg/stub"
"github.com/pelletier/go-toml"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
"github.com/containerd/nydus-snapshotter/pkg/errdefs"
"github.com/containerd/nydus-snapshotter/version"
)
const (
endpointPrefetch = "/api/v1/prefetch"
defaultEvents = "RunPodSandbox"
defaultSystemControllerAddress = "/run/containerd-nydus/system.sock"
defaultPrefetchConfigDir = "/etc/nydus"
nydusPrefetchAnnotation = "containerd.io/nydus-prefetch"
)
type PluginArgs struct {
PluginName string
PluginIdx string
SocketAddress string
}
type Flags struct {
Args *PluginArgs
Flag []cli.Flag
}
func buildFlags(args *PluginArgs) []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "name",
Usage: "plugin name to register to NRI",
Destination: &args.PluginName,
},
&cli.StringFlag{
Name: "idx",
Usage: "plugin index to register to NRI",
Destination: &args.PluginIdx,
},
&cli.StringFlag{
Name: "socket-addr",
Value: defaultSystemControllerAddress,
Usage: "unix domain socket address. If defined in the configuration file, there is no need to add ",
Destination: &args.SocketAddress,
},
}
}
func NewPluginFlags() *Flags {
var args PluginArgs
return &Flags{
Args: &args,
Flag: buildFlags(&args),
}
}
type plugin struct {
stub stub.Stub
mask stub.EventMask
}
var (
globalSocket string
logWriter *syslog.Writer
_ = stub.RunPodInterface(&plugin{})
)
// sendDataOverHTTP sends the prefetch data to the specified endpoint over HTTP using a Unix socket.
func sendDataOverHTTP(data string, endpoint, sock string) error {
url := fmt.Sprintf("http://unix%s", endpoint)
req, err := http.NewRequest(http.MethodPut, url, strings.NewReader(data))
if err != nil {
return err
}
client := &http.Client{
Transport: &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", sock)
},
},
}
resp, err := client.Do(req)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to send data, status code: %d", resp.StatusCode)
}
resp.Body.Close()
return nil
}
func (p *plugin) RunPodSandbox(ctx context.Context, pod *api.PodSandbox) error {
prefetchList, ok := pod.Annotations[nydusPrefetchAnnotation]
if !ok {
return nil
}
err := sendDataOverHTTP(prefetchList, endpointPrefetch, globalSocket)
if err != nil {
log.G(ctx).Errorf("failed to send data: %v", err)
return err
}
return nil
}
func main() {
flags := NewPluginFlags()
app := &cli.App{
Name: "prefetch-nri-plugin",
Usage: "NRI plugin for obtaining and transmitting prefetch files path",
Version: version.Version,
Flags: flags.Flag,
HideVersion: true,
Action: func(_ *cli.Context) error {
var (
opts []stub.Option
err error
)
// FIXME(thaJeztah): ucontainerd's log does not set "PadLevelText: true"
_ = log.SetFormat(log.TextFormat)
ctx := log.WithLogger(context.Background(), log.L)
configFileName := "prefetchConfig.toml"
configDir := defaultPrefetchConfigDir
configFilePath := filepath.Join(configDir, configFileName)
config, err := toml.LoadFile(configFilePath)
if err != nil {
log.G(ctx).Warnf("failed to read config file: %v", err)
}
configSocketAddrRaw := config.Get("file_prefetch.socket_address")
if configSocketAddrRaw != nil {
if configSocketAddr, ok := configSocketAddrRaw.(string); ok {
globalSocket = configSocketAddr
} else {
log.G(ctx).Warnf("failed to read config: 'file_prefetch.socket_address' is not a string")
}
} else {
globalSocket = flags.Args.SocketAddress
}
logWriter, err = syslog.New(syslog.LOG_INFO, "prefetch-nri-plugin")
if err == nil {
log.G(ctx).Logger.SetOutput(io.MultiWriter(os.Stdout, logWriter))
}
if flags.Args.PluginName != "" {
opts = append(opts, stub.WithPluginName(flags.Args.PluginName))
}
if flags.Args.PluginIdx != "" {
opts = append(opts, stub.WithPluginIdx(flags.Args.PluginIdx))
}
p := &plugin{}
if p.mask, err = api.ParseEventMask(defaultEvents); err != nil {
log.G(ctx).Fatalf("failed to parse events: %v", err)
}
if p.stub, err = stub.New(p, opts...); err != nil {
log.G(ctx).Fatalf("failed to create plugin stub: %v", err)
}
err = p.stub.Run(context.Background())
if err != nil {
return errors.Wrap(err, "plugin exited")
}
return nil
},
}
if err := app.Run(os.Args); err != nil {
if errdefs.IsConnectionClosed(err) {
log.L.Info("prefetch NRI plugin exited")
} else {
log.L.WithError(err).Fatal("failed to start prefetch NRI plugin")
}
}
}
================================================
FILE: config/config.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package config
import (
"os"
"time"
"dario.cat/mergo"
"github.com/pelletier/go-toml"
"github.com/pkg/errors"
"github.com/containerd/nydus-snapshotter/internal/constant"
"github.com/containerd/nydus-snapshotter/internal/flags"
"github.com/containerd/nydus-snapshotter/pkg/cgroup"
"github.com/containerd/nydus-snapshotter/pkg/errdefs"
"github.com/containerd/nydus-snapshotter/pkg/utils/file"
"github.com/containerd/nydus-snapshotter/pkg/utils/parser"
"github.com/containerd/nydus-snapshotter/pkg/utils/sysinfo"
)
func init() {
recoverPolicyParser = map[string]DaemonRecoverPolicy{
RecoverPolicyNone.String(): RecoverPolicyNone,
RecoverPolicyRestart.String(): RecoverPolicyRestart,
RecoverPolicyFailover.String(): RecoverPolicyFailover}
}
// Define a policy how to fork nydusd daemon and attach file system instances to serve.
type DaemonMode string
const (
// Spawn a dedicated nydusd for each RAFS instance.
DaemonModeMultiple DaemonMode = DaemonMode(constant.DaemonModeMultiple)
// Spawn a dedicated nydusd for each RAFS instance.
DaemonModeDedicated DaemonMode = DaemonMode(constant.DaemonModeDedicated)
// Share a global nydusd to serve all RAFS instances.
DaemonModeShared DaemonMode = DaemonMode(constant.DaemonModeShared)
// Do not spawn nydusd for RAFS instances.
//
// For tarfs and rund, there's no need to create nydusd to serve RAFS instances,
// the snapshotter just returns mount slices with additional information for runC/runD
// to manage those snapshots.
DaemonModeNone DaemonMode = DaemonMode(constant.DaemonModeNone)
DaemonModeInvalid DaemonMode = DaemonMode(constant.DaemonModeInvalid)
// MaxRootPathLen defines the maximum allowed length of the root portion of a Unix domain socket path.
//
// This value is calculated based on the hard length limit of `sun_path` on Linux systems (108 bytes).
// Nydusd's socket path format is "${rootPath}/socket/${xid}/api?.sock".
// - The length of the fixed part "/socket/${xid}/api?.sock" is 38 bytes.
//
// Since the maximum upper limit of the total path length is 108 bytes, in order to avoid exceeding the limit, the maximum allowed length of rootPath is:
// 108 - len("/socket/${xid}/api?.sock") = 108 - 38 = 70.
// Therefore, we must set the effective maximum length of the root path to 70 bytes.
MaxRootPathLen = 70
)
func parseDaemonMode(m string) (DaemonMode, error) {
switch m {
case string(DaemonModeMultiple):
return DaemonModeDedicated, nil
case string(DaemonModeDedicated):
return DaemonModeDedicated, nil
case string(DaemonModeShared):
return DaemonModeShared, nil
case string(DaemonModeNone):
return DaemonModeNone, nil
default:
return DaemonModeInvalid, errors.Errorf("invalid daemon mode %q", m)
}
}
type DaemonRecoverPolicy int
const (
RecoverPolicyInvalid DaemonRecoverPolicy = iota
RecoverPolicyNone
RecoverPolicyRestart
RecoverPolicyFailover
)
func (p DaemonRecoverPolicy) String() string {
switch p {
case RecoverPolicyNone:
return "none"
case RecoverPolicyRestart:
return "restart"
case RecoverPolicyFailover:
return "failover"
case RecoverPolicyInvalid:
fallthrough
default:
return ""
}
}
var recoverPolicyParser map[string]DaemonRecoverPolicy
func ParseRecoverPolicy(p string) (DaemonRecoverPolicy, error) {
policy, ok := recoverPolicyParser[p]
if !ok {
return RecoverPolicyInvalid, errors.Errorf("invalid recover policy %q", p)
}
return policy, nil
}
const (
FsDriverBlockdev string = constant.FsDriverBlockdev
FsDriverFusedev string = constant.FsDriverFusedev
FsDriverFscache string = constant.FsDriverFscache
FsDriverNodev string = constant.FsDriverNodev
FsDriverProxy string = constant.FsDriverProxy
)
const (
FailoverPolicyNone string = constant.FailoverPolicyNone
FailoverPolicyResend string = constant.FailoverPolicyResend
FailoverPolicyFlush string = constant.FailoverPolicyFlush
)
type Experimental struct {
EnableStargz bool `toml:"enable_stargz"`
EnableReferrerDetect bool `toml:"enable_referrer_detect"`
EnableIndexDetect bool `toml:"enable_index_detect"`
TarfsConfig TarfsConfig `toml:"tarfs"`
EnableBackendSource bool `toml:"enable_backend_source"`
}
type TarfsConfig struct {
EnableTarfs bool `toml:"enable_tarfs"`
MountTarfsOnHost bool `toml:"mount_tarfs_on_host"`
TarfsHint bool `toml:"tarfs_hint"`
MaxConcurrentProc int `toml:"max_concurrent_proc"`
ExportMode string `toml:"export_mode"`
}
type CgroupConfig struct {
Enable bool `toml:"enable"`
MemoryLimit string `toml:"memory_limit"`
}
// Configure how to start and recover nydusd daemons
type DaemonConfig struct {
NydusdPath string `toml:"nydusd_path"`
NydusdConfigPath string `toml:"nydusd_config"`
NydusImagePath string `toml:"nydusimage_path"`
RecoverPolicy string `toml:"recover_policy"`
FsDriver string `toml:"fs_driver"`
ThreadsNumber int `toml:"threads_number"`
LogRotationSize int `toml:"log_rotation_size"`
FailoverPolicy string `toml:"failover_policy"`
}
type LoggingConfig struct {
LogToStdout bool `toml:"log_to_stdout"`
LogLevel string `toml:"level"`
LogDir string `toml:"dir"`
RotateLogMaxSize int `toml:"log_rotation_max_size"`
RotateLogMaxBackups int `toml:"log_rotation_max_backups"`
RotateLogMaxAge int `toml:"log_rotation_max_age"`
RotateLogLocalTime bool `toml:"log_rotation_local_time"`
RotateLogCompress bool `toml:"log_rotation_compress"`
}
// Nydus image layers additional process
type ImageConfig struct {
PublicKeyFile string `toml:"public_key_file"`
ValidateSignature bool `toml:"validate_signature"`
}
// Configure containerd snapshots interfaces and how to process the snapshots
// requests from containerd
type SnapshotConfig struct {
EnableNydusOverlayFS bool `toml:"enable_nydus_overlayfs"`
NydusOverlayFSPath string `toml:"nydus_overlayfs_path"`
EnableKataVolume bool `toml:"enable_kata_volume"`
SyncRemove bool `toml:"sync_remove"`
// EnableOverlayfsVolatile globally enables the "volatile" overlayfs mount option
// on all writable snapshots. This skips sync on the upper layer, improving
// write performance for ephemeral container filesystems at the cost of crash consistency.
EnableOverlayfsVolatile bool `toml:"enable_overlayfs_volatile"`
}
// Configure cache manager that manages the cache files lifecycle
type CacheManagerConfig struct {
Disable bool `toml:"disable"`
// Trigger GC gc_period after the specified period.
// Example format: 24h, 120min
GCPeriod time.Duration `toml:"gc_period"`
CacheDir string `toml:"cache_dir"`
}
// Configure how nydus-snapshotter receive auth information
type AuthConfig struct {
// based on kubeconfig or ServiceAccount
EnableKubeconfigKeychain bool `toml:"enable_kubeconfig_keychain"`
KubeconfigPath string `toml:"kubeconfig_path"`
// CRI proxy mode
EnableCRIKeychain bool `toml:"enable_cri_keychain"`
ImageServiceAddress string `toml:"image_service_address"`
// Kubelet credential provider plugins
EnableKubeletCredentialProviders bool `toml:"enable_kubelet_credential_providers"`
CredentialProviderConfig string `toml:"credential_provider_config"`
CredentialProviderBinDir string `toml:"credential_provider_bin_dir"`
// Periodic credential renewal interval. When set to a positive duration,
// the snapshotter caches credentials from configured renewable providers and
// refreshes them at this interval. Set to 0 (default) to disable.
CredentialRenewalInterval time.Duration `toml:"credential_renewal_interval"`
}
// Configure remote storage like container registry
type RemoteConfig struct {
AuthConfig AuthConfig `toml:"auth"`
ConvertVpcRegistry bool `toml:"convert_vpc_registry"`
SkipSSLVerify bool `toml:"skip_ssl_verify"`
MirrorsConfig MirrorsConfig `toml:"mirrors_config"`
}
type MirrorsConfig struct {
Dir string `toml:"dir"`
}
type MetricsConfig struct {
// Address is the network address for the metrics server. Empty indicates the server is disabled.
Address string `toml:"address"`
// HungIOInterval defines the timeout for a single I/O operation to be considered "hung".
HungIOInterval time.Duration `toml:"hung_io_interval"`
// CollectInterval defines how often metrics are collected and reported.
CollectInterval time.Duration `toml:"collect_interval"`
}
type DebugConfig struct {
ProfileDuration int64 `toml:"daemon_cpu_profile_duration_secs"`
PprofAddress string `toml:"pprof_address"`
}
type SystemControllerConfig struct {
Enable bool `toml:"enable"`
Address string `toml:"address"`
// UID to set on the system controller socket
UID int `toml:"uid"`
// GID to set on the system controller socket
GID int `toml:"gid"`
DebugConfig DebugConfig `toml:"debug"`
}
type SnapshotterConfig struct {
// Configuration format version
Version int `toml:"version"`
// Snapshotter's root work directory
Root string `toml:"root"`
Address string `toml:"address"`
// UID to set on the snapshotter socket
UID int `toml:"uid"`
// GID to set on the snapshotter socket
GID int `toml:"gid"`
DaemonMode string `toml:"daemon_mode"`
// Clean up all the resources when snapshotter is closed
CleanupOnClose bool `toml:"cleanup_on_close"`
SystemControllerConfig SystemControllerConfig `toml:"system"`
MetricsConfig MetricsConfig `toml:"metrics"`
DaemonConfig DaemonConfig `toml:"daemon"`
SnapshotsConfig SnapshotConfig `toml:"snapshot"`
RemoteConfig RemoteConfig `toml:"remote"`
ImageConfig ImageConfig `toml:"image"`
CacheManagerConfig CacheManagerConfig `toml:"cache_manager"`
LoggingConfig LoggingConfig `toml:"log"`
CgroupConfig CgroupConfig `toml:"cgroup"`
Experimental Experimental `toml:"experimental"`
}
func LoadSnapshotterConfig(path string) (*SnapshotterConfig, error) {
var config SnapshotterConfig
// get nydus-snapshotter configuration from specified path of toml file
if path == "" {
return nil, errors.New("snapshotter configuration path cannot be empty")
}
tree, err := toml.LoadFile(path)
if err != nil {
return nil, errors.Wrapf(err, "load toml configuration from file %q", path)
}
if err = tree.Unmarshal(&config); err != nil {
return nil, errors.Wrap(err, "unmarshal snapshotter configuration")
}
if config.Version != 1 {
return nil, errors.Errorf("unsupported configuration version %d", config.Version)
}
return &config, nil
}
func MergeConfig(to, from *SnapshotterConfig) error {
err := mergo.Merge(to, from)
if err != nil {
return err
}
return nil
}
func ValidateConfig(c *SnapshotterConfig) error {
if c == nil {
return errors.Wrapf(errdefs.ErrInvalidArgument, "configuration is none")
}
if c.ImageConfig.ValidateSignature {
if c.ImageConfig.PublicKeyFile == "" {
return errors.New("public key file for signature validation is not provided")
} else if _, err := os.Stat(c.ImageConfig.PublicKeyFile); err != nil {
return errors.Wrapf(err, "check publicKey file %q", c.ImageConfig.PublicKeyFile)
}
}
rootPathLen := len(c.Root)
if rootPathLen == 0 {
return errors.New("empty root directory")
}
if rootPathLen > MaxRootPathLen {
return errors.Errorf("root directory path is too long: %d bytes, max is %d bytes", rootPathLen, MaxRootPathLen)
}
if c.DaemonConfig.FsDriver != FsDriverFscache && c.DaemonConfig.FsDriver != FsDriverFusedev &&
c.DaemonConfig.FsDriver != FsDriverBlockdev && c.DaemonConfig.FsDriver != FsDriverNodev &&
c.DaemonConfig.FsDriver != FsDriverProxy {
return errors.Errorf("invalid filesystem driver %q", c.DaemonConfig.FsDriver)
}
if _, err := ParseRecoverPolicy(c.DaemonConfig.RecoverPolicy); err != nil {
return err
}
if c.DaemonConfig.ThreadsNumber > 1024 {
return errors.Errorf("nydusd worker thread number %d is too big, max 1024", c.DaemonConfig.ThreadsNumber)
}
if c.DaemonConfig.FailoverPolicy != FailoverPolicyNone &&
c.DaemonConfig.FailoverPolicy != FailoverPolicyResend &&
c.DaemonConfig.FailoverPolicy != FailoverPolicyFlush {
return errors.Errorf("invalid failover policy %q", c.DaemonConfig.FailoverPolicy)
}
if c.RemoteConfig.AuthConfig.EnableCRIKeychain && c.RemoteConfig.AuthConfig.EnableKubeconfigKeychain {
return errors.Wrapf(errdefs.ErrInvalidArgument,
"\"enable_cri_keychain\" and \"enable_kubeconfig_keychain\" can't be set at the same time")
}
if c.RemoteConfig.MirrorsConfig.Dir != "" {
dirExisted, err := file.IsDirExisted(c.RemoteConfig.MirrorsConfig.Dir)
if err != nil {
return err
}
if !dirExisted {
return errors.Errorf("mirrors config directory %s does not exist", c.RemoteConfig.MirrorsConfig.Dir)
}
}
return nil
}
// Parse command line arguments and fill the nydus-snapshotter configuration
// Always let options from CLI override those from configuration file.
func ParseParameters(args *flags.Args, cfg *SnapshotterConfig) error {
// --- essential configuration
if args.Address != "" {
cfg.Address = args.Address
}
if args.RootDir != "" {
cfg.Root = args.RootDir
}
// Give --shared-daemon higher priority
if args.DaemonMode != "" {
cfg.DaemonMode = args.DaemonMode
}
// --- image processor configuration
// empty
// --- daemon configuration
daemonConfig := &cfg.DaemonConfig
if args.NydusdConfigPath != "" {
daemonConfig.NydusdConfigPath = args.NydusdConfigPath
}
if args.NydusdPath != "" {
daemonConfig.NydusdPath = args.NydusdPath
}
if args.NydusImagePath != "" {
daemonConfig.NydusImagePath = args.NydusImagePath
}
if args.FsDriver != "" {
daemonConfig.FsDriver = args.FsDriver
}
// --- cache manager configuration
// empty
// --- logging configuration
logConfig := &cfg.LoggingConfig
if args.LogLevel != "" {
logConfig.LogLevel = args.LogLevel
}
if args.LogToStdoutCount > 0 {
logConfig.LogToStdout = args.LogToStdout
}
// --- remote storage configuration
// empty
// --- snapshot configuration
if args.NydusOverlayFSPath != "" {
cfg.SnapshotsConfig.NydusOverlayFSPath = args.NydusOverlayFSPath
}
// --- metrics configuration
// empty
return nil
}
func ParseCgroupConfig(config CgroupConfig) (cgroup.Config, error) {
totalMemory, err := sysinfo.GetTotalMemoryBytes()
if err != nil {
return cgroup.Config{}, errors.Wrap(err, "Failed to get total memory bytes")
}
memoryLimitInBytes, err := parser.MemoryConfigToBytes(config.MemoryLimit, totalMemory)
if err != nil {
return cgroup.Config{}, err
}
return cgroup.Config{
MemoryLimitInBytes: memoryLimitInBytes,
}, nil
}
================================================
FILE: config/config_test.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package config
import (
"path/filepath"
"testing"
"github.com/containerd/nydus-snapshotter/internal/constant"
"github.com/containerd/nydus-snapshotter/internal/flags"
"github.com/stretchr/testify/assert"
)
func TestLoadSnapshotterTOMLConfig(t *testing.T) {
A := assert.New(t)
cfg, err := LoadSnapshotterConfig("../misc/snapshotter/config.toml")
A.NoError(err)
exampleConfig := SnapshotterConfig{
Version: 1,
Root: "/var/lib/containerd/io.containerd.snapshotter.v1.nydus",
Address: "/run/containerd-nydus/containerd-nydus-grpc.sock",
UID: 0,
GID: 0,
DaemonMode: "dedicated",
Experimental: Experimental{
EnableStargz: false,
EnableReferrerDetect: false,
EnableIndexDetect: false,
},
CleanupOnClose: false,
SystemControllerConfig: SystemControllerConfig{
Enable: true,
Address: "/run/containerd-nydus/system.sock",
UID: 0,
GID: 0,
DebugConfig: DebugConfig{
ProfileDuration: 5,
PprofAddress: "",
},
},
DaemonConfig: DaemonConfig{
NydusdPath: "/usr/local/bin/nydusd",
NydusImagePath: "/usr/local/bin/nydus-image",
FsDriver: "fusedev",
RecoverPolicy: "failover",
NydusdConfigPath: "/etc/nydus/nydusd-config.fusedev.json",
ThreadsNumber: 4,
LogRotationSize: 100,
FailoverPolicy: "resend",
},
SnapshotsConfig: SnapshotConfig{
EnableNydusOverlayFS: false,
NydusOverlayFSPath: "nydus-overlayfs",
SyncRemove: false,
},
RemoteConfig: RemoteConfig{
ConvertVpcRegistry: false,
AuthConfig: AuthConfig{
EnableKubeconfigKeychain: false,
KubeconfigPath: "",
},
MirrorsConfig: MirrorsConfig{
Dir: "",
},
},
ImageConfig: ImageConfig{
PublicKeyFile: "",
ValidateSignature: false,
},
CacheManagerConfig: CacheManagerConfig{
Disable: false,
GCPeriod: constant.DefaultGCPeriod,
CacheDir: "",
},
LoggingConfig: LoggingConfig{
LogLevel: "info",
RotateLogCompress: true,
RotateLogLocalTime: true,
RotateLogMaxAge: 7,
RotateLogMaxBackups: 5,
RotateLogMaxSize: 100,
LogToStdout: false,
},
MetricsConfig: MetricsConfig{
Address: ":9110",
HungIOInterval: constant.DefaultHungIOInterval,
CollectInterval: constant.DefaultCollectInterval,
},
CgroupConfig: CgroupConfig{
Enable: true,
MemoryLimit: "",
},
}
A.EqualValues(cfg, &exampleConfig)
args := flags.Args{}
args.RootDir = "/var/lib/containerd/nydus"
exampleConfig.Root = "/var/lib/containerd/nydus"
err = ParseParameters(&args, cfg)
A.NoError(err)
A.EqualValues(cfg, &exampleConfig)
A.EqualValues(cfg.LoggingConfig.LogToStdout, false)
args.LogToStdout = true
args.LogToStdoutCount = 1
err = ParseParameters(&args, cfg)
A.NoError(err)
A.EqualValues(cfg.LoggingConfig.LogToStdout, true)
err = ProcessConfigurations(cfg)
A.NoError(err)
A.Equal(cfg.CacheManagerConfig.GCPeriod, constant.DefaultGCPeriod)
A.Equal(cfg.MetricsConfig.HungIOInterval, constant.DefaultHungIOInterval)
A.Equal(cfg.MetricsConfig.CollectInterval, constant.DefaultCollectInterval)
}
func TestSnapshotterConfig(t *testing.T) {
A := assert.New(t)
var cfg SnapshotterConfig
var args flags.Args
// The log_to_stdout is false in toml file without --log-to-stdout flag.
// Expected false.
cfg.LoggingConfig.LogToStdout = false
args.LogToStdoutCount = 0
err := ParseParameters(&args, &cfg)
A.NoError(err)
A.EqualValues(cfg.LoggingConfig.LogToStdout, false)
// The log_to_stdout is true in toml file without --log-to-stdout flag.
// Expected true.
// This case is failed.
cfg.LoggingConfig.LogToStdout = true
args.LogToStdoutCount = 0
err = ParseParameters(&args, &cfg)
A.NoError(err)
A.EqualValues(cfg.LoggingConfig.LogToStdout, true)
// The log_to_stdout is false in toml file with --log-to-stdout=true.
// Expected true (command flag has higher priority).
args.LogToStdout = true
args.LogToStdoutCount = 1
cfg.LoggingConfig.LogToStdout = false
err = ParseParameters(&args, &cfg)
A.NoError(err)
A.EqualValues(cfg.LoggingConfig.LogToStdout, true)
// The log_to_stdout is true in toml file with --log-to-stdout=true.
// Expected true (command flag has higher priority).
args.LogToStdout = true
args.LogToStdoutCount = 1
cfg.LoggingConfig.LogToStdout = true
err = ParseParameters(&args, &cfg)
A.NoError(err)
A.EqualValues(cfg.LoggingConfig.LogToStdout, true)
// The log_to_stdout is false in toml file with --log-to-stdout=false.
// Expected false (command flag has higher priority).
args.LogToStdout = false
args.LogToStdoutCount = 1
cfg.LoggingConfig.LogToStdout = false
err = ParseParameters(&args, &cfg)
A.NoError(err)
A.EqualValues(cfg.LoggingConfig.LogToStdout, false)
// The log_to_stdout is true in toml file with --log-to-stdout=false.
// Expected false (command flag has higher priority).
args.LogToStdout = false
args.LogToStdoutCount = 1
cfg.LoggingConfig.LogToStdout = true
err = ParseParameters(&args, &cfg)
A.NoError(err)
A.EqualValues(cfg.LoggingConfig.LogToStdout, false)
}
func TestMergeConfig(t *testing.T) {
A := assert.New(t)
var defaultSnapshotterConfig SnapshotterConfig
var snapshotterConfig1 SnapshotterConfig
err := defaultSnapshotterConfig.FillUpWithDefaults()
A.NoError(err)
err = MergeConfig(&snapshotterConfig1, &defaultSnapshotterConfig)
A.NoError(err)
A.Equal(snapshotterConfig1.Root, constant.DefaultRootDir)
A.Equal(snapshotterConfig1.LoggingConfig.LogDir, "")
A.Equal(snapshotterConfig1.CacheManagerConfig.CacheDir, "")
A.Equal(snapshotterConfig1.DaemonMode, constant.DefaultDaemonMode)
A.Equal(snapshotterConfig1.SystemControllerConfig.Address, constant.DefaultSystemControllerAddress)
A.Equal(snapshotterConfig1.LoggingConfig.LogLevel, constant.DefaultLogLevel)
A.Equal(snapshotterConfig1.LoggingConfig.RotateLogMaxSize, constant.DefaultRotateLogMaxSize)
A.Equal(snapshotterConfig1.LoggingConfig.RotateLogMaxBackups, constant.DefaultRotateLogMaxBackups)
A.Equal(snapshotterConfig1.LoggingConfig.RotateLogMaxAge, constant.DefaultRotateLogMaxAge)
A.Equal(snapshotterConfig1.LoggingConfig.RotateLogCompress, constant.DefaultRotateLogCompress)
A.Equal(snapshotterConfig1.DaemonConfig.NydusdConfigPath, constant.DefaultNydusDaemonConfigPath)
A.Equal(snapshotterConfig1.DaemonConfig.RecoverPolicy, RecoverPolicyRestart.String())
A.Equal(snapshotterConfig1.CacheManagerConfig.GCPeriod, constant.DefaultGCPeriod)
A.Equal(snapshotterConfig1.MetricsConfig.HungIOInterval, constant.DefaultHungIOInterval)
A.Equal(snapshotterConfig1.MetricsConfig.CollectInterval, constant.DefaultCollectInterval)
var snapshotterConfig2 SnapshotterConfig
snapshotterConfig2.Root = "/snapshotter/root"
err = MergeConfig(&snapshotterConfig2, &defaultSnapshotterConfig)
A.NoError(err)
A.Equal(snapshotterConfig2.Root, "/snapshotter/root")
A.Equal(snapshotterConfig2.LoggingConfig.LogDir, "")
A.Equal(snapshotterConfig2.CacheManagerConfig.CacheDir, "")
}
func TestProcessConfigurations(t *testing.T) {
A := assert.New(t)
var defaultSnapshotterConfig SnapshotterConfig
var snapshotterConfig1 SnapshotterConfig
err := defaultSnapshotterConfig.FillUpWithDefaults()
A.NoError(err)
err = MergeConfig(&snapshotterConfig1, &defaultSnapshotterConfig)
A.NoError(err)
err = ValidateConfig(&snapshotterConfig1)
A.NoError(err)
PrepareLogDir(&snapshotterConfig1)
err = ProcessConfigurations(&snapshotterConfig1)
A.NoError(err)
A.Equal(snapshotterConfig1.LoggingConfig.LogDir, filepath.Join(snapshotterConfig1.Root, "logs"))
A.Equal(snapshotterConfig1.CacheManagerConfig.CacheDir, filepath.Join(snapshotterConfig1.Root, "cache"))
var snapshotterConfig2 SnapshotterConfig
snapshotterConfig2.Root = "/snapshotter/root"
err = MergeConfig(&snapshotterConfig2, &defaultSnapshotterConfig)
A.NoError(err)
err = ValidateConfig(&snapshotterConfig2)
A.NoError(err)
PrepareLogDir(&snapshotterConfig2)
err = ProcessConfigurations(&snapshotterConfig2)
A.NoError(err)
A.Equal(snapshotterConfig2.LoggingConfig.LogDir, filepath.Join(snapshotterConfig2.Root, "logs"))
A.Equal(snapshotterConfig2.CacheManagerConfig.CacheDir, filepath.Join(snapshotterConfig2.Root, "cache"))
var snapshotterConfig3 SnapshotterConfig
snapshotterConfig3.Root = "./snapshotter/root"
err = MergeConfig(&snapshotterConfig3, &defaultSnapshotterConfig)
A.NoError(err)
err = ValidateConfig(&snapshotterConfig3)
A.NoError(err)
err = ProcessConfigurations(&snapshotterConfig3)
A.NoError(err)
var snapshotterConfig4 SnapshotterConfig
oversizedPath := "/path/to/very/long/root/directory/that/exceed/the/max/nydus-snapshotter"
A.Equal(MaxRootPathLen+1, len(oversizedPath))
snapshotterConfig4.Root = oversizedPath
err = MergeConfig(&snapshotterConfig4, &defaultSnapshotterConfig)
A.NoError(err)
err = ValidateConfig(&snapshotterConfig4)
A.Error(err)
}
================================================
FILE: config/daemonconfig/daemonconfig.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package daemonconfig
import (
"encoding/json"
"io"
"net/http"
"net/url"
"os"
"reflect"
"strings"
"time"
"github.com/containerd/log"
"github.com/pkg/errors"
"github.com/containerd/nydus-snapshotter/config"
"github.com/containerd/nydus-snapshotter/pkg/auth"
"github.com/containerd/nydus-snapshotter/pkg/utils/registry"
)
type StorageBackendType = string
const (
backendTypeLocalfs StorageBackendType = "localfs"
backendTypeOss StorageBackendType = "oss"
backendTypeRegistry StorageBackendType = "registry"
backendTypeS3 StorageBackendType = "s3"
)
type DaemonConfig interface {
// Provide stuffs relevant to accessing registry apart from auth
Supplement(host, repo, snapshotID string, params map[string]string)
// Provide auth
FillAuth(kc *auth.PassKeyChain)
StorageBackend() (StorageBackendType, *BackendConfig)
DumpString() (string, error)
DumpFile(path string) error
}
// Daemon configurations factory
func NewDaemonConfig(fsDriver, path string) (DaemonConfig, error) {
switch fsDriver {
case config.FsDriverFscache:
cfg, err := LoadFscacheConfig(path)
if err != nil {
return nil, err
}
return cfg, nil
case config.FsDriverFusedev:
cfg, err := LoadFuseConfig(path)
if err != nil {
return nil, err
}
return cfg, nil
default:
return nil, errors.Errorf("unsupported, fs driver %q", fsDriver)
}
}
type BackendConfig struct {
// Localfs backend configs
BlobFile string `json:"blob_file,omitempty"`
Dir string `json:"dir,omitempty"`
ReadAhead bool `json:"readahead"`
ReadAheadSec int `json:"readahead_sec,omitempty"`
// Registry backend configs
Host string `json:"host,omitempty"`
Repo string `json:"repo,omitempty"`
Auth string `json:"auth,omitempty" secret:"true"`
RegistryToken string `json:"registry_token,omitempty" secret:"true"`
BlobURLScheme string `json:"blob_url_scheme,omitempty"`
BlobRedirectedHost string `json:"blob_redirected_host,omitempty"`
// Shared by oss and s3 backend configs
EndPoint string `json:"endpoint,omitempty"`
AccessKeyID string `json:"access_key_id,omitempty" secret:"true"`
AccessKeySecret string `json:"access_key_secret,omitempty" secret:"true"`
BucketName string `json:"bucket_name,omitempty"`
ObjectPrefix string `json:"object_prefix,omitempty"`
// S3-specific config
Region string `json:"region,omitempty"`
// Shared by registry, oss, and s3
Scheme string `json:"scheme,omitempty"`
SkipVerify bool `json:"skip_verify,omitempty"`
CACertFiles []string `json:"ca_cert_files,omitempty"`
// Below configs are common configs shared by all backends
Proxy struct {
URL string `json:"url,omitempty"`
Fallback bool `json:"fallback"`
PingURL string `json:"ping_url,omitempty"`
CheckInterval int `json:"check_interval,omitempty"`
UseHTTP bool `json:"use_http,omitempty"`
} `json:"proxy,omitempty"`
Timeout int `json:"timeout,omitempty"`
ConnectTimeout int `json:"connect_timeout,omitempty"`
RetryLimit int `json:"retry_limit,omitempty"`
}
type DeviceConfig struct {
ID string `json:"id,omitempty"`
Backend struct {
BackendType string `json:"type"`
Config BackendConfig `json:"config"`
} `json:"backend"`
Cache struct {
CacheType string `json:"type"`
Compressed bool `json:"compressed,omitempty"`
Config struct {
WorkDir string `json:"work_dir"`
DisableIndexedMap bool `json:"disable_indexed_map"`
} `json:"config"`
} `json:"cache"`
}
// For nydusd as FUSE daemon. Serialize Daemon info and persist to a json file
// We don't have to persist configuration file for fscache since its configuration
// is passed through HTTP API.
func DumpConfigFile(c interface{}, path string) error {
if config.IsBackendSourceEnabled() {
c = serializeWithSecretFilter(c)
}
b, err := json.Marshal(c)
if err != nil {
return errors.Wrapf(err, "marshal config")
}
return os.WriteFile(path, b, 0600)
}
func DumpConfigString(c interface{}) (string, error) {
b, err := json.Marshal(c)
return string(b), err
}
// Achieve a daemon configuration from template or snapshotter's configuration
func SupplementDaemonConfig(c DaemonConfig, imageID, snapshotID string,
vpcRegistry bool, labels map[string]string, params map[string]string) error {
image, err := registry.ParseImage(imageID)
if err != nil {
return errors.Wrapf(err, "parse image %s", imageID)
}
backendType, _ := c.StorageBackend()
switch backendType {
case backendTypeRegistry:
registryHost := image.Host
if vpcRegistry {
registryHost = registry.ConvertToVPCHost(registryHost)
} else if registryHost == "docker.io" {
// For docker.io images, we should use index.docker.io
registryHost = "index.docker.io"
}
effectiveScheme, effectiveHost, caCerts := selectMirrorHost(config.GetMirrorsConfigDir(), registryHost)
// No mirror configured use the original registry host
if effectiveHost == "" {
effectiveHost = registryHost
}
// If no auth is provided, don't touch auth from provided nydusd configuration file.
// We don't validate the original nydusd auth from configuration file since it can be empty
// when repository is public.
keyChain := auth.GetRegistryKeyChain(imageID, labels)
c.Supplement(effectiveHost, image.Repo, snapshotID, params)
c.FillAuth(keyChain)
_, bc := c.StorageBackend()
if len(caCerts) > 0 {
bc.CACertFiles = caCerts
}
if effectiveScheme != "" {
bc.Scheme = effectiveScheme
}
// For Localfs, OSS, and S3 backends, only the WorkDir needs to be supplemented.
case backendTypeLocalfs, backendTypeOss, backendTypeS3:
c.Supplement("", "", snapshotID, params)
default:
return errors.Errorf("unknown backend type %s", backendType)
}
return nil
}
// selectMirrorHost loads mirror configs for the given registry host and returns the host and
// scheme of the first reachable mirror. If a mirror has no PingURL it is used unconditionally.
// Falls back to (registryHost, "") when no mirror is configured or reachable.
func selectMirrorHost(mirrorsConfigDir, registryHost string) (scheme string, host string, caCerts []string) {
mirrors, caCerts, err := LoadMirrorsConfig(mirrorsConfigDir, registryHost)
if err != nil {
log.L.Warnf("Failed to load mirrors config for %s: %v, falling back to origin", registryHost, err)
return "", registryHost, nil
}
client := &http.Client{Timeout: 3 * time.Second}
for _, mirror := range mirrors {
scheme, host, err = splitMirrorURL(mirror.Host)
if err != nil {
log.L.Warnf("Skipping due to Failing to split mirror host %s: %v", mirror.Host, err)
continue
}
if mirror.PingURL == "" {
return scheme, host, caCerts
}
resp, pingErr := client.Get(mirror.PingURL)
if pingErr == nil {
resp.Body.Close()
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
return scheme, host, caCerts
}
}
if resp != nil {
pingBody, _ := io.ReadAll(resp.Body)
log.L.Warnf("Mirror %s ping URL %s check failed with error %v, statusCode %d, response '%s', trying next mirror",
mirror.Host,
mirror.PingURL,
err,
resp.StatusCode,
string(pingBody),
)
} else {
log.L.Warnf("Mirror %s ping URL %s check failed with error %v, trying next mirror",
mirror.Host,
mirror.PingURL,
err,
)
}
}
return "", registryHost, nil
}
// splitMirrorURL splits a mirror host URL (e.g. "http://mirror:5000") into scheme and bare host.
// Scheme is forced to be https if not present.
func splitMirrorURL(mirrorHost string) (scheme, host string, err error) {
// url.Parse requires a scheme to properly works even if it doesn't returns an error
if !strings.HasPrefix(mirrorHost, "http://") && !strings.HasPrefix(mirrorHost, "https://") {
mirrorHost = "https://" + mirrorHost
}
value, err := url.Parse(mirrorHost)
if err != nil {
return "", "", err
}
return value.Scheme, value.Host, nil
}
func serializeWithSecretFilter(obj interface{}) map[string]interface{} {
result := make(map[string]interface{})
value := reflect.ValueOf(obj)
typeOfObj := reflect.TypeOf(obj)
if value.Kind() == reflect.Ptr {
value = value.Elem()
typeOfObj = typeOfObj.Elem()
}
for i := 0; i < value.NumField(); i++ {
field := value.Field(i)
fieldType := typeOfObj.Field(i)
secretTag := fieldType.Tag.Get("secret")
jsonTags := strings.Split(fieldType.Tag.Get("json"), ",")
omitemptyTag := false
for _, tag := range jsonTags {
if tag == "omitempty" {
omitemptyTag = true
break
}
}
if secretTag == "true" {
continue
}
if field.Kind() == reflect.Ptr && field.IsNil() {
continue
}
if omitemptyTag && reflect.DeepEqual(reflect.Zero(field.Type()).Interface(), field.Interface()) {
continue
}
//nolint:exhaustive
switch fieldType.Type.Kind() {
case reflect.Struct:
result[jsonTags[0]] = serializeWithSecretFilter(field.Interface())
case reflect.Ptr:
if fieldType.Type.Elem().Kind() == reflect.Struct {
result[jsonTags[0]] = serializeWithSecretFilter(field.Elem().Interface())
} else {
result[jsonTags[0]] = field.Elem().Interface()
}
default:
result[jsonTags[0]] = field.Interface()
}
}
return result
}
================================================
FILE: config/daemonconfig/daemonconfig_test.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package daemonconfig
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
)
func TestLoadConfig(t *testing.T) {
buf := []byte(`{
"device": {
"backend": {
"type": "registry",
"config": {
"skip_verify": true,
"host": "acr-nydus-registry-vpc.cn-hangzhou.cr.aliyuncs.com",
"repo": "test/myserver",
"auth": "",
"blob_url_scheme": "http",
"proxy": {
"url": "http://p2p-proxy:65001",
"fallback": true,
"ping_url": "http://p2p-proxy:40901/server/ping",
"check_interval": 5
},
"timeout": 5,
"connect_timeout": 5,
"retry_limit": 0
}
},
"cache": {
"type": "blobcache",
"config": {
"work_dir": "/cache"
}
}
},
"mode": "direct",
"digest_validate": true,
"iostats_files": true,
"enable_xattr": true,
"amplify_io": 1048576,
"fs_prefetch": {
"enable": true,
"threads_count": 10,
"merging_size": 131072
}
}`)
var cfg FuseDaemonConfig
err := json.Unmarshal(buf, &cfg)
require.Nil(t, err)
require.Equal(t, cfg.Enable, true)
require.Equal(t, cfg.MergingSize, 131072)
require.Equal(t, cfg.ThreadsCount, 10)
require.Equal(t, cfg.Device.Backend.Config.BlobURLScheme, "http")
require.Equal(t, cfg.Device.Backend.Config.SkipVerify, true)
require.Equal(t, cfg.Device.Backend.Config.Proxy.CheckInterval, 5)
}
func TestAmplifyIo(t *testing.T) {
// Test non-zero value
input1 := []byte(`{"amplify_io": 1048576}`)
var cfg1 FuseDaemonConfig
err1 := json.Unmarshal(input1, &cfg1)
require.Nil(t, err1)
require.Equal(t, *cfg1.AmplifyIo, 1048576)
output1, _ := json.Marshal(cfg1)
require.Contains(t, string(output1), `"amplify_io":1048576`)
// Test zero value
input2 := []byte(`{"amplify_io": 0}`)
var cfg2 FuseDaemonConfig
err2 := json.Unmarshal(input2, &cfg2)
require.Nil(t, err2)
require.Equal(t, *cfg2.AmplifyIo, 0)
output2, _ := json.Marshal(cfg2)
require.Contains(t, string(output2), `"amplify_io":0`)
// Test nil value
input3 := []byte(`{}`)
var cfg3 FuseDaemonConfig
err3 := json.Unmarshal(input3, &cfg3)
require.Nil(t, err3)
require.Nil(t, cfg3.AmplifyIo)
output3, _ := json.Marshal(cfg3)
require.NotContains(t, string(output3), `amplify_io`)
}
func TestSerializeWithSecretFilter(t *testing.T) {
buf := []byte(`{
"device": {
"backend": {
"type": "registry",
"config": {
"skip_verify": true,
"host": "acr-nydus-registry-vpc.cn-hangzhou.cr.aliyuncs.com",
"repo": "test/myserver",
"auth": "token_token",
"blob_url_scheme": "http",
"proxy": {
"url": "http://p2p-proxy:65001",
"fallback": true,
"ping_url": "http://p2p-proxy:40901/server/ping",
"check_interval": 5
},
"timeout": 5,
"connect_timeout": 5,
"retry_limit": 0
}
},
"cache": {
"type": "blobcache",
"config": {
"work_dir": "/cache"
}
}
},
"mode": "direct",
"digest_validate": true,
"iostats_files": true,
"enable_xattr": true,
"amplify_io": 1048576,
"fs_prefetch": {
"enable": true,
"threads_count": 10,
"merging_size": 131072
}
}`)
var cfg FuseDaemonConfig
_ = json.Unmarshal(buf, &cfg)
filter := serializeWithSecretFilter(&cfg)
jsonData, err := json.Marshal(filter)
require.Nil(t, err)
var newCfg FuseDaemonConfig
err = json.Unmarshal(jsonData, &newCfg)
require.Nil(t, err)
require.Equal(t, newCfg.FSPrefetch, cfg.FSPrefetch)
require.Equal(t, newCfg.Device.Cache.CacheType, cfg.Device.Cache.CacheType)
require.Equal(t, newCfg.Device.Cache.Config, cfg.Device.Cache.Config)
require.Equal(t, newCfg.Mode, cfg.Mode)
require.Equal(t, newCfg.DigestValidate, cfg.DigestValidate)
require.Equal(t, newCfg.IOStatsFiles, cfg.IOStatsFiles)
require.Equal(t, newCfg.Device.Backend.Config.Host, cfg.Device.Backend.Config.Host)
require.Equal(t, newCfg.Device.Backend.Config.Repo, cfg.Device.Backend.Config.Repo)
require.Equal(t, newCfg.Device.Backend.Config.Proxy, cfg.Device.Backend.Config.Proxy)
require.Equal(t, newCfg.Device.Backend.Config.BlobURLScheme, cfg.Device.Backend.Config.BlobURLScheme)
require.Equal(t, newCfg.Device.Backend.Config.Auth, "")
require.NotEqual(t, newCfg.Device.Backend.Config.Auth, cfg.Device.Backend.Config.Auth)
require.NotNil(t, newCfg.AmplifyIo)
require.Equal(t, *newCfg.AmplifyIo, *cfg.AmplifyIo)
}
================================================
FILE: config/daemonconfig/fscache.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package daemonconfig
import (
"encoding/json"
"os"
"path"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/auth"
"github.com/containerd/nydus-snapshotter/pkg/utils/erofs"
"github.com/pkg/errors"
)
const (
WorkDir string = "workdir"
Bootstrap string = "bootstrap"
)
type BlobPrefetchConfig struct {
Enable bool `json:"enable"`
ThreadsCount int `json:"threads_count"`
MergingSize int `json:"merging_size"`
BandwidthRate int `json:"bandwidth_rate"`
PrefetchAll bool `json:"prefetch_all"`
}
type FscacheDaemonConfig struct {
// These fields is only for fscache daemon.
Type string `json:"type"`
// Snapshotter fills
ID string `json:"id"`
DomainID string `json:"domain_id"`
Config *struct {
ID string `json:"id"`
BackendType string `json:"backend_type"`
BackendConfig BackendConfig `json:"backend_config"`
CacheType string `json:"cache_type"`
// Snapshotter fills
CacheConfig struct {
WorkDir string `json:"work_dir"`
} `json:"cache_config"`
BlobPrefetchConfig BlobPrefetchConfig `json:"prefetch_config"`
MetadataPath string `json:"metadata_path"`
} `json:"config"`
}
// Load Fscache configuration template file
func LoadFscacheConfig(p string) (*FscacheDaemonConfig, error) {
var cfg FscacheDaemonConfig
b, err := os.ReadFile(p)
if err != nil {
return nil, errors.Wrapf(err, "read fscache configuration file %s", p)
}
if err = json.Unmarshal(b, &cfg); err != nil {
return nil, errors.Wrapf(err, "unmarshal")
}
if cfg.Config == nil {
return nil, errors.New("invalid fscache configuration")
}
return &cfg, nil
}
func (c *FscacheDaemonConfig) StorageBackend() (string, *BackendConfig) {
return c.Config.BackendType, &c.Config.BackendConfig
}
// Each fscache/erofs has a configuration with different fscache ID built from snapshot ID.
func (c *FscacheDaemonConfig) Supplement(host, repo, snapshotID string, params map[string]string) {
if host != "" {
c.Config.BackendConfig.Host = host
}
if repo != "" {
c.Config.BackendConfig.Repo = repo
}
fscacheID := erofs.FscacheID(snapshotID)
c.ID = fscacheID
if c.DomainID != "" {
log.L.Warnf("Linux Kernel Shared Domain feature in use. make sure your kernel version >= 6.1")
} else {
c.DomainID = fscacheID
}
c.Config.ID = fscacheID
if WorkDir, ok := params[WorkDir]; ok {
c.Config.CacheConfig.WorkDir = WorkDir
}
if bootstrap, ok := params[Bootstrap]; ok {
c.Config.MetadataPath = bootstrap
}
}
func (c *FscacheDaemonConfig) FillAuth(kc *auth.PassKeyChain) {
if kc != nil {
if kc.TokenBase() {
c.Config.BackendConfig.RegistryToken = kc.Password
} else {
c.Config.BackendConfig.Auth = kc.ToBase64()
}
}
}
func (c *FscacheDaemonConfig) DumpString() (string, error) {
return DumpConfigString(c)
}
func (c *FscacheDaemonConfig) DumpFile(f string) error {
if err := os.MkdirAll(path.Dir(f), 0755); err != nil {
return err
}
return DumpConfigFile(c, f)
}
================================================
FILE: config/daemonconfig/fuse.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package daemonconfig
import (
"encoding/json"
"os"
"path"
"github.com/pkg/errors"
"github.com/containerd/nydus-snapshotter/pkg/auth"
)
const CacheDir string = "cachedir"
// Used when nydusd works as a FUSE daemon or vhost-user-fs backend
type FuseDaemonConfig struct {
Device *DeviceConfig `json:"device"`
Mode string `json:"mode"`
DigestValidate bool `json:"digest_validate"`
IOStatsFiles bool `json:"iostats_files,omitempty"`
EnableXattr bool `json:"enable_xattr,omitempty"`
AccessPattern bool `json:"access_pattern,omitempty"`
LatestReadFiles bool `json:"latest_read_files,omitempty"`
AmplifyIo *int `json:"amplify_io,omitempty"`
FSPrefetch `json:"fs_prefetch,omitempty"`
// (experimental) The nydus daemon could cache more data to increase hit ratio when enabled the warmup feature.
Warmup uint64 `json:"warmup,omitempty"`
}
// Control how to perform prefetch from file system layer
type FSPrefetch struct {
Enable bool `json:"enable"`
PrefetchAll bool `json:"prefetch_all"`
ThreadsCount int `json:"threads_count,omitempty"`
MergingSize int `json:"merging_size,omitempty"`
BandwidthRate int `json:"bandwidth_rate,omitempty"`
}
// Load fuse daemon configuration from template file
func LoadFuseConfig(p string) (*FuseDaemonConfig, error) {
b, err := os.ReadFile(p)
if err != nil {
return nil, errors.Wrapf(err, "read FUSE configuration file %s", p)
}
var cfg FuseDaemonConfig
if err := json.Unmarshal(b, &cfg); err != nil {
return nil, errors.Wrapf(err, "unmarshal %s", p)
}
if cfg.Device == nil {
return nil, errors.New("invalid fuse daemon configuration")
}
return &cfg, nil
}
func (c *FuseDaemonConfig) Supplement(host, repo, snapshotID string, params map[string]string) {
if host != "" {
c.Device.Backend.Config.Host = host
}
if repo != "" {
c.Device.Backend.Config.Repo = repo
}
// Temporary fix while https://github.com/containerd/nydus-snapshotter/issues/712 is being addressed
if snapshotID != "" {
c.Device.ID = "/" + snapshotID
}
c.Device.Cache.Config.WorkDir = params[CacheDir]
}
func (c *FuseDaemonConfig) FillAuth(kc *auth.PassKeyChain) {
if kc != nil {
if kc.TokenBase() {
c.Device.Backend.Config.RegistryToken = kc.Password
} else {
c.Device.Backend.Config.Auth = kc.ToBase64()
}
}
}
func (c *FuseDaemonConfig) StorageBackend() (string, *BackendConfig) {
return c.Device.Backend.BackendType, &c.Device.Backend.Config
}
func (c *FuseDaemonConfig) DumpString() (string, error) {
return DumpConfigString(c)
}
func (c *FuseDaemonConfig) DumpFile(f string) error {
if err := os.MkdirAll(path.Dir(f), 0755); err != nil {
return err
}
return DumpConfigFile(c, f)
}
================================================
FILE: config/daemonconfig/mirror_select_test.go
================================================
/*
* Copyright (c) 2026. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package daemonconfig
import (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
)
var testRegistryHost = "fake-test.registry.com"
func writeMirrorHostsToml(t *testing.T, dir, content string) {
t.Helper()
hostDir := filepath.Join(dir, testRegistryHost)
require.NoError(t, os.MkdirAll(hostDir, 0755))
require.NoError(t, os.WriteFile(filepath.Join(hostDir, "hosts.toml"), []byte(content), 0600))
}
func TestSplitMirrorURL(t *testing.T) {
cases := []struct {
name string
input string
expectedScheme string
expectedHost string
expectErr bool
}{
{
name: "http with port",
input: "http://mirror:5000",
expectedScheme: "http",
expectedHost: "mirror:5000",
},
{
name: "https without port",
input: "https://mirror.example.com",
expectedScheme: "https",
expectedHost: "mirror.example.com",
},
{
name: "no scheme, host only",
input: "mirror.example.com",
expectedScheme: "https",
expectedHost: "mirror.example.com",
},
{
name: "no scheme, host with port",
input: "mirror:5000",
expectedScheme: "https",
expectedHost: "mirror:5000",
},
{
name: "https with port",
input: "https://mirror.example.com:5000",
expectedScheme: "https",
expectedHost: "mirror.example.com:5000",
},
{
name: "http with path",
input: "http://mirror.example.com/v2",
expectedScheme: "http",
expectedHost: "mirror.example.com",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
scheme, host, err := splitMirrorURL(tc.input)
if tc.expectErr {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, tc.expectedScheme, scheme, "scheme for %s", tc.input)
require.Equal(t, tc.expectedHost, host, "host for %s", tc.input)
})
}
}
func TestSelectMirrorHost_NoConfig(t *testing.T) {
scheme, host, _ := selectMirrorHost("", testRegistryHost)
require.Equal(t, testRegistryHost, host)
require.Equal(t, "", scheme)
}
func TestSelectMirrorHost_EmptyDir(t *testing.T) {
tmpDir := t.TempDir()
scheme, host, _ := selectMirrorHost(tmpDir, testRegistryHost)
require.Equal(t, testRegistryHost, host)
require.Equal(t, "", scheme)
}
func TestSelectMirrorHost_MirrorNoPingURL(t *testing.T) {
tmpDir := t.TempDir()
writeMirrorHostsToml(t, tmpDir, `
[host]
[host."http://mirror1:5000"]
`)
scheme, host, _ := selectMirrorHost(tmpDir, testRegistryHost)
require.Equal(t, "mirror1:5000", host)
require.Equal(t, "http", scheme)
}
func TestSelectMirrorHost_MirrorPingSucceeds(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
defer srv.Close()
tmpDir := t.TempDir()
writeMirrorHostsToml(t, tmpDir, `
[host]
[host."http://mirror1:5000"]
ping_url = "`+srv.URL+`"
`)
scheme, host, _ := selectMirrorHost(tmpDir, testRegistryHost)
require.Equal(t, "mirror1:5000", host)
require.Equal(t, "http", scheme)
}
func TestSelectMirrorHost_MirrorPingFails_FallbackToOrigin(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusServiceUnavailable)
}))
defer srv.Close()
tmpDir := t.TempDir()
writeMirrorHostsToml(t, tmpDir, `
[host]
[host."http://mirror1:5000"]
ping_url = "`+srv.URL+`"
`)
scheme, host, _ := selectMirrorHost(tmpDir, testRegistryHost)
require.Equal(t, testRegistryHost, host)
require.Equal(t, "", scheme)
}
func TestSelectMirrorHost_FirstMirrorFails_SecondMirrorNoPing(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusServiceUnavailable)
}))
defer srv.Close()
tmpDir := t.TempDir()
writeMirrorHostsToml(t, tmpDir, `
[host]
[host."http://mirror1:5000"]
ping_url = "`+srv.URL+`"
[host."https://mirror2.example.com"]
`)
scheme, host, _ := selectMirrorHost(tmpDir, testRegistryHost)
require.Equal(t, "mirror2.example.com", host)
require.Equal(t, "https", scheme)
}
================================================
FILE: config/daemonconfig/mirrors.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package daemonconfig
import (
"fmt"
"net/http"
"net/url"
"os"
"path/filepath"
"sort"
"strings"
"github.com/containerd/log"
"github.com/pelletier/go-toml"
"github.com/pkg/errors"
)
type MirrorConfig struct {
Host string
Headers map[string]string
HealthCheckInterval int
FailureLimit uint8
PingURL string
}
// Copied from containerd, for compatibility with containerd's toml configuration file.
type HostFileConfig struct {
Capabilities []string `toml:"capabilities"`
CACert interface{} `toml:"ca"`
Client interface{} `toml:"client"`
SkipVerify *bool `toml:"skip_verify"`
Header map[string]interface{} `toml:"header"`
OverridePath bool `toml:"override_path"`
// The following configuration items are specific to nydus.
HealthCheckInterval int `toml:"health_check_interval,omitempty"`
FailureLimit uint8 `toml:"failure_limit,omitempty"`
PingURL string `toml:"ping_url,omitempty"`
}
type hostConfig struct {
Scheme string
Host string
Header http.Header
CACerts []string
HealthCheckInterval int
FailureLimit uint8
PingURL string
}
func makeStringSlice(slice []interface{}, cb func(string) string) ([]string, error) {
out := make([]string, len(slice))
for i, value := range slice {
str, ok := value.(string)
if !ok {
return nil, fmt.Errorf("unable to cast %v to string", value)
}
if cb != nil {
out[i] = cb(str)
} else {
out[i] = str
}
}
return out, nil
}
func parseMirrorsConfig(hosts []hostConfig) []MirrorConfig {
var parsedMirrors = make([]MirrorConfig, len(hosts))
for i, host := range hosts {
parsedMirrors[i].Host = fmt.Sprintf("%s://%s", host.Scheme, host.Host)
parsedMirrors[i].HealthCheckInterval = host.HealthCheckInterval
parsedMirrors[i].FailureLimit = host.FailureLimit
parsedMirrors[i].PingURL = host.PingURL
if len(host.Header) > 0 {
mirrorHeader := make(map[string]string, len(host.Header))
for key, value := range host.Header {
if len(value) > 1 {
log.L.Warnf("some values of the header[%q] are omitted: %#v", key, value[1:])
}
mirrorHeader[key] = host.Header.Get(key)
}
parsedMirrors[i].Headers = mirrorHeader
}
}
return parsedMirrors
}
// hostDirectory converts ":port" to "_port_" in directory names
func hostDirectory(host string) string {
idx := strings.LastIndex(host, ":")
if idx > 0 {
return host[:idx] + "_" + host[idx+1:] + "_"
}
return host
}
func hostPaths(root, host string) []string {
var hosts []string
ch := hostDirectory(host)
if ch != host {
hosts = append(hosts, filepath.Join(root, ch))
}
return append(hosts,
filepath.Join(root, host),
filepath.Join(root, "_default"),
)
}
func hostDirFromRoot(root, host string) (string, error) {
for _, p := range hostPaths(root, host) {
if _, err := os.Stat(p); err == nil {
return p, nil
} else if !os.IsNotExist(err) {
return "", err
}
}
return "", nil
}
// getSortedHosts returns the list of hosts as they defined in the file.
func getSortedHosts(root *toml.Tree) ([]string, error) {
iter, ok := root.Get("host").(*toml.Tree)
if !ok {
return nil, errors.New("invalid `host` tree")
}
list := append([]string{}, iter.Keys()...)
// go-toml stores TOML sections in the map object, so no order guaranteed.
// We retrieve line number for each key and sort the keys by position.
sort.Slice(list, func(i, j int) bool {
h1 := iter.GetPath([]string{list[i]}).(*toml.Tree)
h2 := iter.GetPath([]string{list[j]}).(*toml.Tree)
return h1.Position().Line < h2.Position().Line
})
return list, nil
}
// parseHostConfig returns the parsed host configuration, make sure the server is not null.
func parseHostConfig(server string, config HostFileConfig) (hostConfig, error) {
var (
result = hostConfig{}
err error
)
if !strings.HasPrefix(server, "http") {
server = "https://" + server
}
u, err := url.Parse(server)
if err != nil {
return hostConfig{}, fmt.Errorf("unable to parse server %v: %w", server, err)
}
result.Scheme = u.Scheme
result.Host = u.Host
if config.Header != nil {
header := http.Header{}
for key, ty := range config.Header {
switch value := ty.(type) {
case string:
header[key] = []string{value}
case []interface{}:
header[key], err = makeStringSlice(value, nil)
if err != nil {
return hostConfig{}, err
}
default:
return hostConfig{}, fmt.Errorf("invalid type %v for header %q", ty, key)
}
}
result.Header = header
}
if config.CACert != nil {
switch cert := config.CACert.(type) {
case string:
result.CACerts = []string{cert}
case []interface{}:
certs, err := makeStringSlice(cert, nil)
if err != nil {
return hostConfig{}, fmt.Errorf("invalid type for ca: %w", err)
}
result.CACerts = certs
default:
return hostConfig{}, fmt.Errorf("invalid type %v for ca", config.CACert)
}
}
result.HealthCheckInterval = config.HealthCheckInterval
result.FailureLimit = config.FailureLimit
result.PingURL = config.PingURL
return result, nil
}
func parseHostsFile(b []byte) ([]hostConfig, error) {
tree, err := toml.LoadBytes(b)
if err != nil {
return nil, fmt.Errorf("failed to parse TOML: %w", err)
}
c := struct {
// HostConfigs store the per-host configuration
HostConfigs map[string]HostFileConfig `toml:"host"`
}{}
orderedHosts, err := getSortedHosts(tree)
if err != nil {
return nil, err
}
var (
hosts []hostConfig
)
if err := tree.Unmarshal(&c); err != nil {
return nil, err
}
// Parse hosts array
for _, host := range orderedHosts {
if host != "" {
config := c.HostConfigs[host]
parsed, err := parseHostConfig(host, config)
if err != nil {
return nil, err
}
hosts = append(hosts, parsed)
}
}
return hosts, nil
}
func loadHostDir(hostsDir string) ([]hostConfig, error) {
b, err := os.ReadFile(filepath.Join(hostsDir, "hosts.toml"))
if err != nil {
if !os.IsNotExist(err) {
return nil, err
}
return []hostConfig{}, nil
}
hosts, err := parseHostsFile(b)
if err != nil {
return nil, err
}
return hosts, nil
}
func LoadMirrorsConfig(mirrorsConfigDir, registryHost string) ([]MirrorConfig, []string, error) {
if mirrorsConfigDir == "" {
return nil, nil, nil
}
hostDir, err := hostDirFromRoot(mirrorsConfigDir, registryHost)
if err != nil {
return nil, nil, err
}
if hostDir == "" {
return nil, nil, nil
}
hosts, err := loadHostDir(hostDir)
if err != nil {
return nil, nil, err
}
// Collect CA certs from all host entries and deduplicate.
seen := make(map[string]struct{})
var caCerts []string
for _, h := range hosts {
for _, ca := range h.CACerts {
if _, ok := seen[ca]; !ok {
seen[ca] = struct{}{}
caCerts = append(caCerts, ca)
}
}
}
return parseMirrorsConfig(hosts), caCerts, nil
}
================================================
FILE: config/daemonconfig/mirrors_test.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package daemonconfig
import (
"fmt"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLoadMirrorConfigCACerts(t *testing.T) {
registryHost := "registry.example.com"
t.Run("single CA cert as absolute path", func(t *testing.T) {
tmpDir := t.TempDir()
hostDir := filepath.Join(tmpDir, "certs.d", registryHost)
require.NoError(t, os.MkdirAll(hostDir, os.ModePerm))
caPath := filepath.Join(tmpDir, "my-ca.pem")
require.NoError(t, os.WriteFile(caPath, []byte(""), 0600))
hosts := fmt.Sprintf(`
[host."https://mirror.example.com"]
ca = %q
`, caPath)
require.NoError(t, os.WriteFile(filepath.Join(hostDir, "hosts.toml"), []byte(hosts), 0600))
_, caCerts, err := LoadMirrorsConfig(filepath.Join(tmpDir, "certs.d"), registryHost)
require.NoError(t, err)
require.Equal(t, []string{caPath}, caCerts)
})
t.Run("multiple CA certs as array", func(t *testing.T) {
tmpDir := t.TempDir()
hostDir := filepath.Join(tmpDir, "certs.d", registryHost)
require.NoError(t, os.MkdirAll(hostDir, os.ModePerm))
ca1 := filepath.Join(tmpDir, "ca1.pem")
ca2 := filepath.Join(tmpDir, "ca2.pem")
hosts := fmt.Sprintf(`
[host."https://mirror.example.com"]
ca = [%q, %q]
`, ca1, ca2)
require.NoError(t, os.WriteFile(filepath.Join(hostDir, "hosts.toml"), []byte(hosts), 0600))
_, caCerts, err := LoadMirrorsConfig(filepath.Join(tmpDir, "certs.d"), registryHost)
require.NoError(t, err)
require.Equal(t, []string{ca1, ca2}, caCerts)
})
t.Run("CA certs deduplicated across multiple hosts", func(t *testing.T) {
tmpDir := t.TempDir()
hostDir := filepath.Join(tmpDir, "certs.d", registryHost)
require.NoError(t, os.MkdirAll(hostDir, os.ModePerm))
ca1 := filepath.Join(tmpDir, "ca1.pem")
ca2 := filepath.Join(tmpDir, "ca2.pem")
hosts := fmt.Sprintf(`
[host."https://mirror1.example.com"]
ca = %q
[host."https://mirror2.example.com"]
ca = [%q, %q]
`, ca1, ca1, ca2)
require.NoError(t, os.WriteFile(filepath.Join(hostDir, "hosts.toml"), []byte(hosts), 0600))
_, caCerts, err := LoadMirrorsConfig(filepath.Join(tmpDir, "certs.d"), registryHost)
require.NoError(t, err)
require.Equal(t, []string{ca1, ca2}, caCerts)
})
t.Run("no CA cert field returns nil caCerts", func(t *testing.T) {
tmpDir := t.TempDir()
hostDir := filepath.Join(tmpDir, "certs.d", registryHost)
require.NoError(t, os.MkdirAll(hostDir, os.ModePerm))
hosts := `
[host."https://mirror.example.com"]
`
require.NoError(t, os.WriteFile(filepath.Join(hostDir, "hosts.toml"), []byte(hosts), 0600))
_, caCerts, err := LoadMirrorsConfig(filepath.Join(tmpDir, "certs.d"), registryHost)
require.NoError(t, err)
require.Nil(t, caCerts)
})
}
func TestLoadMirrorConfig(t *testing.T) {
tmpDir := t.TempDir()
defer os.RemoveAll(tmpDir)
registryHost := "registry.docker.io"
mirrorsConfigDir := filepath.Join(tmpDir, "certs.d")
registryHostConfigDir := filepath.Join(mirrorsConfigDir, registryHost)
defaultHostConfigDir := filepath.Join(mirrorsConfigDir, "_default")
mirrors, _, err := LoadMirrorsConfig("", registryHost)
require.NoError(t, err)
require.Nil(t, mirrors)
mirrors, _, err = LoadMirrorsConfig(mirrorsConfigDir, registryHost)
require.NoError(t, err)
require.Nil(t, mirrors)
err = os.MkdirAll(defaultHostConfigDir, os.ModePerm)
assert.NoError(t, err)
mirrors, _, err = LoadMirrorsConfig(mirrorsConfigDir, registryHost)
require.NoError(t, err)
require.Equal(t, len(mirrors), 0)
buf1 := []byte(`server = "https://default-docker.hub.com"
[host]
[host."http://default-p2p-mirror1:65001"]
[host."http://default-p2p-mirror1:65001".header]
X-Dragonfly-Registry = ["https://default-docker.hub.com"]
`)
err = os.WriteFile(filepath.Join(defaultHostConfigDir, "hosts.toml"), buf1, 0600)
assert.NoError(t, err)
mirrors, _, err = LoadMirrorsConfig(mirrorsConfigDir, registryHost)
require.NoError(t, err)
require.Equal(t, len(mirrors), 1)
require.Equal(t, mirrors[0].Host, "http://default-p2p-mirror1:65001")
require.Equal(t, mirrors[0].Headers["X-Dragonfly-Registry"], "https://default-docker.hub.com")
err = os.MkdirAll(registryHostConfigDir, os.ModePerm)
assert.NoError(t, err)
buf2 := []byte(`server = "https://docker.hub.com"
[host]
[host."http://p2p-mirror1:65001"]
[host."http://p2p-mirror1:65001".header]
X-Dragonfly-Registry = ["https://docker.hub.com"]
`)
err = os.WriteFile(filepath.Join(registryHostConfigDir, "hosts.toml"), buf2, 0600)
assert.NoError(t, err)
mirrors, _, err = LoadMirrorsConfig(mirrorsConfigDir, registryHost)
require.NoError(t, err)
require.Equal(t, len(mirrors), 1)
require.Equal(t, mirrors[0].Host, "http://p2p-mirror1:65001")
require.Equal(t, mirrors[0].Headers["X-Dragonfly-Registry"], "https://docker.hub.com")
buf3 := []byte(`
[host."http://p2p-mirror2:65001"]
[host."http://p2p-mirror2:65001".header]
X-Dragonfly-Registry = ["https://docker.hub.com"]
`)
err = os.WriteFile(filepath.Join(registryHostConfigDir, "hosts.toml"), buf3, 0600)
assert.NoError(t, err)
mirrors, _, err = LoadMirrorsConfig(mirrorsConfigDir, registryHost)
require.NoError(t, err)
require.Equal(t, len(mirrors), 1)
require.Equal(t, mirrors[0].Host, "http://p2p-mirror2:65001")
require.Equal(t, mirrors[0].Headers["X-Dragonfly-Registry"], "https://docker.hub.com")
}
================================================
FILE: config/default.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package config
import (
"os/exec"
"github.com/containerd/nydus-snapshotter/internal/constant"
)
func (c *SnapshotterConfig) FillUpWithDefaults() error {
c.Version = 1
c.Root = constant.DefaultRootDir
c.Address = constant.DefaultAddress
// essential configuration
if c.DaemonMode == "" {
c.DaemonMode = constant.DefaultDaemonMode
}
// system controller configuration
c.SystemControllerConfig.Address = constant.DefaultSystemControllerAddress
// logging configuration
logConfig := &c.LoggingConfig
if logConfig.LogLevel == "" {
logConfig.LogLevel = constant.DefaultLogLevel
}
logConfig.RotateLogMaxSize = constant.DefaultRotateLogMaxSize
logConfig.RotateLogMaxBackups = constant.DefaultRotateLogMaxBackups
logConfig.RotateLogMaxAge = constant.DefaultRotateLogMaxAge
logConfig.RotateLogLocalTime = constant.DefaultRotateLogLocalTime
logConfig.RotateLogCompress = constant.DefaultRotateLogCompress
// daemon configuration
daemonConfig := &c.DaemonConfig
if daemonConfig.NydusdConfigPath == "" {
daemonConfig.NydusdConfigPath = constant.DefaultNydusDaemonConfigPath
}
daemonConfig.RecoverPolicy = RecoverPolicyRestart.String()
daemonConfig.FsDriver = constant.DefaultFsDriver
daemonConfig.LogRotationSize = constant.DefaultDaemonRotateLogMaxSize
daemonConfig.FailoverPolicy = constant.DefaultFailoverPolicy
// cache configuration
cacheConfig := &c.CacheManagerConfig
cacheConfig.GCPeriod = constant.DefaultGCPeriod
// metrics configuration
metricsConfig := &c.MetricsConfig
metricsConfig.HungIOInterval = constant.DefaultHungIOInterval
metricsConfig.CollectInterval = constant.DefaultCollectInterval
return c.SetupNydusBinaryPaths()
}
func (c *SnapshotterConfig) SetupNydusBinaryPaths() error {
// resolve nydusd path
if path, err := exec.LookPath(constant.NydusdBinaryName); err == nil {
c.DaemonConfig.NydusdPath = path
}
// resolve nydus-image path
if path, err := exec.LookPath(constant.NydusImageBinaryName); err == nil {
c.DaemonConfig.NydusImagePath = path
}
return nil
}
================================================
FILE: config/global.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
// Expose configurations across nydus-snapshotter, the configurations is parsed
// and extracted from nydus-snapshotter toml based configuration file or command line
package config
import (
"os"
"path/filepath"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/internal/logging"
"github.com/containerd/nydus-snapshotter/pkg/utils/mount"
"github.com/pkg/errors"
)
var (
globalConfig GlobalConfig
)
// Global cached configuration information to help:
// - access configuration information without passing a configuration object
// - avoid frequent generation of information from configuration information
type GlobalConfig struct {
origin *SnapshotterConfig
SnapshotsDir string
DaemonMode DaemonMode
SocketRoot string
ConfigRoot string
RootMountpoint string
DaemonThreadsNum int
MirrorsConfig MirrorsConfig
}
func IsFusedevSharedModeEnabled() bool {
return globalConfig.DaemonMode == DaemonModeShared
}
func GetDaemonMode() DaemonMode {
return globalConfig.DaemonMode
}
func GetSnapshotsRootDir() string {
return globalConfig.SnapshotsDir
}
func GetRootMountpoint() string {
return globalConfig.RootMountpoint
}
func GetSocketRoot() string {
return globalConfig.SocketRoot
}
func GetConfigRoot() string {
return globalConfig.ConfigRoot
}
func GetMirrorsConfigDir() string {
return globalConfig.MirrorsConfig.Dir
}
func GetFsDriver() string {
return globalConfig.origin.DaemonConfig.FsDriver
}
func GetLogDir() string {
return globalConfig.origin.LoggingConfig.LogDir
}
func GetLogLevel() string {
return globalConfig.origin.LoggingConfig.LogLevel
}
func GetDaemonLogRotationSize() int {
return globalConfig.origin.DaemonConfig.LogRotationSize
}
func GetDaemonThreadsNumber() int {
return globalConfig.origin.DaemonConfig.ThreadsNumber
}
func GetDaemonFailoverPolicy() string {
return globalConfig.origin.DaemonConfig.FailoverPolicy
}
func GetLogToStdout() bool {
return globalConfig.origin.LoggingConfig.LogToStdout
}
func IsBackendSourceEnabled() bool {
return globalConfig.origin.Experimental.EnableBackendSource && globalConfig.origin.SystemControllerConfig.Enable
}
func IsSystemControllerEnabled() bool {
return globalConfig.origin.SystemControllerConfig.Enable
}
func SystemControllerAddress() string {
return globalConfig.origin.SystemControllerConfig.Address
}
func SystemControllerPprofAddress() string {
return globalConfig.origin.SystemControllerConfig.DebugConfig.PprofAddress
}
func GetDaemonProfileCPUDuration() int64 {
return globalConfig.origin.SystemControllerConfig.DebugConfig.ProfileDuration
}
func GetSkipSSLVerify() bool {
return globalConfig.origin.RemoteConfig.SkipSSLVerify
}
const (
TarfsLayerVerityOnly string = "layer_verity_only"
TarfsImageVerityOnly string = "image_verity_only"
TarfsLayerBlockDevice string = "layer_block"
TarfsImageBlockDevice string = "image_block"
TarfsLayerBlockWithVerity string = "layer_block_with_verity"
TarfsImageBlockWithVerity string = "image_block_with_verity"
)
func GetTarfsMountOnHost() bool {
return globalConfig.origin.Experimental.TarfsConfig.MountTarfsOnHost
}
func GetTarfsExportEnabled() bool {
switch globalConfig.origin.Experimental.TarfsConfig.ExportMode {
case TarfsLayerVerityOnly, TarfsLayerBlockDevice, TarfsLayerBlockWithVerity:
return true
case TarfsImageVerityOnly, TarfsImageBlockDevice, TarfsImageBlockWithVerity:
return true
default:
return false
}
}
// Returns (wholeImage, generateBlockImage, withVerityInfo)
// wholeImage: generate tarfs for the whole image instead of of a specific layer.
// generateBlockImage: generate a block image file.
// withVerityInfo: generate disk verity information.
func GetTarfsExportFlags() (bool, bool, bool) {
switch globalConfig.origin.Experimental.TarfsConfig.ExportMode {
case "layer_verity_only":
return false, false, true
case "image_verity_only":
return true, false, true
case "layer_block":
return false, true, false
case "image_block":
return true, true, false
case "layer_block_with_verity":
return false, true, true
case "image_block_with_verity":
return true, true, true
default:
return false, false, false
}
}
func ProcessConfigurations(c *SnapshotterConfig) error {
if c.CacheManagerConfig.CacheDir == "" {
c.CacheManagerConfig.CacheDir = filepath.Join(c.Root, "cache")
}
globalConfig.origin = c
globalConfig.SnapshotsDir = filepath.Join(c.Root, "snapshots")
globalConfig.ConfigRoot = filepath.Join(c.Root, "config")
globalConfig.SocketRoot = filepath.Join(c.Root, "socket")
globalConfig.RootMountpoint = filepath.Join(c.Root, "mnt")
globalConfig.MirrorsConfig = c.RemoteConfig.MirrorsConfig
m, err := parseDaemonMode(c.DaemonMode)
if err != nil {
return err
}
if c.DaemonConfig.FsDriver == FsDriverFscache && m != DaemonModeShared {
log.L.Infof("fscache driver only supports 'shared' mode, override daemon mode from '%s' to 'shared'", m)
m = DaemonModeShared
}
globalConfig.DaemonMode = m
return nil
}
func PrepareLogDir(c *SnapshotterConfig) {
if c.LoggingConfig.LogDir == "" {
c.LoggingConfig.LogDir = filepath.Join(c.Root, logging.DefaultLogDirName)
}
}
func SetUpEnvironment(c *SnapshotterConfig) error {
if err := os.MkdirAll(c.Root, 0700); err != nil {
return errors.Wrapf(err, "create root dir %s", c.Root)
}
realPath, err := mount.NormalizePath(c.Root)
if err != nil {
return errors.Wrapf(err, "invalid root path")
}
c.Root = realPath
return nil
}
================================================
FILE: docs/configure_nydus.md
================================================
# Configure Nydus-snapshotter
Nydus-snapshotter can receive a toml file as its configurations to start providing image service through CLI parameter `--config`. An example configuration file can be found [here](../misc/snapshotter/config.toml). Besides nydus-snapshotter's configuration, `nydusd`'s configuration has to be provided to nydus-snapshotter too. Nydusd is started by nydus-snapshotter and it is configured by the provided json configuration file. A minimal configuration file can be found [here](../misc/snapshotter/nydusd-config.fusedev.json)
## Authentication
See [Registry Authentication](registry_authentication.md) for the full reference covering Docker config, CRI, kubeconfig, kubelet credential providers, and automatic credential renewal.
## Metrics
Nydusd records metrics in its own format. The metrics are exported via a HTTP server on top of unix domain socket. Nydus-snapshotter fetches the metrics and convert them in to Prometheus format which is exported via a network address. Nydus-snapshotter by default does not fetch metrics from nydusd. You can enable the nydusd metrics download by assigning a network address to `metrics.address` in nydus-snapshotter's toml [configuration file](../misc/snapshotter/config.toml).
Once this entry is enabled, not only nydusd metrics, but also some information about the nydus-snapshotter
runtime and snapshot related events are exported in Prometheus format as well.
## Diagnose
A system controller can be ran insides nydus-snapshotter.
By setting `system.enable` to `true`, nydus-snapshotter will start a simple HTTP server on unix domain socket `system.address` path and exports some internal working status to users. The address defaults to `/var/run/containerd-nydus/system.sock`
================================================
FILE: docs/crictl_dry_run.md
================================================
# Start Container by crictl
## Create crictl Config
The runtime endpoint can be set in the config file. Please refer to [crictl](https://github.com/kubernetes-sigs/cri-tools/blob/master/docs/crictl.md) document for more details.
Compose your `crictl` configuration file named as `crictl.yaml`:
```yml
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 10
debug: true
```
Compose a pod configuration which can be named as `pod.yaml`
```yml
metadata:
attempt: 1
name: nydus-sandbox
namespace: default
uid: hdishd83djaidwnduwk28bcsb
log_directory: /tmp
linux:
security_context:
namespace_options:
network: 2
```
Compose a container configuration which can be named as `container.yaml`
```yml
metadata:
name: nydus-container
image:
image:
command:
- /bin/sleep
args:
- 600
log_path: container.1.log
```
Start a container based on nydus image.
```bash
# auth is base64 encoded string from "username:password"
$ crictl --config ./crictl.yaml run --auth ./container.yaml ./pod.yaml
```
================================================
FILE: docs/index_detection.md
================================================
# Index Detection
## Overview
Index Detection is a feature that automatically discovers Nydus alternative manifests within OCI index manifests. This enables transparent fallback to optimized Nydus images when available while keeping the original index manifest OCI compliant, allowing non-Nydus clients to pull regular OCI images.
## Motivation
The Index Detection feature addresses several limitations of the existing Referrer API-based detection:
- **Registry Support**: Not all registries support the Referrer API
- **Supply Chain Security**: Referrer API relies on external changes to the manifest, creating potential supply chain risks
- **Universal Compatibility**: Index detection works with all OCI-compliant registries
Unlike referrer-based detection, Index Detection packages everything into a single manifest, eliminating supply chain concerns while maintaining universal registry support.
## How It Works
### Detection Process
1. **Index Manifest Parsing**: When a manifest digest is encountered, the system fetches and parses the original OCI index manifest
2. **Platform Matching**: The system finds the original manifest descriptor within the index
3. **Nydus Alternative Search**: It searches for platform-compatible manifests that contain:
- `platform.os.features` containing `nydus.remoteimage.v1`, or
- `artifactType` set to `application/vnd.nydus.image.manifest.v1+json`
4. **Validation**: The found manifest is validated to ensure it's a valid Nydus manifest with metadata layers
5. **Caching**: Results are cached to avoid repeated API calls for the same digest
Both `platform.os.features` and `artifactType` are looked at because index manifests built using `merge-platform` before acceleration-service: v0.2.19 or nydus: v2.3.5 will have `platform.os.features` configured while images built after will have `artifactType` configured.
### Detection Priority
When both Index Detection and Referrer Detection are available, Index Detection takes priority due to its superior supply chain security properties.
## Configuration
Index Detection is controlled by the `EnableIndexDetect` configuration option in the experimental features section:
```toml
[experimental]
enable_index_detect = true
```
## Building Compatible Images
To create images compatible with Index Detection, use the `nydusify convert` command with the `--merge-platform` flag:
```bash
nydusify convert --merge-platform
```
This creates an OCI index manifest containing both the original image and the Nydus alternative:
```json
{
"schemaVersion": 2,
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:a63dfddecc661e4ad05896418b2f3774022269c3bf5b7e01baaa6e851a3a4a23",
"size": 2320,
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:bb82dd8ee111bfe5fdf12145b6ed553da9c15de17f54b1f658d95ff26a65a01c",
"size": 3229,
"platform": {
"architecture": "amd64",
"os": "linux"
},
"artifactType": "application/vnd.nydus.image.manifest.v1+json"
}
]
}
```
## Comparison with Referrer Detection
| Aspect | Index Detection | Referrer Detection |
|--------|----------------|-------------------|
| Registry Support | Universal | Limited |
| Supply Chain Security | Secure (single manifest) | Potential risks (external refs) |
| OCI Compliance | Full compliance | Requires Referrer API |
| Cache Behavior | Immutable (digest-based) | May require invalidation |
| Detection Priority | Higher | Lower |
================================================
FILE: docs/optimize_nydus_image.md
================================================
# Optimize a nydus image
To improve the prefetch hit rate, we can specify a prefetch table when converting an OCI image to a nydus image. The prefetch table is workload related and we should generate the list of accessed files in order when the workload is running. Nydus-snapshotter has implemented an optimizer with Containerd NRI plugin to optimize the nydus image. The optimizer is image format independent and requires NRI 2.0, which is available in containerd (>=v1.7.0). The optimizer subscribes container events to watch what files are opened and read, etc. during container application is starting up. This enables nydus image build tools to optimize the nydus image making it put the necessary and prioritized files into a special region of nydus image with extra image metadata. So nydus runtime can pull this files into local disk in top priority, thus to boost the performance further.
## Requirements
- [NRI 2.0](https://github.com/containerd/nri): Which has been integrated into containerd since [v1.7.0](https://github.com/containerd/containerd/tree/v1.7.0-beta.1).
## Workflow

1. Run the optimizer as a NRI plugin to run optimizer server when the StartContainer event occurs.
2. Optimizer server joins container mount namespace and starts fanotify server.
3. Optimizer server detects fanotify event and sends the accessed file descriptor to client.
4. Optimizer client converts file descriptor to path.
5. Generate list of accessed files on local disk.
6. Convert OCI images with accessed files list to nydus image.
## Generate an accessed files list
To install the optimizer and optimizer-server, invoke below command:
```console
make optimizer && make install-optimizer
```
This command installs the optimizer's default toml configuration file in `/etc/nri/conf.d/02-optimizer-nri-plugin.conf`. Here is an example:
```toml
# The directory to persist accessed files list for container.
persist_dir = "/opt/nri/optimizer/results"
# Whether to make the csv file human readable.
readable = false
# The path of optimizer server binary.
server_path = "/usr/local/bin/optimizer-server"
# The timeout to kill optimizer server, 0 to disable it.
timeout = 0
# Whether to overwrite the existed persistent files.
overwrite = false
# The events that containerd subscribes to.
# Do not change this element.
events = [ "StartContainer", "StopContainer" ]
```
Modify containerd's toml configuration file to enable NRI.
```console
sudo tee -a /etc/containerd/config.toml <<- EOF
[plugins."io.containerd.nri.v1.nri"]
config_file = "/etc/nri/nri.conf"
disable = false
plugin_path = "/opt/nri/plugins"
socket_path = "/var/run/nri.sock"
EOF
```
Containerd will load all NRI plugins in the `plugin_path` directory on startup. If you want to start a NRI plugin manually, please add the following configuration to allow other NRI plugins to connect via `socket_path`.
```console
sudo tee /etc/nri/nri.conf <<- EOF
disableConnections: false
EOF
```
Restart the containerd service.
```console
sudo systemctl restart containerd
```
Now, just run a container workload in sandbox and you will get the list of accessed files in `persist_dir`.
Note that NRI plugin can only be called from containerd/CRI. So start a container using `crictl` as below.
```console
sudo tee nginx.yaml <<- EOF
metadata:
name: nginx
image:
image: nginx:latest
log_path: nginx.0.log
linux: {}
EOF
sudo tee pod.yaml <<- EOF
metadata:
name: nginx-sandbox
namespace: default
attempt: 1
uid: hdishd83djaidwnduwk28bcsb
log_directory: /tmp
linux: {}
EOF
crictl run nginx.yaml pod.yaml
```
The result file for the nginx image is `/opt/nri/optimizer/results/library/nginx:latest`.
## Build Nydus Image with Optimizer's Suggestions
Nydus provides a [nydusify](https://github.com/dragonflyoss/nydus/blob/master/docs/nydusify.md) CLI tool to convert OCI images from the source registry or local file system to nydus format and push them to the target registry.
We can install the `nydusify` cli tool from the nydus package.
```console
VERSION=v2.3.0
wget https://github.com/dragonflyoss/nydus/releases/download/$VERSION/nydus-static-$VERSION-linux-amd64.tgz
tar -zxvf nydus-static-$VERSION-linux-amd64.tgz
sudo install -D -m 755 nydus-static/nydusify /usr/local/bin/nydusify
sudo install -D -m 755 nydus-static/nydus-image /usr/local/bin/nydus-image
```
A simple example converts the OCI image nginx to nydus format in RAFS V6.
```console
sudo nydusify convert --source nginx --target sctb512/nginx:nydusv6 --fs-version 6
```
With the `--prefetch-patterns` argument, we can specify the list of files to be written in the front of the nydus image and be prefetched in order when starting a container.
```console
sudo nydusify convert --source nginx --target sctb512/nginx:optimized-nydusv6 --fs-version 6 --prefetch-patterns < /opt/nri/optimizer/results/library/nginx:latest
```
On a host with nydus-snapshotter installed and configured properly, start a container with an optimized nydus image.
```console
sudo nerdctl --snapshotter nydus run --rm --net host -it sctb512/nginx:optimized-nydusv6
```
================================================
FILE: docs/registry_authentication.md
================================================
# Registry Authentication
As [containerd#3731](https://github.com/containerd/containerd/issues/3731) discussed, containerd doesn't share credentials with third-party snapshotters. Like [stargz snapshotter](https://github.com/containerd/stargz-snapshotter/blob/main/docs/overview.md#authentication), nydus-snapshotter supports multiple ways to access private registries with custom configurations.
Credentials are looked up in the following priority order:
1. Snapshot labels (username and password)
2. CRI request interception
3. Docker config (enabled by default)
4. Kubelet credential provider plugins
5. Kubernetes docker config secrets
## Docker config
By default, the snapshotter reads credentials from `$DOCKER_CONFIG` or `~/.docker/config.json`.
```console
# docker login
(Enter username and password)
# crictl pull --creds USERNAME[:PASSWORD] docker.io//ubuntu:22.04
(Here the credential is only used by containerd)
```
## CRI-based authentication
The following configuration enables nydus-snapshotter to pull private images via CRI requests.
```toml
[remote.auth]
# Fetch the private registry auth as CRI image service proxy
enable_cri_keychain = true
image_service_address = "/run/containerd/containerd.sock"
```
The snapshotter acts as a proxy of the CRI Image Service, exposing the CRI Image Service API on the snapshotter's unix socket (i.e. `/run/containerd/containerd-nydus-grpc.sock`). It acquires registry credentials by scanning requests. `image_service_address` defaults to `/run/containerd/containerd.sock` if omitted.
You **must** specify `--image-service-endpoint=unix:///run/containerd-nydus/containerd-nydus-grpc.sock` to kubelet when using Kubernetes, or set `image-endpoint: "unix:////run/containerd-nydus/containerd-nydus-grpc.sock"` in `crictl.yaml` when using `crictl`.
## Kubeconfig-based authentication
Nydus snapshotter can watch Kubernetes secrets (type `kubernetes.io/dockerconfigjson`) for private registry credentials.
### Using a kubeconfig file
```toml
[remote.auth]
enable_kubeconfig_keychain = true
kubeconfig_path = "/path/to/.kubeconfig"
```
If `kubeconfig_path` is omitted, the snapshotter searches `$KUBECONFIG` or `~/.kube/config`.
> **Note:** This requires additional privilege (a kubeconfig with permission to list/watch secrets) on the node. It does not work if kubelet retrieves credentials outside the API server (e.g. via a [credential provider](https://kubernetes.io/docs/tasks/kubelet-credential-provider/kubelet-credential-provider/)). For that use case, see the [kubelet credential provider](#kubelet-credential-provider) section.
### Using a ServiceAccount
If nydus-snapshotter runs inside a Kubernetes cluster you can use a ServiceAccount instead of a kubeconfig:
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: nydus-snapshotter-sa
namespace: nydus-system
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nydus-snapshotter-role
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
- watch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nydus-snapshotter-role-binding
roleRef:
kind: ClusterRole
name: nydus-snapshotter-role
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: nydus-snapshotter-sa
namespace: nydus-system
```
Then reference the ServiceAccount in the nydus-snapshotter Pod spec:
```yaml
spec:
serviceAccountName: nydus-snapshotter-sa
```
### Creating secrets
If you have logged into a private registry, create a secret from the local config file:
```bash
kubectl create --namespace nydus-system secret generic regcred \
--from-file=.dockerconfigjson=$HOME/.docker/config.json \
--type=kubernetes.io/dockerconfigjson
```
The snapshotter will pick up the secret and use it for subsequent image pulls from the matched registry.
## Kubelet credential provider
Nydus snapshotter supports [Kubernetes kubelet credential provider plugins](https://kubernetes.io/docs/tasks/kubelet-credential-provider/kubelet-credential-provider/), which allow dynamic credential retrieval from external sources such as cloud provider identity services or secrets managers.
> **Note:** This implementation supports **exec-based credential provider plugins** (GA since [Kubernetes v1.26](https://kubernetes.io/blog/2022/12/22/kubelet-credential-providers/)) but does **not** support the [service account token integration](https://kubernetes.io/blog/2025/05/07/kubernetes-v1-33-wi-for-image-pulls/) introduced in Kubernetes v1.33+. Plugins must retrieve credentials via other means (instance metadata, environment variables, local credential files, etc.).
This authentication method is well-suited for:
- Frequently rotated credentials managed by external systems
- Cloud provider-managed registries (ECR, GCR, ACR) using instance or workload identity
- Enterprise secrets management integrations
### Configuration
```toml
[remote.auth]
enable_kubelet_credential_providers = true
credential_provider_config = "/etc/nydus/credential-provider-config.yaml"
credential_provider_bin_dir = "/usr/local/bin/credential-providers"
```
| Parameter | Description |
|---|---|
| `enable_kubelet_credential_providers` | Enable kubelet credential provider support (default: `false`) |
| `credential_provider_config` | Path to the credential provider configuration file |
| `credential_provider_bin_dir` | Directory containing credential provider plugin binaries |
### Credential provider configuration file
Follow the [Kubernetes spec](https://kubernetes.io/docs/reference/config-api/kubelet-config.v1/#kubelet-config-k8s-io-v1-CredentialProviderConfig):
```yaml
apiVersion: kubelet.config.k8s.io/v1
kind: CredentialProviderConfig
providers:
- name: ecr-credential-provider
apiVersion: credentialprovider.kubelet.k8s.io/v1
matchImages:
- "*.dkr.ecr.*.amazonaws.com"
defaultCacheDuration: "12h"
args:
- get-credentials
- name: gcr-credential-provider
apiVersion: credentialprovider.kubelet.k8s.io/v1
matchImages:
- "gcr.io"
- "*.gcr.io"
defaultCacheDuration: "1h"
```
### Plugin installation
1. Place credential provider binaries in the `credential_provider_bin_dir` directory and ensure they are executable.
2. Configure `matchImages` patterns to match your registry URLs. Wildcards are supported (e.g. `*.dkr.ecr.*.amazonaws.com`).
For AWS ECR, use the official [ECR credential provider](https://github.com/kubernetes/cloud-provider-aws/tree/master/cmd/ecr-credential-provider):
```bash
mkdir -p /usr/local/bin/credential-providers
wget https://artifacts.k8s.io/binaries/cloud-provider-aws/v1.30.0/linux/amd64/ecr-credential-provider
chmod +x ecr-credential-provider
mv ecr-credential-provider /usr/local/bin/credential-providers/
```
### Plugin protocol
Plugins communicate via stdin/stdout using JSON.
**Request:**
```json
{
"kind": "CredentialProviderRequest",
"apiVersion": "credentialprovider.kubelet.k8s.io/v1",
"image": "123456789.dkr.ecr.us-east-1.amazonaws.com/my-image:latest"
}
```
**Response:**
```json
{
"kind": "CredentialProviderResponse",
"apiVersion": "credentialprovider.kubelet.k8s.io/v1",
"cacheKeyType": "Image",
"cacheDuration": "12h",
"auth": {
"123456789.dkr.ecr.us-east-1.amazonaws.com": {
"username": "AWS",
"password": ""
}
}
}
```
Set `auth` to `null` if no credentials are available for the requested image.
### Troubleshooting
- Plugin execution failures are logged but don't halt the authentication chain — the snapshotter tries the next provider.
- Check logs for: `level=warning msg="failed to execute credential provider plugin"`
- Test plugins manually by sending a JSON request on stdin to verify output.
## Credential renewal
For providers that issue short-lived tokens (such as the kubelet credential provider with cloud IAM backends), nydus-snapshotter can automatically renew credentials in the background before they expire.
When enabled, a background goroutine periodically reconciles the set of active RAFS instances against an in-memory credential store, renewing credentials for images currently in use and evicting entries for images that are no longer mounted. On each renewal tick the goroutine re-queries the renewable providers (Docker config, kubelet credential providers, Kubernetes secrets) in priority order.
Only providers that support renewal participate: Docker config, kubelet credential providers, and Kubernetes secret-based providers. CRI-based and label-based credentials are not renewed. The kubelet provider being
expiration aware, it will only renew tokens when they are about to expire.
Entries are evicted when the corresponding RAFS instance is no longer mounted; the credential store itself does not expire entries.
### Configuration
```toml
[remote.auth]
# How often to renew credentials. Set to 0 (the default) to disable.
credential_renewal_interval = "30m"
```
Set `credential_renewal_interval` to at most one third of your token lifetime. This ensures at least two renewal attempts before a token expires, so a single transient failure (network blip, metadata service hiccup) does not cause an auth outage. For example, if ECR tokens are valid for 12 hours, use an interval of 4 hours or less.
> **Future improvement:** The current configuration conflates two concerns into a single interval: how frequently the renewal loop runs, and how early before expiry a token should be renewed. A future version may separate these into a `credential_renewal_check_interval` (the loop cadence, kept short) and a `credential_renewal_lead_time` (how far before expiry to trigger renewal, e.g. 2 hours before a 12-hour token expires). This would allow fine-grained control without the lifetime/3 approximation.
### Metrics
When credential renewal is enabled, the following Prometheus metrics are exported:
| Metric | Type | Description |
|---|---|---|
| `snapshotter_credential_renewals_total` | Counter | Renewal attempts, labeled by `image_ref` and `result` (`success` or `failure`) |
| `snapshotter_credential_store_entries` | Gauge | Number of credentials tracked per `image_ref` |
A rising `failure` count in `snapshotter_credential_renewals_total` indicates that a provider is failing to renew credentials and warrants investigation before the current tokens expire.
================================================
FILE: docs/run_nydus_in_kubernetes.md
================================================
# Run Dragonfly & Nydus in Kubernetes
We recommend using the Dragonfly P2P data distribution system to further improve the runtime performance of Nydus images.
If you want to deploy Dragonfly and Nydus at the same time through Helm, please refer to the **[Quick Start](https://github.com/dragonflyoss/helm-charts/blob/main/INSTALL.md)**.
# Run Nydus snapshotter in Kubernetes
This document will introduce how to run Nydus snapshotter in Kubernetes cluster, you can use helm to deploy Nydus snapshotter container.
**NOTE:** This document is mainly to allow everyone to quickly deploy and experience Nydus snapshotter in the Kubernetes cluster. You cannot use it as a deployment and configuration solution for the production environment.
## Setup Kubernetes using kind
[kind](https://kind.sigs.k8s.io/) is a tool for running local Kubernetes clusters using Docker container “nodes”.
kind was primarily designed for testing Kubernetes itself, but may be used for local development or CI.
First, we need to prepare a configuration file(`kind-config.yaml`) for kind to specify the devices and files we need to mount to the kind node.
```yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
ipFamily: dual
nodes:
- role: control-plane
image: kindest/node:v1.30.2
extraMounts:
- hostPath: ./containerd-config.toml
containerPath: /etc/containerd/config.toml
- hostPath: /dev/fuse
containerPath: /dev/fuse
```
Next, we also need a config for containerd(`containerd-config.toml`).
**NOTE:** It may be necessary to explain here why `disable_snapshot_annotations` and `discard_unpacked_layers` need to be configured in containerd.
- `disable_snapshot_annotations`: This variable disables to pass additional annotations (image related information) to snapshotters in containerd (default value is `true`). In nydus snapshotter, we need these annotations to pull images. Therefore, we need to set it to `false`.
- `discard_unpacked_layers`: This variable allows GC to remove layers from the content store after successfully unpacking these layers to the snapshotter in containerd (default value is `true`). In nydus snapshotter, we need to preserve layers for demand pulling and sharing even after they are unpacked. Therefore, we need to set it to `false`.
```toml
version = 2
[debug]
level = "debug"
[plugins."io.containerd.grpc.v1.cri".containerd]
discard_unpacked_layers = false
disable_snapshot_annotations = false
snapshotter = "overlayfs"
default_runtime_name = "runc"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri"]
sandbox_image = "registry.k8s.io/pause:3.6"
```
With these two configuration files, we can create a kind cluster.
```bash
$ kind create cluster --config=kind-config.yaml
```
If everything is fine, kind should have prepared the configuration for us, and we can run the `kubectl` command directly on the host machine, such as:
```bash
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
kind-control-plane Ready control-plane,master 19m v1.23.4
```
## Use helm to install Nydus snapshotter
Before proceeding, you need to make sure [`helm` is installed](https://helm.sh/docs/intro/quickstart/#install-helm).
First, you need to create a `config-nydus.yaml` for helm to install Nydus-snapshotter.
```yaml
name: nydus-snapshotter
pullPolicy: Always
hostNetwork: true
dragonfly:
enable: false
containerRuntime:
containerd:
enable: true
```
Then clone the Nydus snapshotter helm chart.
```bash
$ git clone https://github.com/dragonflyoss/helm-charts.git
```
Last run helm to create Nydus snapshotter.
```bash
$ cd helm-charts
$ helm install --wait --timeout 10m --dependency-update \
--create-namespace --namespace nydus-system \
-f config-nydus.yaml \
nydus-snapshotter charts/nydus-snapshotter
```
## Run Nydus containers
We can then create a Pod(`nydus-pod.yaml`) config file that runs Nydus image.
```yaml
apiVersion: v1
kind: Pod
metadata:
name: nydus-pod
spec:
containers:
- name: nginx
image: ghcr.io/dragonflyoss/image-service/nginx:nydus-latest
imagePullPolicy: Always
command: ["sh", "-c"]
args:
- tail -f /dev/null
```
Use `kubectl` to create the Pod.
```bash
$ kubectl create -f nydus-pod.yaml
$ kubectl get pods -w
```
The `-w` options will block the console and wait for the changes of the status of the pod. If everything is normal, you can see that the pod will become `Running` after a while.
```bash
NAME READY STATUS RESTARTS AGE
nydus-pod 1/1 Running 0 51s
```
================================================
FILE: docs/setup_snapshotter_by_daemonset.md
================================================
# Setup Nydus Snapshotter by DaemonSet
This document will guide you through the simple steps of setting up and cleaning up the nydus snapshotter in a kubernetes cluster that runs on the host.
## Steps for Setting up Nydus Snapshotter
To begin, let's clone the Nydus Snapshotter repository.
```bash
git clone https://github.com/containerd/nydus-snapshotter
cd nydus-snapshotter
```
We can build the docker image locally. (optional)
```bash
$ export NYDUS_VER=$(curl -s "https://api.github.com/repos/dragonflyoss/nydus/releases/latest" | jq -r .tag_name)
$ make # build snapshotter binaries
$ cp bin/* misc/snapshotter/
$ pushd misc/snapshotter/
$ docker build --build-arg NYDUS_VER="${NYDUS_VER}" -t ghcr.io/containerd/nydus-snapshotter:latest .
$ popd
```
**NOTE:** By default, the nydus snapshotter would use the latest release nydus version. If you want to use a specific version, you can set `NYDUS_VER` on your side.
Next, we can configure access control for nydus snapshotter.
```bash
kubectl apply -f misc/snapshotter/nydus-snapshotter-rbac.yaml
```
Afterward, we can deploy a DaemonSet for nydus snapshotter, according to the kubernetes flavour you're using.
```bash
# Vanilla kubernetes
kubectl apply -f misc/snapshotter/base/nydus-snapshotter.yaml
```
```bash
# k3s
kubectl apply -k misc/snapshotter/overlays/k3s/
```
```bash
# rke2
kubectl apply -k misc/snapshotter/overlays/rke2/
```
Then, we can confirm that nydus snapshotter is running through the DaemonSet.
```bash
$ kubectl get pods -n nydus-system
NAME READY STATUS RESTARTS AGE
nydus-snapshotter-26rf7 1/1 Running 0 18s
```
Finally, we can view the logs in the pod.
```bash
$ kubectl logs nydus-snapshotter-26rf7 -n nydus-system
install nydus snapshotter artifacts
there is no proxy plugin!
Created symlink /etc/systemd/system/multi-user.target.wants/nydus-snapshotter.service → /etc/systemd/system/nydus-snapshotter.service.
```
And we can see the nydus snapshotter service on the host.
```bash
$ systemctl status nydus-snapshotter
● nydus-snapshotter.service - nydus snapshotter
Loaded: loaded (/etc/systemd/system/nydus-snapshotter.service; enabled; vendor preset: enabled)
Drop-In: /etc/systemd/system/nydus-snapshotter.service.d
└─proxy.conf
Active: active (running) since Wed 2024-01-17 16:14:22 UTC; 56s ago
Main PID: 1100169 (containerd-nydu)
Tasks: 11 (limit: 96376)
Memory: 8.6M
CPU: 35ms
CGroup: /system.slice/nydus-snapshotter.service
└─1100169 /opt/nydus/bin/containerd-nydus-grpc --config /etc/nydus/config.toml
Jan 17 16:14:22 worker systemd[1]: Started nydus snapshotter.
Jan 17 16:14:22 worker containerd-nydus-grpc[1100169]: time="2024-01-17T16:14:22.998798369Z" level=info msg="Start nydus-snapshotter. Version: v0.7.0-308-g106a6cb, PID: 1100169, FsDriver: fusedev, DaemonMode: dedicated"
Jan 17 16:14:23 worker containerd-nydus-grpc[1100169]: time="2024-01-17T16:14:23.000186538Z" level=info msg="Run daemons monitor..."
```
**NOTE:** By default, the nydus snapshotter operates as a systemd service. If you prefer to run nydus snapshotter as a standalone process, you can set `ENABLE_SYSTEMD_SERVICE` to `false` in `nydus-snapshotter.yaml`.
## Steps for Cleaning up Nydus Snapshotter
We use `preStop`` hook in the DaemonSet to uninstall nydus snapshotter and roll back the containerd configuration.
```bash
# Vanilla kubernetes
$ kubectl delete -f misc/snapshotter/base/nydus-snapshotter.yaml
```
```bash
# k3s
$ kubectl delete -k misc/snapshotter/overlays/k3s/
```
```bash
# rke2
$ kubectl delete -k misc/snapshotter/overlays/rke2/
```
```bash
$ kubectl delete -f misc/snapshotter/nydus-snapshotter-rbac.yaml
$ systemd restart containerd.service
```
## Customized Setup
As we know, nydus snapshotter supports four filesystem drivers (fs_driver): `fusedev`, `fscache`, `blockdev`, `proxy`. Within the container image, we have included configurations for these snapshotter drivers, as well as the corresponding nydusd configurations. By default, the fusedev driver is enabled in the nydus snapshotter, using the snapshotter configuration [`config-fusedev.toml`](../misc/snapshotter/config-fusedev.toml) and the nydusd configuration [`nydusd-config.fusedev.json`](../misc/snapshotter/nydusd-config.fusedev.json).
### Other filesystem driver with related default configuration
If we want to setup the nydus snapshotter with the default configuration for different fs_driver (such as `proxy`), we can modify the values in the `Configmap` in `nydus-snapshotter.yaml`:
```yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nydus-snapshotter-configs
labels:
app: nydus-snapshotter
namespace: nydus-snapshotter
data:
FS_DRIVER: "proxy"
NYDUSD_DAEMON_MODE: "none"
```
Then we can run the nydus snapshotter enabling `proxy` `fs_driver` with the snapshotter configuration [`config-proxy.toml`](../misc/snapshotter/config-proxy.toml).
**NOTE:** The fs_driver (`blockdev` and `proxy`) do not need nydusd, so they do not need nydusd config.
### Same filesystem with different snapshotter configuration and different nydusd configuration
If we want to setup the nydus snapshotter for the same fs_driver (such as `fusedev`) with different snapshotter configuration and different nydusd configuration, we can enable `ENABLE_CONFIG_FROM_VOLUME` and add the snapshotter configuration [`config.toml`](../misc/snapshotter/config.toml) and nydusd configuration [`nydusd-config.json`](../misc/snapshotter/nydusd-config.fusedev.json) in the `Configmap` in `nydus-snapshotter.yaml`:
```yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nydus-snapshotter-configs
labels:
app: nydus-snapshotter
namespace: nydus-snapshotter
data:
ENABLE_CONFIG_FROM_VOLUME: "true"
config.toml: |-
# The snapshotter config content copied here
nydusd-config.json: |-
# The nydusd config content copied here
```
**NOTE:** We need to set `nydusd_config` to `/etc/nydus/nydusd-config.json` in the `config.toml`, so that snapshotter can find the nydusd configuration from configmap.
### Customized Options
| Options | Type | Default | Comment |
| ----------------------------------- | ------ | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| FS_DRIVER | string | "fusedev" | the filesystem driver of snapshotter |
| ENABLE_CONFIG_FROM_VOLUME | bool | false | enabling to use the configurations from volume |
| ENABLE_RUNTIME_SPECIFIC_SNAPSHOTTER | bool | false | enabling to skip to set `plugins."io.containerd.grpc.v1.cri".containerd` to `nydus` for runtime specific snapshotter feature in containerd 1.7+ |
| ENABLE_SYSTEMD_SERVICE | bool | true | enabling to run nydus snapshotter as a systemd service |
================================================
FILE: docs/tarfs.md
================================================
# Nydus Tarfs Mode
`Nydus Tarfs Mode` or `Tarfs` is a working mode for Nydus Image, which uses tar files as Nydus data blobs instead of generating native Nydus data blobs.
### Enable Tarfs
`Nydus Tarfs Mode` is still an experiment feature, please edit the snapshotter configuration file to enable the feature:
```
[experimental.tarfs]
enable_tarfs = true
```
### Generate Raw Disk Image for Each Layer of a Container Image
`Tarfs` supports generating a raw disk image for each layer of a container image, which can be directly mounted as EROFS filesystem through loopdev. Please edit the snapshotter configuration file to enable this submode:
```
[experimental.tarfs]
enable_tarfs = true
export_mode = "layer_block"
```
This is an example to generate and verify raw disk image for each layer of a container image:
```
$ containerd-nydus-grpc --config /etc/nydus/config.toml &
$ nerdctl run --snapshotter nydus --rm nginx
# Show mounted rootfs a container
$ mount
/dev/loop17 on /var/lib/containerd/io.containerd.snapshotter.v1.nydus/snapshots/7/mnt type erofs (ro,relatime,user_xattr,acl,cache_strategy=readaround)
# Show loop devices used to mount layers and bootstrap for a container image
$ losetup
NAME SIZELIMIT OFFSET AUTOCLEAR RO BACK-FILE DIO LOG-SEC
/dev/loop11 0 0 0 0 /var/lib/containerd/io.containerd.snapshotter.v1.nydus/cache/fd9f026c631046113bd492f69761c3ba6042c791c35a60e7c7f3b8f254592daa 0 512
/dev/loop12 0 0 0 0 /var/lib/containerd/io.containerd.snapshotter.v1.nydus/cache/055fa98b43638b67d10c58d41094d99c8696cc34b7a960c7a0cc5d9d152d12b3 0 512
/dev/loop13 0 0 0 0 /var/lib/containerd/io.containerd.snapshotter.v1.nydus/cache/96576293dd2954ff84251aa0455687c8643358ba1b190ea1818f56b41884bdbd 0 512
/dev/loop14 0 0 0 0 /var/lib/containerd/io.containerd.snapshotter.v1.nydus/cache/a7c4092be9044bd4eef78f27c95785ef3a9f345d01fd4512bc94ddaaefc359f4 0 512
/dev/loop15 0 0 0 0 /var/lib/containerd/io.containerd.snapshotter.v1.nydus/cache/e3b6889c89547ec9ba653ab44ed32a99370940d51df956968c0d578dd61ab665 0 512
/dev/loop16 0 0 0 0 /var/lib/containerd/io.containerd.snapshotter.v1.nydus/cache/da761d9a302b21dc50767b67d46f737f5072fb4490c525b4a7ae6f18e1dbbf75 0 512
/dev/loop17 0 0 0 0 /var/lib/containerd/io.containerd.snapshotter.v1.nydus/snapshots/7/fs/image/image.boot 0 512
# Files without suffix are tar files, files with suffix `layer.disk` are raw disk image for container image layers
$ ls -l /var/lib/containerd/io.containerd.snapshotter.v1.nydus/cache/
total 376800
-rw-r--r-- 1 root root 3584 Aug 30 23:18 055fa98b43638b67d10c58d41094d99c8696cc34b7a960c7a0cc5d9d152d12b3
-rw-r--r-- 1 root root 527872 Aug 30 23:18 055fa98b43638b67d10c58d41094d99c8696cc34b7a960c7a0cc5d9d152d12b3.layer.disk
-rw-r--r-- 1 root root 77814784 Aug 30 23:18 52d2b7f179e32b4cbd579ee3c4958027988f9a8274850ab0c7c24661e3adaac5
-rw-r--r-- 1 root root 78863360 Aug 30 23:18 52d2b7f179e32b4cbd579ee3c4958027988f9a8274850ab0c7c24661e3adaac5.layer.disk
-rw-r--r-- 1 root root 4608 Aug 30 23:18 96576293dd2954ff84251aa0455687c8643358ba1b190ea1818f56b41884bdbd
-rw-r--r-- 1 root root 528896 Aug 30 23:18 96576293dd2954ff84251aa0455687c8643358ba1b190ea1818f56b41884bdbd.layer.disk
-rw-r--r-- 1 root root 2560 Aug 30 23:18 a7c4092be9044bd4eef78f27c95785ef3a9f345d01fd4512bc94ddaaefc359f4
-rw-r--r-- 1 root root 526848 Aug 30 23:18 a7c4092be9044bd4eef78f27c95785ef3a9f345d01fd4512bc94ddaaefc359f4.layer.disk
-rw-r--r-- 1 root root 7168 Aug 30 23:18 da761d9a302b21dc50767b67d46f737f5072fb4490c525b4a7ae6f18e1dbbf75
-rw-r--r-- 1 root root 531456 Aug 30 23:18 da761d9a302b21dc50767b67d46f737f5072fb4490c525b4a7ae6f18e1dbbf75.layer.disk
-rw-r--r-- 1 root root 5120 Aug 30 23:18 e3b6889c89547ec9ba653ab44ed32a99370940d51df956968c0d578dd61ab665
-rw-r--r-- 1 root root 529408 Aug 30 23:18 e3b6889c89547ec9ba653ab44ed32a99370940d51df956968c0d578dd61ab665.layer.disk
-rw-r--r-- 1 root root 112968704 Aug 30 23:18 fd9f026c631046113bd492f69761c3ba6042c791c35a60e7c7f3b8f254592daa
-rw-r--r-- 1 root root 113492992 Aug 30 23:18 fd9f026c631046113bd492f69761c3ba6042c791c35a60e7c7f3b8f254592daa.layer.disk
$ file /var/lib/containerd/io.containerd.snapshotter.v1.nydus/cache/055fa98b43638b67d10c58d41094d99c8696cc34b7a960c7a0cc5d9d152d12b3
/var/lib/containerd/io.containerd.snapshotter.v1.nydus/cache/055fa98b43638b67d10c58d41094d99c8696cc34b7a960c7a0cc5d9d152d12b3: POSIX tar archive
# Mount the raw disk image for a container image layer
$ losetup /dev/loop100 /var/lib/containerd/io.containerd.snapshotter.v1.nydus/cache/055fa98b43638b67d10c58d41094d99c8696cc34b7a960c7a0cc5d9d152d12b3.layer.disk
$ mount -t erofs /dev/loop100 ./mnt/
$ mount
tmpfs on /run/user/0 type tmpfs (rw,nosuid,nodev,relatime,size=1544836k,nr_inodes=386209,mode=700,inode64)
/dev/loop17 on /var/lib/containerd/io.containerd.snapshotter.v1.nydus/snapshots/7/mnt type erofs (ro,relatime,user_xattr,acl,cache_strategy=readaround)
/dev/loop100 on /root/ws/nydus-snapshotter.git/mnt type erofs (ro,relatime,user_xattr,acl,cache_strategy=readaround)
```
### Generate Raw Disk Image for a Container Image
`Tarfs` supports generating a raw disk image a container image, which can be directly mounted as EROFS filesystem through loopdev. Please edit the snapshotter configuration file to enable this submode:
```
[experimental.tarfs]
enable_tarfs = true
export_mode = "image_block"
```
This is an example to generate and verify raw disk image for a container image:
```
$ containerd-nydus-grpc --config /etc/nydus/config.toml &
$ nerdctl run --snapshotter nydus --rm nginx
# Files without suffix are tar files, files with suffix `image.disk` are raw disk image for a container image
$ ls -l /var/lib/containerd/io.containerd.snapshotter.v1.nydus/cache/
total 376320
-rw-r--r-- 1 root root 3584 Aug 30 23:35 055fa98b43638b67d10c58d41094d99c8696cc34b7a960c7a0cc5d9d152d12b3
-rw-r--r-- 1 root root 77814784 Aug 30 23:35 52d2b7f179e32b4cbd579ee3c4958027988f9a8274850ab0c7c24661e3adaac5
-rw-r--r-- 1 root root 4608 Aug 30 23:35 96576293dd2954ff84251aa0455687c8643358ba1b190ea1818f56b41884bdbd
-rw-r--r-- 1 root root 2560 Aug 30 23:35 a7c4092be9044bd4eef78f27c95785ef3a9f345d01fd4512bc94ddaaefc359f4
-rw-r--r-- 1 root root 7168 Aug 30 23:35 da761d9a302b21dc50767b67d46f737f5072fb4490c525b4a7ae6f18e1dbbf75
-rw-r--r-- 1 root root 194518016 Aug 30 23:36 da761d9a302b21dc50767b67d46f737f5072fb4490c525b4a7ae6f18e1dbbf75.image.disk
-rw-r--r-- 1 root root 5120 Aug 30 23:35 e3b6889c89547ec9ba653ab44ed32a99370940d51df956968c0d578dd61ab665
-rw-r--r-- 1 root root 112968704 Aug 30 23:36 fd9f026c631046113bd492f69761c3ba6042c791c35a60e7c7f3b8f254592daa
```
### Generate Raw Disk Image with dm-verity Information
`Tarfs` supports generating raw disk images with dm-verity information, to enable runtime data integrity validation. Please change `export_mode` in snapshotter configuration file to `layer_block_with_verity` or `image_block_with_verity`.
```
[experimental.tarfs]
enable_tarfs = true
export_mode = "image_block_with_verity"
```
This is an example to generate and verify raw disk image for a container image with dm-verity information:
```
$ containerd-nydus-grpc --config /etc/nydus/config.toml &
$ nerdctl run --snapshotter nydus --rm nginx
# Files without suffix are tar files, files with suffix `image.disk` are raw disk image for a container image
$ ls -l /var/lib/containerd/io.containerd.snapshotter.v1.nydus/cache/
total 388296
-rw-r--r-- 1 root root 3584 Aug 30 23:45 055fa98b43638b67d10c58d41094d99c8696cc34b7a960c7a0cc5d9d152d12b3
-rw-r--r-- 1 root root 77814784 Aug 30 23:46 52d2b7f179e32b4cbd579ee3c4958027988f9a8274850ab0c7c24661e3adaac5
-rw-r--r-- 1 root root 4608 Aug 30 23:45 96576293dd2954ff84251aa0455687c8643358ba1b190ea1818f56b41884bdbd
-rw-r--r-- 1 root root 2560 Aug 30 23:45 a7c4092be9044bd4eef78f27c95785ef3a9f345d01fd4512bc94ddaaefc359f4
-rw-r--r-- 1 root root 7168 Aug 30 23:45 da761d9a302b21dc50767b67d46f737f5072fb4490c525b4a7ae6f18e1dbbf75
-rw-r--r-- 1 root root 206782464 Aug 30 23:46 da761d9a302b21dc50767b67d46f737f5072fb4490c525b4a7ae6f18e1dbbf75.image.disk
-rw-r--r-- 1 root root 5120 Aug 30 23:45 e3b6889c89547ec9ba653ab44ed32a99370940d51df956968c0d578dd61ab665
-rw-r--r-- 1 root root 112968704 Aug 30 23:46 fd9f026c631046113bd492f69761c3ba6042c791c35a60e7c7f3b8f254592daa
$ losetup /dev/loop100 /var/lib/containerd/io.containerd.snapshotter.v1.nydus/cache/da761d9a302b21dc50767b67d46f737f5072fb4490c525b4a7ae6f18e1dbbf75.image.disk
$ veritysetup open --no-superblock --format=1 -s "" --hash=sha256 --data-block-size=512 --hash-block-size=4096 --data-blocks 379918 --hash-offset 194519040 /dev/loop100 image1 /dev/loop100 8113799aaf9a5d14feca1eadc3b7e6ea98bdaf61e3a2e4a8ef8c24e26a551efd
$ lsblk
loop100 7:100 0 197.2M 0 loop
└─dm-0 252:0 0 185.5M 1 crypt
$ veritysetup status dm-0
/dev/mapper/dm-0 is active and is in use.
type: VERITY
status: verified
hash type: 1
data block: 512
hash block: 4096
hash name: sha256
salt: -
data device: /dev/loop100
data loop: /var/lib/containerd/io.containerd.snapshotter.v1.nydus/cache/da761d9a302b21dc50767b67d46f737f5072fb4490c525b4a7ae6f18e1dbbf75.image.disk
size: 379918 sectors
mode: readonly
hash device: /dev/loop100
hash loop: /var/lib/containerd/io.containerd.snapshotter.v1.nydus/cache/da761d9a302b21dc50767b67d46f737f5072fb4490c525b4a7ae6f18e1dbbf75.image.disk
hash offset: 379920 sectors
root hash: 8113799aaf9a5d14feca1eadc3b7e6ea98bdaf61e3a2e4a8ef8c24e26a551efd
$ mount -t erofs /dev/dm-0 ./mnt/
mount: /root/ws/nydus-snapshotter.git/mnt: WARNING: source write-protected, mounted read-only.
$ ls -l mnt/
total 14
lrwxrwxrwx 1 root root 7 Aug 14 08:00 bin -> usr/bin
drwxr-xr-x 2 root root 27 Jul 15 00:00 boot
drwxr-xr-x 2 root root 27 Aug 14 08:00 dev
drwxr-xr-x 2 root root 184 Aug 16 17:50 docker-entrypoint.d
-rwxrwxr-x 1 root root 1620 Aug 16 17:50 docker-entrypoint.sh
drwxr-xr-x 34 root root 1524 Aug 16 17:50 etc
drwxr-xr-x 2 root root 27 Jul 15 00:00 home
lrwxrwxrwx 1 root root 7 Aug 14 08:00 lib -> usr/lib
lrwxrwxrwx 1 root root 9 Aug 14 08:00 lib32 -> usr/lib32
lrwxrwxrwx 1 root root 9 Aug 14 08:00 lib64 -> usr/lib64
lrwxrwxrwx 1 root root 10 Aug 14 08:00 libx32 -> usr/libx32
drwxr-xr-x 2 root root 27 Aug 14 08:00 media
drwxr-xr-x 2 root root 27 Aug 14 08:00 mnt
drwxr-xr-x 2 root root 27 Aug 14 08:00 opt
drwxr-xr-x 2 root root 27 Jul 15 00:00 proc
drwx------ 2 root root 66 Aug 14 08:00 root
drwxr-xr-x 3 root root 43 Aug 14 08:00 run
lrwxrwxrwx 1 root root 8 Aug 14 08:00 sbin -> usr/sbin
drwxr-xr-x 2 root root 27 Aug 14 08:00 srv
drwxr-xr-x 2 root root 27 Jul 15 00:00 sys
drwxrwxrwt 2 root root 27 Aug 16 17:50 tmp
drwxr-xr-x 14 root root 229 Aug 14 08:00 usr
drwxr-xr-x 11 root root 204 Aug 14 08:00 var
```
================================================
FILE: export/snapshotter/snapshotter.go
================================================
package snapshotter
import (
"github.com/containerd/containerd/v2/plugins"
"github.com/containerd/platforms"
"github.com/containerd/plugin"
"github.com/containerd/plugin/registry"
"github.com/pkg/errors"
"github.com/containerd/nydus-snapshotter/config"
"github.com/containerd/nydus-snapshotter/snapshot"
)
func init() {
registry.Register(&plugin.Registration{
Type: plugins.SnapshotPlugin,
ID: "nydus",
Config: &config.SnapshotterConfig{},
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
ic.Meta.Platforms = append(ic.Meta.Platforms, platforms.DefaultSpec())
cfg, ok := ic.Config.(*config.SnapshotterConfig)
if !ok {
return nil, errors.New("invalid nydus snapshotter configuration")
}
root := ic.Properties[plugins.PropertyRootDir]
if root == "" {
cfg.Root = root
}
if err := cfg.FillUpWithDefaults(); err != nil {
return nil, errors.New("failed to fill up nydus configuration with defaults")
}
rs, err := snapshot.NewSnapshotter(ic.Context, cfg)
if err != nil {
return nil, errors.Wrap(err, "failed to initialize snapshotter")
}
return rs, nil
},
})
}
================================================
FILE: go.mod
================================================
module github.com/containerd/nydus-snapshotter
go 1.25.9
require (
dario.cat/mergo v1.0.1
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6
github.com/KarpelesLab/reflink v1.0.1
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
github.com/aws/aws-sdk-go-v2 v1.41.5
github.com/aws/aws-sdk-go-v2/config v1.32.15
github.com/aws/aws-sdk-go-v2/credentials v1.19.14
github.com/aws/aws-sdk-go-v2/feature/s3/transfermanager v0.1.16
github.com/aws/aws-sdk-go-v2/service/s3 v1.99.0
github.com/containerd/cgroups/v3 v3.0.3
github.com/containerd/containerd/api v1.8.0
github.com/containerd/containerd/v2 v2.0.7
github.com/containerd/continuity v0.4.4
github.com/containerd/errdefs v1.0.0
github.com/containerd/fifo v1.1.0
github.com/containerd/log v0.1.0
github.com/containerd/nri v0.8.0
github.com/containerd/platforms v1.0.0-rc.1
github.com/containerd/plugin v1.0.0
github.com/containerd/stargz-snapshotter v0.15.2-0.20240709063920-1dac5ef89319
github.com/containerd/stargz-snapshotter/estargz v0.15.2-0.20240709063920-1dac5ef89319
github.com/containers/ocicrypt v1.2.1
github.com/distribution/reference v0.6.0
github.com/docker/cli v29.4.0+incompatible
github.com/freddierice/go-losetup v0.0.0-20220711213114-2a14873012db
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
github.com/google/go-containerregistry v0.20.1
github.com/gorilla/mux v1.8.1
github.com/hashicorp/go-retryablehttp v0.7.7
github.com/klauspost/compress v1.17.11
github.com/moby/locker v1.0.1
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.0
github.com/opencontainers/runtime-spec v1.2.0
github.com/pelletier/go-toml v1.9.5
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.20.5
github.com/prometheus/client_model v0.6.1
github.com/rs/xid v1.5.0
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.11.1
github.com/urfave/cli/v2 v2.27.5
go.etcd.io/bbolt v1.3.11
go.opentelemetry.io/otel v1.39.0
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc
golang.org/x/sync v0.20.0
golang.org/x/sys v0.43.0
google.golang.org/grpc v1.79.3
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gotest.tools v2.2.0+incompatible
k8s.io/api v0.31.2
k8s.io/apimachinery v0.31.2
k8s.io/client-go v0.31.2
k8s.io/cri-api v0.31.2
k8s.io/kubelet v0.31.2
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8
sigs.k8s.io/yaml v1.4.0
)
require (
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.12.9 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.0.9 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.15 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.19 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.41.10 // indirect
github.com/aws/smithy-go v1.24.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cilium/ebpf v0.11.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/ttrpc v1.2.7 // indirect
github.com/containerd/typeurl/v2 v2.2.3 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/cyphar/filepath-securejoin v0.5.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/moby/sys/mountinfo v0.7.2 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/signal v0.7.1 // indirect
github.com/moby/sys/user v0.3.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/opencontainers/selinux v1.13.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/smallstep/pkcs7 v0.1.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 // indirect
github.com/vbatts/tar-split v0.11.5 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect
go.opentelemetry.io/otel/metric v1.39.0 // indirect
go.opentelemetry.io/otel/trace v1.39.0 // indirect
golang.org/x/crypto v0.50.0 // indirect
golang.org/x/net v0.53.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/term v0.42.0 // indirect
golang.org/x/text v0.36.0 // indirect
golang.org/x/time v0.12.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
)
retract (
v0.11.3
v0.11.2
v0.11.1
v0.11.0
v0.3.0 // retagged: cd604c1b597558ea045a79c4f80a8c780909801b -> 85653575c7dafb6b06548478ee1dc61ac5905d00
)
exclude (
// These dependencies were updated to "master" in some modules we depend on,
// but have no code-changes since their last release. Unfortunately, this also
// causes a ripple effect, forcing all users of the containerd module to also
// update these dependencies to an unrelease / un-tagged version.
//
// Both these dependencies will unlikely do a new release in the near future,
// so exclude these versions so that we can downgrade to the current release.
//
// For additional details, see this PR and links mentioned in that PR:
// https://github.com/kubernetes-sigs/kustomize/pull/5830#issuecomment-2569960859
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
)
================================================
FILE: go.sum
================================================
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2 h1:dIScnXFlF784X79oi7MzVT6GWqr/W1uUt0pB5CsDs9M=
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2/go.mod h1:gCLVsLfv1egrcZu+GoJATN5ts75F2s62ih/457eWzOw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/KarpelesLab/reflink v1.0.1 h1:d+tdjliwOCqvub9bl0Y02GxahWkNqejNb3TZTTUcQWA=
github.com/KarpelesLab/reflink v1.0.1/go.mod h1:WGkTOKNjd1FsJKBw3mu4JvrPEDJyJJ+JPtxBkbPoCok=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Microsoft/hcsshim v0.12.9 h1:2zJy5KA+l0loz1HzEGqyNnjd3fyZA31ZBCGKacp6lLg=
github.com/Microsoft/hcsshim v0.12.9/go.mod h1:fJ0gkFAna6ukt0bLdKB8djt4XIJhF/vEPuoIWYVvZ8Y=
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY=
github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
github.com/aws/aws-sdk-go-v2/config v1.32.15 h1:i7rHbaySnBXGvCkDndaBU8f3EAlRVgViwNfkwFUrXgE=
github.com/aws/aws-sdk-go-v2/config v1.32.15/go.mod h1:yLJzL0IkI9+4BwjPSOueyHzppJj3t0dhK5tbmmcFk5Q=
github.com/aws/aws-sdk-go-v2/credentials v1.19.14 h1:n+UcGWAIZHkXzYt87uMFBv/l8THYELoX6gVcUvgl6fI=
github.com/aws/aws-sdk-go-v2/credentials v1.19.14/go.mod h1:cJKuyWB59Mqi0jM3nFYQRmnHVQIcgoxjEMAbLkpr62w=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21 h1:NUS3K4BTDArQqNu2ih7yeDLaS3bmHD0YndtA6UP884g=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21/go.mod h1:YWNWJQNjKigKY1RHVJCuupeWDrrHjRqHm0N9rdrWzYI=
github.com/aws/aws-sdk-go-v2/feature/s3/transfermanager v0.1.16 h1:n8TmP5vlknh1B/mVNrNgQfSvQy0isR9B9IgADdwuhhY=
github.com/aws/aws-sdk-go-v2/feature/s3/transfermanager v0.1.16/go.mod h1:Iu9wL4lqscFF6ByhqyDO8mgvCUwGn5bqWr7fuOgUjTA=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ=
github.com/aws/aws-sdk-go-v2/service/s3 v1.99.0 h1:hlSuz394kV0vhv9drL5lhuEFbEOEP1VyQpy15qWh1Pk=
github.com/aws/aws-sdk-go-v2/service/s3 v1.99.0/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.9 h1:QKZH0S178gCmFEgst8hN0mCX1KxLgHBKKY/CLqwP8lg=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.9/go.mod h1:7yuQJoT+OoH8aqIxw9vwF+8KpvLZ8AWmvmUWHsGQZvI=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.15 h1:lFd1+ZSEYJZYvv9d6kXzhkZu07si3f+GQ1AaYwa2LUM=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.15/go.mod h1:WSvS1NLr7JaPunCXqpJnWk1Bjo7IxzZXrZi1QQCkuqM=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.19 h1:dzztQ1YmfPrxdrOiuZRMF6fuOwWlWpD2StNLTceKpys=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.19/go.mod h1:YO8TrYtFdl5w/4vmjL8zaBSsiNp3w0L1FfKVKenZT7w=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.10 h1:p8ogvvLugcR/zLBXTXrTkj0RYBUdErbMnAFFp12Lm/U=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.10/go.mod h1:60dv0eZJfeVXfbT1tFJinbHrDfSJ2GZl4Q//OSSNAVw=
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y=
github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0=
github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0=
github.com/containerd/containerd/api v1.8.0 h1:hVTNJKR8fMc/2Tiw60ZRijntNMd1U+JVMyTRdsD2bS0=
github.com/containerd/containerd/api v1.8.0/go.mod h1:dFv4lt6S20wTu/hMcP4350RL87qPWLVa/OHOwmmdnYc=
github.com/containerd/containerd/v2 v2.0.7 h1:55JsNhqP/L7VZOijyfq6Qn0O8Oeff0UizfRuP+2pc90=
github.com/containerd/containerd/v2 v2.0.7/go.mod h1:su8B0Z1NFQMEIztOIbHwy7xtznbCms/kFlfsxIcQrZ8=
github.com/containerd/continuity v0.4.4 h1:/fNVfTJ7wIl/YPMHjf+5H32uFhl63JucB34PlCpMKII=
github.com/containerd/continuity v0.4.4/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY=
github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/nri v0.8.0 h1:n1S753B9lX8RFrHYeSgwVvS1yaUcHjxbB+f+xzEncRI=
github.com/containerd/nri v0.8.0/go.mod h1:uSkgBrCdEtAiEz4vnrq8gmAC4EnVAM5Klt0OuK5rZYQ=
github.com/containerd/platforms v1.0.0-rc.1 h1:83KIq4yy1erSRgOVHNk1HYdPvzdJ5CnsWaRoJX4C41E=
github.com/containerd/platforms v1.0.0-rc.1/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4=
github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y=
github.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8=
github.com/containerd/stargz-snapshotter v0.15.2-0.20240709063920-1dac5ef89319 h1:Td/dlhRp/kIk9W1rjXHSL87zZZiBQaKPV18OnoEREUA=
github.com/containerd/stargz-snapshotter v0.15.2-0.20240709063920-1dac5ef89319/go.mod h1:dgo5lVziOOnWX8SxxHqYuc8ShsQou54eKLdahxFlHVc=
github.com/containerd/stargz-snapshotter/estargz v0.15.2-0.20240709063920-1dac5ef89319 h1:BRxgmkGWi5vAvajiCwEK+xit4FeFU3GRjbiX4DKTLtM=
github.com/containerd/stargz-snapshotter/estargz v0.15.2-0.20240709063920-1dac5ef89319/go.mod h1:9WSor0wu2swhtYoFkrjy3GHt7aNgKR2A7FhnpP+CH5o=
github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ=
github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o=
github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40=
github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk=
github.com/containers/ocicrypt v1.2.1 h1:0qIOTT9DoYwcKmxSt8QJt+VzMY18onl9jUXsxpVhSmM=
github.com/containers/ocicrypt v1.2.1/go.mod h1:aD0AAqfMp0MtwqWgHM1bUwe1anx0VazI108CRrSKINQ=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.5.1 h1:eYgfMq5yryL4fbWfkLpFFy2ukSELzaJOTaUTuh+oF48=
github.com/cyphar/filepath-securejoin v0.5.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
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/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/cli v29.4.0+incompatible h1:+IjXULMetlvWJiuSI0Nbor36lcJ5BTcVpUmB21KBoVM=
github.com/docker/cli v29.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/freddierice/go-losetup v0.0.0-20220711213114-2a14873012db h1:StM6A9LvaVrFS2chAGcfRVDoBB6rHYPIGJ3GknpB25c=
github.com/freddierice/go-losetup v0.0.0-20220711213114-2a14873012db/go.mod h1:pwuQfHWn6j2Fpl2AWw/bPLlKfojHxIIEa5TeKIgDFW4=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-containerregistry v0.20.1 h1:eTgx9QNYugV4DN5mz4U8hiAGTi1ybXn0TPi4Smd8du0=
github.com/google/go-containerregistry v0.20.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM=
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0=
github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8=
github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo=
github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo/v2 v2.19.1 h1:QXgq3Z8Crl5EL1WBAC98A5sEBHARrAJNzAmMxzLcRF0=
github.com/onsi/ginkgo/v2 v2.19.1/go.mod h1:O3DtEWQkPa/F7fBMgmZQKKsluAy8pd3rEQdrjkPb9zA=
github.com/onsi/gomega v1.34.0 h1:eSSPsPNp6ZpsG8X1OVmOTxig+CblTc4AxpPBykhe2Os=
github.com/onsi/gomega v1.34.0/go.mod h1:MIKI8c+f+QLWk+hxbePD4i0LMJSExPaZOVfkoex4cAo=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk=
github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.13.1 h1:A8nNeceYngH9Ow++M+VVEwJVpdFmrlxsN22F+ISDCJE=
github.com/opencontainers/selinux v1.13.1/go.mod h1:S10WXZ/osk2kWOYKy1x2f/eXF5ZHJoUs8UU/2caNRbg=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smallstep/pkcs7 v0.1.1 h1:x+rPdt2W088V9Vkjho4KtoggyktZJlMduZAtRHm68LU=
github.com/smallstep/pkcs7 v0.1.1/go.mod h1:dL6j5AIz9GHjVEBTXtW+QliALcgM19RtXaTeyxI+AfA=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 h1:pnnLyeX7o/5aX8qUQ69P/mLojDqwda8hFOCBTmP/6hw=
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6/go.mod h1:39R/xuhNgVhi+K0/zst4TLrJrVmbm6LVgl4A0+ZFS5M=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts=
github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc=
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY=
gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0=
k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk=
k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw=
k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc=
k8s.io/client-go v0.31.2/go.mod h1:NPa74jSVR/+eez2dFsEIHNa+3o09vtNaWwWwb1qSxSs=
k8s.io/component-base v0.31.2 h1:Z1J1LIaC0AV+nzcPRFqfK09af6bZ4D1nAOpWsy9owlA=
k8s.io/component-base v0.31.2/go.mod h1:9PeyyFN/drHjtJZMCTkSpQJS3U9OXORnHQqMLDz0sUQ=
k8s.io/cri-api v0.31.2 h1:O/weUnSHvM59nTio0unxIUFyRHMRKkYn96YDILSQKmo=
k8s.io/cri-api v0.31.2/go.mod h1:Po3TMAYH/+KrZabi7QiwQI4a692oZcUOUThd/rqwxrI=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
k8s.io/kubelet v0.31.2 h1:6Hytyw4LqWqhgzoi7sPfpDGClu2UfxmPmaiXPC4FRgI=
k8s.io/kubelet v0.31.2/go.mod h1:0E4++3cMWi2cJxOwuaQP3eMBa7PSOvAFgkTPlVc/2FA=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
================================================
FILE: integration/Dockerfile
================================================
ARG CONTAINERD_VER=2.0.0-rc.3
ARG CONTAINERD_PROJECT=/containerd
ARG RUNC_VER=1.1.4
ARG NYDUS_SNAPSHOTTER_PROJECT=/nydus-snapshotter
ARG DOWNLOADS_MIRROR="https://github.com"
ARG NYDUS_VER=2.3.0
ARG NERDCTL_VER=1.7.6
ARG DELVE_VER=1.23.0
ARG GO_VER=1.24.0-bookworm
FROM golang:$GO_VER AS golang-base
ARG TARGETARCH
ARG CONTAINERD_VER
ARG CONTAINERD_PROJECT
ARG RUNC_VER
ARG NYDUS_SNAPSHOTTER_PROJECT
ARG DOWNLOADS_MIRROR
ARG NYDUS_VER
ARG NERDCTL_VER
ARG DELVE_VER
# RUN echo '\
# deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye main contrib non-free\n\
# deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-updates main contrib non-free\n\
# deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-backports main contrib non-free\n\
# deb https://mirrors.tuna.tsinghua.edu.cn/debian-security bullseye-security main contrib non-free\n' > /etc/apt/sources.list
RUN apt-get update -qq && apt-get install -qq libbtrfs-dev libseccomp-dev sudo psmisc jq lsof net-tools
RUN go install github.com/go-delve/delve/cmd/dlv@v"$DELVE_VER"
# Install containerd
RUN wget -q "$DOWNLOADS_MIRROR"/containerd/containerd/releases/download/v"$CONTAINERD_VER"/containerd-"$CONTAINERD_VER"-linux-"$TARGETARCH".tar.gz && \
tar xzf containerd-"$CONTAINERD_VER"-linux-"$TARGETARCH".tar.gz && \
install -D -m 755 bin/* /usr/local/bin/
COPY misc/example/containerd-config.toml /etc/containerd/config.toml
# Install runc
RUN wget -q "$DOWNLOADS_MIRROR"/opencontainers/runc/releases/download/v"$RUNC_VER"/runc."$TARGETARCH" && \
install -D -m 755 runc."$TARGETARCH" /usr/local/bin/runc
# Install nydusd nydus-image
RUN wget -q "$DOWNLOADS_MIRROR"/dragonflyoss/nydus/releases/download/v"$NYDUS_VER"/nydus-static-v"$NYDUS_VER"-linux-"$TARGETARCH".tgz && \
tar xzf nydus-static-v"$NYDUS_VER"-linux-"$TARGETARCH".tgz && \
install -D -m 755 nydus-static/nydusd /usr/local/bin/nydusd && \
install -D -m 755 nydus-static/nydus-image /usr/local/bin/nydus-image && \
install -D -m 755 nydus-static/nydusctl /usr/local/bin/nydusctl
# Install nerdctl
RUN wget -q "$DOWNLOADS_MIRROR"/containerd/nerdctl/releases/download/v"$NERDCTL_VER"/nerdctl-"$NERDCTL_VER"-linux-"$TARGETARCH".tar.gz && \
tar xzf nerdctl-"$NERDCTL_VER"-linux-"$TARGETARCH".tar.gz && \
install -D -m 755 nerdctl /usr/local/bin/nerdctl
# Install fscache driver configuration file
COPY misc/snapshotter/nydusd-config.fscache.json /etc/nydus/nydusd-config.fscache.json
COPY misc/snapshotter/nydusd-config-localfs.json /etc/nydus/nydusd-config-localfs.json
COPY misc/snapshotter/config.toml /etc/nydus/config.toml
VOLUME [ "/var/lib" ]
COPY integration/entrypoint.sh /
WORKDIR /nydus-snapshotter
ENV PATH="${PATH}:/usr/local/bin/"
# Prevent git from complaining about ownership
RUN git config --global --add safe.directory /nydus-snapshotter
ENTRYPOINT [ "/bin/bash", "-c", "make install && /entrypoint.sh"]
================================================
FILE: integration/entrypoint.sh
================================================
#!/bin/bash
# Copyright (c) 2022. Nydus Developers. All rights reserved.
#
# SPDX-License-Identifier: Apache-2.0
set -eEuo pipefail
FSCACHE_NYDUSD_CONFIG=/etc/nydus/nydusd-config.fscache.json
FUSE_NYDUSD_LOCALFS_CONFIG=/etc/nydus/nydusd-config-localfs.json
SNAPSHOTTER_CONFIG=/etc/nydus/config.toml
CONTAINERD_ROOT=/var/lib/containerd/
CONTAINERD_STATUS=/run/containerd/
REMOTE_SNAPSHOTTER_SOCKET=/run/containerd-nydus/containerd-nydus-grpc.sock
REMOTE_SNAPSHOTTER_ROOT=/var/lib/containerd/io.containerd.snapshotter.v1.nydus
CONTAINERD_SOCKET=/run/containerd/containerd.sock
SNAPSHOTTER_SHARED_MNT=${REMOTE_SNAPSHOTTER_ROOT}/mnt
SNAPSHOTTER_CACHE_DIR=${REMOTE_SNAPSHOTTER_ROOT}/cache
JAVA_IMAGE=${JAVA_IMAGE:-ghcr.io/dragonflyoss/image-service/java:nydus-nightly-v6}
WORDPRESS_IMAGE=${WORDPRESS_IMAGE:-ghcr.io/dragonflyoss/image-service/wordpress:nydus-nightly-v6}
TOMCAT_IMAGE=${TOMCAT_IMAGE:-ghcr.io/dragonflyoss/image-service/tomcat:nydus-nightly-v5}
STARGZ_IMAGE=${STARGZ_IMAGE:-ghcr.io/stargz-containers/wordpress:5.9.2-esgz}
REDIS_OCI_IMAGE=${REDIS_OCI_IMAGE:-ghcr.io/stargz-containers/redis:6.2.6-org}
WORDPRESS_OCI_IMAGE=${WORDPRESS_OCI_IMAGE:-ghcr.io/dragonflyoss/image-service/wordpress:latest}
PLUGIN=nydus
RETRYNUM=30
RETRYINTERVAL=1
TIMEOUTSEC=180
GORACE_REPORT="$(pwd)/go_race_report"
export GORACE="log_path=${GORACE_REPORT}"
# trap "{ pause 1000; }" ERR
function detect_go_race {
if [ -n "$(ls -A ${GORACE_REPORT}.* 2>/dev/null)" ]; then
echo "go race detected"
reports=$(ls -A ${GORACE_REPORT}.* 2>/dev/null)
for r in ${reports}; do
cat "$r"
done
exit 1
fi
}
function stop_all_containers {
containers=$(nerdctl ps -q | tr '\n' ' ')
if [[ ${containers} == "" ]]; then
return 0
else
echo "Killing containers ${containers}"
for C in ${containers}; do
nerdctl kill "${C}" || true
nerdctl stop "${C}" || true
nerdctl rm "${C}" || true
done
return 1
fi
}
function pause {
echo "I am going to wait for ${1} seconds only ..."
sleep "${1}"
}
function func_retry {
local SUCCESS=false
for i in $(seq ${RETRYNUM}); do
if "${*}"; then
SUCCESS=true
break
fi
echo "Fail(${i}). Retrying function..."
sleep ${RETRYINTERVAL}
done
if [ "${SUCCESS}" == "true" ]; then
return 0
else
return 1
fi
}
function retry {
local SUCCESS=false
for i in $(seq ${RETRYNUM}); do
if eval "timeout ${TIMEOUTSEC} ${@}"; then
SUCCESS=true
break
fi
echo "Fail(${i}). Retrying..."
sleep ${RETRYINTERVAL}
done
if [ "${SUCCESS}" == "true" ]; then
return 0
else
return 1
fi
}
function can_erofs_ondemand_read {
return 1
# grep 'CONFIG_EROFS_FS_ONDEMAND=[ym]' /usr/src/linux-headers-"$(uname -r)"/.config 1>/dev/null
# echo $?
}
function validate_mnt_number {
expected="${1}"
found=$(mount -t fuse | wc -l)
if [[ $found != "$expected" ]]; then
echo "expecting $expected mountpoints, but found $found"
return 1
else
return 0
fi
}
function set_config_option {
KEY="${1}"
VALUE="${2}"
sed -i "s/\($KEY *= *\).*/\1$VALUE/" "${SNAPSHOTTER_CONFIG}"
}
function set_recover_policy {
policy="${1}"
set_config_option "recover_policy" \"${policy}\"
}
function set_enable_referrer_detect {
set_config_option "enable_referrer_detect" "true"
}
function reboot_containerd {
killall "containerd" || true
killall "containerd-nydus-grpc" || true
# In case nydusd is using cache dir
killall "nydusd" || true
# Let snapshotter shutdown all its services.
sleep 2
# FIXME
echo "umount globally shared mountpoint"
umount_global_shared_mnt
rm -rf "${CONTAINERD_STATUS}"*
rm -rf "${CONTAINERD_ROOT}"*
if [ -f "${REMOTE_SNAPSHOTTER_SOCKET}" ]; then
rm "${REMOTE_SNAPSHOTTER_SOCKET}"
fi
local daemon_mode=${1}
local fs_driver=${2:-fusedev}
local recover_policy=${3:-none}
if [ -d "${REMOTE_SNAPSHOTTER_ROOT:?}/snapshotter/snapshots/" ]; then
umount -t fuse --all
fi
if [[ "${fs_driver}" == fusedev ]]; then
nydusd_config=/etc/nydus/nydusd-config.json
else
nydusd_config="$FSCACHE_NYDUSD_CONFIG"
fi
# Override nydus configuration, this configuration is usually set by each case
if [[ -n ${NYDUS_CONFIG_PATH:-} ]]; then
nydusd_config=${NYDUS_CONFIG_PATH}
fi
# rm -rf "${REMOTE_SNAPSHOTTER_ROOT:?}"/* || fuser -m "${REMOTE_SNAPSHOTTER_ROOT}/mnt" && false
rm -rf "${REMOTE_SNAPSHOTTER_ROOT:?}"/*
set_recover_policy "${recover_policy}"
containerd-nydus-grpc --log-to-stdout \
--daemon-mode "${daemon_mode}" --fs-driver "${fs_driver}" \
--config "${SNAPSHOTTER_CONFIG}" --nydusd-config "${nydusd_config}" &
retry ls "${REMOTE_SNAPSHOTTER_SOCKET}"
containerd --log-level info --config=/etc/containerd/config.toml &
retry ls "${CONTAINERD_SOCKET}"
# Makes sure containerd and containerd-nydus-grpc are up-and-running.
UNIQUE_SUFFIX=$(date +%s%N | shasum | base64 | fold -w 10 | head -1)
retry ctr snapshots --snapshotter="${PLUGIN}" prepare "connectiontest-dummy-${UNIQUE_SUFFIX}" ""
}
function restart_snapshotter {
killall -INT containerd-nydus-grpc
local daemon_mode=$1
}
function umount_global_shared_mnt {
umount "${SNAPSHOTTER_SHARED_MNT}" || true
}
function is_cache_cleared {
# With fscache driver, 2.1 nydusd don't have API to release the cache files.
# Thy locate at directory ${SNAPSHOTTER_CACHE_DIR}/cache
if [[ $(ls -A -p "${SNAPSHOTTER_CACHE_DIR}" | grep -v /) == "" ]]; then
true
else
echo "ERROR: Cache is not cleared"
false
fi
}
function nerdctl_prune_images {
# Wait for containers observation.
sleep 1
func_retry stop_all_containers
nerdctl container prune -f
nerdctl image prune --all -f
nerdctl images
is_cache_cleared
}
function start_single_container_multiple_daemons {
echo "testing $FUNCNAME"
nerdctl_prune_images
reboot_containerd multiple
nerdctl --snapshotter nydus run -d --net none "${JAVA_IMAGE}"
detect_go_race
}
function start_multiple_containers_multiple_daemons {
echo "testing $FUNCNAME"
nerdctl_prune_images
reboot_containerd multiple
nerdctl --snapshotter nydus run -d --net none "${JAVA_IMAGE}"
nerdctl --snapshotter nydus run -d --net none "${WORDPRESS_IMAGE}"
nerdctl --snapshotter nydus run -d --net none "${TOMCAT_IMAGE}"
nerdctl_prune_images
nerdctl --snapshotter nydus run -d --net none "${TOMCAT_IMAGE}"
nerdctl --snapshotter nydus run -d --net none "${JAVA_IMAGE}"
nerdctl --snapshotter nydus run -d --net none "${WORDPRESS_IMAGE}"
detect_go_race
}
function start_multiple_containers_shared_daemon {
echo "testing $FUNCNAME"
nerdctl_prune_images
reboot_containerd shared
nerdctl --snapshotter nydus run -d --net none "${JAVA_IMAGE}"
nerdctl --snapshotter nydus run -d --net none "${WORDPRESS_IMAGE}"
nerdctl --snapshotter nydus run -d --net none "${TOMCAT_IMAGE}"
detect_go_race
}
function start_single_container_on_stargz {
echo "testing $FUNCNAME"
nerdctl_prune_images
reboot_containerd multiple
killall "containerd-nydus-grpc" || true
sleep 2
containerd-nydus-grpc --enable-stargz --daemon-mode multiple --fs-driver fusedev \
--recover-policy none --log-to-stdout --config-path /etc/nydus/nydusd-config.json &
nerdctl --snapshotter nydus run -d --net none "${STARGZ_IMAGE}"
detect_go_race
}
function start_container_on_oci {
echo "testing $FUNCNAME"
nerdctl_prune_images
reboot_containerd multiple
nerdctl --snapshotter nydus run -d --net none "${REDIS_OCI_IMAGE}"
nerdctl --snapshotter nydus run -d --net none "${WORDPRESS_OCI_IMAGE}"
pause 2
func_retry stop_all_containers
# Deleteing with flag --async as a fuzzer
nerdctl image rm --async --force "${REDIS_OCI_IMAGE}"
nerdctl image rm --force "${WORDPRESS_OCI_IMAGE}"
}
function start_container_with_referrer_detect {
echo "testing $FUNCNAME"
nerdctl_prune_images
reboot_containerd multiple
set_enable_referrer_detect
nerdctl --snapshotter nydus run -d --net none "${WORDPRESS_OCI_IMAGE}"
detect_go_race
}
function pull_remove_one_image {
echo "testing $FUNCNAME"
nerdctl_prune_images
reboot_containerd multiple
nerdctl --snapshotter nydus image pull "${JAVA_IMAGE}"
nerdctl --snapshotter nydus image rm "${JAVA_IMAGE}"
detect_go_race
}
function pull_remove_multiple_images {
local daemon_mode=$1
echo "testing $FUNCNAME"
nerdctl_prune_images
reboot_containerd "${daemon_mode}"
# Because nydusd is not started right after image pull.
# Nydusd is started when preparing the writable active snapshot as the
# uppermost layer. So we must create a container to start nydusd.
# Then to test if snapshotter's nydusd daemons management works well
nerdctl --snapshotter nydus image pull "${JAVA_IMAGE}"
nerdctl --snapshotter nydus image pull "${WORDPRESS_IMAGE}"
nerdctl --snapshotter nydus image pull "${TOMCAT_IMAGE}"
nerdctl --snapshotter nydus create --rm --net none "${TOMCAT_IMAGE}"
nerdctl --snapshotter nydus create --rm --net none "${WORDPRESS_IMAGE}"
nerdctl --snapshotter nydus image rm --force "${JAVA_IMAGE}"
nerdctl --snapshotter nydus image rm --force "${WORDPRESS_IMAGE}"
# Deleteing with flag --async as a fuzzer
nerdctl --snapshotter nydus image rm --force --async "${TOMCAT_IMAGE}"
nerdctl --snapshotter nydus image pull "${TOMCAT_IMAGE}"
nerdctl --snapshotter nydus create --net none "${TOMCAT_IMAGE}"
detect_go_race
# TODO: Validate running nydusd number
}
function start_multiple_containers_shared_daemon_fscache {
echo "testing $FUNCNAME"
nerdctl_prune_images
reboot_containerd shared fscache
nerdctl --snapshotter nydus run -d --net none "${JAVA_IMAGE}"
nerdctl --snapshotter nydus run -d --net none "${WORDPRESS_IMAGE}"
detect_go_race
}
function kill_snapshotter_and_nydusd_recover {
local daemon_mode=$1
echo "testing $FUNCNAME"
nerdctl_prune_images
reboot_containerd "${daemon_mode}"
nerdctl --snapshotter nydus image pull "${WORDPRESS_IMAGE}"
nerdctl --snapshotter nydus image pull "${JAVA_IMAGE}"
c1=$(nerdctl --snapshotter nydus create --net none "${JAVA_IMAGE}")
c2=$(nerdctl --snapshotter nydus create --net none "${WORDPRESS_IMAGE}")
sleep 1
echo "killing nydusd"
killall -9 nydusd || true
echo "killing nydus-snapshotter"
killall -9 containerd-nydus-grpc || true
rm "${REMOTE_SNAPSHOTTER_SOCKET:?}"
containerd-nydus-grpc --config "${SNAPSHOTTER_CONFIG}" \
--daemon-mode "${daemon_mode}" --log-to-stdout --config-path /etc/nydus/nydusd-config.json &
retry ls "${REMOTE_SNAPSHOTTER_SOCKET}"
echo "start new containers"
nerdctl --snapshotter nydus start "$c1"
nerdctl --snapshotter nydus start "$c2"
detect_go_race
}
# No restart or failover recover policy. Just let snapshotter start a new nydusd when it refreshes.
function fscache_kill_snapshotter_and_nydusd_recover {
local daemon_mode=$1
echo "testing $FUNCNAME"
nerdctl_prune_images
reboot_containerd "${daemon_mode}" fscache
nerdctl --snapshotter nydus image pull "${WORDPRESS_IMAGE}"
nerdctl --snapshotter nydus image pull "${JAVA_IMAGE}"
c1=$(nerdctl --snapshotter nydus create --net none "${JAVA_IMAGE}")
c2=$(nerdctl --snapshotter nydus create --net none "${WORDPRESS_IMAGE}")
sleep 1
echo "killing nydusd"
killall -9 nydusd || true
killall -9 containerd-nydus-grpc || true
sleep 1
rm "${REMOTE_SNAPSHOTTER_SOCKET:?}"
containerd-nydus-grpc --log-to-stdout --config "${SNAPSHOTTER_CONFIG}" \
--daemon-mode "${daemon_mode}" --fs-driver fscache --config-path /etc/nydus/nydusd-config.fscache.json &
retry ls "${REMOTE_SNAPSHOTTER_SOCKET}"
echo "start new containers"
nerdctl --snapshotter nydus start "$c1"
nerdctl --snapshotter nydus start "$c2"
# killall -9 nydusd
sleep 0.2
detect_go_race
}
function fscache_kill_nydusd_failover() {
local daemon_mode=shared
echo "testing $FUNCNAME"
nerdctl_prune_images
reboot_containerd "${daemon_mode}" fscache failover
nerdctl --snapshotter nydus image pull "${WORDPRESS_IMAGE}"
nerdctl --snapshotter nydus image pull "${JAVA_IMAGE}"
c1=$(nerdctl --snapshotter nydus create --net none "${JAVA_IMAGE}")
c2=$(nerdctl --snapshotter nydus create --net none "${WORDPRESS_IMAGE}")
killall -9 nydusd
echo "start new containers"
nerdctl --snapshotter nydus start "$c1"
nerdctl --snapshotter nydus start "$c2"
sleep 1
detect_go_race
}
function only_restart_snapshotter {
local daemon_mode=$1
echo "testing $FUNCNAME ${daemon_mode}"
nerdctl_prune_images
reboot_containerd "${daemon_mode}"
nerdctl --snapshotter nydus image pull "${WORDPRESS_IMAGE}"
nerdctl --snapshotter nydus image pull "${JAVA_IMAGE}"
c1=$(nerdctl --snapshotter nydus create --net none "${JAVA_IMAGE}")
c2=$(nerdctl --snapshotter nydus create --net none "${WORDPRESS_IMAGE}")
echo "killing snapshotter"
killall -9 containerd-nydus-grpc || true
rm "${REMOTE_SNAPSHOTTER_SOCKET:?}"
containerd-nydus-grpc --config "${SNAPSHOTTER_CONFIG}" --daemon-mode \
"${daemon_mode}" --log-to-stdout --config-path /etc/nydus/nydusd-config.json &
retry ls "${REMOTE_SNAPSHOTTER_SOCKET}"
if [[ "${daemon_mode}" == "shared" ]]; then
validate_mnt_number 1
else
validate_mnt_number 2
fi
echo "start new containers"
nerdctl --snapshotter nydus start "$c1"
nerdctl --snapshotter nydus start "$c2"
detect_go_race
}
function kill_nydusd_recover_nydusd {
local daemon_mode=$1
echo "testing $FUNCNAME"
nerdctl_prune_images
reboot_containerd "${daemon_mode}" fusedev restart
nerdctl --snapshotter nydus image pull "${WORDPRESS_IMAGE}"
nerdctl --snapshotter nydus image pull "${JAVA_IMAGE}"
c1=$(nerdctl --snapshotter nydus create --net none "${JAVA_IMAGE}")
c2=$(nerdctl --snapshotter nydus create --net none "${WORDPRESS_IMAGE}")
pause 1
echo "killing nydusd"
killall -9 nydusd || true
echo "start new containers"
nerdctl --snapshotter nydus start "$c1"
nerdctl --snapshotter nydus start "$c2"
detect_go_race
}
function ctr_snapshot_usage {
local daemon_mode=$1
echo "testing $FUNCNAME"
nerdctl_prune_images
reboot_containerd "${daemon_mode}" fusedev restart
nerdctl --snapshotter nydus image pull "${WORDPRESS_IMAGE}"
nerdctl --snapshotter nydus image pull "${JAVA_IMAGE}"
c1=$(nerdctl --snapshotter nydus create --net none "${JAVA_IMAGE}")
c2=$(nerdctl --snapshotter nydus create --net none "${WORDPRESS_IMAGE}")
pause 1
ctr snapshot --snapshotter nydus ls
ctr snapshot --snapshotter nydus usage
echo "start new containers"
nerdctl --snapshotter nydus start "$c1"
nerdctl --snapshotter nydus start "$c2"
ctr snapshot --snapshotter nydus ls
ctr snapshot --snapshotter nydus usage
detect_go_race
}
function kill_multiple_nydusd_recover_failover {
local daemon_mode=$1
echo "testing $FUNCNAME"
nerdctl_prune_images
reboot_containerd "${daemon_mode}" fusedev failover
c1=$(nerdctl --snapshotter nydus run -d --net none "${JAVA_IMAGE}")
c2=$(nerdctl --snapshotter nydus run -d --net none "${WORDPRESS_IMAGE}")
pause 1
nerdctl kill "$c1" || true
nerdctl kill "$c2 " || true
echo "killing nydusd"
killall -9 nydusd || true
echo "start new containers"
c1=$(nerdctl --snapshotter nydus run -d --net none "${JAVA_IMAGE}")
c2=$(nerdctl --snapshotter nydus run -d --net none "${WORDPRESS_IMAGE}")
pause 1
nerdctl kill "$c1" || true
nerdctl kill "$c2 " || true
echo "killing nydusd again"
killall -9 nydusd || true
c1=$(nerdctl --snapshotter nydus run -d --net none "${JAVA_IMAGE}")
c2=$(nerdctl --snapshotter nydus run -d --net none "${WORDPRESS_IMAGE}")
detect_go_race
}
# Refer to https://github.com/moby/moby/blob/088afc99e4bf8adb78e29733396182417d67ada2/hack/dind#L28-L38
function enable_nesting_for_cgroup_v2() {
if [ -f /sys/fs/cgroup/cgroup.controllers ]; then
mkdir -p /sys/fs/cgroup/init
xargs -rn1 /sys/fs/cgroup/init/cgroup.procs || :
sed -e 's/ / +/g' -e 's/^/-/' /sys/fs/cgroup/cgroup.subtree_control
fi
}
enable_nesting_for_cgroup_v2
reboot_containerd multiple
start_single_container_multiple_daemons
start_multiple_containers_multiple_daemons
start_multiple_containers_shared_daemon
pull_remove_one_image
pull_remove_multiple_images shared
pull_remove_multiple_images multiple
# start_single_container_on_stargz
only_restart_snapshotter shared
only_restart_snapshotter multiple
kill_snapshotter_and_nydusd_recover shared
kill_snapshotter_and_nydusd_recover multiple
ctr_snapshot_usage multiple
ctr_snapshot_usage shared
if [[ $(can_erofs_ondemand_read) == 0 ]]; then
kill_multiple_nydusd_recover_failover multiple
kill_multiple_nydusd_recover_failover shared
start_multiple_containers_shared_daemon_fscache
fscache_kill_snapshotter_and_nydusd_recover shared
fscache_kill_nydusd_failover
fi
start_container_on_oci
start_container_with_referrer_detect
================================================
FILE: internal/constant/values.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
// constants of nydus snapshotter CLI config
package constant
import "time"
const (
DaemonModeMultiple string = "multiple"
DaemonModeDedicated string = "dedicated"
DaemonModeShared string = "shared"
DaemonModeNone string = "none"
DaemonModeInvalid string = ""
)
const (
// Mount RAFS filesystem by using EROFS over block devices.
FsDriverBlockdev string = "blockdev"
// Mount RAFS filesystem by using FUSE subsystem
FsDriverFusedev string = "fusedev"
// Mount RAFS filesystem by using fscache/EROFS.
FsDriverFscache string = "fscache"
// Only prepare/supply meta/data blobs, do not mount RAFS filesystem.
FsDriverNodev string = "nodev"
// Relay layer content download operation to other agents.
FsDriverProxy string = "proxy"
)
const (
DefaultDaemonMode = DaemonModeMultiple
DefaultFsDriver = FsDriverFusedev
DefaultLogLevel string = "info"
DefaultGCPeriod = 24 * time.Hour
DefaultNydusDaemonConfigPath string = "/etc/nydus/nydusd-config.json"
NydusdBinaryName string = "nydusd"
NydusImageBinaryName string = "nydus-image"
DefaultRootDir = "/var/lib/containerd/io.containerd.snapshotter.v1.nydus"
DefaultAddress = "/run/containerd-nydus/containerd-nydus-grpc.sock"
DefaultSystemControllerAddress = "/run/containerd-nydus/system.sock"
// Log rotation
DefaultDaemonRotateLogMaxSize = 100 // 100 megabytes
DefaultRotateLogMaxSize = 200 // 200 megabytes
DefaultRotateLogMaxBackups = 5
DefaultRotateLogMaxAge = 0 // days
DefaultRotateLogLocalTime = true
DefaultRotateLogCompress = true
// metrics configuration
DefaultHungIOInterval = 10 * time.Second
DefaultCollectInterval = 1 * time.Minute
)
const (
FailoverPolicyNone string = "none"
FailoverPolicyResend string = "resend"
FailoverPolicyFlush string = "flush"
DefaultFailoverPolicy string = FailoverPolicyResend
)
================================================
FILE: internal/flags/flags.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package flags
import (
"github.com/containerd/nydus-snapshotter/internal/constant"
"github.com/urfave/cli/v2"
)
type Args struct {
Address string
NydusdConfigPath string
SnapshotterConfigPath string
RootDir string
NydusdPath string
NydusImagePath string
NydusOverlayFSPath string
DaemonMode string
FsDriver string
LogLevel string
LogToStdout bool
LogToStdoutCount int
PrintVersion bool
}
type Flags struct {
Args *Args
F []cli.Flag
}
func buildFlags(args *Args) []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "root",
Usage: "directory to store snapshotter data and working states",
Destination: &args.RootDir,
DefaultText: constant.DefaultRootDir,
},
&cli.StringFlag{
Name: "address",
Usage: "remote snapshotter gRPC socket path",
Destination: &args.Address,
DefaultText: constant.DefaultAddress,
},
&cli.StringFlag{
Name: "config",
Usage: "path to nydus-snapshotter configuration (such as: config.toml)",
Destination: &args.SnapshotterConfigPath,
},
&cli.StringFlag{
Name: "nydus-image",
Usage: "path to `nydus-image` binary, default to search in $PATH (such as: /usr/local/bin/nydus-image)",
Destination: &args.NydusImagePath,
},
&cli.StringFlag{
Name: "nydusd",
Usage: "path to `nydusd` binary, default to search in $PATH (such as: /usr/local/bin/nydusd)",
Destination: &args.NydusdPath,
},
&cli.StringFlag{
Name: "nydusd-config",
Aliases: []string{"config-path"},
Usage: "path to nydusd configuration (such as: nydusd-config.json or nydusd-config-v2.toml)",
Destination: &args.NydusdConfigPath,
DefaultText: constant.DefaultNydusDaemonConfigPath,
},
&cli.StringFlag{
Name: "nydus-overlayfs-path",
Usage: "path of nydus-overlayfs or name of binary from $PATH, defaults to 'nydus-overlayfs'",
Destination: &args.NydusOverlayFSPath,
},
&cli.StringFlag{
Name: "daemon-mode",
Usage: "nydusd daemon working mode, possible values: \"dedicated\", \"multiple\", \"shared\" or \"none\". \"multiple\" is an alias of \"dedicated\" and will be deprecated in v1.0",
Destination: &args.DaemonMode,
DefaultText: constant.DaemonModeMultiple,
},
&cli.StringFlag{
Name: "fs-driver",
Usage: "driver to mount RAFS filesystem, possible values: \"fusedev\", \"fscache\"",
Destination: &args.FsDriver,
DefaultText: constant.FsDriverFusedev,
},
&cli.StringFlag{
Name: "log-level",
Usage: "logging level, possible values: \"trace\", \"debug\", \"info\", \"warn\", \"error\"",
Destination: &args.LogLevel,
DefaultText: constant.DefaultLogLevel,
},
&cli.BoolFlag{
Name: "log-to-stdout",
Usage: "print log messages to standard output",
Destination: &args.LogToStdout,
Count: &args.LogToStdoutCount,
},
&cli.BoolFlag{
Name: "version",
Usage: "print version and build information",
Destination: &args.PrintVersion,
},
}
}
func NewFlags() *Flags {
var args Args
return &Flags{
Args: &args,
F: buildFlags(&args),
}
}
================================================
FILE: internal/flags/flags_test.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package flags
import (
"flag"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewFlags(t *testing.T) {
set := flag.NewFlagSet("test", 0)
flags := NewFlags()
for _, i := range flags.F {
err := i.Apply(set)
assert.Nil(t, err)
}
err := set.Parse([]string{"--config-path", "/etc/testconfig", "--root", "/root", "--log-level", "info"})
assert.Nil(t, err)
assert.Equal(t, flags.Args.NydusdConfigPath, "/etc/testconfig")
assert.Equal(t, flags.Args.LogLevel, "info")
assert.Equal(t, flags.Args.RootDir, "/root")
}
================================================
FILE: internal/logging/setup.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package logging
import (
"context"
"os"
"path/filepath"
"github.com/containerd/log"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
lumberjack "gopkg.in/natefinch/lumberjack.v2"
)
const (
DefaultLogDirName = "logs"
defaultLogFileName = "nydus-snapshotter.log"
)
type RotateLogArgs struct {
RotateLogMaxSize int
RotateLogMaxBackups int
RotateLogMaxAge int
RotateLogLocalTime bool
RotateLogCompress bool
}
func SetUp(logLevel string, logToStdout bool, logDir string, logRotateArgs *RotateLogArgs) error {
lvl, err := logrus.ParseLevel(logLevel)
if err != nil {
return err
}
logrus.SetLevel(lvl)
if logToStdout {
logrus.SetOutput(os.Stdout)
} else {
if logRotateArgs == nil {
return errors.New("logRotateArgs is needed when logToStdout is false")
}
if err := os.MkdirAll(logDir, 0755); err != nil {
return errors.Wrapf(err, "create log dir %s", logDir)
}
logFile := filepath.Join(logDir, defaultLogFileName)
lumberjackLogger := &lumberjack.Logger{
Filename: logFile,
MaxSize: logRotateArgs.RotateLogMaxSize,
MaxBackups: logRotateArgs.RotateLogMaxBackups,
MaxAge: logRotateArgs.RotateLogMaxAge,
Compress: logRotateArgs.RotateLogCompress,
LocalTime: logRotateArgs.RotateLogLocalTime,
}
logrus.SetOutput(lumberjackLogger)
}
logrus.SetFormatter(&logrus.TextFormatter{
TimestampFormat: log.RFC3339NanoFixed,
FullTimestamp: true,
})
return nil
}
func WithContext() context.Context {
return log.WithLogger(context.Background(), log.L)
}
================================================
FILE: internal/logging/setup_test.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package logging
import (
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/containerd/log"
"github.com/sirupsen/logrus"
"gotest.tools/assert"
)
const (
TestLogDirName = "test-rotate-logs"
TestRootDirName = "test-root"
)
func GetRotateLogFileNumbers(testLogDir string, suffix string) int {
i := 0
err := filepath.Walk(testLogDir, func(fname string, fi os.FileInfo, _ error) error {
if !fi.IsDir() && strings.HasSuffix(fname, suffix) {
i++
}
return nil
})
if err != nil {
log.L.Fatal("walk path")
}
return i
}
func TestSetUp(t *testing.T) {
// Try to clean previously created test directory.
os.RemoveAll(TestLogDirName)
logRotateArgs := &RotateLogArgs{
RotateLogMaxSize: 1, // 1MB
RotateLogMaxBackups: 5,
RotateLogMaxAge: 0,
RotateLogLocalTime: true,
RotateLogCompress: true,
}
logLevel := logrus.InfoLevel.String()
err := SetUp(logLevel, true, TestLogDirName, nil)
assert.NilError(t, err, nil)
err = SetUp(logLevel, false, TestLogDirName, nil)
assert.ErrorContains(t, err, "logRotateArgs is needed when logToStdout is false")
err = SetUp(logLevel, false, TestLogDirName, logRotateArgs)
assert.NilError(t, err)
for i := 0; i < 100000; i++ { // total 9.1MB
log.L.Infof("test log, now: %s", time.Now().Format("2006-01-02 15:04:05"))
}
assert.Equal(t, GetRotateLogFileNumbers(TestLogDirName, "log.gz"), logRotateArgs.RotateLogMaxBackups)
os.RemoveAll(TestLogDirName)
}
================================================
FILE: misc/example/10-containerd-net.conflist
================================================
{
"cniVersion": "1.0.0",
"name": "containerd-net",
"plugins": [
{
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"promiscMode": true,
"ipam": {
"type": "host-local",
"subnet": "10.88.0.0/16",
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
},
{
"type": "portmap",
"capabilities": {"portMappings": true}
}
]
}
================================================
FILE: misc/example/README.md
================================================
## Example to setup and test nydus-snapshotter
The directory holds a few example config files to setup a nydus POC environment that saves its all state files in `/var/lib/containerd-test`, as specified in `containerd-test-config.toml`.
For now it contains following files:
* crictl.yaml: `crictl` tool config
* containerd-config.toml: `containerd` config
* containerd-config-v1.toml: `containerd` config in the deprecated v1 format, provided for old containerd versions
* containerd-test-config.toml: `containerd` config for testing
* 10-containerd-net.conflist: `containerd` cni config
* pod.yaml: pod spec yaml to be used by `crictl`
* container.yaml: container spec yaml to be used by `crictl`
With these config files, users can setup a nydus environment by doing:
```
export CNI_VERSION=v1.1.0
export CRICTL_VERSION=v1.23.0
sudo mkdir -p /var/lib/containerd-test/ /etc/containerd/ /opt/cni/bin/ /etc/cni/net.d/
sudo cp misc/example/containerd-test-config.toml /etc/containerd/
sudo cp misc/example/crictl.yaml /etc/
sudo cp misc/example/10-containerd-net.conflist /etc/cni/net.d/
# install cni plugin
wget https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION/cni-plugins-linux-amd64-$CNI_VERSION.tgz
sudo tar xzf cni-plugins-linux-amd64-$CNI_VERSION.tgz -C /opt/cni/bin/
# install crictl
wget https://github.com/kubernetes-sigs/cri-tools/releases/download/$CRICTL_VERSION/crictl-$CRICTL_VERSION-linux-amd64.tar.gz
tar xzf crictl-$CRICTL_VERSION-linux-amd64.tar.gz -C /usr/local/bin/
# install nydus-overlayfs
NYDUS_VER=v$(curl -s "https://api.github.com/repos/dragonflyoss/nydus/releases/latest" | jq -r .tag_name | sed 's/^v//')
wget https://github.com/dragonflyoss/nydus/releases/download/$NYDUS_VER/nydus-static-$NYDUS_VER-linux-amd64.tgz
tar xzf nydus-static-$NYDUS_VER-linux-amd64.tgz
sudo cp nydus-static/nydus-overlayfs /usr/local/sbin/
```
Then start a `nydus-snapshotter` container with:
```
docker run -d --device /dev/fuse --cap-add SYS_ADMIN --security-opt apparmor:unconfined -e CONTAINERD_ROOT=/var/lib/containerd-test -v /var/lib/containerd-test:/var/lib/containerd-test:shared ghcr.io/containerd/nydus-snapshotter/nydus-snapshotter:latest
```
It will bindmount host directory `/var/lib/containerd-test` inside the container and share all the nydus mountpoints from there.
`containerd` config in `containerd-test-config.toml` has been setup to use the `nydus-snapshotter` grpc unix domain socket at `/var/lib/containerd-test/io.containerd.snapshotter.v1.nydus/containerd-nydus-grpc.sock`.
Then start containerd with:
```
sudo containerd --config /etc/containerd/containerd-test-config.toml
```
Now we can use `crictl` to test against the POC environment.
## Deploy in production
When deploying `nydus-snapshotter` in production, following changes need to be applied:
1. remove the `CONTAINERD_ROOT` environment variable when starting `nydus-snapshotter` container, and bindmount `/var/lib/containerd` to `/var/lib/containerd` in the container.
2. use `containerd-config.toml` instead of `containerd-test-config.toml` to start containerd.
================================================
FILE: misc/example/container.yaml
================================================
metadata:
name: foobar-container
image:
image: ghcr.io/dragonflyoss/image-service/ubuntu:nydus-latest
command: ["tail", "-f", "/dev/null"]
log_path: log.0
================================================
FILE: misc/example/containerd-config.toml
================================================
version = 2
root = "/var/lib/containerd"
state = "/run/containerd"
oom_score = 0
[debug]
level = "debug"
[plugins."io.containerd.grpc.v1.cri"]
[plugins."io.containerd.grpc.v1.cri".containerd]
snapshotter = "nydus"
disable_snapshot_annotations = false
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata]
runtime_type = "io.containerd.kata.v2"
privileged_without_host_devices = true
[plugins."io.containerd.grpc.v1.cri".cni]
bin_dir = "/opt/cni/bin"
conf_dir = "/etc/cni/net.d"
[proxy_plugins]
[proxy_plugins.nydus]
type = "snapshot"
address = "/run/containerd-nydus/containerd-nydus-grpc.sock"
================================================
FILE: misc/example/containerd-test-config.toml
================================================
version = 2
root = "/var/lib/containerd-test"
state = "/run/containerd-test"
oom_score = 0
[grpc]
address = "/run/containerd-test/containerd.sock"
[debug]
level = "debug"
[plugins."io.containerd.grpc.v1.cri"]
[plugins."io.containerd.grpc.v1.cri".containerd]
snapshotter = "nydus"
disable_snapshot_annotations = false
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata]
runtime_type = "io.containerd.kata.v2"
privileged_without_host_devices = true
[plugins."io.containerd.grpc.v1.cri".cni]
bin_dir = "/opt/cni/bin"
conf_dir = "/etc/cni/net.d"
[proxy_plugins]
[proxy_plugins.nydus]
type = "snapshot"
address = "/var/lib/containerd-test/io.containerd.snapshotter.v1.nydus/containerd-nydus-grpc.sock"
================================================
FILE: misc/example/crictl.yaml
================================================
runtime-endpoint: unix:///run/containerd-test/containerd.sock
================================================
FILE: misc/example/optimizer-nri-plugin.conf
================================================
# The directory to persist accessed files list for container.
persist_dir = "/opt/nri/optimizer/results"
# Whether to make the csv file human readable.
readable = false
# The path of optimizer server binary.
server_path = "/usr/local/bin/optimizer-server"
# The timeout to kill optimizer server, 0 to disable it.
timeout = 0
# Whether to overwrite the existed persistent files.
overwrite = false
# The events that containerd subscribes to.
# Do not change this element.
events = [ "StartContainer", "StopContainer" ]
================================================
FILE: misc/example/pod.yaml
================================================
metadata:
attempt: 1
name: foobar
namespace: default
log_directory: /tmp
linux:
namespaces:
options: {}
================================================
FILE: misc/nri-prefetch/prefetchConfig.toml
================================================
[file_prefetch]
# This is used to configure the socket address for the file prefetch.
socket_address = "/run/containerd-nydus/system.sock"
================================================
FILE: misc/optimizer/containerd-config.toml
================================================
# Copyright 2018-2022 Docker Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
version = 2
[proxy_plugins]
[proxy_plugins.nydus]
type = "snapshot"
address = "/run/containerd-nydus/containerd-nydus-grpc.sock"
[plugins."io.containerd.nri.v1.nri"]
config_file = "/etc/nri/nri.conf"
disable = false
plugin_path = "/opt/nri/plugins"
socket_path = "/var/run/nri.sock"
================================================
FILE: misc/optimizer/crictl.yaml
================================================
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:////run/containerd/containerd.sock
timeout: 2
debug: false
pull-image-on-create: false
================================================
FILE: misc/optimizer/nginx.yaml
================================================
metadata:
name: nginx
image:
image: nginx:1.23.3
mounts:
- host_path: script
container_path: /script
command:
- /script/entrypoint.sh
args:
- /script/file_list.txt
log_path: nginx.0.log
linux: {}
================================================
FILE: misc/optimizer/sandbox.yaml
================================================
metadata:
name: nginx-sandbox
namespace: default
attempt: 1
uid: hdishd83djaidwnduwk28bcsb
log_directory: /tmp
linux: {}
================================================
FILE: misc/optimizer/script/entrypoint.sh
================================================
#!/usr/bin/env bash
path=$1
default_path=file_list_path.txt
if [[ $# -eq 0 ]]; then
path=${default_path}
fi
files=($(cat ${path} | tr "\n" " "))
files_number=${#files[@]}
echo "file number: $files_number"
for file in "${files[@]}"; do
file_size=$(stat -c%s "${file}")
echo "file: ${file} size: ${file_size}"
cat ${file} >/dev/null
done
echo "Read file list done."
================================================
FILE: misc/optimizer/script/file_list.txt
================================================
/lib/x86_64-linux-gnu/ld-2.31.so
/lib/x86_64-linux-gnu/libc-2.31.so
/lib/x86_64-linux-gnu/libtinfo.so.6.2
/lib/x86_64-linux-gnu/libdl-2.31.so
/lib/x86_64-linux-gnu/libnss_files-2.31.so
/lib/x86_64-linux-gnu/libselinux.so.1
/usr/lib/x86_64-linux-gnu/libpcre2-8.so.0.10.1
/lib/x86_64-linux-gnu/libpthread-2.31.so
/docker-entrypoint.sh
/docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
/docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.d/30-tune-worker-processes.sh
================================================
FILE: misc/snapshotter/Dockerfile
================================================
FROM alpine:3.17.0 AS base
FROM base AS sourcer
ARG TARGETARCH
ARG NYDUS_VER=v2.3.0
RUN apk add -q --no-cache curl && \
apk add -q --no-cache --upgrade grep && \
curl -fsSL -O https://github.com/dragonflyoss/nydus/releases/download/$NYDUS_VER/nydus-static-$NYDUS_VER-linux-amd64.tgz && \
echo $NYDUS_VER > /.nydus_version && \
tar xzf nydus-static-$NYDUS_VER-linux-amd64.tgz && \
rm nydus-static-$NYDUS_VER-linux-amd64.tgz && \
mv nydus-static/* / \
&& rm -rf /nydus-overlayfs
FROM base AS kubectl-sourcer
ARG TARGETARCH
ARG KUBE_VER=v1.30.2
RUN apk add -q --no-cache curl && \
curl -fsSL -o /usr/bin/kubectl https://dl.k8s.io/release/${KUBE_VER}/bin/linux/${TARGETARCH}/kubectl && \
chmod +x /usr/bin/kubectl
FROM base
ARG DESTINATION=/opt/nydus-artifacts
ARG CONFIG_DESTINATION=${DESTINATION}/etc/nydus
ARG BINARY_DESTINATION=${DESTINATION}/usr/local/bin
ARG SCRIPT_DESTINATION=${DESTINATION}/opt/nydus
WORKDIR /root/
RUN apk add -q --no-cache libc6-compat bash
VOLUME /var/lib/containerd/io.containerd.snapshotter.v1.nydus
VOLUME /run/containerd-nydus
COPY --from=sourcer /.nydus_version /.nydus_version
COPY --from=kubectl-sourcer /usr/bin/kubectl /usr/bin/kubectl
RUN mkdir -p ${CONFIG_DESTINATION} ${BINARY_DESTINATION} ${SCRIPT_DESTINATION} /var/lib/containerd/io.containerd.snapshotter.v1.nydus/cache /tmp/blobs/
COPY --from=sourcer /nydus* ${BINARY_DESTINATION}/
COPY --chmod=755 containerd-nydus-grpc nydus-overlayfs ${BINARY_DESTINATION}/
COPY --chmod=755 snapshotter.sh ${SCRIPT_DESTINATION}/snapshotter.sh
COPY nydusd-config.fusedev.json ${CONFIG_DESTINATION}/nydusd-fusedev.json
COPY nydusd-config-localfs.json ${CONFIG_DESTINATION}/nydusd-localfs.json
COPY nydusd-config.fscache.json ${CONFIG_DESTINATION}/nydusd-fscache.json
COPY config.toml ${CONFIG_DESTINATION}/config.toml
COPY nydus-snapshotter.service ${DESTINATION}/etc/systemd/system/nydus-snapshotter.service
================================================
FILE: misc/snapshotter/base/kustomization.yaml
================================================
resources:
- nydus-snapshotter.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
================================================
FILE: misc/snapshotter/base/nydus-snapshotter.yaml
================================================
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nydus-snapshotter-configs
labels:
app: nydus-snapshotter
namespace: nydus-system
data:
FS_DRIVER: "fusedev"
ENABLE_CONFIG_FROM_VOLUME: "false"
ENABLE_RUNTIME_SPECIFIC_SNAPSHOTTER: "false"
ENABLE_SYSTEMD_SERVICE: "true"
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nydus-snapshotter
namespace: nydus-system
labels:
app: nydus-snapshotter
spec:
selector:
matchLabels:
app: nydus-snapshotter
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
template:
metadata:
labels:
app: nydus-snapshotter
spec:
serviceAccountName: nydus-snapshotter-sa
hostNetwork: true
hostPID: true
containers:
- name: nydus-snapshotter
image: "ghcr.io/containerd/nydus-snapshotter:latest"
imagePullPolicy: Always
env:
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: FS_DRIVER
valueFrom:
configMapKeyRef:
name: nydus-snapshotter-configs
key: FS_DRIVER
optional: true
- name: ENABLE_CONFIG_FROM_VOLUME
valueFrom:
configMapKeyRef:
name: nydus-snapshotter-configs
key: ENABLE_CONFIG_FROM_VOLUME
optional: true
- name: ENABLE_RUNTIME_SPECIFIC_SNAPSHOTTER
valueFrom:
configMapKeyRef:
name: nydus-snapshotter-configs
key: ENABLE_RUNTIME_SPECIFIC_SNAPSHOTTER
optional: true
- name: ENABLE_SYSTEMD_SERVICE
valueFrom:
configMapKeyRef:
name: nydus-snapshotter-configs
key: ENABLE_SYSTEMD_SERVICE
optional: true
lifecycle:
preStop:
exec:
command:
- "bash"
- "-c"
- |
/opt/nydus-artifacts/opt/nydus/snapshotter.sh cleanup
command:
- bash
- -c
- |-
/opt/nydus-artifacts/opt/nydus/snapshotter.sh deploy
volumeMounts:
- name: config-volume
mountPath: "/etc/nydus-snapshotter"
- name: nydus-lib
mountPath: "/var/lib/containerd/io.containerd.snapshotter.v1.nydus"
mountPropagation: Bidirectional
- name: nydus-run
mountPath: "/run/containerd-nydus"
mountPropagation: Bidirectional
- name: nydus-opt
mountPath: "/opt/nydus"
mountPropagation: Bidirectional
- name: nydus-etc
mountPath: "/etc/nydus"
mountPropagation: Bidirectional
- name: containerd-conf
mountPath: "/etc/containerd/"
- name: local-bin
mountPath: "/usr/local/bin/"
- name: etc-systemd-system
mountPath: "/etc/systemd/system/"
securityContext:
privileged: true
volumes:
- name: config-volume
configMap:
name: nydus-snapshotter-configs
optional: true
- name: nydus-run
hostPath:
path: /run/containerd-nydus
type: DirectoryOrCreate
- name: nydus-lib
hostPath:
path: /var/lib/containerd/io.containerd.snapshotter.v1.nydus
type: DirectoryOrCreate
- name: nydus-opt
hostPath:
path: /opt/nydus
type: DirectoryOrCreate
- name: nydus-etc
hostPath:
path: /etc/nydus
type: DirectoryOrCreate
- name: containerd-conf
hostPath:
path: /etc/containerd/
- name: local-bin
hostPath:
path: /usr/local/bin/
- name: etc-systemd-system
hostPath:
path: /etc/systemd/system/
================================================
FILE: misc/snapshotter/config-blockdev.toml
================================================
version = 1
# Snapshotter's own home directory where it stores and creates necessary resources
root = "/var/lib/containerd/io.containerd.snapshotter.v1.nydus"
# The snapshotter's GRPC server socket, containerd will connect to plugin on this socket
address = "/run/containerd-nydus/containerd-nydus-grpc.sock"
# No nydusd daemon needed
daemon_mode = "none"
[daemon]
# Use `blockdev` for tarfs
fs_driver = "blockdev"
# Path to nydus-image binary
nydusimage_path = "/usr/local/bin/nydus-image"
[remote]
skip_ssl_verify = true
[snapshot]
# Insert Kata volume information to `Mount.Options`
enable_kata_volume = true
[experimental.tarfs]
# Whether to enable nydus tarfs mode. Tarfs is supported by:
# - The EROFS filesystem driver since Linux 6.4
# - Nydus Image Service release v2.3
enable_tarfs = true
# Mount rafs on host by loopdev and EROFS
mount_tarfs_on_host = false
# Mode to export tarfs images:
# - "none" or "": do not export tarfs
# - "layer_verity_only": only generate disk verity information for a layer blob
# - "image_verity_only": only generate disk verity information for all blobs of an image
# - "layer_block": generate a raw block disk image with tarfs for a layer
# - "image_block": generate a raw block disk image with tarfs for an image
# - "layer_block_with_verity": generate a raw block disk image with tarfs for a layer with dm-verity info
# - "image_block_with_verity": generate a raw block disk image with tarfs for an image with dm-verity info
export_mode = "image_block_with_verity"
================================================
FILE: misc/snapshotter/config-proxy.toml
================================================
version = 1
# Snapshotter's own home directory where it stores and creates necessary resources
root = "/var/lib/containerd/io.containerd.snapshotter.v1.nydus"
# The snapshotter's GRPC server socket, containerd will connect to plugin on this socket
address = "/run/containerd-nydus/containerd-nydus-grpc.sock"
# No nydusd daemon needed
daemon_mode = "none"
[daemon]
# Enable proxy mode
fs_driver = "proxy"
[snapshot]
# Insert Kata volume information to `Mount.Options`
enable_kata_volume = true
================================================
FILE: misc/snapshotter/config.toml
================================================
version = 1
# Snapshotter's own home directory where it stores and creates necessary resources
root = "/var/lib/containerd/io.containerd.snapshotter.v1.nydus"
# The snapshotter's GRPC server socket, containerd will connect to plugin on this socket
address = "/run/containerd-nydus/containerd-nydus-grpc.sock"
# The nydus daemon mode can be one of the following options: multiple, dedicated, shared, or none.
# If `daemon_mode` option is not specified, the default value is multiple.
daemon_mode = "dedicated"
# Whether snapshotter should try to clean up resources when it is closed
cleanup_on_close = false
[system]
# Snapshotter's debug and trace HTTP server interface
enable = true
# Unix domain socket path where system controller is listening on
address = "/run/containerd-nydus/system.sock"
[system.debug]
# Snapshotter can profile the CPU utilization of each nydusd daemon when it is being started.
# This option specifies the profile duration when nydusd is downloading and uncomproessing data.
daemon_cpu_profile_duration_secs = 5
# Enable by assigning an address, empty indicates pprof server is disabled
pprof_address = ""
[daemon]
# Specify a configuration file for nydusd
nydusd_config = "/etc/nydus/nydusd-config.fusedev.json"
nydusd_path = "/usr/local/bin/nydusd"
nydusimage_path = "/usr/local/bin/nydus-image"
# The fs driver can be one of the following options: fusedev, fscache, blockdev, proxy, or nodev.
# If `fs_driver` option is not specified, the default value is fusedev.
fs_driver = "fusedev"
# How to process when daemon dies: "none", "restart" or "failover"
recover_policy = "failover"
# Nydusd worker thread number to handle FUSE or fscache requests, [0-1024].
# Setting to 0 will use the default configuration of nydusd.
threads_number = 4
# Log rotation size for nydusd, in unit MB(megabytes). (default 100MB)
log_rotation_size = 100
# Nydusd failover policy, can be "none", "resend" or "flush"
failover_policy = "resend"
[cgroup]
# Whether to use separate cgroup for nydusd.
enable = true
# The memory limit for nydusd cgroup, which contains all nydusd processes.
# Percentage is supported as well, please ensure it is end with "%".
# The default unit is bytes. Acceptable values include "209715200", "200MiB", "200Mi" and "10%".
memory_limit = ""
[log]
# Print logs to stdout rather than logging files
log_to_stdout = false
# Snapshotter's log level
level = "info"
log_rotation_compress = true
log_rotation_local_time = true
# Max number of days to retain logs
log_rotation_max_age = 7
log_rotation_max_backups = 5
# In unit MB(megabytes)
log_rotation_max_size = 100
[metrics]
# Enable by assigning an address, empty indicates metrics server is disabled
address = ":9110"
# The maximum duration an I/O operation can be pending before it's considered hung.
hung_io_interval = "10s"
# The interval at which metrics are collected and updated.
collect_interval = "1m"
[remote]
convert_vpc_registry = false
[remote.mirrors_config]
# Snapshotter will rewrite nydusd's backend host to the first reachable mirror
# loaded from this directory before each mount. Mirror selection is done by the
# snapshotter (ping_url health check) and falls back to the origin registry host
# when no mirror is available. Set to "" or an empty directory to disable it.
#dir = "/etc/nydus/certs.d"
[remote.auth]
# Fetch the private registry auth by listening to K8s API server
enable_kubeconfig_keychain = false
# synchronize `kubernetes.io/dockerconfigjson` secret from kubernetes API server with specified kubeconfig (default `$KUBECONFIG` or `~/.kube/config`)
kubeconfig_path = ""
# Fetch the private registry auth as CRI image service proxy
enable_cri_keychain = false
# the target image service when using image proxy
#image_service_address = "/run/containerd/containerd.sock"
# Periodically renew cached credentials from renewable providers.
# Set to a positive duration (e.g., "10m", "1h") to enable. 0 disables.
credential_renewal_interval = "0s"
[snapshot]
# Let containerd use nydus-overlayfs mount helper
enable_nydus_overlayfs = false
# Path to the nydus-overlayfs binary or name of the binary in $PATH
nydus_overlayfs_path = "nydus-overlayfs"
# Insert Kata Virtual Volume option to `Mount.Options`
enable_kata_volume = false
# Whether to remove resources when a snapshot is removed
sync_remove = false
# Globally enable the "volatile" overlayfs mount option on all writable snapshots.
# This skips sync on the upper layer, improving write performance for ephemeral
# container filesystems. Safe for most workloads where the writable layer does not
# need to survive a crash.
enable_overlayfs_volatile = false
[cache_manager]
# Disable or enable recyclebin
disable = false
# How long to keep deleted files in recyclebin
gc_period = "24h"
# Directory to host cached files
cache_dir = ""
[image]
public_key_file = ""
validate_signature = false
# The configuraions for features that are not production ready
[experimental]
# Whether to enable stargz support
enable_stargz = false
# Whether to enable referrers support
# The option enables trying to fetch the Nydus image associated with the OCI image and run it.
# Also see https://github.com/opencontainers/distribution-spec/blob/main/spec.md#listing-referrers
enable_referrer_detect = false
# Whether to enable index detection support
# The option enables trying to fetch the Nydus image present in the same OCI index as the original OCI image and run it.
# Also see https://github.com/containerd/nydus-snapshotter/blob/main/docs/index-detection.md
enable_index_detect = false
# Whether to enable authentication support
# The option enables nydus snapshot to provide backend information to nydusd.
enable_backend_source = false
[experimental.tarfs]
# Whether to enable nydus tarfs mode. Tarfs is supported by:
# - The EROFS filesystem driver since Linux 6.4
# - Nydus Image Service release v2.3
enable_tarfs = false
# Mount rafs on host by loopdev and EROFS
mount_tarfs_on_host = false
# Only enable nydus tarfs mode for images with `tarfs hint` label when true
tarfs_hint = false
# Maximum of concurrence to converting OCIv1 images to tarfs, 0 means default
max_concurrent_proc = 0
# Mode to export tarfs images:
# - "none" or "": do not export tarfs
# - "layer_verity_only": only generate disk verity information for a layer blob
# - "image_verity_only": only generate disk verity information for all blobs of an image
# - "layer_block": generate a raw block disk image with tarfs for a layer
# - "image_block": generate a raw block disk image with tarfs for an image
# - "layer_block_with_verity": generate a raw block disk image with tarfs for a layer with dm-verity info
# - "image_block_with_verity": generate a raw block disk image with tarfs for an image with dm-verity info
export_mode = ""
================================================
FILE: misc/snapshotter/nydus-snapshotter-rbac.yaml
================================================
---
apiVersion: v1
kind: Namespace
metadata:
name: nydus-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: nydus-snapshotter-sa
namespace: nydus-system
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nydus-snapshotter-role
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nydus-snapshotter-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: nydus-snapshotter-role
subjects:
- kind: ServiceAccount
name: nydus-snapshotter-sa
namespace: nydus-system
================================================
FILE: misc/snapshotter/nydus-snapshotter.fscache.service
================================================
[Unit]
Description=nydus snapshotter
After=network.target
Before=containerd.service
[Service]
Type=simple
Environment=HOME=/root
ExecStart=/usr/local/bin/containerd-nydus-grpc --config /etc/nydus/config.toml --fs-driver fscache --nydusd-config /etc/nydus/nydusd-config.fscache.json
Restart=always
RestartSec=1
KillMode=process
OOMScoreAdjust=-999
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
================================================
FILE: misc/snapshotter/nydus-snapshotter.fusedev.service
================================================
[Unit]
Description=nydus snapshotter
After=network.target
Before=containerd.service
[Service]
Type=simple
Environment=HOME=/root
ExecStart=/usr/local/bin/containerd-nydus-grpc --config /etc/nydus/config.toml
Restart=always
RestartSec=1
KillMode=process
OOMScoreAdjust=-999
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
================================================
FILE: misc/snapshotter/nydus-snapshotter.service
================================================
[Unit]
Description=nydus snapshotter
After=network.target
Before=containerd.service
[Service]
Type=simple
Environment=HOME=/root
ExecStart=/usr/local/bin/containerd-nydus-grpc --config /etc/nydus/config-proxy.toml
Restart=always
RestartSec=1
KillMode=process
OOMScoreAdjust=-999
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
================================================
FILE: misc/snapshotter/nydusd-config-localfs.json
================================================
{
"device": {
"backend": {
"type": "localfs",
"config": {
"dir": "/var/lib/containerd/io.containerd.snapshotter.v1.nydus/cache/"
}
},
"cache": {
"type": "blobcache"
}
},
"mode": "direct",
"digest_validate": false,
"iostats_files": false,
"enable_xattr": true,
"amplify_io": 1048576,
"fs_prefetch": {
"enable": true,
"threads_count": 2
}
}
================================================
FILE: misc/snapshotter/nydusd-config.fscache.json
================================================
{
"type": "bootstrap",
"config": {
"backend_type": "registry",
"backend_config": {},
"cache_type": "fscache",
"prefetch_config": {
"enable": true,
"threads_count": 8,
"merging_size": 1048576
}
}
}
================================================
FILE: misc/snapshotter/nydusd-config.fusedev.json
================================================
{
"device": {
"backend": {
"type": "registry",
"config": {
"timeout": 5,
"connect_timeout": 5,
"retry_limit": 2
}
},
"cache": {
"type": "blobcache"
}
},
"mode": "direct",
"digest_validate": false,
"iostats_files": false,
"enable_xattr": true,
"amplify_io": 1048576,
"fs_prefetch": {
"enable": true,
"threads_count": 8,
"merging_size": 1048576,
"prefetch_all": true
}
}
================================================
FILE: misc/snapshotter/overlays/k3s/kustomization.yaml
================================================
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
patches:
- path: mount_k3s_conf.yaml
================================================
FILE: misc/snapshotter/overlays/k3s/mount_k3s_conf.yaml
================================================
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nydus-snapshotter
namespace: nydus-system
spec:
template:
spec:
volumes:
- name: containerd-conf
hostPath:
path: /var/lib/rancher/k3s/agent/etc/containerd/
================================================
FILE: misc/snapshotter/overlays/rke2/kustomization.yaml
================================================
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
patches:
- path: mount_rke2_conf.yaml
================================================
FILE: misc/snapshotter/overlays/rke2/mount_rke2_conf.yaml
================================================
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nydus-snapshotter
namespace: nydus-system
spec:
template:
spec:
volumes:
- name: containerd-conf
hostPath:
path: /var/lib/rancher/rke2/agent/etc/containerd/
================================================
FILE: misc/snapshotter/snapshotter.sh
================================================
#!/usr/bin/env bash
# Copyright (c) 2023. Nydus Developers. All rights reserved.
#
# SPDX-License-Identifier: Apache-2.0
#
set -o errexit
set -o pipefail
set -o nounset
SNAPSHOTTER_ARTIFACTS_DIR="/opt/nydus-artifacts"
# Container runtime config, the default container runtime is containerd
CONTAINER_RUNTIME="${CONTAINER_RUNTIME:-containerd}"
CONTAINER_RUNTIME_CONFIG="/etc/containerd/config.toml"
# Common nydus snapshotter config options
FS_DRIVER="${FS_DRIVER:-fusedev}"
SNAPSHOTTER_GRPC_SOCKET="${SNAPSHOTTER_GRPC_SOCKET:-/run/containerd-nydus/containerd-nydus-grpc.sock}"
# The directory about nydus and nydus snapshotter
NYDUS_CONFIG_DIR="${NYDUS_CONFIG_DIR:-/etc/nydus}"
NYDUS_LIB_DIR="${NYDUS_LIB_DIR:-/var/lib/containerd/io.containerd.snapshotter.v1.nydus}"
NYDUS_BINARY_DIR="${NYDUS_BINARY_DIR:-/usr/local/bin}"
SNAPSHOTTER_SCRYPT_DIR="${SNAPSHOTTER_SCRYPT_DIR:-/opt/nydus}"
# The binary about nydus-snapshotter
SNAPSHOTTER_BINARY="${SNAPSHOTTER_BINARY:-${NYDUS_BINARY_DIR}/containerd-nydus-grpc}"
# The config about nydus snapshotter
SNAPSHOTTER_CONFIG="${SNAPSHOTTER_CONFIG:-${NYDUS_CONFIG_DIR}/config.toml}"
# The systemd service config about nydus snapshotter
SNAPSHOTTER_SERVICE="${SNAPSHOTTER_SERVICE:-/etc/systemd/system/nydus-snapshotter.service}"
# If true, the script would read the config from env.
ENABLE_CONFIG_FROM_VOLUME="${ENABLE_CONFIG_FROM_VOLUME:-false}"
# If true, the script would enable the "runtime specific snapshotter" in containerd config.
ENABLE_RUNTIME_SPECIFIC_SNAPSHOTTER="${ENABLE_RUNTIME_SPECIFIC_SNAPSHOTTER:-false}"
# If true, the snapshotter would be running as a systemd service
ENABLE_SYSTEMD_SERVICE="${ENABLE_SYSTEMD_SERVICE:-false}"
COMMANDLINE=""
# If we fail for any reason a message will be displayed
die() {
msg="$*"
echo "ERROR: $msg" >&2
exit 1
}
print_usage() {
echo "Usage: $0 [deploy/cleanup]"
}
wait_service_active(){
local wait_time="$1"
local sleep_time="$2"
local service="$3"
nsenter -t 1 -m systemctl restart $service
# Wait for containerd to be running
while [ "$wait_time" -gt 0 ]; do
if nsenter -t 1 -m systemctl is-active --quiet $service; then
echo "$service is running"
return 0
else
sleep "$sleep_time"
wait_time=$((wait_time-sleep_time))
fi
done
echo "Timeout reached. $service may not be running."
nsenter -t 1 -m systemctl status $service
return 1
}
function fs_driver_handler() {
if [ "${ENABLE_CONFIG_FROM_VOLUME}" == "true" ]; then
SNAPSHOTTER_CONFIG="${NYDUS_CONFIG_DIR}/config.toml"
else
case "${FS_DRIVER}" in
fusedev)
sed -i -e "s|nydusd_config = .*|nydusd_config = \"${NYDUS_CONFIG_DIR}/nydusd-fusedev.json\"|" "${SNAPSHOTTER_CONFIG}"
sed -i -e "s|fs_driver = .*|fs_driver = \"fusedev\"|" "${SNAPSHOTTER_CONFIG}"
sed -i -e "s|daemon_mode = .*|daemon_mode = \"multiple\"|" "${SNAPSHOTTER_CONFIG}"
;;
fscache)
sed -i -e "s|nydusd_config = .*|nydusd_config = \"${NYDUS_CONFIG_DIR}/nydusd-fscache.json\"|" "${SNAPSHOTTER_CONFIG}"
sed -i -e "s|fs_driver = .*|fs_driver = \"fscache\"|" "${SNAPSHOTTER_CONFIG}"
sed -i -e "s|daemon_mode = .*|daemon_mode = \"multiple\"|" "${SNAPSHOTTER_CONFIG}"
;;
blockdev)
sed -i -e "s|fs_driver = .*|fs_driver = \"blockdev\"|" "${SNAPSHOTTER_CONFIG}"
sed -i -e "s|enable_kata_volume = .*|enable_kata_volume = true|" "${SNAPSHOTTER_CONFIG}"
sed -i -e "s|enable_tarfs = .*|enable_tarfs = true|" "${SNAPSHOTTER_CONFIG}"
sed -i -e "s|daemon_mode = .*|daemon_mode = \"none\"|" "${SNAPSHOTTER_CONFIG}"
sed -i -e "s|export_mode = .*|export_mode = \"layer_block_with_verity\"|" "${SNAPSHOTTER_CONFIG}"
;;
proxy)
sed -i -e "s|fs_driver = .*|fs_driver = \"proxy\"|" "${SNAPSHOTTER_CONFIG}"
sed -i -e "s|enable_kata_volume = .*|enable_kata_volume = true|" "${SNAPSHOTTER_CONFIG}"
sed -i -e "s|daemon_mode = .*|daemon_mode = \"none\"|" "${SNAPSHOTTER_CONFIG}"
;;
*) die "invalid fs driver ${FS_DRIVER}" ;;
esac
fi
COMMANDLINE+=" --config ${SNAPSHOTTER_CONFIG}"
}
function configure_snapshotter() {
echo "configuring snapshotter"
# Copy the container runtime config to a backup
cp "$CONTAINER_RUNTIME_CONFIG" "$CONTAINER_RUNTIME_CONFIG".bak.nydus
# When trying to edit the config file that is mounted by docker with `sed -i`, the error would happend:
# sed: cannot rename /etc/containerd/config.tomlpmdkIP: Device or resource busy
# The reason is that `sed`` with option `-i` creates new file, and then replaces the old file with the new one,
# which definitely will change the file inode. But the file is mounted by docker, which means we are not allowed to
# change its inode from within docker container.
#
# So we copy the original file to a backup, make changes to the backup, and then overwrite the original file with the backup.
cp "$CONTAINER_RUNTIME_CONFIG" "$CONTAINER_RUNTIME_CONFIG".bak
# Check and add nydus proxy plugin in the config
if grep -q '\[proxy_plugins.nydus\]' "$CONTAINER_RUNTIME_CONFIG".bak; then
echo "the config has configured the nydus proxy plugin!"
else
echo "Not found nydus proxy plugin!"
cat <>"$CONTAINER_RUNTIME_CONFIG".bak
[proxy_plugins.nydus]
type = "snapshot"
address = "$SNAPSHOTTER_GRPC_SOCKET"
EOF
fi
if grep -q 'disable_snapshot_annotations' "$CONTAINER_RUNTIME_CONFIG".bak; then
sed -i -e "s|disable_snapshot_annotations = .*|disable_snapshot_annotations = false|" \
"${CONTAINER_RUNTIME_CONFIG}".bak
else
sed -i '/\[plugins\..*\.containerd\]/a\disable_snapshot_annotations = false' \
"${CONTAINER_RUNTIME_CONFIG}".bak
fi
if grep -q 'discard_unpacked_layers' "$CONTAINER_RUNTIME_CONFIG".bak; then
sed -i -e "s|discard_unpacked_layers = .*|discard_unpacked_layers = false|" \
"${CONTAINER_RUNTIME_CONFIG}".bak
else
sed -i '/\[plugins\..*\.containerd\]/a\discard_unpacked_layers = false' \
"${CONTAINER_RUNTIME_CONFIG}".bak
fi
if [ "${ENABLE_RUNTIME_SPECIFIC_SNAPSHOTTER}" == "false" ]; then
sed -i -e '/\[plugins\..*\.containerd\]/,/snapshotter =/ s/snapshotter = "[^"]*"/snapshotter = "nydus"/' "${CONTAINER_RUNTIME_CONFIG}".bak
fi
cat "${CONTAINER_RUNTIME_CONFIG}".bak > "${CONTAINER_RUNTIME_CONFIG}"
}
function install_snapshotter() {
echo "install nydus snapshotter artifacts"
find "${SNAPSHOTTER_ARTIFACTS_DIR}${NYDUS_BINARY_DIR}" -type f -exec install -Dm 755 -t "${NYDUS_BINARY_DIR}" "{}" \;
find "${SNAPSHOTTER_ARTIFACTS_DIR}${NYDUS_CONFIG_DIR}" -type f -exec install -Dm 644 -t "${NYDUS_CONFIG_DIR}" "{}" \;
install -D -m 644 "${SNAPSHOTTER_ARTIFACTS_DIR}${SNAPSHOTTER_SCRYPT_DIR}/snapshotter.sh" "${SNAPSHOTTER_SCRYPT_DIR}/snapshotter.sh"
if [ "${ENABLE_SYSTEMD_SERVICE}" == "true" ]; then
install -D -m 644 "${SNAPSHOTTER_ARTIFACTS_DIR}${SNAPSHOTTER_SERVICE}" "${SNAPSHOTTER_SERVICE}"
fi
if [ "${ENABLE_CONFIG_FROM_VOLUME}" == "true" ]; then
find "/etc/nydus-snapshotter" -type f -exec install -Dm 644 -t "${NYDUS_CONFIG_DIR}" "{}" \;
fi
}
function deploy_snapshotter() {
echo "deploying snapshotter"
install_snapshotter
COMMANDLINE="${SNAPSHOTTER_BINARY}"
fs_driver_handler
configure_snapshotter
if [ "${ENABLE_SYSTEMD_SERVICE}" == "true" ]; then
echo "running snapshotter as systemd service"
sed -i "s|^ExecStart=.*$|ExecStart=$COMMANDLINE|" "${SNAPSHOTTER_SERVICE}"
nsenter -t 1 -m systemctl daemon-reload
nsenter -t 1 -m systemctl enable nydus-snapshotter.service
wait_service_active 30 5 nydus-snapshotter
else
echo "running snapshotter as standalone process"
${COMMANDLINE} &
fi
wait_service_active 30 5 ${CONTAINER_RUNTIME}
}
function remove_images() {
local SNAPSHOTTER="nydus"
local NAMESPACE="k8s.io"
local ctr_args="nsenter -t 1 -m ctr"
if [[ " k3s k3s-agent rke2-agent rke2-server " =~ " ${CONTAINER_RUNTIME} " ]]; then
ctr_args+=" --address /run/k3s/containerd/containerd.sock"
fi
ctr_args+=" --namespace $NAMESPACE"
# List all snapshots for nydus snapshotter
local SNAPSHOTS=$($ctr_args snapshot --snapshotter $SNAPSHOTTER ls | awk 'NR>1 {print $1}')
echo "Images associated with snapshotter $SNAPSHOTTER:"
# Loop through each snapshot and find associated contents
for SNAPSHOT in $SNAPSHOTS; do
local CONTENTS=$($ctr_args content ls | grep $SNAPSHOT | awk '{print $1}')
echo "Snapshot: $SNAPSHOT, Contents: $CONTENTS"
if [ -z "$CONTENTS" ]; then
continue
fi
# Loop through each content and find associated digests of images
for CONTENT in $CONTENTS; do
local DIGESTS=$($ctr_args image ls | grep $CONTENT | awk '{print $3}')
echo "Content: $CONTENT, Digests: $DIGESTS"
if [ -z "$DIGESTS" ]; then
continue
fi
# Loop through each digest and find associated image references
for DIGEST in $DIGESTS; do
local IMAGES=$($ctr_args image ls | grep $DIGEST | awk '{print $1}')
echo "Digest: $DIGEST, Images: $IMAGES"
if [ -z "$IMAGES" ]; then
continue
fi
for IMAGE in $IMAGES; do
# Delete the image
$ctr_args images rm $IMAGE > /dev/null 2>&1 || true
echo "Image $IMAGE removed"
done
done
# Delete the content
$ctr_args content rm $CONTENT > /dev/null 2>&1 || true
echo "content $CONTENT removed"
done
# Delete the snapshot
$ctr_args snapshot --snapshotter $SNAPSHOTTER rm $SNAPSHOT > /dev/null 2>&1 || true
echo "snapshot $SNAPSHOT removed"
done
echo "INFO: Images removed"
}
function cleanup_snapshotter() {
echo "cleaning up snapshotter"
pid=$(ps -ef | grep containerd-nydus-grpc | grep -v grep | awk '{print $1}' || true)
if [ ! -z "$pid" ]; then
remove_images
fi
echo "Recover containerd config"
cat "$CONTAINER_RUNTIME_CONFIG".bak.nydus >"$CONTAINER_RUNTIME_CONFIG"
if [ "${ENABLE_SYSTEMD_SERVICE}" == "true" ]; then
nsenter -t 1 -m systemctl stop nydus-snapshotter.service
nsenter -t 1 -m systemctl disable --now nydus-snapshotter.service
rm -f "${SNAPSHOTTER_SERVICE}" || true
else
kill -9 $pid || true
fi
wait_service_active 30 5 ${CONTAINER_RUNTIME}
echo "Removing nydus-snapshotter artifacts from host"
rm -f "${NYDUS_BINARY_DIR}"/nydus*
rm -rf "${NYDUS_CONFIG_DIR}"/*
rm -rf "${SNAPSHOTTER_SCRYPT_DIR}"/*
rm -rf "${NYDUS_LIB_DIR}"/*
echo "cleaned up snapshotter"
}
function get_container_runtime() {
local runtime=$(kubectl get node ${NODE_NAME} -o jsonpath='{.status.nodeInfo.containerRuntimeVersion}')
if [ "$?" -ne 0 ]; then
die "\"$NODE_NAME\" is an invalid node name"
fi
if echo "$runtime" | grep -qE 'containerd.*-k3s'; then
if nsenter -t 1 -m systemctl is-active --quiet rke2-agent; then
echo "rke2-agent"
elif nsenter -t 1 -m systemctl is-active --quiet rke2-server; then
echo "rke2-server"
elif nsenter -t 1 -m systemctl is-active --quiet k3s-agent; then
echo "k3s-agent"
else
echo "k3s"
fi
elif nsenter -t 1 -m systemctl is-active --quiet k0scontroller; then
echo "k0s-controller"
elif nsenter -t 1 -m systemctl is-active --quiet k0sworker; then
echo "k0s-worker"
else
echo "$runtime" | awk -F '[:]' '{print $1}'
fi
}
function main() {
# script requires that user is root
euid=$(id -u)
if [[ $euid -ne 0 ]]; then
die "This script must be run as root"
fi
CONTAINER_RUNTIME=$(get_container_runtime)
if [[ " k3s k3s-agent rke2-agent rke2-server " =~ " ${CONTAINER_RUNTIME} " ]]; then
CONTAINER_RUNTIME_CONFIG_TMPL="${CONTAINER_RUNTIME_CONFIG}.tmpl"
if [ ! -f "${CONTAINER_RUNTIME_CONFIG_TMPL}" ]; then
cp "${CONTAINER_RUNTIME_CONFIG}" "${CONTAINER_RUNTIME_CONFIG_TMPL}"
fi
CONTAINER_RUNTIME_CONFIG="${CONTAINER_RUNTIME_CONFIG_TMPL}"
elif [ "${CONTAINER_RUNTIME}" == "containerd" ]; then
if [ ! -f "${CONTAINER_RUNTIME_CONFIG}" ]; then
mkdir -p $(dirname ${CONTAINER_RUNTIME_CONFIG}) || true
if [ -x $(command -v ${CONTAINER_RUNTIME}) ]; then
${CONTAINER_RUNTIME} config default > ${CONTAINER_RUNTIME_CONFIG}
else
die "Not able to find an executable ${CONTAINER_RUNTIME} binary to create the default config"
fi
fi
else
die "${CONTAINER_RUNTIME} is a unsupported containe runtime"
fi
action=${1:-}
if [ -z "$action" ]; then
print_usage
die "invalid arguments"
fi
case "$action" in
deploy)
deploy_snapshotter
;;
cleanup)
cleanup_snapshotter
;;
*)
die "invalid arguments"
print_usage
;;
esac
sleep infinity
}
main "$@"
================================================
FILE: pkg/auth/cri.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package auth
import (
"context"
"fmt"
"time"
"github.com/containerd/containerd/v2/defaults"
"github.com/containerd/containerd/v2/pkg/dialer"
"github.com/containerd/log"
"github.com/containerd/stargz-snapshotter/service/keychain/cri"
"github.com/containerd/stargz-snapshotter/service/resolver"
"github.com/pkg/errors"
"google.golang.org/grpc"
"google.golang.org/grpc/backoff"
"google.golang.org/grpc/credentials/insecure"
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
)
const DefaultImageServiceAddress = "/run/containerd/containerd.sock"
// TODO: embed in CRIProvider
// Should be concurrency safe
var credentials []resolver.Credential = make([]resolver.Credential, 0, 8)
// CRIProvider retrieves credentials from CRI image pull requests.
type CRIProvider struct{}
// NewCRIProvider creates a new CRI-based auth provider.
func NewCRIProvider() *CRIProvider {
return &CRIProvider{}
}
func (p *CRIProvider) String() string {
return "cri"
}
func (p *CRIProvider) GetCredentials(req *AuthRequest) (*PassKeyChain, error) {
if len(credentials) == 0 {
return nil, errors.New("no Credentials parsers")
}
if req == nil || req.Ref == "" {
return nil, errors.New("ref not found in request")
}
refSpec, host, err := parseReference(req.Ref)
if err != nil {
return nil, errors.Wrapf(err, "parse reference %s", req.Ref)
}
var u, s string
var keychain *PassKeyChain
for _, cred := range credentials {
if username, secret, err := cred(host, refSpec); err != nil {
return nil, err
} else if username != "" || secret != "" {
u = username
s = secret
keychain = &PassKeyChain{
Username: u,
Password: s,
}
return keychain, nil
}
}
return nil, fmt.Errorf("no credentials found for host: %s", host)
}
// newCRIConn creates a gRPC connection to the CRI service.
// This function is borrowed from stargz
func newCRIConn(criAddr string) (*grpc.ClientConn, error) {
// TODO: make gRPC options configurable from config.toml
backoffConfig := backoff.DefaultConfig
backoffConfig.MaxDelay = 3 * time.Second
connParams := grpc.ConnectParams{
Backoff: backoffConfig,
}
gopts := []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithConnectParams(connParams),
grpc.WithContextDialer(dialer.ContextDialer),
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(defaults.DefaultMaxRecvMsgSize)),
grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(defaults.DefaultMaxSendMsgSize)),
}
return grpc.NewClient(dialer.DialAddress(criAddr), gopts...)
}
// AddImageProxy sets up a CRI image proxy that intercepts credentials.
// This should be called once at startup to enable CRI credential capture.
// from stargz-snapshotter/cmd/containerd-stargz-grpc/main.go#main
func AddImageProxy(ctx context.Context, rpc *grpc.Server, imageServiceAddress string) {
criAddr := DefaultImageServiceAddress
if imageServiceAddress != "" {
criAddr = imageServiceAddress
}
criCred, criServer := cri.NewCRIKeychain(ctx, func() (runtime.ImageServiceClient, error) {
conn, err := newCRIConn(criAddr)
if err != nil {
return nil, err
}
return runtime.NewImageServiceClient(conn), nil
})
runtime.RegisterImageServiceServer(rpc, criServer)
credentials = append(credentials, criCred)
log.G(ctx).WithField("target-image-service", criAddr).Info("setup image proxy keychain")
}
================================================
FILE: pkg/auth/cri_test.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package auth
import (
"context"
"net"
"path/filepath"
"testing"
"github.com/containerd/containerd/v2/pkg/dialer"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
)
type MockImageService struct {
runtime.UnimplementedImageServiceServer
}
func (*MockImageService) PullImage(_ context.Context, _ *runtime.PullImageRequest) (*runtime.PullImageResponse, error) {
return &runtime.PullImageResponse{}, nil
}
func TestFromImagePull(t *testing.T) {
var err error
assertions := assert.New(t)
ctx := context.TODO()
d := t.TempDir()
tagImage := "docker.io/library/busybox:latest"
// should return nil if no proxy
kc, err := NewCRIProvider().GetCredentials(&AuthRequest{Ref: tagImage})
assertions.Nil(kc)
assertions.Error(err)
// Mocking the end CRI request consumer.
mockRPC := grpc.NewServer()
mockSocket := filepath.Join(d, "mock.sock")
listenMock, err := net.Listen("unix", mockSocket)
assertions.NoError(err)
// Mocking the end CRI request consumer.
server := &MockImageService{}
runtime.RegisterImageServiceServer(mockRPC, server)
go func() {
err := mockRPC.Serve(listenMock)
assertions.NoError(err)
}()
defer mockRPC.Stop()
// The server of CRI image service proxy.
proxyRPC := grpc.NewServer()
proxySocket := filepath.Join(d, "proxy.sock")
listenProxy, err := net.Listen("unix", proxySocket)
assertions.NoError(err)
AddImageProxy(ctx, proxyRPC, mockSocket)
go func() {
err := proxyRPC.Serve(listenProxy)
assertions.NoError(err)
}()
defer proxyRPC.Stop()
// should return empty kc before pulling
kc, err = NewCRIProvider().GetCredentials(&AuthRequest{Ref: tagImage})
assertions.Nil(kc)
assertions.Error(err)
gopts := []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithContextDialer(dialer.ContextDialer),
}
conn, err := grpc.NewClient(dialer.DialAddress(proxySocket), gopts...)
assertions.NoError(err)
criClient := runtime.NewImageServiceClient(conn)
_, err = criClient.PullImage(ctx, &runtime.PullImageRequest{
Image: &runtime.ImageSpec{
Image: tagImage,
},
Auth: &runtime.AuthConfig{
Username: "test",
Password: "passwd",
},
})
assertions.NoError(err)
// get correct kc after pulling
kc, err = NewCRIProvider().GetCredentials(&AuthRequest{Ref: tagImage})
assertions.Equal("test", kc.Username)
assertions.Equal("passwd", kc.Password)
assertions.NoError(err)
// get empty kc with wrong tag
kc, err = NewCRIProvider().GetCredentials(&AuthRequest{Ref: "docker.io/library/busybox:another"})
assertions.Nil(kc)
assertions.Error(err)
image2 := "ghcr.io/busybox:latest"
_, err = criClient.PullImage(ctx, &runtime.PullImageRequest{
Image: &runtime.ImageSpec{
Image: image2,
},
Auth: &runtime.AuthConfig{
Username: "test_1",
Password: "passwd_1",
},
})
assertions.NoError(err)
// get correct kc after pulling
kc, err = NewCRIProvider().GetCredentials(&AuthRequest{Ref: image2})
assertions.NoError(err)
assertions.Equal(kc.Username, "test_1")
assertions.Equal(kc.Password, "passwd_1")
// should work with digest
digestImage := "docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa"
_, err = criClient.PullImage(ctx, &runtime.PullImageRequest{
Image: &runtime.ImageSpec{
Image: digestImage,
},
Auth: &runtime.AuthConfig{
Username: "digest",
Password: "dpwd",
},
})
assertions.NoError(err)
// get correct kc after pulling
kc, err = NewCRIProvider().GetCredentials(&AuthRequest{Ref: digestImage})
assertions.NoError(err)
assertions.Equal("digest", kc.Username)
assertions.Equal("dpwd", kc.Password)
}
================================================
FILE: pkg/auth/docker.go
================================================
/*
* Copyright (c) 2021. Ant Group. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package auth
import (
"fmt"
"os"
dockerconfig "github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile"
"github.com/pkg/errors"
)
const (
dockerHost = "https://index.docker.io/v1/"
convertedDockerHost = "registry-1.docker.io"
)
// DockerProvider retrieves credentials from Docker's config.json.
type DockerProvider struct {
dockerConfig *configfile.ConfigFile
}
// NewDockerProvider creates a new Docker config-based auth provider.
func NewDockerProvider() *DockerProvider {
return &DockerProvider{
dockerConfig: dockerconfig.LoadDefaultConfigFile(os.Stderr),
}
}
// CanRenew implements RenewableProvider. Docker credentials can be
// refreshed by re-reading the config file. Works well with docker credential helpers.
func (p *DockerProvider) CanRenew() bool { return true }
func (p *DockerProvider) String() string {
return "docker"
}
// GetCredentials retrieves credentials from Docker's config.json.
// Returns nil if no credentials are found for the registry.
func (p *DockerProvider) GetCredentials(req *AuthRequest) (*PassKeyChain, error) {
if req == nil || req.Ref == "" {
return nil, errors.New("ref not found in request")
}
_, host, err := parseReference(req.Ref)
if err != nil {
return nil, errors.Wrapf(err, "parse reference %s", req.Ref)
}
// The host of docker hub image will be converted to `registry-1.docker.io` in:
// github.com/containerd/containerd/remotes/docker/registry.go
// But we need use the key `https://index.docker.io/v1/` to find auth from docker config.
if host == convertedDockerHost {
host = dockerHost
}
authConfig, err := p.dockerConfig.GetAuthConfig(host)
if err != nil {
return nil, errors.Wrapf(err, "no auth from docker config for host %s", host)
}
// Do not return partially empty auth. It makes caller life easier.
if len(authConfig.Username) == 0 || len(authConfig.Password) == 0 {
return nil, fmt.Errorf("auth config not complete for host: %s", host)
}
return &PassKeyChain{
Username: authConfig.Username,
Password: authConfig.Password,
}, nil
}
================================================
FILE: pkg/auth/docker_test.go
================================================
/*
* Copyright (c) 2021. Ant Group. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package auth
import (
"encoding/base64"
"fmt"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
const (
testConfigFmt = `
{
"auths": {
"%s": {
"auth": "%s"
},
"%s": {
"auth": "%s"
}
}
}
`
dockerUser = "dockeruserfoobar"
dockerPass = "dockerpassfoobar"
extraHost = "reg.docker.alibaba-cloud.com"
extraUser = "extrauserfoobar"
extraPass = "extrapassfoobar"
configFile = "config.json"
)
var oldDockerConfig = os.Getenv("DOCKER_CONFIG")
func setupDockerConfig() (string, error) {
dir, err := os.MkdirTemp("", "testdocker-")
if err != nil {
return "", err
}
os.Setenv("DOCKER_CONFIG", dir)
err = os.WriteFile(filepath.Join(dir, configFile),
[]byte(fmt.Sprintf(testConfigFmt, dockerHost, base64.StdEncoding.EncodeToString([]byte(dockerUser+":"+dockerPass)),
extraHost, base64.StdEncoding.EncodeToString([]byte(extraUser+":"+extraPass)))),
0600)
if err != nil {
os.RemoveAll(dir)
return "", err
}
return dir, nil
}
func TestDockerCred(t *testing.T) {
assert := assert.New(t)
dir, err := setupDockerConfig()
assert.Nil(err)
defer func() {
os.RemoveAll(dir)
os.Setenv("DOCKER_CONFIG", oldDockerConfig)
}()
// Empty host should get empty auth
auth, err := NewDockerProvider().GetCredentials(&AuthRequest{Ref: ""})
assert.Nil(auth)
assert.Error(err)
// Unmatching host should get empty auth
auth, err = NewDockerProvider().GetCredentials(&AuthRequest{Ref: "foo"})
assert.Nil(auth)
assert.Error(err)
auth, err = NewDockerProvider().GetCredentials(&AuthRequest{Ref: convertedDockerHost + "/foo:bar"})
assert.NotNil(auth)
assert.NoError(err)
assert.Equal(auth.Username, dockerUser)
assert.Equal(auth.Password, dockerPass)
auth, err = NewDockerProvider().GetCredentials(&AuthRequest{Ref: extraHost + "/foo:bar"})
assert.NotNil(auth)
assert.NoError(err)
assert.Equal(auth.Username, extraUser)
assert.Equal(auth.Password, extraPass)
}
================================================
FILE: pkg/auth/keychain.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package auth
import (
"encoding/base64"
stderrors "errors"
"fmt"
"strings"
"time"
"github.com/containerd/log"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/pkg/errors"
)
const (
sep = ":"
)
var (
emptyPassKeyChain = PassKeyChain{}
)
// PassKeyChain is user/password based key chain
type PassKeyChain struct {
Username string
Password string
}
func FromBase64(str string) (PassKeyChain, error) {
decoded, err := base64.StdEncoding.DecodeString(str)
if err != nil {
return emptyPassKeyChain, err
}
pair := strings.Split(string(decoded), sep)
if len(pair) != 2 {
return emptyPassKeyChain, errors.New("invalid registry auth token")
}
return PassKeyChain{
Username: pair[0],
Password: pair[1],
}, nil
}
func (kc PassKeyChain) ToBase64() string {
if kc.Username == "" && kc.Password == "" {
return ""
}
return base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", kc.Username, kc.Password)))
}
// TokenBase check if PassKeyChain is token based, when username is empty and password is not empty
// then password is registry token
func (kc PassKeyChain) TokenBase() bool {
return kc.Username == "" && kc.Password != ""
}
// renewableProviders returns the ordered list of providers used exclusively
// by the renewal goroutine. CRI and Labels are excluded: CRI caches
// credentials globally and would keep returning them at renewal time,
// preventing the renewable providers from being reached; Labels are only
// available at pull time via snapshot labels and are not accessible during
// renewal. It is a variable so tests can substitute a different builder.
var renewableProviders = func() []AuthProvider {
providers := []AuthProvider{NewDockerProvider()}
if kubeletProvider != nil {
providers = append(providers, kubeletProvider)
}
return append(providers, NewKubeSecretProvider())
}
// buildProviders returns the full ordered list of auth providers.
// Priority: labels > CRI > docker > kubelet > kubesecret.
// It is a variable so tests can substitute a different builder.
var buildProviders = func() []AuthProvider {
return append([]AuthProvider{NewLabelsProvider(), NewCRIProvider()}, renewableProviders()...)
}
// GetRegistryKeyChain retrieves image pull credentials from the first provider
// that returns a result, checked in priority order:
// 1. credential renewal store (if enabled)
// 2. username and secrets labels
// 3. cri request
// 4. docker config
// 5. kubelet credential helpers
// 6. k8s docker config secret
//
// When a renewable provider returns credentials and the renewal store is
// enabled, the credentials are cached for periodic renewal.
func GetRegistryKeyChain(ref string, labels map[string]string) *PassKeyChain {
return getRegistryKeyChainFromProviders(ref, labels, buildProviders())
}
// getRegistryKeyChainFromProviders is the testable core of GetRegistryKeyChain.
func getRegistryKeyChainFromProviders(ref string, labels map[string]string, providers []AuthProvider) *PassKeyChain {
logger := log.L.WithField("ref", ref)
authReq := &AuthRequest{Ref: ref, Labels: labels}
// Serve from the renewal store if available.
if renewalStore != nil {
if kc := renewalStore.Get(ref); kc != nil {
logger.Debug("serving credentials from renewal store")
return kc
}
// If not available, request credentials valid until the next renewal tick.
authReq.ValidUntil = time.Now().Add(renewalStore.renewInterval)
}
return fetchFromProviders(authReq, providers)
}
// fetchFromProviders walks providers in order and returns credentials from the
// first one that succeeds. If the winning provider is renewable and the renewal
// store is active, the credentials are cached for periodic renewal.
func fetchFromProviders(req *AuthRequest, providers []AuthProvider) *PassKeyChain {
logger := log.L.WithField("ref", req.Ref)
var errs []error
for _, provider := range providers {
logger.Debugf("Trying to get credentials from %s", provider)
kc, err := provider.GetCredentials(req)
if err != nil {
errs = append(errs, errors.Wrapf(err, "get credentials from %T", provider))
}
if kc != nil {
logger.Infof("Got credentials from provider %s", provider)
// Cache in the renewal store when the provider supports renewal.
if renewalStore != nil {
if rp, ok := provider.(RenewableProvider); ok && rp.CanRenew() {
renewalStore.Add(req.Ref, kc)
}
}
return kc
}
}
if len(errs) > 0 {
logger.WithError(stderrors.Join(errs...)).Warn("Could not get registry credentials.")
}
return nil
}
func GetKeyChainByRef(ref string, labels map[string]string) (*PassKeyChain, error) {
keychain := GetRegistryKeyChain(ref, labels)
return keychain, nil
}
func (kc PassKeyChain) Resolve(_ authn.Resource) (authn.Authenticator, error) {
return authn.FromConfig(kc.toAuthConfig()), nil
}
// toAuthConfig convert PassKeyChain to authn.AuthConfig when kc is token based,
// RegistryToken is preferred to
func (kc PassKeyChain) toAuthConfig() authn.AuthConfig {
if kc.TokenBase() {
return authn.AuthConfig{
RegistryToken: kc.Password,
}
}
return authn.AuthConfig{
Username: kc.Username,
Password: kc.Password,
}
}
================================================
FILE: pkg/auth/kubelet.go
================================================
/*
* Copyright (c) 2026. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package auth
import (
"bytes"
"context"
"encoding/json"
"fmt"
"maps"
"net"
"net/url"
"os"
"os/exec"
"path/filepath"
"slices"
"sort"
"strings"
"sync"
"time"
"github.com/containerd/log"
"github.com/pkg/errors"
kubeletconfigv1 "k8s.io/kubelet/config/v1"
credentialproviderv1 "k8s.io/kubelet/pkg/apis/credentialprovider/v1"
"sigs.k8s.io/yaml"
)
const (
pluginExecTimeout = 1 * time.Minute
// globalCacheKey is the key used for caching credentials that are not specific to a registry or image.
// Angle brackets are used to avoid conflicts with actual image or registry names.
globalCacheKey = ""
)
var (
kubeletProvider *KubeletProvider
kubeletProviderMu sync.Mutex
)
// KubeletProvider retrieves credentials using Kubernetes credential provider plugins.
type KubeletProvider struct {
plugins []*kubeletconfigv1.CredentialProvider
binDir string
mu sync.RWMutex
cache map[string]*kubeletCredential // cache key (image or registry or global) -> cached credential
}
// kubeletCredential pairs a PassKeyChain with its provider-reported expiry.
type kubeletCredential struct {
keychains map[string]*PassKeyChain
expiresAt time.Time // zero means no cache (always re-exec plugin)
}
// InitKubeletProvider initializes the global kubelet credential provider.
// This should be called once at startup if kubelet credential providers are enabled.
func InitKubeletProvider(configPath, binDir string) error {
kubeletProviderMu.Lock()
defer kubeletProviderMu.Unlock()
if kubeletProvider != nil {
return nil
}
provider, err := NewKubeletProvider(configPath, binDir)
if err != nil {
return errors.Wrap(err, "failed to create kubelet provider")
}
kubeletProvider = provider
log.L.Info("kubelet credential provider initialized")
return nil
}
// NewKubeletProvider creates a new kubelet credential helpers-based auth provider.
func NewKubeletProvider(configPath, binDir string) (*KubeletProvider, error) {
if configPath == "" {
return nil, errors.New("config path cannot be empty")
}
if binDir == "" {
return nil, errors.New("bin directory cannot be empty")
}
// Load configuration
data, err := os.ReadFile(configPath)
if err != nil {
return nil, errors.Wrapf(err, "failed to read config file %s", configPath)
}
var config kubeletconfigv1.CredentialProviderConfig
// Using the yaml library from sigs.k8s.io/yaml because it supports both YAML and JSON
// and the type CredentialProvider only has json markups which are used by this lib's Unmarshal
if err := yaml.Unmarshal(data, &config); err != nil {
return nil, errors.Wrap(err, "failed to parse credential provider config")
}
if len(config.Providers) == 0 {
return nil, errors.New("at least one provider is required")
}
provider := &KubeletProvider{
plugins: make([]*kubeletconfigv1.CredentialProvider, 0, len(config.Providers)),
binDir: binDir,
cache: make(map[string]*kubeletCredential),
}
// Validate and register credential providers
// Heavily inspired by kubelet's validateCredentialProviderConfig function
credProviderNames := make(map[string]struct{})
for i := range config.Providers {
if err := validateCredentialProvider(&config.Providers[i]); err != nil {
return nil, errors.Wrapf(err, "failed to validate credential provider %s", config.Providers[i].Name)
}
// Check for duplicate provider names
if _, ok := credProviderNames[config.Providers[i].Name]; ok {
return nil, fmt.Errorf("duplicate provider name: %s", config.Providers[i].Name)
}
credProviderNames[config.Providers[i].Name] = struct{}{}
provider.plugins = append(provider.plugins, &config.Providers[i])
log.L.WithField("name", config.Providers[i].Name).Info("registered kubelet credential provider plugin")
}
return provider, nil
}
// validateCredentialProvider validates a single credential provider configuration.
func validateCredentialProvider(p *kubeletconfigv1.CredentialProvider) error {
// Validate provider name
if p.Name == "" {
return fmt.Errorf("provider name is required")
}
// Provider name must not contain path separators or special characters
if strings.ContainsAny(p.Name, " /\\") || p.Name == "." || p.Name == ".." {
return fmt.Errorf("invalid provider name %q: cannot contain spaces, path separators, or be '.' or '..'", p.Name)
}
// Validate API version
if p.APIVersion == "" {
return fmt.Errorf("provider %s: apiVersion is required", p.Name)
}
// Only support v1 for now
if p.APIVersion != "credentialprovider.kubelet.k8s.io/v1" {
return fmt.Errorf("provider %s: unsupported apiVersion %q (only v1 is supported)", p.Name, p.APIVersion)
}
// Validate match images
if len(p.MatchImages) == 0 {
return fmt.Errorf("provider %s: at least one matchImage is required", p.Name)
}
if slices.Contains(p.MatchImages, "") {
return fmt.Errorf("provider %s: empty matchImage pattern", p.Name)
}
// Validate cache duration
if p.DefaultCacheDuration == nil {
return fmt.Errorf("provider %s: defaultCacheDuration is required", p.Name)
}
if p.DefaultCacheDuration.Duration < 0 {
return fmt.Errorf("provider %s: defaultCacheDuration must be >= 0", p.Name)
}
// Validate environment variables
envNames := make(map[string]struct{})
for _, env := range p.Env {
if env.Name == "" {
return fmt.Errorf("provider %s: environment variable name cannot be empty", p.Name)
}
if _, ok := envNames[env.Name]; ok {
return fmt.Errorf("provider %s: duplicate environment variable name: %s", p.Name, env.Name)
}
envNames[env.Name] = struct{}{}
}
return nil
}
// CanRenew implements RenewableProvider. Kubelet credentials can be
// refreshed by re-executing the credential provider plugins.
func (p *KubeletProvider) CanRenew() bool { return true }
func (p *KubeletProvider) String() string {
return "kubelet"
}
// GetCredentials retrieves credentials using kubelet credential provider plugins.
// It first checks the cache using the same cacheKeyType-based lookup as the
// kubelet (image -> registry -> global). On a cache miss it executes all
// matching plugins, stores results keyed by cacheKeyType, and returns the most
// specific match for the requested ref.
func (p *KubeletProvider) GetCredentials(req *AuthRequest) (*PassKeyChain, error) {
if req == nil || req.Ref == "" {
return nil, errors.New("ref not found in request")
}
// Normalize the reference to handle short refs like "nginx:latest" -> "docker.io/library/nginx:latest"
refSpec, _, err := parseReference(req.Ref)
if err != nil {
return nil, errors.Wrap(err, "failed to parse image reference")
}
image := refSpec.String()
// Evict expired before adding new ones to bound the map size
p.evictExpired()
// Fast path: serve from cache when a valid-long-enough credential exists.
// Lookup order follows the kubelet: image key, registry key, global key.
cred := p.getCachedCredential(image, req.ValidUntil)
if cred != nil {
if kc := bestKeychainMatch(cred.keychains, image); kc != nil {
log.L.WithField("ref", req.Ref).Debug("serving kubelet credentials from cache")
return kc, nil
}
}
// Slow path: execute matching plugins
allKeychains := make(map[string]*PassKeyChain)
for _, plugin := range p.plugins {
if !isImageAllowed(plugin, image) {
continue
}
// The spec mentions that:
// "If one of the strings matches the requested image from the kubelet, the plugin will be invoked and given a chance to provide credentials"
// So each matching plugin should have a chance to fetch credentials
// TODO: parallelize?
resp, err := p.execPlugin(context.Background(), plugin, image)
if err != nil {
log.L.WithError(err).WithFields(map[string]any{"plugin": plugin.Name, "ref": req.Ref}).Warn("failed to execute credential provider plugin")
continue
}
if resp.Auth == nil {
// Spec: A plugin should set this field (Auth) to null if no valid credentials can be returned for the requested image.
continue
}
cacheKey := computeCacheKey(resp.CacheKeyType, image)
if cacheKey == "" {
log.L.WithFields(map[string]any{
"plugin": plugin.Name,
"cacheKeyType": resp.CacheKeyType,
}).Warn("credential provider plugin returned invalid cacheKeyType")
continue
}
keychains := make(map[string]*PassKeyChain, len(resp.Auth))
for registryGlob, authConfig := range resp.Auth {
// First plugin wins for overlapping auth keys, matching the kubelet spec:
// "If providers return overlapping auth keys, the value from the provider
// earlier in this list is used."
if _, exists := allKeychains[registryGlob]; exists {
continue
}
kc := &PassKeyChain{
Username: authConfig.Username,
Password: authConfig.Password,
}
keychains[registryGlob] = kc
allKeychains[registryGlob] = kc
}
var expiresAt time.Time
if d := resolveCacheDuration(resp, plugin); d > 0 {
expiresAt = time.Now().Add(d)
}
// Only cache when the plugin provides a TTL.
// A zero duration means no caching (plugin will be re-executed on the next request).
if !expiresAt.IsZero() {
log.L.WithFields(map[string]any{
"cacheKeyType": resp.CacheKeyType,
"cacheKey": cacheKey,
"duration": time.Until(expiresAt).Round(time.Second),
"registries": slices.Collect(maps.Keys(keychains)),
}).Debug("caching kubelet credential provider response")
p.mu.Lock()
p.cache[cacheKey] = &kubeletCredential{
keychains: keychains,
expiresAt: expiresAt,
}
p.mu.Unlock()
}
}
if len(allKeychains) == 0 {
return nil, errors.New("no credentials found")
}
kc := bestKeychainMatch(allKeychains, image)
if kc == nil {
return nil, errors.New("no matching registries found")
}
return kc, nil
}
// evictExpired removes all expired entries from p.cache.
func (p *KubeletProvider) evictExpired() {
p.mu.Lock()
defer p.mu.Unlock()
now := time.Now()
for key, cred := range p.cache {
if cred.expiresAt.IsZero() || now.After(cred.expiresAt) {
delete(p.cache, key)
}
}
}
// getCachedCredential looks up a cached credential for the given image using the
// same lookup order as the kubelet: image key, registry key, then global key.
// Returns nil when no valid, non-expired match exists that satisfies validUntil.
func (p *KubeletProvider) getCachedCredential(image string, validUntil time.Time) *kubeletCredential {
p.mu.RLock()
defer p.mu.RUnlock()
now := time.Now()
for _, key := range []string{image, parseRegistry(image), globalCacheKey} {
cred, ok := p.cache[key]
if !ok {
continue
}
if cred.expiresAt.IsZero() || now.After(cred.expiresAt) {
continue
}
if !validUntil.IsZero() && !cred.expiresAt.After(validUntil) {
continue
}
return cred
}
return nil
}
// bestKeychainMatch returns the most specific PassKeyChain whose registry glob
// matches image, using reverse-alphabetical order (longer/more specific paths first).
func bestKeychainMatch(keychains map[string]*PassKeyChain, image string) *PassKeyChain {
var matching []string
for registry := range keychains {
if matched, err := urlsMatchStr(registry, image); err == nil && matched {
matching = append(matching, registry)
} else if err != nil {
log.L.WithError(err).
WithField("registry", registry).
WithField("image", image).
Warn("registry pattern does not match image")
}
}
if len(matching) == 0 {
return nil
}
// Sort in reverse alphabetical order: longer/more specific paths sort first.
// For example, "gcr.io/etcd-development" matches before "gcr.io".
sort.Sort(sort.Reverse(sort.StringSlice(matching)))
return keychains[matching[0]]
}
// computeCacheKey determines the cache key for a plugin response based on the
// cacheKeyType field, following the same logic as the kubelet.
// Inspired from https://github.com/kubernetes/kubernetes/blob/v1.35.0/pkg/credentialprovider/plugin/plugin.go#L545
func computeCacheKey(cacheKeyType credentialproviderv1.PluginCacheKeyType, image string) string {
switch cacheKeyType {
case credentialproviderv1.ImagePluginCacheKeyType:
return image
case credentialproviderv1.RegistryPluginCacheKeyType:
return parseRegistry(image)
case credentialproviderv1.GlobalPluginCacheKeyType:
return globalCacheKey
default:
return ""
}
}
// parseRegistry extracts the registry (host and port) from an image string.
// Ported: https://github.com/kubernetes/kubernetes/blob/v1.35.0/pkg/credentialprovider/plugin/plugin.go#L592
func parseRegistry(image string) string {
imageParts := strings.Split(image, "/")
return imageParts[0]
}
// resolveCacheDuration picks the effective TTL from a plugin response:
// the per-response CacheDuration takes precedence if it is positive,
// otherwise the plugin's DefaultCacheDuration is used.
func resolveCacheDuration(resp *credentialproviderv1.CredentialProviderResponse, plugin *kubeletconfigv1.CredentialProvider) time.Duration {
if resp.CacheDuration != nil && resp.CacheDuration.Duration >= 0 {
return resp.CacheDuration.Duration
}
return plugin.DefaultCacheDuration.Duration
}
// isImageAllowed returns true if the image matches against the list of allowed matches by the plugin.
// Inspired from https://github.com/kubernetes/kubernetes/blob/v1.35.0/pkg/credentialprovider/plugin/plugin.go#L603
func isImageAllowed(plugin *kubeletconfigv1.CredentialProvider, image string) bool {
for _, matchImage := range plugin.MatchImages {
matched, err := urlsMatchStr(matchImage, image)
if err != nil {
log.L.WithError(err).
WithField("plugin", plugin.Name).
WithField("matchImage", matchImage).
Warn("invalid matchImage pattern in credential provider config")
continue
}
if matched {
return true
}
}
return false
}
// execPlugin executes the credential provider plugin binary.
func (p *KubeletProvider) execPlugin(ctx context.Context, plugin *kubeletconfigv1.CredentialProvider, image string) (*credentialproviderv1.CredentialProviderResponse, error) {
request := &credentialproviderv1.CredentialProviderRequest{
Image: image,
}
request.APIVersion = plugin.APIVersion
request.Kind = "CredentialProviderRequest"
// Encode request
requestJSON, err := json.Marshal(request)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal request")
}
// Create command with timeout
ctx, cancel := context.WithTimeout(ctx, pluginExecTimeout)
defer cancel()
// Inherit environment from snapshotter
env := os.Environ()
for _, e := range plugin.Env {
env = append(env, fmt.Sprintf("%s=%s", e.Name, e.Value))
}
cmd := exec.CommandContext(ctx, filepath.Join(p.binDir, plugin.Name), plugin.Args...)
cmd.Env = env
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
stdin := bytes.NewBuffer(requestJSON)
cmd.Stdout, cmd.Stderr, cmd.Stdin = stdout, stderr, stdin
// Execute
err = cmd.Run()
if ctx.Err() != nil {
return nil, errors.Wrap(ctx.Err(), "error execing credential provider plugin")
}
if err != nil {
stderrStr := stderr.String()
return nil, errors.Wrapf(err, "error execing credential provider plugin, stderr: %s", stderrStr)
}
// Decode response
var response credentialproviderv1.CredentialProviderResponse
if err := json.Unmarshal(stdout.Bytes(), &response); err != nil {
return nil, errors.Wrap(err, "failed to decode plugin response")
}
if response.APIVersion != request.APIVersion {
return nil, fmt.Errorf("plugin returned API version %s, expected %s",
response.APIVersion, request.APIVersion)
}
return &response, nil
}
/*
##### PORTED FROM KUBERNETES #####
The following functions are ported from k8s.io/kubernetes/pkg/credentialprovider/keyring.go
Since they are in `k8s.io/kubernetes`, we can't import them directly.
*/
// parseSchemelessURL parses a schemeless url and returns a url.URL
// url.Parse require a scheme, but ours don't have schemes. Adding a
// scheme to make url.Parse happy, then clear out the resulting scheme.
// Ported: https://github.com/kubernetes/kubernetes/blob/v1.35.0/pkg/credentialprovider/keyring.go#L220
func parseSchemelessURL(schemelessURL string) (*url.URL, error) {
parsed, err := url.Parse("https://" + schemelessURL)
if err != nil {
return nil, err
}
// clear out the resulting scheme
parsed.Scheme = ""
return parsed, nil
}
// splitURL splits the host name into parts, as well as the port
// Ported: https://github.com/kubernetes/kubernetes/blob/v1.35.0/pkg/credentialprovider/keyring.go#L231
func splitURL(url *url.URL) (parts []string, port string) {
host, port, err := net.SplitHostPort(url.Host)
if err != nil {
// could not parse port
host, port = url.Host, ""
}
return strings.Split(host, "."), port
}
// urlsMatchStr is wrapper for urlsMatch, operating on strings instead of URLs.
// Ported: https://github.com/kubernetes/kubernetes/blob/v1.35.0/pkg/credentialprovider/keyring.go#L241
func urlsMatchStr(glob string, target string) (bool, error) {
globURL, err := parseSchemelessURL(glob)
if err != nil {
return false, err
}
targetURL, err := parseSchemelessURL(target)
if err != nil {
return false, err
}
return urlsMatch(globURL, targetURL)
}
// urlsMatch checks whether the given target url matches the glob url, which may have
// glob wild cards in the host name.
//
// Examples:
//
// globURL=*.docker.io, targetURL=blah.docker.io => match
// globURL=*.docker.io, targetURL=not.right.io => no match
//
// Note that we don't support wildcards in ports and paths yet.
// Ported: https://github.com/kubernetes/kubernetes/blob/v1.35.0/pkg/credentialprovider/keyring.go#L262
func urlsMatch(globURL *url.URL, targetURL *url.URL) (bool, error) {
globURLParts, globPort := splitURL(globURL)
targetURLParts, targetPort := splitURL(targetURL)
if globPort != targetPort {
// port doesn't match
return false, nil
}
if len(globURLParts) != len(targetURLParts) {
// host name does not have the same number of parts
return false, nil
}
if !strings.HasPrefix(targetURL.Path, globURL.Path) {
// the path of the credential must be a prefix
return false, nil
}
for k, globURLPart := range globURLParts {
targetURLPart := targetURLParts[k]
matched, err := filepath.Match(globURLPart, targetURLPart)
if err != nil {
return false, err
}
if !matched {
// glob mismatch for some part
return false, nil
}
}
// everything matches
return true, nil
}
/*
##### END OF PORTED FROM KUBERNETES #####
*/
================================================
FILE: pkg/auth/kubelet_test.go
================================================
/*
* Copyright (c) 2026. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package auth
import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kubeletconfigv1 "k8s.io/kubelet/config/v1"
credentialproviderv1 "k8s.io/kubelet/pkg/apis/credentialprovider/v1"
"sigs.k8s.io/yaml"
)
const (
testUser = "test-user"
testPass = "test-pass"
)
type credentialMap map[string]struct{ username, password string }
// setupKubeletProvider creates a temporary directory with mock credential provider
// plugins and configuration file for testing.
func setupKubeletProvider(t *testing.T) (configPath, binDir string) {
t.Helper()
// Create temporary directories
tmpDir := t.TempDir()
binDir = filepath.Join(tmpDir, "bin")
err := os.Mkdir(binDir, 0755)
require.NoError(t, err)
configPath = filepath.Join(tmpDir, "credential-config.yaml")
return configPath, binDir
}
// buildAuthSection builds a JSON auth map for a mock plugin response.
func buildAuthSection(registries credentialMap) string {
if len(registries) == 0 {
return "null"
}
auth := "{"
first := true
for registry, creds := range registries {
if !first {
auth += ","
}
auth += fmt.Sprintf(`"%s":{"username":"%s","password":"%s"}`,
registry, creds.username, creds.password)
first = false
}
return auth + "}"
}
// createMockPlugin creates a mock credential provider plugin that returns
// credentials based on the input image. If registries is empty, return null auth.
// Uses a 10m cacheDuration so results are cached by default.
func createMockPlugin(t *testing.T, binDir, pluginName string, registries credentialMap) {
t.Helper()
createMockPluginWithTTL(t, binDir, pluginName, registries, "10m")
}
// createMockPluginWithTTL is like createMockPlugin but lets the caller control
// the cacheDuration field in the response (e.g. "0s" to disable caching).
func createMockPluginWithTTL(t *testing.T, binDir, pluginName string, registries credentialMap, cacheDuration string) {
t.Helper()
createMockPluginFull(t, binDir, pluginName, registries, cacheDuration, "Image")
}
// createMockPluginFull creates a mock plugin with full control over cacheDuration
// and cacheKeyType fields in the response.
func createMockPluginFull(t *testing.T, binDir, pluginName string, registries credentialMap, cacheDuration, cacheKeyType string) {
t.Helper()
script := fmt.Sprintf(`#!/bin/bash
cat > /dev/null
cat <<'EOF'
{"kind":"CredentialProviderResponse","apiVersion":"credentialprovider.kubelet.k8s.io/v1","cacheKeyType":%q,"cacheDuration":%q,"auth":%s}
EOF
`, cacheKeyType, cacheDuration, buildAuthSection(registries))
err := os.WriteFile(filepath.Join(binDir, pluginName), []byte(script), 0755)
require.NoError(t, err)
}
// createMockCredentialProvider creates a CredentialProvider with the given parameters.
// Uses 10m as the default cache duration.
func createMockCredentialProvider(name string, matchImages []string) kubeletconfigv1.CredentialProvider {
return createMockCredentialProviderWithTTL(name, matchImages, 10*time.Minute)
}
// createMockCredentialProviderWithTTL is like createMockCredentialProvider but
// lets the caller control the DefaultCacheDuration.
func createMockCredentialProviderWithTTL(name string, matchImages []string, defaultTTL time.Duration) kubeletconfigv1.CredentialProvider {
return kubeletconfigv1.CredentialProvider{
Name: name,
APIVersion: "credentialprovider.kubelet.k8s.io/v1",
DefaultCacheDuration: &metav1.Duration{Duration: defaultTTL},
MatchImages: matchImages,
}
}
// createMockProviderConfig creates a credential provider configuration file.
func createMockProviderConfig(t *testing.T, configPath string, providers []kubeletconfigv1.CredentialProvider) {
t.Helper()
config := kubeletconfigv1.CredentialProviderConfig{
TypeMeta: metav1.TypeMeta{
APIVersion: "kubelet.config.k8s.io/v1",
Kind: "CredentialProviderConfig",
},
Providers: providers,
}
data, err := yaml.Marshal(config)
require.NoError(t, err)
err = os.WriteFile(configPath, data, 0644)
require.NoError(t, err)
}
// setupMockProvider creates a complete mock plugin + config for testing.
func setupMockProvider(t *testing.T, binDir, pluginName, configPath string, registries credentialMap, matchImages []string) {
t.Helper()
createMockPlugin(t, binDir, pluginName, registries)
createMockProviderConfig(t, configPath, []kubeletconfigv1.CredentialProvider{
createMockCredentialProvider(pluginName, matchImages),
})
}
func TestNewKubeletProvider(t *testing.T) {
configPath, binDir := setupKubeletProvider(t)
tests := []struct {
name string
setup func(t *testing.T) (configPath, binDir string)
wantErr bool
errContains string
validate func(t *testing.T, provider *KubeletProvider)
}{
{
name: "successful initialization",
setup: func(t *testing.T) (string, string) {
setupMockProvider(t, binDir, "test-plugin", configPath,
credentialMap{"registry.example.com": {testUser, testPass}},
[]string{"*.example.com"})
return configPath, binDir
},
validate: func(t *testing.T, provider *KubeletProvider) {
assert.Len(t, provider.plugins, 1)
assert.Equal(t, "test-plugin", provider.plugins[0].Name)
},
},
{
name: "empty config path",
setup: func(t *testing.T) (string, string) {
return "", binDir
},
wantErr: true,
errContains: "config path cannot be empty",
},
{
name: "empty bin directory",
setup: func(t *testing.T) (string, string) {
return configPath, ""
},
wantErr: true,
errContains: "bin directory cannot be empty",
},
{
name: "nonexistent config file",
setup: func(t *testing.T) (string, string) {
return "/nonexistent/config.yaml", binDir
},
wantErr: true,
},
{
name: "invalid config format",
setup: func(t *testing.T) (string, string) {
invalidConfigPath := filepath.Join(binDir, "invalid.yaml")
err := os.WriteFile(invalidConfigPath, []byte("invalid: yaml: content: ["), 0644)
require.NoError(t, err)
return invalidConfigPath, binDir
},
wantErr: true,
},
{
name: "no providers in config",
setup: func(t *testing.T) (string, string) {
emptyConfigPath := filepath.Join(binDir, "empty.yaml")
err := os.WriteFile(emptyConfigPath, []byte(`apiVersion: kubelet.config.k8s.io/v1
kind: CredentialProviderConfig
providers: []
`), 0644)
require.NoError(t, err)
return emptyConfigPath, binDir
},
wantErr: true,
errContains: "at least one provider is required",
},
{
name: "duplicate provider names",
setup: func(t *testing.T) (string, string) {
createMockPlugin(t, binDir, "duplicate-plugin", credentialMap{"registry.example.com": {testUser, testPass}})
dupConfigPath := filepath.Join(binDir, "duplicate.yaml")
createMockProviderConfig(t, dupConfigPath, []kubeletconfigv1.CredentialProvider{
createMockCredentialProvider("duplicate-plugin", []string{"*.example.com"}),
createMockCredentialProvider("duplicate-plugin", []string{"*.docker.io"}),
})
return dupConfigPath, binDir
},
wantErr: true,
errContains: "duplicate provider name",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
configPath, binDir := tt.setup(t)
provider, err := NewKubeletProvider(configPath, binDir)
if tt.wantErr {
require.Error(t, err)
assert.Nil(t, provider)
if tt.errContains != "" {
assert.Contains(t, err.Error(), tt.errContains)
}
return
}
require.NoError(t, err)
require.NotNil(t, provider)
if tt.validate != nil {
tt.validate(t, provider)
}
})
}
}
func TestValidateCredentialProvider(t *testing.T) {
tests := []struct {
name string
setup func() *kubeletconfigv1.CredentialProvider
wantErr bool
errContains string
}{
{
name: "valid provider",
setup: func() *kubeletconfigv1.CredentialProvider {
provider := createMockCredentialProvider("valid-plugin", []string{"*.example.com"})
return &provider
},
wantErr: false,
},
{
name: "empty provider name",
setup: func() *kubeletconfigv1.CredentialProvider {
provider := createMockCredentialProvider("valid-plugin", []string{"*.example.com"})
provider.Name = ""
return &provider
},
wantErr: true,
errContains: "provider name is required",
},
{
name: "invalid provider name with path separator",
setup: func() *kubeletconfigv1.CredentialProvider {
provider := createMockCredentialProvider("valid-plugin", []string{"*.example.com"})
provider.Name = "invalid/name"
return &provider
},
wantErr: true,
errContains: "cannot contain spaces, path separators",
},
{
name: "provider name with space",
setup: func() *kubeletconfigv1.CredentialProvider {
provider := createMockCredentialProvider("valid-plugin", []string{"*.example.com"})
provider.Name = "invalid name"
return &provider
},
wantErr: true,
errContains: "cannot contain spaces, path separators",
},
{
name: "provider name is dot",
setup: func() *kubeletconfigv1.CredentialProvider {
provider := createMockCredentialProvider("valid-plugin", []string{"*.example.com"})
provider.Name = "."
return &provider
},
wantErr: true,
errContains: "cannot contain spaces, path separators",
},
{
name: "empty API version",
setup: func() *kubeletconfigv1.CredentialProvider {
provider := createMockCredentialProvider("valid-plugin", []string{"*.example.com"})
provider.APIVersion = ""
return &provider
},
wantErr: true,
errContains: "apiVersion is required",
},
{
name: "unsupported API version",
setup: func() *kubeletconfigv1.CredentialProvider {
provider := createMockCredentialProvider("valid-plugin", []string{"*.example.com"})
provider.APIVersion = "credentialprovider.kubelet.k8s.io/v2"
return &provider
},
wantErr: true,
errContains: "unsupported apiVersion",
},
{
name: "no match images",
setup: func() *kubeletconfigv1.CredentialProvider {
provider := createMockCredentialProvider("valid-plugin", []string{"*.example.com"})
provider.MatchImages = []string{}
return &provider
},
wantErr: true,
errContains: "at least one matchImage is required",
},
{
name: "empty match image pattern",
setup: func() *kubeletconfigv1.CredentialProvider {
provider := createMockCredentialProvider("valid-plugin", []string{"*.example.com"})
provider.MatchImages = []string{"valid.com", ""}
return &provider
},
wantErr: true,
errContains: "empty matchImage pattern",
},
{
name: "nil cache duration",
setup: func() *kubeletconfigv1.CredentialProvider {
provider := createMockCredentialProvider("valid-plugin", []string{"*.example.com"})
provider.DefaultCacheDuration = nil
return &provider
},
wantErr: true,
errContains: "defaultCacheDuration is required",
},
{
name: "negative cache duration",
setup: func() *kubeletconfigv1.CredentialProvider {
provider := createMockCredentialProvider("valid-plugin", []string{"*.example.com"})
duration := metav1.Duration{Duration: -1 * time.Minute}
provider.DefaultCacheDuration = &duration
return &provider
},
wantErr: true,
errContains: "defaultCacheDuration must be >= 0",
},
{
name: "empty environment variable name",
setup: func() *kubeletconfigv1.CredentialProvider {
provider := createMockCredentialProvider("valid-plugin", []string{"*.example.com"})
provider.Env = []kubeletconfigv1.ExecEnvVar{
{Name: "", Value: "value1"},
}
return &provider
},
wantErr: true,
errContains: "environment variable name cannot be empty",
},
{
name: "duplicate environment variable names",
setup: func() *kubeletconfigv1.CredentialProvider {
provider := createMockCredentialProvider("valid-plugin", []string{"*.example.com"})
provider.Env = []kubeletconfigv1.ExecEnvVar{
{Name: "VAR1", Value: "value1"},
{Name: "VAR1", Value: "value2"},
}
return &provider
},
wantErr: true,
errContains: "duplicate environment variable name",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
provider := tt.setup()
err := validateCredentialProvider(provider)
if tt.wantErr {
require.Error(t, err)
if tt.errContains != "" {
assert.Contains(t, err.Error(), tt.errContains)
}
return
}
assert.NoError(t, err)
})
}
}
func TestKubeletProviderGetCredentials(t *testing.T) {
// Save and restore the global kubeletProvider
oldProvider := kubeletProvider
defer func() { kubeletProvider = oldProvider }()
configPath, binDir := setupKubeletProvider(t)
tests := []struct {
name string
setup func(t *testing.T) *KubeletProvider
request *AuthRequest
wantErr bool
errContains string
wantUsername string
wantPassword string
wantNil bool
}{
{
name: "nil request",
setup: func(t *testing.T) *KubeletProvider {
setupMockProvider(t, binDir, "test-plugin", configPath,
credentialMap{"registry.example.com": {testUser, testPass}},
[]string{"*.example.com"})
provider, err := NewKubeletProvider(configPath, binDir)
require.NoError(t, err)
return provider
},
request: nil,
wantErr: true,
errContains: "ref not found",
},
{
name: "empty ref",
setup: func(t *testing.T) *KubeletProvider {
cfg := filepath.Join(binDir, "config2.yaml")
setupMockProvider(t, binDir, "test-plugin-2", cfg,
credentialMap{"registry.example.com": {testUser, testPass}},
[]string{"*.example.com"})
provider, err := NewKubeletProvider(cfg, binDir)
require.NoError(t, err)
return provider
},
request: &AuthRequest{Ref: ""},
wantErr: true,
errContains: "ref not found",
},
{
name: "successful credential retrieval",
setup: func(t *testing.T) *KubeletProvider {
cfg := filepath.Join(binDir, "success-config.yaml")
setupMockProvider(t, binDir, "success-plugin", cfg,
credentialMap{"registry.example.com": {testUser, testPass}},
[]string{"*.example.com"})
provider, err := NewKubeletProvider(cfg, binDir)
require.NoError(t, err)
return provider
},
request: &AuthRequest{Ref: "registry.example.com/image:tag"},
wantUsername: testUser,
wantPassword: testPass,
},
{
name: "no matching plugin",
setup: func(t *testing.T) *KubeletProvider {
cfg := filepath.Join(binDir, "nomatch-config.yaml")
setupMockProvider(t, binDir, "nomatch-plugin", cfg,
credentialMap{"registry.docker.io": {testUser, testPass}},
[]string{"*.docker.io"})
provider, err := NewKubeletProvider(cfg, binDir)
require.NoError(t, err)
return provider
},
request: &AuthRequest{Ref: "example.com/image:tag"},
wantNil: true,
wantErr: true,
errContains: "no credentials found",
},
{
name: "plugin returns no auth",
setup: func(t *testing.T) *KubeletProvider {
cfg := filepath.Join(binDir, "noauth-config.yaml")
setupMockProvider(t, binDir, "noauth-plugin", cfg, credentialMap{"registry.docker.io": {}}, []string{"*.example.com"})
provider, err := NewKubeletProvider(cfg, binDir)
require.NoError(t, err)
return provider
},
request: &AuthRequest{Ref: "registry.example.com/image:tag"},
wantNil: true,
wantErr: true,
errContains: "no matching registries found",
},
{
// Per the kubelet spec: "If providers return overlapping auth keys,
// the value from the provider earlier in this list is used."
name: "multiple plugins credentials collected - first plugin wins",
setup: func(t *testing.T) *KubeletProvider {
createMockPlugin(t, binDir, "first-plugin", credentialMap{"registry.example.com": {"first-user", "first-pass"}})
createMockPlugin(t, binDir, "second-plugin", credentialMap{"registry.example.com": {"second-user", "second-pass"}})
cfg := filepath.Join(binDir, "multi-config.yaml")
createMockProviderConfig(t, cfg, []kubeletconfigv1.CredentialProvider{
createMockCredentialProvider("first-plugin", []string{"*.example.com"}),
createMockCredentialProvider("second-plugin", []string{"*.example.com"}),
})
provider, err := NewKubeletProvider(cfg, binDir)
require.NoError(t, err)
return provider
},
request: &AuthRequest{Ref: "registry.example.com/image:tag"},
wantUsername: "first-user",
wantPassword: "first-pass",
},
{
name: "most specific registry path wins - specific over general",
setup: func(t *testing.T) *KubeletProvider {
cfg := filepath.Join(binDir, "specificity-config.yaml")
setupMockProvider(t, binDir, "specificity-plugin", cfg,
credentialMap{
"gcr.io": {"general-user", "general-pass"},
"gcr.io/etcd-development": {"specific-user", "specific-pass"},
"gcr.io/kubernetes-release": {"k8s-user", "k8s-pass"},
},
[]string{"gcr.io", "gcr.io/*"})
provider, err := NewKubeletProvider(cfg, binDir)
require.NoError(t, err)
return provider
},
request: &AuthRequest{Ref: "gcr.io/etcd-development/etcd:v3.5.0"},
wantUsername: "specific-user",
wantPassword: "specific-pass",
},
{
name: "most specific registry path wins - deeper path preferred",
setup: func(t *testing.T) *KubeletProvider {
cfg := filepath.Join(binDir, "deep-path-config.yaml")
setupMockProvider(t, binDir, "deep-path-plugin", cfg,
credentialMap{
"registry.example.com": {"level1-user", "level1-pass"},
"registry.example.com/org": {"level2-user", "level2-pass"},
"registry.example.com/org/team": {"level3-user", "level3-pass"},
},
[]string{"registry.example.com"})
provider, err := NewKubeletProvider(cfg, binDir)
require.NoError(t, err)
return provider
},
request: &AuthRequest{Ref: "registry.example.com/org/team/app:latest"},
wantUsername: "level3-user",
wantPassword: "level3-pass",
},
{
name: "alphabetical sorting ensures consistent specificity",
setup: func(t *testing.T) *KubeletProvider {
cfg := filepath.Join(binDir, "alpha-config.yaml")
setupMockProvider(t, binDir, "alpha-plugin", cfg,
credentialMap{
"registry.example.com": {"root-user", "root-pass"},
"registry.example.com/aaa": {"aaa-user", "aaa-pass"},
"registry.example.com/aaa/bbb": {"nested-user", "nested-pass"},
},
[]string{"registry.example.com"})
provider, err := NewKubeletProvider(cfg, binDir)
require.NoError(t, err)
return provider
},
request: &AuthRequest{Ref: "registry.example.com/aaa/bbb/image:tag"},
wantUsername: "nested-user",
wantPassword: "nested-pass",
},
{
name: "only first plugin matches - verifies no loop variable pointer bug",
setup: func(t *testing.T) *KubeletProvider {
// Create two plugins with different matchImages and credentials
createMockPlugin(t, binDir, "first-match-plugin", credentialMap{"registry.first.com": {"first-user", "first-pass"}})
createMockPlugin(t, binDir, "second-no-match-plugin", credentialMap{"registry.second.com": {"second-user", "second-pass"}})
cfg := filepath.Join(binDir, "first-match-config.yaml")
createMockProviderConfig(t, cfg, []kubeletconfigv1.CredentialProvider{
createMockCredentialProvider("first-match-plugin", []string{"*.first.com"}),
createMockCredentialProvider("second-no-match-plugin", []string{"*.second.com"}),
})
provider, err := NewKubeletProvider(cfg, binDir)
require.NoError(t, err)
return provider
},
request: &AuthRequest{Ref: "registry.first.com/image:tag"},
wantUsername: "first-user",
wantPassword: "first-pass",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
provider := tt.setup(t)
kubeletProvider = provider
kc, err := provider.GetCredentials(tt.request)
if tt.wantErr {
require.Error(t, err)
assert.Nil(t, kc)
if tt.errContains != "" {
assert.Contains(t, err.Error(), tt.errContains)
}
return
}
require.NoError(t, err)
if tt.wantNil {
assert.Nil(t, kc)
return
}
require.NotNil(t, kc)
assert.Equal(t, tt.wantUsername, kc.Username)
assert.Equal(t, tt.wantPassword, kc.Password)
})
}
}
func TestInitKubeletProvider(t *testing.T) {
// Save and restore the global kubeletProvider
oldProvider := kubeletProvider
defer func() { kubeletProvider = oldProvider }()
configPath, binDir := setupKubeletProvider(t)
tests := []struct {
name string
setup func(t *testing.T) (configPath, binDir string)
wantErr bool
errContains string
validate func(t *testing.T)
}{
{
name: "successful initialization",
setup: func(t *testing.T) (string, string) {
kubeletProvider = nil
setupMockProvider(t, binDir, "init-plugin", configPath,
credentialMap{"registry.example.com": {testUser, testPass}},
[]string{"*.example.com"})
return configPath, binDir
},
validate: func(t *testing.T) {
assert.NotNil(t, kubeletProvider)
},
},
{
name: "idempotent initialization",
setup: func(t *testing.T) (string, string) {
// Provider already initialized from previous test
return configPath, binDir
},
wantErr: false,
validate: func(t *testing.T) {
assert.NotNil(t, kubeletProvider)
},
},
{
name: "initialization with invalid config",
setup: func(t *testing.T) (string, string) {
kubeletProvider = nil
return "/nonexistent/config.yaml", binDir
},
wantErr: true,
validate: func(t *testing.T) {
assert.Nil(t, kubeletProvider)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
configPath, binDir := tt.setup(t)
err := InitKubeletProvider(configPath, binDir)
if tt.wantErr {
require.Error(t, err)
if tt.errContains != "" {
assert.Contains(t, err.Error(), tt.errContains)
}
} else {
require.NoError(t, err)
}
if tt.validate != nil {
tt.validate(t)
}
})
}
}
func TestURLsMatch(t *testing.T) {
tests := []struct {
name string
globURL string
targetURL string
wantMatch bool
wantError bool
}{
{
name: "exact match",
globURL: "example.com",
targetURL: "example.com",
wantMatch: true,
},
{
name: "wildcard subdomain match",
globURL: "*.docker.io",
targetURL: "registry.docker.io",
wantMatch: true,
},
{
name: "wildcard subdomain no match",
globURL: "*.docker.io",
targetURL: "example.com",
wantMatch: false,
},
{
name: "port mismatch",
globURL: "example.com:443",
targetURL: "example.com:8080",
wantMatch: false,
},
{
name: "port match",
globURL: "example.com:443",
targetURL: "example.com:443",
wantMatch: true,
},
{
name: "path prefix match",
globURL: "example.com/prefix",
targetURL: "example.com/prefix/image",
wantMatch: true,
},
{
name: "path prefix no match",
globURL: "example.com/prefix",
targetURL: "example.com/other",
wantMatch: false,
},
{
name: "different number of parts",
globURL: "*.example.com",
targetURL: "example.com",
wantMatch: false,
},
{
name: "malformed glob URL with invalid escape",
globURL: "example.com%",
targetURL: "example.com",
wantMatch: false,
wantError: true,
},
{
name: "malformed target URL with invalid escape",
globURL: "example.com",
targetURL: "example.com%",
wantMatch: false,
wantError: true,
},
{
name: "invalid glob pattern unclosed bracket",
globURL: "[.example.com",
targetURL: "x.example.com",
wantMatch: false,
wantError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
matched, err := urlsMatchStr(tt.globURL, tt.targetURL)
if tt.wantError {
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.wantMatch, matched)
})
}
}
// --- evictExpired ---
func TestKubeletProviderEvictExpired(t *testing.T) {
now := time.Now()
provider := &KubeletProvider{
cache: map[string]*kubeletCredential{
"valid.com": {keychains: map[string]*PassKeyChain{"valid.com": {}}, expiresAt: now.Add(10 * time.Minute)},
"expired.com": {keychains: map[string]*PassKeyChain{"expired.com": {}}, expiresAt: now.Add(-1 * time.Minute)},
"zero.com": {keychains: map[string]*PassKeyChain{"zero.com": {}}, expiresAt: time.Time{}},
},
}
provider.evictExpired()
assert.Len(t, provider.cache, 1)
assert.Contains(t, provider.cache, "valid.com")
assert.NotContains(t, provider.cache, "expired.com")
assert.NotContains(t, provider.cache, "zero.com")
}
// TestKubeletProviderValidUntil verifies that ValidUntil causes the provider to
// bypass a cached credential that won't survive until the requested time, while
// still serving from cache when the cached credential is valid long enough.
func TestKubeletProviderValidUntil(t *testing.T) {
_, binDir := setupKubeletProvider(t)
pluginName := "valid-until-plugin"
cfgPath := filepath.Join(binDir, "valid-until-config.yaml")
pluginPath := filepath.Join(binDir, pluginName)
// Plugin returns a 10-minute TTL; cached expiresAt ≈ now+10m.
createMockPluginWithTTL(t, binDir, pluginName,
credentialMap{"registry.example.com": {testUser, testPass}}, "10m")
createMockProviderConfig(t, cfgPath, []kubeletconfigv1.CredentialProvider{
createMockCredentialProvider(pluginName, []string{"registry.example.com"}),
})
provider, err := NewKubeletProvider(cfgPath, binDir)
require.NoError(t, err)
ref := "registry.example.com/image:tag"
// First call: executes the plugin and caches the result (expiresAt ≈ now+10m).
kc, err := provider.GetCredentials(&AuthRequest{Ref: ref})
require.NoError(t, err)
require.NotNil(t, kc)
assert.Equal(t, testUser, kc.Username)
// Remove the plugin binary; any re-execution attempt will now fail.
require.NoError(t, os.Remove(pluginPath))
// ValidUntil within the cached TTL (now+5m < expiresAt ≈ now+10m): cache hit.
kc2, err := provider.GetCredentials(&AuthRequest{Ref: ref, ValidUntil: time.Now().Add(5 * time.Minute)})
require.NoError(t, err, "credential is valid long enough; expected cache hit")
require.NotNil(t, kc2)
assert.Equal(t, testUser, kc2.Username)
// ValidUntil beyond the cached TTL (now+20m > expiresAt ≈ now+10m): cache
// bypassed, re-exec attempted → fails because the binary is gone.
_, err = provider.GetCredentials(&AuthRequest{Ref: ref, ValidUntil: time.Now().Add(20 * time.Minute)})
require.Error(t, err, "cached credential won't last long enough; expected plugin re-exec and failure")
// Cache is still intact (failed re-exec didn't evict it): zero ValidUntil succeeds.
kc3, err := provider.GetCredentials(&AuthRequest{Ref: ref})
require.NoError(t, err, "cache should be unaffected by the failed re-exec attempt")
require.NotNil(t, kc3)
assert.Equal(t, testUser, kc3.Username)
}
// TestKubeletProviderRegistryCache verifies caching behaviour by removing the
// plugin binary after the first successful call. The second call either
// succeeds (cache hit) or fails (no cache), proving whether the result was
// stored.
func TestKubeletProviderRegistryCache(t *testing.T) {
tests := []struct {
name string
pluginName string
cfgName string
responseTTL string // cacheDuration field in the plugin response
defaultTTL time.Duration // DefaultCacheDuration in the provider config
wantCachedOnNext bool // whether second call should succeed from cache
}{
{
name: "positive TTL caches result",
pluginName: "cache-hit-plugin",
cfgName: "cache-hit-config.yaml",
responseTTL: "10m",
defaultTTL: 10 * time.Minute,
wantCachedOnNext: true,
},
{
name: "zero TTL does not cache",
pluginName: "zero-ttl-plugin",
cfgName: "zero-ttl-config.yaml",
responseTTL: "0s",
defaultTTL: 0,
wantCachedOnNext: false,
},
{
name: "response zero TTL overrides non-zero default",
pluginName: "override-ttl-plugin",
cfgName: "override-ttl-config.yaml",
responseTTL: "0s",
defaultTTL: 10 * time.Minute,
wantCachedOnNext: false,
},
{
name: "response negative TTL uses default TTL",
pluginName: "negative-ttl-plugin",
cfgName: "negative-ttl-config.yaml",
responseTTL: "-1s",
defaultTTL: 10 * time.Minute,
wantCachedOnNext: true,
},
}
_, binDir := setupKubeletProvider(t)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
pluginPath := filepath.Join(binDir, tt.pluginName)
cfg := filepath.Join(binDir, tt.cfgName)
createMockPluginWithTTL(t, binDir, tt.pluginName,
credentialMap{"registry.example.com": {testUser, testPass}}, tt.responseTTL)
createMockProviderConfig(t, cfg, []kubeletconfigv1.CredentialProvider{
createMockCredentialProviderWithTTL(tt.pluginName, []string{"registry.example.com"}, tt.defaultTTL),
})
provider, err := NewKubeletProvider(cfg, binDir)
require.NoError(t, err)
// First call: executes the plugin.
kc, err := provider.GetCredentials(&AuthRequest{Ref: "registry.example.com/image:tag"})
require.NoError(t, err)
require.NotNil(t, kc)
assert.Equal(t, testUser, kc.Username)
// Remove the plugin so any subsequent exec attempt fails.
require.NoError(t, os.Remove(pluginPath))
// Second call: served from cache (hit) or re-executes the gone plugin (miss).
kc2, err := provider.GetCredentials(&AuthRequest{Ref: "registry.example.com/image:tag"})
if tt.wantCachedOnNext {
require.NoError(t, err, "expected cache hit; plugin binary is gone")
require.NotNil(t, kc2)
assert.Equal(t, testUser, kc2.Username)
} else {
require.Error(t, err, "expected miss: result should not have been cached")
}
})
}
}
// TestKubeletProviderCacheKeyType verifies that the cache key is determined by
// the cacheKeyType field in the plugin response, following the kubelet behavior.
func TestKubeletProviderCacheKeyType(t *testing.T) {
_, binDir := setupKubeletProvider(t)
tests := []struct {
name string
cacheKeyType string
// firstRef is the image used for the first call (populates the cache).
firstRef string
// secondRef is the image used for the second call (after plugin removal).
// Must share the same registry or be anything for Global.
secondRef string
// wantCacheHit indicates whether the second call should succeed from cache.
wantCacheHit bool
}{
{
name: "Image: same image hits cache",
cacheKeyType: "Image",
firstRef: "registry.example.com/image:tag",
secondRef: "registry.example.com/image:tag",
wantCacheHit: true,
},
{
name: "Image: different image misses cache",
cacheKeyType: "Image",
firstRef: "registry.example.com/image-a:tag",
secondRef: "registry.example.com/image-b:tag",
wantCacheHit: false,
},
{
name: "Registry: same registry different image hits cache",
cacheKeyType: "Registry",
firstRef: "registry.example.com/image-a:tag",
secondRef: "registry.example.com/image-b:tag",
wantCacheHit: true,
},
{
name: "Registry: different registry misses cache",
cacheKeyType: "Registry",
firstRef: "registry.example.com/image:tag",
secondRef: "other.example.com/image:tag",
wantCacheHit: false,
},
{
name: "Global: any image hits cache",
cacheKeyType: "Global",
firstRef: "registry.example.com/image:tag",
secondRef: "other.example.com/different:tag",
wantCacheHit: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
pluginName := "ckt-" + strings.ReplaceAll(strings.ToLower(tt.name[:10]), " ", "-")
cfgName := pluginName + "-config.yaml"
createMockPluginFull(t, binDir, pluginName,
credentialMap{
"registry.example.com": {testUser, testPass},
"other.example.com": {testUser, testPass},
}, "10m", tt.cacheKeyType)
cfg := filepath.Join(binDir, cfgName)
createMockProviderConfig(t, cfg, []kubeletconfigv1.CredentialProvider{
createMockCredentialProviderWithTTL(pluginName,
[]string{"*.example.com"}, 10*time.Minute),
})
provider, err := NewKubeletProvider(cfg, binDir)
require.NoError(t, err)
// First call: execute the plugin and populate the cache.
kc, err := provider.GetCredentials(&AuthRequest{Ref: tt.firstRef})
require.NoError(t, err)
require.NotNil(t, kc)
// Remove the plugin binary so any re-exec attempt fails.
require.NoError(t, os.Remove(filepath.Join(binDir, pluginName)))
// Second call: should hit or miss the cache depending on cacheKeyType.
kc2, err := provider.GetCredentials(&AuthRequest{Ref: tt.secondRef})
if tt.wantCacheHit {
require.NoError(t, err, "expected cache hit")
require.NotNil(t, kc2)
assert.Equal(t, testUser, kc2.Username)
} else {
require.Error(t, err, "expected cache miss")
}
})
}
}
// --- computeCacheKey ---
func TestComputeCacheKey(t *testing.T) {
tests := []struct {
name string
cacheKeyType credentialproviderv1.PluginCacheKeyType
image string
want string
}{
{
name: "Image returns full image",
cacheKeyType: credentialproviderv1.ImagePluginCacheKeyType,
image: "registry.example.com/org/app:latest",
want: "registry.example.com/org/app:latest",
},
{
name: "Registry returns host only",
cacheKeyType: credentialproviderv1.RegistryPluginCacheKeyType,
image: "registry.example.com/org/app:latest",
want: "registry.example.com",
},
{
name: "Global returns global key",
cacheKeyType: credentialproviderv1.GlobalPluginCacheKeyType,
image: "registry.example.com/org/app:latest",
want: "",
},
{
name: "unknown returns empty",
cacheKeyType: "Unknown",
image: "registry.example.com/org/app:latest",
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := computeCacheKey(tt.cacheKeyType, tt.image)
assert.Equal(t, tt.want, got)
})
}
}
// --- parseRegistry ---
func TestParseRegistry(t *testing.T) {
tests := []struct {
image string
want string
}{
{"registry.example.com/org/app:latest", "registry.example.com"},
{"localhost:5000/myimage", "localhost:5000"},
{"docker.io/library/nginx:latest", "docker.io"},
{"singlepart", "singlepart"},
}
for _, tt := range tests {
t.Run(tt.image, func(t *testing.T) {
assert.Equal(t, tt.want, parseRegistry(tt.image))
})
}
}
================================================
FILE: pkg/auth/kubesecret.go
================================================
package auth
import (
"bytes"
"context"
"fmt"
"os"
"sync"
"github.com/docker/cli/cli/config/configfile"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/clientcmd"
)
var (
// TODO: embed in KubeSecretProvider
kubeSecretListener *KubeSecretListener
configMu sync.Mutex
)
// KubeSecretProvider implements AuthProvider for Kubernetes secrets.
type KubeSecretProvider struct{}
// NewKubeSecretProvider creates a new Kubernetes secret-based auth provider.
func NewKubeSecretProvider() *KubeSecretProvider {
return &KubeSecretProvider{}
}
// CanRenew implements RenewableProvider. KubeSecret credentials can be
// refreshed because the underlying informer watches for secret changes.
func (p *KubeSecretProvider) CanRenew() bool { return true }
func (p *KubeSecretProvider) String() string {
return "kubesecret"
}
// GetCredentials retrieves credentials from Kubernetes secrets.
// Returns nil if no credentials are found or the listener is not initialized.
func (p *KubeSecretProvider) GetCredentials(req *AuthRequest) (*PassKeyChain, error) {
if kubeSecretListener == nil {
return nil, errors.New("no secret listener initialized")
}
if req == nil || req.Ref == "" {
return nil, errors.New("ref not found in request")
}
_, host, err := parseReference(req.Ref)
if err != nil {
return nil, errors.Wrapf(err, "parse reference %s", req.Ref)
}
passKey := kubeSecretListener.GetCredentialsStore(host)
if passKey == nil {
return nil, fmt.Errorf("no kube secret credentials found for host: %s", host)
}
return passKey, nil
}
type KubeSecretListener struct {
dockerConfigs map[string]*configfile.ConfigFile
informer cache.SharedIndexInformer
}
func InitKubeSecretListener(ctx context.Context, kubeconfigPath string) error {
configMu.Lock()
defer configMu.Unlock()
if kubeSecretListener != nil {
return nil
}
kubeSecretListener = &KubeSecretListener{
dockerConfigs: make(map[string]*configfile.ConfigFile),
}
if kubeconfigPath != "" {
_, err := os.Stat(kubeconfigPath)
if err != nil && !os.IsNotExist(err) {
logrus.WithError(err).Warningf("kubeconfig does not exist, kubeconfigPath %s", kubeconfigPath)
return err
} else if err != nil {
logrus.WithError(err).Warningf("failed to detect kubeconfig existence, kubeconfigPath %s", kubeconfigPath)
return err
}
}
loadingRule := clientcmd.NewDefaultClientConfigLoadingRules()
loadingRule.ExplicitPath = kubeconfigPath
clientConfig, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
loadingRule,
&clientcmd.ConfigOverrides{},
).ClientConfig()
if err != nil {
logrus.WithError(err).Warningf("failed to load kubeconfig")
return err
}
clientset, err := kubernetes.NewForConfig(clientConfig)
if err != nil {
logrus.WithError(err).Warningf("failed to create kubernetes client")
return err
}
if err := kubeSecretListener.SyncKubeSecrets(ctx, clientset); err != nil {
logrus.WithError(err).Warningf("failed to sync secrets")
return err
}
return nil
}
func (kubelistener *KubeSecretListener) addDockerConfig(key string, obj interface{}) error {
data, ok := obj.(*corev1.Secret).Data[corev1.DockerConfigJsonKey]
if !ok {
return fmt.Errorf("failed to get data from new object")
}
dockerConfig := configfile.ConfigFile{}
if err := dockerConfig.LoadFromReader(bytes.NewReader(data)); err != nil {
return errors.Wrap(err, "failed to load docker config json from secret")
}
configMu.Lock()
kubelistener.dockerConfigs[key] = &dockerConfig
configMu.Unlock()
return nil
}
func (kubelistener *KubeSecretListener) deleteDockerConfig(key string) {
configMu.Lock()
delete(kubelistener.dockerConfigs, key)
configMu.Unlock()
}
func (kubelistener *KubeSecretListener) SyncKubeSecrets(ctx context.Context, clientset *kubernetes.Clientset) error {
if kubelistener.informer != nil {
return nil
}
informer := cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
options.FieldSelector = "type=" + string(corev1.SecretTypeDockerConfigJson)
return clientset.CoreV1().Secrets(metav1.NamespaceAll).List(context.Background(), options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
options.FieldSelector = "type=" + string(corev1.SecretTypeDockerConfigJson)
return clientset.CoreV1().Secrets(metav1.NamespaceAll).Watch(context.Background(), options)
}},
&corev1.Secret{},
0,
cache.Indexers{},
)
kubelistener.informer = informer
_, err := kubelistener.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
key, err := cache.MetaNamespaceKeyFunc(obj)
if err != nil {
logrus.WithError(err).Errorf("failed to get key for secret from cache")
return
}
if err := kubelistener.addDockerConfig(key, obj); err != nil {
logrus.WithError(err).Errorf("failed to add a new dockerconfigjson")
return
}
},
UpdateFunc: func(_, newObj interface{}) {
key, err := cache.MetaNamespaceKeyFunc(newObj)
if err != nil {
logrus.WithError(err).Errorf("failed to get key for secret from cache")
return
}
if err := kubelistener.addDockerConfig(key, newObj); err != nil {
logrus.WithError(err).Errorf("failed to add a new dockerconfigjson")
return
}
},
DeleteFunc: func(obj interface{}) {
key, err := cache.MetaNamespaceKeyFunc(obj)
if err != nil {
logrus.WithError(err).Errorf("failed to get key for secret from cache")
}
kubelistener.deleteDockerConfig(key)
}},
)
if err != nil {
return errors.Wrap(err, "add event handler to informer")
}
go kubelistener.informer.Run(ctx.Done())
if !cache.WaitForCacheSync(ctx.Done(), informer.HasSynced) {
return fmt.Errorf("timed out for syncing cache")
}
return nil
}
func (kubelistener *KubeSecretListener) GetCredentialsStore(host string) *PassKeyChain {
configMu.Lock()
defer configMu.Unlock()
for _, dockerConfig := range kubelistener.dockerConfigs {
// Find the auth for the host.
authConfig, err := dockerConfig.GetAuthConfig(host)
if err != nil {
continue
}
if len(authConfig.Username) != 0 && len(authConfig.Password) != 0 {
return &PassKeyChain{
Username: authConfig.Username,
Password: authConfig.Password,
}
}
}
return nil
}
================================================
FILE: pkg/auth/kubesecret_test.go
================================================
package auth
import (
"context"
"encoding/base64"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
)
const (
testDockerConfigJSONFmt = `
{
"auths": {
"%s": {
"username": "%s",
"password": "%s",
"email": "%s",
"auth": "%s"
}
}
}
`
dockerConfigKey = "testKey"
registryUser = "dockeruserfoobar"
registryPass = "dockerpassfoobar"
registryEmail = "test@alibaba.com"
)
func TestGetCredentialsStore(t *testing.T) {
assert := assert.New(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Host may not has kubeconfig, so ignore the error and continue the test
_ = InitKubeSecretListener(ctx, "")
assert.NotNil(kubeSecretListener)
var obj interface{} = &corev1.Secret{
Data: map[string][]byte{
corev1.DockerConfigJsonKey: []byte(fmt.Sprintf(testDockerConfigJSONFmt, extraHost, registryUser,
registryPass, registryEmail, base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", registryUser, registryPass))))),
},
}
err := kubeSecretListener.addDockerConfig(dockerConfigKey, obj)
assert.Nil(err)
auth, err := NewKubeSecretProvider().GetCredentials(&AuthRequest{Ref: extraHost + "/foo:bar"})
assert.NoError(err)
assert.Equal(auth.Username, registryUser)
assert.Equal(auth.Password, registryPass)
auth = kubeSecretListener.GetCredentialsStore(extraHost)
assert.Equal(auth.Username, registryUser)
assert.Equal(auth.Password, registryPass)
_, ok := kubeSecretListener.dockerConfigs[dockerConfigKey]
assert.Equal(ok, true)
kubeSecretListener.deleteDockerConfig(dockerConfigKey)
_, ok = kubeSecretListener.dockerConfigs[dockerConfigKey]
assert.Equal(ok, false)
auth = kubeSecretListener.GetCredentialsStore(extraHost)
assert.Nil(auth)
}
================================================
FILE: pkg/auth/labels.go
================================================
/*
* Copyright (c) 2026. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package auth
import (
"github.com/containerd/nydus-snapshotter/pkg/label"
"github.com/pkg/errors"
)
// LabelsProvider retrieves credentials from snapshot labels.
type LabelsProvider struct{}
// NewLabelsProvider creates a new labels-based auth provider.
func NewLabelsProvider() *LabelsProvider {
return &LabelsProvider{}
}
func (p *LabelsProvider) String() string {
return "labels"
}
// GetCredentials retrieves credentials from snapshot labels.
// Returns nil if labels don't contain valid credentials.
func (p *LabelsProvider) GetCredentials(req *AuthRequest) (*PassKeyChain, error) {
if req.Labels == nil {
return nil, errors.New("labels not found in request")
}
u, found := req.Labels[label.NydusImagePullUsername]
if !found || u == "" {
return nil, errors.New("username label not found")
}
pass, found := req.Labels[label.NydusImagePullSecret]
if !found || pass == "" {
return nil, errors.New("password label not found")
}
return &PassKeyChain{
Username: u,
Password: pass,
}, nil
}
================================================
FILE: pkg/auth/labels_test.go
================================================
/*
* Copyright (c) 2020. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package auth
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/containerd/nydus-snapshotter/pkg/label"
)
func TestFromLabels(t *testing.T) {
labels := map[string]string{
label.NydusImagePullUsername: "mock",
label.NydusImagePullSecret: "mock",
}
kc, err := NewLabelsProvider().GetCredentials(&AuthRequest{Labels: labels})
assert.NoError(t, err)
assert.Equal(t, kc.Username, "mock")
assert.Equal(t, kc.Password, "mock")
assert.Equal(t, "bW9jazptb2Nr", kc.ToBase64())
kc1, err := FromBase64("bW9jazptb2Nr")
assert.Nil(t, err)
assert.Equal(t, kc1.Username, "mock")
assert.Equal(t, kc1.Password, "mock")
labels = map[string]string{}
kc, err = NewLabelsProvider().GetCredentials(&AuthRequest{Labels: labels})
assert.Nil(t, kc)
assert.Error(t, err)
labels = map[string]string{
label.NydusImagePullSecret: "mock",
}
kc, err = NewLabelsProvider().GetCredentials(&AuthRequest{Labels: labels})
assert.Nil(t, kc)
assert.Error(t, err)
}
================================================
FILE: pkg/auth/provider.go
================================================
/*
* Copyright (c) 2026. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package auth
import (
"time"
"github.com/containerd/containerd/v2/pkg/reference"
distribution "github.com/distribution/reference"
"github.com/pkg/errors"
)
// AuthRequest contains parameters for retrieving registry credentials.
type AuthRequest struct {
// Ref is the full image reference (e.g., "docker.io/library/nginx:latest")
Ref string
// Labels are snapshot labels that may contain credentials
Labels map[string]string
// ValidUntil, when non-zero, instructs providers to return a credential
// that remains valid at least until this time. Providers that do not
// have a notion of expiration will ignore this..
ValidUntil time.Time
}
// AuthProvider manage how credentials are retrieved for different sources
type AuthProvider interface {
// GetCredentials retrieves credentials for the given request.
// Returns nil if no credentials are available.
GetCredentials(req *AuthRequest) (*PassKeyChain, error)
String() string
}
// RenewableProvider extends AuthProvider with credential renewal capability.
// Providers that can refresh credentials implement this interface.
type RenewableProvider interface {
AuthProvider
// CanRenew reports whether this provider can renew credentials.
CanRenew() bool
}
// parseReference returns the reference.Spec and host for the given reference
func parseReference(ref string) (refSpec reference.Spec, host string, err error) {
namedRef, err := distribution.ParseDockerRef(ref)
if err != nil {
return reference.Spec{}, "", errors.Wrap(err, "parse docker reference")
}
refSpec, err = reference.Parse(namedRef.String())
if err != nil {
return reference.Spec{}, "", errors.Wrap(err, "parse image reference")
}
host = distribution.Domain(namedRef)
if host == "" {
return reference.Spec{}, "", errors.New("host not found in ref")
}
return refSpec, host, nil
}
================================================
FILE: pkg/auth/renewal.go
================================================
/*
* Copyright (c) 2026. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package auth
import (
"sync"
"time"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/metrics/data"
)
// renewalStore is the global credential store. It is nil when credential
// renewal is disabled (the default).
var renewalStore *credentialStore
// InitCredentialStore creates the global credential store without starting
// any background goroutine. The caller is responsible for driving renewal
// (e.g., from snapshot/renewal.go).
func InitCredentialStore(interval time.Duration) {
renewalStore = newCredentialStore(interval)
}
// GetStoredCredential returns the cached keychain for ref from the global
// store, or nil if not present or the store is not initialized.
func GetStoredCredential(ref string) *PassKeyChain {
if renewalStore == nil {
return nil
}
return renewalStore.Get(ref)
}
// RenewCredential fetches fresh credentials for ref from the renewable
// provider list and caches them in the global store. Returns the keychain
// on success or nil on failure. Emits renewal metrics.
func RenewCredential(ref string) *PassKeyChain {
kc := fetchFromProviders(
&AuthRequest{Ref: ref, ValidUntil: time.Now().Add(renewalStore.renewInterval)},
renewableProviders(),
)
if kc != nil {
data.CredentialRenewals.WithLabelValues(ref, "success").Inc()
} else {
log.L.WithField("ref", ref).Warn("credential renewal returned no credentials from any provider")
data.CredentialRenewals.WithLabelValues(ref, "failure").Inc()
}
return kc
}
// EvictStaleCredentials removes store entries whose ref is not present in
// liveRefs. Entries added recently (within interval/2) are kept to avoid
// racing with a concurrent image pull: GetRegistryKeyChain adds the ref to
// the store on the first layer fetch, but the RAFS entry is only created
// later when the mount completes. Evicting here would cause redundant
// provider lookups for every remaining layer fetch in the pull.
func EvictStaleCredentials(liveRefs map[string]struct{}) {
if renewalStore == nil {
return
}
grace := renewalStore.renewInterval / 2
for _, entry := range renewalStore.Entries() {
if _, live := liveRefs[entry.ref]; !live && time.Since(entry.renewedAt) >= grace {
renewalStore.Remove(entry.ref)
}
}
}
// --- credentialEntry ---
// credentialEntry holds a cached credential and the time it was last
// successfully renewed.
type credentialEntry struct {
ref string
keychain *PassKeyChain
renewedAt time.Time
}
// --- credentialStore ---
// credentialStore is a concurrency-safe in-memory store for renewable
// credentials keyed by image ref.
type credentialStore struct {
mu sync.RWMutex
entries map[string]*credentialEntry
renewInterval time.Duration
}
func newCredentialStore(renewInterval time.Duration) *credentialStore {
return &credentialStore{
entries: make(map[string]*credentialEntry),
renewInterval: renewInterval,
}
}
// Add inserts or updates a credential entry for the given ref.
func (s *credentialStore) Add(ref string, kc *PassKeyChain) {
s.mu.Lock()
defer s.mu.Unlock()
log.L.WithField("ref", ref).Debug("adding credential entry to store")
s.entries[ref] = &credentialEntry{
ref: ref,
keychain: kc,
renewedAt: time.Now(),
}
data.CredentialStoreEntries.WithLabelValues(ref).Set(1)
}
// Get returns the cached keychain for ref, or nil if not present.
func (s *credentialStore) Get(ref string) *PassKeyChain {
s.mu.RLock()
defer s.mu.RUnlock()
entry, ok := s.entries[ref]
if !ok {
return nil
}
return entry.keychain
}
// Remove deletes the entry for the given ref.
func (s *credentialStore) Remove(ref string) {
s.mu.Lock()
defer s.mu.Unlock()
log.L.WithField("ref", ref).Debug("removing credential entry from store")
delete(s.entries, ref)
data.CredentialStoreEntries.DeleteLabelValues(ref)
data.CredentialRenewals.DeleteLabelValues(ref, "success")
data.CredentialRenewals.DeleteLabelValues(ref, "failure")
}
// Entries returns a snapshot of all current entries.
func (s *credentialStore) Entries() []credentialEntry {
s.mu.RLock()
defer s.mu.RUnlock()
result := make([]credentialEntry, 0, len(s.entries))
for _, e := range s.entries {
result = append(result, *e)
}
return result
}
================================================
FILE: pkg/auth/renewal_test.go
================================================
/*
* Copyright (c) 2026. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package auth
import (
"fmt"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// --- test helpers ---
// mockProvider implements RenewableProvider.
type mockProvider struct {
creds *PassKeyChain
err error
}
func (m *mockProvider) GetCredentials(_ *AuthRequest) (*PassKeyChain, error) {
return m.creds, m.err
}
func (m *mockProvider) CanRenew() bool { return true }
func (m *mockProvider) String() string { return "mockProvider" }
// mockNonRenewableProvider implements AuthProvider but NOT RenewableProvider.
type mockNonRenewableProvider struct {
creds *PassKeyChain
err error
}
func (m *mockNonRenewableProvider) GetCredentials(_ *AuthRequest) (*PassKeyChain, error) {
return m.creds, m.err
}
func (m *mockNonRenewableProvider) String() string { return "mockNonRenewableProvider" }
// trackingProvider counts calls and returns evolving credentials.
type trackingProvider struct {
calls atomic.Int32
failNext atomic.Bool
nilNext atomic.Bool
}
func (p *trackingProvider) GetCredentials(_ *AuthRequest) (*PassKeyChain, error) {
n := p.calls.Add(1)
if p.failNext.Load() {
return nil, fmt.Errorf("simulated failure")
}
if p.nilNext.Load() {
return nil, fmt.Errorf("no credentials available")
}
return &PassKeyChain{
Username: fmt.Sprintf("user-v%d", n),
Password: fmt.Sprintf("pass-v%d", n),
}, nil
}
func (p *trackingProvider) CanRenew() bool { return true }
func (p *trackingProvider) String() string { return "trackingProvider" }
// --- RenewableProvider type assertion ---
func TestRenewableProviderTypeAssertion(t *testing.T) {
tests := []struct {
name string
provider AuthProvider
renewable bool
}{
{"LabelsProvider is not renewable", NewLabelsProvider(), false},
{"CRIProvider is not renewable", NewCRIProvider(), false},
{"DockerProvider is renewable", NewDockerProvider(), true},
{"KubeSecretProvider is renewable", NewKubeSecretProvider(), true},
{"KubeletProvider is renewable", &KubeletProvider{}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rp, ok := tt.provider.(RenewableProvider)
if tt.renewable {
assert.True(t, ok, "expected provider to implement RenewableProvider")
assert.True(t, rp.CanRenew(), "expected CanRenew() to return true")
} else {
assert.False(t, ok, "expected provider to NOT implement RenewableProvider")
}
})
}
}
// --- credentialStore ---
func TestCredentialStoreGet(t *testing.T) {
tests := []struct {
name string
ref string
wantNil bool
}{
{
name: "returns nil for missing ref",
ref: "nonexistent",
wantNil: true,
},
{
name: "returns keychain for present ref",
ref: "ref",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
store := newCredentialStore(5 * time.Minute)
store.Add("ref", &PassKeyChain{Username: "user", Password: "pass"})
got := store.Get(tt.ref)
if tt.wantNil {
assert.Nil(t, got)
} else {
require.NotNil(t, got)
assert.Equal(t, "user", got.Username)
}
})
}
}
func TestCredentialStoreRemove(t *testing.T) {
tests := []struct {
name string
setup func(s *credentialStore)
ref string
}{
{
name: "removes existing entry",
setup: func(s *credentialStore) {
s.Add("ref", &PassKeyChain{Username: "user", Password: "pass"})
},
ref: "ref",
},
{
name: "no-op for nonexistent ref",
setup: func(_ *credentialStore) {},
ref: "nonexistent",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
store := newCredentialStore(5 * time.Minute)
tt.setup(store)
store.Remove(tt.ref)
assert.Nil(t, store.Get(tt.ref))
})
}
}
func TestCredentialStoreAdd_Upsert(t *testing.T) {
store := newCredentialStore(5 * time.Minute)
store.Add("ref", &PassKeyChain{Username: "old", Password: "old"})
got := store.Get("ref")
require.NotNil(t, got)
assert.Equal(t, "old", got.Username)
store.Add("ref", &PassKeyChain{Username: "new", Password: "new"})
got = store.Get("ref")
require.NotNil(t, got)
assert.Equal(t, "new", got.Username)
}
func TestCredentialStoreEntries(t *testing.T) {
store := newCredentialStore(5 * time.Minute)
store.Add("ref1", &PassKeyChain{Username: "u1", Password: "p1"})
store.Add("ref2", &PassKeyChain{Username: "u2", Password: "p2"})
assert.Len(t, store.Entries(), 2)
}
func TestCredentialStoreConcurrency(t *testing.T) {
store := newCredentialStore(5 * time.Minute)
var wg sync.WaitGroup
for i := range 100 {
wg.Add(1)
go func(i int) {
defer wg.Done()
store.Add("ref", &PassKeyChain{Username: "user", Password: "pass"})
store.Get("ref")
store.Entries()
if i%2 == 0 {
store.Remove("ref")
}
}(i)
}
wg.Wait()
}
// --- RenewCredential ---
func TestRenewCredential(t *testing.T) {
const ref = "docker.io/library/nginx:latest"
tests := []struct {
name string
provider *trackingProvider
wantUser string
wantCall int32
wantNil bool
}{
{
name: "updates store on success",
provider: &trackingProvider{},
wantUser: "user-v1",
wantCall: 1,
},
{
name: "returns nil on failure",
provider: func() *trackingProvider {
p := &trackingProvider{}
p.failNext.Store(true)
return p
}(),
wantCall: 1,
wantNil: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
oldRenewable := renewableProviders
oldStore := renewalStore
defer func() {
renewableProviders = oldRenewable
renewalStore = oldStore
}()
renewableProviders = func() []AuthProvider { return []AuthProvider{tt.provider} }
renewalStore = newCredentialStore(5 * time.Minute)
got := RenewCredential(ref)
assert.Equal(t, tt.wantCall, tt.provider.calls.Load())
if tt.wantNil {
assert.Nil(t, got)
} else {
require.NotNil(t, got)
assert.Equal(t, tt.wantUser, got.Username)
}
})
}
}
// --- EvictStaleCredentials ---
func TestEvictStaleCredentials(t *testing.T) {
tests := []struct {
name string
stored []string
live map[string]struct{}
wantRefs []string
}{
{
name: "evicts refs not in live set",
stored: []string{"ref-a", "ref-b", "ref-c"},
live: map[string]struct{}{"ref-a": {}, "ref-c": {}},
wantRefs: []string{"ref-a", "ref-c"},
},
{
name: "evicts all when live set is empty",
stored: []string{"ref-a", "ref-b"},
live: map[string]struct{}{},
wantRefs: nil,
},
{
name: "keeps all when all are live",
stored: []string{"ref-a", "ref-b"},
live: map[string]struct{}{"ref-a": {}, "ref-b": {}},
wantRefs: []string{"ref-a", "ref-b"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
oldStore := renewalStore
defer func() { renewalStore = oldStore }()
// Use a tiny interval so grace period (interval/2) is effectively zero.
renewalStore = newCredentialStore(time.Millisecond)
for _, ref := range tt.stored {
renewalStore.Add(ref, &PassKeyChain{Username: "u", Password: "p"})
}
time.Sleep(time.Millisecond)
EvictStaleCredentials(tt.live)
entries := renewalStore.Entries()
gotRefs := make([]string, 0, len(entries))
for _, e := range entries {
gotRefs = append(gotRefs, e.ref)
}
assert.ElementsMatch(t, tt.wantRefs, gotRefs)
})
}
}
func TestEvictStaleCredentials_GracePeriod(t *testing.T) {
oldStore := renewalStore
defer func() { renewalStore = oldStore }()
// Grace period is interval/2 = 2.5 minutes. A freshly added entry
// should survive eviction even when not in the live set.
renewalStore = newCredentialStore(5 * time.Minute)
renewalStore.Add("recent-ref", &PassKeyChain{Username: "u", Password: "p"})
EvictStaleCredentials(map[string]struct{}{})
assert.NotNil(t, renewalStore.Get("recent-ref"), "entry within grace period should not be evicted")
}
func TestGetStoredCredential(t *testing.T) {
tests := []struct {
name string
initStore bool
addEntry bool
wantNil bool
}{
{
name: "returns keychain when present",
initStore: true,
addEntry: true,
},
{
name: "returns nil when not present",
initStore: true,
wantNil: true,
},
{
name: "returns nil when store is nil",
wantNil: true,
},
}
const ref = "docker.io/library/nginx:latest"
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
oldStore := renewalStore
defer func() { renewalStore = oldStore }()
if tt.initStore {
renewalStore = newCredentialStore(5 * time.Minute)
if tt.addEntry {
renewalStore.Add(ref, &PassKeyChain{Username: "user", Password: "pass"})
}
} else {
renewalStore = nil
}
got := GetStoredCredential(ref)
if tt.wantNil {
assert.Nil(t, got)
} else {
require.NotNil(t, got)
assert.Equal(t, "user", got.Username)
}
})
}
}
func TestEvictStaleCredentials_NilStore(t *testing.T) {
oldStore := renewalStore
defer func() { renewalStore = oldStore }()
renewalStore = nil
// Should not panic.
EvictStaleCredentials(map[string]struct{}{"ref": {}})
}
// --- getRegistryKeyChainFromProviders ---
func TestGetRegistryKeyChainFromProviders(t *testing.T) {
tests := []struct {
name string
storeEnabled bool
cached *PassKeyChain
providers []AuthProvider
wantUser string
wantStored bool
}{
{
name: "serves from store when cached",
storeEnabled: true,
cached: &PassKeyChain{Username: "cached", Password: "cached-pass"},
providers: []AuthProvider{&mockNonRenewableProvider{creds: &PassKeyChain{Username: "fresh", Password: "fresh"}}},
wantUser: "cached",
wantStored: true,
},
{
name: "falls through on store miss",
storeEnabled: true,
providers: []AuthProvider{&mockProvider{creds: &PassKeyChain{Username: "fresh", Password: "fresh"}}},
wantUser: "fresh",
wantStored: true,
},
{
name: "stores renewable provider creds",
storeEnabled: true,
providers: []AuthProvider{&mockProvider{creds: &PassKeyChain{Username: "user", Password: "pass"}}},
wantUser: "user",
wantStored: true,
},
{
name: "does not store non-renewable provider creds",
storeEnabled: true,
providers: []AuthProvider{&mockNonRenewableProvider{creds: &PassKeyChain{Username: "user", Password: "pass"}}},
wantUser: "user",
wantStored: false,
},
{
name: "works when store is nil",
providers: []AuthProvider{&mockProvider{creds: &PassKeyChain{Username: "user", Password: "pass"}}},
wantUser: "user",
},
}
const ref = "docker.io/library/nginx:latest"
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
oldStore := renewalStore
defer func() { renewalStore = oldStore }()
if tt.storeEnabled {
renewalStore = newCredentialStore(5 * time.Minute)
if tt.cached != nil {
renewalStore.Add(ref, tt.cached)
}
} else {
renewalStore = nil
}
got := getRegistryKeyChainFromProviders(ref, nil, tt.providers)
require.NotNil(t, got)
assert.Equal(t, tt.wantUser, got.Username)
if tt.storeEnabled {
stored := renewalStore.Get(ref)
if tt.wantStored {
assert.NotNil(t, stored)
} else {
assert.Nil(t, stored)
}
}
})
}
}
================================================
FILE: pkg/backend/backend.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package backend
import (
"context"
"fmt"
"github.com/containerd/containerd/v2/core/content"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
const (
BackendTypeOSS = "oss"
BackendTypeS3 = "s3"
BackendTypeLocalFS = "localfs"
)
var (
// We always use multipart upload for backend, and limit the
// multipart chunk size to 500MB by default.
MultipartChunkSize int64 = 500 * 1024 * 1024
)
// Backend uploads blobs generated by nydus-image builder to a backend storage.
type Backend interface {
// Push pushes specified blob file to remote storage backend.
Push(ctx context.Context, cs content.Store, desc ocispec.Descriptor) error
// Check checks whether a blob exists in remote storage backend,
// blob exists -> return (blobPath, nil)
// blob not exists -> return ("", err)
Check(blobDigest digest.Digest) (string, error)
// Type returns backend type name.
Type() string
}
// Nydus driver majorly works for registry backend, which means blob is stored in
// registry as per OCI distribution specification. But nydus can also make OSS or
// other storage services as backend storage. Pass config as byte slice here because
// we haven't find a way to represent all backend config at the same time.
func NewBackend(_type string, config []byte, forcePush bool) (Backend, error) {
switch _type {
case BackendTypeOSS:
return newOSSBackend(config, forcePush)
case BackendTypeS3:
return newS3Backend(config, forcePush)
case BackendTypeLocalFS:
return newLocalFSBackend(config, forcePush)
default:
return nil, fmt.Errorf("unsupported backend type %s", _type)
}
}
================================================
FILE: pkg/backend/localfs.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package backend
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"path"
"github.com/containerd/containerd/v2/core/content"
"github.com/containerd/errdefs"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
type LocalFSBackend struct {
dir string
forcePush bool
}
func newLocalFSBackend(rawConfig []byte, forcePush bool) (*LocalFSBackend, error) {
var configMap map[string]string
if err := json.Unmarshal(rawConfig, &configMap); err != nil {
return nil, errors.Wrap(err, "parse LocalFS storage backend configuration")
}
dir, ok := configMap["dir"]
if !ok {
return nil, fmt.Errorf("no `dir` option is specified")
}
return &LocalFSBackend{
dir: dir,
forcePush: forcePush,
}, nil
}
func (b *LocalFSBackend) dstPath(blobID string) string {
return path.Join(b.dir, blobID)
}
func (b *LocalFSBackend) Push(ctx context.Context, cs content.Store, desc ocispec.Descriptor) error {
if _, err := b.Check(desc.Digest); err == nil && !b.forcePush {
return nil
}
if err := os.MkdirAll(b.dir, 0755); err != nil {
return errors.Wrap(err, "create directory in localfs backend")
}
ra, err := cs.ReaderAt(ctx, desc)
if err != nil {
return errors.Wrap(err, "get reader from content store")
}
defer ra.Close()
blobID := desc.Digest.Hex()
dstPath := b.dstPath(blobID)
dstFile, err := os.Create(dstPath)
if err != nil {
return errors.Wrapf(err, "create destination file: %s", dstPath)
}
defer dstFile.Close()
sr := io.NewSectionReader(ra, 0, ra.Size())
if _, err := io.Copy(dstFile, sr); err != nil {
return errors.Wrapf(err, "copy blob to %s", dstPath)
}
return nil
}
func (b *LocalFSBackend) Check(blobDigest digest.Digest) (string, error) {
dstPath := b.dstPath(blobDigest.Hex())
info, err := os.Stat(dstPath)
if err != nil {
if os.IsNotExist(err) {
return "", errdefs.ErrNotFound
}
return "", err
}
if !info.IsDir() {
return dstPath, nil
}
return "", errdefs.ErrNotFound
}
func (b *LocalFSBackend) Type() string {
return BackendTypeLocalFS
}
================================================
FILE: pkg/backend/oss.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package backend
import (
"context"
"encoding/json"
"fmt"
"io"
"time"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"github.com/containerd/containerd/v2/core/content"
"github.com/containerd/errdefs"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
)
type OSSBackend struct {
// OSS storage does not support directory. Therefore add a prefix to each object
// to make it a path-like object.
objectPrefix string
bucket *oss.Bucket
forcePush bool
}
func newOSSBackend(rawConfig []byte, forcePush bool) (*OSSBackend, error) {
var configMap map[string]string
if err := json.Unmarshal(rawConfig, &configMap); err != nil {
return nil, errors.Wrap(err, "Parse OSS storage backend configuration")
}
endpoint, ok1 := configMap["endpoint"]
bucketName, ok2 := configMap["bucket_name"]
// Below fields are not mandatory.
accessKeyID := configMap["access_key_id"]
accessKeySecret := configMap["access_key_secret"]
objectPrefix := configMap["object_prefix"]
if !ok1 || !ok2 {
return nil, fmt.Errorf("no endpoint or bucket is specified")
}
client, err := oss.New(endpoint, accessKeyID, accessKeySecret)
if err != nil {
return nil, errors.Wrap(err, "Create client")
}
bucket, err := client.Bucket(bucketName)
if err != nil {
return nil, errors.Wrap(err, "Create bucket")
}
return &OSSBackend{
objectPrefix: objectPrefix,
bucket: bucket,
forcePush: forcePush,
}, nil
}
// Ported from https://github.com/aliyun/aliyun-oss-go-sdk/blob/v2.2.6/oss/utils.go#L259
func splitFileByPartSize(blobSize, chunkSize int64) ([]oss.FileChunk, error) {
if chunkSize <= 0 {
return nil, errors.New("invalid chunk size")
}
var chunkN = blobSize / chunkSize
if chunkN >= 10000 {
return nil, errors.New("too many parts, please increase chunk size")
}
var chunks []oss.FileChunk
var chunk = oss.FileChunk{}
for i := int64(0); i < chunkN; i++ {
chunk.Number = int(i + 1)
chunk.Offset = i * chunkSize
chunk.Size = chunkSize
chunks = append(chunks, chunk)
}
if blobSize%chunkSize > 0 {
chunk.Number = len(chunks) + 1
chunk.Offset = int64(len(chunks)) * chunkSize
chunk.Size = blobSize % chunkSize
chunks = append(chunks, chunk)
}
return chunks, nil
}
// Upload nydus blob to oss storage backend.
func (b *OSSBackend) push(ctx context.Context, cs content.Store, desc ocispec.Descriptor) error {
blobID := desc.Digest.Hex()
blobObjectKey := b.objectPrefix + blobID
ra, err := cs.ReaderAt(ctx, desc)
if err != nil {
return errors.Wrapf(err, "get reader for compression blob %q", desc.Digest)
}
defer ra.Close()
if exist, err := b.bucket.IsObjectExist(blobObjectKey); err != nil {
return errors.Wrap(err, "check object existence")
} else if exist && !b.forcePush {
return nil
}
chunks, err := splitFileByPartSize(ra.Size(), MultipartChunkSize)
if err != nil {
return errors.Wrap(err, "split blob by part num")
}
imur, err := b.bucket.InitiateMultipartUpload(blobObjectKey)
if err != nil {
return errors.Wrap(err, "initiate multipart upload")
}
partsChan := make(chan oss.UploadPart, len(chunks))
g := new(errgroup.Group)
for _, chunk := range chunks {
ck := chunk
g.Go(func() error {
p, err := b.bucket.UploadPart(imur, io.NewSectionReader(ra, ck.Offset, ck.Size), ck.Size, ck.Number)
if err != nil {
return errors.Wrap(err, "upload part")
}
partsChan <- p
return nil
})
}
if err := g.Wait(); err != nil {
_ = b.bucket.AbortMultipartUpload(imur)
close(partsChan)
return errors.Wrap(err, "upload parts")
}
close(partsChan)
parts := make([]oss.UploadPart, 0, 16)
for p := range partsChan {
parts = append(parts, p)
}
_, err = b.bucket.CompleteMultipartUpload(imur, parts)
if err != nil {
return errors.Wrap(err, "complete multipart upload")
}
return nil
}
func (b *OSSBackend) Push(ctx context.Context, cs content.Store, desc ocispec.Descriptor) error {
backoff := time.Second
for {
err := b.push(ctx, cs, desc)
if err != nil {
select {
case <-ctx.Done():
return err
default:
}
} else {
return nil
}
if backoff >= 8*time.Second {
return err
}
time.Sleep(backoff)
backoff *= 2
}
}
func (b *OSSBackend) Check(blobDigest digest.Digest) (string, error) {
blobID := blobDigest.Hex()
blobObjectKey := b.objectPrefix + blobID
if exist, err := b.bucket.IsObjectExist(blobObjectKey); err != nil {
return "", err
} else if exist {
return blobID, nil
}
return "", errdefs.ErrNotFound
}
func (b *OSSBackend) Type() string {
return BackendTypeOSS
}
================================================
FILE: pkg/backend/s3.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package backend
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/aws/aws-sdk-go-v2/aws"
awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
awscfg "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/feature/s3/transfermanager"
tmtypes "github.com/aws/aws-sdk-go-v2/feature/s3/transfermanager/types"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/containerd/containerd/v2/core/content"
"github.com/containerd/errdefs"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
type S3Backend struct {
// objectPrefix is the path prefix of the uploaded object.
// For example, if the blobID which should be uploaded is "abc",
// and the objectPrefix is "path/to/my-registry/", then the object key will be
// "path/to/my-registry/abc".
objectPrefix string
bucketName string
endpointWithScheme string
region string
accessKeySecret string
accessKeyID string
forcePush bool
checksumAlgorithm types.ChecksumAlgorithm
}
type S3Config struct {
AccessKeyID string `json:"access_key_id,omitempty"`
AccessKeySecret string `json:"access_key_secret,omitempty"`
Endpoint string `json:"endpoint,omitempty"`
Scheme string `json:"scheme,omitempty"`
BucketName string `json:"bucket_name,omitempty"`
Region string `json:"region,omitempty"`
ObjectPrefix string `json:"object_prefix,omitempty"`
ChecksumAlgorithm *string `json:"checksum_algorithm,omitempty"`
}
func newS3Backend(rawConfig []byte, forcePush bool) (*S3Backend, error) {
cfg := &S3Config{}
if err := json.Unmarshal(rawConfig, cfg); err != nil {
return nil, errors.Wrap(err, "parse S3 storage backend configuration")
}
if cfg.Endpoint == "" {
cfg.Endpoint = "s3.amazonaws.com"
}
if cfg.Scheme == "" {
cfg.Scheme = "https"
}
endpointWithScheme := fmt.Sprintf("%s://%s", cfg.Scheme, cfg.Endpoint)
if cfg.BucketName == "" || cfg.Region == "" {
return nil, fmt.Errorf("invalid S3 configuration: missing 'bucket_name' or 'region'")
}
var checksumAlgorithm types.ChecksumAlgorithm
if cfg.ChecksumAlgorithm == nil {
// Default to CRC32 checksum
checksumAlgorithm = types.ChecksumAlgorithmCrc32
} else if *cfg.ChecksumAlgorithm != "" {
for _, algorithm := range checksumAlgorithm.Values() {
if string(algorithm) == *cfg.ChecksumAlgorithm {
checksumAlgorithm = algorithm
break
}
}
if checksumAlgorithm == "" {
return nil, fmt.Errorf("invalid checksum algorithm: %s, supported algorithms: %v", *cfg.ChecksumAlgorithm, checksumAlgorithm.Values())
}
}
return &S3Backend{
objectPrefix: cfg.ObjectPrefix,
bucketName: cfg.BucketName,
region: cfg.Region,
endpointWithScheme: endpointWithScheme,
accessKeySecret: cfg.AccessKeySecret,
accessKeyID: cfg.AccessKeyID,
forcePush: forcePush,
checksumAlgorithm: checksumAlgorithm,
}, nil
}
func (b *S3Backend) client() (*s3.Client, error) {
s3AWSConfig, err := awscfg.LoadDefaultConfig(context.TODO())
if err != nil {
return nil, errors.Wrap(err, "load default AWS config")
}
client := s3.NewFromConfig(s3AWSConfig, func(o *s3.Options) {
o.BaseEndpoint = &b.endpointWithScheme
o.Region = b.region
o.UsePathStyle = true
if len(b.accessKeySecret) > 0 && len(b.accessKeyID) > 0 {
o.Credentials = credentials.NewStaticCredentialsProvider(b.accessKeyID, b.accessKeySecret, "")
}
o.UsePathStyle = true
})
return client, nil
}
func (b *S3Backend) existObject(ctx context.Context, objectKey string) (bool, error) {
client, err := b.client()
if err != nil {
return false, errors.Wrap(err, "failed to create s3 client")
}
_, err = client.HeadObject(ctx, &s3.HeadObjectInput{
Bucket: &b.bucketName,
Key: &objectKey,
})
if err != nil {
var responseError *awshttp.ResponseError
if errors.As(err, &responseError) && responseError.HTTPStatusCode() == http.StatusNotFound {
return false, nil
}
return false, err
}
return true, nil
}
func (b *S3Backend) Push(ctx context.Context, cs content.Store, desc ocispec.Descriptor) error {
blobID := desc.Digest.Hex()
blobObjectKey := b.objectPrefix + blobID
if exist, err := b.existObject(ctx, blobObjectKey); err != nil {
return errors.Wrap(err, "check object existence")
} else if exist && !b.forcePush {
return nil
}
ra, err := cs.ReaderAt(ctx, desc)
if err != nil {
return errors.Wrap(err, "get reader from content store")
}
defer ra.Close()
reader := content.NewReader(ra)
client, err := b.client()
if err != nil {
return errors.Wrap(err, "failed to create s3 client")
}
tm := transfermanager.New(client, func(o *transfermanager.Options) {
o.PartSizeBytes = MultipartChunkSize
})
if _, err := tm.UploadObject(ctx, &transfermanager.UploadObjectInput{
Bucket: aws.String(b.bucketName),
Key: aws.String(blobObjectKey),
Body: reader,
ChecksumAlgorithm: tmtypes.ChecksumAlgorithm(b.checksumAlgorithm),
}); err != nil {
return errors.Wrap(err, "push blob to s3 backend")
}
return nil
}
func (b *S3Backend) Check(blobDigest digest.Digest) (string, error) {
blobID := blobDigest.Hex()
objectKey := b.objectPrefix + blobDigest.Hex()
if exist, err := b.existObject(context.Background(), objectKey); err != nil {
return "", err
} else if exist {
return blobID, nil
}
return "", errdefs.ErrNotFound
}
func (b *S3Backend) Type() string {
return BackendTypeS3
}
================================================
FILE: pkg/backend/s3_test.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package backend
import (
"reflect"
"testing"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
)
func Test_newS3Backend(t *testing.T) {
type args struct {
rawConfig []byte
}
tests := []struct {
name string
args args
want *S3Backend
wantErr bool
}{
{
name: "test1, no error",
args: args{
rawConfig: []byte(`{
"endpoint": "localhost:9000",
"scheme": "http",
"bucket_name": "nydus",
"region": "us-east-1",
"object_prefix": "path/to/my-registry/",
"access_key_id": "minio",
"access_key_secret": "minio123"
}`),
},
want: &S3Backend{
objectPrefix: "path/to/my-registry/",
bucketName: "nydus",
endpointWithScheme: "http://localhost:9000",
region: "us-east-1",
accessKeySecret: "minio123",
accessKeyID: "minio",
checksumAlgorithm: types.ChecksumAlgorithmCrc32,
},
wantErr: false,
},
{
name: "test2, set checksum algorithm",
args: args{
rawConfig: []byte(`{
"endpoint": "localhost:9000",
"scheme": "http",
"bucket_name": "nydus",
"region": "us-east-1",
"object_prefix": "path/to/my-registry/",
"access_key_id": "minio",
"access_key_secret": "minio123",
"checksum_algorithm": "SHA256"
}`),
},
want: &S3Backend{
objectPrefix: "path/to/my-registry/",
bucketName: "nydus",
endpointWithScheme: "http://localhost:9000",
region: "us-east-1",
accessKeySecret: "minio123",
accessKeyID: "minio",
checksumAlgorithm: types.ChecksumAlgorithmSha256,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := newS3Backend(tt.args.rawConfig, false)
if (err != nil) != tt.wantErr {
t.Errorf("newS3Backend() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("newS3Backend() = %+#v\nwant %+#v\n\n", got, tt.want)
}
})
}
}
================================================
FILE: pkg/cache/manager.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package cache
import (
"context"
"os"
"path"
"strings"
"time"
"github.com/pkg/errors"
"github.com/containerd/containerd/v2/core/snapshots"
"github.com/containerd/continuity/fs"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/store"
)
const (
imageDiskFileSuffix = ".image.disk"
layerDiskFileSuffix = ".layer.disk"
chunkMapFileSuffix = ".chunk_map"
metaFileSuffix = ".blob.meta"
// Blob cache is suffixed after nydus v2.1
dataFileSuffix = ".blob.data"
)
// Disk cache manager for fusedev.
type Manager struct {
cacheDir string
period time.Duration
eventCh chan struct{}
}
type Opt struct {
Disabled bool
CacheDir string
Period time.Duration
Database *store.Database
}
func NewManager(opt Opt) (*Manager, error) {
// Ensure cache directory exists
if err := os.MkdirAll(opt.CacheDir, 0755); err != nil {
return nil, errors.Wrapf(err, "failed to create cache dir %s", opt.CacheDir)
}
eventCh := make(chan struct{})
m := &Manager{
cacheDir: opt.CacheDir,
period: opt.Period,
eventCh: eventCh,
}
return m, nil
}
func (m *Manager) CacheDir() string {
return m.cacheDir
}
// Report each blob disk usage
// TODO: For fscache cache files, the cache files are managed by nydusd and Linux kernel
// We don't know how it manages cache files. A method to address this is to query nydusd.
// So we can't report cache usage in the case of fscache now
func (m *Manager) CacheUsage(ctx context.Context, blobID string) (snapshots.Usage, error) {
var usage snapshots.Usage
blobCachePath := path.Join(m.cacheDir, blobID)
blobChunkMap := path.Join(m.cacheDir, blobID+chunkMapFileSuffix)
// For backward compatibility
blobCacheSuffixedPath := path.Join(m.cacheDir, blobID+dataFileSuffix)
blobChunkMapSuffixedPath := path.Join(m.cacheDir, blobID+dataFileSuffix+chunkMapFileSuffix)
blobMeta := path.Join(m.cacheDir, blobID+metaFileSuffix)
imageDisk := path.Join(m.cacheDir, blobID+imageDiskFileSuffix)
layerDisk := path.Join(m.cacheDir, blobID+layerDiskFileSuffix)
stuffs := []string{blobCachePath, blobChunkMap, blobCacheSuffixedPath, blobChunkMapSuffixedPath, blobMeta, imageDisk, layerDisk}
for _, f := range stuffs {
du, err := fs.DiskUsage(ctx, f)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
log.L.Debugf("Cache %s does not exist", f)
continue
}
return snapshots.Usage{}, err
}
usage.Add(snapshots.Usage(du))
}
return usage, nil
}
func (m *Manager) RemoveBlobCache(blobID string) error {
blobCachePath := path.Join(m.cacheDir, blobID)
blobChunkMap := path.Join(m.cacheDir, blobID+chunkMapFileSuffix)
blobCacheSuffixedPath := path.Join(m.cacheDir, blobID+dataFileSuffix)
blobChunkMapSuffixedPath := path.Join(m.cacheDir, blobID+dataFileSuffix+chunkMapFileSuffix)
blobMeta := path.Join(m.cacheDir, blobID+metaFileSuffix)
imageDisk := path.Join(m.cacheDir, blobID+imageDiskFileSuffix)
layerDisk := path.Join(m.cacheDir, blobID+layerDiskFileSuffix)
// NOTE: Delete chunk bitmap file before data blob
stuffs := []string{blobChunkMap, blobChunkMapSuffixedPath, blobMeta, blobCachePath, blobCacheSuffixedPath, imageDisk, layerDisk}
for _, f := range stuffs {
err := os.Remove(f)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
log.L.Debugf("file %s doest not exist.", f)
continue
}
return err
}
}
return nil
}
// extractBlobIDFromFilename extracts the blob ID from a cache filename
// Cache files can have formats like:
// -
// - .blob.data
// - .chunk_map
// - .blob.meta
// - .image.disk
// - .layer.disk
func ExtractBlobIDFromFilename(filename string) string {
// Remove known suffixes
suffixes := []string{
dataFileSuffix,
dataFileSuffix + chunkMapFileSuffix,
chunkMapFileSuffix,
metaFileSuffix,
imageDiskFileSuffix,
layerDiskFileSuffix,
}
for _, suffix := range suffixes {
if strings.HasSuffix(filename, suffix) {
return strings.TrimSuffix(filename, suffix)
}
}
// If no suffix matches, assume it's already a blob ID
return filename
}
================================================
FILE: pkg/cache/manager_test.go
================================================
/*
* Copyright (c) 2025. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package cache
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestExtractBlobIDFromFilename(t *testing.T) {
tests := map[string]struct {
filename string
expected string
}{
"plain blob ID": {
filename: "abc123def456",
expected: "abc123def456",
},
"blob with .blob.data suffix": {
filename: "abc123def456.blob.data",
expected: "abc123def456",
},
"blob with .chunk_map suffix": {
filename: "abc123def456.chunk_map",
expected: "abc123def456",
},
"blob with .blob.meta suffix": {
filename: "abc123def456.blob.meta",
expected: "abc123def456",
},
"blob with .image.disk suffix": {
filename: "abc123def456.image.disk",
expected: "abc123def456",
},
"blob with .layer.disk suffix": {
filename: "abc123def456.layer.disk",
expected: "abc123def456",
},
"blob with combined .blob.data.chunk_map suffix": {
filename: "abc123def456.blob.data.chunk_map",
expected: "abc123def456",
},
"real sha256 hash": {
filename: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
expected: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
},
"real sha256 hash with .blob.data": {
filename: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.blob.data",
expected: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
},
"real sha256 hash with .chunk_map": {
filename: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.chunk_map",
expected: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
},
"real sha256 hash with .blob.data.chunk_map": {
filename: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.blob.data.chunk_map",
expected: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
},
"empty filename": {
filename: "",
expected: "",
},
"filename with unknown suffix": {
filename: "abc123def456.unknown",
expected: "abc123def456.unknown",
},
"filename with multiple dots but no known suffix": {
filename: "abc.def.ghi",
expected: "abc.def.ghi",
},
".blob.data.chunk_map is matched before .blob.data or .chunk_map": {
filename: "test.blob.data.chunk_map",
expected: "test",
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
result := ExtractBlobIDFromFilename(tc.filename)
assert.Equal(t, tc.expected, result)
})
}
}
================================================
FILE: pkg/cgroup/cgroup.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package cgroup
import (
"errors"
"github.com/containerd/cgroups/v3"
v1 "github.com/containerd/nydus-snapshotter/pkg/cgroup/v1"
v2 "github.com/containerd/nydus-snapshotter/pkg/cgroup/v2"
)
const (
defaultSlice = "system.slice"
)
var (
ErrCgroupNotSupported = errors.New("cgroups: cgroup not supported")
)
type Config struct {
MemoryLimitInBytes int64
}
type DaemonCgroup interface {
// Delete the current cgroup.
Delete() error
// Add a process to current cgroup.
AddProc(pid int) error
}
func createCgroup(name string, config Config) (DaemonCgroup, error) {
if cgroups.Mode() == cgroups.Unified {
return v2.NewCgroup(defaultSlice, name, config.MemoryLimitInBytes)
}
return v1.NewCgroup(defaultSlice, name, config.MemoryLimitInBytes)
}
func supported() bool {
return cgroups.Mode() != cgroups.Unavailable
}
func displayMode() string {
switch cgroups.Mode() {
case cgroups.Legacy:
return "legacy"
case cgroups.Hybrid:
return "hybrid"
case cgroups.Unified:
return "unified"
case cgroups.Unavailable:
return "unavailable"
default:
return "unknown"
}
}
================================================
FILE: pkg/cgroup/manager.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package cgroup
import (
"github.com/containerd/log"
)
type Manager struct {
name string
config Config
cgroup DaemonCgroup
}
type Opt struct {
Name string
Config Config
}
func NewManager(opt Opt) (*Manager, error) {
if !supported() {
log.L.Warn("cgroup is not supported")
return nil, ErrCgroupNotSupported
}
log.L.Infof("cgroup mode: %s", displayMode())
cg, err := createCgroup(opt.Name, opt.Config)
if err != nil {
return nil, err
}
return &Manager{
name: opt.Name,
config: opt.Config,
cgroup: cg,
}, nil
}
// Please make sure the *Manager is not null.
func (m *Manager) AddProc(pid int) error {
return m.cgroup.AddProc(pid)
}
// Please make sure the *Manager is not null.
func (m *Manager) Delete() error {
return m.cgroup.Delete()
}
================================================
FILE: pkg/cgroup/v1/v1.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package v1
import (
"github.com/containerd/cgroups/v3/cgroup1"
"github.com/containerd/log"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
type Cgroup struct {
controller cgroup1.Cgroup
}
func generateHierarchy() cgroup1.Hierarchy {
return cgroup1.SingleSubsystem(cgroup1.Default, cgroup1.Memory)
}
func NewCgroup(slice, name string, memoryLimitInBytes int64) (Cgroup, error) {
hierarchy := generateHierarchy()
specResources := &specs.LinuxResources{
Memory: &specs.LinuxMemory{
Limit: &memoryLimitInBytes,
},
}
controller, err := cgroup1.Load(cgroup1.Slice(slice, name), cgroup1.WithHiearchy(hierarchy))
if err != nil && err != cgroup1.ErrCgroupDeleted {
return Cgroup{}, err
}
if controller != nil {
processes, err := controller.Processes(cgroup1.Memory, true)
if err != nil {
return Cgroup{}, err
}
if len(processes) > 0 {
log.L.Infof("target cgroup is existed with processes %v", processes)
if err := controller.Update(specResources); err != nil {
return Cgroup{}, err
}
return Cgroup{
controller: controller,
}, nil
}
if err := controller.Delete(); err != nil {
return Cgroup{}, err
}
}
controller, err = cgroup1.New(cgroup1.Slice(slice, name), specResources, cgroup1.WithHiearchy(hierarchy))
if err != nil {
return Cgroup{}, errors.Wrapf(err, "create cgroup")
}
log.L.Infof("create cgroup (v1) successful, state: %v", controller.State())
return Cgroup{
controller: controller,
}, nil
}
func (cg Cgroup) Delete() error {
processes, err := cg.controller.Processes(cgroup1.Memory, true)
if err != nil {
return err
}
if len(processes) > 0 {
log.L.Infof("skip destroy cgroup because of running daemon %v", processes)
return nil
}
return cg.controller.Delete()
}
func (cg Cgroup) AddProc(pid int) error {
err := cg.controller.AddProc(uint64(pid), cgroup1.Memory)
if err != nil {
return err
}
log.L.Infof("add process %d to daemon cgroup successful", pid)
return nil
}
================================================
FILE: pkg/cgroup/v2/v2.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package v2
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/containerd/cgroups/v3/cgroup2"
"github.com/containerd/log"
"golang.org/x/exp/slices"
)
const (
defaultRoot = "/sys/fs/cgroup"
)
var (
ErrRootMemorySubtreeControllerDisabled = errors.New("cgroups v2: root subtree controller for memory is disabled")
)
type Cgroup struct {
manager *cgroup2.Manager
}
func readSubtreeControllers(dir string) ([]string, error) {
b, err := os.ReadFile(filepath.Join(dir, "cgroup.subtree_control"))
if err != nil {
return nil, err
}
return strings.Fields(string(b)), nil
}
func NewCgroup(slice, name string, memoryLimitInBytes int64) (Cgroup, error) {
resources := &cgroup2.Resources{
Memory: &cgroup2.Memory{},
}
if memoryLimitInBytes > -1 {
resources = &cgroup2.Resources{
Memory: &cgroup2.Memory{
Max: &memoryLimitInBytes,
},
}
}
rootSubtreeControllers, err := readSubtreeControllers(defaultRoot)
if err != nil {
return Cgroup{}, err
}
log.L.Infof("root subtree controllers: %s", rootSubtreeControllers)
if !slices.Contains(rootSubtreeControllers, "memory") {
return Cgroup{}, ErrRootMemorySubtreeControllerDisabled
}
m, err := cgroup2.NewManager(defaultRoot, fmt.Sprintf("/%s/%s", slice, name), resources)
if err != nil {
return Cgroup{}, err
}
controllers, err := m.Controllers()
if err != nil {
return Cgroup{}, err
}
log.L.Infof("create cgroup (v2) successful, controllers: %v", controllers)
return Cgroup{
manager: m,
}, nil
}
func (cg Cgroup) Delete() error {
if cg.manager != nil {
return cg.manager.Delete()
}
return nil
}
func (cg Cgroup) AddProc(pid int) error {
if cg.manager != nil {
err := cg.manager.AddProc(uint64(pid))
if err != nil {
return err
}
log.L.Infof("add process %d to daemon cgroup successful", pid)
}
return nil
}
================================================
FILE: pkg/converter/constant.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package converter
const (
ManifestOSFeatureNydus = "nydus.remoteimage.v1"
ManifestConfigNydus = "application/vnd.nydus.image.config.v1+json"
ManifestArtifactTypeNydus = "application/vnd.nydus.image.manifest.v1+json"
MediaTypeNydusBlob = "application/vnd.oci.image.layer.nydus.blob.v1"
BootstrapFileNameInLayer = "image/image.boot"
ManifestNydusCache = "containerd.io/snapshot/nydus-cache"
LayerAnnotationFSVersion = "containerd.io/snapshot/nydus-fs-version"
LayerAnnotationNydusBlob = "containerd.io/snapshot/nydus-blob"
LayerAnnotationNydusBlobDigest = "containerd.io/snapshot/nydus-blob-digest"
LayerAnnotationNydusBlobSize = "containerd.io/snapshot/nydus-blob-size"
LayerAnnotationNydusBootstrap = "containerd.io/snapshot/nydus-bootstrap"
LayerAnnotationNydusSourceChainID = "containerd.io/snapshot/nydus-source-chainid"
LayerAnnotationNydusEncryptedBlob = "containerd.io/snapshot/nydus-encrypted-blob"
LayerAnnotationNydusSourceDigest = "containerd.io/snapshot/nydus-source-digest"
LayerAnnotationNydusTargetDigest = "containerd.io/snapshot/nydus-target-digest"
LayerAnnotationNydusReferenceBlobIDs = "containerd.io/snapshot/nydus-reference-blob-ids"
LayerAnnotationUncompressed = "containerd.io/uncompressed"
)
================================================
FILE: pkg/converter/convert_unix.go
================================================
//go:build !windows
// +build !windows
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package converter
import (
"archive/tar"
"bytes"
"compress/gzip"
"context"
"encoding/binary"
"fmt"
"io"
"os"
"path/filepath"
"sync"
"syscall"
"github.com/containerd/containerd/v2/core/content"
"github.com/containerd/containerd/v2/core/images"
"github.com/containerd/containerd/v2/core/images/converter"
"github.com/containerd/containerd/v2/pkg/archive"
"github.com/containerd/containerd/v2/pkg/archive/compression"
"github.com/containerd/containerd/v2/pkg/labels"
"github.com/containerd/containerd/v2/plugins/content/local"
"github.com/containerd/errdefs"
"github.com/containerd/fifo"
"github.com/klauspost/compress/zstd"
"github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/identity"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
"github.com/containerd/nydus-snapshotter/pkg/converter/tool"
"github.com/containerd/nydus-snapshotter/pkg/label"
)
const EntryBlob = "image.blob"
const EntryBootstrap = "image.boot"
const EntryBlobMeta = "blob.meta"
const EntryBlobMetaHeader = "blob.meta.header"
const EntryTOC = "rafs.blob.toc"
const envNydusBuilder = "NYDUS_BUILDER"
const envNydusWorkDir = "NYDUS_WORKDIR"
const configGCLabelKey = "containerd.io/gc.ref.content.config"
var bufPool = sync.Pool{
New: func() interface{} {
buffer := make([]byte, 1<<20)
return &buffer
},
}
func getBuilder(specifiedPath string) string {
if specifiedPath != "" {
return specifiedPath
}
builderPath := os.Getenv(envNydusBuilder)
if builderPath != "" {
return builderPath
}
return "nydus-image"
}
func ensureWorkDir(specifiedBasePath string) (string, error) {
var baseWorkDir string
if specifiedBasePath != "" {
baseWorkDir = specifiedBasePath
} else {
baseWorkDir = os.Getenv(envNydusWorkDir)
}
if baseWorkDir == "" {
baseWorkDir = os.TempDir()
}
if err := os.MkdirAll(baseWorkDir, 0750); err != nil {
return "", errors.Wrapf(err, "create base directory %s", baseWorkDir)
}
workDirPath, err := os.MkdirTemp(baseWorkDir, "nydus-converter-")
if err != nil {
return "", errors.Wrap(err, "create work directory")
}
return workDirPath, nil
}
// Unpack a OCI formatted tar stream into a directory.
func unpackOciTar(ctx context.Context, dst string, reader io.Reader) error {
ds, err := compression.DecompressStream(reader)
if err != nil {
return errors.Wrap(err, "unpack stream")
}
defer ds.Close()
if _, err := archive.Apply(
ctx,
dst,
ds,
archive.WithConvertWhiteout(func(_ *tar.Header, _ string) (bool, error) {
// Keep to extract all whiteout files.
return true, nil
}),
); err != nil {
return errors.Wrap(err, "apply with convert whiteout")
}
// Read any trailing data for some tar formats, in case the
// PipeWriter of opposite side gets stuck.
if _, err := io.Copy(io.Discard, ds); err != nil {
return errors.Wrap(err, "trailing data after applying archive")
}
return nil
}
// unpackNydusBlob unpacks a Nydus formatted tar stream into a directory.
// unpackBlob indicates whether to unpack blob data.
func unpackNydusBlob(bootDst, blobDst string, ra content.ReaderAt, unpackBlob bool) error {
boot, err := os.OpenFile(bootDst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0640)
if err != nil {
return errors.Wrapf(err, "write to bootstrap %s", bootDst)
}
defer boot.Close()
if _, err = UnpackEntry(ra, EntryBootstrap, boot); err != nil {
return errors.Wrap(err, "unpack bootstrap from nydus")
}
if unpackBlob {
blob, err := os.OpenFile(blobDst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0640)
if err != nil {
return errors.Wrapf(err, "write to blob %s", blobDst)
}
defer blob.Close()
if _, err = UnpackEntry(ra, EntryBlob, blob); err != nil {
if errors.Is(err, ErrNotFound) {
// The nydus layer may contain only bootstrap and no blob
// data, which should be ignored.
return nil
}
return errors.Wrap(err, "unpack blob from nydus")
}
}
return nil
}
func seekFileByTarHeader(ra content.ReaderAt, targetName string, maxSize *int64, handle func(io.Reader, *tar.Header) error) error {
const headerSize = 512
if headerSize > ra.Size() {
return fmt.Errorf("invalid nydus tar size %d", ra.Size())
}
cur := ra.Size() - headerSize
reader := newSeekReader(ra)
// Seek from tail to head of nydus formatted tar stream to find
// target data.
for {
// Try to seek the part of tar header.
_, err := reader.Seek(cur, io.SeekStart)
if err != nil {
return errors.Wrapf(err, "seek %d for nydus tar header", cur)
}
// Parse tar header.
tr := tar.NewReader(reader)
hdr, err := tr.Next()
if err != nil {
return errors.Wrap(err, "parse nydus tar header")
}
if cur < hdr.Size {
return fmt.Errorf("invalid nydus tar data, name %s, size %d", hdr.Name, hdr.Size)
}
if hdr.Name == targetName {
if maxSize != nil && hdr.Size > *maxSize {
return fmt.Errorf("invalid nydus tar size %d", ra.Size())
}
// Try to seek the part of tar data.
_, err = reader.Seek(cur-hdr.Size, io.SeekStart)
if err != nil {
return errors.Wrap(err, "seek target data offset")
}
dataReader := io.NewSectionReader(reader, cur-hdr.Size, hdr.Size)
if err := handle(dataReader, hdr); err != nil {
return errors.Wrap(err, "handle target data")
}
return nil
}
cur = cur - hdr.Size - headerSize
if cur < 0 {
break
}
}
return errors.Wrapf(ErrNotFound, "can't find target %s by seeking tar", targetName)
}
func seekFileByTOC(ra content.ReaderAt, targetName string, handle func(io.Reader, *tar.Header) error) (*TOCEntry, error) {
entrySize := 128
maxSize := int64(1 << 20)
var tocEntry *TOCEntry
err := seekFileByTarHeader(ra, EntryTOC, &maxSize, func(tocEntryDataReader io.Reader, _ *tar.Header) error {
entryData, err := io.ReadAll(tocEntryDataReader)
if err != nil {
return errors.Wrap(err, "read toc entries")
}
if len(entryData)%entrySize != 0 {
return fmt.Errorf("invalid entries length %d", len(entryData))
}
count := len(entryData) / entrySize
for i := 0; i < count; i++ {
var entry TOCEntry
r := bytes.NewReader(entryData[i*entrySize : i*entrySize+entrySize])
if err := binary.Read(r, binary.LittleEndian, &entry); err != nil {
return errors.Wrap(err, "read toc entries")
}
if entry.GetName() == targetName {
compressor, err := entry.GetCompressor()
if err != nil {
return errors.Wrap(err, "get compressor of entry")
}
compressedOffset := int64(entry.GetCompressedOffset())
compressedSize := int64(entry.GetCompressedSize())
sr := io.NewSectionReader(ra, compressedOffset, compressedSize)
var rd io.Reader
switch compressor {
case CompressorZstd:
decoder, err := zstd.NewReader(sr)
if err != nil {
return errors.Wrap(err, "seek to target data offset")
}
defer decoder.Close()
rd = decoder
case CompressorNone:
rd = sr
default:
return fmt.Errorf("unsupported compressor %x", compressor)
}
if err := handle(rd, nil); err != nil {
return errors.Wrap(err, "handle target entry data")
}
tocEntry = &entry
return nil
}
}
return errors.Wrapf(ErrNotFound, "can't find target %s by seeking TOC", targetName)
})
return tocEntry, err
}
// Unpack the file from nydus formatted tar stream.
// The nydus formatted tar stream is a tar-like structure that arranges the
// data as follows:
//
// `data | tar_header | ... | data | tar_header | [toc_entry | ... | toc_entry | tar_header]`
func UnpackEntry(ra content.ReaderAt, targetName string, target io.Writer) (*TOCEntry, error) {
handle := func(dataReader io.Reader, _ *tar.Header) error {
// Copy data to provided target writer.
if _, err := io.Copy(target, dataReader); err != nil {
return errors.Wrap(err, "copy target data to reader")
}
return nil
}
return seekFile(ra, targetName, handle)
}
func seekFile(ra content.ReaderAt, targetName string, handle func(io.Reader, *tar.Header) error) (*TOCEntry, error) {
// Try seek target data by TOC.
entry, err := seekFileByTOC(ra, targetName, handle)
if err != nil {
if !errors.Is(err, ErrNotFound) {
return nil, errors.Wrap(err, "seek file by TOC")
}
} else {
return entry, nil
}
// Seek target data by tar header, ensure compatible with old rafs blob format.
return nil, seekFileByTarHeader(ra, targetName, nil, handle)
}
// Pack converts an OCI tar stream to nydus formatted stream with a tar-like
// structure that arranges the data as follows:
//
// `data | tar_header | data | tar_header | [toc_entry | ... | toc_entry | tar_header]`
//
// The caller should write OCI tar stream into the returned `io.WriteCloser`,
// then the Pack method will write the nydus formatted stream to `dest`
// provided by the caller.
//
// Important: the caller must check `io.WriteCloser.Close() == nil` to ensure
// the conversion workflow is finished.
func Pack(ctx context.Context, dest io.Writer, opt PackOption) (io.WriteCloser, error) {
if opt.FsVersion == "" {
opt.FsVersion = "6"
}
builderPath := getBuilder(opt.BuilderPath)
requiredFeatures := tool.NewFeatures(tool.FeatureTar2Rafs)
if opt.BatchSize != "" && opt.BatchSize != "0" {
requiredFeatures.Add(tool.FeatureBatchSize)
}
if opt.Encrypt {
requiredFeatures.Add(tool.FeatureEncrypt)
}
detectedFeatures, err := tool.DetectFeatures(builderPath, requiredFeatures, tool.GetHelp)
if err != nil {
return nil, err
}
opt.features = detectedFeatures
if opt.OCIRef {
if opt.FsVersion == "6" {
return packFromTar(ctx, dest, opt)
}
return nil, fmt.Errorf("oci ref can only be supported by fs version 6")
}
if opt.features.Contains(tool.FeatureBatchSize) && opt.FsVersion != "6" {
return nil, fmt.Errorf("'--batch-size' can only be supported by fs version 6")
}
if opt.features.Contains(tool.FeatureTar2Rafs) {
return packFromTar(ctx, dest, opt)
}
return packFromDirectory(ctx, dest, opt, builderPath)
}
func packFromDirectory(ctx context.Context, dest io.Writer, opt PackOption, builderPath string) (io.WriteCloser, error) {
workDir, err := ensureWorkDir(opt.WorkDir)
if err != nil {
return nil, errors.Wrap(err, "ensure work directory")
}
defer func() {
if err != nil {
os.RemoveAll(workDir)
}
}()
sourceDir := filepath.Join(workDir, "source")
if err := os.MkdirAll(sourceDir, 0755); err != nil {
return nil, errors.Wrap(err, "create source directory")
}
pr, pw := io.Pipe()
unpackDone := make(chan bool, 1)
go func() {
if err := unpackOciTar(ctx, sourceDir, pr); err != nil {
pr.CloseWithError(errors.Wrapf(err, "unpack to %s", sourceDir))
close(unpackDone)
return
}
unpackDone <- true
}()
wc := newWriteCloser(pw, func() error {
defer os.RemoveAll(workDir)
// Because PipeWriter#Close is called does not mean that the PipeReader
// has finished reading all the data, and unpack may not be complete yet,
// so we need to wait for that here.
<-unpackDone
blobPath := filepath.Join(workDir, "blob")
blobFifo, err := fifo.OpenFifo(ctx, blobPath, syscall.O_CREAT|syscall.O_RDONLY|syscall.O_NONBLOCK, 0640)
if err != nil {
return errors.Wrapf(err, "create fifo file")
}
defer blobFifo.Close()
go func() {
err := tool.Pack(tool.PackOption{
BuilderPath: builderPath,
BlobPath: blobPath,
FsVersion: opt.FsVersion,
SourcePath: sourceDir,
ChunkDictPath: opt.ChunkDictPath,
PrefetchPatterns: opt.PrefetchPatterns,
AlignedChunk: opt.AlignedChunk,
ChunkSize: opt.ChunkSize,
BatchSize: opt.BatchSize,
Compressor: opt.Compressor,
Timeout: opt.Timeout,
Encrypt: opt.Encrypt,
Features: opt.features,
})
if err != nil {
pw.CloseWithError(errors.Wrapf(err, "convert blob for %s", sourceDir))
blobFifo.Close()
}
}()
buffer := bufPool.Get().(*[]byte)
defer bufPool.Put(buffer)
if _, err := io.CopyBuffer(dest, blobFifo, *buffer); err != nil {
return errors.Wrap(err, "pack nydus tar")
}
return nil
})
return wc, nil
}
func packFromTar(ctx context.Context, dest io.Writer, opt PackOption) (io.WriteCloser, error) {
workDir, err := ensureWorkDir(opt.WorkDir)
if err != nil {
return nil, errors.Wrap(err, "ensure work directory")
}
defer func() {
if err != nil {
os.RemoveAll(workDir)
}
}()
rafsBlobPath := filepath.Join(workDir, "blob.rafs")
rafsBlobFifo, err := fifo.OpenFifo(ctx, rafsBlobPath, syscall.O_CREAT|syscall.O_RDONLY|syscall.O_NONBLOCK, 0640)
if err != nil {
return nil, errors.Wrapf(err, "create fifo file")
}
tarBlobPath := filepath.Join(workDir, "blob.targz")
tarBlobFifo, err := fifo.OpenFifo(ctx, tarBlobPath, syscall.O_CREAT|syscall.O_WRONLY|syscall.O_NONBLOCK, 0640)
if err != nil {
defer rafsBlobFifo.Close()
return nil, errors.Wrapf(err, "create fifo file")
}
pr, pw := io.Pipe()
eg := errgroup.Group{}
wc := newWriteCloser(pw, func() error {
defer os.RemoveAll(workDir)
if err := eg.Wait(); err != nil {
return errors.Wrapf(err, "convert nydus ref")
}
return nil
})
eg.Go(func() error {
defer tarBlobFifo.Close()
buffer := bufPool.Get().(*[]byte)
defer bufPool.Put(buffer)
if _, err := io.CopyBuffer(tarBlobFifo, pr, *buffer); err != nil {
return errors.Wrapf(err, "copy targz to fifo")
}
return nil
})
eg.Go(func() error {
defer rafsBlobFifo.Close()
buffer := bufPool.Get().(*[]byte)
defer bufPool.Put(buffer)
if _, err := io.CopyBuffer(dest, rafsBlobFifo, *buffer); err != nil {
return errors.Wrapf(err, "copy blob meta fifo to nydus blob")
}
return nil
})
eg.Go(func() error {
var err error
if opt.OCIRef {
err = tool.Pack(tool.PackOption{
BuilderPath: getBuilder(opt.BuilderPath),
OCIRef: opt.OCIRef,
BlobPath: rafsBlobPath,
SourcePath: tarBlobPath,
Timeout: opt.Timeout,
Features: opt.features,
})
} else {
err = tool.Pack(tool.PackOption{
BuilderPath: getBuilder(opt.BuilderPath),
BlobPath: rafsBlobPath,
FsVersion: opt.FsVersion,
SourcePath: tarBlobPath,
ChunkDictPath: opt.ChunkDictPath,
PrefetchPatterns: opt.PrefetchPatterns,
AlignedChunk: opt.AlignedChunk,
ChunkSize: opt.ChunkSize,
BatchSize: opt.BatchSize,
Compressor: opt.Compressor,
Timeout: opt.Timeout,
Encrypt: opt.Encrypt,
Features: opt.features,
})
}
if err != nil {
// Without handling the returned error because we just only
// focus on the command exit status in `tool.Pack`.
_ = wc.Close()
}
return errors.Wrapf(err, "call builder")
})
return wc, nil
}
func calcBlobTOCDigest(ra content.ReaderAt) (*digest.Digest, error) {
maxSize := int64(1 << 20)
digester := digest.Canonical.Digester()
if err := seekFileByTarHeader(ra, EntryTOC, &maxSize, func(tocData io.Reader, _ *tar.Header) error {
if _, err := io.Copy(digester.Hash(), tocData); err != nil {
return errors.Wrap(err, "calc toc data and header digest")
}
return nil
}); err != nil {
return nil, err
}
tocDigest := digester.Digest()
return &tocDigest, nil
}
// Merge multiple nydus bootstraps (from each layer of image) to a final
// bootstrap. And due to the possibility of enabling the `ChunkDictPath`
// option causes the data deduplication, it will return the actual blob
// digests referenced by the bootstrap.
func Merge(ctx context.Context, layers []Layer, dest io.Writer, opt MergeOption) ([]digest.Digest, error) {
workDir, err := ensureWorkDir(opt.WorkDir)
if err != nil {
return nil, errors.Wrap(err, "ensure work directory")
}
defer os.RemoveAll(workDir)
getLayerPath := func(layerIdx int, suffix string) string {
if layerIdx < 0 || layerIdx >= len(layers) {
return ""
}
digestHex := layers[layerIdx].Digest.Hex()
if suffix == "" && layers[layerIdx].OriginalDigest != nil {
digestHex = layers[layerIdx].OriginalDigest.Hex()
}
return filepath.Join(workDir, digestHex+suffix)
}
unpackLayerEntry := func(layerIdx int, entryName string, filePath string) error {
if layerIdx < 0 || layerIdx >= len(layers) {
return errors.Errorf("layer index %d out of bounds", layerIdx)
}
file, err := os.Create(filePath)
if err != nil {
return errors.Wrapf(err, "create %s file", entryName)
}
defer file.Close()
if _, err := UnpackEntry(layers[layerIdx].ReaderAt, entryName, file); err != nil {
return errors.Wrapf(err, "unpack %s", entryName)
}
return nil
}
eg, _ := errgroup.WithContext(ctx)
sourceBootstrapPaths := []string{}
rafsBlobDigests := []string{}
rafsBlobSizes := []int64{}
rafsBlobTOCDigests := []string{}
for idx := range layers {
sourceBootstrapPaths = append(sourceBootstrapPaths, getLayerPath(idx, ""))
if layers[idx].OriginalDigest != nil {
rafsBlobTOCDigest, err := calcBlobTOCDigest(layers[idx].ReaderAt)
if err != nil {
return nil, errors.Wrapf(err, "calc blob toc digest for layer %s", layers[idx].Digest)
}
rafsBlobTOCDigests = append(rafsBlobTOCDigests, rafsBlobTOCDigest.Hex())
rafsBlobDigests = append(rafsBlobDigests, layers[idx].Digest.Hex())
rafsBlobSizes = append(rafsBlobSizes, layers[idx].ReaderAt.Size())
}
eg.Go(func(idx int) func() error {
return func() error {
// Use the hex hash string of whole tar blob as the bootstrap name.
if err := unpackLayerEntry(idx, EntryBootstrap, getLayerPath(idx, "")); err != nil {
return err
}
if opt.FsVersion == "6" {
if err := unpackLayerEntry(idx, EntryBlobMeta, getLayerPath(idx, ".blob.meta")); err != nil {
logrus.Warnf("Failed to extract blob.meta.header for layer %d: %v\n", idx, err)
}
if err := unpackLayerEntry(idx, EntryBlobMetaHeader, getLayerPath(idx, ".blob.meta.header")); err != nil {
logrus.Warnf("Failed to extract blob.meta.header for layer %d: %v\n", idx, err)
}
}
return nil
}
}(idx))
}
if err := eg.Wait(); err != nil {
return nil, errors.Wrap(err, "unpack all bootstraps")
}
targetBootstrapPath := filepath.Join(workDir, "bootstrap")
blobDigests, err := tool.Merge(tool.MergeOption{
BuilderPath: getBuilder(opt.BuilderPath),
SourceBootstrapPaths: sourceBootstrapPaths,
RafsBlobDigests: rafsBlobDigests,
RafsBlobSizes: rafsBlobSizes,
RafsBlobTOCDigests: rafsBlobTOCDigests,
TargetBootstrapPath: targetBootstrapPath,
ChunkDictPath: opt.ChunkDictPath,
ParentBootstrapPath: opt.ParentBootstrapPath,
PrefetchPatterns: opt.PrefetchPatterns,
OutputJSONPath: filepath.Join(workDir, "merge-output.json"),
Timeout: opt.Timeout,
})
if err != nil {
return nil, errors.Wrap(err, "merge bootstrap")
}
bootstrapRa, err := local.OpenReader(targetBootstrapPath)
if err != nil {
return nil, errors.Wrap(err, "open bootstrap reader")
}
defer bootstrapRa.Close()
files := []File{
{
Name: EntryBootstrap,
Reader: content.NewReader(bootstrapRa),
Size: bootstrapRa.Size(),
},
}
if opt.FsVersion == "6" {
metaRas := make([]io.Closer, 0, len(layers)*2)
defer func() {
for _, closer := range metaRas {
closer.Close()
}
}()
for idx := range layers {
digestHex := layers[idx].Digest.Hex()
blobMetaPath := getLayerPath(idx, ".blob.meta")
blobMetaHeaderPath := getLayerPath(idx, ".blob.meta.header")
metaContent, err := os.ReadFile(blobMetaPath)
if err != nil {
return nil, errors.Wrap(err, "read blob.meta")
}
headerContent, err := os.ReadFile(blobMetaHeaderPath)
if err != nil {
return nil, errors.Wrap(err, "read blob.meta.header")
}
uncompressedSize := len(metaContent)
alignedUncompressedSize := (uncompressedSize + 4095) &^ 4095
totalSize := alignedUncompressedSize + len(headerContent)
if totalSize == 0 {
logrus.Warnf("blob data for layer %s is empty, skipped\n", digestHex)
continue
}
assembledFileName := fmt.Sprintf("%s.blob.meta", digestHex)
assembledFilePath := filepath.Join(workDir, assembledFileName)
writeMetaFile := func() error {
f, err := os.Create(assembledFilePath)
if err != nil {
return err
}
defer f.Close()
if _, err := f.Write(metaContent); err != nil {
return err
}
if padding := alignedUncompressedSize - uncompressedSize; padding > 0 {
if _, err := f.Write(make([]byte, padding)); err != nil {
return err
}
}
if _, err := f.Write(headerContent); err != nil {
return err
}
return f.Sync()
}
if err := writeMetaFile(); err != nil {
return nil, errors.Wrap(err, "write blob meta file")
}
assembledRa, err := local.OpenReader(assembledFilePath)
if err != nil {
return nil, errors.Wrap(err, "open blob meta file")
}
metaRas = append(metaRas, assembledRa)
files = append(files, File{
Name: assembledFileName,
Reader: content.NewReader(assembledRa),
Size: int64(totalSize),
})
}
}
files = append(files, opt.AppendFiles...)
var rc io.ReadCloser
if opt.WithTar {
rc = packToTar(files, false)
} else {
rc, err = os.Open(targetBootstrapPath)
if err != nil {
return nil, errors.Wrap(err, "open targe bootstrap")
}
}
defer rc.Close()
buffer := bufPool.Get().(*[]byte)
defer bufPool.Put(buffer)
if _, err = io.CopyBuffer(dest, rc, *buffer); err != nil {
return nil, errors.Wrap(err, "copy merged bootstrap")
}
return blobDigests, nil
}
// Unpack converts a nydus blob layer to OCI formatted tar stream.
func Unpack(ctx context.Context, ra content.ReaderAt, dest io.Writer, opt UnpackOption) error {
workDir, err := ensureWorkDir(opt.WorkDir)
if err != nil {
return errors.Wrap(err, "ensure work directory")
}
defer os.RemoveAll(workDir)
bootPath, blobPath := filepath.Join(workDir, EntryBootstrap), filepath.Join(workDir, EntryBlob)
if err = unpackNydusBlob(bootPath, blobPath, ra, !opt.Stream); err != nil {
return errors.Wrap(err, "unpack nydus tar")
}
tarPath := filepath.Join(workDir, "oci.tar")
blobFifo, err := fifo.OpenFifo(ctx, tarPath, syscall.O_CREAT|syscall.O_RDONLY|syscall.O_NONBLOCK, 0640)
if err != nil {
return errors.Wrapf(err, "create fifo file")
}
defer blobFifo.Close()
unpackOpt := tool.UnpackOption{
BuilderPath: getBuilder(opt.BuilderPath),
BootstrapPath: bootPath,
BlobPath: blobPath,
TarPath: tarPath,
Timeout: opt.Timeout,
}
if opt.Stream {
proxy, err := setupContentStoreProxy(opt.WorkDir, ra)
if err != nil {
return errors.Wrap(err, "new content store proxy")
}
defer func() { _ = proxy.close() }()
// generate backend config file
backendConfigStr := fmt.Sprintf(`{"version":2,"backend":{"type":"http-proxy","http-proxy":{"addr":"%s"}}}`, proxy.socketPath)
backendConfigPath := filepath.Join(workDir, "backend-config.json")
if err := os.WriteFile(backendConfigPath, []byte(backendConfigStr), 0640); err != nil {
return errors.Wrap(err, "write backend config")
}
unpackOpt.BlobPath = ""
unpackOpt.BackendConfigPath = backendConfigPath
}
unpackErrChan := make(chan error)
go func() {
defer close(unpackErrChan)
err := tool.Unpack(unpackOpt)
if err != nil {
blobFifo.Close()
unpackErrChan <- err
}
}()
buffer := bufPool.Get().(*[]byte)
defer bufPool.Put(buffer)
if _, err := io.CopyBuffer(dest, blobFifo, *buffer); err != nil {
if unpackErr := <-unpackErrChan; unpackErr != nil {
return errors.Wrap(unpackErr, "unpack")
}
return errors.Wrap(err, "copy oci tar")
}
return nil
}
// IsNydusBlobAndExists returns true when the specified digest of content exists in
// the content store and it's nydus blob format.
func IsNydusBlobAndExists(ctx context.Context, cs content.Store, desc ocispec.Descriptor) bool {
_, err := cs.Info(ctx, desc.Digest)
if err != nil {
return false
}
return IsNydusBlob(desc)
}
// IsNydusBlob returns true when the specified descriptor is nydus blob layer.
func IsNydusBlob(desc ocispec.Descriptor) bool {
if desc.Annotations == nil {
return false
}
_, hasAnno := desc.Annotations[LayerAnnotationNydusBlob]
return hasAnno
}
// IsNydusBootstrap returns true when the specified descriptor is nydus bootstrap layer.
func IsNydusBootstrap(desc ocispec.Descriptor) bool {
if desc.Annotations == nil {
return false
}
_, hasAnno := desc.Annotations[LayerAnnotationNydusBootstrap]
return hasAnno
}
// isNydusImage checks if the last layer is nydus bootstrap,
// so that we can ensure it is a nydus image.
func isNydusImage(manifest *ocispec.Manifest) bool {
layers := manifest.Layers
if len(layers) != 0 {
desc := layers[len(layers)-1]
if IsNydusBootstrap(desc) {
return true
}
}
return false
}
// makeBlobDesc returns a ocispec.Descriptor by the given information.
func makeBlobDesc(ctx context.Context, cs content.Store, opt PackOption, sourceDigest, targetDigest digest.Digest) (*ocispec.Descriptor, error) {
targetInfo, err := cs.Info(ctx, targetDigest)
if err != nil {
return nil, errors.Wrapf(err, "get target blob info %s", targetDigest)
}
if targetInfo.Labels == nil {
targetInfo.Labels = map[string]string{}
}
// Write a diff id label of layer in content store for simplifying
// diff id calculation to speed up the conversion.
// See: https://github.com/containerd/containerd/blob/e4fefea5544d259177abb85b64e428702ac49c97/images/diffid.go#L49
targetInfo.Labels[labels.LabelUncompressed] = targetDigest.String()
_, err = cs.Update(ctx, targetInfo)
if err != nil {
return nil, errors.Wrap(err, "update layer label")
}
targetDesc := ocispec.Descriptor{
Digest: targetDigest,
Size: targetInfo.Size,
MediaType: MediaTypeNydusBlob,
Annotations: map[string]string{
// Use `containerd.io/uncompressed` to generate DiffID of
// layer defined in OCI spec.
LayerAnnotationUncompressed: targetDigest.String(),
LayerAnnotationNydusBlob: "true",
},
}
if opt.OCIRef {
targetDesc.Annotations[label.NydusRefLayer] = sourceDigest.String()
}
if opt.Encrypt {
targetDesc.Annotations[LayerAnnotationNydusEncryptedBlob] = "true"
}
return &targetDesc, nil
}
// LayerConvertFunc returns a function which converts an OCI image layer to
// a nydus blob layer, and set the media type to "application/vnd.oci.image.layer.nydus.blob.v1".
func LayerConvertFunc(opt PackOption) converter.ConvertFunc {
return func(ctx context.Context, cs content.Store, desc ocispec.Descriptor) (*ocispec.Descriptor, error) {
if ctx.Err() != nil {
// The context is already cancelled, no need to proceed.
return nil, ctx.Err()
}
if !images.IsLayerType(desc.MediaType) {
return nil, nil
}
// Skip the conversion of nydus layer.
if IsNydusBlob(desc) || IsNydusBootstrap(desc) {
return nil, nil
}
// Use remote cache to avoid unnecessary conversion
info, err := cs.Info(ctx, desc.Digest)
if err != nil {
return nil, errors.Wrapf(err, "get blob info %s", desc.Digest)
}
if targetDigest := digest.Digest(info.Labels[LayerAnnotationNydusTargetDigest]); targetDigest.Validate() == nil {
return makeBlobDesc(ctx, cs, opt, desc.Digest, targetDigest)
}
ra, err := cs.ReaderAt(ctx, desc)
if err != nil {
return nil, errors.Wrap(err, "get source blob reader")
}
defer ra.Close()
rdr := io.NewSectionReader(ra, 0, ra.Size())
ref := fmt.Sprintf("convert-nydus-from-%s", desc.Digest)
dst, err := content.OpenWriter(ctx, cs, content.WithRef(ref))
if err != nil {
return nil, errors.Wrap(err, "open blob writer")
}
defer dst.Close()
var tr io.ReadCloser
if opt.OCIRef {
tr = io.NopCloser(rdr)
} else {
tr, err = compression.DecompressStream(rdr)
if err != nil {
return nil, errors.Wrap(err, "decompress blob stream")
}
}
digester := digest.SHA256.Digester()
pr, pw := io.Pipe()
tw, err := Pack(ctx, io.MultiWriter(pw, digester.Hash()), opt)
if err != nil {
return nil, errors.Wrap(err, "pack tar to nydus")
}
copyBufferDone := make(chan error, 1)
go func() {
buffer := bufPool.Get().(*[]byte)
defer bufPool.Put(buffer)
_, err := io.CopyBuffer(tw, tr, *buffer)
copyBufferDone <- err
}()
go func() {
defer pw.Close()
select {
case <-ctx.Done():
// The context was cancelled!
// Close the pipe with the context's error to signal
// the reader to stop.
pw.CloseWithError(ctx.Err())
return
case err := <-copyBufferDone:
if err != nil {
pw.CloseWithError(err)
return
}
}
if err := tr.Close(); err != nil {
pw.CloseWithError(err)
return
}
if err := tw.Close(); err != nil {
pw.CloseWithError(err)
return
}
}()
if err := content.Copy(ctx, dst, pr, 0, ""); err != nil {
return nil, errors.Wrap(err, "copy nydus blob to content store")
}
blobDigest := digester.Digest()
newDesc, err := makeBlobDesc(ctx, cs, opt, desc.Digest, blobDigest)
if err != nil {
return nil, err
}
if opt.Backend != nil {
if err := opt.Backend.Push(ctx, cs, *newDesc); err != nil {
return nil, errors.Wrap(err, "push to storage backend")
}
}
return newDesc, nil
}
}
// ConvertHookFunc returns a function which will be used as a callback
// called for each blob after conversion is done. The function only hooks
// the index conversion and the manifest conversion.
func ConvertHookFunc(opt MergeOption) converter.ConvertHookFunc {
return func(ctx context.Context, cs content.Store, orgDesc ocispec.Descriptor, newDesc *ocispec.Descriptor) (*ocispec.Descriptor, error) {
// If the previous conversion did not occur, the `newDesc` may be nil.
if newDesc == nil {
return &orgDesc, nil
}
switch {
case images.IsIndexType(newDesc.MediaType):
return convertIndex(ctx, cs, newDesc)
case images.IsManifestType(newDesc.MediaType):
return convertManifest(ctx, cs, orgDesc, newDesc, opt)
default:
return newDesc, nil
}
}
}
// convertIndex modifies the original index converting it to manifest directly if it contains only one manifest.
func convertIndex(ctx context.Context, cs content.Store, newDesc *ocispec.Descriptor) (*ocispec.Descriptor, error) {
var index ocispec.Index
_, err := readJSON(ctx, cs, &index, *newDesc)
if err != nil {
return nil, errors.Wrap(err, "read index json")
}
// If the converted manifest list contains only one manifest,
// convert it directly to manifest.
if len(index.Manifests) == 1 {
return &index.Manifests[0], nil
}
return newDesc, nil
}
// convertManifest merges all the nydus blob layers into a
// nydus bootstrap layer, update the image config,
// and modify the image manifest.
func convertManifest(ctx context.Context, cs content.Store, oldDesc ocispec.Descriptor, newDesc *ocispec.Descriptor, opt MergeOption) (*ocispec.Descriptor, error) {
var manifest ocispec.Manifest
manifestDesc := *newDesc
manifestLabels, err := readJSON(ctx, cs, &manifest, manifestDesc)
if err != nil {
return nil, errors.Wrap(err, "read manifest json")
}
if isNydusImage(&manifest) {
return &manifestDesc, nil
}
// This option needs to be enabled for image scenario.
opt.WithTar = true
// If the original image is already an OCI type, we should forcibly set the
// bootstrap layer to the OCI type.
if !opt.OCI && oldDesc.MediaType == ocispec.MediaTypeImageManifest {
opt.OCI = true
}
// Append bootstrap layer to manifest, encrypt bootstrap layer if needed.
bootstrapDesc, blobDescs, err := MergeLayers(ctx, cs, manifest.Layers, opt)
if err != nil {
return nil, errors.Wrap(err, "merge nydus layers")
}
if opt.Backend != nil {
// Only append nydus bootstrap layer into manifest, and do not put nydus
// blob layer into manifest if blob storage backend is specified.
manifest.Layers = []ocispec.Descriptor{*bootstrapDesc}
} else {
for idx, blobDesc := range blobDescs {
blobGCLabelKey := fmt.Sprintf("containerd.io/gc.ref.content.l.%d", idx)
manifestLabels[blobGCLabelKey] = blobDesc.Digest.String()
}
// Affected by chunk dict, the blob list referenced by final bootstrap
// are from different layers, part of them are from original layers, part
// from chunk dict bootstrap, so we need to rewrite manifest's layers here.
blobDescs := append(blobDescs, *bootstrapDesc)
manifest.Layers = blobDescs
}
// Update the gc label of bootstrap layer
bootstrapGCLabelKey := fmt.Sprintf("containerd.io/gc.ref.content.l.%d", len(manifest.Layers)-1)
manifestLabels[bootstrapGCLabelKey] = bootstrapDesc.Digest.String()
// Rewrite diff ids and remove useless annotation.
var config ocispec.Image
configLabels, err := readJSON(ctx, cs, &config, manifest.Config)
if err != nil {
return nil, errors.Wrap(err, "read image config")
}
bootstrapHistory := ocispec.History{
CreatedBy: "Nydus Converter",
Comment: "Nydus Bootstrap Layer",
}
if opt.Backend != nil {
config.RootFS.DiffIDs = []digest.Digest{digest.Digest(bootstrapDesc.Annotations[LayerAnnotationUncompressed])}
config.History = []ocispec.History{bootstrapHistory}
} else {
config.RootFS.DiffIDs = make([]digest.Digest, 0, len(manifest.Layers))
for i, layer := range manifest.Layers {
config.RootFS.DiffIDs = append(config.RootFS.DiffIDs, digest.Digest(layer.Annotations[LayerAnnotationUncompressed]))
// Remove useless annotation.
delete(manifest.Layers[i].Annotations, LayerAnnotationUncompressed)
}
// Append history item for bootstrap layer, to ensure the history consistency.
// See https://github.com/distribution/distribution/blob/e5d5810851d1f17a5070e9b6f940d8af98ea3c29/manifest/schema1/config_builder.go#L136
config.History = append(config.History, bootstrapHistory)
}
// Update image config in content store.
newConfigDesc, err := writeJSON(ctx, cs, config, manifest.Config, configLabels)
if err != nil {
return nil, errors.Wrap(err, "write image config")
}
// When manifests are merged, we need to put a special value for the config mediaType.
// This values must be one that containerd doesn't understand to ensure it doesn't try tu pull the nydus image
// but use the OCI one instead. And then if the nydus-snapshotter is used, it can pull the nydus image instead.
if opt.MergeManifest {
newConfigDesc.MediaType = ManifestConfigNydus
}
manifest.Config = *newConfigDesc
// Update the config gc label
manifestLabels[configGCLabelKey] = newConfigDesc.Digest.String()
if opt.WithReferrer {
// Associate a reference to the original OCI manifest.
// See the `subject` field description in
// https://github.com/opencontainers/image-spec/blob/main/manifest.md#image-manifest-property-descriptions
manifest.Subject = &oldDesc
// Remove the platform field as it is not supported by certain registries like ECR.
manifest.Subject.Platform = nil
}
// Update image manifest in content store.
newManifestDesc, err := writeJSON(ctx, cs, manifest, manifestDesc, manifestLabels)
if err != nil {
return nil, errors.Wrap(err, "write manifest")
}
return newManifestDesc, nil
}
// mergeManifestBlobDigests combines the per-layer nydus blob tar digests with any
// additional blobs from the nydus-image merge output that are not already covered.
//
// nydusBlobDigests contains one entry per OCI source layer in layer order, including
// metadata-only layers (e.g. symlink-only layers) whose blob tars have no chunk data.
// originalBlobDigests comes from the nydus-image merge output blob table, which omits
// metadata-only layers but may include chunk-dict blobs not present in nydusBlobDigests.
//
// The result preserves the OCI layer order from nydusBlobDigests and appends any
// dict-only blobs from originalBlobDigests at the end.
func mergeManifestBlobDigests(nydusBlobDigests, originalBlobDigests []digest.Digest) []digest.Digest {
nydusSet := make(map[digest.Digest]struct{}, len(nydusBlobDigests))
for _, d := range nydusBlobDigests {
nydusSet[d] = struct{}{}
}
result := append([]digest.Digest{}, nydusBlobDigests...)
for _, d := range originalBlobDigests {
if _, ok := nydusSet[d]; !ok {
result = append(result, d)
}
}
return result
}
// MergeLayers merges a list of nydus blob layer into a nydus bootstrap layer.
// The media type of the nydus bootstrap layer is "application/vnd.oci.image.layer.v1.tar+gzip".
func MergeLayers(ctx context.Context, cs content.Store, descs []ocispec.Descriptor, opt MergeOption) (*ocispec.Descriptor, []ocispec.Descriptor, error) {
// Extracts nydus bootstrap from nydus format for each layer.
layers := []Layer{}
var chainID digest.Digest
nydusBlobDigests := []digest.Digest{}
for _, nydusBlobDesc := range descs {
ra, err := cs.ReaderAt(ctx, nydusBlobDesc)
if err != nil {
return nil, nil, errors.Wrapf(err, "get reader for blob %q", nydusBlobDesc.Digest)
}
defer ra.Close()
var originalDigest *digest.Digest
if opt.OCIRef {
digestStr := nydusBlobDesc.Annotations[label.NydusRefLayer]
_originalDigest, err := digest.Parse(digestStr)
if err != nil {
return nil, nil, errors.Wrapf(err, "invalid label %s=%s", label.NydusRefLayer, digestStr)
}
originalDigest = &_originalDigest
}
layers = append(layers, Layer{
Digest: nydusBlobDesc.Digest,
OriginalDigest: originalDigest,
ReaderAt: ra,
})
if chainID == "" {
chainID = identity.ChainID([]digest.Digest{nydusBlobDesc.Digest})
} else {
chainID = identity.ChainID([]digest.Digest{chainID, nydusBlobDesc.Digest})
}
nydusBlobDigests = append(nydusBlobDigests, nydusBlobDesc.Digest)
}
// Merge all nydus bootstraps into a final nydus bootstrap.
pr, pw := io.Pipe()
originalBlobDigestChan := make(chan []digest.Digest, 1)
go func() {
defer pw.Close()
originalBlobDigests, err := Merge(ctx, layers, pw, opt)
if err != nil {
pw.CloseWithError(errors.Wrapf(err, "merge nydus bootstrap"))
}
originalBlobDigestChan <- originalBlobDigests
}()
// Compress final nydus bootstrap to tar.gz and write into content store.
cw, err := content.OpenWriter(ctx, cs, content.WithRef("nydus-merge-"+chainID.String()))
if err != nil {
return nil, nil, errors.Wrap(err, "open content store writer")
}
defer cw.Close()
gw := gzip.NewWriter(cw)
uncompressedDgst := digest.SHA256.Digester()
compressed := io.MultiWriter(gw, uncompressedDgst.Hash())
buffer := bufPool.Get().(*[]byte)
defer bufPool.Put(buffer)
if _, err := io.CopyBuffer(compressed, pr, *buffer); err != nil {
return nil, nil, errors.Wrapf(err, "copy bootstrap targz into content store")
}
if err := gw.Close(); err != nil {
return nil, nil, errors.Wrap(err, "close gzip writer")
}
compressedDgst := cw.Digest()
if err := cw.Commit(ctx, 0, compressedDgst, content.WithLabels(map[string]string{
LayerAnnotationUncompressed: uncompressedDgst.Digest().String(),
})); err != nil {
if !errdefs.IsAlreadyExists(err) {
return nil, nil, errors.Wrap(err, "commit to content store")
}
}
if err := cw.Close(); err != nil {
return nil, nil, errors.Wrap(err, "close content store writer")
}
bootstrapInfo, err := cs.Info(ctx, compressedDgst)
if err != nil {
return nil, nil, errors.Wrap(err, "get info from content store")
}
originalBlobDigests := <-originalBlobDigestChan
blobDescs := []ocispec.Descriptor{}
var blobDigests []digest.Digest
if opt.OCIRef {
blobDigests = nydusBlobDigests
} else {
blobDigests = mergeManifestBlobDigests(nydusBlobDigests, originalBlobDigests)
}
for idx, blobDigest := range blobDigests {
blobInfo, err := cs.Info(ctx, blobDigest)
if err != nil {
return nil, nil, errors.Wrap(err, "get info from content store")
}
blobDesc := ocispec.Descriptor{
Digest: blobDigest,
Size: blobInfo.Size,
MediaType: MediaTypeNydusBlob,
Annotations: map[string]string{
LayerAnnotationUncompressed: blobDigest.String(),
LayerAnnotationNydusBlob: "true",
},
}
if opt.OCIRef {
blobDesc.Annotations[label.NydusRefLayer] = layers[idx].OriginalDigest.String()
}
if opt.Encrypt != nil {
blobDesc.Annotations[LayerAnnotationNydusEncryptedBlob] = "true"
}
blobDescs = append(blobDescs, blobDesc)
}
if opt.FsVersion == "" {
opt.FsVersion = "6"
}
mediaType := images.MediaTypeDockerSchema2LayerGzip
if opt.OCI {
mediaType = ocispec.MediaTypeImageLayerGzip
}
bootstrapDesc := ocispec.Descriptor{
Digest: compressedDgst,
Size: bootstrapInfo.Size,
MediaType: mediaType,
Annotations: map[string]string{
LayerAnnotationUncompressed: uncompressedDgst.Digest().String(),
LayerAnnotationFSVersion: opt.FsVersion,
// Use this annotation to identify nydus bootstrap layer.
LayerAnnotationNydusBootstrap: "true",
},
}
if opt.Encrypt != nil {
// Encrypt the Nydus bootstrap layer.
bootstrapDesc, err = opt.Encrypt(ctx, cs, bootstrapDesc)
if err != nil {
return nil, nil, errors.Wrap(err, "encrypt bootstrap layer")
}
}
return &bootstrapDesc, blobDescs, nil
}
================================================
FILE: pkg/converter/convert_windows.go
================================================
//go:build windows
// +build windows
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package converter
import (
"context"
"io"
"github.com/containerd/containerd/v2/core/content"
"github.com/containerd/containerd/v2/core/images/converter"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
func Pack(ctx context.Context, dest io.Writer, opt PackOption) (io.WriteCloser, error) {
panic("not implemented")
}
func Merge(ctx context.Context, layers []Layer, dest io.Writer, opt MergeOption) ([]digest.Digest, error) {
panic("not implemented")
}
func Unpack(ctx context.Context, ra content.ReaderAt, dest io.Writer, opt UnpackOption) error {
panic("not implemented")
}
func IsNydusBlobAndExists(ctx context.Context, cs content.Store, desc ocispec.Descriptor) bool {
panic("not implemented")
}
func IsNydusBlob(desc ocispec.Descriptor) bool {
panic("not implemented")
}
func IsNydusBootstrap(desc ocispec.Descriptor) bool {
panic("not implemented")
}
func LayerConvertFunc(opt PackOption) converter.ConvertFunc {
panic("not implemented")
}
func ConvertHookFunc(opt MergeOption) converter.ConvertHookFunc {
panic("not implemented")
}
func MergeLayers(ctx context.Context, cs content.Store, descs []ocispec.Descriptor, opt MergeOption) (*ocispec.Descriptor, []ocispec.Descriptor, error) {
panic("not implemented")
}
================================================
FILE: pkg/converter/cs_proxy_unix.go
================================================
//go:build !windows
// +build !windows
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package converter
import (
"archive/tar"
"context"
"fmt"
"io"
"net"
"net/http"
"os"
"strconv"
"strings"
"github.com/containerd/containerd/v2/core/content"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
type contentStoreProxy struct {
socketPath string
server *http.Server
}
func setupContentStoreProxy(workDir string, ra content.ReaderAt) (*contentStoreProxy, error) {
sockP, err := os.CreateTemp(workDir, "nydus-cs-proxy-*.sock")
if err != nil {
return nil, errors.Wrap(err, "create unix socket file")
}
if err := os.Remove(sockP.Name()); err != nil {
return nil, err
}
listener, err := net.Listen("unix", sockP.Name())
if err != nil {
return nil, errors.Wrap(err, "listen unix socket when setup content store proxy")
}
server := &http.Server{
Handler: contentProxyHandler(ra),
}
go func() {
if err := server.Serve(listener); err != nil && err != http.ErrServerClosed {
logrus.WithError(err).Warn("serve content store proxy")
}
}()
return &contentStoreProxy{
socketPath: sockP.Name(),
server: server,
}, nil
}
func (p *contentStoreProxy) close() error {
defer os.Remove(p.socketPath)
if err := p.server.Shutdown(context.Background()); err != nil {
return errors.Wrap(err, "shutdown content store proxy")
}
return nil
}
func parseRangeHeader(rangeStr string, totalLen int64) (start, wantedLen int64, err error) {
rangeList := strings.Split(rangeStr, "-")
start, err = strconv.ParseInt(rangeList[0], 10, 64)
if err != nil {
err = errors.Wrap(err, "parse range header")
return
}
if len(rangeList) == 2 {
var end int64
end, err = strconv.ParseInt(rangeList[1], 10, 64)
if err != nil {
err = errors.Wrap(err, "parse range header")
return
}
wantedLen = end - start + 1
} else {
wantedLen = totalLen - start
}
if start < 0 || start >= totalLen || wantedLen <= 0 {
err = fmt.Errorf("invalid range header: %s", rangeStr)
return
}
return
}
func contentProxyHandler(ra content.ReaderAt) http.Handler {
var (
dataReader io.Reader
curPos int64
tarHeader *tar.Header
totalLen int64
)
resetReader := func() {
// TODO: Handle error?
_, _ = seekFile(ra, EntryBlob, func(reader io.Reader, hdr *tar.Header) error {
dataReader, tarHeader = reader, hdr
return nil
})
curPos = 0
}
resetReader()
if tarHeader != nil {
totalLen = tarHeader.Size
} else {
totalLen = ra.Size()
}
handler := func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodHead:
{
w.Header().Set("Content-Length", strconv.FormatInt(totalLen, 10))
w.Header().Set("Content-Type", "application/octet-stream")
return
}
case http.MethodGet:
{
start, wantedLen, err := parseRangeHeader(strings.TrimPrefix(r.Header.Get("Range"), "bytes="), totalLen)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
// TODO: Handle error?
_, _ = w.Write([]byte(err.Error()))
return
}
// we need to make sure that the dataReader is at the right position
if start < curPos {
resetReader()
}
if start > curPos {
_, err = io.CopyN(io.Discard, dataReader, start-curPos)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
// TODO: Handle error?
_, _ = w.Write([]byte(err.Error()))
return
}
curPos = start
}
// then, the curPos must be equal to start
readLen, err := io.CopyN(w, dataReader, wantedLen)
if err != nil && !errors.Is(err, io.EOF) {
w.WriteHeader(http.StatusInternalServerError)
// TODO: Handle error?
_, _ = w.Write([]byte(err.Error()))
return
}
curPos += readLen
w.Header().Set("Content-Length", strconv.FormatInt(readLen, 10))
w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, start+readLen-1, totalLen))
w.Header().Set("Content-Type", "application/octet-stream")
return
}
}
}
return http.HandlerFunc(handler)
}
================================================
FILE: pkg/converter/merge_unix_test.go
================================================
//go:build !windows
// +build !windows
/*
* Copyright (c) 2024. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package converter
import (
"testing"
"github.com/opencontainers/go-digest"
"github.com/stretchr/testify/assert"
)
// d returns a synthetic digest for testing by repeating a hex character.
func d(c byte) digest.Digest {
return digest.NewDigestFromEncoded(digest.SHA256, string(repeat(c, 64)))
}
func repeat(c byte, n int) []byte {
b := make([]byte, n)
for i := range b {
b[i] = c
}
return b
}
// TestMergeManifestBlobDigests verifies the blob digest selection logic used when
// building the nydus manifest layer list.
func TestMergeManifestBlobDigests(t *testing.T) {
D0 := d('0')
D1 := d('1')
D2 := d('2')
D3 := d('3') // metadata-only layer (symlink-only, 0-byte blob data)
DICT := d('d')
tests := []struct {
name string
nydusBlobDigests []digest.Digest
originalBlobDigests []digest.Digest
expected []digest.Digest
}{
{
// A metadata-only layer (e.g. symlink-only OCI layer) produces an empty
// blob inside nydus-image merge, so it is absent from originalBlobDigests.
// mergeManifestBlobDigests must preserve it from nydusBlobDigests so that
// reverse conversion (nydus→OCI) reproduces the correct number of layers.
name: "metadata-only layer preserved from nydusBlobDigests",
nydusBlobDigests: []digest.Digest{D0, D1, D2, D3},
originalBlobDigests: []digest.Digest{D2, D0, D1}, // BFS order, D3 missing
expected: []digest.Digest{D0, D1, D2, D3},
},
{
// When a chunk-dict is used, nydus-image merge adds the dict blob to its
// output blob table. That dict blob is not a regular OCI layer, so it does
// not appear in nydusBlobDigests and must be appended at the end.
name: "chunk-dict blob appended after layer blobs",
nydusBlobDigests: []digest.Digest{D0, D1, D2, D3},
originalBlobDigests: []digest.Digest{D0, D1, D2, DICT},
expected: []digest.Digest{D0, D1, D2, D3, DICT},
},
{
// When chunk-dict is used and layers are also reordered by BFS, both the
// OCI layer order must be preserved and the dict blob must be appended.
name: "chunk-dict blob appended when originalBlobDigests uses BFS order",
nydusBlobDigests: []digest.Digest{D0, D1, D2, D3},
originalBlobDigests: []digest.Digest{D2, D0, D1, DICT}, // BFS order + dict, D3 missing
expected: []digest.Digest{D0, D1, D2, D3, DICT},
},
{
// When all layers have chunk data, originalBlobDigests covers all of
// nydusBlobDigests (possibly in different BFS order). The result must equal
// nydusBlobDigests in OCI layer order without duplicates.
name: "all layers have data, OCI order preserved",
nydusBlobDigests: []digest.Digest{D0, D1, D2},
originalBlobDigests: []digest.Digest{D2, D0, D1}, // BFS order
expected: []digest.Digest{D0, D1, D2},
},
{
name: "empty inputs produce empty result",
nydusBlobDigests: []digest.Digest{},
originalBlobDigests: []digest.Digest{},
expected: []digest.Digest{},
},
{
name: "single metadata-only layer",
nydusBlobDigests: []digest.Digest{D0},
originalBlobDigests: []digest.Digest{},
expected: []digest.Digest{D0},
},
{
// Multiple chunk-dict blobs (edge case; not typical but must not be dropped).
name: "multiple chunk-dict blobs appended",
nydusBlobDigests: []digest.Digest{D0, D1},
originalBlobDigests: []digest.Digest{D0, D1, D2, D3},
expected: []digest.Digest{D0, D1, D2, D3},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := mergeManifestBlobDigests(tc.nydusBlobDigests, tc.originalBlobDigests)
assert.Equal(t, tc.expected, got)
})
}
}
================================================
FILE: pkg/converter/reconvert_unix.go
================================================
//go:build !windows
// +build !windows
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package converter
import (
"compress/gzip"
"context"
"fmt"
"io"
"github.com/containerd/containerd/v2/core/content"
"github.com/containerd/containerd/v2/core/images"
"github.com/containerd/containerd/v2/core/images/converter"
"github.com/containerd/errdefs"
"github.com/containerd/platforms"
"github.com/klauspost/compress/zstd"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// DefaultIndexConvertFunc wraps containerd's converter to handle Nydus blob reconversion.
//
// Problem: Containerd's convertManifest calls images.GetDiffID() which attempts to
// decompress layers. Nydus blobs use a custom format and fail with "magic number mismatch".
//
// Solution: Wrap content store with a proxy that adds containerd.io/uncompressed label
// for Nydus blobs. This makes GetDiffID take the fast path and skip decompression.
func DefaultIndexConvertFunc(layerConvertFunc converter.ConvertFunc, docker2oci bool, platformMC platforms.MatchComparer) converter.ConvertFunc {
hooks := converter.ConvertHooks{
PostConvertHook: ReconvertHookFunc(),
}
fn := converter.IndexConvertFuncWithHook(layerConvertFunc, docker2oci, platformMC, hooks)
return func(ctx context.Context, cs content.Store, desc ocispec.Descriptor) (*ocispec.Descriptor, error) {
logrus.Debugf("DefaultIndexConvertFunc called for desc mediaType=%s digest=%s", desc.MediaType, desc.Digest.String())
nydusBlobs := collectNydusBlobDigests(ctx, cs, desc)
if len(nydusBlobs) == 0 {
return fn(ctx, cs, desc)
}
ws := &wrappedStore{
Store: cs,
nydusBlobs: nydusBlobs,
}
return fn(ctx, ws, desc)
}
}
// This is used to identify which blobs need the uncompressed label workaround.
func collectNydusBlobDigests(ctx context.Context, cs content.Store, desc ocispec.Descriptor) map[digest.Digest]bool {
nydusBlobs := make(map[digest.Digest]bool)
if images.IsIndexType(desc.MediaType) {
var index ocispec.Index
if _, err := readJSON(ctx, cs, &index, desc); err != nil {
logrus.WithError(err).Warn("failed to read index")
return nydusBlobs
}
for _, m := range index.Manifests {
if images.IsManifestType(m.MediaType) {
collectFromManifest(ctx, cs, m, nydusBlobs)
}
}
} else if images.IsManifestType(desc.MediaType) {
collectFromManifest(ctx, cs, desc, nydusBlobs)
}
logrus.Debugf("Collected %d Nydus blob digests", len(nydusBlobs))
return nydusBlobs
}
func collectFromManifest(ctx context.Context, cs content.Store, desc ocispec.Descriptor, nydusBlobs map[digest.Digest]bool) {
var manifest ocispec.Manifest
if _, err := readJSON(ctx, cs, &manifest, desc); err != nil {
logrus.WithError(err).Warnf("failed to read manifest %s", desc.Digest)
return
}
for _, l := range manifest.Layers {
if IsNydusBlob(l) {
logrus.Debugf("Found Nydus blob: %s (mediaType=%s)", l.Digest, l.MediaType)
nydusBlobs[l.Digest] = true
}
}
}
// wrappedStore wraps the content store to add containerd.io/uncompressed labels
// for Nydus blobs. This makes images.GetDiffID skip decompression and return
// the digest directly.
type wrappedStore struct {
content.Store
nydusBlobs map[digest.Digest]bool // Set of Nydus blob digests
}
func (s *wrappedStore) Info(ctx context.Context, dgst digest.Digest) (content.Info, error) {
info, err := s.Store.Info(ctx, dgst)
if err != nil {
return info, err
}
// If this is a Nydus blob, add the uncompressed label
if s.nydusBlobs[dgst] {
if info.Labels == nil {
info.Labels = make(map[string]string)
}
// Use the blob's own digest as the "uncompressed" digest
// This makes GetDiffID return the blob digest directly
info.Labels["containerd.io/uncompressed"] = dgst.String()
logrus.Debugf("Added uncompressed label for Nydus blob: %s", dgst)
}
return info, nil
}
func ReconvertHookFunc() converter.ConvertHookFunc {
return func(ctx context.Context, cs content.Store, _ ocispec.Descriptor, newDesc *ocispec.Descriptor) (*ocispec.Descriptor, error) {
if newDesc == nil {
return nil, nil
}
if !images.IsManifestType(newDesc.MediaType) {
return newDesc, nil
}
var manifest ocispec.Manifest
labels, err := readJSON(ctx, cs, &manifest, *newDesc)
if err != nil {
return nil, errors.Wrap(err, "read manifest")
}
var layersToKeep []ocispec.Descriptor
bootstrapIndex := -1
// 1. Filter Layers: Remove Nydus Bootstrap Layer
for i, l := range manifest.Layers {
if IsNydusBootstrap(l) {
bootstrapIndex = i
// Clean GC labels for the removed layer
converter.ClearGCLabels(labels, l.Digest)
} else {
layersToKeep = append(layersToKeep, l)
}
}
manifest.Layers = layersToKeep
// 2. Read and Update Config
var config ocispec.Image
configLabels, err := readJSON(ctx, cs, &config, manifest.Config)
if err != nil {
return nil, errors.Wrap(err, "read image config")
}
// 2.1 Remove corresponding DiffID
if bootstrapIndex != -1 && len(config.RootFS.DiffIDs) > bootstrapIndex {
config.RootFS.DiffIDs = append(config.RootFS.DiffIDs[:bootstrapIndex], config.RootFS.DiffIDs[bootstrapIndex+1:]...)
}
// 2.2 Clean History
var newHistory []ocispec.History
for _, h := range config.History {
// Remove Nydus Bootstrap History
if h.Comment == "Nydus Bootstrap Layer" && h.CreatedBy == "Nydus Converter" {
continue
}
newHistory = append(newHistory, h)
}
config.History = newHistory
// 3. Write back Config
newConfigDesc, err := writeJSON(ctx, cs, config, manifest.Config, configLabels)
if err != nil {
return nil, errors.Wrap(err, "write image config")
}
manifest.Config = *newConfigDesc
// Update Manifest GC label for config
labels["containerd.io/gc.ref.content.config"] = newConfigDesc.Digest.String()
// 4. Write back Manifest
return writeJSON(ctx, cs, manifest, *newDesc, labels)
}
}
func LayerReconvertFunc(opt UnpackOption) converter.ConvertFunc {
return func(ctx context.Context, cs content.Store, desc ocispec.Descriptor) (*ocispec.Descriptor, error) {
logrus.Debugf("Reconvert layer: mediaType=%s digest=%s, anno=%v", desc.MediaType, desc.Digest.String(), desc.Annotations)
if !images.IsLayerType(desc.MediaType) {
return nil, nil
}
// Skip the nydus bootstrap layer.
if IsNydusBootstrap(desc) {
logrus.Debugf("skip nydus bootstrap layer %s", desc.Digest.String())
return &desc, nil
}
ra, err := cs.ReaderAt(ctx, desc)
if err != nil {
return nil, errors.Wrap(err, "get reader")
}
defer ra.Close()
ref := fmt.Sprintf("convert-oci-from-%s", desc.Digest)
cw, err := content.OpenWriter(ctx, cs, content.WithRef(ref))
if err != nil {
return nil, errors.Wrap(err, "open blob writer")
}
defer cw.Close()
var gw io.WriteCloser
var mediaType string
compressor := opt.Compressor
if compressor == "" {
compressor = "gzip"
}
switch compressor {
case "gzip":
gw = gzip.NewWriter(cw)
mediaType = ocispec.MediaTypeImageLayerGzip
case "zstd":
gw, err = zstd.NewWriter(cw)
if err != nil {
return nil, errors.Wrap(err, "create zstd writer")
}
mediaType = ocispec.MediaTypeImageLayerZstd
case "uncompressed":
gw = cw
mediaType = ocispec.MediaTypeImageLayer
default:
return nil, errors.Errorf("unsupported compressor type: %s (support: gzip, zstd, uncompressed)", opt.Compressor)
}
uncompressedDgster := digest.SHA256.Digester()
pr, pw := io.Pipe()
// Unpack nydus blob to pipe writer in background
go func() {
defer pw.Close()
if err := Unpack(ctx, ra, pw, opt); err != nil {
pw.CloseWithError(errors.Wrap(err, "unpack nydus to tar"))
}
}()
// Stream data from pipe reader to compressed writer and digester
compressed := io.MultiWriter(gw, uncompressedDgster.Hash())
buffer := bufPool.Get().(*[]byte)
defer bufPool.Put(buffer)
if _, err = io.CopyBuffer(compressed, pr, *buffer); err != nil {
return nil, errors.Wrapf(err, "copy to compressed writer")
}
// Close compressor writer if different from content writer
if gw != cw {
if err = gw.Close(); err != nil {
return nil, errors.Wrap(err, "close compressor writer")
}
}
uncompressedDigest := uncompressedDgster.Digest()
compressedDgst := cw.Digest()
if err = cw.Commit(ctx, 0, compressedDgst, content.WithLabels(map[string]string{
LayerAnnotationUncompressed: uncompressedDigest.String(),
})); err != nil {
if !errdefs.IsAlreadyExists(err) {
return nil, errors.Wrap(err, "commit to content store")
}
}
if err = cw.Close(); err != nil {
return nil, errors.Wrap(err, "close content store writer")
}
newDesc, err := makeOCIBlobDesc(ctx, cs, uncompressedDigest, compressedDgst, mediaType)
if err != nil {
return nil, err
}
if opt.Backend != nil {
if err := opt.Backend.Push(ctx, cs, *newDesc); err != nil {
return nil, errors.Wrap(err, "push to storage backend")
}
}
return newDesc, nil
}
}
func makeOCIBlobDesc(ctx context.Context, cs content.Store, uncompressedDigest, targetDigest digest.Digest, mediaType string) (*ocispec.Descriptor, error) {
targetInfo, err := cs.Info(ctx, targetDigest)
if err != nil {
return nil, errors.Wrapf(err, "get target blob info %s", targetDigest)
}
if targetInfo.Labels == nil {
targetInfo.Labels = map[string]string{}
}
targetDesc := ocispec.Descriptor{
Digest: targetDigest,
Size: targetInfo.Size,
MediaType: mediaType,
Annotations: map[string]string{
// Use `containerd.io/uncompressed` to generate DiffID of
// layer defined in OCI spec.
LayerAnnotationUncompressed: uncompressedDigest.String(),
},
}
return &targetDesc, nil
}
================================================
FILE: pkg/converter/reconvert_unix_test.go
================================================
//go:build !windows
// +build !windows
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package converter
import (
"context"
"encoding/json"
"testing"
"github.com/containerd/containerd/v2/core/content"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// mockContentStore implements content.Store interface for testing
type mockContentStore struct {
content.Store
infos map[digest.Digest]content.Info
blobs map[digest.Digest][]byte
}
func newMockContentStore() *mockContentStore {
return &mockContentStore{
infos: make(map[digest.Digest]content.Info),
blobs: make(map[digest.Digest][]byte),
}
}
func (m *mockContentStore) Info(_ context.Context, dgst digest.Digest) (content.Info, error) {
if info, ok := m.infos[dgst]; ok {
return info, nil
}
return content.Info{}, ErrNotFound
}
func (m *mockContentStore) addBlob(dgst digest.Digest, data []byte, labels map[string]string) {
m.infos[dgst] = content.Info{
Digest: dgst,
Size: int64(len(data)),
Labels: labels,
}
m.blobs[dgst] = data
}
func (m *mockContentStore) ReaderAt(_ context.Context, desc ocispec.Descriptor) (content.ReaderAt, error) {
if data, ok := m.blobs[desc.Digest]; ok {
return &mockReaderAt{data: data}, nil
}
return nil, ErrNotFound
}
type mockReaderAt struct {
data []byte
}
func (r *mockReaderAt) ReadAt(p []byte, off int64) (n int, err error) {
if off >= int64(len(r.data)) {
return 0, ErrNotFound
}
n = copy(p, r.data[off:])
return n, nil
}
func (r *mockReaderAt) Size() int64 {
return int64(len(r.data))
}
func (r *mockReaderAt) Close() error {
return nil
}
func TestCollectNydusBlobDigests(t *testing.T) {
ctx := context.Background()
// Create test digests
nydusBlob1 := digest.FromString("nydus-blob-1")
nydusBlob2 := digest.FromString("nydus-blob-2")
regularBlob := digest.FromString("regular-blob")
manifestDigest := digest.FromString("manifest")
indexDigest := digest.FromString("index")
// Test cases
tests := []struct {
name string
setupStore func(*mockContentStore)
desc ocispec.Descriptor
expectedBlobs map[digest.Digest]bool
expectedCount int
}{
{
name: "manifest with nydus blobs",
setupStore: func(cs *mockContentStore) {
manifest := ocispec.Manifest{
Layers: []ocispec.Descriptor{
{
MediaType: MediaTypeNydusBlob,
Digest: nydusBlob1,
Annotations: map[string]string{
LayerAnnotationNydusBlob: "true",
},
},
{
MediaType: ocispec.MediaTypeImageLayerGzip,
Digest: regularBlob,
},
},
}
data, _ := json.Marshal(manifest)
cs.addBlob(manifestDigest, data, nil)
},
desc: ocispec.Descriptor{
MediaType: ocispec.MediaTypeImageManifest,
Digest: manifestDigest,
},
expectedCount: 1,
},
{
name: "index with multiple manifests",
setupStore: func(cs *mockContentStore) {
// Create two manifests
manifest1 := ocispec.Manifest{
Layers: []ocispec.Descriptor{
{
MediaType: MediaTypeNydusBlob,
Digest: nydusBlob1,
Annotations: map[string]string{
LayerAnnotationNydusBlob: "true",
},
},
},
}
manifest1Data, _ := json.Marshal(manifest1)
manifest1Digest := digest.FromBytes(manifest1Data)
cs.addBlob(manifest1Digest, manifest1Data, nil)
manifest2 := ocispec.Manifest{
Layers: []ocispec.Descriptor{
{
MediaType: MediaTypeNydusBlob,
Digest: nydusBlob2,
Annotations: map[string]string{
LayerAnnotationNydusBlob: "true",
},
},
},
}
manifest2Data, _ := json.Marshal(manifest2)
manifest2Digest := digest.FromBytes(manifest2Data)
cs.addBlob(manifest2Digest, manifest2Data, nil)
// Create index
index := ocispec.Index{
Manifests: []ocispec.Descriptor{
{
MediaType: ocispec.MediaTypeImageManifest,
Digest: manifest1Digest,
},
{
MediaType: ocispec.MediaTypeImageManifest,
Digest: manifest2Digest,
},
},
}
indexData, _ := json.Marshal(index)
cs.addBlob(indexDigest, indexData, nil)
},
desc: ocispec.Descriptor{
MediaType: ocispec.MediaTypeImageIndex,
Digest: indexDigest,
},
expectedCount: 2,
},
{
name: "manifest with no nydus blobs",
setupStore: func(cs *mockContentStore) {
manifest := ocispec.Manifest{
Layers: []ocispec.Descriptor{
{
MediaType: ocispec.MediaTypeImageLayerGzip,
Digest: regularBlob,
},
},
}
data, _ := json.Marshal(manifest)
cs.addBlob(manifestDigest, data, nil)
},
desc: ocispec.Descriptor{
MediaType: ocispec.MediaTypeImageManifest,
Digest: manifestDigest,
},
expectedCount: 0,
},
{
name: "invalid descriptor type",
setupStore: func(_ *mockContentStore) {
// No setup needed
},
desc: ocispec.Descriptor{
MediaType: ocispec.MediaTypeImageConfig,
Digest: digest.FromString("config"),
},
expectedCount: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cs := newMockContentStore()
tt.setupStore(cs)
blobs := collectNydusBlobDigests(ctx, cs, tt.desc)
assert.Equal(t, tt.expectedCount, len(blobs))
})
}
}
func TestCollectFromManifest(t *testing.T) {
ctx := context.Background()
nydusBlob1 := digest.FromString("nydus-blob-1")
nydusBlob2 := digest.FromString("nydus-blob-2")
bootstrapBlob := digest.FromString("bootstrap-blob")
regularBlob := digest.FromString("regular-blob")
manifestDigest := digest.FromString("manifest")
tests := []struct {
name string
setupManifest func() (ocispec.Manifest, []byte)
expectedCount int
expectedBlobs []digest.Digest
}{
{
name: "manifest with multiple nydus blobs and bootstrap",
setupManifest: func() (ocispec.Manifest, []byte) {
manifest := ocispec.Manifest{
Layers: []ocispec.Descriptor{
{
MediaType: MediaTypeNydusBlob,
Digest: nydusBlob1,
Annotations: map[string]string{
LayerAnnotationNydusBlob: "true",
},
},
{
MediaType: MediaTypeNydusBlob,
Digest: nydusBlob2,
Annotations: map[string]string{
LayerAnnotationNydusBlob: "true",
},
},
{
MediaType: ocispec.MediaTypeImageLayer,
Digest: bootstrapBlob,
Annotations: map[string]string{
LayerAnnotationNydusBootstrap: "true",
},
},
{
MediaType: ocispec.MediaTypeImageLayerGzip,
Digest: regularBlob,
},
},
}
data, _ := json.Marshal(manifest)
return manifest, data
},
expectedCount: 2,
expectedBlobs: []digest.Digest{nydusBlob1, nydusBlob2},
},
{
name: "manifest with only regular layers",
setupManifest: func() (ocispec.Manifest, []byte) {
manifest := ocispec.Manifest{
Layers: []ocispec.Descriptor{
{
MediaType: ocispec.MediaTypeImageLayerGzip,
Digest: regularBlob,
},
},
}
data, _ := json.Marshal(manifest)
return manifest, data
},
expectedCount: 0,
expectedBlobs: []digest.Digest{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cs := newMockContentStore()
_, manifestData := tt.setupManifest()
cs.addBlob(manifestDigest, manifestData, nil)
nydusBlobs := make(map[digest.Digest]bool)
desc := ocispec.Descriptor{
MediaType: ocispec.MediaTypeImageManifest,
Digest: manifestDigest,
}
collectFromManifest(ctx, cs, desc, nydusBlobs)
assert.Equal(t, tt.expectedCount, len(nydusBlobs))
for _, expectedBlob := range tt.expectedBlobs {
assert.True(t, nydusBlobs[expectedBlob], "expected blob %s not found", expectedBlob)
}
})
}
}
func TestWrappedStore_Info(t *testing.T) {
ctx := context.Background()
nydusBlob := digest.FromString("nydus-blob")
regularBlob := digest.FromString("regular-blob")
tests := []struct {
name string
digest digest.Digest
isNydusBlob bool
existingLabels map[string]string
shouldAddLabel bool
expectedLabelVal string
}{
{
name: "nydus blob without existing labels",
digest: nydusBlob,
isNydusBlob: true,
existingLabels: nil,
shouldAddLabel: true,
expectedLabelVal: nydusBlob.String(),
},
{
name: "nydus blob with existing labels",
digest: nydusBlob,
isNydusBlob: true,
existingLabels: map[string]string{
"other.label": "value",
},
shouldAddLabel: true,
expectedLabelVal: nydusBlob.String(),
},
{
name: "regular blob",
digest: regularBlob,
isNydusBlob: false,
existingLabels: map[string]string{
"other.label": "value",
},
shouldAddLabel: false,
expectedLabelVal: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cs := newMockContentStore()
cs.addBlob(tt.digest, []byte("test data"), tt.existingLabels)
nydusBlobs := make(map[digest.Digest]bool)
if tt.isNydusBlob {
nydusBlobs[tt.digest] = true
}
ws := &wrappedStore{
Store: cs,
nydusBlobs: nydusBlobs,
}
info, err := ws.Info(ctx, tt.digest)
require.NoError(t, err)
assert.Equal(t, tt.digest, info.Digest)
if tt.shouldAddLabel {
assert.NotNil(t, info.Labels)
assert.Equal(t, tt.expectedLabelVal, info.Labels["containerd.io/uncompressed"])
} else if info.Labels != nil {
assert.Empty(t, info.Labels["containerd.io/uncompressed"])
}
})
}
}
func TestWrappedStore_Info_Error(t *testing.T) {
ctx := context.Background()
cs := newMockContentStore()
ws := &wrappedStore{
Store: cs,
nydusBlobs: make(map[digest.Digest]bool),
}
nonExistentDigest := digest.FromString("non-existent")
_, err := ws.Info(ctx, nonExistentDigest)
assert.Error(t, err)
assert.Equal(t, ErrNotFound, err)
}
func TestMakeOCIBlobDesc(t *testing.T) {
ctx := context.Background()
uncompressedDigest := digest.FromString("uncompressed-data")
targetDigest := digest.FromString("compressed-data")
tests := []struct {
name string
uncompressedDgst digest.Digest
targetDgst digest.Digest
mediaType string
existingLabels map[string]string
expectedMediaType string
shouldFail bool
}{
{
name: "gzip layer",
uncompressedDgst: uncompressedDigest,
targetDgst: targetDigest,
mediaType: ocispec.MediaTypeImageLayerGzip,
existingLabels: nil,
expectedMediaType: ocispec.MediaTypeImageLayerGzip,
shouldFail: false,
},
{
name: "zstd layer",
uncompressedDgst: uncompressedDigest,
targetDgst: targetDigest,
mediaType: ocispec.MediaTypeImageLayerZstd,
existingLabels: nil,
expectedMediaType: ocispec.MediaTypeImageLayerZstd,
shouldFail: false,
},
{
name: "uncompressed layer",
uncompressedDgst: uncompressedDigest,
targetDgst: targetDigest,
mediaType: ocispec.MediaTypeImageLayer,
existingLabels: nil,
expectedMediaType: ocispec.MediaTypeImageLayer,
shouldFail: false,
},
{
name: "non-existent target",
uncompressedDgst: uncompressedDigest,
targetDgst: digest.FromString("non-existent"),
mediaType: ocispec.MediaTypeImageLayerGzip,
shouldFail: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cs := newMockContentStore()
if !tt.shouldFail {
cs.addBlob(tt.targetDgst, []byte("compressed data"), tt.existingLabels)
}
desc, err := makeOCIBlobDesc(ctx, cs, tt.uncompressedDgst, tt.targetDgst, tt.mediaType)
if tt.shouldFail {
assert.Error(t, err)
assert.Nil(t, desc)
} else {
require.NoError(t, err)
require.NotNil(t, desc)
assert.Equal(t, tt.targetDgst, desc.Digest)
assert.Equal(t, tt.expectedMediaType, desc.MediaType)
assert.Equal(t, int64(len("compressed data")), desc.Size)
assert.NotNil(t, desc.Annotations)
assert.Equal(t, tt.uncompressedDgst.String(), desc.Annotations[LayerAnnotationUncompressed])
}
})
}
}
func TestReconvertHookFunc_NilDescriptor(t *testing.T) {
ctx := context.Background()
cs := newMockContentStore()
hook := ReconvertHookFunc()
result, err := hook(ctx, cs, ocispec.Descriptor{}, nil)
assert.NoError(t, err)
assert.Nil(t, result)
}
func TestReconvertHookFunc_NonManifestType(t *testing.T) {
ctx := context.Background()
cs := newMockContentStore()
desc := ocispec.Descriptor{
MediaType: ocispec.MediaTypeImageConfig,
Digest: digest.FromString("config"),
}
hook := ReconvertHookFunc()
result, err := hook(ctx, cs, ocispec.Descriptor{}, &desc)
assert.NoError(t, err)
assert.Equal(t, &desc, result)
}
================================================
FILE: pkg/converter/tool/builder.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package tool
import (
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"strings"
"time"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
var logger = logrus.WithField("module", "builder")
func isSignalKilled(err error) bool {
return strings.Contains(err.Error(), "signal: killed")
}
type PackOption struct {
BuilderPath string
BootstrapPath string
BlobPath string
FsVersion string
SourcePath string
ChunkDictPath string
PrefetchPatterns string
Compressor string
OCIRef bool
AlignedChunk bool
ChunkSize string
BatchSize string
Encrypt bool
Timeout *time.Duration
Features Features
}
type MergeOption struct {
BuilderPath string
SourceBootstrapPaths []string
RafsBlobDigests []string
RafsBlobTOCDigests []string
RafsBlobSizes []int64
TargetBootstrapPath string
ChunkDictPath string
ParentBootstrapPath string
PrefetchPatterns string
OutputJSONPath string
Timeout *time.Duration
}
type UnpackOption struct {
BuilderPath string
BootstrapPath string
BlobPath string
BackendConfigPath string
TarPath string
Timeout *time.Duration
}
type outputJSON struct {
Blobs []string
}
func buildPackArgs(option PackOption) []string {
if option.FsVersion == "" {
option.FsVersion = "6"
}
args := []string{
"create",
"--log-level",
"warn",
"--prefetch-policy",
"fs",
"--blob",
option.BlobPath,
"--whiteout-spec",
"none",
"--fs-version",
option.FsVersion,
}
if option.Features.Contains(FeatureTar2Rafs) {
args = append(
args,
"--type",
"tar-rafs",
"--blob-inline-meta",
)
if option.FsVersion == "6" {
args = append(
args,
"--features",
"blob-toc",
)
}
} else {
args = append(
args,
"--source-type",
"directory",
// Sames with `--blob-inline-meta`, it's used for compatibility
// with the old nydus-image builder.
"--inline-bootstrap",
)
}
if option.ChunkDictPath != "" {
args = append(args, "--chunk-dict", fmt.Sprintf("bootstrap=%s", option.ChunkDictPath))
}
if option.PrefetchPatterns == "" {
option.PrefetchPatterns = "/"
}
if option.Compressor != "" {
args = append(args, "--compressor", option.Compressor)
}
if option.AlignedChunk {
args = append(args, "--aligned-chunk")
}
if option.ChunkSize != "" {
args = append(args, "--chunk-size", option.ChunkSize)
}
if option.Features.Contains(FeatureBatchSize) {
args = append(args, "--batch-size", option.BatchSize)
}
if option.Encrypt {
args = append(args, "--encrypt")
}
args = append(args, option.SourcePath)
return args
}
func Pack(option PackOption) error {
if option.OCIRef {
return packRef(option)
}
ctx := context.Background()
var cancel context.CancelFunc
if option.Timeout != nil {
ctx, cancel = context.WithTimeout(ctx, *option.Timeout)
defer cancel()
}
args := buildPackArgs(option)
logrus.Debugf("\tCommand: %s %s", option.BuilderPath, strings.Join(args, " "))
cmd := exec.CommandContext(ctx, option.BuilderPath, args...)
cmd.Stdout = logger.Writer()
cmd.Stderr = logger.Writer()
cmd.Stdin = strings.NewReader(option.PrefetchPatterns)
if err := cmd.Run(); err != nil {
if isSignalKilled(err) && option.Timeout != nil {
logrus.WithError(err).Errorf("fail to run %v %+v, possibly due to timeout %v", option.BuilderPath, args, *option.Timeout)
} else {
logrus.WithError(err).Errorf("fail to run %v %+v", option.BuilderPath, args)
}
return err
}
return nil
}
func packRef(option PackOption) error {
args := []string{
"create",
"--log-level",
"warn",
"--type",
"targz-ref",
"--blob-inline-meta",
"--features",
"blob-toc",
"--blob",
option.BlobPath,
}
args = append(args, option.SourcePath)
ctx := context.Background()
var cancel context.CancelFunc
if option.Timeout != nil {
ctx, cancel = context.WithTimeout(ctx, *option.Timeout)
defer cancel()
}
logrus.Debugf("\tCommand: %s %s", option.BuilderPath, strings.Join(args, " "))
cmd := exec.CommandContext(ctx, option.BuilderPath, args...)
cmd.Stdout = logger.Writer()
cmd.Stderr = logger.Writer()
if err := cmd.Run(); err != nil {
if isSignalKilled(err) && option.Timeout != nil {
logrus.WithError(err).Errorf("fail to run %v %+v, possibly due to timeout %v", option.BuilderPath, args, *option.Timeout)
} else {
logrus.WithError(err).Errorf("fail to run %v %+v", option.BuilderPath, args)
}
return err
}
return nil
}
func Merge(option MergeOption) ([]digest.Digest, error) {
args := []string{
"merge",
"--log-level",
"warn",
"--prefetch-policy",
"fs",
"--output-json",
option.OutputJSONPath,
"--bootstrap",
option.TargetBootstrapPath,
}
if option.ChunkDictPath != "" {
args = append(args, "--chunk-dict", fmt.Sprintf("bootstrap=%s", option.ChunkDictPath))
}
if option.ParentBootstrapPath != "" {
args = append(args, "--parent-bootstrap", option.ParentBootstrapPath)
}
if option.PrefetchPatterns == "" {
option.PrefetchPatterns = "/"
}
args = append(args, option.SourceBootstrapPaths...)
if len(option.RafsBlobDigests) > 0 {
args = append(args, "--blob-digests", strings.Join(option.RafsBlobDigests, ","))
}
if len(option.RafsBlobTOCDigests) > 0 {
args = append(args, "--blob-toc-digests", strings.Join(option.RafsBlobTOCDigests, ","))
}
if len(option.RafsBlobSizes) > 0 {
sizes := []string{}
for _, size := range option.RafsBlobSizes {
sizes = append(sizes, fmt.Sprintf("%d", size))
}
args = append(args, "--blob-sizes", strings.Join(sizes, ","))
}
ctx := context.Background()
var cancel context.CancelFunc
if option.Timeout != nil {
ctx, cancel = context.WithTimeout(ctx, *option.Timeout)
defer cancel()
}
logrus.Debugf("\tCommand: %s %s", option.BuilderPath, strings.Join(args, " "))
cmd := exec.CommandContext(ctx, option.BuilderPath, args...)
cmd.Stdout = logger.Writer()
cmd.Stderr = logger.Writer()
cmd.Stdin = strings.NewReader(option.PrefetchPatterns)
if err := cmd.Run(); err != nil {
if isSignalKilled(err) && option.Timeout != nil {
logrus.WithError(err).Errorf("fail to run %v %+v, possibly due to timeout %v", option.BuilderPath, args, *option.Timeout)
} else {
logrus.WithError(err).Errorf("fail to run %v %+v", option.BuilderPath, args)
}
return nil, errors.Wrap(err, "run merge command")
}
outputBytes, err := os.ReadFile(option.OutputJSONPath)
if err != nil {
return nil, errors.Wrapf(err, "read file %s", option.OutputJSONPath)
}
var output outputJSON
err = json.Unmarshal(outputBytes, &output)
if err != nil {
return nil, errors.Wrapf(err, "unmarshal output json file %s", option.OutputJSONPath)
}
blobDigests := []digest.Digest{}
for _, blobID := range output.Blobs {
blobDigests = append(blobDigests, digest.NewDigestFromHex(string(digest.SHA256), blobID))
}
return blobDigests, nil
}
func Unpack(option UnpackOption) error {
args := []string{
"unpack",
"--log-level",
"warn",
"--bootstrap",
option.BootstrapPath,
"--output",
option.TarPath,
}
if option.BackendConfigPath != "" {
configBytes, err := os.ReadFile(option.BackendConfigPath)
if err != nil {
return errors.Wrapf(err, "fail to read backend config file %s", option.BackendConfigPath)
}
var config map[string]interface{}
if err := json.Unmarshal(configBytes, &config); err != nil {
return errors.Wrapf(err, "fail to unmarshal backend config file %s", option.BackendConfigPath)
}
backendConfigType, ok := config["backend"].(map[string]interface{})["type"]
if !ok {
return errors.New("backend config file should contain a valid backend type")
}
backendConfig, ok := config["backend"].(map[string]interface{})[backendConfigType.(string)]
if !ok {
return errors.New("failed to get backend config with type " + backendConfigType.(string))
}
backendConfigBytes, err := json.Marshal(backendConfig)
if err != nil {
return errors.Wrapf(err, "fail to marshal backend config %v", backendConfig)
}
args = append(args, "--backend-type", backendConfigType.(string))
args = append(args, "--backend-config", string(backendConfigBytes))
} else if option.BlobPath != "" {
args = append(args, "--blob", option.BlobPath)
}
ctx := context.Background()
var cancel context.CancelFunc
if option.Timeout != nil {
ctx, cancel = context.WithTimeout(ctx, *option.Timeout)
defer cancel()
}
logrus.Debugf("\tCommand: %s %s", option.BuilderPath, strings.Join(args, " "))
cmd := exec.CommandContext(ctx, option.BuilderPath, args...)
cmd.Stdout = logger.Writer()
cmd.Stderr = logger.Writer()
if err := cmd.Run(); err != nil {
if isSignalKilled(err) && option.Timeout != nil {
logrus.WithError(err).Errorf("fail to run %v %+v, possibly due to timeout %v", option.BuilderPath, args, *option.Timeout)
} else {
logrus.WithError(err).Errorf("fail to run %v %+v", option.BuilderPath, args)
}
return err
}
return nil
}
================================================
FILE: pkg/converter/tool/feature.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package tool
import (
"context"
"fmt"
"os"
"os/exec"
"strings"
"sync"
"github.com/sirupsen/logrus"
)
type Feature string
type Features map[Feature]struct{}
const envNydusDisableTar2Rafs string = "NYDUS_DISABLE_TAR2RAFS"
const (
// The option `--type tar-rafs` enables converting OCI tar blob
// stream into nydus blob directly, the tar2rafs eliminates the
// need to decompress it to a local directory first, thus greatly
// accelerating the pack process.
FeatureTar2Rafs Feature = "--type tar-rafs"
// The option `--batch-size` enables merging multiple small chunks
// into a big batch chunk, which can reduce the the size of the image
// and accelerate the runtime file loading.
FeatureBatchSize Feature = "--batch-size"
// The option `--encrypt` enables converting directories, tar files
// or OCI images into encrypted nydus blob.
FeatureEncrypt Feature = "--encrypt"
)
var requiredFeatures Features
var detectedFeatures Features
var detectFeaturesOnce sync.Once
var disableTar2Rafs = os.Getenv(envNydusDisableTar2Rafs) != ""
func NewFeatures(items ...Feature) Features {
features := Features{}
features.Add(items...)
return features
}
func (features *Features) Add(items ...Feature) {
for _, item := range items {
(*features)[item] = struct{}{}
}
}
func (features *Features) Remove(items ...Feature) {
for _, item := range items {
delete(*features, item)
}
}
func (features *Features) Contains(feature Feature) bool {
_, ok := (*features)[feature]
return ok
}
func (features *Features) Equals(other Features) bool {
if len(*features) != len(other) {
return false
}
for f := range *features {
if !other.Contains(f) {
return false
}
}
return true
}
// GetHelp returns the help message of `nydus-image create`.
func GetHelp(builder string) []byte {
cmd := exec.CommandContext(context.Background(), builder, "create", "-h")
output, err := cmd.Output()
if err != nil {
return nil
}
return output
}
// detectFeature returns true if the feature is detected in the help message.
func detectFeature(msg []byte, feature Feature) bool {
if feature == "" {
return false
}
if strings.Contains(string(msg), string(feature)) {
return true
}
if parts := strings.Split(string(feature), " "); len(parts) == 2 {
// Check each part of the feature.
// e.g., "--type tar-rafs" -> ["--type", "tar-rafs"]
if strings.Contains(string(msg), parts[0]) && strings.Contains(string(msg), parts[1]) {
return true
}
}
return false
}
// DetectFeatures returns supported feature list from required feature list.
// The supported feature list is detected from the help message of `nydus-image create`.
func DetectFeatures(builder string, required Features, getHelp func(string) []byte) (Features, error) {
detectFeaturesOnce.Do(func() {
requiredFeatures = required
detectedFeatures = Features{}
helpMsg := getHelp(builder)
for feature := range required {
// The feature is supported by current version of nydus-image.
supported := detectFeature(helpMsg, feature)
if supported {
// It is an experimental feature, so we still provide an env
// variable to allow users to disable it.
if feature == FeatureTar2Rafs && disableTar2Rafs {
logrus.Warnf("the feature '%s' is disabled by env '%s'", FeatureTar2Rafs, envNydusDisableTar2Rafs)
continue
}
detectedFeatures.Add(feature)
} else {
logrus.Warnf("the feature '%s' is ignored, it requires higher version of nydus-image", feature)
}
}
})
// Return Error if required features changed in different calls.
if !requiredFeatures.Equals(required) {
return nil, fmt.Errorf("features changed: %v -> %v", requiredFeatures, required)
}
return detectedFeatures, nil
}
================================================
FILE: pkg/converter/tool/feature_test.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package tool
import (
"sync"
"testing"
"github.com/stretchr/testify/require"
)
func TestFeature(t *testing.T) {
testsAdd := []struct {
name string
features Features
items []Feature
expect Features
}{
{
name: "should successfully add items",
features: Features{FeatureBatchSize: {}},
items: []Feature{FeatureTar2Rafs},
expect: Features{FeatureTar2Rafs: {}, FeatureBatchSize: {}},
},
{
name: "should add nothing if duplicated",
features: Features{FeatureBatchSize: {}},
items: []Feature{FeatureBatchSize},
expect: Features{FeatureBatchSize: {}},
},
{
name: "add should accept nil",
features: Features{FeatureBatchSize: {}},
items: nil,
expect: Features{FeatureBatchSize: {}},
},
}
for _, tt := range testsAdd {
t.Run(tt.name, func(t *testing.T) {
tt.features.Add(tt.items...)
require.Equal(t, tt.expect, tt.features)
})
}
testsNew := []struct {
name string
items []Feature
expect Features
}{
{
name: "should successfully new Features",
items: []Feature{FeatureTar2Rafs, FeatureBatchSize},
expect: Features{FeatureTar2Rafs: {}, FeatureBatchSize: {}},
},
{
name: "should duplicate same items",
items: []Feature{FeatureBatchSize, FeatureBatchSize},
expect: Features{FeatureBatchSize: {}},
},
{
name: "New should accept nil",
items: nil,
expect: Features{},
},
}
for _, tt := range testsNew {
t.Run(tt.name, func(t *testing.T) {
features := NewFeatures(tt.items...)
require.Equal(t, tt.expect, features)
})
}
testsRemove := []struct {
name string
features Features
items []Feature
expect Features
}{
{
name: "should successfully remove items",
features: Features{FeatureBatchSize: {}, FeatureTar2Rafs: {}},
items: []Feature{FeatureTar2Rafs},
expect: Features{FeatureBatchSize: {}},
},
{
name: "should remove item iff exists",
features: Features{FeatureBatchSize: {}},
items: []Feature{FeatureBatchSize, FeatureTar2Rafs},
expect: Features{},
},
{
name: "Remove should accept nil",
features: Features{FeatureBatchSize: {}},
items: nil,
expect: Features{FeatureBatchSize: {}},
},
}
for _, tt := range testsRemove {
t.Run(tt.name, func(t *testing.T) {
tt.features.Remove(tt.items...)
require.Equal(t, tt.expect, tt.features)
})
}
testsContains := []struct {
name string
features Features
item Feature
expect bool
}{
{
name: "should return contains",
features: Features{FeatureBatchSize: {}, FeatureTar2Rafs: {}},
item: FeatureTar2Rafs,
expect: true,
},
{
name: "should return not contains",
features: Features{FeatureBatchSize: {}},
item: FeatureTar2Rafs,
expect: false,
},
{
name: "Contains should accept empty string",
features: Features{FeatureBatchSize: {}},
item: "",
expect: false,
},
}
for _, tt := range testsContains {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.expect, tt.features.Contains(tt.item))
})
}
testsEquals := []struct {
name string
features Features
other Features
expect bool
}{
{
name: "should successfully check equality",
features: Features{FeatureBatchSize: {}, FeatureTar2Rafs: {}},
other: Features{FeatureBatchSize: {}, FeatureTar2Rafs: {}},
expect: true,
},
{
name: "should successfully check inequality with different length",
features: Features{FeatureBatchSize: {}, FeatureTar2Rafs: {}},
other: Features{FeatureBatchSize: {}},
expect: false,
},
{
name: "should successfully check inequality with different items",
features: Features{FeatureTar2Rafs: {}},
other: Features{FeatureBatchSize: {}},
expect: false,
},
{
name: "should ignore order",
features: Features{FeatureBatchSize: {}, FeatureTar2Rafs: {}},
other: Features{FeatureTar2Rafs: {}, FeatureBatchSize: {}},
expect: true,
},
{
name: "Equals should accept nil",
features: Features{FeatureBatchSize: {}},
other: nil,
expect: false,
},
}
for _, tt := range testsEquals {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.expect, tt.features.Equals(tt.other))
})
}
}
func TestDetectFeature(t *testing.T) {
tests := []struct {
name string
feature Feature
helpMsg []byte
expect bool
}{
{
name: "'--type tar-rafs' is supported in v2.2.0-239-gf5c08fcf",
feature: FeatureTar2Rafs,
expect: true,
helpMsg: []byte(`
Create RAFS filesystems from directories, tar files or OCI images
Usage: nydus-image create [OPTIONS]
Arguments:
source from which to build the RAFS filesystem
Options:
-L, --log-file
Log file path
-t, --type
Conversion type: [default: dir-rafs] [possible values: directory, dir-rafs, estargz-rafs, estargz-ref, estargztoc-ref, tar-rafs, tar-tarfs, targz-rafs, targz-ref, stargz_index]
-B, --bootstrap
File path to save the generated RAFS metadata blob
-l, --log-level
Log level: [default: info] [possible values: trace, debug, info, warn, error]
-D, --blob-dir
Directory path to save generated RAFS metadata and data blobs
-b, --blob
File path to save the generated RAFS data blob
--blob-inline-meta
Inline RAFS metadata and blob metadata into the data blob
--blob-id
OSS object id for the generated RAFS data blob
--blob-data-size
Set data blob size for 'estargztoc-ref' conversion
--chunk-size
Set the size of data chunks, must be power of two and between 0x1000-0x1000000:
--batch-size
Set the batch size to merge small chunks, must be power of two, between 0x1000-0x1000000 or be zero: [default: 0]
--compressor
Algorithm to compress data chunks: [default: zstd] [possible values: none, lz4_block, zstd]
--digester
Algorithm to digest data chunks: [default: blake3] [possible values: blake3, sha256]
-C, --config
Configuration file for storage backend, cache and RAFS FUSE filesystem.
-v, --fs-version
Set RAFS format version number: [default: 6] [possible values: 5, 6]
--features
Enable/disable features [possible values: blob-toc]
--chunk-dict
File path of chunk dictionary for data deduplication
--parent-bootstrap
File path of the parent/referenced RAFS metadata blob (optional)
--aligned-chunk
Align uncompressed data chunks to 4K, only for RAFS V5
--repeatable
Generate reproducible RAFS metadata
--whiteout-spec
Set the type of whiteout specification: [default: oci] [possible values: oci, overlayfs, none]
--prefetch-policy
Set data prefetch policy [default: none] [possible values: fs, blob, none]
-J, --output-json
File path to save operation result in JSON format
-h, --help
Print help information
`),
},
{
name: "'--batch-size' is supported in v2.2.0-239-gf5c08fcf",
feature: FeatureBatchSize,
expect: true,
helpMsg: []byte(`
Create RAFS filesystems from directories, tar files or OCI images
Usage: nydus-image create [OPTIONS]
Arguments:
source from which to build the RAFS filesystem
Options:
-L, --log-file
Log file path
-t, --type
Conversion type: [default: dir-rafs] [possible values: directory, dir-rafs, estargz-rafs, estargz-ref, estargztoc-ref, tar-rafs, tar-tarfs, targz-rafs, targz-ref, stargz_index]
-B, --bootstrap
File path to save the generated RAFS metadata blob
-l, --log-level
Log level: [default: info] [possible values: trace, debug, info, warn, error]
-D, --blob-dir
Directory path to save generated RAFS metadata and data blobs
-b, --blob
File path to save the generated RAFS data blob
--blob-inline-meta
Inline RAFS metadata and blob metadata into the data blob
--blob-id
OSS object id for the generated RAFS data blob
--blob-data-size
Set data blob size for 'estargztoc-ref' conversion
--chunk-size
Set the size of data chunks, must be power of two and between 0x1000-0x1000000:
--batch-size
Set the batch size to merge small chunks, must be power of two, between 0x1000-0x1000000 or be zero: [default: 0]
--compressor
Algorithm to compress data chunks: [default: zstd] [possible values: none, lz4_block, zstd]
--digester
Algorithm to digest data chunks: [default: blake3] [possible values: blake3, sha256]
-C, --config
Configuration file for storage backend, cache and RAFS FUSE filesystem.
-v, --fs-version
Set RAFS format version number: [default: 6] [possible values: 5, 6]
--features
Enable/disable features [possible values: blob-toc]
--chunk-dict
File path of chunk dictionary for data deduplication
--parent-bootstrap
File path of the parent/referenced RAFS metadata blob (optional)
--aligned-chunk
Align uncompressed data chunks to 4K, only for RAFS V5
--repeatable
Generate reproducible RAFS metadata
--whiteout-spec
Set the type of whiteout specification: [default: oci] [possible values: oci, overlayfs, none]
--prefetch-policy
Set data prefetch policy [default: none] [possible values: fs, blob, none]
-J, --output-json
File path to save operation result in JSON format
-h, --help
Print help information
`),
},
{
name: "'--batch-size' is not supported in v2.2.0-163-g180f6d2c",
feature: FeatureBatchSize,
expect: false,
helpMsg: []byte(`
Create RAFS filesystems from directories, tar files or OCI images
Usage: nydus-image create [OPTIONS]
Arguments:
source from which to build the RAFS filesystem
Options:
-L, --log-file
Log file path
-t, --type
Conversion type: [default: dir-rafs] [possible values: directory, dir-rafs, estargz-rafs, estargz-ref, estargztoc-ref, tar-rafs, tar-tarfs, targz-rafs, targz-ref, stargz_index]
-B, --bootstrap
File path to save the generated RAFS metadata blob
-l, --log-level
Log level: [default: info] [possible values: trace, debug, info, warn, error]
-D, --blob-dir
Directory path to save generated RAFS metadata and data blobs
-b, --blob
File path to save the generated RAFS data blob
--blob-inline-meta
Inline RAFS metadata and blob metadata into the data blob
--blob-id
OSS object id for the generated RAFS data blob
--blob-data-size
Set data blob size for 'estargztoc-ref' conversion
--chunk-size
Set the size of data chunks, must be power of two and between 0x1000-0x1000000:
--compressor
Algorithm to compress data chunks: [default: zstd] [possible values: none, lz4_block, zstd]
--digester
Algorithm to digest data chunks: [default: blake3] [possible values: blake3, sha256]
-C, --config
Configuration file for storage backend, cache and RAFS FUSE filesystem.
-v, --fs-version
Set RAFS format version number: [default: 6] [possible values: 5, 6]
--features
Enable/disable features [possible values: blob-toc]
--chunk-dict
File path of chunk dictionary for data deduplication
--parent-bootstrap
File path of the parent/referenced RAFS metadata blob (optional)
--aligned-chunk
Align uncompressed data chunks to 4K, only for RAFS V5
--repeatable
Generate reproducible RAFS metadata
--whiteout-spec
Set the type of whiteout specification: [default: oci] [possible values: oci, overlayfs, none]
--prefetch-policy
Set data prefetch policy [default: none] [possible values: fs, blob, none]
-J, --output-json
File path to save operation result in JSON format
-h, --help
Print help information
`),
},
{
name: "'--encrypt' is supported in v2.2.0-261-g22ad0e2c",
feature: FeatureEncrypt,
expect: true,
helpMsg: []byte(`
Create RAFS filesystems from directories, tar files or OCI images
Usage: nydus-image create [OPTIONS]
Arguments:
source from which to build the RAFS filesystem
Options:
-L, --log-file
Log file path
-t, --type
Conversion type: [default: dir-rafs] [possible values: directory, dir-rafs, estargz-rafs, estargz-ref, estargztoc-ref, tar-rafs, tar-tarfs, targz-rafs, targz-ref, stargz_index]
-B, --bootstrap
File path to save the generated RAFS metadata blob
-l, --log-level
Log level: [default: info] [possible values: trace, debug, info, warn, error]
-D, --blob-dir
Directory path to save generated RAFS metadata and data blobs
-b, --blob
File path to save the generated RAFS data blob
--blob-inline-meta
Inline RAFS metadata and blob metadata into the data blob
--blob-id
OSS object id for the generated RAFS data blob
--blob-data-size
Set data blob size for 'estargztoc-ref' conversion
--chunk-size
Set the size of data chunks, must be power of two and between 0x1000-0x1000000:
--batch-size
Set the batch size to merge small chunks, must be power of two, between 0x1000-0x1000000 or be zero: [default: 0]
--compressor
Algorithm to compress data chunks: [default: zstd] [possible values: none, lz4_block, zstd]
--digester
Algorithm to digest data chunks: [default: blake3] [possible values: blake3, sha256]
-C, --config
Configuration file for storage backend, cache and RAFS FUSE filesystem.
-v, --fs-version
Set RAFS format version number: [default: 6] [possible values: 5, 6]
--features
Enable/disable features [possible values: blob-toc]
--chunk-dict
File path of chunk dictionary for data deduplication
--parent-bootstrap
File path of the parent/referenced RAFS metadata blob (optional)
--aligned-chunk
Align uncompressed data chunks to 4K, only for RAFS V5
--repeatable
Generate reproducible RAFS metadata
--whiteout-spec
Set the type of whiteout specification: [default: oci] [possible values: oci, overlayfs, none]
--prefetch-policy
Set data prefetch policy [default: none] [possible values: fs, blob, none]
-J, --output-json
File path to save operation result in JSON format
-E, --encrypt
Encrypt the generated RAFS metadata and data blobs
-h, --help
Print help information
`),
},
{
name: "'--type tar-rafs' is not supported in v2.1.4",
feature: FeatureTar2Rafs,
expect: false,
helpMsg: []byte(`
nydus-image-create
Creates a nydus image from source
USAGE:
nydus-image create [FLAGS] [OPTIONS] ... --blob --bootstrap --fs-version --whiteout-spec
FLAGS:
-A, --aligned-chunk Align data chunks to 4K
--disable-check disable validation of metadata after building
-h, --help Prints help information
--inline-bootstrap append bootstrap data to blob
-R, --repeatable generate reproducible nydus image
-V, --version Prints version information
OPTIONS:
--backend-config
[deprecated!] Blob storage backend config - JSON string, only support localfs for compatibility
--backend-type
[deprecated!] Blob storage backend type, only support localfs for compatibility. Try use --blob instead.
[possible values: localfs]
-b, --blob path to store nydus image's data blob
-D, --blob-dir directory to store nydus image's metadata and data blob
--blob-id blob id (as object id in backend/oss)
--blob-meta path to store nydus blob metadata
--blob-offset
add an offset for compressed blob (is only used to put the blob in the tarball) [default: 0]
-B, --bootstrap path to store the nydus image's metadata blob
-M, --chunk-dict Specify a chunk dictionary for chunk deduplication
-S, --chunk-size
size of nydus image data chunk, must be power of two and between 0x1000-0x100000: [default: 0x100000]
-c, --compressor
algorithm to compress image data blob: [default: lz4_block] [possible values: none, lz4_block, gzip, zstd]
-d, --digester
algorithm to digest inodes and data chunks: [default: blake3] [possible values: blake3, sha256]
-v, --fs-version
version number of nydus image format: [default: 5] [possible values: 5, 6]
-o, --log-file Specify log file name
-l, --log-level
Specify log level: [default: info] [possible values: trace, debug, info, warn, error]
-J, --output-json JSON file output path for result
-p, --parent-bootstrap path to parent/referenced image's metadata blob (optional)
-P, --prefetch-policy
blob data prefetch policy [default: none] [possible values: fs, blob, none]
-t, --source-type
type of the source: [default: directory] [possible values: directory, stargz_index]
-W, --whiteout-spec
type of whiteout specification: [default: oci] [possible values: oci, overlayfs, none]
ARGS:
... source path to build the nydus image from
`),
},
{
name: "'--batch-size' is not supported in v1.1.2",
feature: FeatureBatchSize,
expect: false,
helpMsg: []byte(`
nydus-image-create
Create a nydus format accelerated container image
USAGE:
nydus-image create [FLAGS] [OPTIONS] --blob --bootstrap --whiteout-spec
FLAGS:
--aligned-chunk Whether to align chunks into blobcache
--disable-check Disable to validate bootstrap file after building
-h, --help Prints help information
--repeatable Produce environment independent image
-V, --version Prints version information
OPTIONS:
--backend-config
[deprecated!] Blob storage backend config - JSON string, only support localfs for compatibility
--backend-type
[deprecated!] Blob storage backend type, only support localfs for compatibility. Try use --blob instead.
[possible values: localfs]
--blob A path to blob file which stores nydus image data portion
--blob-dir
A directory where blob files are saved named as their sha256 digest. It's very useful when multiple layers
are built at the same time.
--blob-id blob id (as object id in backend/oss)
--bootstrap A path to bootstrap file which stores nydus image metadata portion
--chunk-dict
specify a chunk dictionary file in bootstrap/db format for chunk deduplication.
--compressor
how blob will be compressed: none, lz4_block (default) [default: lz4_block]
--digester
how inode and blob chunk will be digested: blake3 (default), sha256 [default: blake3]
--log-level
Specify log level: trace, debug, info, warn, error [default: info] [possible values: trace, debug, info,
warn, error]
--output-json JSON output path for build result
--parent-bootstrap bootstrap file path of parent (optional)
--prefetch-policy
Prefetch policy: fs(issued from Fs layer), blob(issued from backend/blob layer), none(no readahead is
needed) [default: none]
--source-type
source type [default: directory] [possible values: directory, stargz_index]
--whiteout-spec
decide which whiteout spec to follow: "oci" or "overlayfs" [default: oci] [possible values: oci, overlayfs]
ARGS:
source path
`),
},
{
name: "'--type tar-rafs' is not supported in v0.1.0",
feature: FeatureTar2Rafs,
expect: false,
helpMsg: []byte(`
nydus-image-create
dump image bootstrap and upload blob to storage backend
USAGE:
nydus-image create [FLAGS] [OPTIONS] --bootstrap --whiteout-spec
FLAGS:
--aligned-chunk Whether to align chunks into blobcache
--disable-check Disable to validate bootstrap file after building
-h, --help Prints help information
--repeatable Produce environment independent image
-V, --version Prints version information
OPTIONS:
--backend-config blob storage backend config (JSON string)
--backend-config-file blob storage backend config (JSON file)
--backend-type blob storage backend type (enable blob upload if specified)
--blob blob file path
--blob-id blob id (as object id in backend)
--bootstrap bootstrap file path (required)
--compressor
how blob will be compressed: none, lz4_block (default) [default: lz4_block]
--digester
how inode and blob chunk will be digested: blake3 (default), sha256 [default: blake3]
--log-level
Specify log level: trace, debug, info, warn, error [default: info] [possible values: trace, debug, info,
warn, error]
--output-json JSON output path for build result
--parent-bootstrap bootstrap file path of parent (optional)
--prefetch-policy
Prefetch policy: fs(issued from Fs layer), blob(issued from backend/blob layer), none(no readahead is
needed) [default: none]
--source-type
source type [default: directory] [possible values: directory, stargz_index]
--whiteout-spec
decide which whiteout spec to follow: "oci" or "overlayfs" [default: oci] [possible values: oci, overlayfs]
ARGS:
source path
`),
},
{
name: "'--encrypt' is not supported in v2.2.0",
feature: FeatureEncrypt,
expect: false,
helpMsg: []byte(`
Create RAFS filesystems from directories, tar files or OCI images
Usage: nydus-image create [OPTIONS]
Arguments:
source from which to build the RAFS filesystem
Options:
-L, --log-file
Log file path
-t, --type
Conversion type: [default: dir-rafs] [possible values: directory, dir-rafs, estargz-rafs, estargz-ref, estargztoc-ref, tar-rafs, tar-tarfs, targz-rafs, targz-ref, stargz_index]
-B, --bootstrap
File path to save the generated RAFS metadata blob
-l, --log-level
Log level: [default: info] [possible values: trace, debug, info, warn, error]
-D, --blob-dir
Directory path to save generated RAFS metadata and data blobs
-b, --blob
File path to save the generated RAFS data blob
--blob-inline-meta
Inline RAFS metadata and blob metadata into the data blob
--blob-id
OSS object id for the generated RAFS data blob
--blob-data-size
Set data blob size for 'estargztoc-ref' conversion
--chunk-size
Set the size of data chunks, must be power of two and between 0x1000-0x1000000:
--batch-size
Set the batch size to merge small chunks, must be power of two, between 0x1000-0x1000000 or be zero: [default: 0]
--compressor
Algorithm to compress data chunks: [default: zstd] [possible values: none, lz4_block, zstd]
--digester
Algorithm to digest data chunks: [default: blake3] [possible values: blake3, sha256]
-C, --config
Configuration file for storage backend, cache and RAFS FUSE filesystem.
-v, --fs-version
Set RAFS format version number: [default: 6] [possible values: 5, 6]
--features
Enable/disable features [possible values: blob-toc]
--chunk-dict
File path of chunk dictionary for data deduplication
--parent-bootstrap
File path of the parent/referenced RAFS metadata blob (optional)
--aligned-chunk
Align uncompressed data chunks to 4K, only for RAFS V5
--repeatable
Generate reproducible RAFS metadata
--whiteout-spec
Set the type of whiteout specification: [default: oci] [possible values: oci, overlayfs, none]
--prefetch-policy
Set data prefetch policy [default: none] [possible values: fs, blob, none]
-J, --output-json
File path to save operation result in JSON format
-h, --help
Print help information
`),
},
{
name: "detectFeature should support empty input",
feature: "",
expect: false,
helpMsg: []byte(`
OPTIONS:
--type
[deprecated!] Conversion type.
[possible values: tar-rafs]
`),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.expect, detectFeature(tt.helpMsg, tt.feature))
})
}
}
func TestDetectFeatures(t *testing.T) {
testsCompare := []struct {
name string
resetGlobal bool
disableTar2Rafs bool
helpText []byte
required Features
detected Features
expectErr bool
}{
{
name: "should satisfy required features in v2.2.0-239-gf5c08fcf",
resetGlobal: true,
disableTar2Rafs: false,
helpText: []byte(`
Options:
-t, --type
Conversion type: [default: dir-rafs] [possible values: directory, dir-rafs, estargz-rafs, estargz-ref, estargztoc-ref, tar-rafs, tar-tarfs, targz-rafs, targz-ref, stargz_index]
--batch-size
Set the batch size to merge small chunks, must be power of two, between 0x1000-0x1000000 or be zero: [default: 0]
`),
required: Features{FeatureTar2Rafs: {}, FeatureBatchSize: {}},
detected: Features{FeatureTar2Rafs: {}, FeatureBatchSize: {}},
expectErr: false,
},
{
name: "should not support '--encrypt', '--batch-size' or '--type tar-rafs' in v2.1.4",
resetGlobal: true,
disableTar2Rafs: true,
helpText: []byte(`
nydus-image-create
Creates a nydus image from source
USAGE:
nydus-image create [FLAGS] [OPTIONS] ... --blob --bootstrap --fs-version --whiteout-spec
FLAGS:
-A, --aligned-chunk Align data chunks to 4K
--disable-check disable validation of metadata after building
-h, --help Prints help information
--inline-bootstrap append bootstrap data to blob
-R, --repeatable generate reproducible nydus image
-V, --version Prints version information
OPTIONS:
--backend-config
[deprecated!] Blob storage backend config - JSON string, only support localfs for compatibility
--backend-type
[deprecated!] Blob storage backend type, only support localfs for compatibility. Try use --blob instead.
[possible values: localfs]
-b, --blob path to store nydus image's data blob
-D, --blob-dir directory to store nydus image's metadata and data blob
--blob-id blob id (as object id in backend/oss)
--blob-meta path to store nydus blob metadata
--blob-offset
add an offset for compressed blob (is only used to put the blob in the tarball) [default: 0]
-B, --bootstrap path to store the nydus image's metadata blob
-M, --chunk-dict Specify a chunk dictionary for chunk deduplication
-S, --chunk-size
size of nydus image data chunk, must be power of two and between 0x1000-0x100000: [default: 0x100000]
-c, --compressor
algorithm to compress image data blob: [default: lz4_block] [possible values: none, lz4_block, gzip, zstd]
-d, --digester
algorithm to digest inodes and data chunks: [default: blake3] [possible values: blake3, sha256]
-v, --fs-version
version number of nydus image format: [default: 5] [possible values: 5, 6]
-o, --log-file Specify log file name
-l, --log-level
Specify log level: [default: info] [possible values: trace, debug, info, warn, error]
-J, --output-json JSON file output path for result
-p, --parent-bootstrap path to parent/referenced image's metadata blob (optional)
-P, --prefetch-policy
blob data prefetch policy [default: none] [possible values: fs, blob, none]
-t, --source-type
type of the source: [default: directory] [possible values: directory, stargz_index]
-W, --whiteout-spec
type of whiteout specification: [default: oci] [possible values: oci, overlayfs, none]
ARGS:
... source path to build the nydus image from
`),
required: Features{FeatureTar2Rafs: {}, FeatureBatchSize: {}, FeatureEncrypt: {}},
detected: Features{},
expectErr: false,
},
{
name: "should ignore '--type tar-rafs' if disabled",
resetGlobal: true,
disableTar2Rafs: true,
helpText: []byte(`
Options:
-t, --type
Conversion type: [default: dir-rafs] [possible values: directory, dir-rafs, estargz-rafs, estargz-ref, estargztoc-ref, tar-rafs, tar-tarfs, targz-rafs, targz-ref, stargz_index]
--batch-size
Set the batch size to merge small chunks, must be power of two, between 0x1000-0x1000000 or be zero: [default: 0]
`),
required: Features{FeatureTar2Rafs: {}, FeatureBatchSize: {}},
detected: Features{FeatureBatchSize: {}},
expectErr: false,
},
{
name: "should return error if required features changed in different calls",
resetGlobal: false,
disableTar2Rafs: false,
helpText: nil,
required: Features{},
detected: nil,
expectErr: true,
},
}
for _, tt := range testsCompare {
t.Run(tt.name, func(t *testing.T) {
if tt.resetGlobal {
// Reset global variables.
requiredFeatures = Features{}
detectedFeatures = Features{}
detectFeaturesOnce = sync.Once{}
disableTar2Rafs = tt.disableTar2Rafs
}
detected, err := DetectFeatures("", tt.required, func(_ string) []byte { return tt.helpText })
require.Equal(t, tt.expectErr, err != nil)
require.Equal(t, tt.detected, detected)
})
}
}
================================================
FILE: pkg/converter/types.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package converter
import (
"context"
"errors"
"fmt"
"strings"
"time"
"github.com/containerd/containerd/v2/core/content"
"github.com/containerd/nydus-snapshotter/pkg/converter/tool"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
type Compressor = uint32
type Encrypter = func(context.Context, content.Store, ocispec.Descriptor) (ocispec.Descriptor, error)
const (
CompressorNone Compressor = 0x0000_0001
CompressorZstd Compressor = 0x0000_0002
CompressorLz4Block Compressor = 0x0000_0004
CompressorMask Compressor = 0x0000_000f
)
var (
ErrNotFound = errors.New("data not found")
)
type Layer struct {
// Digest represents the hash of whole tar blob.
Digest digest.Digest
// Digest represents the original OCI tar(.gz) blob.
OriginalDigest *digest.Digest
// ReaderAt holds the reader of whole tar blob.
ReaderAt content.ReaderAt
}
// Backend uploads blobs generated by nydus-image builder to a backend storage.
type Backend interface {
// Push pushes specified blob file to remote storage backend.
Push(ctx context.Context, cs content.Store, desc ocispec.Descriptor) error
// Check checks whether a blob exists in remote storage backend,
// blob exists -> return (blobPath, nil)
// blob not exists -> return ("", err)
Check(blobDigest digest.Digest) (string, error)
// Type returns backend type name.
Type() string
}
type PackOption struct {
// WorkDir is used as the work directory during layer pack.
WorkDir string
// BuilderPath holds the path of `nydus-image` binary tool.
BuilderPath string
// FsVersion specifies nydus RAFS format version, possible
// values: `5`, `6` (EROFS-compatible), default is `6`.
FsVersion string
// ChunkDictPath holds the bootstrap path of chunk dict image.
ChunkDictPath string
// PrefetchPatterns holds file path pattern list want to prefetch.
PrefetchPatterns string
// Compressor specifies nydus blob compression algorithm.
Compressor string
// OCIRef enables converting OCI tar(.gz) blob to nydus referenced blob.
OCIRef bool
// AlignedChunk aligns uncompressed data chunks to 4K, only for RAFS V5.
AlignedChunk bool
// ChunkSize sets the size of data chunks, must be power of two and between 0x1000-0x1000000.
ChunkSize string
// BacthSize sets the size of batch data chunks, must be power of two and between 0x1000-0x1000000 or zero.
BatchSize string
// Backend uploads blobs generated by nydus-image builder to a backend storage.
Backend Backend
// Timeout cancels execution once exceed the specified time.
Timeout *time.Duration
// Whether the generated Nydus blobs should be encrypted.
Encrypt bool
// Features keeps a feature list supported by newer version of builder,
// It is detected automatically, so don't export it.
features tool.Features
}
type MergeOption struct {
// WorkDir is used as the work directory during layer merge.
WorkDir string
// BuilderPath holds the path of `nydus-image` binary tool.
BuilderPath string
// FsVersion specifies nydus RAFS format version, possible
// values: `5`, `6` (EROFS-compatible), default is `6`.
FsVersion string
// ChunkDictPath holds the bootstrap path of chunk dict image.
ChunkDictPath string
// ParentBootstrapPath holds the bootstrap path of parent image.
ParentBootstrapPath string
// PrefetchPatterns holds file path pattern list want to prefetch.
PrefetchPatterns string
// WithTar puts bootstrap into a tar stream (no gzip).
WithTar bool
// OCI converts docker media types to OCI media types.
OCI bool
// OCIRef enables converting OCI tar(.gz) blob to nydus referenced blob.
OCIRef bool
// WithReferrer associates a reference to the original OCI manifest.
// See the `subject` field description in
// https://github.com/opencontainers/image-spec/blob/main/manifest.md#image-manifest-property-descriptions
//
// With this association, we can track all nydus images associated with
// an OCI image. For example, in Harbor we can cascade to show nydus
// images linked to an OCI image, deleting the OCI image can also delete
// the corresponding nydus images. At runtime, nydus snapshotter can also
// automatically upgrade an OCI image run to nydus image.
WithReferrer bool
// Backend uploads blobs generated by nydus-image builder to a backend storage.
Backend Backend
// Timeout cancels execution once exceed the specified time.
Timeout *time.Duration
// Encrypt encrypts the bootstrap layer if it's specified.
Encrypt Encrypter
// AppendFiles specifies the files that need to be appended to the bootstrap layer.
AppendFiles []File
// MergeManifest indicates that the resulting nydus manifest will be merged with the original
// OCI one into a single index manifest.
MergeManifest bool
}
type UnpackOption struct {
// WorkDir is used as the work directory during layer unpack.
WorkDir string
// BuilderPath holds the path of `nydus-image` binary tool.
BuilderPath string
// Timeout cancels execution once exceed the specified time.
Timeout *time.Duration
// Stream enables streaming mode, which doesn't unpack the blob data to disk,
// but setup a http server to serve the blob data.
Stream bool
// Compressor specifies oci blob compression algorithm.
// Supported values: "uncompressed" (no compression), "gzip", "zstd".
// Defaults to "gzip" if not specified.
Compressor string
// Backend uploads blobs generated by oci-image builder to a backend storage.
Backend Backend
}
type TOCEntry struct {
// Feature flags of entry
Flags uint32
Reserved1 uint32
// Name of entry data
Name [16]byte
// Sha256 of uncompressed entry data
UncompressedDigest [32]byte
// Offset of compressed entry data
CompressedOffset uint64
// Size of compressed entry data
CompressedSize uint64
// Size of uncompressed entry data
UncompressedSize uint64
Reserved2 [44]byte
}
func (entry *TOCEntry) GetCompressor() (Compressor, error) {
switch entry.Flags & CompressorMask {
case CompressorNone:
return CompressorNone, nil
case CompressorZstd:
return CompressorZstd, nil
case CompressorLz4Block:
return CompressorLz4Block, nil
}
return 0, fmt.Errorf("unsupported compressor, entry flags %x", entry.Flags)
}
func (entry *TOCEntry) GetName() string {
var name strings.Builder
name.Grow(16)
for _, c := range entry.Name {
if c == 0 {
break
}
fmt.Fprintf(&name, "%c", c)
}
return name.String()
}
func (entry *TOCEntry) GetUncompressedDigest() string {
return fmt.Sprintf("%x", entry.UncompressedDigest)
}
func (entry *TOCEntry) GetCompressedOffset() uint64 {
return entry.CompressedOffset
}
func (entry *TOCEntry) GetCompressedSize() uint64 {
return entry.CompressedSize
}
func (entry *TOCEntry) GetUncompressedSize() uint64 {
return entry.UncompressedSize
}
================================================
FILE: pkg/converter/utils.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package converter
import (
"archive/tar"
"bytes"
"compress/gzip"
"context"
"encoding/json"
"fmt"
"io"
"path/filepath"
"github.com/containerd/containerd/v2/core/content"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
type File struct {
Name string
Reader io.Reader
Size int64
}
type writeCloser struct {
closed bool
io.WriteCloser
action func() error
}
func (c *writeCloser) Close() error {
if c.closed {
return nil
}
if err := c.WriteCloser.Close(); err != nil {
return err
}
c.closed = true
if err := c.action(); err != nil {
return err
}
return nil
}
func newWriteCloser(wc io.WriteCloser, action func() error) *writeCloser {
return &writeCloser{
WriteCloser: wc,
action: action,
}
}
type seekReader struct {
io.ReaderAt
pos int64
}
func (ra *seekReader) Read(p []byte) (int, error) {
n, err := ra.ReadAt(p, ra.pos)
ra.pos += int64(n)
return n, err
}
func (ra *seekReader) Seek(offset int64, whence int) (int64, error) {
switch whence {
case io.SeekCurrent:
ra.pos += offset
case io.SeekStart:
ra.pos = offset
default:
return 0, fmt.Errorf("unsupported whence %d", whence)
}
return ra.pos, nil
}
func newSeekReader(ra io.ReaderAt) *seekReader {
return &seekReader{
ReaderAt: ra,
pos: 0,
}
}
// packToTar packs files to .tar(.gz) stream then return reader.
func packToTar(files []File, compress bool) io.ReadCloser {
dirHdr := &tar.Header{
Name: "image",
Mode: 0755,
Typeflag: tar.TypeDir,
}
pr, pw := io.Pipe()
go func() {
// Prepare targz writer
var tw *tar.Writer
var gw *gzip.Writer
var err error
if compress {
gw = gzip.NewWriter(pw)
tw = tar.NewWriter(gw)
} else {
tw = tar.NewWriter(pw)
}
defer func() {
err1 := tw.Close()
var err2 error
if gw != nil {
err2 = gw.Close()
}
var finalErr error
// Return the first error encountered to the other end and ignore others.
switch {
case err != nil:
finalErr = err
case err1 != nil:
finalErr = err1
case err2 != nil:
finalErr = err2
}
pw.CloseWithError(finalErr)
}()
// Write targz stream
if err = tw.WriteHeader(dirHdr); err != nil {
return
}
for _, file := range files {
hdr := tar.Header{
Name: filepath.Join("image", file.Name),
Mode: 0444,
Size: file.Size,
}
if err = tw.WriteHeader(&hdr); err != nil {
return
}
if _, err = io.Copy(tw, file.Reader); err != nil {
return
}
}
}()
return pr
}
// Copied from containerd/containerd project, copyright The containerd Authors.
// https://github.com/containerd/containerd/blob/4902059cb554f4f06a8d06a12134c17117809f4e/images/converter/default.go#L385
func readJSON(ctx context.Context, cs content.Store, x interface{}, desc ocispec.Descriptor) (map[string]string, error) {
info, err := cs.Info(ctx, desc.Digest)
if err != nil {
return nil, err
}
labels := info.Labels
if labels == nil {
labels = map[string]string{}
}
b, err := content.ReadBlob(ctx, cs, desc)
if err != nil {
return nil, err
}
if err := json.Unmarshal(b, x); err != nil {
return nil, err
}
return labels, nil
}
// Copied from containerd/containerd project, copyright The containerd Authors.
// https://github.com/containerd/containerd/blob/4902059cb554f4f06a8d06a12134c17117809f4e/images/converter/default.go#L401
func writeJSON(ctx context.Context, cs content.Store, x interface{}, oldDesc ocispec.Descriptor, labels map[string]string) (*ocispec.Descriptor, error) {
b, err := json.Marshal(x)
if err != nil {
return nil, err
}
dgst := digest.SHA256.FromBytes(b)
ref := fmt.Sprintf("converter-write-json-%s", dgst.String())
w, err := content.OpenWriter(ctx, cs, content.WithRef(ref))
if err != nil {
return nil, err
}
if err := content.Copy(ctx, w, bytes.NewReader(b), int64(len(b)), dgst, content.WithLabels(labels)); err != nil {
return nil, err
}
if err := w.Close(); err != nil {
return nil, err
}
newDesc := oldDesc
newDesc.Size = int64(len(b))
newDesc.Digest = dgst
return &newDesc, nil
}
================================================
FILE: pkg/daemon/client.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package daemon
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/fs"
"net"
"net/http"
"net/url"
"os"
"time"
"github.com/pkg/errors"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/daemon/types"
"github.com/containerd/nydus-snapshotter/pkg/metrics/tool"
"github.com/containerd/nydus-snapshotter/pkg/utils/retry"
)
const (
// Get information about nydus daemon
endpointDaemonInfo = "/api/v1/daemon"
// Mount or umount filesystems.
endpointMount = "/api/v1/mount"
// Fetch generic filesystem metrics.
endpointMetrics = "/api/v1/metrics"
// Fetch metrics relevant to caches usage.
endpointCacheMetrics = "/api/v1/metrics/blobcache"
// Fetch metrics about inflighting operations.
endpointInflightMetrics = "/api/v1/metrics/inflight"
// Request nydus daemon to retrieve its runtime states from the supervisor, recovering states for failover.
endpointTakeOver = "/api/v1/daemon/fuse/takeover"
// Request nydus daemon to send its runtime states to the supervisor, preparing for failover.
endpointSendFd = "/api/v1/daemon/fuse/sendfd"
// Request nydus daemon to start filesystem service.
endpointStart = "/api/v1/daemon/start"
// Request nydus daemon to exit
endpointExit = "/api/v1/daemon/exit"
// Update daemon configuration at runtime.
endpointConfig = "/api/v1/config"
// --- V2 API begins
// Add/remove blobs managed by the blob cache manager.
endpointBlobs = "/api/v2/blobs"
defaultHTTPClientTimeout = 30 * time.Second
jsonContentType = "application/json"
)
// Nydusd HTTP client to query nydusd runtime status, operate file system instances.
// Control nydusd workflow like failover and upgrade.
type NydusdClient interface {
GetDaemonInfo() (*types.DaemonInfo, error)
Mount(mountpoint, bootstrap, daemonConfig string) error
Umount(mountpoint string) error
BindBlob(daemonConfig string) error
UnbindBlob(domainID, blobID string) error
GetFsMetrics(sid string) (*types.FsMetrics, error)
GetInflightMetrics() (*types.InflightMetrics, error)
GetCacheMetrics(sid string) (*types.CacheMetrics, error)
UpdateConfig(id string, params map[string]string) error
TakeOver() error
SendFd() error
Start() error
Exit() error
}
// Nydusd API server http client used to command nydusd's action and
// query nydusd working status.
type nydusdClient struct {
httpClient *http.Client
}
type query = url.Values
func (c *nydusdClient) url(path string, query query) (url string) {
url = fmt.Sprintf("http://unix%s", path)
if len(query) != 0 {
url += "?" + query.Encode()
}
return
}
// A simple http client request wrapper with capability to take
// request body and handle or process http response if result is expected.
func (c *nydusdClient) request(method string, url string,
body io.Reader, respHandler func(resp *http.Response) error) error {
req, err := http.NewRequest(method, url, body)
if err != nil {
return errors.Wrapf(err, "construct request %s", url)
}
if body != nil {
req.Header.Add("Content-Type", jsonContentType)
}
resp, err := c.httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if succeeded(resp) {
if respHandler != nil {
if err = respHandler(resp); err != nil {
return errors.Wrapf(err, "handle response")
}
}
return nil
}
return parseErrorMessage(resp)
}
func succeeded(resp *http.Response) bool {
return resp.StatusCode == http.StatusNoContent || resp.StatusCode == http.StatusOK
}
func decode(resp *http.Response, v any) error {
if err := json.NewDecoder(resp.Body).Decode(&v); err != nil {
return errors.Wrap(err, "decode response")
}
return nil
}
// Parse http response to get the specific error message formatted by nydusd API server.
// So it will be clear what's wrong in nydusd during processing http requests.
func parseErrorMessage(resp *http.Response) error {
var errMessage types.ErrorMessage
err := decode(resp, &errMessage)
if err != nil {
return err
}
return errors.Errorf("http response: %d, error code: %s, error message: %s",
resp.StatusCode, errMessage.Code, errMessage.Message)
}
func buildTransport(sock string) http.RoundTripper {
return &http.Transport{
MaxIdleConns: 10,
IdleConnTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
dialer := &net.Dialer{
Timeout: 5 * time.Second,
KeepAlive: 5 * time.Second,
}
return dialer.DialContext(ctx, "unix", sock)
},
}
}
func WaitUntilSocketExisted(sock string, pid int) error {
return retry.Do(func() (err error) {
var st fs.FileInfo
if st, err = os.Stat(sock); err != nil {
return
}
if st.Mode()&os.ModeSocket == 0 {
return errors.Errorf("file %s is not socket file", sock)
}
return nil
},
retry.Attempts(100), // totally wait for 10 seconds, should be enough
retry.LastErrorOnly(true),
retry.Delay(100*time.Millisecond),
retry.OnlyRetryIf(func(error) bool {
zombie, err := tool.IsZombieProcess(pid)
if err != nil {
return false
}
// Stop retry if nydus daemon process is already in Zombie state.
if zombie {
log.L.Errorf("Process %d has been a zombie", pid)
return true
}
return false
}),
)
}
func NewNydusClient(sock string) (NydusdClient, error) {
transport := buildTransport(sock)
return &nydusdClient{
httpClient: &http.Client{
Timeout: defaultHTTPClientTimeout,
Transport: transport,
},
}, nil
}
func (c *nydusdClient) GetDaemonInfo() (*types.DaemonInfo, error) {
url := c.url(endpointDaemonInfo, query{})
var info types.DaemonInfo
err := c.request(http.MethodGet, url, nil, func(resp *http.Response) error {
if err := decode(resp, &info); err != nil {
return err
}
return nil
})
if err != nil {
return nil, err
}
return &info, nil
}
func (c *nydusdClient) Mount(mp, bootstrap, mountConfig string) error {
cmd, err := json.Marshal(types.NewMountRequest(bootstrap, mountConfig))
if err != nil {
return errors.Wrap(err, "construct mount request")
}
query := query{}
query.Add("mountpoint", mp)
url := c.url(endpointMount, query)
return c.request(http.MethodPost, url, bytes.NewBuffer(cmd), nil)
}
func (c *nydusdClient) Umount(mp string) error {
query := query{}
query.Add("mountpoint", mp)
url := c.url(endpointMount, query)
return c.request(http.MethodDelete, url, nil, nil)
}
func (c *nydusdClient) BindBlob(daemonConfig string) error {
url := c.url(endpointBlobs, query{})
return c.request(http.MethodPut, url, bytes.NewBuffer([]byte(daemonConfig)), nil)
}
// Delete /api/v2/blobs implements different functions according to different parameters
// 1. domainID , delete all blob entries in the domain.
// 2. domainID + blobID, delete the blob entry, if the blob is bootstrap
// also delete blob entries belong to it.
// 3. blobID, try to find and cull blob cache files by blobID in all domains.
func (c *nydusdClient) UnbindBlob(domainID, blobID string) error {
query := query{}
if domainID != "" {
query.Add("domain_id", domainID)
if domainID != blobID {
query.Add("blob_id", blobID)
}
} else {
query.Add("blob_id", blobID)
}
url := c.url(endpointBlobs, query)
return c.request(http.MethodDelete, url, nil, nil)
}
func (c *nydusdClient) GetFsMetrics(sid string) (*types.FsMetrics, error) {
query := query{}
if sid != "" {
query.Add("id", "/"+sid)
}
url := c.url(endpointMetrics, query)
var m types.FsMetrics
if err := c.request(http.MethodGet, url, nil, func(resp *http.Response) error {
return decode(resp, &m)
}); err != nil {
return nil, err
}
return &m, nil
}
func (c *nydusdClient) GetInflightMetrics() (*types.InflightMetrics, error) {
url := c.url(endpointInflightMetrics, query{})
var m types.InflightMetrics
if err := c.request(http.MethodGet, url, nil, func(resp *http.Response) error {
if resp.StatusCode != http.StatusNoContent {
return decode(resp, &m.Values)
}
return nil
}); err != nil {
return nil, err
}
return &m, nil
}
func (c *nydusdClient) GetCacheMetrics(sid string) (*types.CacheMetrics, error) {
query := query{}
if sid != "" {
query.Add("id", "/"+sid)
}
url := c.url(endpointCacheMetrics, query)
var m types.CacheMetrics
if err := c.request(http.MethodGet, url, nil, func(resp *http.Response) error {
return decode(resp, &m)
}); err != nil {
return nil, err
}
return &m, nil
}
func (c *nydusdClient) UpdateConfig(id string, params map[string]string) error {
body, err := json.Marshal(params)
if err != nil {
return errors.Wrap(err, "marshal config params")
}
q := query{}
q.Add("id", id)
url := c.url(endpointConfig, q)
return c.request(http.MethodPut, url, bytes.NewBuffer(body), nil)
}
func (c *nydusdClient) TakeOver() error {
url := c.url(endpointTakeOver, query{})
return c.request(http.MethodPut, url, nil, nil)
}
func (c *nydusdClient) SendFd() error {
url := c.url(endpointSendFd, query{})
return c.request(http.MethodPut, url, nil, nil)
}
func (c *nydusdClient) Start() error {
url := c.url(endpointStart, query{})
return c.request(http.MethodPut, url, nil, nil)
}
func (c *nydusdClient) Exit() error {
url := c.url(endpointExit, query{})
return c.request(http.MethodPut, url, nil, nil)
}
================================================
FILE: pkg/daemon/client_test.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package daemon
import (
"encoding/json"
"net"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/containerd/nydus-snapshotter/pkg/daemon/types"
)
var BTI = types.BuildTimeInfo{
PackageVer: "1.1.0",
GitCommit: "67f4ecc7acee6dd37234e6a697e72ac09d6cc8ba",
BuildTime: "Thu, 28 Jan 2021 14:02:39 +0000",
Profile: "debug",
Rustc: "rustc 1.46.0 (04488afe3 2020-08-24)",
}
func prepareNydusServer(t *testing.T) (string, func()) {
dir, _ := os.MkdirTemp("", "nydus-snapshotter-test")
mockSocket := filepath.Join(dir, "nydusd.sock")
_, err := os.Stat(mockSocket)
if err == nil {
_ = os.Remove(mockSocket)
}
ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(200)
info := types.DaemonInfo{
ID: "testid",
Version: BTI,
State: "RUNNING",
}
w.Header().Set("Content-Type", "application/json")
j, _ := json.Marshal(info)
_, err := w.Write(j)
assert.Nil(t, err)
}))
unixListener, err := net.Listen("unix", mockSocket)
require.Nil(t, err)
ts.Listener = unixListener
ts.Start()
return mockSocket, func() {
ts.Close()
}
}
func TestNydusClient_CheckStatus(t *testing.T) {
sock, dispose := prepareNydusServer(t)
defer dispose()
client, err := NewNydusClient(sock)
require.Nil(t, err)
info, err := client.GetDaemonInfo()
require.Nil(t, err)
assert.Equal(t, info.DaemonState(), types.DaemonStateRunning)
assert.Equal(t, "testid", info.ID)
assert.Equal(t, BTI, info.Version)
}
func TestUpdateConfig(t *testing.T) {
tests := []struct {
name string
id string
params map[string]string
statusCode int
respBody string
wantErr bool
}{
{
name: "shared daemon with snapshot ID",
id: "/snap-1",
params: map[string]string{"registry_auth": "dXNlcjpwYXNz"},
statusCode: http.StatusNoContent,
},
{
name: "dedicated daemon with root ID",
id: "/",
params: map[string]string{"registry_auth": "dXNlcjpwYXNz"},
statusCode: http.StatusNoContent,
},
{
name: "server returns error",
id: "/snap-1",
params: map[string]string{"registry_auth": "dXNlcjpwYXNz"},
statusCode: http.StatusInternalServerError,
respBody: `{"code":"EINVAL","message":"invalid config"}`,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var gotID string
var gotBody map[string]string
dir := t.TempDir()
sock := filepath.Join(dir, "api.sock")
ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPut, r.Method)
assert.Equal(t, "/api/v1/config", r.URL.Path)
gotID = r.URL.Query().Get("id")
_ = json.NewDecoder(r.Body).Decode(&gotBody)
if tt.respBody != "" {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(tt.statusCode)
_, _ = w.Write([]byte(tt.respBody))
} else {
w.WriteHeader(tt.statusCode)
}
}))
listener, err := net.Listen("unix", sock)
require.NoError(t, err)
ts.Listener = listener
ts.Start()
defer ts.Close()
client, err := NewNydusClient(sock)
require.NoError(t, err)
err = client.UpdateConfig(tt.id, tt.params)
assert.Equal(t, tt.id, gotID)
assert.Equal(t, tt.params, gotBody)
if tt.wantErr {
require.Error(t, err)
assert.Contains(t, err.Error(), "invalid config")
} else {
require.NoError(t, err)
}
})
}
}
================================================
FILE: pkg/daemon/command/command.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package command
import (
"fmt"
"reflect"
"strconv"
"github.com/pkg/errors"
)
type Opt = func(cmd *DaemonCommand)
// Define how to build a command line to start a nydusd daemon
type DaemonCommand struct {
// "singleton" "fuse"
Mode string `type:"subcommand"`
// "blobcache" "fscache" "virtiofs"
FscacheDriver string `type:"param" name:"fscache"`
FscacheThreads string `type:"param" name:"fscache-threads"`
Upgrade bool `type:"flag" name:"upgrade" default:""`
ThreadNum string `type:"param" name:"thread-num"`
// `--id` is required by `--supervisor` when starting nydusd
ID string `type:"param" name:"id"`
Config string `type:"param" name:"config"`
Bootstrap string `type:"param" name:"bootstrap"`
Mountpoint string `type:"param" name:"mountpoint"`
APISock string `type:"param" name:"apisock"`
LogLevel string `type:"param" name:"log-level"`
LogRotationSize int `type:"param" name:"log-rotation-size"`
Supervisor string `type:"param" name:"supervisor"`
LogFile string `type:"param" name:"log-file"`
PrefetchFiles string `type:"param" name:"prefetch-files"`
BackendSource string `type:"param" name:"backend-source"`
FailoverPolicy string `type:"param" name:"failover-policy"`
}
// Build exec style command line
func BuildCommand(opts []Opt) ([]string, error) {
var cmd DaemonCommand
var subcommand string
for _, o := range opts {
o(&cmd)
}
args := make([]string, 0, 32)
t := reflect.TypeOf(cmd)
v := reflect.ValueOf(cmd)
for i := 0; i < t.NumField(); i++ {
tag := t.Field(i).Tag
argType := tag.Get("type")
switch argType {
case "param":
// Zero value will be skipped appending to command line
if v.Field(i).IsZero() {
continue
}
value := v.Field(i).Interface()
pair := []string{fmt.Sprintf("--%s", tag.Get("name")), fmt.Sprintf("%v", value)}
args = append(args, pair...)
case "subcommand":
// Zero value will be skipped appending to command line
if v.Field(i).IsZero() {
continue
}
subcommand = v.Field(i).String()
case "flag":
kind := v.Field(i).Kind()
if kind != reflect.Bool {
return nil, errors.Errorf("flag must be boolean")
}
v := v.Field(i).Bool()
if v {
flag := fmt.Sprintf("--%s", tag.Get("name"))
args = append(args, flag)
} else {
continue
}
default:
return nil, errors.Errorf("unknown tag type: %s ", argType)
}
}
if subcommand != "" {
// Ensure subcommand is at the first place.
args = append([]string{subcommand}, args...)
}
return args, nil
}
func WithMode(m string) Opt {
return func(cmd *DaemonCommand) {
cmd.Mode = m
}
}
func WithPrefetchFiles(p string) Opt {
return func(cmd *DaemonCommand) {
cmd.PrefetchFiles = p
}
}
func WithFscacheDriver(w string) Opt {
return func(cmd *DaemonCommand) {
cmd.FscacheDriver = w
}
}
func WithFscacheThreads(num int) Opt {
return func(cmd *DaemonCommand) {
cmd.FscacheThreads = strconv.Itoa(num)
}
}
func WithThreadNum(num int) Opt {
return func(cmd *DaemonCommand) {
cmd.ThreadNum = strconv.Itoa(num)
}
}
func WithConfig(config string) Opt {
return func(cmd *DaemonCommand) {
cmd.Config = config
}
}
func WithBootstrap(b string) Opt {
return func(cmd *DaemonCommand) {
cmd.Bootstrap = b
}
}
func WithMountpoint(m string) Opt {
return func(cmd *DaemonCommand) {
cmd.Mountpoint = m
}
}
func WithAPISock(api string) Opt {
return func(cmd *DaemonCommand) {
cmd.APISock = api
}
}
func WithLogFile(l string) Opt {
return func(cmd *DaemonCommand) {
cmd.LogFile = l
}
}
func WithLogLevel(l string) Opt {
return func(cmd *DaemonCommand) {
cmd.LogLevel = l
}
}
func WithLogRotationSize(l int) Opt {
return func(cmd *DaemonCommand) {
cmd.LogRotationSize = l
}
}
func WithSupervisor(s string) Opt {
return func(cmd *DaemonCommand) {
cmd.Supervisor = s
}
}
func WithID(id string) Opt {
return func(cmd *DaemonCommand) {
cmd.ID = id
}
}
func WithUpgrade() Opt {
return func(cmd *DaemonCommand) {
cmd.Upgrade = true
}
}
func WithBackendSource(source string) Opt {
return func(cmd *DaemonCommand) {
cmd.BackendSource = source
}
}
func WithFailoverPolicy(policy string) Opt {
return func(cmd *DaemonCommand) {
cmd.FailoverPolicy = policy
}
}
================================================
FILE: pkg/daemon/command/command_builder_test.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package command
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestBuildCommand(t *testing.T) {
c := []Opt{WithMode("singleton"),
WithFscacheDriver("fs_cache_dir"),
WithFscacheThreads(4),
WithAPISock("/dummy/apisock"),
WithUpgrade()}
args, err := BuildCommand(c)
assert.Nil(t, err)
actual := strings.Join(args, " ")
assert.Equal(t, "singleton --fscache fs_cache_dir --fscache-threads 4 --upgrade --apisock /dummy/apisock", actual)
c1 := []Opt{WithMode("singleton"),
WithFscacheDriver("fs_cache_dir"),
WithFscacheThreads(4),
WithAPISock("/dummy/apisock")}
args1, err := BuildCommand(c1)
assert.Nil(t, err)
actual1 := strings.Join(args1, " ")
assert.Equal(t, "singleton --fscache fs_cache_dir --fscache-threads 4 --apisock /dummy/apisock", actual1)
}
// cpu: Intel(R) Xeon(R) Platinum 8260 CPU @ 2.40GHz
// BenchmarkBuildCommand-8 394146 3084 ns/op
// BenchmarkXxx-8 3933902 281.4 ns/op
// PASS
func BenchmarkBuildCommand(b *testing.B) {
c := []Opt{WithMode("singleton"),
WithFscacheDriver("fs_cache_dir"),
WithFscacheThreads(4),
WithAPISock("/dummy/apisock"),
WithUpgrade()}
for n := 0; n < b.N; n++ {
_, err := BuildCommand(c)
assert.Nil(b, err)
}
}
================================================
FILE: pkg/daemon/config.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package daemon
import (
"os"
"path"
"path/filepath"
"github.com/pkg/errors"
"github.com/containerd/nydus-snapshotter/config"
"github.com/containerd/nydus-snapshotter/internal/constant"
)
// Build runtime nydusd daemon object, which might be persisted later
func WithSocketDir(dir string) NewDaemonOpt {
return func(d *Daemon) error {
s := filepath.Join(dir, d.ID())
// this may be failed, should handle that
if err := os.MkdirAll(s, 0755); err != nil {
return errors.Wrapf(err, "create socket dir %s", s)
}
d.States.APISocket = path.Join(s, "api.sock")
return nil
}
}
func WithRef(ref int32) NewDaemonOpt {
return func(d *Daemon) error {
d.ref = ref
return nil
}
}
func WithLogDir(dir string) NewDaemonOpt {
return func(d *Daemon) error {
if err := os.MkdirAll(dir, 0755); err != nil {
return errors.Wrapf(err, "create logging dir %s", dir)
}
d.States.LogDir = filepath.Join(dir, d.ID())
return nil
}
}
func WithLogToStdout(logToStdout bool) NewDaemonOpt {
return func(d *Daemon) error {
d.States.LogToStdout = logToStdout
return nil
}
}
func WithLogLevel(logLevel string) NewDaemonOpt {
return func(d *Daemon) error {
if logLevel == "" {
d.States.LogLevel = constant.DefaultLogLevel
} else {
d.States.LogLevel = logLevel
}
return nil
}
}
func WithLogRotationSize(logRotationSize int) NewDaemonOpt {
return func(d *Daemon) error {
d.States.LogRotationSize = logRotationSize
return nil
}
}
func WithConfigDir(dir string) NewDaemonOpt {
return func(d *Daemon) error {
s := filepath.Join(dir, d.ID())
// this may be failed, should handle that
if err := os.MkdirAll(s, 0755); err != nil {
return errors.Wrapf(err, "failed to create config dir %s", s)
}
d.States.ConfigDir = s
return nil
}
}
func WithMountpoint(mountpoint string) NewDaemonOpt {
return func(d *Daemon) error {
d.States.Mountpoint = mountpoint
return nil
}
}
func WithNydusdThreadNum(nydusdThreadNum int) NewDaemonOpt {
return func(d *Daemon) error {
d.States.ThreadNum = nydusdThreadNum
return nil
}
}
func WithFsDriver(fsDriver string) NewDaemonOpt {
return func(d *Daemon) error {
d.States.FsDriver = fsDriver
return nil
}
}
func WithFailoverPolicy(failoverPolicy string) NewDaemonOpt {
return func(d *Daemon) error {
d.States.FailoverPolicy = failoverPolicy
return nil
}
}
func WithDaemonMode(daemonMode config.DaemonMode) NewDaemonOpt {
return func(d *Daemon) error {
d.States.DaemonMode = daemonMode
return nil
}
}
================================================
FILE: pkg/daemon/daemon.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package daemon
import (
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"
"sync"
"sync/atomic"
"syscall"
"time"
"github.com/pkg/errors"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/config"
"github.com/containerd/nydus-snapshotter/config/daemonconfig"
"github.com/containerd/nydus-snapshotter/pkg/auth"
"github.com/containerd/nydus-snapshotter/pkg/daemon/types"
"github.com/containerd/nydus-snapshotter/pkg/errdefs"
"github.com/containerd/nydus-snapshotter/pkg/metrics/collector"
"github.com/containerd/nydus-snapshotter/pkg/rafs"
"github.com/containerd/nydus-snapshotter/pkg/supervisor"
"github.com/containerd/nydus-snapshotter/pkg/utils/erofs"
"github.com/containerd/nydus-snapshotter/pkg/utils/mount"
"github.com/containerd/nydus-snapshotter/pkg/utils/retry"
)
const (
APISocketFileName = "api.sock"
SharedNydusDaemonID = "shared_daemon"
)
type NewDaemonOpt func(d *Daemon) error
// Fields in this structure should be write-once, and caller should hold `Daemon.mu` when updating fields.
type ConfigState struct {
// A unique ID generated by daemon manager to identify the nydusd instance.
ID string
ProcessID int
APISocket string
DaemonMode config.DaemonMode
FsDriver string
LogDir string
LogLevel string
LogRotationSize int
LogToStdout bool
Mountpoint string
SupervisorPath string
ThreadNum int
FailoverPolicy string
// Where the configuration file resides, all rafs instances share the same configuration template
ConfigDir string
}
// TODO: Record queried nydusd state
type Daemon struct {
States ConfigState
mu sync.Mutex
// Host all RAFS filesystems managed by this daemon:
// fusedev dedicated mode: one and only one RAFS instance
// fusedev shared mode: zero, one or more RAFS instances
// fscache shared mode: zero, one or more RAFS instances
RafsCache rafs.Cache
// Protect nydusd http client
cmu sync.Mutex
// client will be rebuilt on Reconnect, skip marshal/unmarshal
client NydusdClient
// Nil means this daemon object has no supervisor
Supervisor *supervisor.Supervisor
Config daemonconfig.DaemonConfig
// How much CPU nydusd is utilizing when starts since full prefetch might
// consume many CPU cycles
StartupCPUUtilization float64
Version types.BuildTimeInfo
ref int32
// Cache the nydusd daemon state to avoid frequently querying nydusd by API.
state types.DaemonState
}
func (d *Daemon) Lock() {
d.mu.Lock()
}
func (d *Daemon) Unlock() {
d.mu.Unlock()
}
func (d *Daemon) ID() string {
return d.States.ID
}
func (d *Daemon) Pid() int {
return d.States.ProcessID
}
func (d *Daemon) IncRef() {
atomic.AddInt32(&d.ref, 1)
}
func (d *Daemon) DecRef() int32 {
return atomic.AddInt32(&d.ref, -1)
}
func (d *Daemon) GetRef() int32 {
return atomic.LoadInt32(&d.ref)
}
func (d *Daemon) HostMountpoint() (mnt string) {
mnt = d.States.Mountpoint
return
}
// Each nydusd daemon has a copy of configuration json file.
func (d *Daemon) ConfigFile(instanceID string) string {
if instanceID == "" {
return filepath.Join(d.States.ConfigDir, "config.json")
}
return filepath.Join(d.States.ConfigDir, instanceID, "config.json")
}
// NydusdThreadNum returns how many working threads are needed of a single nydusd
func (d *Daemon) NydusdThreadNum() int {
return d.States.ThreadNum
}
func (d *Daemon) GetAPISock() string {
return d.States.APISocket
}
func (d *Daemon) LogFile() string {
return filepath.Join(d.States.LogDir, "nydusd.log")
}
func (d *Daemon) AddRafsInstance(r *rafs.Rafs) {
d.RafsCache.Add(r)
d.IncRef()
r.DaemonID = d.ID()
collector.NewDaemonImageCollector(d.ID(), r.ImageID).Collect()
}
func (d *Daemon) UpdateRafsInstance(r *rafs.Rafs) {
d.RafsCache.Add(r)
}
func (d *Daemon) RemoveRafsInstance(snapshotID string) {
if r := d.RafsCache.Get(snapshotID); r != nil {
collector.NewDaemonImageCollector(d.ID(), r.ImageID).Delete()
}
d.RafsCache.Remove(snapshotID)
d.DecRef()
}
// Get and cache daemon current working state by querying nydusd:
// 1. INIT
// 2. READY: All needed resources are ready.
// 3. RUNNING
func (d *Daemon) GetState() (types.DaemonState, error) {
c, err := d.GetClient()
if err != nil {
return types.DaemonStateUnknown, errors.Wrapf(err, "get daemon state")
}
info, err := c.GetDaemonInfo()
if err != nil {
return types.DaemonStateUnknown, err
}
st := info.DaemonState()
d.Lock()
d.state = st
d.Version = info.DaemonVersion()
d.Unlock()
return st, nil
}
// Return the cached nydusd working status, no API is invoked.
func (d *Daemon) State() types.DaemonState {
d.Lock()
defer d.Unlock()
return d.state
}
// Reset the cached nydusd working status
func (d *Daemon) ResetState() {
d.Lock()
defer d.Unlock()
d.state = types.DaemonStateUnknown
}
// Wait for the nydusd daemon to reach specified state with timeout.
func (d *Daemon) WaitUntilState(expected types.DaemonState) error {
return retry.Do(func() error {
if expected == d.State() {
return nil
}
state, err := d.GetState()
if err != nil {
return errors.Wrapf(err, "wait until daemon is %s", expected)
}
if state != expected {
return errors.Errorf("daemon %s is not %s yet, current state %s",
d.ID(), expected, state)
}
return nil
},
retry.LastErrorOnly(true),
retry.Attempts(20), // totally wait for 2 seconds, should be enough
retry.Delay(100*time.Millisecond),
)
}
func (d *Daemon) IsSharedDaemon() bool {
if d.States.DaemonMode != "" {
return d.States.DaemonMode == config.DaemonModeShared
}
return d.HostMountpoint() == config.GetRootMountpoint()
}
func (d *Daemon) SharedMount(rafs *rafs.Rafs) error {
defer d.SendStates()
switch d.States.FsDriver {
case config.FsDriverFscache:
if err := d.sharedErofsMount(rafs); err != nil {
return errors.Wrapf(err, "mount erofs")
}
return nil
case config.FsDriverFusedev:
return d.sharedFusedevMount(rafs)
default:
return errors.Errorf("unsupported fs driver %s", d.States.FsDriver)
}
}
func (d *Daemon) sharedFusedevMount(rafs *rafs.Rafs) error {
client, err := d.GetClient()
if err != nil {
return errors.Wrapf(err, "mount instance %s", rafs.SnapshotID)
}
bootstrap, err := rafs.BootstrapFile()
if err != nil {
return err
}
c, err := daemonconfig.NewDaemonConfig(d.States.FsDriver, d.ConfigFile(rafs.SnapshotID))
if err != nil {
return errors.Wrapf(err, "Failed to reload instance configuration %s",
d.ConfigFile(rafs.SnapshotID))
}
cfg, err := c.DumpString()
if err != nil {
return errors.Wrap(err, "dump instance configuration")
}
err = client.Mount(rafs.RelaMountpoint(), bootstrap, cfg)
if err != nil {
return errors.Wrapf(err, "mount rafs instance")
}
return nil
}
func (d *Daemon) sharedErofsMount(ra *rafs.Rafs) error {
client, err := d.GetClient()
if err != nil {
return errors.Wrapf(err, "bind blob %s", d.ID())
}
// TODO: Why fs cache needing this work dir?
if err := os.MkdirAll(ra.FscacheWorkDir(), 0755); err != nil {
return errors.Wrapf(err, "failed to create fscache work dir %s", ra.FscacheWorkDir())
}
c, err := daemonconfig.NewDaemonConfig(d.States.FsDriver, d.ConfigFile(ra.SnapshotID))
if err != nil {
log.L.Errorf("Failed to reload daemon configuration %s, %s", d.ConfigFile(ra.SnapshotID), err)
return err
}
cfgStr, err := c.DumpString()
if err != nil {
return err
}
if err := client.BindBlob(cfgStr); err != nil {
return errors.Wrapf(err, "request to bind fscache blob")
}
mountPoint := ra.GetMountpoint()
if err := os.MkdirAll(mountPoint, 0755); err != nil {
return errors.Wrapf(err, "create mountpoint %s", mountPoint)
}
fscacheID := erofs.FscacheID(ra.SnapshotID)
cfg := c.(*daemonconfig.FscacheDaemonConfig)
ra.AddAnnotation(rafs.AnnoFsCacheDomainID, cfg.DomainID)
ra.AddAnnotation(rafs.AnnoFsCacheID, fscacheID)
if err := erofs.Mount(cfg.DomainID, fscacheID, mountPoint); err != nil {
if !errdefs.IsErofsMounted(err) {
return errors.Wrapf(err, "mount erofs to %s", mountPoint)
}
// When snapshotter exits (either normally or abnormally), it will not have a
// chance to umount erofs mountpoint, so if snapshotter resumes running and mount
// again (by a new request to create container), it will need to ignore the mount
// error `device or resource busy`.
log.L.Warnf("erofs mountpoint %s has been mounted", mountPoint)
}
return nil
}
func (d *Daemon) SharedUmount(rafs *rafs.Rafs) error {
defer d.SendStates()
switch d.States.FsDriver {
case config.FsDriverFscache:
if err := d.sharedErofsUmount(rafs); err != nil {
return errors.Wrapf(err, "failed to erofs mount")
}
return nil
case config.FsDriverFusedev:
c, err := d.GetClient()
if err != nil {
return errors.Wrapf(err, "umount instance %s", rafs.SnapshotID)
}
return c.Umount(rafs.RelaMountpoint())
default:
return errors.Errorf("unsupported fs driver %s", d.States.FsDriver)
}
}
func (d *Daemon) sharedErofsUmount(ra *rafs.Rafs) error {
c, err := d.GetClient()
if err != nil {
return errors.Wrapf(err, "unbind blob %s", d.ID())
}
domainID := ra.Annotations[rafs.AnnoFsCacheDomainID]
fscacheID := ra.Annotations[rafs.AnnoFsCacheID]
if err := c.UnbindBlob(domainID, fscacheID); err != nil {
return errors.Wrapf(err, "request to unbind fscache blob, domain %s, fscache %s", domainID, fscacheID)
}
mountpoint := ra.GetMountpoint()
if err := erofs.Umount(mountpoint); err != nil {
return errors.Wrapf(err, "umount erofs %s mountpoint, %s", err, mountpoint)
}
// delete fscache bootstrap cache file
// erofs generate fscache cache file for bootstrap with fscacheID
if err := c.UnbindBlob("", fscacheID); err != nil {
log.L.Warnf("delete bootstrap %s err %s", fscacheID, err)
}
return nil
}
func (d *Daemon) UmountRafsInstance(r *rafs.Rafs) error {
if d.IsSharedDaemon() {
if err := d.SharedUmount(r); err != nil {
return errors.Wrapf(err, "umount fs instance %s", r.SnapshotID)
}
}
return nil
}
func (d *Daemon) UmountRafsInstances() error {
if d.IsSharedDaemon() {
d.RafsCache.Lock()
defer d.RafsCache.Unlock()
instances := d.RafsCache.ListLocked()
for _, r := range instances {
if err := d.SharedUmount(r); err != nil {
return errors.Wrapf(err, "umount fs instance %s", r.SnapshotID)
}
}
}
return nil
}
func (d *Daemon) SendStates() {
su := d.Supervisor
if su != nil {
// TODO: This should be optional by checking snapshotter's configuration.
// FIXME: Is it possible the states are overwritten during two API mounts.
// FIXME: What if nydusd does not support sending states.
err := su.FetchDaemonStates(func() error {
if err := d.doSendStates(); err != nil {
return errors.Wrapf(err, "send daemon %s states", d.ID())
}
return nil
})
if err != nil {
log.L.Warnf("Daemon %s does not support sending states, %v", d.ID(), err)
}
}
}
func (d *Daemon) doSendStates() error {
c, err := d.GetClient()
if err != nil {
return errors.Wrapf(err, "send states %s", d.ID())
}
if err := c.SendFd(); err != nil {
return errors.Wrap(err, "request to send states")
}
return nil
}
func (d *Daemon) TakeOver() error {
c, err := d.GetClient()
if err != nil {
return errors.Wrapf(err, "takeover daemon %s", d.ID())
}
if err := c.TakeOver(); err != nil {
return errors.Wrap(err, "request to take over")
}
return nil
}
func (d *Daemon) Start() error {
c, err := d.GetClient()
if err != nil {
return errors.Wrapf(err, "start service")
}
if err := c.Start(); err != nil {
return errors.Wrap(err, "request to start service")
}
return nil
}
func (d *Daemon) Exit() error {
c, err := d.GetClient()
if err != nil {
return errors.Wrapf(err, "start service")
}
if err := c.Exit(); err != nil {
return errors.Wrap(err, "request to exit service")
}
return nil
}
func (d *Daemon) GetDaemonInfo() (*types.DaemonInfo, error) {
c, err := d.GetClient()
if err != nil {
return nil, errors.Wrapf(err, "get daemon information")
}
return c.GetDaemonInfo()
}
func (d *Daemon) GetFsMetrics(sid string) (*types.FsMetrics, error) {
c, err := d.GetClient()
if err != nil {
return nil, errors.Wrapf(err, "get fs metrics")
}
return c.GetFsMetrics(sid)
}
func (d *Daemon) GetInflightMetrics() (*types.InflightMetrics, error) {
c, err := d.GetClient()
if err != nil {
return nil, errors.Wrapf(err, "get inflight metrics")
}
return c.GetInflightMetrics()
}
// UpdateAuthConfig updates the registry auth for the given rafs instance.
// It rewrites the daemon config file on disk and, for basic auth, issues a
// PUT /api/v1/config to hot-reload the credential in the running nydusd.
// Bearer tokens are only persisted to disk (nydusd does not support runtime
// token updates).
func (d *Daemon) UpdateAuthConfig(snapshotID string, kc *auth.PassKeyChain) error {
var configFile, apiID string
if d.IsSharedDaemon() {
configFile = d.ConfigFile(snapshotID)
apiID = "/" + snapshotID
} else {
configFile = d.ConfigFile("")
apiID = "/"
}
cfg, err := daemonconfig.NewDaemonConfig(d.States.FsDriver, configFile)
if err != nil {
return errors.Wrap(err, "load daemon config for auth update")
}
cfg.FillAuth(kc)
if err := cfg.DumpFile(configFile); err != nil {
return errors.Wrap(err, "write updated daemon config")
}
if kc.TokenBase() {
log.L.WithField("daemon", d.ID()).Warn("bearer token updated on disk only; nydusd API does not support runtime token reload")
return nil
}
client, err := d.GetClient()
if err != nil {
return errors.Wrap(err, "get client for auth update")
}
return client.UpdateConfig(apiID, map[string]string{"registry_auth": kc.ToBase64()})
}
func (d *Daemon) GetCacheMetrics(sid string) (*types.CacheMetrics, error) {
c, err := d.GetClient()
if err != nil {
return nil, errors.Wrapf(err, "get cache metrics")
}
return c.GetCacheMetrics(sid)
}
func (d *Daemon) GetClient() (NydusdClient, error) {
d.cmu.Lock()
defer d.cmu.Unlock()
if d.client == nil {
sock := d.GetAPISock()
// The socket file may be residual from a dead nydusd
err := WaitUntilSocketExisted(sock, d.Pid())
if err != nil {
return nil, errors.Wrapf(errdefs.ErrNotFound, "daemon socket %s", sock)
}
client, err := NewNydusClient(sock)
if err != nil {
return nil, errors.Wrapf(err, "create daemon %s client", d.ID())
}
d.client = client
}
return d.client, nil
}
func (d *Daemon) ResetClient() {
d.cmu.Lock()
d.client = nil
d.cmu.Unlock()
}
func (d *Daemon) Terminate() error {
// if we found pid here, we need to kill and wait process to exit, Pid=0 means somehow we lost
// the daemon pid, so that we can't kill the process, just roughly umount the mountpoint
d.Lock()
defer d.Unlock()
if d.Pid() > 0 {
p, err := os.FindProcess(d.Pid())
if err != nil {
return errors.Wrapf(err, "find process %d", d.Pid())
}
if err = p.Signal(syscall.SIGTERM); err != nil {
return errors.Wrapf(err, "send SIGTERM signal to process %d", d.Pid())
}
}
return nil
}
func (d *Daemon) Wait() error {
// if we found pid here, we need to kill and wait process to exit, Pid=0 means somehow we lost
// the daemon pid, so that we can't kill the process, just roughly umount the mountpoint
d.Lock()
defer d.Unlock()
if d.Pid() > 0 {
p, err := os.FindProcess(d.Pid())
if err != nil {
return errors.Wrapf(err, "find process %d", d.Pid())
}
// if nydus-snapshotter restarts, it will break the relationship between nydusd and
// nydus-snapshotter, p.Wait() will return err, so here should exclude this case
if _, err = p.Wait(); err != nil && !errors.Is(err, syscall.ECHILD) {
log.L.Errorf("failed to process wait, %v", err)
} else if d.HostMountpoint() != "" && config.GetFsDriver() == config.FsDriverFusedev {
// No need to umount if the nydusd never performs mount. In other word, it does not
// associate with a host mountpoint.
if err := mount.WaitUntilUnmounted(d.HostMountpoint()); err != nil {
log.L.WithError(err).Errorf("umount %s", d.HostMountpoint())
}
}
}
return nil
}
// When daemon dies, clean up its vestige before start a new one.
func (d *Daemon) ClearVestige() {
mounter := mount.Mounter{}
if d.States.FsDriver == config.FsDriverFscache {
instances := d.RafsCache.List()
for _, i := range instances {
if err := mounter.Umount(i.GetMountpoint()); err != nil {
log.L.Warnf("Can't umount %s, %v", d.States.Mountpoint, err)
}
}
} else {
log.L.Infof("Unmounting %s when clear vestige", d.HostMountpoint())
if err := mounter.Umount(d.HostMountpoint()); err != nil {
log.L.Warnf("Can't umount %s, %v", d.States.Mountpoint, err)
}
}
// Nydusd judges if it should enter failover phrase by checking
// if unix socket is existed and it can't be connected.
if err := os.Remove(d.GetAPISock()); err != nil {
log.L.Warnf("Can't delete residual unix socket %s, %v", d.GetAPISock(), err)
}
// `CheckStatus->ensureClient` only checks if socket file is existed when building http client.
// But the socket file may be residual and will be cleared before starting a new nydusd.
// So clear the client by assigning nil
d.ResetClient()
}
func (d *Daemon) CloneRafsInstances(src *Daemon) {
instances := src.RafsCache.List()
d.RafsCache.SetIntances(instances)
ref := src.GetRef()
for ref > 0 {
d.IncRef()
ref--
}
}
// Daemon must be started and reach RUNNING state before call this method
func (d *Daemon) RecoverRafsInstances() error {
if d.IsSharedDaemon() {
d.RafsCache.Lock()
defer d.RafsCache.Unlock()
instances := make([]*rafs.Rafs, 0, 16)
for _, r := range d.RafsCache.ListLocked() {
instances = append(instances, r)
}
sort.Slice(instances, func(i, j int) bool {
return instances[i].Seq < instances[j].Seq
})
for _, i := range instances {
if d.HostMountpoint() != i.GetMountpoint() {
log.L.Infof("Recovered mount instance %s", i.SnapshotID)
if err := d.SharedMount(i); err != nil {
return err
}
}
}
}
return nil
}
// Instantiate a daemon object
func NewDaemon(opt ...NewDaemonOpt) (*Daemon, error) {
d := &Daemon{}
d.States.ID = newID()
d.States.DaemonMode = config.DaemonModeDedicated
d.RafsCache = rafs.NewRafsCache()
for _, o := range opt {
err := o(d)
if err != nil {
return nil, err
}
}
return d, nil
}
func GetDaemonGitCommit(nydusdPath string) (string, error) {
cmd := exec.Command(nydusdPath, "--version")
output, err := cmd.Output()
if err != nil {
return "", errors.Wrapf(err, "failed to run %s -V", nydusdPath)
}
re := regexp.MustCompile(`Git Commit:\s*(.+)`)
matches := re.FindStringSubmatch(string(output))
if len(matches) > 1 {
return strings.TrimSpace(matches[1]), nil
}
return "", errors.New("Git Commit not found in nydusd -V output")
}
================================================
FILE: pkg/daemon/daemon_test.go
================================================
/*
* Copyright (c) 2026. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package daemon
import (
"encoding/json"
"net"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/containerd/nydus-snapshotter/config"
"github.com/containerd/nydus-snapshotter/config/daemonconfig"
"github.com/containerd/nydus-snapshotter/pkg/auth"
)
func TestMain(m *testing.M) {
// Initialize global config so DumpFile does not panic.
cfg := &config.SnapshotterConfig{}
cfg.Root = os.TempDir()
_ = config.ProcessConfigurations(cfg)
os.Exit(m.Run())
}
// minimalFuseConfig returns the minimal valid FuseDaemonConfig JSON for tests.
func minimalFuseConfig() []byte {
cfg := daemonconfig.FuseDaemonConfig{
Device: &daemonconfig.DeviceConfig{},
Mode: "direct",
}
cfg.Device.Backend.BackendType = "registry"
b, _ := json.Marshal(cfg)
return b
}
func TestUpdateAuthConfig(t *testing.T) {
tests := []struct {
name string
shared bool
snapshotID string
kc *auth.PassKeyChain
wantAPICall bool
wantAPIID string
wantDiskAuth string // expected Auth field on disk after update
}{
{
name: "shared daemon with basic auth",
shared: true,
snapshotID: "snap-1",
kc: &auth.PassKeyChain{Username: "user", Password: "pass"},
wantAPICall: true,
wantAPIID: "/snap-1",
wantDiskAuth: "dXNlcjpwYXNz", // base64("user:pass")
},
{
name: "dedicated daemon with basic auth",
shared: false,
snapshotID: "",
kc: &auth.PassKeyChain{Username: "user", Password: "pass"},
wantAPICall: true,
wantAPIID: "/",
wantDiskAuth: "dXNlcjpwYXNz",
},
{
name: "bearer token skips API call",
shared: false,
snapshotID: "",
kc: &auth.PassKeyChain{Username: "", Password: "mytoken"},
wantAPICall: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := t.TempDir()
// Write minimal fuse config to the expected path.
var configFile string
if tt.shared && tt.snapshotID != "" {
configFile = filepath.Join(dir, tt.snapshotID, "config.json")
} else {
configFile = filepath.Join(dir, "config.json")
}
require.NoError(t, os.MkdirAll(filepath.Dir(configFile), 0o755))
require.NoError(t, os.WriteFile(configFile, minimalFuseConfig(), 0o644))
// Set up mock API server.
var apiCalled bool
var gotID string
var gotBody map[string]string
sock := filepath.Join(dir, "api.sock")
ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
apiCalled = true
gotID = r.URL.Query().Get("id")
_ = json.NewDecoder(r.Body).Decode(&gotBody)
w.WriteHeader(http.StatusNoContent)
}))
listener, err := net.Listen("unix", sock)
require.NoError(t, err)
ts.Listener = listener
ts.Start()
defer ts.Close()
// Build a daemon with the right state.
var daemonMode config.DaemonMode
if tt.shared {
daemonMode = config.DaemonModeShared
} else {
daemonMode = config.DaemonModeDedicated
}
d := &Daemon{
States: ConfigState{
ConfigDir: dir,
FsDriver: config.FsDriverFusedev,
DaemonMode: daemonMode,
APISocket: sock,
},
}
// Pre-create client to avoid WaitUntilSocketExisted.
client, err := NewNydusClient(sock)
require.NoError(t, err)
d.client = client
err = d.UpdateAuthConfig(tt.snapshotID, tt.kc)
require.NoError(t, err)
assert.Equal(t, tt.wantAPICall, apiCalled)
if tt.wantAPICall {
assert.Equal(t, tt.wantAPIID, gotID)
assert.Equal(t, tt.kc.ToBase64(), gotBody["registry_auth"])
}
// Verify config file was updated on disk.
if tt.wantDiskAuth != "" {
cfg, err := daemonconfig.LoadFuseConfig(configFile)
require.NoError(t, err)
assert.Equal(t, tt.wantDiskAuth, cfg.Device.Backend.Config.Auth)
}
})
}
}
================================================
FILE: pkg/daemon/idgen.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package daemon
import (
"github.com/rs/xid"
)
func newID() string {
return xid.New().String()
}
================================================
FILE: pkg/daemon/types/types.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package types
type BuildTimeInfo struct {
PackageVer string `json:"package_ver"`
GitCommit string `json:"git_commit"`
BuildTime string `json:"build_time"`
Profile string `json:"profile"`
Rustc string `json:"rustc"`
}
type DaemonState string
const (
DaemonStateUnknown DaemonState = "UNKNOWN"
DaemonStateInit DaemonState = "INIT"
DaemonStateReady DaemonState = "READY"
DaemonStateRunning DaemonState = "RUNNING"
DaemonStateDied DaemonState = "DIED"
DaemonStateDestroyed DaemonState = "DESTROYED"
)
type DaemonInfo struct {
ID string `json:"id"`
Version BuildTimeInfo `json:"version"`
State DaemonState `json:"state"`
}
func (info *DaemonInfo) DaemonState() DaemonState {
return info.State
}
func (info *DaemonInfo) DaemonVersion() BuildTimeInfo {
return info.Version
}
type ErrorMessage struct {
Code string `json:"code"`
Message string `json:"message"`
}
type MountRequest struct {
FsType string `json:"fs_type"`
Source string `json:"source"`
Config string `json:"config"`
}
func NewMountRequest(source, config string) MountRequest {
return MountRequest{
FsType: "rafs",
Source: source,
Config: config,
}
}
type FsMetrics struct {
FilesAccountEnabled bool `json:"files_account_enabled"`
AccessPatternEnabled bool `json:"access_pattern_enabled"`
MeasureLatency bool `json:"measure_latency"`
ID string `json:"id"`
DataRead uint64 `json:"data_read"`
BlockCountRead []uint64 `json:"block_count_read"`
FopHits []uint64 `json:"fop_hits"`
FopErrors []uint64 `json:"fop_errors"`
FopCumulativeLatencyTotal []uint64 `json:"fop_cumulative_latency_total"`
ReadLatencyDist []uint64 `json:"read_latency_dist"`
NrOpens uint64 `json:"nr_opens"`
}
type InflightMetrics struct {
Values []struct {
Inode uint64 `json:"inode"`
Opcode uint32 `json:"opcode"`
Unique uint64 `json:"unique"`
TimestampSecs uint64 `json:"timestamp_secs"`
}
}
type CacheMetrics struct {
ID string `json:"id"`
UnderlyingFiles []string `json:"underlying_files"`
StorePath string `json:"store_path"`
PartialHits uint64 `json:"partial_hits"`
WholeHits uint64 `json:"whole_hits"`
Total uint64 `json:"total"`
EntriesCount uint64 `json:"entries_count"`
PrefetchDataAmount uint64 `json:"prefetch_data_amount"`
PrefetchRequestsCount uint64 `json:"prefetch_requests_count"`
PrefetchWorkers uint `json:"prefetch_workers"`
PrefetchUnmergedChunks uint64 `json:"prefetch_unmerged_chunks"`
PrefetchCumulativeTimeMillis uint64 `json:"prefetch_cumulative_time_millis"`
PrefetchBeginTimeSecs uint64 `json:"prefetch_begin_time_secs"`
PrefetchBeginTimeMillis uint64 `json:"prefetch_begin_time_millis"`
PrefetchEndTimeSecs uint64 `json:"prefetch_end_time_secs"`
PrefetchEndTimeMillis uint64 `json:"prefetch_end_time_millis"`
BufferedBackendSize uint64 `json:"buffered_backend_size"`
DataAllReady bool `json:"data_all_ready"`
}
================================================
FILE: pkg/encryption/encryption.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package encryption
import (
"context"
"fmt"
"io"
"math/rand"
"github.com/containerd/containerd/v2/core/content"
"github.com/containerd/containerd/v2/core/images"
"github.com/containerd/errdefs"
"github.com/containers/ocicrypt"
encconfig "github.com/containers/ocicrypt/config"
enchelpers "github.com/containers/ocicrypt/helpers"
encocispec "github.com/containers/ocicrypt/spec"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// Copied from containerd/imgcrypt project, copyright The imgcrypt Authors.
// https://github.com/containerd/imgcrypt/blob/e7500301cabcc9f3cab3daee3f541079b509e95f/images/encryption/encryption.go#LL82C5-L82C5
// encryptLayer encrypts the layer using the CryptoConfig and creates a new OCI Descriptor.
// A call to this function may also only manipulate the wrapped keys list.
// The caller is expected to store the returned encrypted data and OCI Descriptor
func encryptLayer(cc *encconfig.CryptoConfig, dataReader content.ReaderAt, desc ocispec.Descriptor) (ocispec.Descriptor, io.Reader, ocicrypt.EncryptLayerFinalizer, error) {
var (
size int64
d digest.Digest
err error
)
encLayerReader, encLayerFinalizer, err := ocicrypt.EncryptLayer(cc.EncryptConfig, ocicrypt.ReaderFromReaderAt(dataReader), desc)
if err != nil {
return ocispec.Descriptor{}, nil, nil, err
}
// were data touched ?
if encLayerReader != nil {
size = 0
d = ""
} else {
size = desc.Size
d = desc.Digest
}
newDesc := ocispec.Descriptor{
Digest: d,
Size: size,
Platform: desc.Platform,
}
switch desc.MediaType {
case images.MediaTypeDockerSchema2LayerGzip:
newDesc.MediaType = encocispec.MediaTypeLayerGzipEnc
case images.MediaTypeDockerSchema2Layer:
newDesc.MediaType = encocispec.MediaTypeLayerEnc
case encocispec.MediaTypeLayerGzipEnc:
newDesc.MediaType = encocispec.MediaTypeLayerGzipEnc
case encocispec.MediaTypeLayerZstdEnc:
newDesc.MediaType = encocispec.MediaTypeLayerZstdEnc
case encocispec.MediaTypeLayerEnc:
newDesc.MediaType = encocispec.MediaTypeLayerEnc
// TODO: Mediatypes to be added in ocispec
case ocispec.MediaTypeImageLayerGzip:
newDesc.MediaType = encocispec.MediaTypeLayerGzipEnc
case ocispec.MediaTypeImageLayerZstd:
newDesc.MediaType = encocispec.MediaTypeLayerZstdEnc
case ocispec.MediaTypeImageLayer:
newDesc.MediaType = encocispec.MediaTypeLayerEnc
default:
return ocispec.Descriptor{}, nil, nil, fmt.Errorf("unsupporter layer MediaType: %s", desc.MediaType)
}
return newDesc, encLayerReader, encLayerFinalizer, nil
}
// Copied from containerd/imgcrypt project, copyright The imgcrypt Authors.
// https://github.com/containerd/imgcrypt/blob/e7500301cabcc9f3cab3daee3f541079b509e95f/images/encryption/encryption.go#LL164C11-L164C11
// decryptLayer decrypts the layer using the CryptoConfig and creates a new OCI Descriptor.
// The caller is expected to store the returned plain data and OCI Descriptor
func decryptLayer(cc *encconfig.CryptoConfig, dataReader content.ReaderAt, desc ocispec.Descriptor, unwrapOnly bool) (ocispec.Descriptor, io.Reader, error) {
resultReader, d, err := ocicrypt.DecryptLayer(cc.DecryptConfig, ocicrypt.ReaderFromReaderAt(dataReader), desc, unwrapOnly)
if err != nil || unwrapOnly {
return ocispec.Descriptor{}, nil, err
}
newDesc := ocispec.Descriptor{
Digest: d,
Size: 0,
Platform: desc.Platform,
}
switch desc.MediaType {
case encocispec.MediaTypeLayerGzipEnc:
newDesc.MediaType = images.MediaTypeDockerSchema2LayerGzip
case encocispec.MediaTypeLayerZstdEnc:
newDesc.MediaType = ocispec.MediaTypeImageLayerZstd
case encocispec.MediaTypeLayerEnc:
newDesc.MediaType = images.MediaTypeDockerSchema2Layer
default:
return ocispec.Descriptor{}, nil, fmt.Errorf("unsupporter layer MediaType: %s", desc.MediaType)
}
return newDesc, resultReader, nil
}
// Copied from containerd/imgcrypt project, copyright The imgcrypt Authors.
// https://github.com/containerd/imgcrypt/blob/e7500301cabcc9f3cab3daee3f541079b509e95f/images/encryption/encryption.go#LL250C5-L250C5
func ingestReader(ctx context.Context, cs content.Ingester, ref string, r io.Reader) (digest.Digest, int64, error) {
cw, err := content.OpenWriter(ctx, cs, content.WithRef(ref))
if err != nil {
return "", 0, fmt.Errorf("failed to open writer: %w", err)
}
defer cw.Close()
if _, err := content.CopyReader(cw, r); err != nil {
return "", 0, fmt.Errorf("copy failed: %w", err)
}
st, err := cw.Status()
if err != nil {
return "", 0, fmt.Errorf("failed to get state: %w", err)
}
if err := cw.Commit(ctx, st.Offset, ""); err != nil {
if !errdefs.IsAlreadyExists(err) {
return "", 0, fmt.Errorf("failed commit on ref %q: %w", ref, err)
}
}
return cw.Digest(), st.Offset, nil
}
// Encrypt Nydus bootstrap layer
func EncryptNydusBootstrap(ctx context.Context, cs content.Store, desc ocispec.Descriptor, encryptRecipients []string) (ocispec.Descriptor, error) {
var (
resultReader io.Reader
newDesc ocispec.Descriptor
encLayerFinalizer ocicrypt.EncryptLayerFinalizer
)
dataReader, err := cs.ReaderAt(ctx, desc)
if err != nil {
return ocispec.Descriptor{}, err
}
defer dataReader.Close()
cc, err := enchelpers.CreateCryptoConfig(encryptRecipients, []string{})
if err != nil {
return ocispec.Descriptor{}, fmt.Errorf("create encrypt config failed: %w", err)
}
newDesc, resultReader, encLayerFinalizer, err = encryptLayer(&cc, dataReader, desc)
if err != nil {
return ocispec.Descriptor{}, fmt.Errorf("failed to encrypt bootstrap layer: %w", err)
}
newDesc.Annotations = ocicrypt.FilterOutAnnotations(desc.Annotations)
// some operations, such as changing recipients, may not touch the layer at all
if resultReader != nil {
var ref string
// If we have the digest, write blob with checks
haveDigest := newDesc.Digest.String() != ""
if haveDigest {
ref = fmt.Sprintf("encrypted-bootstrap-%s", newDesc.Digest.String())
} else {
ref = fmt.Sprintf("encrypted-bootstrap-%d-%d", rand.Int(), rand.Int())
}
if haveDigest {
// Write blob if digest is known beforehand
if err := content.WriteBlob(ctx, cs, ref, resultReader, newDesc); err != nil {
return ocispec.Descriptor{}, fmt.Errorf("failed to write config: %w", err)
}
} else {
newDesc.Digest, newDesc.Size, err = ingestReader(ctx, cs, ref, resultReader)
if err != nil {
return ocispec.Descriptor{}, err
}
}
}
// After performing encryption, call finalizer to get annotations
if encLayerFinalizer != nil {
annotations, err := encLayerFinalizer()
if err != nil {
return ocispec.Descriptor{}, fmt.Errorf("error getting annotations from encLayer finalizer: %w", err)
}
for k, v := range annotations {
newDesc.Annotations[k] = v
}
}
return newDesc, err
}
// Decrypt the Nydus boostrap layer.
// If unwrapOnly is set we will only try to decrypt the layer encryption key and return,
// the layer itself won't be decrypted actually.
func DeryptNydusBootstrap(ctx context.Context, cs content.Store, desc ocispec.Descriptor, decryptKeys []string, unwrapOnly bool) (ocispec.Descriptor, error) {
var (
resultReader io.Reader
newDesc ocispec.Descriptor
)
dataReader, err := cs.ReaderAt(ctx, desc)
if err != nil {
return ocispec.Descriptor{}, err
}
defer dataReader.Close()
cc, err := enchelpers.CreateCryptoConfig([]string{}, decryptKeys)
if err != nil {
return ocispec.Descriptor{}, fmt.Errorf("create decrypt config failed: %w", err)
}
newDesc, resultReader, err = decryptLayer(&cc, dataReader, desc, unwrapOnly)
if err != nil || unwrapOnly {
return ocispec.Descriptor{}, fmt.Errorf("failed to decrypt bootstrap layer: %w", err)
}
newDesc.Annotations = ocicrypt.FilterOutAnnotations(desc.Annotations)
// some operations, such as changing recipients, may not touch the layer at all
if resultReader != nil {
var ref string
// If we have the digest, write blob with checks
haveDigest := newDesc.Digest.String() != ""
if haveDigest {
ref = fmt.Sprintf("decrypted-bootstrap-%s", newDesc.Digest.String())
} else {
ref = fmt.Sprintf("decrypted-bootstrap-%d-%d", rand.Int(), rand.Int())
}
if haveDigest {
// Write blob if digest is known beforehand
if err := content.WriteBlob(ctx, cs, ref, resultReader, newDesc); err != nil {
return ocispec.Descriptor{}, fmt.Errorf("failed to write config: %w", err)
}
} else {
newDesc.Digest, newDesc.Size, err = ingestReader(ctx, cs, ref, resultReader)
if err != nil {
return ocispec.Descriptor{}, err
}
}
}
return newDesc, err
}
================================================
FILE: pkg/errdefs/errors.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package errdefs
import (
stderrors "errors"
"net"
"syscall"
"github.com/containerd/errdefs"
"github.com/pkg/errors"
)
var (
ErrAlreadyExists = errdefs.ErrAlreadyExists
ErrNotFound = errdefs.ErrNotFound
ErrInvalidArgument = errors.New("invalid argument")
ErrUnavailable = errors.New("unavailable")
ErrNotImplemented = errors.New("not implemented") // represents not supported and unimplemented
ErrDeviceBusy = errors.New("device busy") // represents not supported and unimplemented
)
// IsAlreadyExists returns true if the error is due to already exists
func IsAlreadyExists(err error) bool {
return errors.Is(err, ErrAlreadyExists)
}
// IsNotFound returns true if the error is due to a missing object
func IsNotFound(err error) bool {
return errors.Is(err, ErrNotFound)
}
// IsConnectionClosed returns true if error is due to connection closed
// this is used when snapshotter closed by sig term
func IsConnectionClosed(err error) bool {
switch err := err.(type) {
case *net.OpError:
return err.Err.Error() == "use of closed network connection"
default:
return false
}
}
func IsErofsMounted(err error) bool {
return stderrors.Is(err, syscall.EBUSY)
}
================================================
FILE: pkg/fanotify/conn/conn.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package conn
import (
"bufio"
"encoding/json"
)
type Client struct {
Reader *bufio.Reader
}
type EventInfo struct {
Path string `json:"path"`
Size uint32 `json:"size"`
Elapsed uint64 `json:"elapsed"`
}
func (c *Client) GetEventInfo() (*EventInfo, error) {
eventInfo := EventInfo{}
eventByte, err := c.Reader.ReadBytes('\n')
if err != nil {
return nil, err
}
if err := json.Unmarshal(eventByte, &eventInfo); err != nil {
return nil, err
}
return &eventInfo, nil
}
================================================
FILE: pkg/fanotify/fanotify.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package fanotify
import (
"bufio"
"encoding/csv"
"fmt"
"io"
"log/syslog"
"os"
"os/exec"
"syscall"
"time"
"github.com/containerd/nydus-snapshotter/pkg/fanotify/conn"
"github.com/containerd/nydus-snapshotter/pkg/utils/display"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
type Server struct {
BinaryPath string
ContainerPid uint32
ImageName string
PersistFile string
Readable bool
Overwrite bool
Timeout time.Duration
Client *conn.Client
Cmd *exec.Cmd
LogWriter *syslog.Writer
}
func NewServer(binaryPath string, containerPid uint32, imageName string, persistFile string, readable bool, overwrite bool, timeout time.Duration, logWriter *syslog.Writer) *Server {
return &Server{
BinaryPath: binaryPath,
ContainerPid: containerPid,
ImageName: imageName,
PersistFile: persistFile,
Readable: readable,
Overwrite: overwrite,
Timeout: timeout,
LogWriter: logWriter,
}
}
func (fserver *Server) RunServer() error {
if !fserver.Overwrite {
if file, err := os.Stat(fserver.PersistFile); err == nil && !file.IsDir() {
return nil
}
}
cmd := exec.Command(fserver.BinaryPath)
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWNS,
Setpgid: true,
}
cmd.Env = append(cmd.Env, "_MNTNS_PID="+fmt.Sprint(fserver.ContainerPid))
cmd.Env = append(cmd.Env, "_TARGET=/")
cmd.Stderr = fserver.LogWriter
notifyR, err := cmd.StdoutPipe()
if err != nil {
return err
}
fserver.Client = &conn.Client{
Reader: bufio.NewReader(notifyR),
}
if err := cmd.Start(); err != nil {
return err
}
fserver.Cmd = cmd
go func() {
if err := cmd.Wait(); err != nil {
logrus.WithError(err).Errorf("Failed to wait for fserver to finish")
}
}()
go func() {
if err := fserver.RunReceiver(); err != nil {
logrus.WithError(err).Errorf("Failed to receive event information from server")
}
}()
if fserver.Timeout > 0 {
go func() {
time.Sleep(fserver.Timeout)
fserver.StopServer()
}()
}
return nil
}
func (fserver *Server) RunReceiver() error {
f, err := os.OpenFile(fserver.PersistFile, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return errors.Wrapf(err, "failed to open file %q", fserver.PersistFile)
}
defer f.Close()
persistCsvFile := fmt.Sprintf("%s.csv", fserver.PersistFile)
fCsv, err := os.Create(persistCsvFile)
if err != nil {
return errors.Wrapf(err, "failed to create file %q", persistCsvFile)
}
defer fCsv.Close()
csvWriter := csv.NewWriter(fCsv)
if err := csvWriter.Write([]string{"path", "size", "elapsed"}); err != nil {
return errors.Wrapf(err, "failed to write csv header")
}
csvWriter.Flush()
for {
eventInfo, err := fserver.Client.GetEventInfo()
if err != nil {
if err == io.EOF {
logrus.Infoln("Get EOF from fanotify server, break event receiver")
break
}
return fmt.Errorf("failed to get event information: %v", err)
}
if eventInfo != nil {
fmt.Fprintln(f, eventInfo.Path)
var line []string
if fserver.Readable {
line = []string{eventInfo.Path, display.ByteToReadableIEC(eventInfo.Size), display.MicroSecondToReadable(eventInfo.Elapsed)}
} else {
line = []string{eventInfo.Path, fmt.Sprint(eventInfo.Size), fmt.Sprint(eventInfo.Elapsed)}
}
if err := csvWriter.Write(line); err != nil {
return errors.Wrapf(err, "failed to write csv")
}
csvWriter.Flush()
}
}
return nil
}
func (fserver *Server) StopServer() {
if fserver.Cmd != nil {
logrus.Infof("Send SIGTERM signal to process group %d", fserver.Cmd.Process.Pid)
if err := syscall.Kill(-fserver.Cmd.Process.Pid, syscall.SIGTERM); err != nil {
logrus.WithError(err).Errorf("Stop process group %d failed!", fserver.Cmd.Process.Pid)
}
if _, err := fserver.Cmd.Process.Wait(); err != nil {
logrus.WithError(err).Errorf("Failed to wait for fanotify server")
}
}
}
================================================
FILE: pkg/filesystem/config.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package filesystem
import (
"github.com/containerd/nydus-snapshotter/pkg/cache"
"github.com/containerd/nydus-snapshotter/pkg/index"
"github.com/containerd/nydus-snapshotter/pkg/manager"
"github.com/containerd/nydus-snapshotter/pkg/referrer"
"github.com/containerd/nydus-snapshotter/pkg/signature"
"github.com/containerd/nydus-snapshotter/pkg/stargz"
"github.com/containerd/nydus-snapshotter/pkg/tarfs"
"github.com/pkg/errors"
)
type NewFSOpt func(d *Filesystem) error
func WithNydusdBinaryPath(p string) NewFSOpt {
return func(fs *Filesystem) error {
fs.nydusdBinaryPath = p
return nil
}
}
func WithManagers(managers []*manager.Manager) NewFSOpt {
return func(fs *Filesystem) error {
if fs.enabledManagers == nil {
fs.enabledManagers = map[string]*manager.Manager{}
}
for _, pm := range managers {
fs.enabledManagers[pm.FsDriver] = pm
}
return nil
}
}
func WithCacheManager(cm *cache.Manager) NewFSOpt {
return func(fs *Filesystem) error {
if cm == nil {
return errors.New("cache manager cannot be nil")
}
fs.cacheMgr = cm
return nil
}
}
func WithReferrerManager(rm *referrer.Manager) NewFSOpt {
return func(fs *Filesystem) error {
if rm == nil {
return errors.New("referrer manager cannot be nil")
}
fs.referrerMgr = rm
return nil
}
}
func WithIndexManager(im *index.Manager) NewFSOpt {
return func(fs *Filesystem) error {
if im == nil {
return errors.New("index manager cannot be nil")
}
fs.indexMgr = im
return nil
}
}
func WithTarfsManager(tm *tarfs.Manager) NewFSOpt {
return func(fs *Filesystem) error {
if tm == nil {
return errors.New("tarfs manager cannot be nil")
}
fs.tarfsMgr = tm
return nil
}
}
func WithVerifier(verifier *signature.Verifier) NewFSOpt {
return func(fs *Filesystem) error {
fs.verifier = verifier
return nil
}
}
func WithRootMountpoint(mountpoint string) NewFSOpt {
return func(fs *Filesystem) error {
fs.rootMountpoint = mountpoint
return nil
}
}
func WithEnableStargz(enable bool) NewFSOpt {
return func(fs *Filesystem) error {
if enable {
fs.stargzResolver = stargz.NewResolver()
}
return nil
}
}
================================================
FILE: pkg/filesystem/fs.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
// Abstraction layer of underlying file systems. The file system could be mounted by one
// or more nydusd daemons. fs package hides the details
package filesystem
import (
"context"
"io"
"os"
"path"
"path/filepath"
"sync"
"github.com/containerd/containerd/v2/core/snapshots"
"github.com/containerd/containerd/v2/core/snapshots/storage"
snpkg "github.com/containerd/containerd/v2/pkg/snapshotters"
"github.com/containerd/log"
"github.com/mohae/deepcopy"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
"github.com/containerd/nydus-snapshotter/config"
"github.com/containerd/nydus-snapshotter/config/daemonconfig"
"github.com/containerd/nydus-snapshotter/pkg/cache"
"github.com/containerd/nydus-snapshotter/pkg/daemon"
"github.com/containerd/nydus-snapshotter/pkg/daemon/types"
"github.com/containerd/nydus-snapshotter/pkg/errdefs"
"github.com/containerd/nydus-snapshotter/pkg/index"
"github.com/containerd/nydus-snapshotter/pkg/label"
"github.com/containerd/nydus-snapshotter/pkg/manager"
racache "github.com/containerd/nydus-snapshotter/pkg/rafs"
"github.com/containerd/nydus-snapshotter/pkg/referrer"
"github.com/containerd/nydus-snapshotter/pkg/signature"
"github.com/containerd/nydus-snapshotter/pkg/stargz"
"github.com/containerd/nydus-snapshotter/pkg/tarfs"
"github.com/containerd/nydus-snapshotter/pkg/utils/erofs"
)
type Filesystem struct {
fusedevSharedDaemon *daemon.Daemon
fscacheSharedDaemon *daemon.Daemon
enabledManagers map[string]*manager.Manager
cacheMgr *cache.Manager
referrerMgr *referrer.Manager
indexMgr *index.Manager
stargzResolver *stargz.Resolver
tarfsMgr *tarfs.Manager
verifier *signature.Verifier
nydusdBinaryPath string
rootMountpoint string
snapshotMutexMap sync.Map
}
// NewFileSystem initialize Filesystem instance
// It does mount image layers by starting nydusd doing FUSE mount or not.
func NewFileSystem(ctx context.Context, opt ...NewFSOpt) (*Filesystem, error) {
var fs Filesystem
for _, o := range opt {
err := o(&fs)
if err != nil {
return nil, err
}
}
if config.GetDaemonMode() == config.DaemonModeNone {
return &fs, nil
}
recoveringDaemons := make(map[string]*daemon.Daemon, 0)
liveDaemons := make(map[string]*daemon.Daemon, 0)
for _, fsManager := range fs.enabledManagers {
err := fsManager.Recover(ctx, &recoveringDaemons, &liveDaemons)
if err != nil {
return nil, errors.Wrap(err, "reconnect daemons and recover filesystem instance")
}
}
var hasFscacheSharedDaemon = false
var hasFusedevSharedDaemon = false
for _, daemon := range liveDaemons {
if daemon.States.FsDriver == config.FsDriverFscache {
hasFscacheSharedDaemon = true
} else if daemon.States.FsDriver == config.FsDriverFusedev && daemon.IsSharedDaemon() {
hasFusedevSharedDaemon = true
}
}
for _, daemon := range recoveringDaemons {
if daemon.States.FsDriver == config.FsDriverFscache {
hasFscacheSharedDaemon = true
} else if daemon.States.FsDriver == config.FsDriverFusedev && daemon.IsSharedDaemon() {
hasFusedevSharedDaemon = true
}
}
// Try to bring up the shared daemon early.
// With found recovering daemons, it must be the case that snapshotter is being restarted.
// Situations that shared daemon is not found:
// 1. The first time this nydus-snapshotter runs
// 2. Daemon record is wrongly deleted from DB. Above reconnecting already gathers
// all daemons but still not found shared daemon. The best workaround is to start
// a new nydusd for it.
// TODO: We still need to consider shared daemon the time sequence of initializing daemon,
// start daemon commit its state to DB and retrieving its state.
if fscacheManager, ok := fs.enabledManagers[config.FsDriverFscache]; ok {
if !hasFscacheSharedDaemon && fs.fscacheSharedDaemon == nil {
log.L.Infof("initializing shared nydus daemon for fscache")
if err := fs.initSharedDaemon(fscacheManager); err != nil {
return nil, errors.Wrap(err, "start shared nydusd daemon for fscache")
}
}
} else if hasFscacheSharedDaemon {
return nil, errors.Errorf("shared fscache daemon is present, but manager is missing")
}
if fusedevManager, ok := fs.enabledManagers[config.FsDriverFusedev]; ok {
if config.IsFusedevSharedModeEnabled() && !hasFusedevSharedDaemon && fs.fusedevSharedDaemon == nil {
log.L.Infof("initializing shared nydus daemon for fusedev")
if err := fs.initSharedDaemon(fusedevManager); err != nil {
return nil, errors.Wrap(err, "start shared nydusd daemon for fusedev")
}
}
} else if hasFusedevSharedDaemon {
return nil, errors.Errorf("shared fusedev daemon is present, but manager is missing")
}
// Try to bring all persisted and stopped nydusd up and remount Rafs
egRecover, _ := errgroup.WithContext(context.Background())
for _, d := range recoveringDaemons {
d := d
egRecover.Go(func() error {
d.ClearVestige()
fsManager, err := fs.getManager(d.States.FsDriver)
if err != nil {
log.L.Warnf("Failed to get filesystem manager for daemon %s, skipping recovery: %v", d.States.ID, err)
return nil
}
if err := fsManager.StartDaemon(d); err != nil {
log.L.Warnf("Failed to start daemon %s during recovery, skipping: %v", d.ID(), err)
return nil
}
if err := d.WaitUntilState(types.DaemonStateRunning); err != nil {
log.L.Warnf("Failed to wait for daemon %s to become running, skipping: %v", d.ID(), err)
return nil
}
if err := d.RecoverRafsInstances(); err != nil {
log.L.Warnf("Failed to recover mounts for daemon %s, skipping: %v", d.ID(), err)
return nil
}
fs.TryRetainSharedDaemon(d)
return nil
})
}
if err := egRecover.Wait(); err != nil {
return nil, err
}
// Only check nydusd binary commit when there are live daemons that may need hot upgrade.
if len(liveDaemons) > 0 {
newNydusImageBinaryCommit, err := daemon.GetDaemonGitCommit(fs.nydusdBinaryPath)
if err != nil {
return nil, errors.Wrapf(err, "failed to get git commit from nydusd binary at path: %s", fs.nydusdBinaryPath)
}
egLive, _ := errgroup.WithContext(context.Background())
for _, d := range liveDaemons {
d := d
egLive.Go(func() error {
if d.Supervisor == nil {
log.L.Warnf("Daemon %s is skipped for hot upgrade because recover policy is not set to 'failover'", d.ID())
return nil
}
daemonInfo, err := d.GetDaemonInfo()
if err != nil {
log.L.Warnf("Failed to get daemon info from daemon %s, skipping: %v", d.ID(), err)
return nil
}
if newNydusImageBinaryCommit != daemonInfo.DaemonVersion().GitCommit {
fsManager, err := fs.getManager(d.States.FsDriver)
if err != nil {
log.L.Warnf("Failed to get filesystem manager for daemon %s, skipping: %v", d.ID(), err)
return nil
}
newDaemon, upgradeErr := fsManager.DoDaemonUpgrade(d, fs.nydusdBinaryPath, fsManager)
if upgradeErr != nil {
log.L.Warnf("Daemon %s hot upgrade failed, skipping: %v", d.ID(), upgradeErr)
return nil
}
fs.TryRetainSharedDaemon(newDaemon)
} else {
fs.TryRetainSharedDaemon(d)
}
return nil
})
}
if err := egLive.Wait(); err != nil {
return nil, err
}
}
return &fs, nil
}
func (fs *Filesystem) TryRetainSharedDaemon(d *daemon.Daemon) {
switch d.States.FsDriver {
case config.FsDriverFscache:
if fs.fscacheSharedDaemon == nil {
log.L.Debug("retain fscache shared daemon")
fs.fscacheSharedDaemon = d
d.IncRef()
}
case config.FsDriverFusedev:
if fs.fusedevSharedDaemon == nil && d.HostMountpoint() == fs.rootMountpoint {
log.L.Debug("retain fusedev shared daemon")
fs.fusedevSharedDaemon = d
d.IncRef()
}
}
}
func (fs *Filesystem) TryStopSharedDaemon() {
if fs.fusedevSharedDaemon != nil {
if fs.fusedevSharedDaemon.GetRef() == 1 {
if fusedevManager, ok := fs.enabledManagers[config.FsDriverFusedev]; ok {
if err := fusedevManager.DestroyDaemon(fs.fusedevSharedDaemon); err != nil {
log.L.WithError(err).Errorf("Terminate shared daemon %s failed", fs.fusedevSharedDaemon.ID())
} else {
fs.fusedevSharedDaemon = nil
}
}
}
}
if fs.fscacheSharedDaemon != nil {
if fs.fscacheSharedDaemon.GetRef() == 1 {
if fscacheManager, ok := fs.enabledManagers[config.FsDriverFscache]; ok {
if err := fscacheManager.DestroyDaemon(fs.fscacheSharedDaemon); err != nil {
log.L.WithError(err).Errorf("Terminate shared daemon %s failed", fs.fscacheSharedDaemon.ID())
} else {
fs.fscacheSharedDaemon = nil
}
}
}
}
}
// WaitUntilReady wait until daemon ready by snapshotID, it will wait until nydus domain socket established
// and the status of nydusd daemon must be ready
func (fs *Filesystem) WaitUntilReady(snapshotID string) error {
rafs := racache.RafsGlobalCache.Get(snapshotID)
if rafs == nil {
// If NoneDaemon mode, there's no need to wait for daemon ready
if config.GetDaemonMode() == config.DaemonModeNone {
return nil
}
return errors.Wrapf(errdefs.ErrNotFound, "no instance %s", snapshotID)
}
if rafs.GetFsDriver() == config.FsDriverFscache || rafs.GetFsDriver() == config.FsDriverFusedev {
d, err := fs.getDaemonByRafs(rafs)
if err != nil {
return errors.Wrapf(err, "snapshot id %s daemon id %s", snapshotID, rafs.DaemonID)
}
if err := d.WaitUntilState(types.DaemonStateRunning); err != nil {
return err
}
// For shared daemons, we need to use the correct cache ID to query metrics.
// For fscache, the cache is registered with fscacheID (a digest), not the raw snapshot ID.
// For fusedev, the cache is registered with the snapshot ID.
sid := ""
if d.IsSharedDaemon() {
if rafs.GetFsDriver() == config.FsDriverFscache {
// For fscache, use the fscache ID from annotations if available
if fscacheID, ok := rafs.Annotations[racache.AnnoFsCacheID]; ok && fscacheID != "" {
sid = fscacheID
} else {
// Fallback: compute fscacheID if not in annotations yet
sid = erofs.FscacheID(rafs.SnapshotID)
}
} else {
// For fusedev, use the snapshot ID directly
sid = rafs.SnapshotID
}
}
cacheMetrics, err := d.GetCacheMetrics(sid)
if err != nil {
return errors.Wrapf(err, "failed to get cache metric")
}
log.L.Debugf("Found %d underlying files for rafs instance %s", len(cacheMetrics.UnderlyingFiles), rafs.SnapshotID)
// Lock the daemon's RafsCache when modifying rafs fields to prevent
// race conditions with concurrent reads (e.g., during cache cleanup)
d.RafsCache.Lock()
rafs.UnderlyingFiles = cacheMetrics.UnderlyingFiles
d.RafsCache.Unlock()
fsManager, err := fs.getManager(rafs.GetFsDriver())
if err != nil {
return errors.Wrap(err, "failed to get manager")
}
err = fsManager.UpdateRafsInstance(rafs)
if err != nil {
return errors.Wrap(err, "failed to update rafs instance")
}
log.L.Debugf("Nydus remote snapshot %s is ready", snapshotID)
}
return nil
}
// Mount will be called when containerd snapshotter prepare remote snapshotter
// this method will fork nydus daemon and manage it in the internal store, and indexed by snapshotID
// It must set up all necessary resources during Mount procedure and revoke any step if necessary.
func (fs *Filesystem) Mount(ctx context.Context, snapshotID string, labels map[string]string, s *storage.Snapshot) (err error) {
mu := fs.getSnapshotMutex(snapshotID)
mu.Lock()
defer mu.Unlock()
rafs := racache.RafsGlobalCache.Get(snapshotID)
if rafs != nil {
// Instance already exists, how could this happen? Can containerd handle this case?
return nil
}
fsDriver := config.GetFsDriver()
if label.IsTarfsDataLayer(labels) {
fsDriver = config.FsDriverBlockdev
}
isSharedFusedev := fsDriver == config.FsDriverFusedev && config.GetDaemonMode() == config.DaemonModeShared
useSharedDaemon := fsDriver == config.FsDriverFscache || isSharedFusedev
var imageID string
imageID, ok := labels[snpkg.TargetRefLabel]
if !ok {
// FIXME: Buildkit does not pass labels defined in containerd's fashion. So
// we have to use stargz snapshotter specific labels until Buildkit generalize it the necessary
// labels for all remote snapshotters.
imageID, ok = labels["containerd.io/snapshot/remote/stargz.reference"]
if !ok {
return errors.Errorf("failed to find image ref of snapshot %s, labels %v", snapshotID, labels)
}
}
rafs, err = racache.NewRafs(snapshotID, imageID, fsDriver)
if err != nil {
return errors.Wrapf(err, "create rafs instance %s", snapshotID)
}
defer func() {
if err != nil {
if umountErr := fs.Umount(ctx, snapshotID); umountErr != nil {
log.L.WithError(umountErr).Warnf("failed to umount snapshot %s during cleanup", snapshotID)
}
racache.RafsGlobalCache.Remove(snapshotID)
}
}()
fsManager, err := fs.getManager(fsDriver)
if err != nil {
return errors.Wrapf(err, "get filesystem manager for snapshot %s", snapshotID)
}
var d *daemon.Daemon
if fsDriver == config.FsDriverFscache || fsDriver == config.FsDriverFusedev {
bootstrap, err := rafs.BootstrapFile()
if err != nil {
return errors.Wrapf(err, "find bootstrap file snapshot %s", snapshotID)
}
// Nydusd uses cache manager's directory to store blob caches. So cache
// manager knows where to find those blobs.
cacheDir := fs.cacheMgr.CacheDir()
if err := fs.copyBlobMetaFiles(bootstrap, cacheDir); err != nil {
log.L.Warnf("Failed to copy blob.meta files to cache: %v", err)
}
if useSharedDaemon {
d, err = fs.getSharedDaemon(fsDriver)
if err != nil {
return err
}
} else {
mp, err := fs.decideDaemonMountpoint(fsDriver, false, rafs)
if err != nil {
return err
}
d, err = fs.createDaemon(fsManager, config.DaemonModeDedicated, mp, 0)
// if daemon already exists for snapshotID, just return
if err != nil && !errdefs.IsAlreadyExists(err) {
return err
}
}
// Fscache driver stores blob cache bitmap and blob header files here
workDir := rafs.FscacheWorkDir()
params := map[string]string{
daemonconfig.Bootstrap: bootstrap,
daemonconfig.WorkDir: workDir,
daemonconfig.CacheDir: cacheDir,
}
cfg := deepcopy.Copy(*fsManager.DaemonConfig).(daemonconfig.DaemonConfig)
err = daemonconfig.SupplementDaemonConfig(cfg, imageID, snapshotID, false, labels, params)
if err != nil {
return errors.Wrap(err, "supplement configuration")
}
// TODO: How to manage rafs configurations on-disk? separated json config file or DB record?
// In order to recover erofs mount, the configuration file has to be persisted.
var configSubDir string
if useSharedDaemon {
configSubDir = snapshotID
} else {
// Associate daemon config object when creating a new daemon object to avoid
// reading disk file again and again.
// For shared daemon, each rafs instance has its own configuration, so we don't
// attach a config interface to daemon in this case.
d.Config = cfg
}
err = cfg.DumpFile(d.ConfigFile(configSubDir))
if err != nil {
if errors.Is(err, errdefs.ErrAlreadyExists) {
log.L.Debugf("Configuration file %s already exits", d.ConfigFile(configSubDir))
} else {
return errors.Wrap(err, "dump daemon configuration file")
}
}
d.AddRafsInstance(rafs)
// if publicKey is not empty we should verify bootstrap file of image
err = fs.verifier.Verify(labels, bootstrap)
if err != nil {
return errors.Wrapf(err, "verify signature of daemon %s", d.ID())
}
}
switch fsDriver {
case config.FsDriverFscache:
err = fs.mountRemote(fsManager, useSharedDaemon, d, rafs)
if err != nil {
err = errors.Wrapf(err, "mount file system by daemon %s, snapshot %s", d.ID(), snapshotID)
}
case config.FsDriverFusedev:
err = fs.mountRemote(fsManager, useSharedDaemon, d, rafs)
if err != nil {
err = errors.Wrapf(err, "mount file system by daemon %s, snapshot %s", d.ID(), snapshotID)
}
case config.FsDriverBlockdev:
err = fs.tarfsMgr.MountTarErofs(snapshotID, s, labels, rafs)
if err != nil {
err = errors.Wrapf(err, "mount tarfs for snapshot %s", snapshotID)
}
case config.FsDriverNodev:
// Nothing to do
case config.FsDriverProxy:
if label.IsNydusProxyMode(labels) {
if v, ok := labels[label.CRILayerDigest]; ok {
rafs.AddAnnotation(label.CRILayerDigest, v)
}
rafs.AddAnnotation(label.NydusProxyMode, "true")
rafs.SetMountpoint(path.Join(rafs.GetSnapshotDir(), "fs"))
}
default:
err = errors.Errorf("unknown filesystem driver %s for snapshot %s", fsDriver, snapshotID)
}
// Persist it after associate instance after all the states are calculated.
if err == nil {
if err := fsManager.AddRafsInstance(rafs); err != nil {
// In the CoCo scenario, the existence of a rafs instance is not a concern, as the CoCo guest image pull
// does not utilize snapshots on the host. Therefore, we expect it to pass normally regardless of its existence.
// However, for the convenience of troubleshooting, we tend to print relevant logs.
if config.GetFsDriver() == config.FsDriverProxy {
log.L.Warnf("RAFS instance has associated with snapshot %s possibly: %v", snapshotID, err)
return nil
}
return errors.Wrapf(err, "create instance %s", snapshotID)
}
}
return nil
}
func (fs *Filesystem) getSnapshotMutex(snapshotID string) *sync.Mutex {
mu, _ := fs.snapshotMutexMap.LoadOrStore(snapshotID, &sync.Mutex{})
return mu.(*sync.Mutex)
}
func (fs *Filesystem) copyBlobMetaFiles(bootstrap, cacheDir string) error {
bootstrapDir := filepath.Dir(bootstrap)
pattern := filepath.Join(bootstrapDir, "*.blob.meta")
matches, err := filepath.Glob(pattern)
if err != nil {
return errors.Wrap(err, "glob blob meta files")
}
for _, srcPath := range matches {
fileName := filepath.Base(srcPath)
dstPath := filepath.Join(cacheDir, fileName)
if err := os.Link(srcPath, dstPath); err != nil {
log.L.Warnf("Failed to create hardlink for %s: %v, falling back to copy", fileName, err)
if err := fs.copyFile(srcPath, dstPath); err != nil {
return errors.Wrapf(err, "copy blob meta file %s", fileName)
}
log.L.Debugf("Copied blob meta file: %s -> %s", srcPath, dstPath)
} else {
log.L.Debugf("Created hardlink for blob meta: %s -> %s", srcPath, dstPath)
}
}
return nil
}
func (fs *Filesystem) copyFile(src, dst string) error {
source, err := os.Open(src)
if err != nil {
return err
}
defer source.Close()
destination, err := os.Create(dst)
if err != nil {
return err
}
defer destination.Close()
_, err = io.Copy(destination, source)
return err
}
func (fs *Filesystem) Umount(_ context.Context, snapshotID string) error {
rafs := racache.RafsGlobalCache.Get(snapshotID)
if rafs == nil {
log.L.Debugf("no RAFS filesystem instance associated with snapshot %s", snapshotID)
return nil
}
fsDriver := rafs.GetFsDriver()
if fsDriver == config.FsDriverNodev {
return nil
}
fsManager, err := fs.getManager(fsDriver)
if err != nil {
return errors.Wrapf(err, "get manager for filesystem instance %s", rafs.DaemonID)
}
switch fsDriver {
case config.FsDriverFscache, config.FsDriverFusedev:
daemon, err := fs.getDaemonByRafs(rafs)
if err != nil {
log.L.Debugf("snapshot %s has no associated nydusd", snapshotID)
return errors.Wrapf(err, "get daemon with ID %s for snapshot %s", rafs.DaemonID, snapshotID)
}
daemon.RemoveRafsInstance(snapshotID)
if err := fsManager.RemoveRafsInstance(snapshotID); err != nil {
return errors.Wrapf(err, "remove snapshot %s", snapshotID)
}
if err := daemon.UmountRafsInstance(rafs); err != nil {
return errors.Wrapf(err, "umount instance %s", snapshotID)
}
// Once daemon's reference reaches 0, destroy the whole daemon
if daemon.GetRef() == 0 {
if err := fsManager.DestroyDaemon(daemon); err != nil {
return errors.Wrapf(err, "destroy daemon %s", daemon.ID())
}
}
case config.FsDriverBlockdev:
if err := fs.tarfsMgr.UmountTarErofs(snapshotID); err != nil {
return errors.Wrapf(err, "umount tar erofs on snapshot %s", snapshotID)
}
if err := fsManager.RemoveRafsInstance(snapshotID); err != nil {
return errors.Wrapf(err, "remove snapshot %s", snapshotID)
}
case config.FsDriverNodev, config.FsDriverProxy:
// For proxy mode, we still need to clean up the DB entry to prevent
// "already exists" errors on subsequent Mount() calls.
if err := fsManager.RemoveRafsInstance(snapshotID); err != nil {
// Log but don't fail - the entry might not exist
log.L.Debugf("remove instance %s from DB: %v", snapshotID, err)
}
default:
return errors.Errorf("unknown filesystem driver %s for snapshot %s", fsDriver, snapshotID)
}
return nil
}
// How much space the layer/blob cache filesystem is occupying
// The blob digest mush have `sha256:` prefixed, otherwise, throw errors.
func (fs *Filesystem) CacheUsage(ctx context.Context, blobDigest string) (snapshots.Usage, error) {
log.L.Infof("cache usage %s", blobDigest)
digest := digest.Digest(blobDigest)
if err := digest.Validate(); err != nil {
return snapshots.Usage{}, errors.Wrapf(err, "invalid blob digest from label %q, digest=%s",
snpkg.TargetLayerDigestLabel, blobDigest)
}
blobID := digest.Hex()
return fs.cacheMgr.CacheUsage(ctx, blobID)
}
func (fs *Filesystem) RemoveCache(blobDigest string) error {
log.L.Infof("remove cache %s", blobDigest)
digest := digest.Digest(blobDigest)
if err := digest.Validate(); err != nil {
return errors.Wrapf(err, "invalid blob digest from label %q, digest=%s",
snpkg.TargetLayerDigestLabel, blobDigest)
}
blobID := digest.Hex()
if fscacheManager, ok := fs.enabledManagers[config.FsDriverFscache]; ok {
if fscacheManager != nil {
c, err := fs.fscacheSharedDaemon.GetClient()
if err != nil {
return err
}
// delete fscache blob cache file
// TODO: skip error for blob not existing
if err := c.UnbindBlob("", blobID); err != nil {
return err
}
return nil
}
}
return fs.cacheMgr.RemoveBlobCache(blobID)
}
// WalkManagers iterates over all enabled managers and calls the provided function
func (fs *Filesystem) WalkManagers(fn func(*manager.Manager) error) error {
for _, mgr := range fs.enabledManagers {
if err := fn(mgr); err != nil {
return err
}
}
return nil
}
// GetCacheConfig returns the cache directory from the cache manager
func (fs *Filesystem) GetCacheDir() (string, error) {
if fs.cacheMgr == nil {
return "", errors.New("cache manager is not initialized")
}
return fs.cacheMgr.CacheDir(), nil
}
// Try to stop all the running daemons if they are not referenced by any snapshots
// Clean up resources along with the daemons.
func (fs *Filesystem) Teardown(ctx context.Context) error {
for _, fsManager := range fs.enabledManagers {
if fsManager.FsDriver == config.FsDriverFscache || fsManager.FsDriver == config.FsDriverFusedev {
for _, d := range fsManager.ListDaemons() {
for _, instance := range d.RafsCache.List() {
err := fs.Umount(ctx, instance.SnapshotID)
if err != nil {
log.L.Errorf("Failed to umount snapshot %s, %s", instance.SnapshotID, err)
}
}
}
// } else if fsManager.FsDriver == config.FsDriverBlockdev {
// TODO: support tarfs
}
}
return nil
}
func (fs *Filesystem) MountPoint(snapshotID string) (string, error) {
rafs := racache.RafsGlobalCache.Get(snapshotID)
if rafs != nil {
return rafs.GetMountpoint(), nil
}
return "", errdefs.ErrNotFound
}
func (fs *Filesystem) BootstrapFile(id string) (string, error) {
rafs := racache.RafsGlobalCache.Get(id)
if rafs == nil {
return "", errors.Errorf("no RAFS instance for %s", id)
}
return rafs.BootstrapFile()
}
// daemon mountpoint to rafs mountpoint
// calculate rafs mountpoint for snapshots mount slice.
func (fs *Filesystem) mountRemote(fsManager *manager.Manager, useSharedDaemon bool,
d *daemon.Daemon, r *racache.Rafs) error {
if useSharedDaemon {
if fsManager.FsDriver == config.FsDriverFusedev {
r.SetMountpoint(path.Join(d.HostMountpoint(), r.SnapshotID))
} else {
r.SetMountpoint(path.Join(r.GetSnapshotDir(), "mnt"))
}
if err := d.SharedMount(r); err != nil {
return errors.Wrapf(err, "failed to mount")
}
} else {
r.SetMountpoint(path.Join(d.HostMountpoint()))
err := fsManager.StartDaemon(d)
if err != nil {
return errors.Wrapf(err, "start daemon")
}
}
return nil
}
func (fs *Filesystem) decideDaemonMountpoint(fsDriver string, isSharedDaemonMode bool, rafs *racache.Rafs) (string, error) {
m := ""
if fsDriver == config.FsDriverFscache || fsDriver == config.FsDriverFusedev {
if isSharedDaemonMode {
m = fs.rootMountpoint
} else {
m = path.Join(rafs.GetSnapshotDir(), "mnt")
}
if err := os.MkdirAll(m, 0755); err != nil {
return "", errors.Wrapf(err, "create directory %s", m)
}
}
return m, nil
}
// 1. Create a daemon instance
// 2. Build command line
// 3. Start daemon
func (fs *Filesystem) initSharedDaemon(fsManager *manager.Manager) (err error) {
var daemonMode config.DaemonMode
switch fsManager.FsDriver {
case config.FsDriverFscache:
daemonMode = config.DaemonModeShared
case config.FsDriverFusedev:
daemonMode = config.DaemonModeShared
default:
return errors.Errorf("unsupported filesystem driver %s", fsManager.FsDriver)
}
mp, err := fs.decideDaemonMountpoint(fsManager.FsDriver, true, nil)
if err != nil {
return err
} else if mp == "" {
return errors.Errorf("got null mountpoint for fsDriver %s", fsManager.FsDriver)
}
d, err := fs.createDaemon(fsManager, daemonMode, mp, 0)
if err != nil {
return errors.Wrap(err, "initialize shared daemon")
}
// FIXME: Daemon record should not be removed after starting daemon failure.
defer func() {
if err != nil {
if err := fsManager.DeleteDaemon(d); err != nil {
log.L.Errorf("Start nydusd daemon error %v", err)
}
}
}()
// Shared nydusd daemon does not need configuration to start process but
// it is loaded when requesting mount api
// Dump the configuration file since it is reloaded when recovering the nydusd
d.Config = *fsManager.DaemonConfig
err = d.Config.DumpFile(d.ConfigFile(""))
if err != nil && !errors.Is(err, errdefs.ErrAlreadyExists) {
return errors.Wrapf(err, "dump configuration file %s", d.ConfigFile(""))
}
if err := fsManager.StartDaemon(d); err != nil {
return errors.Wrap(err, "start shared daemon")
}
fs.TryRetainSharedDaemon(d)
return
}
// createDaemon create new nydus daemon by snapshotID and imageID
func (fs *Filesystem) createDaemon(fsManager *manager.Manager, daemonMode config.DaemonMode,
mountpoint string, ref int32) (d *daemon.Daemon, err error) {
opts := []daemon.NewDaemonOpt{
daemon.WithRef(ref),
daemon.WithSocketDir(config.GetSocketRoot()),
daemon.WithConfigDir(config.GetConfigRoot()),
daemon.WithLogDir(config.GetLogDir()),
daemon.WithLogLevel(config.GetLogLevel()),
daemon.WithLogRotationSize(config.GetDaemonLogRotationSize()),
daemon.WithLogToStdout(config.GetLogToStdout()),
daemon.WithNydusdThreadNum(config.GetDaemonThreadsNumber()),
daemon.WithFsDriver(fsManager.FsDriver),
daemon.WithDaemonMode(daemonMode),
}
if fsManager.FsDriver == config.FsDriverFusedev {
opts = append(opts, daemon.WithFailoverPolicy(config.GetDaemonFailoverPolicy()))
}
// For fscache driver, no need to provide mountpoint to nydusd daemon.
if mountpoint != "" {
opts = append(opts, daemon.WithMountpoint(mountpoint))
}
d, err = daemon.NewDaemon(opts...)
if err != nil {
return nil, errors.Wrapf(err, "new daemon")
}
if err = fsManager.AddDaemon(d); err != nil {
return nil, err
}
if fsManager.SupervisorSet != nil {
// Supervisor is strongly associated with real running nydusd daemon.
su := fsManager.SupervisorSet.NewSupervisor(d.ID())
if su == nil {
_ = fsManager.DeleteDaemon(d)
return nil, errors.Errorf("create supervisor for daemon %s", d.ID())
}
d.Supervisor = su
}
return d, nil
}
func (fs *Filesystem) getManager(fsDriver string) (*manager.Manager, error) {
if fsManager, ok := fs.enabledManagers[fsDriver]; ok {
return fsManager, nil
}
return nil, errors.Errorf("no manager for filesystem driver %s", fsDriver)
}
func (fs *Filesystem) getSharedDaemon(fsDriver string) (*daemon.Daemon, error) {
switch fsDriver {
case config.FsDriverFscache:
if fs.fscacheSharedDaemon != nil {
return fs.fscacheSharedDaemon, nil
}
case config.FsDriverFusedev:
if fs.fusedevSharedDaemon != nil {
return fs.fusedevSharedDaemon, nil
}
}
return nil, errors.Errorf("no shared daemon for filesystem driver %s", fsDriver)
}
func (fs *Filesystem) getDaemonByRafs(rafs *racache.Rafs) (*daemon.Daemon, error) {
switch rafs.GetFsDriver() {
case config.FsDriverFscache, config.FsDriverFusedev:
if fsManager, ok := fs.enabledManagers[rafs.GetFsDriver()]; ok {
if d := fsManager.GetByDaemonID(rafs.DaemonID); d != nil {
return d, nil
}
}
}
return nil, errdefs.ErrNotFound
}
func (fs *Filesystem) GetDaemonByID(id string) (*daemon.Daemon, error) {
for _, manager := range fs.enabledManagers {
if d := manager.GetByDaemonID(id); d != nil {
return d, nil
}
}
return nil, errdefs.ErrNotFound
}
================================================
FILE: pkg/filesystem/index_adaptor.go
================================================
/*
* Copyright (c) 2025. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package filesystem
import (
"context"
"fmt"
"os"
snpkg "github.com/containerd/containerd/v2/pkg/snapshotters"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/label"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)
func (fs *Filesystem) IndexDetectEnabled() bool {
return fs.indexMgr != nil
}
// CheckIndexAlternative attempts to find a nydus alternative manifest in the original OCI index manifest
func (fs *Filesystem) CheckIndexAlternative(ctx context.Context, labels map[string]string) bool {
if !fs.IndexDetectEnabled() {
return false
}
ref, ok := labels[snpkg.TargetRefLabel]
if !ok {
return false
}
manifestDigest := digest.Digest(labels[snpkg.TargetManifestDigestLabel])
if manifestDigest.Validate() != nil {
return false
}
logger := log.G(ctx).WithField("ref", ref).WithField("digest", manifestDigest.String())
logger.Debug("attempting index-based nydus detection")
// Early exit if the labels explicitly indicate the presence of a nydus index alternative
hasIndexAlternative, ok := labels[label.NydusIndexAlternative]
if ok && hasIndexAlternative == "true" {
logger.Debug("detected nydus alternative via label")
return true
}
if _, err := fs.indexMgr.CheckIndexAlternative(ctx, ref, manifestDigest); err != nil {
return false
}
logger.Debug("detected nydus alternative via index manifest")
return true
}
// TryFetchMetadataFromIndex attempts to fetch metadata from the nydus index alternative
func (fs *Filesystem) TryFetchMetadataFromIndex(ctx context.Context, labels map[string]string, metadataPath string) error {
ref, ok := labels[snpkg.TargetRefLabel]
if !ok {
return fmt.Errorf("empty label %s", snpkg.TargetRefLabel)
}
manifestDigest := digest.Digest(labels[snpkg.TargetManifestDigestLabel])
if err := manifestDigest.Validate(); err != nil {
return fmt.Errorf("invalid label %s=%s", snpkg.TargetManifestDigestLabel, manifestDigest)
}
// Early exit if the file already exists
if _, err := os.Stat(metadataPath); err == nil {
log.G(ctx).WithField("ref", ref).WithField("digest", manifestDigest.String()).WithField("metadataPath", metadataPath).Debugf("metadata file already exists")
return nil
}
if err := fs.indexMgr.TryFetchMetadata(ctx, ref, manifestDigest, metadataPath); err != nil {
return errors.Wrap(err, "try fetch metadata")
}
return nil
}
================================================
FILE: pkg/filesystem/referer_adaptor.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package filesystem
import (
"context"
"fmt"
snpkg "github.com/containerd/containerd/v2/pkg/snapshotters"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)
func (fs *Filesystem) ReferrerDetectEnabled() bool {
return fs.referrerMgr != nil
}
func (fs *Filesystem) CheckReferrer(ctx context.Context, labels map[string]string) bool {
if !fs.ReferrerDetectEnabled() {
return false
}
ref, ok := labels[snpkg.TargetRefLabel]
if !ok {
return false
}
manifestDigest := digest.Digest(labels[snpkg.TargetManifestDigestLabel])
if manifestDigest.Validate() != nil {
return false
}
if _, err := fs.referrerMgr.CheckReferrer(ctx, ref, manifestDigest); err != nil {
return false
}
return true
}
func (fs *Filesystem) TryFetchMetadata(ctx context.Context, labels map[string]string, metadataPath string) error {
ref, ok := labels[snpkg.TargetRefLabel]
if !ok {
return fmt.Errorf("empty label %s", snpkg.TargetRefLabel)
}
manifestDigest := digest.Digest(labels[snpkg.TargetManifestDigestLabel])
if err := manifestDigest.Validate(); err != nil {
return fmt.Errorf("invalid label %s=%s", snpkg.TargetManifestDigestLabel, manifestDigest)
}
if err := fs.referrerMgr.TryFetchMetadata(ctx, ref, manifestDigest, metadataPath); err != nil {
return errors.Wrap(err, "try fetch metadata")
}
return nil
}
================================================
FILE: pkg/filesystem/stargz_adaptor.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package filesystem
import (
"context"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/KarpelesLab/reflink"
"github.com/containerd/containerd/v2/core/snapshots/storage"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/config"
"github.com/containerd/nydus-snapshotter/pkg/auth"
"github.com/containerd/nydus-snapshotter/pkg/label"
"github.com/containerd/nydus-snapshotter/pkg/stargz"
"github.com/containerd/nydus-snapshotter/pkg/utils/registry"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)
func (fs *Filesystem) UpperPath(id string) string {
return filepath.Join(config.GetSnapshotsRootDir(), id, "fs")
}
func (fs *Filesystem) StargzEnabled() bool {
return fs.stargzResolver != nil
}
// Detect if the blob is type of estargz by downloading its footer since estargz image does not
// have any characteristic annotation.
func (fs *Filesystem) IsStargzDataLayer(labels map[string]string) (bool, *stargz.Blob) {
ref, layerDigest := registry.ParseLabels(labels)
if ref == "" || layerDigest == "" {
return false, nil
}
log.L.Infof("Checking stargz image ref %s digest %s", ref, layerDigest)
keychain, err := auth.GetKeyChainByRef(ref, labels)
if err != nil {
log.L.WithError(err).Warn("get keychain from image reference")
return false, nil
}
blob, err := fs.stargzResolver.GetBlob(ref, layerDigest, keychain)
if err != nil {
log.L.WithError(err).Warn("get stargz blob")
return false, nil
}
off, err := blob.GetTocOffset()
if err != nil {
log.L.WithError(err).Warn("get toc offset")
return false, nil
}
if off <= 0 {
log.L.WithError(err).Warnf("Invalid stargz toc offset %d", off)
return false, nil
}
return true, blob
}
func (fs *Filesystem) MergeStargzMetaLayer(ctx context.Context, s storage.Snapshot) error {
mergedDir := fs.UpperPath(s.ParentIDs[0])
mergedBootstrap := filepath.Join(mergedDir, "image.boot")
if _, err := os.Stat(mergedBootstrap); err == nil {
return nil
}
bootstraps := []string{}
for idx, snapshotID := range s.ParentIDs {
files, err := os.ReadDir(fs.UpperPath(snapshotID))
if err != nil {
return errors.Wrap(err, "read snapshot dir")
}
bootstrapName := ""
blobMetaName := ""
for _, file := range files {
if digest.Digest(fmt.Sprintf("sha256:%s", file.Name())).Validate() == nil {
bootstrapName = file.Name()
}
if strings.HasSuffix(file.Name(), "blob.meta") {
blobMetaName = file.Name()
}
}
if bootstrapName == "" {
return fmt.Errorf("can't find bootstrap for snapshot %s", snapshotID)
}
// The blob meta file is generated in corresponding snapshot dir for each layer,
// but we need copy them to fscache work dir for nydusd use. This is not an
// efficient method, but currently nydusd only supports reading blob meta files
// from the same dir, so it is a workaround. If performance is a concern, it is
// best to convert the estargz image TOC file to a bootstrap / blob meta file
// at build time.
if blobMetaName != "" && idx != 0 {
sourcePath := filepath.Join(fs.UpperPath(snapshotID), blobMetaName)
// This path is same with `d.FscacheWorkDir()`, it's for fscache work dir.
targetPath := filepath.Join(fs.UpperPath(s.ParentIDs[0]), blobMetaName)
if err := reflink.Auto(sourcePath, targetPath); err != nil {
return errors.Wrap(err, "copy source blob.meta to target")
}
}
bootstrapPath := filepath.Join(fs.UpperPath(snapshotID), bootstrapName)
bootstraps = append([]string{bootstrapPath}, bootstraps...)
}
if len(bootstraps) == 1 {
if err := reflink.Auto(bootstraps[0], mergedBootstrap); err != nil {
return errors.Wrap(err, "copy source meta blob to target")
}
} else {
tf, err := os.CreateTemp(mergedDir, "merging-stargz")
if err != nil {
return errors.Wrap(err, "create temp file for merging stargz layers")
}
defer func() {
if err != nil {
os.Remove(tf.Name())
}
tf.Close()
}()
options := []string{
"merge",
"--bootstrap", tf.Name(),
}
options = append(options, bootstraps...)
cmd := exec.Command(fs.nydusdBinaryPath, options...)
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
log.G(ctx).Infof("nydus image command %v", options)
err = cmd.Run()
if err != nil {
return errors.Wrap(err, "merging stargz layers")
}
err = os.Rename(tf.Name(), mergedBootstrap)
if err != nil {
return errors.Wrap(err, "rename merged stargz layers")
}
if err := os.Chmod(mergedBootstrap, 0440); err != nil {
return err
}
}
return nil
}
// Generate nydus bootstrap from stargz layers
// Download estargz TOC part from each layer as `nydus-image` conversion source.
// After conversion, a nydus metadata or bootstrap is used to pointing to each estargz blob
func (fs *Filesystem) PrepareStargzMetaLayer(blob *stargz.Blob, storagePath string, _ map[string]string) error {
ref := blob.GetImageReference()
layerDigest := blob.GetDigest()
if !fs.StargzEnabled() {
return fmt.Errorf("stargz compatibility is not enabled")
}
blobID := digest.Digest(layerDigest).Hex()
convertedBootstrap := filepath.Join(storagePath, blobID)
stargzFile := filepath.Join(storagePath, stargz.TocFileName)
if _, err := os.Stat(convertedBootstrap); err == nil {
return nil
}
start := time.Now()
defer func() {
duration := time.Since(start)
log.L.Infof("total stargz prepare layer duration %d", duration.Milliseconds())
}()
r, err := blob.ReadToc()
if err != nil {
return errors.Wrapf(err, "read TOC, image reference: %s, layer digest: %s", ref, layerDigest)
}
starGzToc, err := os.OpenFile(stargzFile, os.O_CREATE|os.O_RDWR, 0640)
if err != nil {
return errors.Wrap(err, "create stargz index")
}
defer starGzToc.Close()
_, err = io.Copy(starGzToc, r)
if err != nil {
return errors.Wrap(err, "save stargz index")
}
err = os.Chmod(stargzFile, 0440)
if err != nil {
return err
}
blobMetaPath := filepath.Join(fs.cacheMgr.CacheDir(), fmt.Sprintf("%s.blob.meta", blobID))
if config.GetFsDriver() == config.FsDriverFscache {
// For fscache, the cache directory is managed linux fscache driver, so the blob.meta file
// can't be stored there.
if err := os.MkdirAll(storagePath, 0750); err != nil {
return errors.Wrapf(err, "failed to create fscache work dir %s", storagePath)
}
blobMetaPath = filepath.Join(storagePath, fmt.Sprintf("%s.blob.meta", blobID))
}
tf, err := os.CreateTemp(storagePath, "converting-stargz")
if err != nil {
return errors.Wrap(err, "create temp file for merging stargz layers")
}
defer func() {
if err != nil {
os.Remove(tf.Name())
}
tf.Close()
}()
options := []string{
"create",
"--source-type", "stargz_index",
"--bootstrap", tf.Name(),
"--blob-id", blobID,
"--repeatable",
"--disable-check",
// FIXME: allow user to specify fs version and automatically detect
// chunk size and compressor from estargz TOC file.
"--fs-version", "6",
"--chunk-size", "0x400000",
"--blob-meta", blobMetaPath,
}
options = append(options, filepath.Join(storagePath, stargz.TocFileName))
cmd := exec.Command(fs.nydusdBinaryPath, options...)
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
log.L.Infof("nydus image command %v", options)
err = cmd.Run()
if err != nil {
return errors.Wrap(err, "converting stargz layer")
}
err = os.Rename(tf.Name(), convertedBootstrap)
if err != nil {
return errors.Wrap(err, "rename converted stargz layer")
}
if err := os.Chmod(convertedBootstrap, 0440); err != nil {
return err
}
return nil
}
func (fs *Filesystem) StargzLayer(labels map[string]string) bool {
return labels[label.StargzLayer] != ""
}
================================================
FILE: pkg/filesystem/tarfs_adaptor.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package filesystem
import (
"context"
"github.com/containerd/containerd/v2/core/snapshots/storage"
snpkg "github.com/containerd/containerd/v2/pkg/snapshotters"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/label"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)
func (fs *Filesystem) TarfsEnabled() bool {
return fs.tarfsMgr != nil
}
func (fs *Filesystem) PrepareTarfsLayer(ctx context.Context, labels map[string]string, snapshotID, upperDirPath string) error {
ref, ok := labels[snpkg.TargetRefLabel]
if !ok {
return errors.Errorf("not found image reference label")
}
layerDigest := digest.Digest(labels[snpkg.TargetLayerDigestLabel])
if layerDigest.Validate() != nil {
return errors.Errorf("not found layer digest label")
}
manifestDigest := digest.Digest(labels[snpkg.TargetManifestDigestLabel])
if manifestDigest.Validate() != nil {
return errors.Errorf("not found manifest digest label")
}
ok, err := fs.tarfsMgr.CheckTarfsHintAnnotation(ctx, ref, manifestDigest)
if err != nil {
return errors.Wrapf(err, "check tarfs hint annotaion")
}
if !ok {
return errors.Errorf("this image is not recommended for tarfs")
}
limiter := fs.tarfsMgr.GetConcurrentLimiter(ref)
if limiter != nil {
if err := limiter.Acquire(context.Background(), 1); err != nil {
return errors.Wrapf(err, "concurrent limiter acquire")
}
}
if err := fs.tarfsMgr.PrepareLayer(snapshotID, ref, manifestDigest, layerDigest, upperDirPath); err != nil {
log.L.WithError(err).Errorf("async prepare tarfs layer of snapshot ID %s", snapshotID)
}
if limiter != nil {
limiter.Release(1)
}
layerBlobID := layerDigest.Hex()
labels[label.NydusTarfsLayer] = layerBlobID
return nil
}
func (fs *Filesystem) MergeTarfsLayers(s storage.Snapshot, storageLocater func(string) string) error {
return fs.tarfsMgr.MergeLayers(s, storageLocater)
}
func (fs *Filesystem) DetachTarfsLayer(snapshotID string) error {
return fs.tarfsMgr.DetachLayer(snapshotID)
}
func (fs *Filesystem) ExportBlockData(s storage.Snapshot, perLayer bool, labels map[string]string,
storageLocater func(string) string) ([]string, error) {
return fs.tarfsMgr.ExportBlockData(s, perLayer, labels, storageLocater)
}
func (fs *Filesystem) GetTarfsImageDiskFilePath(id string) (string, error) {
if fs.tarfsMgr == nil {
return "", errors.New("tarfs mode is not enabled")
}
return fs.tarfsMgr.ImageDiskFilePath(id), nil
}
func (fs *Filesystem) GetTarfsLayerDiskFilePath(id string) (string, error) {
if fs.tarfsMgr == nil {
return "", errors.New("tarfs mode is not enabled")
}
return fs.tarfsMgr.LayerDiskFilePath(id), nil
}
================================================
FILE: pkg/index/detector.go
================================================
/*
* Copyright (c) 2025. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package index
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"slices"
"github.com/containerd/platforms"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/containerd/nydus-snapshotter/pkg/auth"
"github.com/containerd/nydus-snapshotter/pkg/converter"
"github.com/containerd/nydus-snapshotter/pkg/label"
"github.com/containerd/nydus-snapshotter/pkg/remote"
)
// Containerd restricts the max size of manifest index to 8M, follow it.
const maxManifestIndexSize = 0x800000
type detector struct {
remote *remote.Remote
}
func newDetector(keyChain *auth.PassKeyChain, insecure bool) *detector {
return &detector{
remote: remote.New(keyChain, insecure),
}
}
// checkIndexAlternative attempts to find a nydus alternative manifest
// within an OCI index manifest for the specified manifest digest.
func (d *detector) checkIndexAlternative(ctx context.Context, ref string, manifestDigest digest.Digest) (*ocispec.Descriptor, error) {
handle := func() (*ocispec.Descriptor, error) {
// Create a new fetcher to request.
fetcher, err := d.remote.Fetcher(ctx, ref)
if err != nil {
return nil, errors.Wrap(err, "get fetcher")
}
resolver := d.remote.Resolve(ctx, ref)
_, desc, err := resolver.Resolve(ctx, ref)
if err != nil {
return nil, errors.Wrapf(err, "resolve reference %s", ref)
}
rc, err := fetcher.Fetch(ctx, desc)
if err != nil {
return nil, errors.Wrap(err, "fetch index manifest")
}
defer rc.Close()
// Parse image manifest list from index.
var index ocispec.Index
bytes, err := io.ReadAll(io.LimitReader(rc, maxManifestIndexSize))
if err != nil {
return nil, errors.Wrap(err, "read index manifest")
}
if err := json.Unmarshal(bytes, &index); err != nil {
return nil, errors.Wrap(err, "unmarshal index manifest")
}
nydusDesc, err := d.findNydusManifestInIndex(index, manifestDigest)
if err != nil {
return nil, err
}
rc, err = fetcher.Fetch(ctx, *nydusDesc)
if err != nil {
return nil, errors.Wrap(err, "fetch nydus image manifest")
}
defer rc.Close()
var manifest ocispec.Manifest
bytes, err = io.ReadAll(rc)
if err != nil {
return nil, errors.Wrap(err, "read manifest")
}
if err := json.Unmarshal(bytes, &manifest); err != nil {
return nil, errors.Wrap(err, "unmarshal manifest")
}
if len(manifest.Layers) < 1 {
return nil, fmt.Errorf("invalid manifest")
}
metaLayer := manifest.Layers[len(manifest.Layers)-1]
if !label.IsNydusMetaLayer(metaLayer.Annotations) {
return nil, fmt.Errorf("invalid nydus manifest")
}
return &metaLayer, nil
}
desc, err := handle()
if err != nil && d.remote.RetryWithPlainHTTP(ref, err) {
return handle()
}
return desc, err
}
// findNydusManifestInIndex finds a nydus alternative manifest within an OCI index
// for the specified manifest digest. It returns the nydus manifest descriptor.
func (d *detector) findNydusManifestInIndex(index ocispec.Index, originalDigest digest.Digest) (*ocispec.Descriptor, error) {
var originalDesc *ocispec.Descriptor
for _, manifest := range index.Manifests {
if manifest.Digest == originalDigest {
originalDesc = &manifest
break
}
}
if originalDesc == nil {
return nil, fmt.Errorf("original manifest %s not found in index", originalDigest)
}
pMatcher := platforms.NewMatcher(*originalDesc.Platform)
for _, manifest := range index.Manifests {
if pMatcher.Match(*manifest.Platform) &&
(d.hasNydusFeatures(manifest.Platform) || d.hasNydusArtifactType(&manifest)) {
return &manifest, nil
}
}
return nil, fmt.Errorf("no nydus alternative found in index for %s", originalDigest)
}
// hasNydusFeatures checks if the platform descriptor contains nydus features.
func (d *detector) hasNydusFeatures(platform *ocispec.Platform) bool {
if platform == nil {
return false
}
return slices.Contains(platform.OSFeatures, converter.ManifestOSFeatureNydus)
}
// hasNydusArtifactType checks if the descriptor is of nydus artifact type.
func (d *detector) hasNydusArtifactType(desc *ocispec.Descriptor) bool {
if desc == nil {
return false
}
return desc.ArtifactType == converter.ManifestArtifactTypeNydus
}
// fetchMetadata fetches and unpacks nydus metadata file to specified path.
func (d *detector) fetchMetadata(ctx context.Context, ref string, desc ocispec.Descriptor, metadataPath string) error {
handle := func() error {
resolver := d.remote.Resolve(ctx, ref)
fetcher, err := resolver.Fetcher(ctx, ref)
if err != nil {
return errors.Wrap(err, "get fetcher")
}
rc, err := fetcher.Fetch(ctx, desc)
if err != nil {
return errors.Wrap(err, "fetch nydus metadata")
}
defer rc.Close()
// Unpack nydus metadata file to specified path.
if err := remote.Unpack(rc, converter.BootstrapFileNameInLayer, metadataPath); err != nil {
os.Remove(metadataPath)
return errors.Wrap(err, "unpack metadata from layer")
}
return nil
}
err := handle()
if err != nil && d.remote.RetryWithPlainHTTP(ref, err) {
return handle()
}
return err
}
================================================
FILE: pkg/index/detector_test.go
================================================
/*
* Copyright (c) 2025. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package index
import (
"testing"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/assert"
)
func TestHasNydusFeatures(t *testing.T) {
d := &detector{}
tests := []struct {
name string
platform *ocispec.Platform
expected bool
}{
{
name: "nil platform",
platform: nil,
expected: false,
},
{
name: "platform without os features",
platform: &ocispec.Platform{
OS: "linux",
Architecture: "amd64",
},
expected: false,
},
{
name: "platform with nydus features",
platform: &ocispec.Platform{
OS: "linux",
Architecture: "amd64",
OSFeatures: []string{"nydus.remoteimage.v1"},
},
expected: true,
},
{
name: "platform with multiple features including nydus",
platform: &ocispec.Platform{
OS: "linux",
Architecture: "amd64",
OSFeatures: []string{"feature1", "nydus.remoteimage.v1", "feature2"},
},
expected: true,
},
{
name: "platform with non-nydus features",
platform: &ocispec.Platform{
OS: "linux",
Architecture: "amd64",
OSFeatures: []string{"feature1", "feature2"},
},
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := d.hasNydusFeatures(tt.platform)
assert.Equal(t, tt.expected, result)
})
}
}
func TestFindNydusManifestInIndex(t *testing.T) {
d := &detector{}
manifestDigest := digest.FromString("test-manifest")
nydusManifestDigest := digest.FromString("nydus-manifest")
tests := []struct {
name string
index ocispec.Index
manifestDigest digest.Digest
expectedDigest *digest.Digest
expectError bool
errorContains string
}{
{
name: "original manifest not found in index",
index: ocispec.Index{
Manifests: []ocispec.Descriptor{},
},
manifestDigest: manifestDigest,
expectError: true,
errorContains: "not found in index",
},
{
name: "no nydus alternative found",
index: ocispec.Index{
Manifests: []ocispec.Descriptor{
{
Digest: manifestDigest,
Platform: &ocispec.Platform{
OS: "linux",
Architecture: "amd64",
},
},
{
Digest: digest.FromString("other-manifest"),
Platform: &ocispec.Platform{
OS: "linux",
Architecture: "amd64",
},
},
},
},
manifestDigest: manifestDigest,
expectError: true,
errorContains: "no nydus alternative found",
},
{
name: "nydus alternative found",
index: ocispec.Index{
Manifests: []ocispec.Descriptor{
{
Digest: manifestDigest,
Platform: &ocispec.Platform{
OS: "linux",
Architecture: "amd64",
},
},
{
Digest: nydusManifestDigest,
Platform: &ocispec.Platform{
OS: "linux",
Architecture: "amd64",
OSFeatures: []string{"nydus.remoteimage.v1"},
},
},
},
},
manifestDigest: manifestDigest,
expectedDigest: &nydusManifestDigest,
expectError: false,
},
{
name: "multiple nydus alternatives, returns first match",
index: ocispec.Index{
Manifests: []ocispec.Descriptor{
{
Digest: manifestDigest,
Platform: &ocispec.Platform{
OS: "linux",
Architecture: "amd64",
},
},
{
Digest: nydusManifestDigest,
Platform: &ocispec.Platform{
OS: "linux",
Architecture: "amd64",
OSFeatures: []string{"nydus.remoteimage.v1"},
},
},
{
Digest: digest.FromString("second-nydus-manifest"),
Platform: &ocispec.Platform{
OS: "linux",
Architecture: "amd64",
OSFeatures: []string{"nydus.remoteimage.v1"},
},
},
},
},
manifestDigest: manifestDigest,
expectedDigest: &nydusManifestDigest,
expectError: false,
},
{
name: "nydus alternative with different architecture ignored",
index: ocispec.Index{
Manifests: []ocispec.Descriptor{
{
Digest: manifestDigest,
Platform: &ocispec.Platform{
OS: "linux",
Architecture: "amd64",
},
},
{
Digest: digest.FromString("nydus-arm64"),
Platform: &ocispec.Platform{
OS: "linux",
Architecture: "arm64",
OSFeatures: []string{"nydus.remoteimage.v1"},
},
},
},
},
manifestDigest: manifestDigest,
expectError: true,
errorContains: "no nydus alternative found",
},
{
name: "nydus alternative found with artifact type",
index: ocispec.Index{
Manifests: []ocispec.Descriptor{
{
Digest: manifestDigest,
Platform: &ocispec.Platform{
OS: "linux",
Architecture: "amd64",
},
},
{
Digest: nydusManifestDigest,
Platform: &ocispec.Platform{
OS: "linux",
Architecture: "amd64",
},
ArtifactType: "application/vnd.nydus.image.manifest.v1+json",
},
},
},
manifestDigest: manifestDigest,
expectedDigest: &nydusManifestDigest,
expectError: false,
},
{
name: "different artifact type is ignored",
index: ocispec.Index{
Manifests: []ocispec.Descriptor{
{
Digest: manifestDigest,
Platform: &ocispec.Platform{
OS: "linux",
Architecture: "amd64",
},
},
{
Digest: nydusManifestDigest,
Platform: &ocispec.Platform{
OS: "linux",
Architecture: "amd64",
},
ArtifactType: "application/foo+bar",
},
},
},
manifestDigest: manifestDigest,
expectError: true,
errorContains: "no nydus alternative found",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := d.findNydusManifestInIndex(tt.index, tt.manifestDigest)
if tt.expectError {
assert.Error(t, err)
if tt.errorContains != "" {
assert.Contains(t, err.Error(), tt.errorContains)
}
assert.Nil(t, result)
} else {
assert.NoError(t, err)
assert.NotNil(t, result)
assert.Equal(t, *tt.expectedDigest, result.Digest)
}
})
}
}
================================================
FILE: pkg/index/manager.go
================================================
/*
* Copyright (c) 2025. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package index
import (
"context"
"github.com/containerd/log"
"github.com/golang/groupcache/lru"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"golang.org/x/sync/singleflight"
"github.com/containerd/nydus-snapshotter/pkg/auth"
)
var ErrNoNydusAlternative = errors.New("no alternative nydus descriptor found in index")
type Manager struct {
insecure bool
cache *lru.Cache
sg singleflight.Group
}
func NewManager(insecure bool) *Manager {
manager := Manager{
insecure: insecure,
cache: lru.New(500),
sg: singleflight.Group{},
}
return &manager
}
// CheckIndexAlternative attempts to find a nydus alternative manifest
// within an OCI index manifest for the specified manifest digest.
func (manager *Manager) CheckIndexAlternative(ctx context.Context, ref string, manifestDigest digest.Digest) (*ocispec.Descriptor, error) {
nydusDesc, err, _ := manager.sg.Do(manifestDigest.String(), func() (interface{}, error) {
// Try to get nydus metadata layer descriptor from LRU cache.
desc, ok := manager.cache.Get(manifestDigest)
if ok {
metaLayer, ok := desc.(ocispec.Descriptor)
if ok {
return &metaLayer, nil
}
return nil, ErrNoNydusAlternative
}
keyChain, err := auth.GetKeyChainByRef(ref, nil)
if err != nil {
return nil, errors.Wrap(err, "get key chain")
}
// No LRU cache found, try to detect nydus alternative in index manifest.
detector := newDetector(keyChain, manager.insecure)
metaLayer, err := detector.checkIndexAlternative(ctx, ref, manifestDigest)
if err != nil {
// Cache empty result to avoid repeated failures.
// The index manifest can't change as it would change its digest so checking once is enough
manager.cache.Add(manifestDigest, nil)
return nil, errors.Wrap(err, "check index alternative")
}
// Cache the result for future use
manager.cache.Add(manifestDigest, *metaLayer)
return metaLayer, nil
})
logger := log.G(ctx).WithField("ref", ref).WithField("digest", manifestDigest.String())
if err == ErrNoNydusAlternative {
return nil, err
} else if err != nil {
logger.WithError(err).Warn("index detection failed")
return nil, err
}
return nydusDesc.(*ocispec.Descriptor), nil
}
// TryFetchMetadata try to fetch and unpack nydus metadata file to specified path.
func (manager *Manager) TryFetchMetadata(ctx context.Context, ref string, manifestDigest digest.Digest, metadataPath string) error {
metaLayer, err := manager.CheckIndexAlternative(ctx, ref, manifestDigest)
if err != nil {
return errors.Wrap(err, "check index alternative")
}
keyChain, err := auth.GetKeyChainByRef(ref, nil)
if err != nil {
return errors.Wrap(err, "get key chain")
}
detector := newDetector(keyChain, manager.insecure)
return detector.fetchMetadata(ctx, ref, *metaLayer, metadataPath)
}
================================================
FILE: pkg/index/manager_test.go
================================================
/*
* Copyright (c) 2025. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package index
import (
"context"
"testing"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/assert"
)
func TestCheckIndexAlternative(t *testing.T) {
manifestDigest := digest.FromString("test-manifest")
ref := "registry.example.com/test/repo:latest"
expectedDesc := &ocispec.Descriptor{
Digest: digest.FromString("meta-layer"),
}
t.Run("cache hit, nydus present", func(t *testing.T) {
manager := NewManager(false)
manager.cache.Add(manifestDigest, *expectedDesc)
result, err := manager.CheckIndexAlternative(context.Background(), ref, manifestDigest)
assert.NoError(t, err)
assert.Equal(t, expectedDesc, result)
})
t.Run("cache hit, no nydus", func(t *testing.T) {
manager := NewManager(false)
manager.cache.Add(manifestDigest, nil)
_, err := manager.CheckIndexAlternative(context.Background(), ref, manifestDigest)
assert.Error(t, err)
assert.Contains(t, err.Error(), "no alternative nydus descriptor found in index")
})
}
================================================
FILE: pkg/label/label.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package label
import (
snpkg "github.com/containerd/containerd/v2/pkg/snapshotters"
)
// For package compatibility, we still keep the old exported name here.
var AppendLabelsHandlerWrapper = snpkg.AppendInfoHandlerWrapper
// For package compatibility, we still keep the old exported name here.
const (
CRIImageRef = snpkg.TargetRefLabel
CRIImageLayers = snpkg.TargetImageLayersLabel
CRILayerDigest = snpkg.TargetLayerDigestLabel
CRIManifestDigest = snpkg.TargetManifestDigestLabel
)
const (
// Marker for remote snapshotter to handle the pull request.
// During image pull, the containerd client calls Prepare API with the label containerd.io/snapshot.ref.
// This is a containerd-defined label which contains ChainID that targets a committed snapshot that the
// client is trying to prepare.
TargetSnapshotRef = "containerd.io/snapshot.ref"
// A bool flag to mark the blob as a Nydus data blob, set by image builders.
NydusDataLayer = "containerd.io/snapshot/nydus-blob"
// A bool flag to mark the blob as a nydus bootstrap, set by image builders.
NydusMetaLayer = "containerd.io/snapshot/nydus-bootstrap"
// The referenced blob sha256 in format of `sha256:xxx`, set by image builders.
NydusRefLayer = "containerd.io/snapshot/nydus-ref"
// The blobID of associated layer, also marking the layer as a nydus tarfs, set by the snapshotter
NydusTarfsLayer = "containerd.io/snapshot/nydus-tarfs"
// Dm-verity information for image block device
NydusImageBlockInfo = "containerd.io/snapshot/nydus-image-block"
// Dm-verity information for layer block device
NydusLayerBlockInfo = "containerd.io/snapshot/nydus-layer-block"
// Annotation containing secret to pull images from registry, set by the snapshotter.
NydusImagePullSecret = "containerd.io/snapshot/pullsecret"
// Annotation containing username to pull images from registry, set by the snapshotter.
NydusImagePullUsername = "containerd.io/snapshot/pullusername"
// Proxy image pull actions to other agents.
NydusProxyMode = "containerd.io/snapshot/nydus-proxy-mode"
// A bool flag to enable integrity verification of meta data blob
NydusSignature = "containerd.io/snapshot/nydus-signature"
// A bool flag to mark the blob as a estargz data blob, set by the snapshotter.
StargzLayer = "containerd.io/snapshot/stargz"
// volatileOpt is a key of an optional label to each snapshot.
// If this optional label of a snapshot is specified, when mounted to rootdir
// this snapshot will include volatile option
OverlayfsVolatileOpt = "containerd.io/snapshot/overlay.volatile"
// A bool flag to mark it is recommended to run this image with tarfs mode, set by image builders.
// runtime can decide whether to rely on this annotation
TarfsHint = "containerd.io/snapshot/tarfs-hint"
// An alternative nydus index manifest exists in the original OCI index manifest for this snapshot
NydusIndexAlternative = "containerd.io/snapshot/nydus-index-alternative"
)
func IsNydusDataLayer(labels map[string]string) bool {
_, ok := labels[NydusDataLayer]
return ok
}
func IsNydusMetaLayer(labels map[string]string) bool {
_, ok := labels[NydusMetaLayer]
return ok
}
func IsTarfsDataLayer(labels map[string]string) bool {
_, ok := labels[NydusTarfsLayer]
return ok
}
func IsNydusProxyMode(labels map[string]string) bool {
_, ok := labels[NydusProxyMode]
return ok
}
func HasTarfsHint(labels map[string]string) bool {
_, ok := labels[TarfsHint]
return ok
}
================================================
FILE: pkg/layout/layout.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package layout
import (
"encoding/binary"
"errors"
"unsafe"
)
// RafsV6 layout: 1k + SuperBlock(128) + SuperBlockExtended(256)
// RafsV5 layout: 8K superblock
// So we only need to read the MaxSuperBlockSize size to include both v5 and v6 superblocks
const MaxSuperBlockSize = 8 * 1024
const (
RafsV5 string = "v5"
RafsV6 string = "v6"
RafsV5SuperVersion uint32 = 0x500
RafsV5SuperMagic uint32 = 0x5241_4653
RafsV6SuperMagic uint32 = 0xE0F5_E1E2
RafsV6SuperBlockSize uint32 = 1024 + 128 + 256
RafsV6SuperBlockOffset uint32 = 1024
RafsV6ChunkInfoOffset uint32 = 1024 + 128 + 24
BootstrapFile string = "image/image.boot"
LegacyBootstrapFile string = "image.boot"
DummyMountpoint string = "/dummy"
)
var nativeEndian binary.ByteOrder
type ImageMode int
const (
OnDemand ImageMode = iota
PreLoad
)
func init() {
buf := [2]byte{}
*(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD)
switch buf {
case [2]byte{0xCD, 0xAB}:
nativeEndian = binary.LittleEndian
case [2]byte{0xAB, 0xCD}:
nativeEndian = binary.BigEndian
default:
panic("Could not determine native endianness.")
}
}
func isRafsV6(buf []byte) bool {
return nativeEndian.Uint32(buf[RafsV6SuperBlockOffset:]) == RafsV6SuperMagic
}
func DetectFsVersion(header []byte) (string, error) {
if len(header) < 8 {
return "", errors.New("header buffer to DetectFsVersion is too small")
}
magic := binary.LittleEndian.Uint32(header[0:4])
fsVersion := binary.LittleEndian.Uint32(header[4:8])
if magic == RafsV5SuperMagic && fsVersion == RafsV5SuperVersion {
return RafsV5, nil
}
// FIXME: detect more magic numbers to reduce collision
if len(header) >= int(RafsV6SuperBlockSize) && isRafsV6(header) {
return RafsV6, nil
}
return "", errors.New("unknown file system header")
}
================================================
FILE: pkg/manager/daemon_adaptor.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package manager
import (
"fmt"
"os"
"os/exec"
"strings"
"time"
"github.com/containerd/log"
"github.com/pkg/errors"
"github.com/containerd/nydus-snapshotter/config"
"github.com/containerd/nydus-snapshotter/pkg/daemon"
"github.com/containerd/nydus-snapshotter/pkg/daemon/command"
"github.com/containerd/nydus-snapshotter/pkg/daemon/types"
"github.com/containerd/nydus-snapshotter/pkg/errdefs"
"github.com/containerd/nydus-snapshotter/pkg/metrics/collector"
metrics "github.com/containerd/nydus-snapshotter/pkg/metrics/tool"
"github.com/containerd/nydus-snapshotter/pkg/prefetch"
)
const endpointGetBackend string = "/api/v1/daemons/%s/backend"
// Spawn a nydusd daemon to serve the daemon instance.
//
// When returning from `StartDaemon()` with out error:
// - `d.States.ProcessID` will be set to the pid of the nydusd daemon.
// - `d.State()` may return any validate state, please call `d.WaitUntilState()` to
// ensure the daemon has reached specified state.
// - `d` may have not been inserted into daemonStates and store yet.
func (m *Manager) StartDaemon(d *daemon.Daemon) error {
cmd, err := m.BuildDaemonCommand(d, "", false)
if err != nil {
return errors.Wrapf(err, "create command for daemon %s", d.ID())
}
if err := cmd.Start(); err != nil {
return err
}
d.Lock()
defer d.Unlock()
d.States.ProcessID = cmd.Process.Pid
// Profile nydusd daemon CPU usage during its startup.
if config.GetDaemonProfileCPUDuration() > 0 {
processState, err := metrics.GetProcessStat(cmd.Process.Pid)
if err == nil {
timer := time.NewTimer(time.Duration(config.GetDaemonProfileCPUDuration()) * time.Second)
go func() {
<-timer.C
currentProcessState, err := metrics.GetProcessStat(cmd.Process.Pid)
if err != nil {
log.L.WithError(err).Warnf("Failed to get daemon %s process state.", d.ID())
return
}
d.StartupCPUUtilization, err = metrics.CalculateCPUUtilization(processState, currentProcessState)
if err != nil {
log.L.WithError(err).Warnf("Calculate CPU utilization error")
}
}()
}
}
// Update both states cache and DB
// TODO: Is it right to commit daemon before nydusd successfully started?
// And it brings extra latency of accessing DB. Only write daemon record to
// DB when nydusd is started?
err = m.UpdateDaemon(d)
if err != nil {
// Nothing we can do, just ignore it for now
log.L.Errorf("Fail to update daemon info (%+v) to DB: %v", d, err)
}
// If nydusd fails startup, manager can't subscribe its death event.
// So we can ignore the subscribing error.
go func() {
if err := daemon.WaitUntilSocketExisted(d.GetAPISock(), d.States.ProcessID); err != nil {
// FIXME: Should clean the daemon record in DB if the nydusd fails starting
log.L.Errorf("Nydusd %s probably not started", d.ID())
return
}
if err = m.SubscribeDaemonEvent(d); err != nil {
log.L.Errorf("Nydusd %s probably not started", d.ID())
return
}
if err := d.WaitUntilState(types.DaemonStateRunning); err != nil {
log.L.WithError(err).Errorf("daemon %s is not managed to reach RUNNING state", d.ID())
return
}
collector.NewDaemonEventCollector(types.DaemonStateRunning).Collect()
if m.CgroupMgr != nil {
if err := m.CgroupMgr.AddProc(d.States.ProcessID); err != nil {
log.L.WithError(err).Errorf("add daemon %s to cgroup failed", d.ID())
return
}
}
d.Lock()
collector.NewDaemonInfoCollector(&d.Version, 1).Collect()
d.Unlock()
d.SendStates()
}()
return nil
}
// Build commandline according to nydusd daemon configuration.
func (m *Manager) BuildDaemonCommand(d *daemon.Daemon, bin string, upgrade bool) (*exec.Cmd, error) {
var cmdOpts []command.Opt
var imageReference string
nydusdThreadNum := d.NydusdThreadNum()
if d.States.FsDriver == config.FsDriverFscache {
cmdOpts = append(cmdOpts,
command.WithMode("singleton"),
command.WithFscacheDriver(m.cacheDir))
if nydusdThreadNum != 0 {
cmdOpts = append(cmdOpts, command.WithFscacheThreads(nydusdThreadNum))
}
} else {
cmdOpts = append(cmdOpts, command.WithMode("fuse"), command.WithMountpoint(d.HostMountpoint()))
if nydusdThreadNum != 0 {
cmdOpts = append(cmdOpts, command.WithThreadNum(nydusdThreadNum))
}
switch {
case d.IsSharedDaemon():
break
case !d.IsSharedDaemon():
rafs := d.RafsCache.Head()
if rafs == nil {
return nil, errors.Wrapf(errdefs.ErrNotFound, "daemon %s no rafs instance associated", d.ID())
}
imageReference = rafs.ImageID
bootstrap, err := rafs.BootstrapFile()
if err != nil {
return nil, errors.Wrapf(err, "locate bootstrap %s", bootstrap)
}
cmdOpts = append(cmdOpts,
command.WithConfig(d.ConfigFile("")),
command.WithBootstrap(bootstrap),
)
if config.IsBackendSourceEnabled() {
configAPIPath := fmt.Sprintf(endpointGetBackend, d.States.ID)
cmdOpts = append(cmdOpts,
command.WithBackendSource(config.SystemControllerAddress()+configAPIPath),
)
}
default:
return nil, errors.Errorf("invalid daemon mode %s ", d.States.DaemonMode)
}
}
if d.Supervisor != nil {
cmdOpts = append(cmdOpts,
command.WithSupervisor(d.Supervisor.Sock()),
command.WithID(d.ID()))
}
if imageReference != "" {
prefetchfiles := prefetch.Pm.GetPrefetchInfo(imageReference)
if prefetchfiles != "" {
cmdOpts = append(cmdOpts, command.WithPrefetchFiles(prefetchfiles))
prefetch.Pm.DeleteFromPrefetchMap(imageReference)
}
}
cmdOpts = append(cmdOpts,
command.WithLogLevel(d.States.LogLevel),
command.WithAPISock(d.GetAPISock()))
if d.States.LogRotationSize > 0 {
cmdOpts = append(cmdOpts, command.WithLogRotationSize(d.States.LogRotationSize))
}
if upgrade {
cmdOpts = append(cmdOpts, command.WithUpgrade())
}
if !d.States.LogToStdout {
cmdOpts = append(cmdOpts, command.WithLogFile(d.LogFile()))
}
if d.States.FsDriver == config.FsDriverFusedev {
cmdOpts = append(cmdOpts, command.WithFailoverPolicy(d.States.FailoverPolicy))
}
args, err := command.BuildCommand(cmdOpts)
if err != nil {
return nil, err
}
var nydusdPath string
if bin != "" {
nydusdPath = bin
} else {
nydusdPath = m.NydusdBinaryPath
}
log.L.Infof("nydusd command: %s %s", nydusdPath, strings.Join(args, " "))
cmd := exec.Command(nydusdPath, args...)
// nydusd standard output and standard error rather than its logs are
// always redirected to snapshotter's respectively
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd, nil
}
================================================
FILE: pkg/manager/daemon_cache.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package manager
import (
"sync"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/daemon"
)
// Daemon state cache to speed up access.
type DaemonCache struct {
mu sync.Mutex
idxByDaemonID map[string]*daemon.Daemon // index by ID
}
func newDaemonCache() *DaemonCache {
return &DaemonCache{
idxByDaemonID: make(map[string]*daemon.Daemon),
}
}
// Return nil if the daemon is never inserted or managed,
// otherwise returns the previously inserted daemon pointer.
// Allowing replace an existed daemon since some fields in Daemon can change after restarting nydusd.
func (s *DaemonCache) Add(daemon *daemon.Daemon) *daemon.Daemon {
s.mu.Lock()
defer s.mu.Unlock()
old := s.idxByDaemonID[daemon.ID()]
s.idxByDaemonID[daemon.ID()] = daemon
return old
}
func (s *DaemonCache) removeLocked(d *daemon.Daemon) *daemon.Daemon {
old := s.idxByDaemonID[d.ID()]
delete(s.idxByDaemonID, d.ID())
return old
}
func (s *DaemonCache) Remove(d *daemon.Daemon) *daemon.Daemon {
s.mu.Lock()
old := s.removeLocked(d)
s.mu.Unlock()
return old
}
func (s *DaemonCache) RemoveByDaemonID(id string) *daemon.Daemon {
return s.GetByDaemonID(id, func(d *daemon.Daemon) { s.removeLocked(d) })
}
// Also recover daemon runtime state here
func (s *DaemonCache) Update(d *daemon.Daemon) {
s.mu.Lock()
defer s.mu.Unlock()
log.L.Infof("Recovering daemon ID %s", d.ID())
s.idxByDaemonID[d.ID()] = d
}
func (s *DaemonCache) GetByDaemonID(id string, op func(d *daemon.Daemon)) *daemon.Daemon {
var daemon *daemon.Daemon
s.mu.Lock()
defer s.mu.Unlock()
daemon = s.idxByDaemonID[id]
if daemon != nil && op != nil {
op(daemon)
}
return daemon
}
func (s *DaemonCache) List() []*daemon.Daemon {
s.mu.Lock()
defer s.mu.Unlock()
if len(s.idxByDaemonID) == 0 {
return nil
}
listed := make([]*daemon.Daemon, 0, len(s.idxByDaemonID))
for _, d := range s.idxByDaemonID {
listed = append(listed, d)
}
return listed
}
func (s *DaemonCache) Size() int {
s.mu.Lock()
defer s.mu.Unlock()
return len(s.idxByDaemonID)
}
================================================
FILE: pkg/manager/daemon_cache_test.go
================================================
/*
Copyright The nydus Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package manager
import (
"testing"
"github.com/containerd/nydus-snapshotter/pkg/daemon"
"github.com/stretchr/testify/assert"
)
func TestDaemonStatesCache(t *testing.T) {
states := newDaemonCache()
d1 := &daemon.Daemon{States: daemon.ConfigState{ID: "d1"}}
d2 := &daemon.Daemon{States: daemon.ConfigState{ID: "d2"}}
states.Add(d1)
states.Add(d2)
anotherD1 := states.GetByDaemonID("d1", nil)
anotherD2 := states.GetByDaemonID("d2", nil)
assert.Equal(t, anotherD1, d1)
assert.Equal(t, anotherD2, d2)
daemons := states.List()
assert.Equal(t, len(daemons), 2)
assert.True(t, daemons[0] == d1 || daemons[1] == d1)
assert.True(t, daemons[0] == d2 || daemons[1] == d2)
assert.Equal(t, states.Size(), 2)
states.Remove(d1)
assert.Equal(t, states.Size(), 1)
states.RemoveByDaemonID("d2")
assert.Equal(t, states.Size(), 0)
states.Update(d2)
assert.Equal(t, states.Size(), 1)
states.RemoveByDaemonID("d2")
assert.Equal(t, states.Size(), 0)
}
================================================
FILE: pkg/manager/daemon_event.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package manager
import (
"fmt"
"path"
"regexp"
"strconv"
"strings"
"time"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/config"
"github.com/containerd/nydus-snapshotter/pkg/daemon"
"github.com/containerd/nydus-snapshotter/pkg/daemon/types"
"github.com/containerd/nydus-snapshotter/pkg/metrics/collector"
"github.com/pkg/errors"
)
func (m *Manager) SubscribeDaemonEvent(d *daemon.Daemon) error {
if err := m.monitor.Subscribe(d.ID(), d.GetAPISock(), m.LivenessNotifier); err != nil {
log.L.Errorf("Nydusd %s probably not started", d.ID())
return errors.Wrapf(err, "subscribe daemon %s", d.ID())
}
return nil
}
func (m *Manager) UnsubscribeDaemonEvent(d *daemon.Daemon) error {
// Starting a new nydusd will re-subscribe
if err := m.monitor.Unsubscribe(d.ID()); err != nil {
log.L.Warnf("fail to unsubscribe daemon %s, %v", d.ID(), err)
return errors.Wrapf(err, "unsubscribe daemon %s", d.ID())
}
return nil
}
func (m *Manager) handleDaemonDeathEvent() {
// TODO: ratelimit for daemon recovery operations?
for ev := range m.LivenessNotifier {
log.L.Warnf("Daemon %s died! socket path %s", ev.daemonID, ev.path)
d := m.GetByDaemonID(ev.daemonID)
if d == nil {
log.L.Warnf("Daemon %s was not found, may have been removed", ev.daemonID)
continue
}
d.Lock()
collector.NewDaemonInfoCollector(&d.Version, -1).Collect()
d.Unlock()
d.ResetState()
switch m.RecoverPolicy {
case config.RecoverPolicyRestart:
log.L.Infof("Restart daemon %s", ev.daemonID)
go m.doDaemonRestart(d)
case config.RecoverPolicyFailover:
log.L.Infof("Do failover for daemon %s", ev.daemonID)
go m.doDaemonFailover(d)
default:
// RecoverPolicyNone or RecoverPolicyInvalid - do nothing
}
}
}
func (m *Manager) doDaemonFailover(d *daemon.Daemon) {
if err := d.Wait(); err != nil {
log.L.Warnf("fail to wait for daemon, %v", err)
}
// Starting a new nydusd will re-subscribe
if err := m.UnsubscribeDaemonEvent(d); err != nil {
log.L.Warnf("fail to unsubscribe daemon %s, %v", d.ID(), err)
}
su := m.SupervisorSet.GetSupervisor(d.ID())
if err := su.SendStatesTimeout(time.Second * 10); err != nil {
log.L.Errorf("Send states error, %s", err)
return
}
// Failover nydusd still depends on the old supervisor
if err := m.StartDaemon(d); err != nil {
log.L.Errorf("fail to start daemon %s when recovering", d.ID())
return
}
if err := d.WaitUntilState(types.DaemonStateInit); err != nil {
log.L.WithError(err).Errorf("daemon didn't reach state %s,", types.DaemonStateInit)
return
}
if err := d.TakeOver(); err != nil {
log.L.Errorf("fail to takeover, %s", err)
return
}
if err := d.Start(); err != nil {
log.L.Errorf("fail to start service, %s", err)
return
}
}
func (m *Manager) doDaemonRestart(d *daemon.Daemon) {
if err := d.Wait(); err != nil {
log.L.Warnf("fails to wait for daemon, %v", err)
}
// Starting a new nydusd will re-subscribe
if err := m.UnsubscribeDaemonEvent(d); err != nil {
log.L.Warnf("fails to unsubscribe daemon %s, %v", d.ID(), err)
}
d.ClearVestige()
if err := m.StartDaemon(d); err != nil {
log.L.Errorf("fails to start daemon %s when recovering", d.ID())
return
}
// Mount rafs instance by http API
instances := d.RafsCache.List()
for _, r := range instances {
// For dedicated nydusd daemon, Rafs has already been mounted during starting nydusd
if d.HostMountpoint() == r.GetMountpoint() {
break
}
if err := d.SharedMount(r); err != nil {
log.L.Warnf("Failed to mount rafs instance, %v", err)
}
}
}
// Provide minimal parameters since most of it can be recovered by nydusd states.
// Create a new daemon in Manger to take over the service.
func (m *Manager) DoDaemonUpgrade(d *daemon.Daemon, nydusdPath string, manager *Manager) (*daemon.Daemon, error) {
supervisor := d.Supervisor
newDaemon := &daemon.Daemon{
States: d.States,
Supervisor: supervisor,
}
newDaemon.CloneRafsInstances(d)
s := path.Base(d.GetAPISock())
next, err := buildNextAPISocket(s)
if err != nil {
return nil, err
}
upgradingSocket := path.Join(path.Dir(d.GetAPISock()), next)
newDaemon.States.APISocket = upgradingSocket
cmd, err := manager.BuildDaemonCommand(newDaemon, nydusdPath, true)
if err != nil {
return nil, err
}
if err := supervisor.SendStatesTimeout(time.Second * 10); err != nil {
return nil, errors.Wrap(err, "Send states")
}
if err := cmd.Start(); err != nil {
return nil, errors.Wrap(err, "start process")
}
newDaemon.States.ProcessID = cmd.Process.Pid
if err := newDaemon.WaitUntilState(types.DaemonStateInit); err != nil {
return nil, errors.Wrap(err, "wait until init state")
}
if err := newDaemon.TakeOver(); err != nil {
return nil, errors.Wrap(err, "take over resources")
}
if err := newDaemon.WaitUntilState(types.DaemonStateReady); err != nil {
return nil, errors.Wrap(err, "wait unit ready state")
}
if err := manager.UnsubscribeDaemonEvent(d); err != nil {
return nil, errors.Wrap(err, "unsubscribe daemon event")
}
// Let the older daemon exit without umount
if err := d.Exit(); err != nil {
return nil, errors.Wrap(err, "old daemon exits")
}
if err := newDaemon.Start(); err != nil {
return nil, errors.Wrap(err, "start file system service")
}
if err := manager.SubscribeDaemonEvent(newDaemon); err != nil {
return nil, errors.Wrap(err, "subscribe new daemon event")
}
if err := newDaemon.WaitUntilState(types.DaemonStateRunning); err != nil {
return nil, errors.Wrapf(err, "wait for daemon %s", d.ID())
}
if err := newDaemon.RecoverRafsInstances(); err != nil {
return nil, errors.Wrapf(err, "recover mounts for daemon %s", d.ID())
}
log.L.Infof("Started service of upgraded daemon on socket %s", newDaemon.GetAPISock())
if err := manager.UpdateDaemon(newDaemon); err != nil {
return nil, err
}
log.L.Infof("Upgraded daemon success on socket %s", newDaemon.GetAPISock())
return newDaemon, err
}
// Name next api socket path based on currently api socket path listened on.
// The principle is to add a suffix number to api[0-9]+.sock
func buildNextAPISocket(cur string) (string, error) {
n := strings.Split(cur, ".")
if len(n) != 2 {
return "", errors.Errorf("invalid api socket path format: %s", cur)
}
r := regexp.MustCompile(`[0-9]+`)
m := r.Find([]byte(n[0]))
var num int
if m == nil {
num = 1
} else {
var err error
num, err = strconv.Atoi(string(m))
if err != nil {
return "", err
}
num++
}
nextSocket := fmt.Sprintf("api%d.sock", num)
return nextSocket, nil
}
================================================
FILE: pkg/manager/manager.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package manager
import (
"context"
"os"
"path"
"path/filepath"
"sync"
"github.com/containerd/log"
"github.com/pkg/errors"
"github.com/containerd/nydus-snapshotter/config"
"github.com/containerd/nydus-snapshotter/config/daemonconfig"
"github.com/containerd/nydus-snapshotter/pkg/cgroup"
"github.com/containerd/nydus-snapshotter/pkg/daemon"
"github.com/containerd/nydus-snapshotter/pkg/daemon/types"
"github.com/containerd/nydus-snapshotter/pkg/errdefs"
"github.com/containerd/nydus-snapshotter/pkg/metrics/collector"
"github.com/containerd/nydus-snapshotter/pkg/rafs"
"github.com/containerd/nydus-snapshotter/pkg/store"
"github.com/containerd/nydus-snapshotter/pkg/supervisor"
)
// Manage RAFS filesystem instances and nydusd daemons.
type Manager struct {
// Protect fields `store` and `daemonStates`
mu sync.Mutex
cacheDir string
FsDriver string
store Store
// Fields below are used to manage nydusd daemons.
//
// The `daemonCache` is cache for nydusd daemons stored in `store`.
// You should update `store` first before modifying cached state.
daemonCache *DaemonCache
DaemonConfig *daemonconfig.DaemonConfig // Daemon configuration template.
CgroupMgr *cgroup.Manager
monitor LivenessMonitor
LivenessNotifier chan deathEvent // TODO: Close me
NydusdBinaryPath string
RecoverPolicy config.DaemonRecoverPolicy
SupervisorSet *supervisor.SupervisorsSet
}
type Opt struct {
CacheDir string
CgroupMgr *cgroup.Manager
DaemonConfig *daemonconfig.DaemonConfig
Database *store.Database
FsDriver string
NydusdBinaryPath string
RecoverPolicy config.DaemonRecoverPolicy
RootDir string // Nydus-snapshotter work directory
}
func NewManager(opt Opt) (*Manager, error) {
s, err := store.NewDaemonRafsStore(opt.Database)
if err != nil {
return nil, err
}
monitor, err := newMonitor()
if err != nil {
return nil, errors.Wrap(err, "create daemons liveness monitor")
}
var supervisorSet *supervisor.SupervisorsSet
if opt.RecoverPolicy == config.RecoverPolicyFailover {
supervisorSet, err = supervisor.NewSupervisorSet(filepath.Join(opt.RootDir, "supervisor"))
if err != nil {
return nil, errors.Wrap(err, "create supervisor set")
}
}
mgr := &Manager{
store: s,
NydusdBinaryPath: opt.NydusdBinaryPath,
cacheDir: opt.CacheDir,
daemonCache: newDaemonCache(),
monitor: monitor,
LivenessNotifier: make(chan deathEvent, 32),
RecoverPolicy: opt.RecoverPolicy,
SupervisorSet: supervisorSet,
DaemonConfig: opt.DaemonConfig,
CgroupMgr: opt.CgroupMgr,
FsDriver: opt.FsDriver,
}
// FIXME: How to get error if monitor goroutine terminates with error?
// TODO: Shutdown monitor immediately after snapshotter receive Exit signal
mgr.monitor.Run()
go mgr.handleDaemonDeathEvent()
return mgr, nil
}
func (m *Manager) Lock() {
m.mu.Lock()
}
func (m *Manager) Unlock() {
m.mu.Unlock()
}
func (m *Manager) CacheDir() string {
return m.cacheDir
}
// Recover nydusd daemons and RAFS instances on startup.
//
// To be safe:
// - Never ever delete any records from DB
// - Only cache daemon information from DB, do not actually start/create daemons
// - Only cache RAFS instance information from DB, do not actually recover RAFS runtime state.
func (m *Manager) Recover(ctx context.Context,
recoveringDaemons *map[string]*daemon.Daemon, liveDaemons *map[string]*daemon.Daemon) error {
if err := m.recoverDaemons(ctx, recoveringDaemons, liveDaemons); err != nil {
return errors.Wrapf(err, "recover nydusd daemons")
}
if err := m.recoverRafsInstances(ctx, recoveringDaemons, liveDaemons); err != nil {
return errors.Wrapf(err, "recover RAFS instances")
}
return nil
}
func (m *Manager) AddRafsInstance(r *rafs.Rafs) error {
m.mu.Lock()
defer m.mu.Unlock()
seq, err := m.store.NextInstanceSeq()
if err != nil {
return err
}
r.Seq = seq
return m.store.AddRafsInstance(r)
}
func (m *Manager) UpdateRafsInstance(r *rafs.Rafs) error {
return m.store.UpdateRafsInstance(r)
}
func (m *Manager) RemoveRafsInstance(snapshotID string) error {
return m.store.DeleteRafsInstance(snapshotID)
}
func (m *Manager) recoverRafsInstances(ctx context.Context,
recoveringDaemons *map[string]*daemon.Daemon, liveDaemons *map[string]*daemon.Daemon) error {
if err := m.store.WalkRafsInstances(ctx, func(r *rafs.Rafs) error {
if r.GetFsDriver() != m.FsDriver {
return nil
}
log.L.Debugf("found RAFS instance %#v", r)
if r.GetFsDriver() == config.FsDriverFscache || r.GetFsDriver() == config.FsDriverFusedev {
d := (*recoveringDaemons)[r.DaemonID]
if d != nil {
d.AddRafsInstance(r)
}
d = (*liveDaemons)[r.DaemonID]
if d != nil {
d.AddRafsInstance(r)
}
rafs.RafsGlobalCache.Add(r)
} else if r.GetFsDriver() == config.FsDriverBlockdev {
rafs.RafsGlobalCache.Add(r)
}
return nil
}); err != nil {
return errors.Wrapf(err, "walk instances to reconnect")
}
return nil
}
// Add an instantiated daemon to be managed by the manager.
//
// Return ErrAlreadyExists if a daemon with the same daemon ID already exists.
func (m *Manager) AddDaemon(daemon *daemon.Daemon) error {
m.mu.Lock()
defer m.mu.Unlock()
if old := m.daemonCache.GetByDaemonID(daemon.ID(), nil); old != nil {
return errdefs.ErrAlreadyExists
}
if err := m.store.AddDaemon(daemon); err != nil {
return errors.Wrapf(err, "add daemon %s", daemon.ID())
}
m.daemonCache.Add(daemon)
return nil
}
func (m *Manager) UpdateDaemon(daemon *daemon.Daemon) error {
m.mu.Lock()
defer m.mu.Unlock()
return m.UpdateDaemonLocked(daemon)
}
// Notice: updating daemon states cache and DB should be protect by `mu` lock
func (m *Manager) UpdateDaemonLocked(daemon *daemon.Daemon) error {
if old := m.daemonCache.GetByDaemonID(daemon.ID(), nil); old == nil {
return errdefs.ErrNotFound
}
if err := m.store.UpdateDaemon(daemon); err != nil {
return errors.Wrapf(err, "update daemon state for %s", daemon.ID())
}
m.daemonCache.Add(daemon)
return nil
}
func (m *Manager) DeleteDaemon(daemon *daemon.Daemon) error {
if daemon == nil {
return nil
}
m.mu.Lock()
defer m.mu.Unlock()
if err := m.store.DeleteDaemon(daemon.ID()); err != nil {
return errors.Wrapf(err, "delete daemon state for %s", daemon.ID())
}
m.daemonCache.Remove(daemon)
return nil
}
func (m *Manager) GetByDaemonID(id string) *daemon.Daemon {
return m.daemonCache.GetByDaemonID(id, nil)
}
func (m *Manager) ListDaemons() []*daemon.Daemon {
return m.daemonCache.List()
}
// FIXME: should handle the inconsistent status caused by any step
// in the function that returns an error.
func (m *Manager) DestroyDaemon(d *daemon.Daemon) error {
log.L.Infof("Destroy nydusd daemon %s. Host mountpoint %s", d.ID(), d.HostMountpoint())
// First remove the record from DB, so any failures below won't cause stale records in DB.
if err := m.DeleteDaemon(d); err != nil {
return errors.Wrapf(err, "delete daemon %s", d.ID())
}
defer m.cleanUpDaemonResources(d)
// Clean up any remaining RAFS instances that were not individually
// unmounted (e.g., during forced shutdown or error recovery paths).
for _, r := range d.RafsCache.List() {
d.RemoveRafsInstance(r.SnapshotID)
}
if err := d.UmountRafsInstances(); err != nil {
log.L.Errorf("Failed to detach all fs instances from daemon %s, %s", d.ID(), err)
}
if err := m.UnsubscribeDaemonEvent(d); err != nil {
log.L.Warnf("Unable to unsubscribe, daemon ID %s, %s", d.ID(), err)
}
if m.SupervisorSet != nil {
if err := m.SupervisorSet.DestroySupervisor(d.ID()); err != nil {
log.L.Warnf("Failed to delete supervisor for daemon %s, %s", d.ID(), err)
}
}
// Graceful nydusd termination will umount itself.
if err := d.Terminate(); err != nil {
log.L.Warnf("Fails to terminate daemon, %v", err)
}
if err := d.Wait(); err != nil {
log.L.Warnf("Failed to wait for daemon, %v", err)
}
collector.NewDaemonEventCollector(types.DaemonStateDestroyed).Collect()
d.Lock()
collector.NewDaemonInfoCollector(&d.Version, -1).Collect()
d.Unlock()
return nil
}
func (m *Manager) cleanUpDaemonResources(d *daemon.Daemon) {
// TODO: use recycle bin to stage directories/files to be deleted.
resource := []string{d.States.ConfigDir, d.States.LogDir}
if !d.IsSharedDaemon() {
socketDir := path.Dir(d.GetAPISock())
resource = append(resource, socketDir)
}
for _, dir := range resource {
if err := os.RemoveAll(dir); err != nil {
log.L.Errorf("failed to remove dir %s err %v", dir, err)
}
}
log.L.Infof("Deleting resources %v", resource)
}
func (m *Manager) recoverDaemons(ctx context.Context,
recoveringDaemons *map[string]*daemon.Daemon, liveDaemons *map[string]*daemon.Daemon) error {
if err := m.store.WalkDaemons(ctx, func(s *daemon.ConfigState) error {
if s.FsDriver != m.FsDriver {
return nil
}
log.L.Debugf("found daemon states %#v", s)
opt := make([]daemon.NewDaemonOpt, 0)
var d, _ = daemon.NewDaemon(opt...)
d.States = *s
m.daemonCache.Update(d)
if m.SupervisorSet != nil {
su := m.SupervisorSet.NewSupervisor(d.ID())
if su == nil {
return errors.Errorf("create supervisor for daemon %s", d.ID())
}
d.Supervisor = su
}
if d.States.FsDriver == config.FsDriverFusedev {
cfg, err := daemonconfig.NewDaemonConfig(d.States.FsDriver, d.ConfigFile(""))
if err != nil {
log.L.Errorf("Failed to reload daemon configuration %s, %s", d.ConfigFile(""), err)
return err
}
d.Config = cfg
}
state, err := d.GetState()
if err != nil {
log.L.Warnf("Daemon %s died somehow. Clean up its vestige!, %s", d.ID(), err)
(*recoveringDaemons)[d.ID()] = d
//nolint:nilerr
return nil
}
if state != types.DaemonStateRunning {
log.L.Warnf("daemon %s is not running: %s", d.ID(), state)
return nil
}
// FIXME: Should put the a daemon back file system shared damon field.
log.L.Infof("found RUNNING daemon %s during reconnecting", d.ID())
(*liveDaemons)[d.ID()] = d
if m.CgroupMgr != nil {
if err := m.CgroupMgr.AddProc(d.States.ProcessID); err != nil {
return errors.Wrapf(err, "add daemon %s to cgroup failed", d.ID())
}
}
d.Lock()
collector.NewDaemonInfoCollector(&d.Version, 1).Collect()
d.Unlock()
go func() {
if err := daemon.WaitUntilSocketExisted(d.GetAPISock(), d.Pid()); err != nil {
log.L.Errorf("Nydusd %s probably not started", d.ID())
return
}
if err = m.SubscribeDaemonEvent(d); err != nil {
log.L.Errorf("Nydusd %s probably not started", d.ID())
return
}
// Snapshotter's lost the daemons' states after exit, refetch them.
d.SendStates()
}()
return nil
}); err != nil {
return errors.Wrapf(err, "walk daemons to reconnect")
}
return nil
}
================================================
FILE: pkg/manager/monitor.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package manager
import (
"net"
"sync"
"syscall"
"time"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/daemon/types"
"github.com/containerd/nydus-snapshotter/pkg/errdefs"
"github.com/containerd/nydus-snapshotter/pkg/metrics/collector"
"github.com/containerd/nydus-snapshotter/pkg/utils/retry"
)
// LivenessMonitor liveness of a nydusd daemon.
type LivenessMonitor interface {
// Subscribe death event of a nydusd daemon.
// `path` is where the monitor is listening on.
Subscribe(id string, path string, notifier chan<- deathEvent) error
// Unsubscribe death event of a nydusd daemon.
Unsubscribe(id string) error
// Run the monitor, wait for nydusd death event.
Run()
// Stop the monitor and release all the resources.
Destroy()
}
type target struct {
// A connection to the nydusd. Should close it when stopping the liveness monitor!
uc *net.UnixConn
// Notify subscriber that the nydusd is dead via the channel
notifier chan<- deathEvent
// `id` is usually the daemon ID
id string
path string
}
type FD = uintptr
type livenessMonitor struct {
mu sync.Mutex
// Get a subscribing target by the target ID (usually is the daemon ID)
subscribers map[string]*target
// Get a subscribing target by the connection FD. Each liveness
// probe has a unique connection FD which is listened for epoll event.
set map[FD]*target
epollFd int
}
type deathEvent struct {
daemonID string
path string
}
func newMonitor() (_ *livenessMonitor, err error) {
var epollFd int
epollFd, err = unix.EpollCreate1(unix.EPOLL_CLOEXEC)
if err != nil {
return nil, errors.Wrap(err, "create daemons monitor")
}
m := &livenessMonitor{
epollFd: epollFd,
subscribers: make(map[string]*target),
set: make(map[uintptr]*target),
}
return m, nil
}
func (m *livenessMonitor) Subscribe(id string, path string, notifier chan<- deathEvent) (err error) {
m.mu.Lock()
defer m.mu.Unlock()
if s, ok := m.subscribers[id]; ok && s.path == path {
log.L.Warnf("Daemon %s is already subscribed!", id)
return errdefs.ErrAlreadyExists
}
var (
c net.Conn
rawConn syscall.RawConn
)
err = retry.Do(func() (err error) {
// Don't forget close me!
if c, err = net.Dial("unix", path); err != nil {
log.L.Errorf("Fails to connect to %s, %v", path, err)
return
}
return nil
},
retry.LastErrorOnly(true),
retry.Attempts(20), // totally wait for 2 seconds, should be enough
retry.Delay(100*time.Millisecond))
if err != nil {
return err
}
uc, ok := c.(*net.UnixConn)
if !ok {
return errors.Errorf("a unix socket connection is required")
}
if rawConn, err = uc.SyscallConn(); err != nil {
return
}
err = rawConn.Control(func(fd FD) {
err = unix.SetNonblock(int(fd), true)
if err != nil {
log.L.Errorf("Failed to set file. daemon id %s path %s. %v", id, path, err)
return
}
event := unix.EpollEvent{
Fd: int32(fd),
Events: unix.EPOLLHUP | unix.EPOLLERR | unix.EPOLLET,
}
err = unix.EpollCtl(m.epollFd, unix.EPOLL_CTL_ADD, int(fd), &event)
if err != nil {
log.L.Errorf("Failed to control epoll. daemon id %s path %s. %v", id, path, err)
return
}
target := &target{uc: uc, id: id, path: path}
// Only add subscribed target when everything is OK.
m.set[fd] = target
m.subscribers[id] = target
target.notifier = notifier
})
log.L.Infof("Subscribe daemon %s liveness event, path=%s.", id, path)
return
}
func (m *livenessMonitor) Unsubscribe(id string) (err error) {
m.mu.Lock()
defer m.mu.Unlock()
return m.unsubscribe(id)
}
func (m *livenessMonitor) unsubscribe(id string) (err error) {
target, ok := m.subscribers[id]
if !ok {
return errdefs.ErrNotFound
}
delete(m.subscribers, id)
var rawConn syscall.RawConn
if rawConn, err = target.uc.SyscallConn(); err != nil {
log.L.Errorf("Fail to access underlying FD, id=%s", id)
return
}
// No longer wait for event, delete it from interest list.
if err = rawConn.Control(func(fd uintptr) {
if err := unix.EpollCtl(m.epollFd, unix.EPOLL_CTL_DEL, int(fd), &unix.EpollEvent{}); err != nil {
log.L.Errorf("Fail to delete event fd %d for supervisor %s", int(fd), id)
return
}
delete(m.set, fd)
}); err != nil {
return errors.Wrapf(err, "remove target FD in the interested list, id=%s", id)
}
if err = target.uc.Close(); err != nil {
log.L.Errorf("Fails to close unix connection for daemon %s", id)
return
}
return nil
}
func (m *livenessMonitor) Run() {
var events [512]unix.EpollEvent
go func() {
defer log.L.Infof("Exiting liveness monitor")
log.L.Infof("Run daemons monitor...")
for {
n, err := unix.EpollWait(m.epollFd, events[:], -1)
if err != nil {
if err == unix.EINTR {
continue
}
// `Destroy` should close the epoll fd thus to exit the goroutine.
log.L.Errorf("Monitor fails to wait events, %v. Exiting!", err)
return
}
for i := 0; i < n; i++ {
ev := events[i]
m.mu.Lock()
target, ok := m.set[uintptr(ev.Fd)]
m.mu.Unlock()
// There is a race that it is waken before unsubscribing,
// so the target can't be found.
if !ok {
continue
}
if ev.Events&(unix.EPOLLHUP|unix.EPOLLERR) != 0 {
log.L.Warnf("Daemon %s died", target.id)
collector.NewDaemonEventCollector(types.DaemonStateDied).Collect()
// Notify subscribers that death event happens
target.notifier <- deathEvent{daemonID: target.id, path: target.path}
}
}
}
}()
}
func (m *livenessMonitor) Destroy() {
m.mu.Lock()
defer m.mu.Unlock()
for i := range m.subscribers {
if err := m.unsubscribe(i); err != nil {
log.L.Warnf("fail to unsubscribe %s", i)
}
}
if m.epollFd > 0 {
// Closing epoll fd does not waken `EpollWait`. So ending events loop can not be
// done via closing the file. But liveness monitor is running with nydus-snapshotter
// in the whole life. So we don't stop the loop now.
unix.Close(m.epollFd)
}
}
================================================
FILE: pkg/manager/monitor_test.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package manager
import (
"context"
"log"
"net"
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func startUnixServer(ctx context.Context, sock string) {
os.RemoveAll(sock)
listener, err := net.Listen("unix", sock)
if err != nil {
log.Fatal(err)
}
var conn net.Conn
conn, err = listener.Accept()
if err != nil {
log.Fatal()
}
for {
select {
case <-ctx.Done():
conn.Close()
return
default:
time.Sleep(200 * time.Millisecond)
}
}
}
func TestLivenessMonitor(t *testing.T) {
sockPattern := "liveness_monitor_sock"
s1, err1 := os.CreateTemp("", sockPattern)
assert.Nil(t, err1)
s1.Close()
s2, err2 := os.CreateTemp("", sockPattern)
assert.Nil(t, err2)
s2.Close()
ctx1, cancel1 := context.WithCancel(context.Background())
ctx2, cancel2 := context.WithCancel(context.Background())
go startUnixServer(ctx1, s1.Name())
go startUnixServer(ctx2, s2.Name())
monitor, _ := newMonitor()
assert.NotNil(t, monitor)
time.Sleep(time.Millisecond * 200)
notifier := make(chan deathEvent, 10)
e1 := monitor.Subscribe("daemon_1", s1.Name(), notifier)
assert.Nil(t, e1)
e1 = monitor.Subscribe("daemon_1", s1.Name(), notifier)
assert.NotNil(t, e1)
e2 := monitor.Subscribe("daemon_2", s2.Name(), notifier)
assert.Nil(t, e2)
t.Cleanup(func() {
os.Remove(s1.Name())
os.Remove(s2.Name())
})
monitor.Run()
time.Sleep(time.Millisecond * 200)
// Daemon 1 dies and unblock from channel `n1`
cancel1()
event := <-notifier
assert.Equal(t, event.daemonID, "daemon_1")
err := monitor.Unsubscribe("daemon_2")
assert.Nil(t, err)
cancel2()
// Should not block here.
assert.Equal(t, len(notifier), 0)
time.Sleep(time.Second * 1)
monitor.Destroy()
assert.Equal(t, len(monitor.set), 0)
assert.Equal(t, len(monitor.subscribers), 0)
}
================================================
FILE: pkg/manager/store.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package manager
import (
"context"
"github.com/containerd/nydus-snapshotter/pkg/daemon"
"github.com/containerd/nydus-snapshotter/pkg/rafs"
"github.com/containerd/nydus-snapshotter/pkg/store"
)
// Nydus daemons and fs instances persistence storage.
type Store interface {
// If the daemon is inserted to DB before, return error ErrAlreadyExisted.
AddDaemon(d *daemon.Daemon) error
UpdateDaemon(d *daemon.Daemon) error
DeleteDaemon(id string) error
WalkDaemons(ctx context.Context, cb func(*daemon.ConfigState) error) error
CleanupDaemons(ctx context.Context) error
AddRafsInstance(r *rafs.Rafs) error
UpdateRafsInstance(r *rafs.Rafs) error
DeleteRafsInstance(snapshotID string) error
WalkRafsInstances(ctx context.Context, cb func(*rafs.Rafs) error) error
NextInstanceSeq() (uint64, error)
}
var _ Store = &store.DaemonRafsStore{}
================================================
FILE: pkg/metrics/collector/cache.go
================================================
/*
* Copyright (c) 2025. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package collector
import (
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/daemon/types"
"github.com/containerd/nydus-snapshotter/pkg/metrics/data"
)
type CacheMetricsCollector struct {
Metrics *types.CacheMetrics
ImageRef string
DaemonID string
}
type CacheMetricsVecCollector struct {
MetricsVec []CacheMetricsCollector
}
func (c *CacheMetricsCollector) Collect() {
if c.Metrics == nil {
log.L.Warnf("can not collect cache metrics: Metrics is nil")
return
}
prefetchTotalDuration := (c.Metrics.PrefetchEndTimeSecs*1000 +
c.Metrics.PrefetchCumulativeTimeMillis) -
(c.Metrics.PrefetchBeginTimeSecs*1000 +
c.Metrics.PrefetchCumulativeTimeMillis)
data.CachePartialHits.WithLabelValues(c.ImageRef).Set(float64(c.Metrics.PartialHits))
data.CacheWholeHits.WithLabelValues(c.ImageRef).Set(float64(c.Metrics.WholeHits))
data.CacheTotalRequests.WithLabelValues(c.ImageRef).Set(float64(c.Metrics.Total))
data.CacheEntriesCount.WithLabelValues(c.ImageRef).Set(float64(c.Metrics.EntriesCount))
data.CachePrefetchDataBytes.WithLabelValues(c.ImageRef).Set(float64(c.Metrics.PrefetchDataAmount))
data.CachePrefetchRequestsCount.WithLabelValues(c.ImageRef).Set(float64(c.Metrics.PrefetchRequestsCount))
data.CachePrefetchWorkers.WithLabelValues(c.ImageRef).Set(float64(c.Metrics.PrefetchWorkers))
data.CachePrefetchUnmergedChunks.WithLabelValues(c.ImageRef).Set(float64(c.Metrics.PrefetchUnmergedChunks))
data.CachePrefetchCumulativeTimeMillis.WithLabelValues(c.ImageRef).Set(float64(c.Metrics.PrefetchCumulativeTimeMillis))
data.CachePrefetchTotalDurationMillis.WithLabelValues(c.ImageRef).Set(float64(prefetchTotalDuration))
data.CacheBufferedBackendSize.WithLabelValues(c.ImageRef).Set(float64(c.Metrics.BufferedBackendSize))
}
func (c *CacheMetricsVecCollector) Collect() {
for _, cacheMetrics := range c.MetricsVec {
cacheMetrics.Collect()
}
}
================================================
FILE: pkg/metrics/collector/collector.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package collector
import (
"context"
"time"
"github.com/containerd/nydus-snapshotter/pkg/metrics/data"
"github.com/containerd/nydus-snapshotter/pkg/daemon/types"
"github.com/containerd/nydus-snapshotter/pkg/metrics/tool"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
)
type Collector interface {
// Collect metrics to prometheus data.
Collect()
}
func NewDaemonEventCollector(ev types.DaemonState) *DaemonEventCollector {
return &DaemonEventCollector{event: ev}
}
func NewFsMetricsCollector(m *types.FsMetrics, imageRef string) *FsMetricsCollector {
return &FsMetricsCollector{m, imageRef}
}
func NewFsMetricsVecCollector() *FsMetricsVecCollector {
return &FsMetricsVecCollector{}
}
func NewInflightMetricsVecCollector(hungIOInterval time.Duration) *InflightMetricsVecCollector {
return &InflightMetricsVecCollector{
HungIOInterval: hungIOInterval,
}
}
func NewDaemonInfoCollector(version *types.BuildTimeInfo, value float64) *DaemonInfoCollector {
return &DaemonInfoCollector{version, value}
}
func NewDaemonImageCollector(daemonID, imageRef string) *DaemonImageCollector {
return &DaemonImageCollector{DaemonID: daemonID, ImageRef: imageRef}
}
func NewSnapshotterMetricsCollector(ctx context.Context, cacheDir string, pid int) (*SnapshotterMetricsCollector, error) {
currentStat, err := tool.GetProcessStat(pid)
if err != nil {
return nil, errors.Wrapf(err, "can not get current stat")
}
return &SnapshotterMetricsCollector{ctx, cacheDir, pid, currentStat}, nil
}
func NewSnapshotMetricsTimer(method SnapshotMethod) *prometheus.Timer {
return CollectSnapshotMetricsTimer(data.SnapshotEventElapsedHists, method)
}
func NewCacheMetricsCollector(m *types.CacheMetrics, imageRef, daemonID string) *CacheMetricsCollector {
return &CacheMetricsCollector{m, imageRef, daemonID}
}
func NewCacheMetricsVecCollector() *CacheMetricsVecCollector {
return &CacheMetricsVecCollector{}
}
================================================
FILE: pkg/metrics/collector/daemon.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package collector
import (
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/daemon/types"
"github.com/containerd/nydus-snapshotter/pkg/metrics/data"
)
type DaemonEventCollector struct {
event types.DaemonState
}
type DaemonInfoCollector struct {
Version *types.BuildTimeInfo
value float64
}
type DaemonResourceCollector struct {
DaemonID string
Value float64
}
type DaemonImageCollector struct {
DaemonID string
ImageRef string
}
func (d *DaemonEventCollector) Collect() {
data.NydusdEventCount.WithLabelValues(string(d.event)).Inc()
}
func (d *DaemonInfoCollector) Collect() {
if d.Version == nil {
log.L.Warnf("failed to collect daemon count, version is invalid")
return
}
data.NydusdCount.WithLabelValues(d.Version.PackageVer).Add(d.value)
}
func (d *DaemonResourceCollector) Collect() {
data.NydusdRSS.WithLabelValues(d.DaemonID).Set(d.Value)
}
func (d *DaemonImageCollector) Collect() {
data.NydusdImageInfo.WithLabelValues(d.DaemonID, d.ImageRef).Set(1)
}
func (d *DaemonImageCollector) Delete() {
data.NydusdImageInfo.DeleteLabelValues(d.DaemonID, d.ImageRef)
}
================================================
FILE: pkg/metrics/collector/fs.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package collector
import (
"time"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/daemon/types"
"github.com/containerd/nydus-snapshotter/pkg/metrics/data"
mtypes "github.com/containerd/nydus-snapshotter/pkg/metrics/types"
)
var OPCodeMap = map[uint32]string{
15: "OP_READ",
}
type FsMetricsCollector struct {
Metrics *types.FsMetrics
ImageRef string
}
type FsMetricsVecCollector struct {
MetricsVec []FsMetricsCollector
}
type InflightMetricsVecCollector struct {
MetricsVec []*types.InflightMetrics
HungIOInterval time.Duration
}
func (f *FsMetricsCollector) Collect() {
if f.Metrics == nil {
log.L.Warnf("can not collect FS metrics: Metrics is nil")
return
}
data.FsTotalRead.WithLabelValues(f.ImageRef).Set(float64(f.Metrics.DataRead))
data.FsReadHit.WithLabelValues(f.ImageRef).Set(float64(f.Metrics.FopHits[mtypes.Read]))
data.FsReadError.WithLabelValues(f.ImageRef).Set(float64(f.Metrics.FopErrors[mtypes.Read]))
for _, h := range data.MetricHists {
o, err := h.ToConstHistogram(f.Metrics, f.ImageRef)
if err != nil {
log.L.Warnf("failed to new const histogram for %s, error: %v", h.Desc.String(), err)
return
}
h.Save(o)
}
}
func (i *InflightMetricsVecCollector) Collect() {
if i.MetricsVec == nil {
log.L.Warnf("can not collect inflight metrics: Metrics is nil")
return
}
// The TimestampSecs of inflight IO is the beginning time of this request.
// We can calculate the elapsed time by time.Now().Unix() - TimestampSecs.
// The inflight IOs which have a longer elapsed time than the HungIOInterval (default 10 seconds) are hung IOs.
totalHungIOMap := 0
nowTime := time.Now()
for _, daemonInflightIOMetrics := range i.MetricsVec {
for _, inflightIOMetric := range daemonInflightIOMetrics.Values {
elapsed := nowTime.Sub(time.Unix(int64(inflightIOMetric.TimestampSecs), 0))
if elapsed >= i.HungIOInterval {
totalHungIOMap++
log.L.Debugf("Record hung IO, Inode: %v, Opcode: %v, Unique: %v, Elapsed: %v",
inflightIOMetric.Inode, inflightIOMetric.Opcode, inflightIOMetric.Unique, elapsed)
}
}
}
data.TotalHungIO.Set(float64(totalHungIOMap))
}
func (f *FsMetricsVecCollector) Clear() {
for _, h := range data.MetricHists {
h.Clear()
}
}
func (f *FsMetricsVecCollector) Collect() {
f.Clear()
for _, fsMetrics := range f.MetricsVec {
fsMetrics.Collect()
}
}
================================================
FILE: pkg/metrics/collector/snapshotter.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package collector
import (
"context"
"github.com/containerd/continuity/fs"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/metrics/data"
"github.com/containerd/nydus-snapshotter/pkg/metrics/tool"
"github.com/prometheus/client_golang/prometheus"
)
type SnapshotterMetricsCollector struct {
ctx context.Context
cacheDir string
pid int
lastStat *tool.Stat
}
type SnapshotMethod string
const (
SnapshotMethodUnknown SnapshotMethod = "UNKNOWN"
SnapshotMethodPrepare SnapshotMethod = "PREPARE"
SnapshotMethodMount SnapshotMethod = "MOUNTS"
SnapshotMethodCleanup SnapshotMethod = "CLEANUP"
SnapshotMethodRemove SnapshotMethod = "REMOVE"
)
func (s *SnapshotterMetricsCollector) CollectCacheUsage() {
du, err := fs.DiskUsage(s.ctx, s.cacheDir)
if err != nil {
log.L.Warnf("Get disk usage failed: %v", err)
} else {
data.CacheUsage.Set(float64(du.Size) / 1024)
}
}
func (s *SnapshotterMetricsCollector) CollectResourceUsage() {
currentStat, err := tool.GetProcessStat(s.pid)
if err != nil {
log.L.Warnf("Can not get current process stat.")
return
}
if s.lastStat == nil {
log.L.Debug("Can not get resource usage information: lastStat is nil")
s.lastStat = currentStat
return
}
cpuSys := (currentStat.Stime - s.lastStat.Stime) / tool.ClkTck
cpuUsr := (currentStat.Utime - s.lastStat.Utime) / tool.ClkTck
cpuPercent, err := tool.CalculateCPUUtilization(s.lastStat, currentStat)
if err != nil {
log.L.WithError(err).Warnf("Failed to calculate CPU utilization")
}
s.lastStat = currentStat
memory := currentStat.Rss * tool.PageSize
runTime := currentStat.Uptime - currentStat.Start/tool.ClkTck
data.CPUSystem.Set(tool.FormatFloat64(cpuSys, 2))
data.CPUUser.Set(tool.FormatFloat64(cpuUsr, 2))
data.CPUUsage.Set(tool.FormatFloat64(cpuPercent, 2))
data.MemoryUsage.Set(tool.FormatFloat64(memory/1024, 2))
data.Fds.Set(currentStat.Fds)
data.RunTime.Set(tool.FormatFloat64(runTime, 2))
data.Thread.Set(currentStat.Thread)
}
func (s *SnapshotterMetricsCollector) Collect() {
s.CollectCacheUsage()
s.CollectResourceUsage()
}
func CollectSnapshotMetricsTimer(h *prometheus.HistogramVec, event SnapshotMethod) *prometheus.Timer {
return prometheus.NewTimer(
prometheus.ObserverFunc(
(func(v float64) {
h.WithLabelValues(string(event)).Observe(tool.FormatFloat64(v*1000, 6))
})))
}
================================================
FILE: pkg/metrics/data/auth.go
================================================
/*
* Copyright (c) 2026. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package data
import (
"github.com/prometheus/client_golang/prometheus"
)
var (
// CredentialRenewals counts credential renewal attempts per image ref,
// labeled by outcome ("success" or "failure").
CredentialRenewals = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "snapshotter_credential_renewals_total",
Help: "Total number of credential renewal attempts, labeled by image ref and result (success or failure).",
},
[]string{imageRefLabel, credentialResultLabel},
)
// CredentialStoreEntries tracks the number of credentials currently held
// in the renewal store per image ref.
CredentialStoreEntries = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "snapshotter_credential_store_entries",
Help: "Number of credentials currently tracked in the renewal store per image ref.",
},
[]string{imageRefLabel},
)
)
================================================
FILE: pkg/metrics/data/cache.go
================================================
/*
* Copyright (c) 2025. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package data
import (
"github.com/containerd/nydus-snapshotter/pkg/metrics/types/ttl"
"github.com/prometheus/client_golang/prometheus"
)
var (
CachePartialHits = ttl.NewGaugeVecWithTTL(
prometheus.GaugeOpts{
Name: "nydusd_cache_partial_hits",
Help: "Number of partial cache hits (IO needs a part of the chunk)",
},
[]string{imageRefLabel},
ttl.DefaultTTL,
)
CacheWholeHits = ttl.NewGaugeVecWithTTL(
prometheus.GaugeOpts{
Name: "nydusd_cache_whole_hits",
Help: "Number of whole cache hits (IO needs the entire chunk)",
},
[]string{imageRefLabel},
ttl.DefaultTTL,
)
CacheTotalRequests = ttl.NewGaugeVecWithTTL(
prometheus.GaugeOpts{
Name: "nydusd_cache_total_requests",
Help: "Total number of cache read requests. Cache hit percentage = (partial_hits + whole_hits) / total",
},
[]string{imageRefLabel},
ttl.DefaultTTL,
)
CacheEntriesCount = ttl.NewGaugeVecWithTTL(
prometheus.GaugeOpts{
Name: "nydusd_cache_entries_count",
Help: "Number of chunks in ready status",
},
[]string{imageRefLabel},
ttl.DefaultTTL,
)
CachePrefetchDataBytes = ttl.NewGaugeVecWithTTL(
prometheus.GaugeOpts{
Name: "nydusd_cache_prefetch_data_bytes",
Help: "Total amount of data prefetched, in bytes",
},
[]string{imageRefLabel},
ttl.DefaultTTL,
)
CachePrefetchRequestsCount = ttl.NewGaugeVecWithTTL(
prometheus.GaugeOpts{
Name: "nydusd_cache_prefetch_requests_count",
Help: "Total prefetch requests issued from storage/blobs or rafs filesystem layer for each file that needs prefetch",
},
[]string{imageRefLabel},
ttl.DefaultTTL,
)
CachePrefetchWorkers = ttl.NewGaugeVecWithTTL(
prometheus.GaugeOpts{
Name: "nydusd_cache_prefetch_workers",
Help: "Number of prefetch workers",
},
[]string{imageRefLabel},
ttl.DefaultTTL,
)
CachePrefetchUnmergedChunks = ttl.NewGaugeVecWithTTL(
prometheus.GaugeOpts{
Name: "nydusd_cache_prefetch_unmerged_chunks",
Help: "Number of unmerged chunks",
},
[]string{imageRefLabel},
ttl.DefaultTTL,
)
CachePrefetchCumulativeTimeMillis = ttl.NewGaugeVecWithTTL(
prometheus.GaugeOpts{
Name: "nydusd_cache_prefetch_cumulative_time_millis",
Help: "Cumulative time latencies in milliseconds of each prefetch request which can be handled in parallel. It starts when the request is born including nydusd processing and schedule and end when the chunk is downloaded and stored. The average prefetch latency can be calculated by `prefetch_cumulative_time_millis / prefetch_requests_count`",
},
[]string{imageRefLabel},
ttl.DefaultTTL,
)
CachePrefetchTotalDurationMillis = ttl.NewGaugeVecWithTTL(
prometheus.GaugeOpts{
Name: "nydusd_cache_prefetch_duration_millis",
Help: "Total wall clock duration of the prefetch, in milliseconds",
},
[]string{imageRefLabel},
ttl.DefaultTTL,
)
CacheBufferedBackendSize = ttl.NewGaugeVecWithTTL(
prometheus.GaugeOpts{
Name: "nydusd_cache_buffered_backend_size",
Help: "Size of the buffered backend, in bytes",
},
[]string{imageRefLabel},
ttl.DefaultTTL,
)
)
================================================
FILE: pkg/metrics/data/daemon.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package data
import (
"github.com/containerd/nydus-snapshotter/pkg/metrics/types/ttl"
"github.com/prometheus/client_golang/prometheus"
)
var (
NydusdEventCount = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "nydusd_lifetime_event_counts",
Help: "The lifetime events of nydus daemon.",
},
[]string{nydusdEventLabel},
)
NydusdCount = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "nydusd_counts",
Help: "The counts of nydus daemon.",
},
[]string{nydusdVersionLabel},
)
NydusdRSS = ttl.NewGaugeVecWithTTL(
prometheus.GaugeOpts{
Name: "nydusd_rss_kilobytes",
Help: "Memory usage (RSS) of nydus daemon.",
},
[]string{daemonIDLabel},
ttl.DefaultTTL,
)
NydusdImageInfo = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "nydusd_image_info",
Help: "Mapping of nydus daemon to served image references.",
},
[]string{daemonIDLabel, imageRefLabel},
)
)
================================================
FILE: pkg/metrics/data/fs.go
================================================
/*
* Copyright (c) 2021. Alibaba Cloud. All rights reserved.
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package data
import (
"github.com/containerd/nydus-snapshotter/pkg/daemon/types"
mtypes "github.com/containerd/nydus-snapshotter/pkg/metrics/types"
"github.com/containerd/nydus-snapshotter/pkg/metrics/types/ttl"
"github.com/prometheus/client_golang/prometheus"
)
var (
FsTotalRead = ttl.NewGaugeVecWithTTL(
prometheus.GaugeOpts{
Name: "nydusd_total_read_bytes",
Help: "Total bytes read against the nydus filesystem",
},
[]string{imageRefLabel},
ttl.DefaultTTL,
)
FsReadHit = ttl.NewGaugeVecWithTTL(
prometheus.GaugeOpts{
Name: "nydusd_read_hits",
Help: "Total number of successful read operations.",
},
[]string{imageRefLabel},
ttl.DefaultTTL,
)
FsReadError = ttl.NewGaugeVecWithTTL(
prometheus.GaugeOpts{
Name: "nydusd_read_errors",
Help: "Total number of failed read operations.",
},
[]string{imageRefLabel},
ttl.DefaultTTL,
)
TotalHungIO = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "nydusd_hung_io_counts",
Help: "Total number of hung IOs.",
},
)
)
// Fs metric histograms
var MetricHists = []*mtypes.MetricHistogram{
{
Desc: prometheus.NewDesc(
"nydusd_cumulative_read_block_bytes",
"Cumulative read size histogram for different block size, in bytes.",
[]string{imageRefLabel},
prometheus.Labels{},
),
Buckets: []uint64{1, 4, 16, 64, 128, 512, 1024, 2048},
GetCounters: func(m *types.FsMetrics) []uint64 {
return m.BlockCountRead
},
},
{
Desc: prometheus.NewDesc(
"nydusd_read_latency_microseconds",
"Read latency histogram, in microseconds",
[]string{imageRefLabel},
prometheus.Labels{},
),
Buckets: []uint64{1, 20, 50, 100, 500, 1000, 2000, 4000},
GetCounters: func(m *types.FsMetrics) []uint64 {
return m.ReadLatencyDist
},
},
}
================================================
FILE: pkg/metrics/data/labels.go
================================================
package data
const (
imageRefLabel = "image_ref"
nydusdEventLabel = "nydusd_event"
nydusdVersionLabel = "version"
daemonIDLabel = "daemon_id"
snapshotEventLabel = "snapshot_operation"
credentialResultLabel = "result"
)
================================================
FILE: pkg/metrics/data/snapshotter.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package data
import (
"github.com/prometheus/client_golang/prometheus"
)
var (
defaultDurationBuckets = []float64{.5, 1, 5, 10, 50, 100, 150, 200, 250, 300, 350, 400, 600, 1000}
)
var (
SnapshotEventElapsedHists = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "snapshotter_snapshot_operation_elapsed_milliseconds",
Help: "The elapsed time for snapshot events.",
Buckets: defaultDurationBuckets,
},
[]string{snapshotEventLabel},
)
CacheUsage = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "snapshotter_cache_usage_kilobytes",
Help: "Disk usage of snapshotter local cache.",
},
)
CPUUsage = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "snapshotter_cpu_usage_percentage",
Help: "CPU usage percentage of snapshotter.",
},
)
MemoryUsage = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "snapshotter_memory_usage_kilobytes",
Help: "Memory usage (RSS) of snapshotter.",
},
)
CPUSystem = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "snapshotter_cpu_system_time_seconds",
Help: "CPU time of snapshotter in system.",
},
)
CPUUser = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "snapshotter_cpu_user_time_seconds",
Help: "CPU time of snapshotter in user.",
},
)
Fds = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "snapshotter_fd_counts",
Help: "Fd counts of snapshotter.",
},
)
RunTime = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "snapshotter_run_time_seconds",
Help: "Running time of snapshotter from starting.",
},
)
Thread = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "snapshotter_thread_counts",
Help: "Thread counts of snapshotter.",
},
)
CacheBlobsDeleted = prometheus.NewCounter(
prometheus.CounterOpts{
Name: "snapshotter_cache_blobs_deleted_total",
Help: "Total number of cache blobs deleted during cleanup.",
},
)
CacheBlobsInUse = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "snapshotter_cache_blobs_in_use",
Help: "Number of cache blobs currently in use by running daemons.",
},
)
CacheBlobDeletionErrors = prometheus.NewCounter(
prometheus.CounterOpts{
Name: "snapshotter_cache_blob_deletion_errors_total",
Help: "Total number of errors encountered while deleting cache blobs.",
},
)
)
================================================
FILE: pkg/metrics/listener.go
================================================
/*
* Copyright (c) 2021. Ant Group. All rights reserved.
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package metrics
import (
"fmt"
"net"
"net/http"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/metrics/registry"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// Endpoint for prometheus metrics
var endpointPromMetrics = "/v1/metrics"
func trapClosedConnErr(err error) error {
if err == nil || errors.Is(err, net.ErrClosed) {
return nil
}
return err
}
// NewListener creates a new TCP listener bound to the given address.
func NewMetricsHTTPListenerServer(addr string) error {
if addr == "" {
return fmt.Errorf("the address for metrics HTTP server is invalid")
}
http.Handle(endpointPromMetrics, promhttp.HandlerFor(registry.Registry, promhttp.HandlerOpts{
ErrorHandling: promhttp.HTTPErrorOnError,
}))
l, err := net.Listen("tcp", addr)
if err != nil {
return errors.Wrapf(err, "metrics server listener, addr=%s", addr)
}
go func() {
if err := http.Serve(l, nil); trapClosedConnErr(err) != nil {
log.L.Errorf("Metrics server fails to listen or serve %s: %v", addr, err)
}
}()
return nil
}
================================================
FILE: pkg/metrics/registry/registry.go
================================================
/*
* Copyright (c) 2021. Ant Group. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package registry
import (
"github.com/containerd/nydus-snapshotter/pkg/metrics/data"
"github.com/prometheus/client_golang/prometheus"
)
var (
Registry = prometheus.NewRegistry()
)
func init() {
Registry.MustRegister(
data.FsTotalRead,
data.FsReadHit,
data.FsReadError,
data.TotalHungIO,
data.NydusdEventCount,
data.NydusdCount,
data.NydusdRSS,
data.NydusdImageInfo,
data.SnapshotEventElapsedHists,
data.CacheUsage,
data.CPUUsage,
data.MemoryUsage,
data.CPUSystem,
data.CPUUser,
data.Fds,
data.RunTime,
data.Thread,
data.CachePartialHits,
data.CacheWholeHits,
data.CacheTotalRequests,
data.CacheEntriesCount,
data.CachePrefetchDataBytes,
data.CachePrefetchRequestsCount,
data.CachePrefetchWorkers,
data.CachePrefetchUnmergedChunks,
data.CachePrefetchCumulativeTimeMillis,
data.CachePrefetchTotalDurationMillis,
data.CacheBufferedBackendSize,
data.CacheBlobsDeleted,
data.CacheBlobsInUse,
data.CacheBlobDeletionErrors,
data.CredentialRenewals,
data.CredentialStoreEntries,
)
for _, m := range data.MetricHists {
Registry.MustRegister(m)
}
}
================================================
FILE: pkg/metrics/serve.go
================================================
/*
* Copyright (c) 2021. Ant Group. All rights reserved.
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package metrics
import (
"context"
"fmt"
"os"
"time"
"github.com/pkg/errors"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/config"
"github.com/containerd/nydus-snapshotter/pkg/daemon/types"
"github.com/containerd/nydus-snapshotter/pkg/manager"
"github.com/containerd/nydus-snapshotter/pkg/metrics/collector"
"github.com/containerd/nydus-snapshotter/pkg/metrics/tool"
)
type ServerOpt func(*Server) error
type Server struct {
managers []*manager.Manager
snCollectors []*collector.SnapshotterMetricsCollector
fsCollector *collector.FsMetricsVecCollector
cacheCollector *collector.CacheMetricsVecCollector
inflightCollector *collector.InflightMetricsVecCollector
hungIOInterval time.Duration
collectInterval time.Duration
}
func WithProcessManagers(managers []*manager.Manager) ServerOpt {
return func(s *Server) error {
s.managers = append(s.managers, managers...)
return nil
}
}
func WithCollectInterval(interval time.Duration) ServerOpt {
return func(s *Server) error {
if interval < 0 {
return fmt.Errorf("collect interval (%v) must be positive", interval)
}
s.collectInterval = interval
return nil
}
}
func WithHungIOInterval(hungIOInterval time.Duration) ServerOpt {
return func(s *Server) error {
if hungIOInterval < 0 {
return fmt.Errorf("hung IO interval (%v) must be positive", hungIOInterval)
}
s.hungIOInterval = hungIOInterval
return nil
}
}
func NewServer(ctx context.Context, opts ...ServerOpt) (*Server, error) {
var s Server
for _, o := range opts {
if err := o(&s); err != nil {
return nil, err
}
}
s.fsCollector = collector.NewFsMetricsVecCollector()
s.inflightCollector = collector.NewInflightMetricsVecCollector(s.hungIOInterval)
s.cacheCollector = collector.NewCacheMetricsVecCollector()
for _, pm := range s.managers {
snCollector, err := collector.NewSnapshotterMetricsCollector(ctx, pm.CacheDir(), os.Getpid())
if err != nil {
return nil, errors.Wrap(err, "new snapshotter metrics collector failed")
}
s.snCollectors = append(s.snCollectors, snCollector)
}
return &s, nil
}
func (s *Server) CollectDaemonResourceMetrics(_ context.Context) {
var daemonResource collector.DaemonResourceCollector
for _, pm := range s.managers {
// Collect daemon resource usage metrics.
daemons := pm.ListDaemons()
for _, d := range daemons {
memRSS, err := tool.GetProcessMemoryRSSKiloBytes(d.Pid())
if err != nil {
log.L.Warnf("Failed to get daemon %s RSS memory", d.ID())
}
daemonResource.DaemonID = d.ID()
daemonResource.Value = memRSS
daemonResource.Collect()
}
}
}
func (s *Server) CollectFsMetrics(ctx context.Context) {
var fsMetricsVec []collector.FsMetricsCollector
for _, pm := range s.managers {
// Collect FS metrics from fusedev daemons.
if pm.FsDriver != config.FsDriverFusedev {
continue
}
daemons := pm.ListDaemons()
for _, d := range daemons {
// Skip daemons that are not serving
if d.State() != types.DaemonStateRunning {
continue
}
for _, i := range d.RafsCache.List() {
var sid string
if d.IsSharedDaemon() {
sid = i.SnapshotID
} else {
sid = ""
}
fsMetrics, err := d.GetFsMetrics(sid)
if err != nil {
log.G(ctx).Errorf("failed to get fs metric: %v", err)
continue
}
fsMetricsVec = append(fsMetricsVec, collector.FsMetricsCollector{
Metrics: fsMetrics,
ImageRef: i.ImageID,
})
}
}
}
if fsMetricsVec != nil {
s.fsCollector.MetricsVec = fsMetricsVec
s.fsCollector.Collect()
}
}
func (s *Server) CollectCacheMetrics(ctx context.Context) {
var cacheMetricsVec []collector.CacheMetricsCollector
for _, pm := range s.managers {
daemons := pm.ListDaemons()
for _, d := range daemons {
// Skip daemons that are not serving
if d.State() != types.DaemonStateRunning {
continue
}
for _, i := range d.RafsCache.List() {
var sid string
if d.IsSharedDaemon() {
sid = i.SnapshotID
} else {
sid = ""
}
cacheMetrics, err := d.GetCacheMetrics(sid)
if err != nil {
log.G(ctx).Errorf("failed to get cache metric: %v", err)
continue
}
cacheMetricsVec = append(cacheMetricsVec, collector.CacheMetricsCollector{
Metrics: cacheMetrics,
ImageRef: i.ImageID,
DaemonID: d.ID(),
})
}
}
}
if cacheMetricsVec != nil {
s.cacheCollector.MetricsVec = cacheMetricsVec
s.cacheCollector.Collect()
}
}
func (s *Server) CollectInflightMetrics(ctx context.Context) {
inflightMetricsVec := make([]*types.InflightMetrics, 0, 16)
for _, pm := range s.managers {
// Collect inflight metrics from fusedev daemons.
if pm.FsDriver != config.FsDriverFusedev {
continue
}
daemons := pm.ListDaemons()
for _, d := range daemons {
// Only count for daemon that is serving
if d.State() != types.DaemonStateRunning {
continue
}
inflightMetrics, err := d.GetInflightMetrics()
if err != nil {
log.G(ctx).Errorf("failed to get inflight metric: %v", err)
continue
}
inflightMetricsVec = append(inflightMetricsVec, inflightMetrics)
}
}
if inflightMetricsVec != nil {
s.inflightCollector.MetricsVec = inflightMetricsVec
s.inflightCollector.Collect()
}
}
func (s *Server) StartCollectMetrics(ctx context.Context) error {
timer := time.NewTicker(s.collectInterval)
// The timer period is the same as the interval for determining hung IOs.
//
// Since the elapsed time of hung IO is configuration dependent,
// e.g. timeout * retry times when the backend is a registry.
// Therefore, we cannot get complete hung IO data after 1 minute.
InflightTimer := time.NewTicker(s.hungIOInterval)
outer:
for {
select {
case <-timer.C:
s.CollectFsMetrics(ctx)
s.CollectCacheMetrics(ctx)
s.CollectDaemonResourceMetrics(ctx)
// Collect snapshotter metrics.
for _, snCollector := range s.snCollectors {
snCollector.Collect()
}
case <-InflightTimer.C:
s.CollectInflightMetrics(ctx)
case <-ctx.Done():
log.G(ctx).Infof("cancel metrics collecting")
break outer
}
}
return nil
}
================================================
FILE: pkg/metrics/tool/common.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package tool
import (
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"github.com/containerd/log"
)
const (
// Constant value for linux platform except alpha and ia64.
defaultClkTck = 100
)
func FormatFloat64(f float64, point int) float64 {
var value float64
switch point {
case 6:
value, _ = strconv.ParseFloat(fmt.Sprintf("%.6f", f), 64)
case 2:
fallthrough
default:
value, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", f), 64)
}
return value
}
// FIXME: return error
func ParseFloat64(val string) float64 {
floatVal, _ := strconv.ParseFloat(val, 64)
return floatVal
}
func GetClkTck() float64 {
getconfPath, err := exec.LookPath("getconf")
if err != nil {
log.L.Warnf("can not find getconf in the system PATH, error %v", err)
return defaultClkTck
}
out, err := exec.Command(getconfPath, "CLK_TCK").Output()
if err != nil {
log.L.Warnf("get CLK_TCK failed: %v", err)
return defaultClkTck
}
return ParseFloat64(strings.ReplaceAll(string(out), "\n", ""))
}
func GetPageSize() float64 {
return float64(os.Getpagesize())
}
================================================
FILE: pkg/metrics/tool/stat.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package tool
import (
"os"
"path"
"strconv"
"strings"
"github.com/containerd/nydus-snapshotter/pkg/errdefs"
"github.com/pkg/errors"
)
// Please refer to https://man7.org/linux/man-pages/man5/proc.5.html for the metrics meanings
type Stat struct {
Utime float64
Stime float64
Cutime float64
Cstime float64
Thread float64
Start float64
Rss float64
Fds float64
Uptime float64
}
var (
ClkTck = GetClkTck()
PageSize = GetPageSize()
)
func CalculateCPUUtilization(begin *Stat, now *Stat) (float64, error) {
if begin == nil || now == nil {
return 0.0, errdefs.ErrInvalidArgument
}
cpuSys := (now.Stime - begin.Stime) / ClkTck
cpuUsr := (now.Utime - begin.Utime) / ClkTck
total := cpuSys + cpuUsr
seconds := now.Uptime - begin.Uptime
cpuPercent := (total / seconds) * 100
return cpuPercent, nil
}
func GetProcessMemoryRSSKiloBytes(pid int) (float64, error) {
stat, err := GetProcessStat(pid)
if err != nil {
return 0.0, errors.Wrapf(err, "get process stat")
}
return stat.Rss * PageSize / 1024, nil
}
func GetProcessStat(pid int) (*Stat, error) {
uptimeBytes, err := os.ReadFile(path.Join("/proc", "uptime"))
if err != nil {
return nil, errors.Wrapf(err, "get uptime")
}
uptime := ParseFloat64(strings.Split(string(uptimeBytes), " ")[0])
statBytes, err := os.ReadFile(path.Join("/proc", strconv.Itoa(pid), "stat"))
if err != nil {
return nil, errors.Wrapf(err, "get process %d stat", pid)
}
splitAfterStat := strings.SplitAfter(string(statBytes), ")")
if len(splitAfterStat) == 0 || len(splitAfterStat) == 1 {
return nil, errors.Errorf("Can not find process, PID: %d", pid)
}
infos := strings.Split(splitAfterStat[1], " ")
files, err := os.ReadDir(path.Join("/proc", strconv.Itoa(pid), "fdinfo"))
if err != nil {
return nil, errors.Wrapf(err, "read fdinfo")
}
return &Stat{
Utime: ParseFloat64(infos[12]),
Stime: ParseFloat64(infos[13]),
Cutime: ParseFloat64(infos[14]),
Cstime: ParseFloat64(infos[15]),
Thread: ParseFloat64(infos[18]),
Start: ParseFloat64(infos[20]),
Rss: ParseFloat64(infos[22]),
Fds: float64(len(files)),
Uptime: uptime,
}, nil
}
func GetProcessRunningState(pid int) (string, error) {
statBytes, err := os.ReadFile(path.Join("/proc", strconv.Itoa(pid), "stat"))
if err != nil {
return "", errors.Wrapf(err, "get process %d stat", pid)
}
segments := strings.Split(string(statBytes), " ")
state := segments[2]
return state, nil
}
func IsZombieProcess(pid int) (bool, error) {
s, err := GetProcessRunningState(pid)
if err != nil {
return false, err
}
return s == "Z", nil
}
================================================
FILE: pkg/metrics/tool/stat_test.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package tool
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestFindZombie(t *testing.T) {
s, err := GetProcessRunningState(1)
assert.NoError(t, err)
assert.Contains(t, []string{"Ss", "S"}, s)
}
================================================
FILE: pkg/metrics/types/ttl/gauge.go
================================================
/*
* Copyright (c) 2021. Ant Group. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package ttl
import (
"strings"
"sync"
"time"
"github.com/prometheus/client_golang/prometheus"
)
var (
defaultCleanUpPeriod = 10 * time.Minute
DefaultTTL = 3 * time.Minute
)
type LabelWithValue struct {
name string
value string
}
type GaugeVec struct {
labelName []string
ttl time.Duration
labelValueMap map[LabelWithValue]time.Time
mu sync.Mutex
*prometheus.GaugeVec
}
type GaugeWithTTL struct {
labelValue []string
vec *GaugeVec
gauge prometheus.Gauge
}
func NewGaugeVecWithTTL(opts prometheus.GaugeOpts, labelNames []string, ttl time.Duration) *GaugeVec {
gaugeVec := prometheus.NewGaugeVec(opts, labelNames)
res := &GaugeVec{
labelName: labelNames,
ttl: ttl,
GaugeVec: gaugeVec,
labelValueMap: make(map[LabelWithValue]time.Time),
}
go res.cleanUpExpired()
return res
}
func (gv *GaugeVec) cleanUpExpired() {
timer := time.NewTicker(defaultCleanUpPeriod)
for range timer.C {
gv.mu.Lock()
for k, v := range gv.labelValueMap {
if time.Now().After(v) {
gv.DeleteLabelValues(k.value)
delete(gv.labelValueMap, k)
}
}
gv.mu.Unlock()
}
}
func (gv *GaugeVec) WithLabelValues(val ...string) *GaugeWithTTL {
gauge := gv.GaugeVec.WithLabelValues(val...)
return &GaugeWithTTL{
vec: gv,
labelValue: val,
gauge: gauge,
}
}
func (gwt *GaugeWithTTL) Set(val float64) {
gwt.vec.mu.Lock()
gwt.vec.labelValueMap[LabelWithValue{
name: strings.Join(gwt.vec.labelName, ","),
value: strings.Join(gwt.labelValue, ","),
}] = time.Now().Add(gwt.vec.ttl)
gwt.vec.mu.Unlock()
gwt.gauge.Set(val)
}
================================================
FILE: pkg/metrics/types/ttl/gauge_test.go
================================================
/*
* Copyright (c) 2021. Ant Group. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package ttl
import (
"sync"
"testing"
"time"
"github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
"github.com/stretchr/testify/assert"
)
func TestNewGaugeVecWithTTL(t *testing.T) {
defaultCleanUpPeriod = 5 * time.Second
g := NewGaugeVecWithTTL(prometheus.GaugeOpts{
Name: "nydusd_fuse_connection_waiting_count",
Help: "nydusd_fuse_connection_waiting_count",
},
[]string{"daemon_id"},
3*time.Second,
)
g.WithLabelValues("value1").Set(10)
g.WithLabelValues("value2").Set(10)
metricsCh := make(chan prometheus.Metric, 2)
go g.Collect(metricsCh)
var metricsSlice []*dto.Metric
var mu sync.Mutex
var wg sync.WaitGroup
wg.Add(2)
go func() {
for m := range metricsCh {
var metrics dto.Metric
err := m.Write(&metrics)
assert.Nil(t, err)
metricsSlice = append(metricsSlice, &metrics)
wg.Done()
}
}()
wg.Wait()
assert.Equal(t, 2, len(metricsSlice))
metricsSlice = []*dto.Metric{}
time.Sleep(3 * time.Second)
g.WithLabelValues("value1").Set(10)
g.mu.Lock()
assert.Equal(t, 2, len(g.labelValueMap))
g.mu.Unlock()
time.Sleep(3 * time.Second)
g.mu.Lock()
assert.Equal(t, 1, len(g.labelValueMap))
g.mu.Unlock()
metricsCh = make(chan prometheus.Metric, 2)
go g.Collect(metricsCh)
go func() {
for m := range metricsCh {
var metrics dto.Metric
err := m.Write(&metrics)
assert.Nil(t, err)
mu.Lock()
metricsSlice = append(metricsSlice, &metrics)
mu.Unlock()
}
}()
time.Sleep(6 * time.Second)
mu.Lock()
assert.Equal(t, 1, len(metricsSlice))
mu.Unlock()
g.mu.Lock()
assert.Equal(t, 0, len(g.labelValueMap))
g.mu.Unlock()
}
================================================
FILE: pkg/metrics/types/types.go
================================================
/*
* Copyright (c) 2021. Alibaba Cloud. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package fs
import (
"fmt"
"github.com/containerd/nydus-snapshotter/pkg/daemon/types"
"github.com/prometheus/client_golang/prometheus"
)
type Fop int
const (
Getattr = iota
Readlink
Open
Release
Read
Statfs
Getxattr
Listxattr
Opendir
Lookup
Readdir
Readdirplus
Access
Forget
BatchForget
MaxFops
)
func GetMaxFops() uint {
return MaxFops
}
func MakeFopBuckets() []uint64 {
s := make([]uint64, 0, MaxFops)
for i := 0; i < MaxFops; i++ {
s = append(s, uint64(i))
}
return s
}
type GetCountersFn func(*types.FsMetrics) []uint64
type MetricHistogram struct {
Desc *prometheus.Desc
Buckets []uint64
GetCounters GetCountersFn
// Save the last generated histogram metric
constHists []prometheus.Metric
}
func (h *MetricHistogram) ToConstHistogram(m *types.FsMetrics, imageRef string) (prometheus.Metric, error) {
var count, sum uint64
counters := h.GetCounters(m)
hmap := make(map[float64]uint64)
if len(counters) != len(h.Buckets) {
return nil, fmt.Errorf("length of counters(%d) and buckets(%d) not equal: %+v", len(counters), len(h.Buckets), h.Buckets)
}
for i, c := range counters {
count += c
sum += h.Buckets[i] * c
hmap[float64(h.Buckets[i])] = count
}
return prometheus.MustNewConstHistogram(
h.Desc,
count, float64(sum),
hmap,
imageRef,
), nil
}
func (h *MetricHistogram) Clear() {
h.constHists = nil
}
func (h *MetricHistogram) Save(m prometheus.Metric) {
h.constHists = append(h.constHists, m)
}
// Implement prometheus.Collector interface
func (h *MetricHistogram) Describe(ch chan<- *prometheus.Desc) {
if h.Desc != nil {
ch <- h.Desc
}
}
func (h *MetricHistogram) Collect(ch chan<- prometheus.Metric) {
if h.constHists != nil {
for _, hist := range h.constHists {
ch <- hist
}
}
}
================================================
FILE: pkg/pprof/listener.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package pprof
import (
"net"
"net/http"
"net/http/pprof"
"github.com/containerd/log"
"github.com/pkg/errors"
)
func NewPprofHTTPListener(addr string) error {
if addr == "" {
return errors.New("the address for pprof HTTP server is invalid")
}
http.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate"))
http.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine"))
http.Handle("/debug/pprof/allocs", pprof.Handler("allocs"))
http.Handle("/debug/pprof/block", pprof.Handler("block"))
http.Handle("/debug/pprof/mutex", pprof.Handler("mutex"))
http.Handle("/debug/pprof/heap", pprof.Handler("heap"))
l, err := net.Listen("tcp", addr)
if err != nil {
return errors.Wrapf(err, "pprof server listener, addr=%s", addr)
}
go func() {
log.L.Infof("Start pprof HTTP server on %s", addr)
if err := http.Serve(l, nil); err != nil && !errors.Is(err, net.ErrClosed) {
log.L.Errorf("Pprof server fails to listen or serve %s: %v", addr, err)
}
}()
return nil
}
================================================
FILE: pkg/prefetch/prefetch.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package prefetch
import (
"encoding/json"
"sync"
"github.com/containerd/log"
)
type prefetchInfo struct {
prefetchMap map[string]string
prefetchMutex sync.Mutex
}
var Pm prefetchInfo
func (p *prefetchInfo) SetPrefetchFiles(body []byte) error {
p.prefetchMutex.Lock()
defer p.prefetchMutex.Unlock()
var prefetchMsg []map[string]string
if err := json.Unmarshal(body, &prefetchMsg); err != nil {
return err
}
if p.prefetchMap == nil {
p.prefetchMap = make(map[string]string)
}
for _, item := range prefetchMsg {
image := item["image"]
prefetchfiles := item["prefetch"]
p.prefetchMap[image] = prefetchfiles
}
log.L.Infof("received prefetch list from nri plugin: %v ", p.prefetchMap)
return nil
}
func (p *prefetchInfo) GetPrefetchInfo(image string) string {
p.prefetchMutex.Lock()
defer p.prefetchMutex.Unlock()
if prefetchfiles, ok := p.prefetchMap[image]; ok {
return prefetchfiles
}
return ""
}
func (p *prefetchInfo) DeleteFromPrefetchMap(image string) {
p.prefetchMutex.Lock()
defer p.prefetchMutex.Unlock()
delete(p.prefetchMap, image)
}
================================================
FILE: pkg/rafs/rafs.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package rafs
import (
"os"
"path"
"path/filepath"
"sync"
"github.com/mohae/deepcopy"
"github.com/pkg/errors"
"github.com/containerd/errdefs"
"github.com/containerd/nydus-snapshotter/config"
)
const (
AnnoFsCacheDomainID string = "fscache.domainid"
AnnoFsCacheID string = "fscache.id"
)
type NewRafsOpt func(r *Rafs) error
func init() {
// TODO
// A set of RAFS filesystem instances associated with a nydusd daemon.
RafsGlobalCache = Cache{instances: make(map[string]*Rafs)}
}
// Global cache to hold all RAFS instances.
var RafsGlobalCache Cache
type Cache struct {
mu sync.Mutex
instances map[string]*Rafs
}
func NewRafsCache() Cache {
return Cache{instances: make(map[string]*Rafs)}
}
func (rs *Cache) Lock() {
rs.mu.Lock()
}
func (rs *Cache) Unlock() {
rs.mu.Unlock()
}
func (rs *Cache) Add(r *Rafs) {
rs.mu.Lock()
rs.instances[r.SnapshotID] = r
rs.mu.Unlock()
}
func (rs *Cache) Remove(snapshotID string) {
rs.mu.Lock()
delete(rs.instances, snapshotID)
rs.mu.Unlock()
}
func (rs *Cache) Get(snapshotID string) *Rafs {
rs.mu.Lock()
defer rs.mu.Unlock()
return rs.instances[snapshotID]
}
func (rs *Cache) Len() int {
rs.mu.Lock()
defer rs.mu.Unlock()
return len(rs.instances)
}
func (rs *Cache) Head() *Rafs {
rs.mu.Lock()
defer rs.mu.Unlock()
for _, v := range rs.instances {
return v
}
return nil
}
func (rs *Cache) List() map[string]*Rafs {
rs.mu.Lock()
defer rs.mu.Unlock()
instances := deepcopy.Copy(rs.instances).(map[string]*Rafs)
return instances
}
func (rs *Cache) ListLocked() map[string]*Rafs {
return rs.instances
}
func (rs *Cache) SetIntances(instances map[string]*Rafs) {
rs.Lock()
defer rs.Unlock()
rs.instances = instances
}
// The whole struct will be persisted
type Rafs struct {
Seq uint64
ImageID string // Usually is the image reference
DaemonID string
FsDriver string
SnapshotID string // Given by containerd
SnapshotDir string
UnderlyingFiles []string // Underlying cache blob files
// 1. A host kernel EROFS/TARFS mountpoint
// 2. Absolute path to each rafs instance root directory.
Mountpoint string
Annotations map[string]string
}
func NewRafs(snapshotID, imageID, fsDriver string) (*Rafs, error) {
snapshotDir := path.Join(config.GetSnapshotsRootDir(), snapshotID)
rafs := &Rafs{
FsDriver: fsDriver,
ImageID: imageID,
SnapshotID: snapshotID,
SnapshotDir: snapshotDir,
Annotations: make(map[string]string),
UnderlyingFiles: []string{},
}
if err := os.MkdirAll(snapshotDir, 0755); err != nil {
return nil, err
}
RafsGlobalCache.Add(rafs)
return rafs, nil
}
func (r *Rafs) AddAnnotation(k, v string) {
r.Annotations[k] = v
}
func (r *Rafs) GetSnapshotDir() string {
return r.SnapshotDir
}
func (r *Rafs) GetFsDriver() string {
if r.FsDriver != "" {
return r.FsDriver
}
return config.GetFsDriver()
}
// Blob caches' chunk bitmap and meta headers are stored here.
func (r *Rafs) FscacheWorkDir() string {
return filepath.Join(r.SnapshotDir, "fs")
}
func (r *Rafs) SetMountpoint(mp string) {
r.Mountpoint = mp
}
// Get top level mount point for the RAFS instance:
// - FUSE with dedicated mode: the FUSE filesystem mount point, the RAFS filesystem is directly
// mounted at the mount point.
// - FUSE with shared mode: the FUSE filesystem mount point, the RAFS filesystem is mounted
// at a subdirectory under the mount point.
// - EROFS/fscache: the EROFS filesystem mount point.
func (r *Rafs) GetMountpoint() string {
return r.Mountpoint
}
// Get the sub-directory under a FUSE mount point to mount a RAFS instance.
// For a nydusd daemon in shared mode, one or more RAFS filesystem instances can be mounted
// to sub-directories of the FUSE filesystem. This method returns the subdirectory for a
// RAFS filesystem instance.
func (r *Rafs) RelaMountpoint() string {
return filepath.Join("/", r.SnapshotID)
}
func (r *Rafs) BootstrapFile() (string, error) {
// meta files are stored at /fs/image/image.boot
bootstrap := filepath.Join(r.SnapshotDir, "fs", "image", "image.boot")
_, err := os.Stat(bootstrap)
if err == nil {
return bootstrap, nil
}
if os.IsNotExist(err) {
// check legacy location for backward compatibility
bootstrap = filepath.Join(r.SnapshotDir, "fs", "image.boot")
_, err = os.Stat(bootstrap)
if err == nil {
return bootstrap, nil
}
}
return "", errors.Wrapf(errdefs.ErrNotFound, "bootstrap %s", bootstrap)
}
================================================
FILE: pkg/referrer/manager.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package referrer
import (
"context"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/auth"
"github.com/golang/groupcache/lru"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"golang.org/x/sync/singleflight"
)
type Manager struct {
insecure bool
cache *lru.Cache
sg singleflight.Group
}
func NewManager(insecure bool) *Manager {
manager := Manager{
insecure: insecure,
cache: lru.New(500),
sg: singleflight.Group{},
}
return &manager
}
// CheckReferrer attempts to fetch the referrers and parse out
// the nydus image by specified manifest digest.
func (manager *Manager) CheckReferrer(ctx context.Context, ref string, manifestDigest digest.Digest) (*ocispec.Descriptor, error) {
metaLayer, err, _ := manager.sg.Do(manifestDigest.String(), func() (interface{}, error) {
// Try to get nydus metadata layer descriptor from LRU cache.
if metaLayer, ok := manager.cache.Get(manifestDigest); ok {
desc := metaLayer.(ocispec.Descriptor)
return &desc, nil
}
keyChain, err := auth.GetKeyChainByRef(ref, nil)
if err != nil {
return nil, errors.Wrap(err, "get key chain")
}
// No LRU cache found, try to fetch referrers and parse out
// the nydus metadata layer descriptor.
referrer := newReferrer(keyChain, manager.insecure)
metaLayer, err := referrer.checkReferrer(ctx, ref, manifestDigest)
if err != nil {
return nil, errors.Wrap(err, "check referrer")
}
// FIXME: how to invalidate the LRU cache if referrers update?
manager.cache.Add(manifestDigest, *metaLayer)
return metaLayer, nil
})
if err != nil {
log.L.WithField("ref", ref).WithError(err).Warn("check referrer")
return nil, err
}
return metaLayer.(*ocispec.Descriptor), nil
}
// TryFetchMetadata try to fetch and unpack nydus metadata file to specified path.
func (manager *Manager) TryFetchMetadata(ctx context.Context, ref string, manifestDigest digest.Digest, metadataPath string) error {
metaLayer, err := manager.CheckReferrer(ctx, ref, manifestDigest)
if err != nil {
return errors.Wrap(err, "check referrer")
}
keyChain, err := auth.GetKeyChainByRef(ref, nil)
if err != nil {
return errors.Wrap(err, "get key chain")
}
referrer := newReferrer(keyChain, manager.insecure)
return referrer.fetchMetadata(ctx, ref, *metaLayer, metadataPath)
}
================================================
FILE: pkg/referrer/referrer.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package referrer
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"github.com/containerd/nydus-snapshotter/pkg/auth"
"github.com/containerd/nydus-snapshotter/pkg/converter"
"github.com/containerd/nydus-snapshotter/pkg/label"
"github.com/containerd/nydus-snapshotter/pkg/remote"
"github.com/containerd/nydus-snapshotter/pkg/remote/remotes"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
// Containerd restricts the max size of manifest index to 8M, follow it.
const maxManifestIndexSize = 0x800000
type referrer struct {
remote *remote.Remote
}
func newReferrer(keyChain *auth.PassKeyChain, insecure bool) *referrer {
return &referrer{
remote: remote.New(keyChain, insecure),
}
}
// checkReferrer fetches the referrers and parses out the nydus
// image by specified manifest digest.
// it's using distribution list referrers API.
func (r *referrer) checkReferrer(ctx context.Context, ref string, manifestDigest digest.Digest) (*ocispec.Descriptor, error) {
handle := func() (*ocispec.Descriptor, error) {
// Create an new resolver to request.
fetcher, err := r.remote.Fetcher(ctx, ref)
if err != nil {
return nil, errors.Wrap(err, "get fetcher")
}
// Fetch image referrers from remote registry.
rc, _, err := fetcher.(remotes.ReferrersFetcher).FetchReferrers(ctx, manifestDigest)
if err != nil {
return nil, errors.Wrap(err, "fetch referrers")
}
defer rc.Close()
// Parse image manifest list from referrers.
var index ocispec.Index
bytes, err := io.ReadAll(io.LimitReader(rc, maxManifestIndexSize))
if err != nil {
return nil, errors.Wrap(err, "read referrers")
}
if err := json.Unmarshal(bytes, &index); err != nil {
return nil, errors.Wrap(err, "unmarshal referrers index")
}
if len(index.Manifests) == 0 {
return nil, fmt.Errorf("empty referrer list")
}
// Prefer to fetch the last manifest and check if it is a nydus image.
// TODO: should we search by matching ArtifactType?
rc, err = fetcher.Fetch(ctx, index.Manifests[0])
if err != nil {
return nil, errors.Wrap(err, "fetch referrers")
}
defer rc.Close()
var manifest ocispec.Manifest
bytes, err = io.ReadAll(rc)
if err != nil {
return nil, errors.Wrap(err, "read manifest")
}
if err := json.Unmarshal(bytes, &manifest); err != nil {
return nil, errors.Wrap(err, "unmarshal manifest")
}
if len(manifest.Layers) < 1 {
return nil, fmt.Errorf("invalid manifest")
}
metaLayer := manifest.Layers[len(manifest.Layers)-1]
if !label.IsNydusMetaLayer(metaLayer.Annotations) {
return nil, fmt.Errorf("invalid nydus manifest")
}
return &metaLayer, nil
}
desc, err := handle()
if err != nil && r.remote.RetryWithPlainHTTP(ref, err) {
return handle()
}
return desc, err
}
// fetchMetadata fetches and unpacks nydus metadata file to specified path.
func (r *referrer) fetchMetadata(ctx context.Context, ref string, desc ocispec.Descriptor, metadataPath string) error {
handle := func() error {
// Create an new resolver to request.
resolver := r.remote.Resolve(ctx, ref)
fetcher, err := resolver.Fetcher(ctx, ref)
if err != nil {
return errors.Wrap(err, "get fetcher")
}
// Unpack nydus metadata file to specified path.
rc, err := fetcher.Fetch(ctx, desc)
if err != nil {
return errors.Wrap(err, "fetch nydus metadata")
}
defer rc.Close()
if err := remote.Unpack(rc, converter.BootstrapFileNameInLayer, metadataPath); err != nil {
os.Remove(metadataPath)
return errors.Wrap(err, "unpack metadata from layer")
}
return nil
}
// Check if metafile already exists to avoid unnecessary fetch
if _, err := os.Stat(metadataPath); err == nil {
return nil
}
err := handle()
if err != nil && r.remote.RetryWithPlainHTTP(ref, err) {
return handle()
}
return err
}
================================================
FILE: pkg/remote/remote.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package remote
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"strings"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/auth"
"github.com/containerd/nydus-snapshotter/pkg/remote/remotes"
"github.com/containerd/nydus-snapshotter/pkg/remote/remotes/docker"
"github.com/distribution/reference"
"github.com/pkg/errors"
)
// IsErrHTTPResponseToHTTPSClient returns whether err is
// "http: server gave HTTP response to HTTPS client"
func isErrHTTPResponseToHTTPSClient(err error) bool {
// The error string is unexposed as of Go 1.16, so we can't use `errors.Is`.
// https://github.com/golang/go/issues/44855
const unexposed = "server gave HTTP response to HTTPS client"
return strings.Contains(err.Error(), unexposed)
}
// IsErrConnectionRefused return whether err is
// "connect: connection refused"
func isErrConnectionRefused(err error) bool {
const errMessage = "connect: connection refused"
return strings.Contains(err.Error(), errMessage)
}
type Remote struct {
// The resolver is used for image pull or fetches requests. The best practice
// in containerd is that each resolver instance is used only once for a request
// and is destroyed when the request completes. When a registry token expires,
// the resolver does not re-apply for a new token, so it's better to create a
// new resolver instance using resolverFunc for each request.
resolverFunc func(plainHTTP bool) remotes.Resolver
// withPlainHTTP attempts to request the remote registry using http instead
// of https.
withPlainHTTP bool
// insecure indicates that the registry is explicitly configured as insecure.
// HTTP fallback is only allowed when insecure is true,
// matching Docker's --insecure-registry semantics.
insecure bool
}
func New(keyChain *auth.PassKeyChain, insecure bool) *Remote {
// nolint:unparam
credFunc := func(string) (string, string, error) {
if keyChain == nil {
return "", "", nil
}
return keyChain.Username, keyChain.Password, nil
}
newClient := func(insecure bool) *http.Client {
client := http.DefaultClient
transport := http.DefaultTransport.(*http.Transport)
transport.TLSClientConfig = &tls.Config{
InsecureSkipVerify: insecure,
}
client.Transport = transport
return client
}
resolverFunc := func(plainHTTP bool) remotes.Resolver {
registryHosts := docker.ConfigureDefaultRegistries(
docker.WithAuthorizer(
docker.NewDockerAuthorizer(
docker.WithAuthClient(newClient(insecure)),
docker.WithAuthCreds(credFunc),
),
),
docker.WithClient(newClient(insecure)),
docker.WithPlainHTTP(func(_ string) (bool, error) {
return plainHTTP, nil
}),
)
return docker.NewResolver(docker.ResolverOptions{
Hosts: registryHosts,
})
}
return &Remote{
resolverFunc: resolverFunc,
withPlainHTTP: false,
insecure: insecure,
}
}
func (remote *Remote) RetryWithPlainHTTP(ref string, err error) bool {
if !remote.insecure {
return false
}
retry := err != nil && (isErrHTTPResponseToHTTPSClient(err) || isErrConnectionRefused(err))
if !retry {
return false
}
parsed, _ := reference.ParseNormalizedNamed(ref)
if parsed != nil {
host := reference.Domain(parsed)
// If the error message includes the current registry host string, it
// implies that we can retry the request with plain HTTP.
if strings.Contains(err.Error(), fmt.Sprintf("/%s/", host)) {
log.G(context.TODO()).WithError(err).Warningf("retrying with http for %s", host)
remote.withPlainHTTP = true
}
}
return remote.withPlainHTTP
}
func (remote *Remote) Resolve(_ context.Context, _ string) remotes.Resolver {
return remote.resolverFunc(remote.withPlainHTTP)
}
func (remote *Remote) Fetcher(ctx context.Context, ref string) (remotes.Fetcher, error) {
resolver := remote.Resolve(ctx, ref)
fetcher, err := resolver.Fetcher(ctx, ref)
if err != nil {
return nil, errors.Wrap(err, "get fetcher")
}
return fetcher, nil
}
================================================
FILE: pkg/remote/remote_test.go
================================================
/*
* Copyright (c) 2026. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package remote
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestRetryWithPlainHTTP(t *testing.T) {
const host = "myregistry.example.com"
ref := fmt.Sprintf("%s/repo/image:latest", host)
httpResponseErr := fmt.Errorf("Get https://%s/v2/: server gave HTTP response to HTTPS client", host)
connRefusedErr := fmt.Errorf("Get https://%s/v2/: connect: connection refused", host)
otherErr := fmt.Errorf("some unrelated error")
tests := []struct {
name string
insecure bool
err error
want bool
}{
{
name: "insecure allows HTTP fallback on HTTP response error",
insecure: true,
err: httpResponseErr,
want: true,
},
{
name: "insecure allows HTTP fallback on connection refused",
insecure: true,
err: connRefusedErr,
want: true,
},
{
name: "insecure does not fallback on unrelated error",
insecure: true,
err: otherErr,
want: false,
},
{
name: "secure blocks HTTP fallback on HTTP response error",
insecure: false,
err: httpResponseErr,
want: false,
},
{
name: "secure blocks HTTP fallback on connection refused",
insecure: false,
err: connRefusedErr,
want: false,
},
{
name: "nil error returns false",
insecure: true,
err: nil,
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Remote{insecure: tt.insecure}
got := r.RetryWithPlainHTTP(ref, tt.err)
assert.Equal(t, tt.want, got)
})
}
}
================================================
FILE: pkg/remote/remotes/docker/auth/fetch.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/containerd/containerd/v2/version"
"github.com/containerd/log"
remoteserrors "github.com/containerd/nydus-snapshotter/pkg/remote/remotes/errors"
)
var (
// ErrNoToken is returned if a request is successful but the body does not
// contain an authorization token.
ErrNoToken = errors.New("authorization server did not include a token in the response")
)
// GenerateTokenOptions generates options for fetching a token based on a challenge
func GenerateTokenOptions(ctx context.Context, host, username, secret string, c Challenge) (TokenOptions, error) {
realm, ok := c.Parameters["realm"]
if !ok {
return TokenOptions{}, errors.New("no realm specified for token auth challenge")
}
realmURL, err := url.Parse(realm)
if err != nil {
return TokenOptions{}, fmt.Errorf("invalid token auth challenge realm: %w", err)
}
to := TokenOptions{
Realm: realmURL.String(),
Service: c.Parameters["service"],
Username: username,
Secret: secret,
}
scope, ok := c.Parameters["scope"]
if ok {
to.Scopes = append(to.Scopes, strings.Split(scope, " ")...)
} else {
log.G(ctx).WithField("host", host).Debug("no scope specified for token auth challenge")
}
return to, nil
}
// TokenOptions are options for requesting a token
type TokenOptions struct {
Realm string
Service string
Scopes []string
Username string
Secret string
// FetchRefreshToken enables fetching a refresh token (aka "identity token", "offline token") along with the bearer token.
//
// For HTTP GET mode (FetchToken), FetchRefreshToken sets `offline_token=true` in the request.
// https://docs.docker.com/registry/spec/auth/token/#requesting-a-token
//
// For HTTP POST mode (FetchTokenWithOAuth), FetchRefreshToken sets `access_type=offline` in the request.
// https://docs.docker.com/registry/spec/auth/oauth/#getting-a-token
FetchRefreshToken bool
}
// OAuthTokenResponse is response from fetching token with a OAuth POST request
type OAuthTokenResponse struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresIn int `json:"expires_in"`
IssuedAt time.Time `json:"issued_at"`
Scope string `json:"scope"`
}
// FetchTokenWithOAuth fetches a token using a POST request
func FetchTokenWithOAuth(ctx context.Context, client *http.Client, headers http.Header, clientID string, to TokenOptions) (*OAuthTokenResponse, error) {
form := url.Values{}
if len(to.Scopes) > 0 {
form.Set("scope", strings.Join(to.Scopes, " "))
}
form.Set("service", to.Service)
form.Set("client_id", clientID)
if to.Username == "" {
form.Set("grant_type", "refresh_token")
form.Set("refresh_token", to.Secret)
} else {
form.Set("grant_type", "password")
form.Set("username", to.Username)
form.Set("password", to.Secret)
}
if to.FetchRefreshToken {
form.Set("access_type", "offline")
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, to.Realm, strings.NewReader(form.Encode()))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
for k, v := range headers {
req.Header[k] = append(req.Header[k], v...)
}
if len(req.Header.Get("User-Agent")) == 0 {
req.Header.Set("User-Agent", "containerd/"+version.Version)
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
return nil, remoteserrors.NewUnexpectedStatusErr(resp)
}
decoder := json.NewDecoder(resp.Body)
var tr OAuthTokenResponse
if err = decoder.Decode(&tr); err != nil {
return nil, fmt.Errorf("unable to decode token response: %w", err)
}
if tr.AccessToken == "" {
return nil, ErrNoToken
}
return &tr, nil
}
// FetchTokenResponse is response from fetching token with GET request
type FetchTokenResponse struct {
Token string `json:"token"`
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
IssuedAt time.Time `json:"issued_at"`
RefreshToken string `json:"refresh_token"`
}
// FetchToken fetches a token using a GET request
func FetchToken(ctx context.Context, client *http.Client, headers http.Header, to TokenOptions) (*FetchTokenResponse, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, to.Realm, nil)
if err != nil {
return nil, err
}
for k, v := range headers {
req.Header[k] = append(req.Header[k], v...)
}
if len(req.Header.Get("User-Agent")) == 0 {
req.Header.Set("User-Agent", "containerd/"+version.Version)
}
reqParams := req.URL.Query()
if to.Service != "" {
reqParams.Add("service", to.Service)
}
for _, scope := range to.Scopes {
reqParams.Add("scope", scope)
}
if to.Secret != "" {
req.SetBasicAuth(to.Username, to.Secret)
}
if to.FetchRefreshToken {
reqParams.Add("offline_token", "true")
}
req.URL.RawQuery = reqParams.Encode()
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
return nil, remoteserrors.NewUnexpectedStatusErr(resp)
}
decoder := json.NewDecoder(resp.Body)
var tr FetchTokenResponse
if err = decoder.Decode(&tr); err != nil {
return nil, fmt.Errorf("unable to decode token response: %w", err)
}
// `access_token` is equivalent to `token` and if both are specified
// the choice is undefined. Canonicalize `access_token` by sticking
// things in `token`.
if tr.AccessToken != "" {
tr.Token = tr.AccessToken
}
if tr.Token == "" {
return nil, ErrNoToken
}
return &tr, nil
}
================================================
FILE: pkg/remote/remotes/docker/auth/fetch_test.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
"context"
"reflect"
"strings"
"testing"
)
func TestGenerateTokenOptions(t *testing.T) {
for _, tc := range []struct {
name string
realm string
service string
username string
secret string
scope string
}{
{
name: "MultipleScopes",
realm: "https://test-realm.com",
service: "registry-service",
username: "username",
secret: "secret",
scope: "repository:foo/bar:pull repository:foo/bar:pull,push",
},
{
name: "SingleScope",
realm: "https://test-realm.com",
service: "registry-service",
username: "username",
secret: "secret",
scope: "repository:foo/bar:pull",
},
{
name: "NoScope",
realm: "https://test-realm.com",
service: "registry-service",
username: "username",
secret: "secret",
},
} {
t.Run(tc.name, func(t *testing.T) {
c := Challenge{
Scheme: BearerAuth,
Parameters: map[string]string{
"realm": tc.realm,
"service": tc.service,
"scope": tc.scope,
},
}
options, err := GenerateTokenOptions(context.Background(), "host", tc.username, tc.secret, c)
if err != nil {
t.Fatalf("unexpected error %v", err)
}
expected := TokenOptions{
Realm: tc.realm,
Service: tc.service,
Scopes: strings.Split(tc.scope, " "),
Username: tc.username,
Secret: tc.secret,
}
if !reflect.DeepEqual(options, expected) {
t.Fatalf("expected %v, but got %v", expected, options)
}
})
}
t.Run("MissingRealm", func(t *testing.T) {
c := Challenge{
Scheme: BearerAuth,
Parameters: map[string]string{
"service": "service",
"scope": "repository:foo/bar:pull,push",
},
}
_, err := GenerateTokenOptions(context.Background(), "host", "username", "secret", c)
if err == nil {
t.Fatal("expected an err and got nil")
}
})
t.Run("RealmParseError", func(t *testing.T) {
c := Challenge{
Scheme: BearerAuth,
Parameters: map[string]string{
"realm": "127.0.0.1:8080",
"service": "service",
"scope": "repository:foo/bar:pull,push",
},
}
_, err := GenerateTokenOptions(context.Background(), "host", "username", "secret", c)
if err == nil {
t.Fatal("expected an err and got nil")
}
})
}
================================================
FILE: pkg/remote/remotes/docker/auth/parse.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
"net/http"
"sort"
"strings"
)
// AuthenticationScheme defines scheme of the authentication method
type AuthenticationScheme byte
const (
// BasicAuth is scheme for Basic HTTP Authentication RFC 7617
BasicAuth AuthenticationScheme = 1 << iota
// DigestAuth is scheme for HTTP Digest Access Authentication RFC 7616
DigestAuth
// BearerAuth is scheme for OAuth 2.0 Bearer Tokens RFC 6750
BearerAuth
)
// Challenge carries information from a WWW-Authenticate response header.
// See RFC 2617.
type Challenge struct {
// scheme is the auth-scheme according to RFC 2617
Scheme AuthenticationScheme
// parameters are the auth-params according to RFC 2617
Parameters map[string]string
}
type byScheme []Challenge
func (bs byScheme) Len() int { return len(bs) }
func (bs byScheme) Swap(i, j int) { bs[i], bs[j] = bs[j], bs[i] }
// Sort in priority order: token > digest > basic
func (bs byScheme) Less(i, j int) bool { return bs[i].Scheme > bs[j].Scheme }
// Octet types from RFC 2616.
type octetType byte
var octetTypes [256]octetType
const (
isToken octetType = 1 << iota
isSpace
)
func init() {
// OCTET =
// CHAR =
// CTL =
// CR =
// LF =
// SP =
// HT =
// <"> =
// CRLF = CR LF
// LWS = [CRLF] 1*( SP | HT )
// TEXT =
// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
// | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
// token = 1*
// qdtext = >
for c := 0; c < 256; c++ {
var t octetType
isCtl := c <= 31 || c == 127
isChar := 0 <= c && c <= 127
isSeparator := strings.ContainsRune(" \t\"(),/:;<=>?@[]\\{}", rune(c))
if strings.ContainsRune(" \t\r\n", rune(c)) {
t |= isSpace
}
if isChar && !isCtl && !isSeparator {
t |= isToken
}
octetTypes[c] = t
}
}
// ParseAuthHeader parses challenges from WWW-Authenticate header
func ParseAuthHeader(header http.Header) []Challenge {
challenges := []Challenge{}
for _, h := range header[http.CanonicalHeaderKey("WWW-Authenticate")] {
v, p := parseValueAndParams(h)
var s AuthenticationScheme
switch v {
case "basic":
s = BasicAuth
case "digest":
s = DigestAuth
case "bearer":
s = BearerAuth
default:
continue
}
challenges = append(challenges, Challenge{Scheme: s, Parameters: p})
}
sort.Stable(byScheme(challenges))
return challenges
}
func parseValueAndParams(header string) (value string, params map[string]string) {
params = make(map[string]string)
value, s := expectToken(header)
if value == "" {
return
}
value = strings.ToLower(value)
for {
var pkey string
pkey, s = expectToken(skipSpace(s))
if pkey == "" {
return
}
if !strings.HasPrefix(s, "=") {
return
}
var pvalue string
pvalue, s = expectTokenOrQuoted(s[1:])
pkey = strings.ToLower(pkey)
params[pkey] = pvalue
s = skipSpace(s)
if !strings.HasPrefix(s, ",") {
return
}
s = s[1:]
}
}
func skipSpace(s string) (rest string) {
i := 0
for ; i < len(s); i++ {
if octetTypes[s[i]]&isSpace == 0 {
break
}
}
return s[i:]
}
func expectToken(s string) (token, rest string) {
i := 0
for ; i < len(s); i++ {
if octetTypes[s[i]]&isToken == 0 {
break
}
}
return s[:i], s[i:]
}
func expectTokenOrQuoted(s string) (value string, rest string) {
if !strings.HasPrefix(s, "\"") {
return expectToken(s)
}
s = s[1:]
for i := 0; i < len(s); i++ {
switch s[i] {
case '"':
return s[:i], s[i+1:]
case '\\':
p := make([]byte, len(s)-1)
j := copy(p, s[:i])
escape := true
for i = i + 1; i < len(s); i++ {
b := s[i]
switch {
case escape:
escape = false
p[j] = b
j++
case b == '\\':
escape = true
case b == '"':
return string(p[:j]), s[i+1:]
default:
p[j] = b
j++
}
}
return "", ""
}
}
return "", ""
}
================================================
FILE: pkg/remote/remotes/docker/auth/parse_test.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
"fmt"
"net/http"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseAuthHeaderBearer(t *testing.T) {
headerTemplate := `Bearer realm="%s",service="%s",scope="%s"`
for _, tc := range []struct {
name string
realm string
service string
scope string
}{
{
name: "SingleScope",
realm: "https://auth.docker.io/token",
service: "registry.docker.io",
scope: "repository:foo/bar:pull,push",
},
{
name: "MultipleScopes",
realm: "https://auth.docker.io/token",
service: "registry.docker.io",
scope: "repository:foo/bar:pull,push repository:foo/baz:pull repository:foo/foo:push",
},
} {
t.Run(tc.name, func(t *testing.T) {
expected := []Challenge{
{
Scheme: BearerAuth,
Parameters: map[string]string{
"realm": tc.realm,
"service": tc.service,
"scope": tc.scope,
},
},
}
hdr := http.Header{
http.CanonicalHeaderKey("WWW-Authenticate"): []string{fmt.Sprintf(
headerTemplate, tc.realm, tc.service, tc.scope,
)},
}
actual := ParseAuthHeader(hdr)
if !reflect.DeepEqual(expected, actual) {
t.Fatalf("expected %v, but got %v", expected, actual)
}
})
}
}
func TestParseAuthHeader(t *testing.T) {
v := `Bearer realm="https://auth.example.io/token",empty="",service="registry.example.io",scope="repository:library/hello-world:pull,push"`
h := http.Header{http.CanonicalHeaderKey("WWW-Authenticate"): []string{v}}
challenge := ParseAuthHeader(h)
actual, ok := challenge[0].Parameters["empty"]
assert.True(t, ok)
assert.Equal(t, "", actual)
actual, ok = challenge[0].Parameters["service"]
assert.True(t, ok)
assert.Equal(t, "registry.example.io", actual)
}
func FuzzParseAuthHeader(f *testing.F) {
f.Add(`Bearer realm="https://example.com/token",service="example.com",scope="repository:foo/bar:pull,push"`)
f.Fuzz(func(t *testing.T, v string) {
h := http.Header{http.CanonicalHeaderKey("WWW-Authenticate"): []string{v}}
_ = ParseAuthHeader(h)
})
}
================================================
FILE: pkg/remote/remotes/docker/authorizer.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package docker
import (
"context"
"encoding/base64"
"errors"
"fmt"
"net/http"
"strings"
"sync"
"github.com/containerd/errdefs"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/remote/remotes/docker/auth"
remoteerrors "github.com/containerd/nydus-snapshotter/pkg/remote/remotes/errors"
)
type dockerAuthorizer struct {
credentials func(string) (string, string, error)
client *http.Client
header http.Header
mu sync.RWMutex
// indexed by host name
handlers map[string]*authHandler
onFetchRefreshToken OnFetchRefreshToken
}
type authorizerConfig struct {
credentials func(string) (string, string, error)
client *http.Client
header http.Header
onFetchRefreshToken OnFetchRefreshToken
}
// AuthorizerOpt configures an authorizer
type AuthorizerOpt func(*authorizerConfig)
// WithAuthClient provides the HTTP client for the authorizer
func WithAuthClient(client *http.Client) AuthorizerOpt {
return func(opt *authorizerConfig) {
opt.client = client
}
}
// WithAuthCreds provides a credential function to the authorizer
func WithAuthCreds(creds func(string) (string, string, error)) AuthorizerOpt {
return func(opt *authorizerConfig) {
opt.credentials = creds
}
}
// WithAuthHeader provides HTTP headers for authorization
func WithAuthHeader(hdr http.Header) AuthorizerOpt {
return func(opt *authorizerConfig) {
opt.header = hdr
}
}
// OnFetchRefreshToken is called on fetching request token.
type OnFetchRefreshToken func(ctx context.Context, refreshToken string, req *http.Request)
// WithFetchRefreshToken enables fetching "refresh token" (aka "identity token", "offline token").
func WithFetchRefreshToken(f OnFetchRefreshToken) AuthorizerOpt {
return func(opt *authorizerConfig) {
opt.onFetchRefreshToken = f
}
}
// NewDockerAuthorizer creates an authorizer using Docker's registry
// authentication spec.
// See https://docs.docker.com/registry/spec/auth/
func NewDockerAuthorizer(opts ...AuthorizerOpt) Authorizer {
var ao authorizerConfig
for _, opt := range opts {
opt(&ao)
}
if ao.client == nil {
ao.client = http.DefaultClient
}
return &dockerAuthorizer{
credentials: ao.credentials,
client: ao.client,
header: ao.header,
handlers: make(map[string]*authHandler),
onFetchRefreshToken: ao.onFetchRefreshToken,
}
}
// Authorize handles auth request.
func (a *dockerAuthorizer) Authorize(ctx context.Context, req *http.Request) error {
// skip if there is no auth handler
ah := a.getAuthHandler(req.URL.Host)
if ah == nil {
return nil
}
auth, refreshToken, err := ah.authorize(ctx)
if err != nil {
return err
}
req.Header.Set("Authorization", auth)
if refreshToken != "" {
a.mu.RLock()
onFetchRefreshToken := a.onFetchRefreshToken
a.mu.RUnlock()
if onFetchRefreshToken != nil {
onFetchRefreshToken(ctx, refreshToken, req)
}
}
return nil
}
func (a *dockerAuthorizer) getAuthHandler(host string) *authHandler {
a.mu.Lock()
defer a.mu.Unlock()
return a.handlers[host]
}
func (a *dockerAuthorizer) AddResponses(ctx context.Context, responses []*http.Response) error {
last := responses[len(responses)-1]
host := last.Request.URL.Host
a.mu.Lock()
defer a.mu.Unlock()
for _, c := range auth.ParseAuthHeader(last.Header) {
if c.Scheme == auth.BearerAuth {
if err := invalidAuthorization(c, responses); err != nil {
delete(a.handlers, host)
return err
}
// reuse existing handler
//
// assume that one registry will return the common
// challenge information, including realm and service.
// and the resource scope is only different part
// which can be provided by each request.
if _, ok := a.handlers[host]; ok {
return nil
}
var username, secret string
if a.credentials != nil {
var err error
username, secret, err = a.credentials(host)
if err != nil {
return err
}
}
common, err := auth.GenerateTokenOptions(ctx, host, username, secret, c)
if err != nil {
return err
}
common.FetchRefreshToken = a.onFetchRefreshToken != nil
a.handlers[host] = newAuthHandler(a.client, a.header, c.Scheme, common)
return nil
} else if c.Scheme == auth.BasicAuth && a.credentials != nil {
username, secret, err := a.credentials(host)
if err != nil {
return err
}
if username != "" && secret != "" {
common := auth.TokenOptions{
Username: username,
Secret: secret,
}
a.handlers[host] = newAuthHandler(a.client, a.header, c.Scheme, common)
return nil
}
}
}
return fmt.Errorf("failed to find supported auth scheme: %w", errdefs.ErrNotImplemented)
}
// authResult is used to control limit rate.
type authResult struct {
sync.WaitGroup
token string
refreshToken string
err error
}
// authHandler is used to handle auth request per registry server.
type authHandler struct {
sync.Mutex
header http.Header
client *http.Client
// only support basic and bearer schemes
scheme auth.AuthenticationScheme
// common contains common challenge answer
common auth.TokenOptions
// scopedTokens caches token indexed by scopes, which used in
// bearer auth case
scopedTokens map[string]*authResult
}
func newAuthHandler(client *http.Client, hdr http.Header, scheme auth.AuthenticationScheme, opts auth.TokenOptions) *authHandler {
return &authHandler{
header: hdr,
client: client,
scheme: scheme,
common: opts,
scopedTokens: map[string]*authResult{},
}
}
func (ah *authHandler) authorize(ctx context.Context) (string, string, error) {
switch ah.scheme {
case auth.BasicAuth:
return ah.doBasicAuth(ctx)
case auth.BearerAuth:
return ah.doBearerAuth(ctx)
default:
return "", "", fmt.Errorf("failed to find supported auth scheme: %s: %w", string(ah.scheme), errdefs.ErrNotImplemented)
}
}
func (ah *authHandler) doBasicAuth(ctx context.Context) (string, string, error) {
username, secret := ah.common.Username, ah.common.Secret
if username == "" || secret == "" {
return "", "", fmt.Errorf("failed to handle basic auth because missing username or secret")
}
auth := base64.StdEncoding.EncodeToString([]byte(username + ":" + secret))
return fmt.Sprintf("Basic %s", auth), "", nil
}
func (ah *authHandler) doBearerAuth(ctx context.Context) (token, refreshToken string, err error) {
// copy common tokenOptions
to := ah.common
to.Scopes = GetTokenScopes(ctx, to.Scopes)
// Docs: https://docs.docker.com/registry/spec/auth/scope
scoped := strings.Join(to.Scopes, " ")
ah.Lock()
if r, exist := ah.scopedTokens[scoped]; exist {
ah.Unlock()
r.Wait()
return r.token, r.refreshToken, r.err
}
// only one fetch token job
r := new(authResult)
r.Add(1)
ah.scopedTokens[scoped] = r
ah.Unlock()
defer func() {
token = fmt.Sprintf("Bearer %s", token)
r.token, r.refreshToken, r.err = token, refreshToken, err
r.Done()
}()
// fetch token for the resource scope
if to.Secret != "" {
defer func() {
if err != nil {
err = fmt.Errorf("failed to fetch oauth token: %w", err)
}
}()
// credential information is provided, use oauth POST endpoint
// TODO: Allow setting client_id
resp, err := auth.FetchTokenWithOAuth(ctx, ah.client, ah.header, "containerd-client", to)
if err != nil {
var errStatus remoteerrors.ErrUnexpectedStatus
if errors.As(err, &errStatus) {
// Registries without support for POST may return 404 for POST /v2/token.
// As of September 2017, GCR is known to return 404.
// As of February 2018, JFrog Artifactory is known to return 401.
// As of January 2022, ACR is known to return 400.
if (errStatus.StatusCode == 405 && to.Username != "") || errStatus.StatusCode == 404 || errStatus.StatusCode == 401 || errStatus.StatusCode == 400 {
resp, err := auth.FetchToken(ctx, ah.client, ah.header, to)
if err != nil {
return "", "", err
}
return resp.Token, resp.RefreshToken, nil
}
log.G(ctx).WithFields(log.Fields{
"status": errStatus.Status,
"body": string(errStatus.Body),
}).Debugf("token request failed")
}
return "", "", err
}
return resp.AccessToken, resp.RefreshToken, nil
}
// do request anonymously
resp, err := auth.FetchToken(ctx, ah.client, ah.header, to)
if err != nil {
return "", "", fmt.Errorf("failed to fetch anonymous token: %w", err)
}
return resp.Token, resp.RefreshToken, nil
}
func invalidAuthorization(c auth.Challenge, responses []*http.Response) error {
errStr := c.Parameters["error"]
if errStr == "" {
return nil
}
n := len(responses)
if n == 1 || (n > 1 && !sameRequest(responses[n-2].Request, responses[n-1].Request)) {
return nil
}
return fmt.Errorf("server message: %s: %w", errStr, ErrInvalidAuthorization)
}
func sameRequest(r1, r2 *http.Request) bool {
if r1.Method != r2.Method {
return false
}
if *r1.URL != *r2.URL {
return false
}
return true
}
================================================
FILE: pkg/remote/remotes/docker/config/config_unix.go
================================================
//go:build !windows
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"crypto/x509"
"path/filepath"
)
func hostPaths(root, host string) (hosts []string) {
ch := hostDirectory(host)
if ch != host {
hosts = append(hosts, filepath.Join(root, ch))
}
hosts = append(hosts,
filepath.Join(root, host),
filepath.Join(root, "_default"),
)
return
}
func rootSystemPool() (*x509.CertPool, error) {
return x509.SystemCertPool()
}
================================================
FILE: pkg/remote/remotes/docker/config/config_windows.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"crypto/x509"
"path/filepath"
"strings"
)
func hostPaths(root, host string) (hosts []string) {
ch := hostDirectory(host)
if ch != host {
hosts = append(hosts, filepath.Join(root, strings.Replace(ch, ":", "", -1)))
}
hosts = append(hosts,
filepath.Join(root, strings.Replace(host, ":", "", -1)),
filepath.Join(root, "_default"),
)
return
}
func rootSystemPool() (*x509.CertPool, error) {
return x509.NewCertPool(), nil
}
================================================
FILE: pkg/remote/remotes/docker/config/docker_fuzzer_internal.go
================================================
//go:build gofuzz
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"os"
fuzz "github.com/AdaLogics/go-fuzz-headers"
)
func FuzzParseHostsFile(data []byte) int {
f := fuzz.NewConsumer(data)
dir, err := os.MkdirTemp("", "fuzz-")
if err != nil {
return 0
}
err = f.CreateFiles(dir)
if err != nil {
return 0
}
defer os.RemoveAll(dir)
b, err := f.GetBytes()
if err != nil {
return 0
}
_, _ = parseHostsFile(dir, b)
return 1
}
================================================
FILE: pkg/remote/remotes/docker/config/hosts.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package config contains utilities for helping configure the Docker resolver
package config
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"sort"
"strings"
"time"
"github.com/containerd/errdefs"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/remote/remotes/docker"
"github.com/pelletier/go-toml"
)
// UpdateClientFunc is a function that lets you to amend http Client behavior used by registry clients.
type UpdateClientFunc func(client *http.Client) error
type hostConfig struct {
scheme string
host string
path string
capabilities docker.HostCapabilities
caCerts []string
clientPairs [][2]string
skipVerify *bool
header http.Header
// TODO: Add credential configuration (domain alias, username)
}
// HostOptions is used to configure registry hosts
type HostOptions struct {
HostDir func(string) (string, error)
Credentials func(host string) (string, string, error)
DefaultTLS *tls.Config
DefaultScheme string
// UpdateClient will be called after creating http.Client object, so clients can provide extra configuration
UpdateClient UpdateClientFunc
AuthorizerOpts []docker.AuthorizerOpt
}
// ConfigureHosts creates a registry hosts function from the provided
// host creation options. The host directory can read hosts.toml or
// certificate files laid out in the Docker specific layout.
// If a `HostDir` function is not required, defaults are used.
func ConfigureHosts(ctx context.Context, options HostOptions) docker.RegistryHosts {
return func(host string) ([]docker.RegistryHost, error) {
var hosts []hostConfig
if options.HostDir != nil {
dir, err := options.HostDir(host)
if err != nil && !errdefs.IsNotFound(err) {
return nil, err
}
if dir != "" {
log.G(ctx).WithField("dir", dir).Debug("loading host directory")
hosts, err = loadHostDir(ctx, dir)
if err != nil {
return nil, err
}
}
}
// If hosts was not set, add a default host
// NOTE: Check nil here and not empty, the host may be
// intentionally configured to not have any endpoints
if hosts == nil {
hosts = make([]hostConfig, 1)
}
if len(hosts) > 0 && hosts[len(hosts)-1].host == "" {
if host == "docker.io" {
hosts[len(hosts)-1].scheme = "https"
hosts[len(hosts)-1].host = "registry-1.docker.io"
} else if docker.IsLocalhost(host) {
hosts[len(hosts)-1].host = host
if options.DefaultScheme == "" || options.DefaultScheme == "http" {
hosts[len(hosts)-1].scheme = "http"
// Skipping TLS verification for localhost
var skipVerify = true
hosts[len(hosts)-1].skipVerify = &skipVerify
} else {
hosts[len(hosts)-1].scheme = options.DefaultScheme
}
} else {
hosts[len(hosts)-1].host = host
if options.DefaultScheme != "" {
hosts[len(hosts)-1].scheme = options.DefaultScheme
} else {
hosts[len(hosts)-1].scheme = "https"
}
}
hosts[len(hosts)-1].path = "/v2"
hosts[len(hosts)-1].capabilities = docker.HostCapabilityPull | docker.HostCapabilityResolve | docker.HostCapabilityPush
}
var defaultTLSConfig *tls.Config
if options.DefaultTLS != nil {
defaultTLSConfig = options.DefaultTLS
} else {
defaultTLSConfig = &tls.Config{}
}
defaultTransport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
FallbackDelay: 300 * time.Millisecond,
}).DialContext,
MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: defaultTLSConfig,
ExpectContinueTimeout: 5 * time.Second,
}
client := &http.Client{
Transport: defaultTransport,
}
if options.UpdateClient != nil {
if err := options.UpdateClient(client); err != nil {
return nil, err
}
}
authOpts := []docker.AuthorizerOpt{docker.WithAuthClient(client)}
if options.Credentials != nil {
authOpts = append(authOpts, docker.WithAuthCreds(options.Credentials))
}
authOpts = append(authOpts, options.AuthorizerOpts...)
authorizer := docker.NewDockerAuthorizer(authOpts...)
rhosts := make([]docker.RegistryHost, len(hosts))
for i, host := range hosts {
rhosts[i].Scheme = host.scheme
rhosts[i].Host = host.host
rhosts[i].Path = host.path
rhosts[i].Capabilities = host.capabilities
rhosts[i].Header = host.header
if host.caCerts != nil || host.clientPairs != nil || host.skipVerify != nil {
tr := defaultTransport.Clone()
tlsConfig := tr.TLSClientConfig
if host.skipVerify != nil {
tlsConfig.InsecureSkipVerify = *host.skipVerify
}
if host.caCerts != nil {
if tlsConfig.RootCAs == nil {
rootPool, err := rootSystemPool()
if err != nil {
return nil, fmt.Errorf("unable to initialize cert pool: %w", err)
}
tlsConfig.RootCAs = rootPool
}
for _, f := range host.caCerts {
data, err := os.ReadFile(f)
if err != nil {
return nil, fmt.Errorf("unable to read CA cert %q: %w", f, err)
}
if !tlsConfig.RootCAs.AppendCertsFromPEM(data) {
return nil, fmt.Errorf("unable to load CA cert %q", f)
}
}
}
if host.clientPairs != nil {
for _, pair := range host.clientPairs {
certPEMBlock, err := os.ReadFile(pair[0])
if err != nil {
return nil, fmt.Errorf("unable to read CERT file %q: %w", pair[0], err)
}
var keyPEMBlock []byte
if pair[1] != "" {
keyPEMBlock, err = os.ReadFile(pair[1])
if err != nil {
return nil, fmt.Errorf("unable to read CERT file %q: %w", pair[1], err)
}
} else {
// Load key block from same PEM file
keyPEMBlock = certPEMBlock
}
cert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
if err != nil {
return nil, fmt.Errorf("failed to load X509 key pair: %w", err)
}
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
}
}
c := *client
c.Transport = tr
if options.UpdateClient != nil {
if err := options.UpdateClient(&c); err != nil {
return nil, err
}
}
rhosts[i].Client = &c
rhosts[i].Authorizer = docker.NewDockerAuthorizer(append(authOpts, docker.WithAuthClient(&c))...)
} else {
rhosts[i].Client = client
rhosts[i].Authorizer = authorizer
}
}
return rhosts, nil
}
}
// HostDirFromRoot returns a function which finds a host directory
// based at the given root.
func HostDirFromRoot(root string) func(string) (string, error) {
return func(host string) (string, error) {
for _, p := range hostPaths(root, host) {
if _, err := os.Stat(p); err == nil {
return p, nil
} else if !os.IsNotExist(err) {
return "", err
}
}
return "", errdefs.ErrNotFound
}
}
// hostDirectory converts ":port" to "_port_" in directory names
func hostDirectory(host string) string {
idx := strings.LastIndex(host, ":")
if idx > 0 {
return host[:idx] + "_" + host[idx+1:] + "_"
}
return host
}
func loadHostDir(ctx context.Context, hostsDir string) ([]hostConfig, error) {
b, err := os.ReadFile(filepath.Join(hostsDir, "hosts.toml"))
if err != nil && !os.IsNotExist(err) {
return nil, err
}
if len(b) == 0 {
// If hosts.toml does not exist, fallback to checking for
// certificate files based on Docker's certificate file
// pattern (".crt", ".cert", ".key" files)
return loadCertFiles(ctx, hostsDir)
}
hosts, err := parseHostsFile(hostsDir, b)
if err != nil {
log.G(ctx).WithError(err).Error("failed to decode hosts.toml")
// Fallback to checking certificate files
return loadCertFiles(ctx, hostsDir)
}
return hosts, nil
}
type hostFileConfig struct {
// Capabilities determine what operations a host is
// capable of performing. Allowed values
// - pull
// - resolve
// - push
Capabilities []string `toml:"capabilities"`
// CACert are the public key certificates for TLS
// Accepted types
// - string - Single file with certificate(s)
// - []string - Multiple files with certificates
CACert interface{} `toml:"ca"`
// Client keypair(s) for TLS with client authentication
// Accepted types
// - string - Single file with public and private keys
// - []string - Multiple files with public and private keys
// - [][2]string - Multiple keypairs with public and private keys in separate files
Client interface{} `toml:"client"`
// SkipVerify skips verification of the server's certificate chain
// and host name. This should only be used for testing or in
// combination with other methods of verifying connections.
SkipVerify *bool `toml:"skip_verify"`
// Header are additional header files to send to the server
Header map[string]interface{} `toml:"header"`
// OverridePath indicates the API root endpoint is defined in the URL
// path rather than by the API specification.
// This may be used with non-compliant OCI registries to override the
// API root endpoint.
OverridePath bool `toml:"override_path"`
// TODO: Credentials: helper? name? username? alternate domain? token?
}
func parseHostsFile(baseDir string, b []byte) ([]hostConfig, error) {
tree, err := toml.LoadBytes(b)
if err != nil {
return nil, fmt.Errorf("failed to parse TOML: %w", err)
}
// HACK: we want to keep toml parsing structures private in this package, however go-toml ignores private embedded types.
// so we remap it to a public type within the func body, so technically it's public, but not possible to import elsewhere.
//nolint:unused
type HostFileConfig = hostFileConfig
c := struct {
HostFileConfig
// Server specifies the default server. When `host` is
// also specified, those hosts are tried first.
Server string `toml:"server"`
// HostConfigs store the per-host configuration
HostConfigs map[string]hostFileConfig `toml:"host"`
}{}
orderedHosts, err := getSortedHosts(tree)
if err != nil {
return nil, err
}
var (
hosts []hostConfig
)
if err := tree.Unmarshal(&c); err != nil {
return nil, err
}
// Parse hosts array
for _, host := range orderedHosts {
config := c.HostConfigs[host]
parsed, err := parseHostConfig(host, baseDir, config)
if err != nil {
return nil, err
}
hosts = append(hosts, parsed)
}
// Parse root host config and append it as the last element
parsed, err := parseHostConfig(c.Server, baseDir, c.HostFileConfig)
if err != nil {
return nil, err
}
hosts = append(hosts, parsed)
return hosts, nil
}
func parseHostConfig(server string, baseDir string, config hostFileConfig) (hostConfig, error) {
var (
result = hostConfig{}
err error
)
if server != "" {
if !strings.HasPrefix(server, "http") {
server = "https://" + server
}
u, err := url.Parse(server)
if err != nil {
return hostConfig{}, fmt.Errorf("unable to parse server %v: %w", server, err)
}
result.scheme = u.Scheme
result.host = u.Host
if len(u.Path) > 0 {
u.Path = path.Clean(u.Path)
if !strings.HasSuffix(u.Path, "/v2") && !config.OverridePath {
u.Path = u.Path + "/v2"
}
} else if !config.OverridePath {
u.Path = "/v2"
}
result.path = u.Path
}
result.skipVerify = config.SkipVerify
if len(config.Capabilities) > 0 {
for _, c := range config.Capabilities {
switch strings.ToLower(c) {
case "pull":
result.capabilities |= docker.HostCapabilityPull
case "resolve":
result.capabilities |= docker.HostCapabilityResolve
case "push":
result.capabilities |= docker.HostCapabilityPush
default:
return hostConfig{}, fmt.Errorf("unknown capability %v", c)
}
}
} else {
result.capabilities = docker.HostCapabilityPull | docker.HostCapabilityResolve | docker.HostCapabilityPush
}
if config.CACert != nil {
switch cert := config.CACert.(type) {
case string:
result.caCerts = []string{makeAbsPath(cert, baseDir)}
case []interface{}:
result.caCerts, err = makeStringSlice(cert, func(p string) string {
return makeAbsPath(p, baseDir)
})
if err != nil {
return hostConfig{}, err
}
default:
return hostConfig{}, fmt.Errorf("invalid type %v for \"ca\"", cert)
}
}
if config.Client != nil {
switch client := config.Client.(type) {
case string:
result.clientPairs = [][2]string{{makeAbsPath(client, baseDir), ""}}
case []interface{}:
// []string or [][2]string
for _, pairs := range client {
switch p := pairs.(type) {
case string:
result.clientPairs = append(result.clientPairs, [2]string{makeAbsPath(p, baseDir), ""})
case []interface{}:
slice, err := makeStringSlice(p, func(s string) string {
return makeAbsPath(s, baseDir)
})
if err != nil {
return hostConfig{}, err
}
if len(slice) != 2 {
return hostConfig{}, fmt.Errorf("invalid pair %v for \"client\"", p)
}
var pair [2]string
copy(pair[:], slice)
result.clientPairs = append(result.clientPairs, pair)
default:
return hostConfig{}, fmt.Errorf("invalid type %T for \"client\"", p)
}
}
default:
return hostConfig{}, fmt.Errorf("invalid type %v for \"client\"", client)
}
}
if config.Header != nil {
header := http.Header{}
for key, ty := range config.Header {
switch value := ty.(type) {
case string:
header[key] = []string{value}
case []interface{}:
header[key], err = makeStringSlice(value, nil)
if err != nil {
return hostConfig{}, err
}
default:
return hostConfig{}, fmt.Errorf("invalid type %v for header %q", ty, key)
}
}
result.header = header
}
return result, nil
}
// getSortedHosts returns the list of hosts as they defined in the file.
func getSortedHosts(root *toml.Tree) ([]string, error) {
iter, ok := root.Get("host").(*toml.Tree)
if !ok {
return nil, errors.New("invalid `host` tree")
}
list := append([]string{}, iter.Keys()...)
// go-toml stores TOML sections in the map object, so no order guaranteed.
// We retrieve line number for each key and sort the keys by position.
sort.Slice(list, func(i, j int) bool {
h1 := iter.GetPath([]string{list[i]}).(*toml.Tree)
h2 := iter.GetPath([]string{list[j]}).(*toml.Tree)
return h1.Position().Line < h2.Position().Line
})
return list, nil
}
// makeStringSlice is a helper func to convert from []interface{} to []string.
// Additionally an optional cb func may be passed to perform string mapping.
func makeStringSlice(slice []interface{}, cb func(string) string) ([]string, error) {
out := make([]string, len(slice))
for i, value := range slice {
str, ok := value.(string)
if !ok {
return nil, fmt.Errorf("unable to cast %v to string", value)
}
if cb != nil {
out[i] = cb(str)
} else {
out[i] = str
}
}
return out, nil
}
func makeAbsPath(p string, base string) string {
if filepath.IsAbs(p) {
return p
}
return filepath.Join(base, p)
}
// loadCertsDir loads certs from certsDir like "/etc/docker/certs.d" .
// Compatible with Docker file layout
// - files ending with ".crt" are treated as CA certificate files
// - files ending with ".cert" are treated as client certificates, and
// files with the same name but ending with ".key" are treated as the
// corresponding private key.
// NOTE: If a ".key" file is missing, this function will just return
// the ".cert", which may contain the private key. If the ".cert" file
// does not contain the private key, the caller should detect and error.
func loadCertFiles(ctx context.Context, certsDir string) ([]hostConfig, error) {
fs, err := os.ReadDir(certsDir)
if err != nil && !os.IsNotExist(err) {
return nil, err
}
hosts := make([]hostConfig, 1)
for _, f := range fs {
if f.IsDir() {
continue
}
if strings.HasSuffix(f.Name(), ".crt") {
hosts[0].caCerts = append(hosts[0].caCerts, filepath.Join(certsDir, f.Name()))
}
if strings.HasSuffix(f.Name(), ".cert") {
var pair [2]string
certFile := f.Name()
pair[0] = filepath.Join(certsDir, certFile)
// Check if key also exists
keyFile := filepath.Join(certsDir, certFile[:len(certFile)-5]+".key")
if _, err := os.Stat(keyFile); err == nil {
pair[1] = keyFile
} else if !os.IsNotExist(err) {
return nil, err
}
hosts[0].clientPairs = append(hosts[0].clientPairs, pair)
}
}
return hosts, nil
}
================================================
FILE: pkg/remote/remotes/docker/config/hosts_test.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"bytes"
"context"
"fmt"
"net/http"
"os"
"path/filepath"
"testing"
"github.com/containerd/log/logtest"
"github.com/containerd/nydus-snapshotter/pkg/remote/remotes/docker"
)
const allCaps = docker.HostCapabilityPull | docker.HostCapabilityResolve | docker.HostCapabilityPush
func TestDefaultHosts(t *testing.T) {
ctx := logtest.WithT(context.Background(), t)
resolve := ConfigureHosts(ctx, HostOptions{})
for _, tc := range []struct {
host string
expected []docker.RegistryHost
}{
{
host: "docker.io",
expected: []docker.RegistryHost{
{
Scheme: "https",
Host: "registry-1.docker.io",
Path: "/v2",
Capabilities: allCaps,
},
},
},
} {
hosts, err := resolve(tc.host)
if err != nil {
t.Errorf("[%s] resolve failed: %v", tc.host, err)
continue
}
if len(hosts) != len(tc.expected) {
t.Errorf("[%s] unexpected number of hosts %d, expected %d", tc.host, len(hosts), len(tc.expected))
continue
}
for j := range hosts {
if !compareRegistryHost(hosts[j], tc.expected[j]) {
t.Errorf("[%s] [%d] unexpected host %v, expected %v", tc.host, j, hosts[j], tc.expected[j])
break
}
}
}
}
func TestParseHostFile(t *testing.T) {
const testtoml = `
server = "https://test-default.registry"
ca = "/etc/path/default"
[header]
x-custom-1 = "custom header"
[host."https://mirror.registry"]
capabilities = ["pull"]
ca = "/etc/certs/mirror.pem"
skip_verify = false
[host."https://mirror.registry".header]
x-custom-2 = ["value1", "value2"]
[host."https://mirror-bak.registry/us"]
capabilities = ["pull"]
skip_verify = true
[host."http://mirror.registry"]
capabilities = ["pull"]
[host."https://test-1.registry"]
capabilities = ["pull", "resolve", "push"]
ca = ["/etc/certs/test-1-ca.pem", "/etc/certs/special.pem"]
client = [["/etc/certs/client.cert", "/etc/certs/client.key"],["/etc/certs/client.pem", ""]]
[host."https://test-2.registry"]
client = "/etc/certs/client.pem"
[host."https://test-3.registry"]
client = ["/etc/certs/client-1.pem", "/etc/certs/client-2.pem"]
[host."https://noncompliantmirror.registry/v2/namespaceprefix"]
capabilities = ["pull"]
override_path = true
[host."https://noprefixnoncompliant.registry"]
override_path = true
`
var tb, fb = true, false
expected := []hostConfig{
{
scheme: "https",
host: "mirror.registry",
path: "/v2",
capabilities: docker.HostCapabilityPull,
caCerts: []string{filepath.FromSlash("/etc/certs/mirror.pem")},
skipVerify: &fb,
header: http.Header{"x-custom-2": {"value1", "value2"}},
},
{
scheme: "https",
host: "mirror-bak.registry",
path: "/us/v2",
capabilities: docker.HostCapabilityPull,
skipVerify: &tb,
},
{
scheme: "http",
host: "mirror.registry",
path: "/v2",
capabilities: docker.HostCapabilityPull,
},
{
scheme: "https",
host: "test-1.registry",
path: "/v2",
capabilities: allCaps,
caCerts: []string{filepath.FromSlash("/etc/certs/test-1-ca.pem"), filepath.FromSlash("/etc/certs/special.pem")},
clientPairs: [][2]string{
{filepath.FromSlash("/etc/certs/client.cert"), filepath.FromSlash("/etc/certs/client.key")},
{filepath.FromSlash("/etc/certs/client.pem"), ""},
},
},
{
scheme: "https",
host: "test-2.registry",
path: "/v2",
capabilities: allCaps,
clientPairs: [][2]string{
{filepath.FromSlash("/etc/certs/client.pem")},
},
},
{
scheme: "https",
host: "test-3.registry",
path: "/v2",
capabilities: allCaps,
clientPairs: [][2]string{
{filepath.FromSlash("/etc/certs/client-1.pem")},
{filepath.FromSlash("/etc/certs/client-2.pem")},
},
},
{
scheme: "https",
host: "noncompliantmirror.registry",
path: "/v2/namespaceprefix",
capabilities: docker.HostCapabilityPull,
},
{
scheme: "https",
host: "noprefixnoncompliant.registry",
capabilities: allCaps,
},
{
scheme: "https",
host: "test-default.registry",
path: "/v2",
capabilities: allCaps,
caCerts: []string{filepath.FromSlash("/etc/path/default")},
header: http.Header{"x-custom-1": {"custom header"}},
},
}
hosts, err := parseHostsFile("", []byte(testtoml))
if err != nil {
t.Fatal(err)
}
defer func() {
if t.Failed() {
t.Log("HostConfigs...\nActual:\n" + printHostConfig(hosts) + "Expected:\n" + printHostConfig(expected))
}
}()
if len(hosts) != len(expected) {
t.Fatalf("Unexpected number of hosts %d, expected %d", len(hosts), len(expected))
}
for i := range hosts {
if !compareHostConfig(hosts[i], expected[i]) {
t.Fatalf("Mismatch at host %d", i)
}
}
}
func TestLoadCertFiles(t *testing.T) {
dir := t.TempDir()
type testCase struct {
input hostConfig
}
cases := map[string]testCase{
"crt only": {
input: hostConfig{host: "testing.io", caCerts: []string{filepath.Join(dir, "testing.io", "ca.crt")}},
},
"crt and cert pair": {
input: hostConfig{
host: "testing.io",
caCerts: []string{filepath.Join(dir, "testing.io", "ca.crt")},
clientPairs: [][2]string{
{
filepath.Join(dir, "testing.io", "client.cert"),
filepath.Join(dir, "testing.io", "client.key"),
},
},
},
},
"cert pair only": {
input: hostConfig{
host: "testing.io",
clientPairs: [][2]string{
{
filepath.Join(dir, "testing.io", "client.cert"),
filepath.Join(dir, "testing.io", "client.key"),
},
},
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
hostDir := filepath.Join(dir, tc.input.host)
if err := os.MkdirAll(hostDir, 0700); err != nil {
t.Fatal(err)
}
defer os.RemoveAll(hostDir)
for _, f := range tc.input.caCerts {
if err := os.WriteFile(f, testKey, 0600); err != nil {
t.Fatal(err)
}
}
for _, pair := range tc.input.clientPairs {
if err := os.WriteFile(pair[0], testKey, 0600); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(pair[1], testKey, 0600); err != nil {
t.Fatal(err)
}
}
configs, err := loadHostDir(context.Background(), hostDir)
if err != nil {
t.Fatal(err)
}
if len(configs) != 1 {
t.Fatalf("\nexpected:\n%+v\ngot:\n%+v", tc.input, configs)
}
cfg := configs[0]
cfg.host = tc.input.host
if !compareHostConfig(cfg, tc.input) {
t.Errorf("\nexpected:\n%+v:\n\ngot:\n%+v", tc.input, cfg)
}
})
}
}
func compareRegistryHost(j, k docker.RegistryHost) bool {
if j.Scheme != k.Scheme {
return false
}
if j.Host != k.Host {
return false
}
if j.Path != k.Path {
return false
}
if j.Capabilities != k.Capabilities {
return false
}
// Not comparing TLS configs or authorizations
return true
}
func compareHostConfig(j, k hostConfig) bool {
if j.scheme != k.scheme {
return false
}
if j.host != k.host {
return false
}
if j.path != k.path {
return false
}
if j.capabilities != k.capabilities {
return false
}
if len(j.caCerts) != len(k.caCerts) {
return false
}
for i := range j.caCerts {
if j.caCerts[i] != k.caCerts[i] {
return false
}
}
if len(j.clientPairs) != len(k.clientPairs) {
return false
}
for i := range j.clientPairs {
if j.clientPairs[i][0] != k.clientPairs[i][0] {
return false
}
if j.clientPairs[i][1] != k.clientPairs[i][1] {
return false
}
}
if j.skipVerify != nil && k.skipVerify != nil {
if *j.skipVerify != *k.skipVerify {
return false
}
} else if j.skipVerify != nil || k.skipVerify != nil {
return false
}
if len(j.header) != len(k.header) {
return false
}
for key := range j.header {
if len(j.header[key]) != len(k.header[key]) {
return false
}
for i := range j.header[key] {
if j.header[key][i] != k.header[key][i] {
return false
}
}
}
return true
}
func printHostConfig(hc []hostConfig) string {
b := bytes.NewBuffer(nil)
for i := range hc {
fmt.Fprintf(b, "\t[%d]\tscheme: %q\n", i, hc[i].scheme)
fmt.Fprintf(b, "\t\thost: %q\n", hc[i].host)
fmt.Fprintf(b, "\t\tpath: %q\n", hc[i].path)
fmt.Fprintf(b, "\t\tcaps: %03b\n", hc[i].capabilities)
fmt.Fprintf(b, "\t\tca: %#v\n", hc[i].caCerts)
fmt.Fprintf(b, "\t\tclients: %#v\n", hc[i].clientPairs)
if hc[i].skipVerify == nil {
fmt.Fprintf(b, "\t\tskip-verify: %v\n", hc[i].skipVerify)
} else {
fmt.Fprintf(b, "\t\tskip-verify: %t\n", *hc[i].skipVerify)
}
fmt.Fprintf(b, "\t\theader: %#v\n", hc[i].header)
}
return b.String()
}
var (
testKey = []byte(`-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDa+zvPgFXwra4S
0DzEWRgZHxVTDG1sJsnN/jOaHCNpRyABGVW5kdei9WFWv3dpiELI+guQMjdUL++w
M68bs6cXKW+1nW6u5uWuGwklOwkoKoeHkkn/vHef7ybk+5qdk6AYY0DKQsrBBOvj
f0WAnG+1xi8VIOEBmce0/47MexOiuILVkjokgdmDCOc8ShkT6/EJTCsI1wDew/4G
9IiRzw2xSM0ZATAtEC3HEBRLJGWZQtuKlLCuzJ+erOWUcg2cjnSgR3PmaAXE//5g
SoeqEbtTo1satf9AR4VvreIAI8m0eyo8ABMLTkZovEFcUUHetL63hdqItjCeRfrQ
zK4LMRFbAgMBAAECggEBAJtP6UHo0gtcA8SQMSlJz4+xvhwjClDUyfjyPIMnRe5b
ZdWhtG1jhT+tLhaqwfT1kfidcCobk6aAQU4FukK5jt8cooB7Yo9mcKylvDzNvFbi
ozGCjj113JpwsnNiCG2O0NO7Qa6y5L810GCQWik3yvtvzuD7atsJyN0VDKD3Ahw7
1X8z76grZFlhVMCTAA3vAJ2y2p3sd+TGC/PIhnsvChwxEorGCnMj93mBaUI7zZRY
EZhlk4ZvC9sUvlVUuYC+wAHjasgN9s3AzsOBSx+Xt3NaXQHzhL0mVo/vu/pjjFBs
WBLR1PBoIfveTJPOp+Hrr4cuCK0NuX9sWlWPYLl5A2ECgYEA5fq3n4PhbJ2BuTS5
AVgOmjRpk1eogb6aSY+cx7Mr++ADF9EYXc5tgKoUsDeeiiyK2lv6IKavoTWT1kdd
shiclyEzp2CxG5GtbC/g2XHiBLepgo1fjfev3btCmIeGVBjglOx4F3gEsRygrAID
zcz94m2I+uqLT8hvWnccIqScglkCgYEA88H2ji4Nvx6TmqCLcER0vNDVoxxDfgGb
iohvenD2jmmdTnezTddsgECAI8L0BPNS/0vBCduTjs5BqhKbIfQvuK5CANMUcxuQ
twWH8kPvTYJVgsmWP6sSXSz3PohWC5EA9xACExGtyN6d7sLUCV0SBhjlcgMvGuDM
lP6NjyyWctMCgYBKdfGr+QQsqZaNw48+6ybXMK8aIKCTWYYU2SW21sEf7PizZmTQ
Qnzb0rWeFHQFYsSWTH9gwPdOZ8107GheuG9C02IpCDpvpawTwjC31pKKWnjMpz9P
9OkBDpdSUVbhtahJL4L2fkpumck/x+s5X+y3uiVGsFfovgmnrbbzVH7ECQKBgQCC
MYs7DaYR+obkA/P2FtozL2esIyB5YOpu58iDIWrPTeHTU2PVo8Y0Cj9m2m3zZvNh
oFiOp1T85XV1HVL2o7IJdimSvyshAAwfdTjTUS2zvHVn0bwKbZj1Y1r7b15l9yEI
1OgGv16O9zhrmmweRDOoRgvnBYRXWtJqkjuRyULiOQKBgQC/lSYigV32Eb8Eg1pv
7OcPWv4qV4880lRE0MXuQ4VFa4+pqvdziYFYQD4jDYJ4IX9l//bsobL0j7z0P0Gk
wDFti9bRwRoO1ntqoA8n2pDLlLRGl0dyjB6fHzp27oqtyf1HRlHiow7Gqx5b5JOk
tycYKwA3DuaSyqPe6MthLneq8w==
-----END PRIVATE KEY-----
`)
)
================================================
FILE: pkg/remote/remotes/docker/converter.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package docker
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/containerd/containerd/v2/core/content"
"github.com/containerd/containerd/v2/core/images"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/remote/remotes"
digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// LegacyConfigMediaType should be replaced by OCI image spec.
//
// More detail: docker/distribution#1622
const LegacyConfigMediaType = "application/octet-stream"
// ConvertManifest changes application/octet-stream to schema2 config media type if need.
//
// NOTE:
// 1. original manifest will be deleted by next gc round.
// 2. don't cover manifest list.
func ConvertManifest(ctx context.Context, store content.Store, desc ocispec.Descriptor) (ocispec.Descriptor, error) {
if !(desc.MediaType == images.MediaTypeDockerSchema2Manifest ||
desc.MediaType == ocispec.MediaTypeImageManifest) {
log.G(ctx).Warnf("do nothing for media type: %s", desc.MediaType)
return desc, nil
}
// read manifest data
mb, err := content.ReadBlob(ctx, store, desc)
if err != nil {
return ocispec.Descriptor{}, fmt.Errorf("failed to read index data: %w", err)
}
var manifest ocispec.Manifest
if err := json.Unmarshal(mb, &manifest); err != nil {
return ocispec.Descriptor{}, fmt.Errorf("failed to unmarshal data into manifest: %w", err)
}
// check config media type
if manifest.Config.MediaType != LegacyConfigMediaType {
return desc, nil
}
manifest.Config.MediaType = images.MediaTypeDockerSchema2Config
data, err := json.MarshalIndent(manifest, "", " ")
if err != nil {
return ocispec.Descriptor{}, fmt.Errorf("failed to marshal manifest: %w", err)
}
// update manifest with gc labels
desc.Digest = digest.Canonical.FromBytes(data)
desc.Size = int64(len(data))
labels := map[string]string{}
for i, c := range append([]ocispec.Descriptor{manifest.Config}, manifest.Layers...) {
labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i)] = c.Digest.String()
}
ref := remotes.MakeRefKey(ctx, desc)
if err := content.WriteBlob(ctx, store, ref, bytes.NewReader(data), desc, content.WithLabels(labels)); err != nil {
return ocispec.Descriptor{}, fmt.Errorf("failed to update content: %w", err)
}
return desc, nil
}
================================================
FILE: pkg/remote/remotes/docker/converter_fuzz.go
================================================
//go:build gofuzz
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package docker
import (
"context"
"os"
fuzz "github.com/AdaLogics/go-fuzz-headers"
"github.com/containerd/containerd/v2/plugins/content/local"
"github.com/containerd/log"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
)
func FuzzConvertManifest(data []byte) int {
ctx := context.Background()
// Do not log the message below
// level=warning msg="do nothing for media type: ..."
log.G(ctx).Logger.SetLevel(logrus.PanicLevel)
f := fuzz.NewConsumer(data)
desc := ocispec.Descriptor{}
err := f.GenerateStruct(&desc)
if err != nil {
return 0
}
tmpdir, err := os.MkdirTemp("", "fuzzing-")
if err != nil {
return 0
}
cs, err := local.NewStore(tmpdir)
if err != nil {
return 0
}
_, _ = ConvertManifest(ctx, cs, desc)
return 1
}
================================================
FILE: pkg/remote/remotes/docker/errcode.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package docker
import (
"encoding/json"
"fmt"
"strings"
)
// ErrorCoder is the base interface for ErrorCode and Error allowing
// users of each to just call ErrorCode to get the real ID of each
type ErrorCoder interface {
ErrorCode() ErrorCode
}
// ErrorCode represents the error type. The errors are serialized via strings
// and the integer format may change and should *never* be exported.
type ErrorCode int
var _ error = ErrorCode(0)
// ErrorCode just returns itself
func (ec ErrorCode) ErrorCode() ErrorCode {
return ec
}
// Error returns the ID/Value
func (ec ErrorCode) Error() string {
// NOTE(stevvooe): Cannot use message here since it may have unpopulated args.
return strings.ToLower(strings.Replace(ec.String(), "_", " ", -1))
}
// Descriptor returns the descriptor for the error code.
func (ec ErrorCode) Descriptor() ErrorDescriptor {
d, ok := errorCodeToDescriptors[ec]
if !ok {
return ErrorCodeUnknown.Descriptor()
}
return d
}
// String returns the canonical identifier for this error code.
func (ec ErrorCode) String() string {
return ec.Descriptor().Value
}
// Message returned the human-readable error message for this error code.
func (ec ErrorCode) Message() string {
return ec.Descriptor().Message
}
// MarshalText encodes the receiver into UTF-8-encoded text and returns the
// result.
func (ec ErrorCode) MarshalText() (text []byte, err error) {
return []byte(ec.String()), nil
}
// UnmarshalText decodes the form generated by MarshalText.
func (ec *ErrorCode) UnmarshalText(text []byte) error {
desc, ok := idToDescriptors[string(text)]
if !ok {
desc = ErrorCodeUnknown.Descriptor()
}
*ec = desc.Code
return nil
}
// WithMessage creates a new Error struct based on the passed-in info and
// overrides the Message property.
func (ec ErrorCode) WithMessage(message string) Error {
return Error{
Code: ec,
Message: message,
}
}
// WithDetail creates a new Error struct based on the passed-in info and
// set the Detail property appropriately
func (ec ErrorCode) WithDetail(detail interface{}) Error {
return Error{
Code: ec,
Message: ec.Message(),
}.WithDetail(detail)
}
// WithArgs creates a new Error struct and sets the Args slice
func (ec ErrorCode) WithArgs(args ...interface{}) Error {
return Error{
Code: ec,
Message: ec.Message(),
}.WithArgs(args...)
}
// Error provides a wrapper around ErrorCode with extra Details provided.
type Error struct {
Code ErrorCode `json:"code"`
Message string `json:"message"`
Detail interface{} `json:"detail,omitempty"`
// TODO(duglin): See if we need an "args" property so we can do the
// variable substitution right before showing the message to the user
}
var _ error = Error{}
// ErrorCode returns the ID/Value of this Error
func (e Error) ErrorCode() ErrorCode {
return e.Code
}
// Error returns a human readable representation of the error.
func (e Error) Error() string {
return fmt.Sprintf("%s: %s", e.Code.Error(), e.Message)
}
// WithDetail will return a new Error, based on the current one, but with
// some Detail info added
func (e Error) WithDetail(detail interface{}) Error {
return Error{
Code: e.Code,
Message: e.Message,
Detail: detail,
}
}
// WithArgs uses the passed-in list of interface{} as the substitution
// variables in the Error's Message string, but returns a new Error
func (e Error) WithArgs(args ...interface{}) Error {
return Error{
Code: e.Code,
Message: fmt.Sprintf(e.Code.Message(), args...),
Detail: e.Detail,
}
}
// ErrorDescriptor provides relevant information about a given error code.
type ErrorDescriptor struct {
// Code is the error code that this descriptor describes.
Code ErrorCode
// Value provides a unique, string key, often captilized with
// underscores, to identify the error code. This value is used as the
// keyed value when serializing api errors.
Value string
// Message is a short, human readable description of the error condition
// included in API responses.
Message string
// Description provides a complete account of the errors purpose, suitable
// for use in documentation.
Description string
// HTTPStatusCode provides the http status code that is associated with
// this error condition.
HTTPStatusCode int
}
// ParseErrorCode returns the value by the string error code.
// `ErrorCodeUnknown` will be returned if the error is not known.
func ParseErrorCode(value string) ErrorCode {
ed, ok := idToDescriptors[value]
if ok {
return ed.Code
}
return ErrorCodeUnknown
}
// Errors provides the envelope for multiple errors and a few sugar methods
// for use within the application.
type Errors []error
var _ error = Errors{}
func (errs Errors) Error() string {
switch len(errs) {
case 0:
return ""
case 1:
return errs[0].Error()
default:
msg := "errors:\n"
for _, err := range errs {
msg += err.Error() + "\n"
}
return msg
}
}
// Len returns the current number of errors.
func (errs Errors) Len() int {
return len(errs)
}
// MarshalJSON converts slice of error, ErrorCode or Error into a
// slice of Error - then serializes
func (errs Errors) MarshalJSON() ([]byte, error) {
var tmpErrs struct {
Errors []Error `json:"errors,omitempty"`
}
for _, daErr := range errs {
var err Error
switch daErr := daErr.(type) {
case ErrorCode:
err = daErr.WithDetail(nil)
case Error:
err = daErr
default:
err = ErrorCodeUnknown.WithDetail(daErr)
}
// If the Error struct was setup and they forgot to set the
// Message field (meaning its "") then grab it from the ErrCode
msg := err.Message
if msg == "" {
msg = err.Code.Message()
}
tmpErrs.Errors = append(tmpErrs.Errors, Error{
Code: err.Code,
Message: msg,
Detail: err.Detail,
})
}
return json.Marshal(tmpErrs)
}
// UnmarshalJSON deserializes []Error and then converts it into slice of
// Error or ErrorCode
func (errs *Errors) UnmarshalJSON(data []byte) error {
var tmpErrs struct {
Errors []Error
}
if err := json.Unmarshal(data, &tmpErrs); err != nil {
return err
}
var newErrs Errors
for _, daErr := range tmpErrs.Errors {
// If Message is empty or exactly matches the Code's message string
// then just use the Code, no need for a full Error struct
if daErr.Detail == nil && (daErr.Message == "" || daErr.Message == daErr.Code.Message()) {
// Error's w/o details get converted to ErrorCode
newErrs = append(newErrs, daErr.Code)
} else {
// Error's w/ details are untouched
newErrs = append(newErrs, Error{
Code: daErr.Code,
Message: daErr.Message,
Detail: daErr.Detail,
})
}
}
*errs = newErrs
return nil
}
================================================
FILE: pkg/remote/remotes/docker/errdesc.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package docker
import (
"fmt"
"net/http"
"sort"
"sync"
)
var (
errorCodeToDescriptors = map[ErrorCode]ErrorDescriptor{}
idToDescriptors = map[string]ErrorDescriptor{}
groupToDescriptors = map[string][]ErrorDescriptor{}
)
var (
// ErrorCodeUnknown is a generic error that can be used as a last
// resort if there is no situation-specific error message that can be used
ErrorCodeUnknown = Register("errcode", ErrorDescriptor{
Value: "UNKNOWN",
Message: "unknown error",
Description: `Generic error returned when the error does not have an
API classification.`,
HTTPStatusCode: http.StatusInternalServerError,
})
// ErrorCodeUnsupported is returned when an operation is not supported.
ErrorCodeUnsupported = Register("errcode", ErrorDescriptor{
Value: "UNSUPPORTED",
Message: "The operation is unsupported.",
Description: `The operation was unsupported due to a missing
implementation or invalid set of parameters.`,
HTTPStatusCode: http.StatusMethodNotAllowed,
})
// ErrorCodeUnauthorized is returned if a request requires
// authentication.
ErrorCodeUnauthorized = Register("errcode", ErrorDescriptor{
Value: "UNAUTHORIZED",
Message: "authentication required",
Description: `The access controller was unable to authenticate
the client. Often this will be accompanied by a
Www-Authenticate HTTP response header indicating how to
authenticate.`,
HTTPStatusCode: http.StatusUnauthorized,
})
// ErrorCodeDenied is returned if a client does not have sufficient
// permission to perform an action.
ErrorCodeDenied = Register("errcode", ErrorDescriptor{
Value: "DENIED",
Message: "requested access to the resource is denied",
Description: `The access controller denied access for the
operation on a resource.`,
HTTPStatusCode: http.StatusForbidden,
})
// ErrorCodeUnavailable provides a common error to report unavailability
// of a service or endpoint.
ErrorCodeUnavailable = Register("errcode", ErrorDescriptor{
Value: "UNAVAILABLE",
Message: "service unavailable",
Description: "Returned when a service is not available",
HTTPStatusCode: http.StatusServiceUnavailable,
})
// ErrorCodeTooManyRequests is returned if a client attempts too many
// times to contact a service endpoint.
ErrorCodeTooManyRequests = Register("errcode", ErrorDescriptor{
Value: "TOOMANYREQUESTS",
Message: "too many requests",
Description: `Returned when a client attempts to contact a
service too many times`,
HTTPStatusCode: http.StatusTooManyRequests,
})
)
var nextCode = 1000
var registerLock sync.Mutex
// Register will make the passed-in error known to the environment and
// return a new ErrorCode
func Register(group string, descriptor ErrorDescriptor) ErrorCode {
registerLock.Lock()
defer registerLock.Unlock()
descriptor.Code = ErrorCode(nextCode)
if _, ok := idToDescriptors[descriptor.Value]; ok {
panic(fmt.Sprintf("ErrorValue %q is already registered", descriptor.Value))
}
if _, ok := errorCodeToDescriptors[descriptor.Code]; ok {
panic(fmt.Sprintf("ErrorCode %v is already registered", descriptor.Code))
}
groupToDescriptors[group] = append(groupToDescriptors[group], descriptor)
errorCodeToDescriptors[descriptor.Code] = descriptor
idToDescriptors[descriptor.Value] = descriptor
nextCode++
return descriptor.Code
}
type byValue []ErrorDescriptor
func (a byValue) Len() int { return len(a) }
func (a byValue) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byValue) Less(i, j int) bool { return a[i].Value < a[j].Value }
// GetGroupNames returns the list of Error group names that are registered
func GetGroupNames() []string {
keys := []string{}
for k := range groupToDescriptors {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}
// GetErrorCodeGroup returns the named group of error descriptors
func GetErrorCodeGroup(name string) []ErrorDescriptor {
desc := groupToDescriptors[name]
sort.Sort(byValue(desc))
return desc
}
// GetErrorAllDescriptors returns a slice of all ErrorDescriptors that are
// registered, irrespective of what group they're in
func GetErrorAllDescriptors() []ErrorDescriptor {
result := []ErrorDescriptor{}
for _, group := range GetGroupNames() {
result = append(result, GetErrorCodeGroup(group)...)
}
sort.Sort(byValue(result))
return result
}
================================================
FILE: pkg/remote/remotes/docker/fetcher.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package docker
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"github.com/containerd/containerd/v2/core/images"
"github.com/containerd/errdefs"
"github.com/containerd/log"
digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
type dockerFetcher struct {
*dockerBase
}
func (r dockerFetcher) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.ReadCloser, error) {
ctx = log.WithLogger(ctx, log.G(ctx).WithField("digest", desc.Digest))
hosts := r.filterHosts(HostCapabilityPull)
if len(hosts) == 0 {
return nil, fmt.Errorf("no pull hosts: %w", errdefs.ErrNotFound)
}
ctx, err := ContextWithRepositoryScope(ctx, r.refspec, false)
if err != nil {
return nil, err
}
return newHTTPReadSeeker(desc.Size, func(offset int64) (io.ReadCloser, error) {
// firstly try fetch via external urls
for _, us := range desc.URLs {
u, err := url.Parse(us)
if err != nil {
log.G(ctx).WithError(err).Debugf("failed to parse %q", us)
continue
}
if u.Scheme != "http" && u.Scheme != "https" {
log.G(ctx).Debug("non-http(s) alternative url is unsupported")
continue
}
ctx = log.WithLogger(ctx, log.G(ctx).WithField("url", u))
log.G(ctx).Info("request")
// Try this first, parse it
host := RegistryHost{
Client: http.DefaultClient,
Host: u.Host,
Scheme: u.Scheme,
Path: u.Path,
Capabilities: HostCapabilityPull,
}
req := r.request(host, http.MethodGet)
// Strip namespace from base
req.path = u.Path
if u.RawQuery != "" {
req.path = req.path + "?" + u.RawQuery
}
rc, _, err := r.open(ctx, req, desc.MediaType, offset)
if err != nil {
if errdefs.IsNotFound(err) {
continue // try one of the other urls.
}
return nil, err
}
return rc, nil
}
// Try manifests endpoints for manifests types
switch desc.MediaType {
case images.MediaTypeDockerSchema2Manifest, images.MediaTypeDockerSchema2ManifestList,
images.MediaTypeDockerSchema1Manifest,
ocispec.MediaTypeImageManifest, ocispec.MediaTypeImageIndex:
var firstErr error
for _, host := range r.hosts {
req := r.request(host, http.MethodGet, "manifests", desc.Digest.String())
if err := req.addNamespace(r.refspec.Hostname()); err != nil {
return nil, err
}
rc, _, err := r.open(ctx, req, desc.MediaType, offset)
if err != nil {
// Store the error for referencing later
if firstErr == nil {
firstErr = err
}
continue // try another host
}
return rc, nil
}
return nil, firstErr
}
// Finally use blobs endpoints
var firstErr error
for _, host := range r.hosts {
req := r.request(host, http.MethodGet, "blobs", desc.Digest.String())
if err := req.addNamespace(r.refspec.Hostname()); err != nil {
return nil, err
}
rc, _, err := r.open(ctx, req, desc.MediaType, offset)
if err != nil {
// Store the error for referencing later
if firstErr == nil {
firstErr = err
}
continue // try another host
}
return rc, nil
}
if errdefs.IsNotFound(firstErr) {
firstErr = fmt.Errorf("could not fetch content descriptor %v (%v) from remote: %w",
desc.Digest, desc.MediaType, errdefs.ErrNotFound,
)
}
return nil, firstErr
})
}
func (r dockerFetcher) createGetReq(ctx context.Context, host RegistryHost, mediatype string, ps ...string) (*request, int64, error) {
headReq := r.request(host, http.MethodHead, ps...)
if err := headReq.addNamespace(r.refspec.Hostname()); err != nil {
return nil, 0, err
}
if mediatype == "" {
headReq.header.Set("Accept", "*/*")
} else {
headReq.header.Set("Accept", strings.Join([]string{mediatype, `*/*`}, ", "))
}
headResp, err := headReq.doWithRetries(ctx, nil)
if err != nil {
return nil, 0, err
}
if headResp.Body != nil {
headResp.Body.Close()
}
if headResp.StatusCode > 299 {
return nil, 0, fmt.Errorf("unexpected HEAD status code %v: %s", headReq.String(), headResp.Status)
}
getReq := r.request(host, http.MethodGet, ps...)
if err := getReq.addNamespace(r.refspec.Hostname()); err != nil {
return nil, 0, err
}
return getReq, headResp.ContentLength, nil
}
func (r dockerFetcher) FetchByDigest(ctx context.Context, dgst digest.Digest) (io.ReadCloser, ocispec.Descriptor, error) {
var desc ocispec.Descriptor
ctx = log.WithLogger(ctx, log.G(ctx).WithField("digest", dgst))
hosts := r.filterHosts(HostCapabilityPull)
if len(hosts) == 0 {
return nil, desc, fmt.Errorf("no pull hosts: %w", errdefs.ErrNotFound)
}
ctx, err := ContextWithRepositoryScope(ctx, r.refspec, false)
if err != nil {
return nil, desc, err
}
var (
getReq *request
sz int64
firstErr error
mediaType string
)
for _, host := range r.hosts {
getReq, sz, err = r.createGetReq(ctx, host, mediaType, "blobs", dgst.String())
if err == nil {
break
}
// Store the error for referencing later
if firstErr == nil {
firstErr = err
}
}
if getReq == nil {
// Fall back to the "manifests" endpoint
// TODO: this change should be upstreamed to containerd.
mediaType = strings.Join([]string{
images.MediaTypeDockerSchema2Manifest,
images.MediaTypeDockerSchema2ManifestList,
ocispec.MediaTypeImageManifest,
ocispec.MediaTypeImageIndex,
}, ", ")
for _, host := range r.hosts {
getReq, sz, err = r.createGetReq(ctx, host, mediaType, "manifests", dgst.String())
if err == nil {
break
}
// Store the error for referencing later
if firstErr == nil {
firstErr = err
}
}
}
if getReq == nil {
if errdefs.IsNotFound(firstErr) {
firstErr = fmt.Errorf("could not fetch content %v from remote: %w", dgst, errdefs.ErrNotFound)
}
if firstErr == nil {
firstErr = fmt.Errorf("could not fetch content %v from remote: (unknown)", dgst)
}
return nil, desc, firstErr
}
seeker, err := newHTTPReadSeeker(sz, func(offset int64) (rc io.ReadCloser, err error) {
rc, _, err = r.open(ctx, getReq, mediaType, offset)
return
})
if err != nil {
return nil, desc, err
}
desc = ocispec.Descriptor{
MediaType: "application/octet-stream",
Digest: dgst,
Size: sz,
}
return seeker, desc, nil
}
func (r dockerFetcher) open(ctx context.Context, req *request, mediatype string, offset int64) (_ io.ReadCloser, _ int64, retErr error) {
if mediatype == "" {
req.header.Set("Accept", "*/*")
} else {
req.header.Set("Accept", strings.Join([]string{mediatype, `*/*`}, ", "))
}
if offset > 0 {
// Note: "Accept-Ranges: bytes" cannot be trusted as some endpoints
// will return the header without supporting the range. The content
// range must always be checked.
req.header.Set("Range", fmt.Sprintf("bytes=%d-", offset))
}
resp, err := req.doWithRetries(ctx, nil)
if err != nil {
return nil, 0, err
}
defer func() {
if retErr != nil {
resp.Body.Close()
}
}()
if resp.StatusCode > 299 {
// TODO(stevvooe): When doing a offset specific request, we should
// really distinguish between a 206 and a 200. In the case of 200, we
// can discard the bytes, hiding the seek behavior from the
// implementation.
if resp.StatusCode == http.StatusNotFound {
return nil, 0, fmt.Errorf("content at %v not found: %w", req.String(), errdefs.ErrNotFound)
}
var registryErr Errors
if err := json.NewDecoder(resp.Body).Decode(®istryErr); err != nil || registryErr.Len() < 1 {
return nil, 0, fmt.Errorf("unexpected status code %v: %v", req.String(), resp.Status)
}
return nil, 0, fmt.Errorf("unexpected status code %v: %s - Server message: %s", req.String(), resp.Status, registryErr.Error())
}
cl := resp.ContentLength
if offset > 0 {
cr := resp.Header.Get("content-range")
if cr != "" {
if !strings.HasPrefix(cr, fmt.Sprintf("bytes %d-", offset)) {
return nil, 0, fmt.Errorf("unhandled content range in response: %v", cr)
}
} else {
// TODO: Should any cases where use of content range
// without the proper header be considered?
// 206 responses?
// Discard up to offset
// Could use buffer pool here but this case should be rare
n, err := io.Copy(io.Discard, io.LimitReader(resp.Body, offset))
if err != nil {
return nil, 0, fmt.Errorf("failed to discard to offset: %w", err)
}
if n != offset {
return nil, 0, errors.New("unable to discard to offset")
}
// Subtract discarded bytes from returned content length to return body
// size consistent with content-range
cl = cl - offset
}
}
return resp.Body, cl, nil
}
================================================
FILE: pkg/remote/remotes/docker/fetcher_fuzz.go
================================================
//go:build gofuzz
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package docker
import (
"context"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
distribution "github.com/distribution/reference"
)
func FuzzFetcher(data []byte) int {
dataLen := len(data)
if dataLen == 0 {
return -1
}
s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("content-range", fmt.Sprintf("bytes %d-%d/%d", 0, dataLen-1, dataLen))
rw.Header().Set("content-length", fmt.Sprintf("%d", dataLen))
rw.Write(data)
}))
defer s.Close()
u, err := url.Parse(s.URL)
if err != nil {
return 0
}
f := dockerFetcher{&dockerBase{
repository: "nonempty",
}}
host := RegistryHost{
Client: s.Client(),
Host: u.Host,
Scheme: u.Scheme,
Path: u.Path,
}
ctx := context.Background()
req := f.request(host, http.MethodGet)
rc, _, err := f.open(ctx, req, "", 0)
if err != nil {
return 0
}
b, err := io.ReadAll(rc)
if err != nil {
return 0
}
expected := data
if len(b) != len(expected) {
panic("len of request is not equal to len of expected but should be")
}
return 1
}
func FuzzParseDockerRef(data []byte) int {
_, _ = distribution.ParseDockerRef(string(data))
return 1
}
================================================
FILE: pkg/remote/remotes/docker/fetcher_test.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package docker
import (
"context"
"encoding/json"
"fmt"
"io"
"math/rand"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/stretchr/testify/assert"
)
func TestFetcherOpen(t *testing.T) {
content := make([]byte, 128)
rand.New(rand.NewSource(1)).Read(content)
start := 0
s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if start > 0 {
rw.Header().Set("content-range", fmt.Sprintf("bytes %d-127/128", start))
}
rw.Header().Set("content-length", fmt.Sprintf("%d", len(content[start:])))
rw.Write(content[start:])
}))
defer s.Close()
u, err := url.Parse(s.URL)
if err != nil {
t.Fatal(err)
}
f := dockerFetcher{&dockerBase{
repository: "nonempty",
}}
host := RegistryHost{
Client: s.Client(),
Host: u.Host,
Scheme: u.Scheme,
Path: u.Path,
}
ctx := context.Background()
req := f.request(host, http.MethodGet)
checkReader := func(o int64) {
t.Helper()
rc, cl, err := f.open(ctx, req, "", o)
if err != nil {
t.Fatalf("failed to open: %+v", err)
}
b, err := io.ReadAll(rc)
if err != nil {
t.Fatal(err)
}
expected := content[o:]
if len(b) != len(expected) {
t.Errorf("unexpected length %d, expected %d", len(b), len(expected))
return
}
if cl != int64(len(expected)) {
t.Errorf("unexpected content length %d, expected %d", cl, len(expected))
return
}
for i, c := range expected {
if b[i] != c {
t.Errorf("unexpected byte %x at %d, expected %x", b[i], i, c)
return
}
}
}
checkReader(0)
// Test server ignores content range
checkReader(25)
// Use content range on server
start = 20
checkReader(20)
// Check returning just last byte and no bytes
start = 127
checkReader(127)
start = 128
checkReader(128)
// Check that server returning a different content range
// then requested errors
start = 30
_, _, err = f.open(ctx, req, "", 20)
if err == nil {
t.Fatal("expected error opening with invalid server response")
}
}
// New set of tests to test new error cases
func TestDockerFetcherOpen(t *testing.T) {
tests := []struct {
name string
mockedStatus int
mockedErr error
want io.ReadCloser
wantLen int64
wantErr bool
wantServerMessageError bool
wantPlainError bool
retries int
}{
{
name: "should return status and error.message if it exists if the registry request fails",
mockedStatus: 500,
mockedErr: Errors{Error{
Code: ErrorCodeUnknown,
Message: "Test Error",
}},
want: nil,
wantErr: true,
wantServerMessageError: true,
},
{
name: "should return just status if the registry request fails and does not return a docker error",
mockedStatus: 500,
mockedErr: fmt.Errorf("Non-docker error"),
want: nil,
wantErr: true,
wantPlainError: true,
}, {
name: "should return StatusRequestTimeout after 5 retries",
mockedStatus: http.StatusRequestTimeout,
mockedErr: fmt.Errorf("%s", http.StatusText(http.StatusRequestTimeout)),
want: nil,
wantErr: true,
wantPlainError: true,
retries: 5,
}, {
name: "should return StatusTooManyRequests after 5 retries",
mockedStatus: http.StatusTooManyRequests,
mockedErr: fmt.Errorf("%s", http.StatusText(http.StatusTooManyRequests)),
want: nil,
wantErr: true,
wantPlainError: true,
retries: 5,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if tt.retries > 0 {
tt.retries--
}
rw.WriteHeader(tt.mockedStatus)
bytes, _ := json.Marshal(tt.mockedErr)
rw.Write(bytes)
}))
defer s.Close()
u, err := url.Parse(s.URL)
if err != nil {
t.Fatal(err)
}
f := dockerFetcher{&dockerBase{
repository: "ns",
}}
host := RegistryHost{
Client: s.Client(),
Host: u.Host,
Scheme: u.Scheme,
Path: u.Path,
}
req := f.request(host, http.MethodGet)
got, cl, err := f.open(context.TODO(), req, "", 0)
assert.Equal(t, tt.wantErr, err != nil)
assert.Equal(t, tt.want, got)
assert.Equal(t, tt.wantLen, cl)
assert.Equal(t, tt.retries, 0)
if tt.wantErr {
var expectedError error
if tt.wantServerMessageError {
expectedError = fmt.Errorf("unexpected status code %v/ns: %v %s - Server message: %s", s.URL, tt.mockedStatus, http.StatusText(tt.mockedStatus), tt.mockedErr.Error())
} else if tt.wantPlainError {
expectedError = fmt.Errorf("unexpected status code %v/ns: %v %s", s.URL, tt.mockedStatus, http.StatusText(tt.mockedStatus))
}
assert.Equal(t, expectedError.Error(), err.Error())
}
})
}
}
================================================
FILE: pkg/remote/remotes/docker/handler.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package docker
import (
"context"
"fmt"
"net/url"
"strings"
"github.com/containerd/containerd/v2/core/content"
"github.com/containerd/containerd/v2/core/images"
"github.com/containerd/containerd/v2/pkg/labels"
"github.com/containerd/containerd/v2/pkg/reference"
"github.com/containerd/log"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// AppendDistributionSourceLabel updates the label of blob with distribution source.
func AppendDistributionSourceLabel(manager content.Manager, ref string) (images.HandlerFunc, error) {
refspec, err := reference.Parse(ref)
if err != nil {
return nil, err
}
u, err := url.Parse("dummy://" + refspec.Locator)
if err != nil {
return nil, err
}
source, repo := u.Hostname(), strings.TrimPrefix(u.Path, "/")
return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
info, err := manager.Info(ctx, desc.Digest)
if err != nil {
return nil, err
}
key := distributionSourceLabelKey(source)
originLabel := ""
if info.Labels != nil {
originLabel = info.Labels[key]
}
value := appendDistributionSourceLabel(originLabel, repo)
// The repo name has been limited under 256 and the distribution
// label might hit the limitation of label size, when blob data
// is used as the very, very common layer.
if err := labels.Validate(key, value); err != nil {
log.G(ctx).Warnf("skip to append distribution label: %s", err)
return nil, nil
}
info = content.Info{
Digest: desc.Digest,
Labels: map[string]string{
key: value,
},
}
_, err = manager.Update(ctx, info, fmt.Sprintf("labels.%s", key))
return nil, err
}, nil
}
func appendDistributionSourceLabel(originLabel, repo string) string {
repos := []string{}
if originLabel != "" {
repos = strings.Split(originLabel, ",")
}
repos = append(repos, repo)
// use empty string to present duplicate items
for i := 1; i < len(repos); i++ {
tmp, j := repos[i], i-1
for ; j >= 0 && repos[j] >= tmp; j-- {
if repos[j] == tmp {
tmp = ""
}
repos[j+1] = repos[j]
}
repos[j+1] = tmp
}
i := 0
for ; i < len(repos) && repos[i] == ""; i++ {
}
return strings.Join(repos[i:], ",")
}
func distributionSourceLabelKey(source string) string {
return fmt.Sprintf("%s.%s", labels.LabelDistributionSource, source)
}
// selectRepositoryMountCandidate will select the repo which has longest
// common prefix components as the candidate.
func selectRepositoryMountCandidate(refspec reference.Spec, sources map[string]string) string {
u, err := url.Parse("dummy://" + refspec.Locator)
if err != nil {
// NOTE: basically, it won't be error here
return ""
}
source, target := u.Hostname(), strings.TrimPrefix(u.Path, "/")
repoLabel, ok := sources[distributionSourceLabelKey(source)]
if !ok || repoLabel == "" {
return ""
}
n, match := 0, ""
components := strings.Split(target, "/")
for _, repo := range strings.Split(repoLabel, ",") {
// the target repo is not a candidate
if repo == target {
continue
}
if l := commonPrefixComponents(components, repo); l >= n {
n, match = l, repo
}
}
return match
}
func commonPrefixComponents(components []string, target string) int {
targetComponents := strings.Split(target, "/")
i := 0
for ; i < len(components) && i < len(targetComponents); i++ {
if components[i] != targetComponents[i] {
break
}
}
return i
}
================================================
FILE: pkg/remote/remotes/docker/handler_test.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package docker
import (
"reflect"
"testing"
"github.com/containerd/containerd/v2/pkg/labels"
"github.com/containerd/containerd/v2/pkg/reference"
)
func TestAppendDistributionLabel(t *testing.T) {
for _, tc := range []struct {
originLabel string
repo string
expected string
}{
{
originLabel: "",
repo: "",
expected: "",
},
{
originLabel: "",
repo: "library/busybox",
expected: "library/busybox",
},
{
originLabel: "library/busybox",
repo: "library/busybox",
expected: "library/busybox",
},
// remove the duplicate one in origin
{
originLabel: "library/busybox,library/redis,library/busybox",
repo: "library/alpine",
expected: "library/alpine,library/busybox,library/redis",
},
// remove the empty repo
{
originLabel: "library/busybox,library/redis,library/busybox",
repo: "",
expected: "library/busybox,library/redis",
},
{
originLabel: "library/busybox,library/redis,library/busybox",
repo: "library/redis",
expected: "library/busybox,library/redis",
},
} {
if got := appendDistributionSourceLabel(tc.originLabel, tc.repo); !reflect.DeepEqual(got, tc.expected) {
t.Fatalf("expected %v, but got %v", tc.expected, got)
}
}
}
func TestDistributionSourceLabelKey(t *testing.T) {
expected := labels.LabelDistributionSource + ".testsource"
if got := distributionSourceLabelKey("testsource"); !reflect.DeepEqual(got, expected) {
t.Fatalf("expected %v, but got %v", expected, got)
}
}
func TestCommonPrefixComponents(t *testing.T) {
for _, tc := range []struct {
components []string
target string
expected int
}{
{
components: []string{"foo"},
target: "foo/bar",
expected: 1,
},
{
components: []string{"bar"},
target: "foo/bar",
expected: 0,
},
{
components: []string{"foo", "bar"},
target: "foo/bar",
expected: 2,
},
} {
if got := commonPrefixComponents(tc.components, tc.target); !reflect.DeepEqual(got, tc.expected) {
t.Fatalf("expected %v, but got %v", tc.expected, got)
}
}
}
func TestSelectRepositoryMountCandidate(t *testing.T) {
for _, tc := range []struct {
refspec reference.Spec
source map[string]string
expected string
}{
{
refspec: reference.Spec{},
source: map[string]string{"": ""},
expected: "",
},
{
refspec: reference.Spec{Locator: "user@host/path"},
source: map[string]string{labels.LabelDistributionSource + ".host": "foo,path,bar"},
expected: "bar",
},
{
refspec: reference.Spec{Locator: "user@host/path"},
source: map[string]string{labels.LabelDistributionSource + ".host": "foo,bar,path"},
expected: "bar",
},
} {
if got := selectRepositoryMountCandidate(tc.refspec, tc.source); !reflect.DeepEqual(got, tc.expected) {
t.Fatalf("expected %v, but got %v", tc.expected, got)
}
}
}
================================================
FILE: pkg/remote/remotes/docker/httpreadseeker.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package docker
import (
"bytes"
"fmt"
"io"
"github.com/containerd/errdefs"
"github.com/containerd/log"
)
const maxRetry = 3
type httpReadSeeker struct {
size int64
offset int64
rc io.ReadCloser
open func(offset int64) (io.ReadCloser, error)
closed bool
errsWithNoProgress int
}
func newHTTPReadSeeker(size int64, open func(offset int64) (io.ReadCloser, error)) (io.ReadCloser, error) {
return &httpReadSeeker{
size: size,
open: open,
}, nil
}
func (hrs *httpReadSeeker) Read(p []byte) (n int, err error) {
if hrs.closed {
return 0, io.EOF
}
rd, err := hrs.reader()
if err != nil {
return 0, err
}
n, err = rd.Read(p)
hrs.offset += int64(n)
if n > 0 || err == nil {
hrs.errsWithNoProgress = 0
}
if err == io.ErrUnexpectedEOF {
// connection closed unexpectedly. try reconnecting.
if n == 0 {
hrs.errsWithNoProgress++
if hrs.errsWithNoProgress > maxRetry {
return // too many retries for this offset with no progress
}
}
if hrs.rc != nil {
if clsErr := hrs.rc.Close(); clsErr != nil {
log.L.WithError(clsErr).Error("httpReadSeeker: failed to close ReadCloser")
}
hrs.rc = nil
}
if _, err2 := hrs.reader(); err2 == nil {
return n, nil
}
}
return
}
func (hrs *httpReadSeeker) Close() error {
if hrs.closed {
return nil
}
hrs.closed = true
if hrs.rc != nil {
return hrs.rc.Close()
}
return nil
}
func (hrs *httpReadSeeker) Seek(offset int64, whence int) (int64, error) {
if hrs.closed {
return 0, fmt.Errorf("Fetcher.Seek: closed: %w", errdefs.ErrUnavailable)
}
abs := hrs.offset
switch whence {
case io.SeekStart:
abs = offset
case io.SeekCurrent:
abs += offset
case io.SeekEnd:
if hrs.size == -1 {
return 0, fmt.Errorf("Fetcher.Seek: unknown size, cannot seek from end: %w", errdefs.ErrUnavailable)
}
abs = hrs.size + offset
default:
return 0, fmt.Errorf("Fetcher.Seek: invalid whence: %w", errdefs.ErrInvalidArgument)
}
if abs < 0 {
return 0, fmt.Errorf("Fetcher.Seek: negative offset: %w", errdefs.ErrInvalidArgument)
}
if abs != hrs.offset {
if hrs.rc != nil {
if err := hrs.rc.Close(); err != nil {
log.L.WithError(err).Error("Fetcher.Seek: failed to close ReadCloser")
}
hrs.rc = nil
}
hrs.offset = abs
}
return hrs.offset, nil
}
func (hrs *httpReadSeeker) reader() (io.Reader, error) {
if hrs.rc != nil {
return hrs.rc, nil
}
if hrs.size == -1 || hrs.offset < hrs.size {
// only try to reopen the body request if we are seeking to a value
// less than the actual size.
if hrs.open == nil {
return nil, fmt.Errorf("cannot open: %w", errdefs.ErrNotImplemented)
}
rc, err := hrs.open(hrs.offset)
if err != nil {
return nil, fmt.Errorf("httpReadSeeker: failed open: %w", err)
}
if hrs.rc != nil {
if err := hrs.rc.Close(); err != nil {
log.L.WithError(err).Error("httpReadSeeker: failed to close ReadCloser")
}
}
hrs.rc = rc
} else {
// There is an edge case here where offset == size of the content. If
// we seek, we will probably get an error for content that cannot be
// sought (?). In that case, we should err on committing the content,
// as the length is already satisfied but we just return the empty
// reader instead.
hrs.rc = io.NopCloser(bytes.NewReader([]byte{}))
}
return hrs.rc, nil
}
================================================
FILE: pkg/remote/remotes/docker/pusher.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package docker
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"sync"
"time"
"github.com/containerd/containerd/v2/core/content"
"github.com/containerd/containerd/v2/core/images"
"github.com/containerd/errdefs"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/remote/remotes"
remoteserrors "github.com/containerd/nydus-snapshotter/pkg/remote/remotes/errors"
digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
type dockerPusher struct {
*dockerBase
object string
// TODO: namespace tracker
tracker StatusTracker
}
// Writer implements Ingester API of content store. This allows the client
// to receive ErrUnavailable when there is already an on-going upload.
// Note that the tracker MUST implement StatusTrackLocker interface to avoid
// race condition on StatusTracker.
func (p dockerPusher) Writer(ctx context.Context, opts ...content.WriterOpt) (content.Writer, error) {
var wOpts content.WriterOpts
for _, opt := range opts {
if err := opt(&wOpts); err != nil {
return nil, err
}
}
if wOpts.Ref == "" {
return nil, fmt.Errorf("ref must not be empty: %w", errdefs.ErrInvalidArgument)
}
return p.push(ctx, wOpts.Desc, wOpts.Ref, true)
}
func (p dockerPusher) Push(ctx context.Context, desc ocispec.Descriptor) (content.Writer, error) {
return p.push(ctx, desc, remotes.MakeRefKey(ctx, desc), false)
}
func (p dockerPusher) push(ctx context.Context, desc ocispec.Descriptor, ref string, unavailableOnFail bool) (content.Writer, error) {
if l, ok := p.tracker.(StatusTrackLocker); ok {
l.Lock(ref)
defer l.Unlock(ref)
}
ctx, err := ContextWithRepositoryScope(ctx, p.refspec, true)
if err != nil {
return nil, err
}
status, err := p.tracker.GetStatus(ref)
if err == nil {
if status.Committed && status.Offset == status.Total {
return nil, fmt.Errorf("ref %v: %w", ref, errdefs.ErrAlreadyExists)
}
if unavailableOnFail && status.ErrClosed == nil {
// Another push of this ref is happening elsewhere. The rest of function
// will continue only when `errdefs.IsNotFound(err) == true` (i.e. there
// is no actively-tracked ref already).
return nil, fmt.Errorf("push is on-going: %w", errdefs.ErrUnavailable)
}
// TODO: Handle incomplete status
} else if !errdefs.IsNotFound(err) {
return nil, fmt.Errorf("failed to get status: %w", err)
}
hosts := p.filterHosts(HostCapabilityPush)
if len(hosts) == 0 {
return nil, fmt.Errorf("no push hosts: %w", errdefs.ErrNotFound)
}
var (
isManifest bool
existCheck []string
host = hosts[0]
)
switch desc.MediaType {
case images.MediaTypeDockerSchema2Manifest, images.MediaTypeDockerSchema2ManifestList,
ocispec.MediaTypeImageManifest, ocispec.MediaTypeImageIndex:
isManifest = true
existCheck = getManifestPath(p.object, desc.Digest)
default:
existCheck = []string{"blobs", desc.Digest.String()}
}
req := p.request(host, http.MethodHead, existCheck...)
req.header.Set("Accept", strings.Join([]string{desc.MediaType, `*/*`}, ", "))
log.G(ctx).WithField("url", req.String()).Debugf("checking and pushing to")
resp, err := req.doWithRetries(ctx, nil)
if err != nil {
if !errors.Is(err, ErrInvalidAuthorization) {
return nil, err
}
log.G(ctx).WithError(err).Debugf("Unable to check existence, continuing with push")
} else {
if resp.StatusCode == http.StatusOK {
var exists bool
if isManifest && existCheck[1] != desc.Digest.String() {
dgstHeader := digest.Digest(resp.Header.Get("Docker-Content-Digest"))
if dgstHeader == desc.Digest {
exists = true
}
} else {
exists = true
}
if exists {
p.tracker.SetStatus(ref, Status{
Committed: true,
Status: content.Status{
Ref: ref,
Total: desc.Size,
Offset: desc.Size,
// TODO: Set updated time?
},
})
resp.Body.Close()
return nil, fmt.Errorf("content %v on remote: %w", desc.Digest, errdefs.ErrAlreadyExists)
}
} else if resp.StatusCode != http.StatusNotFound {
err := remoteserrors.NewUnexpectedStatusErr(resp)
log.G(ctx).WithField("resp", resp).WithField("body", string(err.(remoteserrors.ErrUnexpectedStatus).Body)).Debug("unexpected response")
resp.Body.Close()
return nil, err
}
resp.Body.Close()
}
if isManifest {
putPath := getManifestPath(p.object, desc.Digest)
req = p.request(host, http.MethodPut, putPath...)
req.header.Add("Content-Type", desc.MediaType)
} else {
// Start upload request
req = p.request(host, http.MethodPost, "blobs", "uploads/")
var resp *http.Response
if fromRepo := selectRepositoryMountCandidate(p.refspec, desc.Annotations); fromRepo != "" {
preq := requestWithMountFrom(req, desc.Digest.String(), fromRepo)
pctx := ContextWithAppendPullRepositoryScope(ctx, fromRepo)
// NOTE: the fromRepo might be private repo and
// auth service still can grant token without error.
// but the post request will fail because of 401.
//
// for the private repo, we should remove mount-from
// query and send the request again.
resp, err = preq.doWithRetries(pctx, nil)
if err != nil {
return nil, err
}
if resp.StatusCode == http.StatusUnauthorized {
log.G(ctx).Debugf("failed to mount from repository %s", fromRepo)
resp.Body.Close()
resp = nil
}
}
if resp == nil {
resp, err = req.doWithRetries(ctx, nil)
if err != nil {
if errors.Is(err, ErrInvalidAuthorization) {
return nil, fmt.Errorf("push access denied, repository does not exist or may require authorization: %w", err)
}
return nil, err
}
}
defer resp.Body.Close()
switch resp.StatusCode {
case http.StatusOK, http.StatusAccepted, http.StatusNoContent:
case http.StatusCreated:
p.tracker.SetStatus(ref, Status{
Committed: true,
Status: content.Status{
Ref: ref,
Total: desc.Size,
Offset: desc.Size,
},
})
return nil, fmt.Errorf("content %v on remote: %w", desc.Digest, errdefs.ErrAlreadyExists)
default:
err := remoteserrors.NewUnexpectedStatusErr(resp)
log.G(ctx).WithField("resp", resp).WithField("body", string(err.(remoteserrors.ErrUnexpectedStatus).Body)).Debug("unexpected response")
return nil, err
}
var (
location = resp.Header.Get("Location")
lurl *url.URL
lhost = host
)
// Support paths without host in location
if strings.HasPrefix(location, "/") {
lurl, err = url.Parse(lhost.Scheme + "://" + lhost.Host + location)
if err != nil {
return nil, fmt.Errorf("unable to parse location %v: %w", location, err)
}
} else {
if !strings.Contains(location, "://") {
location = lhost.Scheme + "://" + location
}
lurl, err = url.Parse(location)
if err != nil {
return nil, fmt.Errorf("unable to parse location %v: %w", location, err)
}
if lurl.Host != lhost.Host || lhost.Scheme != lurl.Scheme {
lhost.Scheme = lurl.Scheme
lhost.Host = lurl.Host
log.G(ctx).WithField("host", lhost.Host).WithField("scheme", lhost.Scheme).Debug("upload changed destination")
// Strip authorizer if change to host or scheme
lhost.Authorizer = nil
}
}
q := lurl.Query()
q.Add("digest", desc.Digest.String())
req = p.request(lhost, http.MethodPut)
req.header.Set("Content-Type", "application/octet-stream")
req.path = lurl.Path + "?" + q.Encode()
}
p.tracker.SetStatus(ref, Status{
Status: content.Status{
Ref: ref,
Total: desc.Size,
Expected: desc.Digest,
StartedAt: time.Now(),
},
})
// TODO: Support chunked upload
pushw := newPushWriter(p.dockerBase, ref, desc.Digest, p.tracker, isManifest)
req.body = func() (io.ReadCloser, error) {
pr, pw := io.Pipe()
pushw.setPipe(pw)
return io.NopCloser(pr), nil
}
req.size = desc.Size
go func() {
resp, err := req.doWithRetries(ctx, nil)
if err != nil {
pushw.setError(err)
pushw.Close()
return
}
switch resp.StatusCode {
case http.StatusOK, http.StatusCreated, http.StatusNoContent:
default:
err := remoteserrors.NewUnexpectedStatusErr(resp)
log.G(ctx).WithField("resp", resp).WithField("body", string(err.(remoteserrors.ErrUnexpectedStatus).Body)).Debug("unexpected response")
pushw.setError(err)
pushw.Close()
}
pushw.setResponse(resp)
}()
return pushw, nil
}
func getManifestPath(object string, dgst digest.Digest) []string {
if i := strings.IndexByte(object, '@'); i >= 0 {
if object[i+1:] != dgst.String() {
// use digest, not tag
object = ""
} else {
// strip @ for registry path to make tag
object = object[:i]
}
}
if object == "" {
return []string{"manifests", dgst.String()}
}
return []string{"manifests", object}
}
type pushWriter struct {
base *dockerBase
ref string
pipe *io.PipeWriter
pipeC chan *io.PipeWriter
respC chan *http.Response
closeOnce sync.Once
errC chan error
isManifest bool
expected digest.Digest
tracker StatusTracker
}
func newPushWriter(db *dockerBase, ref string, expected digest.Digest, tracker StatusTracker, isManifest bool) *pushWriter {
// Initialize and create response
return &pushWriter{
base: db,
ref: ref,
expected: expected,
tracker: tracker,
pipeC: make(chan *io.PipeWriter, 1),
respC: make(chan *http.Response, 1),
errC: make(chan error, 1),
isManifest: isManifest,
}
}
func (pw *pushWriter) setPipe(p *io.PipeWriter) {
pw.pipeC <- p
}
func (pw *pushWriter) setError(err error) {
pw.errC <- err
}
func (pw *pushWriter) setResponse(resp *http.Response) {
pw.respC <- resp
}
func (pw *pushWriter) Write(p []byte) (n int, err error) {
status, err := pw.tracker.GetStatus(pw.ref)
if err != nil {
return n, err
}
if pw.pipe == nil {
p, ok := <-pw.pipeC
if !ok {
return 0, io.ErrClosedPipe
}
pw.pipe = p
} else {
select {
case p, ok := <-pw.pipeC:
if !ok {
return 0, io.ErrClosedPipe
}
pw.pipe.CloseWithError(content.ErrReset)
pw.pipe = p
// If content has already been written, the bytes
// cannot be written and the caller must reset
status.Offset = 0
status.UpdatedAt = time.Now()
pw.tracker.SetStatus(pw.ref, status)
return 0, content.ErrReset
default:
}
}
n, err = pw.pipe.Write(p)
if errors.Is(err, io.ErrClosedPipe) {
// if the pipe is closed, we might have the original error on the error
// channel - so we should try and get it
select {
case err2 := <-pw.errC:
err = err2
default:
}
}
status.Offset += int64(n)
status.UpdatedAt = time.Now()
pw.tracker.SetStatus(pw.ref, status)
return
}
func (pw *pushWriter) Close() error {
// Ensure pipeC is closed but handle `Close()` being
// called multiple times without panicking
pw.closeOnce.Do(func() {
close(pw.pipeC)
})
if pw.pipe != nil {
status, err := pw.tracker.GetStatus(pw.ref)
if err == nil && !status.Committed {
// Closing an incomplete writer. Record this as an error so that following write can retry it.
status.ErrClosed = errors.New("closed incomplete writer")
pw.tracker.SetStatus(pw.ref, status)
}
return pw.pipe.Close()
}
return nil
}
func (pw *pushWriter) Status() (content.Status, error) {
status, err := pw.tracker.GetStatus(pw.ref)
if err != nil {
return content.Status{}, err
}
return status.Status, nil
}
func (pw *pushWriter) Digest() digest.Digest {
// TODO: Get rid of this function?
return pw.expected
}
func (pw *pushWriter) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) error {
// Check whether read has already thrown an error
if _, err := pw.pipe.Write([]byte{}); err != nil && !errors.Is(err, io.ErrClosedPipe) {
return fmt.Errorf("pipe error before commit: %w", err)
}
if err := pw.pipe.Close(); err != nil {
return err
}
// TODO: timeout waiting for response
var resp *http.Response
select {
case err := <-pw.errC:
return err
case resp = <-pw.respC:
defer resp.Body.Close()
case p, ok := <-pw.pipeC:
// check whether the pipe has changed in the commit, because sometimes Write
// can complete successfully, but the pipe may have changed. In that case, the
// content needs to be reset.
if !ok {
return io.ErrClosedPipe
}
pw.pipe.CloseWithError(content.ErrReset)
pw.pipe = p
// If content has already been written, the bytes
// cannot be written again and the caller must reset
status, err := pw.tracker.GetStatus(pw.ref)
if err != nil {
return err
}
status.Offset = 0
status.UpdatedAt = time.Now()
pw.tracker.SetStatus(pw.ref, status)
return content.ErrReset
}
// 201 is specified return status, some registries return
// 200, 202 or 204.
switch resp.StatusCode {
case http.StatusOK, http.StatusCreated, http.StatusNoContent, http.StatusAccepted:
default:
return remoteserrors.NewUnexpectedStatusErr(resp)
}
status, err := pw.tracker.GetStatus(pw.ref)
if err != nil {
return fmt.Errorf("failed to get status: %w", err)
}
if size > 0 && size != status.Offset {
return fmt.Errorf("unexpected size %d, expected %d", status.Offset, size)
}
if expected == "" {
expected = status.Expected
}
actual, err := digest.Parse(resp.Header.Get("Docker-Content-Digest"))
if err != nil {
return fmt.Errorf("invalid content digest in response: %w", err)
}
if actual != expected {
return fmt.Errorf("got digest %s, expected %s", actual, expected)
}
status.Committed = true
status.UpdatedAt = time.Now()
pw.tracker.SetStatus(pw.ref, status)
return nil
}
func (pw *pushWriter) Truncate(size int64) error {
// TODO: if blob close request and start new request at offset
// TODO: always error on manifest
return errors.New("cannot truncate remote upload")
}
func requestWithMountFrom(req *request, mount, from string) *request {
creq := *req
sep := "?"
if strings.Contains(creq.path, sep) {
sep = "&"
}
creq.path = creq.path + sep + "mount=" + mount + "&from=" + from
return &creq
}
================================================
FILE: pkg/remote/remotes/docker/pusher_test.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package docker
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"regexp"
"strings"
"testing"
"github.com/containerd/containerd/v2/core/content"
"github.com/containerd/errdefs"
"github.com/containerd/nydus-snapshotter/pkg/remote/remotes"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/assert"
)
func TestGetManifestPath(t *testing.T) {
for _, tc := range []struct {
object string
dgst digest.Digest
expected []string
}{
{
object: "foo",
dgst: "bar",
expected: []string{"manifests", "foo"},
},
{
object: "foo@bar",
dgst: "bar",
expected: []string{"manifests", "foo"},
},
{
object: "foo@bar",
dgst: "foobar",
expected: []string{"manifests", "foobar"},
},
} {
if got := getManifestPath(tc.object, tc.dgst); !reflect.DeepEqual(got, tc.expected) {
t.Fatalf("expected %v, but got %v", tc.expected, got)
}
}
}
// TestPusherErrClosedRetry tests if retrying work when error occurred on close.
func TestPusherErrClosedRetry(t *testing.T) {
ctx := context.Background()
p, reg, done := samplePusher(t)
defer done()
layerContent := []byte("test")
reg.uploadable = false
if err := tryUpload(ctx, t, p, layerContent); err == nil {
t.Errorf("upload should fail but succeeded")
}
// retry
reg.uploadable = true
if err := tryUpload(ctx, t, p, layerContent); err != nil {
t.Errorf("upload should succeed but got %v", err)
}
}
// TestPusherErrReset tests the push method if the request needs to be retried
// i.e when ErrReset occurs
func TestPusherErrReset(t *testing.T) {
p, reg, done := samplePusher(t)
defer done()
p.object = "latest@sha256:55d31f3af94c797b65b310569803cacc1c9f4a34bf61afcdc8138f89345c8308"
reg.uploadable = true
reg.putHandlerFunc = func() func(w http.ResponseWriter, r *http.Request) bool {
// sets whether the request should timeout so that a reset error can occur and
// request will be retried
shouldTimeout := true
return func(w http.ResponseWriter, r *http.Request) bool {
if shouldTimeout {
shouldTimeout = !shouldTimeout
w.WriteHeader(http.StatusRequestTimeout)
return true
}
return false
}
}()
ct := []byte("manifest-content")
desc := ocispec.Descriptor{
MediaType: ocispec.MediaTypeImageManifest,
Digest: digest.FromBytes(ct),
Size: int64(len(ct)),
}
w, err := p.push(context.Background(), desc, remotes.MakeRefKey(context.Background(), desc), false)
assert.NoError(t, err)
// first push should fail with ErrReset
_, err = w.Write(ct)
assert.NoError(t, err)
err = w.Commit(context.Background(), desc.Size, desc.Digest)
assert.Equal(t, content.ErrReset, err)
// second push should succeed
_, err = w.Write(ct)
assert.NoError(t, err)
err = w.Commit(context.Background(), desc.Size, desc.Digest)
assert.NoError(t, err)
}
func tryUpload(ctx context.Context, t *testing.T, p dockerPusher, layerContent []byte) error {
desc := ocispec.Descriptor{
MediaType: ocispec.MediaTypeImageLayerGzip,
Digest: digest.FromBytes(layerContent),
Size: int64(len(layerContent)),
}
cw, err := p.Writer(ctx, content.WithRef("test-1"), content.WithDescriptor(desc))
if err != nil {
return err
}
defer cw.Close()
if _, err := cw.Write(layerContent); err != nil {
return err
}
return cw.Commit(ctx, 0, "")
}
func samplePusher(t *testing.T) (dockerPusher, *uploadableMockRegistry, func()) {
reg := &uploadableMockRegistry{
availableContents: make([]string, 0),
}
s := httptest.NewServer(reg)
u, err := url.Parse(s.URL)
if err != nil {
t.Fatal(err)
}
return dockerPusher{
dockerBase: &dockerBase{
repository: "sample",
hosts: []RegistryHost{
{
Client: s.Client(),
Host: u.Host,
Scheme: u.Scheme,
Path: u.Path,
Capabilities: HostCapabilityPush | HostCapabilityResolve,
},
},
},
object: "sample",
tracker: NewInMemoryTracker(),
}, reg, s.Close
}
var manifestRegexp = regexp.MustCompile(`/([a-z0-9]+)/manifests/(.*)`)
var blobUploadRegexp = regexp.MustCompile(`/([a-z0-9]+)/blobs/uploads/(.*)`)
// uploadableMockRegistry provides minimal registry APIs which are enough to serve requests from dockerPusher.
type uploadableMockRegistry struct {
availableContents []string
uploadable bool
putHandlerFunc func(w http.ResponseWriter, r *http.Request) bool
}
func (u *uploadableMockRegistry) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPut && u.putHandlerFunc != nil {
// if true return the response witout calling default handler
if u.putHandlerFunc(w, r) {
return
}
}
u.defaultHandler(w, r)
}
func (u *uploadableMockRegistry) defaultHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
if matches := blobUploadRegexp.FindStringSubmatch(r.URL.Path); len(matches) != 0 {
if u.uploadable {
w.Header().Set("Location", "/upload")
} else {
w.Header().Set("Location", "/cannotupload")
}
dgstr := digest.Canonical.Digester()
if _, err := io.Copy(dgstr.Hash(), r.Body); err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
u.availableContents = append(u.availableContents, dgstr.Digest().String())
w.WriteHeader(http.StatusAccepted)
return
}
} else if r.Method == http.MethodPut {
mfstMatches := manifestRegexp.FindStringSubmatch(r.URL.Path)
if len(mfstMatches) != 0 || strings.HasPrefix(r.URL.Path, "/upload") {
dgstr := digest.Canonical.Digester()
if _, err := io.Copy(dgstr.Hash(), r.Body); err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
u.availableContents = append(u.availableContents, dgstr.Digest().String())
w.Header().Set("Docker-Content-Digest", dgstr.Digest().String())
w.WriteHeader(http.StatusCreated)
return
} else if r.URL.Path == "/cannotupload" {
w.WriteHeader(http.StatusInternalServerError)
return
}
} else if r.Method == http.MethodHead {
var content string
// check for both manifest and blob paths
if manifestMatch := manifestRegexp.FindStringSubmatch(r.URL.Path); len(manifestMatch) == 3 {
content = manifestMatch[2]
} else if blobMatch := blobUploadRegexp.FindStringSubmatch(r.URL.Path); len(blobMatch) == 3 {
content = blobMatch[2]
}
// if content is not found or if the path is not manifest or blob
// we return 404
if u.isContentAlreadyExist(content) {
w.WriteHeader(http.StatusOK)
} else {
w.WriteHeader(http.StatusNotFound)
}
return
}
fmt.Println(r)
w.WriteHeader(http.StatusNotFound)
}
// checks if the content is already present in the registry
func (u *uploadableMockRegistry) isContentAlreadyExist(c string) bool {
for _, ct := range u.availableContents {
if ct == c {
return true
}
}
return false
}
func Test_dockerPusher_push(t *testing.T) {
p, reg, done := samplePusher(t)
defer done()
reg.uploadable = true
manifestContent := []byte("manifest-content")
manifestContentDigest := digest.FromBytes(manifestContent)
layerContent := []byte("layer-content")
layerContentDigest := digest.FromBytes(layerContent)
// using a random object here
baseObject := "latest@sha256:55d31f3af94c797b65b310569803cacc1c9f4a34bf61afcdc8138f89345c8308"
type args struct {
content []byte
mediatype string
ref string
unavailableOnFail bool
}
tests := []struct {
name string
dp dockerPusher
dockerBaseObject string
args args
checkerFunc func(writer *pushWriter) bool
wantErr error
}{
{
name: "when a manifest is pushed",
dp: p,
dockerBaseObject: baseObject,
args: args{
content: manifestContent,
mediatype: ocispec.MediaTypeImageManifest,
ref: fmt.Sprintf("manifest-%s", manifestContentDigest.String()),
unavailableOnFail: false,
},
checkerFunc: func(writer *pushWriter) bool {
select {
case resp := <-writer.respC:
// 201 should be the response code when uploading a new manifest
return resp.StatusCode == http.StatusCreated
case <-writer.errC:
return false
}
},
wantErr: nil,
},
{
name: "trying to push content that already exists",
dp: p,
dockerBaseObject: baseObject,
args: args{
content: manifestContent,
mediatype: ocispec.MediaTypeImageManifest,
ref: fmt.Sprintf("manifest-%s", manifestContentDigest.String()),
unavailableOnFail: false,
},
wantErr: fmt.Errorf("content %v on remote: %w", digest.FromBytes(manifestContent), errdefs.ErrAlreadyExists),
},
{
name: "trying to push a blob layer",
dp: p,
// Not needed to set the base object as it is used to generate path only in case of manifests
// dockerBaseObject:
args: args{
content: layerContent,
mediatype: ocispec.MediaTypeImageLayer,
ref: fmt.Sprintf("layer-%s", layerContentDigest.String()),
unavailableOnFail: false,
},
checkerFunc: func(writer *pushWriter) bool {
select {
case resp := <-writer.respC:
// 201 should be the response code when uploading a new blob
return resp.StatusCode == http.StatusCreated
case <-writer.errC:
return false
}
},
wantErr: nil,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
desc := ocispec.Descriptor{
MediaType: test.args.mediatype,
Digest: digest.FromBytes(test.args.content),
Size: int64(len(test.args.content)),
}
test.dp.object = test.dockerBaseObject
got, err := test.dp.push(context.Background(), desc, test.args.ref, test.args.unavailableOnFail)
assert.Equal(t, test.wantErr, err)
// if an error is expected, further comparisons are not required.
if test.wantErr != nil {
return
}
// write the content to the writer, this will be done when a Read() is called on the body of the request
got.Write(test.args.content)
pw, ok := got.(*pushWriter)
if !ok {
assert.Errorf(t, errors.New("unable to cast content.Writer to pushWriter"), "got %v instead of pushwriter", got)
}
// test whether a proper response has been received after the push operation
assert.True(t, test.checkerFunc(pw))
})
}
}
================================================
FILE: pkg/remote/remotes/docker/referrers.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package docker
import (
"context"
"fmt"
"io"
"net/http"
"strings"
"github.com/containerd/errdefs"
"github.com/containerd/log"
digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
func (r dockerFetcher) FetchReferrers(ctx context.Context, dgst digest.Digest, artifactTypes ...string) (io.ReadCloser, ocispec.Descriptor, error) {
var desc ocispec.Descriptor
desc.MediaType = ocispec.MediaTypeImageIndex
ctx = log.WithLogger(ctx, log.G(ctx).WithField("digest", dgst))
hosts := r.filterHosts(HostCapabilityResolve, HostCapabilityReferrers)
if len(hosts) == 0 {
return nil, desc, fmt.Errorf("no pull hosts: %w", errdefs.ErrNotFound)
}
ctx, err := ContextWithRepositoryScope(ctx, r.refspec, false)
if err != nil {
return nil, desc, err
}
for _, host := range hosts {
var req *request
// if host.Capabilities.Has(HostCapabilityReferrers) {
req = r.request(host, http.MethodGet, "referrers", dgst.String())
for _, artifactType := range artifactTypes {
if err := req.addQuery("artifactType", artifactType); err != nil {
return nil, desc, err
}
}
if err := req.addNamespace(r.refspec.Hostname()); err != nil {
return nil, desc, err
}
rc, cl, err := r.open(ctx, req, desc.MediaType, 0)
if err != nil {
if !errdefs.IsNotFound(err) {
return nil, desc, err
}
} else {
desc.Size = cl
// Digest is not known ahead of time and there is nothing in the distribution
// specification defining an HTTP header to return the digest on referrers.
return rc, desc, nil
}
// }
if host.Capabilities.Has(HostCapabilityResolve) {
req = r.request(host, http.MethodGet, "manifests", strings.Replace(dgst.String(), ":", "-", 1))
if err := req.addNamespace(r.refspec.Hostname()); err != nil {
return nil, desc, err
}
rc, cl, err := r.open(ctx, req, desc.MediaType, 0)
if err != nil {
if !errdefs.IsNotFound(err) {
return nil, desc, err
}
} else {
desc.Size = cl
// Digest could be resolved here the same as for any manifest, don't include the
// digest for consistency with the referrers endpoint.
return rc, desc, nil
}
}
}
return nil, ocispec.Descriptor{}, fmt.Errorf("could not be found at any host: %w", errdefs.ErrNotFound)
}
================================================
FILE: pkg/remote/remotes/docker/registry.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package docker
import (
"errors"
"net"
"net/http"
)
// HostCapabilities represent the capabilities of the registry
// host. This also represents the set of operations for which
// the registry host may be trusted to perform.
//
// For example pushing is a capability which should only be
// performed on an upstream source, not a mirror.
// Resolving (the process of converting a name into a digest)
// must be considered a trusted operation and only done by
// a host which is trusted (or more preferably by secure process
// which can prove the provenance of the mapping). A public
// mirror should never be trusted to do a resolve action.
//
// | Registry Type | Pull | Resolve | Push |
// |------------------|------|---------|------|
// | Public Registry | yes | yes | yes |
// | Private Registry | yes | yes | yes |
// | Public Mirror | yes | no | no |
// | Private Mirror | yes | yes | no |
type HostCapabilities uint8
const (
// HostCapabilityPull represents the capability to fetch manifests
// and blobs by digest
HostCapabilityPull HostCapabilities = 1 << iota
// HostCapabilityResolve represents the capability to fetch manifests
// by name
HostCapabilityResolve
// HostCapabilityPush represents the capability to push blobs and
// manifests
HostCapabilityPush
// HostCapabilityReferrers represents the capability to generate a
// list of referrers using the OCI Distribution referrers endpoint.
HostCapabilityReferrers
// Reserved for future capabilities (i.e. search, catalog, remove)
)
// Has checks whether the capabilities list has the provide capability
func (c HostCapabilities) Has(t HostCapabilities) bool {
return c&t == t
}
// RegistryHost represents a complete configuration for a registry
// host, representing the capabilities, authorizations, connection
// configuration, and location.
type RegistryHost struct {
Client *http.Client
Authorizer Authorizer
Host string
Scheme string
Path string
Capabilities HostCapabilities
Header http.Header
}
func (h RegistryHost) isProxy(refhost string) bool {
if refhost != h.Host {
if refhost != "docker.io" || h.Host != "registry-1.docker.io" {
return true
}
}
return false
}
// RegistryHosts fetches the registry hosts for a given namespace,
// provided by the host component of an distribution image reference.
type RegistryHosts func(string) ([]RegistryHost, error)
// Registries joins multiple registry configuration functions, using the same
// order as provided within the arguments. When an empty registry configuration
// is returned with a nil error, the next function will be called.
// NOTE: This function will not join configurations, as soon as a non-empty
// configuration is returned from a configuration function, it will be returned
// to the caller.
func Registries(registries ...RegistryHosts) RegistryHosts {
return func(host string) ([]RegistryHost, error) {
for _, registry := range registries {
config, err := registry(host)
if err != nil {
return config, err
}
if len(config) > 0 {
return config, nil
}
}
return nil, nil
}
}
type registryOpts struct {
authorizer Authorizer
plainHTTP func(string) (bool, error)
host func(string) (string, error)
client *http.Client
}
// RegistryOpt defines a registry default option
type RegistryOpt func(*registryOpts)
// WithPlainHTTP configures registries to use plaintext http scheme
// for the provided host match function.
func WithPlainHTTP(f func(string) (bool, error)) RegistryOpt {
return func(opts *registryOpts) {
opts.plainHTTP = f
}
}
// WithAuthorizer configures the default authorizer for a registry
func WithAuthorizer(a Authorizer) RegistryOpt {
return func(opts *registryOpts) {
opts.authorizer = a
}
}
// WithHostTranslator defines the default translator to use for registry hosts
func WithHostTranslator(h func(string) (string, error)) RegistryOpt {
return func(opts *registryOpts) {
opts.host = h
}
}
// WithClient configures the default http client for a registry
func WithClient(c *http.Client) RegistryOpt {
return func(opts *registryOpts) {
opts.client = c
}
}
// ConfigureDefaultRegistries is used to create a default configuration for
// registries. For more advanced configurations or per-domain setups,
// the RegistryHosts interface should be used directly.
// NOTE: This function will always return a non-empty value or error
func ConfigureDefaultRegistries(ropts ...RegistryOpt) RegistryHosts {
var opts registryOpts
for _, opt := range ropts {
opt(&opts)
}
return func(host string) ([]RegistryHost, error) {
config := RegistryHost{
Client: opts.client,
Authorizer: opts.authorizer,
Host: host,
Scheme: "https",
Path: "/v2",
Capabilities: HostCapabilityPull | HostCapabilityResolve | HostCapabilityPush,
}
if config.Client == nil {
config.Client = http.DefaultClient
}
if opts.plainHTTP != nil {
match, err := opts.plainHTTP(host)
if err != nil {
return nil, err
}
if match {
config.Scheme = "http"
}
}
if opts.host != nil {
var err error
config.Host, err = opts.host(config.Host)
if err != nil {
return nil, err
}
} else if host == "docker.io" {
config.Host = "registry-1.docker.io"
}
return []RegistryHost{config}, nil
}
}
// MatchAllHosts is a host match function which is always true.
func MatchAllHosts(string) (bool, error) {
return true, nil
}
// MatchLocalhost is a host match function which returns true for
// localhost.
//
// Note: this does not handle matching of ip addresses in octal,
// decimal or hex form.
func MatchLocalhost(host string) (bool, error) {
switch {
case host == "::1":
return true, nil
case host == "[::1]":
return true, nil
}
h, p, err := net.SplitHostPort(host)
// addrError helps distinguish between errors of form
// "no colon in address" and "too many colons in address".
// The former is fine as the host string need not have a
// port. Latter needs to be handled.
addrError := &net.AddrError{
Err: "missing port in address",
Addr: host,
}
if err != nil {
if err.Error() != addrError.Error() {
return false, err
}
// host string without any port specified
h = host
} else if len(p) == 0 {
return false, errors.New("invalid host name format")
}
// use ipv4 dotted decimal for further checking
if h == "localhost" {
h = "127.0.0.1"
}
ip := net.ParseIP(h)
return ip.IsLoopback(), nil
}
================================================
FILE: pkg/remote/remotes/docker/registry_test.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package docker
import "testing"
func TestHasCapability(t *testing.T) {
var (
pull = HostCapabilityPull
rslv = HostCapabilityResolve
push = HostCapabilityPush
all = pull | rslv | push
)
for i, tc := range []struct {
c HostCapabilities
t HostCapabilities
e bool
}{
{all, pull, true},
{all, pull | rslv, true},
{all, pull | push, true},
{all, all, true},
{pull, all, false},
{pull, push, false},
{rslv, pull, false},
{pull | rslv, push, false},
{pull | rslv, rslv, true},
} {
if a := tc.c.Has(tc.t); a != tc.e {
t.Fatalf("%d: failed, expected %t, got %t", i, tc.e, a)
}
}
}
func TestMatchLocalhost(t *testing.T) {
for _, tc := range []struct {
host string
match bool
}{
{"", false},
{"127.1.1.1", true},
{"127.0.0.1", true},
{"127.256.0.1", false}, // test MatchLocalhost does not panic on invalid ip
{"127.23.34.52", true},
{"127.0.0.1:5000", true},
{"registry.org", false},
{"126.example.com", false},
{"localhost", true},
{"localhost:5000", true},
{"[127:0:0:1]", false},
{"[::1]", true},
{"[::1]:", false}, // invalid ip
{"127.0.1.1:", false}, // invalid ip
{"[::1]:5000", true},
{"::1", true},
} {
actual, _ := MatchLocalhost(tc.host)
if actual != tc.match {
if tc.match {
t.Logf("Expected match for %s", tc.host)
} else {
t.Logf("Unexpected match for %s", tc.host)
}
t.Fail()
}
}
}
================================================
FILE: pkg/remote/remotes/docker/resolver.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package docker
import (
"context"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"path"
"strings"
"github.com/containerd/containerd/v2/core/images"
"github.com/containerd/containerd/v2/pkg/reference"
"github.com/containerd/containerd/v2/pkg/tracing"
"github.com/containerd/containerd/v2/version"
"github.com/containerd/errdefs"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/remote/remotes"
"github.com/containerd/nydus-snapshotter/pkg/remote/remotes/docker/schema1" //nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
remoteerrors "github.com/containerd/nydus-snapshotter/pkg/remote/remotes/errors"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"go.opentelemetry.io/otel/attribute"
)
var (
// ErrInvalidAuthorization is used when credentials are passed to a server but
// those credentials are rejected.
ErrInvalidAuthorization = errors.New("authorization failed")
// MaxManifestSize represents the largest size accepted from a registry
// during resolution. Larger manifests may be accepted using a
// resolution method other than the registry.
//
// NOTE: The max supported layers by some runtimes is 128 and individual
// layers will not contribute more than 256 bytes, making a
// reasonable limit for a large image manifests of 32K bytes.
// 4M bytes represents a much larger upper bound for images which may
// contain large annotations or be non-images. A proper manifest
// design puts large metadata in subobjects, as is consistent the
// intent of the manifest design.
MaxManifestSize int64 = 4 * 1048 * 1048
)
// Authorizer is used to authorize HTTP requests based on 401 HTTP responses.
// An Authorizer is responsible for caching tokens or credentials used by
// requests.
type Authorizer interface {
// Authorize sets the appropriate `Authorization` header on the given
// request.
//
// If no authorization is found for the request, the request remains
// unmodified. It may also add an `Authorization` header as
// "bearer "
// "basic "
//
// It may return remotes/errors.ErrUnexpectedStatus, which for example,
// can be used by the caller to find out the status code returned by the registry.
Authorize(context.Context, *http.Request) error
// AddResponses adds a 401 response for the authorizer to consider when
// authorizing requests. The last response should be unauthorized and
// the previous requests are used to consider redirects and retries
// that may have led to the 401.
//
// If response is not handled, returns `ErrNotImplemented`
AddResponses(context.Context, []*http.Response) error
}
// ResolverOptions are used to configured a new Docker register resolver
type ResolverOptions struct {
// Hosts returns registry host configurations for a namespace.
Hosts RegistryHosts
// Headers are the HTTP request header fields sent by the resolver
Headers http.Header
// Tracker is used to track uploads to the registry. This is used
// since the registry does not have upload tracking and the existing
// mechanism for getting blob upload status is expensive.
Tracker StatusTracker
// Authorizer is used to authorize registry requests
// Deprecated: use Hosts
Authorizer Authorizer
// Credentials provides username and secret given a host.
// If username is empty but a secret is given, that secret
// is interpreted as a long lived token.
// Deprecated: use Hosts
Credentials func(string) (string, string, error)
// Host provides the hostname given a namespace.
// Deprecated: use Hosts
Host func(string) (string, error)
// PlainHTTP specifies to use plain http and not https
// Deprecated: use Hosts
PlainHTTP bool
// Client is the http client to used when making registry requests
// Deprecated: use Hosts
Client *http.Client
}
// DefaultHost is the default host function.
func DefaultHost(ns string) (string, error) {
if ns == "docker.io" {
return "registry-1.docker.io", nil
}
return ns, nil
}
type dockerResolver struct {
hosts RegistryHosts
header http.Header
resolveHeader http.Header
tracker StatusTracker
}
// NewResolver returns a new resolver to a Docker registry
func NewResolver(options ResolverOptions) remotes.Resolver {
if options.Tracker == nil {
options.Tracker = NewInMemoryTracker()
}
if options.Headers == nil {
options.Headers = make(http.Header)
}
if _, ok := options.Headers["User-Agent"]; !ok {
options.Headers.Set("User-Agent", "containerd/"+version.Version)
}
resolveHeader := http.Header{}
if _, ok := options.Headers["Accept"]; !ok {
// set headers for all the types we support for resolution.
resolveHeader.Set("Accept", strings.Join([]string{
images.MediaTypeDockerSchema2Manifest,
images.MediaTypeDockerSchema2ManifestList,
ocispec.MediaTypeImageManifest,
ocispec.MediaTypeImageIndex, "*/*",
}, ", "))
} else {
resolveHeader["Accept"] = options.Headers["Accept"]
delete(options.Headers, "Accept")
}
if options.Hosts == nil {
opts := []RegistryOpt{}
if options.Host != nil {
opts = append(opts, WithHostTranslator(options.Host))
}
if options.Authorizer == nil {
options.Authorizer = NewDockerAuthorizer(
WithAuthClient(options.Client),
WithAuthHeader(options.Headers),
WithAuthCreds(options.Credentials))
}
opts = append(opts, WithAuthorizer(options.Authorizer))
if options.Client != nil {
opts = append(opts, WithClient(options.Client))
}
if options.PlainHTTP {
opts = append(opts, WithPlainHTTP(MatchAllHosts))
} else {
opts = append(opts, WithPlainHTTP(MatchLocalhost))
}
options.Hosts = ConfigureDefaultRegistries(opts...)
}
return &dockerResolver{
hosts: options.Hosts,
header: options.Headers,
resolveHeader: resolveHeader,
tracker: options.Tracker,
}
}
func getManifestMediaType(resp *http.Response) string {
// Strip encoding data (manifests should always be ascii JSON)
contentType := resp.Header.Get("Content-Type")
if sp := strings.IndexByte(contentType, ';'); sp != -1 {
contentType = contentType[0:sp]
}
// As of Apr 30 2019 the registry.access.redhat.com registry does not specify
// the content type of any data but uses schema1 manifests.
if contentType == "text/plain" {
contentType = images.MediaTypeDockerSchema1Manifest
}
return contentType
}
type countingReader struct {
reader io.Reader
bytesRead int64
}
func (r *countingReader) Read(p []byte) (int, error) {
n, err := r.reader.Read(p)
r.bytesRead += int64(n)
return n, err
}
var _ remotes.Resolver = &dockerResolver{}
func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocispec.Descriptor, error) {
base, err := r.resolveDockerBase(ref)
if err != nil {
return "", ocispec.Descriptor{}, err
}
refspec := base.refspec
if refspec.Object == "" {
return "", ocispec.Descriptor{}, reference.ErrObjectRequired
}
var (
firstErr error
paths [][]string
dgst = refspec.Digest()
caps = HostCapabilityPull
)
if dgst != "" {
if err := dgst.Validate(); err != nil {
// need to fail here, since we can't actually resolve the invalid
// digest.
return "", ocispec.Descriptor{}, err
}
// turns out, we have a valid digest, make a url.
paths = append(paths, []string{"manifests", dgst.String()})
// fallback to blobs on not found.
paths = append(paths, []string{"blobs", dgst.String()})
} else {
// Add
paths = append(paths, []string{"manifests", refspec.Object})
caps |= HostCapabilityResolve
}
hosts := base.filterHosts(caps)
if len(hosts) == 0 {
return "", ocispec.Descriptor{}, fmt.Errorf("no resolve hosts: %w", errdefs.ErrNotFound)
}
ctx, err = ContextWithRepositoryScope(ctx, refspec, false)
if err != nil {
return "", ocispec.Descriptor{}, err
}
for _, u := range paths {
for _, host := range hosts {
ctx := log.WithLogger(ctx, log.G(ctx).WithField("host", host.Host))
req := base.request(host, http.MethodHead, u...)
if err := req.addNamespace(base.refspec.Hostname()); err != nil {
return "", ocispec.Descriptor{}, err
}
for key, value := range r.resolveHeader {
req.header[key] = append(req.header[key], value...)
}
log.G(ctx).Debug("resolving")
resp, err := req.doWithRetries(ctx, nil)
if err != nil {
if errors.Is(err, ErrInvalidAuthorization) {
err = fmt.Errorf("pull access denied, repository does not exist or may require authorization: %w", err)
}
// Store the error for referencing later
if firstErr == nil {
firstErr = err
}
log.G(ctx).WithError(err).Info("trying next host")
continue // try another host
}
resp.Body.Close() // don't care about body contents.
if resp.StatusCode > 299 {
if resp.StatusCode == http.StatusNotFound {
log.G(ctx).Info("trying next host - response was http.StatusNotFound")
continue
}
if resp.StatusCode > 399 {
// Set firstErr when encountering the first non-404 status code.
if firstErr == nil {
firstErr = remoteerrors.NewUnexpectedStatusErr(resp)
}
continue // try another host
}
return "", ocispec.Descriptor{}, remoteerrors.NewUnexpectedStatusErr(resp)
}
size := resp.ContentLength
contentType := getManifestMediaType(resp)
// if no digest was provided, then only a resolve
// trusted registry was contacted, in this case use
// the digest header (or content from GET)
if dgst == "" {
// this is the only point at which we trust the registry. we use the
// content headers to assemble a descriptor for the name. when this becomes
// more robust, we mostly get this information from a secure trust store.
dgstHeader := digest.Digest(resp.Header.Get("Docker-Content-Digest"))
if dgstHeader != "" && size != -1 {
if err := dgstHeader.Validate(); err != nil {
return "", ocispec.Descriptor{}, fmt.Errorf("%q in header not a valid digest: %w", dgstHeader, err)
}
dgst = dgstHeader
}
}
if dgst == "" || size == -1 {
log.G(ctx).Debug("no Docker-Content-Digest header, fetching manifest instead")
req = base.request(host, http.MethodGet, u...)
if err := req.addNamespace(base.refspec.Hostname()); err != nil {
return "", ocispec.Descriptor{}, err
}
for key, value := range r.resolveHeader {
req.header[key] = append(req.header[key], value...)
}
resp, err := req.doWithRetries(ctx, nil)
if err != nil {
return "", ocispec.Descriptor{}, err
}
bodyReader := countingReader{reader: resp.Body}
contentType = getManifestMediaType(resp)
err = func() error {
defer resp.Body.Close()
if dgst != "" {
_, err = io.Copy(io.Discard, &bodyReader)
return err
}
if contentType == images.MediaTypeDockerSchema1Manifest {
b, err := schema1.ReadStripSignature(&bodyReader)
if err != nil {
return err
}
dgst = digest.FromBytes(b)
return nil
}
dgst, err = digest.FromReader(&bodyReader)
return err
}()
if err != nil {
return "", ocispec.Descriptor{}, err
}
size = bodyReader.bytesRead
}
// Prevent resolving to excessively large manifests
if size > MaxManifestSize {
if firstErr == nil {
firstErr = fmt.Errorf("rejecting %d byte manifest for %s: %w", size, ref, errdefs.ErrNotFound)
}
continue
}
desc := ocispec.Descriptor{
Digest: dgst,
MediaType: contentType,
Size: size,
}
log.G(ctx).WithField("desc.digest", desc.Digest).Debug("resolved")
return ref, desc, nil
}
}
// If above loop terminates without return, then there was an error.
// "firstErr" contains the first non-404 error. That is, "firstErr == nil"
// means that either no registries were given or each registry returned 404.
if firstErr == nil {
firstErr = fmt.Errorf("%s: %w", ref, errdefs.ErrNotFound)
}
return "", ocispec.Descriptor{}, firstErr
}
func (r *dockerResolver) Fetcher(ctx context.Context, ref string) (remotes.Fetcher, error) {
base, err := r.resolveDockerBase(ref)
if err != nil {
return nil, err
}
return dockerFetcher{
dockerBase: base,
}, nil
}
func (r *dockerResolver) Pusher(ctx context.Context, ref string) (remotes.Pusher, error) {
base, err := r.resolveDockerBase(ref)
if err != nil {
return nil, err
}
return dockerPusher{
dockerBase: base,
object: base.refspec.Object,
tracker: r.tracker,
}, nil
}
func (r *dockerResolver) resolveDockerBase(ref string) (*dockerBase, error) {
refspec, err := reference.Parse(ref)
if err != nil {
return nil, err
}
return r.base(refspec)
}
type dockerBase struct {
refspec reference.Spec
repository string
hosts []RegistryHost
header http.Header
}
func (r *dockerResolver) base(refspec reference.Spec) (*dockerBase, error) {
host := refspec.Hostname()
hosts, err := r.hosts(host)
if err != nil {
return nil, err
}
return &dockerBase{
refspec: refspec,
repository: strings.TrimPrefix(refspec.Locator, host+"/"),
hosts: hosts,
header: r.header,
}, nil
}
// filterHosts returns a set of hosts matching the given capabilities
// Multiple arguments are treated as "OR"
// Each argument may be a set of capabilities in which all must be satisfied to match
func (r *dockerBase) filterHosts(capsets ...HostCapabilities) (hosts []RegistryHost) {
for _, host := range r.hosts {
for _, caps := range capsets {
if host.Capabilities.Has(caps) {
hosts = append(hosts, host)
break
}
}
}
return
}
func (r *dockerBase) request(host RegistryHost, method string, ps ...string) *request {
header := r.header.Clone()
if header == nil {
header = http.Header{}
}
for key, value := range host.Header {
header[key] = append(header[key], value...)
}
parts := append([]string{"/", host.Path, r.repository}, ps...)
p := path.Join(parts...)
// Join strips trailing slash, re-add ending "/" if included
if len(parts) > 0 && strings.HasSuffix(parts[len(parts)-1], "/") {
p = p + "/"
}
return &request{
method: method,
path: p,
header: header,
host: host,
}
}
func (r *request) authorize(ctx context.Context, req *http.Request) error {
// Check if has header for host
if r.host.Authorizer != nil {
if err := r.host.Authorizer.Authorize(ctx, req); err != nil {
return err
}
}
return nil
}
func (r *request) addQuery(key, value string) (err error) {
var q url.Values
// Parse query
if i := strings.IndexByte(r.path, '?'); i > 0 {
r.path = r.path[:i+1]
q, err = url.ParseQuery(r.path[i+1:])
if err != nil {
return
}
} else {
r.path = r.path + "?"
q = url.Values{}
}
q.Add(key, value)
r.path = r.path + q.Encode()
return
}
func (r *request) addNamespace(ns string) error {
if !r.host.isProxy(ns) {
return nil
}
return r.addQuery("ns", ns)
}
type request struct {
method string
path string
header http.Header
host RegistryHost
body func() (io.ReadCloser, error)
size int64
}
func (r *request) do(ctx context.Context) (*http.Response, error) {
u := r.host.Scheme + "://" + r.host.Host + r.path
req, err := http.NewRequestWithContext(ctx, r.method, u, nil)
if err != nil {
return nil, err
}
req.Header = http.Header{} // headers need to be copied to avoid concurrent map access
for k, v := range r.header {
req.Header[k] = v
}
if r.body != nil {
body, err := r.body()
if err != nil {
return nil, err
}
req.Body = body
req.GetBody = r.body
if r.size > 0 {
req.ContentLength = r.size
}
}
ctx = log.WithLogger(ctx, log.G(ctx).WithField("url", u))
log.G(ctx).WithFields(requestFields(req)).Debug("do request")
if err := r.authorize(ctx, req); err != nil {
return nil, fmt.Errorf("failed to authorize: %w", err)
}
client := &http.Client{}
if r.host.Client != nil {
*client = *r.host.Client
}
if client.CheckRedirect == nil {
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
if len(via) >= 10 {
return errors.New("stopped after 10 redirects")
}
if err := r.authorize(ctx, req); err != nil {
return fmt.Errorf("failed to authorize redirect: %w", err)
}
return nil
}
}
_, httpSpan := tracing.StartSpan(
ctx,
tracing.Name("remotes.docker.resolver", "HTTPRequest"),
)
defer httpSpan.End()
resp, err := client.Do(req)
if err != nil {
httpSpan.SetStatus(err)
return nil, fmt.Errorf("failed to do request: %w", err)
}
httpSpan.SetAttributes(
attribute.Int("http.response.status_code", resp.StatusCode),
attribute.Int("http.status_code", resp.StatusCode), // Deprecated: SemConv <= v1.21
)
log.G(ctx).WithFields(responseFields(resp)).Debug("fetch response received")
return resp, nil
}
func (r *request) doWithRetries(ctx context.Context, responses []*http.Response) (*http.Response, error) {
resp, err := r.do(ctx)
if err != nil {
return nil, err
}
responses = append(responses, resp)
retry, err := r.retryRequest(ctx, responses)
if err != nil {
resp.Body.Close()
return nil, err
}
if retry {
resp.Body.Close()
return r.doWithRetries(ctx, responses)
}
return resp, err
}
func (r *request) retryRequest(ctx context.Context, responses []*http.Response) (bool, error) {
if len(responses) > 5 {
return false, nil
}
last := responses[len(responses)-1]
switch last.StatusCode {
case http.StatusUnauthorized:
log.G(ctx).WithField("header", last.Header.Get("WWW-Authenticate")).Debug("Unauthorized")
if r.host.Authorizer != nil {
if err := r.host.Authorizer.AddResponses(ctx, responses); err == nil {
return true, nil
} else if !errdefs.IsNotImplemented(err) {
return false, err
}
}
return false, nil
case http.StatusMethodNotAllowed:
// Support registries which have not properly implemented the HEAD method for
// manifests endpoint
if r.method == http.MethodHead && strings.Contains(r.path, "/manifests/") {
r.method = http.MethodGet
return true, nil
}
case http.StatusRequestTimeout, http.StatusTooManyRequests:
return true, nil
}
// TODO: Handle 50x errors accounting for attempt history
return false, nil
}
func (r *request) String() string {
return r.host.Scheme + "://" + r.host.Host + r.path
}
func requestFields(req *http.Request) log.Fields {
fields := map[string]interface{}{
"request.method": req.Method,
}
for k, vals := range req.Header {
k = strings.ToLower(k)
if k == "authorization" {
continue
}
for i, v := range vals {
field := "request.header." + k
if i > 0 {
field = fmt.Sprintf("%s.%d", field, i)
}
fields[field] = v
}
}
return log.Fields(fields)
}
func responseFields(resp *http.Response) log.Fields {
fields := map[string]interface{}{
"response.status": resp.Status,
}
for k, vals := range resp.Header {
k = strings.ToLower(k)
for i, v := range vals {
field := "response.header." + k
if i > 0 {
field = fmt.Sprintf("%s.%d", field, i)
}
fields[field] = v
}
}
return log.Fields(fields)
}
// IsLocalhost checks if the registry host is local.
func IsLocalhost(host string) bool {
if h, _, err := net.SplitHostPort(host); err == nil {
host = h
}
if host == "localhost" {
return true
}
ip := net.ParseIP(host)
return ip.IsLoopback()
}
================================================
FILE: pkg/remote/remotes/docker/resolver_test.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package docker
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
"time"
"github.com/containerd/nydus-snapshotter/pkg/remote/remotes"
"github.com/containerd/nydus-snapshotter/pkg/remote/remotes/docker/auth"
digest "github.com/opencontainers/go-digest"
specs "github.com/opencontainers/image-spec/specs-go"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
func TestHTTPResolver(t *testing.T) {
s := func(h http.Handler) (string, ResolverOptions, func()) {
s := httptest.NewServer(h)
options := ResolverOptions{}
base := s.URL[7:] // strip "http://"
return base, options, s.Close
}
runBasicTest(t, "testname", s)
}
func TestHTTPSResolver(t *testing.T) {
runBasicTest(t, "testname", tlsServer)
}
func TestBasicResolver(t *testing.T) {
basicAuth := func(h http.Handler) (string, ResolverOptions, func()) {
// Wrap with basic auth
wrapped := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok || username != "user1" || password != "password1" {
rw.Header().Set("WWW-Authenticate", "Basic realm=localhost")
rw.WriteHeader(http.StatusUnauthorized)
return
}
h.ServeHTTP(rw, r)
})
base, options, close := tlsServer(wrapped)
authorizer := NewDockerAuthorizer(
WithAuthClient(options.Client),
WithAuthCreds(func(host string) (string, string, error) {
return "user1", "password1", nil
}),
)
options.Hosts = ConfigureDefaultRegistries(
WithClient(options.Client),
WithAuthorizer(authorizer),
)
return base, options, close
}
runBasicTest(t, "testname", basicAuth)
}
func TestAnonymousTokenResolver(t *testing.T) {
th := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
rw.WriteHeader(http.StatusMethodNotAllowed)
return
}
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(http.StatusOK)
rw.Write([]byte(`{"access_token":"perfectlyvalidopaquetoken"}`))
})
runBasicTest(t, "testname", withTokenServer(th, nil))
}
func TestBasicAuthTokenResolver(t *testing.T) {
th := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
rw.WriteHeader(http.StatusMethodNotAllowed)
return
}
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(http.StatusOK)
username, password, ok := r.BasicAuth()
if !ok || username != "user1" || password != "password1" {
rw.Write([]byte(`{"access_token":"insufficientscope"}`))
} else {
rw.Write([]byte(`{"access_token":"perfectlyvalidopaquetoken"}`))
}
})
creds := func(string) (string, string, error) {
return "user1", "password1", nil
}
runBasicTest(t, "testname", withTokenServer(th, creds))
}
func TestRefreshTokenResolver(t *testing.T) {
th := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
rw.WriteHeader(http.StatusMethodNotAllowed)
return
}
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(http.StatusOK)
r.ParseForm()
if r.PostForm.Get("grant_type") != "refresh_token" || r.PostForm.Get("refresh_token") != "somerefreshtoken" {
rw.Write([]byte(`{"access_token":"insufficientscope"}`))
} else {
rw.Write([]byte(`{"access_token":"perfectlyvalidopaquetoken"}`))
}
})
creds := func(string) (string, string, error) {
return "", "somerefreshtoken", nil
}
runBasicTest(t, "testname", withTokenServer(th, creds))
}
func TestFetchRefreshToken(t *testing.T) {
f := func(t *testing.T, disablePOST bool) {
name := "testname"
if disablePOST {
name += "-disable-post"
}
var fetchedRefreshToken string
onFetchRefreshToken := func(ctx context.Context, refreshToken string, req *http.Request) {
fetchedRefreshToken = refreshToken
}
srv := newRefreshTokenServer(t, name, disablePOST, onFetchRefreshToken)
runBasicTest(t, name, srv.BasicTestFunc())
if fetchedRefreshToken != srv.RefreshToken {
t.Errorf("unexpected refresh token: got %q", fetchedRefreshToken)
}
}
t.Run("POST", func(t *testing.T) {
f(t, false)
})
t.Run("GET", func(t *testing.T) {
f(t, true)
})
}
func TestPostBasicAuthTokenResolver(t *testing.T) {
th := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
rw.WriteHeader(http.StatusMethodNotAllowed)
return
}
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(http.StatusOK)
r.ParseForm()
if r.PostForm.Get("grant_type") != "password" || r.PostForm.Get("username") != "user1" || r.PostForm.Get("password") != "password1" {
rw.Write([]byte(`{"access_token":"insufficientscope"}`))
} else {
rw.Write([]byte(`{"access_token":"perfectlyvalidopaquetoken"}`))
}
})
creds := func(string) (string, string, error) {
return "user1", "password1", nil
}
runBasicTest(t, "testname", withTokenServer(th, creds))
}
func TestBadTokenResolver(t *testing.T) {
th := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
rw.WriteHeader(http.StatusMethodNotAllowed)
return
}
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(http.StatusOK)
rw.Write([]byte(`{"access_token":"insufficientscope"}`))
})
creds := func(string) (string, string, error) {
return "", "somerefreshtoken", nil
}
ctx := context.Background()
h := newContent(ocispec.MediaTypeImageManifest, []byte("not anything parse-able"))
base, ro, close := withTokenServer(th, creds)(logHandler{t, h})
defer close()
resolver := NewResolver(ro)
image := fmt.Sprintf("%s/doesntmatter:sometatg", base)
_, _, err := resolver.Resolve(ctx, image)
if err == nil {
t.Fatal("Expected error getting token with inssufficient scope")
}
if !errors.Is(err, ErrInvalidAuthorization) {
t.Fatal(err)
}
}
func TestHostFailureFallbackResolver(t *testing.T) {
sf := func(h http.Handler) (string, ResolverOptions, func()) {
s := httptest.NewServer(h)
base := s.URL[7:] // strip "http://"
options := ResolverOptions{}
createHost := func(host string) RegistryHost {
return RegistryHost{
Client: &http.Client{
// Set the timeout so we timeout waiting for the non-responsive HTTP server
Timeout: 500 * time.Millisecond,
},
Host: host,
Scheme: "http",
Path: "/v2",
Capabilities: HostCapabilityPull | HostCapabilityResolve | HostCapabilityPush,
}
}
// Create an unstarted HTTP server. We use this to generate a random port.
notRunning := httptest.NewUnstartedServer(nil)
notRunningBase := notRunning.Listener.Addr().String()
// Override hosts with two hosts
options.Hosts = func(host string) ([]RegistryHost, error) {
return []RegistryHost{
createHost(notRunningBase), // This host IS running, but with a non-responsive HTTP server
createHost(base), // This host IS running
}, nil
}
return base, options, s.Close
}
runBasicTest(t, "testname", sf)
}
func TestHostTLSFailureFallbackResolver(t *testing.T) {
sf := func(h http.Handler) (string, ResolverOptions, func()) {
// Start up two servers
server := httptest.NewServer(h)
httpBase := server.URL[7:] // strip "http://"
tlsServer := httptest.NewUnstartedServer(h)
tlsServer.StartTLS()
httpsBase := tlsServer.URL[8:] // strip "https://"
capool := x509.NewCertPool()
cert, _ := x509.ParseCertificate(tlsServer.TLS.Certificates[0].Certificate[0])
capool.AddCert(cert)
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: capool,
},
},
}
options := ResolverOptions{}
createHost := func(host string) RegistryHost {
return RegistryHost{
Client: client,
Host: host,
Scheme: "https",
Path: "/v2",
Capabilities: HostCapabilityPull | HostCapabilityResolve | HostCapabilityPush,
}
}
// Override hosts with two hosts
options.Hosts = func(host string) ([]RegistryHost, error) {
return []RegistryHost{
createHost(httpBase), // This host is serving plain HTTP
createHost(httpsBase), // This host is serving TLS
}, nil
}
return httpBase, options, func() {
server.Close()
tlsServer.Close()
}
}
runBasicTest(t, "testname", sf)
}
func TestResolveProxy(t *testing.T) {
var (
ctx = context.Background()
tag = "latest"
r = http.NewServeMux()
name = "testname"
ns = "upstream.example.com"
)
m := newManifest(
newContent(ocispec.MediaTypeImageConfig, []byte("1")),
newContent(ocispec.MediaTypeImageLayerGzip, []byte("2")),
)
mc := newContent(ocispec.MediaTypeImageManifest, m.OCIManifest())
m.RegisterHandler(r, name)
r.Handle(fmt.Sprintf("/v2/%s/manifests/%s", name, tag), mc)
r.Handle(fmt.Sprintf("/v2/%s/manifests/%s", name, mc.Digest()), mc)
nr := namespaceRouter{
"upstream.example.com": r,
}
base, ro, close := tlsServer(logHandler{t, nr})
defer close()
ro.Hosts = func(host string) ([]RegistryHost, error) {
return []RegistryHost{{
Client: ro.Client,
Host: base,
Scheme: "https",
Path: "/v2",
Capabilities: HostCapabilityPull | HostCapabilityResolve,
}}, nil
}
resolver := NewResolver(ro)
image := fmt.Sprintf("%s/%s:%s", ns, name, tag)
_, d, err := resolver.Resolve(ctx, image)
if err != nil {
t.Fatal(err)
}
f, err := resolver.Fetcher(ctx, image)
if err != nil {
t.Fatal(err)
}
refs, err := testocimanifest(ctx, f, d)
if err != nil {
t.Fatal(err)
}
if len(refs) != 2 {
t.Fatalf("Unexpected number of references: %d, expected 2", len(refs))
}
for _, ref := range refs {
if err := testFetch(ctx, f, ref); err != nil {
t.Fatal(err)
}
}
}
func TestResolveProxyFallback(t *testing.T) {
var (
ctx = context.Background()
tag = "latest"
r = http.NewServeMux()
name = "testname"
)
m := newManifest(
newContent(ocispec.MediaTypeImageConfig, []byte("1")),
newContent(ocispec.MediaTypeImageLayerGzip, []byte("2")),
)
mc := newContent(ocispec.MediaTypeImageManifest, m.OCIManifest())
m.RegisterHandler(r, name)
r.Handle(fmt.Sprintf("/v2/%s/manifests/%s", name, tag), mc)
r.Handle(fmt.Sprintf("/v2/%s/manifests/%s", name, mc.Digest()), mc)
nr := namespaceRouter{
"": r,
}
s := httptest.NewServer(logHandler{t, nr})
defer s.Close()
base := s.URL[7:] // strip "http://"
ro := ResolverOptions{
Hosts: func(host string) ([]RegistryHost, error) {
return []RegistryHost{
{
Host: flipLocalhost(host),
Scheme: "http",
Path: "/v2",
Capabilities: HostCapabilityPull | HostCapabilityResolve,
},
{
Host: host,
Scheme: "http",
Path: "/v2",
Capabilities: HostCapabilityPull | HostCapabilityResolve | HostCapabilityPush,
},
}, nil
},
}
resolver := NewResolver(ro)
image := fmt.Sprintf("%s/%s:%s", base, name, tag)
_, d, err := resolver.Resolve(ctx, image)
if err != nil {
t.Fatal(err)
}
f, err := resolver.Fetcher(ctx, image)
if err != nil {
t.Fatal(err)
}
refs, err := testocimanifest(ctx, f, d)
if err != nil {
t.Fatal(err)
}
if len(refs) != 2 {
t.Fatalf("Unexpected number of references: %d, expected 2", len(refs))
}
for _, ref := range refs {
if err := testFetch(ctx, f, ref); err != nil {
t.Fatal(err)
}
}
}
func flipLocalhost(host string) string {
if strings.HasPrefix(host, "127.0.0.1") {
return "localhost" + host[9:]
} else if strings.HasPrefix(host, "localhost") {
return "127.0.0.1" + host[9:]
}
return host
}
func withTokenServer(th http.Handler, creds func(string) (string, string, error)) func(h http.Handler) (string, ResolverOptions, func()) {
return func(h http.Handler) (string, ResolverOptions, func()) {
s := httptest.NewUnstartedServer(th)
s.StartTLS()
cert, _ := x509.ParseCertificate(s.TLS.Certificates[0].Certificate[0])
tokenBase := s.URL + "/token"
// Wrap with token auth
wrapped := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
auth := strings.ToLower(r.Header.Get("Authorization"))
if auth != "bearer perfectlyvalidopaquetoken" {
authHeader := fmt.Sprintf("Bearer realm=%q,service=registry,scope=\"repository:testname:pull,pull\"", tokenBase)
if strings.HasPrefix(auth, "bearer ") {
authHeader = authHeader + ",error=" + auth[7:]
}
rw.Header().Set("WWW-Authenticate", authHeader)
rw.WriteHeader(http.StatusUnauthorized)
return
}
h.ServeHTTP(rw, r)
})
base, options, close := tlsServer(wrapped)
options.Hosts = ConfigureDefaultRegistries(
WithClient(options.Client),
WithAuthorizer(NewDockerAuthorizer(
WithAuthClient(options.Client),
WithAuthCreds(creds),
)),
)
options.Client.Transport.(*http.Transport).TLSClientConfig.RootCAs.AddCert(cert)
return base, options, func() {
s.Close()
close()
}
}
}
func tlsServer(h http.Handler) (string, ResolverOptions, func()) {
s := httptest.NewUnstartedServer(h)
s.StartTLS()
capool := x509.NewCertPool()
cert, _ := x509.ParseCertificate(s.TLS.Certificates[0].Certificate[0])
capool.AddCert(cert)
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: capool,
},
},
}
options := ResolverOptions{
Hosts: ConfigureDefaultRegistries(WithClient(client)),
// Set deprecated field for tests to use for configuration
Client: client,
}
base := s.URL[8:] // strip "https://"
return base, options, s.Close
}
type logHandler struct {
t *testing.T
handler http.Handler
}
func (h logHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
h.handler.ServeHTTP(rw, r)
}
type namespaceRouter map[string]http.Handler
func (nr namespaceRouter) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
h, ok := nr[r.URL.Query().Get("ns")]
if !ok {
rw.WriteHeader(http.StatusNotFound)
return
}
h.ServeHTTP(rw, r)
}
func runBasicTest(t *testing.T, name string, sf func(h http.Handler) (string, ResolverOptions, func())) {
var (
ctx = context.Background()
tag = "latest"
r = http.NewServeMux()
)
m := newManifest(
newContent(ocispec.MediaTypeImageConfig, []byte("1")),
newContent(ocispec.MediaTypeImageLayerGzip, []byte("2")),
)
mc := newContent(ocispec.MediaTypeImageManifest, m.OCIManifest())
m.RegisterHandler(r, name)
r.Handle(fmt.Sprintf("/v2/%s/manifests/%s", name, tag), mc)
r.Handle(fmt.Sprintf("/v2/%s/manifests/%s", name, mc.Digest()), mc)
base, ro, close := sf(logHandler{t, r})
defer close()
resolver := NewResolver(ro)
image := fmt.Sprintf("%s/%s:%s", base, name, tag)
_, d, err := resolver.Resolve(ctx, image)
if err != nil {
t.Fatal(err)
}
f, err := resolver.Fetcher(ctx, image)
if err != nil {
t.Fatal(err)
}
refs, err := testocimanifest(ctx, f, d)
if err != nil {
t.Fatal(err)
}
if len(refs) != 2 {
t.Fatalf("Unexpected number of references: %d, expected 2", len(refs))
}
for _, ref := range refs {
if err := testFetch(ctx, f, ref); err != nil {
t.Fatal(err)
}
}
}
func testFetch(ctx context.Context, f remotes.Fetcher, desc ocispec.Descriptor) error {
r, err := f.Fetch(ctx, desc)
if err != nil {
return err
}
dgstr := desc.Digest.Algorithm().Digester()
io.Copy(dgstr.Hash(), r)
if dgstr.Digest() != desc.Digest {
return fmt.Errorf("content mismatch: %s != %s", dgstr.Digest(), desc.Digest)
}
fByDigest, ok := f.(remotes.FetcherByDigest)
if !ok {
return fmt.Errorf("fetcher %T does not implement FetcherByDigest", f)
}
r2, desc2, err := fByDigest.FetchByDigest(ctx, desc.Digest)
if err != nil {
return fmt.Errorf("FetcherByDigest: faild to fetch %v: %w", desc.Digest, err)
}
if desc2.Size != desc.Size {
r2b, err := io.ReadAll(r2)
if err != nil {
return fmt.Errorf("FetcherByDigest: size mismatch: %d != %d (content: %v)", desc2.Size, desc.Size, err)
}
return fmt.Errorf("FetcherByDigest: size mismatch: %d != %d (content: %q)", desc2.Size, desc.Size, string(r2b))
}
dgstr2 := desc.Digest.Algorithm().Digester()
if _, err = io.Copy(dgstr2.Hash(), r2); err != nil {
return fmt.Errorf("FetcherByDigest: faild to copy: %w", err)
}
if dgstr2.Digest() != desc.Digest {
return fmt.Errorf("FetcherByDigest: content mismatch: %s != %s", dgstr2.Digest(), desc.Digest)
}
return nil
}
func testocimanifest(ctx context.Context, f remotes.Fetcher, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
r, err := f.Fetch(ctx, desc)
if err != nil {
return nil, fmt.Errorf("failed to fetch %s: %w", desc.Digest, err)
}
p, err := io.ReadAll(r)
if err != nil {
return nil, err
}
if dgst := desc.Digest.Algorithm().FromBytes(p); dgst != desc.Digest {
return nil, fmt.Errorf("digest mismatch: %s != %s", dgst, desc.Digest)
}
var manifest ocispec.Manifest
if err := json.Unmarshal(p, &manifest); err != nil {
return nil, err
}
var descs []ocispec.Descriptor
descs = append(descs, manifest.Config)
descs = append(descs, manifest.Layers...)
return descs, nil
}
type testContent struct {
mediaType string
content []byte
}
func newContent(mediaType string, b []byte) testContent {
return testContent{
mediaType: mediaType,
content: b,
}
}
func (tc testContent) Descriptor() ocispec.Descriptor {
return ocispec.Descriptor{
MediaType: tc.mediaType,
Digest: digest.FromBytes(tc.content),
Size: int64(len(tc.content)),
}
}
func (tc testContent) Digest() digest.Digest {
return digest.FromBytes(tc.content)
}
func (tc testContent) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", tc.mediaType)
w.Header().Add("Content-Length", strconv.Itoa(len(tc.content)))
w.Header().Add("Docker-Content-Digest", tc.Digest().String())
w.WriteHeader(http.StatusOK)
w.Write(tc.content)
}
type testManifest struct {
config testContent
references []testContent
}
func newManifest(config testContent, refs ...testContent) testManifest {
return testManifest{
config: config,
references: refs,
}
}
func (m testManifest) OCIManifest() []byte {
manifest := ocispec.Manifest{
Versioned: specs.Versioned{
SchemaVersion: 1,
},
Config: m.config.Descriptor(),
Layers: make([]ocispec.Descriptor, len(m.references)),
}
for i, c := range m.references {
manifest.Layers[i] = c.Descriptor()
}
b, _ := json.Marshal(manifest)
return b
}
func (m testManifest) RegisterHandler(r *http.ServeMux, name string) {
for _, c := range append(m.references, m.config) {
r.Handle(fmt.Sprintf("/v2/%s/blobs/%s", name, c.Digest()), c)
}
}
func newRefreshTokenServer(t testing.TB, name string, disablePOST bool, onFetchRefreshToken OnFetchRefreshToken) *refreshTokenServer {
return &refreshTokenServer{
T: t,
Name: name,
DisablePOST: disablePOST,
OnFetchRefreshToken: onFetchRefreshToken,
AccessToken: "testAccessToken-" + name,
RefreshToken: "testRefreshToken-" + name,
Username: "testUser-" + name,
Password: "testPassword-" + name,
}
}
type refreshTokenServer struct {
T testing.TB
Name string
DisablePOST bool
OnFetchRefreshToken OnFetchRefreshToken
AccessToken string
RefreshToken string
Username string
Password string
}
func (srv *refreshTokenServer) isValidAuthorizationHeader(s string) bool {
fields := strings.Fields(s)
return len(fields) == 2 && strings.ToLower(fields[0]) == "bearer" && (fields[1] == srv.RefreshToken || fields[1] == srv.AccessToken)
}
func (srv *refreshTokenServer) BasicTestFunc() func(h http.Handler) (string, ResolverOptions, func()) {
t := srv.T
return func(h http.Handler) (string, ResolverOptions, func()) {
wrapped := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/token" {
if !srv.isValidAuthorizationHeader(r.Header.Get("Authorization")) {
realm := fmt.Sprintf("https://%s/token", r.Host)
wwwAuthenticateHeader := fmt.Sprintf("Bearer realm=%q,service=registry,scope=\"repository:%s:pull\"", realm, srv.Name)
rw.Header().Set("WWW-Authenticate", wwwAuthenticateHeader)
rw.WriteHeader(http.StatusUnauthorized)
return
}
h.ServeHTTP(rw, r)
return
}
switch r.Method {
case http.MethodGet: // https://docs.docker.com/registry/spec/auth/token/#requesting-a-token
u, p, ok := r.BasicAuth()
if !ok || u != srv.Username || p != srv.Password {
rw.WriteHeader(http.StatusForbidden)
return
}
var resp auth.FetchTokenResponse
resp.Token = srv.AccessToken
resp.AccessToken = srv.AccessToken // alias of Token
query := r.URL.Query()
switch query.Get("offline_token") {
case "true":
resp.RefreshToken = srv.RefreshToken
case "false", "":
default:
rw.WriteHeader(http.StatusBadRequest)
return
}
b, err := json.Marshal(resp)
if err != nil {
rw.WriteHeader(http.StatusInternalServerError)
return
}
rw.WriteHeader(http.StatusOK)
rw.Header().Set("Content-Type", "application/json")
t.Logf("GET mode: returning JSON %q, for query %+v", string(b), query)
rw.Write(b)
case http.MethodPost: // https://docs.docker.com/registry/spec/auth/oauth/#getting-a-token
if srv.DisablePOST {
rw.WriteHeader(http.StatusMethodNotAllowed)
return
}
r.ParseForm()
pf := r.PostForm
if pf.Get("grant_type") != "password" {
rw.WriteHeader(http.StatusBadRequest)
return
}
if pf.Get("username") != srv.Username || pf.Get("password") != srv.Password {
rw.WriteHeader(http.StatusForbidden)
return
}
var resp auth.OAuthTokenResponse
resp.AccessToken = srv.AccessToken
switch pf.Get("access_type") {
case "offline":
resp.RefreshToken = srv.RefreshToken
case "online", "":
default:
rw.WriteHeader(http.StatusBadRequest)
return
}
b, err := json.Marshal(resp)
if err != nil {
rw.WriteHeader(http.StatusInternalServerError)
return
}
rw.WriteHeader(http.StatusOK)
rw.Header().Set("Content-Type", "application/json")
t.Logf("POST mode: returning JSON %q, for form %+v", string(b), pf)
rw.Write(b)
default:
rw.WriteHeader(http.StatusMethodNotAllowed)
return
}
})
base, options, close := tlsServer(wrapped)
authorizer := NewDockerAuthorizer(
WithAuthClient(options.Client),
WithAuthCreds(func(string) (string, string, error) {
return srv.Username, srv.Password, nil
}),
WithFetchRefreshToken(srv.OnFetchRefreshToken),
)
options.Hosts = ConfigureDefaultRegistries(
WithClient(options.Client),
WithAuthorizer(authorizer),
)
return base, options, close
}
}
================================================
FILE: pkg/remote/remotes/docker/schema1/converter.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package schema1 provides a converter to fetch an image formatted in Docker Image Manifest v2, Schema 1.
//
// Deprecated: use images formatted in Docker Image Manifest v2, Schema 2, or OCI Image Spec v1.
package schema1
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"strconv"
"strings"
"sync"
"time"
"github.com/containerd/containerd/v2/core/content"
"github.com/containerd/containerd/v2/core/images"
"github.com/containerd/containerd/v2/pkg/archive/compression"
"github.com/containerd/containerd/v2/pkg/labels"
"github.com/containerd/errdefs"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/remote/remotes"
digest "github.com/opencontainers/go-digest"
specs "github.com/opencontainers/image-spec/specs-go"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"golang.org/x/sync/errgroup"
)
const (
manifestSizeLimit = 8e6 // 8MB
labelDockerSchema1EmptyLayer = "containerd.io/docker.schema1.empty-layer"
)
type blobState struct {
diffID digest.Digest
empty bool
}
// Converter converts schema1 manifests to schema2 on fetch
type Converter struct {
contentStore content.Store
fetcher remotes.Fetcher
pulledManifest *manifest
mu sync.Mutex
blobMap map[digest.Digest]blobState
layerBlobs map[digest.Digest]ocispec.Descriptor
}
// NewConverter returns a new converter
func NewConverter(contentStore content.Store, fetcher remotes.Fetcher) *Converter {
return &Converter{
contentStore: contentStore,
fetcher: fetcher,
blobMap: map[digest.Digest]blobState{},
layerBlobs: map[digest.Digest]ocispec.Descriptor{},
}
}
// Handle fetching descriptors for a docker media type
func (c *Converter) Handle(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
switch desc.MediaType {
case images.MediaTypeDockerSchema1Manifest:
if err := c.fetchManifest(ctx, desc); err != nil {
return nil, err
}
m := c.pulledManifest
if len(m.FSLayers) != len(m.History) {
return nil, errors.New("invalid schema 1 manifest, history and layer mismatch")
}
descs := make([]ocispec.Descriptor, 0, len(c.pulledManifest.FSLayers))
for i := range m.FSLayers {
if _, ok := c.blobMap[c.pulledManifest.FSLayers[i].BlobSum]; !ok {
empty, err := isEmptyLayer([]byte(m.History[i].V1Compatibility))
if err != nil {
return nil, err
}
// Do no attempt to download a known empty blob
if !empty {
descs = append([]ocispec.Descriptor{
{
MediaType: images.MediaTypeDockerSchema2LayerGzip,
Digest: c.pulledManifest.FSLayers[i].BlobSum,
Size: -1,
},
}, descs...)
}
c.blobMap[c.pulledManifest.FSLayers[i].BlobSum] = blobState{
empty: empty,
}
}
}
return descs, nil
case images.MediaTypeDockerSchema2LayerGzip:
if c.pulledManifest == nil {
return nil, errors.New("manifest required for schema 1 blob pull")
}
return nil, c.fetchBlob(ctx, desc)
default:
return nil, fmt.Errorf("%v not support for schema 1 manifests", desc.MediaType)
}
}
// ConvertOptions provides options on converting a docker schema1 manifest.
type ConvertOptions struct {
// ManifestMediaType specifies the media type of the manifest OCI descriptor.
ManifestMediaType string
// ConfigMediaType specifies the media type of the manifest config OCI
// descriptor.
ConfigMediaType string
}
// ConvertOpt allows configuring a convert operation.
type ConvertOpt func(context.Context, *ConvertOptions) error
// UseDockerSchema2 is used to indicate that a schema1 manifest should be
// converted into the media types for a docker schema2 manifest.
func UseDockerSchema2() ConvertOpt {
return func(ctx context.Context, o *ConvertOptions) error {
o.ManifestMediaType = images.MediaTypeDockerSchema2Manifest
o.ConfigMediaType = images.MediaTypeDockerSchema2Config
return nil
}
}
// Convert a docker manifest to an OCI descriptor
func (c *Converter) Convert(ctx context.Context, opts ...ConvertOpt) (ocispec.Descriptor, error) {
co := ConvertOptions{
ManifestMediaType: ocispec.MediaTypeImageManifest,
ConfigMediaType: ocispec.MediaTypeImageConfig,
}
for _, opt := range opts {
if err := opt(ctx, &co); err != nil {
return ocispec.Descriptor{}, err
}
}
history, diffIDs, err := c.schema1ManifestHistory()
if err != nil {
return ocispec.Descriptor{}, fmt.Errorf("schema 1 conversion failed: %w", err)
}
var img ocispec.Image
if err := json.Unmarshal([]byte(c.pulledManifest.History[0].V1Compatibility), &img); err != nil {
return ocispec.Descriptor{}, fmt.Errorf("failed to unmarshal image from schema 1 history: %w", err)
}
img.History = history
img.RootFS = ocispec.RootFS{
Type: "layers",
DiffIDs: diffIDs,
}
b, err := json.MarshalIndent(img, "", " ")
if err != nil {
return ocispec.Descriptor{}, fmt.Errorf("failed to marshal image: %w", err)
}
config := ocispec.Descriptor{
MediaType: co.ConfigMediaType,
Digest: digest.Canonical.FromBytes(b),
Size: int64(len(b)),
}
layers := make([]ocispec.Descriptor, len(diffIDs))
for i, diffID := range diffIDs {
layers[i] = c.layerBlobs[diffID]
}
manifest := ocispec.Manifest{
Versioned: specs.Versioned{
SchemaVersion: 2,
},
Config: config,
Layers: layers,
}
mb, err := json.MarshalIndent(manifest, "", " ")
if err != nil {
return ocispec.Descriptor{}, fmt.Errorf("failed to marshal image: %w", err)
}
desc := ocispec.Descriptor{
MediaType: co.ManifestMediaType,
Digest: digest.Canonical.FromBytes(mb),
Size: int64(len(mb)),
}
labels := map[string]string{}
labels["containerd.io/gc.ref.content.0"] = manifest.Config.Digest.String()
for i, ch := range manifest.Layers {
labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i+1)] = ch.Digest.String()
}
ref := remotes.MakeRefKey(ctx, desc)
if err := content.WriteBlob(ctx, c.contentStore, ref, bytes.NewReader(mb), desc, content.WithLabels(labels)); err != nil {
return ocispec.Descriptor{}, fmt.Errorf("failed to write image manifest: %w", err)
}
ref = remotes.MakeRefKey(ctx, config)
if err := content.WriteBlob(ctx, c.contentStore, ref, bytes.NewReader(b), config); err != nil {
return ocispec.Descriptor{}, fmt.Errorf("failed to write image config: %w", err)
}
return desc, nil
}
// ReadStripSignature reads in a schema1 manifest and returns a byte array
// with the "signatures" field stripped
func ReadStripSignature(schema1Blob io.Reader) ([]byte, error) {
b, err := io.ReadAll(io.LimitReader(schema1Blob, manifestSizeLimit)) // limit to 8MB
if err != nil {
return nil, err
}
return stripSignature(b)
}
func (c *Converter) fetchManifest(ctx context.Context, desc ocispec.Descriptor) error {
log.G(ctx).Debug("fetch schema 1")
rc, err := c.fetcher.Fetch(ctx, desc)
if err != nil {
return err
}
b, err := ReadStripSignature(rc)
rc.Close()
if err != nil {
return err
}
var m manifest
if err := json.Unmarshal(b, &m); err != nil {
return err
}
if len(m.Manifests) != 0 || len(m.Layers) != 0 {
return errors.New("converter: expected schema1 document but found extra keys")
}
c.pulledManifest = &m
return nil
}
func (c *Converter) fetchBlob(ctx context.Context, desc ocispec.Descriptor) error {
log.G(ctx).Debug("fetch blob")
var (
ref = remotes.MakeRefKey(ctx, desc)
calc = newBlobStateCalculator()
compressMethod = compression.Gzip
)
// size may be unknown, set to zero for content ingest
ingestDesc := desc
if ingestDesc.Size == -1 {
ingestDesc.Size = 0
}
cw, err := content.OpenWriter(ctx, c.contentStore, content.WithRef(ref), content.WithDescriptor(ingestDesc))
if err != nil {
if !errdefs.IsAlreadyExists(err) {
return err
}
reuse, err := c.reuseLabelBlobState(ctx, desc)
if err != nil {
return err
}
if reuse {
return nil
}
ra, err := c.contentStore.ReaderAt(ctx, desc)
if err != nil {
return err
}
defer ra.Close()
r, err := compression.DecompressStream(content.NewReader(ra))
if err != nil {
return err
}
compressMethod = r.GetCompression()
_, err = io.Copy(calc, r)
r.Close()
if err != nil {
return err
}
} else {
defer cw.Close()
rc, err := c.fetcher.Fetch(ctx, desc)
if err != nil {
return err
}
defer rc.Close()
eg, _ := errgroup.WithContext(ctx)
pr, pw := io.Pipe()
eg.Go(func() error {
r, err := compression.DecompressStream(pr)
if err != nil {
return err
}
compressMethod = r.GetCompression()
_, err = io.Copy(calc, r)
r.Close()
pr.CloseWithError(err)
return err
})
eg.Go(func() error {
defer pw.Close()
return content.Copy(ctx, cw, io.TeeReader(rc, pw), ingestDesc.Size, ingestDesc.Digest)
})
if err := eg.Wait(); err != nil {
return err
}
}
if desc.Size == -1 {
info, err := c.contentStore.Info(ctx, desc.Digest)
if err != nil {
return fmt.Errorf("failed to get blob info: %w", err)
}
desc.Size = info.Size
}
if compressMethod == compression.Uncompressed {
log.G(ctx).WithField("id", desc.Digest).Debugf("changed media type for uncompressed schema1 layer blob")
desc.MediaType = images.MediaTypeDockerSchema2Layer
}
state := calc.State()
cinfo := content.Info{
Digest: desc.Digest,
Labels: map[string]string{
labels.LabelUncompressed: state.diffID.String(),
labelDockerSchema1EmptyLayer: strconv.FormatBool(state.empty),
},
}
if _, err := c.contentStore.Update(ctx, cinfo, "labels."+labels.LabelUncompressed, fmt.Sprintf("labels.%s", labelDockerSchema1EmptyLayer)); err != nil {
return fmt.Errorf("failed to update uncompressed label: %w", err)
}
c.mu.Lock()
c.blobMap[desc.Digest] = state
c.layerBlobs[state.diffID] = desc
c.mu.Unlock()
return nil
}
func (c *Converter) reuseLabelBlobState(ctx context.Context, desc ocispec.Descriptor) (bool, error) {
cinfo, err := c.contentStore.Info(ctx, desc.Digest)
if err != nil {
return false, fmt.Errorf("failed to get blob info: %w", err)
}
desc.Size = cinfo.Size
diffID, ok := cinfo.Labels[labels.LabelUncompressed]
if !ok {
return false, nil
}
emptyVal, ok := cinfo.Labels[labelDockerSchema1EmptyLayer]
if !ok {
return false, nil
}
isEmpty, err := strconv.ParseBool(emptyVal)
if err != nil {
log.G(ctx).WithField("id", desc.Digest).Warnf("failed to parse bool from label %s: %v", labelDockerSchema1EmptyLayer, isEmpty)
return false, nil
}
bState := blobState{empty: isEmpty}
if bState.diffID, err = digest.Parse(diffID); err != nil {
log.G(ctx).WithField("id", desc.Digest).Warnf("failed to parse digest from label %s: %v", labels.LabelUncompressed, diffID)
return false, nil
}
// NOTE: there is no need to read header to get compression method
// because there are only two kinds of methods.
if bState.diffID == desc.Digest {
desc.MediaType = images.MediaTypeDockerSchema2Layer
} else {
desc.MediaType = images.MediaTypeDockerSchema2LayerGzip
}
c.mu.Lock()
c.blobMap[desc.Digest] = bState
c.layerBlobs[bState.diffID] = desc
c.mu.Unlock()
return true, nil
}
func (c *Converter) schema1ManifestHistory() ([]ocispec.History, []digest.Digest, error) {
if c.pulledManifest == nil {
return nil, nil, errors.New("missing schema 1 manifest for conversion")
}
m := *c.pulledManifest
if len(m.History) == 0 {
return nil, nil, errors.New("no history")
}
history := make([]ocispec.History, len(m.History))
diffIDs := []digest.Digest{}
for i := range m.History {
var h v1History
if err := json.Unmarshal([]byte(m.History[i].V1Compatibility), &h); err != nil {
return nil, nil, fmt.Errorf("failed to unmarshal history: %w", err)
}
blobSum := m.FSLayers[i].BlobSum
state := c.blobMap[blobSum]
history[len(history)-i-1] = ocispec.History{
Author: h.Author,
Comment: h.Comment,
Created: &h.Created,
CreatedBy: strings.Join(h.ContainerConfig.Cmd, " "),
EmptyLayer: state.empty,
}
if !state.empty {
diffIDs = append([]digest.Digest{state.diffID}, diffIDs...)
}
}
return history, diffIDs, nil
}
type fsLayer struct {
BlobSum digest.Digest `json:"blobSum"`
}
type history struct {
V1Compatibility string `json:"v1Compatibility"`
}
type manifest struct {
FSLayers []fsLayer `json:"fsLayers"`
History []history `json:"history"`
Layers json.RawMessage `json:"layers,omitempty"` // OCI manifest
Manifests json.RawMessage `json:"manifests,omitempty"` // OCI index
}
type v1History struct {
Author string `json:"author,omitempty"`
Created time.Time `json:"created"`
Comment string `json:"comment,omitempty"`
ThrowAway *bool `json:"throwaway,omitempty"`
Size *int `json:"Size,omitempty"` // used before ThrowAway field
ContainerConfig struct {
Cmd []string `json:"Cmd,omitempty"`
} `json:"container_config,omitempty"`
}
// isEmptyLayer returns whether the v1 compatibility history describes an
// empty layer. A return value of true indicates the layer is empty,
// however false does not indicate non-empty.
func isEmptyLayer(compatHistory []byte) (bool, error) {
var h v1History
if err := json.Unmarshal(compatHistory, &h); err != nil {
return false, err
}
if h.ThrowAway != nil {
return *h.ThrowAway, nil
}
if h.Size != nil {
return *h.Size == 0, nil
}
// If no `Size` or `throwaway` field is given, then
// it cannot be determined whether the layer is empty
// from the history, return false
return false, nil
}
type signature struct {
Signatures []jsParsedSignature `json:"signatures"`
}
type jsParsedSignature struct {
Protected string `json:"protected"`
}
type protectedBlock struct {
Length int `json:"formatLength"`
Tail string `json:"formatTail"`
}
// joseBase64UrlDecode decodes the given string using the standard base64 url
// decoder but first adds the appropriate number of trailing '=' characters in
// accordance with the jose specification.
// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-2
func joseBase64UrlDecode(s string) ([]byte, error) {
switch len(s) % 4 {
case 0:
case 2:
s += "=="
case 3:
s += "="
default:
return nil, errors.New("illegal base64url string")
}
return base64.URLEncoding.DecodeString(s)
}
func stripSignature(b []byte) ([]byte, error) {
var sig signature
if err := json.Unmarshal(b, &sig); err != nil {
return nil, err
}
if len(sig.Signatures) == 0 {
return nil, errors.New("no signatures")
}
pb, err := joseBase64UrlDecode(sig.Signatures[0].Protected)
if err != nil {
return nil, fmt.Errorf("could not decode %s: %w", sig.Signatures[0].Protected, err)
}
var protected protectedBlock
if err := json.Unmarshal(pb, &protected); err != nil {
return nil, err
}
if protected.Length > len(b) {
return nil, errors.New("invalid protected length block")
}
tail, err := joseBase64UrlDecode(protected.Tail)
if err != nil {
return nil, fmt.Errorf("invalid tail base 64 value: %w", err)
}
return append(b[:protected.Length], tail...), nil
}
type blobStateCalculator struct {
empty bool
digester digest.Digester
}
func newBlobStateCalculator() *blobStateCalculator {
return &blobStateCalculator{
empty: true,
digester: digest.Canonical.Digester(),
}
}
func (c *blobStateCalculator) Write(p []byte) (int, error) {
if c.empty {
for _, b := range p {
if b != 0x00 {
c.empty = false
break
}
}
}
return c.digester.Hash().Write(p)
}
func (c *blobStateCalculator) State() blobState {
return blobState{
empty: c.empty,
diffID: c.digester.Digest(),
}
}
================================================
FILE: pkg/remote/remotes/docker/scope.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package docker
import (
"context"
"fmt"
"net/url"
"sort"
"strings"
"github.com/containerd/containerd/v2/pkg/reference"
)
// RepositoryScope returns a repository scope string such as "repository:foo/bar:pull"
// for "host/foo/bar:baz".
// When push is true, both pull and push are added to the scope.
func RepositoryScope(refspec reference.Spec, push bool) (string, error) {
u, err := url.Parse("dummy://" + refspec.Locator)
if err != nil {
return "", err
}
s := "repository:" + strings.TrimPrefix(u.Path, "/") + ":pull"
if push {
s += ",push"
}
return s, nil
}
// tokenScopesKey is used for the key for context.WithValue().
// value: []string (e.g. {"registry:foo/bar:pull"})
type tokenScopesKey struct{}
// ContextWithRepositoryScope returns a context with tokenScopesKey{} and the repository scope value.
func ContextWithRepositoryScope(ctx context.Context, refspec reference.Spec, push bool) (context.Context, error) {
s, err := RepositoryScope(refspec, push)
if err != nil {
return nil, err
}
return WithScope(ctx, s), nil
}
// WithScope appends a custom registry auth scope to the context.
func WithScope(ctx context.Context, scope string) context.Context {
var scopes []string
if v := ctx.Value(tokenScopesKey{}); v != nil {
scopes = v.([]string)
scopes = append(scopes, scope)
} else {
scopes = []string{scope}
}
return context.WithValue(ctx, tokenScopesKey{}, scopes)
}
// ContextWithAppendPullRepositoryScope is used to append repository pull
// scope into existing scopes indexed by the tokenScopesKey{}.
func ContextWithAppendPullRepositoryScope(ctx context.Context, repo string) context.Context {
return WithScope(ctx, fmt.Sprintf("repository:%s:pull", repo))
}
// GetTokenScopes returns deduplicated and sorted scopes from ctx.Value(tokenScopesKey{}) and common scopes.
func GetTokenScopes(ctx context.Context, common []string) []string {
scopes := []string{}
if x := ctx.Value(tokenScopesKey{}); x != nil {
scopes = append(scopes, x.([]string)...)
}
scopes = append(scopes, common...)
sort.Strings(scopes)
if len(scopes) == 0 {
return scopes
}
l := 0
for idx := 1; idx < len(scopes); idx++ {
// Note: this comparison is unaware of the scope grammar (https://docs.docker.com/registry/spec/auth/scope/)
// So, "repository:foo/bar:pull,push" != "repository:foo/bar:push,pull", although semantically they are equal.
if scopes[l] == scopes[idx] {
continue
}
l++
scopes[l] = scopes[idx]
}
return scopes[:l+1]
}
================================================
FILE: pkg/remote/remotes/docker/scope_test.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package docker
import (
"context"
"testing"
"github.com/containerd/containerd/v2/pkg/reference"
"github.com/stretchr/testify/assert"
)
func TestRepositoryScope(t *testing.T) {
testCases := []struct {
refspec reference.Spec
push bool
expected string
}{
{
refspec: reference.Spec{
Locator: "host/foo/bar",
Object: "ignored",
},
push: false,
expected: "repository:foo/bar:pull",
},
{
refspec: reference.Spec{
Locator: "host:4242/foo/bar",
Object: "ignored",
},
push: true,
expected: "repository:foo/bar:pull,push",
},
}
for _, x := range testCases {
t.Run(x.refspec.String(), func(t *testing.T) {
actual, err := RepositoryScope(x.refspec, x.push)
assert.NoError(t, err)
assert.Equal(t, x.expected, actual)
})
}
}
func TestGetTokenScopes(t *testing.T) {
testCases := []struct {
scopesInCtx []string
commonScopes []string
expected []string
}{
{
scopesInCtx: []string{},
commonScopes: []string{},
expected: []string{},
},
{
scopesInCtx: []string{},
commonScopes: []string{"repository:foo/bar:pull"},
expected: []string{"repository:foo/bar:pull"},
},
{
scopesInCtx: []string{"repository:foo/bar:pull,push"},
commonScopes: []string{},
expected: []string{"repository:foo/bar:pull,push"},
},
{
scopesInCtx: []string{"repository:foo/bar:pull"},
commonScopes: []string{"repository:foo/bar:pull"},
expected: []string{"repository:foo/bar:pull"},
},
{
scopesInCtx: []string{"repository:foo/bar:pull"},
commonScopes: []string{"repository:foo/bar:pull,push"},
expected: []string{"repository:foo/bar:pull", "repository:foo/bar:pull,push"},
},
{
scopesInCtx: []string{"repository:foo/bar:pull"},
commonScopes: []string{"repository:foo/bar:pull,push", "repository:foo/bar:pull"},
expected: []string{"repository:foo/bar:pull", "repository:foo/bar:pull,push"},
},
}
for _, tc := range testCases {
ctx := context.WithValue(context.TODO(), tokenScopesKey{}, tc.scopesInCtx)
actual := GetTokenScopes(ctx, tc.commonScopes)
assert.Equal(t, tc.expected, actual)
}
}
func TestCustomScope(t *testing.T) {
scope := "whatever:foo/bar:pull"
ctx := WithScope(context.Background(), scope)
ctx = ContextWithAppendPullRepositoryScope(ctx, "foo/bar")
scopes := GetTokenScopes(ctx, []string{})
assert.Equal(t, []string{"repository:foo/bar:pull", scope}, scopes)
}
================================================
FILE: pkg/remote/remotes/docker/status.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package docker
import (
"fmt"
"sync"
"github.com/containerd/containerd/v2/core/content"
"github.com/containerd/errdefs"
"github.com/moby/locker"
)
// Status of a content operation
type Status struct {
content.Status
Committed bool
// ErrClosed contains error encountered on close.
ErrClosed error
// UploadUUID is used by the Docker registry to reference blob uploads
UploadUUID string
}
// StatusTracker to track status of operations
type StatusTracker interface {
GetStatus(string) (Status, error)
SetStatus(string, Status)
}
// StatusTrackLocker to track status of operations with lock
type StatusTrackLocker interface {
StatusTracker
Lock(string)
Unlock(string)
}
type memoryStatusTracker struct {
statuses map[string]Status
m sync.Mutex
locker *locker.Locker
}
// NewInMemoryTracker returns a StatusTracker that tracks content status in-memory
func NewInMemoryTracker() StatusTrackLocker {
return &memoryStatusTracker{
statuses: map[string]Status{},
locker: locker.New(),
}
}
func (t *memoryStatusTracker) GetStatus(ref string) (Status, error) {
t.m.Lock()
defer t.m.Unlock()
status, ok := t.statuses[ref]
if !ok {
return Status{}, fmt.Errorf("status for ref %v: %w", ref, errdefs.ErrNotFound)
}
return status, nil
}
func (t *memoryStatusTracker) SetStatus(ref string, status Status) {
t.m.Lock()
t.statuses[ref] = status
t.m.Unlock()
}
func (t *memoryStatusTracker) Lock(ref string) {
t.locker.Lock(ref)
}
func (t *memoryStatusTracker) Unlock(ref string) {
t.locker.Unlock(ref)
}
================================================
FILE: pkg/remote/remotes/errors/errors.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package errors
import (
"fmt"
"io"
"net/http"
)
var _ error = ErrUnexpectedStatus{}
// ErrUnexpectedStatus is returned if a registry API request returned with unexpected HTTP status
type ErrUnexpectedStatus struct {
Status string
StatusCode int
Body []byte
RequestURL, RequestMethod string
}
func (e ErrUnexpectedStatus) Error() string {
return fmt.Sprintf("unexpected status from %s request to %s: %s", e.RequestMethod, e.RequestURL, e.Status)
}
// NewUnexpectedStatusErr creates an ErrUnexpectedStatus from HTTP response
func NewUnexpectedStatusErr(resp *http.Response) error {
var b []byte
if resp.Body != nil {
b, _ = io.ReadAll(io.LimitReader(resp.Body, 64000)) // 64KB
}
err := ErrUnexpectedStatus{
Body: b,
Status: resp.Status,
StatusCode: resp.StatusCode,
RequestMethod: resp.Request.Method,
}
if resp.Request.URL != nil {
err.RequestURL = resp.Request.URL.String()
}
return err
}
================================================
FILE: pkg/remote/remotes/handlers.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package remotes
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"strings"
"sync"
"github.com/containerd/containerd/v2/core/content"
"github.com/containerd/containerd/v2/core/images"
"github.com/containerd/containerd/v2/pkg/labels"
"github.com/containerd/errdefs"
"github.com/containerd/log"
"github.com/containerd/platforms"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"golang.org/x/sync/semaphore"
)
type refKeyPrefix struct{}
// WithMediaTypeKeyPrefix adds a custom key prefix for a media type which is used when storing
// data in the content store from the FetchHandler.
//
// Used in `MakeRefKey` to determine what the key prefix should be.
func WithMediaTypeKeyPrefix(ctx context.Context, mediaType, prefix string) context.Context {
var values map[string]string
if v := ctx.Value(refKeyPrefix{}); v != nil {
values = v.(map[string]string)
} else {
values = make(map[string]string)
}
values[mediaType] = prefix
return context.WithValue(ctx, refKeyPrefix{}, values)
}
// MakeRefKey returns a unique reference for the descriptor. This reference can be
// used to lookup ongoing processes related to the descriptor. This function
// may look to the context to namespace the reference appropriately.
func MakeRefKey(ctx context.Context, desc ocispec.Descriptor) string {
key := desc.Digest.String()
if desc.Annotations != nil {
if name, ok := desc.Annotations[ocispec.AnnotationRefName]; ok {
key = fmt.Sprintf("%s@%s", name, desc.Digest.String())
}
}
if v := ctx.Value(refKeyPrefix{}); v != nil {
values := v.(map[string]string)
if prefix := values[desc.MediaType]; prefix != "" {
return prefix + "-" + key
}
}
switch mt := desc.MediaType; {
case mt == images.MediaTypeDockerSchema2Manifest || mt == ocispec.MediaTypeImageManifest:
return "manifest-" + key
case mt == images.MediaTypeDockerSchema2ManifestList || mt == ocispec.MediaTypeImageIndex:
return "index-" + key
case images.IsLayerType(mt):
return "layer-" + key
case images.IsKnownConfig(mt):
return "config-" + key
default:
log.G(ctx).Warnf("reference for unknown type: %s", mt)
return "unknown-" + key
}
}
// FetchHandler returns a handler that will fetch all content into the ingester
// discovered in a call to Dispatch. Use with ChildrenHandler to do a full
// recursive fetch.
func FetchHandler(ingester content.Ingester, fetcher Fetcher) images.HandlerFunc {
return func(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) {
ctx = log.WithLogger(ctx, log.G(ctx).WithFields(log.Fields{
"digest": desc.Digest,
"mediatype": desc.MediaType,
"size": desc.Size,
}))
switch desc.MediaType {
case images.MediaTypeDockerSchema1Manifest:
return nil, fmt.Errorf("%v not supported", desc.MediaType)
default:
err := Fetch(ctx, ingester, fetcher, desc)
if errdefs.IsAlreadyExists(err) {
return nil, nil
}
return nil, err
}
}
}
// Fetch fetches the given digest into the provided ingester
func Fetch(ctx context.Context, ingester content.Ingester, fetcher Fetcher, desc ocispec.Descriptor) error {
log.G(ctx).Debug("fetch")
cw, err := content.OpenWriter(ctx, ingester, content.WithRef(MakeRefKey(ctx, desc)), content.WithDescriptor(desc))
if err != nil {
return err
}
defer cw.Close()
ws, err := cw.Status()
if err != nil {
return err
}
if desc.Size == 0 {
// most likely a poorly configured registry/web front end which responded with no
// Content-Length header; unable (not to mention useless) to commit a 0-length entry
// into the content store. Error out here otherwise the error sent back is confusing
return fmt.Errorf("unable to fetch descriptor (%s) which reports content size of zero: %w", desc.Digest, errdefs.ErrInvalidArgument)
}
if ws.Offset == desc.Size {
// If writer is already complete, commit and return
err := cw.Commit(ctx, desc.Size, desc.Digest)
if err != nil && !errdefs.IsAlreadyExists(err) {
return fmt.Errorf("failed commit on ref %q: %w", ws.Ref, err)
}
return err
}
if desc.Size == int64(len(desc.Data)) {
return content.Copy(ctx, cw, bytes.NewReader(desc.Data), desc.Size, desc.Digest)
}
rc, err := fetcher.Fetch(ctx, desc)
if err != nil {
return err
}
defer rc.Close()
return content.Copy(ctx, cw, rc, desc.Size, desc.Digest)
}
// PushHandler returns a handler that will push all content from the provider
// using a writer from the pusher.
func PushHandler(pusher Pusher, provider content.Provider) images.HandlerFunc {
return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
ctx = log.WithLogger(ctx, log.G(ctx).WithFields(log.Fields{
"digest": desc.Digest,
"mediatype": desc.MediaType,
"size": desc.Size,
}))
err := push(ctx, provider, pusher, desc)
return nil, err
}
}
func push(ctx context.Context, provider content.Provider, pusher Pusher, desc ocispec.Descriptor) error {
log.G(ctx).Debug("push")
var (
cw content.Writer
err error
)
if cs, ok := pusher.(content.Ingester); ok {
cw, err = content.OpenWriter(ctx, cs, content.WithRef(MakeRefKey(ctx, desc)), content.WithDescriptor(desc))
} else {
cw, err = pusher.Push(ctx, desc)
}
if err != nil {
if !errdefs.IsAlreadyExists(err) {
return err
}
return nil
}
defer cw.Close()
ra, err := provider.ReaderAt(ctx, desc)
if err != nil {
return err
}
defer ra.Close()
rd := io.NewSectionReader(ra, 0, desc.Size)
return content.Copy(ctx, cw, rd, desc.Size, desc.Digest)
}
// PushContent pushes content specified by the descriptor from the provider.
//
// Base handlers can be provided which will be called before any push specific
// handlers.
//
// If the passed in content.Provider is also a content.Manager then this will
// also annotate the distribution sources in the manager.
func PushContent(ctx context.Context, pusher Pusher, desc ocispec.Descriptor, store content.Provider, limiter *semaphore.Weighted, platform platforms.MatchComparer, wrapper func(h images.Handler) images.Handler) error {
var m sync.Mutex
manifests := []ocispec.Descriptor{}
indexStack := []ocispec.Descriptor{}
filterHandler := images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
switch desc.MediaType {
case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest:
m.Lock()
manifests = append(manifests, desc)
m.Unlock()
return nil, images.ErrStopHandler
case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
m.Lock()
indexStack = append(indexStack, desc)
m.Unlock()
return nil, images.ErrStopHandler
default:
return nil, nil
}
})
pushHandler := PushHandler(pusher, store)
platformFilterhandler := images.FilterPlatforms(images.ChildrenHandler(store), platform)
var handler images.Handler
if m, ok := store.(content.Manager); ok {
annotateHandler := annotateDistributionSourceHandler(platformFilterhandler, m)
handler = images.Handlers(annotateHandler, filterHandler, pushHandler)
} else {
handler = images.Handlers(platformFilterhandler, filterHandler, pushHandler)
}
if wrapper != nil {
handler = wrapper(handler)
}
if err := images.Dispatch(ctx, handler, limiter, desc); err != nil {
return err
}
if err := images.Dispatch(ctx, pushHandler, limiter, manifests...); err != nil {
return err
}
// Iterate in reverse order as seen, parent always uploaded after child
for i := len(indexStack) - 1; i >= 0; i-- {
err := images.Dispatch(ctx, pushHandler, limiter, indexStack[i])
if err != nil {
// TODO(estesp): until we have a more complete method for index push, we need to report
// missing dependencies in an index/manifest list by sensing the "400 Bad Request"
// as a marker for this problem
if errors.Unwrap(err) != nil && strings.Contains(errors.Unwrap(err).Error(), "400 Bad Request") {
return fmt.Errorf("manifest list/index references to blobs and/or manifests are missing in your target registry: %w", err)
}
return err
}
}
return nil
}
// SkipNonDistributableBlobs returns a handler that skips blobs that have a media type that is "non-distributeable".
// An example of this kind of content would be a Windows base layer, which is not supposed to be redistributed.
//
// This is based on the media type of the content:
// - application/vnd.oci.image.layer.nondistributable
// - application/vnd.docker.image.rootfs.foreign
func SkipNonDistributableBlobs(f images.HandlerFunc) images.HandlerFunc {
return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
if images.IsNonDistributable(desc.MediaType) {
log.G(ctx).WithField("digest", desc.Digest).WithField("mediatype", desc.MediaType).Debug("Skipping non-distributable blob")
return nil, images.ErrSkipDesc
}
if images.IsLayerType(desc.MediaType) {
return nil, nil
}
children, err := f(ctx, desc)
if err != nil {
return nil, err
}
if len(children) == 0 {
return nil, nil
}
out := make([]ocispec.Descriptor, 0, len(children))
for _, child := range children {
if !images.IsNonDistributable(child.MediaType) {
out = append(out, child)
} else {
log.G(ctx).WithField("digest", child.Digest).WithField("mediatype", child.MediaType).Debug("Skipping non-distributable blob")
}
}
return out, nil
}
}
// FilterManifestByPlatformHandler allows Handler to handle non-target
// platform's manifest and configuration data.
func FilterManifestByPlatformHandler(f images.HandlerFunc, m platforms.Matcher) images.HandlerFunc {
return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
children, err := f(ctx, desc)
if err != nil {
return nil, err
}
// no platform information
if desc.Platform == nil || m == nil {
return children, nil
}
var descs []ocispec.Descriptor
switch desc.MediaType {
case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest:
if m.Match(*desc.Platform) {
descs = children
} else {
for _, child := range children {
if child.MediaType == images.MediaTypeDockerSchema2Config ||
child.MediaType == ocispec.MediaTypeImageConfig {
descs = append(descs, child)
}
}
}
default:
descs = children
}
return descs, nil
}
}
// annotateDistributionSourceHandler add distribution source label into
// annotation of config or blob descriptor.
func annotateDistributionSourceHandler(f images.HandlerFunc, manager content.Manager) images.HandlerFunc {
return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
children, err := f(ctx, desc)
if err != nil {
return nil, err
}
// only add distribution source for the config or blob data descriptor
switch desc.MediaType {
case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest,
images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
default:
return children, nil
}
for i := range children {
child := children[i]
info, err := manager.Info(ctx, child.Digest)
if err != nil {
return nil, err
}
for k, v := range info.Labels {
if !strings.HasPrefix(k, labels.LabelDistributionSource+".") {
continue
}
if child.Annotations == nil {
child.Annotations = map[string]string{}
}
child.Annotations[k] = v
}
children[i] = child
}
return children, nil
}
}
================================================
FILE: pkg/remote/remotes/handlers_test.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package remotes
import (
"context"
_ "crypto/sha256"
"encoding/json"
"sync"
"testing"
"github.com/containerd/containerd/v2/core/content"
"github.com/containerd/containerd/v2/core/images"
"github.com/containerd/containerd/v2/plugins/content/local"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
func TestContextCustomKeyPrefix(t *testing.T) {
ctx := context.Background()
cmt := "testing/custom.media.type"
ctx = WithMediaTypeKeyPrefix(ctx, images.MediaTypeDockerSchema2Layer, "bananas")
ctx = WithMediaTypeKeyPrefix(ctx, cmt, "apples")
// makes sure that even though we've supplied some custom handling, the built-in still works
t.Run("normal supported case", func(t *testing.T) {
desc := ocispec.Descriptor{MediaType: ocispec.MediaTypeImageLayer}
expected := "layer-"
actual := MakeRefKey(ctx, desc)
if actual != expected {
t.Fatalf("unexpected ref key, expected %s, got: %s", expected, actual)
}
})
t.Run("unknown media type", func(t *testing.T) {
desc := ocispec.Descriptor{MediaType: "we.dont.know.what.this.is"}
expected := "unknown-"
actual := MakeRefKey(ctx, desc)
if actual != expected {
t.Fatalf("unexpected ref key, expected %s, got: %s", expected, actual)
}
})
t.Run("overwrite supported media type", func(t *testing.T) {
desc := ocispec.Descriptor{MediaType: images.MediaTypeDockerSchema2Layer}
expected := "bananas-"
actual := MakeRefKey(ctx, desc)
if actual != expected {
t.Fatalf("unexpected ref key, expected %s, got: %s", expected, actual)
}
})
t.Run("custom media type", func(t *testing.T) {
desc := ocispec.Descriptor{MediaType: cmt}
expected := "apples-"
actual := MakeRefKey(ctx, desc)
if actual != expected {
t.Fatalf("unexpected ref key, expected %s, got: %s", expected, actual)
}
})
}
func TestSkipNonDistributableBlobs(t *testing.T) {
ctx := context.Background()
out, err := SkipNonDistributableBlobs(images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
return []ocispec.Descriptor{
{MediaType: images.MediaTypeDockerSchema2Layer, Digest: "test:1"},
{MediaType: images.MediaTypeDockerSchema2LayerForeign, Digest: "test:2"},
{MediaType: images.MediaTypeDockerSchema2LayerForeignGzip, Digest: "test:3"},
{MediaType: ocispec.MediaTypeImageLayerNonDistributable, Digest: "test:4"},
{MediaType: ocispec.MediaTypeImageLayerNonDistributableGzip, Digest: "test:5"},
{MediaType: ocispec.MediaTypeImageLayerNonDistributableZstd, Digest: "test:6"},
}, nil
}))(ctx, ocispec.Descriptor{MediaType: images.MediaTypeDockerSchema2Manifest})
if err != nil {
t.Fatal(err)
}
if len(out) != 1 {
t.Fatalf("unexpected number of descriptors returned: %d", len(out))
}
if out[0].Digest != "test:1" {
t.Fatalf("unexpected digest returned: %s", out[0].Digest)
}
dir := t.TempDir()
cs, err := local.NewLabeledStore(dir, newMemoryLabelStore())
if err != nil {
t.Fatal(err)
}
write := func(i interface{}, ref string) digest.Digest {
t.Helper()
data, err := json.Marshal(i)
if err != nil {
t.Fatal(err)
}
w, err := cs.Writer(ctx, content.WithRef(ref))
if err != nil {
t.Fatal(err)
}
defer w.Close()
dgst := digest.SHA256.FromBytes(data)
n, err := w.Write(data)
if err != nil {
t.Fatal(err)
}
if err := w.Commit(ctx, int64(n), dgst); err != nil {
t.Fatal(err)
}
return dgst
}
configDigest := write(ocispec.ImageConfig{}, "config")
manifest := ocispec.Manifest{
Config: ocispec.Descriptor{Digest: configDigest, MediaType: ocispec.MediaTypeImageConfig},
MediaType: ocispec.MediaTypeImageManifest,
Layers: []ocispec.Descriptor{
{MediaType: images.MediaTypeDockerSchema2Layer, Digest: "test:1"},
{MediaType: images.MediaTypeDockerSchema2LayerForeign, Digest: "test:2"},
{MediaType: images.MediaTypeDockerSchema2LayerForeignGzip, Digest: "test:3"},
{MediaType: ocispec.MediaTypeImageLayerNonDistributable, Digest: "test:4"},
{MediaType: ocispec.MediaTypeImageLayerNonDistributableGzip, Digest: "test:5"},
{MediaType: ocispec.MediaTypeImageLayerNonDistributableZstd, Digest: "test:6"},
},
}
manifestDigest := write(manifest, "manifest")
out, err = SkipNonDistributableBlobs(images.ChildrenHandler(cs))(ctx, ocispec.Descriptor{MediaType: manifest.MediaType, Digest: manifestDigest})
if err != nil {
t.Fatal(err)
}
if len(out) != 2 {
t.Fatalf("unexpected number of descriptors returned: %v", out)
}
if out[0].Digest != configDigest {
t.Fatalf("unexpected digest returned: %v", out[0])
}
if out[1].Digest != manifest.Layers[0].Digest {
t.Fatalf("unexpected digest returned: %v", out[1])
}
}
type memoryLabelStore struct {
l sync.Mutex
labels map[digest.Digest]map[string]string
}
func newMemoryLabelStore() local.LabelStore {
return &memoryLabelStore{
labels: map[digest.Digest]map[string]string{},
}
}
func (mls *memoryLabelStore) Get(d digest.Digest) (map[string]string, error) {
mls.l.Lock()
labels := mls.labels[d]
mls.l.Unlock()
return labels, nil
}
func (mls *memoryLabelStore) Set(d digest.Digest, labels map[string]string) error {
mls.l.Lock()
mls.labels[d] = labels
mls.l.Unlock()
return nil
}
func (mls *memoryLabelStore) Update(d digest.Digest, update map[string]string) (map[string]string, error) {
mls.l.Lock()
labels, ok := mls.labels[d]
if !ok {
labels = map[string]string{}
}
for k, v := range update {
if v == "" {
delete(labels, k)
} else {
labels[k] = v
}
}
mls.labels[d] = labels
mls.l.Unlock()
return labels, nil
}
================================================
FILE: pkg/remote/remotes/resolver.go
================================================
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package remotes
import (
"context"
"io"
"github.com/containerd/containerd/v2/core/content"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// Resolver provides remotes based on a locator.
type Resolver interface {
// Resolve attempts to resolve the reference into a name and descriptor.
//
// The argument `ref` should be a scheme-less URI representing the remote.
// Structurally, it has a host and path. The "host" can be used to directly
// reference a specific host or be matched against a specific handler.
//
// The returned name should be used to identify the referenced entity.
// Depending on the remote namespace, this may be immutable or mutable.
// While the name may differ from ref, it should itself be a valid ref.
//
// If the resolution fails, an error will be returned.
Resolve(ctx context.Context, ref string) (name string, desc ocispec.Descriptor, err error)
// Fetcher returns a new fetcher for the provided reference.
// All content fetched from the returned fetcher will be
// from the namespace referred to by ref.
Fetcher(ctx context.Context, ref string) (Fetcher, error)
// Pusher returns a new pusher for the provided reference
// The returned Pusher should satisfy content.Ingester and concurrent attempts
// to push the same blob using the Ingester API should result in ErrUnavailable.
Pusher(ctx context.Context, ref string) (Pusher, error)
}
// Fetcher fetches content.
// A fetcher implementation may implement the FetcherByDigest interface too.
type Fetcher interface {
// Fetch the resource identified by the descriptor.
Fetch(ctx context.Context, desc ocispec.Descriptor) (io.ReadCloser, error)
}
// FetcherByDigest fetches content by the digest.
type FetcherByDigest interface {
// FetchByDigest fetches the resource identified by the digest.
//
// FetcherByDigest usually returns an incomplete descriptor.
// Typically, the media type is always set to "application/octet-stream",
// and the annotations are unset.
FetchByDigest(ctx context.Context, dgst digest.Digest) (io.ReadCloser, ocispec.Descriptor, error)
}
type ReferrersFetcher interface {
FetchReferrers(ctx context.Context, dgst digest.Digest, artifactTypes ...string) (io.ReadCloser, ocispec.Descriptor, error)
}
// Pusher pushes content
type Pusher interface {
// Push returns a content writer for the given resource identified
// by the descriptor.
Push(ctx context.Context, d ocispec.Descriptor) (content.Writer, error)
}
// FetcherFunc allows package users to implement a Fetcher with just a
// function.
type FetcherFunc func(ctx context.Context, desc ocispec.Descriptor) (io.ReadCloser, error)
// Fetch content
func (fn FetcherFunc) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.ReadCloser, error) {
return fn(ctx, desc)
}
// PusherFunc allows package users to implement a Pusher with just a
// function.
type PusherFunc func(ctx context.Context, desc ocispec.Descriptor) (content.Writer, error)
// Push content
func (fn PusherFunc) Push(ctx context.Context, desc ocispec.Descriptor) (content.Writer, error) {
return fn(ctx, desc)
}
================================================
FILE: pkg/remote/unpack.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package remote
import (
"archive/tar"
"fmt"
"io"
"os"
"github.com/containerd/containerd/v2/pkg/archive/compression"
)
// Unpack unpacks the file named `source` in tar stream
// and write into `target` path.
func Unpack(reader io.Reader, source, target string) error {
rdr, err := compression.DecompressStream(reader)
if err != nil {
return err
}
defer rdr.Close()
found := false
tr := tar.NewReader(rdr)
for {
hdr, err := tr.Next()
if err != nil {
if err == io.EOF {
break
}
return err
}
if hdr.Name == source {
file, err := os.Create(target)
if err != nil {
return err
}
defer file.Close()
if _, err := io.Copy(file, tr); err != nil {
return err
}
found = true
break
}
}
if !found {
return fmt.Errorf("not found file %s in tar", source)
}
return nil
}
================================================
FILE: pkg/resolve/resolver.go
================================================
/*
* Copyright (c) 2021. Alibaba Cloud. All rights reserved.
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package resolve
import (
"fmt"
"io"
"net/http"
"github.com/containerd/nydus-snapshotter/pkg/auth"
"github.com/containerd/nydus-snapshotter/pkg/utils/transport"
distribution "github.com/distribution/reference"
"github.com/google/go-containerregistry/pkg/name"
retryablehttp "github.com/hashicorp/go-retryablehttp"
"github.com/pkg/errors"
)
type Resolver struct {
res transport.Resolve
}
func NewResolver() *Resolver {
resolver := Resolver{
res: transport.NewPool(),
}
return &resolver
}
func (r *Resolver) Resolve(ref, digest string, labels map[string]string) (io.ReadCloser, error) {
named, err := distribution.ParseDockerRef(ref)
if err != nil {
return nil, errors.Wrapf(err, "failed parse docker ref %s", ref)
}
host := distribution.Domain(named)
sref := fmt.Sprintf("%s/%s", host, distribution.Path(named))
nref, err := name.ParseReference(sref)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse ref %q (%q)", sref, digest)
}
keychain := auth.GetRegistryKeyChain(ref, labels)
var tr http.RoundTripper
url, tr, err := r.res.Resolve(nref, digest, keychain)
if err != nil {
return nil, errors.Wrapf(err, "failed to create authn transport %v", keychain)
}
req, err := retryablehttp.NewRequest("GET", url, nil)
if err != nil {
return nil, errors.Wrapf(err, "failed to new http get %s", url)
}
client := newRetryHTTPClient(tr)
res, err := client.Do(req)
if err != nil {
return nil, errors.Wrapf(err, "failed to http get %s", url)
}
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to GET request with code %d", res.StatusCode)
}
return res.Body, nil
}
func newRetryHTTPClient(tr http.RoundTripper) *retryablehttp.Client {
retryClient := retryablehttp.NewClient()
retryClient.HTTPClient.Transport = tr
retryClient.Logger = nil
return retryClient
}
================================================
FILE: pkg/signature/signature.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package signature
import (
"encoding/base64"
"fmt"
"os"
"github.com/pkg/errors"
"github.com/containerd/nydus-snapshotter/pkg/label"
"github.com/containerd/nydus-snapshotter/pkg/utils/signer"
)
type Verifier struct {
signer *signer.Signer
force bool
}
func NewVerifier(publicKeyFile string, validateSignature bool) (*Verifier, error) {
res := &Verifier{
force: validateSignature,
}
if !validateSignature {
return res, nil
}
if publicKeyFile == "" {
return nil, errors.New("publicKeyFile is required")
}
if _, err := os.Stat(publicKeyFile); err != nil {
return nil, fmt.Errorf("failed to find publicKeyFile %q", publicKeyFile)
}
publicKeyByte, err := os.ReadFile(publicKeyFile)
if err != nil {
return nil, errors.Wrapf(err, "failed to read from publicKeyFile %q", publicKeyFile)
}
sign, err := signer.New(publicKeyByte)
if err != nil {
return nil, errors.Wrap(err, "failed to initialize signer")
}
res.signer = sign
return res, nil
}
func (v *Verifier) Verify(label map[string]string, bootstrapFile string) error {
signature, err := getFromLabel(label)
if err != nil {
return err
}
if signature == nil {
if v.force {
return errors.New("bootstrap signature is required when force validation")
}
return nil
}
if v.signer == nil {
return nil
}
f, err := os.Open(bootstrapFile)
if err != nil {
return err
}
defer f.Close()
return v.signer.Verify(f, signature)
}
func getFromLabel(labels map[string]string) ([]byte, error) {
if s, ok := labels[label.NydusSignature]; ok {
res, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return nil, err
}
return res, nil
}
return nil, nil
}
// func Verify(label map[string]string, bootstrapFile, publicKey string, force bool) error {
// signature, err := getFromLabel(label)
// if err != nil {
// return err
// }
// // if we found signature on image manifest, we should verify it
// if signature == nil {
// if force {
// return errors.New("bootstrap signature is required when force validation")
// }
// return nil
// }
//
// publicKeyByte, err := ioutil.ReadFile(publicKey)
// if err != nil {
// return err
// }
// sign, err := signer.New(publicKeyByte)
// if err != nil {
// return err
// }
// f, err := os.Open(bootstrapFile)
// if err != nil {
// return err
// }
// defer f.Close()
// return sign.Verify(f, signature)
// }
================================================
FILE: pkg/snapshot/storage.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package snapshot
import (
"context"
"github.com/containerd/containerd/v2/core/snapshots"
"github.com/containerd/containerd/v2/core/snapshots/storage"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/errdefs"
"github.com/pkg/errors"
)
type WalkFunc = func(id string, info snapshots.Info) bool
func GetSnapshotInfo(ctx context.Context, ms *storage.MetaStore, key string) (string, snapshots.Info, snapshots.Usage, error) {
ctx, t, err := ms.TransactionContext(ctx, false)
if err != nil {
return "", snapshots.Info{}, snapshots.Usage{}, err
}
defer func() {
if err := t.Rollback(); err != nil {
log.L.WithError(err).Errorf("Rollback traction %s", key)
}
}()
id, info, usage, err := storage.GetInfo(ctx, key)
if err != nil {
return "", snapshots.Info{}, snapshots.Usage{}, err
}
return id, info, usage, nil
}
func GetSnapshot(ctx context.Context, ms *storage.MetaStore, key string) (*storage.Snapshot, error) {
ctx, t, err := ms.TransactionContext(ctx, false)
if err != nil {
return nil, err
}
defer func() {
if err := t.Rollback(); err != nil {
log.L.WithError(err).Errorf("Rollback traction %s", key)
}
}()
s, err := storage.GetSnapshot(ctx, key)
if err != nil {
return nil, errors.Wrap(err, "get snapshot")
}
return &s, nil
}
// Iterate all the parents of a snapshot specified by `key`
// Stop the iteration once callback `fn` is invoked successfully and return current iterated snapshot
func IterateParentSnapshots(ctx context.Context, ms *storage.MetaStore, key string, fn WalkFunc) (string, snapshots.Info, error) {
ctx, t, err := ms.TransactionContext(ctx, false)
if err != nil {
return "", snapshots.Info{}, err
}
defer func() {
if err := t.Rollback(); err != nil {
log.L.WithError(err).Errorf("Rollback transaction %s", key)
}
}()
for cKey := key; cKey != ""; {
id, info, _, err := storage.GetInfo(ctx, cKey)
if err != nil {
log.L.WithError(err).Warnf("failed to get snapshot info of %q", cKey)
return "", snapshots.Info{}, err
}
if fn(id, info) {
return id, info, nil
}
cKey = info.Parent
}
return "", snapshots.Info{}, errdefs.ErrNotFound
}
func UpdateSnapshotInfo(ctx context.Context, ms *storage.MetaStore, info snapshots.Info, fieldPaths ...string) (snapshots.Info, error) {
ctx, t, err := ms.TransactionContext(ctx, true)
if err != nil {
return snapshots.Info{}, err
}
info, err = storage.UpdateInfo(ctx, info, fieldPaths...)
if err != nil {
if rerr := t.Rollback(); rerr != nil {
log.L.WithError(rerr).Errorf("update snapshot info")
}
return snapshots.Info{}, err
}
if err := t.Commit(); err != nil {
return snapshots.Info{}, err
}
return info, nil
}
================================================
FILE: pkg/stargz/resolver.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package stargz
import (
"archive/tar"
"bytes"
"compress/gzip"
"context"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/utils/transport"
"github.com/containerd/stargz-snapshotter/estargz"
distribution "github.com/distribution/reference"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
"github.com/pkg/errors"
)
const httpTimeout = 15 * time.Second
const (
FooterSize = 47
TocFileName = "stargz.index.json"
)
type Resolver struct {
res transport.Resolve
}
func NewResolver() *Resolver {
resolver := Resolver{
res: transport.NewPool(),
}
return &resolver
}
type Blob struct {
ref string
digest string
sr *io.SectionReader
}
// getTocOffset get toc offset from stargz footer
func (bb *Blob) GetTocOffset() (int64, error) {
tocOffset, _, err := estargz.OpenFooter(bb.sr)
if err != nil {
return 0, errors.Wrap(err, "open stargz blob footer")
}
return tocOffset, nil
}
// ReadToc read stargz toc content from blob
func (bb *Blob) ReadToc() (io.Reader, error) {
start := time.Now()
defer func() {
duration := time.Since(start)
log.L.Infof("read toc duration %d", duration.Milliseconds())
}()
tocOffset, err := bb.GetTocOffset()
if err != nil {
return nil, err
}
tocBuf := make([]byte, bb.sr.Size()-tocOffset-FooterSize)
_, err = bb.sr.ReadAt(tocBuf, tocOffset)
if err != nil {
return nil, err
}
zr, err := gzip.NewReader(bytes.NewReader(tocBuf))
if err != nil {
return nil, err
}
zr.Multistream(false)
tr := tar.NewReader(zr)
h, err := tr.Next()
if err != nil {
return nil, err
}
if h.Name != TocFileName {
return nil, fmt.Errorf("failed to find toc from image %s blob %s", bb.ref, bb.digest)
}
var buf bytes.Buffer
_, err = buf.ReadFrom(tr)
if err != nil {
return nil, err
}
return &buf, nil
}
func (bb *Blob) GetDigest() string {
return bb.digest
}
func (bb *Blob) GetImageReference() string {
return bb.ref
}
func (r *Resolver) GetBlob(ref, digest string, keychain authn.Keychain) (*Blob, error) {
start := time.Now()
defer func() {
duration := time.Since(start)
log.L.Infof("get blob duration %d", duration.Milliseconds())
}()
sr, err := r.resolve(ref, digest, keychain)
if err != nil {
return nil, err
}
return &Blob{
ref: ref,
digest: digest,
sr: sr,
}, nil
}
type readerAtFunc func([]byte, int64) (int, error)
func (f readerAtFunc) ReadAt(p []byte, offset int64) (int, error) { return f(p, offset) }
// parseFooter extract toc offset from footer
func parseFooter(p []byte) (tocOffset int64, ok bool) {
if len(p) != 47 {
return 0, false
}
zr, err := gzip.NewReader(bytes.NewReader(p))
if err != nil {
return 0, false
}
extra := zr.Extra
if len(extra) != 16+len("STARGZ") {
return 0, false
}
if string(extra[16:]) != "STARGZ" {
return 0, false
}
tocOffset, err = strconv.ParseInt(string(extra[:16]), 16, 64)
return tocOffset, err == nil
}
func (r *Resolver) resolve(ref, digest string, keychain authn.Keychain) (*io.SectionReader, error) {
named, err := distribution.ParseDockerRef(ref)
if err != nil {
return nil, err
}
host := distribution.Domain(named)
sref := fmt.Sprintf("%s/%s", host, distribution.Path(named))
nref, err := name.ParseReference(sref)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse ref %q (%q)", sref, digest)
}
url, tr, err := r.res.Resolve(nref, digest, keychain)
if err != nil {
return nil, errors.Wrapf(err, "failed to resolve reference of %q, %q", nref, digest)
}
size, err := getSize(url, tr)
if err != nil {
return nil, errors.Wrapf(err, "failed to get size from url %s", url)
}
log.L.Infof("get size %d", size)
sr := io.NewSectionReader(readerAtFunc(func(b []byte, offset int64) (int, error) {
length := len(b)
ctx, cancel := context.WithTimeout(context.Background(), httpTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return 0, err
}
req.Close = false
r := fmt.Sprintf("bytes=%d-%d", offset, offset+int64(length)-1)
req.Header.Set("Range", r)
res, err := tr.RoundTrip(req)
if err != nil {
return 0, err
}
defer func() {
if _, err := io.Copy(io.Discard, res.Body); err != nil {
log.L.Errorf("failed to copy %s", err)
}
res.Body.Close()
}()
if res.StatusCode/100 != 2 {
return 0, fmt.Errorf("failed to HEAD request with code %d", res.StatusCode)
}
return io.ReadFull(res.Body, b)
}), 0, size)
return sr, nil
}
func getSize(url string, tr http.RoundTripper) (int64, error) {
ctx, cancel := context.WithTimeout(context.Background(), httpTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return 0, err
}
req.Close = false
req.Header.Set("Range", "bytes=0-0")
res, err := tr.RoundTrip(req)
if err != nil {
return 0, err
}
defer func() {
if _, err := io.Copy(io.Discard, res.Body); err != nil {
log.L.Errorf("failed to copy %s", err)
}
res.Body.Close()
}()
if res.StatusCode/100 != 2 {
return 0, fmt.Errorf("failed to HEAD request with code %d", res.StatusCode)
}
contentRange := res.Header.Get("Content-Range")
totalSize := strings.Split(contentRange, "/")[1]
return strconv.ParseInt(totalSize, 10, 64)
}
================================================
FILE: pkg/stargz/resolver_test.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package stargz
import (
"archive/tar"
"bytes"
"compress/gzip"
"fmt"
"io"
"net/http"
"os"
"testing"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/containerd/nydus-snapshotter/pkg/auth"
)
func TestResolver_resolve(t *testing.T) {
resolver := Resolver{
res: &MockResolver{},
}
keychain, err := auth.FromBase64("dGVzdDp0ZXN0Cg==")
require.Nil(t, err)
sr, err := resolver.resolve("example.com/test/myserver:latest-stargz", "sha256:mock", keychain)
require.Nil(t, err)
size := sr.Size()
var b [47]byte
n, err := sr.ReadAt(b[:], size-47)
assert.Nil(t, err)
assert.Equal(t, 47, n)
tocOffset, ok := parseFooter(b[:])
assert.True(t, ok)
fmt.Printf("tocoffset %d", tocOffset)
tocTargz := make([]byte, size-tocOffset-47)
_, err = sr.ReadAt(tocTargz, tocOffset)
assert.Nil(t, err)
zr, err := gzip.NewReader(bytes.NewReader(tocTargz))
assert.Nil(t, err)
zr.Multistream(false)
tr := tar.NewReader(zr)
h, err := tr.Next()
assert.Nil(t, err)
assert.Equal(t, "stargz.index.json", h.Name)
}
type MockResolver struct {
}
func (res *MockResolver) Resolve(_ name.Reference, _ string, _ authn.Keychain) (string, http.RoundTripper, error) {
return "http://oss.com/v2/test/myserver/blobs/sha256:mock", &mockRoundTripper{}, nil
}
type mockRoundTripper struct{}
func (tr *mockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
url := req.URL.String()
if url == "https://example.com/v2/" {
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader([]byte{})),
}, nil
}
if url == "https://example.com/v2/test/myserver/blobs/sha256:mock" {
header := make(http.Header)
header.Add("Location", "http://oss.com/v2/test/myserver/blobs/sha256:mock")
return &http.Response{
StatusCode: http.StatusMovedPermanently,
Header: header,
Body: io.NopCloser(bytes.NewReader([]byte{})),
}, nil
}
if url == "http://oss.com/v2/test/myserver/blobs/sha256:mock" {
rangeHeader := req.Header.Get("Range")
// get length
if rangeHeader == "bytes=0-0" {
header := make(http.Header)
header.Add("Content-Range", "bytes 0-0/24613186")
return &http.Response{
StatusCode: http.StatusPartialContent,
Header: header,
Body: io.NopCloser(bytes.NewReader([]byte{})),
}, nil
}
// get footer
if rangeHeader == "bytes=24613139-24613185" {
footer, _ := os.ReadFile("testdata/stargzfooter.bin")
return &http.Response{
StatusCode: http.StatusPartialContent,
Body: io.NopCloser(bytes.NewReader(footer)),
}, nil
}
if rangeHeader == "bytes=24442675-24613138" {
toc, _ := os.ReadFile("testdata/stargztoc.bin")
return &http.Response{
StatusCode: http.StatusPartialContent,
Body: io.NopCloser(bytes.NewReader(toc)),
}, nil
}
}
return &http.Response{
StatusCode: 200,
}, nil
}
================================================
FILE: pkg/stargz/testdata/config/nydus.json
================================================
{
"device": {
"backend": {
"type": "registry",
"config": {
"auth": "",
"timeout": 5,
"connect_timeout": 5,
"retry_limit": 0
}
},
"cache": {
"type": "blobcache",
"config": {
"work_dir": ""
}
}
},
"mode": "direct",
"digest_validate": true,
"iostats_files": true,
"enable_xattr": true,
"amplify_io": 1048576,
"fs_prefetch": {
"enable": true,
"threads_count": 10,
"merging_size": 131072
}
}
================================================
FILE: pkg/stargz/testdata/stargz.index.json
================================================
{
"version": 1,
"entries": [
{
"name": "bin/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "bin/bash",
"type": "reg",
"size": 1099016,
"modtime": "2017-05-15T19:45:32Z",
"mode": 33261,
"offset": 123,
"NumLink": 0,
"digest": "sha256:3f0bc167fa8ef1f7a38452a5fd16d077ed8ef657bb81cdce5af9537ca96e8345"
},
{
"name": "bin/cat",
"type": "reg",
"size": 35688,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 557313,
"NumLink": 0,
"digest": "sha256:f1e77796eaa4f465ff57128d4a66b3aaebc1c1c9812a015bc2726da949886f6c"
},
{
"name": "bin/chgrp",
"type": "reg",
"size": 64424,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 574029,
"NumLink": 0,
"digest": "sha256:48774490af3d77e3e7a03f1b0dc06676db9b06866422847d49423297a19a96fe"
},
{
"name": "bin/chmod",
"type": "reg",
"size": 60296,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 604734,
"NumLink": 0,
"digest": "sha256:a514b561e2007d38c8ea158ff62243db30dae741a95fc1da35165ce55b58bcad"
},
{
"name": "bin/chown",
"type": "reg",
"size": 64456,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 634251,
"NumLink": 0,
"digest": "sha256:066d6564f9a97370e347d2b792e2d6d4c8e2705ff2ce8b437a0c402202cd4908"
},
{
"name": "bin/cp",
"type": "reg",
"size": 130504,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 666106,
"NumLink": 0,
"digest": "sha256:e0ba82626269bd1f2d71761ef299508f6a85954721c921f07992ec5563e1f437"
},
{
"name": "bin/dash",
"type": "reg",
"size": 117208,
"modtime": "2017-01-24T05:16:56Z",
"mode": 33261,
"offset": 729890,
"NumLink": 0,
"digest": "sha256:e803088e7938b328b0511957dcd0dd7b5600ec1940010c64dbd3814e3d75495f"
},
{
"name": "bin/date",
"type": "reg",
"size": 105448,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 788656,
"NumLink": 0,
"digest": "sha256:f4c8f40e23e7825a2d6c018adb06de6be4172a252f6ac0241abef92b1b5b192f"
},
{
"name": "bin/dd",
"type": "reg",
"size": 76816,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 839666,
"NumLink": 0,
"digest": "sha256:71dcc2044f22e9575881502576f648c8fccb1327126d067a7db0a3d83fa04ac3"
},
{
"name": "bin/df",
"type": "reg",
"size": 85688,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 876584,
"NumLink": 0,
"digest": "sha256:5c2810e81a1dda76573e378e3468a54594c0322a1def87951d090b084a18918d"
},
{
"name": "bin/dir",
"type": "reg",
"size": 130736,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 919547,
"NumLink": 0,
"digest": "sha256:a42642d5bad1f3a79817513a06646b98b129de7d1df64ef7a6a1aea5e917cc44"
},
{
"name": "bin/dmesg",
"type": "reg",
"size": 73032,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 981644,
"NumLink": 0,
"digest": "sha256:fc00594e32cc8c667f3695ff1db64ac7cc7c6ecdc9e007389b3e8ee2050cb7e8"
},
{
"name": "bin/dnsdomainname",
"type": "symlink",
"modtime": "2016-07-03T19:26:17Z",
"linkName": "hostname",
"mode": 41471,
"NumLink": 0
},
{
"name": "bin/domainname",
"type": "symlink",
"modtime": "2016-07-03T19:26:17Z",
"linkName": "hostname",
"mode": 41471,
"NumLink": 0
},
{
"name": "bin/echo",
"type": "reg",
"size": 31464,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 1010882,
"NumLink": 0,
"digest": "sha256:615183f74a4f135160e2323720678396454266d17647874816f2843714bad102"
},
{
"name": "bin/egrep",
"type": "reg",
"size": 28,
"modtime": "2017-01-23T18:18:59Z",
"mode": 33261,
"offset": 1025997,
"NumLink": 0,
"digest": "sha256:f7c621ae0ceb26a76802743830bc469288996f64342901ae5292950ff713e981"
},
{
"name": "bin/false",
"type": "reg",
"size": 31464,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 1026120,
"NumLink": 0,
"digest": "sha256:127ae383b59b92a437692c9dd0095800bb40347b8a8488358c595c76d85e29ac"
},
{
"name": "bin/fgrep",
"type": "reg",
"size": 28,
"modtime": "2017-01-23T18:18:59Z",
"mode": 33261,
"offset": 1039843,
"NumLink": 0,
"digest": "sha256:5c8b1486de899cdd010d3cacde94579999cb82d0be9ec8c131b1b56886cfd36b"
},
{
"name": "bin/findmnt",
"type": "reg",
"size": 62072,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 1039969,
"NumLink": 0,
"digest": "sha256:ecbf8f39f78385c8dfb35b5495471aa4144ab3c3169e89465419d3c5ba5aae27"
},
{
"name": "bin/grep",
"type": "reg",
"size": 215360,
"modtime": "2017-01-23T18:18:59Z",
"mode": 33261,
"offset": 1066887,
"NumLink": 0,
"digest": "sha256:5772e5f9aea26318c6ab587d38988013d7127531ee185984fec45874c4379596"
},
{
"name": "bin/gunzip",
"type": "reg",
"size": 2301,
"modtime": "2016-03-14T20:41:45Z",
"mode": 33261,
"offset": 1169811,
"NumLink": 0,
"digest": "sha256:091abe52520c96a75cf7d4ff38796fc878cd62c3a75a3fd8161aa3df1e26bebd"
},
{
"name": "bin/gzexe",
"type": "reg",
"size": 5927,
"modtime": "2016-03-14T20:41:45Z",
"mode": 33261,
"offset": 1171050,
"NumLink": 0,
"digest": "sha256:6de8254cfd49543097ae946c303602ffd5899b2c88ec27cfcd86d786f95a1e92"
},
{
"name": "bin/gzip",
"type": "reg",
"size": 102408,
"modtime": "2016-03-14T20:41:45Z",
"mode": 33261,
"offset": 1173484,
"NumLink": 0,
"digest": "sha256:3b7dea216f49346ddd68c471deefa345d5cdfb9d21da0942909d47e39d23b23d"
},
{
"name": "bin/hostname",
"type": "reg",
"size": 18992,
"modtime": "2016-07-03T19:26:17Z",
"mode": 33261,
"offset": 1228033,
"NumLink": 0,
"digest": "sha256:f3187f6c1d9951dc45c8a3dd4579e9e1a24037156cc1126724c18be50a1c8348"
},
{
"name": "bin/ln",
"type": "reg",
"size": 56240,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 1234279,
"NumLink": 0,
"digest": "sha256:2af73dbb7990bc6d3a21aaa386d400b96ccd4df2ff4c0751e786c6c1dc9c0e65"
},
{
"name": "bin/login",
"type": "reg",
"size": 52632,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 1261422,
"NumLink": 0,
"digest": "sha256:07369ff7775bc32fb0e4c977e132a442bf94c68454ddea08a3d02912c4ccb30f"
},
{
"name": "bin/ls",
"type": "reg",
"size": 130736,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 1282844,
"NumLink": 0,
"digest": "sha256:a0e06b5a72fed6c106391cf0162dbee1750c047640117bfea2234857055216a0"
},
{
"name": "bin/lsblk",
"type": "reg",
"size": 81376,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 1344940,
"NumLink": 0,
"digest": "sha256:3f9a7fb1e45651b0b0b4609e3b4597282efeef3f777bb75a986edec97dfcf7cf"
},
{
"name": "bin/mkdir",
"type": "reg",
"size": 81032,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 1381190,
"NumLink": 0,
"digest": "sha256:46254c850ec5d828557ef58c958e793ad96b948161c8378fd4954c3632b560f8"
},
{
"name": "bin/mknod",
"type": "reg",
"size": 68648,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 1420908,
"NumLink": 0,
"digest": "sha256:02d68294caac9d375456f35e26c99d17abb27cce90abb31ecb346a26fc7b56e9"
},
{
"name": "bin/mktemp",
"type": "reg",
"size": 43912,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 1454052,
"NumLink": 0,
"digest": "sha256:3ec6d01605f2a1d0518003dfc0822e9d7f75eafa337a5e4d575db0d98b8325a3"
},
{
"name": "bin/more",
"type": "reg",
"size": 39752,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 1473855,
"NumLink": 0,
"digest": "sha256:c8f448b9780748ae395fefb8abb09a4f94eaba1796996880979c15a2f970c3fa"
},
{
"name": "bin/mount",
"type": "reg",
"size": 44304,
"modtime": "2018-03-07T18:29:09Z",
"mode": 35309,
"offset": 1493006,
"NumLink": 0,
"digest": "sha256:4df3d21877d09f670bc6c0a9021fecf0b0faa4434f8383ea048cbf1528f32910"
},
{
"name": "bin/mountpoint",
"type": "reg",
"size": 14840,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 1509324,
"NumLink": 0,
"digest": "sha256:a5640eba9a1104278409564b771b53276a5f0f48692052a7dcf76a70f9f80ad3"
},
{
"name": "bin/mv",
"type": "reg",
"size": 126416,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 1514148,
"NumLink": 0,
"digest": "sha256:a9c0bbf66cff9f6870a3fa58c7182afb1020ff99282a6e48c82a75931d638b21"
},
{
"name": "bin/nisdomainname",
"type": "symlink",
"modtime": "2016-07-03T19:26:17Z",
"linkName": "hostname",
"mode": 41471,
"NumLink": 0
},
{
"name": "bin/pidof",
"type": "symlink",
"modtime": "2017-02-12T21:55:39Z",
"linkName": "/sbin/killall5",
"mode": 41471,
"NumLink": 0
},
{
"name": "bin/pwd",
"type": "reg",
"size": 35656,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 1577351,
"NumLink": 0,
"digest": "sha256:d27a17c6a7ded3550c37d49e6fd8875a86641a01e18f76c297e43ee65d0a1297"
},
{
"name": "bin/rbash",
"type": "symlink",
"modtime": "2017-05-15T19:45:32Z",
"linkName": "bash",
"mode": 41471,
"NumLink": 0
},
{
"name": "bin/readlink",
"type": "reg",
"size": 43816,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 1593217,
"NumLink": 0,
"digest": "sha256:539ab7e307835455c670f725fafb9831df823ea59e64d11967b9bc43f7dec749"
},
{
"name": "bin/rm",
"type": "reg",
"size": 64424,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 1613850,
"NumLink": 0,
"digest": "sha256:661eba5491e486bb9308dc6fcfd65aaa790471ea5f6bf1b5f775672cd982389e"
},
{
"name": "bin/rmdir",
"type": "reg",
"size": 43816,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 1645395,
"NumLink": 0,
"digest": "sha256:0d02f44490836b4f010e17cc2197921ebf0e58cbca0f206609b59fbd52cbca9d"
},
{
"name": "bin/run-parts",
"type": "reg",
"size": 19288,
"modtime": "2017-04-02T17:10:33Z",
"mode": 33261,
"offset": 1665557,
"NumLink": 0,
"digest": "sha256:a37652fd9ee165d666902bd4e7a3967969dc6fc7ff0d7f017fab3260c93967e9"
},
{
"name": "bin/sed",
"type": "reg",
"size": 105808,
"modtime": "2017-02-04T15:16:08Z",
"mode": 33261,
"offset": 1673379,
"NumLink": 0,
"digest": "sha256:ab8fbc38bf9f67c706d7d422a4ef4c051eb5c4c21579037f4d184ebf189f5e79"
},
{
"name": "bin/sh",
"type": "symlink",
"modtime": "2017-01-24T05:16:56Z",
"linkName": "dash",
"mode": 41471,
"NumLink": 0
},
{
"name": "bin/sh.distrib",
"type": "symlink",
"modtime": "2017-01-24T05:16:56Z",
"linkName": "dash",
"mode": 41471,
"NumLink": 0
},
{
"name": "bin/sleep",
"type": "reg",
"size": 35592,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 1727499,
"NumLink": 0,
"digest": "sha256:196bce40af413a2188c4ca548db12d710334af5523a0d5e4f2e9bedeb987c39c"
},
{
"name": "bin/stty",
"type": "reg",
"size": 76680,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 1742845,
"NumLink": 0,
"digest": "sha256:52fe1b6ffec2a667b411c5b131d86c8fa444bbbcacf3bcbaf43745141bd66321"
},
{
"name": "bin/su",
"type": "reg",
"size": 40536,
"modtime": "2017-05-17T11:59:59Z",
"mode": 35309,
"offset": 1775410,
"NumLink": 0,
"digest": "sha256:0c1d3341d124b09395a959f2674da1d775703e66c285ed309031c2b8cc77f75e"
},
{
"name": "bin/sync",
"type": "reg",
"size": 31496,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 1791921,
"NumLink": 0,
"digest": "sha256:94e555750f59f1ddf60451ffd1902d520a786751f71894b3bce2e75a9ac4d841"
},
{
"name": "bin/tailf",
"type": "reg",
"size": 23232,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 1806686,
"NumLink": 0,
"digest": "sha256:fc2d8a0225c79247fb2d539a2d854210bcbd3512ccb45c526ac67c9f71995407"
},
{
"name": "bin/tar",
"type": "reg",
"size": 416896,
"modtime": "2016-10-30T06:35:31Z",
"mode": 33261,
"offset": 1817371,
"NumLink": 0,
"digest": "sha256:3a216ed63dced5ea5ea949c4f11ee78a7f7aa715de76cb87498940a3433e5b3d"
},
{
"name": "bin/tempfile",
"type": "reg",
"size": 10680,
"modtime": "2017-04-02T17:10:33Z",
"mode": 33261,
"offset": 2021609,
"NumLink": 0,
"digest": "sha256:6702e5b02aaa841832ddc9cfc6cbb6333b2d03ca88839d2cd8dcedaae4e07b8d"
},
{
"name": "bin/touch",
"type": "reg",
"size": 93160,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 2025161,
"NumLink": 0,
"digest": "sha256:952786e16e51f9ee059472abc0d8f1ec9e237af253e09fe49a975445aaaec589"
},
{
"name": "bin/true",
"type": "reg",
"size": 31464,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 2070363,
"NumLink": 0,
"digest": "sha256:5a2de760cb026c9749bf515dd1ad258b3c8b554fb4ba4bc2fd33f99ae834e472"
},
{
"name": "bin/umount",
"type": "reg",
"size": 31720,
"modtime": "2018-03-07T18:29:09Z",
"mode": 35309,
"offset": 2084085,
"NumLink": 0,
"digest": "sha256:4c90ff10f6b7dc1c61e06166792f90954ca4071c4cb43942b991ffd5e1eeee3b"
},
{
"name": "bin/uname",
"type": "reg",
"size": 35592,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 2095736,
"NumLink": 0,
"digest": "sha256:6eb9b55fc05a8938ac49c78d336f8f8669195db70756b9ff3cb9d31e6107c95b"
},
{
"name": "bin/uncompress",
"type": "hardlink",
"modtime": "2016-03-14T20:41:45Z",
"linkName": "bin/gunzip",
"mode": 33261,
"NumLink": 0
},
{
"name": "bin/vdir",
"type": "reg",
"size": 130736,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 2110686,
"NumLink": 0,
"digest": "sha256:b42f70b011e4069795a55ee63719c9e6ed533ed356073eab38092ca92bb6bd9a"
},
{
"name": "bin/wdctl",
"type": "reg",
"size": 31464,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 2172779,
"NumLink": 0,
"digest": "sha256:657b50892f2acf5a3c1b0af2ced09a9cf7f70c1537c86fce5552a9936f55102d"
},
{
"name": "bin/which",
"type": "reg",
"size": 946,
"modtime": "2017-04-02T17:10:33Z",
"mode": 33261,
"offset": 2186759,
"NumLink": 0,
"digest": "sha256:7bdde142dc5cb004ab82f55adba0c56fc78430a6f6b23afd33be491d4c7c238b"
},
{
"name": "bin/ypdomainname",
"type": "symlink",
"modtime": "2016-07-03T19:26:17Z",
"linkName": "hostname",
"mode": 41471,
"NumLink": 0
},
{
"name": "bin/zcat",
"type": "reg",
"size": 1937,
"modtime": "2016-03-14T20:41:45Z",
"mode": 33261,
"offset": 2187365,
"NumLink": 0,
"digest": "sha256:aaf7b2c7036c2597450e109198ea7b5870c164591e2501b1891a3e767dfc0e00"
},
{
"name": "bin/zcmp",
"type": "reg",
"size": 1777,
"modtime": "2016-03-14T20:41:45Z",
"mode": 33261,
"offset": 2188438,
"NumLink": 0,
"digest": "sha256:ae3c2b73273b7d953d432b10c50612011cb0bd0f245220d8ec10a5a47239d86d"
},
{
"name": "bin/zdiff",
"type": "reg",
"size": 5764,
"modtime": "2016-03-14T20:41:45Z",
"mode": 33261,
"offset": 2189446,
"NumLink": 0,
"digest": "sha256:97f3993ead63a1ce0f6a48cda92d6655ffe210242fe057b8803506b57c99b7bc"
},
{
"name": "bin/zegrep",
"type": "reg",
"size": 140,
"modtime": "2016-03-14T20:41:45Z",
"mode": 33261,
"offset": 2191473,
"NumLink": 0,
"digest": "sha256:da2da96324108bbe297a75e8ebfcb2400959bffcdaa4c88b797c4d0ce0c94c50"
},
{
"name": "bin/zfgrep",
"type": "reg",
"size": 140,
"modtime": "2016-03-14T20:41:45Z",
"mode": 33261,
"offset": 2191669,
"NumLink": 0,
"digest": "sha256:b9711301d3ab42575597d8a1c015f49fddba9a7ea9934e11d38b9ff5248503a8"
},
{
"name": "bin/zforce",
"type": "reg",
"size": 2131,
"modtime": "2016-03-14T20:41:45Z",
"mode": 33261,
"offset": 2191867,
"NumLink": 0,
"digest": "sha256:6936c9aa8e17781410f286bb1cbc35b5548ea4e7604c1379dc8e159d91a0193d"
},
{
"name": "bin/zgrep",
"type": "reg",
"size": 5938,
"modtime": "2016-03-14T20:41:45Z",
"mode": 33261,
"offset": 2193069,
"NumLink": 0,
"digest": "sha256:1cab9ec4e390ceba0ddd47e7d4537527df5c1991c5b35208a29005e1494b408d"
},
{
"name": "bin/zless",
"type": "reg",
"size": 2037,
"modtime": "2016-03-14T20:41:45Z",
"mode": 33261,
"offset": 2195544,
"NumLink": 0,
"digest": "sha256:14dc8106ec64c9e2a7c9430e1d0bef170aaad0f5f7f683c1c1810b466cdf5079"
},
{
"name": "bin/zmore",
"type": "reg",
"size": 1910,
"modtime": "2016-03-14T20:41:45Z",
"mode": 33261,
"offset": 2196723,
"NumLink": 0,
"digest": "sha256:f66077e5cc686ee33c99149972abdfd3a36d3e125cc4b42c215a0d2701eeaf6e"
},
{
"name": "bin/znew",
"type": "reg",
"size": 5047,
"modtime": "2016-03-14T20:41:45Z",
"mode": 33261,
"offset": 2197786,
"NumLink": 0,
"digest": "sha256:01764d96697b060b2a449769073b7cf2df61b5cb604937e39dd7a47017e92ee0"
},
{
"name": "boot/",
"type": "dir",
"modtime": "2018-06-26T12:03:08Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "dev/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/.pwd.lock",
"type": "reg",
"modtime": "2018-10-11T00:00:00Z",
"mode": 33152,
"NumLink": 0,
"digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"name": "etc/adduser.conf",
"type": "reg",
"size": 2981,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 2200038,
"NumLink": 0,
"digest": "sha256:ccdc9675d7bd39c3cc79c2a5d6938f2562dd4062350dbed3058c1faee48b353e"
},
{
"name": "etc/alternatives/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/alternatives/README",
"type": "reg",
"size": 100,
"modtime": "2018-06-26T10:28:08Z",
"mode": 33188,
"offset": 2201526,
"NumLink": 0,
"digest": "sha256:100fc671b65c94241e6013d3a7332a1f5d1a97e0324e37f4ff4851fec607497e"
},
{
"name": "etc/alternatives/awk",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "/usr/bin/mawk",
"mode": 41471,
"NumLink": 0
},
{
"name": "etc/alternatives/awk.1.gz",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "/usr/share/man/man1/mawk.1.gz",
"mode": 41471,
"NumLink": 0
},
{
"name": "etc/alternatives/builtins.7.gz",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "/usr/share/man/man7/bash-builtins.7.gz",
"mode": 41471,
"NumLink": 0
},
{
"name": "etc/alternatives/nawk",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "/usr/bin/mawk",
"mode": 41471,
"NumLink": 0
},
{
"name": "etc/alternatives/nawk.1.gz",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "/usr/share/man/man1/mawk.1.gz",
"mode": 41471,
"NumLink": 0
},
{
"name": "etc/alternatives/pager",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "/bin/more",
"mode": 41471,
"NumLink": 0
},
{
"name": "etc/alternatives/pager.1.gz",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "/usr/share/man/man1/more.1.gz",
"mode": 41471,
"NumLink": 0
},
{
"name": "etc/alternatives/rmt",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "/usr/sbin/rmt-tar",
"mode": 41471,
"NumLink": 0
},
{
"name": "etc/alternatives/rmt.8.gz",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "/usr/share/man/man8/rmt-tar.8.gz",
"mode": 41471,
"NumLink": 0
},
{
"name": "etc/apt/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/apt/apt.conf.d/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/apt/apt.conf.d/01autoremove",
"type": "reg",
"size": 769,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33188,
"offset": 2201963,
"NumLink": 0,
"digest": "sha256:295419b57259cb583b726064b66ffcd3b625999c637905fdc63e06432afcf752"
},
{
"name": "etc/apt/apt.conf.d/70debconf",
"type": "reg",
"size": 182,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 2202387,
"NumLink": 0,
"digest": "sha256:db749e19baf3b72ca2c157c70c52522cae23d94bc8b2dc5793fd43d427445367"
},
{
"name": "etc/apt/apt.conf.d/docker-autoremove-suggests",
"type": "reg",
"size": 754,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 2202646,
"NumLink": 0,
"digest": "sha256:84cb100b000873f25f677cf23ab51f841eb490264fa496361591d6071aebf0f4"
},
{
"name": "etc/apt/apt.conf.d/docker-clean",
"type": "reg",
"size": 1175,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 2203183,
"NumLink": 0,
"digest": "sha256:484f60f8d215bf22008b5fe7dd8484350d7abd44655ea79accee79ce11976c81"
},
{
"name": "etc/apt/apt.conf.d/docker-gzip-indexes",
"type": "reg",
"size": 481,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 2203874,
"NumLink": 0,
"digest": "sha256:9da4604fbac169caa47e787b4d85c8e06c887603878afa2a61f39fd551a41359"
},
{
"name": "etc/apt/apt.conf.d/docker-no-languages",
"type": "reg",
"size": 269,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 2204295,
"NumLink": 0,
"digest": "sha256:a7fea31d968796bf459a439279abdf8d45f5e3f68bbaa9e09dbd9e7f3bd831f6"
},
{
"name": "etc/apt/preferences.d/",
"type": "dir",
"modtime": "2017-09-13T16:47:33Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/apt/sources.list",
"type": "reg",
"size": 168,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 2204630,
"NumLink": 0,
"digest": "sha256:5c58eef9e4b1520d146082c7781f775f3fe1bd7d2749a7f7d8db21bd2a9b4ae5"
},
{
"name": "etc/apt/sources.list.d/",
"type": "dir",
"modtime": "2017-09-13T16:47:33Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/apt/trusted.gpg.d/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/apt/trusted.gpg.d/debian-archive-jessie-automatic.gpg",
"type": "reg",
"size": 5138,
"modtime": "2017-05-25T18:17:13Z",
"mode": 33188,
"offset": 2204887,
"NumLink": 0,
"digest": "sha256:d03faac2548c2e653d8fbfdb4088f5ef7b7633cca6a1afa8a67c4df3f445e19f"
},
{
"name": "etc/apt/trusted.gpg.d/debian-archive-jessie-security-automatic.gpg",
"type": "reg",
"size": 5147,
"modtime": "2017-05-25T18:17:13Z",
"mode": 33188,
"offset": 2210121,
"NumLink": 0,
"digest": "sha256:4691fd79441853a23cbd6fc991983cba296280159dfce0ef70508d064bd4c898"
},
{
"name": "etc/apt/trusted.gpg.d/debian-archive-jessie-stable.gpg",
"type": "reg",
"size": 2775,
"modtime": "2017-05-25T18:17:13Z",
"mode": 33188,
"offset": 2215357,
"NumLink": 0,
"digest": "sha256:bcc458a220eb49df0cc61da109c815bdb2db17c5712f5734e5ebc4e28f0ce7a4"
},
{
"name": "etc/apt/trusted.gpg.d/debian-archive-stretch-automatic.gpg",
"type": "reg",
"size": 7483,
"modtime": "2017-05-25T18:17:13Z",
"mode": 33188,
"offset": 2218333,
"NumLink": 0,
"digest": "sha256:1f1c4eba7c74aa485a0da3b170dd0b3d5ea4d5ada4ec9201a2616ec0a3f4130b"
},
{
"name": "etc/apt/trusted.gpg.d/debian-archive-stretch-security-automatic.gpg",
"type": "reg",
"size": 7492,
"modtime": "2017-05-25T18:17:13Z",
"mode": 33188,
"offset": 2225734,
"NumLink": 0,
"digest": "sha256:1eb9ab7a15f18580d449658ff1ba432d06b0a6ac7e0937f933c831cd5ef36d62"
},
{
"name": "etc/apt/trusted.gpg.d/debian-archive-stretch-stable.gpg",
"type": "reg",
"size": 2275,
"modtime": "2017-05-25T18:17:13Z",
"mode": 33188,
"offset": 2233132,
"NumLink": 0,
"digest": "sha256:4d221cb762e470f73d3200d30354b5c39f28c4a1d1d1c2bc01a81a0eac427d7f"
},
{
"name": "etc/apt/trusted.gpg.d/debian-archive-wheezy-automatic.gpg",
"type": "reg",
"size": 3780,
"modtime": "2017-05-25T18:17:13Z",
"mode": 33188,
"offset": 2235599,
"NumLink": 0,
"digest": "sha256:13b45eb5716c72be9831bb1a9e18672ad23f3f41884df63fa42b09eb7d8d30ae"
},
{
"name": "etc/apt/trusted.gpg.d/debian-archive-wheezy-stable.gpg",
"type": "reg",
"size": 2851,
"modtime": "2017-05-25T18:17:13Z",
"mode": 33188,
"offset": 2239517,
"NumLink": 0,
"digest": "sha256:9637cd12bf4cd1f33a24a714dbcaa76bfb1dd5de5c94ae0e9b621616e2d15e34"
},
{
"name": "etc/bash.bashrc",
"type": "reg",
"size": 1863,
"modtime": "2017-05-15T19:45:32Z",
"mode": 33188,
"offset": 2242516,
"NumLink": 0,
"digest": "sha256:5638e43ea1289e856d5fbbc80f96d1e2cf249c04976870fa5481278e081a0163"
},
{
"name": "etc/bindresvport.blacklist",
"type": "reg",
"size": 367,
"modtime": "2017-04-09T21:28:38Z",
"mode": 33188,
"offset": 2243473,
"NumLink": 0,
"digest": "sha256:63229551ffc257f56e3df60ca97e1f2963f3ab2128ce27a0f398b4418fa454d0"
},
{
"name": "etc/cron.daily/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/cron.daily/apt-compat",
"type": "reg",
"size": 1474,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 2243845,
"NumLink": 0,
"digest": "sha256:8eeae3a9df22621d51062e4dadfc5c63b49732b38a37b5d4e52c99c2237e5767"
},
{
"name": "etc/cron.daily/dpkg",
"type": "reg",
"size": 1597,
"modtime": "2018-06-26T02:48:17Z",
"mode": 33261,
"offset": 2244691,
"NumLink": 0,
"digest": "sha256:c08441d2782fbde6e06a6f27cf84ae647efb67b5305919e739431b19f284ec95"
},
{
"name": "etc/cron.daily/passwd",
"type": "reg",
"size": 249,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 2245528,
"NumLink": 0,
"digest": "sha256:777a9112ee093d8683645b031eb6cfeb9ce77274f40575c48ff2054ea24114d1"
},
{
"name": "etc/debconf.conf",
"type": "reg",
"size": 2969,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 2245764,
"NumLink": 0,
"digest": "sha256:fe7e76d4162e80e0bc8c24bc638c56ae92c07a80db750cbf0a87e0904e143f4e"
},
{
"name": "etc/debian_version",
"type": "reg",
"size": 4,
"modtime": "2018-06-26T12:03:08Z",
"mode": 33188,
"offset": 2247154,
"NumLink": 0,
"digest": "sha256:02da5eaddc974610c7221b297e33005e32beead4b2d6371e8625a5ed1494033f"
},
{
"name": "etc/default/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/default/hwclock",
"type": "reg",
"size": 657,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 2247299,
"NumLink": 0,
"digest": "sha256:827cf5c033ba11433d2b4087ec1e36e82766eab39ceed8e7f8f09d983d2d8235"
},
{
"name": "etc/default/nss",
"type": "reg",
"size": 1756,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 2247771,
"NumLink": 0,
"digest": "sha256:836614e9d4d501d0af43087c9c9300365a38d53f24f845efcf0b2ad8194cbaa0"
},
{
"name": "etc/default/useradd",
"type": "reg",
"size": 1118,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33188,
"offset": 2248690,
"NumLink": 0,
"digest": "sha256:03b603de7e8c7a8192902d827edea1891360bd094cf2c810669af13aee823c70"
},
{
"name": "etc/deluser.conf",
"type": "reg",
"size": 604,
"modtime": "2016-06-26T20:00:56Z",
"mode": 33188,
"offset": 2249396,
"NumLink": 0,
"digest": "sha256:946e0f11a8997bf41dbafca1f6f5a4bedf46746c91801ca4f2e90dd0172f06b6"
},
{
"name": "etc/dpkg/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/dpkg/dpkg.cfg",
"type": "reg",
"size": 446,
"modtime": "2014-11-28T01:08:21Z",
"mode": 33188,
"offset": 2249890,
"NumLink": 0,
"digest": "sha256:fead43b89af3ea5691c48f32d7fe1ba0f7ab229fb5d230f612d76fe8e6f5a015"
},
{
"name": "etc/dpkg/dpkg.cfg.d/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/dpkg/dpkg.cfg.d/docker",
"type": "reg",
"size": 3543,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 2250297,
"NumLink": 0,
"digest": "sha256:5493667488dbf55f6aff36bac8c35362faa45803b4d350af959a76a5aaf2dda1"
},
{
"name": "etc/dpkg/dpkg.cfg.d/docker-apt-speedup",
"type": "reg",
"size": 259,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 2251381,
"NumLink": 0,
"digest": "sha256:ab3af717d57cbbea36555833dc1ae031fa46750b879199ec579ee00be9aa0124"
},
{
"name": "etc/dpkg/origins/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/dpkg/origins/debian",
"type": "reg",
"size": 82,
"modtime": "2009-02-02T23:06:58Z",
"mode": 33188,
"offset": 2251704,
"NumLink": 0,
"digest": "sha256:50f35af8ac4a5df3690991a4b428fa49d56580b0020fcc6e38283b3b1b2e6c74"
},
{
"name": "etc/dpkg/origins/default",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "debian",
"mode": 41471,
"NumLink": 0
},
{
"name": "etc/environment",
"type": "reg",
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"NumLink": 0,
"digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"name": "etc/fstab",
"type": "reg",
"size": 37,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 2251928,
"NumLink": 0,
"digest": "sha256:a6b093c9916c6c54e5d634d3689f1a0132e14cce0b8e50ff445da8e85acfbd17"
},
{
"name": "etc/gai.conf",
"type": "reg",
"size": 2584,
"modtime": "2016-08-02T02:01:36Z",
"mode": 33188,
"offset": 2252062,
"NumLink": 0,
"digest": "sha256:76a5771adee7b9f36c7ae66eae78d72f325557500269107f2d98a7e3560a1808"
},
{
"name": "etc/group",
"type": "reg",
"size": 446,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 2253248,
"NumLink": 0,
"digest": "sha256:639aef8980b6ea2ea640187f4117523b4ade4b7289a5f8b3ef8ad7bb1a731fa7"
},
{
"name": "etc/group-",
"type": "reg",
"size": 446,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33152,
"offset": 2253598,
"NumLink": 0,
"digest": "sha256:cfde4574c857edbfbd31667000d75d07efe6bc61f22063693010dee2a912450b"
},
{
"name": "etc/gshadow",
"type": "reg",
"size": 374,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33184,
"gid": 42,
"offset": 2253956,
"NumLink": 0,
"digest": "sha256:9f095e317e25ddd4a4bc3b58d5aaa08e20bbe8a0e42ca745b0e6ba6d17d61a34"
},
{
"name": "etc/host.conf",
"type": "reg",
"size": 9,
"modtime": "2006-08-07T17:14:09Z",
"mode": 33188,
"offset": 2254224,
"NumLink": 0,
"digest": "sha256:380f5fe21d755923b44203b58ca3c8b9681c485d152bd5d7e3914f67d821d32a"
},
{
"name": "etc/hostname",
"type": "reg",
"size": 14,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 2254330,
"NumLink": 0,
"digest": "sha256:411eb208674138b8ce7624993e678f6b1d8a39c6f2bc4156e10d9466e0c71d2b"
},
{
"name": "etc/init.d/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/init.d/hwclock.sh",
"type": "reg",
"size": 3809,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 2254481,
"NumLink": 0,
"digest": "sha256:a919f9434b681974a2f1d4120af10c0527b30e8cda6fdec1dea1eee3077b6609"
},
{
"name": "etc/issue",
"type": "reg",
"size": 26,
"modtime": "2018-06-26T12:03:08Z",
"mode": 33188,
"offset": 2256157,
"NumLink": 0,
"digest": "sha256:3836ea85d67769580f775bb6c2e547b8f1167b00810f00f54bd9ff31fbb5ca12"
},
{
"name": "etc/issue.net",
"type": "reg",
"size": 19,
"modtime": "2018-06-26T12:03:08Z",
"mode": 33188,
"offset": 2256283,
"NumLink": 0,
"digest": "sha256:202afaaf205a231b5cc398d1b78caf8d2d476d108d9ba47c62d3c5105acebe1d"
},
{
"name": "etc/kernel/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/kernel/postinst.d/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/kernel/postinst.d/apt-auto-removal",
"type": "reg",
"size": 2903,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 2256468,
"NumLink": 0,
"digest": "sha256:64fef575566fd0ab647200ed6849156d07432997d22c65a06694c8852bdb7255"
},
{
"name": "etc/ld.so.cache",
"type": "reg",
"size": 5912,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 2257911,
"NumLink": 0,
"digest": "sha256:1b041de9bcd1a26dce46de0dd5b29ad0cfc11991925102bc1ac66bad20a4e372"
},
{
"name": "etc/ld.so.conf",
"type": "reg",
"size": 34,
"modtime": "2017-04-09T21:28:38Z",
"mode": 33188,
"offset": 2259376,
"NumLink": 0,
"digest": "sha256:d4b198c463418b493208485def26a6f4c57279467b9dfa491b70433cedb602e8"
},
{
"name": "etc/ld.so.conf.d/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/ld.so.conf.d/libc.conf",
"type": "reg",
"size": 44,
"modtime": "2016-03-20T23:45:12Z",
"mode": 33188,
"offset": 2259532,
"NumLink": 0,
"digest": "sha256:90d4c7e43e7661cd116010eb9f50ad5817e43162df344bd1ad10898851b15d41"
},
{
"name": "etc/ld.so.conf.d/x86_64-linux-gnu.conf",
"type": "reg",
"size": 68,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 2259690,
"NumLink": 0,
"digest": "sha256:54ad1c27463ba4c81f7cf9f58621e34ab679c760692ac046845940390fb4e612"
},
{
"name": "etc/libaudit.conf",
"type": "reg",
"size": 191,
"modtime": "2017-04-12T16:17:21Z",
"mode": 33188,
"offset": 2259840,
"NumLink": 0,
"digest": "sha256:d48318c90620fde96cb6a8e6eb1eb64663b21200f9d1d053f9e3b4fce24a2543"
},
{
"name": "etc/localtime",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "/usr/share/zoneinfo/Etc/UTC",
"mode": 41471,
"NumLink": 0
},
{
"name": "etc/login.defs",
"type": "reg",
"size": 10477,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33188,
"offset": 2260119,
"NumLink": 0,
"digest": "sha256:ff7cf6759ea81d72c51ab879e751c23bb4aa73307b737743aae9f034a314e7b6"
},
{
"name": "etc/logrotate.d/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/logrotate.d/apt",
"type": "reg",
"size": 173,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33188,
"offset": 2264687,
"NumLink": 0,
"digest": "sha256:fcc2510172fd914ca22762c4b2dc43d36152e65becf8e84abec59f7652da5e3f"
},
{
"name": "etc/logrotate.d/dpkg",
"type": "reg",
"size": 232,
"modtime": "2018-06-26T02:48:17Z",
"mode": 33188,
"offset": 2264867,
"NumLink": 0,
"digest": "sha256:a65183e9cf4e0d9c655514495e204b437baef0717c8207639c2d60d6d7a801c2"
},
{
"name": "etc/machine-id",
"type": "reg",
"size": 33,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 2265064,
"NumLink": 0,
"digest": "sha256:7dcaeff856e487b63782f158ad6de51af878a00135068cecba7c9d4ba0df41a9"
},
{
"name": "etc/mke2fs.conf",
"type": "reg",
"size": 973,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33188,
"offset": 2265192,
"NumLink": 0,
"digest": "sha256:437ffd423945a6427ebb62de923667aea851fc05c79be7428eecea14a024f35e"
},
{
"name": "etc/motd",
"type": "reg",
"size": 286,
"modtime": "2018-06-26T12:03:08Z",
"mode": 33188,
"offset": 2265645,
"NumLink": 0,
"digest": "sha256:a378977155fb42bb006496321cbe31f74cbda803c3f6ca590f30e76d1afad921"
},
{
"name": "etc/nsswitch.conf",
"type": "reg",
"size": 497,
"modtime": "2017-12-31T12:12:42Z",
"mode": 33188,
"offset": 2265946,
"NumLink": 0,
"digest": "sha256:e241e67d7b5c15a5ace818d89277507b5ded8b49688b7a4431afb3b1041a3759"
},
{
"name": "etc/opt/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/os-release",
"type": "symlink",
"modtime": "2018-06-26T12:03:08Z",
"linkName": "../usr/lib/os-release",
"mode": 41471,
"NumLink": 0
},
{
"name": "etc/pam.conf",
"type": "reg",
"size": 552,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 2266374,
"NumLink": 0,
"digest": "sha256:8aa7f3472ec88a24a572d6ffd9748ce3da223fba3b2545098eaaae768b6408c4"
},
{
"name": "etc/pam.d/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/pam.d/chfn",
"type": "reg",
"size": 384,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33188,
"offset": 2266702,
"NumLink": 0,
"digest": "sha256:d66a095a330d7e20d0bbb56a4cb28a4b1bfc92e8a5a5e9bfc3d0a51c5e3d7170"
},
{
"name": "etc/pam.d/chpasswd",
"type": "reg",
"size": 92,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33188,
"offset": 2267039,
"NumLink": 0,
"digest": "sha256:f3f96229e82bf41a7fd3ec12e697b3465235d96bb1e44c39ba91157425a36082"
},
{
"name": "etc/pam.d/chsh",
"type": "reg",
"size": 581,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33188,
"offset": 2267222,
"NumLink": 0,
"digest": "sha256:0101e7e589ce40435c5a8709888225400a78ab6be86dfc5fef86ee23ba5338ad"
},
{
"name": "etc/pam.d/common-account",
"type": "reg",
"size": 1208,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 2267648,
"NumLink": 0,
"digest": "sha256:aa8a63d72e79399b6c51ebe4e9f828c954145a799eb4b8f3224724f51cbb9fac"
},
{
"name": "etc/pam.d/common-auth",
"type": "reg",
"size": 1221,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 2268373,
"NumLink": 0,
"digest": "sha256:df3ed820b414b7163bd61b623e6b7e5896d541f9df392240606a23aa4f421d4a"
},
{
"name": "etc/pam.d/common-password",
"type": "reg",
"size": 1440,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 2269107,
"NumLink": 0,
"digest": "sha256:ede17d3b24602a774a51fde44a5afd536f73eb3da65f948926ab7952b474d3b0"
},
{
"name": "etc/pam.d/common-session",
"type": "reg",
"size": 1156,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 2269931,
"NumLink": 0,
"digest": "sha256:6dc1d9be5915053dec2ac18f1c0b6b3389b3f0ed74383dde4a46dec81edefe68"
},
{
"name": "etc/pam.d/common-session-noninteractive",
"type": "reg",
"size": 1154,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 2270623,
"NumLink": 0,
"digest": "sha256:d137251095e22fca44fcc5c993699e46446673a085dab8238de6a670fb3cc7f2"
},
{
"name": "etc/pam.d/login",
"type": "reg",
"size": 4945,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33188,
"offset": 2271302,
"NumLink": 0,
"digest": "sha256:42e1e68613a5e0d2942ceeb8b5a4544341bde732fed47630108a163545359f6d"
},
{
"name": "etc/pam.d/newusers",
"type": "reg",
"size": 92,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33188,
"offset": 2273285,
"NumLink": 0,
"digest": "sha256:26e75ce7c9066801b8db380ff9d8ba58a5e8cf2de5fb38ffd1db5ba62c85acef"
},
{
"name": "etc/pam.d/other",
"type": "reg",
"size": 520,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 2273468,
"NumLink": 0,
"digest": "sha256:d13078e71d3351ef7f63a7265ddb50b710a2598b9febc78810fbb0130a02695a"
},
{
"name": "etc/pam.d/passwd",
"type": "reg",
"size": 92,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33188,
"offset": 2273845,
"NumLink": 0,
"digest": "sha256:87696fad1046d6b33b6d3407bb419980987331b4dcd8905f7a6041bced90c51d"
},
{
"name": "etc/pam.d/runuser",
"type": "reg",
"size": 143,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 2274030,
"NumLink": 0,
"digest": "sha256:2d430cb6628248953568010427d663f3305856f3cb055955c2239bea226c5280"
},
{
"name": "etc/pam.d/runuser-l",
"type": "reg",
"size": 138,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 2274230,
"NumLink": 0,
"digest": "sha256:be9329a8b26e3cfd4af879fe60900f646f8188f3fbe491688f23d4d8b491c5b1"
},
{
"name": "etc/pam.d/su",
"type": "reg",
"size": 2257,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33188,
"offset": 2274416,
"NumLink": 0,
"digest": "sha256:f7cac62fbcd50f9931d09a9190fc3ec390fd48fb5b8bec57e0996a7246856b12"
},
{
"name": "etc/passwd",
"type": "reg",
"size": 919,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 2275504,
"NumLink": 0,
"digest": "sha256:f3df8ec25c77cb0e445aab589c5fef4d77868f875fa742c0d42fccb9e1a50754"
},
{
"name": "etc/passwd-",
"type": "reg",
"size": 919,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33152,
"offset": 2275953,
"NumLink": 0,
"digest": "sha256:f3df8ec25c77cb0e445aab589c5fef4d77868f875fa742c0d42fccb9e1a50754"
},
{
"name": "etc/profile",
"type": "reg",
"size": 767,
"modtime": "2016-03-04T11:00:00Z",
"mode": 33188,
"offset": 2276404,
"NumLink": 0,
"digest": "sha256:b8ffd2c97588047e1cea84b7dfdb68bfde167e2957f667ca2b6ab2929feb4f49"
},
{
"name": "etc/profile.d/",
"type": "dir",
"modtime": "2018-06-26T12:03:08Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/rc0.d/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/rc0.d/K01hwclock.sh",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "../init.d/hwclock.sh",
"mode": 41471,
"NumLink": 0
},
{
"name": "etc/rc1.d/",
"type": "dir",
"modtime": "2017-05-02T10:20:21Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/rc2.d/",
"type": "dir",
"modtime": "2017-05-02T10:20:21Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/rc3.d/",
"type": "dir",
"modtime": "2017-05-02T10:20:21Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/rc4.d/",
"type": "dir",
"modtime": "2017-05-02T10:20:21Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/rc5.d/",
"type": "dir",
"modtime": "2017-05-02T10:20:21Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/rc6.d/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/rc6.d/K01hwclock.sh",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "../init.d/hwclock.sh",
"mode": 41471,
"NumLink": 0
},
{
"name": "etc/rcS.d/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/rcS.d/S01hwclock.sh",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "../init.d/hwclock.sh",
"mode": 41471,
"NumLink": 0
},
{
"name": "etc/resolv.conf",
"type": "reg",
"size": 104,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 2277035,
"NumLink": 0,
"digest": "sha256:a535ac16b8fe54e9e1afbb474c4c19fd8faeddbac8b1e1c17f8f52191ce84f96"
},
{
"name": "etc/rmt",
"type": "reg",
"size": 268,
"modtime": "2016-10-30T06:35:31Z",
"mode": 33261,
"offset": 2277208,
"NumLink": 0,
"digest": "sha256:0bdd822a1df82458bdf623d6869f9d0817f6e66f517366a67a4770dbbe714dd2"
},
{
"name": "etc/securetty",
"type": "reg",
"size": 4179,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33188,
"offset": 2277489,
"NumLink": 0,
"digest": "sha256:49310071ed72eb85aa785d2934051a9ca846f91a474b7fb9b76290444f6b16df"
},
{
"name": "etc/security/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/security/access.conf",
"type": "reg",
"size": 4620,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 2279287,
"NumLink": 0,
"digest": "sha256:cb2d5cbb78756240990da65ce40431a0ccb4f09746868c6a874234ba5fb42f2b"
},
{
"name": "etc/security/group.conf",
"type": "reg",
"size": 3635,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 2281174,
"NumLink": 0,
"digest": "sha256:41df4bc646811997d0390c6d37d839d2aef4a9a1a940c44ee1a798a9dc1ac864"
},
{
"name": "etc/security/limits.conf",
"type": "reg",
"size": 2150,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 2282877,
"NumLink": 0,
"digest": "sha256:69ff0dea1ee42bc6eda9077627610d3181e6fa82a9a3d03e36f20d35c3dbab9e"
},
{
"name": "etc/security/limits.d/",
"type": "dir",
"modtime": "2017-05-27T15:44:02Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/security/namespace.conf",
"type": "reg",
"size": 1440,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 2283786,
"NumLink": 0,
"digest": "sha256:d0c3045ba5071b8375fde6165d4e4db9b69f49af5d2525cecd2bca1cb7538552"
},
{
"name": "etc/security/namespace.d/",
"type": "dir",
"modtime": "2017-05-27T15:44:02Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/security/namespace.init",
"type": "reg",
"size": 1016,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33261,
"offset": 2284546,
"NumLink": 0,
"digest": "sha256:2d76094c06f10839c566ef64bde5624c325aeab7991e7f5d776c5310e8f41932"
},
{
"name": "etc/security/opasswd",
"type": "reg",
"modtime": "2018-10-11T00:00:00Z",
"mode": 33152,
"NumLink": 0,
"digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"name": "etc/security/pam_env.conf",
"type": "reg",
"size": 2972,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 2285163,
"NumLink": 0,
"digest": "sha256:ff4956721a3f53e56e25ffffde62fe4fa0267e5dd94c3411def12de50322fb8f"
},
{
"name": "etc/security/sepermit.conf",
"type": "reg",
"size": 419,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 2286669,
"NumLink": 0,
"digest": "sha256:885ec2a43042ad88d7f849e5e1cbef4e34e58229764a84a927c0e09cd7d47d70"
},
{
"name": "etc/security/time.conf",
"type": "reg",
"size": 2179,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 2286990,
"NumLink": 0,
"digest": "sha256:6802adfc8efc6168f87e98e960fa7d15e516a295fef7a6472ef5359527e886ff"
},
{
"name": "etc/selinux/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/selinux/semanage.conf",
"type": "reg",
"size": 2041,
"modtime": "2016-12-30T15:42:09Z",
"mode": 33188,
"offset": 2288206,
"NumLink": 0,
"digest": "sha256:80464fb793459392ffbf1e79e57df3247a7b2fe413854f2c155848a0b8c6004f"
},
{
"name": "etc/shadow",
"type": "reg",
"size": 501,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33184,
"gid": 42,
"offset": 2289342,
"NumLink": 0,
"digest": "sha256:c1845e019de82ee247c38d602bd78dd32c53b156789869c53e447399f362d824"
},
{
"name": "etc/shadow-",
"type": "reg",
"size": 501,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33152,
"offset": 2289547,
"NumLink": 0,
"digest": "sha256:c1845e019de82ee247c38d602bd78dd32c53b156789869c53e447399f362d824"
},
{
"name": "etc/shells",
"type": "reg",
"size": 73,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 2289752,
"NumLink": 0,
"digest": "sha256:ddbebd60d26dd772114734225c89b80ef95f3cffa69031ae2b6aaeda791c4652"
},
{
"name": "etc/skel/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/skel/.bash_logout",
"type": "reg",
"size": 220,
"modtime": "2017-05-15T19:45:32Z",
"mode": 33188,
"offset": 2289933,
"NumLink": 0,
"digest": "sha256:26882b79471c25f945c970f8233d8ce29d54e9d5eedcd2884f88affa84a18f56"
},
{
"name": "etc/skel/.bashrc",
"type": "reg",
"size": 3526,
"modtime": "2017-05-15T19:45:32Z",
"mode": 33188,
"offset": 2290189,
"NumLink": 0,
"digest": "sha256:afae8986f549c6403410e029f9cce7983311512d04b1f02af02e4ce0af0dd2bf"
},
{
"name": "etc/skel/.profile",
"type": "reg",
"size": 675,
"modtime": "2017-05-15T19:45:32Z",
"mode": 33188,
"offset": 2291873,
"NumLink": 0,
"digest": "sha256:86512cad76131783f5dae4346ddc3fb39f6f7c0f74b3039bff70ca4015ade034"
},
{
"name": "etc/staff-group-for-usr-local",
"type": "reg",
"size": 771,
"modtime": "2012-06-09T10:00:00Z",
"mode": 33188,
"offset": 2292362,
"NumLink": 0,
"digest": "sha256:64a482506f00572df1d4909a347d6f4fa8e6ce23686b7f058bfbd650ec0658ce"
},
{
"name": "etc/subgid",
"type": "reg",
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"NumLink": 0,
"digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"name": "etc/subuid",
"type": "reg",
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"NumLink": 0,
"digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"name": "etc/systemd/",
"type": "dir",
"modtime": "2017-05-02T10:20:21Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/systemd/system/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/systemd/system/timers.target.wants/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/systemd/system/timers.target.wants/apt-daily-upgrade.timer",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "/lib/systemd/system/apt-daily-upgrade.timer",
"mode": 41471,
"NumLink": 0
},
{
"name": "etc/systemd/system/timers.target.wants/apt-daily.timer",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "/lib/systemd/system/apt-daily.timer",
"mode": 41471,
"NumLink": 0
},
{
"name": "etc/terminfo/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/terminfo/README",
"type": "reg",
"size": 212,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2293065,
"NumLink": 0,
"digest": "sha256:cfc3399b782bb0ecb14b9727dbd5ffd82ef774d473f6c47c39e621f8f4850603"
},
{
"name": "etc/timezone",
"type": "reg",
"size": 8,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 2293303,
"NumLink": 0,
"digest": "sha256:f0dcac7b1d721d2f68937a71f0229b4c4f88564fd711339951528889913cd85d"
},
{
"name": "etc/update-motd.d/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "etc/update-motd.d/10-uname",
"type": "reg",
"size": 23,
"modtime": "2017-04-04T12:00:00Z",
"mode": 33261,
"offset": 2293453,
"NumLink": 0,
"digest": "sha256:1dca09550a75048731bbd17f17e027cc71ae50a86e0d911a8b3813e88d9b5ab6"
},
{
"name": "home/",
"type": "dir",
"modtime": "2018-06-26T12:03:08Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib/init/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib/init/init-d-script",
"type": "reg",
"size": 5643,
"modtime": "2017-02-12T21:55:39Z",
"mode": 33261,
"offset": 2293643,
"NumLink": 0,
"digest": "sha256:31c2f323e3ea8d633880fd46f35954e2e91139c254c887c17cb06add05ef20e0"
},
{
"name": "lib/init/vars.sh",
"type": "reg",
"size": 1212,
"modtime": "2017-02-12T21:55:39Z",
"mode": 33188,
"offset": 2295656,
"NumLink": 0,
"digest": "sha256:49d734860b46c62805fc29a67b9d61210113b6eac2409e4ffd5cf14589f4f0a3"
},
{
"name": "lib/lsb/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib/lsb/init-functions",
"type": "reg",
"size": 11510,
"modtime": "2016-11-25T15:00:10Z",
"mode": 33188,
"offset": 2296440,
"NumLink": 0,
"digest": "sha256:1ca0a1b8aa2acd634075589ef49172d6125e8f7cada2069b8576fc090c953eb5"
},
{
"name": "lib/lsb/init-functions.d/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib/lsb/init-functions.d/20-left-info-blocks",
"type": "reg",
"size": 1088,
"modtime": "2014-08-28T23:04:48Z",
"mode": 33188,
"offset": 2300293,
"NumLink": 0,
"digest": "sha256:10997f92734d15a0a49c153f47cbd11553a2f3d8132f7191e6676a69444f8810"
},
{
"name": "lib/systemd/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib/systemd/system/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib/systemd/system/apt-daily-upgrade.service",
"type": "reg",
"size": 238,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33188,
"offset": 2300807,
"NumLink": 0,
"digest": "sha256:c8e4b846bd3da8819c6c4101556742239234e2cb3f670418fdbfe33606b2c5ba"
},
{
"name": "lib/systemd/system/apt-daily-upgrade.timer",
"type": "reg",
"size": 184,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33188,
"offset": 2301099,
"NumLink": 0,
"digest": "sha256:b804d7bab8eb41202384f9270e25d5383346ace8b3d7c4f5029c150638d77bcd"
},
{
"name": "lib/systemd/system/apt-daily.service",
"type": "reg",
"size": 225,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33188,
"offset": 2301356,
"NumLink": 0,
"digest": "sha256:aaec2cdb7384fc2f5c75105e9daf6b3f5dae507ab6bfe1ea2fb7fbb8ff112c7f"
},
{
"name": "lib/systemd/system/apt-daily.timer",
"type": "reg",
"size": 156,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33188,
"offset": 2301626,
"NumLink": 0,
"digest": "sha256:0075e974af4e3a94757e219ba50ccb8348d4d1a8834d938f6cc9b1f4fd1db4e5"
},
{
"name": "lib/terminfo/",
"type": "dir",
"modtime": "2017-12-28T09:47:33Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib/terminfo/E/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib/terminfo/E/Eterm",
"type": "reg",
"size": 2267,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2301908,
"NumLink": 0,
"digest": "sha256:599e0a90f89a32e077f51ec8a86540680e4ae1faf99c539bc357a693b892cd6d"
},
{
"name": "lib/terminfo/E/Eterm-color",
"type": "symlink",
"modtime": "2017-12-28T09:47:33Z",
"linkName": "Eterm",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/terminfo/a/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib/terminfo/a/ansi",
"type": "reg",
"size": 1481,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2303240,
"NumLink": 0,
"digest": "sha256:93ec8cb9beb0c898ebc7dda0f670de31addb605be9005735228680d592cff657"
},
{
"name": "lib/terminfo/c/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib/terminfo/c/cons25",
"type": "reg",
"size": 1502,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2303991,
"NumLink": 0,
"digest": "sha256:acd69b88fbc9045037b562dd67c876e88cc5d2616af20b9ca6c41d33ee335606"
},
{
"name": "lib/terminfo/c/cons25-debian",
"type": "reg",
"size": 1519,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2304827,
"NumLink": 0,
"digest": "sha256:0437ef75abb06ca00a0ca444a8aa7402276b7217a20330217c84b59fdd8e0b4f"
},
{
"name": "lib/terminfo/c/cygwin",
"type": "reg",
"size": 1518,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2305668,
"NumLink": 0,
"digest": "sha256:1a9b75481dba09d8f73b9b13f39d52db026bfc7ae8eb4de1d250388756445dbc"
},
{
"name": "lib/terminfo/d/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib/terminfo/d/dumb",
"type": "reg",
"size": 308,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2306487,
"NumLink": 0,
"digest": "sha256:123c85a2812a517d967db5f31660db0e6aded4a0b95ed943c5ab435368e7a25c"
},
{
"name": "lib/terminfo/h/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib/terminfo/h/hurd",
"type": "reg",
"size": 1570,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2306686,
"NumLink": 0,
"digest": "sha256:d5dc00724a04eb3b030addab6914380521d40f416818943171070ec64c623607"
},
{
"name": "lib/terminfo/l/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib/terminfo/l/linux",
"type": "reg",
"size": 1788,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2307548,
"NumLink": 0,
"digest": "sha256:b864e16853fe9aa36a43abd59a3d349026a993a74bc9775880cafb1c9bb6675e"
},
{
"name": "lib/terminfo/m/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib/terminfo/m/mach",
"type": "reg",
"size": 617,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2308528,
"NumLink": 0,
"digest": "sha256:ecd31c58040e5908eb434514e67620b2e4be538655126f427155760b273c7e9b"
},
{
"name": "lib/terminfo/m/mach-bold",
"type": "reg",
"size": 652,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2308967,
"NumLink": 0,
"digest": "sha256:4e4400e3ad4df2dbbf90920860c540cd72552ca71a24b556a0b6ba62fa091b84"
},
{
"name": "lib/terminfo/m/mach-color",
"type": "reg",
"size": 1095,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2309431,
"NumLink": 0,
"digest": "sha256:5caa825bd606e26c8b6c55a3206eccfea525e788f74da5e7cb48cc713db52239"
},
{
"name": "lib/terminfo/m/mach-gnu",
"type": "reg",
"size": 1056,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2309925,
"NumLink": 0,
"digest": "sha256:99372cd399478be723230692595362004df345dee6c4145e4d109113a2357717"
},
{
"name": "lib/terminfo/m/mach-gnu-color",
"type": "reg",
"size": 1318,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2310545,
"NumLink": 0,
"digest": "sha256:e1c62541670d0e10fe46daabce8ce95d9fd77115a68106e5eb2c2a7647e40a13"
},
{
"name": "lib/terminfo/p/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib/terminfo/p/pcansi",
"type": "reg",
"size": 1198,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2311246,
"NumLink": 0,
"digest": "sha256:ecda9662049c96ee0a574f40cfb8950b0198b508b5b72a3de05774eb3cb3f34e"
},
{
"name": "lib/terminfo/r/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib/terminfo/r/rxvt",
"type": "reg",
"size": 2078,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2311812,
"NumLink": 0,
"digest": "sha256:cb5b104f26f34e48f9ca6b5626b1f2247f82548b198ae28aa0524b2bdc345557"
},
{
"name": "lib/terminfo/r/rxvt-basic",
"type": "reg",
"size": 2034,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2313001,
"NumLink": 0,
"digest": "sha256:43f20d35ddc7766d030e99d0b850a439e6d97a257aea86ae61bc52b76131d5c1"
},
{
"name": "lib/terminfo/r/rxvt-m",
"type": "symlink",
"modtime": "2017-12-28T09:47:33Z",
"linkName": "rxvt-basic",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/terminfo/r/rxvt-unicode",
"type": "reg",
"size": 2508,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2314190,
"NumLink": 0,
"digest": "sha256:280165734528e93ec7c770524e8ce3a3d29dcf5ca5696dacd093d1eb5ce3460a"
},
{
"name": "lib/terminfo/s/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib/terminfo/s/screen",
"type": "reg",
"size": 1594,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2315571,
"NumLink": 0,
"digest": "sha256:2354bf40efa7c42d938abc9091da7dcfd80b2fc35bbeef6e16ae2e77ffa6a28c"
},
{
"name": "lib/terminfo/s/screen-256color",
"type": "reg",
"size": 1912,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2316442,
"NumLink": 0,
"digest": "sha256:15c3d76efa9475f696ca7f73a22dbe0e91a3cc1d2f316d347577042c6534b154"
},
{
"name": "lib/terminfo/s/screen-256color-bce",
"type": "reg",
"size": 1924,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2317434,
"NumLink": 0,
"digest": "sha256:3a12dcef9fcf27bb68948d692ada56b088ff9c00e902c292447e3edd4ac349a2"
},
{
"name": "lib/terminfo/s/screen-bce",
"type": "reg",
"size": 1590,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2318437,
"NumLink": 0,
"digest": "sha256:479d4fc2e09e17723741a8f69f79dc59f539e28be6fd1dbe9853a54675c8ac71"
},
{
"name": "lib/terminfo/s/screen-s",
"type": "reg",
"size": 1612,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2319302,
"NumLink": 0,
"digest": "sha256:93b4fd80b4159ee730b1881d747d7d173bfd69de3eb6b5325dec66e4f61c2dc8"
},
{
"name": "lib/terminfo/s/screen-w",
"type": "reg",
"size": 1594,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2320187,
"NumLink": 0,
"digest": "sha256:8a80c790b6f86666214cf9189f67581502a4cf74b8d1ce8001d3bba7ce82221e"
},
{
"name": "lib/terminfo/s/sun",
"type": "reg",
"size": 1004,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2321051,
"NumLink": 0,
"digest": "sha256:02e392161cb23f49a8fb1ba2f1a6583e013c0c26672f58c5eaca828db3b19914"
},
{
"name": "lib/terminfo/v/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib/terminfo/v/vt100",
"type": "reg",
"size": 1194,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2321583,
"NumLink": 0,
"digest": "sha256:c919070d5dd70856abcda7993ec63680e0a30b997693926301a2049b419d3fb9"
},
{
"name": "lib/terminfo/v/vt102",
"type": "reg",
"size": 1188,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2322290,
"NumLink": 0,
"digest": "sha256:5af8abfb4747049ccde158eee68a8b8138528ece70fddf31b5c85afbdc03dd67"
},
{
"name": "lib/terminfo/v/vt220",
"type": "reg",
"size": 1377,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2322998,
"NumLink": 0,
"digest": "sha256:75a4723bfcdcd22756366838f1d65233f386d7592b019740c8ca5b578e9a5857"
},
{
"name": "lib/terminfo/v/vt52",
"type": "reg",
"size": 470,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2323827,
"NumLink": 0,
"digest": "sha256:1d8e7d40be89fe71e5d2582caa5168fe53ed85d9063e0ccf42e5c53f4d17b069"
},
{
"name": "lib/terminfo/w/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib/terminfo/w/wsvt25",
"type": "reg",
"size": 1597,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2324150,
"NumLink": 0,
"digest": "sha256:28d3410e6b83a3b78a41f108098ac8772a3af3ee2b627b9f9bb4b19b363a5be3"
},
{
"name": "lib/terminfo/w/wsvt25m",
"type": "reg",
"size": 1607,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2325046,
"NumLink": 0,
"digest": "sha256:18c85db3b0ef0ab15b7eb8dc4ac6ea14a37d851628220c8bb61e2edfa4f81683"
},
{
"name": "lib/terminfo/x/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib/terminfo/x/xterm",
"type": "reg",
"size": 3360,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2325980,
"NumLink": 0,
"digest": "sha256:c51120adbae844f9bb500711c753335bb3235192e54dff4f576bf56d23c51a9b"
},
{
"name": "lib/terminfo/x/xterm-256color",
"type": "reg",
"size": 3430,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2327737,
"NumLink": 0,
"digest": "sha256:7c820689eed8803db2741a0c0832eb9049ab38ce65670b0ce305ca286f15f921"
},
{
"name": "lib/terminfo/x/xterm-color",
"type": "reg",
"size": 1569,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2329533,
"NumLink": 0,
"digest": "sha256:0b270450c7498756c0e99cfb24341e68f7443344adcf1656e30a0333e48f550f"
},
{
"name": "lib/terminfo/x/xterm-debian",
"type": "symlink",
"modtime": "2017-12-28T09:47:33Z",
"linkName": "xterm",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/terminfo/x/xterm-mono",
"type": "reg",
"size": 1515,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2330325,
"NumLink": 0,
"digest": "sha256:183c527a8cbec5b5fef3eb8c28473a0530317eea7d01150eec74c01a214fef59"
},
{
"name": "lib/terminfo/x/xterm-r5",
"type": "reg",
"size": 1301,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2331036,
"NumLink": 0,
"digest": "sha256:82098ec067be6189e91e8264278bb85fe3b7bfdeaa3754be301313be140522ca"
},
{
"name": "lib/terminfo/x/xterm-r6",
"type": "reg",
"size": 1491,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2331654,
"NumLink": 0,
"digest": "sha256:ee12fe6d2d8e1d0b83d1042fe8a38f1aed6fd73e2c7316e6db5ec5b061b09ef8"
},
{
"name": "lib/terminfo/x/xterm-vt220",
"type": "reg",
"size": 2316,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2332359,
"NumLink": 0,
"digest": "sha256:bbb3183473a03823612e1bdfa6d4ba7207b36f37b688cf45f217345064f9bb4c"
},
{
"name": "lib/terminfo/x/xterm-xfree86",
"type": "reg",
"size": 2265,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 2333528,
"NumLink": 0,
"digest": "sha256:2c5638bcc74b380e8d70032c88a5920b116bfd5d3ef84168e9b52a0c217c9a9a"
},
{
"name": "lib/udev/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib/udev/hwclock-set",
"type": "reg",
"size": 776,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 2334755,
"NumLink": 0,
"digest": "sha256:bec691723c892b77efe3c718846c2f320b4218d6448c8cf71d663e3be8396564"
},
{
"name": "lib/udev/rules.d/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib/udev/rules.d/85-hwclock.rules",
"type": "reg",
"size": 204,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 2335252,
"NumLink": 0,
"digest": "sha256:a65b7d818578fa91ebc00d086964c0528fa28d0ca04c54f11009c6c375f3e158"
},
{
"name": "lib/x86_64-linux-gnu/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/ld-2.24.so",
"type": "reg",
"size": 153288,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33261,
"offset": 2335549,
"NumLink": 0,
"digest": "sha256:1e73928ade92803c17729c193954735d3ca8d22e228cd95da4cb0c6ed8996df6"
},
{
"name": "lib/x86_64-linux-gnu/ld-linux-x86-64.so.2",
"type": "symlink",
"modtime": "2018-01-14T10:39:44Z",
"linkName": "ld-2.24.so",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libBrokenLocale-2.24.so",
"type": "reg",
"size": 6304,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 2415597,
"NumLink": 0,
"digest": "sha256:d4743e9fdefe3c995545d378713d7bd8c756d554513a74e9e6413b8941ad1b19"
},
{
"name": "lib/x86_64-linux-gnu/libBrokenLocale.so.1",
"type": "symlink",
"modtime": "2018-01-14T10:39:44Z",
"linkName": "libBrokenLocale-2.24.so",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libSegFault.so",
"type": "reg",
"size": 18736,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 2417375,
"NumLink": 0,
"digest": "sha256:23d3c4e5a8cd6ebf20fb62bca2b5bacd9282dae432eb0eadafccaaf17d9e3ab0"
},
{
"name": "lib/x86_64-linux-gnu/libacl.so.1",
"type": "symlink",
"modtime": "2016-02-06T22:10:44Z",
"linkName": "libacl.so.1.1.0",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libacl.so.1.1.0",
"type": "reg",
"size": 35488,
"modtime": "2016-02-06T22:10:44Z",
"mode": 33188,
"offset": 2423409,
"NumLink": 0,
"digest": "sha256:b9c9311dbc806b487e098f844b06b68070b8681cbd4f71511f29647c188e9f1d"
},
{
"name": "lib/x86_64-linux-gnu/libanl-2.24.so",
"type": "reg",
"size": 15024,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 2439331,
"NumLink": 0,
"digest": "sha256:c7575604b8072decb9046118d5e650111428db5cece0aac60fc280a2c2f051a9"
},
{
"name": "lib/x86_64-linux-gnu/libanl.so.1",
"type": "symlink",
"modtime": "2018-01-14T10:39:44Z",
"linkName": "libanl-2.24.so",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libattr.so.1",
"type": "symlink",
"modtime": "2014-09-08T07:27:07Z",
"linkName": "libattr.so.1.1.0",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libattr.so.1.1.0",
"type": "reg",
"size": 18832,
"modtime": "2014-09-08T07:27:07Z",
"mode": 33188,
"offset": 2445552,
"NumLink": 0,
"digest": "sha256:290a147cfa1c559e27bddef432a125c91f6ba8d3bad33555d125afe2748949aa"
},
{
"name": "lib/x86_64-linux-gnu/libaudit.so.1",
"type": "symlink",
"modtime": "2017-04-12T16:17:21Z",
"linkName": "libaudit.so.1.0.0",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libaudit.so.1.0.0",
"type": "reg",
"size": 120752,
"modtime": "2017-04-12T16:17:21Z",
"mode": 33188,
"offset": 2453677,
"NumLink": 0,
"digest": "sha256:11f89d5e0d0f069eeba5210970669602983da5a0f136a108d5011bb3f96e5119"
},
{
"name": "lib/x86_64-linux-gnu/libblkid.so.1",
"type": "symlink",
"modtime": "2018-03-07T18:29:09Z",
"linkName": "libblkid.so.1.1.0",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libblkid.so.1.1.0",
"type": "reg",
"size": 283464,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 2497883,
"NumLink": 0,
"digest": "sha256:95780e2f163855c81b56c94c3e40266d7d1b9cfaa472b0d7d252e90d9b6c3a23"
},
{
"name": "lib/x86_64-linux-gnu/libbz2.so.1",
"type": "symlink",
"modtime": "2017-01-29T18:30:31Z",
"linkName": "libbz2.so.1.0.4",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libbz2.so.1.0",
"type": "symlink",
"modtime": "2017-01-29T18:30:31Z",
"linkName": "libbz2.so.1.0.4",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libbz2.so.1.0.4",
"type": "reg",
"size": 66992,
"modtime": "2017-01-29T18:30:31Z",
"mode": 33188,
"offset": 2625921,
"NumLink": 0,
"digest": "sha256:9ebec8ea0b22a7e53f4b24d3e543120454eb228af53fa641eafba6f5e3824cb2"
},
{
"name": "lib/x86_64-linux-gnu/libc-2.24.so",
"type": "reg",
"size": 1689360,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33261,
"offset": 2657158,
"NumLink": 0,
"digest": "sha256:b2c8bf64c2cafdce5cb263cc100749b5e6b3e4783eef2fff16ae1b4bc5633e16"
},
{
"name": "lib/x86_64-linux-gnu/libc.so.6",
"type": "symlink",
"modtime": "2018-01-14T10:39:44Z",
"linkName": "libc-2.24.so",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libcap-ng.so.0",
"type": "symlink",
"modtime": "2016-07-03T19:04:40Z",
"linkName": "libcap-ng.so.0.0.0",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libcap-ng.so.0.0.0",
"type": "reg",
"size": 22944,
"modtime": "2016-07-03T19:04:40Z",
"mode": 33188,
"offset": 3402905,
"NumLink": 0,
"digest": "sha256:d6f690e38dee69b10da966bfbf594345328f57993453a0e5606a950216d8e783"
},
{
"name": "lib/x86_64-linux-gnu/libcidn-2.24.so",
"type": "reg",
"size": 190888,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 3411878,
"NumLink": 0,
"digest": "sha256:0f04fd41040235d4aff6bca686da46d8fed6bdf78b6011496fed4f40cd6b8bb1"
},
{
"name": "lib/x86_64-linux-gnu/libcidn.so.1",
"type": "symlink",
"modtime": "2018-01-14T10:39:44Z",
"linkName": "libcidn-2.24.so",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libcom_err.so.2",
"type": "symlink",
"modtime": "2017-02-01T00:54:55Z",
"linkName": "libcom_err.so.2.1",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libcom_err.so.2.1",
"type": "reg",
"size": 14248,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33188,
"offset": 3472898,
"NumLink": 0,
"digest": "sha256:d4be0a20cba38d6c62e06ccd45b2142f8fa798227d6a0a41ce98689940b611a8"
},
{
"name": "lib/x86_64-linux-gnu/libcrypt-2.24.so",
"type": "reg",
"size": 39256,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 3478207,
"NumLink": 0,
"digest": "sha256:f52cec1c11e6d0023332a319ba9b7544450bd252a5b1d4853cf8095de9c13407"
},
{
"name": "lib/x86_64-linux-gnu/libcrypt.so.1",
"type": "symlink",
"modtime": "2018-01-14T10:39:44Z",
"linkName": "libcrypt-2.24.so",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libdl-2.24.so",
"type": "reg",
"size": 14640,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 3496376,
"NumLink": 0,
"digest": "sha256:91a225e90a28326285fac19ea582e32bf02bbc081d6be78df6326cef8e97eee9"
},
{
"name": "lib/x86_64-linux-gnu/libdl.so.2",
"type": "symlink",
"modtime": "2018-01-14T10:39:44Z",
"linkName": "libdl-2.24.so",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libe2p.so.2",
"type": "symlink",
"modtime": "2017-02-01T00:54:55Z",
"linkName": "libe2p.so.2.3",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libe2p.so.2.3",
"type": "reg",
"size": 32264,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33188,
"offset": 3501377,
"NumLink": 0,
"digest": "sha256:dc040f0ec8112483eb1f26986ca459f15fe45d4ec12b20cfdd8877dde0aa8717"
},
{
"name": "lib/x86_64-linux-gnu/libext2fs.so.2",
"type": "symlink",
"modtime": "2017-02-01T00:54:55Z",
"linkName": "libext2fs.so.2.4",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libext2fs.so.2.4",
"type": "reg",
"size": 331536,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33188,
"offset": 3514555,
"NumLink": 0,
"digest": "sha256:fb1d8fb387e6f452eafac6f1b428b38edb8c124d0302daed21bc3f111fd359c4"
},
{
"name": "lib/x86_64-linux-gnu/libfdisk.so.1",
"type": "symlink",
"modtime": "2018-03-07T18:29:09Z",
"linkName": "libfdisk.so.1.1.0",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libfdisk.so.1.1.0",
"type": "reg",
"size": 381000,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 3674215,
"NumLink": 0,
"digest": "sha256:d1bc0b9de54b9b1400ad86ff1ec9133801ba8dfe228be0b038485d6cb6a27b77"
},
{
"name": "lib/x86_64-linux-gnu/libgcc_s.so.1",
"type": "reg",
"size": 92584,
"modtime": "2018-02-14T16:53:20Z",
"mode": 33188,
"offset": 3845933,
"NumLink": 0,
"digest": "sha256:41124e5da0f8d4ee3368868846bebc6bb8953edea57b1c481cf03f72bbc81e46"
},
{
"name": "lib/x86_64-linux-gnu/libgcrypt.so.20",
"type": "symlink",
"modtime": "2018-06-15T09:58:05Z",
"linkName": "libgcrypt.so.20.1.6",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libgcrypt.so.20.1.6",
"type": "reg",
"size": 1112184,
"modtime": "2018-06-15T09:58:05Z",
"mode": 33188,
"offset": 3889670,
"NumLink": 0,
"digest": "sha256:594967450ccdcbb04408bb8904dadef96ae2c9ec2ef1594a897e84c4af5cc1b0"
},
{
"name": "lib/x86_64-linux-gnu/libgpg-error.so.0",
"type": "symlink",
"modtime": "2017-01-18T16:27:10Z",
"linkName": "libgpg-error.so.0.21.0",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libgpg-error.so.0.21.0",
"type": "reg",
"size": 79936,
"modtime": "2017-01-18T16:27:10Z",
"mode": 33188,
"offset": 4347769,
"NumLink": 0,
"digest": "sha256:24781f22c712ea9e073df0e72fac6d720d14f5e7bb7ae0f9729af188aadb038d"
},
{
"name": "lib/x86_64-linux-gnu/liblzma.so.5",
"type": "symlink",
"modtime": "2016-10-08T13:11:19Z",
"linkName": "liblzma.so.5.2.2",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/liblzma.so.5.2.2",
"type": "reg",
"size": 154376,
"modtime": "2016-10-08T13:11:19Z",
"mode": 33188,
"offset": 4383258,
"NumLink": 0,
"digest": "sha256:5b8415cf12090162ee1086681eb2183c509962ffafed315225780a49aa35a205"
},
{
"name": "lib/x86_64-linux-gnu/libm-2.24.so",
"type": "reg",
"size": 1063328,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 4465352,
"NumLink": 0,
"digest": "sha256:289922c95eeb611a45fe25aa04e445244095406837408629d4bbc8365dd85b94"
},
{
"name": "lib/x86_64-linux-gnu/libm.so.6",
"type": "symlink",
"modtime": "2018-01-14T10:39:44Z",
"linkName": "libm-2.24.so",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libmemusage.so",
"type": "reg",
"size": 18808,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 5197320,
"NumLink": 0,
"digest": "sha256:0a4f0bc7c7aaeda9cd9e406f2a7711b9b58fac67a02c00add2a5cad505ce698d"
},
{
"name": "lib/x86_64-linux-gnu/libmount.so.1",
"type": "symlink",
"modtime": "2018-03-07T18:29:09Z",
"linkName": "libmount.so.1.1.0",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libmount.so.1.1.0",
"type": "reg",
"size": 313096,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 5204144,
"NumLink": 0,
"digest": "sha256:52742396b967444df7b701ed146637800950fa7e3f144d77e850d6a0052286c1"
},
{
"name": "lib/x86_64-linux-gnu/libmvec-2.24.so",
"type": "reg",
"size": 170464,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 5345904,
"NumLink": 0,
"digest": "sha256:0e8f744c1124445b8d71d510288887424f2a0e681fc327babb78d9520fe8015c"
},
{
"name": "lib/x86_64-linux-gnu/libmvec.so.1",
"type": "symlink",
"modtime": "2018-01-14T10:39:44Z",
"linkName": "libmvec-2.24.so",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libncursesw.so.5",
"type": "symlink",
"modtime": "2017-12-28T09:47:33Z",
"linkName": "libncursesw.so.5.9",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libncursesw.so.5.9",
"type": "reg",
"size": 194480,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 5442329,
"NumLink": 0,
"digest": "sha256:e07d728762a9d3db0a3f914241133a0273b644c980476c5cae884f6198514cc4"
},
{
"name": "lib/x86_64-linux-gnu/libnsl-2.24.so",
"type": "reg",
"size": 89064,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 5533494,
"NumLink": 0,
"digest": "sha256:c27427576c4c2a418b1751e1a1b0b433d44e160be90955af78f8c95ad3d99ebc"
},
{
"name": "lib/x86_64-linux-gnu/libnsl.so.1",
"type": "symlink",
"modtime": "2018-01-14T10:39:44Z",
"linkName": "libnsl-2.24.so",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libnss_compat-2.24.so",
"type": "reg",
"size": 31616,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 5573691,
"NumLink": 0,
"digest": "sha256:b20464e41de918df0a4d6a3654d6d4c7126a4691d640eea68e56512f2ee50b93"
},
{
"name": "lib/x86_64-linux-gnu/libnss_compat.so.2",
"type": "symlink",
"modtime": "2018-01-14T10:39:44Z",
"linkName": "libnss_compat-2.24.so",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libnss_dns-2.24.so",
"type": "reg",
"size": 22928,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 5587677,
"NumLink": 0,
"digest": "sha256:c825dd0cf1e0fa3d1c6027fe98ef37ef160ee05463a72bac5752af304e9299c8"
},
{
"name": "lib/x86_64-linux-gnu/libnss_dns.so.2",
"type": "symlink",
"modtime": "2018-01-14T10:39:44Z",
"linkName": "libnss_dns-2.24.so",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libnss_files-2.24.so",
"type": "reg",
"size": 47632,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 5598262,
"NumLink": 0,
"digest": "sha256:05e233e28d802968e27b3e4524f15813052f38fac4a53dff5bccee49cc3bcf77"
},
{
"name": "lib/x86_64-linux-gnu/libnss_files.so.2",
"type": "symlink",
"modtime": "2018-01-14T10:39:44Z",
"linkName": "libnss_files-2.24.so",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libnss_hesiod-2.24.so",
"type": "reg",
"size": 18880,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 5616060,
"NumLink": 0,
"digest": "sha256:96093b7999357700ccacfa9ad5b6bc17e8fba3db65fb01009eb46940b215afe5"
},
{
"name": "lib/x86_64-linux-gnu/libnss_hesiod.so.2",
"type": "symlink",
"modtime": "2018-01-14T10:39:44Z",
"linkName": "libnss_hesiod-2.24.so",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libnss_nis-2.24.so",
"type": "reg",
"size": 47688,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 5624293,
"NumLink": 0,
"digest": "sha256:8ba7bbfb7eba66e468c8349e1d55e52d3156b7c0dc67c8f22b24501f56d0f915"
},
{
"name": "lib/x86_64-linux-gnu/libnss_nis.so.2",
"type": "symlink",
"modtime": "2018-01-14T10:39:44Z",
"linkName": "libnss_nis-2.24.so",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libnss_nisplus-2.24.so",
"type": "reg",
"size": 51736,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 5644935,
"NumLink": 0,
"digest": "sha256:c05d785888d0a7e72d6ccc12d245ccfc72b5ffa9797d266a7d749d117d87e84d"
},
{
"name": "lib/x86_64-linux-gnu/libnss_nisplus.so.2",
"type": "symlink",
"modtime": "2018-01-14T10:39:44Z",
"linkName": "libnss_nisplus-2.24.so",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libpam.so.0",
"type": "symlink",
"modtime": "2017-05-27T15:44:02Z",
"linkName": "libpam.so.0.83.1",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libpam.so.0.83.1",
"type": "reg",
"size": 56016,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 5667737,
"NumLink": 0,
"digest": "sha256:010b0a9d601fc5f020fbcea4e94fe406d15ed687b503f58d7aee4b5f6b436dd6"
},
{
"name": "lib/x86_64-linux-gnu/libpam_misc.so.0",
"type": "symlink",
"modtime": "2017-05-27T15:44:02Z",
"linkName": "libpam_misc.so.0.82.0",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libpam_misc.so.0.82.0",
"type": "reg",
"size": 14640,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 5692744,
"NumLink": 0,
"digest": "sha256:cfa4eb5810c55bc5b3335531f0d5d1a8aacf8829983e22a55cc03dd91cf46b91"
},
{
"name": "lib/x86_64-linux-gnu/libpamc.so.0",
"type": "symlink",
"modtime": "2017-05-27T15:44:02Z",
"linkName": "libpamc.so.0.82.1",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libpamc.so.0.82.1",
"type": "reg",
"size": 14640,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 5698007,
"NumLink": 0,
"digest": "sha256:143e37de8cb78d3d42f8ce5dd077026378c58e1aabca523039df185f6b83ef10"
},
{
"name": "lib/x86_64-linux-gnu/libpcprofile.so",
"type": "reg",
"size": 6328,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 5703762,
"NumLink": 0,
"digest": "sha256:3bf9a695f6ae82d14513ce4d620a62210d939579f725e68c6ee57771473bf09c"
},
{
"name": "lib/x86_64-linux-gnu/libpcre.so.3",
"type": "symlink",
"modtime": "2017-03-21T22:03:19Z",
"linkName": "libpcre.so.3.13.3",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libpcre.so.3.13.3",
"type": "reg",
"size": 468920,
"modtime": "2017-03-21T22:03:19Z",
"mode": 33188,
"offset": 5705959,
"NumLink": 0,
"digest": "sha256:8e472c698a67150907281f8ef307f1b4df3f32e8a993f4943abd8182898b7b17"
},
{
"name": "lib/x86_64-linux-gnu/libpthread-2.24.so",
"type": "reg",
"size": 135440,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33261,
"offset": 5886295,
"NumLink": 0,
"digest": "sha256:d54f396d38b23c1111e8912fbbb518404efa0af34f66249634bbcde601e41b1c"
},
{
"name": "lib/x86_64-linux-gnu/libpthread.so.0",
"type": "symlink",
"modtime": "2018-01-14T10:39:44Z",
"linkName": "libpthread-2.24.so",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libresolv-2.24.so",
"type": "reg",
"size": 84848,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 5940730,
"NumLink": 0,
"digest": "sha256:4163c78a92525c3a541fc77988452a8ed5e9c7769f6414267fd0148a3514507f"
},
{
"name": "lib/x86_64-linux-gnu/libresolv.so.2",
"type": "symlink",
"modtime": "2018-01-14T10:39:44Z",
"linkName": "libresolv-2.24.so",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/librt-2.24.so",
"type": "reg",
"size": 31744,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 5983664,
"NumLink": 0,
"digest": "sha256:df94214b7c10737895d7a46236eff917ad66687551b8ba3906f134e672b22380"
},
{
"name": "lib/x86_64-linux-gnu/librt.so.1",
"type": "symlink",
"modtime": "2018-01-14T10:39:44Z",
"linkName": "librt-2.24.so",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libselinux.so.1",
"type": "reg",
"size": 155400,
"modtime": "2017-09-24T15:30:16Z",
"mode": 33188,
"offset": 5997619,
"NumLink": 0,
"digest": "sha256:3f284c04405d5f7f8015347718f31c0e6239886a7d5c02a2a1c6a4f408e8323e"
},
{
"name": "lib/x86_64-linux-gnu/libsepol.so.1",
"type": "reg",
"size": 623320,
"modtime": "2016-12-03T23:19:56Z",
"mode": 33188,
"offset": 6072745,
"NumLink": 0,
"digest": "sha256:e1c8fb50dc88214875a37f46e21e486f1e092b00e3ad830889020c14e3a297cb"
},
{
"name": "lib/x86_64-linux-gnu/libsmartcols.so.1",
"type": "symlink",
"modtime": "2018-03-07T18:29:09Z",
"linkName": "libsmartcols.so.1.1.0",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libsmartcols.so.1.1.0",
"type": "reg",
"size": 168040,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 6342649,
"NumLink": 0,
"digest": "sha256:6e723c7a681e702b349b2cd25949313e3e6c9d3aea240d5ccf445023d6592d16"
},
{
"name": "lib/x86_64-linux-gnu/libss.so.2",
"type": "symlink",
"modtime": "2017-02-01T00:54:55Z",
"linkName": "libss.so.2.0",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libss.so.2.0",
"type": "reg",
"size": 26544,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33188,
"offset": 6418718,
"NumLink": 0,
"digest": "sha256:225f7393cfdee69acf4ba224b9ac1686a52c2bb8473dbf588aea74d84863d7c5"
},
{
"name": "lib/x86_64-linux-gnu/libsystemd.so.0",
"type": "symlink",
"modtime": "2018-06-13T20:20:36Z",
"linkName": "libsystemd.so.0.17.0",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libsystemd.so.0.17.0",
"type": "reg",
"size": 557552,
"modtime": "2018-06-13T20:20:36Z",
"mode": 33188,
"offset": 6428928,
"NumLink": 0,
"digest": "sha256:99c33261698212db4bef7bf5069641f963433ab92f3e87f8d1bf1103eabc1832"
},
{
"name": "lib/x86_64-linux-gnu/libthread_db-1.0.so",
"type": "reg",
"size": 35744,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 6681851,
"NumLink": 0,
"digest": "sha256:004d1374cc5a50f86616fb3047f18b099ad35b2eb8d563faaa63f872c28e50e3"
},
{
"name": "lib/x86_64-linux-gnu/libthread_db.so.1",
"type": "symlink",
"modtime": "2018-01-14T10:39:44Z",
"linkName": "libthread_db-1.0.so",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libtinfo.so.5",
"type": "symlink",
"modtime": "2017-12-28T09:47:33Z",
"linkName": "libtinfo.so.5.9",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libtinfo.so.5.9",
"type": "reg",
"size": 170776,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 6695310,
"NumLink": 0,
"digest": "sha256:33626a82c73c983b4bd872e80cd9d8965eb6d985ac82d53fa1d3a21ccd4a2c27"
},
{
"name": "lib/x86_64-linux-gnu/libudev.so.1",
"type": "symlink",
"modtime": "2018-06-13T20:20:36Z",
"linkName": "libudev.so.1.6.5",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libudev.so.1.6.5",
"type": "reg",
"size": 131344,
"modtime": "2018-06-13T20:20:36Z",
"mode": 33188,
"offset": 6759287,
"NumLink": 0,
"digest": "sha256:7dd6fd6e3d6bfedc594900e40f9ba638144d4ba474de8a922501f5d19ab97a98"
},
{
"name": "lib/x86_64-linux-gnu/libutil-2.24.so",
"type": "reg",
"size": 10688,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 6818192,
"NumLink": 0,
"digest": "sha256:05d83744f7cc822e5f0cb7053f439f04318bb017b176b132ced2b19e5833ab24"
},
{
"name": "lib/x86_64-linux-gnu/libutil.so.1",
"type": "symlink",
"modtime": "2018-01-14T10:39:44Z",
"linkName": "libutil-2.24.so",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libuuid.so.1",
"type": "symlink",
"modtime": "2018-03-07T18:29:09Z",
"linkName": "libuuid.so.1.3.0",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libuuid.so.1.3.0",
"type": "reg",
"size": 19008,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 6822485,
"NumLink": 0,
"digest": "sha256:d7a0fbf91d6863b57af26dd5199c2a27a2d1691b189fb9bebcabfb6e573324a3"
},
{
"name": "lib/x86_64-linux-gnu/libz.so.1",
"type": "symlink",
"modtime": "2017-01-29T17:22:23Z",
"linkName": "libz.so.1.2.8",
"mode": 41471,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/libz.so.1.2.8",
"type": "reg",
"size": 105088,
"modtime": "2017-01-29T17:22:23Z",
"mode": 33188,
"offset": 6830261,
"NumLink": 0,
"digest": "sha256:7fd423a008f108094d410797edabbd5e3f28ec3110b0ee98fa7dee2b2c0426e3"
},
{
"name": "lib/x86_64-linux-gnu/security/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib/x86_64-linux-gnu/security/pam_access.so",
"type": "reg",
"size": 18680,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 6887318,
"NumLink": 0,
"digest": "sha256:741b9d0308461abade6ca1ca9a5c12f68ae6e8e60bfc2c5114eeac658040886b"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_debug.so",
"type": "reg",
"size": 10376,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 6894624,
"NumLink": 0,
"digest": "sha256:ce1931f2608dc6b14204d3a883f5f54663d53163cc465fc0be38411493672dd3"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_deny.so",
"type": "reg",
"size": 6000,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 6897618,
"NumLink": 0,
"digest": "sha256:a22d25e07a233c0a410f215a9276fcc7c1d0996ce5d6a8354220dd241759e768"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_echo.so",
"type": "reg",
"size": 10336,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 6899273,
"NumLink": 0,
"digest": "sha256:0df400110557b2acc88b3cf4f7e4e467100a79940a86304921e7658690f74915"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_env.so",
"type": "reg",
"size": 14536,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 6902605,
"NumLink": 0,
"digest": "sha256:01fd00b9dec9fc67ad9fc5f3ee2b0fb67d5578bfddc1fc6aa1d6f8108974eb75"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_exec.so",
"type": "reg",
"size": 14728,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 6909166,
"NumLink": 0,
"digest": "sha256:1faa12207908abbd20e26bc5bd660b227204126f961c65b095588f962c3bb886"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_faildelay.so",
"type": "reg",
"size": 10368,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 6914974,
"NumLink": 0,
"digest": "sha256:ce49ccfdad0e09bd00861c3d1a527e9214689a70e49791ada68273ba4ef09ccd"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_filter.so",
"type": "reg",
"size": 14576,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 6917932,
"NumLink": 0,
"digest": "sha256:a3342745265703a761e19453c77cef5576c09b010cd3eeebf04df6b871234e54"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_ftp.so",
"type": "reg",
"size": 10304,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 6923870,
"NumLink": 0,
"digest": "sha256:b6fff572ca79d68c3079de5531730ebe229244593ed012619fe557c719502945"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_group.so",
"type": "reg",
"size": 14616,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 6926757,
"NumLink": 0,
"digest": "sha256:a55044f05f249a7bdaff776f5bbf367878910a1c8bb6166241a6b99f0c86dfae"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_issue.so",
"type": "reg",
"size": 10448,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 6932891,
"NumLink": 0,
"digest": "sha256:fb5f07a6e14baf75d91f3414077a34be5bb8df4fdec1b8c825a631b0c6ffea10"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_keyinit.so",
"type": "reg",
"size": 10344,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 6937095,
"NumLink": 0,
"digest": "sha256:5a69822d86f9a9c08640479fe827cd041f0b28ad1948e3ea9d39199d5dc7c31a"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_lastlog.so",
"type": "reg",
"size": 14560,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 6940594,
"NumLink": 0,
"digest": "sha256:94bc347d76d327016475c084a06654eeb16121aec0ff126c267ddfd63fabaeeb"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_limits.so",
"type": "reg",
"size": 22944,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 6947032,
"NumLink": 0,
"digest": "sha256:3ac02e973a5c9e2e2bb5f2f2061b3629f54993ba4302f80dff6a1ccf8636893c"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_listfile.so",
"type": "reg",
"size": 10384,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 6956584,
"NumLink": 0,
"digest": "sha256:54cefeadd3e76f9ea910689356f90941894692b33166466ff6a6ba8a6768430d"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_localuser.so",
"type": "reg",
"size": 10312,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 6960925,
"NumLink": 0,
"digest": "sha256:b0d353746e2325d6adc1393fad9a40f2e99746b0d925974e46f96d2466c6c87b"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_loginuid.so",
"type": "reg",
"size": 10392,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 6963675,
"NumLink": 0,
"digest": "sha256:976ed19a2598c14d89261041083d5b79598652d5d523b9a0270cf810be71a91f"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_mail.so",
"type": "reg",
"size": 10376,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 6967126,
"NumLink": 0,
"digest": "sha256:82a5741ea520b1ebc626dc0908f2a7597ec0a2237c4d2074d119d8fcb02a063f"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_mkhomedir.so",
"type": "reg",
"size": 10376,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 6971797,
"NumLink": 0,
"digest": "sha256:73fbb80cb6b9148628fe7a4b78bb9d24958856338f7916cd5726d6b889687bf7"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_motd.so",
"type": "reg",
"size": 10360,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 6975182,
"NumLink": 0,
"digest": "sha256:311d8406f3e0ff395b92fc9fbfc87ccfcea7951b3cb96ee04d560dbd00fe1e9d"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_namespace.so",
"type": "reg",
"size": 39720,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 6977918,
"NumLink": 0,
"digest": "sha256:51921e28f0808e3f784b886bc5cfc1cb9eb5e6ef6fc01f719c9befe92807e9a1"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_nologin.so",
"type": "reg",
"size": 10328,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 6995679,
"NumLink": 0,
"digest": "sha256:7ff14b70ef4bed07e3b8418e32f3263e67fc4b97ab867a632bf5ef51d36861ce"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_permit.so",
"type": "reg",
"size": 6168,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 6998548,
"NumLink": 0,
"digest": "sha256:125d4284c78f6afb87ecb34601b75d599125156f581a06976930c960e962c1e6"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_pwhistory.so",
"type": "reg",
"size": 14664,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 7000544,
"NumLink": 0,
"digest": "sha256:9d0433c3cf7dff5334ca9feab51c2c959bef02f328bd1a6ecac1c7f83137116c"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_rhosts.so",
"type": "reg",
"size": 10296,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 7006689,
"NumLink": 0,
"digest": "sha256:ff008965f47c629c01acdeeca39430a64ba5eeee423569e76f49af30cd938036"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_rootok.so",
"type": "reg",
"size": 10368,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 7009385,
"NumLink": 0,
"digest": "sha256:659c269828c5fedf40139f7f50edc268192deec5b18ab37640c8d02e974b3ed1"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_securetty.so",
"type": "reg",
"size": 10368,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 7012344,
"NumLink": 0,
"digest": "sha256:ada92bbe998f2c513721676dc3ad5624f4968f33c202cb137387b706b6ffb899"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_selinux.so",
"type": "reg",
"size": 18800,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 7016176,
"NumLink": 0,
"digest": "sha256:cc0e5a04e98404c89b5417f7db0d15924b37d0f24661a929860be2a302950ae0"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_sepermit.so",
"type": "reg",
"size": 14624,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 7024413,
"NumLink": 0,
"digest": "sha256:84722b688949aefcb8d5d093a7253ab794a3af787b8eed1abd6df5bb1e63c929"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_shells.so",
"type": "reg",
"size": 6216,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 7029970,
"NumLink": 0,
"digest": "sha256:e8130cb3fac5f8bb3af5fc799765591cbcd4f257c16ec36adde8e2e9afd8c269"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_stress.so",
"type": "reg",
"size": 14456,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 7032470,
"NumLink": 0,
"digest": "sha256:e5968c6d25951ad2b75857e0f7952d2e070937afe23bba64f4f9692967e46093"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_succeed_if.so",
"type": "reg",
"size": 14496,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 7038360,
"NumLink": 0,
"digest": "sha256:800bcedbc816683381ec819c0b063260afadfc20547072b6fdfa34fe59f374fb"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_tally.so",
"type": "reg",
"size": 14536,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 7043619,
"NumLink": 0,
"digest": "sha256:5e4a5d92cca63d8d48d282200d73d6c7bc2e951176799c6f5b6924d11cff28e7"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_tally2.so",
"type": "reg",
"size": 14576,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 7049672,
"NumLink": 0,
"digest": "sha256:e4022de4f38434e19c4d7df37a64009cba2dbe4937616935b8547c7d4e8056d6"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_time.so",
"type": "reg",
"size": 14584,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 7056004,
"NumLink": 0,
"digest": "sha256:db7a38c09c845b85283618f8c5569f98de10475c6bb1f32fc21eae961e9acf6f"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_timestamp.so",
"type": "reg",
"size": 18816,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 7061595,
"NumLink": 0,
"digest": "sha256:220f806a68e43085e93f1041a1c30c31f5fb5c40967ccab3e60f0d5f6006240a"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_tty_audit.so",
"type": "reg",
"size": 10360,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 7070244,
"NumLink": 0,
"digest": "sha256:2c0b59ae3374776f52ecbb8359646bc1660a9e90e00fd6a524181788af655927"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_umask.so",
"type": "reg",
"size": 10432,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 7074204,
"NumLink": 0,
"digest": "sha256:fa93ee352ded717297820567c4060a511768322790f7b227284f8d6e77478a4e"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_unix.so",
"type": "reg",
"size": 60336,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 7077930,
"NumLink": 0,
"digest": "sha256:af89f5fbafa92beaa31acb1197efa72562ec0b5f633f3ed29211dfeb4e4bfa36"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_userdb.so",
"type": "reg",
"size": 14512,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 7104104,
"NumLink": 0,
"digest": "sha256:1b52f6baa4b37fa10c83acf9dab51cdb5cadcf26016a8926159460e4d11c3a0f"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_warn.so",
"type": "reg",
"size": 6168,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 7109253,
"NumLink": 0,
"digest": "sha256:579b9dd418f75343d13c850ade1166f5de83dd97cfa99bf04e2a79c0b544c479"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_wheel.so",
"type": "reg",
"size": 10320,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 7111509,
"NumLink": 0,
"digest": "sha256:3eb1b65ad1db5dcdbca14e261651021463b40f2988dbb3a90dde4a9dc884f42b"
},
{
"name": "lib/x86_64-linux-gnu/security/pam_xauth.so",
"type": "reg",
"size": 18992,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 7114599,
"NumLink": 0,
"digest": "sha256:7cf30ba8872ef595352d1e0ce316d0ba78476a57bda0dc4eaed265b9432a56e5"
},
{
"name": "lib64/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "lib64/ld-linux-x86-64.so.2",
"type": "symlink",
"modtime": "2018-01-14T10:39:44Z",
"linkName": "/lib/x86_64-linux-gnu/ld-2.24.so",
"mode": 41471,
"NumLink": 0
},
{
"name": "media/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "mnt/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "opt/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "proc/",
"type": "dir",
"modtime": "2018-06-26T12:03:08Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "root/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16832,
"NumLink": 0
},
{
"name": "root/.bashrc",
"type": "reg",
"size": 570,
"modtime": "2010-01-31T11:52:26Z",
"mode": 33188,
"offset": 7122808,
"NumLink": 0,
"digest": "sha256:41f1685d04031d88891dea1cd02d5154f8aa841119001a72017b0e7158159e23"
},
{
"name": "root/.profile",
"type": "reg",
"size": 148,
"modtime": "2015-08-17T15:30:33Z",
"mode": 33188,
"offset": 7123253,
"NumLink": 0,
"digest": "sha256:74bc92bcf960bfb62b22aa65370cdd1cd37739baa4eab9b240d72692c898ef1f"
},
{
"name": "run/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "run/lock/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 17407,
"NumLink": 0
},
{
"name": "run/utmp",
"type": "reg",
"modtime": "2018-10-11T00:00:00Z",
"mode": 33204,
"gid": 43,
"NumLink": 0,
"digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"name": "sbin/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "sbin/agetty",
"type": "reg",
"size": 57680,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 7123549,
"NumLink": 0,
"digest": "sha256:6adcf14685980ef53a00b82359ade7cb3373f620e7bbe3e9100f5cb2ed53a5a3"
},
{
"name": "sbin/badblocks",
"type": "reg",
"size": 26632,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33261,
"offset": 7148884,
"NumLink": 0,
"digest": "sha256:6c22b8ac432d81bf31b3f443dbb2acc284a16ce094b95a61825752dee26c4c1c"
},
{
"name": "sbin/blkdiscard",
"type": "reg",
"size": 27264,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 7161409,
"NumLink": 0,
"digest": "sha256:7ef233f2d7a23b1676923be2e0c55310cc648096b4ce268eb92454c02856f33d"
},
{
"name": "sbin/blkid",
"type": "reg",
"size": 85408,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 7172442,
"NumLink": 0,
"digest": "sha256:b39bc5aa694d02a547bbe02a749ceb0fc141bc2956be48ac19d9056ca320c163"
},
{
"name": "sbin/blockdev",
"type": "reg",
"size": 35624,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 7211380,
"NumLink": 0,
"digest": "sha256:7d832c9473c9718bf6a1d40f8cc6d61ab20c00f44dfb712d05c80dac22e3461c"
},
{
"name": "sbin/cfdisk",
"type": "reg",
"size": 90472,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 7227436,
"NumLink": 0,
"digest": "sha256:1d3202ac1ca49497e274acdd85f85857a36d2299d47465e2bf2a0d92e0ceb57c"
},
{
"name": "sbin/chcpu",
"type": "reg",
"size": 23128,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 7267991,
"NumLink": 0,
"digest": "sha256:85f7dae37c72adafd7db37f7bcfba4d6355f2203d5b1a656ee5c347c5a14a6e8"
},
{
"name": "sbin/ctrlaltdel",
"type": "reg",
"size": 19032,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 7277888,
"NumLink": 0,
"digest": "sha256:bf5470d36f33cfac7e6d701cf2ce612a871c395300f99d19483cd44b743914af"
},
{
"name": "sbin/debugfs",
"type": "reg",
"size": 218624,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33261,
"offset": 7285732,
"NumLink": 0,
"digest": "sha256:453270d361f09f1a18159e97710d81e924fdb9d7ee55326f033a861dd4d8e618"
},
{
"name": "sbin/dumpe2fs",
"type": "reg",
"size": 26704,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33261,
"offset": 7378324,
"NumLink": 0,
"digest": "sha256:2e0450a263144ebef2fa6682593b70c49c096a02555d93ea45f2620b6588c0db"
},
{
"name": "sbin/e2fsck",
"type": "reg",
"size": 305696,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33261,
"offset": 7389192,
"NumLink": 0,
"digest": "sha256:5fe40e84f2369007c7102486a087ed8fd77df64354e2977de0c9316f050df99d"
},
{
"name": "sbin/e2image",
"type": "reg",
"size": 34896,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33261,
"offset": 7527447,
"NumLink": 0,
"digest": "sha256:5bd6c8600fa8decb1057bf9feb055dfb93432a34161fbfcff4c06c588e91217b"
},
{
"name": "sbin/e2label",
"type": "symlink",
"modtime": "2017-02-01T00:54:55Z",
"linkName": "tune2fs",
"mode": 41471,
"NumLink": 0
},
{
"name": "sbin/e2undo",
"type": "reg",
"size": 18504,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33261,
"offset": 7541953,
"NumLink": 0,
"digest": "sha256:78cf9a8072c1bd7295ccf39004203762468a3bdab1f154245892995b386ebb7a"
},
{
"name": "sbin/fdisk",
"type": "reg",
"size": 118056,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 7548613,
"NumLink": 0,
"digest": "sha256:3cd2d0f2b7a9507e7d85cba7b07a149ce9fdf281d245853e5ea80c88e1a37c82"
},
{
"name": "sbin/findfs",
"type": "reg",
"size": 10568,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 7599475,
"NumLink": 0,
"digest": "sha256:6627f4b74b0834856bc7c75b3df1a572b4e521405913847c9165121f56f78b19"
},
{
"name": "sbin/fsck",
"type": "reg",
"size": 48328,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 7602969,
"NumLink": 0,
"digest": "sha256:fe5c55b3131424db749416881d406f69a909738f82a8cfe755179e82f9f0e255"
},
{
"name": "sbin/fsck.cramfs",
"type": "reg",
"size": 35680,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 7624273,
"NumLink": 0,
"digest": "sha256:0ce71a2e669b3d146d603e72b853d8acc736246653c0e60ac008d3653b66e604"
},
{
"name": "sbin/fsck.ext2",
"type": "symlink",
"modtime": "2017-02-01T00:54:55Z",
"linkName": "e2fsck",
"mode": 41471,
"NumLink": 0
},
{
"name": "sbin/fsck.ext3",
"type": "symlink",
"modtime": "2017-02-01T00:54:55Z",
"linkName": "e2fsck",
"mode": 41471,
"NumLink": 0
},
{
"name": "sbin/fsck.ext4",
"type": "symlink",
"modtime": "2017-02-01T00:54:55Z",
"linkName": "e2fsck",
"mode": 41471,
"NumLink": 0
},
{
"name": "sbin/fsck.minix",
"type": "reg",
"size": 85088,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 7640929,
"NumLink": 0,
"digest": "sha256:11a4a0da6c5136844f52674889dcf3a346469f5c06f952a6e11b653558d4f439"
},
{
"name": "sbin/fsfreeze",
"type": "reg",
"size": 10680,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 7679365,
"NumLink": 0,
"digest": "sha256:19c8eb6b70d539642ba85ecdeb101926bff94585c3640d206f62797f01e4b7c1"
},
{
"name": "sbin/fstab-decode",
"type": "reg",
"size": 6352,
"modtime": "2017-02-12T21:55:39Z",
"mode": 33261,
"offset": 7683720,
"NumLink": 0,
"digest": "sha256:2ffad77e711447254ed284248d3ba09af3a1448a29651080c1623ed243049c34"
},
{
"name": "sbin/fstrim",
"type": "reg",
"size": 43992,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 7686161,
"NumLink": 0,
"digest": "sha256:2069a7cd4120fd53a7105dce987ab07c2428dc85cc16eaebfc7e6ce925cc615e"
},
{
"name": "sbin/getty",
"type": "symlink",
"modtime": "2018-03-07T18:29:09Z",
"linkName": "agetty",
"mode": 41471,
"NumLink": 0
},
{
"name": "sbin/hwclock",
"type": "reg",
"size": 64528,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 7704830,
"NumLink": 0,
"digest": "sha256:deb501e34bd14fd38f60af8f4409fb53655845033f77bba564edc9d7f42e3b13"
},
{
"name": "sbin/initctl",
"type": "reg",
"size": 253,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33261,
"offset": 7734039,
"NumLink": 0,
"digest": "sha256:62d14f76b1641895a5ae24a7ea65eb26dd46dadb5ffd9ca356856530ebb822e1"
},
{
"name": "sbin/installkernel",
"type": "reg",
"size": 2638,
"modtime": "2017-04-02T17:10:33Z",
"mode": 33261,
"offset": 7734314,
"NumLink": 0,
"digest": "sha256:ab0bedb106c10aa2c2ce88ca6bb9db1a973854049b36a9961a532c4496051815"
},
{
"name": "sbin/isosize",
"type": "reg",
"size": 23160,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 7735645,
"NumLink": 0,
"digest": "sha256:12d7d7cdfeb659bf7f652f9b0cbb6bd24b179b390ecb9222df3cd163eab63997"
},
{
"name": "sbin/killall5",
"type": "reg",
"size": 23224,
"modtime": "2017-02-12T21:55:39Z",
"mode": 33261,
"offset": 7745675,
"NumLink": 0,
"digest": "sha256:a00fc547beaadb6ecb7496a8aa9e79c05c27e45a452fa179977d722de86daa90"
},
{
"name": "sbin/ldconfig",
"type": "reg",
"size": 881912,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33261,
"offset": 7755155,
"NumLink": 0,
"digest": "sha256:3285f67cbc98d087914ca23f176377383a40351bd36d92f988643b9f048d19c6"
},
{
"name": "sbin/logsave",
"type": "reg",
"size": 10240,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33261,
"offset": 8145697,
"NumLink": 0,
"digest": "sha256:c1dbb0bc743b753ff4fd705853d6943b7b8188c46ea28aa75ca0df2e8c22247a"
},
{
"name": "sbin/losetup",
"type": "reg",
"size": 81120,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 8149804,
"NumLink": 0,
"digest": "sha256:68fa149ce22fb59c02ab0ec40ce05b9ea80d08056bf41cc8b386e287f7102d24"
},
{
"name": "sbin/mke2fs",
"type": "reg",
"size": 125248,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33261,
"offset": 8186566,
"NumLink": 0,
"digest": "sha256:9ced556c67781a6b31069438b3b9d5b499077dbd566317d4485d86a5256806fe"
},
{
"name": "sbin/mkfs",
"type": "reg",
"size": 10664,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 8243659,
"NumLink": 0,
"digest": "sha256:69dc955885b589730049a14d6caafff16f0370951bab247a491fc37497138486"
},
{
"name": "sbin/mkfs.bfs",
"type": "reg",
"size": 31400,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 8248092,
"NumLink": 0,
"digest": "sha256:9fb75647f280ab27b35c95f021a55545d6372c8e46f4b58ca2c0c77ceeff85ce"
},
{
"name": "sbin/mkfs.cramfs",
"type": "reg",
"size": 35456,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 8260743,
"NumLink": 0,
"digest": "sha256:0e13447f68e32306467bf895234bf673fead8e633ce30c195d6d3f1db4469b6e"
},
{
"name": "sbin/mkfs.ext2",
"type": "symlink",
"modtime": "2017-02-01T00:54:55Z",
"linkName": "mke2fs",
"mode": 41471,
"NumLink": 0
},
{
"name": "sbin/mkfs.ext3",
"type": "symlink",
"modtime": "2017-02-01T00:54:55Z",
"linkName": "mke2fs",
"mode": 41471,
"NumLink": 0
},
{
"name": "sbin/mkfs.ext4",
"type": "symlink",
"modtime": "2017-02-01T00:54:55Z",
"linkName": "mke2fs",
"mode": 41471,
"NumLink": 0
},
{
"name": "sbin/mkfs.minix",
"type": "reg",
"size": 81016,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 8278232,
"NumLink": 0,
"digest": "sha256:53b162d90a37217a12257d77ec8239b956e7784603ab1a269753e96592e0e3d4"
},
{
"name": "sbin/mkhomedir_helper",
"type": "reg",
"size": 18848,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33261,
"offset": 8315583,
"NumLink": 0,
"digest": "sha256:83450062779f8461cec78e65ce86265d6eff1875935ea1b3b9219af3261e0a94"
},
{
"name": "sbin/mkswap",
"type": "reg",
"size": 81144,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 8320055,
"NumLink": 0,
"digest": "sha256:cd1950bdb7206435290914fb502c1bbd67776ec4f609bbf4d840bc93954a757d"
},
{
"name": "sbin/pam_tally",
"type": "reg",
"size": 10592,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33261,
"offset": 8355762,
"NumLink": 0,
"digest": "sha256:0f2e66063fecae11a818631269b535d57b75c1ed6d9ae02c42455fd17291e0e1"
},
{
"name": "sbin/pam_tally2",
"type": "reg",
"size": 14776,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33261,
"offset": 8360230,
"NumLink": 0,
"digest": "sha256:96e3830159176453765f5cccc56c53df9869c47750ae5783ba058dfdb6e4f59d"
},
{
"name": "sbin/pivot_root",
"type": "reg",
"size": 10640,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 8365639,
"NumLink": 0,
"digest": "sha256:857da20329f563fff5253f774fc47615a2ba9ac236aefb06178d2a6bc3cf92af"
},
{
"name": "sbin/raw",
"type": "reg",
"size": 14784,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 8369059,
"NumLink": 0,
"digest": "sha256:802c354e5487c50f1083fd1d14342d077c220234e41219c798e9bc288e8cd8b1"
},
{
"name": "sbin/resize2fs",
"type": "reg",
"size": 59464,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33261,
"offset": 8374050,
"NumLink": 0,
"digest": "sha256:0e3ef18176cfb7fe20a71c285ce425603ecb9c60a828e41e71c8d00656ba2cd1"
},
{
"name": "sbin/runuser",
"type": "reg",
"size": 31816,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 8400304,
"NumLink": 0,
"digest": "sha256:440a23508224db42e4315796a4f8cbb88c613cd4735ea634de2b4eaaf6be3b2b"
},
{
"name": "sbin/sfdisk",
"type": "reg",
"size": 106656,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 8413762,
"NumLink": 0,
"digest": "sha256:754823d46d2538d9eeb5f9859a989a6e757eace3b2e036334b2fb05a044a846a"
},
{
"name": "sbin/shadowconfig",
"type": "reg",
"size": 885,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 8460396,
"NumLink": 0,
"digest": "sha256:e456ba3088c0cb498ba86d9ce496273138e9cfe523feeb1a7e7083aae349dded"
},
{
"name": "sbin/start-stop-daemon",
"type": "reg",
"size": 31848,
"modtime": "2018-06-26T10:28:08Z",
"mode": 33261,
"offset": 8460821,
"NumLink": 0,
"digest": "sha256:a8b2eaf2a3a97f5bcafe7ff1b84dc2157510ca38356b54c91f037d89f286fc10"
},
{
"name": "sbin/sulogin",
"type": "reg",
"size": 44208,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 8474664,
"NumLink": 0,
"digest": "sha256:e0855820d16f2bc3f054c7e1be69c79800a9c96e6538b7fdc286e317a2bdbec4"
},
{
"name": "sbin/swaplabel",
"type": "reg",
"size": 14848,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 8495674,
"NumLink": 0,
"digest": "sha256:7c87ea0de1cb075f4d811137106811c53af315eb89286f99d4fec05142c671af"
},
{
"name": "sbin/swapoff",
"type": "reg",
"size": 19104,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 8500908,
"NumLink": 0,
"digest": "sha256:dcae8e64c12b6efb0de4ddfbb96ba7133fc3a2c109dac31b272b42ba95e55961"
},
{
"name": "sbin/swapon",
"type": "reg",
"size": 48488,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 8507986,
"NumLink": 0,
"digest": "sha256:41683c62a58c1a5a718678d8bc38f69dd19e2ed19635dd8056126aafd57bda4c"
},
{
"name": "sbin/switch_root",
"type": "reg",
"size": 14800,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 8528257,
"NumLink": 0,
"digest": "sha256:faea48ed6d9dafed493aa055914886c4b5bc6557af5d22242c9609816e5bfec0"
},
{
"name": "sbin/tune2fs",
"type": "reg",
"size": 100608,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33261,
"offset": 8533221,
"NumLink": 0,
"digest": "sha256:0879be617e4b224d9a560376ff1a3897d2f989f8ddd6a6bda090496cc10ee24a"
},
{
"name": "sbin/unix_chkpwd",
"type": "reg",
"size": 35592,
"modtime": "2017-05-27T15:44:02Z",
"mode": 34285,
"gid": 42,
"offset": 8578992,
"NumLink": 0,
"digest": "sha256:f2a80b648171dbdc98e867fb9fd54c5f03509a4eb475ee6e3c53b6f03908edb1"
},
{
"name": "sbin/unix_update",
"type": "reg",
"size": 35528,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33261,
"offset": 8593770,
"NumLink": 0,
"digest": "sha256:b15b766a76d7f947906478c52e9b6144585499cc3304e2bdffbadeadea8bba7b"
},
{
"name": "sbin/wipefs",
"type": "reg",
"size": 31536,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 8608004,
"NumLink": 0,
"digest": "sha256:bfb983f08b7ac1ba294bab99e8162600b07f73eb4a89e69214390c3b6dd349cc"
},
{
"name": "sbin/zramctl",
"type": "reg",
"size": 93512,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 8622200,
"NumLink": 0,
"digest": "sha256:367402816b433cd360b6038d50dc849736de44f4df104295907cb319ef6f3fce"
},
{
"name": "srv/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "sys/",
"type": "dir",
"modtime": "2018-06-26T12:03:08Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "tmp/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 17407,
"NumLink": 0
},
{
"name": "usr/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/bin/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/bin/[",
"type": "reg",
"size": 52008,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 8664038,
"NumLink": 0,
"digest": "sha256:d204b3e80ec8a1004e4fdb3a6a4f908ec5f267c0a28449cb95af9e1b69f2e572"
},
{
"name": "usr/bin/addpart",
"type": "reg",
"size": 23072,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 8688508,
"NumLink": 0,
"digest": "sha256:412862f7dad64eb66e614d87cecf30359bda3896be474a54aada5c5d241af679"
},
{
"name": "usr/bin/apt",
"type": "reg",
"size": 14424,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 8697489,
"NumLink": 0,
"digest": "sha256:bf44404ddc8b2c0a38dfb3e1a9e6425d2ab8d8760b1a267d759b6b7db1e8da91"
},
{
"name": "usr/bin/apt-cache",
"type": "reg",
"size": 80032,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 8702450,
"NumLink": 0,
"digest": "sha256:e181fc48fa88009dab8c35cfe8f01fdf3958ecd92281e95aedbe0dd5910214cf"
},
{
"name": "usr/bin/apt-cdrom",
"type": "reg",
"size": 22688,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 8737321,
"NumLink": 0,
"digest": "sha256:9c694896977ab9e2ba656267c588970547ff76ebaf636b418fac3ef934effcbb"
},
{
"name": "usr/bin/apt-config",
"type": "reg",
"size": 22616,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 8746221,
"NumLink": 0,
"digest": "sha256:d122c30960616a613c979f9df23776eb226613badcb6d1af69b2ee0c516051ab"
},
{
"name": "usr/bin/apt-get",
"type": "reg",
"size": 43168,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 8755464,
"NumLink": 0,
"digest": "sha256:61e6dc14f653378466cfbc9504b3e34eeb6911878ae93a7477a6570ec169d241"
},
{
"name": "usr/bin/apt-key",
"type": "reg",
"size": 26269,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 8772706,
"NumLink": 0,
"digest": "sha256:945c55192dfc8dd7d55bd4f332943d3fd7107a64878512bdc26c2ddf0b044ff0"
},
{
"name": "usr/bin/apt-mark",
"type": "reg",
"size": 43168,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 8780858,
"NumLink": 0,
"digest": "sha256:bd5e981cdfaa500cb9753e62369e94c599f041c96c4c2f12c25f12cee0f0b5ef"
},
{
"name": "usr/bin/arch",
"type": "reg",
"size": 35592,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 8799713,
"NumLink": 0,
"digest": "sha256:e5895e2f65d872985b5a0c18e5b195fa1b94a8f6cd40e91e597d711b2e17bfd1"
},
{
"name": "usr/bin/awk",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "/etc/alternatives/awk",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/bin/b2sum",
"type": "reg",
"size": 56200,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 8814671,
"NumLink": 0,
"digest": "sha256:2ab02e2d0f12f71322da010a896b8937b6bb18427b2899e3b2e2ce214c38b1b2"
},
{
"name": "usr/bin/base32",
"type": "reg",
"size": 39720,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 8839271,
"NumLink": 0,
"digest": "sha256:40513e3c1d21c68a756fd4c14bfaa6ce686e61d242981c07b3f412d488504b20"
},
{
"name": "usr/bin/base64",
"type": "reg",
"size": 39720,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 8857666,
"NumLink": 0,
"digest": "sha256:ec4ba544c021045e3272a4bfd195fb2f047b27f61a45fe20f1f672a24637281f"
},
{
"name": "usr/bin/basename",
"type": "reg",
"size": 31464,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 8875974,
"NumLink": 0,
"digest": "sha256:b79987e26f5937e8a8122541488ef2e57eb2f0f68636fddc00db04c46c888558"
},
{
"name": "usr/bin/bashbug",
"type": "reg",
"size": 7120,
"modtime": "2017-05-15T19:45:32Z",
"mode": 33261,
"offset": 8890872,
"NumLink": 0,
"digest": "sha256:2bf2c1619c223b4a5041b8319c275a0fbb0798114d320a4a58eff23f0b3a445b"
},
{
"name": "usr/bin/captoinfo",
"type": "symlink",
"modtime": "2017-12-28T09:47:33Z",
"linkName": "tic",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/bin/catchsegv",
"type": "reg",
"size": 3302,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33261,
"offset": 8894112,
"NumLink": 0,
"digest": "sha256:8d4b393b1004e0e15bc9d7db1c83692d5e441551c9f254d0976210c4f9d49069"
},
{
"name": "usr/bin/chage",
"type": "reg",
"size": 71856,
"modtime": "2017-05-17T11:59:59Z",
"mode": 34285,
"gid": 42,
"offset": 8895804,
"NumLink": 0,
"digest": "sha256:1b4cdc81efd9aba0b4d3dd5a18c34f4573b3260074c785a2ec3494b3b7c73a18"
},
{
"name": "usr/bin/chattr",
"type": "reg",
"size": 14336,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33261,
"offset": 8924236,
"NumLink": 0,
"digest": "sha256:503f92590d4a6a65e284f5ad70606a2c3e80680a3b5439753b99341104145807"
},
{
"name": "usr/bin/chcon",
"type": "reg",
"size": 64520,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 8928860,
"NumLink": 0,
"digest": "sha256:68642e97d0d27f8f136c5893c885d8b590a9b1573c0f97dee223120da5b5fe3d"
},
{
"name": "usr/bin/chfn",
"type": "reg",
"size": 50040,
"modtime": "2017-05-17T11:59:59Z",
"mode": 35309,
"offset": 8960141,
"NumLink": 0,
"digest": "sha256:c25505dbc0c17abb8d1bf59e64020c3ee0ca3ad4ccefe3a90480fa95cb574843"
},
{
"name": "usr/bin/chrt",
"type": "reg",
"size": 31496,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 8981024,
"NumLink": 0,
"digest": "sha256:f235546564c11de25611b61a607af7811220885f16cac017d8f9607d3089e2bf"
},
{
"name": "usr/bin/chsh",
"type": "reg",
"size": 40504,
"modtime": "2017-05-17T11:59:59Z",
"mode": 35309,
"offset": 8994743,
"NumLink": 0,
"digest": "sha256:7ef5f2f8a8460950fa1a37736c226cdcc435d7bcf85e15ab90830ce445423b3d"
},
{
"name": "usr/bin/cksum",
"type": "reg",
"size": 35592,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 9011530,
"NumLink": 0,
"digest": "sha256:42b99d097670fab6cffa6b619276c5c0912912576759d22ee6af10c7bfc12e46"
},
{
"name": "usr/bin/clear",
"type": "reg",
"size": 6136,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33261,
"offset": 9027982,
"NumLink": 0,
"digest": "sha256:0efd2a25ce5ded9cc58e7d1913cbd2bc61f0d7dfacf73bea2138e1fe1f7f4070"
},
{
"name": "usr/bin/clear_console",
"type": "reg",
"size": 10552,
"modtime": "2017-05-15T19:45:32Z",
"mode": 33261,
"offset": 9030096,
"NumLink": 0,
"digest": "sha256:8bff03727bb906a63497467f0e2352486613fa900e26df2a4091f950d3dfa9db"
},
{
"name": "usr/bin/cmp",
"type": "reg",
"size": 43768,
"modtime": "2017-01-09T22:55:10Z",
"mode": 33261,
"offset": 9034088,
"NumLink": 0,
"digest": "sha256:ab79ce775431d53706e8986609129837341a9c44b2599302e44307d02f4be312"
},
{
"name": "usr/bin/comm",
"type": "reg",
"size": 39720,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 9055725,
"NumLink": 0,
"digest": "sha256:590005cf6262bc933fd5f15d29616a7405a801c246c49a724a407540e81001ec"
},
{
"name": "usr/bin/csplit",
"type": "reg",
"size": 48072,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 9073796,
"NumLink": 0,
"digest": "sha256:a0f6057870475b80a03ee78071dfd22df815b8ef6955a26e1021e59cddbbf84d"
},
{
"name": "usr/bin/cut",
"type": "reg",
"size": 43880,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 9096748,
"NumLink": 0,
"digest": "sha256:b69c459f0d6a3e85e7368f594d4e0bf622250cc57a13ff6c389de90fac84611e"
},
{
"name": "usr/bin/deb-systemd-helper",
"type": "reg",
"size": 20142,
"modtime": "2017-05-02T10:20:21Z",
"mode": 33261,
"offset": 9116708,
"NumLink": 0,
"digest": "sha256:10b3c17c0f82dbf2e29ca3a8b353237fd198999c541599a5b48f44a00b180020"
},
{
"name": "usr/bin/deb-systemd-invoke",
"type": "reg",
"size": 4507,
"modtime": "2017-05-02T10:20:21Z",
"mode": 33261,
"offset": 9123064,
"NumLink": 0,
"digest": "sha256:e96ed9793cf4bdeb41fe5990323d76b7b33cd950c53828b395e517377f9127f4"
},
{
"name": "usr/bin/debconf",
"type": "reg",
"size": 2859,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33261,
"offset": 9125100,
"NumLink": 0,
"digest": "sha256:d365d13eb1dff880be7361e4043d25875075776445d4edd1eb4fb1e451a2e41a"
},
{
"name": "usr/bin/debconf-apt-progress",
"type": "reg",
"size": 11541,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33261,
"offset": 9126558,
"NumLink": 0,
"digest": "sha256:93fb257df4185cc6b83858bdae3c7aec0a4f759a848c743a0b0fd7c7091cf34b"
},
{
"name": "usr/bin/debconf-communicate",
"type": "reg",
"size": 608,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33261,
"offset": 9129978,
"NumLink": 0,
"digest": "sha256:705b8ce793f999f21bf2f20348b390738a139116c9e483fb39165a508fde4d27"
},
{
"name": "usr/bin/debconf-copydb",
"type": "reg",
"size": 1719,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33261,
"offset": 9130436,
"NumLink": 0,
"digest": "sha256:085ff55904c81115d8d65f8862f0a5ad393e062da3047762e4d9cd4f5ba761f4"
},
{
"name": "usr/bin/debconf-escape",
"type": "reg",
"size": 647,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33261,
"offset": 9131309,
"NumLink": 0,
"digest": "sha256:6c17a536ca0fcefa24a7d37f7eaebc02e774415b3160836918cff6cecdef807d"
},
{
"name": "usr/bin/debconf-set-selections",
"type": "reg",
"size": 2935,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33261,
"offset": 9131786,
"NumLink": 0,
"digest": "sha256:8085a988bb9850fd9a3405c8a1f8e643c197811f6edf53dfc5ad20ef85040224"
},
{
"name": "usr/bin/debconf-show",
"type": "reg",
"size": 1827,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33261,
"offset": 9133035,
"NumLink": 0,
"digest": "sha256:9dd0bfe9a51d92af868012a32a8190b83a6f4b0834385d12033732b24ee2ceca"
},
{
"name": "usr/bin/delpart",
"type": "reg",
"size": 23072,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 9133931,
"NumLink": 0,
"digest": "sha256:0e5ebb3ec95a1cfb3cfea4356e1b5b356f052270bb3f29b41ae169cfab13eefd"
},
{
"name": "usr/bin/diff",
"type": "reg",
"size": 146824,
"modtime": "2017-01-09T22:55:10Z",
"mode": 33261,
"offset": 9142851,
"NumLink": 0,
"digest": "sha256:f580a4abfc32cf085085e532b9d12477cb955879a4482cc2281e201ff3749343"
},
{
"name": "usr/bin/diff3",
"type": "reg",
"size": 60360,
"modtime": "2017-01-09T22:55:10Z",
"mode": 33261,
"offset": 9214889,
"NumLink": 0,
"digest": "sha256:ceb327f5400a38a7e85021dddf454ed114c61c8472d703b8e61031c5ff20cba4"
},
{
"name": "usr/bin/dircolors",
"type": "reg",
"size": 43792,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 9244499,
"NumLink": 0,
"digest": "sha256:f735068729c48d617ddc88845342a191f46b5306ae2e358925f0d6ea71497175"
},
{
"name": "usr/bin/dirname",
"type": "reg",
"size": 31464,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 9265069,
"NumLink": 0,
"digest": "sha256:4a1a02e1267047c0a568b7f97c28ea351331e9a6aa035ec84fc6c85be0d1254f"
},
{
"name": "usr/bin/dpkg",
"type": "reg",
"size": 293376,
"modtime": "2018-06-26T10:28:08Z",
"mode": 33261,
"offset": 9279590,
"NumLink": 0,
"digest": "sha256:f59b2bde8f707858160374c6d2c7929bc82737af4ec27a027c0e6c7f40902d04"
},
{
"name": "usr/bin/dpkg-deb",
"type": "reg",
"size": 149856,
"modtime": "2018-06-26T10:28:08Z",
"mode": 33261,
"offset": 9410564,
"NumLink": 0,
"digest": "sha256:9a66f16b41551031cbee4f129339043770ad7d4eaf32f3842d672170c8acadb1"
},
{
"name": "usr/bin/dpkg-divert",
"type": "reg",
"size": 141728,
"modtime": "2018-06-26T10:28:08Z",
"mode": 33261,
"offset": 9476405,
"NumLink": 0,
"digest": "sha256:c05f47fc6dbbc5109d2ceb9833ac1d751ed6203373a2a7b04dd00972d3768c8d"
},
{
"name": "usr/bin/dpkg-maintscript-helper",
"type": "reg",
"size": 19030,
"modtime": "2018-06-26T10:28:08Z",
"mode": 33261,
"offset": 9539989,
"NumLink": 0,
"digest": "sha256:7183c0a85c8b309d97d6a9713b58fa4cfbbb2287404c1741f781f56b0c829a23"
},
{
"name": "usr/bin/dpkg-query",
"type": "reg",
"size": 149912,
"modtime": "2018-06-26T10:28:08Z",
"mode": 33261,
"offset": 9544415,
"NumLink": 0,
"digest": "sha256:be6a64db2186874046a857578c6927ca09c82f2b0f28d33be035c25a6177cd78"
},
{
"name": "usr/bin/dpkg-split",
"type": "reg",
"size": 117080,
"modtime": "2018-06-26T10:28:08Z",
"mode": 33261,
"offset": 9611851,
"NumLink": 0,
"digest": "sha256:fdcc92761a3390f7a5cd2a9e906daf8ffb5f50eeb3eb7ba781224fcf18ef401a"
},
{
"name": "usr/bin/dpkg-statoverride",
"type": "reg",
"size": 71936,
"modtime": "2018-06-26T10:28:08Z",
"mode": 33261,
"offset": 9665324,
"NumLink": 0,
"digest": "sha256:f1f690ef016f4e17b0aa8df4ebbe303dd908c24ef3475418abecc774be00c22e"
},
{
"name": "usr/bin/dpkg-trigger",
"type": "reg",
"size": 67848,
"modtime": "2018-06-26T10:28:08Z",
"mode": 33261,
"offset": 9696355,
"NumLink": 0,
"digest": "sha256:d1eb6b4ef3af26b32852e8d2beda588fc1f5ad8713bbd58c63d4f20cd5853aa8"
},
{
"name": "usr/bin/du",
"type": "reg",
"size": 105640,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 9725993,
"NumLink": 0,
"digest": "sha256:62c8fd7bcf8cb3fe6426105f997aa5334b4a5a99bbea189506571fe305d34447"
},
{
"name": "usr/bin/env",
"type": "reg",
"size": 31496,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 9778101,
"NumLink": 0,
"digest": "sha256:9e41c33c216856b3f975d8442e782e78497d157b2aa099189a7c8c44ce069b03"
},
{
"name": "usr/bin/expand",
"type": "reg",
"size": 35624,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 9792776,
"NumLink": 0,
"digest": "sha256:2357a0c300183ddda4b593ad4ecf639c6e8e0353ba13af1b980420ecd3d0df77"
},
{
"name": "usr/bin/expiry",
"type": "reg",
"size": 22808,
"modtime": "2017-05-17T11:59:59Z",
"mode": 34285,
"gid": 42,
"offset": 9808891,
"NumLink": 0,
"digest": "sha256:e7e79217ef4aab5c4e1fc12ab6487333e1398572d0b62e57341ed9bac78a0f27"
},
{
"name": "usr/bin/expr",
"type": "reg",
"size": 43848,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 9818087,
"NumLink": 0,
"digest": "sha256:824957efaf21e1e78bee63a4da3ee2d9ad64f8ee8b0a03d6d88b2e254c65c259"
},
{
"name": "usr/bin/factor",
"type": "reg",
"size": 76680,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 9837814,
"NumLink": 0,
"digest": "sha256:d86bcbd395838672f0bde8b3cfb5c049028e925ffa94eb02ca684d0188f8931f"
},
{
"name": "usr/bin/faillog",
"type": "reg",
"size": 18728,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 9880668,
"NumLink": 0,
"digest": "sha256:4b1386c2f119271d3c3771a9b9d37975f695c44fa84fca4d7e1460a5c9a8747c"
},
{
"name": "usr/bin/fallocate",
"type": "reg",
"size": 27280,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 9887976,
"NumLink": 0,
"digest": "sha256:9101a2ce5c3936bde40dce3b92116b8cdc1b9adfd3553f45aff9048e1c4d4812"
},
{
"name": "usr/bin/find",
"type": "reg",
"size": 221768,
"modtime": "2017-02-18T15:37:32Z",
"mode": 33261,
"offset": 9899287,
"NumLink": 0,
"digest": "sha256:97c94127488a66250e93cf93f475258910992f160e222166aa5c2a7890410b62"
},
{
"name": "usr/bin/flock",
"type": "reg",
"size": 27432,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 10003466,
"NumLink": 0,
"digest": "sha256:a711e2146884827b952e586809a51522f4aadc27396d0e23a1f22dab91ab4843"
},
{
"name": "usr/bin/fmt",
"type": "reg",
"size": 39720,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 10015776,
"NumLink": 0,
"digest": "sha256:e13ee863ebaf2e03d0390430c42236c8f07a3c204fc4ae225b8a48d98a18fb1f"
},
{
"name": "usr/bin/fold",
"type": "reg",
"size": 35624,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 10035417,
"NumLink": 0,
"digest": "sha256:dd7b3f381567876171d71316d5dc5a56b096b5cac07d0eca22c1ba0f880744ae"
},
{
"name": "usr/bin/getconf",
"type": "reg",
"size": 22904,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33261,
"offset": 10051803,
"NumLink": 0,
"digest": "sha256:bb1ed5c8b5f0df1aa51624ddb6996ed0e70cfe85d07189384a4e4f6cee7032dd"
},
{
"name": "usr/bin/getent",
"type": "reg",
"size": 23872,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33261,
"offset": 10059112,
"NumLink": 0,
"digest": "sha256:4cb3a246795c58c1134e0b6ce2abfb4db667b6d517b687a8a782ce369f1ede7f"
},
{
"name": "usr/bin/getopt",
"type": "reg",
"size": 14840,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 10068699,
"NumLink": 0,
"digest": "sha256:73c12040481a67e5a4be643d6fb0438383e20bf03a15bd08973150463ed7d394"
},
{
"name": "usr/bin/gpasswd",
"type": "reg",
"size": 75792,
"modtime": "2017-05-17T11:59:59Z",
"mode": 35309,
"offset": 10075055,
"NumLink": 0,
"digest": "sha256:d92f083fc7a6638ee7a3203e7e55de3bf0e539e3f5849e8cb5644a12f1d65698"
},
{
"name": "usr/bin/gpgv",
"type": "reg",
"size": 420496,
"modtime": "2018-06-08T18:12:24Z",
"mode": 33261,
"offset": 10106103,
"NumLink": 0,
"digest": "sha256:0948d08c3939b9060186cf5cc5337f1c4ac6752bda5b80c5bdb2fd1c4a517886"
},
{
"name": "usr/bin/groups",
"type": "reg",
"size": 35624,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 10321770,
"NumLink": 0,
"digest": "sha256:acc452e8dab94df9262cd81258c97fbf0f319418bd90c33ecc3a5cbdf72a20bf"
},
{
"name": "usr/bin/head",
"type": "reg",
"size": 43848,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 10337294,
"NumLink": 0,
"digest": "sha256:a6469ce6901c72f2d80b27e637998d0894adfa5468b812d74aca74e6f2837153"
},
{
"name": "usr/bin/hostid",
"type": "reg",
"size": 31464,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 10357448,
"NumLink": 0,
"digest": "sha256:d436a899df1651fa756a3dc8af940333c331315c10e55642d06f4e211798ac5b"
},
{
"name": "usr/bin/i386",
"type": "symlink",
"modtime": "2018-03-07T18:29:09Z",
"linkName": "setarch",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/bin/iconv",
"type": "reg",
"size": 56328,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33261,
"offset": 10371529,
"NumLink": 0,
"digest": "sha256:2139423a0d8c34211eee59501ac82f6420a08aeaf4d4118b87c9551fecee6aa0"
},
{
"name": "usr/bin/id",
"type": "reg",
"size": 43944,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 10397042,
"NumLink": 0,
"digest": "sha256:a88307cf73282e6b7aa985eccb065711323674e1484a8754103b7254d058baa6"
},
{
"name": "usr/bin/infocmp",
"type": "reg",
"size": 59464,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33261,
"offset": 10416302,
"NumLink": 0,
"digest": "sha256:3ebb18b167aa6b5c798174adb9b5e706ba65b37dbe3a6ac8b56f387db11c595d"
},
{
"name": "usr/bin/infotocap",
"type": "symlink",
"modtime": "2017-12-28T09:47:33Z",
"linkName": "tic",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/bin/install",
"type": "reg",
"size": 138864,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 10444204,
"NumLink": 0,
"digest": "sha256:51522c627f0a50ef871c35c81b8a4f1d153e4cc7e2df8357abf3681693af72d8"
},
{
"name": "usr/bin/ionice",
"type": "reg",
"size": 27304,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 10512302,
"NumLink": 0,
"digest": "sha256:0b23a47f4e71dc982bad25d3284e61f42b29ee2d99a413c89ee41a57c3dfccdb"
},
{
"name": "usr/bin/ipcmk",
"type": "reg",
"size": 27416,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 10523063,
"NumLink": 0,
"digest": "sha256:734a8697d86f4689634039981c11637d69856738a5d27c3c08338b3883a2adab"
},
{
"name": "usr/bin/ipcrm",
"type": "reg",
"size": 27264,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 10533925,
"NumLink": 0,
"digest": "sha256:ddaf50c3a72c430630693ce22f3c489feff0b9fefd0deb58eec8dab46a04f1c7"
},
{
"name": "usr/bin/ipcs",
"type": "reg",
"size": 52008,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 10545492,
"NumLink": 0,
"digest": "sha256:b1ff7436abc1fed72b616367f7aae26632bbf2ac81591888dd987a5c7f909d00"
},
{
"name": "usr/bin/ischroot",
"type": "reg",
"size": 10552,
"modtime": "2017-04-02T17:10:33Z",
"mode": 33261,
"offset": 10567108,
"NumLink": 0,
"digest": "sha256:70a15af1786a0de15ea8fc910b203d38b98790ae43df5efdd24534a51f358b47"
},
{
"name": "usr/bin/join",
"type": "reg",
"size": 47976,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 10570592,
"NumLink": 0,
"digest": "sha256:97469b77f991fce4f29ffaed18dac01e5d8b7559aab02d87a39580244599a03c"
},
{
"name": "usr/bin/last",
"type": "reg",
"size": 43880,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 10592860,
"NumLink": 0,
"digest": "sha256:1dd54f2f90d9ba5d7b60c8a0e458606a6e38dbcda4c02dd7123402ff3c640358"
},
{
"name": "usr/bin/lastb",
"type": "symlink",
"modtime": "2018-03-07T18:29:09Z",
"linkName": "last",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/bin/lastlog",
"type": "reg",
"size": 18504,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 10612362,
"NumLink": 0,
"digest": "sha256:57eb32f4479e2b6c49780d6b6bb7c84bd96c1c8b28565d97d8e45eeab2138e91"
},
{
"name": "usr/bin/ldd",
"type": "reg",
"size": 5395,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33261,
"offset": 10619060,
"NumLink": 0,
"digest": "sha256:8fe75b6fdfdb63c4cb7710f4599a240a8e9d74b49f7b4354f00852afbb815a8d"
},
{
"name": "usr/bin/line",
"type": "reg",
"size": 10648,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 10621531,
"NumLink": 0,
"digest": "sha256:53c72b327830b2a6a769336fff7a957976127f3be818fa13b76023eccbf5f8ba"
},
{
"name": "usr/bin/link",
"type": "reg",
"size": 31464,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 10624948,
"NumLink": 0,
"digest": "sha256:ad85507f80593524834b45e173e6c7d25edef521b09388890cb2237480b0972c"
},
{
"name": "usr/bin/linux32",
"type": "symlink",
"modtime": "2018-03-07T18:29:09Z",
"linkName": "setarch",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/bin/linux64",
"type": "symlink",
"modtime": "2018-03-07T18:29:09Z",
"linkName": "setarch",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/bin/locale",
"type": "reg",
"size": 38824,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33261,
"offset": 10639221,
"NumLink": 0,
"digest": "sha256:8014cd1cc08fe903a26756458680e5b3d5cdda67e77f35b35553ba2366b1bf8d"
},
{
"name": "usr/bin/localedef",
"type": "reg",
"size": 302784,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33261,
"offset": 10654128,
"NumLink": 0,
"digest": "sha256:7897c88201f7e4b01a8f37578c79375e0d013005d3729bde488b3ca12826dc40"
},
{
"name": "usr/bin/logger",
"type": "reg",
"size": 44472,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 10787530,
"NumLink": 0,
"digest": "sha256:b39fdf305c3a2778a1c016ca4551d468707492597e5e8886cf6ecf0039f4f62c"
},
{
"name": "usr/bin/logname",
"type": "reg",
"size": 31464,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 10808065,
"NumLink": 0,
"digest": "sha256:a58a0693abd829aeb1da6f21723bdc9f27a23f2ee5e7b8b9e493a56144d5808e"
},
{
"name": "usr/bin/lsattr",
"type": "reg",
"size": 10240,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33261,
"offset": 10822124,
"NumLink": 0,
"digest": "sha256:d0098d84be8685553ffb2e2a07aebb771e1ce6fc9362a549a0b1e9c8c8e199d4"
},
{
"name": "usr/bin/lscpu",
"type": "reg",
"size": 64712,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 10825931,
"NumLink": 0,
"digest": "sha256:a9509ab607c61d6bcba57ca47556b8cbbec51d75d1a8c9b083a9e92f79140c29"
},
{
"name": "usr/bin/lsipc",
"type": "reg",
"size": 72824,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 10854951,
"NumLink": 0,
"digest": "sha256:e5806384fb3b1d5c58521098a382a534a909de3f892ef6f28135ecdc29826946"
},
{
"name": "usr/bin/lslocks",
"type": "reg",
"size": 36008,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 10885523,
"NumLink": 0,
"digest": "sha256:5353616c18ffc2a8e0e60f5c02edad6e4448484d70a5c47d9621f5ed0cf00a66"
},
{
"name": "usr/bin/lslogins",
"type": "reg",
"size": 60696,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 10900854,
"NumLink": 0,
"digest": "sha256:ffb8b19b306970bde5485c3bcee17303a7615287ce4d688b16edb9ed034827a5"
},
{
"name": "usr/bin/lsns",
"type": "reg",
"size": 39800,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 10927568,
"NumLink": 0,
"digest": "sha256:17501abd11ecc79d079aac38ba9ebff3bc3f152c76983f90686abe714c69d26b"
},
{
"name": "usr/bin/mawk",
"type": "reg",
"size": 121976,
"modtime": "2012-03-23T20:15:00Z",
"mode": 33261,
"offset": 10945121,
"NumLink": 0,
"digest": "sha256:3f791f82e19d9723c55fe3fb70e85a19c1663e5b76936293fe75070667a42cba"
},
{
"name": "usr/bin/mcookie",
"type": "reg",
"size": 31528,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 11005973,
"NumLink": 0,
"digest": "sha256:3b0404268fc5504d4e9bd525a1848ccb92b6a81915f2756575531e6297864872"
},
{
"name": "usr/bin/md5sum",
"type": "reg",
"size": 43880,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 11019661,
"NumLink": 0,
"digest": "sha256:08c9ecb479710c1a5f548bf48b722c7ef03054a1741116ae0794df49a5fbb9d4"
},
{
"name": "usr/bin/md5sum.textutils",
"type": "symlink",
"modtime": "2017-02-22T12:23:45Z",
"linkName": "md5sum",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/bin/mesg",
"type": "reg",
"size": 10704,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 11041129,
"NumLink": 0,
"digest": "sha256:92c01c7ebdbbf0a99a01dff02ee410ba617217ae9eeabe30745f9e82b0429593"
},
{
"name": "usr/bin/mkfifo",
"type": "reg",
"size": 64552,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 11045233,
"NumLink": 0,
"digest": "sha256:6128655aea11b8cd2b24737829c22a3849d3ba76f2ed2c272c44989a595b43c9"
},
{
"name": "usr/bin/namei",
"type": "reg",
"size": 27296,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 11076521,
"NumLink": 0,
"digest": "sha256:0d687cece51a5f5dec3f16e9471fbc069601e31a922e419aa5bb6f46848d2aec"
},
{
"name": "usr/bin/nawk",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "/etc/alternatives/nawk",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/bin/newgrp",
"type": "reg",
"size": 40312,
"modtime": "2017-05-17T11:59:59Z",
"mode": 35309,
"offset": 11088866,
"NumLink": 0,
"digest": "sha256:6fe1acc3cde9a01dd79aea6295ade4f77a6bb409f16a8fc0e807f7b26a9ad7f7"
},
{
"name": "usr/bin/nice",
"type": "reg",
"size": 35592,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 11104827,
"NumLink": 0,
"digest": "sha256:02174675c94855def605d70484e0c5a9d41b97ca4d9188ed1f2e702147122068"
},
{
"name": "usr/bin/nl",
"type": "reg",
"size": 39848,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 11120628,
"NumLink": 0,
"digest": "sha256:8fa1c7f0a5b967d98e52f57641b3ab7a60a2e30b6dd3e59c1ec02f7e43d536e8"
},
{
"name": "usr/bin/nohup",
"type": "reg",
"size": 35624,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 11139298,
"NumLink": 0,
"digest": "sha256:4a5834965b06e51bfbcb33014d6bf4710b658a4d66840f27d38508ea9b539ca4"
},
{
"name": "usr/bin/nproc",
"type": "reg",
"size": 35624,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 11155576,
"NumLink": 0,
"digest": "sha256:404890e77c1e568eff323f84b85705d509c87e9e9e3ce2d67fd45dbcd946abf1"
},
{
"name": "usr/bin/nsenter",
"type": "reg",
"size": 31688,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 11171258,
"NumLink": 0,
"digest": "sha256:d89feb89c37c671d6eea65992a70a5ef85f390f8e0e9bc3005f53a90a767ce4d"
},
{
"name": "usr/bin/numfmt",
"type": "reg",
"size": 60328,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 11183908,
"NumLink": 0,
"digest": "sha256:bcd656bdcb3dd092b8cd04a40491857d5b044efbb6411c372ab155519da35c39"
},
{
"name": "usr/bin/od",
"type": "reg",
"size": 68520,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 11212554,
"NumLink": 0,
"digest": "sha256:68d5ef1443988c44a6a9159bcf5eda795978946e659e90c281d09e4f29742e84"
},
{
"name": "usr/bin/pager",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "/etc/alternatives/pager",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/bin/partx",
"type": "reg",
"size": 85640,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 11244752,
"NumLink": 0,
"digest": "sha256:65f2986421a88eecdcb04485e359f3c09f48374c74f728c1a156b86a75cec20c"
},
{
"name": "usr/bin/passwd",
"type": "reg",
"size": 59680,
"modtime": "2017-05-17T11:59:59Z",
"mode": 35309,
"offset": 11282891,
"NumLink": 0,
"digest": "sha256:1e22ec1a89d852c8429353ea8bb416e83f5b5eefdbceabd26df81e019bc8f1a8"
},
{
"name": "usr/bin/paste",
"type": "reg",
"size": 35624,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 11305777,
"NumLink": 0,
"digest": "sha256:ebfd11891eaf4bf1714e6368a543c70b49bb141ba9f42f49913ad899792b3352"
},
{
"name": "usr/bin/pathchk",
"type": "reg",
"size": 31496,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 11322087,
"NumLink": 0,
"digest": "sha256:2e44afffe8aeaca8c8377f5f26498f6a14d296d97a6c3c3a91c8fd6442bf54ea"
},
{
"name": "usr/bin/perl",
"type": "reg",
"size": 2021960,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33261,
"offset": 11337229,
"NumLink": 0,
"digest": "sha256:d1a8fc5e7ee0803534d6ac82d69293969a6ce78f20f201d7ddb175ad67b0da66"
},
{
"name": "usr/bin/perl5.24.1",
"type": "hardlink",
"modtime": "2018-06-10T17:37:28Z",
"linkName": "usr/bin/perl",
"mode": 33261,
"NumLink": 0
},
{
"name": "usr/bin/pg",
"type": "reg",
"size": 39824,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 12240511,
"NumLink": 0,
"digest": "sha256:e779778855282c0cb23bd2d23734537fe24761f7d15e2657114fadc77dc62e4a"
},
{
"name": "usr/bin/pinky",
"type": "reg",
"size": 39880,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 12260132,
"NumLink": 0,
"digest": "sha256:bd141bf418442f9ba531d33f7cb745e0abf1f1e1e27f798a03d97b46efb85399"
},
{
"name": "usr/bin/pldd",
"type": "reg",
"size": 14920,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33261,
"offset": 12277950,
"NumLink": 0,
"digest": "sha256:d31f258fbf6377e98c9ffa230938692851303aed1b16763d28c04dcf2b5cc73e"
},
{
"name": "usr/bin/pr",
"type": "reg",
"size": 76808,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 12284291,
"NumLink": 0,
"digest": "sha256:0e46076a45a96c94edc03b37b5f1e02584adb923202a403af38b214f80e05d5c"
},
{
"name": "usr/bin/printenv",
"type": "reg",
"size": 31464,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 12320488,
"NumLink": 0,
"digest": "sha256:40b3aa6167ac603d66811e1361b6ca8504f4d0502ba33a304af23f9c5c4c3640"
},
{
"name": "usr/bin/printf",
"type": "reg",
"size": 52008,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 12334770,
"NumLink": 0,
"digest": "sha256:8ccb2aa21eae8183c2a456c22938f8f2fad5f5a8a599091e8bd4c3e147a245de"
},
{
"name": "usr/bin/prlimit",
"type": "reg",
"size": 32136,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 12359026,
"NumLink": 0,
"digest": "sha256:1c2bb9d71566f9503d007356cb588347a79f4a452684f2f7e08b378206b573c9"
},
{
"name": "usr/bin/ptx",
"type": "reg",
"size": 72712,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 12373085,
"NumLink": 0,
"digest": "sha256:2e52751b9d00810563e72af922413a61560ee26050add0289a9f3d5f705e397f"
},
{
"name": "usr/bin/realpath",
"type": "reg",
"size": 47944,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 12408010,
"NumLink": 0,
"digest": "sha256:47bb43b6a5b8e17deb9e88f9b7969043196805aedcf4c9853ed89c67909a78ad"
},
{
"name": "usr/bin/rename.ul",
"type": "reg",
"size": 14824,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 12429787,
"NumLink": 0,
"digest": "sha256:cef334521baa2145e7b86d4386b7df1a65ccb83178ca215617c6e8dd0655b75e"
},
{
"name": "usr/bin/renice",
"type": "reg",
"size": 10608,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 12434769,
"NumLink": 0,
"digest": "sha256:b082856233958a9c9e442c33d029aec9f7c6453e3d2d748c43da904925607393"
},
{
"name": "usr/bin/reset",
"type": "symlink",
"modtime": "2017-12-28T09:47:33Z",
"linkName": "tset",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/bin/resizepart",
"type": "reg",
"size": 39792,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 12439364,
"NumLink": 0,
"digest": "sha256:08a76a3919d9a3c73c8ca880d1c22176db459a43fed3eba8a6f8dd0e504dac0e"
},
{
"name": "usr/bin/rev",
"type": "reg",
"size": 10704,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 12456239,
"NumLink": 0,
"digest": "sha256:369b529387fb569d8547e1862849f4ddcd18ce83a106a7c4649eba53df24679c"
},
{
"name": "usr/bin/rgrep",
"type": "reg",
"size": 30,
"modtime": "2016-11-28T21:59:51Z",
"mode": 33261,
"offset": 12460356,
"NumLink": 0,
"digest": "sha256:0a8dd42a068115f058ae57f9f6347e1ef0ae2ffea89bf658b132974246577748"
},
{
"name": "usr/bin/runcon",
"type": "reg",
"size": 35688,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 12460484,
"NumLink": 0,
"digest": "sha256:596b31f310c95629556e2c5fdc8d81ab224546b58cc4813a814b91ca5e18ebd2"
},
{
"name": "usr/bin/savelog",
"type": "reg",
"size": 10469,
"modtime": "2017-04-02T17:10:33Z",
"mode": 33261,
"offset": 12476468,
"NumLink": 0,
"digest": "sha256:b5a489fb4c7a5e1240384e25ac3ff8de31e0456a3439fd7ad0ae28fda777c5db"
},
{
"name": "usr/bin/script",
"type": "reg",
"size": 23272,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 12480189,
"NumLink": 0,
"digest": "sha256:d0159b8f03fe657f228f1a43559560332dda855e2eee2840d2c4ba48c3345d4f"
},
{
"name": "usr/bin/scriptreplay",
"type": "reg",
"size": 23168,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 12490349,
"NumLink": 0,
"digest": "sha256:1be86c1360ddfac0d4b01829dedf1f8b65900ed5dd279dfbec58e88ab408b235"
},
{
"name": "usr/bin/sdiff",
"type": "reg",
"size": 52072,
"modtime": "2017-01-09T22:55:10Z",
"mode": 33261,
"offset": 12500871,
"NumLink": 0,
"digest": "sha256:146843c271e78b0736e78fc3ffd0bb45ca7c49c03c70c85ea57f91dad0e9a0e7"
},
{
"name": "usr/bin/select-editor",
"type": "reg",
"size": 1215,
"modtime": "2017-12-20T13:39:04Z",
"mode": 33261,
"offset": 12524172,
"NumLink": 0,
"digest": "sha256:41184f27a957f4a6091f9e9959a3888323059c3a97c273ae0a6c3bc98bfc4fd2"
},
{
"name": "usr/bin/sensible-browser",
"type": "reg",
"size": 1138,
"modtime": "2017-12-20T13:39:04Z",
"mode": 33261,
"offset": 12524817,
"NumLink": 0,
"digest": "sha256:535567fc55d7b0e4a893b2c0e27306848115c1a68f4e70a0730005a52df7da2d"
},
{
"name": "usr/bin/sensible-editor",
"type": "reg",
"size": 1109,
"modtime": "2017-12-20T13:39:04Z",
"mode": 33261,
"offset": 12525289,
"NumLink": 0,
"digest": "sha256:e0c6affda06feb37dfe1342d7062bb9110c3e84709e461ac8632ade024d70aac"
},
{
"name": "usr/bin/sensible-pager",
"type": "reg",
"size": 288,
"modtime": "2017-12-20T13:39:04Z",
"mode": 33261,
"offset": 12525822,
"NumLink": 0,
"digest": "sha256:0a691103beee994a548bf309f201a42f3756311a3fdefd93df7c633afbe7052f"
},
{
"name": "usr/bin/seq",
"type": "reg",
"size": 47944,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 12526085,
"NumLink": 0,
"digest": "sha256:775eae051ea6ceb32f30651279aa482f780f0dcc6bd73e0fa06579d89f88e031"
},
{
"name": "usr/bin/setarch",
"type": "reg",
"size": 19184,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 12549367,
"NumLink": 0,
"digest": "sha256:c1e5f4eca6af935fa2458c9947ed0f727a8d04e6fcc7252acc08c040ccf40b4d"
},
{
"name": "usr/bin/setsid",
"type": "reg",
"size": 10696,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 12555490,
"NumLink": 0,
"digest": "sha256:fe4630dc8f7c730d74825519c9119dd8becee95e526575607be1c212938bfdde"
},
{
"name": "usr/bin/setterm",
"type": "reg",
"size": 39608,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 12559504,
"NumLink": 0,
"digest": "sha256:7f4270fcb6ea0734cbcfc81076c4047047d7d6a8a4ee20e9edc76e469dad658f"
},
{
"name": "usr/bin/sg",
"type": "symlink",
"modtime": "2017-05-17T11:59:59Z",
"linkName": "newgrp",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/bin/sha1sum",
"type": "reg",
"size": 47976,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 12576304,
"NumLink": 0,
"digest": "sha256:bdfb66ff9f531063976ccecd408857c52cc4dbf5f7db36724877d332a331f482"
},
{
"name": "usr/bin/sha224sum",
"type": "reg",
"size": 56168,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 12599391,
"NumLink": 0,
"digest": "sha256:bf49bc5ae0f5057a1a7d6568f34573015185c2b127ff466e5314a1a97f396164"
},
{
"name": "usr/bin/sha256sum",
"type": "reg",
"size": 56168,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 12626948,
"NumLink": 0,
"digest": "sha256:355dc2076bf6973fdba060a8a8a98eaa8a04d6a49cdd11bc563dfcb9d650f305"
},
{
"name": "usr/bin/sha384sum",
"type": "reg",
"size": 60264,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 12654510,
"NumLink": 0,
"digest": "sha256:c483a60f508e8bee6102250371a4f11ca9d2665e8d8c2d386ad67b1352c3492b"
},
{
"name": "usr/bin/sha512sum",
"type": "reg",
"size": 60264,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 12685023,
"NumLink": 0,
"digest": "sha256:e7443c21507f7a316189c7af20aa4e7c358b0fcce4d183f22e7df623359ffd39"
},
{
"name": "usr/bin/shred",
"type": "reg",
"size": 60424,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 12715542,
"NumLink": 0,
"digest": "sha256:3693d8c295f3bacd02c57cfdd0ad7b392864585ed61871d88b1fe77e7c8a2909"
},
{
"name": "usr/bin/shuf",
"type": "reg",
"size": 56232,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 12744060,
"NumLink": 0,
"digest": "sha256:88f6ead25d1a76f00e7d328d22b9088381f04c74638638a863e12f39faa5e5ce"
},
{
"name": "usr/bin/sort",
"type": "reg",
"size": 110096,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 12770058,
"NumLink": 0,
"digest": "sha256:bb7b7d5d1d8824b053fb59a5e95d753069231ae21a483ba4fa86a4c98559b748"
},
{
"name": "usr/bin/split",
"type": "reg",
"size": 56712,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 12825908,
"NumLink": 0,
"digest": "sha256:e6c4f385cc62429ee05d25c137e9f631be98012bac82e21b98b9513e245cf7e8"
},
{
"name": "usr/bin/stat",
"type": "reg",
"size": 85096,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 12851880,
"NumLink": 0,
"digest": "sha256:dfe514438c4c69953459973a11aa85d26f6cb7754f85c9bc0a86b21e7275c094"
},
{
"name": "usr/bin/stdbuf",
"type": "reg",
"size": 47944,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 12893340,
"NumLink": 0,
"digest": "sha256:270c6288a4675c11c5b4aa488987b330a323beb33d8f220f44d41f085e0d2cbe"
},
{
"name": "usr/bin/sum",
"type": "reg",
"size": 39760,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 12915966,
"NumLink": 0,
"digest": "sha256:27acd7e71844d21ac757361f299fcdf2271ff52086b0133c2fe48c171ab40052"
},
{
"name": "usr/bin/tabs",
"type": "reg",
"size": 14328,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33261,
"offset": 12935370,
"NumLink": 0,
"digest": "sha256:cbd146a258bb40422a779b98672625fd0e4324371bfeb9493ad3cbd74d40cd83"
},
{
"name": "usr/bin/tac",
"type": "reg",
"size": 39752,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 12940795,
"NumLink": 0,
"digest": "sha256:27541a3a4f5bb50a9be46940ad7d2e5970bf164f57bed59a79cb8229a08d5f01"
},
{
"name": "usr/bin/tail",
"type": "reg",
"size": 68584,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 12958437,
"NumLink": 0,
"digest": "sha256:6d64e365ae41c6e0b72fdce23aef6e914dcfafcb3f8e91c018152e64a48c85f7"
},
{
"name": "usr/bin/taskset",
"type": "reg",
"size": 31488,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 12991834,
"NumLink": 0,
"digest": "sha256:8f1457e2b893dcc8c248afd23756e33449712ea7b955fcfcdc99e67c94ddd0e7"
},
{
"name": "usr/bin/tee",
"type": "reg",
"size": 35656,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 13005738,
"NumLink": 0,
"digest": "sha256:d606a9ee3ec8d9f6a70eebf6d2cf69c0024f335dcd4dfd9deb45b9de869c0925"
},
{
"name": "usr/bin/test",
"type": "reg",
"size": 47912,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 13022316,
"NumLink": 0,
"digest": "sha256:db96ecffc6850632a463c8776f2e37ecdf92d081deb9ae8148084876b8afcd38"
},
{
"name": "usr/bin/tic",
"type": "reg",
"size": 79984,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33261,
"offset": 13045523,
"NumLink": 0,
"digest": "sha256:f64a36b9119e7c8bf0b022d7b3dd3eb30f59b99720753122d7564a3af8496f3c"
},
{
"name": "usr/bin/timeout",
"type": "reg",
"size": 40296,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 13081511,
"NumLink": 0,
"digest": "sha256:48e7c70ff117a19a2155175d117f5d482deecff2627c0ea1dc2a903c38a00a3f"
},
{
"name": "usr/bin/toe",
"type": "reg",
"size": 14328,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33261,
"offset": 13099764,
"NumLink": 0,
"digest": "sha256:5385b0117f1af117a549f38bfccbd2d665b3433800140a3049acc04d514e23da"
},
{
"name": "usr/bin/touch",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "/bin/touch",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/bin/tput",
"type": "reg",
"size": 18456,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33261,
"offset": 13105589,
"NumLink": 0,
"digest": "sha256:a984aac02449d9f7b8c3fb6800db7631419497c777fdfb5cdd6a81a5614cd89e"
},
{
"name": "usr/bin/tr",
"type": "reg",
"size": 47912,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 13113172,
"NumLink": 0,
"digest": "sha256:885bc7a63fbd80c7046f923dd6d840095817bcabbef1ada0c7ed259ec3497ea4"
},
{
"name": "usr/bin/truncate",
"type": "reg",
"size": 39688,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 13135745,
"NumLink": 0,
"digest": "sha256:94a38366047ebdf3ac56b2768c4391ac242a444d1b10a2152ff7493a0d33db3f"
},
{
"name": "usr/bin/tset",
"type": "reg",
"size": 22528,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33261,
"offset": 13153113,
"NumLink": 0,
"digest": "sha256:dbb3fceb4c20598113231d21d01c1c18835b1920a8678a07c375ccda52776d27"
},
{
"name": "usr/bin/tsort",
"type": "reg",
"size": 35592,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 13162552,
"NumLink": 0,
"digest": "sha256:7ccd07efef4bc2b9383284f5d2c45ef292f3d7327ab9ad9b3e91bd7604d68b76"
},
{
"name": "usr/bin/tty",
"type": "reg",
"size": 31496,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 13179745,
"NumLink": 0,
"digest": "sha256:8265e9a3914b6e62563cefbec2bd668d1d1cb79ca6d2c7297e63a18d8525f77d"
},
{
"name": "usr/bin/tzselect",
"type": "reg",
"size": 15183,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33261,
"offset": 13193742,
"NumLink": 0,
"digest": "sha256:098968d62a5ca72aa895c08f7ddd0d3fa34f94e8951279b64e09563c4a0aec2e"
},
{
"name": "usr/bin/unexpand",
"type": "reg",
"size": 35656,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 13199746,
"NumLink": 0,
"digest": "sha256:11a5a98d238c3da9ffb5db16cf3e80fc6827989fff8dc3041a3227e175b544c8"
},
{
"name": "usr/bin/uniq",
"type": "reg",
"size": 43880,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 13216131,
"NumLink": 0,
"digest": "sha256:7022bae3d35b33308300ad93113a65891562560cbc6f65ab1e1f1d931b67cc85"
},
{
"name": "usr/bin/unlink",
"type": "reg",
"size": 31464,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 13236836,
"NumLink": 0,
"digest": "sha256:eceee77777b3852ed4c2e29123a97b9d7c087ee1c8d1c8793a0850578cdb7705"
},
{
"name": "usr/bin/unshare",
"type": "reg",
"size": 19272,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 13250963,
"NumLink": 0,
"digest": "sha256:256157e769562d513e2efbc478b1aa714964f105c401c1b10a9ed5a64fa6cab7"
},
{
"name": "usr/bin/update-alternatives",
"type": "reg",
"size": 47112,
"modtime": "2018-06-26T10:28:08Z",
"mode": 33261,
"offset": 13258678,
"NumLink": 0,
"digest": "sha256:5284d89c9db30387c306ea730834c95a0f970cda37b94ed58a244abac4242dec"
},
{
"name": "usr/bin/users",
"type": "reg",
"size": 31528,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 13279216,
"NumLink": 0,
"digest": "sha256:d342c8f899a264011068f44a8a2abbe5da7d0cb1c6153758033dea0676d849a7"
},
{
"name": "usr/bin/utmpdump",
"type": "reg",
"size": 23256,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 13294291,
"NumLink": 0,
"digest": "sha256:d7a8b32dad8560f1d8a29aa44d81d8279c2c7116e8bae0f3059c8f3be93a122c"
},
{
"name": "usr/bin/wall",
"type": "reg",
"size": 27448,
"modtime": "2018-03-07T18:29:09Z",
"mode": 34285,
"gid": 5,
"offset": 13304152,
"NumLink": 0,
"digest": "sha256:a2c7e6645b55c65905227f696f94731947a8fb2bfc128b3791816869e409c1b5"
},
{
"name": "usr/bin/wc",
"type": "reg",
"size": 43888,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 13316715,
"NumLink": 0,
"digest": "sha256:d566e182f833e3e0365cc35dc2e4c78007a4d4409a1b931b85b5b3bed7c64ebe"
},
{
"name": "usr/bin/whereis",
"type": "reg",
"size": 27744,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 13337559,
"NumLink": 0,
"digest": "sha256:0ffa3486644df8ca05d5e4114a92c105e97a6e714c533516ca7770a86a59652b"
},
{
"name": "usr/bin/which",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "/bin/which",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/bin/who",
"type": "reg",
"size": 52168,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 13348087,
"NumLink": 0,
"digest": "sha256:36312f62a0eb6d337ee41859d3a7c00e905357397c59d5813e8a48b13fb6d7ac"
},
{
"name": "usr/bin/whoami",
"type": "reg",
"size": 31496,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 13372822,
"NumLink": 0,
"digest": "sha256:370dbe69b3aa202cbd31f1f597ff30b9bfa9023e37dafc610b3e4008e9375227"
},
{
"name": "usr/bin/x86_64",
"type": "symlink",
"modtime": "2018-03-07T18:29:09Z",
"linkName": "setarch",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/bin/xargs",
"type": "reg",
"size": 67800,
"modtime": "2017-02-18T15:37:32Z",
"mode": 33261,
"offset": 13387042,
"NumLink": 0,
"digest": "sha256:efcdba7cbbf91d82113f64453ab11c0e64245a963e67b032b8f084ec30d22531"
},
{
"name": "usr/bin/yes",
"type": "reg",
"size": 31464,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 13419643,
"NumLink": 0,
"digest": "sha256:d07b3d1897e68927efcb012bba09d57e39a50e61ee5256ebe37fe919f2645313"
},
{
"name": "usr/bin/zdump",
"type": "reg",
"size": 14768,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33261,
"offset": 13434180,
"NumLink": 0,
"digest": "sha256:7597033749fadd103a24b3a278ffba55ea762ad2f43baea7e645380370c3a422"
},
{
"name": "usr/games/",
"type": "dir",
"modtime": "2018-06-26T12:03:08Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/include/",
"type": "dir",
"modtime": "2018-06-26T12:03:08Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/apt/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/apt/apt-helper",
"type": "reg",
"size": 26784,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 13441022,
"NumLink": 0,
"digest": "sha256:8ca18a42645e85626aabff7cd9320ccb9b3e09a94b4628ca76f9e99aefeb2b13"
},
{
"name": "usr/lib/apt/apt.systemd.daily",
"type": "reg",
"size": 15416,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 13452294,
"NumLink": 0,
"digest": "sha256:100c73ca198b46cd60314fe2caa9c26fe7eb8573539c10358c85df58487db262"
},
{
"name": "usr/lib/apt/methods/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/apt/methods/bzip2",
"type": "symlink",
"modtime": "2017-09-13T16:47:33Z",
"linkName": "store",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/lib/apt/methods/cdrom",
"type": "reg",
"size": 34976,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 13456996,
"NumLink": 0,
"digest": "sha256:8767d67d18e09afaf19fb0c836ef761c5fc50c709a888bb1748e47db382f7560"
},
{
"name": "usr/lib/apt/methods/copy",
"type": "reg",
"size": 18592,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 13470455,
"NumLink": 0,
"digest": "sha256:d90b02abf40ff63071505eb84ede4cfd50c401f98748001c43a179c87eb9b669"
},
{
"name": "usr/lib/apt/methods/file",
"type": "reg",
"size": 18592,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 13476895,
"NumLink": 0,
"digest": "sha256:4b30f5e92352e011facc307c4b13c959218c64e0cc396def475f16ac48697052"
},
{
"name": "usr/lib/apt/methods/ftp",
"type": "reg",
"size": 63744,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 13483905,
"NumLink": 0,
"digest": "sha256:0fad9076801625d64e863ae4200e1b53b5af493c0536c19d4b7a20c21f911902"
},
{
"name": "usr/lib/apt/methods/gpgv",
"type": "reg",
"size": 55456,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 13510659,
"NumLink": 0,
"digest": "sha256:02b85554325276116efdd59b6be7066cfcb4dd6d715d407a8ac90d6f0ddf3f0f"
},
{
"name": "usr/lib/apt/methods/gzip",
"type": "symlink",
"modtime": "2017-09-13T16:47:33Z",
"linkName": "store",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/lib/apt/methods/http",
"type": "reg",
"size": 112808,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 13532943,
"NumLink": 0,
"digest": "sha256:63a27ce8eee65a0c6f91fadfd05feb7d2767181620739c042eea11e22e23b9b0"
},
{
"name": "usr/lib/apt/methods/lzma",
"type": "symlink",
"modtime": "2017-09-13T16:47:33Z",
"linkName": "store",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/lib/apt/methods/mirror",
"type": "reg",
"size": 137448,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 13582418,
"NumLink": 0,
"digest": "sha256:5f5183db0600db27a2b2dab6158144529406d58987cc2f816d62e662883271ab"
},
{
"name": "usr/lib/apt/methods/rred",
"type": "reg",
"size": 47264,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 13642317,
"NumLink": 0,
"digest": "sha256:005da2aa6d732ed000d00138eb744d6a0aeea1346440178739b106d4e0bfec85"
},
{
"name": "usr/lib/apt/methods/rsh",
"type": "reg",
"size": 30896,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 13661946,
"NumLink": 0,
"digest": "sha256:9aa728dbcee13b6ea052300e707e95f805e1ed70ff1778da8e377eb3513ff2bf"
},
{
"name": "usr/lib/apt/methods/ssh",
"type": "symlink",
"modtime": "2017-09-13T16:47:33Z",
"linkName": "rsh",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/lib/apt/methods/store",
"type": "reg",
"size": 22688,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 13674700,
"NumLink": 0,
"digest": "sha256:181be8323b9885182006036cfc7f9aaf2bd73dfa89985923b20e82d9a3b72737"
},
{
"name": "usr/lib/apt/methods/xz",
"type": "symlink",
"modtime": "2017-09-13T16:47:33Z",
"linkName": "store",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/lib/apt/planners/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/apt/planners/dump",
"type": "symlink",
"modtime": "2017-09-13T16:47:33Z",
"linkName": "../solvers/dump",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/lib/apt/solvers/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/apt/solvers/dump",
"type": "reg",
"size": 18520,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 13683482,
"NumLink": 0,
"digest": "sha256:f79e93a0ce641b06284933c1f5c46d1b69610b9d1fc170ba4172480027324107"
},
{
"name": "usr/lib/dpkg/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/dpkg/methods/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/dpkg/methods/apt/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/dpkg/methods/apt/desc.apt",
"type": "reg",
"size": 567,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33188,
"offset": 13690116,
"NumLink": 0,
"digest": "sha256:4035a2ca99d6d473f6e9a0af7b39d395bfe47e48b3a9993488fc2fae139145f8"
},
{
"name": "usr/lib/dpkg/methods/apt/install",
"type": "reg",
"size": 2861,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 13690562,
"NumLink": 0,
"digest": "sha256:810da1fcf97636219401c67e891a04a7a4f01b2a0d73fd60bf3bbbdfb3775f76"
},
{
"name": "usr/lib/dpkg/methods/apt/names",
"type": "reg",
"size": 39,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33188,
"offset": 13691901,
"NumLink": 0,
"digest": "sha256:0a636de469385b41ea06f639a389c523946ec7f023fe2a12c0adf8300e2a82ad"
},
{
"name": "usr/lib/dpkg/methods/apt/setup",
"type": "reg",
"size": 7728,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 13692053,
"NumLink": 0,
"digest": "sha256:c645a091943f61ff46847973d000cbf1817623a86e1ede412f97f437aa1eb56a"
},
{
"name": "usr/lib/dpkg/methods/apt/update",
"type": "reg",
"size": 1242,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 13694442,
"NumLink": 0,
"digest": "sha256:605ae19f87289e8d4cdb80028dd071c4b3ea0e2e46da9ad697b6bd739ba4c6b3"
},
{
"name": "usr/lib/gcc/",
"type": "dir",
"modtime": "2018-02-14T16:53:20Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/gcc/x86_64-linux-gnu/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/gcc/x86_64-linux-gnu/6/",
"type": "dir",
"modtime": "2018-02-14T16:53:20Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/gcc/x86_64-linux-gnu/6.3.0",
"type": "symlink",
"modtime": "2018-02-14T16:53:20Z",
"linkName": "6",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/lib/locale/",
"type": "dir",
"modtime": "2018-01-14T10:39:44Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/locale/C.UTF-8/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/locale/C.UTF-8/LC_ADDRESS",
"type": "reg",
"size": 131,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 13695330,
"NumLink": 0,
"digest": "sha256:e56fdac7f4d70bdb7517a9a3c98bbfefef52fcfb082d3a49c26eec93fd8f9d9d"
},
{
"name": "usr/lib/locale/C.UTF-8/LC_COLLATE",
"type": "reg",
"size": 1515838,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 13695537,
"NumLink": 0,
"digest": "sha256:821d59144c41906337b31ca68dec0e38bcc91b9a532d06050c6aedbf00587472"
},
{
"name": "usr/lib/locale/C.UTF-8/LC_CTYPE",
"type": "reg",
"size": 198372,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14383146,
"NumLink": 0,
"digest": "sha256:4826e6c02b3df039a8aa8fbddacdc060f7d697438e7b525483c44c4d4d7e949f"
},
{
"name": "usr/lib/locale/C.UTF-8/LC_IDENTIFICATION",
"type": "reg",
"size": 243,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14405493,
"NumLink": 0,
"digest": "sha256:d0a1272e17344d4e4d9ac8b783181721cd4a9faae87890793561261e47c7eedd"
},
{
"name": "usr/lib/locale/C.UTF-8/LC_MEASUREMENT",
"type": "reg",
"size": 23,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14405720,
"NumLink": 0,
"digest": "sha256:bb14a6f2cbd5092a755e8f272079822d3e842620dd4542a8dfa1e5e72fc6115b"
},
{
"name": "usr/lib/locale/C.UTF-8/LC_MESSAGES/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/locale/C.UTF-8/LC_MESSAGES/SYS_LC_MESSAGES",
"type": "reg",
"size": 48,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14405898,
"NumLink": 0,
"digest": "sha256:f9ad02f1d8eba721d4cbd50c365b5c681c39aec008f90bfc2be2dc80bfbaddcb"
},
{
"name": "usr/lib/locale/C.UTF-8/LC_MONETARY",
"type": "reg",
"size": 270,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14406061,
"NumLink": 0,
"digest": "sha256:2b453edb3c67a2b0f326d045ce72a5cd0ffde75fcfe31e47edd1c2d802bb18b6"
},
{
"name": "usr/lib/locale/C.UTF-8/LC_NAME",
"type": "reg",
"size": 62,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14406322,
"NumLink": 0,
"digest": "sha256:14507aad9f806112e464b9ca94c93b2e4d759ddc612b5f87922d7cac7170697d"
},
{
"name": "usr/lib/locale/C.UTF-8/LC_NUMERIC",
"type": "reg",
"size": 50,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14406486,
"NumLink": 0,
"digest": "sha256:f5976e6b3e6b24dfe03caad6a5b98d894d8110d8bd15507e690fd60fd3e04ab2"
},
{
"name": "usr/lib/locale/C.UTF-8/LC_PAPER",
"type": "reg",
"size": 34,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14406640,
"NumLink": 0,
"digest": "sha256:cde048b81e2a026517cc707c906aebbd50f5ee3957b6f0c1c04699dffcb7c015"
},
{
"name": "usr/lib/locale/C.UTF-8/LC_TELEPHONE",
"type": "reg",
"size": 47,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14406790,
"NumLink": 0,
"digest": "sha256:f4caf0d12844219b65ba42edc7ec2f5ac1b2fc36a3c88c28887457275daca1ee"
},
{
"name": "usr/lib/locale/C.UTF-8/LC_TIME",
"type": "reg",
"size": 2498,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14406947,
"NumLink": 0,
"digest": "sha256:a61ddf48d8afc6d3aa93effb63cc55033c4ab9e42a4dbed2be1e385b5f6f7451"
},
{
"name": "usr/lib/mime/",
"type": "dir",
"modtime": "2017-12-20T13:39:04Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/mime/packages/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/mime/packages/sensible-utils",
"type": "reg",
"size": 97,
"modtime": "2017-12-20T13:39:04Z",
"mode": 33188,
"offset": 14407873,
"NumLink": 0,
"digest": "sha256:e9d9b4e7782deb0509bb543620c597710bda659a3045bfce55b28c632f201f2a"
},
{
"name": "usr/lib/mime/packages/tar",
"type": "reg",
"size": 327,
"modtime": "2016-10-30T06:35:31Z",
"mode": 33188,
"offset": 14408061,
"NumLink": 0,
"digest": "sha256:31deef64d49013a02645bcd32e43d108d12ebb89a93034fb35947c7c576397c8"
},
{
"name": "usr/lib/mime/packages/util-linux",
"type": "reg",
"size": 90,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 14408260,
"NumLink": 0,
"digest": "sha256:8c2a3124292211ce117712858ad06a036675a7f7d8f578e5b84267b1196c1665"
},
{
"name": "usr/lib/os-release",
"type": "reg",
"size": 236,
"modtime": "2018-06-26T12:03:08Z",
"mode": 33188,
"offset": 14408414,
"NumLink": 0,
"digest": "sha256:b4c8c0cfed0dc1f1b4f6f34fe29539d7177c343624de2ec59c86c762615597a6"
},
{
"name": "usr/lib/python2.7/",
"type": "dir",
"modtime": "2017-05-21T17:08:30Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/python2.7/dist-packages/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/python2.7/dist-packages/debconf.py",
"type": "reg",
"size": 5971,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 14408720,
"NumLink": 0,
"digest": "sha256:6d43e8fb8ce8e97cb0462912d224ee912c56e9453bd52bd6fb0cdfec0b27d575"
},
{
"name": "usr/lib/python3/",
"type": "dir",
"modtime": "2017-05-21T17:08:30Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/python3/dist-packages/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/python3/dist-packages/debconf.py",
"type": "reg",
"size": 5971,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 14411248,
"NumLink": 0,
"digest": "sha256:6d43e8fb8ce8e97cb0462912d224ee912c56e9453bd52bd6fb0cdfec0b27d575"
},
{
"name": "usr/lib/tar/",
"type": "dir",
"modtime": "2016-10-30T06:35:31Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/tmpfiles.d/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/tmpfiles.d/passwd.conf",
"type": "reg",
"size": 239,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33188,
"offset": 14413781,
"NumLink": 0,
"digest": "sha256:1a3b927102b44454eb4c47e4fe659de2f5283f364ba27408329a54d4ad47e310"
},
{
"name": "usr/lib/x86_64-linux-gnu/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/audit/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/audit/sotruss-lib.so",
"type": "reg",
"size": 10512,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14414075,
"NumLink": 0,
"digest": "sha256:d3b259d2cdfddf10191a34b45491fe65b62ba40ab7354baabab6a49729f8a972"
},
{
"name": "usr/lib/x86_64-linux-gnu/coreutils/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/coreutils/libstdbuf.so",
"type": "reg",
"size": 6208,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33188,
"offset": 14418024,
"NumLink": 0,
"digest": "sha256:c8a9a76f2bf31a771caaef396959e0499600db824c6fd10074d2b5d2f310ef3e"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ANSI_X3.110.so",
"type": "reg",
"size": 22696,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14420506,
"NumLink": 0,
"digest": "sha256:52262e2d0a7f214cc2245cff3961f378543e0d39c6d4417ba30c9f2e49c1df9a"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ARMSCII-8.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14428237,
"NumLink": 0,
"digest": "sha256:ae82c1d28f62461266e9cda0cc26c4645b440747c5ca989d7a04051fbf3a6b73"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ASMO_449.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14432539,
"NumLink": 0,
"digest": "sha256:0807e5512dcf1ae8672b10d01c191bed90ec3776d984fe1e7173ca3d2a46639c"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/BIG5.so",
"type": "reg",
"size": 88232,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14436989,
"NumLink": 0,
"digest": "sha256:0a81b5d9ade3c77d046913fb43921df3a07cb580e4846bea244c88cfc49242bd"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/BIG5HKSCS.so",
"type": "reg",
"size": 235688,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14501902,
"NumLink": 0,
"digest": "sha256:088d26cae29c1089532420dce6af0c0834ddbf2027e9a9afbe1b15babeab6326"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/BRF.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14599125,
"NumLink": 0,
"digest": "sha256:d2eb65850ad018518ea8e7ae4c50f847cdda4cdfc7420b6822898342ab3ead23"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/CP10007.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14603462,
"NumLink": 0,
"digest": "sha256:d25df0bc6be89a0422849cad6efaf8e3c3666eaf0be6efa73a0d7d1cab90df7a"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/CP1125.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14608398,
"NumLink": 0,
"digest": "sha256:a96ddfac1fd5bb74ea4a8c19a887225894890762e291196c27b3346472402b0e"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/CP1250.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14613378,
"NumLink": 0,
"digest": "sha256:c761e88cad960d05daa141ab51c03fae9189d49967ee1afde72e5e2543257b3c"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/CP1251.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14618353,
"NumLink": 0,
"digest": "sha256:c8470d189bf9c79a768cab14e380114e1634200bcf5c1b7cd45de0b4aa411ef4"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/CP1252.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14623308,
"NumLink": 0,
"digest": "sha256:f4742d917fafa59406fdeabe074b69a986bfc11e1af5c2337035f465b0431648"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/CP1253.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14628249,
"NumLink": 0,
"digest": "sha256:ebc48f981c92999a5e72516c02bed94d8526575bdc6bd52458cd07644acff61d"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/CP1254.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14633150,
"NumLink": 0,
"digest": "sha256:c91b07b46724dd2a2e4113a96d0111a99da10b5122d2d4158f43a5b0a80ddd83"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/CP1255.so",
"type": "reg",
"size": 14504,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14638095,
"NumLink": 0,
"digest": "sha256:050f26ec19bc43127b0f1d669818c6d9f0bb0b76259291bca38747d1e9b34f34"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/CP1256.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14645317,
"NumLink": 0,
"digest": "sha256:9083224cce4afa3cb1bf1b7ff1ea16b8e085e9664d46043c57063946e9c3c98a"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/CP1257.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14650361,
"NumLink": 0,
"digest": "sha256:ff6585ff708bf5e82fbc25fe25f0e7e4c32db17d04b9784c25b0b6e050e79657"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/CP1258.so",
"type": "reg",
"size": 14512,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14655334,
"NumLink": 0,
"digest": "sha256:c194a2a2ac90d7c9156b215e92e100d8311f5442e85e5ece08e0773b03214bf1"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/CP737.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14663380,
"NumLink": 0,
"digest": "sha256:8eac0e2ad46c543422ed7672850fac763fe58dc28a14a4a82ad0605a0850dfa4"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/CP770.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14668423,
"NumLink": 0,
"digest": "sha256:3fb2446b1b6483defec27db9793c0156498cea296d512e1a0bb799d1f175c751"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/CP771.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14673540,
"NumLink": 0,
"digest": "sha256:4281251137e534c30c61ba1a897c758ad61ee7dfe39d5863e44b96917c806ce3"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/CP772.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14678534,
"NumLink": 0,
"digest": "sha256:512ebd920245e0401557704cd7f2bea935d0471e6a41d4d95d9a7963aa6b4994"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/CP773.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14683587,
"NumLink": 0,
"digest": "sha256:cb607d2235d078938530c1939d1216db8e78c74c77ae0547de9e8cb0a2e4427e"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/CP774.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14688605,
"NumLink": 0,
"digest": "sha256:70ecf1712bf970a9215527626c2d5c6276e1cb448212755038d019ddb2709be0"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/CP775.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14693692,
"NumLink": 0,
"digest": "sha256:61ae97e8aa5c5d703ed36945ecf7f0a8e591bf61dd584bfbd51ec5bed4235a73"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/CP932.so",
"type": "reg",
"size": 96424,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14698751,
"NumLink": 0,
"digest": "sha256:6f626fa00093a081100e122fcebf717df97ed310c48e11f369c838a01686f7ba"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/CSN_369103.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14740144,
"NumLink": 0,
"digest": "sha256:2bc8813d57c2b440c2917aa89b9e409c52fc46a659c14ca91fe3a7bcef7ae279"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/CWI.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14745042,
"NumLink": 0,
"digest": "sha256:6adb325cf1a72086f60d31d01e81243b8b40fbcad35a1e35f1a27a477b267ad0"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/DEC-MCS.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14750111,
"NumLink": 0,
"digest": "sha256:f0aaf44d6cafe56fb90c09c7d1a0dca359f375895de8493522e68649f1ad998e"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/EBCDIC-AT-DE-A.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14754955,
"NumLink": 0,
"digest": "sha256:3d44f8769af83450b7b75d3f9b69621f27833af117daf2b2bd927dce856fed8d"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/EBCDIC-AT-DE.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14759385,
"NumLink": 0,
"digest": "sha256:44b682d782826047e399de9272a2cdfc3437d8221f036ecba701473827935148"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/EBCDIC-CA-FR.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14763835,
"NumLink": 0,
"digest": "sha256:a9b9c635be74120334387edb3e0a0f622aa9fd7c3d58c04c5571ef29640ae54e"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/EBCDIC-DK-NO-A.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14768362,
"NumLink": 0,
"digest": "sha256:5a8dd91fe6988b4141191d94ef30b37e49351cac989b276cb5e9a717e9546ca8"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/EBCDIC-DK-NO.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14772792,
"NumLink": 0,
"digest": "sha256:64df1cc8d7c8746ec30347c4b4800777af8c347ecfc66e3ff7b93df8c70d7d0a"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/EBCDIC-ES-A.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14777239,
"NumLink": 0,
"digest": "sha256:e8e45fa922d6c98a1c515ab7e3a3a8a582c95d383ae63cd0e962e6f060625ed4"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/EBCDIC-ES-S.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14781794,
"NumLink": 0,
"digest": "sha256:88f6ab75e922f12e226b9d0fe92c4a0ac4df64f63dd1d482287db69b638d13c9"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/EBCDIC-ES.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14786227,
"NumLink": 0,
"digest": "sha256:b98e70cfd597501422b69559e7bc448ed6d4f5302953406e68e064ed840dfdf8"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/EBCDIC-FI-SE-A.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14790805,
"NumLink": 0,
"digest": "sha256:f020cfdd4d521d4520a87299908e65f7d6b906452252900b5aa5a673789cd656"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/EBCDIC-FI-SE.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14795234,
"NumLink": 0,
"digest": "sha256:ffb39c7296bdee62e96d7cd05c4a58073e66aecedd6bf70bbc9ca6d429d7dcb2"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/EBCDIC-FR.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14799676,
"NumLink": 0,
"digest": "sha256:c8f03bf1c63291b5bf1d97a041025b91c22c2fc0d930ad38d4eb730f67c790ca"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/EBCDIC-IS-FRISS.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14804118,
"NumLink": 0,
"digest": "sha256:ffc94a955ae58f68e83baf656c3c6b375851e301ff4b5f9fb7610e606a108705"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/EBCDIC-IT.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14808669,
"NumLink": 0,
"digest": "sha256:0cb0f62f17f0bda72a19a2327de4692c8fce54eac0b5f05a875d4516d46fe65e"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/EBCDIC-PT.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14813101,
"NumLink": 0,
"digest": "sha256:423fa5915f65e0b3865c8e864c57d3c5c828515dc142a2985da5773f3572c1ab"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/EBCDIC-UK.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14817543,
"NumLink": 0,
"digest": "sha256:3a4e3458fba6edf9a883d5dc4f909b6ed2fc0d8836bb6df2b6fd8e04e89e4077"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/EBCDIC-US.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14822100,
"NumLink": 0,
"digest": "sha256:0f3a1b38dca122467b74a7a75af0f77f26bf13ea4c328a94d2197343acb194af"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ECMA-CYRILLIC.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14826536,
"NumLink": 0,
"digest": "sha256:073232e33584358d25014f6e1b8c2d502c7bed0413cdabe4f3780f66ff073269"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/EUC-CN.so",
"type": "reg",
"size": 18600,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14831401,
"NumLink": 0,
"digest": "sha256:4f11c781a92eb325d435cb71767224d7e6c3cb87f948d1b73f4729ce7c3c26bc"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/EUC-JISX0213.so",
"type": "reg",
"size": 14504,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14839196,
"NumLink": 0,
"digest": "sha256:7123e1baba3fa0da4b59aba823aff32464d7a9debb950bdd2f2d934f638cccd8"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/EUC-JP-MS.so",
"type": "reg",
"size": 88232,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14846580,
"NumLink": 0,
"digest": "sha256:2f4418c4976ddd63cd367d60f2e0ed53624b6bee43e6d68320b8ce51c47835af"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/EUC-JP.so",
"type": "reg",
"size": 14504,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14885819,
"NumLink": 0,
"digest": "sha256:9fc8079f6369e76c7ac57330cd90e636f8e9db2f1bd40b189a650409f1d84d29"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/EUC-KR.so",
"type": "reg",
"size": 14504,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14892604,
"NumLink": 0,
"digest": "sha256:047e570c03fb7dc3f12a42776f3d14f3b4751aaf9cdd19feaabf67429f11c3b0"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/EUC-TW.so",
"type": "reg",
"size": 22696,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14898948,
"NumLink": 0,
"digest": "sha256:10e457260126d17f57ad318e53c35cc0138b0e1385e4c0b8c32a5bffe12e9d99"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/GB18030.so",
"type": "reg",
"size": 178344,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 14909184,
"NumLink": 0,
"digest": "sha256:facce5fb00a24c8a6c405e08a2076775fa706a665c5b17b0847e2e289649e3f6"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/GBBIG5.so",
"type": "reg",
"size": 55456,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15065887,
"NumLink": 0,
"digest": "sha256:69311d0fbb67b61a6f4da8a77559b5c649f7baf8d9377fddd5637235b7552b39"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/GBGBK.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15099011,
"NumLink": 0,
"digest": "sha256:490382cef8df9d193c080356fb8fbd686bdd3175f1daf0ba902ea58ef758423f"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/GBK.so",
"type": "reg",
"size": 112808,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15103715,
"NumLink": 0,
"digest": "sha256:1cad89450d971726751f89feb19d40373513de10cffe0065e20d4b354f587e4b"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/GEORGIAN-ACADEMY.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15195138,
"NumLink": 0,
"digest": "sha256:645d761630fc542d42c8121c84504f16d01484ffeff9dc355416b00a30e43f52"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/GEORGIAN-PS.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15200078,
"NumLink": 0,
"digest": "sha256:d7636579114a2eba5f995bff71ff91bd6f2f03308f271a9b356673661a47277a"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/GOST_19768-74.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15205024,
"NumLink": 0,
"digest": "sha256:c44a7f287143a6fb190f01ca1488485e81f7ed9fad8f8b81aaaf0c1820d72ed1"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/GREEK-CCITT.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15209845,
"NumLink": 0,
"digest": "sha256:e5a26ab830be4dcdebe57cc422518ed2f2c4dd5275b9441c34af4b5df0d621f4"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/GREEK7-OLD.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15214317,
"NumLink": 0,
"digest": "sha256:c15f04d0267a44780eddf824781d343e25f879d071532bec5feea8332d99c110"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/GREEK7.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15218842,
"NumLink": 0,
"digest": "sha256:4904f214e0f8bda99b02b4721f51943a068c6394c677eb26dbb4f65d94d87f71"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/HP-GREEK8.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15223317,
"NumLink": 0,
"digest": "sha256:8ab569cbc017a53dd32af34d62f7d88ff1d3898fadea71e62eb6ffc7889c3a3b"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/HP-ROMAN8.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15228105,
"NumLink": 0,
"digest": "sha256:3f5d0340f0ce36889d978675a59504e65ad70a1afe4d625a63055628650ff72c"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/HP-ROMAN9.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15233029,
"NumLink": 0,
"digest": "sha256:54bb72b18c528c95b09a4290dd5da78c86c884865bc14590221ecc788dcf4924"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/HP-THAI8.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15237958,
"NumLink": 0,
"digest": "sha256:130909152ac8794e8fe8b4ec538fd608e3e6b6d13f66a4b892e25c0a9225c398"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/HP-TURKISH8.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15242829,
"NumLink": 0,
"digest": "sha256:c7fc4f586144a96fa7de6cc71a6a5445fe80da2cbad80aa7101421a7e1639146"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM037.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15247743,
"NumLink": 0,
"digest": "sha256:ffa677531c2da9a1bf845f2839cc4044aba8e1039de4a01600d4f548b7d57888"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM038.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15252485,
"NumLink": 0,
"digest": "sha256:a01d54b7e0eb7a8aa35d8152e92a5c44bfd3d61e31ab3a8fa9cb5cebd10a7dd1"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1004.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15256912,
"NumLink": 0,
"digest": "sha256:da5a574d13f503b0d81e78d414dcc1a139b04923d239fbcbfa3f097129147ae5"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1008.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15261823,
"NumLink": 0,
"digest": "sha256:5573b22026ba9d681b076fb7b7039cd521e1891f19a7cd5bebd9010fa767ee7b"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1008_420.so",
"type": "reg",
"size": 6304,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15266876,
"NumLink": 0,
"digest": "sha256:c77c2618443efe3604bcc2b3430d81610af1dbd3de34abffe3240b84276fb653"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1025.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15269808,
"NumLink": 0,
"digest": "sha256:d3f1c1d482362ddea8c36d7bbc9ed2dbf4be212155ecc654cc287efa7e2079c7"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1026.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15274665,
"NumLink": 0,
"digest": "sha256:db2d4454948890403012e477944c933aef8760781485b9074e15a555d0a6a315"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1046.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15279560,
"NumLink": 0,
"digest": "sha256:af4e25d6a109a75ea583bb16bc6f7baf836c25019cde02adecbeac0b116a0881"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1047.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15284718,
"NumLink": 0,
"digest": "sha256:a689bcda087e85aed9ac1b09e2b8fc7eca93f1d3be9555f8e5fb3c4e0c5ce108"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1097.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15289460,
"NumLink": 0,
"digest": "sha256:4fc795da190f3eb93324a9ad223481ea599ff5478d8629552e9d50a51220c78a"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1112.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15294514,
"NumLink": 0,
"digest": "sha256:ec977f45a23bfa21197e42502acb11c17666275b488b956e0240540e47088f74"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1122.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15299413,
"NumLink": 0,
"digest": "sha256:0357639fdbdde937f46feb178b768c0135a176aad0f2d2c9936fafdffd8c615a"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1123.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15304248,
"NumLink": 0,
"digest": "sha256:65632ea53e05173632a3d0dcae26bf0c1d2e0b8a3340a0111a9ab2e5f9d7bf7c"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1124.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15309113,
"NumLink": 0,
"digest": "sha256:db992833af0f46a736af4a988167be258272c958225c9f5abe3ecc2bef6e0e58"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1129.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15313977,
"NumLink": 0,
"digest": "sha256:24f9a845bc9caf06ac136b345c8945b975d870415fb17481992d4c216140b71c"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1130.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15318861,
"NumLink": 0,
"digest": "sha256:a8fb0798d47c5826b4eddbfb8eda589f3bed0d13c51d2c4a54634829586a8ddd"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1132.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15323742,
"NumLink": 0,
"digest": "sha256:d2a3fd0bee2d819027bcff4e19b989652dc064c4df22b1cba328548bf5acc47f"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1133.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15328571,
"NumLink": 0,
"digest": "sha256:0a813aacf57c8d5e844f57bafddd48166fb24464486d7eb8f574e109fc0393df"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1137.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15333396,
"NumLink": 0,
"digest": "sha256:c2ccad4af98d6fd4fd6fba34af927eb0bbacaaf0ed19873bd057f39a27256a53"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1140.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15338298,
"NumLink": 0,
"digest": "sha256:ba4d8304f773b6b875d3fc632b24a264b97489f76fac2d040b117e96b86a5f92"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1141.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15343128,
"NumLink": 0,
"digest": "sha256:783877472678337e9e9f3111e0408f0591fab7bf0879926164cb2752cecfb5df"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1142.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15347956,
"NumLink": 0,
"digest": "sha256:edbb8c88aab7ec11323f067da638af465b762bd6fcd48753858ab87c0fbde1d0"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1143.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15352782,
"NumLink": 0,
"digest": "sha256:e3d785fa4bd087becac09242fd4cbe91d19d6d992ba24e372db9f9bf5ba9a47d"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1144.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15357608,
"NumLink": 0,
"digest": "sha256:a44afd8f7d7509e91517da5c7bfcc00212ebcb5b021cd3f8297cc0a97850ad8d"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1145.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15362434,
"NumLink": 0,
"digest": "sha256:93ac0ff644b41571b707d3f78be9c22e39ebe7735a6caf9266d83f6847b86cab"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1146.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15367261,
"NumLink": 0,
"digest": "sha256:132c94776db1d54d57da406e50e8a02bfdb681ff3f3c9d4b716a2b9345f4b4b9"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1147.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15372088,
"NumLink": 0,
"digest": "sha256:cd372c049f87498c44cca93829d2e5129675b2d23b4acef1e010cbc959c5830f"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1148.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15376915,
"NumLink": 0,
"digest": "sha256:9a4dc2316705e8c0c49ce32c381bfc339c232fd26514bd0a5280e474e691712c"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1149.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15381741,
"NumLink": 0,
"digest": "sha256:98155f7777984365e0ce63e51d0ab7cd19f4184fa09a10c85edd757c16a7523e"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1153.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15386569,
"NumLink": 0,
"digest": "sha256:c9ffb0622e8512f6ecdfe1f10ba710f0c3ca47e8867886d21c2b06d3a44eb2af"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1154.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15391473,
"NumLink": 0,
"digest": "sha256:c8b38fa5d3035cca9e8f70da21ab5d0a09eaf02692dabe4ed5908ac74bfdc3b7"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1155.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15396332,
"NumLink": 0,
"digest": "sha256:8bacc4b9c6c33fe729ab68223a5bb263c6734179db8df3cc6793f34377d2588b"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1156.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15401174,
"NumLink": 0,
"digest": "sha256:aa551c5c91eb06f4181855e890e7b12b7063308d841894a2226d7da041a70062"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1157.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15406080,
"NumLink": 0,
"digest": "sha256:6d613439d5fd1cffab59037098eef8132a2ef81105a9f20cf45679dd8272006f"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1158.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15410923,
"NumLink": 0,
"digest": "sha256:315953061208acef9f8e36c4707723e33c1a82ee5648e743a5ebf2756a776917"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1160.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15415794,
"NumLink": 0,
"digest": "sha256:8aee39f57a5a2e37263bd515a2ce9dc0996d8f627a8bbedb26a4403caa5c39f6"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1161.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15420629,
"NumLink": 0,
"digest": "sha256:61a596be406a3ed50b3b5968388ceb1a889471fcceeeb1575408fa3ac7011e3e"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1162.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15425413,
"NumLink": 0,
"digest": "sha256:4420dcd49007c9cd0e59ba80b90096b1652d2e2d8a8bfc7a99ba678141af583b"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1163.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15430305,
"NumLink": 0,
"digest": "sha256:fce604a2be8f9975a159d9601bcf8e5ccdd43460e160ecfdd03172a65fded28e"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1164.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15435199,
"NumLink": 0,
"digest": "sha256:0dfed478ecbc38b59bd59cdf70ff4344b2088d65696901bf3bc92837bb2c7e9e"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1166.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15440084,
"NumLink": 0,
"digest": "sha256:a0e9df4edae2676f8ec5b4f5b9f29bb3ea2202a9d2ba8f4484163b37f6aa9096"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1167.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15445006,
"NumLink": 0,
"digest": "sha256:13799e0bb916470fdf9a785df825ea8d8cc7fc748cab754c690080fb7943fec4"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM12712.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15450020,
"NumLink": 0,
"digest": "sha256:e7b96feddd55908724a0f76348bd6b790ee46aa5cf217bc4688078f2b814c11e"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1364.so",
"type": "reg",
"size": 149664,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15454873,
"NumLink": 0,
"digest": "sha256:5eae3691cf2b1c728cbdeac9483db62b43359baada0f708d7118d0d7cbfd8376"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1371.so",
"type": "reg",
"size": 129184,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15541041,
"NumLink": 0,
"digest": "sha256:6fddb19ff2a399ea3734a21f091e2f75de7334ef813a00e8bdc9bf222cbdfd12"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1388.so",
"type": "reg",
"size": 174240,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15628179,
"NumLink": 0,
"digest": "sha256:c8d7e4a59d4578c6320c588f9a3965121154eb3a1401fcfc909edc3761cf2d50"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1390.so",
"type": "reg",
"size": 231584,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15755302,
"NumLink": 0,
"digest": "sha256:c45c11a5a89eccbf3bbc5da8212e38b23c43d623ec54fe9e8580c8b3825ebce2"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM1399.so",
"type": "reg",
"size": 231584,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15864185,
"NumLink": 0,
"digest": "sha256:69d1ed3af9406cd96f05b4767de1e1844aad4c016fdf3ee25ca341d96ecb1a2d"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM16804.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15973069,
"NumLink": 0,
"digest": "sha256:174471be37f87afb1ff2a2dc5d89ca168fac9eba1c2a7f085a88f79c23de61f6"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM256.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15978116,
"NumLink": 0,
"digest": "sha256:2fd6c744c678b54bea45ac4480281529a532dd6612ae16a46b3789642950cfae"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM273.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15983014,
"NumLink": 0,
"digest": "sha256:94d110b50d0fedefcc97863e11923467eaebfc82d0e8360d37a142b6c4a3a5d1"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM274.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15987881,
"NumLink": 0,
"digest": "sha256:a7a6242291c4cbb59097b0e6f032db96b84e01e20e62859da02687c1edb024e8"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM275.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15992310,
"NumLink": 0,
"digest": "sha256:ea6e9264226e60b9b2a7a3cf0bc36fb3c7d7268b98a6771d4bec1bbf3aebfd8c"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM277.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 15996747,
"NumLink": 0,
"digest": "sha256:07f666cb4198dd56ef05a560686b93cecb7869ec3d937e73a63b26ec72d16356"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM278.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16001614,
"NumLink": 0,
"digest": "sha256:421e5ae26d0d6680f222df26e118a624da3468d5ee8ab4f594c19dffedf15eac"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM280.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16006483,
"NumLink": 0,
"digest": "sha256:5e82dfed5e52f1b370fe5b9b4512e5a21750178dde3cd33c72d8fe4b211a6c3e"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM281.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16011351,
"NumLink": 0,
"digest": "sha256:b836097e81bce60b77c581eb02b22a36eab47385f982d4e681ad994e4e8a3911"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM284.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16015905,
"NumLink": 0,
"digest": "sha256:b5d14e21dd1a4334988c9a7d05026461243032eb28a58be8e6c593076f7b7dd6"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM285.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16020773,
"NumLink": 0,
"digest": "sha256:fcd0cce71970b57ea8f5b514edb87a67dff8881d5f8ebb507053eabe46e1f066"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM290.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16025642,
"NumLink": 0,
"digest": "sha256:8ff364f710e0fe5da17b6fb110f46c2f69bdaec817b3f5be30bd0f9fa13da1b6"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM297.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16030394,
"NumLink": 0,
"digest": "sha256:ac311a9b154b833e687e6da68b5852377826e6a3807108db5d5008ab8cb99a5b"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM420.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16035265,
"NumLink": 0,
"digest": "sha256:bdf124d4625d421740fd4a87c32b1c33c61fc510afad4d22733e90068cff00ae"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM423.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16040216,
"NumLink": 0,
"digest": "sha256:9c9aa740bfa804752c55e6f5e4900b607d080e87dee22db7a6d4fa1d1e21e37b"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM424.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16044984,
"NumLink": 0,
"digest": "sha256:f1709bc66bacb52d1ee29baadb87dfc54413500c5a2e8f2722ed7586a07471b7"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM437.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16049769,
"NumLink": 0,
"digest": "sha256:f5c1a7ff03ce11bfeefcb84dfcb4d076996dc92826c362390643ef61f5d1be77"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM4517.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16054811,
"NumLink": 0,
"digest": "sha256:637fe4b15c525d9189b7ff8327a6b36f5e85b065473d14b12bd40c7c6d3c5195"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM4899.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16059867,
"NumLink": 0,
"digest": "sha256:38d72ffc9cf63da58fdd2e8098d6f7107e9bc7a1ffe69ce7f15e1284032cbf7b"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM4909.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16064487,
"NumLink": 0,
"digest": "sha256:096bafbf84ae0a11c205c69be664d8b25d7c03b9601413c9c56709d110a9fc0b"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM4971.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16069397,
"NumLink": 0,
"digest": "sha256:aabed2c3014ca5ef9ba2343aded8d9b7be4b97b5c8c861ae5df478f146cec2d8"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM500.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16074305,
"NumLink": 0,
"digest": "sha256:5e7ea1eb91ae67e152ccaf54d91a805d79aec6dfda5440c6440204d2dd2d2f02"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM5347.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16079051,
"NumLink": 0,
"digest": "sha256:f7794f33f4f549cd7b900a9e0d124c8d8ee45ed5e72bffbf58b99636574c0dc3"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM803.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16083969,
"NumLink": 0,
"digest": "sha256:12e63f232b6d2c473e1b8e16ee34f718de0ae57753bfb78b148c4220baf5fde0"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM850.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16088531,
"NumLink": 0,
"digest": "sha256:bff40bb4f70daf445f86fc7badb76e7bef76e03d904209f804ce4c003b21ace9"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM851.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16093511,
"NumLink": 0,
"digest": "sha256:2246a0b9995ced61e360318c07f7bf3fb3de6a71ee6a3037f3455c93e9c52aa2"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM852.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16098541,
"NumLink": 0,
"digest": "sha256:94bd3f4c26c97659c7efdd4e5eb22c76ea74bebd4a483aa88e87eda89438f1df"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM855.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16103581,
"NumLink": 0,
"digest": "sha256:d69adcb0986a7b7bb42c5e8eac260ce795700f86f01058bdf6706d5786e672dc"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM856.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16108593,
"NumLink": 0,
"digest": "sha256:716fc1d1f5d59170ae615480b329b93fc17a3ccf65b1bff0193d36fbeb426ac4"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM857.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16113784,
"NumLink": 0,
"digest": "sha256:3ee104d20dce0e3d24e81386c8279bd10fbb44896749734c62cded74ad85593c"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM860.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16118759,
"NumLink": 0,
"digest": "sha256:b90781183dc19b7bb0be4aa3a698149961c6082948d6875940ba9e4b27021ebd"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM861.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16123831,
"NumLink": 0,
"digest": "sha256:6a524114517e8b549bbc4e541f992394904ede309b0dea264ff0c9be51ba3317"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM862.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16128868,
"NumLink": 0,
"digest": "sha256:65a3cf8294db774a0b1f92e147e1f765945181fff298d1e157913ac5da9c09dd"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM863.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16133935,
"NumLink": 0,
"digest": "sha256:44f3abc411039df838412e683539ad909fb5bc5d1ff9684d9f24ede26fe866b8"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM864.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16138969,
"NumLink": 0,
"digest": "sha256:8bf8472ce956e06b5d1d7e4189b3b1b1d9460393fcacd169f9881fbe926b583b"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM865.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16144043,
"NumLink": 0,
"digest": "sha256:4c4d90e3d7c6d7578e31d6bb02f3b433955e2c2adf6080d7888a8ed09daef79b"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM866.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16149081,
"NumLink": 0,
"digest": "sha256:55ae4862b9143fe249143ba2f49cbab7bfbff2f16a04e139d5a69e1ea5fa5945"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM866NAV.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16154069,
"NumLink": 0,
"digest": "sha256:1ad140369291ad4b4c9ed5a99c09647ca1d7024267226f535e32aa6120e9c626"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM868.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16159042,
"NumLink": 0,
"digest": "sha256:37947f8351375fe599584fcab7808373f9ff9dabbd8e76889818fe7e977c7acb"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM869.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16164066,
"NumLink": 0,
"digest": "sha256:41d75ca56cfef68b22f0203a8dcab4ef6b94ac6884c00531e40a06e1abe7f878"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM870.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16169050,
"NumLink": 0,
"digest": "sha256:4171f7ad9654ee4544810afb91c29222062dd454991fbf734d8f0fd2c32206b5"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM871.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16173771,
"NumLink": 0,
"digest": "sha256:781755c6a791c3bbc367e1f77d68a47b911a075bcf2b6870149553900e611eaf"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM874.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16178514,
"NumLink": 0,
"digest": "sha256:3b47f8c7c57e01afa84d534ce3a84740bf4f6f5949f33868cc6b631a160d8708"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM875.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16183333,
"NumLink": 0,
"digest": "sha256:de05a012df560caa790bfbd539a6facafe236146a47aa1086c6f284985d2d4db"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM880.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16188228,
"NumLink": 0,
"digest": "sha256:e1bf245408a0e9c5b4080480818c4d9c03919dc9a9216744bf7fb42c8ff5aeb2"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM891.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16193103,
"NumLink": 0,
"digest": "sha256:8460de36522083da985248fc15b924c3dbe7cbb7608ba15bb3b0b77d228b8367"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM901.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16197397,
"NumLink": 0,
"digest": "sha256:945e714b987eec68188cdf64585bea4cd22ae324715c3bd17246a33c16aa7880"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM902.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16202547,
"NumLink": 0,
"digest": "sha256:7ac939c532a0033b92774d6b211ed90ac688f219988649549e38ac13d9bc233e"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM903.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16207641,
"NumLink": 0,
"digest": "sha256:59c6612d1820088464c63be822e84d685e8034a01c51d0fb722e8f08dba6d789"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM9030.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16211931,
"NumLink": 0,
"digest": "sha256:1bcfb795581ebaeed2fc544cf8160d82e4ea773c48b21b33fc6d7c6692a777d0"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM904.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16216757,
"NumLink": 0,
"digest": "sha256:a1cc3d93256ed9456365a2c01a8a5cc9839b31d0590180b0cf9ccf1c1fce6669"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM905.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16221066,
"NumLink": 0,
"digest": "sha256:171f80601fa0f39d5fcacbb48ee8221e8a1a194910f9ad98339c873f34b1b120"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM9066.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16225815,
"NumLink": 0,
"digest": "sha256:98dc687de3d0354f1224275324e3b80cbfbc47ea789e9f57924efe92a35bc830"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM918.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16230592,
"NumLink": 0,
"digest": "sha256:5908006ffeb6461a7586c6f7be52d01a1449dde53ef874866387d7bf5edc41dc"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM921.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16235488,
"NumLink": 0,
"digest": "sha256:5dd97299d93e5bfd2e8171e73b4230176555cf1ac088b5f704bc628efe55d8b0"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM922.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16240389,
"NumLink": 0,
"digest": "sha256:ee548a7a8ff7579dcda62cb4fba3d2b88f45bc603fcad99e11719ca96928f4d5"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM930.so",
"type": "reg",
"size": 108792,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16245492,
"NumLink": 0,
"digest": "sha256:de5b3714b8c22e51c6e2f82287b0609b036da9c81e51d1f3dfc5759899f6fa25"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM932.so",
"type": "reg",
"size": 71848,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16326736,
"NumLink": 0,
"digest": "sha256:e341b0d949b294e2af29882efa2c177ebedc1061dbdd8def17448ba28e86d2f4"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM933.so",
"type": "reg",
"size": 125176,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16386443,
"NumLink": 0,
"digest": "sha256:63647654e0331c4c95ad7989ebea843bc023583c462a50139e20a9dfaf881942"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM935.so",
"type": "reg",
"size": 88312,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16485478,
"NumLink": 0,
"digest": "sha256:22c972c25479bc47fbed3c1d4d7c50446f9dba07caea37254f3b259f1b4346bf"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM937.so",
"type": "reg",
"size": 112808,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16554275,
"NumLink": 0,
"digest": "sha256:61b08c43ad98438cfda2db99a1b3c68e0e88f82b5a14433563fb8662994e519d"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM939.so",
"type": "reg",
"size": 108792,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16641205,
"NumLink": 0,
"digest": "sha256:7f131860e5285f14114020bc4cd70a42ee1e94ce7aeec5f36a395d65094fe513"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM943.so",
"type": "reg",
"size": 71848,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16722354,
"NumLink": 0,
"digest": "sha256:5a2930c6e3412a012bec7d42e4e214fa4904d1d0117496ce7a3bb480c46e6e67"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IBM9448.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16782497,
"NumLink": 0,
"digest": "sha256:546743fef1081d17e538c4f00759378aa7fcfe8ad3024edd6bff5f60a819f4ea"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/IEC_P27-1.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16787777,
"NumLink": 0,
"digest": "sha256:913aba8336287510c2330d2f7bc614563b932586d282ff17ca08588cedb8b2d5"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/INIS-8.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16792818,
"NumLink": 0,
"digest": "sha256:7dea555becddc9e970a514b4787407289ed4e031d046b9c7644b655bcd265976"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/INIS-CYRILLIC.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16797155,
"NumLink": 0,
"digest": "sha256:8bdc14dff70504bc689a02df98bee665bfb58e233a3785fe793e29e454b40f11"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/INIS.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16801663,
"NumLink": 0,
"digest": "sha256:816a7e39ad8aa48a8cc520d5e0339a4a5f3ac6c1d14b03c6e7456c044cf11dd5"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISIRI-3342.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16805997,
"NumLink": 0,
"digest": "sha256:dda2bd62ba2860068ce89d2cf20e12220a24687ec71b8bc07380ae6b7b85203c"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO-2022-CN-EXT.so",
"type": "reg",
"size": 39088,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16810860,
"NumLink": 0,
"digest": "sha256:e025193c38c4a257c6bc6cbbcfc76636ca73a09c9139b0626430ad27ff795513"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO-2022-CN.so",
"type": "reg",
"size": 34984,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16829575,
"NumLink": 0,
"digest": "sha256:65f8fdb4fa806278d1c8a6e5479cf0935b38c0ac5ef9f803f413db124beed643"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO-2022-JP-3.so",
"type": "reg",
"size": 26792,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16844919,
"NumLink": 0,
"digest": "sha256:f3bcfe27bb653b69949f0d6446544cd721f998dd56917d7e563097088d381afe"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO-2022-JP.so",
"type": "reg",
"size": 43208,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16857314,
"NumLink": 0,
"digest": "sha256:1a299788585bfd0f94ef83bc91170f6e4d8deff92153ab01f269be57e6ff153d"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO-2022-KR.so",
"type": "reg",
"size": 14504,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16877102,
"NumLink": 0,
"digest": "sha256:32dd52a49870d3495a86d9594cea8fb8093f2bd4aff8648b530ccf5043b5562c"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO-IR-197.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16883964,
"NumLink": 0,
"digest": "sha256:cbb8b3adfc2e13b87e0e8ef29c430d5fc8d2f33cbaefdf96a0a549aa4044dc60"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO-IR-209.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16888927,
"NumLink": 0,
"digest": "sha256:8d8b34e4aa48e0b44836ebb35206743cc6d390da28d4ddf64c4ce880213ffd34"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO646.so",
"type": "reg",
"size": 18704,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16893904,
"NumLink": 0,
"digest": "sha256:625a226500b5f6c8ce49a86987decce222f543c22253560bd09949ce22fde3c5"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO8859-1.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16903072,
"NumLink": 0,
"digest": "sha256:46727b1a93dd243dfb0429fcbb477bfdbd6b415daa00acfba60434f9283faaac"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO8859-10.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16906849,
"NumLink": 0,
"digest": "sha256:851f0221d15a6c0e2da791226a6612184105741aa1ef006dda3b655308d91512"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO8859-11.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16911763,
"NumLink": 0,
"digest": "sha256:dba2a8a403fab6f5f12ad1fe136f3fcde328339b61a7910b1de37c837bf545d3"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO8859-13.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16916633,
"NumLink": 0,
"digest": "sha256:b8f450dcf883cd8c998a8755fcbead27e8a72a80d6728d231c41d728c3893277"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO8859-14.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16921539,
"NumLink": 0,
"digest": "sha256:38998a00cb4eb2dbf7b2103ac1ba1c253b6d2bf604f8c2ff90569704e2d224c2"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO8859-15.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16926468,
"NumLink": 0,
"digest": "sha256:c011d6da2ccc5cc2679d32d38b7722527aca659c6c3f449d9c9e73b499bd5a01"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO8859-16.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16931317,
"NumLink": 0,
"digest": "sha256:0e41455363ef0515ff562b97ea50a0bf52f833d56b8a56d9f2adb53315438155"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO8859-2.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16936226,
"NumLink": 0,
"digest": "sha256:5515c694f7211d21914cb5b20c7533d712b0ab8b3509e4eeb7ef89884fec632c"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO8859-3.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16940961,
"NumLink": 0,
"digest": "sha256:2d0c5d71deb6725dc7b6c5b64785cfd67bfef0f29089f8e263e90968ca8c544a"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO8859-4.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16945727,
"NumLink": 0,
"digest": "sha256:b495bfa75a310c1795456ba1d0599299f80fee44cc62264809e52981dcc700d7"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO8859-5.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16950466,
"NumLink": 0,
"digest": "sha256:12dd7d7541d9b049169eb3047adcb274f0a4a0e66fea7206421caed761260e0f"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO8859-6.so",
"type": "reg",
"size": 14504,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16955330,
"NumLink": 0,
"digest": "sha256:553da67d94b5f224c7e687433ded076d6732cde209d6224e76522296230665ff"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO8859-7.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16959999,
"NumLink": 0,
"digest": "sha256:eb496fdaedbc8b165c45a4b9fe86a698fd423ad2b5b44503571c81b66e6f16b9"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO8859-8.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16964920,
"NumLink": 0,
"digest": "sha256:ae95477c13c51f9efcb543114ce0ccac531f42588b286d84ac69778a45409681"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO8859-9.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16969714,
"NumLink": 0,
"digest": "sha256:6e6628a4f0a818d3eef3f22ef8fb119e0f3cae87852997124158135d948f37b8"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO8859-9E.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16974408,
"NumLink": 0,
"digest": "sha256:d575123ca67fb2e29c1fb632b0e42d31fd90bea935673f782e9047b0d6ec1c4d"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO_10367-BOX.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16979336,
"NumLink": 0,
"digest": "sha256:9f7e623d1a813d7499f13eda918130d8b4b6865f7fb44f88eecb9e82a7d33780"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO_11548-1.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16984078,
"NumLink": 0,
"digest": "sha256:99a44f668b7d38c97c2792319b325963f28c47b3677db18a41e8fe4e7a4d9fa3"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO_2033.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16987856,
"NumLink": 0,
"digest": "sha256:26cffd10435270376d9dc677847bafd6eb1c7ea08b0534162798feed6547a1d2"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO_5427-EXT.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16992024,
"NumLink": 0,
"digest": "sha256:0b8cc43ca2ff904d2c67eadd907b9e235055336e4f0b31cd5c413d53ae2754bc"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO_5427.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 16996339,
"NumLink": 0,
"digest": "sha256:346d9e98132068c292ab331c0f1a7291df75b727b2c5c713c4470aa70a95301d"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO_5428.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17000793,
"NumLink": 0,
"digest": "sha256:fa3a53e36ea0e51b216cb0c128a3ec2746099f9b1799d36b3d2ffa78bbb50844"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO_6937-2.so",
"type": "reg",
"size": 22696,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17005266,
"NumLink": 0,
"digest": "sha256:917eec307f0419b0eb5626a3ce06a06c6b2cde5bfb07eee33e690351b5d2bf2c"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/ISO_6937.so",
"type": "reg",
"size": 22696,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17013126,
"NumLink": 0,
"digest": "sha256:7bdf85fceef907f3f107f180ee1c8035df2a4a6f8ea081c470d8ef69d3b4104e"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/JOHAB.so",
"type": "reg",
"size": 18600,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17021010,
"NumLink": 0,
"digest": "sha256:49f1473027e1f4a77cb488a480a2416c2da50817e4ce504382bffbd628020b56"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/KOI-8.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17029049,
"NumLink": 0,
"digest": "sha256:3e7a16200475dd0f5b4efc87d4d7b76d69d74418d3b8a5ea6a9b1103885ef543"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/KOI8-R.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17033579,
"NumLink": 0,
"digest": "sha256:3734f0fce1d3cea25057e64a3040067c2b4a75095a844b0014533f58eae0caab"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/KOI8-RU.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17038579,
"NumLink": 0,
"digest": "sha256:c4bcab695d57c38718e0e0f2bcd54ec8d0c8d5c4ab6b3bdd435b0ae47bba59e3"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/KOI8-T.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17043587,
"NumLink": 0,
"digest": "sha256:79c497aebaaa188ebc8e112fa86c1c52510e1ff8acf5020a413289cfde47103d"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/KOI8-U.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17048509,
"NumLink": 0,
"digest": "sha256:6c0fee532eef0f23262c6f5f40dd7bd0e75f3348c444600dd22974f1efbb869f"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/LATIN-GREEK-1.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17053521,
"NumLink": 0,
"digest": "sha256:bfbafee63147fa484f5d99d50185b8650dc654d0261e30d557caa04f086ee04e"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/LATIN-GREEK.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17057977,
"NumLink": 0,
"digest": "sha256:1d9886ec5ac307b7d6dcdde296d9776ff03742108fc9edde3a7edf5cfb6a4edd"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/MAC-CENTRALEUROPE.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17062446,
"NumLink": 0,
"digest": "sha256:7b5342684ef8b8d3b96bb77796d922eee2d86f51bd4dc3e2f6be99c94d4c89c4"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/MAC-IS.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17067418,
"NumLink": 0,
"digest": "sha256:1cda7799bf65f03fdce63d58f62ff3d3ee232381a936d27dd7ffb2bdd2640e12"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/MAC-SAMI.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17072464,
"NumLink": 0,
"digest": "sha256:7e59bc0bcef532ac1921dabe599d97cf51ac48bc96ba94cd16e3332b34010088"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/MAC-UK.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17077453,
"NumLink": 0,
"digest": "sha256:e1e6608ed00aa7edd17536a89b55772415b69698d74b0e7ac56bff88ddae7d0a"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/MACINTOSH.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17082395,
"NumLink": 0,
"digest": "sha256:bb17f05e20edcb5a297536325145fb8fb8944df8ed3e2b304fd9a3f7d70d6daa"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/MIK.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17087461,
"NumLink": 0,
"digest": "sha256:c1182feb2279704ac8908682559744389a8a643378d6937eb68f78b2ba213ee7"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/NATS-DANO.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17092524,
"NumLink": 0,
"digest": "sha256:bf1cf46fc38c6797ed76283e8d7167dc0db37e7b19988a6dd1021006bd05f371"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/NATS-SEFI.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17097005,
"NumLink": 0,
"digest": "sha256:05c0ce52f879990be8e84ee6fd13cd1056b3ba371769017b9d4739447342891c"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/PT154.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17101473,
"NumLink": 0,
"digest": "sha256:bccaa42a9d9707c53b6646385a5dd19f6498bfe11048a51812d1e576dc6d2577"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/RK1048.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17106400,
"NumLink": 0,
"digest": "sha256:d49391cc45f31a755e61a9200df1f574bb6f2e8be75808b95c54a427f80e0c63"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/SAMI-WS2.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17111410,
"NumLink": 0,
"digest": "sha256:d0dafadac4c42a811741e0cd8c7e2fde3ec62ef1dbab88b93bb0d4b0b2c7c116"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/SHIFT_JISX0213.so",
"type": "reg",
"size": 18600,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17116379,
"NumLink": 0,
"digest": "sha256:ce1c56c87b70ae86f4e8740beb57bf0856842ee16aa681f7c1fab2f3c6ce055a"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/SJIS.so",
"type": "reg",
"size": 96424,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17124377,
"NumLink": 0,
"digest": "sha256:b4dd8bf37ed58cb46c1e62348e7731bcc49a3345646fa0d448e1764276a16203"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/T.61.so",
"type": "reg",
"size": 18600,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17163270,
"NumLink": 0,
"digest": "sha256:3e420f16235289e9ca17826b421ef9ec9db680fe943fbd159ff7cb48e340cd99"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/TCVN5712-1.so",
"type": "reg",
"size": 14512,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17170395,
"NumLink": 0,
"digest": "sha256:6b6c6e685c14292a607b93d809350c567e2a4e3a1548c6bb7b0b80a424163f61"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/TIS-620.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17177861,
"NumLink": 0,
"digest": "sha256:640a340021b4d554c98cff37bcb5b9e15346398e3af69629ff6b3366a04a1392"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/TSCII.so",
"type": "reg",
"size": 18600,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17182612,
"NumLink": 0,
"digest": "sha256:3a12de9b3f86bd3f60a14ecbd5b8736b8889da21d467eb2509a6baa47c9181a4"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/UHC.so",
"type": "reg",
"size": 71848,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17191613,
"NumLink": 0,
"digest": "sha256:87745f69d98339a80a94319c170c349e80b4d82ea2da5396f32e92f9b6baf687"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/UNICODE.so",
"type": "reg",
"size": 10424,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17239936,
"NumLink": 0,
"digest": "sha256:8a56a8d04dfc075211652c6d262025496610816f21c50f24a94dd44e3b464998"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/UTF-16.so",
"type": "reg",
"size": 14528,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17244247,
"NumLink": 0,
"digest": "sha256:968f95cc06ad1abccf4a9d085bc56c4bcb63dfa2a7d89772702ed33c8d7fdeca"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/UTF-32.so",
"type": "reg",
"size": 10432,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17250022,
"NumLink": 0,
"digest": "sha256:f1c221602b45ccc3050a416128510f555017047673d13e56af123a1435717fa0"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/UTF-7.so",
"type": "reg",
"size": 18608,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17254582,
"NumLink": 0,
"digest": "sha256:d69f39af22d64a34517529b6335756ce362daa89c9d90e36865950405ecf789c"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/VISCII.so",
"type": "reg",
"size": 10408,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17263074,
"NumLink": 0,
"digest": "sha256:441f95483f53007c5934b27a02980cb5e3c9a00f8d10600207cf66a84ced2098"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/gconv-modules",
"type": "reg",
"size": 56095,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17267992,
"NumLink": 0,
"digest": "sha256:6f90f42555e1f390e26b6090186384cf4131bba167c331b58e8d7b13f86b7b45"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache",
"type": "reg",
"size": 26258,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17277397,
"NumLink": 0,
"digest": "sha256:eef49f11d96d1ff44e461f9ef1dfbd9a70f02ab5dbaec1cc302527a775c62fe4"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/libCNS.so",
"type": "reg",
"size": 468984,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17288862,
"NumLink": 0,
"digest": "sha256:fbc708c917e17da80bbc83b4d1cbfd47d511c17085ef18e8c0241a83ce8ee5dd"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/libGB.so",
"type": "reg",
"size": 67576,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17580006,
"NumLink": 0,
"digest": "sha256:015d06af82091920aa60f2b9e06ae8b18ad45488917f270a956ebd11c7d67674"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/libISOIR165.so",
"type": "reg",
"size": 59384,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17614076,
"NumLink": 0,
"digest": "sha256:3aed87fc8c51a7f5de1921639de2c02499b1075852c98c23a95827bc40e5b667"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/libJIS.so",
"type": "reg",
"size": 100344,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17654497,
"NumLink": 0,
"digest": "sha256:a329c58c5692ae5fb1a3d900a14226def7afbeae9293a352291f963d9d167fcd"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/libJISX0213.so",
"type": "reg",
"size": 120824,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17721680,
"NumLink": 0,
"digest": "sha256:d4c0867d87e8d3b67cbaf3d16d9e0febbc4c889ea99312b06628641004027ec2"
},
{
"name": "usr/lib/x86_64-linux-gnu/gconv/libKSC.so",
"type": "reg",
"size": 47096,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 17773967,
"NumLink": 0,
"digest": "sha256:a162aa2fce5ab2a500d13c461d90c772dea2e3134f01c788908de538584cb626"
},
{
"name": "usr/lib/x86_64-linux-gnu/libapt-pkg.so.5.0",
"type": "symlink",
"modtime": "2017-09-13T16:47:33Z",
"linkName": "libapt-pkg.so.5.0.1",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/libapt-pkg.so.5.0.1",
"type": "reg",
"size": 1776872,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33188,
"offset": 17813375,
"NumLink": 0,
"digest": "sha256:c90b5b1173c7e515a7fefee77c5d9b8d0fef4e4ca49b89e806061fb0b30f6088"
},
{
"name": "usr/lib/x86_64-linux-gnu/libapt-private.so.0.0",
"type": "symlink",
"modtime": "2017-09-13T16:47:33Z",
"linkName": "libapt-private.so.0.0.0",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/libapt-private.so.0.0.0",
"type": "reg",
"size": 378968,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33188,
"offset": 18584843,
"NumLink": 0,
"digest": "sha256:5b38fedfa8f8a38929bb9fdfa5bc65a15610457b53a662f50f7ec6dca23e2fce"
},
{
"name": "usr/lib/x86_64-linux-gnu/libdb-5.3.so",
"type": "reg",
"size": 1827512,
"modtime": "2017-09-24T07:14:53Z",
"mode": 33188,
"offset": 18743676,
"NumLink": 0,
"digest": "sha256:0d14b28c8e96a50f8532cc26eb8c6e9f49db92fbe9cd35389a3926599c92a12c"
},
{
"name": "usr/lib/x86_64-linux-gnu/libdebconfclient.so.0",
"type": "symlink",
"modtime": "2017-04-03T05:58:27Z",
"linkName": "libdebconfclient.so.0.0.0",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/libdebconfclient.so.0.0.0",
"type": "reg",
"size": 10360,
"modtime": "2017-04-03T05:58:27Z",
"mode": 33188,
"offset": 19590542,
"NumLink": 0,
"digest": "sha256:a2a1f22356ddcffcf35745f744dfeabdee3147653226ab168f994d832fad2638"
},
{
"name": "usr/lib/x86_64-linux-gnu/libformw.so.5",
"type": "symlink",
"modtime": "2017-12-28T09:47:33Z",
"linkName": "libformw.so.5.9",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/libformw.so.5.9",
"type": "reg",
"size": 68840,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 19593588,
"NumLink": 0,
"digest": "sha256:fc08c8a92dab40a2bdefe2524665db90e03cd483a173d7ca643deaf9deb45461"
},
{
"name": "usr/lib/x86_64-linux-gnu/liblz4.so.1",
"type": "symlink",
"modtime": "2016-02-17T15:27:54Z",
"linkName": "liblz4.so.1.7.1",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/liblz4.so.1.7.1",
"type": "reg",
"size": 72024,
"modtime": "2016-02-17T15:27:54Z",
"mode": 33188,
"offset": 19624444,
"NumLink": 0,
"digest": "sha256:89bfd1003b37b293626a7800b34eab1b1d665465960d6955fd151eda316d14a1"
},
{
"name": "usr/lib/x86_64-linux-gnu/libmenuw.so.5",
"type": "symlink",
"modtime": "2017-12-28T09:47:33Z",
"linkName": "libmenuw.so.5.9",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/libmenuw.so.5.9",
"type": "reg",
"size": 35088,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 19658643,
"NumLink": 0,
"digest": "sha256:00069d616f3a56330f24e452e3950fc374fc5f380d46a71d4eea516cdfb46933"
},
{
"name": "usr/lib/x86_64-linux-gnu/libpanelw.so.5",
"type": "symlink",
"modtime": "2017-12-28T09:47:33Z",
"linkName": "libpanelw.so.5.9",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/libpanelw.so.5.9",
"type": "reg",
"size": 14184,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 19673786,
"NumLink": 0,
"digest": "sha256:b59bd2358da7fbe2f4c9a3df20781c72a00dd271fc93243363d566f3b862252c"
},
{
"name": "usr/lib/x86_64-linux-gnu/libpcreposix.so.3",
"type": "symlink",
"modtime": "2017-03-21T22:03:19Z",
"linkName": "libpcreposix.so.3.13.3",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/libpcreposix.so.3.13.3",
"type": "reg",
"size": 10128,
"modtime": "2017-03-21T22:03:19Z",
"mode": 33188,
"offset": 19679391,
"NumLink": 0,
"digest": "sha256:20ab25eb5624d2f2046c5a960f9b430cd5d2d26bb6d06659d2ba5ec99c33dcd6"
},
{
"name": "usr/lib/x86_64-linux-gnu/libsemanage.so.1",
"type": "reg",
"size": 248032,
"modtime": "2016-12-30T15:42:09Z",
"mode": 33188,
"offset": 19682805,
"NumLink": 0,
"digest": "sha256:a49abba01c68d4b5ee596db8bb5d1df4f7fc8eabf7650dcb87f54acd7572d824"
},
{
"name": "usr/lib/x86_64-linux-gnu/libstdc++.so.6",
"type": "symlink",
"modtime": "2018-02-14T16:53:20Z",
"linkName": "libstdc++.so.6.0.22",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.22",
"type": "reg",
"size": 1566168,
"modtime": "2018-02-14T16:53:20Z",
"mode": 33188,
"offset": 19780945,
"NumLink": 0,
"digest": "sha256:4376550db3d4404d019fab791ad0795ec76ded3fc969248c11ec6d775f614860"
},
{
"name": "usr/lib/x86_64-linux-gnu/libtic.so.5",
"type": "symlink",
"modtime": "2017-12-28T09:47:33Z",
"linkName": "libtic.so.5.9",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/libtic.so.5.9",
"type": "reg",
"size": 59400,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 20271814,
"NumLink": 0,
"digest": "sha256:c75698c2c3fe73c1eb5cda96578cf74aebd9d9115e5094cd17d2d14fc21cac89"
},
{
"name": "usr/lib/x86_64-linux-gnu/libustr-1.0.so.1",
"type": "symlink",
"modtime": "2016-11-23T19:59:34Z",
"linkName": "libustr-1.0.so.1.0.4",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/libustr-1.0.so.1.0.4",
"type": "reg",
"size": 215040,
"modtime": "2016-11-23T19:59:34Z",
"mode": 33188,
"offset": 20301589,
"NumLink": 0,
"digest": "sha256:95f03fca5815ca2181d1323e140aa73f89842dd3792efc836dc1ca7bab1b27f8"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl/",
"type": "dir",
"modtime": "2018-06-10T17:37:28Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/AutoLoader.pm",
"type": "reg",
"size": 5487,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20380419,
"NumLink": 0,
"digest": "sha256:750ee369fbf3c34f72a823ca261d33a89bba98ad1d4a967f4f89e19b122eaabf"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Carp/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Carp/Heavy.pm",
"type": "reg",
"size": 773,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20382632,
"NumLink": 0,
"digest": "sha256:d5ac9b22f0e3e5632a1ce0945adedacbcfd02f4409d60437e908604f4857fb58"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Carp.pm",
"type": "reg",
"size": 20138,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20383195,
"NumLink": 0,
"digest": "sha256:fa19d9299eb23a422f3d8220e06e434ea4e6b3154226d8ec8ba84cb3904060d4"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Config.pm",
"type": "reg",
"size": 3331,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20390093,
"NumLink": 0,
"digest": "sha256:2f1a1c4013c19ebf4260c6b6e5f0039145f54409411fd140b4f153e641d7fad2"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Config_git.pl",
"type": "reg",
"size": 409,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20391666,
"NumLink": 0,
"digest": "sha256:09c5e2ee35ee18d9043d95273f1cd37bc82e80567fd1372a1eb134c809c39504"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Config_heavy.pl",
"type": "reg",
"size": 50645,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20391977,
"NumLink": 0,
"digest": "sha256:0ecaa7f841191eed99ec3daaa84d891eb68125d1f357f337160a630dea100d12"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Cwd.pm",
"type": "reg",
"size": 18542,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20407426,
"NumLink": 0,
"digest": "sha256:0e707f6c9273736f137eed9710aa2eec7edf582f86813abc8820fb982c4bf101"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/DynaLoader.pm",
"type": "reg",
"size": 10453,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20414075,
"NumLink": 0,
"digest": "sha256:877b793ad9ae57d997c22aa505862f0a44221764b12201a2bc8634260ac7fffc"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Errno.pm",
"type": "reg",
"size": 4884,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20418413,
"NumLink": 0,
"digest": "sha256:fb0e2878b7d240e5d4a74509a19ae2fff14ffe23ab3a69494c41a916ab915443"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Exporter/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Exporter/Heavy.pm",
"type": "reg",
"size": 6406,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20420798,
"NumLink": 0,
"digest": "sha256:4a05d2fb4ade7016e108d8b8cdb25b8426b232560be0874755b198fc5be0fee1"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Exporter.pm",
"type": "reg",
"size": 2367,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20423386,
"NumLink": 0,
"digest": "sha256:b9db7ff262f5d5bcc8ac7c827b0453c7e514c3cb1dd6a30fad96a25de2a76991"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Fcntl.pm",
"type": "reg",
"size": 2156,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20424559,
"NumLink": 0,
"digest": "sha256:293ad7b0904fa32bf79bda796dd2244e799bb6b42450c910ef8f54d2edf076ee"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/File/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/File/Basename.pm",
"type": "reg",
"size": 5429,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20425726,
"NumLink": 0,
"digest": "sha256:9aeea43fda475ea4e2b75633b2e25528f45b2510c7df4809449fbf938de58bd8"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/File/Glob.pm",
"type": "reg",
"size": 1837,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20427698,
"NumLink": 0,
"digest": "sha256:ad3a5d01dbdaea75fe6a46ab66146583d71ba0fff3b7b91dce67409a2d6e5f84"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/File/Path.pm",
"type": "reg",
"size": 18490,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20428654,
"NumLink": 0,
"digest": "sha256:83defe5b5d9de11983c36de5507cefbf71822786fcd05d4549b4d3ab083f654a"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/File/Spec/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/File/Spec/Unix.pm",
"type": "reg",
"size": 10014,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20433898,
"NumLink": 0,
"digest": "sha256:9be5591be0deed683c85b784c13d7706aaf156301da775975bc529fc31fb7ddd"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/File/Spec.pm",
"type": "reg",
"size": 622,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20437583,
"NumLink": 0,
"digest": "sha256:fd864e0a9be904138992c9174a9f51524d38599ad6b1d584fa8ef723c652c3dd"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/File/Temp.pm",
"type": "reg",
"size": 46927,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20438022,
"NumLink": 0,
"digest": "sha256:f83f5e969b8ba4cb9de0302624d1df4220f1ad8c5e20f83acb9967d474516fb2"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/FileHandle.pm",
"type": "reg",
"size": 2105,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20452671,
"NumLink": 0,
"digest": "sha256:54211db845a110f16d8f3630e2cd2d258f2e47078a1e5261b0eae18a0a5d6cd5"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Getopt/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Getopt/Long.pm",
"type": "reg",
"size": 43321,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20453804,
"NumLink": 0,
"digest": "sha256:0a9a40e9df4b4c1a5aa260ab5fc9affa4cae13a8070fc52aea6768d276434fb9"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Hash/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Hash/Util.pm",
"type": "reg",
"size": 7580,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20466581,
"NumLink": 0,
"digest": "sha256:983dc4ad312e28882bc90465167f4a19e03dd4c27ecae615190cdbc26aa0fc82"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/IO/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/IO/File.pm",
"type": "reg",
"size": 1666,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20468833,
"NumLink": 0,
"digest": "sha256:79263664dcd2997530dd5d941fbccc1470d4c66b8ae82c02fd96290513c39d14"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/IO/Handle.pm",
"type": "reg",
"size": 8235,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20469704,
"NumLink": 0,
"digest": "sha256:fb4a9ff170e5a74ded4c42c539f6d5ce8999761b394830d39e6d3f152ab73b69"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/IO/Pipe.pm",
"type": "reg",
"size": 3425,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20472376,
"NumLink": 0,
"digest": "sha256:45f66014a6b374dbbbcc0325c79024b7d8bb22b41c8c7dc855e4394cebfe7c26"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/IO/Seekable.pm",
"type": "reg",
"size": 686,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20473786,
"NumLink": 0,
"digest": "sha256:a8ac552be340a0b593516d44eb34ca695676e46d2492f2651efeb6ada7f22103"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/IO/Select.pm",
"type": "reg",
"size": 4359,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20474271,
"NumLink": 0,
"digest": "sha256:09c61eed565460a2edee326be3eb5391ce55c0e3c53ec74c831a87646decf955"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/IO/Socket/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/IO/Socket/INET.pm",
"type": "reg",
"size": 7474,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20476044,
"NumLink": 0,
"digest": "sha256:4ff429519f3b93fb5e6ae2e42127b75cc5f37b6978de5885fd8251f7489057f9"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/IO/Socket/IP.pm",
"type": "reg",
"size": 20427,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20478446,
"NumLink": 0,
"digest": "sha256:866b9e45195fabae560e69cb445142d8e2aa902825c54b183fe5463137b7aa9a"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/IO/Socket/UNIX.pm",
"type": "reg",
"size": 1375,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20484519,
"NumLink": 0,
"digest": "sha256:1555039d56f24fccc3b829dcd40b87c1aa04b22e37b228e903ce12fb9bd0d58e"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/IO/Socket.pm",
"type": "reg",
"size": 9455,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20485297,
"NumLink": 0,
"digest": "sha256:c1074f21b6d6a5e43e8c4363cb7ca4847a32e24f373d10ada9dfd3c93bad2d9c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/IO.pm",
"type": "reg",
"size": 472,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20488603,
"NumLink": 0,
"digest": "sha256:3d851a18125f4ad6f9d4d9aa8be00a33ce924b3965f0381fd8e74ac1507f68c6"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/IPC/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/IPC/Open2.pm",
"type": "reg",
"size": 816,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20489092,
"NumLink": 0,
"digest": "sha256:6500142ce04d79ddb58102de7bdf1427c49a9d1be04f6c908027b6240a0e6303"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/IPC/Open3.pm",
"type": "reg",
"size": 9053,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20489716,
"NumLink": 0,
"digest": "sha256:2d4aa629476d90f663ebff57af566044f9cb134f4eef1699516dc13c227cdb83"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/List/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/List/Util.pm",
"type": "reg",
"size": 1078,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20493539,
"NumLink": 0,
"digest": "sha256:0d9f7992c477f3cce159a5477927380e304cca7cb4cc99abf9e520bff652ffab"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/POSIX.pm",
"type": "reg",
"size": 19879,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20494294,
"NumLink": 0,
"digest": "sha256:9d3f00c0ad31d9bc8875d19f0703490b39bc4ff8c36de3864269fd30708165a7"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Scalar/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Scalar/Util.pm",
"type": "reg",
"size": 1410,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20502130,
"NumLink": 0,
"digest": "sha256:3b04edbbe4a7f38411c5081ab10c79f538e7c08dd7b4866d63f60492a2d09be6"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/SelectSaver.pm",
"type": "reg",
"size": 344,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20502957,
"NumLink": 0,
"digest": "sha256:f6f2b9bf40423e54466311866dc9f67a1318396d2d162cf84722e28d715a0ca9"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Socket.pm",
"type": "reg",
"size": 13554,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20503310,
"NumLink": 0,
"digest": "sha256:535d67a90adfaab5f799eea70bf57859da660a0c99fd4777384d7e85123d0b8c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Symbol.pm",
"type": "reg",
"size": 2099,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20508323,
"NumLink": 0,
"digest": "sha256:b2ba240a38118df64bb369c770ae6476502a71097ba53f65fe435d82deafd94d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Text/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Text/ParseWords.pm",
"type": "reg",
"size": 4480,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20509533,
"NumLink": 0,
"digest": "sha256:aa7484ce8671e27adbb65f24d19d376882e84edab8da3ea91d144fefc6906184"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Text/Tabs.pm",
"type": "reg",
"size": 1702,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20511107,
"NumLink": 0,
"digest": "sha256:fa167b69c997b88b575b3a98013421745b41c3681b0a6a7433f17c1da19f8f25"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Text/Wrap.pm",
"type": "reg",
"size": 2922,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20512058,
"NumLink": 0,
"digest": "sha256:d63777a317a5631dcfb6b0ea4ae5f782d2e718ff31b9f091ac03962a997fc095"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Tie/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/Tie/Hash.pm",
"type": "reg",
"size": 2037,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20513449,
"NumLink": 0,
"digest": "sha256:e81ae4e495e961af321beac6695b5d43870418739901785a6b90c742f2d39d42"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/XSLoader.pm",
"type": "reg",
"size": 3899,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20514378,
"NumLink": 0,
"digest": "sha256:7d50e4be2f9f14462e63858a7e2582e3e7eb7ecef4d7f2c8e74634abf71b987d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/attributes.pm",
"type": "reg",
"size": 3018,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20516162,
"NumLink": 0,
"digest": "sha256:67b321a13ffc51ace70061b9d2a079e1775fd7b915c27a3176cc40c7fab5a9e6"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/auto/",
"type": "dir",
"modtime": "2018-06-10T17:37:28Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/auto/Cwd/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/auto/Cwd/Cwd.so",
"type": "reg",
"size": 18704,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20517619,
"NumLink": 0,
"digest": "sha256:ec2714a435c774dba95b03f798b37f13cee4041449dfbc1c131c1234ff2bd0df"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/auto/Fcntl/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/auto/Fcntl/Fcntl.so",
"type": "reg",
"size": 18688,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20524515,
"NumLink": 0,
"digest": "sha256:ea8785c9591bb2c1d35e125a1b7c92fa14c01e3558004ecfd85d6ba0cf932bdf"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/auto/File/",
"type": "dir",
"modtime": "2018-06-10T17:37:28Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/auto/File/Glob/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/auto/File/Glob/Glob.so",
"type": "reg",
"size": 27176,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20531138,
"NumLink": 0,
"digest": "sha256:9a11a966a7961d77dc8b882e35c0424f6a938a8931e6ad8994298f19292e6772"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/auto/Hash/",
"type": "dir",
"modtime": "2018-06-10T17:37:28Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/auto/Hash/Util/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/auto/Hash/Util/Util.so",
"type": "reg",
"size": 14544,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20543995,
"NumLink": 0,
"digest": "sha256:c4f6fe1e6a3f629d31afeb9f4b27d3f83a68f7efaf9e4fa29aa143cf728d38de"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/auto/IO/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/auto/IO/IO.so",
"type": "reg",
"size": 18768,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20550050,
"NumLink": 0,
"digest": "sha256:11b53111335419d0c238e35d067fc2eca5d7b8c0ecf98570b0d6b5a537980870"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/auto/List/",
"type": "dir",
"modtime": "2018-06-10T17:37:28Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/auto/List/Util/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/auto/List/Util/Util.so",
"type": "reg",
"size": 47696,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20557011,
"NumLink": 0,
"digest": "sha256:73c3c43ebc0f93b27345edfd7795f4fdeeb9004d3a30e51dd0a33046282f619e"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/auto/POSIX/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/auto/POSIX/POSIX.so",
"type": "reg",
"size": 106128,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20577193,
"NumLink": 0,
"digest": "sha256:aeb85d4a9ea24a5350c0e68574d7b343aceea965eb733125bd2c3710bd924797"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/auto/Socket/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/auto/Socket/Socket.so",
"type": "reg",
"size": 43464,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20614367,
"NumLink": 0,
"digest": "sha256:503d657ab8a95d1979f74bdcce8796f06f32d49f7a8631815e07e53a0073845c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/auto/attributes/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/auto/attributes/attributes.so",
"type": "reg",
"size": 10408,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20630016,
"NumLink": 0,
"digest": "sha256:4031753d1e50bc3e256e64af263c0ae58f9080219869fd6b3fd0e2e383b43427"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/auto/re/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/auto/re/re.so",
"type": "reg",
"size": 495120,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20634547,
"NumLink": 0,
"digest": "sha256:2aeebf7edb04019dcfefaa326d6d779766d3b5da6ec6633807bd569fbcf5dd85"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/base.pm",
"type": "reg",
"size": 8931,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20858830,
"NumLink": 0,
"digest": "sha256:394447d984af9973eef7ce0628625cfad98ec778f3ed81a467f0b8c6ed2fd1a1"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/bytes.pm",
"type": "reg",
"size": 447,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20862319,
"NumLink": 0,
"digest": "sha256:cb05c506666da8af731d6470f1d5824c794461e1d5a6a4fd0ec9a78d1589e574"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/bytes_heavy.pl",
"type": "reg",
"size": 758,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20862684,
"NumLink": 0,
"digest": "sha256:c7def62cbf7d031c4fe319e414117043f2a273885bff93bd18e11935d00a6677"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/constant.pm",
"type": "reg",
"size": 5737,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20863007,
"NumLink": 0,
"digest": "sha256:5decbf923c0f5f065147a7a1a89fd351a26d5d5efd8799ba4ee992a1d00cd837"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/feature.pm",
"type": "reg",
"size": 4313,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20865364,
"NumLink": 0,
"digest": "sha256:79baab039d05935885bb984532dbfeed64e4fd5b238a6c6cdce38b92524bad92"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/fields.pm",
"type": "reg",
"size": 5022,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20866841,
"NumLink": 0,
"digest": "sha256:47de715abc8377a925a04ca8e0f68f80957c075fd0cddb2400724968c22c08ef"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/integer.pm",
"type": "reg",
"size": 172,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20868888,
"NumLink": 0,
"digest": "sha256:1c387bfbc4bcb4046e7372c534372c7150315499bc5641328d5c3c1c1ad50373"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/lib.pm",
"type": "reg",
"size": 2284,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20869115,
"NumLink": 0,
"digest": "sha256:6636e89a2107d3d76574e41f6535aee9d740700c892fe1f0179f015356d9ba4e"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/locale.pm",
"type": "reg",
"size": 3421,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20870148,
"NumLink": 0,
"digest": "sha256:d584f47f71a525301511965b4a245933e9a936f94406b6bcd1c78e7fe63a7b6f"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/overload.pm",
"type": "reg",
"size": 4466,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20871537,
"NumLink": 0,
"digest": "sha256:6eb2a643d33cb9ad8c265e890836c4c9a7bf3be53efa9a75e6bf8a99704f927d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/overloading.pm",
"type": "reg",
"size": 964,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20873372,
"NumLink": 0,
"digest": "sha256:5387b33943963d2e47f05b3f37926f2d454f9ded33ca857e56ffb33b3adb999d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/parent.pm",
"type": "reg",
"size": 479,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20873905,
"NumLink": 0,
"digest": "sha256:3ce8f47c371967107564fdb66a00c46303213f8f6e7c933e19ed856af79af758"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/re.pm",
"type": "reg",
"size": 8639,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20874317,
"NumLink": 0,
"digest": "sha256:7aec46a23600f1c35b9037a0ffefb061bf064e4954b4e35f4eeb39d22b46c8e3"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/strict.pm",
"type": "reg",
"size": 1606,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20877274,
"NumLink": 0,
"digest": "sha256:3f100533c2abc1cd0b20060bfe71372c28443b13356fd5604133a69e844fc88b"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/Heavy.pl",
"type": "reg",
"size": 129498,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20878088,
"NumLink": 0,
"digest": "sha256:cac1f06d7853a8dd1f6b8b59c3341758cbf59dd48c11c47ef1c1982a09d7fd4e"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Age.pl",
"type": "reg",
"size": 17925,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20905776,
"NumLink": 0,
"digest": "sha256:799fc2db4cef7d7dda3548341210a8682ee71a2f4added92776b9991c65620b3"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Bc.pl",
"type": "reg",
"size": 7925,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20913050,
"NumLink": 0,
"digest": "sha256:172a214478d317cb35788e97fa81a574ed0ffd9ac3abe5fb42119973fad512c3"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Bmg.pl",
"type": "reg",
"size": 5118,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20916542,
"NumLink": 0,
"digest": "sha256:6d45b0fb96dc56845f3c1444b2d92fa7029f6d0f06aa807725d94708e6de1030"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Bpb.pl",
"type": "reg",
"size": 2125,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20919036,
"NumLink": 0,
"digest": "sha256:495995c6eb2b997756c2b2f91071b2f9a2e3a0cafd63a69e9f6f347af868ce05"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Bpt.pl",
"type": "reg",
"size": 1692,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20920165,
"NumLink": 0,
"digest": "sha256:bdbc4054229d801ca6824d97da7cd8b618437a3587eee430578b870c1f55d4eb"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Cf.pl",
"type": "reg",
"size": 15381,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20921046,
"NumLink": 0,
"digest": "sha256:54a8ed05ddd618f1fa3bcbb38843656be0d9665870513e0f7a2565b26f589d9f"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Digit.pl",
"type": "reg",
"size": 5673,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20926472,
"NumLink": 0,
"digest": "sha256:cb144fe271026cc6d95fbc440569557c8ab031e5f20697e2024af502680b0c49"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Ea.pl",
"type": "reg",
"size": 2980,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20928828,
"NumLink": 0,
"digest": "sha256:d1c2abec486552afa3e054d3a04c051337f8a2baf2ad28af3ade507dec6f5bb2"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Fold.pl",
"type": "reg",
"size": 22741,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20930384,
"NumLink": 0,
"digest": "sha256:5ab3a81e3d6fb8c22832d335e3c621a35f084f21d045f2d9ab4ce3c13b85ca6e"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/GCB.pl",
"type": "reg",
"size": 17439,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20938364,
"NumLink": 0,
"digest": "sha256:0d7c93cd1e83dc9729c54cd49fdc39b3f7aeb10e0f010bab7957a4df4510b206"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Gc.pl",
"type": "reg",
"size": 31753,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20944222,
"NumLink": 0,
"digest": "sha256:c7ac42570c6443b79837db38e7b28400def99821d69533222e13456640c0d9dd"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Hst.pl",
"type": "reg",
"size": 9998,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20956677,
"NumLink": 0,
"digest": "sha256:023125f07cd4d45f40c3970bbaae4649dd7749f2ce60afb5065446426a606a83"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/InPC.pl",
"type": "reg",
"size": 7712,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20960205,
"NumLink": 0,
"digest": "sha256:4ed0cf7f0e9344d0359c962b30fee48084b5e1554a52c85fc7f5265e7ebf71fb"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/InSC.pl",
"type": "reg",
"size": 13326,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20963031,
"NumLink": 0,
"digest": "sha256:2442f91af082912354e8047ba1f2ce6c261de8bf48591749e9405f9d242fa8d4"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Isc.pl",
"type": "reg",
"size": 798,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20967132,
"NumLink": 0,
"digest": "sha256:6a774b0bbaa651dbdb6c76d01689c0c5b45258b7cdab8d6b33e8e8e2fa07f151"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Jg.pl",
"type": "reg",
"size": 3112,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20967713,
"NumLink": 0,
"digest": "sha256:a7b09acc2d7518a722dabe81efd8fe4a3f50f633da21709d89d38a8f3ce574a4"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Jt.pl",
"type": "reg",
"size": 4702,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20969363,
"NumLink": 0,
"digest": "sha256:db6d2fdf3147500d04a81c1e3912dfaa0b90d07047f6ca54b7e29b463368029c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Lb.pl",
"type": "reg",
"size": 29045,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20971709,
"NumLink": 0,
"digest": "sha256:d85bf398ecf6f2e50e8d3e7b8dca3f6d011133b50b25e0f20fe9eda8e3511939"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Lc.pl",
"type": "reg",
"size": 8187,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20983312,
"NumLink": 0,
"digest": "sha256:07164c5cdd6a9a387080628aa79b8bee7f330937391b6245b8504a3efd7a1d3f"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Lower.pl",
"type": "reg",
"size": 15524,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20987466,
"NumLink": 0,
"digest": "sha256:d0215cd96047e1d95cab34a25177d117b886b83ca778abefda7e246f84ea10df"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/NFCQC.pl",
"type": "reg",
"size": 1751,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20994145,
"NumLink": 0,
"digest": "sha256:8b3c568799d0d1b374373948f3084efd18881070e6deaac8ec3a09a2ddd10232"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/NFDQC.pl",
"type": "reg",
"size": 2920,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20995181,
"NumLink": 0,
"digest": "sha256:6763a26a0fbf79445cff82309d495738d81b6d6383617683a57f61b3812fb17b"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/NFKCCF.pl",
"type": "reg",
"size": 398291,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 20996710,
"NumLink": 0,
"digest": "sha256:bfecad5f99d94c2f4a2b4963ebe2750fa9908b15cb4efe7c438eb6c80a3d200c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/NFKCQC.pl",
"type": "reg",
"size": 3714,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21055779,
"NumLink": 0,
"digest": "sha256:9b80985800cfef4f1501f705642040f7190fe032b318320a280f4ff35562891a"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/NFKDQC.pl",
"type": "reg",
"size": 4719,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21057625,
"NumLink": 0,
"digest": "sha256:5f65d6e6d9e3fe8083d0ac3020a2b1a862247fa49e5ced277c7958d8255fd920"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Na1.pl",
"type": "reg",
"size": 63563,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21059896,
"NumLink": 0,
"digest": "sha256:63b774155c56a83a978a6694c58f9b57de8eb0f8fa8e06236126eef9aec97841"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/NameAlia.pl",
"type": "reg",
"size": 13366,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21073969,
"NumLink": 0,
"digest": "sha256:b03da714d8dfc017d7bd2f2677061d395d23e923e014c65385d4f19c16374b75"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Nt.pl",
"type": "reg",
"size": 4211,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21077590,
"NumLink": 0,
"digest": "sha256:fea2fae1f19a464744e165bba3bf0547d4551a54205d9e10dad4457ec19e67a8"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Nv.pl",
"type": "reg",
"size": 7517,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21079264,
"NumLink": 0,
"digest": "sha256:2a2360e18d5df4d2b10ad5217bbbb2a60e2a4bb3e7407bdc134786898c7e2c75"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/PerlDeci.pl",
"type": "reg",
"size": 1621,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21082547,
"NumLink": 0,
"digest": "sha256:97866947d1ede559eb516bf4d4460b401381fde6777562b49f1120f0d6013652"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/SB.pl",
"type": "reg",
"size": 31093,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21083542,
"NumLink": 0,
"digest": "sha256:28e91b6b63b070bc0cc7d50c32334165c63035d77c8e68077c5cbccc0c930371"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Sc.pl",
"type": "reg",
"size": 15111,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21092862,
"NumLink": 0,
"digest": "sha256:914389449d1c7e8c20e857efc1f7d130c18624671aff6f857cb0c8091a85e6e8"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Scx.pl",
"type": "reg",
"size": 17831,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21098657,
"NumLink": 0,
"digest": "sha256:fc69aa26dd8227544693047e64023804b49fbbdbb1afa52179196f086db42a5c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Tc.pl",
"type": "reg",
"size": 11534,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21105089,
"NumLink": 0,
"digest": "sha256:288caee21b8c0d7aa9fedaa79e5f09a0aaa3e507cc12aa260273b077e694c642"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Title.pl",
"type": "reg",
"size": 18899,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21110011,
"NumLink": 0,
"digest": "sha256:880c552b5da95595385f8888449619180af41fcf361d02b648f0c0acd5b59e71"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Uc.pl",
"type": "reg",
"size": 15206,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21117499,
"NumLink": 0,
"digest": "sha256:4b6fce17927c7bbfcd067efad997c748bd0419638854e7720d6646d9aa3624c2"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/Upper.pl",
"type": "reg",
"size": 22274,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21122931,
"NumLink": 0,
"digest": "sha256:60fa685cc003713e98ab17fe1ccfdf4b6ff8f4c15f530785a4bc343293a38fb9"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/WB.pl",
"type": "reg",
"size": 15165,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21130821,
"NumLink": 0,
"digest": "sha256:5535f07a25badcb4ad08550577676c465be9e0a4ebb52e1de914a8ce4429a647"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/_PerlLB.pl",
"type": "reg",
"size": 28370,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21135917,
"NumLink": 0,
"digest": "sha256:09d95c10092034783830776b1632d968fe5970a2c9cf0a8ab320d1e22dca0c05"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/To/_PerlWB.pl",
"type": "reg",
"size": 15388,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21144183,
"NumLink": 0,
"digest": "sha256:b1cd7ef951a18bff13baa2e3aef1ae8772e298362fccb86829e58461a6e995f5"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/",
"type": "dir",
"modtime": "2018-06-10T17:37:28Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Age/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Age/NA.pl",
"type": "reg",
"size": 7663,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21149388,
"NumLink": 0,
"digest": "sha256:895666b9e5c02a149077dd4632bad174ac718c2eb73c0ab0d4c0f0183b1a9bba"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Age/V11.pl",
"type": "reg",
"size": 3447,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21152814,
"NumLink": 0,
"digest": "sha256:631458895694ee09d4b5e7a1658c7e8f9dcd7e20773a561e7d8de1ec8e871e42"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Age/V20.pl",
"type": "reg",
"size": 841,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21154539,
"NumLink": 0,
"digest": "sha256:d9fb6634cb4582e18366ecacf6f6a82244f6fb3a092b1f7a9d08a21e05224f7f"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Age/V30.pl",
"type": "reg",
"size": 1709,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21155161,
"NumLink": 0,
"digest": "sha256:9fb591eb2efddaac5e714e4f932debd58f1a8abd0ed686cf37f493db1365984d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Age/V31.pl",
"type": "reg",
"size": 978,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21156189,
"NumLink": 0,
"digest": "sha256:85654494a8650e805227daaec6d25205b0d2d89d5dc722b5a567b7449e44ba7b"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Age/V32.pl",
"type": "reg",
"size": 1115,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21156832,
"NumLink": 0,
"digest": "sha256:39ec1432799ed310f2b97a0e697d4b2f06b1a463a5de819f887e0ffb5e235a47"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Age/V40.pl",
"type": "reg",
"size": 1325,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21157581,
"NumLink": 0,
"digest": "sha256:d3c689b3ba921e05f55cc2f701f7ba910c1d91875edf5ba61d21008477fb06f9"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Age/V41.pl",
"type": "reg",
"size": 1429,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21158431,
"NumLink": 0,
"digest": "sha256:fde400c6deaaa41ffb42a52cc4e4a99b290fd5f12392d707107b6f55f21994a9"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Age/V50.pl",
"type": "reg",
"size": 870,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21159315,
"NumLink": 0,
"digest": "sha256:e5590ee9f1837c84b6e47dd318d952ceb83d729f096df7919d6d1bd6e1030e71"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Age/V51.pl",
"type": "reg",
"size": 1459,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21159960,
"NumLink": 0,
"digest": "sha256:a03248090f3d9dbde0be80a89cb12131035acf9381a6e11a0ba02330d382b138"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Age/V52.pl",
"type": "reg",
"size": 1539,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21160860,
"NumLink": 0,
"digest": "sha256:46e9e09a71988fa10555ab1a452624424f0627c849c0341e1a47b8bb5452317b"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Age/V60.pl",
"type": "reg",
"size": 1815,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21161778,
"NumLink": 0,
"digest": "sha256:b4266095222ffa94a519336edb457a9c4d8a820dcbf4b3783a81d033aebdb1ff"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Age/V61.pl",
"type": "reg",
"size": 1625,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21162757,
"NumLink": 0,
"digest": "sha256:f77c3582d514dc07e2fd26130801864a5b034145ffd22d20df0e71d902fb736b"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Age/V70.pl",
"type": "reg",
"size": 2213,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21163654,
"NumLink": 0,
"digest": "sha256:696ac2fb6561e7598f89790b1dd2559ca33d380d01a1e72039a3afc12df3a9e5"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Age/V80.pl",
"type": "reg",
"size": 1213,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21164825,
"NumLink": 0,
"digest": "sha256:1d065d94198e8268c26a48c173636c6a22b97e2c615883f8f7dc48e0572bbaf0"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Alpha/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Alpha/Y.pl",
"type": "reg",
"size": 7378,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21165629,
"NumLink": 0,
"digest": "sha256:153ecfab205c8ebe3e525e9bde6913d1640c06dd7b2fa7432d8545e798ce6368"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Bc/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Bc/AL.pl",
"type": "reg",
"size": 708,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21169000,
"NumLink": 0,
"digest": "sha256:51b28b80d650595eed708e3f67154a1587d55aaa11143f6674d4678cfc5bdefc"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Bc/AN.pl",
"type": "reg",
"size": 542,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21169562,
"NumLink": 0,
"digest": "sha256:a45e2cf93b810b43af4f6d60e642e4e88c6894ebb05580ebb016194ef5dcb6fc"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Bc/B.pl",
"type": "reg",
"size": 526,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21170033,
"NumLink": 0,
"digest": "sha256:18b1b9e230759a28efbc814575412350931491980dc48c3c0c9e4d0bc29e67ce"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Bc/BN.pl",
"type": "reg",
"size": 882,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21170494,
"NumLink": 0,
"digest": "sha256:94cfe9fcb8120c1054db0a9151767423f6efe1f1b77c70129c21c69ade604667"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Bc/CS.pl",
"type": "reg",
"size": 618,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21171132,
"NumLink": 0,
"digest": "sha256:895fb05bab16084ded08df473a119fb89f207fbcaf90598d0c90ea4451880e25"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Bc/EN.pl",
"type": "reg",
"size": 614,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21171641,
"NumLink": 0,
"digest": "sha256:4278188bd2e78e736d4d53e61dfbdc8bf1f8bb342e2efb4b4909502c6c76d098"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Bc/ES.pl",
"type": "reg",
"size": 580,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21172159,
"NumLink": 0,
"digest": "sha256:930e8c3072267904ed75d0a2b2945018096b1d091bca696a32987812467cfccf"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Bc/ET.pl",
"type": "reg",
"size": 714,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21172649,
"NumLink": 0,
"digest": "sha256:2257544aa2fac4acbdf791327a2fe45990f0f4d33cdc6489b2212d4dee92a39d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Bc/L.pl",
"type": "reg",
"size": 4913,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21173209,
"NumLink": 0,
"digest": "sha256:52adbf5dc0da2d5d721eadf3282a290611eec86bda5d2eb1be7c1b11c9eed969"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Bc/NSM.pl",
"type": "reg",
"size": 3351,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21175512,
"NumLink": 0,
"digest": "sha256:40b12bf6ab2ca1c3c1610530a9b64a2d3c40ca7b204c1c15e8bdbdc9509acc7a"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Bc/ON.pl",
"type": "reg",
"size": 2436,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21177135,
"NumLink": 0,
"digest": "sha256:394079cd6f91f9b3dbb943b526b2cf57129ea244f048b33d5a9e834daf7c84fd"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Bc/R.pl",
"type": "reg",
"size": 838,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21178455,
"NumLink": 0,
"digest": "sha256:e4887c1576cac2abb7788747bca6ff8eba0287ba436699cc56a5808451095b89"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Bc/WS.pl",
"type": "reg",
"size": 554,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21179069,
"NumLink": 0,
"digest": "sha256:e9326135605b27c28f29a3ebc70b8a6e60ce49439b65d8f022eae0840c03ad7a"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/BidiC/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/BidiC/Y.pl",
"type": "reg",
"size": 529,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21179579,
"NumLink": 0,
"digest": "sha256:95b0b0cbd67c31a3c6576d5ae664956d685a9d74c3e05cee58a34ca33c673e06"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/BidiM/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/BidiM/Y.pl",
"type": "reg",
"size": 1717,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21180077,
"NumLink": 0,
"digest": "sha256:153d2c916858765de9b42d64392328010f666b7f06bf8666c1424b1e3be79769"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Blk/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Blk/NB.pl",
"type": "reg",
"size": 1025,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21181075,
"NumLink": 0,
"digest": "sha256:98710c03108d5b8ddd049c49a5c54ea46030d365b8919d6e5df441cd11683228"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Bpt/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Bpt/C.pl",
"type": "reg",
"size": 1177,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21181831,
"NumLink": 0,
"digest": "sha256:01271a108ff293e54e3144751dfc207edf4c57e9c42ff5f72267b468908f1591"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Bpt/N.pl",
"type": "reg",
"size": 800,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21182545,
"NumLink": 0,
"digest": "sha256:21dab8af5f812d52bdf968459e49c740fb4661400bb7ae449db24c7dc5d39a91"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Bpt/O.pl",
"type": "reg",
"size": 1177,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21183145,
"NumLink": 0,
"digest": "sha256:d2f892a7cd36f3f588205261a6f47471fb3f83477a90923014b2d17dd20a2f2d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/CE/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/CE/Y.pl",
"type": "reg",
"size": 846,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21183880,
"NumLink": 0,
"digest": "sha256:c36c6982a245f4ca4e162574511ee721fc264eb4a21742bce44a00b433ce13ac"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/CI/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/CI/Y.pl",
"type": "reg",
"size": 4241,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21184516,
"NumLink": 0,
"digest": "sha256:a265bd13d34abb29313b7987de58e14c44d35b35387f511593d010c3b1c5ec1d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/CWCF/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/CWCF/Y.pl",
"type": "reg",
"size": 6490,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21186542,
"NumLink": 0,
"digest": "sha256:9a28dd0229822f0e85a3c00462cb32e780534f2f16af181997fcfc6a3bebc1bc"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/CWCM/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/CWCM/Y.pl",
"type": "reg",
"size": 1593,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21189631,
"NumLink": 0,
"digest": "sha256:d50b98c979b8b22ad5bfc1feaabb4e1ec33bd8d0389ad7afe9b512d2ac32be0d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/CWKCF/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/CWKCF/Y.pl",
"type": "reg",
"size": 8994,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21190648,
"NumLink": 0,
"digest": "sha256:8bd002f770064e710d41cd11ea343b92d2d536e76aa9b341fe3bce7f7e58bb5d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/CWL/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/CWL/Y.pl",
"type": "reg",
"size": 6374,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21194854,
"NumLink": 0,
"digest": "sha256:88ad9c96eda907f2eb3a7b30373015a29be5950279d6c3927a5c571844159de7"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/CWT/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/CWT/Y.pl",
"type": "reg",
"size": 6532,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21197888,
"NumLink": 0,
"digest": "sha256:abe8a77955ed2930198261b9741bdb4729acd6d853b9f2d224a92c28e65def65"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/CWU/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/CWU/Y.pl",
"type": "reg",
"size": 6524,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21200982,
"NumLink": 0,
"digest": "sha256:13b16e9e72a8b6f7c8d102292042ba80ba3aaa13d96880f63f9b74655b5a8a05"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Cased/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Cased/Y.pl",
"type": "reg",
"size": 1937,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21204072,
"NumLink": 0,
"digest": "sha256:cd8039d00559b55e7d6984e64b62d7baf9f8cd0f756228eada0ced86b60bcc53"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Ccc/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Ccc/A.pl",
"type": "reg",
"size": 1551,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21205189,
"NumLink": 0,
"digest": "sha256:7767a020409dad0f6d805f1adf3c17c362da3eaea55d6961db978f446bfbca66"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Ccc/AR.pl",
"type": "reg",
"size": 525,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21206102,
"NumLink": 0,
"digest": "sha256:4af81b6bbfb9cf07817d5358f6af328c6b697f2cbe4376ec6781d70d231011d7"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Ccc/ATAR.pl",
"type": "reg",
"size": 535,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21206571,
"NumLink": 0,
"digest": "sha256:a68565ea04fe17f76e245a90d84d011bf45b9dd209e2234212693a6f1baf6642"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Ccc/B.pl",
"type": "reg",
"size": 1211,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21207036,
"NumLink": 0,
"digest": "sha256:847b4e8706ad7ebc9bb1c438fa3fe6f0926eac8c72fb281e87c21d08e61f4391"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Ccc/BR.pl",
"type": "reg",
"size": 531,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21207826,
"NumLink": 0,
"digest": "sha256:bfad8077a9404ab7c48861b119912ab5e47cb403bc67c2f358f9dd34718f7ed6"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Ccc/DB.pl",
"type": "reg",
"size": 523,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21208289,
"NumLink": 0,
"digest": "sha256:9e90c185c7c234e6fee15d4967661500f12b628114818720726ca90c22b9c1ec"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Ccc/NK.pl",
"type": "reg",
"size": 710,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21208751,
"NumLink": 0,
"digest": "sha256:4b48f6419b3f652d85c3b11f9ddc4936271f4db389d11b92557af6a96f6f119f"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Ccc/NR.pl",
"type": "reg",
"size": 2149,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21209304,
"NumLink": 0,
"digest": "sha256:f4a6ad8520f85deea7a999166e055d235f35aeb0081ff42e5376255444cf345f"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Ccc/OV.pl",
"type": "reg",
"size": 610,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21210468,
"NumLink": 0,
"digest": "sha256:1ebb46d59960672a29f9045e3a2046cb93e827f58fcf8abff252a1d7c563d364"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Ccc/VR.pl",
"type": "reg",
"size": 942,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21210977,
"NumLink": 0,
"digest": "sha256:fa750cfd0df25a10b0967b28690de1ba5993cb2a448f2d147ffc56080c16859a"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/CompEx/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/CompEx/Y.pl",
"type": "reg",
"size": 1259,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21211670,
"NumLink": 0,
"digest": "sha256:362b71e73a3b072425f5889dd64e4fdc08c93b0c002d91089ab31a36754bf68a"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/DI/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/DI/Y.pl",
"type": "reg",
"size": 678,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21212474,
"NumLink": 0,
"digest": "sha256:582eb22ce663ac2cb933b20f6a1f07628d86118008d611ddf44194b2d5674755"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Dash/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Dash/Y.pl",
"type": "reg",
"size": 718,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21213052,
"NumLink": 0,
"digest": "sha256:3a6aada6ff63c45d487a58fa5b16e5ff15feb253b5db7ae93178eaecbf101fbf"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Dep/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Dep/Y.pl",
"type": "reg",
"size": 586,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21213637,
"NumLink": 0,
"digest": "sha256:7ff21ef6a2e5398dcaf1f20ffef0310f0ab60cb3c28018cefdf7a1b2b83b073a"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Dia/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Dia/Y.pl",
"type": "reg",
"size": 2061,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21214164,
"NumLink": 0,
"digest": "sha256:f0696b99052d6493f9760f1755ef8f8e137480f4fb90de1ee0b60192e6de0bc7"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Dt/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Dt/Com.pl",
"type": "reg",
"size": 1249,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21215342,
"NumLink": 0,
"digest": "sha256:6517d7369068ddf4621da7a984430011d831c9cb2f4c8a0e859bb3f938060199"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Dt/Enc.pl",
"type": "reg",
"size": 586,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21216156,
"NumLink": 0,
"digest": "sha256:2d57420e36706e61b846baf1f831f0a8c265dadf3e928595641f59af61488681"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Dt/Fin.pl",
"type": "reg",
"size": 1847,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21216651,
"NumLink": 0,
"digest": "sha256:c76ba4201f2c128223c5d10779cf0b6b643fc3429d759847f12c49cfd67a745a"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Dt/Font.pl",
"type": "reg",
"size": 1379,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21217580,
"NumLink": 0,
"digest": "sha256:634e71167318263a480c83e8288105d0a588b6c33d680cf82bb791f4a09eaf9c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Dt/Init.pl",
"type": "reg",
"size": 1391,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21218333,
"NumLink": 0,
"digest": "sha256:a4e9f05f11e1f2f5c8c72010d59e5eab18051769ec32d7c307e030271719671e"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Dt/Iso.pl",
"type": "reg",
"size": 1667,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21219121,
"NumLink": 0,
"digest": "sha256:cd0458d5589485eca6c394f28c91636a50e587fbfc652a1348ead4dd459521e9"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Dt/Med.pl",
"type": "reg",
"size": 1139,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21219995,
"NumLink": 0,
"digest": "sha256:fce9c154d14ac6bd407150ed9e99b5c5640c60026a61d8d656baa569b69da0e2"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Dt/Nar.pl",
"type": "reg",
"size": 562,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21220678,
"NumLink": 0,
"digest": "sha256:531d4f55728f8dd5b78b1738c2e7cd45bfbcefde93d1f2f22730a59c24cde560"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Dt/Nb.pl",
"type": "reg",
"size": 538,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21221151,
"NumLink": 0,
"digest": "sha256:0e429db6cf47bfa4a78882fb401e05b37edc9ac8c2fc39c533e8d5a68b7949a6"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Dt/NonCanon.pl",
"type": "reg",
"size": 2657,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21221622,
"NumLink": 0,
"digest": "sha256:1945b1fc2a60d15018317eacde9cd20e0e1499fbc6c906a85ec682bee21dfa84"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Dt/Sqr.pl",
"type": "reg",
"size": 606,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21222963,
"NumLink": 0,
"digest": "sha256:b45c60ee1e042ed6eb2e9dd216a07e2cd361bace92b05bee522b90fecf76bdce"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Dt/Sub.pl",
"type": "reg",
"size": 531,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21223466,
"NumLink": 0,
"digest": "sha256:6ed0dd870aed3f0db2749513931412e06b161fcd6e1c75f987c557e17da9ba2e"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Dt/Sup.pl",
"type": "reg",
"size": 738,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21223935,
"NumLink": 0,
"digest": "sha256:8f90ebc3abf2cf6935c377d8ad362a1b454807fb9f86eb6c3c63ebfb44a0e43b"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Dt/Vert.pl",
"type": "reg",
"size": 550,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21224515,
"NumLink": 0,
"digest": "sha256:eacc5f27dc0f5299b611fa9add6d92f47ddb80dc5f8d8faa6a09c4357ad33da1"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Ea/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Ea/A.pl",
"type": "reg",
"size": 2152,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21225014,
"NumLink": 0,
"digest": "sha256:7dd3d1b8becf26d4673b9de63f045a21b26a30c1cff0c1e029fe41c9f949d44f"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Ea/H.pl",
"type": "reg",
"size": 572,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21226253,
"NumLink": 0,
"digest": "sha256:e29e29e6c6e70eed775d55bb5b62b1cbb442cf9e2b145bac5323799028764bf8"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Ea/N.pl",
"type": "reg",
"size": 2613,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21226733,
"NumLink": 0,
"digest": "sha256:00c916dfbc363c8b6d78559da963bf63d891b9df6634449309b5f905f30d6825"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Ea/Na.pl",
"type": "reg",
"size": 553,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21228158,
"NumLink": 0,
"digest": "sha256:9068ab08b300c75c8ceca277b1ef6e6268b2cdfa7de15176f6f8989463cf8a47"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Ea/W.pl",
"type": "reg",
"size": 896,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21228640,
"NumLink": 0,
"digest": "sha256:eddb361861195dce0801db83acfce1fb7ea7c286a9d73765799e35df4a2ec3de"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Ext/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Ext/Y.pl",
"type": "reg",
"size": 776,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21229309,
"NumLink": 0,
"digest": "sha256:0b98dd664962ed3dba4143d1a23d85265709c2b79c50b547a28ac2afc43c153c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/GCB/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/GCB/CN.pl",
"type": "reg",
"size": 717,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21229924,
"NumLink": 0,
"digest": "sha256:46a2b8690ebfbf7ecb3b7819097259cf2d6c03953495a9e7c3d28b59192d911d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/GCB/EX.pl",
"type": "reg",
"size": 3581,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21230501,
"NumLink": 0,
"digest": "sha256:c4152337f9173e8719f9b4b257f0ba7998ddb54900e52966edbbc3632fd07c13"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/GCB/LV.pl",
"type": "reg",
"size": 5279,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21232206,
"NumLink": 0,
"digest": "sha256:c4568a86bc16057955da044c097ff34a5e8a17f7d0005324ad15284b78468ef3"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/GCB/LVT.pl",
"type": "reg",
"size": 5279,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21234411,
"NumLink": 0,
"digest": "sha256:90aea35d7a172c8b53a3fe1b10c919a9b02c7e489fe46b84ee4ffc4e3a79fc72"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/GCB/SM.pl",
"type": "reg",
"size": 1929,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21236616,
"NumLink": 0,
"digest": "sha256:cb496af4f93e7144e07e9b15755de6497d8fc0e892de6bde46ffcf4797f3a248"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/GCB/XX.pl",
"type": "reg",
"size": 3296,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21237652,
"NumLink": 0,
"digest": "sha256:9a4a41055bf7da9f924c4fbdbb58b075fd4af6afe1e96889a9971b3f1f7de010"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/C.pl",
"type": "reg",
"size": 7529,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21239319,
"NumLink": 0,
"digest": "sha256:1b684544c72a4779455627f4d813423a0ca5dc6312c509b366f9dc6a03758dfd"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/Cf.pl",
"type": "reg",
"size": 680,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21242693,
"NumLink": 0,
"digest": "sha256:e789aae540f2927b3db679c559591cf4e5ff8401b60df1937315d292ade44405"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/Cn.pl",
"type": "reg",
"size": 7523,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21243241,
"NumLink": 0,
"digest": "sha256:38cb532eb185268bf9ce967c557a1af01b6db11398a755ca514d7aca4d4ada05"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/L.pl",
"type": "reg",
"size": 6716,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21246603,
"NumLink": 0,
"digest": "sha256:7b658b5727e3819db80c229aa3b3dcfc392751045c3b4bfb9a1b2125b06df149"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/LC.pl",
"type": "reg",
"size": 1849,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21249648,
"NumLink": 0,
"digest": "sha256:1cc2bbb0394ae80d1d686401c9ae623e34b4f8f537594e5501afa169e6828f72"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/Ll.pl",
"type": "reg",
"size": 6928,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21250691,
"NumLink": 0,
"digest": "sha256:c464c9d51cf75d1a7d3123e0c81dde4f3446175a814053b1ec196a8927d6f960"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/Lm.pl",
"type": "reg",
"size": 1091,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21253975,
"NumLink": 0,
"digest": "sha256:c5becb57eedfbf023e5a36a95a5a67aeac1dec28fce67729a0cd8a4b71d6fa86"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/Lo.pl",
"type": "reg",
"size": 5375,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21254712,
"NumLink": 0,
"digest": "sha256:9c49eea5bc50f051fb58329f23e9675cc01887328f93d29fa0540f727b95d939"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/Lu.pl",
"type": "reg",
"size": 6866,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21257215,
"NumLink": 0,
"digest": "sha256:fa0731e935c0c4ae87db490249f22768933051e24ff8fb3adfd8dc692576f949"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/M.pl",
"type": "reg",
"size": 3065,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21260451,
"NumLink": 0,
"digest": "sha256:b69ebc1803fdef3b03df749ee254f466f80d26bc3010734759312043bcc46580"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/Mc.pl",
"type": "reg",
"size": 2085,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21261986,
"NumLink": 0,
"digest": "sha256:a9a2e66cc09f7e006b0294a27d9fe0b1022c6a3232cd53789f22dda89ce751d6"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/Me.pl",
"type": "reg",
"size": 542,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21263093,
"NumLink": 0,
"digest": "sha256:abbdd8849b43e36186134ba648f58e9f7ccd789683837f543b7004b9c5f678b8"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/Mn.pl",
"type": "reg",
"size": 3391,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21263563,
"NumLink": 0,
"digest": "sha256:d10d8ee92ceb362c2ba777eab53b6857bfbf1c02b8cd95b558d3ecfbee5e3c26"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/N.pl",
"type": "reg",
"size": 1731,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21265199,
"NumLink": 0,
"digest": "sha256:1dc44fa3a7704c2d629b43720749f90790e49127b5a5cc94b848fe76a767399b"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/Nd.pl",
"type": "reg",
"size": 1043,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21266224,
"NumLink": 0,
"digest": "sha256:63dac7016953581540a2538710b0c4318ec2b9fc3acfca829e69ca737f8e49af"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/Nl.pl",
"type": "reg",
"size": 628,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21266959,
"NumLink": 0,
"digest": "sha256:016a472ab6cc5b30ae7cc6551f94b63b473ea4c6b73ea282db39c5d649552868"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/No.pl",
"type": "reg",
"size": 1149,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21267476,
"NumLink": 0,
"digest": "sha256:822dd7912d7229f9f14013f318e2a1ea135fd57e6be75f108ba665d313f6a8f8"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/P.pl",
"type": "reg",
"size": 2229,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21268244,
"NumLink": 0,
"digest": "sha256:371b7f97bad43659b45c06c5935c55d427df4bb8c488672fc78f1982b3391c36"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/Pd.pl",
"type": "reg",
"size": 678,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21269455,
"NumLink": 0,
"digest": "sha256:ad65ae09d8cb997364c099b590b3c28ef4a8db23ec0526278051b6a3208d432b"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/Pe.pl",
"type": "reg",
"size": 1321,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21269993,
"NumLink": 0,
"digest": "sha256:7615e72e7e374f966d60f68243e039dea6c567b3a9be817b40c5b3d8838938aa"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/Pf.pl",
"type": "reg",
"size": 600,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21270747,
"NumLink": 0,
"digest": "sha256:753f0b70a76c59d123209a9ef95ca3793b8d99a1a7b6bf59bff11fedc5bf3b14"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/Pi.pl",
"type": "reg",
"size": 610,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21271241,
"NumLink": 0,
"digest": "sha256:8c45405effca58ee2d3a4fbf1a38a8236a8076a2de9aba13bb48393c8a667468"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/Po.pl",
"type": "reg",
"size": 2197,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21271741,
"NumLink": 0,
"digest": "sha256:58800da8ee0bb4a638eca93796b087a9dfe35012888a45571f2cecd211356b19"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/Ps.pl",
"type": "reg",
"size": 1353,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21272922,
"NumLink": 0,
"digest": "sha256:b34ec254d4e43c5752e2226811eed8c99a41073aa72e6fd8e0b7a1563da414f5"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/S.pl",
"type": "reg",
"size": 2934,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21273684,
"NumLink": 0,
"digest": "sha256:d5ce890e481dff7a947a98d7d7c76f2e7907e472f81cb7c9d5f84dd71782faae"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/Sc.pl",
"type": "reg",
"size": 666,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21275182,
"NumLink": 0,
"digest": "sha256:08d139cf2fd09d29efc97db42f745d90d2bc0a7601f2fb92a43f42eb356f70cd"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/Sk.pl",
"type": "reg",
"size": 772,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21275719,
"NumLink": 0,
"digest": "sha256:aa1fded45460df446896a9545016c9159a610265a1e32fa9df2d8b40c1381b8f"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/Sm.pl",
"type": "reg",
"size": 1191,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21276321,
"NumLink": 0,
"digest": "sha256:164ceffc29dd40a6cfd9ba9332d3a4831a99987e8d2a6c2cdd441b338656d926"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/So.pl",
"type": "reg",
"size": 2468,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21277089,
"NumLink": 0,
"digest": "sha256:6d251b99654781e7e7ec057f316a0a1eb51497f3fbc3be442a1d61bc125ef346"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/Z.pl",
"type": "reg",
"size": 566,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21278364,
"NumLink": 0,
"digest": "sha256:2eab52cc23eb22532a256bf16b3ce41de6ee59dc35289113be6a8d2a804d9f84"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Gc/Zs.pl",
"type": "reg",
"size": 556,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21278852,
"NumLink": 0,
"digest": "sha256:58055a6143a100243cc3e9ed7cbdf794b4a2fff5a5fea630238823f492c6eb0c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/GrBase/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/GrBase/Y.pl",
"type": "reg",
"size": 8947,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21279366,
"NumLink": 0,
"digest": "sha256:790ad94d6a1e74542fa78f2a31e760c96ab9d767018c0a1c6f64f33200f0ffb4"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Hex/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Hex/Y.pl",
"type": "reg",
"size": 545,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21283331,
"NumLink": 0,
"digest": "sha256:21cc921c64c5201357a67823af77d2b6a19acabe9cefc238ad8938adf058e05b"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Hst/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Hst/NA.pl",
"type": "reg",
"size": 550,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21283837,
"NumLink": 0,
"digest": "sha256:5fc815fa3b720f3de4e4d61db293e6abb336d413aebdf60b9b4c507566c2d3ef"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Hyphen/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Hyphen/T.pl",
"type": "reg",
"size": 594,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21284347,
"NumLink": 0,
"digest": "sha256:db11dd24449811de8c6717ebf0ce58d3e68f05d280f18b8d5816ae313b072d5f"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/IDC/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/IDC/Y.pl",
"type": "reg",
"size": 7786,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21284880,
"NumLink": 0,
"digest": "sha256:988886312785a308c02e30225a6f72e3109dc0acc30b89ac0169bbebe8ed7b7c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/IDS/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/IDS/Y.pl",
"type": "reg",
"size": 6730,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21288422,
"NumLink": 0,
"digest": "sha256:2b0e8a0ac8dfa48e844a0a40a043aa11198a1564895ef9df692ebc50c226ed5e"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Ideo/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Ideo/Y.pl",
"type": "reg",
"size": 644,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21291508,
"NumLink": 0,
"digest": "sha256:b9a3f4b14ad1df197900ccd1f6e40fdb030486c1cf11e1cfb4ae4d2c632f3bac"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/In/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/In/2_0.pl",
"type": "reg",
"size": 3746,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21292070,
"NumLink": 0,
"digest": "sha256:13f6d56ca5be2fcc4826008e757ea3250f99bd6135926db97001bd25d9608109"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/In/2_1.pl",
"type": "reg",
"size": 3746,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21293931,
"NumLink": 0,
"digest": "sha256:3e9efcf7212fe4e7a64db95b2a03d7b420f1cd07013bd3ccca2ccdc9a72f4e7d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/In/3_0.pl",
"type": "reg",
"size": 4350,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21295794,
"NumLink": 0,
"digest": "sha256:d0c46b7bde68599a3b1954a0eb362cf190294381a30127a7968a92c92e42be8a"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/In/3_1.pl",
"type": "reg",
"size": 4802,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21297911,
"NumLink": 0,
"digest": "sha256:a82015a87e5674584856f531e4143deeb2ede5c72b9b106f08fd72ce6159a39b"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/In/3_2.pl",
"type": "reg",
"size": 4740,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21300214,
"NumLink": 0,
"digest": "sha256:01217f692d41f64cf3e72eaa4efafaef4a8ffc8f3289e8d944d8dbd20dec2ce0"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/In/4_0.pl",
"type": "reg",
"size": 4924,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21302486,
"NumLink": 0,
"digest": "sha256:b0d564073aae2946b46181dc7f7352e2f2a18b96c7570f0e5c8d980813a7912e"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/In/4_1.pl",
"type": "reg",
"size": 5170,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21304829,
"NumLink": 0,
"digest": "sha256:e69aa31c985304dbd9691fecc2e8eae34c2424c867e65a3d60f023fcdb5b10b9"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/In/5_0.pl",
"type": "reg",
"size": 5298,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21307271,
"NumLink": 0,
"digest": "sha256:fd2ee1e5d66198cf658a0bbb582b37f29f13f247b299c5380f168c98a776afaf"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/In/5_1.pl",
"type": "reg",
"size": 5490,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21309760,
"NumLink": 0,
"digest": "sha256:f0166a4d8804a9e22594eec9c4ef7b17e4c1363f57e30dfa8cac6cd110a77ba6"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/In/5_2.pl",
"type": "reg",
"size": 6002,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21312330,
"NumLink": 0,
"digest": "sha256:02fe6fae9f32062930bf45f7dbd669f9f321fa6543f6c8ee93483fefacd62904"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/In/6_0.pl",
"type": "reg",
"size": 6261,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21315100,
"NumLink": 0,
"digest": "sha256:9dd477413e652a76542f7d038d7fdd4f7236179b61c33040b1ff78850c4361e3"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/In/6_1.pl",
"type": "reg",
"size": 6759,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21317977,
"NumLink": 0,
"digest": "sha256:04c5ae4eb50ebf723ac3db094a8d42b56eeba5ed8adc5d5a0833509afb9ffe2c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/In/6_2.pl",
"type": "reg",
"size": 6759,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21321037,
"NumLink": 0,
"digest": "sha256:ab1d7d9b60dd8903cae87e7918b121b8c9fa4ce2a60324cf3ccd174ac8ce9789"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/In/6_3.pl",
"type": "reg",
"size": 6759,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21324098,
"NumLink": 0,
"digest": "sha256:d6d7ed8d5392f036d48104c22ccf3fb71c9a0a5a99b49599554818ff0125e8b2"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/In/7_0.pl",
"type": "reg",
"size": 7515,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21327162,
"NumLink": 0,
"digest": "sha256:923bb6a9d109b0da8a3cac2d6a1b442f9fd932ef0804a3867f9e7646365e1d0a"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/In/8_0.pl",
"type": "reg",
"size": 7665,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21330521,
"NumLink": 0,
"digest": "sha256:6e3ad837e82daa5f3ec302a18814ce9f6662b5de63d557e75ff8153fda240395"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InPC/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InPC/Bottom.pl",
"type": "reg",
"size": 1699,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21333992,
"NumLink": 0,
"digest": "sha256:468da8bf6dfba6e473f2972b251691aff4c6f0e3e7f2d2c08441c6e76c70566c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InPC/Left.pl",
"type": "reg",
"size": 902,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21334962,
"NumLink": 0,
"digest": "sha256:c6dc18df59e6636c97eedeec3fd68ac87b9fe3c9949c6be0d848a69c2482fa12"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InPC/LeftAndR.pl",
"type": "reg",
"size": 628,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21335602,
"NumLink": 0,
"digest": "sha256:4550e724c609641a8ac46c39d0ed5bd150aff8baf8300693321f92123c02b842"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InPC/NA.pl",
"type": "reg",
"size": 2311,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21336118,
"NumLink": 0,
"digest": "sha256:b6feb9c1b0413d1da65ee9a47ace8183300edf33ecb6bb51022dbd0a001a0f73"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InPC/Overstru.pl",
"type": "reg",
"size": 533,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21337345,
"NumLink": 0,
"digest": "sha256:8a78146108cfd6325943bfa19174f77476accd0f7a2a662f5acd052e371d92b4"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InPC/Right.pl",
"type": "reg",
"size": 1981,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21337816,
"NumLink": 0,
"digest": "sha256:4e3b2907207137d39f00dcdfb4e9de5d6990068a0f8a2ebe32a1150e9300ae4e"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InPC/Top.pl",
"type": "reg",
"size": 2125,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21338873,
"NumLink": 0,
"digest": "sha256:630fc5fc51a6d904805f10a558983948b9a0bb3f5f785b0843897e58d278ce3d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InPC/TopAndBo.pl",
"type": "reg",
"size": 552,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21340018,
"NumLink": 0,
"digest": "sha256:f1afb1d74d21ae82b54c28ac2c4266f33cfeab3de1e10e8b9db7e6b9716eda7c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InPC/TopAndL2.pl",
"type": "reg",
"size": 531,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21340500,
"NumLink": 0,
"digest": "sha256:9a0f38b4d2a324af133bfdc80cba6e71a4b12504d516d698a0afefd27aa028dc"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InPC/TopAndLe.pl",
"type": "reg",
"size": 554,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21340977,
"NumLink": 0,
"digest": "sha256:3d33132e3c015dab8fca9eeaec0779e29cab54fd50443ac1d4b613a302482b85"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InPC/TopAndRi.pl",
"type": "reg",
"size": 584,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21341459,
"NumLink": 0,
"digest": "sha256:75f7ba0cd02d1d35e55cc1a752bda119c86525e228849f6b07d895486c2fb3a3"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InPC/VisualOr.pl",
"type": "reg",
"size": 566,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21341955,
"NumLink": 0,
"digest": "sha256:3ed0c5935a1cda0e566a66e4568d2caba1f227952c22b93e37a4cdf34c276707"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InSC/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InSC/Avagraha.pl",
"type": "reg",
"size": 626,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21342472,
"NumLink": 0,
"digest": "sha256:f72d20989594d3e67120d0be23e41d7aa7c035ad751ba8c72634368e7360cac0"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InSC/Bindu.pl",
"type": "reg",
"size": 892,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21342993,
"NumLink": 0,
"digest": "sha256:d2f8b73a98b0cdee98c1bf27fc6466d7c4befa157ed16e469b2fd72030937451"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InSC/Cantilla.pl",
"type": "reg",
"size": 576,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21343640,
"NumLink": 0,
"digest": "sha256:1dd353f8f78fff54c57d1dd7e972f709ba595ae55d97f374e06f41165591fdf3"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InSC/Consona2.pl",
"type": "reg",
"size": 608,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21344135,
"NumLink": 0,
"digest": "sha256:f5384d0716886b40810a8342cb7c8437229b7b5d98ee83ea953fe7951fc5f29c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InSC/Consona3.pl",
"type": "reg",
"size": 586,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21344656,
"NumLink": 0,
"digest": "sha256:4c807a57b2b975117eb16ac10789e0de5af30f597c79957062279adab01afd2a"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InSC/Consona4.pl",
"type": "reg",
"size": 586,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21345157,
"NumLink": 0,
"digest": "sha256:4c5f334957a5abf4cad68467b86087af42a926e518c4480080822a6b96fd6b1b"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InSC/Consona5.pl",
"type": "reg",
"size": 562,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21345658,
"NumLink": 0,
"digest": "sha256:2efd67da131f00815609e67de866cc43760fccde62d46414855bf5399b31ac53"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InSC/Consona6.pl",
"type": "reg",
"size": 531,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21346150,
"NumLink": 0,
"digest": "sha256:7050f2f53849a374b0f2af1ec7de9ecd1d48f1d537b8c52a899dd4ca9ecf9552"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InSC/Consonan.pl",
"type": "reg",
"size": 2019,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21346620,
"NumLink": 0,
"digest": "sha256:5339e1b16056a13163fc91a699698cc9708952648ac828f1f7e5a7652a2da8b1"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InSC/Invisibl.pl",
"type": "reg",
"size": 566,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21347738,
"NumLink": 0,
"digest": "sha256:9e2be1b50e53e515a745b2c595ab7186e590f9e74917e602bd9c14b8408250da"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InSC/Nukta.pl",
"type": "reg",
"size": 722,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21348226,
"NumLink": 0,
"digest": "sha256:ad89d93922400e9011557a23689ed306877926358f8fa2e9eb79b6092f9c725b"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InSC/Number.pl",
"type": "reg",
"size": 918,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21348791,
"NumLink": 0,
"digest": "sha256:f037c0204cb21032219cb0a78893a2aa2857a01e2d09d15e430564f99ffd834b"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InSC/Other.pl",
"type": "reg",
"size": 3459,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21349466,
"NumLink": 0,
"digest": "sha256:5b0c6ebd2b4fa552ea5799e784af2b49d9116eb2196eaa28f41b075ebe96fee5"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InSC/PureKill.pl",
"type": "reg",
"size": 652,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21351189,
"NumLink": 0,
"digest": "sha256:75421328514596cfafcc7de3ff352d428137c2d6d8e38250625fb0f49f4696f0"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InSC/Syllable.pl",
"type": "reg",
"size": 618,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21351727,
"NumLink": 0,
"digest": "sha256:cead1ea4a4fe43635e01bce4b9fc348abef77d318e3713498e125f7548516714"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InSC/ToneMark.pl",
"type": "reg",
"size": 650,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21352246,
"NumLink": 0,
"digest": "sha256:b75f698a239534cbab48e902e8bb88c9f1ca298de111e7c5ed2346757155e5c5"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InSC/Virama.pl",
"type": "reg",
"size": 732,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21352779,
"NumLink": 0,
"digest": "sha256:0da894d458c455ba17371f79be3244c4c97b823e2943b699393857300affeaca"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InSC/Visarga.pl",
"type": "reg",
"size": 796,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21353352,
"NumLink": 0,
"digest": "sha256:8a87513f8a1d92e4f8a8ac566b700322d47f36b9ac10f70f0468f01fe23cc6a4"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InSC/Vowel.pl",
"type": "reg",
"size": 548,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21353956,
"NumLink": 0,
"digest": "sha256:d468fa92702a4e0f60574c696863cca6561ab504fa31062ab2cf19e096b76e65"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InSC/VowelDep.pl",
"type": "reg",
"size": 1625,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21354436,
"NumLink": 0,
"digest": "sha256:d33a82fec0243216a80c828c8032de7d45df1bd8156e936406911527c59e8f5e"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/InSC/VowelInd.pl",
"type": "reg",
"size": 1241,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21355390,
"NumLink": 0,
"digest": "sha256:84a3f31938cd83f47d9383d92cae715d5d608e97e0c31f6a3477ee2bd1f9ec10"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Jg/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Jg/Ain.pl",
"type": "reg",
"size": 540,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21356218,
"NumLink": 0,
"digest": "sha256:f24016b9d14d5ed0291379def4ea1de1dab8aa0daeeca3785e790377048bfb21"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Jg/Alef.pl",
"type": "reg",
"size": 550,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21356690,
"NumLink": 0,
"digest": "sha256:f4b11ee075b7873457c9899e1f7be563c4787cd0128277cd1fe80192086fcb6f"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Jg/Beh.pl",
"type": "reg",
"size": 550,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21357162,
"NumLink": 0,
"digest": "sha256:3a251179bbb302b9ff9b437f1bad999c7287026cee7403079202dcf38250f7e1"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Jg/Dal.pl",
"type": "reg",
"size": 540,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21357638,
"NumLink": 0,
"digest": "sha256:56947fd737c7f13b8376e6702eb5f26e6296a5b99a04565a6880ec2025d74ea7"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Jg/FarsiYeh.pl",
"type": "reg",
"size": 529,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21358114,
"NumLink": 0,
"digest": "sha256:5dc68c9a2b5f3544d5df69f5571d284ae453244ff58d1a64de0efbb1b8a22323"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Jg/Feh.pl",
"type": "reg",
"size": 529,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21358580,
"NumLink": 0,
"digest": "sha256:ae53d8e50eaa7b8787692e3589b38ca28db44bc5542c0cd499dd8581642f5bf5"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Jg/Gaf.pl",
"type": "reg",
"size": 550,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21359048,
"NumLink": 0,
"digest": "sha256:e4e395972574b5ab0668dc6a4f210a7dfbb03f06b04a08ade566d5474a6216e5"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Jg/Hah.pl",
"type": "reg",
"size": 570,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21359522,
"NumLink": 0,
"digest": "sha256:d14cced32f4055d4ac13cf8d92b6be4fdcdc1c9b1674ec2ec9f46384239dc4ac"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Jg/Kaf.pl",
"type": "reg",
"size": 529,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21360010,
"NumLink": 0,
"digest": "sha256:87bfeee9af881a4d1357a6a08cc239b7e22d0b7df7e6f543fe83bf744c3cffe9"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Jg/Lam.pl",
"type": "reg",
"size": 529,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21360478,
"NumLink": 0,
"digest": "sha256:e43f4f9917b7243e1673aeb509e0c063a56718d1e1d998fbdc05da0aa33ea7e6"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Jg/NoJoinin.pl",
"type": "reg",
"size": 714,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21360948,
"NumLink": 0,
"digest": "sha256:a12c205a59adfa5912d715e3313fbb00a35952d9a9f4c79b1463267c79d0ca69"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Jg/Qaf.pl",
"type": "reg",
"size": 529,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21361502,
"NumLink": 0,
"digest": "sha256:d5f83fc3feeb39fad902ccae85624b39d6afb82504930dd3239ef71e0095822e"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Jg/Reh.pl",
"type": "reg",
"size": 570,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21361964,
"NumLink": 0,
"digest": "sha256:264d7bee09393a22317c8583efda0c39c886997c8839ed5c2beed124d1369fc8"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Jg/Sad.pl",
"type": "reg",
"size": 529,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21362454,
"NumLink": 0,
"digest": "sha256:e51b935f2bcb554b10978d7236af8a127b2760c0ce33376de6af82c9314df086"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Jg/Seen.pl",
"type": "reg",
"size": 560,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21362923,
"NumLink": 0,
"digest": "sha256:b3127bc9cd8f5b8c005e9b3235ecda61da74279beae78d560cbd7de1b4b7627d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Jg/Waw.pl",
"type": "reg",
"size": 560,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21363403,
"NumLink": 0,
"digest": "sha256:920dcab0a75a0cbb76488df491ae59bdfc7bde9641cc858d7662aec79f2a94dd"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Jg/Yeh.pl",
"type": "reg",
"size": 560,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21363885,
"NumLink": 0,
"digest": "sha256:76e8ea7d2005c5a1eefac0d1b20e58f09df82db8d61c7891c780eac65446f83b"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Jt/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Jt/C.pl",
"type": "reg",
"size": 529,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21364397,
"NumLink": 0,
"digest": "sha256:65fa3c06bb480760cf92b70a8e4472aa9768f6b25785f3fc766f3900015327f7"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Jt/D.pl",
"type": "reg",
"size": 1047,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21364857,
"NumLink": 0,
"digest": "sha256:6b7ac3e4cbfdd06d82a7486db53310fc013bec3a8e075126f4f3e309985c1abe"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Jt/R.pl",
"type": "reg",
"size": 1021,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21365548,
"NumLink": 0,
"digest": "sha256:a2cb7c3e73e46c497012713f475252c94893a6a1060f6349eb136a356bc2db56"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Jt/T.pl",
"type": "reg",
"size": 3527,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21366210,
"NumLink": 0,
"digest": "sha256:d332613820c49cbf11b5704ed9c4540b7ec73f4b822f94f9da42aed0010c95b5"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Jt/U.pl",
"type": "reg",
"size": 3715,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21367906,
"NumLink": 0,
"digest": "sha256:e4d9ca5f6e99a834259c84c9fae537399938892687b735fd24965f360280a799"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Lb/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Lb/AI.pl",
"type": "reg",
"size": 1581,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21369711,
"NumLink": 0,
"digest": "sha256:b11022673f7f67894f2decc01c1c48b7979ed84edc80ee1e260312d0c91f6367"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Lb/AL.pl",
"type": "reg",
"size": 8299,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21370654,
"NumLink": 0,
"digest": "sha256:9844fcc9be847140cd01ffb3ea9af682a28215faa9078140cdf143f69d4bee9f"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Lb/BA.pl",
"type": "reg",
"size": 1436,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21374369,
"NumLink": 0,
"digest": "sha256:201498aad9193c088657a784984d18f4fb4375457addd1206b0871e1737a9694"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Lb/BB.pl",
"type": "reg",
"size": 652,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21375228,
"NumLink": 0,
"digest": "sha256:bd32e12c3a73321d3ee2caf733d70c574f2443798d38a3f834d33430170aad83"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Lb/CJ.pl",
"type": "reg",
"size": 790,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21375765,
"NumLink": 0,
"digest": "sha256:42c7825541dc6610e017372d3a8efb1ca40e9f902f7b753dc41cc795e950835b"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Lb/CL.pl",
"type": "reg",
"size": 1453,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21376323,
"NumLink": 0,
"digest": "sha256:9103ed646347e80f105a396fb40da96c67987d228006afbec4f9a8a142996888"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Lb/CM.pl",
"type": "reg",
"size": 2913,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21377123,
"NumLink": 0,
"digest": "sha256:bff75a24686a2f41fd0584ca0591fbc5f305f51fb6d5393120240eea6e5d87e8"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Lb/EX.pl",
"type": "reg",
"size": 734,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21378606,
"NumLink": 0,
"digest": "sha256:20a5578ef22b824b1d46f57c7975a9988de472db0923e14da1114eda5a127d9e"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Lb/GL.pl",
"type": "reg",
"size": 594,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21379172,
"NumLink": 0,
"digest": "sha256:fbfd4d27e4263cbcedd2ab51a0565064e497b26e150f1aef9da80bc5bd563d19"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Lb/ID.pl",
"type": "reg",
"size": 1997,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21379674,
"NumLink": 0,
"digest": "sha256:d0fcbb793a7a08737f7cdc8cd292a32e5394850852e49382a281577a829c6160"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Lb/IN.pl",
"type": "reg",
"size": 533,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21380723,
"NumLink": 0,
"digest": "sha256:05939a3120788239583c7041e285cfb294a7d86e9fa8551c36b16cc45a7289ed"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Lb/IS.pl",
"type": "reg",
"size": 580,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21381187,
"NumLink": 0,
"digest": "sha256:6dab652d9ca506eec3a81522f0264dd939c145d3340f96a20517d45c875f8e2a"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Lb/NS.pl",
"type": "reg",
"size": 678,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21381678,
"NumLink": 0,
"digest": "sha256:316b077ed0564abd6ed7751171e36beca2e14cfac602472fbb676b8483a58f6f"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Lb/OP.pl",
"type": "reg",
"size": 1441,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21382208,
"NumLink": 0,
"digest": "sha256:15bea04086658d18d80ce78c654cc87f72eb3c9776e4ac555ee2b6aa5981107d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Lb/PO.pl",
"type": "reg",
"size": 692,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21383011,
"NumLink": 0,
"digest": "sha256:30e78aaf87f8f2e91f79f63c244b8d77ee976000fa38f6b095119c23481a2b94"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Lb/PR.pl",
"type": "reg",
"size": 702,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21383562,
"NumLink": 0,
"digest": "sha256:93e20a79854fe08ed2aece340eb63149e65e4c3916add2d554a2d125c43972bc"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Lb/QU.pl",
"type": "reg",
"size": 620,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21384121,
"NumLink": 0,
"digest": "sha256:6baa2930012d73c927e385f3b9181c8ec08a8092ad5e80338ba6413fa4a928b0"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Lb/SA.pl",
"type": "reg",
"size": 926,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21384637,
"NumLink": 0,
"digest": "sha256:fbac2fd92d95af1bd7960f50252247040c8f1da99eaad06fea8703a90d3b31a3"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Lb/XX.pl",
"type": "reg",
"size": 7404,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21385293,
"NumLink": 0,
"digest": "sha256:5e4a09109f7eddad90eaddf7ec155f28c2ea3543ea9693894cf679cd66a9d93c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Lower/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Lower/Y.pl",
"type": "reg",
"size": 6986,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21388636,
"NumLink": 0,
"digest": "sha256:2fd2550c8dd707f916b5608dc6adbd5a25a41ca1cb0556461f8daebec81d3732"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Math/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Math/Y.pl",
"type": "reg",
"size": 2093,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21391966,
"NumLink": 0,
"digest": "sha256:659033c57408a43fcd825c8233d423b70d51d17aa126582a99e394da31104392"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/NFCQC/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/NFCQC/M.pl",
"type": "reg",
"size": 894,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21393083,
"NumLink": 0,
"digest": "sha256:35b860728ea7e0d195e3ff942c26df489663e927b6207e9df4b210a42e7ada2d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/NFCQC/Y.pl",
"type": "reg",
"size": 1641,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21393741,
"NumLink": 0,
"digest": "sha256:310eb93664b58c215224857f99291667c99d6be09702efcfefaf972e6b7ab4db"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/NFDQC/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/NFDQC/N.pl",
"type": "reg",
"size": 2885,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21394726,
"NumLink": 0,
"digest": "sha256:abaa7ba35e14c3bad73e74789f290c325ff1625bd31d61ccda3c5042abcf56e1"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/NFDQC/Y.pl",
"type": "reg",
"size": 2887,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21396206,
"NumLink": 0,
"digest": "sha256:3c29d7fad1c6929e8b0e7e8ab6104be050eb47b24e6126b8c4afbf6cd6b3f3a7"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/NFKCQC/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/NFKCQC/N.pl",
"type": "reg",
"size": 3321,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21397718,
"NumLink": 0,
"digest": "sha256:fe77c77030f1e1e99698aefa57fb2cf5b8e8eccf23bf9565b542650b9e7e2fcd"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/NFKCQC/Y.pl",
"type": "reg",
"size": 3691,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21399334,
"NumLink": 0,
"digest": "sha256:1f5fab3008efa3c5b53190bdf969ea1f547baeabe9a41e4c9b790ca84a6fcf93"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/NFKDQC/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/NFKDQC/N.pl",
"type": "reg",
"size": 4791,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21401145,
"NumLink": 0,
"digest": "sha256:e0f895808e8339727f0b5744779e2103ab91ebc74dc77734352667e575ff0669"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/NFKDQC/Y.pl",
"type": "reg",
"size": 4793,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21403408,
"NumLink": 0,
"digest": "sha256:8bed0812bc523847488a63a7c3aae16aa5caa8f4474b11bc2fa8173d4608a544"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nt/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nt/Di.pl",
"type": "reg",
"size": 702,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21405695,
"NumLink": 0,
"digest": "sha256:3c278d93de413ca5e0210b4ed4ad791223c18ea02ac9ccc923482b9d09d38980"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nt/None.pl",
"type": "reg",
"size": 2629,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21406252,
"NumLink": 0,
"digest": "sha256:56de36761d9a9cfd088c85e561b6081759f8e2bc77ee7b175882ffeb41a33274"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nt/Nu.pl",
"type": "reg",
"size": 2149,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21407619,
"NumLink": 0,
"digest": "sha256:beca0b577b03dca5c3e976bc219c662bbe60c40f1667d0389fbdb11008c2afd5"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/0.pl",
"type": "reg",
"size": 1257,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21408787,
"NumLink": 0,
"digest": "sha256:d59f895d8d9db1e3ba2f030543fc3c58f29781dfd67bdfa3894cf2c97618c1d0"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/1.pl",
"type": "reg",
"size": 1787,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21409578,
"NumLink": 0,
"digest": "sha256:ee1c6cc39b46109853103c6c93e8f19166e59d89e02b87d5977c5eee269e0900"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/10.pl",
"type": "reg",
"size": 1062,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21410585,
"NumLink": 0,
"digest": "sha256:be3afd12ad8c3b06f8e210c273fe03b12fa43e7ad71cc72c72f9e76148897c6a"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/100.pl",
"type": "reg",
"size": 828,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21411284,
"NumLink": 0,
"digest": "sha256:062447e211a0753dc7b8d69aca1bb364937d1065b74768bc0338831b56ed776f"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/1000.pl",
"type": "reg",
"size": 710,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21411886,
"NumLink": 0,
"digest": "sha256:7f61ec9f997bae10ad53eba48eddf2416ad833f761564e1f77aafa90ff06d748"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/10000.pl",
"type": "reg",
"size": 594,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21412442,
"NumLink": 0,
"digest": "sha256:fbe03cac66a477c30ba70339e69ad16997456727d28ae3fd1e1edd049a3e6352"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/11.pl",
"type": "reg",
"size": 550,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21412940,
"NumLink": 0,
"digest": "sha256:10c0beb58714c1a2d12a98ba86666ad288872cf1a3a28e53c986477b13d2255f"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/12.pl",
"type": "reg",
"size": 550,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21413416,
"NumLink": 0,
"digest": "sha256:7e19666dbe9e7cff6581f954c1b90842312aecfd9c137e2109947e7f986bd6b2"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/13.pl",
"type": "reg",
"size": 529,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21413890,
"NumLink": 0,
"digest": "sha256:b09047105c8f53ebf6f559362f0af14470a3b21e4e7794fa441a432d7c5019d2"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/14.pl",
"type": "reg",
"size": 529,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21414352,
"NumLink": 0,
"digest": "sha256:799cef8b8010ef2aa1edc5ec0da21261031f1942917b9e9f9252efc812a8d913"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/15.pl",
"type": "reg",
"size": 529,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21414815,
"NumLink": 0,
"digest": "sha256:80e45d74c4701efe80e6305a0965114dae16bd82a7d0dfde5d4f32bdac25d81a"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/16.pl",
"type": "reg",
"size": 540,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21415277,
"NumLink": 0,
"digest": "sha256:5207764dac87e97fdcf4787cad1966721e636ff768c984dfb69f1f7444463bee"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/17.pl",
"type": "reg",
"size": 540,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21415746,
"NumLink": 0,
"digest": "sha256:18c61339544aab80adf0219e8e46046c35d188766f17996993e26b3f6e617f19"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/18.pl",
"type": "reg",
"size": 540,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21416214,
"NumLink": 0,
"digest": "sha256:825518e61f5b329281357fbc566496484a3ee5b27bf65a83dcb86ba65e8a16a4"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/19.pl",
"type": "reg",
"size": 540,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21416685,
"NumLink": 0,
"digest": "sha256:fd7a996d357f74ef65d6d9e174293dd9b2b62e1fc8b0f231c761baf67d1059c7"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/1_2.pl",
"type": "reg",
"size": 624,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21417156,
"NumLink": 0,
"digest": "sha256:2aaf91c335a92bc9014f5342abd9565cf90a28610455bd4ba8471f66c27db2fa"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/1_3.pl",
"type": "reg",
"size": 560,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21417673,
"NumLink": 0,
"digest": "sha256:71cfd371216fa9290dc38b17ec5a9f810acf1a8988bbb974aeca18ae03205134"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/1_4.pl",
"type": "reg",
"size": 612,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21418151,
"NumLink": 0,
"digest": "sha256:f3aa2dc45272ea33a70f9eb387faf24f1a521db91dfecc1229a914a896584b03"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/1_8.pl",
"type": "reg",
"size": 544,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21418657,
"NumLink": 0,
"digest": "sha256:90d84e969a919182cbb641336f84927bb38827287027c0a0f0da3e8231ad1d93"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/2.pl",
"type": "reg",
"size": 1789,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21419132,
"NumLink": 0,
"digest": "sha256:400d0cf0f66474b868ef996e910c4dc33486cdff13c2dd436bde843fc7841439"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/20.pl",
"type": "reg",
"size": 842,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21420124,
"NumLink": 0,
"digest": "sha256:26cb5831d1afaf01f35e659269bd4455b124df20d7eb12a114516b2a9a4da22b"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/200.pl",
"type": "reg",
"size": 537,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21420733,
"NumLink": 0,
"digest": "sha256:748a077ee40368a52770dc192fe6ddde19d7161061c621c64228f56fbf180342"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/2_3.pl",
"type": "reg",
"size": 572,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21421199,
"NumLink": 0,
"digest": "sha256:ea4dc781c6de8fd29ad1b3d3884c3a8d9a4df1a1ae458fae80915d4bb3d7a0c6"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/3.pl",
"type": "reg",
"size": 1771,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21421682,
"NumLink": 0,
"digest": "sha256:a169a6edc2d01accc73b423afe0a4cbc165a79de17f3da89dcb90619bc6c76f9"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/30.pl",
"type": "reg",
"size": 672,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21422679,
"NumLink": 0,
"digest": "sha256:e82e4c2aa63e7bdfcd08eff764bb40cad93a2ff06de84a8f6df03496b79a5205"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/300.pl",
"type": "reg",
"size": 550,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21423214,
"NumLink": 0,
"digest": "sha256:c975e4a9b2cfec95d561f3fcb33018472b139e787a614a52d38b9ebdce94f7ad"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/3_4.pl",
"type": "reg",
"size": 564,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21423688,
"NumLink": 0,
"digest": "sha256:59e611a9c7d9a6113998ddc4081599b7f1b8bfca865df319c3c64c346e456b44"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/4.pl",
"type": "reg",
"size": 1703,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21424171,
"NumLink": 0,
"digest": "sha256:86f8e4e022f5063e8916dcafdf0d948f9d3e9c949d49eb15a5adddc4f11b87e7"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/40.pl",
"type": "reg",
"size": 674,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21425131,
"NumLink": 0,
"digest": "sha256:606184715fc996afb0a828ddfaed5cb76207645a780757cb1055df956f9c910c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/400.pl",
"type": "reg",
"size": 537,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21425669,
"NumLink": 0,
"digest": "sha256:1d7a72efd682759fc95fb185033e24f78039ae948aaf7a499f9939b4c8ad4b0c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/5.pl",
"type": "reg",
"size": 1723,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21426135,
"NumLink": 0,
"digest": "sha256:d91aae3728260a7bd81a57f286e6ecec5757eb92e9391e78cea83a44d6487281"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/50.pl",
"type": "reg",
"size": 760,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21427113,
"NumLink": 0,
"digest": "sha256:c8979e1564d8d627864464b1c82808503e831a9e8dafd55f6dda8d5409debd92"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/500.pl",
"type": "reg",
"size": 606,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21427686,
"NumLink": 0,
"digest": "sha256:e084754ed9e32f713f5b5e66288dd33c1c81c59680b0fe59c4b048d71d451a15"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/5000.pl",
"type": "reg",
"size": 560,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21428187,
"NumLink": 0,
"digest": "sha256:67b3570ce87591ac940ac05127d6fe4cd86bdcb3bab1f1a5a45ae8d48e645e47"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/50000.pl",
"type": "reg",
"size": 548,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21428665,
"NumLink": 0,
"digest": "sha256:43ceac5194c4b6c13063e1bacf84965db8fc070ca441ab784eeb28e8d38ed17a"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/6.pl",
"type": "reg",
"size": 1577,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21429135,
"NumLink": 0,
"digest": "sha256:4f3861e2b7418ce50ea92ab959cb1cbefd9a0e4ae87a9775a4d4793dd5e4aaa1"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/60.pl",
"type": "reg",
"size": 610,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21430053,
"NumLink": 0,
"digest": "sha256:9056982a2e7a716fce6862af087423dbd076e6522b20857d5a5cbce35e303cee"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/600.pl",
"type": "reg",
"size": 537,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21430560,
"NumLink": 0,
"digest": "sha256:31ab8a3d4a609401735014ac484ab69125f20e57cc72075ba26149106d5eb957"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/7.pl",
"type": "reg",
"size": 1543,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21431026,
"NumLink": 0,
"digest": "sha256:d88d19bd34873be079c39de0e6c15a9738850d6895fc882db01610d89486b59b"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/70.pl",
"type": "reg",
"size": 610,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21431944,
"NumLink": 0,
"digest": "sha256:a4eea7e7052640fd72bd5c91eb7b59b70804c89a384f53eb67d6d7abb2cf014b"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/700.pl",
"type": "reg",
"size": 537,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21432453,
"NumLink": 0,
"digest": "sha256:5576ee77c26a8d03997339628698136e995b6868e6e8da37e6c2e3bdecfb1a0c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/8.pl",
"type": "reg",
"size": 1505,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21432919,
"NumLink": 0,
"digest": "sha256:d865096d0036aa6bc3b8c418bffbe31bfceff0e438ec1689be161333245d2c00"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/80.pl",
"type": "reg",
"size": 598,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21433806,
"NumLink": 0,
"digest": "sha256:c008bbe4be4c267926deb9c1f80ac6675958042db9b02f4eab7da592a52c3f50"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/800.pl",
"type": "reg",
"size": 537,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21434310,
"NumLink": 0,
"digest": "sha256:34117ef3e064aaeed5c77798891de425b93f3493454d43388222a8dd17906429"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/9.pl",
"type": "reg",
"size": 1531,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21434776,
"NumLink": 0,
"digest": "sha256:0021379b5504a82adb2eb8292ca703b5bac28d67ed2764202476ad89526db1e9"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/90.pl",
"type": "reg",
"size": 598,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21435681,
"NumLink": 0,
"digest": "sha256:d107b601802e681fcc8102c0360127dfc691b3a301ea27050b1b6dfed632f906"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Nv/900.pl",
"type": "reg",
"size": 550,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21436183,
"NumLink": 0,
"digest": "sha256:2553e3e7f5fafcee581b2dd9e769b51501b4731ff721f8a4e987a4f39c3e5d58"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/PatSyn/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/PatSyn/Y.pl",
"type": "reg",
"size": 747,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21436691,
"NumLink": 0,
"digest": "sha256:7f8b03f8f1438356ca96901ffd41d91499bea0b8401eac571cb29261abaa711c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Perl/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Perl/Alnum.pl",
"type": "reg",
"size": 7714,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21437317,
"NumLink": 0,
"digest": "sha256:9952517b0c62af4389521abaf62d3f77d595f3ffa0b19a4e27390ba92610cc8c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Perl/Assigned.pl",
"type": "reg",
"size": 7525,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21440807,
"NumLink": 0,
"digest": "sha256:8a4fc3da22c19db3eb90d62a430d70f94845ef607485015e3829da58ac88d08a"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Perl/Blank.pl",
"type": "reg",
"size": 561,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21444177,
"NumLink": 0,
"digest": "sha256:6caa50b959c6f4371ec7a8ff725912b6a9485ceab430a2a9d74e17379db8755d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Perl/Graph.pl",
"type": "reg",
"size": 7574,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21444667,
"NumLink": 0,
"digest": "sha256:d635200d45c7c73eab08976c3a06c4ee07d4dd09f2e9d519a05c34f1b66b818b"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Perl/PerlWord.pl",
"type": "reg",
"size": 514,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21448060,
"NumLink": 0,
"digest": "sha256:b77ce2917f3475d0cbca36f6fcbe815012fef84ef399ef9cca93105f07d577d5"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Perl/PosixPun.pl",
"type": "reg",
"size": 515,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21448523,
"NumLink": 0,
"digest": "sha256:2fcf2c1b20a2cf64bf2d8ae307e88f7e73a630e092f3f53c531c0d4d2eb60483"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Perl/Print.pl",
"type": "reg",
"size": 7544,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21448985,
"NumLink": 0,
"digest": "sha256:7050e54f3f298bddbcdfe968001ac0c47cf9970e5a7935acee6b4b7cea9e929e"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Perl/SpacePer.pl",
"type": "reg",
"size": 579,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21452371,
"NumLink": 0,
"digest": "sha256:290b77398af470f5a01ae7c5adfaf1bcb07923be2bca3714ebe75753c140c48d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Perl/Title.pl",
"type": "reg",
"size": 582,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21452864,
"NumLink": 0,
"digest": "sha256:c253dd153be8fe8a5801253eb6481afcb77db713d8f610438ca48442f6075cb6"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Perl/Word.pl",
"type": "reg",
"size": 7842,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21453369,
"NumLink": 0,
"digest": "sha256:1a1cc4d2d8e4ba131156d73913dfc69be0a7356248788439afb7dff8ea42280a"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Perl/XPosixPu.pl",
"type": "reg",
"size": 2197,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21456911,
"NumLink": 0,
"digest": "sha256:e26a1dd90019c5aab86075ad71a2cb5f3e8ccdd351ab1f1f587bd2e66b56b164"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Perl/_PerlAny.pl",
"type": "reg",
"size": 1673,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21458109,
"NumLink": 0,
"digest": "sha256:fee75cd5ad55c4c63be21af06b5fb660423e00e818bfbd2ebe51fe05136605df"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Perl/_PerlCh2.pl",
"type": "reg",
"size": 7896,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21459137,
"NumLink": 0,
"digest": "sha256:ad559d235f4ad82d1e76a7bdfe5bdd1f8fe81e6ae46a219d505945c65c4ea72d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Perl/_PerlCha.pl",
"type": "reg",
"size": 6788,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21462706,
"NumLink": 0,
"digest": "sha256:9a8988e8ae2dd1e436489caba1a27a9656c39ca9e92a4882c009a2f6de955fba"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Perl/_PerlFol.pl",
"type": "reg",
"size": 772,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21465780,
"NumLink": 0,
"digest": "sha256:5108f098c6e2d5784eb1f5c257db5a86c7a03dbd4e9665f4c03a8f1830f48cb1"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Perl/_PerlIDC.pl",
"type": "reg",
"size": 7870,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21466365,
"NumLink": 0,
"digest": "sha256:5316fa69c266fa288c4822ead5fe7a992be767c5a590f4c6be7af568560d8f11"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Perl/_PerlIDS.pl",
"type": "reg",
"size": 6830,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21469916,
"NumLink": 0,
"digest": "sha256:607494d403c1ec2bb6e3bb337779829df1cc146a816a4be29c6a9ba1f9ef19fc"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Perl/_PerlNch.pl",
"type": "reg",
"size": 742,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21473011,
"NumLink": 0,
"digest": "sha256:5838b0621e3cb7ca2c504a6d84c340c204a109f0196e49b5753ca7b476a8802c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Perl/_PerlPat.pl",
"type": "reg",
"size": 529,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21473583,
"NumLink": 0,
"digest": "sha256:82bb1d0597b2a672b6c7f758ea8ee3b455c0b838335a78e4c6caf928a78bf21e"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Perl/_PerlPr2.pl",
"type": "reg",
"size": 602,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21474050,
"NumLink": 0,
"digest": "sha256:1a6f866d8d1d7959aa1c516dc9e2f897fdea0979ac8a4ae72ede7cc914ebc77b"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Perl/_PerlPro.pl",
"type": "reg",
"size": 594,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21474567,
"NumLink": 0,
"digest": "sha256:7c66d8b2697b4c70f21e26079140360c36135ad4ff40f06220f71d9168202964"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Perl/_PerlQuo.pl",
"type": "reg",
"size": 880,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21475077,
"NumLink": 0,
"digest": "sha256:77ed1efcc69728561207468b1633d4eb689b5b52fe1b1c67cf580ee461384650"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/QMark/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/QMark/Y.pl",
"type": "reg",
"size": 622,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21475767,
"NumLink": 0,
"digest": "sha256:cb3ab30d546abbb3adb5bcec8f5d87e03971aa26cac99751b30131a4e522c541"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/SB/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/SB/AT.pl",
"type": "reg",
"size": 529,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21476317,
"NumLink": 0,
"digest": "sha256:8b3f0b56cc1ca8ee4c761f2f97708e8488e2bc3663030b405d221502d6b581a8"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/SB/CL.pl",
"type": "reg",
"size": 962,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21476782,
"NumLink": 0,
"digest": "sha256:0d142d8538c03b5c1f92bd79bd0788afbcf0df2f4fc31a1f4331f268ea62e359"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/SB/EX.pl",
"type": "reg",
"size": 3087,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21477450,
"NumLink": 0,
"digest": "sha256:cacbd9e5476b94620ad8fd69bb61c78eeafcb81025961cea5ca835abc2f27945"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/SB/FO.pl",
"type": "reg",
"size": 690,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21479003,
"NumLink": 0,
"digest": "sha256:66981228c363ee5ded673fc92d223eaffa3f04125cddbec3697ce26a91e358cb"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/SB/LE.pl",
"type": "reg",
"size": 5607,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21479555,
"NumLink": 0,
"digest": "sha256:695c41da6f692fd87bd7dc442d14583021a8e8d88be6a5c58afc30d7f44279b8"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/SB/LO.pl",
"type": "reg",
"size": 6978,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21482153,
"NumLink": 0,
"digest": "sha256:7fdbc639de5654f060468fd16c3b81e28dd423130b719b927d8fe3b093ed1b50"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/SB/NU.pl",
"type": "reg",
"size": 1041,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21485451,
"NumLink": 0,
"digest": "sha256:021d9f558b33d16cebba51f98f508b8312a7c281736a2ff87b0451e703dc51b2"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/SB/SC.pl",
"type": "reg",
"size": 694,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21486184,
"NumLink": 0,
"digest": "sha256:552575e253a94f8201b781e7ce63ebe61297a31223d72695407a2195109e9742"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/SB/ST.pl",
"type": "reg",
"size": 1173,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21486726,
"NumLink": 0,
"digest": "sha256:b74f0863978c79d6d0274bde0cb8ef806da132f2d707311e7530ac3f9ec96d25"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/SB/Sp.pl",
"type": "reg",
"size": 567,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21487477,
"NumLink": 0,
"digest": "sha256:f79d66f8d7ea24ddd3799fc847bfac9a89681e0a4c2dfd5be626a680aba329c1"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/SB/UP.pl",
"type": "reg",
"size": 6958,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21487969,
"NumLink": 0,
"digest": "sha256:0ef1d8351b9ed81b1849112bab416fb7b34e54ab0db3b894697899eb71ac83a7"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/SB/XX.pl",
"type": "reg",
"size": 8583,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21491241,
"NumLink": 0,
"digest": "sha256:a612aa0ebcf3a22e857a4b6da1b787d96d154f13916c0aec1d0e5b46b9248323"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/SD/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/SD/Y.pl",
"type": "reg",
"size": 842,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21495129,
"NumLink": 0,
"digest": "sha256:79830cc7a53e501897ed8ac651e1144c52ae1296c75bae0c59f3ba64b8e09725"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/STerm/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/STerm/Y.pl",
"type": "reg",
"size": 1203,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21495759,
"NumLink": 0,
"digest": "sha256:1a2401619db14c43424d2859f389995dc07c28574cc2ec9ae553adbb6480e621"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Arab.pl",
"type": "reg",
"size": 1183,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21496550,
"NumLink": 0,
"digest": "sha256:391e6e7828216774af135c7db740773bf09a654a371848fbd058c65a57e6645c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Armn.pl",
"type": "reg",
"size": 552,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21497272,
"NumLink": 0,
"digest": "sha256:dc53d34785f52f46ad7b070bbee2b5bfd29659d8c3404b45d75eea5e0a779e1c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Beng.pl",
"type": "reg",
"size": 630,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21497750,
"NumLink": 0,
"digest": "sha256:9d880049f3d8aa0de59fff5e0b00b0f2eed60c75f41d7793855d3895923edccd"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Cham.pl",
"type": "reg",
"size": 537,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21498267,
"NumLink": 0,
"digest": "sha256:5090d5f71d3ebc95537aab6d7f8a9a9718d868ed2f70d0f168daac292965f793"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Cprt.pl",
"type": "reg",
"size": 562,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21498730,
"NumLink": 0,
"digest": "sha256:677f13ec7877273622ff8452af3556fa95b6eb26ff8277e5770fed2af32f83d5"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Cyrl.pl",
"type": "reg",
"size": 566,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21499205,
"NumLink": 0,
"digest": "sha256:71208af01e306dff25877689ac0c93dd1c73421060f7ea5ddbe2c456a464959a"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Deva.pl",
"type": "reg",
"size": 531,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21499693,
"NumLink": 0,
"digest": "sha256:1fa5bef570ad8ca7776e6b43fb8cad5922c7a415cf79e8ff5499130c607c79e0"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Dupl.pl",
"type": "reg",
"size": 560,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21500158,
"NumLink": 0,
"digest": "sha256:5d62fbf32fa26c241f8e6fbc5886168de8ac180c62f5e099b25275efe96b0e4c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Ethi.pl",
"type": "reg",
"size": 838,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21500629,
"NumLink": 0,
"digest": "sha256:f442e3c03c2e5bf4e4cff85fea65895e3dd30b3a8d8897ccf5876d543721357b"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Geor.pl",
"type": "reg",
"size": 576,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21501229,
"NumLink": 0,
"digest": "sha256:5cd28f74fa815920c263618885f86cb12a522eb5c20e752ef68e7ee07e0fecdd"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Gran.pl",
"type": "reg",
"size": 670,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21501716,
"NumLink": 0,
"digest": "sha256:627ef9311d3318708c1d1e969fbbd759012b60ae1137165ae2fa675e57a44d4a"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Grek.pl",
"type": "reg",
"size": 840,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21502233,
"NumLink": 0,
"digest": "sha256:ba3ee2c1a1243596b4c674f19c862b794cb39f437789dfd581a65fbcd740bc51"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Gujr.pl",
"type": "reg",
"size": 630,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21502858,
"NumLink": 0,
"digest": "sha256:606ab94e81f5d4fdfaa8d06df3ca7a76880b864ac9b457de14ace9ef971b58e6"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Guru.pl",
"type": "reg",
"size": 650,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21503373,
"NumLink": 0,
"digest": "sha256:81005b5c2ea47842b1c641758b7159435ab42cdfdd9b3b59f3c6446ff491614c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Han.pl",
"type": "reg",
"size": 692,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21503890,
"NumLink": 0,
"digest": "sha256:03730121b80a9be3287176180ac9af84c037bb55315e6d20dc32bef95f4c0172"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Hang.pl",
"type": "reg",
"size": 656,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21504442,
"NumLink": 0,
"digest": "sha256:e7074414982df58297d3be0ffafbf8151a392cf9685961ae390c0d748dfdbe74"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Hebr.pl",
"type": "reg",
"size": 592,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21504974,
"NumLink": 0,
"digest": "sha256:95eda2aab9fa22213c9f0670fab3ce7898bb4968fa0749aa4bafb1458fbe906d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Hira.pl",
"type": "reg",
"size": 541,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21505465,
"NumLink": 0,
"digest": "sha256:f4a6388261e150b9582ae97adde0196558ab29cf9aaffb8e6ac1419d785aa98d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Hmng.pl",
"type": "reg",
"size": 550,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21505937,
"NumLink": 0,
"digest": "sha256:025d3ed22ce8b000246b516faef397e8cbc6325d87d10e2fe46bdbd8b8aded7d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Kana.pl",
"type": "reg",
"size": 588,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21506407,
"NumLink": 0,
"digest": "sha256:370e75d471e839252b932ad9c692847e29b6e8ff6b37672aa9edf78c304d45c5"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Khar.pl",
"type": "reg",
"size": 586,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21506904,
"NumLink": 0,
"digest": "sha256:abde89231e73224c436c23c41863e463b5ddc0309f984dc2c7976b7257939aaa"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Khmr.pl",
"type": "reg",
"size": 529,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21507389,
"NumLink": 0,
"digest": "sha256:557b71c15ae04706c5b2e3cf62d0f0dd117ccdf798f7f44109405f5ff30f8e99"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Knda.pl",
"type": "reg",
"size": 630,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21507856,
"NumLink": 0,
"digest": "sha256:424843eea932b69369654447ad0954df298ee8490fdab5a44d9ac8489083c51d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Lana.pl",
"type": "reg",
"size": 540,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21508367,
"NumLink": 0,
"digest": "sha256:ee52439efb562c59d288b90995590a46aa413d112a1e4a9f2718f0b47ed9b57c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Lao.pl",
"type": "reg",
"size": 670,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21508838,
"NumLink": 0,
"digest": "sha256:0061ceac406c2a7dfd5cabead0df79fd3c271de81259c35cb1cad786d1d4d98f"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Latn.pl",
"type": "reg",
"size": 801,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21509362,
"NumLink": 0,
"digest": "sha256:aa65fa55c01ad33641ae16773d397266e557275150c9297b3e22a1c408563242"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Limb.pl",
"type": "reg",
"size": 540,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21509978,
"NumLink": 0,
"digest": "sha256:76f8749d6cb28af3362f19954ac816a4a62a6eb0884bac2281a2c41d9c889705"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Linb.pl",
"type": "reg",
"size": 574,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21510445,
"NumLink": 0,
"digest": "sha256:c3ce24b3f422fdbeb65a76caa35a8ef562f2d8f903c6300af8a8c024fabfec73"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Mlym.pl",
"type": "reg",
"size": 600,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21510927,
"NumLink": 0,
"digest": "sha256:38aa93ada735f156fd8c1d1344d8acba18193574987e3b3dfcb2c6b506d028a9"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Mong.pl",
"type": "reg",
"size": 550,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21511430,
"NumLink": 0,
"digest": "sha256:40db11e81fd26763bae5d0c38cdba267e6234c9783bc8ae26894b9964631068b"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Mult.pl",
"type": "reg",
"size": 550,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21511904,
"NumLink": 0,
"digest": "sha256:bb5e9949b1f091314c524ba997f053697da005a46973046a88890f3cfb44c1ab"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Orya.pl",
"type": "reg",
"size": 630,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21512374,
"NumLink": 0,
"digest": "sha256:b121f8cb42bb658146b2a83f51c74b42464376e9b0451b344067f35e723019b8"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Sinh.pl",
"type": "reg",
"size": 622,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21512885,
"NumLink": 0,
"digest": "sha256:1520c9231481722f778117aa4b80385ef3864d74fe8a7d2d9e5ae1eb11013e1c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Talu.pl",
"type": "reg",
"size": 529,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21513399,
"NumLink": 0,
"digest": "sha256:c9bc81c423d40136bd98ce46a2bc93e77e0a486269f5403169d9f5ab15650b83"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Taml.pl",
"type": "reg",
"size": 650,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21513861,
"NumLink": 0,
"digest": "sha256:63e80f0800761f549a1f22c9b40a44a9efba5ea36557d10afc92ad321a1af6df"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Telu.pl",
"type": "reg",
"size": 620,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21514382,
"NumLink": 0,
"digest": "sha256:a9809ee1dd684d7f274340480239376911d01438763b2dcee24635a09d95f454"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Tibt.pl",
"type": "reg",
"size": 560,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21514892,
"NumLink": 0,
"digest": "sha256:34f03fe6ebecf64c53a6337df7d1fb81555d6bd91fa7da3f09a6fd1ed047b37d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Xsux.pl",
"type": "reg",
"size": 537,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21515378,
"NumLink": 0,
"digest": "sha256:326756f86813e33faab4db12d459add1418300d5c03b888c734e1ea95fc90290"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Zinh.pl",
"type": "reg",
"size": 790,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21515850,
"NumLink": 0,
"digest": "sha256:6ffc0b229edff63e248bb867894b1841209a0bbbdb5acef54f07fc457bee6f33"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Zyyy.pl",
"type": "reg",
"size": 2417,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21516447,
"NumLink": 0,
"digest": "sha256:bf1d7c65f2be5a71a43c5b712ec5b74231f792d282936df11a75e63d46917cf3"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Sc/Zzzz.pl",
"type": "reg",
"size": 7492,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21517702,
"NumLink": 0,
"digest": "sha256:aef97d8558d5eca3edc325992d61ec229ad72c20ce142c06c182103372e4110d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Arab.pl",
"type": "reg",
"size": 1145,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21521084,
"NumLink": 0,
"digest": "sha256:6fe86c9c031605dc818bb6a9fb35aa206da84cce9befac7c5675f66def073342"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Armn.pl",
"type": "reg",
"size": 552,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21521792,
"NumLink": 0,
"digest": "sha256:8104bbaab001aaff455371ba6ba7d4ec8be75ea8bca2291e6f53bb018a3ad8c5"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Beng.pl",
"type": "reg",
"size": 650,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21522270,
"NumLink": 0,
"digest": "sha256:f6b09a7f2773d738fecf42eba0ed8ecb69bf44f7a297e3e7ff03c239c9ebdbab"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Bopo.pl",
"type": "reg",
"size": 726,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21522795,
"NumLink": 0,
"digest": "sha256:4d84e492592330f3afc443c10b145d23b89ab83a4edbfa14226def74e8984243"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Cakm.pl",
"type": "reg",
"size": 533,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21523350,
"NumLink": 0,
"digest": "sha256:1024f84347f2bfaacd98b372330c72948ffa6fb37592e0386e0a83879a8f1bea"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Copt.pl",
"type": "reg",
"size": 534,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21523824,
"NumLink": 0,
"digest": "sha256:81926afb1a8b5fdeb65d82b4e5c72a57acdccc3bf5e1c90e41963f02a696b766"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Cprt.pl",
"type": "reg",
"size": 598,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21524298,
"NumLink": 0,
"digest": "sha256:b0f8205991529c307461b5624b2ed0fcd7a19138439ea33b3bd48ca203754875"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Cyrl.pl",
"type": "reg",
"size": 556,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21524790,
"NumLink": 0,
"digest": "sha256:ade9d8f1e4a8831d77d5acb967b07967e332d869a0a6d5674b4333e2f955abc3"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Deva.pl",
"type": "reg",
"size": 554,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21525274,
"NumLink": 0,
"digest": "sha256:c2a8adc4eaf7cb7e68e6ed8e65b76019b5b6d8d76cc73a39a110ef7c832786d6"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Dupl.pl",
"type": "reg",
"size": 560,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21525754,
"NumLink": 0,
"digest": "sha256:5815a61fc51923241fc94eefc15df6c75df4b9618a7aa4aa7a9eb29793a658fc"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Geor.pl",
"type": "reg",
"size": 586,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21526227,
"NumLink": 0,
"digest": "sha256:1824c8df685eeeb654bfb2b909fe1b2e2402362645c3c7e0b2023e57e5918f21"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Glag.pl",
"type": "reg",
"size": 535,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21526723,
"NumLink": 0,
"digest": "sha256:a5e9b2059e7af774f3c7fb6e745964cf4ae1d13e9aab87930c02e067448bfd96"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Gran.pl",
"type": "reg",
"size": 770,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21527192,
"NumLink": 0,
"digest": "sha256:e4863cbc11c2641691ee9feec52be7ff57419b665da9f00fc9958243097ef581"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Grek.pl",
"type": "reg",
"size": 856,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21527761,
"NumLink": 0,
"digest": "sha256:453bd840cd2d2d7707e9d46e43d4bda59547feb83c4d7a6d2a7b6dcdea4f9592"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Gujr.pl",
"type": "reg",
"size": 662,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21528399,
"NumLink": 0,
"digest": "sha256:f001bbab220d35ccb9b34c59b8dff27679875cbebd579287332dab6b0f5244e2"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Guru.pl",
"type": "reg",
"size": 682,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21528933,
"NumLink": 0,
"digest": "sha256:201226ee4d7744d62bc277f6c91605d6089279be4050b8692e9c045dd612fe79"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Han.pl",
"type": "reg",
"size": 862,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21529469,
"NumLink": 0,
"digest": "sha256:1dbb87fdab60f31731ca377da46da55bbc3c447b490d9bd525dcfc20620c311d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Hang.pl",
"type": "reg",
"size": 836,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21530099,
"NumLink": 0,
"digest": "sha256:2a883383f42b37a0aca4c031601fa4c692034f9796237e53e3efc2703e624534"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Hira.pl",
"type": "reg",
"size": 782,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21530703,
"NumLink": 0,
"digest": "sha256:b0915643b0657a726fec45094508f0187bca5c62d9f53d11e9540c67ab8ba2ae"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Kana.pl",
"type": "reg",
"size": 756,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21531279,
"NumLink": 0,
"digest": "sha256:d6bcf7da353acdd5e9a838aba68ffa73077a5cd67a8f0a2f8479cfcc9c9510aa"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Knda.pl",
"type": "reg",
"size": 670,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21531851,
"NumLink": 0,
"digest": "sha256:aab06d08de88e4e006480c31f71c74b2caa2fd40b0cd642c40550a723d05a884"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Latn.pl",
"type": "reg",
"size": 851,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21532386,
"NumLink": 0,
"digest": "sha256:ef74e04ab1fc951cd7b386640df48f54f393cb33a2b2caff3eb20b2a9f7535c8"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Limb.pl",
"type": "reg",
"size": 550,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21533027,
"NumLink": 0,
"digest": "sha256:94f401e7b8f7427cc3eb6c380151a384678cba6c3b81c51a15e0aba18281861e"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Linb.pl",
"type": "reg",
"size": 610,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21533502,
"NumLink": 0,
"digest": "sha256:819681fdf147c367160322000e3a10aceab7ae0f70db24160ea56f22ceaca7f2"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Mlym.pl",
"type": "reg",
"size": 630,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21534001,
"NumLink": 0,
"digest": "sha256:4a6c2c6ad8bf6c4815eedeffefcbe97f6bfa8570ece0b155160d3193693b0a88"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Mong.pl",
"type": "reg",
"size": 529,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21534522,
"NumLink": 0,
"digest": "sha256:2173e35e7fc77ae45b7766c3e2b50abf45425d2a0cc3581983ba156b45812dfc"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Mult.pl",
"type": "reg",
"size": 560,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21534991,
"NumLink": 0,
"digest": "sha256:97324117263a39a06e1a3c1316c87f40c9dc5548ad048e3d3fe2140a0da4a90c"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Mymr.pl",
"type": "reg",
"size": 535,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21535466,
"NumLink": 0,
"digest": "sha256:27c0e0c7503416ad0f75d333d716c53a21deed0f9be28714813824c14d9a7143"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Orya.pl",
"type": "reg",
"size": 650,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21535940,
"NumLink": 0,
"digest": "sha256:b5d8725e96463b4813fae097ae1a729f33fd83b67789294fa460fe28bc4372c5"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Phlp.pl",
"type": "reg",
"size": 535,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21536467,
"NumLink": 0,
"digest": "sha256:ebe6ef500aa24950d10667482969d9aef942d2d56c1f3997ecb3d01b102563ad"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Shrd.pl",
"type": "reg",
"size": 564,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21536932,
"NumLink": 0,
"digest": "sha256:6b80a96df206302d6c2fec2eef8c86b9485263011e895cb0a64150cf58fda411"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Sind.pl",
"type": "reg",
"size": 535,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21537413,
"NumLink": 0,
"digest": "sha256:d6935b15d4f1ed7c247eb259942edfecd2995d987243064580d1164f3b1e0389"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Sinh.pl",
"type": "reg",
"size": 632,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21537879,
"NumLink": 0,
"digest": "sha256:6eb5b894fb05bc0ea822513f50b086a1b6e6165853193d84ed7a5d0fc1602be5"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Syrc.pl",
"type": "reg",
"size": 580,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21538400,
"NumLink": 0,
"digest": "sha256:791c57c89d6787c329cff0b2a2624f2cc8d621fde44eee3af0e081268e496c7b"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Tagb.pl",
"type": "reg",
"size": 529,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21538892,
"NumLink": 0,
"digest": "sha256:b2141832862363d05881b316c18718c45e4d22521e0dcf1db2e477973415e101"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Takr.pl",
"type": "reg",
"size": 535,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21539358,
"NumLink": 0,
"digest": "sha256:271b68e19689772c67e7729a9115197c5e98a59d1183c9532320a8c8078950a4"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Taml.pl",
"type": "reg",
"size": 692,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21539825,
"NumLink": 0,
"digest": "sha256:2ebc5b5308aa6ecd879262949a3b692e2f9b0d28b73fee509d62a06e37b71461"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Telu.pl",
"type": "reg",
"size": 650,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21540367,
"NumLink": 0,
"digest": "sha256:6516bc5e07b64daee53ae3043509486cf12d3b7adef5361351f1307391df09c5"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Thaa.pl",
"type": "reg",
"size": 564,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21540894,
"NumLink": 0,
"digest": "sha256:038d130ed6d6e5a837012ef3b9a9d466de9dbae8f77f099fe4848f3cdba5a63e"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Tirh.pl",
"type": "reg",
"size": 535,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21541377,
"NumLink": 0,
"digest": "sha256:1d5118ade0a294e66ddca138067da39088548bbc12150d960fb2502a1ed82a62"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Yi.pl",
"type": "reg",
"size": 574,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21541844,
"NumLink": 0,
"digest": "sha256:5f568a0611b2ef374c716ae445fc62896602f0d242f2633d83efa7d498d94a9f"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Zinh.pl",
"type": "reg",
"size": 670,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21542334,
"NumLink": 0,
"digest": "sha256:9ad52bb42607c737a802884ed51a2a38899cd1e7651b2b4331bb8424308e71d4"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Scx/Zyyy.pl",
"type": "reg",
"size": 2151,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21542874,
"NumLink": 0,
"digest": "sha256:1c674c07686b646a5b4640746d9b94ab71cf955d9540dfb56d4d4e9395328bb7"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Term/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Term/Y.pl",
"type": "reg",
"size": 1473,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21544059,
"NumLink": 0,
"digest": "sha256:2cdaf79acbaf347b062afee8411fcd64b40e21b710e6f592b65d0d87b7a7e888"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/UIdeo/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/UIdeo/Y.pl",
"type": "reg",
"size": 654,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21544970,
"NumLink": 0,
"digest": "sha256:06089c8caabc43cdd90864cf04dda05623bd78a8ebacbdee14bac4d07f87d604"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Upper/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Upper/Y.pl",
"type": "reg",
"size": 6928,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21545529,
"NumLink": 0,
"digest": "sha256:8f05c1aaeaba2805ad712abc324c676601cf1f876ba5a83df86fb0fd0ca41e9d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/WB/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/WB/EX.pl",
"type": "reg",
"size": 552,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21548821,
"NumLink": 0,
"digest": "sha256:4ea9d698b63a3cb363ed7832498a4badac62ac24a52c7d56784b886c475719b1"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/WB/FO.pl",
"type": "reg",
"size": 680,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21549300,
"NumLink": 0,
"digest": "sha256:7aa80285ec594e5c8ec72caa6b91afba3e6c8ac2d179d9ee74314db19c9cc652"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/WB/HL.pl",
"type": "reg",
"size": 606,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21549850,
"NumLink": 0,
"digest": "sha256:dc84621340949f46330e6a3a5d3a25c9493f26f5ba236225db60d1ad65552aff"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/WB/KA.pl",
"type": "reg",
"size": 600,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21550349,
"NumLink": 0,
"digest": "sha256:5e4bc630ef471f37941ac7687982495aa16ca408cd6d4040652451b14eb94e3d"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/WB/LE.pl",
"type": "reg",
"size": 5957,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21550853,
"NumLink": 0,
"digest": "sha256:04a789e6d81dde8c6dde9f7ad82819bda5dd4bb9cf8bf45ec3fc22594dc8af62"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/WB/MB.pl",
"type": "reg",
"size": 552,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21553584,
"NumLink": 0,
"digest": "sha256:a5cf1c45426be4afe0b186372b415c9be3cbfe6c47bd75aa7d2c74e83d4abaa7"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/WB/ML.pl",
"type": "reg",
"size": 576,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21554059,
"NumLink": 0,
"digest": "sha256:ce95c41d800744b08f2a236e8c8768baa35c1b355f2f1d22ea0f787054a35399"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/WB/MN.pl",
"type": "reg",
"size": 632,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21554553,
"NumLink": 0,
"digest": "sha256:adb85c1d377067046b950aef31786832b791113ce661a4272f437bdfe18f90d9"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/WB/NU.pl",
"type": "reg",
"size": 1041,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21555072,
"NumLink": 0,
"digest": "sha256:667c6073a434ac9ba291c8d74894f8d20bf230ebf24a110ce540c04002297a3e"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/WB/XX.pl",
"type": "reg",
"size": 7978,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21555806,
"NumLink": 0,
"digest": "sha256:958a3765a4043eb2b27a977d845b343ac7c8f23ea83e1c1febc12fcbb46a17ed"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/XIDC/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/XIDC/Y.pl",
"type": "reg",
"size": 7870,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21559416,
"NumLink": 0,
"digest": "sha256:f7c1bda1333c0e2c8d8ee1aff7a4cff506ec10ae2e37ea196899e3f1e50e0753"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/XIDS/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/XIDS/Y.pl",
"type": "reg",
"size": 6814,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21562992,
"NumLink": 0,
"digest": "sha256:bbc41d2ee979acb3350dc9160b0a5e6ddfe135d80e35cd352fb46b30a40a65cb"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/utf8.pm",
"type": "reg",
"size": 342,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21566068,
"NumLink": 0,
"digest": "sha256:f206b889aca0a761b973cb3dc58763c2863f3b9429f47164d1dd57f3d041d088"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/utf8_heavy.pl",
"type": "reg",
"size": 31615,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21566393,
"NumLink": 0,
"digest": "sha256:dd1cea927bfb0cd3ddc6020ee822aac3745aa7723ccfbd4beb1d70a652224a4b"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/vars.pm",
"type": "reg",
"size": 1149,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21574998,
"NumLink": 0,
"digest": "sha256:665260cd8f2ce2ae717e29e87c8f5499b7154ac6f20a4bb8c222ee4456639aa5"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/warnings/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/warnings/register.pm",
"type": "reg",
"size": 488,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21575707,
"NumLink": 0,
"digest": "sha256:962110d3faf3e55749a260d97edaf8a9d31293b0f33d2e7771663a9ef364aa94"
},
{
"name": "usr/lib/x86_64-linux-gnu/perl-base/warnings.pm",
"type": "reg",
"size": 21639,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 21576114,
"NumLink": 0,
"digest": "sha256:6677ba2321f7c3135854ca6f6b0d8bcaaac3fcf72dd182058a03d83705f457d8"
},
{
"name": "usr/local/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 17917,
"gid": 50,
"NumLink": 0
},
{
"name": "usr/local/bin/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 17917,
"gid": 50,
"NumLink": 0
},
{
"name": "usr/local/etc/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 17917,
"gid": 50,
"NumLink": 0
},
{
"name": "usr/local/games/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 17917,
"gid": 50,
"NumLink": 0
},
{
"name": "usr/local/include/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 17917,
"gid": 50,
"NumLink": 0
},
{
"name": "usr/local/lib/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 17917,
"gid": 50,
"NumLink": 0
},
{
"name": "usr/local/man",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "share/man",
"mode": 41471,
"gid": 50,
"NumLink": 0
},
{
"name": "usr/local/sbin/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 17917,
"gid": 50,
"NumLink": 0
},
{
"name": "usr/local/share/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 17917,
"gid": 50,
"NumLink": 0
},
{
"name": "usr/local/share/man/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 17917,
"gid": 50,
"NumLink": 0
},
{
"name": "usr/local/src/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 17917,
"gid": 50,
"NumLink": 0
},
{
"name": "usr/sbin/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/sbin/add-shell",
"type": "reg",
"size": 860,
"modtime": "2017-04-02T17:10:33Z",
"mode": 33261,
"offset": 21580564,
"NumLink": 0,
"digest": "sha256:959e8afe5754f4f09e70e95f3aa952d265e9344b0cb635663aeb4bbf70347bf3"
},
{
"name": "usr/sbin/addgroup",
"type": "symlink",
"modtime": "2016-06-26T22:55:17Z",
"linkName": "adduser",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/sbin/adduser",
"type": "reg",
"size": 34509,
"modtime": "2016-06-26T22:55:16Z",
"mode": 33261,
"offset": 21581154,
"NumLink": 0,
"digest": "sha256:90bcaa8bc2e8a547ec25f521717cfe28c7c8449144622f65b24b373055d97f86"
},
{
"name": "usr/sbin/chgpasswd",
"type": "reg",
"size": 59184,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 21590493,
"NumLink": 0,
"digest": "sha256:a6accd32550d5ef4be45cafb960928c94fc2dd4f4ceba3a1fa0dc2d6a6cec83a"
},
{
"name": "usr/sbin/chpasswd",
"type": "reg",
"size": 51096,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 21614137,
"NumLink": 0,
"digest": "sha256:47c59f3e369c234f699786c6a248c9bbbd3e77059ec9518dfb649e7b7e12de5d"
},
{
"name": "usr/sbin/chroot",
"type": "reg",
"size": 39816,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 21634667,
"NumLink": 0,
"digest": "sha256:503b2a1eef247103e996f554a39cc0993874458acdea6a8a2d8a91abe266dfa5"
},
{
"name": "usr/sbin/cpgr",
"type": "symlink",
"modtime": "2017-05-17T11:59:59Z",
"linkName": "cppw",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/sbin/cppw",
"type": "reg",
"size": 53248,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 21652809,
"NumLink": 0,
"digest": "sha256:ec0206f95e21c88c1df1a18c6a22de7e66df89f82f59630954e427ff521097a8"
},
{
"name": "usr/sbin/delgroup",
"type": "symlink",
"modtime": "2016-06-26T22:55:17Z",
"linkName": "deluser",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/sbin/deluser",
"type": "reg",
"size": 15798,
"modtime": "2016-06-26T22:55:16Z",
"mode": 33261,
"offset": 21673966,
"NumLink": 0,
"digest": "sha256:74c622e5b715f007b903281046a02b42789bd5b5a23ca402141d29551b539507"
},
{
"name": "usr/sbin/dpkg-preconfigure",
"type": "reg",
"size": 3604,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33261,
"offset": 21679333,
"NumLink": 0,
"digest": "sha256:82fa48ad05b55af0aed8fe79455301af57d57bfe76f32902ba3f87189b04f55f"
},
{
"name": "usr/sbin/dpkg-reconfigure",
"type": "reg",
"size": 4335,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33261,
"offset": 21680816,
"NumLink": 0,
"digest": "sha256:bb6b8c55f0054f73f45d65918c59b1b8cc4435d60239c96f0717491b509cf69e"
},
{
"name": "usr/sbin/e2freefrag",
"type": "reg",
"size": 10312,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33261,
"offset": 21682662,
"NumLink": 0,
"digest": "sha256:06851e325b694d1a54e0b706cd3dacbb522791770c1e82ac9bc103df8d66dfc2"
},
{
"name": "usr/sbin/e4crypt",
"type": "reg",
"size": 22600,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33261,
"offset": 21687223,
"NumLink": 0,
"digest": "sha256:352d971ad3389b75059fca8a68c2bba434f5e44625c157e31148e73fd341ad60"
},
{
"name": "usr/sbin/e4defrag",
"type": "reg",
"size": 26616,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33261,
"offset": 21696070,
"NumLink": 0,
"digest": "sha256:9fb3b0dceaef564bf6c0c52de2398b4ae8d93f779a08b4d6d8e0fce3eef89d55"
},
{
"name": "usr/sbin/fdformat",
"type": "reg",
"size": 31400,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 21707697,
"NumLink": 0,
"digest": "sha256:82115367585702c576cb36c3f2a326937624394e67911d62197c54a5098535c1"
},
{
"name": "usr/sbin/filefrag",
"type": "reg",
"size": 14352,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33261,
"offset": 21720393,
"NumLink": 0,
"digest": "sha256:9a1e6e12e7c7bfec60296ab2d770fb407577e5a3f6954938935f0065418b5129"
},
{
"name": "usr/sbin/groupadd",
"type": "reg",
"size": 59248,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 21726797,
"NumLink": 0,
"digest": "sha256:2c74c36be4c3f0ffb3545e0e52e76b50cb52e55a7d30dfa54b56012b285b9310"
},
{
"name": "usr/sbin/groupdel",
"type": "reg",
"size": 54936,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 21750959,
"NumLink": 0,
"digest": "sha256:135dc18f16a06d1f950f6fc14cb52ed45fb447ce8043c360a86e8c7cf294d0e3"
},
{
"name": "usr/sbin/groupmems",
"type": "reg",
"size": 55128,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 21773295,
"NumLink": 0,
"digest": "sha256:9450933cf5092fa7360c8656168f39c00f93a58b78772ba25b6c0166d34d589d"
},
{
"name": "usr/sbin/groupmod",
"type": "reg",
"size": 69856,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 21796121,
"NumLink": 0,
"digest": "sha256:7a0bd57b4fa63cf06c4e2f4c274e2566301cda9345afe5de424bdd4decf2bf57"
},
{
"name": "usr/sbin/grpck",
"type": "reg",
"size": 55064,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 21823831,
"NumLink": 0,
"digest": "sha256:d2e867ecab4765822b1d6d5a432ca7091be3e4392189c684570712dd674f2552"
},
{
"name": "usr/sbin/grpconv",
"type": "reg",
"size": 50840,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 21846506,
"NumLink": 0,
"digest": "sha256:6833fceebf2df346b988f68587c501877df463bbba0f2254f17076af3392ed99"
},
{
"name": "usr/sbin/grpunconv",
"type": "reg",
"size": 50840,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 21866740,
"NumLink": 0,
"digest": "sha256:90b88dc6a4ac3352dbd8a419534c8922439cbb5c85ab6c67197591802aa4b670"
},
{
"name": "usr/sbin/iconvconfig",
"type": "reg",
"size": 23208,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33261,
"offset": 21886856,
"NumLink": 0,
"digest": "sha256:2c27f482761dab9ce4e634c1d3a5995082292c6c9f0eea8eaefd55ad16360ac0"
},
{
"name": "usr/sbin/invoke-rc.d",
"type": "reg",
"size": 18110,
"modtime": "2017-05-02T10:20:21Z",
"mode": 33261,
"offset": 21897773,
"NumLink": 0,
"digest": "sha256:b97967909c85bb209845a7568d6e179ea1859e4761eed87edd864ae71625f79a"
},
{
"name": "usr/sbin/ldattach",
"type": "reg",
"size": 31448,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 21903781,
"NumLink": 0,
"digest": "sha256:e2e130f7119d1991a6b611f8c29d45f4ddf58c630d5fe5cf03833e101f4e5e2d"
},
{
"name": "usr/sbin/mklost+found",
"type": "reg",
"size": 10232,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33261,
"offset": 21916527,
"NumLink": 0,
"digest": "sha256:aaebcc49bc9e8396188d597de70fd9efdd6bdad30400a52796afa2fd61c6cc4a"
},
{
"name": "usr/sbin/newusers",
"type": "reg",
"size": 80312,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 21919174,
"NumLink": 0,
"digest": "sha256:ca6ec91c32fd12432ab9f79eae4d58a79b07dec65e54d5ae0e524c1d7934d76d"
},
{
"name": "usr/sbin/nologin",
"type": "reg",
"size": 6136,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 21949610,
"NumLink": 0,
"digest": "sha256:3d9563812289692aa1cb3d0c7ba193312ecfd98a34421ffdb62ed854d018c73c"
},
{
"name": "usr/sbin/pam-auth-update",
"type": "reg",
"size": 19490,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33261,
"offset": 21951688,
"NumLink": 0,
"digest": "sha256:d0edc70d5d2e46f81be6b1d5647fc54508e23630f9cb139bf6722965f21d64d3"
},
{
"name": "usr/sbin/pam_getenv",
"type": "reg",
"size": 2890,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33261,
"offset": 21958701,
"NumLink": 0,
"digest": "sha256:982cca7d6a9afe07d7ba3fc929082ef11ed1cadb88d253f6464bfc0a19730674"
},
{
"name": "usr/sbin/pam_timestamp_check",
"type": "reg",
"size": 10616,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33261,
"offset": 21960193,
"NumLink": 0,
"digest": "sha256:3698a30b91c6362501b703f24664dd367b8c73a9d25a60825fcfbe29a3992577"
},
{
"name": "usr/sbin/policy-rc.d",
"type": "reg",
"size": 255,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33261,
"offset": 21964501,
"NumLink": 0,
"digest": "sha256:21a8dca0d6a52cbac52cb7a86178d973616ffb42ea08747e6ff311659ab095e1"
},
{
"name": "usr/sbin/pwck",
"type": "reg",
"size": 51032,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 21964776,
"NumLink": 0,
"digest": "sha256:3db87b030c79facd0e66f3fce64416820580d9509c6bb4c822152027c8ff427d"
},
{
"name": "usr/sbin/pwconv",
"type": "reg",
"size": 46840,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 21984047,
"NumLink": 0,
"digest": "sha256:ad2df81e018de0becd84b6ba604135696e0ef9b55ef8b4c45fda13474674ce49"
},
{
"name": "usr/sbin/pwunconv",
"type": "reg",
"size": 42720,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 22001336,
"NumLink": 0,
"digest": "sha256:3927d6643798b70627213deb68041a1176aa6bc001f2c35dd64e96215c5e6a37"
},
{
"name": "usr/sbin/readprofile",
"type": "reg",
"size": 19032,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 22018269,
"NumLink": 0,
"digest": "sha256:53baf68d38dbc992407f89e6c4bfbfe40d70e2e322e32737c67a2d0da1c158f5"
},
{
"name": "usr/sbin/remove-shell",
"type": "reg",
"size": 904,
"modtime": "2017-04-02T17:10:33Z",
"mode": 33261,
"offset": 22025169,
"NumLink": 0,
"digest": "sha256:5a15986f8fd235bccf215832a5c39070acd6c48176c73a2415a7148bed327267"
},
{
"name": "usr/sbin/rmt",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "/etc/alternatives/rmt",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/sbin/rmt-tar",
"type": "reg",
"size": 56352,
"modtime": "2016-10-30T06:35:31Z",
"mode": 33261,
"offset": 22025793,
"NumLink": 0,
"digest": "sha256:3ba1fec0831eefd0ddc86525288b27c0c0780322861d95a36b6f558c237290c4"
},
{
"name": "usr/sbin/rtcwake",
"type": "reg",
"size": 43840,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 22054312,
"NumLink": 0,
"digest": "sha256:5bcd24a767249c4585697dcd3d37219b42a9a7fda402df5d18bfa436c01cea8c"
},
{
"name": "usr/sbin/service",
"type": "reg",
"size": 10061,
"modtime": "2017-05-02T10:20:21Z",
"mode": 33261,
"offset": 22073569,
"NumLink": 0,
"digest": "sha256:2727d140345fbba81c6c842d54822f2f8ee78e4c57a9b488f5ce0ff7c572e6bb"
},
{
"name": "usr/sbin/tarcat",
"type": "reg",
"size": 936,
"modtime": "2016-10-30T06:35:31Z",
"mode": 33261,
"offset": 22076719,
"NumLink": 0,
"digest": "sha256:4307aa7cc97a4db32a674ad32f893b251188903cafa6d5266c813fc5c9ea755e"
},
{
"name": "usr/sbin/tunelp",
"type": "reg",
"size": 27248,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 22077335,
"NumLink": 0,
"digest": "sha256:ddcafb165a85a34431f1d2989e6405f80f6df3baa9d69fceb980962e6c22fdb4"
},
{
"name": "usr/sbin/tzconfig",
"type": "reg",
"size": 106,
"modtime": "2016-01-21T17:04:38Z",
"mode": 33261,
"offset": 22088271,
"NumLink": 0,
"digest": "sha256:816e1759cc6a017900439acc6c85dd2293cc78948d54bf76c1f23bf16437b1fc"
},
{
"name": "usr/sbin/update-passwd",
"type": "reg",
"size": 31136,
"modtime": "2017-01-16T15:52:12Z",
"mode": 33261,
"offset": 22088466,
"NumLink": 0,
"digest": "sha256:69f054530b4a1631c477d3b1096046836ac257cd9a1f8ecbdd279229cf7dd3a3"
},
{
"name": "usr/sbin/update-rc.d",
"type": "reg",
"size": 16062,
"modtime": "2017-05-02T10:20:21Z",
"mode": 33261,
"offset": 22101198,
"NumLink": 0,
"digest": "sha256:7334018b606ce73b6266b4f073638f706df24ef4f1dcbebe53bb48bbb81e322e"
},
{
"name": "usr/sbin/useradd",
"type": "reg",
"size": 122152,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 22105960,
"NumLink": 0,
"digest": "sha256:1dec582729d5dad31c8b797f8a5b3625a5e84a9f6c0a33149458ff9372066853"
},
{
"name": "usr/sbin/userdel",
"type": "reg",
"size": 84472,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 22155067,
"NumLink": 0,
"digest": "sha256:5b9097e787567d9ffac2bb5e4c5e4b151c377acea6d2718751ad52e97e652131"
},
{
"name": "usr/sbin/usermod",
"type": "reg",
"size": 121960,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 22188064,
"NumLink": 0,
"digest": "sha256:43260cdabc4a9286c44b87749f5537c6ee6740c9f902fcf93bfafa7b07691fab"
},
{
"name": "usr/sbin/vigr",
"type": "symlink",
"modtime": "2017-05-17T11:59:59Z",
"linkName": "vipw",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/sbin/vipw",
"type": "reg",
"size": 61664,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 22237251,
"NumLink": 0,
"digest": "sha256:4b3632017c35f6bc85a13ffa93094059e6b3919c8b0ce9e6e6083b41f6d0c2c4"
},
{
"name": "usr/sbin/zic",
"type": "reg",
"size": 43560,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33261,
"offset": 22260567,
"NumLink": 0,
"digest": "sha256:f0af0e819802cbc10e6855ec744a891b18e3db25c8b3ed9cfda4e5cfb77ed40e"
},
{
"name": "usr/share/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/adduser/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/adduser/adduser.conf",
"type": "reg",
"size": 2981,
"modtime": "2016-06-26T20:20:46Z",
"mode": 33188,
"offset": 22283213,
"NumLink": 0,
"digest": "sha256:ccdc9675d7bd39c3cc79c2a5d6938f2562dd4062350dbed3058c1faee48b353e"
},
{
"name": "usr/share/base-files/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/base-files/dot.bashrc",
"type": "reg",
"size": 570,
"modtime": "2010-01-31T11:52:26Z",
"mode": 33188,
"offset": 22284711,
"NumLink": 0,
"digest": "sha256:41f1685d04031d88891dea1cd02d5154f8aa841119001a72017b0e7158159e23"
},
{
"name": "usr/share/base-files/dot.profile",
"type": "reg",
"size": 148,
"modtime": "2015-08-17T15:30:33Z",
"mode": 33188,
"offset": 22285167,
"NumLink": 0,
"digest": "sha256:74bc92bcf960bfb62b22aa65370cdd1cd37739baa4eab9b240d72692c898ef1f"
},
{
"name": "usr/share/base-files/dot.profile.md5sums",
"type": "reg",
"size": 72,
"modtime": "2015-08-17T15:32:01Z",
"mode": 33188,
"offset": 22285395,
"NumLink": 0,
"digest": "sha256:8961ee041c712c735fb05287740ab62737777bd58ce631b54b07d8083efad3bf"
},
{
"name": "usr/share/base-files/info.dir",
"type": "reg",
"size": 781,
"modtime": "2018-06-26T12:03:08Z",
"mode": 33188,
"offset": 22285559,
"NumLink": 0,
"digest": "sha256:c58a258cb9c410c29486aa8fa37f4e5b738bfeedc2b8e97be1cd6cff1df28459"
},
{
"name": "usr/share/base-files/motd",
"type": "reg",
"size": 286,
"modtime": "2018-06-26T12:03:08Z",
"mode": 33188,
"offset": 22286147,
"NumLink": 0,
"digest": "sha256:a378977155fb42bb006496321cbe31f74cbda803c3f6ca590f30e76d1afad921"
},
{
"name": "usr/share/base-files/profile",
"type": "reg",
"size": 767,
"modtime": "2016-03-04T11:00:00Z",
"mode": 33188,
"offset": 22286448,
"NumLink": 0,
"digest": "sha256:b8ffd2c97588047e1cea84b7dfdb68bfde167e2957f667ca2b6ab2929feb4f49"
},
{
"name": "usr/share/base-files/profile.md5sums",
"type": "reg",
"size": 607,
"modtime": "2016-03-04T11:00:00Z",
"mode": 33188,
"offset": 22286904,
"NumLink": 0,
"digest": "sha256:7d09366be1eb6bd4fa6b3cf9837e87236bbb9ec5ebe4e2428fbec3f88b7ec762"
},
{
"name": "usr/share/base-files/staff-group-for-usr-local",
"type": "reg",
"size": 771,
"modtime": "2012-06-09T10:00:00Z",
"mode": 33188,
"offset": 22287395,
"NumLink": 0,
"digest": "sha256:64a482506f00572df1d4909a347d6f4fa8e6ce23686b7f058bfbd650ec0658ce"
},
{
"name": "usr/share/base-passwd/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/base-passwd/group.master",
"type": "reg",
"size": 446,
"modtime": "2017-01-16T15:52:12Z",
"mode": 33188,
"offset": 22287950,
"NumLink": 0,
"digest": "sha256:cfde4574c857edbfbd31667000d75d07efe6bc61f22063693010dee2a912450b"
},
{
"name": "usr/share/base-passwd/passwd.master",
"type": "reg",
"size": 877,
"modtime": "2017-01-16T15:52:12Z",
"mode": 33188,
"offset": 22288319,
"NumLink": 0,
"digest": "sha256:5e66dea2e22fa69f64b4f92053bedbf8ea2550dffb95967994730b718f4eb930"
},
{
"name": "usr/share/bash-completion/",
"type": "dir",
"modtime": "2017-05-21T17:08:30Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/bash-completion/completions/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/bash-completion/completions/addpart",
"type": "reg",
"size": 447,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22288823,
"NumLink": 0,
"digest": "sha256:61badc8851eb6f1c153df1a07c9c2f3bffa048fbd05d1ef775384087440a08c8"
},
{
"name": "usr/share/bash-completion/completions/apt",
"type": "reg",
"size": 7034,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33188,
"offset": 22289190,
"NumLink": 0,
"digest": "sha256:df95a61a8b6c5e4e779919c37fb600030ea04215e4aa82e510fbd7b6b3bf55ed"
},
{
"name": "usr/share/bash-completion/completions/blkdiscard",
"type": "reg",
"size": 571,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22290932,
"NumLink": 0,
"digest": "sha256:42afb5f346bd7fbb967529a9fbb92f80207d9757c9e718254e0ebf6dd6e048d1"
},
{
"name": "usr/share/bash-completion/completions/blkid",
"type": "reg",
"size": 1395,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22291326,
"NumLink": 0,
"digest": "sha256:e2d3fa59a351e8af1582816ace0670c72b4088caf60b6dd983cc37c3439c93b1"
},
{
"name": "usr/share/bash-completion/completions/blockdev",
"type": "reg",
"size": 726,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22291897,
"NumLink": 0,
"digest": "sha256:0e33e84094c213cca2fc2e3ea06efc4a655ef4bb27ae4126943c574b98709767"
},
{
"name": "usr/share/bash-completion/completions/cfdisk",
"type": "reg",
"size": 546,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22292352,
"NumLink": 0,
"digest": "sha256:2995b77066141f0a0b67dec82d554a20909466eca6a3e8f237eb709b7f5b4c62"
},
{
"name": "usr/share/bash-completion/completions/chcpu",
"type": "reg",
"size": 1510,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22292738,
"NumLink": 0,
"digest": "sha256:a33736eafd741e7929ab8c13d1484f19a0874fb5a220dac8c6e4e7d3b2dc6458"
},
{
"name": "usr/share/bash-completion/completions/chrt",
"type": "reg",
"size": 920,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22293361,
"NumLink": 0,
"digest": "sha256:1dd13080d71c8d880e628eee89e8f493c979441692ef364e01ca8c8a761d381e"
},
{
"name": "usr/share/bash-completion/completions/ctrlaltdel",
"type": "reg",
"size": 335,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22293872,
"NumLink": 0,
"digest": "sha256:52021091a5554e9b6275cdabbf1820ccd18eff38d8b0094284ef7fb68c38f66f"
},
{
"name": "usr/share/bash-completion/completions/debconf",
"type": "reg",
"size": 294,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22294193,
"NumLink": 0,
"digest": "sha256:45a6978806b39111a579c0e7d4e85937bd6c8e20c3f6732d3ecf5fbd2344cfb0"
},
{
"name": "usr/share/bash-completion/completions/debconf-show",
"type": "symlink",
"modtime": "2017-05-21T17:08:30Z",
"linkName": "debconf",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/bash-completion/completions/delpart",
"type": "reg",
"size": 526,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22294515,
"NumLink": 0,
"digest": "sha256:e337b3898cacda9485085c522d9306a043146cc52c780bbcf2c65b8d366f095a"
},
{
"name": "usr/share/bash-completion/completions/dmesg",
"type": "reg",
"size": 1056,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22294910,
"NumLink": 0,
"digest": "sha256:4084df6fbe19ec0f23ab8793c95a03ef7e48b22b663012bb48ca94128ef3c62b"
},
{
"name": "usr/share/bash-completion/completions/fallocate",
"type": "reg",
"size": 643,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22295482,
"NumLink": 0,
"digest": "sha256:7113bc40db42905fd04f5d0d2ee1cc5bbb2646bffad8c02d4499f57809339888"
},
{
"name": "usr/share/bash-completion/completions/fdformat",
"type": "reg",
"size": 414,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22295914,
"NumLink": 0,
"digest": "sha256:fc16d071c7e14848ac2f86c3456afec6b3231f72acc352177790b90a2ce3350d"
},
{
"name": "usr/share/bash-completion/completions/fdisk",
"type": "reg",
"size": 1231,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22296293,
"NumLink": 0,
"digest": "sha256:6c432ff454d3dded724a4d7cea8e0d4c772bea33ef16fe6ee4599eedde9f2d6f"
},
{
"name": "usr/share/bash-completion/completions/findmnt",
"type": "reg",
"size": 3137,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22296835,
"NumLink": 0,
"digest": "sha256:bf5c1444b07690929d34d98f220de25dfe58c9e4859b902b698fc7525ebccd86"
},
{
"name": "usr/share/bash-completion/completions/flock",
"type": "reg",
"size": 860,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22298159,
"NumLink": 0,
"digest": "sha256:34ace7c62e5f9dfff7729a2b7182f1353b7223203c86068f61da24723b758c9b"
},
{
"name": "usr/share/bash-completion/completions/fsck",
"type": "reg",
"size": 752,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22298639,
"NumLink": 0,
"digest": "sha256:f1a807ffd9e57c34d769a425080836eefb882a060e4c3571db2f8596d6ccdb06"
},
{
"name": "usr/share/bash-completion/completions/fsck.cramfs",
"type": "reg",
"size": 607,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22299063,
"NumLink": 0,
"digest": "sha256:5c79d1606f356dd70e7d82179956c29451cbfc85ce80f04b60e72ed6d2c781b7"
},
{
"name": "usr/share/bash-completion/completions/fsck.minix",
"type": "reg",
"size": 383,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22299477,
"NumLink": 0,
"digest": "sha256:c80e2696bcb4fb7642418d3aec96f7c2931cdcfdaf245786ee3b6cded99e22fe"
},
{
"name": "usr/share/bash-completion/completions/fsfreeze",
"type": "reg",
"size": 524,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22299829,
"NumLink": 0,
"digest": "sha256:7f99f914f660697f78e57850e4e70c027e73a0aa5074a28bd3a5d25c2564b371"
},
{
"name": "usr/share/bash-completion/completions/fstrim",
"type": "reg",
"size": 677,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22300235,
"NumLink": 0,
"digest": "sha256:9c36b670fa1b23811490e2b47b6576a4b58f38ded511ed4f3757e7b858e3700e"
},
{
"name": "usr/share/bash-completion/completions/getopt",
"type": "reg",
"size": 815,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22300690,
"NumLink": 0,
"digest": "sha256:0ae50ed789c556f2abdabe09362169d385f9facf1bd05c13cf57b3f8e0078a5d"
},
{
"name": "usr/share/bash-completion/completions/hwclock",
"type": "reg",
"size": 937,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22301129,
"NumLink": 0,
"digest": "sha256:a16d3a4bf89dac8b127a08357fa278aa6741b0949f5a163e1509985afcc2191d"
},
{
"name": "usr/share/bash-completion/completions/ionice",
"type": "reg",
"size": 837,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22301634,
"NumLink": 0,
"digest": "sha256:121f9bd6d08e331cd90ddf509b2821e9f025141f0ba30b16f201d304a3b10500"
},
{
"name": "usr/share/bash-completion/completions/ipcmk",
"type": "reg",
"size": 576,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22302117,
"NumLink": 0,
"digest": "sha256:701db74a1133159cf159c9a182a46eb77ecdab618c52e373f432b3924d8e2707"
},
{
"name": "usr/share/bash-completion/completions/ipcrm",
"type": "reg",
"size": 1423,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22302477,
"NumLink": 0,
"digest": "sha256:454731bfb20b7be1e41f5b0704536a549ebf7ee755d64bd9ef882b04ae84173d"
},
{
"name": "usr/share/bash-completion/completions/ipcs",
"type": "reg",
"size": 514,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22302981,
"NumLink": 0,
"digest": "sha256:c8d2fc0706082e013fdec16a7b7fcc3aadbc0c3e22f87d8a818d9aa95f364acf"
},
{
"name": "usr/share/bash-completion/completions/isosize",
"type": "reg",
"size": 529,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22303373,
"NumLink": 0,
"digest": "sha256:0afdd61db90044908eef15ef623eb9e6f4598cdfe581f20b1b812650d961f567"
},
{
"name": "usr/share/bash-completion/completions/last",
"type": "reg",
"size": 737,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22303750,
"NumLink": 0,
"digest": "sha256:bc8f9d4188ca658f681e93797ff614bbe0cfae2386bb860735d00b278bc28ced"
},
{
"name": "usr/share/bash-completion/completions/ldattach",
"type": "reg",
"size": 1263,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22304174,
"NumLink": 0,
"digest": "sha256:363c860122e61a3daa0b76cd8f949a81b8d82e81558f148468b0bb272af450ca"
},
{
"name": "usr/share/bash-completion/completions/logger",
"type": "reg",
"size": 1431,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22304860,
"NumLink": 0,
"digest": "sha256:5f755d0b65c08ea67c0fb32a13c36f104952fb3d52a64ec139814633672c98fd"
},
{
"name": "usr/share/bash-completion/completions/losetup",
"type": "reg",
"size": 1657,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22305543,
"NumLink": 0,
"digest": "sha256:6dc177c2c2677dfb5211371de83298a217f33ef6b724d0250728caa6c6842045"
},
{
"name": "usr/share/bash-completion/completions/lsblk",
"type": "reg",
"size": 1881,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22306352,
"NumLink": 0,
"digest": "sha256:7d8bf9fb21ea6a53a41380105d49afe964c0d00182653ecfeb2ea1044ff442cd"
},
{
"name": "usr/share/bash-completion/completions/lscpu",
"type": "reg",
"size": 985,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22307246,
"NumLink": 0,
"digest": "sha256:3d7d68b009e60e1a8c0e381aacd3e3e920f655742ac733fb42cac35ebd89dd58"
},
{
"name": "usr/share/bash-completion/completions/lsipc",
"type": "reg",
"size": 1301,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22307869,
"NumLink": 0,
"digest": "sha256:dff12d8ed515f2ad0cfbd27bb663e761fb25b3263bc02cfb0a61ddac68d38625"
},
{
"name": "usr/share/bash-completion/completions/lslocks",
"type": "reg",
"size": 1176,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22308627,
"NumLink": 0,
"digest": "sha256:fe9cdb7ee7e64deae4b7638f8fc53c585714430803c49f339128d98be4ee6907"
},
{
"name": "usr/share/bash-completion/completions/lslogins",
"type": "reg",
"size": 1704,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22309333,
"NumLink": 0,
"digest": "sha256:713c0e99482efd5139818e6f2a3383db05a6b6f9ae2356ab7f6578e95e3c8eb7"
},
{
"name": "usr/share/bash-completion/completions/lsns",
"type": "reg",
"size": 1160,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22310192,
"NumLink": 0,
"digest": "sha256:e82a2cee42be609becdfda8c1c94d4af59a85bac89741f09eb34fc6248ed7315"
},
{
"name": "usr/share/bash-completion/completions/mcookie",
"type": "reg",
"size": 502,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22310825,
"NumLink": 0,
"digest": "sha256:94c71202101d2267e421343ca9f1e70c9884a2462e1441cdc5fd186cf834dc34"
},
{
"name": "usr/share/bash-completion/completions/mesg",
"type": "reg",
"size": 412,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22311198,
"NumLink": 0,
"digest": "sha256:67d09288e327f405fa72009d7e85b81a70d20c5577ffb8b418b6b0de043e7fc1"
},
{
"name": "usr/share/bash-completion/completions/mkfs",
"type": "reg",
"size": 638,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22311531,
"NumLink": 0,
"digest": "sha256:b90a36595a7585f33e27d5028c66fecb8db2f01832240b70527aa93f9d93c486"
},
{
"name": "usr/share/bash-completion/completions/mkfs.bfs",
"type": "reg",
"size": 656,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22311974,
"NumLink": 0,
"digest": "sha256:fd5315672633d2cc96166fedd130416e7ec9c37a87f8afe57dc906059a4fac04"
},
{
"name": "usr/share/bash-completion/completions/mkfs.cramfs",
"type": "reg",
"size": 821,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22312383,
"NumLink": 0,
"digest": "sha256:a33449ad64a9c1e51ad0b82fc6afbb07febd5650842fea214231f399d7f3bf4d"
},
{
"name": "usr/share/bash-completion/completions/mkfs.minix",
"type": "reg",
"size": 714,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22312807,
"NumLink": 0,
"digest": "sha256:aa873021ae3d172211a649626f466efd53423970ba15d29d6f5ce4fc5d78fcc9"
},
{
"name": "usr/share/bash-completion/completions/mkswap",
"type": "reg",
"size": 765,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22313230,
"NumLink": 0,
"digest": "sha256:55df38a11a3a5c062176af86f5218daa930a8d5883d5c31ae2062ac8da756e68"
},
{
"name": "usr/share/bash-completion/completions/more",
"type": "reg",
"size": 528,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22313662,
"NumLink": 0,
"digest": "sha256:f2a14123adff1db7144fa503b2e939d9383055e7b893a7730e146dd93c328032"
},
{
"name": "usr/share/bash-completion/completions/mount",
"type": "reg",
"size": 1908,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22314034,
"NumLink": 0,
"digest": "sha256:ed4e4eb18ffde93e069890cf183aa5d9682a4f6253db5998267196b6375b3ac6"
},
{
"name": "usr/share/bash-completion/completions/mountpoint",
"type": "reg",
"size": 570,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22314884,
"NumLink": 0,
"digest": "sha256:d882adc45a617a1d4f9f68586dc2c0e91f50045b9b864fed66af94156fed8eba"
},
{
"name": "usr/share/bash-completion/completions/namei",
"type": "reg",
"size": 500,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22315289,
"NumLink": 0,
"digest": "sha256:1519a1b96f840f476647daa9d041a7d5db2dde0d80d3c99e973b1eccff8b259d"
},
{
"name": "usr/share/bash-completion/completions/nsenter",
"type": "reg",
"size": 955,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22315681,
"NumLink": 0,
"digest": "sha256:70f6b574d524a5e9c5453d43c3e0288fc82c7434b9183ae2474eb69e4b1299a3"
},
{
"name": "usr/share/bash-completion/completions/partx",
"type": "reg",
"size": 1157,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22316277,
"NumLink": 0,
"digest": "sha256:ad72aff002951a0534dc0eb986fe83ee4aaf2a439e34bdf14838654ed942d5b6"
},
{
"name": "usr/share/bash-completion/completions/pg",
"type": "reg",
"size": 605,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22316980,
"NumLink": 0,
"digest": "sha256:5dc94774a322d4d6152cf69d869cc70db29a802108675539d658580242715e09"
},
{
"name": "usr/share/bash-completion/completions/pivot_root",
"type": "reg",
"size": 387,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22317376,
"NumLink": 0,
"digest": "sha256:e3fd2fed08fe53b1bdf0b344633375273ce54bdb75c65a4383f2bf29aa9868dd"
},
{
"name": "usr/share/bash-completion/completions/prlimit",
"type": "reg",
"size": 1351,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22317717,
"NumLink": 0,
"digest": "sha256:6e552891869727f1249d3295811069d92dfb23d1cbfe25393b7684938f016018"
},
{
"name": "usr/share/bash-completion/completions/raw",
"type": "reg",
"size": 482,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22318469,
"NumLink": 0,
"digest": "sha256:f546700af22030dffeefaa6b13e9ebb7c24538adb0941dcb98a9d68a6426640f"
},
{
"name": "usr/share/bash-completion/completions/readprofile",
"type": "reg",
"size": 679,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22318858,
"NumLink": 0,
"digest": "sha256:874cf09268bc51c0dd77267ef8e3d9948ff9c8c376a1b9ce911ca135b7baf9a5"
},
{
"name": "usr/share/bash-completion/completions/renice",
"type": "reg",
"size": 812,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22319295,
"NumLink": 0,
"digest": "sha256:631231b93ab12774133788a62650ce3d818ba80b56dc7632e6b7940a565f02b7"
},
{
"name": "usr/share/bash-completion/completions/resizepart",
"type": "reg",
"size": 568,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22319764,
"NumLink": 0,
"digest": "sha256:e0692d25787a3625816b07ea00ef66cfeada23fff0016cf0a37efd95ad84b0d5"
},
{
"name": "usr/share/bash-completion/completions/rev",
"type": "reg",
"size": 432,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22320171,
"NumLink": 0,
"digest": "sha256:ffab4735212694543267952b527e72f3ee4ac9b0e07d49b432db219bd26a3aae"
},
{
"name": "usr/share/bash-completion/completions/rtcwake",
"type": "reg",
"size": 921,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22320527,
"NumLink": 0,
"digest": "sha256:c3dcadb79aa4b54a8c09721acae529ad6d264c794c64068d9f0e279039267f9c"
},
{
"name": "usr/share/bash-completion/completions/runuser",
"type": "reg",
"size": 864,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22321008,
"NumLink": 0,
"digest": "sha256:cfd2d8d2f8c11b25c9c9810aeb1ce2817c17e252a8d0f1e9ee0502cf24008e75"
},
{
"name": "usr/share/bash-completion/completions/script",
"type": "reg",
"size": 667,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22321475,
"NumLink": 0,
"digest": "sha256:4939f89fc4e6fb5577b1803562c103d8ee43648f1f87a938c6c79a67e5850492"
},
{
"name": "usr/share/bash-completion/completions/scriptreplay",
"type": "reg",
"size": 592,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22321919,
"NumLink": 0,
"digest": "sha256:147d35e1d56387599071cb2e27eea8c345433948a478285dac280878b41a2d9d"
},
{
"name": "usr/share/bash-completion/completions/setarch",
"type": "reg",
"size": 770,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22322327,
"NumLink": 0,
"digest": "sha256:9d0a6caf10559e1c1ebec9ad0a34f80711e51da451ab67deec521e46373af12f"
},
{
"name": "usr/share/bash-completion/completions/setsid",
"type": "reg",
"size": 433,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22322832,
"NumLink": 0,
"digest": "sha256:3bcd86d1051046fb0f9c122e0db037e0688dd2cf08f74d8b803cdb4ad2cd7d08"
},
{
"name": "usr/share/bash-completion/completions/setterm",
"type": "reg",
"size": 2580,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22323182,
"NumLink": 0,
"digest": "sha256:05b03c3d0766d7fb55d9b5c9bdc1dd84b837fbdd4739f1d78b4e910375e9f92d"
},
{
"name": "usr/share/bash-completion/completions/sfdisk",
"type": "reg",
"size": 1168,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22324128,
"NumLink": 0,
"digest": "sha256:84c05320ee42af8c98466b2a6482a05d5a393a7b89915984c88e0c311cde8451"
},
{
"name": "usr/share/bash-completion/completions/swaplabel",
"type": "reg",
"size": 635,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22324730,
"NumLink": 0,
"digest": "sha256:1805b9fa1953570fa4bb99339afbb25a6d701ce5a442d22b8ddabe486b46c9c9"
},
{
"name": "usr/share/bash-completion/completions/swapoff",
"type": "reg",
"size": 743,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22325132,
"NumLink": 0,
"digest": "sha256:e84478bfbcfb4eb0accf290e7b158085cb0db7f679afade1a270f7e1e731a691"
},
{
"name": "usr/share/bash-completion/completions/swapon",
"type": "reg",
"size": 1523,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22325552,
"NumLink": 0,
"digest": "sha256:9eb769c80ce8bcffeb27b1b11129ee23e1ea534892df855357ce988862d30ca8"
},
{
"name": "usr/share/bash-completion/completions/tailf",
"type": "reg",
"size": 530,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22326307,
"NumLink": 0,
"digest": "sha256:217ff91e243a27ff18c0ce71505c35da7429950e5df9c8bda810e3cbd54e1395"
},
{
"name": "usr/share/bash-completion/completions/taskset",
"type": "reg",
"size": 1235,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22326689,
"NumLink": 0,
"digest": "sha256:f5d55cf1e4460be7e1121b9a7db533591994a9cb5138124903c19be05574be16"
},
{
"name": "usr/share/bash-completion/completions/tunelp",
"type": "reg",
"size": 1040,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22327418,
"NumLink": 0,
"digest": "sha256:249c5bbde4c34e5345986ddbad444ec4491a55eebb31135ea739fd750e7a8c58"
},
{
"name": "usr/share/bash-completion/completions/umount",
"type": "reg",
"size": 1469,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22327943,
"NumLink": 0,
"digest": "sha256:49047c62c6d9536c04be599640f37b81cf658dc6849180991e757a6c849cdbec"
},
{
"name": "usr/share/bash-completion/completions/unshare",
"type": "reg",
"size": 496,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22328723,
"NumLink": 0,
"digest": "sha256:bfd6b74257cd2802fc0b2fa02e890145870eb2dd83a47e929fd52d7a70f8b520"
},
{
"name": "usr/share/bash-completion/completions/utmpdump",
"type": "reg",
"size": 475,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22329092,
"NumLink": 0,
"digest": "sha256:69a8a5a630ce32790499b7690d033c7d73c40c861a5985ca23c4f1585fd69b48"
},
{
"name": "usr/share/bash-completion/completions/wall",
"type": "reg",
"size": 543,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22329467,
"NumLink": 0,
"digest": "sha256:4edebafffadbeefffad804423daf697de0ff2439b903fb203a1e9570169e1100"
},
{
"name": "usr/share/bash-completion/completions/wdctl",
"type": "reg",
"size": 1365,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22329859,
"NumLink": 0,
"digest": "sha256:6cc49f54ef1974fad909f827694f7bdc42e4ff9aed20bbd4dad265473f9e49db"
},
{
"name": "usr/share/bash-completion/completions/whereis",
"type": "reg",
"size": 535,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22330604,
"NumLink": 0,
"digest": "sha256:2a040afc44337e73ffcb2b910180aacf09566b784f887e82255a09cc42689d50"
},
{
"name": "usr/share/bash-completion/completions/wipefs",
"type": "reg",
"size": 690,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22330999,
"NumLink": 0,
"digest": "sha256:5479d455af2977d9d5bc9dc219e21c376abc63781eaaf6c0a5336bea82648f2f"
},
{
"name": "usr/share/bash-completion/completions/zramctl",
"type": "reg",
"size": 1213,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22331432,
"NumLink": 0,
"digest": "sha256:a8ca02df882804e93da5cc93f24d2125637d09e49abfcc850760569a0db31768"
},
{
"name": "usr/share/bug/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/bug/apt/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/bug/apt/script",
"type": "reg",
"size": 886,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 22332138,
"NumLink": 0,
"digest": "sha256:b2cba592c2bd6e4077dd8b732d4895c22e77f555ed51540899b9a7bc908751bb"
},
{
"name": "usr/share/bug/init-system-helpers/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/bug/init-system-helpers/control",
"type": "reg",
"size": 24,
"modtime": "2017-05-02T10:20:21Z",
"mode": 33188,
"offset": 22332689,
"NumLink": 0,
"digest": "sha256:c0306308b3e8655628cca8238616b8750ab7003201bdd68937a4739fe087ce5c"
},
{
"name": "usr/share/common-licenses/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/common-licenses/Apache-2.0",
"type": "reg",
"size": 11358,
"modtime": "2004-12-19T20:30:25Z",
"mode": 33188,
"offset": 22332862,
"NumLink": 0,
"digest": "sha256:cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30"
},
{
"name": "usr/share/common-licenses/Artistic",
"type": "reg",
"size": 6111,
"modtime": "1996-12-16T02:58:50Z",
"mode": 33188,
"offset": 22336884,
"NumLink": 0,
"digest": "sha256:b7fd9b73ea99602016a326e0b62e6646060d18febdd065ceca8bb482208c3d88"
},
{
"name": "usr/share/common-licenses/BSD",
"type": "reg",
"size": 1499,
"modtime": "1999-08-26T12:06:20Z",
"mode": 33188,
"offset": 22339375,
"NumLink": 0,
"digest": "sha256:5d588eb3b157d52112afea935c88a7ff9efddc1e2d95a42c25d3b96ad9055008"
},
{
"name": "usr/share/common-licenses/GFDL",
"type": "symlink",
"modtime": "2018-06-26T12:03:08Z",
"linkName": "GFDL-1.3",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/common-licenses/GFDL-1.2",
"type": "reg",
"size": 20431,
"modtime": "2010-03-23T23:34:05Z",
"mode": 33188,
"offset": 22340330,
"NumLink": 0,
"digest": "sha256:56976e64523fa1e68db4e6f464f5b2cb89d7d08f54b1d012e317b8db286b3faf"
},
{
"name": "usr/share/common-licenses/GFDL-1.3",
"type": "reg",
"size": 22962,
"modtime": "2008-11-03T16:47:07Z",
"mode": 33188,
"offset": 22347574,
"NumLink": 0,
"digest": "sha256:4748f03ed2dbcc14cde6ebc30799899c403e356a7465dc30fcf2b80c45fc0059"
},
{
"name": "usr/share/common-licenses/GPL",
"type": "symlink",
"modtime": "2018-06-26T12:03:08Z",
"linkName": "GPL-3",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/common-licenses/GPL-1",
"type": "reg",
"size": 12632,
"modtime": "2010-03-23T23:34:05Z",
"mode": 33188,
"offset": 22355724,
"NumLink": 0,
"digest": "sha256:d77d235e41d54594865151f4751e835c5a82322b0e87ace266567c3391a4b912"
},
{
"name": "usr/share/common-licenses/GPL-2",
"type": "reg",
"size": 18092,
"modtime": "2010-03-23T23:34:05Z",
"mode": 33188,
"offset": 22360744,
"NumLink": 0,
"digest": "sha256:8177f97513213526df2cf6184d8ff986c675afb514d4e68a404010521b880643"
},
{
"name": "usr/share/common-licenses/GPL-3",
"type": "reg",
"size": 35147,
"modtime": "2007-07-01T22:55:35Z",
"mode": 33188,
"offset": 22367652,
"NumLink": 0,
"digest": "sha256:8ceb4b9ee5adedde47b31e975c1d90c73ad27b6b165a1dcd80c7c545eb65b903"
},
{
"name": "usr/share/common-licenses/LGPL",
"type": "symlink",
"modtime": "2018-06-26T12:03:08Z",
"linkName": "LGPL-3",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/common-licenses/LGPL-2",
"type": "reg",
"size": 25383,
"modtime": "2010-03-25T17:29:55Z",
"mode": 33188,
"offset": 22379891,
"NumLink": 0,
"digest": "sha256:b7993225104d90ddd8024fd838faf300bea5e83d91203eab98e29512acebd69c"
},
{
"name": "usr/share/common-licenses/LGPL-2.1",
"type": "reg",
"size": 26530,
"modtime": "2010-03-23T23:34:05Z",
"mode": 33188,
"offset": 22388991,
"NumLink": 0,
"digest": "sha256:dc626520dcd53a22f727af3ee42c770e56c97a64fe3adb063799d8ab032fe551"
},
{
"name": "usr/share/common-licenses/LGPL-3",
"type": "reg",
"size": 7651,
"modtime": "2010-03-23T23:34:01Z",
"mode": 33188,
"offset": 22398425,
"NumLink": 0,
"digest": "sha256:da7eabb7bafdf7d3ae5e9f223aa5bdc1eece45ac569dc21b3b037520b4464768"
},
{
"name": "usr/share/common-licenses/MPL-1.1",
"type": "reg",
"size": 25755,
"modtime": "2017-04-03T11:00:00Z",
"mode": 33188,
"offset": 22401124,
"NumLink": 0,
"digest": "sha256:f849fc26a7a99981611a3a370e83078deb617d12a45776d6c4cada4d338be469"
},
{
"name": "usr/share/common-licenses/MPL-2.0",
"type": "reg",
"size": 16726,
"modtime": "2017-04-03T20:00:00Z",
"mode": 33188,
"offset": 22409547,
"NumLink": 0,
"digest": "sha256:fab3dd6bdab226f1c08630b1dd917e11fcb4ec5e1e020e2c16f83a0a13863e85"
},
{
"name": "usr/share/debconf/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/debconf/confmodule",
"type": "reg",
"size": 2690,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22414952,
"NumLink": 0,
"digest": "sha256:640ca3222686c4d336a8a0b4721c5e54b0caba1a83e1e3409b137bea478e9a8b"
},
{
"name": "usr/share/debconf/confmodule.sh",
"type": "reg",
"size": 2875,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22416201,
"NumLink": 0,
"digest": "sha256:9bb739cea6c1f88c3282c6743a230acba64ac1d12c40a89daf12fe5f2390cb68"
},
{
"name": "usr/share/debconf/debconf.conf",
"type": "reg",
"size": 414,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22417708,
"NumLink": 0,
"digest": "sha256:2ed2f1e25211c95ae120cecfff540e33a533c03bb2793504c3d692b0b034c2dd"
},
{
"name": "usr/share/debconf/fix_db.pl",
"type": "reg",
"size": 2004,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33261,
"offset": 22417986,
"NumLink": 0,
"digest": "sha256:3023d816728349ffe6647f852722046631f1802737753f75517ebff0461473df"
},
{
"name": "usr/share/debconf/frontend",
"type": "reg",
"size": 2452,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33261,
"offset": 22418850,
"NumLink": 0,
"digest": "sha256:4425ec2e272875006ca7dde26d1c11b79a0a3570ececcd11f65baddd4b474ae0"
},
{
"name": "usr/share/debconf/transition_db.pl",
"type": "reg",
"size": 2404,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33261,
"offset": 22419970,
"NumLink": 0,
"digest": "sha256:9dbd7e5dcf314de4f1c1f15c46c69754f9c61c6be632b7c5a6efee08df0aa798"
},
{
"name": "usr/share/debianutils/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/debianutils/shells",
"type": "reg",
"size": 42,
"modtime": "2011-10-09T00:49:46Z",
"mode": 33188,
"offset": 22420954,
"NumLink": 0,
"digest": "sha256:184fd1ac95af5ad460ee875e9fddb4975e8720ee7d805d964c68d125573537be"
},
{
"name": "usr/share/dict/",
"type": "dir",
"modtime": "2018-06-26T12:03:08Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/adduser/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/adduser/copyright",
"type": "reg",
"size": 1995,
"modtime": "2016-06-26T20:00:56Z",
"mode": 33188,
"offset": 22421163,
"NumLink": 0,
"digest": "sha256:3e67668ed552fe3a0684e77282325d07fa8847f2575ea1e1906000de571b5c1e"
},
{
"name": "usr/share/doc/apt/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/apt/copyright",
"type": "reg",
"size": 1029,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33188,
"offset": 22422270,
"NumLink": 0,
"digest": "sha256:307e96c4b7e8170b422d86cfb04d9ae4a404e6d46755448331cdedb23cf1c3b0"
},
{
"name": "usr/share/doc/base-files/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/base-files/copyright",
"type": "reg",
"size": 1228,
"modtime": "2018-06-26T12:03:08Z",
"mode": 33188,
"offset": 22422986,
"NumLink": 0,
"digest": "sha256:cdb5461d8515002d0fe3babb764eec3877458b20f4e4bb16219f62ea953afeea"
},
{
"name": "usr/share/doc/base-passwd/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/base-passwd/copyright",
"type": "reg",
"size": 799,
"modtime": "2016-02-20T02:44:33Z",
"mode": 33188,
"offset": 22423766,
"NumLink": 0,
"digest": "sha256:c33ce99cd93b1e2f5c6067be725b73099e712afe8a4d8dffeb1659642afcd22d"
},
{
"name": "usr/share/doc/bash/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/bash/copyright",
"type": "reg",
"size": 10231,
"modtime": "2014-03-03T21:46:18Z",
"mode": 33188,
"offset": 22424330,
"NumLink": 0,
"digest": "sha256:da7a8d93abf1eccdeaf326642c8ce9ed760f3a973ca46f3f69b3cf755bb81ade"
},
{
"name": "usr/share/doc/bsdutils/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/bsdutils/copyright",
"type": "reg",
"size": 17975,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22427545,
"NumLink": 0,
"digest": "sha256:b99c6679c1def231db02b5c30e3a552be6840021271f1e5c4cf5b7a639b0c08c"
},
{
"name": "usr/share/doc/coreutils/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/coreutils/copyright",
"type": "reg",
"size": 12531,
"modtime": "2016-01-15T17:31:23Z",
"mode": 33188,
"offset": 22432395,
"NumLink": 0,
"digest": "sha256:350c1a60923248396acdf5aa3d20cdd5156e82648b3411bf9dff3a16b1ce9c7e"
},
{
"name": "usr/share/doc/dash/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/dash/copyright",
"type": "reg",
"size": 3101,
"modtime": "2017-01-24T05:16:56Z",
"mode": 33188,
"offset": 22434945,
"NumLink": 0,
"digest": "sha256:7c77d28679de92b8aca0b3dd400eabac91bf9f6c68171e49355888d2593a968f"
},
{
"name": "usr/share/doc/debconf/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/debconf/copyright",
"type": "reg",
"size": 2648,
"modtime": "2011-02-02T00:08:31Z",
"mode": 33188,
"offset": 22436654,
"NumLink": 0,
"digest": "sha256:5210ee02334b7cadcfe497df11a9d1ebd6e8e7de8a7fa44d15ac797b591eaac5"
},
{
"name": "usr/share/doc/debian-archive-keyring/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/debian-archive-keyring/copyright",
"type": "reg",
"size": 1243,
"modtime": "2006-11-21T15:36:55Z",
"mode": 33188,
"offset": 22437912,
"NumLink": 0,
"digest": "sha256:b32aecaae84643700a33bc9ee83fa9b36938d35aa7b61b5042092eca77ddb732"
},
{
"name": "usr/share/doc/debianutils/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/debianutils/copyright",
"type": "reg",
"size": 8017,
"modtime": "2009-05-06T01:07:50Z",
"mode": 33188,
"offset": 22438695,
"NumLink": 0,
"digest": "sha256:a8698f078cd21fc501e66d070e12cf2f23ec1eaf5841bbc87629de76858ef7a7"
},
{
"name": "usr/share/doc/diffutils/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/diffutils/copyright",
"type": "reg",
"size": 1839,
"modtime": "2017-01-09T21:00:00Z",
"mode": 33188,
"offset": 22442319,
"NumLink": 0,
"digest": "sha256:6555a03a39adb6588f85a8aac4ea9f3f1cfdf93ac921ffbbc806215f77166b07"
},
{
"name": "usr/share/doc/dpkg/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/dpkg/copyright",
"type": "reg",
"size": 7882,
"modtime": "2018-03-22T04:29:34Z",
"mode": 33188,
"offset": 22443295,
"NumLink": 0,
"digest": "sha256:1a87ca75439f4e5be763913b21e9d23476b7e5c37814b9a0500c819e437f9b8f"
},
{
"name": "usr/share/doc/e2fslibs/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/e2fslibs/copyright",
"type": "reg",
"size": 3594,
"modtime": "2016-09-02T04:17:32Z",
"mode": 33188,
"offset": 22446456,
"NumLink": 0,
"digest": "sha256:b7b391571e7253d4cf607e33e3b463895768fad264471e7774882974f834faa1"
},
{
"name": "usr/share/doc/e2fsprogs/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/e2fsprogs/copyright",
"type": "reg",
"size": 3594,
"modtime": "2016-09-02T04:17:32Z",
"mode": 33188,
"offset": 22448282,
"NumLink": 0,
"digest": "sha256:b7b391571e7253d4cf607e33e3b463895768fad264471e7774882974f834faa1"
},
{
"name": "usr/share/doc/findutils/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/findutils/copyright",
"type": "reg",
"size": 3089,
"modtime": "2016-07-04T11:21:09Z",
"mode": 33188,
"offset": 22450112,
"NumLink": 0,
"digest": "sha256:7b056c63abe117298b7b8fd193f96f0a324a87c1f70fa38f5d1c750abf0314fd"
},
{
"name": "usr/share/doc/gcc-6-base/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/gcc-6-base/copyright",
"type": "reg",
"size": 30116,
"modtime": "2018-02-14T16:53:20Z",
"mode": 33188,
"offset": 22451638,
"NumLink": 0,
"digest": "sha256:360fb8c50734b43dea21842b5e32a97e4b0d247530b7250a86e8057af33c1bc6"
},
{
"name": "usr/share/doc/gpgv/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/gpgv/copyright",
"type": "reg",
"size": 9804,
"modtime": "2018-06-08T18:12:24Z",
"mode": 33188,
"offset": 22460146,
"NumLink": 0,
"digest": "sha256:b6003da5877b0d74ed5b82690d2d79129985a9eda068fc8913ba7cbdc0950263"
},
{
"name": "usr/share/doc/grep/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/grep/copyright",
"type": "reg",
"size": 1806,
"modtime": "2016-11-28T21:59:51Z",
"mode": 33188,
"offset": 22463372,
"NumLink": 0,
"digest": "sha256:11638f295e8713bb780508b539e094aa9e82ae3cca8545163be3e64aec65a8e5"
},
{
"name": "usr/share/doc/gzip/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/gzip/copyright",
"type": "reg",
"size": 1112,
"modtime": "2016-03-14T20:41:45Z",
"mode": 33188,
"offset": 22464469,
"NumLink": 0,
"digest": "sha256:f9ac4a5d7a670e3891881a2cdba5fa2cd625c4d58eae4a7aa372ac00a06803bd"
},
{
"name": "usr/share/doc/hostname/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/hostname/copyright",
"type": "reg",
"size": 1146,
"modtime": "2013-09-06T10:04:07Z",
"mode": 33188,
"offset": 22465241,
"NumLink": 0,
"digest": "sha256:272c5b1c9235edc311f202b2a6abfff24ba96a47c779b4db3b97c2e60eb3e81a"
},
{
"name": "usr/share/doc/init-system-helpers/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/init-system-helpers/copyright",
"type": "reg",
"size": 3014,
"modtime": "2017-05-02T10:20:21Z",
"mode": 33188,
"offset": 22465990,
"NumLink": 0,
"digest": "sha256:a3ee9fffdc037227ad9461b61700323bb07e3e4be81d9da5c068ad09232e5fa5"
},
{
"name": "usr/share/doc/libacl1/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libacl1/copyright",
"type": "reg",
"size": 761,
"modtime": "2009-08-25T00:37:49Z",
"mode": 33188,
"offset": 22467575,
"NumLink": 0,
"digest": "sha256:26522b0fba5e435a1afcd0e3482d0aaab245d60f00b3d5b777c31457c740756b"
},
{
"name": "usr/share/doc/libapt-pkg5.0/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libapt-pkg5.0/copyright",
"type": "reg",
"size": 1029,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33188,
"offset": 22468117,
"NumLink": 0,
"digest": "sha256:307e96c4b7e8170b422d86cfb04d9ae4a404e6d46755448331cdedb23cf1c3b0"
},
{
"name": "usr/share/doc/libattr1/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libattr1/copyright",
"type": "reg",
"size": 789,
"modtime": "2009-08-26T01:32:06Z",
"mode": 33188,
"offset": 22468833,
"NumLink": 0,
"digest": "sha256:2c18722769cd1f0494668b8118ba8c9c785be759d34a85d0b1c9dd2d89f87328"
},
{
"name": "usr/share/doc/libaudit-common/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libaudit-common/copyright",
"type": "reg",
"size": 1588,
"modtime": "2017-04-12T16:17:21Z",
"mode": 33188,
"offset": 22469382,
"NumLink": 0,
"digest": "sha256:0c90284161619a0ccd817b467be8b225d5e52ca76ed151073c4d1e8f7fe25dd7"
},
{
"name": "usr/share/doc/libaudit1/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libaudit1/copyright",
"type": "reg",
"size": 1588,
"modtime": "2017-04-12T16:17:21Z",
"mode": 33188,
"offset": 22470319,
"NumLink": 0,
"digest": "sha256:0c90284161619a0ccd817b467be8b225d5e52ca76ed151073c4d1e8f7fe25dd7"
},
{
"name": "usr/share/doc/libblkid1/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libblkid1/copyright",
"type": "reg",
"size": 17975,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22471259,
"NumLink": 0,
"digest": "sha256:b99c6679c1def231db02b5c30e3a552be6840021271f1e5c4cf5b7a639b0c08c"
},
{
"name": "usr/share/doc/libbz2-1.0/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libbz2-1.0/copyright",
"type": "reg",
"size": 2499,
"modtime": "2017-01-29T18:30:31Z",
"mode": 33188,
"offset": 22476110,
"NumLink": 0,
"digest": "sha256:8d2269223e3162f7253432d7790060fe0ce16b73b12700778cde19fccb776fed"
},
{
"name": "usr/share/doc/libc-bin/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libc-bin/copyright",
"type": "reg",
"size": 25465,
"modtime": "2018-01-14T10:39:16Z",
"mode": 33188,
"offset": 22477540,
"NumLink": 0,
"digest": "sha256:a09cfc6ecf8fcd835ba851a1c95b256e01420982ac7acdd01fcfaa748c402f11"
},
{
"name": "usr/share/doc/libc6/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libc6/copyright",
"type": "reg",
"size": 25465,
"modtime": "2018-01-14T10:39:16Z",
"mode": 33188,
"offset": 22483797,
"NumLink": 0,
"digest": "sha256:a09cfc6ecf8fcd835ba851a1c95b256e01420982ac7acdd01fcfaa748c402f11"
},
{
"name": "usr/share/doc/libcap-ng0/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libcap-ng0/copyright",
"type": "reg",
"size": 1407,
"modtime": "2015-03-25T17:03:55Z",
"mode": 33188,
"offset": 22490056,
"NumLink": 0,
"digest": "sha256:8cdc2d0eeeb4ed84963c58be27389b148e830ce5ec42c2598cf194721d0430ec"
},
{
"name": "usr/share/doc/libcomerr2/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libcomerr2/copyright",
"type": "reg",
"size": 1154,
"modtime": "2009-08-13T01:39:57Z",
"mode": 33188,
"offset": 22490926,
"NumLink": 0,
"digest": "sha256:9e3a4384b6d8d2358d44103f62bcd948328b3f8a63a1a6baa66abeb43302d581"
},
{
"name": "usr/share/doc/libdb5.3/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libdb5.3/copyright",
"type": "reg",
"size": 7517,
"modtime": "2017-09-24T07:14:53Z",
"mode": 33188,
"offset": 22491689,
"NumLink": 0,
"digest": "sha256:b3bbc6fbb3f2a0e6a487e953eb8c3cc4bdb6f4150f7f51d20b1e9a3c8ef92d3d"
},
{
"name": "usr/share/doc/libdebconfclient0/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libdebconfclient0/copyright",
"type": "reg",
"size": 1978,
"modtime": "2011-07-05T01:00:24Z",
"mode": 33188,
"offset": 22493616,
"NumLink": 0,
"digest": "sha256:1f48252e8bd71085c67fe1f5468c67da1d2d7cc34cd8a7e9d90b9ad2df080e8e"
},
{
"name": "usr/share/doc/libfdisk1/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libfdisk1/copyright",
"type": "reg",
"size": 17975,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22494822,
"NumLink": 0,
"digest": "sha256:b99c6679c1def231db02b5c30e3a552be6840021271f1e5c4cf5b7a639b0c08c"
},
{
"name": "usr/share/doc/libgcc1",
"type": "symlink",
"modtime": "2018-02-14T16:53:20Z",
"linkName": "gcc-6-base",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/doc/libgcrypt20/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libgcrypt20/copyright",
"type": "reg",
"size": 14374,
"modtime": "2018-06-15T09:58:05Z",
"mode": 33188,
"offset": 22499714,
"NumLink": 0,
"digest": "sha256:f8f5c4ea63d81b94e29754072a6cc2fa30cc1656a9d16e0761946c9a8a594a72"
},
{
"name": "usr/share/doc/libgpg-error0/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libgpg-error0/copyright",
"type": "reg",
"size": 1154,
"modtime": "2017-01-18T16:22:13Z",
"mode": 33188,
"offset": 22505795,
"NumLink": 0,
"digest": "sha256:25806fa5ca76e47d4868beb59064475397ed841f15584dd9782dbd3228ff99db"
},
{
"name": "usr/share/doc/liblz4-1/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/liblz4-1/copyright",
"type": "reg",
"size": 3092,
"modtime": "2016-02-17T15:27:54Z",
"mode": 33188,
"offset": 22506575,
"NumLink": 0,
"digest": "sha256:18abf872210b2e1640d078bd84dc3adc34086f964346d71b6002b80b03860730"
},
{
"name": "usr/share/doc/liblzma5/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/liblzma5/copyright",
"type": "reg",
"size": 15308,
"modtime": "2016-10-08T13:01:46Z",
"mode": 33188,
"offset": 22508084,
"NumLink": 0,
"digest": "sha256:36109804b8e69c5344232598a4f064da4465683b9461662085be7d8fca29b35a"
},
{
"name": "usr/share/doc/libmount1/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libmount1/copyright",
"type": "reg",
"size": 17975,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22513112,
"NumLink": 0,
"digest": "sha256:b99c6679c1def231db02b5c30e3a552be6840021271f1e5c4cf5b7a639b0c08c"
},
{
"name": "usr/share/doc/libncursesw5",
"type": "symlink",
"modtime": "2017-12-28T09:47:33Z",
"linkName": "libtinfo5",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/doc/libpam-modules/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libpam-modules/copyright",
"type": "reg",
"size": 3176,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 22518010,
"NumLink": 0,
"digest": "sha256:7c584b7b1f37b612da7fdf3b076eb2b2c1bae72865f0699d745ae2ac7d40d13e"
},
{
"name": "usr/share/doc/libpam-modules-bin/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libpam-modules-bin/copyright",
"type": "reg",
"size": 3176,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 22519765,
"NumLink": 0,
"digest": "sha256:7c584b7b1f37b612da7fdf3b076eb2b2c1bae72865f0699d745ae2ac7d40d13e"
},
{
"name": "usr/share/doc/libpam-runtime/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libpam-runtime/copyright",
"type": "reg",
"size": 3176,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 22521517,
"NumLink": 0,
"digest": "sha256:7c584b7b1f37b612da7fdf3b076eb2b2c1bae72865f0699d745ae2ac7d40d13e"
},
{
"name": "usr/share/doc/libpam0g/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libpam0g/copyright",
"type": "reg",
"size": 3176,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 22523264,
"NumLink": 0,
"digest": "sha256:7c584b7b1f37b612da7fdf3b076eb2b2c1bae72865f0699d745ae2ac7d40d13e"
},
{
"name": "usr/share/doc/libpcre3/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libpcre3/copyright",
"type": "reg",
"size": 2765,
"modtime": "2015-03-07T18:28:32Z",
"mode": 33188,
"offset": 22525011,
"NumLink": 0,
"digest": "sha256:ac9276490d2fa167442ae1aae33926514ad10c8886baa40046c5e367fccc5938"
},
{
"name": "usr/share/doc/libselinux1/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libselinux1/copyright",
"type": "reg",
"size": 3129,
"modtime": "2016-11-06T17:22:28Z",
"mode": 33188,
"offset": 22526540,
"NumLink": 0,
"digest": "sha256:514ea9dc715249906df09333d9a7583b9251fc7c3c265602b53066e564fdfcdc"
},
{
"name": "usr/share/doc/libsemanage-common/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libsemanage-common/copyright",
"type": "reg",
"size": 1828,
"modtime": "2016-12-30T15:42:09Z",
"mode": 33188,
"offset": 22528038,
"NumLink": 0,
"digest": "sha256:1ca1d72a22e1f1d16c2d805c823723df6b8bc7c1407e5e1e5d2bdca7ff222ae9"
},
{
"name": "usr/share/doc/libsemanage1/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libsemanage1/copyright",
"type": "reg",
"size": 1828,
"modtime": "2016-12-30T15:42:09Z",
"mode": 33188,
"offset": 22529020,
"NumLink": 0,
"digest": "sha256:1ca1d72a22e1f1d16c2d805c823723df6b8bc7c1407e5e1e5d2bdca7ff222ae9"
},
{
"name": "usr/share/doc/libsepol1/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libsepol1/copyright",
"type": "reg",
"size": 2071,
"modtime": "2016-12-03T23:19:56Z",
"mode": 33188,
"offset": 22530004,
"NumLink": 0,
"digest": "sha256:bd7d13a1f53de96562c3240d9189705ebbca107d860e9b811780ef9472a21ce7"
},
{
"name": "usr/share/doc/libsmartcols1/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libsmartcols1/copyright",
"type": "reg",
"size": 17975,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22531126,
"NumLink": 0,
"digest": "sha256:b99c6679c1def231db02b5c30e3a552be6840021271f1e5c4cf5b7a639b0c08c"
},
{
"name": "usr/share/doc/libss2/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libss2/copyright",
"type": "reg",
"size": 1163,
"modtime": "2016-09-02T04:17:32Z",
"mode": 33188,
"offset": 22535973,
"NumLink": 0,
"digest": "sha256:6f717a1464301a641ac17e7301f1a1b221da802e8edf8cf4bc963721d487b146"
},
{
"name": "usr/share/doc/libstdc++6",
"type": "symlink",
"modtime": "2018-02-14T16:53:20Z",
"linkName": "gcc-6-base",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/doc/libsystemd0/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libsystemd0/copyright",
"type": "reg",
"size": 7776,
"modtime": "2018-06-13T20:20:36Z",
"mode": 33188,
"offset": 22536790,
"NumLink": 0,
"digest": "sha256:75f342d2dda6c3e10591b8dbccc4a1d4868f026d27a2afd5ed4565971f533b7f"
},
{
"name": "usr/share/doc/libtinfo5/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libtinfo5/copyright",
"type": "reg",
"size": 6366,
"modtime": "2017-12-28T09:32:23Z",
"mode": 33188,
"offset": 22539591,
"NumLink": 0,
"digest": "sha256:b7606456c4e97c19c54c67c1887a705b4d44e5121c48c851b256aeb65e518a4c"
},
{
"name": "usr/share/doc/libudev1/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libudev1/copyright",
"type": "reg",
"size": 7776,
"modtime": "2018-06-13T20:20:36Z",
"mode": 33188,
"offset": 22541844,
"NumLink": 0,
"digest": "sha256:75f342d2dda6c3e10591b8dbccc4a1d4868f026d27a2afd5ed4565971f533b7f"
},
{
"name": "usr/share/doc/libustr-1.0-1/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libustr-1.0-1/copyright",
"type": "reg",
"size": 5072,
"modtime": "2016-11-17T13:42:55Z",
"mode": 33188,
"offset": 22544645,
"NumLink": 0,
"digest": "sha256:41c7b465fec47217af1aba6e39525ff8e4ed904f22e8a10175a314b11f8a34b8"
},
{
"name": "usr/share/doc/libuuid1/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/libuuid1/copyright",
"type": "reg",
"size": 17975,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22546818,
"NumLink": 0,
"digest": "sha256:b99c6679c1def231db02b5c30e3a552be6840021271f1e5c4cf5b7a639b0c08c"
},
{
"name": "usr/share/doc/login/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/login/copyright",
"type": "reg",
"size": 5153,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33188,
"offset": 22551664,
"NumLink": 0,
"digest": "sha256:56ca9c00cdef4d69b35d2013465c539eb9e09a80fec538d0f6dc91423dbaa1d3"
},
{
"name": "usr/share/doc/lsb-base/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/lsb-base/copyright",
"type": "reg",
"size": 2648,
"modtime": "2014-05-27T14:13:24Z",
"mode": 33188,
"offset": 22554262,
"NumLink": 0,
"digest": "sha256:0e9ed2924c559a5fd2fd5e32f59885baee291b46dcff09c90fbc12c7aeb61c10"
},
{
"name": "usr/share/doc/mawk/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/mawk/copyright",
"type": "reg",
"size": 1298,
"modtime": "2012-03-23T20:15:00Z",
"mode": 33188,
"offset": 22555721,
"NumLink": 0,
"digest": "sha256:80910bdabaf183ae4d3ffd72d9fe9066a9a1035be8e5d7dd541ddc6755d19abb"
},
{
"name": "usr/share/doc/mount/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/mount/copyright",
"type": "reg",
"size": 17975,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22556577,
"NumLink": 0,
"digest": "sha256:b99c6679c1def231db02b5c30e3a552be6840021271f1e5c4cf5b7a639b0c08c"
},
{
"name": "usr/share/doc/multiarch-support/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/multiarch-support/copyright",
"type": "reg",
"size": 25465,
"modtime": "2018-01-14T10:39:16Z",
"mode": 33188,
"offset": 22561433,
"NumLink": 0,
"digest": "sha256:a09cfc6ecf8fcd835ba851a1c95b256e01420982ac7acdd01fcfaa748c402f11"
},
{
"name": "usr/share/doc/ncurses-base/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/ncurses-base/copyright",
"type": "reg",
"size": 6366,
"modtime": "2017-12-28T09:32:23Z",
"mode": 33188,
"offset": 22567694,
"NumLink": 0,
"digest": "sha256:b7606456c4e97c19c54c67c1887a705b4d44e5121c48c851b256aeb65e518a4c"
},
{
"name": "usr/share/doc/ncurses-bin/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/ncurses-bin/copyright",
"type": "reg",
"size": 6366,
"modtime": "2017-12-28T09:32:23Z",
"mode": 33188,
"offset": 22569948,
"NumLink": 0,
"digest": "sha256:b7606456c4e97c19c54c67c1887a705b4d44e5121c48c851b256aeb65e518a4c"
},
{
"name": "usr/share/doc/passwd/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/passwd/copyright",
"type": "reg",
"size": 5153,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33188,
"offset": 22572201,
"NumLink": 0,
"digest": "sha256:56ca9c00cdef4d69b35d2013465c539eb9e09a80fec538d0f6dc91423dbaa1d3"
},
{
"name": "usr/share/doc/perl/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/perl/copyright",
"type": "reg",
"size": 104049,
"modtime": "2018-06-10T17:35:32Z",
"mode": 33188,
"offset": 22574795,
"NumLink": 0,
"digest": "sha256:84302bc1e1c76708a293f348a0b2b9a0eee86eac9cc065bb73b4a28318c6fd9b"
},
{
"name": "usr/share/doc/perl-base",
"type": "symlink",
"modtime": "2018-06-10T17:37:28Z",
"linkName": "perl",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/doc/sed/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/sed/copyright",
"type": "reg",
"size": 849,
"modtime": "2017-01-05T01:31:41Z",
"mode": 33188,
"offset": 22598004,
"NumLink": 0,
"digest": "sha256:60886b6264e9531565ccf1e31a63cd9b23f0bf4bbd7053b46aa9e6ae7622d31d"
},
{
"name": "usr/share/doc/sensible-utils/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/sensible-utils/copyright",
"type": "reg",
"size": 694,
"modtime": "2017-12-20T13:39:04Z",
"mode": 33188,
"offset": 22598598,
"NumLink": 0,
"digest": "sha256:5c58dcf1d8debb43fcec08ae0cbf2f049aa5d48c567147046db48e42c2f706ac"
},
{
"name": "usr/share/doc/sysvinit-utils/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/sysvinit-utils/copyright",
"type": "reg",
"size": 2199,
"modtime": "2017-02-12T21:55:39Z",
"mode": 33188,
"offset": 22599093,
"NumLink": 0,
"digest": "sha256:7778a27498a3909c855d6b3ccec28dc16c67ca5d5aaba6660842cd333ffe188d"
},
{
"name": "usr/share/doc/tar/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/tar/copyright",
"type": "reg",
"size": 1486,
"modtime": "2016-10-30T06:35:31Z",
"mode": 33188,
"offset": 22600344,
"NumLink": 0,
"digest": "sha256:9292780f2dfd11900e92ea67c7a316cf9df740b955956f87ec099a5b4f3d9136"
},
{
"name": "usr/share/doc/tzdata/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/tzdata/copyright",
"type": "reg",
"size": 307,
"modtime": "2018-05-04T18:20:29Z",
"mode": 33188,
"offset": 22601083,
"NumLink": 0,
"digest": "sha256:2b81a12912f0ea7b6c928bc57c950234a13bfeb690611bfd81b5c329b914cd8b"
},
{
"name": "usr/share/doc/util-linux/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/util-linux/copyright",
"type": "reg",
"size": 17975,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 22601449,
"NumLink": 0,
"digest": "sha256:b99c6679c1def231db02b5c30e3a552be6840021271f1e5c4cf5b7a639b0c08c"
},
{
"name": "usr/share/doc/zlib1g/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc/zlib1g/copyright",
"type": "reg",
"size": 1606,
"modtime": "2013-05-03T15:30:15Z",
"mode": 33188,
"offset": 22606295,
"NumLink": 0,
"digest": "sha256:bcf07f8f2414e28405b35fee95fb14569e926a7d4e99cc4a5db2fe1a0d1aca56"
},
{
"name": "usr/share/doc-base/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/doc-base/findutils",
"type": "reg",
"size": 323,
"modtime": "2014-04-13T14:12:32Z",
"mode": 33188,
"offset": 22607266,
"NumLink": 0,
"digest": "sha256:83026123456c2f7c1cc44196701180752a65423ade28344ef277cf577b12faa6"
},
{
"name": "usr/share/doc-base/users-and-groups",
"type": "reg",
"size": 423,
"modtime": "2016-02-20T02:44:33Z",
"mode": 33188,
"offset": 22607568,
"NumLink": 0,
"digest": "sha256:159902a0284dbbcc039824ebab914948f8a803280fa2b67742b8f7fd40c50250"
},
{
"name": "usr/share/dpkg/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/dpkg/abitable",
"type": "reg",
"size": 362,
"modtime": "2018-06-26T10:28:08Z",
"mode": 33188,
"offset": 22607923,
"NumLink": 0,
"digest": "sha256:c9ed655f391a2dffdfee2070e9c4bd1f502ffff17d19abff21ba492ab643c919"
},
{
"name": "usr/share/dpkg/cputable",
"type": "reg",
"size": 1971,
"modtime": "2018-06-26T10:28:08Z",
"mode": 33188,
"offset": 22608231,
"NumLink": 0,
"digest": "sha256:88d770b7e95d793c6250285c4d901365487c44e460a8ebdd4895685f7613d104"
},
{
"name": "usr/share/dpkg/ostable",
"type": "reg",
"size": 2000,
"modtime": "2018-06-26T10:28:08Z",
"mode": 33188,
"offset": 22609080,
"NumLink": 0,
"digest": "sha256:49b8a094236fd9f06463afab94d49956cf6fba363ec14d75da6ae3310214ca42"
},
{
"name": "usr/share/dpkg/tupletable",
"type": "reg",
"size": 1361,
"modtime": "2018-06-26T10:28:08Z",
"mode": 33188,
"offset": 22609851,
"NumLink": 0,
"digest": "sha256:d8311834400bd4c74bcdc764968d13c44b5ac8745a9d06068c2209c51401b826"
},
{
"name": "usr/share/gcc-6/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/gcc-6/python/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/gcc-6/python/libstdcxx/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/gcc-6/python/libstdcxx/__init__.py",
"type": "reg",
"size": 1,
"modtime": "2018-02-14T16:53:20Z",
"mode": 33188,
"offset": 22610429,
"NumLink": 0,
"digest": "sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"
},
{
"name": "usr/share/gcc-6/python/libstdcxx/v6/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/gcc-6/python/libstdcxx/v6/__init__.py",
"type": "reg",
"size": 1161,
"modtime": "2018-02-14T16:53:20Z",
"mode": 33188,
"offset": 22610594,
"NumLink": 0,
"digest": "sha256:78a3a4acc2b1b08db8f766d251b6f4d009e84663022dfee9ce2c66efd54bcb77"
},
{
"name": "usr/share/gcc-6/python/libstdcxx/v6/printers.py",
"type": "reg",
"size": 55953,
"modtime": "2018-02-14T16:53:20Z",
"mode": 33188,
"offset": 22611282,
"NumLink": 0,
"digest": "sha256:8d290e0fd40318fcbaf0dae5a8e17a45a94be40b6c0285a75fdd3f30b412e65b"
},
{
"name": "usr/share/gcc-6/python/libstdcxx/v6/xmethods.py",
"type": "reg",
"size": 26822,
"modtime": "2018-02-14T16:53:20Z",
"mode": 33188,
"offset": 22622344,
"NumLink": 0,
"digest": "sha256:ea30e5690abecb7808b4cb9dbe36afc3922cde4f982b5f64e1888a339880b1c9"
},
{
"name": "usr/share/gdb/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/gdb/auto-load/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/gdb/auto-load/usr/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/gdb/auto-load/usr/lib/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/gdb/auto-load/usr/lib/x86_64-linux-gnu/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/gdb/auto-load/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.22-gdb.py",
"type": "reg",
"size": 2389,
"modtime": "2018-02-14T16:53:20Z",
"mode": 33188,
"offset": 22626081,
"NumLink": 0,
"digest": "sha256:6957a1cadd8485b585a3821f167b8867893b5fc4ce7639c4b843901746858cdd"
},
{
"name": "usr/share/info/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/keyrings/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/keyrings/debian-archive-keyring.gpg",
"type": "reg",
"size": 36941,
"modtime": "2017-05-25T18:17:13Z",
"mode": 33188,
"offset": 22627405,
"NumLink": 0,
"digest": "sha256:350902b7289c1db8ffc28749772723a195dcd5ee42bbc43d2f57e59d5b06b08f"
},
{
"name": "usr/share/keyrings/debian-archive-removed-keys.gpg",
"type": "reg",
"size": 17538,
"modtime": "2017-05-25T18:17:13Z",
"mode": 33188,
"offset": 22662459,
"NumLink": 0,
"digest": "sha256:ac8b1500ec8cd69c437ff1b23a2b47f60cc740573cdae02a42abe342890faa09"
},
{
"name": "usr/share/libc-bin/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/libc-bin/nsswitch.conf",
"type": "reg",
"size": 497,
"modtime": "2017-12-31T12:12:42Z",
"mode": 33188,
"offset": 22678696,
"NumLink": 0,
"digest": "sha256:e241e67d7b5c15a5ace818d89277507b5ded8b49688b7a4431afb3b1041a3759"
},
{
"name": "usr/share/lintian/",
"type": "dir",
"modtime": "2018-06-26T12:03:08Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/lintian/overrides/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/locale/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/man/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/menu/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/menu/bash",
"type": "reg",
"size": 194,
"modtime": "2013-10-23T12:41:22Z",
"mode": 33188,
"offset": 22679157,
"NumLink": 0,
"digest": "sha256:1e862c7883df7a31e995769e90b4e6b87399a70f2cad6b2ce95fd865975286aa"
},
{
"name": "usr/share/menu/dash",
"type": "reg",
"size": 108,
"modtime": "2017-01-24T05:16:56Z",
"mode": 33188,
"offset": 22679353,
"NumLink": 0,
"digest": "sha256:c8bdff923cdb32fc55c80c70546c344e22444dfdccdea6280d9c4c6fa25c7c38"
},
{
"name": "usr/share/misc/",
"type": "dir",
"modtime": "2018-06-26T12:03:08Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/pam/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/pam/common-account",
"type": "reg",
"size": 1175,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 22679605,
"NumLink": 0,
"digest": "sha256:8657819f898333b2ff09e8bb4fcc6ffabb02acd015411d0ec89f28a70e672537"
},
{
"name": "usr/share/pam/common-account.md5sums",
"type": "reg",
"size": 107,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 22680317,
"NumLink": 0,
"digest": "sha256:8e2e9985399c29f44638f97acc1b71f9b2b946cfedb21ead16a54cd12c25992a"
},
{
"name": "usr/share/pam/common-auth",
"type": "reg",
"size": 1194,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 22680502,
"NumLink": 0,
"digest": "sha256:940827d13f472a45ebd38cca4c4b2f91b7fc22f08a266a9e987222e88df4cc9b"
},
{
"name": "usr/share/pam/common-auth.md5sums",
"type": "reg",
"size": 159,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 22681223,
"NumLink": 0,
"digest": "sha256:9dfd7b4897f508b7e327e7a511c7d097551d7063145b411e266aa91f2c0c1d57"
},
{
"name": "usr/share/pam/common-password",
"type": "reg",
"size": 1416,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 22681432,
"NumLink": 0,
"digest": "sha256:fa54ac2f7b8f18c22c1a73dc63d471bfd2694686686fd244d02cd774772dc911"
},
{
"name": "usr/share/pam/common-password.md5sums",
"type": "reg",
"size": 357,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 22682250,
"NumLink": 0,
"digest": "sha256:5f44a9db909d4ec8cc580261a4674f9a609ad5e7a776a7896087b738e9581b38"
},
{
"name": "usr/share/pam/common-session",
"type": "reg",
"size": 1127,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 22682543,
"NumLink": 0,
"digest": "sha256:b1413e5c9105ebdaec6be4eee00535552331a09c5f6206006e8c280374b5a2df"
},
{
"name": "usr/share/pam/common-session-noninteractive",
"type": "reg",
"size": 1139,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 22683232,
"NumLink": 0,
"digest": "sha256:7ac763203bc998dde4c1ddbdf0c572be2afa61e3f59d82194a51f7b3a5d11f7f"
},
{
"name": "usr/share/pam/common-session-noninteractive.md5sums",
"type": "reg",
"size": 46,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 22683911,
"NumLink": 0,
"digest": "sha256:c4e029edf22d18a793db84b3092d36b88ddfacc5e361b785364e4756755b10e1"
},
{
"name": "usr/share/pam/common-session.md5sums",
"type": "reg",
"size": 174,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 22684065,
"NumLink": 0,
"digest": "sha256:b80d0e21fd90493886ba1dd4a4fbacf283e6ffc07815469a0ccb130f2e5d156b"
},
{
"name": "usr/share/pam-configs/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/pam-configs/unix",
"type": "reg",
"size": 682,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 22684316,
"NumLink": 0,
"digest": "sha256:5b434421d10875a53932e967eddc9b885ea42bd9f1360600af04248e8d42be74"
},
{
"name": "usr/share/perl5/",
"type": "dir",
"modtime": "2017-05-21T17:08:30Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/perl5/Debconf/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/perl5/Debconf/AutoSelect.pm",
"type": "reg",
"size": 1833,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22684716,
"NumLink": 0,
"digest": "sha256:05d14126837c8b32b3f6a96c204d0946521827cfe7629e6f6060f698ab6788e3"
},
{
"name": "usr/share/perl5/Debconf/Base.pm",
"type": "reg",
"size": 499,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22685564,
"NumLink": 0,
"digest": "sha256:66ddc0f2cb2cae1e92bce9f5ae48cc82aea652a9ade8ab86cf9a1a3093e1f3e1"
},
{
"name": "usr/share/perl5/Debconf/Client/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/perl5/Debconf/Client/ConfModule.pm",
"type": "reg",
"size": 3900,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22686015,
"NumLink": 0,
"digest": "sha256:7d1b308c40b249d160ca49488f5ef1658db695940b53c7bf83bc89f49640b066"
},
{
"name": "usr/share/perl5/Debconf/ConfModule.pm",
"type": "reg",
"size": 15639,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22688084,
"NumLink": 0,
"digest": "sha256:387c6f3a403d3e4cd862b3e68450ab014be1b3b7b6c5824e09ad2f590aae2314"
},
{
"name": "usr/share/perl5/Debconf/Config.pm",
"type": "reg",
"size": 7020,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22691708,
"NumLink": 0,
"digest": "sha256:cd8de0d581b2ef08ff3deddd060fed52e4a914ddc0b59d46e3af617ee4961d1d"
},
{
"name": "usr/share/perl5/Debconf/Db.pm",
"type": "reg",
"size": 1202,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22694150,
"NumLink": 0,
"digest": "sha256:c0e7a554455e3cf9c42d937bbc8a701627b43f048cbf7a1d7590af44f218338f"
},
{
"name": "usr/share/perl5/Debconf/DbDriver/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/perl5/Debconf/DbDriver/Backup.pm",
"type": "reg",
"size": 1638,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22694834,
"NumLink": 0,
"digest": "sha256:0dd39de90e426e2062483ea468348aef62ecbdfb76fb1292728f3679a42b9dfd"
},
{
"name": "usr/share/perl5/Debconf/DbDriver/Cache.pm",
"type": "reg",
"size": 4825,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22695517,
"NumLink": 0,
"digest": "sha256:7075a4f322db7e12d96e2391267073cf11a01277ed972b8fd5285c8b3b89e272"
},
{
"name": "usr/share/perl5/Debconf/DbDriver/Copy.pm",
"type": "reg",
"size": 950,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22696546,
"NumLink": 0,
"digest": "sha256:d79bf94498c68355489fdd90c09d7aaa62116e42f0845ae78ba2c3b45fef9859"
},
{
"name": "usr/share/perl5/Debconf/DbDriver/Debug.pm",
"type": "reg",
"size": 950,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22697054,
"NumLink": 0,
"digest": "sha256:feb9793dbf296b01cc866c0bb792843e711f2126a2ed4171f4a1f2561da76cb8"
},
{
"name": "usr/share/perl5/Debconf/DbDriver/DirTree.pm",
"type": "reg",
"size": 1990,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22697621,
"NumLink": 0,
"digest": "sha256:57aba77c08bd3c0106b9d21c1c177984e20a477c2528b85b13ef1345b20af677"
},
{
"name": "usr/share/perl5/Debconf/DbDriver/Directory.pm",
"type": "reg",
"size": 3609,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22698531,
"NumLink": 0,
"digest": "sha256:ecf618cb2a38996f131f775e72084062b9c942c274133f7adb50ab8c36ef598f"
},
{
"name": "usr/share/perl5/Debconf/DbDriver/File.pm",
"type": "reg",
"size": 3633,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22699802,
"NumLink": 0,
"digest": "sha256:18906996142065a6c897392a366934054c821d08f34acacb645724da5f6235d5"
},
{
"name": "usr/share/perl5/Debconf/DbDriver/LDAP.pm",
"type": "reg",
"size": 6226,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22701131,
"NumLink": 0,
"digest": "sha256:633a36005440e5101b822a293751d63e0574787fe2b13157aacc2bc00fb0ef02"
},
{
"name": "usr/share/perl5/Debconf/DbDriver/PackageDir.pm",
"type": "reg",
"size": 3671,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22703234,
"NumLink": 0,
"digest": "sha256:081953795cd5ffbfc2b56cdc1e24c59f76be1fd7664ceaaee6688a55f12c1a3e"
},
{
"name": "usr/share/perl5/Debconf/DbDriver/Pipe.pm",
"type": "reg",
"size": 1783,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22704553,
"NumLink": 0,
"digest": "sha256:b50341f54a9e86d13c0fc1b607466df39f8db062f226b22123a97d73b1d73063"
},
{
"name": "usr/share/perl5/Debconf/DbDriver/Stack.pm",
"type": "reg",
"size": 5256,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22705362,
"NumLink": 0,
"digest": "sha256:f55d0e3f3f4f3ad8910ca1db54838eea67ee5b4fd4efdfa668e4fb23b432f6b9"
},
{
"name": "usr/share/perl5/Debconf/DbDriver.pm",
"type": "reg",
"size": 2359,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22706941,
"NumLink": 0,
"digest": "sha256:7ce0f564262a8d5dcdc1027951b080af151669b1841ac88cf6db54f9f5dcacab"
},
{
"name": "usr/share/perl5/Debconf/Element/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/perl5/Debconf/Element/Dialog/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/perl5/Debconf/Element/Dialog/Boolean.pm",
"type": "reg",
"size": 729,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22707971,
"NumLink": 0,
"digest": "sha256:81d60ef5716baeedcd3ec9ed5d59d8d310d6a187d7c902133770fa201604cfa5"
},
{
"name": "usr/share/perl5/Debconf/Element/Dialog/Error.pm",
"type": "reg",
"size": 331,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22708430,
"NumLink": 0,
"digest": "sha256:94044eacc5f6137204dc1c805e9323d8ee370163e21e16485961c073bd244c16"
},
{
"name": "usr/share/perl5/Debconf/Element/Dialog/Multiselect.pm",
"type": "reg",
"size": 1832,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22708761,
"NumLink": 0,
"digest": "sha256:6731d2d9fec3870ff1bece763a60b97666dc9401a52ca5abac9a5c981b607e3b"
},
{
"name": "usr/share/perl5/Debconf/Element/Dialog/Note.pm",
"type": "reg",
"size": 330,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22709571,
"NumLink": 0,
"digest": "sha256:768e0c2ebfbc4e9227ce359f38b2469e070135c56adba1ec7f5b7505e8ec87e6"
},
{
"name": "usr/share/perl5/Debconf/Element/Dialog/Password.pm",
"type": "reg",
"size": 690,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22709902,
"NumLink": 0,
"digest": "sha256:0f795cb55435e9660233584f899e769e263d1e605e9748860b5edb02ffad3aae"
},
{
"name": "usr/share/perl5/Debconf/Element/Dialog/Progress.pm",
"type": "reg",
"size": 1846,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22710363,
"NumLink": 0,
"digest": "sha256:abe844eae752340e23f9f21ddcb6b24764f0eb980471c25f5158c452cfce961d"
},
{
"name": "usr/share/perl5/Debconf/Element/Dialog/Select.pm",
"type": "reg",
"size": 1350,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22711099,
"NumLink": 0,
"digest": "sha256:53b327be9f1f04b236b0b5baa15b3ec041f56cf9b6f6f98cad37860a495d81bb"
},
{
"name": "usr/share/perl5/Debconf/Element/Dialog/String.pm",
"type": "reg",
"size": 788,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22711762,
"NumLink": 0,
"digest": "sha256:6c2a414ab5362a2d25f5d080c18da7dfb6efee6d6073635b6e3f1da5793ffac8"
},
{
"name": "usr/share/perl5/Debconf/Element/Dialog/Text.pm",
"type": "reg",
"size": 330,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22712227,
"NumLink": 0,
"digest": "sha256:d95c589193ffc32fa5f5c7fb151a458f210ec17a8f33ef5d1644b61110c7a8b0"
},
{
"name": "usr/share/perl5/Debconf/Element/Editor/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/perl5/Debconf/Element/Editor/Boolean.pm",
"type": "reg",
"size": 1009,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22712592,
"NumLink": 0,
"digest": "sha256:03aaf5a065ce57bce012d4ac22f19c776eb5b49daf5842f06bf50de0948b7b17"
},
{
"name": "usr/share/perl5/Debconf/Element/Editor/Error.pm",
"type": "reg",
"size": 165,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22713132,
"NumLink": 0,
"digest": "sha256:dea5e8e710441f23f824944d013a72167c5e5a0eb9ed31502b543e6aea6d33ba"
},
{
"name": "usr/share/perl5/Debconf/Element/Editor/Multiselect.pm",
"type": "reg",
"size": 916,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22713367,
"NumLink": 0,
"digest": "sha256:6a607d19aec535e157cc301eb01a49d25335000d3312f5d0a06e9701d46edfb3"
},
{
"name": "usr/share/perl5/Debconf/Element/Editor/Note.pm",
"type": "reg",
"size": 164,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22713948,
"NumLink": 0,
"digest": "sha256:f3922ec83c3ff8ab7f2a0c9a832628c64aeff1a92249544259df48f642df14b7"
},
{
"name": "usr/share/perl5/Debconf/Element/Editor/Password.pm",
"type": "reg",
"size": 171,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22714182,
"NumLink": 0,
"digest": "sha256:e0802d54649714aafd6d6ef426ed4bf4f0429bc8b5ad930e8822b4835d66c4be"
},
{
"name": "usr/share/perl5/Debconf/Element/Editor/Progress.pm",
"type": "reg",
"size": 235,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22714419,
"NumLink": 0,
"digest": "sha256:f5b8cf39ff9f833f719d260e27466e4e11839a488286c25dacb56a23b049f831"
},
{
"name": "usr/share/perl5/Debconf/Element/Editor/Select.pm",
"type": "reg",
"size": 834,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22714684,
"NumLink": 0,
"digest": "sha256:4d8a031e7a23ad208675d097d700692b04304704fca6764630ffaeaa647843d0"
},
{
"name": "usr/share/perl5/Debconf/Element/Editor/String.pm",
"type": "reg",
"size": 439,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22715194,
"NumLink": 0,
"digest": "sha256:f8aec4b503d26da2b87eba5acb67456aa6752ad37add8915beee15646626d4a6"
},
{
"name": "usr/share/perl5/Debconf/Element/Editor/Text.pm",
"type": "reg",
"size": 316,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22715552,
"NumLink": 0,
"digest": "sha256:5478c4f864725771238f7a4d42071be055fdf1781098f9a67671ce4c082d6236"
},
{
"name": "usr/share/perl5/Debconf/Element/Gnome/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/perl5/Debconf/Element/Gnome/Boolean.pm",
"type": "reg",
"size": 684,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22715912,
"NumLink": 0,
"digest": "sha256:c318ba062e156a6aee047e374602c3706c6b411fc42a3ec7e007e72d0ed9a633"
},
{
"name": "usr/share/perl5/Debconf/Element/Gnome/Error.pm",
"type": "reg",
"size": 1166,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22716373,
"NumLink": 0,
"digest": "sha256:30728fd5851c519aa6d04fc389141c269b933e715c7e4f34c4b374eea08f0e6e"
},
{
"name": "usr/share/perl5/Debconf/Element/Gnome/Multiselect.pm",
"type": "reg",
"size": 2479,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22717012,
"NumLink": 0,
"digest": "sha256:f038ebdef7cac98c970b5a009e72c9436addd0bb43b5c3dad66cde38601b4422"
},
{
"name": "usr/share/perl5/Debconf/Element/Gnome/Note.pm",
"type": "reg",
"size": 1076,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22718096,
"NumLink": 0,
"digest": "sha256:ebb6c28c8d917316a985911221448d6df0b448db5ac259b8e6281e82d600762d"
},
{
"name": "usr/share/perl5/Debconf/Element/Gnome/Password.pm",
"type": "reg",
"size": 580,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22718691,
"NumLink": 0,
"digest": "sha256:909030e9b56deb035d804e5acf0bbb8e31d57cf6b7ded3c2bc44634ca1465a41"
},
{
"name": "usr/share/perl5/Debconf/Element/Gnome/Progress.pm",
"type": "reg",
"size": 1113,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22719113,
"NumLink": 0,
"digest": "sha256:88e82679aa749d40dc705ddd37bc9b39732c8d67444c2a0d1b8bba605bc53309"
},
{
"name": "usr/share/perl5/Debconf/Element/Gnome/Select.pm",
"type": "reg",
"size": 975,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22719663,
"NumLink": 0,
"digest": "sha256:15ae5e72faf962e197edc6da61a5a12aa660027e8ab4e4294c6571dbe2271cf9"
},
{
"name": "usr/share/perl5/Debconf/Element/Gnome/String.pm",
"type": "reg",
"size": 649,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22720228,
"NumLink": 0,
"digest": "sha256:cb92206c450cf81313d1c96a9905a2e5a74fadcf9eb810bd7f5ad1e54d57c207"
},
{
"name": "usr/share/perl5/Debconf/Element/Gnome/Text.pm",
"type": "reg",
"size": 300,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22720673,
"NumLink": 0,
"digest": "sha256:21ebacfb1853ff920ebf8ec0d948ffba52863e7acf3d95185bc0c3c213f02af3"
},
{
"name": "usr/share/perl5/Debconf/Element/Gnome.pm",
"type": "reg",
"size": 2956,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22720986,
"NumLink": 0,
"digest": "sha256:7a249e4a2d49d59af9d1432adda52712f607ee20c85b0748356f85e25d17d729"
},
{
"name": "usr/share/perl5/Debconf/Element/Kde/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/perl5/Debconf/Element/Kde/Boolean.pm",
"type": "reg",
"size": 730,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22722086,
"NumLink": 0,
"digest": "sha256:00fe2541a80981c26a26f33df614e88a080c00d592088c6c065a560af16030d2"
},
{
"name": "usr/share/perl5/Debconf/Element/Kde/Error.pm",
"type": "reg",
"size": 339,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22722556,
"NumLink": 0,
"digest": "sha256:587b37fd9a3dbdb70116fa02a95592b6c713852df37c8bd67d10884aa9778cae"
},
{
"name": "usr/share/perl5/Debconf/Element/Kde/Multiselect.pm",
"type": "reg",
"size": 1241,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22722886,
"NumLink": 0,
"digest": "sha256:e76353e8dd6e224fdf11c0589a4d9fc50f2658f0a3830f4c7b154fd58f6354b3"
},
{
"name": "usr/share/perl5/Debconf/Element/Kde/Note.pm",
"type": "reg",
"size": 365,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22723575,
"NumLink": 0,
"digest": "sha256:e3dcea774a31296c300a5b3f3759e4280dca2b6ed4d603758102caac2a4f8b23"
},
{
"name": "usr/share/perl5/Debconf/Element/Kde/Password.pm",
"type": "reg",
"size": 605,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22723906,
"NumLink": 0,
"digest": "sha256:a1cf8c7a6c23c4011d59b0e5d947f76edf59d2ec2a0ad59d6b1c962c477f81ae"
},
{
"name": "usr/share/perl5/Debconf/Element/Kde/Progress.pm",
"type": "reg",
"size": 1164,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22724332,
"NumLink": 0,
"digest": "sha256:f72dcf155b89c7b6275f246f083d5aeefc188a66056c49bf0420f30968606b26"
},
{
"name": "usr/share/perl5/Debconf/Element/Kde/Select.pm",
"type": "reg",
"size": 1083,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22724884,
"NumLink": 0,
"digest": "sha256:531a4048d81c02f477ac9405a13ca1110c63dcd8bd7a28607afe670cf081fdcf"
},
{
"name": "usr/share/perl5/Debconf/Element/Kde/String.pm",
"type": "reg",
"size": 636,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22725519,
"NumLink": 0,
"digest": "sha256:daaaf6d66dca96cca82e9d64c563c705927d1a604a18fb352d3fa17b32f09547"
},
{
"name": "usr/share/perl5/Debconf/Element/Kde/Text.pm",
"type": "reg",
"size": 323,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22725961,
"NumLink": 0,
"digest": "sha256:b49c5cf58bf4ce29ed7d2789f6b8f44480d84527dd166f80fc3abd2272c0a394"
},
{
"name": "usr/share/perl5/Debconf/Element/Kde.pm",
"type": "reg",
"size": 2185,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22726278,
"NumLink": 0,
"digest": "sha256:46e40500a4283dc9d8a581182cc5ccbfed82c54163969a4f425568e303e323f1"
},
{
"name": "usr/share/perl5/Debconf/Element/Multiselect.pm",
"type": "reg",
"size": 916,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22727089,
"NumLink": 0,
"digest": "sha256:8f4919e0b67b61f650af51406b13998467a477cd04f7772bc8b5ad6cb18649c3"
},
{
"name": "usr/share/perl5/Debconf/Element/Noninteractive/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/perl5/Debconf/Element/Noninteractive/Boolean.pm",
"type": "reg",
"size": 178,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22727632,
"NumLink": 0,
"digest": "sha256:13e3e74cafc97617862521b51e2c4ba4b87e87658363d1537452fcd5d874d261"
},
{
"name": "usr/share/perl5/Debconf/Element/Noninteractive/Error.pm",
"type": "reg",
"size": 1666,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22727869,
"NumLink": 0,
"digest": "sha256:799e48fc5a362d653334159305bafe67cd451420d98988d3047d515b2408120b"
},
{
"name": "usr/share/perl5/Debconf/Element/Noninteractive/Multiselect.pm",
"type": "reg",
"size": 182,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22728700,
"NumLink": 0,
"digest": "sha256:14bbbea9a46115ea16632083ea3ba850b13594957e5dc49b7907ec07e14a0789"
},
{
"name": "usr/share/perl5/Debconf/Element/Noninteractive/Note.pm",
"type": "reg",
"size": 175,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22728938,
"NumLink": 0,
"digest": "sha256:88f2b6eaca06a4b404d62cbdaad4f497aa17aca4d44e785c3a78a763b66c24d4"
},
{
"name": "usr/share/perl5/Debconf/Element/Noninteractive/Password.pm",
"type": "reg",
"size": 179,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22729174,
"NumLink": 0,
"digest": "sha256:5a046112c91b00436a614d93590338a04ad14710cf3af56ebdb883041e38a955"
},
{
"name": "usr/share/perl5/Debconf/Element/Noninteractive/Progress.pm",
"type": "reg",
"size": 258,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22729413,
"NumLink": 0,
"digest": "sha256:f21d00539d3a4b8dafa82322d0e0cb1281f0493995723dfdd9a6fc7cc28a7434"
},
{
"name": "usr/share/perl5/Debconf/Element/Noninteractive/Select.pm",
"type": "reg",
"size": 602,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22729684,
"NumLink": 0,
"digest": "sha256:e0b972e4af3cefd2ab4858fac0a20df5ac91feb886bb8f0003cfac187581609b"
},
{
"name": "usr/share/perl5/Debconf/Element/Noninteractive/String.pm",
"type": "reg",
"size": 177,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22730106,
"NumLink": 0,
"digest": "sha256:505643b340d236ba8fe6e3bb930640e79ecf6ac15b43513456cb81228c946e23"
},
{
"name": "usr/share/perl5/Debconf/Element/Noninteractive/Text.pm",
"type": "reg",
"size": 226,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22730341,
"NumLink": 0,
"digest": "sha256:17d40add0e5bf98fdfb6ad5f71f337a04536c57c5b4e663cb6f5308398f27349"
},
{
"name": "usr/share/perl5/Debconf/Element/Noninteractive.pm",
"type": "reg",
"size": 342,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22730614,
"NumLink": 0,
"digest": "sha256:e1b6db74cda4b4b43bdf2244350494ea6a53d13a1f7dfeaca90ed16b22d13791"
},
{
"name": "usr/share/perl5/Debconf/Element/Select.pm",
"type": "reg",
"size": 1982,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22730932,
"NumLink": 0,
"digest": "sha256:249a05d5f564db0f40e09c13d79b0a77ab128563a19a1ceee585c3431b844d1e"
},
{
"name": "usr/share/perl5/Debconf/Element/Teletype/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/perl5/Debconf/Element/Teletype/Boolean.pm",
"type": "reg",
"size": 1196,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22731691,
"NumLink": 0,
"digest": "sha256:a97d6deb504426af124f6c1dd8d522a6abd009dbb37fbe0a3e2a284b921ea1f3"
},
{
"name": "usr/share/perl5/Debconf/Element/Teletype/Error.pm",
"type": "reg",
"size": 170,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22732309,
"NumLink": 0,
"digest": "sha256:a271b558a75a7617474d93cd77402c339334e84938651ea0a01f7336db09d010"
},
{
"name": "usr/share/perl5/Debconf/Element/Teletype/Multiselect.pm",
"type": "reg",
"size": 1899,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22732550,
"NumLink": 0,
"digest": "sha256:dccdb67e08a4053c00c2395d4178ecf37ce8e64e81c63b4807e1d763ab0d5450"
},
{
"name": "usr/share/perl5/Debconf/Element/Teletype/Note.pm",
"type": "reg",
"size": 403,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22733446,
"NumLink": 0,
"digest": "sha256:0ea94576f9890a0f5f9a4a59361bd1e0f6153b3ff23b883e6078b2e08063155d"
},
{
"name": "usr/share/perl5/Debconf/Element/Teletype/Password.pm",
"type": "reg",
"size": 629,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22733811,
"NumLink": 0,
"digest": "sha256:9c3cc0ef1ccbe6da38b3885610369edef2005a691d043023c27689621e027a39"
},
{
"name": "usr/share/perl5/Debconf/Element/Teletype/Progress.pm",
"type": "reg",
"size": 805,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22734250,
"NumLink": 0,
"digest": "sha256:63968f89058f1066176e80bc3a4e4ada8f4f24dcf78fc56c722edc7aa12f0f9c"
},
{
"name": "usr/share/perl5/Debconf/Element/Teletype/Select.pm",
"type": "reg",
"size": 3292,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22734710,
"NumLink": 0,
"digest": "sha256:a5749589dee50745a7db1e2c6743788334381461cca018552058183edae81d8c"
},
{
"name": "usr/share/perl5/Debconf/Element/Teletype/String.pm",
"type": "reg",
"size": 573,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22736001,
"NumLink": 0,
"digest": "sha256:9bbe2c52e9629b135b888fe81fdcaf9eb1280076df252bb86292ba904aad8a1c"
},
{
"name": "usr/share/perl5/Debconf/Element/Teletype/Text.pm",
"type": "reg",
"size": 315,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22736409,
"NumLink": 0,
"digest": "sha256:f61a5ec96a09bf7671dadd87281a75622df7043b2736d788af6767f6827bcbba"
},
{
"name": "usr/share/perl5/Debconf/Element/Web/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/perl5/Debconf/Element/Web/Boolean.pm",
"type": "reg",
"size": 660,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22736769,
"NumLink": 0,
"digest": "sha256:e74c742b01709c060bd24a58f960c03cbe688a818919037295d6222f8c3165ee"
},
{
"name": "usr/share/perl5/Debconf/Element/Web/Error.pm",
"type": "reg",
"size": 160,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22737256,
"NumLink": 0,
"digest": "sha256:1699c8211d030d2d67fcfaafd5e30867d2661c21dad41c574dec2d88bd96ebc6"
},
{
"name": "usr/share/perl5/Debconf/Element/Web/Multiselect.pm",
"type": "reg",
"size": 958,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22737491,
"NumLink": 0,
"digest": "sha256:367f3dce72c5bb9e40587416570903867824a063eb04319b6d57afdce1039d01"
},
{
"name": "usr/share/perl5/Debconf/Element/Web/Note.pm",
"type": "reg",
"size": 159,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22738086,
"NumLink": 0,
"digest": "sha256:6654230ab4dc25692f22f7eae0798896067c2f5471ceabc4bbfdfa63258970aa"
},
{
"name": "usr/share/perl5/Debconf/Element/Web/Password.pm",
"type": "reg",
"size": 483,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22738319,
"NumLink": 0,
"digest": "sha256:aff9175f3d6d46cd1a98d602e670d565c66005bc1e482174ca8ca75b53a40300"
},
{
"name": "usr/share/perl5/Debconf/Element/Web/Progress.pm",
"type": "reg",
"size": 231,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22738728,
"NumLink": 0,
"digest": "sha256:430c8365d9c3278fc0375434cc93725f8fa75a5353b072f0f8cfdfe4c7dd4dfc"
},
{
"name": "usr/share/perl5/Debconf/Element/Web/Select.pm",
"type": "reg",
"size": 877,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22738993,
"NumLink": 0,
"digest": "sha256:42a7752151c302e29228a17fa3d0fa8bd65dc999025846c013ee580e06d78766"
},
{
"name": "usr/share/perl5/Debconf/Element/Web/String.pm",
"type": "reg",
"size": 467,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22739538,
"NumLink": 0,
"digest": "sha256:3dacc1ab78f01cd9db77d95163cc33fec708f1ba1d3ca10146f5b14c6096def7"
},
{
"name": "usr/share/perl5/Debconf/Element/Web/Text.pm",
"type": "reg",
"size": 315,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22739935,
"NumLink": 0,
"digest": "sha256:03e9ea3bda1040086d423f3fbe2ee6cc93f59d90d26b4bf8554ab5c6cdc73885"
},
{
"name": "usr/share/perl5/Debconf/Element.pm",
"type": "reg",
"size": 196,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22740263,
"NumLink": 0,
"digest": "sha256:00c10272de11fd0ddfbacf752982b22623e629b9d796533c95d586d858ce2c39"
},
{
"name": "usr/share/perl5/Debconf/Encoding.pm",
"type": "reg",
"size": 1487,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22740523,
"NumLink": 0,
"digest": "sha256:801fe349da27f9312f3b6c60ea38e44e34bf7fce5138a0a9235b5efd1f7ff7eb"
},
{
"name": "usr/share/perl5/Debconf/Format/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/perl5/Debconf/Format/822.pm",
"type": "reg",
"size": 2139,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22741293,
"NumLink": 0,
"digest": "sha256:47bb2e39010d9e051b79bdae364bb5824c663441b05037babae6a634fd4c0fb4"
},
{
"name": "usr/share/perl5/Debconf/Format.pm",
"type": "reg",
"size": 133,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22742209,
"NumLink": 0,
"digest": "sha256:5ce727bb1a20759b641ffb8bd6c45176bae65e65f907e237c6dc1f093899b02f"
},
{
"name": "usr/share/perl5/Debconf/FrontEnd/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/perl5/Debconf/FrontEnd/Dialog.pm",
"type": "reg",
"size": 7389,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22742465,
"NumLink": 0,
"digest": "sha256:03417133c48efa609a58085ee135db1008ac8326a6457874df9105ad5fce075e"
},
{
"name": "usr/share/perl5/Debconf/FrontEnd/Editor.pm",
"type": "reg",
"size": 2165,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22745016,
"NumLink": 0,
"digest": "sha256:24b3da6ed94ce2682a51501683188675dbb50d4b154496e875ef1488c391711c"
},
{
"name": "usr/share/perl5/Debconf/FrontEnd/Gnome.pm",
"type": "reg",
"size": 4956,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22746092,
"NumLink": 0,
"digest": "sha256:a2e09f7bbe1cbd15850b432a626a0321bba98cec860960e8fc01963c446faea0"
},
{
"name": "usr/share/perl5/Debconf/FrontEnd/Kde/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/perl5/Debconf/FrontEnd/Kde/Ui_DebconfWizard.pm",
"type": "reg",
"size": 4244,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22747752,
"NumLink": 0,
"digest": "sha256:649442e8cccc20d0f5937021d5f5fc551f4c91d9fc30f2c6181d295824a4948e"
},
{
"name": "usr/share/perl5/Debconf/FrontEnd/Kde/Wizard.pm",
"type": "reg",
"size": 1707,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22748897,
"NumLink": 0,
"digest": "sha256:9ae54bf90aa55c3aaca791616079cc9c342364995f992bc1e03d374440a9dc62"
},
{
"name": "usr/share/perl5/Debconf/FrontEnd/Kde.pm",
"type": "reg",
"size": 4409,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22749664,
"NumLink": 0,
"digest": "sha256:457b5677b1907bf3b85fd3189efdd47a537dcbce15943f953495e9739ad82825"
},
{
"name": "usr/share/perl5/Debconf/FrontEnd/Noninteractive.pm",
"type": "reg",
"size": 734,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22751134,
"NumLink": 0,
"digest": "sha256:1604a04e4c2ddebc03659bef3bfe94a1f97330ca8e4ea88b036b10ca509308e5"
},
{
"name": "usr/share/perl5/Debconf/FrontEnd/Passthrough.pm",
"type": "reg",
"size": 6438,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22751635,
"NumLink": 0,
"digest": "sha256:6cc52e0f85ac962e7fcdf11cabf01b13a7493d02209c23e8690a0c9afa3426da"
},
{
"name": "usr/share/perl5/Debconf/FrontEnd/Readline.pm",
"type": "reg",
"size": 3486,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22753569,
"NumLink": 0,
"digest": "sha256:de513abc3901f8244dc3cee1f84ccbeb817f7c3594167c9d3d52c074ea586d81"
},
{
"name": "usr/share/perl5/Debconf/FrontEnd/ScreenSize.pm",
"type": "reg",
"size": 881,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22754913,
"NumLink": 0,
"digest": "sha256:abd4a309996b0f8798df81bd51d881fa014b66ebbd0d626220884aee2868922e"
},
{
"name": "usr/share/perl5/Debconf/FrontEnd/Teletype.pm",
"type": "reg",
"size": 1573,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22755436,
"NumLink": 0,
"digest": "sha256:e01ec0a98b14a8092dda1656e66248212dc4f164248f4058178e2964e5b72928"
},
{
"name": "usr/share/perl5/Debconf/FrontEnd/Text.pm",
"type": "reg",
"size": 155,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22756257,
"NumLink": 0,
"digest": "sha256:cf4595fb746e12a9c0a43cd6806aec7d45523aa71b2bf9d98328d8c713c8470b"
},
{
"name": "usr/share/perl5/Debconf/FrontEnd/Web.pm",
"type": "reg",
"size": 2665,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22756483,
"NumLink": 0,
"digest": "sha256:ca7b97f7c6ecd207e6b161a89a0d42e5e547c81206b09c430b9a4ac1f0b9f847"
},
{
"name": "usr/share/perl5/Debconf/FrontEnd.pm",
"type": "reg",
"size": 2864,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22757757,
"NumLink": 0,
"digest": "sha256:e7a545c137bb45bfc70dc75f6f14f2246219a4cf9a60344bff9dcf04b1e25b33"
},
{
"name": "usr/share/perl5/Debconf/Gettext.pm",
"type": "reg",
"size": 301,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22758780,
"NumLink": 0,
"digest": "sha256:a88fa616d014b4668aa0acddd4b62c6f16f23ca4d770f74c42a465bccd757ddf"
},
{
"name": "usr/share/perl5/Debconf/Iterator.pm",
"type": "reg",
"size": 198,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22759107,
"NumLink": 0,
"digest": "sha256:25e26945b7cd46f0807fc71e4aa0187668d883f9068076356ec4fc3515d58b15"
},
{
"name": "usr/share/perl5/Debconf/Log.pm",
"type": "reg",
"size": 914,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22759368,
"NumLink": 0,
"digest": "sha256:295705788c02bc5ecab17acab91e8bca411c88a5ec5404b64ea1bf85f408d98a"
},
{
"name": "usr/share/perl5/Debconf/Path.pm",
"type": "reg",
"size": 291,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22759962,
"NumLink": 0,
"digest": "sha256:028380a22abab29ad18b0f4411bb3f1a1f3bf192f139ae61121393cebf5acc6e"
},
{
"name": "usr/share/perl5/Debconf/Priority.pm",
"type": "reg",
"size": 642,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22760266,
"NumLink": 0,
"digest": "sha256:bf398f78a07d12eb8aee3fb782ffd002f311027279d076eaa6fffd0de11ce0d1"
},
{
"name": "usr/share/perl5/Debconf/Question.pm",
"type": "reg",
"size": 5867,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22760693,
"NumLink": 0,
"digest": "sha256:457f0d6a12d8dc4ab1fc109b5c54c423a703184630b50e49abfd3b2cbba2e640"
},
{
"name": "usr/share/perl5/Debconf/Template/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/perl5/Debconf/Template/Transient.pm",
"type": "reg",
"size": 1108,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22762474,
"NumLink": 0,
"digest": "sha256:3a0e780834bf3c643f32931ad4fe02b3447343b1a54def72f03e15823e6723ae"
},
{
"name": "usr/share/perl5/Debconf/Template.pm",
"type": "reg",
"size": 8201,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22763043,
"NumLink": 0,
"digest": "sha256:4ee3d67fccb651ed7bca60a48bd85758e3a177fdda3167a5209f1513379eb6ac"
},
{
"name": "usr/share/perl5/Debconf/TmpFile.pm",
"type": "reg",
"size": 374,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22765731,
"NumLink": 0,
"digest": "sha256:01a27c6a5acbf11d145efb8341e0328892a0c00db0526ad522a8c2165e4e01f6"
},
{
"name": "usr/share/perl5/Debian/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/perl5/Debian/AdduserCommon.pm",
"type": "reg",
"size": 6185,
"modtime": "2016-06-26T22:55:16Z",
"mode": 33188,
"offset": 22766120,
"NumLink": 0,
"digest": "sha256:94551d94231a98889d4fe7559c990cd5b26abf14c53a5ed8d072287e66b637d1"
},
{
"name": "usr/share/perl5/Debian/DebConf/",
"type": "dir",
"modtime": "2017-05-21T17:08:30Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/perl5/Debian/DebConf/Client/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/perl5/Debian/DebConf/Client/ConfModule.pm",
"type": "reg",
"size": 610,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22768783,
"NumLink": 0,
"digest": "sha256:18613eb5076afb75cd5cd97811c465b78533ab566d482023c9f3379f4f7fe7a0"
},
{
"name": "usr/share/pixmaps/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/pixmaps/debian-logo.png",
"type": "reg",
"size": 1718,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 22769254,
"NumLink": 0,
"digest": "sha256:18aa40cbfcd56cd1dddb005a7b20560d5466046472c665bad8683dfa9e17d6f2"
},
{
"name": "usr/share/tabset/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/tabset/std",
"type": "reg",
"size": 135,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 22771163,
"NumLink": 0,
"digest": "sha256:fbadb5f608b355fe481c0c7d9c6265b2372bfa35250662f81f68d46540080770"
},
{
"name": "usr/share/tabset/stdcrt",
"type": "reg",
"size": 95,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 22771283,
"NumLink": 0,
"digest": "sha256:cf6c37b18ceea7c306f7e3a5e604a03b0dfb9c22ec99163e4b52f885ce063145"
},
{
"name": "usr/share/tabset/vt100",
"type": "reg",
"size": 160,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 22771399,
"NumLink": 0,
"digest": "sha256:075251754239d9973945d82b95c18cd90997acd2017393e70c8832e9297de056"
},
{
"name": "usr/share/tabset/vt300",
"type": "reg",
"size": 64,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 22771518,
"NumLink": 0,
"digest": "sha256:61f8388cad6a381feb819bc6a8d299d06a853d15e1f4bfdfd6b6f40069ad4956"
},
{
"name": "usr/share/terminfo/",
"type": "dir",
"modtime": "2017-12-28T09:47:33Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Abidjan",
"type": "reg",
"size": 170,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22771745,
"NumLink": 0,
"digest": "sha256:d5ded126df8f693ce1ff83e85aa4d44185c2bdef7da1f915b214f53deffdee47"
},
{
"name": "usr/share/zoneinfo/Africa/Accra",
"type": "reg",
"size": 842,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22771916,
"NumLink": 0,
"digest": "sha256:ea0a89ec3c253390f746107c3ea69392270d8df0dc2d2aed6f23f4cff852bf91"
},
{
"name": "usr/share/zoneinfo/Africa/Addis_Ababa",
"type": "reg",
"size": 285,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22772459,
"NumLink": 0,
"digest": "sha256:f143bcb83b80bc1ad0bbb8ad736c852e62bbeb6b3134412bfa77684663ed222a"
},
{
"name": "usr/share/zoneinfo/Africa/Algiers",
"type": "reg",
"size": 760,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22772689,
"NumLink": 0,
"digest": "sha256:d777e8eecb9ebe269692349daa6b45b2463e4a3c2d107ccd139b6206c4fa73cc"
},
{
"name": "usr/share/zoneinfo/Africa/Asmara",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Asmera",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Bamako",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Bangui",
"type": "reg",
"size": 171,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22773263,
"NumLink": 0,
"digest": "sha256:e40c3386f3a5cd88a03c811fa30ecac34f31368f960ae79e4a90de295c5b1938"
},
{
"name": "usr/share/zoneinfo/Africa/Banjul",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Bissau",
"type": "reg",
"size": 208,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22773480,
"NumLink": 0,
"digest": "sha256:8ddad13adc33cdee8eaf55cfa31efcafd79305ae8dfcc3be06ff78299f29f1d8"
},
{
"name": "usr/share/zoneinfo/Africa/Blantyre",
"type": "reg",
"size": 171,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22773681,
"NumLink": 0,
"digest": "sha256:3d7e6d17cabdaa1814a56dddec02687e1087bc3334fe920ad268a892bf080511"
},
{
"name": "usr/share/zoneinfo/Africa/Brazzaville",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Bujumbura",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Blantyre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Cairo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Egypt",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Casablanca",
"type": "reg",
"size": 1629,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22773963,
"NumLink": 0,
"digest": "sha256:afb98baa9972580559410aa145c57fb8c86689a05f7f0e97815f96bdea7af5ad"
},
{
"name": "usr/share/zoneinfo/Africa/Ceuta",
"type": "reg",
"size": 2059,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22774905,
"NumLink": 0,
"digest": "sha256:387d2c354117fe2a22f6f3b429e4193a331d3e93d7007a55c50b3b9eedee63de"
},
{
"name": "usr/share/zoneinfo/Africa/Conakry",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Dakar",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Dar_es_Salaam",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Djibouti",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Douala",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/El_Aaiun",
"type": "reg",
"size": 1459,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22776159,
"NumLink": 0,
"digest": "sha256:9c3a4c95fd64c01a78dcd44ca4b16cf72f2416be4704e8c5d52ffc7c131ae015"
},
{
"name": "usr/share/zoneinfo/Africa/Freetown",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Gaborone",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Blantyre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Harare",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Blantyre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Johannesburg",
"type": "reg",
"size": 271,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22777115,
"NumLink": 0,
"digest": "sha256:fcec4247091905d88a0b869e8e5c7ee6bcfba7db6c310165baa95078b0be31af"
},
{
"name": "usr/share/zoneinfo/Africa/Juba",
"type": "reg",
"size": 683,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22777350,
"NumLink": 0,
"digest": "sha256:73d986c70173c763e9b262dbf367120bc2a4f63f006c1d412ea9adab77e09da3"
},
{
"name": "usr/share/zoneinfo/Africa/Kampala",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Khartoum",
"type": "reg",
"size": 713,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22777856,
"NumLink": 0,
"digest": "sha256:5256a96f78382e82db80ad8591240c3886fc58f9a83fe94506e3a192fbcf2f4e"
},
{
"name": "usr/share/zoneinfo/Africa/Kigali",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Blantyre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Kinshasa",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Lagos",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Libreville",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Lome",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Luanda",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Lubumbashi",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Blantyre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Lusaka",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Blantyre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Malabo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Maputo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Blantyre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Maseru",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Johannesburg",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Mbabane",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Johannesburg",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Mogadishu",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Monrovia",
"type": "reg",
"size": 233,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22778584,
"NumLink": 0,
"digest": "sha256:3f9672c98983af595b3c6274cf8135728c8815a4f9c98ffba043707609e5d122"
},
{
"name": "usr/share/zoneinfo/Africa/Nairobi",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Ndjamena",
"type": "reg",
"size": 225,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22778843,
"NumLink": 0,
"digest": "sha256:b1391c8edd23b3f73e0bfacf8b878801c58206ca42349c30b22fcb7e8d13de3a"
},
{
"name": "usr/share/zoneinfo/Africa/Niamey",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Nouakchott",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Ouagadougou",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Porto-Novo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Sao_Tome",
"type": "reg",
"size": 234,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22779160,
"NumLink": 0,
"digest": "sha256:8d9cc441a8cb7dfebbf23510dba8510ba8b7a2cac7661b0cccab368555d865bf"
},
{
"name": "usr/share/zoneinfo/Africa/Timbuktu",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Tripoli",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Libya",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Africa/Tunis",
"type": "reg",
"size": 710,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22779449,
"NumLink": 0,
"digest": "sha256:eecc34436d1dd96c49d6b671ed61bc594548d280a967537a9653841b537a9a92"
},
{
"name": "usr/share/zoneinfo/Africa/Windhoek",
"type": "reg",
"size": 988,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22779935,
"NumLink": 0,
"digest": "sha256:f6d37353c3cf02bb1b950cdc1cc024e9849a374532a8f7e4ee0bb00bdd4b6ab1"
},
{
"name": "usr/share/zoneinfo/America/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Adak",
"type": "reg",
"size": 2365,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22780581,
"NumLink": 0,
"digest": "sha256:c45c94d316413c8f666aff65ed1f837a7e2d392262de31ce59fac2e96a1edc81"
},
{
"name": "usr/share/zoneinfo/America/Anchorage",
"type": "reg",
"size": 2380,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22781880,
"NumLink": 0,
"digest": "sha256:f5df0a6f7f9d43cbbd3e74d33a23fe686080eb55965f5d9246b6e859b3db9d18"
},
{
"name": "usr/share/zoneinfo/America/Anguilla",
"type": "reg",
"size": 170,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22783178,
"NumLink": 0,
"digest": "sha256:b2659c267f7555c0640505660234cbe0d7feead3a5e29f41272e28a1d7d18962"
},
{
"name": "usr/share/zoneinfo/America/Antigua",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Araguaina",
"type": "reg",
"size": 896,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22783403,
"NumLink": 0,
"digest": "sha256:272c5c90b22d2661b00ed78567b641a8e7aacd3261e19c40ef191af4c7b1c60c"
},
{
"name": "usr/share/zoneinfo/America/Argentina/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Argentina/Buenos_Aires",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Buenos_Aires",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Argentina/Catamarca",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Catamarca",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Argentina/ComodRivadavia",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Catamarca",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Argentina/Cordoba",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Cordoba",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Argentina/Jujuy",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Jujuy",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Argentina/La_Rioja",
"type": "reg",
"size": 1109,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22784151,
"NumLink": 0,
"digest": "sha256:621f1bb3113e8ef3675827b6bcd718fc09f13157639ce8986c98bde382f3c957"
},
{
"name": "usr/share/zoneinfo/America/Argentina/Mendoza",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Mendoza",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Argentina/Rio_Gallegos",
"type": "reg",
"size": 1095,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22784897,
"NumLink": 0,
"digest": "sha256:29a13c6e65962f48b48ba73bffd41474a98257d1212584f32b3b51e5daa092a9"
},
{
"name": "usr/share/zoneinfo/America/Argentina/Salta",
"type": "reg",
"size": 1067,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22785586,
"NumLink": 0,
"digest": "sha256:113093ce6d0f2189f9a34183b0a1117b028c89761740700378f64c26ca60a884"
},
{
"name": "usr/share/zoneinfo/America/Argentina/San_Juan",
"type": "reg",
"size": 1109,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22786267,
"NumLink": 0,
"digest": "sha256:01d61c864a3edb46fc9c0245bda0c551c6b12253e962178ad42b9fa3d2ea96a3"
},
{
"name": "usr/share/zoneinfo/America/Argentina/San_Luis",
"type": "reg",
"size": 1125,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22786966,
"NumLink": 0,
"digest": "sha256:954240d24edafb8de8aec83efe5f926bde08620d4564105c68b5e59afcddcaf4"
},
{
"name": "usr/share/zoneinfo/America/Argentina/Tucuman",
"type": "reg",
"size": 1123,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22787667,
"NumLink": 0,
"digest": "sha256:82abb66f092dc63c03be1c298b65a8266ec6a638d17c00051701138b9bf7e00e"
},
{
"name": "usr/share/zoneinfo/America/Argentina/Ushuaia",
"type": "reg",
"size": 1095,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22788378,
"NumLink": 0,
"digest": "sha256:1caf03ba65f9d802fd8160f103c613ba9266500ef95956af6af8636ac4f5a7e3"
},
{
"name": "usr/share/zoneinfo/America/Aruba",
"type": "reg",
"size": 212,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22789059,
"NumLink": 0,
"digest": "sha256:023d877932f35d889772f561f79e266c8541b984e0ce2bd257723aafc7d883c1"
},
{
"name": "usr/share/zoneinfo/America/Asuncion",
"type": "reg",
"size": 2063,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22789263,
"NumLink": 0,
"digest": "sha256:f84a9ad05fdbc043ec0d57f60c0d9dde2c4979ff53de5f4b09520174f908c8c1"
},
{
"name": "usr/share/zoneinfo/America/Atikokan",
"type": "reg",
"size": 345,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22790378,
"NumLink": 0,
"digest": "sha256:c30226b472b507b5a30b69557d56f24fcc8e9467110e4d3ec15c2c51de5d245c"
},
{
"name": "usr/share/zoneinfo/America/Atka",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Adak",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Bahia",
"type": "reg",
"size": 1036,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22790689,
"NumLink": 0,
"digest": "sha256:2907605adbb291db6f65ffa990fb28e2150b37896752171c664a9cff6b4a890d"
},
{
"name": "usr/share/zoneinfo/America/Bahia_Banderas",
"type": "reg",
"size": 1588,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22791336,
"NumLink": 0,
"digest": "sha256:9d0e0d9f6168635a5f98550a170d423854ff5b662ce0d874c3cba77199bf1cbd"
},
{
"name": "usr/share/zoneinfo/America/Barbados",
"type": "reg",
"size": 344,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22792245,
"NumLink": 0,
"digest": "sha256:9b1a7857a01ae2a3ae9a51e0b40cfbed91bac468579ed3c08b54466276bbb1fa"
},
{
"name": "usr/share/zoneinfo/America/Belem",
"type": "reg",
"size": 588,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22792521,
"NumLink": 0,
"digest": "sha256:cab597d9b0946c0542e1e7efc41de81ffb67e5ad1b982ba7512e7c3b7abf9712"
},
{
"name": "usr/share/zoneinfo/America/Belize",
"type": "reg",
"size": 978,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22792941,
"NumLink": 0,
"digest": "sha256:eb4e57a8c657d0c253ef4e0a5af118c928e6e5875564081a714563addfd4b31a"
},
{
"name": "usr/share/zoneinfo/America/Blanc-Sablon",
"type": "reg",
"size": 307,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22793566,
"NumLink": 0,
"digest": "sha256:c7d383bfb7e85331030f8091a9055a5f424e3674407ca0c76cce06b5a0316fdb"
},
{
"name": "usr/share/zoneinfo/America/Boa_Vista",
"type": "reg",
"size": 644,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22793828,
"NumLink": 0,
"digest": "sha256:593ec3794b5fdc2fdb6141cf37eb943ccaf64818224f4c6c9eb64e001562eecc"
},
{
"name": "usr/share/zoneinfo/America/Bogota",
"type": "reg",
"size": 257,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22794278,
"NumLink": 0,
"digest": "sha256:7035a44b8a40432315da50a5db760cabde6fb2ad4004e1416645a04841c72b79"
},
{
"name": "usr/share/zoneinfo/America/Boise",
"type": "reg",
"size": 2403,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22794506,
"NumLink": 0,
"digest": "sha256:33353ef05ccdda7debe757cf865ee7bd78031f38c6797b8fbfc11f9010f55a10"
},
{
"name": "usr/share/zoneinfo/America/Buenos_Aires",
"type": "reg",
"size": 1095,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22795805,
"NumLink": 0,
"digest": "sha256:734a334127e43faddb2bc722a0e508719105a4aa25897c2a280fb1dfec261113"
},
{
"name": "usr/share/zoneinfo/America/Cambridge_Bay",
"type": "reg",
"size": 2098,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22796491,
"NumLink": 0,
"digest": "sha256:0aeaed5b104ee99ab13a9f5b5a7aaf7f6c9a7f59bdefd15d3c9951551c7b5f6f"
},
{
"name": "usr/share/zoneinfo/America/Campo_Grande",
"type": "reg",
"size": 2016,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22797635,
"NumLink": 0,
"digest": "sha256:ef672b3c0637f5e677307da2759bc261afa4f07e52fe45197f85b211f8bb9364"
},
{
"name": "usr/share/zoneinfo/America/Cancun",
"type": "reg",
"size": 816,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22798770,
"NumLink": 0,
"digest": "sha256:d22316873f309799c6f97fefb8a0a8897d0df4eb376f84bb0b71602ec240d04f"
},
{
"name": "usr/share/zoneinfo/America/Caracas",
"type": "reg",
"size": 275,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22799294,
"NumLink": 0,
"digest": "sha256:2f9e7150c7461615db09d15ae2ac14d42d3fa06327f57fa7a4064a9bedba2992"
},
{
"name": "usr/share/zoneinfo/America/Catamarca",
"type": "reg",
"size": 1095,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22799541,
"NumLink": 0,
"digest": "sha256:81e2c01af75fe321d32147db82facd088b6c191e9ea4cc3f8dd0ca36d90c1d12"
},
{
"name": "usr/share/zoneinfo/America/Cayenne",
"type": "reg",
"size": 210,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22800223,
"NumLink": 0,
"digest": "sha256:0afd6f15f47edfdfdeb34d7cfd8e1c1da974259085acec6fa03ad2f2c27f138a"
},
{
"name": "usr/share/zoneinfo/America/Cayman",
"type": "reg",
"size": 203,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22800426,
"NumLink": 0,
"digest": "sha256:fc4fbba14653a3186f3c5b719f7b9bf7d8a5824401064cf6d508205592e55a9f"
},
{
"name": "usr/share/zoneinfo/America/Chicago",
"type": "reg",
"size": 3585,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22800632,
"NumLink": 0,
"digest": "sha256:143f29b957173a46008187230a38125bd3a03b3dbcba0dc1d1b8661331f71693"
},
{
"name": "usr/share/zoneinfo/America/Chihuahua",
"type": "reg",
"size": 1522,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22802506,
"NumLink": 0,
"digest": "sha256:a7d150e716db5b1c96bb0e50252b80306e1d9e8bcc2bf90a3ae7071906e71513"
},
{
"name": "usr/share/zoneinfo/America/Coral_Harbour",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Atikokan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Cordoba",
"type": "reg",
"size": 1095,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22803431,
"NumLink": 0,
"digest": "sha256:aa24a2867e3cdce8158a4abb12118fff2a65c7ead38edbb8d56a43b8345b3600"
},
{
"name": "usr/share/zoneinfo/America/Costa_Rica",
"type": "reg",
"size": 341,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22804110,
"NumLink": 0,
"digest": "sha256:4e6ff29a776e053226bf590ebe734896f9150d69074f22a16a1c7199a1484ec5"
},
{
"name": "usr/share/zoneinfo/America/Creston",
"type": "reg",
"size": 233,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22804393,
"NumLink": 0,
"digest": "sha256:97b74beec3714be518219f24533cc21edf9b53d01f4ab792a6fe0f88973449eb"
},
{
"name": "usr/share/zoneinfo/America/Cuiaba",
"type": "reg",
"size": 1988,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22804608,
"NumLink": 0,
"digest": "sha256:1df651a890eb83ad31e503fa10697555f735974ae4501b48735d67cddf5f4a2b"
},
{
"name": "usr/share/zoneinfo/America/Curacao",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Aruba",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Danmarkshavn",
"type": "reg",
"size": 712,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22805784,
"NumLink": 0,
"digest": "sha256:087e39cd6f10b6944b68b1de557289ef33769467f1b29806b96a16cf8e438e6e"
},
{
"name": "usr/share/zoneinfo/America/Dawson",
"type": "reg",
"size": 2093,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22806252,
"NumLink": 0,
"digest": "sha256:2d01b4b6114df8b0ed8aa64654f777842f20729818f1566a5f341d73ef7093dc"
},
{
"name": "usr/share/zoneinfo/America/Dawson_Creek",
"type": "reg",
"size": 1059,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22807410,
"NumLink": 0,
"digest": "sha256:29d0245bc04dadcbb196a3b6a81bace1696451166a7417d5bb2a8610f37ec5ba"
},
{
"name": "usr/share/zoneinfo/America/Denver",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Navajo",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Detroit",
"type": "reg",
"size": 2188,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22808104,
"NumLink": 0,
"digest": "sha256:d9037aded390d30e2e30f2302062ad6042651b261392872ef772cad5afaed92a"
},
{
"name": "usr/share/zoneinfo/America/Dominica",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Edmonton",
"type": "reg",
"size": 2402,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22809338,
"NumLink": 0,
"digest": "sha256:6678c23f86573b6c95e6c748a317aa8ce90f68636bcdad18db081e70986fb54e"
},
{
"name": "usr/share/zoneinfo/America/Eirunepe",
"type": "reg",
"size": 676,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22810634,
"NumLink": 0,
"digest": "sha256:1cf7b8dcba3182d527e72c1307bb6fc6908e8722d7ae10ff6522608df38d70c3"
},
{
"name": "usr/share/zoneinfo/America/El_Salvador",
"type": "reg",
"size": 250,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22811100,
"NumLink": 0,
"digest": "sha256:63215b213a31505bfd545fd52b11214cb614f13f0f55911f414edb44286e7f8a"
},
{
"name": "usr/share/zoneinfo/America/Ensenada",
"type": "reg",
"size": 2356,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22811333,
"NumLink": 0,
"digest": "sha256:d827b95b4fa16b8c56d9a1636341c9112657e567ae84b37a9bfca133ae31babb"
},
{
"name": "usr/share/zoneinfo/America/Fort_Nelson",
"type": "reg",
"size": 2249,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22812593,
"NumLink": 0,
"digest": "sha256:e7ce9a01cdd313d20352112430341c262e1b90182de490fc95c132daac118ce7"
},
{
"name": "usr/share/zoneinfo/America/Fort_Wayne",
"type": "reg",
"size": 1675,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22813803,
"NumLink": 0,
"digest": "sha256:55c2f3feb241f88435e9876e76e2c69ddfd0dfd36a273b551ff480e2cfad99fe"
},
{
"name": "usr/share/zoneinfo/America/Fortaleza",
"type": "reg",
"size": 728,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22814775,
"NumLink": 0,
"digest": "sha256:dc020e10d5350251d78744ffa59f54a7fc47eb521e3813a4811d614067a929ba"
},
{
"name": "usr/share/zoneinfo/America/Glace_Bay",
"type": "reg",
"size": 2206,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22815275,
"NumLink": 0,
"digest": "sha256:e6af24e3b9f1240abb8618fac326ee3a1ecccd25d2fa4936b2a28b0b0880e550"
},
{
"name": "usr/share/zoneinfo/America/Godthab",
"type": "reg",
"size": 1878,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22816473,
"NumLink": 0,
"digest": "sha256:69351ff5190c3330d5910eb4d23211674dca4a120c6636bd535d6b6687f96b23"
},
{
"name": "usr/share/zoneinfo/America/Goose_Bay",
"type": "reg",
"size": 3219,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22817492,
"NumLink": 0,
"digest": "sha256:f0c06c6a1841cdc37bfb311c11caba1c974d0d6c27725c04b69657e7ca112a49"
},
{
"name": "usr/share/zoneinfo/America/Grand_Turk",
"type": "reg",
"size": 1881,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22819220,
"NumLink": 0,
"digest": "sha256:90848fabb8bcfdbb4e66f5a624c4e7fa88962f16f8b6005f527cd84abebfa574"
},
{
"name": "usr/share/zoneinfo/America/Grenada",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Guadeloupe",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Guatemala",
"type": "reg",
"size": 306,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22820334,
"NumLink": 0,
"digest": "sha256:795cc25e5ffe825a8b3d86eed98ad5f9bb9f6d152df2b624f0e2a63b17f0ee43"
},
{
"name": "usr/share/zoneinfo/America/Guayaquil",
"type": "reg",
"size": 257,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22820599,
"NumLink": 0,
"digest": "sha256:b144b87141ba1c6e6e7e0da5b35ec3c50d57dda3b2be94caf3186062563ccf35"
},
{
"name": "usr/share/zoneinfo/America/Guyana",
"type": "reg",
"size": 252,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22820833,
"NumLink": 0,
"digest": "sha256:4b38a7f5eb21df41f8efbca839606a2b76dcb13f13aecab80956d6cb1196a76a"
},
{
"name": "usr/share/zoneinfo/America/Halifax",
"type": "reg",
"size": 3438,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22821055,
"NumLink": 0,
"digest": "sha256:627e18218c6f3c3f446c4970abe8164672e2a7ba94bcf841b1e06af5089b94ea"
},
{
"name": "usr/share/zoneinfo/America/Havana",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Cuba",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Hermosillo",
"type": "reg",
"size": 454,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22822913,
"NumLink": 0,
"digest": "sha256:ec6eb21d068f1ca8e80a9e825206ad1b5a04b1e8abb2dd981c6c90b7686b8820"
},
{
"name": "usr/share/zoneinfo/America/Indiana/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Indiana/Indianapolis",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Fort_Wayne",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Indiana/Knox",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Knox_IN",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Indiana/Marengo",
"type": "reg",
"size": 1731,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22823353,
"NumLink": 0,
"digest": "sha256:64bb87669a7c161454b283e5855e28de7655450680cc403eea0572e580eb2625"
},
{
"name": "usr/share/zoneinfo/America/Indiana/Petersburg",
"type": "reg",
"size": 1913,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22824368,
"NumLink": 0,
"digest": "sha256:3eb5dd510d677f9118ec4bca7892db4ed181722fbaf94ef9cb1a441718400321"
},
{
"name": "usr/share/zoneinfo/America/Indiana/Tell_City",
"type": "reg",
"size": 1735,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22825472,
"NumLink": 0,
"digest": "sha256:268c538689f4815f86ae1d100b4b072a785d5e68b03de71b74f437135bc52843"
},
{
"name": "usr/share/zoneinfo/America/Indiana/Vevay",
"type": "reg",
"size": 1423,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22826469,
"NumLink": 0,
"digest": "sha256:5cf728ac267cce51bddf1875219bf0e800d5aaa17794dc2c7408293773f516e5"
},
{
"name": "usr/share/zoneinfo/America/Indiana/Vincennes",
"type": "reg",
"size": 1703,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22827322,
"NumLink": 0,
"digest": "sha256:8a9db38f7575f9e2a624c9466128927caef0eea7e9a3841679cd6eb129b019d3"
},
{
"name": "usr/share/zoneinfo/America/Indiana/Winamac",
"type": "reg",
"size": 1787,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22828318,
"NumLink": 0,
"digest": "sha256:d2dc919207b8db95bd82aff68b8a498a6d3bec2c33220ba5381ebca171dcc095"
},
{
"name": "usr/share/zoneinfo/America/Indianapolis",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Fort_Wayne",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Inuvik",
"type": "reg",
"size": 1928,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22829403,
"NumLink": 0,
"digest": "sha256:ea64cbc0bf92cf88c8917222f11db8838f42e2d41360cd81bb93f669a2bc685b"
},
{
"name": "usr/share/zoneinfo/America/Iqaluit",
"type": "reg",
"size": 2046,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22830466,
"NumLink": 0,
"digest": "sha256:eb8797250b8bd8c3d09893b4f261ffb1bedd668869a051c8e2c80f6c0d63408c"
},
{
"name": "usr/share/zoneinfo/America/Jamaica",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Jamaica",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Jujuy",
"type": "reg",
"size": 1067,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22831625,
"NumLink": 0,
"digest": "sha256:9e5e87b452a3cdc3f010537e096150239b10ee67ec1c438b7864096a96758992"
},
{
"name": "usr/share/zoneinfo/America/Juneau",
"type": "reg",
"size": 2362,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22832294,
"NumLink": 0,
"digest": "sha256:8185913ee68f7ec72cd98efcee68d1b6bd0c304e303a2dc613b06cd97e6333c8"
},
{
"name": "usr/share/zoneinfo/America/Kentucky/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Kentucky/Louisville",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Louisville",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Kentucky/Monticello",
"type": "reg",
"size": 2361,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22833680,
"NumLink": 0,
"digest": "sha256:b87081e46bbe688816a7e03cfdc5b4efe10bd6b92cb5a780df6b0c86af4c1a37"
},
{
"name": "usr/share/zoneinfo/America/Knox_IN",
"type": "reg",
"size": 2437,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22834964,
"NumLink": 0,
"digest": "sha256:9e75dd6c52c5339c8e2b195ff8ac6a7212e2c5e84b2be3023cc355972c20d856"
},
{
"name": "usr/share/zoneinfo/America/Kralendijk",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Aruba",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/La_Paz",
"type": "reg",
"size": 243,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22836334,
"NumLink": 0,
"digest": "sha256:379b1f2fd14b7f6b584e9e7030f45b580a84fc7c0c96314006c76c0afd620532"
},
{
"name": "usr/share/zoneinfo/America/Lima",
"type": "reg",
"size": 417,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22836556,
"NumLink": 0,
"digest": "sha256:4adf338b94b1680c4e114ffa17669dba4363a08009d6e21f090610e55aaa30a1"
},
{
"name": "usr/share/zoneinfo/America/Los_Angeles",
"type": "reg",
"size": 2845,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22836893,
"NumLink": 0,
"digest": "sha256:fea9d66ff6522e69d22073dc4e84179b7cac2e372b398dc2fafdfdb61a9eb2e1"
},
{
"name": "usr/share/zoneinfo/America/Louisville",
"type": "reg",
"size": 2781,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22838408,
"NumLink": 0,
"digest": "sha256:779c6a6c566774f94f754dad8936ea7f7f6afb028ed9c76b74d8f375355439ee"
},
{
"name": "usr/share/zoneinfo/America/Lower_Princes",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Aruba",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Maceio",
"type": "reg",
"size": 756,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22839952,
"NumLink": 0,
"digest": "sha256:c640e22f9bb0f3c376f8a3440622e7a29de3b48d71d06d900ae66e0e2ea4f392"
},
{
"name": "usr/share/zoneinfo/America/Managua",
"type": "reg",
"size": 463,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22840458,
"NumLink": 0,
"digest": "sha256:a3c1f00bc879ee84e623d25252f1fb8ffd8cf43cd9c8b8bf12b6b5eda8045683"
},
{
"name": "usr/share/zoneinfo/America/Manaus",
"type": "reg",
"size": 616,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22840809,
"NumLink": 0,
"digest": "sha256:7a411c339329e8984d50258dd3bbf44e06e4a86db28d1b34a27c056f2a00686a"
},
{
"name": "usr/share/zoneinfo/America/Marigot",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Martinique",
"type": "reg",
"size": 257,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22841288,
"NumLink": 0,
"digest": "sha256:e087c3e4ae20234a08667adcf6ab4cd03ab3aeee7e035278f271ddb0b65cfff2"
},
{
"name": "usr/share/zoneinfo/America/Matamoros",
"type": "reg",
"size": 1416,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22841520,
"NumLink": 0,
"digest": "sha256:4398d831df4bfcfd9bafa22ac35cd55dbb7dfd5f25c138452e8adf275ea11735"
},
{
"name": "usr/share/zoneinfo/America/Mazatlan",
"type": "reg",
"size": 1564,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22842351,
"NumLink": 0,
"digest": "sha256:9340b719b250773262cec62e771d121105aed168836723dfc305e30bb04cfbb1"
},
{
"name": "usr/share/zoneinfo/America/Mendoza",
"type": "reg",
"size": 1095,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22843245,
"NumLink": 0,
"digest": "sha256:38933e04d16131a11b1052c7daba2794b994030dd485142d8766f7f13d105b61"
},
{
"name": "usr/share/zoneinfo/America/Menominee",
"type": "reg",
"size": 2283,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22843927,
"NumLink": 0,
"digest": "sha256:46ced580e74834d2c68a80a60ae05a0b715014bb2b4dad67cf994ac20ce2ac22"
},
{
"name": "usr/share/zoneinfo/America/Merida",
"type": "reg",
"size": 1456,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22845164,
"NumLink": 0,
"digest": "sha256:ecdcf4b29f7b439152b9e9b559f04d7d4ba759dd942c2fd685a4ee36798fc8d1"
},
{
"name": "usr/share/zoneinfo/America/Metlakatla",
"type": "reg",
"size": 1418,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22846006,
"NumLink": 0,
"digest": "sha256:49ad5e4fc549d0db2e2cc6dcd2936ecb12b8591dbc3a806ffcf843df8240f6a0"
},
{
"name": "usr/share/zoneinfo/America/Mexico_City",
"type": "reg",
"size": 1618,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22846865,
"NumLink": 0,
"digest": "sha256:88bc3fcb1a92ef053e0af4af9053ecbf12855fdf18f859b2a54d826dbfacb655"
},
{
"name": "usr/share/zoneinfo/America/Miquelon",
"type": "reg",
"size": 1682,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22847792,
"NumLink": 0,
"digest": "sha256:41f2e13c925042a466b8cdcc8bd1dced7e9247cc893bcde20b91d98ce10d316f"
},
{
"name": "usr/share/zoneinfo/America/Moncton",
"type": "reg",
"size": 3163,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22848744,
"NumLink": 0,
"digest": "sha256:1300d5a93bb376cbf23a890a23ea05fa2b229486e7eb7d5959c7834260fbb7b7"
},
{
"name": "usr/share/zoneinfo/America/Monterrey",
"type": "reg",
"size": 1416,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22850420,
"NumLink": 0,
"digest": "sha256:89dadca852ebf52e4405c958132255c25fb195604d7df9b844bf50b48a0865d8"
},
{
"name": "usr/share/zoneinfo/America/Montevideo",
"type": "reg",
"size": 1550,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22851248,
"NumLink": 0,
"digest": "sha256:b58bb9b5a2c6f4f5c20067ff6b4a56037962fa37e5264c521d340eb7323e5b3a"
},
{
"name": "usr/share/zoneinfo/America/Montreal",
"type": "reg",
"size": 3503,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22852152,
"NumLink": 0,
"digest": "sha256:842f7a103dfac9c0c2c33c9cc392a113d266ac064c5c7497883ab41b797ce194"
},
{
"name": "usr/share/zoneinfo/America/Montserrat",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Nassau",
"type": "reg",
"size": 2284,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22854046,
"NumLink": 0,
"digest": "sha256:4c999dbcc6c6045acbedf6fd48f5b70b20eb8d8b0bd9612d2bea88dc03aa9f4d"
},
{
"name": "usr/share/zoneinfo/America/New_York",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../posixrules",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Nipigon",
"type": "reg",
"size": 2131,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22855326,
"NumLink": 0,
"digest": "sha256:7100ca4bf3044211ff8e770dade20b683880f3965f146ebbdd72c644aaec7c37"
},
{
"name": "usr/share/zoneinfo/America/Nome",
"type": "reg",
"size": 2376,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22856491,
"NumLink": 0,
"digest": "sha256:d312bc797eb1b384ccfba0c40822d50b2e0abf37d5daa43dd3e255fdc7573b00"
},
{
"name": "usr/share/zoneinfo/America/Noronha",
"type": "reg",
"size": 728,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22857800,
"NumLink": 0,
"digest": "sha256:4b71d8f0d772be18ab8425cff1be9d927f340511a91e248e962745ccc76ced79"
},
{
"name": "usr/share/zoneinfo/America/North_Dakota/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/North_Dakota/Beulah",
"type": "reg",
"size": 2389,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22858337,
"NumLink": 0,
"digest": "sha256:f604eb42f0197f5edefcd6d43051a0b64e6e1327dbd2d3cfa94d0348b23e1fa8"
},
{
"name": "usr/share/zoneinfo/America/North_Dakota/Center",
"type": "reg",
"size": 2389,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22859652,
"NumLink": 0,
"digest": "sha256:08efea4af9a2b6a5ab25e86116cfcf4b93c16ba9b7b22f762d9cb3481ecf3ac8"
},
{
"name": "usr/share/zoneinfo/America/North_Dakota/New_Salem",
"type": "reg",
"size": 2389,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22860971,
"NumLink": 0,
"digest": "sha256:c5a43eb6776c326b1250f914f00e9e00e121b9b51f63665f90f245376da2017a"
},
{
"name": "usr/share/zoneinfo/America/Ojinaga",
"type": "reg",
"size": 1522,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22862271,
"NumLink": 0,
"digest": "sha256:a07ff8fe3ee3531f866afca0f7937d86acec1826fb3eaa655d54300ec22dc713"
},
{
"name": "usr/share/zoneinfo/America/Panama",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Cayman",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Pangnirtung",
"type": "reg",
"size": 2108,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22863195,
"NumLink": 0,
"digest": "sha256:1b6a497653df9220c30a022d87ec2aa1320af9e51bb3cdc48756e72d770a6738"
},
{
"name": "usr/share/zoneinfo/America/Paramaribo",
"type": "reg",
"size": 282,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22864347,
"NumLink": 0,
"digest": "sha256:95afc0b1d45e18abc0fc3a8952500b443a1c49174b6a61de4fb09c8a1284131c"
},
{
"name": "usr/share/zoneinfo/America/Phoenix",
"type": "reg",
"size": 353,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22864579,
"NumLink": 0,
"digest": "sha256:9c013ecf82b6ed1dde235b5fc983fe9d23c8d56d610323f7c94c6ba3cd7b4564"
},
{
"name": "usr/share/zoneinfo/America/Port-au-Prince",
"type": "reg",
"size": 1455,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22864881,
"NumLink": 0,
"digest": "sha256:b0b60ad1c557c41596e0fcc7fc8b9b39444c78295b147cee495e29985a225da6"
},
{
"name": "usr/share/zoneinfo/America/Port_of_Spain",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Porto_Acre",
"type": "reg",
"size": 648,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22865790,
"NumLink": 0,
"digest": "sha256:f3461eaf7c44f1bf84193b3889dd3ff27901f8d0c4412afe77f917ce1afb2b85"
},
{
"name": "usr/share/zoneinfo/America/Porto_Velho",
"type": "reg",
"size": 588,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22866239,
"NumLink": 0,
"digest": "sha256:526e90a17542252332361dbaf136103513731b094788e8705f5df5d981c1fa4f"
},
{
"name": "usr/share/zoneinfo/America/Puerto_Rico",
"type": "reg",
"size": 255,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22866673,
"NumLink": 0,
"digest": "sha256:7eee44e1cb7ac885fdd5042815c548bcb6ca84cff8de007e52b53c0b9ad53f19"
},
{
"name": "usr/share/zoneinfo/America/Punta_Arenas",
"type": "reg",
"size": 1897,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22866911,
"NumLink": 0,
"digest": "sha256:12d157ad893061c7f49cb94cc6a0ec3b20a11ef71fdc150c0baf4c39d97836e6"
},
{
"name": "usr/share/zoneinfo/America/Rainy_River",
"type": "reg",
"size": 2131,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22867972,
"NumLink": 0,
"digest": "sha256:9a7b8dd4c968de31fe35fd9fe075fad8afc15892067119f638b5aef0d2288d83"
},
{
"name": "usr/share/zoneinfo/America/Rankin_Inlet",
"type": "reg",
"size": 1930,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22869151,
"NumLink": 0,
"digest": "sha256:b3ae38ccfa3091ebd8037fd5e1fd2715a72008932f94526a15fb1aa7fae722dc"
},
{
"name": "usr/share/zoneinfo/America/Recife",
"type": "reg",
"size": 728,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22870219,
"NumLink": 0,
"digest": "sha256:a87a7203eb4cb49346d80581463e0b8d0e8fde53f3801cc98c12d26d016708d7"
},
{
"name": "usr/share/zoneinfo/America/Regina",
"type": "reg",
"size": 994,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22870714,
"NumLink": 0,
"digest": "sha256:929d07457407529637626d09f5ca975b4f05801f35d0bfac4e3b0027efee6776"
},
{
"name": "usr/share/zoneinfo/America/Resolute",
"type": "reg",
"size": 1930,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22871326,
"NumLink": 0,
"digest": "sha256:c529141807704725b059187b9f458080f911b6dd4313e74139d2c7dac287c901"
},
{
"name": "usr/share/zoneinfo/America/Rio_Branco",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Porto_Acre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Rosario",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Cordoba",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Santa_Isabel",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Ensenada",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Santarem",
"type": "reg",
"size": 618,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22872513,
"NumLink": 0,
"digest": "sha256:d126927c9419ef3afbb32be4fc3600d5f44c022a4e8472cf2532863ca9787d0b"
},
{
"name": "usr/share/zoneinfo/America/Santiago",
"type": "reg",
"size": 2524,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22872951,
"NumLink": 0,
"digest": "sha256:9250d4f8f07d8cee14e8ae9e2ece611b7ea9bf69bca4fde77788346477b14658"
},
{
"name": "usr/share/zoneinfo/America/Santo_Domingo",
"type": "reg",
"size": 491,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22874310,
"NumLink": 0,
"digest": "sha256:adf349e4c7314aaa699c4893c589b077f6dfa7d9a54ea9eae459658f9af41dc7"
},
{
"name": "usr/share/zoneinfo/America/Sao_Paulo",
"type": "reg",
"size": 2016,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22874684,
"NumLink": 0,
"digest": "sha256:e54f2d187b08effd2d1ac9c4f66d6bc0a3136704887928557a5c7f74c23af408"
},
{
"name": "usr/share/zoneinfo/America/Scoresbysund",
"type": "reg",
"size": 1916,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22875829,
"NumLink": 0,
"digest": "sha256:2c37c6a1f497e2c993dadbd3aab2bf04ab72b626c3abde7ac29d55d4601f967e"
},
{
"name": "usr/share/zoneinfo/America/Shiprock",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Navajo",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Sitka",
"type": "reg",
"size": 2350,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22876913,
"NumLink": 0,
"digest": "sha256:ba79b89ecd8e64dba4119f2c5af2373167557c4bc71b7b134ab252e0b7485fdb"
},
{
"name": "usr/share/zoneinfo/America/St_Barthelemy",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/St_Johns",
"type": "reg",
"size": 3664,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22878264,
"NumLink": 0,
"digest": "sha256:2b960a58d6d3f6a272707f941f55b15b8ba3fd0fd55f8680ea84af6b1e98bae0"
},
{
"name": "usr/share/zoneinfo/America/St_Kitts",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/St_Lucia",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/St_Thomas",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/St_Vincent",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Swift_Current",
"type": "reg",
"size": 574,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22880313,
"NumLink": 0,
"digest": "sha256:6b6029f04ac07c382e20d315da7a72d9204d813ef83585f042562e7f6788a78f"
},
{
"name": "usr/share/zoneinfo/America/Tegucigalpa",
"type": "reg",
"size": 278,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22880722,
"NumLink": 0,
"digest": "sha256:fd295a9cc689a966786b6e0ec9d0f101796ac8b4f04a6f529b192937df3a2115"
},
{
"name": "usr/share/zoneinfo/America/Thule",
"type": "reg",
"size": 1528,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22880965,
"NumLink": 0,
"digest": "sha256:9a474a1fc764343470d310369cdbf6d7007ea611116e25bea68f60ddf5a6cd68"
},
{
"name": "usr/share/zoneinfo/America/Thunder_Bay",
"type": "reg",
"size": 2211,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22881853,
"NumLink": 0,
"digest": "sha256:aef45e1474369f52e1afb1cd7f312e9f035ca13686092cf2351a353133e1cee6"
},
{
"name": "usr/share/zoneinfo/America/Tijuana",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Ensenada",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Toronto",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Montreal",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Tortola",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Vancouver",
"type": "reg",
"size": 2901,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22883151,
"NumLink": 0,
"digest": "sha256:8b414b9a20be09ad61214deeb2dbff3bb1bd1e9d351a79aceae757fa0fbe91dd"
},
{
"name": "usr/share/zoneinfo/America/Virgin",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/America/Whitehorse",
"type": "reg",
"size": 2093,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22884733,
"NumLink": 0,
"digest": "sha256:1864fe33d51e58780a5c8400a47e7c42e6e6655abe49a82cc11a461059058313"
},
{
"name": "usr/share/zoneinfo/America/Winnipeg",
"type": "reg",
"size": 2891,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22885882,
"NumLink": 0,
"digest": "sha256:77be5c08f6f8ebe5330fb86a60c4447ea2549620609f4ef6a7a7a68d1a12d9d6"
},
{
"name": "usr/share/zoneinfo/America/Yakutat",
"type": "reg",
"size": 2314,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22887419,
"NumLink": 0,
"digest": "sha256:07f189f49873b1392989ecbde19d19d33cd6c16953bf7e6b927ca2f1040f7106"
},
{
"name": "usr/share/zoneinfo/America/Yellowknife",
"type": "reg",
"size": 1980,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22888688,
"NumLink": 0,
"digest": "sha256:3140de7fe4136fb4920e8bb8ed8d707256e78c672edfce1657ae22672de68768"
},
{
"name": "usr/share/zoneinfo/Antarctica/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Antarctica/Casey",
"type": "reg",
"size": 297,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22889822,
"NumLink": 0,
"digest": "sha256:235e572b63f7ae91a9cd1f352fef86fb61b506465f418e0127c5ad86c88e2253"
},
{
"name": "usr/share/zoneinfo/Antarctica/Davis",
"type": "reg",
"size": 297,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22890070,
"NumLink": 0,
"digest": "sha256:ee123489006dfbc147e65020c9836054e7fcb494d150582464bc2060d409a16e"
},
{
"name": "usr/share/zoneinfo/Antarctica/DumontDUrville",
"type": "reg",
"size": 202,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22890326,
"NumLink": 0,
"digest": "sha256:26fa92c4c643cc230c34b1641355726fddba6587181ddf3c3b1f1a63149e64c7"
},
{
"name": "usr/share/zoneinfo/Antarctica/Macquarie",
"type": "reg",
"size": 1529,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22890530,
"NumLink": 0,
"digest": "sha256:19fe3ba56b287f6117a93b06d16402bfd5386deb06676349c1b8501d497f5a90"
},
{
"name": "usr/share/zoneinfo/Antarctica/Mawson",
"type": "reg",
"size": 211,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22891378,
"NumLink": 0,
"digest": "sha256:5779e99cfe950860cd5fadb21f21c8a9a3ff264a0e1f06a41de944e8d62d68b0"
},
{
"name": "usr/share/zoneinfo/Antarctica/McMurdo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../NZ",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Antarctica/Palmer",
"type": "reg",
"size": 1418,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22891618,
"NumLink": 0,
"digest": "sha256:59261d3bbe6da255add2fc7fb8eb711b6c4a718de8982cde36806c2a766f74eb"
},
{
"name": "usr/share/zoneinfo/Antarctica/Rothera",
"type": "reg",
"size": 172,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22892432,
"NumLink": 0,
"digest": "sha256:c15f59336f5a90350fb5c13cfe7e4836a54d387f7b7248eaa154ca7e21ca0fbb"
},
{
"name": "usr/share/zoneinfo/Antarctica/South_Pole",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../NZ",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Antarctica/Syowa",
"type": "reg",
"size": 173,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22892651,
"NumLink": 0,
"digest": "sha256:9b96709317fec4c34c541fd36cf4fe466da31a1f40e62905b1dff9217dcb85c0"
},
{
"name": "usr/share/zoneinfo/Antarctica/Troll",
"type": "reg",
"size": 1162,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22892830,
"NumLink": 0,
"digest": "sha256:5038d1f2d45e68bcadb7c6e3fce73f1b3ed6719c9ae9ff46225ffabe7eadd717"
},
{
"name": "usr/share/zoneinfo/Antarctica/Vostok",
"type": "reg",
"size": 173,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22893516,
"NumLink": 0,
"digest": "sha256:57d73835fefd936f16f953339a6f0195050e4d1499fb4e6551be1b90c25872c0"
},
{
"name": "usr/share/zoneinfo/Arctic/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Arctic/Longyearbyen",
"type": "reg",
"size": 2251,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22893736,
"NumLink": 0,
"digest": "sha256:0fa4e635da2b178fa3ea13ff3829c702844cf8bd69e16bca8e1d34dbd9249d01"
},
{
"name": "usr/share/zoneinfo/Asia/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Aden",
"type": "reg",
"size": 173,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22894950,
"NumLink": 0,
"digest": "sha256:baacbd8acbcee961f154dbbd5d4b40f82f124576b8191f08c7b1501ad7e78364"
},
{
"name": "usr/share/zoneinfo/Asia/Almaty",
"type": "reg",
"size": 1017,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22895128,
"NumLink": 0,
"digest": "sha256:038801e8f58d0f0166ed053af84d11fde658e6e997c100101a9f4330c8cde4e0"
},
{
"name": "usr/share/zoneinfo/Asia/Amman",
"type": "reg",
"size": 1877,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22895726,
"NumLink": 0,
"digest": "sha256:ba18a1f823f77e478e7030eeb82ddd71a5f3fae6efdf56325b7776304912da08"
},
{
"name": "usr/share/zoneinfo/Asia/Anadyr",
"type": "reg",
"size": 1208,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22896767,
"NumLink": 0,
"digest": "sha256:0bd4d0ef1c353f753b6e034aff4e004b77df3c92d1f0de648b07e03b1f90a253"
},
{
"name": "usr/share/zoneinfo/Asia/Aqtau",
"type": "reg",
"size": 1003,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22897471,
"NumLink": 0,
"digest": "sha256:4abd31e6c7b73775c51c2bc1c78de6a9901cd9e241627c2c8e311fd306896b0e"
},
{
"name": "usr/share/zoneinfo/Asia/Aqtobe",
"type": "reg",
"size": 1033,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22898074,
"NumLink": 0,
"digest": "sha256:062a98f4317d5dc621baa9a377caedb8bc5c48163930d33a6ad8747f5d2a49a9"
},
{
"name": "usr/share/zoneinfo/Asia/Ashgabat",
"type": "reg",
"size": 637,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22898687,
"NumLink": 0,
"digest": "sha256:205016ab50f3a20f3cdae2177d7e52df062d8fe6f9512adbece19c877575ec58"
},
{
"name": "usr/share/zoneinfo/Asia/Ashkhabad",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Ashgabat",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Atyrau",
"type": "reg",
"size": 1011,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22899160,
"NumLink": 0,
"digest": "sha256:b8946a7b9d150a7179177c8ad06e98f1e283a3496116f4c258e14f16d256f737"
},
{
"name": "usr/share/zoneinfo/Asia/Baghdad",
"type": "reg",
"size": 990,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22899769,
"NumLink": 0,
"digest": "sha256:21e1a6fad8d08e442b3f0ac5a30f8424f06207083a804d14dea2026432d0a80b"
},
{
"name": "usr/share/zoneinfo/Asia/Bahrain",
"type": "reg",
"size": 211,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22900378,
"NumLink": 0,
"digest": "sha256:7bced891a18abd8ea443dd4f0c9d7dd40270fca879e9a116f573b9f73152d5e1"
},
{
"name": "usr/share/zoneinfo/Asia/Baku",
"type": "reg",
"size": 1255,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22900573,
"NumLink": 0,
"digest": "sha256:aeb15bd0c24d68422bff995167cb959c7b060475f6b814cb25b76cd9bafc7b14"
},
{
"name": "usr/share/zoneinfo/Asia/Bangkok",
"type": "reg",
"size": 206,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22901290,
"NumLink": 0,
"digest": "sha256:2417678a4ccd8a6d6ee3cebc6eaadb5991cfaed89d1fee3a4c7f3166a0eb14f4"
},
{
"name": "usr/share/zoneinfo/Asia/Barnaul",
"type": "reg",
"size": 1241,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22901493,
"NumLink": 0,
"digest": "sha256:5d6b63755c1e59a9b18197b85b39ec174a26cbd578a76aed38f5d43483c3fac2"
},
{
"name": "usr/share/zoneinfo/Asia/Beirut",
"type": "reg",
"size": 2175,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22902219,
"NumLink": 0,
"digest": "sha256:abdfa509ed982455873c1035962d8642ae8b88ab75f7f1a9a4cf7eea5ce120ef"
},
{
"name": "usr/share/zoneinfo/Asia/Bishkek",
"type": "reg",
"size": 1031,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22903399,
"NumLink": 0,
"digest": "sha256:3ea68d645187bc48c3a502d987750306c1c45660d889e2bc641c4f562726319c"
},
{
"name": "usr/share/zoneinfo/Asia/Brunei",
"type": "reg",
"size": 215,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22904019,
"NumLink": 0,
"digest": "sha256:f08de1917e19626c3ff34c1cab734a420b3dd4ec3a3446df12b8960f9c7a420f"
},
{
"name": "usr/share/zoneinfo/Asia/Calcutta",
"type": "reg",
"size": 312,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22904220,
"NumLink": 0,
"digest": "sha256:c71d7bc10d52c64f59eae8eac701c1b156bbec3fd9fe750970bed15e9b408fa5"
},
{
"name": "usr/share/zoneinfo/Asia/Chita",
"type": "reg",
"size": 1243,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22904515,
"NumLink": 0,
"digest": "sha256:f01ce6d6e72d33ebd2982f99e1070a057a8a4991c60445bfe60fd0963ff4e7e5"
},
{
"name": "usr/share/zoneinfo/Asia/Choibalsan",
"type": "reg",
"size": 977,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22905226,
"NumLink": 0,
"digest": "sha256:f8e8eb47d086dee4559a9db7cca2add3be302c98203b2fbb356e342c5978a480"
},
{
"name": "usr/share/zoneinfo/Asia/Chongqing",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../PRC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Chungking",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../PRC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Colombo",
"type": "reg",
"size": 399,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22905870,
"NumLink": 0,
"digest": "sha256:711f68dfe65cec6d99564f98f631d093bc2285e3829aeefea3692002eb45c6e4"
},
{
"name": "usr/share/zoneinfo/Asia/Dacca",
"type": "reg",
"size": 356,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22906154,
"NumLink": 0,
"digest": "sha256:829ff841289d431205ea745311f94fd3c32196a23d492f9f458617e0b5800c30"
},
{
"name": "usr/share/zoneinfo/Asia/Damascus",
"type": "reg",
"size": 2320,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22906436,
"NumLink": 0,
"digest": "sha256:0f42d5702ee52944dde43071569de645a5d668a385c4a2e0cd8aa43d39d2ea21"
},
{
"name": "usr/share/zoneinfo/Asia/Dhaka",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Dacca",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Dili",
"type": "reg",
"size": 239,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22907724,
"NumLink": 0,
"digest": "sha256:8f845da70d08bcd62be5a9d0d80420b08936bfbe1e3012ed18fdcf90f152b91f"
},
{
"name": "usr/share/zoneinfo/Asia/Dubai",
"type": "reg",
"size": 173,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22907941,
"NumLink": 0,
"digest": "sha256:ee6d427e4bc5e3d6d97b348d4b0cf666636d2ea95bcb117490d8d8ab0641f2ed"
},
{
"name": "usr/share/zoneinfo/Asia/Dushanbe",
"type": "reg",
"size": 607,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22908121,
"NumLink": 0,
"digest": "sha256:68f352f8356e69f25650cb2f25bc7ece7209ef85e73796ce974807136678f175"
},
{
"name": "usr/share/zoneinfo/Asia/Famagusta",
"type": "reg",
"size": 2042,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22908539,
"NumLink": 0,
"digest": "sha256:4a3e66759c060ff5d9e338169781678161df5b9acd9aec6146f1d4d3cfd9030b"
},
{
"name": "usr/share/zoneinfo/Asia/Gaza",
"type": "reg",
"size": 2295,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22909640,
"NumLink": 0,
"digest": "sha256:3a9df8fb70a5130a724e84221e63bb18cc84cf5c6c8788778bee070eb7330ea2"
},
{
"name": "usr/share/zoneinfo/Asia/Harbin",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../PRC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Hebron",
"type": "reg",
"size": 2323,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22910928,
"NumLink": 0,
"digest": "sha256:93e1c96bff5d265217a68939ae160e86bbee16d96394d7c2b99589063435eea4"
},
{
"name": "usr/share/zoneinfo/Asia/Ho_Chi_Minh",
"type": "reg",
"size": 375,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22912197,
"NumLink": 0,
"digest": "sha256:23fb827e1435ea540aedbc7725a4ae82980205befe4aff833a0ad039dd14a73c"
},
{
"name": "usr/share/zoneinfo/Asia/Hong_Kong",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Hongkong",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Hovd",
"type": "reg",
"size": 907,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22912525,
"NumLink": 0,
"digest": "sha256:1561b7c3cc7a943046780584b726eaba320ccab4a46120b2c77af01dea9dfdf3"
},
{
"name": "usr/share/zoneinfo/Asia/Irkutsk",
"type": "reg",
"size": 1262,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22913088,
"NumLink": 0,
"digest": "sha256:102019f46d5e031cbc28520b08c080de899f5b1cadf93590787c1719e5a9dc4f"
},
{
"name": "usr/share/zoneinfo/Asia/Istanbul",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Turkey",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Jakarta",
"type": "reg",
"size": 392,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22913851,
"NumLink": 0,
"digest": "sha256:7ea1c3e53a0d3f40cb6d7724f1bacb4fca1cf0ae96d9ab42f00f2d30dc0dee3a"
},
{
"name": "usr/share/zoneinfo/Asia/Jayapura",
"type": "reg",
"size": 251,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22914145,
"NumLink": 0,
"digest": "sha256:c6fa24de2e83f471878b8e10ecd68828eb3aba1f49ce462dac7721661386eb0c"
},
{
"name": "usr/share/zoneinfo/Asia/Jerusalem",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Israel",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Kabul",
"type": "reg",
"size": 215,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22914400,
"NumLink": 0,
"digest": "sha256:2df091b8e1752a8c8d402b12037977392347626a34c8f011f179ede4079ded1b"
},
{
"name": "usr/share/zoneinfo/Asia/Kamchatka",
"type": "reg",
"size": 1184,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22914607,
"NumLink": 0,
"digest": "sha256:c5d9b980d12e91cfea342560702708c488aabe957c1fc2b7afc6c996518bef33"
},
{
"name": "usr/share/zoneinfo/Asia/Karachi",
"type": "reg",
"size": 417,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22915300,
"NumLink": 0,
"digest": "sha256:fc4b2a68ad79efadecf52f333fa19cbaa5dd084cdc9bf96ab8b65a75c559a370"
},
{
"name": "usr/share/zoneinfo/Asia/Kashgar",
"type": "reg",
"size": 173,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22915608,
"NumLink": 0,
"digest": "sha256:f582157ddba6cf2bf93e8cd57ecd61318e9faa7fff2abbd0e2e7897690373587"
},
{
"name": "usr/share/zoneinfo/Asia/Kathmandu",
"type": "reg",
"size": 224,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22915789,
"NumLink": 0,
"digest": "sha256:b7d597ba6228a77823c20d3b48f343ad78843ee888b1b17a40b05ffcc0da3913"
},
{
"name": "usr/share/zoneinfo/Asia/Katmandu",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Kathmandu",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Khandyga",
"type": "reg",
"size": 1297,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22916034,
"NumLink": 0,
"digest": "sha256:762a1f42993920d6475e1ef40b46ab6dbc65b6f48ea9537387ce418929c21a3b"
},
{
"name": "usr/share/zoneinfo/Asia/Kolkata",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Calcutta",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Krasnoyarsk",
"type": "reg",
"size": 1229,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22916824,
"NumLink": 0,
"digest": "sha256:ec5d6603322f8e1a0d7e5d7a1cf798b66e5def66b2ae79bbc8c961e976ad4a6b"
},
{
"name": "usr/share/zoneinfo/Asia/Kuala_Lumpur",
"type": "reg",
"size": 410,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22917526,
"NumLink": 0,
"digest": "sha256:521d864f401ae2eae2e9f0292aeecf38d2d6daf3e12b442795ee696e629f3700"
},
{
"name": "usr/share/zoneinfo/Asia/Kuching",
"type": "reg",
"size": 507,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22917820,
"NumLink": 0,
"digest": "sha256:2b4cb8160273db177c6ddb669dd648162730ef1a1b62d60abff510e37444628e"
},
{
"name": "usr/share/zoneinfo/Asia/Kuwait",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Aden",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Macao",
"type": "reg",
"size": 771,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22918221,
"NumLink": 0,
"digest": "sha256:c2b8cdca155a78c7ae34ca5c30185713c60b2496b01cfd31f998bb6bda16f3ce"
},
{
"name": "usr/share/zoneinfo/Asia/Macau",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Macao",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Magadan",
"type": "reg",
"size": 1244,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22918757,
"NumLink": 0,
"digest": "sha256:d6f81d26a42303d75910f98033f0fc147eed23c574b016995fa002f5075a3fd6"
},
{
"name": "usr/share/zoneinfo/Asia/Makassar",
"type": "reg",
"size": 288,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22919466,
"NumLink": 0,
"digest": "sha256:24fac901695ef43b73fa8b3cd9e4bf893ceb757c5200b6628ae6a0fc70f01956"
},
{
"name": "usr/share/zoneinfo/Asia/Manila",
"type": "reg",
"size": 353,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22919696,
"NumLink": 0,
"digest": "sha256:1f1bc3dd4bac8e3da2ad11709b6882359c9821caced28b545d1b738524f3419b"
},
{
"name": "usr/share/zoneinfo/Asia/Muscat",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Dubai",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Nicosia",
"type": "reg",
"size": 2016,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22920037,
"NumLink": 0,
"digest": "sha256:f2aa2a3f77a43b7558a7508a6cd6c50fdf7d991f9d64da5948fd9003923b1d72"
},
{
"name": "usr/share/zoneinfo/Asia/Novokuznetsk",
"type": "reg",
"size": 1183,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22921136,
"NumLink": 0,
"digest": "sha256:ad7f74c0ace0cee04470ceb51df7f58321e23a002f3d3ac64ca0e889b3ab698c"
},
{
"name": "usr/share/zoneinfo/Asia/Novosibirsk",
"type": "reg",
"size": 1241,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22921832,
"NumLink": 0,
"digest": "sha256:fc386ed4132a37957acfba6984468588a1e9ff08e24bd5c15bf27ccba8a15a0b"
},
{
"name": "usr/share/zoneinfo/Asia/Omsk",
"type": "reg",
"size": 1229,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22922553,
"NumLink": 0,
"digest": "sha256:abaab5e651101ef99c5425ac184efc1b5d778d4c4c3c86aa894d9059dbe951b6"
},
{
"name": "usr/share/zoneinfo/Asia/Oral",
"type": "reg",
"size": 1025,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22923251,
"NumLink": 0,
"digest": "sha256:eacbd3df5ab43997cf2396786dc819f12461e61de06dd829ee8c8fa76baf7886"
},
{
"name": "usr/share/zoneinfo/Asia/Phnom_Penh",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bangkok",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Pontianak",
"type": "reg",
"size": 395,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22923912,
"NumLink": 0,
"digest": "sha256:2516ac2bc84fe6498a50bc8865ec00e3499b38f2f485403cd5028578a98d1fd8"
},
{
"name": "usr/share/zoneinfo/Asia/Pyongyang",
"type": "reg",
"size": 267,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22924204,
"NumLink": 0,
"digest": "sha256:40434acc1dc1ac05a9ff3d3bb12448629ca45f7a415f03eb4b13f5416061d299"
},
{
"name": "usr/share/zoneinfo/Asia/Qatar",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bahrain",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Qyzylorda",
"type": "reg",
"size": 1033,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22924470,
"NumLink": 0,
"digest": "sha256:4530cb2e704a356eaa4aadcbf12e5faafd411ffe3210bd83ed3370d6876e8182"
},
{
"name": "usr/share/zoneinfo/Asia/Rangoon",
"type": "reg",
"size": 283,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22925083,
"NumLink": 0,
"digest": "sha256:95329208ae33f6298dbecd3309e6c397f4a0a42fd3bc214a87a3cbd6278c5752"
},
{
"name": "usr/share/zoneinfo/Asia/Riyadh",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Aden",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Saigon",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Ho_Chi_Minh",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Sakhalin",
"type": "reg",
"size": 1220,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22925387,
"NumLink": 0,
"digest": "sha256:597a4d79d7ad4b2ecae1c5dbe53a19716cf30cfccd4fa7452c9e9822ca23ca03"
},
{
"name": "usr/share/zoneinfo/Asia/Samarkand",
"type": "reg",
"size": 605,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22926102,
"NumLink": 0,
"digest": "sha256:716675c5cb21a2ce4a48b9ce438744a501828079b06c80ce99a7dead0eeb2dc9"
},
{
"name": "usr/share/zoneinfo/Asia/Seoul",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../ROK",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Shanghai",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../PRC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Singapore",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Singapore",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Srednekolymsk",
"type": "reg",
"size": 1230,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22926617,
"NumLink": 0,
"digest": "sha256:84c8f7ebdb10b6720a01a11198c8b1fa1d1856f1bef11473d2c9a33b97f812a6"
},
{
"name": "usr/share/zoneinfo/Asia/Taipei",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../ROC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Tashkent",
"type": "reg",
"size": 621,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22927354,
"NumLink": 0,
"digest": "sha256:1cce3ce8c9cb736d9163691f4b6fabd2d2e076c7a209621a57bbc207973a8ef3"
},
{
"name": "usr/share/zoneinfo/Asia/Tbilisi",
"type": "reg",
"size": 1066,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22927779,
"NumLink": 0,
"digest": "sha256:b3da0a88ac9d5cf61c1e42ca1a941b36d83ecff5e9f819f80876512ddb07e0f1"
},
{
"name": "usr/share/zoneinfo/Asia/Tehran",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Iran",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Tel_Aviv",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Israel",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Thimbu",
"type": "reg",
"size": 215,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22928487,
"NumLink": 0,
"digest": "sha256:3912ba10af951b2d81f680dee0f89507c14cd7f38bcb9e55a87db48aaed01ab4"
},
{
"name": "usr/share/zoneinfo/Asia/Thimphu",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Thimbu",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Tokyo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Japan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Tomsk",
"type": "reg",
"size": 1241,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22928752,
"NumLink": 0,
"digest": "sha256:b41e5deff2d573ef6e76cf17e0f91ab467576d584b8e5ff5eba01e10f5dee92d"
},
{
"name": "usr/share/zoneinfo/Asia/Ujung_Pandang",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Makassar",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Ulaanbaatar",
"type": "reg",
"size": 907,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22929528,
"NumLink": 0,
"digest": "sha256:aa2c8bbf2b6133470875236c95cbea9d75d369475cdb64ec3098781ae89ffe15"
},
{
"name": "usr/share/zoneinfo/Asia/Ulan_Bator",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Ulaanbaatar",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Urumqi",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Kashgar",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Ust-Nera",
"type": "reg",
"size": 1276,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22930166,
"NumLink": 0,
"digest": "sha256:3711785086a9e0d04a87c1591be40b3a0cd944ea3907182b6b45f6fceb0e024c"
},
{
"name": "usr/share/zoneinfo/Asia/Vientiane",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bangkok",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Vladivostok",
"type": "reg",
"size": 1230,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22930941,
"NumLink": 0,
"digest": "sha256:2b86847acf51b50e5286e87efecf44cb0f2ea26735a6c9810623a814c4f69899"
},
{
"name": "usr/share/zoneinfo/Asia/Yakutsk",
"type": "reg",
"size": 1229,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22931636,
"NumLink": 0,
"digest": "sha256:350cb7d4232de1e621e9363412b75a161053bcc6510401b13cdd2405ddc867ac"
},
{
"name": "usr/share/zoneinfo/Asia/Yangon",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Rangoon",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Asia/Yekaterinburg",
"type": "reg",
"size": 1267,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22932381,
"NumLink": 0,
"digest": "sha256:d36b87b5bbe9787ec26cecca1e9c2446c4126011ea2ae570b59138468f3f2f56"
},
{
"name": "usr/share/zoneinfo/Asia/Yerevan",
"type": "reg",
"size": 1199,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22933097,
"NumLink": 0,
"digest": "sha256:c595907a2d221300674badfb7c157198f862936331bc44bcfd6fb22ba953d05a"
},
{
"name": "usr/share/zoneinfo/Atlantic/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Atlantic/Azores",
"type": "reg",
"size": 3479,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22933829,
"NumLink": 0,
"digest": "sha256:c05864219d3fde7b5d5865bd1433a09f59a08da3980eb550666fdabf99634147"
},
{
"name": "usr/share/zoneinfo/Atlantic/Bermuda",
"type": "reg",
"size": 2004,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22935633,
"NumLink": 0,
"digest": "sha256:462d205904f32dcb79317f83ddb4dea1548d51849217dc3e42ba17c3cc7fcf08"
},
{
"name": "usr/share/zoneinfo/Atlantic/Canary",
"type": "reg",
"size": 1911,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22936737,
"NumLink": 0,
"digest": "sha256:4617cb1aa75514003f181908e9ccfc1d3d062ef22bb0196867dbe530ec2e1416"
},
{
"name": "usr/share/zoneinfo/Atlantic/Cape_Verde",
"type": "reg",
"size": 270,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22937762,
"NumLink": 0,
"digest": "sha256:0cf879a38c3443ff0cba02040a778ec7d07971ac3b43943f1e205c77afdd05bc"
},
{
"name": "usr/share/zoneinfo/Atlantic/Faeroe",
"type": "reg",
"size": 1829,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22937992,
"NumLink": 0,
"digest": "sha256:6b1a5769f8ffa2ec29bf298dffd7fb324e625e36fc527c14bb66b6520e6f76a7"
},
{
"name": "usr/share/zoneinfo/Atlantic/Faroe",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Faeroe",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Atlantic/Jan_Mayen",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Arctic/Longyearbyen",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Atlantic/Madeira",
"type": "reg",
"size": 3484,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22939064,
"NumLink": 0,
"digest": "sha256:24c616780589fb6a7e22913e3402522517ba4a7460738ccd38f1a3a0e4a21f40"
},
{
"name": "usr/share/zoneinfo/Atlantic/Reykjavik",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Iceland",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Atlantic/South_Georgia",
"type": "reg",
"size": 150,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22940919,
"NumLink": 0,
"digest": "sha256:64868e0ba919bae24ad2bd06290aef6200296c282cbc74d2d814a07a5e9342a0"
},
{
"name": "usr/share/zoneinfo/Atlantic/St_Helena",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Africa/Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Atlantic/Stanley",
"type": "reg",
"size": 1237,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22941164,
"NumLink": 0,
"digest": "sha256:53a20741ef1ef47a19fc9c192da23e9a0cf08c3f3b1fd72673d023dce0281890"
},
{
"name": "usr/share/zoneinfo/Australia/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Australia/ACT",
"type": "reg",
"size": 2223,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22941936,
"NumLink": 0,
"digest": "sha256:b540f8e21ed6a6b262336e0eb020c18ab43f283e9774613dd9864239523e4233"
},
{
"name": "usr/share/zoneinfo/Australia/Adelaide",
"type": "reg",
"size": 2238,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22943116,
"NumLink": 0,
"digest": "sha256:735476eef81652d7189574f8b7a11c942a986aba24b6ddc644fbebd1eb49245c"
},
{
"name": "usr/share/zoneinfo/Australia/Brisbane",
"type": "reg",
"size": 452,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22944338,
"NumLink": 0,
"digest": "sha256:74ac9f5d1d15ef0f6bd9e69c687b9047fb1f749c59279475685721f574b427cc"
},
{
"name": "usr/share/zoneinfo/Australia/Broken_Hill",
"type": "reg",
"size": 2274,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22944684,
"NumLink": 0,
"digest": "sha256:d6451675d3b5afb8572e2cbb4d381730da23daa3bfcb57601fe6f815985237db"
},
{
"name": "usr/share/zoneinfo/Australia/Canberra",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "ACT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Australia/Currie",
"type": "reg",
"size": 2223,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22945977,
"NumLink": 0,
"digest": "sha256:543c7afaebfdd907f8d637efce48bf41c407da72658b8e9c12f7208a54d1d84a"
},
{
"name": "usr/share/zoneinfo/Australia/Darwin",
"type": "reg",
"size": 323,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22947148,
"NumLink": 0,
"digest": "sha256:6172d8687a78608d884b04903d36053bdfb56433541930b2a42b405cbb62dc0b"
},
{
"name": "usr/share/zoneinfo/Australia/Eucla",
"type": "reg",
"size": 489,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22947450,
"NumLink": 0,
"digest": "sha256:73e13a985c1612b5881e45564bdf93c8f35a30298ee563dcf50d1cc35bf76a44"
},
{
"name": "usr/share/zoneinfo/Australia/Hobart",
"type": "reg",
"size": 2335,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22947817,
"NumLink": 0,
"digest": "sha256:de1bb5e82f86774e70082b906462e02a062238e5c4d76149566e21b1cb31b23a"
},
{
"name": "usr/share/zoneinfo/Australia/LHI",
"type": "reg",
"size": 1875,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22949038,
"NumLink": 0,
"digest": "sha256:3d35779f2c81370c90f29ba92693a409a094d84d9f236cccda8438d063fb6e5f"
},
{
"name": "usr/share/zoneinfo/Australia/Lindeman",
"type": "reg",
"size": 522,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22950087,
"NumLink": 0,
"digest": "sha256:663df35f044a15c743b9716e183595147d0c1838e99148a9473623ac82076bf9"
},
{
"name": "usr/share/zoneinfo/Australia/Lord_Howe",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "LHI",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Australia/Melbourne",
"type": "reg",
"size": 2223,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22950514,
"NumLink": 0,
"digest": "sha256:272c1f13d01e35e6a58855cbb53878795451928adbf0c8ca2982b79db1f450a7"
},
{
"name": "usr/share/zoneinfo/Australia/NSW",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "ACT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Australia/North",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Darwin",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Australia/Perth",
"type": "reg",
"size": 479,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22951747,
"NumLink": 0,
"digest": "sha256:4ecd4a085ca9ec5b7903016c2d4e311276024a2bcd0c35d40281658e48421f93"
},
{
"name": "usr/share/zoneinfo/Australia/Queensland",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Brisbane",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Australia/South",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Adelaide",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Australia/Sydney",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "ACT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Australia/Tasmania",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Hobart",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Australia/Victoria",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Melbourne",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Australia/West",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Perth",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Australia/Yancowinna",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Broken_Hill",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Brazil/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Brazil/Acre",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Porto_Acre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Brazil/DeNoronha",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Noronha",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Brazil/East",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Sao_Paulo",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Brazil/West",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Manaus",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/CET",
"type": "reg",
"size": 2102,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22952449,
"NumLink": 0,
"digest": "sha256:3c0029045f6f80bc5a84f1bb8ed36230454759c54578eb9a8c195d14f442213c"
},
{
"name": "usr/share/zoneinfo/CST6CDT",
"type": "reg",
"size": 2294,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22953547,
"NumLink": 0,
"digest": "sha256:44e8b569e60027647f9801a33d0b43be0106a6d3f6cd059677e0ed65c9b8b831"
},
{
"name": "usr/share/zoneinfo/Canada/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Canada/Atlantic",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Halifax",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Canada/Central",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Winnipeg",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Canada/Eastern",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Montreal",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Canada/Mountain",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Edmonton",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Canada/Newfoundland",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/St_Johns",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Canada/Pacific",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Vancouver",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Canada/Saskatchewan",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Regina",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Canada/Yukon",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Whitehorse",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Chile/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Chile/Continental",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Santiago",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Chile/EasterIsland",
"type": "reg",
"size": 2228,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22955143,
"NumLink": 0,
"digest": "sha256:c0632af9cba4869ddc8dbfdd78fdc6094d751d0e1d54564e380bad0ff6cbcaf7"
},
{
"name": "usr/share/zoneinfo/Cuba",
"type": "reg",
"size": 2437,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22956340,
"NumLink": 0,
"digest": "sha256:7871f875a8819f415c292519db1590556a0dc1a6ce691bf4f7af55e6716fb894"
},
{
"name": "usr/share/zoneinfo/EET",
"type": "reg",
"size": 1876,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22957640,
"NumLink": 0,
"digest": "sha256:0bf6d2669ab45c13a1c9be47c351972feb671770b90a61d9d313fc60b721b2b4"
},
{
"name": "usr/share/zoneinfo/EST",
"type": "reg",
"size": 127,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22958639,
"NumLink": 0,
"digest": "sha256:c9e75f112a498ff00344551c3c5c4a62bd15d5c218ee951f4363ab218c5d88eb"
},
{
"name": "usr/share/zoneinfo/EST5EDT",
"type": "reg",
"size": 2294,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22958790,
"NumLink": 0,
"digest": "sha256:79ce27e03a2752091e8a49cc7e7ccc9ac202d6c52dd5d224571fe82262fbeec8"
},
{
"name": "usr/share/zoneinfo/Egypt",
"type": "reg",
"size": 1972,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22960019,
"NumLink": 0,
"digest": "sha256:279bbe1fa62da67387c63593b60bb655252ef5c8f189cf43469087740af2b4fc"
},
{
"name": "usr/share/zoneinfo/Eire",
"type": "reg",
"size": 3531,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22961121,
"NumLink": 0,
"digest": "sha256:44bdc5d63e5b1663867491cc0d30b81820fc8694ad0fd5ef500ba8ac6b7fba5f"
},
{
"name": "usr/share/zoneinfo/Etc/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Etc/GMT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Etc/GMT+0",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Etc/GMT+1",
"type": "reg",
"size": 129,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22963015,
"NumLink": 0,
"digest": "sha256:f549f85e4a86989da1b3c592cd455d6846f8103c2c1c7c7d193b3e29de92efc9"
},
{
"name": "usr/share/zoneinfo/Etc/GMT+10",
"type": "reg",
"size": 130,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22963173,
"NumLink": 0,
"digest": "sha256:4b331f62b7c4dd05398fe87517e824152a0faa3ffe80d73e3f1b2bab03e16c99"
},
{
"name": "usr/share/zoneinfo/Etc/GMT+11",
"type": "reg",
"size": 130,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22963331,
"NumLink": 0,
"digest": "sha256:3be86f06310b626b26b42bee3b8ba37be7b836927e204fa7b84e35758e99dce1"
},
{
"name": "usr/share/zoneinfo/Etc/GMT+12",
"type": "reg",
"size": 130,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22963489,
"NumLink": 0,
"digest": "sha256:bb3cf7591690fd770967c92719c1ba023f62a7e40de886f89f57178b0faaf616"
},
{
"name": "usr/share/zoneinfo/Etc/GMT+2",
"type": "reg",
"size": 129,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22963644,
"NumLink": 0,
"digest": "sha256:ae3b221ddd94a88c94c3a069b042c1b06d2718b5bbe6903724110103621e40c5"
},
{
"name": "usr/share/zoneinfo/Etc/GMT+3",
"type": "reg",
"size": 129,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22963799,
"NumLink": 0,
"digest": "sha256:6cdb1f7a18e6008da4e9a72ac9c2be59a26bc03109700b3ec64e4ed8161660db"
},
{
"name": "usr/share/zoneinfo/Etc/GMT+4",
"type": "reg",
"size": 129,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22963956,
"NumLink": 0,
"digest": "sha256:b4c86cac6e9f40dfdbec05b88a38eea2463fe92f1fff80a6e24ff15a9814218f"
},
{
"name": "usr/share/zoneinfo/Etc/GMT+5",
"type": "reg",
"size": 129,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22964113,
"NumLink": 0,
"digest": "sha256:a6653c6b0ba94d0c0bd4b8a6f4c69d001db916f93c0723b9c9fcbbaa34bba098"
},
{
"name": "usr/share/zoneinfo/Etc/GMT+6",
"type": "reg",
"size": 129,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22964270,
"NumLink": 0,
"digest": "sha256:c4a801cd93bdec1b9341b334868ef1ccaeb01b74a31a9f5bb1f1da90ea985789"
},
{
"name": "usr/share/zoneinfo/Etc/GMT+7",
"type": "reg",
"size": 129,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22964427,
"NumLink": 0,
"digest": "sha256:9456c866abdf01fde371357edb5ec59d0c5adc3c09ed9d2bbafd0be21807adad"
},
{
"name": "usr/share/zoneinfo/Etc/GMT+8",
"type": "reg",
"size": 129,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22964584,
"NumLink": 0,
"digest": "sha256:7084ca1e2d9a7349e0556d8c63107fdc64cb5d50778cc74b4b9ea3d2ca1eeb43"
},
{
"name": "usr/share/zoneinfo/Etc/GMT+9",
"type": "reg",
"size": 129,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22964740,
"NumLink": 0,
"digest": "sha256:847ded37313ae7de90d5dec700acf7d229ad88cc7462e9527ac5bb37cd2c00ac"
},
{
"name": "usr/share/zoneinfo/Etc/GMT-0",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Etc/GMT-1",
"type": "reg",
"size": 130,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22964928,
"NumLink": 0,
"digest": "sha256:355d77cdeed83884c287b29454078b89865d4e56508dd71e8597c8c038029ade"
},
{
"name": "usr/share/zoneinfo/Etc/GMT-10",
"type": "reg",
"size": 131,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22965086,
"NumLink": 0,
"digest": "sha256:4653c5c305ed3a46ac02c471feb310f356ed6c8d2465fd37e555132b0004899c"
},
{
"name": "usr/share/zoneinfo/Etc/GMT-11",
"type": "reg",
"size": 131,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22965245,
"NumLink": 0,
"digest": "sha256:b65fdb39373fa4c47b1e6a2105931e5459fdceaa74194ea737dd981527e5a1a5"
},
{
"name": "usr/share/zoneinfo/Etc/GMT-12",
"type": "reg",
"size": 131,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22965404,
"NumLink": 0,
"digest": "sha256:f67b770d0224c9dbf776e8bc913c0dc462af7eb90f04b88b7387af662c248545"
},
{
"name": "usr/share/zoneinfo/Etc/GMT-13",
"type": "reg",
"size": 131,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22965561,
"NumLink": 0,
"digest": "sha256:551aebd6c58a89bbc34283170a01c5de12d98ca658aee39f68e8ed7cf11dd872"
},
{
"name": "usr/share/zoneinfo/Etc/GMT-14",
"type": "reg",
"size": 131,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22965720,
"NumLink": 0,
"digest": "sha256:414d785a776406dce6ccf450aa2ae48c4d3e4db23482ac3cc285567c0624b33f"
},
{
"name": "usr/share/zoneinfo/Etc/GMT-2",
"type": "reg",
"size": 130,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22965878,
"NumLink": 0,
"digest": "sha256:6a4f02622392c0c01ad74aacdd1d9835949b10084b6ff3494ca593871fa36279"
},
{
"name": "usr/share/zoneinfo/Etc/GMT-3",
"type": "reg",
"size": 130,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22966033,
"NumLink": 0,
"digest": "sha256:2331d90cf59bd06209da3f57fe71f8eeff4f9987cda8f209f7d19605173c7940"
},
{
"name": "usr/share/zoneinfo/Etc/GMT-4",
"type": "reg",
"size": 130,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22966190,
"NumLink": 0,
"digest": "sha256:3d1d2a77c8fc7121113f50455ab5ecf7a83af5d100b1665c069ad2283beedd05"
},
{
"name": "usr/share/zoneinfo/Etc/GMT-5",
"type": "reg",
"size": 130,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22966347,
"NumLink": 0,
"digest": "sha256:a998fef5ca21deb83d7b6801cecb36f851af7d09e45b23f1a8e1cab037c94cd2"
},
{
"name": "usr/share/zoneinfo/Etc/GMT-6",
"type": "reg",
"size": 130,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22966504,
"NumLink": 0,
"digest": "sha256:59a432b594fd6a1775aaf1798ffded3e325481040786cf56500d61194d156b1d"
},
{
"name": "usr/share/zoneinfo/Etc/GMT-7",
"type": "reg",
"size": 130,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22966661,
"NumLink": 0,
"digest": "sha256:6b8fd7cee7b9e0ac90f11617a6e4f0c68cda81a2153a0b37a46a3516f94c3e1a"
},
{
"name": "usr/share/zoneinfo/Etc/GMT-8",
"type": "reg",
"size": 130,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22966818,
"NumLink": 0,
"digest": "sha256:47625566b0b8e5476810ac5fe4882586ec53793d4f53ed281702a220eee7d554"
},
{
"name": "usr/share/zoneinfo/Etc/GMT-9",
"type": "reg",
"size": 130,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22966975,
"NumLink": 0,
"digest": "sha256:2f4662e616bd94e9eb6a2ed069f01bdf316b3e2c39c3b945e5397a53e26d16c4"
},
{
"name": "usr/share/zoneinfo/Etc/GMT0",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Etc/Greenwich",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Etc/UCT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../UCT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Etc/UTC",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../UTC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Etc/Universal",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../UTC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Etc/Zulu",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../UTC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Europe/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Europe/Amsterdam",
"type": "reg",
"size": 2949,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22967288,
"NumLink": 0,
"digest": "sha256:8a813ac6b8d1b68a7960242cae5325a2269fd1c791b203f8d22f2dfa3b61ba87"
},
{
"name": "usr/share/zoneinfo/Europe/Andorra",
"type": "reg",
"size": 1751,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22968805,
"NumLink": 0,
"digest": "sha256:add5505c473225e33a884a02105610a9b95003f429195624b953c18f771317af"
},
{
"name": "usr/share/zoneinfo/Europe/Astrakhan",
"type": "reg",
"size": 1183,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22969765,
"NumLink": 0,
"digest": "sha256:08873fa11e5ab238e56d7a2fbbfb0876aa3ea0b71ee8ce95b0d5d394be923514"
},
{
"name": "usr/share/zoneinfo/Europe/Athens",
"type": "reg",
"size": 2271,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22970465,
"NumLink": 0,
"digest": "sha256:799090551202c0b8417f836facf75049573dd1c27b5e6adeb584fcc414051139"
},
{
"name": "usr/share/zoneinfo/Europe/Belfast",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GB",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Europe/Belgrade",
"type": "reg",
"size": 1957,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22971711,
"NumLink": 0,
"digest": "sha256:e957543623baaba84999b40188e7e0948471b75a8ff4f88abb267e773feb8e5c"
},
{
"name": "usr/share/zoneinfo/Europe/Berlin",
"type": "reg",
"size": 2335,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22972765,
"NumLink": 0,
"digest": "sha256:7eb93dcba603d528fdf536160ef6911c16f834afcf88ce23a382b97ff28319d4"
},
{
"name": "usr/share/zoneinfo/Europe/Bratislava",
"type": "reg",
"size": 2338,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22973995,
"NumLink": 0,
"digest": "sha256:a50be470a22de9def3e4fec7bcd95d5d7e24e5b11edf448a82b04d19da3d3e52"
},
{
"name": "usr/share/zoneinfo/Europe/Brussels",
"type": "reg",
"size": 2970,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22975270,
"NumLink": 0,
"digest": "sha256:f372a903a6a57da43a6f500d6aceccfc74e800d7dfe88a7eaf56ca2564557e66"
},
{
"name": "usr/share/zoneinfo/Europe/Bucharest",
"type": "reg",
"size": 2221,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22976855,
"NumLink": 0,
"digest": "sha256:3b3a0017333b2f466e59c8ac3dc0cf7aa4f0a4608040a3180f752b19d6a93526"
},
{
"name": "usr/share/zoneinfo/Europe/Budapest",
"type": "reg",
"size": 2405,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22978053,
"NumLink": 0,
"digest": "sha256:b67f2c4690a87f294ea5d35ae3967c8aa8bde227aeb36c3877285e4e94a17418"
},
{
"name": "usr/share/zoneinfo/Europe/Busingen",
"type": "reg",
"size": 1918,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22979324,
"NumLink": 0,
"digest": "sha256:bc45f8c6c8190477cdaae46f77059fab74fde92a02fc57b733f07cb9a55e98a3"
},
{
"name": "usr/share/zoneinfo/Europe/Chisinau",
"type": "reg",
"size": 2445,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22980397,
"NumLink": 0,
"digest": "sha256:5749f01c78d0c2fd50d0dc2280c1957ce0419edbfc7c4073c67e6da78153d8c8"
},
{
"name": "usr/share/zoneinfo/Europe/Copenhagen",
"type": "reg",
"size": 2160,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22981687,
"NumLink": 0,
"digest": "sha256:d2d9a359ef02d2afe293f429c4fd60fc04fbf8d1d8343c9b224dcfc116c011a8"
},
{
"name": "usr/share/zoneinfo/Europe/Dublin",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Eire",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Europe/Gibraltar",
"type": "reg",
"size": 3061,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22982916,
"NumLink": 0,
"digest": "sha256:c79088f67ba5d3fa9ad989bd573bfdef0e86c89e310ea70bc3e01e14dca1075e"
},
{
"name": "usr/share/zoneinfo/Europe/Guernsey",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GB",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Europe/Helsinki",
"type": "reg",
"size": 1909,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22984560,
"NumLink": 0,
"digest": "sha256:ed7d89fae1fb40a9582edd7e03ed02d7fe81ba456b9c1ed8d6ee5f0b931aad45"
},
{
"name": "usr/share/zoneinfo/Europe/Isle_of_Man",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GB",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Europe/Istanbul",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Turkey",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Europe/Jersey",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GB",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Europe/Kaliningrad",
"type": "reg",
"size": 1518,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22985701,
"NumLink": 0,
"digest": "sha256:450a2c6ff69d262b5c5d8ef584464e9c8274b389a1c39b7abc5aa70608d29425"
},
{
"name": "usr/share/zoneinfo/Europe/Kiev",
"type": "reg",
"size": 2097,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22986542,
"NumLink": 0,
"digest": "sha256:242912df3212e0725ded4aab25fd869c52f13c3ce619764a883adcbbd937afc5"
},
{
"name": "usr/share/zoneinfo/Europe/Kirov",
"type": "reg",
"size": 1153,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22987671,
"NumLink": 0,
"digest": "sha256:d562c884fcbbde25ad884da464c7f981296c2c4e13b3b7c1d3c322b82bd4f726"
},
{
"name": "usr/share/zoneinfo/Europe/Lisbon",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Portugal",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Europe/Ljubljana",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Belgrade",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Europe/London",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GB",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Europe/Luxembourg",
"type": "reg",
"size": 2974,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22988462,
"NumLink": 0,
"digest": "sha256:90b76259274c78a40f34aa5b58545b5409edfbba2fd08efa1b300896cb4062ee"
},
{
"name": "usr/share/zoneinfo/Europe/Madrid",
"type": "reg",
"size": 2637,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22989991,
"NumLink": 0,
"digest": "sha256:74103ad1e48f71f4cd9b6d1c03dcd97b58d87bb8affb02b1d6967b204036ebd6"
},
{
"name": "usr/share/zoneinfo/Europe/Malta",
"type": "reg",
"size": 2629,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22991364,
"NumLink": 0,
"digest": "sha256:7c4134c8d37bd159e31fd739e8b1b8203a9f3023788bd9c83b8109e361eee5d5"
},
{
"name": "usr/share/zoneinfo/Europe/Mariehamn",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Helsinki",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Europe/Minsk",
"type": "reg",
"size": 1356,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22992812,
"NumLink": 0,
"digest": "sha256:0d0535cb101110d8fbb1238dde8444e980fab90bbdc8402371cc6ab5caa1a96c"
},
{
"name": "usr/share/zoneinfo/Europe/Monaco",
"type": "reg",
"size": 2953,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22993582,
"NumLink": 0,
"digest": "sha256:7c723359888417b86a66a609ffdd0becf81673cbb3e8b011131088b4d26f6bcd"
},
{
"name": "usr/share/zoneinfo/Europe/Moscow",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../W-SU",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Europe/Nicosia",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Asia/Nicosia",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Europe/Oslo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Arctic/Longyearbyen",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Europe/Paris",
"type": "reg",
"size": 2971,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22995220,
"NumLink": 0,
"digest": "sha256:735b08e2737de2b47e79f596f3574b5a9e9019e56d2ead0cdc17c0b29e84a585"
},
{
"name": "usr/share/zoneinfo/Europe/Podgorica",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Belgrade",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Europe/Prague",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bratislava",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Europe/Riga",
"type": "reg",
"size": 2235,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22996819,
"NumLink": 0,
"digest": "sha256:79d10debbaa2743458d0dec1fb71d3c576cea80d245f84819da82a25d93c1401"
},
{
"name": "usr/share/zoneinfo/Europe/Rome",
"type": "reg",
"size": 2692,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22998013,
"NumLink": 0,
"digest": "sha256:3eff2ef2cdc67e2f5cfd44866cdb4d448956b89eae8b0e37f63126f2068ee889"
},
{
"name": "usr/share/zoneinfo/Europe/Samara",
"type": "reg",
"size": 1239,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 22999495,
"NumLink": 0,
"digest": "sha256:24506ef1ada9839602c5d6589f46c16ba427e1e310c473f0ec77f4436b973185"
},
{
"name": "usr/share/zoneinfo/Europe/San_Marino",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Rome",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Europe/Sarajevo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Belgrade",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Europe/Saratov",
"type": "reg",
"size": 1183,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23000281,
"NumLink": 0,
"digest": "sha256:4ad0f06f89a5b7ef2abd7d249300cd31328bfed591f3a6a7acede6bb8c0b7977"
},
{
"name": "usr/share/zoneinfo/Europe/Simferopol",
"type": "reg",
"size": 1490,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23000978,
"NumLink": 0,
"digest": "sha256:b1ee6f714fd88fd61fef6df54f95abacb80dd3036c25e9a10708fec9b11c34cf"
},
{
"name": "usr/share/zoneinfo/Europe/Skopje",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Belgrade",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Europe/Sofia",
"type": "reg",
"size": 2130,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23001878,
"NumLink": 0,
"digest": "sha256:16813fb30f2ebb782a806ce0664014ddfbf921890d32ec3d1398bd182bf9245c"
},
{
"name": "usr/share/zoneinfo/Europe/Stockholm",
"type": "reg",
"size": 1918,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23003096,
"NumLink": 0,
"digest": "sha256:07b242f9e3d8150663bfaf417fe7d209927fc299fac487789b70841956c35335"
},
{
"name": "usr/share/zoneinfo/Europe/Tallinn",
"type": "reg",
"size": 2187,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23004161,
"NumLink": 0,
"digest": "sha256:e3c4ba916c25500c709c56395c040abad62a834fafaf5163a89974b7f66b019a"
},
{
"name": "usr/share/zoneinfo/Europe/Tirane",
"type": "reg",
"size": 2098,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23005327,
"NumLink": 0,
"digest": "sha256:62dbc606a32a5f50ceca86c6f96d088ea689bced60a1623c986f045cde9c730a"
},
{
"name": "usr/share/zoneinfo/Europe/Tiraspol",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Chisinau",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Europe/Ulyanovsk",
"type": "reg",
"size": 1267,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23006502,
"NumLink": 0,
"digest": "sha256:0d96c23e54ab8bcae4174fe792f1c00e4158f81a5b98039121e6502ce6686716"
},
{
"name": "usr/share/zoneinfo/Europe/Uzhgorod",
"type": "reg",
"size": 2103,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23007229,
"NumLink": 0,
"digest": "sha256:098575b4ec6599758c85bcad8dd21d89bca213a9f890c0ead6defd14878705f3"
},
{
"name": "usr/share/zoneinfo/Europe/Vaduz",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Busingen",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Europe/Vatican",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Rome",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Europe/Vienna",
"type": "reg",
"size": 2237,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23008430,
"NumLink": 0,
"digest": "sha256:be42c32ad7c54f15dcf5dd9425e289fd20bebe986e5e8c240a11dfeec46550f7"
},
{
"name": "usr/share/zoneinfo/Europe/Vilnius",
"type": "reg",
"size": 2199,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23009609,
"NumLink": 0,
"digest": "sha256:75adc0a906b39e283f5e5020984a36f34b4f58ef1d3099efbc899ff07f035f7e"
},
{
"name": "usr/share/zoneinfo/Europe/Volgograd",
"type": "reg",
"size": 1153,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23010776,
"NumLink": 0,
"digest": "sha256:4b826a3d9cdffa786e46da2315977f13cac9ce01e4b214415f3362505b97fdd1"
},
{
"name": "usr/share/zoneinfo/Europe/Warsaw",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Poland",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Europe/Zagreb",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Belgrade",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Europe/Zaporozhye",
"type": "reg",
"size": 2115,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23011522,
"NumLink": 0,
"digest": "sha256:b6c5127b52518818e3b4211e89e5e1e9a479cca65f7763a0afb145d512c38e34"
},
{
"name": "usr/share/zoneinfo/Europe/Zurich",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Busingen",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Factory",
"type": "reg",
"size": 129,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23012709,
"NumLink": 0,
"digest": "sha256:ce4929e887a3f9f403d15e734f7798028addf502e767e7da09b8c837e7bd9bbd"
},
{
"name": "usr/share/zoneinfo/GB",
"type": "reg",
"size": 3687,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23012854,
"NumLink": 0,
"digest": "sha256:b14c486019e3cb259cf8235a0d6a4bc3ff6cfa726a165f1ea2df403c8ae31b86"
},
{
"name": "usr/share/zoneinfo/GB-Eire",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "GB",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/GMT",
"type": "reg",
"size": 127,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23014784,
"NumLink": 0,
"digest": "sha256:d7b39879094135d13efd282937690b43f48bb53597ce3e78697f48dcceaeb3ec"
},
{
"name": "usr/share/zoneinfo/GMT+0",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/GMT-0",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/GMT0",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Greenwich",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/HST",
"type": "reg",
"size": 128,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23015004,
"NumLink": 0,
"digest": "sha256:44048bf7df61bdcf45972c13426b039f0d34d80947d60a2603183b3b6be4027f"
},
{
"name": "usr/share/zoneinfo/Hongkong",
"type": "reg",
"size": 1189,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23015158,
"NumLink": 0,
"digest": "sha256:9786fbe4ad9b820f61a5664b3b85dd601aa6e9152c93ff4e14bd54effdf8f1d5"
},
{
"name": "usr/share/zoneinfo/Iceland",
"type": "reg",
"size": 1188,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23015865,
"NumLink": 0,
"digest": "sha256:9cdcea6aa1eed8276d3f6620e0c0ffae9cfcc3722c443ea6ba39147975eafaa3"
},
{
"name": "usr/share/zoneinfo/Indian/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Indian/Antananarivo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Africa/Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Indian/Chagos",
"type": "reg",
"size": 211,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23016657,
"NumLink": 0,
"digest": "sha256:1b1797b71359a3a9fd49683dd9028692c20b4c9b56088c50de6ae122f075f3d1"
},
{
"name": "usr/share/zoneinfo/Indian/Christmas",
"type": "reg",
"size": 151,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23016859,
"NumLink": 0,
"digest": "sha256:720ed3c1a13aa4e6b4a9acb7c6f017a5e28e20507169ce1b3ec25075c98be656"
},
{
"name": "usr/share/zoneinfo/Indian/Cocos",
"type": "reg",
"size": 160,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23017045,
"NumLink": 0,
"digest": "sha256:b78aea130d938147f36de61341c44388915da43f467f3723c98dcc3d8b808884"
},
{
"name": "usr/share/zoneinfo/Indian/Comoro",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Africa/Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Indian/Kerguelen",
"type": "reg",
"size": 173,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23017295,
"NumLink": 0,
"digest": "sha256:29bc1cca3de7cf37433fcee5ec99d490abf20c767f284d5bb40a15feac0ad398"
},
{
"name": "usr/share/zoneinfo/Indian/Mahe",
"type": "reg",
"size": 173,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23017469,
"NumLink": 0,
"digest": "sha256:aed7dcefc4d7a4659ef1a62e56d1dd6f21cf0e9cea7a1089a94ac1d48113d5fe"
},
{
"name": "usr/share/zoneinfo/Indian/Maldives",
"type": "reg",
"size": 206,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23017651,
"NumLink": 0,
"digest": "sha256:49fa18af1a1c531f2a63bffc82e4cba7c53f4e587376f453c97084d605d4a9f1"
},
{
"name": "usr/share/zoneinfo/Indian/Mauritius",
"type": "reg",
"size": 253,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23017858,
"NumLink": 0,
"digest": "sha256:25c927943cddfb96e41b9bfa014791a9375f8cb77e672f42d72fc81d83607f0c"
},
{
"name": "usr/share/zoneinfo/Indian/Mayotte",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Africa/Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Indian/Reunion",
"type": "reg",
"size": 173,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23018144,
"NumLink": 0,
"digest": "sha256:263f52802a6b69445a66bb8d75a4ca3dfe717ce6512d7e2501466e7a1b6d56a3"
},
{
"name": "usr/share/zoneinfo/Iran",
"type": "reg",
"size": 1704,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23018313,
"NumLink": 0,
"digest": "sha256:19084f9dda0e334eb24bd8ff70daedffefd64c537cb27e5a54c5df7694c534d5"
},
{
"name": "usr/share/zoneinfo/Israel",
"type": "reg",
"size": 2265,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23019248,
"NumLink": 0,
"digest": "sha256:60019ee0d1deb6e6994a1a5c951861e1db9f992a5c027fbc4e3617f942c021cf"
},
{
"name": "usr/share/zoneinfo/Jamaica",
"type": "reg",
"size": 507,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23020475,
"NumLink": 0,
"digest": "sha256:addb98caf3459bb75d6e14ed76aa66e642bead2d067e7fe81814a4f02cf13503"
},
{
"name": "usr/share/zoneinfo/Japan",
"type": "reg",
"size": 318,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23020841,
"NumLink": 0,
"digest": "sha256:4d598cea3fe55b44afbd9c6f603f748960e4a9806e1ea1e87fc2f2bb98214377"
},
{
"name": "usr/share/zoneinfo/Kwajalein",
"type": "reg",
"size": 245,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23021102,
"NumLink": 0,
"digest": "sha256:728eed0b199aa19468fe7f1dd887fe7f0c3a659a897741aa6e2d4f5b80f75eb5"
},
{
"name": "usr/share/zoneinfo/Libya",
"type": "reg",
"size": 655,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23021318,
"NumLink": 0,
"digest": "sha256:8ff53f7072863fb56f1e71339392b6de7e50675efa4333b9e032b677c9c9a527"
},
{
"name": "usr/share/zoneinfo/MET",
"type": "reg",
"size": 2102,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23021774,
"NumLink": 0,
"digest": "sha256:1fff331a4414e98097d33bec1a9bbf2a155d991b57acd1bb4c11f8559fb4e514"
},
{
"name": "usr/share/zoneinfo/MST",
"type": "reg",
"size": 127,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23022868,
"NumLink": 0,
"digest": "sha256:f8fb610056087bb3ca8ecf5cdcb5305c1652b649fde512f606b9ee1b3556fb9e"
},
{
"name": "usr/share/zoneinfo/MST7MDT",
"type": "reg",
"size": 2294,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23023019,
"NumLink": 0,
"digest": "sha256:85452d031526621178e9b24c91af69b7ecc30df47036669378956135c4e735d0"
},
{
"name": "usr/share/zoneinfo/Mexico/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Mexico/BajaNorte",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Ensenada",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Mexico/BajaSur",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Mazatlan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Mexico/General",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Mexico_City",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/NZ",
"type": "reg",
"size": 2460,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23024405,
"NumLink": 0,
"digest": "sha256:d7b5175387ac78e29f7b9021e411512756be283ed3d1819942ef5d45ecf338e4"
},
{
"name": "usr/share/zoneinfo/NZ-CHAT",
"type": "reg",
"size": 2073,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23025679,
"NumLink": 0,
"digest": "sha256:b8f1a26d43eeecaf8302456d69fdf047b847b6973069804c5a39f8ecb886d74c"
},
{
"name": "usr/share/zoneinfo/Navajo",
"type": "reg",
"size": 2453,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23026786,
"NumLink": 0,
"digest": "sha256:f4df3cc74c79d070a25a7927744d3a422a05d862a9a234a12105c5c964efb22d"
},
{
"name": "usr/share/zoneinfo/PRC",
"type": "reg",
"size": 414,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23028092,
"NumLink": 0,
"digest": "sha256:953622bbd7eb9eba8c3b9e8cd5d5ec98cea6a085a9deb1c43e49e889a154d344"
},
{
"name": "usr/share/zoneinfo/PST8PDT",
"type": "reg",
"size": 2294,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23028413,
"NumLink": 0,
"digest": "sha256:4d8e69bd43e8d71f0f58e115593814d68c1a6aa441255b17b3e9a92a9d6efc46"
},
{
"name": "usr/share/zoneinfo/Pacific/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Pacific/Apia",
"type": "reg",
"size": 1120,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23029687,
"NumLink": 0,
"digest": "sha256:171011084ad201170e51b42435679a50bab661ff6114ca09a764e822f13f45db"
},
{
"name": "usr/share/zoneinfo/Pacific/Auckland",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../NZ",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Pacific/Bougainville",
"type": "reg",
"size": 282,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23030404,
"NumLink": 0,
"digest": "sha256:35e0203bf554d7ccf0b4e0176829aa342747427924ae9e9b661e484205e828e2"
},
{
"name": "usr/share/zoneinfo/Pacific/Chatham",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../NZ-CHAT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Pacific/Chuuk",
"type": "reg",
"size": 152,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23030725,
"NumLink": 0,
"digest": "sha256:6efbcffeaffabb8b91c03e12dd02707f3b2f12ed6c4583ede4d8d6b72721c4fc"
},
{
"name": "usr/share/zoneinfo/Pacific/Easter",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Chile/EasterIsland",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Pacific/Efate",
"type": "reg",
"size": 478,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23030964,
"NumLink": 0,
"digest": "sha256:adbee8a8544a400050d7dce148e2c10a785b9120c755a69a674395b04c36092e"
},
{
"name": "usr/share/zoneinfo/Pacific/Enderbury",
"type": "reg",
"size": 245,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23031328,
"NumLink": 0,
"digest": "sha256:dab23f777b9cb06d602bcd5de7670da9191830816d802041846132e137f7eae6"
},
{
"name": "usr/share/zoneinfo/Pacific/Fakaofo",
"type": "reg",
"size": 207,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23031556,
"NumLink": 0,
"digest": "sha256:0427f848089402d7105907710ec0a9096365b10d1dd3ee015edc5da527e1c3de"
},
{
"name": "usr/share/zoneinfo/Pacific/Fiji",
"type": "reg",
"size": 1076,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23031762,
"NumLink": 0,
"digest": "sha256:7281ebcc865b2c2712ac51b6f4654543a99ddce68ad42ea7bca0105b825e3a94"
},
{
"name": "usr/share/zoneinfo/Pacific/Funafuti",
"type": "reg",
"size": 152,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23032435,
"NumLink": 0,
"digest": "sha256:7fae086ba7234a7684b22e8948faec02be41bb76b3944213206d1857b049ba1a"
},
{
"name": "usr/share/zoneinfo/Pacific/Galapagos",
"type": "reg",
"size": 254,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23032625,
"NumLink": 0,
"digest": "sha256:c91721b38d5ee01676190b91cf5150050b3fed2ad4634816724f8207d69877be"
},
{
"name": "usr/share/zoneinfo/Pacific/Gambier",
"type": "reg",
"size": 172,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23032851,
"NumLink": 0,
"digest": "sha256:5008ae162414c442be54580de29eb211a9f652f8eda265f038a6a82eddd5e5d2"
},
{
"name": "usr/share/zoneinfo/Pacific/Guadalcanal",
"type": "reg",
"size": 174,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23033038,
"NumLink": 0,
"digest": "sha256:69ec8cb3bd0e92db41f2d7b9ffcdd1881d0f7b7ee6048d3f092ad17cc686e5a5"
},
{
"name": "usr/share/zoneinfo/Pacific/Guam",
"type": "reg",
"size": 225,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23033218,
"NumLink": 0,
"digest": "sha256:6613576ebdf4d9d6be5eb849539d3f74a4fb137acf6bcf8cda20d28e5c612e77"
},
{
"name": "usr/share/zoneinfo/Pacific/Honolulu",
"type": "reg",
"size": 276,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23033439,
"NumLink": 0,
"digest": "sha256:88885824bf915a9a6f39a7030798da825e34be79d1b35c2444696a81c159f2e8"
},
{
"name": "usr/share/zoneinfo/Pacific/Johnston",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Honolulu",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Pacific/Kiritimati",
"type": "reg",
"size": 249,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23033735,
"NumLink": 0,
"digest": "sha256:43d21c8bf4a0547479d1512db27d5dd7d8b2db69ee232680a88863c3e446bd42"
},
{
"name": "usr/share/zoneinfo/Pacific/Kosrae",
"type": "reg",
"size": 237,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23033959,
"NumLink": 0,
"digest": "sha256:dd24709003b34eca2e8975694b370b5892dc8a1560892e42fa5c3a9669dc00b9"
},
{
"name": "usr/share/zoneinfo/Pacific/Kwajalein",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Kwajalein",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Pacific/Majuro",
"type": "reg",
"size": 207,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23034217,
"NumLink": 0,
"digest": "sha256:2784fe14ae4b06d84b54e2ef9195ee8fcd33a1151ae9d92b2b144ed80324c4ec"
},
{
"name": "usr/share/zoneinfo/Pacific/Marquesas",
"type": "reg",
"size": 181,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23034425,
"NumLink": 0,
"digest": "sha256:c1c151ea927de5f087bab64d962d1bf4c8af0672fb66dab9eb63e93c5c0ce906"
},
{
"name": "usr/share/zoneinfo/Pacific/Midway",
"type": "reg",
"size": 196,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23034611,
"NumLink": 0,
"digest": "sha256:7218a2ae386cd5e8981a940f6b56f6f9b60a65f3e3bd2ec1fe6c9d43bac4db1a"
},
{
"name": "usr/share/zoneinfo/Pacific/Nauru",
"type": "reg",
"size": 268,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23034809,
"NumLink": 0,
"digest": "sha256:8eabc930825033369d3dd4d85d1cab23b40c0214c91a3a2cac6547a2d549b8c2"
},
{
"name": "usr/share/zoneinfo/Pacific/Niue",
"type": "reg",
"size": 252,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23035038,
"NumLink": 0,
"digest": "sha256:c7247452c2c61010dfc417d172e4f4cf955feefd89b9649fa08e51832d9ed708"
},
{
"name": "usr/share/zoneinfo/Pacific/Norfolk",
"type": "reg",
"size": 309,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23035264,
"NumLink": 0,
"digest": "sha256:5007254c619ebc6200e235c5aaa3b66eec80e9052d00ba63d84bce6c5408c755"
},
{
"name": "usr/share/zoneinfo/Pacific/Noumea",
"type": "reg",
"size": 314,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23035517,
"NumLink": 0,
"digest": "sha256:47834b191dcb3a4e4f0cb3e6c375ff66c07e609382be7d2c21e0c4d854008dca"
},
{
"name": "usr/share/zoneinfo/Pacific/Pago_Pago",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Midway",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Pacific/Palau",
"type": "reg",
"size": 151,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23035811,
"NumLink": 0,
"digest": "sha256:d1a61b4bffd5b61eac5d9ef0a7f10a80ffc1d027a9330e7ad89278a3f3857357"
},
{
"name": "usr/share/zoneinfo/Pacific/Pitcairn",
"type": "reg",
"size": 209,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23036001,
"NumLink": 0,
"digest": "sha256:97a89892a57db71698c25c9ae2bb8cd6436e239988b3bbb5e028fd9b0c233224"
},
{
"name": "usr/share/zoneinfo/Pacific/Pohnpei",
"type": "reg",
"size": 152,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23036208,
"NumLink": 0,
"digest": "sha256:9360c42536001cdbe62e033e03b1766ac3dc7315966c31166f86a4e9aafa4567"
},
{
"name": "usr/share/zoneinfo/Pacific/Ponape",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Pohnpei",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Pacific/Port_Moresby",
"type": "reg",
"size": 174,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23036442,
"NumLink": 0,
"digest": "sha256:9c505d421d2cfc45dab9819b6abdf1d9c616d63feab9ec1c22db149b192cdd3f"
},
{
"name": "usr/share/zoneinfo/Pacific/Rarotonga",
"type": "reg",
"size": 588,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23036648,
"NumLink": 0,
"digest": "sha256:b309575c63dbf5a1a8e499105cfc7b51dbba38302571e4a51d861a3f46eadbc3"
},
{
"name": "usr/share/zoneinfo/Pacific/Saipan",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Guam",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Pacific/Samoa",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Midway",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Pacific/Tahiti",
"type": "reg",
"size": 173,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23037129,
"NumLink": 0,
"digest": "sha256:2b908353b2b5ba977afe11a11c088376c816dd4b7f71358db6b95b060f51b637"
},
{
"name": "usr/share/zoneinfo/Pacific/Tarawa",
"type": "reg",
"size": 152,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23037313,
"NumLink": 0,
"digest": "sha256:5c0324994691766186531137a292759ef788a558273a13c7617f3c373b4426ec"
},
{
"name": "usr/share/zoneinfo/Pacific/Tongatapu",
"type": "reg",
"size": 379,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23037503,
"NumLink": 0,
"digest": "sha256:3a742868cd652d1ccd0bd219406bc09db1063c057dc2fdc05b3ff96e81b314da"
},
{
"name": "usr/share/zoneinfo/Pacific/Truk",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Chuuk",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Pacific/Wake",
"type": "reg",
"size": 152,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23037834,
"NumLink": 0,
"digest": "sha256:7d63ddb8dbd42baa9bf4fd136ad3be2612db4402fd498d67ebcfcb93eb42dd6a"
},
{
"name": "usr/share/zoneinfo/Pacific/Wallis",
"type": "reg",
"size": 152,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23038022,
"NumLink": 0,
"digest": "sha256:25cfc2e1a664e96e86dc7e9bcf754bd5b8608ddf7673ffa407e4a762e309ecf5"
},
{
"name": "usr/share/zoneinfo/Pacific/Yap",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Chuuk",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Poland",
"type": "reg",
"size": 2705,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23038247,
"NumLink": 0,
"digest": "sha256:68e7493c1ca050e4134062a74aa4a4fc32159c042b4c9d8d40c8bfc9d273c5fa"
},
{
"name": "usr/share/zoneinfo/Portugal",
"type": "reg",
"size": 3469,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23039667,
"NumLink": 0,
"digest": "sha256:4bbe65d4ff3394ffa1b4ae6fe2296e333f55bad0ae49ca6717b6076e53490ea2"
},
{
"name": "usr/share/zoneinfo/ROC",
"type": "reg",
"size": 790,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23041447,
"NumLink": 0,
"digest": "sha256:25cfd02bc847bdcb11e586445ba886a76315f1f9be86f7e74944a6e8e8644543"
},
{
"name": "usr/share/zoneinfo/ROK",
"type": "reg",
"size": 531,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23041946,
"NumLink": 0,
"digest": "sha256:22de94642a978a6af463db175e9f93a3292e8a81a74a8b92989bc2174379edd6"
},
{
"name": "usr/share/zoneinfo/Singapore",
"type": "reg",
"size": 410,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23042328,
"NumLink": 0,
"digest": "sha256:1b4684d9bb6ec48169abb530da58b8cb238b785bb5ffeb5e06e50f1b2b9d60e0"
},
{
"name": "usr/share/zoneinfo/SystemV/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/SystemV/AST4",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Puerto_Rico",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/SystemV/AST4ADT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Halifax",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/SystemV/CST6",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Regina",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/SystemV/CST6CDT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Chicago",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/SystemV/EST5",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Cayman",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/SystemV/EST5EDT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../posixrules",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/SystemV/HST10",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Pacific/Honolulu",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/SystemV/MST7",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Phoenix",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/SystemV/MST7MDT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Navajo",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/SystemV/PST8",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Pacific/Pitcairn",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/SystemV/PST8PDT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Los_Angeles",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/SystemV/YST9",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Pacific/Gambier",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/SystemV/YST9YDT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Anchorage",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/Turkey",
"type": "reg",
"size": 2152,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23042976,
"NumLink": 0,
"digest": "sha256:2a694b73d6e78b3e9d3a94e1bc44c410bc306a360d7eebfabf8fd95c753760a3"
},
{
"name": "usr/share/zoneinfo/UCT",
"type": "reg",
"size": 127,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23044141,
"NumLink": 0,
"digest": "sha256:b3b762863cac2569aa33f7da192584afd9965ceeb7263fed4ad87c1b35e4c7d8"
},
{
"name": "usr/share/zoneinfo/US/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/US/Alaska",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Anchorage",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/US/Aleutian",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Adak",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/US/Arizona",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Phoenix",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/US/Central",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Chicago",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/US/East-Indiana",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Fort_Wayne",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/US/Eastern",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../posixrules",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/US/Hawaii",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Pacific/Honolulu",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/US/Indiana-Starke",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Knox_IN",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/US/Michigan",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Detroit",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/US/Mountain",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Navajo",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/US/Pacific",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Los_Angeles",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/US/Pacific-New",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Los_Angeles",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/US/Samoa",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Pacific/Midway",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/UTC",
"type": "reg",
"size": 127,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23044644,
"NumLink": 0,
"digest": "sha256:3c71b358be81e13b1c24e199a119fd001dbcdb90edc7d44c2c7ae175321a0215"
},
{
"name": "usr/share/zoneinfo/Universal",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "UTC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/W-SU",
"type": "reg",
"size": 1544,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23044828,
"NumLink": 0,
"digest": "sha256:02d55516d0f9d497998260b4f129aee59867b77a920ba2ca0c58b15ec8588e7a"
},
{
"name": "usr/share/zoneinfo/WET",
"type": "reg",
"size": 1873,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23045683,
"NumLink": 0,
"digest": "sha256:e5e7c4631295e7f17085e3530f99fc2984cc7e4bdb9a07db7702de8c18c2aab1"
},
{
"name": "usr/share/zoneinfo/Zulu",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "UTC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/iso3166.tab",
"type": "reg",
"size": 4445,
"modtime": "2017-02-28T16:08:12Z",
"mode": 33188,
"offset": 23046733,
"NumLink": 0,
"digest": "sha256:fe8bd1792c0e7ec6b42507c58a3ba433ebddadbf4c3031eaca6da9c41def37d2"
},
{
"name": "usr/share/zoneinfo/leap-seconds.list",
"type": "reg",
"size": 10666,
"modtime": "2018-01-15T17:19:18Z",
"mode": 33188,
"offset": 23049504,
"NumLink": 0,
"digest": "sha256:6255ce742f8189384c18dceb92ecf7639d49c480c4dc83c770e95a6f3bbdef1c"
},
{
"name": "usr/share/zoneinfo/localtime",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "/etc/localtime",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Abidjan",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Accra",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Accra",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Addis_Ababa",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Algiers",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Algiers",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Asmara",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Asmera",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Bamako",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Bangui",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Banjul",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Bissau",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Bissau",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Blantyre",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Blantyre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Brazzaville",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Bujumbura",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Blantyre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Cairo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Egypt",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Casablanca",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Casablanca",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Ceuta",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Ceuta",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Conakry",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Dakar",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Dar_es_Salaam",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Djibouti",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Douala",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/El_Aaiun",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/El_Aaiun",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Freetown",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Gaborone",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Blantyre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Harare",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Blantyre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Johannesburg",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Johannesburg",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Juba",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Juba",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Kampala",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Khartoum",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Khartoum",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Kigali",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Blantyre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Kinshasa",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Lagos",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Libreville",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Lome",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Luanda",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Lubumbashi",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Blantyre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Lusaka",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Blantyre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Malabo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Maputo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Blantyre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Maseru",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Johannesburg",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Mbabane",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Johannesburg",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Mogadishu",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Monrovia",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Monrovia",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Nairobi",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Ndjamena",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Ndjamena",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Niamey",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Nouakchott",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Ouagadougou",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Porto-Novo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Sao_Tome",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Sao_Tome",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Timbuktu",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Tripoli",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Libya",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Tunis",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Tunis",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Africa/Windhoek",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Windhoek",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Adak",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Adak",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Anchorage",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Anchorage",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Anguilla",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Antigua",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Araguaina",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Araguaina",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Argentina/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Argentina/Buenos_Aires",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/Buenos_Aires",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Argentina/Catamarca",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/Catamarca",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Argentina/ComodRivadavia",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/Catamarca",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Argentina/Cordoba",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/Cordoba",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Argentina/Jujuy",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/Jujuy",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Argentina/La_Rioja",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/Argentina/La_Rioja",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Argentina/Mendoza",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/Mendoza",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Argentina/Rio_Gallegos",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/Argentina/Rio_Gallegos",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Argentina/Salta",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/Argentina/Salta",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Argentina/San_Juan",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/Argentina/San_Juan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Argentina/San_Luis",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/Argentina/San_Luis",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Argentina/Tucuman",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/Argentina/Tucuman",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Argentina/Ushuaia",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/Argentina/Ushuaia",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Aruba",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Aruba",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Asuncion",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Asuncion",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Atikokan",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Atikokan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Atka",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Adak",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Bahia",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Bahia",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Bahia_Banderas",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Bahia_Banderas",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Barbados",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Barbados",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Belem",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Belem",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Belize",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Belize",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Blanc-Sablon",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Blanc-Sablon",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Boa_Vista",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Boa_Vista",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Bogota",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Bogota",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Boise",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Boise",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Buenos_Aires",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Buenos_Aires",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Cambridge_Bay",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Cambridge_Bay",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Campo_Grande",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Campo_Grande",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Cancun",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Cancun",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Caracas",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Caracas",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Catamarca",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Catamarca",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Cayenne",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Cayenne",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Cayman",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Cayman",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Chicago",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Chicago",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Chihuahua",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Chihuahua",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Coral_Harbour",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Atikokan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Cordoba",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Cordoba",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Costa_Rica",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Costa_Rica",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Creston",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Creston",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Cuiaba",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Cuiaba",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Curacao",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Aruba",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Danmarkshavn",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Danmarkshavn",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Dawson",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Dawson",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Dawson_Creek",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Dawson_Creek",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Denver",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Navajo",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Detroit",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Detroit",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Dominica",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Edmonton",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Edmonton",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Eirunepe",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Eirunepe",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/El_Salvador",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/El_Salvador",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Ensenada",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Ensenada",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Fort_Nelson",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Fort_Nelson",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Fort_Wayne",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Fort_Wayne",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Fortaleza",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Fortaleza",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Glace_Bay",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Glace_Bay",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Godthab",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Godthab",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Goose_Bay",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Goose_Bay",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Grand_Turk",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Grand_Turk",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Grenada",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Guadeloupe",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Guatemala",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Guatemala",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Guayaquil",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Guayaquil",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Guyana",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Guyana",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Halifax",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Halifax",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Havana",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Cuba",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Hermosillo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Hermosillo",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Indiana/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Indiana/Indianapolis",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/Fort_Wayne",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Indiana/Knox",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/Knox_IN",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Indiana/Marengo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/Indiana/Marengo",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Indiana/Petersburg",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/Indiana/Petersburg",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Indiana/Tell_City",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/Indiana/Tell_City",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Indiana/Vevay",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/Indiana/Vevay",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Indiana/Vincennes",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/Indiana/Vincennes",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Indiana/Winamac",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/Indiana/Winamac",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Indianapolis",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Fort_Wayne",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Inuvik",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Inuvik",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Iqaluit",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Iqaluit",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Jamaica",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Jamaica",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Jujuy",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Jujuy",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Juneau",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Juneau",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Kentucky/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Kentucky/Louisville",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/Louisville",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Kentucky/Monticello",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/Kentucky/Monticello",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Knox_IN",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Knox_IN",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Kralendijk",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Aruba",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/La_Paz",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/La_Paz",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Lima",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Lima",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Los_Angeles",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Los_Angeles",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Louisville",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Louisville",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Lower_Princes",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Aruba",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Maceio",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Maceio",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Managua",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Managua",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Manaus",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Manaus",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Marigot",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Martinique",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Martinique",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Matamoros",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Matamoros",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Mazatlan",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Mazatlan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Mendoza",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Mendoza",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Menominee",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Menominee",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Merida",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Merida",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Metlakatla",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Metlakatla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Mexico_City",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Mexico_City",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Miquelon",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Miquelon",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Moncton",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Moncton",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Monterrey",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Monterrey",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Montevideo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Montevideo",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Montreal",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Montreal",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Montserrat",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Nassau",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Nassau",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/New_York",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../posixrules",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Nipigon",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Nipigon",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Nome",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Nome",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Noronha",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Noronha",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/North_Dakota/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/North_Dakota/Beulah",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/North_Dakota/Beulah",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/North_Dakota/Center",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/North_Dakota/Center",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/North_Dakota/New_Salem",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../../America/North_Dakota/New_Salem",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Ojinaga",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Ojinaga",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Panama",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Cayman",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Pangnirtung",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Pangnirtung",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Paramaribo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Paramaribo",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Phoenix",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Phoenix",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Port-au-Prince",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Port-au-Prince",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Port_of_Spain",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Porto_Acre",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Porto_Acre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Porto_Velho",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Porto_Velho",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Puerto_Rico",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Puerto_Rico",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Punta_Arenas",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Punta_Arenas",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Rainy_River",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Rainy_River",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Rankin_Inlet",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Rankin_Inlet",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Recife",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Recife",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Regina",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Regina",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Resolute",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Resolute",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Rio_Branco",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Porto_Acre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Rosario",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Cordoba",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Santa_Isabel",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Ensenada",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Santarem",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Santarem",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Santiago",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Santiago",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Santo_Domingo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Santo_Domingo",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Sao_Paulo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Sao_Paulo",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Scoresbysund",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Scoresbysund",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Shiprock",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Navajo",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Sitka",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Sitka",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/St_Barthelemy",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/St_Johns",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/St_Johns",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/St_Kitts",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/St_Lucia",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/St_Thomas",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/St_Vincent",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Swift_Current",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Swift_Current",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Tegucigalpa",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Tegucigalpa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Thule",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Thule",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Thunder_Bay",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Thunder_Bay",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Tijuana",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Ensenada",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Toronto",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Montreal",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Tortola",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Vancouver",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Vancouver",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Virgin",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Whitehorse",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Whitehorse",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Winnipeg",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Winnipeg",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Yakutat",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Yakutat",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/America/Yellowknife",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Yellowknife",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Antarctica/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Antarctica/Casey",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Antarctica/Casey",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Antarctica/Davis",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Antarctica/Davis",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Antarctica/DumontDUrville",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Antarctica/DumontDUrville",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Antarctica/Macquarie",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Antarctica/Macquarie",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Antarctica/Mawson",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Antarctica/Mawson",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Antarctica/McMurdo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../NZ",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Antarctica/Palmer",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Antarctica/Palmer",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Antarctica/Rothera",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Antarctica/Rothera",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Antarctica/South_Pole",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../NZ",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Antarctica/Syowa",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Antarctica/Syowa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Antarctica/Troll",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Antarctica/Troll",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Antarctica/Vostok",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Antarctica/Vostok",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Arctic/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Arctic/Longyearbyen",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Arctic/Longyearbyen",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Aden",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Aden",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Almaty",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Almaty",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Amman",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Amman",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Anadyr",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Anadyr",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Aqtau",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Aqtau",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Aqtobe",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Aqtobe",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Ashgabat",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Ashgabat",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Ashkhabad",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Ashgabat",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Atyrau",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Atyrau",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Baghdad",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Baghdad",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Bahrain",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Bahrain",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Baku",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Baku",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Bangkok",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Bangkok",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Barnaul",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Barnaul",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Beirut",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Beirut",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Bishkek",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Bishkek",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Brunei",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Brunei",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Calcutta",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Calcutta",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Chita",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Chita",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Choibalsan",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Choibalsan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Chongqing",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../PRC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Chungking",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../PRC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Colombo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Colombo",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Dacca",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Dacca",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Damascus",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Damascus",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Dhaka",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Dacca",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Dili",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Dili",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Dubai",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Dubai",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Dushanbe",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Dushanbe",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Famagusta",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Famagusta",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Gaza",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Gaza",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Harbin",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../PRC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Hebron",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Hebron",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Ho_Chi_Minh",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Ho_Chi_Minh",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Hong_Kong",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Hongkong",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Hovd",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Hovd",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Irkutsk",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Irkutsk",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Istanbul",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Turkey",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Jakarta",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Jakarta",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Jayapura",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Jayapura",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Jerusalem",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Israel",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Kabul",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Kabul",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Kamchatka",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Kamchatka",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Karachi",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Karachi",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Kashgar",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Kashgar",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Kathmandu",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Kathmandu",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Katmandu",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Kathmandu",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Khandyga",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Khandyga",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Kolkata",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Calcutta",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Krasnoyarsk",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Krasnoyarsk",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Kuala_Lumpur",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Kuala_Lumpur",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Kuching",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Kuching",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Kuwait",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Aden",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Macao",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Macao",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Macau",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Macao",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Magadan",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Magadan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Makassar",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Makassar",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Manila",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Manila",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Muscat",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Dubai",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Nicosia",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Nicosia",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Novokuznetsk",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Novokuznetsk",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Novosibirsk",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Novosibirsk",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Omsk",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Omsk",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Oral",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Oral",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Phnom_Penh",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Bangkok",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Pontianak",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Pontianak",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Pyongyang",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Pyongyang",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Qatar",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Bahrain",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Qyzylorda",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Qyzylorda",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Rangoon",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Rangoon",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Riyadh",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Aden",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Saigon",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Ho_Chi_Minh",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Sakhalin",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Sakhalin",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Samarkand",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Samarkand",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Seoul",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../ROK",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Shanghai",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../PRC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Singapore",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Singapore",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Srednekolymsk",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Srednekolymsk",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Taipei",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../ROC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Tashkent",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Tashkent",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Tbilisi",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Tbilisi",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Tehran",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Iran",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Tel_Aviv",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Israel",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Thimbu",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Thimbu",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Thimphu",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Thimbu",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Tokyo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Japan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Tomsk",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Tomsk",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Ujung_Pandang",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Makassar",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Ulaanbaatar",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Ulaanbaatar",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Ulan_Bator",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Ulaanbaatar",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Urumqi",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Kashgar",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Ust-Nera",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Ust-Nera",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Vientiane",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Bangkok",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Vladivostok",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Vladivostok",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Yakutsk",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Yakutsk",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Yangon",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Rangoon",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Yekaterinburg",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Yekaterinburg",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Asia/Yerevan",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Yerevan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Atlantic/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Atlantic/Azores",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Atlantic/Azores",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Atlantic/Bermuda",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Atlantic/Bermuda",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Atlantic/Canary",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Atlantic/Canary",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Atlantic/Cape_Verde",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Atlantic/Cape_Verde",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Atlantic/Faeroe",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Atlantic/Faeroe",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Atlantic/Faroe",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Atlantic/Faeroe",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Atlantic/Jan_Mayen",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Arctic/Longyearbyen",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Atlantic/Madeira",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Atlantic/Madeira",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Atlantic/Reykjavik",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Iceland",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Atlantic/South_Georgia",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Atlantic/South_Georgia",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Atlantic/St_Helena",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Atlantic/Stanley",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Atlantic/Stanley",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Australia/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Australia/ACT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Australia/ACT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Australia/Adelaide",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Australia/Adelaide",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Australia/Brisbane",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Australia/Brisbane",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Australia/Broken_Hill",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Australia/Broken_Hill",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Australia/Canberra",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Australia/ACT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Australia/Currie",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Australia/Currie",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Australia/Darwin",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Australia/Darwin",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Australia/Eucla",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Australia/Eucla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Australia/Hobart",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Australia/Hobart",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Australia/LHI",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Australia/LHI",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Australia/Lindeman",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Australia/Lindeman",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Australia/Lord_Howe",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Australia/LHI",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Australia/Melbourne",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Australia/Melbourne",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Australia/NSW",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Australia/ACT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Australia/North",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Australia/Darwin",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Australia/Perth",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Australia/Perth",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Australia/Queensland",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Australia/Brisbane",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Australia/South",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Australia/Adelaide",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Australia/Sydney",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Australia/ACT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Australia/Tasmania",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Australia/Hobart",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Australia/Victoria",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Australia/Melbourne",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Australia/West",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Australia/Perth",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Australia/Yancowinna",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Australia/Broken_Hill",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Brazil/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Brazil/Acre",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Porto_Acre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Brazil/DeNoronha",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Noronha",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Brazil/East",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Sao_Paulo",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Brazil/West",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Manaus",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/CET",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../CET",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/CST6CDT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../CST6CDT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Canada/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Canada/Atlantic",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Halifax",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Canada/Central",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Winnipeg",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Canada/Eastern",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Montreal",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Canada/Mountain",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Edmonton",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Canada/Newfoundland",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/St_Johns",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Canada/Pacific",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Vancouver",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Canada/Saskatchewan",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Regina",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Canada/Yukon",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Whitehorse",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Chile/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Chile/Continental",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Santiago",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Chile/EasterIsland",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Chile/EasterIsland",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Cuba",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Cuba",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/EET",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../EET",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/EST",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../EST",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/EST5EDT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../EST5EDT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Egypt",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Egypt",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Eire",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Eire",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT+0",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT+1",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT+1",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT+10",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT+10",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT+11",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT+11",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT+12",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT+12",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT+2",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT+2",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT+3",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT+3",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT+4",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT+4",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT+5",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT+5",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT+6",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT+6",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT+7",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT+7",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT+8",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT+8",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT+9",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT+9",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT-0",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT-1",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT-1",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT-10",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT-10",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT-11",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT-11",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT-12",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT-12",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT-13",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT-13",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT-14",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT-14",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT-2",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT-2",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT-3",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT-3",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT-4",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT-4",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT-5",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT-5",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT-6",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT-6",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT-7",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT-7",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT-8",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT-8",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT-9",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Etc/GMT-9",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/GMT0",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/Greenwich",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/UCT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../UCT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/UTC",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../UTC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/Universal",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../UTC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Etc/Zulu",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../UTC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Amsterdam",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Amsterdam",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Andorra",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Andorra",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Astrakhan",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Astrakhan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Athens",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Athens",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Belfast",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../GB",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Belgrade",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Belgrade",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Berlin",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Berlin",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Bratislava",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Bratislava",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Brussels",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Brussels",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Bucharest",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Bucharest",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Budapest",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Budapest",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Busingen",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Busingen",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Chisinau",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Chisinau",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Copenhagen",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Copenhagen",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Dublin",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Eire",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Gibraltar",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Gibraltar",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Guernsey",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../GB",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Helsinki",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Helsinki",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Isle_of_Man",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../GB",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Istanbul",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Turkey",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Jersey",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../GB",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Kaliningrad",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Kaliningrad",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Kiev",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Kiev",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Kirov",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Kirov",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Lisbon",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Portugal",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Ljubljana",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Belgrade",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/London",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../GB",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Luxembourg",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Luxembourg",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Madrid",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Madrid",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Malta",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Malta",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Mariehamn",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Helsinki",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Minsk",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Minsk",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Monaco",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Monaco",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Moscow",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../W-SU",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Nicosia",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Asia/Nicosia",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Oslo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Arctic/Longyearbyen",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Paris",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Paris",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Podgorica",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Belgrade",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Prague",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Bratislava",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Riga",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Riga",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Rome",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Rome",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Samara",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Samara",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/San_Marino",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Rome",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Sarajevo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Belgrade",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Saratov",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Saratov",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Simferopol",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Simferopol",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Skopje",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Belgrade",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Sofia",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Sofia",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Stockholm",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Stockholm",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Tallinn",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Tallinn",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Tirane",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Tirane",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Tiraspol",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Chisinau",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Ulyanovsk",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Ulyanovsk",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Uzhgorod",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Uzhgorod",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Vaduz",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Busingen",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Vatican",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Rome",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Vienna",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Vienna",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Vilnius",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Vilnius",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Volgograd",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Volgograd",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Warsaw",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Poland",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Zagreb",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Belgrade",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Zaporozhye",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Zaporozhye",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Europe/Zurich",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Europe/Busingen",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Factory",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Factory",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/GB",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GB",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/GB-Eire",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GB",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/GMT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/GMT+0",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/GMT-0",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/GMT0",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Greenwich",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/HST",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../HST",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Hongkong",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Hongkong",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Iceland",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Iceland",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Indian/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Indian/Antananarivo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Indian/Chagos",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Indian/Chagos",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Indian/Christmas",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Indian/Christmas",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Indian/Cocos",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Indian/Cocos",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Indian/Comoro",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Indian/Kerguelen",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Indian/Kerguelen",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Indian/Mahe",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Indian/Mahe",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Indian/Maldives",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Indian/Maldives",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Indian/Mauritius",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Indian/Mauritius",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Indian/Mayotte",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Africa/Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Indian/Reunion",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Indian/Reunion",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Iran",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Iran",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Israel",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Israel",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Jamaica",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Jamaica",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Japan",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Japan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Kwajalein",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Kwajalein",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Libya",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Libya",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/MET",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../MET",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/MST",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../MST",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/MST7MDT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../MST7MDT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Mexico/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Mexico/BajaNorte",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Ensenada",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Mexico/BajaSur",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Mazatlan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Mexico/General",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Mexico_City",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/NZ",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../NZ",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/NZ-CHAT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../NZ-CHAT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Navajo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Navajo",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/PRC",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../PRC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/PST8PDT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../PST8PDT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Apia",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Apia",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Auckland",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../NZ",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Bougainville",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Bougainville",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Chatham",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../NZ-CHAT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Chuuk",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Chuuk",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Easter",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Chile/EasterIsland",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Efate",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Efate",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Enderbury",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Enderbury",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Fakaofo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Fakaofo",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Fiji",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Fiji",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Funafuti",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Funafuti",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Galapagos",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Galapagos",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Gambier",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Gambier",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Guadalcanal",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Guadalcanal",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Guam",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Guam",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Honolulu",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Honolulu",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Johnston",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Honolulu",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Kiritimati",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Kiritimati",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Kosrae",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Kosrae",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Kwajalein",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Kwajalein",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Majuro",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Majuro",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Marquesas",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Marquesas",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Midway",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Midway",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Nauru",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Nauru",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Niue",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Niue",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Norfolk",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Norfolk",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Noumea",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Noumea",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Pago_Pago",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Midway",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Palau",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Palau",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Pitcairn",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Pitcairn",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Pohnpei",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Pohnpei",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Ponape",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Pohnpei",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Port_Moresby",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Port_Moresby",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Rarotonga",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Rarotonga",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Saipan",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Guam",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Samoa",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Midway",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Tahiti",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Tahiti",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Tarawa",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Tarawa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Tongatapu",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Tongatapu",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Truk",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Chuuk",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Wake",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Wake",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Wallis",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Wallis",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Pacific/Yap",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Chuuk",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Poland",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Poland",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Portugal",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Portugal",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/ROC",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../ROC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/ROK",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../ROK",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Singapore",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Singapore",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/SystemV/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/SystemV/AST4",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Puerto_Rico",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/SystemV/AST4ADT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Halifax",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/SystemV/CST6",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Regina",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/SystemV/CST6CDT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Chicago",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/SystemV/EST5",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Cayman",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/SystemV/EST5EDT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../posixrules",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/SystemV/HST10",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Honolulu",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/SystemV/MST7",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Phoenix",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/SystemV/MST7MDT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Navajo",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/SystemV/PST8",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Pitcairn",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/SystemV/PST8PDT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Los_Angeles",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/SystemV/YST9",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Gambier",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/SystemV/YST9YDT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Anchorage",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Turkey",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Turkey",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/UCT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../UCT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/US/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/US/Alaska",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Anchorage",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/US/Aleutian",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Adak",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/US/Arizona",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Phoenix",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/US/Central",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Chicago",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/US/East-Indiana",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Fort_Wayne",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/US/Eastern",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../posixrules",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/US/Hawaii",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Honolulu",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/US/Indiana-Starke",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Knox_IN",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/US/Michigan",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Detroit",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/US/Mountain",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Navajo",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/US/Pacific",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Los_Angeles",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/US/Pacific-New",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../America/Los_Angeles",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/US/Samoa",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../../Pacific/Midway",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/UTC",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../UTC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Universal",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../UTC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/W-SU",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../W-SU",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/WET",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../WET",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posix/Zulu",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../UTC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/posixrules",
"type": "reg",
"size": 3545,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23065057,
"NumLink": 0,
"digest": "sha256:5fa6dccc303352e1195c4348b189f3085014d8a56a1976c8e8a32bd4fedb69fd"
},
{
"name": "usr/share/zoneinfo/right/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Abidjan",
"type": "reg",
"size": 710,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23066974,
"NumLink": 0,
"digest": "sha256:c177f3894cfb7acf27cfefcefd177aebfa1699e409e7fc4cd5a11ef116f8d236"
},
{
"name": "usr/share/zoneinfo/right/Africa/Accra",
"type": "reg",
"size": 1382,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23067446,
"NumLink": 0,
"digest": "sha256:fbee22da817893911690f2ee7093499893985e39c0ced1116709947f9bd206d5"
},
{
"name": "usr/share/zoneinfo/right/Africa/Addis_Ababa",
"type": "reg",
"size": 825,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23068277,
"NumLink": 0,
"digest": "sha256:ae39f133e4fad735bf5e099f9090b322f84efb7fe050968da196289a3c703905"
},
{
"name": "usr/share/zoneinfo/right/Africa/Algiers",
"type": "reg",
"size": 1300,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23068801,
"NumLink": 0,
"digest": "sha256:84081708d8029add247fd5add1ed963c2811b8b7841b65540ba7bbb02a068019"
},
{
"name": "usr/share/zoneinfo/right/Africa/Asmara",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Asmera",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Bamako",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Bangui",
"type": "reg",
"size": 711,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23069674,
"NumLink": 0,
"digest": "sha256:699bdf57ddb361bef922d520674b4fecd15db812c73e984727455fa7c3ada2d3"
},
{
"name": "usr/share/zoneinfo/right/Africa/Banjul",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Bissau",
"type": "reg",
"size": 748,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23070186,
"NumLink": 0,
"digest": "sha256:197606820a95c35d6c3d2f64e5e1d9542e198732db52d3b9ca6ff7294bb0ff9b"
},
{
"name": "usr/share/zoneinfo/right/Africa/Blantyre",
"type": "reg",
"size": 711,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23070680,
"NumLink": 0,
"digest": "sha256:3e39755e95604e242f4218d248dafd51ffa09392975c3f86c26907e6aad60da9"
},
{
"name": "usr/share/zoneinfo/right/Africa/Brazzaville",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Bujumbura",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Blantyre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Cairo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Egypt",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Casablanca",
"type": "reg",
"size": 2169,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23071255,
"NumLink": 0,
"digest": "sha256:891b90e824430fb61b050271bf1f409a9166b28f55885e50ca63ebb24d0679ab"
},
{
"name": "usr/share/zoneinfo/right/Africa/Ceuta",
"type": "reg",
"size": 2599,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23072513,
"NumLink": 0,
"digest": "sha256:ca6b06411c93017e93ded83d917cac8399690d6fb2771bcc66ecf132ee2e3ef6"
},
{
"name": "usr/share/zoneinfo/right/Africa/Conakry",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Dakar",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Dar_es_Salaam",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Djibouti",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Douala",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/El_Aaiun",
"type": "reg",
"size": 1999,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23074107,
"NumLink": 0,
"digest": "sha256:9568caf33829340936f06819a98b4458092b9c363ab915421ea3bf46e0665e9f"
},
{
"name": "usr/share/zoneinfo/right/Africa/Freetown",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Gaborone",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Blantyre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Harare",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Blantyre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Johannesburg",
"type": "reg",
"size": 811,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23075380,
"NumLink": 0,
"digest": "sha256:af7338314b4255661ce9afdd064e487321d4369ce15488395bb20bb4627d1ae2"
},
{
"name": "usr/share/zoneinfo/right/Africa/Juba",
"type": "reg",
"size": 1223,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23075905,
"NumLink": 0,
"digest": "sha256:2837717687d9cf2982e14bc35199ebe448380678330f15de5a6a2c49ef204b3c"
},
{
"name": "usr/share/zoneinfo/right/Africa/Kampala",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Khartoum",
"type": "reg",
"size": 1253,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23076712,
"NumLink": 0,
"digest": "sha256:0cd1350c87bd18606418d615dd78790f8ee9458ade7acb1af82b67afbdfc27f1"
},
{
"name": "usr/share/zoneinfo/right/Africa/Kigali",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Blantyre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Kinshasa",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Lagos",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Libreville",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Lome",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Luanda",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Lubumbashi",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Blantyre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Lusaka",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Blantyre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Malabo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Maputo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Blantyre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Maseru",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Johannesburg",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Mbabane",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Johannesburg",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Mogadishu",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Monrovia",
"type": "reg",
"size": 773,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23077761,
"NumLink": 0,
"digest": "sha256:eb1deb0dd9325844227c376d1532b484511c76f79f42032806553d36a9464e55"
},
{
"name": "usr/share/zoneinfo/right/Africa/Nairobi",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Ndjamena",
"type": "reg",
"size": 765,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23078309,
"NumLink": 0,
"digest": "sha256:180050fab205819406f6d6b572fe6f37eb805cc3f796543185c3229108470189"
},
{
"name": "usr/share/zoneinfo/right/Africa/Niamey",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Nouakchott",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Ouagadougou",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Porto-Novo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bangui",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Sao_Tome",
"type": "reg",
"size": 774,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23078918,
"NumLink": 0,
"digest": "sha256:29e95715ef8893f1bd3f1a1625d200fdd0fe65fe0432b659130b69cff2460dbb"
},
{
"name": "usr/share/zoneinfo/right/Africa/Timbuktu",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Tripoli",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Libya",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Africa/Tunis",
"type": "reg",
"size": 1250,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23079488,
"NumLink": 0,
"digest": "sha256:dbc64f2ddde5756e10d2ec8156bbe83f6f9ecbeb33b5bd6d771542f623d8fc39"
},
{
"name": "usr/share/zoneinfo/right/Africa/Windhoek",
"type": "reg",
"size": 1528,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23080260,
"NumLink": 0,
"digest": "sha256:9232e815aee566ea37642ad831abe23161c2f1c286dcffdc72d95f71d5e40289"
},
{
"name": "usr/share/zoneinfo/right/America/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Adak",
"type": "reg",
"size": 2905,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23081220,
"NumLink": 0,
"digest": "sha256:6d3e31970fee36399f30950e3f68ce7a5038be38a64547241c2ac97daaccd994"
},
{
"name": "usr/share/zoneinfo/right/America/Anchorage",
"type": "reg",
"size": 2920,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23082890,
"NumLink": 0,
"digest": "sha256:b5b62f7337785e26117bbc34ef7976018f021e63eb3e208f8c752d4f949ef2ef"
},
{
"name": "usr/share/zoneinfo/right/America/Anguilla",
"type": "reg",
"size": 710,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23084562,
"NumLink": 0,
"digest": "sha256:dbe365d9a8abfd164690db1cec6c51c19f86154900595509c991401e13a16585"
},
{
"name": "usr/share/zoneinfo/right/America/Antigua",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Araguaina",
"type": "reg",
"size": 1436,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23085077,
"NumLink": 0,
"digest": "sha256:43bf02f6b5a9899a0f6991d0077f4d2216971d7b30b5de2fc790e327e019ee5e"
},
{
"name": "usr/share/zoneinfo/right/America/Argentina/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Argentina/Buenos_Aires",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Buenos_Aires",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Argentina/Catamarca",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Catamarca",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Argentina/ComodRivadavia",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Catamarca",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Argentina/Cordoba",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Cordoba",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Argentina/Jujuy",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Jujuy",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Argentina/La_Rioja",
"type": "reg",
"size": 1649,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23086135,
"NumLink": 0,
"digest": "sha256:c7ded9821e935c4b533f557a31ccc2e4c3e89589091854b080f633b4a914c21c"
},
{
"name": "usr/share/zoneinfo/right/America/Argentina/Mendoza",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Mendoza",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Argentina/Rio_Gallegos",
"type": "reg",
"size": 1635,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23087183,
"NumLink": 0,
"digest": "sha256:e94db6d4db923985621567e409b6db116b4ef5bee49c750518bd2f6be1f6863b"
},
{
"name": "usr/share/zoneinfo/right/America/Argentina/Salta",
"type": "reg",
"size": 1607,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23088175,
"NumLink": 0,
"digest": "sha256:6829678c15796b06de4b1da92b4eb8468d2ad8b25e2811c5faca7e6d4cf96098"
},
{
"name": "usr/share/zoneinfo/right/America/Argentina/San_Juan",
"type": "reg",
"size": 1649,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23089159,
"NumLink": 0,
"digest": "sha256:87e65aaeddf18f1584306b42fc929f85e875f4eb729d15e2fb872bce277dc4c4"
},
{
"name": "usr/share/zoneinfo/right/America/Argentina/San_Luis",
"type": "reg",
"size": 1665,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23090160,
"NumLink": 0,
"digest": "sha256:959fd4de4b39ef6c77a411d434d75fe5941da9374d58f9d40fbeef0e01f0886c"
},
{
"name": "usr/share/zoneinfo/right/America/Argentina/Tucuman",
"type": "reg",
"size": 1663,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23091165,
"NumLink": 0,
"digest": "sha256:daae915c9ee5454bf0caff7c0e76f34166a3f6a3f2457325f02cdfe628324d04"
},
{
"name": "usr/share/zoneinfo/right/America/Argentina/Ushuaia",
"type": "reg",
"size": 1635,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23092175,
"NumLink": 0,
"digest": "sha256:45587dee42013d07c5cbe4cc755f71552d24755d364f933de4f0cbd595545f42"
},
{
"name": "usr/share/zoneinfo/right/America/Aruba",
"type": "reg",
"size": 752,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23093157,
"NumLink": 0,
"digest": "sha256:ec9709d87bbdd0aae7be4070156e5dc05c12d822da203cb1030354342bae2df0"
},
{
"name": "usr/share/zoneinfo/right/America/Asuncion",
"type": "reg",
"size": 2603,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23093653,
"NumLink": 0,
"digest": "sha256:1f9c20fdeff202451fcef7540f2787175f5c494157805091e7943d736dbcd919"
},
{
"name": "usr/share/zoneinfo/right/America/Atikokan",
"type": "reg",
"size": 885,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23095126,
"NumLink": 0,
"digest": "sha256:2fe220cd5f52ece0e3aa468ed33c443eaad61fc9e3bfbb47b7b754e8ad1a4613"
},
{
"name": "usr/share/zoneinfo/right/America/Atka",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Adak",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Bahia",
"type": "reg",
"size": 1576,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23095721,
"NumLink": 0,
"digest": "sha256:9945af7dcc0f3d98b12a34a0f312f79d54ae2cbd8261d4be697e3e0cd10f1b56"
},
{
"name": "usr/share/zoneinfo/right/America/Bahia_Banderas",
"type": "reg",
"size": 2128,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23096687,
"NumLink": 0,
"digest": "sha256:e44343d2e96738b96a6d83d5327dda8d041b1fb8e62b281f7fac7e56642b960b"
},
{
"name": "usr/share/zoneinfo/right/America/Barbados",
"type": "reg",
"size": 884,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23097912,
"NumLink": 0,
"digest": "sha256:64d664790217c76976a3a1f386ac82fc0f2295247f64547385711656b9f8f13b"
},
{
"name": "usr/share/zoneinfo/right/America/Belem",
"type": "reg",
"size": 1128,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23098477,
"NumLink": 0,
"digest": "sha256:5c276cfcf3596de48c0e4945ac5d19041e615fe79cd9e87fdaff0e7aaa558b7d"
},
{
"name": "usr/share/zoneinfo/right/America/Belize",
"type": "reg",
"size": 1518,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23099183,
"NumLink": 0,
"digest": "sha256:8128f9212e7e47481a2893da5f65f2c0047bf1329e314591435d7bb18b81b136"
},
{
"name": "usr/share/zoneinfo/right/America/Blanc-Sablon",
"type": "reg",
"size": 847,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23100102,
"NumLink": 0,
"digest": "sha256:6e9969e343200d865e82628b88310774524c38ae9c5ede30b7e5163acb70e6a1"
},
{
"name": "usr/share/zoneinfo/right/America/Boa_Vista",
"type": "reg",
"size": 1184,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23100654,
"NumLink": 0,
"digest": "sha256:33c2236e501f89ef5f2a5d1afcfc1434c264b10d1e14f9def6efd4c00c3f5003"
},
{
"name": "usr/share/zoneinfo/right/America/Bogota",
"type": "reg",
"size": 797,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23101399,
"NumLink": 0,
"digest": "sha256:b517e5bdb8aeeede03927787dcf5529f4fb33fbcb76daa7c5bcfda5320cd4468"
},
{
"name": "usr/share/zoneinfo/right/America/Boise",
"type": "reg",
"size": 2943,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23101924,
"NumLink": 0,
"digest": "sha256:7189c739198e19615b75a8a336aabd3cb223a765487382eb16c783cbb91c8c95"
},
{
"name": "usr/share/zoneinfo/right/America/Buenos_Aires",
"type": "reg",
"size": 1635,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23103582,
"NumLink": 0,
"digest": "sha256:d47983ff5ce9a8ecb9d65fdea3f3f603ccccdd170da513266c852d25ade2a6ca"
},
{
"name": "usr/share/zoneinfo/right/America/Cambridge_Bay",
"type": "reg",
"size": 2638,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23104571,
"NumLink": 0,
"digest": "sha256:dfa58c30cd1217d5f8e9dcb757d64bfb901c28a5f9475a38ac7de9e03c2d80c0"
},
{
"name": "usr/share/zoneinfo/right/America/Campo_Grande",
"type": "reg",
"size": 2556,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23106050,
"NumLink": 0,
"digest": "sha256:dab46c4d5ef0c2ec09cbf68010b9f73c7e5082669c3b92e3be032a6af4ccb61d"
},
{
"name": "usr/share/zoneinfo/right/America/Cancun",
"type": "reg",
"size": 1356,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23107525,
"NumLink": 0,
"digest": "sha256:c8ce65577ce494166ad8f57f14a55ae961e27dd7aa356d8802fb5c9f2d510fb6"
},
{
"name": "usr/share/zoneinfo/right/America/Caracas",
"type": "reg",
"size": 815,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23108350,
"NumLink": 0,
"digest": "sha256:08c9e8707db81610f374294e7d3e8338ce329e60bc467547ef1714287271f563"
},
{
"name": "usr/share/zoneinfo/right/America/Catamarca",
"type": "reg",
"size": 1635,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23108882,
"NumLink": 0,
"digest": "sha256:0d0d4323f8e3489b61b4c512a2300d68672a9f92a8d7bf5ecaf6e2dd786ee449"
},
{
"name": "usr/share/zoneinfo/right/America/Cayenne",
"type": "reg",
"size": 750,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23109869,
"NumLink": 0,
"digest": "sha256:b11737b697bbe5e67a9fc1ab08a0dd868df4b2290225e3b15d7b677ec2009bcb"
},
{
"name": "usr/share/zoneinfo/right/America/Cayman",
"type": "reg",
"size": 743,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23110367,
"NumLink": 0,
"digest": "sha256:6962181adfca6314029efa4c3a2dae17c775d6031cff3bd6c63d49ed30c31cb1"
},
{
"name": "usr/share/zoneinfo/right/America/Chicago",
"type": "reg",
"size": 4125,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23110864,
"NumLink": 0,
"digest": "sha256:94f71253caa6bde6db52f2f5ad3b816df0c2da9af7cc7d4a4abf1b91e391821f"
},
{
"name": "usr/share/zoneinfo/right/America/Chihuahua",
"type": "reg",
"size": 2062,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23113109,
"NumLink": 0,
"digest": "sha256:1f0007a74669d2ede5ccea2c49ff17ced52ae2e28756ebbf5eb08aa08b3e9d45"
},
{
"name": "usr/share/zoneinfo/right/America/Coral_Harbour",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Atikokan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Cordoba",
"type": "reg",
"size": 1635,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23114349,
"NumLink": 0,
"digest": "sha256:3941316300e3137c4e8826d17e00a9df06aff8cec73034e6b61e9b371bb0a540"
},
{
"name": "usr/share/zoneinfo/right/America/Costa_Rica",
"type": "reg",
"size": 881,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23115335,
"NumLink": 0,
"digest": "sha256:a697b205589062aab7599c2e812f164df50b32f2d9c8f2ea7b42f1e53b4e3e94"
},
{
"name": "usr/share/zoneinfo/right/America/Creston",
"type": "reg",
"size": 773,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23115910,
"NumLink": 0,
"digest": "sha256:071acbeb9c771d5bd244b0d6bd73b7e7893aa40deb19c1074908a0094de4463f"
},
{
"name": "usr/share/zoneinfo/right/America/Cuiaba",
"type": "reg",
"size": 2528,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23116419,
"NumLink": 0,
"digest": "sha256:7728725c174a947f2e7c6acee48c0b40b6ef1947aa706c8b3ef1bff9a3b4a453"
},
{
"name": "usr/share/zoneinfo/right/America/Curacao",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Aruba",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Danmarkshavn",
"type": "reg",
"size": 1252,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23117933,
"NumLink": 0,
"digest": "sha256:99b1ff4ad370c93145279b56504ccd2d0bb39f92c002aaa490bd5969285cdd1f"
},
{
"name": "usr/share/zoneinfo/right/America/Dawson",
"type": "reg",
"size": 2633,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23118705,
"NumLink": 0,
"digest": "sha256:93a9938a26f814adf696f2e4f537164bc8ca9e4843d8bc6cb96dc59377534437"
},
{
"name": "usr/share/zoneinfo/right/America/Dawson_Creek",
"type": "reg",
"size": 1599,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23120198,
"NumLink": 0,
"digest": "sha256:4f2bb35e6c4c8804409dc81ad024881457705dca4ae468184f73c04bff51ead1"
},
{
"name": "usr/share/zoneinfo/right/America/Denver",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Navajo",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Detroit",
"type": "reg",
"size": 2728,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23121184,
"NumLink": 0,
"digest": "sha256:d7c0f33030d0e2d553b385457787d9faf8ce714c0b3a17778d5d45ad1a6560b2"
},
{
"name": "usr/share/zoneinfo/right/America/Dominica",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Edmonton",
"type": "reg",
"size": 2942,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23122773,
"NumLink": 0,
"digest": "sha256:da80c9534c7b548bbb92e982c1ca2b15b30608ca72567d20e9881444cf58b4aa"
},
{
"name": "usr/share/zoneinfo/right/America/Eirunepe",
"type": "reg",
"size": 1216,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23124428,
"NumLink": 0,
"digest": "sha256:9396017faecf7a3931851bec7fc924eeb8a2a53ceda91da3d458edcb555ced62"
},
{
"name": "usr/share/zoneinfo/right/America/El_Salvador",
"type": "reg",
"size": 790,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23125181,
"NumLink": 0,
"digest": "sha256:04993007b8086580ac35e8fa524bbdcd45f045c965608fca3da26deaa1ede337"
},
{
"name": "usr/share/zoneinfo/right/America/Ensenada",
"type": "reg",
"size": 2896,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23125708,
"NumLink": 0,
"digest": "sha256:a1a5199868d6aa4c24fef5e908e99e4c6e116d16afc554d25ec431990d8f02da"
},
{
"name": "usr/share/zoneinfo/right/America/Fort_Nelson",
"type": "reg",
"size": 2789,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23127332,
"NumLink": 0,
"digest": "sha256:c20108fb21d7e76aef2c0bd669f1dfd6043b5269020bde6cff669f088d13abec"
},
{
"name": "usr/share/zoneinfo/right/America/Fort_Wayne",
"type": "reg",
"size": 2215,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23128915,
"NumLink": 0,
"digest": "sha256:7dfb7b2796f9b9d9a69d402b2e8269a2f834e1d01e2da34af490b2b24c21ace5"
},
{
"name": "usr/share/zoneinfo/right/America/Fortaleza",
"type": "reg",
"size": 1268,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23130193,
"NumLink": 0,
"digest": "sha256:6ab4cad81917df05f5ba0f3dc90923aa5074f740095a6439a8739249bc26df0e"
},
{
"name": "usr/share/zoneinfo/right/America/Glace_Bay",
"type": "reg",
"size": 2746,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23130981,
"NumLink": 0,
"digest": "sha256:799c72cab5fafdcf48dfe766d52c24e7fd7f4a61e0d554c97888023766219287"
},
{
"name": "usr/share/zoneinfo/right/America/Godthab",
"type": "reg",
"size": 2418,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23132536,
"NumLink": 0,
"digest": "sha256:c9d318e4f59dd5f3016f5e3981a67ab34140b767c987178738e14a80eae74976"
},
{
"name": "usr/share/zoneinfo/right/America/Goose_Bay",
"type": "reg",
"size": 3759,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23133907,
"NumLink": 0,
"digest": "sha256:462bef059c879d10cbcce574a128bc2d847dfc342dd77f43e6494f3aba6cf94c"
},
{
"name": "usr/share/zoneinfo/right/America/Grand_Turk",
"type": "reg",
"size": 2421,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23135999,
"NumLink": 0,
"digest": "sha256:50148cea43182f25effdaba081e72c55239b58f5985a2af2e1ace6d591ef2388"
},
{
"name": "usr/share/zoneinfo/right/America/Grenada",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Guadeloupe",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Guatemala",
"type": "reg",
"size": 846,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23137448,
"NumLink": 0,
"digest": "sha256:b8d8d7b3edd1a237b4d4aee860162700cf11e25aa9102ba61bed6640ced94463"
},
{
"name": "usr/share/zoneinfo/right/America/Guayaquil",
"type": "reg",
"size": 797,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23138012,
"NumLink": 0,
"digest": "sha256:cd8bef091fd1f925658709c0f130381dc8b9406dd50af15dbba3ee0740761777"
},
{
"name": "usr/share/zoneinfo/right/America/Guyana",
"type": "reg",
"size": 792,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23138541,
"NumLink": 0,
"digest": "sha256:607345856a6751417bbfc7c185e9245fc34af1d87b2ea11c77aadc7b8f7b7799"
},
{
"name": "usr/share/zoneinfo/right/America/Halifax",
"type": "reg",
"size": 3978,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23139058,
"NumLink": 0,
"digest": "sha256:86c84ef0a21a387fdd0058046268c5eca94c10cb73231638724d344cc478bd10"
},
{
"name": "usr/share/zoneinfo/right/America/Havana",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Cuba",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Hermosillo",
"type": "reg",
"size": 994,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23141298,
"NumLink": 0,
"digest": "sha256:778350bbb96f05ab2e74834f35be215801da358dd7c261f1ba3bfe6f1c9b07e9"
},
{
"name": "usr/share/zoneinfo/right/America/Indiana/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Indiana/Indianapolis",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Fort_Wayne",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Indiana/Knox",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Knox_IN",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Indiana/Marengo",
"type": "reg",
"size": 2271,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23142027,
"NumLink": 0,
"digest": "sha256:b3e8fe887a5ce407f7208f16d0b296a4594b3f93de33b70d5a260139f66cfab2"
},
{
"name": "usr/share/zoneinfo/right/America/Indiana/Petersburg",
"type": "reg",
"size": 2453,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23143358,
"NumLink": 0,
"digest": "sha256:285f27ebb54838060d3a33238dae6ee695af5c258d40780fc89c797a239360ba"
},
{
"name": "usr/share/zoneinfo/right/America/Indiana/Tell_City",
"type": "reg",
"size": 2275,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23144773,
"NumLink": 0,
"digest": "sha256:17b0f617df438b7a18a3bbb5f43d1a0fbda97ac074c429d699fc6ff2b70f9080"
},
{
"name": "usr/share/zoneinfo/right/America/Indiana/Vevay",
"type": "reg",
"size": 1963,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23146072,
"NumLink": 0,
"digest": "sha256:74d2b6f797d63017075f1425e695e5f61a6e1b3ef08f812bc330e22fae18333a"
},
{
"name": "usr/share/zoneinfo/right/America/Indiana/Vincennes",
"type": "reg",
"size": 2243,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23147239,
"NumLink": 0,
"digest": "sha256:970baaf1ed777c07d74546d61282e9df9a0488ad90084210a770c82ae78b8357"
},
{
"name": "usr/share/zoneinfo/right/America/Indiana/Winamac",
"type": "reg",
"size": 2327,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23148540,
"NumLink": 0,
"digest": "sha256:cade9b122bd306fd5bb1fd4ff0471861c8eaed414a166ba570554a349a7a20b6"
},
{
"name": "usr/share/zoneinfo/right/America/Indianapolis",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Fort_Wayne",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Inuvik",
"type": "reg",
"size": 2468,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23149925,
"NumLink": 0,
"digest": "sha256:c51f2f3cef4844e5c800ed3592d85bc69e5f05dc4a53e94c76b2433be16b1a5f"
},
{
"name": "usr/share/zoneinfo/right/America/Iqaluit",
"type": "reg",
"size": 2586,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23151324,
"NumLink": 0,
"digest": "sha256:7179d696e8f2aac641bbe8a0b0635128246fd4669e258befaac2e91170f75d1e"
},
{
"name": "usr/share/zoneinfo/right/America/Jamaica",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Jamaica",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Jujuy",
"type": "reg",
"size": 1607,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23152819,
"NumLink": 0,
"digest": "sha256:29822a6d077e97673b9e892cd65c00709d43e0aa90ce6d84b0c1228083def36d"
},
{
"name": "usr/share/zoneinfo/right/America/Juneau",
"type": "reg",
"size": 2902,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23153792,
"NumLink": 0,
"digest": "sha256:2be628832b78514026f7932644e221fd4490d502f686f069d7ebf8ba0b220c40"
},
{
"name": "usr/share/zoneinfo/right/America/Kentucky/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Kentucky/Louisville",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Louisville",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Kentucky/Monticello",
"type": "reg",
"size": 2901,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23155552,
"NumLink": 0,
"digest": "sha256:ff56ff4d9ee52923c57354d5d836e87cc8acb748bbf0648c2406bcbafaa4227c"
},
{
"name": "usr/share/zoneinfo/right/America/Knox_IN",
"type": "reg",
"size": 2977,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23157195,
"NumLink": 0,
"digest": "sha256:ac4c928ad03acaf42f346aa81bf9d269a513adb14a955ff55a5177927832c6a8"
},
{
"name": "usr/share/zoneinfo/right/America/Kralendijk",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Aruba",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/La_Paz",
"type": "reg",
"size": 783,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23158926,
"NumLink": 0,
"digest": "sha256:beab009d797e8c2a8607eaf7192b5ff2736c220073fc5c29e45a8cd9dc6a5a1e"
},
{
"name": "usr/share/zoneinfo/right/America/Lima",
"type": "reg",
"size": 957,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23159443,
"NumLink": 0,
"digest": "sha256:c75b82ab26d2838d0eec739aabecbc33de43a72d3f7551e25284197563ad6fcb"
},
{
"name": "usr/share/zoneinfo/right/America/Los_Angeles",
"type": "reg",
"size": 3385,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23160074,
"NumLink": 0,
"digest": "sha256:5f2a6fb2744e29e2a6ac88e89843ce0c74f8934b37d1b35d67e115d5363c9e57"
},
{
"name": "usr/share/zoneinfo/right/America/Louisville",
"type": "reg",
"size": 3321,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23161959,
"NumLink": 0,
"digest": "sha256:2d8d4b0dbd3eda1d9c9992b42fb309f7c645a9ac0048e2dba3283e7ad9d67f1b"
},
{
"name": "usr/share/zoneinfo/right/America/Lower_Princes",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Aruba",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Maceio",
"type": "reg",
"size": 1296,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23163862,
"NumLink": 0,
"digest": "sha256:dd3e4475c42101aaa3010513fb3dc80dcf598582c4d7aa3caa0dd1cbd55e23ac"
},
{
"name": "usr/share/zoneinfo/right/America/Managua",
"type": "reg",
"size": 1003,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23164661,
"NumLink": 0,
"digest": "sha256:1a2e937499925a46e4d2b93113e7b035fdc270174a8fb4b65fd61f163b430ca3"
},
{
"name": "usr/share/zoneinfo/right/America/Manaus",
"type": "reg",
"size": 1156,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23165300,
"NumLink": 0,
"digest": "sha256:876c087b224fcebb22e281b18dadd4283844ab83280afc8c9092ad2f8defd3a4"
},
{
"name": "usr/share/zoneinfo/right/America/Marigot",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Martinique",
"type": "reg",
"size": 797,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23166078,
"NumLink": 0,
"digest": "sha256:18a0f75cca7c62ff1b7c8e0463856c1fa811e796806ef23cfd53d40a860861fa"
},
{
"name": "usr/share/zoneinfo/right/America/Matamoros",
"type": "reg",
"size": 1956,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23166601,
"NumLink": 0,
"digest": "sha256:298a0a44d6576a9c127e048262a3f7f1b613653e0520a75bf912a65ccef50771"
},
{
"name": "usr/share/zoneinfo/right/America/Mazatlan",
"type": "reg",
"size": 2104,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23167751,
"NumLink": 0,
"digest": "sha256:972ebc94965c29a285412a68e853fc7729bd741b5be52edeb9f3b9a1a707ac43"
},
{
"name": "usr/share/zoneinfo/right/America/Mendoza",
"type": "reg",
"size": 1635,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23168962,
"NumLink": 0,
"digest": "sha256:0f4bf9977f2591788ba0b60418f7b932da1112abffe03a742ee2b770d4fcefbf"
},
{
"name": "usr/share/zoneinfo/right/America/Menominee",
"type": "reg",
"size": 2823,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23169947,
"NumLink": 0,
"digest": "sha256:a9f238c519f7699a3bdd001ffb5588fc5c80329a14c95309aa424c0026440108"
},
{
"name": "usr/share/zoneinfo/right/America/Merida",
"type": "reg",
"size": 1996,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23171534,
"NumLink": 0,
"digest": "sha256:a6352eb8ee46c326f0231e5e22ae330d465964514c21390e28aa5ddee377fb5c"
},
{
"name": "usr/share/zoneinfo/right/America/Metlakatla",
"type": "reg",
"size": 1958,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23172688,
"NumLink": 0,
"digest": "sha256:0e130ce578bb5bffd9871e92519541592cca5e32e28912f82883efb4348f7382"
},
{
"name": "usr/share/zoneinfo/right/America/Mexico_City",
"type": "reg",
"size": 2158,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23173867,
"NumLink": 0,
"digest": "sha256:2b2e4e7d462f693d46142205a03882b383f3cfbc6eaa0a4c514fe71fd5112f12"
},
{
"name": "usr/share/zoneinfo/right/America/Miquelon",
"type": "reg",
"size": 2222,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23175115,
"NumLink": 0,
"digest": "sha256:7da3a78f48b2870cdb2b246107ad28ad353c006f6d10e26034f8bb7ae299e837"
},
{
"name": "usr/share/zoneinfo/right/America/Moncton",
"type": "reg",
"size": 3703,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23176404,
"NumLink": 0,
"digest": "sha256:d587577011570e8271781f98b52f5ccb8650a7a1dc2c50789f4cb5d5d7e9c13a"
},
{
"name": "usr/share/zoneinfo/right/America/Monterrey",
"type": "reg",
"size": 1956,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23178451,
"NumLink": 0,
"digest": "sha256:a0334e1c8f6ebfdb959a536fea620a72031db45036d563864cdaba4e34b0c2c7"
},
{
"name": "usr/share/zoneinfo/right/America/Montevideo",
"type": "reg",
"size": 2090,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23179597,
"NumLink": 0,
"digest": "sha256:17c9e4bcff4188d1cb3a8bc583991d6ee81896fc64664f8d6b01c02fda66c1a3"
},
{
"name": "usr/share/zoneinfo/right/America/Montreal",
"type": "reg",
"size": 4043,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23180808,
"NumLink": 0,
"digest": "sha256:bd1fdaf567be65339c2fe8cadf9e690d0929548cd731edcb41ae7f322885a590"
},
{
"name": "usr/share/zoneinfo/right/America/Montserrat",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Nassau",
"type": "reg",
"size": 2824,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23183080,
"NumLink": 0,
"digest": "sha256:b3526d4c47155f9f9049070f75b3981b9f17b153e9482afe6e3f83e0f84adad6"
},
{
"name": "usr/share/zoneinfo/right/America/New_York",
"type": "reg",
"size": 4085,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23184683,
"NumLink": 0,
"digest": "sha256:9699b3dbf1b5a2fe33cc0eeb1bae542d83608786c0b1872b07b24adda81556a1"
},
{
"name": "usr/share/zoneinfo/right/America/Nipigon",
"type": "reg",
"size": 2671,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23186917,
"NumLink": 0,
"digest": "sha256:bedddedf42d1ecd3db1eeb61988fa1216f75b06c45a768822a16b4bf3e78542f"
},
{
"name": "usr/share/zoneinfo/right/America/Nome",
"type": "reg",
"size": 2916,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23188435,
"NumLink": 0,
"digest": "sha256:2f167608a9d4171d89ee0a4d68c3ee845ebbd073e3759dc663a95dc8c865e4c1"
},
{
"name": "usr/share/zoneinfo/right/America/Noronha",
"type": "reg",
"size": 1268,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23190108,
"NumLink": 0,
"digest": "sha256:276cc0daf61bee62a6f8ca0636b0da6a4bebf95dbdde6859c7b53434ab6e3eaa"
},
{
"name": "usr/share/zoneinfo/right/America/North_Dakota/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/North_Dakota/Beulah",
"type": "reg",
"size": 2929,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23190948,
"NumLink": 0,
"digest": "sha256:5a05c79be4ba9c4ed5a70aedbb8e83c2864c9ee5d974824130552d11097f654d"
},
{
"name": "usr/share/zoneinfo/right/America/North_Dakota/Center",
"type": "reg",
"size": 2929,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23192612,
"NumLink": 0,
"digest": "sha256:c4daf0f8f179948ca4f3edfef1c4bf6ea9926d157cbb83334d990c4f3ae76fb3"
},
{
"name": "usr/share/zoneinfo/right/America/North_Dakota/New_Salem",
"type": "reg",
"size": 2929,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23194273,
"NumLink": 0,
"digest": "sha256:f728e13f15039666ade97ebb9e0e7d54b575495e14aa88ccf2761d29e5a390f4"
},
{
"name": "usr/share/zoneinfo/right/America/Ojinaga",
"type": "reg",
"size": 2062,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23195927,
"NumLink": 0,
"digest": "sha256:e9cb05ec786e833e837b19a3d7bfe611e6d3ff3da1fc576fa5ea4f44c43f937f"
},
{
"name": "usr/share/zoneinfo/right/America/Panama",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Cayman",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Pangnirtung",
"type": "reg",
"size": 2648,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23197164,
"NumLink": 0,
"digest": "sha256:c71e8ae865312e517ebc4076ecb9665c2cfdc7155549d18462e01275961925b8"
},
{
"name": "usr/share/zoneinfo/right/America/Paramaribo",
"type": "reg",
"size": 822,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23198646,
"NumLink": 0,
"digest": "sha256:69e775220eadc309a7cfce0aabc4936f9e5631a4a64a844d157a8371d5293c5c"
},
{
"name": "usr/share/zoneinfo/right/America/Phoenix",
"type": "reg",
"size": 893,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23199171,
"NumLink": 0,
"digest": "sha256:8a89f0e65cd0a0b2edbbf65a7a81441aa91073b133edac17c25c6c9f809b8995"
},
{
"name": "usr/share/zoneinfo/right/America/Port-au-Prince",
"type": "reg",
"size": 1995,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23199760,
"NumLink": 0,
"digest": "sha256:aab1855d3200ed78c12406a44d8558a9a875dc57f94090b2c205811a92b5e066"
},
{
"name": "usr/share/zoneinfo/right/America/Port_of_Spain",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Porto_Acre",
"type": "reg",
"size": 1188,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23200987,
"NumLink": 0,
"digest": "sha256:fa8436992c68303c75f248539845c855269b6f3c307032f2510b43e0779efbe4"
},
{
"name": "usr/share/zoneinfo/right/America/Porto_Velho",
"type": "reg",
"size": 1128,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23201719,
"NumLink": 0,
"digest": "sha256:753c3bf10a58b02e6b991d48e356a4953cc1cdd3042665a1824dadf58d6ace07"
},
{
"name": "usr/share/zoneinfo/right/America/Puerto_Rico",
"type": "reg",
"size": 795,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23202435,
"NumLink": 0,
"digest": "sha256:a9e478dd8515a4c8086ff535afe44db1cf53b9400ec62aed7b6d122ecfb778f3"
},
{
"name": "usr/share/zoneinfo/right/America/Punta_Arenas",
"type": "reg",
"size": 2437,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23202967,
"NumLink": 0,
"digest": "sha256:9039a86b9fae5f31ebd11cf51e70e79d7fe7e082ef0f9f85a6dac48cb7f945eb"
},
{
"name": "usr/share/zoneinfo/right/America/Rainy_River",
"type": "reg",
"size": 2671,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23204364,
"NumLink": 0,
"digest": "sha256:1680a0ae7e1d154aa9672b6e2d24155987de256acd7273f23177ef258d4ffe16"
},
{
"name": "usr/share/zoneinfo/right/America/Rankin_Inlet",
"type": "reg",
"size": 2470,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23205889,
"NumLink": 0,
"digest": "sha256:1bb3cc33e21e5e7663a0cabcb02f9e7f74ee0619dcd0d84d4a4a31f611698b57"
},
{
"name": "usr/share/zoneinfo/right/America/Recife",
"type": "reg",
"size": 1268,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23207291,
"NumLink": 0,
"digest": "sha256:1e7229c4468b197480ae588656cd52e8ff11bacb3af0bdfab2f090e6d25a9e0c"
},
{
"name": "usr/share/zoneinfo/right/America/Regina",
"type": "reg",
"size": 1534,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23208080,
"NumLink": 0,
"digest": "sha256:7ef7b8f3a4e1baf07289907c77a4218ac0be68c7a24e980940a05cbba77e53c9"
},
{
"name": "usr/share/zoneinfo/right/America/Resolute",
"type": "reg",
"size": 2470,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23208978,
"NumLink": 0,
"digest": "sha256:2a208ed79da175c5081c8db0fb767d9b0e0270e4a73bb95f24ae0ffd22b6354f"
},
{
"name": "usr/share/zoneinfo/right/America/Rio_Branco",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Porto_Acre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Rosario",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Cordoba",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Santa_Isabel",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Ensenada",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Santarem",
"type": "reg",
"size": 1158,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23210497,
"NumLink": 0,
"digest": "sha256:1a927437ef7079f8ce9cb80607c823e609f1c9cd757282718932435dd4e8a5bf"
},
{
"name": "usr/share/zoneinfo/right/America/Santiago",
"type": "reg",
"size": 3064,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23211220,
"NumLink": 0,
"digest": "sha256:1f31a3a5f1e1ea4f97a2cd73d27dc28c51ca294378f1268b021e2e0be6a336c2"
},
{
"name": "usr/share/zoneinfo/right/America/Santo_Domingo",
"type": "reg",
"size": 1031,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23212957,
"NumLink": 0,
"digest": "sha256:24bf349defe5c3ed5d8950593acbcd57dc662784ddfae68b31cddfa02746f2ba"
},
{
"name": "usr/share/zoneinfo/right/America/Sao_Paulo",
"type": "reg",
"size": 2556,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23213625,
"NumLink": 0,
"digest": "sha256:11592bb442b3322b0f50685787c1ea58e5db05774a5c2cb6e133cbdd763c2bc5"
},
{
"name": "usr/share/zoneinfo/right/America/Scoresbysund",
"type": "reg",
"size": 2456,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23215107,
"NumLink": 0,
"digest": "sha256:42653212318e5d075e6fddd5a579507a13bbd0538623d1943cfff1569e24d0de"
},
{
"name": "usr/share/zoneinfo/right/America/Shiprock",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Navajo",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Sitka",
"type": "reg",
"size": 2890,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23216534,
"NumLink": 0,
"digest": "sha256:81ba6e3b87b7f9c9814c4f607a3c722a0bbd8355ab1647c51847882b3a3c0628"
},
{
"name": "usr/share/zoneinfo/right/America/St_Barthelemy",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/St_Johns",
"type": "reg",
"size": 4204,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23218260,
"NumLink": 0,
"digest": "sha256:10784794564849767481803ad10924bd7092c041b5e5859dc7cdf883abe13a7e"
},
{
"name": "usr/share/zoneinfo/right/America/St_Kitts",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/St_Lucia",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/St_Thomas",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/St_Vincent",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Swift_Current",
"type": "reg",
"size": 1114,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23220672,
"NumLink": 0,
"digest": "sha256:7752d4a7cd93e6c9a16f89dfa148ce6ac6f491cf40359f9d6730e03b4aedf848"
},
{
"name": "usr/share/zoneinfo/right/America/Tegucigalpa",
"type": "reg",
"size": 818,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23221355,
"NumLink": 0,
"digest": "sha256:c37dd7463adb25b95fd20bd17473e710e03c9c5d36481b4626a82aabd7469983"
},
{
"name": "usr/share/zoneinfo/right/America/Thule",
"type": "reg",
"size": 2068,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23221893,
"NumLink": 0,
"digest": "sha256:67e571d61867a4ea726d7b19f5fabca2a7751219685d57eee29570ec9225f0ea"
},
{
"name": "usr/share/zoneinfo/right/America/Thunder_Bay",
"type": "reg",
"size": 2751,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23223108,
"NumLink": 0,
"digest": "sha256:2c04cafec84e648e5d1154986626b32ebdb0def10d7c8843d27bbee20ad82e5e"
},
{
"name": "usr/share/zoneinfo/right/America/Tijuana",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Ensenada",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Toronto",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Montreal",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Tortola",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Vancouver",
"type": "reg",
"size": 3441,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23224766,
"NumLink": 0,
"digest": "sha256:20b9d3c10c060d827a80bb81260a5407b879a95af0e114b9a258c9214ac55f22"
},
{
"name": "usr/share/zoneinfo/right/America/Virgin",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Anguilla",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/America/Whitehorse",
"type": "reg",
"size": 2633,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23226731,
"NumLink": 0,
"digest": "sha256:84e26d472821fc0a5280f3bbd9b7d6d4130ccd78272d637314628cf299f203e5"
},
{
"name": "usr/share/zoneinfo/right/America/Winnipeg",
"type": "reg",
"size": 3431,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23228220,
"NumLink": 0,
"digest": "sha256:a91e3074914af25d44e7428f07b7e15c46a9b1c61adf146d78058bfb95128031"
},
{
"name": "usr/share/zoneinfo/right/America/Yakutat",
"type": "reg",
"size": 2854,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23230115,
"NumLink": 0,
"digest": "sha256:a8d6177b9fb9653500c7a5468e35508251be2a6dce9040ad9ebfbffcd4cc3ad2"
},
{
"name": "usr/share/zoneinfo/right/America/Yellowknife",
"type": "reg",
"size": 2520,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23231745,
"NumLink": 0,
"digest": "sha256:ffb6c39b1c757ff250651391b07dc8d3e3ea361f837472ef57c62eea144677ef"
},
{
"name": "usr/share/zoneinfo/right/Antarctica/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Antarctica/Casey",
"type": "reg",
"size": 837,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23233212,
"NumLink": 0,
"digest": "sha256:f2ca1ef915c00d2afcdb6d67afb8f02bd84eaa77953373a0b0744bb22814e632"
},
{
"name": "usr/share/zoneinfo/right/Antarctica/Davis",
"type": "reg",
"size": 837,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23233752,
"NumLink": 0,
"digest": "sha256:68203c490a421f72442b46179525552b863773b136d499e9fda7dde39a52b234"
},
{
"name": "usr/share/zoneinfo/right/Antarctica/DumontDUrville",
"type": "reg",
"size": 742,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23234302,
"NumLink": 0,
"digest": "sha256:3b2fc9d546516f429b2e95338925a2e52c328d0145157b311830c8f8fcbe56ea"
},
{
"name": "usr/share/zoneinfo/right/Antarctica/Macquarie",
"type": "reg",
"size": 2069,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23234801,
"NumLink": 0,
"digest": "sha256:05971185e4ba26201409ea2b3045b99efc9402e78cbb89bd4fcaaaaef76ec016"
},
{
"name": "usr/share/zoneinfo/right/Antarctica/Mawson",
"type": "reg",
"size": 751,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23235993,
"NumLink": 0,
"digest": "sha256:6d71da7e0dc64cde67ba892f72d2635aecfc6f8b5ba32d8e4bce77b6d43b7b1c"
},
{
"name": "usr/share/zoneinfo/right/Antarctica/McMurdo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../NZ",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Antarctica/Palmer",
"type": "reg",
"size": 1958,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23236524,
"NumLink": 0,
"digest": "sha256:890b910a9d56462b6b02cb97f94e4c6b1bc21d28e22c91856ffdb4491e5c9af1"
},
{
"name": "usr/share/zoneinfo/right/Antarctica/Rothera",
"type": "reg",
"size": 712,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23237665,
"NumLink": 0,
"digest": "sha256:2248bdca29d8909f713111c13c4562206c315e778dae04191e2c71be7e41d66e"
},
{
"name": "usr/share/zoneinfo/right/Antarctica/South_Pole",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../NZ",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Antarctica/Syowa",
"type": "reg",
"size": 713,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23238183,
"NumLink": 0,
"digest": "sha256:2ed146fb4b60eec7f3fd2a45ec5a47efe97628bc6f6d718bbadc4e7d0efd2699"
},
{
"name": "usr/share/zoneinfo/right/Antarctica/Troll",
"type": "reg",
"size": 1702,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23238657,
"NumLink": 0,
"digest": "sha256:0015c4226733a9e187e53587097ff75b4cb2aedf4157811e50d2aa88c658cb08"
},
{
"name": "usr/share/zoneinfo/right/Antarctica/Vostok",
"type": "reg",
"size": 713,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23239650,
"NumLink": 0,
"digest": "sha256:ac1aa91f07d0fe56ae6f0df158a36e86063712784e6692f95b1804815cbf1ee9"
},
{
"name": "usr/share/zoneinfo/right/Arctic/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Arctic/Longyearbyen",
"type": "reg",
"size": 2791,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23240160,
"NumLink": 0,
"digest": "sha256:b9f4a8a248c4b945a12640b4be549baaa438a9c3472822028e9f4988cd4e8b63"
},
{
"name": "usr/share/zoneinfo/right/Asia/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Aden",
"type": "reg",
"size": 713,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23241753,
"NumLink": 0,
"digest": "sha256:cc7153eb830b43242dfe8683480c44497fc209145045c93d2e75774495d2cfc8"
},
{
"name": "usr/share/zoneinfo/right/Asia/Almaty",
"type": "reg",
"size": 1557,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23242229,
"NumLink": 0,
"digest": "sha256:cd94dd030db0f5d766b246af2dc09f394a1e1a6abca0b84385a6b460c0a95001"
},
{
"name": "usr/share/zoneinfo/right/Asia/Amman",
"type": "reg",
"size": 2417,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23243139,
"NumLink": 0,
"digest": "sha256:0dfa946fc43a7d4ad00e75aba44720018d82f36411351b2e5a103dbfadaf0d8c"
},
{
"name": "usr/share/zoneinfo/right/Asia/Anadyr",
"type": "reg",
"size": 1748,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23244511,
"NumLink": 0,
"digest": "sha256:658b7df8bb11f46e1c785e1ecb46e54a7e4e4435da78edf0ed9f3c8920ee7e9e"
},
{
"name": "usr/share/zoneinfo/right/Asia/Aqtau",
"type": "reg",
"size": 1543,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23245534,
"NumLink": 0,
"digest": "sha256:e900765cb6d9b6c7d3320185701d7f70c8bfe1e462abf1ec687c2494afbe027a"
},
{
"name": "usr/share/zoneinfo/right/Asia/Aqtobe",
"type": "reg",
"size": 1573,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23246447,
"NumLink": 0,
"digest": "sha256:fe5ae88a2c0c1af7dab63e796db665ef7037a045ee93bb480f742b24143dc13e"
},
{
"name": "usr/share/zoneinfo/right/Asia/Ashgabat",
"type": "reg",
"size": 1177,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23247367,
"NumLink": 0,
"digest": "sha256:ce671b85f724114ded28698c8a0f90df2451b52b1ab02bc266067c6236b45792"
},
{
"name": "usr/share/zoneinfo/right/Asia/Ashkhabad",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Ashgabat",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Atyrau",
"type": "reg",
"size": 1551,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23248131,
"NumLink": 0,
"digest": "sha256:3ba6963e4536ac91a31a8c53de06b962be6f2265828c1fb7d8471ad2513a6c72"
},
{
"name": "usr/share/zoneinfo/right/Asia/Baghdad",
"type": "reg",
"size": 1530,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23249050,
"NumLink": 0,
"digest": "sha256:d50a00eca951cc4945a502bfc4993096fdf661213ec534bb8a7dc4774af88000"
},
{
"name": "usr/share/zoneinfo/right/Asia/Bahrain",
"type": "reg",
"size": 751,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23249965,
"NumLink": 0,
"digest": "sha256:c39550b6dca350b213a7bd957339b96089c9d4ce4d5dd975444af4d848afd06c"
},
{
"name": "usr/share/zoneinfo/right/Asia/Baku",
"type": "reg",
"size": 1795,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23250454,
"NumLink": 0,
"digest": "sha256:15d6954f582895136fe83e61ef7defe2abe84ae2670be41a46adfdf5021d152a"
},
{
"name": "usr/share/zoneinfo/right/Asia/Bangkok",
"type": "reg",
"size": 746,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23251494,
"NumLink": 0,
"digest": "sha256:27712ede5e3faa189b802aff8b25bb37058710ca9d62439643304bc522ba03d8"
},
{
"name": "usr/share/zoneinfo/right/Asia/Barnaul",
"type": "reg",
"size": 1781,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23251991,
"NumLink": 0,
"digest": "sha256:f422f78d9ea036ae9979c3ea4ec864d06930257d671613e49457160d602c2019"
},
{
"name": "usr/share/zoneinfo/right/Asia/Beirut",
"type": "reg",
"size": 2715,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23253036,
"NumLink": 0,
"digest": "sha256:3e24a502c1fb5fb0dd7a8c738f28074b8e785311ba73a33fb597c2172ca288a0"
},
{
"name": "usr/share/zoneinfo/right/Asia/Bishkek",
"type": "reg",
"size": 1571,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23254578,
"NumLink": 0,
"digest": "sha256:ea503d6f5d42be0d3b9cc97dcc71a07b570be925d94104180652bd4ad8667031"
},
{
"name": "usr/share/zoneinfo/right/Asia/Brunei",
"type": "reg",
"size": 755,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23255497,
"NumLink": 0,
"digest": "sha256:38e8f436a5483d23eb24c1422a098014239e51cf74ae41120f3d98662a9513d2"
},
{
"name": "usr/share/zoneinfo/right/Asia/Calcutta",
"type": "reg",
"size": 852,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23255993,
"NumLink": 0,
"digest": "sha256:637147fe7b40e10955c08b61ecc5639148589ce9bd6939cf7b98bc02cccd5036"
},
{
"name": "usr/share/zoneinfo/right/Asia/Chita",
"type": "reg",
"size": 1783,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23256570,
"NumLink": 0,
"digest": "sha256:0b4b8ac6a5125aae18dad505db9a49a269f4c61054dc0adf0bc3e19436fc809a"
},
{
"name": "usr/share/zoneinfo/right/Asia/Choibalsan",
"type": "reg",
"size": 1517,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23257605,
"NumLink": 0,
"digest": "sha256:edc88f73de96408868609ce2dfa5c125aededfd799ff7b3b2c2e5c216898cd31"
},
{
"name": "usr/share/zoneinfo/right/Asia/Chongqing",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../PRC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Chungking",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../PRC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Colombo",
"type": "reg",
"size": 939,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23258557,
"NumLink": 0,
"digest": "sha256:514a6511932107df30932d496a3e469a0415837679103d50ac262694b0699b61"
},
{
"name": "usr/share/zoneinfo/right/Asia/Dacca",
"type": "reg",
"size": 896,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23259131,
"NumLink": 0,
"digest": "sha256:bc6a481333c3867538193302c3390acc62a591d8556f378fc0fb90f30c75c66e"
},
{
"name": "usr/share/zoneinfo/right/Asia/Damascus",
"type": "reg",
"size": 2860,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23259697,
"NumLink": 0,
"digest": "sha256:e23e8576a6f8b5b65a356e535ba0ffc916d211a155207e486dd40b1757f3b831"
},
{
"name": "usr/share/zoneinfo/right/Asia/Dhaka",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Dacca",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Dili",
"type": "reg",
"size": 779,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23261338,
"NumLink": 0,
"digest": "sha256:29412d668b9a191c7ca3132f85e479f4fbba5daa8b7ddc6ea599639e96fe9299"
},
{
"name": "usr/share/zoneinfo/right/Asia/Dubai",
"type": "reg",
"size": 713,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23261846,
"NumLink": 0,
"digest": "sha256:8137fb36c9c9f6f273c75abeb39d415d411777289d0dc361c28d23e741933d20"
},
{
"name": "usr/share/zoneinfo/right/Asia/Dushanbe",
"type": "reg",
"size": 1147,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23262323,
"NumLink": 0,
"digest": "sha256:18ba97fd4f84ae2c99f6a60b651ec043bf5d464600aa68b7fdd331a15455c5e2"
},
{
"name": "usr/share/zoneinfo/right/Asia/Famagusta",
"type": "reg",
"size": 2582,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23263022,
"NumLink": 0,
"digest": "sha256:40438f760718c79a99e171a199690123f0cc531fef996423c2ca840d20b6bce9"
},
{
"name": "usr/share/zoneinfo/right/Asia/Gaza",
"type": "reg",
"size": 2835,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23264463,
"NumLink": 0,
"digest": "sha256:6ed90d85deebc9fb9292b65cbb103fea833bd5c9c3890598c587a5ff66fb01b1"
},
{
"name": "usr/share/zoneinfo/right/Asia/Harbin",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../PRC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Hebron",
"type": "reg",
"size": 2863,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23266107,
"NumLink": 0,
"digest": "sha256:fa78ccb4bf6e2c7c212cb2ffd3d0e892ab59db2f96cba57d67e1bfc84c40096f"
},
{
"name": "usr/share/zoneinfo/right/Asia/Ho_Chi_Minh",
"type": "reg",
"size": 915,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23267728,
"NumLink": 0,
"digest": "sha256:18f4bc73f6e1b70aa7af8cb7f03b6191b91c4fe160cdabe7365a8c15c98fd209"
},
{
"name": "usr/share/zoneinfo/right/Asia/Hong_Kong",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Hongkong",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Hovd",
"type": "reg",
"size": 1447,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23268339,
"NumLink": 0,
"digest": "sha256:68a7869d2b4493ef412edda98d0c69171d9012de857f78f93bc1aa511d357753"
},
{
"name": "usr/share/zoneinfo/right/Asia/Irkutsk",
"type": "reg",
"size": 1802,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23269212,
"NumLink": 0,
"digest": "sha256:758dfc5e377e82cab46bde761d3678cb8136a7e3f6b7bc4f18a14ed8e2e45ade"
},
{
"name": "usr/share/zoneinfo/right/Asia/Istanbul",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Turkey",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Jakarta",
"type": "reg",
"size": 932,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23270295,
"NumLink": 0,
"digest": "sha256:1e1d6c8a2a336137b702b0e68da98b2b2c3135a1844f9bc17d05d0eff1ef18bd"
},
{
"name": "usr/share/zoneinfo/right/Asia/Jayapura",
"type": "reg",
"size": 791,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23270873,
"NumLink": 0,
"digest": "sha256:ccfa8a6a43b97d5dd2e2c33f84951460b2bffcc1202919ef38c3b097a83167db"
},
{
"name": "usr/share/zoneinfo/right/Asia/Jerusalem",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Israel",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Kabul",
"type": "reg",
"size": 755,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23271421,
"NumLink": 0,
"digest": "sha256:05079923f0dcdb139abc702b063aec10c46f283019af08087b4d126f3317a5f9"
},
{
"name": "usr/share/zoneinfo/right/Asia/Kamchatka",
"type": "reg",
"size": 1724,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23271922,
"NumLink": 0,
"digest": "sha256:91922e506d3db985233b289c6c5be03d1c9a87cf18253b47ab3a77217e52a8c3"
},
{
"name": "usr/share/zoneinfo/right/Asia/Karachi",
"type": "reg",
"size": 957,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23272934,
"NumLink": 0,
"digest": "sha256:08c8c89e2687b19464bc067b6b368bfe3106c980538c0b3314e7301e192a295a"
},
{
"name": "usr/share/zoneinfo/right/Asia/Kashgar",
"type": "reg",
"size": 713,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23273531,
"NumLink": 0,
"digest": "sha256:7e9286573873c9113636fbac7e5559a35faeca0db3fb73da0a3bd4fc5f65a456"
},
{
"name": "usr/share/zoneinfo/right/Asia/Kathmandu",
"type": "reg",
"size": 764,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23274009,
"NumLink": 0,
"digest": "sha256:8bd14af9de4726482304666173d593a0847f2b429cada1a7fea3fa70a10696e4"
},
{
"name": "usr/share/zoneinfo/right/Asia/Katmandu",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Kathmandu",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Khandyga",
"type": "reg",
"size": 1837,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23274544,
"NumLink": 0,
"digest": "sha256:2b08289062a1307816fb246bf351b9264af434aa6a6a65551b8e6cfaa208e5f8"
},
{
"name": "usr/share/zoneinfo/right/Asia/Kolkata",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Calcutta",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Krasnoyarsk",
"type": "reg",
"size": 1769,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23275644,
"NumLink": 0,
"digest": "sha256:0e93c034f7647538f5a51adc8195d9cace109a45b5c5e3e6481fd0247c0ad11f"
},
{
"name": "usr/share/zoneinfo/right/Asia/Kuala_Lumpur",
"type": "reg",
"size": 950,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23276677,
"NumLink": 0,
"digest": "sha256:c8b289f4113407322af39192ffae6d4bd70fc07dece86db7afbf51bf51ab36ab"
},
{
"name": "usr/share/zoneinfo/right/Asia/Kuching",
"type": "reg",
"size": 1047,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23277254,
"NumLink": 0,
"digest": "sha256:1d16f6894cb9b51fa6426bf60bf6e28ca8953776a5d007d6f95a9a160658be40"
},
{
"name": "usr/share/zoneinfo/right/Asia/Kuwait",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Aden",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Macao",
"type": "reg",
"size": 1311,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23277938,
"NumLink": 0,
"digest": "sha256:1d776cf7633495715a0794e17c4cad9338ab05fb74fce498890acd45b6922082"
},
{
"name": "usr/share/zoneinfo/right/Asia/Macau",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Macao",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Magadan",
"type": "reg",
"size": 1784,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23278774,
"NumLink": 0,
"digest": "sha256:1e59de996c881c4e1440a0840b80ae9accef36d547f947e7d139629675a95ba5"
},
{
"name": "usr/share/zoneinfo/right/Asia/Makassar",
"type": "reg",
"size": 828,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23279806,
"NumLink": 0,
"digest": "sha256:6bb9b9d31d2f3c57b17a9ec143294ffaa86669e793adf98c5acd1a980d6d4125"
},
{
"name": "usr/share/zoneinfo/right/Asia/Manila",
"type": "reg",
"size": 893,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23280325,
"NumLink": 0,
"digest": "sha256:eef003e76600751c7e9e742f9aadee8e9a54fea94aba62815ec755d633772d32"
},
{
"name": "usr/share/zoneinfo/right/Asia/Muscat",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Dubai",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Nicosia",
"type": "reg",
"size": 2556,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23280953,
"NumLink": 0,
"digest": "sha256:b801d86c6b957dd1affe34a2d19a0c580be861d8ccd283d9f48e2dc991fe3696"
},
{
"name": "usr/share/zoneinfo/right/Asia/Novokuznetsk",
"type": "reg",
"size": 1723,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23282384,
"NumLink": 0,
"digest": "sha256:25ec788512729a0767bf726100faadc6e7d9d8c3d2f93f05a537480a1677c594"
},
{
"name": "usr/share/zoneinfo/right/Asia/Novosibirsk",
"type": "reg",
"size": 1781,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23283404,
"NumLink": 0,
"digest": "sha256:133caf0866411002a774c95699d4c3d655fc32451cd6622142cbfe322f2f9b0c"
},
{
"name": "usr/share/zoneinfo/right/Asia/Omsk",
"type": "reg",
"size": 1769,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23284446,
"NumLink": 0,
"digest": "sha256:e4e00ab7d4297379b94c840a93dec5aa02ee4675c0b0a063c6bb28ffb8a8ff96"
},
{
"name": "usr/share/zoneinfo/right/Asia/Oral",
"type": "reg",
"size": 1565,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23285470,
"NumLink": 0,
"digest": "sha256:8538cffe3000c7c62519075440e7efcf120610601bc38d4f74cf8c0c20fe8326"
},
{
"name": "usr/share/zoneinfo/right/Asia/Phnom_Penh",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bangkok",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Pontianak",
"type": "reg",
"size": 935,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23286440,
"NumLink": 0,
"digest": "sha256:5387a863122580120cb3ee351bcada3b3dfbf163424291b97bac4e2cabd9845f"
},
{
"name": "usr/share/zoneinfo/right/Asia/Pyongyang",
"type": "reg",
"size": 807,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23287017,
"NumLink": 0,
"digest": "sha256:1ee9bd69289e2d175fc25a780f11c1c4b71ff6dd172427015e47e8b9afc7d9c0"
},
{
"name": "usr/share/zoneinfo/right/Asia/Qatar",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bahrain",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Qyzylorda",
"type": "reg",
"size": 1573,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23287578,
"NumLink": 0,
"digest": "sha256:d58e0f06c11ed11cbc3543944c48df4e7850c15f375b2c0784bc40181f5cbcaa"
},
{
"name": "usr/share/zoneinfo/right/Asia/Rangoon",
"type": "reg",
"size": 823,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23288499,
"NumLink": 0,
"digest": "sha256:e82e81e81d7becfd4796a9620afc7950e9dc8ea1cf28ce85b91f11f8e94ff2b3"
},
{
"name": "usr/share/zoneinfo/right/Asia/Riyadh",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Aden",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Saigon",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Ho_Chi_Minh",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Sakhalin",
"type": "reg",
"size": 1760,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23289091,
"NumLink": 0,
"digest": "sha256:20e5edafd438adf3d299e2d1020f3034b18cc943e0b8e6183a4783fd68c91660"
},
{
"name": "usr/share/zoneinfo/right/Asia/Samarkand",
"type": "reg",
"size": 1145,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23290126,
"NumLink": 0,
"digest": "sha256:610fa9ffafe3bf07021986093d23c13be582da74f5240418538dce729e52cac7"
},
{
"name": "usr/share/zoneinfo/right/Asia/Seoul",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../ROK",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Shanghai",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../PRC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Singapore",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Singapore",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Srednekolymsk",
"type": "reg",
"size": 1770,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23290931,
"NumLink": 0,
"digest": "sha256:a86ea1167361757c9c361e526dc5d6dc955e54127bbe26bb329c10cdaf3b910c"
},
{
"name": "usr/share/zoneinfo/right/Asia/Taipei",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../ROC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Tashkent",
"type": "reg",
"size": 1161,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23291991,
"NumLink": 0,
"digest": "sha256:2c547ea5d0b629d373549ae10d689305371b1b2ad50f50a9674d9dddb9d6c292"
},
{
"name": "usr/share/zoneinfo/right/Asia/Tbilisi",
"type": "reg",
"size": 1606,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23292699,
"NumLink": 0,
"digest": "sha256:fc510dc1e0fb0046ab25efb7e690494b8de952653c47e36e120126ae8f90090e"
},
{
"name": "usr/share/zoneinfo/right/Asia/Tehran",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Iran",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Tel_Aviv",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Israel",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Thimbu",
"type": "reg",
"size": 755,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23293713,
"NumLink": 0,
"digest": "sha256:3ff501701002f41f0a8104e3f6bf7e510ed880c196cf00fd5df68c05af58368c"
},
{
"name": "usr/share/zoneinfo/right/Asia/Thimphu",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Thimbu",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Tokyo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Japan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Tomsk",
"type": "reg",
"size": 1781,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23294266,
"NumLink": 0,
"digest": "sha256:f2607287c46263b4309de532febe5bef76f9608d9ff63086073d70106f1244fe"
},
{
"name": "usr/share/zoneinfo/right/Asia/Ujung_Pandang",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Makassar",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Ulaanbaatar",
"type": "reg",
"size": 1447,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23295371,
"NumLink": 0,
"digest": "sha256:fb2a3f180a8e9876464b94f82d304af657abe650242cca5e1f213c79ad23d1fa"
},
{
"name": "usr/share/zoneinfo/right/Asia/Ulan_Bator",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Ulaanbaatar",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Urumqi",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Kashgar",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Ust-Nera",
"type": "reg",
"size": 1816,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23296320,
"NumLink": 0,
"digest": "sha256:8d96e0257286d1a9a587ab8282502edfd46ff11f689c1781024ef1983698b483"
},
{
"name": "usr/share/zoneinfo/right/Asia/Vientiane",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bangkok",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Vladivostok",
"type": "reg",
"size": 1770,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23297410,
"NumLink": 0,
"digest": "sha256:baa65ed34a7bd4fb6cb2d3049bd300e8d1a4c94da531440f228eabf118cbdf1d"
},
{
"name": "usr/share/zoneinfo/right/Asia/Yakutsk",
"type": "reg",
"size": 1769,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23298432,
"NumLink": 0,
"digest": "sha256:198fe2c858c12f6ea9694e14082c83d7cd2b0cfad08d777e24649a32f4d4cd77"
},
{
"name": "usr/share/zoneinfo/right/Asia/Yangon",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Rangoon",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Asia/Yekaterinburg",
"type": "reg",
"size": 1807,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23299500,
"NumLink": 0,
"digest": "sha256:b628820d8324f3f6f5158b205f2d2ad039c39f902c224069a0b1e4fe5d34d2fc"
},
{
"name": "usr/share/zoneinfo/right/Asia/Yerevan",
"type": "reg",
"size": 1739,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23300540,
"NumLink": 0,
"digest": "sha256:ef2995b9457cf5a134415bd2edc791e517cf105b82c996d562ff7924e96ef17a"
},
{
"name": "usr/share/zoneinfo/right/Atlantic/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Atlantic/Azores",
"type": "reg",
"size": 4019,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23301590,
"NumLink": 0,
"digest": "sha256:94aa009afe8600f7e4e9d2e08010d9e8e298c7db4b5d037083aae2623dd39c7e"
},
{
"name": "usr/share/zoneinfo/right/Atlantic/Bermuda",
"type": "reg",
"size": 2544,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23303763,
"NumLink": 0,
"digest": "sha256:d6de741b6c7895bfd687374854ea64fb2998d65436d657d27bd4c7c59b351918"
},
{
"name": "usr/share/zoneinfo/right/Atlantic/Canary",
"type": "reg",
"size": 2451,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23305215,
"NumLink": 0,
"digest": "sha256:f0bf9911e3c52ec1a10e1b9c0cb94d323c7425614948efc559ffe15c67fb48cd"
},
{
"name": "usr/share/zoneinfo/right/Atlantic/Cape_Verde",
"type": "reg",
"size": 810,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23306595,
"NumLink": 0,
"digest": "sha256:26e5af107ba390b89da815bf73393eda913c93bf59da49028d07c2de8c47a93f"
},
{
"name": "usr/share/zoneinfo/right/Atlantic/Faeroe",
"type": "reg",
"size": 2369,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23307116,
"NumLink": 0,
"digest": "sha256:4f5a41b4ae349e1c6466cba14b5afaf7d7b4a9623fc8395173e4d3a2b4a5f90d"
},
{
"name": "usr/share/zoneinfo/right/Atlantic/Faroe",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Faeroe",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Atlantic/Jan_Mayen",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Arctic/Longyearbyen",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Atlantic/Madeira",
"type": "reg",
"size": 4024,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23308535,
"NumLink": 0,
"digest": "sha256:35dedfac50f7e188bdf50bf3e03fd597e822d77b353461bbb60e3ffc3be1b71f"
},
{
"name": "usr/share/zoneinfo/right/Atlantic/Reykjavik",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Iceland",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Atlantic/South_Georgia",
"type": "reg",
"size": 690,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23310760,
"NumLink": 0,
"digest": "sha256:443c6fad70d4b18113f7c9377ee84d4449aa7fca3b0b7a215fe7ef37405210a5"
},
{
"name": "usr/share/zoneinfo/right/Atlantic/St_Helena",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Africa/Abidjan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Atlantic/Stanley",
"type": "reg",
"size": 1777,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23311300,
"NumLink": 0,
"digest": "sha256:fb5739968a69015fe6f3848e3139045a95883307b05b8f7d21ffb48182bf4567"
},
{
"name": "usr/share/zoneinfo/right/Australia/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Australia/ACT",
"type": "reg",
"size": 2763,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23312393,
"NumLink": 0,
"digest": "sha256:3cb5754502c335e80d639b47a7f36ddda899d8456ebfe72308882ee5ef92a698"
},
{
"name": "usr/share/zoneinfo/right/Australia/Adelaide",
"type": "reg",
"size": 2778,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23313946,
"NumLink": 0,
"digest": "sha256:02aaaee74a3e5b3d1629ae41daa5b46da01708d3bb87b3c7100c1e7b8202ab81"
},
{
"name": "usr/share/zoneinfo/right/Australia/Brisbane",
"type": "reg",
"size": 992,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23315538,
"NumLink": 0,
"digest": "sha256:ddda440f5a5595a004677523165714a25709a156027554a06dd427d5308d9b18"
},
{
"name": "usr/share/zoneinfo/right/Australia/Broken_Hill",
"type": "reg",
"size": 2814,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23316176,
"NumLink": 0,
"digest": "sha256:fd0d2135fb2fa2835646f9e83c23e040bbd8e45b949f3b181925049c7cf419d9"
},
{
"name": "usr/share/zoneinfo/right/Australia/Canberra",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "ACT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Australia/Currie",
"type": "reg",
"size": 2763,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23317839,
"NumLink": 0,
"digest": "sha256:f37c37574a6bbbebaf7d25eae300f4499741c50b8967335185ba7ee122c56869"
},
{
"name": "usr/share/zoneinfo/right/Australia/Darwin",
"type": "reg",
"size": 863,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23319388,
"NumLink": 0,
"digest": "sha256:a7223b77c9ca3ab6de014f4f9b566c79dd3f4161c5ea4223b6ff8608285535d9"
},
{
"name": "usr/share/zoneinfo/right/Australia/Eucla",
"type": "reg",
"size": 1029,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23319975,
"NumLink": 0,
"digest": "sha256:2b7483a966785d32052458bb16347dc254f8b94bcfab5eb914f4fb68b6b2a044"
},
{
"name": "usr/share/zoneinfo/right/Australia/Hobart",
"type": "reg",
"size": 2875,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23320635,
"NumLink": 0,
"digest": "sha256:b833dd205698e9e4ec4f134c910a46c2c203379594b70fb3dc15dac7d8d15a42"
},
{
"name": "usr/share/zoneinfo/right/Australia/LHI",
"type": "reg",
"size": 2415,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23322236,
"NumLink": 0,
"digest": "sha256:63e2d8d01b17c6ca1a6e7c7eedeb65f1fd8c2611c7354458bd44dac83e8f4c84"
},
{
"name": "usr/share/zoneinfo/right/Australia/Lindeman",
"type": "reg",
"size": 1062,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23323610,
"NumLink": 0,
"digest": "sha256:9134536b2b9078c1807e7529de64da0bc34f32f97aea996b769ccaa474c3aa12"
},
{
"name": "usr/share/zoneinfo/right/Australia/Lord_Howe",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "LHI",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Australia/Melbourne",
"type": "reg",
"size": 2763,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23324330,
"NumLink": 0,
"digest": "sha256:f11ce1fe7a552704c280c6e642ab51a416aff8ad6b9d9c987e90280ce0c6fc3f"
},
{
"name": "usr/share/zoneinfo/right/Australia/NSW",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "ACT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Australia/North",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Darwin",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Australia/Perth",
"type": "reg",
"size": 1019,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23325936,
"NumLink": 0,
"digest": "sha256:dd69002d8273324fcb2dff6a52a11667b34b9cdbd36858d5ccda4f2785b7ef7a"
},
{
"name": "usr/share/zoneinfo/right/Australia/Queensland",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Brisbane",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Australia/South",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Adelaide",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Australia/Sydney",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "ACT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Australia/Tasmania",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Hobart",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Australia/Victoria",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Melbourne",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Australia/West",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Perth",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Australia/Yancowinna",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Broken_Hill",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Brazil/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Brazil/Acre",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Porto_Acre",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Brazil/DeNoronha",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Noronha",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Brazil/East",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Sao_Paulo",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Brazil/West",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Manaus",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/CET",
"type": "reg",
"size": 2642,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23326941,
"NumLink": 0,
"digest": "sha256:a9ab0888ef44577631cf924205405001a436f7d97899c8989907aba1b77a61be"
},
{
"name": "usr/share/zoneinfo/right/CST6CDT",
"type": "reg",
"size": 2834,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23328412,
"NumLink": 0,
"digest": "sha256:fc4181f42429479b45e3b5d1e9d8775017957bca5c82fc9530769fcb81b2fe8e"
},
{
"name": "usr/share/zoneinfo/right/Canada/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Canada/Atlantic",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Halifax",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Canada/Central",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Winnipeg",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Canada/Eastern",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Montreal",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Canada/Mountain",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Edmonton",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Canada/Newfoundland",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/St_Johns",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Canada/Pacific",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Vancouver",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Canada/Saskatchewan",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Regina",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Canada/Yukon",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Whitehorse",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Chile/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Chile/Continental",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Santiago",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Chile/EasterIsland",
"type": "reg",
"size": 2768,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23330371,
"NumLink": 0,
"digest": "sha256:b8c22dce2336c2177f7f27feecaf145c40f13e16b5038abe13ce8dd764ec6673"
},
{
"name": "usr/share/zoneinfo/right/Cuba",
"type": "reg",
"size": 2977,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23331930,
"NumLink": 0,
"digest": "sha256:0d4bfd6b414442661f79556ac625c9f30e051af6694be67e3ad312e9e9589c50"
},
{
"name": "usr/share/zoneinfo/right/EET",
"type": "reg",
"size": 2416,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23333598,
"NumLink": 0,
"digest": "sha256:7a4178745768032216702f31fa03f676677d5951079d7e17856ab4be0ddc4061"
},
{
"name": "usr/share/zoneinfo/right/EST",
"type": "reg",
"size": 667,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23334948,
"NumLink": 0,
"digest": "sha256:e7c90a5d782d8843b78996aaf9cc7d332f29d923e8b41739fa0d523b6675a816"
},
{
"name": "usr/share/zoneinfo/right/EST5EDT",
"type": "reg",
"size": 2834,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23335393,
"NumLink": 0,
"digest": "sha256:0f7e22d9f44ba8c1c49034d187a3910eabea89ea5702363f55eadfcd12e01daa"
},
{
"name": "usr/share/zoneinfo/right/Egypt",
"type": "reg",
"size": 2512,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23336984,
"NumLink": 0,
"digest": "sha256:a3c84ceb744bbb495b49ef286308143f267da03f75931b82fdcb5c5a3aebcdd8"
},
{
"name": "usr/share/zoneinfo/right/Eire",
"type": "reg",
"size": 4071,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23338418,
"NumLink": 0,
"digest": "sha256:2004b00c415e097897394d0fcbe0e1b28f6db7b708f7777d51344a73de890132"
},
{
"name": "usr/share/zoneinfo/right/Etc/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT+0",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT+1",
"type": "reg",
"size": 669,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23340689,
"NumLink": 0,
"digest": "sha256:9012b089ee5a16e2015052ea85bac9a4b506801bb4acbdc9e03b4d712ef198dd"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT+10",
"type": "reg",
"size": 670,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23341141,
"NumLink": 0,
"digest": "sha256:76db27877af699cfe9adbc2bff16b7cef3fe3b9f74f09cf277b129e32260f736"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT+11",
"type": "reg",
"size": 670,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23341593,
"NumLink": 0,
"digest": "sha256:704c6797421f83f7b8a2e0d2fb8dc81bdcb0d35557fac776eb3f015f86405ed6"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT+12",
"type": "reg",
"size": 670,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23342046,
"NumLink": 0,
"digest": "sha256:cf46ced23a838bea5786cb93a1d8a6fecc8767bdf8e038d288f29716ee516393"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT+2",
"type": "reg",
"size": 669,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23342499,
"NumLink": 0,
"digest": "sha256:d79c8c528f6339f8db638202241705f906bfc75898e1898c163fa660e3139e69"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT+3",
"type": "reg",
"size": 669,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23342952,
"NumLink": 0,
"digest": "sha256:c779ce86e70bb6f078ae7eaea3eec065cf34d174193efe2c0d8269ab56a315a5"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT+4",
"type": "reg",
"size": 669,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23343405,
"NumLink": 0,
"digest": "sha256:c0127004db0b9e3f959a7d5a2ce85e945d9f6cbab766b84ea7ad290c205a36fa"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT+5",
"type": "reg",
"size": 669,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23343856,
"NumLink": 0,
"digest": "sha256:b4c34c2105cee69da93a16895a70309d6f66cbc3963e50fbc34a3323c1fb702d"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT+6",
"type": "reg",
"size": 669,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23344307,
"NumLink": 0,
"digest": "sha256:ee118477c847ebfb76305fb363fb15e85eb96321c4ddbf7342c66e2e96ce3dbd"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT+7",
"type": "reg",
"size": 669,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23344760,
"NumLink": 0,
"digest": "sha256:b27b23ce790d6fd0c471e0e3b982d8c17bf3c4eb80b5e96c4746d188d9e0befd"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT+8",
"type": "reg",
"size": 669,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23345214,
"NumLink": 0,
"digest": "sha256:86c26e9def2a5dcee6c2df681fab85b0bc007d9598ec04b9322f43238cb29d15"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT+9",
"type": "reg",
"size": 669,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23345668,
"NumLink": 0,
"digest": "sha256:a90f65d22e4d8e80308d4dc9bef7b81d8cdb0a475725df4fe8b4629afa28b9d0"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT-0",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT-1",
"type": "reg",
"size": 670,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23346152,
"NumLink": 0,
"digest": "sha256:a41423d3823b3f569397236083416aad827ce76ba59b60c3e90b7d770399c51a"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT-10",
"type": "reg",
"size": 671,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23346603,
"NumLink": 0,
"digest": "sha256:4d69d3e560e906a2577bcd83ad7311ec9dbdd30eea0f7d8528a2b6e16c7a0232"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT-11",
"type": "reg",
"size": 671,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23347056,
"NumLink": 0,
"digest": "sha256:1f5428fafb9651fd439f216d062ee891777ab36678e3691b0aefe33c124b59e1"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT-12",
"type": "reg",
"size": 671,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23347507,
"NumLink": 0,
"digest": "sha256:50a8dc6b9660c4aca6babc6af0470463410d0196745f49824f38a03e0f6c1410"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT-13",
"type": "reg",
"size": 671,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23347959,
"NumLink": 0,
"digest": "sha256:c11600b000c15510f07608a1e114b211b9cfd1503d3b2218fc6714bff20d66ba"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT-14",
"type": "reg",
"size": 671,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23348412,
"NumLink": 0,
"digest": "sha256:41247eb4e0246a199f0efcec850f45bb8af4eb81a9a3370cc826b782ea741df6"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT-2",
"type": "reg",
"size": 670,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23348865,
"NumLink": 0,
"digest": "sha256:d12a5d3f5ce6aaa1cf0c06dbb47f0453cc50df54294775dfff2572ace625985d"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT-3",
"type": "reg",
"size": 670,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23349316,
"NumLink": 0,
"digest": "sha256:ecc1a893c3f29152db79772575ffabd6560594896bad41a22a891b22063b24fd"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT-4",
"type": "reg",
"size": 670,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23349768,
"NumLink": 0,
"digest": "sha256:8cc82f5c8d7c9c91f94e85b79bfff7f6cd20c6caf8b7e584a801453f7665d109"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT-5",
"type": "reg",
"size": 670,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23350218,
"NumLink": 0,
"digest": "sha256:ab8fd10e20bcc651da9a0576a54ba9a078a5c5cb5d0b3360b62cf2f89c107448"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT-6",
"type": "reg",
"size": 670,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23350669,
"NumLink": 0,
"digest": "sha256:c42ac9c37dd6f9693b5688d7dfd969c78dd830dd9c937c1fc1866009f4fa8c57"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT-7",
"type": "reg",
"size": 670,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23351119,
"NumLink": 0,
"digest": "sha256:4a5dbdf8cfcc24be96f0e4050f77cf2955d7e1c6b0b22f80acf3257b33677878"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT-8",
"type": "reg",
"size": 670,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23351571,
"NumLink": 0,
"digest": "sha256:5b800d1e9507634e1e900f3a9ef7389852954cec6bdac489beac822a5dfdbc1b"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT-9",
"type": "reg",
"size": 670,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23352023,
"NumLink": 0,
"digest": "sha256:4e3d7d971253c770534e2344257199faa641ee50f914237c23a81af9b0ebcef8"
},
{
"name": "usr/share/zoneinfo/right/Etc/GMT0",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Etc/Greenwich",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Etc/UCT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../UCT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Etc/UTC",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../UTC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Etc/Universal",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../UTC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Etc/Zulu",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../UTC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Europe/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Europe/Amsterdam",
"type": "reg",
"size": 3489,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23352633,
"NumLink": 0,
"digest": "sha256:ae32e0cb5a9ea89b38213163daf4feb4840a6b72e82d5464c647d0a618531697"
},
{
"name": "usr/share/zoneinfo/right/Europe/Andorra",
"type": "reg",
"size": 2291,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23354524,
"NumLink": 0,
"digest": "sha256:1330f8009adea3c6e5134cdeadaedaa6fbbcd72b2753207ccbb953ccf5001d18"
},
{
"name": "usr/share/zoneinfo/right/Europe/Astrakhan",
"type": "reg",
"size": 1723,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23355815,
"NumLink": 0,
"digest": "sha256:d5a385bc8f28267d22d117464f6cb9a9cb179dfd192a4f04a5cc49044b6cf3bf"
},
{
"name": "usr/share/zoneinfo/right/Europe/Athens",
"type": "reg",
"size": 2811,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23356823,
"NumLink": 0,
"digest": "sha256:0c23a856f0e9c47bba55cb81c426eca0d190aea6043b018e2affa55f21b43664"
},
{
"name": "usr/share/zoneinfo/right/Europe/Belfast",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GB",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Europe/Belgrade",
"type": "reg",
"size": 2497,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23358438,
"NumLink": 0,
"digest": "sha256:3debded934cdf0c472df38375bd1a69c9135fb21890969bd68a50956e7181e7c"
},
{
"name": "usr/share/zoneinfo/right/Europe/Berlin",
"type": "reg",
"size": 2875,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23359833,
"NumLink": 0,
"digest": "sha256:0b4604767edbae4fc43cd24bd96ec235232471fddff9dc15328c0b74d4c3dc90"
},
{
"name": "usr/share/zoneinfo/right/Europe/Bratislava",
"type": "reg",
"size": 2878,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23361442,
"NumLink": 0,
"digest": "sha256:faec42cc5833011f1a0f07219bbdea30138befb2c85258c5e5a0d37961a0bae7"
},
{
"name": "usr/share/zoneinfo/right/Europe/Brussels",
"type": "reg",
"size": 3510,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23363099,
"NumLink": 0,
"digest": "sha256:b7389bbaad40e29c14b6286ef458854c08a820a22dabc9a9df0a9a371589da0a"
},
{
"name": "usr/share/zoneinfo/right/Europe/Bucharest",
"type": "reg",
"size": 2761,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23365064,
"NumLink": 0,
"digest": "sha256:4052f7958d6994e77da457cc9255749b2df0a6270841d23cb93937d58e1f4ec2"
},
{
"name": "usr/share/zoneinfo/right/Europe/Budapest",
"type": "reg",
"size": 2945,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23366618,
"NumLink": 0,
"digest": "sha256:865242b95f7d1177e289a4853ab538079af84710f8f09b4908db5928670e016a"
},
{
"name": "usr/share/zoneinfo/right/Europe/Busingen",
"type": "reg",
"size": 2458,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23368270,
"NumLink": 0,
"digest": "sha256:46a95ddb0c5047f5b03c6ebdd2c35f87f59e6719d2a2385134408f826753e60f"
},
{
"name": "usr/share/zoneinfo/right/Europe/Chisinau",
"type": "reg",
"size": 2985,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23369688,
"NumLink": 0,
"digest": "sha256:ea169f6577633d68990e74350d1f53c54d309ea1fb461bc235f6dc3c2bfd03c4"
},
{
"name": "usr/share/zoneinfo/right/Europe/Copenhagen",
"type": "reg",
"size": 2700,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23371326,
"NumLink": 0,
"digest": "sha256:cc7d0746ef3061d3de714685a821a4781abe813fdc6327f162fc70b0c22d62ac"
},
{
"name": "usr/share/zoneinfo/right/Europe/Dublin",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Eire",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Europe/Gibraltar",
"type": "reg",
"size": 3601,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23372927,
"NumLink": 0,
"digest": "sha256:16fa8d1cb048eb5d4defb961df736ae7db4d477b6f08b9d94572da2303681fe7"
},
{
"name": "usr/share/zoneinfo/right/Europe/Guernsey",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GB",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Europe/Helsinki",
"type": "reg",
"size": 2449,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23374929,
"NumLink": 0,
"digest": "sha256:a1894d50d5bc5e541f340d601cd8ababe40da90fa867cc7ed3215be1cd6282de"
},
{
"name": "usr/share/zoneinfo/right/Europe/Isle_of_Man",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GB",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Europe/Istanbul",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Turkey",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Europe/Jersey",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GB",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Europe/Kaliningrad",
"type": "reg",
"size": 2058,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23376409,
"NumLink": 0,
"digest": "sha256:5776b8702f94742194f5280ee49e61d1b8474b6d4808284afb2aaf62b9c4c7fb"
},
{
"name": "usr/share/zoneinfo/right/Europe/Kiev",
"type": "reg",
"size": 2637,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23377565,
"NumLink": 0,
"digest": "sha256:24c81f9cf9518821df795eb6c2660d1de98a29d658922bec6f70b05dc9f427e7"
},
{
"name": "usr/share/zoneinfo/right/Europe/Kirov",
"type": "reg",
"size": 1693,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23379024,
"NumLink": 0,
"digest": "sha256:07d63183733df20b6fd7ab0978715f8635406b229e4c8f0b6340aadd57567618"
},
{
"name": "usr/share/zoneinfo/right/Europe/Lisbon",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Portugal",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Europe/Ljubljana",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Belgrade",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Europe/London",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../GB",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Europe/Luxembourg",
"type": "reg",
"size": 3514,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23380120,
"NumLink": 0,
"digest": "sha256:af6eb983dfb45c9e363dafb9d26b8466179415e37ef87991b258523a36c18239"
},
{
"name": "usr/share/zoneinfo/right/Europe/Madrid",
"type": "reg",
"size": 3177,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23382026,
"NumLink": 0,
"digest": "sha256:01a00debc9c470fe55edfa31568135024d2450ba34c1c88ed16d620b67af5d83"
},
{
"name": "usr/share/zoneinfo/right/Europe/Malta",
"type": "reg",
"size": 3169,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23383772,
"NumLink": 0,
"digest": "sha256:80919f93a3dd18f905ec9ecc82797ea80f289fe05795f32bd9390dbed7ff7d05"
},
{
"name": "usr/share/zoneinfo/right/Europe/Mariehamn",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Helsinki",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Europe/Minsk",
"type": "reg",
"size": 1896,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23385589,
"NumLink": 0,
"digest": "sha256:c77b4d9a74af85f48b6bea8b640f6c5de557d64f3995567d4978fad4e8263f21"
},
{
"name": "usr/share/zoneinfo/right/Europe/Monaco",
"type": "reg",
"size": 3493,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23386672,
"NumLink": 0,
"digest": "sha256:7bac15b163365f1415bd5830c70e2433d36b70b490a393be07d0562071bdc98c"
},
{
"name": "usr/share/zoneinfo/right/Europe/Moscow",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../W-SU",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Europe/Nicosia",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Asia/Nicosia",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Europe/Oslo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Arctic/Longyearbyen",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Europe/Paris",
"type": "reg",
"size": 3511,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23388684,
"NumLink": 0,
"digest": "sha256:6cd62d7d4c3d0be12b47e2e3c026ad0b5513f16e3dbf46d7812f0501c0522def"
},
{
"name": "usr/share/zoneinfo/right/Europe/Podgorica",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Belgrade",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Europe/Prague",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Bratislava",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Europe/Riga",
"type": "reg",
"size": 2775,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23390657,
"NumLink": 0,
"digest": "sha256:543f059b0b90d203ac284c74a9eb1a43acfb6f6de2c3f618b9df24b1b53564d8"
},
{
"name": "usr/share/zoneinfo/right/Europe/Rome",
"type": "reg",
"size": 3232,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23392186,
"NumLink": 0,
"digest": "sha256:52028442c2ca14e1c747c8a0e0c5c152b1e0faacdabe0dec7e93374bf5d36ec8"
},
{
"name": "usr/share/zoneinfo/right/Europe/Samara",
"type": "reg",
"size": 1779,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23394045,
"NumLink": 0,
"digest": "sha256:e1ba77c8d804455d5fee38e828902dba23e86521f398f406523a4de117599f6b"
},
{
"name": "usr/share/zoneinfo/right/Europe/San_Marino",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Rome",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Europe/Sarajevo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Belgrade",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Europe/Saratov",
"type": "reg",
"size": 1723,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23395140,
"NumLink": 0,
"digest": "sha256:38653f429ca844f5c49babb688a91384baa163800bcdd63fd80197d042b3ed86"
},
{
"name": "usr/share/zoneinfo/right/Europe/Simferopol",
"type": "reg",
"size": 2030,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23396154,
"NumLink": 0,
"digest": "sha256:c749271304d25a3772fec2f14e1a9fe29d66a993e88384b4fb8c35305208aa06"
},
{
"name": "usr/share/zoneinfo/right/Europe/Skopje",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Belgrade",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Europe/Sofia",
"type": "reg",
"size": 2670,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23397362,
"NumLink": 0,
"digest": "sha256:2d9a0ae58e5f9d5b678d68e5874b4d4bf0481a5decd3e9a097bed5f172a16cd0"
},
{
"name": "usr/share/zoneinfo/right/Europe/Stockholm",
"type": "reg",
"size": 2458,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23398913,
"NumLink": 0,
"digest": "sha256:57c9a0626fe99afad4195a26bdd53a0013d465c876e4970a365196242191009a"
},
{
"name": "usr/share/zoneinfo/right/Europe/Tallinn",
"type": "reg",
"size": 2727,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23400331,
"NumLink": 0,
"digest": "sha256:3ce08292fa8bf8241cfc2bd85b05dec3459e3b653a0333720380ef66841e9db1"
},
{
"name": "usr/share/zoneinfo/right/Europe/Tirane",
"type": "reg",
"size": 2638,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23401829,
"NumLink": 0,
"digest": "sha256:aecddbe0e431c06b7d90ce8c9be834f2aafeba71716812b98797272f72d54141"
},
{
"name": "usr/share/zoneinfo/right/Europe/Tiraspol",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Chisinau",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Europe/Ulyanovsk",
"type": "reg",
"size": 1807,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23403357,
"NumLink": 0,
"digest": "sha256:40df4bd977a40651e76d094572dd6bdef9124ee8d9d534ed06aebbfc8c5abbc3"
},
{
"name": "usr/share/zoneinfo/right/Europe/Uzhgorod",
"type": "reg",
"size": 2643,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23404397,
"NumLink": 0,
"digest": "sha256:7ede029288ea2e0af7da34e57ef3414159d510db5e41db99ef2bd0becff29c72"
},
{
"name": "usr/share/zoneinfo/right/Europe/Vaduz",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Busingen",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Europe/Vatican",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Rome",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Europe/Vienna",
"type": "reg",
"size": 2777,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23405928,
"NumLink": 0,
"digest": "sha256:0157530a51d22c4596dfb3d37153ba3b41379ddafd6ca1ec40ea288b3fe64dd5"
},
{
"name": "usr/share/zoneinfo/right/Europe/Vilnius",
"type": "reg",
"size": 2739,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23407483,
"NumLink": 0,
"digest": "sha256:60409d0bfd11d41ed822fbc0564645c41d63b215b8b1822c369a5af7824631fe"
},
{
"name": "usr/share/zoneinfo/right/Europe/Volgograd",
"type": "reg",
"size": 1693,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23408974,
"NumLink": 0,
"digest": "sha256:75aa972974961db6368d4fde9ce7d891f1b5dbac6911f636330347f5ef3e8513"
},
{
"name": "usr/share/zoneinfo/right/Europe/Warsaw",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Poland",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Europe/Zagreb",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Belgrade",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Europe/Zaporozhye",
"type": "reg",
"size": 2655,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23410037,
"NumLink": 0,
"digest": "sha256:67d4c4e23f865eeb4fcc7779563524189dd9852d7547ab5b9374f637f4f3faef"
},
{
"name": "usr/share/zoneinfo/right/Europe/Zurich",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Busingen",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Factory",
"type": "reg",
"size": 669,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23411552,
"NumLink": 0,
"digest": "sha256:10cef6c7d0457d242b17866e3e811bed8a06ab56064acebac07f0debcbe92006"
},
{
"name": "usr/share/zoneinfo/right/GB",
"type": "reg",
"size": 4227,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23411990,
"NumLink": 0,
"digest": "sha256:256c78cc8a67c9b7796737d6af67860246715133443319c41c5962d4d4411f6d"
},
{
"name": "usr/share/zoneinfo/right/GB-Eire",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "GB",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/GMT",
"type": "reg",
"size": 667,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23414295,
"NumLink": 0,
"digest": "sha256:da4f5e177e0e5138774896d0b82b59cce878cf3700734a5d6cdfac2f0f96eb28"
},
{
"name": "usr/share/zoneinfo/right/GMT+0",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/GMT-0",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/GMT0",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Greenwich",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "GMT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/HST",
"type": "reg",
"size": 668,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23414810,
"NumLink": 0,
"digest": "sha256:d6c14dcfa90060f656344c7f7078d78fd6046e38d850b78de5f066c0f3c0c655"
},
{
"name": "usr/share/zoneinfo/right/Hongkong",
"type": "reg",
"size": 1729,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23415259,
"NumLink": 0,
"digest": "sha256:49e939c0d7b29e67f9aa7c598b0f9b058f41ea38a34c76622346a60315c4312f"
},
{
"name": "usr/share/zoneinfo/right/Iceland",
"type": "reg",
"size": 1728,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23416264,
"NumLink": 0,
"digest": "sha256:4a419779c4e933cb9ba204355f1a3649c72d443a95e0a6d81ff506ab467293c0"
},
{
"name": "usr/share/zoneinfo/right/Indian/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Indian/Antananarivo",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Africa/Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Indian/Chagos",
"type": "reg",
"size": 751,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23417349,
"NumLink": 0,
"digest": "sha256:be59ae7c9d5f671c7ed258fc7096743913d78bea71902d23bb77c54b8d0175eb"
},
{
"name": "usr/share/zoneinfo/right/Indian/Christmas",
"type": "reg",
"size": 691,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23417847,
"NumLink": 0,
"digest": "sha256:38acf43ebd68867bedb27ae1544e6c1f25942ed76726c77e08ef523d84e57c9b"
},
{
"name": "usr/share/zoneinfo/right/Indian/Cocos",
"type": "reg",
"size": 700,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23418329,
"NumLink": 0,
"digest": "sha256:e0ee8a9a676f4bb453d822ceab90aea7d606f9db114a0b1f97bd8f301db00f51"
},
{
"name": "usr/share/zoneinfo/right/Indian/Comoro",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Africa/Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Indian/Kerguelen",
"type": "reg",
"size": 713,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23418870,
"NumLink": 0,
"digest": "sha256:efeb12b4af7ee8fb2896dfa04e1864e486423a653258b5bcc24b126eaca3f260"
},
{
"name": "usr/share/zoneinfo/right/Indian/Mahe",
"type": "reg",
"size": 713,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23419339,
"NumLink": 0,
"digest": "sha256:99669b539e7dbdd6a9a06df816e3b5b9e3596c9daf2df41605609c1a0507cd9c"
},
{
"name": "usr/share/zoneinfo/right/Indian/Maldives",
"type": "reg",
"size": 746,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23419818,
"NumLink": 0,
"digest": "sha256:882ac362aab99f9682e268966fed6700a7981dbbfd2d64ad9e2bd864f752d996"
},
{
"name": "usr/share/zoneinfo/right/Indian/Mauritius",
"type": "reg",
"size": 793,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23420316,
"NumLink": 0,
"digest": "sha256:6e54749fc42ea492dce64468fec1dd4ce09ac3270884a0e3b92a804126940b1c"
},
{
"name": "usr/share/zoneinfo/right/Indian/Mayotte",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Africa/Addis_Ababa",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Indian/Reunion",
"type": "reg",
"size": 713,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23420891,
"NumLink": 0,
"digest": "sha256:c271916f61f082d48ae258c12cf41ee28dd98e83d370deb4599ae69b6e8afa59"
},
{
"name": "usr/share/zoneinfo/right/Iran",
"type": "reg",
"size": 2244,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23421357,
"NumLink": 0,
"digest": "sha256:d3001b5add2c5861093986cd21513ff46898e42b18f10a60693dbf6c53d9f8f7"
},
{
"name": "usr/share/zoneinfo/right/Israel",
"type": "reg",
"size": 2805,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23422628,
"NumLink": 0,
"digest": "sha256:a8271f9608d08293b37ec6a3ce75711954ee4fbce8cc80b19e5c4249618ac3a0"
},
{
"name": "usr/share/zoneinfo/right/Jamaica",
"type": "reg",
"size": 1047,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23424200,
"NumLink": 0,
"digest": "sha256:47ddec901639efbe7268363dcc0d8b92a855372b0cae12720d68cce9a8974fdb"
},
{
"name": "usr/share/zoneinfo/right/Japan",
"type": "reg",
"size": 858,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23424863,
"NumLink": 0,
"digest": "sha256:f3fe73852e6756d6691f8c06056cde1a666f23d1f9bc940ac0e11c7fed3805dc"
},
{
"name": "usr/share/zoneinfo/right/Kwajalein",
"type": "reg",
"size": 785,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23425419,
"NumLink": 0,
"digest": "sha256:e6d534633a44ad56204f74ad53a6329eda2bc44a9ec9f8b9d9942431f2c8ce9d"
},
{
"name": "usr/share/zoneinfo/right/Libya",
"type": "reg",
"size": 1195,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23425926,
"NumLink": 0,
"digest": "sha256:72bb85f8ac4ec74b2ae92214c047c82f1f39c9c0785d0f54e152b8c1d940adda"
},
{
"name": "usr/share/zoneinfo/right/MET",
"type": "reg",
"size": 2642,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23426660,
"NumLink": 0,
"digest": "sha256:735b7a481c6b4024076996235dc00c88eba04788ea0d2320f52f3b6f832e2c5c"
},
{
"name": "usr/share/zoneinfo/right/MST",
"type": "reg",
"size": 667,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23428127,
"NumLink": 0,
"digest": "sha256:d7ed7889d7e664fa5f24d3ebca03abc5e6f4c1af886e13b6d057d0aa4009a953"
},
{
"name": "usr/share/zoneinfo/right/MST7MDT",
"type": "reg",
"size": 2834,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23428574,
"NumLink": 0,
"digest": "sha256:f5bff9e9551d99b333440822fd3fa74d2bf03b0303585ce0705557b869e47e83"
},
{
"name": "usr/share/zoneinfo/right/Mexico/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Mexico/BajaNorte",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Ensenada",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Mexico/BajaSur",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Mazatlan",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Mexico/General",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Mexico_City",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/NZ",
"type": "reg",
"size": 3000,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23430323,
"NumLink": 0,
"digest": "sha256:c6fa7d47e6fb209806155ca05d2d0721726515ad678714f3197b9daeb9da3a96"
},
{
"name": "usr/share/zoneinfo/right/NZ-CHAT",
"type": "reg",
"size": 2613,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23431982,
"NumLink": 0,
"digest": "sha256:26f595850d1431c8e91ec414ecd29867fe361f2e49ae0d04f44ed3b53df66240"
},
{
"name": "usr/share/zoneinfo/right/Navajo",
"type": "reg",
"size": 2993,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23433446,
"NumLink": 0,
"digest": "sha256:7448c66048e5489a28ecad3adc495351163e62cedff714bec6d15506a9e1f548"
},
{
"name": "usr/share/zoneinfo/right/PRC",
"type": "reg",
"size": 954,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23435124,
"NumLink": 0,
"digest": "sha256:ad1e89bcb5b895b4163043ba8b4806c1dd3d1886964770688532adbc0d6ffc4a"
},
{
"name": "usr/share/zoneinfo/right/PST8PDT",
"type": "reg",
"size": 2834,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23435732,
"NumLink": 0,
"digest": "sha256:f08ae6766d50d2008608f2e668ff34560eef0fb62b3e60df8c019e04cb914780"
},
{
"name": "usr/share/zoneinfo/right/Pacific/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Pacific/Apia",
"type": "reg",
"size": 1660,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23437373,
"NumLink": 0,
"digest": "sha256:d83e28595f25234e2e75a754d66606779f497f41f3e1e5a8389c62c85f8456e8"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Auckland",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../NZ",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Pacific/Bougainville",
"type": "reg",
"size": 822,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23438397,
"NumLink": 0,
"digest": "sha256:74c033543ad47e18b82f929308eb88b742b84a4b718d0c743e87339a917730b3"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Chatham",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../NZ-CHAT",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Pacific/Chuuk",
"type": "reg",
"size": 692,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23439002,
"NumLink": 0,
"digest": "sha256:31b6fa3e8b930a5d14cf1fe401480d9bd3acc0d2b202ee8d0e2a72aeed2e3be9"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Easter",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Chile/EasterIsland",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Pacific/Efate",
"type": "reg",
"size": 1018,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23439534,
"NumLink": 0,
"digest": "sha256:11867f8f79968dab62432ae65be80de4c1ce8c9f4f2509c73f1239f87a389e74"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Enderbury",
"type": "reg",
"size": 785,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23440190,
"NumLink": 0,
"digest": "sha256:4980802f3f305cbbc84449c6318262384c855d2c2b1c86cdddc64c50b6eb2f17"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Fakaofo",
"type": "reg",
"size": 747,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23440705,
"NumLink": 0,
"digest": "sha256:ffa2f3656163dac98d7128708f767a9003fea91c6a0eab4e088c8530ad819ff7"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Fiji",
"type": "reg",
"size": 1616,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23441201,
"NumLink": 0,
"digest": "sha256:7c48cfd35fece2a2e2d9e69bf8a6ac59c85db1414f67d322e98d21b6ad246a80"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Funafuti",
"type": "reg",
"size": 692,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23442179,
"NumLink": 0,
"digest": "sha256:3b3a5bbce2ef074a52e8c7d0c1152b4859439e053ab54fbf8fcac3416946313f"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Galapagos",
"type": "reg",
"size": 794,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23442666,
"NumLink": 0,
"digest": "sha256:a73b7fbb4d963a4de8a2116696fb14cc1d4fea7f4ee63a7bebf3de6d37ea3f35"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Gambier",
"type": "reg",
"size": 712,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23443183,
"NumLink": 0,
"digest": "sha256:1467e739b00e63003ee811eb5f3a7598ec82c8e69132c9d13b05b4c972afb883"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Guadalcanal",
"type": "reg",
"size": 714,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23443666,
"NumLink": 0,
"digest": "sha256:305ea4443760cd08775ece730d93eead1265d9c30f02f162009397a653892294"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Guam",
"type": "reg",
"size": 765,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23444142,
"NumLink": 0,
"digest": "sha256:ee75c94a1fc4f5a363f4c6af4f386a4ba4751b6dbb43068cadaa613fa4ea2219"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Honolulu",
"type": "reg",
"size": 816,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23444659,
"NumLink": 0,
"digest": "sha256:cb9b83b1f1adf4db95a208e851e6681f5111c25071b85e42620aae507b93ee40"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Johnston",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Honolulu",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Pacific/Kiritimati",
"type": "reg",
"size": 789,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23445243,
"NumLink": 0,
"digest": "sha256:b72c4f9c16069a15f87449a89faf1d1cea5388eef787f99f605a5c24316ae995"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Kosrae",
"type": "reg",
"size": 777,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23445758,
"NumLink": 0,
"digest": "sha256:330b3690e2848d83b135182823d6a896338ee4512e9a3bd59b166395992d289a"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Kwajalein",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Kwajalein",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Pacific/Majuro",
"type": "reg",
"size": 747,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23446307,
"NumLink": 0,
"digest": "sha256:896736fad3956359a0366760467b2af60178d8c6dbc439a3d004dfa02bfb3130"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Marquesas",
"type": "reg",
"size": 721,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23446804,
"NumLink": 0,
"digest": "sha256:90d1dafd8f01d16ee5d029e9c9643275a6ce5f611a99580243a478cdd9d334c3"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Midway",
"type": "reg",
"size": 736,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23447285,
"NumLink": 0,
"digest": "sha256:c996db6822af6b663b37e0c8750132432169950eed2c24bab4c9f843475b5cb5"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Nauru",
"type": "reg",
"size": 808,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23447774,
"NumLink": 0,
"digest": "sha256:636be1327c578fde487fbba425de45a1b6ed7ad12921b99b74f40443ff10a28a"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Niue",
"type": "reg",
"size": 792,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23448291,
"NumLink": 0,
"digest": "sha256:6ea86a60548452d527af1bf1437a846d35a2023295426a924060e001d9b34986"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Norfolk",
"type": "reg",
"size": 849,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23448807,
"NumLink": 0,
"digest": "sha256:46a26c4fc3281118941c7da66a7b333ff252e5c411310a13ef974e4600e4635b"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Noumea",
"type": "reg",
"size": 854,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23449346,
"NumLink": 0,
"digest": "sha256:4181221801b85e6905d0523c8ad5a799c5827cbe3813676d40c47ba8f2a53043"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Pago_Pago",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Midway",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Pacific/Palau",
"type": "reg",
"size": 691,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23449932,
"NumLink": 0,
"digest": "sha256:4353921b3ff2f1d2e37cb674dbcd8ce1ed96401630d55a21bdddadd87a5af7f1"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Pitcairn",
"type": "reg",
"size": 749,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23450416,
"NumLink": 0,
"digest": "sha256:9a483181af824f93b4626871dae31022260d27180e0f15a7dbfe076bedebcb71"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Pohnpei",
"type": "reg",
"size": 692,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23450913,
"NumLink": 0,
"digest": "sha256:01f21467d5a56ff85427dc4cff9466eb47be79d73fa657658c111453d0653919"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Ponape",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Pohnpei",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Pacific/Port_Moresby",
"type": "reg",
"size": 714,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23451438,
"NumLink": 0,
"digest": "sha256:dd70e9df820da5d290670f409b945186fb3e4c2597b981f6aa00618f9e9fe602"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Rarotonga",
"type": "reg",
"size": 1128,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23451938,
"NumLink": 0,
"digest": "sha256:03017bb1fd0bc79957dbac5888563c69e0b2dace69e995e7f5dec95d15cdb78d"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Saipan",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Guam",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Pacific/Samoa",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Midway",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Pacific/Tahiti",
"type": "reg",
"size": 713,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23452715,
"NumLink": 0,
"digest": "sha256:aa925368f0d1362d2c8c738f6d78e729bce16dfafb6a19111ba54c28cfcf77af"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Tarawa",
"type": "reg",
"size": 692,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23453192,
"NumLink": 0,
"digest": "sha256:d1e0a1228e386b4a69c1d79581e8d034f919c251109e6ad18a7ba7a269cc0e1d"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Tongatapu",
"type": "reg",
"size": 919,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23453679,
"NumLink": 0,
"digest": "sha256:9729f5d257db4cd92c0055ba6c667f6e5ee21ee7436cb5df495a887d2481cccd"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Truk",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Chuuk",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Pacific/Wake",
"type": "reg",
"size": 692,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23454297,
"NumLink": 0,
"digest": "sha256:8258c1f19702e381c4bfce3cb5b1335814a593531d3211b7a4a323d42aed7140"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Wallis",
"type": "reg",
"size": 692,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23454780,
"NumLink": 0,
"digest": "sha256:2077f480b59aec6abf5cc989c61246f8fc4c1e8cd8a3e5738eb9ce63b543d586"
},
{
"name": "usr/share/zoneinfo/right/Pacific/Yap",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "Chuuk",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Poland",
"type": "reg",
"size": 3245,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23455299,
"NumLink": 0,
"digest": "sha256:2776052936d3361f49da479c0a6d522ea7ec256300cc9263d1011848ab2bb3a9"
},
{
"name": "usr/share/zoneinfo/right/Portugal",
"type": "reg",
"size": 4009,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23457078,
"NumLink": 0,
"digest": "sha256:f8e850961fc42ea64edb30cebaabad27fccda97bdf6cf1047e4051298bdf20d0"
},
{
"name": "usr/share/zoneinfo/right/ROC",
"type": "reg",
"size": 1330,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23459223,
"NumLink": 0,
"digest": "sha256:d5ff02c7a5dc9adb7a5b8de1e492f8ace92d832e8f2f6879265520c47f850078"
},
{
"name": "usr/share/zoneinfo/right/ROK",
"type": "reg",
"size": 1071,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23460015,
"NumLink": 0,
"digest": "sha256:8520047dc59323aa8ba91ad43e39229e36e4e0381ad14f59af4395b669845afc"
},
{
"name": "usr/share/zoneinfo/right/Singapore",
"type": "reg",
"size": 950,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23460673,
"NumLink": 0,
"digest": "sha256:e2140fbf66b5f7a235fca25454176d2eaab4c8b8303fea4ddfec5455c549bbac"
},
{
"name": "usr/share/zoneinfo/right/SystemV/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/SystemV/AST4",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Puerto_Rico",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/SystemV/AST4ADT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Halifax",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/SystemV/CST6",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Regina",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/SystemV/CST6CDT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Chicago",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/SystemV/EST5",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Cayman",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/SystemV/EST5EDT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/New_York",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/SystemV/HST10",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Pacific/Honolulu",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/SystemV/MST7",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Phoenix",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/SystemV/MST7MDT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Navajo",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/SystemV/PST8",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Pacific/Pitcairn",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/SystemV/PST8PDT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Los_Angeles",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/SystemV/YST9",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Pacific/Gambier",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/SystemV/YST9YDT",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Anchorage",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/Turkey",
"type": "reg",
"size": 2692,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23461615,
"NumLink": 0,
"digest": "sha256:5bd39443ec8a05b4c68c2cb546d9a76b11c59a8d66823d6fd2eb6e49c50497ba"
},
{
"name": "usr/share/zoneinfo/right/UCT",
"type": "reg",
"size": 667,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23463108,
"NumLink": 0,
"digest": "sha256:de715c1e70331ba26ec0db477d50f3df14a4be5a552a5b40683a3aa7882b2975"
},
{
"name": "usr/share/zoneinfo/right/US/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/US/Alaska",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Anchorage",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/US/Aleutian",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Adak",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/US/Arizona",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Phoenix",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/US/Central",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Chicago",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/US/East-Indiana",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Fort_Wayne",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/US/Eastern",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/New_York",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/US/Hawaii",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Pacific/Honolulu",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/US/Indiana-Starke",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Knox_IN",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/US/Michigan",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Detroit",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/US/Mountain",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Navajo",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/US/Pacific",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Los_Angeles",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/US/Pacific-New",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../America/Los_Angeles",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/US/Samoa",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "../Pacific/Midway",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/UTC",
"type": "reg",
"size": 667,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23463934,
"NumLink": 0,
"digest": "sha256:d19fdd5d26a7753518ee9a09ab74a8e4efa9a4e6ed56eff85baabd8af9c0e459"
},
{
"name": "usr/share/zoneinfo/right/Universal",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "UTC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/right/W-SU",
"type": "reg",
"size": 2084,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23464417,
"NumLink": 0,
"digest": "sha256:8e1367300579da7319742b25b9da707131b058482c77e7a707c71aeb60f067ff"
},
{
"name": "usr/share/zoneinfo/right/WET",
"type": "reg",
"size": 2413,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 23465586,
"NumLink": 0,
"digest": "sha256:e9826478fee66e6f8a7583d9a67d82f114f8a12856b4f4a6bfed5db540c54753"
},
{
"name": "usr/share/zoneinfo/right/Zulu",
"type": "symlink",
"modtime": "2018-05-04T18:22:35Z",
"linkName": "UTC",
"mode": 41471,
"NumLink": 0
},
{
"name": "usr/share/zoneinfo/zone.tab",
"type": "reg",
"size": 19165,
"modtime": "2018-02-16T17:21:32Z",
"mode": 33188,
"offset": 23466994,
"NumLink": 0,
"digest": "sha256:14cdfb2c5e3f7201d82c368d3ac204a3cfb9e50d89dd106d414e302db57c141e"
},
{
"name": "usr/share/zoneinfo/zone1970.tab",
"type": "reg",
"size": 17781,
"modtime": "2018-02-16T21:59:02Z",
"mode": 33188,
"offset": 23476414,
"NumLink": 0,
"digest": "sha256:3124b997b9183f7029585690ae3cb31afe1e271a4bbdbf05ef002d23d03760b2"
},
{
"name": "usr/src/",
"type": "dir",
"modtime": "2018-06-26T12:03:08Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/backups/",
"type": "dir",
"modtime": "2018-06-26T12:03:08Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/cache/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/cache/apt/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/cache/debconf/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/cache/debconf/config.dat",
"type": "reg",
"size": 4454,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 23485364,
"NumLink": 0,
"digest": "sha256:dd96d0ac5c66a80416a50f0d94b856efcb707bbc0c49d7ecf07a4dcf33bc4106"
},
{
"name": "var/cache/debconf/config.dat-old",
"type": "reg",
"size": 4294,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 23486224,
"NumLink": 0,
"digest": "sha256:700ffa319f2c418a677fccd79546da9c099e71835817ee040fbc3720840923f2"
},
{
"name": "var/cache/debconf/passwords.dat",
"type": "reg",
"modtime": "2018-10-11T00:00:00Z",
"mode": 33152,
"NumLink": 0,
"digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"name": "var/cache/debconf/templates.dat",
"type": "reg",
"size": 725910,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 23487077,
"NumLink": 0,
"digest": "sha256:a5076b4345d5aafe17f85ae91c948727df60311056601540afc7c89b449929c8"
},
{
"name": "var/cache/debconf/templates.dat-old",
"type": "reg",
"size": 708570,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 23673393,
"NumLink": 0,
"digest": "sha256:e4e46a38994fe5ce8a0198f315ddfaaa3fcf76b6d64357d066e2090330eeff90"
},
{
"name": "var/cache/ldconfig/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16832,
"NumLink": 0
},
{
"name": "var/lib/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/lib/apt/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/lib/apt/extended_states",
"type": "reg",
"size": 4437,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 23852673,
"NumLink": 0,
"digest": "sha256:54dbd6ae802c89a09e004b60fef8a1af518bf488683b270f72d0e88c842f8e6a"
},
{
"name": "var/lib/apt/lists/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/lib/apt/mirrors/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/lib/apt/mirrors/partial/",
"type": "dir",
"modtime": "2017-09-13T16:47:33Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/lib/apt/periodic/",
"type": "dir",
"modtime": "2017-09-13T16:47:33Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/lib/dpkg/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/lib/dpkg/alternatives/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/lib/dpkg/alternatives/awk",
"type": "reg",
"size": 207,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 23853417,
"NumLink": 0,
"digest": "sha256:06c7cfca68d405ca5e20fd379b93fe97fd1698841a1449f9b403115cedcf8eba"
},
{
"name": "var/lib/dpkg/alternatives/builtins.7.gz",
"type": "reg",
"size": 83,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 23853603,
"NumLink": 0,
"digest": "sha256:6cd368606c13e12657f237f25e3ddfd89c6ebc34b52b8391561ff89d0c052f62"
},
{
"name": "var/lib/dpkg/alternatives/pager",
"type": "reg",
"size": 150,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 23853763,
"NumLink": 0,
"digest": "sha256:904b645490f378ef86609d5f40ddc2334aad824f59ee117664d9598ec0ac48f7"
},
{
"name": "var/lib/dpkg/alternatives/rmt",
"type": "reg",
"size": 113,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 23853938,
"NumLink": 0,
"digest": "sha256:cc31c88e6e9660da820eaf68418b4e9dfd76dfdbbbcebbaed446afe397971dc9"
},
{
"name": "var/lib/dpkg/available",
"type": "reg",
"size": 60988,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 23854101,
"NumLink": 0,
"digest": "sha256:2aa0bc06f4328dd5232529021351d42ea097dbc731a3cc1c07fbe208b0f1379f"
},
{
"name": "var/lib/dpkg/cmethopt",
"type": "reg",
"size": 8,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 23872106,
"NumLink": 0,
"digest": "sha256:162ba45c89e2735aba3a5fcd4a0ad00efd9120edaa2215a8f541058cb895ce29"
},
{
"name": "var/lib/dpkg/diversions",
"type": "reg",
"size": 136,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 23872222,
"NumLink": 0,
"digest": "sha256:164c982bde1284c84173ddcfac3bd28581551a51abae77ac01f278829477525a"
},
{
"name": "var/lib/dpkg/diversions-old",
"type": "reg",
"size": 98,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 23872399,
"NumLink": 0,
"digest": "sha256:0e4c917474e8711b898165d3b464a2c47c027a28ec6d7f87f4bd2fc1deee2a2a"
},
{
"name": "var/lib/dpkg/info/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/lib/dpkg/info/adduser.conffiles",
"type": "reg",
"size": 18,
"modtime": "2016-06-26T22:55:18Z",
"mode": 33188,
"offset": 23872595,
"NumLink": 0,
"digest": "sha256:2ed8c75e30af614b19760b001063fbeb2a48ebaf469c44d921d3df787f2ca173"
},
{
"name": "var/lib/dpkg/info/adduser.config",
"type": "reg",
"size": 929,
"modtime": "2016-06-26T22:55:17Z",
"mode": 33261,
"offset": 23872725,
"NumLink": 0,
"digest": "sha256:7bbbed298ee1cba145ccced3c596025d1bf420c978feef4269e9e3c0c8ef1855"
},
{
"name": "var/lib/dpkg/info/adduser.list",
"type": "reg",
"size": 6420,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 23873215,
"NumLink": 0,
"digest": "sha256:1d2871a453ad11657eb656cc724f8fde70e8e618722a033460deb3418a80615d"
},
{
"name": "var/lib/dpkg/info/adduser.md5sums",
"type": "reg",
"size": 6061,
"modtime": "2016-06-26T22:55:18Z",
"mode": 33188,
"offset": 23874065,
"NumLink": 0,
"digest": "sha256:b64ccdfd5680940021be7d9ce124fe90be65b8680bd1ce1228581d61efa5a616"
},
{
"name": "var/lib/dpkg/info/adduser.postinst",
"type": "reg",
"size": 964,
"modtime": "2016-06-26T22:55:18Z",
"mode": 33261,
"offset": 23876178,
"NumLink": 0,
"digest": "sha256:725a17e3ccb6bcda57076fa51d824c1a0903c885261c34e053f9750be30a2755"
},
{
"name": "var/lib/dpkg/info/adduser.postrm",
"type": "reg",
"size": 297,
"modtime": "2016-06-26T22:55:18Z",
"mode": 33261,
"offset": 23876695,
"NumLink": 0,
"digest": "sha256:873add4d34b1673f89a54e7bfa41f6d70f006f4c2576259e848063dbe216e233"
},
{
"name": "var/lib/dpkg/info/adduser.templates",
"type": "reg",
"size": 16424,
"modtime": "2016-06-26T22:55:17Z",
"mode": 33188,
"offset": 23876976,
"NumLink": 0,
"digest": "sha256:c32dc4475a9743b42b46001f490733dce5bc10ee442208f4f50b406f466c1214"
},
{
"name": "var/lib/dpkg/info/apt.conffiles",
"type": "reg",
"size": 121,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33188,
"offset": 23884545,
"NumLink": 0,
"digest": "sha256:40d910bb31d4f4744f154ccd80bf53c3ec5574f1577f35af78e5ef77dbcaae47"
},
{
"name": "var/lib/dpkg/info/apt.list",
"type": "reg",
"size": 10104,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 23884732,
"NumLink": 0,
"digest": "sha256:f7f8f166f78e81f6e3c62b94413224d446b3097bf1bc06553125db46d2d049f2"
},
{
"name": "var/lib/dpkg/info/apt.md5sums",
"type": "reg",
"size": 11361,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33188,
"offset": 23886194,
"NumLink": 0,
"digest": "sha256:fb6e26258b39c79746935376b34fb7e5393c1d4f9f599291e7772decf30c5f02"
},
{
"name": "var/lib/dpkg/info/apt.postinst",
"type": "reg",
"size": 4578,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 23890307,
"NumLink": 0,
"digest": "sha256:fbbdc95f0bafae4c6e30b833a24a1488415c9cecf5e3b8b77e5eff0515092d21"
},
{
"name": "var/lib/dpkg/info/apt.postrm",
"type": "reg",
"size": 1313,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 23891951,
"NumLink": 0,
"digest": "sha256:c98cf0eb8b2f164769d0d658eff78b558ee86604444306f2a8fa6388e53d3b10"
},
{
"name": "var/lib/dpkg/info/apt.preinst",
"type": "reg",
"size": 247,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 23892610,
"NumLink": 0,
"digest": "sha256:dc05f90439c5bc1efaff372a49e9fbc394de3645424b88245fc7b9f42cd0723e"
},
{
"name": "var/lib/dpkg/info/apt.prerm",
"type": "reg",
"size": 459,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33261,
"offset": 23892864,
"NumLink": 0,
"digest": "sha256:61c2d8f74fc8d7ccfab6bc555d4d658ef270e93aab26fd58d522518e8c225bf5"
},
{
"name": "var/lib/dpkg/info/apt.shlibs",
"type": "reg",
"size": 23,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33188,
"offset": 23893211,
"NumLink": 0,
"digest": "sha256:ac130374b3322f21573625a13454d60cf7b5166bcd43b931f8ec09e99d54c455"
},
{
"name": "var/lib/dpkg/info/apt.triggers",
"type": "reg",
"size": 60,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33188,
"offset": 23893346,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/base-files.conffiles",
"type": "reg",
"size": 114,
"modtime": "2018-06-26T12:03:08Z",
"mode": 33188,
"offset": 23893518,
"NumLink": 0,
"digest": "sha256:4b3e9e5378d40d8d9e7c508b759513975ad83bf6faeaa3a1b1684c53931b8556"
},
{
"name": "var/lib/dpkg/info/base-files.list",
"type": "reg",
"size": 1749,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 23893703,
"NumLink": 0,
"digest": "sha256:4bbc622bdfec8ff870c92223f56614cc43a22361d99e7e4f9d4b030c1cd70ba7"
},
{
"name": "var/lib/dpkg/info/base-files.md5sums",
"type": "reg",
"size": 1829,
"modtime": "2018-06-26T12:03:08Z",
"mode": 33188,
"offset": 23894270,
"NumLink": 0,
"digest": "sha256:378b9d019a502f188899b209f441427839c7e49d19db0896481343133e8083a5"
},
{
"name": "var/lib/dpkg/info/base-files.postinst",
"type": "reg",
"size": 3370,
"modtime": "2018-06-26T12:03:08Z",
"mode": 33261,
"offset": 23895147,
"NumLink": 0,
"digest": "sha256:5b201cc9661b9d7ec075602076165244482cb4b4afadb1a8ed8bc62f10938fd6"
},
{
"name": "var/lib/dpkg/info/base-passwd.list",
"type": "reg",
"size": 1120,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 23896095,
"NumLink": 0,
"digest": "sha256:59e7d3f01481b921ab2fe7b7c0f4c9010c5dbd68e63fd6aa6b98144ec444129c"
},
{
"name": "var/lib/dpkg/info/base-passwd.md5sums",
"type": "reg",
"size": 1238,
"modtime": "2017-01-16T15:52:12Z",
"mode": 33188,
"offset": 23896429,
"NumLink": 0,
"digest": "sha256:4e1e3bf27aa7bad2a465a62d6db8f7eb57c60ffebd9473e2d0351ad8a92ef117"
},
{
"name": "var/lib/dpkg/info/base-passwd.postinst",
"type": "reg",
"size": 2802,
"modtime": "2017-01-16T15:52:12Z",
"mode": 33261,
"offset": 23897056,
"NumLink": 0,
"digest": "sha256:01ea366cf8c635d5e21ce289ab3b9998247eac1d3f33312cf002257b0abce445"
},
{
"name": "var/lib/dpkg/info/base-passwd.postrm",
"type": "reg",
"size": 206,
"modtime": "2017-01-16T15:52:12Z",
"mode": 33261,
"offset": 23898426,
"NumLink": 0,
"digest": "sha256:c1c04914a45a56607aa795b52767684381625c27cb3a8f45302cbfa088f735b5"
},
{
"name": "var/lib/dpkg/info/base-passwd.preinst",
"type": "reg",
"size": 1516,
"modtime": "2017-01-16T15:52:12Z",
"mode": 33261,
"offset": 23898677,
"NumLink": 0,
"digest": "sha256:6e38694265a25ef4a21a595dbcf58679b02eb3ef91a76b8ce293cfb97def209c"
},
{
"name": "var/lib/dpkg/info/base-passwd.templates",
"type": "reg",
"size": 108623,
"modtime": "2017-01-16T15:52:12Z",
"mode": 33188,
"offset": 23899429,
"NumLink": 0,
"digest": "sha256:f7de1e47978fedea5a736df4f16b947218f4e65735753d97c1c8954e8312c776"
},
{
"name": "var/lib/dpkg/info/bash.conffiles",
"type": "reg",
"size": 77,
"modtime": "2017-05-15T19:45:32Z",
"mode": 33188,
"offset": 23906721,
"NumLink": 0,
"digest": "sha256:547d1cf6291bce0dcc569f9caad2b7d204f42efb7a20047d86aa48e0c305a52f"
},
{
"name": "var/lib/dpkg/info/bash.list",
"type": "reg",
"size": 4636,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 23906872,
"NumLink": 0,
"digest": "sha256:58b51db22b40ea38bb1ba60552d57ad2f97a790e218779fde6502d1bb2df8dc4"
},
{
"name": "var/lib/dpkg/info/bash.md5sums",
"type": "reg",
"size": 4281,
"modtime": "2017-05-15T19:45:32Z",
"mode": 33188,
"offset": 23907602,
"NumLink": 0,
"digest": "sha256:d31124a7c119d4a35de5759395224b560bcd1f0d475877e11be7cb455c3ddb33"
},
{
"name": "var/lib/dpkg/info/bash.postinst",
"type": "reg",
"size": 596,
"modtime": "2017-05-15T19:45:32Z",
"mode": 33261,
"offset": 23909308,
"NumLink": 0,
"digest": "sha256:28393012a0c44900dd8c3c0cb5a707ff1774556a5630efb2d853ab4d2564ec58"
},
{
"name": "var/lib/dpkg/info/bash.postrm",
"type": "reg",
"size": 493,
"modtime": "2017-05-15T19:45:32Z",
"mode": 33261,
"offset": 23909725,
"NumLink": 0,
"digest": "sha256:614ae2549afbdf6887b92b717a380196b3da6e892df5594b37b1490a94c9d65b"
},
{
"name": "var/lib/dpkg/info/bash.preinst",
"type": "reg",
"size": 36112,
"modtime": "2017-05-15T19:45:32Z",
"mode": 33261,
"offset": 23910111,
"NumLink": 0,
"digest": "sha256:41dc07e488ade0ebca895980e2be50c12111e82784e27949a0a4418c04bec114"
},
{
"name": "var/lib/dpkg/info/bash.prerm",
"type": "reg",
"size": 289,
"modtime": "2017-05-15T19:45:32Z",
"mode": 33261,
"offset": 23923457,
"NumLink": 0,
"digest": "sha256:8d4c0e961f582ca7821cd5b81c8ba304a90f2ddfd6efd963f58cf6020eb2198a"
},
{
"name": "var/lib/dpkg/info/bsdutils.list",
"type": "reg",
"size": 851,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 23923757,
"NumLink": 0,
"digest": "sha256:d4b57cd70404a8b1f20addaa2a31b0ff0539f2b9175814229e904f5f4804661a"
},
{
"name": "var/lib/dpkg/info/bsdutils.md5sums",
"type": "reg",
"size": 1262,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 23924053,
"NumLink": 0,
"digest": "sha256:7c9fe1c6a5c5d8bd7615a54b636fa7f952de2f9c66b3d62fd5df7e98f366b38d"
},
{
"name": "var/lib/dpkg/info/coreutils.list",
"type": "reg",
"size": 9706,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 23924713,
"NumLink": 0,
"digest": "sha256:73887279428ab722d2e19ab01daa96efa399ff7fd71c5670f3b692d86dacf190"
},
{
"name": "var/lib/dpkg/info/coreutils.md5sums",
"type": "reg",
"size": 15674,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33188,
"offset": 23926177,
"NumLink": 0,
"digest": "sha256:dc2084243a4eadceb52eb0911ca26725b3537176ee922973d1c2b4e412cadc91"
},
{
"name": "var/lib/dpkg/info/coreutils.postinst",
"type": "reg",
"size": 114,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 23932568,
"NumLink": 0,
"digest": "sha256:b8cc23953515e77bc392d81af72f7362118a5fdf5961680620c2f832bf4c56a8"
},
{
"name": "var/lib/dpkg/info/coreutils.postrm",
"type": "reg",
"size": 95,
"modtime": "2017-02-22T12:23:45Z",
"mode": 33261,
"offset": 23932765,
"NumLink": 0,
"digest": "sha256:fb04069056d6bd6aadb41350b25da0f9da66ff520185f4f16445430535e1bf18"
},
{
"name": "var/lib/dpkg/info/dash.config",
"type": "reg",
"size": 769,
"modtime": "2017-01-24T05:16:56Z",
"mode": 33261,
"offset": 23932954,
"NumLink": 0,
"digest": "sha256:6ce6e1f0695bac42e20a96be49b70c79cf596e8f81047ff4ea3cf922c0a9cc57"
},
{
"name": "var/lib/dpkg/info/dash.list",
"type": "reg",
"size": 418,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 23933488,
"NumLink": 0,
"digest": "sha256:8c70bf1decf43dd6558efc6fd6115e0a7079f2ed6b6d67cdd16b3cddd170ef83"
},
{
"name": "var/lib/dpkg/info/dash.md5sums",
"type": "reg",
"size": 569,
"modtime": "2017-01-24T05:16:56Z",
"mode": 33188,
"offset": 23933727,
"NumLink": 0,
"digest": "sha256:bbb41d92377cc9909cbb9eff846728815b57b30770a7e4435570bc839f136596"
},
{
"name": "var/lib/dpkg/info/dash.postinst",
"type": "reg",
"size": 3651,
"modtime": "2017-01-24T05:16:56Z",
"mode": 33261,
"offset": 23934134,
"NumLink": 0,
"digest": "sha256:0ba947daf43666c6e13f9da16a5afe0349c1bb89de4776f9fcf10f73a426e23b"
},
{
"name": "var/lib/dpkg/info/dash.postrm",
"type": "reg",
"size": 341,
"modtime": "2017-01-24T05:16:56Z",
"mode": 33261,
"offset": 23935661,
"NumLink": 0,
"digest": "sha256:9f0a19bf26bba371cd2ddc8b00ef14af396d279c5838beb2a13faa9a7ea624bc"
},
{
"name": "var/lib/dpkg/info/dash.preinst",
"type": "reg",
"size": 1025,
"modtime": "2017-01-24T05:16:56Z",
"mode": 33261,
"offset": 23935929,
"NumLink": 0,
"digest": "sha256:3de6d6ca5b09c610c66ec74b2e40f3d68b11c7c982639a588e5533505ee4a6b5"
},
{
"name": "var/lib/dpkg/info/dash.prerm",
"type": "reg",
"size": 563,
"modtime": "2017-01-24T05:16:56Z",
"mode": 33261,
"offset": 23936576,
"NumLink": 0,
"digest": "sha256:2b6e56c709fbdf8956f50193ca6312a74f598c49269298699df88a5aa1df704a"
},
{
"name": "var/lib/dpkg/info/dash.templates",
"type": "reg",
"size": 8304,
"modtime": "2017-01-24T05:16:56Z",
"mode": 33188,
"offset": 23937002,
"NumLink": 0,
"digest": "sha256:9451f0fff5c8aa0da29e1947305f9430ab0b1ee6d7eac8b23f46d73716c5c64c"
},
{
"name": "var/lib/dpkg/info/debconf.conffiles",
"type": "reg",
"size": 48,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 23940553,
"NumLink": 0,
"digest": "sha256:1980054721522ce9b99b3c1ea992a517b0ae93c7eebb9c502129c5f12ad928a8"
},
{
"name": "var/lib/dpkg/info/debconf.config",
"type": "reg",
"size": 478,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33261,
"offset": 23940693,
"NumLink": 0,
"digest": "sha256:4f6cca919cf82b607d0ec001e451566c887383c41320d796021eee66b4473db2"
},
{
"name": "var/lib/dpkg/info/debconf.list",
"type": "reg",
"size": 7673,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 23941070,
"NumLink": 0,
"digest": "sha256:87aabfdba6416de57218fc0c9df32a0ee7ca4135c6c27ff1ba3439538983ee79"
},
{
"name": "var/lib/dpkg/info/debconf.md5sums",
"type": "reg",
"size": 11273,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 23942199,
"NumLink": 0,
"digest": "sha256:4ee64bce4e28a40642b016c6600a3a750c01b04da33b2fc70efd7327483adda9"
},
{
"name": "var/lib/dpkg/info/debconf.postinst",
"type": "reg",
"size": 2771,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33261,
"offset": 23946063,
"NumLink": 0,
"digest": "sha256:4cbedde53a7f9b8656d360515ba3c21687202322424f3425ec94f6418befa5d6"
},
{
"name": "var/lib/dpkg/info/debconf.postrm",
"type": "reg",
"size": 173,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33261,
"offset": 23947305,
"NumLink": 0,
"digest": "sha256:51a4e382b6fe7c3211ff6bbe2284d0556e92c66d8204f35314687236b1d0bf36"
},
{
"name": "var/lib/dpkg/info/debconf.preinst",
"type": "reg",
"size": 304,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33261,
"offset": 23947542,
"NumLink": 0,
"digest": "sha256:790e4c93cdc18ccaafffb44e248525d75f467aa801915de8bf6e5b73e8e37050"
},
{
"name": "var/lib/dpkg/info/debconf.prerm",
"type": "reg",
"size": 563,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33261,
"offset": 23947827,
"NumLink": 0,
"digest": "sha256:70c4846220638de0c245ad9c355927c73569e9bacb8d2b0b502c60bf33112713"
},
{
"name": "var/lib/dpkg/info/debconf.templates",
"type": "reg",
"size": 143753,
"modtime": "2017-05-21T17:08:30Z",
"mode": 33188,
"offset": 23948268,
"NumLink": 0,
"digest": "sha256:2dbd5c40bbf8d9c6b5f4130166e3ff9fd011bb821baa748aaa18215c00f8931d"
},
{
"name": "var/lib/dpkg/info/debian-archive-keyring.conffiles",
"type": "reg",
"size": 484,
"modtime": "2017-05-25T18:17:13Z",
"mode": 33188,
"offset": 24004857,
"NumLink": 0,
"digest": "sha256:654e2a0d33fb4d1c829c44c70dafa3e5595e38d152995596e376c06642eeefea"
},
{
"name": "var/lib/dpkg/info/debian-archive-keyring.list",
"type": "reg",
"size": 856,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24005062,
"NumLink": 0,
"digest": "sha256:96f4f39133ba8c082c06a57b06c06aad72883f4200d0393ac33b48703a7675c9"
},
{
"name": "var/lib/dpkg/info/debian-archive-keyring.md5sums",
"type": "reg",
"size": 408,
"modtime": "2017-05-25T18:17:13Z",
"mode": 33188,
"offset": 24005347,
"NumLink": 0,
"digest": "sha256:54f451ebd946b6a0d11d99c92515a5038a0f82c6b47899e1e34b644baf4c7b17"
},
{
"name": "var/lib/dpkg/info/debian-archive-keyring.postinst",
"type": "reg",
"size": 1003,
"modtime": "2017-05-25T18:17:13Z",
"mode": 33261,
"offset": 24005655,
"NumLink": 0,
"digest": "sha256:2f8c9f4f5775f627d7be2a1243f276d0102cd21a538e29c09be63533f93c9938"
},
{
"name": "var/lib/dpkg/info/debian-archive-keyring.postrm",
"type": "reg",
"size": 502,
"modtime": "2017-05-25T18:17:13Z",
"mode": 33261,
"offset": 24006260,
"NumLink": 0,
"digest": "sha256:b5d6c516c9658fca66891cf2d4cbefa8df3a7589ac59823648d922e8e3f2da20"
},
{
"name": "var/lib/dpkg/info/debian-archive-keyring.preinst",
"type": "reg",
"size": 386,
"modtime": "2017-05-25T18:17:13Z",
"mode": 33261,
"offset": 24006579,
"NumLink": 0,
"digest": "sha256:ecd03bbc5ff5faaafb62bf6386197c83d87ef43cf40d3bb61207c36950e85e65"
},
{
"name": "var/lib/dpkg/info/debian-archive-keyring.prerm",
"type": "reg",
"size": 386,
"modtime": "2017-05-25T18:17:13Z",
"mode": 33261,
"offset": 24006845,
"NumLink": 0,
"digest": "sha256:f5ce562982ff479363c27e6ee1205852f1112277e91f449e1f8c17018ed6b340"
},
{
"name": "var/lib/dpkg/info/debianutils.list",
"type": "reg",
"size": 3047,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24007107,
"NumLink": 0,
"digest": "sha256:ce60da81c5675bf3052f1bc84b3716607de015c561813f1420797e4a106e3efe"
},
{
"name": "var/lib/dpkg/info/debianutils.md5sums",
"type": "reg",
"size": 4707,
"modtime": "2017-04-02T17:10:33Z",
"mode": 33188,
"offset": 24007642,
"NumLink": 0,
"digest": "sha256:7e90c622a29d314ec3518d506f3aaca217a8df282813351308a62ef199ae2e81"
},
{
"name": "var/lib/dpkg/info/debianutils.postinst",
"type": "reg",
"size": 467,
"modtime": "2015-04-26T14:59:54Z",
"mode": 33261,
"offset": 24009426,
"NumLink": 0,
"digest": "sha256:d7aa50ccd2391e2b8c627cde1ca98128022bcc387801ef50c12fee0bca864ddc"
},
{
"name": "var/lib/dpkg/info/debianutils.postrm",
"type": "reg",
"size": 377,
"modtime": "2015-04-26T15:13:56Z",
"mode": 33261,
"offset": 24009780,
"NumLink": 0,
"digest": "sha256:cb0dfa3fc27cf2dfac46a355e383fc25a920cda0622e1225cc74fc3b01a6c4b1"
},
{
"name": "var/lib/dpkg/info/diffutils.list",
"type": "reg",
"size": 3727,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24010100,
"NumLink": 0,
"digest": "sha256:368baf799339402c56245928477592cbb954b5da994f62c79d5b0e641b100746"
},
{
"name": "var/lib/dpkg/info/diffutils.md5sums",
"type": "reg",
"size": 3329,
"modtime": "2017-01-09T22:55:10Z",
"mode": 33188,
"offset": 24010617,
"NumLink": 0,
"digest": "sha256:ca2a2ba957ccbf9b43ed420641684fe93521f7e1772f816f3d39cd8edc30e55c"
},
{
"name": "var/lib/dpkg/info/dpkg.conffiles",
"type": "reg",
"size": 87,
"modtime": "2018-06-26T10:28:08Z",
"mode": 33188,
"offset": 24011873,
"NumLink": 0,
"digest": "sha256:e862d61384229595858b2123b380b187a831dacd342813efd146386e09a6d91b"
},
{
"name": "var/lib/dpkg/info/dpkg.list",
"type": "reg",
"size": 8442,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24012043,
"NumLink": 0,
"digest": "sha256:893380cde895568233bd6640c6e79a271f5cfc599389e056f2f5b504afbb302e"
},
{
"name": "var/lib/dpkg/info/dpkg.md5sums",
"type": "reg",
"size": 9424,
"modtime": "2018-06-26T10:28:08Z",
"mode": 33188,
"offset": 24013171,
"NumLink": 0,
"digest": "sha256:87683e11f19a7ef51d7fb580d027cbd03e08e4ec18df283d24deafc688e769e5"
},
{
"name": "var/lib/dpkg/info/dpkg.postinst",
"type": "reg",
"size": 718,
"modtime": "2018-06-26T10:28:08Z",
"mode": 33261,
"offset": 24016462,
"NumLink": 0,
"digest": "sha256:f8cee032bc3abd6ef824d60fd2ebedd9c676c0dad1468dcffafaeb1f44d8b2fa"
},
{
"name": "var/lib/dpkg/info/dpkg.postrm",
"type": "reg",
"size": 873,
"modtime": "2018-06-26T10:28:08Z",
"mode": 33261,
"offset": 24016981,
"NumLink": 0,
"digest": "sha256:9340ee2661b36e6e22ced24f954f6e372792c9fb08808de2286592031388e507"
},
{
"name": "var/lib/dpkg/info/dpkg.prerm",
"type": "reg",
"size": 4320,
"modtime": "2018-06-26T10:28:08Z",
"mode": 33261,
"offset": 24017569,
"NumLink": 0,
"digest": "sha256:90d17414a0558d9c3fa44570de414a3aca7efe58019a9dd008776c2d4fa0d4eb"
},
{
"name": "var/lib/dpkg/info/e2fslibs:amd64.list",
"type": "reg",
"size": 309,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24019268,
"NumLink": 0,
"digest": "sha256:dda7841fc00411ce8a30c6df76478ba03721a0624593cc3dc1f368a7bc51eb00"
},
{
"name": "var/lib/dpkg/info/e2fslibs:amd64.md5sums",
"type": "reg",
"size": 285,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33188,
"offset": 24019488,
"NumLink": 0,
"digest": "sha256:eb214c1b1b7446602a6a9e5ee69b74795987566d6384bf9ed3ec5216e4f43b8a"
},
{
"name": "var/lib/dpkg/info/e2fslibs:amd64.shlibs",
"type": "reg",
"size": 102,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33188,
"offset": 24019775,
"NumLink": 0,
"digest": "sha256:0a5eff72f9cdae23f1af177e666a874f6b7762fe6d059cd5f6c2a59f6687b9d3"
},
{
"name": "var/lib/dpkg/info/e2fslibs:amd64.symbols",
"type": "reg",
"size": 22220,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33188,
"offset": 24019943,
"NumLink": 0,
"digest": "sha256:01c14a7a5cc623a23209987e9efd7f93157653e312ebba93a2860b4dcabc0bd3"
},
{
"name": "var/lib/dpkg/info/e2fslibs:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33188,
"offset": 24023654,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/e2fsprogs.conffiles",
"type": "reg",
"size": 17,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33188,
"offset": 24023824,
"NumLink": 0,
"digest": "sha256:4a21d18e62ee18b0b1afd94364e581e0b9b9881ea6e2df831fe8d4086a9672b9"
},
{
"name": "var/lib/dpkg/info/e2fsprogs.list",
"type": "reg",
"size": 3724,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24023956,
"NumLink": 0,
"digest": "sha256:57ef0996457e5974ef969080522b44c749a50a1eaa75213139bec8cfa323242a"
},
{
"name": "var/lib/dpkg/info/e2fsprogs.md5sums",
"type": "reg",
"size": 4105,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33188,
"offset": 24024636,
"NumLink": 0,
"digest": "sha256:09b53240f72858bef3331cb8bd2fc2d699ce58d604fb4abb1f19347d4e9eb0f5"
},
{
"name": "var/lib/dpkg/info/e2fsprogs.postinst",
"type": "reg",
"size": 162,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33261,
"offset": 24026378,
"NumLink": 0,
"digest": "sha256:6cd869d76cb66fbd9e1cbeb40d7a21a060ffbef4337f5486ec167b9fa733c240"
},
{
"name": "var/lib/dpkg/info/e2fsprogs.preinst",
"type": "reg",
"size": 259,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33261,
"offset": 24026600,
"NumLink": 0,
"digest": "sha256:0ac4873d936cbf6ea2c958b686aa5097f82306e1bd50fa6eff1b95337664dac1"
},
{
"name": "var/lib/dpkg/info/findutils.list",
"type": "reg",
"size": 4524,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24026905,
"NumLink": 0,
"digest": "sha256:6cc308cb40b40db24caf9341efaf30f30cac9d69f53fa2a1316d95d49603ff4b"
},
{
"name": "var/lib/dpkg/info/findutils.md5sums",
"type": "reg",
"size": 3992,
"modtime": "2017-02-18T15:37:32Z",
"mode": 33188,
"offset": 24027492,
"NumLink": 0,
"digest": "sha256:1035eade64b7d7879968133488de2363b3631c5ac2eb17a2394b5d764a588432"
},
{
"name": "var/lib/dpkg/info/findutils.postinst",
"type": "reg",
"size": 1195,
"modtime": "2017-02-18T15:37:32Z",
"mode": 33261,
"offset": 24028958,
"NumLink": 0,
"digest": "sha256:c42144b0d65ed7c0e11524c73d8d8d3eb4effc7fa0f5f9625a0fcbf3a1447417"
},
{
"name": "var/lib/dpkg/info/findutils.preinst",
"type": "reg",
"size": 2426,
"modtime": "2017-02-18T15:37:32Z",
"mode": 33261,
"offset": 24029640,
"NumLink": 0,
"digest": "sha256:3e3c2ec17f71c24420c30fc67e3ad2b333e57e2482397c793ed79d0824872393"
},
{
"name": "var/lib/dpkg/info/format",
"type": "reg",
"size": 2,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24030546,
"NumLink": 0,
"digest": "sha256:4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865"
},
{
"name": "var/lib/dpkg/info/gcc-6-base:amd64.list",
"type": "reg",
"size": 349,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24030672,
"NumLink": 0,
"digest": "sha256:4ae17456b60df15cf95281a880673f742c88d849f8fa894e6947001e9b773e1c"
},
{
"name": "var/lib/dpkg/info/gcc-6-base:amd64.md5sums",
"type": "reg",
"size": 301,
"modtime": "2018-02-14T16:53:20Z",
"mode": 33188,
"offset": 24030906,
"NumLink": 0,
"digest": "sha256:01193a7e0f32c2ff715c52b21b473c39664a88cdc2f4d57cfffc38741aa62baa"
},
{
"name": "var/lib/dpkg/info/gpgv.list",
"type": "reg",
"size": 280,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24031184,
"NumLink": 0,
"digest": "sha256:fa4d4420c6b77df26e9b1b0a18411c1430ad18ef9dfbb5e833d8b5480954c598"
},
{
"name": "var/lib/dpkg/info/gpgv.md5sums",
"type": "reg",
"size": 380,
"modtime": "2018-06-08T18:12:24Z",
"mode": 33188,
"offset": 24031387,
"NumLink": 0,
"digest": "sha256:3246d99396b1b6878d53e58d30c99e85f22d6b5ce05e31535894cdecd188903f"
},
{
"name": "var/lib/dpkg/info/grep.list",
"type": "reg",
"size": 4692,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24031702,
"NumLink": 0,
"digest": "sha256:aa6915ec381a63d8a0a642d76ca65dc13762d2148bf113aa53899a0ae8168026"
},
{
"name": "var/lib/dpkg/info/grep.md5sums",
"type": "reg",
"size": 4003,
"modtime": "2017-01-23T18:18:59Z",
"mode": 33188,
"offset": 24032323,
"NumLink": 0,
"digest": "sha256:96b2aaba953973e6dfc8d42bc8cdffbcc30c66b48611b709c8bf13aaf53e13b9"
},
{
"name": "var/lib/dpkg/info/gzip.list",
"type": "reg",
"size": 1024,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24033852,
"NumLink": 0,
"digest": "sha256:8a2f948cb270e6e1b596c66c81badfec1a83a66c51d685cbb913e4b579db389d"
},
{
"name": "var/lib/dpkg/info/gzip.md5sums",
"type": "reg",
"size": 1664,
"modtime": "2016-03-14T20:41:45Z",
"mode": 33188,
"offset": 24034205,
"NumLink": 0,
"digest": "sha256:9edef4bf71da5d06c2135332126caae7d5685da7264f3fe6da0065924a32b404"
},
{
"name": "var/lib/dpkg/info/hostname.list",
"type": "reg",
"size": 484,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24035094,
"NumLink": 0,
"digest": "sha256:2e13d739864a9c8648b05ddd0d95c9774a37e57684ba6bad8e0b3d2e806c4f0f"
},
{
"name": "var/lib/dpkg/info/hostname.md5sums",
"type": "reg",
"size": 327,
"modtime": "2016-07-03T19:26:17Z",
"mode": 33188,
"offset": 24035326,
"NumLink": 0,
"digest": "sha256:670b5c913b6da065c03bfe8ea8c01748271833e08edd312b50cfcfd7b032625f"
},
{
"name": "var/lib/dpkg/info/init-system-helpers.list",
"type": "reg",
"size": 1012,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24035622,
"NumLink": 0,
"digest": "sha256:9746762ee1530db38b7065f1c13389638e238c6215ea54bec21d0fdaafdaf31f"
},
{
"name": "var/lib/dpkg/info/init-system-helpers.md5sums",
"type": "reg",
"size": 1142,
"modtime": "2017-05-02T10:20:21Z",
"mode": 33188,
"offset": 24035978,
"NumLink": 0,
"digest": "sha256:666ecb63b005586a769fdfce6e438c8fac1159b6f7062f2c3808356f2135fb15"
},
{
"name": "var/lib/dpkg/info/init-system-helpers.postinst",
"type": "reg",
"size": 275,
"modtime": "2017-05-02T10:20:21Z",
"mode": 33261,
"offset": 24036596,
"NumLink": 0,
"digest": "sha256:fc3b0684d7e3817cd81b736b6705541dd779e8af434558ccb9b370d0f52bfc8a"
},
{
"name": "var/lib/dpkg/info/libacl1:amd64.list",
"type": "reg",
"size": 317,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24036878,
"NumLink": 0,
"digest": "sha256:e2629d1d7243300ab8132dec55f14a86cd969a75cd29eedd44307cd00b14cd41"
},
{
"name": "var/lib/dpkg/info/libacl1:amd64.md5sums",
"type": "reg",
"size": 364,
"modtime": "2016-02-06T22:10:44Z",
"mode": 33188,
"offset": 24037091,
"NumLink": 0,
"digest": "sha256:da8ad230618cacd1466ab9a4162a4b78bafcc18481adf556636c3bb29c9a4e5f"
},
{
"name": "var/lib/dpkg/info/libacl1:amd64.shlibs",
"type": "reg",
"size": 31,
"modtime": "2016-02-06T22:10:44Z",
"mode": 33188,
"offset": 24037397,
"NumLink": 0,
"digest": "sha256:22cf70aad343f840f2ad3e2248b2afa1211b91e83ae25ccdd93c34cfcb64ed1b"
},
{
"name": "var/lib/dpkg/info/libacl1:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2016-02-06T22:10:44Z",
"mode": 33188,
"offset": 24037544,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libapt-pkg5.0:amd64.list",
"type": "reg",
"size": 4833,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24037722,
"NumLink": 0,
"digest": "sha256:f8dd334e23c8030e4860575052965286e08b44db92dd22cb81669f9d9ab65966"
},
{
"name": "var/lib/dpkg/info/libapt-pkg5.0:amd64.md5sums",
"type": "reg",
"size": 3882,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33188,
"offset": 24038309,
"NumLink": 0,
"digest": "sha256:736c62eded3c14d921f9cc373dfbc0ae6a2b0b052c3126e123ea70b45921de98"
},
{
"name": "var/lib/dpkg/info/libapt-pkg5.0:amd64.shlibs",
"type": "reg",
"size": 29,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33188,
"offset": 24039624,
"NumLink": 0,
"digest": "sha256:66acf56dd792547b8f0503be6e9d104da1c208fce8864bbc2b472efc009005f6"
},
{
"name": "var/lib/dpkg/info/libapt-pkg5.0:amd64.symbols",
"type": "reg",
"size": 143029,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33188,
"offset": 24039768,
"NumLink": 0,
"digest": "sha256:aa71b10df877584493174076f143defdcaaf59e4771aaafe34bede8857579ea7"
},
{
"name": "var/lib/dpkg/info/libapt-pkg5.0:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2017-09-13T16:47:33Z",
"mode": 33188,
"offset": 24057089,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libattr1:amd64.list",
"type": "reg",
"size": 324,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24057261,
"NumLink": 0,
"digest": "sha256:0e3ea509a127c52d380f9c63b130034276a4a91ba0abe0a384a6e7bfbecd8b88"
},
{
"name": "var/lib/dpkg/info/libattr1:amd64.md5sums",
"type": "reg",
"size": 369,
"modtime": "2014-09-08T07:27:07Z",
"mode": 33188,
"offset": 24057475,
"NumLink": 0,
"digest": "sha256:8c1f3dde4ab64f0f2f275a58274b92c7ac6503664e35ec68f0b684a60d12f780"
},
{
"name": "var/lib/dpkg/info/libattr1:amd64.shlibs",
"type": "reg",
"size": 81,
"modtime": "2014-09-08T07:27:07Z",
"mode": 33188,
"offset": 24057780,
"NumLink": 0,
"digest": "sha256:748a3ebb90124c9caad8a81d4f484295df4f49ab91c9012b5e7cad01140c88a6"
},
{
"name": "var/lib/dpkg/info/libattr1:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2014-09-08T07:27:07Z",
"mode": 33188,
"offset": 24057943,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libaudit-common.conffiles",
"type": "reg",
"size": 19,
"modtime": "2017-04-12T16:17:21Z",
"mode": 33188,
"offset": 24058120,
"NumLink": 0,
"digest": "sha256:e59079facf60922dd9bef9974c852e6ad3315ca2639141786bb268e8e877bec3"
},
{
"name": "var/lib/dpkg/info/libaudit-common.list",
"type": "reg",
"size": 299,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24058253,
"NumLink": 0,
"digest": "sha256:809f79c7732df66a7252edea0a4ef3388b43e122fedd8a27bf0a90a059c5c3d8"
},
{
"name": "var/lib/dpkg/info/libaudit-common.md5sums",
"type": "reg",
"size": 307,
"modtime": "2017-04-12T16:17:21Z",
"mode": 33188,
"offset": 24058457,
"NumLink": 0,
"digest": "sha256:ec9f52547dddbb491f7347c0c8869c5c77f62e0190d48bcedc5ddb31b7dec556"
},
{
"name": "var/lib/dpkg/info/libaudit1:amd64.list",
"type": "reg",
"size": 280,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24058733,
"NumLink": 0,
"digest": "sha256:af8c5c402019fb6f8eb0afeb14b68d25173855aab601b50c3fedeede3f1bbab5"
},
{
"name": "var/lib/dpkg/info/libaudit1:amd64.md5sums",
"type": "reg",
"size": 290,
"modtime": "2017-04-12T16:17:21Z",
"mode": 33188,
"offset": 24058942,
"NumLink": 0,
"digest": "sha256:c50686eda08e1f881321c160bdd141c8372bb4ead4455878ced47463626fbc95"
},
{
"name": "var/lib/dpkg/info/libaudit1:amd64.shlibs",
"type": "reg",
"size": 21,
"modtime": "2017-04-12T16:17:21Z",
"mode": 33188,
"offset": 24059225,
"NumLink": 0,
"digest": "sha256:7f372d6c7b0b7c8da4b6d8de368a525bf6ecbd2c1508e2a9e78a1b1aeb8684b7"
},
{
"name": "var/lib/dpkg/info/libaudit1:amd64.symbols",
"type": "reg",
"size": 2729,
"modtime": "2017-04-12T16:17:21Z",
"mode": 33188,
"offset": 24059361,
"NumLink": 0,
"digest": "sha256:f536380e2359838d0d7b6e4f39d035fffa27cbaf0ee6c037bc8961dcd109fe35"
},
{
"name": "var/lib/dpkg/info/libaudit1:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2017-04-12T16:17:21Z",
"mode": 33188,
"offset": 24060080,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libblkid1:amd64.list",
"type": "reg",
"size": 280,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24060252,
"NumLink": 0,
"digest": "sha256:6780ed0dca80693cadcbb6c482432ea79e940e32e4022372a0ea1a67475e0d22"
},
{
"name": "var/lib/dpkg/info/libblkid1:amd64.md5sums",
"type": "reg",
"size": 290,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 24060463,
"NumLink": 0,
"digest": "sha256:cbdc8b7425256a16bfb81bcccaf719d5105ef7defa393995222e4a9c5562c72f"
},
{
"name": "var/lib/dpkg/info/libblkid1:amd64.shlibs",
"type": "reg",
"size": 73,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 24060744,
"NumLink": 0,
"digest": "sha256:9c7ba7351473074d5a2f0f23a2378307bfbe271e53d689db16663870b1e7e3aa"
},
{
"name": "var/lib/dpkg/info/libblkid1:amd64.symbols",
"type": "reg",
"size": 4619,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 24060904,
"NumLink": 0,
"digest": "sha256:b951a14c5f41e6ab1aa17a3ca0cffcf5f8d6c88de04ced37d6369cc6af34fbc5"
},
{
"name": "var/lib/dpkg/info/libblkid1:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 24061842,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libbz2-1.0:amd64.list",
"type": "reg",
"size": 316,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24062016,
"NumLink": 0,
"digest": "sha256:dcc4781ab86d80723d1cfe1f0ffe2494bbf07fb9c0f68133289d0f67816b3613"
},
{
"name": "var/lib/dpkg/info/libbz2-1.0:amd64.md5sums",
"type": "reg",
"size": 291,
"modtime": "2017-01-29T18:30:31Z",
"mode": 33188,
"offset": 24062230,
"NumLink": 0,
"digest": "sha256:ca7005e5434d78e7e4eb43e5cb453f72563f1c39cd3a5f63e6fbbd43c29574c5"
},
{
"name": "var/lib/dpkg/info/libbz2-1.0:amd64.shlibs",
"type": "reg",
"size": 22,
"modtime": "2017-01-29T17:57:57Z",
"mode": 33188,
"offset": 24062515,
"NumLink": 0,
"digest": "sha256:a48a7878642bc8269876a391a88dd3be02eeff425924be9462c4306ecccbcb20"
},
{
"name": "var/lib/dpkg/info/libbz2-1.0:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2017-01-29T18:30:31Z",
"mode": 33188,
"offset": 24062655,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libc-bin.conffiles",
"type": "reg",
"size": 103,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 24062826,
"NumLink": 0,
"digest": "sha256:ed1819b3573af7f7dfa94bc34567884dce8f5fabd0ae065a646d4f23d28ade32"
},
{
"name": "var/lib/dpkg/info/libc-bin.list",
"type": "reg",
"size": 1353,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24062997,
"NumLink": 0,
"digest": "sha256:5f95bcc50c60a7da16e8712cf72a7e7eae8a4f89640a790b0f86414ae5498a90"
},
{
"name": "var/lib/dpkg/info/libc-bin.md5sums",
"type": "reg",
"size": 2032,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 24063486,
"NumLink": 0,
"digest": "sha256:39a13558bc6e5e62157aea5f1874eb4b3d4199960efe0556fbc839ab39a67827"
},
{
"name": "var/lib/dpkg/info/libc-bin.postinst",
"type": "reg",
"size": 1222,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33261,
"offset": 24064565,
"NumLink": 0,
"digest": "sha256:47fc92ea75c72d16a695f78d1fc35671020810e4bef594266a269b34d6e9ac9a"
},
{
"name": "var/lib/dpkg/info/libc-bin.triggers",
"type": "reg",
"size": 18,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 24065205,
"NumLink": 0,
"digest": "sha256:3766ef03d6f8c75bc29b5131021ba41e1787667878a97b81264c5fac6ec07ae4"
},
{
"name": "var/lib/dpkg/info/libc6:amd64.conffiles",
"type": "reg",
"size": 40,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 24065345,
"NumLink": 0,
"digest": "sha256:a59a14914231cc9a833ba49b14308e57b755236ff70bc3b0c4ee77b2bd34eed8"
},
{
"name": "var/lib/dpkg/info/libc6:amd64.list",
"type": "reg",
"size": 13478,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24065499,
"NumLink": 0,
"digest": "sha256:510f950eefd185e3b1e8ca219c366602149dccfc13ccd0c1b2bc8b3497b19b29"
},
{
"name": "var/lib/dpkg/info/libc6:amd64.md5sums",
"type": "reg",
"size": 21875,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 24067091,
"NumLink": 0,
"digest": "sha256:7b72ebbbdbef6e0b3516a86fb65d6b7384367b943ee17d8499336efa424bbb71"
},
{
"name": "var/lib/dpkg/info/libc6:amd64.postinst",
"type": "reg",
"size": 10697,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33261,
"offset": 24074198,
"NumLink": 0,
"digest": "sha256:985a06a1f9156657c6a0db76cb2eeae6ceb2878668067c7b05a54e8f44f1ac73"
},
{
"name": "var/lib/dpkg/info/libc6:amd64.postrm",
"type": "reg",
"size": 2186,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33261,
"offset": 24078145,
"NumLink": 0,
"digest": "sha256:19000428e9404e06ed796d7dce5be523f175a88691e101df1fe0b5b3cb3cfe68"
},
{
"name": "var/lib/dpkg/info/libc6:amd64.preinst",
"type": "reg",
"size": 17153,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33261,
"offset": 24079117,
"NumLink": 0,
"digest": "sha256:792222823d198c8bcadfe6753386216da79458f15b78675634fced792929f91e"
},
{
"name": "var/lib/dpkg/info/libc6:amd64.shlibs",
"type": "reg",
"size": 1047,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 24084363,
"NumLink": 0,
"digest": "sha256:005d577db433aef01db36bf3cdd005b18e1f432f594e3399179f27165bcde2b8"
},
{
"name": "var/lib/dpkg/info/libc6:amd64.symbols",
"type": "reg",
"size": 114353,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 24084689,
"NumLink": 0,
"digest": "sha256:aaad67bd69138c3eb8deacd4e39786f2ce2a32fc1a656965f11bcc617587d0e9"
},
{
"name": "var/lib/dpkg/info/libc6:amd64.templates",
"type": "reg",
"size": 77955,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 24100943,
"NumLink": 0,
"digest": "sha256:da65f855fe06ff7bca68e544a08fb5191abd6f68450b6d7e6201d26bc2f2e03e"
},
{
"name": "var/lib/dpkg/info/libc6:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 24128786,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libcap-ng0:amd64.list",
"type": "reg",
"size": 347,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24128960,
"NumLink": 0,
"digest": "sha256:7693823b3ad20f192c8094c97d956b9c74db6589b7bddf8949e8fa74ae377bf9"
},
{
"name": "var/lib/dpkg/info/libcap-ng0:amd64.md5sums",
"type": "reg",
"size": 379,
"modtime": "2016-07-03T19:04:40Z",
"mode": 33188,
"offset": 24129179,
"NumLink": 0,
"digest": "sha256:06038ad6ef856d76ac52a74735cbb48b6aa3b0ab85a0cd1aaf330bcb73895201"
},
{
"name": "var/lib/dpkg/info/libcap-ng0:amd64.shlibs",
"type": "reg",
"size": 23,
"modtime": "2016-07-03T19:04:40Z",
"mode": 33188,
"offset": 24129487,
"NumLink": 0,
"digest": "sha256:a6ed952c5c08c8997ec9e95b301fe30a0f32e984ed47ba51b542e208ee63143f"
},
{
"name": "var/lib/dpkg/info/libcap-ng0:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2016-07-03T19:04:40Z",
"mode": 33188,
"offset": 24129625,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libcomerr2:amd64.list",
"type": "reg",
"size": 247,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24129800,
"NumLink": 0,
"digest": "sha256:9f75eec6de039a666ceb3b8c0e090728947957d50e3dd48336266c4bd24d32b3"
},
{
"name": "var/lib/dpkg/info/libcomerr2:amd64.md5sums",
"type": "reg",
"size": 221,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33188,
"offset": 24130013,
"NumLink": 0,
"digest": "sha256:f76f1ed9c07f3abc32de857fcaf7e3c13b77e27e886a6c72f63c5b571a32654c"
},
{
"name": "var/lib/dpkg/info/libcomerr2:amd64.shlibs",
"type": "reg",
"size": 58,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33188,
"offset": 24130273,
"NumLink": 0,
"digest": "sha256:4b4d72efc9babddd5618d2fbd2b3bb3ba97f06b4e82c8660bf2df6391b137f54"
},
{
"name": "var/lib/dpkg/info/libcomerr2:amd64.symbols",
"type": "reg",
"size": 574,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33188,
"offset": 24130433,
"NumLink": 0,
"digest": "sha256:55c2170f11bf72c7ea81ecf80cd619f1ead728f739bc048ec7989abea0b627dd"
},
{
"name": "var/lib/dpkg/info/libcomerr2:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33188,
"offset": 24130750,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libdb5.3:amd64.list",
"type": "reg",
"size": 346,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24130923,
"NumLink": 0,
"digest": "sha256:3d4faca15ced03057f8bc69053d5e2e67c00703abfcf93632139571546ad916f"
},
{
"name": "var/lib/dpkg/info/libdb5.3:amd64.md5sums",
"type": "reg",
"size": 370,
"modtime": "2017-09-24T07:14:53Z",
"mode": 33188,
"offset": 24131161,
"NumLink": 0,
"digest": "sha256:d1729499e5e3880d6c9fa118f119c908c0591aa46bdb93e734ec9e887a786a65"
},
{
"name": "var/lib/dpkg/info/libdb5.3:amd64.shlibs",
"type": "reg",
"size": 19,
"modtime": "2017-09-24T07:14:53Z",
"mode": 33188,
"offset": 24131487,
"NumLink": 0,
"digest": "sha256:43a64578438bcbe0fa74ce2478b2f913223f79a3f76d18a41a389ec6d7e509d3"
},
{
"name": "var/lib/dpkg/info/libdb5.3:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2017-09-24T07:14:53Z",
"mode": 33188,
"offset": 24131622,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libdebconfclient0:amd64.list",
"type": "reg",
"size": 291,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24131801,
"NumLink": 0,
"digest": "sha256:91c1e72cb0d152067397a31ea09d54b6593c93164a399b8f6be9a0bcddb9e5d0"
},
{
"name": "var/lib/dpkg/info/libdebconfclient0:amd64.md5sums",
"type": "reg",
"size": 240,
"modtime": "2017-04-03T05:58:27Z",
"mode": 33188,
"offset": 24132007,
"NumLink": 0,
"digest": "sha256:17d7b1886ddb3fcdb0bdc192d6bd6c3bfe13425f06119042a0325acdf40da30f"
},
{
"name": "var/lib/dpkg/info/libdebconfclient0:amd64.shlibs",
"type": "reg",
"size": 85,
"modtime": "2017-04-03T05:58:27Z",
"mode": 33188,
"offset": 24132262,
"NumLink": 0,
"digest": "sha256:31978d3a0c5700466d753a9b79e77e61a871511da1b0dbb2b70f696378c94217"
},
{
"name": "var/lib/dpkg/info/libdebconfclient0:amd64.symbols",
"type": "reg",
"size": 143,
"modtime": "2017-04-03T05:58:27Z",
"mode": 33188,
"offset": 24132419,
"NumLink": 0,
"digest": "sha256:e237639741b96561a9a2b619cf681517370df3a03896fa12b247be2b529827a0"
},
{
"name": "var/lib/dpkg/info/libdebconfclient0:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2017-04-03T05:58:27Z",
"mode": 33188,
"offset": 24132610,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libfdisk1:amd64.list",
"type": "reg",
"size": 280,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24132781,
"NumLink": 0,
"digest": "sha256:cf7d82e71a043affb869d05ea4b7eed4f8f56c2f3ffeb4c924fcbf8fb7f9d36e"
},
{
"name": "var/lib/dpkg/info/libfdisk1:amd64.md5sums",
"type": "reg",
"size": 290,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 24132991,
"NumLink": 0,
"digest": "sha256:058eb37fa093aa54b1c4d8de8b22a469debcf5dc9ef179e741819d409ce3ab73"
},
{
"name": "var/lib/dpkg/info/libfdisk1:amd64.shlibs",
"type": "reg",
"size": 75,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 24133272,
"NumLink": 0,
"digest": "sha256:162982062d89eaa9d5eac8ff1977aa687f1eb0640d3ff21ecca969a62bb8a605"
},
{
"name": "var/lib/dpkg/info/libfdisk1:amd64.symbols",
"type": "reg",
"size": 11005,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 24133433,
"NumLink": 0,
"digest": "sha256:4a53c2e609f5a5860dd0485715f6362a0a88fd77628db72cc5e2bf3404385db9"
},
{
"name": "var/lib/dpkg/info/libfdisk1:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 24135118,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libgcc1:amd64.list",
"type": "reg",
"size": 205,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24135289,
"NumLink": 0,
"digest": "sha256:bfd58c1e14511b97df3f8e414924c7759e0da246e1ea62500e9240007c1060dd"
},
{
"name": "var/lib/dpkg/info/libgcc1:amd64.md5sums",
"type": "reg",
"size": 139,
"modtime": "2018-02-14T16:53:20Z",
"mode": 33188,
"offset": 24135485,
"NumLink": 0,
"digest": "sha256:cd76e81c89511ba018765a2b3e6da8de7ee448c6aab0abb110cb0ba9e290c995"
},
{
"name": "var/lib/dpkg/info/libgcc1:amd64.postinst",
"type": "reg",
"size": 180,
"modtime": "2018-02-14T16:53:20Z",
"mode": 33261,
"offset": 24135707,
"NumLink": 0,
"digest": "sha256:91466095137000731e5aaa4c2e0a84b5c5817ef0c3e4152901d2c0dd753d2b90"
},
{
"name": "var/lib/dpkg/info/libgcc1:amd64.shlibs",
"type": "reg",
"size": 19,
"modtime": "2018-02-14T16:53:20Z",
"mode": 33188,
"offset": 24135944,
"NumLink": 0,
"digest": "sha256:bd9a2ed68fc98f63e44063dc4f3f7baaa2a26699e91e00f864d8ae1ae459beee"
},
{
"name": "var/lib/dpkg/info/libgcc1:amd64.symbols",
"type": "reg",
"size": 4360,
"modtime": "2018-02-14T16:53:20Z",
"mode": 33188,
"offset": 24136079,
"NumLink": 0,
"digest": "sha256:1ea41fd012f575401b19ea387b90bed877f20087b3720253828f5a451a0ead8a"
},
{
"name": "var/lib/dpkg/info/libgcc1:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2018-02-14T16:53:20Z",
"mode": 33188,
"offset": 24137047,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libgcrypt20:amd64.list",
"type": "reg",
"size": 439,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24137221,
"NumLink": 0,
"digest": "sha256:91683fea355adec504710db635979e8817691c8cbf8c83cff1b988cbb0092783"
},
{
"name": "var/lib/dpkg/info/libgcrypt20:amd64.md5sums",
"type": "reg",
"size": 577,
"modtime": "2018-06-15T09:58:05Z",
"mode": 33188,
"offset": 24137470,
"NumLink": 0,
"digest": "sha256:69a2e988b1b547f1219f24a0de4f02388f52202474bd125b92d599ebc8d5b7be"
},
{
"name": "var/lib/dpkg/info/libgcrypt20:amd64.shlibs",
"type": "reg",
"size": 85,
"modtime": "2018-06-15T09:58:05Z",
"mode": 33188,
"offset": 24137879,
"NumLink": 0,
"digest": "sha256:9f43b99b16c2a39b2f53ff68fc28c1aec77d7daa459c4ccee2fb319138e3dc84"
},
{
"name": "var/lib/dpkg/info/libgcrypt20:amd64.symbols",
"type": "reg",
"size": 7364,
"modtime": "2018-06-15T09:58:05Z",
"mode": 33188,
"offset": 24138044,
"NumLink": 0,
"digest": "sha256:0c0a991c6f2cc653d05fd1322535590fd83ac863f1bdd9f45bbda58d0c4faa1a"
},
{
"name": "var/lib/dpkg/info/libgcrypt20:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2018-06-15T09:58:05Z",
"mode": 33188,
"offset": 24139220,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libgpg-error0:amd64.list",
"type": "reg",
"size": 2337,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24139397,
"NumLink": 0,
"digest": "sha256:36afe0dc7ee8330b2dc1b1df8b9ea152ec4ad1445ea8862e2d1771ab1725f623"
},
{
"name": "var/lib/dpkg/info/libgpg-error0:amd64.md5sums",
"type": "reg",
"size": 1943,
"modtime": "2017-01-18T16:27:10Z",
"mode": 33188,
"offset": 24139809,
"NumLink": 0,
"digest": "sha256:10d0827517555904cceb0b464dc4a4730aea28f269d7cbce3138268b5b6da215"
},
{
"name": "var/lib/dpkg/info/libgpg-error0:amd64.shlibs",
"type": "reg",
"size": 89,
"modtime": "2017-01-18T16:27:10Z",
"mode": 33188,
"offset": 24140600,
"NumLink": 0,
"digest": "sha256:6369bf8001d9f8e107f7c57e0fa983c27c234db6f67ce6a38e4dc2f9076bfcda"
},
{
"name": "var/lib/dpkg/info/libgpg-error0:amd64.symbols",
"type": "reg",
"size": 3652,
"modtime": "2017-01-18T16:27:10Z",
"mode": 33188,
"offset": 24140763,
"NumLink": 0,
"digest": "sha256:9be5deaeebda64772fdca2136bf03412dd183c255a2d2c8c8dbf97df6ff17817"
},
{
"name": "var/lib/dpkg/info/libgpg-error0:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2017-01-18T16:27:10Z",
"mode": 33188,
"offset": 24141435,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/liblz4-1:amd64.list",
"type": "reg",
"size": 301,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24141607,
"NumLink": 0,
"digest": "sha256:1b8651c732da43e7bbedfb35730a94b753486eea886a6004b3f3aea93a23e13f"
},
{
"name": "var/lib/dpkg/info/liblz4-1:amd64.md5sums",
"type": "reg",
"size": 302,
"modtime": "2016-02-17T15:27:54Z",
"mode": 33188,
"offset": 24141819,
"NumLink": 0,
"digest": "sha256:f490e2a1b302cc5342c931e0aac525fad2e2dd2743d99835ba672c500f55cf43"
},
{
"name": "var/lib/dpkg/info/liblz4-1:amd64.shlibs",
"type": "reg",
"size": 18,
"modtime": "2016-02-17T15:27:54Z",
"mode": 33188,
"offset": 24142101,
"NumLink": 0,
"digest": "sha256:88f3b33028e4581770c7c23cdf757398b80bc672210d8ff5dc9b78aadaf40722"
},
{
"name": "var/lib/dpkg/info/liblz4-1:amd64.symbols",
"type": "reg",
"size": 3436,
"modtime": "2016-02-17T15:27:54Z",
"mode": 33188,
"offset": 24142236,
"NumLink": 0,
"digest": "sha256:60d87d63ced63b4cc5e06660925c131ac795036909f9ea3a35e9e82ce1dbc5aa"
},
{
"name": "var/lib/dpkg/info/liblz4-1:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2016-02-17T15:27:54Z",
"mode": 33188,
"offset": 24142920,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/liblzma5:amd64.list",
"type": "reg",
"size": 419,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24143094,
"NumLink": 0,
"digest": "sha256:3a391825d566d675fddca15f6e4086f4369271bf056ee9d94294146b835c235c"
},
{
"name": "var/lib/dpkg/info/liblzma5:amd64.md5sums",
"type": "reg",
"size": 563,
"modtime": "2016-10-08T13:11:19Z",
"mode": 33188,
"offset": 24143341,
"NumLink": 0,
"digest": "sha256:4cf1bd29dd0f2be41754f6984bce0563d48dddabd94af2cc61c1af496fc99290"
},
{
"name": "var/lib/dpkg/info/liblzma5:amd64.shlibs",
"type": "reg",
"size": 19,
"modtime": "2016-10-08T13:11:19Z",
"mode": 33188,
"offset": 24143746,
"NumLink": 0,
"digest": "sha256:8a99812b1ff7964dc1406e712978d1ebc4e7ef9d74bc1b271c28a2a48446769d"
},
{
"name": "var/lib/dpkg/info/liblzma5:amd64.symbols",
"type": "reg",
"size": 4880,
"modtime": "2016-10-08T13:11:19Z",
"mode": 33188,
"offset": 24143882,
"NumLink": 0,
"digest": "sha256:682a57db44010fb9eeb18ce1a2e7792b2aaf77f0ffd39d95f8a444d43e97c86c"
},
{
"name": "var/lib/dpkg/info/liblzma5:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2016-10-08T13:11:19Z",
"mode": 33188,
"offset": 24144639,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libmount1:amd64.list",
"type": "reg",
"size": 280,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24144813,
"NumLink": 0,
"digest": "sha256:d0f8689fb2b0b1547e30af3725a7be7fcafb94c85206a373d1c14f934f1d458a"
},
{
"name": "var/lib/dpkg/info/libmount1:amd64.md5sums",
"type": "reg",
"size": 290,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 24145023,
"NumLink": 0,
"digest": "sha256:71d40c703c53148c3cb8f6d818da8b5f9b297f3cbcf4f832bcc731bf99fc5d5c"
},
{
"name": "var/lib/dpkg/info/libmount1:amd64.shlibs",
"type": "reg",
"size": 75,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 24145305,
"NumLink": 0,
"digest": "sha256:a524d83582803e9f8c30a1731b71f3a538c74a8dbdce4506922d2ff257674970"
},
{
"name": "var/lib/dpkg/info/libmount1:amd64.symbols",
"type": "reg",
"size": 11419,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 24145470,
"NumLink": 0,
"digest": "sha256:cec9fd5edad1e27fda3d91132929d1559b9ae6bb7df61ad71bb8928e97ee8bc1"
},
{
"name": "var/lib/dpkg/info/libmount1:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 24147308,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libncursesw5:amd64.list",
"type": "reg",
"size": 452,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24147483,
"NumLink": 0,
"digest": "sha256:14d7e39ef1e7a0aa1ebfb268d7e55e8317ef7dd893410351ac024bae982d7ea9"
},
{
"name": "var/lib/dpkg/info/libncursesw5:amd64.md5sums",
"type": "reg",
"size": 300,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 24147698,
"NumLink": 0,
"digest": "sha256:394c6d75586d60423595b6d5cec4e9e822e4d36ca23dbdbb5b602ba3a84baab1"
},
{
"name": "var/lib/dpkg/info/libncursesw5:amd64.shlibs",
"type": "reg",
"size": 128,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 24147961,
"NumLink": 0,
"digest": "sha256:37120c652701ac2173dafe1cde3a1243897f02a48ccb6025dbb1c198afc62445"
},
{
"name": "var/lib/dpkg/info/libncursesw5:amd64.symbols",
"type": "reg",
"size": 25048,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 24148130,
"NumLink": 0,
"digest": "sha256:80047a5dfe0a4aa69614140fd9d270d20a3cfa971b34c3da53fbef887afa585a"
},
{
"name": "var/lib/dpkg/info/libncursesw5:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 24151006,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libpam-modules-bin.list",
"type": "reg",
"size": 692,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24151180,
"NumLink": 0,
"digest": "sha256:df966cbf9d317eac8664ace185fcc8719052f35021d521c74d83c79054fd58a6"
},
{
"name": "var/lib/dpkg/info/libpam-modules-bin.md5sums",
"type": "reg",
"size": 1020,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 24151487,
"NumLink": 0,
"digest": "sha256:0678312e97ff9641033dd232de0c4275f4fb33c976f95a791127ab3ebe35225e"
},
{
"name": "var/lib/dpkg/info/libpam-modules:amd64.conffiles",
"type": "reg",
"size": 214,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 24152083,
"NumLink": 0,
"digest": "sha256:d55c2f2fa42a5a8d21bb599e010aa7053a91156c8523bc54fe6907ddfbf665dd"
},
{
"name": "var/lib/dpkg/info/libpam-modules:amd64.list",
"type": "reg",
"size": 4547,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24152279,
"NumLink": 0,
"digest": "sha256:76d7cfa38780402b874c7125a4597155f4eb73df587b929630963eda56ac9f8c"
},
{
"name": "var/lib/dpkg/info/libpam-modules:amd64.md5sums",
"type": "reg",
"size": 7235,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 24153050,
"NumLink": 0,
"digest": "sha256:e6249c0bb8c62d8fb7508eae5768d38d0c7620f2cc297e9d419ba66266a2a3ee"
},
{
"name": "var/lib/dpkg/info/libpam-modules:amd64.postinst",
"type": "reg",
"size": 706,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33261,
"offset": 24155725,
"NumLink": 0,
"digest": "sha256:a7cc62a56ddb6c83ffa89bc07acc3a39173336c24e030f94359ee202e3c04581"
},
{
"name": "var/lib/dpkg/info/libpam-modules:amd64.postrm",
"type": "reg",
"size": 206,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33261,
"offset": 24156207,
"NumLink": 0,
"digest": "sha256:c1c04914a45a56607aa795b52767684381625c27cb3a8f45302cbfa088f735b5"
},
{
"name": "var/lib/dpkg/info/libpam-modules:amd64.preinst",
"type": "reg",
"size": 259,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33261,
"offset": 24156462,
"NumLink": 0,
"digest": "sha256:bb1e2ecebc040efddf6cd8a31d090bd0f7dd7f7aa897e55b0176c52f2f186c9a"
},
{
"name": "var/lib/dpkg/info/libpam-modules:amd64.templates",
"type": "reg",
"size": 13331,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 24156752,
"NumLink": 0,
"digest": "sha256:492118f3907959065d04da4f35ebcf39f0c80bf68e0c2e3314967f390cd53714"
},
{
"name": "var/lib/dpkg/info/libpam-runtime.conffiles",
"type": "reg",
"size": 31,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 24162918,
"NumLink": 0,
"digest": "sha256:0e5df5e3149674007ef3a300996163a4d8692d21dabe9df527cf6a07e3abd65d"
},
{
"name": "var/lib/dpkg/info/libpam-runtime.list",
"type": "reg",
"size": 8374,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24163062,
"NumLink": 0,
"digest": "sha256:d4f0f038515bd00292f58941c66c08f36505f1d3b8a646644af7169f3670586a"
},
{
"name": "var/lib/dpkg/info/libpam-runtime.md5sums",
"type": "reg",
"size": 7166,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 24164016,
"NumLink": 0,
"digest": "sha256:40df1963a92dd2d90b50962e8b268178e071b61bd7049cc9a2e0135109a747fe"
},
{
"name": "var/lib/dpkg/info/libpam-runtime.postinst",
"type": "reg",
"size": 1282,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33261,
"offset": 24166482,
"NumLink": 0,
"digest": "sha256:d6db2d0dd0999f840faaacc10a2cdc4e6b9227022b2d72da42ee67fe7c5968ec"
},
{
"name": "var/lib/dpkg/info/libpam-runtime.postrm",
"type": "reg",
"size": 517,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33261,
"offset": 24167161,
"NumLink": 0,
"digest": "sha256:dda72491512e6f9757bec477428df23d17cd1f20c9c6c6b72599db270a0e62ea"
},
{
"name": "var/lib/dpkg/info/libpam-runtime.prerm",
"type": "reg",
"size": 92,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33261,
"offset": 24167516,
"NumLink": 0,
"digest": "sha256:b3e3b7ea23ac1d1c74ebc6aa3efee132675f5e69d9e2caba6c23e64a7b94b7f8"
},
{
"name": "var/lib/dpkg/info/libpam-runtime.templates",
"type": "reg",
"size": 34973,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 24167717,
"NumLink": 0,
"digest": "sha256:96fc978c5f7ce82b7aa2ab6a57a91228f44836db4288d95a50f42056c7581b14"
},
{
"name": "var/lib/dpkg/info/libpam0g:amd64.list",
"type": "reg",
"size": 710,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24181173,
"NumLink": 0,
"digest": "sha256:14c598b8be0d1f112802deb1b52843e3d9d9bdcc78f187c7093111c7b33391f8"
},
{
"name": "var/lib/dpkg/info/libpam0g:amd64.md5sums",
"type": "reg",
"size": 865,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 24181476,
"NumLink": 0,
"digest": "sha256:b8bc98dbb552c8df8ef92a791d1d5bc2cf227706e21c5b5bb9738ea1b5654d4b"
},
{
"name": "var/lib/dpkg/info/libpam0g:amd64.postinst",
"type": "reg",
"size": 5194,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33261,
"offset": 24182005,
"NumLink": 0,
"digest": "sha256:b1ea7619ae35ddbee9f58c0f71db9124ea5fab366e61309077dee4ef09ee0174"
},
{
"name": "var/lib/dpkg/info/libpam0g:amd64.postrm",
"type": "reg",
"size": 206,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33261,
"offset": 24184093,
"NumLink": 0,
"digest": "sha256:c1c04914a45a56607aa795b52767684381625c27cb3a8f45302cbfa088f735b5"
},
{
"name": "var/lib/dpkg/info/libpam0g:amd64.shlibs",
"type": "reg",
"size": 60,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 24184344,
"NumLink": 0,
"digest": "sha256:a9c316ab6c53dfa6f54c6eaa85530094f69c5da701e5edba5fad35ba336c2704"
},
{
"name": "var/lib/dpkg/info/libpam0g:amd64.symbols",
"type": "reg",
"size": 2959,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 24184490,
"NumLink": 0,
"digest": "sha256:d031a2a300bdc8769b22fe280cbb8d05fdb812b92a73b8454fe3ded9fe1f021d"
},
{
"name": "var/lib/dpkg/info/libpam0g:amd64.templates",
"type": "reg",
"size": 35685,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 24185131,
"NumLink": 0,
"digest": "sha256:d27358a3c3a7cddd4ba50ab21b8dcf903b0bb32f25c51703bda07d450a3f4b7f"
},
{
"name": "var/lib/dpkg/info/libpam0g:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2017-05-27T15:44:02Z",
"mode": 33188,
"offset": 24199156,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libpcre3:amd64.list",
"type": "reg",
"size": 630,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24199329,
"NumLink": 0,
"digest": "sha256:4a2e555c79ca4430e5833b31e4e8fadb8282bd948756095acd44081a976dfcc0"
},
{
"name": "var/lib/dpkg/info/libpcre3:amd64.md5sums",
"type": "reg",
"size": 707,
"modtime": "2017-03-21T22:03:19Z",
"mode": 33188,
"offset": 24199625,
"NumLink": 0,
"digest": "sha256:b267cc3b7967e3d2776b1e83a22dd8699cc3099874aa6cd7b47998211c41a7c4"
},
{
"name": "var/lib/dpkg/info/libpcre3:amd64.shlibs",
"type": "reg",
"size": 156,
"modtime": "2017-03-21T22:03:19Z",
"mode": 33188,
"offset": 24200093,
"NumLink": 0,
"digest": "sha256:0777f0af691ada09d50ad2634d2eb2a2f29faa1ef79b9be0f5688e568b90dc75"
},
{
"name": "var/lib/dpkg/info/libpcre3:amd64.symbols",
"type": "reg",
"size": 1125,
"modtime": "2017-03-21T22:03:19Z",
"mode": 33188,
"offset": 24200266,
"NumLink": 0,
"digest": "sha256:b6c27cab7115a1677a8bfcd33ad4b5e644aa729086c14e20fba7d8bf44d8a659"
},
{
"name": "var/lib/dpkg/info/libpcre3:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2017-03-21T22:03:19Z",
"mode": 33188,
"offset": 24200667,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libselinux1:amd64.list",
"type": "reg",
"size": 303,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24200842,
"NumLink": 0,
"digest": "sha256:243992305f3d7120e42236255f9ec91df9cfd525ffcb6d1d5098d7e7dbcc7213"
},
{
"name": "var/lib/dpkg/info/libselinux1:amd64.md5sums",
"type": "reg",
"size": 380,
"modtime": "2017-09-24T15:30:16Z",
"mode": 33188,
"offset": 24201052,
"NumLink": 0,
"digest": "sha256:5b4a608204edd53909754f87f898ee8758f058548f2af020d7a77295563de5e9"
},
{
"name": "var/lib/dpkg/info/libselinux1:amd64.shlibs",
"type": "reg",
"size": 61,
"modtime": "2017-09-24T15:30:16Z",
"mode": 33188,
"offset": 24201358,
"NumLink": 0,
"digest": "sha256:f2b70f61e92067b391c00c2c7f7b2f9322c979cf0b8073969f12dda8130c1209"
},
{
"name": "var/lib/dpkg/info/libselinux1:amd64.symbols",
"type": "reg",
"size": 7774,
"modtime": "2017-09-24T15:30:16Z",
"mode": 33188,
"offset": 24201509,
"NumLink": 0,
"digest": "sha256:45c42758b028297f26de8ff6726c232a701c1a337149f6e10d08f50fcb7d67ab"
},
{
"name": "var/lib/dpkg/info/libselinux1:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2017-09-24T15:30:16Z",
"mode": 33188,
"offset": 24203194,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libsemanage-common.conffiles",
"type": "reg",
"size": 27,
"modtime": "2016-12-30T15:42:09Z",
"mode": 33188,
"offset": 24203372,
"NumLink": 0,
"digest": "sha256:e0b0a931b5ae99c4bc030b6ecb183385a46fbd5e55e0e10cce9d5ed1eae556e2"
},
{
"name": "var/lib/dpkg/info/libsemanage-common.list",
"type": "reg",
"size": 332,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24203517,
"NumLink": 0,
"digest": "sha256:efcc339a072893927b9f958a2c0bc7a572c06e8ded38a584aeac11042daa35c4"
},
{
"name": "var/lib/dpkg/info/libsemanage-common.md5sums",
"type": "reg",
"size": 316,
"modtime": "2016-12-30T15:42:09Z",
"mode": 33188,
"offset": 24203732,
"NumLink": 0,
"digest": "sha256:f4273b00cf42096e4fae6cc517c36887f62ba0cb157994d6bf834d3fdf8f0d10"
},
{
"name": "var/lib/dpkg/info/libsemanage1:amd64.list",
"type": "reg",
"size": 267,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24204007,
"NumLink": 0,
"digest": "sha256:61cec03dea647551fb6f821e1943cb5e12d98ab7083369b3637e273bc206f142"
},
{
"name": "var/lib/dpkg/info/libsemanage1:amd64.md5sums",
"type": "reg",
"size": 302,
"modtime": "2016-12-30T15:42:09Z",
"mode": 33188,
"offset": 24204214,
"NumLink": 0,
"digest": "sha256:9e3daa074a4802ec23550997202c21727b53421b347d2d1f21594b5fd48062ad"
},
{
"name": "var/lib/dpkg/info/libsemanage1:amd64.shlibs",
"type": "reg",
"size": 27,
"modtime": "2016-12-30T15:42:09Z",
"mode": 33188,
"offset": 24204494,
"NumLink": 0,
"digest": "sha256:efe84e47b7324e371a1b1263461f16814e0c9e6946e1351c507d025d34acf475"
},
{
"name": "var/lib/dpkg/info/libsemanage1:amd64.symbols",
"type": "reg",
"size": 14021,
"modtime": "2016-12-30T15:42:09Z",
"mode": 33188,
"offset": 24204634,
"NumLink": 0,
"digest": "sha256:b3961b9b87663a5ec73130821a6f55c1eb9f8741c809805dc893d6363253bd58"
},
{
"name": "var/lib/dpkg/info/libsemanage1:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2016-12-30T15:42:09Z",
"mode": 33188,
"offset": 24206155,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libsepol1:amd64.list",
"type": "reg",
"size": 240,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24206328,
"NumLink": 0,
"digest": "sha256:520817a40e203c8c1229c95fd28404339755cf77be8104fa74c10ce6cb3f17c5"
},
{
"name": "var/lib/dpkg/info/libsepol1:amd64.md5sums",
"type": "reg",
"size": 286,
"modtime": "2016-12-03T23:19:56Z",
"mode": 33188,
"offset": 24206535,
"NumLink": 0,
"digest": "sha256:1ac559eb4577215f3e7a264c2f76c440dcc8b5db0fcd4ebd230febe0b8d0c586"
},
{
"name": "var/lib/dpkg/info/libsepol1:amd64.shlibs",
"type": "reg",
"size": 21,
"modtime": "2016-12-03T23:19:56Z",
"mode": 33188,
"offset": 24206814,
"NumLink": 0,
"digest": "sha256:cc698287eb9ae72e133e0c0626577b90bea3ce48a97d3e66f5b64930faed553f"
},
{
"name": "var/lib/dpkg/info/libsepol1:amd64.symbols",
"type": "reg",
"size": 8778,
"modtime": "2016-12-03T23:19:56Z",
"mode": 33188,
"offset": 24206951,
"NumLink": 0,
"digest": "sha256:36f5937b5da6e3caa3656b4b21ef446363313fa285587827f20855714ab3f1cd"
},
{
"name": "var/lib/dpkg/info/libsepol1:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2016-12-03T23:19:56Z",
"mode": 33188,
"offset": 24208263,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libsmartcols1:amd64.list",
"type": "reg",
"size": 304,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24208440,
"NumLink": 0,
"digest": "sha256:0cd160716b9177e16605a23da8290f36f84c720bab0d86f9f927af83e795ef2a"
},
{
"name": "var/lib/dpkg/info/libsmartcols1:amd64.md5sums",
"type": "reg",
"size": 306,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 24208654,
"NumLink": 0,
"digest": "sha256:24eaa7f521a0bb356fab52ee49aa1c449d51246e7a2f28c24e170af01144db6f"
},
{
"name": "var/lib/dpkg/info/libsmartcols1:amd64.shlibs",
"type": "reg",
"size": 91,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 24208939,
"NumLink": 0,
"digest": "sha256:ec414a341698598b15f989a8b16843af57299e1cb21fda64bbf5f35c569202a8"
},
{
"name": "var/lib/dpkg/info/libsmartcols1:amd64.symbols",
"type": "reg",
"size": 6360,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 24209104,
"NumLink": 0,
"digest": "sha256:722622113db03ecb577f0d6b5a5157f2b1c4f5d774da1e3d1134a1f18e48b368"
},
{
"name": "var/lib/dpkg/info/libsmartcols1:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 24210116,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libss2:amd64.list",
"type": "reg",
"size": 225,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24210288,
"NumLink": 0,
"digest": "sha256:bd2cbff7ebeb04d501ae90b49ff4df1c1aa732ef53f42440a141901bf2678292"
},
{
"name": "var/lib/dpkg/info/libss2:amd64.md5sums",
"type": "reg",
"size": 208,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33188,
"offset": 24210495,
"NumLink": 0,
"digest": "sha256:a8d87407d1ae7edf18cdc4ffe8d4c4639585f560af6482e1c02ed4dc99e61fbf"
},
{
"name": "var/lib/dpkg/info/libss2:amd64.shlibs",
"type": "reg",
"size": 44,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33188,
"offset": 24210751,
"NumLink": 0,
"digest": "sha256:fe74b5099d94fe674169a287bcaf1faf7b1c47bd4e0942912045bbf2f07f7d7e"
},
{
"name": "var/lib/dpkg/info/libss2:amd64.symbols",
"type": "reg",
"size": 948,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33188,
"offset": 24210903,
"NumLink": 0,
"digest": "sha256:95b3f9a1df92569d95521970b6cb9bc2bdce224c6e110d4f29257438e83cc952"
},
{
"name": "var/lib/dpkg/info/libss2:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2017-02-01T00:54:55Z",
"mode": 33188,
"offset": 24211308,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libstdc++6:amd64.list",
"type": "reg",
"size": 716,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24211483,
"NumLink": 0,
"digest": "sha256:0e69f4b8a0543c85ca3630f04e065aacd94d2e685c8e087fae406f05292fe565"
},
{
"name": "var/lib/dpkg/info/libstdc++6:amd64.md5sums",
"type": "reg",
"size": 514,
"modtime": "2018-02-14T16:53:20Z",
"mode": 33188,
"offset": 24211754,
"NumLink": 0,
"digest": "sha256:a2507120023b89d1313adc62772d3c33383b0fe687522d701acf92a84aae9352"
},
{
"name": "var/lib/dpkg/info/libstdc++6:amd64.postinst",
"type": "reg",
"size": 523,
"modtime": "2018-02-14T16:53:20Z",
"mode": 33261,
"offset": 24212112,
"NumLink": 0,
"digest": "sha256:d58ab32524b4b72f16d6d6a55c792491bef47487e605b655cf92bd19d1363002"
},
{
"name": "var/lib/dpkg/info/libstdc++6:amd64.prerm",
"type": "reg",
"size": 402,
"modtime": "2018-02-14T16:53:20Z",
"mode": 33261,
"offset": 24212494,
"NumLink": 0,
"digest": "sha256:be4d3d37ee6abd9f99c8eddd0c20e012aaffbdd984fbbc3ed6322adbd32a93f2"
},
{
"name": "var/lib/dpkg/info/libstdc++6:amd64.shlibs",
"type": "reg",
"size": 23,
"modtime": "2018-02-14T16:53:20Z",
"mode": 33188,
"offset": 24212857,
"NumLink": 0,
"digest": "sha256:610aef7f93b6381fbcd118f65502ff420c29f00846ea92e308f1a66b1e34a98d"
},
{
"name": "var/lib/dpkg/info/libstdc++6:amd64.symbols",
"type": "reg",
"size": 369490,
"modtime": "2018-02-14T16:53:20Z",
"mode": 33188,
"offset": 24212995,
"NumLink": 0,
"digest": "sha256:7baf601dddcf028a6542863262791417cf2ba4b2646f823022988ffc65146f0e"
},
{
"name": "var/lib/dpkg/info/libstdc++6:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2018-02-14T16:53:20Z",
"mode": 33188,
"offset": 24241707,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libsystemd0:amd64.list",
"type": "reg",
"size": 253,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24241880,
"NumLink": 0,
"digest": "sha256:cc07699f8e6948f95a506e8bd0eeee0a7a2a4dc0934110a0b2f7ce8dc22c0914"
},
{
"name": "var/lib/dpkg/info/libsystemd0:amd64.md5sums",
"type": "reg",
"size": 226,
"modtime": "2018-06-13T20:20:36Z",
"mode": 33188,
"offset": 24242091,
"NumLink": 0,
"digest": "sha256:fbbd82eb187521829fac79a610e0821ebb14a26e775b70fbe170c03126b0a35b"
},
{
"name": "var/lib/dpkg/info/libsystemd0:amd64.shlibs",
"type": "reg",
"size": 25,
"modtime": "2018-06-13T20:20:36Z",
"mode": 33188,
"offset": 24242353,
"NumLink": 0,
"digest": "sha256:7305604403b49cd3046a4557ea2e0fd922a284501901c382934a6930c6d34175"
},
{
"name": "var/lib/dpkg/info/libsystemd0:amd64.symbols",
"type": "reg",
"size": 19018,
"modtime": "2018-06-13T20:20:36Z",
"mode": 33188,
"offset": 24242492,
"NumLink": 0,
"digest": "sha256:49364dbae4b83985980134598def25260a777915a93f1843eefe640689c4eeae"
},
{
"name": "var/lib/dpkg/info/libsystemd0:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2018-06-13T20:20:36Z",
"mode": 33188,
"offset": 24245204,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libtinfo5:amd64.list",
"type": "reg",
"size": 457,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24245376,
"NumLink": 0,
"digest": "sha256:a0f01eff00a4161b5ad6557223b01fd261da27a523a70a13189a72a308c2d1b1"
},
{
"name": "var/lib/dpkg/info/libtinfo5:amd64.md5sums",
"type": "reg",
"size": 493,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 24245618,
"NumLink": 0,
"digest": "sha256:4d3d4430b4f7545a3adc282e107efa016a1aa9a9959b424e00e2e19706918945"
},
{
"name": "var/lib/dpkg/info/libtinfo5:amd64.shlibs",
"type": "reg",
"size": 93,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 24245979,
"NumLink": 0,
"digest": "sha256:1d1e509ac4df064ec070dae7db743cbbdf4f0e89293c9e36dad136b01c7acae8"
},
{
"name": "var/lib/dpkg/info/libtinfo5:amd64.symbols",
"type": "reg",
"size": 10583,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 24246139,
"NumLink": 0,
"digest": "sha256:31a47ec102f3e749acb433cc824795306a932dfdddc8b07df81260f82be0ad3c"
},
{
"name": "var/lib/dpkg/info/libtinfo5:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 24247745,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libudev1:amd64.list",
"type": "reg",
"size": 237,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24247918,
"NumLink": 0,
"digest": "sha256:6304d6cc70c1fbac022db9e86253dccac0d732dc65dba5d9a3d5180bbcc768f0"
},
{
"name": "var/lib/dpkg/info/libudev1:amd64.md5sums",
"type": "reg",
"size": 216,
"modtime": "2018-06-13T20:20:36Z",
"mode": 33188,
"offset": 24248128,
"NumLink": 0,
"digest": "sha256:808b5d81ac9664efe9106b1a0fe73fdb3cfe5b93abb0ff2b8c552d41e40fe160"
},
{
"name": "var/lib/dpkg/info/libudev1:amd64.shlibs",
"type": "reg",
"size": 49,
"modtime": "2018-06-13T20:20:36Z",
"mode": 33188,
"offset": 24248387,
"NumLink": 0,
"digest": "sha256:1ec36f721c65c10a314a26f2c2eab2e8646738f213c1ea46b3ab5b7032be656e"
},
{
"name": "var/lib/dpkg/info/libudev1:amd64.symbols",
"type": "reg",
"size": 4117,
"modtime": "2018-06-13T20:20:36Z",
"mode": 33188,
"offset": 24248535,
"NumLink": 0,
"digest": "sha256:58bbd25ce4937cdf92003159e262eb853b16d6e2a0dfd203f3e2cc780260ec2f"
},
{
"name": "var/lib/dpkg/info/libudev1:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2018-06-13T20:20:36Z",
"mode": 33188,
"offset": 24249349,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libustr-1.0-1:amd64.list",
"type": "reg",
"size": 361,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24249526,
"NumLink": 0,
"digest": "sha256:ce9393596d841f4e0e573108c148bc013c3707c7847bc11236d834e3fcbfda27"
},
{
"name": "var/lib/dpkg/info/libustr-1.0-1:amd64.md5sums",
"type": "reg",
"size": 385,
"modtime": "2016-11-23T19:59:34Z",
"mode": 33188,
"offset": 24249753,
"NumLink": 0,
"digest": "sha256:4dfa449478b952b012c7c93a19af0f27ee539ee745c1d5f6947b01fed7297379"
},
{
"name": "var/lib/dpkg/info/libustr-1.0-1:amd64.shlibs",
"type": "reg",
"size": 28,
"modtime": "2016-11-23T19:59:34Z",
"mode": 33188,
"offset": 24250069,
"NumLink": 0,
"digest": "sha256:1afc929d34afd105bd39adb703aec7c834edac65652ca6921ef065320811a1da"
},
{
"name": "var/lib/dpkg/info/libustr-1.0-1:amd64.symbols",
"type": "reg",
"size": 18424,
"modtime": "2016-11-23T19:59:34Z",
"mode": 33188,
"offset": 24250210,
"NumLink": 0,
"digest": "sha256:9d5b87acbd80cd5b9e2c496f184353ee33a28425121131dd963cf3625874fcde"
},
{
"name": "var/lib/dpkg/info/libustr-1.0-1:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2016-11-23T19:59:34Z",
"mode": 33188,
"offset": 24252282,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/libuuid1:amd64.list",
"type": "reg",
"size": 274,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24252455,
"NumLink": 0,
"digest": "sha256:2409d77e3d353fc085b09905265dbc76ab7ca8455834f058f2f8c883408a4a14"
},
{
"name": "var/lib/dpkg/info/libuuid1:amd64.md5sums",
"type": "reg",
"size": 286,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 24252664,
"NumLink": 0,
"digest": "sha256:cc145fbd4b2f26de67cfff818b6f793213812e790154502120071910d35099fd"
},
{
"name": "var/lib/dpkg/info/libuuid1:amd64.postinst",
"type": "reg",
"size": 1223,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 24252949,
"NumLink": 0,
"digest": "sha256:7575000f027ad8adff7f6512d37f7c9fa1ccdf9b7703076fe605a1cf70e84d0e"
},
{
"name": "var/lib/dpkg/info/libuuid1:amd64.shlibs",
"type": "reg",
"size": 73,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 24253621,
"NumLink": 0,
"digest": "sha256:fa73b85ed0ceadffbcb5b0449a3c3c506e99fe78a4da2a6e5fec839711ffc06c"
},
{
"name": "var/lib/dpkg/info/libuuid1:amd64.symbols",
"type": "reg",
"size": 655,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 24253781,
"NumLink": 0,
"digest": "sha256:ba5466eb5b879be271cc4783c6d5a549e862799db4064cd4e37a841ccee9dad6"
},
{
"name": "var/lib/dpkg/info/libuuid1:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 24254085,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/info/login.conffiles",
"type": "reg",
"size": 62,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33188,
"offset": 24254252,
"NumLink": 0,
"digest": "sha256:3f7efa04144f955d2ff173cfc54734d007eeefb9b474237040f5a3e74d43be91"
},
{
"name": "var/lib/dpkg/info/login.list",
"type": "reg",
"size": 8764,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24254402,
"NumLink": 0,
"digest": "sha256:d3e2e91cc4337468ae7420115b52a0779e752c059b487af8b2d18573b1b3bf34"
},
{
"name": "var/lib/dpkg/info/login.md5sums",
"type": "reg",
"size": 10068,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33188,
"offset": 24255528,
"NumLink": 0,
"digest": "sha256:74cf33cb4937f5502456d8c4456ed6fc2ed3d432fcc2b7ed6debc819a3da144c"
},
{
"name": "var/lib/dpkg/info/login.postinst",
"type": "reg",
"size": 1214,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 24259071,
"NumLink": 0,
"digest": "sha256:13780f74190f9e9564d2eaf2d4f9d26e09c67b15a61ebae713b070854e263f58"
},
{
"name": "var/lib/dpkg/info/login.preinst",
"type": "reg",
"size": 1048,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 24259587,
"NumLink": 0,
"digest": "sha256:60940bcd58ac8bc2308601c284183358fa5814064ac33e90345f706dc35f473b"
},
{
"name": "var/lib/dpkg/info/lsb-base.list",
"type": "reg",
"size": 319,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24260256,
"NumLink": 0,
"digest": "sha256:cdb270a80c74a84629d08e14fe0e6cefadb23bfe782bcbc828e7c119d5cffc18"
},
{
"name": "var/lib/dpkg/info/lsb-base.md5sums",
"type": "reg",
"size": 419,
"modtime": "2016-11-25T15:15:24Z",
"mode": 33188,
"offset": 24260485,
"NumLink": 0,
"digest": "sha256:e1742e14feb7ee32fe0d7d8b7296b9d68f713dc006ca49aba1a5325598f5ae6b"
},
{
"name": "var/lib/dpkg/info/mawk.list",
"type": "reg",
"size": 772,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24260826,
"NumLink": 0,
"digest": "sha256:362378374a77852a70e0e0131ef2241675623e663817a1167f8db4497ae9e80a"
},
{
"name": "var/lib/dpkg/info/mawk.md5sums",
"type": "reg",
"size": 1239,
"modtime": "2012-03-23T20:15:00Z",
"mode": 33188,
"offset": 24261125,
"NumLink": 0,
"digest": "sha256:66921a131a48fd13c2ea73b30af7873a74b632d845164aefee1f4dae60a0a55e"
},
{
"name": "var/lib/dpkg/info/mawk.postinst",
"type": "reg",
"size": 299,
"modtime": "2012-03-23T20:15:00Z",
"mode": 33261,
"offset": 24261785,
"NumLink": 0,
"digest": "sha256:6374f7996297a6933c9ccae7eecc506a14c85112bf1984c12da1f975dab573b2"
},
{
"name": "var/lib/dpkg/info/mawk.prerm",
"type": "reg",
"size": 104,
"modtime": "2012-03-23T20:15:00Z",
"mode": 33261,
"offset": 24262010,
"NumLink": 0,
"digest": "sha256:7f5bf8abeb16efa0dea7513462bf1a1dbfee6a7945dc39424d87547128d52aca"
},
{
"name": "var/lib/dpkg/info/mount.list",
"type": "reg",
"size": 1131,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24262209,
"NumLink": 0,
"digest": "sha256:0e92f2cd59071401cf442f4425247ec2a370687d275b7a708be3e868fab9cad5"
},
{
"name": "var/lib/dpkg/info/mount.md5sums",
"type": "reg",
"size": 1691,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 24262559,
"NumLink": 0,
"digest": "sha256:891211cc70fcbc9a6acfbe22962959861d83f5d7b75934075a3c3b3c09b7297e"
},
{
"name": "var/lib/dpkg/info/multiarch-support.list",
"type": "reg",
"size": 209,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24263409,
"NumLink": 0,
"digest": "sha256:a07b2062b40da61f3acde302fe39f3702ce54c8cabd2052a550065579d897ea0"
},
{
"name": "var/lib/dpkg/info/multiarch-support.md5sums",
"type": "reg",
"size": 241,
"modtime": "2018-01-14T10:39:44Z",
"mode": 33188,
"offset": 24263593,
"NumLink": 0,
"digest": "sha256:215fc7a9a5de62426e894e146f56220eeed171590d1b01f97bf9535d224bc521"
},
{
"name": "var/lib/dpkg/info/ncurses-base.conffiles",
"type": "reg",
"size": 21,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 24263839,
"NumLink": 0,
"digest": "sha256:b97a47660009db296563cccb2fdce8826e134e3b1bf90248d9c7cf825f06e50a"
},
{
"name": "var/lib/dpkg/info/ncurses-base.list",
"type": "reg",
"size": 1710,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24263977,
"NumLink": 0,
"digest": "sha256:f6c127275c38e828d43d2b3d539e01d38521f5f88b8a4619180d826fda4d9121"
},
{
"name": "var/lib/dpkg/info/ncurses-base.md5sums",
"type": "reg",
"size": 2733,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 24264443,
"NumLink": 0,
"digest": "sha256:0f31ffcd76b076fab4bff5ef9f7a9b8ff672fcdc07b68c5f5760fdaccc456006"
},
{
"name": "var/lib/dpkg/info/ncurses-bin.list",
"type": "reg",
"size": 829,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24265733,
"NumLink": 0,
"digest": "sha256:eba367c1ae9cf3bb8c3a404926611d885a84edef9dce4a7b03f87b7eb6ff3a4e"
},
{
"name": "var/lib/dpkg/info/ncurses-bin.md5sums",
"type": "reg",
"size": 1326,
"modtime": "2017-12-28T09:47:33Z",
"mode": 33188,
"offset": 24266036,
"NumLink": 0,
"digest": "sha256:f75af139f1b948c4d15cc7d94672d15900cef875cbc38b2a05944f8c3800f755"
},
{
"name": "var/lib/dpkg/info/passwd.conffiles",
"type": "reg",
"size": 134,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33188,
"offset": 24266742,
"NumLink": 0,
"digest": "sha256:05cfff1ddfa6cf7fc524250fe96fba6e443e070b15a4014b74c7a4a5d07a12a2"
},
{
"name": "var/lib/dpkg/info/passwd.list",
"type": "reg",
"size": 12326,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24266917,
"NumLink": 0,
"digest": "sha256:3054328771c1a422e3af1132091b55842b27ef0c50de8d95faad643a4a12e895"
},
{
"name": "var/lib/dpkg/info/passwd.md5sums",
"type": "reg",
"size": 20584,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33188,
"offset": 24268517,
"NumLink": 0,
"digest": "sha256:753f53b1dfa3d2dca894c5f6f1a6de36ec7730e48a8fa8cc66c920c99cd38967"
},
{
"name": "var/lib/dpkg/info/passwd.postinst",
"type": "reg",
"size": 1574,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 24275157,
"NumLink": 0,
"digest": "sha256:59054ead4042a38538dde7f4921de2afc56f2c694cf0ed439b3a872c9185c325"
},
{
"name": "var/lib/dpkg/info/passwd.preinst",
"type": "reg",
"size": 1044,
"modtime": "2017-05-17T11:59:59Z",
"mode": 33261,
"offset": 24276104,
"NumLink": 0,
"digest": "sha256:cf6e8844db16f76a96c215d16d674c35d3ff7128fef855c6c690f04f3aa38a64"
},
{
"name": "var/lib/dpkg/info/perl-base.list",
"type": "reg",
"size": 35267,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24276772,
"NumLink": 0,
"digest": "sha256:f5958bd11445205585820d8e92386d6d3388798f23db3dd33f3da11014d2d7fc"
},
{
"name": "var/lib/dpkg/info/perl-base.md5sums",
"type": "reg",
"size": 47930,
"modtime": "2018-06-10T17:37:28Z",
"mode": 33188,
"offset": 24279426,
"NumLink": 0,
"digest": "sha256:ddbed287c72567e570a07ccda9e4caba252e0276241ce0a819d0fc1d623b3395"
},
{
"name": "var/lib/dpkg/info/sed.list",
"type": "reg",
"size": 4224,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24292597,
"NumLink": 0,
"digest": "sha256:5ade75cbe445776676e0d7b189262df16f41b85cc3277075a969540b1d30d2f0"
},
{
"name": "var/lib/dpkg/info/sed.md5sums",
"type": "reg",
"size": 3659,
"modtime": "2017-02-04T15:16:08Z",
"mode": 33188,
"offset": 24293194,
"NumLink": 0,
"digest": "sha256:fe65ce9bfe5ece14806214a7744ef962f38cc54c18da0342a47caf2a58bdbdf8"
},
{
"name": "var/lib/dpkg/info/sensible-utils.list",
"type": "reg",
"size": 1573,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24294631,
"NumLink": 0,
"digest": "sha256:2188612130b563d8c56994d94dee0071b9ce86fa383cba21b0cf281f1d77b031"
},
{
"name": "var/lib/dpkg/info/sensible-utils.md5sums",
"type": "reg",
"size": 1058,
"modtime": "2017-12-20T13:39:04Z",
"mode": 33188,
"offset": 24294982,
"NumLink": 0,
"digest": "sha256:b5edfd558e93ead2a8dce4cbf7977b3a6a252f755293ba1609f71b1768abc302"
},
{
"name": "var/lib/dpkg/info/sensible-utils.postinst",
"type": "reg",
"size": 283,
"modtime": "2017-12-20T13:39:04Z",
"mode": 33261,
"offset": 24295533,
"NumLink": 0,
"digest": "sha256:f679fc646210850d00acf80323dd891df6c1e5dbc73741a5078c1322f609df11"
},
{
"name": "var/lib/dpkg/info/sensible-utils.postrm",
"type": "reg",
"size": 313,
"modtime": "2017-12-20T13:39:04Z",
"mode": 33261,
"offset": 24295822,
"NumLink": 0,
"digest": "sha256:338cfffb71842952258014189d1fad44046418b6fb2d0799cab294bb1619d223"
},
{
"name": "var/lib/dpkg/info/sysvinit-utils.list",
"type": "reg",
"size": 552,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24296120,
"NumLink": 0,
"digest": "sha256:201c4dc0cc171838e01e801bfdf022ac760fef040ffe2a8997d66033430f41ca"
},
{
"name": "var/lib/dpkg/info/sysvinit-utils.md5sums",
"type": "reg",
"size": 792,
"modtime": "2017-02-12T21:55:39Z",
"mode": 33188,
"offset": 24296402,
"NumLink": 0,
"digest": "sha256:7371e60db15baaa34dbc98c231754b058da015e9791e89cf71ceae366b30bcf9"
},
{
"name": "var/lib/dpkg/info/tar.conffiles",
"type": "reg",
"size": 9,
"modtime": "2016-10-30T06:35:31Z",
"mode": 33188,
"offset": 24296908,
"NumLink": 0,
"digest": "sha256:a1edc2c1b98b0d285ada5f8075b6d1dcfe08be7a7c177462542023d293f667e9"
},
{
"name": "var/lib/dpkg/info/tar.list",
"type": "reg",
"size": 4262,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24297029,
"NumLink": 0,
"digest": "sha256:f1d513203e8b5b15753ef78caf5ca6a0db6268a4d4ba27eb6c0797a59a5f3161"
},
{
"name": "var/lib/dpkg/info/tar.md5sums",
"type": "reg",
"size": 3760,
"modtime": "2016-10-30T06:35:31Z",
"mode": 33188,
"offset": 24297652,
"NumLink": 0,
"digest": "sha256:6d445fa9a5cc958326e9d95ecdcd4b7c0a9f9d411852e9cfe35bde24462da261"
},
{
"name": "var/lib/dpkg/info/tar.postinst",
"type": "reg",
"size": 270,
"modtime": "2016-10-30T06:35:31Z",
"mode": 33261,
"offset": 24299129,
"NumLink": 0,
"digest": "sha256:4eed7466e8f44c6b480002d80becc0d998c462b759c0118e285170a93f4b3dca"
},
{
"name": "var/lib/dpkg/info/tar.prerm",
"type": "reg",
"size": 317,
"modtime": "2016-10-30T06:35:31Z",
"mode": 33261,
"offset": 24299393,
"NumLink": 0,
"digest": "sha256:4d2e0a20637a93acbba993f09ed56659869ed3b5749243c2441f8443fa0bdc83"
},
{
"name": "var/lib/dpkg/info/tzdata.config",
"type": "reg",
"size": 10137,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33261,
"offset": 24299704,
"NumLink": 0,
"digest": "sha256:06b375f4c33ed60b3a5c0509e77d6e1268afcdafee18f7a93ac7cb60ffe1fd67"
},
{
"name": "var/lib/dpkg/info/tzdata.list",
"type": "reg",
"size": 73851,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24302050,
"NumLink": 0,
"digest": "sha256:f493aa8eb818ba2b93305d8bdb78380398a02bed5159e1af89b10b99155f4e52"
},
{
"name": "var/lib/dpkg/info/tzdata.md5sums",
"type": "reg",
"size": 55759,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 24311261,
"NumLink": 0,
"digest": "sha256:6a93492f5bf955082bec9f1b49cf3940c9fcb36291de452c1729614ddab0fd56"
},
{
"name": "var/lib/dpkg/info/tzdata.postinst",
"type": "reg",
"size": 1330,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33261,
"offset": 24330605,
"NumLink": 0,
"digest": "sha256:d0b65a6ef298850b186a12b84cdeff63f79b272a259b2f4495aca6f3e14eed86"
},
{
"name": "var/lib/dpkg/info/tzdata.postrm",
"type": "reg",
"size": 298,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33261,
"offset": 24331329,
"NumLink": 0,
"digest": "sha256:e64bf8cc4b58a0536bb10676fc0b9e59a00b8451b76be07cedb2f7a660e49443"
},
{
"name": "var/lib/dpkg/info/tzdata.templates",
"type": "reg",
"size": 267379,
"modtime": "2018-05-04T18:22:35Z",
"mode": 33188,
"offset": 24331619,
"NumLink": 0,
"digest": "sha256:3bd45d8ada7d2659c953df98d4f8c5ebf0988fe1597675614610c6be9e3bc768"
},
{
"name": "var/lib/dpkg/info/util-linux.conffiles",
"type": "reg",
"size": 84,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 24387384,
"NumLink": 0,
"digest": "sha256:ab71f6f05a3f0a7cb78fe1ec8c90af9cd821e9f66bcbde54cd6769696300317f"
},
{
"name": "var/lib/dpkg/info/util-linux.list",
"type": "reg",
"size": 10638,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24387550,
"NumLink": 0,
"digest": "sha256:1151138a9f9dd100ca903a2eedd17d89a8f1dc0cf3e7c1fb28b8efa928b2101d"
},
{
"name": "var/lib/dpkg/info/util-linux.md5sums",
"type": "reg",
"size": 19143,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33188,
"offset": 24389331,
"NumLink": 0,
"digest": "sha256:8e3d967d38b96c3898bab4656a4aa000c44d3b16184d2498b29f92b6aed3310e"
},
{
"name": "var/lib/dpkg/info/util-linux.postinst",
"type": "reg",
"size": 1500,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 24396523,
"NumLink": 0,
"digest": "sha256:f694e5496272871d77a31cbb471c96c8a4de630b62d90b1c1a03cceeda2513a7"
},
{
"name": "var/lib/dpkg/info/util-linux.postrm",
"type": "reg",
"size": 148,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 24397317,
"NumLink": 0,
"digest": "sha256:e6a730615e91fb9c7a3729817fc8208e889cd52aa80d31dbb3adf993711a9a8b"
},
{
"name": "var/lib/dpkg/info/util-linux.preinst",
"type": "reg",
"size": 205,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 24397547,
"NumLink": 0,
"digest": "sha256:5cc1c06e4e2aee2896e0fc0e6aae4a18070677a42fe98cb84e02007e83cbd6f0"
},
{
"name": "var/lib/dpkg/info/util-linux.prerm",
"type": "reg",
"size": 192,
"modtime": "2018-03-07T18:29:09Z",
"mode": 33261,
"offset": 24397815,
"NumLink": 0,
"digest": "sha256:75c29194452a7242c7363da15ce655cca80fdf31d31a1836e820d74e5fc5381d"
},
{
"name": "var/lib/dpkg/info/zlib1g:amd64.list",
"type": "reg",
"size": 260,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24398048,
"NumLink": 0,
"digest": "sha256:e60913bb8b589fb1d44cd9b89f1bb69096dd12c93041242ecc30f1093d2e1266"
},
{
"name": "var/lib/dpkg/info/zlib1g:amd64.md5sums",
"type": "reg",
"size": 277,
"modtime": "2017-01-29T17:22:23Z",
"mode": 33188,
"offset": 24398257,
"NumLink": 0,
"digest": "sha256:b66744b6adc964b7ef438a144ed211fa482a3183352a3aef4036fccf95df05d1"
},
{
"name": "var/lib/dpkg/info/zlib1g:amd64.shlibs",
"type": "reg",
"size": 83,
"modtime": "2017-01-29T17:22:23Z",
"mode": 33188,
"offset": 24398539,
"NumLink": 0,
"digest": "sha256:b83364c157bf331505e018105935763d36db4e070e26d1f7d6c08ea576fc7d5c"
},
{
"name": "var/lib/dpkg/info/zlib1g:amd64.symbols",
"type": "reg",
"size": 2752,
"modtime": "2017-01-29T17:22:23Z",
"mode": 33188,
"offset": 24398706,
"NumLink": 0,
"digest": "sha256:01394311eb033a90295b3de0e879a126e5f0d65250cb06dec4c239e57fbedff0"
},
{
"name": "var/lib/dpkg/info/zlib1g:amd64.triggers",
"type": "reg",
"size": 60,
"modtime": "2017-01-29T17:22:23Z",
"mode": 33188,
"offset": 24399426,
"NumLink": 0,
"digest": "sha256:f2bec0f57ef529571abb4370d4e3cfa911ae3606a0d31559bca5980c0a1de91e"
},
{
"name": "var/lib/dpkg/lock",
"type": "reg",
"modtime": "2018-10-11T00:00:00Z",
"mode": 33184,
"NumLink": 0,
"digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"name": "var/lib/dpkg/parts/",
"type": "dir",
"modtime": "2018-06-26T10:28:08Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/lib/dpkg/statoverride",
"type": "reg",
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"NumLink": 0,
"digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"name": "var/lib/dpkg/status",
"type": "reg",
"size": 68838,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24399656,
"NumLink": 0,
"digest": "sha256:c09e66b2e4369263618d407aaa41a5cd56b41edfc6f9a52036f41d6e20c7e44a"
},
{
"name": "var/lib/dpkg/status-old",
"type": "reg",
"size": 68559,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24420214,
"NumLink": 0,
"digest": "sha256:f9aab52daeabfffab21c128e899a2ad3dfaa28de29eaf97a1d0df2ac7a59c18d"
},
{
"name": "var/lib/dpkg/triggers/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/lib/dpkg/triggers/Lock",
"type": "reg",
"modtime": "2018-10-11T00:00:00Z",
"mode": 33152,
"NumLink": 0,
"digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"name": "var/lib/dpkg/triggers/Unincorp",
"type": "reg",
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"NumLink": 0,
"digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"name": "var/lib/dpkg/triggers/ldconfig",
"type": "reg",
"size": 9,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24440548,
"NumLink": 0,
"digest": "sha256:e1919c8b2afb858bd2aadc7ccb27c847b3b687ed9dffe00219d645d0c30b8b83"
},
{
"name": "var/lib/dpkg/updates/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/lib/misc/",
"type": "dir",
"modtime": "2018-06-26T12:03:08Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/lib/pam/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/lib/pam/account",
"type": "reg",
"size": 76,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24440733,
"NumLink": 0,
"digest": "sha256:9f0f66ec5d201dd496bda06760e5871fa68e349fb9d423eac447d332652f6f13"
},
{
"name": "var/lib/pam/auth",
"type": "reg",
"size": 68,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24440906,
"NumLink": 0,
"digest": "sha256:6f405869d7e61b56e8b4c6eab08c60c538dc24853867655e208b09778d656aab"
},
{
"name": "var/lib/pam/password",
"type": "reg",
"size": 69,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24441075,
"NumLink": 0,
"digest": "sha256:78f7673a76134223418cd0b3479b892bb260a7d6b734bfc9caca62edcfb22d7a"
},
{
"name": "var/lib/pam/seen",
"type": "reg",
"size": 5,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24441240,
"NumLink": 0,
"digest": "sha256:d66f691617a7b447171b7586b8f16741a023a810f1307542c254053318f19ca0"
},
{
"name": "var/lib/pam/session",
"type": "reg",
"size": 75,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24441349,
"NumLink": 0,
"digest": "sha256:4a0e1452e4462ee2c1337989554acb3fe7a2473086870ec5b16e46aceb48e0e1"
},
{
"name": "var/lib/pam/session-noninteractive",
"type": "reg",
"size": 75,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24441526,
"NumLink": 0,
"digest": "sha256:4a0e1452e4462ee2c1337989554acb3fe7a2473086870ec5b16e46aceb48e0e1"
},
{
"name": "var/lib/systemd/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/lib/systemd/deb-systemd-helper-enabled/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/lib/systemd/deb-systemd-helper-enabled/apt-daily-upgrade.timer.dsh-also",
"type": "reg",
"size": 64,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24441766,
"NumLink": 0,
"digest": "sha256:05e0a72e601e00feaeff5d237f892b5f891cbb9c156fc5da667ac56c25f20fdf"
},
{
"name": "var/lib/systemd/deb-systemd-helper-enabled/apt-daily.timer.dsh-also",
"type": "reg",
"size": 56,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24441940,
"NumLink": 0,
"digest": "sha256:71649d5e78c0ba864daf3778e8d90a3707b88c587a4634cba67aa9094def7750"
},
{
"name": "var/lib/systemd/deb-systemd-helper-enabled/timers.target.wants/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/lib/systemd/deb-systemd-helper-enabled/timers.target.wants/apt-daily-upgrade.timer",
"type": "reg",
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"NumLink": 0,
"digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"name": "var/lib/systemd/deb-systemd-helper-enabled/timers.target.wants/apt-daily.timer",
"type": "reg",
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"NumLink": 0,
"digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"name": "var/local/",
"type": "dir",
"modtime": "2018-06-26T12:03:08Z",
"mode": 17917,
"gid": 50,
"NumLink": 0
},
{
"name": "var/lock",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "/run/lock",
"mode": 41471,
"NumLink": 0
},
{
"name": "var/log/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/log/apt/",
"type": "dir",
"modtime": "2017-09-13T16:47:33Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/log/btmp",
"type": "reg",
"modtime": "2018-10-11T00:00:00Z",
"mode": 33200,
"gid": 43,
"NumLink": 0,
"digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"name": "var/log/faillog",
"type": "reg",
"size": 3232,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33188,
"offset": 24442276,
"NumLink": 0,
"digest": "sha256:df66d6e43afb0468eda3149e5eaaffda44271cf157a9d9ea4ff6b6dafccb030c"
},
{
"name": "var/log/lastlog",
"type": "reg",
"size": 29492,
"modtime": "2018-10-11T00:00:00Z",
"mode": 33204,
"gid": 43,
"offset": 24442396,
"NumLink": 0,
"digest": "sha256:a04ece22923d2adaafa89580dbe2851cc93eee9f1c8f43264638d002229f2cfe"
},
{
"name": "var/log/wtmp",
"type": "reg",
"modtime": "2018-10-11T00:00:00Z",
"mode": 33204,
"gid": 43,
"NumLink": 0,
"digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"name": "var/mail/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 17917,
"gid": 8,
"NumLink": 0
},
{
"name": "var/opt/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/run",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "/run",
"mode": 41471,
"NumLink": 0
},
{
"name": "var/spool/",
"type": "dir",
"modtime": "2018-10-11T00:00:00Z",
"mode": 16877,
"NumLink": 0
},
{
"name": "var/spool/mail",
"type": "symlink",
"modtime": "2018-10-11T00:00:00Z",
"linkName": "../mail",
"mode": 41471,
"NumLink": 0
},
{
"name": "var/tmp/",
"type": "dir",
"modtime": "2018-06-26T12:03:08Z",
"mode": 17407,
"NumLink": 0
}
]
}
================================================
FILE: pkg/store/daemonstore.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package store
import (
"context"
"github.com/containerd/nydus-snapshotter/pkg/daemon"
"github.com/containerd/nydus-snapshotter/pkg/rafs"
)
type DaemonRafsStore struct {
db *Database // save daemons in database
}
func NewDaemonRafsStore(db *Database) (*DaemonRafsStore, error) {
return &DaemonRafsStore{
db: db,
}, nil
}
// If the daemon is inserted to DB before, return error ErrAlreadyExisted.
func (s *DaemonRafsStore) AddDaemon(d *daemon.Daemon) error {
// Save daemon info in case snapshotter restarts so that we can restore the
// daemon states and reconnect the daemons.
return s.db.SaveDaemon(context.TODO(), d)
}
func (s *DaemonRafsStore) UpdateDaemon(d *daemon.Daemon) error {
return s.db.UpdateDaemon(context.TODO(), d)
}
func (s *DaemonRafsStore) DeleteDaemon(id string) error {
return s.db.DeleteDaemon(context.TODO(), id)
}
func (s *DaemonRafsStore) WalkDaemons(ctx context.Context, cb func(d *daemon.ConfigState) error) error {
return s.db.WalkDaemons(ctx, cb)
}
func (s *DaemonRafsStore) CleanupDaemons(ctx context.Context) error {
return s.db.CleanupDaemons(ctx)
}
func (s *DaemonRafsStore) AddRafsInstance(r *rafs.Rafs) error {
return s.db.AddRafsInstance(context.TODO(), r)
}
func (s *DaemonRafsStore) UpdateRafsInstance(r *rafs.Rafs) error {
return s.db.UpdateRafsInstance(context.TODO(), r)
}
func (s *DaemonRafsStore) DeleteRafsInstance(snapshotID string) error {
return s.db.DeleteRafsInstance(context.TODO(), snapshotID)
}
func (s *DaemonRafsStore) WalkRafsInstances(ctx context.Context, cb func(*rafs.Rafs) error) error {
return s.db.WalkRafsInstances(ctx, cb)
}
func (s *DaemonRafsStore) NextInstanceSeq() (uint64, error) {
return s.db.NextInstanceSeq()
}
================================================
FILE: pkg/store/database.go
================================================
/*
* Copyright (c) 2021. Ant Financial. All rights reserved.
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package store
import (
"context"
"encoding/json"
"os"
"path/filepath"
"time"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/daemon"
"github.com/containerd/nydus-snapshotter/pkg/errdefs"
"github.com/containerd/nydus-snapshotter/pkg/rafs"
"github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
)
const (
databaseFileName = "nydus.db"
)
// Bucket names:
// Buckets hierarchy:
// - v1:
// - daemons
// - instances
var (
v1RootBucket = []byte("v1")
// Nydusd daemon instances.
// A daemon may host one (dedicated mode) or more (shared mode) RAFS filesystem instances.
versionKey = []byte("version")
daemonsBucket = []byte("daemons")
// RAFS filesystem instances.
// A RAFS filesystem may have associated daemon or not.
instancesBucket = []byte("instances")
)
// Database keeps infos that need to survive among snapshotter restart
type Database struct {
db *bolt.DB
}
// NewDatabase creates a new or open existing database file
func NewDatabase(rootDir string) (*Database, error) {
f := filepath.Join(rootDir, databaseFileName)
if err := ensureDirectory(filepath.Dir(f)); err != nil {
return nil, err
}
opts := bolt.Options{Timeout: time.Second * 4}
db, err := bolt.Open(f, 0600, &opts)
if err != nil {
return nil, err
}
d := &Database{db: db}
if err := d.initDatabase(); err != nil {
return nil, errors.Wrap(err, "failed to initialize database")
}
return d, nil
}
func ensureDirectory(dir string) error {
if _, err := os.Stat(dir); os.IsNotExist(err) {
return os.MkdirAll(dir, 0700)
}
return nil
}
func getDaemonsBucket(tx *bolt.Tx) *bolt.Bucket {
bucket := tx.Bucket(v1RootBucket)
return bucket.Bucket(daemonsBucket)
}
func getInstancesBucket(tx *bolt.Tx) *bolt.Bucket {
bucket := tx.Bucket(v1RootBucket)
return bucket.Bucket(instancesBucket)
}
func updateObject(bucket *bolt.Bucket, key string, obj interface{}) error {
keyBytes := []byte(key)
value, err := json.Marshal(obj)
if err != nil {
return errors.Wrapf(err, "marshall key %s", key)
}
if err := bucket.Put(keyBytes, value); err != nil {
return errors.Wrapf(err, "put key %s", key)
}
return nil
}
func putObject(bucket *bolt.Bucket, key string, obj interface{}) error {
keyBytes := []byte(key)
if bucket.Get(keyBytes) != nil {
return errors.Errorf("object with key %q already exists", key)
}
value, err := json.Marshal(obj)
if err != nil {
return errors.Wrapf(err, "marshall %s", key)
}
if err := bucket.Put(keyBytes, value); err != nil {
return errors.Wrapf(err, "put key %s", key)
}
return nil
}
// A basic wrapper to retrieve a object from bucket.
func getObject(bucket *bolt.Bucket, key string, obj interface{}) error {
if obj == nil {
return errdefs.ErrInvalidArgument
}
value := bucket.Get([]byte(key))
if value == nil {
return errdefs.ErrNotFound
}
if err := json.Unmarshal(value, obj); err != nil {
return errors.Wrapf(err, "unmarshall %s", key)
}
return nil
}
func (db *Database) initDatabase() error {
var notV1 = false
var version string
err := db.db.Update(func(tx *bolt.Tx) error {
bk := tx.Bucket(v1RootBucket)
if bk == nil {
notV1 = true
}
// Must create v1 bucket
bk, err := tx.CreateBucketIfNotExists(v1RootBucket)
if err != nil {
return err
}
if _, err := bk.CreateBucketIfNotExists(daemonsBucket); err != nil {
return errors.Wrapf(err, "bucket %s", daemonsBucket)
}
if _, err := bk.CreateBucketIfNotExists(instancesBucket); err != nil {
return errors.Wrapf(err, "bucket %s", instancesBucket)
}
if val := bk.Get(versionKey); val == nil {
version = "v1.0"
} else {
version = string(val)
}
return nil
})
if err != nil {
return err
}
if notV1 {
if err := db.tryTranslateRecords(); err != nil && !errors.Is(err, errdefs.ErrNotFound) {
return errors.Wrapf(err, "convert old database")
}
}
if version == "v1.0" {
if err := db.tryUpgradeRecords(version); err != nil && !errors.Is(err, errdefs.ErrNotFound) {
return errors.Wrapf(err, "convert old database")
}
}
return nil
}
func (db *Database) Close() error {
err := db.db.Close()
if err != nil {
return errors.Wrapf(err, "failed to close boltdb")
}
return nil
}
func (db *Database) SaveDaemon(_ context.Context, d *daemon.Daemon) error {
return db.db.Update(func(tx *bolt.Tx) error {
bucket := getDaemonsBucket(tx)
var existing daemon.ConfigState
if err := getObject(bucket, d.ID(), &existing); err == nil {
return errdefs.ErrAlreadyExists
}
return putObject(bucket, d.ID(), d.States)
})
}
func (db *Database) UpdateDaemon(_ context.Context, d *daemon.Daemon) error {
return db.db.Update(func(tx *bolt.Tx) error {
bucket := getDaemonsBucket(tx)
var existing daemon.ConfigState
if err := getObject(bucket, d.ID(), &existing); err != nil {
return err
}
return updateObject(bucket, d.ID(), d.States)
})
}
func (db *Database) DeleteDaemon(_ context.Context, id string) error {
return db.db.Update(func(tx *bolt.Tx) error {
bucket := getDaemonsBucket(tx)
if err := bucket.Delete([]byte(id)); err != nil {
return errors.Wrapf(err, "delete daemon %s", id)
}
return nil
})
}
// Cleanup deletes all daemon records
func (db *Database) CleanupDaemons(_ context.Context) error {
return db.db.Update(func(tx *bolt.Tx) error {
bucket := getDaemonsBucket(tx)
return bucket.ForEach(func(k, _ []byte) error {
return bucket.Delete(k)
})
})
}
func (db *Database) WalkDaemons(_ context.Context, cb func(info *daemon.ConfigState) error) error {
return db.db.View(func(tx *bolt.Tx) error {
bucket := getDaemonsBucket(tx)
return bucket.ForEach(func(key, value []byte) error {
states := &daemon.ConfigState{}
if err := json.Unmarshal(value, states); err != nil {
return errors.Wrapf(err, "unmarshal %s", key)
}
return cb(states)
})
})
}
// WalkDaemons iterates all daemon records and invoke callback on each
func (db *Database) WalkRafsInstances(_ context.Context, cb func(r *rafs.Rafs) error) error {
return db.db.View(func(tx *bolt.Tx) error {
bucket := getInstancesBucket(tx)
return bucket.ForEach(func(key, value []byte) error {
instance := &rafs.Rafs{}
if err := json.Unmarshal(value, instance); err != nil {
return errors.Wrapf(err, "unmarshal %s", key)
}
return cb(instance)
})
})
}
func (db *Database) AddRafsInstance(_ context.Context, instance *rafs.Rafs) error {
return db.db.Update(func(tx *bolt.Tx) error {
bucket := getInstancesBucket(tx)
return putObject(bucket, instance.SnapshotID, instance)
})
}
func (db *Database) UpdateRafsInstance(_ context.Context, instance *rafs.Rafs) error {
return db.db.Update(func(tx *bolt.Tx) error {
bucket := getInstancesBucket(tx)
return updateObject(bucket, instance.SnapshotID, instance)
})
}
func (db *Database) DeleteRafsInstance(_ context.Context, snapshotID string) error {
return db.db.Update(func(tx *bolt.Tx) error {
bucket := getInstancesBucket(tx)
if err := bucket.Delete([]byte(snapshotID)); err != nil {
return errors.Wrapf(err, "instance snapshot ID %s", snapshotID)
}
return nil
})
}
func (db *Database) NextInstanceSeq() (uint64, error) {
tx, err := db.db.Begin(true)
if err != nil {
return 0, errors.New("failed to start transaction")
}
defer func() {
if err != nil {
if err := tx.Rollback(); err != nil {
log.L.WithError(err).Errorf("Rollback error when getting next sequence")
}
}
}()
bk := getInstancesBucket(tx)
if bk == nil {
return 0, errdefs.ErrNotFound
}
seq, err := bk.NextSequence()
if err != nil {
return 0, err
}
if err := tx.Commit(); err != nil {
return 0, err
}
return seq, nil
}
================================================
FILE: pkg/store/database_compat.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package store
import (
"context"
"encoding/json"
"io"
"os"
"path"
"path/filepath"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/config"
"github.com/containerd/nydus-snapshotter/pkg/daemon"
"github.com/containerd/nydus-snapshotter/pkg/errdefs"
"github.com/containerd/nydus-snapshotter/pkg/rafs"
"github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
)
const SharedNydusDaemonID = "shared_daemon"
type CompatDaemon struct {
ID string
SnapshotID string
ConfigDir string
SocketDir string
LogDir string
LogLevel string
LogToStdout bool
SnapshotDir string
Pid int
ImageID string
FsDriver string
APISock *string
RootMountPoint *string
CustomMountPoint *string
}
func (db *Database) WalkCompatDaemons(_ context.Context, handler func(cd *CompatDaemon) error) error {
return db.db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket(daemonsBucket)
if bucket == nil {
return errdefs.ErrNotFound
}
return bucket.ForEach(func(key, value []byte) error {
d := &CompatDaemon{}
if err := json.Unmarshal(value, d); err != nil {
return errors.Wrapf(err, "unmarshal %s", key)
}
return handler(d)
})
})
}
// Snapshotter v0.3.0 and lower store nydusd and rafs instance configurations in the different folders.
func RedirectInstanceConfig(newPath, oldPath string) error {
oldConfig, err := os.Open(oldPath)
if err != nil {
return err
}
defer oldConfig.Close()
err = os.MkdirAll(filepath.Dir(newPath), 0700)
if err != nil {
return err
}
newConfig, err := os.Create(newPath)
if err != nil {
return err
}
defer newConfig.Close()
_, err = io.Copy(newConfig, oldConfig)
if err != nil {
return err
}
return nil
}
func (db *Database) tryTranslateRecords() error {
log.L.Info("Trying to translate bucket records...")
daemons := make([]*CompatDaemon, 0)
err := db.WalkCompatDaemons(context.TODO(), func(cd *CompatDaemon) error {
daemons = append(daemons, cd)
return nil
})
if err != nil {
return err
}
var sharedMode = false
var configDir string
// Scan all the daemons if it is started as shared mode last time
for _, d := range daemons {
if d.ID == SharedNydusDaemonID {
sharedMode = true
} else if configDir == "" {
configDir = d.ConfigDir
}
}
for _, d := range daemons {
var mp string
var newDaemon *daemon.Daemon
if sharedMode {
if d.ID == SharedNydusDaemonID {
oldConfig := path.Join(configDir, "config.json")
newConfig := filepath.Join(filepath.Dir(configDir), SharedNydusDaemonID, "config.json")
newDaemon = &daemon.Daemon{
States: daemon.ConfigState{
ID: d.ID,
ProcessID: d.Pid,
APISocket: path.Join(d.SnapshotDir, "api.sock"),
FsDriver: d.FsDriver,
Mountpoint: *d.RootMountPoint,
LogDir: d.LogDir,
LogLevel: d.LogLevel,
// Shared daemon does not need config file when start
ConfigDir: filepath.Dir(newConfig),
}}
if err := RedirectInstanceConfig(newConfig, oldConfig); err != nil {
log.L.WithError(err).Warnf("Redirect configuration from %s to %s", oldConfig, newConfig)
}
} else {
// Redirect rafs instance configuration files. We have to do it here to
// prevent scattering compatibility code anywhere.
oldConfig := path.Join(d.ConfigDir, "config.json")
newConfig := path.Join(filepath.Dir(d.ConfigDir), SharedNydusDaemonID,
d.SnapshotID, "config.json")
log.L.Infof("Redirect configuration to %s", newConfig)
if err := RedirectInstanceConfig(newConfig, oldConfig); err != nil {
log.L.WithError(err).Warnf("Redirect configuration from %s to %s", oldConfig, newConfig)
}
}
} else if !sharedMode {
mp = *d.CustomMountPoint
newDaemon = &daemon.Daemon{
States: daemon.ConfigState{
ID: d.ID,
ProcessID: d.Pid,
APISocket: path.Join(d.SocketDir, "api.sock"),
FsDriver: d.FsDriver,
Mountpoint: mp,
LogDir: d.LogDir,
LogLevel: d.LogLevel,
ConfigDir: d.ConfigDir,
}}
}
var instance *rafs.Rafs
if !sharedMode {
instance = &rafs.Rafs{
SnapshotID: d.SnapshotID,
ImageID: d.ImageID,
DaemonID: d.ID,
SnapshotDir: path.Join(d.SnapshotDir, d.SnapshotID),
Mountpoint: path.Join(d.SnapshotDir, d.SnapshotID, "mnt"),
}
} else if sharedMode && d.ID != SharedNydusDaemonID {
instance = &rafs.Rafs{
SnapshotID: d.SnapshotID,
ImageID: d.ImageID,
DaemonID: SharedNydusDaemonID,
SnapshotDir: path.Join(d.SnapshotDir, d.SnapshotID),
Mountpoint: path.Join(*d.RootMountPoint, d.SnapshotID),
}
}
if newDaemon != nil {
if err := db.SaveDaemon(context.TODO(), newDaemon); err != nil {
return err
}
}
if instance != nil {
if err := db.AddRafsInstance(context.TODO(), instance); err != nil {
return err
}
}
}
return nil
}
func (db *Database) tryUpgradeRecords(version string) error {
log.L.Infof("Trying to update bucket records from %s to v1.1 ...", version)
if version == "v1.0" {
daemons := make([]*daemon.ConfigState, 0)
err := db.WalkDaemons(context.TODO(), func(cd *daemon.ConfigState) error {
daemons = append(daemons, cd)
return nil
})
if err != nil {
return err
}
for _, d := range daemons {
if d.DaemonMode == "" {
switch d.FsDriver {
case config.FsDriverFscache:
d.DaemonMode = config.DaemonModeShared
case config.FsDriverFusedev:
if d.Mountpoint == config.GetRootMountpoint() {
d.DaemonMode = config.DaemonModeShared
} else {
d.DaemonMode = config.DaemonModeDedicated
}
}
var daemon = daemon.Daemon{States: *d}
err := db.UpdateDaemon(context.TODO(), &daemon)
if err != nil {
return errors.Wrapf(err, "upgrade daemon instance %s", d.ID)
}
}
}
}
err := db.db.Update(func(tx *bolt.Tx) error {
bk := tx.Bucket(v1RootBucket)
if bk != nil {
return bk.Put(versionKey, []byte("v1.1"))
}
return errors.New("boltdb is not v1")
})
return err
}
================================================
FILE: pkg/store/database_test.go
================================================
package store
import (
"context"
"encoding/json"
"fmt"
"os"
"path"
"path/filepath"
"testing"
"time"
"github.com/containerd/nydus-snapshotter/config"
"github.com/containerd/nydus-snapshotter/pkg/daemon"
"github.com/containerd/nydus-snapshotter/pkg/rafs"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
bolt "go.etcd.io/bbolt"
)
func Test_daemon(t *testing.T) {
rootDir := "testdata/snapshot"
err := os.MkdirAll(rootDir, 0755)
require.Nil(t, err)
defer func() {
_ = os.RemoveAll(rootDir)
}()
db, err := NewDatabase(rootDir)
require.Nil(t, err)
ctx := context.TODO()
// Add daemons
d1 := daemon.Daemon{States: daemon.ConfigState{ID: "d1"}}
d2 := daemon.Daemon{States: daemon.ConfigState{ID: "d2"}}
d3 := daemon.Daemon{States: daemon.ConfigState{ID: "d3"}}
err = db.SaveDaemon(ctx, &d1)
require.Nil(t, err)
err = db.SaveDaemon(ctx, &d2)
require.Nil(t, err)
err = db.SaveDaemon(ctx, &d3)
assert.Nil(t, err)
require.Nil(t, err)
// duplicate daemon id should fail
err = db.SaveDaemon(ctx, &d1)
require.Error(t, err)
// Delete one daemon
err = db.DeleteDaemon(ctx, "d2")
require.Nil(t, err)
// Check records
ids := make(map[string]string)
_ = db.WalkDaemons(ctx, func(info *daemon.ConfigState) error {
ids[info.ID] = ""
return nil
})
_, ok := ids["d1"]
require.Equal(t, ok, true)
_, ok = ids["d2"]
require.Equal(t, ok, false)
_, ok = ids["d3"]
require.Equal(t, ok, true)
// Cleanup records
err = db.CleanupDaemons(ctx)
require.Nil(t, err)
ids2 := make([]string, 0)
err = db.WalkDaemons(ctx, func(info *daemon.ConfigState) error {
ids2 = append(ids2, info.ID)
return nil
})
require.Nil(t, err)
require.Equal(t, len(ids2), 0)
}
func TestLegacyRecordsMultipleDaemonModes(t *testing.T) {
rootDir := t.TempDir()
prepareCompatTestConfig(t, rootDir, config.FsDriverFusedev, config.DaemonModeDedicated)
mount1 := filepath.Join(rootDir, "mounts", "daemon-1")
mount2 := filepath.Join(rootDir, "mounts", "daemon-2")
snapshotsDir := filepath.Join(rootDir, "snapshots")
records := []*CompatDaemon{
{
ID: "daemon-1",
SnapshotID: "snapshot-1",
ConfigDir: filepath.Join(rootDir, "config", "daemon-1"),
SocketDir: filepath.Join(rootDir, "socket", "daemon-1"),
LogDir: filepath.Join(rootDir, "logs", "daemon-1"),
LogLevel: "info",
SnapshotDir: snapshotsDir,
Pid: 101,
ImageID: "image-1",
FsDriver: config.FsDriverFusedev,
CustomMountPoint: &mount1,
},
{
ID: "daemon-2",
SnapshotID: "snapshot-2",
ConfigDir: filepath.Join(rootDir, "config", "daemon-2"),
SocketDir: filepath.Join(rootDir, "socket", "daemon-2"),
LogDir: filepath.Join(rootDir, "logs", "daemon-2"),
LogLevel: "debug",
SnapshotDir: snapshotsDir,
Pid: 202,
ImageID: "image-2",
FsDriver: config.FsDriverFusedev,
CustomMountPoint: &mount2,
},
}
writeLegacyDatabase(t, rootDir, records)
db, err := NewDatabase(rootDir)
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, db.Close())
})
gotDaemons := listDaemons(t, db)
require.Len(t, gotDaemons, len(records))
for _, record := range records {
got := gotDaemons[record.ID]
require.Equal(t, record.ID, got.ID)
require.Equal(t, record.Pid, got.ProcessID)
require.Equal(t, path.Join(record.SocketDir, "api.sock"), got.APISocket)
require.Equal(t, config.DaemonModeDedicated, got.DaemonMode)
require.Equal(t, record.FsDriver, got.FsDriver)
require.Equal(t, record.LogDir, got.LogDir)
require.Equal(t, record.LogLevel, got.LogLevel)
require.Equal(t, *record.CustomMountPoint, got.Mountpoint)
require.Equal(t, record.ConfigDir, got.ConfigDir)
}
gotInstances := listRafsInstances(t, db)
require.Len(t, gotInstances, len(records))
for _, record := range records {
got := gotInstances[record.SnapshotID]
require.Equal(t, record.ImageID, got.ImageID)
require.Equal(t, record.ID, got.DaemonID)
require.Equal(t, path.Join(record.SnapshotDir, record.SnapshotID), got.SnapshotDir)
require.Equal(t, path.Join(record.SnapshotDir, record.SnapshotID, "mnt"), got.Mountpoint)
}
}
func TestLegacyRecordsSharedDaemonModes(t *testing.T) {
rootDir := t.TempDir()
prepareCompatTestConfig(t, rootDir, config.FsDriverFscache, config.DaemonModeShared)
rootMountpoint := filepath.Join(rootDir, "mnt")
snapshotsDir := filepath.Join(rootDir, "snapshots")
configDir1 := filepath.Join(rootDir, "config", "instance-1")
configDir2 := filepath.Join(rootDir, "config", "instance-2")
writeConfigFile(t, filepath.Join(configDir1, "config.json"), []byte(`{"instance":"1"}`))
writeConfigFile(t, filepath.Join(configDir2, "config.json"), []byte(`{"instance":"2"}`))
records := []*CompatDaemon{
{
ID: "instance-daemon-1",
SnapshotID: "snapshot-a",
ConfigDir: configDir1,
LogDir: filepath.Join(rootDir, "logs", "shared"),
LogLevel: "info",
SnapshotDir: snapshotsDir,
Pid: 301,
ImageID: "image-a",
FsDriver: config.FsDriverFscache,
RootMountPoint: &rootMountpoint,
},
{
ID: "instance-daemon-2",
SnapshotID: "snapshot-b",
ConfigDir: configDir2,
LogDir: filepath.Join(rootDir, "logs", "shared"),
LogLevel: "info",
SnapshotDir: snapshotsDir,
Pid: 302,
ImageID: "image-b",
FsDriver: config.FsDriverFscache,
RootMountPoint: &rootMountpoint,
},
{
ID: SharedNydusDaemonID,
SnapshotDir: snapshotsDir,
Pid: 303,
FsDriver: config.FsDriverFscache,
LogDir: filepath.Join(rootDir, "logs", "shared"),
LogLevel: "debug",
RootMountPoint: &rootMountpoint,
},
}
writeLegacyDatabase(t, rootDir, records)
db, err := NewDatabase(rootDir)
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, db.Close())
})
gotDaemons := listDaemons(t, db)
require.Len(t, gotDaemons, 1)
sharedDaemon := gotDaemons[SharedNydusDaemonID]
require.Equal(t, SharedNydusDaemonID, sharedDaemon.ID)
require.Equal(t, 303, sharedDaemon.ProcessID)
require.Equal(t, path.Join(snapshotsDir, "api.sock"), sharedDaemon.APISocket)
require.Equal(t, config.DaemonModeShared, sharedDaemon.DaemonMode)
require.Equal(t, config.FsDriverFscache, sharedDaemon.FsDriver)
require.Equal(t, rootMountpoint, sharedDaemon.Mountpoint)
require.Equal(t, filepath.Join(rootDir, "config", SharedNydusDaemonID), sharedDaemon.ConfigDir)
gotInstances := listRafsInstances(t, db)
require.Len(t, gotInstances, 2)
for _, record := range records[:2] {
got := gotInstances[record.SnapshotID]
require.Equal(t, record.ImageID, got.ImageID)
require.Equal(t, SharedNydusDaemonID, got.DaemonID)
require.Equal(t, path.Join(record.SnapshotDir, record.SnapshotID), got.SnapshotDir)
require.Equal(t, path.Join(rootMountpoint, record.SnapshotID), got.Mountpoint)
}
require.FileExists(t, filepath.Join(rootDir, "config", SharedNydusDaemonID, "config.json"))
require.FileExists(t, filepath.Join(rootDir, "config", SharedNydusDaemonID, "snapshot-a", "config.json"))
require.FileExists(t, filepath.Join(rootDir, "config", SharedNydusDaemonID, "snapshot-b", "config.json"))
}
func prepareCompatTestConfig(t *testing.T, rootDir, fsDriver string, daemonMode config.DaemonMode) {
t.Helper()
cfg := &config.SnapshotterConfig{
Root: rootDir,
DaemonMode: string(daemonMode),
DaemonConfig: config.DaemonConfig{
FsDriver: fsDriver,
},
}
require.NoError(t, config.ProcessConfigurations(cfg))
}
func writeLegacyDatabase(t *testing.T, rootDir string, records []*CompatDaemon) {
t.Helper()
db, err := bolt.Open(filepath.Join(rootDir, databaseFileName), 0600, &bolt.Options{Timeout: 4 * time.Second})
require.NoError(t, err)
defer func() {
require.NoError(t, db.Close())
}()
err = db.Update(func(tx *bolt.Tx) error {
bucket, err := tx.CreateBucketIfNotExists(daemonsBucket)
if err != nil {
return err
}
for i, record := range records {
payload, err := json.Marshal(record)
if err != nil {
return err
}
key := fmt.Sprintf("%02d-%s", i, record.ID)
if err := bucket.Put([]byte(key), payload); err != nil {
return err
}
}
return nil
})
require.NoError(t, err)
}
func writeConfigFile(t *testing.T, file string, content []byte) {
t.Helper()
require.NoError(t, os.MkdirAll(filepath.Dir(file), 0755))
require.NoError(t, os.WriteFile(file, content, 0644))
}
func listDaemons(t *testing.T, db *Database) map[string]daemon.ConfigState {
t.Helper()
daemons := make(map[string]daemon.ConfigState)
err := db.WalkDaemons(context.TODO(), func(info *daemon.ConfigState) error {
daemons[info.ID] = *info
return nil
})
require.NoError(t, err)
return daemons
}
func listRafsInstances(t *testing.T, db *Database) map[string]rafs.Rafs {
t.Helper()
instances := make(map[string]rafs.Rafs)
err := db.WalkRafsInstances(context.TODO(), func(instance *rafs.Rafs) error {
instances[instance.SnapshotID] = *instance
return nil
})
require.NoError(t, err)
return instances
}
================================================
FILE: pkg/supervisor/supervisor.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package supervisor
import (
"context"
"fmt"
"io"
"net"
"os"
"sync"
"syscall"
"time"
"path/filepath"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/errdefs"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
"golang.org/x/sync/semaphore"
"golang.org/x/sys/unix"
)
type StatesStorage interface {
// Save state to storage space.
Save([]byte)
// Load state from storage space.
Load() ([]byte, error)
// Clean the previously saved state.
Clean()
}
// Store daemon states in memory
type MemStatesStorage struct {
data []byte
}
func newMemStatesStorage() *MemStatesStorage {
return &MemStatesStorage{
data: []byte{},
}
}
func (mss *MemStatesStorage) Save(data []byte) {
mss.data = make([]byte, len(data))
copy(mss.data, data)
}
func (mss *MemStatesStorage) Load() ([]byte, error) {
data := make([]byte, len(mss.data))
copy(data, mss.data)
return data, nil
}
func (mss *MemStatesStorage) Clean() {
mss.data = []byte{}
}
// Use daemon ID as the supervisor ID
type Supervisor struct {
id string
// To which nydusd daemon will try to connect
path string
// Hold the sended file descriptors.
fd int
dataStorage StatesStorage
mu sync.Mutex
sem *semaphore.Weighted
}
func (su *Supervisor) save(data []byte, fd int) {
su.mu.Lock()
defer su.mu.Unlock()
// Always overwrite states and FDs
// We should clean up the stored states since each received states set is atomic
su.dataStorage.Clean()
if fd > 0 {
su.fd = fd
}
su.dataStorage.Save(data)
}
// Load resources kept by this supervisor
// 1. daemon runtime states
// 2. file descriptor
//
// Note: the resources should be not be consumed.
func (su *Supervisor) load() ([]byte, int, error) {
su.mu.Lock()
defer su.mu.Unlock()
data, err := su.dataStorage.Load()
if err != nil {
return nil, 0, err
}
return data, su.fd, nil
}
func recv(uc *net.UnixConn) ([]byte, int, error) {
data := make([]byte, 0)
oob := make([]byte, 0)
var dataBufLen = 1024 * 256 // Bytes
// oobSpace is the size of the oob slice required to store for multiple FDs. Note
// that unix.UnixRights appears to make the assumption that fd is always int32,
// so sizeof(fd) = 4.
// At most can accommodate 64 fds
var oobSpace = unix.CmsgSpace(4) * 64
for {
dataBuf := make([]byte, dataBufLen)
oobBuf := make([]byte, oobSpace)
n, oobn, _, _, err := uc.ReadMsgUnix(dataBuf, oobBuf)
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return nil, 0, errors.Wrap(err, "receive message")
}
if n == 0 {
break // EOF
}
data = append(data, dataBuf[:n]...)
oob = append(oob, oobBuf[:oobn]...)
}
scms, err := unix.ParseSocketControlMessage(oob)
if err != nil {
return nil, 0, errors.Wrap(err, "parse control message")
}
var fds []int
if len(scms) == 0 {
return nil, 0, fmt.Errorf("received no control file descriptor")
}
scm := scms[0]
fds, err = unix.ParseUnixRights(&scm)
if err != nil {
return nil, 0, errors.Wrap(err, "extract file descriptors")
}
var fd int
if len(fds) > 0 {
fd = fds[0]
} else {
fd = -1
}
return data, fd, nil
}
func send(uc *net.UnixConn, data []byte, fd int) error {
oob := syscall.UnixRights(fd)
for len(data) > 0 || len(oob) > 0 {
n, oobn, err := uc.WriteMsgUnix(data, oob, nil)
if err != nil {
return errors.Wrapf(err, "send message, datan %d oobn %d", n, oobn)
}
data = data[n:]
oob = oob[oobn:]
}
return nil
}
// There are several stages from different goroutines to trigger sending daemon states
// the waiter will overlap each other causing the UDS being deleted.
// But we don't want to keep the server listen for ever.
// `to` equal to zero indicates that caller should call the receiver the receive stats
// when it thinks is appropriate. `to` is not zero, no receiver callback will be returned.
// Then this method is responsible to receive states inside.
func (su *Supervisor) waitStatesTimeout(to time.Duration) (func() error, error) {
if err := os.Remove(su.path); err != nil {
if !os.IsNotExist(err) {
log.L.Warnf("Unable to remove existed socket file %s, %s", su.path, err)
}
}
listener, err := net.Listen("unix", su.path)
if err != nil {
return nil, errors.Wrapf(err, "listen on socket %s", su.path)
}
receiver := func() error {
defer func() { _ = listener.Close() }()
// After the listener is closed, Accept() wakes up
conn, err := listener.Accept()
if err != nil {
return errors.Wrapf(err, "Listener is closed")
}
defer func() { _ = conn.Close() }()
data, fd, err := recv(conn.(*net.UnixConn))
if err != nil {
return err
}
log.L.Infof("Supervisor %s receives states. data %d", su.id, len(data))
su.save(data, fd)
return nil
}
cancelTimer := make(chan int, 1)
// Once timeouts, stop waiting for states
if to > 0 {
timer := time.NewTimer(to)
go func() {
select {
case <-timer.C:
log.L.Warnf("Receiving state timeouts after %s", to)
// Wake up the blocking `Accept`
_ = listener.Close()
case <-cancelTimer:
}
}()
go func() {
if err := receiver(); err != nil {
log.L.Errorf("receiver fails, %s", err)
}
if to > 0 {
cancelTimer <- 1
}
}()
// With non-zero timeout parameter, call should be aware that receiver is nil
//nolint: nilnil
return nil, nil
}
return receiver, nil
}
func (su *Supervisor) SendStatesTimeout(to time.Duration) error {
// It is used to receive before
if err := os.Remove(su.path); err != nil {
if !os.IsNotExist(err) {
log.L.Warnf("Unable to remove existed socket file %s, %s", su.path, err)
}
}
listener, err := net.Listen("unix", su.path)
if err != nil {
return errors.Wrap(err, "listen on socket")
}
sender := func() error {
defer func() { _ = listener.Close() }()
conn, err := listener.Accept()
if err != nil {
return errors.Wrapf(err, "Listener is closed")
}
defer func() { _ = conn.Close() }()
// FIXME: It's possible that sending states happens before storing state to the storage.
data, fd, err := su.load()
if err != nil {
return errors.Wrapf(err, "load resources for %s", su.id)
}
if err := send(conn.(*net.UnixConn), data, fd); err != nil {
return err
}
log.L.Infof("Supervisor %s sends states. data %d", su.id, len(data))
return nil
}
cancelTimer := make(chan int, 1)
if to > 0 {
timer := time.NewTimer(to)
go func() {
select {
case <-timer.C:
log.L.Warnf("Sending state timeouts after %s", to)
// Wake up the blocking `Accept()`
_ = listener.Close()
case <-cancelTimer:
}
}()
}
// Once timeouts, stop waiting for others fetching states
go func() {
err := sender()
if err != nil {
log.L.Errorf("Sender fails, %s", err)
}
if to > 0 {
cancelTimer <- 1
}
}()
return nil
}
func (su *Supervisor) FetchDaemonStates(trigger func() error) error {
if err := su.sem.Acquire(context.TODO(), 1); err != nil {
return err
}
defer su.sem.Release(1)
receiver, err := su.waitStatesTimeout(0)
if err != nil {
return errors.Wrapf(err, "wait states on %s", su.Sock())
}
eg := errgroup.Group{}
eg.Go(func() error {
err := trigger()
return errors.Wrapf(err, "trigger on %s", su.Sock())
})
eg.Go(func() error {
err := receiver()
return errors.Wrapf(err, "receiver on %s", su.Sock())
})
// FIXME: With Timeout context!
return eg.Wait()
}
// The unix domain socket on which nydus daemon is connected to
func (su *Supervisor) Sock() string {
return su.path
}
// Manage all supervisors each of which works for a nydusd to keep its resources
// for sake of failover and live-upgrade.
type SupervisorsSet struct {
mu sync.Mutex
set map[string]*Supervisor
// A directory where all the supervisor sockets resides.
root string
}
func NewSupervisorSet(root string) (*SupervisorsSet, error) {
if err := os.MkdirAll(root, 0755); err != nil {
return nil, err
}
return &SupervisorsSet{
set: make(map[string]*Supervisor),
root: root}, nil
}
func (ss *SupervisorsSet) NewSupervisor(id string) *Supervisor {
sockPath := filepath.Join(ss.root, fmt.Sprintf("%s.sock", id))
supervisor := &Supervisor{
id: id,
path: sockPath,
// Negative value means no FD was ever held.
fd: -1,
dataStorage: newMemStatesStorage(),
sem: semaphore.NewWeighted(1),
}
ss.mu.Lock()
defer ss.mu.Unlock()
// Allow overwrite the old supervisor
ss.set[id] = supervisor
return supervisor
}
// Get supervisor by its id which is typically the nydus damon ID.
func (ss *SupervisorsSet) GetSupervisor(id string) *Supervisor {
ss.mu.Lock()
defer ss.mu.Unlock()
return ss.set[id]
}
func (ss *SupervisorsSet) DestroySupervisor(id string) error {
ss.mu.Lock()
defer ss.mu.Unlock()
supervisor, ok := ss.set[id]
if !ok {
return errdefs.ErrNotFound
}
delete(ss.set, id)
supervisor.mu.Lock()
defer supervisor.mu.Unlock()
if supervisor.fd > 0 {
// Prevent hanging after nydusd exits.
if err := syscall.Close(supervisor.fd); err != nil {
log.L.Errorf("Fail to close fd %d, %s", supervisor.fd, err)
}
}
return nil
}
================================================
FILE: pkg/supervisor/supervisor_test.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package supervisor
import (
"crypto/rand"
"net"
"os"
"reflect"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestSupervisor(t *testing.T) {
rootDir, err1 := os.MkdirTemp("", "supervisor")
assert.Nil(t, err1)
t.Cleanup(func() {
os.RemoveAll(rootDir)
})
supervisorSet, err := NewSupervisorSet(rootDir)
assert.Nil(t, err)
su1 := supervisorSet.NewSupervisor("su1")
assert.NotNil(t, su1)
defer func() {
err = supervisorSet.DestroySupervisor("su1")
assert.NotNil(t, su1)
}()
sock := su1.Sock()
addr, err := net.ResolveUnixAddr("unix", sock)
assert.Nil(t, err)
// Build a large data to test the multiple recvmsg / sendmsg
// syscalls can handle all the data.
sentData := make([]byte, 1024*1024*2)
_, err = rand.Read(sentData)
assert.Nil(t, err)
tmpFile, err := os.CreateTemp("", "nydus-supervisor-test")
assert.Nil(t, err)
defer tmpFile.Close()
defer os.Remove(tmpFile.Name())
nydusdSendFd := func() error {
conn, err := net.DialUnix("unix", nil, addr)
assert.Nil(t, err)
defer func() { _ = conn.Close() }()
err = send(conn, sentData, int(tmpFile.Fd()))
assert.Nil(t, err)
return nil
}
err = su1.FetchDaemonStates(nydusdSendFd)
assert.NoError(t, err)
nydusdTakeover := func() {
err = su1.SendStatesTimeout(0)
assert.Nil(t, err)
conn, err := net.DialUnix("unix", nil, addr)
assert.Nil(t, err)
recvData, _, err := recv(conn)
assert.Nil(t, err)
assert.Equal(t, len(sentData), len(recvData))
assert.True(t, reflect.DeepEqual(recvData, sentData))
}
nydusdTakeover()
}
func TestSupervisorTimeout(t *testing.T) {
rootDir, err1 := os.MkdirTemp("", "supervisor")
assert.Nil(t, err1)
t.Cleanup(func() {
os.RemoveAll(rootDir)
})
supervisorSet, err := NewSupervisorSet(rootDir)
assert.Nil(t, err, "%v", err)
su1 := supervisorSet.NewSupervisor("su1")
assert.NotNil(t, su1)
err = su1.SendStatesTimeout(10 * time.Millisecond)
assert.Nil(t, err, "%v", err)
sock := su1.Sock()
time.Sleep(200 * time.Millisecond)
addr, err := net.ResolveUnixAddr("unix", sock)
assert.Nil(t, err)
_, err = net.DialUnix("unix", nil, addr)
assert.NotNil(t, err, "%v", err)
}
================================================
FILE: pkg/system/system.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package system
import (
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"os"
"path"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
"github.com/containerd/log"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/containerd/nydus-snapshotter/pkg/daemon"
"github.com/containerd/nydus-snapshotter/pkg/daemon/types"
"github.com/containerd/nydus-snapshotter/pkg/errdefs"
"github.com/containerd/nydus-snapshotter/pkg/filesystem"
"github.com/containerd/nydus-snapshotter/pkg/manager"
metrics "github.com/containerd/nydus-snapshotter/pkg/metrics/tool"
"github.com/containerd/nydus-snapshotter/pkg/prefetch"
"github.com/containerd/nydus-snapshotter/pkg/utils/signals"
)
const (
// NOTE: Below service endpoints are still experimental.
endpointDaemons string = "/api/v1/daemons"
// Retrieve daemons' persisted states in boltdb. Because the db file is always locked,
// it's very helpful to check daemon's record in database.
endpointDaemonRecords string = "/api/v1/daemons/records"
endpointDaemonsUpgrade string = "/api/v1/daemons/upgrade"
endpointPrefetch string = "/api/v1/prefetch"
// Provide backend information
endpointGetBackend string = "/api/v1/daemons/{id}/backend"
)
const defaultErrorCode string = "Unknown"
// Nydus-snapshotter might manage dozens of running nydus daemons, each daemon may have multiple
// file system instances attached. For easy maintenance, the system controller can interact with
// all the daemons in a consistent and automatic way.
// 1. Get all daemons status and information
// 2. Trigger all daemons to restart and reload configuration
// 3. Rolling update
// 4. Daemons failures record as metrics
type Controller struct {
fs *filesystem.Filesystem
managers []*manager.Manager
// httpSever *http.Server
addr *net.UnixAddr
uid int
gid int
router *mux.Router
}
type upgradeRequest struct {
NydusdPath string `json:"nydusd_path"`
Version string `json:"version"`
Policy string `json:"policy"`
}
type errorMessage struct {
Code string `json:"code"`
Message string `json:"message"`
}
func newErrorMessage(message string) errorMessage {
return errorMessage{Code: defaultErrorCode, Message: message}
}
func (m *errorMessage) encode() string {
msg, err := json.Marshal(&m)
if err != nil {
log.L.Errorf("Failed to encode error message, %s", err)
return ""
}
return string(msg)
}
func jsonResponse(w http.ResponseWriter, payload interface{}) {
respBody, err := json.Marshal(&payload)
if err != nil {
log.L.Errorf("marshal error, %s", err)
m := newErrorMessage(err.Error())
http.Error(w, m.encode(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
if _, err := w.Write(respBody); err != nil {
log.L.Errorf("write body %s", err)
}
}
type daemonInfo struct {
ID string `json:"id"`
Pid int `json:"pid"`
APISock string `json:"api_socket"`
SupervisorPath string `json:"supervisor_path"`
Reference int `json:"reference"`
HostMountpoint string `json:"mountpoint"`
StartupCPUUtilization float64 `json:"startup_cpu_utilization"`
MemoryRSS float64 `json:"memory_rss_kb"`
ReadData float32 `json:"read_data_kb"`
Instances map[string]rafsInstanceInfo `json:"instances"`
}
type rafsInstanceInfo struct {
SnapshotID string `json:"snapshot_id"`
SnapshotDir string `json:"snapshot_dir"`
Mountpoint string `json:"mountpoint"`
ImageID string `json:"image_id"`
}
func NewSystemController(fs *filesystem.Filesystem, managers []*manager.Manager, sock string, uid, gid int) (*Controller, error) {
if err := os.MkdirAll(filepath.Dir(sock), os.ModePerm); err != nil {
return nil, err
}
if err := os.Remove(sock); err != nil {
if !os.IsNotExist(err) {
return nil, err
}
}
addr, err := net.ResolveUnixAddr("unix", sock)
if err != nil {
return nil, errors.Wrapf(err, "resolve address %s", sock)
}
sc := Controller{
fs: fs,
managers: managers,
addr: addr,
uid: uid,
gid: gid,
router: mux.NewRouter(),
}
sc.registerRouter()
return &sc, nil
}
func (sc *Controller) Run() error {
log.L.Infof("Start system controller API server on %s", sc.addr)
stopChan := signals.SetupSignalHandler()
listener, err := net.ListenUnix("unix", sc.addr)
if err != nil {
return errors.Wrapf(err, "listen to socket %s ", sc.addr)
}
if err := os.Chown(sc.addr.String(), sc.uid, sc.gid); err != nil {
return errors.Wrap(err, "chown socket")
}
go func() {
<-stopChan
if err := listener.Close(); err != nil {
log.L.Errorf("Failed to close listener %s, err: %v", sc.addr.String(), err)
}
}()
err = http.Serve(listener, sc.router)
if err != nil {
return errors.Wrapf(err, "system management serving")
}
return nil
}
func (sc *Controller) registerRouter() {
sc.router.HandleFunc(endpointDaemons, sc.describeDaemons()).Methods(http.MethodGet)
sc.router.HandleFunc(endpointDaemonsUpgrade, sc.upgradeDaemons()).Methods(http.MethodPut)
sc.router.HandleFunc(endpointDaemonRecords, sc.getDaemonRecords()).Methods(http.MethodGet)
sc.router.HandleFunc(endpointPrefetch, sc.setPrefetchConfiguration()).Methods(http.MethodPut)
sc.router.HandleFunc(endpointGetBackend, sc.getBackend()).Methods(http.MethodGet)
}
func (sc *Controller) getBackend() func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var statusCode int
defer func() {
if err != nil {
m := newErrorMessage(err.Error())
http.Error(w, m.encode(), statusCode)
}
}()
vars := mux.Vars(r)
id := vars["id"]
for _, ma := range sc.managers {
ma.Lock()
d := ma.GetByDaemonID(id)
if d != nil {
backendType, backendConfig := d.Config.StorageBackend()
backend := struct {
BackendType string `json:"type"`
Config interface{} `json:"config"`
}{
backendType,
backendConfig,
}
jsonResponse(w, backend)
ma.Unlock()
return
}
ma.Unlock()
}
err = errdefs.ErrNotFound
statusCode = http.StatusNotFound
}
}
func (sc *Controller) setPrefetchConfiguration() func(w http.ResponseWriter, r *http.Request) {
return func(_ http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
log.L.Errorf("Failed to read prefetch list: %v", err)
return
}
if err = prefetch.Pm.SetPrefetchFiles(body); err != nil {
log.L.Errorf("Failed to parse request body: %v", err)
return
}
}
}
func (sc *Controller) describeDaemons() func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, _ *http.Request) {
info := make([]daemonInfo, 0, 10)
for _, manager := range sc.managers {
daemons := manager.ListDaemons()
for _, d := range daemons {
instances := make(map[string]rafsInstanceInfo)
for _, i := range d.RafsCache.List() {
instances[i.SnapshotID] = rafsInstanceInfo{
SnapshotID: i.SnapshotID,
SnapshotDir: i.SnapshotDir,
Mountpoint: i.GetMountpoint(),
ImageID: i.ImageID,
}
}
memRSS, err := metrics.GetProcessMemoryRSSKiloBytes(d.Pid())
if err != nil {
log.L.Warnf("Failed to get daemon %s RSS memory", d.ID())
}
var readData float32
fsMetrics, err := d.GetFsMetrics("")
if err != nil {
log.L.Warnf("Failed to get file system metrics")
} else {
readData = float32(fsMetrics.DataRead) / 1024
}
i := daemonInfo{
ID: d.ID(),
Pid: d.Pid(),
APISock: d.GetAPISock(),
SupervisorPath: d.States.SupervisorPath,
HostMountpoint: d.HostMountpoint(),
Reference: int(d.GetRef()),
Instances: instances,
StartupCPUUtilization: d.StartupCPUUtilization,
MemoryRSS: memRSS,
ReadData: readData,
}
info = append(info, i)
}
}
jsonResponse(w, &info)
}
}
// TODO: Implement me!
func (sc *Controller) getDaemonRecords() func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, _ *http.Request) {
m := newErrorMessage("not implemented")
http.Error(w, m.encode(), http.StatusNotImplemented)
}
}
// PUT /api/v1/nydusd/upgrade
// body: {"nydusd_path": "/path/to/new/nydusd", "version": "v2.2.1", "policy": "rolling"}
// Possible policy: rolling, immediate
// Live upgrade procedure:
// 1. Check if new version of nydusd executive is existed.
// 2. Validate its version matching `version` in this request.
// 3. Upgrade one nydusd:
// a. Lock the whole manager daemons cache, no daemon can be inserted of deleted from manager
// b. Start a new nydusd with `--upgrade` flag, wait until it reaches INTI state
// c. Validate the new nydusd's version returned by API /daemon
// d. Send resources like FD and daemon running states to the new nydusd by API /takeover
// e. Wait until new nydusd reaches state READY
// f. Command the old nydusd to exit
// g. Send API /start to the new nydusd making it take over the whole file system service
//
// 4. Upgrade next nydusd like step 3.
// 5. If upgrading a certain nydusd fails, abort!
// 6. Delete the old nydusd executive
func (sc *Controller) upgradeDaemons() func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var c upgradeRequest
var err error
var statusCode int
defer func() {
if err != nil {
m := newErrorMessage(err.Error())
http.Error(w, m.encode(), statusCode)
}
}()
err = json.NewDecoder(r.Body).Decode(&c)
if err != nil {
log.L.Errorf("request %v, decode error %s", r, err)
statusCode = http.StatusBadRequest
return
}
for _, manager := range sc.managers {
manager.Lock()
defer manager.Unlock()
daemons := manager.ListDaemons()
// TODO: Keep the nydusd executive path in Daemon state and persis it since nydusd
// can run on both versions.
// Create a dedicated directory storing nydusd of various versions?
// TODO: daemon client has a method to query daemon version and information.
for _, d := range daemons {
err = sc.upgradeNydusDaemon(d, c, manager)
if err != nil {
log.L.Errorf("Upgrade daemon %s failed, %s", d.ID(), err)
statusCode = http.StatusInternalServerError
return
}
}
sourcePath := c.NydusdPath
destinationPath := manager.NydusdBinaryPath
if err = upgradeNydusdWithSymlink(sourcePath, destinationPath); err != nil {
log.L.Errorf("Failed to copy nydusd binary from %s to %s: %v",
sourcePath, destinationPath, err)
statusCode = http.StatusInternalServerError
return
}
}
}
}
// Provide minimal parameters since most of it can be recovered by nydusd states.
// Create a new daemon in Manger to take over the service.
func (sc *Controller) upgradeNydusDaemon(d *daemon.Daemon, c upgradeRequest, manager *manager.Manager) error {
supervisor := d.Supervisor
if supervisor == nil {
return errors.New("should set recover policy to failover to enable hot upgrade")
}
log.L.Infof("Upgrading nydusd %s, request %v", d.ID(), c)
fs := sc.fs
newDaemon := daemon.Daemon{
States: d.States,
Supervisor: supervisor,
}
newDaemon.CloneRafsInstances(d)
s := path.Base(d.GetAPISock())
next, err := buildNextAPISocket(s)
if err != nil {
return err
}
upgradingSocket := path.Join(path.Dir(d.GetAPISock()), next)
newDaemon.States.APISocket = upgradingSocket
cmd, err := manager.BuildDaemonCommand(&newDaemon, c.NydusdPath, true)
if err != nil {
return err
}
if err := supervisor.SendStatesTimeout(time.Second * 10); err != nil {
return errors.Wrap(err, "Send states")
}
if err := cmd.Start(); err != nil {
return errors.Wrap(err, "start process")
}
newDaemon.States.ProcessID = cmd.Process.Pid
if err := newDaemon.WaitUntilState(types.DaemonStateInit); err != nil {
return errors.Wrap(err, "wait until init state")
}
if err := newDaemon.TakeOver(); err != nil {
return errors.Wrap(err, "take over resources")
}
if err := newDaemon.WaitUntilState(types.DaemonStateReady); err != nil {
return errors.Wrap(err, "wait unit ready state")
}
if err := manager.UnsubscribeDaemonEvent(d); err != nil {
return errors.Wrap(err, "unsubscribe daemon event")
}
// Let the older daemon exit without umount
if err := d.Exit(); err != nil {
return errors.Wrap(err, "old daemon exits")
}
fs.TryRetainSharedDaemon(&newDaemon)
if err := newDaemon.Start(); err != nil {
return errors.Wrap(err, "start file system service")
}
if err := manager.SubscribeDaemonEvent(&newDaemon); err != nil {
return &json.InvalidUnmarshalError{}
}
log.L.Infof("Started service of upgraded daemon on socket %s", newDaemon.GetAPISock())
if err := manager.UpdateDaemonLocked(&newDaemon); err != nil {
return err
}
log.L.Infof("Upgraded daemon success on socket %s", newDaemon.GetAPISock())
return nil
}
// Name next api socket path based on currently api socket path listened on.
// The principle is to add a suffix number to api[0-9]+.sock
func buildNextAPISocket(cur string) (string, error) {
n := strings.Split(cur, ".")
if len(n) != 2 {
return "", errdefs.ErrInvalidArgument
}
r := regexp.MustCompile(`[0-9]+`)
m := r.Find([]byte(n[0]))
var num int
if m == nil {
num = 1
} else {
var err error
num, err = strconv.Atoi(string(m))
if err != nil {
return "", err
}
num++
}
nextSocket := fmt.Sprintf("api%d.sock", num)
return nextSocket, nil
}
// upgradeNydusdWithSymlink atomically creates a symbolic link from destinationPath to sourcePath.
// It uses atomic rename to avoid any gap period where the destination doesn't exist.
// Running processes are not affected as they hold file descriptors to the original inode.
func upgradeNydusdWithSymlink(sourcePath, destinationPath string) error {
// Ensure source file exists and is accessible
if _, err := os.Stat(sourcePath); err != nil {
return fmt.Errorf("source file %s does not exist or is not accessible: %w", sourcePath, err)
}
// Ensure destination directory exists
destDir := filepath.Dir(destinationPath)
if err := os.MkdirAll(destDir, 0755); err != nil {
return fmt.Errorf("failed to create destination directory %s: %w", destDir, err)
}
// Use absolute path for source to avoid relative path issues
absSourcePath, err := filepath.Abs(sourcePath)
if err != nil {
return fmt.Errorf("failed to get absolute path for %s: %w", sourcePath, err)
}
// Get absolute path for destination to compare
absDestinationPath, err := filepath.Abs(destinationPath)
if err != nil {
return fmt.Errorf("failed to get absolute path for %s: %w", destinationPath, err)
}
// Check if source and destination are the same to avoid circular symlink
if absSourcePath == absDestinationPath {
return fmt.Errorf("source path and destination path are the same: %s", absSourcePath)
}
// Create a temporary symbolic link first
tempSymlinkPath := absDestinationPath + ".tmp"
if err := os.Symlink(absSourcePath, tempSymlinkPath); err != nil {
return fmt.Errorf("failed to create temporary symbolic link %s: %w", tempSymlinkPath, err)
}
// Atomically replace the destination with the temporary symlink
if err := os.Rename(tempSymlinkPath, absDestinationPath); err != nil {
// Clean up temporary symlink if rename fails
os.Remove(tempSymlinkPath)
return fmt.Errorf("failed to atomically replace symlink %s: %w", absDestinationPath, err)
}
log.L.Infof("Successfully created symbolic link from %s to %s", absDestinationPath, absSourcePath)
return nil
}
================================================
FILE: pkg/system/system_test.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package system
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestBuildUpgradeSocket(t *testing.T) {
cur := "api.sock"
next, err := buildNextAPISocket(cur)
assert.Nil(t, err)
assert.Equal(t, next, "api1.sock")
cur = "api2.sock"
next, err = buildNextAPISocket(cur)
assert.Nil(t, err)
assert.Equal(t, "api3.sock", next)
cur = "api23.sock"
next, err = buildNextAPISocket(cur)
assert.Nil(t, err)
assert.Equal(t, "api24.sock", next)
cur = "api222.sock"
next, err = buildNextAPISocket(cur)
assert.Nil(t, err)
assert.Equal(t, "api223.sock", next)
}
================================================
FILE: pkg/tarfs/tarfs.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package tarfs
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
"github.com/containerd/containerd/v2/core/snapshots/storage"
"github.com/containerd/containerd/v2/pkg/archive/compression"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/config"
"github.com/containerd/nydus-snapshotter/pkg/auth"
"github.com/containerd/nydus-snapshotter/pkg/errdefs"
"github.com/containerd/nydus-snapshotter/pkg/label"
"github.com/containerd/nydus-snapshotter/pkg/rafs"
"github.com/containerd/nydus-snapshotter/pkg/remote"
"github.com/containerd/nydus-snapshotter/pkg/remote/remotes"
losetup "github.com/freddierice/go-losetup"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"golang.org/x/sync/semaphore"
"golang.org/x/sync/singleflight"
"golang.org/x/sys/unix"
"k8s.io/utils/lru"
)
const (
TarfsStatusInit = 0
TarfsStatusPrepare = 1
TarfsStatusReady = 2
TarfsStatusFailed = 3
)
const (
MaxManifestConfigSize = 0x100000
TarfsLayerBootstrapName = "layer.boot"
TarfsImageBootstrapName = "image.boot"
TarfsLayerDiskName = "layer.disk"
TarfsImageDiskName = "image.disk"
)
type Manager struct {
snapshotMap map[string]*snapshotStatus // tarfs snapshots status, indexed by snapshot ID
mutex sync.Mutex
mutexLoopDev sync.Mutex
cacheDirPath string
nydusImagePath string
insecure bool
validateDiffID bool // whether to validate digest for uncompressed content
checkTarfsHint bool // whether to rely on tarfs hint annotation
maxConcurrentProcess int64
processLimiterCache *lru.Cache // cache image ref and concurrent limiter for blob processes
tarfsHintCache *lru.Cache // cache oci image ref and tarfs hint annotation
diffIDCache *lru.Cache // cache oci blob digest and diffID
sg singleflight.Group
}
type snapshotStatus struct {
mutex sync.Mutex
status int
blobID string
blobTarFilePath string
erofsMountPoint string
dataLoopdev *losetup.Device
metaLoopdev *losetup.Device
wg *sync.WaitGroup
cancel context.CancelFunc
}
func NewManager(insecure, checkTarfsHint bool, cacheDirPath, nydusImagePath string, maxConcurrentProcess int64) *Manager {
return &Manager{
snapshotMap: map[string]*snapshotStatus{},
cacheDirPath: cacheDirPath,
nydusImagePath: nydusImagePath,
insecure: insecure,
validateDiffID: true,
checkTarfsHint: checkTarfsHint,
maxConcurrentProcess: maxConcurrentProcess,
tarfsHintCache: lru.New(50),
processLimiterCache: lru.New(50),
diffIDCache: lru.New(1000),
sg: singleflight.Group{},
}
}
// Fetch image manifest and config contents, cache frequently used information.
// FIXME need an update policy
func (t *Manager) fetchImageInfo(ctx context.Context, remote *remote.Remote, ref string, manifestDigest digest.Digest) error {
manifest, err := t.fetchImageManifest(ctx, remote, ref, manifestDigest)
if err != nil {
return err
}
config, err := t.fetchImageConfig(ctx, remote, ref, &manifest)
if err != nil {
return err
}
if t.checkTarfsHint {
// cache ref & tarfs hint annotation
t.tarfsHintCache.Add(ref, label.HasTarfsHint(manifest.Annotations))
}
if t.validateDiffID {
// cache OCI blob digest & diff id
for i := range manifest.Layers {
t.diffIDCache.Add(manifest.Layers[i].Digest, config.RootFS.DiffIDs[i])
}
}
return nil
}
func (t *Manager) fetchImageManifest(ctx context.Context, remote *remote.Remote, ref string, manifestDigest digest.Digest) (ocispec.Manifest, error) {
rc, desc, err := t.getBlobStream(ctx, remote, ref, manifestDigest)
if err != nil {
return ocispec.Manifest{}, err
}
defer rc.Close()
if desc.Size > MaxManifestConfigSize {
return ocispec.Manifest{}, errors.Errorf("image manifest content size %x is too big", desc.Size)
}
bytes, err := io.ReadAll(rc)
if err != nil {
return ocispec.Manifest{}, errors.Wrap(err, "read image manifest content")
}
var manifestOCI ocispec.Manifest
if err := json.Unmarshal(bytes, &manifestOCI); err != nil {
return ocispec.Manifest{}, errors.Wrap(err, "unmarshal OCI image manifest")
}
if len(manifestOCI.Layers) < 1 {
return ocispec.Manifest{}, errors.Errorf("invalid OCI image manifest without any layer")
}
return manifestOCI, nil
}
func (t *Manager) fetchImageConfig(ctx context.Context, remote *remote.Remote, ref string, manifest *ocispec.Manifest) (ocispec.Image, error) {
// fetch image config content and extract diffIDs
rc, desc, err := t.getBlobStream(ctx, remote, ref, manifest.Config.Digest)
if err != nil {
return ocispec.Image{}, errors.Wrap(err, "fetch image config content")
}
defer rc.Close()
if desc.Size > MaxManifestConfigSize {
return ocispec.Image{}, errors.Errorf("image config content size %x is too big", desc.Size)
}
bytes, err := io.ReadAll(rc)
if err != nil {
return ocispec.Image{}, errors.Wrap(err, "read image config content")
}
var config ocispec.Image
if err := json.Unmarshal(bytes, &config); err != nil {
return ocispec.Image{}, errors.Wrap(err, "unmarshal image config")
}
if len(config.RootFS.DiffIDs) != len(manifest.Layers) {
return ocispec.Image{}, errors.Errorf("number of diffIDs does not match manifest layers")
}
return config, nil
}
func (t *Manager) getBlobDiffID(ctx context.Context, remote *remote.Remote, ref string, manifestDigest, layerDigest digest.Digest) (digest.Digest, error) {
if diffid, ok := t.diffIDCache.Get(layerDigest); ok {
return diffid.(digest.Digest), nil
}
if _, err, _ := t.sg.Do(ref, func() (interface{}, error) {
err := t.fetchImageInfo(ctx, remote, ref, manifestDigest)
return nil, err
}); err != nil {
return "", err
}
if diffid, ok := t.diffIDCache.Get(layerDigest); ok {
return diffid.(digest.Digest), nil
}
return "", errors.Errorf("get blob diff id failed")
}
func (t *Manager) getBlobStream(ctx context.Context, remote *remote.Remote, ref string, contentDigest digest.Digest) (io.ReadCloser, ocispec.Descriptor, error) {
fetcher, err := remote.Fetcher(ctx, ref)
if err != nil {
return nil, ocispec.Descriptor{}, errors.Wrap(err, "get remote fetcher")
}
fetcherByDigest, ok := fetcher.(remotes.FetcherByDigest)
if !ok {
return nil, ocispec.Descriptor{}, errors.Errorf("fetcher %T does not implement remotes.FetcherByDigest", fetcher)
}
return fetcherByDigest.FetchByDigest(ctx, contentDigest)
}
// generate tar file and layer bootstrap, return if this blob is an empty blob
func (t *Manager) generateBootstrap(tarReader io.Reader, snapshotID, layerBlobID, upperDirPath string) (err error) {
snapshotImageDir := filepath.Join(upperDirPath, "image")
if err := os.MkdirAll(snapshotImageDir, 0750); err != nil {
return errors.Wrapf(err, "create data dir %s for tarfs snapshot", snapshotImageDir)
}
layerMetaFile := t.layerMetaFilePath(upperDirPath)
if _, err := os.Stat(layerMetaFile); err == nil {
return errdefs.ErrAlreadyExists
}
layerMetaFileTmp := layerMetaFile + ".tarfs.tmp"
defer os.Remove(layerMetaFileTmp)
layerTarFile := t.layerTarFilePath(layerBlobID)
layerTarFileTmp := layerTarFile + ".tarfs.tmp"
tarFile, err := os.Create(layerTarFileTmp)
if err != nil {
return errors.Wrap(err, "create temporary file to store tar stream")
}
defer tarFile.Close()
defer os.Remove(layerTarFileTmp)
fifoName := filepath.Join(upperDirPath, "layer_"+snapshotID+"_"+"tar.fifo")
if err = syscall.Mkfifo(fifoName, 0644); err != nil {
return err
}
defer os.Remove(fifoName)
go func() {
fifoFile, err := os.OpenFile(fifoName, os.O_WRONLY, os.ModeNamedPipe)
if err != nil {
log.L.Warnf("can not open fifo file, err %v", err)
return
}
defer fifoFile.Close()
if _, err := io.Copy(fifoFile, io.TeeReader(tarReader, tarFile)); err != nil {
log.L.Warnf("tar stream copy err %v", err)
}
}()
options := []string{
"create",
"--type", "tar-tarfs",
"--bootstrap", layerMetaFileTmp,
"--blob-id", layerBlobID,
"--blob-dir", t.cacheDirPath,
fifoName,
}
cmd := exec.Command(t.nydusImagePath, options...)
var errb, outb bytes.Buffer
cmd.Stderr = &errb
cmd.Stdout = &outb
log.L.Debugf("nydus image command %v", options)
err = cmd.Run()
if err != nil {
log.L.Warnf("nydus image exec failed, %s", errb.String())
return errors.Wrap(err, "converting OCIv1 layer blob to tarfs")
}
log.L.Debugf("nydus image output %s", outb.String())
log.L.Debugf("nydus image err %s", errb.String())
if err := os.Rename(layerTarFileTmp, layerTarFile); err != nil {
return errors.Wrapf(err, "rename file %s to %s", layerTarFileTmp, layerTarFile)
}
if err := os.Rename(layerMetaFileTmp, layerMetaFile); err != nil {
return errors.Wrapf(err, "rename file %s to %s", layerMetaFileTmp, layerMetaFile)
}
return nil
}
func (t *Manager) getImageBlobInfo(metaFilePath string) (string, error) {
if _, err := os.Stat(metaFilePath); err != nil {
return "", err
}
options := []string{
"inspect",
"-R blobs",
metaFilePath,
}
cmd := exec.Command(t.nydusImagePath, options...)
var errb, outb bytes.Buffer
cmd.Stderr = &errb
cmd.Stdout = &outb
log.L.Debugf("nydus image command %v", options)
err := cmd.Run()
if err != nil {
log.L.Warnf("nydus image exec failed, %s", errb.String())
return "", errors.Wrap(err, "converting OCIv1 layer blob to tarfs")
}
return outb.String(), nil
}
// download & uncompress an oci/docker blob, and then generate the tarfs bootstrap
func (t *Manager) blobProcess(ctx context.Context, wg *sync.WaitGroup, snapshotID, ref string,
manifestDigest, layerDigest digest.Digest, upperDirPath string) error {
layerBlobID := layerDigest.Hex()
epilog := func(err error, msg string) {
st, err1 := t.getSnapshotStatus(snapshotID, true)
if err1 != nil {
// return errors.Errorf("can not found status object for snapshot %s after prepare", snapshotID)
err1 = errors.Wrapf(err1, "can not found status object for snapshot %s after prepare", snapshotID)
log.L.WithError(err1).Errorf("async prepare tarfs layer for snapshot ID %s", snapshotID)
return
}
defer st.mutex.Unlock()
st.blobID = layerBlobID
st.blobTarFilePath = t.layerTarFilePath(layerBlobID)
if err != nil {
log.L.WithError(err).Error(msg)
st.status = TarfsStatusFailed
} else {
st.status = TarfsStatusReady
}
log.L.Info(msg)
}
keyChain, err := auth.GetKeyChainByRef(ref, nil)
if err != nil {
epilog(err, "create key chain for connection")
return err
}
remote := remote.New(keyChain, t.insecure)
rc, _, err := t.getBlobStream(ctx, remote, ref, layerDigest)
if err != nil && remote.RetryWithPlainHTTP(ref, err) {
rc, _, err = t.getBlobStream(ctx, remote, ref, layerDigest)
}
if err != nil {
epilog(err, "get blob stream for layer")
return errors.Wrapf(err, "get blob stream by digest")
}
go func() {
defer wg.Done()
defer rc.Close()
ds, err := compression.DecompressStream(rc)
if err != nil {
epilog(err, "unpack layer blob stream for tarfs")
return
}
defer ds.Close()
if t.validateDiffID {
diffID, err := t.getBlobDiffID(ctx, remote, ref, manifestDigest, layerDigest)
if err != nil {
epilog(err, "get layer diffID")
return
}
digester := digest.Canonical.Digester()
dr := io.TeeReader(ds, digester.Hash())
err = t.generateBootstrap(dr, snapshotID, layerBlobID, upperDirPath)
switch {
case err != nil && !errdefs.IsAlreadyExists(err):
epilog(err, "generate tarfs from image layer blob")
case err == nil && digester.Digest() != diffID:
epilog(err, "image layer diffID does not match")
default:
msg := fmt.Sprintf("nydus tarfs for snapshot %s is ready, digest %s", snapshotID, digester.Digest())
epilog(nil, msg)
}
} else {
err = t.generateBootstrap(ds, snapshotID, layerBlobID, upperDirPath)
if err != nil && !errdefs.IsAlreadyExists(err) {
epilog(err, "generate tarfs data from image layer blob")
} else {
msg := fmt.Sprintf("nydus tarfs for snapshot %s is ready", snapshotID)
epilog(nil, msg)
}
}
}()
return err
}
func (t *Manager) PrepareLayer(snapshotID, ref string, manifestDigest, layerDigest digest.Digest, upperDirPath string) error {
t.mutex.Lock()
if _, ok := t.snapshotMap[snapshotID]; ok {
t.mutex.Unlock()
return errors.Errorf("snapshot %s has already been prapared", snapshotID)
}
wg := &sync.WaitGroup{}
wg.Add(1)
ctx, cancel := context.WithCancel(context.Background())
t.snapshotMap[snapshotID] = &snapshotStatus{
status: TarfsStatusPrepare,
wg: wg,
cancel: cancel,
}
t.mutex.Unlock()
return t.blobProcess(ctx, wg, snapshotID, ref, manifestDigest, layerDigest, upperDirPath)
}
func (t *Manager) MergeLayers(s storage.Snapshot, storageLocater func(string) string) error {
mergedBootstrap := t.imageMetaFilePath(storageLocater(s.ParentIDs[0]))
if _, err := os.Stat(mergedBootstrap); err == nil {
log.L.Debugf("tarfs snapshot %s already has merged bootstrap %s", s.ParentIDs[0], mergedBootstrap)
return nil
}
bootstraps := []string{}
// When merging bootstrap, we need to arrange layer bootstrap in order from low to high
for idx := len(s.ParentIDs) - 1; idx >= 0; idx-- {
snapshotID := s.ParentIDs[idx]
err := t.waitLayerReady(snapshotID)
if err != nil {
return errors.Wrapf(err, "wait for tarfs snapshot %s to get ready", snapshotID)
}
st, err := t.getSnapshotStatus(snapshotID, false)
if err != nil {
return err
}
if st.status != TarfsStatusReady {
return errors.Errorf("tarfs snapshot %s is not ready, %d", snapshotID, st.status)
}
metaFilePath := t.layerMetaFilePath(storageLocater(snapshotID))
bootstraps = append(bootstraps, metaFilePath)
}
mergedBootstrapTmp := mergedBootstrap + ".tarfs.tmp"
defer os.Remove(mergedBootstrapTmp)
options := []string{
"merge",
"--bootstrap", mergedBootstrapTmp,
}
options = append(options, bootstraps...)
cmd := exec.Command(t.nydusImagePath, options...)
var errb, outb bytes.Buffer
cmd.Stderr = &errb
cmd.Stdout = &outb
log.L.Debugf("nydus image command %v", options)
err := cmd.Run()
if err != nil {
return errors.Wrap(err, "merge tarfs image layers")
}
err = os.Rename(mergedBootstrapTmp, mergedBootstrap)
if err != nil {
return errors.Wrap(err, "rename merged bootstrap file")
}
return nil
}
func (t *Manager) ExportBlockData(s storage.Snapshot, perLayer bool, labels map[string]string, storageLocater func(string) string) ([]string, error) {
updateFields := []string{}
wholeImage, exportDisk, withVerity := config.GetTarfsExportFlags()
log.L.Debugf("ExportBlockData wholeImage = %v, exportDisk = %v, withVerity = %v, perLayer = %v", wholeImage, exportDisk, withVerity, perLayer)
// Nothing to do for this case, all needed datum are ready.
if !exportDisk && !withVerity {
return updateFields, nil
} else if !wholeImage != perLayer {
// Special handling for `layer_block` mode
if exportDisk && !withVerity && !perLayer {
labels[label.NydusLayerBlockInfo] = ""
updateFields = append(updateFields, "labels."+label.NydusLayerBlockInfo)
}
return updateFields, nil
}
var snapshotID string
if perLayer {
snapshotID = s.ID
} else {
if len(s.ParentIDs) == 0 {
return updateFields, errors.Errorf("snapshot %s has no parent", s.ID)
}
snapshotID = s.ParentIDs[0]
}
err := t.waitLayerReady(snapshotID)
if err != nil {
return updateFields, errors.Wrapf(err, "wait for tarfs snapshot %s to get ready", snapshotID)
}
st, err := t.getSnapshotStatus(snapshotID, false)
if err != nil {
return updateFields, err
}
if st.status != TarfsStatusReady {
return updateFields, errors.Errorf("tarfs snapshot %s is not ready, %d", snapshotID, st.status)
}
blobID, ok := labels[label.NydusTarfsLayer]
if !ok {
return updateFields, errors.Errorf("Missing Nydus tarfs layer annotation for snapshot %s", s.ID)
}
var metaFileName, diskFileName string
if wholeImage {
metaFileName = t.imageMetaFilePath(storageLocater(snapshotID))
diskFileName = t.ImageDiskFilePath(blobID)
} else {
metaFileName = t.layerMetaFilePath(storageLocater(snapshotID))
diskFileName = t.LayerDiskFilePath(blobID)
}
// Do not regenerate if the disk image already exists.
if _, err := os.Stat(diskFileName); err == nil {
return updateFields, nil
}
diskFileNameTmp := diskFileName + ".tarfs.tmp"
defer os.Remove(diskFileNameTmp)
options := []string{
"export",
"--block",
"--localfs-dir", t.cacheDirPath,
"--bootstrap", metaFileName,
"--output", diskFileNameTmp,
}
if withVerity {
options = append(options, "--verity")
}
log.L.Debugf("nydus image command %v", options)
cmd := exec.Command(t.nydusImagePath, options...)
var errb, outb bytes.Buffer
cmd.Stderr = &errb
cmd.Stdout = &outb
err = cmd.Run()
if err != nil {
return updateFields, errors.Wrap(err, "merge tarfs image layers")
}
log.L.Debugf("nydus image export command, stdout: %s, stderr: %s", &outb, &errb)
blockInfo := ""
if withVerity {
pattern := "dm-verity options: --no-superblock --format=1 -s \"\" --hash=sha256 --data-block-size=512 --hash-block-size=4096 --data-blocks %d --hash-offset %d %s\n"
var dataBlobks, hashOffset uint64
var rootHash string
if count, err := fmt.Sscanf(outb.String(), pattern, &dataBlobks, &hashOffset, &rootHash); err != nil || count != 3 {
return updateFields, errors.Errorf("failed to parse dm-verity options from nydus image output: %s", outb.String())
}
blockInfo = strconv.FormatUint(dataBlobks, 10) + "," + strconv.FormatUint(hashOffset, 10) + "," + "sha256:" + rootHash
}
if wholeImage {
labels[label.NydusImageBlockInfo] = blockInfo
updateFields = append(updateFields, "labels."+label.NydusImageBlockInfo)
} else {
labels[label.NydusLayerBlockInfo] = blockInfo
updateFields = append(updateFields, "labels."+label.NydusLayerBlockInfo)
}
log.L.Debugf("export block labels %v", labels)
err = os.Rename(diskFileNameTmp, diskFileName)
if err != nil {
return updateFields, errors.Wrap(err, "rename disk image file")
}
return updateFields, nil
}
func (t *Manager) MountTarErofs(snapshotID string, s *storage.Snapshot, labels map[string]string, rafs *rafs.Rafs) error {
if s == nil {
return errors.New("snapshot object for MountTarErofs() is nil")
}
// Copy meta info from snapshot to rafs
t.copyTarfsAnnotations(labels, rafs)
upperDirPath := path.Join(rafs.GetSnapshotDir(), "fs")
if !config.GetTarfsMountOnHost() {
rafs.SetMountpoint(upperDirPath)
return nil
}
mergedBootstrap := t.imageMetaFilePath(upperDirPath)
blobInfo, err := t.getImageBlobInfo(mergedBootstrap)
if err != nil {
return errors.Wrapf(err, "get image blob info")
}
var devices []string
// When merging bootstrap, we need to arrange layer bootstrap in order from low to high
for idx := len(s.ParentIDs) - 1; idx >= 0; idx-- {
snapshotID := s.ParentIDs[idx]
err := t.waitLayerReady(snapshotID)
if err != nil {
return errors.Wrapf(err, "wait for tarfs conversion task")
}
st, err := t.getSnapshotStatus(snapshotID, true)
if err != nil {
return err
}
if st.status != TarfsStatusReady {
st.mutex.Unlock()
return errors.Errorf("snapshot %s tarfs format error %d", snapshotID, st.status)
}
var blobMarker = "\"blob_id\":\"" + st.blobID + "\""
if strings.Contains(blobInfo, blobMarker) {
if st.dataLoopdev == nil {
loopdev, err := t.attachLoopdev(st.blobTarFilePath)
if err != nil {
st.mutex.Unlock()
return errors.Wrapf(err, "attach layer tar file %s to loopdev", st.blobTarFilePath)
}
st.dataLoopdev = loopdev
}
devices = append(devices, "device="+st.dataLoopdev.Path())
}
st.mutex.Unlock()
}
mountOpts := strings.Join(devices, ",")
st, err := t.getSnapshotStatus(snapshotID, true)
if err != nil {
return err
}
defer st.mutex.Unlock()
mountPoint := path.Join(rafs.GetSnapshotDir(), "mnt")
if len(st.erofsMountPoint) > 0 {
if st.erofsMountPoint == mountPoint {
log.L.Debugf("tarfs for snapshot %s has already been mounted at %s", snapshotID, mountPoint)
return nil
}
return errors.Errorf("tarfs for snapshot %s has already been mounted at %s", snapshotID, st.erofsMountPoint)
}
if st.metaLoopdev == nil {
loopdev, err := t.attachLoopdev(mergedBootstrap)
if err != nil {
return errors.Wrapf(err, "attach merged bootstrap %s to loopdev", mergedBootstrap)
}
st.metaLoopdev = loopdev
}
devName := st.metaLoopdev.Path()
if err = os.MkdirAll(mountPoint, 0750); err != nil {
return errors.Wrapf(err, "create tarfs mount dir %s", mountPoint)
}
err = unix.Mount(devName, mountPoint, "erofs", 0, mountOpts)
if err != nil {
return errors.Wrapf(err, "mount erofs at %s with opts %s", mountPoint, mountOpts)
}
st.erofsMountPoint = mountPoint
rafs.SetMountpoint(mountPoint)
return nil
}
func (t *Manager) UmountTarErofs(snapshotID string) error {
st, err := t.getSnapshotStatus(snapshotID, true)
if err != nil {
return errors.Wrapf(err, "umount a tarfs snapshot %s which is already removed", snapshotID)
}
defer st.mutex.Unlock()
if len(st.erofsMountPoint) > 0 {
err := unix.Unmount(st.erofsMountPoint, 0)
if err != nil {
return errors.Wrapf(err, "umount erofs tarfs %s", st.erofsMountPoint)
}
st.erofsMountPoint = ""
}
return nil
}
func (t *Manager) DetachLayer(snapshotID string) error {
st, err := t.getSnapshotStatus(snapshotID, true)
if err != nil {
return os.ErrNotExist
}
if len(st.erofsMountPoint) > 0 {
err := unix.Unmount(st.erofsMountPoint, 0)
if err != nil {
st.mutex.Unlock()
return errors.Wrapf(err, "umount erofs tarfs %s", st.erofsMountPoint)
}
st.erofsMountPoint = ""
}
if st.metaLoopdev != nil {
err := st.metaLoopdev.Detach()
if err != nil {
st.mutex.Unlock()
return errors.Wrapf(err, "detach merged bootstrap loopdev for tarfs snapshot %s", snapshotID)
}
st.metaLoopdev = nil
}
if st.dataLoopdev != nil {
err := st.dataLoopdev.Detach()
if err != nil {
st.mutex.Unlock()
return errors.Wrapf(err, "detach layer bootstrap loopdev for tarfs snapshot %s", snapshotID)
}
st.dataLoopdev = nil
}
st.mutex.Unlock()
// TODO: check order
st.cancel()
t.mutex.Lock()
delete(t.snapshotMap, snapshotID)
t.mutex.Unlock()
return nil
}
func (t *Manager) getSnapshotStatus(snapshotID string, lock bool) (*snapshotStatus, error) {
t.mutex.Lock()
defer t.mutex.Unlock()
st, ok := t.snapshotMap[snapshotID]
if ok {
if lock {
st.mutex.Lock()
}
return st, nil
}
return nil, errors.Errorf("not found snapshot %s", snapshotID)
}
func (t *Manager) waitLayerReady(snapshotID string) error {
st, err := t.getSnapshotStatus(snapshotID, false)
if err != nil {
return err
}
if st.status != TarfsStatusReady {
log.L.Debugf("wait tarfs conversion task for snapshot %s", snapshotID)
}
st.wg.Wait()
if st.status != TarfsStatusReady {
return errors.Errorf("snapshot %s is in state %d instead of ready state", snapshotID, st.status)
}
return nil
}
func (t *Manager) attachLoopdev(blob string) (*losetup.Device, error) {
// losetup.Attach() is not thread-safe hold lock here
t.mutexLoopDev.Lock()
defer t.mutexLoopDev.Unlock()
dev, err := losetup.Attach(blob, 0, false)
return &dev, err
}
func (t *Manager) CheckTarfsHintAnnotation(ctx context.Context, ref string, manifestDigest digest.Digest) (bool, error) {
if !t.checkTarfsHint {
return true, nil
}
keyChain, err := auth.GetKeyChainByRef(ref, nil)
if err != nil {
return false, err
}
remote := remote.New(keyChain, t.insecure)
handle := func() (bool, error) {
if tarfsHint, ok := t.tarfsHintCache.Get(ref); ok {
return tarfsHint.(bool), nil
}
if _, err, _ := t.sg.Do(ref, func() (interface{}, error) {
err := t.fetchImageInfo(ctx, remote, ref, manifestDigest)
return nil, err
}); err != nil {
return false, err
}
if tarfsHint, ok := t.tarfsHintCache.Get(ref); ok {
return tarfsHint.(bool), nil
}
return false, errors.Errorf("get tarfs hint annotation failed")
}
tarfsHint, err := handle()
if err != nil && remote.RetryWithPlainHTTP(ref, err) {
tarfsHint, err = handle()
}
return tarfsHint, err
}
func (t *Manager) GetConcurrentLimiter(ref string) *semaphore.Weighted {
if t.maxConcurrentProcess <= 0 {
return nil
}
if limiter, ok := t.processLimiterCache.Get(ref); ok {
return limiter.(*semaphore.Weighted)
}
limiter := semaphore.NewWeighted(t.maxConcurrentProcess)
t.processLimiterCache.Add(ref, limiter)
return limiter
}
func (t *Manager) copyTarfsAnnotations(labels map[string]string, rafs *rafs.Rafs) {
keys := []string{
label.NydusTarfsLayer,
label.NydusImageBlockInfo,
label.NydusLayerBlockInfo,
}
for _, k := range keys {
if v, ok := labels[k]; ok {
rafs.AddAnnotation(k, v)
}
}
}
func (t *Manager) layerTarFilePath(blobID string) string {
return filepath.Join(t.cacheDirPath, blobID)
}
func (t *Manager) LayerDiskFilePath(blobID string) string {
return filepath.Join(t.cacheDirPath, blobID+"."+TarfsLayerDiskName)
}
func (t *Manager) ImageDiskFilePath(blobID string) string {
return filepath.Join(t.cacheDirPath, blobID+"."+TarfsImageDiskName)
}
func (t *Manager) layerMetaFilePath(upperDirPath string) string {
return filepath.Join(upperDirPath, "image", TarfsLayerBootstrapName)
}
func (t *Manager) imageMetaFilePath(upperDirPath string) string {
return filepath.Join(upperDirPath, "image", TarfsImageBootstrapName)
}
================================================
FILE: pkg/utils/display/display.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package display
import "fmt"
func ByteToReadableIEC(b uint32) string {
const unit = 1024
if b < unit {
return fmt.Sprintf("%d B", b)
}
div, exp := int64(unit), 0
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %ciB",
float64(b)/float64(div), "KMGTPE"[exp])
}
func MicroSecondToReadable(b uint64) string {
const unit = 1000
if b < unit {
return fmt.Sprintf("%d us", b)
}
if b < unit*unit {
return fmt.Sprintf("%.3f ms", float64(b)/float64(unit))
}
return fmt.Sprintf("%.3f s", float64(b)/float64(unit*unit))
}
================================================
FILE: pkg/utils/erofs/erofs.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package erofs
import (
"fmt"
"github.com/containerd/log"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
func Mount(domainID, fscacheID, mountpoint string) error {
mount := unix.Mount
var opts string
// Nydusd must have domain_id specified and it is set to fsid if it is
// never specified.
if domainID != "" && domainID != fscacheID {
opts = fmt.Sprintf("domain_id=%s,fsid=%s", domainID, fscacheID)
} else {
opts = "fsid=" + fscacheID
}
log.L.Infof("Mount erofs to %s with options %s", mountpoint, opts)
if err := mount("erofs", mountpoint, "erofs", 0, opts); err != nil {
if errors.Is(err, unix.EINVAL) && domainID != "" {
log.L.Errorf("mount erofs with shared domain failed, " +
"If using this feature, make sure your Linux kernel version >= 6.1")
}
return errors.Wrapf(err, "mount erofs at %s", mountpoint)
}
return nil
}
func Umount(mountPoint string) error {
return unix.Unmount(mountPoint, 0)
}
func FscacheID(snapshotID string) string {
return digest.FromString(fmt.Sprintf("nydus-snapshot-%s", snapshotID)).Hex()
}
================================================
FILE: pkg/utils/file/file.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package file
import "os"
func IsDirExisted(path string) (bool, error) {
s, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
return s.IsDir(), nil
}
================================================
FILE: pkg/utils/mount/mount.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package mount
import (
"os"
"path/filepath"
"syscall"
"time"
"github.com/pkg/errors"
"github.com/containerd/nydus-snapshotter/pkg/errdefs"
"github.com/containerd/nydus-snapshotter/pkg/utils/retry"
)
type Interface interface {
Umount(target string) error
}
type Mounter struct {
}
func (m *Mounter) Umount(target string) error {
if mounted, err := IsMountpoint(target); err == nil {
if !mounted {
return errors.New("not mounted")
}
} else {
return err
}
// return syscall.Unmount(target, syscall.MNT_FORCE)
return syscall.Unmount(target, 0)
}
func NormalizePath(path string) (realPath string, err error) {
if realPath, err = filepath.Abs(path); err != nil {
return "", errors.Wrapf(err, "get absolute path for %s", path)
}
if realPath, err = filepath.EvalSymlinks(realPath); err != nil {
return "", errors.Wrapf(err, "canonicalise path for %s", path)
}
if _, err := os.Stat(realPath); err != nil {
return "", errors.Wrapf(err, "stat target of %s", path)
}
return realPath, nil
}
// return value `true` means the path is mounted
func IsMountpoint(path string) (bool, error) {
realPath, err := NormalizePath(path)
if err != nil {
return false, err
}
if path == "/" {
return true, nil
}
stat, err := os.Stat(realPath)
if err != nil {
return false, err
}
parentStat, err := os.Stat(filepath.Dir(realPath))
if err != nil {
return false, err
}
// If the directory has a different device as parent, then it is a mountpoint.
if stat.Sys().(*syscall.Stat_t).Dev != parentStat.Sys().(*syscall.Stat_t).Dev {
return true, nil
}
return false, nil
}
func WaitUntilUnmounted(path string) error {
return retry.Do(func() error {
mounted, err := IsMountpoint(path)
if err != nil {
return err
}
if mounted {
return errdefs.ErrDeviceBusy
}
return nil
},
retry.Attempts(20), // totally wait for 1 seconds, should be enough
retry.LastErrorOnly(true),
retry.Delay(50*time.Millisecond),
)
}
================================================
FILE: pkg/utils/parser/parser.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package parser
import (
"regexp"
"strconv"
"sync"
"github.com/pkg/errors"
)
var (
unitMultipliers map[string]int64
unitMultipliersOnce sync.Once
)
func InitUnitMultipliers() {
unitMultipliers = make(map[string]int64, 10)
unitMultipliers["KiB"] = 1024
unitMultipliers["MiB"] = unitMultipliers["KiB"] * 1024
unitMultipliers["GiB"] = unitMultipliers["MiB"] * 1024
unitMultipliers["TiB"] = unitMultipliers["GiB"] * 1024
unitMultipliers["PiB"] = unitMultipliers["TiB"] * 1024
unitMultipliers["Ki"] = 1024
unitMultipliers["Mi"] = unitMultipliers["Ki"] * 1024
unitMultipliers["Gi"] = unitMultipliers["Mi"] * 1024
unitMultipliers["Ti"] = unitMultipliers["Gi"] * 1024
unitMultipliers["Pi"] = unitMultipliers["Ti"] * 1024
}
func MemoryConfigToBytes(data string, totalMemoryBytes int) (int64, error) {
if data == "" {
return -1, nil
}
// Memory value without unit.
value, err := strconv.ParseFloat(data, 64)
if err == nil {
return int64(value), nil
}
re := regexp.MustCompile(`(\d*\.?\d+)([a-zA-Z\%]+)`)
matches := re.FindStringSubmatch(data)
if len(matches) != 3 {
return 0, errors.Errorf("Falied to convert data to bytes: Unknown unit in %s", data)
}
// Parse memory value and unit.
valueString, unit := matches[1], matches[2]
value, err = strconv.ParseFloat(valueString, 64)
if err != nil {
return 0, errors.Wrap(err, "Failed to parse memory limit")
}
// Return if the unit is byte.
if unit == "B" {
return int64(value), nil
}
// Calculate value if the unit is "%".
if unit == "%" {
limitMemory := float64(totalMemoryBytes) * value / 100
return int64(limitMemory + 0.5), nil
}
unitMultipliersOnce.Do(InitUnitMultipliers)
multiplier := unitMultipliers[unit]
return int64(value * float64(multiplier)), nil
}
================================================
FILE: pkg/utils/parser/parser_test.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package parser
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestMemoryLimitToBytes(t *testing.T) {
totalMemoryBytes := 10000
for desc, test := range map[string]struct {
MemoryLimit string
expected int64
}{
"memory limit is zero": {
MemoryLimit: "",
expected: -1,
},
"memory limit is a percentage": {
MemoryLimit: "20%",
expected: 2000,
},
"memory limit is a float percentage": {
MemoryLimit: "0.2%",
expected: 20,
},
"memory limit is a value without unit": {
MemoryLimit: "10240",
expected: 10240,
},
"memory limit is a value with Byte unit": {
MemoryLimit: "10240B",
expected: 10240,
},
"memory limit is a value with KiB unit": {
MemoryLimit: "30KiB",
expected: 30 * 1024,
},
"memory limit is a value with MiB unit": {
MemoryLimit: "30MiB",
expected: 30 * 1024 * 1024,
},
"memory limit is a value with GiB unit": {
MemoryLimit: "30GiB",
expected: 30 * 1024 * 1024 * 1024,
},
"memory limit is a value with TiB unit": {
MemoryLimit: "30TiB",
expected: 30 * 1024 * 1024 * 1024 * 1024,
},
"memory limit is a value with PiB unit": {
MemoryLimit: "30PiB",
expected: 30 * 1024 * 1024 * 1024 * 1024 * 1024,
},
"memory limit is a value with Ki unit": {
MemoryLimit: "30Ki",
expected: 30 * 1024,
},
"memory limit is a value with Mi unit": {
MemoryLimit: "30Mi",
expected: 30 * 1024 * 1024,
},
"memory limit is a value with Gi unit": {
MemoryLimit: "30Gi",
expected: 30 * 1024 * 1024 * 1024,
},
"memory limit is a value with Ti unit": {
MemoryLimit: "30Ti",
expected: 30 * 1024 * 1024 * 1024 * 1024,
},
"memory limit is a value with Pi unit": {
MemoryLimit: "30Pi",
expected: 30 * 1024 * 1024 * 1024 * 1024 * 1024,
},
} {
t.Logf("TestCase %q", desc)
memoryLimitInBytes, err := MemoryConfigToBytes(test.MemoryLimit, totalMemoryBytes)
assert.NoError(t, err)
assert.Equal(t, memoryLimitInBytes, test.expected)
}
}
================================================
FILE: pkg/utils/registry/registry.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package registry
import (
"context"
"fmt"
"net/http"
"reflect"
"strings"
"time"
snpkg "github.com/containerd/containerd/v2/pkg/snapshotters"
distribution "github.com/distribution/reference"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
"github.com/pkg/errors"
)
type Image struct {
Host string
Repo string
}
func ConvertToVPCHost(registryHost string) string {
parts := strings.Split(registryHost, ".")
if strings.HasSuffix(parts[0], "-vpc") {
return registryHost
}
parts[0] = fmt.Sprintf("%s-vpc", parts[0])
return strings.Join(parts, ".")
}
func ParseImage(imageID string) (Image, error) {
named, err := distribution.ParseDockerRef(imageID)
if err != nil {
return Image{}, err
}
host := distribution.Domain(named)
repo := distribution.Path(named)
return Image{
Host: host,
Repo: repo,
}, nil
}
func ParseLabels(labels map[string]string) (rRef, rDigest string) {
if ref, ok := labels[snpkg.TargetRefLabel]; ok {
rRef = ref
}
if layerDigest, ok := labels[snpkg.TargetLayerDigestLabel]; ok {
rDigest = layerDigest
}
return
}
func AuthnTransport(ref name.Reference, tr http.RoundTripper, keychain authn.Keychain) (http.RoundTripper, error) {
var err error
var auth authn.Authenticator
if keychain == nil || (reflect.ValueOf(keychain).Kind() == reflect.Ptr && reflect.ValueOf(keychain).IsNil()) {
auth = authn.Anonymous
} else {
auth, err = keychain.Resolve(ref.Context())
if err != nil {
return nil, errors.Wrapf(err, "failed to resolve reference %q", ref)
}
}
errCh := make(chan error)
var rTr http.RoundTripper
go func() {
rTr, err = transport.NewWithContext(
context.TODO(),
ref.Context().Registry,
auth,
tr,
[]string{ref.Scope(transport.PullScope)},
)
errCh <- err
}()
select {
case err = <-errCh:
case <-time.After(10 * time.Second):
return nil, fmt.Errorf("authentication timeout")
}
return rTr, err
}
================================================
FILE: pkg/utils/registry/registry_test.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package registry
import (
"reflect"
"testing"
)
func TestConvertToVPCHost1(t *testing.T) {
type args struct {
registryHost string
}
tests := []struct {
name string
args args
want string
}{
{
name: "with no vpc registry",
args: args{
registryHost: "acr-nydus-registry.cn-hangzhou.cr.aliyuncs.com",
},
want: "acr-nydus-registry-vpc.cn-hangzhou.cr.aliyuncs.com",
},
{
name: "with vpc registry",
args: args{
registryHost: "acr-nydus-registry-vpc.cn-hangzhou.cr.aliyuncs.com",
},
want: "acr-nydus-registry-vpc.cn-hangzhou.cr.aliyuncs.com",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ConvertToVPCHost(tt.args.registryHost); got != tt.want {
t.Errorf("ConvertToVPCHost() = %v, want %v", got, tt.want)
}
})
}
}
func TestParseImage(t *testing.T) {
type args struct {
imageID string
}
tests := []struct {
name string
args args
want Image
wantErr bool
}{
{
name: "multi path",
args: args{
imageID: "localhost:5000/hello-world/foo/bar:latest",
},
want: Image{
Host: "localhost:5000",
Repo: "hello-world/foo/bar",
},
wantErr: false,
},
{
name: "no namespace",
args: args{
imageID: "localhost:5000/bar:latest",
},
want: Image{
Host: "localhost:5000",
Repo: "bar",
},
wantErr: false,
},
{
name: "normal",
args: args{
imageID: "nydus-registry.cn-hangzhou.cr.aliyuncs.com/poc/tomcat:latest-app-nydus-platform",
},
want: Image{
Host: "nydus-registry.cn-hangzhou.cr.aliyuncs.com",
Repo: "poc/tomcat",
},
wantErr: false,
},
{
name: "invalid",
args: args{
imageID: "nydus-registry.cn-hangzhou.cr.aliyuncs.com/:latest-app-nydus-platform",
},
want: Image{
Host: "",
Repo: "",
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseImage(tt.args.imageID)
if (err != nil) != tt.wantErr {
t.Errorf("ParseImage() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ParseImage() got = %v, want %v", got, tt.want)
}
})
}
}
================================================
FILE: pkg/utils/retry/retry.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package retry
import (
"fmt"
"math/rand"
"strings"
"time"
)
type RetryableFunc func() error
var (
DefaultAttempts = uint(10)
DefaultDelay = 100 * time.Millisecond
DefaultMaxJitter = 100 * time.Millisecond
DefaultOnRetry = func(_ uint, _ error) {}
DefaultRetryIf = IsRecoverable
DefaultDelayType = CombineDelay(FixedDelay, RandomDelay)
DefaultLastErrorOnly = false
)
// Function signature of retry if function
type retryIfFunc func(error) bool
type AbortFunc func(error) bool
// Function signature of OnRetry function
// n = count of attempts
type OnRetryFunc func(n uint, err error)
type DelayTypeFunc func(n uint, config *Config) time.Duration
type Config struct {
attempts uint
delay time.Duration
maxDelay time.Duration
maxJitter time.Duration
onRetry OnRetryFunc
retryIf retryIfFunc
delayType DelayTypeFunc
lastErrorOnly bool
}
// Option represents an option for retry.
type Option func(*Config)
// return the direct last error that came from the retried function
// default is false (return wrapped errors with everything)
func LastErrorOnly(lastErrorOnly bool) Option {
return func(c *Config) {
c.lastErrorOnly = lastErrorOnly
}
}
// Attempts set count of retry
// default is 10
func Attempts(attempts uint) Option {
return func(c *Config) {
c.attempts = attempts
}
}
// `abortFunc` return true means no further need to retry
func OnlyRetryIf(abortFunc AbortFunc) Option {
return func(c *Config) {
c.retryIf = func(err error) bool {
if !IsRecoverable(err) {
return false
}
return !abortFunc(err)
}
}
}
// Delay set delay between retry
// default is 100ms
func Delay(delay time.Duration) Option {
return func(c *Config) {
c.delay = delay
}
}
// MaxDelay set maximum delay between retry
// does not apply by default
func MaxDelay(maxDelay time.Duration) Option {
return func(c *Config) {
c.maxDelay = maxDelay
}
}
// MaxJitter sets the maximum random Jitter between retries for RandomDelay
func MaxJitter(maxJitter time.Duration) Option {
return func(c *Config) {
c.maxJitter = maxJitter
}
}
// DelayType set type of the delay between retries
// default is BackOff
func DelayType(delayType DelayTypeFunc) Option {
return func(c *Config) {
c.delayType = delayType
}
}
// BackOffDelay is a DelayType which increases delay between consecutive retries
func BackOffDelay(n uint, config *Config) time.Duration {
return config.delay * (1 << n)
}
// FixedDelay is a DelayType which keeps delay the same through all iterations
func FixedDelay(_ uint, config *Config) time.Duration {
return config.delay
}
// RandomDelay is a DelayType which picks a random delay up to config.maxJitter
func RandomDelay(_ uint, config *Config) time.Duration {
return time.Duration(rand.Int63n(int64(config.maxJitter)))
}
// CombineDelay is a DelayType the combines all of the specified delays into a new DelayTypeFunc
func CombineDelay(delays ...DelayTypeFunc) DelayTypeFunc {
return func(n uint, config *Config) time.Duration {
var total time.Duration
for _, delay := range delays {
total += delay(n, config)
}
return total
}
}
func OnRetry(onRetry OnRetryFunc) Option {
return func(c *Config) {
c.onRetry = onRetry
}
}
func Do(retryFunc RetryableFunc, opts ...Option) error {
var n uint
// default
config := &Config{
attempts: DefaultAttempts,
delay: DefaultDelay,
maxJitter: DefaultMaxJitter,
onRetry: DefaultOnRetry,
retryIf: DefaultRetryIf,
delayType: DefaultDelayType,
lastErrorOnly: DefaultLastErrorOnly,
}
// apply opts
for _, opt := range opts {
opt(config)
}
var errorLog Error
if !config.lastErrorOnly {
errorLog = make(Error, config.attempts)
} else {
errorLog = make(Error, 1)
}
lastErrIndex := n
for n < config.attempts {
err := retryFunc()
if err != nil {
errorLog[lastErrIndex] = unpackUnrecoverable(err)
if !config.retryIf(err) {
break
}
config.onRetry(n, err)
// if this is last attempt - don't wait
if n == config.attempts-1 {
break
}
delayTime := config.delayType(n, config)
if config.maxDelay > 0 && delayTime > config.maxDelay {
delayTime = config.maxDelay
}
time.Sleep(delayTime)
} else {
return nil
}
n++
if !config.lastErrorOnly {
lastErrIndex = n
}
}
if config.lastErrorOnly {
return errorLog[lastErrIndex]
}
return errorLog
}
// Error type represents list of errors in retry
type Error []error
// Error method return string representation of Error
// It is an implementation of error interface
func (e Error) Error() string {
logWithNumber := make([]string, lenWithoutNil(e))
for i, l := range e {
if l != nil {
logWithNumber[i] = fmt.Sprintf("#%d: %s", i+1, l.Error())
}
}
return fmt.Sprintf("All attempts fail:\n%s", strings.Join(logWithNumber, "\n"))
}
func lenWithoutNil(e Error) (count int) {
for _, v := range e {
if v != nil {
count++
}
}
return
}
// WrappedErrors returns the list of errors that this Error is wrapping.
// It is an implementation of the `errwrap.Wrapper` interface
// in package [errwrap](https://github.com/hashicorp/errwrap) so that
// `retry.Error` can be used with that library.
func (e Error) WrappedErrors() []error {
return e
}
type unrecoverableError struct {
error
}
// Unrecoverable wraps an error in `unrecoverableError` struct
func Unrecoverable(err error) error {
return unrecoverableError{err}
}
// IsRecoverable checks if error is an instance of `unrecoverableError`
func IsRecoverable(err error) bool {
_, isUnrecoverable := err.(unrecoverableError)
return !isUnrecoverable
}
func unpackUnrecoverable(err error) error {
if unrecoverable, isUnrecoverable := err.(unrecoverableError); isUnrecoverable {
return unrecoverable.error
}
return err
}
================================================
FILE: pkg/utils/signals/signal.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package signals
import (
"os"
"os/signal"
"sync"
"syscall"
)
var (
once sync.Once
stop chan struct{}
shutdownSignals = []os.Signal{os.Interrupt, syscall.SIGTERM}
)
func SetupSignalHandler() (stopCh <-chan struct{}) {
// make sure SetupSignalHandler will not call twice
once.Do(func() {
stop = make(chan struct{})
c := make(chan os.Signal, 2)
signal.Notify(c, shutdownSignals...)
go func() {
<-c
close(stop)
<-c
os.Exit(1)
}()
})
return stop
}
================================================
FILE: pkg/utils/signals/signal_test.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package signals
import (
"sync/atomic"
"syscall"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestSetupSignalHandler(t *testing.T) {
signal := SetupSignalHandler()
var expected int32 = 2
var actual int32
var func1 = func(stop <-chan struct{}) {
<-stop
atomic.AddInt32(&actual, 1)
}
var func2 = func(stop <-chan struct{}) {
<-stop
atomic.AddInt32(&actual, 1)
}
go func1(signal)
go func2(signal)
err := syscall.Kill(syscall.Getpid(), syscall.SIGINT)
require.Nil(t, err)
time.Sleep(1 * time.Second)
require.Equal(t, atomic.LoadInt32(&actual), expected)
}
================================================
FILE: pkg/utils/signer/signer.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package signer
import (
"crypto"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"io"
)
type Signer struct {
publicKey *rsa.PublicKey
}
func New(publicKey []byte) (*Signer, error) {
block, _ := pem.Decode(publicKey)
key, err := x509.ParsePKCS1PublicKey(block.Bytes)
if err != nil {
return nil, err
}
return &Signer{
publicKey: key,
}, nil
}
func (s *Signer) Verify(input io.Reader, signature []byte) error {
h := sha256.New()
_, err := io.Copy(h, input)
if err != nil {
return err
}
return rsa.VerifyPKCS1v15(s.publicKey, crypto.SHA256, h.Sum(nil), signature)
}
================================================
FILE: pkg/utils/sysinfo/sysinfo.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package sysinfo
import (
"sync"
"syscall"
)
var (
sysinfo *syscall.Sysinfo_t
sysinfoOnce sync.Once
sysinfoErr error
)
func GetSysinfo() {
var info syscall.Sysinfo_t
err := syscall.Sysinfo(&info)
if err != nil {
sysinfoErr = err
return
}
sysinfo = &info
sysinfoErr = nil
}
func GetTotalMemoryBytes() (int, error) {
sysinfoOnce.Do(GetSysinfo)
if sysinfo == nil {
return 0, sysinfoErr
}
return int(sysinfo.Totalram), nil
}
================================================
FILE: pkg/utils/transport/pool.go
================================================
package transport
import (
"context"
"fmt"
"io"
"net/http"
"sync"
"time"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/utils/registry"
"github.com/golang/groupcache/lru"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
"github.com/pkg/errors"
)
var _ Resolve = &Pool{}
const HTTPClientTimeOut = time.Second * 60
// LRU cache for authenticated network connections.
type Pool struct {
trPoolMu sync.Mutex
trPool *lru.Cache
transport http.RoundTripper
}
func NewPool() *Pool {
pool := Pool{
transport: http.DefaultTransport,
trPool: lru.New(3000),
}
return &pool
}
type Resolve interface {
Resolve(ref name.Reference, digest string, keychain authn.Keychain) (string, http.RoundTripper, error)
}
func (r *Pool) Resolve(ref name.Reference, digest string, keychain authn.Keychain) (string, http.RoundTripper, error) {
r.trPoolMu.Lock()
defer r.trPoolMu.Unlock()
endpointURL := fmt.Sprintf("%s://%s/v2/%s/blobs/%s",
ref.Context().Scheme(),
ref.Context().RegistryStr(),
ref.Context().RepositoryStr(),
digest)
if tr, ok := r.trPool.Get(ref.Name()); ok {
var err error
if url, err := redirect(endpointURL, tr.(http.RoundTripper)); err == nil {
return url, tr.(http.RoundTripper), nil
}
r.trPool.Remove(ref.Name())
log.L.Warnf("redirect %s, failed, err: %s", endpointURL, err)
}
tr, err := registry.AuthnTransport(ref, r.transport, keychain)
if err != nil {
return "", nil, errors.Wrapf(err, "failed to authn transport")
}
url, err := redirect(endpointURL, tr)
if err != nil {
return "", nil, errors.Wrapf(err, "failed to redirect %s", endpointURL)
}
r.trPool.Add(ref.Name(), tr)
return url, tr, nil
}
func redirect(endpointURL string, tr http.RoundTripper) (url string, err error) {
ctx, cancel := context.WithTimeout(context.Background(), HTTPClientTimeOut)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", endpointURL, nil)
if err != nil {
return "", errors.Wrapf(err, "failed to request to the registry")
}
req.Close = false
req.Header.Set("Range", "bytes=0-0")
res, err := tr.RoundTrip(req)
if err != nil {
return "", errors.Wrapf(err, "failed to request to %q", endpointURL)
}
defer func() {
_, err := io.Copy(io.Discard, res.Body)
if err != nil {
log.L.Warnf("Discard body failed %s", err)
}
res.Body.Close()
}()
if res.StatusCode/100 == 2 {
url = endpointURL
} else if redir := res.Header.Get("Location"); redir != "" && res.StatusCode/100 == 3 {
url = redir
} else {
body, err := io.ReadAll(res.Body)
if err != nil {
return "", errors.Wrapf(err, "failed to get response body")
}
return "", fmt.Errorf("failed to access to %q with code %v, body: %s", endpointURL, res.StatusCode, body)
}
return
}
================================================
FILE: pkg/utils/transport/pool_test.go
================================================
package transport
import (
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/google/go-containerregistry/pkg/name"
"github.com/stretchr/testify/require"
)
type FakeReference struct {
Tag string
Repository name.Repository
}
var _ name.Reference = (*FakeReference)(nil)
func (t FakeReference) Context() name.Repository {
return t.Repository
}
func (t FakeReference) Identifier() string {
return t.Tag
}
func (t FakeReference) Name() string {
return t.Repository.Name() + ":" + t.Tag
}
func (t FakeReference) String() string {
return t.Name()
}
func (t FakeReference) Scope(action string) string {
return t.Repository.Scope(action)
}
func TestResolve(t *testing.T) {
var callCount int
failed := false
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
callCount++
if !failed {
fmt.Fprintf(w, "ok")
return
}
failed = false
w.WriteHeader(http.StatusBadRequest)
}))
defer svr.Close()
pool := NewPool()
repo, err := name.NewRepository(strings.TrimPrefix(svr.URL, "http://")+"/nginx", name.WeakValidation, name.Insecure)
require.NoError(t, err)
ref := &FakeReference{
Tag: "latest",
Repository: repo,
}
url1, tr1, err := pool.Resolve(ref, "fake digest", nil)
require.NoError(t, err)
// auth request + redirect request
require.Equal(t, 2, callCount)
// reset
callCount = 0
url2, tr2, err := pool.Resolve(ref, "fake digest", nil)
// get transport from pool and call redirect
require.Equal(t, 1, callCount)
require.NoError(t, err)
require.Equal(t, tr1, tr2)
require.Equal(t, url1, url2)
failed = true
// reset
callCount = 0
url3, tr3, err := pool.Resolve(ref, "fake digest", nil)
// redirect failed and retry redirect + auth
require.Equal(t, 3, callCount)
require.NoError(t, err)
require.Equal(t, tr2, tr3)
require.Equal(t, url2, url3)
}
================================================
FILE: snapshot/mount_option.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package snapshot
import (
"context"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"strings"
"github.com/containerd/containerd/v2/core/mount"
"github.com/containerd/containerd/v2/core/snapshots"
"github.com/containerd/containerd/v2/core/snapshots/storage"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/config/daemonconfig"
"github.com/containerd/nydus-snapshotter/pkg/label"
"github.com/containerd/nydus-snapshotter/pkg/layout"
"github.com/containerd/nydus-snapshotter/pkg/rafs"
"github.com/containerd/nydus-snapshotter/pkg/snapshot"
"github.com/pkg/errors"
)
const (
KataVirtualVolumeDefaultSource = "overlay"
KataVirtualVolumeDummySource = "dummy-image-reference"
)
type ExtraOption struct {
Source string `json:"source"`
Config string `json:"config"`
Snapshotdir string `json:"snapshotdir"`
Version string `json:"fs_version"`
}
func (o *snapshotter) remoteMountWithExtraOptions(ctx context.Context, s storage.Snapshot, id string, overlayOptions []string) ([]mount.Mount, error) {
source, err := o.fs.BootstrapFile(id)
if err != nil {
return nil, err
}
instance := rafs.RafsGlobalCache.Get(id)
daemon, err := o.fs.GetDaemonByID(instance.DaemonID)
if err != nil {
return nil, errors.Wrapf(err, "get daemon with ID %s", instance.DaemonID)
}
var c daemonconfig.DaemonConfig
if daemon.IsSharedDaemon() {
c, err = daemonconfig.NewDaemonConfig(daemon.States.FsDriver, daemon.ConfigFile(instance.SnapshotID))
if err != nil {
return nil, errors.Wrapf(err, "Failed to load instance configuration %s",
daemon.ConfigFile(instance.SnapshotID))
}
} else {
c = daemon.Config
}
configContent, err := c.DumpString()
if err != nil {
return nil, errors.Wrapf(err, "remoteMounts: failed to marshal config")
}
// get version from bootstrap
f, err := os.Open(source)
if err != nil {
return nil, errors.Wrapf(err, "remoteMounts: check bootstrap version: failed to open bootstrap")
}
defer f.Close()
header := make([]byte, 4096)
sz, err := f.Read(header)
if err != nil {
return nil, errors.Wrapf(err, "remoteMounts: check bootstrap version: failed to read bootstrap")
}
version, err := layout.DetectFsVersion(header[0:sz])
if err != nil {
return nil, errors.Wrapf(err, "remoteMounts: failed to detect filesystem version")
}
// when enable nydus-overlayfs, return unified mount slice for runc and kata
extraOption := &ExtraOption{
Source: source,
Config: configContent,
Snapshotdir: o.snapshotDir(s.ID),
Version: version,
}
no, err := json.Marshal(extraOption)
if err != nil {
return nil, errors.Wrapf(err, "remoteMounts: failed to marshal NydusOption")
}
// XXX: Log options without extraoptions as it might contain secrets.
log.G(ctx).Debugf("fuse.nydus-overlayfs mount options %v", overlayOptions)
// base64 to filter easily in `nydus-overlayfs`
opt := fmt.Sprintf("extraoption=%s", base64.StdEncoding.EncodeToString(no))
overlayOptions = append(overlayOptions, opt)
mountType := "fuse.nydus-overlayfs"
if o.nydusOverlayFSPath != "" {
log.G(ctx).Infof("Using nydus-overlayfs from path: %s", o.nydusOverlayFSPath)
mountType = fmt.Sprintf("fuse.%s", o.nydusOverlayFSPath)
}
return []mount.Mount{
{
Type: mountType,
Source: KataVirtualVolumeDefaultSource,
Options: overlayOptions,
},
}, nil
}
func (o *snapshotter) mountWithKataVolume(ctx context.Context, id string, overlayOptions []string, key string) ([]mount.Mount, error) {
hasVolume := false
rafs := rafs.RafsGlobalCache.Get(id)
if rafs == nil {
return []mount.Mount{}, errors.Errorf("failed to find RAFS instance for snapshot %s", id)
}
// Insert Kata volume for proxy
if label.IsNydusProxyMode(rafs.Annotations) {
options, err := o.mountWithProxyVolume(*rafs)
if err != nil {
return []mount.Mount{}, errors.Wrapf(err, "create kata volume for proxy")
}
if len(options) > 0 {
overlayOptions = append(overlayOptions, options...)
hasVolume = true
}
}
// Insert Kata volume for tarfs
if blobID, ok := rafs.Annotations[label.NydusTarfsLayer]; ok {
options, err := o.mountWithTarfsVolume(ctx, *rafs, blobID, key)
if err != nil {
return []mount.Mount{}, errors.Wrapf(err, "create kata volume for tarfs")
}
if len(options) > 0 {
overlayOptions = append(overlayOptions, options...)
hasVolume = true
}
}
if hasVolume {
log.G(ctx).Debugf("fuse.nydus-overlayfs mount options %v", overlayOptions)
mountType := "fuse.nydus-overlayfs"
if o.nydusOverlayFSPath != "" {
log.G(ctx).Infof("Using nydus-overlayfs from path: %s", o.nydusOverlayFSPath)
mountType = fmt.Sprintf("fuse.%s", o.nydusOverlayFSPath)
}
mounts := []mount.Mount{
{
Type: mountType,
Source: KataVirtualVolumeDefaultSource,
Options: overlayOptions,
},
}
return mounts, nil
}
return overlayMount(overlayOptions), nil
}
func (o *snapshotter) mountWithProxyVolume(rafs rafs.Rafs) ([]string, error) {
options := []string{}
source := rafs.Annotations[label.CRIImageRef]
// In the normal flow, this should correctly return the imageRef. However, passing the CRIImageRef label
// from containerd is not supported. Therefore, the source will be set to "".
// But in this case, kata runtime-rs has a non-empty check for the source field. To ensure this field
// remains non-empty, a forced assignment is used here. This does not affect the passing of information.
// it is solely to pass the check.
if len(source) == 0 {
source = KataVirtualVolumeDummySource
}
for k, v := range rafs.Annotations {
options = append(options, fmt.Sprintf("%s=%s", k, v))
}
opt, err := o.prepareKataVirtualVolume(label.NydusProxyMode, source, KataVirtualVolumeImageGuestPullType, "", options, rafs.Annotations)
if err != nil {
return options, errors.Wrapf(err, "failed to prepare KataVirtualVolume")
}
return []string{opt}, nil
}
func (o *snapshotter) mountWithTarfsVolume(ctx context.Context, rafs rafs.Rafs, blobID, key string) ([]string, error) {
options := []string{}
if info, ok := rafs.Annotations[label.NydusImageBlockInfo]; ok {
path, err := o.fs.GetTarfsImageDiskFilePath(blobID)
if err != nil {
return []string{}, errors.Wrapf(err, "get tarfs image disk file path")
}
log.L.Debugf("mountWithTarfsVolume info %v", info)
opt, err := o.prepareKataVirtualVolume(label.NydusImageBlockInfo, path, KataVirtualVolumeImageRawBlockType, "erofs", []string{"ro"}, rafs.Annotations)
if err != nil {
return options, errors.Wrapf(err, "failed to prepare KataVirtualVolume for image_raw_block")
}
options = append(options, opt)
log.L.Debugf("mountWithTarfsVolume type=%v, options %v", KataVirtualVolumeImageRawBlockType, options)
return options, nil
}
if _, ok := rafs.Annotations[label.NydusLayerBlockInfo]; ok {
for {
pID, pInfo, _, pErr := snapshot.GetSnapshotInfo(ctx, o.ms, key)
log.G(ctx).Debugf("mountWithKataVolume pID= %v, pInfo = %v", pID, pInfo)
if pErr != nil {
return options, errors.Wrapf(pErr, "failed to get snapshot info")
}
if pInfo.Kind == snapshots.KindActive {
key = pInfo.Parent
continue
}
blobID = pInfo.Labels[label.NydusTarfsLayer]
path, err := o.fs.GetTarfsLayerDiskFilePath(blobID)
if err != nil {
return options, errors.Wrapf(err, "get tarfs image disk file path")
}
opt, err := o.prepareKataVirtualVolume(label.NydusLayerBlockInfo, path, KataVirtualVolumeLayerRawBlockType, "erofs", []string{"ro"}, pInfo.Labels)
if err != nil {
return options, errors.Wrapf(err, "failed to prepare KataVirtualVolume for layer_raw_block")
}
options = append(options, opt)
if pInfo.Parent == "" {
break
}
key = pInfo.Parent
}
log.L.Debugf("mountWithTarfsVolume type=%v, options %v", KataVirtualVolumeLayerRawBlockType, options)
return options, nil
}
// If Neither image_raw_block or layer_raw_block, return empty strings
return options, nil
}
func (o *snapshotter) prepareKataVirtualVolume(blockType, source, volumeType, fsType string, options []string, labels map[string]string) (string, error) {
volume := &KataVirtualVolume{
VolumeType: volumeType,
Source: source,
FSType: fsType,
Options: options,
}
switch blockType {
case label.NydusImageBlockInfo, label.NydusLayerBlockInfo:
dmverityInfo := labels[blockType]
if len(dmverityInfo) > 0 {
dmverity, err := parseTarfsDmVerityInfo(dmverityInfo)
if err != nil {
return "", err
}
volume.DmVerity = &dmverity
}
case label.NydusProxyMode:
volume.ImagePull = &ImagePullVolume{Metadata: labels}
}
if !volume.Validate() {
return "", errors.Errorf("got invalid kata volume, %v", volume)
}
info, err := EncodeKataVirtualVolumeToBase64(*volume)
if err != nil {
return "", errors.Errorf("failed to encoding Kata Volume info %v", volume)
}
opt := fmt.Sprintf("%s=%s", KataVirtualVolumeOptionName, info)
return opt, nil
}
func parseTarfsDmVerityInfo(info string) (DmVerityInfo, error) {
var dataBlocks, hashOffset uint64
var rootHash string
pattern := "%d,%d,sha256:%s"
if count, err := fmt.Sscanf(info, pattern, &dataBlocks, &hashOffset, &rootHash); err == nil && count == 3 {
di := DmVerityInfo{
HashType: "sha256",
Hash: rootHash,
BlockNum: dataBlocks,
Blocksize: 512,
Hashsize: 4096,
Offset: hashOffset,
}
if err := di.Validate(); err != nil {
return DmVerityInfo{}, errors.Wrap(err, "validate dm-verity information")
}
return di, nil
}
return DmVerityInfo{}, errors.Errorf("invalid dm-verity information: %s", info)
}
// Consts and data structures for Kata Virtual Volume
const (
minBlockSize = 1 << 9
maxBlockSize = 1 << 19
)
const (
KataVirtualVolumeOptionName = "io.katacontainers.volume"
KataVirtualVolumeDirectBlockType = "direct_block"
KataVirtualVolumeImageRawBlockType = "image_raw_block"
KataVirtualVolumeLayerRawBlockType = "layer_raw_block"
KataVirtualVolumeImageNydusBlockType = "image_nydus_block"
KataVirtualVolumeLayerNydusBlockType = "layer_nydus_block"
KataVirtualVolumeImageNydusFsType = "image_nydus_fs"
KataVirtualVolumeLayerNydusFsType = "layer_nydus_fs"
KataVirtualVolumeImageGuestPullType = "image_guest_pull"
)
// DmVerityInfo contains configuration information for DmVerity device.
type DmVerityInfo struct {
HashType string `json:"hashtype"`
Hash string `json:"hash"`
BlockNum uint64 `json:"blocknum"`
Blocksize uint64 `json:"blocksize"`
Hashsize uint64 `json:"hashsize"`
Offset uint64 `json:"offset"`
}
func (d *DmVerityInfo) Validate() error {
err := d.validateHashType()
if err != nil {
return err
}
if d.BlockNum == 0 || d.BlockNum > uint64(^uint32(0)) {
return fmt.Errorf("zero block count for DmVerity device %s", d.Hash)
}
if !validateBlockSize(d.Blocksize) || !validateBlockSize(d.Hashsize) {
return fmt.Errorf("unsupported verity block size: data_block_size = %d, hash_block_size = %d", d.Blocksize, d.Hashsize)
}
if d.Offset%d.Hashsize != 0 || d.Offset < d.Blocksize*d.BlockNum {
return fmt.Errorf("invalid hashvalue offset %d for DmVerity device %s", d.Offset, d.Hash)
}
return nil
}
func (d *DmVerityInfo) validateHashType() error {
switch strings.ToLower(d.HashType) {
case "sha256":
return d.validateHash(64, "sha256")
case "sha1":
return d.validateHash(40, "sha1")
default:
return fmt.Errorf("unsupported hash algorithm %s for DmVerity device %s", d.HashType, d.Hash)
}
}
func (d *DmVerityInfo) validateHash(expectedLen int, hashType string) error {
_, err := hex.DecodeString(d.Hash)
if len(d.Hash) != expectedLen || err != nil {
return fmt.Errorf("invalid hash value %s:%s for DmVerity device with %s", hashType, d.Hash, hashType)
}
return nil
}
func validateBlockSize(blockSize uint64) bool {
return minBlockSize <= blockSize && blockSize <= maxBlockSize
}
func ParseDmVerityInfo(option string) (*DmVerityInfo, error) {
no := &DmVerityInfo{}
if err := json.Unmarshal([]byte(option), no); err != nil {
return nil, errors.Wrapf(err, "DmVerityInfo json unmarshal err")
}
if err := no.Validate(); err != nil {
return nil, fmt.Errorf("DmVerityInfo is not correct, %+v; error = %+v", no, err)
}
return no, nil
}
// DirectAssignedVolume contains meta information for a directly assigned volume.
type DirectAssignedVolume struct {
Metadata map[string]string `json:"metadata"`
}
func (d *DirectAssignedVolume) Validate() bool {
return d.Metadata != nil
}
// ImagePullVolume contains meta information for pulling an image inside the guest.
type ImagePullVolume struct {
Metadata map[string]string `json:"metadata"`
}
func (i *ImagePullVolume) Validate() bool {
return i.Metadata != nil
}
// NydusImageVolume contains Nydus image volume information.
type NydusImageVolume struct {
Config string `json:"config"`
SnapshotDir string `json:"snapshot_dir"`
}
func (n *NydusImageVolume) Validate() bool {
return len(n.Config) > 0 || len(n.SnapshotDir) > 0
}
// KataVirtualVolume encapsulates information for extra mount options and direct volumes.
type KataVirtualVolume struct {
VolumeType string `json:"volume_type"`
Source string `json:"source,omitempty"`
FSType string `json:"fs_type,omitempty"`
Options []string `json:"options,omitempty"`
DirectVolume *DirectAssignedVolume `json:"direct_volume,omitempty"`
ImagePull *ImagePullVolume `json:"image_pull,omitempty"`
NydusImage *NydusImageVolume `json:"nydus_image,omitempty"`
DmVerity *DmVerityInfo `json:"dm_verity,omitempty"`
}
func (k *KataVirtualVolume) Validate() bool {
switch k.VolumeType {
case KataVirtualVolumeDirectBlockType:
if k.Source != "" && k.DirectVolume != nil && k.DirectVolume.Validate() {
return true
}
case KataVirtualVolumeImageRawBlockType, KataVirtualVolumeLayerRawBlockType:
if k.Source != "" && (k.DmVerity == nil || k.DmVerity.Validate() == nil) {
return true
}
case KataVirtualVolumeImageNydusBlockType, KataVirtualVolumeLayerNydusBlockType, KataVirtualVolumeImageNydusFsType, KataVirtualVolumeLayerNydusFsType:
if k.Source != "" && k.NydusImage != nil && k.NydusImage.Validate() {
return true
}
case KataVirtualVolumeImageGuestPullType:
if k.ImagePull != nil && k.ImagePull.Validate() {
return true
}
}
return false
}
func ParseKataVirtualVolume(option []byte) (*KataVirtualVolume, error) {
no := &KataVirtualVolume{}
if err := json.Unmarshal(option, no); err != nil {
return nil, errors.Wrapf(err, "KataVirtualVolume json unmarshal err")
}
if !no.Validate() {
return nil, fmt.Errorf("KataVirtualVolume is not correct, %+v", no)
}
return no, nil
}
func ParseKataVirtualVolumeFromBase64(option string) (*KataVirtualVolume, error) {
opt, err := base64.StdEncoding.DecodeString(option)
if err != nil {
return nil, errors.Wrap(err, "KataVirtualVolume base64 decoding err")
}
return ParseKataVirtualVolume(opt)
}
func EncodeKataVirtualVolumeToBase64(volume KataVirtualVolume) (string, error) {
validKataVirtualVolumeJSON, err := json.Marshal(volume)
if err != nil {
return "", errors.Wrapf(err, "marshal KataVirtualVolume object")
}
log.L.Infof("encode kata volume %s", validKataVirtualVolumeJSON)
option := base64.StdEncoding.EncodeToString(validKataVirtualVolumeJSON)
return option, nil
}
================================================
FILE: snapshot/mount_option_test.go
================================================
package snapshot
import (
"encoding/base64"
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
)
func TestDmVerityInfoValidation(t *testing.T) {
TestData := []DmVerityInfo{
{
HashType: "md5", // "md5" is not a supported hash algorithm
Blocksize: 512,
Hashsize: 512,
BlockNum: 16384,
Offset: 8388608,
Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174",
},
{
HashType: "sha256",
Blocksize: 3000, // Invalid block size, not a power of 2.
Hashsize: 512,
BlockNum: 16384,
Offset: 8388608,
Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174",
},
{
HashType: "sha256",
Blocksize: 0, // Invalid block size, less than 512.
Hashsize: 512,
BlockNum: 16384,
Offset: 8388608,
Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174",
},
{
HashType: "sha256",
Blocksize: 524800, // Invalid block size, greater than 524288.
Hashsize: 512,
BlockNum: 16384,
Offset: 8388608,
Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174",
},
{
HashType: "sha256",
Blocksize: 512,
Hashsize: 3000, // Invalid hash block size, not a power of 2.
BlockNum: 16384,
Offset: 8388608,
Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174",
},
{
HashType: "sha256",
Blocksize: 512,
Hashsize: 0, // Invalid hash block size, less than 512.
BlockNum: 16384,
Offset: 8388608,
Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174",
},
{
HashType: "sha256",
Blocksize: 512,
Hashsize: 524800, // Invalid hash block size, greater than 524288.
BlockNum: 16384,
Offset: 8388608,
Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174",
},
{
HashType: "sha256",
Blocksize: 512,
Hashsize: 512,
BlockNum: 0, // Invalid BlockNum, it must be greater than 0.
Offset: 8388608,
Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174",
},
{
HashType: "sha256",
Blocksize: 512,
Hashsize: 512,
BlockNum: 16384,
Offset: 0, // Invalid offset, it must be greater than 0.
Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174",
},
{
HashType: "sha256",
Blocksize: 512,
Hashsize: 512,
BlockNum: 16384,
Offset: 8193, // Invalid offset, it must be aligned to 512.
Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174",
},
{
HashType: "sha256",
Blocksize: 512,
Hashsize: 512,
BlockNum: 16384,
Offset: 8388608 - 4096, // Invalid offset, it must be equal to blocksize * BlockNum.
Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174",
},
}
for _, d := range TestData {
assert.Error(t, d.Validate())
}
TestCorrectData := DmVerityInfo{
HashType: "sha256",
Blocksize: 512,
Hashsize: 512,
BlockNum: 16384,
Offset: 8388608,
Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174",
}
assert.NoError(t, TestCorrectData.Validate())
}
func TestDirectAssignedVolumeValidation(t *testing.T) {
validDirectVolume := DirectAssignedVolume{
Metadata: map[string]string{"key": "value"},
}
assert.True(t, validDirectVolume.Validate())
invalidDirectVolume := DirectAssignedVolume{
Metadata: nil,
}
assert.False(t, invalidDirectVolume.Validate())
}
func TestImagePullVolumeValidation(t *testing.T) {
validImagePull := ImagePullVolume{
Metadata: map[string]string{"key": "value"},
}
assert.True(t, validImagePull.Validate())
invalidImagePull := ImagePullVolume{
Metadata: nil,
}
assert.False(t, invalidImagePull.Validate())
}
func TestNydusImageVolumeValidation(t *testing.T) {
validNydusImage := NydusImageVolume{
Config: "config_value",
SnapshotDir: "",
}
assert.True(t, validNydusImage.Validate())
invalidNydusImage := NydusImageVolume{
Config: "",
SnapshotDir: "",
}
assert.False(t, invalidNydusImage.Validate())
}
func TestKataVirtualVolumeValidation(t *testing.T) {
validKataVirtualVolume := KataVirtualVolume{
VolumeType: "direct_block",
Source: "/dev/sdb",
FSType: "ext4",
Options: []string{"rw"},
DirectVolume: &DirectAssignedVolume{
Metadata: map[string]string{"key": "value"},
},
// Initialize other fields
}
assert.True(t, validKataVirtualVolume.Validate())
invalidKataVirtualVolume := KataVirtualVolume{
VolumeType: "direct_block",
Source: "/dev/sdb",
FSType: "",
Options: nil,
DirectVolume: &DirectAssignedVolume{
Metadata: nil,
},
// Initialize other fields
}
assert.False(t, invalidKataVirtualVolume.Validate())
}
func TestParseDmVerityInfo(t *testing.T) {
// Create a mock valid KataVirtualVolume
validDmVerityInfo := DmVerityInfo{
HashType: "sha256",
Blocksize: 512,
Hashsize: 512,
BlockNum: 16384,
Offset: 8388608,
Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174",
}
validKataVirtualVolumeJSON, _ := json.Marshal(validDmVerityInfo)
t.Run("Valid Option", func(t *testing.T) {
volume, err := ParseDmVerityInfo(string(validKataVirtualVolumeJSON))
assert.NoError(t, err)
assert.NotNil(t, volume)
assert.NoError(t, volume.Validate())
})
t.Run("Invalid JSON Option", func(t *testing.T) {
volume, err := ParseDmVerityInfo("invalid_json")
assert.Error(t, err)
assert.Nil(t, volume)
})
}
func TestParseKataVirtualVolume(t *testing.T) {
// Create a mock valid KataVirtualVolume
validKataVirtualVolume := KataVirtualVolume{
VolumeType: "direct_block",
Source: "/dev/sdb",
FSType: "ext4",
Options: []string{"rw"},
DirectVolume: &DirectAssignedVolume{
Metadata: map[string]string{"key": "value"},
},
// Initialize other fields
}
validOption, err := EncodeKataVirtualVolumeToBase64(validKataVirtualVolume)
assert.Nil(t, err)
t.Run("Valid Option", func(t *testing.T) {
volume, err := ParseKataVirtualVolumeFromBase64(validOption)
assert.NoError(t, err)
assert.NotNil(t, volume)
assert.True(t, volume.Validate())
})
t.Run("Invalid JSON Option", func(t *testing.T) {
invalidJSONOption := base64.StdEncoding.EncodeToString([]byte("invalid_json"))
volume, err := ParseKataVirtualVolumeFromBase64(invalidJSONOption)
assert.Error(t, err)
assert.Nil(t, volume)
})
invalidBase64Option := "invalid_base64"
t.Run("Invalid Base64 Option", func(t *testing.T) {
volume, err := ParseKataVirtualVolumeFromBase64(invalidBase64Option)
assert.Error(t, err)
assert.Nil(t, volume)
})
t.Run("Invalid Fields", func(t *testing.T) {
// Create a mock invalid KataVirtualVolume
validKataVirtualVolume = KataVirtualVolume{
VolumeType: "direct_block",
Source: "/dev/sdb",
FSType: "ext4",
Options: []string{"rw"},
// Initialize other fields
}
invalidFieldOption, err := EncodeKataVirtualVolumeToBase64(validKataVirtualVolume)
assert.Nil(t, err)
volume, err := ParseKataVirtualVolumeFromBase64(invalidFieldOption)
assert.Error(t, err)
assert.Nil(t, volume)
})
}
================================================
FILE: snapshot/process.go
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package snapshot
import (
"context"
"path"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/containerd/containerd/v2/core/mount"
"github.com/containerd/containerd/v2/core/snapshots/storage"
snpkg "github.com/containerd/containerd/v2/pkg/snapshotters"
"github.com/containerd/nydus-snapshotter/config"
"github.com/containerd/nydus-snapshotter/pkg/label"
"github.com/containerd/nydus-snapshotter/pkg/snapshot"
)
// `storageLocater` provides a local storage for each handler to save their intermediates.
// Different actions for different layer types
func chooseProcessor(ctx context.Context, logger *logrus.Entry,
sn *snapshotter, s storage.Snapshot, key, parent string, labels map[string]string,
storageLocater func() string) (_ func() (bool, []mount.Mount, error), target string, commitLabels map[string]string, err error) {
var handler func() (bool, []mount.Mount, error)
commitLabels = labels
// Handler to prepare a directory for containerd to download and unpacking layer.
defaultHandler := func() (bool, []mount.Mount, error) {
mounts, err := sn.mountNative(ctx, labels, s)
return false, mounts, err
}
// Handler to stop containerd from downloading and unpacking layer.
skipHandler := func() (bool, []mount.Mount, error) {
return true, nil, nil
}
remoteHandler := func(id string, labels map[string]string) func() (bool, []mount.Mount, error) {
return func() (bool, []mount.Mount, error) {
logger.Debugf("Prepare remote snapshot %s", id)
if err := sn.fs.Mount(ctx, id, labels, &s); err != nil {
return false, nil, err
}
// Let Prepare operation show the rootfs content.
if err := sn.fs.WaitUntilReady(id); err != nil {
return false, nil, err
}
logger.Infof("Nydus remote snapshot %s is ready", id)
mounts, err := sn.mountRemote(ctx, labels, s, id, key)
return false, mounts, err
}
}
proxyHandler := func() (bool, []mount.Mount, error) {
mounts, err := sn.mountProxy(ctx, s)
return false, mounts, err
}
// OCI image is also marked with "containerd.io/snapshot.ref" by Containerd
target, isRoLayer := labels[label.TargetSnapshotRef]
if isRoLayer {
// Containerd won't consume mount slice for below snapshots
switch {
case config.GetFsDriver() == config.FsDriverProxy:
logger.Debugf("proxy image pull request to other agents")
if ref := labels[label.CRILayerDigest]; len(ref) > 0 {
labels[label.NydusProxyMode] = "true"
handler = skipHandler
} else {
return nil, "", nil, errors.Errorf("missing CRI reference annotation for snapshot %s", s.ID)
}
case label.IsNydusMetaLayer(labels):
logger.Debugf("found nydus meta layer")
handler = defaultHandler
case label.IsNydusDataLayer(labels):
logger.Debugf("found nydus data layer")
handler = skipHandler
case sn.fs.CheckIndexAlternative(ctx, labels):
logger.Debugf("found nydus alternative image in index")
commitLabels[label.NydusIndexAlternative] = "true"
handler = skipHandler
case sn.fs.CheckReferrer(ctx, labels):
logger.Debugf("found referenced nydus manifest")
handler = skipHandler
default:
if sn.fs.StargzEnabled() {
// Check if the blob is format of estargz
if ok, blob := sn.fs.IsStargzDataLayer(labels); ok {
err := sn.fs.PrepareStargzMetaLayer(blob, storageLocater(), labels)
if err != nil {
logger.Errorf("prepare stargz layer of snapshot ID %s, err: %v", s.ID, err)
} else {
logger.Debugf("found estargz data layer")
// Mark this snapshot as stargz layer since estargz image format does not
// has special annotation or media type.
labels[label.StargzLayer] = "true"
handler = skipHandler
}
}
}
if handler == nil && sn.fs.TarfsEnabled() {
logger.Debugf("convert OCIv1 layer to tarfs")
err := sn.fs.PrepareTarfsLayer(ctx, labels, s.ID, sn.upperPath(s.ID))
if err != nil {
logger.Warnf("snapshot ID %s can't be converted into tarfs, fallback to containerd, err: %v", s.ID, err)
} else {
if config.GetTarfsExportEnabled() {
_, err = sn.fs.ExportBlockData(s, true, labels, func(id string) string { return sn.upperPath(id) })
if err != nil {
return nil, "", nil, errors.Wrap(err, "export layer as tarfs block device")
}
}
handler = skipHandler
}
}
}
} else {
// Container writable layer comes into this branch.
// It should not be committed during this Prepare() operation.
pID, pInfo, _, pErr := snapshot.GetSnapshotInfo(ctx, sn.ms, parent)
if treatAsProxyDriver(pInfo.Labels) {
logger.Warnf("treat as proxy mode for the prepared snapshot by other snapshotter possibly: id = %s, labels = %v", pID, pInfo.Labels)
handler = proxyHandler
}
if pErr == nil && label.IsNydusProxyMode(pInfo.Labels) {
logger.Infof("Prepare active snapshot %s in proxy mode", key)
handler = remoteHandler(pID, pInfo.Labels)
}
// Hope to find bootstrap layer and prepares to start nydusd
// TODO: Trying find nydus meta layer will slow down setting up rootfs to OCI images
if handler == nil {
if id, info, err := sn.findMetaLayer(ctx, key); err == nil {
logger.Infof("Prepare active Nydus snapshot %s", key)
handler = remoteHandler(id, info.Labels)
}
}
if handler == nil && sn.fs.IndexDetectEnabled() {
if id, info, err := sn.findIndexAlternativeLayer(ctx, key); err == nil {
logger.Infof("Found nydus alternative image in index for image: %s", info.Labels[snpkg.TargetRefLabel])
metaPath := path.Join(sn.snapshotDir(id), "fs", "image.boot")
if err := sn.fs.TryFetchMetadataFromIndex(ctx, info.Labels, metaPath); err != nil {
return nil, "", nil, errors.Wrap(err, "try fetch metadata")
}
handler = remoteHandler(id, info.Labels)
}
}
if handler == nil && sn.fs.ReferrerDetectEnabled() {
if id, info, err := sn.findReferrerLayer(ctx, key); err == nil {
logger.Infof("Found referenced nydus manifest for image: %s", info.Labels[snpkg.TargetRefLabel])
metaPath := path.Join(sn.snapshotDir(id), "fs", "image.boot")
if err := sn.fs.TryFetchMetadata(ctx, info.Labels, metaPath); err != nil {
return nil, "", nil, errors.Wrap(err, "try fetch metadata")
}
handler = remoteHandler(id, info.Labels)
}
}
if handler == nil && pErr == nil && sn.fs.StargzEnabled() && sn.fs.StargzLayer(pInfo.Labels) {
if err := sn.fs.MergeStargzMetaLayer(ctx, s); err != nil {
return nil, "", nil, errors.Wrap(err, "merge stargz meta layers")
}
handler = remoteHandler(pID, pInfo.Labels)
logger.Infof("Generated estargz merged meta for %s", key)
}
if handler == nil && pErr == nil && sn.fs.TarfsEnabled() && label.IsTarfsDataLayer(pInfo.Labels) {
// Merge and mount tarfs on the uppermost parent layer.
// TODO may need to check all parrent layers, in case share layers with other images
// which have already been prepared by overlay snapshotter
logger.Infof("Prepare active snapshot %s in Nydus tarfs mode", key)
err = sn.mergeTarfs(ctx, s, pID, pInfo)
if err != nil {
return nil, "", nil, errors.Wrapf(err, "merge tarfs layers for snapshot %s", pID)
}
logger.Infof("Prepared active snapshot %s in Nydus tarfs mode", key)
handler = remoteHandler(pID, pInfo.Labels)
}
}
if handler == nil {
handler = defaultHandler
}
return handler, target, commitLabels, err
}
================================================
FILE: snapshot/renewal.go
================================================
/*
* Copyright (c) 2026. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package snapshot
import (
"context"
"time"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/pkg/auth"
"github.com/containerd/nydus-snapshotter/pkg/daemon/types"
mgr "github.com/containerd/nydus-snapshotter/pkg/manager"
)
// startCredentialRenewal initializes the credential store, runs an initial
// reconciliation, and starts a background goroutine that periodically
// renews credentials and hot-reloads running nydusd daemons.
func startCredentialRenewal(ctx context.Context, interval time.Duration, managers []*mgr.Manager) {
auth.InitCredentialStore(interval)
reconcileCredentials(ctx, managers)
log.G(ctx).WithField("interval", interval).Info("credential renewal initialized")
go credentialRenewalLoop(ctx, interval, managers)
}
// credentialRenewalLoop is the background goroutine that periodically
// reconciles and renews credentials.
func credentialRenewalLoop(ctx context.Context, interval time.Duration, managers []*mgr.Manager) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
reconcileCredentials(ctx, managers)
}
}
}
// reconcileCredentials reconciles the credential store against the live RAFS
// instances by walking managers -> daemons -> rafs, and renews all entries
// that should be active. Stale store entries (refs no longer backed by a
// running rafs) are evicted.
// 4 different situations are possible for each live ref:
// - entry in store, live in RAFS: renew + hot-reload (fusedev only)
// - entry not in store, live in RAFS: add + hot-reload (fusedev only)
// - entry not in store, not live in RAFS: nothing
// - entry in store, not live in RAFS: evict
func reconcileCredentials(ctx context.Context, managers []*mgr.Manager) {
live := make(map[string]struct{})
for _, m := range managers {
for _, d := range m.ListDaemons() {
if d.State() != types.DaemonStateRunning {
continue
}
for _, r := range d.RafsCache.List() {
if r.ImageID == "" {
continue
}
live[r.ImageID] = struct{}{}
old := auth.GetStoredCredential(r.ImageID)
log.L.WithField("ref", r.ImageID).Debug("renewing credential entry")
kc := auth.RenewCredential(r.ImageID)
if kc == nil || (old != nil && old.ToBase64() == kc.ToBase64()) {
continue
}
if err := d.UpdateAuthConfig(r.SnapshotID, kc); err != nil {
log.G(ctx).WithError(err).WithField("daemon", d.ID()).
Warn("failed to hot-reload auth config")
}
}
}
}
auth.EvictStaleCredentials(live)
}
================================================
FILE: snapshot/renewal_test.go
================================================
/*
* Copyright (c) 2026. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package snapshot
import (
"context"
"testing"
"time"
mgr "github.com/containerd/nydus-snapshotter/pkg/manager"
)
// TestStartCredentialRenewalLifecycle verifies that the renewal goroutine
// starts, ticks, and stops cleanly on context cancellation. The managers
// list is empty so reconcile is a no-op each tick.
func TestStartCredentialRenewalLifecycle(t *testing.T) {
ctx, cancel := context.WithCancel(t.Context())
startCredentialRenewal(ctx, 30*time.Millisecond, []*mgr.Manager{})
// Let it tick a few times without error.
time.Sleep(100 * time.Millisecond)
cancel()
// Give goroutine time to observe cancellation.
time.Sleep(60 * time.Millisecond)
}
================================================
FILE: snapshot/snapshot.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package snapshot
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/pkg/errors"
"github.com/containerd/containerd/v2/core/mount"
"github.com/containerd/containerd/v2/core/snapshots"
"github.com/containerd/containerd/v2/core/snapshots/storage"
snpkg "github.com/containerd/containerd/v2/pkg/snapshotters"
"github.com/containerd/continuity/fs"
"github.com/containerd/log"
"github.com/containerd/nydus-snapshotter/config"
"github.com/containerd/nydus-snapshotter/config/daemonconfig"
"github.com/containerd/nydus-snapshotter/pkg/rafs"
"github.com/containerd/nydus-snapshotter/pkg/cache"
"github.com/containerd/nydus-snapshotter/pkg/cgroup"
v2 "github.com/containerd/nydus-snapshotter/pkg/cgroup/v2"
"github.com/containerd/nydus-snapshotter/pkg/errdefs"
"github.com/containerd/nydus-snapshotter/pkg/index"
mgr "github.com/containerd/nydus-snapshotter/pkg/manager"
"github.com/containerd/nydus-snapshotter/pkg/metrics"
"github.com/containerd/nydus-snapshotter/pkg/metrics/collector"
"github.com/containerd/nydus-snapshotter/pkg/metrics/data"
"github.com/containerd/nydus-snapshotter/pkg/pprof"
"github.com/containerd/nydus-snapshotter/pkg/referrer"
"github.com/containerd/nydus-snapshotter/pkg/system"
"github.com/containerd/nydus-snapshotter/pkg/tarfs"
"github.com/containerd/nydus-snapshotter/pkg/store"
"github.com/containerd/nydus-snapshotter/pkg/filesystem"
"github.com/containerd/nydus-snapshotter/pkg/label"
"github.com/containerd/nydus-snapshotter/pkg/signature"
"github.com/containerd/nydus-snapshotter/pkg/snapshot"
)
var _ snapshots.Snapshotter = &snapshotter{}
type snapshotter struct {
root string
nydusdPath string
ms *storage.MetaStore // Storing snapshots' state, parentage and other metadata
fs *filesystem.Filesystem
cgroupManager *cgroup.Manager
enableNydusOverlayFS bool
nydusOverlayFSPath string
enableKataVolume bool
syncRemove bool
cleanupOnClose bool
enableOverlayfsVolatile bool
}
func NewSnapshotter(ctx context.Context, cfg *config.SnapshotterConfig) (snapshots.Snapshotter, error) {
verifier, err := signature.NewVerifier(cfg.ImageConfig.PublicKeyFile, cfg.ImageConfig.ValidateSignature)
if err != nil {
return nil, errors.Wrap(err, "initialize image verifier")
}
db, err := store.NewDatabase(cfg.Root)
if err != nil {
return nil, errors.Wrap(err, "create database")
}
rp, err := config.ParseRecoverPolicy(cfg.DaemonConfig.RecoverPolicy)
if err != nil {
return nil, errors.Wrap(err, "parse recover policy")
}
var cgroupMgr *cgroup.Manager
if cfg.CgroupConfig.Enable {
cgroupConfig, err := config.ParseCgroupConfig(cfg.CgroupConfig)
if err != nil {
return nil, errors.Wrap(err, "parse cgroup configuration")
}
log.L.Infof("parsed cgroup config: %#v", cgroupConfig)
cgroupMgr, err = cgroup.NewManager(cgroup.Opt{
Name: "nydusd",
Config: cgroupConfig,
})
if err != nil && (err != cgroup.ErrCgroupNotSupported || err != v2.ErrRootMemorySubtreeControllerDisabled) {
return nil, errors.Wrap(err, "create cgroup manager")
}
}
var skipSSLVerify bool
var daemonConfig *daemonconfig.DaemonConfig
fsDriver := config.GetFsDriver()
if fsDriver == config.FsDriverFscache || fsDriver == config.FsDriverFusedev {
config, err := daemonconfig.NewDaemonConfig(config.GetFsDriver(), cfg.DaemonConfig.NydusdConfigPath)
if err != nil {
return nil, errors.Wrap(err, "load daemon configuration")
}
daemonConfig = &config
_, backendConfig := config.StorageBackend()
skipSSLVerify = backendConfig.SkipVerify
} else {
skipSSLVerify = config.GetSkipSSLVerify()
}
fsManagers := []*mgr.Manager{}
if cfg.Experimental.TarfsConfig.EnableTarfs {
blockdevManager, err := mgr.NewManager(mgr.Opt{
NydusdBinaryPath: "",
Database: db,
CacheDir: cfg.CacheManagerConfig.CacheDir,
RootDir: cfg.Root,
RecoverPolicy: rp,
FsDriver: config.FsDriverBlockdev,
DaemonConfig: nil,
CgroupMgr: cgroupMgr,
})
if err != nil {
return nil, errors.Wrap(err, "create blockdevice manager")
}
fsManagers = append(fsManagers, blockdevManager)
}
if config.GetFsDriver() == config.FsDriverFscache {
fscacheManager, err := mgr.NewManager(mgr.Opt{
NydusdBinaryPath: cfg.DaemonConfig.NydusdPath,
Database: db,
CacheDir: cfg.CacheManagerConfig.CacheDir,
RootDir: cfg.Root,
RecoverPolicy: rp,
FsDriver: config.FsDriverFscache,
DaemonConfig: daemonConfig,
CgroupMgr: cgroupMgr,
})
if err != nil {
return nil, errors.Wrap(err, "create fscache manager")
}
fsManagers = append(fsManagers, fscacheManager)
}
if config.GetFsDriver() == config.FsDriverFusedev {
fusedevManager, err := mgr.NewManager(mgr.Opt{
NydusdBinaryPath: cfg.DaemonConfig.NydusdPath,
Database: db,
CacheDir: cfg.CacheManagerConfig.CacheDir,
RootDir: cfg.Root,
RecoverPolicy: rp,
FsDriver: config.FsDriverFusedev,
DaemonConfig: daemonConfig,
CgroupMgr: cgroupMgr,
})
if err != nil {
return nil, errors.Wrap(err, "create fusedev manager")
}
fsManagers = append(fsManagers, fusedevManager)
}
if config.GetFsDriver() == config.FsDriverProxy {
proxyManager, err := mgr.NewManager(mgr.Opt{
NydusdBinaryPath: "",
Database: db,
CacheDir: cfg.CacheManagerConfig.CacheDir,
RootDir: cfg.Root,
RecoverPolicy: rp,
FsDriver: config.FsDriverProxy,
DaemonConfig: nil,
CgroupMgr: cgroupMgr,
})
if err != nil {
return nil, errors.Wrap(err, "create proxy manager")
}
fsManagers = append(fsManagers, proxyManager)
}
metricServer, err := metrics.NewServer(
ctx,
metrics.WithProcessManagers(fsManagers),
metrics.WithHungIOInterval(cfg.MetricsConfig.HungIOInterval),
metrics.WithCollectInterval(cfg.MetricsConfig.CollectInterval),
)
if err != nil {
return nil, errors.Wrap(err, "create metrics server")
}
// Start to collect metrics.
if cfg.MetricsConfig.Address != "" {
if err := metrics.NewMetricsHTTPListenerServer(cfg.MetricsConfig.Address); err != nil {
return nil, errors.Wrap(err, "start metrics HTTP server")
}
go func() {
if err := metricServer.StartCollectMetrics(ctx); err != nil {
log.L.WithError(err).Errorf("Failed to start collecting metrics")
}
}()
log.L.Infof("Started metrics HTTP server on %q", cfg.MetricsConfig.Address)
}
opts := []filesystem.NewFSOpt{
filesystem.WithManagers(fsManagers),
filesystem.WithNydusdBinaryPath(cfg.DaemonConfig.NydusdPath),
filesystem.WithVerifier(verifier),
filesystem.WithRootMountpoint(config.GetRootMountpoint()),
filesystem.WithEnableStargz(cfg.Experimental.EnableStargz),
}
cacheConfig := &cfg.CacheManagerConfig
cacheMgr, err := cache.NewManager(cache.Opt{
Database: db,
Period: cfg.CacheManagerConfig.GCPeriod,
CacheDir: cacheConfig.CacheDir,
Disabled: cacheConfig.Disable,
})
if err != nil {
return nil, errors.Wrap(err, "create cache manager")
}
opts = append(opts, filesystem.WithCacheManager(cacheMgr))
if cfg.Experimental.EnableIndexDetect {
indexMgr := index.NewManager(skipSSLVerify)
opts = append(opts, filesystem.WithIndexManager(indexMgr))
}
if cfg.Experimental.EnableReferrerDetect {
referrerMgr := referrer.NewManager(skipSSLVerify)
opts = append(opts, filesystem.WithReferrerManager(referrerMgr))
}
if cfg.Experimental.TarfsConfig.EnableTarfs {
tarfsMgr := tarfs.NewManager(skipSSLVerify, cfg.Experimental.TarfsConfig.TarfsHint,
cacheConfig.CacheDir, cfg.DaemonConfig.NydusImagePath,
int64(cfg.Experimental.TarfsConfig.MaxConcurrentProc))
opts = append(opts, filesystem.WithTarfsManager(tarfsMgr))
}
nydusFs, err := filesystem.NewFileSystem(ctx, opts...)
if err != nil {
return nil, errors.Wrap(err, "initialize filesystem thin layer")
}
// Start credential renewal after NewFileSystem, which calls Manager.Recover()
// and populates the daemon caches from the DB. Starting earlier would cause
// the initial reconciliation to see empty managers on restart.
if interval := cfg.RemoteConfig.AuthConfig.CredentialRenewalInterval; interval > 0 {
startCredentialRenewal(ctx, interval, fsManagers)
}
if config.IsSystemControllerEnabled() {
systemController, err := system.NewSystemController(nydusFs, fsManagers, config.SystemControllerAddress(), cfg.SystemControllerConfig.UID, cfg.SystemControllerConfig.GID)
if err != nil {
return nil, errors.Wrap(err, "create system controller")
}
go func() {
if err := systemController.Run(); err != nil {
log.L.WithError(err).Error("Failed to start system controller")
}
}()
log.L.Infof("Started system controller on %q", config.SystemControllerAddress())
pprofAddress := config.SystemControllerPprofAddress()
if pprofAddress != "" {
if err := pprof.NewPprofHTTPListener(pprofAddress); err != nil {
return nil, errors.Wrap(err, "start pprof HTTP server")
}
log.L.Infof("Started pprof sever on %q", pprofAddress)
}
}
supportsDType, err := getSupportsDType(cfg.Root)
if err != nil {
return nil, err
}
if !supportsDType {
return nil, fmt.Errorf("%s does not support d_type. If the backing filesystem is xfs, please reformat with ftype=1 to enable d_type support", cfg.Root)
}
ms, err := storage.NewMetaStore(filepath.Join(cfg.Root, "metadata.db"))
if err != nil {
return nil, err
}
if err := os.Mkdir(filepath.Join(cfg.Root, "snapshots"), 0700); err != nil && !os.IsExist(err) {
return nil, err
}
syncRemove := cfg.SnapshotsConfig.SyncRemove
if config.GetFsDriver() == config.FsDriverFscache {
log.L.Infof("enable syncRemove for fscache mode")
syncRemove = true
}
return &snapshotter{
root: cfg.Root,
nydusdPath: cfg.DaemonConfig.NydusdPath,
ms: ms,
syncRemove: syncRemove,
fs: nydusFs,
cgroupManager: cgroupMgr,
enableNydusOverlayFS: cfg.SnapshotsConfig.EnableNydusOverlayFS,
nydusOverlayFSPath: cfg.SnapshotsConfig.NydusOverlayFSPath,
enableKataVolume: cfg.SnapshotsConfig.EnableKataVolume,
enableOverlayfsVolatile: cfg.SnapshotsConfig.EnableOverlayfsVolatile,
cleanupOnClose: cfg.CleanupOnClose,
}, nil
}
func (o *snapshotter) Cleanup(ctx context.Context) error {
log.L.Debugf("[Cleanup] snapshots")
if timer := collector.NewSnapshotMetricsTimer(collector.SnapshotMethodCleanup); timer != nil {
defer timer.ObserveDuration()
}
cleanup, err := o.cleanupDirectories(ctx)
if err != nil {
return err
}
log.L.Infof("[Cleanup] orphan directories %v", cleanup)
for _, dir := range cleanup {
if err := o.cleanupSnapshotDirectory(ctx, dir); err != nil {
log.L.WithError(err).Warnf("failed to remove directory %s", dir)
}
}
cleanup, err = o.getUnusedCacheBlobs(ctx)
if err != nil {
return err
}
log.L.Infof("[Cleanup] unused cache blobs %v", cleanup)
for _, blob := range cleanup {
if err := ctx.Err(); err != nil {
return err
}
// Add sha256: prefix to make it a valid blob digest
if err := o.fs.RemoveCache("sha256:" + blob); err != nil {
log.L.WithError(err).Warnf("failed to remove cache blob %s", blob)
data.CacheBlobDeletionErrors.Inc()
} else {
data.CacheBlobsDeleted.Inc()
}
}
return nil
}
func (o *snapshotter) Stat(ctx context.Context, key string) (snapshots.Info, error) {
_, info, _, err := snapshot.GetSnapshotInfo(ctx, o.ms, key)
return info, err
}
func (o *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (snapshots.Info, error) {
return snapshot.UpdateSnapshotInfo(ctx, o.ms, info, fieldpaths...)
}
func (o *snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) {
id, info, usage, err := snapshot.GetSnapshotInfo(ctx, o.ms, key)
if err != nil {
return snapshots.Usage{}, err
}
switch info.Kind {
case snapshots.KindActive:
upperPath := o.upperPath(id)
du, err := fs.DiskUsage(ctx, upperPath)
if err != nil {
return snapshots.Usage{}, err
}
usage = snapshots.Usage(du)
case snapshots.KindCommitted:
// Caculate disk space usage under cacheDir of committed snapshots.
if label.IsNydusDataLayer(info.Labels) || label.IsTarfsDataLayer(info.Labels) {
if blobDigest, ok := info.Labels[snpkg.TargetLayerDigestLabel]; ok {
// Try to get nydus meta layer/snapshot disk usage
cacheUsage, err := o.fs.CacheUsage(ctx, blobDigest)
if err != nil {
return snapshots.Usage{}, errors.Wrapf(err, "try to get snapshot %s nydus disk usage", id)
}
usage.Add(cacheUsage)
}
}
case snapshots.KindUnknown:
case snapshots.KindView:
}
return usage, nil
}
func (o *snapshotter) Mounts(ctx context.Context, key string) ([]mount.Mount, error) {
log.L.Debugf("[Mounts] snapshot %s", key)
if timer := collector.NewSnapshotMetricsTimer(collector.SnapshotMethodMount); timer != nil {
defer timer.ObserveDuration()
}
var (
needRemoteMounts = false
metaSnapshotID string
)
id, info, _, err := snapshot.GetSnapshotInfo(ctx, o.ms, key)
if err != nil {
return nil, errors.Wrapf(err, "mounts get snapshot %q info", key)
}
log.L.Infof("[Mounts] snapshot %s ID %s Kind %s", key, id, info.Kind)
switch info.Kind {
case snapshots.KindView:
if label.IsNydusMetaLayer(info.Labels) {
err = o.fs.WaitUntilReady(id)
if err != nil {
// Skip waiting if clients is unpacking nydus artifacts to `mounts`
// For example, nydus-snapshotter's client like Buildkit is calling snapshotter in below workflow:
// 1. [Prepare] snapshot for the uppermost layer - bootstrap
// 2. [Mounts]
// 3. Unpacking by applying the mounts, then we get bootstrap in its path position.
// In above steps, no container write layer is called to set up from nydus-snapshotter. So it has no
// chance to start nydusd, during which the Rafs instance is created.
if !errors.Is(err, errdefs.ErrNotFound) {
return nil, errors.Wrapf(err, "mounts: snapshot %s is not ready, err: %v", id, err)
}
} else {
needRemoteMounts = true
metaSnapshotID = id
}
} else if (o.fs.TarfsEnabled() && label.IsTarfsDataLayer(info.Labels)) || label.IsNydusProxyMode(info.Labels) {
needRemoteMounts = true
metaSnapshotID = id
}
case snapshots.KindActive:
if info.Parent != "" {
pKey := info.Parent
if pID, pInfo, _, err := snapshot.GetSnapshotInfo(ctx, o.ms, pKey); err == nil {
if label.IsNydusMetaLayer(pInfo.Labels) {
if err = o.fs.WaitUntilReady(pID); err != nil {
return nil, errors.Wrapf(err, "mounts: snapshot %s is not ready, err: %v", pID, err)
}
needRemoteMounts = true
metaSnapshotID = pID
} else if (o.fs.TarfsEnabled() && label.IsTarfsDataLayer(pInfo.Labels)) || label.IsNydusProxyMode(pInfo.Labels) {
needRemoteMounts = true
metaSnapshotID = pID
}
} else {
return nil, errors.Wrapf(err, "get parent snapshot info, parent key=%q", pKey)
}
}
case snapshots.KindCommitted:
case snapshots.KindUnknown:
}
if o.fs.IndexDetectEnabled() && !needRemoteMounts {
if id, _, err := o.findIndexAlternativeLayer(ctx, key); err == nil {
needRemoteMounts = true
metaSnapshotID = id
}
}
if o.fs.ReferrerDetectEnabled() && !needRemoteMounts {
if id, _, err := o.findReferrerLayer(ctx, key); err == nil {
needRemoteMounts = true
metaSnapshotID = id
}
}
snap, err := snapshot.GetSnapshot(ctx, o.ms, key)
if err != nil {
return nil, errors.Wrapf(err, "get snapshot %s", key)
}
if treatAsProxyDriver(info.Labels) {
log.L.Warnf("[Mounts] treat as proxy mode for the prepared snapshot by other snapshotter possibly: id = %s, labels = %v", id, info.Labels)
return o.mountProxy(ctx, *snap)
}
if needRemoteMounts {
return o.mountRemote(ctx, info.Labels, *snap, metaSnapshotID, key)
}
return o.mountNative(ctx, info.Labels, *snap)
}
func (o *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) {
log.L.Infof("[Prepare] snapshot with key %s parent %s", key, parent)
if timer := collector.NewSnapshotMetricsTimer(collector.SnapshotMethodPrepare); timer != nil {
defer timer.ObserveDuration()
}
logger := log.L.WithField("key", key).WithField("parent", parent)
info, s, err := o.createSnapshot(ctx, snapshots.KindActive, key, parent, opts)
if err != nil {
return nil, err
}
logger.Debugf("[Prepare] snapshot with labels %v", info.Labels)
processor, target, commitLabels, err := chooseProcessor(ctx, logger, o, s, key, parent, info.Labels, func() string { return o.upperPath(s.ID) })
if err != nil {
return nil, err
}
needCommit, mounts, err := processor()
if needCommit {
err := o.Commit(ctx, target, key, append(opts, snapshots.WithLabels(commitLabels))...)
if err == nil || errdefs.IsAlreadyExists(err) {
return nil, errors.Wrapf(errdefs.ErrAlreadyExists, "target snapshot %q", target)
}
}
return mounts, err
}
// The work on supporting View operation for nydus-snapshotter is divided into 2 parts:
// 1. View on the topmost layer of nydus images or zran images
// 2. View on the any layer of nydus images or zran images
func (o *snapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) {
log.L.Infof("[View] snapshot with key %s parent %s", key, parent)
pID, pInfo, _, err := snapshot.GetSnapshotInfo(ctx, o.ms, parent)
if err != nil {
return nil, errors.Wrapf(err, "get snapshot %s info", parent)
}
var (
needRemoteMounts = false
metaSnapshotID string
)
if label.IsNydusMetaLayer(pInfo.Labels) {
// Nydusd might not be running. We should run nydusd to reflect the rootfs.
if err = o.fs.WaitUntilReady(pID); err != nil {
if errors.Is(err, errdefs.ErrNotFound) {
if err := o.fs.Mount(ctx, pID, pInfo.Labels, nil); err != nil {
return nil, errors.Wrapf(err, "mount rafs, instance id %s", pID)
}
if err := o.fs.WaitUntilReady(pID); err != nil {
return nil, errors.Wrapf(err, "wait for instance id %s", pID)
}
} else {
return nil, errors.Wrapf(err, "daemon is not running %s", pID)
}
}
needRemoteMounts = true
metaSnapshotID = pID
} else if label.IsNydusDataLayer(pInfo.Labels) {
return nil, errors.New("only can view nydus topmost layer")
}
// Otherwise, it is OCI snapshots
base, s, err := o.createSnapshot(ctx, snapshots.KindView, key, parent, opts)
if err != nil {
return nil, err
}
if o.fs.TarfsEnabled() && label.IsTarfsDataLayer(pInfo.Labels) {
log.L.Infof("Prepare view snapshot %s in Nydus tarfs mode", pID)
err = o.mergeTarfs(ctx, s, pID, pInfo)
if err != nil {
return nil, errors.Wrapf(err, "merge tarfs layers for snapshot %s", pID)
}
if err := o.fs.Mount(ctx, pID, pInfo.Labels, &s); err != nil {
return nil, errors.Wrapf(err, "mount tarfs, snapshot id %s", pID)
}
log.L.Infof("Prepared view snapshot %s in Nydus tarfs mode", pID)
needRemoteMounts = true
metaSnapshotID = pID
}
if needRemoteMounts {
return o.mountRemote(ctx, base.Labels, s, metaSnapshotID, key)
}
return o.mountNative(ctx, base.Labels, s)
}
func (o *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) error {
log.L.Debugf("[Commit] snapshot with key %s", key)
ctx, t, err := o.ms.TransactionContext(ctx, true)
if err != nil {
return err
}
defer func() {
if err != nil {
if err := t.Rollback(); err != nil {
log.L.WithError(err).Warn("failed to rollback transaction")
}
}
}()
// grab the existing id
id, _, _, err := storage.GetInfo(ctx, key)
if err != nil {
return err
}
log.L.Infof("[Commit] snapshot with key %q snapshot id %s", key, id)
// For OCI compatibility, we calculate disk usage of the snapshotDir and commit the usage to DB.
// Nydus disk usage under the cacheDir will be delayed until containerd queries.
usage, err := fs.DiskUsage(ctx, o.upperPath(id))
if err != nil {
return err
}
if _, err = storage.CommitActive(ctx, key, name, snapshots.Usage(usage), opts...); err != nil {
return errors.Wrapf(err, "commit active snapshot %s", key)
}
// Let rollback catch the commit error
err = t.Commit()
if err != nil {
return errors.Wrapf(err, "commit snapshot %s", key)
}
return err
}
func (o *snapshotter) Remove(ctx context.Context, key string) error {
log.L.Debugf("[Remove] snapshot with key %s", key)
if timer := collector.NewSnapshotMetricsTimer(collector.SnapshotMethodRemove); timer != nil {
defer timer.ObserveDuration()
}
ctx, t, err := o.ms.TransactionContext(ctx, true)
if err != nil {
return err
}
defer func() {
if err != nil {
if err := t.Rollback(); err != nil {
log.G(ctx).WithError(err).Warn("failed to rollback transaction")
}
}
}()
id, info, _, err := storage.GetInfo(ctx, key)
if err != nil {
return errors.Wrapf(err, "get snapshot %s", key)
}
switch {
case label.IsNydusMetaLayer(info.Labels):
log.L.Infof("[Remove] nydus meta snapshot with key %s snapshot id %s", key, id)
case label.IsNydusDataLayer(info.Labels):
log.L.Infof("[Remove] nydus data snapshot with key %s snapshot id %s", key, id)
case label.IsTarfsDataLayer(info.Labels):
log.L.Infof("[Remove] nydus tarfs snapshot with key %s snapshot id %s", key, id)
default:
// For example: remove snapshot with key sha256:c33c40022c8f333e7f199cd094bd56758bc479ceabf1e490bb75497bf47c2ebf
log.L.Infof("[Remove] snapshot with key %s snapshot id %s", key, id)
}
_, _, err = storage.Remove(ctx, key)
if err != nil {
return errors.Wrapf(err, "failed to remove key %s", key)
}
if o.syncRemove {
var removals []string
removals, err = o.getCleanupDirectories(ctx)
if err != nil {
return errors.Wrap(err, "get directories for removal")
}
// Remove directories after the transaction is closed, failures must not
// return error since the transaction is committed with the removal
// key no longer available.
defer func() {
if err == nil {
for _, dir := range removals {
if err := o.cleanupSnapshotDirectory(ctx, dir); err != nil {
log.G(ctx).WithError(err).WithField("path", dir).Warn("failed to remove directory")
}
}
}
}()
}
return t.Commit()
}
func (o *snapshotter) Walk(ctx context.Context, fn snapshots.WalkFunc, fs ...string) error {
ctx, t, err := o.ms.TransactionContext(ctx, false)
if err != nil {
return err
}
defer func() {
if err := t.Rollback(); err != nil {
log.L.WithError(err)
}
}()
return storage.WalkInfo(ctx, fn, fs...)
}
func (o *snapshotter) Close() error {
log.L.Info("[Close] shutdown snapshotter")
if o.cleanupOnClose {
err := o.fs.Teardown(context.Background())
if err != nil {
log.L.Errorf("failed to clean up remote snapshot, err %v", err)
}
}
o.fs.TryStopSharedDaemon()
if o.cgroupManager != nil {
if err := o.cgroupManager.Delete(); err != nil {
log.L.Errorf("failed to destroy cgroup, err %v", err)
}
}
return o.ms.Close()
}
func (o *snapshotter) upperPath(id string) string {
return filepath.Join(o.root, "snapshots", id, "fs")
}
// Get the rootdir of nydus image file system contents.
func (o *snapshotter) lowerPath(id string) (mnt string, err error) {
if mnt, err = o.fs.MountPoint(id); err == nil {
return mnt, nil
} else if errors.Is(err, errdefs.ErrNotFound) {
return filepath.Join(o.root, "snapshots", id, "fs"), nil
}
return "", err
}
func (o *snapshotter) workPath(id string) string {
return filepath.Join(o.root, "snapshots", id, "work")
}
func (o *snapshotter) findIndexAlternativeLayer(ctx context.Context, key string) (string, snapshots.Info, error) {
return snapshot.IterateParentSnapshots(ctx, o.ms, key, func(_ string, i snapshots.Info) bool {
return o.fs.CheckIndexAlternative(ctx, i.Labels)
})
}
func (o *snapshotter) findReferrerLayer(ctx context.Context, key string) (string, snapshots.Info, error) {
return snapshot.IterateParentSnapshots(ctx, o.ms, key, func(_ string, info snapshots.Info) bool {
return o.fs.CheckReferrer(ctx, info.Labels)
})
}
func (o *snapshotter) findMetaLayer(ctx context.Context, key string) (string, snapshots.Info, error) {
return snapshot.IterateParentSnapshots(ctx, o.ms, key, func(_ string, i snapshots.Info) bool {
return label.IsNydusMetaLayer(i.Labels)
})
}
func (o *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) (info *snapshots.Info, _ storage.Snapshot, err error) {
return o.createSnapshotWithRecovery(ctx, kind, key, parent, opts, false)
}
// createSnapshotWithRecovery attempts to create a snapshot and recovers from
// "missing parent" errors by querying containerd for the parent's metadata and
// recreating it in the local BoltDB. This handles the desynchronization
// scenarios where nydus-snapshotter's BoltDB is missing entries that
// containerd knows about.
func (o *snapshotter) createSnapshotWithRecovery(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt, isRecovery bool) (info *snapshots.Info, _ storage.Snapshot, err error) {
ctx, t, err := o.ms.TransactionContext(ctx, true)
if err != nil {
return nil, storage.Snapshot{}, err
}
rollback := true
defer func() {
if rollback {
if rerr := t.Rollback(); rerr != nil {
log.G(ctx).WithError(rerr).Warn("failed to rollback transaction")
}
}
}()
var base snapshots.Info
for _, opt := range opts {
if err := opt(&base); err != nil {
return &base, storage.Snapshot{}, err
}
}
if base.Labels == nil {
base.Labels = map[string]string{}
}
var td, path string
defer func() {
if td != "" {
if err1 := o.cleanupSnapshotDirectory(ctx, td); err1 != nil {
log.G(ctx).WithError(err1).Warn("failed to clean up temp snapshot directory")
}
}
if path != "" {
if err1 := o.cleanupSnapshotDirectory(ctx, path); err1 != nil {
log.G(ctx).WithError(err1).WithField("path", path).Error("failed to reclaim snapshot directory, directory may need removal")
err = errors.Wrapf(err, "failed to remove path: %v", err1)
}
}
}()
td, err = o.prepareDirectory(o.snapshotRoot(), kind)
if err != nil {
return nil, storage.Snapshot{}, errors.Wrap(err, "create prepare snapshot dir")
}
s, err := storage.CreateSnapshot(ctx, kind, key, parent, opts...)
if err != nil {
// Check if this is a "missing parent" error and we can attempt recovery
if !isRecovery && parent != "" && o.isMissingParentError(err) {
log.G(ctx).WithError(err).Warnf("Missing parent %q in local BoltDB, attempting lazy recovery from containerd", parent)
// Rollback current transaction before recovery
rollback = true
if rerr := t.Rollback(); rerr != nil {
log.G(ctx).WithError(rerr).Warn("failed to rollback transaction before recovery")
}
rollback = false // Don't rollback again in defer
// Clean up the temp directory we created
if td != "" {
if err1 := o.cleanupSnapshotDirectory(ctx, td); err1 != nil {
log.G(ctx).WithError(err1).Warn("failed to clean up temp snapshot directory during recovery")
}
td = ""
}
// Attempt to recover the parent
// The recovery function uses a fresh context internally
if recoverErr := o.recoverParentFromContainerd(ctx, parent); recoverErr != nil {
log.G(ctx).WithError(recoverErr).Errorf("Failed to recover parent %q from containerd", parent)
return nil, storage.Snapshot{}, errors.Wrapf(err, "create snapshot (recovery failed: %v)", recoverErr)
}
log.G(ctx).Infof("Successfully recovered parent %q from containerd, retrying snapshot creation", parent)
// Retry with recovery flag set to prevent infinite recursion
// Use the original context for the retry
return o.createSnapshotWithRecovery(ctx, kind, key, parent, opts, true)
}
return nil, storage.Snapshot{}, errors.Wrap(err, "create snapshot")
}
// Try to keep the whole stack having the same UID and GID
if len(s.ParentIDs) > 0 {
st, err := os.Stat(o.upperPath(s.ParentIDs[0]))
if err != nil {
return nil, storage.Snapshot{}, errors.Wrap(err, "stat parent")
}
if err := lchown(filepath.Join(td, "fs"), st); err != nil {
return nil, storage.Snapshot{}, errors.Wrap(err, "perform chown")
}
}
path = o.snapshotDir(s.ID)
if err = os.Rename(td, path); err != nil {
return nil, storage.Snapshot{}, errors.Wrap(err, "perform rename")
}
td = ""
rollback = false
if err = t.Commit(); err != nil {
return nil, storage.Snapshot{}, errors.Wrap(err, "perform commit")
}
path = ""
return &base, s, nil
}
// isMissingParentError checks if the error is a "missing parent bucket" error
// from the storage layer, which indicates the parent snapshot exists in containerd
// but not in our local BoltDB.
func (o *snapshotter) isMissingParentError(err error) bool {
if err == nil {
return false
}
errStr := err.Error()
return strings.Contains(errStr, "missing parent") && strings.Contains(errStr, "bucket")
}
// recoverParentFromContainerd attempts to recover a missing parent snapshot.
// This handles the desyncrhonization scenario where nydus-snapshotter's BoltDB
// was wiped but containerd still has metadata references.
//
// For proxy mode (used by Kata Containers for guest image pulling), we create
// a minimal placeholder snapshot since there's no actual filesystem data stored.
// The real data will be pulled fresh by the guest.
func (o *snapshotter) recoverParentFromContainerd(ctx context.Context, parent string) error {
log.G(ctx).Infof("Attempting lazy recovery for missing parent: %q", parent)
// For proxy mode (Kata guest pulling), create a minimal placeholder
// since there's no actual filesystem content to restore
fsDriver := config.GetFsDriver()
if fsDriver == config.FsDriverProxy {
log.G(ctx).Infof("Proxy mode detected - creating placeholder snapshot for %q", parent)
// Use the full parent key as-is since the storage layer uses this exact key
return o.createPlaceholderSnapshot(ctx, parent)
}
// For other modes (fusedev, fscache), we would need the actual parent metadata
// from containerd. Unfortunately, containerd's snapshot service for remote
// snapshotters calls back to us, creating a circular dependency when our DB is empty.
return errors.Errorf(
"lazy parent recovery not supported for fs_driver=%q. "+
"Please clean up containerd's stale snapshot references with: "+
"ctr snapshot --snapshotter nydus rm %s",
fsDriver, parent)
}
// createPlaceholderSnapshot creates a minimal committed snapshot entry in the local
// BoltDB for recovery purposes. This is used in proxy mode where no actual filesystem
// content is stored locally.
func (o *snapshotter) createPlaceholderSnapshot(ctx context.Context, key string) error {
// Use a fresh context to avoid any transaction context pollution
cleanCtx := context.Background()
// First check if the snapshot already exists (from a previous partial recovery)
// nolint:dogsled
_, _, _, existErr := snapshot.GetSnapshotInfo(cleanCtx, o.ms, key)
if existErr == nil {
log.G(ctx).Infof("Placeholder snapshot %q already exists, skipping creation", key)
return nil
}
txCtx, t, err := o.ms.TransactionContext(cleanCtx, true)
if err != nil {
return errors.Wrap(err, "begin transaction for placeholder snapshot")
}
rollback := true
defer func() {
if rollback {
if rerr := t.Rollback(); rerr != nil {
log.G(ctx).WithError(rerr).Warn("failed to rollback placeholder transaction")
}
}
}()
// Prepare the snapshot directory
td, err := o.prepareDirectory(o.snapshotRoot(), snapshots.KindCommitted)
if err != nil {
return errors.Wrap(err, "prepare directory for placeholder snapshot")
}
// Create a placeholder with minimal labels indicating it was recovered
opts := []snapshots.Opt{
snapshots.WithLabels(map[string]string{
"nydus.recovered": "true",
"nydus.recovered.at": fmt.Sprintf("%d", time.Now().Unix()),
}),
}
// Create as active first (no parent)
// Use a unique key to avoid conflicts with retries
activeKey := fmt.Sprintf("recovery-%d-%s", time.Now().UnixNano(), key)
s, err := storage.CreateSnapshot(txCtx, snapshots.KindActive, activeKey, "", opts...)
if err != nil {
if err1 := o.cleanupSnapshotDirectory(cleanCtx, td); err1 != nil {
log.G(ctx).WithError(err1).Warn("failed to clean up temp directory")
}
return errors.Wrapf(err, "create placeholder snapshot entry for %q", key)
}
// Move temp directory to final location
path := o.snapshotDir(s.ID)
if err = os.Rename(td, path); err != nil {
return errors.Wrap(err, "rename placeholder snapshot directory")
}
// Commit the active snapshot to create the final committed snapshot
// CommitActive(ctx, key, name) commits active snapshot `key` as committed snapshot `name`
if _, err := storage.CommitActive(txCtx, activeKey, key, snapshots.Usage{}, opts...); err != nil {
if err1 := o.cleanupSnapshotDirectory(cleanCtx, path); err1 != nil {
log.G(ctx).WithError(err1).Warn("failed to clean up snapshot directory")
}
return errors.Wrapf(err, "commit placeholder snapshot %q", key)
}
rollback = false
if err = t.Commit(); err != nil {
return errors.Wrap(err, "commit placeholder transaction")
}
log.G(ctx).Infof("Successfully created placeholder snapshot %q for lazy recovery", key)
return nil
}
func (o *snapshotter) mergeTarfs(ctx context.Context, s storage.Snapshot, pID string, pInfo snapshots.Info) error {
if err := o.fs.MergeTarfsLayers(s, func(id string) string { return o.upperPath(id) }); err != nil {
return errors.Wrapf(err, "tarfs merge fail %s", pID)
}
if config.GetTarfsExportEnabled() {
updateFields, err := o.fs.ExportBlockData(s, false, pInfo.Labels, func(id string) string { return o.upperPath(id) })
if err != nil {
return errors.Wrap(err, "export tarfs as block image")
}
if len(updateFields) > 0 {
_, err = o.Update(ctx, pInfo, updateFields...)
if err != nil {
return errors.Wrapf(err, "update snapshot label information")
}
}
}
return nil
}
func bindMount(source, roFlag string) []mount.Mount {
return []mount.Mount{
{
Type: "bind",
Source: source,
Options: []string{
roFlag,
"rbind",
},
},
}
}
func overlayMount(options []string) []mount.Mount {
return []mount.Mount{
{
Type: "overlay",
Source: "overlay",
Options: options,
},
}
}
// Handle proxy mount which the snapshot has been prepared by other snapshotter, mainly used for pause image in containerd
func (o *snapshotter) mountProxy(ctx context.Context, s storage.Snapshot) ([]mount.Mount, error) {
var overlayOptions []string
if s.Kind == snapshots.KindActive {
overlayOptions = append(overlayOptions,
fmt.Sprintf("workdir=%s", o.workPath(s.ID)),
fmt.Sprintf("upperdir=%s", o.upperPath(s.ID)),
)
}
log.G(ctx).Debugf("len(s.ParentIDs) = %v", len(s.ParentIDs))
parentPaths := make([]string, 0, len(s.ParentIDs)+1)
if len(s.ParentIDs) == 0 {
parentPaths = append(parentPaths, config.GetSnapshotsRootDir())
} else {
for _, id := range s.ParentIDs {
parentPaths = append(parentPaths, o.upperPath(id))
}
}
lowerDirOption := fmt.Sprintf("lowerdir=%s", strings.Join(parentPaths, ":"))
overlayOptions = append(overlayOptions, lowerDirOption)
log.G(ctx).Infof("proxy mount options %v", overlayOptions)
options, err := o.mountWithProxyVolume(rafs.Rafs{
FsDriver: config.GetFsDriver(),
Annotations: make(map[string]string),
})
if err != nil {
return []mount.Mount{}, errors.Wrapf(err, "create kata volume for proxy")
}
if len(options) > 0 {
overlayOptions = append(overlayOptions, options...)
}
log.G(ctx).Debugf("fuse.nydus-overlayfs mount options %v", overlayOptions)
mountType := "fuse.nydus-overlayfs"
if o.nydusOverlayFSPath != "" {
log.G(ctx).Debugf("Using nydus-overlayfs from path: %s", o.nydusOverlayFSPath)
mountType = fmt.Sprintf("fuse.%s", o.nydusOverlayFSPath)
}
mounts := []mount.Mount{
{
Type: mountType,
Source: "overlay",
Options: overlayOptions,
},
}
return mounts, nil
}
// `s` is the upmost snapshot and `id` refers to the nydus meta snapshot
// `s` and `id` can represent a different layer, it's useful when View an image
func (o *snapshotter) mountRemote(ctx context.Context, labels map[string]string, s storage.Snapshot, id, key string) ([]mount.Mount, error) {
var overlayOptions []string
if _, ok := labels[label.OverlayfsVolatileOpt]; ok || o.enableOverlayfsVolatile {
overlayOptions = append(overlayOptions, "volatile")
}
lowerPaths := make([]string, 0, 8)
if o.fs.ReferrerDetectEnabled() || o.fs.IndexDetectEnabled() {
// From the parent list, we want to add all the layers
// between the upmost snapshot and the nydus meta snapshot.
// On the other hand, we consider that all the layers below
// the nydus meta snapshot will be included in its mount.
for i := range s.ParentIDs {
if s.ParentIDs[i] == id {
break
}
lowerPaths = append(lowerPaths, o.upperPath(s.ParentIDs[i]))
}
}
lowerPathNydus, err := o.lowerPath(id)
if err != nil {
return nil, errors.Wrapf(err, "failed to locate overlay lowerdir")
}
lowerPaths = append(lowerPaths, lowerPathNydus)
switch s.Kind {
case snapshots.KindActive:
overlayOptions = append(overlayOptions,
fmt.Sprintf("workdir=%s", o.workPath(s.ID)),
fmt.Sprintf("upperdir=%s", o.upperPath(s.ID)),
)
case snapshots.KindView:
lowerPathNormal, err := o.lowerPath(s.ID)
if err != nil {
return nil, errors.Wrapf(err, "failed to locate overlay lowerdir for view snapshot")
}
lowerPaths = append(lowerPaths, lowerPathNormal)
default:
// KindUnknown or KindCommitted - no additional options needed
}
lowerDirOption := fmt.Sprintf("lowerdir=%s", strings.Join(lowerPaths, ":"))
overlayOptions = append(overlayOptions, lowerDirOption)
log.G(ctx).Infof("remote mount options %v", overlayOptions)
if o.enableKataVolume {
return o.mountWithKataVolume(ctx, id, overlayOptions, key)
}
// Add `extraoption` if NydusOverlayFS is enable or daemonMode is `None`
if o.enableNydusOverlayFS || config.GetDaemonMode() == config.DaemonModeNone {
return o.remoteMountWithExtraOptions(ctx, s, id, overlayOptions)
}
return overlayMount(overlayOptions), nil
}
func (o *snapshotter) mountNative(ctx context.Context, labels map[string]string, s storage.Snapshot) ([]mount.Mount, error) {
if len(s.ParentIDs) == 0 {
// if we only have one layer/no parents then just return a bind mount as overlay will not work
roFlag := "rw"
if s.Kind == snapshots.KindView {
roFlag = "ro"
}
return bindMount(o.upperPath(s.ID), roFlag), nil
}
var options []string
if s.Kind == snapshots.KindActive {
options = append(options,
fmt.Sprintf("workdir=%s", o.workPath(s.ID)),
fmt.Sprintf("upperdir=%s", o.upperPath(s.ID)),
)
if _, ok := labels[label.OverlayfsVolatileOpt]; ok || o.enableOverlayfsVolatile {
options = append(options, "volatile")
}
} else if len(s.ParentIDs) == 1 {
parentPath := o.upperPath(s.ParentIDs[0])
// if we only have one parent then just return a bind mount
log.G(ctx).Debugf("bind mount on %s", parentPath)
return bindMount(parentPath, "ro"), nil
}
parentPaths := make([]string, len(s.ParentIDs))
for i := range s.ParentIDs {
parentPaths[i] = o.upperPath(s.ParentIDs[i])
}
options = append(options, fmt.Sprintf("lowerdir=%s", strings.Join(parentPaths, ":")))
log.G(ctx).Debugf("overlayfs mount options %s", options)
return overlayMount(options), nil
}
func (o *snapshotter) prepareDirectory(snapshotDir string, kind snapshots.Kind) (string, error) {
td, err := os.MkdirTemp(snapshotDir, "new-")
if err != nil {
return "", errors.Wrap(err, "failed to create temp dir")
}
if err := os.Mkdir(filepath.Join(td, "fs"), 0755); err != nil {
return td, err
}
if kind == snapshots.KindActive {
if err := os.Mkdir(filepath.Join(td, "work"), 0711); err != nil {
return td, err
}
}
return td, nil
}
func (o *snapshotter) getCleanupDirectories(ctx context.Context) ([]string, error) {
ids, err := storage.IDMap(ctx)
if err != nil {
return nil, err
}
// For example:
// 23:default/24/sha256:8c2ed532dce363da2d08489f385c432f7c0ee4509ab4e20eb2778803916adc93
// 24:sha256:c858413d9e5162c287028d630128ea4323d5029bf8a093af783480b38cf8d44e
// 25:sha256:fcb51e3c865d96542718beba0bb247376e4c78e039412c5d30c989872e66b6d5
fd, err := os.Open(o.snapshotRoot())
if err != nil {
return nil, err
}
defer fd.Close()
dirs, err := fd.Readdirnames(0)
if err != nil {
return nil, err
}
cleanup := make([]string, 0, 16)
for _, d := range dirs {
if _, ok := ids[d]; ok {
continue
}
// When it quits, there will be nothing inside
// TODO: try to clean up config/sockets/logs directories
cleanup = append(cleanup, o.snapshotDir(d))
}
return cleanup, nil
}
func (o *snapshotter) cleanupDirectories(ctx context.Context) ([]string, error) {
// Get a write transaction to ensure no other write transaction can be entered
// while the cleanup is scanning.
ctx, t, err := o.ms.TransactionContext(ctx, true)
if err != nil {
return nil, err
}
defer func() {
if err := t.Rollback(); err != nil {
log.L.WithError(err)
}
}()
return o.getCleanupDirectories(ctx)
}
func (o *snapshotter) cleanupSnapshotDirectory(ctx context.Context, dir string) error {
// For example: cleanupSnapshotDirectory /var/lib/containerd/io.containerd.snapshotter.v1.nydus/snapshots/34" dir=/var/lib/containerd/io.containerd.snapshotter.v1.nydus/snapshots/34
snapshotID := filepath.Base(dir)
if err := o.fs.Umount(ctx, snapshotID); err != nil && !os.IsNotExist(err) {
log.G(ctx).WithError(err).WithField("dir", dir).Error("failed to unmount")
}
if o.fs.TarfsEnabled() {
if err := o.fs.DetachTarfsLayer(snapshotID); err != nil && !os.IsNotExist(err) {
log.G(ctx).WithError(err).Errorf("failed to detach tarfs layer for snapshot %s", snapshotID)
}
}
if err := os.RemoveAll(dir); err != nil {
return errors.Wrapf(err, "remove directory %q", dir)
}
return nil
}
func (o *snapshotter) getUnusedCacheBlobs(ctx context.Context) ([]string, error) {
// Get a write transaction to ensure no other write transaction can be entered
// while the cleanup is scanning.
_, t, err := o.ms.TransactionContext(ctx, true)
if err != nil {
return nil, err
}
defer func() {
if err := t.Rollback(); err != nil {
log.L.WithError(err)
}
}()
// Collect all used blob IDs from all daemons
usedBlobIDs := make(map[string]bool)
if err := o.fs.WalkManagers(func(mgr *mgr.Manager) error {
for _, d := range mgr.ListDaemons() {
// Get all rafs instances for this daemon
for _, rafs := range d.RafsCache.List() {
// Extract blob IDs from underlying files and mark as used
for _, filePath := range rafs.UnderlyingFiles {
// Extract blob ID from the file path
filename := filepath.Base(filePath)
blobID := cache.ExtractBlobIDFromFilename(filename)
if blobID != "" {
usedBlobIDs[blobID] = true
}
}
}
}
return nil
}); err != nil {
return nil, errors.Wrap(err, "walk managers to collect used blobs")
}
log.L.Debugf("[getUnusedCacheBlobs] found %d used blob IDs", len(usedBlobIDs))
// Update metrics for blobs in use
data.CacheBlobsInUse.Set(float64(len(usedBlobIDs)))
cacheDir, err := o.fs.GetCacheDir()
if err != nil {
return nil, errors.Wrap(err, "get cache directory")
}
// List all files in cache directory
entries, err := os.ReadDir(cacheDir)
if err != nil {
return nil, errors.Wrapf(err, "read cache directory %s", cacheDir)
}
// Track which blob IDs we've already processed to avoid duplicate RemoveCache calls
storedBlobIDs := map[string]interface{}{}
for _, entry := range entries {
if entry.IsDir() {
continue
}
blobID := cache.ExtractBlobIDFromFilename(entry.Name())
if blobID == "" {
log.L.Debugf("[getUnusedCacheBlobs] skipping file with unknown format: %s", entry.Name())
continue
}
storedBlobIDs[blobID] = nil
}
log.L.Debugf("[getUnusedCacheBlobs] found %d stored blob IDs", len(storedBlobIDs))
var unusedBlobIDs []string
for blobID := range storedBlobIDs {
if _, ok := usedBlobIDs[blobID]; !ok {
unusedBlobIDs = append(unusedBlobIDs, blobID)
}
}
return unusedBlobIDs, nil
}
func (o *snapshotter) snapshotRoot() string {
return filepath.Join(o.root, "snapshots")
}
func (o *snapshotter) snapshotDir(id string) string {
return filepath.Join(o.snapshotRoot(), id)
}
func treatAsProxyDriver(labels map[string]string) bool {
isProxyDriver := config.GetFsDriver() == config.FsDriverProxy
isProxyLabel := label.IsNydusProxyMode(labels)
_, isProxyImage := labels[label.CRIImageRef]
log.G(context.Background()).Debugf("isProxyDriver = %t, isProxyLabel = %t, isProxyImage = %t", isProxyDriver, isProxyLabel, isProxyImage)
switch {
case isProxyDriver && isProxyImage:
return false
case isProxyDriver != isProxyLabel:
log.G(context.Background()).Warnf("check Labels With Driver failed, driver = %q, labels = %q", config.GetFsDriver(), labels)
return true
default:
return false
}
}
================================================
FILE: snapshot/snapshot_test.go
================================================
/*
* Copyright (c) 2025. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package snapshot
import (
"context"
"fmt"
"path/filepath"
"testing"
"github.com/containerd/containerd/v2/core/mount"
"github.com/containerd/containerd/v2/core/snapshots"
"github.com/containerd/containerd/v2/core/snapshots/storage"
"github.com/containerd/nydus-snapshotter/pkg/label"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMountNative(t *testing.T) {
// Create a minimal snapshotter instance for testing
snapshotterRoot := "/var/lib/containerd/snapshotter"
s := &snapshotter{
root: snapshotterRoot,
}
tests := []struct {
name string
snapshot storage.Snapshot
labels map[string]string
expectedMounts []mount.Mount
validate func(t *testing.T, mounts []mount.Mount, s *snapshotter)
}{
{
name: "no parents, active snapshot - rw bind mount",
snapshot: storage.Snapshot{
ID: "snap1",
Kind: snapshots.KindActive,
ParentIDs: []string{},
},
expectedMounts: []mount.Mount{
{
Type: "bind",
Source: s.upperPath("snap1"),
Options: []string{
"rw",
"rbind",
},
},
},
},
{
name: "no parents, view snapshot - ro bind mount",
snapshot: storage.Snapshot{
ID: "snap2",
Kind: snapshots.KindView,
ParentIDs: []string{},
},
expectedMounts: []mount.Mount{
{
Type: "bind",
Source: s.upperPath("snap2"),
Options: []string{
"ro",
"rbind",
},
},
},
},
{
name: "one parent, committed snapshot - ro bind mount of parent",
snapshot: storage.Snapshot{
ID: "snap3",
Kind: snapshots.KindCommitted,
ParentIDs: []string{"parent1"},
},
expectedMounts: []mount.Mount{
{
Type: "bind",
Source: s.upperPath("parent1"),
Options: []string{
"ro",
"rbind",
},
},
},
},
{
name: "one parent, view snapshot - ro bind mount of parent",
snapshot: storage.Snapshot{
ID: "snap4",
Kind: snapshots.KindView,
ParentIDs: []string{"parent1"},
},
expectedMounts: []mount.Mount{
{
Type: "bind",
Source: s.upperPath("parent1"),
Options: []string{
"ro",
"rbind",
},
},
},
},
{
name: "multiple parents, active snapshot - overlay with work and upper dirs",
snapshot: storage.Snapshot{
ID: "snap5",
Kind: snapshots.KindActive,
ParentIDs: []string{"parent1", "parent2"},
},
expectedMounts: []mount.Mount{
{
Type: "overlay",
Source: "overlay",
Options: []string{
fmt.Sprintf("workdir=%s", s.workPath("snap5")),
fmt.Sprintf("upperdir=%s", s.upperPath("snap5")),
fmt.Sprintf("lowerdir=%s:%s", s.upperPath("parent1"), s.upperPath("parent2")),
},
},
},
},
{
name: "multiple parents, active snapshot with volatile option",
snapshot: storage.Snapshot{
ID: "snap6",
Kind: snapshots.KindActive,
ParentIDs: []string{"parent1", "parent2"},
},
labels: map[string]string{
label.OverlayfsVolatileOpt: "true",
},
expectedMounts: []mount.Mount{
{
Type: "overlay",
Source: "overlay",
Options: []string{
fmt.Sprintf("workdir=%s", s.workPath("snap6")),
fmt.Sprintf("upperdir=%s", s.upperPath("snap6")),
fmt.Sprintf("lowerdir=%s:%s", s.upperPath("parent1"), s.upperPath("parent2")),
"volatile",
},
},
},
},
{
name: "multiple parents, committed snapshot - overlay with only lowerdir",
snapshot: storage.Snapshot{
ID: "snap7",
Kind: snapshots.KindCommitted,
ParentIDs: []string{"parent1", "parent2", "parent3"},
},
expectedMounts: []mount.Mount{
{
Type: "overlay",
Source: "overlay",
Options: []string{
fmt.Sprintf("lowerdir=%s:%s:%s", s.upperPath("parent1"), s.upperPath("parent2"), s.upperPath("parent3")),
},
},
},
},
{
name: "multiple parents, view snapshot - overlay with only lowerdir",
snapshot: storage.Snapshot{
ID: "snap8",
Kind: snapshots.KindView,
ParentIDs: []string{"parent1", "parent2"},
},
expectedMounts: []mount.Mount{
{
Type: "overlay",
Source: "overlay",
Options: []string{
fmt.Sprintf("lowerdir=%s:%s", s.upperPath("parent1"), s.upperPath("parent2")),
},
},
},
},
{
name: "no parents, committed snapshot - rw bind mount",
snapshot: storage.Snapshot{
ID: "snap9",
Kind: snapshots.KindCommitted,
ParentIDs: []string{},
},
expectedMounts: []mount.Mount{
{
Type: "bind",
Source: filepath.Join(s.root, "snapshots", "snap9", "fs"),
Options: []string{
"rw",
"rbind",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
// Call the mountNative function
mounts, err := s.mountNative(ctx, tt.labels, tt.snapshot)
// Verify no error occurred
require.NoError(t, err)
// Verify expected number of mounts
require.Len(t, mounts, len(tt.expectedMounts))
for i, expectedMount := range tt.expectedMounts {
// Verify expected mount
assert.Equal(t, expectedMount.Type, mounts[i].Type)
assert.Equal(t, expectedMount.Source, mounts[i].Source)
assert.ElementsMatch(t, expectedMount.Options, mounts[i].Options)
}
})
}
}
func TestMountNativeConfigVolatile(t *testing.T) {
snapshotterRoot := "/var/lib/containerd/snapshotter"
s := &snapshotter{
root: snapshotterRoot,
enableOverlayfsVolatile: true,
}
ctx := context.Background()
t.Run("active snapshot gets volatile from config", func(t *testing.T) {
snap := storage.Snapshot{
ID: "snap1",
Kind: snapshots.KindActive,
ParentIDs: []string{"parent1", "parent2"},
}
mounts, err := s.mountNative(ctx, nil, snap)
require.NoError(t, err)
require.Len(t, mounts, 1)
assert.Contains(t, mounts[0].Options, "volatile")
})
t.Run("view snapshot does not get volatile from config", func(t *testing.T) {
snap := storage.Snapshot{
ID: "snap2",
Kind: snapshots.KindView,
ParentIDs: []string{"parent1", "parent2"},
}
mounts, err := s.mountNative(ctx, nil, snap)
require.NoError(t, err)
require.Len(t, mounts, 1)
assert.NotContains(t, mounts[0].Options, "volatile")
})
t.Run("committed snapshot does not get volatile from config", func(t *testing.T) {
snap := storage.Snapshot{
ID: "snap3",
Kind: snapshots.KindCommitted,
ParentIDs: []string{"parent1", "parent2"},
}
mounts, err := s.mountNative(ctx, nil, snap)
require.NoError(t, err)
require.Len(t, mounts, 1)
assert.NotContains(t, mounts[0].Options, "volatile")
})
}
================================================
FILE: snapshot/utils.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package snapshot
import (
"os"
"syscall"
"github.com/containerd/continuity/fs"
)
func getSupportsDType(dir string) (bool, error) {
return fs.SupportsDType(dir)
}
func lchown(target string, st os.FileInfo) error {
stat := st.Sys().(*syscall.Stat_t)
return os.Lchown(target, int(stat.Uid), int(stat.Gid))
}
================================================
FILE: tests/converter_test.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package tests
import (
"archive/tar"
"bytes"
"compress/gzip"
"context"
"crypto/rand"
"errors"
"fmt"
"io"
"io/fs"
"os"
"os/exec"
"os/user"
"path"
"path/filepath"
"strings"
"testing"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
awscfg "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3"
containerd "github.com/containerd/containerd/v2/client"
"github.com/containerd/containerd/v2/core/content"
"github.com/containerd/containerd/v2/pkg/namespaces"
"github.com/containerd/containerd/v2/plugins/content/local"
"github.com/containerd/log"
"github.com/containerd/platforms"
"github.com/opencontainers/go-digest"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
containerdConverter "github.com/containerd/containerd/v2/core/images/converter"
"github.com/containerd/nydus-snapshotter/pkg/backend"
"github.com/containerd/nydus-snapshotter/pkg/converter"
"github.com/containerd/nydus-snapshotter/pkg/encryption"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
const envNydusdPath = "NYDUS_NYDUSD"
var (
privateKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAw8mbbvEiQ6BSfsjSq8XEz9ZOS5lOzI2Up3zXlKU9mZN/JBc2
0TGQp0SnSCXYHmzJ9UrXqX2MDU2UFsQpVF0vTrvj+PURYHO5K2NIo8Dqj9uUTq5q
ylwYLWWcuUQPLHeShOWDOeiVycaSP2dnSKwZOyhiQc/TvRXZ5oooFBHzMOrKhACv
ZOfVxYNrrhwdsIX5+TRL8isxT4y8bkdzhY0R4J6z3aVhOAQhrp7fKtNtEA1edkIE
Vd+F7k72qdTCSCNAhjy1Dyom3A9Yii2wNafcEtEr8D3dgnQMnkgpDhPCqLeeecmX
n2BmELmBOW9Nio/tR8dc/wpW3OqB5ycmgnGu+QIDAQABAoIBAQCaAJUQmP/Yrdz1
+UUs9C0xRmLjuD1xTNRnQh3YwHlJuelCHDh0KEaeK7RhXdM3a18YYLxuh2CIfkND
/Rx9TacOiWByzWHTunMmm7vhgrd+XLu1gCBj+DjUTJ8QY2aEFbHccyPbgwV/Z4BV
+yIU2bom/Eb9eVoV24BAhN+tmcju6f6PtGDQciV3QJqnzn+YUpl+e1+qjs1UnvSH
fYRDWdoKIeBr9C4NYMh9qMxuduKErld+01aJvthSdxC6E2GKyQNj726IZm5cYQPL
tbGc+Om+tR3mI3uK1MJZ00yoThuG216SB2hFqGCqgW4gpXpWGZVyGlh/JiQDDuSL
sSFiLqYBAoGBAPXUgX+eIOfwh88tUy+VNKkAQvazv/KF+/eosU2aazJQBxvLRIWL
qm1pjEU0ZzUP3a2rEsnZxHh2SYERGGJQVBVUlITQSaeW+XbEZbGCh5x9dTnY0hvc
BjRLOk8jGYxAVtvXl88zfhS+c8jNM5M0ECRCP/yFkGE99DzWhXLf/D4ZAoGBAMvj
HoZZvNaku3ruNgmI/Kp3Gx3aUiMwXIKXjQ0xc3f0A1Ccc5SAgYXLmLvqB+ZNJ+Sd
Fd/87Zpb8kEy+JC5UE4ZwZo2SnL1wDyMZWRw3cTsf8mhvQ1kCpj5fPWUrGzUs/bh
ImL6w5sh5IB3BYN5nAtZiRyrqQCHxqW2HK+EJVPhAoGAXLevuABeDNzNfDhuHY46
9Fri5sVY6hHavMflR42sTKeeZr89stjAiM+8VgWzv3GifHP/fB4kWgLTKljWR45g
iEMEWStt/EWXBVKBwHeoyj8PTagXZuaPeH2/GkX0xs8lc3lXCpEzRoOmi9/JSgXi
6KoMFCQUFnkVezS11GPicVECgYEAhacXrniK+qW4JIidMbjz8IbtZq9kIp8kNZNF
Kn3dNKfnuGMmvRVUUrG5KI3sqcKwQQPcgB1cYFCfyK+yE6T3CIuHxyCJwzxnzQk3
uhTmu51Q04tL08hdzhPWH2JbeWghpNfGY94AdeRM1w2utpX0fdgusnWw7qESzjRI
L6JPmeECgYBUkLNcEyrNMc90tk5jdQqCQb/frT6K5k04LjvXe+TlJPHBO3XJun0H
OYrCC8EcKknH6jkHejHUdDTG/XjHvohzKI43xg61touKtgFOVpoq79vUBRVOuH85
6vNEksKyKXUKYya19LJS/EpfC2uHUENPSmq/Bh7TDb4y70JWnGTVWA==
-----END RSA PRIVATE KEY-----`)
publicKey = []byte(`-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw8mbbvEiQ6BSfsjSq8XE
z9ZOS5lOzI2Up3zXlKU9mZN/JBc20TGQp0SnSCXYHmzJ9UrXqX2MDU2UFsQpVF0v
Trvj+PURYHO5K2NIo8Dqj9uUTq5qylwYLWWcuUQPLHeShOWDOeiVycaSP2dnSKwZ
OyhiQc/TvRXZ5oooFBHzMOrKhACvZOfVxYNrrhwdsIX5+TRL8isxT4y8bkdzhY0R
4J6z3aVhOAQhrp7fKtNtEA1edkIEVd+F7k72qdTCSCNAhjy1Dyom3A9Yii2wNafc
EtEr8D3dgnQMnkgpDhPCqLeeecmXn2BmELmBOW9Nio/tR8dc/wpW3OqB5ycmgnGu
+QIDAQAB
-----END PUBLIC KEY-----`)
)
func hugeString(mb int) string {
var buf strings.Builder
size := mb * 1024 * 1024
seqSize := 512 * 1024
buf.Grow(size)
seq := size / seqSize
for i := 0; i < seq; i++ {
data := make([]byte, seqSize)
if i%2 == 0 {
_, err := rand.Read(data)
if err != nil {
log.L.WithError(err)
}
}
buf.Write(data)
}
return buf.String()
}
func dropCache(t *testing.T) {
cmd := exec.Command("sh", "-c", "echo 3 > /proc/sys/vm/drop_caches")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
require.NoError(t, cmd.Run())
}
func ensureFile(t *testing.T, name string) {
_, err := os.Stat(name)
require.NoError(t, err)
}
func ensureNoFile(t *testing.T, name string) {
_, err := os.Stat(name)
require.True(t, errors.Is(err, os.ErrNotExist))
}
func writeFileToTar(t *testing.T, tw *tar.Writer, name string, data string) {
u, err := user.Current()
require.NoError(t, err)
g, err := user.LookupGroupId(u.Uid)
require.NoError(t, err)
hdr := &tar.Header{
Name: name,
Mode: 0444,
Size: int64(len(data)),
Uname: u.Username,
Gname: g.Name,
}
err = tw.WriteHeader(hdr)
require.NoError(t, err)
_, err = io.Copy(tw, bytes.NewReader([]byte(data)))
require.Nil(t, err)
require.NoError(t, err)
}
func writeDirToTar(t *testing.T, tw *tar.Writer, name string) {
u, err := user.Current()
require.NoError(t, err)
g, err := user.LookupGroupId(u.Uid)
require.NoError(t, err)
hdr := &tar.Header{
Name: name,
Mode: 0444,
Typeflag: tar.TypeDir,
Uname: u.Username,
Gname: g.Name,
}
err = tw.WriteHeader(hdr)
require.NoError(t, err)
}
func writeToFile(t *testing.T, reader io.Reader, fileName string) {
file, err := os.Create(fileName)
require.NoError(t, err)
defer file.Close()
_, err = io.Copy(file, reader)
require.NoError(t, err)
}
func buildChunkDictTar(t *testing.T, n int) io.ReadCloser {
pr, pw := io.Pipe()
tw := tar.NewWriter(pw)
go func() {
defer pw.Close()
writeDirToTar(t, tw, "dir-1")
for i := 1; i < n; i++ {
writeFileToTar(t, tw, fmt.Sprintf("dir-1/file-%d", i), fmt.Sprintf("lower-file-%d", i))
}
require.NoError(t, tw.Close())
}()
return pr
}
func buildOCILowerTar(t *testing.T, n int) (io.ReadCloser, map[string]string) {
fileTree := map[string]string{}
pr, pw := io.Pipe()
tw := tar.NewWriter(pw)
go func() {
defer pw.Close()
writeDirToTar(t, tw, "dir-1")
fileTree["dir-1"] = ""
for i := 1; i < n; i++ {
writeFileToTar(t, tw, fmt.Sprintf("dir-1/file-%d", i), fmt.Sprintf("lower-file-%d", i))
fileTree[fmt.Sprintf("dir-1/file-%d", i)] = fmt.Sprintf("lower-file-%d", i)
}
writeDirToTar(t, tw, "dir-2")
fileTree["dir-2"] = ""
writeFileToTar(t, tw, "dir-2/file-1", "lower-file-1")
fileTree["dir-2/file-1"] = "lower-file-1"
require.NoError(t, tw.Close())
}()
return pr, fileTree
}
func buildOCIUpperTar(t *testing.T, teePath string, lowerFileTree map[string]string, tarSize int) (io.ReadCloser, map[string]string) {
if lowerFileTree == nil {
lowerFileTree = map[string]string{}
}
hugeStr := hugeString(tarSize)
pr, pw := io.Pipe()
go func() {
tw := tar.NewWriter(pw)
defer pw.Close()
if len(teePath) > 0 {
teew, err := os.OpenFile(teePath, os.O_WRONLY|os.O_CREATE, 0644)
require.NoError(t, err)
defer teew.Close()
tw = tar.NewWriter(io.MultiWriter(pw, teew))
}
writeDirToTar(t, tw, "dir-1")
lowerFileTree["dir-1"] = ""
writeFileToTar(t, tw, "dir-1/.wh.file-1", "")
delete(lowerFileTree, "dir-1/file-1")
writeDirToTar(t, tw, "dir-2")
lowerFileTree["dir-2"] = ""
writeFileToTar(t, tw, "dir-2/.wh..wh..opq", "")
for k := range lowerFileTree {
if strings.HasPrefix(k, "dir-2/") {
delete(lowerFileTree, k)
}
}
writeFileToTar(t, tw, "dir-2/file-1", hugeStr)
lowerFileTree["dir-2/file-1"] = hugeStr
writeFileToTar(t, tw, "dir-2/file-2", "upper-file-2")
lowerFileTree["dir-2/file-2"] = "upper-file-2"
writeFileToTar(t, tw, "dir-2/file-3", "upper-file-3")
lowerFileTree["dir-2/file-3"] = "upper-file-3"
require.NoError(t, tw.Close())
}()
return pr, lowerFileTree
}
func packLayer(t *testing.T, source io.ReadCloser, chunkDict, workDir string, fsVersion string) (string, digest.Digest) {
var data bytes.Buffer
writer := io.Writer(&data)
twc, err := converter.Pack(context.TODO(), writer, converter.PackOption{
ChunkDictPath: chunkDict,
FsVersion: fsVersion,
})
require.NoError(t, err)
_, err = io.Copy(twc, source)
require.NoError(t, err)
err = twc.Close()
require.NoError(t, err)
blobDigester := digest.Canonical.Digester()
_, err = blobDigester.Hash().Write(data.Bytes())
require.NoError(t, err)
blobDigest := blobDigester.Digest()
tarBlobFilePath := filepath.Join(workDir, blobDigest.Hex())
writeToFile(t, bytes.NewReader(data.Bytes()), tarBlobFilePath)
return tarBlobFilePath, blobDigest
}
func packLayerRef(t *testing.T, gzipSource io.ReadCloser, workDir string) (string, digest.Digest, digest.Digest) {
var blobMetaData bytes.Buffer
blobMetaWriter := io.Writer(&blobMetaData)
pr, pw := io.Pipe()
gzipBlobDigester := digest.Canonical.Digester()
hw := io.MultiWriter(pw, gzipBlobDigester.Hash())
go func() {
defer pw.Close()
_, err := io.Copy(hw, gzipSource)
require.NoError(t, err)
}()
twc, err := converter.Pack(context.TODO(), blobMetaWriter, converter.PackOption{
OCIRef: true,
})
require.NoError(t, err)
_, err = io.Copy(twc, pr)
require.NoError(t, err)
err = twc.Close()
require.NoError(t, err)
blobMetaDigester := digest.Canonical.Digester()
_, err = blobMetaDigester.Hash().Write(blobMetaData.Bytes())
require.NoError(t, err)
blobMetaDigest := blobMetaDigester.Digest()
tarBlobMetaFilePath := filepath.Join(workDir, blobMetaDigest.Hex())
writeToFile(t, bytes.NewReader(blobMetaData.Bytes()), tarBlobMetaFilePath)
gzipBlobDigest := gzipBlobDigester.Digest()
return tarBlobMetaFilePath, blobMetaDigest, gzipBlobDigest
}
func unpackLayer(t *testing.T, workDir string, ra content.ReaderAt, stream bool) (string, digest.Digest) {
var data bytes.Buffer
writer := io.Writer(&data)
err := converter.Unpack(context.TODO(), ra, writer, converter.UnpackOption{
Stream: stream,
})
require.NoError(t, err)
digester := digest.Canonical.Digester()
_, err = digester.Hash().Write(data.Bytes())
require.NoError(t, err)
digest := digester.Digest()
tarPath := filepath.Join(workDir, digest.Hex())
writeToFile(t, bytes.NewReader(data.Bytes()), tarPath)
return tarPath, digest
}
func verify(t *testing.T, workDir string, expectedFileTree map[string]string) {
mountDir := filepath.Join(workDir, "mnt")
blobDir := filepath.Join(workDir, "blobs")
nydusdPath := os.Getenv(envNydusdPath)
if nydusdPath == "" {
nydusdPath = "nydusd"
}
config := NydusdConfig{
EnablePrefetch: false,
NydusdPath: nydusdPath,
BootstrapPath: filepath.Join(workDir, "bootstrap"),
ConfigPath: filepath.Join(workDir, "nydusd-config.fusedev.json"),
BackendType: "localfs",
BackendConfig: fmt.Sprintf(`{"dir": "%s"}`, blobDir),
BlobCacheDir: filepath.Join(workDir, "cache"),
APISockPath: filepath.Join(workDir, "nydusd-api.sock"),
MountPath: mountDir,
Mode: "direct",
DigestValidate: false,
}
nydusd, err := NewNydusd(config)
require.NoError(t, err)
err = nydusd.Mount()
require.NoError(t, err)
defer func() {
if err := nydusd.Umount(); err != nil {
log.L.WithError(err).Errorf("umount")
}
}()
actualFileTree := map[string]string{}
err = filepath.WalkDir(mountDir, func(path string, entry fs.DirEntry, err error) error {
require.Nil(t, err)
info, err := entry.Info()
require.NoError(t, err)
targetPath, err := filepath.Rel(mountDir, path)
require.NoError(t, err)
if targetPath == "." {
return nil
}
data := ""
if !info.IsDir() {
file, err := os.Open(path)
require.NoError(t, err)
defer file.Close()
_data, err := io.ReadAll(file)
require.NoError(t, err)
data = string(_data)
}
actualFileTree[targetPath] = data
return nil
})
require.NoError(t, err)
require.Equal(t, expectedFileTree, actualFileTree)
}
func buildChunkDict(t *testing.T, workDir, fsVersion string, n int) (string, string) {
dictOCITarReader := buildChunkDictTar(t, n)
blobDir := filepath.Join(workDir, "blobs")
nydusTarPath, lowerNydusBlobDigest := packLayer(t, dictOCITarReader, "", blobDir, fsVersion)
ra, err := local.OpenReader(nydusTarPath)
require.NoError(t, err)
defer ra.Close()
layers := []converter.Layer{
{
Digest: lowerNydusBlobDigest,
ReaderAt: ra,
},
}
bootstrapPath := filepath.Join(workDir, "dict-bootstrap")
file, err := os.Create(bootstrapPath)
require.NoError(t, err)
defer file.Close()
blobDigests, err := converter.Merge(context.TODO(), layers, file, converter.MergeOption{})
require.NoError(t, err)
require.Equal(t, []digest.Digest{lowerNydusBlobDigest}, blobDigests)
dictBlobPath := ""
err = filepath.WalkDir(blobDir, func(path string, _ fs.DirEntry, err error) error {
require.NoError(t, err)
if path == blobDir {
return nil
}
dictBlobPath = path
return nil
})
require.NoError(t, err)
return bootstrapPath, filepath.Base(dictBlobPath)
}
func TestPack(t *testing.T) {
testPack(t, "5")
testPack(t, "6")
}
func testPack(t *testing.T, fsVersion string) {
workDir, err := os.MkdirTemp("", "nydus-converter-test-")
require.NoError(t, err)
defer os.RemoveAll(workDir)
lowerOCITarReader, expectedLowerFileTree := buildOCILowerTar(t, 100)
upperOCITarReader, expectedOverlayFileTree := buildOCIUpperTar(t, "", expectedLowerFileTree, 3)
blobDir := filepath.Join(workDir, "blobs")
err = os.MkdirAll(blobDir, 0755)
require.NoError(t, err)
cacheDir := filepath.Join(workDir, "cache")
err = os.MkdirAll(cacheDir, 0755)
require.NoError(t, err)
mountDir := filepath.Join(workDir, "mnt")
err = os.MkdirAll(mountDir, 0755)
require.NoError(t, err)
chunkDictBootstrapPath, chunkDictBlobHash := buildChunkDict(t, workDir, fsVersion, 100)
lowerNydusTarPath, lowerNydusBlobDigest := packLayer(t, lowerOCITarReader, chunkDictBootstrapPath, blobDir, fsVersion)
upperNydusTarPath, upperNydusBlobDigest := packLayer(t, upperOCITarReader, chunkDictBootstrapPath, blobDir, fsVersion)
lowerTarRa, err := local.OpenReader(lowerNydusTarPath)
require.NoError(t, err)
defer lowerTarRa.Close()
upperTarRa, err := local.OpenReader(upperNydusTarPath)
require.NoError(t, err)
defer upperTarRa.Close()
layers := []converter.Layer{
{
Digest: lowerNydusBlobDigest,
ReaderAt: lowerTarRa,
},
{
Digest: upperNydusBlobDigest,
ReaderAt: upperTarRa,
},
}
bootstrapPath := filepath.Join(workDir, "bootstrap")
file, err := os.Create(bootstrapPath)
require.NoError(t, err)
defer file.Close()
blobDigests, err := converter.Merge(context.TODO(), layers, file, converter.MergeOption{
ChunkDictPath: chunkDictBootstrapPath,
})
require.NoError(t, err)
chunkDictBlobDigest := digest.NewDigestFromHex(string(digest.SHA256), chunkDictBlobHash)
expectedBlobDigests := []digest.Digest{chunkDictBlobDigest, upperNydusBlobDigest}
require.Equal(t, expectedBlobDigests, blobDigests)
verify(t, workDir, expectedOverlayFileTree)
dropCache(t)
verify(t, workDir, expectedOverlayFileTree)
ensureFile(t, filepath.Join(cacheDir, chunkDictBlobHash)+".blob.data.chunk_map")
ensureNoFile(t, filepath.Join(cacheDir, lowerNydusBlobDigest.Hex())+".blob.data.chunk_map")
ensureFile(t, filepath.Join(cacheDir, upperNydusBlobDigest.Hex())+".blob.data.chunk_map")
}
// sudo go test -v -count=1 -run TestPackRef ./tests
func TestPackRef(t *testing.T) {
if os.Getenv("TEST_PACK_REF") == "" {
t.Skip("skip TestPackRef test until new nydus-image/nydusd release")
}
workDir, err := os.MkdirTemp("", "nydus-converter-test-")
require.NoError(t, err)
defer os.RemoveAll(workDir)
blobDir := filepath.Join(workDir, "blobs")
err = os.MkdirAll(blobDir, 0755)
require.NoError(t, err)
cacheDir := filepath.Join(workDir, "cache")
err = os.MkdirAll(cacheDir, 0755)
require.NoError(t, err)
mountDir := filepath.Join(workDir, "mnt")
err = os.MkdirAll(mountDir, 0755)
require.NoError(t, err)
lowerOCITarReader, expectedLowerFileTree := buildOCILowerTar(t, 500)
defer lowerOCITarReader.Close()
var gzipData bytes.Buffer
gzipWriter := gzip.NewWriter(&gzipData)
_, err = io.Copy(gzipWriter, lowerOCITarReader)
require.NoError(t, err)
gzipWriter.Close()
dupGzipData := gzipData
gzipReader := io.NopCloser(&gzipData)
lowerNydusBlobPath, lowerNydusBlobDigest, lowerGzipBlobDigest := packLayerRef(t, gzipReader, blobDir)
writeToFile(t, &dupGzipData, path.Join(blobDir, lowerGzipBlobDigest.Hex()))
lowerNydusBlobRa, err := local.OpenReader(lowerNydusBlobPath)
require.NoError(t, err)
defer lowerNydusBlobRa.Close()
// Check uncompressed bootstrap digest in TOC
bootstrapDigester := digest.Canonical.Digester()
bootstrapTOC, err := converter.UnpackEntry(lowerNydusBlobRa, converter.EntryBootstrap, bootstrapDigester.Hash())
require.NoError(t, err)
require.Equal(t, bootstrapTOC.GetUncompressedDigest(), bootstrapDigester.Digest().Hex())
// Check uncompressed blob meta digest in TOC
blobMetaDigester := digest.Canonical.Digester()
blobMetaTOC, err := converter.UnpackEntry(lowerNydusBlobRa, converter.EntryBlobMeta, blobMetaDigester.Hash())
require.NoError(t, err)
require.Equal(t, blobMetaTOC.GetUncompressedDigest(), blobMetaDigester.Digest().Hex())
layers := []converter.Layer{
{
Digest: lowerNydusBlobDigest,
OriginalDigest: &lowerGzipBlobDigest,
ReaderAt: lowerNydusBlobRa,
},
}
bootstrapPath := filepath.Join(workDir, "bootstrap")
file, err := os.Create(bootstrapPath)
require.NoError(t, err)
defer file.Close()
blobDigests, err := converter.Merge(context.TODO(), layers, file, converter.MergeOption{
OCIRef: true,
})
require.NoError(t, err)
require.Equal(t, []digest.Digest{lowerGzipBlobDigest}, blobDigests)
verify(t, workDir, expectedLowerFileTree)
}
// sudo go test -v -count=1 -run TestUnpack ./tests
func TestUnpack(t *testing.T) {
testUnpack(t, "5", 3)
testUnpack(t, "6", 3)
}
func testUnpack(t *testing.T, fsVersion string, tarSize int) {
workDir, err := os.MkdirTemp("", "nydus-converter-test-")
require.NoError(t, err)
defer os.RemoveAll(workDir)
ociTar := filepath.Join(workDir, "oci.tar")
ociTarReader, _ := buildOCIUpperTar(t, ociTar, nil, tarSize)
nydusTar, _ := packLayer(t, ociTarReader, "", workDir, fsVersion)
tarTa, err := local.OpenReader(nydusTar)
require.NoError(t, err)
defer tarTa.Close()
ociTarReader, err = os.OpenFile(ociTar, os.O_RDONLY, 0644)
require.NoError(t, err)
ociTarDigest, err := digest.Canonical.FromReader(ociTarReader)
require.NoError(t, err)
for _, stream := range []bool{true, false} {
tarPath, newTarDigest := unpackLayer(t, workDir, tarTa, stream)
os.RemoveAll(tarPath)
require.Equal(t, ociTarDigest, newTarDigest)
}
}
type ConvertTestOption struct {
t *testing.T
fsVersion string
backend converter.Backend
disableCheck bool
encryptRecipients []string
decryptKeys []string
beforeConversionHook func() error
afterConversionHook func() error
}
type ReConvertTestOption struct {
t *testing.T
backend converter.Backend
encryptRecipients []string
beforeConversionHook func() error
afterConversionHook func() error
}
// sudo go test -v -count=1 -run TestImageConvert ./tests
func TestImageConvert(t *testing.T) {
for _, fsVersion := range []string{"5", "6"} {
testImageConvertNoBackend(t, fsVersion)
testImageConvertS3Backend(t, fsVersion)
testImageConvertWithCrypt(t, fsVersion)
}
}
func testImageConvertNoBackend(t *testing.T, fsVersion string) {
testImageConvertBasic(&ConvertTestOption{
t: t,
fsVersion: fsVersion,
})
}
func testImageConvertS3Backend(t *testing.T, fsVersion string) {
testOpt := &ConvertTestOption{
t: t,
fsVersion: fsVersion,
}
rawConfig := []byte(`{
"endpoint": "localhost:9000",
"scheme": "http",
"bucket_name": "nydus",
"region": "us-east-1",
"object_prefix": "path/to/my-registry/",
"access_key_id": "minio",
"access_key_secret": "minio123"
}`)
backend, err := backend.NewBackend("s3", rawConfig, true)
if err != nil {
t.Fatalf("failed to create s3 backend: %v", err)
}
testOpt.backend = backend
minioContainerName := fmt.Sprintf("minio-%d", time.Now().UnixNano())
testOpt.beforeConversionHook = func() error {
// setup minio server
if err := exec.Command("docker", "run", "-d", "-p", "9000:9000", "--name", minioContainerName, "-e", "MINIO_ACCESS_KEY=minio", "-e", "MINIO_SECRET_KEY=minio123", "minio/minio", "server", "/data").Run(); err != nil {
t.Fatalf("failed to start minio server: %v", err)
return err
}
time.Sleep(5 * time.Second)
// create nydus bucket
s3AWSConfig, err := awscfg.LoadDefaultConfig(context.TODO())
if err != nil {
t.Errorf("failed to load aws config")
}
endpoint := "http://localhost:9000"
client := s3.NewFromConfig(s3AWSConfig, func(o *s3.Options) {
o.BaseEndpoint = &endpoint
o.Region = "us-east-1"
o.UsePathStyle = true
o.Credentials = credentials.NewStaticCredentialsProvider("minio", "minio123", "")
o.UsePathStyle = true
})
_, err = client.CreateBucket(context.Background(), &s3.CreateBucketInput{Bucket: aws.String("nydus")})
if err != nil {
return err
}
logrus.Info("create s3 bucket successfully")
return nil
}
testOpt.afterConversionHook = func() error {
if err := exec.Command("docker", "rm", "-f", minioContainerName).Run(); err != nil {
return err
}
return nil
}
// TODO by now, the last release of nydusify doesn't support s3 backend
// so skip the check
testOpt.disableCheck = true
testImageConvertBasic(testOpt)
}
func testImageConvertWithCrypt(t *testing.T, fsVersion string) {
workDir, err := os.MkdirTemp("", fmt.Sprintf("nydus-bootstrap-crypt-test-%d", time.Now().UnixNano()))
require.NoError(t, err)
defer os.RemoveAll(workDir)
encryptRecipient, err := os.Create(filepath.Join(workDir, "pubkey.pem"))
if err != nil {
t.Fatalf("failed to create pubkey.pem: %v", err)
return
}
defer encryptRecipient.Close()
_, err = encryptRecipient.WriteString(string(publicKey))
if err != nil {
t.Fatalf("failed to write into pubkey.pem: %v", err)
return
}
decryptKey, err := os.Create(filepath.Join(workDir, "key.pem"))
if err != nil {
t.Fatalf("failed to create key.pem: %v", err)
return
}
defer decryptKey.Close()
_, err = decryptKey.WriteString(string(privateKey))
if err != nil {
t.Fatalf("failed to write into key.pem: %v", err)
return
}
testImageConvertBasic(&ConvertTestOption{
t: t,
fsVersion: fsVersion,
// TODO set false when nydusify ready
disableCheck: true,
encryptRecipients: []string{fmt.Sprint("jwe:", encryptRecipient.Name())},
decryptKeys: []string{decryptKey.Name()},
})
}
func testImageConvertBasic(testOpt *ConvertTestOption) {
const (
srcImageRef = "docker.io/library/nginx:latest"
targetImageRef = "localhost:5000/nydus/nginx:nydus-latest"
)
t := testOpt.t
// setup docker registry
if err := exec.Command("docker", "run", "-d", "-p", "5000:5000", "--restart=always", "--name", "registry", "registry:2").Run(); err != nil {
t.Fatalf("failed to start docker registry: %v", err)
return
}
defer func() {
if err := exec.Command("docker", "stop", "registry").Run(); err != nil {
t.Fatalf("failed to stop docker registry: %v", err)
}
if err := exec.Command("docker", "rm", "registry").Run(); err != nil {
t.Fatalf("failed to remove docker registry: %v", err)
}
}()
if testOpt.beforeConversionHook != nil {
if err := testOpt.beforeConversionHook(); err != nil {
t.Fatalf("failed to run before conversion hook: %v", err)
return
}
defer func() {
if err := testOpt.afterConversionHook(); err != nil {
t.Fatalf("failed to run after conversion hook: %v", err)
}
}()
}
if err := exec.Command("ctr", "images", "pull", srcImageRef).Run(); err != nil {
t.Fatalf("failed to pull image %s: %v", srcImageRef, err)
return
}
defer func() {
if err := exec.Command("ctr", "images", "rm", srcImageRef).Run(); err != nil {
t.Fatalf("failed to remove image %s: %v", srcImageRef, err)
}
}()
workDir, err := os.MkdirTemp("", fmt.Sprintf("nydus-containerd-converter-test-%d", time.Now().UnixNano()))
require.NoError(t, err)
defer os.RemoveAll(workDir)
nydusOpts := &converter.PackOption{
WorkDir: workDir,
FsVersion: testOpt.fsVersion,
Backend: testOpt.backend,
}
convertFunc := converter.LayerConvertFunc(*nydusOpts)
var encrypter converter.Encrypter
if len(testOpt.encryptRecipients) > 0 {
encrypter = func(ctx context.Context, cs content.Store, desc ocispec.Descriptor) (ocispec.Descriptor, error) {
return encryption.EncryptNydusBootstrap(ctx, cs, desc, testOpt.encryptRecipients)
}
}
convertHooks := containerdConverter.ConvertHooks{
PostConvertHook: converter.ConvertHookFunc(converter.MergeOption{
WorkDir: nydusOpts.WorkDir,
BuilderPath: nydusOpts.BuilderPath,
FsVersion: nydusOpts.FsVersion,
ChunkDictPath: nydusOpts.ChunkDictPath,
Backend: testOpt.backend,
PrefetchPatterns: nydusOpts.PrefetchPatterns,
Encrypt: encrypter,
}),
}
convertFuncOpt := containerdConverter.WithIndexConvertFunc(
containerdConverter.IndexConvertFuncWithHook(
convertFunc,
true,
platforms.DefaultStrict(),
convertHooks,
),
)
client, err := containerd.New("/run/containerd/containerd.sock")
if err != nil {
t.Fatal(err)
return
}
ctx := namespaces.WithNamespace(context.Background(), "default")
if _, err = containerdConverter.Convert(ctx, client, targetImageRef, srcImageRef, convertFuncOpt); err != nil {
t.Fatal(err)
return
}
defer func() {
if err := exec.Command("ctr", "images", "rm", targetImageRef).Run(); err != nil {
t.Fatalf("failed to remove image %s: %v", targetImageRef, err)
}
}()
// push target image
if output, err := exec.Command("ctr", "images", "push", "--plain-http", targetImageRef).CombinedOutput(); err != nil {
t.Fatalf("failed to push image %s: %v, output:\n%s", targetImageRef, err, string(output))
return
}
// check whether the converted image is valid
if testOpt.disableCheck {
return
}
if len(testOpt.decryptKeys) != 0 {
if output, err := exec.Command("nydusify", "check",
"--source", srcImageRef,
"--target", targetImageRef,
"--decrypt-keys", strings.Join(testOpt.decryptKeys, ","),
"--target-insecure").CombinedOutput(); err != nil {
t.Fatalf("failed to check image %s: %v, \noutput:\n%s", targetImageRef, err, output)
return
}
return
}
if output, err := exec.Command("nydusify", "check", "--source", srcImageRef, "--target", targetImageRef, "--target-insecure").CombinedOutput(); err != nil {
t.Fatalf("failed to check image %s: %v, \noutput:\n%s", targetImageRef, err, output)
return
}
}
// sudo go test -v -count=1 -run TestImageReConvert ./tests
func TestImageReConvert(t *testing.T) {
testImageReConvertNoBackend(t)
}
func testImageReConvertNoBackend(t *testing.T) {
testImageReConvertBasic(&ReConvertTestOption{
t: t,
})
}
func testImageReConvertBasic(testOpt *ReConvertTestOption) {
const (
preSrcImageRef = "docker.io/library/nginx:latest"
srcImageRef = "localhost:5000/nydus/nginx:nydus-latest"
targetImageRef = "localhost:5000/nydus/nginx:oci"
)
t := testOpt.t
// setup docker registry
if err := exec.Command("docker", "run", "-d", "-p", "5000:5000", "--restart=always", "--name", "registry", "registry:2").Run(); err != nil {
t.Fatalf("failed to start docker registry: %v", err)
return
}
defer func() {
if err := exec.Command("docker", "stop", "registry").Run(); err != nil {
t.Fatalf("failed to stop docker registry: %v", err)
}
if err := exec.Command("docker", "rm", "registry").Run(); err != nil {
t.Fatalf("failed to remove docker registry: %v", err)
}
}()
if testOpt.beforeConversionHook != nil {
if err := testOpt.beforeConversionHook(); err != nil {
t.Fatalf("failed to run before conversion hook: %v", err)
return
}
defer func() {
if err := testOpt.afterConversionHook(); err != nil {
t.Fatalf("failed to run after conversion hook: %v", err)
}
}()
}
// use convert to create nydus format srcImager
if err := exec.Command("ctr", "images", "pull", preSrcImageRef).Run(); err != nil {
t.Fatalf("failed to pull image %s: %v", preSrcImageRef, err)
return
}
defer func() {
if err := exec.Command("ctr", "images", "rm", preSrcImageRef).Run(); err != nil {
t.Fatalf("failed to remove image %s: %v", preSrcImageRef, err)
}
}()
workDir, err := os.MkdirTemp("", fmt.Sprintf("nydus-containerd-converter-test-%d", time.Now().UnixNano()))
require.NoError(t, err)
defer os.RemoveAll(workDir)
nydusOpts := &converter.PackOption{
WorkDir: workDir,
Backend: testOpt.backend,
}
convertFunc := converter.LayerConvertFunc(*nydusOpts)
var encrypter converter.Encrypter
if len(testOpt.encryptRecipients) > 0 {
encrypter = func(ctx context.Context, cs content.Store, desc ocispec.Descriptor) (ocispec.Descriptor, error) {
return encryption.EncryptNydusBootstrap(ctx, cs, desc, testOpt.encryptRecipients)
}
}
convertHooks := containerdConverter.ConvertHooks{
PostConvertHook: converter.ConvertHookFunc(converter.MergeOption{
WorkDir: nydusOpts.WorkDir,
BuilderPath: nydusOpts.BuilderPath,
FsVersion: nydusOpts.FsVersion,
ChunkDictPath: nydusOpts.ChunkDictPath,
Backend: testOpt.backend,
PrefetchPatterns: nydusOpts.PrefetchPatterns,
Encrypt: encrypter,
}),
}
convertFuncOpt := containerdConverter.WithIndexConvertFunc(
containerdConverter.IndexConvertFuncWithHook(
convertFunc,
true,
platforms.DefaultStrict(),
convertHooks,
),
)
client, err := containerd.New("/run/containerd/containerd.sock")
if err != nil {
t.Fatal(err)
return
}
ctx := namespaces.WithNamespace(context.Background(), "default")
if _, err = containerdConverter.Convert(ctx, client, srcImageRef, preSrcImageRef, convertFuncOpt); err != nil {
t.Fatal(err)
return
}
defer func() {
if err = exec.Command("ctr", "images", "rm", srcImageRef).Run(); err != nil {
t.Fatalf("failed to remove image %s: %v", srcImageRef, err)
}
}()
nydusReconvertOpts := &converter.UnpackOption{
WorkDir: workDir,
Backend: testOpt.backend,
}
reconvertFunc := converter.LayerReconvertFunc(*nydusReconvertOpts)
reconvertHook := containerdConverter.ConvertHooks{
PostConvertHook: converter.ReconvertHookFunc(),
}
reConvertFuncOpt := containerdConverter.WithIndexConvertFunc(
containerdConverter.IndexConvertFuncWithHook(
reconvertFunc,
false,
platforms.DefaultStrict(),
reconvertHook,
),
)
if _, err = containerdConverter.Convert(ctx, client, targetImageRef, srcImageRef, reConvertFuncOpt); err != nil {
t.Fatal(err)
return
}
defer func() {
if err := exec.Command("ctr", "images", "rm", targetImageRef).Run(); err != nil {
t.Fatalf("failed to remove image %s: %v", targetImageRef, err)
}
}()
}
================================================
FILE: tests/e2e/k8s/kind.yaml
================================================
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
ipFamily: dual
containerdConfigPatches:
- |-
[plugins."io.containerd.grpc.v1.cri".containerd]
discard_unpacked_layers = false
disable_snapshot_annotations = false
nodes:
- role: control-plane
extraMounts:
- hostPath: /dev/fuse
containerPath: /dev/fuse
================================================
FILE: tests/e2e/k8s/snapshotter-cri.yaml
================================================
---
apiVersion: v1
kind: Namespace
metadata:
name: nydus-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: nydus-snapshotter-sa
namespace: nydus-system
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nydus-snapshotter-role
rules:
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- patch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nydus-snapshotter-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: nydus-snapshotter-role
subjects:
- kind: ServiceAccount
name: nydus-snapshotter-sa
namespace: nydus-system
---
apiVersion: v1
kind: Pod
metadata:
name: nydus-snapshotter
namespace: nydus-system
labels:
app: nydus-snapshotter
spec:
serviceAccountName: nydus-snapshotter-sa
hostNetwork: true
hostPID: true
containers:
- name: nydus-snapshotter
image: "local-dev:e2e"
imagePullPolicy: IfNotPresent
env:
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: FS_DRIVER
valueFrom:
configMapKeyRef:
name: nydus-snapshotter-configs
key: FS_DRIVER
optional: true
- name: ENABLE_CONFIG_FROM_VOLUME
valueFrom:
configMapKeyRef:
name: nydus-snapshotter-configs
key: ENABLE_CONFIG_FROM_VOLUME
optional: true
- name: ENABLE_RUNTIME_SPECIFIC_SNAPSHOTTER
valueFrom:
configMapKeyRef:
name: nydus-snapshotter-configs
key: ENABLE_RUNTIME_SPECIFIC_SNAPSHOTTER
optional: true
- name: ENABLE_SYSTEMD_SERVICE
valueFrom:
configMapKeyRef:
name: nydus-snapshotter-configs
key: ENABLE_SYSTEMD_SERVICE
optional: true
lifecycle:
preStop:
exec:
command:
- "bash"
- "-c"
- |
/opt/nydus-artifacts/opt/nydus/snapshotter.sh cleanup
command:
- bash
- -c
- |-
/opt/nydus-artifacts/opt/nydus/snapshotter.sh deploy
volumeMounts:
- name: config-volume
mountPath: "/etc/nydus-snapshotter"
- name: nydus-lib
mountPath: "/var/lib/containerd/io.containerd.snapshotter.v1.nydus"
mountPropagation: Bidirectional
- name: nydus-run
mountPath: "/run/containerd-nydus"
mountPropagation: Bidirectional
- name: nydus-opt
mountPath: "/opt/nydus"
mountPropagation: Bidirectional
- name: nydus-etc
mountPath: "/etc/nydus"
mountPropagation: Bidirectional
- name: containerd-conf
mountPath: "/etc/containerd/"
- name: local-bin
mountPath: "/usr/local/bin/"
- name: etc-systemd-system
mountPath: "/etc/systemd/system/"
- name: fuse
mountPath: /dev/fuse
securityContext:
privileged: true
volumes:
- name: config-volume
configMap:
name: nydus-snapshotter-configs
optional: true
- name: nydus-run
hostPath:
path: /run/containerd-nydus
type: DirectoryOrCreate
- name: nydus-lib
hostPath:
path: /var/lib/containerd/io.containerd.snapshotter.v1.nydus
type: DirectoryOrCreate
- name: nydus-etc
hostPath:
path: /etc/nydus
type: DirectoryOrCreate
- name: nydus-opt
hostPath:
path: /opt/nydus
type: DirectoryOrCreate
- name: containerd-conf
hostPath:
path: /etc/containerd/
- name: local-bin
hostPath:
path: /usr/local/bin/
- name: etc-systemd-system
hostPath:
path: /etc/systemd/system/
- name: fuse
hostPath:
path: /dev/fuse
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nydus-snapshotter-configs
labels:
app: nydus-snapshotter
namespace: nydus-system
data:
FS_DRIVER: "fusedev"
ENABLE_CONFIG_FROM_VOLUME: "true"
ENABLE_RUNTIME_SPECIFIC_SNAPSHOTTER: "false"
ENABLE_SYSTEMD_SERVICE: "true"
config.toml: |-
version = 1
root = "/var/lib/containerd/io.containerd.snapshotter.v1.nydus"
address = "/run/containerd-nydus/containerd-nydus-grpc.sock"
daemon_mode = "multiple"
# Whether snapshotter should try to clean up resources when it is closed
cleanup_on_close = false
[system]
# Snapshotter's debug and trace HTTP server interface
enable = true
# Unix domain socket path where system controller is listening on
address = "/run/containerd-nydus/system.sock"
[metrics]
# Enable by assigning an address, empty indicates metrics server is disabled
address = ":9110"
# The maximum duration an I/O operation can be pending before it's considered hung.
hung_io_interval = "30s"
# The interval at which metrics are collected and updated.
collect_interval = "1m"
[experimental]
# Whether tp enable stargz support
enable_stargz = false
[daemon]
nydusd_path = "/usr/local/bin/nydusd"
nydusimage_path = "/usr/local/bin/nydus-image"
# fusedev or fscache
fs_driver = "fusedev"
# Specify nydusd log level
log_level = "info"
# How to process when daemon dies: "none", "restart" or "failover"
recover_policy = "restart"
# Specify a configuration file for nydusd
nydusd_config = "/etc/nydus/nydusd.json"
# The fuse or fscache IO working threads started by nydusd
threads_number = 4
[log]
# Snapshotter's log level
level = "info"
log_rotation_compress = true
log_rotation_local_time = true
# Max number of days to retain logs
log_rotation_max_age = 7
log_rotation_max_backups = 5
# In unit MB(megabytes)
log_rotation_max_size = 1
log_to_stdout = false
[remote]
convert_vpc_registry = false
[remote.auth]
# Fetch the private registry auth by listening to K8s API server
enable_kubeconfig_keychain = false
# synchronize `kubernetes.io/dockerconfigjson` secret from kubernetes API server with specified kubeconfig (default `$KUBECONFIG` or `~/.kube/config`)
kubeconfig_path = ""
# Fetch the private registry auth as CRI image service proxy
enable_cri_keychain = true
# the target image service when using image proxy
image_service_address = ""
[snapshot]
enable_nydus_overlayfs = false
# Whether to remove resources when a snapshot is removed
sync_remove = false
[cache_manager]
disable = false
gc_period = "24h"
cache_dir = ""
[image]
public_key_file = ""
validate_signature = false
nydusd.json: |-
{
"device": {
"backend": {
"type": "registry",
"config": {
"scheme": "",
"skip_verify": true,
"timeout": 10,
"connect_timeout": 10,
"retry_limit": 2
}
},
"cache": {
"type": "blobcache",
"config": {
"work_dir": "/var/lib/nydus/cache/"
}
}
},
"mode": "direct",
"digest_validate": false,
"iostats_files": false,
"enable_xattr": true,
"amplify_io": 1048576,
"fs_prefetch": {
"enable": true,
"threads_count": 10,
"merging_size": 131072,
"bandwidth_rate": 1048576
}
}
================================================
FILE: tests/e2e/k8s/snapshotter-kubeconf.yaml
================================================
---
apiVersion: v1
kind: Namespace
metadata:
name: nydus-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: nydus-snapshotter-sa
namespace: nydus-system
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nydus-snapshotter-role
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- patch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nydus-snapshotter-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: nydus-snapshotter-role
subjects:
- kind: ServiceAccount
name: nydus-snapshotter-sa
namespace: nydus-system
---
apiVersion: v1
kind: Pod
metadata:
name: nydus-snapshotter
namespace: nydus-system
labels:
app: nydus-snapshotter
spec:
serviceAccountName: nydus-snapshotter-sa
hostNetwork: true
hostPID: true
containers:
- name: nydus-snapshotter
image: "local-dev:e2e"
imagePullPolicy: IfNotPresent
env:
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: FS_DRIVER
valueFrom:
configMapKeyRef:
name: nydus-snapshotter-configs
key: FS_DRIVER
optional: true
- name: ENABLE_CONFIG_FROM_VOLUME
valueFrom:
configMapKeyRef:
name: nydus-snapshotter-configs
key: ENABLE_CONFIG_FROM_VOLUME
optional: true
- name: ENABLE_RUNTIME_SPECIFIC_SNAPSHOTTER
valueFrom:
configMapKeyRef:
name: nydus-snapshotter-configs
key: ENABLE_RUNTIME_SPECIFIC_SNAPSHOTTER
optional: true
- name: ENABLE_SYSTEMD_SERVICE
valueFrom:
configMapKeyRef:
name: nydus-snapshotter-configs
key: ENABLE_SYSTEMD_SERVICE
optional: true
lifecycle:
preStop:
exec:
command:
- "bash"
- "-c"
- |
/opt/nydus-artifacts/opt/nydus/snapshotter.sh cleanup
command:
- bash
- -c
- |-
/opt/nydus-artifacts/opt/nydus/snapshotter.sh deploy
volumeMounts:
- name: config-volume
mountPath: "/etc/nydus-snapshotter"
- name: nydus-lib
mountPath: "/var/lib/containerd/io.containerd.snapshotter.v1.nydus"
mountPropagation: Bidirectional
- name: nydus-run
mountPath: "/run/containerd-nydus"
mountPropagation: Bidirectional
- name: nydus-opt
mountPath: "/opt/nydus"
mountPropagation: Bidirectional
- name: nydus-etc
mountPath: "/etc/nydus"
mountPropagation: Bidirectional
- name: containerd-conf
mountPath: "/etc/containerd/"
- name: local-bin
mountPath: "/usr/local/bin/"
- name: etc-systemd-system
mountPath: "/etc/systemd/system/"
- name: fuse
mountPath: /dev/fuse
securityContext:
privileged: true
volumes:
- name: config-volume
configMap:
name: nydus-snapshotter-configs
optional: true
- name: nydus-run
hostPath:
path: /run/containerd-nydus
type: DirectoryOrCreate
- name: nydus-lib
hostPath:
path: /var/lib/containerd/io.containerd.snapshotter.v1.nydus
type: DirectoryOrCreate
- name: nydus-etc
hostPath:
path: /etc/nydus
type: DirectoryOrCreate
- name: nydus-opt
hostPath:
path: /opt/nydus
type: DirectoryOrCreate
- name: containerd-conf
hostPath:
path: /etc/containerd/
- name: local-bin
hostPath:
path: /usr/local/bin/
- name: etc-systemd-system
hostPath:
path: /etc/systemd/system/
- name: fuse
hostPath:
path: /dev/fuse
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nydus-snapshotter-configs
labels:
app: nydus-snapshotter
namespace: nydus-system
data:
FS_DRIVER: "fusedev"
ENABLE_CONFIG_FROM_VOLUME: "true"
ENABLE_RUNTIME_SPECIFIC_SNAPSHOTTER: "false"
ENABLE_SYSTEMD_SERVICE: "false"
config.toml: |-
version = 1
root = "/var/lib/containerd/io.containerd.snapshotter.v1.nydus"
address = "/run/containerd-nydus/containerd-nydus-grpc.sock"
daemon_mode = "multiple"
# Whether snapshotter should try to clean up resources when it is closed
cleanup_on_close = false
[system]
# Snapshotter's debug and trace HTTP server interface
enable = true
# Unix domain socket path where system controller is listening on
address = "/run/containerd-nydus/system.sock"
[metrics]
# Enable by assigning an address, empty indicates metrics server is disabled
address = ":9110"
# The maximum duration an I/O operation can be pending before it's considered hung.
hung_io_interval = "30s"
# The interval at which metrics are collected and updated.
collect_interval = "1m"
[experimental]
# Whether tp enable stargz support
enable_stargz = false
enable_index_detect = true
[daemon]
nydusd_path = "/usr/local/bin/nydusd"
nydusimage_path = "/usr/local/bin/nydus-image"
# fusedev or fscache
fs_driver = "fusedev"
# Specify nydusd log level
log_level = "info"
# How to process when daemon dies: "none", "restart" or "failover"
recover_policy = "restart"
# Specify a configuration file for nydusd
nydusd_config = "/etc/nydus/nydusd.json"
# The fuse or fscache IO working threads started by nydusd
threads_number = 4
[log]
# Snapshotter's log level
level = "info"
log_rotation_compress = true
log_rotation_local_time = true
# Max number of days to retain logs
log_rotation_max_age = 7
log_rotation_max_backups = 5
# In unit MB(megabytes)
log_rotation_max_size = 1
log_to_stdout = false
[remote]
convert_vpc_registry = false
[remote.auth]
# Fetch the private registry auth by listening to K8s API server
enable_kubeconfig_keychain = true
# synchronize `kubernetes.io/dockerconfigjson` secret from kubernetes API server with specified kubeconfig (default `$KUBECONFIG` or `~/.kube/config`)
kubeconfig_path = ""
# Fetch the private registry auth as CRI image service proxy
enable_cri_keychain = false
# the target image service when using image proxy
image_service_address = ""
[snapshot]
enable_nydus_overlayfs = false
# Whether to remove resources when a snapshot is removed
sync_remove = false
[cache_manager]
disable = false
gc_period = "24h"
cache_dir = ""
[image]
public_key_file = ""
validate_signature = false
nydusd.json: |-
{
"device": {
"backend": {
"type": "registry",
"config": {
"scheme": "",
"skip_verify": true,
"timeout": 10,
"connect_timeout": 10,
"retry_limit": 2
}
},
"cache": {
"type": "blobcache",
"config": {
"work_dir": "/var/lib/nydus/cache/"
}
}
},
"mode": "direct",
"digest_validate": false,
"iostats_files": false,
"enable_xattr": true,
"amplify_io": 1048576,
"fs_prefetch": {
"enable": true,
"threads_count": 10,
"merging_size": 131072,
"bandwidth_rate": 1048576
}
}
================================================
FILE: tests/e2e/k8s/test-pod.yaml.tpl
================================================
apiVersion: v1
kind: Pod
metadata:
name: test-pod
namespace: nydus-system
spec:
containers:
- name: busybox
image: REGISTRY_URL/busybox:nydus-v6-latest
imagePullPolicy: Always
command: ["sh", "-c"]
args:
- tail -f /dev/null
imagePullSecrets:
- name: regcred
================================================
FILE: tests/helpers/helpers.sh
================================================
#!/usr/bin/env bash
# Copyright The containerd Authors.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -o errexit -o errtrace -o functrace -o nounset -o pipefail
## Helpers specific to nydus
_rootful=
configure::rootful(){
log::debug "Configuring rootful to: ${1:+true}"
_rootful="${1:+true}"
}
configure::dockerd(){
local config="$1"
log::debug "Reconfiguring docker and restarting"
log::debug "$config"
sudo touch /etc/docker/daemon.json
printf "%s" "$config" | sudo tee /etc/docker/daemon.json >/dev/null
sudo systemctl restart docker
}
exec::docker(){
local args=()
[ ! "$_rootful" ] || args=(sudo)
args+=(docker)
log::debug "${args[*]} $*"
"${args[@]}" "$@"
}
exec::kind(){
local args=()
[ ! "$_rootful" ] || args=(sudo)
args+=(kind)
log::debug "${args[*]} $*"
"${args[@]}" "$@"
}
exec::kubectl(){
local args=()
[ ! "$_rootful" ] || args=(sudo)
args+=(kubectl)
log::debug "${args[*]} $*"
"${args[@]}" "$@"
}
exec::nydusify(){
local args=()
[ ! "$_rootful" ] || args=(sudo)
args+=(nydusify)
log::debug "${args[*]} $*"
"${args[@]}" "$@"
}
docker::configpath(){
[ "$_rootful" ] && printf "/root/.docker/config.json" || printf "%s/.docker/config.json" "$HOME"
}
docker::login(){
local user="$1"
local password="$2"
local registry_url="$3"
local args=()
[ ! "$_rootful" ] || args=(sudo)
args+=(docker login --password-stdin)
log::debug "${args[*]} --username=$user $registry_url"
"${args[@]}" --username="$user" "$registry_url" <<<"$password" >/dev/null
}
# Installation helpers
install::kind(){
local version="$1"
local temp
temp="$(fs::mktemp "install")"
http::get "$temp"/kind "https://kind.sigs.k8s.io/dl/$version/kind-linux-${GOARCH:-amd64}"
host::install "$temp"/kind
}
install::kubectl(){
local version="${1:-v1.30.0}"
[ "$version" ] || version="$(http::get /dev/stdout https://dl.k8s.io/release/stable.txt)"
local temp
temp="$(fs::mktemp "install")"
http::get "$temp"/kubectl "https://dl.k8s.io/release/$version/bin/linux/${GOARCH:-amd64}/kubectl"
host::install "$temp"/kubectl
}
install::nydus(){
local version="$1"
local temp
temp="$(fs::mktemp "install")"
http::get "$temp"/nydus-static.tgz "https://github.com/dragonflyoss/nydus/releases/download/$version/nydus-static-$version-linux-${GOARCH:-amd64}.tgz"
tar::expand "$temp" "$temp"/nydus-static.tgz
host::install "$temp"/nydus-static/nydus-image
host::install "$temp"/nydus-static/nydusify
host::install "$temp"/nydus-static/nydusd
host::install "$temp"/nydus-static/nydusctl
}
install::nerdctl(){
local version="$1"
local temp
temp="$(fs::mktemp "install")"
http::get "$temp"/nerdctl.tar.gz "https://github.com/containerd/nerdctl/releases/download/v$version/nerdctl-$version-linux-${GOARCH:-amd64}.tar.gz"
tar::expand "$temp" "$temp"/nerdctl.tar.gz
host::install "$temp"/nerdctl
}
start::registry(){
local user="$1"
local password="$2"
local tempdir
tempdir="$(fs::mktemp registry)"
local port=5000
local ip
exec::docker rm -f registry-auth-"$port" >/dev/null 2>&1 || true
exec::docker run \
--rm \
--entrypoint htpasswd \
httpd:2 -Bbn "$user" "$password" > "$tempdir"/htpasswd
exec::docker run -d \
-p "$port":5000 \
--restart=always \
--name registry-auth-"$port" \
-v "$tempdir":/auth \
-e "REGISTRY_AUTH=htpasswd" \
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
registry:2 >/dev/null
ip="$(ip addr show eth0 | grep 'inet ' | awk '{print $2}' | cut -d/ -f1)"
printf "%s:%s" "$ip" "$port"
}
================================================
FILE: tests/helpers/kind.sh
================================================
#!/usr/bin/env bash
set -o errexit -o errtrace -o functrace -o nounset -o pipefail
AUTH_TYPE="${AUTH_TYPE:-kubeconf}"
INDEX_DETECT="${INDEX_DETECT:-false}"
[ "$(uname -m)" == "aarch64" ] && GOARCH=arm64 || GOARCH=amd64
ROOTFUL="${ROOTFUL:-}"
root="$(cd "$(dirname "${BASH_SOURCE[0]:-$PWD}")" 2>/dev/null 1>&2 && pwd)"
readonly root
# shellcheck source=/dev/null
. "$root/lib.sh"
# shellcheck source=/dev/null
. "$root/helpers.sh"
KIND_VERSION=v0.23.0
KUBE_VERSION=v1.30.2
NYDUS_VERSION=v2.3.0
DOCKER_USER=testuser
DOCKER_PASSWORD=testpassword
NAMESPACE=nydus-system
ADDITIONAL_NYDUS_CONVERT_ARGS=""
if [ "$INDEX_DETECT" == "true" ]; then
ADDITIONAL_NYDUS_CONVERT_ARGS="--merge-platform"
fi
log::info "Configuring rootful and github"
configure::rootful "${ROOTFUL:-}"
github::settoken '${{ secrets.GITHUB_TOKEN }}'
NYDUS_VERSION="$(github::releases::latest dragonflyoss/nydus | jq -rc 'select(.tag_name != null) | .tag_name' || printf "%s" "$NYDUS_VERSION")"
log::info "Get latest nydus version $NYDUS_VERSION"
# Build
log::info "Building... make"
# Binaries are going to be copied over inside the kind container that likely has a different glibc we do not control.
# So, build static
if [ "$LOG_LEVEL" == debug ]; then
make -d static
else
make -s static
fi
cp bin/containerd-nydus-grpc ./
cp bin/nydus-overlayfs ./
cp -r misc/snapshotter/* ./
log::info "Building... docker image"
pwd
exec::docker build --build-arg NYDUS_VER="$NYDUS_VERSION" --build-arg KUBE_VER="$KUBE_VERSION" -t local-dev:e2e .
# Start local registry, and configure docker
log::info "Starting registry"
registry_url="$(start::registry "$DOCKER_USER" "$DOCKER_PASSWORD")"
# Configure template
sed -e "s|REGISTRY_URL|$registry_url|" tests/e2e/k8s/test-pod.yaml.tpl > tests/e2e/k8s/test-pod.yaml
configure::dockerd '{
"exec-opts": ["native.cgroupdriver=cgroupfs"],
"cgroup-parent": "/actions_job",
"insecure-registries" : [ "'"$registry_url"'" ]
}'
http::healthcheck "$registry_url"/v2/ 10 2 "$DOCKER_USER" "$DOCKER_PASSWORD"
log::info "Login"
docker::login "$DOCKER_USER" "$DOCKER_PASSWORD" "$registry_url"
# Install dependencies
log::info "Installing host dependencies"
install::kind "$KIND_VERSION"
install::kubectl "$KUBE_VERSION"
install::nydus "$NYDUS_VERSION"
# Convert a nydus image and push it
log::info "Converting test image to nydus and push"
exec::nydusify convert \
--source busybox:latest \
--target $registry_url/busybox:nydus-v6-latest \
--fs-version 6 \
${ADDITIONAL_NYDUS_CONVERT_ARGS}
# Create fresh cluster
log::info "Creating new cluster"
exec::kind delete cluster 2>/dev/null || true
exec::kind create cluster --config tests/e2e/k8s/kind.yaml --image "kindest/node:$KUBE_VERSION"
exec::kind load docker-image local-dev:e2e
# Deploy nydus
log::info "Deploying nydus"
exec::kubectl create -f tests/e2e/k8s/snapshotter-"$AUTH_TYPE".yaml
pod="$(exec::kubectl --namespace "$NAMESPACE" get pods --no-headers -o custom-columns=NAME:metadata.name)"
exec::kubectl --namespace "$NAMESPACE" wait po "$pod" --for=condition=ready --timeout=1m
# Reconfigure and restart kind containerd
log::info "Restarting containerd"
echo '[plugins."io.containerd.grpc.v1.cri".registry.mirrors."'"$registry_url"'"]
endpoint = ["http://'"$registry_url"'"]' |
exec::docker exec -i kind-control-plane sh -c 'cat /dev/stdin >> /etc/containerd/config.toml'
exec::docker exec kind-control-plane systemctl restart containerd
# Actual testing
exec::kubectl delete --namespace "$NAMESPACE" secret generic regcred 2>/dev/null || true
exec::kubectl create --namespace "$NAMESPACE" secret generic regcred \
--from-file=.dockerconfigjson="$(docker::configpath)" \
--type=kubernetes.io/dockerconfigjson
if [ "$AUTH_TYPE" == "cri" ]; then
exec::docker exec kind-control-plane sh -c 'echo " --image-service-endpoint=unix:///run/containerd-nydus/containerd-nydus-grpc.sock" >> /etc/default/kubelet'
exec::docker exec kind-control-plane sh -c 'systemctl daemon-reload && systemctl restart kubelet'
# The API server may become briefly unavailable after the kubelet
# restart. Wait for it to recover before issuing kubectl commands.
log::info "Waiting for API server to recover"
for _ in $(seq 1 10); do
kubectl get --raw /healthz &>/dev/null && break
sleep 2
done
kubectl get --raw /healthz &>/dev/null || {
log::error "API server did not recover within 20s"
exit 1
}
fi
exec::kubectl apply -f tests/e2e/k8s/test-pod.yaml
exec::kubectl wait po test-pod --namespace nydus-system --for=condition=ready --timeout=1m || {
exec::kubectl --namespace nydus-system get pods
exec::kubectl --namespace nydus-system get events
exec::kubectl --namespace nydus-system logs nydus-snapshotter
exec::kubectl --namespace nydus-system logs test-pod
exit 1
}
if [ "$INDEX_DETECT" == "true" ]; then
snapshotter_logs="$(exec::kubectl --namespace nydus-system exec nydus-snapshotter -- cat /var/lib/containerd/io.containerd.snapshotter.v1.nydus/logs/nydus-snapshotter.log)"
echo "$snapshotter_logs" | grep -q "Found nydus alternative image in index for image" || {
log::error "Nydus snapshotter did not detect nydus image in index. Logs:"
log::error "$snapshotter_logs"
exit 1
}
fi
exec::kubectl delete -f tests/e2e/k8s/test-pod.yaml
================================================
FILE: tests/helpers/lib.sh
================================================
#!/usr/bin/env bash
# Copyright The containerd Authors.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# shellcheck disable=SC2034
set -o errexit -o errtrace -o functrace -o nounset -o pipefail
## This is a library of generic helpers that can be used accross many different projects
# Simple logger
readonly LOG_LEVEL_DEBUG=0
readonly LOG_LEVEL_INFO=1
readonly LOG_LEVEL_WARNING=2
readonly LOG_LEVEL_ERROR=3
readonly LOG_COLOR_BLACK=0
readonly LOG_COLOR_RED=1
readonly LOG_COLOR_GREEN=2
readonly LOG_COLOR_YELLOW=3
readonly LOG_COLOR_BLUE=4
readonly LOG_COLOR_MAGENTA=5
readonly LOG_COLOR_CYAN=6
readonly LOG_COLOR_WHITE=7
readonly LOG_COLOR_DEFAULT=9
readonly LOG_STYLE_DEBUG=( setaf "$LOG_COLOR_WHITE" )
readonly LOG_STYLE_INFO=( setaf "$LOG_COLOR_GREEN" )
readonly LOG_STYLE_WARNING=( setaf "$LOG_COLOR_YELLOW" )
readonly LOG_STYLE_ERROR=( setaf "$LOG_COLOR_RED" )
_log::log(){
local level
local style
local numeric_level
local message="$2"
level="$(printf "%s" "$1" | tr '[:lower:]' '[:upper:]')"
numeric_level="$(printf "LOG_LEVEL_%s" "$level")"
style="LOG_STYLE_${level}[@]"
[ "${!numeric_level}" -ge "$LOG_LEVEL" ] || return 0
[ ! "$TERM" ] || [ ! -t 2 ] || >&2 tput "${!style}" 2>/dev/null || true
>&2 printf "[%s] %s: %s\n" "$(date 2>/dev/null || true)" "$(printf "%s" "$level" | tr '[:lower:]' '[:upper:]')" "$message"
[ ! "$TERM" ] || [ ! -t 2 ] || >&2 tput op 2>/dev/null || true
}
log::init(){
local _ll
# Default log to warning if unspecified
_ll="$(printf "LOG_LEVEL_%s" "${LOG_LEVEL:-warning}" | tr '[:lower:]' '[:upper:]')"
# Default to 3 (warning) if unrecognized
LOG_LEVEL="${!_ll:-3}"
}
log::debug(){
_log::log debug "$@"
}
log::info(){
_log::log info "$@"
}
log::warning(){
_log::log warning "$@"
}
log::error(){
_log::log error "$@"
}
# Helpers
host::require(){
local binary="$1"
log::debug "Checking presence of $binary"
command -v "$binary" >/dev/null || {
log::error "You need $binary for this script to work, and it cannot be found in your path"
return 1
}
}
host::install(){
local binary
for binary in "$@"; do
log::debug "sudo install -D -m 755 $binary /usr/local/bin/$(basename "$binary")"
sudo install -D -m 755 "$binary" /usr/local/bin/"$(basename "$binary")"
done
}
fs::mktemp(){
local prefix="${1:-temporary}"
mktemp -dq "${TMPDIR:-/tmp}/$prefix.XXXXXX" 2>/dev/null || mktemp -dq || {
log::error "Failed to create temporary directory"
return 1
}
}
tar::expand(){
local dir="$1"
local arc="$2"
log::debug "tar -C $dir -xzf $arc"
tar -C "$dir" -xzf "$arc"
}
_http::get(){
local url="$1"
local output="$2"
local retry="$3"
local delay="$4"
local user="${5:-}"
local password="${6:-}"
shift
shift
shift
shift
shift
shift
local header
local command=(curl -fsSL --retry-connrefused --retry "$retry" --retry-delay "$delay" -o "$output")
# Add a basic auth user if necessary
[ "$user" == "" ] || command+=(--user "$user:$password")
# Force tls v1.2 and no redirect to http if url scheme is https
[ "${url:0:5}" != "https" ] || command+=(--proto '=https' --tlsv1.2)
# Stuff in any additional arguments as headers
for header in "$@"; do
command+=(-H "$header")
done
# Debug
log::debug "${command[*]} $url"
# Exec
"${command[@]}" "$url" || {
log::error "Failed to connect to $url with $retry retries every $delay seconds"
return 1
}
}
http::get(){
local output="$1"
local url="$2"
shift
shift
_http::get "$url" "$output" "2" "1" "" "" "$@"
}
http::healthcheck(){
local url="$1"
local retry="${2:-5}"
local delay="${3:-1}"
local user="${4:-}"
local password="${5:-}"
shift
shift
shift
shift
shift
_http::get "$url" /dev/null "$retry" "$delay" "$user" "$password" "$@"
}
http::checksum(){
local urls=("$@")
local url
local temp
temp="$(fs::mktemp "http-checksum")"
for url in "${urls[@]}"; do
http::get -o "$temp/${url##*/}" "$url"
done
cd "$temp"
shasum -a 256 ./*
cd - >/dev/null || true
}
# Github API helpers
# Set GITHUB_TOKEN to use authenticated requests to workaround limitations
github::settoken(){
local token="$1"
# If passed token is a github action pattern replace, and we are NOT on github, ignore it
[ "${token:0:3}" == '${{' ] || GITHUB_TOKEN="$token"
}
github::request(){
local endpoint="$1"
local args=(
"Accept: application/vnd.github+json"
"X-GitHub-Api-Version: 2022-11-28"
)
[ "${GITHUB_TOKEN:-}" == "" ] || args+=("Authorization: Bearer $GITHUB_TOKEN")
http::get /dev/stdout https://api.github.com/"$endpoint" "${args[@]}"
}
github::tags::latest(){
local repo="$1"
github::request "repos/$repo/tags" | jq -rc .[0].name
}
github::releases::latest(){
local repo="$1"
github::request "repos/$repo/releases/latest" | jq -rc .
}
log::init
host::require jq
host::require tar
host::require curl
host::require shasum
================================================
FILE: tests/nydusd.go
================================================
/*
* Copyright (c) 2022. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package tests
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"os"
"os/exec"
"text/template"
"time"
"github.com/pkg/errors"
)
type NydusdConfig struct {
EnablePrefetch bool
NydusdPath string
BootstrapPath string
ConfigPath string
BackendType string
BackendConfig string
BlobCacheDir string
APISockPath string
MountPath string
Mode string
DigestValidate bool
}
// Nydusd runs nydusd binary.
type Nydusd struct {
NydusdConfig
}
type daemonInfo struct {
State string `json:"state"`
}
var configTpl = `
{
"device": {
"backend": {
"type": "{{.BackendType}}",
"config": {{.BackendConfig}}
},
"cache": {
"type": "blobcache",
"config": {
"work_dir": "{{.BlobCacheDir}}"
}
}
},
"mode": "{{.Mode}}",
"iostats_files": false,
"fs_prefetch": {
"enable": {{.EnablePrefetch}},
"threads_count": 10,
"merging_size": 131072
},
"digest_validate": {{.DigestValidate}},
"enable_xattr": true,
"amplify_io": 1048576
}
`
func makeConfig(conf NydusdConfig) error {
tpl := template.Must(template.New("").Parse(configTpl))
var ret bytes.Buffer
if err := tpl.Execute(&ret, conf); err != nil {
return errors.New("prepare config template for Nydusd")
}
if err := os.WriteFile(conf.ConfigPath, ret.Bytes(), 0600); err != nil {
return errors.New("write config file for Nydusd")
}
return nil
}
// Wait until Nydusd ready by checking daemon state RUNNING
func checkReady(ctx context.Context, sock string) <-chan bool {
ready := make(chan bool)
transport := &http.Transport{
MaxIdleConns: 10,
IdleConnTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
dialer := &net.Dialer{
Timeout: 5 * time.Second,
KeepAlive: 5 * time.Second,
}
return dialer.DialContext(ctx, "unix", sock)
},
}
client := &http.Client{
Timeout: 30 * time.Second,
Transport: transport,
}
go func() {
for {
select {
case <-ctx.Done():
return
default:
}
resp, err := client.Get(fmt.Sprintf("http://unix%s", "/api/v1/daemon"))
if err != nil {
continue
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
continue
}
var info daemonInfo
if err = json.Unmarshal(body, &info); err != nil {
continue
}
if info.State == "RUNNING" {
ready <- true
break
}
}
}()
return ready
}
func NewNydusd(conf NydusdConfig) (*Nydusd, error) {
if err := makeConfig(conf); err != nil {
return nil, errors.New("create config file for Nydusd")
}
return &Nydusd{
NydusdConfig: conf,
}, nil
}
func (nydusd *Nydusd) Mount() error {
// Ignore the error since the nydusd may not ever start
_ = nydusd.Umount()
args := []string{
"--config",
nydusd.ConfigPath,
"--mountpoint",
nydusd.MountPath,
"--bootstrap",
nydusd.BootstrapPath,
"--apisock",
nydusd.APISockPath,
"--log-level",
"error",
}
cmd := exec.Command(nydusd.NydusdPath, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
runErr := make(chan error)
go func() {
runErr <- cmd.Run()
}()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ready := checkReady(ctx, nydusd.APISockPath)
select {
case err := <-runErr:
if err != nil {
return errors.Wrap(err, "run Nydusd binary")
}
case <-ready:
return nil
case <-time.After(10 * time.Second):
return errors.New("timeout to wait Nydusd ready")
}
return nil
}
func (nydusd *Nydusd) Umount() error {
if _, err := os.Stat(nydusd.MountPath); err == nil {
cmd := exec.Command("umount", nydusd.MountPath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return err
}
}
return nil
}
================================================
FILE: tools/optimizer-server/Cargo.toml
================================================
[package]
name = "optimizer-server"
version = "0.1.0"
edition = "2021"
description = "Optimizer server to generate accessed files in target mount namespace"
authors = ["The Nydus Developers"]
license = "Apache-2.0 OR BSD-3-Clause"
[dependencies]
clap = "4.1.8"
lazy_static = "1.4.0"
libc = "0.2.140"
nix = "0.26.2"
serde = { version="1.0.198", features = ["derive"] }
serde_json = "1.0.116"
signal-hook = "0.3.15"
================================================
FILE: tools/optimizer-server/Makefile
================================================
OS ?= linux
ARCH ?= amd64
arch_amd64 := x86_64
arch_arm64 := aarch64
arch_ppc64le := powerpc64le
arch_riscv64 := riscv64gc
linker_amd64 := x86_64
linker_arm64 := aarch64
linker_ppc64le := powerpc64le
ifneq ($(arch_$(ARCH)),)
RUST_ARCH := $(arch_$(ARCH))
else
RUST_ARCH := $(ARCH)
endif
ifneq ($(linker_$(ARCH)),)
RUST_LINKER := $(linker_$(ARCH))
else
RUST_LINKER := $(ARCH)
endif
RUST_TARGET := $(RUST_ARCH)-unknown-$(OS)-gnu
RUST_LINKER := $(RUST_LINKER)-$(OS)-gnu-gcc
RUST_TYPE := debug
all: build
.PHONY: .release_version .format build release
.release_version:
$(eval CARGO_BUILD_FLAGS += --release)
$(eval RUST_TYPE := release)
$(eval RUST_FLAGS += -C target-feature=+crt-static -C strip=symbols)
.format:
cargo fmt -- --check
build: .format
rustup target add $(RUST_TARGET)
RUSTFLAGS="-C linker=$(RUST_LINKER) $(RUST_FLAGS)" cargo build $(CARGO_BUILD_FLAGS) --target $(RUST_TARGET)
cargo clippy $(CARGO_BUILD_FLAGS) -- -Dwarnings
install -D -m 755 target/$(RUST_TARGET)/$(RUST_TYPE)/optimizer-server bin/optimizer-server
release: .format .release_version build
static-release: .format
cargo clippy $(CARGO_BUILD_FLAGS) -- -Dwarnings
RUSTFLAGS="-C linker=$(RUST_LINKER) -C target-feature=+crt-static -C target-feature=+crt-static -C strip=symbols" cargo build --release --target $(RUST_TARGET)
install -D -m 755 target/$(RUST_TARGET)/release/optimizer-server bin/optimizer-server
clean:
cargo clean
rm -rf bin/*
================================================
FILE: tools/optimizer-server/src/main.rs
================================================
/*
* Copyright (c) 2023. Nydus Developers. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
use std::{
env, ffi, fs, io,
io::Write,
mem,
os::unix::{io::AsRawFd, net::UnixStream},
path::{Path, PathBuf},
slice,
time::Instant,
};
use nix::{
poll::{poll, PollFd, PollFlags},
sched::{setns, CloneFlags},
sys::wait::{waitpid, WaitStatus},
unistd::{fork, getpgid, ForkResult},
};
use serde::Serialize;
use lazy_static::lazy_static;
use signal_hook::{consts::SIGTERM, low_level::pipe};
#[derive(Debug, Clone, Copy)]
#[repr(C)]
struct FanotifyEvent {
event_len: u32,
vers: u8,
reserved: u8,
metadata_len: u16,
mask: u64,
fd: i32,
pid: i32,
}
#[derive(Serialize, Debug)]
struct EventInfo {
path: String,
size: u64,
elapsed: u128,
}
impl PartialEq for EventInfo {
fn eq(&self, target: &EventInfo) -> bool {
self.path == target.path
}
}
lazy_static! {
static ref FAN_EVENT_METADATA_LEN: usize = mem::size_of::();
static ref BEGIN_TIME: Instant = Instant::now();
}
const DEFAULT_TARGET: &str = "/";
const FAN_CLOEXEC: u32 = 0x0000_0001;
const FAN_NONBLOCK: u32 = 0x0000_0002;
const FAN_CLASS_CONTENT: u32 = 0x0000_0004;
const O_RDONLY: u32 = 0;
const O_LARGEFILE: u32 = 0;
const FAN_MARK_ADD: u32 = 0x0000_0001;
const FAN_MARK_MOUNT: u32 = 0x0000_0010;
const FAN_ACCESS: u64 = 0x0000_0001;
const FAN_OPEN: u64 = 0x0000_0020;
const FAN_OPEN_EXEC: u64 = 0x00001000;
const AT_FDCWD: i32 = -100;
#[allow(dead_code)]
#[derive(Debug)]
enum SetnsError {
IO(io::Error),
Nix(nix::Error),
}
#[allow(dead_code)]
#[derive(Debug)]
enum SendError {
IO(io::Error),
Serde(serde_json::Error),
}
fn get_pid() -> Option {
env::var("_MNTNS_PID").ok()
}
fn get_target() -> String {
env::var("_TARGET").map_or(DEFAULT_TARGET.to_string(), |str| str)
}
fn get_fd_path(fd: i32) -> io::Result {
let fd_path = format!("/proc/self/fd/{fd}");
fs::read_link(fd_path)
}
fn set_ns(ns_path: String, flags: CloneFlags) -> Result<(), SetnsError> {
let file = fs::File::open(Path::new(ns_path.as_str())).map_err(SetnsError::IO)?;
setns(file.as_raw_fd(), flags).map_err(SetnsError::Nix)
}
fn init_fanotify() -> Result {
unsafe {
match libc::fanotify_init(
FAN_CLOEXEC | FAN_CLASS_CONTENT | FAN_NONBLOCK,
O_RDONLY | O_LARGEFILE,
) {
-1 => Err(io::Error::last_os_error()),
fd => Ok(fd),
}
}
}
fn mark_fanotify(fd: i32, path: &str) -> Result<(), io::Error> {
let path = ffi::CString::new(path)?;
unsafe {
match libc::fanotify_mark(
fd,
FAN_MARK_ADD | FAN_MARK_MOUNT,
FAN_OPEN | FAN_ACCESS | FAN_OPEN_EXEC,
AT_FDCWD,
path.as_ptr(),
) {
0 => Ok(()),
_ => Err(io::Error::last_os_error()),
}
}
}
fn read_fanotify(fanotify_fd: i32) -> Vec {
let mut vec = Vec::new();
unsafe {
let buffer = libc::malloc(*FAN_EVENT_METADATA_LEN * 1024);
let sizeof = libc::read(fanotify_fd, buffer, *FAN_EVENT_METADATA_LEN * 1024);
let src = slice::from_raw_parts(
buffer as *mut FanotifyEvent,
sizeof as usize / *FAN_EVENT_METADATA_LEN,
);
vec.extend_from_slice(src);
libc::free(buffer);
}
vec
}
fn close_fd(fd: i32) {
unsafe {
libc::close(fd);
}
}
fn generate_event_info(path: &Path) -> Result {
fs::metadata(path).map(|metadata| EventInfo {
path: path.to_string_lossy().to_string(),
size: metadata.len(),
elapsed: BEGIN_TIME.elapsed().as_micros(),
})
}
fn send_event(event: &EventInfo) -> Result<(), SendError> {
let mut writer = io::stdout();
let event_string = serde_json::to_string(event).map_err(SendError::Serde)?;
writer
.write_all(format!("{event_string}\n").as_bytes())
.map_err(SendError::IO)?;
writer.flush().map_err(SendError::IO)
}
fn handle_event(event: &FanotifyEvent, event_duplicate: &mut Vec) -> Result<(), SendError> {
let path = get_fd_path(event.fd).map_err(SendError::IO)?;
let info = generate_event_info(&path).map_err(SendError::IO)?;
if !event_duplicate.contains(&info.path) {
send_event(&info)?;
event_duplicate.push(info.path);
}
Ok(())
}
fn handle_fanotify_event(fd: i32) {
let mut event_duplicate = Vec::new();
let (reader, writer) = match UnixStream::pair() {
Ok((reader, writer)) => (reader, writer),
Err(e) => {
eprintln!("failed to create a pair of sockets: {e:?}");
return;
}
};
if let Err(e) = pipe::register(SIGTERM, writer) {
eprintln!("failed to set SIGTERM signal handler {e:?}");
return;
}
let mut fds = [
PollFd::new(fd.as_raw_fd(), PollFlags::POLLIN),
PollFd::new(reader.as_raw_fd(), PollFlags::POLLIN),
];
loop {
match poll(&mut fds, -1) {
Ok(polled_num) => {
if polled_num <= 0 {
eprintln!("polled_num <= 0!");
break;
}
if let Some(flag) = fds[0].revents() {
if flag.contains(PollFlags::POLLIN) {
let events = read_fanotify(fd);
for event in events {
if let Err(e) = handle_event(&event, &mut event_duplicate) {
eprintln!("failed to handle event {event:?} {e:?}")
};
// No matter the target path is valid or not, we should close the fd
close_fd(event.fd);
}
}
}
if let Some(flag) = fds[1].revents() {
if flag.contains(PollFlags::POLLIN) {
println!("received SIGTERM signal");
break;
}
}
}
Err(e) => {
if e == nix::Error::EINTR {
continue;
}
eprintln!("Poll error {e:?}");
break;
}
}
}
}
fn start_fanotify() -> Result<(), io::Error> {
let fd = init_fanotify()?;
mark_fanotify(fd, get_target().as_str())?;
handle_fanotify_event(fd);
Ok(())
}
fn join_namespace(pid: String) -> Result<(), SetnsError> {
set_ns(format!("/proc/{pid}/ns/pid"), CloneFlags::CLONE_NEWPID)?;
set_ns(format!("/proc/{pid}/ns/mnt"), CloneFlags::CLONE_NEWNS)?;
Ok(())
}
fn main() {
if let Some(pid) = get_pid() {
if let Err(e) = join_namespace(pid) {
eprintln!("join namespace failed {e:?}");
return;
}
}
match unsafe { fork() } {
Ok(ForkResult::Child) => {
if let Err(e) = start_fanotify() {
eprintln!("failed to start fanotify server {e:?}");
}
}
Ok(ForkResult::Parent { child }) => {
if let Err(e) = getpgid(Some(child)).map(|pgid| {
eprintln!("forked optimizer server subprocess, pid: {child}, pgid: {pgid}");
}) {
eprintln!("failed to get pgid of {child} {e:?}");
}
match waitpid(child, None) {
Ok(WaitStatus::Signaled(pid, signal, _)) => {
eprintln!("child process {pid} was killed by signal {signal}");
}
Ok(WaitStatus::Stopped(pid, signal)) => {
eprintln!("child process {pid} was stopped by signal {signal}");
}
Err(e) => {
eprintln!("failed to wait for child process: {e}");
}
_ => {}
}
}
Err(e) => {
eprintln!("fork failed: unable to create child process: {e:?}");
}
}
}
================================================
FILE: version/version.go
================================================
/*
* Copyright (c) 2020. Ant Group. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package version
import "runtime"
var (
// Version holds the complete version number. Filled in at linking time.
Version = "unknown"
// Revision is filled with the VCS (e.g. git) revision being used to build
// the program at linking time.
Revision = "unknown"
// GoVersion is Go tree's version.
GoVersion = runtime.Version()
// BuildTimestamp is timestamp of building.
BuildTimestamp = "unknown"
)