<commit_before>package irma

import (
	"encoding/xml"
	"fmt"

	"github.com/go-errors/errors"
	"github.com/privacybydesign/irmago/internal/fs"
)

// This file contains data types for scheme managers, issuers, credential types
// matching the XML files in irma_configuration.

// SchemeManager describes a scheme manager.
type SchemeManager struct {
	ID                string           `xml:"Id"`
	Name              TranslatedString `xml:"Name"`
	URL               string           `xml:"Url"`
	Contact           string           `xml:"contact"`
	Description       TranslatedString
	KeyshareServer    string
	KeyshareWebsite   string
	KeyshareAttribute string
	XMLVersion        int      `xml:"version,attr"`
	XMLName           xml.Name `xml:"SchemeManager"`

	Status SchemeManagerStatus `xml:"-"`
	Valid  bool                `xml:"-"` // true iff Status == SchemeManagerStatusValid

	Timestamp Timestamp

	index SchemeManagerIndex
}

// Issuer describes an issuer.
type Issuer struct {
	ID              string           `xml:"ID"`
	Name            TranslatedString `xml:"Name"`
	ShortName       TranslatedString `xml:"ShortName"`
	SchemeManagerID string           `xml:"SchemeManager"`
	ContactAddress  string
	ContactEMail    string
	URL             string `xml:"baseURL"`
	XMLVersion      int    `xml:"version,attr"`

	Valid bool `xml:"-"`
}

// CredentialType is a description of a credential type, specifying (a.o.) its name, issuer, and attributes.
type CredentialType struct {
	ID              string           `xml:"CredentialID"`
	Name            TranslatedString `xml:"Name"`
	ShortName       TranslatedString `xml:"ShortName"`
	IssuerID        string           `xml:"IssuerID"`
	SchemeManagerID string           `xml:"SchemeManager"`
	IsSingleton     bool             `xml:"ShouldBeSingleton"`
	Description     TranslatedString
	Attributes      []AttributeDescription `xml:"Attributes>Attribute"`
	XMLVersion      int                    `xml:"version,attr"`
	XMLName         xml.Name               `xml:"IssueSpecification"`

	Valid bool `xml:"-"`
}

// AttributeDescription is a description of an attribute within a credential type.
type AttributeDescription struct {
	ID           string `xml:"id,attr"`
	Optional     string `xml:"optional,attr"  json:",omitempty"`
	DisplayIndex *int   `xml:"displayIndex,attr" json:",omitempty"`
	Name         TranslatedString
	Description  TranslatedString
}

func (ad AttributeDescription) GetAttributeTypeIdentifier(cred CredentialTypeIdentifier) AttributeTypeIdentifier {
	return NewAttributeTypeIdentifier(cred.String() + "." + ad.ID)
}

func (ad AttributeDescription) IsOptional() bool {
	return ad.Optional == "true"
}

// ContainsAttribute tests whether the specified attribute is contained in this
// credentialtype.
func (ct *CredentialType) ContainsAttribute(ai AttributeTypeIdentifier) bool {
	if ai.CredentialTypeIdentifier().String() != ct.Identifier().String() {
		return false
	}
	for _, desc := range ct.Attributes {
		if desc.ID == ai.Name() {
			return true
		}
	}
	return false
}

// IndexOf returns the index of the specified attribute if present,
// or an error (and -1) if not present.
func (ct CredentialType) IndexOf(ai AttributeTypeIdentifier) (int, error) {
	if ai.CredentialTypeIdentifier() != ct.Identifier() {
		return -1, errors.New("Wrong credential type")
	}
	for i, description := range ct.Attributes {
		if description.ID == ai.Name() {
			return i, nil
		}
	}
	return -1, errors.New("Attribute identifier not found")
}

func (ct CredentialType) AttributeDescription(ai AttributeTypeIdentifier) *AttributeDescription {
	i, _ := ct.IndexOf(ai)
	if i == -1 {
		return nil
	}
	return &ct.Attributes[i]
}

// TranslatedString is a map of translated strings.
type TranslatedString map[string]string

type xmlTranslation struct {
	XMLName xml.Name
	Text    string `xml:",chardata"`
}

type xmlTranslatedString struct {
	Translations []xmlTranslation `xml:",any"`
}

// MarshalXML implements xml.Marshaler.
func (ts *TranslatedString) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
	temp := &xmlTranslatedString{}
	for lang, text := range *ts {
		temp.Translations = append(temp.Translations,
			xmlTranslation{XMLName: xml.Name{Local: lang}, Text: text},
		)
	}
	return e.EncodeElement(temp, start)
}

// UnmarshalXML unmarshals an XML tag containing a string translated to multiple languages,
// for example: <Foo><en>Hello world</en><nl>Hallo wereld</nl></Foo>
// into a TranslatedString: { "en": "Hello world" , "nl": "Hallo wereld" }
func (ts *TranslatedString) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
	if map[string]string(*ts) == nil {
		*ts = TranslatedString(make(map[string]string))
	}
	temp := &xmlTranslatedString{}
	if err := d.DecodeElement(temp, &start); err != nil {
		return err
	}
	for _, translation := range temp.Translations {
		(*ts)[translation.XMLName.Local] = translation.Text
	}
	return nil
}

// Identifier returns the identifier of the specified credential type.
func (ct *CredentialType) Identifier() CredentialTypeIdentifier {
	return NewCredentialTypeIdentifier(ct.SchemeManagerID + "." + ct.IssuerID + "." + ct.ID)
}

// IssuerIdentifier returns the issuer identifier of the specified credential type.
func (ct *CredentialType) IssuerIdentifier() IssuerIdentifier {
	return NewIssuerIdentifier(ct.SchemeManagerID + "." + ct.IssuerID)
}

func (ct *CredentialType) SchemeManagerIdentifier() SchemeManagerIdentifier {
	return NewSchemeManagerIdentifier(ct.SchemeManagerID)
}

func (ct *CredentialType) Logo(conf *Configuration) string {
	path := fmt.Sprintf("%s/%s/%s/Issues/%s/logo.png", conf.Path, ct.SchemeManagerID, ct.IssuerID, ct.ID)
	exists, err := fs.PathExists(path)
	if err != nil || !exists {
		return ""
	}
	return path
}

// Identifier returns the identifier of the specified issuer description.
func (id *Issuer) Identifier() IssuerIdentifier {
	return NewIssuerIdentifier(id.SchemeManagerID + "." + id.ID)
}

func (id *Issuer) SchemeManagerIdentifier() SchemeManagerIdentifier {
	return NewSchemeManagerIdentifier(id.SchemeManagerID)
}

func NewSchemeManager(name string) *SchemeManager {
	return &SchemeManager{ID: name, Status: SchemeManagerStatusUnprocessed, Valid: false}
}

// Identifier returns the identifier of the specified scheme manager.
func (sm *SchemeManager) Identifier() SchemeManagerIdentifier {
	return NewSchemeManagerIdentifier(sm.ID)
}

// Distributed indicates if this scheme manager uses a keyshare server.
func (sm *SchemeManager) Distributed() bool {
	return len(sm.KeyshareServer) > 0
}
<commit_msg>Add IssueUrl tag to credential type<commit_after>package irma

import (
	"encoding/xml"
	"fmt"

	"github.com/go-errors/errors"
	"github.com/privacybydesign/irmago/internal/fs"
)

// This file contains data types for scheme managers, issuers, credential types
// matching the XML files in irma_configuration.

// SchemeManager describes a scheme manager.
type SchemeManager struct {
	ID                string           `xml:"Id"`
	Name              TranslatedString `xml:"Name"`
	URL               string           `xml:"Url"`
	Contact           string           `xml:"contact"`
	Description       TranslatedString
	KeyshareServer    string
	KeyshareWebsite   string
	KeyshareAttribute string
	XMLVersion        int      `xml:"version,attr"`
	XMLName           xml.Name `xml:"SchemeManager"`

	Status SchemeManagerStatus `xml:"-"`
	Valid  bool                `xml:"-"` // true iff Status == SchemeManagerStatusValid

	Timestamp Timestamp

	index SchemeManagerIndex
}

// Issuer describes an issuer.
type Issuer struct {
	ID              string           `xml:"ID"`
	Name            TranslatedString `xml:"Name"`
	ShortName       TranslatedString `xml:"ShortName"`
	SchemeManagerID string           `xml:"SchemeManager"`
	ContactAddress  string
	ContactEMail    string
	URL             string `xml:"ContactAddress"`
	XMLVersion      int    `xml:"version,attr"`

	Valid bool `xml:"-"`
}

// CredentialType is a description of a credential type, specifying (a.o.) its name, issuer, and attributes.
type CredentialType struct {
	ID              string           `xml:"CredentialID"`
	Name            TranslatedString `xml:"Name"`
	ShortName       TranslatedString `xml:"ShortName"`
	IssuerID        string           `xml:"IssuerID"`
	SchemeManagerID string           `xml:"SchemeManager"`
	IsSingleton     bool             `xml:"ShouldBeSingleton"`
	Description     TranslatedString
	Attributes      []AttributeDescription `xml:"Attributes>Attribute"`
	XMLVersion      int                    `xml:"version,attr"`
	XMLName         xml.Name               `xml:"IssueSpecification"`
	IssueURL        TranslatedString       `xml:'IssueUrl'`

	Valid bool `xml:"-"`
}

// AttributeDescription is a description of an attribute within a credential type.
type AttributeDescription struct {
	ID           string `xml:"id,attr"`
	Optional     string `xml:"optional,attr"  json:",omitempty"`
	DisplayIndex *int   `xml:"displayIndex,attr" json:",omitempty"`
	Name         TranslatedString
	Description  TranslatedString
}

func (ad AttributeDescription) GetAttributeTypeIdentifier(cred CredentialTypeIdentifier) AttributeTypeIdentifier {
	return NewAttributeTypeIdentifier(cred.String() + "." + ad.ID)
}

func (ad AttributeDescription) IsOptional() bool {
	return ad.Optional == "true"
}

// ContainsAttribute tests whether the specified attribute is contained in this
// credentialtype.
func (ct *CredentialType) ContainsAttribute(ai AttributeTypeIdentifier) bool {
	if ai.CredentialTypeIdentifier().String() != ct.Identifier().String() {
		return false
	}
	for _, desc := range ct.Attributes {
		if desc.ID == ai.Name() {
			return true
		}
	}
	return false
}

// IndexOf returns the index of the specified attribute if present,
// or an error (and -1) if not present.
func (ct CredentialType) IndexOf(ai AttributeTypeIdentifier) (int, error) {
	if ai.CredentialTypeIdentifier() != ct.Identifier() {
		return -1, errors.New("Wrong credential type")
	}
	for i, description := range ct.Attributes {
		if description.ID == ai.Name() {
			return i, nil
		}
	}
	return -1, errors.New("Attribute identifier not found")
}

func (ct CredentialType) AttributeDescription(ai AttributeTypeIdentifier) *AttributeDescription {
	i, _ := ct.IndexOf(ai)
	if i == -1 {
		return nil
	}
	return &ct.Attributes[i]
}

// TranslatedString is a map of translated strings.
type TranslatedString map[string]string

type xmlTranslation struct {
	XMLName xml.Name
	Text    string `xml:",chardata"`
}

type xmlTranslatedString struct {
	Translations []xmlTranslation `xml:",any"`
}

// MarshalXML implements xml.Marshaler.
func (ts *TranslatedString) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
	temp := &xmlTranslatedString{}
	for lang, text := range *ts {
		temp.Translations = append(temp.Translations,
			xmlTranslation{XMLName: xml.Name{Local: lang}, Text: text},
		)
	}
	return e.EncodeElement(temp, start)
}

// UnmarshalXML unmarshals an XML tag containing a string translated to multiple languages,
// for example: <Foo><en>Hello world</en><nl>Hallo wereld</nl></Foo>
// into a TranslatedString: { "en": "Hello world" , "nl": "Hallo wereld" }
func (ts *TranslatedString) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
	if map[string]string(*ts) == nil {
		*ts = TranslatedString(make(map[string]string))
	}
	temp := &xmlTranslatedString{}
	if err := d.DecodeElement(temp, &start); err != nil {
		return err
	}
	for _, translation := range temp.Translations {
		(*ts)[translation.XMLName.Local] = translation.Text
	}
	return nil
}

// Identifier returns the identifier of the specified credential type.
func (ct *CredentialType) Identifier() CredentialTypeIdentifier {
	return NewCredentialTypeIdentifier(ct.SchemeManagerID + "." + ct.IssuerID + "." + ct.ID)
}

