<?php

include_once "lang.php";
include_once "device_config_file.php";

function randStr($length=16)
{
  return substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, $length);
}

function hashPassword($password, $salt)
{
  return sha1($password . $salt)
}

function arrayMapToString($array, $connector)
{
  $result = array();
  foreach($array as $k => $v) {
    $result[] = "$k='$v'";
  }
  return implode($connector, $result);
}

function arrayGet($array, $key, $default=null) {
  return isset($array[$key]) ? $array[$key] : $default;
}

function startsWith($haystack, $needle) {
    return $needle === "" || strpos($haystack, $needle) === 0;
}

function endsWith($haystack, $needle) {
  return $needle === "" || substr($haystack, -strlen($needle)) === $needle;
}

function parentPath($level) {
  $curPath = __FILE__;
  $parts = explode(DIRECTORY_SEPARATOR, $curPath);
  for($i=0; $i<$level; ++$i)
    array_pop($parts);
  return realpath(implode(DIRECTORY_SEPARATOR, $parts));
}

function cptBaseDir() {
  return parentPath(2);
}

function sync() {
    // shell_exec("sync");
    // shell_exec("echo 3 > /proc/sys/vm/drop_caches");
    // return;

    $output = array();
    $return_val = 0;
    exec("sync", $output, $return_val);
    error_log("sync output : " . implode("\n", $output) . " return: " . $return_val);

    unset($output);

    $return_val = 0;
    exec("echo 3 > /proc/sys/vm/drop_caches", $output, $return_val);
    error_log("drop caches output : " . implode("\n", $output) . " return: " . $return_val);
}

function build_file_path() {
  return join(DIRECTORY_SEPARATOR, func_get_args());
}

function get_absolute_path($path) {
  $path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
  $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
  $absolutes = array();
  foreach ($parts as $part) {
    if ('.' == $part) continue;
    if ('..' == $part) {
      array_pop($absolutes);
    } else {
      $absolutes[] = $part;
    }
  }
  $result = implode(DIRECTORY_SEPARATOR, $absolutes); 
  if (startsWith($path, DIRECTORY_SEPARATOR))
    return DIRECTORY_SEPARATOR . $result;
  else
    return $result;
}

function webrootPath() {
  $confFile = new DeviceConfigFile;

  if (!$confFile->load())
    return NULL;

  $path = $confFile->value("Webroot Path", NULL);
  if (is_null($path)) {
    if (file_exists("/var/www"))
      $path = "/var/www";
    else if (file_exists("/www"))
      $path = "/www";
  }
  return rtrim($path, "/");
}

function sdcardMountPath($confFile=NULL) {
  if (is_null($confFile)) {
    $confFile = new DeviceConfigFile;

    if (!$confFile->load())
      return NULL;
  }

  $sdcardPath = $confFile->value("SDcard Path", NULL);
  return $sdcardPath;
}

function sdcardBackupPath() {
  if (in_array(platformName(), array("FG", "FG+", "FG-")))
    return backupBasePath();

  $sdcardPath = sdcardMountPath();

  //TODO: how to integrate with configs in cpt_site_config file ? 
  //      maybe we should get rid of the cpt_site_config.php file ?
  $sdBackupBasePath = build_file_path($sdcardPath, 'cpt-backups');
  return $sdBackupBasePath;
}

function sdcardFtpPath() {
  if (in_array(platformName(), array("FG", "FG+", "FG-")))
    return build_file_path('', 'public', 'sdcard');

  return build_file_path('', 'mountable_sdcard');
}

function sdcardBackupFtpPath() {
  return build_file_path(sdcardFtpPath(), 'cpt-backups');
}

function backupBasePath() {
    $rootPath = $_SERVER['DOCUMENT_ROOT'];
    if (file_exists(build_file_path($rootPath, 'cpt_site_config.php')))
    {
      include_once(build_file_path($rootPath, 'cpt_site_config.php'))
      return CptSiteConfig::$backupBasePath;
    }
    else
      return build_file_path($rootPath, 'sdcard', 'cpt-backups');
}

function sedonaPath() {
    $confFile = new DeviceConfigFile;
    if ($confFile->load()) {
      $path = $confFile->value("SVM Path", NULL); 
      if (!is_null($path)) 
        return $path;
    }

    $rootPath = $_SERVER['DOCUMENT_ROOT'];
    if (file_exists(build_file_path($rootPath, 'cpt_site_config.php')))
    {
      include_once(build_file_path($rootPath, 'cpt_site_config.php'))
      return CptSiteConfig::$sedonaPath;
    }
    else
    {
      $sedonaPath = build_file_path('', 'mnt', 'sedona');
      if (!file_exists($sedonaPath))
        $sedonaPath = build_file_path('', 'mnt');

      return $sedonaPath;
    }
}

function imgPath() {
  return build_file_path(cptBaseDir(), "img");
}

function dashboardPath() {
  return build_file_path(cptBaseDir(), "dashboard");
}

function isDashboardExisted() {
  return file_exists(dashboardPath());
}

function pluginsPath() {
  return build_file_path(cptBaseDir(), "plugins");
}

