mimedb

mimedb reads the freedesktop shared-mime-info database from the same files the desktop reads, and decides a file's MIME type by GLib's rules. The answer agrees with gio info and the file manager. It is a plain Rust library with no Bus verbs, and its only dependency is libc.

Two components use it:

  • Mix, through the mime_type() builtin (behind the mix crate's mime feature; see Mix I/O).
  • thumbs, which types a file before choosing a thumbnailer for it.

The source is libs/mimedb.

The database

Each mime directory under $XDG_DATA_HOME (default ~/.local/share) and $XDG_DATA_DIRS (default /usr/local/share:/usr/share) can hold globs2 (or the older globs), magic, aliases and subclasses. Directories are read highest precedence first, and relative entries are ignored, as the XDG spec requires. A higher directory's __NOGLOBS__ and __NOMAGIC__ entries clear a lower one's rules for that type. Missing directories and files are skipped. A file over 16 MiB (MAX_FILE) is skipped, and so is anything that is not a regular file once opened: a FIFO in a database directory is never waited on.

How a type is decided

  1. A non-regular file is its inode/* type (inode/directory, inode/fifo and so on), and an empty file is application/x-zerosize.
  2. If exactly one glob matches the name, that is the answer.
  3. Otherwise the leading bytes are sniffed: the highest-priority magic match, or text/plain when the content looks like text.
  4. With no glob match, the sniffed type is the answer, or application/octet-stream (uncertain) if nothing was sniffed. With several glob matches, a sniffed type of priority 80 or more wins; failing that, the first glob type that is a subclass of the sniffed type wins; failing that, the first glob type wins, marked uncertain.

Globs are matched in GLib's order: a literal name, then the longest *.suffix, then full patterns. At most SNIFF_MAX (16384) bytes are read, GLib's limit, and fewer when the magic rules reach less far.

One known limit: full patterns use the crate's own fnmatch, not glibc's. POSIX named classes use Unicode's predicates instead of a locale's tables, and collating symbols cannot end a range. The stock database uses neither.

API

  • Database::load() loads the desktop's database (xdg_mime_dirs()); Database::load_from(&dirs) loads given mime directories, highest precedence first.
  • for_path(path, content) -> PathGuess types a file, following symlinks. With content false the file is never opened. A missing path is typed by its name (exists: false); an unreadable one by its name, uncertain.
  • guess(name, data) -> Guess decides from an optional name and optional leading bytes, for a caller that already has the file open.
  • Guess { mime, by, uncertain }, where by is a By: Stat, Glob, Magic, Content (an empty file or the text check) or Fallback. By::as_str() gives the lower-case name.
  • globs(name), sniff(data), unalias(mime), is_subclass(mime, base) and parents(mime). Parents come nearest first and include the spec's implicit ones: text/plain for text/* types and application/octet-stream for everything but inode/*. Cycles in subclasses are visited once.
  • is_stale() reports whether any directory or database file has changed (timestamp or size) since loading, so a long-lived caller can reload. sniff_len() is how many bytes to read before calling guess.
  • text_fallback(data) and looks_like_text(data) are the two text checks.

Example

thumbs reads the start of a file it already has open and asks for its type:

let db = mimedb::Database::load();
let mut data = Vec::new();
source.take(db.sniff_len() as u64).read_to_end(&mut data)?;
let mime = db.guess(Some("photo.jpg"), Some(&data)).mime;

From Mix, the same answer comes from mime_type(), which keeps one database per thread and reloads it when is_stale() says so or the XDG directories change:

$r = mime_type("/etc/hosts")
print($r.mime)

Source and tests

  • src/lib.rs: the database, loading and layering, the decision rules, and the subclass graph.
  • src/glob.rs: parsing globs2/globs and the desktop's match order, including fnmatch.
  • src/magic.rs: parsing and matching the binary magic file.

Unit tests in each module build private mime directories in a temporary directory, so they never depend on the host's /usr/share/mime. They cover layer clearing, staleness after a same-size rewrite, oversized files, priority-zero magic, cycles and very wide parent graphs. The mix crate's tests/mime_type.rs tests the builtin against a private database the same way.

This page as Markdown · This page describes the current main branch of MixOS.