Adam Ierymenko 5 lat temu
rodzic
commit
8a9669f130

+ 76 - 0
go/cmd/zerotier/cli/help.go

@@ -0,0 +1,76 @@
+/*
+ * Copyright (c)2019 ZeroTier, Inc.
+ *
+ * Use of this software is governed by the Business Source License included
+ * in the LICENSE.TXT file in the project's root directory.
+ *
+ * Change Date: 2023-01-01
+ *
+ * On the date above, in accordance with the Business Source License, use
+ * of this software will be governed by version 2.0 of the Apache License.
+ */
+/****/
+
+package cli
+
+import (
+	"fmt"
+	"zerotier/pkg/zerotier"
+)
+
+var copyrightText = fmt.Sprintf(`ZeroTier Network Virtualization Service Version %d.%d.%d
+(c)2019 ZeroTier, Inc.
+Licensed under the ZeroTier BSL (see LICENSE.txt)`, zerotier.CoreVersionMajor, zerotier.CoreVersionMinor, zerotier.CoreVersionRevision)
+
+// Help dumps help to stdout
+func Help() {
+	fmt.Println(copyrightText + `
+
+Usage: zerotier [-options] <command> [-options] [command args]
+
+Global Options
+  -j                                   Output raw JSON where applicable
+  -p <path>                            Use alternate base path
+  -t <authtoken.secret path>           Use secret auth token from this file
+
+Commands:
+  help                                 Show this help
+  version                              Print version
+  service                              Start in system service mode
+  status                               Show ZeroTier service status and config
+  peers                                Show VL1 peers
+  roots                                Show VL1 root servers
+  addroot <type> [options]             Add a VL1 root
+    static <identity> <ip/port> [...]  Add a root with a set identity and IPs
+    dynamic <name> [default locator]   Add a dynamic root fetched by name
+  removeroot <type> [options]          Remove a VL1 root
+    static <identity>                  Remove a root with a set identity
+    dynamic <name>                     Remove a dynamic root fetched by name
+  networks                             Show joined VL2 virtual networks
+  join <network ID>                    Join a virtual network
+  leave <network ID>                   Leave a virtual network
+  show <network ID>                    Show verbose network info
+  set <network ID> <option> <value>    Set a network local config option
+    manageips <boolean>                Is IP management allowed?
+    manageroutes <boolean>             Is route management allowed?
+    globalips <boolean>                Can IPs in global IP space be managed?
+    globalroutes <boolean>             Can global IP space routes be set?
+    defaultroute <boolean>             Can default route be overridden?
+  set <local config option> <value>    Set a local configuration option
+    phy <IP/bits> blacklist <boolean>  Set or clear blacklist for CIDR
+    phy <IP/bits> trust <path ID/0>    Set or clear trusted path ID for CIDR
+    virt <address> try <IP/port> [...] Set explicit IPs for reaching a peer
+    port <port>                        Set primary local port for VL1 P2P
+    secondaryport <port/0>             Set or disable secondary VL1 P2P port
+    tertiaryport <port/0>              Set or disable tertiary VL1 P2P port
+    portsearch <boolean>               Set or disable port search on startup
+    portmapping <boolean>              Set or disable use of uPnP and NAT-PMP
+    explicitaddresses <IP/port> [...]  Set explicit external IPs to advertise
+
+Most commands require a secret token to permit control of a running ZeroTier
+service. The CLI will automatically try to read this token from the
+authtoken.secret file in the service's working directory and then from a
+file called .zerotierauth in the user's home directory. The -t option can be
+used to explicitly specify a location.
+`)
+}

+ 23 - 7
go/cmd/zerotier/cli/service.go

@@ -13,17 +13,33 @@
 
 package cli
 
-/*
-func nodeStart() {
+import (
+	"fmt"
+	"os"
+	"os/signal"
+	"syscall"
+	"zerotier/pkg/zerotier"
+)
+
+// Service is "zerotier service ..."
+func Service(basePath, authToken string, args []string) {
+	if len(args) > 0 {
+		Help()
+		os.Exit(1)
+	}
+
+	node, err := zerotier.NewNode(basePath)
+	if err != nil {
+		fmt.Println("FATAL: error initializing node: " + err.Error())
+		os.Exit(1)
+	}
+
 	osSignalChannel := make(chan os.Signal, 2)
 	signal.Notify(osSignalChannel, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT, syscall.SIGBUS)
 	signal.Ignore(syscall.SIGUSR1, syscall.SIGUSR2)
 	go func() {
 		<-osSignalChannel
+		node.Close()
+		os.Exit(0)
 	}()
 }
-*/
-
-// Service is "zerotier service ..."
-func Service(basePath, authToken string, args []string) {
-}