function collectPlugins() {
  $plugins = array();
  $pluginsPath = pluginsPath();
  if (file_exists($pluginsPath) && is_dir($pluginsPath)) {
    $pathIterIter = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($pluginsPath, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST);
    $pathIterIter->setMaxDepth(0);
    foreach($pathIterIter as $path) {
      if (!$path->isDir())
        continue;

      $indexFile = NULL;
      $label = NULL;
      $enabled = true;
      $isSupported = true;
      $openNewPage = true;
      $pluginManifestFile = build_file_path($path->getPathName(), "manifest.json");
      if (file_exists($pluginManifestFile)) {
        $manifest = json_decode(file_get_contents($pluginManifestFile), true);
        if (is_array($manifest)) {
          $indexFile = isset($manifest['indexFile']) ? $manifest['indexFile'] : NULL;
          $utilityFile = isset($manifest['utilityFile']) ? $manifest['utilityFile'] : NULL;
          $label = isset($manifest['label']) ? $manifest['label'] : NULL;
          $enabled = isset($manifest['enabled']) ? $manifest['enabled'] : true;
          $show = isset($manifest['show']) ? $manifest['show'] : true;
          $openNewPage = isset($manifest['openNewPage']) ? $manifest['openNewPage'] : true;
          if (isset($manifest['supportedPlatforms'])) {
            $platformName = platformName();
            $isSupported = in_array($platformName, $manifest['supportedPlatforms']);
          }
        }
      }

      if (!$enabled || !$isSupported)
        continue;

      if (empty($indexFile)) {
        if (file_exists(build_file_path($path->getPathName(), "index.php")))
          $indexFile = "index.php";
        else if (file_exists(build_file_path($path->getPathName(), "index.html"))) 
          $indexFile = "index.html";
        else if (file_exists(build_file_path($path->getPathName(), "index.htm")))
          $indexFile = "index.htm";
        else
          continue;
      }

      $baseName = $path->getBaseName();
      $indexFilePath = "plugins/" . $baseName . '/' . $indexFile;

      if (!empty($utilityFile))
        $utilityFile = "plugins/" . $baseName . '/' . $utilityFile;

      $plugins[] = array("name" => empty($label) ? $baseName : $label, 
          "openNewPage" => $openNewPage,
          "show" => $show,
          "indexFile" => $indexFilePath,
          "utilityFile" => $utilityFile);
    }
  }

  return $plugins;
}

function rmIfExists($dirPath) {
    if (!is_dir($dirPath))
        return;

    // execCmd('rm -rf ' . $dirPath);
    foreach(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dirPath, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST) as $path) {
        $path->isFile() ? unlink($path->getPathname()) : rmdir($path->getPathname())
    }
    rmdir($dirPath);
}

function escapeJsonChars($val) {
  return addcslashes($val, "\\\"\n\r");
}

function collectBackups($path, $type, $returnJson) {
  $backups = array();
  if (!file_exists($path))
    return $backups;

  $type = strtolower($type);
  if ($handle = openDir($path)) {
    while (false !== ($entry = readdir($handle))) {
      if ($entry == "." || $entry == "..")
        continue;

      $fullPath = build_file_path($path, $entry);
      $withNetworkConf = withNetworkConfBackup($fullPath);
      $backupEntry = array("name" => $entry, "type" => $type, 
                           "withNetworkConf" => $withNetworkConf,
                           "ts" => filemtime($fullPath));

      if ($returnJson)
        $backups[] = map2json($backupEntry);
      else
        $backups[] = $backupEntry;
    }
    closedir($handle);
  }
  return $backups;
}

function doListBackups($returnJson=true) {
  $basePath = backupBasePath();
  $backups = array();

  //for old platforms
  if (in_array(platformName(), array("FG+", "FG", "FG-")))
    $backups = collectBackups($basePath, "sdcard", $returnJson);
  else { // for newer platform check backups in flash and sdcard
    $backups = collectBackups($basePath, "flash", $returnJson);

    $sdBackupPath = sdcardBackupPath();
    if (!is_null($sdBackupPath) && does_sdcard_exist())
      $backups = array_merge($backups, collectBackups($sdBackupPath, 'sdcard', $returnJson));
  }
  return $backups;
}



