您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

database.go 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. // Copyright (c) 2012-2014 Jeremy Latt
  2. // Copyright (c) 2016 Daniel Oaks <daniel@danieloaks.net>
  3. // released under the MIT license
  4. package irc
  5. import (
  6. "encoding/base64"
  7. "encoding/json"
  8. "fmt"
  9. "log"
  10. "os"
  11. "strings"
  12. "time"
  13. "github.com/oragono/oragono/irc/modes"
  14. "github.com/oragono/oragono/irc/passwd"
  15. "github.com/oragono/oragono/irc/utils"
  16. "github.com/tidwall/buntdb"
  17. )
  18. const (
  19. // 'version' of the database schema
  20. keySchemaVersion = "db.version"
  21. // latest schema of the db
  22. latestDbSchema = "3"
  23. // key for the primary salt used by the ircd
  24. keySalt = "crypto.salt"
  25. )
  26. type SchemaChanger func(*Config, *buntdb.Tx) error
  27. type SchemaChange struct {
  28. InitialVersion string // the change will take this version
  29. TargetVersion string // and transform it into this version
  30. Changer SchemaChanger
  31. }
  32. // maps an initial version to a schema change capable of upgrading it
  33. var schemaChanges map[string]SchemaChange
  34. type incompatibleSchemaError struct {
  35. currentVersion string
  36. requiredVersion string
  37. }
  38. func IncompatibleSchemaError(currentVersion string) (result *incompatibleSchemaError) {
  39. return &incompatibleSchemaError{
  40. currentVersion: currentVersion,
  41. requiredVersion: latestDbSchema,
  42. }
  43. }
  44. func (err *incompatibleSchemaError) Error() string {
  45. return fmt.Sprintf("Database requires update. Expected schema v%s, got v%s", err.requiredVersion, err.currentVersion)
  46. }
  47. // InitDB creates the database.
  48. func InitDB(path string) {
  49. // prepare kvstore db
  50. //TODO(dan): fail if already exists instead? don't want to overwrite good data
  51. os.Remove(path)
  52. store, err := buntdb.Open(path)
  53. if err != nil {
  54. log.Fatal(fmt.Sprintf("Failed to open datastore: %s", err.Error()))
  55. }
  56. defer store.Close()
  57. err = store.Update(func(tx *buntdb.Tx) error {
  58. // set base db salt
  59. salt, err := passwd.NewSalt()
  60. encodedSalt := base64.StdEncoding.EncodeToString(salt)
  61. if err != nil {
  62. log.Fatal("Could not generate cryptographically-secure salt for the user:", err.Error())
  63. }
  64. tx.Set(keySalt, encodedSalt, nil)
  65. // set schema version
  66. tx.Set(keySchemaVersion, latestDbSchema, nil)
  67. return nil
  68. })
  69. if err != nil {
  70. log.Fatal("Could not save datastore:", err.Error())
  71. }
  72. }
  73. // OpenDatabase returns an existing database, performing a schema version check.
  74. func OpenDatabase(config *Config) (*buntdb.DB, error) {
  75. return openDatabaseInternal(config, config.Datastore.AutoUpgrade)
  76. }
  77. // open the database, giving it at most one chance to auto-upgrade the schema
  78. func openDatabaseInternal(config *Config, allowAutoupgrade bool) (db *buntdb.DB, err error) {
  79. db, err = buntdb.Open(config.Datastore.Path)
  80. if err != nil {
  81. return
  82. }
  83. defer func() {
  84. if err != nil && db != nil {
  85. db.Close()
  86. db = nil
  87. }
  88. }()
  89. // read the current version string
  90. var version string
  91. err = db.View(func(tx *buntdb.Tx) error {
  92. version, err = tx.Get(keySchemaVersion)
  93. return err
  94. })
  95. if err != nil {
  96. return
  97. }
  98. if version == latestDbSchema {
  99. // success
  100. return
  101. }
  102. // XXX quiesce the DB so we can be sure it's safe to make a backup copy
  103. db.Close()
  104. db = nil
  105. if allowAutoupgrade {
  106. err = performAutoUpgrade(version, config)
  107. if err != nil {
  108. return
  109. }
  110. // successful autoupgrade, let's try this again:
  111. return openDatabaseInternal(config, false)
  112. } else {
  113. err = IncompatibleSchemaError(version)
  114. return
  115. }
  116. }
  117. func performAutoUpgrade(currentVersion string, config *Config) (err error) {
  118. path := config.Datastore.Path
  119. log.Printf("attempting to auto-upgrade schema from version %s to %s\n", currentVersion, latestDbSchema)
  120. timestamp := time.Now().UTC().Format("2006-01-02-15:04:05.000Z")
  121. backupPath := fmt.Sprintf("%s.v%s.%s.bak", path, currentVersion, timestamp)
  122. log.Printf("making a backup of current database at %s\n", backupPath)
  123. err = utils.CopyFile(path, backupPath)
  124. if err != nil {
  125. return err
  126. }
  127. err = UpgradeDB(config)
  128. if err != nil {
  129. // database upgrade is a single transaction, so we don't need to restore the backup;
  130. // we can just delete it
  131. os.Remove(backupPath)
  132. }
  133. return err
  134. }
  135. // UpgradeDB upgrades the datastore to the latest schema.
  136. func UpgradeDB(config *Config) (err error) {
  137. store, err := buntdb.Open(config.Datastore.Path)
  138. if err != nil {
  139. return err
  140. }
  141. defer store.Close()
  142. var version string
  143. err = store.Update(func(tx *buntdb.Tx) error {
  144. for {
  145. version, _ = tx.Get(keySchemaVersion)
  146. change, schemaNeedsChange := schemaChanges[version]
  147. if !schemaNeedsChange {
  148. if version == latestDbSchema {
  149. // success!
  150. break
  151. }
  152. // unable to upgrade to the desired version, roll back
  153. return IncompatibleSchemaError(version)
  154. }
  155. log.Println("attempting to update schema from version " + version)
  156. err := change.Changer(config, tx)
  157. if err != nil {
  158. return err
  159. }
  160. _, _, err = tx.Set(keySchemaVersion, change.TargetVersion, nil)
  161. if err != nil {
  162. return err
  163. }
  164. log.Println("successfully updated schema to version " + change.TargetVersion)
  165. }
  166. return nil
  167. })
  168. if err != nil {
  169. log.Println("database upgrade failed and was rolled back")
  170. }
  171. return err
  172. }
  173. func schemaChangeV1toV2(config *Config, tx *buntdb.Tx) error {
  174. // == version 1 -> 2 ==
  175. // account key changes and account.verified key bugfix.
  176. var keysToRemove []string
  177. newKeys := make(map[string]string)
  178. tx.AscendKeys("account *", func(key, value string) bool {
  179. keysToRemove = append(keysToRemove, key)
  180. splitkey := strings.Split(key, " ")
  181. // work around bug
  182. if splitkey[2] == "exists" {
  183. // manually create new verified key
  184. newVerifiedKey := fmt.Sprintf("%s.verified %s", splitkey[0], splitkey[1])
  185. newKeys[newVerifiedKey] = "1"
  186. } else if splitkey[1] == "%s" {
  187. return true
  188. }
  189. newKey := fmt.Sprintf("%s.%s %s", splitkey[0], splitkey[2], splitkey[1])
  190. newKeys[newKey] = value
  191. return true
  192. })
  193. for _, key := range keysToRemove {
  194. tx.Delete(key)
  195. }
  196. for key, value := range newKeys {
  197. tx.Set(key, value, nil)
  198. }
  199. return nil
  200. }
  201. // 1. channel founder names should be casefolded
  202. // 2. founder should be explicitly granted the ChannelFounder user mode
  203. // 3. explicitly initialize stored channel modes to the server default values
  204. func schemaChangeV2ToV3(config *Config, tx *buntdb.Tx) error {
  205. var channels []string
  206. prefix := "channel.exists "
  207. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  208. if !strings.HasPrefix(key, prefix) {
  209. return false
  210. }
  211. chname := strings.TrimPrefix(key, prefix)
  212. channels = append(channels, chname)
  213. return true
  214. })
  215. // founder names should be casefolded
  216. // founder should be explicitly granted the ChannelFounder user mode
  217. for _, channel := range channels {
  218. founderKey := "channel.founder " + channel
  219. founder, _ := tx.Get(founderKey)
  220. if founder != "" {
  221. founder, err := CasefoldName(founder)
  222. if err == nil {
  223. tx.Set(founderKey, founder, nil)
  224. accountToUmode := map[string]modes.Mode{
  225. founder: modes.ChannelFounder,
  226. }
  227. atustr, _ := json.Marshal(accountToUmode)
  228. tx.Set("channel.accounttoumode "+channel, string(atustr), nil)
  229. }
  230. }
  231. }
  232. // explicitly store the channel modes
  233. defaultModes := ParseDefaultChannelModes(config)
  234. modeStrings := make([]string, len(defaultModes))
  235. for i, mode := range defaultModes {
  236. modeStrings[i] = string(mode)
  237. }
  238. defaultModeString := strings.Join(modeStrings, "")
  239. for _, channel := range channels {
  240. tx.Set("channel.modes "+channel, defaultModeString, nil)
  241. }
  242. return nil
  243. }
  244. func init() {
  245. allChanges := []SchemaChange{
  246. {
  247. InitialVersion: "1",
  248. TargetVersion: "2",
  249. Changer: schemaChangeV1toV2,
  250. },
  251. {
  252. InitialVersion: "2",
  253. TargetVersion: "3",
  254. Changer: schemaChangeV2ToV3,
  255. },
  256. }
  257. // build the index
  258. schemaChanges = make(map[string]SchemaChange)
  259. for _, change := range allChanges {
  260. schemaChanges[change.InitialVersion] = change
  261. }
  262. }