Skip to content

Commit

Permalink
refactor: update test scripts and improve volume metadata handling
Browse files Browse the repository at this point in the history
- on linux, we now try to backfill label and uuid by traversing /dev/disks-by-* symlinks.
  • Loading branch information
mceachen committed Nov 22, 2024
1 parent 76f6d6f commit f3a1511
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 12 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"watch": "tsc -p tsconfig.test.json --watch",
"docs": "typedoc --out docs src/index.ts",
"jest:coverage": "jest --coverage",
"jest:watch": "jest --watch",
"jest:watch": "npm t -- --watch",
"jest:clear": "jest --clearCache",
"// tests": "is called by .github/workflows/test.yml",
"tests": "run-s test test:memory",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// src/__tests__/mtab.test.ts
// src/__tests__/linux_mtab.test.ts
import { formatMtab, parseMtab } from "../linux/mtab.js";

describe("mtab", () => {
Expand Down
6 changes: 5 additions & 1 deletion src/common/metadata_worker.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ class MetadataWorkerBase : public Napi::AsyncWorker {
? env.Null()
: Napi::String::New(env, metadata.uri));
result.Set("status", Napi::String::New(env, metadata.status));
result.Set("remote", Napi::Boolean::New(env, metadata.remote));

if (metadata.remote) {
result.Set("remote", Napi::Boolean::New(env, metadata.remote));
}
result.Set("remoteHost", metadata.remoteHost.empty()
? env.Null()
: Napi::String::New(env, metadata.remoteHost));
Expand All @@ -59,4 +62,5 @@ class MetadataWorkerBase : public Napi::AsyncWorker {

void OnOK() override { deferred_.Resolve(CreateResultObject()); }
}; // class MetadataWorkerBase

} // namespace FSMeta
21 changes: 20 additions & 1 deletion src/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { filterMountPoints, filterTypedMountPoints } from "./config_filters.js";
import { defer } from "./defer.js";
import { WrappedError } from "./error.js";
import { isRemoteFsType } from "./fs_type.js";
import { getLabelFromDevDisk, getUuidFromDevDisk } from "./linux/dev_disk.js";
import { getLinuxMountPoints } from "./linux/mount_points.js";
import { isRemoteFSInfo, parseFsSpec, parseMtab } from "./linux/mtab.js";
import { normalizeMountPoint } from "./mount_point.js";
Expand Down Expand Up @@ -132,11 +133,13 @@ export class ExportsImpl {
const entry = entries.find((e) => e.fs_file === mountPoint);

if (entry != null) {
device = entry.fs_spec;
mtabInfo.fileSystem = entry.fs_vfstype;
if (isRemoteFSInfo(entry)) {
mtabInfo.remote = true;
mtabInfo.remoteHost = entry.remoteHost;
mtabInfo.remoteShare = entry.remoteShare;
console.log("mtab found remote", { mountPoint, entry });
}
}
} catch (error) {
Expand Down Expand Up @@ -174,11 +177,27 @@ export class ExportsImpl {
mountPoint,
remote,
}) as unknown as VolumeMetadata;

// Backfill if blkid or gio failed us:
if (isLinux && isNotBlank(device)) {
if (isBlank(result.uuid)) {
// Sometimes blkid doesn't have the UUID in cache. Try to get it from
// /dev/disk/by-uuid:
result.uuid = await getUuidFromDevDisk(device);
}
if (isBlank(result.label)) {
result.label = await getLabelFromDevDisk(device);
}
}

// Fix microsoft UUID format:
result.uuid = extractUUID(result.uuid) ?? result.uuid;

// Normalize remote share path
if (isNotBlank(result.remoteShare)) {
result.remoteShare = normalizeMountPoint(result.remoteShare);
}

return result;
return compactValues(result) as unknown as VolumeMetadata;
}
}
55 changes: 55 additions & 0 deletions src/linux/dev_disk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Dirent } from "node:fs";
import { readdir, readlink } from "node:fs/promises";
import { join, resolve } from "node:path";