// IssuerIdentifier returns the issuer identifier of the specified credential type.
func (ct *CredentialType) IssuerIdentifier() IssuerIdentifier {
	return NewIssuerIdentifier(ct.SchemeManagerID + "." + ct.IssuerID)
}

func (ct *CredentialType) SchemeManagerIdentifier() SchemeManagerIdentifier {
	return NewSchemeManagerIdentifier(ct.SchemeManagerID)
}

func (ct *CredentialType) Logo(conf *Configuration) string {
	path := fmt.Sprintf("%s/%s/%s/Issues/%s/logo.png", conf.Path, ct.SchemeManagerID, ct.IssuerID, ct.ID)
	exists, err := fs.PathExists(path)
	if err != nil || !exists {
		return ""
	}
	return path
}

// Identifier returns the identifier of the specified issuer description.
func (id *Issuer) Identifier() IssuerIdentifier {
	return NewIssuerIdentifier(id.SchemeManagerID + "." + id.ID)
}

func (id *Issuer) SchemeManagerIdentifier() SchemeManagerIdentifier {
	return NewSchemeManagerIdentifier(id.SchemeManagerID)
}

func NewSchemeManager(name string) *SchemeManager {
	return &SchemeManager{ID: name, Status: SchemeManagerStatusUnprocessed, Valid: false}
}

// Identifier returns the identifier of the specified scheme manager.
func (sm *SchemeManager) Identifier() SchemeManagerIdentifier {
	return NewSchemeManagerIdentifier(sm.ID)
}

// Distributed indicates if this scheme manager uses a keyshare server.
func (sm *SchemeManager) Distributed() bool {
	return len(sm.KeyshareServer) > 0
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"math/rand"
	"regexp"
	"strings"
	"text/template"
	"time"

	"github.com/mattn/go-xmpp"
	log "github.com/sirupsen/logrus"
	"github.com/spf13/viper"
)

var (
	cdsbot    *botClient
	xmlRegexp = regexp.MustCompile(`<\/[\w\-][^><]*>|<[\w\-][^><]*\/>`)
)

const resource = "cds"
const waitTimeOnError = 5 * time.Second

type botClient struct {
	creation               time.Time
	XMPPClient             *xmpp.Client
	admins                 []string
	nbXMPPErrors           int
	nbXMPPErrorsAfterRetry int
	nbXMPPSent             int
	nbXMPPAnswers          int
	nbRenew                int
	chats                  chan xmpp.Chat
}

func getBotClient() (*botClient, error) {
	xClient, err := getNewXMPPClient()
	if err != nil {
		log.Errorf("getClient >> error with getNewXMPPClient err:%s", err)
		return nil, err
	}

	instance := &botClient{
		XMPPClient: xClient,
		admins:     strings.Split(viper.GetString("admin_cds2xmpp"), ","),
	}

	log.Infof("admin configured:%+v", viper.GetString("admin_cds2xmpp"))

	return instance, nil
}

func (bot *botClient) born() {
	bot.creation = time.Now().UTC()
	rand.Seed(time.Now().Unix())

	if viper.GetString("admin_conference") != "" {
		conferences = append(conferences, viper.GetString("admin_conference"))
	}

	bot.chats = make(chan xmpp.Chat)
	go bot.sendToXMPP()

	bot.helloWorld()

	go bot.receive()

	for {
		if err := sendInitialPresence(bot.XMPPClient); err != nil {
			log.Errorf("born - sendInitialPresence >> error: %v", err)
			bot.reconnectXMPPClient()
		}
		time.Sleep(10 * time.Second)

		if err := bot.sendPresencesOnConfs(); err != nil {
			log.Errorf("born - sendPresencesOnConfs >> error: %v", err)
		}
		time.Sleep(20 * time.Second)
	}
}

func (bot *botClient) helloWorld() {
	for _, a := range bot.admins {
		log.Infof("helloWorld >> sending hello world to %s", a)

		bot.chats <- xmpp.Chat{
			Remote: a,
			Type:   "chat",
			Text:   fmt.Sprintf("Hi, I'm CDS2XMPP, what a good day to be alive. /cds cds2xmpp status for more information"),
		}
	}
}

const status = `
CDS2XMPP Status

Started:{{.creation}} since {{.since}}
Admin: {{.admin}}

XMPP:
- sent: {{.sent}}, errors: {{.nbXMPPErrors}}, errors after retry: {{.nbXMPPErrorsAfterRetry}}
- renew: {{.nbRenew}}

----
Bot:
- answers: {{.nbXMPPAnswers}}

`

func (bot *botClient) getStatus() string {

	data := map[string]string{
		"creation":               fmt.Sprintf("%s", cdsbot.creation),
		"since":                  fmt.Sprintf("%s", time.Now().Sub(cdsbot.creation)),
		"admin":                  viper.GetString("admin_cds2xmpp"),
		"sent":                   fmt.Sprintf("%d", bot.nbXMPPSent),
		"nbXMPPErrors":           fmt.Sprintf("%d", bot.nbXMPPErrors),
		"nbXMPPErrorsAfterRetry": fmt.Sprintf("%d", bot.nbXMPPErrorsAfterRetry),
		"nbRenew":                fmt.Sprintf("%d", bot.nbRenew),
		"nbXMPPAnswers":          fmt.Sprintf("%d", bot.nbXMPPAnswers),
	}

	t, errp := template.New("status").Parse(status)
	if errp != nil {
		log.Errorf("getStatus> Error:%s", errp.Error())
		return "Error while prepare status:" + errp.Error()
	}

	var buffer bytes.Buffer
	if err := t.Execute(&buffer, data); err != nil {
		log.Errorf("getStatus> Error:%s", errp.Error())
		return "Error while prepare status (execute):" + err.Error()
	}

	return buffer.String()
}

func (bot *botClient) sendPresencesOnConfs() error {
	bot.nbRenew++
	for _, c := range conferences {
		bot.XMPPClient.JoinMUCNoHistory(c, resource)
	}
	return nil
}

func (bot *botClient) sendToXMPP() {
	for {
		chat := <-bot.chats
		if isXML(chat.Text) {
			cdsbot.XMPPClient.SendHtml(chat)
		} else {
			cdsbot.XMPPClient.Send(chat)
		}
		time.Sleep(time.Duration(viper.GetInt("xmpp_delay")) * time.Second)
	}
}

// XML is detected if presence of tags like </foo> or <foo/>
// This means that
 is not detected as XML, but
 is
func isXML(text string) bool {
	return len(xmlRegexp.FindAllString(text, -1)) > 0
}

func (bot *botClient) receive() {
	for {
		chat, err := bot.XMPPClient.Recv()
		if err != nil {
			if !strings.Contains(err.Error(), "EOF") {
				log.Errorf("receive >> err: %s", err)
				bot.reconnectXMPPClient()
			} else {
				// FIXME: This log (and the else block) are here to troubleshoot potential connexion problems
				// If this log here shows that we can have connection problems not handled by the code below,
				// we will need to apply the same fix as below to renew the XMPP client
				// Else, we will be able to remove this log securely
				// Until then, keep it here to troubleshoot potential connection problems
				log.Errorf("receive >> err WITH EOF: %v", err)
				time.Sleep(waitTimeOnError)
			}
		}
		isError := false
		switch v := chat.(type) {
		case xmpp.Chat:
			if v.Remote != "" {
				if v.Type == "error" {

					isError = true
					log.Errorf("receive> msg error from xmpp :%+v\n", v)

					if !strings.HasSuffix(v.Text, " [cds2xmppRetry]") {
						bot.nbXMPPErrors++
						go cdsbot.sendRetry(v)
					} else {
						bot.nbXMPPErrorsAfterRetry++
					}
				} else {
					log.Debugf("receive> msg from xmpp :%+v\n", v)
				}
			}

			if !isError {
				bot.receiveMsg(v)
			}
		}
	}
}

func (bot *botClient) sendRetry(v xmpp.Chat) {
	time.Sleep(60 * time.Second)
	bot.chats <- xmpp.Chat{
		Remote: v.Remote,
		Type:   getTypeChat(v.Remote),
		Text:   v.Text + " [cds2xmppRetry]",
	}
}

func getTypeChat(s string) string {
	if strings.Contains(s, "@conference") {
		return typeGroupChat
	}
	return typeChat
}

func (bot *botClient) receiveMsg(chat xmpp.Chat) {
	log.Debugf("receiveMsg >> enter remote:%s text:%s", chat.Remote, chat.Text)
	if time.Now().Add(-10*time.Second).Unix() < bot.creation.Unix() {
		log.Debugf("receiveMsg >> exit, bot is starting... ")
		return
	}

	if strings.HasPrefix(chat.Text, "cds, ") || strings.HasPrefix(chat.Text, "/cds ") {
		log.Infof("receiveMsg for cdsbot >> %s from remote:%s stamp:%s", chat.Text, chat.Remote, chat.Stamp)
		bot.answer(chat)
	}

}

