package uevent import ( "bytes" "errors" "io/fs" "log" "path/filepath" "unsafe" ) // Enumerate scans sysfs and emits [Synthetic] events. It returns the first // error it encounters. // // The specified filesystem must present the sysfs root. func Enumerate( sysfs fs.FS, handleWalkErr func(error) error, events chan<- *Message, ) error { if handleWalkErr == nil { handleWalkErr = func(err error) error { if errors.Is(err, fs.ErrNotExist) { log.Println("enumerate", err) return nil } return err } } return fs.WalkDir(sysfs, "devices", func( path string, d fs.DirEntry, err error, ) error { if err != nil { return handleWalkErr(err) } if d.IsDir() || d.Name() != "uevent" { return nil } msg := Message{ Action: Synthetic, // cleans path, appears to be compatible with kernel behaviour DevPath: filepath.Dir(path), } var target string if target, err = fs.ReadLink( sysfs, filepath.Join(msg.DevPath, "subsystem"), ); err != nil { if err = handleWalkErr(err); err != nil { return err } } else { msg.Env = append(msg.Env, "SUBSYSTEM="+filepath.Base(target)) } // read entire file: slicing does not copy var env []byte if env, err = fs.ReadFile(sysfs, path); err != nil { return handleWalkErr(err) } for _, s := range bytes.Split(env, []byte{'\n'}) { if len(s) == 0 { continue } msg.Env = append(msg.Env, unsafe.String(unsafe.SliceData(s), len(s))) } if len(msg.Env) == 0 { // this implies absent subsystem, its error is already handled return nil } if msg.DevPath != "" && msg.DevPath[0] != '/' { msg.DevPath = "/" + msg.DevPath } events <- &msg return nil }) }