[serial-mapper] Get mac and serial number

Change serial mapper to get the mac address and serial number of each
device along with then nodename.

Change-Id: I1c6dcf85a0c3771699d9d7a231a9a0b24e248c88
diff --git a/cmd/serial_mapper/main.go b/cmd/serial_mapper/main.go
index 1465c22..5c01d66 100644
--- a/cmd/serial_mapper/main.go
+++ b/cmd/serial_mapper/main.go
@@ -15,8 +15,11 @@
 	"fmt"
 	"io"
 	"io/ioutil"
+	"os/exec"
 	"path"
 	"regexp"
+	"strings"
+	"sync"
 	"time"
 
 	"go.fuchsia.dev/tools/logger"
@@ -29,6 +32,83 @@
 	serialDir = "/dev/serial/by-id"
 )
 
+type DeviceInfo struct {
+	mac      string
+	nodename string
+	serial   string
+}
+
+func getDeviceInfo(fPath string) (*DeviceInfo, error) {
+	device, err := serial.Open(fPath)
+	defer device.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	if _, err = io.WriteString(device, "\ndlog\n"); err != nil {
+		return nil, err
+	}
+
+	nodenameRe := regexp.MustCompile("nodename='(.*?)'")
+	macRe := regexp.MustCompile("macaddr: ([0-9a-f]{2}[:]){5}([0-9a-f]{2})")
+
+	scanner := bufio.NewScanner(device)
+	info := &DeviceInfo{
+		nodename: "",
+		mac:      "",
+		serial:   "",
+	}
+	for scanner.Scan() {
+		line := scanner.Text()
+		nodenameMatch := nodenameRe.FindStringSubmatch(line)
+		macMatch := macRe.FindStringSubmatch(line)
+
+		if nodenameMatch != nil {
+			info.nodename = nodenameMatch[1]
+		} else if macMatch != nil {
+			info.mac = strings.Split(macMatch[0], " ")[1]
+		}
+
+		if info.nodename != "" && info.mac != "" {
+			break
+		}
+	}
+
+	if err := scanner.Err(); err != nil {
+		return nil, err
+	} else if info.nodename == "" && info.mac == "" {
+		return nil, fmt.Errorf("info not found")
+	}
+
+	return info, nil
+}
+
+func runFastbootDevices() string {
+	cmd := exec.Command("fastboot", "devices")
+	output, err := cmd.Output()
+	if err != nil {
+		return ""
+	}
+	return strings.Split(string(output), "\t")[0]
+}
+
+func getSerialNumber(fPath string) (string, error) {
+	device, err := serial.Open(fPath)
+	defer device.Close()
+	if err != nil {
+		return "", err
+	}
+	if _, err := io.WriteString(device, "\ndm reboot-bootloader\n"); err != nil {
+		return "", err
+	}
+	time.Sleep(15 * time.Second)
+	if serial := runFastbootDevices(); serial != "" {
+		io.WriteString(device, "\x03")
+		return serial, nil
+	}
+	return "", fmt.Errorf("could not get device into fastboot")
+}
+
 func main() {
 	ctx := context.Background()
 
@@ -37,62 +117,34 @@
 		logger.Fatalf(ctx, "failed to readdir() %s: %s", serialDir, err)
 	}
 
-	mapping := make(map[string]string)
-	re := regexp.MustCompile("nodename='(.*?)'")
+	mapping := make(map[string]*DeviceInfo)
+	var mapLock sync.Mutex
 
+	var wg sync.WaitGroup
+	wg.Add(len(files))
 	for _, f := range files {
 		fPath := path.Join(serialDir, f.Name())
-
-		device, err := serial.Open(fPath)
-		if err != nil {
-			logger.Errorf(ctx, "unable to open() %s: %s", fPath, err)
-		}
-
-		_, err = io.WriteString(device, "\ndlog\n")
-		if err != nil {
-			logger.Errorf(ctx, "failed to writestring() to %s: %s", fPath, err)
-		}
-
-		errs := make(chan error)
-		c := make(chan string)
-
 		go func() {
-			defer close(c)
-			defer close(errs)
-
-			scanner := bufio.NewScanner(device)
-			for scanner.Scan() {
-				line := scanner.Text()
-				match := re.FindStringSubmatch(line)
-				if match != nil {
-					c <- match[1]
-					break
-				}
-			}
-			if err := scanner.Err(); err != nil {
-				errs <- fmt.Errorf("scan() error: %s", err)
+			defer wg.Done()
+			info, err := getDeviceInfo(fPath)
+			if err != nil {
+				logger.Errorf(ctx, "could not get device info from %s: %v", fPath, err)
+			} else {
+				mapLock.Lock()
+				mapping[fPath] = info
+				mapLock.Unlock()
 			}
 		}()
-
-		select {
-		case err := <-errs:
-			logger.Errorf(ctx, "%s", err)
-		// Timeout after 10 seconds.
-		case <-time.After(10 * time.Second):
-			logger.Errorf(ctx, "timed out %s", fPath)
-		case nodename := <-c:
-			mapping[nodename] = fPath
-		}
-
-		device.Close()
 	}
-
-	if len(mapping) > 0 {
-		fmt.Println("device:serial mapping:")
-		for k, v := range mapping {
-			fmt.Printf("%s\t%s\n", k, v)
+	wg.Wait()
+	fmt.Printf("\n\nserial\tnodename\tmac\tfastbootSernum\n")
+	for k, v := range mapping {
+		serial, err := getSerialNumber(k)
+		if err != nil {
+			fmt.Printf("err: %v\n", err)
+			continue
 		}
-	} else {
-		fmt.Println("found nothing!")
+		v.serial = serial
+		fmt.Printf("%s\t%s\t%s\t%s\n", k, v.nodename, v.mac, v.serial)
 	}
 }