<?php

/**
 * Keeps files on local disk, but sudo-s as the daemon user for it.
 *
 * @task internal Internals
 */
final class LocalDiskSudoFileStorageEngine
  extends PhabricatorFileStorageEngine {


/* -(  Engine Metadata  )---------------------------------------------------- */


  public function getEngineIdentifier() {
    return 'local-disk-sudo';
  }

  public function getEnginePriority() {
    return 4; // Just below the regular disk one.
  }

  public function canWriteFiles() {
    $path = PhabricatorEnv::getEnvConfig('storage.local-disk.path');
    $user = PhabricatorEnv::getEnvConfig('phd.user');
    return (bool)strlen($path) && (bool)$user;
  }


/* -(  Managing File Data  )------------------------------------------------- */

  /**
   * Write the file data to local disk. Returns the relative path as the
   * file data handle.
   * @task impl
   */
  public function writeFile($data, array $params) {
    $root = $this->getLocalDiskFileStorageRoot();

    // Generate a random, unique file path like "ab/29/1f918a9ac39201ff". We
    // put a couple of subdirectories up front to avoid a situation where we
    // have one directory with a zillion files in it, since this is generally
    // bad news.
    do {
      $name = md5(mt_rand());
      $name = preg_replace('/^(..)(..)(.*)$/', '\\1/\\2/\\3', $name);
      if (!Filesystem::pathExists($root.'/'.$name)) {
        break;
      }
    } while (true);

    AphrontWriteGuard::willWrite();
    $binary = Filesystem::resolveBinary('write-file-to-storage.sh');
    $command = csprintf('%s %s', $binary, $root.'/'.$name);
    $command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
    id(new ExecFuture('%C', $command))
      ->write($data)
      ->resolvex();

    return $name;
  }


  /**
   * Read the file data off local disk.
   * @task impl
   */
  public function readFile($handle) {
    $path = $this->getLocalDiskFileStorageFullPath($handle);
    return Filesystem::readFile($path);
  }


  /**
   * Deletes the file from local disk, if it exists.
   * @task impl
   */
  public function deleteFile($handle) {
    $path = $this->getLocalDiskFileStorageFullPath($handle);
    if (Filesystem::pathExists($path)) {
      AphrontWriteGuard::willWrite();
      $binary = Filesystem::resolveBinary('rm-file-from-storage.php');
      $command = csprintf('%s %s', $binary, $path);
      $command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
      execx('%C', $command);
    }
  }


/* -(  Internals  )---------------------------------------------------------- */


  /**
   * Get the configured local disk path for file storage.
   *
   * @return string Absolute path to somewhere that files can be stored.
   * @task internal
   */
  private function getLocalDiskFileStorageRoot() {
    $root = PhabricatorEnv::getEnvConfig('storage.local-disk.path');

    if (!$root || $root == '/' || $root[0] != '/') {
      throw new PhabricatorFileStorageConfigurationException(
        pht(
          "Malformed local disk storage root. You must provide an absolute ".
          "path, and can not use '%s' as the root.",
          '/'));
    }

    return rtrim($root, '/');
  }


  /**
   * Convert a handle into an absolute local disk path.
   *
   * @param string File data handle.
   * @return string Absolute path to the corresponding file.
   * @task internal
   */
  private function getLocalDiskFileStorageFullPath($handle) {
    // Make sure there's no funny business going on here. Users normally have
    // no ability to affect the content of handles, but double-check that
    // we're only accessing local storage just in case.
    if (!preg_match('@^[a-f0-9]{2}/[a-f0-9]{2}/[a-f0-9]{28}\z@', $handle)) {
      throw new Exception(
        pht(
          "Local disk filesystem handle '%s' is malformed!",
          $handle));
    }
    $root = $this->getLocalDiskFileStorageRoot();
    return $root.'/'.$handle;
  }

}


/**
// rm-file-from-storage.php:

#!/usr/bin/env php
<?php

$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php';
$filename = $argv[1];


$root = PhabricatorEnv::getEnvConfig('storage.local-disk.path');
if (substr($filename, 0, strlen($root)) != $root) {
  throw new Exception('File is not in storage directory!');
}
if (strstr($filename, '..') != false) {
  throw new Exception('invalid filename!');
}

execx('rm %s', $filename);
*/

/**
// write-file-to-storage.sh:

#! /usr/bin/env bash
set -e

# Saves data from STDIO to this file
# run this as the daemon user
DEST_FILE=$1  # abs path

if [[ -z $DEST_FILE ]]
then
  echo 'needs dst filename!' >&2
  exit 3
fi

mkdir -p $(dirname $DEST_FILE)
cat - > $DEST_FILE

*/