]> Cypherpunks.ru repositories - nncp.git/blob - src/cmd/nncp-rm/main.go
Refactored and tested nncp-rm
[nncp.git] / src / cmd / nncp-rm / main.go
1 /*
2 NNCP -- Node to Node copy, utilities for store-and-forward data exchange
3 Copyright (C) 2016-2022 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 // Remove packet from the queue.
19 package main
20
21 import (
22         "flag"
23         "fmt"
24         "io"
25         "log"
26         "os"
27         "path/filepath"
28         "regexp"
29         "strconv"
30         "strings"
31         "time"
32
33         "go.cypherpunks.ru/nncp/v8"
34 )
35
36 func usage() {
37         fmt.Fprintf(os.Stderr, nncp.UsageHeader())
38         fmt.Fprintf(os.Stderr, "nncp-rm -- remove packet\n\n")
39         fmt.Fprintf(os.Stderr, "Usage: %s [options] [-older X] -tmp\n", os.Args[0])
40         fmt.Fprintf(os.Stderr, "       %s [options] -lock\n", os.Args[0])
41         fmt.Fprintf(os.Stderr, "       %s [options] [-older X] {-all|-node NODE} -part\n", os.Args[0])
42         fmt.Fprintf(os.Stderr, "       %s [options] [-older X] {-all|-node NODE} -seen\n", os.Args[0])
43         fmt.Fprintf(os.Stderr, "       %s [options] [-older X] {-all|-node NODE} -nock\n", os.Args[0])
44         fmt.Fprintf(os.Stderr, "       %s [options] [-older X] {-all|-node NODE} -area\n", os.Args[0])
45         fmt.Fprintf(os.Stderr, "       %s [options] [-older X] {-all|-node NODE} {-rx|-tx} [-hdr]\n", os.Args[0])
46         fmt.Fprintf(os.Stderr, "       %s [options] [-older X] {-all|-node NODE} -pkt < ...\n", os.Args[0])
47         fmt.Fprintln(os.Stderr, "-older option's time units are: (s)econds, (m)inutes, (h)ours, (d)ays")
48         fmt.Fprintln(os.Stderr, "Options:")
49         flag.PrintDefaults()
50 }
51
52 func main() {
53         var (
54                 cfgPath   = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file")
55                 doTmp     = flag.Bool("tmp", false, "Remove temporary files")
56                 doLock    = flag.Bool("lock", false, "Remove all lock files")
57                 doAll     = flag.Bool("all", false, "Process all nodes")
58                 nodeRaw   = flag.String("node", "", "Process only that node")
59                 doRx      = flag.Bool("rx", false, "Process inbound packets")
60                 doTx      = flag.Bool("tx", false, "Process outbound packets")
61                 doPart    = flag.Bool("part", false, "Remove only .part files")
62                 doSeen    = flag.Bool("seen", false, "Remove only seen/ files")
63                 doNoCK    = flag.Bool("nock", false, "Remove only .nock files")
64                 doHdr     = flag.Bool("hdr", false, "Remove only hdr/ files")
65                 doArea    = flag.Bool("area", false, "Remove only area/* seen files")
66                 older     = flag.String("older", "", "XXX{smhd}: only older than XXX number of time units")
67                 dryRun    = flag.Bool("dryrun", false, "Do not actually remove files")
68                 doPkt     = flag.Bool("pkt", false, "Remove only that packets")
69                 spoolPath = flag.String("spool", "", "Override path to spool")
70                 quiet     = flag.Bool("quiet", false, "Print only errors")
71                 debug     = flag.Bool("debug", false, "Print debug messages")
72                 version   = flag.Bool("version", false, "Print version information")
73                 warranty  = flag.Bool("warranty", false, "Print warranty information")
74         )
75         log.SetFlags(log.Lshortfile)
76         flag.Usage = usage
77         flag.Parse()
78         if *warranty {
79                 fmt.Println(nncp.Warranty)
80                 return
81         }
82         if *version {
83                 fmt.Println(nncp.VersionGet())
84                 return
85         }
86
87         ctx, err := nncp.CtxFromCmdline(*cfgPath, *spoolPath, "", *quiet, false, false, *debug)
88         if err != nil {
89                 log.Fatalln("Error during initialization:", err)
90         }
91         ctx.Umask()
92
93         var oldBoundaryRaw int
94         if *older != "" {
95                 olderRe := regexp.MustCompile(`^(\d+)([smhd])$`)
96                 matches := olderRe.FindStringSubmatch(*older)
97                 if len(matches) != 1+2 {
98                         log.Fatalln("can not parse -older")
99                 }
100                 oldBoundaryRaw, err = strconv.Atoi(matches[1])
101                 if err != nil {
102                         log.Fatalln("can not parse -older:", err)
103                 }
104                 switch matches[2] {
105                 case "s":
106                         break
107                 case "m":
108                         oldBoundaryRaw *= 60
109                 case "h":
110                         oldBoundaryRaw *= 60 * 60
111                 case "d":
112                         oldBoundaryRaw *= 60 * 60 * 24
113                 }
114         }
115         oldBoundary := time.Second * time.Duration(oldBoundaryRaw)
116
117         pkts := make(map[string]struct{})
118         if *doPkt {
119                 raw, err := io.ReadAll(os.Stdin)
120                 if err != nil {
121                         log.Fatalln("can not read -pkt from stdin:", err)
122                 }
123                 for _, line := range strings.Fields(string(raw)) {
124                         if len(line) == 0 {
125                                 continue
126                         }
127                         cols := strings.Split(line, "/")
128                         pkts[cols[len(cols)-1]] = struct{}{}
129                 }
130         }
131
132         now := time.Now()
133         if *doTmp {
134                 err = filepath.Walk(
135                         filepath.Join(ctx.Spool, "tmp"),
136                         func(path string, info os.FileInfo, err error) error {
137                                 if err != nil {
138                                         return err
139                                 }
140                                 if info.IsDir() {
141                                         return nil
142                                 }
143                                 if now.Sub(info.ModTime()) < oldBoundary {
144                                         ctx.LogD("rm-skip", nncp.LEs{{K: "File", V: path}}, func(les nncp.LEs) string {
145                                                 return fmt.Sprintf("File %s: too fresh, skipping", path)
146                                         })
147                                         return nil
148                                 }
149                                 ctx.LogI("rm", nncp.LEs{{K: "File", V: path}}, func(les nncp.LEs) string {
150                                         return fmt.Sprintf("File %s: removed", path)
151                                 })
152                                 if *dryRun {
153                                         return nil
154                                 }
155                                 return os.Remove(path)
156                         })
157                 if err != nil {
158                         log.Fatalln("Error during walking:", err)
159                 }
160                 return
161         }
162
163         if *doLock {
164                 err = filepath.Walk(ctx.Spool, func(path string, info os.FileInfo, err error) error {
165                         if err != nil {
166                                 return err
167                         }
168                         if info.IsDir() {
169                                 return nil
170                         }
171                         if strings.HasSuffix(info.Name(), ".lock") {
172                                 ctx.LogI("rm", nncp.LEs{{K: "File", V: path}}, func(les nncp.LEs) string {
173                                         return fmt.Sprintf("File %s: removed", path)
174                                 })
175                                 if *dryRun {
176                                         return nil
177                                 }
178                                 return os.Remove(path)
179                         }
180                         return nil
181                 })
182                 if err != nil {
183                         log.Fatalln("Error during walking:", err)
184                 }
185                 return
186         }
187
188         var nodeId *nncp.NodeId
189         if *nodeRaw == "" {
190                 if !*doAll {
191                         usage()
192                         os.Exit(1)
193                 }
194         } else {
195                 node, err := ctx.FindNode(*nodeRaw)
196                 if err != nil {
197                         log.Fatalln("Invalid -node specified:", err)
198                 }
199                 nodeId = node.Id
200         }
201
202         for _, node := range ctx.Neigh {
203                 if nodeId != nil && node.Id != nodeId {
204                         continue
205                 }
206                 remove := func(xx nncp.TRxTx) error {
207                         p := filepath.Join(ctx.Spool, node.Id.String(), string(xx))
208                         if _, err := os.Stat(p); err != nil && os.IsNotExist(err) {
209                                 return nil
210                         }
211                         dir, err := os.Open(p)
212                         if err != nil {
213                                 if os.IsNotExist(err) {
214                                         return nil
215                                 }
216                                 return err
217                         }
218                         defer dir.Close()
219                         for {
220                                 entries, err := dir.ReadDir(1 << 10)
221                                 if err != nil {
222                                         if err == io.EOF {
223                                                 break
224                                         }
225                                         return err
226                                 }
227                                 for _, entry := range entries {
228                                         if entry.IsDir() {
229                                                 continue
230                                         }
231                                         pth := filepath.Join(p, entry.Name())
232                                         logMsg := func(les nncp.LEs) string {
233                                                 return fmt.Sprintf("File %s: removed", pth)
234                                         }
235                                         if len(pkts) > 0 {
236                                                 if _, exists := pkts[filepath.Base(entry.Name())]; exists {
237                                                         ctx.LogI("rm", nncp.LEs{{K: "File", V: pth}}, logMsg)
238                                                         if !*dryRun {
239                                                                 os.Remove(nncp.JobPath2Hdr(pth))
240                                                                 if err = os.Remove(pth); err != nil {
241                                                                         return err
242                                                                 }
243                                                         }
244                                                 }
245                                                 continue
246                                         }
247                                         info, err := entry.Info()
248                                         if err != nil {
249                                                 return err
250                                         }
251                                         if now.Sub(info.ModTime()) < oldBoundary {
252                                                 ctx.LogD("rm-skip", nncp.LEs{{K: "File", V: pth}}, func(les nncp.LEs) string {
253                                                         return fmt.Sprintf("File %s: too fresh, skipping", pth)
254                                                 })
255                                                 continue
256                                         }
257                                         if (*doNoCK && strings.HasSuffix(entry.Name(), nncp.NoCKSuffix)) ||
258                                                 (*doPart && strings.HasSuffix(entry.Name(), nncp.PartSuffix)) {
259                                                 ctx.LogI("rm", nncp.LEs{{K: "File", V: pth}}, logMsg)
260                                                 if *dryRun {
261                                                         continue
262                                                 }
263                                                 if err = os.Remove(pth); err != nil {
264                                                         return err
265                                                 }
266                                         }
267                                         if !*doSeen && !*doNoCK && !*doHdr && !*doPart &&
268                                                 (*doRx || *doTx) &&
269                                                 ((*doRx && xx == nncp.TRx) || (*doTx && xx == nncp.TTx)) {
270                                                 ctx.LogI("rm", nncp.LEs{{K: "File", V: pth}}, logMsg)
271                                                 if *dryRun {
272                                                         continue
273                                                 }
274                                                 os.Remove(nncp.JobPath2Hdr(pth))
275                                                 if err = os.Remove(pth); err != nil {
276                                                         return err
277                                                 }
278                                         }
279                                 }
280                         }
281                         return nil
282                 }
283                 if len(pkts) > 0 || *doRx || *doNoCK || *doPart {
284                         if err = remove(nncp.TRx); err != nil {
285                                 log.Fatalln("Can not remove:", err)
286                         }
287                 }
288                 if len(pkts) > 0 || *doTx {
289                         if err = remove(nncp.TTx); err != nil {
290                                 log.Fatalln("Can not remove:", err)
291                         }
292                 }
293                 removeSub := func(p string) error {
294                         return filepath.Walk(p, func(path string, info os.FileInfo, err error) error {
295                                 if err != nil {
296                                         if os.IsNotExist(err) {
297                                                 return nil
298                                         }
299                                         return err
300                                 }
301                                 if info.IsDir() {
302                                         return nil
303                                 }
304                                 logMsg := func(les nncp.LEs) string {
305                                         return fmt.Sprintf("File %s: removed", path)
306                                 }
307                                 if len(pkts) > 0 {
308                                         if _, exists := pkts[filepath.Base(info.Name())]; !exists {
309                                                 return nil
310                                         }
311                                         ctx.LogI("rm", nncp.LEs{{K: "File", V: path}}, logMsg)
312                                         if *dryRun {
313                                                 return nil
314                                         }
315                                         return os.Remove(path)
316                                 }
317                                 if now.Sub(info.ModTime()) < oldBoundary {
318                                         ctx.LogD(
319                                                 "rm-skip", nncp.LEs{{K: "File", V: path}},
320                                                 func(les nncp.LEs) string {
321                                                         return fmt.Sprintf("File %s: too fresh, skipping", path)
322                                                 },
323                                         )
324                                         return nil
325                                 }
326                                 ctx.LogI("rm", nncp.LEs{{K: "File", V: path}}, logMsg)
327                                 if *dryRun {
328                                         return nil
329                                 }
330                                 return os.Remove(path)
331                         })
332                 }
333                 if len(pkts) > 0 || *doSeen {
334                         if err = removeSub(filepath.Join(
335                                 ctx.Spool, node.Id.String(), string(nncp.TRx), nncp.SeenDir,
336                         )); err != nil {
337                                 log.Fatalln("Can not remove:", err)
338                         }
339                 }
340                 if *doRx && *doHdr {
341                         if err = removeSub(filepath.Join(
342                                 ctx.Spool, node.Id.String(), string(nncp.TRx), nncp.HdrDir,
343                         )); err != nil {
344                                 log.Fatalln("Can not remove:", err)
345                         }
346                 }
347                 if *doTx && *doHdr {
348                         if err = removeSub(filepath.Join(
349                                 ctx.Spool, node.Id.String(), string(nncp.TTx), nncp.HdrDir,
350                         )); err != nil {
351                                 log.Fatalln("Can not remove:", err)
352                         }
353                 }
354                 if *doArea {
355                         if err = filepath.Walk(
356                                 filepath.Join(ctx.Spool, node.Id.String(), nncp.AreaDir),
357                                 func(path string, info os.FileInfo, err error) error {
358                                         if err != nil {
359                                                 if os.IsNotExist(err) {
360                                                         return nil
361                                                 }
362                                                 return err
363                                         }
364                                         if info.IsDir() {
365                                                 return nil
366                                         }
367                                         if now.Sub(info.ModTime()) < oldBoundary {
368                                                 ctx.LogD(
369                                                         "rm-skip", nncp.LEs{{K: "File", V: path}},
370                                                         func(les nncp.LEs) string {
371                                                                 return fmt.Sprintf("File %s: too fresh, skipping", path)
372                                                         },
373                                                 )
374                                                 return nil
375                                         }
376                                         ctx.LogI(
377                                                 "rm",
378                                                 nncp.LEs{{K: "File", V: path}},
379                                                 func(les nncp.LEs) string {
380                                                         return fmt.Sprintf("File %s: removed", path)
381                                                 },
382                                         )
383                                         if *dryRun {
384                                                 return nil
385                                         }
386                                         return os.Remove(path)
387                                 }); err != nil {
388                                 log.Fatalln("Can not remove:", err)
389                         }
390                 }
391         }
392 }