function checkBackupError($backupDir)
{
  rmIfExists($backupDir);

  $freeSpace = disk_free_space($backupDir);
  $minBackupSize = 4*1024*1024; //MB
  if ($freeSpace < $minBackupSize)
    return sprintf(L("failed to backup '%s', please make sure there is enough disk space."), 'graphics');
  else
    return sprintf(L("failed to backup '%s'."), 'graphics');
}
function doBackup($name, $cptBasePath=NULL, $backupHistoryDB=false, $type="sdcard", $dataOnly=false, $backupNetworkConf=false) {
    if (in_array(platformName(), array("FG", "FG+", "FG-")) && !does_sdcard_exist())
      return L("can not make backup when there is no sdcard.");

    $type = strtolower($type);
    if ($type == "flash" && in_array(platformName(), array("FG+", "FG", "FG-")))
      return L("can not backup to flash.");

    $backupPath = backupBasePath();
    if ($type == "sdcard") {
      $sdBackupPath = sdcardBackupPath();
      if (!is_null($sdBackupPath))
        $backupPath = $sdBackupPath;
    }
    $backupDir = build_file_path($backupPath, $name);
    if (file_exists($backupDir))
      return L("backup folder already exists, please choose a new folder.");

    // make sure all things have been saved
    sync();

    if (!mkdir($backupDir, 0777, true))
      return sprintf(L("failed to create backup folder '%s'."), $name);
    
    //write backup manifest data
    $manifest = array();
    $manifest['version'] = '1.0';
    $manifest['createdAt'] = date_create("now", new DateTimeZone("UTC"))->format("Y-m-d H:i:s");
    $manifest['dataOnly'] = $dataOnly;
    $manifest['networkConf'] = $backupNetworkConf;

    file_put_contents(build_file_path($backupDir, 'manifest.json'), map2json($manifest));

    //backup sedona files(app.sab & kits.scode)
    $sedonaPath = sedonaPath();
    if (!copy(build_file_path($sedonaPath, 'app.sab'),
      build_file_path($backupDir, 'app.sab')))
    {
      rmIfExists($backupDir);
      return sprintf(L("failed to backup '%s'."), 'app.sab');
    }

    if (!copy(build_file_path($sedonaPath, 'kits.scode'),
      build_file_path($backupDir, 'kits.scode')))
    {
      rmIfExists($backupDir);
      return sprintf(L("failed to backup '%s'."), 'kits.scode');
    }

    if (!copy(build_file_path($sedonaPath, 'cpt_session.xml'),
      build_file_path($backupDir, 'cpt_session.xml')))
      error_log("can not backup cpt_session.xml.");

    //backup app files
    if (empty($cptBasePath))
      $cptBasePath = cptBaseDir();
    $graphicDir = rtrim($cptBasePath, DIRECTORY_SEPARATOR);
    if (is_dir($graphicDir))
    {
      if ($dataOnly)
      {
        $dataFiles = array();
        $dataFiles[] = build_file_path($graphicDir, "app", "cpt-web.db");
        $dataFiles[] = build_file_path($graphicDir, "app", "grdata");
        $dataFiles[] = build_file_path($graphicDir, "app", "graphics", "assets", "imported");
        $dataFiles[] = build_file_path($graphicDir, "dashboard", "uploads");
        $dataFiles[] = build_file_path($graphicDir, "user_data");
        foreach($dataFiles as $dataFile) 
        {
          if (!file_exists($dataFile))
            continue;
          
          $dstPath = build_file_path($backupDir, 'cpt', 
                                     substr($dataFile, strlen($graphicDir)+1));
          if (!recurseCopy($dataFile, $dstPath)) 
          {
            error_log("failed to backup " . $dataFile . " to " . $dstPath);
            return checkBackupError($backupDir);
          }
        }
      } 
      else 
      {
        if (!recurseCopy($graphicDir, build_file_path($backupDir, 'cpt')))
          return checkBackupError($backupDir);
      }

      runPluginBackupCallbacks($backupDir, $graphicDir);

      if (!backupFirmwareCustomData($backupDir))
        return L("failed to backup firmware data");

      if ($backupHistoryDB)
      {
        $parentPath = dirname($graphicDir);
        $historyDBPath = build_file_path($parentPath, "easyio.db");
        if (file_exists($historyDBPath))
        {
          if (!copy($historyDBPath, build_file_path($backupDir, 'easyio.db')))
            return sprintf(L("failed to backup '%s'."), 'easyio.db');
        }
        else
          error_log("can not find history db file at " . $historyDBPath);
          // return sprintf(L("can not find history db file '%s'."), 'easyio.db');
      }
    }

    if ($backupNetworkConf)
    {
      $networkConfs = array('/etc/config/wireless', '/etc/config/network', '/etc/config/dhcp', '/etc/config/samba', '/mnt/fw.config', '/etc/wpa_supplicant/wpa_supplicant_wired.conf');
      foreach($networkConfs as $conf) {
        if (file_exists($conf)) {
          $dstPath = get_absolute_path(build_file_path($backupDir, 'root', $conf));
          if (!recurseCopy($conf, $dstPath))
            return sprintf(L("failed to backup network config file: '%s'."), $conf);
        } else {
          error_log("can not find config file: " . $conf);
        }
      }
    }

    error_log("backup " . $name . " done");

    sync();
}

function isDataOnlyBackup($backupDir) {
  if (!function_exists("json_decode"))
      return false;

  $filePath = build_file_path($backupDir, 'manifest.json');
  if (file_exists($filePath)) {
    $manifest = json_decode(file_get_contents($filePath), true);
    if (!is_null($manifest) && isset($manifest['dataOnly']))
      return $manifest['dataOnly'];
  }
  return !file_exists(build_file_path($backupDir, 'cpt', 'js'));
}

function withNetworkConfBackup($backupDir) {
  if (!function_exists("json_decode"))
      return false;

  $filePath = build_file_path($backupDir, 'manifest.json');
  if (file_exists($filePath)) {
    $manifest = json_decode(file_get_contents($filePath), true);
    if (!is_null($manifest) && isset($manifest['networkConf']))
      return $manifest['networkConf'];
  }
  return false;
}

