1/*
2 * Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23#include <CoreFoundation/CoreFoundation.h>
24#include <libc.h>
25
26#include <IOKit/IOKitLib.h>
27#include <IOKit/IOKitServer.h>
28#include <IOKit/kext/OSKextPrivate.h>
29
30#include "kextunload_main.h"
31
32const char * progname = "(unknown)";
33
34/*******************************************************************************
35*******************************************************************************/
36int main(int argc, char * const * argv)
37{
38    ExitStatus        result        = EX_OK;
39    KextunloadArgs    toolArgs;
40    ExitStatus        scratchResult = EX_OK;
41    Boolean           fatal         = false;
42
43
44   /*****
45    * Find out what my name is.
46    */
47    progname = rindex(argv[0], '/');
48    if (progname) {
49        progname++;   // go past the '/'
50    } else {
51        progname = (char *)argv[0];
52    }
53
54   /* Set the OSKext log callback right away.
55    */
56    OSKextSetLogOutputFunction(&tool_log);
57
58    result = readArgs(argc, argv, &toolArgs);
59    if (result != EX_OK) {
60        goto finish;
61    }
62
63    result = checkArgs(&toolArgs);
64    if (result != EX_OK) {
65        goto finish;
66    }
67
68   /* If given URLs, create OSKext objects for them so we can get
69    * bundle identifiers (that's what IOCatalogueTerminate() expects).
70    * If we failed to open one, keep going on but save the not-found
71    * error for our exit status.
72    */
73    result = createKextsIfNecessary(&toolArgs);
74    if (result != EX_OK && result != kKextunloadExitNotFound) {
75        goto finish;
76    }
77
78   /* Do the terminates & unloads. Catch the first nonfatal error as our
79    * exit and don't overwrite it with later nonfatal errors.
80    * *Do* overwrite it with a fatal error if we got one.
81    */
82    scratchResult = terminateKextClasses(&toolArgs, &fatal);
83    if (result == EX_OK && scratchResult != EX_OK) {
84        result = scratchResult;
85    }
86    if (fatal) {
87        result = scratchResult;
88        goto finish;
89    }
90
91    scratchResult = unloadKextsByIdentifier(&toolArgs, &fatal);
92    if (result == EX_OK && scratchResult != EX_OK) {
93        result = scratchResult;
94    }
95    if (fatal) {
96        result = scratchResult;
97        goto finish;
98    }
99
100    scratchResult = unloadKextsByURL(&toolArgs, &fatal);
101    if (result == EX_OK && scratchResult != EX_OK) {
102        result = scratchResult;
103    }
104    if (fatal) {
105        result = scratchResult;
106        goto finish;
107    }
108
109finish:
110
111    if (result == kKextunloadExitHelp) {
112        result = EX_OK;
113    }
114
115    exit(result);
116
117   /*****
118    * Clean everything up.
119    */
120    SAFE_RELEASE(toolArgs.kextURLs);
121    SAFE_RELEASE(toolArgs.kextClassNames);
122    SAFE_RELEASE(toolArgs.kextBundleIDs);
123
124    return result;
125}
126
127/*******************************************************************************
128*******************************************************************************/
129ExitStatus readArgs(int argc, char * const * argv, KextunloadArgs * toolArgs)
130{
131    ExitStatus   result          = EX_USAGE;
132    ExitStatus   scratchResult   = EX_USAGE;
133    int          optchar         = 0;
134    int          longindex       = -1;
135    CFStringRef  scratchString   = NULL;  // must release
136    CFNumberRef  scratchNumber   = NULL;  // must release
137    CFURLRef     scratchURL      = NULL;  // must release
138    CFIndex      i;
139
140    bzero(toolArgs, sizeof(*toolArgs));
141
142   /* Default is to unload both kext and driver personalities.
143    */
144    toolArgs->terminateOption = kIOCatalogModuleUnload;
145
146   /*****
147    * Allocate collection objects needed for command line argument processing.
148    */
149    if (!createCFMutableArray(&toolArgs->kextURLs, &kCFTypeArrayCallBacks)    ||
150        !createCFMutableArray(&toolArgs->kextBundleIDs, NULL /* C strings */) ||
151        !createCFMutableArray(&toolArgs->kextClassNames, NULL /* C strings */)) {
152
153        result = EX_OSERR;
154        OSKextLogMemError();
155        goto finish;
156    }
157
158   /*****
159    * Process command-line arguments.
160    */
161    result = EX_USAGE;
162
163    /*****
164    * Process command line arguments.
165    */
166    while ((optchar = getopt_long_only(argc, (char * const *)argv,
167        kOptChars, sOptInfo, &longindex)) != -1) {
168
169        SAFE_RELEASE_NULL(scratchString);
170        SAFE_RELEASE_NULL(scratchNumber);
171        SAFE_RELEASE_NULL(scratchURL);
172
173        switch (optchar) {
174            case kOptHelp:
175                usage(kUsageLevelFull);
176                result = kKextunloadExitHelp;
177                goto finish;
178                break;
179
180            case kOptBundleIdentifier:
181            case kOptModule:
182                addToArrayIfAbsent(toolArgs->kextBundleIDs, optarg);
183                break;
184
185            case kOptClassName:
186                addToArrayIfAbsent(toolArgs->kextClassNames, optarg);
187                break;
188              break;
189
190            case kOptPersonalitiesOnly:
191              toolArgs->terminateOption = kIOCatalogModuleTerminate;
192              break;
193
194            case kOptQuiet:
195                beQuiet();
196                break;
197
198            case kOptVerbose:
199                scratchResult = setLogFilterForOpt(argc, argv,
200                    /* forceOnFlags */ kOSKextLogKextOrGlobalMask);
201                if (scratchResult != EX_OK) {
202                    result = scratchResult;
203                    goto finish;
204                }
205                break;
206
207            default:
208               /* getopt_long_only() prints an error message for us. */
209                goto finish;
210                break;
211
212        } /* switch (optchar) */
213    } /* while (optchar = getopt_long_only(...) */
214
215   /*****
216    * Record the kext names from the command line.
217    */
218    for (i = optind; i < argc; i++) {
219
220        SAFE_RELEASE_NULL(scratchURL);
221
222        scratchURL = CFURLCreateFromFileSystemRepresentation(
223            kCFAllocatorDefault,
224            (const UInt8 *)argv[i], strlen(argv[i]), true);
225        if (!scratchURL) {
226            result = EX_OSERR;
227            OSKextLogMemError();
228            goto finish;
229        }
230        addToArrayIfAbsent(toolArgs->kextURLs, scratchURL);
231    }
232
233    result = EX_OK;
234finish:
235    SAFE_RELEASE(scratchString);
236    SAFE_RELEASE(scratchNumber);
237    SAFE_RELEASE(scratchURL);
238
239    return result;
240}
241
242/*******************************************************************************
243*******************************************************************************/
244ExitStatus
245checkArgs(KextunloadArgs * toolArgs)
246{
247    ExitStatus         result = EX_USAGE;
248
249    if (geteuid() != 0) {
250        OSKextLog(/* kext */ NULL,
251            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
252            "You must be running as root to unload kexts, "
253            "terminate services, or remove driver personalities.");
254        result = EX_NOPERM;
255        goto finish;
256    }
257
258    if (!CFArrayGetCount(toolArgs->kextURLs)      &&
259        !CFArrayGetCount(toolArgs->kextBundleIDs) &&
260        !CFArrayGetCount(toolArgs->kextClassNames)) {
261
262       /* Put an extra newline for readability.
263        */
264        OSKextLog(/* kext */ NULL,
265            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
266            "No kernel extensions specified.");
267        usage(kUsageLevelBrief);
268        goto finish;
269    }
270
271    result = EX_OK;
272
273finish:
274    return result;
275}
276
277/*******************************************************************************
278*******************************************************************************/
279ExitStatus
280createKextsIfNecessary(KextunloadArgs * toolArgs)
281{
282    ExitStatus result = EX_OK;
283    OSKextRef  aKext  = NULL;   // must release
284    CFIndex    count, i;
285
286    if (!CFArrayGetCount(toolArgs->kextURLs)) {
287        goto finish;
288    }
289
290    if (!createCFMutableArray(&toolArgs->kexts, &kCFTypeArrayCallBacks)) {
291        result = EX_OSERR;
292        goto finish;
293    }
294
295    count = CFArrayGetCount(toolArgs->kextURLs);
296    for (i = 0; i < count; i++) {
297        CFURLRef kextURL = CFArrayGetValueAtIndex(toolArgs->kextURLs, i);
298        char     kextPath[PATH_MAX] = "(unknown)";
299
300        CFURLGetFileSystemRepresentation(kextURL,
301            /* resolveToBase */ false,
302            (UInt8 *)kextPath,
303            sizeof(kextPath));
304
305        SAFE_RELEASE_NULL(aKext);
306        aKext = OSKextCreate(kCFAllocatorDefault, kextURL);
307        if (!aKext) {
308            OSKextLog(/* kext */ NULL,
309                kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
310                "Can't create %s.", kextPath);
311            result = kKextunloadExitNotFound;
312            continue; // not fatal!
313        }
314
315        addToArrayIfAbsent(toolArgs->kexts, aKext);
316    }
317
318finish:
319    SAFE_RELEASE(aKext);
320    return result;
321}
322
323/*******************************************************************************
324*******************************************************************************/
325ExitStatus
326terminateKextClasses(KextunloadArgs * toolArgs, Boolean * fatal)
327{
328    ExitStatus    result      = EX_OK;
329    kern_return_t kernResult;
330    CFIndex       count, i;
331
332    count = CFArrayGetCount(toolArgs->kextClassNames);
333    for (i = 0; i < count; i++) {
334        char * className = NULL;  // do not free
335
336        className = (char *)CFArrayGetValueAtIndex(toolArgs->kextClassNames, i);
337
338        kernResult = IOCatalogueTerminate(kIOMasterPortDefault,
339            kIOCatalogServiceTerminate,
340            className);
341
342        if (kernResult == kIOReturnNotPrivileged) {
343             OSKextLog(/* kext */ NULL,
344                 kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
345                 "You must be running as root to terminate IOService instances.");
346             result = kKextunloadExitNotPrivileged;
347             *fatal = true;
348             goto finish;
349
350        } else if (kernResult != KERN_SUCCESS) {
351            result = kKextunloadExitPartialFailure;
352            OSKextLog(/* kext */ NULL,
353                kOSKextLogErrorLevel | kOSKextLogIPCFlag,
354                "Failed to terminate class %s - %s.",
355                className, safe_mach_error_string(kernResult));
356        } else {
357            OSKextLog(/* kext */ NULL,
358                kOSKextLogBasicLevel | kOSKextLogIPCFlag,
359                "All instances of class %s terminated.",
360                className);
361        }
362    }
363
364finish:
365    if (result == kKextunloadExitPartialFailure) {
366        OSKextLog(/* kext */ NULL,
367            kOSKextLogErrorLevel | kOSKextLogIPCFlag,
368            "Check the system/kernel logs for error messages from the I/O Kit.");
369    }
370
371    return result;
372}
373
374/*******************************************************************************
375*******************************************************************************/
376ExitStatus unloadKextsByIdentifier(KextunloadArgs * toolArgs, Boolean * fatal)
377{
378    ExitStatus      result = EX_OK;
379    CFStringRef     kextIdentifier = NULL;  // must release
380    CFIndex         count, i;
381
382    count = CFArrayGetCount(toolArgs->kextBundleIDs);
383    for (i = 0; i < count; i++) {
384        char       * kextIDCString = NULL;  // do not free
385        ExitStatus   thisResult;
386
387        SAFE_RELEASE_NULL(kextIdentifier);
388        kextIDCString = (char *)CFArrayGetValueAtIndex(toolArgs->kextBundleIDs, i);
389        kextIdentifier = CFStringCreateWithCString(kCFAllocatorDefault,
390            kextIDCString, kCFStringEncodingUTF8);
391        thisResult = unloadKextWithIdentifier(kextIdentifier, toolArgs, fatal);
392
393       /* Only nab the first nonfatal error.
394        */
395        if (result == EX_OK && thisResult != EX_OK) {
396            result = thisResult;
397        }
398        if (*fatal) {
399            result = thisResult;
400            goto finish;
401        }
402    }
403
404finish:
405    SAFE_RELEASE(kextIdentifier);
406
407   /* If we didn't suffer a catastrophic error, but only a routine
408    * terminate failure, then recommend checking the system log.
409    * Note that for unloads we capture kernel error logs from the OSKext
410    * subsystem and print them to stderr.
411    */
412    if (!*fatal &&
413        result != EX_OK &&
414        toolArgs->terminateOption == kIOCatalogModuleTerminate) {
415
416        OSKextLog(/* kext */ NULL,
417            kOSKextLogErrorLevel | kOSKextLogIPCFlag,
418            "Check the system/kernel logs for error messages from the I/O Kit.");
419    }
420    return result;
421}
422
423/*******************************************************************************
424*******************************************************************************/
425ExitStatus unloadKextsByURL(KextunloadArgs * toolArgs, Boolean * fatal)
426{
427    ExitStatus      result  = EX_OK;
428    CFIndex         count, i;
429
430   /* This is one CF collection we only allocate when needed.
431    */
432    if (!toolArgs->kexts) {
433        goto finish;
434    }
435
436    count = CFArrayGetCount(toolArgs->kexts);
437    for (i = 0; i < count; i++) {
438        ExitStatus  thisResult = EX_OK;
439        OSKextRef   aKext      = NULL;  // do not release
440        CFURLRef    kextURL    = NULL;  // do not release
441        CFStringRef kextID     = NULL;  // do not release
442        char        kextPath[PATH_MAX];
443
444        aKext = (OSKextRef)CFArrayGetValueAtIndex(toolArgs->kexts, i);
445        kextURL = OSKextGetURL(aKext);
446        kextID = OSKextGetIdentifier(aKext);
447
448        if (!CFURLGetFileSystemRepresentation(kextURL,
449            /* resolveToBase */ false,
450            (UInt8 *)kextPath,
451            sizeof(kextPath))) {
452
453            memcpy(kextPath, "(unknown)", sizeof("(unknown)"));
454            continue;
455        }
456
457        thisResult = unloadKextWithIdentifier(kextID, toolArgs, fatal);
458
459       /* Only nab the first nonfatal error.
460        */
461        if (result == EX_OK && thisResult != EX_OK) {
462            result = thisResult;
463        }
464        if (*fatal) {
465            result = thisResult;
466            goto finish;
467        }
468    }
469
470finish:
471   /* If we didn't suffer a catastrophic error, but only a routine
472    * terminate failure, then recommend checking the system log.
473    * Note that for unloads we capture kernel error logs from the OSKext
474    * subsystem and print them to stderr.
475    */
476    if (!*fatal &&
477        result != EX_OK &&
478        toolArgs->terminateOption == kIOCatalogModuleTerminate) {
479
480        OSKextLog(/* kext */ NULL,
481            kOSKextLogErrorLevel | kOSKextLogIPCFlag,
482            "Check the system/kernel logs for error messages from the I/O Kit.");
483    }
484    return result;
485}
486
487/*******************************************************************************
488*******************************************************************************/
489ExitStatus unloadKextWithIdentifier(
490    CFStringRef      kextIdentifier,
491    KextunloadArgs * toolArgs,
492    Boolean        * fatal)
493{
494    ExitStatus      result = EX_OK;
495    char          * kextIdentifierCString = NULL;  // must free
496    kern_return_t   kernResult;
497
498    if (!kextIdentifierCString) {
499        kextIdentifierCString = createUTF8CStringForCFString(kextIdentifier);
500        if (!kextIdentifierCString) {
501            OSKextLogMemError();
502            result = EX_OSERR;
503            *fatal = true;
504            goto finish;
505        }
506    }
507
508    if (toolArgs->terminateOption == kIOCatalogModuleTerminate) {
509        kernResult = IOCatalogueTerminate(kIOMasterPortDefault,
510            toolArgs->terminateOption, kextIdentifierCString);
511    } else {
512        kernResult = OSKextUnloadKextWithIdentifier(kextIdentifier,
513            /* terminateAndRemovePersonalities */ true);
514    }
515
516    if (kernResult == kIOReturnNotPrivileged) {
517         OSKextLog(/* kext */ NULL,
518             kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
519             "You must be running as root to unload kexts.");
520         result = kKextunloadExitNotPrivileged;
521         *fatal = true;
522         goto finish;
523
524    } else if (kernResult != KERN_SUCCESS) {
525         result = kKextunloadExitPartialFailure;
526        if (toolArgs->terminateOption == kIOCatalogModuleTerminate) {
527            OSKextLog(/* kext */ NULL,
528                kOSKextLogErrorLevel | kOSKextLogIPCFlag,
529                "Terminate for %s failed - %s.",
530                kextIdentifierCString, safe_mach_error_string(kernResult));
531        } else {
532            // OSKextUnloadKextWithIdentifier() logged an error
533        }
534    } else {
535        if (toolArgs->terminateOption == kIOCatalogModuleTerminate) {
536            OSKextLog(/* kext */ NULL,
537                kOSKextLogBasicLevel | kOSKextLogIPCFlag,
538                "%s: services terminated and personalities removed "
539                "(kext not unloaded).",
540                kextIdentifierCString);
541        } else {
542            OSKextLog(/* kext */ NULL,
543                kOSKextLogBasicLevel | kOSKextLogIPCFlag,
544                "%s unloaded and personalities removed.",
545                kextIdentifierCString);
546        }
547    }
548finish:
549    SAFE_FREE(kextIdentifierCString);
550
551    return result;
552}
553
554/*******************************************************************************
555*
556*******************************************************************************/
557void usage(UsageLevel usageLevel)
558{
559    fprintf(stderr, "usage: %s [-h] [-v [0-6]]\n"
560        "        [-p] [-c class_name] ... [-b bundle_id] ... [kext] ...\n",
561        progname);
562
563    if (usageLevel == kUsageLevelBrief) {
564        goto finish;
565    }
566
567    fprintf(stderr, "kext: unload the named kext and all personalities for it\n");
568    fprintf(stderr, "\n");
569
570    fprintf(stderr, "-%s <bundle_id> (-%c):\n"
571        "        unload the kext and personalities for CFBundleIdentifier <bundle_id>\n",
572        kOptNameBundleIdentifier, kOptBundleIdentifier);
573    fprintf(stderr, "-%s <class_name> (-%c):\n"
574        "        terminate all instances of IOService class <class_name> but do not\n"
575        "        unload its kext or remove its personalities\n",
576        kOptNameClassName, kOptClassName);
577    fprintf(stderr, "\n");
578    fprintf(stderr,
579        "-%s (-%c):\n"
580        "        terminate services and remove personalities only; do not unload kexts\n"
581        "        (applies only to unload by bundle-id or kext)\n",
582            kOptNamePersonalitiesOnly, kOptPersonalitiesOnly);
583
584// :doc: meaning of -p inverted all these years
585
586    fprintf(stderr, "-%s (-%c):\n"
587        "        quiet mode: print no informational or error messages\n",
588        kOptNameQuiet, kOptQuiet);
589    fprintf(stderr, "-%s [ 0-6 | 0x<flags> ] (-%c):\n"
590        "        verbose mode; print info about analysis & loading\n",
591        kOptNameVerbose, kOptVerbose);
592    fprintf(stderr, "\n");
593
594    fprintf(stderr, "-%s (-%c): print this message and exit\n",
595        kOptNameHelp, kOptHelp);
596    fprintf(stderr, "\n");
597
598    fprintf(stderr, "--: end of options\n");
599
600finish:
601    return;
602}
603