1- // FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
2- //go:build go1.23
3-
41// Package resolvconf is used to generate a container's /etc/resolv.conf file.
52//
63// Constructor Load and Parse read a resolv.conf file from the filesystem or
@@ -21,19 +18,15 @@ import (
2118 "bufio"
2219 "bytes"
2320 "context"
24- "fmt"
2521 "io"
26- "io/fs"
2722 "net/netip"
2823 "os"
24+ "slices"
2925 "strconv"
3026 "strings"
31- "text/template"
3227
33- "github.com/containerd/log"
34- "github.com/moby/sys/atomicwriter"
35- "github.com/opencontainers/go-digest"
36- "github.com/pkg/errors"
28+ "github.com/moby/buildkit/errdefs"
29+ "github.com/moby/buildkit/util/bklog"
3730)
3831
3932// Fallback nameservers, to use if none can be obtained from the host or command
@@ -70,7 +63,7 @@ type ExtDNSEntry struct {
7063
7164func (ed ExtDNSEntry ) String () string {
7265 if ed .HostLoopback {
73- return fmt . Sprintf ( "host(%s)" , ed .Addr )
66+ return "host(" + ed .Addr . String () + ")"
7467 }
7568 return ed .Addr .String ()
7669}
@@ -119,7 +112,7 @@ func Parse(reader io.Reader, path string) (ResolvConf, error) {
119112 rc .processLine (scanner .Text ())
120113 }
121114 if err := scanner .Err (); err != nil {
122- return ResolvConf {}, errSystem { err }
115+ return ResolvConf {}, errdefs . Internal ( err )
123116 }
124117 if _ , ok := rc .Option ("ndots" ); ok {
125118 rc .md .NDotsFrom = "host"
@@ -141,7 +134,7 @@ func (rc *ResolvConf) SetHeader(c string) {
141134
142135// NameServers returns addresses used in nameserver directives.
143136func (rc * ResolvConf ) NameServers () []netip.Addr {
144- return append ([]netip. Addr ( nil ), rc .nameServers ... )
137+ return slices . Clone ( rc .nameServers )
145138}
146139
147140// OverrideNameServers replaces the current set of nameservers.
@@ -152,7 +145,7 @@ func (rc *ResolvConf) OverrideNameServers(nameServers []netip.Addr) {
152145
153146// Search returns the current DNS search domains.
154147func (rc * ResolvConf ) Search () []string {
155- return append ([] string ( nil ), rc .search ... )
148+ return slices . Clone ( rc .search )
156149}
157150
158151// OverrideSearch replaces the current DNS search domains.
@@ -169,7 +162,7 @@ func (rc *ResolvConf) OverrideSearch(search []string) {
169162
170163// Options returns the current options.
171164func (rc * ResolvConf ) Options () []string {
172- return append ([] string ( nil ), rc .options ... )
165+ return slices . Clone ( rc .options )
173166}
174167
175168// Option finds the last option named search, and returns (value, true) if
@@ -181,7 +174,7 @@ func (rc *ResolvConf) Options() []string {
181174// Option("ndots") -> ("1", true)
182175// Option("edns0") -> ("", true)
183176func (rc * ResolvConf ) Option (search string ) (string , bool ) {
184- for i := len (rc .options ) - 1 ; i >= 0 ; i -= 1 {
177+ for i := len (rc .options ) - 1 ; i >= 0 ; i -- {
185178 k , v , _ := strings .Cut (rc .options [i ], ":" )
186179 if k == search {
187180 return v , true
@@ -192,7 +185,7 @@ func (rc *ResolvConf) Option(search string) (string, bool) {
192185
193186// OverrideOptions replaces the current DNS options.
194187func (rc * ResolvConf ) OverrideOptions (options []string ) {
195- rc .options = append ([] string ( nil ), options ... )
188+ rc .options = slices . Clone ( options )
196189 rc .md .NDotsFrom = ""
197190 if _ , exists := rc .Option ("ndots" ); exists {
198191 rc .md .NDotsFrom = "override"
@@ -227,7 +220,7 @@ func (rc *ResolvConf) TransformForLegacyNw(ipv6 bool) {
227220 }
228221 rc .nameServers = filtered
229222 if len (rc .nameServers ) == 0 {
230- log .G (context .TODO ()).Info ("No non-localhost DNS nameservers are left in resolv.conf. Using default external servers" )
223+ bklog .G (context .TODO ()).Info ("No non-localhost DNS nameservers are left in resolv.conf. Using default external servers" )
231224 rc .nameServers = defaultNSAddrs (ipv6 )
232225 rc .md .Warnings = append (rc .md .Warnings , "Used default nameservers." )
233226 }
@@ -283,145 +276,123 @@ func (rc *ResolvConf) TransformForIntNS(
283276 if len (rc .md .ExtNameServers ) == 0 {
284277 rc .md .Warnings = append (rc .md .Warnings , "NO EXTERNAL NAMESERVERS DEFINED" )
285278 }
286- return append ([] ExtDNSEntry ( nil ), rc .md .ExtNameServers ... ), nil
279+ return slices . Clone ( rc .md .ExtNameServers ), nil
287280}
288281
289282// Generate returns content suitable for writing to a resolv.conf file. If comments
290283// is true, the file will include header information if supplied, and a trailing
291284// comment that describes how the file was constructed and lists external resolvers.
292285func (rc * ResolvConf ) Generate (comments bool ) ([]byte , error ) {
293- s := struct {
294- Md * metadata
295- NameServers []netip.Addr
296- Search []string
297- Options []string
298- Other []string
299- Overrides []string
300- Comments bool
301- }{
302- Md : & rc .md ,
303- NameServers : rc .nameServers ,
304- Search : rc .search ,
305- Options : rc .options ,
306- Other : rc .other ,
307- Comments : comments ,
308- }
309- if rc .md .NSOverride {
310- s .Overrides = append (s .Overrides , "nameservers" )
311- }
312- if rc .md .SearchOverride {
313- s .Overrides = append (s .Overrides , "search" )
314- }
315- if rc .md .OptionsOverride {
316- s .Overrides = append (s .Overrides , "options" )
317- }
318-
319- const templateText = `{{if .Comments}}{{with .Md.Header}}{{.}}
320-
321- {{end}}{{end}}{{range .NameServers -}}
322- nameserver {{.}}
323- {{end}}{{with .Search -}}
324- search {{join . " "}}
325- {{end}}{{with .Options -}}
326- options {{join . " "}}
327- {{end}}{{with .Other -}}
328- {{join . "\n"}}
329- {{end}}{{if .Comments}}
330- # Based on host file: '{{.Md.SourcePath}}'{{with .Md.Transform}} ({{.}}){{end}}
331- {{range .Md.Warnings -}}
332- # {{.}}
333- {{end -}}
334- {{with .Md.ExtNameServers -}}
335- # ExtServers: {{.}}
336- {{end -}}
337- {{with .Md.InvalidNSs -}}
338- # Invalid nameservers: {{.}}
339- {{end -}}
340- # Overrides: {{.Overrides}}
341- {{with .Md.NDotsFrom -}}
342- # Option ndots from: {{.}}
343- {{end -}}
344- {{end -}}
345- `
346-
347- funcs := template.FuncMap {"join" : strings .Join }
348- var buf bytes.Buffer
349- templ , err := template .New ("summary" ).Funcs (funcs ).Parse (templateText )
350- if err != nil {
351- return nil , errSystem {err }
286+ var b bytes.Buffer
287+ b .Grow (512 ) // estimated size for a regular resolv.conf we produce.
288+
289+ if comments && rc .md .Header != "" {
290+ b .WriteString (rc .md .Header + "\n " )
291+ b .WriteByte ('\n' )
292+ }
293+ for _ , ns := range rc .nameServers {
294+ b .WriteString ("nameserver " )
295+ b .WriteString (ns .String ())
296+ b .WriteByte ('\n' )
297+ }
298+ if len (rc .search ) > 0 {
299+ b .WriteString ("search " )
300+ for i , s := range rc .search {
301+ if i > 0 {
302+ b .WriteByte (' ' )
303+ }
304+ b .WriteString (s )
305+ }
306+ b .WriteByte ('\n' )
352307 }
353- if err := templ .Execute (& buf , s ); err != nil {
354- return nil , errSystem {err }
308+ if len (rc .options ) > 0 {
309+ b .WriteString ("options " )
310+ for i , s := range rc .options {
311+ if i > 0 {
312+ b .WriteByte (' ' )
313+ }
314+ b .WriteString (s )
315+ }
316+ b .WriteByte ('\n' )
355317 }
356- return buf .Bytes (), nil
357- }
358-
359- // WriteFile generates content and writes it to path. If hashPath is non-zero, it
360- // also writes a file containing a hash of the content, to enable UserModified()
361- // to determine whether the file has been modified.
362- func (rc * ResolvConf ) WriteFile (path , hashPath string , perm os.FileMode ) error {
363- content , err := rc .Generate (true )
364- if err != nil {
365- return err
318+ for _ , o := range rc .other {
319+ b .WriteString (o )
320+ b .WriteByte ('\n' )
366321 }
367322
368- // Write the resolv.conf file - it's bind-mounted into the container, so can't
369- // move a temp file into place, just have to truncate and write it.
370- if err := os .WriteFile (path , content , perm ); err != nil {
371- return errSystem {err }
372- }
323+ if comments {
324+ b .WriteByte ('\n' )
325+ b .WriteString ("# Based on host file: '" + rc .md .SourcePath + "'" )
326+ if rc .md .Transform != "" {
327+ b .WriteString (" (" + rc .md .Transform + ")" )
328+ }
329+ b .WriteByte ('\n' )
330+ for _ , w := range rc .md .Warnings {
331+ b .WriteString ("# " )
332+ b .WriteString (w )
333+ b .WriteByte ('\n' )
334+ }
335+ if len (rc .md .ExtNameServers ) > 0 {
336+ b .WriteString ("# ExtServers: [" )
337+ for i , ext := range rc .md .ExtNameServers {
338+ if i > 0 {
339+ b .WriteByte (' ' )
340+ }
341+ b .WriteString (ext .String ())
342+ }
343+ b .WriteByte (']' )
344+ b .WriteByte ('\n' )
345+ }
346+ if len (rc .md .InvalidNSs ) > 0 {
347+ b .WriteString ("# Invalid nameservers: [" )
348+ for i , ext := range rc .md .InvalidNSs {
349+ if i > 0 {
350+ b .WriteByte (' ' )
351+ }
352+ b .WriteString (ext )
353+ }
354+ b .WriteByte (']' )
355+ b .WriteByte ('\n' )
356+ }
373357
374- // Write the hash file.
375- if hashPath != "" {
376- hashFile , err := atomicwriter .New (hashPath , perm )
377- if err != nil {
378- return errSystem {err }
358+ b .WriteString ("# Overrides: [" )
359+ var overrides int
360+ if rc .md .NSOverride {
361+ b .WriteString ("nameservers" )
362+ overrides ++
363+ }
364+ if rc .md .SearchOverride {
365+ if overrides > 0 {
366+ b .WriteByte (' ' )
367+ }
368+ b .WriteString ("search" )
369+ overrides ++
379370 }
380- defer hashFile .Close ()
371+ if rc .md .OptionsOverride {
372+ if overrides > 0 {
373+ b .WriteByte (' ' )
374+ }
375+ b .WriteString ("options" )
376+ }
377+ b .WriteByte (']' )
378+ b .WriteByte ('\n' )
381379
382- if _ , err = hashFile . Write ([] byte ( digest . FromBytes ( content ))); err != nil {
383- return err
380+ if rc . md . NDotsFrom != "" {
381+ b . WriteString ( "# Option ndots from: " + rc . md . NDotsFrom + " \n " )
384382 }
385383 }
386384
387- return nil
385+ return b . Bytes (), nil
388386}
389387
390- // UserModified can be used to determine whether the resolv.conf file has been
391- // modified since it was generated. It returns false with no error if the file
392- // matches the hash, true with no error if the file no longer matches the hash,
393- // and false with an error if the result cannot be determined.
394- func UserModified (rcPath , rcHashPath string ) (bool , error ) {
395- currRCHash , err := os .ReadFile (rcHashPath )
396- if err != nil {
397- // If the hash file doesn't exist, can only assume it hasn't been written
398- // yet (so, the user hasn't modified the file it hashes).
399- if errors .Is (err , fs .ErrNotExist ) {
400- return false , nil
401- }
402- return false , errors .Wrapf (err , "failed to read hash file %s" , rcHashPath )
403- }
404- expected , err := digest .Parse (string (currRCHash ))
405- if err != nil {
406- return false , errors .Wrapf (err , "failed to parse hash file %s" , rcHashPath )
407- }
408- v := expected .Verifier ()
409- currRC , err := os .Open (rcPath )
410- if err != nil {
411- return false , errors .Wrapf (err , "failed to open %s to check for modifications" , rcPath )
412- }
413- defer currRC .Close ()
414- if _ , err := io .Copy (v , currRC ); err != nil {
415- return false , errors .Wrapf (err , "failed to hash %s to check for modifications" , rcPath )
388+ func (rc * ResolvConf ) processLine (line string ) {
389+ // Strip blank lines and comments.
390+ if line == "" || line [0 ] == '#' || line [0 ] == ';' {
391+ return
416392 }
417- return ! v .Verified (), nil
418- }
419393
420- func (rc * ResolvConf ) processLine (line string ) {
421394 fields := strings .Fields (line )
422-
423- // Strip blank lines and comments.
424- if len (fields ) == 0 || fields [0 ][0 ] == '#' || fields [0 ][0 ] == ';' {
395+ if len (fields ) == 0 {
425396 return
426397 }
427398
@@ -470,8 +441,11 @@ func defaultNSAddrs(ipv6 bool) []netip.Addr {
470441func removeInvalidNDots (options []string ) []string {
471442 n := 0
472443 for _ , opt := range options {
473- k , v , _ := strings .Cut (opt , ":" )
444+ k , v , hasSep := strings .Cut (opt , ":" )
474445 if k == "ndots" {
446+ if ! hasSep || v == "" {
447+ continue
448+ }
475449 ndots , err := strconv .Atoi (v )
476450 if err != nil || ndots < 0 {
477451 continue
@@ -483,16 +457,3 @@ func removeInvalidNDots(options []string) []string {
483457 clear (options [n :]) // Zero out the obsolete elements, for GC.
484458 return options [:n ]
485459}
486-
487- // errSystem implements [github.com/docker/docker/errdefs.ErrSystem].
488- //
489- // We don't use the errdefs helpers here, because the resolvconf package
490- // is imported in BuildKit, and this is the only location that used the
491- // errdefs package outside of the client.
492- type errSystem struct { error }
493-
494- func (errSystem ) System () {}
495-
496- func (e errSystem ) Unwrap () error {
497- return e .error
498- }
0 commit comments