Browse Source

Symlinks support !

main v2.3.0
Unbewohnte 3 years ago
parent
commit
8eb2f181fd
  1. 75
      src/fsys/dir.go
  2. 36
      src/fsys/dir_test.go
  3. 70
      src/fsys/symlink.go
  4. 22
      src/fsys/symlink_test.go
  5. 2
      src/main.go
  6. 70
      src/node/node.go
  7. 7
      src/protocol/headers.go
  8. 33
      src/protocol/send.go
  9. 1
      src/testfiles/testdir/testsymlink2.txt
  10. 1
      src/testfiles/testsymlink.txt

75
src/fsys/dir.go

@ -33,6 +33,7 @@ type Directory struct {
Path string Path string
Size uint64 Size uint64
RelativeParentPath string // Relative path to the directory, where the highest point in the hierarchy is the upmost parent dir. Set manually 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 Files []*File
Directories []*Directory Directories []*Directory
} }
@ -70,6 +71,7 @@ func GetDir(path string, recursive bool) (*Directory, error) {
var innerDirs []*Directory var innerDirs []*Directory
var innerFiles []*File var innerFiles []*File
var innerSymlinks []*Symlink
for _, entry := range entries { for _, entry := range entries {
entryInfo, err := entry.Info() entryInfo, err := entry.Info()
if err != nil { 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 // if not - skip the directory and only work with the files
} else { } else {
innerFilePath := filepath.Join(absPath, entryInfo.Name()) // not a directory
innerFile, err := GetFile(innerFilePath) switch entryInfo.Mode()&os.ModeSymlink != 0 {
if err != nil { case true:
// skip this file // it is a symlink
continue 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.Directories = innerDirs
directory.Files = innerFiles directory.Files = innerFiles
directory.Symlinks = innerSymlinks
return &directory, nil return &directory, nil
} }
@ -134,7 +156,27 @@ func (dir *Directory) GetAllFiles(recursive bool) []*File {
return files 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: // file with such path:
// /home/user/directory/somefile.txt // /home/user/directory/somefile.txt
// had a relative path like that: // had a relative path like that:
@ -150,5 +192,20 @@ func (dir *Directory) SetRelativePaths(base string, recursive bool) error {
file.RelativeParentPath = relPath 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 return nil
} }

36
src/fsys/dir_test.go

@ -21,6 +21,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
package fsys package fsys
import ( import (
"os"
"path/filepath"
"testing" "testing"
) )
@ -79,25 +81,23 @@ func Test_GetFiles(t *testing.T) {
} }
// func Test_SetRelativePaths(t *testing.T) { func Test_GetSymlinks(t *testing.T) {
// dirpath := "../testfiles/" dirpath := "../testfiles/"
// dir, err := GetDir(dirpath, true) os.Symlink(filepath.Join(dirpath, "testfile.txt"), filepath.Join(dirpath, "testsymlink.txt"))
// if err != nil { os.Symlink(filepath.Join(dirpath, "testdir", "testfile2.txt"), filepath.Join(dirpath, "testdir", "testsymlink2.txt"))
// t.Fatalf("%s", err)
// }
// absDirPath, err := filepath.Abs(dirpath) dir, err := GetDir(dirpath, true)
// if err != nil { if err != nil {
// t.Fatalf("%s", err) t.Fatalf("%s", err)
// } }
// err = dir.SetRelativePaths(absDirPath, true) // recursive
// if err != nil { symlinks := dir.GetAllSymlinks(true)
// t.Fatalf("%s", err)
// } symlinkCount := 2
// for count, file := range dir.GetAllFiles(true) { if len(symlinks) != symlinkCount {
// t.Errorf("[%d] %v\n", count, file.RelativeParentPath) t.Fatalf("expected to get %d symlinks; got %d\n", symlinkCount, len(symlinks))
// } }
// } }

70
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 <https://www.gnu.org/licenses/>.
*/
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
}

22
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)
}
}

2
src/main.go

@ -30,7 +30,7 @@ import (
) )
var ( 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) 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)

70
src/node/node.go

@ -49,16 +49,18 @@ type netInfo struct {
// Sending-side node information // Sending-side node information
type sending struct { type sending struct {
ServingPath string // path to the thing that will be sent ServingPath string // path to the thing that will be sent
IsDirectory bool // is ServingPath a directory IsDirectory bool // is ServingPath a directory
Recursive bool // recursively send directory Recursive bool // recursively send directory
CanSendBytes bool // is the other node ready to receive another piece 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 AllowedToTransfer bool // the way to notify the mainloop of a sending node to start sending pieces of files
InTransfer bool // already transferring|receiving files InTransfer bool // already transferring|receiving files
FilesToSend []*fsys.File FilesToSend []*fsys.File
CurrentFileID uint64 // an id of a file that is currently being transported SymlinksToSend []*fsys.Symlink
SentBytes uint64 // how many bytes sent already CurrentFileID uint64 // an id of a file that is currently being transported
TotalTransferSize uint64 // how many bytes will be sent in total 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 // Receiving-side node information
@ -355,8 +357,10 @@ func (node *Node) send() {
panic(err) panic(err)
} }
filesToSend := DIRTOSEND.GetAllFiles(node.transferInfo.Sending.Recursive) 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 { for counter, file := range filesToSend {
// assign ID and add it to the node sendlist // assign ID and add it to the node sendlist
file.ID = uint64(counter) file.ID = uint64(counter)
@ -410,7 +414,14 @@ func (node *Node) send() {
// Transfer section // 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 // if there`s nothing else to send - create and send DONE packet
protocol.SendPacket(node.netInfo.Conn, protocol.Packet{ protocol.SendPacket(node.netInfo.Conn, protocol.Packet{
Header: protocol.HeaderDone, Header: protocol.HeaderDone,
@ -857,6 +868,41 @@ func (node *Node) receive() {
fmt.Printf("\nGot an encryption key: %s", encrKey) 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: case protocol.HeaderDone:
node.mutex.Lock() node.mutex.Lock()
node.stopped = true node.stopped = true

7
src/protocol/headers.go

@ -114,3 +114,10 @@ const HeaderDirectory Header = "DIRECTORY"
// Body must contain a file ID. // Body must contain a file ID.
// ie: ALREADYHAVE~(file ID in binary) // ie: ALREADYHAVE~(file ID in binary)
const HeaderAlreadyHave Header = "ALREADYHAVE" 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"

33
src/protocol/send.go

@ -209,3 +209,36 @@ func SendPiece(file *fsys.File, connection net.Conn, encrKey []byte) (uint64, er
return sentBytes, nil 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
}

1
src/testfiles/testdir/testsymlink2.txt

@ -0,0 +1 @@
../testfiles/testdir/testfile2.txt

1
src/testfiles/testsymlink.txt

@ -0,0 +1 @@
../testfiles/testfile.txt
Loading…
Cancel
Save