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