@@ -22,6 +22,7 @@ import (
2222 "os"
2323 "os/exec"
2424 "path/filepath"
25+ "regexp"
2526 "strconv"
2627 "strings"
2728
@@ -225,48 +226,47 @@ func (l deviceLib) getImexChannelCount() (int, error) {
225226 return 2048 , nil
226227}
227228
229+ // getDeviceMajor searches for one "<integer> <name>" occurrence in the
230+ // "Character devices" section of the /proc/devices file, and returns the
231+ // integer.
228232func (l deviceLib ) getDeviceMajor (name string ) (int , error ) {
229- file , err := os .Open (procDevicesPath )
230- if err != nil {
231- return - 1 , err
232- }
233- defer file .Close ()
234-
235- scanner := bufio .NewScanner (file )
236- foundCharDevices := false
237233
238- for scanner .Scan () {
239- line := scanner .Text ()
234+ re := regexp .MustCompile (
235+ // The `(?s)` flag makes `.` match newlines. The greedy modifier in
236+ // `.*?` ensures to pick the first match after "Character devices".
237+ // Extract the number as capture group (the first and only group).
238+ "(?s)Character devices:.*?" +
239+ "([0-9]+) " + regexp .QuoteMeta (name ) +
240+ ".*Block devices:" ,
241+ )
240242
241- // Ignore empty lines
242- if line == "" {
243- continue
244- }
243+ data , err := os . ReadFile ( procDevicesPath )
244+ if err != nil {
245+ return - 1 , fmt . Errorf ( "error reading '%s': %w" , procDevicesPath , err )
246+ }
245247
246- // Check for any line with text followed by a colon (header)
247- if strings .Contains (line , ":" ) {
248- // Stop if we've already found the character devices section and reached another section
249- if foundCharDevices {
250- break
251- }
252- // Check if we entered the character devices section
253- if strings .HasSuffix (line , ":" ) && strings .HasPrefix (line , "Character" ) {
254- foundCharDevices = true
255- }
256- // Continue to the next line, regardless
257- continue
258- }
248+ // Expect precisely one match: first element is the total match, second
249+ // element corresponds to first capture group within that match (i.e., the
250+ // number of interest).
251+ matches := re .FindStringSubmatch (string (data ))
252+ if len (matches ) != 2 {
253+ return - 1 , fmt .Errorf ("error parsing '%s': unexpected regex match: %v" , procDevicesPath , matches )
254+ }
259255
260- // If we've passed the character devices section, check for nvidiaCapsImexChannelsDeviceName
261- if foundCharDevices {
262- parts := strings . Fields ( line )
263- if len ( parts ) == 2 && parts [ 1 ] == name {
264- return strconv .Atoi ( parts [ 0 ] )
265- }
266- }
256+ // Convert capture group content to integer. Perform upper bound check:
257+ // value must fit into 32-bit integer (it's then also guaranteed to fit into
258+ // a 32-bit unsigned integer, which is the type that must be passed to
259+ // unix.Mkdev()).
260+ major , err := strconv .ParseInt ( matches [ 1 ], 10 , 32 )
261+ if err != nil {
262+ return - 1 , fmt . Errorf ( "int conversion failed for '%v': %w" , matches [ 1 ], err )
267263 }
268264
269- return - 1 , scanner .Err ()
265+ // ParseInt() always returns an integer of explicit type `int64`. We have
266+ // performed an upper bound check so it's safe to convert this to `int`
267+ // (which is documented as "int is a signed integer type that is at least 32
268+ // bits in size", so in theory it could be smaller than int64).
269+ return int (major ), nil
270270}
271271
272272func (l deviceLib ) parseNVCapDeviceInfo (nvcapsFilePath string ) (* nvcapDeviceInfo , error ) {
0 commit comments