@@ -21,6 +21,12 @@ import (
2121 "strings"
2222)
2323
24+ // ValidateQualifiedName checks the validity of a fully-qualified device name.
25+ func ValidateQualifiedName (name string ) error {
26+ _ , _ , _ , err := ParseFullyQualifiedName (name )
27+ return err
28+ }
29+
2430// ValidateKind checks the validity of a CDI kind.
2531// The syntax for a device kind“ is
2632//
@@ -74,19 +80,19 @@ func validateVendorOrClassName(name string) error {
7480 if name == "" {
7581 return fmt .Errorf ("empty name" )
7682 }
77- if ! IsLetter (rune (name [0 ])) {
83+ if ! isLetter (rune (name [0 ])) {
7884 return fmt .Errorf ("%q, should start with letter" , name )
7985 }
8086 for _ , c := range string (name [1 : len (name )- 1 ]) {
8187 switch {
82- case IsAlphaNumeric (c ):
88+ case isAlphaNumeric (c ):
8389 case c == '_' || c == '-' || c == '.' :
8490 default :
8591 return fmt .Errorf ("invalid character '%c' in name %q" ,
8692 c , name )
8793 }
8894 }
89- if ! IsAlphaNumeric (rune (name [len (name )- 1 ])) {
95+ if ! isAlphaNumeric (rune (name [len (name )- 1 ])) {
9096 return fmt .Errorf ("%q, should end with a letter or digit" , name )
9197 }
9298
@@ -102,38 +108,107 @@ func ValidateDeviceName(name string) error {
102108 if name == "" {
103109 return fmt .Errorf ("invalid (empty) device name" )
104110 }
105- if ! IsAlphaNumeric (rune (name [0 ])) {
111+ if ! isAlphaNumeric (rune (name [0 ])) {
106112 return fmt .Errorf ("invalid class %q, should start with a letter or digit" , name )
107113 }
108114 if len (name ) == 1 {
109115 return nil
110116 }
111117 for _ , c := range string (name [1 : len (name )- 1 ]) {
112118 switch {
113- case IsAlphaNumeric (c ):
119+ case isAlphaNumeric (c ):
114120 case c == '_' || c == '-' || c == '.' || c == ':' :
115121 default :
116122 return fmt .Errorf ("invalid character '%c' in device name %q" ,
117123 c , name )
118124 }
119125 }
120- if ! IsAlphaNumeric (rune (name [len (name )- 1 ])) {
126+ if ! isAlphaNumeric (rune (name [len (name )- 1 ])) {
121127 return fmt .Errorf ("invalid name %q, should end with a letter or digit" , name )
122128 }
123129 return nil
124130}
125131
126- // IsLetter reports whether the rune is a letter.
127- func IsLetter (c rune ) bool {
132+ // ParseFullyQualifiedName splits a fully-qualified name into device vendor, class,
133+ // and name. If the device fails to parse as a qualified name, or if any
134+ // of the split components fail to pass syntax validation, vendor and
135+ // class are returned as empty, together with the verbatim input as the
136+ // name and an error describing the reason for failure.
137+ func ParseFullyQualifiedName (device string ) (string , string , string , error ) {
138+ vendor , class , name := parseDevice (device )
139+
140+ if vendor == "" {
141+ return "" , "" , device , fmt .Errorf ("unqualified device %q, missing vendor" , device )
142+ }
143+ if class == "" {
144+ return "" , "" , device , fmt .Errorf ("unqualified device %q, missing class" , device )
145+ }
146+ if name == "" {
147+ return "" , "" , device , fmt .Errorf ("unqualified device %q, missing device name" , device )
148+ }
149+
150+ if err := ValidateVendorName (vendor ); err != nil {
151+ return "" , "" , device , fmt .Errorf ("invalid device %q: %w" , device , err )
152+ }
153+ if err := ValidateClassName (class ); err != nil {
154+ return "" , "" , device , fmt .Errorf ("invalid device %q: %w" , device , err )
155+ }
156+ if err := ValidateDeviceName (name ); err != nil {
157+ return "" , "" , device , fmt .Errorf ("invalid device %q: %w" , device , err )
158+ }
159+
160+ return vendor , class , name , nil
161+ }
162+
163+ // parseDevice tries to split a device name into vendor, class, and name.
164+ // If this fails, for instance in the case of unqualified device names,
165+ // parseDevice returns an empty vendor and class together with name set
166+ // to the verbatim input.
167+ func parseDevice (device string ) (string , string , string ) {
168+ if device == "" || device [0 ] == '/' {
169+ return "" , "" , device
170+ }
171+
172+ parts := strings .SplitN (device , "=" , 2 )
173+ if len (parts ) != 2 || parts [0 ] == "" || parts [1 ] == "" {
174+ return "" , "" , device
175+ }
176+
177+ name := parts [1 ]
178+ vendor , class := ParseKind (parts [0 ])
179+ if vendor == "" {
180+ return "" , "" , device
181+ }
182+
183+ return vendor , class , name
184+ }
185+
186+ // ParseKind splits a device qualifier into vendor and class.
187+ // The syntax for a device qualifier is
188+ //
189+ // "<vendor>/<class>"
190+ //
191+ // If parsing fails, an empty vendor and the class set to the
192+ // verbatim input is returned.
193+ func ParseKind (kind string ) (string , string ) {
194+ parts := strings .SplitN (kind , "/" , 2 )
195+ if len (parts ) != 2 || parts [0 ] == "" || parts [1 ] == "" {
196+ return "" , kind
197+ }
198+ return parts [0 ], parts [1 ]
199+ }
200+
201+ // isLetter reports whether the rune is a letter.
202+ func isLetter (c rune ) bool {
128203 return ('A' <= c && c <= 'Z' ) || ('a' <= c && c <= 'z' )
129204}
130205
131206// IsDigit reports whether the rune is a digit.
132- func IsDigit (c rune ) bool {
207+ func isDigit (c rune ) bool {
133208 return '0' <= c && c <= '9'
134209}
135210
136- // IsAlphaNumeric reports whether the rune is a letter or digit.
137- func IsAlphaNumeric (c rune ) bool {
138- return IsLetter (c ) || IsDigit (c )
211+ // isAlphaNumeric reports whether the rune is a letter or digit.
212+ func isAlphaNumeric (c rune ) bool {
213+ return isLetter (c ) || isDigit (c )
139214}
0 commit comments