• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ev3go / ev3dev / 556

09 Jan 2023 11:37AM UTC coverage: 63.876% (-0.07%) from 63.948%
556

cron

travis-ci-com

kortschak
ev3dev: use file registry to reduce file opening costs

77 of 77 new or added lines in 2 files covered. (100.0%)

1770 of 2771 relevant lines covered (63.88%)

6215.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

69.89
/ev3dev.go
1
// Copyright ©2016 The ev3go Authors. All rights reserved.
2
// Use of this source code is governed by a BSD-style
3
// license that can be found in the LICENSE file.
4

5
// Package ev3dev provides low level access to the ev3dev control and sensor drivers.
6
// See documentation at http://www.ev3dev.org/docs/drivers/.
7
//
8
// The API provided in the ev3dev package allows fluent chaining of action calls.
9
// Methods for each of the device handle types are split into three classes: action,
10
// result and constant. Action method calls return the receiver and result method calls
11
// return an error value generally with another result. Action methods result in
12
// a change of state in the robot while result methods return the requested attribute
13
// state of the robot. Constant methods return values that are constant for the device
14
// or sensor mode.
15
//
16
// To allow fluent call chains, errors are sticky for action methods and are cleared
17
// and returned by result methods. In a chain of calls the first error that is caused
18
// by an action method prevents execution of all subsequent action method calls, and
19
// is returned by the first result method called on the device handle, clearing the
20
// error state. Any attribute value returned by a call chain returning a non-nil error
21
// is invalid.
22
//
23
// To avoid confusion caused by multiple writes to the same underlying device by
24
// different handles, only one handle is allowed per physical device.
25
//
26
// In most cases, errors returned by functions in the ev3dev package implement
27
// the Causer error interface and will be able to print a stack trace if printed
28
// with the "+v" fmt verb.
29
package ev3dev
30

31
import (
32
        "bytes"
33
        "errors"
34
        "fmt"
35
        "io"
36
        "io/ioutil"
37
        "log"
38
        "math"
39
        "os"
40
        "path/filepath"
41
        "reflect"
42
        "sort"
43
        "strconv"
44
        "strings"
45
        "sync"
46
        "time"
47
)
48

49
const (
50
        linearPrefix = "linear"
51
        motorPrefix  = "motor"
52
        portPrefix   = "port"
53
        sensorPrefix = "sensor"
54
)
55

56
// prefix is the filesystem root prefix.
57
// Currently it is used only for testing.
58
var prefix = ""
59

60
const (
61
        // LEDPath is the path to the ev3 LED file system.
62
        LEDPath = "/sys/class/leds"
63

64
        // ButtonPath is the path to the ev3 button events.
65
        ButtonPath = "/dev/input/by-path/platform-gpio_keys-event"
66

67
        // LegoPortPath is the path to the ev3 lego-port file system.
68
        LegoPortPath = "/sys/class/lego-port"
69

70
        // SensorPath is the path to the ev3 lego-sensor file system.
71
        SensorPath = "/sys/class/lego-sensor"
72

73
        // TachoMotorPath is the path to the ev3 tacho-motor file system.
74
        TachoMotorPath = "/sys/class/tacho-motor"
75

76
        // ServoMotorPath is the path to the ev3 servo-motor file system.
77
        ServoMotorPath = "/sys/class/servo-motor"
78

79
        // DCMotorPath is the path to the ev3 dc-motor file system.
80
        DCMotorPath = "/sys/class/dc-motor"
81

82
        // PowerSupplyPath is the path to the ev3 power supply file system.
83
        PowerSupplyPath = "/sys/class/power_supply"
84
)
85