function doRestore($name, $cptBasePath=NULL, $withHistoryDB=true, $type="sdcard", $withNetworkConf=false) {
    if (in_array(platformName(), array("FG", "FG+", "FG-")) && !does_sdcard_exist())
      return L("can not restore backup when there is no sdcard.");

    $type = strtolower($type);
    if ($type == "sdcard") {
      if (!does_sdcard_exist())
        return L("no sdcard available");

      $sdBackupPath = sdcardBackupPath();
      if (!file_exists($sdBackupPath)) {
        error_log("can not find sdcard mount path: " . $sdBackupPath);
        return L("can not find sdcard path");
      }

      $backupPath = $sdBackupPath;
    } else
      $backupPath = backupBasePath();

    $backupDir = build_file_path($backupPath, $name);

    if (!file_exists($backupDir))
      return sprintf(L("backup folder(%s) does not exist."), $backupDir);

    // make sure all things have been saved
    sync();
    
    $sedonaAppRestored = false;
    
    $sedonaPath = sedonaPath();
    $appPathInBackup = build_file_path($backupDir, 'app.sab');
    $kitsPathInBackup = build_file_path($backupDir, 'kits.scode');
    if (file_exists($appPathInBackup) && file_exists($kitsPathInBackup))
    {
      if (!copy($appPathInBackup,
        build_file_path($sedonaPath, 'app.sab.writing')))
        return sprintf(L("failed to restore '%s'."), 'app.sab');
      if (!copy($kitsPathInBackup,
        build_file_path($sedonaPath, 'kits.scode.writing')))
        return sprintf(L("failed to restore '%s'."), 'kits.scode');

      error_log("copied app.sab and kits.scode");

      if (!rename(build_file_path($sedonaPath, 'app.sab.writing'),
        build_file_path($sedonaPath, 'app.sab.stage'))
       || !rename(build_file_path($sedonaPath, 'kits.scode.writing'),
        build_file_path($sedonaPath, 'kits.scode.stage')))
        return sprintf(L("failed to rename '%s' to '%s'."), 'writing', 'stage');
      $sedonaAppRestored = true;
    }
    else
      error_log("can not find sedona app in backup folder, skip it");

    $cpt_session_file = build_file_path($backupDir, 'cpt_session.xml');
    if (file_exists($cpt_session_file))
      copy($cpt_session_file,
        build_file_path($sedonaPath, 'cpt_session.xml'));

    if (empty($cptBasePath))
      $cptBasePath = cptBaseDir();
    $graphicDir = rtrim($cptBasePath, DIRECTORY_SEPARATOR);
    $parts = explode(DIRECTORY_SEPARATOR, $graphicDir);
    array_pop($parts);
    $cptParentDir = implode(DIRECTORY_SEPARATOR, $parts);

    $dstPath = null;
    
    $dataOnly = isDataOnlyBackup($backupDir);
    if ($dataOnly) 
    {
      $dstPath = build_file_path($cptParentDir, 'cpt');
      if (!file_exists($dstPath))
        return L("failed to restore backup: 'cpt' folder missing.");
    }
    else
    {
      $dstPath = build_file_path($cptParentDir, 'cpt-writing');
      rmIfExists($dstPath);
    }

    $cptFolderInBackup = build_file_path($backupDir, 'cpt'); 
    if (is_dir($cptFolderInBackup))
    {
      if (!recurseCopy($cptFolderInBackup, $dstPath))
        return L("failed to restore graphics, please make sure there is enough free disk space.");

      error_log("cpt folder copied ");
      
      if (!$dataOnly)
      {
        rmIfExists(build_file_path($cptParentDir, 'cpt-old'));

        if (is_dir($graphicDir) && !rename($graphicDir, build_file_path($cptParentDir, 'cpt-old')))
          return sprintf(L("failed to rename %s to %s."), 'cpt', 'cpt-old');
        if (!rename($dstPath, $graphicDir))
          return sprintf(L("failed to rename %s to %s."), 'cpt.writing', 'cpt');
      }
    }
    else
      error_log("can not find 'cpt' folder in this backup, skip it");

    runPluginRestoreCallbacks($graphicDir);

    if (!restoreFirmwareCustomData($backupDir))
      return L("failed to restore firmware data");

    if ($withHistoryDB)
    {
      $historyDBPath = build_file_path($backupDir, 'easyio.db');
      if (file_exists($historyDBPath))
      {
        $origHistoryDBPath = build_file_path($cptParentDir, 'easyio.db');
        if (file_exists($origHistoryDBPath))
          if (!rename($origHistoryDBPath, build_file_path($cptParentDir, 'easyio.db.old')))
            return L("failed to rename old history db.");

        if (!copy($historyDBPath, $origHistoryDBPath))
          return sprintf(L("failed to restore '%s'."), 'easyio.db');
      }
    }

    if ($withNetworkConf)
    {
      //call external handler app to do customized restore process
      $postRestoreHandler = "/mnt/sedona/post_restore_handler";
      if (file_exists($postRestoreHandler)) {
        $backupConfigRootDir = build_file_path($backupDir, 'root');
        $output = rtrim(`( $postRestoreHandler $backupConfigRootDir 2>&1 && echo OK ) || echo ERROR`);
        if (!endsWith($output, 'OK'))
          error_log("failed to execute post restore handler");
      } else {
        error_log("can not find post restore handler at: " . $postRestoreHandler);
      }
       
      // //merge fw.config file first
      // $fw_config = "/mnt/fw.config";
      // $new_fw_config = get_absolute_path(build_file_path($backupDir, 'root', $fw_config));
      // if (file_exists($fw_config) && file_exists($new_fw_config))
      //   mergeFWConfig($fw_config, $new_fw_config);
      //
      // $networkConfs = array('/etc/config/wireless', '/etc/config/network', '/etc/config/dhcp', '/etc/config/samba');
      // foreach($networkConfs as $conf) 
      // {
      //   $confPath = get_absolute_path(build_file_path($backupDir, 'root', $conf));
      //   if (file_exists($confPath) && startsWith($confPath, $backupDir))
      //     if (!recurseCopy($confPath, $conf))
      //       return sprintf(L("failed to restore config file: '%s'."), $conf); 
      // }
    }

    error_log("restored to " . $name);

    sync();

    if ($withNetworkConf)
      doReboot(true);
    else if ($sedonaAppRestored)
      restartSvm();
}

function loadFWConfig($config_path) {
  $lines = explode("\n", file_get_contents($config_path));
  $config = array();
  foreach($lines as $line) {
    if (strlen($line) == 0)
      continue;

    $parts = explode(":", $line, 2);
    if (count($parts) != 2)
      continue;

    $config[$parts[0]] = $parts[1];
  }
  return $config;
}

function mergeFWConfig($fw_config_old, $fw_config_new) {
  $old_config = loadFWConfig($fw_config_old);
  $new_config = loadFWConfig($fw_config_new);
  //we need to keep ReleaseDate, SvmVersion, McuVersion old configs, so we
  //ignore these in new config
  unset($new_config['ReleaseDate']);
  unset($new_config['SvmVersion']);
  unset($new_config['McuVersion']);

  $result = array();
  $merged_config = array_merge($old_config, $new_config);
  foreach($merged_config as $key => $value) {
    array_push($result, $key . ":" . $value);
  }
  file_put_contents($fw_config_old, implode("\n", $result));
}

