github.com/swiftstack/ProxyFS@v0.0.0-20210203235616-4017c267d62f/conf/api.go (about)

     1  // Copyright (c) 2015-2021, NVIDIA CORPORATION.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package conf
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/base64"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"os"
    12  	"path/filepath"
    13  	"regexp"
    14  	"sort"
    15  	"strconv"
    16  	"strings"
    17  	"time"
    18  	"unicode/utf8"
    19  )
    20  
    21  // ConfMap is accessed via compMap[section_name][option_name][option_value_index] or via the methods below
    22  
    23  type ConfMapOption []string
    24  type ConfMapSection map[string]ConfMapOption
    25  type ConfMap map[string]ConfMapSection
    26  
    27  // MakeConfMap returns an newly created empty ConfMap
    28  func MakeConfMap() (confMap ConfMap) {
    29  	confMap = make(ConfMap)
    30  	return
    31  }
    32  
    33  // MakeConfMapFromFile returns a newly created ConfMap loaded with the contents of the confFilePath-specified file
    34  func MakeConfMapFromFile(confFilePath string) (confMap ConfMap, err error) {
    35  	confMap = MakeConfMap()
    36  	err = confMap.UpdateFromFile(confFilePath)
    37  	return
    38  }
    39  
    40  // MakeConfMapFromStrings returns a newly created ConfMap loaded with the contents specified in confStrings
    41  func MakeConfMapFromStrings(confStrings []string) (confMap ConfMap, err error) {
    42  	confMap = MakeConfMap()
    43  	for _, confString := range confStrings {
    44  		err = confMap.UpdateFromString(confString)
    45  		if nil != err {
    46  			err = fmt.Errorf("Error building confMap from conf strings: %v", err)
    47  			return
    48  		}
    49  	}
    50  
    51  	err = nil
    52  	return
    53  }
    54  
    55  // RegEx components used below:
    56  
    57  const assignment = "([ \t]*[=:][ \t]*)"
    58  const dot = "(\\.)"
    59  const leftBracket = "(\\[)"
    60  const rightBracket = "(\\])"
    61  const sectionName = "([0-9A-Za-z_\\-/:\\.]+)"
    62  const separator = "([ \t]+|([ \t]*,[ \t]*))"
    63  const token = "(([0-9A-Za-z_\\*\\-/:\\.\\[\\]]+)\\$?)"
    64  const value = "(([0-9A-Za-z_=\\*\\-\\+/:\\.\\[\\]\\\"\\{\\}\\\\]+)\\$?)"
    65  const whiteSpace = "([ \t]+)"
    66  
    67  // A string to load looks like:
    68  
    69  //   <section_name_0>.<option_name_0> =
    70  //     or
    71  //   <section_name_1>.<option_name_1> : <value_1>
    72  //     or
    73  //   <section_name_2>.<option_name_2> = <value_2>, <value_3>
    74  //     or
    75  //   <section_name_3>.<option_name_3> : <value_4> <value_5>,<value_6>
    76  
    77  var stringRE = regexp.MustCompile("\\A" + token + dot + token + assignment + "(" + value + "(" + separator + value + ")*)?\\z")
    78  var sectionNameOptionNameSeparatorRE = regexp.MustCompile(dot)
    79  
    80  // A .INI/.conf file to load typically looks like:
    81  //
    82  //   [<section_name_1>]
    83  //   <option_name_0> :
    84  //   <option_name_1> = <value_1>
    85  //   <option_name_2> : <value_2> <value_3>
    86  //   <option_name_3> = <value_4> <value_5>,<value_6>
    87  //
    88  //   # A comment on it's own line starting with '#'
    89  //   ; A comment on it's own line starting with ';'
    90  //
    91  //   [<section_name_2>]          ; A comment at the end of a line starting with ';'
    92  //   <option_name_4> : <value_7> # A comment at the end of a line starting with '#'
    93  //
    94  // One .INI/.conf file may include another before/between/after its own sections like:
    95  //
    96  //   [<section_name_3>]
    97  //   <option_name_5> = <value_8>
    98  //
    99  //   .include <included .INI/.conf path>
   100  //
   101  //   [<section_name_4>]
   102  //   <option_name_6> : <value_9>
   103  
   104  // Section Name lines are of the form:
   105  
   106  var sectionHeaderLineRE = regexp.MustCompile("\\A" + leftBracket + token + rightBracket + "\\z")
   107  var sectionNameRE = regexp.MustCompile(sectionName)
   108  
   109  // Option Name:Value lines are of the form:
   110  
   111  var optionLineRE = regexp.MustCompile("\\A" + token + assignment + "(" + value + "(" + separator + value + ")*)?\\z")
   112  
   113  var optionNameOptionValuesSeparatorRE = regexp.MustCompile(assignment)
   114  var optionValueSeparatorRE = regexp.MustCompile(separator)
   115  
   116  // Include lines are of the form:
   117  
   118  var includeLineRE = regexp.MustCompile("\\A\\.include" + whiteSpace + token + "\\z")
   119  var includeFilePathSeparatorRE = regexp.MustCompile(whiteSpace)
   120  
   121  // UpdateFromString modifies a pre-existing ConfMap based on an update
   122  // specified in confString (e.g., from an extra command-line argument)
   123  func (confMap ConfMap) UpdateFromString(confString string) (err error) {
   124  	confStringTrimmed := strings.Trim(confString, " \t") // Trim leading & trailing spaces & tabs
   125  
   126  	if 0 == len(confStringTrimmed) {
   127  		err = fmt.Errorf("trimmed confString: \"%v\" was found to be empty", confString)
   128  		return
   129  	}
   130  
   131  	if !stringRE.MatchString(confStringTrimmed) {
   132  		err = fmt.Errorf("malformed confString: \"%v\"", confString)
   133  		return
   134  	}
   135  
   136  	// confStringTrimmed well formed, so extract Section Name, Option Name, and Values
   137  
   138  	confStringSectionNameOptionPayloadStrings := sectionNameOptionNameSeparatorRE.Split(confStringTrimmed, 2)
   139  
   140  	sectionName := confStringSectionNameOptionPayloadStrings[0]
   141  	optionPayload := confStringSectionNameOptionPayloadStrings[1]
   142  
   143  	confStringOptionNameOptionValuesStrings := optionNameOptionValuesSeparatorRE.Split(optionPayload, 2)
   144  
   145  	optionName := confStringOptionNameOptionValuesStrings[0]
   146  	optionValues := confStringOptionNameOptionValuesStrings[1]
   147  
   148  	optionValuesSplit := optionValueSeparatorRE.Split(optionValues, -1)
   149  
   150  	if (1 == len(optionValuesSplit)) && ("" == optionValuesSplit[0]) {
   151  		// Handle special case where optionValuesSplit == []string{""}... changing it to []string{}
   152  
   153  		optionValuesSplit = []string{}
   154  	}
   155  
   156  	section, found := confMap[sectionName]
   157  
   158  	if !found {
   159  		// Need to create new Section
   160  
   161  		section = make(ConfMapSection)
   162  		confMap[sectionName] = section
   163  	}
   164  
   165  	section[optionName] = replaceUTF8SpacesAndCommasInStrings(optionValuesSplit)
   166  
   167  	// If we reach here, confString successfully processed
   168  
   169  	err = nil
   170  	return
   171  }
   172  
   173  // UpdateFromStrings modifies a pre-existing ConfMap based on an update
   174  // specified in confStrings (e.g., from an extra command-line argument)
   175  func (confMap ConfMap) UpdateFromStrings(confStrings []string) (err error) {
   176  	for _, confString := range confStrings {
   177  		err = confMap.UpdateFromString(confString)
   178  		if nil != err {
   179  			return
   180  		}
   181  	}
   182  	err = nil
   183  	return
   184  }
   185  
   186  // UpdateFromFile modifies a pre-existing ConfMap based on updates specified in confFilePath
   187  func (confMap ConfMap) UpdateFromFile(confFilePath string) (err error) {
   188  	var (
   189  		absConfFilePath                          string
   190  		confFileBytes                            []byte
   191  		confFileBytesLineOffsetStart             int
   192  		confFileBytesOffset                      int
   193  		currentLine                              string
   194  		currentLineDotIncludeIncludePathStrings  []string
   195  		currentLineNumber                        int
   196  		currentLineOptionNameOptionValuesStrings []string
   197  		currentSection                           ConfMapSection
   198  		currentSectionName                       string
   199  		dirAbsConfFilePath                       string
   200  		found                                    bool
   201  		lastRune                                 rune
   202  		nestedConfFilePath                       string
   203  		optionName                               string
   204  		optionValues                             string
   205  		optionValuesSplit                        []string
   206  		runeSize                                 int
   207  	)
   208  
   209  	if "-" == confFilePath {
   210  		confFileBytes, err = ioutil.ReadAll(os.Stdin)
   211  		if nil != err {
   212  			return
   213  		}
   214  	} else {
   215  		confFileBytes, err = ioutil.ReadFile(confFilePath)
   216  		if nil != err {
   217  			return
   218  		}
   219  	}
   220  
   221  	lastRune = '\n'
   222  
   223  	for len(confFileBytes) > confFileBytesOffset {
   224  		// Consume next rune
   225  
   226  		lastRune, runeSize = utf8.DecodeRune(confFileBytes[confFileBytesOffset:])
   227  		if utf8.RuneError == lastRune {
   228  			err = fmt.Errorf("file %v contained invalid UTF-8 at byte %v", confFilePath, confFileBytesOffset)
   229  			return
   230  		}
   231  
   232  		if '\n' == lastRune {
   233  			// Terminate currentLine adding (non-empty) trimmed version to confFileLines
   234  
   235  			currentLineNumber++
   236  
   237  			if confFileBytesLineOffsetStart < confFileBytesOffset {
   238  				currentLine = string(confFileBytes[confFileBytesLineOffsetStart:confFileBytesOffset])
   239  
   240  				currentLine = strings.SplitN(currentLine, ";", 2)[0] // Trim comment after ';'
   241  				currentLine = strings.SplitN(currentLine, "#", 2)[0] // Trim comment after '#'
   242  				currentLine = strings.Trim(currentLine, " \t")       // Trim leading & trailing spaces & tabs
   243  
   244  				if 0 < len(currentLine) {
   245  					// Process non-empty, non-comment portion of currentLine
   246  
   247  					if includeLineRE.MatchString(currentLine) {
   248  						// Include found
   249  
   250  						currentLineDotIncludeIncludePathStrings = includeFilePathSeparatorRE.Split(currentLine, 2)
   251  
   252  						nestedConfFilePath = currentLineDotIncludeIncludePathStrings[1]
   253  
   254  						if '/' != nestedConfFilePath[0] {
   255  							// Need to adjust for relative path
   256  
   257  							absConfFilePath, err = filepath.Abs(confFilePath)
   258  							if nil != err {
   259  								return
   260  							}
   261  
   262  							dirAbsConfFilePath = filepath.Dir(absConfFilePath)
   263  
   264  							nestedConfFilePath = dirAbsConfFilePath + "/" + nestedConfFilePath
   265  						}
   266  
   267  						err = confMap.UpdateFromFile(nestedConfFilePath)
   268  						if nil != err {
   269  							return
   270  						}
   271  
   272  						currentSectionName = ""
   273  					} else if sectionHeaderLineRE.MatchString(currentLine) {
   274  						// Section Header found
   275  
   276  						currentSectionName = sectionNameRE.FindString(currentLine)
   277  					} else {
   278  						if "" == currentSectionName {
   279  							// Options only allowed within a Section
   280  
   281  							err = fmt.Errorf("file %v did not start with a Section Name", confFilePath)
   282  							return
   283  						}
   284  
   285  						// Option within currentSectionName possibly found
   286  
   287  						if !optionLineRE.MatchString(currentLine) {
   288  							// Expected valid Option Line
   289  
   290  							err = fmt.Errorf("file %v malformed line '%v'", confFilePath, currentLine)
   291  							return
   292  						}
   293  
   294  						// Option Line found, so extract Option Name and Option Values
   295  
   296  						currentLineOptionNameOptionValuesStrings = optionNameOptionValuesSeparatorRE.Split(currentLine, 2)
   297  
   298  						optionName = currentLineOptionNameOptionValuesStrings[0]
   299  						optionValues = currentLineOptionNameOptionValuesStrings[1]
   300  
   301  						optionValuesSplit = optionValueSeparatorRE.Split(optionValues, -1)
   302  
   303  						if (1 == len(optionValuesSplit)) && ("" == optionValuesSplit[0]) {
   304  							// Handle special case where optionValuesSplit == []string{""}... changing it to []string{}
   305  
   306  							optionValuesSplit = []string{}
   307  						}
   308  
   309  						// Insert or Update confMap creating a new Section if necessary
   310  
   311  						currentSection, found = confMap[currentSectionName]
   312  
   313  						if !found {
   314  							// Need to create the new Section
   315  
   316  							currentSection = make(ConfMapSection)
   317  							confMap[currentSectionName] = currentSection
   318  						}
   319  
   320  						currentSection[optionName] = replaceUTF8SpacesAndCommasInStrings(optionValuesSplit)
   321  					}
   322  				}
   323  			}
   324  
   325  			// Record where next line would start
   326  
   327  			confFileBytesLineOffsetStart = confFileBytesOffset + runeSize
   328  		}
   329  
   330  		// Loop back for next rune
   331  
   332  		confFileBytesOffset += runeSize
   333  	}
   334  
   335  	if '\n' != lastRune {
   336  		err = fmt.Errorf("file %v did not end in a '\n' character", confFilePath)
   337  		return
   338  	}
   339  
   340  	// If we reach here, confFilePath successfully processed
   341  
   342  	err = nil
   343  	return
   344  }
   345  
   346  // Dump returns a single string that, if passed written to a file used as
   347  // input to MakeConfMapFromFile() would result in an identical ConfMap.
   348  //
   349  // To enable efficient comparisons, the elements of the ConfMap will be
   350  // sorted in the output (both by sectionName and by optionName).
   351  //
   352  func (confMap ConfMap) Dump() (confMapString string) {
   353  	var (
   354  		confOption                ConfMapOption
   355  		confOptionName            string
   356  		confOptionNameLenMax      int
   357  		confOptionNameSlice       []string
   358  		confOptionValue           string
   359  		confOptionValueIndex      int
   360  		confSection               ConfMapSection
   361  		confSectionName           string
   362  		confSectionNameSlice      []string
   363  		confSectionNameSliceIndex int
   364  	)
   365  
   366  	// 2 Mibyte should be more than enough to hold the confMap so get the
   367  	// memory in 1 allocation
   368  	var buf strings.Builder
   369  	buf.Grow(2 * 1024 * 1024)
   370  
   371  	confSectionNameSlice = make([]string, 0, len(confMap))
   372  
   373  	for confSectionName = range confMap {
   374  		confSectionNameSlice = append(confSectionNameSlice, confSectionName)
   375  	}
   376  
   377  	sort.Strings(confSectionNameSlice)
   378  
   379  	for confSectionNameSliceIndex, confSectionName = range confSectionNameSlice {
   380  		confSection = confMap[confSectionName]
   381  
   382  		if 0 < confSectionNameSliceIndex {
   383  			buf.WriteString("\n")
   384  		}
   385  
   386  		buf.WriteString("[" + confSectionName + "]\n")
   387  
   388  		confOptionNameSlice = make([]string, 0, len(confSection))
   389  		confOptionNameLenMax = 0
   390  
   391  		for confOptionName = range confSection {
   392  			confOptionNameSlice = append(confOptionNameSlice, confOptionName)
   393  			if len(confOptionName) > confOptionNameLenMax {
   394  				confOptionNameLenMax = len(confOptionName)
   395  			}
   396  		}
   397  
   398  		sort.Strings(confOptionNameSlice)
   399  
   400  		for _, confOptionName = range confOptionNameSlice {
   401  			confOption = confSection[confOptionName]
   402  
   403  			buf.WriteString(confOptionName + ":" +
   404  				strings.Repeat(" ", confOptionNameLenMax-len(confOptionName)))
   405  
   406  			for confOptionValueIndex, confOptionValue = range confOption {
   407  				if 0 == confOptionValueIndex {
   408  					buf.WriteString(" " + confOptionValue)
   409  				} else {
   410  					buf.WriteString(", " + confOptionValue)
   411  				}
   412  			}
   413  
   414  			buf.WriteString("\n")
   415  		}
   416  	}
   417  
   418  	confMapString = buf.String()
   419  	return
   420  }
   421  
   422  // VerifyOptionIsMissing returns an error if [sectionName]optionName does not
   423  // exist.
   424  func (confMap ConfMap) VerifyOptionIsMissing(sectionName string, optionName string) (err error) {
   425  	section, ok := confMap[sectionName]
   426  	if !ok {
   427  		err = fmt.Errorf("[%v] missing", sectionName)
   428  		return
   429  	}
   430  
   431  	_, ok = section[optionName]
   432  	if ok {
   433  		err = fmt.Errorf("[%v]%v exists", sectionName, optionName)
   434  	} else {
   435  		err = nil
   436  	}
   437  
   438  	return
   439  }
   440  
   441  // VerifyOptionValueIsEmpty returns an error if [sectionName]optionName's value
   442  // is not empty or if the option does not exist.
   443  func (confMap ConfMap) VerifyOptionValueIsEmpty(sectionName string, optionName string) (err error) {
   444  	section, ok := confMap[sectionName]
   445  	if !ok {
   446  		err = fmt.Errorf("[%v] missing", sectionName)
   447  		return
   448  	}
   449  
   450  	option, ok := section[optionName]
   451  	if !ok {
   452  		err = fmt.Errorf("[%v]%v missing", sectionName, optionName)
   453  		return
   454  	}
   455  
   456  	if 0 == len(option) {
   457  		err = nil
   458  	} else {
   459  		err = fmt.Errorf("[%v]%v must have no value", sectionName, optionName)
   460  	}
   461  
   462  	return
   463  }
   464  
   465  // SetOptionIfMissing sets the value of the option to optionVal if and only if
   466  // the option is not already specified.  The section is created if it doesn't
   467  // already exist.
   468  //
   469  // This is useful to apply "default" options after the confmap has been loaded.
   470  func (confMap ConfMap) SetOptionIfMissing(sectionName string, optionName string, optionVal ConfMapOption) {
   471  
   472  	section, ok := confMap[sectionName]
   473  	if !ok {
   474  		section := make(ConfMapSection)
   475  		confMap[sectionName] = section
   476  	}
   477  
   478  	_, ok = section[optionName]
   479  	if ok {
   480  		return
   481  	}
   482  	confMap[sectionName][optionName] = optionVal
   483  }
   484  
   485  // SetSectionIfMissing sets the section value to the valued passed in if and
   486  // only if the sectionName is not already specified.  The section is created if
   487  // it doesn't already exist.
   488  //
   489  // This is useful to apply "default" sections after the confmap has been loaded.
   490  func (confMap ConfMap) SetSectionIfMissing(sectionName string, sectionVal ConfMapSection) {
   491  
   492  	_, ok := confMap[sectionName]
   493  	if ok {
   494  		return
   495  	}
   496  
   497  	confMap[sectionName] = sectionVal
   498  }
   499  
   500  // FetchOptionValueStringSlice returns [sectionName]optionName's string values as a []string
   501  func (confMap ConfMap) FetchOptionValueStringSlice(sectionName string, optionName string) (optionValue []string, err error) {
   502  	optionValue = []string{}
   503  
   504  	section, ok := confMap[sectionName]
   505  	if !ok {
   506  		err = fmt.Errorf("Section '[%v]' is missing", sectionName)
   507  		return
   508  	}
   509  
   510  	option, ok := section[optionName]
   511  	if !ok {
   512  		err = fmt.Errorf("Option '[%v]%v' is missing", sectionName, optionName)
   513  		return
   514  	}
   515  
   516  	optionValue = option
   517  
   518  	return
   519  }
   520  
   521  // FetchOptionValueString returns [sectionName]optionName's single string value
   522  func (confMap ConfMap) FetchOptionValueString(sectionName string, optionName string) (optionValue string, err error) {
   523  	optionValue = ""
   524  
   525  	optionValueSlice, err := confMap.FetchOptionValueStringSlice(sectionName, optionName)
   526  	if nil != err {
   527  		return
   528  	}
   529  
   530  	if 0 == len(optionValueSlice) {
   531  		err = fmt.Errorf("[%v]%v must have a value", sectionName, optionName)
   532  		return
   533  	}
   534  	if 1 != len(optionValueSlice) {
   535  		err = fmt.Errorf("[%v]%v must have a single value", sectionName, optionName)
   536  		return
   537  	}
   538  
   539  	optionValue = optionValueSlice[0]
   540  
   541  	err = nil
   542  	return
   543  }
   544  
   545  // FetchOptionValueBase64String returns [sectionName]optionName's single string value Base64-decoded
   546  func (confMap ConfMap) FetchOptionValueBase64String(sectionName string, optionName string) (optionValue string, err error) {
   547  	var (
   548  		base64DecodedOptionValue []byte
   549  		base64EncodedOptionValue string
   550  	)
   551  
   552  	base64EncodedOptionValue, err = confMap.FetchOptionValueString(sectionName, optionName)
   553  	if nil != err {
   554  		return
   555  	}
   556  
   557  	base64DecodedOptionValue, err = base64.StdEncoding.DecodeString(base64EncodedOptionValue)
   558  	if nil != err {
   559  		return
   560  	}
   561  
   562  	optionValue = string(base64DecodedOptionValue[:])
   563  
   564  	return
   565  }
   566  
   567  // FetchOptionValueBase64StringSlice returns [sectionName]optionName's string values as a []string each element Base64-decoded
   568  func (confMap ConfMap) FetchOptionValueBase64StringSlice(sectionName string, optionName string) (optionValue []string, err error) {
   569  	var (
   570  		base64DecodedOptionValueElement []byte
   571  		base64EncodedOptionValue        []string
   572  		base64EncodedOptionValueElement string
   573  		i                               int
   574  	)
   575  
   576  	base64EncodedOptionValue, err = confMap.FetchOptionValueStringSlice(sectionName, optionName)
   577  	if nil != err {
   578  		return
   579  	}
   580  
   581  	optionValue = make([]string, len(base64EncodedOptionValue))
   582  
   583  	for i, base64EncodedOptionValueElement = range base64EncodedOptionValue {
   584  		base64DecodedOptionValueElement, err = base64.StdEncoding.DecodeString(base64EncodedOptionValueElement)
   585  		if nil != err {
   586  			return
   587  		}
   588  
   589  		optionValue[i] = string(base64DecodedOptionValueElement[:])
   590  	}
   591  
   592  	return
   593  }
   594  
   595  // FetchOptionValueBool returns [sectionName]optionName's single string value converted to a bool
   596  func (confMap ConfMap) FetchOptionValueBool(sectionName string, optionName string) (optionValue bool, err error) {
   597  	optionValueString, err := confMap.FetchOptionValueString(sectionName, optionName)
   598  	if nil != err {
   599  		return
   600  	}
   601  
   602  	optionValueStringDownshifted := strings.ToLower(optionValueString)
   603  
   604  	switch optionValueStringDownshifted {
   605  	case "yes":
   606  		fallthrough
   607  	case "on":
   608  		fallthrough
   609  	case "true":
   610  		optionValue = true
   611  	case "no":
   612  		fallthrough
   613  	case "off":
   614  		fallthrough
   615  	case "false":
   616  		optionValue = false
   617  	default:
   618  		err = fmt.Errorf("Couldn't interpret %q as boolean (expected one of 'true'/'false'/'yes'/'no'/'on'/'off')", optionValueString)
   619  		return
   620  	}
   621  
   622  	err = nil
   623  	return
   624  }
   625  
   626  // FetchOptionValueUint8 returns [sectionName]optionName's single string value converted to a uint8
   627  func (confMap ConfMap) FetchOptionValueUint8(sectionName string, optionName string) (optionValue uint8, err error) {
   628  	optionValue = 0
   629  
   630  	optionValueString, err := confMap.FetchOptionValueString(sectionName, optionName)
   631  	if nil != err {
   632  		return
   633  	}
   634  
   635  	optionValueUint64, strconvErr := strconv.ParseUint(optionValueString, 10, 8)
   636  	if nil != strconvErr {
   637  		err = fmt.Errorf("[%v]%v strconv.ParseUint() error: %v", sectionName, optionName, strconvErr)
   638  		return
   639  	}
   640  
   641  	optionValue = uint8(optionValueUint64)
   642  
   643  	err = nil
   644  	return
   645  }
   646  
   647  // FetchOptionValueUint16 returns [sectionName]optionName's single string value converted to a uint16
   648  func (confMap ConfMap) FetchOptionValueUint16(sectionName string, optionName string) (optionValue uint16, err error) {
   649  	optionValue = 0
   650  
   651  	optionValueString, err := confMap.FetchOptionValueString(sectionName, optionName)
   652  	if nil != err {
   653  		return
   654  	}
   655  
   656  	optionValueUint64, strconvErr := strconv.ParseUint(optionValueString, 10, 16)
   657  	if nil != strconvErr {
   658  		err = fmt.Errorf("[%v]%v strconv.ParseUint() error: %v", sectionName, optionName, strconvErr)
   659  		return
   660  	}
   661  
   662  	optionValue = uint16(optionValueUint64)
   663  
   664  	err = nil
   665  	return
   666  }
   667  
   668  // FetchOptionValueUint32 returns [sectionName]optionName's single string value converted to a uint32
   669  func (confMap ConfMap) FetchOptionValueUint32(sectionName string, optionName string) (optionValue uint32, err error) {
   670  	optionValue = 0
   671  
   672  	optionValueString, err := confMap.FetchOptionValueString(sectionName, optionName)
   673  	if nil != err {
   674  		return
   675  	}
   676  
   677  	optionValueUint64, strconvErr := strconv.ParseUint(optionValueString, 10, 32)
   678  	if nil != strconvErr {
   679  		err = fmt.Errorf("[%v]%v strconv.ParseUint() error: %v", sectionName, optionName, strconvErr)
   680  		return
   681  	}
   682  
   683  	optionValue = uint32(optionValueUint64)
   684  
   685  	err = nil
   686  	return
   687  }
   688  
   689  // FetchOptionValueUint64 returns [sectionName]optionName's single string value converted to a uint64
   690  func (confMap ConfMap) FetchOptionValueUint64(sectionName string, optionName string) (optionValue uint64, err error) {
   691  	optionValue = 0
   692  
   693  	optionValueString, err := confMap.FetchOptionValueString(sectionName, optionName)
   694  	if nil != err {
   695  		return
   696  	}
   697  
   698  	optionValueUint64, strconvErr := strconv.ParseUint(optionValueString, 10, 64)
   699  	if nil != strconvErr {
   700  		err = fmt.Errorf("[%v]%v strconv.ParseUint() error: %v", sectionName, optionName, strconvErr)
   701  		return
   702  	}
   703  
   704  	optionValue = uint64(optionValueUint64)
   705  
   706  	err = nil
   707  	return
   708  }
   709  
   710  // FetchOptionValueFloat32 returns [sectionName]optionName's single string value converted to a float32
   711  func (confMap ConfMap) FetchOptionValueFloat32(sectionName string, optionName string) (optionValue float32, err error) {
   712  	optionValueString, err := confMap.FetchOptionValueString(sectionName, optionName)
   713  	if nil != err {
   714  		return
   715  	}
   716  
   717  	optionValueAsFloat64, strconvErr := strconv.ParseFloat(optionValueString, 32)
   718  	if nil != strconvErr {
   719  		err = fmt.Errorf("[%v]%v strconv.ParseFloat() error: %v", sectionName, optionName, strconvErr)
   720  		return
   721  	}
   722  
   723  	optionValue = float32(optionValueAsFloat64) // strconv.ParseFloat(,32) guarantees this will work
   724  	err = nil
   725  	return
   726  }
   727  
   728  // FetchOptionValueFloat64 returns [sectionName]optionName's single string value converted to a float32
   729  func (confMap ConfMap) FetchOptionValueFloat64(sectionName string, optionName string) (optionValue float64, err error) {
   730  	optionValueString, err := confMap.FetchOptionValueString(sectionName, optionName)
   731  	if nil != err {
   732  		return
   733  	}
   734  
   735  	optionValue, strconvErr := strconv.ParseFloat(optionValueString, 64)
   736  	if nil != strconvErr {
   737  		err = fmt.Errorf("[%v]%v strconv.ParseFloat() error: %v", sectionName, optionName, strconvErr)
   738  		return
   739  	}
   740  
   741  	err = nil
   742  	return
   743  }
   744  
   745  // FetchOptionValueFloatScaledToUint32 returns [sectionName]optionName's single string value converted to a float64, multiplied by the uint32 multiplier, as a uint32
   746  func (confMap ConfMap) FetchOptionValueFloatScaledToUint32(sectionName string, optionName string, multiplier uint32) (optionValue uint32, err error) {
   747  	optionValue = 0
   748  
   749  	optionValueString, err := confMap.FetchOptionValueString(sectionName, optionName)
   750  	if nil != err {
   751  		return
   752  	}
   753  
   754  	optionValueFloat64, strconvErr := strconv.ParseFloat(optionValueString, 64)
   755  	if nil != strconvErr {
   756  		err = fmt.Errorf("[%v]%v strconv.ParseFloat() error: %v", sectionName, optionName, strconvErr)
   757  		return
   758  	}
   759  
   760  	if optionValueFloat64 < float64(0.0) {
   761  		err = fmt.Errorf("[%v]%v is negative", sectionName, optionName)
   762  		return
   763  	}
   764  
   765  	optionValueFloat64Scaled := optionValueFloat64*float64(multiplier) + float64(0.5)
   766  
   767  	if optionValueFloat64Scaled >= float64(uint32(0xFFFFFFFF)) {
   768  		err = fmt.Errorf("[%v]%v after scaling won't fit in uint32", sectionName, optionName)
   769  		return
   770  	}
   771  
   772  	optionValue = uint32(optionValueFloat64Scaled)
   773  
   774  	err = nil
   775  	return
   776  }
   777  
   778  // FetchOptionValueFloatScaledToUint64 returns [sectionName]optionName's single string value converted to a float64, multiplied by the uint64 multiplier, as a uint64
   779  func (confMap ConfMap) FetchOptionValueFloatScaledToUint64(sectionName string, optionName string, multiplier uint64) (optionValue uint64, err error) {
   780  	optionValue = 0
   781  
   782  	optionValueString, err := confMap.FetchOptionValueString(sectionName, optionName)
   783  	if nil != err {
   784  		return
   785  	}
   786  
   787  	optionValueFloat64, strconvErr := strconv.ParseFloat(optionValueString, 64)
   788  	if nil != strconvErr {
   789  		err = fmt.Errorf("[%v]%v strconv.ParseFloat() error: %v", sectionName, optionName, strconvErr)
   790  		return
   791  	}
   792  
   793  	if optionValueFloat64 < float64(0.0) {
   794  		err = fmt.Errorf("[%v]%v is negative", sectionName, optionName)
   795  		return
   796  	}
   797  
   798  	optionValueFloat64Scaled := optionValueFloat64*float64(multiplier) + float64(0.5)
   799  
   800  	if optionValueFloat64Scaled >= float64(uint64(0xFFFFFFFFFFFFFFFF)) {
   801  		err = fmt.Errorf("[%v]%v after scaling won't fit in uint64", sectionName, optionName)
   802  		return
   803  	}
   804  
   805  	optionValue = uint64(optionValueFloat64Scaled)
   806  
   807  	err = nil
   808  	return
   809  }
   810  
   811  // FetchOptionValueDuration returns [sectionName]optionName's single string value converted to a time.Duration
   812  func (confMap ConfMap) FetchOptionValueDuration(sectionName string, optionName string) (optionValue time.Duration, err error) {
   813  	optionValueString, err := confMap.FetchOptionValueString(sectionName, optionName)
   814  	if nil != err {
   815  		optionValue = time.Since(time.Now()) // Roughly zero
   816  		return
   817  	}
   818  
   819  	optionValue, err = time.ParseDuration(optionValueString)
   820  	if nil != err {
   821  		return
   822  	}
   823  
   824  	if 0.0 > optionValue.Seconds() {
   825  		err = fmt.Errorf("[%v]%v is negative", sectionName, optionName)
   826  		return
   827  	}
   828  
   829  	err = nil
   830  	return
   831  }
   832  
   833  // FetchOptionValueUUID returns [sectionName]optionName's single string value converted to a UUID ([16]byte)
   834  //
   835  // From RFC 4122, a UUID string is defined as follows:
   836  //
   837  //   UUID                   = time-low "-" time-mid "-" time-high-and-version "-" clock-seq-and-reserved clock-seq-low "-" node
   838  //   time-low               = 4hexOctet
   839  //   time-mid               = 2hexOctet
   840  //   time-high-and-version  = 2hexOctet
   841  //   clock-seq-and-reserved = hexOctet
   842  //   clock-seq-low          = hexOctet
   843  //   node                   = 6hexOctet
   844  //   hexOctet               = hexDigit hexDigit
   845  //   hexDigit               = "0" / "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / "a" / "b" / "c" / "d" / "e" / "f" / "A" / "B" / "C" / "D" / "E" / "F"
   846  //
   847  // From RFC 4122, a UUID (i.e. "in memory") is defined as follows (BigEndian/NetworkByteOrder):
   848  //
   849  //      0                   1                   2                   3
   850  //       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   851  //      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   852  //      |                          time_low                             |
   853  //      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   854  //      |       time_mid                |         time_hi_and_version   |
   855  //      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   856  //      |clk_seq_hi_res |  clk_seq_low  |         node (0-1)            |
   857  //      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   858  //      |                         node (2-5)                            |
   859  //      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   860  func (confMap ConfMap) FetchOptionValueUUID(sectionName string, optionName string) (optionValue []byte, err error) {
   861  	optionValue = make([]byte, 16)
   862  
   863  	optionValueSlice, err := confMap.FetchOptionValueStringSlice(sectionName, optionName)
   864  	if nil != err {
   865  		return
   866  	}
   867  
   868  	if 1 != len(optionValueSlice) {
   869  		err = fmt.Errorf("[%v]%v must be single-valued", sectionName, optionName)
   870  		return
   871  	}
   872  
   873  	uuidString := optionValueSlice[0]
   874  
   875  	if (8 + 1 + 4 + 1 + 4 + 1 + 4 + 1 + 12) != len(uuidString) {
   876  		err = fmt.Errorf("[%v]%v UUID string (\"%v\") has invalid length (%v)", sectionName, optionName, uuidString, len(uuidString))
   877  		return
   878  	}
   879  
   880  	if ('-' != uuidString[8]) || ('-' != uuidString[13]) || ('-' != uuidString[18]) || ('-' != uuidString[23]) {
   881  		err = fmt.Errorf("[%v]%v UUID string (\"%v\") has missing '-' separators", sectionName, optionName, uuidString)
   882  		return
   883  	}
   884  
   885  	timeLowUint64, strconvErr := strconv.ParseUint(uuidString[:8], 16, 64)
   886  	if nil != strconvErr {
   887  		err = fmt.Errorf("[%v]%v time_low (\"%v\") invalid", sectionName, optionName, uuidString[:8])
   888  		return
   889  	}
   890  
   891  	timeMidUint64, strconvErr := strconv.ParseUint(uuidString[9:13], 16, 64)
   892  	if nil != strconvErr {
   893  		err = fmt.Errorf("[%v]%v time_mid (\"%v\") invalid", sectionName, optionName, uuidString[9:13])
   894  		return
   895  	}
   896  
   897  	timeHiAndVersionUint64, strconvErr := strconv.ParseUint(uuidString[14:18], 16, 64)
   898  	if nil != strconvErr {
   899  		err = fmt.Errorf("[%v]%v time_hi_and_version (\"%v\") invalid", sectionName, optionName, uuidString[14:18])
   900  		return
   901  	}
   902  
   903  	clkSeqHiResUint64, strconvErr := strconv.ParseUint(uuidString[19:21], 16, 64)
   904  	if nil != strconvErr {
   905  		err = fmt.Errorf("[%v]%v clk_seq_hi_res (\"%v\") invalid", sectionName, optionName, uuidString[19:21])
   906  		return
   907  	}
   908  
   909  	clkClkSeqLowUint64, strconvErr := strconv.ParseUint(uuidString[21:23], 16, 64)
   910  	if nil != strconvErr {
   911  		err = fmt.Errorf("[%v]%v clk_seq_low (\"%v\") invalid", sectionName, optionName, uuidString[21:23])
   912  		return
   913  	}
   914  
   915  	nodeUint64, strconvErr := strconv.ParseUint(uuidString[24:], 16, 64)
   916  	if nil != strconvErr {
   917  		err = fmt.Errorf("[%v]%v node (\"%v\") invalid", sectionName, optionName, uuidString[24:])
   918  		return
   919  	}
   920  
   921  	optionValue[0x0] = byte((timeLowUint64 >> 0x18) & 0xFF)
   922  	optionValue[0x1] = byte((timeLowUint64 >> 0x10) & 0xFF)
   923  	optionValue[0x2] = byte((timeLowUint64 >> 0x08) & 0xFF)
   924  	optionValue[0x3] = byte((timeLowUint64 >> 0x00) & 0xFF)
   925  
   926  	optionValue[0x4] = byte((timeMidUint64 >> 0x08) & 0xFF)
   927  	optionValue[0x5] = byte((timeMidUint64 >> 0x00) & 0xFF)
   928  
   929  	optionValue[0x6] = byte((timeHiAndVersionUint64 >> 0x08) & 0xFF)
   930  	optionValue[0x7] = byte((timeHiAndVersionUint64 >> 0x00) & 0xFF)
   931  
   932  	optionValue[0x8] = byte((clkSeqHiResUint64 >> 0x00) & 0xFF)
   933  
   934  	optionValue[0x9] = byte((clkClkSeqLowUint64 >> 0x00) & 0xFF)
   935  
   936  	optionValue[0xA] = byte((nodeUint64 >> 0x28) & 0xFF)
   937  	optionValue[0xB] = byte((nodeUint64 >> 0x20) & 0xFF)
   938  	optionValue[0xC] = byte((nodeUint64 >> 0x18) & 0xFF)
   939  	optionValue[0xD] = byte((nodeUint64 >> 0x10) & 0xFF)
   940  	optionValue[0xE] = byte((nodeUint64 >> 0x08) & 0xFF)
   941  	optionValue[0xF] = byte((nodeUint64 >> 0x00) & 0xFF)
   942  
   943  	err = nil
   944  	return
   945  }
   946  
   947  // DumpConfMapToFile outputs the ConfMap to a confFilePath-specified file with the perm-specified os.FileMode
   948  func (confMap ConfMap) DumpConfMapToFile(confFilePath string, perm os.FileMode) (err error) {
   949  	var (
   950  		bufToOutput      []byte
   951  		firstOption      bool
   952  		firstSection     bool
   953  		optionName       string
   954  		optionNameLen    int
   955  		optionNameMaxLen int
   956  		option           string
   957  		options          ConfMapOption
   958  		section          ConfMapSection
   959  		sectionName      string
   960  	)
   961  
   962  	firstSection = true
   963  	for sectionName, section = range confMap {
   964  		if firstSection {
   965  			firstSection = false
   966  		} else {
   967  			bufToOutput = append(bufToOutput, '\n')
   968  		}
   969  		bufToOutput = append(bufToOutput, '[')
   970  		bufToOutput = append(bufToOutput, []byte(sectionName)...)
   971  		bufToOutput = append(bufToOutput, ']')
   972  		bufToOutput = append(bufToOutput, '\n')
   973  		optionNameMaxLen = 0
   974  		for optionName = range section {
   975  			optionNameLen = len(optionName)
   976  			if optionNameLen > optionNameMaxLen {
   977  				optionNameMaxLen = optionNameLen
   978  			}
   979  		}
   980  		for optionName, options = range section {
   981  			bufToOutput = append(bufToOutput, []byte(optionName)...)
   982  			optionNameLen = len(optionName)
   983  			bufToOutput = append(bufToOutput, bytes.Repeat([]byte(" "), optionNameMaxLen-optionNameLen+1)...)
   984  			bufToOutput = append(bufToOutput, ':')
   985  			firstOption = true
   986  			for _, option = range options {
   987  				if firstOption {
   988  					firstOption = false
   989  				} else {
   990  					bufToOutput = append(bufToOutput, ',')
   991  				}
   992  				bufToOutput = append(bufToOutput, ' ')
   993  				bufToOutput = append(bufToOutput, []byte(option)...)
   994  			}
   995  			bufToOutput = append(bufToOutput, '\n')
   996  		}
   997  	}
   998  	if !firstSection {
   999  		bufToOutput = append(bufToOutput, '\n')
  1000  	}
  1001  
  1002  	err = ioutil.WriteFile(confFilePath, bufToOutput, perm)
  1003  
  1004  	return // err as returned from ioutil.WriteFile() suffices here
  1005  }
  1006  
  1007  func replaceUTF8SpacesAndCommasInString(src string) (dst string) {
  1008  	dst = strings.ReplaceAll(src, "\\u0020", " ")
  1009  	dst = strings.ReplaceAll(dst, "\\U0020", " ")
  1010  	dst = strings.ReplaceAll(dst, "\\u002C", ",")
  1011  	dst = strings.ReplaceAll(dst, "\\U002C", ",")
  1012  	dst = strings.ReplaceAll(dst, "\\u002c", ",")
  1013  	dst = strings.ReplaceAll(dst, "\\U002c", ",")
  1014  
  1015  	return
  1016  }
  1017  
  1018  func replaceUTF8SpacesAndCommasInStrings(src []string) (dst []string) {
  1019  	var (
  1020  		element      string
  1021  		elementIndex int
  1022  	)
  1023  
  1024  	dst = make([]string, len(src))
  1025  
  1026  	for elementIndex, element = range src {
  1027  		dst[elementIndex] = replaceUTF8SpacesAndCommasInString(element)
  1028  	}
  1029  
  1030  	return
  1031  }