86
// These are the subsystem path definitions for all device classes.
87
const (
88
        address                   = "address"
89
        binData                   = "bin_data"
90
        batteryTechnology         = "technology"
91
        batteryType               = "type"
92
        binDataFormat             = "bin_data_format"
93
        brightness                = "brightness"
94
        command                   = "command"
95
        commands                  = "commands"
96
        countPerMeter             = "count_per_m"
97
        countPerRot               = "count_per_rot"
98
        currentNow                = "current_now"
99
        decimals                  = "decimals"
100
        delayOff                  = "delay_off"
101
        delayOn                   = "delay_on"
102
        direct                    = "direct"
103
        driverName                = "driver_name"
104
        dutyCycle                 = "duty_cycle"
105
        dutyCycleSetpoint         = "duty_cycle_sp"
106
        firmwareVersion           = "fw_version"
107
        fullTravelCount           = "full_travel_count"
108
        holdPID                   = "hold_pid"
109
        holdPIDkd                 = holdPID + "/" + kd
110
        holdPIDki                 = holdPID + "/" + ki
111
        holdPIDkp                 = holdPID + "/" + kp
112
        kd                        = "Kd"
113
        ki                        = "Ki"
114
        kp                        = "Kp"
115
        maxBrightness             = "max_brightness"
116
        maxPulseSetpoint          = "max_pulse_sp"
117
        midPulseSetpoint          = "mid_pulse_sp"
118
        minPulseSetpoint          = "min_pulse_sp"
119
        maxSpeed                  = "max_speed"
120
        mode                      = "mode"
121
        modes                     = "modes"
122
        numValues                 = "num_values"
123
        polarity                  = "polarity"
124
        pollRate                  = "poll_ms"
125
        position                  = "position"
126
        positionSetpoint          = "position_sp"
127
        power                     = "power"
128
        powerAutosuspendDelay     = power + "/" + "autosuspend_delay_ms"
129
        powerControl              = power + "/" + "control"
130
        powerRuntimeActiveTime    = power + "/" + "runtime_active_time"
131
        powerRuntimeStatus        = power + "/" + "runtime_status"
132
        powerRuntimeSuspendedTime = power + "/" + "runtime_suspended_time"
133
        rampDownSetpoint          = "ramp_down_sp"
134
        rampUpSetpoint            = "ramp_up_sp"
135
        rateSetpoint              = "rate_sp"
136
        setDevice                 = "set_device"
137
        speed                     = "speed"
138
        speedPID                  = "speed_pid"
139
        speedPIDkd                = speedPID + "/" + kd
140
        speedPIDki                = speedPID + "/" + ki
141
        speedPIDkp                = speedPID + "/" + kp
142
        speedSetpoint             = "speed_sp"
143
        state                     = "state"
144
        status                    = "status"
145
        stopAction                = "stop_action"
146
        stopActions               = "stop_actions"
147
        subsystem                 = "subsystem"
148
        textValues                = "text_values"
149
        timeSetpoint              = "time_sp"
150
        trigger                   = "trigger"
151
        uevent                    = "uevent"
152
        units                     = "units"
153
        value                     = "value"
154
        voltageMaxDesign          = "voltage_max_design"
155
        voltageMinDesign          = "voltage_min_design"
156
        voltageNow                = "voltage_now"
157
)
158

159
// Polarity represent motor polarity states.
160
type Polarity string
161

162
const (
163
        Normal   Polarity = "normal"
164
        Inversed Polarity = "inversed"
165
)
166

167
// MotorState is a flag set representing the state of a TachoMotor.
168
type MotorState uint
169

170
const (
171
        Running MotorState = 1 << iota
172
        Ramping
173
        Holding
174
        Overloaded
175
        Stalled
176
)
177

178
const (
179
        running    = "running"
180
        ramping    = "ramping"
181
        holding    = "holding"
182
        overloaded = "overloaded"
183
        stalled    = "stalled"
184
)
185

186
var motorStateTable = map[string]MotorState{
187
        running:    Running,
188
        ramping:    Ramping,
189
        holding:    Holding,
190
        overloaded: Overloaded,
191
        stalled:    Stalled,
192
}
193

194
func keys(states map[string]MotorState) []string {
3✔
195
        l := make([]string, 0, len(states))
3✔
196
        for k := range states {
18✔
197
                l = append(l, k)
15✔
198
        }
15✔
199
        sort.Strings(l)
3✔
200
        return l
3✔
201
}
202

