// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// MtabWatcherLinux implementation.

#include "components/storage_monitor/mtab_watcher_linux.h"

#include <mntent.h>
#include <stddef.h>
#include <stdio.h>

#include "base/bind.h"
#include "base/macros.h"
#include "content/public/browser/browser_thread.h"

namespace {

// List of file systems we care about.
const char* const kKnownFileSystems[] = {
  "btrfs",
  "ext2",
  "ext3",
  "ext4",
  "fat",
  "hfsplus",
  "iso9660",
  "msdos",
  "ntfs",
  "udf",
  "vfat",
};

}  // namespace

namespace storage_monitor {

MtabWatcherLinux::MtabWatcherLinux(const base::FilePath& mtab_path,
                                   base::WeakPtr<Delegate> delegate)
    : mtab_path_(mtab_path),
      delegate_(delegate),
      weak_ptr_factory_(this) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
  bool ret = file_watcher_.Watch(
      mtab_path_, false,
      base::Bind(&MtabWatcherLinux::OnFilePathChanged,
                 weak_ptr_factory_.GetWeakPtr()));
  if (!ret) {
    LOG(ERROR) << "Adding watch for " << mtab_path_.value() << " failed";
    return;
  }

  ReadMtab();
}

MtabWatcherLinux::~MtabWatcherLinux() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
}

void MtabWatcherLinux::ReadMtab() const {
  DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);

  FILE* fp = setmntent(mtab_path_.value().c_str(), "r");
  if (!fp)
    return;

  MountPointDeviceMap device_map;
  mntent entry;
  char buf[512];

  // We return the same device mounted to multiple locations, but hide
  // devices that have been mounted over.
  while (getmntent_r(fp, &entry, buf, sizeof(buf))) {
    // We only care about real file systems.
    for (size_t i = 0; i < arraysize(kKnownFileSystems); ++i) {
      if (strcmp(kKnownFileSystems[i], entry.mnt_type) == 0) {
        device_map[base::FilePath(entry.mnt_dir)] =
            base::FilePath(entry.mnt_fsname);
        break;
      }
    }
  }
  endmntent(fp);

  content::BrowserThread::PostTask(
      content::BrowserThread::UI, FROM_HERE,
      base::Bind(&Delegate::UpdateMtab, delegate_, device_map));
}

void MtabWatcherLinux::OnFilePathChanged(
    const base::FilePath& path, bool error) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);

  if (path != mtab_path_) {
    // This cannot happen unless FilePathWatcher is buggy. Just ignore this
    // notification and do nothing.
    NOTREACHED();
    return;
  }
  if (error) {
    LOG(ERROR) << "Error watching " << mtab_path_.value();
    return;
  }

  ReadMtab();
}

}  // namespace storage_monitor