function doCompressBackup($name, $type) {
    $type = strtolower($type);
    if ($type == "sdcard") {
      if (!does_sdcard_exist())
        return L("no sdcard available");

      $sdBackupPath = sdcardBackupPath();
      if (!file_exists($sdBackupPath)) {
        error_log("can not find sdcard mount path: " . $sdBackupPath);
        return L("can not find sdcard path");
      }

      $backupPath = $sdBackupPath;
    } else
      $backupPath = backupBasePath();

    $backupDir = build_file_path($backupPath, $name);
    if (!file_exists($backupDir))
      return; // just return when backup folder is missing
    
    //tar -C /www/public/sdcard/cpt-backups -czvf /tmp/foo.tgz 20170914_backup 
    $platName = platformName();
    $tarFilePath = "/tmp/{$platName}-{$name}.tgz";

    //tar: tar -cf - t2 | gzip -c > test.tgz
    $cmd = "tar -C {$backupPath} -cf - " . escapeshellarg($name) . " | gzip -c > " . escapeshellarg($tarFilePath);
    if(!execShellCmd($cmd)) {
      error_log("failed to build backup tgz");
      return;
    }

    return $tarFilePath;
}

function isUpgradeFix($backupDir) {
  if (!function_exists("json_decode"))
      return false;

  $filePath = build_file_path($backupDir, 'manifest.json');
  if (file_exists($filePath)) {
    $manifest = json_decode(file_get_contents($filePath), true);
    if (!is_null($manifest) && isset($manifest['dataOnly']) && isset($manifest['upgradeFix']))
      return $manifest['dataOnly'] && $manifest['upgradeFix'];
  } 
  return false;
}

function doHandleUploadBackup($tempName, $tarBallName, $size, $type, $restoreNow, $withNetworkConf, &$response) {
  $type = strtolower($type);
  if ($type == "sdcard") {
    if (!does_sdcard_exist())
      return L("no sdcard available");

    $sdBackupPath = sdcardBackupPath();
    if (!file_exists($sdBackupPath)) {
      error_log("can not find sdcard mount path: " . $sdBackupPath);
      return L("can not find sdcard path");
    }

    $backupPath = $sdBackupPath;
  } else
    $backupPath = backupBasePath();

  if (!file_exists($backupPath)) {
    if (!mkdir($backupPath, 0777, true)) {
      error_log("can not create backup folder: " . $backupPath);
      return L("can not create backup folder");
    }
  }
  
  $freeSpace = disk_free_space($backupPath);
  $compressRatio = 5;
  $size = (int)$size;
  if ($freeSpace <= $size*$compressRatio) {
    error_log("not enough disk free space({$freeSpace} bytes left)");
    return L("not enough disk free space({$freeSpace} bytes left)");
  }
  
  $ext = pathinfo($tarBallName, PATHINFO_EXTENSION);
  $name = basename($tarBallName, ".$ext");

  if (!is_uploaded_file($tempName)) {
    error_log("uploaded file {$name} verfication failed.");
    return L("uploaded file {$name} verfication failed.");
  }

  $cmd = null;
  if ($ext == "tgz")
    $cmd = "gunzip -c {$tempName} | tar -C {$backupPath} -xf -";
  elseif ($ext == "zip")
    $cmd = "unzip -o -d {$backupPath} {$tempName}";
  else {
    error_log("invalid backup tarball format: $ext");
    return L("invalid backup tarball format: $ext");
  }

  if (!execShellCmd($cmd)) {
    error_log("failed to uncompress backup tarball");
    return L("failed to uncompress backup tarball");
  }
  
  //check if this is a upgrade tarball, if yes, apply it now and clean it up
  $output = null;
  if ($ext == "tgz")
    $output = shell_exec("gunzip -c {$tempName} | tar -tf -");
  elseif ($ext == "zip")
    $output = shell_exec("unzip -l {$tempName} | awk 'NR == 4 {print $NF}'");

  if (!empty($output) && sizeof($output) > 0) {
    $output = explode("\n", $output);
    $backupName = rtrim($output[0], DIRECTORY_SEPARATOR);
    $backupDir = build_file_path($backupPath, $backupName);
    $isUpgrade = isUpgradeFix($backupDir);
    if ($isUpgrade) {
      error_log("Found upgrade backup {$backupName}, apply this ugprade ...");
      $error = doRestore($backupName, NULL, false, $type, $withNetworkConf);
      error_log("Delete upgrade backup folder {$backupName}");
      rmIfExists($backupDir);

      if (!empty($error)) {
        error_log("Failed to apply upgrade: {$error}");
        return L("failed to apply upgrade: {$error}");
      }
      else {
        if (isset($response)) {
          $response['msg'] = L("Success to apply upgrade {$backupName}.");
          $response['reloadRequired'] = true;
        }
        error_log("Success to apply upgrade {$backupName}.");
      }
    } else if ($restoreNow) {
      $error = doRestore($backupName, NULL, true, $type, $withNetworkConf);
      if (!empty($error)) {
        error_log("Failed to restore backup: {$error}");
        return L("failed to restore backup: {$error}");
      }
      else {
        if (isset($response)) {
          $response['msg'] = L("Success to restore to backup {$backupName}.");
          $response['reloadRequired'] = true;
        }
        error_log("Success to restore to backup {$backupName}.");
      }
    }
  }

  return '';
} 

function doDeleteBackup($name, $type) {
    $type = strtolower($type);
    if ($type == "sdcard") {
      if (!does_sdcard_exist())
        return L("no sdcard available");

      $sdBackupPath = sdcardBackupPath();
      if (!file_exists($sdBackupPath)) {
        error_log("can not find sdcard mount path: " . $sdBackupPath);
        return L("can not find sdcard path");
      }

      $backupPath = $sdBackupPath;
    } else
      $backupPath = backupBasePath();

    $backupDir = build_file_path($backupPath, $name);
    if (!file_exists($backupDir))
      return; // just return when backup folder is missing
      // return "backup folder(" . $backupDir . ") does not exist.";

    //security check
    if (!startsWith(realpath($backupDir), $backupPath))
      return L("invalid backup folder.");

    rmIfExists($backupDir);

    error_log("backup " . $name . " is deleted");

    sync();
}

