Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(launchdoctor): detect missing libraries for dlopen #3202

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 31 additions & 10 deletions src/server/validateDependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ export async function validateHostRequirements(browserPath: string, browser: Bro
await validateDependencies(browserPath, browser);
}

const DL_OPEN_LIBRARIES = {
chromium: [],
webkit: ['libGLESv2.so.2'],
firefox: [],
};

async function validateDependencies(browserPath: string, browser: BrowserDescriptor) {
// We currently only support Linux.
if (os.platform() !== 'linux')
Expand All @@ -47,6 +53,8 @@ async function validateDependencies(browserPath: string, browser: BrowserDescrip
for (const dep of deps)
missingDeps.add(dep);
}
for (const dep of (await missingDLOPENLibraries(browser)))
missingDeps.add(dep);
if (!missingDeps.size)
return;
// Check Ubuntu version.
Expand Down Expand Up @@ -110,27 +118,40 @@ async function executablesOrSharedLibraries(directoryPath: string): Promise<stri
}

async function missingFileDependencies(filePath: string): Promise<Array<string>> {
const {stdout} = await lddAsync(filePath);
const missingDeps = stdout.split('\n').map(line => line.trim()).filter(line => line.endsWith('not found') && line.includes('=>')).map(line => line.split('=>')[0].trim());
return missingDeps;
}

function lddAsync(filePath: string): Promise<{stdout: string, stderr: string, code: number}> {
const dirname = path.dirname(filePath);
const ldd = spawn('ldd', [filePath], {
const {stdout, code} = await spawnAsync('ldd', [filePath], {
cwd: dirname,
env: {
...process.env,
LD_LIBRARY_PATH: process.env.LD_LIBRARY_PATH ? `${process.env.LD_LIBRARY_PATH}:${dirname}` : dirname,
},
});
if (code !== 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should throw if it failed otherwise it's indistinguishable from the case when all dependencies are good.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ldd fails if we feed in a non-ELF file (and we might occasionally do so).

We should throw if it failed otherwise it's indistinguishable from the case when all dependencies are good.

If our spawn fails for some reason, i'd still try launching to avoid false negatives.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's unfortunate, perhaps we could run this checks after launch failed? Don't remember what was the rationale for running the checks before launch.

Copy link
Collaborator Author

@aslushnikov aslushnikov Jul 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason is that there might be no browser crash at all. In case of missing libglesv2 the browsers won't crash - instead, renderer will either misbehave (on ubuntu 18.04) or crash (on ubuntu 20.04)

return [];
const missingDeps = stdout.split('\n').map(line => line.trim()).filter(line => line.endsWith('not found') && line.includes('=>')).map(line => line.split('=>')[0].trim());
return missingDeps;
}

async function missingDLOPENLibraries(browser: BrowserDescriptor): Promise<string[]> {
const libraries = DL_OPEN_LIBRARIES[browser.name];
if (!libraries.length)
return [];
const {stdout, code} = await spawnAsync('ldconfig', ['-p'], {});
if (code !== 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

return [];
const isLibraryAvailable = (library: string) => stdout.toLowerCase().includes(library.toLowerCase());
return libraries.filter(library => !isLibraryAvailable(library));
}

function spawnAsync(cmd: string, args: string[], options: any): Promise<{stdout: string, stderr: string, code: number}> {
const process = spawn(cmd, args, options);

return new Promise(resolve => {
let stdout = '';
let stderr = '';
ldd.stdout.on('data', data => stdout += data);
ldd.stderr.on('data', data => stderr += data);
ldd.on('close', code => resolve({stdout, stderr, code}));
process.stdout.on('data', data => stdout += data);
process.stderr.on('data', data => stderr += data);
process.on('close', code => resolve({stdout, stderr, code}));
});
}

Expand Down