Skip to content

Commit c0165bf

Browse files
committed
Generate real file stats
The various fstat handlers were all returning default attributes for files or directories (depending on path type). This updates those calls to actually return things like size and access time based on Permanent data. It also does this via a generic handler (since our SFTP service does not support things like semantic links, so we want all of the stats to just use the same logic for now). Issue #64 Implement stats properly
1 parent 60746f7 commit c0165bf

File tree

4 files changed

+75
-51
lines changed

4 files changed

+75
-51
lines changed

src/classes/PermanentFileSystem.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
getRecord,
88
} from '@permanentorg/sdk';
99
import {
10+
generateAttributesForFile,
11+
generateAttributesForFolder,
1012
generateDefaultAttributes,
1113
generateFileEntry,
1214
generateFileEntriesForRecords,
@@ -21,7 +23,10 @@ import type {
2123
File,
2224
Record,
2325
} from '@permanentorg/sdk';
24-
import type { FileEntry } from 'ssh2';
26+
import type {
27+
Attributes,
28+
FileEntry,
29+
} from 'ssh2';
2530

2631
const isRootPath = (requestedPath: string): boolean => (
2732
requestedPath === '/'
@@ -99,6 +104,27 @@ export class PermanentFileSystem {
99104
throw new Error('Item was not found');
100105
}
101106

107+
public async getItemAttributes(itemPath: string): Promise<Attributes> {
108+
if (isRootPath(itemPath)
109+
|| isArchiveCataloguePath(itemPath)
110+
|| isArchivePath(itemPath)) {
111+
return generateDefaultAttributes(fs.constants.S_IFDIR);
112+
}
113+
const fileType = await this.getItemType(itemPath);
114+
switch (fileType) {
115+
case fs.constants.S_IFREG: {
116+
const file = await this.loadFile(itemPath);
117+
return generateAttributesForFile(file);
118+
}
119+
case fs.constants.S_IFDIR: {
120+
const folder = await this.loadFolder(itemPath);
121+
return generateAttributesForFolder(folder);
122+
}
123+
default:
124+
throw new Error('The specified path is neither a file nor a directory.');
125+
}
126+
}
127+
102128
public async loadDirectory(requestedPath: string): Promise<FileEntry[]> {
103129
if (isRootPath(requestedPath)) {
104130
return PermanentFileSystem.loadRootFileEntries();

src/classes/SftpSessionHandler.ts

Lines changed: 34 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,7 @@ import fetch from 'node-fetch';
33
import { v4 as uuidv4 } from 'uuid';
44
import ssh2 from 'ssh2';
55
import { logger } from '../logger';
6-
import {
7-
generateFileEntry,
8-
generateDefaultAttributes,
9-
generateAttributesForFile,
10-
} from '../utils';
6+
import { generateFileEntry } from '../utils';
117
import { PermanentFileSystem } from './PermanentFileSystem';
128
import type {
139
Attributes,
@@ -156,20 +152,11 @@ export class SftpSessionHandler {
156152
*/
157153
public fstatHandler = (
158154
reqId: number,
159-
handle: Buffer,
155+
itemPath: Buffer,
160156
): void => {
161157
logger.verbose('SFTP read open file statistics request (SSH_FXP_FSTAT)');
162-
logger.debug('Request:', { reqId, handle });
163-
const file = this.openFiles.get(handle.toString());
164-
if (!file) {
165-
logger.info('There is no open file associated with this handle', { reqId, handle });
166-
logger.debug('Response: Status (FAILURE)', { reqId }, SFTP_STATUS_CODE.FAILURE);
167-
this.sftpConnection.status(reqId, SFTP_STATUS_CODE.FAILURE);
168-
return;
169-
}
170-
const attrs = generateAttributesForFile(file);
171-
logger.debug('Response: Attributes', { reqId, attrs });
172-
this.sftpConnection.attrs(reqId, attrs);
158+
logger.debug('Request:', { reqId, itemPath });
159+
this.genericStatHandler(reqId, itemPath);
173160
};
174161

175162
/**
@@ -253,22 +240,13 @@ export class SftpSessionHandler {
253240
* Also: Retrieving File Attributes
254241
* https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-02#section-6.8
255242
*/
256-
public lstatHandler = (reqId: number, handle: Buffer): void => {
243+
public lstatHandler = (
244+
reqId: number,
245+
itemPath: Buffer,
246+
): void => {
257247
logger.verbose('SFTP read file statistics without following symbolic links request (SSH_FXP_LSTAT)');
258-
logger.debug('Request:', { reqId, handle });
259-
this.permanentFileSystem.getItemType(handle.toString())
260-
.then((fileType) => {
261-
const attrs = generateDefaultAttributes(fileType);
262-
logger.debug('Response:', { reqId, attrs });
263-
this.sftpConnection.attrs(
264-
reqId,
265-
attrs,
266-
);
267-
})
268-
.catch(() => {
269-
logger.debug('Response: Status (EOF)', { reqId }, SFTP_STATUS_CODE.NO_SUCH_FILE);
270-
this.sftpConnection.status(reqId, SFTP_STATUS_CODE.NO_SUCH_FILE);
271-
});
248+
logger.debug('Request:', { reqId, itemPath });
249+
this.genericStatHandler(reqId, itemPath);
272250
};
273251

274252
/**
@@ -277,22 +255,13 @@ export class SftpSessionHandler {
277255
* Also: Retrieving File Attributes
278256
* https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-02#section-6.8
279257
*/
280-
public statHandler = (reqId: number, handle: Buffer): void => {
258+
public statHandler = (
259+
reqId: number,
260+
itemPath: Buffer,
261+
): void => {
281262
logger.verbose('SFTP read file statistics following symbolic links request (SSH_FXP_STAT)');
282-
logger.debug('Request:', { reqId, handle });
283-
this.permanentFileSystem.getItemType(handle.toString())
284-
.then((fileType) => {
285-
const attrs = generateDefaultAttributes(fileType);
286-
logger.debug('Response:', { reqId, attrs });
287-
this.sftpConnection.attrs(
288-
reqId,
289-
attrs,
290-
);
291-
})
292-
.catch(() => {
293-
logger.debug('Response: Status (EOF)', { reqId }, SFTP_STATUS_CODE.NO_SUCH_FILE);
294-
this.sftpConnection.status(reqId, SFTP_STATUS_CODE.NO_SUCH_FILE);
295-
});
263+
logger.debug('Request:', { reqId, itemPath });
264+
this.genericStatHandler(reqId, itemPath);
296265
};
297266

298267
/**
@@ -327,11 +296,11 @@ export class SftpSessionHandler {
327296
logger.verbose('SFTP canonicalize path request (SSH_FXP_REALPATH)');
328297
logger.debug('Request:', { reqId, relativePath });
329298
const resolvedPath = path.resolve('/', relativePath);
330-
this.permanentFileSystem.getItemType(resolvedPath)
331-
.then((fileType) => {
299+
this.permanentFileSystem.getItemAttributes(resolvedPath)
300+
.then((attrs) => {
332301
const fileEntry = generateFileEntry(
333302
resolvedPath,
334-
generateDefaultAttributes(fileType),
303+
attrs,
335304
);
336305
const names = [fileEntry];
337306
logger.debug('Response:', { reqId, names });
@@ -397,4 +366,19 @@ export class SftpSessionHandler {
397366
public symLinkHandler = (): void => {
398367
logger.verbose('SFTP create symlink request (SSH_FXP_SYMLINK)');
399368
};
369+
370+
private readonly genericStatHandler = (reqId: number, itemPath: Buffer): void => {
371+
this.permanentFileSystem.getItemAttributes(itemPath.toString())
372+
.then((attrs) => {
373+
logger.debug('Response:', { reqId, attrs });
374+
this.sftpConnection.attrs(
375+
reqId,
376+
attrs,
377+
);
378+
})
379+
.catch(() => {
380+
logger.debug('Response: Status (NO_SUCH_FILE)', { reqId }, SFTP_STATUS_CODE.NO_SUCH_FILE);
381+
this.sftpConnection.status(reqId, SFTP_STATUS_CODE.NO_SUCH_FILE);
382+
});
383+
};
400384
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import fs from 'fs';
2+
import { generateDefaultMode } from './generateDefaultMode';
3+
import type { Attributes } from 'ssh2';
4+
import type { Folder } from '@permanentorg/sdk';
5+
6+
export const generateAttributesForFolder = (folder: Folder): Attributes => ({
7+
mode: generateDefaultMode(fs.constants.S_IFDIR),
8+
uid: 0,
9+
gid: 0,
10+
size: folder.size,
11+
atime: 0,
12+
mtime: folder.updatedAt.getTime(),
13+
});

src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './generateAttributesForFile';
2+
export * from './generateAttributesForFolder';
23
export * from './generateDefaultAttributes';
34
export * from './generateFileEntriesForFolders';
45
export * from './generateFileEntriesForRecords';

0 commit comments

Comments
 (0)