#!/usr/bin/php -q 0) { natcasesort($rows); $user = array_filter($rows, function($path){return preg_match('/^\/mnt\/user0?\//', $path);}); if (count($user) > 0) { for ($n=0; $n < count($user); $n+=100) { $name = array_slice($user, $n, 100); $set[] = exec("getfattr --no-dereference --absolute-names --only-values -n system.LOCATIONS ".quoted($name)." 2>$null"); } $disks = parse_ini_file('state/disks.ini', true); $tag = implode('|',array_merge(['disk'],pools_filter($disks))); $set = explode(';',str_replace(',;',',',preg_replace("/($tag)/", ';$1', implode($set)))); } foreach (array_diff($rows, $user) as $row) { [$none, $root, $main] = explode('/',$row,4); $cat[] = ($root == 'mnt' ? $main : ($root == 'boot' ? 'flash' : '---'))."\0".$row; } $i = 0; foreach ($user as $row) $cat[] = $set[++$i]."\0".$row; } return "#cat#\n".implode("\n",$cat)."\n"; } function escape($name) {return escapeshellarg(validname($name));} function quoted($name) {return is_array($name) ? implode(' ',array_map('escape', $name)) : escape($name);} function source($name) {return is_array($name) ? implode(' ',array_map('escapeshellarg', $name)) : escapeshellarg($name);} // Escape rsync filter meta characters in a single path component function rsync_escape_component($s) { // escape: *, ?, [, ], \ return preg_replace('/([*?\[\]\\\\])/', '\\\\$1', $s); } function quoted_rsync_include($paths) { // note: this function is never called with invalid names because of "if (!$valid_source_path)" $result = []; foreach ($paths as $path) { $valid_path = validname($path); $base = rsync_escape_component(basename($valid_path)); if (is_dir($valid_path) && !is_link($valid_path)) { $result[] = "--include=" . escapeshellarg("/{$base}/***"); } else { $result[] = "--include=" . escapeshellarg("/{$base}"); } } return implode(' ', $result); } // create empty directory for optimized move if not exists if (!file_exists($empty_dir)) { mkdir($empty_dir); } // initialize $move state: null = not a move operation (yet), true = rsync phase, false = cleanup phase $move = null; while (true) { unset($action, $source, $target, $H, $sparse, $exist, $zfs); // read job parameters from ini file: $action, $title, $source, $target, $H, $sparse, $exist, $zfs (set by emhttp/plugins/dynamix/include/Control.php) if (file_exists($active)) extract(parse_ini_file($active)); // read PID from file (file_manager may have been restarted) if (!$pid && file_exists($pid_file)) { $pid = trim(file_get_contents($pid_file)); } $reply = []; if (isset($action)) { // check for language changes extract(parse_plugin_cfg('dynamix', true)); if ($display['locale'] != $locale_init) { $locale_init = $display['locale']; update_translation($locale_init); } $source = explode("\r", $source); switch ($action) { case 0: // create folder if (!empty($pid)) { $reply['status'] = ''._('Creating').'...'; } else { $dir = $source[0].'/'.$target; exec("mkdir -pm0777 ".quoted($dir)." 1>$null 2>$error & echo $!", $pid); exec("chown -Rfv nobody:users ".quoted($dir)); } break; case 1: // delete folder case 6: // delete file if (!empty($pid)) { $reply['status'] = ''._('Removing').'... '.exec("tail -1 $status"); } else { exec("find ".quoted($source)." -name \"*\" -print -delete 1>$status 2>$null & echo \$!", $pid); } break; case 2: // rename folder case 7: // rename file if (!empty($pid)) { $reply['status'] = ''._('Renaming').'...'; } else { $path = dirname($source[0]); exec("mv -f ".quoted($source)." ".quoted("$path/$target")." 1>$null 2>$error & echo \$!", $pid); } break; case 3: // copy folder case 8: // copy file if (!empty($pid)) { $reply['status'] = ''._('Copying').'... '.shell_exec("tail -2 $status | awk -F\"\\r\" '{gsub(/^ +/,\"\",\$NF);print \$NF}'"); } else { $target = validname($target, false); if ($target) { $mkpath = isdir($target) ? '--mkpath' : ''; exec("rsync -ahPIX$H $sparse $exist $mkpath --out-format=%f --info=flist0,misc0,stats0,name1,progress2 ".quoted($source)." ".escapeshellarg($target)." 1>$status 2>$error & echo \$!", $pid); } else { $reply['error'] = 'Invalid target name'; } } break; case 4: // move folder (rsync) case 5: // move folder (mv) - this case needs to be removed from Browse.page case 9: // move file (rsync) case 10: // move file (mv) - this case needs to be removed from Browse.page if (!empty($pid)) { // Set move state for resume: true=rsync phase, false=cleanup phase if ($move === null) $move = true; $reply['status'] = ''._('Moving').'... '.($move===false ? exec("tail -1 $status") : shell_exec("tail -2 $status | awk -F\"\\r\" '{gsub(/^ +/,\"\",\$NF);print \$NF}'")); } else { $target = validname($target, false); if ($target) { $move = true; $mkpath = isdir($target) ? '--mkpath' : ''; // determine if we can use rsync with rename(2) // requirements: // 1. all sources and target must be on the same filesystem (device) // 2. all sources must have the same parent directory // 3. either no --ignore-existing flag OR no conflicts with existing files on target $use_rsync_rename = false; $last_dirname = ''; $target_device_id = []; // for filesystem check, find first existing parent directory $target_for_stat = $target; while (!file_exists($target_for_stat) && $target_for_stat != '/') { $target_for_stat = dirname($target_for_stat); } exec("stat -c %d -- ".escapeshellarg($target_for_stat)." 2>/dev/null", $target_device_id); // check all source paths if (!empty($target_device_id)) { $use_rsync_rename = true; // assume we can use it, then check for disqualifying conditions foreach ($source as $source_path) { // source path must be valid $valid_source_path = validname($source_path); if (!$valid_source_path) { $use_rsync_rename = false; break; } // filesystem (device) of source and target must be equal $source_device_id = []; exec("stat -c %d -- ".escapeshellarg($valid_source_path)." 2>/dev/null", $source_device_id); if (empty($source_device_id) || $source_device_id[0] != $target_device_id[0]) { $use_rsync_rename = false; break; } // parent directory of all source paths must be equal (not sure if this is really required, but keeping for now) if (!empty($last_dirname) && $last_dirname != dirname($valid_source_path) ) { $use_rsync_rename = false; break; } $last_dirname = dirname($valid_source_path); // target must be a directory if (!is_dir(rtrim($target,'/'))) { $use_rsync_rename = false; break; } // selected source files and directories must not exist on target when "Overwrite existing files" is not set if (!empty($exist)) { // would add "--ignore-existing" to rsync $target_item = rtrim($target, '/') . '/' . basename($valid_source_path); if (file_exists($target_item)) { $use_rsync_rename = false; break; } } } } // use rsync rename // let rsync act like "mv" by syncing an empty directory against the source location, which moves the files to --backup-dir // notes: // - existing files are overwritten in --backup-dir (like not using --ignore-existing) // - missing directories are created in --backup-dir (like using --mkpath) if ($use_rsync_rename) { $parent_dir = dirname(validname($source[0])); $cmd = "rsync -r --out-format=%f --info=flist0,misc0,stats0,name1,progress2 --delete --backup --backup-dir=".escapeshellarg($target)." ".quoted_rsync_include($source)." --exclude='*' ".escapeshellarg($empty_dir)." ".escapeshellarg($parent_dir)." 1>$status 2>$error & echo \$!"; exec($cmd, $pid); // use rsync copy-delete } else { $cmd = "rsync -ahPIX$H $sparse $exist $mkpath --out-format=%f --info=flist0,misc0,stats0,name1,progress2 --remove-source-files ".quoted($source)." ".escapeshellarg($target)." 1>$status 2>$error & echo \$!"; exec($cmd, $pid); } } else { $reply['error'] = 'Invalid target name'; } } break; case 11: // change owner if (!empty($pid)) { $reply['status'] = ''._('Updating').'... '.exec("tail -2 $status | grep -Pom1 \"^.+ of '\\K[^']+\""); } else { exec("chown -Rfv $target ".quoted($source)." 1>$status 2>$error & echo \$!", $pid); } break; case 12: // change permission if (!empty($pid)) { $reply['status'] = ''._('Updating').'... '.exec("tail -2 $status | grep -Pom1 \"^.+ of '\\K[^']+\""); } else { exec("chmod -Rfv $target ".quoted($source)." 1>$status 2>$error & echo \$!", $pid); } break; case 15: // search if (!empty($pid)) { $reply['status'] = ''._('Searching').'... '.exec("wc -l $status | grep -Pom1 '^[0-9]+'"); } else { exec("find ".source($source)." -iname ".escapeshellarg($target)." 1>$status 2>$null & echo \$!", $pid); } break; case 99: // kill running background process if (!empty($pid)) exec("kill $pid"); delete_file($active, $pid_file, $status, $error); unset($pid, $move); break; default: continue 2; } $pid = pgrep($pid??0); // Store PID to survive file_manager restarts if ($pid !== false) { file_put_contents($pid_file, $pid); } if ($pid === false) { if (!empty($move)) { exec("find ".quoted($source)." -type d -empty -print -delete 1>$status 2>$null & echo \$!", $pid); $move = false; $pid = pgrep($pid); } else { if ($action != 15) { $reply['status'] = _('Done'); $reply['done'] = 1; } else { $reply['status'] = cat($status); $reply['done'] = 2; } if ($zfs) { $pools = explode("\r",$zfs); foreach ($pools as $pool) { unset($datasets); exec("zfs list -Ho name|grep '^$pool'",$datasets); foreach ($datasets as $dataset) if (exec("ls --indicator-style=none /mnt/$dataset|wc -l")==0) exec("zfs destroy $dataset 2>/dev/null"); } } if (file_exists($error)) $reply['error'] = str_replace("\n","
", trim(file_get_contents($error))); delete_file($active, $pid_file, $status, $error); unset($pid, $move); } } } if (time() - $timer) { // update every second publish('filemonitor', file_exists($active) ? 1 : 0); $timer = time(); } publish('filemanager', json_encode($reply)); usleep(250000); } ?>