203
var motorStates = []string{
204
        running,
205
        ramping,
206
        holding,
207
        overloaded,
208
        stalled,
209
}
210

211
// String satisfies the fmt.Stringer interface.
212
func (f MotorState) String() string {
882✔
213
        const stateMask = Running | Ramping | Holding | Overloaded | Stalled
882✔
214

882✔
215
        var b []byte
882✔
216
        for i, s := range motorStates {
5,292✔
217
                if f&(1<<uint(i)) != 0 {
5,084✔
218
                        if len(b) != 0 {
986✔
219
                                b = append(b, '|')
312✔
220
                        }
312✔
221
                        b = append(b, s...)
674✔
222
                }
223
        }
224
        if b == nil {
1,402✔
225
                return "none"
520✔
226
        }
520✔
227
        return string(b)
362✔
228
}
229

230
// StaterDevice is a device that can return a motor state.
231
type StaterDevice interface {
232
        Device
233
        State() (MotorState, error)
234
}
235

236
var canPoll = true
237

238
func motorState(d Device, f *os.File) (MotorState, error) {
352,419✔
239
        var b [4096]byte
352,419✔
240
        n, err := f.ReadAt(b[:], 0)
352,419✔
241
        if n == len(b) && err == nil {
352,419✔
242
                // This is more strict that justified by the
×
243
                // io.ReaderAt docs, but we prefer failure
×
244
                // and a short buffer is extremely unlikely.
×
245
                return 0, errors.New("ev3dev: buffer full")
×
246
        }
×
247
        if err == io.EOF {
704,838✔
248
                err = nil
352,419✔
249
        }
352,419✔
250
        return stateFrom(d, string(chomp(b[:n])), "", err)
352,419✔
251
}
252

253
func stateIsOK(stat, mask, want, not MotorState, any bool) bool {
352,438✔
254
        if any {
352,470✔
255
                stat &= mask
32✔
256
                return stat^not != 0 && stat&not == 0
32✔
257
        }
32✔
258
        return (stat&mask)^not == want|not
352,406✔
259
}
260

261
// DriverMismatch errors are returned when a device is found that
262
// does not match the requested driver.
263
type DriverMismatch struct {
264
        // Want is the string describing
265
        // the requested driver.
266
        Want string
267

268
        // Have is the string describing
269
        // the driver present on the device.
270
        Have string
271
}
272

273
func (e DriverMismatch) Error() string {
×
274
        return fmt.Sprintf("ev3dev: mismatched driver names: want %q but have %q", e.Want, e.Have)
×
275
}
×
276

277
// Device is an ev3dev API device.
278
type Device interface {
279
        // Path returns the sysfs path
280
        // for the device type.
281
        Path() string
282

283
        // Type returns the type of the
284
        // device, one of "linear", "motor",
285
        // "port" or "sensor".
286
        Type() string
287

288
        // Err returns and clears the
289
        // error state of the Device.
290
        Err() error
291

292
        fmt.Stringer
293
}
294

295
// idSetter is a Device that can set its
296
// device id and clear its error field.
297
type idSetter interface {
298
        Device
299

300
        // idInt returns the id integer value of
301
        // the device.
302
        // A nil idSetter must return -1.
303
        idInt() int
304

305
        // setID sets the device id to the given id
306
        // and clears the error field to allow an
307
        // already used Device to be reused.
308
        setID(id int) error
309
}
310

311
// FindAfter finds the first device after d matching the class of the
312
// dst Device with the given driver name, or returns an error. The
313
// concrete types of d and dst must match. On return with a nil
314
// error, dst is usable as a handle for the device.
315
// If d is nil, FindAfter finds the first matching device.
316
//
317
// Only ev3dev.Device implementations are supported.
318
func FindAfter(d, dst Device, driver string) error {
28✔
319
        _, ok := dst.(idSetter)
28✔
320
        if !ok {
28✔
321
                return fmt.Errorf("ev3dev: device type %T not supported", dst)
×
322
        }
×
323

324
        after := -1
28✔
325
        if d != nil {
56✔
326
                if reflect.TypeOf(d) != reflect.TypeOf(dst) {
28✔
327
                        return fmt.Errorf("ev3dev: device types do not match %T != %T", d, dst)
×
328
                }
×
329
                after = d.(idSetter).idInt()
28✔
330
        }
331

332
        id, err := deviceIDFor("", driver, dst, after)
28✔
333
        if err != nil {
28✔
334
                return err
×
335
        }
×
336
        return dst.(idSetter).setID(id)
28✔
337
}
338

