Quellcode durchsuchen

Merge remote-tracking branch 'origin/master' into vhosts.4

tags/v0.12.0
Shivaram Lingamneni vor 6 Jahren
Ursprung
Commit
74fa58dda0
23 geänderte Dateien mit 622 neuen und 355 gelöschten Zeilen
  1. 10
    0
      .travis.gofmt.sh
  2. 1
    0
      .travis.yml
  3. 12
    2
      DEVELOPING.md
  4. 4
    2
      Makefile
  5. 34
    48
      irc/channel.go
  6. 2
    2
      irc/chanserv.go
  7. 22
    16
      irc/client.go
  8. 10
    3
      irc/config.go
  9. 90
    24
      irc/database.go
  10. 1
    5
      irc/gateways.go
  11. 6
    24
      irc/getters.go
  12. 34
    56
      irc/handlers.go
  13. 33
    97
      irc/modes.go
  14. 136
    13
      irc/modes/modes.go
  15. 37
    0
      irc/modes/modes_test.go
  16. 3
    3
      irc/roleplay.go
  17. 18
    15
      irc/server.go
  18. 69
    32
      irc/socket.go
  19. 57
    0
      irc/stats.go
  20. 3
    12
      irc/types.go
  21. 31
    0
      irc/utils/os.go
  22. 4
    1
      oragono.go
  23. 5
    0
      oragono.yaml

+ 10
- 0
.travis.gofmt.sh Datei anzeigen

@@ -0,0 +1,10 @@
1
+#!/bin/bash
2
+
3
+# exclude vendor/
4
+SOURCES="./oragono.go ./irc"
5
+
6
+if [ -n "$(gofmt -s -l $SOURCES)" ]; then
7
+    echo "Go code is not formatted correctly with \`gofmt -s\`:"
8
+    gofmt -s -d $SOURCES
9
+    exit 1
10
+fi

+ 1
- 0
.travis.yml Datei anzeigen

@@ -7,3 +7,4 @@ script:
7 7
 - tar -xzf goreleaser_Linux_x86_64.tar.gz -C $GOPATH/bin
8 8
 - make
9 9
 - make test
10
+- bash ./.travis.gofmt.sh

+ 12
- 2
DEVELOPING.md Datei anzeigen

@@ -79,9 +79,19 @@ As well, there's a decent set of 'tests' here, which I like to run Oragono throu
79 79
 https://github.com/DanielOaks/irctest
80 80
 
81 81
 
82
-## Debugging Hangs
82
+## Debugging
83 83
 