func (bot *botClient) reconnectXMPPClient() {
	log.Warn("We will try to get a new XMPP client now to fix this error")
	newXMPPClient, err := getNewXMPPClient()
	if err != nil {
		log.Errorf("XMPP Client renewal >> error with getNewXMPPClient err:%s", err)
	} else {
		log.Info("Reconnection successful, replace the old client with the new one")
		bot.XMPPClient = newXMPPClient
	}

	// Wait 10 seconds between each retry after an error to avoid spamming logs and connection retries
	time.Sleep(waitTimeOnError)
}
<commit_msg>fix(contrib/cds2xmpp): call do (#3609)<commit_after>package main

import (
	"bytes"
	"fmt"
	"math/rand"
	"regexp"
	"strings"
	"text/template"
	"time"

	"github.com/mattn/go-xmpp"
	log "github.com/sirupsen/logrus"
	"github.com/spf13/viper"
)

var (
	cdsbot    *botClient
	xmlRegexp = regexp.MustCompile(`<\/[\w\-][^><]*>|<[\w\-][^><]*\/>`)
)

const resource = "cds"
const waitTimeOnError = 5 * time.Second

type botClient struct {
	creation               time.Time
	XMPPClient             *xmpp.Client
	admins                 []string
	nbXMPPErrors           int
	nbXMPPErrorsAfterRetry int
	nbXMPPSent             int
	nbXMPPAnswers          int
	nbRenew                int
	chats                  chan xmpp.Chat
}

func getBotClient() (*botClient, error) {
	xClient, err := getNewXMPPClient()
	if err != nil {
		log.Errorf("getClient >> error with getNewXMPPClient err:%s", err)
		return nil, err
	}

	instance := &botClient{
		XMPPClient: xClient,
		admins:     strings.Split(viper.GetString("admin_cds2xmpp"), ","),
	}

	log.Infof("admin configured:%+v", viper.GetString("admin_cds2xmpp"))

	return instance, nil
}

func (bot *botClient) born() {
	bot.creation = time.Now().UTC()
	rand.Seed(time.Now().Unix())

	if viper.GetString("admin_conference") != "" {
		conferences = append(conferences, viper.GetString("admin_conference"))
	}

	bot.chats = make(chan xmpp.Chat)
	go bot.sendToXMPP()

	bot.helloWorld()

	go bot.receive()
	go do()

	for {
		if err := sendInitialPresence(bot.XMPPClient); err != nil {
			log.Errorf("born - sendInitialPresence >> error: %v", err)
			bot.reconnectXMPPClient()
		}
		time.Sleep(10 * time.Second)

		if err := bot.sendPresencesOnConfs(); err != nil {
			log.Errorf("born - sendPresencesOnConfs >> error: %v", err)
		}
		time.Sleep(20 * time.Second)
	}
}

func (bot *botClient) helloWorld() {
	for _, a := range bot.admins {
		log.Infof("helloWorld >> sending hello world to %s", a)

		bot.chats <- xmpp.Chat{
			Remote: a,
			Type:   "chat",
			Text:   fmt.Sprintf("Hi, I'm CDS2XMPP, what a good day to be alive. /cds cds2xmpp status for more information"),
		}
	}
}

const status = `
CDS2XMPP Status

Started:{{.creation}} since {{.since}}
Admin: {{.admin}}

XMPP:
- sent: {{.sent}}, errors: {{.nbXMPPErrors}}, errors after retry: {{.nbXMPPErrorsAfterRetry}}
- renew: {{.nbRenew}}

----
Bot:
- answers: {{.nbXMPPAnswers}}

`

func (bot *botClient) getStatus() string {

	data := map[string]string{
		"creation":               fmt.Sprintf("%s", cdsbot.creation),
		"since":                  fmt.Sprintf("%s", time.Now().Sub(cdsbot.creation)),
		"admin":                  viper.GetString("admin_cds2xmpp"),
		"sent":                   fmt.Sprintf("%d", bot.nbXMPPSent),
		"nbXMPPErrors":           fmt.Sprintf("%d", bot.nbXMPPErrors),
		"nbXMPPErrorsAfterRetry": fmt.Sprintf("%d", bot.nbXMPPErrorsAfterRetry),
		"nbRenew":                fmt.Sprintf("%d", bot.nbRenew),
		"nbXMPPAnswers":          fmt.Sprintf("%d", bot.nbXMPPAnswers),
	}

	t, errp := template.New("status").Parse(status)
	if errp != nil {
		log.Errorf("getStatus> Error:%s", errp.Error())
		return "Error while prepare status:" + errp.Error()
	}

	var buffer bytes.Buffer
	if err := t.Execute(&buffer, data); err != nil {
		log.Errorf("getStatus> Error:%s", errp.Error())
		return "Error while prepare status (execute):" + err.Error()
	}

	return buffer.String()
}

func (bot *botClient) sendPresencesOnConfs() error {
	bot.nbRenew++
	for _, c := range conferences {
		bot.XMPPClient.JoinMUCNoHistory(c, resource)
	}
	return nil
}

func (bot *botClient) sendToXMPP() {
	for {
		chat := <-bot.chats
		if isXML(chat.Text) {
			cdsbot.XMPPClient.SendHtml(chat)
		} else {
			cdsbot.XMPPClient.Send(chat)
		}
		time.Sleep(time.Duration(viper.GetInt("xmpp_delay")) * time.Second)
	}
}

// XML is detected if presence of tags like </foo> or <foo/>
// This means that
 is not detected as XML, but
 is
func isXML(text string) bool {
	return len(xmlRegexp.FindAllString(text, -1)) > 0
}

func (bot *botClient) receive() {
	for {
		chat, err := bot.XMPPClient.Recv()
		if err != nil {
			if !strings.Contains(err.Error(), "EOF") {
				log.Errorf("receive >> err: %s", err)
				bot.reconnectXMPPClient()
			} else {
				// FIXME: This log (and the else block) are here to troubleshoot potential connexion problems
				// If this log here shows that we can have connection problems not handled by the code below,
				// we will need to apply the same fix as below to renew the XMPP client
				// Else, we will be able to remove this log securely
				// Until then, keep it here to troubleshoot potential connection problems
				log.Errorf("receive >> err WITH EOF: %v", err)
				time.Sleep(waitTimeOnError)
			}
		}
		isError := false
		switch v := chat.(type) {
		case xmpp.Chat:
			if v.Remote != "" {
				if v.Type == "error" {

					isError = true
					log.Errorf("receive> msg error from xmpp :%+v\n", v)

					if !strings.HasSuffix(v.Text, " [cds2xmppRetry]") {
						bot.nbXMPPErrors++
						go cdsbot.sendRetry(v)
					} else {
						bot.nbXMPPErrorsAfterRetry++
					}
				} else {
					log.Debugf("receive> msg from xmpp :%+v\n", v)
				}
			}

			if !isError {
				bot.receiveMsg(v)
			}
		}
	}
}

func (bot *botClient) sendRetry(v xmpp.Chat) {
	time.Sleep(60 * time.Second)
	bot.chats <- xmpp.Chat{
		Remote: v.Remote,
		Type:   getTypeChat(v.Remote),
		Text:   v.Text + " [cds2xmppRetry]",
	}
}

func getTypeChat(s string) string {
	if strings.Contains(s, "@conference") {
		return typeGroupChat
	}
	return typeChat
}

func (bot *botClient) receiveMsg(chat xmpp.Chat) {
	log.Debugf("receiveMsg >> enter remote:%s text:%s", chat.Remote, chat.Text)
	if time.Now().Add(-10*time.Second).Unix() < bot.creation.Unix() {
		log.Debugf("receiveMsg >> exit, bot is starting... ")
		return
	}

	if strings.HasPrefix(chat.Text, "cds, ") || strings.HasPrefix(chat.Text, "/cds ") {
		log.Infof("receiveMsg for cdsbot >> %s from remote:%s stamp:%s", chat.Text, chat.Remote, chat.Stamp)
		bot.answer(chat)
	}

}

func (bot *botClient) reconnectXMPPClient() {
	log.Warn("We will try to get a new XMPP client now to fix this error")
	newXMPPClient, err := getNewXMPPClient()
	if err != nil {
		log.Errorf("XMPP Client renewal >> error with getNewXMPPClient err:%s", err)
	} else {
		log.Info("Reconnection successful, replace the old client with the new one")
		bot.XMPPClient = newXMPPClient
	}

	// Wait 10 seconds between each retry after an error to avoid spamming logs and connection retries
	time.Sleep(waitTimeOnError)
}
<|endoftext|>


<commit_before>package doc

import (
	"bytes"
	"fmt"
	"os"
	"strings"
	"testing"
)

var _ = fmt.Println
var _ = os.Stderr

func translate(in string) string {
	return strings.Replace(in, "-", "\\-", -1)
}

func TestGenManDoc(t *testing.T) {
	c := initializeWithRootCmd()
	// Need two commands to run the command alphabetical sort
	cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated)
	c.AddCommand(cmdPrint, cmdEcho)
	cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp)

	out := new(bytes.Buffer)

	header := &GenManHeader{
		Title:   "Project",
		Section: "2",
	}
	// We generate on a subcommand so we have both subcommands and parents
	if err := GenMan(cmdEcho, header, out); err != nil {
		t.Fatal(err)
	}
	found := out.String()

	// Make sure parent has - in CommandPath() in SEE ALSO:
	parentPath := cmdEcho.Parent().CommandPath()
	dashParentPath := strings.Replace(parentPath, " ", "-", -1)
	expected := translate(dashParentPath)
	expected = expected + "(" + header.Section + ")"
	checkStringContains(t, found, expected)

	// Our description
	expected = translate(cmdEcho.Name())
	checkStringContains(t, found, expected)

	// Better have our example
	expected = translate(cmdEcho.Name())
	checkStringContains(t, found, expected)

	// A local flag
	expected = "boolone"
	checkStringContains(t, found, expected)

	// persistent flag on parent
	expected = "rootflag"
	checkStringContains(t, found, expected)

	// We better output info about our parent
	expected = translate(cmdRootWithRun.Name())
	checkStringContains(t, found, expected)

	// And about subcommands
	expected = translate(cmdEchoSub.Name())
	checkStringContains(t, found, expected)

	unexpected := translate(cmdDeprecated.Name())
	checkStringOmits(t, found, unexpected)

	// auto generated
	expected = translate("Auto generated")
	checkStringContains(t, found, expected)
}

func TestGenManNoGenTag(t *testing.T) {
	c := initializeWithRootCmd()
	// Need two commands to run the command alphabetical sort
	cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated)
	c.AddCommand(cmdPrint, cmdEcho)
	cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp)
	cmdEcho.DisableAutoGenTag = true
	out := new(bytes.Buffer)

	header := &GenManHeader{
		Title:   "Project",
		Section: "2",
	}
	// We generate on a subcommand so we have both subcommands and parents
	if err := GenMan(cmdEcho, header, out); err != nil {
		t.Fatal(err)
	}
	found := out.String()

	unexpected := translate("#HISTORY")
	checkStringOmits(t, found, unexpected)
}
<commit_msg>Add explicit doc.GenMan SEE ALSO test covering #229<commit_after>package doc

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"strings"
	"testing"

	"github.com/spf13/cobra"
)

var _ = fmt.Println
var _ = os.Stderr

func translate(in string) string {
	return strings.Replace(in, "-", "\\-", -1)
}

func TestGenManDoc(t *testing.T) {
	c := initializeWithRootCmd()
	// Need two commands to run the command alphabetical sort
	cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated)
	c.AddCommand(cmdPrint, cmdEcho)
	cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp)

	out := new(bytes.Buffer)

	header := &GenManHeader{
		Title:   "Project",
		Section: "2",
	}
	// We generate on a subcommand so we have both subcommands and parents
	if err := GenMan(cmdEcho, header, out); err != nil {
		t.Fatal(err)
	}
	found := out.String()

	// Make sure parent has - in CommandPath() in SEE ALSO:
	parentPath := cmdEcho.Parent().CommandPath()
	dashParentPath := strings.Replace(parentPath, " ", "-", -1)
	expected := translate(dashParentPath)
	expected = expected + "(" + header.Section + ")"
	checkStringContains(t, found, expected)

	// Our description
	expected = translate(cmdEcho.Name())
	checkStringContains(t, found, expected)

	// Better have our example
	expected = translate(cmdEcho.Name())
	checkStringContains(t, found, expected)

	// A local flag
	expected = "boolone"
	checkStringContains(t, found, expected)

	// persistent flag on parent
	expected = "rootflag"
	checkStringContains(t, found, expected)

	// We better output info about our parent
	expected = translate(cmdRootWithRun.Name())
	checkStringContains(t, found, expected)

	// And about subcommands
	expected = translate(cmdEchoSub.Name())
	checkStringContains(t, found, expected)

	unexpected := translate(cmdDeprecated.Name())
	checkStringOmits(t, found, unexpected)

	// auto generated
	expected = translate("Auto generated")
	checkStringContains(t, found, expected)
}

func TestGenManNoGenTag(t *testing.T) {
	c := initializeWithRootCmd()
	// Need two commands to run the command alphabetical sort
	cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated)
	c.AddCommand(cmdPrint, cmdEcho)
	cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp)
	cmdEcho.DisableAutoGenTag = true
	out := new(bytes.Buffer)

	header := &GenManHeader{
		Title:   "Project",
		Section: "2",
	}
	// We generate on a subcommand so we have both subcommands and parents
	if err := GenMan(cmdEcho, header, out); err != nil {
		t.Fatal(err)
	}
	found := out.String()

	unexpected := translate("#HISTORY")
	checkStringOmits(t, found, unexpected)
}

func TestGenManSeeAlso(t *testing.T) {
	noop := func(cmd *cobra.Command, args []string) {}

	top := &cobra.Command{Use: "top", Run: noop}
	aaa := &cobra.Command{Use: "aaa", Run: noop, Hidden: true} // #229
	bbb := &cobra.Command{Use: "bbb", Run: noop}
	ccc := &cobra.Command{Use: "ccc", Run: noop}
	top.AddCommand(aaa, bbb, ccc)

	out := new(bytes.Buffer)
	header := &GenManHeader{}
	if err := GenMan(top, header, out); err != nil {
		t.Fatal(err)
	}

	scanner := bufio.NewScanner(out)

	if err := AssertLineFound(scanner, ".SH SEE ALSO"); err != nil {
		t.Fatal(fmt.Errorf("Couldn't find SEE ALSO section header: %s", err.Error()))
	}

	if err := AssertNextLineEquals(scanner, ".PP"); err != nil {
		t.Fatal(fmt.Errorf("First line after SEE ALSO wasn't break-indent: %s", err.Error()))
	}

	if err := AssertNextLineEquals(scanner, `\fBtop\-bbb(1)\fP, \fBtop\-ccc(1)\fP`); err != nil {
		t.Fatal(fmt.Errorf("Second line after SEE ALSO wasn't correct: %s", err.Error()))
	}
}

func AssertLineFound(scanner *bufio.Scanner, expectedLine string) error {
	for scanner.Scan() {
		line := scanner.Text()
		if line == expectedLine {
			return nil
		}
	}

	if err := scanner.Err(); err != nil {
		return fmt.Errorf("AssertLineFound: scan failed: %s", err.Error())
	}

	return fmt.Errorf("AssertLineFound: hit EOF before finding %#v", expectedLine)
}

func AssertNextLineEquals(scanner *bufio.Scanner, expectedLine string) error {
	if scanner.Scan() {
		line := scanner.Text()
		if line == expectedLine {
			return nil
		} else {
			return fmt.Errorf("AssertNextLineEquals: got %#v, not %#v", line, expectedLine)
		}
	}

	if err := scanner.Err(); err != nil {
		return fmt.Errorf("AssertNextLineEquals: scan failed: %s", err.Error())
	}

	return fmt.Errorf("AssertNextLineEquals: hit EOF before finding %#v", expectedLine)
}
<|endoftext|>


<commit_before>// Derived from Inferno utils/6l/obj.c and utils/6l/span.c
// http://code.google.com/p/inferno-os/source/browse/utils/6l/obj.c
// http://code.google.com/p/inferno-os/source/browse/utils/6l/span.c
//
//	Copyright © 1994-1999 Lucent Technologies Inc.  All rights reserved.
//	Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
//	Portions Copyright © 1997-1999 Vita Nuova Limited
//	Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
//	Portions Copyright © 2004,2006 Bruce Ellis
//	Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
//	Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
//	Portions Copyright © 2009 The Go Authors.  All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package ld