+ 4 - 60
go/cmd/zerotier/zerotier.go

@@ -25,62 +25,6 @@ import (
 	"zerotier/pkg/zerotier"
 )
 
-var copyrightText = fmt.Sprintf(`ZeroTier Network Virtualization Service Version %d.%d.%d
-(c)2019 ZeroTier, Inc.
-Licensed under the ZeroTier BSL (see LICENSE.txt)`, zerotier.CoreVersionMajor, zerotier.CoreVersionMinor, zerotier.CoreVersionRevision)
-
-func printHelp() {
-	fmt.Println(copyrightText + `
-
-Usage: zerotier [-options] <command> [-options] [command args]
-
-Global Options
-  -j                                   Output raw JSON where applicable
-  -p <path>                            Connect to service running at this path
-  -t <authtoken.secret path>           Use secret auth token from this file
-
-Commands:
-  help                                 Show this help
-  version                              Print version
-  service [path]                       Start in system service mode
-  status                               Show ZeroTier service status and config
-  peers                                Show VL1 peers
-  roots                                Show VL1 root servers
-  addroot <type> [options]             Add a VL1 root
-    static <identity> <ip/port> [...]  Add a root with a set identity and IPs
-    dynamic <name> [default locator]   Add a dynamic root fetched by name
-  removeroot <type> [options]          Remove a VL1 root
-    static <identity>                  Remove a root with a set identity
-    dynamic <name>                     Remove a dynamic root fetched by name
-  networks                             Show joined VL2 virtual networks
-  join <network ID>                    Join a virtual network
-  leave <network ID>                   Leave a virtual network
-  show <network ID>                    Show verbose network info
-  set <network ID> <option> <value>    Set a network local config option
-    manageips <boolean>                Is IP management allowed?
-    manageroutes <boolean>             Is route management allowed?
-    globalips <boolean>                Can IPs in global IP space be managed?
-    globalroutes <boolean>             Can global IP space routes be set?
-    defaultroute <boolean>             Can default route be overridden?
-  set <local config option> <value>    Set a local configuration option
-    phy <IP/bits> blacklist <boolean>  Set or clear blacklist for CIDR
-    phy <IP/bits> trust <path ID/0>    Set or clear trusted path ID for CIDR
-    virt <address> try <IP/port> [...] Set explicit IPs for reaching a peer
-    port <port>                        Set primary local port for VL1 P2P
-    secondaryport <port/0>             Set or disable secondary VL1 P2P port
-    tertiaryport <port/0>              Set or disable tertiary VL1 P2P port
-    portsearch <boolean>               Set or disable port search on startup
-    portmapping <boolean>              Set or disable use of uPnP and NAT-PMP
-    explicitaddresses <IP/port> [...]  Set explicit external IPs to advertise
-
-Most commands require a secret token to permit control of a running ZeroTier
-service. The CLI will automatically try to read this token from the
-authtoken.secret file in the service's working directory and then from a
-file called .zerotierauth in the user's home directory. The -t option can be
-used to explicitly specify a location.
-`)
-}
-
 func readAuthToken(basePath string) string {
 	data, _ := ioutil.ReadFile(path.Join(basePath, "authtoken.secret"))
 	if len(data) > 0 {
@@ -118,13 +62,13 @@ func main() {
 	tflag := globalOpts.String("t", "", "")
 	err := globalOpts.Parse(os.Args[1:])
 	if err != nil {
-		printHelp()
+		cli.Help()
 		os.Exit(1)
 		return
 	}
 	args := globalOpts.Args()
 	if len(args) < 1 || *hflag {
-		printHelp()
+		cli.Help()
 		os.Exit(0)
 		return
 	}
@@ -151,7 +95,7 @@ func main() {
 
 	switch args[0] {
 	case "help":
-		printHelp()
+		cli.Help()
 		os.Exit(0)
 	case "version":
 		fmt.Printf("%d.%d.%d\n", zerotier.CoreVersionMajor, zerotier.CoreVersionMinor, zerotier.CoreVersionRevision)
@@ -180,6 +124,6 @@ func main() {
 		cli.Set(basePath, authToken, cmdArgs)
 	}
 
-	printHelp()
+	cli.Help()
 	os.Exit(1)
 }

+ 4 - 0
go/pkg/zerotier/localconfig.go

@@ -53,6 +53,9 @@ type LocalConfigSettings struct {
 	// PortMapping enables uPnP and NAT-PMP support
 	PortMapping bool
 
+	// LogSizeMax is the maximum size of the log in kilobytes or 0 for no limit and -1 to disable logging
+	LogSizeMax int
+
 	// MultipathMode sets the multipath link aggregation mode
 	MuiltipathMode int
 
@@ -89,6 +92,7 @@ func (lc *LocalConfig) Read(p string, saveDefaultsIfNotExist bool) error {
 		lc.Settings.TertiaryPort = 32768 + (rand.Int() % 16384)
 		lc.Settings.PortSearch = true
 		lc.Settings.PortMapping = true
+		lc.Settings.LogSizeMax = 128
 		lc.Settings.MuiltipathMode = 0
 		switch runtime.GOOS {
 		case "darwin":

+ 92 - 12
go/pkg/zerotier/node.go

@@ -28,12 +28,14 @@ import (
 	"errors"
 	"fmt"
 	"io/ioutil"
+	"log"
 	rand "math/rand"
 	"net"
 	"net/http"
 	"os"
 	"path"
 	"sort"
+	"strings"
 	"sync"
 	"sync/atomic"
 	"time"
@@ -42,6 +44,8 @@ import (
 	acl "github.com/hectane/go-acl"
 )
 
+var nullLogger = log.New(ioutil.Discard, "", 0)
+
 // Network status states
 const (
 	NetworkStatusRequestConfiguration int = C.ZT_NETWORK_STATUS_REQUESTING_CONFIGURATION
@@ -159,6 +163,8 @@ type Node struct {
 	localConfigLock        sync.RWMutex
 	networksLock           sync.RWMutex
 	interfaceAddressesLock sync.Mutex
+	logW                   *sizeLimitWriter
+	log                    *log.Logger
 	gn                     *C.ZT_GoNode
 	zn                     *C.ZT_Node
 	id                     *Identity
@@ -170,23 +176,36 @@ type Node struct {
 
 // NewNode creates and initializes a new instance of the ZeroTier node service
 func NewNode(basePath string) (*Node, error) {
+	var err error
+
 	os.MkdirAll(basePath, 0755)
 	if _, err := os.Stat(basePath); err != nil {
 		return nil, err
 	}
 
 	n := new(Node)
+
 	n.networks = make(map[NetworkID]*Network)
 	n.networksByMAC = make(map[MAC]*Network)
 	n.interfaceAddresses = make(map[string]net.IP)
 
 	n.basePath = basePath
 	n.localConfigPath = path.Join(basePath, "local.conf")
-	err := n.localConfig.Read(n.localConfigPath, true)
+	err = n.localConfig.Read(n.localConfigPath, true)
 	if err != nil {
 		return nil, err
 	}
 
+	if n.localConfig.Settings.LogSizeMax >= 0 {
+		n.logW, err = sizeLimitWriterOpen(path.Join(basePath, "service.log"))
+		if err != nil {
+			return nil, err
+		}
+		n.log = log.New(n.logW, "", log.LstdFlags)
+	} else {
+		n.log = nullLogger
+	}
+
 	if n.localConfig.Settings.PortSearch {
 		portsChanged := false
 
@@ -196,6 +215,7 @@ func NewNode(basePath string) (*Node, error) {
 			if checkPort(n.localConfig.Settings.PrimaryPort) {
 				break
 			}
+			n.log.Printf("primary port %d unavailable, trying next port (port search enabled)", n.localConfig.Settings.PrimaryPort)
 			n.localConfig.Settings.PrimaryPort++
 			n.localConfig.Settings.PrimaryPort &= 0xffff
 			portsChanged = true
@@ -211,6 +231,7 @@ func NewNode(basePath string) (*Node, error) {
 				if checkPort(n.localConfig.Settings.SecondaryPort) {
 					break
 				}
+				n.log.Printf("secondary port %d unavailable, trying next port (port search enabled)", n.localConfig.Settings.SecondaryPort)
 				n.localConfig.Settings.SecondaryPort++
 				n.localConfig.Settings.SecondaryPort &= 0xffff
 				portsChanged = true
@@ -227,6 +248,7 @@ func NewNode(basePath string) (*Node, error) {
 				if checkPort(n.localConfig.Settings.TertiaryPort) {
 					break
 				}
+				n.log.Printf("tertiary port %d unavailable, trying next port (port search enabled)", n.localConfig.Settings.TertiaryPort)
 				n.localConfig.Settings.TertiaryPort++
 				n.localConfig.Settings.TertiaryPort &= 0xffff
 				portsChanged = true
@@ -247,20 +269,24 @@ func NewNode(basePath string) (*Node, error) {
 	n.gn = C.ZT_GoNode_new(cpath)
 	C.free(unsafe.Pointer(cpath))
 	if n.gn == nil {
+		n.log.Println("FATAL: node initialization failed")
 		return nil, ErrNodeInitFailed
 	}
 	n.zn = (*C.ZT_Node)(C.ZT_GoNode_getNode(n.gn))
 
 	var ns C.ZT_NodeStatus
 	C.ZT_Node_status(unsafe.Pointer(n.zn), &ns)
-	n.id, err = NewIdentityFromString(C.GoString(ns.secretIdentity))
+	idstr := C.GoString(ns.secretIdentity)
+	n.id, err = NewIdentityFromString(idstr)
 	if err != nil {
+		n.log.Printf("FATAL: node's identity does not seem valid (%s)", idstr)
 		C.ZT_GoNode_delete(n.gn)
 		return nil, err
 	}
 
 	n.apiServer, err = createAPIServer(basePath, n)
 	if err != nil {
+		n.log.Printf("FATAL: unable to start API server: %s", err.Error())
 		C.ZT_GoNode_delete(n.gn)
 		return nil, err
 	}
@@ -275,13 +301,13 @@ func NewNode(basePath string) (*Node, error) {
 
 	n.runLock.Lock()
 	go func() {
-		lastScannedInterfaces := int64(0)
+		lastMaintenanceRun := int64(0)
 		for atomic.LoadUint32(&n.running) != 0 {
 			time.Sleep(1 * time.Second)
 
 			now := TimeMs()
-			if (now - lastScannedInterfaces) >= 30000 {
-				lastScannedInterfaces = now
+			if (now - lastMaintenanceRun) >= 30000 {
+				lastMaintenanceRun = now
 
 				interfaceAddresses := make(map[string]net.IP)
 				ifs, _ := net.Interfaces()
@@ -305,17 +331,21 @@ func NewNode(basePath string) (*Node, error) {
 				}
 
 				n.localConfigLock.RLock()
+
 				n.interfaceAddressesLock.Lock()
 				for astr, ipn := range interfaceAddresses {
 					if _, alreadyKnown := n.interfaceAddresses[astr]; !alreadyKnown {
 						ipCstr := C.CString(ipn.String())
 						if n.localConfig.Settings.PrimaryPort > 0 && n.localConfig.Settings.PrimaryPort < 65536 {
+							n.log.Printf("UDP binding to port %d on interface %s", n.localConfig.Settings.PrimaryPort, astr)
 							C.ZT_GoNode_phyStartListen(n.gn, nil, ipCstr, C.int(n.localConfig.Settings.PrimaryPort))
 						}
 						if n.localConfig.Settings.SecondaryPort > 0 && n.localConfig.Settings.SecondaryPort < 65536 {
+							n.log.Printf("UDP binding to port %d on interface %s", n.localConfig.Settings.SecondaryPort, astr)
 							C.ZT_GoNode_phyStartListen(n.gn, nil, ipCstr, C.int(n.localConfig.Settings.SecondaryPort))
 						}
 						if n.localConfig.Settings.TertiaryPort > 0 && n.localConfig.Settings.TertiaryPort < 65536 {
+							n.log.Printf("UDP binding to port %d on interface %s", n.localConfig.Settings.TertiaryPort, astr)
 							C.ZT_GoNode_phyStartListen(n.gn, nil, ipCstr, C.int(n.localConfig.Settings.TertiaryPort))
 						}
 						C.free(unsafe.Pointer(ipCstr))
@@ -325,12 +355,15 @@ func NewNode(basePath string) (*Node, error) {
 					if _, stillPresent := interfaceAddresses[astr]; !stillPresent {
 						ipCstr := C.CString(ipn.String())
 						if n.localConfig.Settings.PrimaryPort > 0 && n.localConfig.Settings.PrimaryPort < 65536 {
+							n.log.Printf("UDP closing socket bound to port %d on interface %s", n.localConfig.Settings.PrimaryPort, astr)
 							C.ZT_GoNode_phyStopListen(n.gn, nil, ipCstr, C.int(n.localConfig.Settings.PrimaryPort))
 						}
 						if n.localConfig.Settings.SecondaryPort > 0 && n.localConfig.Settings.SecondaryPort < 65536 {
+							n.log.Printf("UDP closing socket bound to port %d on interface %s", n.localConfig.Settings.SecondaryPort, astr)
 							C.ZT_GoNode_phyStopListen(n.gn, nil, ipCstr, C.int(n.localConfig.Settings.SecondaryPort))
 						}
 						if n.localConfig.Settings.TertiaryPort > 0 && n.localConfig.Settings.TertiaryPort < 65536 {
+							n.log.Printf("UDP closing socket bound to port %d on interface %s", n.localConfig.Settings.TertiaryPort, astr)
 							C.ZT_GoNode_phyStopListen(n.gn, nil, ipCstr, C.int(n.localConfig.Settings.TertiaryPort))
 						}
 						C.free(unsafe.Pointer(ipCstr))
@@ -338,6 +371,11 @@ func NewNode(basePath string) (*Node, error) {
 				}
 				n.interfaceAddresses = interfaceAddresses
 				n.interfaceAddressesLock.Unlock()
+
+				if n.localConfig.Settings.LogSizeMax > 0 && n.logW != nil {
+					n.logW.trim(n.localConfig.Settings.LogSizeMax*1024, 0.5, true)
+				}
+
 				n.localConfigLock.RUnlock()
 			}
 		}
@@ -388,25 +426,64 @@ func (n *Node) LocalConfig() LocalConfig {
 	return n.localConfig
 }
 
+// SetLocalConfig updates this node's local configuration
+func (n *Node) SetLocalConfig(lc *LocalConfig) (restartRequired bool, err error) {
+	n.networksLock.RLock()
+	n.localConfigLock.Lock()
+	defer n.localConfigLock.Unlock()
+	defer n.networksLock.RUnlock()
+
+	for nid, nc := range lc.Network {
+		nw := n.networks[nid]
+		if nw != nil {
+			nw.SetLocalSettings(nc)
+		}
+	}
+
+	if n.localConfig.Settings.PrimaryPort != lc.Settings.PrimaryPort || n.localConfig.Settings.SecondaryPort != lc.Settings.SecondaryPort || n.localConfig.Settings.TertiaryPort != lc.Settings.TertiaryPort {
+		restartRequired = true
+	}
+	if lc.Settings.LogSizeMax < 0 {
+		n.log = nullLogger
+		n.logW.Close()
+		n.logW = nil
+	} else if n.logW != nil {
+		n.logW, err = sizeLimitWriterOpen(path.Join(n.basePath, "service.log"))
+		if err == nil {
+			n.log = log.New(n.logW, "", log.LstdFlags)
+		} else {
+			n.log = nullLogger
+			n.logW = nil
+		}
+	}
+
+	n.localConfig = *lc
+
+	return
+}
+
 // Join joins a network
 // If tap is nil, the default system tap for this OS/platform is used (if available).
 func (n *Node) Join(nwid uint64, tap Tap) (*Network, error) {
 	n.networksLock.RLock()
 	if nw, have := n.networks[NetworkID(nwid)]; have {
+		n.log.Printf("join network %.16x ignored: already a member", nwid)
 		return nw, nil
 	}
 	n.networksLock.RUnlock()
 
 	if tap != nil {
-		return nil, errors.New("non-native taps not implemented yet")
+		panic("non-native taps not yet implemented")
 	}
 	ntap := C.ZT_GoNode_join(n.gn, C.uint64_t(nwid))
 	if ntap == nil {
+		n.log.Printf("join network %.16x failed: tap device failed to initialize (check drivers / kernel modules)")
 		return nil, ErrTapInitFailed
 	}
 
 	nw, err := newNetwork(n, NetworkID(nwid), &nativeTap{tap: unsafe.Pointer(ntap), enabled: 1})
 	if err != nil {
+		n.log.Printf("join network %.16x failed: network failed to initialize: %s", nwid, err.Error())
 		C.ZT_GoNode_leave(n.gn, C.uint64_t(nwid))
 		return nil, err
 	}
@@ -419,6 +496,7 @@ func (n *Node) Join(nwid uint64, tap Tap) (*Network, error) {
 
 // Leave leaves a network
 func (n *Node) Leave(nwid uint64) error {
+	n.log.Printf("leaving network %.16x", nwid)
 	C.ZT_GoNode_leave(n.gn, C.uint64_t(nwid))
 	n.networksLock.Lock()
 	delete(n.networks, NetworkID(nwid))
@@ -439,15 +517,17 @@ func (n *Node) Networks() []*Network {
 
 // AddStaticRoot adds a statically defined root server to this node.
 // If a static root with the given identity already exists this will update its IP and port information.
-func (n *Node) AddStaticRoot(id *Identity, addrs []net.Addr) {
+func (n *Node) AddStaticRoot(id *Identity, addrs []InetAddress) {
 	var saddrs []C.struct_sockaddr_storage
+	var straddrs strings.Builder
 	for _, a := range addrs {
-		aa, _ := a.(*net.UDPAddr)
-		if aa != nil {
-			ss := new(C.struct_sockaddr_storage)
-			if makeSockaddrStorage(aa.IP, aa.Port, ss) {
-				saddrs = append(saddrs, *ss)
+		ss := new(C.struct_sockaddr_storage)
+		if makeSockaddrStorage(a.IP, a.Port, ss) {
+			saddrs = append(saddrs, *ss)
+			if straddrs.Len() > 0 {
+				straddrs.WriteString(",")
 			}
+			straddrs.WriteString(a.String())
 		}
 	}
 	if len(saddrs) > 0 {

+ 108 - 0
go/pkg/zerotier/sizelimitwriter.go

@@ -0,0 +1,108 @@
+/*
+ * Copyright (c)2019 ZeroTier, Inc.
+ *
+ * Use of this software is governed by the Business Source License included
+ * in the LICENSE.TXT file in the project's root directory.
+ *
+ * Change Date: 2023-01-01
+ *
+ * On the date above, in accordance with the Business Source License, use
+ * of this software will be governed by version 2.0 of the Apache License.
+ */
+/****/
+
+package zerotier
+
+import (
+	"os"
+	"sync"
+)
+
+type sizeLimitWriter struct {
+	f *os.File
+	l sync.Mutex
+}
+
+func sizeLimitWriterOpen(p string) (*sizeLimitWriter, error) {
+	f, err := os.OpenFile(p, os.O_CREATE|os.O_RDWR, 0644)
+	if err != nil {
+		return nil, err
+	}
+	f.Seek(0, os.SEEK_END)
+	return &sizeLimitWriter{f: f}, nil
+}
+
+// Write implements io.Writer
+func (w *sizeLimitWriter) Write(b []byte) (int, error) {
+	w.l.Lock()
+	defer w.l.Unlock()
+	return w.f.Write(b)
+}
+
+// Close closes the underlying file
+func (w *sizeLimitWriter) Close() error {
+	w.l.Lock()
+	defer w.l.Unlock()
+	return w.f.Close()
+}
+
+func (w *sizeLimitWriter) trim(maxSize int, trimFactor float64, trimAtCR bool) error {
+	w.l.Lock()
+	defer w.l.Unlock()
+
+	flen, err := w.f.Seek(0, os.SEEK_END)
+	if err != nil {
+		return err
+	}
+
+	if flen > int64(maxSize) {
+		var buf [131072]byte
+		trimAt := int64(float64(flen) * trimFactor)
+		if trimAt >= flen { // sanity check
+			return nil
+		}
+
+		if trimAtCR {
+		lookForCR:
+			for {
+				nr, err := w.f.ReadAt(buf[0:1024], trimAt)
+				if err != nil {
+					return err
+				}
+				for i := 0; i < nr; i++ {
+					trimAt++
+					if buf[i] == byte('\n') {
+						break lookForCR
+					}
+				}
+				if trimAt >= flen {
+					return nil
+				}
+			}
+		}
+
+		copyTo := int64(0)
+		for trimAt < flen {
+			nr, _ := w.f.ReadAt(buf[:], trimAt)
+			if nr > 0 {
+				wr, _ := w.f.WriteAt(buf[0:nr], copyTo)
+				if wr > 0 {
+					copyTo += int64(wr)
+				} else {
+					break
+				}
+			} else {
+				break
+			}
+		}
+
+		err = w.f.Truncate(copyTo)
+		if err != nil {
+			return err
+		}
+		_, err = w.f.Seek(0, os.SEEK_END)
+		return err
+	}
+
+	return nil
+}