/**
* Gets the UUID from symlinks for a given device path asynchronously
* @param devicePath The device path to look up
* @returns Promise that resolves to the UUID if found, empty string otherwise
*/
export function getUuidFromDevDisk(devicePath: string) {
return getBasenameLinkedTo("/dev/disk/by-uuid", resolve(devicePath)).catch(
() => undefined,
);
}

/**
* Gets the label from symlinks for a given device path asynchronously
* @param devicePath The device path to look up
* @returns Promise that resolves to the label if found, empty string otherwise
*/
export function getLabelFromDevDisk(devicePath: string) {
return getBasenameLinkedTo("/dev/disk/by-label", resolve(devicePath)).catch(
() => undefined,
);
}

async function getBasenameLinkedTo(
linkDir: string,
linkPath: string,
): Promise<string | undefined> {
for await (const ea of readLinks(linkDir)) {
if (ea.linkTarget === linkPath) {
return ea.dirent.name;
}
}
return;
}

// only exposed for test mocking
export async function* readLinks(
directory: string,
): AsyncGenerator<{ dirent: Dirent; linkTarget: string }, void, unknown> {
for (const dirent of await readdir(directory, { withFileTypes: true })) {
if (dirent.isSymbolicLink()) {
try {
const linkTarget = resolve(
await readlink(join(directory, dirent.name)),
);
yield { dirent, linkTarget };
} catch {
// Ignore errors
}
}
}
}
11 changes: 7 additions & 4 deletions src/linux/mtab.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// src/linux/mtab.ts

import { isRemoteFsType } from "../fs_type.js";
import { normalizeLinuxMountPoint } from "../mount_point.js";
import { toInt } from "../number.js";
import { isObject } from "../object.js";
Expand Down Expand Up @@ -148,7 +149,7 @@ export function parseMtab(
continue; // Skip malformed lines
}

const mountEntry: MountEntry = {
const entry: MountEntry = {
fs_spec: fields[0]!,
fs_file: normalizeLinuxMountPoint(fields[1] ?? ""),
fs_vfstype: fields[2]!,
Expand All @@ -157,11 +158,13 @@ export function parseMtab(
fs_passno: toInt(fields[5]),
};

const remoteInfo = parseFsSpec(mountEntry.fs_spec);
const remoteInfo = isRemoteFsType(entry.fs_vfstype)
? parseFsSpec(entry.fs_spec)
: undefined;
if (remoteInfo) {
entries.push({ ...mountEntry, ...remoteInfo });
entries.push({ ...entry, ...remoteInfo });
} else {
entries.push(mountEntry);
entries.push(entry);
}
}

Expand Down
4 changes: 1 addition & 3 deletions src/linux/volume_metadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class LinuxMetadataWorker : public MetadataWorkerBase {
}

uint64_t blockSize = vfs.f_frsize ? vfs.f_frsize : vfs.f_bsize;
metadata.remote = false;
metadata.size = static_cast<double>(blockSize) * vfs.f_blocks;
metadata.available = static_cast<double>(blockSize) * vfs.f_bavail;
metadata.used =
Expand Down Expand Up @@ -60,14 +61,11 @@ class LinuxMetadataWorker : public MetadataWorkerBase {
metadata.status = std::string("Blkid warning: ") + e.what();
}
}

} catch (const std::exception &e) {
SetError(e.what());
}
}

void OnOK() override { deferred_.Resolve(CreateResultObject()); }

private:
VolumeMetadataOptions options_;
};
Expand Down
2 changes: 1 addition & 1 deletion src/volume_metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface VolumeMetadata {
/**
* The name of the partition
*/
label?: string;
label?: string | undefined;
/**
* Total size in bytes
*/
Expand Down

0 comments on commit f3a1511

Please sign in to comment.