diff --git a/services/appclassmanager/appclassmanager.go b/services/appclassmanager/appclassmanager.go index df8a6c65..0b8175da 100644 --- a/services/appclassmanager/appclassmanager.go +++ b/services/appclassmanager/appclassmanager.go @@ -10,6 +10,7 @@ import ( "strconv" logService "github.com/untangle/golang-shared/services/logger" + "github.com/untangle/golang-shared/services/settings" ) var logger = logService.GetLoggerInstance() @@ -132,9 +133,14 @@ func loadApplicationTable() { var err error ApplicationTable = make(map[string]*ApplicationInfo) - + filename, err := settings.LocateFile(guidInfoFile) + if err != nil { + logger.Warn("Unable to locate GUID info file: %s\n", + guidInfoFile) + return + } // open the guid info file provided by Sandvine - file, err = os.Open(guidInfoFile) + file, err = os.Open(filename) // if there was an error log and return if err != nil { diff --git a/services/settings/filesystem.go b/services/settings/filesystem.go new file mode 100644 index 00000000..ecbf84aa --- /dev/null +++ b/services/settings/filesystem.go @@ -0,0 +1,84 @@ +package settings + +import ( + "fmt" + "os" + "path/filepath" + "strings" +) + +// FilenameLocator finds files on the local filesytem, allowing the +// system to be in hybrid or non-hybid mode and concealing the +// diffrences. +type FilenameLocator struct { + fileExists func(filename string) bool +} + +const ( + // prefix for generic filepaths in hybrid mode + hybridModeGenericPrefix = "/mfw" + + // kernel forwarding mode/BST container mode path prefix. + kernelModeSettingsPrefix = "/etc/config" + + // prefix specifically for config files in hybrid mode + hybridModeSettingsPrefix = "/mnt/flash/mfw-settings" +) + +// FileExists returns true if we can Stat the filename. We don't +// distinguish between various kinds of errors, but do log them, on +// the theory that if you can't Stat the filename, for most purposes, +// that is the same as it not existing, and isn't a common case. +func FileExists(fname string) bool { + if _, err := os.Stat(fname); err != nil { + if !os.IsNotExist(err) { + logger.Warn("Unexpected error code from os.Stat: %s", + err) + } + return false + } + return true +} + +func (f *FilenameLocator) findEOSFileName(filename string) (string, error) { + if strings.HasPrefix(filename, kernelModeSettingsPrefix) { + newFileName := strings.Replace( + filename, + kernelModeSettingsPrefix, + hybridModeSettingsPrefix, + 1) + if !f.fileExists(newFileName) { + return "", fmt.Errorf("unable to find config file: %s", filename) + } + return newFileName, nil + } else { + newFileName := filepath.Join( + hybridModeGenericPrefix, + filename) + if !f.fileExists(newFileName) { + return "", fmt.Errorf( + "unable to locate file: %s", filename) + } + return newFileName, nil + } +} + +// LocateFile locates the input filename on the filesystem, +// automatically translating it to hybrid mode filenames when needed. +func (f *FilenameLocator) LocateFile(filename string) (string, error) { + if f.fileExists(filename) { + return filename, nil + } + return f.findEOSFileName(filename) + +} + +var defaultLocator = &FilenameLocator{ + fileExists: FileExists, +} + +// LocateFile calls FilenameLocator.LocateFile on the default filename +// locator. +func LocateFile(filename string) (string, error) { + return defaultLocator.LocateFile(filename) +} diff --git a/services/settings/filesystem_test.go b/services/settings/filesystem_test.go new file mode 100644 index 00000000..186e4851 --- /dev/null +++ b/services/settings/filesystem_test.go @@ -0,0 +1,85 @@ +package settings + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/untangle/golang-shared/testing/mocks" +) + +type fileExistsFake struct { + rvals []bool +} + +func (f *fileExistsFake) doesExist(fname string) bool { + rval := f.rvals[0] + f.rvals = f.rvals[1:] + return rval +} +func TestFilenameLocator(t *testing.T) { + existFake := &fileExistsFake{} + locator := FilenameLocator{ + fileExists: existFake.doesExist} + tests := []struct { + filename string + existResults []bool + returnValue string + returnErr error + }{ + { + filename: "/etc/config/settings.json", + existResults: []bool{false, true}, + returnValue: "/mnt/flash/mfw-settings/settings.json", + }, + { + filename: "/usr/share/geoip", + existResults: []bool{false, true}, + returnValue: "/mfw/usr/share/geoip", + }, + { + filename: "/etc/config/appstate.json", + existResults: []bool{false, true}, + returnValue: "/mnt/flash/mfw-settings/appstate.json", + }, + { + filename: "/etc/config/settings.json", + existResults: []bool{true, true}, + returnValue: "/etc/config/settings.json", + }, + { + filename: "/etc/config/appstate.json", + existResults: []bool{true, true}, + returnValue: "/etc/config/appstate.json", + }, + { + filename: "/etc/config/appstate.json", + existResults: []bool{false, false}, + returnValue: "", + returnErr: fmt.Errorf("/etc/config/appstate.json"), + }, + } + + for _, test := range tests { + existFake.rvals = test.existResults + result, err := locator.LocateFile(test.filename) + assert.Equal(t, result, test.returnValue) + if test.returnErr == nil { + assert.NoError(t, err) + } else { + assert.Regexp(t, test.returnErr.Error(), err.Error(), + "errors should match") + } + } + +} + +func TestFileExists(t *testing.T) { + thisFile, err := os.Executable() + logger = mocks.NewMockLogger() + assert.NoError(t, err) + assert.True(t, FileExists(thisFile)) + assert.False(t, + FileExists("/some-file/that-should/definitely-not/exist-anywhere")) +} diff --git a/services/settings/settings.go b/services/settings/settings.go index a5c73883..37c41737 100644 --- a/services/settings/settings.go +++ b/services/settings/settings.go @@ -19,12 +19,27 @@ import ( "github.com/untangle/golang-shared/plugins/util" ) +// TODO: fix this, we should not rely on people happening to call Startup(). var logger loggerModel.LoggerLevels var once sync.Once -const settingsFile = "/etc/config/settings.json" -const defaultsFile = "/etc/config/defaults.json" -const currentFile = "/etc/config/current.json" +// find the file or fallback to the old filename if we can't. +func locateOrDefault(filename string) string { + // useing fmt.Fprintf because of the above logger var. + if filename, err := LocateFile(filename); err == nil { + return filename + } + fmt.Fprintf(os.Stderr, + "settings: Unable to locate: %s, defaulting...", + filename) + return filename +} + +var ( + settingsFile = locateOrDefault("/etc/config/settings.json") + defaultsFile = locateOrDefault("/etc/config/defaults.json") + currentFile = locateOrDefault("/etc/config/current.json") +) var syncCallbacks []func() @@ -64,9 +79,18 @@ var settingsFileSingleton *SettingsFile // singleton. Prefer using this if you can. func GetSettingsFileSingleton() *SettingsFile { if settingsFileSingleton == nil { - settingsFileSingleton = NewSettingsFile( - settingsFile, - WithLock(&saveLocker)) + if fileName, err := LocateFile(settingsFile); err == nil { + settingsFileSingleton = NewSettingsFile( + fileName, + WithLock(&saveLocker)) + } else { + fmt.Fprintf(os.Stderr, + "Unable to locate settings file, falling back to %s and hoping for the best...\n", + settingsFile) + settingsFileSingleton = NewSettingsFile( + settingsFile, + WithLock(&saveLocker)) + } } return settingsFileSingleton } @@ -77,6 +101,7 @@ func GetCurrentSettings(segments []string) (interface{}, error) { // for backwards compatibility before we saved current.json // if it does not exist, just read settings.json // XXX this should be removed at some point in the future + if _, err := os.Stat(currentFile); os.IsNotExist(err) { return GetSettingsFile(segments, settingsFile) } @@ -597,7 +622,7 @@ func tempFile(dir, pattern string) (f *os.File, err error) { // GetUIDOpenwrt returns the UID of the system func GetUIDOpenwrt() (string, error) { - return GetUID("/etc/config/uid") + return GetUID(locateOrDefault("/etc/config/uid")) } // GetUID returns the UID of the system diff --git a/util/net/interfaces/net.go b/util/net/interfaces/net.go index 940d5468..13d59691 100644 --- a/util/net/interfaces/net.go +++ b/util/net/interfaces/net.go @@ -32,9 +32,8 @@ func (ifaces *InterfaceSettings) SetJsonPath(jsonPath ...string) { } const ( - defaultSettingsFile = "/etc/config/settings.json" - defaultJsonParent = "network" - defaultJsonChild = "interfaces" + defaultJsonParent = "network" + defaultJsonChild = "interfaces" ) // NewInterfaceSettings returns an InterfaceSettings object which uses @@ -115,7 +114,7 @@ func GetLocalInterfaces() []Interface { func GetDefaultInterfaceSettings() InterfaceSettings { intfSettings := InterfaceSettings{ - file: settings.NewSettingsFile(defaultSettingsFile), + file: settings.GetSettingsFileSingleton(), jsonPath: []string{}, } intfSettings.SetJsonPath(defaultJsonParent, defaultJsonChild)