diff --git a/src/fsys/dir.go b/src/fsys/dir.go
index 6140aba..3e77ffd 100644
--- a/src/fsys/dir.go
+++ b/src/fsys/dir.go
@@ -33,6 +33,7 @@ type Directory struct {
Path string
Size uint64
RelativeParentPath string // Relative path to the directory, where the highest point in the hierarchy is the upmost parent dir. Set manually
+ Symlinks []*Symlink
Files []*File
Directories []*Directory
}
@@ -70,6 +71,7 @@ func GetDir(path string, recursive bool) (*Directory, error) {
var innerDirs []*Directory
var innerFiles []*File
+ var innerSymlinks []*Symlink
for _, entry := range entries {
entryInfo, err := entry.Info()
if err != nil {
@@ -93,22 +95,42 @@ func GetDir(path string, recursive bool) (*Directory, error) {
// if not - skip the directory and only work with the files
} else {
- innerFilePath := filepath.Join(absPath, entryInfo.Name())
+ // not a directory
- innerFile, err := GetFile(innerFilePath)
- if err != nil {
- // skip this file
- continue
- }
+ switch entryInfo.Mode()&os.ModeSymlink != 0 {
+ case true:
+ // it is a symlink
+ innerSymlinkPath := filepath.Join(absPath, entryInfo.Name())
+
+ symlink, err := GetSymlink(innerSymlinkPath, false)
+ if err != nil {
+ // skip this symlink
+ continue
+ }
+
+ innerSymlinks = append(innerSymlinks, symlink)
- directory.Size += innerFile.Size
+ case false:
+ // it is a usual file
+
+ innerFilePath := filepath.Join(absPath, entryInfo.Name())
+
+ innerFile, err := GetFile(innerFilePath)
+ if err != nil {
+ // skip this file
+ continue
+ }
- innerFiles = append(innerFiles, innerFile)
+ directory.Size += innerFile.Size
+
+ innerFiles = append(innerFiles, innerFile)
+ }
}
}
directory.Directories = innerDirs
directory.Files = innerFiles
+ directory.Symlinks = innerSymlinks
return &directory, nil
}
@@ -134,7 +156,27 @@ func (dir *Directory) GetAllFiles(recursive bool) []*File {
return files
}
-// Sets `RelativeParentPath` relative to the given base path.
+// Returns every symlink in that directory
+func (dir *Directory) GetAllSymlinks(recursive bool) []*Symlink {
+ var symlinks []*Symlink = dir.Symlinks
+
+ if recursive {
+ if len(dir.Directories) == 0 {
+ return symlinks
+ }
+
+ for _, innerDir := range dir.Directories {
+ innerSymlinks := innerDir.GetAllSymlinks(recursive)
+ symlinks = append(symlinks, innerSymlinks...)
+ }
+ } else {
+ symlinks = dir.Symlinks
+ }
+
+ return symlinks
+}
+
+// Sets `RelativeParentPath` relative to the given base path for files and `Path`, `TargetPath` for symlinks so the
// file with such path:
// /home/user/directory/somefile.txt
// had a relative path like that:
@@ -150,5 +192,20 @@ func (dir *Directory) SetRelativePaths(base string, recursive bool) error {
file.RelativeParentPath = relPath
}
+
+ for _, symlink := range dir.GetAllSymlinks(recursive) {
+ symRelPath, err := filepath.Rel(base, symlink.Path)
+ if err != nil {
+ return err
+ }
+ symlink.Path = symRelPath
+
+ symRelTargetPath, err := filepath.Rel(base, symlink.TargetPath)
+ if err != nil {
+ return err
+ }
+ symlink.TargetPath = symRelTargetPath
+ }
+
return nil
}
diff --git a/src/fsys/dir_test.go b/src/fsys/dir_test.go
index 76904a3..c798824 100644
--- a/src/fsys/dir_test.go
+++ b/src/fsys/dir_test.go
@@ -21,6 +21,8 @@ along with this program. If not, see .
package fsys
import (
+ "os"
+ "path/filepath"
"testing"
)
@@ -79,25 +81,23 @@ func Test_GetFiles(t *testing.T) {
}
-// func Test_SetRelativePaths(t *testing.T) {
-// dirpath := "../testfiles/"
+func Test_GetSymlinks(t *testing.T) {
+ dirpath := "../testfiles/"
-// dir, err := GetDir(dirpath, true)
-// if err != nil {
-// t.Fatalf("%s", err)
-// }
+ os.Symlink(filepath.Join(dirpath, "testfile.txt"), filepath.Join(dirpath, "testsymlink.txt"))
+ os.Symlink(filepath.Join(dirpath, "testdir", "testfile2.txt"), filepath.Join(dirpath, "testdir", "testsymlink2.txt"))
-// absDirPath, err := filepath.Abs(dirpath)
-// if err != nil {
-// t.Fatalf("%s", err)
-// }
+ dir, err := GetDir(dirpath, true)
+ if err != nil {
+ t.Fatalf("%s", err)
+ }
-// err = dir.SetRelativePaths(absDirPath, true)
-// if err != nil {
-// t.Fatalf("%s", err)
-// }
+ // recursive
+ symlinks := dir.GetAllSymlinks(true)
+
+ symlinkCount := 2
-// for count, file := range dir.GetAllFiles(true) {
-// t.Errorf("[%d] %v\n", count, file.RelativeParentPath)
-// }
-// }
+ if len(symlinks) != symlinkCount {
+ t.Fatalf("expected to get %d symlinks; got %d\n", symlinkCount, len(symlinks))
+ }
+}
diff --git a/src/fsys/symlink.go b/src/fsys/symlink.go
new file mode 100644
index 0000000..34d90e2
--- /dev/null
+++ b/src/fsys/symlink.go
@@ -0,0 +1,70 @@
+/*
+ftu - file transferring utility.
+Copyright (C) 2021,2022 Kasyanov Nikolay Alexeevich (Unbewohnte (https://unbewohnte.xyz/))
+
+This file is a part of ftu
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+package fsys
+
+import (
+ "fmt"
+ "os"
+)
+
+type Symlink struct {
+ TargetPath string
+ Path string
+}
+
+// Checks whether path is referring to a symlink or not
+func IsSymlink(path string) (bool, error) {
+ stats, err := os.Lstat(path)
+ if err != nil {
+ return false, err
+ }
+ isSymlink := stats.Mode()&os.ModeSymlink != 0
+
+ return isSymlink, nil
+}
+
+var ErrorNotSymlink error = fmt.Errorf("not a symlink")
+
+// get necessary information about a symlink in a filesystem. If check is false -
+// does not check if path REALLY refers to a symlink
+func GetSymlink(path string, check bool) (*Symlink, error) {
+ if check {
+ isSymlink, err := IsSymlink(path)
+ if err != nil {
+ return nil, err
+ }
+ if !isSymlink {
+ return nil, ErrorNotSymlink
+ }
+ }
+
+ target, err := os.Readlink(path)
+ if err != nil {
+ return nil, err
+ }
+
+ symlink := Symlink{
+ TargetPath: target,
+ Path: path,
+ }
+
+ return &symlink, nil
+}
diff --git a/src/fsys/symlink_test.go b/src/fsys/symlink_test.go
new file mode 100644
index 0000000..deb0e06
--- /dev/null
+++ b/src/fsys/symlink_test.go
@@ -0,0 +1,22 @@
+package fsys
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+func Test_IsSymlink(t *testing.T) {
+ dirpath := "../testfiles/"
+
+ symlinkPath := filepath.Join(dirpath, "testsymlink.txt")
+ os.Symlink(filepath.Join(dirpath, "testfile.txt"), symlinkPath)
+
+ isSymlink, err := IsSymlink(symlinkPath)
+ if err != nil {
+ t.Fatalf("%s\n", err)
+ }
+ if !isSymlink {
+ t.Fatalf("%s expected to be a symlink\n", symlinkPath)
+ }
+}
diff --git a/src/main.go b/src/main.go
index bb17ea7..f258926 100644
--- a/src/main.go
+++ b/src/main.go
@@ -30,7 +30,7 @@ import (
)
var (
- VERSION string = "v2.2.3"
+ VERSION string = "v2.3.0"
versionInformation string = fmt.Sprintf("ftu %s\nfile transferring utility\n\nCopyright (C) 2021,2022 Kasyanov Nikolay Alexeevich (Unbewohnte (me@unbewohnte.xyz))\nThis program comes with ABSOLUTELY NO WARRANTY.\nThis is free software, and you are welcome to redistribute it under certain conditions; type \"ftu -l\" for details.\n", VERSION)
diff --git a/src/node/node.go b/src/node/node.go
index 68334fb..503fd7e 100644
--- a/src/node/node.go
+++ b/src/node/node.go
@@ -49,16 +49,18 @@ type netInfo struct {
// Sending-side node information
type sending struct {
- ServingPath string // path to the thing that will be sent
- IsDirectory bool // is ServingPath a directory
- Recursive bool // recursively send directory
- CanSendBytes bool // is the other node ready to receive another piece
- AllowedToTransfer bool // the way to notify the mainloop of a sending node to start sending pieces of files
- InTransfer bool // already transferring|receiving files
- FilesToSend []*fsys.File
- CurrentFileID uint64 // an id of a file that is currently being transported
- SentBytes uint64 // how many bytes sent already
- TotalTransferSize uint64 // how many bytes will be sent in total
+ ServingPath string // path to the thing that will be sent
+ IsDirectory bool // is ServingPath a directory
+ Recursive bool // recursively send directory
+ CanSendBytes bool // is the other node ready to receive another piece
+ AllowedToTransfer bool // the way to notify the mainloop of a sending node to start sending pieces of files
+ InTransfer bool // already transferring|receiving files
+ FilesToSend []*fsys.File
+ SymlinksToSend []*fsys.Symlink
+ CurrentFileID uint64 // an id of a file that is currently being transported
+ SentBytes uint64 // how many bytes sent already
+ TotalTransferSize uint64 // how many bytes will be sent in total
+ CurrentSymlinkIndex uint64 // current index of a symlink that is
}
// Receiving-side node information
@@ -355,8 +357,10 @@ func (node *Node) send() {
panic(err)
}
filesToSend := DIRTOSEND.GetAllFiles(node.transferInfo.Sending.Recursive)
+ symlinksToSend := DIRTOSEND.GetAllSymlinks(node.transferInfo.Sending.Recursive)
+
+ node.transferInfo.Sending.SymlinksToSend = symlinksToSend
- // notify the other node about all the files that are going to be sent
for counter, file := range filesToSend {
// assign ID and add it to the node sendlist
file.ID = uint64(counter)
@@ -410,7 +414,14 @@ func (node *Node) send() {
// Transfer section
- if len(node.transferInfo.Sending.FilesToSend) == 0 {
+ // if all files have been sent -> send symlinks
+ if len(node.transferInfo.Sending.FilesToSend) == 0 && node.transferInfo.Sending.CurrentSymlinkIndex < uint64(len(node.transferInfo.Sending.SymlinksToSend)) {
+ protocol.SendSymlink(node.transferInfo.Sending.SymlinksToSend[node.transferInfo.Sending.CurrentSymlinkIndex], node.netInfo.Conn, encrKey)
+ node.transferInfo.Sending.CurrentSymlinkIndex++
+ continue
+ }
+
+ if len(node.transferInfo.Sending.FilesToSend) == 0 && node.transferInfo.Sending.CurrentSymlinkIndex == uint64(len(node.transferInfo.Sending.SymlinksToSend)) {
// if there`s nothing else to send - create and send DONE packet
protocol.SendPacket(node.netInfo.Conn, protocol.Packet{
Header: protocol.HeaderDone,
@@ -857,6 +868,41 @@ func (node *Node) receive() {
fmt.Printf("\nGot an encryption key: %s", encrKey)
+ case protocol.HeaderSymlink:
+ // SYMLINK~(string size in binary)(location in the filesystem)(string size in binary)(location of a target)
+ packetReader := bytes.NewReader(incomingPacket.Body)
+
+ // extract the location of the symlink
+ var locationSize uint64
+ binary.Read(packetReader, binary.BigEndian, &locationSize)
+
+ symlinkLocationBytes := make([]byte, locationSize)
+ packetReader.Read(symlinkLocationBytes)
+
+ // extract the target of a symlink
+ var targetSize uint64
+ binary.Read(packetReader, binary.BigEndian, &targetSize)
+
+ symlinkTargetLocationBytes := make([]byte, targetSize)
+ packetReader.Read(symlinkTargetLocationBytes)
+
+ symlinkLocation := string(symlinkLocationBytes)
+ symlinkTargetLocation := string(symlinkTargetLocationBytes)
+
+ // create a symlink
+
+ // should be already downloaded
+ symlinkDir := filepath.Join(node.transferInfo.Receiving.DownloadsPath, filepath.Dir(symlinkLocation))
+ os.MkdirAll(symlinkDir, os.ModePerm)
+
+ os.Symlink(
+ filepath.Join(node.transferInfo.Receiving.DownloadsPath, symlinkTargetLocation),
+ filepath.Join(node.transferInfo.Receiving.DownloadsPath, symlinkLocation))
+
+ protocol.SendPacket(node.netInfo.Conn, protocol.Packet{
+ Header: protocol.HeaderReady,
+ })
+
case protocol.HeaderDone:
node.mutex.Lock()
node.stopped = true
diff --git a/src/protocol/headers.go b/src/protocol/headers.go
index 17d336f..0bb8e47 100644
--- a/src/protocol/headers.go
+++ b/src/protocol/headers.go
@@ -114,3 +114,10 @@ const HeaderDirectory Header = "DIRECTORY"
// Body must contain a file ID.
// ie: ALREADYHAVE~(file ID in binary)
const HeaderAlreadyHave Header = "ALREADYHAVE"
+
+// SYMLINK
+// Sent by sender AFTER ALL FILES has been sent already. Indicates that there
+// is a symlink in some place that points to some other already received file.
+// Body must contain information where the symlink is and the target file.
+// ie: SYMLINK~(string size in binary)(location in the filesystem)(string size in binary)(location of a target)
+const HeaderSymlink Header = "SYMLINK"
diff --git a/src/protocol/send.go b/src/protocol/send.go
index 27dba0e..9beeabc 100644
--- a/src/protocol/send.go
+++ b/src/protocol/send.go
@@ -209,3 +209,36 @@ func SendPiece(file *fsys.File, connection net.Conn, encrKey []byte) (uint64, er
return sentBytes, nil
}
+
+// Sends a symlink to the other side. If encrKey is not nil - encrypts the packet with this key
+func SendSymlink(symlink *fsys.Symlink, connection net.Conn, encrKey []byte) error {
+ symlinkPacket := Packet{
+ Header: HeaderSymlink,
+ }
+
+ symlinkPacketBodyBuff := new(bytes.Buffer)
+
+ // SYMLINK~(string size in binary)(location in the filesystem)(string size in binary)(location of a target)
+
+ binary.Write(symlinkPacketBodyBuff, binary.BigEndian, uint64(len(symlink.Path)))
+ symlinkPacketBodyBuff.Write([]byte(symlink.Path))
+
+ binary.Write(symlinkPacketBodyBuff, binary.BigEndian, uint64(len(symlink.TargetPath)))
+ symlinkPacketBodyBuff.Write([]byte(symlink.TargetPath))
+
+ symlinkPacket.Body = symlinkPacketBodyBuff.Bytes()
+
+ if encrKey != nil {
+ err := symlinkPacket.EncryptBody(encrKey)
+ if err != nil {
+ return err
+ }
+ }
+
+ err := SendPacket(connection, symlinkPacket)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/src/testfiles/testdir/testsymlink2.txt b/src/testfiles/testdir/testsymlink2.txt
new file mode 120000
index 0000000..2ed3a5a
--- /dev/null
+++ b/src/testfiles/testdir/testsymlink2.txt
@@ -0,0 +1 @@
+../testfiles/testdir/testfile2.txt
\ No newline at end of file
diff --git a/src/testfiles/testsymlink.txt b/src/testfiles/testsymlink.txt
new file mode 120000
index 0000000..c6a254b
--- /dev/null
+++ b/src/testfiles/testsymlink.txt
@@ -0,0 +1 @@
+../testfiles/testfile.txt
\ No newline at end of file