import (
	"cmd/internal/obj"
	"log"
	"os"
	"path/filepath"
	"strconv"
)

var headers = []struct {
	name string
	val  int
}{
	{"darwin", obj.Hdarwin},
	{"dragonfly", obj.Hdragonfly},
	{"elf", obj.Helf},
	{"freebsd", obj.Hfreebsd},
	{"linux", obj.Hlinux},
	{"android", obj.Hlinux}, // must be after "linux" entry or else headstr(Hlinux) == "android"
	{"nacl", obj.Hnacl},
	{"netbsd", obj.Hnetbsd},
	{"openbsd", obj.Hopenbsd},
	{"plan9", obj.Hplan9},
	{"solaris", obj.Hsolaris},
	{"windows", obj.Hwindows},
	{"windowsgui", obj.Hwindows},
}

func linknew(arch *LinkArch) *Link {
	ctxt := new(Link)
	ctxt.Hash = make(map[symVer]*LSym)
	ctxt.Arch = arch
	ctxt.Version = obj.HistVersion
	ctxt.Goroot = obj.Getgoroot()

	p := obj.Getgoarch()
	if p != arch.Name {
		log.Fatalf("invalid goarch %s (want %s)", p, arch.Name)
	}

	var buf string
	buf, _ = os.Getwd()
	if buf == "" {
		buf = "/???"
	}
	buf = filepath.ToSlash(buf)

	ctxt.Headtype = headtype(obj.Getgoos())
	if ctxt.Headtype < 0 {
		log.Fatalf("unknown goos %s", obj.Getgoos())
	}

	// Record thread-local storage offset.
	// TODO(rsc): Move tlsoffset back into the linker.
	switch ctxt.Headtype {
	default:
		log.Fatalf("unknown thread-local storage offset for %s", Headstr(ctxt.Headtype))

	case obj.Hplan9, obj.Hwindows:
		break

		/*
		 * ELF uses TLS offset negative from FS.
		 * Translate 0(FS) and 8(FS) into -16(FS) and -8(FS).
		 * Known to low-level assembly in package runtime and runtime/cgo.
		 */
	case obj.Hlinux,
		obj.Hfreebsd,
		obj.Hnetbsd,
		obj.Hopenbsd,
		obj.Hdragonfly,
		obj.Hsolaris:
		if obj.Getgoos() == "android" {
			switch ctxt.Arch.Thechar {
			case '6':
				// Android/amd64 constant - offset from 0(FS) to our TLS slot.
				// Explained in src/runtime/cgo/gcc_android_*.c
				ctxt.Tlsoffset = 0x1d0
			case '8':
				// Android/386 constant - offset from 0(GS) to our TLS slot.
				ctxt.Tlsoffset = 0xf8
			default:
				ctxt.Tlsoffset = -1 * ctxt.Arch.Ptrsize
			}
		} else {
			ctxt.Tlsoffset = -1 * ctxt.Arch.Ptrsize
		}

	case obj.Hnacl:
		switch ctxt.Arch.Thechar {
		default:
			log.Fatalf("unknown thread-local storage offset for nacl/%s", ctxt.Arch.Name)

		case '5':
			ctxt.Tlsoffset = 0

		case '6':
			ctxt.Tlsoffset = 0

		case '8':
			ctxt.Tlsoffset = -8
		}

		/*
		 * OS X system constants - offset from 0(GS) to our TLS.
		 * Explained in src/runtime/cgo/gcc_darwin_*.c.
		 */
	case obj.Hdarwin:
		switch ctxt.Arch.Thechar {
		default:
			log.Fatalf("unknown thread-local storage offset for darwin/%s", ctxt.Arch.Name)

		case '5':
			ctxt.Tlsoffset = 0 // dummy value, not needed

		case '6':
			ctxt.Tlsoffset = 0x8a0

		case '7':
			ctxt.Tlsoffset = 0 // dummy value, not needed

		case '8':
			ctxt.Tlsoffset = 0x468
		}
	}

	// On arm, record goarm.
	if ctxt.Arch.Thechar == '5' {
		ctxt.Goarm = obj.Getgoarm()
	}

	return ctxt
}

func linknewsym(ctxt *Link, symb string, v int) *LSym {
	s := new(LSym)
	*s = LSym{}

	s.Dynid = -1
	s.Plt = -1
	s.Got = -1
	s.Name = symb
	s.Type = 0
	s.Version = int16(v)
	s.Value = 0
	s.Size = 0
	ctxt.Nsymbol++

	s.Allsym = ctxt.Allsym
	ctxt.Allsym = s

	return s
}

type symVer struct {
	sym string
	ver int
}

func _lookup(ctxt *Link, symb string, v int, creat int) *LSym {
	s := ctxt.Hash[symVer{symb, v}]
	if s != nil {
		return s
	}
	if creat == 0 {
		return nil
	}

	s = linknewsym(ctxt, symb, v)
	s.Extname = s.Name
	ctxt.Hash[symVer{symb, v}] = s
	return s
}

func Linklookup(ctxt *Link, name string, v int) *LSym {
	return _lookup(ctxt, name, v, 1)
}

// read-only lookup
func Linkrlookup(ctxt *Link, name string, v int) *LSym {
	return _lookup(ctxt, name, v, 0)
}

func Headstr(v int) string {
	for i := 0; i < len(headers); i++ {
		if v == headers[i].val {
			return headers[i].name
		}
	}
	return strconv.Itoa(v)
}

func headtype(name string) int {
	for i := 0; i < len(headers); i++ {
		if name == headers[i].name {
			return headers[i].val
		}
	}
	return -1
}
<commit_msg>cmd/link/internal/ld: remove unused call to os.Getwd<commit_after>// Derived from Inferno utils/6l/obj.c and utils/6l/span.c
// http://code.google.com/p/inferno-os/source/browse/utils/6l/obj.c
// http://code.google.com/p/inferno-os/source/browse/utils/6l/span.c
//
//	Copyright © 1994-1999 Lucent Technologies Inc.  All rights reserved.
//	Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
//	Portions Copyright © 1997-1999 Vita Nuova Limited
//	Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
//	Portions Copyright © 2004,2006 Bruce Ellis
//	Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
//	Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
//	Portions Copyright © 2009 The Go Authors.  All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package ld

import (
	"cmd/internal/obj"
	"log"
	"strconv"
)

var headers = []struct {
	name string
	val  int
}{
	{"darwin", obj.Hdarwin},
	{"dragonfly", obj.Hdragonfly},
	{"elf", obj.Helf},
	{"freebsd", obj.Hfreebsd},
	{"linux", obj.Hlinux},
	{"android", obj.Hlinux}, // must be after "linux" entry or else headstr(Hlinux) == "android"
	{"nacl", obj.Hnacl},
	{"netbsd", obj.Hnetbsd},
	{"openbsd", obj.Hopenbsd},
	{"plan9", obj.Hplan9},
	{"solaris", obj.Hsolaris},
	{"windows", obj.Hwindows},
	{"windowsgui", obj.Hwindows},
}

func linknew(arch *LinkArch) *Link {
	ctxt := new(Link)
	ctxt.Hash = make(map[symVer]*LSym)
	ctxt.Arch = arch
	ctxt.Version = obj.HistVersion
	ctxt.Goroot = obj.Getgoroot()

	p := obj.Getgoarch()
	if p != arch.Name {
		log.Fatalf("invalid goarch %s (want %s)", p, arch.Name)
	}

	ctxt.Headtype = headtype(obj.Getgoos())
	if ctxt.Headtype < 0 {
		log.Fatalf("unknown goos %s", obj.Getgoos())
	}

	// Record thread-local storage offset.
	// TODO(rsc): Move tlsoffset back into the linker.
	switch ctxt.Headtype {
	default:
		log.Fatalf("unknown thread-local storage offset for %s", Headstr(ctxt.Headtype))

	case obj.Hplan9, obj.Hwindows:
		break

		/*
		 * ELF uses TLS offset negative from FS.
		 * Translate 0(FS) and 8(FS) into -16(FS) and -8(FS).
		 * Known to low-level assembly in package runtime and runtime/cgo.
		 */
	case obj.Hlinux,
		obj.Hfreebsd,
		obj.Hnetbsd,
		obj.Hopenbsd,
		obj.Hdragonfly,
		obj.Hsolaris:
		if obj.Getgoos() == "android" {
			switch ctxt.Arch.Thechar {
			case '6':
				// Android/amd64 constant - offset from 0(FS) to our TLS slot.
				// Explained in src/runtime/cgo/gcc_android_*.c
				ctxt.Tlsoffset = 0x1d0
			case '8':
				// Android/386 constant - offset from 0(GS) to our TLS slot.
				ctxt.Tlsoffset = 0xf8
			default:
				ctxt.Tlsoffset = -1 * ctxt.Arch.Ptrsize
			}
		} else {
			ctxt.Tlsoffset = -1 * ctxt.Arch.Ptrsize
		}

	case obj.Hnacl:
		switch ctxt.Arch.Thechar {
		default:
			log.Fatalf("unknown thread-local storage offset for nacl/%s", ctxt.Arch.Name)

		case '5':
			ctxt.Tlsoffset = 0

		case '6':
			ctxt.Tlsoffset = 0

		case '8':
			ctxt.Tlsoffset = -8
		}

		/*
		 * OS X system constants - offset from 0(GS) to our TLS.
		 * Explained in src/runtime/cgo/gcc_darwin_*.c.
		 */
	case obj.Hdarwin:
		switch ctxt.Arch.Thechar {
		default:
			log.Fatalf("unknown thread-local storage offset for darwin/%s", ctxt.Arch.Name)

		case '5':
			ctxt.Tlsoffset = 0 // dummy value, not needed

		case '6':
			ctxt.Tlsoffset = 0x8a0

		case '7':
			ctxt.Tlsoffset = 0 // dummy value, not needed

		case '8':
			ctxt.Tlsoffset = 0x468
		}
	}

	// On arm, record goarm.
	if ctxt.Arch.Thechar == '5' {
		ctxt.Goarm = obj.Getgoarm()
	}

	return ctxt
}

func linknewsym(ctxt *Link, symb string, v int) *LSym {
	s := new(LSym)
	*s = LSym{}

	s.Dynid = -1
	s.Plt = -1
	s.Got = -1
	s.Name = symb
	s.Type = 0
	s.Version = int16(v)
	s.Value = 0
	s.Size = 0
	ctxt.Nsymbol++

	s.Allsym = ctxt.Allsym
	ctxt.Allsym = s

	return s
}

type symVer struct {
	sym string
	ver int
}

func _lookup(ctxt *Link, symb string, v int, creat int) *LSym {
	s := ctxt.Hash[symVer{symb, v}]
	if s != nil {
		return s
	}
	if creat == 0 {
		return nil
	}

	s = linknewsym(ctxt, symb, v)
	s.Extname = s.Name
	ctxt.Hash[symVer{symb, v}] = s
	return s
}

func Linklookup(ctxt *Link, name string, v int) *LSym {
	return _lookup(ctxt, name, v, 1)
}

// read-only lookup
func Linkrlookup(ctxt *Link, name string, v int) *LSym {
	return _lookup(ctxt, name, v, 0)
}

func Headstr(v int) string {
	for i := 0; i < len(headers); i++ {
		if v == headers[i].val {
			return headers[i].name
		}
	}
	return strconv.Itoa(v)
}

func headtype(name string) int {
	for i := 0; i < len(headers); i++ {
		if name == headers[i].name {
			return headers[i].val
		}
	}
	return -1
}
<|endoftext|>


<commit_before>// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"crypto/md5"
	"flag"
	"fmt"
	"go/build"
	"internal/testenv"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"testing"
)

var tmp, exe string // populated by buildObjdump

func TestMain(m *testing.M) {
	if !testenv.HasGoBuild() {
		return
	}

	var exitcode int
	if err := buildObjdump(); err == nil {
		exitcode = m.Run()
	} else {
		fmt.Println(err)
		exitcode = 1
	}
	os.RemoveAll(tmp)
	os.Exit(exitcode)
}

func buildObjdump() error {
	var err error
	tmp, err = ioutil.TempDir("", "TestObjDump")
	if err != nil {
		return fmt.Errorf("TempDir failed: %v", err)
	}

	exe = filepath.Join(tmp, "testobjdump.exe")
	gotool, err := testenv.GoTool()
	if err != nil {
		return err
	}
	out, err := exec.Command(gotool, "build", "-o", exe, "cmd/objdump").CombinedOutput()
	if err != nil {
		os.RemoveAll(tmp)
		return fmt.Errorf("go build -o %v cmd/objdump: %v\n%s", exe, err, string(out))
	}

	return nil
}

