pkg-license(1)
NAME
pkg-license - report what is installed under which license, and print the license text
SYNOPSIS
pkg license [options]
pkg license [options] name[/subpkg]
DESCRIPTION
Sooner or later somebody asks what your machine is licensed under. Maybe it's legal, maybe it's a customer, maybe it's you. There are really two questions there: what is every installed package licensed under, and what does that license actually say. The package manager is the only thing on the system that can answer both honestly, so it does.
Both answers are local. Every installed manifest carries a [license] table with two things in it. spdx is the license expression the recipe declared. files is the list of license files the build found, installed under /usr/share/pkg-licenses/<pkg>/. Nothing is fetched, and nothing is rescanned.
With no name, you get the whole installed set, one row per package, collapsed per --collapse. With a name, you get that one package in detail.
Declared and detected
The two are shown side by side, and never reconciled. declared is the package-level expression, carried verbatim from the recipe's license_hint, and it's the authoritative one. text is evidence, per file: every file the license scan matched, with the SPDX id it matched for that file alone.
They can disagree, and usually that's not a bug. A vendored source file under a compatible license that the package-level expression doesn't name is completely ordinary. This command's job is to put that in front of a human, not to rule on it.
What can't be identified?
Two states get called out instead of passed over. An unknown that looks like an answer is worse than an unknown that says so.
unidentified- The expression uses the SPDX
LicenseRef-(orDocumentRef-) escape hatch. That's part of the SPDX standard, but it names a license no SPDX id resolves. - no text
- [license] files is empty. The license scan matched nothing in the source tree, so no license text was installed, and the declared expression stands on the recipe's hint alone.
The two are independent. A package can be either, both or neither, and --unknown is the standing report of everything in either state.
Installed, or in the repository?
A name is looked up in the installed set first, and the repository chain second. The report tells you which one answered: (installed) or (repo). "What am I running" and "what would I be agreeing to" are both questions about one name. --all forces the second one.
OPTIONS
--textPrint the license text, each file under a
==>path<==header, in the order [license] files lists them. Needs a name, and the package has to be installed, because it reads the installed files.It exits non-zero when there's nothing to print, instead of succeeding silently. If you asked for the license text, an empty answer must never look like a permissive license. A file that can't be read gets a warning on stderr, and the others still print.
--files- List the license files as spdx path, one per line. That's the per-file evidence, without the block around it. Needs a name.
--unknown- Report only the packages that are
unidentifiedor ship no text, each with the reason, followed by a count of how many rows that is. Exits zero: it's a report, not a check. --summaryCount packages per license id instead of listing them, most common first, with totals underneath.
A count is followed by (N optional) when N of those packages only name the id on one branch of an
OR. That's a license you're offered, not one you're bound by. Without that split, a dual-licensed set reads as far more encumbered than it really is, and avoiding that misreading is the point of this report.--match=GLOBReport only packages whose expression names a matching license id or
WITHexception. Case-insensitive, because SPDX ids are, and nobody should have to reproduce their canonical spelling.*and?glob (GPL-*). Each row then says which term matched, and whether the package is actually bound by it.It's a filter, not a report, and it applies before everything else. So it narrows the listing, the summary and the policy check alike.
--mismatchReport packages that ship a license file whose detected id the declared expression doesn't name, with the file each one came from.
It's a review aid, not a list of defects. A vendored source file under a compatible license is ordinary. The grant suffix isn't a mismatch either: declaring
GPL-2.0-or-laterwhile shipping the text ofGPL-2.0-onlyis how a GPL package is normally packaged. Comparing those verbatim reported two thirds of a real installed set as disagreeing with itself.--checkCheck the reported set against
[license]in pkg.toml(5), and exit 1 if anything violates it, naming the refused term on each row.This is for what's already installed. A policy written on Tuesday has nothing to say about what arrived on Monday, until something asks. It never prompts and never changes anything.
With no
[license]table configured, it says so and exits 0. That's not a clean bill of health, and it doesn't pretend to be one.--all- Report the repository chain instead of the installed set. With a name, answer from the chain instead of preferring the installed copy.
--summary, --unknown, --mismatch and --check are separate reports: ask for one at a time. They, and --match, work over a set, so they take no name.
All global options apply; see pkg(1). --collapse accepts main (the default) and none. sets and roots explain an installed set from its selection roots, which only pkg-list(1) can do.
OUTPUT
The listing
acl GPL-2.0-or-later AND LGPL-2.1-or-later [/bin, /lib] acl/doc GPL-2.0-or-later AND LGPL-2.1-or-later fontconfig MIT [/lib] (no text) tzdata LicenseRef-public-domain [/data] (unidentified, no text)
The bracket lists the runtime subpackages the row collapsed, same as pkg-list(1). The flag in parentheses at the end is never a second license. No flag means the expression is an SPDX id and the text is on disk.
One package
acl 2.4.0-6 (installed) subpkgs: /bin, /doc, /lib, /man declared: GPL-2.0-or-later AND LGPL-2.1-or-later text: /usr/share/pkg-licenses/acl/doc/COPYING GPL-2.0-only /usr/share/pkg-licenses/acl/doc/COPYING.LGPL LGPL-2.1-only
You get one block per distinct license fact, not one per subpackage. [license] is the same table across a whole source build, so printing it for each subpackage would just repeat itself. Two blocks under one name therefore mean something real: a half-upgraded package, or subpackages that genuinely differ. --collapse=none asks for a block per subpackage anyway.
A summary
GPL-2.0-or-later 118 (11 optional) GPL-3.0-or-later 97 (2 optional) LGPL-2.1-or-later 91 (2 optional) X11 32 -- 500 packages, 45 distinct ids, 13 unidentified, 76 without text
A policy check
nmap LicenseRef-NPSL-0.95 [/bin, /data] (unidentified, and unknown = "deny") p7zip LGPL-2.1-or-later AND LicenseRef-unRAR [/bin] (denied: LicenseRef-unRAR) -- 2 of 500 packages violate [license]
EXAMPLES
What is this machine licensed under?
pkg license
What can't be identified?
pkg license --unknown
What is it mostly licensed under?
pkg license --summary
Is there any AGPL here, and am I bound by it?
pkg license --match 'AGPL-*'
Does anything installed violate my policy?
pkg license --check
Where does a shipped license file disagree with its package?
pkg license --mismatch
What one package says, and what it says in full
pkg license openssl pkg license openssl --text pkg license openssl --text | less
What would a package I haven't installed commit me to?
pkg license nmap --all
Every distinct license expression on the machine, by frequency
pkg --json license \ | jq -r '.packages[].declared' | sort | uniq -c | sort -rn
Anything mentioning the AGPL
pkg --json license \ | jq -r '.packages[] | select(.declared | test("AGPL")) | "(.name)/(.subpkg)"'
Everything that ships no license text
pkg --json license \ | jq -r '.packages[] | select(.text == []) | .name' | sort -u
Every license file on the system, with the id matched for it
pkg --json license \ | jq -r '.packages[].text[] | "(.spdx)\t(.path)"' | sort -u
EXIT STATUS
0- The question was answered. That includes an answer like "six packages can't be identified".
1--checkfound a violation,--texthad no license text to print, an argument was unusable, or the name is in neither the installed set nor the chain (and the error reports both misses).A
--checkviolation is a successful report, not a failed command. Under--jsonthe document still carries ok: true, and the verdict rides on this exit code alone. Same split as pkg-check-update(1).
FILES
/usr/share/pkg-licenses/<pkg>/- License text as the build installed it, at the paths [license] files records.
/var/lib/pkg/installed/- The manifests every answer is read from.
/etc/pkg.toml- The
[license]policy that--checkevaluates. See pkg.toml(5).
NOTES
Every judgement about an expression goes through the parser in pkg-format, never a test on the raw string. The difference between Apache-2.0 OR MIT and GPL-2.0-only AND MIT is the whole question. Denying Apache-2.0 leaves the first one installable under MIT, and makes no difference to the second. A denial that only looked at the list of ids would get both wrong, in opposite directions.
Not here yet: prompting before you accept a license, a cache of what you already accepted, and a gate that applies [license] at install time instead of after the fact. Those need a terminal and a record of decisions. This command only reports, and reporting needs neither.
SEE ALSO
pkg(1), pkg-info(1), pkg-list(1), pkg-query(1), pkg-check-update(1), pkg.toml(5), pkg-state(5), pkg-subpkgs(7)
docs/design/format.md, docs/design/sbom.md