modules.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. package nginx
  2. import (
  3. "os"
  4. "regexp"
  5. "strings"
  6. "sync"
  7. "time"
  8. "github.com/elliotchance/orderedmap/v3"
  9. )
  10. const (
  11. ModuleStream = "stream"
  12. )
  13. type Module struct {
  14. Name string `json:"name"`
  15. Params string `json:"params,omitempty"`
  16. Dynamic bool `json:"dynamic"`
  17. Loaded bool `json:"loaded"`
  18. }
  19. // modulesCache stores the cached modules list and related metadata
  20. var (
  21. modulesCache = orderedmap.NewOrderedMap[string, *Module]()
  22. modulesCacheLock sync.RWMutex
  23. lastPIDPath string
  24. lastPIDModTime time.Time
  25. lastPIDSize int64
  26. )
  27. // clearModulesCache clears the modules cache
  28. func clearModulesCache() {
  29. modulesCacheLock.Lock()
  30. defer modulesCacheLock.Unlock()
  31. modulesCache = orderedmap.NewOrderedMap[string, *Module]()
  32. lastPIDPath = ""
  33. lastPIDModTime = time.Time{}
  34. lastPIDSize = 0
  35. }
  36. // isPIDFileChanged checks if the PID file has changed since the last check
  37. func isPIDFileChanged() bool {
  38. pidPath := GetPIDPath()
  39. // If PID path has changed, consider it changed
  40. if pidPath != lastPIDPath {
  41. return true
  42. }
  43. // If Nginx is not running, consider PID changed
  44. if !IsNginxRunning() {
  45. return true
  46. }
  47. // Check if PID file has changed (modification time or size)
  48. fileInfo, err := os.Stat(pidPath)
  49. if err != nil {
  50. return true
  51. }
  52. modTime := fileInfo.ModTime()
  53. size := fileInfo.Size()
  54. return modTime != lastPIDModTime || size != lastPIDSize
  55. }
  56. // updatePIDFileInfo updates the stored PID file information
  57. func updatePIDFileInfo() {
  58. pidPath := GetPIDPath()
  59. if fileInfo, err := os.Stat(pidPath); err == nil {
  60. modulesCacheLock.Lock()
  61. defer modulesCacheLock.Unlock()
  62. lastPIDPath = pidPath
  63. lastPIDModTime = fileInfo.ModTime()
  64. lastPIDSize = fileInfo.Size()
  65. }
  66. }
  67. // updateDynamicModulesStatus checks which dynamic modules are actually loaded in the running Nginx
  68. func updateDynamicModulesStatus() {
  69. modulesCacheLock.Lock()
  70. defer modulesCacheLock.Unlock()
  71. // If cache is empty, there's nothing to update
  72. if modulesCache.Len() == 0 {
  73. return
  74. }
  75. // Get nginx -T output to check for loaded modules
  76. out := getNginxT()
  77. if out == "" {
  78. return
  79. }
  80. // Regular expression to find loaded dynamic modules in nginx -T output
  81. // Look for lines like "load_module modules/ngx_http_image_filter_module.so;"
  82. loadModuleRe := regexp.MustCompile(`load_module\s+(?:modules/|/.*/)([a-zA-Z0-9_-]+)\.so;`)
  83. matches := loadModuleRe.FindAllStringSubmatch(out, -1)
  84. // Create a map of loaded dynamic modules
  85. loadedDynamicModules := make(map[string]bool)
  86. for _, match := range matches {
  87. if len(match) > 1 {
  88. // Extract the module name without path and suffix
  89. moduleName := match[1]
  90. // Some normalization to match format in GetModules
  91. moduleName = strings.TrimPrefix(moduleName, "ngx_")
  92. moduleName = strings.TrimSuffix(moduleName, "_module")
  93. loadedDynamicModules[moduleName] = true
  94. }
  95. }
  96. // Update the status for each module in the cache
  97. for key := range modulesCache.Keys() {
  98. // If the module is already marked as dynamic, check if it's actually loaded
  99. if loadedDynamicModules[key] {
  100. module, ok := modulesCache.Get(key)
  101. if ok {
  102. module.Loaded = true
  103. }
  104. }
  105. }
  106. }
  107. func GetModules() *orderedmap.OrderedMap[string, *Module] {
  108. modulesCacheLock.RLock()
  109. cachedModules := modulesCache
  110. modulesCacheLock.RUnlock()
  111. // If we have cached modules and PID file hasn't changed, return cached modules
  112. if cachedModules.Len() > 0 && !isPIDFileChanged() {
  113. return cachedModules
  114. }
  115. // If PID has changed or we don't have cached modules, get fresh modules
  116. out := getNginxV()
  117. // Regular expression to find module parameters with values
  118. paramRe := regexp.MustCompile(`--with-([a-zA-Z0-9_-]+)(?:_module)?(?:=([^"'\s]+|"[^"]*"|'[^']*'))?`)
  119. paramMatches := paramRe.FindAllStringSubmatch(out, -1)
  120. // Update cache
  121. modulesCacheLock.Lock()
  122. modulesCache = orderedmap.NewOrderedMap[string, *Module]()
  123. // Extract module names and parameters from matches
  124. for _, match := range paramMatches {
  125. if len(match) > 1 {
  126. module := match[1]
  127. var params string
  128. // Check if there's a parameter value
  129. if len(match) > 2 && match[2] != "" {
  130. params = match[2]
  131. // Remove surrounding quotes if present
  132. params = strings.TrimPrefix(params, "'")
  133. params = strings.TrimPrefix(params, "\"")
  134. params = strings.TrimSuffix(params, "'")
  135. params = strings.TrimSuffix(params, "\"")
  136. }
  137. // Special handling for configuration options like cc-opt, not actual modules
  138. if module == "cc-opt" || module == "ld-opt" || module == "prefix" {
  139. modulesCache.Set(module, &Module{
  140. Name: module,
  141. Params: params,
  142. Dynamic: false,
  143. Loaded: true,
  144. })
  145. continue
  146. }
  147. // Determine if the module is dynamic
  148. isDynamic := false
  149. if strings.Contains(out, "--with-"+module+"=dynamic") ||
  150. strings.Contains(out, "--with-"+module+"_module=dynamic") {
  151. isDynamic = true
  152. }
  153. if params == "dynamic" {
  154. params = ""
  155. }
  156. modulesCache.Set(module, &Module{
  157. Name: module,
  158. Params: params,
  159. Dynamic: isDynamic,
  160. Loaded: !isDynamic || isDynamic, // Static modules are always loaded, dynamic need to be checked
  161. })
  162. }
  163. }
  164. modulesCacheLock.Unlock()
  165. // Update dynamic modules status by checking if they're actually loaded
  166. updateDynamicModulesStatus()
  167. // Update PID file info
  168. updatePIDFileInfo()
  169. return modulesCache
  170. }
  171. // IsModuleLoaded checks if a module is loaded in Nginx
  172. func IsModuleLoaded(module string) bool {
  173. // Ensure modules are in the cache
  174. if modulesCache.Len() == 0 {
  175. GetModules()
  176. }
  177. modulesCacheLock.RLock()
  178. defer modulesCacheLock.RUnlock()
  179. status, exists := modulesCache.Get(module)
  180. if !exists {
  181. return false
  182. }
  183. return status.Loaded
  184. }