|
@@ -1,44 +1,217 @@
|
|
|
package nginx
|
|
|
|
|
|
import (
|
|
|
+ "os"
|
|
|
"regexp"
|
|
|
"strings"
|
|
|
+ "sync"
|
|
|
+ "time"
|
|
|
+
|
|
|
+ "github.com/elliotchance/orderedmap/v3"
|
|
|
)
|
|
|
|
|
|
const (
|
|
|
- ModuleStream = "stream_module"
|
|
|
+ ModuleStream = "stream"
|
|
|
)
|
|
|
|
|
|
-func GetModules() (modules []string) {
|
|
|
- out := getNginxV()
|
|
|
-
|
|
|
- // Regular expression to find modules in nginx -V output
|
|
|
- r := regexp.MustCompile(`--with-([a-zA-Z0-9_-]+)(_module)?`)
|
|
|
-
|
|
|
- // Find all matches
|
|
|
- matches := r.FindAllStringSubmatch(out, -1)
|
|
|
-
|
|
|
- // Extract module names from matches
|
|
|
+type Module struct {
|
|
|
+ Name string `json:"name"`
|
|
|
+ Params string `json:"params,omitempty"`
|
|
|
+ Dynamic bool `json:"dynamic"`
|
|
|
+ Loaded bool `json:"loaded"`
|
|
|
+}
|
|
|
+
|
|
|
+// modulesCache stores the cached modules list and related metadata
|
|
|
+var (
|
|
|
+ modulesCache = orderedmap.NewOrderedMap[string, Module]()
|
|
|
+ modulesCacheLock sync.RWMutex
|
|
|
+ lastPIDPath string
|
|
|
+ lastPIDModTime time.Time
|
|
|
+ lastPIDSize int64
|
|
|
+)
|
|
|
+
|
|
|
+// clearModulesCache clears the modules cache
|
|
|
+func clearModulesCache() {
|
|
|
+ modulesCacheLock.Lock()
|
|
|
+ defer modulesCacheLock.Unlock()
|
|
|
+
|
|
|
+ modulesCache = orderedmap.NewOrderedMap[string, Module]()
|
|
|
+ lastPIDPath = ""
|
|
|
+ lastPIDModTime = time.Time{}
|
|
|
+ lastPIDSize = 0
|
|
|
+}
|
|
|
+
|
|
|
+// isPIDFileChanged checks if the PID file has changed since the last check
|
|
|
+func isPIDFileChanged() bool {
|
|
|
+ pidPath := GetPIDPath()
|
|
|
+
|
|
|
+ // If PID path has changed, consider it changed
|
|
|
+ if pidPath != lastPIDPath {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ // If Nginx is not running, consider PID changed
|
|
|
+ if !IsNginxRunning() {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check if PID file has changed (modification time or size)
|
|
|
+ fileInfo, err := os.Stat(pidPath)
|
|
|
+ if err != nil {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ modTime := fileInfo.ModTime()
|
|
|
+ size := fileInfo.Size()
|
|
|
+
|
|
|
+ return modTime != lastPIDModTime || size != lastPIDSize
|
|
|
+}
|
|
|
+
|
|
|
+// updatePIDFileInfo updates the stored PID file information
|
|
|
+func updatePIDFileInfo() {
|
|
|
+ pidPath := GetPIDPath()
|
|
|
+
|
|
|
+ if fileInfo, err := os.Stat(pidPath); err == nil {
|
|
|
+ modulesCacheLock.Lock()
|
|
|
+ defer modulesCacheLock.Unlock()
|
|
|
+
|
|
|
+ lastPIDPath = pidPath
|
|
|
+ lastPIDModTime = fileInfo.ModTime()
|
|
|
+ lastPIDSize = fileInfo.Size()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// updateDynamicModulesStatus checks which dynamic modules are actually loaded in the running Nginx
|
|
|
+func updateDynamicModulesStatus() {
|
|
|
+ modulesCacheLock.Lock()
|
|
|
+ defer modulesCacheLock.Unlock()
|
|
|
+
|
|
|
+ // If cache is empty, there's nothing to update
|
|
|
+ if modulesCache.Len() == 0 {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get nginx -T output to check for loaded modules
|
|
|
+ out := getNginxT()
|
|
|
+ if out == "" {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // Regular expression to find loaded dynamic modules in nginx -T output
|
|
|
+ // Look for lines like "load_module modules/ngx_http_image_filter_module.so;"
|
|
|
+ loadModuleRe := regexp.MustCompile(`load_module\s+(?:modules/|/.*/)([a-zA-Z0-9_-]+)\.so;`)
|
|
|
+ matches := loadModuleRe.FindAllStringSubmatch(out, -1)
|
|
|
+
|
|
|
+ // Create a map of loaded dynamic modules
|
|
|
+ loadedDynamicModules := make(map[string]bool)
|
|
|
for _, match := range matches {
|
|
|
- module := match[1]
|
|
|
- // If the module doesn't end with "_module", add it
|
|
|
- if !strings.HasSuffix(module, "_module") {
|
|
|
- module = module + "_module"
|
|
|
+ if len(match) > 1 {
|
|
|
+ // Extract the module name without path and suffix
|
|
|
+ moduleName := match[1]
|
|
|
+ // Some normalization to match format in GetModules
|
|
|
+ moduleName = strings.TrimPrefix(moduleName, "ngx_")
|
|
|
+ moduleName = strings.TrimSuffix(moduleName, "_module")
|
|
|
+ loadedDynamicModules[moduleName] = true
|
|
|
}
|
|
|
- modules = append(modules, module)
|
|
|
}
|
|
|
|
|
|
- return modules
|
|
|
+ // Update the status for each module in the cache
|
|
|
+ for key := range modulesCache.Keys() {
|
|
|
+ // If the module is already marked as dynamic, check if it's actually loaded
|
|
|
+ if loadedDynamicModules[key] {
|
|
|
+ modulesCache.Set(key, Module{
|
|
|
+ Name: key,
|
|
|
+ Dynamic: true,
|
|
|
+ Loaded: true,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-func IsModuleLoaded(module string) bool {
|
|
|
- modules := GetModules()
|
|
|
-
|
|
|
- for _, m := range modules {
|
|
|
- if m == module {
|
|
|
- return true
|
|
|
+func GetModules() *orderedmap.OrderedMap[string, Module] {
|
|
|
+ modulesCacheLock.RLock()
|
|
|
+ cachedModules := modulesCache
|
|
|
+ modulesCacheLock.RUnlock()
|
|
|
+
|
|
|
+ // If we have cached modules and PID file hasn't changed, return cached modules
|
|
|
+ if cachedModules.Len() > 0 && !isPIDFileChanged() {
|
|
|
+ return cachedModules
|
|
|
+ }
|
|
|
+
|
|
|
+ // If PID has changed or we don't have cached modules, get fresh modules
|
|
|
+ out := getNginxV()
|
|
|
+
|
|
|
+ // Regular expression to find built-in modules in nginx -V output
|
|
|
+ builtinRe := regexp.MustCompile(`--with-([a-zA-Z0-9_-]+)(_module)?`)
|
|
|
+ builtinMatches := builtinRe.FindAllStringSubmatch(out, -1)
|
|
|
+
|
|
|
+ // Extract built-in module names from matches and put in map for quick lookup
|
|
|
+ moduleMap := make(map[string]bool)
|
|
|
+ for _, match := range builtinMatches {
|
|
|
+ if len(match) > 1 {
|
|
|
+ module := match[1]
|
|
|
+ moduleMap[module] = true
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- return false
|
|
|
-}
|
|
|
+
|
|
|
+ // Regular expression to find dynamic modules in nginx -V output
|
|
|
+ dynamicRe := regexp.MustCompile(`--with-([a-zA-Z0-9_-]+)(_module)?=dynamic`)
|
|
|
+ dynamicMatches := dynamicRe.FindAllStringSubmatch(out, -1)
|
|
|
+
|
|
|
+ // Extract dynamic module names from matches
|
|
|
+ for _, match := range dynamicMatches {
|
|
|
+ if len(match) > 1 {
|
|
|
+ module := match[1]
|
|
|
+ // Only add if not already in list (to avoid duplicates)
|
|
|
+ if !moduleMap[module] {
|
|
|
+ moduleMap[module] = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Update cache
|
|
|
+ modulesCacheLock.Lock()
|
|
|
+ modulesCache = orderedmap.NewOrderedMap[string, Module]()
|
|
|
+ for module := range moduleMap {
|
|
|
+ // Mark modules as built-in (loaded) or dynamic (potentially not loaded)
|
|
|
+ if strings.Contains(out, "--with-"+module+"=dynamic") {
|
|
|
+ modulesCache.Set(module, Module{
|
|
|
+ Name: module,
|
|
|
+ Dynamic: true,
|
|
|
+ Loaded: true,
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ modulesCache.Set(module, Module{
|
|
|
+ Name: module,
|
|
|
+ Dynamic: true,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ modulesCacheLock.Unlock()
|
|
|
+
|
|
|
+ // Update dynamic modules status by checking if they're actually loaded
|
|
|
+ updateDynamicModulesStatus()
|
|
|
+
|
|
|
+ // Update PID file info
|
|
|
+ updatePIDFileInfo()
|
|
|
+
|
|
|
+ return modulesCache
|
|
|
+}
|
|
|
+
|
|
|
+// IsModuleLoaded checks if a module is loaded in Nginx
|
|
|
+func IsModuleLoaded(module string) bool {
|
|
|
+ // Ensure modules are in the cache
|
|
|
+ if modulesCache.Len() == 0 {
|
|
|
+ GetModules()
|
|
|
+ }
|
|
|
+
|
|
|
+ modulesCacheLock.RLock()
|
|
|
+ defer modulesCacheLock.RUnlock()
|
|
|
+
|
|
|
+ status, exists := modulesCache.Get(module)
|
|
|
+ if !exists {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ return status.Loaded
|
|
|
+}
|