Skip to content

Commit

Permalink
ignore perm errors
Browse files Browse the repository at this point in the history
  • Loading branch information
etnoy committed Feb 6, 2025
1 parent 48d421e commit 43d66c7
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 30 deletions.
3 changes: 3 additions & 0 deletions e2e/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ services:
- database
ports:
- 2285:2285
cap_drop:
# We need this to perform testing on permission errors
- DAC_OVERRIDE

redis:
image: redis:6.2-alpine@sha256:905c4ee67b8e0aa955331960d2aa745781e6bd89afc44a8584bfd13bc890f0ae
Expand Down
30 changes: 29 additions & 1 deletion e2e/src/api/specs/library.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { LibraryResponseDto, LoginResponseDto, getAllLibraries, scanLibrary } from '@immich/sdk';
import { cpSync, existsSync, rmSync, unlinkSync } from 'node:fs';
import { chmodSync, cpSync, existsSync, promises, rmSync, unlinkSync } from 'node:fs';
import { Socket } from 'socket.io-client';
import { userDto, uuidDto } from 'src/fixtures';
import { errorDto } from 'src/responses';
Expand Down Expand Up @@ -492,6 +492,34 @@ describe('/libraries', () => {
utils.removeImageFile(`${testAssetDir}/temp/folder${char}2/asset2.png`);
});

it('should handle permission errors on import paths without error', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
importPaths: [`${testAssetDirInternal}/temp/`],
});

const stat = await promises.stat(`${testAssetDir}/temp/directoryA`);
const mode = stat.mode;

chmodSync(`${testAssetDir}/temp/directoryB`, 0o000);

const { status } = await request(app)
.post(`/libraries/${library.id}/scan`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(204);

await utils.waitForQueueFinish(admin.accessToken, 'library');

chmodSync(`${testAssetDir}/temp/directoryB`, mode);

const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });

expect(assets.count).toBe(1);
expect(assets.items.find((asset) => asset.originalPath.includes('directoryA'))).toBeDefined();
expect(assets.items.find((asset) => asset.originalPath.includes('directoryB'))).not.toBeDefined();
});

it('should reimport a modified file', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
Expand Down
190 changes: 162 additions & 28 deletions server/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"class-validator": "^0.14.0",
"cookie-parser": "^1.4.6",
"exiftool-vendored": "^28.3.1",
"fast-glob": "^3.3.2",
"fast-glob": "github:etnoy/fast-glob#built",
"fluent-ffmpeg": "^2.1.2",
"geo-tz": "^8.0.0",
"handlebars": "^4.7.8",
Expand Down
15 changes: 15 additions & 0 deletions server/src/repositories/storage.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import archiver from 'archiver';
import chokidar, { WatchOptions } from 'chokidar';
import { escapePath, glob, globStream } from 'fast-glob';
import { ErrnoException } from 'fast-glob/out/types';
import { constants, createReadStream, createWriteStream, existsSync, mkdirSync } from 'node:fs';
import fs from 'node:fs/promises';
import path from 'node:path';
Expand Down Expand Up @@ -170,6 +171,19 @@ export class StorageRepository implements IStorageRepository {
});
}

private errorHandler = (error: ErrnoException) => {
if (error.code === 'ENOENT') {
this.logger.warn(`Path ${error.path} does not exist, ignoring.`);
return true;
} else if (error.code === 'EACCES') {
this.logger.warn(`Permission denied for path ${error.path}, ignoring.`);
return true;
}

this.logger.error(`Error while walking path ${error.path}: ${error.message}`);
return false;
};

async *walk(walkOptions: WalkOptionsDto): AsyncGenerator<string[]> {
const { pathsToCrawl, exclusionPatterns, includeHidden } = walkOptions;
if (pathsToCrawl.length === 0) {
Expand All @@ -185,6 +199,7 @@ export class StorageRepository implements IStorageRepository {
onlyFiles: true,
dot: includeHidden,
ignore: exclusionPatterns,
errorHandler: this.errorHandler,
});

let batch: string[] = [];
Expand Down

0 comments on commit 43d66c7

Please sign in to comment.