123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- package nginx
- import (
- "os"
- "regexp"
- "strings"
- "sync"
- "time"
- "github.com/elliotchance/orderedmap/v3"
- )
- const (
- ModuleStream = "stream"
- )
- 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 {
- 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
- }
- }
- // 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 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
- }
- }
- // 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
- }
|