diff --git a/README.md b/README.md index 8964ba3..6061f4a 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,10 @@ See the [developer guide](https://github.com/rwinkhart/libmutton/blob/main/wiki/ - [x] Only run getSSHClient once to prevent being asked for keyfile password multiple times - [ ] After this, handle all errors in sync/client.go - [ ] Ensure all config files and entry files are created with 0600 permissions -- [ ] Add fail-specific error codes +- [x] Add error-specific exit codes - [x] Split into separate repos 1. libmutton: backend package (rename to core), sync package, libmuttonserver - 3. MUTN: cli package + 2. MUTN: cli package #### Release v0.3.0 - [ ] Swap to native (cascade) encryption (custom) - [ ] Implement "netpin" (quick-unlock) with new encryption diff --git a/core/1globals.go b/core/1globals.go index 2b4c0bd..ddc8673 100644 --- a/core/1globals.go +++ b/core/1globals.go @@ -13,3 +13,17 @@ const ( AnsiReset = "\033[0m" LibmuttonVersion = "0.2.B" // untagged releases feature a letter suffix corresponding to the eventual release version, e.g "0.2.A" -> "0.2.0", "0.2.B" -> "0.2.1" ) + +// numeric error codes legend +// 0: no error +// 101: read error +// 102: write error +// 103: sync process error +// 104: server connection error +// 105: target not found +// 106: target already exists +// 107: target wrong type (file/directory) +// 108: decryption error +// 109: encryption error +// 110: clipboard error +// 111: other error diff --git a/core/configParser.go b/core/configParser.go index 8174b4b..68f829c 100644 --- a/core/configParser.go +++ b/core/configParser.go @@ -13,7 +13,7 @@ func loadConfig() *ini.File { cfg, err := ini.Load(ConfigPath) if err != nil { fmt.Println(AnsiError + "Failed to load libmutton.ini: " + err.Error() + AnsiReset) - os.Exit(1) + os.Exit(101) } return cfg } @@ -40,7 +40,7 @@ func ParseConfig(valuesRequested [][2]string, missingValueError string) []string default: fmt.Println(AnsiError + missingValueError + AnsiReset) } - os.Exit(1) + os.Exit(101) } config = append(config, value) @@ -81,6 +81,6 @@ func WriteConfig(valuesToWrite [][3]string, append bool) { err := cfg.SaveTo(ConfigPath) if err != nil { fmt.Println(AnsiError + "Failed to save libmutton.ini: " + err.Error() + AnsiReset) - os.Exit(1) + os.Exit(102) } } diff --git a/core/copy.go b/core/copy.go index 5cd47f1..f4b428c 100644 --- a/core/copy.go +++ b/core/copy.go @@ -24,7 +24,7 @@ func CopyArgument(executableName, targetLocation string, field int) { // ensure field is not empty if decryptedEntry[field] == "" { fmt.Println(AnsiError + "Field is empty" + AnsiReset) - os.Exit(1) + os.Exit(105) } if field != 2 { @@ -40,7 +40,7 @@ func CopyArgument(executableName, targetLocation string, field int) { secret = decryptedEntry[2] } - fmt.Println("TOTP code has been copied to the clipboard - your clipboard will be kept up to date with the current code until this process is closed") + fmt.Println("TOTP code has been copied to the clipboard - Your clipboard will be kept up to date with the current code until this process is closed") for { // keep field copied to clipboard, refresh on 30-second intervals currentTime := time.Now() @@ -51,7 +51,7 @@ func CopyArgument(executableName, targetLocation string, field int) { } } else { fmt.Println(AnsiError + "Field does not exist in entry" + AnsiReset) - os.Exit(1) + os.Exit(105) } // copy field to clipboard, launch clipboard clearing process @@ -84,7 +84,7 @@ func GenTOTP(secret string, time time.Time, forSteam bool) string { if err != nil { fmt.Println(AnsiError + "Error generating TOTP code" + AnsiReset) - os.Exit(1) + os.Exit(111) } return totpToken diff --git a/core/copyDARWIN.go b/core/copyDARWIN.go index e0fa150..10962d4 100644 --- a/core/copyDARWIN.go +++ b/core/copyDARWIN.go @@ -17,7 +17,7 @@ func copyField(executableName, copySubject string) { err := cmd.Run() if err != nil { fmt.Println(AnsiError + "Failed to copy to clipboard: " + err.Error() + AnsiReset) - os.Exit(1) + os.Exit(110) } // launch clipboard clearing process if executableName is provided @@ -26,8 +26,8 @@ func copyField(executableName, copySubject string) { writeToStdin(cmd, copySubject) err = cmd.Start() if err != nil { - fmt.Println(AnsiError + "Failed to launch automated clipboard clearing process - does this libmutton implementation support the \"clipclear\" argument?" + AnsiReset) - os.Exit(1) + fmt.Println(AnsiError + "Failed to launch automated clipboard clearing process - Does this libmutton implementation support the \"clipclear\" argument?" + AnsiReset) + os.Exit(110) } Exit(0) // only exit if clipboard clearing process is launched, otherwise assume continuous clipboard refresh } @@ -41,7 +41,7 @@ func clipClear(oldContents string) { newContents, err := cmd.Output() if err != nil { fmt.Println(AnsiError + "Failed to read clipboard contents: " + err.Error() + AnsiReset) - os.Exit(1) + os.Exit(110) } if oldContents == strings.TrimRight(string(newContents), "\r\n") { @@ -50,7 +50,7 @@ func clipClear(oldContents string) { err = cmd.Run() if err != nil { fmt.Println(AnsiError + "Failed to clear clipboard: " + err.Error() + AnsiReset) - os.Exit(1) + os.Exit(110) } } os.Exit(0) // use os.Exit instead of core.Exit, as this function runs out of a background subprocess that is invisible to the user (will never appear in GUI/TUI environment) diff --git a/core/copyTERMUX.go b/core/copyTERMUX.go index 550851f..d7e5f86 100644 --- a/core/copyTERMUX.go +++ b/core/copyTERMUX.go @@ -17,7 +17,7 @@ func copyField(executableName, copySubject string) { err := cmd.Run() if err != nil { fmt.Println(AnsiError + "Failed to copy to clipboard: " + err.Error() + AnsiReset) - os.Exit(1) + os.Exit(110) } // launch clipboard clearing process if executableName is provided @@ -26,8 +26,8 @@ func copyField(executableName, copySubject string) { writeToStdin(cmd, copySubject) err = cmd.Start() if err != nil { - fmt.Println(AnsiError + "Failed to launch automated clipboard clearing process - does this libmutton implementation support the \"clipclear\" argument?" + AnsiReset) - os.Exit(1) + fmt.Println(AnsiError + "Failed to launch automated clipboard clearing process - Does this libmutton implementation support the \"clipclear\" argument?" + AnsiReset) + os.Exit(110) } Exit(0) // only exit if clipboard clearing process is launched, otherwise assume continuous clipboard refresh } @@ -41,7 +41,7 @@ func clipClear(oldContents string) { newContents, err := cmd.Output() if err != nil { fmt.Println(AnsiError + "Failed to read clipboard contents: " + err.Error() + AnsiReset) - os.Exit(1) + os.Exit(110) } if oldContents == strings.TrimRight(string(newContents), "\r\n") { @@ -50,7 +50,7 @@ func clipClear(oldContents string) { err = cmd.Run() if err != nil { fmt.Println(AnsiError + "Failed to clear clipboard: " + err.Error() + AnsiReset) - os.Exit(1) + os.Exit(110) } } os.Exit(0) // use os.Exit instead of core.Exit, as this function runs out of a background subprocess that is invisible to the user (will never appear in GUI/TUI environment) diff --git a/core/copyUNIXGeneric.go b/core/copyUNIXGeneric.go index c6c6a82..4dade11 100644 --- a/core/copyUNIXGeneric.go +++ b/core/copyUNIXGeneric.go @@ -20,15 +20,15 @@ func copyField(executableName, copySubject string) { } else if _, envSet = os.LookupEnv("DISPLAY"); envSet { cmd = exec.Command("xclip", "-sel", "c") } else { - fmt.Println(AnsiError + "Clipboard platform could not be determined - note that the clipboard does not function in a raw TTY" + AnsiReset) - os.Exit(1) + fmt.Println(AnsiError + "Clipboard platform could not be determined - Note that the clipboard does not function in a raw TTY" + AnsiReset) + os.Exit(110) } writeToStdin(cmd, copySubject) err := cmd.Run() if err != nil { fmt.Println(AnsiError + "Failed to copy to clipboard: " + err.Error() + AnsiReset) - os.Exit(1) + os.Exit(110) } // launch clipboard clearing process if executableName is provided @@ -37,8 +37,8 @@ func copyField(executableName, copySubject string) { writeToStdin(cmd, copySubject) err = cmd.Start() if err != nil { - fmt.Println(AnsiError + "Failed to launch automated clipboard clearing process - does this libmutton implementation support the \"clipclear\" argument?" + AnsiReset) - os.Exit(1) + fmt.Println(AnsiError + "Failed to launch automated clipboard clearing process - Does this libmutton implementation support the \"clipclear\" argument?" + AnsiReset) + os.Exit(110) } Exit(0) // only exit if clipboard clearing process is launched, otherwise assume continuous clipboard refresh } @@ -58,15 +58,15 @@ func clipClear(oldContents string) { cmdClear = exec.Command("xclip", "-i", "/dev/null", "-sel", "c") cmdPaste = exec.Command("xclip", "-o", "-sel", "c") } else { - fmt.Println(AnsiError + "Clipboard platform could not be determined - neither $WAYLAND_DISPLAY nor $DISPLAY are set" + AnsiReset) - os.Exit(1) + fmt.Println(AnsiError + "Clipboard platform could not be determined - Neither $WAYLAND_DISPLAY nor $DISPLAY are set" + AnsiReset) + os.Exit(110) } // read current clipboard contents newContents, err := cmdPaste.Output() if err != nil { fmt.Println(AnsiError + "Failed to read clipboard contents: " + err.Error() + AnsiReset) - os.Exit(1) + os.Exit(110) } // clear clipboard if contents have not been modified @@ -74,7 +74,7 @@ func clipClear(oldContents string) { err = cmdClear.Run() if err != nil { fmt.Println(AnsiError + "Failed to clear clipboard: " + err.Error() + AnsiReset) - os.Exit(1) + os.Exit(110) } } os.Exit(0) // use os.Exit instead of core.Exit, as this function runs out of a background subprocess that is invisible to the user (will never appear in GUI/TUI environment) diff --git a/core/copyWIN.go b/core/copyWIN.go index 3671b90..fc78ae8 100644 --- a/core/copyWIN.go +++ b/core/copyWIN.go @@ -16,7 +16,7 @@ func copyField(executableName, copySubject string) { err := cmd.Run() if err != nil { fmt.Println(AnsiError + "Failed to copy to clipboard: " + err.Error() + AnsiReset) - os.Exit(1) + os.Exit(110) } // launch clipboard clearing process if executableName is provided @@ -25,8 +25,8 @@ func copyField(executableName, copySubject string) { writeToStdin(cmd, copySubject) err = cmd.Start() if err != nil { - fmt.Println(AnsiError + "Failed to launch automated clipboard clearing process - does this libmutton implementation support the \"clipclear\" argument?" + AnsiReset) - os.Exit(1) + fmt.Println(AnsiError + "Failed to launch automated clipboard clearing process - Does this libmutton implementation support the \"clipclear\" argument?" + AnsiReset) + os.Exit(110) } Exit(0) // only exit if clipboard clearing process is launched, otherwise assume continuous clipboard refresh } @@ -40,7 +40,7 @@ func clipClear(oldContents string) { newContents, err := cmd.Output() if err != nil { fmt.Println(AnsiError + "Failed to read clipboard contents: " + err.Error() + AnsiReset) - os.Exit(1) + os.Exit(110) } if oldContents == strings.TrimRight(string(newContents), "\r\n") { @@ -48,7 +48,7 @@ func clipClear(oldContents string) { err = cmd.Run() if err != nil { fmt.Println(AnsiError + "Failed to clear clipboard: " + err.Error() + AnsiReset) - os.Exit(1) + os.Exit(110) } } os.Exit(0) // use os.Exit instead of core.Exit, as this function runs out of a background subprocess that is invisible to the user (will never appear in GUI/TUI environment) diff --git a/core/gpg.go b/core/gpg.go index d868f5d..44a8b72 100644 --- a/core/gpg.go +++ b/core/gpg.go @@ -7,7 +7,7 @@ import ( "strings" ) -// TODO GPG support is a temporary feature - it will be replaced with a different encryption scheme in the future +// TODO GPG support is a temporary feature - It will be replaced with a different encryption scheme in the future // DecryptGPG decrypts a GPG-encrypted file and returns the contents as a slice of (trimmed) strings. func DecryptGPG(targetLocation string) []string { @@ -18,8 +18,8 @@ func DecryptGPG(targetLocation string) []string { enableVirtualTerminalProcessing() if err != nil { - fmt.Println(AnsiError + "Failed to decrypt \"" + targetLocation + "\" - ensure it is a valid GPG-encrypted file and that you entered your passphrase correctly" + AnsiReset) - os.Exit(1) + fmt.Println(AnsiError + "Failed to decrypt \"" + targetLocation + "\" - Ensure it is a valid GPG-encrypted file and that you entered your passphrase correctly" + AnsiReset) + os.Exit(108) } outputSlice := strings.Split(string(output), "\n") @@ -32,8 +32,8 @@ func EncryptGPG(input []string) []byte { writeToStdin(cmd, strings.Join(input, "\n")) encryptedBytes, err := cmd.Output() if err != nil { - fmt.Println(AnsiError + "Failed to encrypt data - ensure that your GPG key is valid and that you have a valid GPG ID set in libmutton.ini" + AnsiReset) - os.Exit(1) + fmt.Println(AnsiError + "Failed to encrypt data - Ensure that your GPG key is valid and that you have a valid GPG ID set in libmutton.ini" + AnsiReset) + os.Exit(109) } return encryptedBytes } diff --git a/core/init.go b/core/init.go index 2d26e3b..02a1343 100644 --- a/core/init.go +++ b/core/init.go @@ -46,7 +46,7 @@ func GpgKeyGen() string { err := cmd.Run() if err != nil { fmt.Println(AnsiError + "Failed to generate GPG key: " + err.Error() + AnsiReset) - os.Exit(1) + os.Exit(111) } return "libmutton-" + unixTime + " (gpg-libmutton) " @@ -58,7 +58,7 @@ func DirInit(preserveOldConfigDir bool) { err := os.MkdirAll(EntryRoot, 0700) if err != nil { fmt.Println(AnsiError + "Failed to create \"" + EntryRoot + "\":" + err.Error() + AnsiReset) - os.Exit(1) + os.Exit(102) } // remove existing config directory (if it exists and not in append mode) @@ -68,7 +68,7 @@ func DirInit(preserveOldConfigDir bool) { err = os.RemoveAll(ConfigDir) if err != nil { fmt.Println(AnsiError + "Failed to remove existing config directory: " + err.Error() + AnsiReset) - os.Exit(1) + os.Exit(102) } } } @@ -77,6 +77,6 @@ func DirInit(preserveOldConfigDir bool) { err = os.MkdirAll(ConfigDir+PathSeparator+"devices", 0700) if err != nil { fmt.Println(AnsiError + "Failed to create \"" + ConfigDir + "\":" + err.Error() + AnsiReset) - os.Exit(1) + os.Exit(102) } } diff --git a/core/utilitiesMisc.go b/core/utilitiesMisc.go index 308bff1..573e7c1 100644 --- a/core/utilitiesMisc.go +++ b/core/utilitiesMisc.go @@ -18,8 +18,8 @@ func TargetIsFile(targetLocation string, errorOnFail bool, failCondition uint8) targetInfo, err := os.Stat(targetLocation) if err != nil { if errorOnFail { - fmt.Println(AnsiError + "Failed to access \"" + targetLocation + "\" - ensure it exists and has the correct permissions" + AnsiReset) - os.Exit(1) + fmt.Println(AnsiError + "Failed to access \"" + targetLocation + "\" - Ensure it exists and has the correct permissions" + AnsiReset) + os.Exit(105) } else { return false, false } @@ -27,13 +27,13 @@ func TargetIsFile(targetLocation string, errorOnFail bool, failCondition uint8) if targetInfo.IsDir() { if errorOnFail && failCondition == 2 { fmt.Println(AnsiError + "\"" + targetLocation + "\" is a directory" + AnsiReset) - os.Exit(1) + os.Exit(107) } return false, true } else { if errorOnFail && failCondition == 1 { fmt.Println(AnsiError + "\"" + targetLocation + "\" is a file" + AnsiReset) - os.Exit(1) + os.Exit(107) } return true, true } @@ -45,7 +45,7 @@ func WriteEntry(targetLocation string, entryData []string, verifyEntryDoesNotExi _, isAccessible := TargetIsFile(targetLocation, false, 0) if isAccessible { fmt.Println(AnsiError + "Target location already exists" + AnsiReset) - os.Exit(1) + os.Exit(106) } } @@ -53,7 +53,7 @@ func WriteEntry(targetLocation string, entryData []string, verifyEntryDoesNotExi err := os.WriteFile(targetLocation, encryptedBytes, 0600) if err != nil { fmt.Println(AnsiError + "Failed to write to file: " + err.Error() + AnsiReset) - os.Exit(1) + os.Exit(102) } } @@ -62,7 +62,7 @@ func writeToStdin(cmd *exec.Cmd, input string) { stdin, err := cmd.StdinPipe() if err != nil { fmt.Println(AnsiError + "Failed to access stdin for system command: " + err.Error() + AnsiReset) - os.Exit(1) + os.Exit(111) } go func() { @@ -78,7 +78,7 @@ func CreateTempFile() *os.File { tempFile, err := os.CreateTemp("", "*.markdown") if err != nil { fmt.Println(AnsiError + "Failed to create temporary file: " + err.Error() + AnsiReset) - os.Exit(1) + os.Exit(102) } return tempFile } diff --git a/go.mod b/go.mod index e6b03fb..7783704 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/pkg/sftp v1.13.6 github.com/pquerna/otp v1.4.1-0.20231130234153-3357de7c0481 golang.org/x/crypto v0.26.0 + golang.org/x/term v0.23.0 gopkg.in/ini.v1 v1.67.0 ) @@ -14,5 +15,4 @@ require ( github.com/boombuler/barcode v1.0.2 // indirect github.com/kr/fs v0.1.0 // indirect golang.org/x/sys v0.23.0 // indirect - golang.org/x/term v0.23.0 // indirect ) diff --git a/libmuttonserver.go b/libmuttonserver.go index 3a28e7a..7ac3672 100644 --- a/libmuttonserver.go +++ b/libmuttonserver.go @@ -40,18 +40,18 @@ func main() { case "rename": // move an entry to a new location before using fallthrough to add its previous iteration to the deletions directory // stdin[0] is evaluated after fallthrough - // stdin[1] is expected to be the OLD incomplete target location with FSPath representing path separators - always pass in UNIX format - // stdin[2] is expected to be the NEW incomplete target location with FSPath representing path separators - always pass in UNIX format + // stdin[1] is expected to be the OLD incomplete target location with FSPath representing path separators - Always pass in UNIX format + // stdin[2] is expected to be the NEW incomplete target location with FSPath representing path separators - Always pass in UNIX format sync.RenameLocal(strings.ReplaceAll(stdin[1], sync.FSPath, "/"), strings.ReplaceAll(stdin[2], sync.FSPath, "/"), true) fallthrough // fallthrough to add the old entry to the deletions directory case "shear": // shear an entry from the server and add it to the deletions directory // stdin[0] is expected to be the device ID - // stdin[1] is expected to be the incomplete target location with FSPath representing path separators - always pass in UNIX format + // stdin[1] is expected to be the incomplete target location with FSPath representing path separators - Always pass in UNIX format sync.ShearLocal(strings.ReplaceAll(stdin[1], sync.FSPath, "/"), stdin[0]) case "addfolder": // add a new folder to the server - // stdin[0] is expected to be the incomplete target location with FSPath representing path separators - always pass in UNIX format + // stdin[0] is expected to be the incomplete target location with FSPath representing path separators - Always pass in UNIX format sync.AddFolderLocal(strings.ReplaceAll(stdin[0], sync.FSPath, "/")) case "register": // register a new device ID diff --git a/sync/client.go b/sync/client.go index 96df1e7..6e275cb 100644 --- a/sync/client.go +++ b/sync/client.go @@ -27,7 +27,7 @@ func GetSSHClient(manualSync bool) (*ssh.Client, string, bool) { var sshUserConfig []string var missingValueError string if manualSync { - missingValueError = joinErrorWithEXE("SSH settings not configured - run \"", " init\" to configure") + missingValueError = joinErrorWithEXE("SSH settings not configured - Run \"", " init\" to configure") } else { missingValueError = "0" } @@ -57,8 +57,8 @@ func GetSSHClient(manualSync bool) (*ssh.Client, string, bool) { // read private key key, err := os.ReadFile(keyFile) if err != nil { - fmt.Println(core.AnsiError+"Sync failed - unable to read private key file:", keyFile+core.AnsiReset) - os.Exit(1) + fmt.Println(core.AnsiError+"Sync failed - Unable to read private key file:", keyFile+core.AnsiReset) + os.Exit(101) } // parse private key @@ -70,14 +70,14 @@ func GetSSHClient(manualSync bool) (*ssh.Client, string, bool) { } if err != nil { fmt.Println(core.AnsiError+"Sync failed - Unable to parse private key:", keyFile+core.AnsiReset) - os.Exit(1) + os.Exit(101) } // read known hosts file hostKeyCallback, err := knownhosts.New(core.Home + core.PathSeparator + ".ssh" + core.PathSeparator + "known_hosts") if err != nil { fmt.Println(core.AnsiError + "Sync failed - Unable to read known hosts file:" + err.Error() + core.AnsiReset) - os.Exit(1) + os.Exit(101) } // configure SSH client @@ -93,7 +93,7 @@ func GetSSHClient(manualSync bool) (*ssh.Client, string, bool) { sshClient, err := ssh.Dial("tcp", ip+":"+port, sshConfig) if err != nil { fmt.Println(core.AnsiError+"Sync failed - Unable to connect to remote server:", err.Error()+core.AnsiReset) - os.Exit(1) + os.Exit(104) } return sshClient, entryRoot, isWindows @@ -105,7 +105,7 @@ func GetSSHOutput(sshClient *ssh.Client, cmd, stdin string) string { sshSession, err := sshClient.NewSession() if err != nil { fmt.Println(core.AnsiError+"Sync failed - Unable to establish SSH session:", err.Error()+core.AnsiReset) - os.Exit(1) + os.Exit(104) } // provide stdin data for session @@ -116,7 +116,7 @@ func GetSSHOutput(sshClient *ssh.Client, cmd, stdin string) string { output, err = sshSession.CombinedOutput(cmd) if err != nil { fmt.Println(core.AnsiError+"Sync failed - Unable to run SSH command:", err.Error()+core.AnsiReset) - os.Exit(1) + os.Exit(103) } // convert the output to a string and remove leading/trailing whitespace @@ -133,7 +133,7 @@ func getRemoteDataFromClient(sshClient *ssh.Client, manualSync bool) (map[string if len(clientDeviceID) == 0 { if manualSync { fmt.Println(joinErrorWithEXE("Sync failed - No device ID found; run \"", " init\" to generate a device ID")) - os.Exit(1) + os.Exit(105) } else { core.Exit(0) // exit silently if the sync job was called automatically, as the user may just be in offline mode } @@ -147,7 +147,7 @@ func getRemoteDataFromClient(sshClient *ssh.Client, manualSync bool) (map[string // parse output/re-form lists if len(outputSlice) != 5 { // ensure information from server is complete fmt.Println(core.AnsiError + "Sync failed - Unable to fetch remote data; server returned an unexpected response" + core.AnsiReset) - os.Exit(1) + os.Exit(103) } serverTime, _ := strconv.ParseInt(outputSlice[0], 10, 64) entries := strings.Split(outputSlice[1], FSMisc)[1:] @@ -204,7 +204,7 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow sftpClient, err := sftp.NewClient(sshClient) if err != nil { fmt.Println(core.AnsiError+"Sync failed - Unable to establish SFTP session:", err.Error()+core.AnsiReset) - os.Exit(1) + os.Exit(104) } defer sftpClient.Close() @@ -223,7 +223,7 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow fileInfo, err = sftpClient.Stat(remoteEntryFullPath) if err != nil { fmt.Println(core.AnsiError+"Sync failed - Unable to get remote file info (mod time):", err.Error()+core.AnsiReset) - os.Exit(1) + os.Exit(101) } modTime := fileInfo.ModTime() @@ -232,7 +232,7 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow remoteFile, err = sftpClient.Open(remoteEntryFullPath) if err != nil { fmt.Println(core.AnsiError+"Sync failed - Unable to open remote file:", err.Error()+core.AnsiReset) - os.Exit(1) + os.Exit(101) } // store path to local entry @@ -243,14 +243,14 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow localFile, err = os.Create(localEntryFullPath) if err != nil { fmt.Println(core.AnsiError+"Sync failed - Unable to create local file:", err.Error()+core.AnsiReset) - os.Exit(1) + os.Exit(102) } // download the file _, err = remoteFile.WriteTo(localFile) if err != nil { fmt.Println(core.AnsiError+"Sync failed - Unable to download remote file:", err.Error()+core.AnsiReset) - os.Exit(1) + os.Exit(103) } // close the files @@ -280,7 +280,7 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow fileInfo, err = os.Stat(localEntryFullPath) if err != nil { fmt.Println(core.AnsiError+"Sync failed - Unable to get local file info (mod time):", err.Error()+core.AnsiReset) - os.Exit(1) + os.Exit(101) } modTime := fileInfo.ModTime() @@ -289,7 +289,7 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow localFile, err = os.Open(localEntryFullPath) if err != nil { fmt.Println(core.AnsiError+"Sync failed - Unable to open local file:", err.Error()+core.AnsiReset) - os.Exit(1) + os.Exit(101) } // store path to remote entry @@ -300,14 +300,14 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow remoteFile, err = sftpClient.Create(remoteEntryFullPath) if err != nil { fmt.Println(core.AnsiError+"Sync failed - Unable to create remote file ("+remoteEntryFullPath+"):", err.Error()+core.AnsiReset) - os.Exit(1) + os.Exit(102) } // upload the file _, err = localFile.WriteTo(remoteFile) if err != nil { fmt.Println(core.AnsiError+"Sync failed - Unable to upload local file:", err.Error()+core.AnsiReset) - os.Exit(1) + os.Exit(103) } // close the files @@ -432,8 +432,8 @@ func folderSync(folders []string) { if !isFile && !isAccessible { os.MkdirAll(folderFullPath, 0700) } else if isFile { - fmt.Println(core.AnsiError + "Sync failed - Failed to create folder \"" + folder + "\" - a file with the same name already exists" + core.AnsiReset) - os.Exit(1) + fmt.Println(core.AnsiError + "Sync failed - Failed to create folder \"" + folder + "\" - A file with the same name already exists" + core.AnsiReset) + os.Exit(106) } } } diff --git a/sync/common.go b/sync/common.go index 6b5f779..70264cd 100644 --- a/sync/common.go +++ b/sync/common.go @@ -26,7 +26,7 @@ func genDeviceIDList() *[]fs.DirEntry { deviceIDList, err := os.ReadDir(core.ConfigDir + core.PathSeparator + "devices") if err != nil { fmt.Println(core.AnsiError + "Failed to read the devices directory: " + err.Error() + core.AnsiReset) - os.Exit(1) + os.Exit(101) } return &deviceIDList } @@ -52,7 +52,7 @@ func ShearLocal(targetLocationIncomplete, clientDeviceID string) string { if err != nil { // do not print error as there is currently no way of seeing server-side errors // failure to add the target to the deletions list will exit the program and result in a client re-uploading the target (non-critical) - os.Exit(1) + os.Exit(102) } } } @@ -66,7 +66,7 @@ func ShearLocal(targetLocationIncomplete, clientDeviceID string) string { err := os.RemoveAll(targetLocationComplete) if err != nil { fmt.Println(core.AnsiError + "Failed to remove local target: " + err.Error() + core.AnsiReset) - os.Exit(1) + os.Exit(102) } if !onServer && len(*deviceIDList) > 0 { // return the device ID if running on the client and a device ID exists (online mode) @@ -92,13 +92,13 @@ func RenameLocal(oldLocationIncomplete, newLocationIncomplete string, verifyOldL _, isAccessible := core.TargetIsFile(newLocation, false, 0) if isAccessible { fmt.Println(core.AnsiError + "\"" + newLocation + "\" already exists" + core.AnsiReset) - os.Exit(1) + os.Exit(106) } // rename oldLocation to newLocation err := os.Rename(oldLocation, newLocation) if err != nil { - fmt.Println(core.AnsiError + "Failed to rename - does the target containing directory exist?" + core.AnsiReset) + fmt.Println(core.AnsiError + "Failed to rename - Does the target containing directory exist?" + core.AnsiReset) } // do not exit program, as this function is used as part of RenameRemoteFromClient @@ -113,10 +113,10 @@ func AddFolderLocal(targetLocationIncomplete string) { if err != nil { if os.IsExist(err) { fmt.Println(core.AnsiError + "Directory already exists" + core.AnsiReset) - os.Exit(1) + os.Exit(106) } else { fmt.Println(core.AnsiError + "Failed to create directory: " + err.Error() + core.AnsiReset) - os.Exit(1) + os.Exit(102) } } diff --git a/sync/commonUNIX.go b/sync/commonUNIX.go index 1071f7f..0d55ce8 100644 --- a/sync/commonUNIX.go +++ b/sync/commonUNIX.go @@ -25,12 +25,12 @@ func WalkEntryDir() ([]string, []string) { // check for errors encountered while walking directory if err != nil { if os.IsNotExist(err) { - fmt.Println(core.AnsiError+"The entry directory does not exist - run \""+os.Args[0], "init"+"\" to create it"+core.AnsiReset) + fmt.Println(core.AnsiError+"The entry directory does not exist - Run \""+os.Args[0], "init"+"\" to create it"+core.AnsiReset) } else { // otherwise, print the source of the error fmt.Println(core.AnsiError + "An unexpected error occurred while generating the entry list: " + err.Error() + core.AnsiReset) } - os.Exit(1) + os.Exit(111) } // trim root path from each path before storing diff --git a/sync/commonWIN.go b/sync/commonWIN.go index 9ea5e1a..b1a1eaf 100644 --- a/sync/commonWIN.go +++ b/sync/commonWIN.go @@ -26,12 +26,12 @@ func WalkEntryDir() ([]string, []string) { // check for errors encountered while walking directory if err != nil { if os.IsNotExist(err) { - fmt.Println(joinErrorWithEXE("The entry directory does not exist - run \"", " init"+"\" to create it")) + fmt.Println(joinErrorWithEXE("The entry directory does not exist - Run \"", " init"+"\" to create it")) } else { // otherwise, print the source of the error fmt.Println(core.AnsiError + "An unexpected error occurred while generating the entry list: " + err.Error() + core.AnsiReset) } - os.Exit(1) + os.Exit(111) } // trim root path from each path before storing and replace backslashes with forward slashes diff --git a/sync/server.go b/sync/server.go index 443be10..a3e2264 100644 --- a/sync/server.go +++ b/sync/server.go @@ -18,7 +18,7 @@ func GetRemoteDataFromServer(clientDeviceID string) { deletionsList, err := os.ReadDir(core.ConfigDir + core.PathSeparator + "deletions") if err != nil { fmt.Println(core.AnsiError + "Failed to read the deletions directory: " + err.Error() + core.AnsiReset) - os.Exit(1) + os.Exit(101) } // print the current UNIX timestamp to stdout