var x86Need = []string{
	"JMP main.main(SB)",
	"CALL main.Println(SB)",
	"RET",
}

var amd64GnuNeed = []string{
	"movq",
	"callq",
	"cmpb",
}

var i386GnuNeed = []string{
	"mov",
	"call",
	"cmp",
}

var armNeed = []string{
	"B main.main(SB)",
	"BL main.Println(SB)",
	"RET",
}

var arm64GnuNeed = []string{
	"ldr",
	"bl",
	"cmp",
}

var ppcNeed = []string{
	"BR main.main(SB)",
	"CALL main.Println(SB)",
	"RET",
}

var ppcGnuNeed = []string{
	"mflr",
	"lbz",
	"cmpw",
}

var target = flag.String("target", "", "test disassembly of `goos/goarch` binary")

// objdump is fully cross platform: it can handle binaries
// from any known operating system and architecture.
// We could in principle add binaries to testdata and check
// all the supported systems during this test. However, the
// binaries would be about 1 MB each, and we don't want to
// add that much junk to the hg repository. Instead, build a
// binary for the current system (only) and test that objdump
// can handle that one.

func testDisasm(t *testing.T, printCode bool, printGnuAsm bool, flags ...string) {
	t.Parallel()
	goarch := runtime.GOARCH
	if *target != "" {
		f := strings.Split(*target, "/")
		if len(f) != 2 {
			t.Fatalf("-target argument must be goos/goarch")
		}
		defer os.Setenv("GOOS", os.Getenv("GOOS"))
		defer os.Setenv("GOARCH", os.Getenv("GOARCH"))
		os.Setenv("GOOS", f[0])
		os.Setenv("GOARCH", f[1])
		goarch = f[1]
	}

	hash := md5.Sum([]byte(fmt.Sprintf("%v-%v-%v", flags, printCode, printGnuAsm)))
	hello := filepath.Join(tmp, fmt.Sprintf("hello-%x.exe", hash))
	args := []string{"build", "-o", hello}
	args = append(args, flags...)
	args = append(args, "fmthello.go")
	cmd := exec.Command(testenv.GoToolPath(t), args...)
	cmd.Dir = "testdata" // "Bad line" bug #36683 is sensitive to being run in the source directory
	t.Logf("Running %v", cmd.Args)
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("go build fmthello.go: %v\n%s", err, out)
	}
	need := []string{
		"TEXT main.main(SB)",
	}

	if printCode {
		need = append(need, `	Println("hello, world")`)
	} else {
		need = append(need, "fmthello.go:6")
	}

	switch goarch {
	case "amd64", "386":
		need = append(need, x86Need...)
	case "arm":
		need = append(need, armNeed...)
	case "ppc64", "ppc64le":
		need = append(need, ppcNeed...)
	}

	if printGnuAsm {
		switch goarch {
		case "amd64":
			need = append(need, amd64GnuNeed...)
		case "386":
			need = append(need, i386GnuNeed...)
		case "arm64":
			need = append(need, arm64GnuNeed...)
		case "ppc64", "ppc64le":
			need = append(need, ppcGnuNeed...)
		}
	}
	args = []string{
		"-s", "main.main",
		hello,
	}

	if printCode {
		args = append([]string{"-S"}, args...)
	}

	if printGnuAsm {
		args = append([]string{"-gnu"}, args...)
	}
	cmd = exec.Command(exe, args...)
	cmd.Dir = "testdata" // "Bad line" bug #36683 is sensitive to being run in the source directory
	out, err = cmd.CombinedOutput()
	t.Logf("Running %v", cmd.Args)

	if err != nil {
		t.Fatalf("objdump fmthello.exe: %v\n%s", err, out)
	}

	text := string(out)
	ok := true
	for _, s := range need {
		if !strings.Contains(text, s) {
			t.Errorf("disassembly missing '%s'", s)
			ok = false
		}
	}
	if goarch == "386" {
		if strings.Contains(text, "(IP)") {
			t.Errorf("disassembly contains PC-Relative addressing on 386")
			ok = false
		}
	}

	if !ok {
		t.Logf("full disassembly:\n%s", text)
	}
}

func TestDisasm(t *testing.T) {
	switch runtime.GOARCH {
	case "mips", "mipsle", "mips64", "mips64le":
		t.Skipf("skipping on %s, issue 12559", runtime.GOARCH)
	case "riscv64":
		t.Skipf("skipping on %s, issue 36738", runtime.GOARCH)
	case "s390x":
		t.Skipf("skipping on %s, issue 15255", runtime.GOARCH)
	}
	testDisasm(t, false, false)
}

func TestDisasmCode(t *testing.T) {
	switch runtime.GOARCH {
	case "mips", "mipsle", "mips64", "mips64le", "riscv64", "s390x":
		t.Skipf("skipping on %s, issue 19160", runtime.GOARCH)
	}
	testDisasm(t, true, false)
}

func TestDisasmGnuAsm(t *testing.T) {
	switch runtime.GOARCH {
	case "mips", "mipsle", "mips64", "mips64le", "riscv64", "s390x":
		t.Skipf("skipping on %s, issue 19160", runtime.GOARCH)
	case "arm":
		t.Skipf("skipping gnuAsm test on %s", runtime.GOARCH)
	}
	testDisasm(t, false, true)
}

func TestDisasmExtld(t *testing.T) {
	switch runtime.GOOS {
	case "plan9", "windows":
		t.Skipf("skipping on %s", runtime.GOOS)
	}
	switch runtime.GOARCH {
	case "ppc64":
		t.Skipf("skipping on %s, no support for external linking, issue 9038", runtime.GOARCH)
	case "mips64", "mips64le", "mips", "mipsle":
		t.Skipf("skipping on %s, issue 12559 and 12560", runtime.GOARCH)
	case "riscv64":
		t.Skipf("skipping on %s, no support for external linking, issue 36739", runtime.GOARCH)
	case "s390x":
		t.Skipf("skipping on %s, issue 15255", runtime.GOARCH)
	}
	if !build.Default.CgoEnabled {
		t.Skip("skipping because cgo is not enabled")
	}
	testDisasm(t, false, false, "-ldflags=-linkmode=external")
}

func TestDisasmGoobj(t *testing.T) {
	switch runtime.GOARCH {
	case "mips", "mipsle", "mips64", "mips64le":
		t.Skipf("skipping on %s, issue 12559", runtime.GOARCH)
	case "riscv64":
		t.Skipf("skipping on %s, issue 36738", runtime.GOARCH)
	case "s390x":
		t.Skipf("skipping on %s, issue 15255", runtime.GOARCH)
	}

	hello := filepath.Join(tmp, "hello.o")
	args := []string{"tool", "compile", "-o", hello}
	args = append(args, "testdata/fmthello.go")
	out, err := exec.Command(testenv.GoToolPath(t), args...).CombinedOutput()
	if err != nil {
		t.Fatalf("go tool compile fmthello.go: %v\n%s", err, out)
	}
	need := []string{
		"main(SB)",
		"fmthello.go:6",
	}

	args = []string{
		"-s", "main",
		hello,
	}

	out, err = exec.Command(exe, args...).CombinedOutput()
	if err != nil {
		t.Fatalf("objdump fmthello.o: %v\n%s", err, out)
	}

	text := string(out)
	ok := true
	for _, s := range need {
		if !strings.Contains(text, s) {
			t.Errorf("disassembly missing '%s'", s)
			ok = false
		}
	}
	if runtime.GOARCH == "386" {
		if strings.Contains(text, "(IP)") {
			t.Errorf("disassembly contains PC-Relative addressing on 386")
			ok = false
		}
	}
	if !ok {
		t.Logf("full disassembly:\n%s", text)
	}
}
<commit_msg>cmd/objdump: test disassembly on ARM and ARM64<commit_after>// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"crypto/md5"
	"flag"
	"fmt"
	"go/build"
	"internal/testenv"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"testing"
)

var tmp, exe string // populated by buildObjdump

func TestMain(m *testing.M) {
	if !testenv.HasGoBuild() {
		return
	}

	var exitcode int
	if err := buildObjdump(); err == nil {
		exitcode = m.Run()
	} else {
		fmt.Println(err)
		exitcode = 1
	}
	os.RemoveAll(tmp)
	os.Exit(exitcode)
}

func buildObjdump() error {
	var err error
	tmp, err = ioutil.TempDir("", "TestObjDump")
	if err != nil {
		return fmt.Errorf("TempDir failed: %v", err)
	}

	exe = filepath.Join(tmp, "testobjdump.exe")
	gotool, err := testenv.GoTool()
	if err != nil {
		return err
	}
	out, err := exec.Command(gotool, "build", "-o", exe, "cmd/objdump").CombinedOutput()
	if err != nil {
		os.RemoveAll(tmp)
		return fmt.Errorf("go build -o %v cmd/objdump: %v\n%s", exe, err, string(out))
	}

	return nil
}

var x86Need = []string{ // for both 386 and AMD64
	"JMP main.main(SB)",
	"CALL main.Println(SB)",
	"RET",
}

var amd64GnuNeed = []string{
	"movq",
	"callq",
	"cmpb",
}

var i386GnuNeed = []string{
	"mov",
	"call",
	"cmp",
}

var armNeed = []string{
	"B main.main(SB)",
	"BL main.Println(SB)",
	"RET",
}

var arm64Need = []string{
	"JMP main.main(SB)",
	"CALL main.Println(SB)",
	"RET",
}

var armGnuNeed = []string{ // for both ARM and AMR64
	"ldr",
	"bl",
	"cmp",
}

var ppcNeed = []string{
	"BR main.main(SB)",
	"CALL main.Println(SB)",
	"RET",
}

var ppcGnuNeed = []string{
	"mflr",
	"lbz",
	"cmpw",
}

var target = flag.String("target", "", "test disassembly of `goos/goarch` binary")

// objdump is fully cross platform: it can handle binaries
// from any known operating system and architecture.
// We could in principle add binaries to testdata and check
// all the supported systems during this test. However, the
// binaries would be about 1 MB each, and we don't want to
// add that much junk to the hg repository. Instead, build a
// binary for the current system (only) and test that objdump
// can handle that one.

func testDisasm(t *testing.T, printCode bool, printGnuAsm bool, flags ...string) {
	t.Parallel()
	goarch := runtime.GOARCH
	if *target != "" {
		f := strings.Split(*target, "/")
		if len(f) != 2 {
			t.Fatalf("-target argument must be goos/goarch")
		}
		defer os.Setenv("GOOS", os.Getenv("GOOS"))
		defer os.Setenv("GOARCH", os.Getenv("GOARCH"))
		os.Setenv("GOOS", f[0])
		os.Setenv("GOARCH", f[1])
		goarch = f[1]
	}

	hash := md5.Sum([]byte(fmt.Sprintf("%v-%v-%v", flags, printCode, printGnuAsm)))
	hello := filepath.Join(tmp, fmt.Sprintf("hello-%x.exe", hash))
	args := []string{"build", "-o", hello}
	args = append(args, flags...)
	args = append(args, "fmthello.go")
	cmd := exec.Command(testenv.GoToolPath(t), args...)
	cmd.Dir = "testdata" // "Bad line" bug #36683 is sensitive to being run in the source directory
	t.Logf("Running %v", cmd.Args)
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("go build fmthello.go: %v\n%s", err, out)
	}
	need := []string{
		"TEXT main.main(SB)",
	}

	if printCode {
		need = append(need, `	Println("hello, world")`)
	} else {
		need = append(need, "fmthello.go:6")
	}

	switch goarch {
	case "amd64", "386":
		need = append(need, x86Need...)
	case "arm":
		need = append(need, armNeed...)
	case "arm64":
		need = append(need, arm64Need...)
	case "ppc64", "ppc64le":
		need = append(need, ppcNeed...)
	}

	if printGnuAsm {
		switch goarch {
		case "amd64":
			need = append(need, amd64GnuNeed...)
		case "386":
			need = append(need, i386GnuNeed...)
		case "arm", "arm64":
			need = append(need, armGnuNeed...)
		case "ppc64", "ppc64le":
			need = append(need, ppcGnuNeed...)
		}
	}
	args = []string{
		"-s", "main.main",
		hello,
	}

	if printCode {
		args = append([]string{"-S"}, args...)
	}

	if printGnuAsm {
		args = append([]string{"-gnu"}, args...)
	}
	cmd = exec.Command(exe, args...)
	cmd.Dir = "testdata" // "Bad line" bug #36683 is sensitive to being run in the source directory
	out, err = cmd.CombinedOutput()
	t.Logf("Running %v", cmd.Args)

	if err != nil {
		t.Fatalf("objdump fmthello.exe: %v\n%s", err, out)
	}

	text := string(out)
	ok := true
	for _, s := range need {
		if !strings.Contains(text, s) {
			t.Errorf("disassembly missing '%s'", s)
			ok = false
		}
	}
	if goarch == "386" {
		if strings.Contains(text, "(IP)") {
			t.Errorf("disassembly contains PC-Relative addressing on 386")
			ok = false
		}
	}

	if !ok {
		t.Logf("full disassembly:\n%s", text)
	}
}