function doRenameBackup($old_name, $new_name, $type) {
    $type = strtolower($type);
    if ($type == "sdcard") {
      if (!does_sdcard_exist())
        return L("no sdcard available");

      $sdBackupPath = sdcardBackupPath();
      if (!file_exists($sdBackupPath)) {
        error_log("can not find sdcard mount path: " . $sdBackupPath);
        return L("can not find sdcard path");
      }

      $backupPath = $sdBackupPath;
    }
    else
      $backupPath = backupBasePath();

    $oldBackupDir = build_file_path($backupPath, $old_name);
    if (!file_exists($oldBackupDir))
      return L("invalid backup folder.");

    $newBackupDir = build_file_path($backupPath, $new_name);
    if (file_exists($newBackupDir))
      return sprintf(L("rename failed. '%s' already exists."), $new_name);
    
    //security check
    if (!startsWith(realpath($oldBackupDir), $backupPath)
        || !startsWith($newBackupDir, $backupPath))
      return L("invalid backup folder.");
    
    if (!rename($oldBackupDir, $newBackupDir))
        return sprintf(L("can not rename backup from '%s' to '%s'"), $old_name, $new_name);

    error_log("backup '$old_name' is renamed to '$new_name'");

    sync();
}

function doReboot($async=false) {
    sync();

    if ($async)
      //FW doesn't support sleep less than 1 second
      shell_exec('(sleep 1 && reboot >/dev/null 2>&1 ) &');
    else
      execCmd('reboot');
}

function recurseCopy($src, $dst) {
  if (is_file($src)) {
    $dst_dir = dirname($dst);
    if (!file_exists($dst_dir))
      mkdir($dst_dir, 0777, true);
    return copy($src, $dst);
  }

  if (!file_exists($dst))
    mkdir($dst, 0777, true);

  $dir = opendir($src);
  while(false !== ( $file = readdir($dir)) ) {
    if (( $file != '.' ) && ( $file != '..' )) {
      if ( is_dir(build_file_path($src, $file)) ) {
        if (!recurseCopy(build_file_path($src, $file), build_file_path($dst, $file))) {
          closedir($dir);
          return false;
        }
      }
      else {
        if (!copy(build_file_path($src, $file), build_file_path($dst, $file))) {
          closedir($dir);
          return false;
        }
      }
    }
  }
  closedir($dir);
  return true;
}

function execCmd($cmd) {
  exec(escapeshellcmd($cmd))
}

function execShellCmd($cmd) {
  //the input cmd is not escaped yet, it's the caller's responsibility to escape it 
  $output = shell_exec("( $cmd ) && echo 'SUCCESS'");
  return !(is_null($output) || preg_match("/SUCCESS\s*$/", $output) != 1);
}

function cleanupGhostScripts() {
    $docRootPath = rtrim($_SERVER['DOCUMENT_ROOT'], DIRECTORY_SEPARATOR);
    $ghostScriptsPath = build_file_path($docRootPath, 'ghost_scripts');

    rmIfExists($ghostScriptsPath);
}

function restartSvm() {
    $pfName = platformName();
    if ($pfName == "FW") {
      include_once "UDPClient.class.php";
      //restart svm by sedona action
      $client = new UDPClient("127.0.0.1", "1001");
      $client->setDebug(false);
      $client->invokeAction("/app/objects/.restart", "void", "");
    }
    else {
      //reboot command was added since FG+(from cheah)
      //for FG and FG-, use the old way, otherwise use reboot command
      if ($pfName == "FG" || $pfName == "FG-")
          execCmd("killall FGBase svm.exe");
      else
          doReboot(true);
          //execCmd("/sbin/reboot");
    }
}

// echo arrayMapToString(array('a' => 1, 'b' =>2, 'c' => 100), ",");

function firmwareUpgradePath() {
    $name = platformName();
    if ($name == "FS")
      return build_file_path('', 'media', 'boot', 'fs');
    else if ($name == "FW")
      return build_file_path('', 'tmp');
    else {
      $rootPath = $_SERVER['DOCUMENT_ROOT'];
      if (file_exists(build_file_path($rootPath, 'cpt_site_config.php')))
      {
        include_once(build_file_path($rootPath, 'cpt_site_config.php'))
        return CptSiteConfig::$firmwareUpgradePath;
      }
      else
      {
        if ($name == "FG+" || $name == "FG")
            return build_file_path('', 'sdcard', 'fg320');
        else 
        {
            error_log("firmware upgrade is not supported on this platform: " . $name);
            return null;
        }
      }
    }
}

function firmwareFileName() {
  // FS32: fs_rom.xz 
  // FG32: fg320.tar.gz 
  // FG32+: fg320plus.md5, rootfs.yaffs2, u-boot.bin, zImage (not supported)
  $name = platformName();
  if ($name == "FS")
    return "fs_rom.xz";
  else if ($name == "FG")
    return "fg320.tar.gz";
  else if ($name == "FW")
    return "fw140.tar.gz";
  else
    return "";
}

function does_sdcard_exist() {
  $sdcardPath = sdcardMountPath();
  if (is_null($sdcardPath)) {
    $filePath = realpath("/sdcard/No SD Card");
    if (file_exists($filePath))
      return false;
    else
      return true;
  } else {
    return (file_exists($sdcardPath) && !file_exists(rtrim($sdcardPath, '/') . "/No SD Card"));
  }
}

function isPlatformNewerThan($dt_str) {
  if (is_string($dt_str))
    $dt = date_create_from_format("Y-m-d H:i:s", $dt_str);
  else {
    if ($dt_str instanceof DateTime)
      $dt = $dt_str;
    else
      throw new Exception("invalid datetime parameter");
  }
  
  $plat_dt = platformReleaseDate();
  return $plat_dt >= $dt;
}

function platformReleaseDate() {
  $plat_dt = NULL;
  $confFile = new DeviceConfigFile;
  if ($confFile->load()) {
    $plat_dt = $confFile->value("ReleaseDate", NULL);
    if (!is_null($plat_dt))
      $plat_dt = date_create_from_format("Y-m-d H:i:s", $plat_dt);
  }
  return $plat_dt;
}

