Skip to content

Commit

Permalink
bridge: add a new fsinfo channel
Browse files Browse the repository at this point in the history
This channel should support all of the existing usecases of both fslist1
and fswatch1, while presenting a significantly easier-to-consume API
contained within a single channel.

The core concept is that the state of a directory and all of its files
can be represented with the value of a JSON document containing
attributes about those files.  When something changes, the document is
modified.

This works by sending RFC 7396 JSON Merge Patches as messages on the
channel. This is the same format that we already use for manifest
overrides.  We add a somewhat evil extra helper alongside our current
patch function, which does the same thing, but modifies-in-place and
also modifies the provided patch, removing redundant information.  This
is done using JsonDocument/JsonDict, which makes it a bit harder to use
from a typing standpoint, but prevents us from having to make copies of
large sets of data when only one item has changed.

There is no client API provided yet because this new channel type is
only supported in the Python bridge, with no intent to add it to the C
bridge.  We're developing a client-side API as part of cockpit-navigator
and will move it to `cockpit.js` when it's ready.

Add many unit tests — coverage is nearly complete.
  • Loading branch information
allisonkarlitskaya committed Jan 24, 2024
1 parent a1a507c commit 0bb7438
Show file tree
Hide file tree
Showing 5 changed files with 668 additions and 5 deletions.
110 changes: 110 additions & 0 deletions doc/protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,116 @@ or pipe is done.
Additionally, a "options" control message may be sent in this channel
to change the "max-size" options.

Payload: fsinfo
----------------

This channel is used to report information about a given path, and the file or
a directory which may reside there, including the possibility of updates. In
the case of directories, information can also be reported about the (directly)
contained entries.

Additional "open" command options should be specified with a channel of
this payload type:

* `path` (mandatory): a string, an absolute path name. If this ends with `/`
then the path must refer to a directory.
* `attrs` (mandatory): a list of strings, the requested attributes to be
reported for the file at `path`, and (optionally) its sub-items. See below
for the possibilities.
* `fnmatch`: (optional, default ''): if non-empty, this is a [`fnmatch`-style
pattern](https://docs.python.org/3/library/fnmatch.html). In case `path`
refers to a readable directory, an `entries` attribute will be reported
(described below). Use `*` to report all files. Can also be used to
implement search (`searc*`) or hide temporary files (`[!.]*`).
* `watch` (optional, default: false): if the channel should watch for changes
* `follow` (default: true): if the trailing component of `path` is a symbolic
link, whether we should follow it. `follow: false` mode is not supported
for `watch` (but is also mostly unnecessary).

A channel is created with an associated absolute pathname (`path`). A path is
a string that you pass to the `open()` syscall. The object returned by that
call is described by the (JSON Document) value of the channel.

Strictly speaking, the path is specified to the channel as a unicode string,
but paths on the filesystem are binary strings. This mapping is handled using
the usual Python convention: filenames are stored in utf8 and encoded and
decoded with `errors='surrogateescape'`, which allows for handling of filenames
which are not utf8.

Conceptually, the channel represents the value of a JSON object describing what
is found at the path, or the reason for an error. Each message on the channel
is a JSON Merge Patch ([RFC
7396](https://datatracker.ietf.org/doc/html/rfc7396)) which updates or replaces
this object.

The top-level keys in the object are:

- `info`: the info about the reported path (described below)
- `error`: an object with information about why `info` can't be reported.
This is currently basically an errno formatted in several possible ways:
- `problem`: a Cockpit "problem" code type of string like `not-found` or
`not-directory`
- `message`: the result of strerror(). Currently English, hopefully
translated at some point
- `errno`: the symbolic `errno` name like `ENOENT` or `ENOTDIR`
- `partial`: `true` if the data is in a "partial" state. This is used when
sending updates in multiple pieces.

The value of the `info` attribute is a JSON object describing the attributes of
the file. Any attribute which cannot be read (eg: due to permissions problems)
is simply not reported.

* `type`: a string: `reg`, `dir`, `lnk`, `chr`, `blk`, `fifo`, or `sock`
* `mode`: an integer representing the "file permission" bits
(`S_IMODE(st_mode)`). As per JSON, this is transmitted as a decimal value,
but it is meant to be interpreted in octal, in the usual way.
* `uid`: an integer, the uid of the file owner (`st_uid`)
* `owner`: a string, or an integer if the lookup failed
* `gid`: an integer, the gid of the file group (`st_gid`)
* `group`: a string, or an integer if the lookup failed
* `size`: an integer, the (apparent) size of the file (`st_size`)
* `modified`: a float, the mtime of the file (`st_mtim`)
* `tag`: a value type: the current 'transaction tag' of the file, with the
same meaning as elsewhere in this document. Ideally: this changes if the
content of the file changes. This is the same as the tag used in `fsread1`
and `fsreplace1`.
* `entries`: for directories: the contents of the directory (see below)
* `target`: for symbolic links: the target of the link (a string)

The `entries` attribute is reported iff `fnmatch` is non-empty, and `path`
refers to a readable directory. It is an object where each key is the name of
a file in the directory, and each value is an object describing that file,
using the same attributes as above. `entries` is not reported on entries (ie:
no recursive listing).

If the provided `path` is a symbolic link, it is followed, unless
`follow: false` is specified. If symbolic links are present in the directory,
they are always reported directly (ie: not followed). `follow: false` is not
supported in combination with `watch: true` but you probably don't need it:
watch mode doesn't only watch the final path component, but all components
leading up to it, including any symlink that have been followed. That means
that you can watch `/etc/localtime` and find out about if the link changed, or
if the contents of the file itself changed, or was deleted or replaced, or also
if `/usr`, `/etc`, or any other directories or intermediate links were changed,
deleted, or moved out of the way.

The life-cycle of the channel works like this:

- When opening the channel, `ready` is always immediately sent.

- One or more patches are sent to modify the initial value of the channel
(`null`). The "initial state" is reached when the value is a object
which does not have the `partial` attribute set on it.

- If `watch` mode is false then `done` is sent immediately, and the channel is
closed.

- Otherwise (`watch: true`), the channel remains open. When any changes are
detected, they are sent as updates to the value of the channel. This may
involve transitions between reporting `info` and reporting `problem`.

- The channel will stay open until the client closes it.

Payload: fswatch1
-----------------

Expand Down
3 changes: 2 additions & 1 deletion src/cockpit/channels/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.

from .dbus import DBusChannel
from .filesystem import FsListChannel, FsReadChannel, FsReplaceChannel, FsWatchChannel
from .filesystem import FsInfoChannel, FsListChannel, FsReadChannel, FsReplaceChannel, FsWatchChannel
from .http import HttpChannel
from .metrics import InternalMetricsChannel
from .packages import PackagesChannel
Expand All @@ -26,6 +26,7 @@
CHANNEL_TYPES = [
DBusChannel,
EchoChannel,
FsInfoChannel,
FsListChannel,
FsReadChannel,
FsReplaceChannel,
Expand Down
Loading

0 comments on commit 0bb7438

Please sign in to comment.