Warning: That file was not part of the compilation database. It may have many parsing errors.
1 | /* |
---|---|
2 | * This file is part of the KDE libraries |
3 | * Copyright (c) 1999-2000 Waldo Bastian <bastian@kde.org> |
4 | * (c) 1999 Mario Weilguni <mweilguni@sime.com> |
5 | * (c) 2001 Lubos Lunak <l.lunak@kde.org> |
6 | * (c) 2006-2011 Ralf Habacker <ralf.habacker@freenet.de> |
7 | * (c) 2009 Patrick Spendrin <ps_ml@gmx.de> |
8 | * |
9 | * This library is free software; you can redistribute it and/or |
10 | * modify it under the terms of the GNU Library General Public |
11 | * License version 2 as published by the Free Software Foundation. |
12 | * |
13 | * This library is distributed in the hope that it will be useful, |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
16 | * Library General Public License for more details. |
17 | * |
18 | * You should have received a copy of the GNU Library General Public License |
19 | * along with this library; see the file COPYING.LIB. If not, write to |
20 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
21 | * Boston, MA 02110-1301, USA. |
22 | */ |
23 | |
24 | #include <config.h> |
25 | |
26 | |
27 | #include <errno.h> |
28 | #include <stdio.h> |
29 | #include <stdlib.h> |
30 | #include <string.h> |
31 | |
32 | #include <windows.h> |
33 | #ifndef _WIN32_WCE |
34 | #include <Sddl.h> |
35 | #endif |
36 | #include <tlhelp32.h> |
37 | #include <psapi.h> |
38 | |
39 | |
40 | #include <QtCore/QProcess> |
41 | #include <QtCore/QFileInfo> |
42 | // Under wince interface is defined, so undef it otherwise it breaks it |
43 | #undef interface |
44 | #include <QtDBus/QtDBus> |
45 | |
46 | #include <kcomponentdata.h> |
47 | #include <kstandarddirs.h> |
48 | #include <kapplication.h> |
49 | #include <kdeversion.h> |
50 | |
51 | //#define ENABLE_SUICIDE |
52 | //#define ENABLE_EXIT |
53 | |
54 | #define KDED_EXENAME "kded4" |
55 | |
56 | static KComponentData *s_instance = 0; |
57 | |
58 | // print verbose messages |
59 | int verbose=0; |
60 | |
61 | /// holds process list for suicide mode |
62 | QList<QProcess*> startedProcesses; |
63 | |
64 | /* -------------------------------------------------------------------- |
65 | sid helper - will be migrated later to a class named Sid, which could |
66 | be used as base class for platform independent K_UID and K_GID types |
67 | - would this be possible before KDE 5 ? |
68 | --------------------------------------------------------------------- */ |
69 | |
70 | /** |
71 | copy sid |
72 | @param from sif to copy from |
73 | @return copied sid, need to be free'd with free |
74 | @note null sid's are handled too |
75 | */ |
76 | PSID copySid(PSID from) |
77 | { |
78 | if (!from) |
79 | return 0; |
80 | int sidLength = GetLengthSid(from); |
81 | PSID to = (PSID) malloc(sidLength); |
82 | CopySid(sidLength, to, from); |
83 | return to; |
84 | } |
85 | |
86 | /** |
87 | copy sid |
88 | @param from sif to copy from |
89 | @return copied sid, need to be free'd with free |
90 | @note null sid's are handled too |
91 | */ |
92 | void freeSid(PSID sid) |
93 | { |
94 | if (sid) |
95 | free(sid); |
96 | } |
97 | |
98 | /** |
99 | copy sid |
100 | @param from sif to copy from |
101 | @return copied sid, need to be free'd with free |
102 | @note null sid's are handled too |
103 | */ |
104 | QString toString(PSID sid) |
105 | { |
106 | LPWSTR s; |
107 | if (!ConvertSidToStringSid(sid, &s)) |
108 | return QString(); |
109 | |
110 | QString result = QString::fromUtf16(reinterpret_cast<ushort*>(s)); |
111 | LocalFree(s); |
112 | return result; |
113 | } |
114 | |
115 | /* -------------------------------------------------------------------- |
116 | process helper |
117 | --------------------------------------------------------------------- */ |
118 | |
119 | /** |
120 | return process handle |
121 | @param pid process id |
122 | @return process handle |
123 | */ |
124 | static HANDLE getProcessHandle(int processID) |
125 | { |
126 | return OpenProcess( SYNCHRONIZE|PROCESS_QUERY_INFORMATION | |
127 | PROCESS_VM_READ | PROCESS_TERMINATE, |
128 | false, processID ); |
129 | } |
130 | |
131 | /** |
132 | return absolute path of process |
133 | @param pid process id |
134 | @return process name |
135 | */ |
136 | static QString getProcessName(DWORD pid) |
137 | { |
138 | HANDLE hModuleSnap = INVALID_HANDLE_VALUE; |
139 | MODULEENTRY32 me32; |
140 | |
141 | hModuleSnap = CreateToolhelp32Snapshot( TH32CS_SNAPMODULE, pid ); |
142 | if( hModuleSnap == INVALID_HANDLE_VALUE ) |
143 | return QString(); |
144 | |
145 | me32.dwSize = sizeof( MODULEENTRY32 ); |
146 | |
147 | if( !Module32First( hModuleSnap, &me32 ) ) { |
148 | CloseHandle( hModuleSnap ); // clean the snapshot object |
149 | return QString(); |
150 | } |
151 | QString name = QString::fromWCharArray(me32.szExePath); |
152 | CloseHandle( hModuleSnap ); |
153 | return name; |
154 | } |
155 | |
156 | /** |
157 | return sid of specific process |
158 | @param hProcess handle to process |
159 | @return sid pointer to PSID structure, must be freed with LocalAlloc |
160 | */ |
161 | static PSID getProcessOwner(HANDLE hProcess) |
162 | { |
163 | #ifndef _WIN32_WCE |
164 | HANDLE hToken = NULL; |
165 | PSID sid; |
166 | |
167 | OpenProcessToken(hProcess, TOKEN_READ, &hToken); |
168 | if(hToken) |
169 | { |
170 | DWORD size; |
171 | PTOKEN_USER userStruct; |
172 | |
173 | // check how much space is needed |
174 | GetTokenInformation(hToken, TokenUser, NULL, 0, &size); |
175 | if( ERROR_INSUFFICIENT_BUFFER == GetLastError() ) |
176 | { |
177 | userStruct = reinterpret_cast<PTOKEN_USER>( new BYTE[size] ); |
178 | GetTokenInformation(hToken, TokenUser, userStruct, size, &size); |
179 | |
180 | sid = copySid(userStruct->User.Sid); |
181 | CloseHandle(hToken); |
182 | delete [] userStruct; |
183 | return sid; |
184 | } |
185 | } |
186 | #endif |
187 | return 0; |
188 | } |
189 | |
190 | /** |
191 | return sid of current process owner |
192 | */ |
193 | static PSID getCurrentProcessOwner() |
194 | { |
195 | return getProcessOwner(GetCurrentProcess()); |
196 | } |
197 | |
198 | /** |
199 | holds single process |
200 | */ |
201 | class ProcessListEntry { |
202 | public: |
203 | ProcessListEntry( HANDLE _handle, QString _path, int _pid, PSID _owner=0 ) |
204 | { |
205 | QFileInfo p(_path); |
206 | path = p.absolutePath(); |
207 | name = p.baseName(); |
208 | handle = _handle; |
209 | pid = _pid; |
210 | owner = copySid(_owner); |
211 | } |
212 | |
213 | ~ProcessListEntry() |
214 | { |
215 | freeSid(owner); |
216 | CloseHandle(handle); |
217 | } |
218 | |
219 | QString name; |
220 | QString path; |
221 | int pid; |
222 | HANDLE handle; |
223 | PSID owner; |
224 | friend QDebug operator <<(QDebug out, const ProcessListEntry &c); |
225 | }; |
226 | |
227 | QDebug operator <<(QDebug out, const ProcessListEntry &c) |
228 | { |
229 | out << "(ProcessListEntry" |
230 | << "name"<< c.name |
231 | << "path"<< c.path |
232 | << "pid"<< c.pid |
233 | << "handle"<< c.handle |
234 | << "sid"<< toString(c.owner) |
235 | << ")"; |
236 | return out; |
237 | } |
238 | |
239 | /** |
240 | holds system process list snapshot |
241 | |
242 | Could be used as a public platform independent class or namespace in kdecore |
243 | for dealing with system processes, named perhaps KSystemProcessSnapshot or similar. |
244 | If implemented at Qt level it will be named QSystemProcessSnapshot or similar |
245 | */ |
246 | class ProcessList { |
247 | public: |
248 | /** |
249 | collect process |
250 | @param userSid sid of user for which processes should be collected or 0 for all processes |
251 | */ |
252 | ProcessList(PSID userSid=0); |
253 | |
254 | ~ProcessList(); |
255 | |
256 | /** |
257 | find process in list |
258 | @param name process name (with or without extension) |
259 | @return instance of process entry |
260 | */ |
261 | ProcessListEntry *find(const QString &name); |
262 | |
263 | /** |
264 | killprocess from list |
265 | @param name process name (with or without extension) |
266 | @return ... |
267 | */ |
268 | bool terminateProcess(const QString &name); |
269 | |
270 | /** |
271 | return all processes |
272 | @return list with processes |
273 | */ |
274 | QList<ProcessListEntry *> &list() { return m_processes; } |
275 | |
276 | private: |
277 | void init(); |
278 | QList<ProcessListEntry *> m_processes; |
279 | PSID m_userId; |
280 | }; |
281 | |
282 | ProcessList::ProcessList(PSID userSid) |
283 | { |
284 | m_userId = userSid; |
285 | init(); |
286 | } |
287 | |
288 | ProcessList::~ProcessList() |
289 | { |
290 | foreach(const ProcessListEntry *ple,m_processes) |
291 | delete ple; |
292 | } |
293 | |
294 | void ProcessList::init() |
295 | { |
296 | HANDLE h; |
297 | PROCESSENTRY32 pe32; |
298 | |
299 | h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); |
300 | if (h == INVALID_HANDLE_VALUE) { |
301 | return; |
302 | } |
303 | pe32.dwSize = sizeof(PROCESSENTRY32); |
304 | if (!Process32First( h, &pe32 )) |
305 | return; |
306 | |
307 | do |
308 | { |
309 | HANDLE hProcess = getProcessHandle(pe32.th32ProcessID); |
310 | if (!hProcess) |
311 | continue; |
312 | QString name = getProcessName(pe32.th32ProcessID); |
313 | #ifndef _WIN32_WCE |
314 | PSID sid = getProcessOwner(hProcess); |
315 | if (!sid || m_userId && !EqualSid(m_userId,sid)) |
316 | { |
317 | freeSid(sid); |
318 | continue; |
319 | } |
320 | #else |
321 | PSID sid = 0; |
322 | #endif |
323 | m_processes << new ProcessListEntry( hProcess, name, pe32.th32ProcessID, sid); |
324 | } while(Process32Next( h, &pe32 )); |
325 | #ifndef _WIN32_WCE |
326 | CloseHandle(h); |
327 | #else |
328 | CloseToolhelp32Snapshot(h); |
329 | #endif |
330 | } |
331 | |
332 | ProcessListEntry *ProcessList::find(const QString &name) |
333 | { |
334 | ProcessListEntry *ple; |
335 | foreach(ple,m_processes) { |
336 | if (ple->pid < 0) { |
337 | qDebug() << "negative pid!"; |
338 | continue; |
339 | } |
340 | |
341 | if (ple->name != name && ple->name != name + ".exe") { |
342 | continue; |
343 | } |
344 | |
345 | if (!ple->path.isEmpty() && !ple->path.toLower().startsWith(KStandardDirs::installPath("kdedir").toLower())) { |
346 | // process is outside of installation directory |
347 | qDebug() << "path of the process"<< name << "seems to be outside of the installPath:"<< ple->path << KStandardDirs::installPath( "kdedir"); |
348 | continue; |
349 | } |
350 | return ple; |
351 | } |
352 | return NULL; |
353 | } |
354 | |
355 | bool ProcessList::terminateProcess(const QString &name) |
356 | { |
357 | qDebug() << "going to terminate process"<< name; |
358 | ProcessListEntry *p = find(name); |
359 | if (!p) { |
360 | qDebug() << "could not find ProcessListEntry for process name"<< name; |
361 | return false; |
362 | } |
363 | |
364 | bool ret = TerminateProcess(p->handle,0); |
365 | if (ret) { |
366 | int i = m_processes.indexOf(p); |
367 | if(i != -1) m_processes.removeAt(i); |
368 | delete p; |
369 | return true; |
370 | } else { |
371 | return false; |
372 | } |
373 | } |
374 | |
375 | // internal launch function |
376 | int launch(const QString &cmd) |
377 | { |
378 | QProcess *proc = new QProcess(); |
379 | proc->start(cmd); |
380 | proc->waitForStarted(); |
381 | startedProcesses << proc; |
382 | _PROCESS_INFORMATION* _pid = proc->pid(); |
383 | int pid = _pid ? _pid->dwProcessId : 0; |
384 | if (verbose) { |
385 | fprintf(stderr,"%s",proc->readAllStandardError().constData()); |
386 | fprintf(stderr,"%s",proc->readAllStandardOutput().constData()); |
387 | } |
388 | if (pid) { |
389 | if (verbose) |
390 | fprintf(stderr, "kdeinit4: Launched %s, pid = %ld\n", qPrintable(cmd),(long) pid); |
391 | } |
392 | else { |
393 | if (verbose) |
394 | fprintf(stderr, "kdeinit4: could not launch %s, exiting\n",qPrintable(cmd)); |
395 | } |
396 | return pid; |
397 | } |
398 | |
399 | /// check dbus registration |
400 | bool checkIfRegisteredInDBus(const QString &name, int _timeout=10) |
401 | { |
402 | int timeout = _timeout * 5; |
403 | while(timeout) { |
404 | if ( QDBusConnection::sessionBus().interface()->isServiceRegistered( name ) ) |
405 | break; |
406 | Sleep(200); |
407 | timeout--; |
408 | } |
409 | if (!timeout) { |
410 | if (verbose) |
411 | fprintf(stderr,"not registered %s in dbus after %d secs\n",qPrintable(name),_timeout); |
412 | return false; |
413 | } |
414 | if (verbose) |
415 | fprintf(stderr,"%s is registered in dbus\n",qPrintable(name)); |
416 | return true; |
417 | } |
418 | |
419 | void listAllRunningKDEProcesses(ProcessList &processList) |
420 | { |
421 | QString installPrefix = KStandardDirs::installPath("kdedir"); |
422 | |
423 | foreach(const ProcessListEntry *ple, processList.list()) |
424 | { |
425 | if (!ple->path.isEmpty() && ple->path.toLower().startsWith(installPrefix.toLower())) |
426 | fprintf(stderr,"path: %s name: %s pid: %u\n", ple->path.toLatin1().data(), ple->name.toLatin1().data(), ple->pid); |
427 | } |
428 | } |
429 | |
430 | void terminateAllRunningKDEProcesses(ProcessList &processList) |
431 | { |
432 | QString installPrefix = KStandardDirs::installPath("kdedir"); |
433 | |
434 | foreach(const ProcessListEntry *ple, processList.list()) |
435 | { |
436 | if (!ple->path.isEmpty() && ple->path.toLower().startsWith(installPrefix.toLower())) |
437 | { |
438 | if (verbose) |
439 | fprintf(stderr,"terminating path: %s name: %s pid: %u\n", ple->path.toLatin1().data(), ple->name.toLatin1().data(), ple->pid); |
440 | processList.terminateProcess(ple->name); |
441 | } |
442 | } |
443 | } |
444 | |
445 | void listAllNamedAppsInDBus() |
446 | { |
447 | QDBusConnection connection = QDBusConnection::sessionBus(); |
448 | QDBusConnectionInterface *bus = connection.interface(); |
449 | const QStringList services = bus->registeredServiceNames(); |
450 | foreach(const QString &service, services) { |
451 | if (service.startsWith(QLatin1String("org.freedesktop.DBus")) || service.startsWith(QLatin1Char( |
452 | continue; |
453 | fprintf(stderr, "%s \n", service.toLatin1().data()); |
454 | } |
455 | } |
456 | |
457 | void quitApplicationsOverDBus() |
458 | { |
459 | QDBusConnection connection = QDBusConnection::sessionBus(); |
460 | QDBusConnectionInterface *bus = connection.interface(); |
461 | const QStringList services = bus->registeredServiceNames(); |
462 | foreach(const QString &service, services) { |
463 | if (service.startsWith(QLatin1String("org.freedesktop.DBus")) || service.startsWith(QLatin1Char( |
464 | continue; |
465 | QDBusInterface *iface = new QDBusInterface(service, |
466 | QLatin1String("/MainApplication"), |
467 | QLatin1String("org.kde.KApplication"), |
468 | connection); |
469 | if (!iface->isValid()) { |
470 | if (verbose) |
471 | fprintf(stderr, "invalid interface of service %s\n", service.toLatin1().data()); |
472 | continue; |
473 | } |
474 | iface->call("quit"); |
475 | if (iface->lastError().isValid()) { |
476 | if (verbose) |
477 | fprintf(stderr,"killing %s with result\n", iface->lastError().message().toLatin1().data()); |
478 | } |
479 | delete iface; |
480 | } |
481 | } |
482 | |
483 | int main(int argc, char **argv, char **envp) |
484 | { |
485 | pid_t pid = 0; |
486 | bool launch_dbus = true; |
487 | bool launch_klauncher = true; |
488 | bool launch_kded = true; |
489 | bool suicide = false; |
490 | bool listProcesses = false; |
491 | bool killProcesses = false; |
492 | bool listAppsInDBus = false; |
493 | bool quitAppsOverDBus = false; |
494 | bool shutdown = false; |
495 | |
496 | /** Save arguments first... **/ |
497 | char **safe_argv = (char **) malloc( sizeof(char *) * argc); |
498 | for(int i = 0; i < argc; i++) |
499 | { |
500 | safe_argv[i] = strcpy((char*)malloc(strlen(argv[i])+1), argv[i]); |
501 | if (strcmp(safe_argv[i], "--no-dbus") == 0) |
502 | launch_dbus = false; |
503 | if (strcmp(safe_argv[i], "--no-klauncher") == 0) |
504 | launch_klauncher = false; |
505 | if (strcmp(safe_argv[i], "--no-kded") == 0) |
506 | launch_kded = false; |
507 | if (strcmp(safe_argv[i], "--suicide") == 0) |
508 | suicide = true; |
509 | #ifdef ENABLE_EXIT |
510 | if (strcmp(safe_argv[i], "--exit") == 0) |
511 | keep_running = 0; |
512 | #endif |
513 | if (strcmp(safe_argv[i], "--verbose") == 0) |
514 | verbose = 1; |
515 | if (strcmp(safe_argv[i], "--version") == 0) |
516 | { |
517 | printf("Qt: %s\n",qVersion()); |
518 | printf("KDE: %s\n", KDE_VERSION_STRING); |
519 | exit(0); |
520 | } |
521 | if (strcmp(safe_argv[i], "--help") == 0) |
522 | { |
523 | printf("Usage: kdeinit4 [options]\n"); |
524 | #ifdef ENABLE_EXIT |
525 | printf(" --exit Terminate when kded has run\n"); |
526 | #endif |
527 | printf(" --help this help page\n"); |
528 | printf(" --list list kde processes\n"); |
529 | printf(" --list-dbus-apps list all applications registered in dbus\n"); |
530 | printf(" --quit-over-dbus quit all application registered in dbus\n"); |
531 | printf(" --no-dbus do not start dbus-daemon\n"); |
532 | printf(" --no-klauncher do not start klauncher\n"); |
533 | printf(" --no-kded do not start kded\n"); |
534 | printf(" --shutdown safe shutdown of all running kde processes\n"); |
535 | printf(" first over dbus, then using hard kill\n"); |
536 | #ifdef ENABLE_SUICIDE |
537 | printf(" --suicide terminate when no KDE applications are left running\n"); |
538 | #endif |
539 | printf(" --terminate hard kill of *all* running kde processes\n"); |
540 | printf(" --verbose print verbose messages\n"); |
541 | printf(" --version Show version information\n"); |
542 | exit(0); |
543 | } |
544 | if (strcmp(safe_argv[i], "--list") == 0) |
545 | listProcesses = true; |
546 | if (strcmp(safe_argv[i], "--shutdown") == 0) |
547 | shutdown = true; |
548 | if (strcmp(safe_argv[i], "--terminate") == 0 || strcmp(safe_argv[i], "--kill") == 0) |
549 | killProcesses = true; |
550 | if (strcmp(safe_argv[i], "--list-dbus-apps") == 0) |
551 | listAppsInDBus = true; |
552 | if (strcmp(safe_argv[i], "--quit-over-dbus") == 0) |
553 | quitAppsOverDBus = true; |
554 | } |
555 | |
556 | PSID currentSid = getCurrentProcessOwner(); |
557 | if (verbose) |
558 | fprintf(stderr,"current user sid: %s\n",qPrintable(toString(currentSid))); |
559 | ProcessList processList(currentSid); |
560 | freeSid(currentSid); |
561 | |
562 | if (listProcesses) { |
563 | listAllRunningKDEProcesses(processList); |
564 | return 0; |
565 | } |
566 | else if (killProcesses) { |
567 | terminateAllRunningKDEProcesses(processList); |
568 | return 0; |
569 | } |
570 | else if (listAppsInDBus) { |
571 | listAllNamedAppsInDBus(); |
572 | return 0; |
573 | } |
574 | else if (quitAppsOverDBus) { |
575 | quitApplicationsOverDBus(); |
576 | return 0; |
577 | } |
578 | else if (shutdown) { |
579 | quitApplicationsOverDBus(); |
580 | Sleep(2000); |
581 | terminateAllRunningKDEProcesses(processList); |
582 | } |
583 | |
584 | /** Create our instance **/ |
585 | s_instance = new KComponentData("kdeinit4", QByteArray(), KComponentData::SkipMainComponentRegistration); |
586 | |
587 | #ifdef _DEBUG |
588 | // first try to launch dbus-daemond in debug mode |
589 | if (launch_dbus && processList.find("dbus-daemond")) |
590 | launch_dbus = false; |
591 | if (launch_dbus) |
592 | { |
593 | pid = launch("dbus-launchd.exe"); |
594 | if (!pid) |
595 | pid = launch("dbus-launchd.bat"); |
596 | launch_dbus = (pid == 0); |
597 | } |
598 | #endif |
599 | if (launch_dbus && !processList.find("dbus-daemon")) |
600 | { |
601 | if (!pid) |
602 | pid = launch("dbus-launch.exe"); |
603 | if (!pid) |
604 | pid = launch("dbus-launch.bat"); |
605 | if (!pid) |
606 | exit(1); |
607 | } |
608 | |
609 | if (launch_klauncher && !processList.find("klauncher")) |
610 | { |
611 | pid = launch("klauncher"); |
612 | if (!pid || !checkIfRegisteredInDBus("org.kde.klauncher",10)) |
613 | exit(1); |
614 | } |
615 | |
616 | |
617 | if (launch_kded && !processList.find(KDED_EXENAME)) |
618 | { |
619 | pid = launch(KDED_EXENAME); |
620 | if (!pid || !checkIfRegisteredInDBus("org.kde.kded",10)) |
621 | exit(1); |
622 | } |
623 | |
624 | for(int i = 1; i < argc; i++) |
625 | { |
626 | if (safe_argv[i][0] == |
627 | { |
628 | pid = launch(safe_argv[i]+1); |
629 | } |
630 | else if (safe_argv[i][0] == |
631 | { |
632 | // Ignore |
633 | } |
634 | else |
635 | { |
636 | pid = launch( safe_argv[i]); |
637 | } |
638 | } |
639 | |
640 | /** Free arguments **/ |
641 | for(int i = 0; i < argc; i++) |
642 | { |
643 | free(safe_argv[i]); |
644 | } |
645 | free (safe_argv); |
646 | |
647 | /** wait for termination of all (core) processes */ |
648 | #ifdef ENABLE_SUICIDE |
649 | if (suicide) { |
650 | QProcess *proc; |
651 | int can_exit=1; |
652 | do { |
653 | foreach(proc,startedProcesses) { |
654 | if (proc->state() != QProcess::NotRunning) |
655 | can_exit = 0; |
656 | } |
657 | if (!can_exit) |
658 | Sleep(2000); |
659 | } while(!can_exit); |
660 | return 0; |
661 | } |
662 | #endif |
663 | return 0; |
664 | } |
665 |
Warning: That file was not part of the compilation database. It may have many parsing errors.