function platformName() {
  //first check /mnt/sedona/easyio.conf file 
  $confFile = new DeviceConfigFile;
  if ($confFile->load()) {
    $name = $confFile->value("Model", NULL);
    if (!is_null($name))
      return strtoupper($name);
  }

  // if can not find easyio.conf, use legacy hacky way
  if (file_exists("/mnt/fw.config"))
    return "FW";

  $matches = array();
  $fg_linux_ver = '2.6.29.2';
  $fs_linux_ver = '3.4';
  $versionStr = file_get_contents("/proc/version"); 
  if (!preg_match('/linux version ([0-9.]+)/i', $versionStr, $matches))
    return "InvalidPlatform";
  else {
    if (count($matches) < 2 || !isset($matches[1]))
      return "InvalidPlatform";
    else {
      $result = strcmp($matches[1], $fg_linux_ver);
      if ($result > 0) {
        $result2 = strcmp($matches[1], $fs_linux_ver);
        if ($result2 >= 0)
          return "FS";
        else
          return "FG+"; # platform released after FG
      }
      else if ($result == 0)
        return "FG"; # FG platform
      else
        return "FG-"; # platform released before FG
    }
  }
}

function file_force_contents($filename, $data, $flags = 0){
  if(!is_dir(dirname($filename)))
      mkdir(dirname($filename).DIRECTORY_SEPARATOR, 0777, true);
  return file_put_contents($filename, $data, $flags);
}

function setupIndex($docRootPath, $cptBasePath, $graphicRoot=true) {
  if (empty($docRootPath))
    $docRootPath = rtrim($_SERVER['DOCUMENT_ROOT'], DIRECTORY_SEPARATOR);
  
  if (empty($cptBasePath))
    $cptBasePath = cptBaseDir();

  $indexPath = join(DIRECTORY_SEPARATOR, array($docRootPath, 'index.html'));
  $indexLegacyPath = join(DIRECTORY_SEPARATOR, array($docRootPath, 'index_legacy.html'));

  if ($graphicRoot) {
    $graphicIndexPath  = join(DIRECTORY_SEPARATOR, array($cptBasePath, 'index.html'));
    if (!file_exists($indexLegacyPath) && file_exists($indexPath))
      copy($indexPath, $indexLegacyPath);
    copy($graphicIndexPath, $indexPath);
  }
  else {
    if (file_exists($indexLegacyPath))
      copy($indexLegacyPath, $indexPath);
  }
}


function isAssoc($array) {
  $keys = array_keys($array);
  return array_keys($keys) !== $keys;
}

function list2json($listData) {
  $json = array();
  foreach($listData as $item) {
      if (is_string($item))
        $item = addcslashes($item,"\\\"\n\r");
      else if (is_array($item)) {
        if (isAssoc($item))
          $item = map2json($item);
        else
          $item = list2json($item);
        $json[] = $item;
        continue;
      }
      $json[] = '"' . $item . '"';
  }
  return '[' . implode(',', $json) . ']';
}

function map2json($mapData) {
  if (function_exists("json_encode"))
    return json_encode($mapData);

  $json = array();
  foreach($mapData as $k => $v) {
    if (is_string($k))
      $k = addcslashes($k,"\\\"\n\r");

    if (is_string($v))
      $v = addcslashes($v,"\\\"\n\r");
    else if (is_bool($v)) {
      $v = $v ? 'true' : 'false';
      $json[] = "\"$k\": $v";
      continue;
    }
    else if (is_array($v)) {
      if (isAssoc($v))
        $v = map2json($v);
      else
        $v = list2json($v);

      $json[] = "\"$k\": $v";
      continue;
    }
    $json[] = "\"$k\": \"$v\"";
  }
  return '{' . implode(',', $json) . '}';
}
  
function cptToolsPath() {
  $rootPath = $_SERVER['DOCUMENT_ROOT'];
  return build_file_path($rootPath, 'sdcard', 'CPT-Tools.zip');
}

function updatePhpAdminAuth($salt, $checksum) {
  $root_path = parentPath(3);
  $file_path = build_file_path($root_path, 'phpliteadmin.config.php');
  $cpt_auth_content = <<<EOS
<?php
  \$cpt_password_salt = "$salt";
  \$cpt_password_checksum = "$checksum";
?>
EOS;
  file_force_contents($file_path, $cpt_auth_content);
}

function readfile_chunked($filename, $retbytes=true) { 
    $chunksize = 1*(32*1024); // how many bytes per chunk 
    $buffer = ''; 
    $cnt =0; 
    $handle = fopen($filename, 'rb'); 
    if ($handle === false) { 
       return false; 
    } 
    while (!feof($handle)) { 
        $buffer = fread($handle, $chunksize); 
        echo $buffer; 
        ob_flush(); 
        flush(); 
        if ($retbytes) { 
            $cnt += strlen($buffer); 
        } 
    } 
    $status = fclose($handle); 
    if ($retbytes && $status) { 
        return $cnt; // return num. bytes delivered like readfile() does. 
    } 
   return $status; 
} 

function getDeviceData() {
  $data = array();

  //populate device config data
  $confFile = new DeviceConfigFile;
  if ($confFile->load()) {
    foreach($confFile->keys() as $key) {
      $value = $confFile->value($key, NULL);
      if (is_null($value))
        continue;
      $key = '_' . str_replace(" ", "_", trim(strtolower($key)));
      $data[$key] = $value;
    }
  }
  
  //add calculated device data 
  $data["platform_name"] = platformName();
  $data["sdcard_exists"] = does_sdcard_exist();
  if ($data["sdcard_exists"]) {
    $sdcardPath = sdcardMountPath();
    if (is_null($sdcardPath)) // then is the case of FG or FG+
      $sdcardPath = "/sdcard"; //sdcard is mounted at /sdcard for FG or FG+ 
    $data["sdcard_free_space"] = disk_free_space($sdcardPath);
    $data["sdcard_backup_ftp_path"] = sdcardBackupFtpPath();
    $data["sdcard_ftp_path"] = sdcardFtpPath();
  }
  $data["flash_free_space"] = disk_free_space("/");

  return $data;
}

