fix: add symlink handling in createFirejailedBinary #108
| @@ -28,7 +28,6 @@ import ( | ||||
| 	"github.com/goreleaser/nfpm/v2/files" | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||
| ) | ||||
| @@ -51,6 +50,92 @@ var binaryDirectories = []string{ | ||||
| 	"/usr/local/bin/", | ||||
| } | ||||
|  | ||||
| func moveWithSymlinkHandling(src, dst string) error { | ||||
| 	srcInfo, err := os.Lstat(src) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to get source info: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil { | ||||
| 		return fmt.Errorf("failed to create destination directory: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	if srcInfo.Mode()&os.ModeSymlink != 0 { | ||||
| 		return moveSymlink(src, dst) | ||||
| 	} | ||||
|  | ||||
| 	if err := os.Rename(src, dst); err != nil { | ||||
| 		return copyAndRemove(src, dst) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func moveSymlink(src, dst string) error { | ||||
| 	target, err := os.Readlink(src) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to read symlink: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	if err := os.Symlink(target, dst); err != nil { | ||||
| 		return fmt.Errorf("failed to create symlink: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	if err := os.Remove(src); err != nil { | ||||
| 		os.Remove(dst) | ||||
| 		return fmt.Errorf("failed to remove original symlink: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func copyAndRemove(src, dst string) error { | ||||
| 	srcFile, err := os.Open(src) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to open source: %w", err) | ||||
| 	} | ||||
| 	defer srcFile.Close() | ||||
|  | ||||
| 	dstFile, err := os.Create(dst) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to create destination: %w", err) | ||||
| 	} | ||||
| 	defer dstFile.Close() | ||||
|  | ||||
| 	if _, err := io.Copy(dstFile, srcFile); err != nil { | ||||
| 		return fmt.Errorf("failed to copy content: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	srcInfo, err := srcFile.Stat() | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to get source stats: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	if err := dstFile.Chmod(srcInfo.Mode()); err != nil { | ||||
| 		return fmt.Errorf("failed to set permissions: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	if err := os.Remove(src); err != nil { | ||||
| 		return fmt.Errorf("failed to remove source: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func moveFileWithErrorHandling(src, dst string) error { | ||||
| 	err := moveWithSymlinkHandling(src, dst) | ||||
| 	if err != nil { | ||||
| 		if os.IsPermission(err) { | ||||
| 			return fmt.Errorf("permission denied: %w", err) | ||||
| 		} | ||||
| 		if os.IsNotExist(err) { | ||||
| 			return fmt.Errorf("source file does not exist: %w", err) | ||||
| 		} | ||||
| 		return fmt.Errorf("failed to move file: %w", err) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func applyFirejailIntegration( | ||||
| 	vars *alrsh.Package, | ||||
| 	dirs types.Directories, | ||||
| @@ -143,12 +228,15 @@ func createFirejailedBinary( | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if err := osutils.Move(content.Source, filepath.Join(dirs.PkgDir, origFilePath)); err != nil { | ||||
| 	if err := moveFileWithErrorHandling(filepath.Join(dirs.PkgDir, content.Destination), filepath.Join(dirs.PkgDir, origFilePath)); err != nil { | ||||
| 		return nil, fmt.Errorf("failed to move original binary: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	content.Type = "file" | ||||
| 	content.Source = filepath.Join(dirs.PkgDir, content.Destination) | ||||
|  | ||||
| 	// Create wrapper script | ||||
| 	if err := createWrapperScript(content.Source, origFilePath, dest); err != nil { | ||||
| 	if err := createWrapperScript(filepath.Join(dirs.PkgDir, content.Destination), origFilePath, dest); err != nil { | ||||
| 		return nil, fmt.Errorf("failed to create wrapper script: %w", err) | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -138,7 +138,10 @@ func TestCreateFirejailedBinary(t *testing.T) { | ||||
| 				os.MkdirAll(pkgDir, 0o755) | ||||
| 				os.MkdirAll(scriptDir, 0o755) | ||||
|  | ||||
| 				srcBinary := filepath.Join(tmpDir, "test-binary") | ||||
| 				binDir := filepath.Join(pkgDir, "usr", "bin") | ||||
| 				os.MkdirAll(binDir, 0o755) | ||||
|  | ||||
| 				srcBinary := filepath.Join(binDir, "test-binary") | ||||
| 				os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755) | ||||
|  | ||||
| 				defaultProfile := filepath.Join(scriptDir, "default.profile") | ||||
| @@ -154,7 +157,7 @@ func TestCreateFirejailedBinary(t *testing.T) { | ||||
|  | ||||
| 				content := &files.Content{ | ||||
| 					Source:      srcBinary, | ||||
| 					Destination: "./usr/bin/test-binary", | ||||
| 					Destination: "/usr/bin/test-binary", | ||||
| 					Type:        "file", | ||||
| 				} | ||||
|  | ||||
| @@ -172,7 +175,10 @@ func TestCreateFirejailedBinary(t *testing.T) { | ||||
| 				os.MkdirAll(pkgDir, 0o755) | ||||
| 				os.MkdirAll(scriptDir, 0o755) | ||||
|  | ||||
| 				srcBinary := filepath.Join(tmpDir, "special-binary") | ||||
| 				binDir := filepath.Join(pkgDir, "usr", "bin") | ||||
| 				os.MkdirAll(binDir, 0o755) | ||||
|  | ||||
| 				srcBinary := filepath.Join(binDir, "special-binary") | ||||
| 				os.WriteFile(srcBinary, []byte("#!/bin/bash\necho special"), 0o755) | ||||
|  | ||||
| 				defaultProfile := filepath.Join(scriptDir, "default.profile") | ||||
| @@ -191,7 +197,7 @@ func TestCreateFirejailedBinary(t *testing.T) { | ||||
|  | ||||
| 				content := &files.Content{ | ||||
| 					Source:      srcBinary, | ||||
| 					Destination: "./usr/bin/special-binary", | ||||
| 					Destination: "/usr/bin/special-binary", | ||||
| 					Type:        "file", | ||||
| 				} | ||||
|  | ||||
|   | ||||
| @@ -220,7 +220,7 @@ msgstr "" | ||||
| msgid "AutoReq is not implemented for this package format, so it's skipped" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/build/firejail.go:59 | ||||
| #: internal/build/firejail.go:144 | ||||
| msgid "Applying FireJail integration" | ||||
| msgstr "" | ||||
|  | ||||
|   | ||||
| @@ -12,8 +12,8 @@ msgstr "" | ||||
| "MIME-Version: 1.0\n" | ||||
| "Content-Type: text/plain; charset=UTF-8\n" | ||||
| "Content-Transfer-Encoding: 8bit\n" | ||||
| "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " | ||||
| "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" | ||||
| "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" | ||||
| "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" | ||||
| "X-Generator: Gtranslator 48.0\n" | ||||
|  | ||||
| #: build.go:42 | ||||
| @@ -231,7 +231,7 @@ msgid "AutoReq is not implemented for this package format, so it's skipped" | ||||
| msgstr "" | ||||
| "AutoReq не реализовано для этого формата пакета, поэтому будет пропущено" | ||||
|  | ||||
| #: internal/build/firejail.go:59 | ||||
| #: internal/build/firejail.go:144 | ||||
| msgid "Applying FireJail integration" | ||||
| msgstr "Применение интеграции FireJail" | ||||
|  | ||||
| @@ -356,8 +356,8 @@ msgid "" | ||||
| "This command is deprecated and would be removed in the future, use \"%s\" " | ||||
| "instead!" | ||||
| msgstr "" | ||||
| "Эта команда устарела и будет удалена в будущем, используйте вместо нее " | ||||
| "\"%s\"!" | ||||
| "Эта команда устарела и будет удалена в будущем, используйте вместо нее \"%s" | ||||
| "\"!" | ||||
|  | ||||
| #: internal/db/db.go:76 | ||||
| msgid "Database version mismatch; resetting" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user