LCOV - code coverage report
Current view: top level - pkg/lib/cockpit - fsinfo.ts Coverage Total Hit
Test: cockpit Lines: 77.6 % 76 59
Test Date: 2026-06-25 11:17:56

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2024 Red Hat, Inc.
       3              :  * SPDX-License-Identifier: GPL-3.0-or-later
       4              :  */
       5              : 
       6              : 'use strict';
       7              : 
       8              : import type { JsonObject, JsonValue } from './_internal/common';
       9              : import { Channel } from './channel';
      10              : import { EventEmitter } from './event';
      11              : 
      12          146 : function is_json_dict(value: JsonValue): value is JsonObject {
      13          146 :     return value?.constructor === Object;
      14          146 : }
      15              : 
      16              : /* RFC 7396 — JSON Merge Patch — functional */
      17          146 : function json_merge(current: JsonValue, patch: JsonValue): JsonValue {
      18          146 :     if (is_json_dict(patch)) {
      19           44 :         const updated = is_json_dict(current) ? { ...current } : { };
      20              : 
      21          146 :         for (const [key, value] of Object.entries(patch)) {
      22           44 :             if (value === null) {
      23           44 :                 delete updated[key];
      24           44 :             } else {
      25          146 :                 updated[key] = json_merge(updated[key], value);
      26          146 :             }
      27          146 :         }
      28              : 
      29          146 :         return updated;
      30          131 :     } else {
      31          131 :         return patch;
      32          131 :     }
      33          146 : }
      34              : 
      35              : export interface FileInfo {
      36              :     type?: string;
      37              :     tag?: string;
      38              :     mode?: number;
      39              :     size?: number;
      40              :     uid?: number;
      41              :     user?: string | number;
      42              :     gid?: number;
      43              :     group?: string | number;
      44              :     mtime?: number;
      45              :     target?: string;
      46              :     ['r-ok']?: boolean,
      47              :     ['w-ok']?: boolean,
      48              :     ['x-ok']?: boolean,
      49              :     entries?: Record<string, FileInfo>;
      50              :     targets?: Record<string, FileInfo>;
      51              : }
      52              : 
      53              : export interface FsInfoError {
      54              :     problem: string;
      55              :     message: string;
      56              :     errno: string;
      57              : }
      58              : 
      59              : export interface FsInfoState {
      60              :     info?: FileInfo;
      61              :     error?: FsInfoError;
      62              :     loading?: true;
      63              : }
      64              : 
      65              : export interface FsInfoEvents {
      66              :     change(state: FsInfoState): void;
      67              :     close(message: JsonObject): void;
      68              : }
      69              : 
      70          406 : export class FsInfoClient extends EventEmitter<FsInfoEvents> {
      71          147 :     state: FsInfoState = { loading: true };
      72              : 
      73          147 :     private partial_state: JsonValue = null;
      74              :     private channel: Channel<string>;
      75              : 
      76          147 :     constructor(path: string, attrs: (keyof FileInfo)[], options?: JsonObject) {
      77          147 :         super();
      78              : 
      79          147 :         this.channel = new Channel({
      80          147 :             payload: "fsinfo",
      81          147 :             path,
      82          147 :             attrs,
      83          147 :             watch: true,
      84          147 :             ...options
      85          147 :         });
      86              : 
      87          146 :         this.channel.on('data', payload => {
      88          146 :             this.partial_state = json_merge(this.partial_state, JSON.parse(payload));
      89              : 
      90          146 :             if (is_json_dict(this.partial_state) && !this.partial_state.partial) {
      91          146 :                 this.state = { ...this.partial_state };
      92          146 :                 this.emit('change', this.state);
      93          146 :             }
      94          146 :         });
      95              : 
      96           40 :         this.channel.on('close', message => {
      97           40 :             this.emit('close', message);
      98           40 :         });
      99          147 :     }
     100              : 
     101            8 :     close() {
     102            8 :         this.channel.close();
     103            8 :     }
     104              : 
     105            0 :     static entry(info: FileInfo, name: string): FileInfo | null {
     106            0 :         return info.entries?.[name] ?? null;
     107            0 :     }
     108              : 
     109            0 :     static target(info: FileInfo, name: string): FileInfo | null {
     110            0 :         const entries = info.entries ?? {};
     111            0 :         const targets = info.targets ?? {};
     112              : 
     113            0 :         let entry = entries[name] ?? null;
     114            0 :         for (let i = 0; i < 40; i++) {
     115            0 :             const target = entry?.target;
     116            0 :             if (!target)
     117            0 :                 return entry;
     118            0 :             if (target === '.')
     119            0 :                 return info;
     120            0 :             entry = entries[target] ?? targets[target] ?? null;
     121            0 :         }
     122            0 :         return null; // ELOOP
     123            0 :     }
     124          406 : }
     125              : 
     126           20 : export function fsinfo(path: string, attrs: (keyof FileInfo)[], options?: JsonObject): Promise<FileInfo> {
     127           20 :     return new Promise((resolve, reject) => {
     128           20 :         const client = new FsInfoClient(path, attrs, { ...options, watch: false });
     129           20 :         client.on('close', (message) => {
     130            4 :             if (message.problem) {
     131            4 :                 reject(message);
     132            4 :             } else if (client.state.info) {
     133           20 :                 resolve(client.state.info);
     134            5 :             } else {
     135            5 :                 reject(client.state.error);
     136            5 :             }
     137           20 :         });
     138           20 :     });
     139           20 : }
        

Generated by: LCOV version 2.0-1