339
// IsConnected returns whether the Device is connected.
340
func IsConnected(d Device) (ok bool, err error) {
76✔
341
        path := filepath.Join(d.Path(), d.String())
76✔
342
        _, err = os.Stat(path)
76✔
343
        if err == nil {
152✔
344
                return true, nil
76✔
345
        }
76✔
346
        if os.IsNotExist(err) {
×
347
                err = nil
×
348
        }
×
349
        return false, err
×
350
}
351

352
// AddressOf returns the port address of the Device.
353
func AddressOf(d Device) (string, error) {
414✔
354
        path := filepath.Join(d.Path(), d.String(), address)
414✔
355
        b, err := readFile(path)
414✔
356
        if err != nil {
442✔
357
                return "", fmt.Errorf("ev3dev: failed to read %s address: %w", d.Type(), err)
28✔
358
        }
28✔
359
        return string(chomp(b)), err
386✔
360
}
361

362
// DriverFor returns the driver name for the Device.
363
func DriverFor(d Device) (string, error) {
480✔
364
        path := filepath.Join(d.Path(), d.String(), driverName)
480✔
365
        b, err := readFile(path)
480✔
366
        if err != nil {
480✔
367
                return "", fmt.Errorf("ev3dev: failed to read %s driver name: %w", d.Type(), err)
×
368
        }
×
369
        return string(chomp(b)), err
480✔
370
}
371

372
// deviceIDFor returns the id for the given ev3 port name and driver of the Device.
373
// If the driver does not match the driver string, an id for the device is returned
374
// with a DriverMismatch error.
375
// If port is empty, the first device satisfying the driver name with an id after the
376
// specified after parameter is returned.
377
func deviceIDFor(port, driver string, d Device, after int) (int, error) {
398✔
378
        devNames, err := devicesIn(d.Path())
398✔
379
        if err != nil {
398✔
380
                return -1, fmt.Errorf("ev3dev: could not get devices for %s: %w", d.Path(), err)
×
381
        }
×
382
        devices, err := sortedDevices(devNames, d.Type())
398✔
383
        if err != nil {
398✔
384
                return -1, err
×
385
        }
×
386

387
        portBytes := []byte(port)
398✔
388
        driverBytes := []byte(driver)
398✔
389
        for _, device := range devices {
992✔
390
                if port == "" {
680✔
391
                        if device.id <= after {
120✔
392
                                continue
34✔
393
                        }
394
                        drvr, err := probeAttributeFor(d, device.name, driverName)
52✔
395
                        if os.IsNotExist(cause(err)) {
52✔
396
                                // If the device disappeared
×
397
                                // try the next one.
×
398
                                continue
×
399
                        }
400
                        if err != nil {
52✔
401
                                return -1, err
×
402
                        }
×
403
                        if !bytes.Equal(driverBytes, chomp(drvr)) {
52✔
404
                                continue
×
405
                        }
406
                        addr, err := probeAttributeFor(d, device.name, address)
52✔
407
                        if err != nil {
52✔
408
                                return -1, err
×
409
                        }
×
410
                        if inUse(d, addr) {
52✔
411
                                continue
×
412
                        }
413
                        return device.id, nil
52✔
414
                }
415

416
                addr, err := probeAttributeFor(d, device.name, address)
508✔
417
                if err != nil {
508✔
418
                        return -1, err
×
419
                }
×
420
                if !bytes.Equal(portBytes, chomp(addr)) {
670✔
421
                        continue
162✔
422
                }
423
                if inUse(d, addr) {
346✔
424
                        return -1, fmt.Errorf("ev3dev: port %s in use", port)
×
425
                }
×
426
                drvr, err := probeAttributeFor(d, device.name, driverName)
346✔
427
                if err != nil {
346✔
428
                        return -1, err
×
429
                }
×
430
                if !bytes.Equal(driverBytes, chomp(drvr)) {
358✔
431
                        err = DriverMismatch{Want: driver, Have: string(chomp(drvr))}
12✔
432
                }
12✔
433
                return device.id, err
346✔
434
        }
435

436
        if port != "" {
×
437
                return -1, fmt.Errorf("ev3dev: could not find device for driver %q on port %s", driver, port)
×
438
        }
×
439
        if after < 0 {
×
440
                return -1, fmt.Errorf("ev3dev: could not find device for driver %q", driver)
×
441
        }
×
442
        return -1, fmt.Errorf("ev3dev: could find device with driver name %q after %s%d", driver, d.Type(), after)
×
443
}
444

