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