4 Commits

Author SHA1 Message Date
Aine
bf89b8fe1b fix auth; update deps 2024-07-03 12:21:47 +03:00
Aine
f91691bc7c update deps 2024-05-19 15:13:18 +03:00
Aine
cfe31d89b9 update deps 2024-05-16 23:05:21 +03:00
Aine
ea1533acae add !pm relay - per-mailbox relay config 2024-05-02 11:28:37 +03:00
234 changed files with 369919 additions and 173572 deletions

View File

@@ -63,7 +63,6 @@ linters-settings:
time-layout: true time-layout: true
crypto-hash: true crypto-hash: true
default-rpc-path: true default-rpc-path: true
os-dev-null: true
sql-isolation-level: true sql-isolation-level: true
tls-signature-scheme: true tls-signature-scheme: true
constant-kind: true constant-kind: true
@@ -86,7 +85,6 @@ linters:
- errchkjson - errchkjson
- errname - errname
- errorlint - errorlint
- execinquery
- exhaustive - exhaustive
- exportloopref - exportloopref
- forcetypeassert - forcetypeassert

View File

@@ -35,6 +35,7 @@ so you can use it to send emails from your apps and scripts as well.
- [x] SMTP client - [x] SMTP client
- [x] SMTP server (you can use Postmoogle as general purpose SMTP server to send emails from your scripts or apps) - [x] SMTP server (you can use Postmoogle as general purpose SMTP server to send emails from your scripts or apps)
- [x] SMTP Relaying (postmoogle can send emails via relay host), global and per-mailbox
- [x] Send a message to matrix room with special format to send a new email, even to multiple email addresses at once - [x] Send a message to matrix room with special format to send a new email, even to multiple email addresses at once
- [x] Reply to matrix thread sends reply into email thread - [x] Reply to matrix thread sends reply into email thread
- [x] Email signatures - [x] Email signatures
@@ -76,10 +77,10 @@ env vars
* **POSTMOOGLE_MAILBOXES_ACTIVATION** - activation flow for new mailboxes, [docs/mailboxes.md](docs/mailboxes.md) * **POSTMOOGLE_MAILBOXES_ACTIVATION** - activation flow for new mailboxes, [docs/mailboxes.md](docs/mailboxes.md)
* **POSTMOOGLE_MAXSIZE** - max email size (including attachments) in megabytes * **POSTMOOGLE_MAXSIZE** - max email size (including attachments) in megabytes
* **POSTMOOGLE_ADMINS** - a space-separated list of admin users. See `POSTMOOGLE_USERS` for syntax examples * **POSTMOOGLE_ADMINS** - a space-separated list of admin users. See `POSTMOOGLE_USERS` for syntax examples
* **POSTMOOGLE_RELAY_HOST** - SMTP hostname of relay host (e.g. Sendgrid) * **POSTMOOGLE_RELAY_HOST** - (global) SMTP hostname of relay host (e.g. Sendgrid)
* **POSTMOOGLE_RELAY_PORT** - SMTP port of relay host * **POSTMOOGLE_RELAY_PORT** - (global) SMTP port of relay host
* **POSTMOOGLE_RELAY_USERNAME** - Username of relay host * **POSTMOOGLE_RELAY_USERNAME** - (global) Username of relay host
* **POSTMOOGLE_RELAY_PASSWORD** - Password of relay host * **POSTMOOGLE_RELAY_PASSWORD** - (global) Password of relay host
You can find default values in [config/defaults.go](config/defaults.go) You can find default values in [config/defaults.go](config/defaults.go)
@@ -118,7 +119,7 @@ If you want to change them - check available options in the help message (`!pm h
* **`!pm domain`** - Get or set default domain of the room * **`!pm domain`** - Get or set default domain of the room
* **`!pm owner`** - Get or set owner of the room * **`!pm owner`** - Get or set owner of the room
* **`!pm password`** - Get or set SMTP password of the room's mailbox * **`!pm password`** - Get or set SMTP password of the room's mailbox
* **`!pm relay`** - Get or set SMTP relay of that mailbox. Format: `smtp://user:password@host:port`, e.g. `smtp://54b7bfb9-b95f-44b8-9879-9b560baf4e3a:8528a3a9-bea8-4583-9912-d4357ba565eb@example.com:587`
--- ---
#### mailbox options #### mailbox options

View File

@@ -3,6 +3,7 @@ package bot
import ( import (
"context" "context"
"fmt" "fmt"
"net/url"
"regexp" "regexp"
"sync" "sync"
@@ -36,7 +37,7 @@ type Bot struct {
commands commandList commands commandList
rooms sync.Map rooms sync.Map
proxies []string proxies []string
sendmail func(string, string, string) error sendmail func(string, string, string, *url.URL) error
psdc *psd.Client psdc *psd.Client
cfg *config.Manager cfg *config.Manager
log *zerolog.Logger log *zerolog.Logger

View File

@@ -103,6 +103,12 @@ func (b *Bot) initCommands() commandList {
description: "Get or set SMTP password of the room's mailbox", description: "Get or set SMTP password of the room's mailbox",
allowed: b.allowOwner, allowed: b.allowOwner,
}, },
{
key: config.RoomRelay,
description: "Configure SMTP Relay for that mailbox, format: `smtp://user:pass@host:port`",
sanitizer: utils.SanitizeURL,
allowed: b.allowOwner,
},
{allowed: b.allowOwner, description: "mailbox options"}, // delimiter {allowed: b.allowOwner, description: "mailbox options"}, // delimiter
{ {
key: config.RoomAutoreply, key: config.RoomAutoreply,
@@ -630,7 +636,7 @@ func (b *Bot) runSendCommand(ctx context.Context, cfg config.Room, tos []string,
b.lp.SendNotice(ctx, evt.RoomID, "email body is empty", linkpearl.RelatesTo(evt.ID, cfg.NoThreads())) b.lp.SendNotice(ctx, evt.RoomID, "email body is empty", linkpearl.RelatesTo(evt.ID, cfg.NoThreads()))
return return
} }
queued, err := b.Sendmail(ctx, evt.ID, from, to, data) queued, err := b.Sendmail(ctx, evt.ID, from, to, data, cfg.Relay())
if queued { if queued {
b.log.Warn().Err(err).Msg("email has been queued") b.log.Warn().Err(err).Msg("email has been queued")
b.saveSentMetadata(ctx, queued, evt.ID, to, eml, cfg) b.saveSentMetadata(ctx, queued, evt.ID, to, eml, cfg)

View File

@@ -51,6 +51,8 @@ func (b *Bot) handleOption(ctx context.Context, cmd []string) {
b.setMailbox(ctx, cmd[1]) b.setMailbox(ctx, cmd[1])
case config.RoomPassword: case config.RoomPassword:
b.setPassword(ctx) b.setPassword(ctx)
case config.RoomRelay:
b.setRelay(ctx)
default: default:
b.setOption(ctx, cmd[0], cmd[1]) b.setOption(ctx, cmd[0], cmd[1])
} }
@@ -152,6 +154,25 @@ func (b *Bot) setPassword(ctx context.Context) {
b.lp.SendNotice(ctx, evt.RoomID, "SMTP password has been set", linkpearl.RelatesTo(evt.ID, cfg.NoThreads())) b.lp.SendNotice(ctx, evt.RoomID, "SMTP password has been set", linkpearl.RelatesTo(evt.ID, cfg.NoThreads()))
} }
func (b *Bot) setRelay(ctx context.Context) {
evt := eventFromContext(ctx)
cfg, err := b.cfg.GetRoom(ctx, evt.RoomID)
if err != nil {
b.Error(ctx, "failed to retrieve settings: %v", err)
return
}
value := b.parseCommand(evt.Content.AsMessage().Body, false)[1] // get original value, without forced lower case
cfg.Set(config.RoomRelay, value)
err = b.cfg.SetRoom(ctx, evt.RoomID, cfg)
if err != nil {
b.Error(ctx, "cannot update settings: %v", err)
return
}
b.lp.SendNotice(ctx, evt.RoomID, "Relay config has been set", linkpearl.RelatesTo(evt.ID, cfg.NoThreads()))
}
func (b *Bot) setOption(ctx context.Context, name, value string) { func (b *Bot) setOption(ctx context.Context, name, value string) {
cmd := b.commands.get(name) cmd := b.commands.get(name)
if cmd != nil && cmd.sanitizer != nil { if cmd != nil && cmd.sanitizer != nil {

View File

@@ -1,8 +1,11 @@
package config package config
import ( import (
"fmt"
"net/url"
"strings" "strings"
"gitlab.com/etke.cc/go/healthchecks/v2"
"gitlab.com/etke.cc/postmoogle/email" "gitlab.com/etke.cc/postmoogle/email"
"gitlab.com/etke.cc/postmoogle/utils" "gitlab.com/etke.cc/postmoogle/utils"
) )
@@ -21,6 +24,7 @@ const (
RoomPassword = "password" RoomPassword = "password"
RoomSignature = "signature" RoomSignature = "signature"
RoomAutoreply = "autoreply" RoomAutoreply = "autoreply"
RoomRelay = "relay"
RoomThreadify = "threadify" RoomThreadify = "threadify"
RoomStripify = "stripify" RoomStripify = "stripify"
@@ -69,6 +73,20 @@ func (s Room) Active() bool {
return utils.Bool(s.Get(RoomActive)) return utils.Bool(s.Get(RoomActive))
} }
// Relay returns the SMTP Relay configuration in a manner of URL: smtp://user:pass@host:port
func (s Room) Relay() *url.URL {
relay := s.Get(RoomRelay)
if relay == "" {
return nil
}
u, err := url.Parse(relay)
if err != nil {
healthchecks.Global().Fail(strings.NewReader(fmt.Sprintf("cannot parse relay URL %q: %v", relay, err)))
return nil
}
return u
}
func (s Room) Password() string { func (s Room) Password() string {
return s.Get(RoomPassword) return s.Get(RoomPassword)
} }

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"net/url"
"strings" "strings"
"time" "time"
@@ -36,7 +37,7 @@ const (
var ErrNoRoom = errors.New("room not found") var ErrNoRoom = errors.New("room not found")
// SetSendmail sets mail sending func to the bot // SetSendmail sets mail sending func to the bot
func (b *Bot) SetSendmail(sendmail func(string, string, string) error) { func (b *Bot) SetSendmail(sendmail func(string, string, string, *url.URL) error) {
b.sendmail = sendmail b.sendmail = sendmail
b.q.SetSendmail(sendmail) b.q.SetSendmail(sendmail)
} }
@@ -60,14 +61,14 @@ func (b *Bot) shouldQueue(msg string) bool {
// Sendmail tries to send email immediately, but if it gets 4xx error (greylisting), // Sendmail tries to send email immediately, but if it gets 4xx error (greylisting),
// the email will be added to the queue and retried several times after that // the email will be added to the queue and retried several times after that
func (b *Bot) Sendmail(ctx context.Context, eventID id.EventID, from, to, data string) (bool, error) { func (b *Bot) Sendmail(ctx context.Context, eventID id.EventID, from, to, data string, relayOverride *url.URL) (bool, error) {
log := b.log.With().Str("from", from).Str("to", to).Str("eventID", eventID.String()).Logger() log := b.log.With().Str("from", from).Str("to", to).Str("eventID", eventID.String()).Logger()
log.Info().Msg("attempting to deliver email") log.Info().Msg("attempting to deliver email")
err := b.sendmail(from, to, data) err := b.sendmail(from, to, data, relayOverride)
if err != nil { if err != nil {
if b.shouldQueue(err.Error()) { if b.shouldQueue(err.Error()) {
log.Info().Err(err).Msg("email has been added to the queue") log.Info().Err(err).Msg("email has been added to the queue")
return true, b.q.Add(ctx, eventID.String(), from, to, data) return true, b.q.Add(ctx, eventID.String(), from, to, data, relayOverride)
} }
log.Warn().Err(err).Msg("email delivery failed") log.Warn().Err(err).Msg("email delivery failed")
return false, err return false, err
@@ -82,6 +83,16 @@ func (b *Bot) GetDKIMprivkey(ctx context.Context) string {
return b.cfg.GetBot(ctx).DKIMPrivateKey() return b.cfg.GetBot(ctx).DKIMPrivateKey()
} }
// GetRelayConfig returns relay config for specific room (mailbox) if set
func (b *Bot) GetRelayConfig(ctx context.Context, roomID id.RoomID) *url.URL {
cfg, err := b.cfg.GetRoom(ctx, roomID)
if err != nil {
b.log.Error().Err(err).Str("room_id", roomID.String()).Msg("cannot get room config")
return nil
}
return cfg.Relay()
}
func (b *Bot) getMapping(mailbox string) (id.RoomID, bool) { func (b *Bot) getMapping(mailbox string) (id.RoomID, bool) {
v, ok := b.rooms.Load(mailbox) v, ok := b.rooms.Load(mailbox)
if !ok { if !ok {
@@ -277,7 +288,7 @@ func (b *Bot) sendAutoreply(ctx context.Context, roomID id.RoomID, threadID id.E
ctx = newContext(ctx, threadEvt) ctx = newContext(ctx, threadEvt)
recipients := meta.Recipients recipients := meta.Recipients
for _, to := range recipients { for _, to := range recipients {
queued, err = b.Sendmail(ctx, evt.ID, meta.From, to, data) queued, err = b.Sendmail(ctx, evt.ID, meta.From, to, data, cfg.Relay())
if queued { if queued {
b.log.Info().Err(err).Str("from", meta.From).Str("to", to).Msg("email has been queued") b.log.Info().Err(err).Str("from", meta.From).Str("to", to).Msg("email has been queued")
b.saveSentMetadata(ctx, queued, meta.ThreadID, to, eml, cfg, "Autoreply has been sent to "+to+" (queued)") b.saveSentMetadata(ctx, queued, meta.ThreadID, to, eml, cfg, "Autoreply has been sent to "+to+" (queued)")
@@ -361,7 +372,7 @@ func (b *Bot) SendEmailReply(ctx context.Context) {
var queued bool var queued bool
recipients := meta.Recipients recipients := meta.Recipients
for _, to := range recipients { for _, to := range recipients {
queued, err = b.Sendmail(ctx, evt.ID, meta.From, to, data) queued, err = b.Sendmail(ctx, evt.ID, meta.From, to, data, cfg.Relay())
if queued { if queued {
b.log.Info().Err(err).Str("from", meta.From).Str("to", to).Msg("email has been queued") b.log.Info().Err(err).Str("from", meta.From).Str("to", to).Msg("email has been queued")
b.saveSentMetadata(ctx, queued, meta.ThreadID, to, eml, cfg) b.saveSentMetadata(ctx, queued, meta.ThreadID, to, eml, cfg)

View File

@@ -2,6 +2,7 @@ package queue
import ( import (
"context" "context"
"net/url"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"gitlab.com/etke.cc/linkpearl" "gitlab.com/etke.cc/linkpearl"
@@ -22,7 +23,7 @@ type Queue struct {
lp *linkpearl.Linkpearl lp *linkpearl.Linkpearl
cfg *config.Manager cfg *config.Manager
log *zerolog.Logger log *zerolog.Logger
sendmail func(string, string, string) error sendmail func(string, string, string, *url.URL) error
} }
// New queue // New queue
@@ -36,7 +37,7 @@ func New(lp *linkpearl.Linkpearl, cfg *config.Manager, log *zerolog.Logger) *Que
} }
// SetSendmail func // SetSendmail func
func (q *Queue) SetSendmail(function func(string, string, string) error) { func (q *Queue) SetSendmail(function func(string, string, string, *url.URL) error) {
q.sendmail = function q.sendmail = function
} }

View File

@@ -2,14 +2,20 @@ package queue
import ( import (
"context" "context"
"net/url"
"strconv" "strconv"
) )
// Add to queue // Add to queue
func (q *Queue) Add(ctx context.Context, id, from, to, data string) error { func (q *Queue) Add(ctx context.Context, id, from, to, data string, relayOverride ...*url.URL) error {
itemkey := acQueueKey + "." + id itemkey := acQueueKey + "." + id
relay := ""
if len(relayOverride) > 0 {
relay = relayOverride[0].String()
}
item := map[string]string{ item := map[string]string{
"attempts": "0", "attempts": "0",
"relay": relay,
"data": data, "data": data,
"from": from, "from": from,
"to": to, "to": to,
@@ -84,7 +90,12 @@ func (q *Queue) try(ctx context.Context, itemkey string, maxRetries int) bool {
return true return true
} }
err = q.sendmail(item["from"], item["to"], item["data"]) var relayOverride *url.URL
if item["relay"] != "" {
relayOverride, _ = url.Parse(item["relay"]) //nolint:errcheck // doesn't matter
}
err = q.sendmail(item["from"], item["to"], item["data"], relayOverride)
if err == nil { if err == nil {
q.log.Info().Str("id", itemkey).Msg("email from queue was delivered") q.log.Info().Str("id", itemkey).Msg("email from queue was delivered")
return true return true

View File

@@ -148,7 +148,7 @@ func initSMTP(cfg *config.Config) {
Relay: &smtp.RelayConfig{ Relay: &smtp.RelayConfig{
Host: cfg.Relay.Host, Host: cfg.Relay.Host,
Port: cfg.Relay.Port, Port: cfg.Relay.Port,
Usename: cfg.Relay.Username, Username: cfg.Relay.Username,
Password: cfg.Relay.Password, Password: cfg.Relay.Password,
}, },
}) })

View File

@@ -240,6 +240,7 @@ func (e *Email) Compose(privkey string) string {
From("", e.From). From("", e.From).
To("", e.To). To("", e.To).
Header("Message-Id", e.MessageID). Header("Message-Id", e.MessageID).
Header("X-PM-Tag", e.From).
Subject(e.Subject) Subject(e.Subject)
if textSize > 0 { if textSize > 0 {
mail = mail.Text([]byte(e.Text)) mail = mail.Text([]byte(e.Text))

30
go.mod
View File

@@ -7,21 +7,21 @@ toolchain go1.22.0
// replace gitlab.com/etke.cc/linkpearl => ../linkpearl // replace gitlab.com/etke.cc/linkpearl => ../linkpearl
require ( require (
github.com/archdx/zerolog-sentry v1.8.2 github.com/archdx/zerolog-sentry v1.8.3
github.com/emersion/go-msgauth v0.6.8 github.com/emersion/go-msgauth v0.6.8
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43
github.com/emersion/go-smtp v0.21.1 github.com/emersion/go-smtp v0.21.2
github.com/fsnotify/fsnotify v1.7.0 github.com/fsnotify/fsnotify v1.7.0
github.com/gabriel-vasile/mimetype v1.4.3 github.com/gabriel-vasile/mimetype v1.4.4
github.com/getsentry/sentry-go v0.27.0 github.com/getsentry/sentry-go v0.28.1
github.com/jhillyerd/enmime v1.2.0 github.com/jhillyerd/enmime v1.2.0
github.com/kvannotten/mailstrip v0.0.0-20200711213611-0002f5c0467e github.com/kvannotten/mailstrip v0.0.0-20200711213611-0002f5c0467e
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/mcnijman/go-emailaddress v1.1.1 github.com/mcnijman/go-emailaddress v1.1.1
github.com/mileusna/crontab v1.2.0 github.com/mileusna/crontab v1.2.0
github.com/raja/argon2pw v1.0.2-0.20210910183755-a391af63bd39 github.com/raja/argon2pw v1.0.2-0.20210910183755-a391af63bd39
github.com/rs/zerolog v1.32.0 github.com/rs/zerolog v1.33.0
gitlab.com/etke.cc/go/env v1.1.0 gitlab.com/etke.cc/go/env v1.2.0
gitlab.com/etke.cc/go/fswatcher v1.0.0 gitlab.com/etke.cc/go/fswatcher v1.0.0
gitlab.com/etke.cc/go/healthchecks/v2 v2.2.0 gitlab.com/etke.cc/go/healthchecks/v2 v2.2.0
gitlab.com/etke.cc/go/mxidwc v1.0.0 gitlab.com/etke.cc/go/mxidwc v1.0.0
@@ -29,9 +29,9 @@ require (
gitlab.com/etke.cc/go/secgen v1.2.0 gitlab.com/etke.cc/go/secgen v1.2.0
gitlab.com/etke.cc/go/validator v1.0.7 gitlab.com/etke.cc/go/validator v1.0.7
gitlab.com/etke.cc/linkpearl v0.0.0-20240425105001-435ae2720365 gitlab.com/etke.cc/linkpearl v0.0.0-20240425105001-435ae2720365
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
maunium.net/go/mautrix v0.18.1 maunium.net/go/mautrix v0.18.1
modernc.org/sqlite v1.29.8 modernc.org/sqlite v1.30.1
) )
require ( require (
@@ -57,15 +57,15 @@ require (
github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect github.com/tidwall/sjson v1.2.5 // indirect
github.com/yuin/goldmark v1.7.1 // indirect github.com/yuin/goldmark v1.7.4 // indirect
gitlab.com/etke.cc/go/trysmtp v1.1.3 // indirect gitlab.com/etke.cc/go/trysmtp v1.1.3 // indirect
go.mau.fi/util v0.4.2 // indirect go.mau.fi/util v0.5.0 // indirect
golang.org/x/crypto v0.22.0 // indirect golang.org/x/crypto v0.24.0 // indirect
golang.org/x/net v0.24.0 // indirect golang.org/x/net v0.26.0 // indirect
golang.org/x/sys v0.19.0 // indirect golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.16.0 // indirect
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect
modernc.org/libc v1.50.4 // indirect modernc.org/libc v1.54.1 // indirect
modernc.org/mathutil v1.6.0 // indirect modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect modernc.org/memory v1.8.0 // indirect
modernc.org/strutil v1.2.0 // indirect modernc.org/strutil v1.2.0 // indirect

84
go.sum
View File

@@ -2,8 +2,8 @@ blitiri.com.ar/go/spf v1.5.1 h1:CWUEasc44OrANJD8CzceRnRn1Jv0LttY68cYym2/pbE=
blitiri.com.ar/go/spf v1.5.1/go.mod h1:E71N92TfL4+Yyd5lpKuE9CAF2pd4JrUq1xQfkTxoNdk= blitiri.com.ar/go/spf v1.5.1/go.mod h1:E71N92TfL4+Yyd5lpKuE9CAF2pd4JrUq1xQfkTxoNdk=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/archdx/zerolog-sentry v1.8.2 h1:zS8n0+H7SsG161RN8dP47CSsdyrhODdo9LEDOPXJhXI= github.com/archdx/zerolog-sentry v1.8.3 h1:d3MiV+2406pHnUodv6O00sqafIohjtsYGb8cuFAf2KQ=
github.com/archdx/zerolog-sentry v1.8.2/go.mod h1:XrFHGe1CH5DQk/XSySu/IJSi5C9XR6+zpc97zVf/c4c= github.com/archdx/zerolog-sentry v1.8.3/go.mod h1:XrFHGe1CH5DQk/XSySu/IJSi5C9XR6+zpc97zVf/c4c=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI= github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI=
@@ -18,14 +18,14 @@ github.com/emersion/go-msgauth v0.6.8/go.mod h1:YDwuyTCUHu9xxmAeVj0eW4INnwB6NNZo
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 h1:hH4PQfOndHDlpzYfLAAfl63E8Le6F2+EL/cdhlkyRJY= github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 h1:hH4PQfOndHDlpzYfLAAfl63E8Le6F2+EL/cdhlkyRJY=
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-smtp v0.21.1 h1:VQeZSZAKk8ueYii1yR5Zalmy7jI287eWDUqSaJ68vRM= github.com/emersion/go-smtp v0.21.2 h1:OLDgvZKuofk4em9fT5tFG5j4jE1/hXnX75UMvcrL4AA=
github.com/emersion/go-smtp v0.21.1/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ= github.com/emersion/go-smtp v0.21.2/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= github.com/getsentry/sentry-go v0.28.1 h1:zzaSm/vHmGllRM6Tpx1492r0YDzauArdBfkJRtY6P5k=
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/getsentry/sentry-go v0.28.1/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
@@ -33,8 +33,8 @@ github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncV
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs= github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs=
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14= github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@@ -84,8 +84,8 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo= github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM= github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
@@ -100,10 +100,10 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U= github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg=
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
gitlab.com/etke.cc/go/env v1.1.0 h1:nbMhZkMu6C8lysRlb5siIiylWuyVkGAgEvwWEqz/82o= gitlab.com/etke.cc/go/env v1.2.0 h1:mPCk9GTQn5D/HQ9T+nP5OuCSyidgmllDM4OnV9n9UNY=
gitlab.com/etke.cc/go/env v1.1.0/go.mod h1:e1l4RM5MA1sc0R1w/RBDAESWRwgo5cOG9gx8BKUn2C4= gitlab.com/etke.cc/go/env v1.2.0/go.mod h1:e1l4RM5MA1sc0R1w/RBDAESWRwgo5cOG9gx8BKUn2C4=
gitlab.com/etke.cc/go/fswatcher v1.0.0 h1:uyiVn+1NVCjOLZrXSZouIDBDZBMwVipS4oYuvAFpPzo= gitlab.com/etke.cc/go/fswatcher v1.0.0 h1:uyiVn+1NVCjOLZrXSZouIDBDZBMwVipS4oYuvAFpPzo=
gitlab.com/etke.cc/go/fswatcher v1.0.0/go.mod h1:MqTOxyhXfvaVZQUL9/Ksbl2ow1PTBVu3eqIldvMq0RE= gitlab.com/etke.cc/go/fswatcher v1.0.0/go.mod h1:MqTOxyhXfvaVZQUL9/Ksbl2ow1PTBVu3eqIldvMq0RE=
gitlab.com/etke.cc/go/healthchecks/v2 v2.2.0 h1:7Y0qm8OAqeJApDNNfGSeA6WFF60q/394gMKinZNbSWE= gitlab.com/etke.cc/go/healthchecks/v2 v2.2.0 h1:7Y0qm8OAqeJApDNNfGSeA6WFF60q/394gMKinZNbSWE=
@@ -120,47 +120,47 @@ gitlab.com/etke.cc/go/validator v1.0.7 h1:4BGDTa9x68vJhbyn7m8W2yX+2Nb5im9+JLRrgo
gitlab.com/etke.cc/go/validator v1.0.7/go.mod h1:Id0SxRj0J3IPhiKlj0w1plxVLZfHlkwipn7HfRZsDts= gitlab.com/etke.cc/go/validator v1.0.7/go.mod h1:Id0SxRj0J3IPhiKlj0w1plxVLZfHlkwipn7HfRZsDts=
gitlab.com/etke.cc/linkpearl v0.0.0-20240425105001-435ae2720365 h1:MZnAxKJ5Bwv6ZHfC+HUEQyTDC0HPXVB8Q63ISkZ1lHo= gitlab.com/etke.cc/linkpearl v0.0.0-20240425105001-435ae2720365 h1:MZnAxKJ5Bwv6ZHfC+HUEQyTDC0HPXVB8Q63ISkZ1lHo=
gitlab.com/etke.cc/linkpearl v0.0.0-20240425105001-435ae2720365/go.mod h1:onM3Tge1SUII+oda91/ThDvVvMhkPiSRJ7EOsC2g0yc= gitlab.com/etke.cc/linkpearl v0.0.0-20240425105001-435ae2720365/go.mod h1:onM3Tge1SUII+oda91/ThDvVvMhkPiSRJ7EOsC2g0yc=
go.mau.fi/util v0.4.2 h1:RR3TOcRHmCF9Bx/3YG4S65MYfa+nV6/rn8qBWW4Mi30= go.mau.fi/util v0.5.0 h1:8yELAl+1CDRrwGe9NUmREgVclSs26Z68pTWePHVxuDo=
go.mau.fi/util v0.4.2/go.mod h1:PlAVfUUcPyHPrwnvjkJM9UFcPE7qGPDJqk+Oufa1Gtw= go.mau.fi/util v0.5.0/go.mod h1:DsJzUrJAG53lCZnnYvq9/mOyLuPScWwYhvETiTrpdP4=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
maunium.net/go/mautrix v0.18.1 h1:a6mUsJixegBNTXUoqC5RQ9gsumIPzKvCubKwF+zmCt4= maunium.net/go/mautrix v0.18.1 h1:a6mUsJixegBNTXUoqC5RQ9gsumIPzKvCubKwF+zmCt4=
maunium.net/go/mautrix v0.18.1/go.mod h1:2oHaq792cSXFGvxLvYw3Gf1L4WVVP4KZcYys5HVk/h8= maunium.net/go/mautrix v0.18.1/go.mod h1:2oHaq792cSXFGvxLvYw3Gf1L4WVVP4KZcYys5HVk/h8=
modernc.org/cc/v4 v4.21.0 h1:D/gLKtcztomvWbsbvBKo3leKQv+86f+DdqEZBBXhnag= modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
modernc.org/cc/v4 v4.21.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.17.3 h1:t2CQci84jnxKw3GGnHvjGKjiNZeZqyQx/023spkk4hU= modernc.org/ccgo/v4 v4.19.0 h1:f9K5VdC0nVhHKTFMvhjtZ8TbRgFQbASvE5yO1zs8eC0=
modernc.org/ccgo/v4 v4.17.3/go.mod h1:1FCbAtWYJoKuc+AviS+dH+vGNtYmFJqBeRWjmnDWsIg= modernc.org/ccgo/v4 v4.19.0/go.mod h1:CfpAl+673iXNwMG/aqcQn+vDcu4Es/YLya7+9RHjTa4=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b h1:BnN1t+pb1cy61zbvSUV7SeI0PwosMhlAEi/vBY4qxp8= modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b h1:BnN1t+pb1cy61zbvSUV7SeI0PwosMhlAEi/vBY4qxp8=
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.50.4 h1:GeqBes21PQHbVitLewzkhLXLFnQ1AWxOlHI+g5InUnQ= modernc.org/libc v1.54.1 h1:5vzs86ANQehsFZXqjxUam20JwgK2eODTOcGMIWcCdg4=
modernc.org/libc v1.50.4/go.mod h1:rhzrUx5oePTSTIzBgM0mTftwWHK8tiT9aNFUt1mldl0= modernc.org/libc v1.54.1/go.mod h1:B0D6klDmSmnq26T1iocn9kzyX6NtbzjuI3+oX/xfvng=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
@@ -169,8 +169,8 @@ modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/sqlite v1.29.8 h1:nGKglNx9K5v0As+zF0/Gcl1kMkmaU1XynYyq92PbsC8= modernc.org/sqlite v1.30.1 h1:YFhPVfu2iIgUf9kuA1CR7iiHdcEEsI2i+yjRYHscyxk=
modernc.org/sqlite v1.29.8/go.mod h1:lQPm27iqa4UNZpmr4Aor0MH0HkCLbt1huYDfWylLZFk= modernc.org/sqlite v1.30.1/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=

View File

@@ -6,13 +6,14 @@ import (
"io" "io"
"net" "net"
"net/smtp" "net/smtp"
"net/url"
"strings" "strings"
"github.com/rs/zerolog" "github.com/rs/zerolog"
) )
type MailSender interface { type MailSender interface {
Send(from, to, data string) error Send(from, to, data string, relayOverride *url.URL) error
} }
// SMTP client // SMTP client
@@ -30,16 +31,35 @@ func newClient(cfg *RelayConfig, log *zerolog.Logger) *Client {
} }
} }
// relayFromURL creates a RelayConfig from a URL
func relayFromURL(relayURL *url.URL) *RelayConfig {
if relayURL == nil {
return nil
}
password, _ := relayURL.User.Password()
return &RelayConfig{
Host: relayURL.Hostname(),
Port: relayURL.Port(),
Username: relayURL.User.Username(),
Password: password,
}
}
// Send email // Send email
func (c Client) Send(from, to, data string) error { func (c Client) Send(from, to, data string, relayOverride *url.URL) error {
log := c.log.With().Str("from", from).Str("to", to).Logger() log := c.log.With().Str("from", from).Str("to", to).Logger()
log.Debug().Msg("sending email") log.Debug().Msg("sending email")
relay := c.config
if relayOverrideCfg := relayFromURL(relayOverride); relayOverrideCfg != nil {
relay = relayOverrideCfg
}
var conn *smtp.Client var conn *smtp.Client
var err error var err error
if c.config.Host != "" { if relay != nil && relay.Host != "" {
log.Debug().Msg("creating relay client...") log.Debug().Msg("creating relay client...")
conn, err = c.createRelayClient(from, to) conn, err = c.createRelayClient(relay, from, to)
} else { } else {
log.Debug().Msg("trying direct SMTP connection...") log.Debug().Msg("trying direct SMTP connection...")
conn, err = c.createDirectClient(from, to) conn, err = c.createDirectClient(from, to)
@@ -73,9 +93,9 @@ func (c Client) Send(from, to, data string) error {
} }
// createRelayClientconnects directly to the provided smtp host // createRelayClientconnects directly to the provided smtp host
func (c *Client) createRelayClient(from, to string) (*smtp.Client, error) { func (c *Client) createRelayClient(config *RelayConfig, from, to string) (*smtp.Client, error) {
localname := strings.SplitN(from, "@", 2)[1] localname := strings.SplitN(from, "@", 2)[1]
target := c.config.Host + ":" + c.config.Port target := config.Host + ":" + config.Port
conn, err := smtp.Dial(target) conn, err := smtp.Dial(target)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -87,12 +107,12 @@ func (c *Client) createRelayClient(from, to string) (*smtp.Client, error) {
} }
if ok, _ := conn.Extension("STARTTLS"); ok { if ok, _ := conn.Extension("STARTTLS"); ok {
config := &tls.Config{ServerName: c.config.Host} //nolint:gosec // it's smtp, even that is too strict sometimes tlsConfig := &tls.Config{ServerName: config.Host} //nolint:gosec // it's smtp, even that is too strict sometimes
conn.StartTLS(config) //nolint:errcheck // if it doesn't work - we can't do anything anyway conn.StartTLS(tlsConfig) //nolint:errcheck // if it doesn't work - we can't do anything anyway
} }
if c.config.Usename != "" { if config.Username != "" {
err = conn.Auth(smtp.PlainAuth("", c.config.Usename, c.config.Password, c.config.Host)) err = conn.Auth(smtp.PlainAuth("", config.Username, config.Password, config.Host))
if err != nil { if err != nil {
conn.Close() conn.Close()
return nil, err return nil, err

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"net" "net"
"net/url"
"sync" "sync"
"time" "time"
@@ -44,7 +45,7 @@ type TLSConfig struct {
type RelayConfig struct { type RelayConfig struct {
Host string Host string
Port string Port string
Usename string Username string
Password string Password string
} }
@@ -70,11 +71,12 @@ type matrixbot interface {
GetIFOptions(context.Context, id.RoomID) email.IncomingFilteringOptions GetIFOptions(context.Context, id.RoomID) email.IncomingFilteringOptions
IncomingEmail(context.Context, *email.Email) error IncomingEmail(context.Context, *email.Email) error
GetDKIMprivkey(context.Context) string GetDKIMprivkey(context.Context) string
GetRelayConfig(context.Context, id.RoomID) *url.URL
} }
// Caller is Sendmail caller // Caller is Sendmail caller
type Caller interface { type Caller interface {
SetSendmail(func(string, string, string) error) SetSendmail(func(string, string, string, *url.URL) error)
} }
// NewManager creates new SMTP server manager // NewManager creates new SMTP server manager

View File

@@ -6,9 +6,11 @@ import (
"errors" "errors"
"io" "io"
"net" "net"
"net/url"
"strconv" "strconv"
"github.com/emersion/go-msgauth/dkim" "github.com/emersion/go-msgauth/dkim"
"github.com/emersion/go-sasl"
"github.com/emersion/go-smtp" "github.com/emersion/go-smtp"
"github.com/jhillyerd/enmime" "github.com/jhillyerd/enmime"
"github.com/rs/zerolog" "github.com/rs/zerolog"
@@ -40,7 +42,7 @@ type session struct {
ctx context.Context //nolint:containedctx // that's session ctx context.Context //nolint:containedctx // that's session
conn *smtp.Conn conn *smtp.Conn
domains []string domains []string
sendmail func(string, string, string) error sendmail func(string, string, string, *url.URL) error
dir string dir string
tos []string tos []string
@@ -50,7 +52,18 @@ type session struct {
fromRoom id.RoomID fromRoom id.RoomID
} }
func (s *session) AuthPlain(username, password string) error { func (s *session) AuthMechanisms() []string {
return []string{sasl.Plain}
}
//nolint:unparam // it's a part of the interface
func (s *session) Auth(_ string) (sasl.Server, error) {
return sasl.NewPlainServer(func(identity, username, password string) error {
return s.authPlain(identity, username, password)
}), nil
}
func (s *session) authPlain(_, username, password string) error {
addr := s.conn.Conn().RemoteAddr() addr := s.conn.Conn().RemoteAddr()
if s.bot.IsBanned(s.ctx, addr) { if s.bot.IsBanned(s.ctx, addr) {
return ErrBanned return ErrBanned
@@ -124,7 +137,7 @@ func (s *session) outgoingData(r io.Reader) error {
eml := email.FromEnvelope(s.tos[0], envelope) eml := email.FromEnvelope(s.tos[0], envelope)
for _, to := range s.tos { for _, to := range s.tos {
eml.RcptTo = to eml.RcptTo = to
err := s.sendmail(eml.From, to, eml.Compose(s.privkey)) err := s.sendmail(eml.From, to, eml.Compose(s.privkey), s.bot.GetRelayConfig(s.ctx, s.fromRoom))
if err != nil { if err != nil {
return err return err
} }

View File

@@ -2,6 +2,7 @@ package utils
import ( import (
"net" "net"
"net/url"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@@ -40,6 +41,15 @@ func SanitizeDomain(domain string) string {
return domains[0] return domains[0]
} }
// SanitizeURL checks that input URL is valid
func SanitizeURL(str string) string {
parsed, err := url.Parse(str)
if err != nil {
return ""
}
return parsed.String()
}
// Bool converts string to boolean // Bool converts string to boolean
func Bool(str string) bool { func Bool(str string) bool {
str = strings.ToLower(str) str = strings.ToLower(str)

View File

@@ -418,9 +418,10 @@ func NewWithHub(hub *sentry.Hub, opts ...WriterOption) (*Writer, error) {
} }
return &Writer{ return &Writer{
hub: hub, hub: hub,
levels: levels, levels: levels,
flushTimeout: cfg.flushTimeout, flushTimeout: cfg.flushTimeout,
withBreadcrumbs: cfg.breadcrumbs,
}, nil }, nil
} }

View File

@@ -1,4 +1,4 @@
image: alpine/edge image: alpine/latest
packages: packages:
- go - go
sources: sources:
@@ -8,10 +8,13 @@ artifacts:
tasks: tasks:
- build: | - build: |
cd go-smtp cd go-smtp
go build -v ./... go build -race -v ./...
- test: | - test: |
cd go-smtp cd go-smtp
go test -coverprofile=coverage.txt -covermode=atomic ./... go test -race -coverprofile=coverage.txt -covermode=atomic ./...
- coverage: | - coverage: |
cd go-smtp cd go-smtp
go tool cover -html=coverage.txt -o ~/coverage.html go tool cover -html=coverage.txt -o ~/coverage.html
- gofmt: |
cd go-smtp
test -z $(gofmt -l .)

View File

@@ -183,12 +183,9 @@ func (c *Client) greet() error {
c.conn.SetDeadline(time.Now().Add(c.CommandTimeout)) c.conn.SetDeadline(time.Now().Add(c.CommandTimeout))
defer c.conn.SetDeadline(time.Time{}) defer c.conn.SetDeadline(time.Time{})
_, _, err := c.text.ReadResponse(220) _, _, err := c.readResponse(220)
if err != nil { if err != nil {
c.text.Close() c.text.Close()
if protoErr, ok := err.(*textproto.Error); ok {
return toSMTPErr(protoErr)
}
return err return err
} }
@@ -197,12 +194,23 @@ func (c *Client) greet() error {
// hello runs a hello exchange if needed. // hello runs a hello exchange if needed.
func (c *Client) hello() error { func (c *Client) hello() error {
if !c.didHello { if c.didHello {
c.didHello = true return c.helloError
if err := c.greet(); err != nil { }
c.helloError = err
} else if err := c.ehlo(); err != nil { c.didHello = true
if err := c.greet(); err != nil {
c.helloError = err
return c.helloError
}
if err := c.ehlo(); err != nil {
var smtpError *SMTPError
if errors.As(err, &smtpError) && (smtpError.Code == 500 || smtpError.Code == 502) {
// The server doesn't support EHLO, fallback to HELO
c.helloError = c.helo() c.helloError = c.helo()
} else {
c.helloError = err
} }
} }
return c.helloError return c.helloError
@@ -226,6 +234,14 @@ func (c *Client) Hello(localName string) error {
return c.hello() return c.hello()
} }
func (c *Client) readResponse(expectCode int) (int, string, error) {
code, msg, err := c.text.ReadResponse(expectCode)
if protoErr, ok := err.(*textproto.Error); ok {
err = toSMTPErr(protoErr)
}
return code, msg, err
}
// cmd is a convenience function that sends a command and returns the response // cmd is a convenience function that sends a command and returns the response
// textproto.Error returned by c.text.ReadResponse is converted into SMTPError. // textproto.Error returned by c.text.ReadResponse is converted into SMTPError.
func (c *Client) cmd(expectCode int, format string, args ...interface{}) (int, string, error) { func (c *Client) cmd(expectCode int, format string, args ...interface{}) (int, string, error) {
@@ -238,15 +254,8 @@ func (c *Client) cmd(expectCode int, format string, args ...interface{}) (int, s
} }
c.text.StartResponse(id) c.text.StartResponse(id)
defer c.text.EndResponse(id) defer c.text.EndResponse(id)
code, msg, err := c.text.ReadResponse(expectCode)
if err != nil { return c.readResponse(expectCode)
if protoErr, ok := err.(*textproto.Error); ok {
smtpErr := toSMTPErr(protoErr)
return code, smtpErr.Message, smtpErr
}
return code, msg, err
}
return code, msg, nil
} }
// helo sends the HELO greeting to the server. It should be used only when the // helo sends the HELO greeting to the server. It should be used only when the
@@ -544,10 +553,10 @@ func (d *dataCloser) Close() error {
if d.c.lmtp { if d.c.lmtp {
for expectedResponses > 0 { for expectedResponses > 0 {
rcpt := d.c.rcpts[len(d.c.rcpts)-expectedResponses] rcpt := d.c.rcpts[len(d.c.rcpts)-expectedResponses]
if _, _, err := d.c.text.ReadResponse(250); err != nil { if _, _, err := d.c.readResponse(250); err != nil {
if protoErr, ok := err.(*textproto.Error); ok { if smtpErr, ok := err.(*SMTPError); ok {
if d.statusCb != nil { if d.statusCb != nil {
d.statusCb(rcpt, toSMTPErr(protoErr)) d.statusCb(rcpt, smtpErr)
} }
} else { } else {
return err return err
@@ -558,11 +567,8 @@ func (d *dataCloser) Close() error {
expectedResponses-- expectedResponses--
} }
} else { } else {
_, _, err := d.c.text.ReadResponse(250) _, _, err := d.c.readResponse(250)
if err != nil { if err != nil {
if protoErr, ok := err.(*textproto.Error); ok {
return toSMTPErr(protoErr)
}
return err return err
} }
} }

View File

@@ -773,15 +773,11 @@ func (c *Conn) handleAuth(arg string) {
// Parse client initial response if there is one // Parse client initial response if there is one
var ir []byte var ir []byte
if len(parts) > 1 { if len(parts) > 1 {
if parts[1] == "=" { var err error
ir = []byte{} ir, err = decodeSASLResponse(parts[1])
} else { if err != nil {
var err error c.writeResponse(454, EnhancedCode{4, 7, 0}, "Invalid base64 data")
ir, err = base64.StdEncoding.DecodeString(parts[1]) return
if err != nil {
c.writeResponse(454, EnhancedCode{4, 7, 0}, "Invalid base64 data")
return
}
} }
} }
@@ -820,14 +816,10 @@ func (c *Conn) handleAuth(arg string) {
return return
} }
if encoded == "=" { response, err = decodeSASLResponse(encoded)
response = []byte{} if err != nil {
} else { c.writeResponse(454, EnhancedCode{4, 7, 0}, "Invalid base64 data")
response, err = base64.StdEncoding.DecodeString(encoded) return
if err != nil {
c.writeResponse(454, EnhancedCode{4, 7, 0}, "Invalid base64 data")
return
}
} }
} }
@@ -835,6 +827,13 @@ func (c *Conn) handleAuth(arg string) {
c.didAuth = true c.didAuth = true
} }
func decodeSASLResponse(s string) ([]byte, error) {
if s == "=" {
return []byte{}, nil
}
return base64.StdEncoding.DecodeString(s)
}
func (c *Conn) authMechanisms() []string { func (c *Conn) authMechanisms() []string {
if authSession, ok := c.Session().(AuthSession); ok { if authSession, ok := c.Session().(AuthSession); ok {
return authSession.AuthMechanisms() return authSession.AuthMechanisms()

View File

@@ -120,7 +120,7 @@ func (s *Server) Serve(l net.Listener) error {
err := s.handleConn(newConn(c, s)) err := s.handleConn(newConn(c, s))
if err != nil { if err != nil {
s.ErrorLog.Printf("handler error: %s", err) s.ErrorLog.Printf("error handling %v: %s", c.RemoteAddr(), err)
} }
}() }()
} }

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2018-2020 Gabriel Vasile Copyright (c) 2018 Gabriel Vasile
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -16,9 +16,6 @@
<a href="https://goreportcard.com/report/github.com/gabriel-vasile/mimetype"> <a href="https://goreportcard.com/report/github.com/gabriel-vasile/mimetype">
<img alt="Go report card" src="https://goreportcard.com/badge/github.com/gabriel-vasile/mimetype"> <img alt="Go report card" src="https://goreportcard.com/badge/github.com/gabriel-vasile/mimetype">
</a> </a>
<a href="https://codecov.io/gh/gabriel-vasile/mimetype">
<img alt="Code coverage" src="https://codecov.io/gh/gabriel-vasile/mimetype/branch/master/graph/badge.svg?token=qcfJF1kkl2"/>
</a>
<a href="LICENSE"> <a href="LICENSE">
<img alt="License" src="https://img.shields.io/badge/License-MIT-green.svg"> <img alt="License" src="https://img.shields.io/badge/License-MIT-green.svg">
</a> </a>

View File

@@ -3,6 +3,7 @@ package magic
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"strconv"
) )
var ( var (
@@ -74,51 +75,87 @@ func CRX(raw []byte, limit uint32) bool {
} }
// Tar matches a (t)ape (ar)chive file. // Tar matches a (t)ape (ar)chive file.
// Tar files are divided into 512 bytes records. First record contains a 257
// bytes header padded with NUL.
func Tar(raw []byte, _ uint32) bool { func Tar(raw []byte, _ uint32) bool {
// The "magic" header field for files in in UStar (POSIX IEEE P1003.1) archives const sizeRecord = 512
// has the prefix "ustar". The values of the remaining bytes in this field vary
// by archiver implementation.
if len(raw) >= 512 && bytes.HasPrefix(raw[257:], []byte{0x75, 0x73, 0x74, 0x61, 0x72}) {
return true
}
if len(raw) < 256 { // The structure of a tar header:
// type TarHeader struct {
// Name [100]byte
// Mode [8]byte
// Uid [8]byte
// Gid [8]byte
// Size [12]byte
// Mtime [12]byte
// Chksum [8]byte
// Linkflag byte
// Linkname [100]byte
// Magic [8]byte
// Uname [32]byte
// Gname [32]byte
// Devmajor [8]byte
// Devminor [8]byte
// }
if len(raw) < sizeRecord {
return false
}
raw = raw[:sizeRecord]
// First 100 bytes of the header represent the file name.
// Check if file looks like Gentoo GLEP binary package.
if bytes.Contains(raw[:100], []byte("/gpkg-1\x00")) {
return false return false
} }
// The older v7 format has no "magic" field, and therefore must be identified // Get the checksum recorded into the file.
// with heuristics based on legal ranges of values for other header fields: recsum, err := tarParseOctal(raw[148:156])
// https://www.nationalarchives.gov.uk/PRONOM/Format/proFormatSearch.aspx?status=detailReport&id=385&strPageToDisplay=signatures if err != nil {
rules := []struct { return false
min, max uint8
i int
}{
{0x21, 0xEF, 0},
{0x30, 0x37, 105},
{0x20, 0x37, 106},
{0x00, 0x00, 107},
{0x30, 0x37, 113},
{0x20, 0x37, 114},
{0x00, 0x00, 115},
{0x30, 0x37, 121},
{0x20, 0x37, 122},
{0x00, 0x00, 123},
{0x30, 0x37, 134},
{0x30, 0x37, 146},
{0x30, 0x37, 153},
{0x00, 0x37, 154},
} }
for _, r := range rules { sum1, sum2 := tarChksum(raw)
if raw[r.i] < r.min || raw[r.i] > r.max { return recsum == sum1 || recsum == sum2
return false }
}
} // tarParseOctal converts octal string to decimal int.
func tarParseOctal(b []byte) (int64, error) {
for _, i := range []uint8{135, 147, 155} { // Because unused fields are filled with NULs, we need to skip leading NULs.
if raw[i] != 0x00 && raw[i] != 0x20 { // Fields may also be padded with spaces or NULs.
return false // So we remove leading and trailing NULs and spaces to be sure.
} b = bytes.Trim(b, " \x00")
}
if len(b) == 0 {
return true return 0, nil
}
x, err := strconv.ParseUint(tarParseString(b), 8, 64)
if err != nil {
return 0, err
}
return int64(x), nil
}
// tarParseString converts a NUL ended bytes slice to a string.
func tarParseString(b []byte) string {
if i := bytes.IndexByte(b, 0); i >= 0 {
return string(b[:i])
}
return string(b)
}
// tarChksum computes the checksum for the header block b.
// The actual checksum is written to same b block after it has been calculated.
// Before calculation the bytes from b reserved for checksum have placeholder
// value of ASCII space 0x20.
// POSIX specifies a sum of the unsigned byte values, but the Sun tar used
// signed byte values. We compute and return both.
func tarChksum(b []byte) (unsigned, signed int64) {
for i, c := range b {
if 148 <= i && i < 156 {
c = ' ' // Treat the checksum field itself as all spaces.
}
unsigned += int64(c)
signed += int64(int8(c))
}
return unsigned, signed
} }

View File

@@ -153,8 +153,11 @@ func ftyp(sigs ...[]byte) Detector {
if len(raw) < 12 { if len(raw) < 12 {
return false return false
} }
if !bytes.Equal(raw[4:8], []byte("ftyp")) {
return false
}
for _, s := range sigs { for _, s := range sigs {
if bytes.Equal(raw[4:12], append([]byte("ftyp"), s...)) { if bytes.Equal(raw[8:12], s) {
return true return true
} }
} }

View File

@@ -1,7 +1,6 @@
package magic package magic
import ( import (
"bufio"
"bytes" "bytes"
"strings" "strings"
"time" "time"
@@ -234,9 +233,10 @@ func GeoJSON(raw []byte, limit uint32) bool {
// types. // types.
func NdJSON(raw []byte, limit uint32) bool { func NdJSON(raw []byte, limit uint32) bool {
lCount, hasObjOrArr := 0, false lCount, hasObjOrArr := 0, false
sc := bufio.NewScanner(dropLastLine(raw, limit)) raw = dropLastLine(raw, limit)
for sc.Scan() { var l []byte
l := sc.Bytes() for len(raw) != 0 {
l, raw = scanLine(raw)
// Empty lines are allowed in NDJSON. // Empty lines are allowed in NDJSON.
if l = trimRWS(trimLWS(l)); len(l) == 0 { if l = trimRWS(trimLWS(l)); len(l) == 0 {
continue continue
@@ -301,20 +301,15 @@ func Svg(raw []byte, limit uint32) bool {
} }
// Srt matches a SubRip file. // Srt matches a SubRip file.
func Srt(in []byte, _ uint32) bool { func Srt(raw []byte, _ uint32) bool {
s := bufio.NewScanner(bytes.NewReader(in)) line, raw := scanLine(raw)
if !s.Scan() {
return false
}
// First line must be 1.
if s.Text() != "1" {
return false
}
if !s.Scan() { // First line must be 1.
if string(line) != "1" {
return false return false
} }
secondLine := s.Text() line, raw = scanLine(raw)
secondLine := string(line)
// Timestamp format (e.g: 00:02:16,612 --> 00:02:19,376) limits secondLine // Timestamp format (e.g: 00:02:16,612 --> 00:02:19,376) limits secondLine
// length to exactly 29 characters. // length to exactly 29 characters.
if len(secondLine) != 29 { if len(secondLine) != 29 {
@@ -325,14 +320,12 @@ func Srt(in []byte, _ uint32) bool {
if strings.Contains(secondLine, ".") { if strings.Contains(secondLine, ".") {
return false return false
} }
// For Go <1.17, comma is not recognised as a decimal separator by `time.Parse`.
secondLine = strings.ReplaceAll(secondLine, ",", ".")
// Second line must be a time range. // Second line must be a time range.
ts := strings.Split(secondLine, " --> ") ts := strings.Split(secondLine, " --> ")
if len(ts) != 2 { if len(ts) != 2 {
return false return false
} }
const layout = "15:04:05.000" const layout = "15:04:05,000"
t0, err := time.Parse(layout, ts[0]) t0, err := time.Parse(layout, ts[0])
if err != nil { if err != nil {
return false return false
@@ -345,8 +338,9 @@ func Srt(in []byte, _ uint32) bool {
return false return false
} }
line, _ = scanLine(raw)
// A third line must exist and not be empty. This is the actual subtitle text. // A third line must exist and not be empty. This is the actual subtitle text.
return s.Scan() && len(s.Bytes()) != 0 return len(line) != 0
} }
// Vtt matches a Web Video Text Tracks (WebVTT) file. See // Vtt matches a Web Video Text Tracks (WebVTT) file. See
@@ -373,3 +367,15 @@ func Vtt(raw []byte, limit uint32) bool {
return bytes.Equal(raw, []byte{0xEF, 0xBB, 0xBF, 0x57, 0x45, 0x42, 0x56, 0x54, 0x54}) || // UTF-8 BOM and "WEBVTT" return bytes.Equal(raw, []byte{0xEF, 0xBB, 0xBF, 0x57, 0x45, 0x42, 0x56, 0x54, 0x54}) || // UTF-8 BOM and "WEBVTT"
bytes.Equal(raw, []byte{0x57, 0x45, 0x42, 0x56, 0x54, 0x54}) // "WEBVTT" bytes.Equal(raw, []byte{0x57, 0x45, 0x42, 0x56, 0x54, 0x54}) // "WEBVTT"
} }
// dropCR drops a terminal \r from the data.
func dropCR(data []byte) []byte {
if len(data) > 0 && data[len(data)-1] == '\r' {
return data[0 : len(data)-1]
}
return data
}
func scanLine(b []byte) (line, remainder []byte) {
line, remainder, _ = bytes.Cut(b, []byte("\n"))
return dropCR(line), remainder
}

View File

@@ -18,7 +18,7 @@ func Tsv(raw []byte, limit uint32) bool {
} }
func sv(in []byte, comma rune, limit uint32) bool { func sv(in []byte, comma rune, limit uint32) bool {
r := csv.NewReader(dropLastLine(in, limit)) r := csv.NewReader(bytes.NewReader(dropLastLine(in, limit)))
r.Comma = comma r.Comma = comma
r.ReuseRecord = true r.ReuseRecord = true
r.LazyQuotes = true r.LazyQuotes = true
@@ -44,20 +44,14 @@ func sv(in []byte, comma rune, limit uint32) bool {
// mimetype limits itself to ReadLimit bytes when performing a detection. // mimetype limits itself to ReadLimit bytes when performing a detection.
// This means, for file formats like CSV for NDJSON, the last line of the input // This means, for file formats like CSV for NDJSON, the last line of the input
// can be an incomplete line. // can be an incomplete line.
func dropLastLine(b []byte, cutAt uint32) io.Reader { func dropLastLine(b []byte, readLimit uint32) []byte {
if cutAt == 0 { if readLimit == 0 || uint32(len(b)) < readLimit {
return bytes.NewReader(b) return b
} }
if uint32(len(b)) >= cutAt { for i := len(b) - 1; i > 0; i-- {
for i := cutAt - 1; i > 0; i-- { if b[i] == '\n' {
if b[i] == '\n' { return b[:i]
return bytes.NewReader(b[:i])
}
} }
// No newline was found between the 0 index and cutAt.
return bytes.NewReader(b[:cutAt])
} }
return b
return bytes.NewReader(b)
} }

View File

@@ -7,14 +7,15 @@ package mimetype
import ( import (
"io" "io"
"io/ioutil"
"mime" "mime"
"os" "os"
"sync/atomic" "sync/atomic"
) )
var defaultLimit uint32 = 3072
// readLimit is the maximum number of bytes from the input used when detecting. // readLimit is the maximum number of bytes from the input used when detecting.
var readLimit uint32 = 3072 var readLimit uint32 = defaultLimit
// Detect returns the MIME type found from the provided byte slice. // Detect returns the MIME type found from the provided byte slice.
// //
@@ -48,7 +49,7 @@ func DetectReader(r io.Reader) (*MIME, error) {
// Using atomic because readLimit can be written at the same time in other goroutine. // Using atomic because readLimit can be written at the same time in other goroutine.
l := atomic.LoadUint32(&readLimit) l := atomic.LoadUint32(&readLimit)
if l == 0 { if l == 0 {
in, err = ioutil.ReadAll(r) in, err = io.ReadAll(r)
if err != nil { if err != nil {
return errMIME, err return errMIME, err
} }
@@ -103,6 +104,7 @@ func EqualsAny(s string, mimes ...string) bool {
// SetLimit sets the maximum number of bytes read from input when detecting the MIME type. // SetLimit sets the maximum number of bytes read from input when detecting the MIME type.
// Increasing the limit provides better detection for file formats which store // Increasing the limit provides better detection for file formats which store
// their magical numbers towards the end of the file: docx, pptx, xlsx, etc. // their magical numbers towards the end of the file: docx, pptx, xlsx, etc.
// During detection data is read in a single block of size limit, i.e. it is not buffered.
// A limit of 0 means the whole input file will be used. // A limit of 0 means the whole input file will be used.
func SetLimit(limit uint32) { func SetLimit(limit uint32) {
// Using atomic because readLimit can be read at the same time in other goroutine. // Using atomic because readLimit can be read at the same time in other goroutine.

View File

@@ -1,5 +1,34 @@
# Changelog # Changelog
## 0.28.1
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.28.1.
### Bug Fixes
- Implement `http.ResponseWriter` to hook into various parts of the response process ([#837](https://github.com/getsentry/sentry-go/pull/837))
## 0.28.0
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.28.0.
### Features
- Add a `Fiber` performance tracing & error reporting integration ([#795](https://github.com/getsentry/sentry-go/pull/795))
- Add performance tracing to the `Echo` integration ([#722](https://github.com/getsentry/sentry-go/pull/722))
- Add performance tracing to the `FastHTTP` integration ([#732](https://github.com/getsentry/sentry-go/pull/723))
- Add performance tracing to the `Iris` integration ([#809](https://github.com/getsentry/sentry-go/pull/809))
- Add performance tracing to the `Negroni` integration ([#808](https://github.com/getsentry/sentry-go/pull/808))
- Add `FailureIssueThreshold` & `RecoveryThreshold` to `MonitorConfig` ([#775](https://github.com/getsentry/sentry-go/pull/775))
- Use `errors.Unwrap()` to create exception groups ([#792](https://github.com/getsentry/sentry-go/pull/792))
- Add support for matching on strings for `ClientOptions.IgnoreErrors` & `ClientOptions.IgnoreTransactions` ([#819](https://github.com/getsentry/sentry-go/pull/819))
- Add `http.request.method` attribute for performance span data ([#786](https://github.com/getsentry/sentry-go/pull/786))
- Accept `interface{}` for span data values ([#784](https://github.com/getsentry/sentry-go/pull/784))
### Bug Fixes
- Fix missing stack trace for parsing error in `logrusentry` ([#689](https://github.com/getsentry/sentry-go/pull/689))
## 0.27.0 ## 0.27.0
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.27.0. The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.27.0.

View File

@@ -13,7 +13,6 @@
[![Build Status](https://github.com/getsentry/sentry-go/workflows/go-workflow/badge.svg)](https://github.com/getsentry/sentry-go/actions?query=workflow%3Ago-workflow) [![Build Status](https://github.com/getsentry/sentry-go/workflows/go-workflow/badge.svg)](https://github.com/getsentry/sentry-go/actions?query=workflow%3Ago-workflow)
[![Go Report Card](https://goreportcard.com/badge/github.com/getsentry/sentry-go)](https://goreportcard.com/report/github.com/getsentry/sentry-go) [![Go Report Card](https://goreportcard.com/badge/github.com/getsentry/sentry-go)](https://goreportcard.com/report/github.com/getsentry/sentry-go)
[![Discord](https://img.shields.io/discord/621778831602221064)](https://discord.gg/Ww9hbqr) [![Discord](https://img.shields.io/discord/621778831602221064)](https://discord.gg/Ww9hbqr)
[![GoDoc](https://godoc.org/github.com/getsentry/sentry-go?status.svg)](https://godoc.org/github.com/getsentry/sentry-go)
[![go.dev](https://img.shields.io/badge/go.dev-pkg-007d9c.svg?style=flat)](https://pkg.go.dev/github.com/getsentry/sentry-go) [![go.dev](https://img.shields.io/badge/go.dev-pkg-007d9c.svg?style=flat)](https://pkg.go.dev/github.com/getsentry/sentry-go)
`sentry-go` provides a Sentry client implementation for the Go programming `sentry-go` provides a Sentry client implementation for the Go programming
@@ -85,7 +84,6 @@ checkout the official documentation:
- [Bug Tracker](https://github.com/getsentry/sentry-go/issues) - [Bug Tracker](https://github.com/getsentry/sentry-go/issues)
- [GitHub Project](https://github.com/getsentry/sentry-go) - [GitHub Project](https://github.com/getsentry/sentry-go)
- [![GoDoc](https://godoc.org/github.com/getsentry/sentry-go?status.svg)](https://godoc.org/github.com/getsentry/sentry-go)
- [![go.dev](https://img.shields.io/badge/go.dev-pkg-007d9c.svg?style=flat)](https://pkg.go.dev/github.com/getsentry/sentry-go) - [![go.dev](https://img.shields.io/badge/go.dev-pkg-007d9c.svg?style=flat)](https://pkg.go.dev/github.com/getsentry/sentry-go)
- [![Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/platforms/go/) - [![Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/platforms/go/)
- [![Discussions](https://img.shields.io/github/discussions/getsentry/sentry-go.svg)](https://github.com/getsentry/sentry-go/discussions) - [![Discussions](https://img.shields.io/github/discussions/getsentry/sentry-go.svg)](https://github.com/getsentry/sentry-go/discussions)

View File

@@ -87,6 +87,10 @@ type MonitorConfig struct { //nolint: maligned // prefer readability over optima
// A tz database string representing the timezone which the monitor's execution schedule is in. // A tz database string representing the timezone which the monitor's execution schedule is in.
// See: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones // See: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
Timezone string `json:"timezone,omitempty"` Timezone string `json:"timezone,omitempty"`
// The number of consecutive failed check-ins it takes before an issue is created.
FailureIssueThreshold int64 `json:"failure_issue_threshold,omitempty"`
// The number of consecutive OK check-ins it takes before an issue is resolved.
RecoveryThreshold int64 `json:"recovery_threshold,omitempty"`
} }
type CheckIn struct { //nolint: maligned // prefer readability over optimal memory layout type CheckIn struct { //nolint: maligned // prefer readability over optimal memory layout

View File

@@ -90,11 +90,10 @@ func NewDsn(rawURL string) (*Dsn, error) {
// Port // Port
var port int var port int
if parsedURL.Port() != "" { if parsedURL.Port() != "" {
parsedPort, err := strconv.Atoi(parsedURL.Port()) port, err = strconv.Atoi(parsedURL.Port())
if err != nil { if err != nil {
return nil, &DsnParseError{"invalid port"} return nil, &DsnParseError{"invalid port"}
} }
port = parsedPort
} else { } else {
port = scheme.defaultPort() port = scheme.defaultPort()
} }

View File

@@ -140,7 +140,7 @@ func (iei *ignoreErrorsIntegration) processor(event *Event, _ *EventHint) *Event
for _, suspect := range suspects { for _, suspect := range suspects {
for _, pattern := range iei.ignoreErrors { for _, pattern := range iei.ignoreErrors {
if pattern.Match([]byte(suspect)) { if pattern.Match([]byte(suspect)) || strings.Contains(suspect, pattern.String()) {
Logger.Printf("Event dropped due to being matched by `IgnoreErrors` option."+ Logger.Printf("Event dropped due to being matched by `IgnoreErrors` option."+
"| Value matched: %s | Filter used: %s", suspect, pattern) "| Value matched: %s | Filter used: %s", suspect, pattern)
return nil return nil
@@ -202,7 +202,7 @@ func (iei *ignoreTransactionsIntegration) processor(event *Event, _ *EventHint)
} }
for _, pattern := range iei.ignoreTransactions { for _, pattern := range iei.ignoreTransactions {
if pattern.Match([]byte(suspect)) { if pattern.Match([]byte(suspect)) || strings.Contains(suspect, pattern.String()) {
Logger.Printf("Transaction dropped due to being matched by `IgnoreTransactions` option."+ Logger.Printf("Transaction dropped due to being matched by `IgnoreTransactions` option."+
"| Value matched: %s | Filter used: %s", suspect, pattern) "| Value matched: %s | Filter used: %s", suspect, pattern)
return nil return nil

View File

@@ -3,6 +3,7 @@ package sentry
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
@@ -24,6 +25,9 @@ const profileType = "profile"
// checkInType is the type of a check in event. // checkInType is the type of a check in event.
const checkInType = "check_in" const checkInType = "check_in"
// metricType is the type of a metric event.
const metricType = "statsd"
// Level marks the severity of the event. // Level marks the severity of the event.
type Level string type Level string
@@ -36,15 +40,6 @@ const (
LevelFatal Level = "fatal" LevelFatal Level = "fatal"
) )
func getSensitiveHeaders() map[string]bool {
return map[string]bool{
"Authorization": true,
"Cookie": true,
"X-Forwarded-For": true,
"X-Real-Ip": true,
}
}
// SdkInfo contains all metadata about about the SDK being used. // SdkInfo contains all metadata about about the SDK being used.
type SdkInfo struct { type SdkInfo struct {
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
@@ -170,6 +165,13 @@ type Request struct {
Env map[string]string `json:"env,omitempty"` Env map[string]string `json:"env,omitempty"`
} }
var sensitiveHeaders = map[string]struct{}{
"Authorization": {},
"Cookie": {},
"X-Forwarded-For": {},
"X-Real-Ip": {},
}
// NewRequest returns a new Sentry Request from the given http.Request. // NewRequest returns a new Sentry Request from the given http.Request.
// //
// NewRequest avoids operations that depend on network access. In particular, it // NewRequest avoids operations that depend on network access. In particular, it
@@ -200,7 +202,6 @@ func NewRequest(r *http.Request) *Request {
env = map[string]string{"REMOTE_ADDR": addr, "REMOTE_PORT": port} env = map[string]string{"REMOTE_ADDR": addr, "REMOTE_PORT": port}
} }
} else { } else {
sensitiveHeaders := getSensitiveHeaders()
for k, v := range r.Header { for k, v := range r.Header {
if _, ok := sensitiveHeaders[k]; !ok { if _, ok := sensitiveHeaders[k]; !ok {
headers[k] = strings.Join(v, ",") headers[k] = strings.Join(v, ",")
@@ -222,11 +223,15 @@ func NewRequest(r *http.Request) *Request {
// Mechanism is the mechanism by which an exception was generated and handled. // Mechanism is the mechanism by which an exception was generated and handled.
type Mechanism struct { type Mechanism struct {
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
Description string `json:"description,omitempty"` Description string `json:"description,omitempty"`
HelpLink string `json:"help_link,omitempty"` HelpLink string `json:"help_link,omitempty"`
Handled *bool `json:"handled,omitempty"` Source string `json:"source,omitempty"`
Data map[string]interface{} `json:"data,omitempty"` Handled *bool `json:"handled,omitempty"`
ParentID *int `json:"parent_id,omitempty"`
ExceptionID int `json:"exception_id"`
IsExceptionGroup bool `json:"is_exception_group,omitempty"`
Data map[string]any `json:"data,omitempty"`
} }
// SetUnhandled indicates that the exception is an unhandled exception, i.e. // SetUnhandled indicates that the exception is an unhandled exception, i.e.
@@ -318,6 +323,7 @@ type Event struct {
Exception []Exception `json:"exception,omitempty"` Exception []Exception `json:"exception,omitempty"`
DebugMeta *DebugMeta `json:"debug_meta,omitempty"` DebugMeta *DebugMeta `json:"debug_meta,omitempty"`
Attachments []*Attachment `json:"-"` Attachments []*Attachment `json:"-"`
Metrics []Metric `json:"-"`
// The fields below are only relevant for transactions. // The fields below are only relevant for transactions.
@@ -339,27 +345,43 @@ type Event struct {
// SetException appends the unwrapped errors to the event's exception list. // SetException appends the unwrapped errors to the event's exception list.
// //
// maxErrorDepth is the maximum depth of the error chain we will look // maxErrorDepth is the maximum depth of the error chain we will look
// into while unwrapping the errors. // into while unwrapping the errors. If maxErrorDepth is -1, we will
// unwrap all errors in the chain.
func (e *Event) SetException(exception error, maxErrorDepth int) { func (e *Event) SetException(exception error, maxErrorDepth int) {
err := exception if exception == nil {
if err == nil {
return return
} }
for i := 0; i < maxErrorDepth && err != nil; i++ { err := exception
for i := 0; err != nil && (i < maxErrorDepth || maxErrorDepth == -1); i++ {
// Add the current error to the exception slice with its details
e.Exception = append(e.Exception, Exception{ e.Exception = append(e.Exception, Exception{
Value: err.Error(), Value: err.Error(),
Type: reflect.TypeOf(err).String(), Type: reflect.TypeOf(err).String(),
Stacktrace: ExtractStacktrace(err), Stacktrace: ExtractStacktrace(err),
}) })
switch previous := err.(type) {
case interface{ Unwrap() error }: // Attempt to unwrap the error using the standard library's Unwrap method.
err = previous.Unwrap() // If errors.Unwrap returns nil, it means either there is no error to unwrap,
case interface{ Cause() error }: // or the error does not implement the Unwrap method.
err = previous.Cause() unwrappedErr := errors.Unwrap(err)
default:
err = nil if unwrappedErr != nil {
// The error was successfully unwrapped using the standard library's Unwrap method.
err = unwrappedErr
continue
} }
cause, ok := err.(interface{ Cause() error })
if !ok {
// We cannot unwrap the error further.
break
}
// The error implements the Cause method, indicating it may have been wrapped
// using the github.com/pkg/errors package.
err = cause.Cause()
} }
// Add a trace of the current stack to the most recent error in a chain if // Add a trace of the current stack to the most recent error in a chain if
@@ -370,8 +392,23 @@ func (e *Event) SetException(exception error, maxErrorDepth int) {
e.Exception[0].Stacktrace = NewStacktrace() e.Exception[0].Stacktrace = NewStacktrace()
} }
if len(e.Exception) <= 1 {
return
}
// event.Exception should be sorted such that the most recent error is last. // event.Exception should be sorted such that the most recent error is last.
reverse(e.Exception) reverse(e.Exception)
for i := range e.Exception {
e.Exception[i].Mechanism = &Mechanism{
IsExceptionGroup: true,
ExceptionID: i,
}
if i == 0 {
continue
}
e.Exception[i].Mechanism.ParentID = Pointer(i - 1)
}
} }
// TODO: Event.Contexts map[string]interface{} => map[string]EventContext, // TODO: Event.Contexts map[string]interface{} => map[string]EventContext,
@@ -492,13 +529,12 @@ func (e *Event) checkInMarshalJSON() ([]byte, error) {
// NewEvent creates a new Event. // NewEvent creates a new Event.
func NewEvent() *Event { func NewEvent() *Event {
event := Event{ return &Event{
Contexts: make(map[string]Context), Contexts: make(map[string]Context),
Extra: make(map[string]interface{}), Extra: make(map[string]interface{}),
Tags: make(map[string]string), Tags: make(map[string]string),
Modules: make(map[string]string), Modules: make(map[string]string),
} }
return &event
} }
// Thread specifies threads that were running at the time of an event. // Thread specifies threads that were running at the time of an event.

View File

@@ -32,15 +32,14 @@ var knownCategories = map[Category]struct{}{
// String returns the category formatted for debugging. // String returns the category formatted for debugging.
func (c Category) String() string { func (c Category) String() string {
switch c { if c == "" {
case "":
return "CategoryAll" return "CategoryAll"
default:
caser := cases.Title(language.English)
rv := "Category"
for _, w := range strings.Fields(string(c)) {
rv += caser.String(w)
}
return rv
} }
caser := cases.Title(language.English)
rv := "Category"
for _, w := range strings.Fields(string(c)) {
rv += caser.String(w)
}
return rv
} }

427
vendor/github.com/getsentry/sentry-go/metrics.go generated vendored Normal file
View File

@@ -0,0 +1,427 @@
package sentry
import (
"fmt"
"hash/crc32"
"math"
"regexp"
"sort"
"strings"
)
type (
NumberOrString interface {
int | string
}
void struct{}
)
var (
member void
keyRegex = regexp.MustCompile(`[^a-zA-Z0-9_/.-]+`)
valueRegex = regexp.MustCompile(`[^\w\d\s_:/@\.{}\[\]$-]+`)
unitRegex = regexp.MustCompile(`[^a-z]+`)
)
type MetricUnit struct {
unit string
}
func (m MetricUnit) toString() string {
return m.unit
}
func NanoSecond() MetricUnit {
return MetricUnit{
"nanosecond",
}
}
func MicroSecond() MetricUnit {
return MetricUnit{
"microsecond",
}
}
func MilliSecond() MetricUnit {
return MetricUnit{
"millisecond",
}
}
func Second() MetricUnit {
return MetricUnit{
"second",
}
}
func Minute() MetricUnit {
return MetricUnit{
"minute",
}
}
func Hour() MetricUnit {
return MetricUnit{
"hour",
}
}
func Day() MetricUnit {
return MetricUnit{
"day",
}
}
func Week() MetricUnit {
return MetricUnit{
"week",
}
}
func Bit() MetricUnit {
return MetricUnit{
"bit",
}
}
func Byte() MetricUnit {
return MetricUnit{
"byte",
}
}
func KiloByte() MetricUnit {
return MetricUnit{
"kilobyte",
}
}
func KibiByte() MetricUnit {
return MetricUnit{
"kibibyte",
}
}
func MegaByte() MetricUnit {
return MetricUnit{
"megabyte",
}
}
func MebiByte() MetricUnit {
return MetricUnit{
"mebibyte",
}
}
func GigaByte() MetricUnit {
return MetricUnit{
"gigabyte",
}
}
func GibiByte() MetricUnit {
return MetricUnit{
"gibibyte",
}
}
func TeraByte() MetricUnit {
return MetricUnit{
"terabyte",
}
}
func TebiByte() MetricUnit {
return MetricUnit{
"tebibyte",
}
}
func PetaByte() MetricUnit {
return MetricUnit{
"petabyte",
}
}
func PebiByte() MetricUnit {
return MetricUnit{
"pebibyte",
}
}
func ExaByte() MetricUnit {
return MetricUnit{
"exabyte",
}
}
func ExbiByte() MetricUnit {
return MetricUnit{
"exbibyte",
}
}
func Ratio() MetricUnit {
return MetricUnit{
"ratio",
}
}
func Percent() MetricUnit {
return MetricUnit{
"percent",
}
}
func CustomUnit(unit string) MetricUnit {
return MetricUnit{
unitRegex.ReplaceAllString(unit, ""),
}
}
type Metric interface {
GetType() string
GetTags() map[string]string
GetKey() string
GetUnit() string
GetTimestamp() int64
SerializeValue() string
SerializeTags() string
}
type abstractMetric struct {
key string
unit MetricUnit
tags map[string]string
// A unix timestamp (full seconds elapsed since 1970-01-01 00:00 UTC).
timestamp int64
}
func (am abstractMetric) GetTags() map[string]string {
return am.tags
}
func (am abstractMetric) GetKey() string {
return am.key
}
func (am abstractMetric) GetUnit() string {
return am.unit.toString()
}
func (am abstractMetric) GetTimestamp() int64 {
return am.timestamp
}
func (am abstractMetric) SerializeTags() string {
var sb strings.Builder
values := make([]string, 0, len(am.tags))
for k := range am.tags {
values = append(values, k)
}
sortSlice(values)
for _, key := range values {
val := sanitizeValue(am.tags[key])
key = sanitizeKey(key)
sb.WriteString(fmt.Sprintf("%s:%s,", key, val))
}
s := sb.String()
if len(s) > 0 {
s = s[:len(s)-1]
}
return s
}
// Counter Metric.
type CounterMetric struct {
value float64
abstractMetric
}
func (c *CounterMetric) Add(value float64) {
c.value += value
}
func (c CounterMetric) GetType() string {
return "c"
}
func (c CounterMetric) SerializeValue() string {
return fmt.Sprintf(":%v", c.value)
}
// timestamp: A unix timestamp (full seconds elapsed since 1970-01-01 00:00 UTC).
func NewCounterMetric(key string, unit MetricUnit, tags map[string]string, timestamp int64, value float64) CounterMetric {
am := abstractMetric{
key,
unit,
tags,
timestamp,
}
return CounterMetric{
value,
am,
}
}
// Distribution Metric.
type DistributionMetric struct {
values []float64
abstractMetric
}
func (d *DistributionMetric) Add(value float64) {
d.values = append(d.values, value)
}
func (d DistributionMetric) GetType() string {
return "d"
}
func (d DistributionMetric) SerializeValue() string {
var sb strings.Builder
for _, el := range d.values {
sb.WriteString(fmt.Sprintf(":%v", el))
}
return sb.String()
}
// timestamp: A unix timestamp (full seconds elapsed since 1970-01-01 00:00 UTC).
func NewDistributionMetric(key string, unit MetricUnit, tags map[string]string, timestamp int64, value float64) DistributionMetric {
am := abstractMetric{
key,
unit,
tags,
timestamp,
}
return DistributionMetric{
[]float64{value},
am,
}
}
// Gauge Metric.
type GaugeMetric struct {
last float64
min float64
max float64
sum float64
count float64
abstractMetric
}
func (g *GaugeMetric) Add(value float64) {
g.last = value
g.min = math.Min(g.min, value)
g.max = math.Max(g.max, value)
g.sum += value
g.count++
}
func (g GaugeMetric) GetType() string {
return "g"
}
func (g GaugeMetric) SerializeValue() string {
return fmt.Sprintf(":%v:%v:%v:%v:%v", g.last, g.min, g.max, g.sum, g.count)
}
// timestamp: A unix timestamp (full seconds elapsed since 1970-01-01 00:00 UTC).
func NewGaugeMetric(key string, unit MetricUnit, tags map[string]string, timestamp int64, value float64) GaugeMetric {
am := abstractMetric{
key,
unit,
tags,
timestamp,
}
return GaugeMetric{
value, // last
value, // min
value, // max
value, // sum
value, // count
am,
}
}
// Set Metric.
type SetMetric[T NumberOrString] struct {
values map[T]void
abstractMetric
}
func (s *SetMetric[T]) Add(value T) {
s.values[value] = member
}
func (s SetMetric[T]) GetType() string {
return "s"
}
func (s SetMetric[T]) SerializeValue() string {
_hash := func(s string) uint32 {
return crc32.ChecksumIEEE([]byte(s))
}
values := make([]T, 0, len(s.values))
for k := range s.values {
values = append(values, k)
}
sortSlice(values)
var sb strings.Builder
for _, el := range values {
switch any(el).(type) {
case int:
sb.WriteString(fmt.Sprintf(":%v", el))
case string:
s := fmt.Sprintf("%v", el)
sb.WriteString(fmt.Sprintf(":%d", _hash(s)))
}
}
return sb.String()
}
// timestamp: A unix timestamp (full seconds elapsed since 1970-01-01 00:00 UTC).
func NewSetMetric[T NumberOrString](key string, unit MetricUnit, tags map[string]string, timestamp int64, value T) SetMetric[T] {
am := abstractMetric{
key,
unit,
tags,
timestamp,
}
return SetMetric[T]{
map[T]void{
value: member,
},
am,
}
}
func sanitizeKey(s string) string {
return keyRegex.ReplaceAllString(s, "_")
}
func sanitizeValue(s string) string {
return valueRegex.ReplaceAllString(s, "")
}
type Ordered interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~string
}
func sortSlice[T Ordered](s []T) {
sort.Slice(s, func(i, j int) bool {
return s[i] < s[j]
})
}

View File

@@ -6,7 +6,7 @@ import (
) )
// The version of the SDK. // The version of the SDK.
const SDKVersion = "0.27.0" const SDKVersion = "0.28.1"
// apiVersion is the minimum version of the Sentry API compatible with the // apiVersion is the minimum version of the Sentry API compatible with the
// sentry-go SDK. // sentry-go SDK.
@@ -72,18 +72,17 @@ func Recover() *EventID {
// RecoverWithContext captures a panic and passes relevant context object. // RecoverWithContext captures a panic and passes relevant context object.
func RecoverWithContext(ctx context.Context) *EventID { func RecoverWithContext(ctx context.Context) *EventID {
if err := recover(); err != nil { err := recover()
var hub *Hub if err == nil {
return nil
if HasHubOnContext(ctx) {
hub = GetHubFromContext(ctx)
} else {
hub = CurrentHub()
}
return hub.RecoverWithContext(ctx, err)
} }
return nil
hub := GetHubFromContext(ctx)
if hub == nil {
hub = CurrentHub()
}
return hub.RecoverWithContext(ctx, err)
} }
// WithScope is a shorthand for CurrentHub().WithScope. // WithScope is a shorthand for CurrentHub().WithScope.

View File

@@ -7,8 +7,8 @@ import (
// Checks whether the transaction should be profiled (according to ProfilesSampleRate) // Checks whether the transaction should be profiled (according to ProfilesSampleRate)
// and starts a profiler if so. // and starts a profiler if so.
func (span *Span) sampleTransactionProfile() { func (s *Span) sampleTransactionProfile() {
var sampleRate = span.clientOptions().ProfilesSampleRate var sampleRate = s.clientOptions().ProfilesSampleRate
switch { switch {
case sampleRate < 0.0 || sampleRate > 1.0: case sampleRate < 0.0 || sampleRate > 1.0:
Logger.Printf("Skipping transaction profiling: ProfilesSampleRate out of range [0.0, 1.0]: %f\n", sampleRate) Logger.Printf("Skipping transaction profiling: ProfilesSampleRate out of range [0.0, 1.0]: %f\n", sampleRate)
@@ -19,7 +19,7 @@ func (span *Span) sampleTransactionProfile() {
if globalProfiler == nil { if globalProfiler == nil {
Logger.Println("Skipping transaction profiling: the profiler couldn't be started") Logger.Println("Skipping transaction profiling: the profiler couldn't be started")
} else { } else {
span.collectProfile = collectTransactionProfile s.collectProfile = collectTransactionProfile
} }
} }
} }
@@ -69,22 +69,27 @@ func (info *profileInfo) UpdateFromEvent(event *Event) {
info.Dist = event.Dist info.Dist = event.Dist
info.Transaction.ID = event.EventID info.Transaction.ID = event.EventID
getStringFromContext := func(context map[string]interface{}, originalValue, key string) string {
v, ok := context[key]
if !ok {
return originalValue
}
if s, ok := v.(string); ok {
return s
}
return originalValue
}
if runtimeContext, ok := event.Contexts["runtime"]; ok { if runtimeContext, ok := event.Contexts["runtime"]; ok {
if value, ok := runtimeContext["name"]; !ok { info.Runtime.Name = getStringFromContext(runtimeContext, info.Runtime.Name, "name")
info.Runtime.Name = value.(string) info.Runtime.Version = getStringFromContext(runtimeContext, info.Runtime.Version, "version")
}
if value, ok := runtimeContext["version"]; !ok {
info.Runtime.Version = value.(string)
}
} }
if osContext, ok := event.Contexts["os"]; ok { if osContext, ok := event.Contexts["os"]; ok {
if value, ok := osContext["name"]; !ok { info.OS.Name = getStringFromContext(osContext, info.OS.Name, "name")
info.OS.Name = value.(string)
}
} }
if deviceContext, ok := event.Contexts["device"]; ok { if deviceContext, ok := event.Contexts["device"]; ok {
if value, ok := deviceContext["arch"]; !ok { info.Device.Architecture = getStringFromContext(deviceContext, info.Device.Architecture, "arch")
info.Device.Architecture = value.(string)
}
} }
} }

View File

@@ -169,11 +169,11 @@ func StartSpan(ctx context.Context, operation string, options ...SpanOption) *Sp
span.Sampled = span.sample() span.Sampled = span.sample()
span.recorder = &spanRecorder{}
if hasParent { if hasParent {
span.recorder = parent.spanRecorder() span.recorder = parent.spanRecorder()
} else {
span.recorder = &spanRecorder{}
} }
span.recorder.record(&span) span.recorder.record(&span)
hub := hubFromContext(ctx) hub := hubFromContext(ctx)
@@ -226,7 +226,11 @@ func (s *Span) SetTag(name, value string) {
// SetData sets a data on the span. It is recommended to use SetData instead of // SetData sets a data on the span. It is recommended to use SetData instead of
// accessing the data map directly as SetData takes care of initializing the map // accessing the data map directly as SetData takes care of initializing the map
// when necessary. // when necessary.
func (s *Span) SetData(name, value string) { func (s *Span) SetData(name string, value interface{}) {
if value == nil {
return
}
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()

View File

@@ -2,6 +2,7 @@ package sentry
import ( import (
"bytes" "bytes"
"context"
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"errors" "errors"
@@ -94,6 +95,55 @@ func getRequestBodyFromEvent(event *Event) []byte {
return nil return nil
} }
func marshalMetrics(metrics []Metric) []byte {
var b bytes.Buffer
for i, metric := range metrics {
b.WriteString(metric.GetKey())
if unit := metric.GetUnit(); unit != "" {
b.WriteString(fmt.Sprintf("@%s", unit))
}
b.WriteString(fmt.Sprintf("%s|%s", metric.SerializeValue(), metric.GetType()))
if serializedTags := metric.SerializeTags(); serializedTags != "" {
b.WriteString(fmt.Sprintf("|#%s", serializedTags))
}
b.WriteString(fmt.Sprintf("|T%d", metric.GetTimestamp()))
if i < len(metrics)-1 {
b.WriteString("\n")
}
}
return b.Bytes()
}
func encodeMetric(enc *json.Encoder, b io.Writer, metrics []Metric) error {
body := marshalMetrics(metrics)
// Item header
err := enc.Encode(struct {
Type string `json:"type"`
Length int `json:"length"`
}{
Type: metricType,
Length: len(body),
})
if err != nil {
return err
}
// metric payload
if _, err = b.Write(body); err != nil {
return err
}
// "Envelopes should be terminated with a trailing newline."
//
// [1]: https://develop.sentry.dev/sdk/envelopes/#envelopes
if _, err := b.Write([]byte("\n")); err != nil {
return err
}
return err
}
func encodeAttachment(enc *json.Encoder, b io.Writer, attachment *Attachment) error { func encodeAttachment(enc *json.Encoder, b io.Writer, attachment *Attachment) error {
// Attachment header // Attachment header
err := enc.Encode(struct { err := enc.Encode(struct {
@@ -175,11 +225,15 @@ func envelopeFromBody(event *Event, dsn *Dsn, sentAt time.Time, body json.RawMes
return nil, err return nil, err
} }
if event.Type == transactionType || event.Type == checkInType { switch event.Type {
case transactionType, checkInType:
err = encodeEnvelopeItem(enc, event.Type, body) err = encodeEnvelopeItem(enc, event.Type, body)
} else { case metricType:
err = encodeMetric(enc, &b, event.Metrics)
default:
err = encodeEnvelopeItem(enc, eventType, body) err = encodeEnvelopeItem(enc, eventType, body)
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -206,7 +260,7 @@ func envelopeFromBody(event *Event, dsn *Dsn, sentAt time.Time, body json.RawMes
return &b, nil return &b, nil
} }
func getRequestFromEvent(event *Event, dsn *Dsn) (r *http.Request, err error) { func getRequestFromEvent(ctx context.Context, event *Event, dsn *Dsn) (r *http.Request, err error) {
defer func() { defer func() {
if r != nil { if r != nil {
r.Header.Set("User-Agent", fmt.Sprintf("%s/%s", event.Sdk.Name, event.Sdk.Version)) r.Header.Set("User-Agent", fmt.Sprintf("%s/%s", event.Sdk.Name, event.Sdk.Version))
@@ -233,7 +287,13 @@ func getRequestFromEvent(event *Event, dsn *Dsn) (r *http.Request, err error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return http.NewRequest(
if ctx == nil {
ctx = context.Background()
}
return http.NewRequestWithContext(
ctx,
http.MethodPost, http.MethodPost,
dsn.GetAPIURL().String(), dsn.GetAPIURL().String(),
envelope, envelope,
@@ -344,8 +404,13 @@ func (t *HTTPTransport) Configure(options ClientOptions) {
}) })
} }
// SendEvent assembles a new packet out of Event and sends it to remote server. // SendEvent assembles a new packet out of Event and sends it to the remote server.
func (t *HTTPTransport) SendEvent(event *Event) { func (t *HTTPTransport) SendEvent(event *Event) {
t.SendEventWithContext(context.Background(), event)
}
// SendEventWithContext assembles a new packet out of Event and sends it to the remote server.
func (t *HTTPTransport) SendEventWithContext(ctx context.Context, event *Event) {
if t.dsn == nil { if t.dsn == nil {
return return
} }
@@ -356,7 +421,7 @@ func (t *HTTPTransport) SendEvent(event *Event) {
return return
} }
request, err := getRequestFromEvent(event, t.dsn) request, err := getRequestFromEvent(ctx, event, t.dsn)
if err != nil { if err != nil {
return return
} }
@@ -478,6 +543,13 @@ func (t *HTTPTransport) worker() {
Logger.Printf("There was an issue with sending an event: %v", err) Logger.Printf("There was an issue with sending an event: %v", err)
continue continue
} }
if response.StatusCode >= 400 && response.StatusCode <= 599 {
b, err := io.ReadAll(response.Body)
if err != nil {
Logger.Printf("Error while reading response code: %v", err)
}
Logger.Printf("Sending %s failed with the following error: %s", eventType, string(b))
}
t.mu.Lock() t.mu.Lock()
t.limits.Merge(ratelimit.FromResponse(response)) t.limits.Merge(ratelimit.FromResponse(response))
t.mu.Unlock() t.mu.Unlock()
@@ -567,8 +639,13 @@ func (t *HTTPSyncTransport) Configure(options ClientOptions) {
} }
} }
// SendEvent assembles a new packet out of Event and sends it to remote server. // SendEvent assembles a new packet out of Event and sends it to the remote server.
func (t *HTTPSyncTransport) SendEvent(event *Event) { func (t *HTTPSyncTransport) SendEvent(event *Event) {
t.SendEventWithContext(context.Background(), event)
}
// SendEventWithContext assembles a new packet out of Event and sends it to the remote server.
func (t *HTTPSyncTransport) SendEventWithContext(ctx context.Context, event *Event) {
if t.dsn == nil { if t.dsn == nil {
return return
} }
@@ -577,15 +654,18 @@ func (t *HTTPSyncTransport) SendEvent(event *Event) {
return return
} }
request, err := getRequestFromEvent(event, t.dsn) request, err := getRequestFromEvent(ctx, event, t.dsn)
if err != nil { if err != nil {
return return
} }
var eventType string var eventType string
if event.Type == transactionType { switch {
case event.Type == transactionType:
eventType = "transaction" eventType = "transaction"
} else { case event.Type == metricType:
eventType = metricType
default:
eventType = fmt.Sprintf("%s event", event.Level) eventType = fmt.Sprintf("%s event", event.Level)
} }
Logger.Printf( Logger.Printf(
@@ -601,6 +681,14 @@ func (t *HTTPSyncTransport) SendEvent(event *Event) {
Logger.Printf("There was an issue with sending an event: %v", err) Logger.Printf("There was an issue with sending an event: %v", err)
return return
} }
if response.StatusCode >= 400 && response.StatusCode <= 599 {
b, err := io.ReadAll(response.Body)
if err != nil {
Logger.Printf("Error while reading response code: %v", err)
}
Logger.Printf("Sending %s failed with the following error: %s", eventType, string(b))
}
t.mu.Lock() t.mu.Lock()
t.limits.Merge(ratelimit.FromResponse(response)) t.limits.Merge(ratelimit.FromResponse(response))
t.mu.Unlock() t.mu.Unlock()

View File

@@ -112,3 +112,7 @@ func revisionFromBuildInfo(info *debug.BuildInfo) string {
return "" return ""
} }
func Pointer[T any](v T) *T {
return &v
}

View File

@@ -60,7 +60,7 @@ func main() {
// Output: {"time":1516134303,"level":"debug","message":"hello world"} // Output: {"time":1516134303,"level":"debug","message":"hello world"}
``` ```
> Note: By default log writes to `os.Stderr` > Note: By default log writes to `os.Stderr`
> Note: The default log level for `log.Print` is *debug* > Note: The default log level for `log.Print` is *trace*
### Contextual Logging ### Contextual Logging
@@ -412,15 +412,7 @@ Equivalent of `Lshortfile`:
```go ```go
zerolog.CallerMarshalFunc = func(pc uintptr, file string, line int) string { zerolog.CallerMarshalFunc = func(pc uintptr, file string, line int) string {
short := file return filepath.Base(file) + ":" + strconv.Itoa(line)
for i := len(file) - 1; i > 0; i-- {
if file[i] == '/' {
short = file[i+1:]
break
}
}
file = short
return file + ":" + strconv.Itoa(line)
} }
log.Logger = log.With().Caller().Logger() log.Logger = log.With().Caller().Logger()
log.Info().Msg("hello world") log.Info().Msg("hello world")
@@ -646,10 +638,14 @@ Some settings can be changed and will be applied to all loggers:
* `zerolog.LevelFieldName`: Can be set to customize level field name. * `zerolog.LevelFieldName`: Can be set to customize level field name.
* `zerolog.MessageFieldName`: Can be set to customize message field name. * `zerolog.MessageFieldName`: Can be set to customize message field name.
* `zerolog.ErrorFieldName`: Can be set to customize `Err` field name. * `zerolog.ErrorFieldName`: Can be set to customize `Err` field name.
* `zerolog.TimeFieldFormat`: Can be set to customize `Time` field value formatting. If set with `zerolog.TimeFormatUnix`, `zerolog.TimeFormatUnixMs` or `zerolog.TimeFormatUnixMicro`, times are formated as UNIX timestamp. * `zerolog.TimeFieldFormat`: Can be set to customize `Time` field value formatting. If set with `zerolog.TimeFormatUnix`, `zerolog.TimeFormatUnixMs` or `zerolog.TimeFormatUnixMicro`, times are formatted as UNIX timestamp.
* `zerolog.DurationFieldUnit`: Can be set to customize the unit for time.Duration type fields added by `Dur` (default: `time.Millisecond`). * `zerolog.DurationFieldUnit`: Can be set to customize the unit for time.Duration type fields added by `Dur` (default: `time.Millisecond`).
* `zerolog.DurationFieldInteger`: If set to `true`, `Dur` fields are formatted as integers instead of floats (default: `false`). * `zerolog.DurationFieldInteger`: If set to `true`, `Dur` fields are formatted as integers instead of floats (default: `false`).
* `zerolog.ErrorHandler`: Called whenever zerolog fails to write an event on its output. If not set, an error is printed on the stderr. This handler must be thread safe and non-blocking. * `zerolog.ErrorHandler`: Called whenever zerolog fails to write an event on its output. If not set, an error is printed on the stderr. This handler must be thread safe and non-blocking.
* `zerolog.FloatingPointPrecision`: If set to a value other than -1, controls the number
of digits when formatting float numbers in JSON. See
[strconv.FormatFloat](https://pkg.go.dev/strconv#FormatFloat)
for more details.
## Field Types ## Field Types

View File

@@ -183,13 +183,13 @@ func (a *Array) Uint64(i uint64) *Array {
// Float32 appends f as a float32 to the array. // Float32 appends f as a float32 to the array.
func (a *Array) Float32(f float32) *Array { func (a *Array) Float32(f float32) *Array {
a.buf = enc.AppendFloat32(enc.AppendArrayDelim(a.buf), f) a.buf = enc.AppendFloat32(enc.AppendArrayDelim(a.buf), f, FloatingPointPrecision)
return a return a
} }
// Float64 appends f as a float64 to the array. // Float64 appends f as a float64 to the array.
func (a *Array) Float64(f float64) *Array { func (a *Array) Float64(f float64) *Array {
a.buf = enc.AppendFloat64(enc.AppendArrayDelim(a.buf), f) a.buf = enc.AppendFloat64(enc.AppendArrayDelim(a.buf), f, FloatingPointPrecision)
return a return a
} }
@@ -201,7 +201,7 @@ func (a *Array) Time(t time.Time) *Array {
// Dur appends d to the array. // Dur appends d to the array.
func (a *Array) Dur(d time.Duration) *Array { func (a *Array) Dur(d time.Duration) *Array {
a.buf = enc.AppendDuration(enc.AppendArrayDelim(a.buf), d, DurationFieldUnit, DurationFieldInteger) a.buf = enc.AppendDuration(enc.AppendArrayDelim(a.buf), d, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
return a return a
} }

View File

@@ -28,6 +28,8 @@ const (
colorBold = 1 colorBold = 1
colorDarkGray = 90 colorDarkGray = 90
unknownLevel = "???"
) )
var ( var (
@@ -57,12 +59,21 @@ type ConsoleWriter struct {
// TimeFormat specifies the format for timestamp in output. // TimeFormat specifies the format for timestamp in output.
TimeFormat string TimeFormat string
// TimeLocation tells ConsoleWriters default FormatTimestamp
// how to localize the time.
TimeLocation *time.Location
// PartsOrder defines the order of parts in output. // PartsOrder defines the order of parts in output.
PartsOrder []string PartsOrder []string
// PartsExclude defines parts to not display in output. // PartsExclude defines parts to not display in output.
PartsExclude []string PartsExclude []string
// FieldsOrder defines the order of contextual fields in output.
FieldsOrder []string
fieldIsOrdered map[string]int
// FieldsExclude defines contextual fields to not display in output. // FieldsExclude defines contextual fields to not display in output.
FieldsExclude []string FieldsExclude []string
@@ -83,9 +94,9 @@ type ConsoleWriter struct {
// NewConsoleWriter creates and initializes a new ConsoleWriter. // NewConsoleWriter creates and initializes a new ConsoleWriter.
func NewConsoleWriter(options ...func(w *ConsoleWriter)) ConsoleWriter { func NewConsoleWriter(options ...func(w *ConsoleWriter)) ConsoleWriter {
w := ConsoleWriter{ w := ConsoleWriter{
Out: os.Stdout, Out: os.Stdout,
TimeFormat: consoleDefaultTimeFormat, TimeFormat: consoleDefaultTimeFormat,
PartsOrder: consoleDefaultPartsOrder(), PartsOrder: consoleDefaultPartsOrder(),
} }
for _, opt := range options { for _, opt := range options {
@@ -185,7 +196,12 @@ func (w ConsoleWriter) writeFields(evt map[string]interface{}, buf *bytes.Buffer
} }
fields = append(fields, field) fields = append(fields, field)
} }
sort.Strings(fields)
if len(w.FieldsOrder) > 0 {
w.orderFields(fields)
} else {
sort.Strings(fields)
}
// Write space only if something has already been written to the buffer, and if there are fields. // Write space only if something has already been written to the buffer, and if there are fields.
if buf.Len() > 0 && len(fields) > 0 { if buf.Len() > 0 && len(fields) > 0 {
@@ -284,7 +300,7 @@ func (w ConsoleWriter) writePart(buf *bytes.Buffer, evt map[string]interface{},
} }
case TimestampFieldName: case TimestampFieldName:
if w.FormatTimestamp == nil { if w.FormatTimestamp == nil {
f = consoleDefaultFormatTimestamp(w.TimeFormat, w.NoColor) f = consoleDefaultFormatTimestamp(w.TimeFormat, w.TimeLocation, w.NoColor)
} else { } else {
f = w.FormatTimestamp f = w.FormatTimestamp
} }
@@ -318,6 +334,32 @@ func (w ConsoleWriter) writePart(buf *bytes.Buffer, evt map[string]interface{},
} }
} }
// orderFields takes an array of field names and an array representing field order
// and returns an array with any ordered fields at the beginning, in order,
// and the remaining fields after in their original order.
func (w ConsoleWriter) orderFields(fields []string) {
if w.fieldIsOrdered == nil {
w.fieldIsOrdered = make(map[string]int)
for i, fieldName := range w.FieldsOrder {
w.fieldIsOrdered[fieldName] = i
}
}
sort.Slice(fields, func(i, j int) bool {
ii, iOrdered := w.fieldIsOrdered[fields[i]]
jj, jOrdered := w.fieldIsOrdered[fields[j]]
if iOrdered && jOrdered {
return ii < jj
}
if iOrdered {
return true
}
if jOrdered {
return false
}
return fields[i] < fields[j]
})
}
// needsQuote returns true when the string s should be quoted in output. // needsQuote returns true when the string s should be quoted in output.
func needsQuote(s string) bool { func needsQuote(s string) bool {
for i := range s { for i := range s {
@@ -352,19 +394,23 @@ func consoleDefaultPartsOrder() []string {
} }
} }
func consoleDefaultFormatTimestamp(timeFormat string, noColor bool) Formatter { func consoleDefaultFormatTimestamp(timeFormat string, location *time.Location, noColor bool) Formatter {
if timeFormat == "" { if timeFormat == "" {
timeFormat = consoleDefaultTimeFormat timeFormat = consoleDefaultTimeFormat
} }
if location == nil {
location = time.Local
}
return func(i interface{}) string { return func(i interface{}) string {
t := "<nil>" t := "<nil>"
switch tt := i.(type) { switch tt := i.(type) {
case string: case string:
ts, err := time.ParseInLocation(TimeFieldFormat, tt, time.Local) ts, err := time.ParseInLocation(TimeFieldFormat, tt, location)
if err != nil { if err != nil {
t = tt t = tt
} else { } else {
t = ts.Local().Format(timeFormat) t = ts.In(location).Format(timeFormat)
} }
case json.Number: case json.Number:
i, err := tt.Int64() i, err := tt.Int64()
@@ -385,32 +431,37 @@ func consoleDefaultFormatTimestamp(timeFormat string, noColor bool) Formatter {
} }
ts := time.Unix(sec, nsec) ts := time.Unix(sec, nsec)
t = ts.Format(timeFormat) t = ts.In(location).Format(timeFormat)
} }
} }
return colorize(t, colorDarkGray, noColor) return colorize(t, colorDarkGray, noColor)
} }
} }
func stripLevel(ll string) string {
if len(ll) == 0 {
return unknownLevel
}
if len(ll) > 3 {
ll = ll[:3]
}
return strings.ToUpper(ll)
}
func consoleDefaultFormatLevel(noColor bool) Formatter { func consoleDefaultFormatLevel(noColor bool) Formatter {
return func(i interface{}) string { return func(i interface{}) string {
var l string
if ll, ok := i.(string); ok { if ll, ok := i.(string); ok {
level, _ := ParseLevel(ll) level, _ := ParseLevel(ll)
fl, ok := FormattedLevels[level] fl, ok := FormattedLevels[level]
if ok { if ok {
l = colorize(fl, LevelColors[level], noColor) return colorize(fl, LevelColors[level], noColor)
} else {
l = strings.ToUpper(ll)[0:3]
}
} else {
if i == nil {
l = "???"
} else {
l = strings.ToUpper(fmt.Sprintf("%s", i))[0:3]
} }
return stripLevel(ll)
} }
return l if i == nil {
return unknownLevel
}
return stripLevel(fmt.Sprintf("%s", i))
} }
} }

View File

@@ -325,25 +325,25 @@ func (c Context) Uints64(key string, i []uint64) Context {
// Float32 adds the field key with f as a float32 to the logger context. // Float32 adds the field key with f as a float32 to the logger context.
func (c Context) Float32(key string, f float32) Context { func (c Context) Float32(key string, f float32) Context {
c.l.context = enc.AppendFloat32(enc.AppendKey(c.l.context, key), f) c.l.context = enc.AppendFloat32(enc.AppendKey(c.l.context, key), f, FloatingPointPrecision)
return c return c
} }
// Floats32 adds the field key with f as a []float32 to the logger context. // Floats32 adds the field key with f as a []float32 to the logger context.
func (c Context) Floats32(key string, f []float32) Context { func (c Context) Floats32(key string, f []float32) Context {
c.l.context = enc.AppendFloats32(enc.AppendKey(c.l.context, key), f) c.l.context = enc.AppendFloats32(enc.AppendKey(c.l.context, key), f, FloatingPointPrecision)
return c return c
} }
// Float64 adds the field key with f as a float64 to the logger context. // Float64 adds the field key with f as a float64 to the logger context.
func (c Context) Float64(key string, f float64) Context { func (c Context) Float64(key string, f float64) Context {
c.l.context = enc.AppendFloat64(enc.AppendKey(c.l.context, key), f) c.l.context = enc.AppendFloat64(enc.AppendKey(c.l.context, key), f, FloatingPointPrecision)
return c return c
} }
// Floats64 adds the field key with f as a []float64 to the logger context. // Floats64 adds the field key with f as a []float64 to the logger context.
func (c Context) Floats64(key string, f []float64) Context { func (c Context) Floats64(key string, f []float64) Context {
c.l.context = enc.AppendFloats64(enc.AppendKey(c.l.context, key), f) c.l.context = enc.AppendFloats64(enc.AppendKey(c.l.context, key), f, FloatingPointPrecision)
return c return c
} }
@@ -365,13 +365,13 @@ func (c Context) Timestamp() Context {
return c return c
} }
// Time adds the field key with t formated as string using zerolog.TimeFieldFormat. // Time adds the field key with t formatted as string using zerolog.TimeFieldFormat.
func (c Context) Time(key string, t time.Time) Context { func (c Context) Time(key string, t time.Time) Context {
c.l.context = enc.AppendTime(enc.AppendKey(c.l.context, key), t, TimeFieldFormat) c.l.context = enc.AppendTime(enc.AppendKey(c.l.context, key), t, TimeFieldFormat)
return c return c
} }
// Times adds the field key with t formated as string using zerolog.TimeFieldFormat. // Times adds the field key with t formatted as string using zerolog.TimeFieldFormat.
func (c Context) Times(key string, t []time.Time) Context { func (c Context) Times(key string, t []time.Time) Context {
c.l.context = enc.AppendTimes(enc.AppendKey(c.l.context, key), t, TimeFieldFormat) c.l.context = enc.AppendTimes(enc.AppendKey(c.l.context, key), t, TimeFieldFormat)
return c return c
@@ -379,13 +379,13 @@ func (c Context) Times(key string, t []time.Time) Context {
// Dur adds the fields key with d divided by unit and stored as a float. // Dur adds the fields key with d divided by unit and stored as a float.
func (c Context) Dur(key string, d time.Duration) Context { func (c Context) Dur(key string, d time.Duration) Context {
c.l.context = enc.AppendDuration(enc.AppendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldInteger) c.l.context = enc.AppendDuration(enc.AppendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
return c return c
} }
// Durs adds the fields key with d divided by unit and stored as a float. // Durs adds the fields key with d divided by unit and stored as a float.
func (c Context) Durs(key string, d []time.Duration) Context { func (c Context) Durs(key string, d []time.Duration) Context {
c.l.context = enc.AppendDurations(enc.AppendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldInteger) c.l.context = enc.AppendDurations(enc.AppendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
return c return c
} }
@@ -409,6 +409,12 @@ func (c Context) Any(key string, i interface{}) Context {
return c.Interface(key, i) return c.Interface(key, i)
} }
// Reset removes all the context fields.
func (c Context) Reset() Context {
c.l.context = enc.AppendBeginMarker(make([]byte, 0, 500))
return c
}
type callerHook struct { type callerHook struct {
callerSkipFrameCount int callerSkipFrameCount int
} }

View File

@@ -13,13 +13,13 @@ type encoder interface {
AppendBool(dst []byte, val bool) []byte AppendBool(dst []byte, val bool) []byte
AppendBools(dst []byte, vals []bool) []byte AppendBools(dst []byte, vals []bool) []byte
AppendBytes(dst, s []byte) []byte AppendBytes(dst, s []byte) []byte
AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool) []byte AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool, precision int) []byte
AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool) []byte AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool, precision int) []byte
AppendEndMarker(dst []byte) []byte AppendEndMarker(dst []byte) []byte
AppendFloat32(dst []byte, val float32) []byte AppendFloat32(dst []byte, val float32, precision int) []byte
AppendFloat64(dst []byte, val float64) []byte AppendFloat64(dst []byte, val float64, precision int) []byte
AppendFloats32(dst []byte, vals []float32) []byte AppendFloats32(dst []byte, vals []float32, precision int) []byte
AppendFloats64(dst []byte, vals []float64) []byte AppendFloats64(dst []byte, vals []float64, precision int) []byte
AppendHex(dst, s []byte) []byte AppendHex(dst, s []byte) []byte
AppendIPAddr(dst []byte, ip net.IP) []byte AppendIPAddr(dst []byte, ip net.IP) []byte
AppendIPPrefix(dst []byte, pfx net.IPNet) []byte AppendIPPrefix(dst []byte, pfx net.IPNet) []byte

View File

@@ -644,7 +644,7 @@ func (e *Event) Float32(key string, f float32) *Event {
if e == nil { if e == nil {
return e return e
} }
e.buf = enc.AppendFloat32(enc.AppendKey(e.buf, key), f) e.buf = enc.AppendFloat32(enc.AppendKey(e.buf, key), f, FloatingPointPrecision)
return e return e
} }
@@ -653,7 +653,7 @@ func (e *Event) Floats32(key string, f []float32) *Event {
if e == nil { if e == nil {
return e return e
} }
e.buf = enc.AppendFloats32(enc.AppendKey(e.buf, key), f) e.buf = enc.AppendFloats32(enc.AppendKey(e.buf, key), f, FloatingPointPrecision)
return e return e
} }
@@ -662,7 +662,7 @@ func (e *Event) Float64(key string, f float64) *Event {
if e == nil { if e == nil {
return e return e
} }
e.buf = enc.AppendFloat64(enc.AppendKey(e.buf, key), f) e.buf = enc.AppendFloat64(enc.AppendKey(e.buf, key), f, FloatingPointPrecision)
return e return e
} }
@@ -671,7 +671,7 @@ func (e *Event) Floats64(key string, f []float64) *Event {
if e == nil { if e == nil {
return e return e
} }
e.buf = enc.AppendFloats64(enc.AppendKey(e.buf, key), f) e.buf = enc.AppendFloats64(enc.AppendKey(e.buf, key), f, FloatingPointPrecision)
return e return e
} }
@@ -713,7 +713,7 @@ func (e *Event) Dur(key string, d time.Duration) *Event {
if e == nil { if e == nil {
return e return e
} }
e.buf = enc.AppendDuration(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger) e.buf = enc.AppendDuration(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
return e return e
} }
@@ -724,7 +724,7 @@ func (e *Event) Durs(key string, d []time.Duration) *Event {
if e == nil { if e == nil {
return e return e
} }
e.buf = enc.AppendDurations(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger) e.buf = enc.AppendDurations(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
return e return e
} }
@@ -739,7 +739,7 @@ func (e *Event) TimeDiff(key string, t time.Time, start time.Time) *Event {
if t.After(start) { if t.After(start) {
d = t.Sub(start) d = t.Sub(start)
} }
e.buf = enc.AppendDuration(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger) e.buf = enc.AppendDuration(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
return e return e
} }

View File

@@ -139,13 +139,13 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte {
case uint64: case uint64:
dst = enc.AppendUint64(dst, val) dst = enc.AppendUint64(dst, val)
case float32: case float32:
dst = enc.AppendFloat32(dst, val) dst = enc.AppendFloat32(dst, val, FloatingPointPrecision)
case float64: case float64:
dst = enc.AppendFloat64(dst, val) dst = enc.AppendFloat64(dst, val, FloatingPointPrecision)
case time.Time: case time.Time:
dst = enc.AppendTime(dst, val, TimeFieldFormat) dst = enc.AppendTime(dst, val, TimeFieldFormat)
case time.Duration: case time.Duration:
dst = enc.AppendDuration(dst, val, DurationFieldUnit, DurationFieldInteger) dst = enc.AppendDuration(dst, val, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
case *string: case *string:
if val != nil { if val != nil {
dst = enc.AppendString(dst, *val) dst = enc.AppendString(dst, *val)
@@ -220,13 +220,13 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte {
} }
case *float32: case *float32:
if val != nil { if val != nil {
dst = enc.AppendFloat32(dst, *val) dst = enc.AppendFloat32(dst, *val, FloatingPointPrecision)
} else { } else {
dst = enc.AppendNil(dst) dst = enc.AppendNil(dst)
} }
case *float64: case *float64:
if val != nil { if val != nil {
dst = enc.AppendFloat64(dst, *val) dst = enc.AppendFloat64(dst, *val, FloatingPointPrecision)
} else { } else {
dst = enc.AppendNil(dst) dst = enc.AppendNil(dst)
} }
@@ -238,7 +238,7 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte {
} }
case *time.Duration: case *time.Duration:
if val != nil { if val != nil {
dst = enc.AppendDuration(dst, *val, DurationFieldUnit, DurationFieldInteger) dst = enc.AppendDuration(dst, *val, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
} else { } else {
dst = enc.AppendNil(dst) dst = enc.AppendNil(dst)
} }
@@ -267,13 +267,13 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte {
case []uint64: case []uint64:
dst = enc.AppendUints64(dst, val) dst = enc.AppendUints64(dst, val)
case []float32: case []float32:
dst = enc.AppendFloats32(dst, val) dst = enc.AppendFloats32(dst, val, FloatingPointPrecision)
case []float64: case []float64:
dst = enc.AppendFloats64(dst, val) dst = enc.AppendFloats64(dst, val, FloatingPointPrecision)
case []time.Time: case []time.Time:
dst = enc.AppendTimes(dst, val, TimeFieldFormat) dst = enc.AppendTimes(dst, val, TimeFieldFormat)
case []time.Duration: case []time.Duration:
dst = enc.AppendDurations(dst, val, DurationFieldUnit, DurationFieldInteger) dst = enc.AppendDurations(dst, val, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
case nil: case nil:
dst = enc.AppendNil(dst) dst = enc.AppendNil(dst)
case net.IP: case net.IP:

View File

@@ -1,6 +1,7 @@
package zerolog package zerolog
import ( import (
"bytes"
"encoding/json" "encoding/json"
"strconv" "strconv"
"sync/atomic" "sync/atomic"
@@ -81,8 +82,22 @@ var (
} }
// InterfaceMarshalFunc allows customization of interface marshaling. // InterfaceMarshalFunc allows customization of interface marshaling.
// Default: "encoding/json.Marshal" // Default: "encoding/json.Marshal" with disabled HTML escaping
InterfaceMarshalFunc = json.Marshal InterfaceMarshalFunc = func(v interface{}) ([]byte, error) {
var buf bytes.Buffer
encoder := json.NewEncoder(&buf)
encoder.SetEscapeHTML(false)
err := encoder.Encode(v)
if err != nil {
return nil, err
}
b := buf.Bytes()
if len(b) > 0 {
// Remove trailing \n which is added by Encode.
return b[:len(b)-1], nil
}
return b, nil
}
// TimeFieldFormat defines the time format of the Time field type. If set to // TimeFieldFormat defines the time format of the Time field type. If set to
// TimeFormatUnix, TimeFormatUnixMs, TimeFormatUnixMicro or TimeFormatUnixNano, the time is formatted as a UNIX // TimeFormatUnix, TimeFormatUnixMs, TimeFormatUnixMicro or TimeFormatUnixNano, the time is formatted as a UNIX
@@ -136,6 +151,11 @@ var (
// TriggerLevelWriterBufferReuseLimit is a limit in bytes that a buffer is dropped // TriggerLevelWriterBufferReuseLimit is a limit in bytes that a buffer is dropped
// from the TriggerLevelWriter buffer pool if the buffer grows above the limit. // from the TriggerLevelWriter buffer pool if the buffer grows above the limit.
TriggerLevelWriterBufferReuseLimit = 64 * 1024 TriggerLevelWriterBufferReuseLimit = 64 * 1024
// FloatingPointPrecision, if set to a value other than -1, controls the number
// of digits when formatting float numbers in JSON. See strconv.FormatFloat for
// more details.
FloatingPointPrecision = -1
) )
var ( var (

View File

@@ -95,7 +95,7 @@ func decodeFloat(src *bufio.Reader) (float64, int) {
switch minor { switch minor {
case additionalTypeFloat16: case additionalTypeFloat16:
panic(fmt.Errorf("float16 is not suppported in decodeFloat")) panic(fmt.Errorf("float16 is not supported in decodeFloat"))
case additionalTypeFloat32: case additionalTypeFloat32:
pb := readNBytes(src, 4) pb := readNBytes(src, 4)

View File

@@ -29,7 +29,7 @@ func (e Encoder) appendFloatTimestamp(dst []byte, t time.Time) []byte {
nanos := t.Nanosecond() nanos := t.Nanosecond()
var val float64 var val float64
val = float64(secs)*1.0 + float64(nanos)*1e-9 val = float64(secs)*1.0 + float64(nanos)*1e-9
return e.AppendFloat64(dst, val) return e.AppendFloat64(dst, val, -1)
} }
// AppendTime encodes and adds a timestamp to the dst byte array. // AppendTime encodes and adds a timestamp to the dst byte array.
@@ -64,17 +64,17 @@ func (e Encoder) AppendTimes(dst []byte, vals []time.Time, unused string) []byte
// AppendDuration encodes and adds a duration to the dst byte array. // AppendDuration encodes and adds a duration to the dst byte array.
// useInt field indicates whether to store the duration as seconds (integer) or // useInt field indicates whether to store the duration as seconds (integer) or
// as seconds+nanoseconds (float). // as seconds+nanoseconds (float).
func (e Encoder) AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool) []byte { func (e Encoder) AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool, unused int) []byte {
if useInt { if useInt {
return e.AppendInt64(dst, int64(d/unit)) return e.AppendInt64(dst, int64(d/unit))
} }
return e.AppendFloat64(dst, float64(d)/float64(unit)) return e.AppendFloat64(dst, float64(d)/float64(unit), unused)
} }
// AppendDurations encodes and adds an array of durations to the dst byte array. // AppendDurations encodes and adds an array of durations to the dst byte array.
// useInt field indicates whether to store the duration as seconds (integer) or // useInt field indicates whether to store the duration as seconds (integer) or
// as seconds+nanoseconds (float). // as seconds+nanoseconds (float).
func (e Encoder) AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool) []byte { func (e Encoder) AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool, unused int) []byte {
major := majorTypeArray major := majorTypeArray
l := len(vals) l := len(vals)
if l == 0 { if l == 0 {
@@ -87,7 +87,7 @@ func (e Encoder) AppendDurations(dst []byte, vals []time.Duration, unit time.Dur
dst = appendCborTypePrefix(dst, major, uint64(l)) dst = appendCborTypePrefix(dst, major, uint64(l))
} }
for _, d := range vals { for _, d := range vals {
dst = e.AppendDuration(dst, d, unit, useInt) dst = e.AppendDuration(dst, d, unit, useInt, unused)
} }
return dst return dst
} }

View File

@@ -352,7 +352,7 @@ func (e Encoder) AppendUints64(dst []byte, vals []uint64) []byte {
} }
// AppendFloat32 encodes and inserts a single precision float value into the dst byte array. // AppendFloat32 encodes and inserts a single precision float value into the dst byte array.
func (Encoder) AppendFloat32(dst []byte, val float32) []byte { func (Encoder) AppendFloat32(dst []byte, val float32, unused int) []byte {
switch { switch {
case math.IsNaN(float64(val)): case math.IsNaN(float64(val)):
return append(dst, "\xfa\x7f\xc0\x00\x00"...) return append(dst, "\xfa\x7f\xc0\x00\x00"...)
@@ -372,7 +372,7 @@ func (Encoder) AppendFloat32(dst []byte, val float32) []byte {
} }
// AppendFloats32 encodes and inserts an array of single precision float value into the dst byte array. // AppendFloats32 encodes and inserts an array of single precision float value into the dst byte array.
func (e Encoder) AppendFloats32(dst []byte, vals []float32) []byte { func (e Encoder) AppendFloats32(dst []byte, vals []float32, unused int) []byte {
major := majorTypeArray major := majorTypeArray
l := len(vals) l := len(vals)
if l == 0 { if l == 0 {
@@ -385,13 +385,13 @@ func (e Encoder) AppendFloats32(dst []byte, vals []float32) []byte {
dst = appendCborTypePrefix(dst, major, uint64(l)) dst = appendCborTypePrefix(dst, major, uint64(l))
} }
for _, v := range vals { for _, v := range vals {
dst = e.AppendFloat32(dst, v) dst = e.AppendFloat32(dst, v, unused)
} }
return dst return dst
} }
// AppendFloat64 encodes and inserts a double precision float value into the dst byte array. // AppendFloat64 encodes and inserts a double precision float value into the dst byte array.
func (Encoder) AppendFloat64(dst []byte, val float64) []byte { func (Encoder) AppendFloat64(dst []byte, val float64, unused int) []byte {
switch { switch {
case math.IsNaN(val): case math.IsNaN(val):
return append(dst, "\xfb\x7f\xf8\x00\x00\x00\x00\x00\x00"...) return append(dst, "\xfb\x7f\xf8\x00\x00\x00\x00\x00\x00"...)
@@ -412,7 +412,7 @@ func (Encoder) AppendFloat64(dst []byte, val float64) []byte {
} }
// AppendFloats64 encodes and inserts an array of double precision float values into the dst byte array. // AppendFloats64 encodes and inserts an array of double precision float values into the dst byte array.
func (e Encoder) AppendFloats64(dst []byte, vals []float64) []byte { func (e Encoder) AppendFloats64(dst []byte, vals []float64, unused int) []byte {
major := majorTypeArray major := majorTypeArray
l := len(vals) l := len(vals)
if l == 0 { if l == 0 {
@@ -425,7 +425,7 @@ func (e Encoder) AppendFloats64(dst []byte, vals []float64) []byte {
dst = appendCborTypePrefix(dst, major, uint64(l)) dst = appendCborTypePrefix(dst, major, uint64(l))
} }
for _, v := range vals { for _, v := range vals {
dst = e.AppendFloat64(dst, v) dst = e.AppendFloat64(dst, v, unused)
} }
return dst return dst
} }

View File

@@ -88,24 +88,24 @@ func appendUnixNanoTimes(dst []byte, vals []time.Time, div int64) []byte {
// AppendDuration formats the input duration with the given unit & format // AppendDuration formats the input duration with the given unit & format
// and appends the encoded string to the input byte slice. // and appends the encoded string to the input byte slice.
func (e Encoder) AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool) []byte { func (e Encoder) AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool, precision int) []byte {
if useInt { if useInt {
return strconv.AppendInt(dst, int64(d/unit), 10) return strconv.AppendInt(dst, int64(d/unit), 10)
} }
return e.AppendFloat64(dst, float64(d)/float64(unit)) return e.AppendFloat64(dst, float64(d)/float64(unit), precision)
} }
// AppendDurations formats the input durations with the given unit & format // AppendDurations formats the input durations with the given unit & format
// and appends the encoded string list to the input byte slice. // and appends the encoded string list to the input byte slice.
func (e Encoder) AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool) []byte { func (e Encoder) AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool, precision int) []byte {
if len(vals) == 0 { if len(vals) == 0 {
return append(dst, '[', ']') return append(dst, '[', ']')
} }
dst = append(dst, '[') dst = append(dst, '[')
dst = e.AppendDuration(dst, vals[0], unit, useInt) dst = e.AppendDuration(dst, vals[0], unit, useInt, precision)
if len(vals) > 1 { if len(vals) > 1 {
for _, d := range vals[1:] { for _, d := range vals[1:] {
dst = e.AppendDuration(append(dst, ','), d, unit, useInt) dst = e.AppendDuration(append(dst, ','), d, unit, useInt, precision)
} }
} }
dst = append(dst, ']') dst = append(dst, ']')

View File

@@ -299,7 +299,7 @@ func (Encoder) AppendUints64(dst []byte, vals []uint64) []byte {
return dst return dst
} }
func appendFloat(dst []byte, val float64, bitSize int) []byte { func appendFloat(dst []byte, val float64, bitSize, precision int) []byte {
// JSON does not permit NaN or Infinity. A typical JSON encoder would fail // JSON does not permit NaN or Infinity. A typical JSON encoder would fail
// with an error, but a logging library wants the data to get through so we // with an error, but a logging library wants the data to get through so we
// make a tradeoff and store those types as string. // make a tradeoff and store those types as string.
@@ -311,26 +311,47 @@ func appendFloat(dst []byte, val float64, bitSize int) []byte {
case math.IsInf(val, -1): case math.IsInf(val, -1):
return append(dst, `"-Inf"`...) return append(dst, `"-Inf"`...)
} }
return strconv.AppendFloat(dst, val, 'f', -1, bitSize) // convert as if by es6 number to string conversion
// see also https://cs.opensource.google/go/go/+/refs/tags/go1.20.3:src/encoding/json/encode.go;l=573
strFmt := byte('f')
// If precision is set to a value other than -1, we always just format the float using that precision.
if precision == -1 {
// Use float32 comparisons for underlying float32 value to get precise cutoffs right.
if abs := math.Abs(val); abs != 0 {
if bitSize == 64 && (abs < 1e-6 || abs >= 1e21) || bitSize == 32 && (float32(abs) < 1e-6 || float32(abs) >= 1e21) {
strFmt = 'e'
}
}
}
dst = strconv.AppendFloat(dst, val, strFmt, precision, bitSize)
if strFmt == 'e' {
// Clean up e-09 to e-9
n := len(dst)
if n >= 4 && dst[n-4] == 'e' && dst[n-3] == '-' && dst[n-2] == '0' {
dst[n-2] = dst[n-1]
dst = dst[:n-1]
}
}
return dst
} }
// AppendFloat32 converts the input float32 to a string and // AppendFloat32 converts the input float32 to a string and
// appends the encoded string to the input byte slice. // appends the encoded string to the input byte slice.
func (Encoder) AppendFloat32(dst []byte, val float32) []byte { func (Encoder) AppendFloat32(dst []byte, val float32, precision int) []byte {
return appendFloat(dst, float64(val), 32) return appendFloat(dst, float64(val), 32, precision)
} }
// AppendFloats32 encodes the input float32s to json and // AppendFloats32 encodes the input float32s to json and
// appends the encoded string list to the input byte slice. // appends the encoded string list to the input byte slice.
func (Encoder) AppendFloats32(dst []byte, vals []float32) []byte { func (Encoder) AppendFloats32(dst []byte, vals []float32, precision int) []byte {
if len(vals) == 0 { if len(vals) == 0 {
return append(dst, '[', ']') return append(dst, '[', ']')
} }
dst = append(dst, '[') dst = append(dst, '[')
dst = appendFloat(dst, float64(vals[0]), 32) dst = appendFloat(dst, float64(vals[0]), 32, precision)
if len(vals) > 1 { if len(vals) > 1 {
for _, val := range vals[1:] { for _, val := range vals[1:] {
dst = appendFloat(append(dst, ','), float64(val), 32) dst = appendFloat(append(dst, ','), float64(val), 32, precision)
} }
} }
dst = append(dst, ']') dst = append(dst, ']')
@@ -339,21 +360,21 @@ func (Encoder) AppendFloats32(dst []byte, vals []float32) []byte {
// AppendFloat64 converts the input float64 to a string and // AppendFloat64 converts the input float64 to a string and
// appends the encoded string to the input byte slice. // appends the encoded string to the input byte slice.
func (Encoder) AppendFloat64(dst []byte, val float64) []byte { func (Encoder) AppendFloat64(dst []byte, val float64, precision int) []byte {
return appendFloat(dst, val, 64) return appendFloat(dst, val, 64, precision)
} }
// AppendFloats64 encodes the input float64s to json and // AppendFloats64 encodes the input float64s to json and
// appends the encoded string list to the input byte slice. // appends the encoded string list to the input byte slice.
func (Encoder) AppendFloats64(dst []byte, vals []float64) []byte { func (Encoder) AppendFloats64(dst []byte, vals []float64, precision int) []byte {
if len(vals) == 0 { if len(vals) == 0 {
return append(dst, '[', ']') return append(dst, '[', ']')
} }
dst = append(dst, '[') dst = append(dst, '[')
dst = appendFloat(dst, vals[0], 64) dst = appendFloat(dst, vals[0], 64, precision)
if len(vals) > 1 { if len(vals) > 1 {
for _, val := range vals[1:] { for _, val := range vals[1:] {
dst = appendFloat(append(dst, ','), val, 64) dst = appendFloat(append(dst, ','), val, 64, precision)
} }
} }
dst = append(dst, ']') dst = append(dst, ']')

View File

@@ -24,7 +24,7 @@
// //
// Sub-loggers let you chain loggers with additional context: // Sub-loggers let you chain loggers with additional context:
// //
// sublogger := log.With().Str("component": "foo").Logger() // sublogger := log.With().Str("component", "foo").Logger()
// sublogger.Info().Msg("hello world") // sublogger.Info().Msg("hello world")
// // Output: {"time":1494567715,"level":"info","message":"hello world","component":"foo"} // // Output: {"time":1494567715,"level":"info","message":"hello world","component":"foo"}
// //

View File

@@ -84,7 +84,7 @@ func (s *BurstSampler) Sample(lvl Level) bool {
} }
func (s *BurstSampler) inc() uint32 { func (s *BurstSampler) inc() uint32 {
now := time.Now().UnixNano() now := TimestampFunc().UnixNano()
resetAt := atomic.LoadInt64(&s.resetAt) resetAt := atomic.LoadInt64(&s.resetAt)
var c uint32 var c uint32
if now > resetAt { if now > resetAt {

View File

@@ -46,10 +46,11 @@ func (s *strikethroughParser) Trigger() []byte {
func (s *strikethroughParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node { func (s *strikethroughParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
before := block.PrecendingCharacter() before := block.PrecendingCharacter()
line, segment := block.PeekLine() line, segment := block.PeekLine()
node := parser.ScanDelimiter(line, before, 2, defaultStrikethroughDelimiterProcessor) node := parser.ScanDelimiter(line, before, 1, defaultStrikethroughDelimiterProcessor)
if node == nil { if node == nil || node.OriginalLength > 2 || before == '~' {
return nil return nil
} }
node.Segment = segment.WithStop(segment.Start + node.OriginalLength) node.Segment = segment.WithStop(segment.Start + node.OriginalLength)
block.Advance(node.OriginalLength) block.Advance(node.OriginalLength)
pc.PushDelimiter(node) pc.PushDelimiter(node)

View File

@@ -492,7 +492,7 @@ func (r *TableHTMLRenderer) renderTableCell(
tag = "th" tag = "th"
} }
if entering { if entering {
fmt.Fprintf(w, "<%s", tag) _, _ = fmt.Fprintf(w, "<%s", tag)
if n.Alignment != ast.AlignNone { if n.Alignment != ast.AlignNone {
amethod := r.TableConfig.TableCellAlignMethod amethod := r.TableConfig.TableCellAlignMethod
if amethod == TableCellAlignDefault { if amethod == TableCellAlignDefault {
@@ -505,7 +505,7 @@ func (r *TableHTMLRenderer) renderTableCell(
switch amethod { switch amethod {
case TableCellAlignAttribute: case TableCellAlignAttribute:
if _, ok := n.AttributeString("align"); !ok { // Skip align render if overridden if _, ok := n.AttributeString("align"); !ok { // Skip align render if overridden
fmt.Fprintf(w, ` align="%s"`, n.Alignment.String()) _, _ = fmt.Fprintf(w, ` align="%s"`, n.Alignment.String())
} }
case TableCellAlignStyle: case TableCellAlignStyle:
v, ok := n.AttributeString("style") v, ok := n.AttributeString("style")
@@ -528,7 +528,7 @@ func (r *TableHTMLRenderer) renderTableCell(
} }
_ = w.WriteByte('>') _ = w.WriteByte('>')
} else { } else {
fmt.Fprintf(w, "</%s>\n", tag) _, _ = fmt.Fprintf(w, "</%s>\n", tag)
} }
return gast.WalkContinue, nil return gast.WalkContinue, nil
} }

View File

@@ -2,12 +2,13 @@
package goldmark package goldmark
import ( import (
"io"
"github.com/yuin/goldmark/parser" "github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer" "github.com/yuin/goldmark/renderer"
"github.com/yuin/goldmark/renderer/html" "github.com/yuin/goldmark/renderer/html"
"github.com/yuin/goldmark/text" "github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util" "github.com/yuin/goldmark/util"
"io"
) )
// DefaultParser returns a new Parser that is configured by default values. // DefaultParser returns a new Parser that is configured by default values.

View File

@@ -126,13 +126,13 @@ func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.N
if line[0] == '!' { if line[0] == '!' {
if len(line) > 1 && line[1] == '[' { if len(line) > 1 && line[1] == '[' {
block.Advance(1) block.Advance(1)
pc.Set(linkBottom, pc.LastDelimiter()) pushLinkBottom(pc)
return processLinkLabelOpen(block, segment.Start+1, true, pc) return processLinkLabelOpen(block, segment.Start+1, true, pc)
} }
return nil return nil
} }
if line[0] == '[' { if line[0] == '[' {
pc.Set(linkBottom, pc.LastDelimiter()) pushLinkBottom(pc)
return processLinkLabelOpen(block, segment.Start, false, pc) return processLinkLabelOpen(block, segment.Start, false, pc)
} }
@@ -143,6 +143,7 @@ func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.N
} }
last := tlist.(*linkLabelState).Last last := tlist.(*linkLabelState).Last
if last == nil { if last == nil {
_ = popLinkBottom(pc)
return nil return nil
} }
block.Advance(1) block.Advance(1)
@@ -151,11 +152,13 @@ func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.N
// > A link label can have at most 999 characters inside the square brackets. // > A link label can have at most 999 characters inside the square brackets.
if linkLabelStateLength(tlist.(*linkLabelState)) > 998 { if linkLabelStateLength(tlist.(*linkLabelState)) > 998 {
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment) ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
_ = popLinkBottom(pc)
return nil return nil
} }
if !last.IsImage && s.containsLink(last) { // a link in a link text is not allowed if !last.IsImage && s.containsLink(last) { // a link in a link text is not allowed
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment) ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
_ = popLinkBottom(pc)
return nil return nil
} }
@@ -169,6 +172,7 @@ func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.N
link, hasValue = s.parseReferenceLink(parent, last, block, pc) link, hasValue = s.parseReferenceLink(parent, last, block, pc)
if link == nil && hasValue { if link == nil && hasValue {
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment) ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
_ = popLinkBottom(pc)
return nil return nil
} }
} }
@@ -182,12 +186,14 @@ func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.N
// > A link label can have at most 999 characters inside the square brackets. // > A link label can have at most 999 characters inside the square brackets.
if len(maybeReference) > 999 { if len(maybeReference) > 999 {
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment) ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
_ = popLinkBottom(pc)
return nil return nil
} }
ref, ok := pc.Reference(util.ToLinkReference(maybeReference)) ref, ok := pc.Reference(util.ToLinkReference(maybeReference))
if !ok { if !ok {
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment) ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
_ = popLinkBottom(pc)
return nil return nil
} }
link = ast.NewLink() link = ast.NewLink()
@@ -230,11 +236,7 @@ func processLinkLabelOpen(block text.Reader, pos int, isImage bool, pc Context)
} }
func (s *linkParser) processLinkLabel(parent ast.Node, link *ast.Link, last *linkLabelState, pc Context) { func (s *linkParser) processLinkLabel(parent ast.Node, link *ast.Link, last *linkLabelState, pc Context) {
var bottom ast.Node bottom := popLinkBottom(pc)
if v := pc.Get(linkBottom); v != nil {
bottom = v.(ast.Node)
}
pc.Set(linkBottom, nil)
ProcessDelimiters(bottom, pc) ProcessDelimiters(bottom, pc)
for c := last.NextSibling(); c != nil; { for c := last.NextSibling(); c != nil; {
next := c.NextSibling() next := c.NextSibling()
@@ -395,6 +397,43 @@ func parseLinkTitle(block text.Reader) ([]byte, bool) {
return nil, false return nil, false
} }
func pushLinkBottom(pc Context) {
bottoms := pc.Get(linkBottom)
b := pc.LastDelimiter()
if bottoms == nil {
pc.Set(linkBottom, b)
return
}
if s, ok := bottoms.([]ast.Node); ok {
pc.Set(linkBottom, append(s, b))
return
}
pc.Set(linkBottom, []ast.Node{bottoms.(ast.Node), b})
}
func popLinkBottom(pc Context) ast.Node {
bottoms := pc.Get(linkBottom)
if bottoms == nil {
return nil
}
if v, ok := bottoms.(ast.Node); ok {
pc.Set(linkBottom, nil)
return v
}
s := bottoms.([]ast.Node)
v := s[len(s)-1]
n := s[0 : len(s)-1]
switch len(n) {
case 0:
pc.Set(linkBottom, nil)
case 1:
pc.Set(linkBottom, n[0])
default:
pc.Set(linkBottom, s[0:len(s)-1])
}
return v
}
func (s *linkParser) CloseBlock(parent ast.Node, block text.Reader, pc Context) { func (s *linkParser) CloseBlock(parent ast.Node, block text.Reader, pc Context) {
pc.Set(linkBottom, nil) pc.Set(linkBottom, nil)
tlist := pc.Get(linkLabelStateKey) tlist := pc.Get(linkLabelStateKey)

View File

@@ -445,7 +445,7 @@ func (r *Renderer) renderList(w util.BufWriter, source []byte, node ast.Node, en
_ = w.WriteByte('<') _ = w.WriteByte('<')
_, _ = w.WriteString(tag) _, _ = w.WriteString(tag)
if n.IsOrdered() && n.Start != 1 { if n.IsOrdered() && n.Start != 1 {
fmt.Fprintf(w, " start=\"%d\"", n.Start) _, _ = fmt.Fprintf(w, " start=\"%d\"", n.Start)
} }
if n.Attributes() != nil { if n.Attributes() != nil {
RenderAttributes(w, n, ListAttributeFilter) RenderAttributes(w, n, ListAttributeFilter)
@@ -680,7 +680,7 @@ func (r *Renderer) renderImage(w util.BufWriter, source []byte, node ast.Node, e
_, _ = w.Write(util.EscapeHTML(util.URLEscape(n.Destination, true))) _, _ = w.Write(util.EscapeHTML(util.URLEscape(n.Destination, true)))
} }
_, _ = w.WriteString(`" alt="`) _, _ = w.WriteString(`" alt="`)
_, _ = w.Write(nodeToHTMLText(n, source)) r.renderAttribute(w, source, n)
_ = w.WriteByte('"') _ = w.WriteByte('"')
if n.Title != nil { if n.Title != nil {
_, _ = w.WriteString(` title="`) _, _ = w.WriteString(` title="`)
@@ -770,6 +770,23 @@ func (r *Renderer) renderString(w util.BufWriter, source []byte, node ast.Node,
return ast.WalkContinue, nil return ast.WalkContinue, nil
} }
func (r *Renderer) renderAttribute(w util.BufWriter, source []byte, n ast.Node) {
for c := n.FirstChild(); c != nil; c = c.NextSibling() {
if s, ok := c.(*ast.String); ok {
_, _ = r.renderString(w, source, s, true)
} else if t, ok := c.(*ast.String); ok {
_, _ = r.renderText(w, source, t, true)
} else if !c.HasChildren() {
r.Writer.Write(w, c.Text(source))
if t, ok := c.(*ast.Text); ok && t.SoftLineBreak() {
_ = w.WriteByte('\n')
}
} else {
r.renderAttribute(w, source, c)
}
}
}
var dataPrefix = []byte("data-") var dataPrefix = []byte("data-")
// RenderAttributes renders given node's attributes. // RenderAttributes renders given node's attributes.
@@ -1007,20 +1024,3 @@ func IsDangerousURL(url []byte) bool {
return hasPrefix(url, bJs) || hasPrefix(url, bVb) || return hasPrefix(url, bJs) || hasPrefix(url, bVb) ||
hasPrefix(url, bFile) || hasPrefix(url, bData) hasPrefix(url, bFile) || hasPrefix(url, bData)
} }
func nodeToHTMLText(n ast.Node, source []byte) []byte {
var buf bytes.Buffer
for c := n.FirstChild(); c != nil; c = c.NextSibling() {
if s, ok := c.(*ast.String); ok && s.IsCode() {
buf.Write(s.Text(source))
} else if !c.HasChildren() {
buf.Write(util.EscapeHTML(c.Text(source)))
if t, ok := c.(*ast.Text); ok && t.SoftLineBreak() {
buf.WriteByte('\n')
}
} else {
buf.Write(nodeToHTMLText(c, source))
}
}
return buf.Bytes()
}

View File

@@ -1,11 +1,15 @@
lint: stages:
image: registry.gitlab.com/etke.cc/base - test
script: - release
- golangci-lint run ./... default:
image: registry.gitlab.com/etke.cc/base/build
unit: test:
image: registry.gitlab.com/etke.cc/base stage: test
script: script:
- go test -coverprofile=cover.out ./... - just lint
- go tool cover -func=cover.out - just test
- rm -f cover.out cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- /root/cache/

144
vendor/gitlab.com/etke.cc/go/env/.golangci.yml generated vendored Normal file
View File

@@ -0,0 +1,144 @@
run:
concurrency: 4
timeout: 30m
issues-exit-code: 1
tests: true
build-tags: []
skip-dirs-use-default: true
skip-files: []
modules-download-mode: readonly
output:
formats:
- format: colored-line-number
print-issued-lines: true
print-linter-name: true
sort-results: true
linters-settings:
decorder:
dec-order:
- const
- var
- type
- func
dogsled:
max-blank-identifiers: 3
errcheck:
check-type-assertions: true
check-blank: true
errchkjson:
report-no-exported: true
exhaustive:
check:
- switch
- map
default-signifies-exhaustive: true
gocognit:
min-complexity: 15
nestif:
min-complexity: 5
gocritic:
enabled-tags:
- diagnostic
- style
- performance
gofmt:
simplify: true
rewrite-rules:
- pattern: 'interface{}'
replacement: 'any'
- pattern: 'a[b:len(a)]'
replacement: 'a[b:]'
gofumpt:
extra-rules: true
grouper:
const-require-single-const: true
import-require-single-import: true
var-require-single-var: true
misspell:
locale: US
usestdlibvars:
time-month: true
time-layout: true
crypto-hash: true
default-rpc-path: true
sql-isolation-level: true
tls-signature-scheme: true
constant-kind: true
unparam:
check-exported: true
linters:
disable-all: false
enable:
- asasalint
- asciicheck
- bidichk
- bodyclose
- containedctx
- decorder
- dogsled
- dupl
- dupword
- durationcheck
- errcheck
- errchkjson
- errname
- errorlint
- exhaustive
- exportloopref
- forcetypeassert
- gocognit
- gocritic
- gocyclo
- gofmt
- gofumpt
- goimports
- gosec
- gosimple
- gosmopolitan
- govet
- ineffassign
- makezero
- mirror
- misspell
- nestif
- nolintlint
- prealloc
- predeclared
- revive
- sqlclosecheck
- staticcheck
- unconvert
- unparam
- unused
- usestdlibvars
- wastedassign
fast: false
issues:
exclude-dirs:
- mocks
exclude-files:
- dotenv/parser.go
exclude-rules:
- path: _test\.go
linters:
- gocyclo
- gocognit
- errcheck
- dupl
- gosec
- linters:
- staticcheck
text: "SA9003:"
- linters:
- lll
source: "^//go:generate "
- linters:
- revive
text: "returns unexported type"
max-issues-per-linter: 0
max-same-issues: 0
new: false

View File

@@ -2,12 +2,17 @@
Simple go environment variables reader, for env-based configs Simple go environment variables reader, for env-based configs
Automatically loads .env file if exists
```go ```go
env.SetPrefix("app") // all env vars should start with APP_ env.SetPrefix("app") // all env vars should start with APP_
login := env.String("matrix.login", "none") // export APP_MATRIX_LOGIN=buscarron login := env.String("matrix.login", "none") // export APP_MATRIX_LOGIN=buscarron
enabled := env.Bool("form.enabled") // export APP_FORM_ENABLED=1 enabled := env.Bool("form.enabled") // export APP_FORM_ENABLED=1
size := env.Int("cache.size", 1000) // export APP_CACHE_SIZE=500 size := env.Int("cache.size", 1000) // export APP_CACHE_SIZE=500
slice := env.Slice("form.fields") // export APP_FORM_FIELDS="one two three" slice := env.Slice("form.fields") // export APP_FORM_FIELDS="one two three"
// need to load custom env file?
dotenv.Load(".env.dev", ".env.local")
``` ```
see more in godoc see more in godoc

43
vendor/gitlab.com/etke.cc/go/env/dotenv/dotenv.go generated vendored Normal file
View File

@@ -0,0 +1,43 @@
package dotenv
import (
"errors"
"os"
)
// EnvFile is the default file to load
const EnvFile = ".env"
// Load loads the EnvFile and additional files
func Load(additionalFiles ...string) {
files := []string{EnvFile}
if len(additionalFiles) > 0 {
files = append(files, additionalFiles...)
}
for _, file := range files {
loadFile(file) //nolint:errcheck // ignore error
}
}
func loadFile(file string) error {
if _, err := os.Stat(".env"); errors.Is(err, os.ErrNotExist) {
return nil
}
contents, err := os.ReadFile(file)
if err != nil {
return err
}
contentsMap := make(map[string]string)
if err := parseBytes(contents, contentsMap); err != nil {
return err
}
for key, value := range contentsMap {
os.Setenv(key, value)
}
return nil
}

273
vendor/gitlab.com/etke.cc/go/env/dotenv/parser.go generated vendored Normal file
View File

@@ -0,0 +1,273 @@
package dotenv
// This file is modified version of github.com/joho/godotenv/parser.go
// The original file is licensed under MIT License
import (
"bytes"
"errors"
"fmt"
"regexp"
"strings"
"unicode"
)
const (
charComment = '#'
prefixSingleQuote = '\''
prefixDoubleQuote = '"'
exportPrefix = "export"
)
func parseBytes(src []byte, out map[string]string) error {
src = bytes.ReplaceAll(src, []byte("\r\n"), []byte("\n"))
cutset := src
for {
cutset = getStatementStart(cutset)
if cutset == nil {
// reached end of file
break
}
key, left, err := locateKeyName(cutset)
if err != nil {
return err
}
value, left, err := extractVarValue(left, out)
if err != nil {
return err
}
out[key] = value
cutset = left
}
return nil
}
// getStatementPosition returns position of statement begin.
//
// It skips any comment line or non-whitespace character.
func getStatementStart(src []byte) []byte {
pos := indexOfNonSpaceChar(src)
if pos == -1 {
return nil
}
src = src[pos:]
if src[0] != charComment {
return src
}
// skip comment section
pos = bytes.IndexFunc(src, isCharFunc('\n'))
if pos == -1 {
return nil
}
return getStatementStart(src[pos:])
}
// locateKeyName locates and parses key name and returns rest of slice
func locateKeyName(src []byte) (key string, cutset []byte, err error) {
// trim "export" and space at beginning
src = bytes.TrimLeftFunc(src, isSpace)
if bytes.HasPrefix(src, []byte(exportPrefix)) {
trimmed := bytes.TrimPrefix(src, []byte(exportPrefix))
if bytes.IndexFunc(trimmed, isSpace) == 0 {
src = bytes.TrimLeftFunc(trimmed, isSpace)
}
}
// locate key name end and validate it in single loop
offset := 0
loop:
for i, char := range src {
rchar := rune(char)
if isSpace(rchar) {
continue
}
switch char {
case '=', ':':
// library also supports yaml-style value declaration
key = string(src[0:i])
offset = i + 1
break loop
case '_':
default:
// variable name should match [A-Za-z0-9_.]
if unicode.IsLetter(rchar) || unicode.IsNumber(rchar) || rchar == '.' {
continue
}
return "", nil, fmt.Errorf(
`unexpected character %q in variable name near %q`,
string(char), string(src))
}
}
if len(src) == 0 {
return "", nil, errors.New("zero length string")
}
// trim whitespace
key = strings.TrimRightFunc(key, unicode.IsSpace)
cutset = bytes.TrimLeftFunc(src[offset:], isSpace)
return key, cutset, nil
}
// extractVarValue extracts variable value and returns rest of slice
func extractVarValue(src []byte, vars map[string]string) (value string, rest []byte, err error) {
quote, hasPrefix := hasQuotePrefix(src)
if !hasPrefix {
// unquoted value - read until end of line
endOfLine := bytes.IndexFunc(src, isLineEnd)
// Hit EOF without a trailing newline
if endOfLine == -1 {
endOfLine = len(src)
if endOfLine == 0 {
return "", nil, nil
}
}
// Convert line to rune away to do accurate countback of runes
line := []rune(string(src[0:endOfLine]))
// Assume end of line is end of var
endOfVar := len(line)
if endOfVar == 0 {
return "", src[endOfLine:], nil
}
// Work backwards to check if the line ends in whitespace then
// a comment (ie asdasd # some comment)
for i := endOfVar - 1; i >= 0; i-- {
if line[i] == charComment && i > 0 {
if isSpace(line[i-1]) {
endOfVar = i
break
}
}
}
trimmed := strings.TrimFunc(string(line[0:endOfVar]), isSpace)
return expandVariables(trimmed, vars), src[endOfLine:], nil
}
// lookup quoted string terminator
for i := 1; i < len(src); i++ {
if char := src[i]; char != quote {
continue
}
// skip escaped quote symbol (\" or \', depends on quote)
if prevChar := src[i-1]; prevChar == '\\' {
continue
}
// trim quotes
trimFunc := isCharFunc(rune(quote))
value = string(bytes.TrimLeftFunc(bytes.TrimRightFunc(src[0:i], trimFunc), trimFunc))
if quote == prefixDoubleQuote {
// unescape newlines for double quote (this is compat feature)
// and expand environment variables
value = expandVariables(expandEscapes(value), vars)
}
return value, src[i+1:], nil
}
// return formatted error if quoted string is not terminated
valEndIndex := bytes.IndexFunc(src, isCharFunc('\n'))
if valEndIndex == -1 {
valEndIndex = len(src)
}
return "", nil, fmt.Errorf("unterminated quoted value %s", src[:valEndIndex])
}
func expandEscapes(str string) string {
out := escapeRegex.ReplaceAllStringFunc(str, func(match string) string {
c := strings.TrimPrefix(match, `\`)
switch c {
case "n":
return "\n"
case "r":
return "\r"
default:
return match
}
})
return unescapeCharsRegex.ReplaceAllString(out, "$1")
}
func indexOfNonSpaceChar(src []byte) int {
return bytes.IndexFunc(src, func(r rune) bool {
return !unicode.IsSpace(r)
})
}
// hasQuotePrefix reports whether charset starts with single or double quote and returns quote character
func hasQuotePrefix(src []byte) (prefix byte, isQuored bool) {
if len(src) == 0 {
return 0, false
}
switch prefix := src[0]; prefix {
case prefixDoubleQuote, prefixSingleQuote:
return prefix, true
default:
return 0, false
}
}
func isCharFunc(char rune) func(rune) bool {
return func(v rune) bool {
return v == char
}
}
// isSpace reports whether the rune is a space character but not line break character
//
// this differs from unicode.IsSpace, which also applies line break as space
func isSpace(r rune) bool {
switch r {
case '\t', '\v', '\f', '\r', ' ', 0x85, 0xA0:
return true
}
return false
}
func isLineEnd(r rune) bool {
if r == '\n' || r == '\r' {
return true
}
return false
}
var (
escapeRegex = regexp.MustCompile(`\\.`)
expandVarRegex = regexp.MustCompile(`(\\)?(\$)(\()?\{?([A-Z0-9_]+)?\}?`)
unescapeCharsRegex = regexp.MustCompile(`\\([^$])`)
)
func expandVariables(v string, m map[string]string) string {
return expandVarRegex.ReplaceAllStringFunc(v, func(s string) string {
submatch := expandVarRegex.FindStringSubmatch(s)
if submatch == nil {
return s
}
if submatch[1] == "\\" || submatch[2] == "(" {
return submatch[0][1:]
} else if submatch[4] != "" {
return m[submatch[4]]
}
return s
})
}

View File

@@ -4,10 +4,16 @@ import (
"os" "os"
"strconv" "strconv"
"strings" "strings"
"gitlab.com/etke.cc/go/env/dotenv"
) )
var envprefix string var envprefix string
func init() {
dotenv.Load()
}
// SetPrefix sets prefix for all env vars // SetPrefix sets prefix for all env vars
func SetPrefix(prefix string) { func SetPrefix(prefix string) {
envprefix = prefix envprefix = prefix

22
vendor/gitlab.com/etke.cc/go/env/justfile generated vendored Normal file
View File

@@ -0,0 +1,22 @@
# show help by default
default:
@just --list --justfile {{ justfile() }}
# update go deps
update *flags:
go get {{flags}} .
go mod tidy
# run linter
lint:
golangci-lint run ./...
# automatically fix liter issues
lintfix:
golangci-lint run --fix ./...
# run unit tests
test:
@go test -cover -coverprofile=cover.out -coverpkg=./... -covermode=set ./...
@go tool cover -func=cover.out
-@rm -f cover.out

View File

@@ -104,6 +104,8 @@ type Database struct {
Dialect Dialect Dialect Dialect
UpgradeTable UpgradeTable UpgradeTable UpgradeTable
txnCtxKey contextKey
IgnoreForeignTables bool IgnoreForeignTables bool
IgnoreUnsupportedDatabase bool IgnoreUnsupportedDatabase bool
} }
@@ -132,6 +134,8 @@ func (db *Database) Child(versionTable string, upgradeTable UpgradeTable, log Da
Log: log, Log: log,
Dialect: db.Dialect, Dialect: db.Dialect,
txnCtxKey: db.txnCtxKey,
IgnoreForeignTables: true, IgnoreForeignTables: true,
IgnoreUnsupportedDatabase: db.IgnoreUnsupportedDatabase, IgnoreUnsupportedDatabase: db.IgnoreUnsupportedDatabase,
} }
@@ -149,6 +153,8 @@ func NewWithDB(db *sql.DB, rawDialect string) (*Database, error) {
IgnoreForeignTables: true, IgnoreForeignTables: true,
VersionTable: "version", VersionTable: "version",
txnCtxKey: contextKey(nextContextKeyDatabaseTransaction.Add(1)),
} }
wrappedDB.LoggingDB.UnderlyingExecable = db wrappedDB.LoggingDB.UnderlyingExecable = db
wrappedDB.LoggingDB.db = wrappedDB wrappedDB.LoggingDB.db = wrappedDB

View File

@@ -4,6 +4,7 @@ import (
"database/sql/driver" "database/sql/driver"
"encoding/json" "encoding/json"
"fmt" "fmt"
"unsafe"
) )
// JSON is a utility type for using arbitrary JSON data as values in database Exec and Scan calls. // JSON is a utility type for using arbitrary JSON data as values in database Exec and Scan calls.
@@ -29,7 +30,7 @@ func (j JSON) Value() (driver.Value, error) {
return nil, nil return nil, nil
} }
v, err := json.Marshal(j.Data) v, err := json.Marshal(j.Data)
return string(v), err return unsafe.String(unsafe.SliceData(v), len(v)), err
} }
// JSONPtr is a convenience function for wrapping a pointer to a value in the JSON utility, but removing typed nils // JSONPtr is a convenience function for wrapping a pointer to a value in the JSON utility, but removing typed nils

View File

@@ -2,8 +2,8 @@
CREATE TABLE foo ( CREATE TABLE foo (
-- only: postgres -- only: postgres
key BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, key BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
-- only: sqlite -- only: sqlite (line commented)
key INTEGER PRIMARY KEY, -- key INTEGER PRIMARY KEY,
data JSONB NOT NULL data JSONB NOT NULL
); );

View File

@@ -8,4 +8,3 @@ CREATE FUNCTION delete_data() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN
DELETE FROM test WHERE key <= NEW.data->>'index'; DELETE FROM test WHERE key <= NEW.data->>'index';
RETURN NEW; RETURN NEW;
END $$; END $$;
-- end only postgres

View File

@@ -7,4 +7,3 @@ CREATE TABLE foo (
CREATE TRIGGER test AFTER INSERT ON foo WHEN NEW.data->>'action' = 'delete' BEGIN CREATE TRIGGER test AFTER INSERT ON foo WHEN NEW.data->>'action' = 'delete' BEGIN
DELETE FROM test WHERE key <= NEW.data->>'index'; DELETE FROM test WHERE key <= NEW.data->>'index';
END; END;
-- end only sqlite

View File

@@ -12,6 +12,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"runtime" "runtime"
"sync/atomic"
"time" "time"
"github.com/rs/zerolog" "github.com/rs/zerolog"
@@ -26,13 +27,18 @@ var (
ErrTxnCommit = fmt.Errorf("%w: commit", ErrTxn) ErrTxnCommit = fmt.Errorf("%w: commit", ErrTxn)
) )
type contextKey int type contextKey int64
const ( const (
ContextKeyDatabaseTransaction contextKey = iota ContextKeyDoTxnCallerSkip contextKey = 1
ContextKeyDoTxnCallerSkip
) )
var nextContextKeyDatabaseTransaction atomic.Uint64
func init() {
nextContextKeyDatabaseTransaction.Store(1 << 61)
}
func (db *Database) Exec(ctx context.Context, query string, args ...any) (sql.Result, error) { func (db *Database) Exec(ctx context.Context, query string, args ...any) (sql.Result, error) {
return db.Conn(ctx).ExecContext(ctx, query, args...) return db.Conn(ctx).ExecContext(ctx, query, args...)
} }
@@ -56,7 +62,7 @@ func (db *Database) DoTxn(ctx context.Context, opts *sql.TxOptions, fn func(ctx
if ctx == nil { if ctx == nil {
panic("DoTxn() called with nil ctx") panic("DoTxn() called with nil ctx")
} }
if ctx.Value(ContextKeyDatabaseTransaction) != nil { if ctx.Value(db.txnCtxKey) != nil {
zerolog.Ctx(ctx).Trace().Msg("Already in a transaction, not creating a new one") zerolog.Ctx(ctx).Trace().Msg("Already in a transaction, not creating a new one")
return fn(ctx) return fn(ctx)
} }
@@ -82,7 +88,7 @@ func (db *Database) DoTxn(ctx context.Context, opts *sql.TxOptions, fn func(ctx
select { select {
case <-ticker.C: case <-ticker.C:
slowLog.Warn(). slowLog.Warn().
Dur("duration_seconds", time.Since(start)). Float64("duration_seconds", time.Since(start).Seconds()).
Msg("Transaction still running") Msg("Transaction still running")
case <-deadlockCh: case <-deadlockCh:
return return
@@ -106,7 +112,7 @@ func (db *Database) DoTxn(ctx context.Context, opts *sql.TxOptions, fn func(ctx
log.Trace().Msg("Transaction started") log.Trace().Msg("Transaction started")
tx.noTotalLog = true tx.noTotalLog = true
ctx = log.WithContext(ctx) ctx = log.WithContext(ctx)
ctx = context.WithValue(ctx, ContextKeyDatabaseTransaction, tx) ctx = context.WithValue(ctx, db.txnCtxKey, tx)
err = fn(ctx) err = fn(ctx)
if err != nil { if err != nil {
log.Trace().Err(err).Msg("Database transaction failed, rolling back") log.Trace().Err(err).Msg("Database transaction failed, rolling back")
@@ -131,7 +137,7 @@ func (db *Database) Conn(ctx context.Context) Execable {
if ctx == nil { if ctx == nil {
panic("Conn() called with nil ctx") panic("Conn() called with nil ctx")
} }
txn, ok := ctx.Value(ContextKeyDatabaseTransaction).(Transaction) txn, ok := ctx.Value(db.txnCtxKey).(Transaction)
if ok { if ok {
return txn return txn
} }

View File

@@ -121,40 +121,38 @@ func parseFileHeader(file []byte) (from, to, compat int, message string, txn boo
// -- only: sqlite for next 123 lines // -- only: sqlite for next 123 lines
// //
// If the single-line limit is on the second line of the file, the whole file is limited to that dialect. // If the single-line limit is on the second line of the file, the whole file is limited to that dialect.
var dialectLineFilter = regexp.MustCompile(`^\s*-- only: (postgres|sqlite)(?: for next (\d+) lines| until "(end) only")?`) //
// If the filter ends with `(lines commented)`, then ALL lines chosen by the filter will be uncommented.
var dialectLineFilter = regexp.MustCompile(`^\s*-- only: (postgres|sqlite)(?: for next (\d+) lines| until "(end) only")?(?: \(lines? (commented)\))?`)
// Constants used to make parseDialectFilter clearer // Constants used to make parseDialectFilter clearer
const ( const (
skipUntilEndTag = -1 skipUntilEndTag = -1
skipNothing = 0 skipNothing = 0
skipCurrentLine = 1 skipNextLine = 1
skipNextLine = 2
) )
func (db *Database) parseDialectFilter(line []byte) (int, error) { func (db *Database) parseDialectFilter(line []byte) (dialect Dialect, lineCount int, uncomment bool, err error) {
match := dialectLineFilter.FindSubmatch(line) match := dialectLineFilter.FindSubmatch(line)
if match == nil { if match == nil {
return skipNothing, nil return
} }
dialect, err := ParseDialect(string(match[1])) dialect, err = ParseDialect(string(match[1]))
if err != nil { if err != nil {
return skipNothing, err return
} else if dialect == db.Dialect {
// Skip the dialect filter line
return skipCurrentLine, nil
} else if bytes.Equal(match[3], []byte("end")) {
return skipUntilEndTag, nil
} else if len(match[2]) == 0 {
// Skip the dialect filter and the next line
return skipNextLine, nil
} else {
// Parse number of lines to skip, add 1 for current line
lineCount, err := strconv.Atoi(string(match[2]))
if err != nil {
return skipNothing, fmt.Errorf("invalid line count '%s': %w", match[2], err)
}
return skipCurrentLine + lineCount, nil
} }
uncomment = bytes.Equal(match[4], []byte("commented"))
if bytes.Equal(match[3], []byte("end")) {
lineCount = skipUntilEndTag
} else if len(match[2]) == 0 {
lineCount = skipNextLine
} else {
lineCount, err = strconv.Atoi(string(match[2]))
if err != nil {
err = fmt.Errorf("invalid line count %q: %w", match[2], err)
}
}
return
} }
var endLineFilter = regexp.MustCompile(`^\s*-- end only (postgres|sqlite)$`) var endLineFilter = regexp.MustCompile(`^\s*-- end only (postgres|sqlite)$`)
@@ -162,15 +160,16 @@ var endLineFilter = regexp.MustCompile(`^\s*-- end only (postgres|sqlite)$`)
func (db *Database) filterSQLUpgrade(lines [][]byte) (string, error) { func (db *Database) filterSQLUpgrade(lines [][]byte) (string, error) {
output := make([][]byte, 0, len(lines)) output := make([][]byte, 0, len(lines))
for i := 0; i < len(lines); i++ { for i := 0; i < len(lines); i++ {
skipLines, err := db.parseDialectFilter(lines[i]) dialect, lineCount, uncomment, err := db.parseDialectFilter(lines[i])
if err != nil { if err != nil {
return "", err return "", err
} else if skipLines > 0 { } else if lineCount == skipNothing {
// Current line is implicitly skipped, so reduce one here output = append(output, lines[i])
i += skipLines - 1 } else if lineCount == skipUntilEndTag {
} else if skipLines == skipUntilEndTag {
startedAt := i startedAt := i
startedAtMatch := dialectLineFilter.FindSubmatch(lines[startedAt]) startedAtMatch := dialectLineFilter.FindSubmatch(lines[startedAt])
// Skip filter start line
i++
for ; i < len(lines); i++ { for ; i < len(lines); i++ {
if match := endLineFilter.FindSubmatch(lines[i]); match != nil { if match := endLineFilter.FindSubmatch(lines[i]); match != nil {
if !bytes.Equal(match[1], startedAtMatch[1]) { if !bytes.Equal(match[1], startedAtMatch[1]) {
@@ -178,12 +177,32 @@ func (db *Database) filterSQLUpgrade(lines [][]byte) (string, error) {
} }
break break
} }
if dialect == db.Dialect {
if uncomment {
output = append(output, bytes.TrimPrefix(lines[i], []byte("--")))
} else {
output = append(output, lines[i])
}
}
} }
if i == len(lines) { if i == len(lines) {
return "", fmt.Errorf(`didn't get end tag matching start %q at line %d`, string(startedAtMatch[1]), startedAt) return "", fmt.Errorf(`didn't get end tag matching start %q at line %d`, string(startedAtMatch[1]), startedAt)
} }
} else if dialect != db.Dialect {
i += lineCount
} else { } else {
output = append(output, lines[i]) // Skip current line, uncomment the specified number of lines
i++
targetI := i + lineCount
for ; i < targetI; i++ {
if uncomment {
output = append(output, bytes.TrimPrefix(lines[i], []byte("--")))
} else {
output = append(output, lines[i])
}
}
// Decrement counter to avoid skipping the next line
i--
} }
} }
return string(bytes.Join(output, []byte("\n"))), nil return string(bytes.Join(output, []byte("\n"))), nil
@@ -191,7 +210,7 @@ func (db *Database) filterSQLUpgrade(lines [][]byte) (string, error) {
func sqlUpgradeFunc(fileName string, lines [][]byte) upgradeFunc { func sqlUpgradeFunc(fileName string, lines [][]byte) upgradeFunc {
return func(ctx context.Context, db *Database) error { return func(ctx context.Context, db *Database) error {
if skip, err := db.parseDialectFilter(lines[0]); err == nil && skip == skipNextLine { if dialect, skip, _, err := db.parseDialectFilter(lines[0]); err == nil && skip == skipNextLine && dialect != db.Dialect {
return nil return nil
} else if upgradeSQL, err := db.filterSQLUpgrade(lines); err != nil { } else if upgradeSQL, err := db.filterSQLUpgrade(lines); err != nil {
panic(fmt.Errorf("failed to parse upgrade %s: %w", fileName, err)) panic(fmt.Errorf("failed to parse upgrade %s: %w", fileName, err))

View File

@@ -404,10 +404,10 @@ func validateKey(key PublicKey, algo string, user string, c packetConn) (bool, e
return false, err return false, err
} }
return confirmKeyAck(key, algo, c) return confirmKeyAck(key, c)
} }
func confirmKeyAck(key PublicKey, algo string, c packetConn) (bool, error) { func confirmKeyAck(key PublicKey, c packetConn) (bool, error) {
pubKey := key.Marshal() pubKey := key.Marshal()
for { for {
@@ -425,7 +425,15 @@ func confirmKeyAck(key PublicKey, algo string, c packetConn) (bool, error) {
if err := Unmarshal(packet, &msg); err != nil { if err := Unmarshal(packet, &msg); err != nil {
return false, err return false, err
} }
if msg.Algo != algo || !bytes.Equal(msg.PubKey, pubKey) { // According to RFC 4252 Section 7 the algorithm in
// SSH_MSG_USERAUTH_PK_OK should match that of the request but some
// servers send the key type instead. OpenSSH allows any algorithm
// that matches the public key, so we do the same.
// https://github.com/openssh/openssh-portable/blob/86bdd385/sshconnect2.c#L709
if !contains(algorithmsForKeyFormat(key.Type()), msg.Algo) {
return false, nil
}
if !bytes.Equal(msg.PubKey, pubKey) {
return false, nil return false, nil
} }
return true, nil return true, nil

View File

@@ -904,6 +904,10 @@ func (k *skECDSAPublicKey) Verify(data []byte, sig *Signature) error {
return errors.New("ssh: signature did not verify") return errors.New("ssh: signature did not verify")
} }
func (k *skECDSAPublicKey) CryptoPublicKey() crypto.PublicKey {
return &k.PublicKey
}
type skEd25519PublicKey struct { type skEd25519PublicKey struct {
// application is a URL-like string, typically "ssh:" for SSH. // application is a URL-like string, typically "ssh:" for SSH.
// see openssh/PROTOCOL.u2f for details. // see openssh/PROTOCOL.u2f for details.
@@ -1000,6 +1004,10 @@ func (k *skEd25519PublicKey) Verify(data []byte, sig *Signature) error {
return nil return nil
} }
func (k *skEd25519PublicKey) CryptoPublicKey() crypto.PublicKey {
return k.PublicKey
}
// NewSignerFromKey takes an *rsa.PrivateKey, *dsa.PrivateKey, // NewSignerFromKey takes an *rsa.PrivateKey, *dsa.PrivateKey,
// *ecdsa.PrivateKey or any other crypto.Signer and returns a // *ecdsa.PrivateKey or any other crypto.Signer and returns a
// corresponding Signer instance. ECDSA keys must use P-256, P-384 or // corresponding Signer instance. ECDSA keys must use P-256, P-384 or

View File

@@ -462,6 +462,24 @@ func (p *PartialSuccessError) Error() string {
// It is returned in ServerAuthError.Errors from NewServerConn. // It is returned in ServerAuthError.Errors from NewServerConn.
var ErrNoAuth = errors.New("ssh: no auth passed yet") var ErrNoAuth = errors.New("ssh: no auth passed yet")
// BannerError is an error that can be returned by authentication handlers in
// ServerConfig to send a banner message to the client.
type BannerError struct {
Err error
Message string
}
func (b *BannerError) Unwrap() error {
return b.Err
}
func (b *BannerError) Error() string {
if b.Err == nil {
return b.Message
}
return b.Err.Error()
}
func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, error) { func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, error) {
sessionID := s.transport.getSessionID() sessionID := s.transport.getSessionID()
var cache pubKeyCache var cache pubKeyCache
@@ -734,6 +752,18 @@ userAuthLoop:
config.AuthLogCallback(s, userAuthReq.Method, authErr) config.AuthLogCallback(s, userAuthReq.Method, authErr)
} }
var bannerErr *BannerError
if errors.As(authErr, &bannerErr) {
if bannerErr.Message != "" {
bannerMsg := &userAuthBannerMsg{
Message: bannerErr.Message,
}
if err := s.transport.writePacket(Marshal(bannerMsg)); err != nil {
return nil, err
}
}
}
if authErr == nil { if authErr == nil {
break userAuthLoop break userAuthLoop
} }

View File

@@ -22,10 +22,12 @@ func Sort[S ~[]E, E constraints.Ordered](x S) {
// SortFunc sorts the slice x in ascending order as determined by the cmp // SortFunc sorts the slice x in ascending order as determined by the cmp
// function. This sort is not guaranteed to be stable. // function. This sort is not guaranteed to be stable.
// cmp(a, b) should return a negative number when a < b, a positive number when // cmp(a, b) should return a negative number when a < b, a positive number when
// a > b and zero when a == b. // a > b and zero when a == b or when a is not comparable to b in the sense
// of the formal definition of Strict Weak Ordering.
// //
// SortFunc requires that cmp is a strict weak ordering. // SortFunc requires that cmp is a strict weak ordering.
// See https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings. // See https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings.
// To indicate 'uncomparable', return 0 from the function.
func SortFunc[S ~[]E, E any](x S, cmp func(a, b E) int) { func SortFunc[S ~[]E, E any](x S, cmp func(a, b E) int) {
n := len(x) n := len(x)
pdqsortCmpFunc(x, 0, n, bits.Len(uint(n)), cmp) pdqsortCmpFunc(x, 0, n, bits.Len(uint(n)), cmp)

View File

@@ -104,7 +104,7 @@ tokenization, and tokenization and tree construction stages of the WHATWG HTML
parsing specification respectively. While the tokenizer parses and normalizes parsing specification respectively. While the tokenizer parses and normalizes
individual HTML tokens, only the parser constructs the DOM tree from the individual HTML tokens, only the parser constructs the DOM tree from the
tokenized HTML, as described in the tree construction stage of the tokenized HTML, as described in the tree construction stage of the
specification, dynamically modifying or extending the docuemnt's DOM tree. specification, dynamically modifying or extending the document's DOM tree.
If your use case requires semantically well-formed HTML documents, as defined by If your use case requires semantically well-formed HTML documents, as defined by
the WHATWG specification, the parser should be used rather than the tokenizer. the WHATWG specification, the parser should be used rather than the tokenizer.

1
vendor/golang.org/x/sys/cpu/cpu.go generated vendored
View File

@@ -103,6 +103,7 @@ var ARM64 struct {
HasASIMDDP bool // Advanced SIMD double precision instruction set HasASIMDDP bool // Advanced SIMD double precision instruction set
HasSHA512 bool // SHA512 hardware implementation HasSHA512 bool // SHA512 hardware implementation
HasSVE bool // Scalable Vector Extensions HasSVE bool // Scalable Vector Extensions
HasSVE2 bool // Scalable Vector Extensions 2
HasASIMDFHM bool // Advanced SIMD multiplication FP16 to FP32 HasASIMDFHM bool // Advanced SIMD multiplication FP16 to FP32
_ CacheLinePad _ CacheLinePad
} }

View File

@@ -28,6 +28,7 @@ func initOptions() {
{Name: "sm3", Feature: &ARM64.HasSM3}, {Name: "sm3", Feature: &ARM64.HasSM3},
{Name: "sm4", Feature: &ARM64.HasSM4}, {Name: "sm4", Feature: &ARM64.HasSM4},
{Name: "sve", Feature: &ARM64.HasSVE}, {Name: "sve", Feature: &ARM64.HasSVE},
{Name: "sve2", Feature: &ARM64.HasSVE2},
{Name: "crc32", Feature: &ARM64.HasCRC32}, {Name: "crc32", Feature: &ARM64.HasCRC32},
{Name: "atomics", Feature: &ARM64.HasATOMICS}, {Name: "atomics", Feature: &ARM64.HasATOMICS},
{Name: "asimdhp", Feature: &ARM64.HasASIMDHP}, {Name: "asimdhp", Feature: &ARM64.HasASIMDHP},
@@ -164,6 +165,15 @@ func parseARM64SystemRegisters(isar0, isar1, pfr0 uint64) {
switch extractBits(pfr0, 32, 35) { switch extractBits(pfr0, 32, 35) {
case 1: case 1:
ARM64.HasSVE = true ARM64.HasSVE = true
parseARM64SVERegister(getzfr0())
}
}
func parseARM64SVERegister(zfr0 uint64) {
switch extractBits(zfr0, 0, 3) {
case 1:
ARM64.HasSVE2 = true
} }
} }

View File

@@ -29,3 +29,11 @@ TEXT ·getpfr0(SB),NOSPLIT,$0-8
WORD $0xd5380400 WORD $0xd5380400
MOVD R0, ret+0(FP) MOVD R0, ret+0(FP)
RET RET
// func getzfr0() uint64
TEXT ·getzfr0(SB),NOSPLIT,$0-8
// get SVE Feature Register 0 into x0
// mrs x0, ID_AA64ZFR0_EL1 = d5380480
WORD $0xd5380480
MOVD R0, ret+0(FP)
RET

View File

@@ -9,3 +9,4 @@ package cpu
func getisar0() uint64 func getisar0() uint64
func getisar1() uint64 func getisar1() uint64
func getpfr0() uint64 func getpfr0() uint64
func getzfr0() uint64

View File

@@ -35,6 +35,8 @@ const (
hwcap_SHA512 = 1 << 21 hwcap_SHA512 = 1 << 21
hwcap_SVE = 1 << 22 hwcap_SVE = 1 << 22
hwcap_ASIMDFHM = 1 << 23 hwcap_ASIMDFHM = 1 << 23
hwcap2_SVE2 = 1 << 1
) )
// linuxKernelCanEmulateCPUID reports whether we're running // linuxKernelCanEmulateCPUID reports whether we're running
@@ -104,6 +106,9 @@ func doinit() {
ARM64.HasSHA512 = isSet(hwCap, hwcap_SHA512) ARM64.HasSHA512 = isSet(hwCap, hwcap_SHA512)
ARM64.HasSVE = isSet(hwCap, hwcap_SVE) ARM64.HasSVE = isSet(hwCap, hwcap_SVE)
ARM64.HasASIMDFHM = isSet(hwCap, hwcap_ASIMDFHM) ARM64.HasASIMDFHM = isSet(hwCap, hwcap_ASIMDFHM)
// HWCAP2 feature bits
ARM64.HasSVE2 = isSet(hwCap2, hwcap2_SVE2)
} }
func isSet(hwc uint, value uint) bool { func isSet(hwc uint, value uint) bool {

View File

@@ -9,9 +9,11 @@
#define PSALAA 1208(R0) #define PSALAA 1208(R0)
#define GTAB64(x) 80(x) #define GTAB64(x) 80(x)
#define LCA64(x) 88(x) #define LCA64(x) 88(x)
#define SAVSTACK_ASYNC(x) 336(x) // in the LCA
#define CAA(x) 8(x) #define CAA(x) 8(x)
#define EDCHPXV(x) 1016(x) // in the CAA #define CEECAATHDID(x) 976(x) // in the CAA
#define SAVSTACK_ASYNC(x) 336(x) // in the LCA #define EDCHPXV(x) 1016(x) // in the CAA
#define GOCB(x) 1104(x) // in the CAA
// SS_*, where x=SAVSTACK_ASYNC // SS_*, where x=SAVSTACK_ASYNC
#define SS_LE(x) 0(x) #define SS_LE(x) 0(x)
@@ -19,394 +21,125 @@
#define SS_ERRNO(x) 16(x) #define SS_ERRNO(x) 16(x)
#define SS_ERRNOJR(x) 20(x) #define SS_ERRNOJR(x) 20(x)
#define LE_CALL BYTE $0x0D; BYTE $0x76; // BL R7, R6 // Function Descriptor Offsets
#define __errno 0x156*16
#define __err2ad 0x16C*16
TEXT ·clearErrno(SB),NOSPLIT,$0-0 // Call Instructions
BL addrerrno<>(SB) #define LE_CALL BYTE $0x0D; BYTE $0x76 // BL R7, R6
MOVD $0, 0(R3) #define SVC_LOAD BYTE $0x0A; BYTE $0x08 // SVC 08 LOAD
#define SVC_DELETE BYTE $0x0A; BYTE $0x09 // SVC 09 DELETE
DATA zosLibVec<>(SB)/8, $0
GLOBL zosLibVec<>(SB), NOPTR, $8
TEXT ·initZosLibVec(SB), NOSPLIT|NOFRAME, $0-0
MOVW PSALAA, R8
MOVD LCA64(R8), R8
MOVD CAA(R8), R8
MOVD EDCHPXV(R8), R8
MOVD R8, zosLibVec<>(SB)
RET
TEXT ·GetZosLibVec(SB), NOSPLIT|NOFRAME, $0-0
MOVD zosLibVec<>(SB), R8
MOVD R8, ret+0(FP)
RET
TEXT ·clearErrno(SB), NOSPLIT, $0-0
BL addrerrno<>(SB)
MOVD $0, 0(R3)
RET RET
// Returns the address of errno in R3. // Returns the address of errno in R3.
TEXT addrerrno<>(SB),NOSPLIT|NOFRAME,$0-0 TEXT addrerrno<>(SB), NOSPLIT|NOFRAME, $0-0
// Get library control area (LCA). // Get library control area (LCA).
MOVW PSALAA, R8 MOVW PSALAA, R8
MOVD LCA64(R8), R8 MOVD LCA64(R8), R8
// Get __errno FuncDesc. // Get __errno FuncDesc.
MOVD CAA(R8), R9 MOVD CAA(R8), R9
MOVD EDCHPXV(R9), R9 MOVD EDCHPXV(R9), R9
ADD $(0x156*16), R9 ADD $(__errno), R9
LMG 0(R9), R5, R6 LMG 0(R9), R5, R6
// Switch to saved LE stack. // Switch to saved LE stack.
MOVD SAVSTACK_ASYNC(R8), R9 MOVD SAVSTACK_ASYNC(R8), R9
MOVD 0(R9), R4 MOVD 0(R9), R4
MOVD $0, 0(R9) MOVD $0, 0(R9)
// Call __errno function. // Call __errno function.
LE_CALL LE_CALL
NOPH NOPH
// Switch back to Go stack. // Switch back to Go stack.
XOR R0, R0 // Restore R0 to $0. XOR R0, R0 // Restore R0 to $0.
MOVD R4, 0(R9) // Save stack pointer. MOVD R4, 0(R9) // Save stack pointer.
RET
TEXT ·syscall_syscall(SB),NOSPLIT,$0-56
BL runtime·entersyscall(SB)
MOVD a1+8(FP), R1
MOVD a2+16(FP), R2
MOVD a3+24(FP), R3
// Get library control area (LCA).
MOVW PSALAA, R8
MOVD LCA64(R8), R8
// Get function.
MOVD CAA(R8), R9
MOVD EDCHPXV(R9), R9
MOVD trap+0(FP), R5
SLD $4, R5
ADD R5, R9
LMG 0(R9), R5, R6
// Restore LE stack.
MOVD SAVSTACK_ASYNC(R8), R9
MOVD 0(R9), R4
MOVD $0, 0(R9)
// Call function.
LE_CALL
NOPH
XOR R0, R0 // Restore R0 to $0.
MOVD R4, 0(R9) // Save stack pointer.
MOVD R3, r1+32(FP)
MOVD R0, r2+40(FP)
MOVD R0, err+48(FP)
MOVW R3, R4
CMP R4, $-1
BNE done
BL addrerrno<>(SB)
MOVWZ 0(R3), R3
MOVD R3, err+48(FP)
done:
BL runtime·exitsyscall(SB)
RET
TEXT ·syscall_rawsyscall(SB),NOSPLIT,$0-56
MOVD a1+8(FP), R1
MOVD a2+16(FP), R2
MOVD a3+24(FP), R3
// Get library control area (LCA).
MOVW PSALAA, R8
MOVD LCA64(R8), R8
// Get function.
MOVD CAA(R8), R9
MOVD EDCHPXV(R9), R9
MOVD trap+0(FP), R5
SLD $4, R5
ADD R5, R9
LMG 0(R9), R5, R6
// Restore LE stack.
MOVD SAVSTACK_ASYNC(R8), R9
MOVD 0(R9), R4
MOVD $0, 0(R9)
// Call function.
LE_CALL
NOPH
XOR R0, R0 // Restore R0 to $0.
MOVD R4, 0(R9) // Save stack pointer.
MOVD R3, r1+32(FP)
MOVD R0, r2+40(FP)
MOVD R0, err+48(FP)
MOVW R3, R4
CMP R4, $-1
BNE done
BL addrerrno<>(SB)
MOVWZ 0(R3), R3
MOVD R3, err+48(FP)
done:
RET
TEXT ·syscall_syscall6(SB),NOSPLIT,$0-80
BL runtime·entersyscall(SB)
MOVD a1+8(FP), R1
MOVD a2+16(FP), R2
MOVD a3+24(FP), R3
// Get library control area (LCA).
MOVW PSALAA, R8
MOVD LCA64(R8), R8
// Get function.
MOVD CAA(R8), R9
MOVD EDCHPXV(R9), R9
MOVD trap+0(FP), R5
SLD $4, R5
ADD R5, R9
LMG 0(R9), R5, R6
// Restore LE stack.
MOVD SAVSTACK_ASYNC(R8), R9
MOVD 0(R9), R4
MOVD $0, 0(R9)
// Fill in parameter list.
MOVD a4+32(FP), R12
MOVD R12, (2176+24)(R4)
MOVD a5+40(FP), R12
MOVD R12, (2176+32)(R4)
MOVD a6+48(FP), R12
MOVD R12, (2176+40)(R4)
// Call function.
LE_CALL
NOPH
XOR R0, R0 // Restore R0 to $0.
MOVD R4, 0(R9) // Save stack pointer.
MOVD R3, r1+56(FP)
MOVD R0, r2+64(FP)
MOVD R0, err+72(FP)
MOVW R3, R4
CMP R4, $-1
BNE done
BL addrerrno<>(SB)
MOVWZ 0(R3), R3
MOVD R3, err+72(FP)
done:
BL runtime·exitsyscall(SB)
RET
TEXT ·syscall_rawsyscall6(SB),NOSPLIT,$0-80
MOVD a1+8(FP), R1
MOVD a2+16(FP), R2
MOVD a3+24(FP), R3
// Get library control area (LCA).
MOVW PSALAA, R8
MOVD LCA64(R8), R8
// Get function.
MOVD CAA(R8), R9
MOVD EDCHPXV(R9), R9
MOVD trap+0(FP), R5
SLD $4, R5
ADD R5, R9
LMG 0(R9), R5, R6
// Restore LE stack.
MOVD SAVSTACK_ASYNC(R8), R9
MOVD 0(R9), R4
MOVD $0, 0(R9)
// Fill in parameter list.
MOVD a4+32(FP), R12
MOVD R12, (2176+24)(R4)
MOVD a5+40(FP), R12
MOVD R12, (2176+32)(R4)
MOVD a6+48(FP), R12
MOVD R12, (2176+40)(R4)
// Call function.
LE_CALL
NOPH
XOR R0, R0 // Restore R0 to $0.
MOVD R4, 0(R9) // Save stack pointer.
MOVD R3, r1+56(FP)
MOVD R0, r2+64(FP)
MOVD R0, err+72(FP)
MOVW R3, R4
CMP R4, $-1
BNE done
BL ·rrno<>(SB)
MOVWZ 0(R3), R3
MOVD R3, err+72(FP)
done:
RET
TEXT ·syscall_syscall9(SB),NOSPLIT,$0
BL runtime·entersyscall(SB)
MOVD a1+8(FP), R1
MOVD a2+16(FP), R2
MOVD a3+24(FP), R3
// Get library control area (LCA).
MOVW PSALAA, R8
MOVD LCA64(R8), R8
// Get function.
MOVD CAA(R8), R9
MOVD EDCHPXV(R9), R9
MOVD trap+0(FP), R5
SLD $4, R5
ADD R5, R9
LMG 0(R9), R5, R6
// Restore LE stack.
MOVD SAVSTACK_ASYNC(R8), R9
MOVD 0(R9), R4
MOVD $0, 0(R9)
// Fill in parameter list.
MOVD a4+32(FP), R12
MOVD R12, (2176+24)(R4)
MOVD a5+40(FP), R12
MOVD R12, (2176+32)(R4)
MOVD a6+48(FP), R12
MOVD R12, (2176+40)(R4)
MOVD a7+56(FP), R12
MOVD R12, (2176+48)(R4)
MOVD a8+64(FP), R12
MOVD R12, (2176+56)(R4)
MOVD a9+72(FP), R12
MOVD R12, (2176+64)(R4)
// Call function.
LE_CALL
NOPH
XOR R0, R0 // Restore R0 to $0.
MOVD R4, 0(R9) // Save stack pointer.
MOVD R3, r1+80(FP)
MOVD R0, r2+88(FP)
MOVD R0, err+96(FP)
MOVW R3, R4
CMP R4, $-1
BNE done
BL addrerrno<>(SB)
MOVWZ 0(R3), R3
MOVD R3, err+96(FP)
done:
BL runtime·exitsyscall(SB)
RET
TEXT ·syscall_rawsyscall9(SB),NOSPLIT,$0
MOVD a1+8(FP), R1
MOVD a2+16(FP), R2
MOVD a3+24(FP), R3
// Get library control area (LCA).
MOVW PSALAA, R8
MOVD LCA64(R8), R8
// Get function.
MOVD CAA(R8), R9
MOVD EDCHPXV(R9), R9
MOVD trap+0(FP), R5
SLD $4, R5
ADD R5, R9
LMG 0(R9), R5, R6
// Restore LE stack.
MOVD SAVSTACK_ASYNC(R8), R9
MOVD 0(R9), R4
MOVD $0, 0(R9)
// Fill in parameter list.
MOVD a4+32(FP), R12
MOVD R12, (2176+24)(R4)
MOVD a5+40(FP), R12
MOVD R12, (2176+32)(R4)
MOVD a6+48(FP), R12
MOVD R12, (2176+40)(R4)
MOVD a7+56(FP), R12
MOVD R12, (2176+48)(R4)
MOVD a8+64(FP), R12
MOVD R12, (2176+56)(R4)
MOVD a9+72(FP), R12
MOVD R12, (2176+64)(R4)
// Call function.
LE_CALL
NOPH
XOR R0, R0 // Restore R0 to $0.
MOVD R4, 0(R9) // Save stack pointer.
MOVD R3, r1+80(FP)
MOVD R0, r2+88(FP)
MOVD R0, err+96(FP)
MOVW R3, R4
CMP R4, $-1
BNE done
BL addrerrno<>(SB)
MOVWZ 0(R3), R3
MOVD R3, err+96(FP)
done:
RET RET
// func svcCall(fnptr unsafe.Pointer, argv *unsafe.Pointer, dsa *uint64) // func svcCall(fnptr unsafe.Pointer, argv *unsafe.Pointer, dsa *uint64)
TEXT ·svcCall(SB),NOSPLIT,$0 TEXT ·svcCall(SB), NOSPLIT, $0
BL runtime·save_g(SB) // Save g and stack pointer BL runtime·save_g(SB) // Save g and stack pointer
MOVW PSALAA, R8 MOVW PSALAA, R8
MOVD LCA64(R8), R8 MOVD LCA64(R8), R8
MOVD SAVSTACK_ASYNC(R8), R9 MOVD SAVSTACK_ASYNC(R8), R9
MOVD R15, 0(R9) MOVD R15, 0(R9)
MOVD argv+8(FP), R1 // Move function arguments into registers MOVD argv+8(FP), R1 // Move function arguments into registers
MOVD dsa+16(FP), g MOVD dsa+16(FP), g
MOVD fnptr+0(FP), R15 MOVD fnptr+0(FP), R15
BYTE $0x0D // Branch to function BYTE $0x0D // Branch to function
BYTE $0xEF BYTE $0xEF
BL runtime·load_g(SB) // Restore g and stack pointer BL runtime·load_g(SB) // Restore g and stack pointer
MOVW PSALAA, R8 MOVW PSALAA, R8
MOVD LCA64(R8), R8 MOVD LCA64(R8), R8
MOVD SAVSTACK_ASYNC(R8), R9 MOVD SAVSTACK_ASYNC(R8), R9
MOVD 0(R9), R15 MOVD 0(R9), R15
RET RET
// func svcLoad(name *byte) unsafe.Pointer // func svcLoad(name *byte) unsafe.Pointer
TEXT ·svcLoad(SB),NOSPLIT,$0 TEXT ·svcLoad(SB), NOSPLIT, $0
MOVD R15, R2 // Save go stack pointer MOVD R15, R2 // Save go stack pointer
MOVD name+0(FP), R0 // Move SVC args into registers MOVD name+0(FP), R0 // Move SVC args into registers
MOVD $0x80000000, R1 MOVD $0x80000000, R1
MOVD $0, R15 MOVD $0, R15
BYTE $0x0A // SVC 08 LOAD SVC_LOAD
BYTE $0x08 MOVW R15, R3 // Save return code from SVC
MOVW R15, R3 // Save return code from SVC MOVD R2, R15 // Restore go stack pointer
MOVD R2, R15 // Restore go stack pointer CMP R3, $0 // Check SVC return code
CMP R3, $0 // Check SVC return code BNE error
BNE error
MOVD $-2, R3 // Reset last bit of entry point to zero MOVD $-2, R3 // Reset last bit of entry point to zero
AND R0, R3 AND R0, R3
MOVD R3, addr+8(FP) // Return entry point returned by SVC MOVD R3, ret+8(FP) // Return entry point returned by SVC
CMP R0, R3 // Check if last bit of entry point was set CMP R0, R3 // Check if last bit of entry point was set
BNE done BNE done
MOVD R15, R2 // Save go stack pointer MOVD R15, R2 // Save go stack pointer
MOVD $0, R15 // Move SVC args into registers (entry point still in r0 from SVC 08) MOVD $0, R15 // Move SVC args into registers (entry point still in r0 from SVC 08)
BYTE $0x0A // SVC 09 DELETE SVC_DELETE
BYTE $0x09 MOVD R2, R15 // Restore go stack pointer
MOVD R2, R15 // Restore go stack pointer
error: error:
MOVD $0, addr+8(FP) // Return 0 on failure MOVD $0, ret+8(FP) // Return 0 on failure
done: done:
XOR R0, R0 // Reset r0 to 0 XOR R0, R0 // Reset r0 to 0
RET RET
// func svcUnload(name *byte, fnptr unsafe.Pointer) int64 // func svcUnload(name *byte, fnptr unsafe.Pointer) int64
TEXT ·svcUnload(SB),NOSPLIT,$0 TEXT ·svcUnload(SB), NOSPLIT, $0
MOVD R15, R2 // Save go stack pointer MOVD R15, R2 // Save go stack pointer
MOVD name+0(FP), R0 // Move SVC args into registers MOVD name+0(FP), R0 // Move SVC args into registers
MOVD addr+8(FP), R15 MOVD fnptr+8(FP), R15
BYTE $0x0A // SVC 09 SVC_DELETE
BYTE $0x09 XOR R0, R0 // Reset r0 to 0
XOR R0, R0 // Reset r0 to 0 MOVD R15, R1 // Save SVC return code
MOVD R15, R1 // Save SVC return code MOVD R2, R15 // Restore go stack pointer
MOVD R2, R15 // Restore go stack pointer MOVD R1, ret+16(FP) // Return SVC return code
MOVD R1, rc+0(FP) // Return SVC return code
RET RET
// func gettid() uint64 // func gettid() uint64
@@ -417,7 +150,233 @@ TEXT ·gettid(SB), NOSPLIT, $0
// Get CEECAATHDID // Get CEECAATHDID
MOVD CAA(R8), R9 MOVD CAA(R8), R9
MOVD 0x3D0(R9), R9 MOVD CEECAATHDID(R9), R9
MOVD R9, ret+0(FP) MOVD R9, ret+0(FP)
RET RET
//
// Call LE function, if the return is -1
// errno and errno2 is retrieved
//
TEXT ·CallLeFuncWithErr(SB), NOSPLIT, $0
MOVW PSALAA, R8
MOVD LCA64(R8), R8
MOVD CAA(R8), R9
MOVD g, GOCB(R9)
// Restore LE stack.
MOVD SAVSTACK_ASYNC(R8), R9 // R9-> LE stack frame saving address
MOVD 0(R9), R4 // R4-> restore previously saved stack frame pointer
MOVD parms_base+8(FP), R7 // R7 -> argument array
MOVD parms_len+16(FP), R8 // R8 number of arguments
// arg 1 ---> R1
CMP R8, $0
BEQ docall
SUB $1, R8
MOVD 0(R7), R1
// arg 2 ---> R2
CMP R8, $0
BEQ docall
SUB $1, R8
ADD $8, R7
MOVD 0(R7), R2
// arg 3 --> R3
CMP R8, $0
BEQ docall
SUB $1, R8
ADD $8, R7
MOVD 0(R7), R3
CMP R8, $0
BEQ docall
MOVD $2176+16, R6 // starting LE stack address-8 to store 4th argument
repeat:
ADD $8, R7
MOVD 0(R7), R0 // advance arg pointer by 8 byte
ADD $8, R6 // advance LE argument address by 8 byte
MOVD R0, (R4)(R6*1) // copy argument from go-slice to le-frame
SUB $1, R8
CMP R8, $0
BNE repeat
docall:
MOVD funcdesc+0(FP), R8 // R8-> function descriptor
LMG 0(R8), R5, R6
MOVD $0, 0(R9) // R9 address of SAVSTACK_ASYNC
LE_CALL // balr R7, R6 (return #1)
NOPH
MOVD R3, ret+32(FP)
CMP R3, $-1 // compare result to -1
BNE done
// retrieve errno and errno2
MOVD zosLibVec<>(SB), R8
ADD $(__errno), R8
LMG 0(R8), R5, R6
LE_CALL // balr R7, R6 __errno (return #3)
NOPH
MOVWZ 0(R3), R3
MOVD R3, err+48(FP)
MOVD zosLibVec<>(SB), R8
ADD $(__err2ad), R8
LMG 0(R8), R5, R6
LE_CALL // balr R7, R6 __err2ad (return #2)
NOPH
MOVW (R3), R2 // retrieve errno2
MOVD R2, errno2+40(FP) // store in return area
done:
MOVD R4, 0(R9) // Save stack pointer.
RET
//
// Call LE function, if the return is 0
// errno and errno2 is retrieved
//
TEXT ·CallLeFuncWithPtrReturn(SB), NOSPLIT, $0
MOVW PSALAA, R8
MOVD LCA64(R8), R8
MOVD CAA(R8), R9
MOVD g, GOCB(R9)
// Restore LE stack.
MOVD SAVSTACK_ASYNC(R8), R9 // R9-> LE stack frame saving address
MOVD 0(R9), R4 // R4-> restore previously saved stack frame pointer
MOVD parms_base+8(FP), R7 // R7 -> argument array
MOVD parms_len+16(FP), R8 // R8 number of arguments
// arg 1 ---> R1
CMP R8, $0
BEQ docall
SUB $1, R8
MOVD 0(R7), R1
// arg 2 ---> R2
CMP R8, $0
BEQ docall
SUB $1, R8
ADD $8, R7
MOVD 0(R7), R2
// arg 3 --> R3
CMP R8, $0
BEQ docall
SUB $1, R8
ADD $8, R7
MOVD 0(R7), R3
CMP R8, $0
BEQ docall
MOVD $2176+16, R6 // starting LE stack address-8 to store 4th argument
repeat:
ADD $8, R7
MOVD 0(R7), R0 // advance arg pointer by 8 byte
ADD $8, R6 // advance LE argument address by 8 byte
MOVD R0, (R4)(R6*1) // copy argument from go-slice to le-frame
SUB $1, R8
CMP R8, $0
BNE repeat
docall:
MOVD funcdesc+0(FP), R8 // R8-> function descriptor
LMG 0(R8), R5, R6
MOVD $0, 0(R9) // R9 address of SAVSTACK_ASYNC
LE_CALL // balr R7, R6 (return #1)
NOPH
MOVD R3, ret+32(FP)
CMP R3, $0 // compare result to 0
BNE done
// retrieve errno and errno2
MOVD zosLibVec<>(SB), R8
ADD $(__errno), R8
LMG 0(R8), R5, R6
LE_CALL // balr R7, R6 __errno (return #3)
NOPH
MOVWZ 0(R3), R3
MOVD R3, err+48(FP)
MOVD zosLibVec<>(SB), R8
ADD $(__err2ad), R8
LMG 0(R8), R5, R6
LE_CALL // balr R7, R6 __err2ad (return #2)
NOPH
MOVW (R3), R2 // retrieve errno2
MOVD R2, errno2+40(FP) // store in return area
XOR R2, R2
MOVWZ R2, (R3) // clear errno2
done:
MOVD R4, 0(R9) // Save stack pointer.
RET
//
// function to test if a pointer can be safely dereferenced (content read)
// return 0 for succces
//
TEXT ·ptrtest(SB), NOSPLIT, $0-16
MOVD arg+0(FP), R10 // test pointer in R10
// set up R2 to point to CEECAADMC
BYTE $0xE3; BYTE $0x20; BYTE $0x04; BYTE $0xB8; BYTE $0x00; BYTE $0x17 // llgt 2,1208
BYTE $0xB9; BYTE $0x17; BYTE $0x00; BYTE $0x22 // llgtr 2,2
BYTE $0xA5; BYTE $0x26; BYTE $0x7F; BYTE $0xFF // nilh 2,32767
BYTE $0xE3; BYTE $0x22; BYTE $0x00; BYTE $0x58; BYTE $0x00; BYTE $0x04 // lg 2,88(2)
BYTE $0xE3; BYTE $0x22; BYTE $0x00; BYTE $0x08; BYTE $0x00; BYTE $0x04 // lg 2,8(2)
BYTE $0x41; BYTE $0x22; BYTE $0x03; BYTE $0x68 // la 2,872(2)
// set up R5 to point to the "shunt" path which set 1 to R3 (failure)
BYTE $0xB9; BYTE $0x82; BYTE $0x00; BYTE $0x33 // xgr 3,3
BYTE $0xA7; BYTE $0x55; BYTE $0x00; BYTE $0x04 // bras 5,lbl1
BYTE $0xA7; BYTE $0x39; BYTE $0x00; BYTE $0x01 // lghi 3,1
// if r3 is not zero (failed) then branch to finish
BYTE $0xB9; BYTE $0x02; BYTE $0x00; BYTE $0x33 // lbl1 ltgr 3,3
BYTE $0xA7; BYTE $0x74; BYTE $0x00; BYTE $0x08 // brc b'0111',lbl2
// stomic store shunt address in R5 into CEECAADMC
BYTE $0xE3; BYTE $0x52; BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x24 // stg 5,0(2)
// now try reading from the test pointer in R10, if it fails it branches to the "lghi" instruction above
BYTE $0xE3; BYTE $0x9A; BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x04 // lg 9,0(10)
// finish here, restore 0 into CEECAADMC
BYTE $0xB9; BYTE $0x82; BYTE $0x00; BYTE $0x99 // lbl2 xgr 9,9
BYTE $0xE3; BYTE $0x92; BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x24 // stg 9,0(2)
MOVD R3, ret+8(FP) // result in R3
RET
//
// function to test if a untptr can be loaded from a pointer
// return 1: the 8-byte content
// 2: 0 for success, 1 for failure
//
// func safeload(ptr uintptr) ( value uintptr, error uintptr)
TEXT ·safeload(SB), NOSPLIT, $0-24
MOVD ptr+0(FP), R10 // test pointer in R10
MOVD $0x0, R6
BYTE $0xE3; BYTE $0x20; BYTE $0x04; BYTE $0xB8; BYTE $0x00; BYTE $0x17 // llgt 2,1208
BYTE $0xB9; BYTE $0x17; BYTE $0x00; BYTE $0x22 // llgtr 2,2
BYTE $0xA5; BYTE $0x26; BYTE $0x7F; BYTE $0xFF // nilh 2,32767
BYTE $0xE3; BYTE $0x22; BYTE $0x00; BYTE $0x58; BYTE $0x00; BYTE $0x04 // lg 2,88(2)
BYTE $0xE3; BYTE $0x22; BYTE $0x00; BYTE $0x08; BYTE $0x00; BYTE $0x04 // lg 2,8(2)
BYTE $0x41; BYTE $0x22; BYTE $0x03; BYTE $0x68 // la 2,872(2)
BYTE $0xB9; BYTE $0x82; BYTE $0x00; BYTE $0x33 // xgr 3,3
BYTE $0xA7; BYTE $0x55; BYTE $0x00; BYTE $0x04 // bras 5,lbl1
BYTE $0xA7; BYTE $0x39; BYTE $0x00; BYTE $0x01 // lghi 3,1
BYTE $0xB9; BYTE $0x02; BYTE $0x00; BYTE $0x33 // lbl1 ltgr 3,3
BYTE $0xA7; BYTE $0x74; BYTE $0x00; BYTE $0x08 // brc b'0111',lbl2
BYTE $0xE3; BYTE $0x52; BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x24 // stg 5,0(2)
BYTE $0xE3; BYTE $0x6A; BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x04 // lg 6,0(10)
BYTE $0xB9; BYTE $0x82; BYTE $0x00; BYTE $0x99 // lbl2 xgr 9,9
BYTE $0xE3; BYTE $0x92; BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x24 // stg 9,0(2)
MOVD R6, value+8(FP) // result in R6
MOVD R3, error+16(FP) // error in R3
RET

657
vendor/golang.org/x/sys/unix/bpxsvc_zos.go generated vendored Normal file
View File

@@ -0,0 +1,657 @@
// Copyright 2024 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.
//go:build zos
package unix
import (
"bytes"
"fmt"
"unsafe"
)
//go:noescape
func bpxcall(plist []unsafe.Pointer, bpx_offset int64)
//go:noescape
func A2e([]byte)
//go:noescape
func E2a([]byte)
const (
BPX4STA = 192 // stat
BPX4FST = 104 // fstat
BPX4LST = 132 // lstat
BPX4OPN = 156 // open
BPX4CLO = 72 // close
BPX4CHR = 500 // chattr
BPX4FCR = 504 // fchattr
BPX4LCR = 1180 // lchattr
BPX4CTW = 492 // cond_timed_wait
BPX4GTH = 1056 // __getthent
BPX4PTQ = 412 // pthread_quiesc
BPX4PTR = 320 // ptrace
)
const (
//options
//byte1
BPX_OPNFHIGH = 0x80
//byte2
BPX_OPNFEXEC = 0x80
//byte3
BPX_O_NOLARGEFILE = 0x08
BPX_O_LARGEFILE = 0x04
BPX_O_ASYNCSIG = 0x02
BPX_O_SYNC = 0x01
//byte4
BPX_O_CREXCL = 0xc0
BPX_O_CREAT = 0x80
BPX_O_EXCL = 0x40
BPX_O_NOCTTY = 0x20
BPX_O_TRUNC = 0x10
BPX_O_APPEND = 0x08
BPX_O_NONBLOCK = 0x04
BPX_FNDELAY = 0x04
BPX_O_RDWR = 0x03
BPX_O_RDONLY = 0x02
BPX_O_WRONLY = 0x01
BPX_O_ACCMODE = 0x03
BPX_O_GETFL = 0x0f
//mode
// byte1 (file type)
BPX_FT_DIR = 1
BPX_FT_CHARSPEC = 2
BPX_FT_REGFILE = 3
BPX_FT_FIFO = 4
BPX_FT_SYMLINK = 5
BPX_FT_SOCKET = 6
//byte3
BPX_S_ISUID = 0x08
BPX_S_ISGID = 0x04
BPX_S_ISVTX = 0x02
BPX_S_IRWXU1 = 0x01
BPX_S_IRUSR = 0x01
//byte4
BPX_S_IRWXU2 = 0xc0
BPX_S_IWUSR = 0x80
BPX_S_IXUSR = 0x40
BPX_S_IRWXG = 0x38
BPX_S_IRGRP = 0x20
BPX_S_IWGRP = 0x10
BPX_S_IXGRP = 0x08
BPX_S_IRWXOX = 0x07
BPX_S_IROTH = 0x04
BPX_S_IWOTH = 0x02
BPX_S_IXOTH = 0x01
CW_INTRPT = 1
CW_CONDVAR = 32
CW_TIMEOUT = 64
PGTHA_NEXT = 2
PGTHA_CURRENT = 1
PGTHA_FIRST = 0
PGTHA_LAST = 3
PGTHA_PROCESS = 0x80
PGTHA_CONTTY = 0x40
PGTHA_PATH = 0x20
PGTHA_COMMAND = 0x10
PGTHA_FILEDATA = 0x08
PGTHA_THREAD = 0x04
PGTHA_PTAG = 0x02
PGTHA_COMMANDLONG = 0x01
PGTHA_THREADFAST = 0x80
PGTHA_FILEPATH = 0x40
PGTHA_THDSIGMASK = 0x20
// thread quiece mode
QUIESCE_TERM int32 = 1
QUIESCE_FORCE int32 = 2
QUIESCE_QUERY int32 = 3
QUIESCE_FREEZE int32 = 4
QUIESCE_UNFREEZE int32 = 5
FREEZE_THIS_THREAD int32 = 6
FREEZE_EXIT int32 = 8
QUIESCE_SRB int32 = 9
)
type Pgtha struct {
Pid uint32 // 0
Tid0 uint32 // 4
Tid1 uint32
Accesspid byte // C
Accesstid byte // D
Accessasid uint16 // E
Loginname [8]byte // 10
Flag1 byte // 18
Flag1b2 byte // 19
}
type Bpxystat_t struct { // DSECT BPXYSTAT
St_id [4]uint8 // 0
St_length uint16 // 0x4
St_version uint16 // 0x6
St_mode uint32 // 0x8
St_ino uint32 // 0xc
St_dev uint32 // 0x10
St_nlink uint32 // 0x14
St_uid uint32 // 0x18
St_gid uint32 // 0x1c
St_size uint64 // 0x20
St_atime uint32 // 0x28
St_mtime uint32 // 0x2c
St_ctime uint32 // 0x30
St_rdev uint32 // 0x34
St_auditoraudit uint32 // 0x38
St_useraudit uint32 // 0x3c
St_blksize uint32 // 0x40
St_createtime uint32 // 0x44
St_auditid [4]uint32 // 0x48
St_res01 uint32 // 0x58
Ft_ccsid uint16 // 0x5c
Ft_flags uint16 // 0x5e
St_res01a [2]uint32 // 0x60
St_res02 uint32 // 0x68
St_blocks uint32 // 0x6c
St_opaque [3]uint8 // 0x70
St_visible uint8 // 0x73
St_reftime uint32 // 0x74
St_fid uint64 // 0x78
St_filefmt uint8 // 0x80
St_fspflag2 uint8 // 0x81
St_res03 [2]uint8 // 0x82
St_ctimemsec uint32 // 0x84
St_seclabel [8]uint8 // 0x88
St_res04 [4]uint8 // 0x90
// end of version 1
_ uint32 // 0x94
St_atime64 uint64 // 0x98
St_mtime64 uint64 // 0xa0
St_ctime64 uint64 // 0xa8
St_createtime64 uint64 // 0xb0
St_reftime64 uint64 // 0xb8
_ uint64 // 0xc0
St_res05 [16]uint8 // 0xc8
// end of version 2
}
type BpxFilestatus struct {
Oflag1 byte
Oflag2 byte
Oflag3 byte
Oflag4 byte
}
type BpxMode struct {
Ftype byte
Mode1 byte
Mode2 byte
Mode3 byte
}
// Thr attribute structure for extended attributes
type Bpxyatt_t struct { // DSECT BPXYATT
Att_id [4]uint8
Att_version uint16
Att_res01 [2]uint8
Att_setflags1 uint8
Att_setflags2 uint8
Att_setflags3 uint8
Att_setflags4 uint8
Att_mode uint32
Att_uid uint32
Att_gid uint32
Att_opaquemask [3]uint8
Att_visblmaskres uint8
Att_opaque [3]uint8
Att_visibleres uint8
Att_size_h uint32
Att_size_l uint32
Att_atime uint32
Att_mtime uint32
Att_auditoraudit uint32
Att_useraudit uint32
Att_ctime uint32
Att_reftime uint32
// end of version 1
Att_filefmt uint8
Att_res02 [3]uint8
Att_filetag uint32
Att_res03 [8]uint8
// end of version 2
Att_atime64 uint64
Att_mtime64 uint64
Att_ctime64 uint64
Att_reftime64 uint64
Att_seclabel [8]uint8
Att_ver3res02 [8]uint8
// end of version 3
}
func BpxOpen(name string, options *BpxFilestatus, mode *BpxMode) (rv int32, rc int32, rn int32) {
if len(name) < 1024 {
var namebuf [1024]byte
sz := int32(copy(namebuf[:], name))
A2e(namebuf[:sz])
var parms [7]unsafe.Pointer
parms[0] = unsafe.Pointer(&sz)
parms[1] = unsafe.Pointer(&namebuf[0])
parms[2] = unsafe.Pointer(options)
parms[3] = unsafe.Pointer(mode)
parms[4] = unsafe.Pointer(&rv)
parms[5] = unsafe.Pointer(&rc)
parms[6] = unsafe.Pointer(&rn)
bpxcall(parms[:], BPX4OPN)
return rv, rc, rn
}
return -1, -1, -1
}
func BpxClose(fd int32) (rv int32, rc int32, rn int32) {
var parms [4]unsafe.Pointer
parms[0] = unsafe.Pointer(&fd)
parms[1] = unsafe.Pointer(&rv)
parms[2] = unsafe.Pointer(&rc)
parms[3] = unsafe.Pointer(&rn)
bpxcall(parms[:], BPX4CLO)
return rv, rc, rn
}
func BpxFileFStat(fd int32, st *Bpxystat_t) (rv int32, rc int32, rn int32) {
st.St_id = [4]uint8{0xe2, 0xe3, 0xc1, 0xe3}
st.St_version = 2
stat_sz := uint32(unsafe.Sizeof(*st))
var parms [6]unsafe.Pointer
parms[0] = unsafe.Pointer(&fd)
parms[1] = unsafe.Pointer(&stat_sz)
parms[2] = unsafe.Pointer(st)
parms[3] = unsafe.Pointer(&rv)
parms[4] = unsafe.Pointer(&rc)
parms[5] = unsafe.Pointer(&rn)
bpxcall(parms[:], BPX4FST)
return rv, rc, rn
}
func BpxFileStat(name string, st *Bpxystat_t) (rv int32, rc int32, rn int32) {
if len(name) < 1024 {
var namebuf [1024]byte
sz := int32(copy(namebuf[:], name))
A2e(namebuf[:sz])
st.St_id = [4]uint8{0xe2, 0xe3, 0xc1, 0xe3}
st.St_version = 2
stat_sz := uint32(unsafe.Sizeof(*st))
var parms [7]unsafe.Pointer
parms[0] = unsafe.Pointer(&sz)
parms[1] = unsafe.Pointer(&namebuf[0])
parms[2] = unsafe.Pointer(&stat_sz)
parms[3] = unsafe.Pointer(st)
parms[4] = unsafe.Pointer(&rv)
parms[5] = unsafe.Pointer(&rc)
parms[6] = unsafe.Pointer(&rn)
bpxcall(parms[:], BPX4STA)
return rv, rc, rn
}
return -1, -1, -1
}
func BpxFileLStat(name string, st *Bpxystat_t) (rv int32, rc int32, rn int32) {
if len(name) < 1024 {
var namebuf [1024]byte
sz := int32(copy(namebuf[:], name))
A2e(namebuf[:sz])
st.St_id = [4]uint8{0xe2, 0xe3, 0xc1, 0xe3}
st.St_version = 2
stat_sz := uint32(unsafe.Sizeof(*st))
var parms [7]unsafe.Pointer
parms[0] = unsafe.Pointer(&sz)
parms[1] = unsafe.Pointer(&namebuf[0])
parms[2] = unsafe.Pointer(&stat_sz)
parms[3] = unsafe.Pointer(st)
parms[4] = unsafe.Pointer(&rv)
parms[5] = unsafe.Pointer(&rc)
parms[6] = unsafe.Pointer(&rn)
bpxcall(parms[:], BPX4LST)
return rv, rc, rn
}
return -1, -1, -1
}
func BpxChattr(path string, attr *Bpxyatt_t) (rv int32, rc int32, rn int32) {
if len(path) >= 1024 {
return -1, -1, -1
}
var namebuf [1024]byte
sz := int32(copy(namebuf[:], path))
A2e(namebuf[:sz])
attr_sz := uint32(unsafe.Sizeof(*attr))
var parms [7]unsafe.Pointer
parms[0] = unsafe.Pointer(&sz)
parms[1] = unsafe.Pointer(&namebuf[0])
parms[2] = unsafe.Pointer(&attr_sz)
parms[3] = unsafe.Pointer(attr)
parms[4] = unsafe.Pointer(&rv)
parms[5] = unsafe.Pointer(&rc)
parms[6] = unsafe.Pointer(&rn)
bpxcall(parms[:], BPX4CHR)
return rv, rc, rn
}
func BpxLchattr(path string, attr *Bpxyatt_t) (rv int32, rc int32, rn int32) {
if len(path) >= 1024 {
return -1, -1, -1
}
var namebuf [1024]byte
sz := int32(copy(namebuf[:], path))
A2e(namebuf[:sz])
attr_sz := uint32(unsafe.Sizeof(*attr))
var parms [7]unsafe.Pointer
parms[0] = unsafe.Pointer(&sz)
parms[1] = unsafe.Pointer(&namebuf[0])
parms[2] = unsafe.Pointer(&attr_sz)
parms[3] = unsafe.Pointer(attr)
parms[4] = unsafe.Pointer(&rv)
parms[5] = unsafe.Pointer(&rc)
parms[6] = unsafe.Pointer(&rn)
bpxcall(parms[:], BPX4LCR)
return rv, rc, rn
}
func BpxFchattr(fd int32, attr *Bpxyatt_t) (rv int32, rc int32, rn int32) {
attr_sz := uint32(unsafe.Sizeof(*attr))
var parms [6]unsafe.Pointer
parms[0] = unsafe.Pointer(&fd)
parms[1] = unsafe.Pointer(&attr_sz)
parms[2] = unsafe.Pointer(attr)
parms[3] = unsafe.Pointer(&rv)
parms[4] = unsafe.Pointer(&rc)
parms[5] = unsafe.Pointer(&rn)
bpxcall(parms[:], BPX4FCR)
return rv, rc, rn
}
func BpxCondTimedWait(sec uint32, nsec uint32, events uint32, secrem *uint32, nsecrem *uint32) (rv int32, rc int32, rn int32) {
var parms [8]unsafe.Pointer
parms[0] = unsafe.Pointer(&sec)
parms[1] = unsafe.Pointer(&nsec)
parms[2] = unsafe.Pointer(&events)
parms[3] = unsafe.Pointer(secrem)
parms[4] = unsafe.Pointer(nsecrem)
parms[5] = unsafe.Pointer(&rv)
parms[6] = unsafe.Pointer(&rc)
parms[7] = unsafe.Pointer(&rn)
bpxcall(parms[:], BPX4CTW)
return rv, rc, rn
}
func BpxGetthent(in *Pgtha, outlen *uint32, out unsafe.Pointer) (rv int32, rc int32, rn int32) {
var parms [7]unsafe.Pointer
inlen := uint32(26) // nothing else will work. Go says Pgtha is 28-byte because of alignment, but Pgtha is "packed" and must be 26-byte
parms[0] = unsafe.Pointer(&inlen)
parms[1] = unsafe.Pointer(&in)
parms[2] = unsafe.Pointer(outlen)
parms[3] = unsafe.Pointer(&out)
parms[4] = unsafe.Pointer(&rv)
parms[5] = unsafe.Pointer(&rc)
parms[6] = unsafe.Pointer(&rn)
bpxcall(parms[:], BPX4GTH)
return rv, rc, rn
}
func ZosJobname() (jobname string, err error) {
var pgtha Pgtha
pgtha.Pid = uint32(Getpid())
pgtha.Accesspid = PGTHA_CURRENT
pgtha.Flag1 = PGTHA_PROCESS
var out [256]byte
var outlen uint32
outlen = 256
rv, rc, rn := BpxGetthent(&pgtha, &outlen, unsafe.Pointer(&out[0]))
if rv == 0 {
gthc := []byte{0x87, 0xa3, 0x88, 0x83} // 'gthc' in ebcdic
ix := bytes.Index(out[:], gthc)
if ix == -1 {
err = fmt.Errorf("BPX4GTH: gthc return data not found")
return
}
jn := out[ix+80 : ix+88] // we didn't declare Pgthc, but jobname is 8-byte at offset 80
E2a(jn)
jobname = string(bytes.TrimRight(jn, " "))
} else {
err = fmt.Errorf("BPX4GTH: rc=%d errno=%d reason=code=0x%x", rv, rc, rn)
}
return
}
func Bpx4ptq(code int32, data string) (rv int32, rc int32, rn int32) {
var userdata [8]byte
var parms [5]unsafe.Pointer
copy(userdata[:], data+" ")
A2e(userdata[:])
parms[0] = unsafe.Pointer(&code)
parms[1] = unsafe.Pointer(&userdata[0])
parms[2] = unsafe.Pointer(&rv)
parms[3] = unsafe.Pointer(&rc)
parms[4] = unsafe.Pointer(&rn)
bpxcall(parms[:], BPX4PTQ)
return rv, rc, rn
}
const (
PT_TRACE_ME = 0 // Debug this process
PT_READ_I = 1 // Read a full word
PT_READ_D = 2 // Read a full word
PT_READ_U = 3 // Read control info
PT_WRITE_I = 4 //Write a full word
PT_WRITE_D = 5 //Write a full word
PT_CONTINUE = 7 //Continue the process
PT_KILL = 8 //Terminate the process
PT_READ_GPR = 11 // Read GPR, CR, PSW
PT_READ_FPR = 12 // Read FPR
PT_READ_VR = 13 // Read VR
PT_WRITE_GPR = 14 // Write GPR, CR, PSW
PT_WRITE_FPR = 15 // Write FPR
PT_WRITE_VR = 16 // Write VR
PT_READ_BLOCK = 17 // Read storage
PT_WRITE_BLOCK = 19 // Write storage
PT_READ_GPRH = 20 // Read GPRH
PT_WRITE_GPRH = 21 // Write GPRH
PT_REGHSET = 22 // Read all GPRHs
PT_ATTACH = 30 // Attach to a process
PT_DETACH = 31 // Detach from a process
PT_REGSET = 32 // Read all GPRs
PT_REATTACH = 33 // Reattach to a process
PT_LDINFO = 34 // Read loader info
PT_MULTI = 35 // Multi process mode
PT_LD64INFO = 36 // RMODE64 Info Area
PT_BLOCKREQ = 40 // Block request
PT_THREAD_INFO = 60 // Read thread info
PT_THREAD_MODIFY = 61
PT_THREAD_READ_FOCUS = 62
PT_THREAD_WRITE_FOCUS = 63
PT_THREAD_HOLD = 64
PT_THREAD_SIGNAL = 65
PT_EXPLAIN = 66
PT_EVENTS = 67
PT_THREAD_INFO_EXTENDED = 68
PT_REATTACH2 = 71
PT_CAPTURE = 72
PT_UNCAPTURE = 73
PT_GET_THREAD_TCB = 74
PT_GET_ALET = 75
PT_SWAPIN = 76
PT_EXTENDED_EVENT = 98
PT_RECOVER = 99 // Debug a program check
PT_GPR0 = 0 // General purpose register 0
PT_GPR1 = 1 // General purpose register 1
PT_GPR2 = 2 // General purpose register 2
PT_GPR3 = 3 // General purpose register 3
PT_GPR4 = 4 // General purpose register 4
PT_GPR5 = 5 // General purpose register 5
PT_GPR6 = 6 // General purpose register 6
PT_GPR7 = 7 // General purpose register 7
PT_GPR8 = 8 // General purpose register 8
PT_GPR9 = 9 // General purpose register 9
PT_GPR10 = 10 // General purpose register 10
PT_GPR11 = 11 // General purpose register 11
PT_GPR12 = 12 // General purpose register 12
PT_GPR13 = 13 // General purpose register 13
PT_GPR14 = 14 // General purpose register 14
PT_GPR15 = 15 // General purpose register 15
PT_FPR0 = 16 // Floating point register 0
PT_FPR1 = 17 // Floating point register 1
PT_FPR2 = 18 // Floating point register 2
PT_FPR3 = 19 // Floating point register 3
PT_FPR4 = 20 // Floating point register 4
PT_FPR5 = 21 // Floating point register 5
PT_FPR6 = 22 // Floating point register 6
PT_FPR7 = 23 // Floating point register 7
PT_FPR8 = 24 // Floating point register 8
PT_FPR9 = 25 // Floating point register 9
PT_FPR10 = 26 // Floating point register 10
PT_FPR11 = 27 // Floating point register 11
PT_FPR12 = 28 // Floating point register 12
PT_FPR13 = 29 // Floating point register 13
PT_FPR14 = 30 // Floating point register 14
PT_FPR15 = 31 // Floating point register 15
PT_FPC = 32 // Floating point control register
PT_PSW = 40 // PSW
PT_PSW0 = 40 // Left half of the PSW
PT_PSW1 = 41 // Right half of the PSW
PT_CR0 = 42 // Control register 0
PT_CR1 = 43 // Control register 1
PT_CR2 = 44 // Control register 2
PT_CR3 = 45 // Control register 3
PT_CR4 = 46 // Control register 4
PT_CR5 = 47 // Control register 5
PT_CR6 = 48 // Control register 6
PT_CR7 = 49 // Control register 7
PT_CR8 = 50 // Control register 8
PT_CR9 = 51 // Control register 9
PT_CR10 = 52 // Control register 10
PT_CR11 = 53 // Control register 11
PT_CR12 = 54 // Control register 12
PT_CR13 = 55 // Control register 13
PT_CR14 = 56 // Control register 14
PT_CR15 = 57 // Control register 15
PT_GPRH0 = 58 // GP High register 0
PT_GPRH1 = 59 // GP High register 1
PT_GPRH2 = 60 // GP High register 2
PT_GPRH3 = 61 // GP High register 3
PT_GPRH4 = 62 // GP High register 4
PT_GPRH5 = 63 // GP High register 5
PT_GPRH6 = 64 // GP High register 6
PT_GPRH7 = 65 // GP High register 7
PT_GPRH8 = 66 // GP High register 8
PT_GPRH9 = 67 // GP High register 9
PT_GPRH10 = 68 // GP High register 10
PT_GPRH11 = 69 // GP High register 11
PT_GPRH12 = 70 // GP High register 12
PT_GPRH13 = 71 // GP High register 13
PT_GPRH14 = 72 // GP High register 14
PT_GPRH15 = 73 // GP High register 15
PT_VR0 = 74 // Vector register 0
PT_VR1 = 75 // Vector register 1
PT_VR2 = 76 // Vector register 2
PT_VR3 = 77 // Vector register 3
PT_VR4 = 78 // Vector register 4
PT_VR5 = 79 // Vector register 5
PT_VR6 = 80 // Vector register 6
PT_VR7 = 81 // Vector register 7
PT_VR8 = 82 // Vector register 8
PT_VR9 = 83 // Vector register 9
PT_VR10 = 84 // Vector register 10
PT_VR11 = 85 // Vector register 11
PT_VR12 = 86 // Vector register 12
PT_VR13 = 87 // Vector register 13
PT_VR14 = 88 // Vector register 14
PT_VR15 = 89 // Vector register 15
PT_VR16 = 90 // Vector register 16
PT_VR17 = 91 // Vector register 17
PT_VR18 = 92 // Vector register 18
PT_VR19 = 93 // Vector register 19
PT_VR20 = 94 // Vector register 20
PT_VR21 = 95 // Vector register 21
PT_VR22 = 96 // Vector register 22
PT_VR23 = 97 // Vector register 23
PT_VR24 = 98 // Vector register 24
PT_VR25 = 99 // Vector register 25
PT_VR26 = 100 // Vector register 26
PT_VR27 = 101 // Vector register 27
PT_VR28 = 102 // Vector register 28
PT_VR29 = 103 // Vector register 29
PT_VR30 = 104 // Vector register 30
PT_VR31 = 105 // Vector register 31
PT_PSWG = 106 // PSWG
PT_PSWG0 = 106 // Bytes 0-3
PT_PSWG1 = 107 // Bytes 4-7
PT_PSWG2 = 108 // Bytes 8-11 (IA high word)
PT_PSWG3 = 109 // Bytes 12-15 (IA low word)
)
func Bpx4ptr(request int32, pid int32, addr unsafe.Pointer, data unsafe.Pointer, buffer unsafe.Pointer) (rv int32, rc int32, rn int32) {
var parms [8]unsafe.Pointer
parms[0] = unsafe.Pointer(&request)
parms[1] = unsafe.Pointer(&pid)
parms[2] = unsafe.Pointer(&addr)
parms[3] = unsafe.Pointer(&data)
parms[4] = unsafe.Pointer(&buffer)
parms[5] = unsafe.Pointer(&rv)
parms[6] = unsafe.Pointer(&rc)
parms[7] = unsafe.Pointer(&rn)
bpxcall(parms[:], BPX4PTR)
return rv, rc, rn
}
func copyU8(val uint8, dest []uint8) int {
if len(dest) < 1 {
return 0
}
dest[0] = val
return 1
}
func copyU8Arr(src, dest []uint8) int {
if len(dest) < len(src) {
return 0
}
for i, v := range src {
dest[i] = v
}
return len(src)
}
func copyU16(val uint16, dest []uint16) int {
if len(dest) < 1 {
return 0
}
dest[0] = val
return 1
}
func copyU32(val uint32, dest []uint32) int {
if len(dest) < 1 {
return 0
}
dest[0] = val
return 1
}
func copyU32Arr(src, dest []uint32) int {
if len(dest) < len(src) {
return 0
}
for i, v := range src {
dest[i] = v
}
return len(src)
}
func copyU64(val uint64, dest []uint64) int {
if len(dest) < 1 {
return 0
}
dest[0] = val
return 1
}

192
vendor/golang.org/x/sys/unix/bpxsvc_zos.s generated vendored Normal file
View File

@@ -0,0 +1,192 @@
// Copyright 2024 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.
#include "go_asm.h"
#include "textflag.h"
// function to call USS assembly language services
//
// doc: https://www.ibm.com/support/knowledgecenter/en/SSLTBW_3.1.0/com.ibm.zos.v3r1.bpxb100/bit64env.htm
//
// arg1 unsafe.Pointer array that ressembles an OS PLIST
//
// arg2 function offset as in
// doc: https://www.ibm.com/support/knowledgecenter/en/SSLTBW_3.1.0/com.ibm.zos.v3r1.bpxb100/bpx2cr_List_of_offsets.htm
//
// func bpxcall(plist []unsafe.Pointer, bpx_offset int64)
TEXT ·bpxcall(SB), NOSPLIT|NOFRAME, $0
MOVD plist_base+0(FP), R1 // r1 points to plist
MOVD bpx_offset+24(FP), R2 // r2 offset to BPX vector table
MOVD R14, R7 // save r14
MOVD R15, R8 // save r15
MOVWZ 16(R0), R9
MOVWZ 544(R9), R9
MOVWZ 24(R9), R9 // call vector in r9
ADD R2, R9 // add offset to vector table
MOVWZ (R9), R9 // r9 points to entry point
BYTE $0x0D // BL R14,R9 --> basr r14,r9
BYTE $0xE9 // clobbers 0,1,14,15
MOVD R8, R15 // restore 15
JMP R7 // return via saved return address
// func A2e(arr [] byte)
// code page conversion from 819 to 1047
TEXT ·A2e(SB), NOSPLIT|NOFRAME, $0
MOVD arg_base+0(FP), R2 // pointer to arry of characters
MOVD arg_len+8(FP), R3 // count
XOR R0, R0
XOR R1, R1
BYTE $0xA7; BYTE $0x15; BYTE $0x00; BYTE $0x82 // BRAS 1,(2+(256/2))
// ASCII -> EBCDIC conversion table:
BYTE $0x00; BYTE $0x01; BYTE $0x02; BYTE $0x03
BYTE $0x37; BYTE $0x2d; BYTE $0x2e; BYTE $0x2f
BYTE $0x16; BYTE $0x05; BYTE $0x15; BYTE $0x0b
BYTE $0x0c; BYTE $0x0d; BYTE $0x0e; BYTE $0x0f
BYTE $0x10; BYTE $0x11; BYTE $0x12; BYTE $0x13
BYTE $0x3c; BYTE $0x3d; BYTE $0x32; BYTE $0x26
BYTE $0x18; BYTE $0x19; BYTE $0x3f; BYTE $0x27
BYTE $0x1c; BYTE $0x1d; BYTE $0x1e; BYTE $0x1f
BYTE $0x40; BYTE $0x5a; BYTE $0x7f; BYTE $0x7b
BYTE $0x5b; BYTE $0x6c; BYTE $0x50; BYTE $0x7d
BYTE $0x4d; BYTE $0x5d; BYTE $0x5c; BYTE $0x4e
BYTE $0x6b; BYTE $0x60; BYTE $0x4b; BYTE $0x61
BYTE $0xf0; BYTE $0xf1; BYTE $0xf2; BYTE $0xf3
BYTE $0xf4; BYTE $0xf5; BYTE $0xf6; BYTE $0xf7
BYTE $0xf8; BYTE $0xf9; BYTE $0x7a; BYTE $0x5e
BYTE $0x4c; BYTE $0x7e; BYTE $0x6e; BYTE $0x6f
BYTE $0x7c; BYTE $0xc1; BYTE $0xc2; BYTE $0xc3
BYTE $0xc4; BYTE $0xc5; BYTE $0xc6; BYTE $0xc7
BYTE $0xc8; BYTE $0xc9; BYTE $0xd1; BYTE $0xd2
BYTE $0xd3; BYTE $0xd4; BYTE $0xd5; BYTE $0xd6
BYTE $0xd7; BYTE $0xd8; BYTE $0xd9; BYTE $0xe2
BYTE $0xe3; BYTE $0xe4; BYTE $0xe5; BYTE $0xe6
BYTE $0xe7; BYTE $0xe8; BYTE $0xe9; BYTE $0xad
BYTE $0xe0; BYTE $0xbd; BYTE $0x5f; BYTE $0x6d
BYTE $0x79; BYTE $0x81; BYTE $0x82; BYTE $0x83
BYTE $0x84; BYTE $0x85; BYTE $0x86; BYTE $0x87
BYTE $0x88; BYTE $0x89; BYTE $0x91; BYTE $0x92
BYTE $0x93; BYTE $0x94; BYTE $0x95; BYTE $0x96
BYTE $0x97; BYTE $0x98; BYTE $0x99; BYTE $0xa2
BYTE $0xa3; BYTE $0xa4; BYTE $0xa5; BYTE $0xa6
BYTE $0xa7; BYTE $0xa8; BYTE $0xa9; BYTE $0xc0
BYTE $0x4f; BYTE $0xd0; BYTE $0xa1; BYTE $0x07
BYTE $0x20; BYTE $0x21; BYTE $0x22; BYTE $0x23
BYTE $0x24; BYTE $0x25; BYTE $0x06; BYTE $0x17
BYTE $0x28; BYTE $0x29; BYTE $0x2a; BYTE $0x2b
BYTE $0x2c; BYTE $0x09; BYTE $0x0a; BYTE $0x1b
BYTE $0x30; BYTE $0x31; BYTE $0x1a; BYTE $0x33
BYTE $0x34; BYTE $0x35; BYTE $0x36; BYTE $0x08
BYTE $0x38; BYTE $0x39; BYTE $0x3a; BYTE $0x3b
BYTE $0x04; BYTE $0x14; BYTE $0x3e; BYTE $0xff
BYTE $0x41; BYTE $0xaa; BYTE $0x4a; BYTE $0xb1
BYTE $0x9f; BYTE $0xb2; BYTE $0x6a; BYTE $0xb5
BYTE $0xbb; BYTE $0xb4; BYTE $0x9a; BYTE $0x8a
BYTE $0xb0; BYTE $0xca; BYTE $0xaf; BYTE $0xbc
BYTE $0x90; BYTE $0x8f; BYTE $0xea; BYTE $0xfa
BYTE $0xbe; BYTE $0xa0; BYTE $0xb6; BYTE $0xb3
BYTE $0x9d; BYTE $0xda; BYTE $0x9b; BYTE $0x8b
BYTE $0xb7; BYTE $0xb8; BYTE $0xb9; BYTE $0xab
BYTE $0x64; BYTE $0x65; BYTE $0x62; BYTE $0x66
BYTE $0x63; BYTE $0x67; BYTE $0x9e; BYTE $0x68
BYTE $0x74; BYTE $0x71; BYTE $0x72; BYTE $0x73
BYTE $0x78; BYTE $0x75; BYTE $0x76; BYTE $0x77
BYTE $0xac; BYTE $0x69; BYTE $0xed; BYTE $0xee
BYTE $0xeb; BYTE $0xef; BYTE $0xec; BYTE $0xbf
BYTE $0x80; BYTE $0xfd; BYTE $0xfe; BYTE $0xfb
BYTE $0xfc; BYTE $0xba; BYTE $0xae; BYTE $0x59
BYTE $0x44; BYTE $0x45; BYTE $0x42; BYTE $0x46
BYTE $0x43; BYTE $0x47; BYTE $0x9c; BYTE $0x48
BYTE $0x54; BYTE $0x51; BYTE $0x52; BYTE $0x53
BYTE $0x58; BYTE $0x55; BYTE $0x56; BYTE $0x57
BYTE $0x8c; BYTE $0x49; BYTE $0xcd; BYTE $0xce
BYTE $0xcb; BYTE $0xcf; BYTE $0xcc; BYTE $0xe1
BYTE $0x70; BYTE $0xdd; BYTE $0xde; BYTE $0xdb
BYTE $0xdc; BYTE $0x8d; BYTE $0x8e; BYTE $0xdf
retry:
WORD $0xB9931022 // TROO 2,2,b'0001'
BVS retry
RET
// func e2a(arr [] byte)
// code page conversion from 1047 to 819
TEXT ·E2a(SB), NOSPLIT|NOFRAME, $0
MOVD arg_base+0(FP), R2 // pointer to arry of characters
MOVD arg_len+8(FP), R3 // count
XOR R0, R0
XOR R1, R1
BYTE $0xA7; BYTE $0x15; BYTE $0x00; BYTE $0x82 // BRAS 1,(2+(256/2))
// EBCDIC -> ASCII conversion table:
BYTE $0x00; BYTE $0x01; BYTE $0x02; BYTE $0x03
BYTE $0x9c; BYTE $0x09; BYTE $0x86; BYTE $0x7f
BYTE $0x97; BYTE $0x8d; BYTE $0x8e; BYTE $0x0b
BYTE $0x0c; BYTE $0x0d; BYTE $0x0e; BYTE $0x0f
BYTE $0x10; BYTE $0x11; BYTE $0x12; BYTE $0x13
BYTE $0x9d; BYTE $0x0a; BYTE $0x08; BYTE $0x87
BYTE $0x18; BYTE $0x19; BYTE $0x92; BYTE $0x8f
BYTE $0x1c; BYTE $0x1d; BYTE $0x1e; BYTE $0x1f
BYTE $0x80; BYTE $0x81; BYTE $0x82; BYTE $0x83
BYTE $0x84; BYTE $0x85; BYTE $0x17; BYTE $0x1b
BYTE $0x88; BYTE $0x89; BYTE $0x8a; BYTE $0x8b
BYTE $0x8c; BYTE $0x05; BYTE $0x06; BYTE $0x07
BYTE $0x90; BYTE $0x91; BYTE $0x16; BYTE $0x93
BYTE $0x94; BYTE $0x95; BYTE $0x96; BYTE $0x04
BYTE $0x98; BYTE $0x99; BYTE $0x9a; BYTE $0x9b
BYTE $0x14; BYTE $0x15; BYTE $0x9e; BYTE $0x1a
BYTE $0x20; BYTE $0xa0; BYTE $0xe2; BYTE $0xe4
BYTE $0xe0; BYTE $0xe1; BYTE $0xe3; BYTE $0xe5
BYTE $0xe7; BYTE $0xf1; BYTE $0xa2; BYTE $0x2e
BYTE $0x3c; BYTE $0x28; BYTE $0x2b; BYTE $0x7c
BYTE $0x26; BYTE $0xe9; BYTE $0xea; BYTE $0xeb
BYTE $0xe8; BYTE $0xed; BYTE $0xee; BYTE $0xef
BYTE $0xec; BYTE $0xdf; BYTE $0x21; BYTE $0x24
BYTE $0x2a; BYTE $0x29; BYTE $0x3b; BYTE $0x5e
BYTE $0x2d; BYTE $0x2f; BYTE $0xc2; BYTE $0xc4
BYTE $0xc0; BYTE $0xc1; BYTE $0xc3; BYTE $0xc5
BYTE $0xc7; BYTE $0xd1; BYTE $0xa6; BYTE $0x2c
BYTE $0x25; BYTE $0x5f; BYTE $0x3e; BYTE $0x3f
BYTE $0xf8; BYTE $0xc9; BYTE $0xca; BYTE $0xcb
BYTE $0xc8; BYTE $0xcd; BYTE $0xce; BYTE $0xcf
BYTE $0xcc; BYTE $0x60; BYTE $0x3a; BYTE $0x23
BYTE $0x40; BYTE $0x27; BYTE $0x3d; BYTE $0x22
BYTE $0xd8; BYTE $0x61; BYTE $0x62; BYTE $0x63
BYTE $0x64; BYTE $0x65; BYTE $0x66; BYTE $0x67
BYTE $0x68; BYTE $0x69; BYTE $0xab; BYTE $0xbb
BYTE $0xf0; BYTE $0xfd; BYTE $0xfe; BYTE $0xb1
BYTE $0xb0; BYTE $0x6a; BYTE $0x6b; BYTE $0x6c
BYTE $0x6d; BYTE $0x6e; BYTE $0x6f; BYTE $0x70
BYTE $0x71; BYTE $0x72; BYTE $0xaa; BYTE $0xba
BYTE $0xe6; BYTE $0xb8; BYTE $0xc6; BYTE $0xa4
BYTE $0xb5; BYTE $0x7e; BYTE $0x73; BYTE $0x74
BYTE $0x75; BYTE $0x76; BYTE $0x77; BYTE $0x78
BYTE $0x79; BYTE $0x7a; BYTE $0xa1; BYTE $0xbf
BYTE $0xd0; BYTE $0x5b; BYTE $0xde; BYTE $0xae
BYTE $0xac; BYTE $0xa3; BYTE $0xa5; BYTE $0xb7
BYTE $0xa9; BYTE $0xa7; BYTE $0xb6; BYTE $0xbc
BYTE $0xbd; BYTE $0xbe; BYTE $0xdd; BYTE $0xa8
BYTE $0xaf; BYTE $0x5d; BYTE $0xb4; BYTE $0xd7
BYTE $0x7b; BYTE $0x41; BYTE $0x42; BYTE $0x43
BYTE $0x44; BYTE $0x45; BYTE $0x46; BYTE $0x47
BYTE $0x48; BYTE $0x49; BYTE $0xad; BYTE $0xf4
BYTE $0xf6; BYTE $0xf2; BYTE $0xf3; BYTE $0xf5
BYTE $0x7d; BYTE $0x4a; BYTE $0x4b; BYTE $0x4c
BYTE $0x4d; BYTE $0x4e; BYTE $0x4f; BYTE $0x50
BYTE $0x51; BYTE $0x52; BYTE $0xb9; BYTE $0xfb
BYTE $0xfc; BYTE $0xf9; BYTE $0xfa; BYTE $0xff
BYTE $0x5c; BYTE $0xf7; BYTE $0x53; BYTE $0x54
BYTE $0x55; BYTE $0x56; BYTE $0x57; BYTE $0x58
BYTE $0x59; BYTE $0x5a; BYTE $0xb2; BYTE $0xd4
BYTE $0xd6; BYTE $0xd2; BYTE $0xd3; BYTE $0xd5
BYTE $0x30; BYTE $0x31; BYTE $0x32; BYTE $0x33
BYTE $0x34; BYTE $0x35; BYTE $0x36; BYTE $0x37
BYTE $0x38; BYTE $0x39; BYTE $0xb3; BYTE $0xdb
BYTE $0xdc; BYTE $0xd9; BYTE $0xda; BYTE $0x9f
retry:
WORD $0xB9931022 // TROO 2,2,b'0001'
BVS retry
RET

View File

@@ -1,220 +0,0 @@
// Copyright 2020 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.
//go:build zos && s390x
package unix
import (
"sync"
)
// This file simulates epoll on z/OS using poll.
// Analogous to epoll_event on Linux.
// TODO(neeilan): Pad is because the Linux kernel expects a 96-bit struct. We never pass this to the kernel; remove?
type EpollEvent struct {
Events uint32
Fd int32
Pad int32
}
const (
EPOLLERR = 0x8
EPOLLHUP = 0x10
EPOLLIN = 0x1
EPOLLMSG = 0x400
EPOLLOUT = 0x4
EPOLLPRI = 0x2
EPOLLRDBAND = 0x80
EPOLLRDNORM = 0x40
EPOLLWRBAND = 0x200
EPOLLWRNORM = 0x100
EPOLL_CTL_ADD = 0x1
EPOLL_CTL_DEL = 0x2
EPOLL_CTL_MOD = 0x3
// The following constants are part of the epoll API, but represent
// currently unsupported functionality on z/OS.
// EPOLL_CLOEXEC = 0x80000
// EPOLLET = 0x80000000
// EPOLLONESHOT = 0x40000000
// EPOLLRDHUP = 0x2000 // Typically used with edge-triggered notis
// EPOLLEXCLUSIVE = 0x10000000 // Exclusive wake-up mode
// EPOLLWAKEUP = 0x20000000 // Relies on Linux's BLOCK_SUSPEND capability
)
// TODO(neeilan): We can eliminate these epToPoll / pToEpoll calls by using identical mask values for POLL/EPOLL
// constants where possible The lower 16 bits of epoll events (uint32) can fit any system poll event (int16).
// epToPollEvt converts epoll event field to poll equivalent.
// In epoll, Events is a 32-bit field, while poll uses 16 bits.
func epToPollEvt(events uint32) int16 {
var ep2p = map[uint32]int16{
EPOLLIN: POLLIN,
EPOLLOUT: POLLOUT,
EPOLLHUP: POLLHUP,
EPOLLPRI: POLLPRI,
EPOLLERR: POLLERR,
}
var pollEvts int16 = 0
for epEvt, pEvt := range ep2p {
if (events & epEvt) != 0 {
pollEvts |= pEvt
}
}
return pollEvts
}
// pToEpollEvt converts 16 bit poll event bitfields to 32-bit epoll event fields.
func pToEpollEvt(revents int16) uint32 {
var p2ep = map[int16]uint32{
POLLIN: EPOLLIN,
POLLOUT: EPOLLOUT,
POLLHUP: EPOLLHUP,
POLLPRI: EPOLLPRI,
POLLERR: EPOLLERR,
}
var epollEvts uint32 = 0
for pEvt, epEvt := range p2ep {
if (revents & pEvt) != 0 {
epollEvts |= epEvt
}
}
return epollEvts
}
// Per-process epoll implementation.
type epollImpl struct {
mu sync.Mutex
epfd2ep map[int]*eventPoll
nextEpfd int
}
// eventPoll holds a set of file descriptors being watched by the process. A process can have multiple epoll instances.
// On Linux, this is an in-kernel data structure accessed through a fd.
type eventPoll struct {
mu sync.Mutex
fds map[int]*EpollEvent
}
// epoll impl for this process.
var impl epollImpl = epollImpl{
epfd2ep: make(map[int]*eventPoll),
nextEpfd: 0,
}
func (e *epollImpl) epollcreate(size int) (epfd int, err error) {
e.mu.Lock()
defer e.mu.Unlock()
epfd = e.nextEpfd
e.nextEpfd++
e.epfd2ep[epfd] = &eventPoll{
fds: make(map[int]*EpollEvent),
}
return epfd, nil
}
func (e *epollImpl) epollcreate1(flag int) (fd int, err error) {
return e.epollcreate(4)
}
func (e *epollImpl) epollctl(epfd int, op int, fd int, event *EpollEvent) (err error) {
e.mu.Lock()
defer e.mu.Unlock()
ep, ok := e.epfd2ep[epfd]
if !ok {
return EBADF
}
switch op {
case EPOLL_CTL_ADD:
// TODO(neeilan): When we make epfds and fds disjoint, detect epoll
// loops here (instances watching each other) and return ELOOP.
if _, ok := ep.fds[fd]; ok {
return EEXIST
}
ep.fds[fd] = event
case EPOLL_CTL_MOD:
if _, ok := ep.fds[fd]; !ok {
return ENOENT
}
ep.fds[fd] = event
case EPOLL_CTL_DEL:
if _, ok := ep.fds[fd]; !ok {
return ENOENT
}
delete(ep.fds, fd)
}
return nil
}
// Must be called while holding ep.mu
func (ep *eventPoll) getFds() []int {
fds := make([]int, len(ep.fds))
for fd := range ep.fds {
fds = append(fds, fd)
}
return fds
}
func (e *epollImpl) epollwait(epfd int, events []EpollEvent, msec int) (n int, err error) {
e.mu.Lock() // in [rare] case of concurrent epollcreate + epollwait
ep, ok := e.epfd2ep[epfd]
if !ok {
e.mu.Unlock()
return 0, EBADF
}
pollfds := make([]PollFd, 4)
for fd, epollevt := range ep.fds {
pollfds = append(pollfds, PollFd{Fd: int32(fd), Events: epToPollEvt(epollevt.Events)})
}
e.mu.Unlock()
n, err = Poll(pollfds, msec)
if err != nil {
return n, err
}
i := 0
for _, pFd := range pollfds {
if pFd.Revents != 0 {
events[i] = EpollEvent{Fd: pFd.Fd, Events: pToEpollEvt(pFd.Revents)}
i++
}
if i == n {
break
}
}
return n, nil
}
func EpollCreate(size int) (fd int, err error) {
return impl.epollcreate(size)
}
func EpollCreate1(flag int) (fd int, err error) {
return impl.epollcreate1(flag)
}
func EpollCtl(epfd int, op int, fd int, event *EpollEvent) (err error) {
return impl.epollctl(epfd, op, fd, event)
}
// Because EpollWait mutates events, the caller is expected to coordinate
// concurrent access if calling with the same epfd from multiple goroutines.
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
return impl.epollwait(epfd, events, msec)
}

View File

@@ -1,163 +0,0 @@
// Copyright 2020 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.
//go:build zos && s390x
package unix
import (
"unsafe"
)
// This file simulates fstatfs on z/OS using fstatvfs and w_getmntent.
func Fstatfs(fd int, stat *Statfs_t) (err error) {
var stat_v Statvfs_t
err = Fstatvfs(fd, &stat_v)
if err == nil {
// populate stat
stat.Type = 0
stat.Bsize = stat_v.Bsize
stat.Blocks = stat_v.Blocks
stat.Bfree = stat_v.Bfree
stat.Bavail = stat_v.Bavail
stat.Files = stat_v.Files
stat.Ffree = stat_v.Ffree
stat.Fsid = stat_v.Fsid
stat.Namelen = stat_v.Namemax
stat.Frsize = stat_v.Frsize
stat.Flags = stat_v.Flag
for passn := 0; passn < 5; passn++ {
switch passn {
case 0:
err = tryGetmntent64(stat)
break
case 1:
err = tryGetmntent128(stat)
break
case 2:
err = tryGetmntent256(stat)
break
case 3:
err = tryGetmntent512(stat)
break
case 4:
err = tryGetmntent1024(stat)
break
default:
break
}
//proceed to return if: err is nil (found), err is nonnil but not ERANGE (another error occurred)
if err == nil || err != nil && err != ERANGE {
break
}
}
}
return err
}
func tryGetmntent64(stat *Statfs_t) (err error) {
var mnt_ent_buffer struct {
header W_Mnth
filesys_info [64]W_Mntent
}
var buffer_size int = int(unsafe.Sizeof(mnt_ent_buffer))
fs_count, err := W_Getmntent((*byte)(unsafe.Pointer(&mnt_ent_buffer)), buffer_size)
if err != nil {
return err
}
err = ERANGE //return ERANGE if no match is found in this batch
for i := 0; i < fs_count; i++ {
if stat.Fsid == uint64(mnt_ent_buffer.filesys_info[i].Dev) {
stat.Type = uint32(mnt_ent_buffer.filesys_info[i].Fstname[0])
err = nil
break
}
}
return err
}
func tryGetmntent128(stat *Statfs_t) (err error) {
var mnt_ent_buffer struct {
header W_Mnth
filesys_info [128]W_Mntent
}
var buffer_size int = int(unsafe.Sizeof(mnt_ent_buffer))
fs_count, err := W_Getmntent((*byte)(unsafe.Pointer(&mnt_ent_buffer)), buffer_size)
if err != nil {
return err
}
err = ERANGE //return ERANGE if no match is found in this batch
for i := 0; i < fs_count; i++ {
if stat.Fsid == uint64(mnt_ent_buffer.filesys_info[i].Dev) {
stat.Type = uint32(mnt_ent_buffer.filesys_info[i].Fstname[0])
err = nil
break
}
}
return err
}
func tryGetmntent256(stat *Statfs_t) (err error) {
var mnt_ent_buffer struct {
header W_Mnth
filesys_info [256]W_Mntent
}
var buffer_size int = int(unsafe.Sizeof(mnt_ent_buffer))
fs_count, err := W_Getmntent((*byte)(unsafe.Pointer(&mnt_ent_buffer)), buffer_size)
if err != nil {
return err
}
err = ERANGE //return ERANGE if no match is found in this batch
for i := 0; i < fs_count; i++ {
if stat.Fsid == uint64(mnt_ent_buffer.filesys_info[i].Dev) {
stat.Type = uint32(mnt_ent_buffer.filesys_info[i].Fstname[0])
err = nil
break
}
}
return err
}
func tryGetmntent512(stat *Statfs_t) (err error) {
var mnt_ent_buffer struct {
header W_Mnth
filesys_info [512]W_Mntent
}
var buffer_size int = int(unsafe.Sizeof(mnt_ent_buffer))
fs_count, err := W_Getmntent((*byte)(unsafe.Pointer(&mnt_ent_buffer)), buffer_size)
if err != nil {
return err
}
err = ERANGE //return ERANGE if no match is found in this batch
for i := 0; i < fs_count; i++ {
if stat.Fsid == uint64(mnt_ent_buffer.filesys_info[i].Dev) {
stat.Type = uint32(mnt_ent_buffer.filesys_info[i].Fstname[0])
err = nil
break
}
}
return err
}
func tryGetmntent1024(stat *Statfs_t) (err error) {
var mnt_ent_buffer struct {
header W_Mnth
filesys_info [1024]W_Mntent
}
var buffer_size int = int(unsafe.Sizeof(mnt_ent_buffer))
fs_count, err := W_Getmntent((*byte)(unsafe.Pointer(&mnt_ent_buffer)), buffer_size)
if err != nil {
return err
}
err = ERANGE //return ERANGE if no match is found in this batch
for i := 0; i < fs_count; i++ {
if stat.Fsid == uint64(mnt_ent_buffer.filesys_info[i].Dev) {
stat.Type = uint32(mnt_ent_buffer.filesys_info[i].Fstname[0])
err = nil
break
}
}
return err
}

View File

@@ -263,6 +263,7 @@ struct ltchars {
#include <linux/sched.h> #include <linux/sched.h>
#include <linux/seccomp.h> #include <linux/seccomp.h>
#include <linux/serial.h> #include <linux/serial.h>
#include <linux/sock_diag.h>
#include <linux/sockios.h> #include <linux/sockios.h>
#include <linux/taskstats.h> #include <linux/taskstats.h>
#include <linux/tipc.h> #include <linux/tipc.h>
@@ -549,6 +550,7 @@ ccflags="$@"
$2 !~ "NLA_TYPE_MASK" && $2 !~ "NLA_TYPE_MASK" &&
$2 !~ /^RTC_VL_(ACCURACY|BACKUP|DATA)/ && $2 !~ /^RTC_VL_(ACCURACY|BACKUP|DATA)/ &&
$2 ~ /^(NETLINK|NLM|NLMSG|NLA|IFA|IFAN|RT|RTC|RTCF|RTN|RTPROT|RTNH|ARPHRD|ETH_P|NETNSA)_/ || $2 ~ /^(NETLINK|NLM|NLMSG|NLA|IFA|IFAN|RT|RTC|RTCF|RTN|RTPROT|RTNH|ARPHRD|ETH_P|NETNSA)_/ ||
$2 ~ /^SOCK_|SK_DIAG_|SKNLGRP_$/ ||
$2 ~ /^FIORDCHK$/ || $2 ~ /^FIORDCHK$/ ||
$2 ~ /^SIOC/ || $2 ~ /^SIOC/ ||
$2 ~ /^TIOC/ || $2 ~ /^TIOC/ ||

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris //go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
// For Unix, get the pagesize from the runtime. // For Unix, get the pagesize from the runtime.

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build darwin //go:build darwin || zos
package unix package unix

58
vendor/golang.org/x/sys/unix/sockcmsg_zos.go generated vendored Normal file
View File

@@ -0,0 +1,58 @@
// Copyright 2024 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.
// Socket control messages
package unix
import "unsafe"
// UnixCredentials encodes credentials into a socket control message
// for sending to another process. This can be used for
// authentication.
func UnixCredentials(ucred *Ucred) []byte {
b := make([]byte, CmsgSpace(SizeofUcred))
h := (*Cmsghdr)(unsafe.Pointer(&b[0]))
h.Level = SOL_SOCKET
h.Type = SCM_CREDENTIALS
h.SetLen(CmsgLen(SizeofUcred))
*(*Ucred)(h.data(0)) = *ucred
return b
}
// ParseUnixCredentials decodes a socket control message that contains
// credentials in a Ucred structure. To receive such a message, the
// SO_PASSCRED option must be enabled on the socket.
func ParseUnixCredentials(m *SocketControlMessage) (*Ucred, error) {
if m.Header.Level != SOL_SOCKET {
return nil, EINVAL
}
if m.Header.Type != SCM_CREDENTIALS {
return nil, EINVAL
}
ucred := *(*Ucred)(unsafe.Pointer(&m.Data[0]))
return &ucred, nil
}
// PktInfo4 encodes Inet4Pktinfo into a socket control message of type IP_PKTINFO.
func PktInfo4(info *Inet4Pktinfo) []byte {
b := make([]byte, CmsgSpace(SizeofInet4Pktinfo))
h := (*Cmsghdr)(unsafe.Pointer(&b[0]))
h.Level = SOL_IP
h.Type = IP_PKTINFO
h.SetLen(CmsgLen(SizeofInet4Pktinfo))
*(*Inet4Pktinfo)(h.data(0)) = *info
return b
}
// PktInfo6 encodes Inet6Pktinfo into a socket control message of type IPV6_PKTINFO.
func PktInfo6(info *Inet6Pktinfo) []byte {
b := make([]byte, CmsgSpace(SizeofInet6Pktinfo))
h := (*Cmsghdr)(unsafe.Pointer(&b[0]))
h.Level = SOL_IPV6
h.Type = IPV6_PKTINFO
h.SetLen(CmsgLen(SizeofInet6Pktinfo))
*(*Inet6Pktinfo)(h.data(0)) = *info
return b
}

75
vendor/golang.org/x/sys/unix/symaddr_zos_s390x.s generated vendored Normal file
View File

@@ -0,0 +1,75 @@
// Copyright 2024 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.
//go:build zos && s390x && gc
#include "textflag.h"
// provide the address of function variable to be fixed up.
TEXT ·getPipe2Addr(SB), NOSPLIT|NOFRAME, $0-8
MOVD $·Pipe2(SB), R8
MOVD R8, ret+0(FP)
RET
TEXT ·get_FlockAddr(SB), NOSPLIT|NOFRAME, $0-8
MOVD $·Flock(SB), R8
MOVD R8, ret+0(FP)
RET
TEXT ·get_GetxattrAddr(SB), NOSPLIT|NOFRAME, $0-8
MOVD $·Getxattr(SB), R8
MOVD R8, ret+0(FP)
RET
TEXT ·get_NanosleepAddr(SB), NOSPLIT|NOFRAME, $0-8
MOVD $·Nanosleep(SB), R8
MOVD R8, ret+0(FP)
RET
TEXT ·get_SetxattrAddr(SB), NOSPLIT|NOFRAME, $0-8
MOVD $·Setxattr(SB), R8
MOVD R8, ret+0(FP)
RET
TEXT ·get_Wait4Addr(SB), NOSPLIT|NOFRAME, $0-8
MOVD $·Wait4(SB), R8
MOVD R8, ret+0(FP)
RET
TEXT ·get_MountAddr(SB), NOSPLIT|NOFRAME, $0-8
MOVD $·Mount(SB), R8
MOVD R8, ret+0(FP)
RET
TEXT ·get_UnmountAddr(SB), NOSPLIT|NOFRAME, $0-8
MOVD $·Unmount(SB), R8
MOVD R8, ret+0(FP)
RET
TEXT ·get_UtimesNanoAtAddr(SB), NOSPLIT|NOFRAME, $0-8
MOVD $·UtimesNanoAt(SB), R8
MOVD R8, ret+0(FP)
RET
TEXT ·get_UtimesNanoAddr(SB), NOSPLIT|NOFRAME, $0-8
MOVD $·UtimesNano(SB), R8
MOVD R8, ret+0(FP)
RET
TEXT ·get_MkfifoatAddr(SB), NOSPLIT|NOFRAME, $0-8
MOVD $·Mkfifoat(SB), R8
MOVD R8, ret+0(FP)
RET
TEXT ·get_ChtagAddr(SB), NOSPLIT|NOFRAME, $0-8
MOVD $·Chtag(SB), R8
MOVD R8, ret+0(FP)
RET
TEXT ·get_ReadlinkatAddr(SB), NOSPLIT|NOFRAME, $0-8
MOVD $·Readlinkat(SB), R8
MOVD R8, ret+0(FP)
RET

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build (darwin && !ios) || linux //go:build (darwin && !ios) || linux || zos
package unix package unix

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build darwin && !ios //go:build (darwin && !ios) || zos
package unix package unix

View File

@@ -491,6 +491,7 @@ const (
BPF_F_REPLACE = 0x4 BPF_F_REPLACE = 0x4
BPF_F_SLEEPABLE = 0x10 BPF_F_SLEEPABLE = 0x10
BPF_F_STRICT_ALIGNMENT = 0x1 BPF_F_STRICT_ALIGNMENT = 0x1
BPF_F_TEST_REG_INVARIANTS = 0x80
BPF_F_TEST_RND_HI32 = 0x4 BPF_F_TEST_RND_HI32 = 0x4
BPF_F_TEST_RUN_ON_CPU = 0x1 BPF_F_TEST_RUN_ON_CPU = 0x1
BPF_F_TEST_STATE_FREQ = 0x8 BPF_F_TEST_STATE_FREQ = 0x8
@@ -501,6 +502,7 @@ const (
BPF_IMM = 0x0 BPF_IMM = 0x0
BPF_IND = 0x40 BPF_IND = 0x40
BPF_JA = 0x0 BPF_JA = 0x0
BPF_JCOND = 0xe0
BPF_JEQ = 0x10 BPF_JEQ = 0x10
BPF_JGE = 0x30 BPF_JGE = 0x30
BPF_JGT = 0x20 BPF_JGT = 0x20
@@ -656,6 +658,9 @@ const (
CAN_NPROTO = 0x8 CAN_NPROTO = 0x8
CAN_RAW = 0x1 CAN_RAW = 0x1
CAN_RAW_FILTER_MAX = 0x200 CAN_RAW_FILTER_MAX = 0x200
CAN_RAW_XL_VCID_RX_FILTER = 0x4
CAN_RAW_XL_VCID_TX_PASS = 0x2
CAN_RAW_XL_VCID_TX_SET = 0x1
CAN_RTR_FLAG = 0x40000000 CAN_RTR_FLAG = 0x40000000
CAN_SFF_ID_BITS = 0xb CAN_SFF_ID_BITS = 0xb
CAN_SFF_MASK = 0x7ff CAN_SFF_MASK = 0x7ff
@@ -1338,6 +1343,7 @@ const (
F_OFD_SETLK = 0x25 F_OFD_SETLK = 0x25
F_OFD_SETLKW = 0x26 F_OFD_SETLKW = 0x26
F_OK = 0x0 F_OK = 0x0
F_SEAL_EXEC = 0x20
F_SEAL_FUTURE_WRITE = 0x10 F_SEAL_FUTURE_WRITE = 0x10
F_SEAL_GROW = 0x4 F_SEAL_GROW = 0x4
F_SEAL_SEAL = 0x1 F_SEAL_SEAL = 0x1
@@ -1626,6 +1632,7 @@ const (
IP_FREEBIND = 0xf IP_FREEBIND = 0xf
IP_HDRINCL = 0x3 IP_HDRINCL = 0x3
IP_IPSEC_POLICY = 0x10 IP_IPSEC_POLICY = 0x10
IP_LOCAL_PORT_RANGE = 0x33
IP_MAXPACKET = 0xffff IP_MAXPACKET = 0xffff
IP_MAX_MEMBERSHIPS = 0x14 IP_MAX_MEMBERSHIPS = 0x14
IP_MF = 0x2000 IP_MF = 0x2000
@@ -1652,6 +1659,7 @@ const (
IP_PMTUDISC_OMIT = 0x5 IP_PMTUDISC_OMIT = 0x5
IP_PMTUDISC_PROBE = 0x3 IP_PMTUDISC_PROBE = 0x3
IP_PMTUDISC_WANT = 0x1 IP_PMTUDISC_WANT = 0x1
IP_PROTOCOL = 0x34
IP_RECVERR = 0xb IP_RECVERR = 0xb
IP_RECVERR_RFC4884 = 0x1a IP_RECVERR_RFC4884 = 0x1a
IP_RECVFRAGSIZE = 0x19 IP_RECVFRAGSIZE = 0x19
@@ -1697,6 +1705,7 @@ const (
KEXEC_ARCH_S390 = 0x160000 KEXEC_ARCH_S390 = 0x160000
KEXEC_ARCH_SH = 0x2a0000 KEXEC_ARCH_SH = 0x2a0000
KEXEC_ARCH_X86_64 = 0x3e0000 KEXEC_ARCH_X86_64 = 0x3e0000
KEXEC_FILE_DEBUG = 0x8
KEXEC_FILE_NO_INITRAMFS = 0x4 KEXEC_FILE_NO_INITRAMFS = 0x4
KEXEC_FILE_ON_CRASH = 0x2 KEXEC_FILE_ON_CRASH = 0x2
KEXEC_FILE_UNLOAD = 0x1 KEXEC_FILE_UNLOAD = 0x1
@@ -1898,6 +1907,7 @@ const (
MNT_DETACH = 0x2 MNT_DETACH = 0x2
MNT_EXPIRE = 0x4 MNT_EXPIRE = 0x4
MNT_FORCE = 0x1 MNT_FORCE = 0x1
MNT_ID_REQ_SIZE_VER0 = 0x18
MODULE_INIT_COMPRESSED_FILE = 0x4 MODULE_INIT_COMPRESSED_FILE = 0x4
MODULE_INIT_IGNORE_MODVERSIONS = 0x1 MODULE_INIT_IGNORE_MODVERSIONS = 0x1
MODULE_INIT_IGNORE_VERMAGIC = 0x2 MODULE_INIT_IGNORE_VERMAGIC = 0x2
@@ -2166,7 +2176,7 @@ const (
NFT_SECMARK_CTX_MAXLEN = 0x100 NFT_SECMARK_CTX_MAXLEN = 0x100
NFT_SET_MAXNAMELEN = 0x100 NFT_SET_MAXNAMELEN = 0x100
NFT_SOCKET_MAX = 0x3 NFT_SOCKET_MAX = 0x3
NFT_TABLE_F_MASK = 0x3 NFT_TABLE_F_MASK = 0x7
NFT_TABLE_MAXNAMELEN = 0x100 NFT_TABLE_MAXNAMELEN = 0x100
NFT_TRACETYPE_MAX = 0x3 NFT_TRACETYPE_MAX = 0x3
NFT_TUNNEL_F_MASK = 0x7 NFT_TUNNEL_F_MASK = 0x7
@@ -2302,6 +2312,7 @@ const (
PERF_AUX_FLAG_PARTIAL = 0x4 PERF_AUX_FLAG_PARTIAL = 0x4
PERF_AUX_FLAG_PMU_FORMAT_TYPE_MASK = 0xff00 PERF_AUX_FLAG_PMU_FORMAT_TYPE_MASK = 0xff00
PERF_AUX_FLAG_TRUNCATED = 0x1 PERF_AUX_FLAG_TRUNCATED = 0x1
PERF_BRANCH_ENTRY_INFO_BITS_MAX = 0x21
PERF_BR_ARM64_DEBUG_DATA = 0x7 PERF_BR_ARM64_DEBUG_DATA = 0x7
PERF_BR_ARM64_DEBUG_EXIT = 0x5 PERF_BR_ARM64_DEBUG_EXIT = 0x5
PERF_BR_ARM64_DEBUG_HALT = 0x4 PERF_BR_ARM64_DEBUG_HALT = 0x4
@@ -2399,6 +2410,7 @@ const (
PERF_RECORD_MISC_USER = 0x2 PERF_RECORD_MISC_USER = 0x2
PERF_SAMPLE_BRANCH_PLM_ALL = 0x7 PERF_SAMPLE_BRANCH_PLM_ALL = 0x7
PERF_SAMPLE_WEIGHT_TYPE = 0x1004000 PERF_SAMPLE_WEIGHT_TYPE = 0x1004000
PID_FS_MAGIC = 0x50494446
PIPEFS_MAGIC = 0x50495045 PIPEFS_MAGIC = 0x50495045
PPPIOCGNPMODE = 0xc008744c PPPIOCGNPMODE = 0xc008744c
PPPIOCNEWUNIT = 0xc004743e PPPIOCNEWUNIT = 0xc004743e
@@ -2892,8 +2904,9 @@ const (
RWF_APPEND = 0x10 RWF_APPEND = 0x10
RWF_DSYNC = 0x2 RWF_DSYNC = 0x2
RWF_HIPRI = 0x1 RWF_HIPRI = 0x1
RWF_NOAPPEND = 0x20
RWF_NOWAIT = 0x8 RWF_NOWAIT = 0x8
RWF_SUPPORTED = 0x1f RWF_SUPPORTED = 0x3f
RWF_SYNC = 0x4 RWF_SYNC = 0x4
RWF_WRITE_LIFE_NOT_SET = 0x0 RWF_WRITE_LIFE_NOT_SET = 0x0
SCHED_BATCH = 0x3 SCHED_BATCH = 0x3
@@ -2914,7 +2927,9 @@ const (
SCHED_RESET_ON_FORK = 0x40000000 SCHED_RESET_ON_FORK = 0x40000000
SCHED_RR = 0x2 SCHED_RR = 0x2
SCM_CREDENTIALS = 0x2 SCM_CREDENTIALS = 0x2
SCM_PIDFD = 0x4
SCM_RIGHTS = 0x1 SCM_RIGHTS = 0x1
SCM_SECURITY = 0x3
SCM_TIMESTAMP = 0x1d SCM_TIMESTAMP = 0x1d
SC_LOG_FLUSH = 0x100000 SC_LOG_FLUSH = 0x100000
SECCOMP_ADDFD_FLAG_SEND = 0x2 SECCOMP_ADDFD_FLAG_SEND = 0x2
@@ -3047,6 +3062,8 @@ const (
SIOCSMIIREG = 0x8949 SIOCSMIIREG = 0x8949
SIOCSRARP = 0x8962 SIOCSRARP = 0x8962
SIOCWANDEV = 0x894a SIOCWANDEV = 0x894a
SK_DIAG_BPF_STORAGE_MAX = 0x3
SK_DIAG_BPF_STORAGE_REQ_MAX = 0x1
SMACK_MAGIC = 0x43415d53 SMACK_MAGIC = 0x43415d53
SMART_AUTOSAVE = 0xd2 SMART_AUTOSAVE = 0xd2
SMART_AUTO_OFFLINE = 0xdb SMART_AUTO_OFFLINE = 0xdb
@@ -3067,6 +3084,8 @@ const (
SOCKFS_MAGIC = 0x534f434b SOCKFS_MAGIC = 0x534f434b
SOCK_BUF_LOCK_MASK = 0x3 SOCK_BUF_LOCK_MASK = 0x3
SOCK_DCCP = 0x6 SOCK_DCCP = 0x6
SOCK_DESTROY = 0x15
SOCK_DIAG_BY_FAMILY = 0x14
SOCK_IOC_TYPE = 0x89 SOCK_IOC_TYPE = 0x89
SOCK_PACKET = 0xa SOCK_PACKET = 0xa
SOCK_RAW = 0x3 SOCK_RAW = 0x3
@@ -3168,6 +3187,7 @@ const (
STATX_GID = 0x10 STATX_GID = 0x10
STATX_INO = 0x100 STATX_INO = 0x100
STATX_MNT_ID = 0x1000 STATX_MNT_ID = 0x1000
STATX_MNT_ID_UNIQUE = 0x4000
STATX_MODE = 0x2 STATX_MODE = 0x2
STATX_MTIME = 0x40 STATX_MTIME = 0x40
STATX_NLINK = 0x4 STATX_NLINK = 0x4
@@ -3255,6 +3275,7 @@ const (
TCP_MAX_WINSHIFT = 0xe TCP_MAX_WINSHIFT = 0xe
TCP_MD5SIG = 0xe TCP_MD5SIG = 0xe
TCP_MD5SIG_EXT = 0x20 TCP_MD5SIG_EXT = 0x20
TCP_MD5SIG_FLAG_IFINDEX = 0x2
TCP_MD5SIG_FLAG_PREFIX = 0x1 TCP_MD5SIG_FLAG_PREFIX = 0x1
TCP_MD5SIG_MAXKEYLEN = 0x50 TCP_MD5SIG_MAXKEYLEN = 0x50
TCP_MSS = 0x200 TCP_MSS = 0x200
@@ -3562,12 +3583,16 @@ const (
XDP_RX_RING = 0x2 XDP_RX_RING = 0x2
XDP_SHARED_UMEM = 0x1 XDP_SHARED_UMEM = 0x1
XDP_STATISTICS = 0x7 XDP_STATISTICS = 0x7
XDP_TXMD_FLAGS_CHECKSUM = 0x2
XDP_TXMD_FLAGS_TIMESTAMP = 0x1
XDP_TX_METADATA = 0x2
XDP_TX_RING = 0x3 XDP_TX_RING = 0x3
XDP_UMEM_COMPLETION_RING = 0x6 XDP_UMEM_COMPLETION_RING = 0x6
XDP_UMEM_FILL_RING = 0x5 XDP_UMEM_FILL_RING = 0x5
XDP_UMEM_PGOFF_COMPLETION_RING = 0x180000000 XDP_UMEM_PGOFF_COMPLETION_RING = 0x180000000
XDP_UMEM_PGOFF_FILL_RING = 0x100000000 XDP_UMEM_PGOFF_FILL_RING = 0x100000000
XDP_UMEM_REG = 0x4 XDP_UMEM_REG = 0x4
XDP_UMEM_TX_SW_CSUM = 0x2
XDP_UMEM_UNALIGNED_CHUNK_FLAG = 0x1 XDP_UMEM_UNALIGNED_CHUNK_FLAG = 0x1
XDP_USE_NEED_WAKEUP = 0x8 XDP_USE_NEED_WAKEUP = 0x8
XDP_USE_SG = 0x10 XDP_USE_SG = 0x10

Some files were not shown because too many files have changed in this diff Show More