瀏覽代碼

Fixed realpath to virtual path translation for incorrect translation of path owned by other user

TC pushbot 5 4 年之前
父節點
當前提交
6bf9023078
共有 4 個文件被更改,包括 249 次插入13 次删除
  1. 117 2
      backup.go
  2. 61 1
      mod/disk/hybridBackup/hybridBackup.go
  3. 10 0
      mod/user/directoryHandler.go
  4. 61 10
      web/SystemAO/disk/disk_restore.html

+ 117 - 2
backup.go

@@ -2,7 +2,11 @@ package main
 
 import (
 	"encoding/json"
+	"log"
 	"net/http"
+	"path/filepath"
+
+	"imuslab.com/arozos/mod/disk/hybridBackup"
 
 	prout "imuslab.com/arozos/mod/prouter"
 )
@@ -17,9 +21,89 @@ func backup_init() {
 		},
 	})
 
+	//Register API endpoints
 	router.HandleFunc("/system/backup/listRestorable", backup_listRestorable)
+	router.HandleFunc("/system/backup/restoreFile", backup_restoreSelected)
 }
 
+//Restore a given file
+func backup_restoreSelected(w http.ResponseWriter, r *http.Request) {
+	//Get user accessiable storage pools
+	userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
+	if err != nil {
+		sendErrorResponse(w, "User not logged in")
+		return
+	}
+
+	//Get Backup disk ID from request
+	bdid, err := mv(r, "bdid", true)
+	if err != nil {
+		sendErrorResponse(w, "Invalid backup disk ID given")
+		return
+	}
+
+	//Get fsh from the id
+	fsh, err := GetFsHandlerByUUID(bdid)
+	if err != nil {
+		sendErrorResponse(w, err.Error())
+		return
+	}
+
+	//Get the relative path for the restorable file
+	relpath, err := mv(r, "relpath", true)
+	if err != nil {
+		sendErrorResponse(w, "Invalid relative path given")
+		return
+	}
+
+	//Check if the path exists in the backup disk
+	if !fileExists(filepath.Join(fsh.Path, relpath)) {
+		sendErrorResponse(w, "Restore source file not exists")
+		return
+	}
+
+	//Handle restore of the file
+	err = userinfo.HomeDirectories.HyperBackupManager.HandleRestore(fsh.UUID, relpath)
+	if err != nil {
+		sendErrorResponse(w, err.Error())
+		return
+	}
+
+	type RestoreResult struct {
+		RestoreDiskID       string
+		TargetDiskID        string
+		RestoredVirtualPath string
+	}
+
+	result := RestoreResult{
+		RestoreDiskID: fsh.UUID,
+	}
+
+	//Get access path for this file
+	parentDiskId, err := userinfo.HomeDirectories.HyperBackupManager.GetParentDiskIDByRestoreDiskID(fsh.UUID)
+	if err != nil {
+		//Unable to get parent disk ID???
+
+	} else {
+		//Get the path of the parent disk
+		parentDiskHandler, err := GetFsHandlerByUUID(parentDiskId)
+		if err == nil {
+			//Join the result to create a virtual path
+			assumedRestoreRealPath := filepath.ToSlash(filepath.Join(parentDiskHandler.Path, relpath))
+			restoreVpath, err := userinfo.RealPathToVirtualPath(assumedRestoreRealPath)
+			if err == nil {
+				result.RestoredVirtualPath = restoreVpath
+			}
+			result.TargetDiskID = parentDiskId
+		}
+
+	}
+
+	js, _ := json.Marshal(result)
+	sendJSONResponse(w, string(js))
+}
+
+//Generate and return a restorable report
 func backup_listRestorable(w http.ResponseWriter, r *http.Request) {
 	//Get user accessiable storage pools
 	userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
@@ -42,13 +126,44 @@ func backup_listRestorable(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	userBackupManager := userinfo.HomeDirectories.HyperBackupManager
+
 	//Get the user's storage pool and list restorable by the user's storage pool access
-	restorableReport, err := userinfo.HomeDirectories.HyperBackupManager.ListRestorable(fsh.UUID)
+	restorableReport, err := userBackupManager.ListRestorable(fsh.UUID)
 	if err != nil {
 		sendErrorResponse(w, err.Error())
 		return
 	}
 
-	js, _ := json.Marshal(restorableReport)
+	//Get and check if the parent disk has a user Hierarcy
+	paretnfsh, err := GetFsHandlerByUUID(restorableReport.ParentUID)
+	if err != nil {
+		sendErrorResponse(w, err.Error())
+		return
+	}
+
+	result := hybridBackup.RestorableReport{
+		ParentUID: restorableReport.ParentUID,
+	}
+
+	log.Println(paretnfsh)
+
+	if paretnfsh.Hierarchy == "user" {
+		//The file system is user based. Filter out those file that is not belong to this user
+		for _, restorableFile := range restorableReport.RestorableFiles {
+			fileAbsPath := filepath.Join(fsh.Path, restorableFile.RelpathOnDisk)
+			_, err := userinfo.RealPathToVirtualPath(fileAbsPath)
+			if err != nil {
+				//Cannot translate this file. That means the file is not owned by this user
+			} else {
+				//Can translate the path.
+				result.RestorableFiles = append(result.RestorableFiles, restorableFile)
+			}
+		}
+	} else {
+		result = restorableReport
+	}
+
+	js, _ := json.Marshal(result)
 	sendJSONResponse(w, string(js))
 }

+ 61 - 1
mod/disk/hybridBackup/hybridBackup.go

@@ -366,9 +366,52 @@ func (backupConfig *BackupTask) HandleBackupProcess() (string, error) {
 	return "", nil
 }
 
+//Get the restore parent disk ID by backup disk ID
+func (m *Manager) GetParentDiskIDByRestoreDiskID(restoreDiskID string) (string, error) {
+	backupTask := m.getTaskByBackupDiskID(restoreDiskID)
+	if backupTask == nil {
+		return "", errors.New("This disk do not have a backup task in this backup maanger")
+	}
+
+	return backupTask.ParentUID, nil
+}
+
 //Restore accidentailly removed file from backup
-func HandleRestore(parentDiskID string, restoreDiskID string, targetFileRelpath string) error {
+func (m *Manager) HandleRestore(restoreDiskID string, targetFileRelpath string) error {
+	//Get the backup task from backup disk id
+	backupTask := m.getTaskByBackupDiskID(restoreDiskID)
+	if backupTask == nil {
+		return errors.New("Target disk is not a backup disk")
+	}
+
+	//Check if source exists and target not exists
+	log.Println("[debug]", backupTask)
+
+	restoreSource := filepath.Join(backupTask.DiskPath, targetFileRelpath)
+	restoreTarget := filepath.Join(backupTask.ParentPath, targetFileRelpath)
+
+	if !fileExists(restoreSource) {
+		//Restore source not exists
+		return errors.New("Restore source file not exists")
+	}
+
+	if fileExists(restoreTarget) {
+		//Restore target already exists.
+		return errors.New("Restore target already exists. Cannot overwrite.")
+	}
+
+	//Check if the restore target parent folder exists. If not, create it
+	if !fileExists(filepath.Dir(restoreTarget)) {
+		os.MkdirAll(filepath.Dir(restoreTarget), 0755)
+	}
 
+	//Ready to move it back
+	err := BufferedLargeFileCopy(restoreSource, restoreTarget, 4086)
+	if err != nil {
+		return errors.New("Restore failed: " + err.Error())
+	}
+
+	//Restore completed
 	return nil
 }
 
@@ -403,6 +446,7 @@ func (m *Manager) ListRestorable(parentDiskID string) (RestorableReport, error)
 	return thisReport, nil
 }
 
+//Get tasks from parent disk id, might return multiple task or no tasks
 func (m *Manager) getTaskByParentDiskID(parentDiskID string) []*BackupTask {
 	//Convert ID:/ format to ID
 	if strings.Contains(parentDiskID, ":") {
@@ -419,6 +463,22 @@ func (m *Manager) getTaskByParentDiskID(parentDiskID string) []*BackupTask {
 	return possibleTask
 }
 
+//Get task by backup Disk ID, only return 1 task
+func (m *Manager) getTaskByBackupDiskID(backupDiskID string) *BackupTask {
+	//Trim the :/ parts
+	if strings.Contains(backupDiskID, ":") {
+		backupDiskID = strings.Split(backupDiskID, ":")[0]
+	}
+
+	for _, task := range m.Tasks {
+		if task.DiskUID == backupDiskID {
+			return task
+		}
+	}
+
+	return nil
+}
+
 //Get and return the file hash for a file
 func getFileHash(filename string) (string, error) {
 	f, err := os.Open(filename)

+ 10 - 0
mod/user/directoryHandler.go

@@ -145,12 +145,22 @@ func (u *User) RealPathToVirtualPath(rpath string) (string, error) {
 			pathContained = true
 			subtractionPath := thisStorageRoot
 			if storage.Hierarchy == "user" {
+				//Check if this file is belongs to this user
+				startOffset := len(filepath.Clean(thisStorageRoot) + "/users/")
+				userNameMatch := realPath[startOffset : startOffset+len(u.Username)]
+				if userNameMatch != u.Username {
+					//This file is not owned by this user
+					return "", errors.New("File not owned by this user")
+				}
+
+				//Generate subtraction path
 				subtractionPath = thisStorageRoot + "/users/" + u.Username + "/"
 			}
 
 			if len(subtractionPath) < len(realPath) {
 				subPath = realPath[len(subtractionPath):]
 			}
+
 		} else if len(realPath) > len(thisStorageRootAbs) && filepath.ToSlash(realPath[:len(thisStorageRootAbs)]) == filepath.ToSlash(thisStorageRootAbs) {
 			//The realpath contains the absolute path of this storage root
 			pathContained = true

+ 61 - 10
web/SystemAO/disk/disk_restore.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html>
 <head>
-    <title>File Properties</title>
+    <title>File Restore</title>
 	<meta name="mobile-web-app-capable" content="yes">
 	<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
 	<meta charset="UTF-8">
@@ -94,6 +94,15 @@
                 </div>
             </h4>
        </div>
+       <div id="succ" class="ui green inverted segment" style="display:none;">
+        <h4 class="ui header">
+            <i class="checkmark icon"></i>
+            <div class="content">
+                File Restored
+                <div class="sub reason header openfolder" style="color: white;"><span style="cursor: pointer" onclick="handleOpenRestoreLocation()"><i class="ui folder icon"></i> Open Restore Location</span></div>
+            </div>
+        </h4>
+       </div>
        <div id="fileinfo" class="ui segment" style="display:none;">
         <h4 class="ui header">
             <div class="content">
@@ -102,6 +111,8 @@
             </div>
         </h4>
         <div class="ui divider"></div>
+        <h5>Restorable File Properties</h5>
+        <span class="backupDisk">N/A</span><br>
         <span class="restorePoint">N/A</span><br>
         <span class="deleteTime">N/A</span><br>
         <span class="deleteRemainingTime">N/A</span><br>
@@ -159,9 +170,10 @@
             shortenRelpath.pop();
             shortenRelpath = shortenRelpath.join("/") + "/";
             $("#fileinfo").find(".relpath").text(shortenRelpath);
-            $("#fileinfo").find(".deleteTime").text("Delete Time: " + timeConverter(filedata.DeleteTime));
-            $("#fileinfo").find(".restorePoint").text("Restore Location: " + (filedata.RestorePoint));
-            $("#fileinfo").find(".deleteRemainingTime").text("Complete Delete in: " + secondsToHms(filedata.RemainingTime));
+            $("#fileinfo").find(".backupDisk").html('<i class="ui blue disk icon"></i> Restorable file from ' + filedata.BackupDiskUID + ":/");
+            $("#fileinfo").find(".deleteTime").html('<i class="ui trash icon"></i> File deleted since ' + timeConverter(filedata.DeleteTime));
+            $("#fileinfo").find(".restorePoint").html('<i class="ui refresh icon"></i> Restore to ' + (filedata.RestorePoint));
+            $("#fileinfo").find(".deleteRemainingTime").html('<i class="ui red remove icon"></i> Auto delete in '+ secondsToHms(filedata.RemainingTime));
 
             $("#fileinfo").slideDown('fast');
         }
@@ -173,8 +185,8 @@
             var m = Math.floor(d % 3600 / 60);
             var s = Math.floor(d % 3600 % 60);
 
-            var hDisplay = h > 0 ? h + (h == 1 ? " hour, " : " hours, ") : "";
-            var mDisplay = m > 0 ? m + (m == 1 ? " minute, " : " minutes") : "";
+            var hDisplay = h > 0 ? h.toString().padStart(2, "0") + (h == 1 ? " hour, " : " hours, ") : "";
+            var mDisplay = m > 0 ? m.toString().padStart(2, "0") + (m == 1 ? " minute, " : " minutes") : "";
             return hDisplay + mDisplay; 
         }
 
@@ -184,9 +196,9 @@
             var year = a.getFullYear();
             var month = months[a.getMonth()];
             var date = a.getDate();
-            var hour = a.getHours();
-            var min = a.getMinutes();
-            var sec = a.getSeconds();
+            var hour = a.getHours().toString().padStart(2, "0");
+            var min = a.getMinutes().toString().padStart(2, "0");
+            var sec = a.getSeconds().toString().padStart(2, "0");
             var time = date + ' ' + month + ' ' + year + ' ' + hour + ':' + min + ':' + sec ;
             return time;
         }
@@ -202,6 +214,45 @@
             
         }
 
+        //Restore this file
+        var restoreFileInfo = undefined;
+        function restoreThisFile(fileObject){
+            var filedata = $(fileObject).attr("filedata");
+            filedata = JSON.parse(decodeURIComponent(filedata));
+            console.log(filedata);
+
+            $.ajax({
+                url: "../../system/backup/restoreFile",
+                method: "POST",
+                data: {"bdid": filedata.BackupDiskUID, "relpath": filedata.RelpathOnDisk},
+                success: function(data){
+                    $("#succ").slideDown('fast').delay(10000).slideUp('fast');
+                    if (data.error !== undefined){
+                        alert("Restore failed: " + data.error);
+                    }else{
+                        console.log(data);
+                        restoreFileInfo = data;
+
+                        if (restoreFileInfo.RestoredVirtualPath == ""){
+                            $("#openfolder").hide();
+                        }else{
+                            $("#openfolder").show();
+                        }
+                    }
+                   
+                }
+            })
+        }
+
+        function handleOpenRestoreLocation(){
+            if (restoreFileInfo.RestoredVirtualPath != undefined && restoreFileInfo.RestoredVirtualPath != ""){
+                var filepath = restoreFileInfo.RestoredVirtualPath;
+                var tmp = filepath.split("/");
+                var filename = tmp.pop();
+                var dirname = tmp.join("/");
+                ao_module_openPath(dirname, filename);
+            }
+        }
 
         function listRestorableFiles(rootID){
             $.ajax({
@@ -255,7 +306,7 @@
                                         <img class="ui fluid image" src="../../img/icons/backup/${timerIcon}"/>
                                     </div>
                                     <div class="ui tiny button" onclick="showFileInfo(this.parentNode.parentNode)">Info</div>
-                                    <div class="ui tiny green  button">Restore</div>
+                                    <div class="ui tiny green button" onclick="restoreThisFile($(this).parent().parent());">Restore</div>
                                 </div>
                                 
                                 <div class="content">