84
-To debug a hang, the best thing to do is to get a stack trace. Go's nice, and you can do so by running this:
84
+It's helpful to enable all loglines while developing. Here's how to configure this:
85
+
86
+```yaml
87
+logging:
88
+    -
89
+        method: stderr
90
+        type: "*"
91
+        level: debug
92
+```
93
+
94
+To debug a hang, the best thing to do is to get a stack trace. The easiest way to get stack traces is with the [pprof listener](https://golang.org/pkg/net/http/pprof/), which can be enabled in the `debug` section of the config. Once it's enabled, you can navigate to `http://localhost:6060/debug/pprof/` in your browser and go from there. If that doesn't work, try:
85 95
 
86 96
     $ kill -ABRT <procid>
87 97
 

+ 4
- 2
Makefile Datei anzeigen

@@ -12,5 +12,7 @@ deps:
12 12
 	git submodule update --init
13 13
 
14 14
 test:
15
-	cd irc && go test .
16
-	cd irc && go vet .
15
+	cd irc && go test . && go vet .
16
+	cd irc/isupport && go test . && go vet .
17
+	cd irc/modes && go test . && go vet .
18
+	cd irc/utils && go test . && go vet .

+ 34
- 48
irc/channel.go Datei anzeigen

@@ -20,7 +20,7 @@ import (
20 20
 
21 21
 // Channel represents a channel that clients can join.
22 22
 type Channel struct {
23
-	flags             modes.ModeSet
23
+	flags             *modes.ModeSet
24 24
 	lists             map[modes.Mode]*UserMaskSet
25 25
 	key               string
26 26
 	members           MemberSet
@@ -51,7 +51,7 @@ func NewChannel(s *Server, name string, regInfo *RegisteredChannel) *Channel {
51 51
 
52 52
 	channel := &Channel{
53 53
 		createdTime: time.Now(), // may be overwritten by applyRegInfo
54
-		flags:       make(modes.ModeSet),
54
+		flags:       modes.NewModeSet(),
55 55
 		lists: map[modes.Mode]*UserMaskSet{
56 56
 			modes.BanMask:    NewUserMaskSet(),
57 57
 			modes.ExceptMask: NewUserMaskSet(),
@@ -68,7 +68,7 @@ func NewChannel(s *Server, name string, regInfo *RegisteredChannel) *Channel {
68 68
 		channel.applyRegInfo(regInfo)
69 69
 	} else {
70 70
 		for _, mode := range s.DefaultChannelModes() {
71
-			channel.flags[mode] = true
71
+			channel.flags.SetMode(mode, true)
72 72
 		}
73 73
 	}
74 74
 
@@ -87,7 +87,7 @@ func (channel *Channel) applyRegInfo(chanReg *RegisteredChannel) {
87 87
 	channel.key = chanReg.Key
88 88
 
89 89
 	for _, mode := range chanReg.Modes {
90
-		channel.flags[mode] = true
90
+		channel.flags.SetMode(mode, true)
91 91
 	}
92 92
 	for _, mask := range chanReg.Banlist {
93 93
 		channel.lists[modes.BanMask].Add(mask)
@@ -120,9 +120,7 @@ func (channel *Channel) ExportRegistration(includeFlags uint) (info RegisteredCh
120 120
 
121 121
 	if includeFlags&IncludeModes != 0 {
122 122
 		info.Key = channel.key
123
-		for mode := range channel.flags {
124
-			info.Modes = append(info.Modes, mode)
125
-		}
123
+		info.Modes = channel.flags.AllModes()
126 124
 	}
127 125
 
128 126
 	if includeFlags&IncludeLists != 0 {
@@ -225,14 +223,16 @@ func (channel *Channel) ClientIsAtLeast(client *Client, permission modes.Mode) b
225 223
 	channel.stateMutex.RLock()
226 224
 	defer channel.stateMutex.RUnlock()
227 225
 
226
+	clientModes := channel.members[client]
227
+
228 228
 	// get voice, since it's not a part of ChannelPrivModes
229
-	if channel.members.HasMode(client, permission) {
229
+	if clientModes.HasMode(permission) {
230 230
 		return true
231 231
 	}
232 232
 
233 233
 	// check regular modes
234 234
 	for _, mode := range modes.ChannelPrivModes {
235
-		if channel.members.HasMode(client, mode) {
235
+		if clientModes.HasMode(mode) {
236 236
 			return true
237 237
 		}
238 238
 
@@ -263,14 +263,14 @@ func (channel *Channel) ClientHasPrivsOver(client *Client, target *Client) bool
263 263
 	targetModes := channel.members[target]
264 264
 	result := false
265 265
 	for _, mode := range modes.ChannelPrivModes {
266
-		if clientModes[mode] {
266
+		if clientModes.HasMode(mode) {
267 267
 			result = true
268 268
 			// admins cannot kick other admins
269
-			if mode == modes.ChannelAdmin && targetModes[modes.ChannelAdmin] {
269
+			if mode == modes.ChannelAdmin && targetModes.HasMode(modes.ChannelAdmin) {
270 270
 				result = false
271 271
 			}
272 272
 			break
273
-		} else if channel.members[target][mode] {
273
+		} else if targetModes.HasMode(mode) {
274 274
 			break
275 275
 		}
276 276
 	}
@@ -331,14 +331,11 @@ func (channel *Channel) modeStrings(client *Client) (result []string) {
331 331
 		mods += modes.UserLimit.String()
332 332
 	}
333 333
 
334
+	mods += channel.flags.String()
335
+
334 336
 	channel.stateMutex.RLock()
335 337
 	defer channel.stateMutex.RUnlock()
336 338
 
337
-	// flags
338
-	for mode := range channel.flags {
339
-		mods += mode.String()
340
-	}
341
-
342 339
 	result = []string{mods}
343 340
 
344 341
 	// args for flags with args: The order must match above to keep
@@ -395,7 +392,7 @@ func (channel *Channel) Join(client *Client, key string, rb *ResponseBuffer) {
395 392
 	}
396 393
 
397 394
 	isInvited := channel.lists[modes.InviteMask].Match(client.nickMaskCasefolded)
398
-	if channel.flags[modes.InviteOnly] && !isInvited {
395
+	if channel.flags.HasMode(modes.InviteOnly) && !isInvited {
399 396
 		rb.Add(nil, client.server.name, ERR_INVITEONLYCHAN, channel.name, fmt.Sprintf(client.t("Cannot join channel (+%s)"), "i"))
400 397
 		return
401 398
 	}
@@ -446,7 +443,7 @@ func (channel *Channel) Join(client *Client, key string, rb *ResponseBuffer) {
446 443
 		givenMode = &modes.ChannelOperator
447 444
 	}
448 445
 	if givenMode != nil {
449
-		channel.members[client][*givenMode] = true
446
+		channel.members[client].SetMode(*givenMode, true)
450 447
 	}
451 448
 	channel.stateMutex.Unlock()
452 449
 
@@ -515,12 +512,12 @@ func (channel *Channel) SendTopic(client *Client, rb *ResponseBuffer) {
515 512
 
516 513
 // SetTopic sets the topic of this channel, if the client is allowed to do so.
517 514
 func (channel *Channel) SetTopic(client *Client, topic string, rb *ResponseBuffer) {
518
-	if !(client.flags[modes.Operator] || channel.hasClient(client)) {
515
+	if !(client.HasMode(modes.Operator) || channel.hasClient(client)) {
519 516
 		rb.Add(nil, client.server.name, ERR_NOTONCHANNEL, channel.name, client.t("You're not on that channel"))
520 517
 		return
521 518
 	}
522 519
 
523
-	if channel.HasMode(modes.OpOnlyTopic) && !channel.ClientIsAtLeast(client, modes.ChannelOperator) {
520
+	if channel.flags.HasMode(modes.OpOnlyTopic) && !channel.ClientIsAtLeast(client, modes.ChannelOperator) {
524 521
 		rb.Add(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, client.t("You're not a channel operator"))
525 522
 		return
526 523
 	}
@@ -552,13 +549,13 @@ func (channel *Channel) CanSpeak(client *Client) bool {
552 549
 	defer channel.stateMutex.RUnlock()
553 550
 
554 551
 	_, hasClient := channel.members[client]
555
-	if channel.flags[modes.NoOutside] && !hasClient {
552
+	if channel.flags.HasMode(modes.NoOutside) && !hasClient {
556 553
 		return false
557 554
 	}
558
-	if channel.flags[modes.Moderated] && !channel.ClientIsAtLeast(client, modes.Voice) {
555
+	if channel.flags.HasMode(modes.Moderated) && !channel.ClientIsAtLeast(client, modes.Voice) {
559 556
 		return false
560 557
 	}
561
-	if channel.flags[modes.RegisteredOnly] && client.Account() == "" {
558
+	if channel.flags.HasMode(modes.RegisteredOnly) && client.Account() == "" {
562 559
 		return false
563 560
 	}
564 561
 	return true
@@ -682,13 +679,7 @@ func (channel *Channel) sendSplitMessage(msgid, cmd string, minPrefix *modes.Mod
682 679
 	}
683 680
 }
684 681
 
685
-func (channel *Channel) applyModeMemberNoMutex(client *Client, mode modes.Mode, op modes.ModeOp, nick string, rb *ResponseBuffer) *modes.ModeChange {
686
-	if nick == "" {
687
-		//TODO(dan): shouldn't this be handled before it reaches this function?
688
-		rb.Add(nil, client.server.name, ERR_NEEDMOREPARAMS, "MODE", client.t("Not enough parameters"))
689
-		return nil
690
-	}
691
-
682
+func (channel *Channel) applyModeToMember(client *Client, mode modes.Mode, op modes.ModeOp, nick string, rb *ResponseBuffer) (result *modes.ModeChange) {
692 683
 	casefoldedName, err := CasefoldName(nick)
693 684
 	target := channel.server.clients.Get(casefoldedName)
694 685
 	if err != nil || target == nil {
@@ -698,26 +689,21 @@ func (channel *Channel) applyModeMemberNoMutex(client *Client, mode modes.Mode,
698 689
 
699 690
 	channel.stateMutex.Lock()
700 691
 	modeset, exists := channel.members[target]
701
-	var already bool
702 692
 	if exists {
703
-		enable := op == modes.Add
704
-		already = modeset[mode] == enable
705
-		modeset[mode] = enable
693
+		if modeset.SetMode(mode, op == modes.Add) {
694
+			result = &modes.ModeChange{
695
+				Op:   op,
696
+				Mode: mode,
697
+				Arg:  nick,
698
+			}
699
+		}
706 700
 	}
707 701
 	channel.stateMutex.Unlock()
708 702
 
709 703
 	if !exists {
710 704
 		rb.Add(nil, client.server.name, ERR_USERNOTINCHANNEL, client.nick, channel.name, client.t("They aren't on that channel"))
711
-		return nil
712
-	} else if already {
713
-		return nil
714
-	} else {
715
-		return &modes.ModeChange{
716
-			Op:   op,
717
-			Mode: mode,
718
-			Arg:  nick,
719
-		}
720 705
 	}
706
+	return
721 707
 }
722 708
 
723 709
 // ShowMaskList shows the given list to the client.
@@ -790,7 +776,7 @@ func (channel *Channel) Quit(client *Client) {
790 776
 }
791 777
 
792 778
 func (channel *Channel) Kick(client *Client, target *Client, comment string, rb *ResponseBuffer) {
793
-	if !(client.flags[modes.Operator] || channel.hasClient(client)) {
779
+	if !(client.HasMode(modes.Operator) || channel.hasClient(client)) {
794 780
 		rb.Add(nil, client.server.name, ERR_NOTONCHANNEL, channel.name, client.t("You're not on that channel"))
795 781
 		return
796 782
 	}
@@ -823,7 +809,7 @@ func (channel *Channel) Kick(client *Client, target *Client, comment string, rb
823 809
 
824 810
 // Invite invites the given client to the channel, if the inviter can do so.
825 811
 func (channel *Channel) Invite(invitee *Client, inviter *Client, rb *ResponseBuffer) {
826
-	if channel.flags[modes.InviteOnly] && !channel.ClientIsAtLeast(inviter, modes.ChannelOperator) {
812
+	if channel.flags.HasMode(modes.InviteOnly) && !channel.ClientIsAtLeast(inviter, modes.ChannelOperator) {
827 813
 		rb.Add(nil, inviter.server.name, ERR_CHANOPRIVSNEEDED, channel.name, inviter.t("You're not a channel operator"))
828 814
 		return
829 815
 	}
@@ -834,7 +820,7 @@ func (channel *Channel) Invite(invitee *Client, inviter *Client, rb *ResponseBuf
834 820
 	}
835 821
 
836 822
 	//TODO(dan): handle this more nicely, keep a list of last X invited channels on invitee rather than explicitly modifying the invite list?
837
-	if channel.flags[modes.InviteOnly] {
823
+	if channel.flags.HasMode(modes.InviteOnly) {
838 824
 		nmc := invitee.NickCasefolded()
839 825
 		channel.stateMutex.Lock()
840 826
 		channel.lists[modes.InviteMask].Add(nmc)
@@ -850,7 +836,7 @@ func (channel *Channel) Invite(invitee *Client, inviter *Client, rb *ResponseBuf
850 836
 	//TODO(dan): should inviter.server.name here be inviter.nickMaskString ?
851 837
 	rb.Add(nil, inviter.server.name, RPL_INVITING, invitee.nick, channel.name)
852 838
 	invitee.Send(nil, inviter.nickMaskString, "INVITE", invitee.nick, channel.name)
853
-	if invitee.flags[modes.Away] {
839
+	if invitee.HasMode(modes.Away) {
854 840
 		rb.Add(nil, inviter.server.name, RPL_AWAY, invitee.nick, invitee.awayMessage)
855 841
 	}
856 842
 }

+ 2
- 2
irc/chanserv.go Datei anzeigen

@@ -95,7 +95,7 @@ func csOpHandler(server *Server, client *Client, command, params string, rb *Res
95 95
 	if client == target {
96 96
 		givenMode = modes.ChannelFounder
97 97
 	}
98
-	change := channelInfo.applyModeMemberNoMutex(target, givenMode, modes.Add, client.NickCasefolded(), rb)
98
+	change := channelInfo.applyModeToMember(target, givenMode, modes.Add, client.NickCasefolded(), rb)
99 99
 	if change != nil {
100 100
 		//TODO(dan): we should change the name of String and make it return a slice here
101 101
 		//TODO(dan): unify this code with code in modes.go
@@ -151,7 +151,7 @@ func csRegisterHandler(server *Server, client *Client, command, params string, r
151 151
 	server.snomasks.Send(sno.LocalChannels, fmt.Sprintf(ircfmt.Unescape("Channel registered $c[grey][$r%s$c[grey]] by $c[grey][$r%s$c[grey]]"), channelName, client.nickMaskString))
152 152
 
153 153
 	// give them founder privs
154
-	change := channelInfo.applyModeMemberNoMutex(client, modes.ChannelFounder, modes.Add, client.NickCasefolded(), rb)
154
+	change := channelInfo.applyModeToMember(client, modes.ChannelFounder, modes.Add, client.NickCasefolded(), rb)
155 155
 	if change != nil {
156 156
 		//TODO(dan): we should change the name of String and make it return a slice here
157 157
 		//TODO(dan): unify this code with code in modes.go

+ 22
- 16
irc/client.go Datei anzeigen

@@ -49,7 +49,7 @@ type Client struct {
49 49
 	ctime              time.Time
50 50
 	exitedSnomaskSent  bool
51 51
 	fakelag            *Fakelag
52
-	flags              map[modes.Mode]bool
52
+	flags              *modes.ModeSet
53 53
 	hasQuit            bool
54 54
 	hops               int
55 55
 	hostname           string
@@ -88,7 +88,6 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) *Client {
88 88
 	limits := server.Limits()
89 89
 	fullLineLenLimit := limits.LineLen.Tags + limits.LineLen.Rest
90 90
 	socket := NewSocket(conn, fullLineLenLimit*2, server.MaxSendQBytes())
91
-	go socket.RunSocketWriter()
92 91
 	client := &Client{
93 92
 		atime:          now,
94 93
 		authorized:     server.Password() == nil,
@@ -97,9 +96,9 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) *Client {
97 96
 		capVersion:     caps.Cap301,
98 97
 		channels:       make(ChannelSet),
99 98
 		ctime:          now,
100
-		flags:          make(map[modes.Mode]bool),
99
+		flags:          modes.NewModeSet(),
101 100
 		server:         server,
102
-		socket:         &socket,
101
+		socket:         socket,
103 102
 		nick:           "*", // * is used until actual nick is given
104 103
 		nickCasefolded: "*",
105 104
 		nickMaskString: "*", // * is used until actual nick is given
@@ -108,7 +107,7 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) *Client {
108 107
 
109 108
 	client.recomputeMaxlens()
110 109
 	if isTLS {
111
-		client.flags[modes.TLS] = true
110
+		client.SetMode(modes.TLS, true)
112 111
 
113 112
 		// error is not useful to us here anyways so we can ignore it
114 113
 		client.certfp, _ = client.socket.CertFP()
@@ -500,13 +499,7 @@ func (client *Client) HasRoleCapabs(capabs ...string) bool {
500 499
 
501 500
 // ModeString returns the mode string for this client.
502 501
 func (client *Client) ModeString() (str string) {
503
-	str = "+"
504
-
505
-	for flag := range client.flags {
506
-		str += flag.String()
507
-	}
508
-
509
-	return
502
+	return "+" + client.flags.String()
510 503
 }
511 504
 
512 505
 // Friends refers to clients that share a channel with this client.
@@ -661,10 +654,14 @@ func (client *Client) LoggedIntoAccount() bool {
661 654
 // RplISupport outputs our ISUPPORT lines to the client. This is used on connection and in VERSION responses.
662 655
 func (client *Client) RplISupport(rb *ResponseBuffer) {
663 656
 	translatedISupport := client.t("are supported by this server")
664
-	for _, tokenline := range client.server.ISupport().CachedReply {
665
-		// ugly trickery ahead
666
-		tokenline = append(tokenline, translatedISupport)
667
-		rb.Add(nil, client.server.name, RPL_ISUPPORT, append([]string{client.nick}, tokenline...)...)
657
+	nick := client.Nick()
658
+	for _, cachedTokenLine := range client.server.ISupport().CachedReply {
659
+		length := len(cachedTokenLine) + 2
660
+		tokenline := make([]string, length)
661
+		tokenline[0] = nick
662
+		copy(tokenline[1:], cachedTokenLine)
663
+		tokenline[length-1] = translatedISupport
664
+		rb.Add(nil, client.server.name, RPL_ISUPPORT, tokenline...)
668 665
 	}
669 666
 }
670 667
 
@@ -758,6 +755,15 @@ func (client *Client) destroy(beingResumed bool) {
758 755
 
759 756
 	// send quit messages to friends
760 757
 	if !beingResumed {
758
+		client.server.stats.ChangeTotal(-1)
759
+		if client.HasMode(modes.Invisible) {
760
+			client.server.stats.ChangeInvisible(-1)
761
+		}
762
+
763
+		if client.HasMode(modes.Operator) || client.HasMode(modes.LocalOperator) {
764
+			client.server.stats.ChangeOperators(-1)
765
+		}
766
+
761 767
 		for friend := range friends {
762 768
 			if client.quitMessage == "" {
763 769
 				client.quitMessage = "Exited"

+ 10
- 3
irc/config.go Datei anzeigen

@@ -22,6 +22,7 @@ import (
22 22
 	"github.com/oragono/oragono/irc/custime"
23 23
 	"github.com/oragono/oragono/irc/languages"
24 24
 	"github.com/oragono/oragono/irc/logger"
25
+	"github.com/oragono/oragono/irc/modes"
25 26
 	"github.com/oragono/oragono/irc/passwd"
26 27
 	"github.com/oragono/oragono/irc/utils"
27 28
 	"gopkg.in/yaml.v2"
@@ -243,7 +244,8 @@ type Config struct {
243 244
 	}
244 245
 
245 246
 	Datastore struct {
246
-		Path string
247
+		Path        string
248
+		AutoUpgrade bool
247 249
 	}
248 250
 
249 251
 	Accounts AccountConfig
@@ -366,7 +368,7 @@ type Oper struct {
366 368
 	WhoisLine string
367 369
 	Vhost     string
368 370
 	Pass      []byte
369
-	Modes     string
371
+	Modes     []modes.ModeChange
370 372
 }
371 373
 
372 374
 // Operators returns a map of operator configs from the given OperClass and config.
@@ -394,7 +396,12 @@ func (conf *Config) Operators(oc map[string]*OperClass) (map[string]*Oper, error
394 396
 		} else {
395 397
 			oper.WhoisLine = class.WhoisLine
396 398
 		}
397
-		oper.Modes = strings.TrimSpace(opConf.Modes)
399
+		modeStr := strings.TrimSpace(opConf.Modes)
400
+		modeChanges, unknownChanges := modes.ParseUserModeChanges(strings.Split(modeStr, " ")...)
401
+		if len(unknownChanges) > 0 {
402
+			return nil, fmt.Errorf("Could not load operator [%s] due to unknown modes %v", name, unknownChanges)
403
+		}
404
+		oper.Modes = modeChanges
398 405
 
399 406
 		// successful, attach to list of opers
400 407
 		operators[name] = &oper

+ 90
- 24
irc/database.go Datei anzeigen

@@ -11,9 +11,11 @@ import (
11 11
 	"log"
12 12
 	"os"
13 13
 	"strings"
14
+	"time"
14 15
 
15 16
 	"github.com/oragono/oragono/irc/modes"
16 17
 	"github.com/oragono/oragono/irc/passwd"
18
+	"github.com/oragono/oragono/irc/utils"
17 19
 
18 20
 	"github.com/tidwall/buntdb"
19 21
 )
@@ -38,6 +40,22 @@ type SchemaChange struct {
38 40
 // maps an initial version to a schema change capable of upgrading it
39 41
 var schemaChanges map[string]SchemaChange
40 42
 
43
+type incompatibleSchemaError struct {
44
+	currentVersion  string
45
+	requiredVersion string
46
+}
47
+
48
+func IncompatibleSchemaError(currentVersion string) (result *incompatibleSchemaError) {
49
+	return &incompatibleSchemaError{
50
+		currentVersion:  currentVersion,
51
+		requiredVersion: latestDbSchema,
52
+	}
53
+}
54
+
55
+func (err *incompatibleSchemaError) Error() string {
56
+	return fmt.Sprintf("Database requires update. Expected schema v%s, got v%s", err.requiredVersion, err.currentVersion)
57
+}
58
+
41 59
 // InitDB creates the database.
42 60
 func InitDB(path string) {
43 61
 	// prepare kvstore db
@@ -69,36 +87,80 @@ func InitDB(path string) {
69 87
 }
70 88
 
71 89
 // OpenDatabase returns an existing database, performing a schema version check.
72
-func OpenDatabase(path string) (*buntdb.DB, error) {
73
-	// open data store
74
-	db, err := buntdb.Open(path)
90
+func OpenDatabase(config *Config) (*buntdb.DB, error) {
91
+	return openDatabaseInternal(config, config.Datastore.AutoUpgrade)
92
+}
93
+
94
+// open the database, giving it at most one chance to auto-upgrade the schema
95
+func openDatabaseInternal(config *Config, allowAutoupgrade bool) (db *buntdb.DB, err error) {
96
+	db, err = buntdb.Open(config.Datastore.Path)
75 97
 	if err != nil {
76
-		return nil, err
98
+		return
77 99
 	}
78 100
 
79
-	// check db version
80
-	err = db.View(func(tx *buntdb.Tx) error {
81
-		version, _ := tx.Get(keySchemaVersion)
82
-		if version != latestDbSchema {
83
-			return fmt.Errorf("Database must be updated. Expected schema v%s, got v%s", latestDbSchema, version)
101
+	defer func() {
102
+		if err != nil && db != nil {
103
+			db.Close()
104
+			db = nil
84 105
 		}
85
-		return nil
106
+	}()
107
+
108
+	// read the current version string
109
+	var version string
110
+	err = db.View(func(tx *buntdb.Tx) error {
111
+		version, err = tx.Get(keySchemaVersion)
112
+		return err
86 113
 	})
114
+	if err != nil {
115
+		return
116
+	}
117
+
118
+	if version == latestDbSchema {
119
+		// success
120
+		return
121
+	}
122
+
123
+	// XXX quiesce the DB so we can be sure it's safe to make a backup copy
124
+	db.Close()
125
+	db = nil
126
+	if allowAutoupgrade {
127
+		err = performAutoUpgrade(version, config)
128
+		if err != nil {
129
+			return
130
+		}
131
+		// successful autoupgrade, let's try this again:
132
+		return openDatabaseInternal(config, false)
133
+	} else {
134
+		err = IncompatibleSchemaError(version)
135
+		return
136
+	}
137
+}
87 138
 
139
+func performAutoUpgrade(currentVersion string, config *Config) (err error) {
140
+	path := config.Datastore.Path
141
+	log.Printf("attempting to auto-upgrade schema from version %s to %s\n", currentVersion, latestDbSchema)
142
+	timestamp := time.Now().UTC().Format("2006-01-02-15:04:05.000Z")
143
+	backupPath := fmt.Sprintf("%s.v%s.%s.bak", path, currentVersion, timestamp)
144
+	log.Printf("making a backup of current database at %s\n", backupPath)
145
+	err = utils.CopyFile(path, backupPath)
88 146
 	if err != nil {
89
-		// close the db
90
-		db.Close()
91
-		return nil, err
147
+		return err
92 148
 	}
93 149
 
94
-	return db, nil
150
+	err = UpgradeDB(config)
151
+	if err != nil {
152
+		// database upgrade is a single transaction, so we don't need to restore the backup;
153
+		// we can just delete it
154
+		os.Remove(backupPath)
155
+	}
156
+	return err
95 157
 }
96 158
 
97 159
 // UpgradeDB upgrades the datastore to the latest schema.
98
-func UpgradeDB(config *Config) {
160
+func UpgradeDB(config *Config) (err error) {
99 161
 	store, err := buntdb.Open(config.Datastore.Path)
100 162
 	if err != nil {
101
-		log.Fatal(fmt.Sprintf("Failed to open datastore: %s", err.Error()))
163
+		return err
102 164
 	}
103 165
 	defer store.Close()
104 166
 
@@ -108,9 +170,14 @@ func UpgradeDB(config *Config) {
108 170
 			version, _ = tx.Get(keySchemaVersion)
109 171
 			change, schemaNeedsChange := schemaChanges[version]
110 172
 			if !schemaNeedsChange {
111
-				break
173
+				if version == latestDbSchema {
174
+					// success!
175
+					break
176
+				}
177
+				// unable to upgrade to the desired version, roll back
178
+				return IncompatibleSchemaError(version)
112 179
 			}
113
-			log.Println("attempting to update store from version " + version)
180
+			log.Println("attempting to update schema from version " + version)
114 181
 			err := change.Changer(config, tx)
115 182
 			if err != nil {
116 183
 				return err
@@ -119,16 +186,15 @@ func UpgradeDB(config *Config) {
119 186
 			if err != nil {
120 187
 				return err
121 188
 			}
122
-			log.Println("successfully updated store to version " + change.TargetVersion)
189
+			log.Println("successfully updated schema to version " + change.TargetVersion)
123 190
 		}
124 191
 		return nil
125 192
 	})
126 193
 
127 194
 	if err != nil {
128
-		log.Fatal("Could not update datastore:", err.Error())
195
+		log.Println("database upgrade failed and was rolled back")
129 196
 	}
130
-
131
-	return
197
+	return err
132 198
 }
133 199
 
134 200
 func schemaChangeV1toV2(config *Config, tx *buntdb.Tx) error {
@@ -216,12 +282,12 @@ func schemaChangeV2ToV3(config *Config, tx *buntdb.Tx) error {
216 282
 
217 283
 func init() {
218 284
 	allChanges := []SchemaChange{
219
-		SchemaChange{
285
+		{
220 286
 			InitialVersion: "1",
221 287
 			TargetVersion:  "2",
222 288
 			Changer:        schemaChangeV1toV2,
223 289
 		},
224
-		SchemaChange{
290
+		{
225 291
 			InitialVersion: "2",
226 292
 			TargetVersion:  "3",
227 293
 			Changer:        schemaChangeV2ToV3,

+ 1
- 5
irc/gateways.go Datei anzeigen

@@ -84,11 +84,7 @@ func (client *Client) ApplyProxiedIP(proxiedIP string, tls bool) (exiting bool)
84 84
 
85 85
 	// set tls info
86 86
 	client.certfp = ""
87
-	if tls {
88
-		client.flags[modes.TLS] = true
89
-	} else {
90
-		delete(client.flags, modes.TLS)
91
-	}
87
+	client.SetMode(modes.TLS, tls)
92 88
 
93 89
 	return false
94 90
 }

+ 6
- 24
irc/getters.go Datei anzeigen

@@ -210,9 +210,12 @@ func (client *Client) SetPreregNick(preregNick string) {
210 210
 }
211 211
 
212 212
 func (client *Client) HasMode(mode modes.Mode) bool {
213
-	client.stateMutex.RLock()
214
-	defer client.stateMutex.RUnlock()
215
-	return client.flags[mode]
213
+	// client.flags has its own synch
214
+	return client.flags.HasMode(mode)
215
+}
216
+
217
+func (client *Client) SetMode(mode modes.Mode, on bool) bool {
218
+	return client.flags.SetMode(mode, on)
216 219
 }
217 220
 
218 221
 func (client *Client) Channels() (result []*Channel) {
@@ -282,29 +285,8 @@ func (channel *Channel) setKey(key string) {
282 285
 	channel.key = key
283 286
 }
284 287
 
285
-func (channel *Channel) HasMode(mode modes.Mode) bool {
286
-	channel.stateMutex.RLock()
287
-	defer channel.stateMutex.RUnlock()
288
-	return channel.flags[mode]
289
-}
290
-
291 288
 func (channel *Channel) Founder() string {
292 289
 	channel.stateMutex.RLock()
293 290
 	defer channel.stateMutex.RUnlock()
294 291
 	return channel.registeredFounder
295 292
 }
296
-
297
-// set a channel mode, return whether it was already set
298
-func (channel *Channel) setMode(mode modes.Mode, enable bool) (already bool) {
299
-	channel.stateMutex.Lock()
300
-	already = (channel.flags[mode] == enable)
301
-	if !already {
302
-		if enable {
303
-			channel.flags[mode] = true
304
-		} else {
305
-			delete(channel.flags, mode)
306
-		}
307
-	}
308
-	channel.stateMutex.Unlock()
309
-	return
310
-}

+ 34
- 56
irc/handlers.go Datei anzeigen

@@ -406,15 +406,11 @@ func awayHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
406 406
 		}
407 407
 	}
408 408
 
409
-	if isAway {
410
-		client.flags[modes.Away] = true
411
-	} else {
412
-		delete(client.flags, modes.Away)
413
-	}
409
+	client.SetMode(modes.Away, isAway)
414 410
 	client.awayMessage = text
415 411
 
416 412
 	var op modes.ModeOp
417
-	if client.flags[modes.Away] {
413
+	if isAway {
418 414
 		op = modes.Add
419 415
 		rb.Add(nil, server.name, RPL_NOWAWAY, client.nick, client.t("You have been marked as being away"))
420 416
 	} else {
@@ -430,7 +426,7 @@ func awayHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
430 426
 
431 427
 	// dispatch away-notify
432 428
 	for friend := range client.Friends(caps.AwayNotify) {
433
-		if client.flags[modes.Away] {
429
+		if isAway {
434 430
 			friend.SendFromClient("", client, nil, "AWAY", client.awayMessage)
435 431
 		} else {
436 432
 			friend.SendFromClient("", client, nil, "AWAY")
@@ -773,7 +769,7 @@ Get an explanation of <argument>, or "index" for a list of help topics.`), rb)
773 769
 
774 770
 	// handle index
775 771
 	if argument == "index" {
776
-		if client.flags[modes.Operator] {
772
+		if client.HasMode(modes.Operator) {
777 773
 			client.sendHelp("HELP", GetHelpIndex(client.languages, HelpIndexOpers), rb)
778 774
 		} else {
779 775
 			client.sendHelp("HELP", GetHelpIndex(client.languages, HelpIndex), rb)
@@ -783,7 +779,7 @@ Get an explanation of <argument>, or "index" for a list of help topics.`), rb)
783 779
 
784 780
 	helpHandler, exists := Help[argument]
785 781
 
786
-	if exists && (!helpHandler.oper || (helpHandler.oper && client.flags[modes.Operator])) {
782
+	if exists && (!helpHandler.oper || (helpHandler.oper && client.HasMode(modes.Operator))) {
787 783
 		if helpHandler.textGenerator != nil {
788 784
 			client.sendHelp(strings.ToUpper(argument), client.t(helpHandler.textGenerator(client)), rb)
789 785
 		} else {
@@ -1254,9 +1250,10 @@ func listHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
1254 1250
 		}
1255 1251
 	}
1256 1252
 
1253
+	clientIsOp := client.HasMode(modes.Operator)
1257 1254
 	if len(channels) == 0 {
1258 1255
 		for _, channel := range server.channels.Channels() {
1259
-			if !client.flags[modes.Operator] && channel.flags[modes.Secret] {
1256
+			if !clientIsOp && channel.flags.HasMode(modes.Secret) {
1260 1257
 				continue
1261 1258
 			}
1262 1259
 			if matcher.Matches(channel) {
@@ -1265,14 +1262,14 @@ func listHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
1265 1262
 		}
1266 1263
 	} else {
1267 1264
 		// limit regular users to only listing one channel
1268
-		if !client.flags[modes.Operator] {
1265
+		if !clientIsOp {
1269 1266
 			channels = channels[:1]
1270 1267
 		}
1271 1268
 
1272 1269
 		for _, chname := range channels {
1273 1270
 			casefoldedChname, err := CasefoldChannel(chname)
1274 1271
 			channel := server.channels.Get(casefoldedChname)
1275
-			if err != nil || channel == nil || (!client.flags[modes.Operator] && channel.flags[modes.Secret]) {
1272
+			if err != nil || channel == nil || (!clientIsOp && channel.flags.HasMode(modes.Secret)) {
1276 1273
 				if len(chname) > 0 {
1277 1274
 					rb.Add(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, chname, client.t("No such channel"))
1278 1275
 				}
@@ -1290,21 +1287,13 @@ func listHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
1290 1287
 // LUSERS [<mask> [<server>]]
1291 1288
 func lusersHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
1292 1289
 	//TODO(vegax87) Fix network statistics and additional parameters
1293
-	var totalcount, invisiblecount, opercount int
1290
+	totalCount, invisibleCount, operCount := server.stats.GetStats()
1291
+
1292
+	rb.Add(nil, server.name, RPL_LUSERCLIENT, client.nick, fmt.Sprintf(client.t("There are %[1]d users and %[2]d invisible on %[3]d server(s)"), totalCount-invisibleCount, invisibleCount, 1))
1293
+	rb.Add(nil, server.name, RPL_LUSEROP, client.nick, strconv.Itoa(operCount), client.t("IRC Operators online"))
1294
+	rb.Add(nil, server.name, RPL_LUSERCHANNELS, client.nick, strconv.Itoa(server.channels.Len()), client.t("channels formed"))
1295
+	rb.Add(nil, server.name, RPL_LUSERME, client.nick, fmt.Sprintf(client.t("I have %[1]d clients and %[2]d servers"), totalCount, 1))
1294 1296
 
1295
-	for _, onlineusers := range server.clients.AllClients() {
1296
-		totalcount++
1297
-		if onlineusers.flags[modes.Invisible] {
1298
-			invisiblecount++
1299
-		}
1300
-		if onlineusers.flags[modes.Operator] {
1301
-			opercount++
1302
-		}
1303
-	}
1304
-	rb.Add(nil, server.name, RPL_LUSERCLIENT, client.nick, fmt.Sprintf(client.t("There are %[1]d users and %[2]d invisible on %[3]d server(s)"), totalcount, invisiblecount, 1))
1305
-	rb.Add(nil, server.name, RPL_LUSEROP, client.nick, fmt.Sprintf(client.t("%d IRC Operators online"), opercount))
1306
-	rb.Add(nil, server.name, RPL_LUSERCHANNELS, client.nick, fmt.Sprintf(client.t("%d channels formed"), server.channels.Len()))
1307
-	rb.Add(nil, server.name, RPL_LUSERME, client.nick, fmt.Sprintf(client.t("I have %[1]d clients and %[2]d servers"), totalcount, 1))
1308 1297
 	return false
1309 1298
 }
1310 1299
 
@@ -1334,7 +1323,7 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
1334 1323
 	if 1 < len(msg.Params) {
1335 1324
 		// parse out real mode changes
1336 1325
 		params := msg.Params[1:]
1337
-		changes, unknown := ParseChannelModeChanges(params...)
1326
+		changes, unknown := modes.ParseChannelModeChanges(params...)
1338 1327
 
1339 1328
 		// alert for unknown mode changes
1340 1329
 		for char := range unknown {
@@ -1420,14 +1409,14 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
1420 1409
 		}
1421 1410
 
1422 1411
 		// apply mode changes
1423
-		applied = target.applyUserModeChanges(msg.Command == "SAMODE", changes)
1412
+		applied = ApplyUserModeChanges(client, changes, msg.Command == "SAMODE")
1424 1413
 	}
1425 1414
 
1426 1415
 	if len(applied) > 0 {
1427 1416
 		rb.Add(nil, client.nickMaskString, "MODE", targetNick, applied.String())
1428 1417
 	} else if hasPrivs {
1429 1418
 		rb.Add(nil, target.nickMaskString, RPL_UMODEIS, targetNick, target.ModeString())
1430
-		if client.flags[modes.LocalOperator] || client.flags[modes.Operator] {
1419
+		if client.HasMode(modes.LocalOperator) || client.HasMode(modes.Operator) {
1431 1420
 			masks := server.snomasks.String(client)
1432 1421
 			if 0 < len(masks) {
1433 1422
 				rb.Add(nil, target.nickMaskString, RPL_SNOMASKIS, targetNick, masks, client.t("Server notice masks"))
@@ -1673,7 +1662,7 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
1673 1662
 			msgid := server.generateMessageID()
1674 1663
 			// restrict messages appropriately when +R is set
1675 1664
 			// intentionally make the sending user think the message went through fine
1676
-			if !user.flags[modes.RegisteredOnly] || client.registered {
1665
+			if !user.HasMode(modes.RegisteredOnly) || client.LoggedIntoAccount() {
1677 1666
 				user.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "NOTICE", user.nick, splitMsg)
1678 1667
 			}
1679 1668
 			if client.capabilities.Has(caps.EchoMessage) {
@@ -1723,7 +1712,7 @@ func npcaHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
1723 1712
 
1724 1713
 // OPER <name> <password>
1725 1714
 func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
1726
-	if client.flags[modes.Operator] == true {
1715
+	if client.HasMode(modes.Operator) == true {
1727 1716
 		rb.Add(nil, server.name, ERR_UNKNOWNERROR, "OPER", client.t("You're already opered-up!"))
1728 1717
 		return false
1729 1718
 	}
@@ -1746,26 +1735,16 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
1746 1735
 		client.sendChghost(oldNickmask, oper.Vhost)
1747 1736
 	}
1748 1737
 
1749
-	// set new modes
1750
-	var applied modes.ModeChanges
1751
-	if 0 < len(oper.Modes) {
1752
-		modeChanges, unknownChanges := modes.ParseUserModeChanges(strings.Split(oper.Modes, " ")...)
1753
-		applied = client.applyUserModeChanges(true, modeChanges)
1754
-		if 0 < len(unknownChanges) {
1755
-			var runes string
1756
-			for r := range unknownChanges {
1757
-				runes += string(r)
1758
-			}
1759
-			rb.Notice(fmt.Sprintf(client.t("Could not apply mode changes: +%s"), runes))
1760
-		}
1738
+	// set new modes: modes.Operator, plus anything specified in the config
1739
+	modeChanges := make([]modes.ModeChange, len(oper.Modes)+1)
1740
+	modeChanges[0] = modes.ModeChange{
1741
+		Mode: modes.Operator,
1742
+		Op:   modes.Add,
1761 1743
 	}
1744
+	copy(modeChanges[1:], oper.Modes)
1745
+	applied := ApplyUserModeChanges(client, modeChanges, true)
1762 1746
 
1763 1747
 	rb.Add(nil, server.name, RPL_YOUREOPER, client.nick, client.t("You are now an IRC operator"))
1764
-
1765
-	applied = append(applied, modes.ModeChange{
1766
-		Mode: modes.Operator,
1767
-		Op:   modes.Add,
1768
-	})
1769 1748
 	rb.Add(nil, server.name, "MODE", client.nick, applied.String())
1770 1749
 
1771 1750
 	server.snomasks.Send(sno.LocalOpers, fmt.Sprintf(ircfmt.Unescape("Client opered up $c[grey][$r%s$c[grey], $r%s$c[grey]]"), client.nickMaskString, oper.Name))
@@ -1773,7 +1752,6 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
1773 1752
 	// client may now be unthrottled by the fakelag system
1774 1753
 	client.resetFakelag()
1775 1754
 
1776
-	client.flags[modes.Operator] = true
1777 1755
 	return false
1778 1756
 }
1779 1757
 
@@ -1885,13 +1863,13 @@ func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R
1885 1863
 			msgid := server.generateMessageID()
1886 1864
 			// restrict messages appropriately when +R is set
1887 1865
 			// intentionally make the sending user think the message went through fine
1888
-			if !user.flags[modes.RegisteredOnly] || client.registered {
1866
+			if !user.HasMode(modes.RegisteredOnly) || client.LoggedIntoAccount() {
1889 1867
 				user.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "PRIVMSG", user.nick, splitMsg)
1890 1868
 			}
1891 1869
 			if client.capabilities.Has(caps.EchoMessage) {
1892 1870
 				rb.AddSplitMessageFromClient(msgid, client, clientOnlyTags, "PRIVMSG", user.nick, splitMsg)
1893 1871
 			}
1894
-			if user.flags[modes.Away] {
1872
+			if user.HasMode(modes.Away) {
1895 1873
 				//TODO(dan): possibly implement cooldown of away notifications to users
1896 1874
 				rb.Add(nil, server.name, RPL_AWAY, user.nick, user.awayMessage)
1897 1875
 			}
@@ -2139,7 +2117,7 @@ func tagmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
2139 2117
 			if client.capabilities.Has(caps.EchoMessage) {
2140 2118
 				rb.AddFromClient(msgid, client, clientOnlyTags, "TAGMSG", user.nick)
2141 2119
 			}
2142
-			if user.flags[modes.Away] {
2120
+			if user.HasMode(modes.Away) {
2143 2121
 				//TODO(dan): possibly implement cooldown of away notifications to users
2144 2122
 				rb.Add(nil, server.name, RPL_AWAY, user.nick, user.awayMessage)
2145 2123
 			}
@@ -2337,10 +2315,10 @@ func userhostHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *
2337 2315
 
2338 2316
 		var isOper, isAway string
2339 2317
 
2340
-		if target.flags[modes.Operator] {
2318
+		if target.HasMode(modes.Operator) {
2341 2319
 			isOper = "*"
2342 2320
 		}
2343
-		if target.flags[modes.Away] {
2321
+		if target.HasMode(modes.Away) {
2344 2322
 			isAway = "-"
2345 2323
 		} else {
2346 2324
 			isAway = "+"
@@ -2381,7 +2359,7 @@ func webircHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
2381 2359
 			lkey := strings.ToLower(key)
2382 2360
 			if lkey == "tls" || lkey == "secure" {
2383 2361
 				// only accept "tls" flag if the gateway's connection to us is secure as well
2384
-				if client.flags[modes.TLS] || utils.AddrIsLocal(client.socket.conn.RemoteAddr()) {
2362
+				if client.HasMode(modes.TLS) || utils.AddrIsLocal(client.socket.conn.RemoteAddr()) {
2385 2363
 					secure = true
2386 2364
 				}
2387 2365
 			}
@@ -2470,7 +2448,7 @@ func whoisHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
2470 2448
 		return false
2471 2449
 	}
2472 2450
 
2473
-	if client.flags[modes.Operator] {
2451
+	if client.HasMode(modes.Operator) {
2474 2452
 		masks := strings.Split(masksString, ",")
2475 2453
 		for _, mask := range masks {
2476 2454
 			casefoldedMask, err := Casefold(mask)

+ 33
- 97
irc/modes.go Datei anzeigen

@@ -21,8 +21,8 @@ var (
21 21
 	}
22 22
 )
23 23
 
24
-// applyUserModeChanges applies the given changes, and returns the applied changes.
25
-func (client *Client) applyUserModeChanges(force bool, changes modes.ModeChanges) modes.ModeChanges {
24
+// ApplyUserModeChanges applies the given changes, and returns the applied changes.
25
+func ApplyUserModeChanges(client *Client, changes modes.ModeChanges, force bool) modes.ModeChanges {
26 26
 	applied := make(modes.ModeChanges, 0)
27 27
 
28 28
 	for _, change := range changes {
@@ -34,22 +34,28 @@ func (client *Client) applyUserModeChanges(force bool, changes modes.ModeChanges
34 34
 					continue
35 35
 				}
36 36
 
37
-				if client.flags[change.Mode] {
38
-					continue
37
+				if client.SetMode(change.Mode, true) {
38
+					if change.Mode == modes.Invisible {
39
+						client.server.stats.ChangeInvisible(1)
40
+					} else if change.Mode == modes.Operator || change.Mode == modes.LocalOperator {
41
+						client.server.stats.ChangeOperators(1)
42
+					}
43
+					applied = append(applied, change)
39 44
 				}
40
-				client.flags[change.Mode] = true
41
-				applied = append(applied, change)
42 45
 
43 46
 			case modes.Remove:
44
-				if !client.flags[change.Mode] {
45
-					continue
47
+				if client.SetMode(change.Mode, false) {
48
+					if change.Mode == modes.Invisible {
49
+						client.server.stats.ChangeInvisible(-1)
50
+					} else if change.Mode == modes.Operator || change.Mode == modes.LocalOperator {
51
+						client.server.stats.ChangeOperators(-1)
52
+					}
53
+					applied = append(applied, change)
46 54
 				}
47
-				delete(client.flags, change.Mode)
48
-				applied = append(applied, change)
49 55
 			}
50 56
 
51 57
 		case modes.ServerNotice:
52
-			if !client.flags[modes.Operator] {
58
+			if !client.HasMode(modes.Operator) {
53 59
 				continue
54 60
 			}
55 61
 			var masks []sno.Mask
@@ -87,7 +93,7 @@ func ParseDefaultChannelModes(config *Config) modes.Modes {
87 93
 		return DefaultChannelModes
88 94
 	}
89 95
 	modeChangeStrings := strings.Split(strings.TrimSpace(*config.Channels.DefaultModes), " ")
90
-	modeChanges, _ := ParseChannelModeChanges(modeChangeStrings...)
96
+	modeChanges, _ := modes.ParseChannelModeChanges(modeChangeStrings...)
91 97
 	defaultChannelModes := make(modes.Modes, 0)
92 98
 	for _, modeChange := range modeChanges {
93 99
 		if modeChange.Op == modes.Add {
@@ -97,83 +103,6 @@ func ParseDefaultChannelModes(config *Config) modes.Modes {
97 103
 	return defaultChannelModes
98 104
 }
99 105
 
100
-// ParseChannelModeChanges returns the valid changes, and the list of unknown chars.
101
-func ParseChannelModeChanges(params ...string) (modes.ModeChanges, map[rune]bool) {
102
-	changes := make(modes.ModeChanges, 0)
103
-	unknown := make(map[rune]bool)
104
-
105
-	op := modes.List
106
-
107
-	if 0 < len(params) {
108
-		modeArg := params[0]
109
-		skipArgs := 1
110
-
111
-		for _, mode := range modeArg {
112
-			if mode == '-' || mode == '+' {
113
-				op = modes.ModeOp(mode)
114
-				continue
115
-			}
116
-			change := modes.ModeChange{
117
-				Mode: modes.Mode(mode),
118
-				Op:   op,
119
-			}
120
-
121
-			// put arg into modechange if needed
122
-			switch modes.Mode(mode) {
123
-			case modes.BanMask, modes.ExceptMask, modes.InviteMask:
124
-				if len(params) > skipArgs {
125
-					change.Arg = params[skipArgs]
126
-					skipArgs++
127
-				} else {
128
-					change.Op = modes.List
129
-				}
130
-			case modes.ChannelFounder, modes.ChannelAdmin, modes.ChannelOperator, modes.Halfop, modes.Voice:
131
-				if len(params) > skipArgs {
132
-					change.Arg = params[skipArgs]
133
-					skipArgs++
134
-				} else {
135
-					continue
136
-				}
137
-			case modes.Key, modes.UserLimit:
138
-				// don't require value when removing
139
-				if change.Op == modes.Add {
140
-					if len(params) > skipArgs {
141
-						change.Arg = params[skipArgs]
142
-						skipArgs++
143
-					} else {
144
-						continue
145
-					}
146
-				}
147
-			}
148
-
149
-			var isKnown bool
150
-			for _, supportedMode := range modes.SupportedChannelModes {
151
-				if rune(supportedMode) == mode {
152
-					isKnown = true
153
-					break
154
-				}
155
-			}
156
-			for _, supportedMode := range modes.ChannelPrivModes {
157
-				if rune(supportedMode) == mode {
158
-					isKnown = true
159
-					break
160
-				}
161
-			}
162
-			if mode == rune(modes.Voice) {
163
-				isKnown = true
164
-			}
165
-			if !isKnown {
166
-				unknown[mode] = true
167
-				continue
168
-			}
169
-
170
-			changes = append(changes, change)
171
-		}
172
-	}
173
-
174
-	return changes, unknown
175
-}
176
-
177 106
 // ApplyChannelModeChanges applies a given set of mode changes.
178 107
 func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, changes modes.ModeChanges, rb *ResponseBuffer) modes.ModeChanges {
179 108
 	// so we only output one warning for each list type when full
@@ -194,15 +123,17 @@ func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, c
194 123
 		}
195 124
 		switch change.Mode {
196 125
 		case modes.ChannelFounder, modes.ChannelAdmin, modes.ChannelOperator, modes.Halfop, modes.Voice:
197
-			// Admins can't give other people Admin or remove it from others
198
-			if change.Mode == modes.ChannelAdmin {
199
-				return false
200
-			}
126
+			// List on these modes is a no-op anyway
201 127
 			if change.Op == modes.List {
202 128
 				return true
203 129
 			}
204 130
 			cfarg, _ := CasefoldName(change.Arg)
205
-			if change.Op == modes.Remove && cfarg == client.nickCasefolded {
131
+			isSelfChange := cfarg == client.NickCasefolded()
132
+			// Admins can't give other people Admin or remove it from others
133
+			if change.Mode == modes.ChannelAdmin && !isSelfChange {
134
+				return false
135
+			}
136
+			if change.Op == modes.Remove && isSelfChange {
206 137
 				// "There is no restriction, however, on anyone `deopping' themselves"
207 138
 				// <https://tools.ietf.org/html/rfc2812#section-3.1.5>
208 139
 				return true
@@ -285,8 +216,7 @@ func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, c
285 216
 				continue
286 217
 			}
287 218
 
288
-			already := channel.setMode(change.Mode, change.Op == modes.Add)
289
-			if !already {
219
+			if channel.flags.SetMode(change.Mode, change.Op == modes.Add) {
290 220
 				applied = append(applied, change)
291 221
 			}
292 222
 
@@ -295,7 +225,13 @@ func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, c
295 225
 				continue
296 226
 			}
297 227
 
298
-			change := channel.applyModeMemberNoMutex(client, change.Mode, change.Op, change.Arg, rb)
228
+			nick := change.Arg
229
+			if nick == "" {
230
+				rb.Add(nil, client.server.name, ERR_NEEDMOREPARAMS, "MODE", client.t("Not enough parameters"))
231
+				return nil
232
+			}
233
+
234
+			change := channel.applyModeToMember(client, change.Mode, change.Op, nick, rb)
299 235
 			if change != nil {
300 236
 				applied = append(applied, *change)
301 237
 			}

+ 136
- 13
irc/modes/modes.go Datei anzeigen

@@ -7,6 +7,7 @@ package modes
7 7
 
8 8
 import (
9 9
 	"strings"
10
+	"sync"
10 11
 )
11 12
 
12 13
 var (
@@ -247,34 +248,156 @@ func ParseUserModeChanges(params ...string) (ModeChanges, map[rune]bool) {
247 248
 	return changes, unknown
248 249
 }
249 250
 
251
+// ParseChannelModeChanges returns the valid changes, and the list of unknown chars.
252
+func ParseChannelModeChanges(params ...string) (ModeChanges, map[rune]bool) {
253
+	changes := make(ModeChanges, 0)
254
+	unknown := make(map[rune]bool)
255
+
256
+	op := List
257
+
258
+	if 0 < len(params) {
259
+		modeArg := params[0]
260
+		skipArgs := 1
261
+
262
+		for _, mode := range modeArg {
263
+			if mode == '-' || mode == '+' {
264
+				op = ModeOp(mode)
265
+				continue
266
+			}
267
+			change := ModeChange{
268
+				Mode: Mode(mode),
269
+				Op:   op,
270
+			}
271
+
272
+			// put arg into modechange if needed
273
+			switch Mode(mode) {
274
+			case BanMask, ExceptMask, InviteMask:
275
+				if len(params) > skipArgs {
276
+					change.Arg = params[skipArgs]
277
+					skipArgs++
278
+				} else {
279
+					change.Op = List
280
+				}
281
+			case ChannelFounder, ChannelAdmin, ChannelOperator, Halfop, Voice:
282
+				if len(params) > skipArgs {
283
+					change.Arg = params[skipArgs]
284
+					skipArgs++
285
+				} else {
286
+					continue
287
+				}
288
+			case Key, UserLimit:
289
+				// don't require value when removing
290
+				if change.Op == Add {
291
+					if len(params) > skipArgs {
292
+						change.Arg = params[skipArgs]
293
+						skipArgs++
294
+					} else {
295
+						continue
296
+					}
297
+				}
298
+			}
299
+
300
+			var isKnown bool
301
+			for _, supportedMode := range SupportedChannelModes {
302
+				if rune(supportedMode) == mode {
303
+					isKnown = true
304
+					break
305
+				}
306
+			}
307
+			for _, supportedMode := range ChannelPrivModes {
308
+				if rune(supportedMode) == mode {
309
+					isKnown = true
310
+					break
311
+				}
312
+			}
313
+			if mode == rune(Voice) {
314
+				isKnown = true
315
+			}
316
+			if !isKnown {
317
+				unknown[mode] = true
318
+				continue
319
+			}
320
+
321
+			changes = append(changes, change)
322
+		}
323
+	}
324
+
325
+	return changes, unknown
326
+}
327
+
250 328
 // ModeSet holds a set of modes.
251
-type ModeSet map[Mode]bool
329
+type ModeSet struct {
330
+	sync.RWMutex // tier 0
331
+	modes        map[Mode]bool
332
+}
333
+
334
+// returns a pointer to a new ModeSet
335
+func NewModeSet() *ModeSet {
336
+	return &ModeSet{
337
+		modes: make(map[Mode]bool),
338
+	}
339
+}
340
+
341
+// test whether `mode` is set
342
+func (set *ModeSet) HasMode(mode Mode) bool {
343
+	set.RLock()
344
+	defer set.RUnlock()
345
+	return set.modes[mode]
346
+}
347
+
348
+// set `mode` to be on or off, return whether the value actually changed
349
+func (set *ModeSet) SetMode(mode Mode, on bool) (applied bool) {
350
+	set.Lock()
351
+	defer set.Unlock()
352
+
353
+	previouslyOn := set.modes[mode]
354
+	needsApply := (on != previouslyOn)
355
+	if on && needsApply {
356
+		set.modes[mode] = true
357
+	} else if !on && needsApply {
358
+		delete(set.modes, mode)
359
+	}
360
+	return needsApply
361
+}
362
+
363
+// return the modes in the set as a slice
364
+func (set *ModeSet) AllModes() (result []Mode) {
365
+	set.RLock()
366
+	defer set.RUnlock()
367
+
368
+	for mode := range set.modes {
369
+		result = append(result, mode)
370
+	}
371
+	return
372
+}
252 373
 
253 374
 // String returns the modes in this set.
254
-func (set ModeSet) String() string {
255
-	if len(set) == 0 {
375
+func (set *ModeSet) String() string {
376
+	set.RLock()
377
+	defer set.RUnlock()
378
+
379
+	if len(set.modes) == 0 {
256 380
 		return ""
257 381
 	}
258
-	strs := make([]string, len(set))
259
-	index := 0
260
-	for mode := range set {
261
-		strs[index] = mode.String()
262
-		index++
382
+	var result []byte
383
+	for mode := range set.modes {
384
+		result = append(result, mode.String()...)
263 385
 	}
264
-	return strings.Join(strs, "")
386
+	return string(result)
265 387
 }
266 388
 
267 389
 // Prefixes returns a list of prefixes for the given set of channel modes.
268
-func (set ModeSet) Prefixes(isMultiPrefix bool) string {
269
-	var prefixes string
390
+func (set *ModeSet) Prefixes(isMultiPrefix bool) (prefixes string) {
391
+	set.RLock()
392
+	defer set.RUnlock()
270 393
 
271 394
 	// add prefixes in order from highest to lowest privs
272 395
 	for _, mode := range ChannelPrivModes {
273
-		if set[mode] {
396
+		if set.modes[mode] {
274 397
 			prefixes += ChannelModePrefixes[mode]
275 398
 		}
276 399
 	}
277
-	if set[Voice] {
400
+	if set.modes[Voice] {
278 401
 		prefixes += ChannelModePrefixes[Voice]
279 402
 	}
280 403
 

+ 37
- 0
irc/modes/modes_test.go Datei anzeigen

@@ -0,0 +1,37 @@
1
+// Copyright (c) 2018 Shivaram Lingamneni
2
+// released under the MIT license
3
+
4
+package modes
5
+
6
+import (
7
+	"reflect"
8
+	"testing"
9
+)
10
+
11
+func TestSetMode(t *testing.T) {
12
+	set := NewModeSet()
13
+
14
+	if applied := set.SetMode(Invisible, false); applied != false {
15
+		t.Errorf("all modes should be false by default")
16
+	}
17
+
18
+	if applied := set.SetMode(Invisible, true); applied != true {
19
+		t.Errorf("initial SetMode call should return true")
20
+	}
21
+
22
+	set.SetMode(Operator, true)
23
+
24
+	if applied := set.SetMode(Invisible, true); applied != false {
25
+		t.Errorf("redundant SetMode call should return false")
26
+	}
27
+
28
+	expected1 := []Mode{Invisible, Operator}
29
+	expected2 := []Mode{Operator, Invisible}
30
+	if allModes := set.AllModes(); !(reflect.DeepEqual(allModes, expected1) || reflect.DeepEqual(allModes, expected2)) {
31
+		t.Errorf("unexpected AllModes value: %v", allModes)
32
+	}
33
+
34
+	if modeString := set.String(); !(modeString == "io" || modeString == "oi") {
35
+		t.Errorf("unexpected modestring: %s", modeString)
36
+	}
37
+}

+ 3
- 3
irc/roleplay.go Datei anzeigen

@@ -35,7 +35,7 @@ func sendRoleplayMessage(server *Server, client *Client, source string, targetSt
35 35
 			return
36 36
 		}
37 37
 
38
-		if !channel.flags[modes.ChanRoleplaying] {
38
+		if !channel.flags.HasMode(modes.ChanRoleplaying) {
39 39
 			rb.Add(nil, client.server.name, ERR_CANNOTSENDRP, channel.name, client.t("Channel doesn't have roleplaying mode available"))
40 40
 			return
41 41
 		}
@@ -58,7 +58,7 @@ func sendRoleplayMessage(server *Server, client *Client, source string, targetSt
58 58
 			return
59 59
 		}
60 60
 
61
-		if !user.flags[modes.UserRoleplaying] {
61
+		if !user.HasMode(modes.UserRoleplaying) {
62 62
 			rb.Add(nil, client.server.name, ERR_CANNOTSENDRP, user.nick, client.t("User doesn't have roleplaying mode enabled"))
63 63
 			return
64 64
 		}
@@ -67,7 +67,7 @@ func sendRoleplayMessage(server *Server, client *Client, source string, targetSt
67 67
 		if client.capabilities.Has(caps.EchoMessage) {
68 68
 			rb.Add(nil, source, "PRIVMSG", user.nick, message)
69 69
 		}
70
-		if user.flags[modes.Away] {
70
+		if user.HasMode(modes.Away) {
71 71
 			//TODO(dan): possibly implement cooldown of away notifications to users
72 72
 			rb.Add(nil, server.name, RPL_AWAY, user.nick, user.awayMessage)
73 73
 		}

+ 18
- 15
irc/server.go Datei anzeigen

@@ -127,10 +127,10 @@ type Server struct {
127 127
 	signals                    chan os.Signal
128 128
 	snomasks                   *SnoManager
129 129
 	store                      *buntdb.DB
130
-	storeFilename              string
131 130
 	stsEnabled                 bool
132 131
 	webirc                     []webircConfig
133 132
 	whoWas                     *WhoWasList
133
+	stats                      *Stats
134 134
 }
135 135
 
136 136
 var (
@@ -164,6 +164,7 @@ func NewServer(config *Config, logger *logger.Manager) (*Server, error) {
164 164
 		signals:             make(chan os.Signal, len(ServerExitSignals)),
165 165
 		snomasks:            NewSnoManager(),
166 166
 		whoWas:              NewWhoWasList(config.Limits.WhowasEntries),
167
+		stats:               NewStats(),
167 168
 	}
168 169
 
169 170
 	if err := server.applyConfig(config, true); err != nil {
@@ -472,6 +473,9 @@ func (server *Server) tryRegister(c *Client) {
472 473
 		return
473 474
 	}
474 475
 
476
+	// count new user in statistics
477
+	server.stats.ChangeTotal(1)
478
+
475 479
 	// continue registration
476 480
 	server.logger.Debug("localconnect", fmt.Sprintf("Client registered [%s] [u:%s] [r:%s]", c.nick, c.username, c.realname))
477 481
 	server.snomasks.Send(sno.LocalConnects, fmt.Sprintf(ircfmt.Unescape("Client registered $c[grey][$r%s$c[grey]] [u:$r%s$c[grey]] [h:$r%s$c[grey]] [r:$r%s$c[grey]]"), c.nick, c.username, c.rawHostname, c.realname))
@@ -633,8 +637,8 @@ func (client *Client) WhoisChannelsNames(target *Client) []string {
633 637
 	var chstrs []string
634 638
 	for _, channel := range target.Channels() {
635 639
 		// channel is secret and the target can't see it
636
-		if !client.flags[modes.Operator] {
637
-			if (target.HasMode(modes.Invisible) || channel.HasMode(modes.Secret)) && !channel.hasClient(client) {
640
+		if !client.HasMode(modes.Operator) {
641
+			if (target.HasMode(modes.Invisible) || channel.flags.HasMode(modes.Secret)) && !channel.hasClient(client) {
638 642
 				continue
639 643
 			}
640 644
 		}
@@ -657,16 +661,16 @@ func (client *Client) getWhoisOf(target *Client, rb *ResponseBuffer) {
657 661
 	if tOper != nil {
658 662
 		rb.Add(nil, client.server.name, RPL_WHOISOPERATOR, client.nick, target.nick, tOper.WhoisLine)
659 663
 	}
660
-	if client.flags[modes.Operator] || client == target {
664
+	if client.HasMode(modes.Operator) || client == target {
661 665
 		rb.Add(nil, client.server.name, RPL_WHOISACTUALLY, client.nick, target.nick, fmt.Sprintf("%s@%s", target.username, utils.LookupHostname(target.IPString())), target.IPString(), client.t("Actual user@host, Actual IP"))
662 666
 	}
663
-	if target.flags[modes.TLS] {
667
+	if target.HasMode(modes.TLS) {
664 668
 		rb.Add(nil, client.server.name, RPL_WHOISSECURE, client.nick, target.nick, client.t("is using a secure connection"))
665 669
 	}
666 670
 	if target.LoggedIntoAccount() {
667 671
 		rb.Add(nil, client.server.name, RPL_WHOISACCOUNT, client.nick, target.AccountName(), client.t("is logged in as"))
668 672
 	}
669
-	if target.flags[modes.Bot] {
673
+	if target.HasMode(modes.Bot) {
670 674
 		rb.Add(nil, client.server.name, RPL_WHOISBOT, client.nick, target.nick, ircfmt.Unescape(fmt.Sprintf(client.t("is a $bBot$b on %s"), client.server.networkName)))
671 675
 	}
672 676
 
@@ -679,7 +683,7 @@ func (client *Client) getWhoisOf(target *Client, rb *ResponseBuffer) {
679 683
 		rb.Add(nil, client.server.name, RPL_WHOISLANGUAGE, params...)
680 684
 	}
681 685
 
682
-	if target.certfp != "" && (client.flags[modes.Operator] || client == target) {
686
+	if target.certfp != "" && (client.HasMode(modes.Operator) || client == target) {
683 687
 		rb.Add(nil, client.server.name, RPL_WHOISCERTFP, client.nick, target.nick, fmt.Sprintf(client.t("has client certificate fingerprint %s"), target.certfp))
684 688
 	}
685 689
 	rb.Add(nil, client.server.name, RPL_WHOISIDLE, client.nick, target.nick, strconv.FormatUint(target.IdleSeconds(), 10), strconv.FormatInt(target.SignonTime(), 10), client.t("seconds idle, signon time"))
@@ -710,7 +714,7 @@ func (target *Client) rplWhoReply(channel *Channel, client *Client, rb *Response
710 714
 
711 715
 func whoChannel(client *Client, channel *Channel, friends ClientSet, rb *ResponseBuffer) {
712 716
 	for _, member := range channel.Members() {
713
-		if !client.flags[modes.Invisible] || friends[client] {
717
+		if !client.HasMode(modes.Invisible) || friends[client] {
714 718
 			client.rplWhoReply(channel, member, rb)
715 719
 		}
716 720
 	}
@@ -749,7 +753,7 @@ func (server *Server) applyConfig(config *Config, initial bool) error {
749 753
 			return fmt.Errorf("Maximum line length (linelen) cannot be changed after launching the server, rehash aborted")
750 754
 		} else if server.name != config.Server.Name {
751 755
 			return fmt.Errorf("Server name cannot be changed after launching the server, rehash aborted")
752
-		} else if server.storeFilename != config.Datastore.Path {
756
+		} else if server.config.Datastore.Path != config.Datastore.Path {
753 757
 			return fmt.Errorf("Datastore path cannot be changed after launching the server, rehash aborted")
754 758
 		}
755 759
 	}
@@ -975,10 +979,9 @@ func (server *Server) applyConfig(config *Config, initial bool) error {
975 979
 	server.config = config
976 980
 	server.configurableStateMutex.Unlock()
977 981
 
978
-	server.storeFilename = config.Datastore.Path
979
-	server.logger.Info("rehash", "Using datastore", server.storeFilename)
982
+	server.logger.Info("rehash", "Using datastore", config.Datastore.Path)
980 983
 	if initial {
981
-		if err := server.loadDatastore(server.storeFilename); err != nil {
984
+		if err := server.loadDatastore(config); err != nil {
982 985
 			return err
983 986
 		}
984 987
 	}
@@ -1075,11 +1078,11 @@ func (server *Server) loadMOTD(motdPath string, useFormatting bool) error {
1075 1078
 	return nil
1076 1079
 }
1077 1080
 
1078
-func (server *Server) loadDatastore(datastorePath string) error {
1081
+func (server *Server) loadDatastore(config *Config) error {
1079 1082
 	// open the datastore and load server state for which it (rather than config)
1080 1083
 	// is the source of truth
1081 1084
 
1082
-	db, err := OpenDatabase(datastorePath)
1085
+	db, err := OpenDatabase(config)
1083 1086
 	if err == nil {
1084 1087
 		server.store = db
1085 1088
 	} else {
@@ -1213,7 +1216,7 @@ func (matcher *elistMatcher) Matches(channel *Channel) bool {
1213 1216
 func (target *Client) RplList(channel *Channel, rb *ResponseBuffer) {
1214 1217
 	// get the correct number of channel members
1215 1218
 	var memberCount int
1216
-	if target.flags[modes.Operator] || channel.hasClient(target) {
1219
+	if target.HasMode(modes.Operator) || channel.hasClient(target) {
1217 1220
 		memberCount = len(channel.Members())
1218 1221
 	} else {
1219 1222
 		for _, member := range channel.Members() {

+ 69
- 32
irc/socket.go Datei anzeigen

@@ -31,23 +31,26 @@ type Socket struct {
31 31
 
32 32
 	maxSendQBytes int
33 33
 
34
-	// coordination system for asynchronous writes
35
-	buffer           []byte
36
-	lineToSendExists chan bool
34
+	// this is a trylock enforcing that only one goroutine can write to `conn` at a time
35
+	writerSlotOpen chan bool
37 36
 
37
+	buffer        []byte
38 38
 	closed        bool
39 39
 	sendQExceeded bool
40 40
 	finalData     string // what to send when we die
41
+	finalized     bool
41 42
 }
42 43
 
43 44
 // NewSocket returns a new Socket.
44
-func NewSocket(conn net.Conn, maxReadQBytes int, maxSendQBytes int) Socket {
45
-	return Socket{
46
-		conn:             conn,
47
-		reader:           bufio.NewReaderSize(conn, maxReadQBytes),
48
-		maxSendQBytes:    maxSendQBytes,
49
-		lineToSendExists: make(chan bool, 1),
45
+func NewSocket(conn net.Conn, maxReadQBytes int, maxSendQBytes int) *Socket {
46
+	result := Socket{
47
+		conn:           conn,
48
+		reader:         bufio.NewReaderSize(conn, maxReadQBytes),
49
+		maxSendQBytes:  maxSendQBytes,
50
+		writerSlotOpen: make(chan bool, 1),
50 51
 	}
52
+	result.writerSlotOpen <- true
53
+	return &result
51 54
 }
52 55
 
53 56
 // Close stops a Socket from being able to send/receive any more data.
@@ -114,7 +117,11 @@ func (socket *Socket) Read() (string, error) {
114 117
 	return line, nil
115 118
 }
116 119
 
117
-// Write sends the given string out of Socket.
120
+// Write sends the given string out of Socket. Requirements:
121
+// 1. MUST NOT block for macroscopic amounts of time
122
+// 2. MUST NOT reorder messages
123
+// 3. MUST provide mutual exclusion for socket.conn.Write
124
+// 4. SHOULD NOT tie up additional goroutines, beyond the one blocked on socket.conn.Write
118 125
 func (socket *Socket) Write(data string) (err error) {
119 126
 	socket.Lock()
120 127
 	if socket.closed {
@@ -131,12 +138,15 @@ func (socket *Socket) Write(data string) (err error) {
131 138
 	return
132 139
 }
133 140
 
134
-// wakeWriter wakes up the goroutine that actually performs the write, without blocking
141
+// wakeWriter starts the goroutine that actually performs the write, without blocking
135 142
 func (socket *Socket) wakeWriter() {
136
-	// nonblocking send to the channel, no-op if it's full
143
+	// attempt to acquire the trylock
137 144
 	select {
138
-	case socket.lineToSendExists <- true:
145
+	case <-socket.writerSlotOpen:
146
+		// acquired the trylock; send() will release it
147
+		go socket.send()
139 148
 	default:
149
+		// failed to acquire; the holder will check for more data after releasing it
140 150
 	}
141 151
 }
142 152
 
@@ -154,32 +164,59 @@ func (socket *Socket) IsClosed() bool {
154 164
 	return socket.closed
155 165
 }
156 166
 
157
-// RunSocketWriter starts writing messages to the outgoing socket.
158
-func (socket *Socket) RunSocketWriter() {
159
-	localBuffer := make([]byte, 0)
160
-	shouldStop := false
161
-	for !shouldStop {
162
-		// wait for new lines
167
+// is there data to write?
168
+func (socket *Socket) readyToWrite() bool {
169
+	socket.Lock()
170
+	defer socket.Unlock()
171
+	// on the first time observing socket.closed, we still have to write socket.finalData
172
+	return !socket.finalized && (len(socket.buffer) > 0 || socket.closed || socket.sendQExceeded)
173
+}
174
+
175
+// send actually writes messages to socket.Conn; it may block
176
+func (socket *Socket) send() {
177
+	for {
178
+		// we are holding the trylock: actually do the write
179
+		socket.performWrite()
180
+		// surrender the trylock, avoiding a race where a write comes in after we've
181
+		// checked readyToWrite() and it returned false, but while we still hold the trylock:
182
+		socket.writerSlotOpen <- true
183
+		// check if more data came in while we held the trylock:
184
+		if !socket.readyToWrite() {
185
+			return
186
+		}
163 187
 		select {
164
-		case <-socket.lineToSendExists:
165
-			// retrieve the buffered data, clear the buffer
166
-			socket.Lock()
167
-			localBuffer = append(localBuffer, socket.buffer...)
168
-			socket.buffer = socket.buffer[:0]
169
-			socket.Unlock()
170
-
171
-			_, err := socket.conn.Write(localBuffer)
172
-			localBuffer = localBuffer[:0]
173
-
174
-			socket.Lock()
175
-			shouldStop = (err != nil) || socket.closed || socket.sendQExceeded
176
-			socket.Unlock()
188
+		case <-socket.writerSlotOpen:
189
+			// got the trylock, loop back around and write
190
+		default:
191
+			// failed to acquire; exit and wait for the holder to observe readyToWrite()
192
+			// after releasing it
193
+			return
177 194
 		}
178 195
 	}
196
+}
197
+
198
+// write the contents of the buffer, then see if we need to close
199
+func (socket *Socket) performWrite() {
200
+	// retrieve the buffered data, clear the buffer
201
+	socket.Lock()
202
+	buffer := socket.buffer
203
+	socket.buffer = nil
204
+	socket.Unlock()
205
+
206
+	_, err := socket.conn.Write(buffer)
207
+
208
+	socket.Lock()
209
+	shouldClose := (err != nil) || socket.closed || socket.sendQExceeded
210
+	socket.Unlock()
211
+
212
+	if !shouldClose {
213
+		return
214
+	}
179 215
 
180 216
 	// mark the socket closed (if someone hasn't already), then write error lines
181 217
 	socket.Lock()
182 218
 	socket.closed = true
219
+	socket.finalized = true
183 220
 	finalData := socket.finalData
184 221
 	if socket.sendQExceeded {
185 222
 		finalData = "\r\nERROR :SendQ Exceeded\r\n"

+ 57
- 0
irc/stats.go Datei anzeigen

@@ -0,0 +1,57 @@
1
+package irc
2
+
3
+import (
4
+	"sync"
5
+)
6
+
7
+// Stats contains the numbers of total, invisible and operators on the server
8
+type Stats struct {
9
+	sync.RWMutex
10
+
11
+	Total     int
12
+	Invisible int
13
+	Operators int
14
+}
15
+
16
+// NewStats creates a new instance of Stats
17
+func NewStats() *Stats {
18
+	serverStats := &Stats{
19
+		Total:     0,
20
+		Invisible: 0,
21
+		Operators: 0,
22
+	}
23
+
24
+	return serverStats
25
+}
26
+
27
+// ChangeTotal increments the total user count on server
28
+func (s *Stats) ChangeTotal(i int) {
29
+	s.Lock()
30
+	defer s.Unlock()
31
+
32
+	s.Total += i
33
+}
34
+
35
+// ChangeInvisible increments the invisible count
36
+func (s *Stats) ChangeInvisible(i int) {
37
+	s.Lock()
38
+	defer s.Unlock()
39
+
40
+	s.Invisible += i
41
+}
42
+
43
+// ChangeOperators increases the operator count
44
+func (s *Stats) ChangeOperators(i int) {
45
+	s.Lock()
46
+	defer s.Unlock()
47
+
48
+	s.Operators += i
49
+}
50
+
51
+// GetStats retrives total, invisible and oper count
52
+func (s *Stats) GetStats() (int, int, int) {
53
+	s.Lock()
54
+	defer s.Unlock()
55
+
56
+	return s.Total, s.Invisible, s.Operators
57
+}

+ 3
- 12
irc/types.go Datei anzeigen

@@ -26,11 +26,11 @@ func (clients ClientSet) Has(client *Client) bool {
26 26
 }
27 27
 
28 28
 // MemberSet is a set of members with modes.
29
-type MemberSet map[*Client]modes.ModeSet
29
+type MemberSet map[*Client]*modes.ModeSet
30 30
 
31 31
 // Add adds the given client to this set.
32 32
 func (members MemberSet) Add(member *Client) {
33
-	members[member] = make(modes.ModeSet)
33
+	members[member] = modes.NewModeSet()
34 34
 }
35 35
 
36 36
 // Remove removes the given client from this set.
@@ -44,19 +44,10 @@ func (members MemberSet) Has(member *Client) bool {
44 44
 	return ok
45 45
 }
46 46
 
47
-// HasMode returns true if the given client is in this set with the given mode.
48
-func (members MemberSet) HasMode(member *Client, mode modes.Mode) bool {
49
-	modes, ok := members[member]
50
-	if !ok {
51
-		return false
52
-	}
53
-	return modes[mode]
54
-}
55
-
56 47
 // AnyHasMode returns true if any of our clients has the given mode.
57 48
 func (members MemberSet) AnyHasMode(mode modes.Mode) bool {
58 49
 	for _, modes := range members {
59
-		if modes[mode] {
50
+		if modes.HasMode(mode) {
60 51
 			return true
61 52
 		}
62 53
 	}

+ 31
- 0
irc/utils/os.go Datei anzeigen

@@ -0,0 +1,31 @@
1
+// Copyright (c) 2018 Shivaram Lingamneni
2
+
3
+package utils
4
+
5
+import (
6
+	"io"
7
+	"os"
8
+)
9
+
10
+// implementation of `cp` (go should really provide this...)
11
+func CopyFile(src string, dst string) (err error) {
12
+	in, err := os.Open(src)
13
+	if err != nil {
14
+		return
15
+	}
16
+	defer in.Close()
17
+	out, err := os.Create(dst)
18
+	if err != nil {
19
+		return
20
+	}
21
+	defer func() {
22
+		closeError := out.Close()
23
+		if err == nil {
24
+			err = closeError
25
+		}
26
+	}()
27
+	if _, err = io.Copy(out, in); err != nil {
28
+		return
29
+	}
30
+	return
31
+}

+ 4
- 1
oragono.go Datei anzeigen

@@ -84,7 +84,10 @@ Options:
84 84
 			log.Println("database initialized: ", config.Datastore.Path)
85 85
 		}
86 86
 	} else if arguments["upgradedb"].(bool) {
87
-		irc.UpgradeDB(config)
87
+		err = irc.UpgradeDB(config)
88
+		if err != nil {
89
+			log.Fatal("Error while upgrading db:", err.Error())
90
+		}
88 91
 		if !arguments["--quiet"].(bool) {
89 92
 			log.Println("database upgraded: ", config.Datastore.Path)
90 93
 		}

+ 5
- 0
oragono.yaml Datei anzeigen

@@ -373,6 +373,11 @@ datastore:
373 373
     # path to the datastore
374 374
     path: ircd.db
375 375
 
376
+    # if the database schema requires an upgrade, `autoupgrade` will attempt to
377
+    # perform it automatically on startup. the database will be backed
378
+    # up, and if the upgrade fails, the original database will be restored.
379
+    autoupgrade: true
380
+
376 381
 # languages config
377 382
 languages:
378 383
     # whether to load languages

Laden…
Abbrechen
Speichern