445
func probeAttributeFor(d Device, name, attr string) ([]byte, error) {
958✔
446
        path := filepath.Join(d.Path(), name, attr)
958✔
447
        b, err := readFile(path)
958✔
448
        if err != nil {
958✔
449
                return nil, newAttrOpError(d, attr, string(b), "read", err)
×
450
        }
×
451
        return b, nil
958✔
452
}
453

454
var (
455
        resLock   sync.Mutex
456
        resources = map[string]map[string]Device{
457
                "in":   make(map[string]Device),
458
                "out":  make(map[string]Device),
459
                "port": make(map[string]Device),
460
        }
461
)
462

463
func inUse(d Device, address []byte) bool {
398✔
464
        typ := d.Type()
398✔
465
        switch typ {
398✔
466
        case "linear", "motor":
310✔
467
                typ = "out"
310✔
468
        case "sensor":
48✔
469
                typ = "in"
48✔
470
        }
471
        id := d.String()
398✔
472

398✔
473
        resLock.Lock()
398✔
474
        defer resLock.Unlock()
398✔
475

398✔
476
        attached, exists := resources[typ][string(address)]
398✔
477
        if !exists {
458✔
478
                if id[len(id)-1] != '*' {
80✔
479
                        resources[typ][string(address)] = d
20✔
480
                }
20✔
481
                return false
60✔
482
        }
483
        addr, err := AddressOf(attached)
338✔
484
        if err != nil || addr != string(address) {
676✔
485
                if id[len(id)-1] != '*' {
346✔
486
                        resources[typ][string(address)] = d
8✔
487
                }
8✔
488
                return false
338✔
489
        }
490
        return true
×
491
}
492

493
func devicesIn(path string) ([]string, error) {
414✔
494
        f, err := os.Open(path)
414✔
495
        if err != nil {
416✔
496
                return nil, err
2✔
497
        }
2✔
498
        defer f.Close()
412✔
499
        return f.Readdirnames(0)
412✔
500
}
501

502
func sortedDevices(names []string, prefix string) ([]idDevice, error) {
398✔
503
        devices := make([]idDevice, 0, len(names))
398✔
504
        for _, n := range names {
1,272✔
505
                if !strings.HasPrefix(n, prefix) {
874✔
506
                        continue
×
507
                }
508
                id, err := strconv.Atoi(n[len(prefix):])
874✔
509
                if err != nil {
874✔
510
                        return nil, fmt.Errorf("ev3dev: could not parse id from device name %q: %w", n, err)
×
511
                }
×
512
                devices = append(devices, idDevice{id: id, name: n})
874✔
513
        }
514
        sort.Sort(byID(devices))
398✔
515
        return devices, nil
398✔
516
}
517

518
type idDevice struct {
519
        id   int
520
        name string
521
}
522

523
type byID []idDevice
524

525
func (d byID) Len() int           { return len(d) }
398✔
526
func (d byID) Less(i, j int) bool { return d[i].id < d[j].id }
529✔
527
func (d byID) Swap(i, j int)      { d[i], d[j] = d[j], d[i] }
115✔
528

