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 }