1
0
Fork 0
forked from External/ergo

Merge remote-tracking branch 'origin/master' into issue702

This commit is contained in:
Shivaram Lingamneni 2019-12-23 11:32:15 -05:00
commit 7a6925fa3f
12 changed files with 680 additions and 97 deletions

View file

@ -13,24 +13,32 @@ type channelManagerEntry struct {
// think the channel is empty (without holding a lock across the entire Channel.Join()
// call)
pendingJoins int
skeleton string
}
// ChannelManager keeps track of all the channels on the server,
// providing synchronization for creation of new channels on first join,
// cleanup of empty channels on last part, and renames.
type ChannelManager struct {
sync.RWMutex // tier 2
chans map[string]*channelManagerEntry
registeredChannels map[string]bool
server *Server
sync.RWMutex // tier 2
// chans is the main data structure, mapping casefolded name -> *Channel
chans map[string]*channelManagerEntry
chansSkeletons StringSet // skeletons of *unregistered* chans
registeredChannels StringSet // casefolds of registered chans
registeredSkeletons StringSet // skeletons of registered chans
purgedChannels StringSet // casefolds of purged chans
server *Server
}
// NewChannelManager returns a new ChannelManager.
func (cm *ChannelManager) Initialize(server *Server) {
cm.chans = make(map[string]*channelManagerEntry)
cm.chansSkeletons = make(StringSet)
cm.server = server
cm.loadRegisteredChannels(server.Config())
// purging should work even if registration is disabled
cm.purgedChannels = cm.server.channelRegistry.PurgedChannels()
}
func (cm *ChannelManager) loadRegisteredChannels(config *Config) {
@ -38,10 +46,23 @@ func (cm *ChannelManager) loadRegisteredChannels(config *Config) {
return
}
registeredChannels := cm.server.channelRegistry.AllChannels()
rawNames := cm.server.channelRegistry.AllChannels()
registeredChannels := make(StringSet, len(rawNames))
registeredSkeletons := make(StringSet, len(rawNames))
for _, name := range rawNames {
cfname, err := CasefoldChannel(name)
if err == nil {
registeredChannels.Add(cfname)
}
skeleton, err := Skeleton(name)
if err == nil {
registeredSkeletons.Add(skeleton)
}
}
cm.Lock()
defer cm.Unlock()
cm.registeredChannels = registeredChannels
cm.registeredSkeletons = registeredSkeletons
}
// Get returns an existing channel with name equivalent to `name`, or nil
@ -63,33 +84,49 @@ func (cm *ChannelManager) Get(name string) (channel *Channel) {
func (cm *ChannelManager) Join(client *Client, name string, key string, isSajoin bool, rb *ResponseBuffer) error {
server := client.server
casefoldedName, err := CasefoldChannel(name)
if err != nil || len(casefoldedName) > server.Config().Limits.ChannelLen {
skeleton, skerr := Skeleton(name)
if err != nil || skerr != nil || len(casefoldedName) > server.Config().Limits.ChannelLen {
return errNoSuchChannel
}
channel := func() *Channel {
channel, err := func() (*Channel, error) {
cm.Lock()
defer cm.Unlock()
if cm.purgedChannels.Has(casefoldedName) {
return nil, errChannelPurged
}
entry := cm.chans[casefoldedName]
if entry == nil {
registered := cm.registeredChannels[casefoldedName]
registered := cm.registeredChannels.Has(casefoldedName)
// enforce OpOnlyCreation
if !registered && server.Config().Channels.OpOnlyCreation && !client.HasRoleCapabs("chanreg") {
return nil
return nil, errInsufficientPrivs
}
// enforce confusables
if cm.chansSkeletons.Has(skeleton) || (!registered && cm.registeredSkeletons.Has(skeleton)) {
return nil, errConfusableIdentifier
}
entry = &channelManagerEntry{
channel: NewChannel(server, name, registered),
channel: NewChannel(server, name, casefoldedName, registered),
pendingJoins: 0,
}
if !registered {
// for an unregistered channel, we already have the correct unfolded name
// and therefore the final skeleton. for a registered channel, we don't have
// the unfolded name yet (it needs to be loaded from the db), but we already
// have the final skeleton in `registeredSkeletons` so we don't need to track it
cm.chansSkeletons.Add(skeleton)
entry.skeleton = skeleton
}
cm.chans[casefoldedName] = entry
}
entry.pendingJoins += 1
return entry.channel
return entry.channel, nil
}()
if channel == nil {
return errNoSuchChannel
if err != nil {
return err
}
channel.EnsureLoaded()
@ -104,8 +141,9 @@ func (cm *ChannelManager) maybeCleanup(channel *Channel, afterJoin bool) {
cm.Lock()
defer cm.Unlock()
nameCasefolded := channel.NameCasefolded()
entry := cm.chans[nameCasefolded]
cfname := channel.NameCasefolded()
entry := cm.chans[cfname]
if entry == nil || entry.channel != channel {
return
}
@ -114,7 +152,10 @@ func (cm *ChannelManager) maybeCleanup(channel *Channel, afterJoin bool) {
entry.pendingJoins -= 1
}
if entry.pendingJoins == 0 && entry.channel.IsClean() {
delete(cm.chans, nameCasefolded)
delete(cm.chans, cfname)
if entry.skeleton != "" {
delete(cm.chansSkeletons, entry.skeleton)
}
}
}
@ -172,7 +213,7 @@ func (cm *ChannelManager) SetRegistered(channelName string, account string) (err
if err != nil {
return err
}
cm.registeredChannels[cfname] = true
cm.registeredChannels.Add(cfname)
return nil
}
@ -206,13 +247,17 @@ func (cm *ChannelManager) SetUnregistered(channelName string, account string) (e
}
// Rename renames a channel (but does not notify the members)
func (cm *ChannelManager) Rename(name string, newname string) (err error) {
func (cm *ChannelManager) Rename(name string, newName string) (err error) {
cfname, err := CasefoldChannel(name)
if err != nil {
return errNoSuchChannel
}
cfnewname, err := CasefoldChannel(newname)
newCfname, err := CasefoldChannel(newName)
if err != nil {
return errInvalidChannelName
}
newSkeleton, err := Skeleton(newName)
if err != nil {
return errInvalidChannelName
}
@ -231,22 +276,35 @@ func (cm *ChannelManager) Rename(name string, newname string) (err error) {
cm.Lock()
defer cm.Unlock()
if cm.chans[cfnewname] != nil || cm.registeredChannels[cfnewname] {
if cm.chans[newCfname] != nil || cm.registeredChannels.Has(newCfname) {
return errChannelNameInUse
}
if cm.chansSkeletons.Has(newSkeleton) || cm.registeredSkeletons.Has(newSkeleton) {
return errChannelNameInUse
}
entry := cm.chans[cfname]
if entry == nil {
if entry == nil || !entry.channel.IsLoaded() {
return errNoSuchChannel
}
channel = entry.channel
info = channel.ExportRegistration(IncludeInitial)
registered := info.Founder != ""
delete(cm.chans, cfname)
cm.chans[cfnewname] = entry
if cm.registeredChannels[cfname] {
cm.chans[newCfname] = entry
if registered {
delete(cm.registeredChannels, cfname)
cm.registeredChannels[cfnewname] = true
if oldSkeleton, err := Skeleton(info.Name); err == nil {
delete(cm.registeredSkeletons, oldSkeleton)
}
cm.registeredChannels.Add(newCfname)
cm.registeredSkeletons.Add(newSkeleton)
} else {
delete(cm.chansSkeletons, entry.skeleton)
cm.chansSkeletons.Add(newSkeleton)
entry.skeleton = newSkeleton
cm.chans[cfname] = entry
}
entry.channel.Rename(newname, cfnewname)
entry.channel.Rename(newName, newCfname)
return nil
}
@ -269,3 +327,50 @@ func (cm *ChannelManager) Channels() (result []*Channel) {
}
return
}
// Purge marks a channel as purged.
func (cm *ChannelManager) Purge(chname string, record ChannelPurgeRecord) (err error) {
chname, err = CasefoldChannel(chname)
if err != nil {
return errInvalidChannelName
}
cm.Lock()
cm.purgedChannels.Add(chname)
cm.Unlock()
cm.server.channelRegistry.PurgeChannel(chname, record)
return nil
}
// IsPurged queries whether a channel is purged.
func (cm *ChannelManager) IsPurged(chname string) (result bool) {
chname, err := CasefoldChannel(chname)
if err != nil {
return false
}
cm.Lock()
result = cm.purgedChannels.Has(chname)
cm.Unlock()
return
}
// Unpurge deletes a channel's purged status.
func (cm *ChannelManager) Unpurge(chname string) (err error) {
chname, err = CasefoldChannel(chname)
if err != nil {
return errNoSuchChannel
}
cm.Lock()
found := cm.purgedChannels.Has(chname)
delete(cm.purgedChannels, chname)
cm.Unlock()
cm.server.channelRegistry.UnpurgeChannel(chname)
if !found {
return errNoSuchChannel
}
return nil
}