blob: 8bd3e414dcfee3145900b332d87685a07e903bfc
1 | /* |
2 | * Copyright (C) 2015 The Android Open Source Project |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | */ |
16 | |
17 | #include "Disk.h" |
18 | #include "PublicVolume.h" |
19 | #include "Utils.h" |
20 | #include "VolumeBase.h" |
21 | #include "VolumeManager.h" |
22 | #include "ResponseCode.h" |
23 | |
24 | #include <android-base/file.h> |
25 | #include <android-base/stringprintf.h> |
26 | #include <android-base/logging.h> |
27 | |
28 | #include <vector> |
29 | #include <fcntl.h> |
30 | #include <inttypes.h> |
31 | #include <stdio.h> |
32 | #include <stdlib.h> |
33 | #include <sys/types.h> |
34 | #include <sys/stat.h> |
35 | #include <sys/mount.h> |
36 | #include <sys/sysmacros.h> |
37 | |
38 | using android::base::ReadFileToString; |
39 | using android::base::WriteStringToFile; |
40 | using android::base::StringPrintf; |
41 | |
42 | namespace android { |
43 | namespace droidvold { |
44 | |
45 | static const char* kSgdiskPath = "/system/bin/sgdisk"; |
46 | static const char* kSgdiskToken = " \t\n"; |
47 | |
48 | static const char* kSysfsMmcMaxMinors = "/sys/module/mmcblk/parameters/perdev_minors"; |
49 | |
50 | static const unsigned int kMajorBlockScsiA = 8; |
51 | static const unsigned int kMajorBlockSr = 11; |
52 | static const unsigned int kMajorBlockScsiB = 65; |
53 | static const unsigned int kMajorBlockScsiC = 66; |
54 | static const unsigned int kMajorBlockScsiD = 67; |
55 | static const unsigned int kMajorBlockScsiE = 68; |
56 | static const unsigned int kMajorBlockScsiF = 69; |
57 | static const unsigned int kMajorBlockScsiG = 70; |
58 | static const unsigned int kMajorBlockScsiH = 71; |
59 | static const unsigned int kMajorBlockScsiI = 128; |
60 | static const unsigned int kMajorBlockScsiJ = 129; |
61 | static const unsigned int kMajorBlockScsiK = 130; |
62 | static const unsigned int kMajorBlockScsiL = 131; |
63 | static const unsigned int kMajorBlockScsiM = 132; |
64 | static const unsigned int kMajorBlockScsiN = 133; |
65 | static const unsigned int kMajorBlockScsiO = 134; |
66 | static const unsigned int kMajorBlockScsiP = 135; |
67 | static const unsigned int kMajorBlockMmc = 179; |
68 | static const unsigned int kMajorBlockExperimentalMin = 240; |
69 | static const unsigned int kMajorBlockExperimentalMax = 254; |
70 | |
71 | static const char* kGptBasicData = "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7"; |
72 | static const char* kGptAndroidMeta = "19A710A2-B3CA-11E4-B026-10604B889DCF"; |
73 | static const char* kGptAndroidExpand = "193D1EA4-B3CA-11E4-B075-10604B889DCF"; |
74 | |
75 | enum class Table { |
76 | kUnknown, |
77 | kMbr, |
78 | kGpt, |
79 | }; |
80 | |
81 | static bool isVirtioBlkDevice(unsigned int major) { |
82 | /* |
83 | * The new emulator's "ranchu" virtual board no longer includes a goldfish |
84 | * MMC-based SD card device; instead, it emulates SD cards with virtio-blk, |
85 | * which has been supported by upstream kernel and QEMU for quite a while. |
86 | * Unfortunately, the virtio-blk block device driver does not use a fixed |
87 | * major number, but relies on the kernel to assign one from a specific |
88 | * range of block majors, which are allocated for "LOCAL/EXPERIMENAL USE" |
89 | * per Documentation/devices.txt. This is true even for the latest Linux |
90 | * kernel (4.4; see init() in drivers/block/virtio_blk.c). |
91 | * |
92 | * This makes it difficult for vold to detect a virtio-blk based SD card. |
93 | * The current solution checks two conditions (both must be met): |
94 | * |
95 | * a) If the running environment is the emulator; |
96 | * b) If the major number is an experimental block device major number (for |
97 | * x86/x86_64 3.10 ranchu kernels, virtio-blk always gets major number |
98 | * 253, but it is safer to match the range than just one value). |
99 | * |
100 | * Other conditions could be used, too, e.g. the hardware name should be |
101 | * "ranchu", the device's sysfs path should end with "/block/vd[d-z]", etc. |
102 | * But just having a) and b) is enough for now. |
103 | */ |
104 | return IsRunningInEmulator() && major >= kMajorBlockExperimentalMin |
105 | && major <= kMajorBlockExperimentalMax; |
106 | } |
107 | |
108 | Disk::Disk(const std::string& eventPath, dev_t device, |
109 | const std::string& nickname, const std::string& eventName, int flags): |
110 | mDevice(device), mSize(-1), mNickname(nickname), mFlags(flags), mCreated( |
111 | false), mJustPartitioned(false) { |
112 | mId = StringPrintf("disk:%u,%u", major(device), minor(device)); |
113 | mEventPath = eventPath; |
114 | mDevName = eventName; |
115 | mSysPath = StringPrintf("/sys/%s", eventPath.c_str()); |
116 | mDevPath = StringPrintf("/dev/block/%s", mDevName.c_str()); |
117 | |
118 | mSrdisk = (!strncmp(nickname.c_str(), "sr", 2)) ? true : false; |
119 | } |
120 | |
121 | Disk::~Disk() { |
122 | CHECK(!mCreated); |
123 | } |
124 | |
125 | std::shared_ptr<VolumeBase> Disk::findVolume(const std::string& id) { |
126 | for (auto vol : mVolumes) { |
127 | if (vol->getId() == id) { |
128 | return vol; |
129 | } |
130 | auto stackedVol = vol->findVolume(id); |
131 | if (stackedVol != nullptr) { |
132 | return stackedVol; |
133 | } |
134 | } |
135 | return nullptr; |
136 | } |
137 | |
138 | std::shared_ptr<VolumeBase> Disk::findVolumeByPath(const std::string& path) { |
139 | for (auto vol : mVolumes) { |
140 | if (vol->getPath() == path) { |
141 | return vol; |
142 | } |
143 | } |
144 | return nullptr; |
145 | } |
146 | |
147 | void Disk::listVolumes(VolumeBase::Type type, std::list<std::string>& list) { |
148 | for (auto vol : mVolumes) { |
149 | if (vol->getType() == type) { |
150 | list.push_back(vol->getId()); |
151 | } |
152 | // TODO: consider looking at stacked volumes |
153 | } |
154 | } |
155 | |
156 | status_t Disk::create() { |
157 | CHECK(!mCreated); |
158 | mCreated = true; |
159 | notifyEvent(ResponseCode::DiskCreated, StringPrintf("%d", mFlags)); |
160 | |
161 | // do nothing when srdisk is created |
162 | if (mSrdisk) |
163 | return OK; |
164 | |
165 | readDiskMetadata(); |
166 | // sleep 10ms |
167 | usleep(10000); |
168 | |
169 | std::string fsType; |
170 | std::string unused; |
171 | |
172 | // if no partition, handle here |
173 | if (ReadPartMetadata(mDevPath, fsType, unused, unused) == OK) { |
174 | if (!fsType.empty()) { |
175 | if (VolumeManager::Instance()->getDebug()) |
176 | LOG(DEBUG) << "treat entire disk as partition, devPath=" << mDevPath; |
177 | createPublicVolume(mDevName, true, 0); |
178 | } |
179 | } |
180 | |
181 | return OK; |
182 | } |
183 | |
184 | status_t Disk::reset() { |
185 | CHECK(!mCreated); |
186 | mCreated = true; |
187 | notifyEvent(ResponseCode::DiskCreated, StringPrintf("%d", mFlags)); |
188 | |
189 | // do nothing when srdisk is created |
190 | if (mSrdisk) |
191 | return OK; |
192 | |
193 | readDiskMetadata(); |
194 | |
195 | if (mPartNo.size() == 0) { |
196 | createPublicVolume(mDevName, true, 0); |
197 | } else { |
198 | std::string partDevName; |
199 | for (auto part : mPartNo) { |
200 | if (mFlags & Flags::kUsb) |
201 | partDevName = StringPrintf("%s%d", mDevName.c_str(), part); |
202 | else if (mFlags & Flags::kSd) |
203 | partDevName = StringPrintf("%sp%d", mDevName.c_str(), part); |
204 | |
205 | createPublicVolume(partDevName, false, part); |
206 | } |
207 | } |
208 | |
209 | |
210 | return OK; |
211 | } |
212 | |
213 | status_t Disk::destroy() { |
214 | CHECK(mCreated); |
215 | destroyAllVolumes(); |
216 | notifyEvent(ResponseCode::DiskDestroyed); |
217 | mCreated = false; |
218 | return OK; |
219 | } |
220 | |
221 | void Disk::handleJustPublicPhysicalDevice( |
222 | const std::string& physicalDevName) { |
223 | auto vol = std::shared_ptr<VolumeBase>(new PublicVolume(physicalDevName, true)); |
224 | if (mJustPartitioned) { |
225 | LOG(DEBUG) << "Device just partitioned; silently formatting"; |
226 | vol->setSilent(true); |
227 | vol->create(); |
228 | vol->format("auto"); |
229 | vol->destroy(); |
230 | vol->setSilent(false); |
231 | } |
232 | |
233 | mVolumes.push_back(vol); |
234 | vol->setDiskId(getId()); |
235 | vol->setSysPath(getSysPath()); |
236 | vol->create(); |
237 | //vol->mount(); |
238 | } |
239 | |
240 | void Disk::createPublicVolume(const std::string& partDevName, |
241 | const bool isPhysical, int part) { |
242 | auto vol = std::shared_ptr<VolumeBase>(new PublicVolume(partDevName, isPhysical)); |
243 | if (mJustPartitioned) { |
244 | LOG(DEBUG) << "Device just partitioned; silently formatting"; |
245 | vol->setSilent(true); |
246 | vol->create(); |
247 | vol->format("auto"); |
248 | vol->destroy(); |
249 | vol->setSilent(false); |
250 | } |
251 | |
252 | mVolumes.push_back(vol); |
253 | vol->setDiskId(getId()); |
254 | vol->setSysPath(getSysPath()); |
255 | vol->setDiskFlags(mFlags); |
256 | vol->setPartNo(part); |
257 | |
258 | vol->create(); |
259 | //vol->mount(); |
260 | } |
261 | |
262 | void Disk::destroyAllVolumes() { |
263 | for (auto vol : mVolumes) { |
264 | vol->destroy(); |
265 | } |
266 | mVolumes.clear(); |
267 | } |
268 | |
269 | status_t Disk::readDiskMetadata() { |
270 | mSize = -1; |
271 | mLabel.clear(); |
272 | |
273 | int fd = open(mDevPath.c_str(), O_RDONLY | O_CLOEXEC); |
274 | if (fd != -1) { |
275 | if (ioctl(fd, BLKGETSIZE64, &mSize)) { |
276 | mSize = -1; |
277 | } |
278 | close(fd); |
279 | } |
280 | |
281 | unsigned int majorId = major(mDevice); |
282 | switch (majorId) { |
283 | case kMajorBlockSr: |
284 | case kMajorBlockScsiA: case kMajorBlockScsiB: case kMajorBlockScsiC: case kMajorBlockScsiD: |
285 | case kMajorBlockScsiE: case kMajorBlockScsiF: case kMajorBlockScsiG: case kMajorBlockScsiH: |
286 | case kMajorBlockScsiI: case kMajorBlockScsiJ: case kMajorBlockScsiK: case kMajorBlockScsiL: |
287 | case kMajorBlockScsiM: case kMajorBlockScsiN: case kMajorBlockScsiO: case kMajorBlockScsiP: { |
288 | std::string path(mSysPath + "/device/vendor"); |
289 | std::string tmp; |
290 | if (!ReadFileToString(path, &tmp)) { |
291 | PLOG(WARNING) << "Failed to read vendor from " << path; |
292 | return -errno; |
293 | } |
294 | mLabel = tmp; |
295 | break; |
296 | } |
297 | case kMajorBlockMmc: { |
298 | std::string path(mSysPath + "/device/manfid"); |
299 | std::string tmp; |
300 | if (!ReadFileToString(path, &tmp)) { |
301 | PLOG(WARNING) << "Failed to read manufacturer from " << path; |
302 | return -errno; |
303 | } |
304 | uint64_t manfid = strtoll(tmp.c_str(), nullptr, 16); |
305 | // Our goal here is to give the user a meaningful label, ideally |
306 | // matching whatever is silk-screened on the card. To reduce |
307 | // user confusion, this list doesn't contain white-label manfid. |
308 | switch (manfid) { |
309 | case 0x000003: mLabel = "SanDisk"; break; |
310 | case 0x00001b: mLabel = "Samsung"; break; |
311 | case 0x000028: mLabel = "Lexar"; break; |
312 | case 0x000074: mLabel = "Transcend"; break; |
313 | } |
314 | break; |
315 | } |
316 | default: { |
317 | if (isVirtioBlkDevice(majorId)) { |
318 | LOG(DEBUG) << "Recognized experimental block major ID " << majorId |
319 | << " as virtio-blk (emulator's virtual SD card device)"; |
320 | mLabel = "Virtual"; |
321 | break; |
322 | } |
323 | LOG(WARNING) << "Unsupported block major type " << majorId; |
324 | return -ENOTSUP; |
325 | } |
326 | } |
327 | |
328 | notifyEvent(ResponseCode::DiskSizeChanged, StringPrintf("%" PRIu64, mSize)); |
329 | notifyEvent(ResponseCode::DiskLabelChanged, mLabel); |
330 | notifyEvent(ResponseCode::DiskSysPathChanged, mSysPath); |
331 | return OK; |
332 | } |
333 | |
334 | void Disk::handleBlockEvent(NetlinkEvent *evt) { |
335 | std::string eventPath(evt->findParam("DEVPATH")?evt->findParam("DEVPATH"):""); |
336 | std::string devName(evt->findParam("DEVNAME")?evt->findParam("DEVNAME"):""); |
337 | std::string devType(evt->findParam("DEVTYPE")?evt->findParam("DEVTYPE"):""); |
338 | |
339 | // can we handle this event |
340 | if (eventPath.find(mEventPath) == std::string::npos) { |
341 | LOG(DEBUG) << "evt will handle by other disk " << mEventPath; |
342 | return; |
343 | } |
344 | |
345 | if (devType != "partition") { |
346 | LOG(DEBUG) << "evt type is not partition " << devType; |
347 | evt->dump(); |
348 | return; |
349 | } |
350 | |
351 | std::string partDevName; |
352 | switch (evt->getAction()) { |
353 | case NetlinkEvent::Action::kAdd: { |
354 | int part = atoi(evt->findParam("PARTN")); |
355 | |
356 | mPartNo.push_back(part); |
357 | if (mFlags & Flags::kUsb) |
358 | partDevName = StringPrintf("%s%d", mDevName.c_str(), part); |
359 | else if (mFlags & Flags::kSd) |
360 | partDevName = StringPrintf("%sp%d", mDevName.c_str(), part); |
361 | |
362 | LOG(INFO) << " partDevName =" << partDevName; |
363 | createPublicVolume(partDevName, false, part); |
364 | break; |
365 | } |
366 | case NetlinkEvent::Action::kChange: { |
367 | // ignore |
368 | LOG(DEBUG) << "Disk at " << mDevPath << " changed"; |
369 | break; |
370 | } |
371 | case NetlinkEvent::Action::kRemove: { |
372 | // will handle by vm |
373 | break; |
374 | } |
375 | default: { |
376 | LOG(WARNING) << "Unexpected block event action " << (int) evt->getAction(); |
377 | break; |
378 | } |
379 | } |
380 | } |
381 | |
382 | #if 0 |
383 | status_t Disk::readPartitions() { |
384 | if (mSrdisk) { |
385 | // srdisk has no partiton concept. |
386 | LOG(INFO) << "srdisk try entire disk as fake partition"; |
387 | //createPublicVolume(mDevice); |
388 | return OK; |
389 | } |
390 | |
391 | int8_t maxMinors = getMaxMinors(); |
392 | if (maxMinors < 0) { |
393 | return -ENOTSUP; |
394 | } |
395 | |
396 | destroyAllVolumes(); |
397 | |
398 | // Parse partition table |
399 | |
400 | std::vector<std::string> cmd; |
401 | cmd.push_back(kSgdiskPath); |
402 | cmd.push_back("--android-dump"); |
403 | cmd.push_back(mDevPath); |
404 | |
405 | std::vector<std::string> output; |
406 | status_t res = ForkExecvp(cmd, output); |
407 | if (res != OK) { |
408 | LOG(WARNING) << "sgdisk failed to scan " << mDevPath; |
409 | notifyEvent(ResponseCode::DiskScanned); |
410 | mJustPartitioned = false; |
411 | return res; |
412 | } |
413 | |
414 | Table table = Table::kUnknown; |
415 | bool foundParts = false; |
416 | std::string physicalDevName, partDevName; |
417 | for (auto line : output) { |
418 | char* cline = (char*) line.c_str(); |
419 | char* token = strtok(cline, kSgdiskToken); |
420 | if (token == nullptr) continue; |
421 | |
422 | if (!strcmp(token, "DISK")) { |
423 | const char* type = strtok(nullptr, kSgdiskToken); |
424 | if (!strcmp(type, "mbr")) { |
425 | table = Table::kMbr; |
426 | } else if (!strcmp(type, "gpt")) { |
427 | table = Table::kGpt; |
428 | } |
429 | } else if (!strcmp(token, "PART")) { |
430 | foundParts = true; |
431 | int i = strtol(strtok(nullptr, kSgdiskToken), nullptr, 10); |
432 | if (i <= 0 || i > maxMinors) { |
433 | LOG(WARNING) << mId << " is ignoring partition " << i |
434 | << " beyond max supported devices"; |
435 | continue; |
436 | } |
437 | dev_t partDevice = makedev(major(mDevice), minor(mDevice) + i); |
438 | |
439 | if (mFlags & Flags::kUsb) |
440 | partDevName = StringPrintf("%s%d", mDevName.c_str(), i); |
441 | else if (mFlags & Flags::kSd) |
442 | partDevName = StringPrintf("%sp%d", mDevName.c_str(), i); |
443 | LOG(INFO) << " partDevName =" << partDevName; |
444 | |
445 | if (table == Table::kMbr) { |
446 | const char* type = strtok(nullptr, kSgdiskToken); |
447 | |
448 | if (IsJustPhysicalDevice(mSysPath, physicalDevName)) { |
449 | LOG(INFO) << " here,we don't create public:xx,xx for physical device only!"; |
450 | handleJustPublicPhysicalDevice(physicalDevName); |
451 | break; |
452 | } |
453 | |
454 | // support mort than 16 partitions |
455 | if (i > 15) |
456 | getPhysicalDev(partDevice, mSysPath, i); |
457 | |
458 | switch (strtol(type, nullptr, 16)) { |
459 | case 0x06: // FAT16 |
460 | case 0x0b: // W95 FAT32 (LBA) |
461 | case 0x0c: // W95 FAT32 (LBA) |
462 | case 0x0e: // W95 FAT16 (LBA) |
463 | |
464 | case 0x07: // NTFS & EXFAT |
465 | createPublicVolume(partDevName, false); |
466 | break; |
467 | |
468 | default: |
469 | // We should still create public volume here |
470 | // cause some disk table types are not matched above |
471 | // but can be mounted successfully |
472 | createPublicVolume(partDevName, false); |
473 | LOG(WARNING) << "unsupported table kMbr type " << type; |
474 | break; |
475 | } |
476 | } else if (table == Table::kGpt) { |
477 | const char* typeGuid = strtok(nullptr, kSgdiskToken); |
478 | const char* partGuid = strtok(nullptr, kSgdiskToken); |
479 | |
480 | if (!strcasecmp(typeGuid, kGptBasicData)) { |
481 | createPublicVolume(partDevName, false); |
482 | } |
483 | } |
484 | } |
485 | } |
486 | |
487 | // Ugly last ditch effort, treat entire disk as partition |
488 | if (table == Table::kUnknown || !foundParts) { |
489 | LOG(WARNING) << mId << " has unknown partition table; trying entire device"; |
490 | |
491 | std::string fsType; |
492 | std::string unused; |
493 | if (ReadPartMetadata(mDevPath, fsType, unused, unused) == OK) { |
494 | if (IsJustPhysicalDevice(mSysPath, physicalDevName)) { |
495 | handleJustPublicPhysicalDevice(physicalDevName); |
496 | } else { |
497 | createPublicVolume(partDevName, false); |
498 | } |
499 | } else { |
500 | LOG(WARNING) << mId << " failed to identify, giving up"; |
501 | } |
502 | } |
503 | |
504 | notifyEvent(ResponseCode::DiskScanned); |
505 | mJustPartitioned = false; |
506 | return OK; |
507 | } |
508 | #endif |
509 | |
510 | status_t Disk::unmountAll() { |
511 | for (auto vol : mVolumes) { |
512 | vol->unmount(); |
513 | } |
514 | return OK; |
515 | } |
516 | |
517 | void Disk::notifyEvent(int event) { |
518 | VolumeManager::Instance()->getBroadcaster()->sendBroadcast(event, |
519 | getId()); |
520 | } |
521 | |
522 | void Disk::notifyEvent(int event, const std::string& value) { |
523 | VolumeManager::Instance()->getBroadcaster()->sendBroadcast(event, |
524 | StringPrintf("%s %s", getId().c_str(), value.c_str())); |
525 | } |
526 | |
527 | |
528 | bool Disk::isSrdiskMounted() { |
529 | if (!mSrdisk) { |
530 | return false; |
531 | } |
532 | |
533 | for (auto vol : mVolumes) { |
534 | return vol->isSrdiskMounted(); |
535 | } |
536 | |
537 | return false; |
538 | } |
539 | |
540 | int Disk::getMaxMinors() { |
541 | // Figure out maximum partition devices supported |
542 | unsigned int majorId = major(mDevice); |
543 | switch (majorId) { |
544 | case kMajorBlockScsiA: case kMajorBlockScsiB: case kMajorBlockScsiC: case kMajorBlockScsiD: |
545 | case kMajorBlockScsiE: case kMajorBlockScsiF: case kMajorBlockScsiG: case kMajorBlockScsiH: |
546 | case kMajorBlockScsiI: case kMajorBlockScsiJ: case kMajorBlockScsiK: case kMajorBlockScsiL: |
547 | case kMajorBlockScsiM: case kMajorBlockScsiN: case kMajorBlockScsiO: case kMajorBlockScsiP: { |
548 | // Per Documentation/devices.txt this is static |
549 | return 31; |
550 | } |
551 | case kMajorBlockMmc: { |
552 | // Per Documentation/devices.txt this is dynamic |
553 | std::string tmp; |
554 | if (!ReadFileToString(kSysfsMmcMaxMinors, &tmp)) { |
555 | LOG(ERROR) << "Failed to read max minors"; |
556 | return -errno; |
557 | } |
558 | return atoi(tmp.c_str()); |
559 | } |
560 | default: { |
561 | if (isVirtioBlkDevice(majorId)) { |
562 | // drivers/block/virtio_blk.c has "#define PART_BITS 4", so max is |
563 | // 2^4 - 1 = 15 |
564 | return 15; |
565 | } |
566 | } |
567 | } |
568 | |
569 | LOG(ERROR) << "Unsupported block major type " << majorId; |
570 | return -ENOTSUP; |
571 | } |
572 | |
573 | void Disk::getPhysicalDev(dev_t &device, const std:: string& sysPath, int part) { |
574 | std::string smajor, sminor; |
575 | std::string physicalDev, lpDev; |
576 | int major, minor; |
577 | |
578 | if (part <= 15) |
579 | return; |
580 | |
581 | if (GetPhysicalDevice(sysPath, physicalDev) == OK) { |
582 | lpDev = StringPrintf("%s%d", physicalDev.c_str(), part); |
583 | if (!access(lpDev.c_str(), F_OK) && |
584 | readBlockDevMajorAndMinor(lpDev, smajor, sminor) == OK) { |
585 | major = atoi(smajor.c_str()); |
586 | minor = atoi(sminor.c_str()); |
587 | device = makedev(major, minor); |
588 | } |
589 | } |
590 | } |
591 | |
592 | } // namespace vold |
593 | } // namespace android |
594 |