Skip to content

Commit 8eb2f18

Browse files
author
Unbewohnte
committed
Symlinks support !
1 parent 700811b commit 8eb2f18

File tree

10 files changed

+277
-40
lines changed

10 files changed

+277
-40
lines changed

src/fsys/dir.go

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type Directory struct {
3333
Path string
3434
Size uint64
3535
RelativeParentPath string // Relative path to the directory, where the highest point in the hierarchy is the upmost parent dir. Set manually
36+
Symlinks []*Symlink
3637
Files []*File
3738
Directories []*Directory
3839
}
@@ -70,6 +71,7 @@ func GetDir(path string, recursive bool) (*Directory, error) {
7071

7172
var innerDirs []*Directory
7273
var innerFiles []*File
74+
var innerSymlinks []*Symlink
7375
for _, entry := range entries {
7476
entryInfo, err := entry.Info()
7577
if err != nil {
@@ -93,22 +95,42 @@ func GetDir(path string, recursive bool) (*Directory, error) {
9395
// if not - skip the directory and only work with the files
9496

9597
} else {
96-
innerFilePath := filepath.Join(absPath, entryInfo.Name())
98+
// not a directory
9799

98-
innerFile, err := GetFile(innerFilePath)
99-
if err != nil {
100-
// skip this file
101-
continue
102-
}
100+
switch entryInfo.Mode()&os.ModeSymlink != 0 {
101+
case true:
102+
// it is a symlink
103+
innerSymlinkPath := filepath.Join(absPath, entryInfo.Name())
104+
105+
symlink, err := GetSymlink(innerSymlinkPath, false)
106+
if err != nil {
107+
// skip this symlink
108+
continue
109+
}
110+
111+
innerSymlinks = append(innerSymlinks, symlink)
103112

104-
directory.Size += innerFile.Size
113+
case false:
114+
// it is a usual file
115+
116+
innerFilePath := filepath.Join(absPath, entryInfo.Name())
117+
118+
innerFile, err := GetFile(innerFilePath)
119+
if err != nil {
120+
// skip this file
121+
continue
122+
}
105123

106-
innerFiles = append(innerFiles, innerFile)
124+
directory.Size += innerFile.Size
125+
126+
innerFiles = append(innerFiles, innerFile)
127+
}
107128
}
108129
}
109130

110131
directory.Directories = innerDirs
111132
directory.Files = innerFiles
133+
directory.Symlinks = innerSymlinks
112134

113135
return &directory, nil
114136
}
@@ -134,7 +156,27 @@ func (dir *Directory) GetAllFiles(recursive bool) []*File {
134156
return files
135157
}
136158

137-
// Sets `RelativeParentPath` relative to the given base path.
159+
// Returns every symlink in that directory
160+
func (dir *Directory) GetAllSymlinks(recursive bool) []*Symlink {
161+
var symlinks []*Symlink = dir.Symlinks
162+
163+
if recursive {
164+
if len(dir.Directories) == 0 {
165+
return symlinks
166+
}
167+
168+
for _, innerDir := range dir.Directories {
169+
innerSymlinks := innerDir.GetAllSymlinks(recursive)
170+
symlinks = append(symlinks, innerSymlinks...)
171+
}
172+
} else {
173+
symlinks = dir.Symlinks
174+
}
175+
176+
return symlinks
177+
}
178+
179+
// Sets `RelativeParentPath` relative to the given base path for files and `Path`, `TargetPath` for symlinks so the
138180
// file with such path:
139181
// /home/user/directory/somefile.txt
140182
// had a relative path like that:
@@ -150,5 +192,20 @@ func (dir *Directory) SetRelativePaths(base string, recursive bool) error {
150192
file.RelativeParentPath = relPath
151193

152194
}
195+
196+
for _, symlink := range dir.GetAllSymlinks(recursive) {
197+
symRelPath, err := filepath.Rel(base, symlink.Path)
198+
if err != nil {
199+
return err
200+
}
201+
symlink.Path = symRelPath
202+
203+
symRelTargetPath, err := filepath.Rel(base, symlink.TargetPath)
204+
if err != nil {
205+
return err
206+
}
207+
symlink.TargetPath = symRelTargetPath
208+
}
209+
153210
return nil
154211
}

src/fsys/dir_test.go

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
2121
package fsys
2222

2323
import (
24+
"os"
25+
"path/filepath"
2426
"testing"
2527
)
2628

@@ -79,25 +81,23 @@ func Test_GetFiles(t *testing.T) {
7981

8082
}
8183

