Compare commits

...

11 commits

Author SHA1 Message Date
tobi ecd77b8782
Merge 4be1f780a1 into e3019eada4 2024-10-01 12:38:18 +02:00
dependabot[bot] e3019eada4
[chore]: Bump go.uber.org/automaxprocs from 1.5.3 to 1.6.0 (#3376)
Bumps [go.uber.org/automaxprocs](https://github.com/uber-go/automaxprocs) from 1.5.3 to 1.6.0.
- [Release notes](https://github.com/uber-go/automaxprocs/releases)
- [Changelog](https://github.com/uber-go/automaxprocs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uber-go/automaxprocs/compare/v1.5.3...v1.6.0)

---
updated-dependencies:
- dependency-name: go.uber.org/automaxprocs
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-30 12:58:44 +02:00
dependabot[bot] 188d28f054
[chore]: Bump github.com/ncruces/go-sqlite3 from 0.18.3 to 0.18.4 (#3375)
Bumps [github.com/ncruces/go-sqlite3](https://github.com/ncruces/go-sqlite3) from 0.18.3 to 0.18.4.
- [Release notes](https://github.com/ncruces/go-sqlite3/releases)
- [Commits](https://github.com/ncruces/go-sqlite3/compare/v0.18.3...v0.18.4)

---
updated-dependencies:
- dependency-name: github.com/ncruces/go-sqlite3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-30 12:46:23 +02:00
tobi 43e443f9f3
[bugfix] Carry-over "PinnedAt" when refreshing status (#3373) 2024-09-29 14:46:52 +02:00
tobi 4be1f780a1 goreleaser deprecation notices 2024-09-02 15:14:27 +02:00
tobi 8db3d6b700 allow overflow in imaging 2024-09-02 15:08:07 +02:00
tobi 666b8bc4f2 Merge branch 'main' into go_123 2024-09-02 14:38:00 +02:00
tobi 7c6c74243b bump go version in go.mod 2024-09-01 17:44:54 +02:00
tobi 75d3fca08c sign 2024-09-01 17:42:45 +02:00
tobi bd4c4d79fe undo silly change 2024-09-01 17:37:17 +02:00
tobi c1543c029b [chore] Bump tooling versions, bump go -> v1.23.0 2024-09-01 17:35:31 +02:00
46 changed files with 357 additions and 285 deletions

View file

@ -12,7 +12,7 @@ steps:
# We use golangci-lint for linting. # We use golangci-lint for linting.
# See: https://golangci-lint.run/ # See: https://golangci-lint.run/
- name: lint - name: lint
image: golangci/golangci-lint:v1.57.2 image: golangci/golangci-lint:v1.60.3
volumes: volumes:
- name: go-build-cache - name: go-build-cache
path: /root/.cache/go-build path: /root/.cache/go-build
@ -28,7 +28,7 @@ steps:
- pull_request - pull_request
- name: test - name: test
image: golang:1.22-alpine image: golang:1.23.0-alpine
volumes: volumes:
- name: go-build-cache - name: go-build-cache
path: /root/.cache/go-build path: /root/.cache/go-build
@ -94,7 +94,7 @@ steps:
- pull_request - pull_request
- name: snapshot - name: snapshot
image: superseriousbusiness/gotosocial-drone-build:0.6.0 # https://github.com/superseriousbusiness/gotosocial-drone-build image: superseriousbusiness/gotosocial-drone-build:0.7.0 # https://github.com/superseriousbusiness/gotosocial-drone-build
volumes: volumes:
- name: go-build-cache - name: go-build-cache
path: /root/.cache/go-build path: /root/.cache/go-build
@ -135,7 +135,7 @@ steps:
- main - main
- name: release - name: release
image: superseriousbusiness/gotosocial-drone-build:0.6.0 # https://github.com/superseriousbusiness/gotosocial-drone-build image: superseriousbusiness/gotosocial-drone-build:0.7.0 # https://github.com/superseriousbusiness/gotosocial-drone-build
volumes: volumes:
- name: go-build-cache - name: go-build-cache
path: /root/.cache/go-build path: /root/.cache/go-build
@ -194,7 +194,7 @@ clone:
steps: steps:
- name: mirror - name: mirror
image: superseriousbusiness/gotosocial-drone-build:0.6.0 image: superseriousbusiness/gotosocial-drone-build:0.7.0
environment: environment:
ORIGIN_REPO: https://github.com/superseriousbusiness/gotosocial ORIGIN_REPO: https://github.com/superseriousbusiness/gotosocial
TARGET_REPO: https://codeberg.org/superseriousbusiness/gotosocial TARGET_REPO: https://codeberg.org/superseriousbusiness/gotosocial
@ -207,6 +207,6 @@ steps:
--- ---
kind: signature kind: signature
hmac: f4008d87e4e5b67251eb89f255c1224e6ab5818828cab24fc319b8f829176058 hmac: 9810bf692fb1029c13b0a1e2f556e2306d16f7d3eec9ca6163a0499c147280c1
... ...

View file

@ -1,4 +1,5 @@
# https://goreleaser.com # Version 2 of GoReleaser: https://goreleaser.com/errors/version/
version: 2
project_name: gotosocial project_name: gotosocial
before: before:
# https://goreleaser.com/customization/hooks/ # https://goreleaser.com/customization/hooks/
@ -185,7 +186,7 @@ checksum:
name_template: 'checksums.txt' name_template: 'checksums.txt'
snapshot: snapshot:
# https://goreleaser.com/customization/snapshots/ # https://goreleaser.com/customization/snapshots/
name_template: "{{ incpatch .Version }}-SNAPSHOT" version_template: "{{ incpatch .Version }}-SNAPSHOT"
source: source:
# https://goreleaser.com/customization/source/ # https://goreleaser.com/customization/source/
enabled: true enabled: true

View file

@ -2,7 +2,7 @@
# Dockerfile reference: https://docs.docker.com/engine/reference/builder/ # Dockerfile reference: https://docs.docker.com/engine/reference/builder/
# stage 1: generate up-to-date swagger.yaml to put in the final container # stage 1: generate up-to-date swagger.yaml to put in the final container
FROM --platform=${BUILDPLATFORM} golang:1.22-alpine AS swagger FROM --platform=${BUILDPLATFORM} golang:1.23.0-alpine AS swagger
RUN \ RUN \
### Installs goswagger for building swagger definitions inside this container ### Installs goswagger for building swagger definitions inside this container
@ -28,7 +28,7 @@ RUN yarn --cwd ./web/source install && \
rm -rf ./web/source rm -rf ./web/source
# stage 3: build the executor container # stage 3: build the executor container
FROM --platform=${TARGETPLATFORM} alpine:3.19.1 as executor FROM --platform=${TARGETPLATFORM} alpine:3.20.2 as executor
# switch to non-root user:group for GtS # switch to non-root user:group for GtS
USER 1000:1000 USER 1000:1000

6
go.mod
View file

@ -1,6 +1,6 @@
module github.com/superseriousbusiness/gotosocial module github.com/superseriousbusiness/gotosocial
go 1.22.2 go 1.23
replace modernc.org/sqlite => gitlab.com/NyaaaWhatsUpDoc/sqlite v1.33.1-concurrency-workaround replace modernc.org/sqlite => gitlab.com/NyaaaWhatsUpDoc/sqlite v1.33.1-concurrency-workaround
@ -44,7 +44,7 @@ require (
github.com/miekg/dns v1.1.62 github.com/miekg/dns v1.1.62
github.com/minio/minio-go/v7 v7.0.77 github.com/minio/minio-go/v7 v7.0.77
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/ncruces/go-sqlite3 v0.18.3 github.com/ncruces/go-sqlite3 v0.18.4
github.com/oklog/ulid v1.3.1 github.com/oklog/ulid v1.3.1
github.com/prometheus/client_golang v1.20.4 github.com/prometheus/client_golang v1.20.4
github.com/spf13/cobra v1.8.1 github.com/spf13/cobra v1.8.1
@ -72,7 +72,7 @@ require (
go.opentelemetry.io/otel/sdk v1.29.0 go.opentelemetry.io/otel/sdk v1.29.0
go.opentelemetry.io/otel/sdk/metric v1.29.0 go.opentelemetry.io/otel/sdk/metric v1.29.0
go.opentelemetry.io/otel/trace v1.29.0 go.opentelemetry.io/otel/trace v1.29.0
go.uber.org/automaxprocs v1.5.3 go.uber.org/automaxprocs v1.6.0
golang.org/x/crypto v0.27.0 golang.org/x/crypto v0.27.0
golang.org/x/image v0.20.0 golang.org/x/image v0.20.0
golang.org/x/net v0.29.0 golang.org/x/net v0.29.0

8
go.sum
View file

@ -434,8 +434,8 @@ github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/ncruces/go-sqlite3 v0.18.3 h1:tyMa75uh7LcINcfo0WrzOvcTkfz8Hqu0TEPX+KVyes4= github.com/ncruces/go-sqlite3 v0.18.4 h1:Je8o3y33MDwPYY/Cacas8yCsuoUzpNY/AgoSlN2ekyE=
github.com/ncruces/go-sqlite3 v0.18.3/go.mod h1:HAwOtA+cyEX3iN6YmkpQwfT4vMMgCB7rQRFUdOgEFik= github.com/ncruces/go-sqlite3 v0.18.4/go.mod h1:4HLag13gq1k10s4dfGBhMfRVsssJRT9/5hYqVM9RUYo=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M= github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
@ -652,8 +652,8 @@ go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt3
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=

View file

@ -145,8 +145,8 @@ func validateCreateEmoji(form *apimodel.EmojiCreateRequest) error {
return errors.New("no emoji given") return errors.New("no emoji given")
} }
maxSize := config.GetMediaEmojiLocalMaxSize() maxSize := int64(config.GetMediaEmojiLocalMaxSize()) // #nosec G115 -- Already validated.
if form.Image.Size > int64(maxSize) { if form.Image.Size > maxSize {
return fmt.Errorf("emoji image too large: image is %dKB but size limit for custom emojis is %dKB", form.Image.Size/1024, maxSize/1024) return fmt.Errorf("emoji image too large: image is %dKB but size limit for custom emojis is %dKB", form.Image.Size/1024, maxSize/1024)
} }

View file

@ -208,8 +208,8 @@ func validateUpdateEmoji(form *apimodel.EmojiUpdateRequest) error {
} }
if hasImage { if hasImage {
maxSize := config.GetMediaEmojiLocalMaxSize() maxSize := int64(config.GetMediaEmojiLocalMaxSize()) // #nosec G115 -- Already validated.
if form.Image.Size > int64(maxSize) { if form.Image.Size > maxSize {
return fmt.Errorf("emoji image too large: image is %dKB but size limit for custom emojis is %dKB", form.Image.Size/1024, maxSize/1024) return fmt.Errorf("emoji image too large: image is %dKB but size limit for custom emojis is %dKB", form.Image.Size/1024, maxSize/1024)
} }
} }

View file

@ -160,7 +160,7 @@ type MediaDimensions struct {
Duration float32 `json:"duration,omitempty"` Duration float32 `json:"duration,omitempty"`
// Bitrate of the media in bits per second. // Bitrate of the media in bits per second.
// example: 1000000 // example: 1000000
Bitrate int `json:"bitrate,omitempty"` Bitrate uint64 `json:"bitrate,omitempty"`
// Size of the media, in the format `[width]x[height]`. // Size of the media, in the format `[width]x[height]`.
// Not set for audio. // Not set for audio.
// example: 1920x1080 // example: 1920x1080

View file

@ -220,7 +220,7 @@ func (n *node) getChild(part string) *node {
for i < j { for i < j {
// avoid overflow when computing h // avoid overflow when computing h
h := int(uint(i+j) >> 1) h := int(uint(i+j) >> 1) // #nosec G115
// i ≤ h < j // i ≤ h < j
if n.child[h].part < part { if n.child[h].part < part {

View file

@ -25,6 +25,7 @@
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt" "fmt"
"math"
"net/url" "net/url"
"os" "os"
"runtime" "runtime"
@ -407,13 +408,12 @@ func maxOpenConns() int {
// deriveBunDBPGOptions takes an application config and returns either a ready-to-use set of options // deriveBunDBPGOptions takes an application config and returns either a ready-to-use set of options
// with sensible defaults, or an error if it's not satisfied by the provided config. // with sensible defaults, or an error if it's not satisfied by the provided config.
func deriveBunDBPGOptions() (*pgx.ConnConfig, error) { func deriveBunDBPGOptions() (*pgx.ConnConfig, error) {
url := config.GetDbPostgresConnectionString() // If database URL is defined, ignore
// other DB-related configuration fields.
// if database URL is defined, ignore other DB related configuration fields if url := config.GetDbPostgresConnectionString(); url != "" {
if url != "" { return pgx.ParseConfig(url)
cfg, err := pgx.ParseConfig(url)
return cfg, err
} }
// these are all optional, the db adapter figures out defaults // these are all optional, the db adapter figures out defaults
address := config.GetDbAddress() address := config.GetDbAddress()
@ -477,7 +477,10 @@ func deriveBunDBPGOptions() (*pgx.ConnConfig, error) {
cfg.Host = address cfg.Host = address
} }
if port := config.GetDbPort(); port > 0 { if port := config.GetDbPort(); port > 0 {
cfg.Port = uint16(port) if port > math.MaxUint16 {
return nil, errors.New("invalid port, must be in range 1-65535")
}
cfg.Port = uint16(port) // #nosec G115 -- Just validated above.
} }
if u := config.GetDbUser(); u != "" { if u := config.GetDbUser(); u != "" {
cfg.User = u cfg.User = u

View file

@ -97,11 +97,11 @@ func() (*media.ProcessingEmoji, error) {
} }
// Get maximum supported remote emoji size. // Get maximum supported remote emoji size.
maxsz := config.GetMediaEmojiRemoteMaxSize() maxsz := int64(config.GetMediaEmojiRemoteMaxSize()) // #nosec G115 -- Already validated.
// Prepare data function to dereference remote emoji media. // Prepare data function to dereference remote emoji media.
data := func(context.Context) (io.ReadCloser, error) { data := func(context.Context) (io.ReadCloser, error) {
return tsport.DereferenceMedia(ctx, url, int64(maxsz)) return tsport.DereferenceMedia(ctx, url, maxsz)
} }
// Create new emoji with prepared info. // Create new emoji with prepared info.
@ -189,11 +189,11 @@ func() (*media.ProcessingEmoji, error) {
} }
// Get maximum supported remote emoji size. // Get maximum supported remote emoji size.
maxsz := config.GetMediaEmojiRemoteMaxSize() maxsz := int64(config.GetMediaEmojiRemoteMaxSize()) // #nosec G115 -- Already validated.
// Prepare data function to dereference remote emoji media. // Prepare data function to dereference remote emoji media.
data := func(context.Context) (io.ReadCloser, error) { data := func(context.Context) (io.ReadCloser, error) {
return tsport.DereferenceMedia(ctx, url, int64(maxsz)) return tsport.DereferenceMedia(ctx, url, maxsz)
} }
// Update emoji with prepared info. // Update emoji with prepared info.
@ -255,11 +255,11 @@ func() (*media.ProcessingEmoji, error) {
} }
// Get maximum supported remote emoji size. // Get maximum supported remote emoji size.
maxsz := config.GetMediaEmojiRemoteMaxSize() maxsz := int64(config.GetMediaEmojiRemoteMaxSize()) // #nosec G115 -- Already validated.
// Prepare data function to dereference remote emoji media. // Prepare data function to dereference remote emoji media.
data := func(context.Context) (io.ReadCloser, error) { data := func(context.Context) (io.ReadCloser, error) {
return tsport.DereferenceMedia(ctx, url, int64(maxsz)) return tsport.DereferenceMedia(ctx, url, maxsz)
} }
// Recache emoji with prepared info. // Recache emoji with prepared info.

View file

@ -77,14 +77,14 @@ func() (*media.ProcessingMedia, error) {
} }
// Get maximum supported remote media size. // Get maximum supported remote media size.
maxsz := config.GetMediaRemoteMaxSize() maxsz := int64(config.GetMediaRemoteMaxSize()) // #nosec G115 -- Already validated.
// Create media with prepared info. // Create media with prepared info.
return d.mediaManager.CreateMedia( return d.mediaManager.CreateMedia(
ctx, ctx,
accountID, accountID,
func(ctx context.Context) (io.ReadCloser, error) { func(ctx context.Context) (io.ReadCloser, error) {
return tsport.DereferenceMedia(ctx, url, int64(maxsz)) return tsport.DereferenceMedia(ctx, url, maxsz)
}, },
info, info,
) )
@ -168,14 +168,14 @@ func() (*media.ProcessingMedia, error) {
} }
// Get maximum supported remote media size. // Get maximum supported remote media size.
maxsz := config.GetMediaRemoteMaxSize() maxsz := int64(config.GetMediaRemoteMaxSize()) // #nosec G115 -- Already validated.
// Recache media with prepared info, // Recache media with prepared info,
// this will also update media in db. // this will also update media in db.
return d.mediaManager.CacheMedia( return d.mediaManager.CacheMedia(
attach, attach,
func(ctx context.Context) (io.ReadCloser, error) { func(ctx context.Context) (io.ReadCloser, error) {
return tsport.DereferenceMedia(ctx, url, int64(maxsz)) return tsport.DereferenceMedia(ctx, url, maxsz)
}, },
), nil ), nil
}, },

View file

@ -516,10 +516,12 @@ func (d *Dereferencer) enrichStatus(
latestStatus.ID = status.ID latestStatus.ID = status.ID
} }
// Carry-over values and set fetch time. // Set latest fetch time and carry-
latestStatus.UpdatedAt = status.UpdatedAt // over some values from "old" status.
latestStatus.FetchedAt = time.Now() latestStatus.FetchedAt = time.Now()
latestStatus.UpdatedAt = status.UpdatedAt
latestStatus.Local = status.Local latestStatus.Local = status.Local
latestStatus.PinnedAt = status.PinnedAt
// Carry-over approvals. Remote instances might not yet // Carry-over approvals. Remote instances might not yet
// serve statuses with the `approved_by` field, but we // serve statuses with the `approved_by` field, but we

View file

@ -340,14 +340,14 @@ func (c *Client) do(r *Request) (rsp *http.Response, retry bool, err error) {
if u, _ := strconv.ParseUint(after, 10, 32); u != 0 { if u, _ := strconv.ParseUint(after, 10, 32); u != 0 {
// An integer no. of backoff seconds was provided. // An integer no. of backoff seconds was provided.
r.backoff = time.Duration(u) * time.Second r.backoff = time.Duration(u) * time.Second // #nosec G115 -- We clamp backoff below.
} else if at, _ := http.ParseTime(after); !at.Before(now) { } else if at, _ := http.ParseTime(after); !at.Before(now) {
// An HTTP formatted future date-time was provided. // An HTTP formatted future date-time was provided.
r.backoff = at.Sub(now) r.backoff = at.Sub(now)
} }
// Don't let their provided backoff exceed our max. // Don't let their provided backoff exceed our max.
if max := baseBackoff * time.Duration(c.retries); // if max := baseBackoff * time.Duration(c.retries); // #nosec G115 -- We control c.retries.
r.backoff > max { r.backoff > max {
r.backoff = max r.backoff = max
} }

View file

@ -21,6 +21,7 @@
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"math"
"os" "os"
"path" "path"
"strconv" "strconv"
@ -556,10 +557,18 @@ func (res *ffprobeResult) Process() (*result, error) {
if p := strings.SplitN(str, "/", 2); len(p) == 2 { if p := strings.SplitN(str, "/", 2); len(p) == 2 {
n, _ := strconv.ParseUint(p[0], 10, 32) n, _ := strconv.ParseUint(p[0], 10, 32)
d, _ := strconv.ParseUint(p[1], 10, 32) d, _ := strconv.ParseUint(p[1], 10, 32)
num, den = uint32(n), uint32(d)
if n > math.MaxUint32 || d > math.MaxUint32 {
return nil, gtserror.Newf("overflowed numerator or denominator")
}
num, den = uint32(n), uint32(d) // #nosec G115 -- Just checked.
} else { } else {
n, _ := strconv.ParseUint(p[0], 10, 32) n, _ := strconv.ParseUint(p[0], 10, 32)
num = uint32(n)
if n > math.MaxUint32 {
return nil, gtserror.Newf("overflowed numerator")
}
num = uint32(n) // #nosec G115 -- Just checked.
} }
// Set final divised framerate. // Set final divised framerate.

View file

@ -399,9 +399,9 @@ func (s *scanner) scan(x1, y1, x2, y2 int, dst []uint8) {
g16 := uint16(s[1]) g16 := uint16(s[1])
b16 := uint16(s[2]) b16 := uint16(s[2])
a16 := uint16(a) a16 := uint16(a)
d[0] = uint8(r16 * 0xff / a16) d[0] = uint8(r16 * 0xff / a16) // #nosec G115 -- Overflow desired.
d[1] = uint8(g16 * 0xff / a16) d[1] = uint8(g16 * 0xff / a16) // #nosec G115 -- Overflow desired.
d[2] = uint8(b16 * 0xff / a16) d[2] = uint8(b16 * 0xff / a16) // #nosec G115 -- Overflow desired.
d[3] = a d[3] = a
} }
j += 4 j += 4
@ -431,9 +431,9 @@ func (s *scanner) scan(x1, y1, x2, y2 int, dst []uint8) {
g32 := uint32(s[2])<<8 | uint32(s[3]) g32 := uint32(s[2])<<8 | uint32(s[3])
b32 := uint32(s[4])<<8 | uint32(s[5]) b32 := uint32(s[4])<<8 | uint32(s[5])
a32 := uint32(s[6])<<8 | uint32(s[7]) a32 := uint32(s[6])<<8 | uint32(s[7])
d[0] = uint8((r32 * 0xffff / a32) >> 8) d[0] = uint8((r32 * 0xffff / a32) >> 8) // #nosec G115 -- Overflow desired.
d[1] = uint8((g32 * 0xffff / a32) >> 8) d[1] = uint8((g32 * 0xffff / a32) >> 8) // #nosec G115 -- Overflow desired.
d[2] = uint8((b32 * 0xffff / a32) >> 8) d[2] = uint8((b32 * 0xffff / a32) >> 8) // #nosec G115 -- Overflow desired.
} }
d[3] = a d[3] = a
j += 4 j += 4
@ -530,9 +530,9 @@ func (s *scanner) scan(x1, y1, x2, y2 int, dst []uint8) {
} }
d := dst[j : j+4 : j+4] d := dst[j : j+4 : j+4]
d[0] = uint8(r) d[0] = uint8(r) // #nosec G115 -- Overflow desired.
d[1] = uint8(g) d[1] = uint8(g) // #nosec G115 -- Overflow desired.
d[2] = uint8(b) d[2] = uint8(b) // #nosec G115 -- Overflow desired.
d[3] = 0xff d[3] = 0xff
iy++ iy++
@ -569,9 +569,9 @@ func (s *scanner) scan(x1, y1, x2, y2 int, dst []uint8) {
d := dst[j : j+4 : j+4] d := dst[j : j+4 : j+4]
switch a16 { switch a16 {
case 0xffff: case 0xffff:
d[0] = uint8(r16 >> 8) d[0] = uint8(r16 >> 8) // #nosec G115 -- Overflow desired.
d[1] = uint8(g16 >> 8) d[1] = uint8(g16 >> 8) // #nosec G115 -- Overflow desired.
d[2] = uint8(b16 >> 8) d[2] = uint8(b16 >> 8) // #nosec G115 -- Overflow desired.
d[3] = 0xff d[3] = 0xff
case 0: case 0:
d[0] = 0 d[0] = 0
@ -579,10 +579,10 @@ func (s *scanner) scan(x1, y1, x2, y2 int, dst []uint8) {
d[2] = 0 d[2] = 0
d[3] = 0 d[3] = 0
default: default:
d[0] = uint8(((r16 * 0xffff) / a16) >> 8) d[0] = uint8(((r16 * 0xffff) / a16) >> 8) // #nosec G115 -- Overflow desired.
d[1] = uint8(((g16 * 0xffff) / a16) >> 8) d[1] = uint8(((g16 * 0xffff) / a16) >> 8) // #nosec G115 -- Overflow desired.
d[2] = uint8(((b16 * 0xffff) / a16) >> 8) d[2] = uint8(((b16 * 0xffff) / a16) >> 8) // #nosec G115 -- Overflow desired.
d[3] = uint8(a16 >> 8) d[3] = uint8(a16 >> 8) // #nosec G115 -- Overflow desired.
} }
j += 4 j += 4
} }
@ -617,7 +617,7 @@ func clampFloat(x float64) uint8 {
return 255 return 255
} }
if v > 0 { if v > 0 {
return uint8(v) return uint8(v) // #nosec G115 -- Just checked.
} }
return 0 return 0
} }

View file

@ -49,9 +49,6 @@ func (m *Manager) RefetchEmojis(ctx context.Context, domain string, dereferenceM
refetchIDs []string refetchIDs []string
) )
// Get max supported remote emoji media size.
maxsz := config.GetMediaEmojiRemoteMaxSize()
// page through emojis 20 at a time, looking for those with missing images // page through emojis 20 at a time, looking for those with missing images
for { for {
// Fetch next block of emojis from database // Fetch next block of emojis from database
@ -111,8 +108,10 @@ func (m *Manager) RefetchEmojis(ctx context.Context, domain string, dereferenceM
continue continue
} }
// Get max supported remote emoji media size.
maxsz := int64(config.GetMediaEmojiRemoteMaxSize()) // #nosec G115 -- Already validated.
dataFunc := func(ctx context.Context) (reader io.ReadCloser, err error) { dataFunc := func(ctx context.Context) (reader io.ReadCloser, err error) {
return dereferenceMedia(ctx, emojiImageIRI, int64(maxsz)) return dereferenceMedia(ctx, emojiImageIRI, maxsz)
} }
processingEmoji, err := m.UpdateEmoji(ctx, emoji, dataFunc, AdditionalEmojiInfo{ processingEmoji, err := m.UpdateEmoji(ctx, emoji, dataFunc, AdditionalEmojiInfo{

View file

@ -462,11 +462,11 @@ func (p *Processor) UpdateAvatar(
gtserror.WithCode, gtserror.WithCode,
) { ) {
// Get maximum supported local media size. // Get maximum supported local media size.
maxsz := config.GetMediaLocalMaxSize() maxsz := int64(config.GetMediaLocalMaxSize()) // #nosec G115 -- Already validated.
// Ensure media within size bounds. // Ensure media within size bounds.
if avatar.Size > int64(maxsz) { if avatar.Size > maxsz {
text := fmt.Sprintf("media exceeds configured max size: %s", maxsz) text := fmt.Sprintf("media exceeds configured max size: %d", maxsz)
return nil, gtserror.NewErrorBadRequest(errors.New(text), text) return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
} }
@ -478,7 +478,7 @@ func (p *Processor) UpdateAvatar(
} }
// Wrap the multipart file reader to ensure is limited to max. // Wrap the multipart file reader to ensure is limited to max.
rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, int64(maxsz)) rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, maxsz)
// Write to instance storage. // Write to instance storage.
return p.c.StoreLocalMedia(ctx, return p.c.StoreLocalMedia(ctx,
@ -507,11 +507,11 @@ func (p *Processor) UpdateHeader(
gtserror.WithCode, gtserror.WithCode,
) { ) {
// Get maximum supported local media size. // Get maximum supported local media size.
maxsz := config.GetMediaLocalMaxSize() maxsz := int64(config.GetMediaLocalMaxSize()) // #nosec G115 -- Already validated.
// Ensure media within size bounds. // Ensure media within size bounds.
if header.Size > int64(maxsz) { if header.Size > maxsz {
text := fmt.Sprintf("media exceeds configured max size: %s", maxsz) text := fmt.Sprintf("media exceeds configured max size: %d", maxsz)
return nil, gtserror.NewErrorBadRequest(errors.New(text), text) return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
} }
@ -523,7 +523,7 @@ func (p *Processor) UpdateHeader(
} }
// Wrap the multipart file reader to ensure is limited to max. // Wrap the multipart file reader to ensure is limited to max.
rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, int64(maxsz)) rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, maxsz)
// Write to instance storage. // Write to instance storage.
return p.c.StoreLocalMedia(ctx, return p.c.StoreLocalMedia(ctx,

View file

@ -45,11 +45,11 @@ func (p *Processor) EmojiCreate(
) (*apimodel.Emoji, gtserror.WithCode) { ) (*apimodel.Emoji, gtserror.WithCode) {
// Get maximum supported local emoji size. // Get maximum supported local emoji size.
maxsz := config.GetMediaEmojiLocalMaxSize() maxsz := int64(config.GetMediaEmojiLocalMaxSize()) // #nosec G115 -- Already validated.
// Ensure media within size bounds. // Ensure media within size bounds.
if form.Image.Size > int64(maxsz) { if form.Image.Size > maxsz {
text := fmt.Sprintf("emoji exceeds configured max size: %s", maxsz) text := fmt.Sprintf("emoji exceeds configured max size: %d", maxsz)
return nil, gtserror.NewErrorBadRequest(errors.New(text), text) return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
} }
@ -61,7 +61,7 @@ func (p *Processor) EmojiCreate(
} }
// Wrap the multipart file reader to ensure is limited to max. // Wrap the multipart file reader to ensure is limited to max.
rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, int64(maxsz)) rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, maxsz)
data := func(context.Context) (io.ReadCloser, error) { data := func(context.Context) (io.ReadCloser, error) {
return rc, nil return rc, nil
} }
@ -441,11 +441,11 @@ func (p *Processor) emojiUpdateModify(
// We can do both at the same time :) // We can do both at the same time :)
// Get maximum supported local emoji size. // Get maximum supported local emoji size.
maxsz := config.GetMediaEmojiLocalMaxSize() maxsz := int64(config.GetMediaEmojiLocalMaxSize()) // #nosec G115 -- Already validated.
// Ensure media within size bounds. // Ensure media within size bounds.
if image.Size > int64(maxsz) { if image.Size > maxsz {
text := fmt.Sprintf("emoji exceeds configured max size: %s", maxsz) text := fmt.Sprintf("emoji exceeds configured max size: %d", maxsz)
return nil, gtserror.NewErrorBadRequest(errors.New(text), text) return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
} }
@ -457,7 +457,7 @@ func (p *Processor) emojiUpdateModify(
} }
// Wrap the multipart file reader to ensure is limited to max. // Wrap the multipart file reader to ensure is limited to max.
rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, int64(maxsz)) rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, maxsz)
data := func(context.Context) (io.ReadCloser, error) { data := func(context.Context) (io.ReadCloser, error) {
return rc, nil return rc, nil
} }

View file

@ -35,11 +35,11 @@
func (p *Processor) Create(ctx context.Context, account *gtsmodel.Account, form *apimodel.AttachmentRequest) (*apimodel.Attachment, gtserror.WithCode) { func (p *Processor) Create(ctx context.Context, account *gtsmodel.Account, form *apimodel.AttachmentRequest) (*apimodel.Attachment, gtserror.WithCode) {
// Get maximum supported local media size. // Get maximum supported local media size.
maxsz := config.GetMediaLocalMaxSize() maxsz := int64(config.GetMediaLocalMaxSize()) // #nosec G115 -- Already validated.
// Ensure media within size bounds. // Ensure media within size bounds.
if form.File.Size > int64(maxsz) { if form.File.Size > maxsz {
text := fmt.Sprintf("media exceeds configured max size: %s", maxsz) text := fmt.Sprintf("media exceeds configured max size: %d", maxsz)
return nil, gtserror.NewErrorBadRequest(errors.New(text), text) return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
} }
@ -58,7 +58,7 @@ func (p *Processor) Create(ctx context.Context, account *gtsmodel.Account, form
} }
// Wrap the multipart file reader to ensure is limited to max. // Wrap the multipart file reader to ensure is limited to max.
rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, int64(maxsz)) rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, maxsz)
// Create local media and write to instance storage. // Create local media and write to instance storage.
attachment, errWithCode := p.c.StoreLocalMedia(ctx, attachment, errWithCode := p.c.StoreLocalMedia(ctx,

View file

@ -647,7 +647,7 @@ func (c *Converter) AttachmentToAPIAttachment(ctx context.Context, media *gtsmod
Size: toAPISize(media.FileMeta.Original.Width, media.FileMeta.Original.Height), Size: toAPISize(media.FileMeta.Original.Width, media.FileMeta.Original.Height),
FrameRate: toAPIFrameRate(media.FileMeta.Original.Framerate), FrameRate: toAPIFrameRate(media.FileMeta.Original.Framerate),
Duration: util.PtrOrZero(media.FileMeta.Original.Duration), Duration: util.PtrOrZero(media.FileMeta.Original.Duration),
Bitrate: int(util.PtrOrZero(media.FileMeta.Original.Bitrate)), Bitrate: util.PtrOrZero(media.FileMeta.Original.Bitrate),
} }
// Copy over local file URL. // Copy over local file URL.
@ -1551,9 +1551,9 @@ func (c *Converter) InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Ins
instance.Configuration.Statuses.CharactersReservedPerURL = instanceStatusesCharactersReservedPerURL instance.Configuration.Statuses.CharactersReservedPerURL = instanceStatusesCharactersReservedPerURL
instance.Configuration.Statuses.SupportedMimeTypes = instanceStatusesSupportedMimeTypes instance.Configuration.Statuses.SupportedMimeTypes = instanceStatusesSupportedMimeTypes
instance.Configuration.MediaAttachments.SupportedMimeTypes = media.SupportedMIMETypes instance.Configuration.MediaAttachments.SupportedMimeTypes = media.SupportedMIMETypes
instance.Configuration.MediaAttachments.ImageSizeLimit = int(config.GetMediaRemoteMaxSize()) instance.Configuration.MediaAttachments.ImageSizeLimit = int(config.GetMediaRemoteMaxSize()) // #nosec G115 -- Already validated.
instance.Configuration.MediaAttachments.ImageMatrixLimit = instanceMediaAttachmentsImageMatrixLimit instance.Configuration.MediaAttachments.ImageMatrixLimit = instanceMediaAttachmentsImageMatrixLimit
instance.Configuration.MediaAttachments.VideoSizeLimit = int(config.GetMediaRemoteMaxSize()) instance.Configuration.MediaAttachments.VideoSizeLimit = int(config.GetMediaRemoteMaxSize()) // #nosec G115 -- Already validated.
instance.Configuration.MediaAttachments.VideoFrameRateLimit = instanceMediaAttachmentsVideoFrameRateLimit instance.Configuration.MediaAttachments.VideoFrameRateLimit = instanceMediaAttachmentsVideoFrameRateLimit
instance.Configuration.MediaAttachments.VideoMatrixLimit = instanceMediaAttachmentsVideoMatrixLimit instance.Configuration.MediaAttachments.VideoMatrixLimit = instanceMediaAttachmentsVideoMatrixLimit
instance.Configuration.Polls.MaxOptions = config.GetStatusesPollMaxOptions() instance.Configuration.Polls.MaxOptions = config.GetStatusesPollMaxOptions()
@ -1563,7 +1563,7 @@ func (c *Converter) InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Ins
instance.Configuration.Accounts.AllowCustomCSS = config.GetAccountsAllowCustomCSS() instance.Configuration.Accounts.AllowCustomCSS = config.GetAccountsAllowCustomCSS()
instance.Configuration.Accounts.MaxFeaturedTags = instanceAccountsMaxFeaturedTags instance.Configuration.Accounts.MaxFeaturedTags = instanceAccountsMaxFeaturedTags
instance.Configuration.Accounts.MaxProfileFields = instanceAccountsMaxProfileFields instance.Configuration.Accounts.MaxProfileFields = instanceAccountsMaxProfileFields
instance.Configuration.Emojis.EmojiSizeLimit = int(config.GetMediaEmojiLocalMaxSize()) instance.Configuration.Emojis.EmojiSizeLimit = int(config.GetMediaEmojiLocalMaxSize()) // #nosec G115 -- Already validated.
instance.Configuration.OIDCEnabled = config.GetOIDCEnabled() instance.Configuration.OIDCEnabled = config.GetOIDCEnabled()
// URLs // URLs
@ -1695,9 +1695,9 @@ func (c *Converter) InstanceToAPIV2Instance(ctx context.Context, i *gtsmodel.Ins
instance.Configuration.Statuses.CharactersReservedPerURL = instanceStatusesCharactersReservedPerURL instance.Configuration.Statuses.CharactersReservedPerURL = instanceStatusesCharactersReservedPerURL
instance.Configuration.Statuses.SupportedMimeTypes = instanceStatusesSupportedMimeTypes instance.Configuration.Statuses.SupportedMimeTypes = instanceStatusesSupportedMimeTypes
instance.Configuration.MediaAttachments.SupportedMimeTypes = media.SupportedMIMETypes instance.Configuration.MediaAttachments.SupportedMimeTypes = media.SupportedMIMETypes
instance.Configuration.MediaAttachments.ImageSizeLimit = int(config.GetMediaRemoteMaxSize()) instance.Configuration.MediaAttachments.ImageSizeLimit = int(config.GetMediaRemoteMaxSize()) // #nosec G115 -- Already validated.
instance.Configuration.MediaAttachments.ImageMatrixLimit = instanceMediaAttachmentsImageMatrixLimit instance.Configuration.MediaAttachments.ImageMatrixLimit = instanceMediaAttachmentsImageMatrixLimit
instance.Configuration.MediaAttachments.VideoSizeLimit = int(config.GetMediaRemoteMaxSize()) instance.Configuration.MediaAttachments.VideoSizeLimit = int(config.GetMediaRemoteMaxSize()) // #nosec G115 -- Already validated.
instance.Configuration.MediaAttachments.VideoFrameRateLimit = instanceMediaAttachmentsVideoFrameRateLimit instance.Configuration.MediaAttachments.VideoFrameRateLimit = instanceMediaAttachmentsVideoFrameRateLimit
instance.Configuration.MediaAttachments.VideoMatrixLimit = instanceMediaAttachmentsVideoMatrixLimit instance.Configuration.MediaAttachments.VideoMatrixLimit = instanceMediaAttachmentsVideoMatrixLimit
instance.Configuration.Polls.MaxOptions = config.GetStatusesPollMaxOptions() instance.Configuration.Polls.MaxOptions = config.GetStatusesPollMaxOptions()
@ -1707,7 +1707,7 @@ func (c *Converter) InstanceToAPIV2Instance(ctx context.Context, i *gtsmodel.Ins
instance.Configuration.Accounts.AllowCustomCSS = config.GetAccountsAllowCustomCSS() instance.Configuration.Accounts.AllowCustomCSS = config.GetAccountsAllowCustomCSS()
instance.Configuration.Accounts.MaxFeaturedTags = instanceAccountsMaxFeaturedTags instance.Configuration.Accounts.MaxFeaturedTags = instanceAccountsMaxFeaturedTags
instance.Configuration.Accounts.MaxProfileFields = instanceAccountsMaxProfileFields instance.Configuration.Accounts.MaxProfileFields = instanceAccountsMaxProfileFields
instance.Configuration.Emojis.EmojiSizeLimit = int(config.GetMediaEmojiLocalMaxSize()) instance.Configuration.Emojis.EmojiSizeLimit = int(config.GetMediaEmojiLocalMaxSize()) // #nosec G115 -- Already validated.
instance.Configuration.OIDCEnabled = config.GetOIDCEnabled() instance.Configuration.OIDCEnabled = config.GetOIDCEnabled()
// registrations // registrations

View file

@ -10,7 +10,7 @@ as well as direct access to most of the [C SQLite API](https://sqlite.org/cintro
It wraps a [Wasm](https://webassembly.org/) [build](embed/) of SQLite, It wraps a [Wasm](https://webassembly.org/) [build](embed/) of SQLite,
and uses [wazero](https://wazero.io/) as the runtime.\ and uses [wazero](https://wazero.io/) as the runtime.\
Go, wazero and [`x/sys`](https://pkg.go.dev/golang.org/x/sys) are the _only_ runtime dependencies [^1]. Go, wazero and [`x/sys`](https://pkg.go.dev/golang.org/x/sys) are the _only_ runtime dependencies.
### Getting started ### Getting started
@ -49,6 +49,8 @@ db.QueryRow(`SELECT sqlite_version()`).Scan(&version)
simplifies [incremental BLOB I/O](https://sqlite.org/c3ref/blob_open.html). simplifies [incremental BLOB I/O](https://sqlite.org/c3ref/blob_open.html).
- [`github.com/ncruces/go-sqlite3/ext/bloom`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/bloom) - [`github.com/ncruces/go-sqlite3/ext/bloom`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/bloom)
provides a [Bloom filter](https://github.com/nalgeon/sqlean/issues/27#issuecomment-1002267134) virtual table. provides a [Bloom filter](https://github.com/nalgeon/sqlean/issues/27#issuecomment-1002267134) virtual table.
- [`github.com/ncruces/go-sqlite3/ext/closure`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/closure)
provides a transitive closure virtual table.
- [`github.com/ncruces/go-sqlite3/ext/csv`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/csv) - [`github.com/ncruces/go-sqlite3/ext/csv`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/csv)
reads [comma-separated values](https://sqlite.org/csv.html). reads [comma-separated values](https://sqlite.org/csv.html).
- [`github.com/ncruces/go-sqlite3/ext/fileio`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/fileio) - [`github.com/ncruces/go-sqlite3/ext/fileio`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/fileio)
@ -108,7 +110,7 @@ It also benefits greatly from [SQLite's](https://sqlite.org/testing.html) and
[wazero's](https://tetrate.io/blog/introducing-wazero-from-tetrate/#:~:text=Rock%2Dsolid%20test%20approach) thorough testing. [wazero's](https://tetrate.io/blog/introducing-wazero-from-tetrate/#:~:text=Rock%2Dsolid%20test%20approach) thorough testing.
Every commit is [tested](https://github.com/ncruces/go-sqlite3/wiki/Test-matrix) on Every commit is [tested](https://github.com/ncruces/go-sqlite3/wiki/Test-matrix) on
Linux (amd64/arm64/386/riscv64/s390x), macOS (amd64/arm64), Linux (amd64/arm64/386/riscv64/ppc64le/s390x), macOS (amd64/arm64),
Windows (amd64), FreeBSD (amd64), OpenBSD (amd64), NetBSD (amd64), Windows (amd64), FreeBSD (amd64), OpenBSD (amd64), NetBSD (amd64),
illumos (amd64), and Solaris (amd64). illumos (amd64), and Solaris (amd64).
@ -128,7 +130,4 @@ The Wasm and VFS layers are also tested by running SQLite's
- [`modernc.org/sqlite`](https://pkg.go.dev/modernc.org/sqlite) - [`modernc.org/sqlite`](https://pkg.go.dev/modernc.org/sqlite)
- [`crawshaw.io/sqlite`](https://pkg.go.dev/crawshaw.io/sqlite) - [`crawshaw.io/sqlite`](https://pkg.go.dev/crawshaw.io/sqlite)
- [`github.com/mattn/go-sqlite3`](https://pkg.go.dev/github.com/mattn/go-sqlite3) - [`github.com/mattn/go-sqlite3`](https://pkg.go.dev/github.com/mattn/go-sqlite3)
- [`github.com/zombiezen/go-sqlite`](https://pkg.go.dev/github.com/zombiezen/go-sqlite) - [`github.com/zombiezen/go-sqlite`](https://pkg.go.dev/github.com/zombiezen/go-sqlite)
[^1]: anything else you find in `go.mod` is either a test dependency,
or needed by one of the extensions.

View file

@ -302,7 +302,7 @@ func (c *Conn) SoftHeapLimit(n int64) int64 {
return int64(c.call("sqlite3_soft_heap_limit64", uint64(n))) return int64(c.call("sqlite3_soft_heap_limit64", uint64(n)))
} }
// SoftHeapLimit imposes a hard limit on heap size. // HardHeapLimit imposes a hard limit on heap size.
// //
// https://sqlite.org/c3ref/hard_heap_limit64.html // https://sqlite.org/c3ref/hard_heap_limit64.html
func (c *Conn) HardHeapLimit(n int64) int64 { func (c *Conn) HardHeapLimit(n int64) int64 {

View file

@ -578,8 +578,15 @@ type rows struct {
*stmt *stmt
names []string names []string
types []string types []string
nulls []bool
} }
var (
// Ensure these interfaces are implemented:
_ driver.RowsColumnTypeDatabaseTypeName = &rows{}
_ driver.RowsColumnTypeNullable = &rows{}
)
func (r *rows) Close() error { func (r *rows) Close() error {
r.Stmt.ClearBindings() r.Stmt.ClearBindings()
return r.Stmt.Reset() return r.Stmt.Reset()
@ -596,6 +603,22 @@ func (r *rows) Columns() []string {
return r.names return r.names
} }
func (r *rows) loadTypes() {
if r.nulls == nil {
count := r.Stmt.ColumnCount()
r.nulls = make([]bool, count)
r.types = make([]string, count)
for i := range r.nulls {
if col := r.Stmt.ColumnOriginName(i); col != "" {
r.types[i], _, r.nulls[i], _, _, _ = r.Stmt.Conn().TableColumnMetadata(
r.Stmt.ColumnDatabaseName(i),
r.Stmt.ColumnTableName(i),
col)
}
}
}
}
func (r *rows) declType(index int) string { func (r *rows) declType(index int) string {
if r.types == nil { if r.types == nil {
count := r.Stmt.ColumnCount() count := r.Stmt.ColumnCount()
@ -608,7 +631,8 @@ func (r *rows) declType(index int) string {
} }
func (r *rows) ColumnTypeDatabaseTypeName(index int) string { func (r *rows) ColumnTypeDatabaseTypeName(index int) string {
decltype := r.declType(index) r.loadTypes()
decltype := r.types[index]
if len := len(decltype); len > 0 && decltype[len-1] == ')' { if len := len(decltype); len > 0 && decltype[len-1] == ')' {
if i := strings.LastIndexByte(decltype, '('); i >= 0 { if i := strings.LastIndexByte(decltype, '('); i >= 0 {
decltype = decltype[:i] decltype = decltype[:i]
@ -617,6 +641,14 @@ func (r *rows) ColumnTypeDatabaseTypeName(index int) string {
return strings.TrimSpace(decltype) return strings.TrimSpace(decltype)
} }
func (r *rows) ColumnTypeNullable(index int) (nullable, ok bool) {
r.loadTypes()
if r.nulls[index] {
return false, true
}
return true, false
}
func (r *rows) Next(dest []driver.Value) error { func (r *rows) Next(dest []driver.Value) error {
old := r.Stmt.Conn().SetInterrupt(r.ctx) old := r.Stmt.Conn().SetInterrupt(r.ctx)
defer r.Stmt.Conn().SetInterrupt(old) defer r.Stmt.Conn().SetInterrupt(old)

View file

@ -1,4 +1,4 @@
//go:build !(darwin || linux) || !(amd64 || arm64 || riscv64) || sqlite3_noshm || sqlite3_nosys //go:build !(darwin || linux) || !(amd64 || arm64 || riscv64 || ppc64le) || sqlite3_noshm || sqlite3_nosys
package alloc package alloc

View file

@ -1,4 +1,4 @@
//go:build unix && (amd64 || arm64 || riscv64) && !(sqlite3_noshm || sqlite3_nosys) //go:build unix && (amd64 || arm64 || riscv64 || ppc64le) && !(sqlite3_noshm || sqlite3_nosys)
package util package util

View file

@ -1,4 +1,4 @@
//go:build !unix || !(amd64 || arm64 || riscv64) || sqlite3_noshm || sqlite3_nosys //go:build !unix || !(amd64 || arm64 || riscv64 || ppc64le) || sqlite3_noshm || sqlite3_nosys
package util package util

View file

@ -0,0 +1,12 @@
package util
type Set[E comparable] map[E]struct{}
func (s Set[E]) Add(v E) {
s[v] = struct{}{}
}
func (s Set[E]) Contains(v E) bool {
_, ok := s[v]
return ok
}

View file

@ -46,7 +46,7 @@ to check if your build supports file locking.
### Write-Ahead Logging ### Write-Ahead Logging
On 64-bit Unix, this module uses `mmap` to implement On 64-bit little-endian Unix, this module uses `mmap` to implement
[shared-memory for the WAL-index](https://sqlite.org/wal.html#implementation_of_shared_memory_for_the_wal_index), [shared-memory for the WAL-index](https://sqlite.org/wal.html#implementation_of_shared_memory_for_the_wal_index),
like SQLite. like SQLite.

View file

@ -75,19 +75,7 @@ func (f *vfsFile) Lock(lock LockLevel) error {
if f.lock <= LOCK_NONE || f.lock >= LOCK_EXCLUSIVE { if f.lock <= LOCK_NONE || f.lock >= LOCK_EXCLUSIVE {
panic(util.AssertErr()) panic(util.AssertErr())
} }
reserved := f.lock == LOCK_RESERVED if rc := osGetExclusiveLock(f.File, &f.lock); rc != _OK {
// A PENDING lock is needed before acquiring an EXCLUSIVE lock.
if f.lock < LOCK_PENDING {
// If we're already RESERVED, we can block indefinitely,
// since only incoming readers may briefly hold the PENDING lock.
if rc := osGetPendingLock(f.File, reserved /* block */); rc != _OK {
return rc
}
f.lock = LOCK_PENDING
}
// We are now PENDING, so we're just waiting for readers to leave.
// If we were RESERVED, we can block for a bit before invoking the busy handler.
if rc := osGetExclusiveLock(f.File, reserved /* block */); rc != _OK {
return rc return rc
} }
f.lock = LOCK_EXCLUSIVE f.lock = LOCK_EXCLUSIVE

View file

@ -4,31 +4,15 @@
import ( import (
"os" "os"
"time"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
func osUnlock(file *os.File, start, len int64) _ErrorCode { func osGetSharedLock(file *os.File) _ErrorCode {
if start == 0 && len == 0 {
err := unix.Flock(int(file.Fd()), unix.LOCK_UN)
if err != nil {
return _IOERR_UNLOCK
}
}
return _OK
}
func osLock(file *os.File, how int, def _ErrorCode) _ErrorCode {
err := unix.Flock(int(file.Fd()), how)
return osLockErrorCode(err, def)
}
func osReadLock(file *os.File, _ /*start*/, _ /*len*/ int64, _ /*timeout*/ time.Duration) _ErrorCode {
return osLock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK) return osLock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK)
} }
func osWriteLock(file *os.File, _ /*start*/, _ /*len*/ int64, _ /*timeout*/ time.Duration) _ErrorCode { func osGetReservedLock(file *os.File) _ErrorCode {
rc := osLock(file, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK) rc := osLock(file, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK)
if rc == _BUSY { if rc == _BUSY {
// The documentation states the lock is upgraded by releasing the previous lock, // The documentation states the lock is upgraded by releasing the previous lock,
@ -38,3 +22,40 @@ func osWriteLock(file *os.File, _ /*start*/, _ /*len*/ int64, _ /*timeout*/ time
} }
return rc return rc
} }
func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode {
if *state >= LOCK_RESERVED {
return _OK
}
return osGetReservedLock(file)
}
func osDowngradeLock(file *os.File, _ LockLevel) _ErrorCode {
rc := osLock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK)
if rc == _BUSY {
// The documentation states the lock is upgraded by releasing the previous lock,
// then acquiring the new lock.
// This is a race, so return IOERR_RDLOCK to ensure the transaction is aborted.
return _IOERR_RDLOCK
}
return _OK
}
func osReleaseLock(file *os.File, _ LockLevel) _ErrorCode {
err := unix.Flock(int(file.Fd()), unix.LOCK_UN)
if err != nil {
return _IOERR_UNLOCK
}
return _OK
}
func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
// Test the RESERVED lock.
lock, rc := osTestLock(file, _RESERVED_BYTE, 1)
return lock == unix.F_WRLCK, rc
}
func osLock(file *os.File, how int, def _ErrorCode) _ErrorCode {
err := unix.Flock(int(file.Fd()), how)
return osLockErrorCode(err, def)
}

View file

@ -1,4 +1,4 @@
//go:build (amd64 || arm64 || riscv64) && !sqlite3_nosys //go:build (amd64 || arm64 || riscv64 || ppc64le) && !sqlite3_nosys
package vfs package vfs
@ -9,6 +9,7 @@
) )
const ( const (
// https://godbolt.org/z/1PcK5vea3
_F2FS_IOC_START_ATOMIC_WRITE = 62721 _F2FS_IOC_START_ATOMIC_WRITE = 62721
_F2FS_IOC_COMMIT_ATOMIC_WRITE = 62722 _F2FS_IOC_COMMIT_ATOMIC_WRITE = 62722
_F2FS_IOC_ABORT_ATOMIC_WRITE = 62725 _F2FS_IOC_ABORT_ATOMIC_WRITE = 62725

59
vendor/github.com/ncruces/go-sqlite3/vfs/os_ofd.go generated vendored Normal file
View file

@ -0,0 +1,59 @@
//go:build (linux || darwin) && !(sqlite3_flock || sqlite3_nosys)
package vfs
import (
"os"
"time"
"golang.org/x/sys/unix"
)
func osGetSharedLock(file *os.File) _ErrorCode {
// Test the PENDING lock before acquiring a new SHARED lock.
if lock, _ := osTestLock(file, _PENDING_BYTE, 1); lock == unix.F_WRLCK {
return _BUSY
}
// Acquire the SHARED lock.
return osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
}
func osGetReservedLock(file *os.File) _ErrorCode {
// Acquire the RESERVED lock.
return osWriteLock(file, _RESERVED_BYTE, 1, 0)
}
func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode {
if *state == LOCK_RESERVED {
// A PENDING lock is needed before acquiring an EXCLUSIVE lock.
if rc := osWriteLock(file, _PENDING_BYTE, 1, -1); rc != _OK {
return rc
}
*state = LOCK_PENDING
}
// Acquire the EXCLUSIVE lock.
return osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, time.Millisecond)
}
func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode {
if state >= LOCK_EXCLUSIVE {
// Downgrade to a SHARED lock.
if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK {
// notest // this should never happen
return _IOERR_RDLOCK
}
}
// Release the PENDING and RESERVED locks.
return osUnlock(file, _PENDING_BYTE, 2)
}
func osReleaseLock(file *os.File, _ LockLevel) _ErrorCode {
// Release all locks.
return osUnlock(file, 0, 0)
}
func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
// Test the RESERVED lock.
lock, rc := osTestLock(file, _RESERVED_BYTE, 1)
return lock == unix.F_WRLCK, rc
}

View file

@ -1,4 +1,4 @@
//go:build !linux || !(amd64 || arm64 || riscv64) || sqlite3_nosys //go:build !linux || !(amd64 || arm64 || riscv64 || ppc64le) || sqlite3_nosys
package vfs package vfs

View file

@ -31,3 +31,41 @@ func osSetMode(file *os.File, modeof string) error {
} }
return nil return nil
} }
func osTestLock(file *os.File, start, len int64) (int16, _ErrorCode) {
lock := unix.Flock_t{
Type: unix.F_WRLCK,
Start: start,
Len: len,
}
if unix.FcntlFlock(file.Fd(), unix.F_GETLK, &lock) != nil {
return 0, _IOERR_CHECKRESERVEDLOCK
}
return lock.Type, _OK
}
func osLockErrorCode(err error, def _ErrorCode) _ErrorCode {
if err == nil {
return _OK
}
if errno, ok := err.(unix.Errno); ok {
switch errno {
case
unix.EACCES,
unix.EAGAIN,
unix.EBUSY,
unix.EINTR,
unix.ENOLCK,
unix.EDEADLK,
unix.ETIMEDOUT:
return _BUSY
case unix.EPERM:
return _PERM
}
// notest // usually EWOULDBLOCK == EAGAIN
if errno == unix.EWOULDBLOCK && unix.EWOULDBLOCK != unix.EAGAIN {
return _BUSY
}
}
return def
}

View file

@ -1,108 +0,0 @@
//go:build (linux || darwin || freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) && !sqlite3_nosys
package vfs
import (
"os"
"time"
"golang.org/x/sys/unix"
)
func osGetSharedLock(file *os.File) _ErrorCode {
// Test the PENDING lock before acquiring a new SHARED lock.
if lock, _ := osGetLock(file, _PENDING_BYTE, 1); lock == unix.F_WRLCK {
return _BUSY
}
// Acquire the SHARED lock.
return osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
}
func osGetReservedLock(file *os.File) _ErrorCode {
// Acquire the RESERVED lock.
return osWriteLock(file, _RESERVED_BYTE, 1, 0)
}
func osGetPendingLock(file *os.File, block bool) _ErrorCode {
var timeout time.Duration
if block {
timeout = -1
}
// Acquire the PENDING lock.
return osWriteLock(file, _PENDING_BYTE, 1, timeout)
}
func osGetExclusiveLock(file *os.File, block bool) _ErrorCode {
var timeout time.Duration
if block {
timeout = time.Millisecond
}
// Acquire the EXCLUSIVE lock.
return osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout)
}
func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode {
if state >= LOCK_EXCLUSIVE {
// Downgrade to a SHARED lock.
if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK {
// In theory, the downgrade to a SHARED cannot fail because another
// process is holding an incompatible lock. If it does, this
// indicates that the other process is not following the locking
// protocol. If this happens, return IOERR_RDLOCK. Returning
// BUSY would confuse the upper layer.
// notest
return _IOERR_RDLOCK
}
}
// Release the PENDING and RESERVED locks.
return osUnlock(file, _PENDING_BYTE, 2)
}
func osReleaseLock(file *os.File, _ LockLevel) _ErrorCode {
// Release all locks.
return osUnlock(file, 0, 0)
}
func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
// Test the RESERVED lock.
lock, rc := osGetLock(file, _RESERVED_BYTE, 1)
return lock == unix.F_WRLCK, rc
}
func osGetLock(file *os.File, start, len int64) (int16, _ErrorCode) {
lock := unix.Flock_t{
Type: unix.F_WRLCK,
Start: start,
Len: len,
}
if unix.FcntlFlock(file.Fd(), unix.F_GETLK, &lock) != nil {
return 0, _IOERR_CHECKRESERVEDLOCK
}
return lock.Type, _OK
}
func osLockErrorCode(err error, def _ErrorCode) _ErrorCode {
if err == nil {
return _OK
}
if errno, ok := err.(unix.Errno); ok {
switch errno {
case
unix.EACCES,
unix.EAGAIN,
unix.EBUSY,
unix.EINTR,
unix.ENOLCK,
unix.EDEADLK,
unix.ETIMEDOUT:
return _BUSY
case unix.EPERM:
return _PERM
}
// notest // usually EWOULDBLOCK == EAGAIN
if errno == unix.EWOULDBLOCK && unix.EWOULDBLOCK != unix.EAGAIN {
return _BUSY
}
}
return def
}

View file

@ -28,27 +28,25 @@ func osGetReservedLock(file *os.File) _ErrorCode {
return osWriteLock(file, _RESERVED_BYTE, 1, 0) return osWriteLock(file, _RESERVED_BYTE, 1, 0)
} }
func osGetPendingLock(file *os.File, block bool) _ErrorCode { func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode {
var timeout time.Duration // A PENDING lock is needed before releasing the SHARED lock.
if block { if *state < LOCK_PENDING {
timeout = -1 // If we were RESERVED, we can block indefinitely.
} var timeout time.Duration
if *state == LOCK_RESERVED {
// Acquire the PENDING lock. timeout = -1
return osWriteLock(file, _PENDING_BYTE, 1, timeout) }
} if rc := osWriteLock(file, _PENDING_BYTE, 1, timeout); rc != _OK {
return rc
func osGetExclusiveLock(file *os.File, block bool) _ErrorCode { }
var timeout time.Duration *state = LOCK_PENDING
if block {
timeout = time.Millisecond
} }
// Release the SHARED lock. // Release the SHARED lock.
osUnlock(file, _SHARED_FIRST, _SHARED_SIZE) osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
// Acquire the EXCLUSIVE lock. // Acquire the EXCLUSIVE lock.
rc := osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout) rc := osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, time.Millisecond)
if rc != _OK { if rc != _OK {
// Reacquire the SHARED lock. // Reacquire the SHARED lock.
@ -64,9 +62,7 @@ func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode {
// Reacquire the SHARED lock. // Reacquire the SHARED lock.
if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK { if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK {
// This should never happen. // notest // this should never happen
// We should always be able to reacquire the read lock.
// notest
return _IOERR_RDLOCK return _IOERR_RDLOCK
} }
} }

View file

@ -1,4 +1,4 @@
//go:build (darwin || linux) && (amd64 || arm64 || riscv64) && !(sqlite3_flock || sqlite3_noshm || sqlite3_nosys) //go:build (darwin || linux) && (amd64 || arm64 || riscv64 || ppc64le) && !(sqlite3_flock || sqlite3_noshm || sqlite3_nosys)
package vfs package vfs
@ -70,7 +70,7 @@ func (s *vfsShm) shmOpen() _ErrorCode {
} }
// Dead man's switch. // Dead man's switch.
if lock, rc := osGetLock(s.File, _SHM_DMS, 1); rc != _OK { if lock, rc := osTestLock(s.File, _SHM_DMS, 1); rc != _OK {
return _IOERR_LOCK return _IOERR_LOCK
} else if lock == unix.F_WRLCK { } else if lock == unix.F_WRLCK {
return _BUSY return _BUSY

View file

@ -1,4 +1,4 @@
//go:build (freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) && (amd64 || arm64 || riscv64) && !(sqlite3_noshm || sqlite3_nosys) //go:build (freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) && (amd64 || arm64 || riscv64 || ppc64le) && !(sqlite3_noshm || sqlite3_nosys)
package vfs package vfs

View file

@ -1,4 +1,4 @@
//go:build !(darwin || linux || freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) || !(amd64 || arm64 || riscv64) || sqlite3_noshm || sqlite3_nosys //go:build !(darwin || linux || freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) || !(amd64 || arm64 || riscv64 || ppc64le) || sqlite3_noshm || sqlite3_nosys
package vfs package vfs

View file

@ -25,15 +25,18 @@
import ( import (
"errors" "errors"
"math"
cg "go.uber.org/automaxprocs/internal/cgroups" cg "go.uber.org/automaxprocs/internal/cgroups"
) )
// CPUQuotaToGOMAXPROCS converts the CPU quota applied to the calling process // CPUQuotaToGOMAXPROCS converts the CPU quota applied to the calling process
// to a valid GOMAXPROCS value. // to a valid GOMAXPROCS value. The quota is converted from float to int using round.
func CPUQuotaToGOMAXPROCS(minValue int) (int, CPUQuotaStatus, error) { // If round == nil, DefaultRoundFunc is used.
cgroups, err := newQueryer() func CPUQuotaToGOMAXPROCS(minValue int, round func(v float64) int) (int, CPUQuotaStatus, error) {
if round == nil {
round = DefaultRoundFunc
}
cgroups, err := _newQueryer()
if err != nil { if err != nil {
return -1, CPUQuotaUndefined, err return -1, CPUQuotaUndefined, err
} }
@ -43,7 +46,7 @@ func CPUQuotaToGOMAXPROCS(minValue int) (int, CPUQuotaStatus, error) {
return -1, CPUQuotaUndefined, err return -1, CPUQuotaUndefined, err
} }
maxProcs := int(math.Floor(quota)) maxProcs := round(quota)
if minValue > 0 && maxProcs < minValue { if minValue > 0 && maxProcs < minValue {
return minValue, CPUQuotaMinUsed, nil return minValue, CPUQuotaMinUsed, nil
} }
@ -57,6 +60,7 @@ type queryer interface {
var ( var (
_newCgroups2 = cg.NewCGroups2ForCurrentProcess _newCgroups2 = cg.NewCGroups2ForCurrentProcess
_newCgroups = cg.NewCGroupsForCurrentProcess _newCgroups = cg.NewCGroupsForCurrentProcess
_newQueryer = newQueryer
) )
func newQueryer() (queryer, error) { func newQueryer() (queryer, error) {

View file

@ -26,6 +26,6 @@
// CPUQuotaToGOMAXPROCS converts the CPU quota applied to the calling process // CPUQuotaToGOMAXPROCS converts the CPU quota applied to the calling process
// to a valid GOMAXPROCS value. This is Linux-specific and not supported in the // to a valid GOMAXPROCS value. This is Linux-specific and not supported in the
// current OS. // current OS.
func CPUQuotaToGOMAXPROCS(_ int) (int, CPUQuotaStatus, error) { func CPUQuotaToGOMAXPROCS(_ int, _ func(v float64) int) (int, CPUQuotaStatus, error) {
return -1, CPUQuotaUndefined, nil return -1, CPUQuotaUndefined, nil
} }

View file

@ -20,6 +20,8 @@
package runtime package runtime
import "math"
// CPUQuotaStatus presents the status of how CPU quota is used // CPUQuotaStatus presents the status of how CPU quota is used
type CPUQuotaStatus int type CPUQuotaStatus int
@ -31,3 +33,8 @@
// CPUQuotaMinUsed is returned when CPU quota is smaller than the min value // CPUQuotaMinUsed is returned when CPU quota is smaller than the min value
CPUQuotaMinUsed CPUQuotaMinUsed
) )
// DefaultRoundFunc is the default function to convert CPU quota from float to int. It rounds the value down (floor).
func DefaultRoundFunc(v float64) int {
return int(math.Floor(v))
}

View file

@ -37,9 +37,10 @@ func currentMaxProcs() int {
} }
type config struct { type config struct {
printf func(string, ...interface{}) printf func(string, ...interface{})
procs func(int) (int, iruntime.CPUQuotaStatus, error) procs func(int, func(v float64) int) (int, iruntime.CPUQuotaStatus, error)
minGOMAXPROCS int minGOMAXPROCS int
roundQuotaFunc func(v float64) int
} }
func (c *config) log(fmt string, args ...interface{}) { func (c *config) log(fmt string, args ...interface{}) {
@ -71,6 +72,13 @@ func Min(n int) Option {
}) })
} }
// RoundQuotaFunc sets the function that will be used to covert the CPU quota from float to int.
func RoundQuotaFunc(rf func(v float64) int) Option {
return optionFunc(func(cfg *config) {
cfg.roundQuotaFunc = rf
})
}
type optionFunc func(*config) type optionFunc func(*config)
func (of optionFunc) apply(cfg *config) { of(cfg) } func (of optionFunc) apply(cfg *config) { of(cfg) }
@ -82,8 +90,9 @@ func (of optionFunc) apply(cfg *config) { of(cfg) }
// configured CPU quota. // configured CPU quota.
func Set(opts ...Option) (func(), error) { func Set(opts ...Option) (func(), error) {
cfg := &config{ cfg := &config{
procs: iruntime.CPUQuotaToGOMAXPROCS, procs: iruntime.CPUQuotaToGOMAXPROCS,
minGOMAXPROCS: 1, roundQuotaFunc: iruntime.DefaultRoundFunc,
minGOMAXPROCS: 1,
} }
for _, o := range opts { for _, o := range opts {
o.apply(cfg) o.apply(cfg)
@ -102,7 +111,7 @@ func Set(opts ...Option) (func(), error) {
return undoNoop, nil return undoNoop, nil
} }
maxProcs, status, err := cfg.procs(cfg.minGOMAXPROCS) maxProcs, status, err := cfg.procs(cfg.minGOMAXPROCS, cfg.roundQuotaFunc)
if err != nil { if err != nil {
return undoNoop, err return undoNoop, err
} }

View file

@ -21,4 +21,4 @@
package maxprocs package maxprocs
// Version is the current package version. // Version is the current package version.
const Version = "1.5.2" const Version = "1.6.0"

6
vendor/modules.txt vendored
View file

@ -517,7 +517,7 @@ github.com/modern-go/reflect2
# github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 # github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822
## explicit ## explicit
github.com/munnerz/goautoneg github.com/munnerz/goautoneg
# github.com/ncruces/go-sqlite3 v0.18.3 # github.com/ncruces/go-sqlite3 v0.18.4
## explicit; go 1.21 ## explicit; go 1.21
github.com/ncruces/go-sqlite3 github.com/ncruces/go-sqlite3
github.com/ncruces/go-sqlite3/driver github.com/ncruces/go-sqlite3/driver
@ -1046,8 +1046,8 @@ go.opentelemetry.io/proto/otlp/collector/trace/v1
go.opentelemetry.io/proto/otlp/common/v1 go.opentelemetry.io/proto/otlp/common/v1
go.opentelemetry.io/proto/otlp/resource/v1 go.opentelemetry.io/proto/otlp/resource/v1
go.opentelemetry.io/proto/otlp/trace/v1 go.opentelemetry.io/proto/otlp/trace/v1
# go.uber.org/automaxprocs v1.5.3 # go.uber.org/automaxprocs v1.6.0
## explicit; go 1.18 ## explicit; go 1.20
go.uber.org/automaxprocs/internal/cgroups go.uber.org/automaxprocs/internal/cgroups
go.uber.org/automaxprocs/internal/runtime go.uber.org/automaxprocs/internal/runtime
go.uber.org/automaxprocs/maxprocs go.uber.org/automaxprocs/maxprocs