├── .gitignore
├── Base.lproj
├── AttachDiskImageAccessory.xib
├── DiskInfo.xib
├── MainMenu.xib
├── MountOptions.xib
└── Preferences.xib
├── DiskArbitrator.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ ├── Build Disk Arbitrator DMG.xcscheme
│ └── DiskArbitrator.xcscheme
├── Documents
├── App Icon
│ ├── App Icon 16.png
│ ├── App Icon 32.png
│ ├── App Icon.icns
│ └── App Icon.png
├── Disk Image Toolbar
│ └── Disk Image Document.graffle
├── SecPasswordAction
│ ├── AuthorizationTagsPriv.h
│ ├── Readme.rtf
│ ├── SecPassword.cpp
│ └── SecPassword.h
├── Status Item Icons.graffle
└── Toolbar Item Icons.graffle
├── LICENSE
├── README.markdown
├── ReleaseNotes.markdown
├── Resources
├── DiskArbitrator-Info.plist
├── StatusItem Disabled 1.png
├── StatusItem Disabled 2.png
├── StatusItem Green.png
├── StatusItem Orange.png
├── ToolbarItem Attach Disk Image.png
├── ToolbarItem Attach Disk Plug.png
├── ToolbarItem Eject.png
├── ToolbarItem Info.png
└── ToolbarItem Mount.png
├── Source
├── AppError.h
├── AppError.m
├── Arbitrator.h
├── Arbitrator.m
├── AttachDiskImageController.h
├── AttachDiskImageController.m
├── Disk.h
├── Disk.m
├── DiskArbitrationPrivateFunctions.h
├── DiskArbitrationPrivateFunctions.m
├── DiskArbitratorAppController+Toolbar.h
├── DiskArbitratorAppController+Toolbar.m
├── DiskArbitratorAppController.h
├── DiskArbitratorAppController.m
├── DiskArbitrator_Prefix.pch
├── DiskCell.h
├── DiskCell.m
├── DiskInfoController.h
├── DiskInfoController.m
├── SheetController.h
├── SheetController.m
├── build_dmg.sh
└── main.m
└── scripts
└── markdown.pl
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pbxuser
2 | *.perspectivev3
3 | *.mode1v3
4 | .DS_Store
5 | *.log
6 | *.out
7 | *.aux
8 | xcuserdata
9 |
--------------------------------------------------------------------------------
/Base.lproj/AttachDiskImageAccessory.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | readOnly
20 | noOwners
21 | noBrowse
22 | rootPath
23 | password
24 | needPassword
25 | noVerify
26 | attemptMount
27 | filePath
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
69 |
81 |
93 |
104 |
115 |
116 |
117 |
118 | If Path is empty, the OS will create a folder in /Volumes and use that for the mount point for the filesystem, which is the normal action when a disk is connected.
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 | NSAllRomanInputSourcesLocaleIdentifier
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 | NSNegateBoolean
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 | NSNegateBoolean
205 |
206 |
207 |
208 |
209 |
210 | NSIsNotNil
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
253 |
264 |
265 |
266 |
267 |
268 |
269 |
--------------------------------------------------------------------------------
/Base.lproj/DiskInfo.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | Cg
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 | icon
150 | BSDName
151 | diskDescription
152 | mountable
153 | mounted
154 | isWholeDisk
155 | diskDescription.DAMediaName
156 | DAMediaName
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 | DAMediaName
165 | DAVolumeName
166 | diskDescription.DAVolumeName
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
--------------------------------------------------------------------------------
/Base.lproj/MountOptions.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
45 |
56 |
67 |
82 |
83 |
84 |
85 | If Path is empty, the OS will create a folder in /Volumes and use that for the mount point for the filesystem, which is the normal action when a disk is connected.
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
109 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 | readOnly
148 | noOwners
149 | noBrowse
150 | ignoreJournal
151 | path
152 | isHFS
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
--------------------------------------------------------------------------------
/Base.lproj/Preferences.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
36 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/DiskArbitrator.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/DiskArbitrator.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/DiskArbitrator.xcodeproj/xcshareddata/xcschemes/Build Disk Arbitrator DMG.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
45 |
46 |
52 |
53 |
54 |
55 |
63 |
64 |
66 |
67 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/DiskArbitrator.xcodeproj/xcshareddata/xcschemes/DiskArbitrator.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/Documents/App Icon/App Icon 16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aburgh/Disk-Arbitrator/23e687f868ed9e084660b8620801413297546fc5/Documents/App Icon/App Icon 16.png
--------------------------------------------------------------------------------
/Documents/App Icon/App Icon 32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aburgh/Disk-Arbitrator/23e687f868ed9e084660b8620801413297546fc5/Documents/App Icon/App Icon 32.png
--------------------------------------------------------------------------------
/Documents/App Icon/App Icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aburgh/Disk-Arbitrator/23e687f868ed9e084660b8620801413297546fc5/Documents/App Icon/App Icon.icns
--------------------------------------------------------------------------------
/Documents/App Icon/App Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aburgh/Disk-Arbitrator/23e687f868ed9e084660b8620801413297546fc5/Documents/App Icon/App Icon.png
--------------------------------------------------------------------------------
/Documents/SecPasswordAction/AuthorizationTagsPriv.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2003-2004 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 |
24 |
25 | /*
26 | * AuthorizationTagsPriv.h -- private Authorization tags
27 | *
28 | */
29 |
30 | #ifndef _SECURITY_AUTHORIZATIONTAGSPRIV_H_
31 | #define _SECURITY_AUTHORIZATIONTAGSPRIV_H_
32 |
33 | /*!
34 | @header AuthorizationTagsPriv
35 | Version 1.0 10/2003
36 |
37 | This header contains private details for authorization services.
38 | */
39 |
40 |
41 | /* meta-rightname prefixes that configure authorization for policy changes */
42 |
43 | /*!
44 | @defined kConfigRightAdd
45 | meta-rightname for prefix adding rights.
46 | */
47 | #define kAuthorizationConfigRightAdd "config.add."
48 | /*!
49 | @defined kConfigRightModify
50 | meta-rightname prefix for modifying rights.
51 | */
52 | #define kAuthorizationConfigRightModify "config.modify."
53 | /*!
54 | @defined kConfigRightRemove
55 | meta-rightname prefix for removing rights.
56 | */
57 | #define kAuthorizationConfigRightRemove "config.remove."
58 | /*!
59 | @defined kConfigRight
60 | meta-rightname prefix.
61 | */
62 | #define kConfigRight "config."
63 |
64 | /*!
65 | @defined kRuleIsRoot
66 | canned rule for daemon to daemon convincing (see AuthorizationDB.h for public ones)
67 | */
68 | #define kAuthorizationRuleIsRoot "is-root"
69 |
70 | /* rule classes the specify behavior */
71 |
72 | /*! @defined kAuthorizationRuleClass
73 | Specifying rule class
74 | */
75 | #define kAuthorizationRuleClass "class"
76 |
77 | /*! @defined kAuthorizationRuleClassUser
78 | Specifying user class
79 | */
80 | #define kAuthorizationRuleClassUser "user"
81 |
82 | /*! @defined kAuthorizationRuleClassMechanisms
83 | Specifying evaluate-mechanisms class
84 | */
85 | #define kAuthorizationRuleClassMechanisms "evaluate-mechanisms"
86 |
87 | /* rule attributes to specify above classes */
88 |
89 | /*! @defined kAuthorizationRuleParameterGroup
90 | string, group specification for user rules.
91 | */
92 | #define kAuthorizationRuleParameterGroup "group"
93 |
94 | /*! @defined kAuthorizationRuleParameterKofN
95 | number, k specification for k-of-n
96 | */
97 | #define kAuthorizationRuleParameterKofN "k-of-n"
98 |
99 | /*! @defined kAuthorizationRuleParameterRules
100 | rules specification for rule delegation (incl. k-of-n)
101 | */
102 | #define kAuthorizationRuleParameterRules "rules"
103 |
104 | /*! @defined kAuthorizationRuleParameterMechanisms
105 | mechanism specification, a sequence of mechanisms to be evaluated */
106 | #define kAuthorizationRuleParameterMechanisms "mechanisms"
107 |
108 | /*! @defined kAuthorizationRightParameterTimeout
109 | timeout if any when a remembered right expires.
110 | special values:
111 | - not specified retains previous behavior: most privileged, credential based.
112 | - zero grants the right once
113 | (can be achieved with zero credential timeout, needed?)
114 | - all other values are interpreted as number of seconds since granted.
115 | */
116 | #define kAuthorizationRightParameterTimeout "timeout-right"
117 |
118 | /*! @defined kAuthorizationRuleParameterCredentialTimeout
119 | timeout if any for the use of cached credentials when authorizing rights.
120 | - not specified allows for any credentials regardless of age; rights will be remembered in authorizations, removing a credential does not stop it from granting this right, specifying a zero timeout for the right will delegate it back to requiring a credential.
121 | - all other values are interpreted as number of seconds since the credential was created
122 | - zero only allows for the use of credentials created "now" // This is deprecated by means of specifying zero for kRightTimeout
123 | */
124 | #define kAuthorizationRuleParameterCredentialTimeout "timeout"
125 |
126 | /*! @defined kAuthorizationRuleParameterCredentialShared
127 | boolean that indicates whether credentials acquired during authorization are added to the shared pool.
128 | */
129 | #define kAuthorizationRuleParameterCredentialShared "shared"
130 |
131 | /*! @defined kAuthorizationRuleParameterAllowRoot
132 | boolean that indicates whether to grant a right purely because the caller is root */
133 | #define kAuthorizationRuleParameterAllowRoot "allow-root"
134 |
135 | /*! @defined kAuthorizationRuleParameterCredentialSessionOwner
136 | boolean that indicates whether to grant a right based on a valid session-owner credential */
137 | #define kAuthorizationRuleParameterCredentialSessionOwner "session-owner"
138 |
139 | /*! @defined kRuleDefaultPrompt
140 | dictionary of localization-name and localized prompt pairs */
141 | #define kAuthorizationRuleParameterDefaultPrompt "default-prompt"
142 |
143 | /*! @defined kAuthorizationRuleParameterDescription
144 | string, default description of right. Usually localized versions are added using the
145 | AuthorizationDBSet call (@see AuthorizationDB.h). */
146 | #define kAuthorizationRuleParameterDescription "description"
147 |
148 | /*! @defined kAuthorizationRuleParameterAuthenticateUser
149 | boolean that indicates whether to authenticate the user requesting authorization */
150 | #define kAuthorizationRuleParameterAuthenticateUser "authenticate-user"
151 |
152 | /* authorization hints passed between securityd and agent */
153 | #define AGENT_HINT_SUGGESTED_USER "suggested-user"
154 | #define AGENT_HINT_SUGGESTED_USER_LONG "suggested-realname"
155 | #define AGENT_HINT_REQUIRE_USER_IN_GROUP "require-user-in-group"
156 | #define AGENT_HINT_CUSTOM_PROMPT "prompt"
157 | #define AGENT_HINT_AUTHORIZE_RIGHT "authorize-right"
158 | #define AGENT_HINT_CLIENT_PID "client-pid"
159 | #define AGENT_HINT_CLIENT_UID "client-uid"
160 | #define AGENT_HINT_CLIENT_VALIDITY "client-signature-validity"
161 | #define AGENT_HINT_CREATOR_PID "creator-pid"
162 | #define AGENT_HINT_CLIENT_TYPE "client-type"
163 | #define AGENT_HINT_CLIENT_PATH "client-path"
164 | #define AGENT_HINT_CLIENT_NAME "client-name"
165 | #define AGENT_HINT_TRIES "tries"
166 | #define AGENT_HINT_RETRY_REASON "reason"
167 | #define AGENT_HINT_AUTHORIZE_RULE "authorize-rule"
168 | #define AGENT_HINT_TOKEN_NAME "token-name"
169 |
170 | /* passed from mechanisms to loginwindow */
171 | #define kAuthorizationEnvironmentTokenSubserviceID "token-subservice-uid"
172 |
173 | // remote home directory specification
174 | #define AGENT_CONTEXT_AFP_DIR "afp_dir"
175 | // home directory (where it's locally mounted)
176 | #define AGENT_CONTEXT_HOME "home"
177 | #define AGENT_CONTEXT_UID "uid"
178 | #define AGENT_CONTEXT_GID "gid"
179 | // kerberos principal; decoded from auth-authority specification
180 | #define AGENT_CONTEXT_KERBEROSPRINCIPAL "kerberos-principal"
181 | // tell loginwindow where we're mounted
182 | // (this should really be equal to our homedirectory according to HOME
183 | #define AGENT_CONTEXT_MOUNTPOINT "mountpoint"
184 |
185 | /* authorization context passed from agent to securityd */
186 | #define AGENT_USERNAME "username"
187 | #define AGENT_PASSWORD "password"
188 | #define AGENT_CONTEXT_NEW_PASSWORD "new-password"
189 |
190 | #define AGENT_HINT_SHOW_ADD_TO_KEYCHAIN "show-add-to-keychain"
191 | #define AGENT_ADD_TO_KEYCHAIN "add-to-keychain"
192 |
193 | #define AGENT_CONTEXT_AUTHENTICATION_FAILURE "authentication-failure"
194 |
195 | /* keychain panels */
196 | // ACLowner etc. code identity panel
197 |
198 | // Application Path is needed at this stage for identifying the application
199 | // for which the ACL entry is about to be updated
200 | #define AGENT_HINT_APPLICATION_PATH "application-path"
201 | #define AGENT_HINT_ACL_TAG "acl-tag"
202 | #define AGENT_HINT_GROUPKEY "group-key"
203 | #define AGENT_HINT_ACL_MISMATCH "acl-mismatch"
204 | #define AGENT_HINT_KEYCHAIN_ITEM_NAME "keychain-item-name"
205 | #define AGENT_HINT_KEYCHAIN_PATH "keychain-path"
206 | #define AGENT_HINT_WINDOW_LEVEL "window-level"
207 |
208 | #define AGENT_CONTEXT_REMEMBER_ACTION "remember-action"
209 | #define AGENT_CONTEXT_ALLOW "allow"
210 |
211 | /* Login Keychain Creation hint and context keys */
212 |
213 | #define AGENT_HINT_ATTR_NAME "loginKCCreate:attributeName"
214 | #define AGENT_HINT_LOGIN_KC_NAME "loginKCCreate:pathName"
215 | #define AGENT_HINT_LOGIN_KC_EXISTS_IN_KC_FOLDER "loginKCCreate:exists"
216 | #define AGENT_HINT_LOGIN_KC_USER_NAME "loginKCCreate:userName"
217 | #define AGENT_HINT_LOGIN_KC_CUST_STR1 "loginKCCreate:customStr1"
218 | #define AGENT_HINT_LOGIN_KC_CUST_STR2 "loginKCCreate:customStr2"
219 | #define AGENT_HINT_LOGIN_KC_USER_HAS_OTHER_KCS_STR "loginKCCreate:moreThanOneKeychainExists"
220 |
221 | /*! @defined LOGIN_KC_CREATION_RIGHT
222 | the right used to invoke the right mechanisms to (re)create a login
223 | keychain */
224 | #define LOGIN_KC_CREATION_RIGHT "system.keychain.create.loginkc"
225 |
226 | /* Keychain synchronization */
227 | // iDisk keychain blob metainfo dictionary; follows "defaults" naming
228 | #define AGENT_HINT_KCSYNC_DICT "com.apple.keychainsync.dictionary"
229 |
230 | #endif /* !_SECURITY_AUTHORIZATIONTAGSPRIV_H_ */
231 |
--------------------------------------------------------------------------------
/Documents/SecPasswordAction/Readme.rtf:
--------------------------------------------------------------------------------
1 | {\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf250
2 | {\fonttbl\f0\fswiss\fcharset0 Helvetica;}
3 | {\colortbl;\red255\green255\blue255;}
4 | {\info
5 | {\author Aaron Burghardt}}\margl1440\margr1440\vieww16860\viewh14820\viewkind0
6 | \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural
7 |
8 | \f0\fs24 \cf0 SecPasswordAction() is an private function in Security.framework. The source code was downloaded from:\
9 | \
10 | http://www.opensource.apple.com/source/libsecurity_keychain/libsecurity_keychain-26098/lib/SecPassword.cpp\
11 | http://www.opensource.apple.com/source/libsecurity_authorization/libsecurity_authorization-36329/lib/AuthorizationTagsPriv.h\
12 | \
13 | It is used by the DiskImages framework to prompt for the password of an encrypted disk image and is saved here as an example.\
14 | }
--------------------------------------------------------------------------------
/Documents/SecPasswordAction/SecPassword.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2000-2004 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 |
24 | #include "SecPassword.h"
25 | #include "Password.h"
26 |
27 | #include "SecBridge.h"
28 |
29 | #include "KCExceptions.h"
30 | #include
31 | #include
32 |
33 | CFTypeID
34 | SecPasswordGetTypeID(void)
35 | {
36 | BEGIN_SECAPI
37 |
38 | secdebug("passwditem", "SecPasswordGetTypeID()");
39 | return gTypes().PasswordImpl.typeID;
40 |
41 | END_SECAPI1(_kCFRuntimeNotATypeID)
42 | }
43 |
44 | OSStatus
45 | SecGenericPasswordCreate(SecKeychainAttributeList *searchAttrList, SecKeychainAttributeList *itemAttrList, SecPasswordRef *itemRef)
46 | {
47 | BEGIN_SECAPI
48 | KCThrowParamErrIf_( (itemRef == NULL) );
49 | KCThrowParamErrIf_( (searchAttrList == NULL) ^ (itemAttrList == NULL) ); // Both or neither
50 | secdebug("passworditem", "SecPasswordCreate(%p, %p)", searchAttrList, itemAttrList);
51 |
52 | Password passwordItem(kSecGenericPasswordItemClass, searchAttrList, itemAttrList);
53 | if (itemRef)
54 | *itemRef = passwordItem->handle();
55 |
56 | END_SECAPI
57 | }
58 |
59 |
60 | OSStatus
61 | SecPasswordAction(SecPasswordRef itemRef, CFTypeRef message, UInt32 flags, UInt32 *length, const void **data)
62 | {
63 | BEGIN_SECAPI
64 |
65 | Password passwordRef = PasswordImpl::required(itemRef);
66 |
67 | void *passwordData = NULL;
68 | uint32_t passwordLength = 0;
69 |
70 | // no flags has no meaning, and there is no apparent default
71 | assert( flags );
72 |
73 | // fail can only be combined with get or new
74 | assert( (flags & kSecPasswordFail) ? ((flags & kSecPasswordGet) || (flags & kSecPasswordNew)) : true );
75 |
76 | // XXX/cs replace this with our CFString->UTF8 conversion
77 | const char *messageData = NULL;
78 | auto_array messageBuffer;
79 |
80 | if (message && (CFStringGetTypeID() == CFGetTypeID(message)))
81 | {
82 | messageData = CFStringGetCStringPtr(static_cast(message), kCFStringEncodingUTF8);
83 |
84 | if (messageData == NULL)
85 | {
86 | CFIndex maxLen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(static_cast(message)), kCFStringEncodingUTF8) + 1;
87 |
88 | messageBuffer.allocate(maxLen);
89 | if (CFStringGetCString(static_cast(message), messageBuffer.get(), maxLen, kCFStringEncodingUTF8))
90 | messageData = messageBuffer.get();
91 | }
92 | }
93 |
94 | secdebug("SecPassword", "SecPasswordGet(%p, %p)", length, data);
95 |
96 | if (passwordRef->useKeychain() && !(flags & kSecPasswordNew) && !(flags & kSecPasswordFail))
97 | {
98 | // Pull out data and if it's successful return it
99 | if (flags & kSecPasswordGet)
100 | {
101 |
102 | // XXX/cs if there are unsaved changes this doesn't work
103 | // so doing a Get followed by a Get|Set will do the wrong thing
104 |
105 | // check mItem whether it's got data
106 | if (passwordRef->getData(length, data))
107 | return noErr;
108 | }
109 |
110 | // User might cancel here, immediately return that too (it will be thrown)
111 | }
112 |
113 | // If we're still here we're not using the keychain or it wasn't there yet
114 |
115 | // Do the authorization call to get the password, unless only kSecPasswordSet is specified)
116 | if ((flags & kSecPasswordNew) || (flags & kSecPasswordGet))
117 | {
118 | AuthorizationRef authRef;
119 | OSStatus status = AuthorizationCreate(NULL,NULL,0,&authRef);
120 | AuthorizationItem right = { NULL, 0, NULL, 0 };
121 | AuthorizationItemSet rightSet = { 1, &right };
122 | uint32_t reason, tries;
123 | bool keychain, addToKeychain;
124 |
125 | if (passwordRef->useKeychain())
126 | {
127 | keychain = 1;
128 | addToKeychain = 1;
129 | }
130 |
131 | // Get|Fail conceivable would have it enabled, but since the effect is that it will get overwritten
132 | // we'll make the user explicitly do it
133 | if (flags & kSecPasswordNew)
134 | addToKeychain = 1; // turn it on for new items
135 | else
136 | if (flags & kSecPasswordGet)
137 | addToKeychain = 0; // turn it off for old items that weren't successfully retrieved from the keychain
138 |
139 | if (flags & kSecPasswordFail) // set up retry to reflect failure
140 | {
141 | if (flags & kSecPasswordNew)
142 | reason = 34; // passphraseUnacceptable = 34 passphrase unacceptable for some other reason
143 | else
144 | reason = 21; // invalidPassphrase = 21 passphrase was wrong
145 | }
146 | else
147 | reason = 0;
148 |
149 | if (flags & kSecPasswordNew) // pick new passphrase
150 | right.name = "com.apple.builtin.generic-new-passphrase";
151 | else
152 | right.name = "com.apple.builtin.generic-unlock";
153 |
154 | AuthorizationItem envRights[5] = { { AGENT_HINT_RETRY_REASON, sizeof(reason), &reason, 0 },
155 | { AGENT_HINT_TRIES, sizeof(tries), &tries, 0 },
156 | { AGENT_HINT_CUSTOM_PROMPT, messageData ? strlen(messageData) : 0, const_cast(messageData), 0 },
157 | { AGENT_HINT_SHOW_ADD_TO_KEYCHAIN, keychain ? strlen("YES") : strlen("NO"), const_cast(keychain ? "YES" : "NO"), 0 },
158 | { AGENT_ADD_TO_KEYCHAIN, addToKeychain ? strlen("YES") : strlen("NO"), const_cast(addToKeychain ? "YES" : "NO"), 0 } };
159 |
160 | AuthorizationItemSet envSet = { sizeof(envRights) / sizeof(*envRights), envRights };
161 |
162 | status = AuthorizationCopyRights(authRef, &rightSet, &envSet, kAuthorizationFlagDefaults|kAuthorizationFlagInteractionAllowed|kAuthorizationFlagExtendRights, NULL);
163 |
164 | if (status)
165 | {
166 | AuthorizationFree(authRef, 0);
167 | return errAuthorizationCanceled;
168 | }
169 |
170 | // if success pull the data
171 | AuthorizationItemSet *returnedInfo;
172 | status = AuthorizationCopyInfo(authRef, NULL, &returnedInfo);
173 |
174 | if (status)
175 | {
176 | AuthorizationFree(authRef, 0);
177 |
178 | return status;
179 | }
180 |
181 | if (returnedInfo && (returnedInfo->count > 0))
182 | {
183 | for (uint32_t index = 0; index < returnedInfo->count; index++)
184 | {
185 | AuthorizationItem &item = returnedInfo->items[index];
186 |
187 | if (!strcmp(AGENT_PASSWORD, item.name))
188 | {
189 | passwordLength = item.valueLength;
190 |
191 | if (passwordLength)
192 | {
193 | Allocator &allocator = Allocator::standard();
194 | passwordData = allocator.malloc(passwordLength);
195 | if (passwordData)
196 | memcpy(passwordData, item.value, passwordLength);
197 | }
198 |
199 | if (length)
200 | *length = passwordLength;
201 | if (data)
202 | *data = passwordData;
203 | }
204 | else if (!strcmp(AGENT_ADD_TO_KEYCHAIN, item.name))
205 | {
206 | if (item.value && item.valueLength == strlen("YES") && !memcmp("YES", static_cast(item.value), item.valueLength))
207 | passwordRef->setRememberInKeychain(true);
208 | else
209 | passwordRef->setRememberInKeychain(false);
210 | }
211 | }
212 | }
213 |
214 | AuthorizationFreeItemSet(returnedInfo);
215 | AuthorizationFree(authRef, 0);
216 |
217 | }
218 |
219 | // If we're still here the use gave us his password, store it if keychain is in use
220 | if (passwordRef->useKeychain())
221 | {
222 | if (passwordRef->rememberInKeychain() && passwordLength && passwordData)
223 | passwordRef->setData(passwordLength, passwordData);
224 |
225 | if ((flags & kSecPasswordSet) && passwordRef->rememberInKeychain())
226 | passwordRef->save();
227 | }
228 |
229 | END_SECAPI
230 | }
231 |
--------------------------------------------------------------------------------
/Documents/SecPasswordAction/SecPassword.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2000-2004 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 |
24 | /*!
25 | @header SecPassword
26 | SecPassword implements logic to use the system facilities for acquiring a password,
27 | optionally stored and retrieved from the user's keychain.
28 | */
29 |
30 | #include
31 | #include
32 | #include
33 |
34 | #ifndef _SECURITY_SECPASSWORD_H_
35 | #define _SECURITY_SECPASSWORD_H_
36 |
37 | #if defined(__cplusplus)
38 | extern "C" {
39 | #endif
40 |
41 | /*!
42 | @abstract Flags to specify SecPasswordAction behavior, as the application steps through the options
43 | Get, just get it.
44 | Get|Set, get it and set it if it wasn't in the keychain; client doesn't verify it before it's stored
45 | Get|Fail, get it and flag that the previously given or stored password is busted.
46 | Get|Set|Fail, same as above but also store it.
47 | New instead of Get toggles between asking for a new passphrase and an existing one.
48 | */
49 | enum {
50 | kSecPasswordGet = 1<<0, // Get password from keychain or user
51 | kSecPasswordSet = 1<<1, // Set password (passed in if kSecPasswordGet not set, otherwise from user)
52 | kSecPasswordFail = 1<<2, // Wrong password (ignore item in keychain and flag error)
53 | kSecPasswordNew = 1<<3 // Explicitly get a new passphrase
54 | };
55 |
56 | /*!
57 | @abstract Create an SecPassword object used to query and set a password used in the client.
58 | Keychain item attributes and class are optional, pass NULL if the password shouldn't be searched
59 | for or stored in the keychain. Use CFRelease on
60 | @param itemClass (in/opt) class of item to search for or store password as.
61 | @param attrList (in/opt) the list of attributes of the item to search or create.
62 | @param passwordRef (out) On return, a pointer to a password reference. Release this by calling CFRelease function.
63 | */
64 | OSStatus SecGenericPasswordCreate(SecKeychainAttributeList *searchAttrList, SecKeychainAttributeList *itemAttrList, SecPasswordRef *itemRef);
65 |
66 | /*!
67 | @abstract Get the password for a SecPassword, either from the user or the keychain and return it.
68 | Use SecKeychainItemFreeContent to free the data.
69 |
70 | @param message Message to display to the user as a CFString or nil for a default message.
71 | (future extension accepts CFDictionary for other hints, icon, secaccess)
72 | @param flags (in) The mode of operation. See
73 | @param length (out) The length of the buffer pointed to by data.
74 | @param data A pointer to a buffer containing the data to store.
75 |
76 | */
77 | OSStatus SecPasswordAction(SecPasswordRef itemRef, CFTypeRef message, UInt32 flags, UInt32 *length, const void **data);
78 |
79 | #if defined(__cplusplus)
80 | }
81 | #endif
82 |
83 | #endif /* !_SECURITY_SECPASSWORD_H_ */
84 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2010, Aaron Burghardt
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 |
6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7 |
8 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9 |
10 | * Neither the name Aaron Burghardt nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11 |
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | ## Introduction
2 |
3 | Disk Arbitrator is a Mac OS X forensic utility designed to help the user ensure correct forensic procedures are followed during imaging of a disk device.
4 | Disk Arbitrator is essentially a user interface to the Disk Arbitration framework, which enables a program to participate in the management of block
5 | storage devices, including the automatic mounting of file systems. When enabled, Disk Arbitrator will block the mounting of file systems to avoid mounting as read-write and violating the integrity of the evidence.
6 |
7 | It is important to note that Disk Arbitrator is *not* a software write blocker---it does not change the state of currently attached devices nor does it affect newly attached devices to force a device to be read-only. The user still must be careful to not accidentally write to a disk with a command such as "dd". Owing to this fact, a hardware or software write-blocker may still be desirable for the most sound procedure. Disk Arbitrator compliments a write-blocker with additional useful features and eliminates the typical forensic recommendation to "disable disk arbitration."
8 |
9 | ## System Requirements
10 |
11 | * Intel Mac
12 | * OS X 10.5 or later
13 |
14 | ## Downloads
15 |
16 | You can find links to compiled executables on the [releases](https://github.com/aburgh/Disk-Arbitrator/releases) page.
17 |
18 | ## Quick Start
19 |
20 | ### Installation
21 |
22 | To install, drag the Disk Arbitrator application to the desired location, for example /Applications.
23 |
24 | You may optionally want to have Disk Arbitrator automatically running every time you log in. There are two ways to do this:
25 |
26 | * Add Disk Arbitrator to your Login Items in the User & Groups (or Accounts on older OS X versions) preference panel in System Preferences.
27 |
28 | * Use the included "Install User Launch Agent" feature (accessible from the menu). When installed, the system's launchd will automatically launch Disk Arbitrator when you log in, just like a Login Item. In addition, the plist contains a setting which instructs launchd to monitor the application and automatically relaunch it in the event of a crash or if otherwise quit. This offers the most assurance that Disk Arbitrator will be running whenever you are logged in.
29 |
30 | ### Usage
31 |
32 | When launched, it adds its icon to the status bar on the right side of the menu bar. The status bar icon indicates one of three states:
33 |
34 | * Green: the utility is activated and in Block Mounts mode.
35 |
36 | * Orange: the utility is activated and in Read-only mode.
37 |
38 | * Gray: the utility is deactivated and attached disks will be automatically mounted by the system.
39 |
40 | Disk Arbitrator continuously monitors for disks to appear and disappear and tracks the disks in the main window. When a new disk is attached, the system notifies Disk Arbitrator and gives it a chance to reject mounting of a disk volume. Disk Arbitrator responds as such:
41 |
42 | * When deactivated, it just observes the disk changes
43 |
44 | * When activated and in Block Mounts mode, it simply rejects every new system attempt to mount a volume.
45 |
46 | * When activated and in Read-only mode, it rejects the original mount action and automatically sends its own request to mount the volume, but it ensures the mount includes the option to make the file system read-only. It also checks the file system type and, if it is HFS, it includes the flag to ignore the journal.
47 |
48 | **Reminder:** Disk Arbitrator does its work by actively participating in the mounting process. If the utility is deactivated or is quit and not running, there is no protection from auto-mounting attached disks. However, once a disk appears and the process of either mounting or rejecting the mount is finished, then Disk Arbitrator may be quit without affecting the state of the disk.
49 |
50 | ### Working With Disk Images
51 |
52 | As of version 0.3.0, Disk Arbitrator has support for disk images.
53 |
54 | Using Disk Arbitrator's Attach Disk Image feature, attaching the disk image is effectively coordinated. When "Attach Disk Image..." is selected from the menu, an open panel appears which includes additional options for attaching and mounting the disk image. The default is to only attach the disk image. When the "Open" button is clicked, Disk Arbitrator attaches the disk using hdiutil. Once it is attached, Disk Arbitrator's normal behavior applies, so if it is activated and the mode is set to Read-Only, the volumes on the disk image will be mounted read-only. If the mode is set to Block Mounts, the volumes will be ignored.
55 |
56 | The disk image open panel includes an option to attempt to mount the disk image for when Disk Arbitrator isn't activated and is being used as a convenient means to attach a disk image. When the mount option is used, Disk Arbitrator passes "-mount optional" to hdiutil, which attempts to attach and mount the disk image, but with the benefit that if the mount fails, the disk image is not detached.
57 |
58 | Disk Arbitrator also supports drag and drop to attach a disk image. Simply drag one or more disk images from a Finder window to Disk Arbitrator's list of disks to initiate attaching the disk images. When using drag and drop, the default options include "-mount optional", so mounting is also attempted. If the mount fails, the disk image remains attached and can be mounted manually.
59 |
60 | Notes:
61 |
62 | * Attempting to attach and mount a disk image outside of Disk Arbitrator, either by double-clicking it in the Finder or using hdiutil attach, will behave as before: the disk image will be attached, then the system will attempt to mount it, Disk Arbitrator will reject the mount, and the disk image will be unattached because the mount failed.
63 |
64 | * If a disk image with a Software License Agreement is attached, Disk Arbitrator automatically replies "Yes" to the agreement. Caveat Emptor.
65 |
66 | ### A Note On Dirty Journals
67 |
68 | When set to Read-only mode, the mount request that Disk Arbitrator sends includes the option to ignore the journal, which is useful when the HFS file system was last detached without unmounting (e.g., the system crashed, or an external drive was unplugged without ejecting it). If you are working with a disk that was not cleanly ejected, then attempts to attach it read-only will normally fail because HFS knows the journal needs to be replayed and it is not allowed to make the changes, so it fails to mount.
69 |
70 | Mounting a disk image with a dirty file system can be achieved by using a shadow file, but this is less than ideal. The shadow file protects the original disk image from changes, but the file system is mounted read-write. A better option is to execute the two steps manually, using Terminal:
71 |
72 | 1. `hdiutil attach -nomount disk_image.dmg`
73 | 2. `mount_hfs -j -o rdonly /dev/diskx /mount/path`
74 |
75 | Disk Arbitrator provides a convenient way to execute the second step, and a future version will perform both steps in one operation.
76 |
--------------------------------------------------------------------------------
/ReleaseNotes.markdown:
--------------------------------------------------------------------------------
1 | ## Release Notes
2 |
3 | #### 0.8 2017/9/16
4 |
5 | * Added "Show main window at activation" preference (thanks melomac)
6 | * Fix memory leaks
7 |
8 | #### 0.7 2016/8/3
9 |
10 | * Fix order of disks
11 | * Fix crash during eject
12 |
13 | #### 0.6 2016/4/21
14 |
15 | * Fix read-only automatic mounts not working on OS X 10.11+
16 | * Disable the "ignore journal" mount option for non-HFS disks
17 |
18 | #### 0.5 2015/9/9
19 |
20 | * Added lock icon to disks that are read-only
21 | * Fixed launchd plist containing hard coded app path
22 | * Improved compatibility with 10.10
23 | * Improved code stability
24 |
25 | #### 0.4.2 2012/11/23
26 |
27 | * Added Developer ID signature for Gatekeeper.
28 | * Updated for Xcode 4.5 (should have no affect on behavior).
29 | * Removed PPC architecture.
30 |
31 | #### 0.4.1 2012/4/8
32 |
33 | * Added feature to install/uninstall a launchd agent plist.
34 | * Added labels and tool tips to make mount options easier to understand.
35 |
36 | #### 0.4.0 2011/4/20
37 |
38 | * Fixed overwriting AppLogLevel preference at startup.
39 | * Added log message for mount approval callback.
40 | * Activate application when showing the main window, Preferences, or About panel.
41 | * Changed Attach Disk Image icon.
42 | * Changed mode and activation state to persist across application restart.
43 | * Enabled Sudden Termination (new in Snow Leopard for faster shutdowns).
44 |
45 | #### 0.3.2 2010/8/23
46 |
47 | * Fixed use of Snow Leopard API to retrieve disk icons.
48 | * Minor code cleanup.
49 |
50 | #### 0.3.1 2010/8/9
51 |
52 | * Added Preferences window and an option to show the main window at launch.
53 |
54 | #### 0.3.0 2010/3/29
55 |
56 | * Added Attach Disk Image feature.
57 | * Added check for encrypted disk image.
58 | * Added support for attaching a disk image by dragging the file into the disks window.
59 | * Added a progress window while a disk image is verified.
60 | * Added disk menu to the status item menu.
61 | * Improved validation of toolbar items.
62 |
63 | #### 0.2.0 2010/2/14
64 |
65 | * Added Disk Info window.
66 | * Added mounting feature with dialog for options and mountpoint path.
67 | * Added unmounting and ejecting.
68 | * Added toolbar and custom icons.
69 | * Improved logging.
70 |
71 | #### 0.1.1 2010/2/7
72 |
73 | * Added target to build the distribution DMG.
74 | * Fixed refresh bug when a disk is mounted or unmounted.
75 | * Added controls to the main window to activate and change mode.
76 |
77 | #### 0.1.0 2010/1/31
78 |
79 | * Initial release
80 |
--------------------------------------------------------------------------------
/Resources/DiskArbitrator-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIconFile
10 | App Icon.icns
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | ${PRODUCT_NAME}
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 0.8
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | LSMinimumSystemVersion
26 | ${MACOSX_DEPLOYMENT_TARGET}
27 | LSMinimumSystemVersionByArchitecture
28 |
29 | x86_64
30 | 10.6
31 |
32 | LSUIElement
33 |
34 | NSMainNibFile
35 | MainMenu
36 | NSPrincipalClass
37 | NSApplication
38 | NSSupportsSuddenTermination
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/Resources/StatusItem Disabled 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aburgh/Disk-Arbitrator/23e687f868ed9e084660b8620801413297546fc5/Resources/StatusItem Disabled 1.png
--------------------------------------------------------------------------------
/Resources/StatusItem Disabled 2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aburgh/Disk-Arbitrator/23e687f868ed9e084660b8620801413297546fc5/Resources/StatusItem Disabled 2.png
--------------------------------------------------------------------------------
/Resources/StatusItem Green.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aburgh/Disk-Arbitrator/23e687f868ed9e084660b8620801413297546fc5/Resources/StatusItem Green.png
--------------------------------------------------------------------------------
/Resources/StatusItem Orange.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aburgh/Disk-Arbitrator/23e687f868ed9e084660b8620801413297546fc5/Resources/StatusItem Orange.png
--------------------------------------------------------------------------------
/Resources/ToolbarItem Attach Disk Image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aburgh/Disk-Arbitrator/23e687f868ed9e084660b8620801413297546fc5/Resources/ToolbarItem Attach Disk Image.png
--------------------------------------------------------------------------------
/Resources/ToolbarItem Attach Disk Plug.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aburgh/Disk-Arbitrator/23e687f868ed9e084660b8620801413297546fc5/Resources/ToolbarItem Attach Disk Plug.png
--------------------------------------------------------------------------------
/Resources/ToolbarItem Eject.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aburgh/Disk-Arbitrator/23e687f868ed9e084660b8620801413297546fc5/Resources/ToolbarItem Eject.png
--------------------------------------------------------------------------------
/Resources/ToolbarItem Info.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aburgh/Disk-Arbitrator/23e687f868ed9e084660b8620801413297546fc5/Resources/ToolbarItem Info.png
--------------------------------------------------------------------------------
/Resources/ToolbarItem Mount.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aburgh/Disk-Arbitrator/23e687f868ed9e084660b8620801413297546fc5/Resources/ToolbarItem Mount.png
--------------------------------------------------------------------------------
/Source/AppError.h:
--------------------------------------------------------------------------------
1 | /*
2 | * AppError.h
3 | * DiskArbitrator
4 | *
5 | * Created by Aaron Burghardt on 1/24/10.
6 | * Copyright 2010 Aaron Burghardt. All rights reserved.
7 | *
8 | */
9 |
10 | /*
11 | Log levels:
12 |
13 | LOG_EMERG A panic condition. This is normally broadcast to all users.
14 |
15 | LOG_ALERT A condition that should be corrected immediately, such as a corrupted system database.
16 |
17 | LOG_CRIT Critical conditions, e.g., hard device errors.
18 |
19 | LOG_ERR Errors.
20 |
21 | LOG_WARNING Warning messages.
22 |
23 | LOG_NOTICE Conditions that are not error conditions, but should possibly be handled specially.
24 |
25 | LOG_INFO Informational messages.
26 |
27 | LOG_DEBUG Messages that contain information normally of use only when debugging a program.
28 |
29 | */
30 |
31 | #include
32 |
33 | void Log(NSInteger level, NSString *format, ...);
34 |
35 | void SetAppLogLevel(NSInteger level);
36 | void SetShouldLogToSyslog(BOOL flag);
37 |
38 | extern NSString * const AppErrorDomain;
39 | extern NSString * const AppLogLevelDefaultsKey;
40 | extern NSString * const AppShouldEnableSyslogDefaultsKey;
--------------------------------------------------------------------------------
/Source/AppError.m:
--------------------------------------------------------------------------------
1 | /*
2 | * AppError.m
3 | * DiskArbitrator
4 | *
5 | * Created by Aaron Burghardt on 1/24/10.
6 | * Copyright 2010 Aaron Burghardt. All rights reserved.
7 | *
8 | */
9 |
10 | #import "AppError.h"
11 | #include
12 | #include
13 |
14 | void Log(NSInteger level, NSString *format, ...)
15 | {
16 | va_list args;
17 | NSString *formattedError;
18 |
19 | if (level > [[NSUserDefaults standardUserDefaults] integerForKey:AppLogLevelDefaultsKey])
20 | return;
21 |
22 | va_start(args, format);
23 |
24 | formattedError = [[NSString alloc] initWithFormat:format arguments:args];
25 |
26 | va_end(args);
27 |
28 | const char *utfFormattedError = [formattedError UTF8String];
29 |
30 | BOOL shouldUseSyslog = [[NSUserDefaults standardUserDefaults] boolForKey:AppShouldEnableSyslogDefaultsKey];
31 |
32 | if (shouldUseSyslog)
33 | syslog((int)level, "%s\n", utfFormattedError);
34 |
35 | os_log(OS_LOG_DEFAULT, "%s\n", utfFormattedError);
36 | }
37 |
38 | void SetAppLogLevel(NSInteger level)
39 | {
40 | [[NSUserDefaults standardUserDefaults] setInteger:level forKey:AppLogLevelDefaultsKey];
41 | }
42 |
43 | void SetShouldLogToSyslog(BOOL flag)
44 | {
45 | [[NSUserDefaults standardUserDefaults] setBool:flag forKey:AppShouldEnableSyslogDefaultsKey];
46 | }
47 |
48 | NSString * const AppErrorDomain = @"AppErrorDomain";
49 | NSString * const AppLogLevelDefaultsKey = @"AppLogLevel";
50 | NSString * const AppShouldEnableSyslogDefaultsKey = @"AppShouldEnableSyslog";
51 |
--------------------------------------------------------------------------------
/Source/Arbitrator.h:
--------------------------------------------------------------------------------
1 | //
2 | // Arbitrator.h
3 | // DiskArbitrator
4 | //
5 | // Created by Aaron Burghardt on 1/10/10.
6 | // Copyright 2010 Aaron Burghardt. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 |
12 | // Mount Modes
13 | #define MM_BLOCK 0
14 | #define MM_READONLY 1
15 |
16 |
17 | @interface Arbitrator : NSObject
18 |
19 | @property (retain) NSMutableSet *disks;
20 | @property (readonly) NSSet *wholeDisks;
21 | @property BOOL isActivated;
22 | @property NSInteger mountMode;
23 | @property (readonly) NSString *dissenterMessage;
24 |
25 | - (BOOL)registerSession;
26 | - (void)unregisterSession;
27 | - (BOOL)registerApprovalSession;
28 | - (void)unregisterApprovalSession;
29 |
30 | - (BOOL)activate;
31 | - (void)deactivate;
32 |
33 | @end
34 |
35 | DADissenterRef DiskMountApprovalCallback(DADiskRef disk, void *arbitrator);
36 |
37 | extern NSString * const ArbitratorIsEnabled;
38 | extern NSString * const ArbitratorMountMode;
39 |
--------------------------------------------------------------------------------
/Source/Arbitrator.m:
--------------------------------------------------------------------------------
1 | //
2 | // Arbitrator.m
3 | // DiskArbitrator
4 | //
5 | // Created by Aaron Burghardt on 1/10/10.
6 | // Copyright 2010 Aaron Burghardt. All rights reserved.
7 | //
8 |
9 | #import "Arbitrator.h"
10 | #import "AppError.h"
11 | #import "Disk.h"
12 | #import "DiskArbitrationPrivateFunctions.h"
13 |
14 | ////////////////////////////////////////////////////////////////////////////////
15 |
16 | @interface Arbitrator ()
17 | {
18 | DAApprovalSessionRef approvalSession;
19 | }
20 |
21 | @end
22 |
23 | ////////////////////////////////////////////////////////////////////////////////
24 |
25 | @implementation Arbitrator
26 |
27 | @synthesize disks;
28 |
29 | + (void)initialize
30 | {
31 | InitializeDiskArbitration();
32 |
33 | NSMutableDictionary *defaults = [NSMutableDictionary dictionary];
34 | [defaults setObject:[NSNumber numberWithBool:YES] forKey:ArbitratorIsEnabled];
35 | [defaults setObject:[NSNumber numberWithInteger:0] forKey:ArbitratorMountMode];
36 | [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
37 | }
38 |
39 | + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
40 | {
41 | if ([key isEqual:@"wholeDisks"])
42 | return [NSSet setWithObject:@"disks"];
43 |
44 | return [super keyPathsForValuesAffectingValueForKey:key];
45 | }
46 |
47 | - (id)init
48 | {
49 | self = [super init];
50 | if (self)
51 | {
52 | disks = [NSMutableSet new];
53 | [self registerSession];
54 |
55 | if ([[NSUserDefaults standardUserDefaults] boolForKey:ArbitratorIsEnabled]) {
56 | if ([self activate] == NO) {
57 | [self release];
58 | return nil;
59 | }
60 | }
61 | }
62 | return self;
63 | }
64 |
65 | - (void)dealloc
66 | {
67 | if (approvalSession)
68 | [self deactivate];
69 |
70 | [self unregisterSession];
71 |
72 | [disks release];
73 | [super dealloc];
74 | }
75 |
76 | - (BOOL)registerSession
77 | {
78 | NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
79 |
80 | [nc addObserver:self selector:@selector(diskDidAppear:) name:DADiskDidAppearNotification object:nil];
81 | [nc addObserver:self selector:@selector(diskDidDisappear:) name:DADiskDidDisappearNotification object:nil];
82 | [nc addObserver:self selector:@selector(diskDidChange:) name:DADiskDidChangeNotification object:nil];
83 |
84 | return YES;
85 | }
86 |
87 | - (void)unregisterSession
88 | {
89 | [[NSNotificationCenter defaultCenter] removeObserver:self];
90 | }
91 |
92 | - (void)diskDidAppear:(NSNotification *)notif
93 | {
94 | Disk *disk = notif.object;
95 |
96 | Log(LOG_DEBUG, @"%s disk: %@", __func__, disk.BSDName);
97 |
98 | [self addDisksObject:disk];
99 | }
100 |
101 | - (void)diskDidDisappear:(NSNotification *)notif
102 | {
103 | [self removeDisksObject:notif.object];
104 | }
105 |
106 | - (void)diskDidChange:(NSNotification *)notif
107 | {
108 | Log(LOG_DEBUG, @"Changed disk notification: %@", notif.description);
109 | }
110 |
111 | - (BOOL)registerApprovalSession
112 | {
113 | approvalSession = DAApprovalSessionCreate(kCFAllocatorDefault);
114 | if (!approvalSession) {
115 | Log(LOG_CRIT, @"Failed to create Disk Arbitration approval session.");
116 | return NO;
117 | }
118 |
119 | DAApprovalSessionScheduleWithRunLoop(approvalSession, CFRunLoopGetMain(), kCFRunLoopCommonModes);
120 |
121 | DARegisterDiskMountApprovalCallback(approvalSession, NULL, DiskMountApprovalCallback, self);
122 |
123 | return YES;
124 | }
125 |
126 | - (void)unregisterApprovalSession
127 | {
128 | if (approvalSession) {
129 | DAUnregisterApprovalCallback(approvalSession, DiskMountApprovalCallback, self);
130 |
131 | DAApprovalSessionUnscheduleFromRunLoop(approvalSession, CFRunLoopGetMain(), kCFRunLoopCommonModes);
132 | SafeCFRelease(approvalSession);
133 | approvalSession = NULL;
134 | }
135 | }
136 |
137 | - (void)mountApprovedDisk:(Disk *)disk
138 | {
139 | NSAssert(self.isActivated, @"bug");
140 |
141 | NSArray *args = disk.mountArgs;
142 | NSString *path = disk.mountPath;
143 | if (!args || !args.count) {
144 | NSAssert(self.mountMode == MM_READONLY, @"Unknown mount mode");
145 |
146 | // Arguments will be passed via the -o flag of mount. If the file system specific mount, e.g. mount_hfs,
147 | // supports additional flags that mount(8) doesn't, they can be passed to -o. That feature is used to
148 | // pass -j to mount_hfs, which instructs HFS to ignore journal. Normally, an HFS volume that
149 | // has a dirty journal will fail to mount read-only because the file system is inconsistent. "-j" is
150 | // a work-around.
151 |
152 | if (disk.isHFS)
153 | args = [NSArray arrayWithObjects:@"-j", @"rdonly", nil];
154 | else
155 | args = [NSArray arrayWithObjects:@"rdonly", nil];
156 | path = nil;
157 | }
158 | [disk mountAtPath:path withArguments:args];
159 | }
160 |
161 | - (NSString *)dissenterMessage
162 | {
163 | return @"Disk Arbitrator is in charge";
164 | }
165 |
166 | - (DADissenterRef)defaultDissenter __attribute__((cf_returns_retained))
167 | {
168 | return DADissenterCreate(kCFAllocatorDefault, kDAReturnNotPermitted, (CFStringRef)self.dissenterMessage);
169 | }
170 |
171 | - (DADissenterRef)approveMount:(Disk *)disk __attribute__((cf_returns_retained))
172 | {
173 | if (self.isActivated) {
174 | // Block mode prevents everything from mounting, unless this disk is being mounted from our GUI
175 | if (self.mountMode == MM_BLOCK && !disk.isMounting) {
176 | return [self defaultDissenter];
177 | }
178 |
179 | // When an approve mount callback is received, we have no idea if this approval was from
180 | // a mount that belongs to us, or someone else. So we track whether we have rejected a
181 | // mount request, and only allow mounts after we have rejected the initial request.
182 | if (!disk.rejectedMount) {
183 | disk.rejectedMount = YES;
184 | // Do the mount after a slight delay to allow time for this approval to finish
185 | [self performSelector:@selector(mountApprovedDisk:) withObject:disk afterDelay:0.1];
186 | return [self defaultDissenter];
187 | } else {
188 | // Allow the mount since we previously rejected it
189 | NSAssert(disk.isMounting == YES, @"invalid state");
190 | disk.isMounting = NO;
191 | disk.rejectedMount = NO;
192 | return NULL;
193 | }
194 | }
195 |
196 | // Not activated, all mounting of everything
197 | return NULL;
198 | }
199 |
200 | - (BOOL)activate
201 | {
202 | BOOL success;
203 |
204 | [self willChangeValueForKey:@"isActivated"];
205 | success = [self registerApprovalSession];
206 | [[NSUserDefaults standardUserDefaults] setBool:YES forKey:ArbitratorIsEnabled];
207 | [self didChangeValueForKey:@"isActivated"];
208 |
209 | return success;
210 | }
211 |
212 | - (void)deactivate
213 | {
214 | [self willChangeValueForKey:@"isActivated"];
215 | [self unregisterApprovalSession];
216 | [[NSUserDefaults standardUserDefaults] setBool:NO forKey:ArbitratorIsEnabled];
217 | [self didChangeValueForKey:@"isActivated"];
218 | }
219 |
220 | - (BOOL)isActivated
221 | {
222 | return approvalSession ? YES : NO;
223 | }
224 |
225 | - (void)setIsActivated:(BOOL)shouldActivate
226 | {
227 | if (shouldActivate && !self.isActivated)
228 | [self activate];
229 |
230 | else if (!shouldActivate && self.isActivated)
231 | [self deactivate];
232 | }
233 |
234 | - (NSInteger)mountMode {
235 | return [[NSUserDefaults standardUserDefaults] integerForKey:ArbitratorMountMode];
236 | }
237 |
238 | - (void)setMountMode:(NSInteger)mountMode {
239 | NSInteger currentMode = [[NSUserDefaults standardUserDefaults] integerForKey:ArbitratorMountMode];
240 | if (currentMode != mountMode) {
241 | [self willChangeValueForKey:@"mountMode"];
242 | [[NSUserDefaults standardUserDefaults] setInteger:mountMode forKey:ArbitratorMountMode];
243 | [self didChangeValueForKey:@"mountMode"];
244 | }
245 | }
246 |
247 | - (NSSet *)wholeDisks
248 | {
249 | NSMutableSet *wholeDisks = [NSMutableSet new];
250 |
251 | for (Disk *disk in disks)
252 | if (disk.isWholeDisk)
253 | [wholeDisks addObject:disk];
254 |
255 | return [wholeDisks autorelease];
256 | }
257 |
258 | #pragma mark Disks KVC Methods
259 |
260 | - (NSUInteger)countOfDisks
261 | {
262 | return disks.count;
263 | }
264 |
265 | - (NSEnumerator *)enumeratorOfDisks
266 | {
267 | return [disks objectEnumerator];
268 | }
269 |
270 | - (Disk *)memberOfDisks:(Disk *)anObject
271 | {
272 | return [disks member:anObject];
273 | }
274 |
275 | - (void)addDisksObject:(Disk *)object
276 | {
277 | [disks addObject:object];
278 | }
279 |
280 | - (void)addDisks:(NSSet *)objects
281 | {
282 | [disks unionSet:objects];
283 | }
284 |
285 | - (void)removeDisksObject:(Disk *)anObject
286 | {
287 | if (anObject) {
288 | [disks removeObject:anObject];
289 | }
290 | }
291 |
292 | - (void)removeDisks:(NSSet *)objects
293 | {
294 | [disks minusSet:objects];
295 | }
296 |
297 | @end
298 |
299 | #pragma mark Callbacks
300 |
301 | DADissenterRef __attribute__((cf_returns_retained)) DiskMountApprovalCallback(DADiskRef diskRef, void *arbitrator)
302 | {
303 | Log(LOG_DEBUG, @"%s called: %p %s", __func__, diskRef, DADiskGetBSDName(diskRef));
304 | Log(LOG_DEBUG, @"\t claimed: %s", DADiskIsClaimed(diskRef) ? "Yes" : "No");
305 |
306 | Disk *disk = [Disk uniqueDiskForDADisk:diskRef create:YES];
307 |
308 | Log(LOG_DEBUG, @"%@", disk.diskDescription);
309 |
310 | DADissenterRef dissenter = [(Arbitrator*)arbitrator approveMount:disk];
311 |
312 | Log(LOG_DEBUG, @"Mount allowed: %s", dissenter ? "No" : "Yes");
313 | return dissenter;
314 | }
315 |
316 | NSString * const ArbitratorIsEnabled = @"ArbitratorIsEnabled";
317 | NSString * const ArbitratorMountMode = @"ArbitratorMountMode";
318 |
319 |
--------------------------------------------------------------------------------
/Source/AttachDiskImageController.h:
--------------------------------------------------------------------------------
1 | //
2 | // AttachDiskImageController.h
3 | // DiskArbitrator
4 | //
5 | // Created by Aaron Burghardt on 2/21/10.
6 | // Copyright 2010 . All rights reserved.
7 | //
8 |
9 | #import
10 | #import "SheetController.h"
11 |
12 | @interface AttachDiskImageController : SheetController
13 |
14 | @property (retain) IBOutlet NSView *view;
15 | @property (retain) NSTask *task;
16 | @property (copy) NSString *title;
17 | @property (copy) NSString *message;
18 | @property (copy) NSString *errorMessage;
19 | @property double progress;
20 | @property BOOL isVerifying;
21 | @property BOOL canceled;
22 |
23 | + (NSArray *)diskImageFileExtensions;
24 |
25 | - (NSTask *)hdiutilTaskWithCommand:(NSString *)command path:(NSString *)path options:(NSArray *)options password:(NSString *)password;
26 |
27 | - (BOOL)getDiskImagePropertyList:(id *)outPlist atPath:(NSString *)path command:(NSString *)command password:(NSString *)password error:(NSError **)outError;
28 |
29 | - (BOOL)getDiskImageEncryptionStatus:(BOOL *)outFlag atPath:(NSString *)path error:(NSError **)outError;
30 |
31 | - (BOOL)getDiskImageSLAStatus:(BOOL *)outFlag atPath:(NSString *)path password:(NSString *)password error:(NSError **)outError;
32 |
33 | - (BOOL)attachDiskImageAtPath:(NSString *)path options:(NSArray *)options password:(NSString *)password error:(NSError **)error;
34 |
35 | - (void)attachDiskImageOptionsSheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo;
36 |
37 | - (void)performAttachDiskImage;
38 |
39 | - (IBAction)performAttachDiskImage:(id)sender;
40 |
41 | - (IBAction)cancel:(id)sender;
42 |
43 | - (IBAction)skip:(id)sender;
44 |
45 | #pragma mark Delegates
46 |
47 | - (void)panelSelectionDidChange:(id)sender;
48 |
49 | @end
50 |
--------------------------------------------------------------------------------
/Source/AttachDiskImageController.m:
--------------------------------------------------------------------------------
1 | //
2 | // AttachDiskImageController.m
3 | // DiskArbitrator
4 | //
5 | // Created by Aaron Burghardt on 2/21/10.
6 | // Copyright 2010 . All rights reserved.
7 | //
8 |
9 | #import "AttachDiskImageController.h"
10 | #import
11 | #import
12 | #import "AppError.h"
13 |
14 | ////////////////////////////////////////////////////////////////////////////////
15 |
16 | @interface AttachDiskImageController ()
17 | {
18 | NSMutableString *stdoutBuffer;
19 | NSMutableString *stderrBuffer;
20 | }
21 |
22 | @end
23 |
24 | ////////////////////////////////////////////////////////////////////////////////
25 |
26 | @implementation AttachDiskImageController
27 |
28 | @synthesize view;
29 | @synthesize task;
30 | @synthesize title;
31 | @synthesize message;
32 | @synthesize errorMessage;
33 | @synthesize progress;
34 | @synthesize isVerifying;
35 | @synthesize canceled;
36 |
37 | + (NSArray *)diskImageFileExtensions;
38 | {
39 | static NSArray *diskImageFileExtensions = nil;
40 |
41 | if (!diskImageFileExtensions)
42 | diskImageFileExtensions = [[NSArray alloc] initWithObjects:@"img", @"dmg", @"sparseimage", @"sparsebundle", @"iso", @"cdr", nil];
43 |
44 | return diskImageFileExtensions;
45 | }
46 |
47 | - (void)dealloc
48 | {
49 | [[NSNotificationCenter defaultCenter] removeObserver:self];
50 | [stdoutBuffer release];
51 | [stderrBuffer release];
52 | [errorMessage release];
53 | [message release];
54 | [title release];
55 | [task release];
56 | [view release];
57 | [super dealloc];
58 | }
59 |
60 | - (NSTask *)hdiutilTaskWithCommand:(NSString *)command path:(NSString *)path options:(NSArray *)options password:(NSString *)password
61 | {
62 | Log(LOG_DEBUG, @"%s command: %@ path: %@ options: %@", __func__, command, path, options);
63 |
64 | NSTask *newTask;
65 | NSFileHandle *stdinHandle;
66 |
67 | newTask = [[NSTask new] autorelease];
68 | newTask.launchPath = @"/usr/bin/hdiutil";
69 |
70 | NSMutableArray *arguments = [NSMutableArray arrayWithObject:command];
71 | [arguments addObject:path];
72 | [arguments addObjectsFromArray:options];
73 |
74 | newTask.standardOutput = NSPipe.pipe;
75 | newTask.standardError = NSPipe.pipe;
76 | newTask.standardInput = NSPipe.pipe;
77 |
78 | if (password) {
79 | stdinHandle = [newTask.standardInput fileHandleForWriting];
80 | [stdinHandle writeData:[password dataUsingEncoding:NSUTF8StringEncoding]];
81 | [stdinHandle writeData:[NSData dataWithBytes:"" length:1]];
82 |
83 | [arguments addObject:@"-stdinpass"];
84 | }
85 |
86 | newTask.arguments = arguments;
87 |
88 | return newTask;
89 | }
90 |
91 | - (BOOL)getDiskImagePropertyList:(id *)outPlist atPath:(NSString *)path command:(NSString *)command password:(NSString *)password error:(NSError **)outError
92 | {
93 | BOOL retval = YES;
94 | NSMutableDictionary *info;
95 | NSString *failureReason = nil;
96 | NSData *outputData;
97 | NSTask *newTask;
98 |
99 | NSArray *options = [NSArray arrayWithObjects:@"-plist", nil];
100 | newTask = [self hdiutilTaskWithCommand:command path:path options:options password:password];
101 | [newTask launch];
102 | [newTask waitUntilExit];
103 |
104 | outputData = [[newTask.standardOutput fileHandleForReading] readDataToEndOfFile];
105 |
106 | if (newTask.terminationStatus == 0) {
107 | NSError *plistErr = nil;
108 | *outPlist = [NSPropertyListSerialization propertyListWithData:outputData options:NSPropertyListImmutable format:nil error:&plistErr];
109 | if (plistErr) {
110 | failureReason = plistErr.localizedDescription;
111 | }
112 | if (!*outPlist) {
113 | Log(LOG_ERR, @"Plist deserialization error: %@", failureReason);
114 | failureReason = NSLocalizedString(@"hdiutil output is not a property list.", nil);
115 | retval = NO;
116 | }
117 | }
118 | else {
119 | Log(LOG_ERR, @"hdiutil termination status: %d", newTask.terminationStatus);
120 | failureReason = NSLocalizedString(@"hdiutil ended abnormally.", nil);
121 | retval = NO;
122 | }
123 |
124 | if (retval == NO && outError) {
125 | info = [NSMutableDictionary dictionaryWithObjectsAndKeys:
126 | NSLocalizedString(@"Error executing hdiutil command", nil), NSLocalizedDescriptionKey,
127 | failureReason, NSLocalizedFailureReasonErrorKey,
128 | failureReason, NSLocalizedRecoverySuggestionErrorKey,
129 | nil];
130 | *outError = [NSError errorWithDomain:AppErrorDomain code:-1 userInfo:info];
131 | }
132 |
133 | return retval;
134 | }
135 |
136 | - (BOOL)getDiskImageEncryptionStatus:(BOOL *)outFlag atPath:(NSString *)path error:(NSError **)outError
137 | {
138 | BOOL isOK = YES;
139 | NSMutableDictionary *plist;
140 | id value;
141 |
142 | isOK = [self getDiskImagePropertyList:&plist atPath:path command:@"isencrypted" password:nil error:outError];
143 | if (isOK) {
144 | value = [plist objectForKey:@"encrypted"];
145 | if (value) {
146 | *outFlag = [value boolValue];
147 | }
148 | else {
149 | NSMutableDictionary *info = [NSMutableDictionary dictionaryWithObjectsAndKeys:
150 | NSLocalizedString(@"Failed to get encryption property", nil), NSLocalizedDescriptionKey,
151 | NSLocalizedString(@"Check that \"/usr/bin/hdiutil isencrypted\" is functioning correctly.", nil),
152 | NSLocalizedRecoverySuggestionErrorKey,
153 | nil];
154 | if (outError != nil) {
155 | *outError = [NSError errorWithDomain:AppErrorDomain code:-1 userInfo:info];
156 | }
157 | isOK = NO;
158 | }
159 | }
160 |
161 | return isOK;
162 | }
163 |
164 | - (BOOL)getDiskImageSLAStatus:(BOOL *)outFlag atPath:(NSString *)path password:(NSString *)password error:(NSError **)outError
165 | {
166 | BOOL isOK = YES;
167 | NSMutableDictionary *plist;
168 |
169 | isOK = [self getDiskImagePropertyList:&plist atPath:path command:@"imageinfo" password:password error:outError];
170 | if (isOK) {
171 | id value = [plist valueForKeyPath:@"Properties.Software License Agreement"];
172 | if (value) {
173 | *outFlag = [value boolValue];
174 | }
175 | else {
176 | NSMutableDictionary *info = [NSMutableDictionary dictionaryWithObjectsAndKeys:
177 | NSLocalizedString(@"Failed to get SLA property", nil), NSLocalizedDescriptionKey,
178 | NSLocalizedString(@"Check that \"/usr/bin/hdiutil imageinfo\" is functioning correctly.", nil),
179 | NSLocalizedRecoverySuggestionErrorKey,
180 | nil];
181 | if (outError != nil) {
182 | *outError = [NSError errorWithDomain:AppErrorDomain code:-1 userInfo:info];
183 | }
184 | isOK = NO;
185 | }
186 | }
187 | return isOK;
188 | }
189 |
190 | #pragma mark Attaching
191 |
192 | - (void)hdiutilAttachDidTerminate:(NSNotification *)notif
193 | {
194 | [self.window orderOut:self];
195 |
196 | NSTask *theTask = notif.object;
197 |
198 | if (!canceled && theTask.terminationStatus != 0) {
199 |
200 | NSMutableDictionary *info = [NSMutableDictionary dictionary];
201 |
202 | [info setObject:[NSString stringWithFormat:@"%@: %@.", NSLocalizedString(@"Error running hdiutil", nil), self.errorMessage]
203 | forKey:NSLocalizedDescriptionKey];
204 |
205 | [info setObject:self.errorMessage
206 | forKey:NSLocalizedFailureReasonErrorKey];
207 |
208 | [info setObject:NSLocalizedString(@"Check the system log for details.", nil)
209 | forKey:NSLocalizedRecoverySuggestionErrorKey];
210 |
211 | Log(LOG_ERR, @"%s termination status: (%d) %@", __func__, theTask.terminationStatus, self.errorMessage);
212 |
213 | NSError *error = [NSError errorWithDomain:AppErrorDomain code:theTask.terminationStatus userInfo:info];
214 | [NSApp presentError:error];
215 | }
216 | self.task = nil;
217 | [self autorelease];
218 | }
219 |
220 | - (NSString *)promptUserForPasswordAtPath:(NSString *)path error:(NSError **)outError
221 | {
222 | SFAuthorization *authorization;
223 | AuthorizationRights rights;
224 | AuthorizationEnvironment env;
225 | AuthorizationFlags flags;
226 | AuthorizationItemSet *info;
227 | OSStatus status;
228 | NSString *password;
229 |
230 | NSString *fileName = path.lastPathComponent;
231 | NSString *prompt = [NSString stringWithFormat:NSLocalizedString(@"Enter password to access %@", nil), fileName];
232 |
233 | AuthorizationItem rightsItems[1] = { { "com.apple.builtin.generic-unlock", 0, NULL, 0 } };
234 | rights.count = sizeof(rightsItems) / sizeof(AuthorizationItem);;
235 | rights.items = rightsItems;
236 |
237 | AuthorizationItem envItems[1] = {
238 | { kAuthorizationEnvironmentPrompt, strlen([prompt UTF8String]), (void *)[prompt UTF8String], 0 }
239 | // { kAuthorizationEnvironmentPassword, 0, NULL, 0 }
240 | };
241 | env.count = sizeof(envItems) / sizeof(AuthorizationItem);
242 | env.items = envItems;
243 |
244 | flags = kAuthorizationFlagDefaults| kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize;
245 |
246 | authorization = [SFAuthorization authorization];
247 |
248 | if (![authorization obtainWithRights:&rights flags:flags environment:&env authorizedRights:NULL error:outError])
249 | {
250 | if (outError && [*outError code] != errAuthorizationCanceled)
251 | Log(LOG_ERR, @"Authorization error: %@", *outError);
252 | return nil;
253 | }
254 |
255 | password = nil;
256 |
257 | status = AuthorizationCopyInfo([authorization authorizationRef], kAuthorizationEnvironmentPassword, &info);
258 |
259 | if (status == noErr) {
260 | if (info->count > 0 && info->items[0].valueLength > 0)
261 | password = [NSString stringWithUTF8String:info->items[0].value];
262 | }
263 | else {
264 | if (outError) {
265 | NSDictionary *info;
266 | info = [NSDictionary dictionaryWithObject:NSLocalizedString(@"Authorization did not return a password.", nil)
267 | forKey:NSLocalizedDescriptionKey];
268 | *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:info];
269 | }
270 | }
271 |
272 | AuthorizationFreeItemSet(info);
273 |
274 | return password;
275 | }
276 |
277 | - (BOOL)attachDiskImageAtPath:(NSString *)path options:(NSArray *)options password:(NSString *)password error:(NSError **)outError
278 | {
279 | Log(LOG_DEBUG, @"%s path: %@ options: %@", __func__, path, options);
280 |
281 | BOOL isEncrypted = NO, hasSLA = NO;
282 | NSTask *newTask;
283 |
284 | if (!password) {
285 | if ([self getDiskImageEncryptionStatus:&isEncrypted atPath:path error:outError]) {
286 | if (isEncrypted) {
287 | password = [self promptUserForPasswordAtPath:path error:outError];
288 | if (!password)
289 | return [*outError code] == errAuthorizationCanceled ? YES : NO;
290 | }
291 | }
292 | else {
293 | return NO; // get encryption status failed
294 | }
295 | }
296 |
297 | if ([self getDiskImageSLAStatus:&hasSLA atPath:path password:password error:outError] == NO)
298 | return NO;
299 |
300 | self.title = [NSString stringWithFormat:@"Attaching \"%@\" ...", path.lastPathComponent];
301 |
302 | NSMutableArray *arguments = [NSMutableArray array];
303 | [arguments addObject:@"-plist"];
304 | [arguments addObject:@"-puppetstrings"];
305 | [arguments addObjectsFromArray:options];
306 |
307 | newTask = [self hdiutilTaskWithCommand:@"attach" path:path options:arguments password:password];
308 |
309 | if (hasSLA) {
310 | [[newTask.standardInput fileHandleForWriting] writeData:[NSData dataWithBytes:"Y\n" length:3]];
311 | }
312 |
313 | [[NSNotificationCenter defaultCenter] addObserver:self
314 | selector:@selector(processStandardOutput:)
315 | name:NSFileHandleReadCompletionNotification
316 | object:[newTask.standardOutput fileHandleForReading]];
317 | [[newTask.standardOutput fileHandleForReading] readInBackgroundAndNotify];
318 |
319 | [[NSNotificationCenter defaultCenter] addObserver:self
320 | selector:@selector(processStandardError:)
321 | name:NSFileHandleReadCompletionNotification
322 | object:[newTask.standardError fileHandleForReading]];
323 | [[newTask.standardError fileHandleForReading] readInBackgroundAndNotify];
324 |
325 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(hdiutilAttachDidTerminate:) name:NSTaskDidTerminateNotification object:newTask];
326 | [newTask launch];
327 | self.task = newTask;
328 | [self retain];
329 | return YES;
330 | }
331 |
332 | - (void)attachDiskImageOptionsSheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo;
333 | {
334 | if (sheet.isSheet)
335 | [sheet orderOut:self];
336 |
337 | NSString *password = nil;
338 | BOOL isOK;
339 | NSError *error;
340 | NSMutableArray *attachOptions = [NSMutableArray array];
341 |
342 | if (returnCode == NSModalResponseOK) {
343 | NSDictionary *options = self.userInfo;
344 |
345 | if ([[options objectForKey:@"readOnly"] boolValue] == YES)
346 | [attachOptions addObject:@"-readonly"];
347 |
348 | if ([[options objectForKey:@"noVerify"] boolValue] == YES)
349 | [attachOptions addObject:@"-noverify"];
350 | else
351 | [attachOptions addObject:@"-verify"];
352 |
353 | if ([[options objectForKey:@"attemptMount"] boolValue] == YES)
354 | {
355 | [attachOptions addObject:@"-mount"];
356 | [attachOptions addObject:@"optional"];
357 |
358 | if ([[options objectForKey:@"noOwners"] boolValue] == YES) {
359 | [attachOptions addObject:@"-owners"];
360 | [attachOptions addObject:@"off"];
361 | }
362 |
363 | if ([[options objectForKey:@"noBrowse"] boolValue] == YES)
364 | [attachOptions addObject:@"-nobrowse"];
365 |
366 | NSString *rootPath = [options objectForKey:@"rootPath"];
367 | if (rootPath && [rootPath length]) {
368 | [attachOptions addObject:@"-mountroot"];
369 | [attachOptions addObject:rootPath];
370 | }
371 | }
372 | else {
373 | [attachOptions addObject:@"-nomount"];
374 | }
375 |
376 | password = [options objectForKey:@"password"];
377 |
378 | isOK = [self attachDiskImageAtPath:[options objectForKey:@"filePath"] options:attachOptions password:password error:&error];
379 | if (!isOK) [NSApp presentError:error];
380 | }
381 | }
382 |
383 | #pragma mark Actions
384 |
385 | - (void)performAttachDiskImage
386 | {
387 | NSOpenPanel *panel = [NSOpenPanel openPanel];
388 | panel.canChooseFiles = YES;
389 | panel.canChooseDirectories = NO;
390 | panel.allowsMultipleSelection = NO;
391 | panel.message = NSLocalizedString(@"Select a disk image to attach:", nil);
392 |
393 | panel.allowedFileTypes = [self.class diskImageFileExtensions];
394 |
395 | [self.userInfo setObject:[NSNumber numberWithBool:YES] forKey:@"readOnly"];
396 |
397 | panel.accessoryView = self.view;
398 | panel.delegate = self;
399 |
400 | // This is a little strange, but left over from an initial implementation which used cascading sheets on
401 | // the main window. The code sheetDidEnd code is usable for this variation, though
402 |
403 | if ([panel runModal] == NSModalResponseOK) {
404 | [self.userInfo setObject:panel.URL.path forKey:@"filePath"];
405 | [self attachDiskImageOptionsSheetDidEnd:panel returnCode:NSModalResponseOK contextInfo:self];
406 | }
407 | }
408 |
409 | - (IBAction)performAttachDiskImage:(id)sender
410 | {
411 | [self performAttachDiskImage];
412 | }
413 |
414 | - (IBAction)cancel:(id)sender
415 | {
416 | self.canceled = YES;
417 | [task terminate];
418 | }
419 |
420 | - (IBAction)skip:(id)sender
421 | {
422 | }
423 |
424 | #pragma mark Delegates
425 |
426 | - (void)panelSelectionDidChange:(id)sender
427 | {
428 | NSString *filename;
429 | NSFileHandle *handle;
430 | NSData *header;
431 |
432 | Log(LOG_DEBUG, @"%s ", __func__);
433 |
434 | filename = [sender filename];
435 |
436 | if (!filename) {
437 | filename = [[[sender directoryURL] path] stringByAppendingPathComponent:@"token"]; // SparseBundle
438 | }
439 |
440 | Log(LOG_DEBUG, @"filename: %@\n", filename);
441 |
442 | handle = [NSFileHandle fileHandleForReadingAtPath:filename];
443 |
444 | if (handle) {
445 |
446 | header = [handle readDataOfLength:8];
447 | [handle closeFile];
448 |
449 | // This check only works for Version 2 encrypted disk images, the default in Tiger and beyond
450 | // We check only to remind the user if a password is needed, so do not need to catch every case.
451 | //
452 | // http://lorenzo.yellowspace.net/corrupt-sparseimage.html
453 | //
454 |
455 | if (header && [header length] == 8) {
456 |
457 | if (memcmp("encrcdsa", [header bytes], 8) == 0)
458 | [self.userInfo setObject:[NSNumber numberWithBool:YES] forKey:@"needPassword"];
459 | else
460 | [self.userInfo setObject:[NSNumber numberWithBool:NO] forKey:@"needPassword"];
461 | }
462 | }
463 | else {
464 | [self.userInfo setObject:[NSNumber numberWithBool:NO] forKey:@"needPassword"];
465 | }
466 | }
467 |
468 | #pragma mark Notification Callbacks
469 |
470 | - (NSString *)_parseNextMessage:(NSMutableString **)bufferRef newData:(NSData *)data
471 | {
472 | NSMutableString **buffer = bufferRef;
473 | NSString *returnString = nil;
474 | NSString *newString;
475 |
476 | // If data, append to buffer
477 |
478 | if (data && [data length] > 0) {
479 | newString = [[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:NSUTF8StringEncoding];
480 |
481 | if (newString) {
482 | if (*buffer)
483 | [*buffer appendString:newString];
484 | else
485 | *buffer = [newString mutableCopy];
486 | [newString release];
487 | }
488 | }
489 |
490 | // Parse either a plist or a single-line message
491 |
492 | NSString *endOfMessage = [*buffer hasPrefix:@"\n" : @"\n";
493 |
494 | NSRange range = [*buffer rangeOfString:endOfMessage];
495 | if (range.location != NSNotFound)
496 | {
497 | returnString = [*buffer substringToIndex:(range.location + range.length - 1)];
498 | [*buffer deleteCharactersInRange:NSMakeRange(0, [returnString length] + 1)];
499 | }
500 |
501 | return returnString;
502 | }
503 |
504 | - (void)processStandardOutput:(NSNotification *)notif
505 | {
506 | Log(LOG_DEBUG, @"%s", __func__);
507 |
508 | NSString *mymessage;
509 | NSFileHandle *stdoutHandle = notif.object;
510 | double percentage;
511 |
512 | // NSData *data = [stdoutHandle availableData];
513 | NSData *data = [[notif userInfo] objectForKey:NSFileHandleNotificationDataItem];
514 |
515 | while ((mymessage = [self _parseNextMessage:&stdoutBuffer newData:data]))
516 | {
517 | data = nil;
518 |
519 | if ([mymessage hasPrefix:@"PERCENT:"]) {
520 | percentage = [[mymessage substringFromIndex:[@"PERCENT:" length]] doubleValue];
521 | Log(LOG_DEBUG, @"Percent: %f", percentage);
522 |
523 | if (percentage > 0.0) {
524 | if (!self.isVerifying) {
525 | self.isVerifying = YES;
526 | [self showWindow:self];
527 | [NSApp unhide:self];
528 | // [self.window makeKeyAndOrderFront:self];
529 | }
530 | self.progress = percentage;
531 | }
532 | }
533 |
534 | else if ([mymessage hasPrefix:@"MESSAGE:"]) {
535 | mymessage = [mymessage substringFromIndex:[@"MESSAGE:" length]];
536 | Log(LOG_DEBUG, @"Message: %@", mymessage);
537 |
538 | self.message = mymessage;
539 | }
540 |
541 | else if ([mymessage hasPrefix:@"hdiutil:"]) {
542 | mymessage = [mymessage substringFromIndex:[@"hdiutil:" length]];
543 | // error?
544 | Log(LOG_ERR, @"Error: %@", mymessage);
545 | }
546 |
547 | else if ([mymessage hasPrefix:@"
10 |
11 | extern NSString * const DADiskDidAppearNotification;
12 | extern NSString * const DADiskDidDisappearNotification;
13 | extern NSString * const DADiskDidChangeNotification;
14 | extern NSString * const DADiskDidAttemptMountNotification;
15 | extern NSString * const DADiskDidAttemptUnmountNotification;
16 | extern NSString * const DADiskDidAttemptEjectNotification;
17 |
18 | extern NSString * const DAStatusErrorKey;
19 |
20 | enum {
21 | kDiskUnmountOptionDefault = 0x00000000,
22 | kDiskUnmountOptionForce = 0x00080000,
23 | kDiskUnmountOptionWhole = 0x00000001
24 | };
25 |
26 | /* The mounting attribute exists because of a quirk when attaching a volume using "hdiutil attach". Even if "-nomount"
27 | * is specified, the mount approval callback is called. This confuses the situation when attempting to initiate
28 | * a mount when Disk Arbitrator is in read-only mode. "mounting" distinguishes between a callback that is triggered by hdiutil
29 | * and the one that results from calling DADiskMount().
30 | *
31 | * Even if the extra callback issue didn't exist, "mounting" (or some other means) is needed to know when to allow a mount
32 | * in the mount approval callback.
33 | */
34 |
35 | @interface Disk : NSObject
36 |
37 | @property (copy) NSString *BSDName;
38 | @property (readonly) int BSDNameNumber;
39 | @property (nonatomic, retain) NSDictionary *diskDescription;
40 | @property (readonly) BOOL isMountable;
41 | @property (readonly) BOOL isMounted;
42 | @property (readwrite) BOOL isMounting;
43 | @property (readwrite) BOOL rejectedMount;
44 | @property (readonly) BOOL isWritable;
45 | @property (readonly) BOOL isFileSystemWritable;
46 | @property (readonly) BOOL isEjectable;
47 | @property (readonly) BOOL isRemovable;
48 | @property (readonly) BOOL isWholeDisk;
49 | @property (readonly) BOOL isLeaf;
50 | @property (readonly) BOOL isNetworkVolume;
51 | @property (readonly, retain) NSImage *icon;
52 | @property (assign) Disk *parent;
53 | @property (retain) NSMutableSet *children;
54 | @property (readwrite, retain) NSArray *mountArgs;
55 | @property (readwrite, retain) NSString *mountPath;
56 | @property (readonly) BOOL isHFS;
57 |
58 | - (void)mount;
59 | - (void)mountAtPath:(NSString *)path withArguments:(NSArray *)args;
60 | - (void)unmountWithOptions:(NSUInteger)options;
61 | - (void)eject;
62 |
63 | @end
64 |
--------------------------------------------------------------------------------
/Source/Disk.m:
--------------------------------------------------------------------------------
1 | //
2 | // Disk.m
3 | // DiskArbitrator
4 | //
5 | // Created by Aaron Burghardt on 1/10/10.
6 | // Copyright 2010 Aaron Burghardt. All rights reserved.
7 | //
8 |
9 | #import "Disk.h"
10 | #import "AppError.h"
11 | #import
12 | #import "DiskArbitrationPrivateFunctions.h"
13 | #import
14 | #include
15 | #include
16 |
17 | ////////////////////////////////////////////////////////////////////////////////
18 |
19 | @interface Disk ()
20 | {
21 | CFTypeRef disk;
22 | }
23 |
24 | @end
25 |
26 | ////////////////////////////////////////////////////////////////////////////////
27 |
28 | @implementation Disk
29 |
30 | @synthesize diskDescription = _diskDescription;
31 | @synthesize BSDName;
32 | @synthesize isMounting;
33 | @synthesize rejectedMount;
34 | @synthesize icon;
35 | @synthesize parent;
36 | @synthesize children;
37 | @synthesize mountArgs;
38 | @synthesize mountPath;
39 |
40 | + (void)initialize
41 | {
42 | InitializeDiskArbitration();
43 | }
44 |
45 | + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
46 | {
47 | if ([key isEqual:@"isMountable"])
48 | return [NSSet setWithObject:@"diskDescription"];
49 |
50 | if ([key isEqual:@"isMounted"])
51 | return [NSSet setWithObject:@"diskDescription"];
52 |
53 | if ([key isEqual:@"isEjectable"])
54 | return [NSSet setWithObject:@"diskDescription"];
55 |
56 | if ([key isEqual:@"isWritable"])
57 | return [NSSet setWithObject:@"diskDescription"];
58 |
59 | if ([key isEqual:@"isRemovable"])
60 | return [NSSet setWithObject:@"diskDescription"];
61 |
62 | if ([key isEqual:@"isFileSystemWritable"])
63 | return [NSSet setWithObject:@"diskDescription"];
64 |
65 | if ([key isEqual:@"icon"])
66 | return [NSSet setWithObject:@"diskDescription"];
67 |
68 | return [super keyPathsForValuesAffectingValueForKey:key];
69 | }
70 |
71 | + (id)uniqueDiskForDADisk:(DADiskRef)diskRef create:(BOOL)create
72 | {
73 | for (Disk *disk in uniqueDisks) {
74 | if (disk.hash == CFHash(diskRef))
75 | return disk;
76 | }
77 |
78 | return create ? [[[self.class alloc] initWithDADisk:diskRef shouldCreateParent:YES] autorelease] : nil;
79 | }
80 |
81 | - (id)initWithDADisk:(DADiskRef)diskRef shouldCreateParent:(BOOL)shouldCreateParent
82 | {
83 | NSAssert(diskRef, @"No Disk Arbitration disk provided to initializer.");
84 |
85 | self = [super init];
86 |
87 | // Return unique instance
88 | Disk *uniqueDisk = [Disk uniqueDiskForDADisk:diskRef create:NO];
89 | if (uniqueDisk)
90 | {
91 | [self release];
92 | return [uniqueDisk retain];
93 | }
94 |
95 | if (self)
96 | {
97 | disk = CFRetain(diskRef);
98 | const char *bsdName = DADiskGetBSDName(diskRef);
99 | BSDName = [[NSString alloc] initWithUTF8String:bsdName ? bsdName : ""];
100 | children = [NSMutableSet new];
101 | _diskDescription = (NSDictionary *)DADiskCopyDescription(diskRef);
102 |
103 | // CFShow(description);
104 |
105 | if (self.isWholeDisk == NO)
106 | {
107 | DADiskRef parentRef = DADiskCopyWholeDisk(diskRef);
108 | if (parentRef)
109 | {
110 | Disk *parentDisk = [Disk uniqueDiskForDADisk:parentRef create:shouldCreateParent];
111 | if (parentDisk)
112 | {
113 | parent = parentDisk; // weak reference
114 | [[parent mutableSetValueForKey:@"children"] addObject:self];
115 | }
116 | SafeCFRelease(parentRef);
117 | }
118 | }
119 | [uniqueDisks addObject:self];
120 | }
121 | return self;
122 | }
123 |
124 | - (void)dealloc
125 | {
126 | SafeCFRelease(disk);
127 | [_diskDescription release];
128 | [BSDName release];
129 | [icon release];
130 | parent = nil;
131 | [children release];
132 | [mountArgs release];
133 | [mountPath release];
134 | [super dealloc];
135 | }
136 |
137 | - (NSUInteger)hash
138 | {
139 | return CFHash(disk);
140 | }
141 |
142 | - (BOOL)isEqual:(id)object
143 | {
144 | return (CFHash(disk) == [object hash]);
145 | }
146 |
147 | - (NSString *)description
148 | {
149 | return [NSString stringWithFormat:@"<%@ 0x%p %@>", self.class, self, BSDName];
150 | }
151 |
152 | - (void)mount
153 | {
154 | [self mountAtPath:nil withArguments:[NSArray array]];
155 | }
156 |
157 | - (void)mountAtPath:(NSString *)path withArguments:(NSArray *)args
158 | {
159 | NSAssert(self.isMountable, @"Disk isn't mountable.");
160 | NSAssert(self.isMounted == NO, @"Disk is already mounted.");
161 |
162 | self.isMounting = YES;
163 | self.mountArgs = args;
164 | self.mountPath = path;
165 |
166 | Log(LOG_DEBUG, @"%s mount %@ at mountpoint: %@ arguments: %@", __func__, BSDName, path, args.description);
167 |
168 | // ensure arg list is NULL terminated
169 | id *argv = calloc(args.count + 1, sizeof(id));
170 |
171 | [args getObjects:argv range:NSMakeRange(0, args.count)];
172 |
173 | NSURL *url = path ? [NSURL fileURLWithPath:path.stringByExpandingTildeInPath] : NULL;
174 |
175 | DADiskMountWithArguments((DADiskRef) disk, (CFURLRef) url, kDADiskMountOptionDefault,
176 | DiskMountCallback, self, (CFStringRef *)argv);
177 |
178 | free(argv);
179 | }
180 |
181 | - (void)unmountWithOptions:(NSUInteger)options
182 | {
183 | NSAssert(self.isMountable, @"Disk isn't mountable.");
184 | NSAssert(self.isMounted, @"Disk isn't mounted.");
185 |
186 | DADiskUnmount((DADiskRef) disk, (DADiskUnmountOptions)options, DiskUnmountCallback, self);
187 | }
188 |
189 | - (void)eject
190 | {
191 | NSAssert1(self.isEjectable, @"Disk is not ejectable: %@", self);
192 |
193 | DADiskEject((DADiskRef) disk, kDADiskEjectOptionDefault, DiskEjectCallback, [self retain]); // self is released in DiskEjectCallback
194 | }
195 |
196 | - (void)diskDidDisappear
197 | {
198 | [uniqueDisks removeObject:self];
199 | [[parent mutableSetValueForKey:@"children"] removeObject:self];
200 |
201 | SafeCFRelease(disk);
202 | disk = NULL;
203 |
204 | self.parent = nil;
205 | [children removeAllObjects];
206 |
207 | self.rejectedMount = NO;
208 | self.mountArgs = [NSArray array];
209 | self.mountPath = nil;
210 | }
211 |
212 | - (BOOL)isMountable
213 | {
214 | CFBooleanRef value = self.diskDescription ?
215 | (CFBooleanRef)[self.diskDescription objectForKey:(NSString *)kDADiskDescriptionVolumeMountableKey] : NULL;
216 | return value ? CFBooleanGetValue(value) : NO;
217 | }
218 |
219 | - (BOOL)isMounted
220 | {
221 | CFStringRef value = self.diskDescription ?
222 | (CFStringRef)[self.diskDescription objectForKey:(NSString *)kDADiskDescriptionVolumePathKey] : NULL;
223 |
224 | return value ? YES : NO;
225 | }
226 |
227 | - (BOOL)isWholeDisk
228 | {
229 | CFBooleanRef value = self.diskDescription ?
230 | (CFBooleanRef)[self.diskDescription objectForKey:(NSString *)kDADiskDescriptionMediaWholeKey] : NULL;
231 | return value ? CFBooleanGetValue(value) : NO;
232 | }
233 |
234 | - (BOOL)isLeaf
235 | {
236 | CFBooleanRef value = self.diskDescription ?
237 | (CFBooleanRef)[self.diskDescription objectForKey:(NSString *)kDADiskDescriptionMediaLeafKey] : NULL;
238 | return value ? CFBooleanGetValue(value) : NO;
239 | }
240 |
241 | - (BOOL)isNetworkVolume
242 | {
243 | CFBooleanRef value = self.diskDescription ?
244 | (CFBooleanRef)[self.diskDescription objectForKey:(NSString *)kDADiskDescriptionVolumeNetworkKey] : NULL;
245 | return value ? CFBooleanGetValue(value) : NO;
246 | }
247 |
248 | - (BOOL)isWritable
249 | {
250 | CFBooleanRef value = self.diskDescription ?
251 | (CFBooleanRef)[self.diskDescription objectForKey:(NSString *)kDADiskDescriptionMediaWritableKey] : NULL;
252 | return value ? CFBooleanGetValue(value) : NO;
253 | }
254 |
255 | - (BOOL)isEjectable
256 | {
257 | CFBooleanRef value = self.diskDescription ?
258 | (CFBooleanRef)[self.diskDescription objectForKey:(NSString *)kDADiskDescriptionMediaEjectableKey] : NULL;
259 | return value ? CFBooleanGetValue(value) : NO;
260 | }
261 |
262 | - (BOOL)isRemovable
263 | {
264 | CFBooleanRef value = self.diskDescription ?
265 | (CFBooleanRef)[self.diskDescription objectForKey:(NSString *)kDADiskDescriptionMediaRemovableKey] : NULL;
266 | return value ? CFBooleanGetValue(value) : NO;
267 | }
268 |
269 | - (BOOL)isHFS
270 | {
271 | CFStringRef volumeKind = self.diskDescription ?
272 | (CFStringRef)[self.diskDescription objectForKey:(NSString *)kDADiskDescriptionVolumeKindKey] : NULL;
273 | return volumeKind ? CFEqual(CFSTR("hfs"), volumeKind) : NO;
274 | }
275 |
276 | - (BOOL)isFileSystemWritable
277 | {
278 | BOOL retval = NO;
279 | struct statfs fsstat;
280 | CFURLRef volumePath;
281 | UInt8 fsrep[MAXPATHLEN];
282 |
283 | // if the media is not writable, the file system cannot be either
284 | if (self.isWritable == NO)
285 | return NO;
286 |
287 | volumePath = (CFURLRef)[self.diskDescription objectForKey:(NSString *)kDADiskDescriptionVolumePathKey];
288 | if (volumePath) {
289 |
290 | if (CFURLGetFileSystemRepresentation(volumePath, true, fsrep, sizeof(fsrep))) {
291 |
292 | if (statfs((char *)fsrep, &fsstat) == 0)
293 | retval = (fsstat.f_flags & MNT_RDONLY) ? NO : YES;
294 | }
295 | }
296 |
297 | return retval;
298 | }
299 |
300 | - (void)setDiskDescription:(NSDictionary *)desc
301 | {
302 | NSAssert(desc, @"A NULL disk description is not allowed.");
303 |
304 | if (desc != _diskDescription)
305 | {
306 | [self willChangeValueForKey:@"diskDescription"];
307 |
308 | SafeCFRelease(_diskDescription);
309 | _diskDescription = desc ? [desc retain] : nil;
310 |
311 | [self didChangeValueForKey:@"diskDescription"];
312 | }
313 | }
314 |
315 | - (NSImage *)icon
316 | {
317 | if (!icon)
318 | {
319 | if (self.diskDescription)
320 | {
321 | CFDictionaryRef iconRef = (CFDictionaryRef)[self.diskDescription
322 | objectForKey:(NSString *)kDADiskDescriptionMediaIconKey];
323 | if (iconRef)
324 | {
325 |
326 | CFStringRef identifier = CFDictionaryGetValue(iconRef, CFSTR("CFBundleIdentifier"));
327 | NSURL *url = [(NSURL *)KextManagerCreateURLForBundleIdentifier(kCFAllocatorDefault, identifier) autorelease];
328 | if (url) {
329 | NSString *bundlePath = [url path];
330 |
331 | NSBundle *bundle = [NSBundle bundleWithPath:bundlePath];
332 | if (bundle) {
333 | NSString *filename = (NSString *) CFDictionaryGetValue(iconRef, CFSTR("IOBundleResourceFile"));
334 | NSString *basename = [filename stringByDeletingPathExtension];
335 | NSString *fileext = [filename pathExtension];
336 |
337 | NSString *path = [bundle pathForResource:basename ofType:fileext];
338 | if (path) {
339 | icon = [[NSImage alloc] initWithContentsOfFile:path];
340 | }
341 | }
342 | else {
343 | Log(LOG_WARNING, @"Failed to load bundle with URL: %@", [url absoluteString]);
344 | CFShow(self.diskDescription);
345 | }
346 | }
347 | else {
348 | Log(LOG_WARNING, @"Failed to create URL for bundle identifier: %@", (NSString *)identifier);
349 | CFShow(self.diskDescription);
350 | }
351 | }
352 | }
353 | }
354 |
355 | return icon;
356 | }
357 |
358 | - (int)BSDNameNumber
359 | {
360 | // Take the BSDName and convert it into a number that can be compared with other disks for sorting.
361 | // For example, "disk2s1" would become 2 * 1000 + 1 = 2001.
362 | // If we just compare by the string value itself, then disk10 would come after disk1, instead of disk9.
363 | NSString *s = self.BSDName;
364 | int device = 0;
365 | int slice = 0;
366 | const int found = sscanf(s.UTF8String, "disk%ds%d", &device, &slice);
367 | if (found == 0 || device < 0 || slice < 0) {
368 | NSLog(@"Invalid BSD Name %@", s);
369 | }
370 | return (device * 1000) + slice;
371 | }
372 |
373 | @end
374 |
--------------------------------------------------------------------------------
/Source/DiskArbitrationPrivateFunctions.h:
--------------------------------------------------------------------------------
1 | /*
2 | * DiskArbitrationPrivateFunctions.h
3 | * DiskArbitrator
4 | *
5 | * Created by Aaron Burghardt on 1/28/10.
6 | * Copyright 2010 Aaron Burghardt. All rights reserved.
7 | *
8 | */
9 |
10 | #import
11 | #import "Disk.h"
12 |
13 | void InitializeDiskArbitration(void);
14 | BOOL DADiskValidate(DADiskRef diskRef);
15 | void DiskAppearedCallback(DADiskRef diskRef, void *context);
16 | void DiskDisappearedCallback(DADiskRef diskRef, void *context);
17 | void DiskDescriptionChangedCallback(DADiskRef diskRef, CFArrayRef keys, void *context);
18 | void DiskMountCallback(DADiskRef diskRef, DADissenterRef dissenter, void *context);
19 | void DiskUnmountCallback(DADiskRef diskRef, DADissenterRef dissenter, void *context);
20 | void DiskEjectCallback(DADiskRef diskRef, DADissenterRef dissenter, void *context);
21 |
22 |
23 | @interface Disk (DiskPrivate)
24 |
25 | + (id)uniqueDiskForDADisk:(DADiskRef)diskRef create:(BOOL)create;
26 |
27 | - (id)initWithDADisk:(DADiskRef)diskRef shouldCreateParent:(BOOL)shouldCreateParent;
28 | - (void)refreshFromDescription;
29 | - (void)diskDidDisappear;
30 | @end
31 |
32 | extern NSMutableSet *uniqueDisks;
33 |
--------------------------------------------------------------------------------
/Source/DiskArbitrationPrivateFunctions.m:
--------------------------------------------------------------------------------
1 | /*
2 | * DiskArbitrationPrivateFunctions.m
3 | * DiskArbitrator
4 | *
5 | * Created by Aaron Burghardt on 1/28/10.
6 | * Copyright 2010 Aaron Burghardt. All rights reserved.
7 | *
8 | */
9 |
10 | #import "DiskArbitrationPrivateFunctions.h"
11 | #import "AppError.h"
12 |
13 | // Globals
14 | NSMutableSet *uniqueDisks;
15 | DASessionRef session;
16 |
17 |
18 | void InitializeDiskArbitration(void)
19 | {
20 | static BOOL isInitialized = NO;
21 |
22 | if (isInitialized) return;
23 |
24 | isInitialized = YES;
25 |
26 | uniqueDisks = [NSMutableSet new];
27 |
28 | session = DASessionCreate(kCFAllocatorDefault);
29 | if (!session) {
30 | [NSException raise:NSInternalInconsistencyException format:@"Failed to create Disk Arbitration session."];
31 | return;
32 | }
33 |
34 | DASessionScheduleWithRunLoop(session, CFRunLoopGetMain(), kCFRunLoopCommonModes);
35 |
36 | CFMutableDictionaryRef matching = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
37 | CFDictionaryAddValue(matching, kDADiskDescriptionVolumeNetworkKey, kCFBooleanFalse);
38 |
39 | DARegisterDiskAppearedCallback(session, matching, DiskAppearedCallback, [Disk class]);
40 | DARegisterDiskDisappearedCallback(session, matching, DiskDisappearedCallback, [Disk class]);
41 | DARegisterDiskDescriptionChangedCallback(session, matching, NULL, DiskDescriptionChangedCallback, [Disk class]);
42 |
43 | SafeCFRelease(matching);
44 | }
45 |
46 | BOOL DADiskValidate(DADiskRef diskRef)
47 | {
48 | //
49 | // Reject certain disk media
50 | //
51 |
52 | BOOL isOK = YES;
53 |
54 | // Reject if no BSDName
55 | if (DADiskGetBSDName(diskRef) == NULL)
56 | [NSException raise:NSInternalInconsistencyException format:@"Disk without BSDName"];
57 | // return NO;
58 |
59 | CFDictionaryRef desc = DADiskCopyDescription(diskRef);
60 | // CFShow(desc);
61 |
62 | // Reject if no key-value for Whole Media
63 | CFBooleanRef wholeMediaValue = CFDictionaryGetValue(desc, kDADiskDescriptionMediaWholeKey);
64 | if (isOK && !wholeMediaValue) isOK = NO;
65 |
66 | // If not a whole disk, then must be a media leaf
67 | if (isOK && CFBooleanGetValue(wholeMediaValue) == false)
68 | {
69 | CFBooleanRef mediaLeafValue = CFDictionaryGetValue(desc, kDADiskDescriptionMediaLeafKey);
70 | if (!mediaLeafValue || CFBooleanGetValue(mediaLeafValue) == false) isOK = NO;
71 | }
72 | SafeCFRelease(desc);
73 |
74 | return isOK;
75 | }
76 |
77 | void DiskAppearedCallback(DADiskRef diskRef, void *context)
78 | {
79 | if (context != [Disk class]) return;
80 |
81 | Log(LOG_DEBUG, @"%s <%p> %s", __func__, diskRef, DADiskGetBSDName(diskRef));
82 |
83 | if (DADiskValidate(diskRef))
84 | {
85 | Disk *disk = [Disk uniqueDiskForDADisk:diskRef create:YES];
86 | [[NSNotificationCenter defaultCenter] postNotificationName:DADiskDidAppearNotification object:disk];
87 | }
88 | }
89 |
90 | void DiskDisappearedCallback(DADiskRef diskRef, void *context)
91 | {
92 | if (context != [Disk class]) return;
93 |
94 | Log(LOG_DEBUG, @"%s <%p> %s", __func__, diskRef, DADiskGetBSDName(diskRef));
95 |
96 | Disk *tmpDisk = [Disk uniqueDiskForDADisk:diskRef create:NO];
97 | if (!tmpDisk) {
98 | return;
99 | }
100 |
101 | [[NSNotificationCenter defaultCenter] postNotificationName:DADiskDidDisappearNotification object:tmpDisk];
102 |
103 | [tmpDisk diskDidDisappear];
104 | }
105 |
106 | void DiskDescriptionChangedCallback(DADiskRef diskRef, CFArrayRef keys, void *context)
107 | {
108 | if (context != [Disk class]) return;
109 |
110 | Log(LOG_DEBUG, @"%s <%p> %s, keys changed:", __func__, diskRef, DADiskGetBSDName(diskRef));
111 | Log(LOG_DEBUG, @"%@", keys);
112 |
113 | for (Disk *disk in uniqueDisks) {
114 | if (CFHash(diskRef) == disk.hash) {
115 | CFDictionaryRef desc = DADiskCopyDescription(diskRef);
116 | disk.diskDescription = (NSDictionary *)desc;
117 | SafeCFRelease(desc);
118 |
119 | [[NSNotificationCenter defaultCenter] postNotificationName:DADiskDidChangeNotification object:disk];
120 | }
121 | }
122 | }
123 |
124 | void DiskMountCallback(DADiskRef diskRef, DADissenterRef dissenter, void *context)
125 | {
126 | // Disk *disk = (Disk *)context;
127 | NSMutableDictionary *info = nil;
128 |
129 | Log(LOG_DEBUG, @"%s %@ dissenter: %p", __func__, context, dissenter);
130 |
131 | if (dissenter)
132 | {
133 | DAReturn status = DADissenterGetStatus(dissenter);
134 |
135 | NSString *statusString = (NSString *) DADissenterGetStatusString(dissenter);
136 | if (!statusString)
137 | {
138 | IOReturn systemCode = err_get_system(status);
139 | IOReturn subSystemCode = err_get_sub(status);
140 | IOReturn errorCode = err_get_code(status);
141 |
142 | statusString = [NSString stringWithFormat:@"%@: system: %d; subsystem: %d; error: %d",
143 | NSLocalizedString(@"Dissenter status code", nil), systemCode, subSystemCode, errorCode];
144 | }
145 |
146 | Log(LOG_INFO, @"%s %@ dissenter: (%#x) %@", __func__, context, status, statusString);
147 |
148 | info = [NSMutableDictionary dictionary];
149 | [info setObject:statusString forKey:NSLocalizedFailureReasonErrorKey];
150 | [info setObject:[NSNumber numberWithInt:status] forKey:DAStatusErrorKey];
151 | }
152 | else
153 | {
154 | Log(LOG_DEBUG, @"%s disk %@ mounted", __func__, context);
155 | }
156 |
157 | [[NSNotificationCenter defaultCenter] postNotificationName:DADiskDidAttemptMountNotification object:context userInfo:info];
158 | }
159 |
160 | void DiskUnmountCallback(DADiskRef diskRef, DADissenterRef dissenter, void *context)
161 | {
162 | NSDictionary *info = nil;
163 |
164 | if (dissenter) {
165 | DAReturn status = DADissenterGetStatus(dissenter);
166 |
167 | NSString *statusString = (NSString *) DADissenterGetStatusString(dissenter);
168 | if (!statusString)
169 | statusString = [NSString stringWithFormat:@"Error code: %d", status];
170 |
171 | Log(LOG_DEBUG, @"%s disk %@ dissenter: (%d) %@", __func__, context, status, statusString);
172 |
173 | info = [NSDictionary dictionaryWithObjectsAndKeys:
174 | [NSNumber numberWithInt:status], DAStatusErrorKey,
175 | statusString, NSLocalizedFailureReasonErrorKey,
176 | statusString, NSLocalizedRecoverySuggestionErrorKey,
177 | nil];
178 | }
179 | else {
180 | Log(LOG_DEBUG, @"%s disk %@ unmounted", __func__, context);
181 | }
182 |
183 | [[NSNotificationCenter defaultCenter] postNotificationName:DADiskDidAttemptUnmountNotification object:context userInfo:info];
184 | }
185 |
186 | void DiskEjectCallback(DADiskRef diskRef, DADissenterRef dissenter, void *context)
187 | {
188 | [(id)context autorelease]; // got retained when passed in
189 | NSDictionary *info = nil;
190 |
191 | if (dissenter) {
192 | DAReturn status = DADissenterGetStatus(dissenter);
193 |
194 | NSString *statusString = (NSString *) DADissenterGetStatusString(dissenter);
195 | if (!statusString)
196 | statusString = [NSString stringWithFormat:@"Error code: %d", status];
197 |
198 | Log(LOG_INFO, @"%s disk: %@ dissenter: (%d) %@", __func__, context, status, statusString);
199 |
200 | info = [NSDictionary dictionaryWithObjectsAndKeys:
201 | [NSNumber numberWithInt:status], DAStatusErrorKey,
202 | statusString, NSLocalizedFailureReasonErrorKey,
203 | statusString, NSLocalizedRecoverySuggestionErrorKey,
204 | nil];
205 | }
206 | else {
207 | Log(LOG_DEBUG, @"%s disk ejected: %@ ", __func__, context);
208 | }
209 |
210 | [[NSNotificationCenter defaultCenter] postNotificationName:DADiskDidAttemptEjectNotification object:context userInfo:info];
211 | }
212 |
213 |
214 | NSString * const DADiskDidAppearNotification = @"DADiskDidAppearNotification";
215 | NSString * const DADiskDidDisappearNotification = @"DADiskDidDisppearNotification";
216 | NSString * const DADiskDidChangeNotification = @"DADiskDidChangeNotification";
217 | NSString * const DADiskDidAttemptMountNotification = @"DADiskDidAttemptMountNotification";
218 | NSString * const DADiskDidAttemptUnmountNotification = @"DADiskDidAttemptUnmountNotification";
219 | NSString * const DADiskDidAttemptEjectNotification = @"DADiskDidAttemptEjectNotification";
220 |
221 | NSString * const DAStatusErrorKey = @"DAStatusErrorKey";
222 |
--------------------------------------------------------------------------------
/Source/DiskArbitratorAppController+Toolbar.h:
--------------------------------------------------------------------------------
1 | //
2 | // DiskArbitratorAppController+Toolbar.h
3 | // DiskArbitrator
4 | //
5 | // Created by Aaron Burghardt on 2/7/10.
6 | // Copyright 2010 . All rights reserved.
7 | //
8 |
9 | #import
10 | #import "DiskArbitratorAppController.h"
11 |
12 | @interface AppController (AppControllerToolbar)
13 |
14 |
15 | @end
16 |
17 | void SetupToolbar(NSWindow *window, id delegate);
18 |
19 | // Toolbar Item Identifier constants
20 | extern NSString * const ToolbarItemMainIdentifier;
21 | extern NSString * const ToolbarItemInfoIdentifier;
22 | extern NSString * const ToolbarItemMountIdentifier;
23 | extern NSString * const ToolbarItemEjectIdentifier;
24 | extern NSString * const ToolbarItemAttachDiskImageIdentifier;
25 |
--------------------------------------------------------------------------------
/Source/DiskArbitratorAppController+Toolbar.m:
--------------------------------------------------------------------------------
1 | //
2 | // DiskArbitratorAppController+Toolbar.m
3 | // DiskArbitrator
4 | //
5 | // Created by Aaron Burghardt on 2/7/10.
6 | // Copyright 2010 . All rights reserved.
7 | //
8 |
9 | #import "DiskArbitratorAppController+Toolbar.h"
10 | #import "Disk.h"
11 |
12 |
13 | @implementation AppController (AppControllerToolbar)
14 |
15 | - (NSImage *)attachDiskImageIcon
16 | {
17 | NSImage *dmgIcon = [[NSWorkspace sharedWorkspace] iconForFileType:@"dmg"];
18 | NSImage *plugImage = [NSImage imageNamed:@"ToolbarItem Attach Disk Plug"];
19 |
20 | NSBitmapImageRep *compositedImage = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL pixelsWide:32 pixelsHigh:32 bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO colorSpaceName:NSCalibratedRGBColorSpace bytesPerRow:0 bitsPerPixel:0];
21 |
22 | [NSGraphicsContext saveGraphicsState];
23 | [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:compositedImage]];
24 | [[NSGraphicsContext currentContext] setShouldAntialias:NO];
25 |
26 | [dmgIcon drawInRect:NSMakeRect(0, 0, 32, 32) fromRect:NSZeroRect operation:NSCompositingOperationCopy fraction:1.0];
27 | [plugImage drawInRect:NSMakeRect(2, 16, 16, 16) fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0];
28 |
29 | [NSGraphicsContext restoreGraphicsState];
30 |
31 | return [[NSImage alloc] initWithData:[compositedImage TIFFRepresentation]];
32 | }
33 |
34 | - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag
35 | {
36 | NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
37 |
38 | if ([itemIdentifier isEqual:ToolbarItemInfoIdentifier]) { // Info/Inspect
39 | item.label = NSLocalizedString(@"Info", nil);
40 | item.paletteLabel = NSLocalizedString(@"Info", nil);
41 | item.image = [NSImage imageNamed:@"ToolbarItem Info"]; // NSImageNameInfo]];
42 | item.target = self;
43 | item.action = @selector(performGetInfo:);
44 | item.toolTip = NSLocalizedString(@"Show detailed disk info", nil);
45 | }
46 | else if ([itemIdentifier isEqual:ToolbarItemEjectIdentifier]) { // Eject
47 | item.label = NSLocalizedString(@"Eject", nil);
48 | item.paletteLabel = NSLocalizedString(@"Eject", nil);
49 | item.image = [NSImage imageNamed:@"ToolbarItem Eject"];
50 | item.target = self;
51 | item.action = @selector(performEject:);
52 | item.toolTip = NSLocalizedString(@"Eject removable media.", nil);
53 | }
54 | else if ([itemIdentifier isEqual:ToolbarItemMountIdentifier]) { // Mount
55 | item.label = NSLocalizedString(@"Mount", nil);
56 | item.paletteLabel = NSLocalizedString(@"Mount/Unmount", nil);
57 | item.image = [NSImage imageNamed:@"ToolbarItem Mount"];
58 | item.target = self;
59 | item.action = @selector(performMountOrUnmount:);
60 | item.toolTip = NSLocalizedString(@"Select a volume, then click to mount or unmount.", nil);
61 | }
62 | else if ([itemIdentifier isEqual:ToolbarItemAttachDiskImageIdentifier]) { // Attach Disk Image
63 | item.label = NSLocalizedString(@"Attach", nil);
64 | item.paletteLabel = NSLocalizedString(@"Attach Disk Image", nil);
65 | // item.image = [NSImage imageNamed:@"ToolbarItem Attach Disk Image"];
66 | item.image = self.attachDiskImageIcon;
67 | item.target = self;
68 | item.action = @selector(performAttachDiskImage:);
69 | item.toolTip = NSLocalizedString(@"Attach Disk Image", nil);
70 | }
71 | return item;
72 | }
73 |
74 |
75 | //----------------------------------------------------------
76 | - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar {
77 | return [NSArray arrayWithObjects:
78 | NSToolbarSeparatorItemIdentifier,
79 | NSToolbarSpaceItemIdentifier,
80 | NSToolbarFlexibleSpaceItemIdentifier,
81 | NSToolbarCustomizeToolbarItemIdentifier,
82 | ToolbarItemInfoIdentifier,
83 | ToolbarItemMountIdentifier,
84 | ToolbarItemEjectIdentifier,
85 | ToolbarItemAttachDiskImageIdentifier,
86 | nil];
87 | }
88 |
89 |
90 | //----------------------------------------------------------
91 | - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar {
92 | return [NSArray arrayWithObjects:
93 | ToolbarItemInfoIdentifier,
94 | ToolbarItemEjectIdentifier,
95 | ToolbarItemMountIdentifier,
96 | NSToolbarSpaceItemIdentifier,
97 | ToolbarItemAttachDiskImageIdentifier,
98 | NSToolbarFlexibleSpaceItemIdentifier,
99 | NSToolbarCustomizeToolbarItemIdentifier,
100 | nil];
101 | }
102 |
103 |
104 | //----------------------------------------------------------
105 | -(BOOL)validateToolbarItem:(NSToolbarItem*)toolbarItem {
106 | BOOL enabled = YES;
107 |
108 | if ([[toolbarItem itemIdentifier] isEqual:ToolbarItemInfoIdentifier])
109 | enabled = YES;
110 |
111 | else if ([[toolbarItem itemIdentifier] isEqual:ToolbarItemMountIdentifier]) {
112 |
113 | // Enable the item if the disk is mountable
114 | // Set the label "Unmount" if mounted, otherwise to "Mount"
115 |
116 | if (self.canUnmountSelectedDisk) {
117 | [toolbarItem setLabel:NSLocalizedString(@"Unmount", nil)];
118 | enabled = YES;
119 | }
120 | else {
121 | [toolbarItem setLabel:NSLocalizedString(@"Mount", nil)];
122 |
123 | if (!self.canMountSelectedDisk)
124 | enabled = NO;
125 | }
126 | }
127 |
128 | else if ([[toolbarItem itemIdentifier] isEqual:ToolbarItemEjectIdentifier]) {
129 |
130 | enabled = self.canEjectSelectedDisk;
131 | }
132 |
133 | return enabled;
134 | }
135 |
136 | @end
137 |
138 |
139 | void SetupToolbar(NSWindow *window, id delegate)
140 | {
141 | NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:ToolbarItemMainIdentifier];
142 | [toolbar setDelegate:delegate];
143 | [toolbar setAllowsUserCustomization:YES];
144 | [toolbar setAutosavesConfiguration:YES];
145 | [window setToolbar:toolbar];
146 | }
147 |
148 |
149 |
150 | // Toolbar Item Identifier constants
151 | NSString * const ToolbarItemMainIdentifier = @"ToolbarItemMainIdentifier";
152 | NSString * const ToolbarItemInfoIdentifier = @"ToolbarItemInfoIdentifier";
153 | NSString * const ToolbarItemMountIdentifier = @"ToolbarItemMountIdentifier";
154 | NSString * const ToolbarItemEjectIdentifier = @"ToolbarItemEjectIdentifier";
155 | NSString * const ToolbarItemAttachDiskImageIdentifier = @"ToolbarItemAttachDiskImageIdentifier";
156 |
157 |
--------------------------------------------------------------------------------
/Source/DiskArbitratorAppController.h:
--------------------------------------------------------------------------------
1 | //
2 | // DiskArbitratorAppController.h
3 | // DiskArbitrator
4 | //
5 | // Created by Aaron Burghardt on 1/10/10.
6 | // Copyright 2010 Aaron Burghardt. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @class Arbitrator;
12 | @class Disk;
13 |
14 | @interface AppController : NSObject //
15 |
16 | @property (assign) IBOutlet NSPanel *window;
17 | @property (assign) IBOutlet NSMenu *statusMenu;
18 | @property (assign) IBOutlet NSTableView *tableView;
19 | @property (assign) IBOutlet NSArrayController *disksArrayController;
20 | @property (copy) NSArray *sortDescriptors;
21 | @property (strong) NSStatusItem *statusItem;
22 | @property (strong) Arbitrator *arbitrator;
23 | @property (assign) BOOL hasUserLaunchAgent;
24 | @property (readonly) BOOL canInstallLaunchAgent;
25 | @property (strong) NSString *installUserLaunchAgentMenuTitle;
26 |
27 | - (IBAction)showAboutPanel:(id)sender;
28 | - (IBAction)showMainWindow:(id)sender;
29 | - (IBAction)showPreferences:(id)sender;
30 | - (IBAction)performActivation:(id)sender;
31 | - (IBAction)performDeactivation:(id)sender;
32 | - (IBAction)toggleActivation:(id)sender;
33 |
34 | - (IBAction)performSetMountBlockMode:(id)sender;
35 | - (IBAction)performSetMountReadOnlyMode:(id)sender;
36 |
37 | - (IBAction)performMount:(id)sender;
38 | - (IBAction)performUnmount:(id)sender;
39 | - (IBAction)performMountOrUnmount:(id)sender;
40 | - (IBAction)performEject:(id)sender;
41 | - (IBAction)performGetInfo:(id)sender;
42 | - (IBAction)performAttachDiskImage:(id)sender;
43 |
44 | - (void)refreshLaunchAgentStatus;
45 | - (IBAction)installUserLaunchAgent:(id)sender;
46 |
47 | - (Disk *)selectedDisk;
48 | - (BOOL)canEjectSelectedDisk;
49 | - (BOOL)canMountSelectedDisk;
50 | - (BOOL)canUnmountSelectedDisk;
51 |
52 | @end
53 |
--------------------------------------------------------------------------------
/Source/DiskArbitratorAppController.m:
--------------------------------------------------------------------------------
1 | //
2 | // DiskArbitratorAppController.m
3 | // DiskArbitrator
4 | //
5 | // Created by Aaron Burghardt on 1/10/10.
6 | // Copyright 2010 Aaron Burghardt. All rights reserved.
7 | //
8 |
9 | #import "DiskArbitratorAppController.h"
10 | #import "DiskArbitratorAppController+Toolbar.h"
11 | #import "AppError.h"
12 | #import "Arbitrator.h"
13 | #import "Disk.h"
14 | #import "SheetController.h"
15 | #import "DiskInfoController.h"
16 | #import "AttachDiskImageController.h"
17 |
18 | ////////////////////////////////////////////////////////////////////////////////
19 |
20 | @interface AppController ()
21 | {
22 | NSMutableArray *displayErrorQueue; //
23 | NSMutableArray *diskInfoControllers;
24 | }
25 |
26 | @end
27 |
28 | ////////////////////////////////////////////////////////////////////////////////
29 |
30 | @implementation AppController
31 |
32 | @synthesize window;
33 | @synthesize statusMenu;
34 | @synthesize tableView;
35 | @synthesize disksArrayController;
36 | @synthesize sortDescriptors;
37 | @synthesize statusItem;
38 | @synthesize arbitrator;
39 | @synthesize hasUserLaunchAgent;
40 | @synthesize installUserLaunchAgentMenuTitle;
41 |
42 | - (void)dealloc
43 | {
44 | if (arbitrator.isActivated)
45 | [arbitrator deactivate];
46 | }
47 |
48 | - (void)setStatusItemIconWithName:(NSString *)name
49 | {
50 | NSString *iconPath = [[NSBundle mainBundle] pathForResource:name ofType:@"png"];
51 | NSImage *statusIcon = [[NSImage alloc] initWithContentsOfFile:iconPath];
52 | statusItem.image = statusIcon;
53 | }
54 |
55 | - (void)refreshStatusItemIcon
56 | {
57 | if (arbitrator.isActivated == NO)
58 | [self setStatusItemIconWithName:@"StatusItem Disabled 1"];
59 |
60 | else if (arbitrator.mountMode == MM_BLOCK)
61 | [self setStatusItemIconWithName:@"StatusItem Green"];
62 |
63 | else if (arbitrator.mountMode == MM_READONLY)
64 | [self setStatusItemIconWithName:@"StatusItem Orange"];
65 |
66 | else
67 | NSAssert1(NO, @"Invalid mount mode: %ld\n", (long)arbitrator.mountMode);
68 | }
69 |
70 | - (void)applicationDidFinishLaunching:(NSNotification *)notification
71 | {
72 | [self refreshLaunchAgentStatus];
73 |
74 | NSMutableDictionary *defaults = [NSMutableDictionary dictionary];
75 | [defaults setObject:[NSNumber numberWithInt:LOG_INFO] forKey:AppLogLevelDefaultsKey];
76 | [defaults setObject:[NSNumber numberWithBool:YES] forKey:@"ShowMainWindowAtStartup"];
77 | [defaults setObject:[NSNumber numberWithBool:NO] forKey:@"ShowMainWindowAtActivate"];
78 | [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
79 |
80 | displayErrorQueue = [NSMutableArray new];
81 |
82 | NSStatusBar *bar = [NSStatusBar systemStatusBar];
83 | self.statusItem = [bar statusItemWithLength:NSSquareStatusItemLength];
84 | NSImage *altImage = [[NSImage imageNamed:@"StatusItem Disabled 1.png"] copy];
85 | [altImage setTemplate:YES];
86 | self.statusItem.alternateImage = altImage;
87 | [self.statusItem setHighlightMode:YES];
88 | [self setStatusItemIconWithName:@"StatusItem Disabled 1"];
89 | statusItem.menu = statusMenu;
90 |
91 | NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
92 | [center addObserver:self selector:@selector(diskDidChange:) name:DADiskDidChangeNotification object:nil];
93 | [center addObserver:self selector:@selector(didAttemptEject:) name:DADiskDidAttemptEjectNotification object:nil];
94 | [center addObserver:self selector:@selector(didAttemptMount:) name:DADiskDidAttemptMountNotification object:nil];
95 | [center addObserver:self selector:@selector(didAttemptUnmount:) name:DADiskDidAttemptUnmountNotification object:nil];
96 |
97 | self.arbitrator = [Arbitrator new];
98 | [arbitrator addObserver:self forKeyPath:@"isActivated" options:0 context:NULL];
99 | [arbitrator addObserver:self forKeyPath:@"mountMode" options:0 context:NULL];
100 | [self refreshStatusItemIcon]; // arbitrator status initial state is taken from user defaults, which was initialized before KVO initialized
101 |
102 | self.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"BSDNameNumber" ascending:YES]];
103 |
104 | SetupToolbar(window, self);
105 | window.collectionBehavior = NSWindowCollectionBehaviorCanJoinAllSpaces;
106 | window.worksWhenModal = YES;
107 |
108 | [tableView registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
109 |
110 | if ([[NSUserDefaults standardUserDefaults] boolForKey:@"ShowMainWindowAtStartup"])
111 | [window makeKeyAndOrderFront:self];
112 | }
113 |
114 | - (void)applicationDidBecomeActive:(NSNotification *)notification
115 | {
116 | if ([[NSUserDefaults standardUserDefaults] boolForKey:@"ShowMainWindowAtActivate"])
117 | [window makeKeyAndOrderFront:self];
118 | }
119 |
120 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
121 | {
122 | if (object == arbitrator)
123 | if ([keyPath isEqual:@"isActivated"] || [keyPath isEqual:@"mountMode"])
124 | [self refreshStatusItemIcon];
125 | }
126 |
127 | - (IBAction)showAboutPanel:(id)sender
128 | {
129 | [NSApp orderFrontStandardAboutPanel:sender];
130 | [NSApp activateIgnoringOtherApps:YES];
131 | }
132 |
133 | - (IBAction)showMainWindow:(id)sender
134 | {
135 | [window makeKeyAndOrderFront:sender];
136 | [NSApp activateIgnoringOtherApps:YES];
137 | }
138 |
139 | - (IBAction)showPreferences:(id)sender
140 | {
141 | static NSWindowController *controller = nil;
142 |
143 | if (!controller)
144 | controller = [[NSWindowController alloc] initWithWindowNibName:@"Preferences"];
145 |
146 | [controller.window makeKeyAndOrderFront:self];
147 | [NSApp activateIgnoringOtherApps:YES];
148 | }
149 |
150 | - (IBAction)performActivation:(id)sender
151 | {
152 | [arbitrator activate];
153 | }
154 |
155 | - (IBAction)performDeactivation:(id)sender
156 | {
157 | [arbitrator deactivate];
158 | }
159 |
160 | - (IBAction)toggleActivation:(id)sender
161 | {
162 | if (arbitrator.isActivated)
163 | [self performDeactivation:sender];
164 | else
165 | [self performActivation:sender];
166 | }
167 |
168 | - (IBAction)performSetMountBlockMode:(id)sender
169 | {
170 | arbitrator.mountMode = MM_BLOCK;
171 | }
172 |
173 | - (IBAction)performSetMountReadOnlyMode:(id)sender
174 | {
175 | arbitrator.mountMode = MM_READONLY;
176 | }
177 |
178 | - (void)performMountSheetDidEnd:(NSWindow *)sheet
179 | returnCode:(NSInteger)returnCode sheetController:(SheetController *)controller;
180 | {
181 | [sheet orderOut:self];
182 |
183 | Disk *selectedDisk = self.selectedDisk;
184 | NSMutableArray *arguments = [NSMutableArray array];
185 |
186 | if (returnCode == NSModalResponseOK) {
187 | NSDictionary *options = controller.userInfo;
188 |
189 | if ([[options objectForKey:@"readOnly"] boolValue] == YES)
190 | [arguments addObject:@"rdonly"];
191 |
192 | if ([[options objectForKey:@"noOwners"] boolValue] == YES)
193 | [arguments addObject:@"noowners"];
194 |
195 | if ([[options objectForKey:@"noBrowse"] boolValue] == YES)
196 | [arguments addObject:@"nobrowse"];
197 |
198 | if ([[options objectForKey:@"ignoreJournal"] boolValue] == YES)
199 | [arguments addObject:@"-j"];
200 |
201 | NSString *path = [options objectForKey:@"path"];
202 |
203 | [selectedDisk mountAtPath:path withArguments:arguments];
204 | }
205 | }
206 |
207 | - (IBAction)performMount:(id)sender
208 | {
209 | Disk *selectedDisk = self.selectedDisk;
210 |
211 | NSAssert(selectedDisk, @"No disk selected.");
212 | NSAssert(selectedDisk.isMounted == NO, @"Disk is already mounted.");
213 |
214 | SheetController *controller = [[SheetController alloc] initWithWindowNibName:@"MountOptions"];
215 | [controller window]; // triggers controller to load the NIB
216 |
217 | [[controller userInfo] setObject:[NSNumber numberWithBool:YES] forKey:@"readOnly"];
218 | [[controller userInfo] setObject:[NSNumber numberWithBool:selectedDisk.isHFS] forKey:@"ignoreJournal"];
219 | [[controller userInfo] setObject:[NSNumber numberWithBool:selectedDisk.isHFS] forKey:@"isHFS"];
220 |
221 | [window makeKeyAndOrderFront:self];
222 |
223 | [window beginSheet:controller.window completionHandler:^(NSModalResponse returnCode)
224 | {
225 | [self performMountSheetDidEnd:controller.window
226 | returnCode:returnCode sheetController:controller];
227 | }];
228 | }
229 |
230 | - (IBAction)performUnmount:(id)sender
231 | {
232 | Disk *theDisk = self.selectedDisk;
233 |
234 | if (!theDisk) return;
235 |
236 | [theDisk unmountWithOptions: theDisk.isWholeDisk ? kDiskUnmountOptionWhole : kDiskUnmountOptionDefault];
237 | }
238 |
239 | - (IBAction)performMountOrUnmount:(id)sender
240 | {
241 | Disk *theDisk = self.selectedDisk;
242 |
243 | if (theDisk.isMounted)
244 | [self performUnmount:sender];
245 | else
246 | [self performMount:sender];
247 | }
248 |
249 | - (void)_childDidAttemptUnmountBeforeEject:(NSNotification *)notif
250 | {
251 | Disk *disk = notif.object;
252 |
253 | // Disk may be a mountable whole disk that we were waiting on, so the parent may be the disk itself
254 |
255 | Disk *parent = disk.isWholeDisk ? disk : disk.parent;
256 |
257 | Log(LOG_DEBUG, @"%s disk: %@ child: %@", __func__, parent, disk);
258 |
259 | [[NSNotificationCenter defaultCenter] removeObserver:self name:DADiskDidAttemptUnmountNotification object:disk];
260 |
261 | // Confirm the child unmounted
262 |
263 | if (disk.isMounted) {
264 | // Unmount of child failed
265 |
266 | NSMutableDictionary *info = [[notif userInfo] mutableCopy];
267 |
268 | Log(LOG_INFO, @"%s eject disk: %@ canceled due to mounted child: %@", __func__, disk, info);
269 |
270 | NSString *statusString = [NSString stringWithFormat:@"%@:\n\n%@",
271 | NSLocalizedString(@"Failed to unmount child", nil),
272 | [info objectForKey:NSLocalizedFailureReasonErrorKey]];
273 |
274 | [info setObject:statusString forKey:NSLocalizedFailureReasonErrorKey];
275 | [info setObject:statusString forKey:NSLocalizedRecoverySuggestionErrorKey];
276 |
277 | [[NSNotificationCenter defaultCenter] postNotificationName:DADiskDidAttemptEjectNotification object:disk userInfo:info];
278 | }
279 |
280 | // Child from notification is unmounted, check for remaining children to unmount
281 |
282 | for (Disk *child in parent.children) {
283 | if (child.isMounted)
284 | return; // Still waiting for child
285 | }
286 |
287 | // Need to test if parent is ejectable because we enable "Eject" for a disk
288 | // that has children that can be unmounted (ala Disk Utility)
289 |
290 | if (parent.isEjectable)
291 | [parent eject];
292 | }
293 |
294 | - (IBAction)performEject:(id)sender
295 | {
296 | Disk *selectedDisk = self.selectedDisk;
297 | BOOL waitForChildren = NO;
298 |
299 | NSSet *disks;
300 | if (selectedDisk.isWholeDisk && selectedDisk.isLeaf)
301 | disks = [NSSet setWithObject:selectedDisk];
302 | else
303 | disks = selectedDisk.children;
304 |
305 | for (Disk *disk in disks) {
306 | if (disk.isMountable && disk.isMounted) {
307 | [[NSNotificationCenter defaultCenter] addObserver:self
308 | selector:@selector(_childDidAttemptUnmountBeforeEject:)
309 | name:DADiskDidAttemptUnmountNotification
310 | object:disk];
311 | [disk unmountWithOptions:0];
312 | waitForChildren = YES;
313 | }
314 | }
315 |
316 | if (!waitForChildren) {
317 | if (selectedDisk.isEjectable)
318 | [selectedDisk eject];
319 | }
320 | }
321 |
322 | - (IBAction)performGetInfo:(id)sender
323 | {
324 | DiskInfoController *controller = [[DiskInfoController alloc] initWithWindowNibName:@"DiskInfo"];
325 | controller.disk = self.selectedDisk;
326 | [controller showWindow:self];
327 | [controller refreshDiskInfo];
328 | if (!diskInfoControllers) {
329 | diskInfoControllers = [[NSMutableArray alloc] init];
330 | }
331 | [diskInfoControllers addObject:controller];
332 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(diskInfoWindowDidClose:) name:NSWindowWillCloseNotification object:[controller window]];
333 | }
334 |
335 | - (void)diskInfoWindowDidClose:(NSNotification *)notif
336 | {
337 | DiskInfoController *controllerToRemove = nil;
338 | for (DiskInfoController *controller in diskInfoControllers) {
339 | if ([controller window] == [notif object]) {
340 | controllerToRemove = controller;
341 | break;
342 | }
343 | }
344 | if (controllerToRemove) {
345 | [[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowWillCloseNotification object:[notif object]];
346 | [diskInfoControllers removeObject:controllerToRemove];
347 | }
348 | }
349 |
350 | - (IBAction)performAttachDiskImage:(id)sender
351 | {
352 | AttachDiskImageController *controller = [[AttachDiskImageController alloc] initWithWindowNibName:@"AttachDiskImageAccessory"];
353 | [controller window];
354 | [controller performAttachDiskImage:sender];
355 | }
356 |
357 | #pragma mark Launch Agent
358 |
359 | - (NSString *)userLaunchAgentPath {
360 |
361 | NSString *path = nil;
362 |
363 | CFStringRef identifier = CFBundleGetIdentifier(CFBundleGetMainBundle());
364 | if (identifier) {
365 | path = [NSString stringWithFormat:@"%@/%@/%@.plist", NSHomeDirectory(), @"Library/LaunchAgents", identifier];
366 | }
367 |
368 | return path;
369 | }
370 |
371 | - (void)refreshLaunchAgentStatus {
372 | NSFileManager *fm = [NSFileManager defaultManager];
373 |
374 | self.hasUserLaunchAgent = [fm fileExistsAtPath:self.userLaunchAgentPath];
375 | if (self.hasUserLaunchAgent)
376 | self.installUserLaunchAgentMenuTitle = @"Uninstall User Launch Agent...";
377 | else
378 | self.installUserLaunchAgentMenuTitle = @"Install User Launch Agent...";
379 | }
380 |
381 | - (BOOL)canInstallLaunchAgent {
382 | return (self.hasUserLaunchAgent == NO);
383 | }
384 |
385 | - (IBAction)installUserLaunchAgent:(id)sender {
386 | NSError *error;
387 | NSAlert *alert = nil;
388 | NSString *dstPath;
389 | NSFileManager *fm = [NSFileManager defaultManager];
390 |
391 | [NSApp activateIgnoringOtherApps:YES];
392 |
393 | if (self.hasUserLaunchAgent) {
394 |
395 | dstPath = self.userLaunchAgentPath;
396 |
397 | if ([fm removeItemAtPath:dstPath error:&error] == YES) {
398 | alert = [[NSAlert alloc] init];
399 | alert.messageText = @"Launch Agent removed. Changes will take effect on next login.";
400 | }
401 | else {
402 | alert = [NSAlert alertWithError:error];
403 | }
404 | }
405 | else {
406 | NSMutableDictionary *dict = [NSMutableDictionary dictionary];
407 | [dict setObject:@"us.burghardt.Disk-Arbitrator" forKey:@"Label"];
408 | [dict setObject:[NSArray arrayWithObject:[[NSBundle mainBundle] executablePath]] forKey:@"ProgramArguments"];
409 | [dict setObject:[NSNumber numberWithBool:NO] forKey:@"Disabled"];
410 | [dict setObject:[NSNumber numberWithBool:YES] forKey:@"EnableTransactions"];
411 | [dict setObject:[NSNumber numberWithBool:YES] forKey:@"KeepAlive"];
412 |
413 | dstPath = [self userLaunchAgentPath];
414 |
415 | NSData *plistData;
416 | plistData = [NSPropertyListSerialization dataWithPropertyList:dict format:NSPropertyListXMLFormat_v1_0 options:0 error:&error];
417 | if (!plistData) {
418 | alert = [NSAlert alertWithError:error];
419 | } else {
420 | if (![plistData writeToFile:dstPath options:NSDataWritingAtomic error:&error]) {
421 | alert = [NSAlert alertWithError:error];
422 | } else {
423 | alert = [[NSAlert alloc] init];
424 | alert.messageText = @"Launch Agent installed. Changes will take effect on next login.";
425 | }
426 | }
427 | }
428 | if (alert)
429 | [alert runModal];
430 |
431 | [self refreshLaunchAgentStatus];
432 | }
433 |
434 | #pragma mark Selected Disk
435 |
436 | - (Disk *)selectedDisk
437 | {
438 | NSIndexSet *indexes = [disksArrayController selectionIndexes];
439 |
440 | if (indexes.count == 1)
441 | return [disksArrayController.arrangedObjects objectAtIndex:indexes.lastIndex];
442 | else
443 | return nil;
444 | }
445 |
446 | - (BOOL)canEjectSelectedDisk
447 | {
448 | /* "Eject" in the GUI means eject or unmount (like Disk Utility)
449 | * To the Disk class, "ejectable" means the media object is ejectable.
450 | */
451 |
452 | Disk *selectedDisk = self.selectedDisk;
453 | BOOL canEject = [selectedDisk isEjectable];
454 |
455 | if (!canEject) {
456 | for (Disk *child in selectedDisk.children) {
457 | if (child.isMountable && child.isMounted)
458 | canEject = YES;
459 | }
460 | }
461 | return canEject;
462 | }
463 |
464 | - (BOOL)canMountSelectedDisk
465 | {
466 | Disk *disk = self.selectedDisk;
467 |
468 | if (disk.isMountable && !disk.isMounted)
469 | return YES;
470 | else
471 | return NO;
472 | }
473 |
474 | - (BOOL)canUnmountSelectedDisk
475 | {
476 | Disk *disk = self.selectedDisk;
477 |
478 | return (disk.isMountable && disk.isMounted);
479 |
480 | // // Yes if the disk or any children are mounted
481 | //
482 | // if (disk.mountable && disk.mounted) return YES;
483 | //
484 | // for (Disk *child in disk.children)
485 | // if (child.mountable && child.mounted)
486 | // return YES;
487 | }
488 |
489 | #pragma mark TableView Delegates
490 |
491 | // A custom cell is used for the media description column. Couldn't find a way to bind it to the disk
492 | // object, so implemented the dataSource delegate.
493 |
494 | - (id)tableView:(NSTableView *)tv objectValueForTableColumn:(NSTableColumn *)column row:(int)rowIndex
495 | {
496 | Disk *disk;
497 |
498 | NSParameterAssert(rowIndex >= 0 && rowIndex < arbitrator.disks.count);
499 | disk = [disksArrayController.arrangedObjects objectAtIndex:rowIndex];
500 |
501 | if ([column.identifier isEqual:@"BSDName"])
502 | return disk.BSDName;
503 |
504 | // fprintf(stdout, "getting value: %s\n", disk.BSDName.UTF8String);
505 | return disk;
506 | }
507 |
508 | - (NSDragOperation)tableView:(NSTableView*)tv validateDrop:(id )info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)op
509 | {
510 | Log(LOG_DEBUG, @"%s op: %ld info: %@", __func__, op, info);
511 |
512 | NSPasteboard* pboard = [info draggingPasteboard];
513 |
514 | if (op == NSDragOperationCopy && [pboard.types containsObject:NSFilenamesPboardType]) {
515 |
516 | NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
517 | NSArray *extensions = [AttachDiskImageController diskImageFileExtensions];
518 |
519 | for (NSString *file in files) {
520 | if ([extensions containsObject:[file pathExtension]] == NO)
521 | return NSDragOperationNone;
522 | }
523 | return NSDragOperationCopy;
524 | }
525 | return NSDragOperationNone;
526 | }
527 |
528 | - (void)doAttachDiskImageAtPath:(NSString *)path
529 | {
530 | NSError *error;
531 |
532 | AttachDiskImageController *controller = [[AttachDiskImageController alloc] initWithWindowNibName:@"AttachDiskImageAccessory"];
533 | [controller window];
534 |
535 | NSArray *options;
536 | if (arbitrator.isActivated)
537 | options = [NSArray arrayWithObjects:@"-readonly", @"-nomount", nil];
538 | else
539 | options = [NSArray arrayWithObjects:@"-readonly", @"-mount", @"optional", nil];
540 |
541 | if (![controller attachDiskImageAtPath:path options:options password:nil error:&error])
542 | {
543 | [NSApp presentError:error];
544 | }
545 | }
546 |
547 | - (BOOL)tableView:(NSTableView *)aTableView acceptDrop:(id )info
548 | row:(int)row dropOperation:(NSTableViewDropOperation)operation
549 | {
550 | NSPasteboard* pboard = [info draggingPasteboard];
551 |
552 | Log(LOG_DEBUG, @"%s", __func__);
553 |
554 | if (operation == NSDragOperationCopy && [pboard.types containsObject:NSFilenamesPboardType] ) {
555 | NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
556 |
557 | Log(LOG_DEBUG, @"files: %@", files);
558 |
559 | for (NSString *file in files)
560 | [self performSelector:@selector(doAttachDiskImageAtPath:) withObject:file afterDelay:0.01];
561 | }
562 | return YES;
563 | }
564 |
565 | #pragma mark Window Delegates
566 |
567 | - (void)windowWillClose:(NSNotification *)notification
568 | {
569 | [NSApp deactivate];
570 | }
571 |
572 | #pragma mark Disk Notifications
573 |
574 | - (void)didPresentErrorWithRecovery:(BOOL)didRecover contextInfo:(void *)contextInfo
575 | {
576 | // If another sheet has unexpected been displayed, recover gracefully
577 |
578 | if (window.attachedSheet) {
579 | Log(LOG_INFO, @"Discarding pending errors: %@", displayErrorQueue);
580 | [displayErrorQueue removeAllObjects];
581 | return;
582 | }
583 |
584 | if (displayErrorQueue.count > 0)
585 | {
586 | NSError *nextError = [displayErrorQueue objectAtIndex:0];
587 |
588 | [window makeKeyAndOrderFront:self];
589 | [NSApp presentError:nextError
590 | modalForWindow:window
591 | delegate:self
592 | didPresentSelector:@selector(didPresentErrorWithRecovery:contextInfo:)
593 | contextInfo:NULL];
594 |
595 | [displayErrorQueue removeObjectAtIndex:0];
596 | }
597 | }
598 |
599 | - (void)diskDidChange:(NSNotification *)notif
600 | {
601 | NSUInteger row = [disksArrayController.arrangedObjects indexOfObject:notif.object];
602 |
603 | [tableView setNeedsDisplayInRect:[tableView rectOfRow:row]];
604 | [window.toolbar validateVisibleItems];
605 | }
606 |
607 | - (void)didAttemptMount:(NSNotification *)notif
608 | {
609 | Disk *disk = notif.object;
610 | NSMutableDictionary *info;
611 |
612 | if (disk.isMounted) {
613 | Log(LOG_DEBUG, @"%s: Mounted: %@", __func__, disk.BSDName);
614 | }
615 | else {
616 | // If the mount failed, the notification userInfo will have keys/values that correspond to an NSError
617 |
618 | info = [[notif userInfo] mutableCopy];
619 |
620 | NSString *reason = [info objectForKey:NSLocalizedFailureReasonErrorKey];
621 | Log(LOG_ERR, @"Mount failed: %@ (%@) %@", disk.BSDName, [info objectForKey:DAStatusErrorKey], reason);
622 |
623 | [info setObject:[NSString stringWithFormat:@"%@: %@", NSLocalizedString(@"Mount rejected", nil), disk.BSDName]
624 | forKey:NSLocalizedDescriptionKey];
625 |
626 | NSError *error = [NSError errorWithDomain:AppErrorDomain
627 | code:[[info objectForKey:DAStatusErrorKey] intValue]
628 | userInfo:info];
629 |
630 | // Don't show our internal dissenter error
631 | if (![reason isEqualToString:arbitrator.dissenterMessage]) {
632 | if (window.attachedSheet) {
633 | [displayErrorQueue addObject:error];
634 | }
635 | else {
636 | [window makeKeyAndOrderFront:self];
637 | [NSApp presentError:error
638 | modalForWindow:window
639 | delegate:self
640 | didPresentSelector:@selector(didPresentErrorWithRecovery:contextInfo:)
641 | contextInfo:NULL];
642 | }
643 | }
644 | }
645 | [window.toolbar validateVisibleItems];
646 | }
647 |
648 | - (void)didAttemptUnmount:(NSNotification *)notif
649 | {
650 | Disk *disk = notif.object;
651 | NSMutableDictionary *info;
652 |
653 | Log(LOG_DEBUG, @"%s: Unmount %@: %@", __func__, (disk.isMounted ? @"failed" : @"succeeded"), disk.BSDName);
654 |
655 | if (disk.isMounted) {
656 | // If the unmount failed, the notification userInfo will have keys/values that correspond to an NSError
657 |
658 | info = [[notif userInfo] mutableCopy];
659 |
660 | Log(LOG_INFO, @"Unmount %@ failed: (%@) %@", disk.BSDName, [info objectForKey:DAStatusErrorKey], [info objectForKey:NSLocalizedFailureReasonErrorKey]);
661 |
662 | [info setObject:NSLocalizedString(@"Unmount failed", nil) forKey:NSLocalizedDescriptionKey];
663 |
664 | NSError *error = [NSError errorWithDomain:AppErrorDomain
665 | code:[[info objectForKey:DAStatusErrorKey] intValue]
666 | userInfo:info];
667 |
668 | if (window.attachedSheet) {
669 | [displayErrorQueue addObject:error];
670 | }
671 | else {
672 |
673 | [window makeKeyAndOrderFront:self];
674 | [NSApp presentError:error
675 | modalForWindow:window
676 | delegate:self
677 | didPresentSelector:@selector(didPresentErrorWithRecovery:contextInfo:)
678 | contextInfo:NULL];
679 | }
680 | }
681 | [window.toolbar validateVisibleItems];
682 | }
683 |
684 | - (void)didAttemptEject:(NSNotification *)notif
685 | {
686 | Disk *disk = notif.object;
687 |
688 | if ([notif userInfo]) {
689 |
690 | NSMutableDictionary *info = [[notif userInfo] mutableCopy];
691 |
692 | // If the eject failed, the notification userInfo will have keys/values that correspond to an NSError
693 |
694 | Log(LOG_INFO, @"Ejecting %@ failed: (%@) %@", disk.BSDName, [info objectForKey:DAStatusErrorKey], [info objectForKey:NSLocalizedFailureReasonErrorKey]);
695 |
696 | [info setObject:NSLocalizedString(@"Eject failed", nil) forKey:NSLocalizedDescriptionKey];
697 |
698 | NSError *error = [NSError errorWithDomain:AppErrorDomain
699 | code:[[info objectForKey:DAStatusErrorKey] intValue]
700 | userInfo:info];
701 |
702 | if (window.attachedSheet) {
703 | [displayErrorQueue addObject:error];
704 | }
705 | else {
706 |
707 | [window makeKeyAndOrderFront:self];
708 | [NSApp presentError:error
709 | modalForWindow:window
710 | delegate:self
711 | didPresentSelector:@selector(didPresentErrorWithRecovery:contextInfo:)
712 | contextInfo:NULL];
713 | }
714 | }
715 | else {
716 | Log(LOG_DEBUG, @"%s: Ejected: %@", __func__, disk);
717 | }
718 | [window.toolbar validateVisibleItems];
719 | }
720 |
721 | @end
722 |
--------------------------------------------------------------------------------
/Source/DiskArbitrator_Prefix.pch:
--------------------------------------------------------------------------------
1 | //
2 | // Prefix header for all source files of the 'DiskArbitrator' target in the 'DiskArbitrator' project
3 | //
4 |
5 | #ifdef __OBJC__
6 | #import
7 | #endif
8 |
9 | #define SafeCFRelease(v) if (v) { CFRelease(v); }
10 |
--------------------------------------------------------------------------------
/Source/DiskCell.h:
--------------------------------------------------------------------------------
1 | //
2 | // DiskCell.h
3 | // DiskArbitrator
4 | //
5 | // Created by Aaron Burghardt on 1/23/10.
6 | // Copyright 2010 Aaron Burghardt. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface DiskCell : NSTextFieldCell
12 |
13 | @property (retain) NSImageCell *iconCell;
14 | @property (retain) NSCell *textCell;
15 | @property CGFloat indentation;
16 | @property BOOL mountable;
17 | @property BOOL mounted;
18 | @property BOOL isDiskWritable;
19 | @property BOOL isFileSystemWritable;
20 | @property (copy) NSString *BSDName;
21 | @property (copy) NSString *mediaName;
22 | @property (copy) NSNumber *mediaSize;
23 | @property (copy) NSString *volumeName;
24 |
25 | @end
26 |
--------------------------------------------------------------------------------
/Source/DiskCell.m:
--------------------------------------------------------------------------------
1 | //
2 | // DiskCell.m
3 | // DiskArbitrator
4 | //
5 | // Created by Aaron Burghardt on 1/23/10.
6 | // Copyright 2010 Aaron Burghardt. All rights reserved.
7 | //
8 |
9 | #import "DiskCell.h"
10 | #import "AppError.h"
11 | #import "Disk.h"
12 |
13 |
14 | #define ICONPADDING 3.0
15 |
16 | @implementation DiskCell
17 |
18 | @synthesize iconCell;
19 | @synthesize textCell;
20 | @synthesize indentation;
21 | @synthesize mountable;
22 | @synthesize mounted;
23 | @synthesize isDiskWritable;
24 | @synthesize isFileSystemWritable;
25 | @synthesize mediaName;
26 | @synthesize mediaSize;
27 | @synthesize BSDName;
28 | @synthesize volumeName;
29 |
30 |
31 | - (id)copyWithZone:(NSZone *)zone
32 | {
33 | id obj = [super copyWithZone:zone];
34 | if (iconCell) iconCell = [iconCell copyWithZone:zone];
35 | if (textCell) textCell = [textCell copyWithZone:zone];
36 | [BSDName retain];
37 | [mediaName retain];
38 | [mediaSize retain];
39 | [volumeName retain];
40 |
41 | return obj;
42 | }
43 |
44 | - (void)dealloc
45 | {
46 | [iconCell release];
47 | [textCell release];
48 | [BSDName release];
49 | [mediaName release];
50 | [mediaSize release];
51 | [volumeName release];
52 |
53 | [super dealloc];
54 | }
55 |
56 | - (void)drawInteriorWithFrame:(NSRect)frame inView:(NSView *)controlView
57 | {
58 | // Log(LOG_DEBUG, @"Frame: %s\t\tView Frame: %@", NSStringFromRect(frame), NSStringFromRect([view frame]));
59 |
60 | NSRect iconFrame, textFrame;
61 |
62 | CGFloat minwh = MIN(frame.size.width - indentation, frame.size.height);
63 | iconFrame = NSMakeRect(frame.origin.x + indentation + ICONPADDING, frame.origin.y, minwh, minwh);
64 |
65 | iconCell.enabled = (mountable && !mounted) ? NO : YES; // dimmed if a volume but not mounted
66 | iconCell.highlighted = self.isHighlighted;
67 | [iconCell drawWithFrame:iconFrame inView:controlView];
68 |
69 | // A disk may be mounted R/O even though the underlying disk is R/W. To avoid giving the false
70 | // impression that a disk is completely protected, display a transparent, faint lock when the
71 | // disk is R/W, and display a solid black lock when the disk and the FS are both R/O.
72 |
73 | if (mountable && mounted && !isFileSystemWritable) {
74 |
75 | NSImage *lockImage = [NSImage imageNamed:NSImageNameLockLockedTemplate];
76 |
77 | CGFloat scale;
78 | if (frame.size.height <= 16.0)
79 | scale = 0.5;
80 | else if (frame.size.height <= 32.0)
81 | scale = 0.75;
82 | else if (frame.size.height <= 64.0)
83 | scale = 2.0;
84 | else if (frame.size.height <= 128.0)
85 | scale = 4.0;
86 | else if (frame.size.height <= 256.0)
87 | scale = 8.0;
88 | else if (frame.size.height <= 512.0)
89 | scale = 16.0;
90 | else if (frame.size.height <= 1024.0)
91 | scale = 32.0;
92 | else
93 | scale = 2.0;
94 |
95 | CGFloat opacity = isDiskWritable ? 0.40 : 1.0;
96 |
97 | CGFloat scaledWidth = lockImage.size.width * scale;
98 | CGFloat scaledHeight = lockImage.size.height * scale;
99 | NSRect rect = NSMakeRect(NSMaxX(iconFrame) - scaledWidth, NSMaxY(iconFrame) - scaledHeight, scaledWidth, scaledHeight);
100 |
101 | [NSGraphicsContext saveGraphicsState];
102 | if ([controlView isFlipped]) {
103 | NSAffineTransform *transform = [NSAffineTransform transform];
104 | [transform translateXBy:NSMidX(rect) yBy:NSMidY(rect)];
105 | [transform rotateByDegrees:180];
106 | [transform translateXBy:-NSMidX(rect) yBy:-NSMidY(rect)];
107 | [transform concat];
108 | }
109 |
110 | [lockImage drawInRect:rect fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:opacity];
111 |
112 | [NSGraphicsContext restoreGraphicsState];
113 | }
114 |
115 | textFrame = NSMakeRect(NSMaxX(iconFrame) + ICONPADDING, frame.origin.y,
116 | MAX(NSWidth(frame) - (NSMaxX(iconFrame) + ICONPADDING), 0.0), NSHeight(frame));
117 | NSSize textCellSize = textCell.cellSize;
118 | textFrame.origin.y = frame.origin.y + floor((frame.size.height - textCellSize.height) / 2);
119 |
120 | textCell.enabled = (mountable && !mounted) ? NO : YES; // dimmed if a volume but not mounted
121 | textCell.highlighted = self.isHighlighted;
122 | [textCell drawWithFrame:textFrame inView:controlView];
123 | }
124 |
125 |
126 | - (void)setObjectValue:(id)value
127 | {
128 | Disk *disk = (Disk *)value;
129 |
130 | if (disk) {
131 | // Log(LOG_DEBUG, @"%s self: %p disk: %p", __func__, self, disk);
132 |
133 | self.BSDName = disk.BSDName;
134 | self.indentation = disk.isWholeDisk ? 0.0 : 17.0;
135 | self.mounted = disk.isMounted;
136 | self.mountable = disk.isMountable;
137 | self.isDiskWritable = disk.isWritable;
138 | self.isFileSystemWritable = disk.isFileSystemWritable;
139 |
140 | self.mediaName = (NSString *)[disk.diskDescription objectForKey: (NSString *)kDADiskDescriptionMediaNameKey];
141 | self.mediaSize = (NSNumber *)[disk.diskDescription objectForKey: (NSString *)kDADiskDescriptionMediaSizeKey];
142 | self.volumeName = (NSString *)[disk.diskDescription objectForKey: (NSString *)kDADiskDescriptionVolumeNameKey];
143 |
144 | // Create Text description cell
145 |
146 | NSString *sizeDisplayValue = nil;
147 | if (mediaSize) {
148 | double size = [mediaSize doubleValue];
149 | if (size > 999 && size < 1000000)
150 | sizeDisplayValue = [NSString stringWithFormat:@"%03.02f KB ", (size / 1000.0)];
151 | else if (size > 999999 && size < 1000000000)
152 | sizeDisplayValue = [NSString stringWithFormat:@"%03.02f MB ", (size / 1000000.0)];
153 | else if (size > 999999999 && size < 1000000000000)
154 | sizeDisplayValue = [NSString stringWithFormat:@"%03.02f GB ", (size / 1000000000.0)];
155 | else if (size > 999999999999)
156 | sizeDisplayValue = [NSString stringWithFormat:@"%03.02f TB ", (size / 1000000000000.0)];
157 | }
158 | NSMutableString *desc = sizeDisplayValue ? [sizeDisplayValue mutableCopy] : [NSMutableString new];
159 |
160 | if (volumeName)
161 | [desc appendString:volumeName];
162 | else if (mediaName)
163 | [desc appendString:mediaName];
164 |
165 | self.textCell = [[[NSCell alloc] initTextCell:desc] autorelease];
166 | self.textCell.lineBreakMode = NSLineBreakByTruncatingTail;
167 | [desc release];
168 |
169 | // Create Icon cell
170 |
171 | self.iconCell = [[[NSImageCell alloc] initImageCell:disk.icon] autorelease];
172 | iconCell.imageScaling = NSImageScaleProportionallyDown;
173 | iconCell.alignment = NSTextAlignmentLeft;
174 | }
175 | else {
176 | self.textCell = nil;
177 | self.iconCell = nil;
178 | }
179 | }
180 |
181 | @end
182 |
--------------------------------------------------------------------------------
/Source/DiskInfoController.h:
--------------------------------------------------------------------------------
1 | //
2 | // DiskInfoController.h
3 | // DiskArbitrator
4 | //
5 | // Created by Aaron Burghardt on 2/11/10.
6 | // Copyright 2010 . All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @class Disk;
12 |
13 | @interface DiskInfoController : NSWindowController
14 |
15 | @property (strong) IBOutlet NSTextView *textView;
16 | @property (strong, nonatomic) Disk *disk;
17 | @property (copy) NSDictionary *diskDescription;
18 | @property (copy) NSAttributedString *diskInfo;
19 |
20 | - (void)refreshDiskInfo;
21 |
22 | @end
23 |
--------------------------------------------------------------------------------
/Source/DiskInfoController.m:
--------------------------------------------------------------------------------
1 | //
2 | // DiskInfoController.m
3 | // DiskArbitrator
4 | //
5 | // Created by Aaron Burghardt on 2/11/10.
6 | // Copyright 2010 . All rights reserved.
7 | //
8 |
9 | #import "DiskInfoController.h"
10 | #import "AppError.h"
11 | #import
12 | #import "Disk.h"
13 |
14 |
15 | @implementation DiskInfoController
16 |
17 | @synthesize textView;
18 | @synthesize diskDescription;
19 | @synthesize diskInfo;
20 |
21 | + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
22 | {
23 | if ([key isEqual:@"diskDescription"])
24 | return [NSSet setWithObject:@"disk.diskDescription"];
25 |
26 | return [super keyPathsForValuesAffectingValueForKey:key];
27 | }
28 |
29 | - (void)dealloc
30 | {
31 | [[NSNotificationCenter defaultCenter] removeObserver:self];
32 | }
33 |
34 | - (NSString *)localizedStringForDADiskKey:(NSString *)key
35 | {
36 | static dispatch_once_t sOnceToken;
37 | static NSDictionary *localizedStrings = nil;
38 | dispatch_once(&sOnceToken, ^{
39 | localizedStrings =
40 | @{
41 | (NSString *)kDADiskDescriptionVolumeKindKey: NSLocalizedString(@"Volume Kind", nil),
42 | (NSString *)kDADiskDescriptionVolumeMountableKey: NSLocalizedString(@"Volume Mountable", nil),
43 | (NSString *)kDADiskDescriptionVolumeNameKey: NSLocalizedString(@"Volume Name", nil),
44 | (NSString *)kDADiskDescriptionVolumeNetworkKey: NSLocalizedString(@"Volume Network", nil),
45 | (NSString *)kDADiskDescriptionVolumePathKey: NSLocalizedString(@"Mount Path", nil),
46 | (NSString *)kDADiskDescriptionVolumeTypeKey: NSLocalizedString(@"Volume Type", nil),
47 | (NSString *)kDADiskDescriptionVolumeUUIDKey: NSLocalizedString(@"Volume UUID", nil),
48 | (NSString *)kDADiskDescriptionMediaBlockSizeKey: NSLocalizedString(@"Media Block Size", nil),
49 | (NSString *)kDADiskDescriptionMediaBSDMajorKey: NSLocalizedString(@"Media BSD Major", nil),
50 | (NSString *)kDADiskDescriptionMediaBSDMinorKey: NSLocalizedString(@"Media BSD Minor", nil),
51 | (NSString *)kDADiskDescriptionMediaBSDNameKey: NSLocalizedString(@"Media BSD Name", nil),
52 | (NSString *)kDADiskDescriptionMediaBSDUnitKey: NSLocalizedString(@"Media BSD Unit", nil),
53 | (NSString *)kDADiskDescriptionMediaContentKey: NSLocalizedString(@"Media Content", nil),
54 | (NSString *)kDADiskDescriptionMediaEjectableKey: NSLocalizedString(@"Media Ejectable", nil),
55 | (NSString *)kDADiskDescriptionMediaIconKey: NSLocalizedString(@"Media Icon", nil),
56 | (NSString *)kDADiskDescriptionMediaKindKey: NSLocalizedString(@"Media Kind", nil),
57 | (NSString *)kDADiskDescriptionMediaLeafKey: NSLocalizedString(@"Media Is Leaf", nil),
58 | (NSString *)kDADiskDescriptionMediaNameKey: NSLocalizedString(@"Media Name", nil),
59 | (NSString *)kDADiskDescriptionMediaPathKey: NSLocalizedString(@"Media Path", nil),
60 | (NSString *)kDADiskDescriptionMediaRemovableKey: NSLocalizedString(@"Media Is Removable", nil),
61 | (NSString *)kDADiskDescriptionMediaSizeKey: NSLocalizedString(@"Media Size", nil),
62 | (NSString *)kDADiskDescriptionMediaTypeKey: NSLocalizedString(@"Media Type", nil),
63 | (NSString *)kDADiskDescriptionMediaUUIDKey: NSLocalizedString(@"Media UUID", nil),
64 | (NSString *)kDADiskDescriptionMediaWholeKey: NSLocalizedString(@"Media Is Whole", nil),
65 | (NSString *)kDADiskDescriptionMediaWritableKey: NSLocalizedString(@"Media Is Writable", nil),
66 | (NSString *)kDADiskDescriptionMediaEncryptedKey: NSLocalizedString(@"Encrypted", nil),
67 | (NSString *)kDADiskDescriptionMediaEncryptionDetailKey: NSLocalizedString(@"Encryption Detail", nil),
68 | (NSString *)kDADiskDescriptionDeviceGUIDKey: NSLocalizedString(@"Device GUID", nil),
69 | (NSString *)kDADiskDescriptionDeviceInternalKey: NSLocalizedString(@"Device Is Internal", nil),
70 | (NSString *)kDADiskDescriptionDeviceModelKey: NSLocalizedString(@"Device Model", nil),
71 | (NSString *)kDADiskDescriptionDevicePathKey: NSLocalizedString(@"Device Path", nil),
72 | (NSString *)kDADiskDescriptionDeviceProtocolKey: NSLocalizedString(@"Device Protocol", nil),
73 | (NSString *)kDADiskDescriptionDeviceRevisionKey: NSLocalizedString(@"Device Revision", nil),
74 | (NSString *)kDADiskDescriptionDeviceUnitKey: NSLocalizedString(@"Device Unit", nil),
75 | (NSString *)kDADiskDescriptionDeviceVendorKey: NSLocalizedString(@"Device Vendor", nil),
76 | (NSString *)kDADiskDescriptionDeviceTDMLockedKey: NSLocalizedString(@"TDM Locked", nil),
77 | (NSString *)kDADiskDescriptionBusNameKey: NSLocalizedString(@"Bus", nil),
78 | (NSString *)kDADiskDescriptionBusPathKey: NSLocalizedString(@"Bus Path", nil),
79 | @"DAAppearanceTime": NSLocalizedString(@"Appearance Time", nil)
80 | };
81 | });
82 |
83 | NSString *description = localizedStrings[key];
84 |
85 | if (nil == description)
86 | {
87 | Log(LOG_INFO, @"Unknown disk description key: %@", key);
88 | description = @"N/A";
89 | }
90 |
91 | return description;
92 | }
93 |
94 | - (NSString *)formattedSizeDescriptionFromNumber:(NSNumber *)sizeValue
95 | {
96 | NSString *formattedValue;
97 |
98 | double size = [sizeValue doubleValue];
99 |
100 | if (size > 999.0 && size < 1000000.0)
101 | formattedValue = [NSString stringWithFormat:@"%03.02f KB (%@ bytes)", (size / 1000.0), sizeValue];
102 | else if (size > 999999.0 && size < 1000000000.0)
103 | formattedValue = [NSString stringWithFormat:@"%03.02f MB (%@ bytes)", (size / 1000000.0), sizeValue];
104 | else if (size > 999999999.0 && size < 1000000000000.0)
105 | formattedValue = [NSString stringWithFormat:@"%03.02f GB (%@ bytes)", (size / 1000000000.0), sizeValue];
106 | else if (size > 999999999999.0)
107 | formattedValue = [NSString stringWithFormat:@"%03.02f TB (%@ bytes)", (size / 1000000000000.0), sizeValue];
108 | else
109 | formattedValue = sizeValue.stringValue;
110 |
111 | return formattedValue;
112 | }
113 |
114 | - (NSString *)localizedValueStringForDADiskKey:(NSString *)key value:(id)value
115 | {
116 | CFStringRef keyRef = (__bridge CFStringRef) key;
117 |
118 | if (CFEqual(keyRef, kDADiskDescriptionVolumeKindKey)) /* ( CFString ) */
119 | return value;
120 |
121 | if (CFEqual(keyRef, kDADiskDescriptionVolumeMountableKey) || /* ( CFBoolean ) */
122 | CFEqual(keyRef, kDADiskDescriptionVolumeNetworkKey) ||
123 | CFEqual(keyRef, kDADiskDescriptionMediaLeafKey) ||
124 | CFEqual(keyRef, kDADiskDescriptionMediaEjectableKey) ||
125 | CFEqual(keyRef, kDADiskDescriptionMediaRemovableKey) ||
126 | CFEqual(keyRef, kDADiskDescriptionMediaWholeKey) ||
127 | CFEqual(keyRef, kDADiskDescriptionMediaWritableKey) ||
128 | CFEqual(keyRef, kDADiskDescriptionDeviceInternalKey)
129 | )
130 | return [value boolValue] ? @"Yes" : @"No";
131 |
132 |
133 | if (CFEqual(keyRef, kDADiskDescriptionVolumeNameKey)) /* ( CFString ) */
134 | return value;
135 |
136 | if (CFEqual(keyRef, kDADiskDescriptionVolumePathKey)) /* ( CFURL ) */
137 | return [(NSURL *)value path];
138 |
139 | if (CFEqual(keyRef, kDADiskDescriptionVolumeUUIDKey) || /* ( CFUUID ) */
140 | CFEqual(keyRef, kDADiskDescriptionMediaUUIDKey))
141 | {
142 | NSString *uuidString = (NSString *) CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, (CFUUIDRef)value));
143 |
144 | return uuidString;
145 | }
146 |
147 | if (CFEqual(keyRef, kDADiskDescriptionMediaBlockSizeKey)) /* ( CFNumber ) */
148 | return [value stringValue];
149 |
150 | if (CFEqual(keyRef, kDADiskDescriptionMediaBSDMajorKey)) /* ( CFNumber ) */
151 | return [value stringValue];
152 |
153 | if (CFEqual(keyRef, kDADiskDescriptionMediaBSDMinorKey)) /* ( CFNumber ) */
154 | return [value stringValue];
155 |
156 | if (CFEqual(keyRef, kDADiskDescriptionMediaBSDNameKey)) /* ( CFString ) */
157 | return value;
158 |
159 | if (CFEqual(keyRef, kDADiskDescriptionMediaBSDUnitKey)) /* ( CFNumber ) */
160 | return [value stringValue];
161 |
162 | if (CFEqual(keyRef, kDADiskDescriptionMediaContentKey)) /* ( CFString ) */
163 | return value;
164 |
165 | if (CFEqual(keyRef, kDADiskDescriptionMediaIconKey)) /* ( CFDictionary ) */
166 | return [value description];
167 |
168 | if (CFEqual(keyRef, kDADiskDescriptionMediaKindKey)) /* ( CFString ) */
169 | return value;
170 |
171 | if (CFEqual(keyRef, kDADiskDescriptionMediaNameKey)) /* ( CFString ) */
172 | return value;
173 |
174 | if (CFEqual(keyRef, kDADiskDescriptionMediaPathKey)) /* ( CFString ) */
175 | return value;
176 |
177 | if (CFEqual(keyRef, kDADiskDescriptionMediaSizeKey)) /* ( CFNumber ) */
178 | return [self formattedSizeDescriptionFromNumber:(NSNumber *)value];
179 |
180 | if (CFEqual(keyRef, kDADiskDescriptionMediaTypeKey)) /* ( CFString ) */
181 | return value;
182 |
183 | if (CFEqual(keyRef, kDADiskDescriptionDeviceGUIDKey)) /* ( CFData ) */
184 | return [value description];
185 |
186 | if (CFEqual(keyRef, kDADiskDescriptionDeviceModelKey)) /* ( CFString ) */
187 | return value;
188 |
189 | if (CFEqual(keyRef, kDADiskDescriptionDevicePathKey)) /* ( CFString ) */
190 | return value;
191 |
192 | if (CFEqual(keyRef, kDADiskDescriptionDeviceProtocolKey)) /* ( CFString ) */
193 | return value;
194 |
195 | if (CFEqual(keyRef, kDADiskDescriptionDeviceRevisionKey)) /* ( CFString ) */
196 | return value;
197 |
198 | if (CFEqual(keyRef, kDADiskDescriptionDeviceUnitKey)) /* ( CFNumber ) */
199 | return [value stringValue];
200 |
201 | if (CFEqual(keyRef, kDADiskDescriptionDeviceVendorKey)) /* ( CFString ) */
202 | return value;
203 |
204 | if (CFEqual(keyRef, kDADiskDescriptionBusNameKey)) /* ( CFString ) */
205 | return value;
206 |
207 | if (CFEqual(keyRef, kDADiskDescriptionBusPathKey)) /* ( CFString ) */
208 | return value;
209 |
210 | if (CFEqual(keyRef, CFSTR("DAAppearanceTime")))
211 | return [[NSDate dateWithTimeIntervalSinceReferenceDate:[value doubleValue]] description];
212 |
213 | Log(LOG_INFO, @"Unknown disk description key: %@", keyRef);
214 |
215 | return @"N/A";
216 | }
217 |
218 | - (NSString *)stringForDADiskKey:(NSString *)key value:(id)value
219 | {
220 | return [NSString stringWithFormat:@"\t%@\t%@\n",
221 | [self localizedStringForDADiskKey:key],
222 | [self localizedValueStringForDADiskKey:key value:value]];
223 | }
224 |
225 | - (void)refreshDiskInfo
226 | {
227 | self.diskDescription = self.disk.diskDescription;
228 |
229 | NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithString:@""];
230 |
231 | NSFont *font = [NSFont fontWithName:@"Helvetica Bold" size:12.0];
232 | NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, nil];
233 |
234 | NSArray *keys = [self.disk.diskDescription allKeys];
235 | keys = [keys sortedArrayUsingSelector:@selector(compare:)];
236 |
237 | for (NSString *key in keys)
238 | {
239 | // Ignore certain keys
240 | if ([key isEqual: (NSString *)kDADiskDescriptionMediaIconKey])
241 | continue;
242 |
243 | id value = [self.disk.diskDescription objectForKey:key];
244 |
245 | NSString *string;
246 | NSAttributedString *attrString;
247 |
248 | string = [NSString stringWithFormat:@"\t%@\t", [self localizedStringForDADiskKey:key]];
249 | attrString = [[NSAttributedString alloc] initWithString:string attributes:attrs];
250 | [text appendAttributedString:attrString];
251 |
252 | string = [NSString stringWithFormat:@"%@\n", [self localizedValueStringForDADiskKey:key value:value]];
253 | attrString = [[NSAttributedString alloc] initWithString:string];
254 | [text appendAttributedString:attrString];
255 | }
256 |
257 |
258 | NSMutableParagraphStyle *style = [NSMutableParagraphStyle new];
259 | NSMutableArray *tabStops = [NSMutableArray array];
260 | [tabStops addObject:[[NSTextTab alloc] initWithType:NSRightTabStopType location:2.0 * 72.0]];
261 | [tabStops addObject:[[NSTextTab alloc] initWithType:NSLeftTabStopType location:2.125 * 72.0]];
262 | style.tabStops = tabStops;
263 | style.headIndent = (2.125 * 72.0);
264 |
265 | attrs = [NSDictionary dictionaryWithObjectsAndKeys:style, NSParagraphStyleAttributeName, nil];
266 | [text addAttributes:attrs range:NSMakeRange(0, [text length])];
267 |
268 | self.diskInfo = text;
269 | }
270 |
271 | - (void)diskDidChange:(NSNotification *)notif
272 | {
273 | [self refreshDiskInfo];
274 | }
275 |
276 | - (void)setDisk:(Disk *)newDisk
277 | {
278 | if (newDisk != _disk) {
279 | [[NSNotificationCenter defaultCenter] removeObserver:self
280 | name:DADiskDidChangeNotification object:_disk];
281 | _disk = newDisk;
282 | [[NSNotificationCenter defaultCenter] addObserver:self
283 | selector:@selector(diskDidChange:) name:DADiskDidChangeNotification
284 | object:_disk];
285 | }
286 | }
287 |
288 | @end
289 |
--------------------------------------------------------------------------------
/Source/SheetController.h:
--------------------------------------------------------------------------------
1 | //
2 | // SheetController.h
3 | // DiskArbitrator
4 | //
5 | // Created by Aaron Burghardt on 2/10/10.
6 | // Copyright 2010 . All rights reserved.
7 | //
8 |
9 | #import
10 |
11 |
12 | @interface SheetController : NSWindowController
13 |
14 | @property (strong) NSMutableDictionary *userInfo;
15 |
16 | - (IBAction)alternate:(id)sender;
17 | - (IBAction)cancel:(id)sender;
18 | - (IBAction)ok:(id)sender;
19 |
20 | @end
21 |
--------------------------------------------------------------------------------
/Source/SheetController.m:
--------------------------------------------------------------------------------
1 | //
2 | // SheetController.m
3 | // DiskArbitrator
4 | //
5 | // Created by Aaron Burghardt on 2/10/10.
6 | // Copyright 2010 . All rights reserved.
7 | //
8 |
9 | #import "SheetController.h"
10 |
11 |
12 | @implementation SheetController
13 |
14 | @synthesize userInfo;
15 |
16 | - (void)windowWillLoad
17 | {
18 | self.userInfo = [NSMutableDictionary dictionary];
19 | }
20 |
21 | - (IBAction)alternate:(id)sender
22 | {
23 | [self.window endEditingFor:nil];
24 | [NSApp endSheet:self.window returnCode:-1];
25 | }
26 |
27 | - (IBAction)cancel:(id)sender
28 | {
29 | [NSApp endSheet:self.window returnCode:NSModalResponseCancel];
30 | }
31 |
32 | - (IBAction)ok:(id)sender
33 | {
34 | [self.window endEditingFor:nil];
35 | [NSApp endSheet:self.window returnCode:NSModalResponseOK];
36 | }
37 |
38 | @end
39 |
--------------------------------------------------------------------------------
/Source/build_dmg.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # build_dmg.sh
4 | # DiskArbitrator
5 | #
6 | # Created by Aaron Burghardt on 2/6/10.
7 | # Copyright 2010 . All rights reserved.
8 |
9 | set -e # fail on any error
10 |
11 | SRC_PRODUCT="$1"
12 | SRC_PRODUCT_PATH="${BUILT_PRODUCTS_DIR}/${SRC_PRODUCT}.app"
13 | SRC_PRODUCT_CONTENTS="${SRC_PRODUCT_PATH}/Contents"
14 | SRC_PRODUCT_RESOURCES="${SRC_PRODUCT_CONTENTS}/Resources"
15 | SRC_PRODUCT_VERSION=`/usr/libexec/PlistBuddy -c 'Print CFBundleShortVersionString' "${SRC_PRODUCT_CONTENTS}/Info.plist"`
16 |
17 | echo "Building DMG for ${SRC_PRODUCT} ${SRC_PRODUCT_VERSION}"
18 |
19 | DMG_DST_PATH="${BUILT_PRODUCTS_DIR}/${SRC_PRODUCT}-${SRC_PRODUCT_VERSION}.dmg"
20 |
21 | DMG_SRC_DIR="${CONFIGURATION_TEMP_DIR}/${SRC_PRODUCT}-${SRC_PRODUCT_VERSION}"
22 |
23 | if [ -e "$DMG_DST_PATH" ] ; then
24 | rm -rf "$DMG_DST_PATH"
25 | fi
26 |
27 | mkdir -p "${DMG_SRC_DIR}"
28 | cp -LR "${SRC_PRODUCT_PATH}" "${DMG_SRC_DIR}"
29 | cp "${SRC_PRODUCT_PATH}/Contents/Resources/README.html" "${DMG_SRC_DIR}"
30 | cp "${SRC_PRODUCT_PATH}/Contents/Resources/ReleaseNotes.html" "${DMG_SRC_DIR}"
31 |
32 | hdiutil create -layout NONE -srcfolder "${DMG_SRC_DIR}" "$DMG_DST_PATH"
33 | hdiutil verify "$DMG_DST_PATH"
34 |
35 | rm -r "${DMG_SRC_DIR}"
36 |
37 | # Reveal the file in Finder
38 | open -R "$DMG_DST_PATH"
39 |
--------------------------------------------------------------------------------
/Source/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // DiskArbitrator
4 | //
5 | // Created by Aaron Burghardt on 1/10/10.
6 | // Copyright 2010 Aaron Burghardt. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | int main(int argc, char *argv[])
12 | {
13 | return NSApplicationMain(argc, (const char **) argv);
14 | }
15 |
--------------------------------------------------------------------------------