82-
// func Test_SetRelativePaths(t *testing.T) {
83-
// dirpath := "../testfiles/"
84+
func Test_GetSymlinks(t *testing.T) {
85+
dirpath := "../testfiles/"
8486

85-
// dir, err := GetDir(dirpath, true)
86-
// if err != nil {
87-
// t.Fatalf("%s", err)
88-
// }
87+
os.Symlink(filepath.Join(dirpath, "testfile.txt"), filepath.Join(dirpath, "testsymlink.txt"))
88+
os.Symlink(filepath.Join(dirpath, "testdir", "testfile2.txt"), filepath.Join(dirpath, "testdir", "testsymlink2.txt"))
8989

90-
// absDirPath, err := filepath.Abs(dirpath)
91-
// if err != nil {
92-
// t.Fatalf("%s", err)
93-
// }
90+
dir, err := GetDir(dirpath, true)
91+
if err != nil {
92+
t.Fatalf("%s", err)
93+
}
9494

95-
// err = dir.SetRelativePaths(absDirPath, true)
96-
// if err != nil {
97-
// t.Fatalf("%s", err)
98-
// }
95+
// recursive
96+
symlinks := dir.GetAllSymlinks(true)
97+
98+
symlinkCount := 2
9999

100-
// for count, file := range dir.GetAllFiles(true) {
101-
// t.Errorf("[%d] %v\n", count, file.RelativeParentPath)
102-
// }
103-
// }
100+
if len(symlinks) != symlinkCount {
101+
t.Fatalf("expected to get %d symlinks; got %d\n", symlinkCount, len(symlinks))
102+
}
103+
}