func TestDisasm(t *testing.T) {
	switch runtime.GOARCH {
	case "mips", "mipsle", "mips64", "mips64le":
		t.Skipf("skipping on %s, issue 12559", runtime.GOARCH)
	case "riscv64":
		t.Skipf("skipping on %s, issue 36738", runtime.GOARCH)
	case "s390x":
		t.Skipf("skipping on %s, issue 15255", runtime.GOARCH)
	}
	testDisasm(t, false, false)
}

func TestDisasmCode(t *testing.T) {
	switch runtime.GOARCH {
	case "mips", "mipsle", "mips64", "mips64le", "riscv64", "s390x":
		t.Skipf("skipping on %s, issue 19160", runtime.GOARCH)
	}
	testDisasm(t, true, false)
}

func TestDisasmGnuAsm(t *testing.T) {
	switch runtime.GOARCH {
	case "mips", "mipsle", "mips64", "mips64le", "riscv64", "s390x":
		t.Skipf("skipping on %s, issue 19160", runtime.GOARCH)
	}
	testDisasm(t, false, true)
}

func TestDisasmExtld(t *testing.T) {
	switch runtime.GOOS {
	case "plan9", "windows":
		t.Skipf("skipping on %s", runtime.GOOS)
	}
	switch runtime.GOARCH {
	case "ppc64":
		t.Skipf("skipping on %s, no support for external linking, issue 9038", runtime.GOARCH)
	case "mips64", "mips64le", "mips", "mipsle":
		t.Skipf("skipping on %s, issue 12559 and 12560", runtime.GOARCH)
	case "riscv64":
		t.Skipf("skipping on %s, no support for external linking, issue 36739", runtime.GOARCH)
	case "s390x":
		t.Skipf("skipping on %s, issue 15255", runtime.GOARCH)
	}
	if !build.Default.CgoEnabled {
		t.Skip("skipping because cgo is not enabled")
	}
	testDisasm(t, false, false, "-ldflags=-linkmode=external")
}

func TestDisasmGoobj(t *testing.T) {
	switch runtime.GOARCH {
	case "mips", "mipsle", "mips64", "mips64le":
		t.Skipf("skipping on %s, issue 12559", runtime.GOARCH)
	case "riscv64":
		t.Skipf("skipping on %s, issue 36738", runtime.GOARCH)
	case "s390x":
		t.Skipf("skipping on %s, issue 15255", runtime.GOARCH)
	}

	hello := filepath.Join(tmp, "hello.o")
	args := []string{"tool", "compile", "-o", hello}
	args = append(args, "testdata/fmthello.go")
	out, err := exec.Command(testenv.GoToolPath(t), args...).CombinedOutput()
	if err != nil {
		t.Fatalf("go tool compile fmthello.go: %v\n%s", err, out)
	}
	need := []string{
		"main(SB)",
		"fmthello.go:6",
	}

	args = []string{
		"-s", "main",
		hello,
	}

	out, err = exec.Command(exe, args...).CombinedOutput()
	if err != nil {
		t.Fatalf("objdump fmthello.o: %v\n%s", err, out)
	}

	text := string(out)
	ok := true
	for _, s := range need {
		if !strings.Contains(text, s) {
			t.Errorf("disassembly missing '%s'", s)
			ok = false
		}
	}
	if runtime.GOARCH == "386" {
		if strings.Contains(text, "(IP)") {
			t.Errorf("disassembly contains PC-Relative addressing on 386")
			ok = false
		}
	}
	if !ok {
		t.Logf("full disassembly:\n%s", text)
	}
}
<|endoftext|>


<commit_before>// Package document provides the structures for Goose wiki pages (Document) and
// an abstraction over their storage (DocumentStore). It also provides various
// utility functions for the manipulation of Documents.
package document

import (
	"fmt"
	"time"
)

// MAX_CONTENT_SIZE is the maximum size of a Goose Document's Content in bytes.
// A Document's Content must be less than this length.
const MAX_CONTENT_SIZE = 512 * 1024

// DocumentStore represents Goose's Document storage backend.
//
// DocumentStore stores Documents in a Name-addressable store with immutable
// versions. Modifying a given Document never edits or overwrites the current
// version - instead, a new version is always created. Versions are identified
// and ordered by timestamp. For maintenance, a Document can be reverted to an
// older version (discarding newer ones), or its history can be truncated to a
// certain version to save space (discarding older ones).
//
// Several methods of the DocumentStore can return errors. Some errors are
// implementation-agnostic (eg NotFoundError) and all implementations must use
// these errors as noted. Any other error value has implementation-specific
// meaning and can appear at any time, in which case all other return values
// have implementation-specific meaning as well.
//
// DocumentStore requires eventual consistency across a single instance and
// across all copies thereof. Read-your-writes consistency is useful (required
// for tests) but not mandatory. Because Documents are immutable, there should
// be no conflicting writes.
//
// In the event of a timestamp collision, DocumentStore is expected to preserve
// both versions. If those versions are the most recent, then the behavior of
// Get is implementation-specific, but should be deterministic (ie a Get can
// return either version, but it should be the same version every time) and
// should match the behavior of GetAll (ie whichever version is returned by Get
// should also be the first version returned by GetAll).
//
// DocumentStores should be safe for concurrent access across goroutines.
// Instance-wide locking is an acceptable solution, since DocumentStores can be
// copied. Locking across copies should be avoided if possible.
//
// DocumentStore methods are expected to be synchronous, and block until the
// method behavior is completed. The meaning of "completed" depends on the
// implementation, however. For example, if Updating a Document to a replicated
// database cluster, you could return without waiting for any write ack, or
// waiting for a primary ack, or waiting for a majority ack. Depending on the
// implementation, any of these could be "complete".
//
// Because Document Timestamps require single-second precision, DocumentStore
// implementations must also support at least that much precision when storing
// and comparing timestamps.
//
// The Documents returned by a DocumentStore must be valid, satisfying all the
// guarantees defined for Document.
type DocumentStore interface {
	// Returns the Document specified by name, at its newest version.
	//
	// If the name is invalid, the error return must be a non-nil
	// document.InvalidNameError. If the Document does not exist, the error
	// return must be a non-nil document.NotFoundError. In either case, the
	// returned Document has undefined value.
	Get(name string) (Document, error)

	// Returns all versions of the Document specified by name, in order from
	// newest (index 0) to oldest (index n-1).
	//
	// If the name is invalid, the error return must be a non-nil
	// document.InvalidNameError. If the Document does not exist, the error
	// return must be a non-nil document.NotFoundError. In either case, the
	// returned slice must be empty.
	GetAll(name string) ([]Document, error)

	// Creates a new version of the Document specified by name, containing the
	// specified content. The Timestamp of the created version is determined by
	// the DocumentStore.
	//
	// Update can be invoked for Documents that do not exist, in which case the
	// first version is created. Update never modifies an old version of an
	// existing Document.
	//
	// A DocumentStore must support at least document.MAX_CONTENT_SIZE bytes of
	// content as an argument to this function. Passing a larger string must
	// return a non-nil ContentTooLargeError.
	//
	// If the name is invalid, the error return must be a non-nil
	// document.InvalidNameError.
	Update(name, content string) error

	// Clear deletes all versions of all Documents in this DocumentStore. This
	// operation is highly destructive, and is primarily intended for tests.
	Clear() error

	// Returns a new DocumentStore instance that uses the same underlying
	// storage as the receiver. Copying a DocumentStore should be a lightweight
	// operation.
	//
	// The receiver and return value must have the same implementation type. In
	// addition, they should share the actual data and be eventually consistent
	// with each other. For example, if the receiver is using a connection to a
	// MongoDB replica set, its copies should use the same replica set, but
	// possibly with different TCP connections, or to different members of the
	// set.
	//
	// The interface does not impose a limit on the number of copies allowed.
	// Implementations may define their own limit, and impose it via an error
	// return when the limit is exceeded.
	Copy() (DocumentStore, error)

	// Closes the DocumentStore, allowing it to release any internal resources.
	//
	// After Closing a DocumentStore, any further DocumentStore method calls
	// must return a non-nil ClosedError.
	//
	// Closing a DocumentStore that is already Closed should have no effect.
	Close()
}

// Document represents a single version of a single Goose wiki page. Every page
// is UTF-8 Markdown. The storage and persistence of Documents is handled by a
// DocumentStore.
type Document struct {
	// Name is the key by which this Document can be retrieved from its
	// DocumentStore. A Name is unique across a DocumentStore instance and its
	// copies (ignoring versions).
	//
	// The format of Name is a nonempty sequence of segments. Each segment
	// consists of a slash, followed by at least one non-slash printable ASCII
	// character (ie any character in the range \x20-\x2E or \x30-\x7E).
	// Furthermore, a segment may not be the strings "/." or "/..".
	//
	// The Name is part of the URL used to access the Document. However, do not
	// %-encode the characters of the Name. In addition, the last slash-
	// separated segment in the string is the Document's title.
	Name string

	// Content is the body of the Document, in UTF-8 Markdown. See package
	// github.com/tummychow/goose/markdown for details on the expected syntax.
	//
	// The maximum size of a valid Content string is document.MAX_CONTENT_SIZE.
	// There are no other technical restrictions on this variable.
	Content string

	// Timestamp is the time at which this Document was added to its
	// DocumentStore. It must be a UTC timestamp with at least single-second
	// precision.
	//
	// The actual meaning of Timestamp is implementation-specific. It depends
	// on the DocumentStore from which this Document originated. In general, it
	// approximately reflects when Update was called to add this Document to
	// the DocumentStore.
	Timestamp time.Time
}

// NotFoundError is the error returned by a DocumentStore when an operation is
// attempted against a Document that does not exist.
type NotFoundError struct {
	// Name is the Name of the nonexistent Document that caused the error.
	Name string
}

func (e NotFoundError) Error() string {
	return fmt.Sprintf("goose/document: document %q not found", e.Name)
}

// InvalidNameError is the error returned by a DocumentStore when an operation
// is invoked with a Document Name that is not actually valid. (Refer to
// Document.Name for details on what constitutes a valid Name.)
type InvalidNameError struct {
	Name string
}

func (e InvalidNameError) Error() string {
	return fmt.Sprintf("goose/document: name %q is invalid", e.Name)
}

// ClosedError is the error returned when a method call is invoked on a
// DocumentStore that was already closed.
type ClosedError string

func (e ClosedError) Error() string {
	return string(e)
}

// ContentTooLargeError is the error returned when a Document has too much
// content, exceeding document.MAX_CONTENT_SIZE.
type ContentTooLargeError struct {
	Size int
}

func (e ContentTooLargeError) Error() string {
	return fmt.Sprintf("goose/document: content is %v bytes (%v bytes too long)", e.Size, e.Size-MAX_CONTENT_SIZE)
}
<commit_msg>Clarify Update MAX_CONTENT_SIZE<commit_after>// Package document provides the structures for Goose wiki pages (Document) and
// an abstraction over their storage (DocumentStore). It also provides various
// utility functions for the manipulation of Documents.
package document

import (
	"fmt"
	"time"
)

// MAX_CONTENT_SIZE is the maximum size of a Goose Document's Content in bytes.
// A Document's Content must be less than this length.
const MAX_CONTENT_SIZE = 512 * 1024

