1/*
2 * Copyright (c) 1998-2013 Apple 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
24#include "DAMain.h"
25
26#include "DABase.h"
27#include "DADialog.h"
28#include "DADisk.h"
29#include "DAFileSystem.h"
30#include "DAInternal.h"
31#include "DALog.h"
32#include "DAServer.h"
33#include "DASession.h"
34#include "DAStage.h"
35#include "DASupport.h"
36#include "DAThread.h"
37
38#include <assert.h>
39#include <dirent.h>
40#include <libgen.h>
41#include <notify.h>
42#include <signal.h>
43#include <sysexits.h>
44#include <unistd.h>
45#include <sys/param.h>
46#include <sys/stat.h>
47#include <sys/wait.h>
48#include <CoreFoundation/CoreFoundation.h>
49#include <IOKit/IOKitLib.h>
50#include <IOKit/storage/IOMedia.h>
51
52static const CFStringRef __kDABundlePath = CFSTR( "/System/Library/Frameworks/DiskArbitration.framework" );
53
54static SCDynamicStoreRef     __gDAConfigurationPort   = NULL;
55static Boolean               __gDAOptionDebug         = FALSE;
56static CFMachPortRef         __gDAVolumeMountedPort   = NULL;
57static CFMachPortRef         __gDAVolumeUnmountedPort = NULL;
58
59const char * kDAMainMountPointFolder           = "/Volumes";
60const char * kDAMainMountPointFolderCookieFile = ".autodiskmounted";
61
62CFURLRef               gDABundlePath                   = NULL;
63CFStringRef            gDAConsoleUser                  = NULL;
64gid_t                  gDAConsoleUserGID               = 0;
65uid_t                  gDAConsoleUserUID               = 0;
66CFArrayRef             gDAConsoleUserList              = NULL;
67CFMutableArrayRef      gDADiskList                     = NULL;
68Boolean                gDAExit                         = FALSE;
69CFMutableArrayRef      gDAFileSystemList               = NULL;
70CFMutableArrayRef      gDAFileSystemProbeList          = NULL;
71Boolean                gDAIdle                         = TRUE;
72io_iterator_t          gDAMediaAppearedNotification    = IO_OBJECT_NULL;
73io_iterator_t          gDAMediaDisappearedNotification = IO_OBJECT_NULL;
74IONotificationPortRef  gDAMediaPort                    = NULL;
75CFMutableArrayRef      gDAMountMapList1                = NULL;
76CFMutableArrayRef      gDAMountMapList2                = NULL;
77CFMutableDictionaryRef gDAPreferenceList               = NULL;
78pid_t                  gDAProcessID                    = 0;
79char *                 gDAProcessName                  = NULL;
80char *                 gDAProcessNameID                = NULL;
81CFMutableArrayRef      gDARequestList                  = NULL;
82CFMutableArrayRef      gDAResponseList                 = NULL;
83CFMutableArrayRef      gDASessionList                  = NULL;
84CFMutableDictionaryRef gDAUnitList                     = NULL;
85
86static void __usage( void )
87{
88    /*
89     * Print usage.
90     */
91
92    fprintf( stderr, "%s: [-d]\n", gDAProcessName );
93    fprintf( stderr, "options:\n" );
94    fprintf( stderr, "\t-d\tenable debugging\n" );
95
96    exit( EX_USAGE );
97}
98
99static Boolean __DAMainCreateMountPointFolder( void )
100{
101    /*
102     * Create the mount point folder in which our mounts will be made.
103     */
104
105    struct stat status;
106    Boolean     success;
107
108    /*
109     * Determine whether the mount point folder exists.
110     */
111
112    if ( stat( kDAMainMountPointFolder, &status ) )
113    {
114        /*
115         * Create the mount point folder.
116         */
117
118        success = ___mkdir( kDAMainMountPointFolder, 01777 ) ? FALSE : TRUE;
119    }
120    else
121    {
122        /*
123         * Determine whether the mount point folder is a folder.
124         */
125
126        success = S_ISDIR( status.st_mode ) ? TRUE : FALSE;
127
128        if ( success )
129        {
130            DIR * folder;
131
132            /*
133             * Correct the mount point folder's contents.
134             */
135
136            folder = opendir( kDAMainMountPointFolder );
137
138            if ( folder )
139            {
140                struct dirent * item;
141
142                while ( ( item = readdir( folder ) ) )
143                {
144                    char path[MAXPATHLEN];
145
146                    if ( item->d_type == DT_DIR )
147                    {
148                        /*
149                         * Determine whether the mount point cookie file exists.
150                         */
151
152                        strlcpy( path, kDAMainMountPointFolder,           sizeof( path ) );
153                        strlcat( path, "/",                               sizeof( path ) );
154                        strlcat( path, item->d_name,                      sizeof( path ) );
155                        strlcat( path, "/",                               sizeof( path ) );
156                        strlcat( path, kDAMainMountPointFolderCookieFile, sizeof( path ) );
157
158                        if ( stat( path, &status ) == 0 )
159                        {
160                            /*
161                             * Remove the mount point cookie file.
162                             */
163
164                            unlink( path );
165                        }
166
167                        /*
168                         * Remove the mount point.
169                         */
170
171                        rmdir( dirname( path ) );
172                    }
173                    else if ( item->d_type == DT_LNK )
174                    {
175                        /*
176                         * Remove the link.
177                         */
178
179                        strlcpy( path, kDAMainMountPointFolder, sizeof( path ) );
180                        strlcat( path, "/",                     sizeof( path ) );
181                        strlcat( path, item->d_name,            sizeof( path ) );
182
183                        unlink( path );
184                    }
185                }
186
187                closedir( folder );
188            }
189        }
190    }
191
192    return success;
193}
194
195static void __DAMainSignal( int sig )
196{
197    /*
198     * Process a SIGTERM signal.
199     */
200
201    gDAExit = TRUE;
202}
203
204static void __DAMain( void )
205{
206    FILE *             file;
207    CFStringRef        key;
208    CFMutableArrayRef  keys;
209    char               path[MAXPATHLEN];
210    mach_port_t        port;
211    CFRunLoopSourceRef source;
212    int                token;
213
214    /*
215     * Initialize classes.
216     */
217
218    DADiskInitialize( );
219
220    DAFileSystemInitialize( );
221
222    DASessionInitialize( );
223
224    /*
225     * Initialize bundle path.
226     */
227
228    gDABundlePath = CFURLCreateWithFileSystemPath( kCFAllocatorDefault, __kDABundlePath, kCFURLPOSIXPathStyle, TRUE );
229
230    assert( gDABundlePath );
231
232    /*
233     * Initialize console user.
234     */
235
236    gDAConsoleUser     = ___SCDynamicStoreCopyConsoleUser( NULL, &gDAConsoleUserUID, &gDAConsoleUserGID );
237    gDAConsoleUserList = ___SCDynamicStoreCopyConsoleInformation( NULL );
238
239    /*
240     * Initialize log.
241     */
242
243    DALogOpen( gDAProcessName, __gDAOptionDebug, TRUE, TRUE );
244
245    /*
246     * Initialize process ID.
247     */
248
249    gDAProcessID = getpid( );
250
251    /*
252     * Initialize process ID tag.
253     */
254
255    asprintf( &gDAProcessNameID, "%s [%d]", gDAProcessName, gDAProcessID );
256
257    assert( gDAProcessNameID );
258
259    /*
260     * Create the disk list.
261     */
262
263    gDADiskList = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks );
264
265    assert( gDADiskList );
266
267    /*
268     * Create the file system list.
269     */
270
271    gDAFileSystemList = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks );
272
273    assert( gDAFileSystemList );
274
275    /*
276     * Create the file system probe list.
277     */
278
279    gDAFileSystemProbeList = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks );
280
281    assert( gDAFileSystemProbeList );
282
283    /*
284     * Create the mount map list.
285     */
286
287    gDAMountMapList1 = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks );
288
289    assert( gDAMountMapList1 );
290
291    /*
292     * Create the mount map list.
293     */
294
295    gDAMountMapList2 = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks );
296
297    assert( gDAMountMapList2 );
298
299    /*
300     * Create the preference list.
301     */
302
303    gDAPreferenceList = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
304
305    assert( gDAPreferenceList );
306
307    /*
308     * Create the request list.
309     */
310
311    gDARequestList = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks );
312
313    assert( gDARequestList );
314
315    /*
316     * Create the response list.
317     */
318
319    gDAResponseList = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks );
320
321    assert( gDAResponseList );
322
323    /*
324     * Create the session list.
325     */
326
327    gDASessionList = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks );
328
329    assert( gDASessionList );
330
331    /*
332     * Create the unit list.
333     */
334
335    gDAUnitList = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
336
337    assert( gDAUnitList );
338
339    /*
340     * Create the Disk Arbitration master run loop source.
341     */
342
343    source = DAServerCreateRunLoopSource( kCFAllocatorDefault, 0 );
344
345    if ( source == NULL )
346    {
347        DALogError( "could not create Disk Arbitration master port." );
348        exit( EX_SOFTWARE );
349    }
350
351    CFRunLoopAddSource( CFRunLoopGetCurrent( ), source, kCFRunLoopDefaultMode );
352
353    CFRelease( source );
354
355    /*
356     * Create the BSD notification run loop source.
357     */
358
359    __gDAVolumeMountedPort = CFMachPortCreate( kCFAllocatorDefault, _DAVolumeMountedCallback, NULL, NULL );
360
361    if ( __gDAVolumeMountedPort == NULL )
362    {
363        DALogError( "could not create BSD notification port." );
364        exit( EX_SOFTWARE );
365    }
366
367    source = CFMachPortCreateRunLoopSource( kCFAllocatorDefault, __gDAVolumeMountedPort, 0 );
368
369    if ( source == NULL )
370    {
371        DALogError( "could not create BSD notification run loop source." );
372        exit( EX_SOFTWARE );
373    }
374
375    CFRunLoopAddSource( CFRunLoopGetCurrent( ), source, kCFRunLoopDefaultMode );
376
377    CFRelease( source );
378
379    /*
380     * Create the BSD notification run loop source.
381     */
382
383    __gDAVolumeUnmountedPort = CFMachPortCreate( kCFAllocatorDefault, _DAVolumeUnmountedCallback, NULL, NULL );
384
385    if ( __gDAVolumeUnmountedPort == NULL )
386    {
387        DALogError( "could not create BSD notification port." );
388        exit( EX_SOFTWARE );
389    }
390
391    source = CFMachPortCreateRunLoopSource( kCFAllocatorDefault, __gDAVolumeUnmountedPort, 0 );
392
393    if ( source == NULL )
394    {
395        DALogError( "could not create BSD notification run loop source." );
396        exit( EX_SOFTWARE );
397    }
398
399    CFRunLoopAddSource( CFRunLoopGetCurrent( ), source, kCFRunLoopDefaultMode );
400
401    CFRelease( source );
402
403    /*
404     * Create the I/O Kit notification run loop source.
405     */
406
407    gDAMediaPort = IONotificationPortCreate( kIOMasterPortDefault );
408
409    if ( gDAMediaPort == NULL )
410    {
411        DALogError( "could not create I/O Kit notification port." );
412        exit( EX_SOFTWARE );
413    }
414
415    source = IONotificationPortGetRunLoopSource( gDAMediaPort ),
416
417    CFRunLoopAddSource( CFRunLoopGetCurrent( ), source, kCFRunLoopDefaultMode );
418
419    /*
420     * Create the System Configuration notification run loop source.
421     */
422
423    __gDAConfigurationPort = SCDynamicStoreCreate( kCFAllocatorDefault, CFSTR( _kDADaemonName ), _DAConfigurationCallback, NULL );
424
425    if ( __gDAConfigurationPort == NULL )
426    {
427        DALogError( "could not create System Configuration notification port." );
428        exit( EX_SOFTWARE );
429    }
430
431    source = SCDynamicStoreCreateRunLoopSource( kCFAllocatorDefault, __gDAConfigurationPort, 0 );
432
433    if ( source == NULL )
434    {
435        DALogError( "could not create System Configuration notification run loop source." );
436        exit( EX_SOFTWARE );
437    }
438
439    CFRunLoopAddSource( CFRunLoopGetCurrent( ), source, kCFRunLoopDefaultMode );
440
441    CFRelease( source );
442
443    /*
444     * Create the file system run loop source.
445     */
446
447    source = DAFileSystemCreateRunLoopSource( kCFAllocatorDefault, 0 );
448
449    if ( source == NULL )
450    {
451        DALogError( "could not create file system run loop source." );
452        exit( EX_SOFTWARE );
453    }
454
455    CFRunLoopAddSource( CFRunLoopGetCurrent( ), source, kCFRunLoopDefaultMode );
456
457    CFRelease( source );
458
459    /*
460     * Create the stage run loop source.
461     */
462
463    source = DAStageCreateRunLoopSource( kCFAllocatorDefault, 0 );
464
465    if ( source == NULL )
466    {
467        DALogError( "could not create stage run loop source." );
468        exit( EX_SOFTWARE );
469    }
470
471    CFRunLoopAddSource( CFRunLoopGetCurrent( ), source, kCFRunLoopDefaultMode );
472
473    CFRelease( source );
474
475    /*
476     * Create the thread run loop source.
477     */
478
479    source = DAThreadCreateRunLoopSource( kCFAllocatorDefault, 0 );
480
481    if ( source == NULL )
482    {
483        DALogError( "could not create thread run loop source." );
484        exit( EX_SOFTWARE );
485    }
486
487    CFRunLoopAddSource( CFRunLoopGetCurrent( ), source, kCFRunLoopDefaultMode );
488
489    CFRelease( source );
490
491    /*
492     * Create the "media disappeared" notification.
493     */
494
495    IOServiceAddMatchingNotification( gDAMediaPort,
496                                      kIOTerminatedNotification,
497                                      IOServiceMatching( kIOMediaClass ),
498                                      _DAMediaDisappearedCallback,
499                                      NULL,
500                                      &gDAMediaDisappearedNotification );
501
502    if ( gDAMediaDisappearedNotification == IO_OBJECT_NULL )
503    {
504        DALogError( "could not create \"media disappeared\" notification." );
505        exit( EX_SOFTWARE );
506    }
507
508    /*
509     * Create the "media appeared" notification.
510     */
511
512    IOServiceAddMatchingNotification( gDAMediaPort,
513                                      kIOMatchedNotification,
514                                      IOServiceMatching( kIOMediaClass ),
515                                      _DAMediaAppearedCallback,
516                                      NULL,
517                                      &gDAMediaAppearedNotification );
518
519    if ( gDAMediaAppearedNotification == IO_OBJECT_NULL )
520    {
521        DALogError( "could not create \"media appeared\" notification." );
522        exit( EX_SOFTWARE );
523    }
524
525    /*
526     * Create the "configuration changed" notification.
527     */
528
529    key  = SCDynamicStoreKeyCreateConsoleUser( kCFAllocatorDefault );
530    keys = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks );
531
532    assert( key  );
533    assert( keys );
534
535    CFArrayAppendValue( keys, key );
536
537    if ( SCDynamicStoreSetNotificationKeys( __gDAConfigurationPort, keys, NULL ) == FALSE )
538    {
539        DALogError( "could not create \"configuration changed\" notification." );
540        exit( EX_SOFTWARE );
541    }
542
543    CFRelease( key  );
544    CFRelease( keys );
545
546    /*
547     * Create the "file system mounted" notification.
548     */
549
550    port = CFMachPortGetPort( __gDAVolumeMountedPort );
551
552    if ( notify_register_mach_port( "com.apple.system.kernel.mount", &port, NOTIFY_REUSE, &token ) )
553    {
554        DALogError( "could not create \"file system mounted\" notification." );
555        exit( EX_SOFTWARE );
556    }
557
558    /*
559     * Create the "file system unmounted" notification.
560     */
561
562    port = CFMachPortGetPort( __gDAVolumeUnmountedPort );
563
564    if ( notify_register_mach_port( "com.apple.system.kernel.unmount", &port, NOTIFY_REUSE, &token ) )
565    {
566        DALogError( "could not create \"file system unmounted\" notification." );
567        exit( EX_SOFTWARE );
568    }
569
570    /*
571     * Create the mount point folder.
572     */
573
574    if ( __DAMainCreateMountPointFolder( ) == FALSE )
575    {
576        DALogError( "could not create mount point folder." );
577        exit( EX_SOFTWARE );
578    }
579
580    /*
581     * Create the process ID file.
582     */
583
584    snprintf( path, sizeof( path ), "/var/run/%s.pid", gDAProcessName );
585
586    file = fopen( path, "w" );
587
588    if ( file )
589    {
590        fprintf( file, "%d\n", gDAProcessID );
591        fclose( file );
592    }
593
594    /*
595     * Announce our arrival in the debug log.
596     */
597
598    DALogDebug( "" );
599    DALogDebug( "server has been started." );
600
601    if ( gDAConsoleUser )
602    {
603        DALogDebug( "  console user = %@ [%d].", gDAConsoleUser, gDAConsoleUserUID );
604    }
605    else
606    {
607        DALogDebug( "  console user = none." );
608    }
609
610    /*
611     * Freshen the file system list.
612     */
613
614    DAFileSystemListRefresh( );
615
616    /*
617     * Freshen the mount map list.
618     */
619
620    DAMountMapListRefresh1( );
621
622    /*
623     * Freshen the mount map list.
624     */
625
626    DAMountMapListRefresh2( );
627
628    /*
629     * Freshen the preference list.
630     */
631
632    DAPreferenceListRefresh( );
633
634    /*
635     * Process the initial set of media objects in I/O Kit.
636     */
637
638    _DAMediaDisappearedCallback( NULL, gDAMediaDisappearedNotification );
639
640    _DAMediaAppearedCallback( NULL, gDAMediaAppearedNotification );
641
642    /*
643     * Start the server.
644     */
645
646    CFRunLoopRun( );
647}
648
649int main( int argc, char * argv[], char * envp[] )
650{
651    /*
652     * Start.
653     */
654
655    char option;
656
657    /*
658     * Initialize.
659     */
660
661    gDAProcessName = basename( argv[0] );
662
663    /*
664     * Check credentials.
665     */
666
667    if ( geteuid( ) )
668    {
669        fprintf( stderr, "%s: permission denied.\n", gDAProcessName );
670
671        exit( EX_NOPERM );
672    }
673
674    /*
675     * Process arguments.
676     */
677
678    while ( ( option = getopt( argc, argv, "d" ) ) != -1 )
679    {
680        switch ( option )
681        {
682            case 'd':
683            {
684                __gDAOptionDebug = TRUE;
685
686                break;
687            }
688            default:
689            {
690                __usage( );
691
692                break;
693            }
694        }
695    }
696
697    /*
698     * Continue to start up.
699     */
700
701    signal( SIGTERM, __DAMainSignal );
702
703    __DAMain( );
704
705    exit( EX_OK );
706}
707