pixiecore: rework logging to provide less verbose, more useful logs by default.

Also adds a debug channel with the full line noise.
This commit is contained in:
David Anderson 2016-08-15 21:33:57 -07:00
parent 63a0c5fb32
commit 4aa8885728
10 changed files with 146 additions and 86 deletions

View File

@ -48,7 +48,8 @@ the Pixiecore boot API. The specification can be found at <TODO>.`,
s := &pixiecore.Server{ s := &pixiecore.Server{
Booter: booter, Booter: booter,
Ipxe: Ipxe, Ipxe: Ipxe,
Log: func(msg string) { fmt.Println(msg) }, Log: logStdout,
Debug: debugLog,
} }
fmt.Println(s.Serve()) fmt.Println(s.Serve())
}} }}

View File

@ -56,7 +56,8 @@ var bootCmd = &cobra.Command{
s := &pixiecore.Server{ s := &pixiecore.Server{
Booter: booter, Booter: booter,
Ipxe: Ipxe, Ipxe: Ipxe,
Log: func(msg string) { fmt.Println(msg) }, Log: logStdout,
Debug: debugLog,
} }
fmt.Println(s.Serve()) fmt.Println(s.Serve())
}, },

View File

@ -81,7 +81,8 @@ func v1compatCLI() bool {
s := &pixiecore.Server{ s := &pixiecore.Server{
Booter: booter, Booter: booter,
Ipxe: Ipxe, Ipxe: Ipxe,
Log: func(msg string) { fmt.Println(msg) }, Log: logStdout,
Debug: debugLog,
Address: *listenAddr, Address: *listenAddr,
HTTPPort: *portHTTP, HTTPPort: *portHTTP,
DHCPPort: *portDHCP, DHCPPort: *portDHCP,
@ -116,7 +117,8 @@ func v1compatCLI() bool {
s := &pixiecore.Server{ s := &pixiecore.Server{
Booter: booter, Booter: booter,
Ipxe: Ipxe, Ipxe: Ipxe,
Log: func(msg string) { fmt.Println(msg) }, Log: logStdout,
Debug: debugLog,
Address: *listenAddr, Address: *listenAddr,
HTTPPort: *portHTTP, HTTPPort: *portHTTP,
DHCPPort: *portDHCP, DHCPPort: *portDHCP,

View File

@ -26,7 +26,7 @@ func (s *Server) serveDHCP(conn *dhcp4.Conn) {
for { for {
pkt, intf, err := conn.RecvDHCP() pkt, intf, err := conn.RecvDHCP()
if err != nil { if err != nil {
s.logf("receiving DHCP packet: %s", err) s.log("DHCP", "Receiving packet: %s", err)
// TODO: fatal errors that return from one of the handler // TODO: fatal errors that return from one of the handler
// goroutines should plumb the error back to the // goroutines should plumb the error back to the
// coordinating goroutine, so that it can do an orderly // coordinating goroutine, so that it can do an orderly
@ -36,54 +36,65 @@ func (s *Server) serveDHCP(conn *dhcp4.Conn) {
return return
} }
if intf == nil { if intf == nil {
s.logf("received DHCP packet with no interface information (this is a violation of dhcp4.Conn's contract)") s.log("DHCP", "Received packet with no interface information (this is a violation of dhcp4.Conn's contract, please file a bug)")
return return
} }
if err = s.isBootDHCP(pkt); err != nil {
s.debug("DHCP", "Ignoring packet from %s: %s", pkt.HardwareAddr, err)
continue
}
mach, isIpxe, fwtype, err := s.validateDHCP(pkt) mach, isIpxe, fwtype, err := s.validateDHCP(pkt)
if err != nil { if err != nil {
s.logf("DHCP packet from %s not usable: %s", pkt.HardwareAddr, err) s.log("DHCP", "Unusable packet from %s: %s", pkt.HardwareAddr, err)
continue continue
} }
spec, err := s.Booter.BootSpec(mach) spec, err := s.Booter.BootSpec(mach)
if err != nil { if err != nil {
s.logf("couldn't get bootspec for %s: %s", pkt.HardwareAddr, err) s.log("DHCP", "Couldn't get bootspec for %s: %s", pkt.HardwareAddr, err)
continue continue
} }
if spec == nil { if spec == nil {
s.logf("booter gave no spec for %s, ignoring boot request", pkt.HardwareAddr) s.debug("DHCP", "No boot spec for %s, ignoring boot request", pkt.HardwareAddr)
continue continue
} }
s.log("DHCP", "Offering to boot %s", pkt.HardwareAddr)
// Machine should be booted. // Machine should be booted.
serverIP, err := interfaceIP(intf) serverIP, err := interfaceIP(intf)
if err != nil { if err != nil {
s.logf("want to boot %s on %s, but couldn't find a unicast source address on that interface: %s", pkt.HardwareAddr, intf.Name, err) s.log("DHCP", "Want to boot %s on %s, but couldn't get a source address: %s", pkt.HardwareAddr, intf.Name, err)
continue continue
} }
resp, err := s.offerDHCP(pkt, mach, serverIP, isIpxe, fwtype) resp, err := s.offerDHCP(pkt, mach, serverIP, isIpxe, fwtype)
if err != nil { if err != nil {
s.logf("failed to construct ProxyDHCP offer for %s: %s", pkt.HardwareAddr, err) s.log("DHCP", "Failed to construct ProxyDHCP offer for %s: %s", pkt.HardwareAddr, err)
continue continue
} }
if err = conn.SendDHCP(resp, intf); err != nil { if err = conn.SendDHCP(resp, intf); err != nil {
s.logf("failed to send ProxyDHCP offer for %s: %s", pkt.HardwareAddr, err) s.log("DHCP", "Failed to send ProxyDHCP offer for %s: %s", pkt.HardwareAddr, err)
continue continue
} }
} }
} }
func (s *Server) validateDHCP(pkt *dhcp4.Packet) (mach Machine, isIpxe bool, fwtype Firmware, err error) { func (s *Server) isBootDHCP(pkt *dhcp4.Packet) error {
if pkt.Type != dhcp4.MsgDiscover { if pkt.Type != dhcp4.MsgDiscover {
return mach, false, 0, errors.New("not a DHCPDISCOVER packet") return fmt.Errorf("packet is %s, not %s", pkt.Type, dhcp4.MsgDiscover)
} }
if pkt.Options[93] == nil { if pkt.Options[93] == nil {
return mach, false, 0, errors.New("not a PXE boot request (missing option 93)") return errors.New("not a PXE boot request (missing option 93)")
} }
return nil
}
func (s *Server) validateDHCP(pkt *dhcp4.Packet) (mach Machine, isIpxe bool, fwtype Firmware, err error) {
fwt, err := pkt.Options.Uint16(93) fwt, err := pkt.Options.Uint16(93)
if err != nil { if err != nil {
return mach, false, 0, fmt.Errorf("malformed DHCP option 93 (required for PXE): %s", err) return mach, false, 0, fmt.Errorf("malformed DHCP option 93 (required for PXE): %s", err)
@ -218,5 +229,5 @@ func interfaceIP(intf *net.Interface) (net.IP, error) {
} }
} }
return nil, errors.New("no usable source address") return nil, errors.New("no usable unicast address configured on interface")
} }

View File

@ -26,29 +26,55 @@ import (
"text/template" "text/template"
) )
func (s *Server) httpError(w http.ResponseWriter, r *http.Request, status int, format string, args ...interface{}) { func (s *Server) serveHTTP(l net.Listener) {
s.logHTTP(r, format, args...) httpMux := http.NewServeMux()
http.Error(w, fmt.Sprintf(format, args...), status) httpMux.HandleFunc("/_/ipxe", s.handleIpxe)
httpMux.HandleFunc("/_/file", s.handleFile)
if err := http.Serve(l, httpMux); err != nil {
s.log("HTTP", "%s", err)
// TODO: fatal errors that return from one of the handler
// goroutines should plumb the error back to the
// coordinating goroutine, so that it can do an orderly
// shutdown and return the error from Serve(). This "log +
// randomly stop a piece of pixiecore" is a terrible
// kludge.
return
}
} }
func (s *Server) handleIpxe(w http.ResponseWriter, r *http.Request) { func (s *Server) handleIpxe(w http.ResponseWriter, r *http.Request) {
args := r.URL.Query() macStr := r.URL.Query().Get("mac")
mac, err := net.ParseMAC(args.Get("mac")) if macStr == "" {
if err != nil { s.debug("HTTP", "Bad request %q from %s, missing MAC address", r.URL, r.RemoteAddr)
s.httpError(w, r, http.StatusBadRequest, "invalid MAC address %q: %s\n", args.Get("mac"), err) http.Error(w, "missing MAC address parameter", http.StatusBadRequest)
return
}
archStr := r.URL.Query().Get("arch")
if archStr == "" {
s.debug("HTTP", "Bad request %q from %s, missing architecture", r.URL, r.RemoteAddr)
http.Error(w, "missing architecture parameter", http.StatusBadRequest)
return return
} }
i, err := strconv.Atoi(args.Get("arch")) mac, err := net.ParseMAC(macStr)
if err != nil { if err != nil {
s.httpError(w, r, http.StatusBadRequest, "invalid architecture %q: %s\n", args.Get("arch"), err) s.debug("HTTP", "Bad request %q from %s, invalid MAC address %q (%s)", r.URL, r.RemoteAddr, macStr, err)
http.Error(w, "invalid MAC address", http.StatusBadRequest)
return
}
i, err := strconv.Atoi(archStr)
if err != nil {
s.debug("HTTP", "Bad request %q from %s, invalid architecture %q (%s)", r.URL, r.RemoteAddr, archStr, err)
http.Error(w, "invalid architecture", http.StatusBadRequest)
return return
} }
arch := Architecture(i) arch := Architecture(i)
switch arch { switch arch {
case ArchIA32, ArchX64: case ArchIA32, ArchX64:
default: default:
s.httpError(w, r, http.StatusBadRequest, "Unknown architecture %q\n", arch) s.debug("HTTP", "Bad request %q from %s, unknown architecture %q", r.URL, r.RemoteAddr, arch)
http.Error(w, "unknown architecture", http.StatusBadRequest)
return return
} }
@ -58,39 +84,48 @@ func (s *Server) handleIpxe(w http.ResponseWriter, r *http.Request) {
} }
spec, err := s.Booter.BootSpec(mach) spec, err := s.Booter.BootSpec(mach)
if err != nil { if err != nil {
// TODO: maybe don't send this error over the network? s.log("HTTP", "Couldn't get a bootspec for %s (query %q from %s): %s", mac, r.URL, r.RemoteAddr, err)
s.logHTTP(r, "error getting bootspec for %#v: %s", mach, err)
http.Error(w, "couldn't get a bootspec", http.StatusInternalServerError) http.Error(w, "couldn't get a bootspec", http.StatusInternalServerError)
return return
} }
if spec == nil { if spec == nil {
// TODO: make ipxe abort netbooting so it can fall through to // TODO: make ipxe abort netbooting so it can fall through to
// other boot options - unsure if that's possible. // other boot options - unsure if that's possible.
s.httpError(w, r, http.StatusNotFound, "no bootspec found for %q", mach.MAC) s.debug("HTTP", "No boot spec for %s (query %q from %s), ignoring boot request", mac, r.URL, r.RemoteAddr)
http.Error(w, "you don't netboot", http.StatusNotFound)
return return
} }
script, err := ipxeScript(spec, r.Host) script, err := ipxeScript(spec, r.Host)
if err != nil { if err != nil {
s.logHTTP(r, "failed to assemble ipxe script: %s", err) s.log("HTTP", "Failed to assemble ipxe script for %s (query %q from %s): %s", mac, r.URL, r.RemoteAddr, err)
http.Error(w, "couldn't get a bootspec", http.StatusInternalServerError) http.Error(w, "couldn't get a boot script", http.StatusInternalServerError)
return
} }
s.log("HTTP", "Sending ipxe boot script to %s", r.RemoteAddr)
w.Header().Set("Content-Type", "text/plain") w.Header().Set("Content-Type", "text/plain")
w.Write(script) w.Write(script)
} }
func (s *Server) handleFile(w http.ResponseWriter, r *http.Request) { func (s *Server) handleFile(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name") name := r.URL.Query().Get("name")
if name == "" {
s.debug("HTTP", "Bad request %q from %s, missing filename", r.URL, r.RemoteAddr)
http.Error(w, "missing filename", http.StatusBadRequest)
}
f, err := s.Booter.ReadBootFile(ID(name)) f, err := s.Booter.ReadBootFile(ID(name))
if err != nil { if err != nil {
s.logHTTP(r, "error getting requested file %q: %s", name, err) s.log("HTTP", "Error getting file %q (query %q from %s)", name, r.URL, r.RemoteAddr)
http.Error(w, "couldn't get file", http.StatusInternalServerError) http.Error(w, "couldn't get file", http.StatusInternalServerError)
return return
} }
defer f.Close() defer f.Close()
if _, err = io.Copy(w, f); err != nil { if _, err = io.Copy(w, f); err != nil {
s.logHTTP(r, "copy of file %q failed: %s", name, err) s.log("HTTP", "Copy of %q to %s (query %q) failed: %s", name, r.RemoteAddr, r.URL, err)
return
} }
s.log("HTTP", "Sent file %q to %s", name, r.RemoteAddr)
} }
func ipxeScript(spec *Spec, serverHost string) ([]byte, error) { func ipxeScript(spec *Spec, serverHost string) ([]byte, error) {

View File

@ -22,6 +22,7 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"sync"
"testing" "testing"
) )
@ -31,6 +32,8 @@ func (b booterFunc) BootSpec(m Machine) (*Spec, error) { return b(m) }
func (b booterFunc) ReadBootFile(id ID) (io.ReadCloser, error) { return nil, errors.New("no") } func (b booterFunc) ReadBootFile(id ID) (io.ReadCloser, error) { return nil, errors.New("no") }
func (b booterFunc) WriteBootFile(id ID, r io.Reader) error { return errors.New("no") } func (b booterFunc) WriteBootFile(id ID, r io.Reader) error { return errors.New("no") }
var logSync sync.Mutex
func TestIpxe(t *testing.T) { func TestIpxe(t *testing.T) {
booter := func(m Machine) (*Spec, error) { booter := func(m Machine) (*Spec, error) {
return &Spec{ return &Spec{
@ -43,9 +46,11 @@ func TestIpxe(t *testing.T) {
Message: "Hello from the test!", Message: "Hello from the test!",
}, nil }, nil
} }
log := func(subsystem, msg string) { t.Logf("[%s] %s", subsystem, msg) }
s := &Server{ s := &Server{
Booter: booterFunc(booter), Booter: booterFunc(booter),
Log: func(msg string) { fmt.Println(msg) }, Log: log,
Debug: log,
} }
// Successful boot // Successful boot
@ -150,9 +155,11 @@ func (b readBootFile) ReadBootFile(id ID) (io.ReadCloser, error) {
func (b readBootFile) WriteBootFile(id ID, r io.Reader) error { return errors.New("no") } func (b readBootFile) WriteBootFile(id ID, r io.Reader) error { return errors.New("no") }
func TestFile(t *testing.T) { func TestFile(t *testing.T) {
log := func(subsystem, msg string) { t.Logf("[%s] %s", subsystem, msg) }
s := &Server{ s := &Server{
Booter: readBootFile("stuff"), Booter: readBootFile("stuff"),
Log: func(msg string) { fmt.Println(msg) }, Log: log,
Debug: log,
} }
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/_/file?name=test", nil) req, err := http.NewRequest("GET", "/_/file?name=test", nil)

View File

@ -15,22 +15,27 @@
package pixiecore package pixiecore
import ( import (
"encoding/base64"
"fmt" "fmt"
"net/http"
) )
func (s *Server) logf(format string, args ...interface{}) { func (s *Server) log(subsystem, format string, args ...interface{}) {
if s.Log == nil { if s.Log == nil {
return return
} }
s.Log(fmt.Sprintf(format, args...)) s.Log(subsystem, fmt.Sprintf(format, args...))
} }
// logHTTP logs a message with some context about the HTTP request func (s *Server) debug(subsystem, format string, args ...interface{}) {
// that caused the statement to be logged. if s.Debug == nil {
func (s *Server) logHTTP(r *http.Request, format string, args ...interface{}) { return
if s.Log != nil {
pfx := fmt.Sprintf("HTTP request for %s from %s: ", r.URL, r.RemoteAddr)
s.logf(pfx+format, args...)
} }
s.Debug(subsystem, fmt.Sprintf(format, args...))
}
func (s *Server) debugPacket(subsystem string, layer int, packet []byte) {
if s.Debug == nil {
return
}
s.Debug(subsystem, fmt.Sprintf("PKT %d %s END", layer, base64.StdEncoding.EncodeToString(packet)))
} }

View File

@ -19,7 +19,6 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"net/http"
"strings" "strings"
"text/template" "text/template"
@ -29,7 +28,7 @@ import (
const ( const (
portDHCP = 67 portDHCP = 67
portTFTP = 69 portTFTP = 69
portHTTP = 81 portHTTP = 80
portPXE = 4011 portPXE = 4011
) )
@ -143,11 +142,10 @@ type Server struct {
// Log receives logs on Pixiecore's operation. If nil, logging // Log receives logs on Pixiecore's operation. If nil, logging
// is suppressed. // is suppressed.
// Log func(subsystem, msg string)
// TODO: until Trace is better defined and plumbed through, we use // Debug receives extensive logging on Pixiecore's internals. Very
// Log extensively, which may make it too noisy for normal // useful for debugging, but very verbose.
// use. Need to decide what, if anything, we should do about that. Debug func(subsystem, msg string)
Log func(msg string)
// These ports can technically be set for testing, but the // These ports can technically be set for testing, but the
// protocols burned in firmware on the client side hardcode these, // protocols burned in firmware on the client side hardcode these,
@ -155,16 +153,6 @@ type Server struct {
DHCPPort int DHCPPort int
TFTPPort int TFTPPort int
PXEPort int PXEPort int
// Trace receives huge amounts of detail about what Pixiecore is
// doing, including raw packets it sent/received. This should be
// nil unless you intend to provide a bug report.
//
// TODO: figure out the format this logs in, and write a
// decoder. It'll likely include bits of pcap for packets
// received, interleaved with what bits of Pixiecore thought about
// them, so that you get a timeline complete with wire traffic.
Trace io.Writer
} }
// Serve listens for machines attempting to boot, and uses Booter to // Serve listens for machines attempting to boot, and uses Booter to
@ -198,15 +186,19 @@ func (s *Server) Serve() error {
tftp.Close() tftp.Close()
return err return err
} }
http, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Address, s.HTTPPort))
if err != nil {
dhcp.Close()
tftp.Close()
pxe.Close()
return err
}
// TODO: have something here for orderly shutdown when things go wrong. // TODO: have something here for orderly shutdown when things go wrong.
go s.serveDHCP(dhcp) go s.serveDHCP(dhcp)
go s.servePXE(pxe) go s.servePXE(pxe)
go s.serveTFTP(tftp) go s.serveTFTP(tftp)
http.HandleFunc("/_/ipxe", s.handleIpxe) go s.serveHTTP(http)
http.HandleFunc("/_/file", s.handleFile) select {}
http.ListenAndServe(fmt.Sprintf("%s:%d", s.Address, s.HTTPPort), nil)
return nil
} }

View File

@ -39,68 +39,64 @@ func (s *Server) servePXE(conn net.PacketConn) {
// shutdown and return the error from Serve(). This "log + // shutdown and return the error from Serve(). This "log +
// randomly stop a piece of pixiecore" is a terrible // randomly stop a piece of pixiecore" is a terrible
// kludge. // kludge.
s.logf("couldn't get interface metadata on PXE PacketConn: %s", err) s.log("PXE", "Couldn't get interface metadata on PXE port: %s", err)
return return
} }
for { for {
n, msg, addr, err := l.ReadFrom(buf) n, msg, addr, err := l.ReadFrom(buf)
if err != nil { if err != nil {
s.logf("receiving PXE packet: %s", err) s.log("PXE", "Receiving packet: %s", err)
return return
} }
pkt, err := dhcp4.Unmarshal(buf[:n]) pkt, err := dhcp4.Unmarshal(buf[:n])
if err != nil { if err != nil {
s.logf("PXE request from %s not usable: %s", addr, err) s.debug("PXE", "Packet from %s is not a DHCP packet: %s", addr, err)
continue continue
} }
if err = s.isBootDHCP(pkt); err != nil {
s.debug("PXE", "Ignoring packet from %s (%s): %s", pkt.HardwareAddr, addr, err)
}
fwtype, err := s.validatePXE(pkt) fwtype, err := s.validatePXE(pkt)
if err != nil { if err != nil {
s.logf("PXE request from %s not usable: %s", addr, err) s.log("PXE", "Unusable packet from %s (%s): %s", pkt.HardwareAddr, addr, err)
continue continue
} }
intf, err := net.InterfaceByIndex(msg.IfIndex) intf, err := net.InterfaceByIndex(msg.IfIndex)
if err != nil { if err != nil {
s.logf("Couldn't get information about local network interface %d: %s", msg.IfIndex, err) s.log("PXE", "Couldn't get information about local network interface %d: %s", msg.IfIndex, err)
continue continue
} }
serverIP, err := interfaceIP(intf) serverIP, err := interfaceIP(intf)
if err != nil { if err != nil {
s.logf("want to boot %s on %s, but couldn't find a unicast source address on that interface: %s", addr, intf.Name, err) s.log("PXE", "Want to boot %s (%s) on %s, but couldn't get a source address: %s", pkt.HardwareAddr, addr, intf.Name, err)
continue continue
} }
resp, err := s.offerPXE(pkt, serverIP, fwtype) resp, err := s.offerPXE(pkt, serverIP, fwtype)
if err != nil { if err != nil {
s.logf("failed to construct PXE response for %s: %s", addr, err) s.log("PXE", "Failed to construct PXE offer for %s (%s): %s", pkt.HardwareAddr, addr, err)
continue continue
} }
bs, err := resp.Marshal() bs, err := resp.Marshal()
if err != nil { if err != nil {
s.logf("failed to marshal PXE response for %s: %s", addr, err) s.log("PXE", "Failed to marshal PXE offer for %s (%s): %s", pkt.HardwareAddr, addr, err)
} }
if _, err := l.WriteTo(bs, &ipv4.ControlMessage{ if _, err := l.WriteTo(bs, &ipv4.ControlMessage{
IfIndex: msg.IfIndex, IfIndex: msg.IfIndex,
}, addr); err != nil { }, addr); err != nil {
s.logf("failed to send PXE response to %s: %s", addr, err) s.log("PXE", "Failed to send PXE response to %s (%s): %s", pkt.HardwareAddr, addr, err)
} }
} }
} }
func (s *Server) validatePXE(pkt *dhcp4.Packet) (fwtype Firmware, err error) { func (s *Server) validatePXE(pkt *dhcp4.Packet) (fwtype Firmware, err error) {
if pkt.Type != dhcp4.MsgRequest {
return 0, errors.New("not a DHCPREQUEST packet")
}
if pkt.Options[93] == nil {
return 0, errors.New("not a PXE boot request (missing option 93)")
}
fwt, err := pkt.Options.Uint16(93) fwt, err := pkt.Options.Uint16(93)
if err != nil { if err != nil {
return 0, fmt.Errorf("malformed DHCP option 93 (required for PXE): %s", err) return 0, fmt.Errorf("malformed DHCP option 93 (required for PXE): %s", err)

View File

@ -17,6 +17,7 @@ package pixiecore
import ( import (
"bytes" "bytes"
"errors" "errors"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net" "net"
@ -27,8 +28,9 @@ import (
func (s *Server) serveTFTP(l net.PacketConn) { func (s *Server) serveTFTP(l net.PacketConn) {
ts := tftp.Server{ ts := tftp.Server{
Handler: s.handleTFTP, Handler: s.handleTFTP,
InfoLog: func(msg string) { s.logf(msg) }, InfoLog: func(msg string) { s.debug("TFTP", msg) },
TransferLog: s.logTFTPTransfer,
} }
err := ts.Serve(l) err := ts.Serve(l)
if err != nil { if err != nil {
@ -38,7 +40,15 @@ func (s *Server) serveTFTP(l net.PacketConn) {
// shutdown and return the error from Serve(). This "log + // shutdown and return the error from Serve(). This "log +
// randomly stop a piece of pixiecore" is a terrible // randomly stop a piece of pixiecore" is a terrible
// kludge. // kludge.
s.logf("TFTP server shut down unexpectedly: %s", err) s.log("TFTP", "Server shut down unexpectedly: %s", err)
}
}
func (s *Server) logTFTPTransfer(clientAddr net.Addr, path string, err error) {
if err != nil {
s.log("TFTP", "Send of %q to %s failed: %s", path, clientAddr, err)
} else {
s.log("TFTP", "Sent %q to %s", path, clientAddr)
} }
} }
@ -50,7 +60,7 @@ func (s *Server) handleTFTP(path string, clientAddr net.Addr) (io.ReadCloser, in
bs, ok := s.Ipxe[Firmware(i)] bs, ok := s.Ipxe[Firmware(i)]
if !ok { if !ok {
return nil, 0, errors.New("not found") return nil, 0, fmt.Errorf("unknown firmware type %d", i)
} }
return ioutil.NopCloser(bytes.NewBuffer(bs)), int64(len(bs)), nil return ioutil.NopCloser(bytes.NewBuffer(bs)), int64(len(bs)), nil