]> Cypherpunks.ru repositories - nncp.git/blob - src/cmd/nncp-xfer/main.go
Major namespace version was already updated
[nncp.git] / src / cmd / nncp-xfer / main.go
1 /*
2 NNCP -- Node to Node copy, utilities for store-and-forward data exchange
3 Copyright (C) 2016-2021 Sergey Matveev <stargrave@stargrave.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, version 3 of the License.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 // Exchange NNCP inbound and outbounds packets with external directory.
19 package main
20
21 import (
22         "bufio"
23         "errors"
24         "flag"
25         "fmt"
26         "io"
27         "log"
28         "os"
29         "path/filepath"
30
31         "go.cypherpunks.ru/nncp/v6"
32 )
33
34 func usage() {
35         fmt.Fprintf(os.Stderr, nncp.UsageHeader())
36         fmt.Fprintf(os.Stderr, "nncp-xfer -- copy inbound and outbounds packets\n\n")
37         fmt.Fprintf(os.Stderr, "Usage: %s [options] DIR\nOptions:\n", os.Args[0])
38         flag.PrintDefaults()
39 }
40
41 func main() {
42         var (
43                 cfgPath   = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file")
44                 nodeRaw   = flag.String("node", "", "Process only that node")
45                 niceRaw   = flag.String("nice", nncp.NicenessFmt(255), "Minimal required niceness")
46                 rxOnly    = flag.Bool("rx", false, "Only receive packets")
47                 txOnly    = flag.Bool("tx", false, "Only transfer packets")
48                 mkdir     = flag.Bool("mkdir", false, "Create necessary outbound directories")
49                 keep      = flag.Bool("keep", false, "Do not delete transferred packets")
50                 spoolPath = flag.String("spool", "", "Override path to spool")
51                 logPath   = flag.String("log", "", "Override path to logfile")
52                 quiet     = flag.Bool("quiet", false, "Print only errors")
53                 showPrgrs = flag.Bool("progress", false, "Force progress showing")
54                 omitPrgrs = flag.Bool("noprogress", false, "Omit progress showing")
55                 debug     = flag.Bool("debug", false, "Print debug messages")
56                 version   = flag.Bool("version", false, "Print version information")
57                 warranty  = flag.Bool("warranty", false, "Print warranty information")
58         )
59         flag.Usage = usage
60         flag.Parse()
61         if *warranty {
62                 fmt.Println(nncp.Warranty)
63                 return
64         }
65         if *version {
66                 fmt.Println(nncp.VersionGet())
67                 return
68         }
69         if flag.NArg() != 1 {
70                 usage()
71                 os.Exit(1)
72         }
73         nice, err := nncp.NicenessParse(*niceRaw)
74         if err != nil {
75                 log.Fatalln(err)
76         }
77         if *rxOnly && *txOnly {
78                 log.Fatalln("-rx and -tx can not be set simultaneously")
79         }
80
81         ctx, err := nncp.CtxFromCmdline(
82                 *cfgPath,
83                 *spoolPath,
84                 *logPath,
85                 *quiet,
86                 *showPrgrs,
87                 *omitPrgrs,
88                 *debug,
89         )
90         if err != nil {
91                 log.Fatalln("Error during initialization:", err)
92         }
93
94         var nodeOnly *nncp.Node
95         if *nodeRaw != "" {
96                 nodeOnly, err = ctx.FindNode(*nodeRaw)
97                 if err != nil {
98                         log.Fatalln("Invalid -node specified:", err)
99                 }
100         }
101
102         ctx.Umask()
103         selfPath := filepath.Join(flag.Arg(0), ctx.SelfId.String())
104         isBad := false
105         var dir *os.File
106         var fis []os.FileInfo
107         var les nncp.LEs
108         if *txOnly {
109                 goto Tx
110         }
111         les = nncp.LEs{
112                 {K: "XX", V: string(nncp.TRx)},
113                 {K: "Dir", V: selfPath},
114         }
115         ctx.LogD("nncp-xfer", les, "self")
116         if _, err = os.Stat(selfPath); err != nil {
117                 if os.IsNotExist(err) {
118                         ctx.LogD("nncp-xfer", les, "no dir")
119                         goto Tx
120                 }
121                 ctx.LogE("nncp-xfer", les, err, "stat")
122                 isBad = true
123                 goto Tx
124         }
125         dir, err = os.Open(selfPath)
126         if err != nil {
127                 ctx.LogE("nncp-xfer", les, err, "open")
128                 isBad = true
129                 goto Tx
130         }
131         fis, err = dir.Readdir(0)
132         dir.Close() // #nosec G104
133         if err != nil {
134                 ctx.LogE("nncp-xfer", les, err, "read")
135                 isBad = true
136                 goto Tx
137         }
138         for _, fi := range fis {
139                 if !fi.IsDir() {
140                         continue
141                 }
142                 nodeId, err := nncp.NodeIdFromString(fi.Name())
143                 les := append(les, nncp.LE{K: "Node", V: fi.Name()})
144                 if err != nil {
145                         ctx.LogD("nncp-xfer", les, "is not NodeId")
146                         continue
147                 }
148                 if nodeOnly != nil && *nodeId != *nodeOnly.Id {
149                         ctx.LogD("nncp-xfer", les, "skip")
150                         continue
151                 }
152                 if _, known := ctx.Neigh[*nodeId]; !known {
153                         ctx.LogD("nncp-xfer", les, "unknown")
154                         continue
155                 }
156                 dir, err = os.Open(filepath.Join(selfPath, fi.Name()))
157                 if err != nil {
158                         ctx.LogE("nncp-xfer", les, err, "open")
159                         isBad = true
160                         continue
161                 }
162                 fisInt, err := dir.Readdir(0)
163                 dir.Close() // #nosec G104
164                 if err != nil {
165                         ctx.LogE("nncp-xfer", les, err, "read")
166                         isBad = true
167                         continue
168                 }
169                 for _, fiInt := range fisInt {
170                         if !fi.IsDir() {
171                                 continue
172                         }
173                         // Check that it is valid Base32 encoding
174                         if _, err = nncp.NodeIdFromString(fiInt.Name()); err != nil {
175                                 continue
176                         }
177                         filename := filepath.Join(dir.Name(), fiInt.Name())
178                         les := append(les, nncp.LE{K: "File", V: filename})
179                         fd, err := os.Open(filename)
180                         if err != nil {
181                                 ctx.LogE("nncp-xfer", les, err, "open")
182                                 isBad = true
183                                 continue
184                         }
185                         pktEnc, pktEncRaw, err := ctx.HdrRead(fd)
186                         if err != nil || pktEnc.Magic != nncp.MagicNNCPEv4 {
187                                 ctx.LogD("nncp-xfer", les, "is not a packet")
188                                 fd.Close() // #nosec G104
189                                 continue
190                         }
191                         if pktEnc.Nice > nice {
192                                 ctx.LogD("nncp-xfer", les, "too nice")
193                                 fd.Close() // #nosec G104
194                                 continue
195                         }
196                         les = append(les, nncp.LE{K: "Size", V: fiInt.Size()})
197                         if !ctx.IsEnoughSpace(fiInt.Size()) {
198                                 ctx.LogE("nncp-xfer", les, errors.New("is not enough space"), "")
199                                 fd.Close() // #nosec G104
200                                 continue
201                         }
202                         if _, err = fd.Seek(0, 0); err != nil {
203                                 log.Fatalln(err)
204                         }
205                         tmp, err := ctx.NewTmpFileWHash()
206                         if err != nil {
207                                 log.Fatalln(err)
208                         }
209                         r, w := io.Pipe()
210                         go func() {
211                                 _, err := io.CopyN(w, bufio.NewReader(fd), fiInt.Size())
212                                 if err == nil {
213                                         err = w.Close()
214                                 }
215                                 if err != nil {
216                                         ctx.LogE("nncp-xfer", les, err, "copy")
217                                         w.CloseWithError(err) // #nosec G104
218                                 }
219                         }()
220                         if _, err = nncp.CopyProgressed(
221                                 tmp.W, r, "Rx",
222                                 append(les, nncp.LEs{
223                                         {K: "Pkt", V: filename},
224                                         {K: "FullSize", V: fiInt.Size()},
225                                 }...),
226                                 ctx.ShowPrgrs,
227                         ); err != nil {
228                                 ctx.LogE("nncp-xfer", les, err, "copy")
229                                 isBad = true
230                         }
231                         fd.Close() // #nosec G104
232                         if isBad {
233                                 tmp.Cancel()
234                                 continue
235                         }
236                         if err = tmp.Commit(filepath.Join(
237                                 ctx.Spool,
238                                 nodeId.String(),
239                                 string(nncp.TRx),
240                         )); err != nil {
241                                 log.Fatalln(err)
242                         }
243                         ctx.LogI("nncp-xfer", les, "")
244                         if !*keep {
245                                 if err = os.Remove(filename); err != nil {
246                                         ctx.LogE("nncp-xfer", les, err, "remove")
247                                         isBad = true
248                                 }
249                         }
250                         if ctx.HdrUsage {
251                                 ctx.HdrWrite(pktEncRaw, filepath.Join(
252                                         ctx.Spool,
253                                         nodeId.String(),
254                                         string(nncp.TRx),
255                                         tmp.Checksum(),
256                                 ))
257                         }
258                 }
259         }
260
261 Tx:
262         if *rxOnly {
263                 if isBad {
264                         os.Exit(1)
265                 }
266                 return
267         }
268         for nodeId := range ctx.Neigh {
269                 les := nncp.LEs{
270                         {K: "XX", V: string(nncp.TTx)},
271                         {K: "Node", V: nodeId},
272                 }
273                 if nodeOnly != nil && nodeId != *nodeOnly.Id {
274                         ctx.LogD("nncp-xfer", les, "skip")
275                         continue
276                 }
277                 dirLock, err := ctx.LockDir(&nodeId, string(nncp.TTx))
278                 if err != nil {
279                         continue
280                 }
281                 nodePath := filepath.Join(flag.Arg(0), nodeId.String())
282                 les = append(les, nncp.LE{K: "Dir", V: nodePath})
283                 _, err = os.Stat(nodePath)
284                 if err != nil {
285                         if os.IsNotExist(err) {
286                                 ctx.LogD("nncp-xfer", les, "does not exist")
287                                 if !*mkdir {
288                                         ctx.UnlockDir(dirLock)
289                                         continue
290                                 }
291                                 if err = os.Mkdir(nodePath, os.FileMode(0777)); err != nil {
292                                         ctx.UnlockDir(dirLock)
293                                         ctx.LogE("nncp-xfer", les, err, "mkdir")
294                                         isBad = true
295                                         continue
296                                 }
297                         } else {
298                                 ctx.UnlockDir(dirLock)
299                                 ctx.LogE("nncp-xfer", les, err, "stat")
300                                 isBad = true
301                                 continue
302                         }
303                 }
304                 dstPath := filepath.Join(nodePath, ctx.SelfId.String())
305                 les[len(les)-1].V = dstPath
306                 _, err = os.Stat(dstPath)
307                 if err != nil {
308                         if os.IsNotExist(err) {
309                                 if err = os.Mkdir(dstPath, os.FileMode(0777)); err != nil {
310                                         ctx.UnlockDir(dirLock)
311                                         ctx.LogE("nncp-xfer", les, err, "mkdir")
312                                         isBad = true
313                                         continue
314                                 }
315                         } else {
316                                 ctx.UnlockDir(dirLock)
317                                 ctx.LogE("nncp-xfer", les, err, "stat")
318                                 isBad = true
319                                 continue
320                         }
321                 }
322                 les = les[:len(les)-1]
323                 for job := range ctx.Jobs(&nodeId, nncp.TTx) {
324                         pktName := filepath.Base(job.Path)
325                         les := append(les, nncp.LE{K: "Pkt", V: pktName})
326                         if job.PktEnc.Nice > nice {
327                                 ctx.LogD("nncp-xfer", les, "too nice")
328                                 continue
329                         }
330                         if _, err = os.Stat(filepath.Join(dstPath, pktName)); err == nil || !os.IsNotExist(err) {
331                                 ctx.LogD("nncp-xfer", les, "already exists")
332                                 continue
333                         }
334                         if _, err = os.Stat(filepath.Join(dstPath, pktName+nncp.SeenSuffix)); err == nil || !os.IsNotExist(err) {
335                                 ctx.LogD("nncp-xfer", les, "already exists")
336                                 continue
337                         }
338                         tmp, err := nncp.TempFile(dstPath, "xfer")
339                         if err != nil {
340                                 ctx.LogE("nncp-xfer", les, err, "mktemp")
341                                 isBad = true
342                                 break
343                         }
344                         les = append(les, nncp.LE{K: "Tmp", V: tmp.Name()})
345                         ctx.LogD("nncp-xfer", les, "created")
346                         fd, err := os.Open(job.Path)
347                         if err != nil {
348                                 ctx.LogE("nncp-xfer", les, err, "open")
349                                 tmp.Close() // #nosec G104
350                                 isBad = true
351                                 continue
352                         }
353                         bufW := bufio.NewWriter(tmp)
354                         copied, err := nncp.CopyProgressed(
355                                 bufW, bufio.NewReader(fd), "Tx",
356                                 append(les, nncp.LE{K: "FullSize", V: job.Size}),
357                                 ctx.ShowPrgrs,
358                         )
359                         fd.Close() // #nosec G104
360                         if err != nil {
361                                 ctx.LogE("nncp-xfer", les, err, "copy")
362                                 tmp.Close() // #nosec G104
363                                 isBad = true
364                                 continue
365                         }
366                         if err = bufW.Flush(); err != nil {
367                                 tmp.Close() // #nosec G104
368                                 ctx.LogE("nncp-xfer", les, err, "flush")
369                                 isBad = true
370                                 continue
371                         }
372                         if err = tmp.Sync(); err != nil {
373                                 tmp.Close() // #nosec G104
374                                 ctx.LogE("nncp-xfer", les, err, "sync")
375                                 isBad = true
376                                 continue
377                         }
378                         if err = tmp.Close(); err != nil {
379                                 ctx.LogE("nncp-xfer", les, err, "sync")
380                         }
381                         if err = os.Rename(tmp.Name(), filepath.Join(dstPath, pktName)); err != nil {
382                                 ctx.LogE("nncp-xfer", les, err, "rename")
383                                 isBad = true
384                                 continue
385                         }
386                         if err = nncp.DirSync(dstPath); err != nil {
387                                 ctx.LogE("nncp-xfer", les, err, "sync")
388                                 isBad = true
389                                 continue
390                         }
391                         os.Remove(filepath.Join(dstPath, pktName+".part")) // #nosec G104
392                         les = les[:len(les)-1]
393                         ctx.LogI("nncp-xfer", append(les, nncp.LE{K: "Size", V: copied}), "")
394                         if !*keep {
395                                 if err = os.Remove(job.Path); err != nil {
396                                         ctx.LogE("nncp-xfer", les, err, "remove")
397                                         isBad = true
398                                 } else if ctx.HdrUsage {
399                                         os.Remove(job.Path + nncp.HdrSuffix)
400                                 }
401                         }
402                 }
403                 ctx.UnlockDir(dirLock)
404         }
405         if isBad {
406                 os.Exit(1)
407         }
408 }