2 NNCP -- Node to Node copy, utilities for store-and-forward data exchange
3 Copyright (C) 2016-2021 Sergey Matveev <stargrave@stargrave.org>
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.
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.
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/>.
38 xdr "github.com/davecgh/go-xdr/xdr2"
39 "github.com/dustin/go-humanize"
40 "github.com/klauspost/compress/zstd"
41 "golang.org/x/crypto/blake2b"
42 "golang.org/x/crypto/poly1305"
49 func newNotification(fromTo *FromToJSON, subject string, body []byte) io.Reader {
51 "From: " + fromTo.From,
53 "Subject: " + mime.BEncoding.Encode("UTF-8", subject),
59 "Content-Type: text/plain; charset=utf-8",
60 "Content-Transfer-Encoding: base64",
62 base64.StdEncoding.EncodeToString(body),
65 return strings.NewReader(strings.Join(lines, "\n"))
68 func pktSizeWithoutEnc(pktSize int64) int64 {
69 pktSize = pktSize - PktEncOverhead - PktOverhead - PktSizeOverhead
70 pktSizeBlocks := pktSize / (EncBlkSize + poly1305.TagSize)
71 if pktSize%(EncBlkSize+poly1305.TagSize) != 0 {
72 pktSize -= poly1305.TagSize
74 pktSize -= pktSizeBlocks * poly1305.TagSize
78 var JobRepeatProcess = errors.New("needs processing repeat")
89 decompressor *zstd.Decoder,
90 dryRun, doSeen, noFile, noFreq, noExec, noTrns, noArea bool,
93 sendmail := ctx.Neigh[*ctx.SelfId].Exec["sendmail"]
95 _, err := xdr.Unmarshal(pipeR, &pkt)
97 ctx.LogE("rx-unmarshal", les, err, func(les LEs) string {
98 return fmt.Sprintf("Tossing %s/%s: unmarshal", sender.Name, pktName)
102 les = append(les, LE{"Size", int64(pktSize)})
103 ctx.LogD("rx", les, func(les LEs) string {
105 "Tossing %s/%s (%s)",
106 sender.Name, pktName,
107 humanize.IBytes(pktSize),
111 case PktTypeExec, PktTypeExecFat:
115 path := bytes.Split(pkt.Path[:int(pkt.PathLen)], []byte{0})
116 handle := string(path[0])
117 args := make([]string, 0, len(path)-1)
118 for _, p := range path[1:] {
119 args = append(args, string(p))
121 argsStr := strings.Join(append([]string{handle}, args...), " ")
122 les = append(les, LE{"Type", "exec"}, LE{"Dst", argsStr})
123 cmdline := sender.Exec[handle]
124 if len(cmdline) == 0 {
125 err = errors.New("No handle found")
127 "rx-no-handle", les, err,
128 func(les LEs) string {
130 "Tossing exec %s/%s (%s): %s",
131 sender.Name, pktName,
132 humanize.IBytes(pktSize), argsStr,
138 if pkt.Type == PktTypeExec {
139 if err = decompressor.Reset(pipeR); err != nil {
144 cmd := exec.Command(cmdline[0], append(cmdline[1:], args...)...)
147 "NNCP_SELF="+ctx.Self.Id.String(),
148 "NNCP_SENDER="+sender.Id.String(),
149 "NNCP_NICE="+strconv.Itoa(int(pkt.Nice)),
151 if pkt.Type == PktTypeExec {
152 cmd.Stdin = decompressor
156 output, err := cmd.Output()
158 ctx.LogE("rx-hande", les, err, func(les LEs) string {
160 "Tossing exec %s/%s (%s): %s: handling",
161 sender.Name, pktName,
162 humanize.IBytes(uint64(pktSize)), argsStr,
167 if len(sendmail) > 0 && ctx.NotifyExec != nil {
168 notify := ctx.NotifyExec[sender.Name+"."+handle]
170 notify = ctx.NotifyExec["*."+handle]
175 append(sendmail[1:], notify.To)...,
177 cmd.Stdin = newNotification(notify, fmt.Sprintf(
178 "Exec from %s: %s", sender.Name, argsStr,
180 if err = cmd.Run(); err != nil {
181 ctx.LogE("rx-notify", les, err, func(les LEs) string {
183 "Tossing exec %s/%s (%s): %s: notifying",
184 sender.Name, pktName,
185 humanize.IBytes(pktSize), argsStr,
192 ctx.LogI("rx", les, func(les LEs) string {
194 "Got exec from %s to %s (%s)",
195 sender.Name, argsStr,
196 humanize.IBytes(pktSize),
199 if !dryRun && jobPath != "" {
201 if fd, err := os.Create(jobPath + SeenSuffix); err == nil {
203 if err = DirSync(filepath.Dir(jobPath)); err != nil {
204 ctx.LogE("rx-dirsync", les, err, func(les LEs) string {
206 "Tossing file %s/%s (%s): %s: dirsyncing",
207 sender.Name, pktName,
208 humanize.IBytes(pktSize),
209 filepath.Base(jobPath),
216 if err = os.Remove(jobPath); err != nil {
217 ctx.LogE("rx-notify", les, err, func(les LEs) string {
219 "Tossing exec %s/%s (%s): %s: notifying",
220 sender.Name, pktName,
221 humanize.IBytes(pktSize), argsStr,
225 } else if ctx.HdrUsage {
226 os.Remove(jobPath + HdrSuffix)
234 dst := string(pkt.Path[:int(pkt.PathLen)])
235 les = append(les, LE{"Type", "file"}, LE{"Dst", dst})
236 if filepath.IsAbs(dst) {
237 err = errors.New("non-relative destination path")
239 "rx-non-rel", les, err,
240 func(les LEs) string {
242 "Tossing file %s/%s (%s): %s",
243 sender.Name, pktName,
244 humanize.IBytes(pktSize), dst,
250 incoming := sender.Incoming
252 err = errors.New("incoming is not allowed")
254 "rx-no-incoming", les, err,
255 func(les LEs) string {
257 "Tossing file %s/%s (%s): %s",
258 sender.Name, pktName,
259 humanize.IBytes(pktSize), dst,
265 dir := filepath.Join(*incoming, path.Dir(dst))
266 if err = os.MkdirAll(dir, os.FileMode(0777)); err != nil {
267 ctx.LogE("rx-mkdir", les, err, func(les LEs) string {
269 "Tossing file %s/%s (%s): %s: mkdir",
270 sender.Name, pktName,
271 humanize.IBytes(pktSize), dst,
277 tmp, err := TempFile(dir, "file")
279 ctx.LogE("rx-mktemp", les, err, func(les LEs) string {
281 "Tossing file %s/%s (%s): %s: mktemp",
282 sender.Name, pktName,
283 humanize.IBytes(pktSize), dst,
288 les = append(les, LE{"Tmp", tmp.Name()})
289 ctx.LogD("rx-tmp-created", les, func(les LEs) string {
291 "Tossing file %s/%s (%s): %s: created: %s",
292 sender.Name, pktName,
293 humanize.IBytes(pktSize), dst, tmp.Name(),
296 bufW := bufio.NewWriter(tmp)
297 if _, err = CopyProgressed(
298 bufW, pipeR, "Rx file",
299 append(les, LE{"FullSize", int64(pktSize)}),
302 ctx.LogE("rx-copy", les, err, func(les LEs) string {
304 "Tossing file %s/%s (%s): %s: copying",
305 sender.Name, pktName,
306 humanize.IBytes(pktSize), dst,
311 if err = bufW.Flush(); err != nil {
312 tmp.Close() // #nosec G104
313 ctx.LogE("rx-flush", les, err, func(les LEs) string {
315 "Tossing file %s/%s (%s): %s: flushing",
316 sender.Name, pktName,
317 humanize.IBytes(pktSize), dst,
322 if err = tmp.Sync(); err != nil {
323 tmp.Close() // #nosec G104
324 ctx.LogE("rx-sync", les, err, func(les LEs) string {
326 "Tossing file %s/%s (%s): %s: syncing",
327 sender.Name, pktName,
328 humanize.IBytes(pktSize), dst,
333 if err = tmp.Close(); err != nil {
334 ctx.LogE("rx-close", les, err, func(les LEs) string {
336 "Tossing file %s/%s (%s): %s: closing",
337 sender.Name, pktName,
338 humanize.IBytes(pktSize), dst,
343 dstPathOrig := filepath.Join(*incoming, dst)
344 dstPath := dstPathOrig
347 if _, err = os.Stat(dstPath); err != nil {
348 if os.IsNotExist(err) {
351 ctx.LogE("rx-stat", les, err, func(les LEs) string {
353 "Tossing file %s/%s (%s): %s: stating: %s",
354 sender.Name, pktName,
355 humanize.IBytes(pktSize), dst, dstPath,
360 dstPath = dstPathOrig + "." + strconv.Itoa(dstPathCtr)
363 if err = os.Rename(tmp.Name(), dstPath); err != nil {
364 ctx.LogE("rx-rename", les, err, func(les LEs) string {
366 "Tossing file %s/%s (%s): %s: renaming",
367 sender.Name, pktName,
368 humanize.IBytes(pktSize), dst,
373 if err = DirSync(*incoming); err != nil {
374 ctx.LogE("rx-dirsync", les, err, func(les LEs) string {
376 "Tossing file %s/%s (%s): %s: dirsyncing",
377 sender.Name, pktName,
378 humanize.IBytes(pktSize), dst,
383 les = les[:len(les)-1] // delete Tmp
385 ctx.LogI("rx", les, func(les LEs) string {
387 "Got file %s (%s) from %s",
388 dst, humanize.IBytes(pktSize), sender.Name,
394 if fd, err := os.Create(jobPath + SeenSuffix); err == nil {
396 if err = DirSync(filepath.Dir(jobPath)); err != nil {
397 ctx.LogE("rx-dirsync", les, err, func(les LEs) string {
399 "Tossing file %s/%s (%s): %s: dirsyncing",
400 sender.Name, pktName,
401 humanize.IBytes(pktSize),
402 filepath.Base(jobPath),
409 if err = os.Remove(jobPath); err != nil {
410 ctx.LogE("rx-remove", les, err, func(les LEs) string {
412 "Tossing file %s/%s (%s): %s: removing",
413 sender.Name, pktName,
414 humanize.IBytes(pktSize), dst,
418 } else if ctx.HdrUsage {
419 os.Remove(jobPath + HdrSuffix)
422 if len(sendmail) > 0 && ctx.NotifyFile != nil {
425 append(sendmail[1:], ctx.NotifyFile.To)...,
427 cmd.Stdin = newNotification(ctx.NotifyFile, fmt.Sprintf(
428 "File from %s: %s (%s)",
429 sender.Name, dst, humanize.IBytes(pktSize),
431 if err = cmd.Run(); err != nil {
432 ctx.LogE("rx-notify", les, err, func(les LEs) string {
434 "Tossing file %s/%s (%s): %s: notifying",
435 sender.Name, pktName,
436 humanize.IBytes(pktSize), dst,
447 src := string(pkt.Path[:int(pkt.PathLen)])
448 les := append(les, LE{"Type", "freq"}, LE{"Src", src})
449 if filepath.IsAbs(src) {
450 err = errors.New("non-relative source path")
452 "rx-non-rel", les, err,
453 func(les LEs) string {
455 "Tossing freq %s/%s (%s): %s: notifying",
456 sender.Name, pktName,
457 humanize.IBytes(pktSize), src,
463 dstRaw, err := ioutil.ReadAll(pipeR)
465 ctx.LogE("rx-read", les, err, func(les LEs) string {
467 "Tossing freq %s/%s (%s): %s: reading",
468 sender.Name, pktName,
469 humanize.IBytes(pktSize), src,
474 dst := string(dstRaw)
475 les = append(les, LE{"Dst", dst})
476 freqPath := sender.FreqPath
478 err = errors.New("freqing is not allowed")
480 "rx-no-freq", les, err,
481 func(les LEs) string {
483 "Tossing freq %s/%s (%s): %s -> %s",
484 sender.Name, pktName,
485 humanize.IBytes(pktSize), src, dst,
495 filepath.Join(*freqPath, src),
503 ctx.LogE("rx-tx", les, err, func(les LEs) string {
505 "Tossing freq %s/%s (%s): %s -> %s: txing",
506 sender.Name, pktName,
507 humanize.IBytes(pktSize), src, dst,
513 ctx.LogI("rx", les, func(les LEs) string {
514 return fmt.Sprintf("Got file request %s to %s", src, sender.Name)
519 if fd, err := os.Create(jobPath + SeenSuffix); err == nil {
521 if err = DirSync(filepath.Dir(jobPath)); err != nil {
522 ctx.LogE("rx-dirsync", les, err, func(les LEs) string {
524 "Tossing file %s/%s (%s): %s: dirsyncing",
525 sender.Name, pktName,
526 humanize.IBytes(pktSize),
527 filepath.Base(jobPath),
534 if err = os.Remove(jobPath); err != nil {
535 ctx.LogE("rx-remove", les, err, func(les LEs) string {
537 "Tossing freq %s/%s (%s): %s -> %s: removing",
538 sender.Name, pktName,
539 humanize.IBytes(pktSize), src, dst,
543 } else if ctx.HdrUsage {
544 os.Remove(jobPath + HdrSuffix)
547 if len(sendmail) > 0 && ctx.NotifyFreq != nil {
550 append(sendmail[1:], ctx.NotifyFreq.To)...,
552 cmd.Stdin = newNotification(ctx.NotifyFreq, fmt.Sprintf(
553 "Freq from %s: %s", sender.Name, src,
555 if err = cmd.Run(); err != nil {
556 ctx.LogE("rx-notify", les, err, func(les LEs) string {
558 "Tossing freq %s/%s (%s): %s -> %s: notifying",
559 sender.Name, pktName,
560 humanize.IBytes(pktSize), src, dst,
571 dst := new([MTHSize]byte)
572 copy(dst[:], pkt.Path[:int(pkt.PathLen)])
573 nodeId := NodeId(*dst)
574 les := append(les, LE{"Type", "trns"}, LE{"Dst", nodeId})
575 logMsg := func(les LEs) string {
577 "Tossing trns %s/%s (%s): %s",
578 sender.Name, pktName,
579 humanize.IBytes(pktSize),
583 node := ctx.Neigh[nodeId]
585 err = errors.New("unknown node")
586 ctx.LogE("rx-unknown", les, err, logMsg)
589 ctx.LogD("rx-tx", les, logMsg)
591 if len(node.Via) == 0 {
592 if err = ctx.TxTrns(node, nice, int64(pktSize), pipeR); err != nil {
593 ctx.LogE("rx", les, err, func(les LEs) string {
594 return logMsg(les) + ": txing"
599 via := node.Via[:len(node.Via)-1]
600 node = ctx.Neigh[*node.Via[len(node.Via)-1]]
601 node = &Node{Id: node.Id, Via: via, ExchPub: node.ExchPub}
602 pktTrns, err := NewPkt(PktTypeTrns, 0, nodeId[:])
615 ctx.LogE("rx", les, err, func(les LEs) string {
616 return logMsg(les) + ": txing"
622 ctx.LogI("rx", les, func(les LEs) string {
624 "Got transitional packet from %s to %s (%s)",
626 ctx.NodeName(&nodeId),
627 humanize.IBytes(pktSize),
630 if !dryRun && jobPath != "" {
632 if fd, err := os.Create(jobPath + SeenSuffix); err == nil {
634 if err = DirSync(filepath.Dir(jobPath)); err != nil {
635 ctx.LogE("rx-dirsync", les, err, func(les LEs) string {
637 "Tossing file %s/%s (%s): %s: dirsyncing",
638 sender.Name, pktName,
639 humanize.IBytes(pktSize),
640 filepath.Base(jobPath),
647 if err = os.Remove(jobPath); err != nil {
648 ctx.LogE("rx", les, err, func(les LEs) string {
650 "Tossing trns %s/%s (%s): %s: removing",
651 sender.Name, pktName,
652 humanize.IBytes(pktSize),
653 ctx.NodeName(&nodeId),
657 } else if ctx.HdrUsage {
658 os.Remove(jobPath + HdrSuffix)
666 areaId := new(AreaId)
667 copy(areaId[:], pkt.Path[:int(pkt.PathLen)])
668 les := append(les, LE{"Type", "area"}, LE{"Area", areaId})
669 logMsg := func(les LEs) string {
671 "Tossing %s/%s (%s): area %s",
672 sender.Name, pktName,
673 humanize.IBytes(pktSize),
674 ctx.AreaName(areaId),
677 area := ctx.AreaId2Area[*areaId]
679 err = errors.New("unknown area")
680 ctx.LogE("rx-area-unknown", les, err, logMsg)
683 pktEnc, pktEncRaw, err := ctx.HdrRead(pipeR)
684 fullPipeR := io.MultiReader(bytes.NewReader(pktEncRaw), pipeR)
686 ctx.LogE("rx-area-pkt-enc-read", les, err, logMsg)
689 msgHashRaw := blake2b.Sum256(pktEncRaw)
690 msgHash := Base32Codec.EncodeToString(msgHashRaw[:])
691 les = append(les, LE{"AreaMsg", msgHash})
692 ctx.LogD("rx-area", les, logMsg)
695 for _, nodeId := range area.Subs {
696 node := ctx.Neigh[*nodeId]
697 lesEcho := append(les, LE{"Echo", nodeId})
698 seenDir := filepath.Join(
699 ctx.Spool, nodeId.String(), AreaDir, area.Id.String(),
701 seenPath := filepath.Join(seenDir, msgHash+SeenSuffix)
702 logMsgNode := func(les LEs) string {
704 "%s: echoing to: %s", logMsg(les), node.Name,
707 if _, err := os.Stat(seenPath); err == nil {
708 ctx.LogD("rx-area-echo-seen", lesEcho, func(les LEs) string {
709 return logMsgNode(les) + ": already sent"
713 ctx.LogI("rx-area-echo", lesEcho, logMsgNode)
716 for _, nodeId := range area.Subs {
717 node := ctx.Neigh[*nodeId]
718 lesEcho := append(les, LE{"Echo", nodeId})
719 seenDir := filepath.Join(
720 ctx.Spool, nodeId.String(), AreaDir, area.Id.String(),
722 seenPath := filepath.Join(seenDir, msgHash+SeenSuffix)
723 logMsgNode := func(les LEs) string {
724 return fmt.Sprintf("%s: echo to: %s", logMsg(les), node.Name)
726 if _, err := os.Stat(seenPath); err == nil {
727 ctx.LogD("rx-area-echo-seen", lesEcho, func(les LEs) string {
728 return logMsgNode(les) + ": already sent"
732 if nodeId != sender.Id && nodeId != pktEnc.Sender {
733 ctx.LogI("rx-area-echo", lesEcho, logMsgNode)
735 node, &pkt, nice, int64(pktSize), 0, fullPipeR, pktName, nil,
737 ctx.LogE("rx-area", lesEcho, err, logMsgNode)
741 if err = os.MkdirAll(seenDir, os.FileMode(0777)); err != nil {
742 ctx.LogE("rx-area-mkdir", lesEcho, err, logMsgNode)
745 if fd, err := os.Create(seenPath); err == nil {
747 if err = DirSync(seenDir); err != nil {
748 ctx.LogE("rx-area-dirsync", les, err, logMsgNode)
752 ctx.LogE("rx-area-touch", lesEcho, err, logMsgNode)
755 return JobRepeatProcess
759 seenDir := filepath.Join(
760 ctx.Spool, ctx.SelfId.String(), AreaDir, area.Id.String(),
762 seenPath := filepath.Join(seenDir, msgHash+SeenSuffix)
763 if _, err := os.Stat(seenPath); err == nil {
764 ctx.LogD("rx-area-seen", les, func(les LEs) string {
765 return logMsg(les) + ": already seen"
767 if !dryRun && jobPath != "" {
768 if err = os.Remove(jobPath); err != nil {
769 ctx.LogE("rx-area-remove", les, err, func(les LEs) string {
771 "Tossing area %s/%s (%s): %s: removing",
772 sender.Name, pktName,
773 humanize.IBytes(pktSize),
778 } else if ctx.HdrUsage {
779 os.Remove(jobPath + HdrSuffix)
786 ctx.LogD("rx-area-no-prv", les, func(les LEs) string {
787 return logMsg(les) + ": no private key for decoding"
790 signatureVerify := true
791 if _, senderKnown := ctx.Neigh[*pktEnc.Sender]; !senderKnown {
792 if !area.AllowUnknown {
793 err = errors.New("unknown sender")
796 append(les, LE{"Sender", pktEnc.Sender}),
798 func(les LEs) string {
799 return logMsg(les) + ": sender: " + pktEnc.Sender.String()
804 signatureVerify = false
806 areaNodeOur := NodeOur{Id: new(NodeId), ExchPrv: new([32]byte)}
807 copy(areaNodeOur.Id[:], area.Id[:])
808 copy(areaNodeOur.ExchPrv[:], area.Prv[:])
812 Incoming: area.Incoming,
815 copy(areaNode.Id[:], area.Id[:])
816 pktName := fmt.Sprintf(
818 Base32Codec.EncodeToString(areaId[:]), msgHash,
821 pipeR, pipeW := io.Pipe()
822 errs := make(chan error, 1)
831 uint64(pktSizeWithoutEnc(int64(pktSize))),
834 dryRun, doSeen, noFile, noFreq, noExec, noTrns, noArea,
837 _, _, _, err = PktEncRead(
846 pipeW.CloseWithError(err)
851 if err = <-errs; err != nil {
856 if !dryRun && jobPath != "" {
857 if err = os.MkdirAll(seenDir, os.FileMode(0777)); err != nil {
858 ctx.LogE("rx-area-mkdir", les, err, logMsg)
861 if fd, err := os.Create(seenPath); err == nil {
863 if err = DirSync(seenDir); err != nil {
864 ctx.LogE("rx-area-dirsync", les, err, logMsg)
868 if err = os.Remove(jobPath); err != nil {
869 ctx.LogE("rx", les, err, func(les LEs) string {
871 "Tossing area %s/%s (%s): %s: removing",
872 sender.Name, pktName,
873 humanize.IBytes(pktSize),
878 } else if ctx.HdrUsage {
879 os.Remove(jobPath + HdrSuffix)
884 err = errors.New("unknown type")
886 "rx-type-unknown", les, err,
887 func(les LEs) string {
889 "Tossing %s/%s (%s)",
890 sender.Name, pktName, humanize.IBytes(pktSize),
899 func (ctx *Ctx) Toss(
903 dryRun, doSeen, noFile, noFreq, noExec, noTrns, noArea bool,
905 dirLock, err := ctx.LockDir(nodeId, "toss")
909 defer ctx.UnlockDir(dirLock)
911 decompressor, err := zstd.NewReader(nil)
915 defer decompressor.Close()
916 for job := range ctx.Jobs(nodeId, xx) {
917 pktName := filepath.Base(job.Path)
919 {"Node", job.PktEnc.Sender},
921 {"Nice", int(job.PktEnc.Nice)},
923 if job.PktEnc.Nice > nice {
924 ctx.LogD("rx-too-nice", les, func(les LEs) string {
926 "Tossing %s/%s: too nice: %s",
927 ctx.NodeName(job.PktEnc.Sender), pktName,
928 NicenessFmt(job.PktEnc.Nice),
933 fd, err := os.Open(job.Path)
935 ctx.LogE("rx-open", les, err, func(les LEs) string {
937 "Tossing %s/%s: opening %s",
938 ctx.NodeName(job.PktEnc.Sender), pktName, job.Path,
944 sender := ctx.Neigh[*job.PktEnc.Sender]
946 err := errors.New("unknown node")
947 ctx.LogE("rx-open", les, err, func(les LEs) string {
950 ctx.NodeName(job.PktEnc.Sender), pktName,
956 errs := make(chan error, 1)
959 pipeR, pipeW := io.Pipe()
968 uint64(pktSizeWithoutEnc(job.Size)),
971 dryRun, doSeen, noFile, noFreq, noExec, noTrns, noArea,
974 pipeWB := bufio.NewWriter(pipeW)
975 sharedKey, _, _, err = PktEncRead(
984 pipeW.CloseWithError(err)
986 if err := pipeWB.Flush(); err != nil {
987 pipeW.CloseWithError(err)
997 if err = <-errs; err == JobRepeatProcess {
998 if _, err = fd.Seek(0, io.SeekStart); err != nil {
999 ctx.LogE("rx-seek", les, err, func(les LEs) string {
1001 "Tossing %s/%s: can not seek",
1002 ctx.NodeName(job.PktEnc.Sender),
1010 } else if err != nil {
1018 func (ctx *Ctx) AutoToss(
1021 doSeen, noFile, noFreq, noExec, noTrns, noArea bool,
1022 ) (chan struct{}, chan bool) {
1023 finish := make(chan struct{})
1024 badCode := make(chan bool)
1034 time.Sleep(time.Second)
1036 nodeId, TRx, nice, false,
1037 doSeen, noFile, noFreq, noExec, noTrns, noArea) || bad
1040 return finish, badCode