529
func attributeOf(d Device, attr string) (dev Device, data string, _attr string, err error) {
2,706✔
530
        err = d.Err()
2,706✔
531
        if err != nil {
2,706✔
532
                return d, "", "", err
×
533
        }
×
534
        path := filepath.Join(d.Path(), d.String(), attr)
2,706✔
535
        b, err := readFile(path)
2,706✔
536
        if err != nil {
2,706✔
537
                return d, "", "", newAttrOpError(d, attr, string(b), "read", err)
×
538
        }
×
539
        return d, string(chomp(b)), attr, nil
2,706✔
540
}
541

542
func chomp(b []byte) []byte {
356,909✔
543
        if b[len(b)-1] == '\n' {
713,814✔
544
                return b[:len(b)-1]
356,905✔
545
        }
356,905✔
546
        return b
4✔
547
}
548

549
func intFrom(d Device, data, attr string, err error) (int, error) {
1,232✔
550
        if err != nil {
1,234✔
551
                return -1, err
2✔
552
        }
2✔
553
        i, err := strconv.Atoi(data)
1,230✔
554
        if err != nil {
1,232✔
555
                return -1, newParseError(d, attr, err)
2✔
556
        }
2✔
557
        return i, nil
1,228✔
558
}
559

560
func float64From(d Device, data, attr string, err error) (float64, error) {
18✔
561
        if err != nil {
20✔
562
                return math.NaN(), err
2✔
563
        }
2✔
564
        f, err := strconv.ParseFloat(data, 64)
16✔
565
        if err != nil {
18✔
566
                return math.NaN(), newParseError(d, attr, err)
2✔
567
        }
2✔
568
        return f, nil
14✔
569
}
570

571
func durationFrom(dev Device, data, attr string, err error) (time.Duration, error) {
216✔
572
        if err != nil {
218✔
573
                return -1, err
2✔
574
        }
2✔
575
        d, err := strconv.Atoi(data)
214✔
576
        if err != nil {
216✔
577
                return -1, newParseError(dev, attr, err)
2✔
578
        }
2✔
579
        return time.Duration(d) * time.Millisecond, nil
212✔
580
}
581

582
func stringFrom(_ Device, data, _ string, err error) (string, error) {
430✔
583
        return data, err
430✔
584
}
430✔
585

586
func stringSliceFrom(_ Device, data, _ string, err error) ([]string, error) {
706✔
587
        if err != nil {
708✔
588
                return nil, err
2✔
589
        }
2✔
590
        if len(data) == 0 {
968✔
591
                return nil, nil
264✔
592
        }
264✔
593
        return strings.Split(data, " "), nil
440✔
594
}
595

596
func stateFrom(d Device, data, _ string, err error) (MotorState, error) {
352,549✔
597
        if err != nil {
352,551✔
598
                return 0, err
2✔
599
        }
2✔
600
        if data == "" {
704,956✔
601
                return 0, nil
352,409✔
602
        }
352,409✔
603
        var stat MotorState
138✔
604
        for _, s := range strings.Split(data, " ") {
388✔
605
                bit, ok := motorStateTable[s]
250✔
606
                if !ok {
253✔
607
                        return 0, newInvalidValueError(d, state, "unrecognized motor state", s, keys(motorStateTable))
3✔
608
                }
3✔
609
                stat |= bit
247✔
610
        }
611
        return stat, nil
135✔
612
}
613

614
func ueventFrom(d Device, data, attr string, err error) (map[string]string, error) {
42✔
615
        if err != nil {
44✔
616
                return nil, err
2✔
617
        }
2✔
618
        if len(data) == 0 {
58✔
619
                return nil, nil
18✔
620
        }
18✔
621
        uevent := make(map[string]string)
22✔
622
        for _, l := range strings.Split(data, "\n") {
74✔
623
                parts := strings.Split(l, "=")
52✔
624
                if len(parts) != 2 {
54✔
625
                        return nil, newParseError(d, attr, syntaxError(l))
2✔
626
                }
2✔
627
                uevent[parts[0]] = parts[1]
50✔
628
        }
629
        return uevent, nil
20✔
630
}
631