function getPlatformConfig() {
  $config = array();
  //first, read easyio.config file into $config hash
  $conf_file_path = "/mnt/sedona/easyio.config";
  if (!file_exists($conf_file_path)) {
    error_log("can not find " . $conf_file_path);
    return $config;
  }

  $contents = file_get_contents($conf_file_path);
  foreach(explode(PHP_EOL, $contents) as $line) {
    if (empty($line))
      continue;

    $parts = explode(":", $line, 2);
    if (count($parts) != 2) {
      error_log("invalid config line: " . $line);
      continue;
    }
    $config[$parts[0]] = $parts[1]; 
  }

  if (empty($config))
    error_log("can not read content from config file: " . $conf_file_path);
  return $config;
}

function savePlatformConfig($config) {
  $lines = array();
  foreach($config as $key => $value)
    $lines[] = $key . ':' . $value;
  $conf_file_path = "/mnt/sedona/easyio.config";
  file_put_contents($conf_file_path, implode(PHP_EOL, $lines));
}

$netmaskList = array(
  "0.0.0.0", "128.0.0.0", "192.0.0.0", "224.0.0.0",
  "240.0.0.0", "248.0.0.0", "252.0.0.0", "254.0.0.0",
  "255.0.0.0", "255.128.0.0", "255.192.0.0", "255.224.0.0",
  "255.240.0.0", "255.248.0.0", "255.252.0.0", "255.254.0.0",
  "255.255.0.0", "255.255.128.0", "255.255.192.0", "255.255.224.0",
  "255.255.240.0", "255.255.248.0", "255.255.252.0", "255.255.254.0",
  "255.255.255.0", "255.255.255.128", "255.255.255.192", "255.255.255.224",
  "255.255.255.240", "255.255.255.248", "255.255.255.252", "255.255.255.254",
);
function cdir2MaskIP($cdir) {
  global $netmaskList;
  $index = intval($cdir);
  if ($index >= 0 && $index < count($netmaskList))
    return $netmaskList[$index];
  else
    return null;
}
function maskIP2CDIR($netmask) {
  global $netmaskList;
  return array_search($netmask, $netmaskList);
}

function render_template($path, array $args){
  ob_start();
  include($path);
  $var=ob_get_contents(); 
  ob_end_clean();
  return $var;
}

function runPluginBackupCallbacks($backupDir, $graphicDir) {
  error_log("run plugin backup callback");
  $pluginsBackupDir = build_file_path($backupDir, "cpt", "plugins");
  if (!file_exists($pluginsBackupDir))
    mkdir($pluginsBackupDir, 0777);
  $plugins = collectPlugins();
  foreach($plugins as $plugin) {
    if (!is_array($plugin) || !isset($plugin['utilityFile']))
      continue;

    $utilityFilePath = build_file_path($graphicDir, $plugin['utilityFile']);
    if (!file_exists($utilityFilePath))
      continue;

    $utilityInstance = include $utilityFilePath;
    if (is_null($utilityInstance))
      continue;

    if (method_exists($utilityInstance, "backup"))
      $utilityInstance->backup($pluginsBackupDir);
  }
}

function runPluginRestoreCallbacks($graphicDir) {
  error_log("run plugin restore callback");
  $plugins = collectPlugins();
  foreach($plugins as $plugin) {
    if (!is_array($plugin) || !isset($plugin['utilityFile']))
      continue;

    $utilityFilePath = build_file_path($graphicDir, $plugin['utilityFile']);
    if (!file_exists($utilityFilePath))
      continue;

    $utilityInstance = include $utilityFilePath;
    if (is_null($utilityInstance))
      continue;

    if (method_exists($utilityInstance, "restore"))
      $utilityInstance->restore();
  }
}

function backupFirmwareCustomData($backupDir) {
  $fwCustomDataDir = "/mnt/data";
  if (file_exists($fwCustomDataDir))
  {
    $tarFilePath = build_file_path($backupDir, "firmware_data.tar");
    $output = shell_exec("tar -C /mnt/data -cf $tarFilePath . && echo 'SUCCESS'");
    if (is_null($output) || preg_match("/SUCCESS\s*$/", $output) != 1) {
      error_log("failed to backup " . $fwCustomDataDir . " to " . $tarFilePath);
      return false;
    }
  }
  return true;
}

function restoreFirmwareCustomData($backupDir) {
  $fwCustomDataBackupTarFile = build_file_path($backupDir, "firmware_data.tar");
  if (file_exists($fwCustomDataBackupTarFile)) 
  {
    $fwCustomDataDir = "/mnt/data";
    if (!file_exists($fwCustomDataDir))
      mkdir($fwCustomDataDir, 0644, TRUE);

    $output = shell_exec("tar -C /mnt/data -xf $fwCustomDataBackupTarFile && echo 'SUCCESS'");
    if (is_null($output) || preg_match("/SUCCESS\s*$/", $output) != 1) {
      error_log("failed to restore " . $fwCustomDataBackupTarFile . " to " . $fwCustomDataDir);
      return false;
    }
  }
  return true;
}

function clientIP() {
  $ip = $_SERVER['REMOTE_ADDR'];
  if (isset($_SERVER['HTTP_CLIENT_IP']) && preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $_SERVER['HTTP_CLIENT_IP'])) {
    $ip = $_SERVER['HTTP_CLIENT_IP'];
  }
  return $ip;
}

?>