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

[![Release Version](https://img.shields.io/github/v/release/containerd/nydus-snapshotter?style=flat)](https://github.com/containerd/nydus-snapshotter/releases) [![LICENSE](https://img.shields.io/github/license/containerd/nydus-snapshotter.svg?style=flat)](https://github.com/containerd/nydus-snapshotter/blob/main/LICENSE) ![CI](https://github.com/containerd/nydus-snapshotter/actions/workflows/ci.yml/badge.svg?event=push) [![Go Report Card](https://goreportcard.com/badge/github.com/containerd/nydus-snapshotter?style=flat)](https://goreportcard.com/report/github.com/containerd/nydus-snapshotter) [![Twitter](https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Ftwitter.com%2Fdragonfly_oss)](https://twitter.com/dragonfly_oss) [![Nydus Stars](https://img.shields.io/github/stars/dragonflyoss/nydus?label=Nydus%20Stars&style=social)](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 ![fuse arch](./docs/diagram/nydus_fuse_arch.svg) ### Architecture Based on Fscache/Erofs ![fscache arch](./docs/diagram/nydus_fscache_erofs_arch.svg) ## 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 [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fcontainerd%2Fnydus-snapshotter.svg?type=large)](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 ![optimizer workload](./diagram/optmizer_workflow.svg) 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" )