-
-
Notifications
You must be signed in to change notification settings - Fork 16
/
rsync.go
165 lines (134 loc) · 4.13 KB
/
rsync.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package core
/* License: GPLv3
Authors:
Mirko Brombin <mirko@fabricators.ltd>
Vanilla OS Contributors <https://github.com/vanilla-os/>
Luca di Maio <https://github.com/89luca89>
Mateus B. Melchiades <https://github.com/matbme>
Copyright: 2024
Description:
ABRoot is utility which provides full immutability and
atomicity to a Linux system, by transacting between
two root filesystems. Updates are performed using OCI
images, to ensure that the system is always in a
consistent state.
*/
import (
"bufio"
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"github.com/vanilla-os/orchid/cmdr"
)
// rsyncCmd executes the rsync command with the requested options.
// If silent is true, rsync progress will not appear in stdout.
func rsyncCmd(src, dst string, opts []string, silent bool) error {
args := []string{"-avxHAX"}
args = append(args, opts...)
args = append(args, src)
args = append(args, dst)
cmd := exec.Command("rsync", args...)
stdout, _ := cmd.StdoutPipe()
var totalFiles int
if !silent {
countCmdOut, _ := exec.Command(
"/bin/sh",
"-c",
fmt.Sprintf("echo -n $(($(rsync --dry-run %s | wc -l) - 4))", strings.Join(args, " ")),
).Output()
totalFiles, _ = strconv.Atoi(string(countCmdOut))
}
reader := bufio.NewReader(stdout)
err := cmd.Start()
if err != nil {
return err
}
if !silent {
verbose := IsVerbose()
p, _ := cmdr.ProgressBar.WithTotal(totalFiles).WithTitle("Sync in progress").WithMaxWidth(120).Start()
maxLineLen := cmdr.TerminalWidth() / 4
for i := 0; i < p.Total; i++ {
line, _ := reader.ReadString('\n')
line = strings.TrimSpace(line)
if verbose {
cmdr.Info.Println(line + " synced")
}
if len(line) > maxLineLen {
startingLen := len(line) - maxLineLen + 1
line = "<" + line[startingLen:]
} else {
padding := maxLineLen - len(line)
line += strings.Repeat(" ", padding)
}
p.UpdateTitle("Syncing " + line)
p.Increment()
}
} else {
stdout.Close()
}
err = cmd.Wait()
if err != nil {
// exit status 24 is a warning, not an error, we don't care about it
// since rsync is going to be removed in the OCI version
if !strings.Contains(err.Error(), "exit status 24") {
return err
}
}
return nil
}
// rsyncDryRun executes the rsync command with the --dry-run option.
func rsyncDryRun(src, dst string, excluded []string) error {
opts := []string{"--dry-run"}
if len(excluded) > 0 {
for _, exclude := range excluded {
opts = append(opts, "--exclude="+exclude)
}
}
return rsyncCmd(src, dst, opts, false)
}
// AtomicRsync executes the rsync command in an atomic-like manner.
// It does so by dry-running the rsync, and if it succeeds, it runs
// the rsync again performing changes.
// If the keepUnwanted option
// is set to true, it will omit the --delete option, so that the already
// existing and unwanted files will not be deleted.
// To ensure the changes are applied atomically, we rsync on a _new directory first,
// and use atomicSwap to replace the _new with the dst directory.
func AtomicRsync(src, dst string, transitionalPath string, finalPath string, excluded []string, keepUnwanted bool) error {
PrintVerboseInfo("AtomicRsync", "Running...")
if _, err := os.Stat(transitionalPath); os.IsNotExist(err) {
err = os.Mkdir(transitionalPath, 0755)
if err != nil {
PrintVerboseErr("AtomicRsync", 0, err)
return err
}
}
PrintVerboseInfo("AtomicRsync", "Starting dry run process...")
err := rsyncDryRun(src, transitionalPath, excluded)
if err != nil {
return err
}
opts := []string{"--link-dest", dst, "--exclude", finalPath, "--exclude", transitionalPath}
if len(excluded) > 0 {
for _, exclude := range excluded {
opts = append(opts, "--exclude", exclude)
}
}
if !keepUnwanted {
opts = append(opts, "--delete")
}
PrintVerboseInfo("AtomicRsync", "Starting rsync process...")
err = rsyncCmd(src, transitionalPath, opts, true)
if err != nil {
return err
}
PrintVerboseInfo("AtomicRsync", "Starting atomic swap process...")
err = AtomicSwap(transitionalPath, finalPath)
if err != nil {
return err
}
PrintVerboseInfo("AtomicRsync", "Removing transitional path...")
return os.RemoveAll(transitionalPath)
}