// DocumentStore represents Goose's Document storage backend.
//
// DocumentStore stores Documents in a Name-addressable store with immutable
// versions. Modifying a given Document never edits or overwrites the current
// version - instead, a new version is always created. Versions are identified
// and ordered by timestamp. For maintenance, a Document can be reverted to an
// older version (discarding newer ones), or its history can be truncated to a
// certain version to save space (discarding older ones).
//
// Several methods of the DocumentStore can return errors. Some errors are
// implementation-agnostic (eg NotFoundError) and all implementations must use
// these errors as noted. Any other error value has implementation-specific
// meaning and can appear at any time, in which case all other return values
// have implementation-specific meaning as well.
//
// DocumentStore requires eventual consistency across a single instance and
// across all copies thereof. Read-your-writes consistency is useful (required
// for tests) but not mandatory. Because Documents are immutable, there should
// be no conflicting writes.
//
// In the event of a timestamp collision, DocumentStore is expected to preserve
// both versions. If those versions are the most recent, then the behavior of
// Get is implementation-specific, but should be deterministic (ie a Get can
// return either version, but it should be the same version every time) and
// should match the behavior of GetAll (ie whichever version is returned by Get
// should also be the first version returned by GetAll).
//
// DocumentStores should be safe for concurrent access across goroutines.
// Instance-wide locking is an acceptable solution, since DocumentStores can be
// copied. Locking across copies should be avoided if possible.
//
// DocumentStore methods are expected to be synchronous, and block until the
// method behavior is completed. The meaning of "completed" depends on the
// implementation, however. For example, if Updating a Document to a replicated
// database cluster, you could return without waiting for any write ack, or
// waiting for a primary ack, or waiting for a majority ack. Depending on the
// implementation, any of these could be "complete".
//
// Because Document Timestamps require single-second precision, DocumentStore
// implementations must also support at least that much precision when storing
// and comparing timestamps.
//
// The Documents returned by a DocumentStore must be valid, satisfying all the
// guarantees defined for Document.
type DocumentStore interface {
	// Returns the Document specified by name, at its newest version.
	//
	// If the name is invalid, the error return must be a non-nil
	// document.InvalidNameError. If the Document does not exist, the error
	// return must be a non-nil document.NotFoundError. In either case, the
	// returned Document has undefined value.
	Get(name string) (Document, error)

	// Returns all versions of the Document specified by name, in order from
	// newest (index 0) to oldest (index n-1).
	//
	// If the name is invalid, the error return must be a non-nil
	// document.InvalidNameError. If the Document does not exist, the error
	// return must be a non-nil document.NotFoundError. In either case, the
	// returned slice must be empty.
	GetAll(name string) ([]Document, error)

	// Creates a new version of the Document specified by name, containing the
	// specified content. The Timestamp of the created version is determined by
	// the DocumentStore.
	//
	// Update can be invoked for Documents that do not exist, in which case the
	// first version is created. Update never modifies an old version of an
	// existing Document.
	//
	// A DocumentStore must support at least document.MAX_CONTENT_SIZE bytes of
	// content as an argument to this function. Passing a larger string may
	// return a non-nil ContentTooLargeError. However, if a DocumentStore does
	// accept content over that size, it must also be capable of retrieving and
	// returning that content. A DocumentStore should not truncate content over
	// the size limit.
	//
	// If the name is invalid, the error return must be a non-nil
	// document.InvalidNameError.
	Update(name, content string) error

	// Clear deletes all versions of all Documents in this DocumentStore. This
	// operation is highly destructive, and is primarily intended for tests.
	Clear() error

	// Returns a new DocumentStore instance that uses the same underlying
	// storage as the receiver. Copying a DocumentStore should be a lightweight
	// operation.
	//
	// The receiver and return value must have the same implementation type. In
	// addition, they should share the actual data and be eventually consistent
	// with each other. For example, if the receiver is using a connection to a
	// MongoDB replica set, its copies should use the same replica set, but
	// possibly with different TCP connections, or to different members of the
	// set.
	//
	// The interface does not impose a limit on the number of copies allowed.
	// Implementations may define their own limit, and impose it via an error
	// return when the limit is exceeded.
	Copy() (DocumentStore, error)

	// Closes the DocumentStore, allowing it to release any internal resources.
	//
	// After Closing a DocumentStore, any further DocumentStore method calls
	// must return a non-nil ClosedError.
	//
	// Closing a DocumentStore that is already Closed should have no effect.
	Close()
}

// Document represents a single version of a single Goose wiki page. Every page
// is UTF-8 Markdown. The storage and persistence of Documents is handled by a
// DocumentStore.
type Document struct {
	// Name is the key by which this Document can be retrieved from its
	// DocumentStore. A Name is unique across a DocumentStore instance and its
	// copies (ignoring versions).
	//
	// The format of Name is a nonempty sequence of segments. Each segment
	// consists of a slash, followed by at least one non-slash printable ASCII
	// character (ie any character in the range \x20-\x2E or \x30-\x7E).
	// Furthermore, a segment may not be the strings "/." or "/..".
	//
	// The Name is part of the URL used to access the Document. However, do not
	// %-encode the characters of the Name. In addition, the last slash-
	// separated segment in the string is the Document's title.
	Name string

	// Content is the body of the Document, in UTF-8 Markdown. See package
	// github.com/tummychow/goose/markdown for details on the expected syntax.
	//
	// The maximum size of a valid Content string is document.MAX_CONTENT_SIZE.
	// There are no other technical restrictions on this variable.
	Content string

	// Timestamp is the time at which this Document was added to its
	// DocumentStore. It must be a UTC timestamp with at least single-second
	// precision.
	//
	// The actual meaning of Timestamp is implementation-specific. It depends
	// on the DocumentStore from which this Document originated. In general, it
	// approximately reflects when Update was called to add this Document to
	// the DocumentStore.
	Timestamp time.Time
}

// NotFoundError is the error returned by a DocumentStore when an operation is
// attempted against a Document that does not exist.
type NotFoundError struct {
	// Name is the Name of the nonexistent Document that caused the error.
	Name string
}

func (e NotFoundError) Error() string {
	return fmt.Sprintf("goose/document: document %q not found", e.Name)
}

// InvalidNameError is the error returned by a DocumentStore when an operation
// is invoked with a Document Name that is not actually valid. (Refer to
// Document.Name for details on what constitutes a valid Name.)
type InvalidNameError struct {
	Name string
}

func (e InvalidNameError) Error() string {
	return fmt.Sprintf("goose/document: name %q is invalid", e.Name)
}

// ClosedError is the error returned when a method call is invoked on a
// DocumentStore that was already closed.
type ClosedError string

func (e ClosedError) Error() string {
	return string(e)
}

// ContentTooLargeError is the error returned when a Document has too much
// content, exceeding document.MAX_CONTENT_SIZE.
type ContentTooLargeError struct {
	Size int
}

func (e ContentTooLargeError) Error() string {
	return fmt.Sprintf("goose/document: content is %v bytes (%v bytes too long)", e.Size, e.Size-MAX_CONTENT_SIZE)
}
<|endoftext|>


<commit_before>// Copyright 2015 Ethan Miller. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package editor

import (
	"errors"

	"github.com/millere/jk/keys"
)

// A ModeFunc is a function that can be executed by a keypress in a mode
type ModeFunc func(v *View, count int) error

// A Mode is used to map keypresses into events in the editor
type Mode struct {
	OnEnter  func(v *View) error
	OnExit   func(v *View) error
	EventMap map[keys.Keypress]ModeFunc
}

// Normal returns a simple normal mode for testing
func Normal() Mode {
	m := make(map[keys.Keypress]ModeFunc)
	m[keys.Keypress{Key: 'h'}] = func(v *View, count int) error {
		v.MoveCursor(-1, 0)
		return nil
	}
	m[keys.Keypress{Key: 'n'}] = func(v *View, count int) error {
		v.MoveCursor(0, 1)
		return nil
	}
	m[keys.Keypress{Key: 'e'}] = func(v *View, count int) error {
		v.MoveCursor(0, -1)
		return nil
	}
	m[keys.Keypress{Key: 'i'}] = func(v *View, count int) error {
		v.MoveCursor(1, 0)
		return nil
	}
	m[keys.Keypress{Key: keys.Esc}] = func(v *View, count int) error {
		return errors.New("Should quit")
	}
	m[keys.Keypress{Key: 't'}] = func(v *View, count int) error {
		v.SetMode((*v.modes)["insert"])
		return nil
	}

	return Mode{
		OnEnter:  nil,
		OnExit:   nil,
		EventMap: m,
	}
}

func Insert() Mode {
	m := make(map[keys.Keypress]ModeFunc)
	m[keys.Keypress{Key: keys.Esc}] = func(v *View, count int) error {
		v.SetMode((*v.modes)["normal"])
		return nil
	}

	insertable := []rune{}
	for c := rune(0x20); c <= 0x7E; c++ {
		insertable = append(insertable, c)
	}
	LogItAll.Println("Adding insertables:", insertable)

	for _, c := range insertable {
		cc := c
		m[keys.Keypress{Key: cc}] = func(v *View, count int) error {
			for i := 0; i < count; i++ {
				v.InsertChar(cc)
				v.MoveCursor(1, 0)
			}
			return nil
		}
	}
	return Mode{
		OnEnter:  nil,
		OnExit:   nil,
		EventMap: m,
	}
}

// RegisterMode registers a mode for use in the editor with a name to be referred to as
func (e *Editor) RegisterMode(name string, mode Mode) {
	e.Log("Adding mode:", name)
	e.modes[name] = &mode
	e.Log("Modes:", e.modes)
}
<commit_msg>Remove debug print<commit_after>// Copyright 2015 Ethan Miller. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package editor

import (
	"errors"

	"github.com/millere/jk/keys"
)

// A ModeFunc is a function that can be executed by a keypress in a mode
type ModeFunc func(v *View, count int) error

// A Mode is used to map keypresses into events in the editor
type Mode struct {
	OnEnter  func(v *View) error
	OnExit   func(v *View) error
	EventMap map[keys.Keypress]ModeFunc
}

// Normal returns a simple normal mode for testing
func Normal() Mode {
	m := make(map[keys.Keypress]ModeFunc)
	m[keys.Keypress{Key: 'h'}] = func(v *View, count int) error {
		v.MoveCursor(-1, 0)
		return nil
	}
	m[keys.Keypress{Key: 'n'}] = func(v *View, count int) error {
		v.MoveCursor(0, 1)
		return nil
	}
	m[keys.Keypress{Key: 'e'}] = func(v *View, count int) error {
		v.MoveCursor(0, -1)
		return nil
	}
	m[keys.Keypress{Key: 'i'}] = func(v *View, count int) error {
		v.MoveCursor(1, 0)
		return nil
	}
	m[keys.Keypress{Key: keys.Esc}] = func(v *View, count int) error {
		return errors.New("Should quit")
	}
	m[keys.Keypress{Key: 't'}] = func(v *View, count int) error {
		v.SetMode((*v.modes)["insert"])
		return nil
	}

	return Mode{
		OnEnter:  nil,
		OnExit:   nil,
		EventMap: m,
	}
}

func Insert() Mode {
	m := make(map[keys.Keypress]ModeFunc)
	m[keys.Keypress{Key: keys.Esc}] = func(v *View, count int) error {
		v.SetMode((*v.modes)["normal"])
		return nil
	}

	insertable := []rune{}
	for c := rune(0x20); c <= 0x7E; c++ {
		insertable = append(insertable, c)
	}

	for _, c := range insertable {
		cc := c
		m[keys.Keypress{Key: cc}] = func(v *View, count int) error {
			for i := 0; i < count; i++ {
				v.InsertChar(cc)
				v.MoveCursor(1, 0)
			}
			return nil
		}
	}
	return Mode{
		OnEnter:  nil,
		OnExit:   nil,
		EventMap: m,
	}
}

// RegisterMode registers a mode for use in the editor with a name to be referred to as
func (e *Editor) RegisterMode(name string, mode Mode) {
	e.Log("Adding mode:", name)
	e.modes[name] = &mode
	e.Log("Modes:", e.modes)
}
<|endoftext|>


<commit_before>package fuse

import (
	"log"
	"sync"
)

var _ = log.Println

// The inode reflects the kernel's idea of the inode.  Inodes may be
// created automatically when the kernel does lookups inode, or by
// explicitly by calling Inode.CreateChild().
type Inode struct {
	handled Handled

	// Number of open files and its protection.
	openFilesMutex sync.Mutex
	openFiles      []*openedFile

	fsInode FsNode

	// Each inode belongs to exactly one fileSystemMount. This
	// pointer is constant during the lifetime, except upon
	// Unmount() when it is set to nil.
	mount *fileSystemMount

	// treeLock is a pointer to me.mount.treeLock.  We store it
	// here for convenience.  Constant during lifetime of the
	// inode.
	//
	// If multiple treeLocks must be acquired, the treeLocks
	// closer to the root must be acquired first.
	treeLock *sync.RWMutex

	// All data below is protected by treeLock.
	children map[string]*Inode

	// Contains directories that function as mounts. The entries
	// are duplicated in children.
	mounts map[string]*fileSystemMount

	// The nodeId is only used to communicate to the kernel.  If
	// it is zero, it means the kernel does not know about this
	// Inode.  You should probably never read nodeId, but always
	// do lookupUpdate() on the node instead.
	nodeId uint64

	// lookupCount registers how often the kernel got this inode
	// back for a Lookup operation. This number is a reference
	// count, and the Forget operation lists how many references to drop.
	//
	// The lookupCount is exclusively used for managing the
	// lifetime of nodeId variable.  It is ok for a node to have 0
	// == lookupCount.  This can happen if the inode return false
	// for Deletable().
	lookupCount int

	// Non-nil if this inode is a mountpoint, ie. the Root of a
	// NodeFileSystem.
	mountPoint *fileSystemMount
}