632
func setAttributeOf(d Device, attr, data string) error {
820✔
633
        path := filepath.Join(d.Path(), d.String(), attr)
820✔
634
        err := ioutil.WriteFile(path, []byte(data), 0)
820✔
635
        if err != nil {
820✔
636
                return newAttrOpError(d, attr, data, "set", err)
×
637
        }
×
638
        return nil
820✔
639
}
640

641
var (
642
        isTesting bool
643

644
        // files and fileRegLock record files that have been opened
645
        // during the life of the program. There is currently no
646
        // mechanism to remove a file from the registry, but this is
647
        // probably not a problem given that attached devices are
648
        // extremely likely to remain attached for the life of the
649
        // program.
650
        fileRegLock sync.Mutex
651
        files       = make(map[string]*os.File)
652
)
653

654
func readFile(path string) ([]byte, error) {
4,562✔
655
        if isTesting {
9,124✔
656
                // FIXME(kortschak): Make this work always.
4,562✔
657
                //
4,562✔
658
                // This horror is here to work around flakey
4,562✔
659
                // kernel hangs that happen during testing if
4,562✔
660
                // we use the fast path code below.
4,562✔
661
                // The flakes appear to be in bazil.org/fuse
4,562✔
662
                // or in FUSE itself since the behaviour is
4,562✔
663
                // dependent on bazil.org/fuse version. The
4,562✔
664
                // behaviour is very variable, depending on
4,562✔
665
                // timing and debugging output.
4,562✔
666
                //
4,562✔
667
                // The upshot of this is that the code below
4,562✔
668
                // is only exercised on actual devices. This
4,562✔
669
                // is not terrible, since bugs should show up
4,562✔
670
                // quickly and the remainder of the code is
4,562✔
671
                // properly tested using the slow path.
4,562✔
672
                return ioutil.ReadFile(path)
4,562✔
673
        }
4,562✔
674

675
        f, err := fileFor(path)
×
676
        if err != nil {
×
677
                return nil, err
×
678
        }
×
679
        if f == nil {
×
680
                // Don't try fast path for files that already
×
681
                // failed to read into short buffer.
×
682
                return ioutil.ReadFile(path)
×
683
        }
×
684
        var buf [256]byte
×
685
        n, err := f.ReadAt(buf[:], 0)
×
686
        if err == nil {
×
687
                // EV3 sysfs files are maximally 4096 byte
×
688
                // (memory page size), but files are likely
×
689
                // to be significantly smaller. The size of
×
690
                // 128 bytes was suggested in ev3go/ev3dev#93,
×
691
                // but this fails with the LED trigger files.
×
692
                // We log if there is no error since ReadAt
×
693
                // will always return an error if n is less
×
694
                // than len(buf). So we catch all the cases
×
695
                // where the file is longer, with a small number
×
696
                // of false positives where the file is exactly
×
697
                // the length of the buffer. Bump the length
×
698
                // of the buffer when that happens.
×
699
                log.Printf("ev3dev: buffer too short for %s: falling back to ioutil.ReadFile", path)
×
700
                fileRegLock.Lock()
×
701
                f.Close()
×
702
                files[path] = nil
×
703
                fileRegLock.Unlock()
×
704
                return ioutil.ReadFile(path)
×
705
        }
×
706
        if err == io.EOF {
×
707
                err = nil
×
708
        }
×
709
        return buf[:n], err
×
710
}
711

712
func fileFor(path string) (*os.File, error) {
×
713
        defer fileRegLock.Unlock()
×
714
        fileRegLock.Lock()
×
715
        f, ok := files[path]
×
716
        if ok {
×
717
                return f, nil
×
718
        }
×
719
        f, err := os.Open(path)
×
720
        if err != nil {
×
721
                return nil, err
×
722
        }
×
723
        files[path] = f
×
724
        return f, nil
×
725
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc