1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package main
17
18 import (
19 "fmt"
20 "io"
21 "os"
22 "path/filepath"
23 )
24
25 type replicateMode int
26
27 const (
28 copyMode replicateMode = iota
29 hardlinkMode
30 softlinkMode
31 )
32
33 type replicateOption func(*replicateConfig)
34 type replicateConfig struct {
35 removeFirst bool
36 fileMode replicateMode
37 dirMode replicateMode
38 paths []string
39 }
40
41 func replicatePaths(paths ...string) replicateOption {
42 return func(config *replicateConfig) {
43 config.paths = append(config.paths, paths...)
44 }
45 }
46
47
48 func replicatePrepare(dst string, config *replicateConfig) error {
49 dir := filepath.Dir(dst)
50 if err := os.MkdirAll(dir, 0755); err != nil {
51 return fmt.Errorf("Failed to make %s: %v", dir, err)
52 }
53 if config.removeFirst {
54 _ = os.Remove(dst)
55 }
56 return nil
57 }
58
59
60 func replicateFile(src, dst string, config *replicateConfig) error {
61 if err := replicatePrepare(dst, config); err != nil {
62 return err
63 }
64 switch config.fileMode {
65 case copyMode:
66 in, err := os.Open(src)
67 if err != nil {
68 return err
69 }
70 defer in.Close()
71 out, err := os.Create(dst)
72 if err != nil {
73 return err
74 }
75 _, err = io.Copy(out, in)
76 closeerr := out.Close()
77 if err != nil {
78 return err
79 }
80 if closeerr != nil {
81 return closeerr
82 }
83 s, err := os.Stat(src)
84 if err != nil {
85 return err
86 }
87 if err := os.Chmod(dst, s.Mode()); err != nil {
88 return err
89 }
90 return nil
91 case hardlinkMode:
92 return os.Link(src, dst)
93 case softlinkMode:
94 return os.Symlink(src, dst)
95 default:
96 return fmt.Errorf("Invalid replication mode %d", config.fileMode)
97 }
98 }
99
100
101
102 func replicateDir(src, dst string, config *replicateConfig) error {
103 if err := replicatePrepare(dst, config); err != nil {
104 return err
105 }
106 switch config.dirMode {
107 case copyMode:
108 return filepath.Walk(src, func(path string, f os.FileInfo, err error) error {
109 if f.IsDir() {
110 return nil
111 }
112 relative, err := filepath.Rel(src, path)
113 if err != nil {
114 return err
115 }
116 return replicateFile(path, filepath.Join(dst, relative), config)
117 })
118 case hardlinkMode:
119 return os.Link(src, dst)
120 case softlinkMode:
121 return os.Symlink(src, dst)
122 default:
123 return fmt.Errorf("Invalid replication mode %d", config.fileMode)
124 }
125 }
126
127
128 func replicateTree(src, dst string, config *replicateConfig) error {
129 if err := os.RemoveAll(dst); err != nil {
130 return fmt.Errorf("Failed to remove file at destination %s: %v", dst, err)
131 }
132 if l, err := filepath.EvalSymlinks(src); err != nil {
133 return err
134 } else {
135 src = l
136 }
137 if s, err := os.Stat(src); err != nil {
138 return err
139 } else if s.IsDir() {
140 return replicateDir(src, dst, config)
141 }
142 return replicateFile(src, dst, config)
143 }
144
145
146
147
148
149 func replicate(src, dst string, options ...replicateOption) error {
150 config := replicateConfig{
151 removeFirst: true,
152 }
153 for _, option := range options {
154 option(&config)
155 }
156 if len(config.paths) == 0 {
157 return replicateTree(src, dst, &config)
158 }
159 for _, base := range config.paths {
160 from := filepath.Join(src, base)
161 to := filepath.Join(dst, base)
162 if err := replicateTree(from, to, &config); err != nil {
163 return err
164 }
165 }
166 return nil
167 }
168
View as plain text