blob: 690b1395c8eab7bcd859ac6c9e268bc18743fcaa [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
// Program to map Fuchsia devices to their associated serial connection on a
// controller machine. Connects to all available serial connections on the
// controller machine, invokes `dlog` (kernel debug log) and attempts to
// extract nodename=(.*?) from netsvc start up.
import (
"bufio"
"context"
"flag"
"fmt"
"io"
"io/ioutil"
"os/exec"
"path"
"regexp"
"strings"
"sync"
"time"
"go.fuchsia.dev/tools/logger"
"go.fuchsia.dev/tools/serial"
)
const (
// The default path under which /lib/udev/rules.d/60-serial.rules
// will create serial device files with full identifiers.
serialDir = "/dev/serial/by-id"
)
var (
getFastbootSernums bool
)
type DeviceInfo struct {
mac string
nodename string
serial string
}
func init() {
flag.BoolVar(&getFastbootSernums, "get-fastboot-sernums", false, "Retrieve the fastboot serial numbers as well.")
}
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 runFastbootReboot() error {
cmd := exec.Command("fastboot", "reboot")
return cmd.Run()
}
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 != "" {
if err := runFastbootReboot(); err != nil {
return "", err
}
return serial, nil
}
return "", fmt.Errorf("could not get device into fastboot")
}
func main() {
ctx := context.Background()
files, err := ioutil.ReadDir(serialDir)
if err != nil {
logger.Fatalf(ctx, "failed to readdir() %s: %s", serialDir, err)
}
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())
go func() {
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()
}
}()
}
wg.Wait()
fmt.Printf("\n\nserial\tnodename\tmac\tfastbootSernum\n")
for k, v := range mapping {
if getFastbootSernums {
serial, err := getSerialNumber(k)
time.Sleep(30 * time.Second)
if err != nil {
fmt.Printf("err: %v\n", err)
}
v.serial = serial
}
fmt.Printf("%s\t%s\t%s\t%s\n", k, v.nodename, v.mac, v.serial)
}
}