1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/chromeos/cros/mount_library.h"
6
7#include <set>
8
9#include "base/message_loop.h"
10#include "base/string_util.h"
11#include "chrome/browser/chromeos/cros/cros_library.h"
12#include "content/browser/browser_thread.h"
13
14const char* kLibraryNotLoaded = "Cros Library not loaded";
15
16namespace chromeos {
17
18MountLibrary::Disk::Disk(const std::string& device_path,
19     const std::string& mount_path,
20     const std::string& system_path,
21     const std::string& file_path,
22     const std::string& device_label,
23     const std::string& drive_label,
24     const std::string& parent_path,
25     DeviceType device_type,
26     uint64 total_size,
27     bool is_parent,
28     bool is_read_only,
29     bool has_media,
30     bool on_boot_device)
31    : device_path_(device_path),
32      mount_path_(mount_path),
33      system_path_(system_path),
34      file_path_(file_path),
35      device_label_(device_label),
36      drive_label_(drive_label),
37      parent_path_(parent_path),
38      device_type_(device_type),
39      total_size_(total_size),
40      is_parent_(is_parent),
41      is_read_only_(is_read_only),
42      has_media_(has_media),
43      on_boot_device_(on_boot_device) {
44  // Add trailing slash to mount path.
45  if (mount_path_.length() && mount_path_.at(mount_path_.length() -1) != '/')
46    mount_path_ = mount_path_.append("/");
47}
48
49class MountLibraryImpl : public MountLibrary {
50 public:
51  MountLibraryImpl() : mount_status_connection_(NULL) {
52    if (CrosLibrary::Get()->EnsureLoaded())
53      Init();
54    else
55      LOG(ERROR) << kLibraryNotLoaded;
56  }
57
58  virtual ~MountLibraryImpl() {
59    if (mount_status_connection_)
60      DisconnectMountEventMonitor(mount_status_connection_);
61  }
62
63  // MountLibrary overrides.
64  virtual void AddObserver(Observer* observer) OVERRIDE {
65    observers_.AddObserver(observer);
66  }
67
68  virtual void RemoveObserver(Observer* observer) OVERRIDE {
69    observers_.RemoveObserver(observer);
70  }
71
72  virtual void MountPath(const char* device_path) OVERRIDE {
73    CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
74    if (!CrosLibrary::Get()->EnsureLoaded()) {
75      OnMountRemovableDevice(device_path,
76                             NULL,
77                             MOUNT_METHOD_ERROR_LOCAL,
78                             kLibraryNotLoaded);
79      return;
80    }
81    MountRemovableDevice(device_path,
82                         &MountLibraryImpl::MountRemovableDeviceCallback,
83                         this);
84  }
85
86  virtual void UnmountPath(const char* device_path) OVERRIDE {
87    CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
88    if (!CrosLibrary::Get()->EnsureLoaded()) {
89      OnUnmountRemovableDevice(device_path,
90                               MOUNT_METHOD_ERROR_LOCAL,
91                               kLibraryNotLoaded);
92      return;
93    }
94    UnmountRemovableDevice(device_path,
95                           &MountLibraryImpl::UnmountRemovableDeviceCallback,
96                           this);
97  }
98
99  virtual void RequestMountInfoRefresh() OVERRIDE {
100    CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
101    if (!CrosLibrary::Get()->EnsureLoaded()) {
102      OnRequestMountInfo(NULL,
103                         0,
104                         MOUNT_METHOD_ERROR_LOCAL,
105                         kLibraryNotLoaded);
106      return;
107    }
108    RequestMountInfo(&MountLibraryImpl::RequestMountInfoCallback,
109                     this);
110  }
111
112  virtual void RefreshDiskProperties(const Disk* disk) OVERRIDE {
113    CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
114    DCHECK(disk);
115    if (!CrosLibrary::Get()->EnsureLoaded()) {
116      OnGetDiskProperties(disk->device_path().c_str(),
117                          NULL,
118                          MOUNT_METHOD_ERROR_LOCAL,
119                          kLibraryNotLoaded);
120      return;
121    }
122    GetDiskProperties(disk->device_path().c_str(),
123                      &MountLibraryImpl::GetDiskPropertiesCallback,
124                      this);
125  }
126
127  const DiskMap& disks() const OVERRIDE { return disks_; }
128
129 private:
130
131  // Callback for MountRemovableDevice method.
132  static void MountRemovableDeviceCallback(void* object,
133                                           const char* device_path,
134                                           const char* mount_path,
135                                           MountMethodErrorType error,
136                                           const char* error_message) {
137    DCHECK(object);
138    MountLibraryImpl* self = static_cast<MountLibraryImpl*>(object);
139    self->OnMountRemovableDevice(device_path,
140                                 mount_path,
141                                 error,
142                                 error_message);
143  }
144
145  // Callback for UnmountRemovableDevice method.
146  static void UnmountRemovableDeviceCallback(void* object,
147                                             const char* device_path,
148                                             const char* mount_path,
149                                             MountMethodErrorType error,
150                                             const char* error_message) {
151    DCHECK(object);
152    MountLibraryImpl* self = static_cast<MountLibraryImpl*>(object);
153    self->OnUnmountRemovableDevice(device_path,
154                                   error,
155                                   error_message);
156  }
157
158  // Callback for disk information retrieval calls.
159  static void GetDiskPropertiesCallback(void* object,
160                                        const char* device_path,
161                                        const DiskInfo* disk,
162                                        MountMethodErrorType error,
163                                        const char* error_message) {
164    DCHECK(object);
165    MountLibraryImpl* self = static_cast<MountLibraryImpl*>(object);
166    self->OnGetDiskProperties(device_path,
167                              disk,
168                              error,
169                              error_message);
170  }
171
172  // Callback for RequestMountInfo call.
173  static void RequestMountInfoCallback(void* object,
174                                       const char** devices,
175                                       size_t device_len,
176                                       MountMethodErrorType error,
177                                       const char* error_message) {
178    DCHECK(object);
179    MountLibraryImpl* self = static_cast<MountLibraryImpl*>(object);
180    self->OnRequestMountInfo(devices,
181                             device_len,
182                             error,
183                             error_message);
184  }
185
186  // This method will receive events that are caused by drive status changes.
187  static void MonitorMountEventsHandler(void* object,
188                                        MountEventType evt,
189                                        const char* device_path) {
190    DCHECK(object);
191    MountLibraryImpl* self = static_cast<MountLibraryImpl*>(object);
192    self->OnMountEvent(evt, device_path);
193  }
194
195
196  void OnMountRemovableDevice(const char* device_path,
197                              const char* mount_path,
198                              MountMethodErrorType error,
199                              const char* error_message) {
200    DCHECK(device_path);
201
202    if (error == MOUNT_METHOD_ERROR_NONE && device_path && mount_path) {
203      std::string path(device_path);
204      DiskMap::iterator iter = disks_.find(path);
205      if (iter == disks_.end()) {
206        // disk might have been removed by now?
207        return;
208      }
209      Disk* disk = iter->second;
210      DCHECK(disk);
211      disk->set_mount_path(mount_path);
212      FireDiskStatusUpdate(MOUNT_DISK_MOUNTED, disk);
213    } else {
214      LOG(WARNING) << "Mount request failed for device "
215                   << device_path << ", with error: "
216                   << (error_message ? error_message : "Unknown");
217    }
218  }
219
220  void OnUnmountRemovableDevice(const char* device_path,
221                                MountMethodErrorType error,
222                                const char* error_message) {
223    DCHECK(device_path);
224    if (error == MOUNT_METHOD_ERROR_NONE && device_path) {
225      std::string path(device_path);
226      DiskMap::iterator iter = disks_.find(path);
227      if (iter == disks_.end()) {
228        // disk might have been removed by now?
229        return;
230      }
231      Disk* disk = iter->second;
232      DCHECK(disk);
233      disk->clear_mount_path();
234      FireDiskStatusUpdate(MOUNT_DISK_UNMOUNTED, disk);
235    } else {
236      LOG(WARNING) << "Unmount request failed for device "
237                   << device_path << ", with error: "
238                   << (error_message ? error_message : "Unknown");
239    }
240  }
241
242  void OnGetDiskProperties(const char* device_path,
243                           const DiskInfo* disk1,
244                           MountMethodErrorType error,
245                           const char* error_message) {
246    DCHECK(device_path);
247    if (error == MOUNT_METHOD_ERROR_NONE && device_path) {
248      // TODO(zelidrag): Find a better way to filter these out before we
249      // fetch the properties:
250      // Ignore disks coming from the device we booted the system from.
251
252      // This cast is temporal solution, until we merge DiskInfo and
253      // DiskInfoAdvanced into single interface.
254      const DiskInfoAdvanced* disk =
255          reinterpret_cast<const DiskInfoAdvanced*>(disk1);
256      if (disk->on_boot_device())
257        return;
258
259      LOG(WARNING) << "Found disk " << device_path;
260      // Delete previous disk info for this path:
261      bool is_new = true;
262      std::string device_path_string(device_path);
263      DiskMap::iterator iter = disks_.find(device_path_string);
264      if (iter != disks_.end()) {
265        delete iter->second;
266        disks_.erase(iter);
267        is_new = false;
268      }
269      std::string path;
270      std::string mountpath;
271      std::string systempath;
272      std::string filepath;
273      std::string devicelabel;
274      std::string drivelabel;
275      std::string parentpath;
276
277      if (disk->path() != NULL)
278        path = disk->path();
279
280      if (disk->mount_path() != NULL)
281        mountpath = disk->mount_path();
282
283      if (disk->system_path() != NULL)
284        systempath = disk->system_path();
285
286      if (disk->file_path() != NULL)
287        filepath = disk->file_path();
288
289      if (disk->label() != NULL)
290        devicelabel = disk->label();
291
292      if (disk->drive_label() != NULL)
293        drivelabel = disk->drive_label();
294
295      if (disk->partition_slave() != NULL)
296        parentpath = disk->partition_slave();
297
298      Disk* new_disk = new Disk(path,
299                                mountpath,
300                                systempath,
301                                filepath,
302                                devicelabel,
303                                drivelabel,
304                                parentpath,
305                                disk->device_type(),
306                                disk->size(),
307                                disk->is_drive(),
308                                disk->is_read_only(),
309                                disk->has_media(),
310                                disk->on_boot_device());
311      disks_.insert(
312          std::pair<std::string, Disk*>(device_path_string, new_disk));
313      FireDiskStatusUpdate(is_new ? MOUNT_DISK_ADDED : MOUNT_DISK_CHANGED,
314                           new_disk);
315    } else {
316      LOG(WARNING) << "Property retrieval request failed for device "
317                   << device_path << ", with error: "
318                   << (error_message ? error_message : "Unknown");
319    }
320  }
321
322  void OnRequestMountInfo(const char** devices,
323                          size_t devices_len,
324                          MountMethodErrorType error,
325                          const char* error_message) {
326    std::set<std::string> current_device_set;
327    if (error == MOUNT_METHOD_ERROR_NONE && devices && devices_len) {
328      // Initiate properties fetch for all removable disks,
329      bool found_disk = false;
330      for (size_t i = 0; i < devices_len; i++) {
331        if (!devices[i]) {
332          NOTREACHED();
333          continue;
334        }
335        current_device_set.insert(std::string(devices[i]));
336        found_disk = true;
337        // Initiate disk property retrieval for each relevant device path.
338        GetDiskProperties(devices[i],
339                          &MountLibraryImpl::GetDiskPropertiesCallback,
340                          this);
341      }
342    } else if (error != MOUNT_METHOD_ERROR_NONE) {
343      LOG(WARNING) << "Request mount info retrieval request failed with error: "
344                   << (error_message ? error_message : "Unknown");
345    }
346    // Search and remove disks that are no longer present.
347    for (MountLibrary::DiskMap::iterator iter = disks_.begin();
348         iter != disks_.end(); ) {
349      if (current_device_set.find(iter->first) == current_device_set.end()) {
350        Disk* disk = iter->second;
351        FireDiskStatusUpdate(MOUNT_DISK_REMOVED, disk);
352        delete iter->second;
353        disks_.erase(iter++);
354      } else {
355        ++iter;
356      }
357    }
358  }
359
360  void OnMountEvent(MountEventType evt,
361                    const char* device_path) {
362    if (!device_path)
363      return;
364    MountLibraryEventType type = MOUNT_DEVICE_ADDED;
365    switch (evt) {
366      case DISK_ADDED:
367      case DISK_CHANGED: {
368        GetDiskProperties(device_path,
369                          &MountLibraryImpl::GetDiskPropertiesCallback,
370                          this);
371        return;
372      }
373      case DISK_REMOVED: {
374        // Search and remove disks that are no longer present.
375        MountLibrary::DiskMap::iterator iter =
376            disks_.find(std::string(device_path));
377        if (iter != disks_.end()) {
378            Disk* disk = iter->second;
379            FireDiskStatusUpdate(MOUNT_DISK_REMOVED, disk);
380            delete iter->second;
381            disks_.erase(iter);
382        }
383        return;
384      }
385      case DEVICE_ADDED: {
386        type = MOUNT_DEVICE_ADDED;
387        break;
388      }
389      case DEVICE_REMOVED: {
390        type = MOUNT_DEVICE_REMOVED;
391        break;
392      }
393      case DEVICE_SCANNED: {
394        type = MOUNT_DEVICE_SCANNED;
395        break;
396      }
397    }
398    FireDeviceStatusUpdate(type, std::string(device_path));
399  }
400
401  void Init() {
402    // Getting the monitor status so that the daemon starts up.
403    mount_status_connection_ = MonitorMountEvents(
404        &MonitorMountEventsHandler, this);
405  }
406
407  void FireDiskStatusUpdate(MountLibraryEventType evt,
408                            const Disk* disk) {
409    // Make sure we run on UI thread.
410    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
411    FOR_EACH_OBSERVER(
412        Observer, observers_, DiskChanged(evt, disk));
413  }
414
415  void FireDeviceStatusUpdate(MountLibraryEventType evt,
416                              const std::string& device_path) {
417    // Make sure we run on UI thread.
418    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
419    FOR_EACH_OBSERVER(
420        Observer, observers_, DeviceChanged(evt, device_path));
421  }
422
423  // Mount event change observers.
424  ObserverList<Observer> observers_;
425
426  // A reference to the  mount api, to allow callbacks when the mount
427  // status changes.
428  MountEventConnection mount_status_connection_;
429
430  // The list of disks found.
431  MountLibrary::DiskMap disks_;
432
433  DISALLOW_COPY_AND_ASSIGN(MountLibraryImpl);
434};
435
436class MountLibraryStubImpl : public MountLibrary {
437 public:
438  MountLibraryStubImpl() {}
439  virtual ~MountLibraryStubImpl() {}
440
441  // MountLibrary overrides.
442  virtual void AddObserver(Observer* observer) OVERRIDE {}
443  virtual void RemoveObserver(Observer* observer) OVERRIDE {}
444  virtual const DiskMap& disks() const OVERRIDE { return disks_; }
445  virtual void RequestMountInfoRefresh() OVERRIDE {}
446  virtual void MountPath(const char* device_path) OVERRIDE {}
447  virtual void UnmountPath(const char* device_path) OVERRIDE {}
448  virtual bool IsBootPath(const char* device_path) OVERRIDE { return true; }
449
450 private:
451  // The list of disks found.
452  DiskMap disks_;
453
454  DISALLOW_COPY_AND_ASSIGN(MountLibraryStubImpl);
455};
456
457// static
458MountLibrary* MountLibrary::GetImpl(bool stub) {
459  if (stub)
460    return new MountLibraryStubImpl();
461  else
462    return new MountLibraryImpl();
463}
464
465}  // namespace chromeos
466
467// Allows InvokeLater without adding refcounting. This class is a Singleton and
468// won't be deleted until it's last InvokeLater is run.
469DISABLE_RUNNABLE_METHOD_REFCOUNT(chromeos::MountLibraryImpl);
470
471