src/fsys/symlink.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
ftu - file transferring utility.
3+
Copyright (C) 2021,2022 Kasyanov Nikolay Alexeevich (Unbewohnte (https://unbewohnte.xyz/))
4+
5+
This file is a part of ftu
6+
7+
This program is free software: you can redistribute it and/or modify
8+
it under the terms of the GNU General Public License as published by
9+
the Free Software Foundation, either version 3 of the License, or
10+
(at your option) any later version.
11+
12+
This program is distributed in the hope that it will be useful,
13+
but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
GNU General Public License for more details.
16+
17+
You should have received a copy of the GNU General Public License
18+
along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
*/
20+
21+
package fsys
22+
23+
import (
24+
"fmt"
25+
"os"
26+
)
27+
28+
type Symlink struct {
29+
TargetPath string
30+
Path string
31+
}
32+
33+
// Checks whether path is referring to a symlink or not
34+
func IsSymlink(path string) (bool, error) {
35+
stats, err := os.Lstat(path)
36+
if err != nil {
37+
return false, err
38+
}
39+
isSymlink := stats.Mode()&os.ModeSymlink != 0
40+
41+
return isSymlink, nil
42+
}
43+
44+
var ErrorNotSymlink error = fmt.Errorf("not a symlink")
45+
46+
// get necessary information about a symlink in a filesystem. If check is false -
47+
// does not check if path REALLY refers to a symlink
48+
func GetSymlink(path string, check bool) (*Symlink, error) {
49+
if check {
50+
isSymlink, err := IsSymlink(path)
51+
if err != nil {
52+
return nil, err
53+
}
54+
if !isSymlink {
55+
return nil, ErrorNotSymlink
56+
}
57+
}
58+
59+
target, err := os.Readlink(path)
60+
if err != nil {
61+
return nil, err
62+
}
63+
64+
symlink := Symlink{
65+
TargetPath: target,
66+
Path: path,
67+
}
68+
69+
return &symlink, nil
70+
}

src/fsys/symlink_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package fsys
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
)
8+
9+
func Test_IsSymlink(t *testing.T) {
10+
dirpath := "../testfiles/"
11+
12+
symlinkPath := filepath.Join(dirpath, "testsymlink.txt")
13+
os.Symlink(filepath.Join(dirpath, "testfile.txt"), symlinkPath)
14+
15+
isSymlink, err := IsSymlink(symlinkPath)
16+
if err != nil {
17+
t.Fatalf("%s\n", err)
18+
}
19+
if !isSymlink {
20+
t.Fatalf("%s expected to be a symlink\n", symlinkPath)
21+
}
22+
}

src/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import (
3030
)
3131

3232
var (
33-
VERSION string = "v2.2.3"
33+
VERSION string = "v2.3.0"
3434

3535
versionInformation string = fmt.Sprintf("ftu %s\nfile transferring utility\n\nCopyright (C) 2021,2022 Kasyanov Nikolay Alexeevich (Unbewohnte ([email protected]))\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)
3636

src/node/node.go

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,18 @@ type netInfo struct {
4949

5050
// Sending-side node information
5151
type sending struct {
52-
ServingPath string // path to the thing that will be sent
53-
IsDirectory bool // is ServingPath a directory
54-
Recursive bool // recursively send directory
55-
CanSendBytes bool // is the other node ready to receive another piece
56-
AllowedToTransfer bool // the way to notify the mainloop of a sending node to start sending pieces of files
57-
InTransfer bool // already transferring|receiving files
58-
FilesToSend []*fsys.File
59-
CurrentFileID uint64 // an id of a file that is currently being transported
60-
SentBytes uint64 // how many bytes sent already
61-
TotalTransferSize uint64 // how many bytes will be sent in total
52+
ServingPath string // path to the thing that will be sent
53+
IsDirectory bool // is ServingPath a directory
54+
Recursive bool // recursively send directory
55+
CanSendBytes bool // is the other node ready to receive another piece
56+
AllowedToTransfer bool // the way to notify the mainloop of a sending node to start sending pieces of files
57+
InTransfer bool // already transferring|receiving files
58+
FilesToSend []*fsys.File
59+
SymlinksToSend []*fsys.Symlink
60+
CurrentFileID uint64 // an id of a file that is currently being transported
61+
SentBytes uint64 // how many bytes sent already
62+
TotalTransferSize uint64 // how many bytes will be sent in total
63+
CurrentSymlinkIndex uint64 // current index of a symlink that is
6264
}
6365

6466
// Receiving-side node information
@@ -355,8 +357,10 @@ func (node *Node) send() {
355357
panic(err)
356358
}
357359
filesToSend := DIRTOSEND.GetAllFiles(node.transferInfo.Sending.Recursive)
360+
symlinksToSend := DIRTOSEND.GetAllSymlinks(node.transferInfo.Sending.Recursive)
361+
362+
node.transferInfo.Sending.SymlinksToSend = symlinksToSend
358363

359-
// notify the other node about all the files that are going to be sent
360364
for counter, file := range filesToSend {
361365
// assign ID and add it to the node sendlist
362366
file.ID = uint64(counter)
@@ -410,7 +414,14 @@ func (node *Node) send() {
410414

411415
// Transfer section
412416

413-
if len(node.transferInfo.Sending.FilesToSend) == 0 {
417+
// if all files have been sent -> send symlinks
418+
if len(node.transferInfo.Sending.FilesToSend) == 0 && node.transferInfo.Sending.CurrentSymlinkIndex < uint64(len(node.transferInfo.Sending.SymlinksToSend)) {
419+
protocol.SendSymlink(node.transferInfo.Sending.SymlinksToSend[node.transferInfo.Sending.CurrentSymlinkIndex], node.netInfo.Conn, encrKey)
420+
node.transferInfo.Sending.CurrentSymlinkIndex++
421+
continue
422+
}
423+
424+
if len(node.transferInfo.Sending.FilesToSend) == 0 && node.transferInfo.Sending.CurrentSymlinkIndex == uint64(len(node.transferInfo.Sending.SymlinksToSend)) {
414425
// if there`s nothing else to send - create and send DONE packet
415426
protocol.SendPacket(node.netInfo.Conn, protocol.Packet{
416427
Header: protocol.HeaderDone,
@@ -857,6 +868,41 @@ func (node *Node) receive() {
857868

858869
fmt.Printf("\nGot an encryption key: %s", encrKey)
859870

871+
case protocol.HeaderSymlink:
872+
// SYMLINK~(string size in binary)(location in the filesystem)(string size in binary)(location of a target)
873+
packetReader := bytes.NewReader(incomingPacket.Body)
874+
875+
// extract the location of the symlink
876+
var locationSize uint64
877+
binary.Read(packetReader, binary.BigEndian, &locationSize)
878+
879+
symlinkLocationBytes := make([]byte, locationSize)
880+
packetReader.Read(symlinkLocationBytes)
881+
882+
// extract the target of a symlink
883+
var targetSize uint64
884+
binary.Read(packetReader, binary.BigEndian, &targetSize)
885+
886+
symlinkTargetLocationBytes := make([]byte, targetSize)
887+
packetReader.Read(symlinkTargetLocationBytes)
888+
889+
symlinkLocation := string(symlinkLocationBytes)
890+
symlinkTargetLocation := string(symlinkTargetLocationBytes)
891+
892+
// create a symlink
893+
894+
// should be already downloaded
895+
symlinkDir := filepath.Join(node.transferInfo.Receiving.DownloadsPath, filepath.Dir(symlinkLocation))
896+
os.MkdirAll(symlinkDir, os.ModePerm)
897+
898+
os.Symlink(
899+
filepath.Join(node.transferInfo.Receiving.DownloadsPath, symlinkTargetLocation),
900+
filepath.Join(node.transferInfo.Receiving.DownloadsPath, symlinkLocation))
901+
902+
protocol.SendPacket(node.netInfo.Conn, protocol.Packet{
903+
Header: protocol.HeaderReady,
904+
})
905+
860906
case protocol.HeaderDone:
861907
node.mutex.Lock()
862908
node.stopped = true

src/protocol/headers.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,10 @@ const HeaderDirectory Header = "DIRECTORY"
114114
// Body must contain a file ID.
115115
// ie: ALREADYHAVE~(file ID in binary)
116116
const HeaderAlreadyHave Header = "ALREADYHAVE"
117+
118+
// SYMLINK
119+
// Sent by sender AFTER ALL FILES has been sent already. Indicates that there
120+
// is a symlink in some place that points to some other already received file.
121+
// Body must contain information where the symlink is and the target file.
122+
// ie: SYMLINK~(string size in binary)(location in the filesystem)(string size in binary)(location of a target)
123+
const HeaderSymlink Header = "SYMLINK"

0 commit comments

Comments
 (0)