func newInode(isDir bool, fsNode FsNode) *Inode {
	me := new(Inode)
	if isDir {
		me.children = make(map[string]*Inode, initDirSize)
	}
	me.fsInode = fsNode
	me.fsInode.SetInode(me)
	return me
}

// public methods.

// Returns any open file, preferably a r/w one.
func (me *Inode) AnyFile() (file File) {
	me.openFilesMutex.Lock()
	defer me.openFilesMutex.Unlock()

	for _, f := range me.openFiles {
		if file == nil || f.WithFlags.OpenFlags&O_ANYWRITE != 0 {
			file = f.WithFlags.File
		}
	}
	return file
}

func (me *Inode) Children() (out map[string]*Inode) {
	me.treeLock.RLock()
	defer me.treeLock.RUnlock()

	out = map[string]*Inode{}
	for k, v := range me.children {
		out[k] = v
	}
	return out
}

// FsChildren returns all the children from the same filesystem.  It
// will skip mountpoints.
func (me *Inode) FsChildren() (out map[string]*Inode) {
	me.treeLock.RLock()
	defer me.treeLock.RUnlock()

	out = map[string]*Inode{}
	for k, v := range me.children {
		if v.mount == me.mount {
			out[k] = v
		}
	}
	return out
}

func (me *Inode) FsNode() FsNode {
	return me.fsInode
}

// Files() returns an opens file that have bits in common with the
// give mask.  Use mask==0 to return all files.
func (me *Inode) Files(mask uint32) (files []WithFlags) {
	me.openFilesMutex.Lock()
	defer me.openFilesMutex.Unlock()
	for _, f := range me.openFiles {
		if mask == 0 || f.WithFlags.OpenFlags&mask != 0 {
			files = append(files, f.WithFlags)
		}
	}
	return files
}

func (me *Inode) IsDir() bool {
	return me.children != nil
}

func (me *Inode) New(isDir bool, fsi FsNode) *Inode {
	ch := newInode(isDir, fsi)
	ch.mount = me.mount
	ch.treeLock = me.treeLock
	return ch
}

func (me *Inode) GetChild(name string) (child *Inode) {
	me.treeLock.RLock()
	defer me.treeLock.RUnlock()

	return me.children[name]
}

func (me *Inode) AddChild(name string, child *Inode) {
	if child == nil {
		log.Panicf("adding nil child as %q", name)
	}
	me.treeLock.Lock()
	defer me.treeLock.Unlock()
	me.addChild(name, child)
}

func (me *Inode) RmChild(name string) (ch *Inode) {
	me.treeLock.Lock()
	defer me.treeLock.Unlock()
	return me.rmChild(name)
}

//////////////////////////////////////////////////////////////
// private

// Must be called with treeLock for the mount held.
func (me *Inode) addChild(name string, child *Inode) {
	if paranoia {
		ch := me.children[name]
		if ch != nil {
			log.Panicf("Already have an Inode with same name: %v: %v", name, ch)
		}
	}
	me.children[name] = child
}

// Must be called with treeLock for the mount held.
func (me *Inode) rmChild(name string) (ch *Inode) {
	ch = me.children[name]
	if ch != nil {
		delete(me.children, name)
	}
	return ch
}

// Can only be called on untouched inodes.
func (me *Inode) mountFs(fs NodeFileSystem, opts *FileSystemOptions) {
	me.mountPoint = &fileSystemMount{
		fs:         fs,
		openFiles:  NewHandleMap(false),
		mountInode: me,
		options:    opts,
	}
	me.mount = me.mountPoint
	me.treeLock = &me.mountPoint.treeLock
}

// Must be called with treeLock held.
func (me *Inode) canUnmount() bool {
	for _, v := range me.children {
		if v.mountPoint != nil {
			// This access may be out of date, but it is no
			// problem to err on the safe side.
			return false
		}
		if !v.canUnmount() {
			return false
		}
	}

	me.openFilesMutex.Lock()
	defer me.openFilesMutex.Unlock()
	return len(me.openFiles) == 0
}

func (me *Inode) getMountDirEntries() (out []DirEntry) {
	me.treeLock.RLock()
	defer me.treeLock.RUnlock()

	for k := range me.mounts {
		out = append(out, DirEntry{
			Name: k,
			Mode: S_IFDIR,
		})
	}
	return out
}

const initDirSize = 20

func (me *Inode) verify(cur *fileSystemMount) {
	if me.lookupCount < 0 {
		log.Panicf("negative lookup count %d on node %d", me.lookupCount, me.nodeId)
	}
	if (me.lookupCount == 0) != (me.nodeId == 0) {
		log.Panicf("kernel registration mismatch: lookup %d id %d", me.lookupCount, me.nodeId)
	}
	if me.mountPoint != nil {
		if me != me.mountPoint.mountInode {
			log.Panicf("mountpoint mismatch %v %v", me, me.mountPoint.mountInode)
		}
		cur = me.mountPoint
	}
	if me.mount != cur {
		log.Panicf("me.mount not set correctly %v %v", me.mount, cur)
	}

	for name, m := range me.mounts {
		if m.mountInode != me.children[name] {
			log.Panicf("mountpoint parent mismatch: node:%v name:%v ch:%v",
				me.mountPoint, name, me.children)
		}
	}

	for n, ch := range me.children {
		if ch == nil {
			log.Panicf("Found nil child: %q", n)
		}
		ch.verify(cur)
	}
}
<commit_msg>Fix comment.<commit_after>package fuse

import (
	"log"
	"sync"
)

var _ = log.Println

// The inode reflects the kernel's idea of the inode.  Inodes may be
// created automatically when the kernel does lookups inode, or by
// explicitly by calling Inode.New().
type Inode struct {
	handled Handled

	// Number of open files and its protection.
	openFilesMutex sync.Mutex
	openFiles      []*openedFile

	fsInode FsNode

	// Each inode belongs to exactly one fileSystemMount. This
	// pointer is constant during the lifetime, except upon
	// Unmount() when it is set to nil.
	mount *fileSystemMount

	// treeLock is a pointer to me.mount.treeLock.  We store it
	// here for convenience.  Constant during lifetime of the
	// inode.
	//
	// If multiple treeLocks must be acquired, the treeLocks
	// closer to the root must be acquired first.
	treeLock *sync.RWMutex

	// All data below is protected by treeLock.
	children map[string]*Inode

	// Contains directories that function as mounts. The entries
	// are duplicated in children.
	mounts map[string]*fileSystemMount

	// The nodeId is only used to communicate to the kernel.  If
	// it is zero, it means the kernel does not know about this
	// Inode.  You should probably never read nodeId, but always
	// do lookupUpdate() on the node instead.
	nodeId uint64

	// lookupCount registers how often the kernel got this inode
	// back for a Lookup operation. This number is a reference
	// count, and the Forget operation lists how many references to drop.
	//
	// The lookupCount is exclusively used for managing the
	// lifetime of nodeId variable.  It is ok for a node to have 0
	// == lookupCount.  This can happen if the inode return false
	// for Deletable().
	lookupCount int

	// Non-nil if this inode is a mountpoint, ie. the Root of a
	// NodeFileSystem.
	mountPoint *fileSystemMount
}

func newInode(isDir bool, fsNode FsNode) *Inode {
	me := new(Inode)
	if isDir {
		me.children = make(map[string]*Inode, initDirSize)
	}
	me.fsInode = fsNode
	me.fsInode.SetInode(me)
	return me
}

// public methods.

// Returns any open file, preferably a r/w one.
func (me *Inode) AnyFile() (file File) {
	me.openFilesMutex.Lock()
	defer me.openFilesMutex.Unlock()

	for _, f := range me.openFiles {
		if file == nil || f.WithFlags.OpenFlags&O_ANYWRITE != 0 {
			file = f.WithFlags.File
		}
	}
	return file
}

func (me *Inode) Children() (out map[string]*Inode) {
	me.treeLock.RLock()
	defer me.treeLock.RUnlock()

	out = map[string]*Inode{}
	for k, v := range me.children {
		out[k] = v
	}
	return out
}

// FsChildren returns all the children from the same filesystem.  It
// will skip mountpoints.
func (me *Inode) FsChildren() (out map[string]*Inode) {
	me.treeLock.RLock()
	defer me.treeLock.RUnlock()

	out = map[string]*Inode{}
	for k, v := range me.children {
		if v.mount == me.mount {
			out[k] = v
		}
	}
	return out
}

func (me *Inode) FsNode() FsNode {
	return me.fsInode
}

// Files() returns an opens file that have bits in common with the
// give mask.  Use mask==0 to return all files.
func (me *Inode) Files(mask uint32) (files []WithFlags) {
	me.openFilesMutex.Lock()
	defer me.openFilesMutex.Unlock()
	for _, f := range me.openFiles {
		if mask == 0 || f.WithFlags.OpenFlags&mask != 0 {
			files = append(files, f.WithFlags)
		}
	}
	return files
}

func (me *Inode) IsDir() bool {
	return me.children != nil
}

func (me *Inode) New(isDir bool, fsi FsNode) *Inode {
	ch := newInode(isDir, fsi)
	ch.mount = me.mount
	ch.treeLock = me.treeLock
	return ch
}

func (me *Inode) GetChild(name string) (child *Inode) {
	me.treeLock.RLock()
	defer me.treeLock.RUnlock()

	return me.children[name]
}

func (me *Inode) AddChild(name string, child *Inode) {
	if child == nil {
		log.Panicf("adding nil child as %q", name)
	}
	me.treeLock.Lock()
	defer me.treeLock.Unlock()
	me.addChild(name, child)
}

func (me *Inode) RmChild(name string) (ch *Inode) {
	me.treeLock.Lock()
	defer me.treeLock.Unlock()
	return me.rmChild(name)
}

//////////////////////////////////////////////////////////////
// private

// Must be called with treeLock for the mount held.
func (me *Inode) addChild(name string, child *Inode) {
	if paranoia {
		ch := me.children[name]
		if ch != nil {
			log.Panicf("Already have an Inode with same name: %v: %v", name, ch)
		}
	}
	me.children[name] = child
}

// Must be called with treeLock for the mount held.
func (me *Inode) rmChild(name string) (ch *Inode) {
	ch = me.children[name]
	if ch != nil {
		delete(me.children, name)
	}
	return ch
}

// Can only be called on untouched inodes.
func (me *Inode) mountFs(fs NodeFileSystem, opts *FileSystemOptions) {
	me.mountPoint = &fileSystemMount{
		fs:         fs,
		openFiles:  NewHandleMap(false),
		mountInode: me,
		options:    opts,
	}
	me.mount = me.mountPoint
	me.treeLock = &me.mountPoint.treeLock
}

// Must be called with treeLock held.
func (me *Inode) canUnmount() bool {
	for _, v := range me.children {
		if v.mountPoint != nil {
			// This access may be out of date, but it is no
			// problem to err on the safe side.
			return false
		}
		if !v.canUnmount() {
			return false
		}
	}

	me.openFilesMutex.Lock()
	defer me.openFilesMutex.Unlock()
	return len(me.openFiles) == 0
}

func (me *Inode) getMountDirEntries() (out []DirEntry) {
	me.treeLock.RLock()
	defer me.treeLock.RUnlock()

	for k := range me.mounts {
		out = append(out, DirEntry{
			Name: k,
			Mode: S_IFDIR,
		})
	}
	return out
}

const initDirSize = 20

func (me *Inode) verify(cur *fileSystemMount) {
	if me.lookupCount < 0 {
		log.Panicf("negative lookup count %d on node %d", me.lookupCount, me.nodeId)
	}
	if (me.lookupCount == 0) != (me.nodeId == 0) {
		log.Panicf("kernel registration mismatch: lookup %d id %d", me.lookupCount, me.nodeId)
	}
	if me.mountPoint != nil {
		if me != me.mountPoint.mountInode {
			log.Panicf("mountpoint mismatch %v %v", me, me.mountPoint.mountInode)
		}
		cur = me.mountPoint
	}
	if me.mount != cur {
		log.Panicf("me.mount not set correctly %v %v", me.mount, cur)
	}

	for name, m := range me.mounts {
		if m.mountInode != me.children[name] {
			log.Panicf("mountpoint parent mismatch: node:%v name:%v ch:%v",
				me.mountPoint, name, me.children)
		}
	}

	for n, ch := range me.children {
		if ch == nil {
			log.Panicf("Found nil child: %q", n)
		}
		ch.verify(cur)
	}
}
<|endoftext|>