Repository: rclone/rclone
Pull request #6389 (2022-08-20 14:43:59)

Replace deprecated ioutil
#### What is the purpose of this change?

As of Go 1.16, the same functionality is now provided by package io or package os, and those implementations should be preferred in new code.

TLDR;

Most changes are simple search-replace of ioutil function call to identical io or os function call. The only exception is ReadDir:
- ioutil had ReadDir returning FileInfo
- os has ReadDir returning DirEntry

The only place where this lead to some extra thought was regarding the Oser interface in rclone. It has a ReadDir function which for the realOs implementation was one-liner mapping to ioutil.ReadDir. I landed on the smallest possible change: Changing implementation of the realOs.ReadDir to use os.ReadDir and convert from DirEntry to FileInfo, as suggested in the osutil.ReadDir documentation. I.e. as little change to rclone code as possible, I think. But there is some mismatch with the standard package the Oser interface is emulating(?) (though there was before as well, since it used ioutil, and not os).

#### Was the change discussed in an issue or in the forum before?

<!--
Link issues and relevant forum posts here.
-->

#### Checklist

- [X] I have read the [contribution guidelines](https://github.com/rclone/rclone/blob/master/CONTRIBUTING.md#submitting-a-new-feature-or-bug-fix).
- [ ] I have added tests for all changes in this PR if appropriate.
- [ ] I have added documentation for the changes if appropriate.
- [ ] All commit messages are in [house style](https://github.com/rclone/rclone/blob/master/CONTRIBUTING.md#commit-messages).
- [x] I'm done, this Pull Request is ready for review :-)



Patch:
diff --git a/backend/azureblob/azureblob.go b/backend/azureblob/azureblob.go
index 0db359222..e9402ad0e 100644
--- a/backend/azureblob/azureblob.go
+++ b/backend/azureblob/azureblob.go
@@ -12,9 +12,9 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"net/url"
+	"os"
 	"path"
 	"strconv"
 	"strings"
@@ -717,7 +717,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
 			return nil, fmt.Errorf("failed to make azure storage url from account and endpoint: %w", err)
 		}
 		// Try loading service principal credentials from file.
-		loadedCreds, err := ioutil.ReadFile(env.ShellExpand(opt.ServicePrincipalFile))
+		loadedCreds, err := os.ReadFile(env.ShellExpand(opt.ServicePrincipalFile))
 		if err != nil {
 			return nil, fmt.Errorf("error opening service principal credentials file: %w", err)
 		}
diff --git a/backend/azureblob/imds.go b/backend/azureblob/imds.go
index 84c6379e6..889e224db 100644
--- a/backend/azureblob/imds.go
+++ b/backend/azureblob/imds.go
@@ -9,7 +9,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 
 	"github.com/Azure/go-autorest/autorest/adal"
@@ -97,7 +96,7 @@ func GetMSIToken(ctx context.Context, identity *userMSI) (adal.Token, error) {
 		return result, fmt.Errorf("MSI is not enabled on this VM: %w", err)
 	}
 	defer func() { // resp and Body should not be nil
-		_, err = io.Copy(ioutil.Discard, resp.Body)
+		_, err = io.Copy(io.Discard, resp.Body)
 		if err != nil {
 			fs.Debugf(nil, "Unable to drain IMDS response: %v", err)
 		}
@@ -112,12 +111,12 @@ func GetMSIToken(ctx context.Context, identity *userMSI) (adal.Token, error) {
 	case 200, 201, 202:
 		break
 	default:
-		body, _ := ioutil.ReadAll(resp.Body)
+		body, _ := io.ReadAll(resp.Body)
 		fs.Errorf(nil, "Couldn't obtain OAuth token from IMDS; server returned status code %d and body: %v", resp.StatusCode, string(body))
 		return result, httpError{Response: resp}
 	}
 
-	b, err := ioutil.ReadAll(resp.Body)
+	b, err := io.ReadAll(resp.Body)
 	if err != nil {
 		return result, fmt.Errorf("couldn't read IMDS response: %w", err)
 	}
diff --git a/backend/box/box.go b/backend/box/box.go
index a6a33efdb..e0cfbed07 100644
--- a/backend/box/box.go
+++ b/backend/box/box.go
@@ -17,9 +17,9 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"net/url"
+	"os"
 	"path"
 	"strconv"
 	"strings"
@@ -183,7 +183,7 @@ func refreshJWTToken(ctx context.Context, jsonFile string, boxSubType string, na
 }
 
 func getBoxConfig(configFile string) (boxConfig *api.ConfigJSON, err error) {
-	file, err := ioutil.ReadFile(configFile)
+	file, err := os.ReadFile(configFile)
 	if err != nil {
 		return nil, fmt.Errorf("box: failed to read Box config: %w", err)
 	}
diff --git a/backend/cache/plex.go b/backend/cache/plex.go
index 09ea47233..8d57ecb17 100644
--- a/backend/cache/plex.go
+++ b/backend/cache/plex.go
@@ -8,7 +8,7 @@ import (
 	"crypto/tls"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"net/url"
 	"strings"
@@ -167,7 +167,7 @@ func (p *plexConnector) listenWebsocket() {
 								continue
 							}
 							var data []byte
-							data, err = ioutil.ReadAll(resp.Body)
+							data, err = io.ReadAll(resp.Body)
 							if err != nil {
 								continue
 							}
diff --git a/backend/cache/storage_persistent.go b/backend/cache/storage_persistent.go
index eb36a0806..ac8fdfd57 100644
--- a/backend/cache/storage_persistent.go
+++ b/backend/cache/storage_persistent.go
@@ -9,7 +9,6 @@ import (
 	"encoding/binary"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path"
 	"strconv"
@@ -473,7 +472,7 @@ func (b *Persistent) GetChunk(cachedObject *Object, offset int64) ([]byte, error
 	var data []byte
 
 	fp := path.Join(b.dataPath, cachedObject.abs(), strconv.FormatInt(offset, 10))
-	data, err := ioutil.ReadFile(fp)
+	data, err := os.ReadFile(fp)
 	if err != nil {
 		return nil, err
 	}
@@ -486,7 +485,7 @@ func (b *Persistent) AddChunk(fp string, data []byte, offset int64) error {
 	_ = os.MkdirAll(path.Join(b.dataPath, fp), os.ModePerm)
 
 	filePath := path.Join(b.dataPath, fp, strconv.FormatInt(offset, 10))
-	err := ioutil.WriteFile(filePath, data, os.ModePerm)
+	err := os.WriteFile(filePath, data, os.ModePerm)
 	if err != nil {
 		return err
 	}
diff --git a/backend/chunker/chunker.go b/backend/chunker/chunker.go
index 17da9393c..bb4b49812 100644
--- a/backend/chunker/chunker.go
+++ b/backend/chunker/chunker.go
@@ -12,7 +12,6 @@ import (
 	"fmt"
 	gohash "hash"
 	"io"
-	"io/ioutil"
 	"math/rand"
 	"path"
 	"regexp"
@@ -1038,7 +1037,7 @@ func (o *Object) readMetadata(ctx context.Context) error {
 	if err != nil {
 		return err
 	}
-	metadata, err := ioutil.ReadAll(reader)
+	metadata, err := io.ReadAll(reader)
 	_ = reader.Close() // ensure file handle is freed on windows
 	if err != nil {
 		return err
@@ -1097,7 +1096,7 @@ func (o *Object) readXactID(ctx context.Context) (xactID string, err error) {
 	if err != nil {
 		return "", err
 	}
-	data, err := ioutil.ReadAll(reader)
+	data, err := io.ReadAll(reader)
 	_ = reader.Close() // ensure file handle is freed on windows
 	if err != nil {
 		return "", err
diff --git a/backend/compress/compress.go b/backend/compress/compress.go
index 6bfe1210c..21ab99c03 100644
--- a/backend/compress/compress.go
+++ b/backend/compress/compress.go
@@ -13,7 +13,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
 	"regexp"
 	"strings"
@@ -468,7 +467,7 @@ func (f *Fs) rcat(ctx context.Context, dstFileName string, in io.ReadCloser, mod
 	}
 
 	fs.Debugf(f, "Target remote doesn't support streaming uploads, creating temporary local file")
-	tempFile, err := ioutil.TempFile("", "rclone-press-")
+	tempFile, err := os.CreateTemp("", "rclone-press-")
 	defer func() {
 		// these errors should be relatively uncritical and the upload should've succeeded so it's okay-ish
 		// to ignore them
@@ -546,8 +545,8 @@ func (f *Fs) putCompress(ctx context.Context, in io.Reader, src fs.ObjectInfo, o
 	}
 
 	// Transfer the data
-	o, err := f.rcat(ctx, makeDataName(src.Remote(), src.Size(), f.mode), ioutil.NopCloser(wrappedIn), src.ModTime(ctx), options)
-	//o, err := operations.Rcat(ctx, f.Fs, makeDataName(src.Remote(), src.Size(), f.mode), ioutil.NopCloser(wrappedIn), src.ModTime(ctx))
+	o, err := f.rcat(ctx, makeDataName(src.Remote(), src.Size(), f.mode), io.NopCloser(wrappedIn), src.ModTime(ctx), options)
+	//o, err := operations.Rcat(ctx, f.Fs, makeDataName(src.Remote(), src.Size(), f.mode), io.NopCloser(wrappedIn), src.ModTime(ctx))
 	if err != nil {
 		if o != nil {
 			removeErr := o.Remove(ctx)
diff --git a/backend/drive/drive.go b/backend/drive/drive.go
index e0b4abb61..cca4245c3 100644
--- a/backend/drive/drive.go
+++ b/backend/drive/drive.go
@@ -14,9 +14,9 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"mime"
 	"net/http"
+	"os"
 	"path"
 	"regexp"
 	"sort"
@@ -1108,7 +1108,7 @@ func createOAuthClient(ctx context.Context, opt *Options, name string, m configm
 
 	// try loading service account credentials from env variable, then from a file
 	if len(opt.ServiceAccountCredentials) == 0 && opt.ServiceAccountFile != "" {
-		loadedCreds, err := ioutil.ReadFile(env.ShellExpand(opt.ServiceAccountFile))
+		loadedCreds, err := os.ReadFile(env.ShellExpand(opt.ServiceAccountFile))
 		if err != nil {
 			return nil, fmt.Errorf("error opening service account credentials file: %w", err)
 		}
@@ -3800,7 +3800,7 @@ func (o *linkObject) Open(ctx context.Context, options ...fs.OpenOption) (in io.
 		data = data[:limit]
 	}
 
-	return ioutil.NopCloser(bytes.NewReader(data)), nil
+	return io.NopCloser(bytes.NewReader(data)), nil
 }
 
 func (o *baseObject) update(ctx context.Context, updateInfo *drive.File, uploadMimeType string, in io.Reader,
diff --git a/backend/filefabric/filefabric.go b/backend/filefabric/filefabric.go
index 7442814c5..3d157f8fb 100644
--- a/backend/filefabric/filefabric.go
+++ b/backend/filefabric/filefabric.go
@@ -20,7 +20,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"net/url"
 	"path"
@@ -1186,7 +1185,7 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.Read
 		return nil, errors.New("can't download - no id")
 	}
 	if o.contentType == emptyMimeType {
-		return ioutil.NopCloser(bytes.NewReader([]byte{})), nil
+		return io.NopCloser(bytes.NewReader([]byte{})), nil
 	}
 	fs.FixRangeOption(options, o.size)
 	resp, err := o.fs.rpc(ctx, "getFile", params{
diff --git a/backend/googlecloudstorage/googlecloudstorage.go b/backend/googlecloudstorage/googlecloudstorage.go
index dc233bfcd..f984fa79d 100644
--- a/backend/googlecloudstorage/googlecloudstorage.go
+++ b/backend/googlecloudstorage/googlecloudstorage.go
@@ -19,8 +19,8 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
+	"os"
 	"path"
 	"strconv"
 	"strings"
@@ -487,7 +487,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
 
 	// try loading service account credentials from env variable, then from a file
 	if opt.ServiceAccountCredentials == "" && opt.ServiceAccountFile != "" {
-		loadedCreds, err := ioutil.ReadFile(env.ShellExpand(opt.ServiceAccountFile))
+		loadedCreds, err := os.ReadFile(env.ShellExpand(opt.ServiceAccountFile))
 		if err != nil {
 			return nil, fmt.Errorf("error opening service account credentials file: %w", err)
 		}
diff --git a/backend/hasher/object.go b/backend/hasher/object.go
index 6b9a8e4d0..ef6349b0f 100644
--- a/backend/hasher/object.go
+++ b/backend/hasher/object.go
@@ -5,7 +5,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"path"
 	"time"
 
@@ -118,7 +117,7 @@ func (o *Object) updateHashes(ctx context.Context) error {
 	defer func() {
 		_ = r.Close()
 	}()
-	if _, err = io.Copy(ioutil.Discard, r); err != nil {
+	if _, err = io.Copy(io.Discard, r); err != nil {
 		fs.Infof(o, "update failed (copy): %v", err)
 		return err
 	}
diff --git a/backend/jottacloud/jottacloud.go b/backend/jottacloud/jottacloud.go
index 45e8489ea..6eb76d1ef 100644
--- a/backend/jottacloud/jottacloud.go
+++ b/backend/jottacloud/jottacloud.go
@@ -12,7 +12,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"math/rand"
 	"net/http"
 	"net/url"
@@ -822,7 +821,7 @@ func (f *Fs) allocatePathRaw(file string, absolute bool) string {
 func grantTypeFilter(req *http.Request) {
 	if legacyTokenURL == req.URL.String() {
 		// read the entire body
-		refreshBody, err := ioutil.ReadAll(req.Body)
+		refreshBody, err := io.ReadAll(req.Body)
 		if err != nil {
 			return
 		}
@@ -832,7 +831,7 @@ func grantTypeFilter(req *http.Request) {
 		refreshBody = []byte(strings.Replace(string(refreshBody), "grant_type=refresh_token", "grant_type=REFRESH_TOKEN", 1))
 
 		// set the new ReadCloser (with a dummy Close())
-		req.Body = ioutil.NopCloser(bytes.NewReader(refreshBody))
+		req.Body = io.NopCloser(bytes.NewReader(refreshBody))
 	}
 }
 
@@ -1789,7 +1788,7 @@ func readMD5(in io.Reader, size, threshold int64) (md5sum string, out io.Reader,
 		var tempFile *os.File
 
 		// create the cache file
-		tempFile, err = ioutil.TempFile("", cachePrefix)
+		tempFile, err = os.CreateTemp("", cachePrefix)
 		if err != nil {
 			return
 		}
@@ -1817,7 +1816,7 @@ func readMD5(in io.Reader, size, threshold int64) (md5sum string, out io.Reader,
 	} else {
 		// that's a small file, just read it into memory
 		var inData []byte
-		inData, err = ioutil.ReadAll(teeReader)
+		inData, err = io.ReadAll(teeReader)
 		if err != nil {
 			return
 		}
@@ -1914,7 +1913,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
 
 		// copy the already uploaded bytes into the trash :)
 		var result api.UploadResponse
-		_, err = io.CopyN(ioutil.Discard, in, response.ResumePos)
+		_, err = io.CopyN(io.Discard, in, response.ResumePos)
 		if err != nil {
 			return err
 		}
diff --git a/backend/local/local.go b/backend/local/local.go
index 654bb02d1..8d16ad4a8 100644
--- a/backend/local/local.go
+++ b/backend/local/local.go
@@ -7,7 +7,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
 	"path"
 	"path/filepath"
@@ -124,8 +123,8 @@ routine so this flag shouldn't normally be used.`,
 			Help: `Don't check to see if the files change during upload.
 
 Normally rclone checks the size and modification time of files as they
-are being uploaded and aborts with a message which starts "can't copy
-- source file is being updated" if the file changes during upload.
+are being uploaded and aborts with a message which starts "can't copy -
+source file is being updated" if the file changes during upload.
 
 However on some file systems this modification time check may fail (e.g.
 [Glusterfs #2206](https://github.com/rclone/rclone/issues/2206)) so this
@@ -646,7 +645,7 @@ func (f *Fs) readPrecision() (precision time.Duration) {
 	precision = time.Second
 
 	// Create temporary file and test it
-	fd, err := ioutil.TempFile("", "rclone")
+	fd, err := os.CreateTemp("", "rclone")
 	if err != nil {
 		// If failed return 1s
 		// fmt.Println("Failed to create temp file", err)
@@ -1073,7 +1072,7 @@ func (o *Object) openTranslatedLink(offset, limit int64) (lrc io.ReadCloser, err
 	if err != nil {
 		return nil, err
 	}
-	return readers.NewLimitedReadCloser(ioutil.NopCloser(strings.NewReader(linkdst[offset:])), limit), nil
+	return readers.NewLimitedReadCloser(io.NopCloser(strings.NewReader(linkdst[offset:])), limit), nil
 }
 
 // Open an object for read
diff --git a/backend/mailru/mailru.go b/backend/mailru/mailru.go
index 95507996c..5a630a2d3 100644
--- a/backend/mailru/mailru.go
+++ b/backend/mailru/mailru.go
@@ -18,7 +18,6 @@ import (
 
 	"encoding/hex"
 	"encoding/json"
-	"io/ioutil"
 	"net/http"
 	"net/url"
 
@@ -1660,7 +1659,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
 
 	// Attempt to put by calculating hash in memory
 	if trySpeedup && size <= int64(o.fs.opt.SpeedupMaxMem) {
-		fileBuf, err = ioutil.ReadAll(in)
+		fileBuf, err = io.ReadAll(in)
 		if err != nil {
 			return err
 		}
@@ -1703,7 +1702,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
 	if size <= mrhash.Size {
 		// Optimize upload: skip extra request if data fits in the hash buffer.
 		if fileBuf == nil {
-			fileBuf, err = ioutil.ReadAll(wrapIn)
+			fileBuf, err = io.ReadAll(wrapIn)
 		}
 		if fileHash == nil && err == nil {
 			fileHash = mrhash.Sum(fileBuf)
@@ -2214,7 +2213,7 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.Read
 		fs.Debugf(o, "Server returned full content instead of range")
 		if start > 0 {
 			// Discard the beginning of the data
-			_, err = io.CopyN(ioutil.Discard, wrapStream, start)
+			_, err = io.CopyN(io.Discard, wrapStream, start)
 			if err != nil {
 				closeBody(res)
 				return nil, err
diff --git a/backend/memory/memory.go b/backend/memory/memory.go
index 838a060d5..80fc321f9 100644
--- a/backend/memory/memory.go
+++ b/backend/memory/memory.go
@@ -8,7 +8,6 @@ import (
 	"encoding/hex"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"path"
 	"strings"
 	"sync"
@@ -575,7 +574,7 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.Read
 		}
 		data = data[:limit]
 	}
-	return ioutil.NopCloser(bytes.NewBuffer(data)), nil
+	return io.NopCloser(bytes.NewBuffer(data)), nil
 }
 
 // Update the object with the contents of the io.Reader, modTime and size
@@ -583,7 +582,7 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.Read
 // The new object may have been created if an error is returned
 func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (err error) {
 	bucket, bucketPath := o.split()
-	data, err := ioutil.ReadAll(in)
+	data, err := io.ReadAll(in)
 	if err != nil {
 		return fmt.Errorf("failed to update memory object: %w", err)
 	}
diff --git a/backend/netstorage/netstorage.go b/backend/netstorage/netstorage.go
index 31adcbe7a..20a6401ab 100755
--- a/backend/netstorage/netstorage.go
+++ b/backend/netstorage/netstorage.go
@@ -12,7 +12,6 @@ import (
 	"fmt"
 	gohash "hash"
 	"io"
-	"io/ioutil"
 	"math/rand"
 	"net/http"
 	"net/url"
@@ -972,7 +971,7 @@ func (o *Object) netStorageUploadRequest(ctx context.Context, in io.Reader, src
 		URL = o.fs.url(src.Remote())
 	}
 	if strings.HasSuffix(URL, ".rclonelink") {
-		bits, err := ioutil.ReadAll(in)
+		bits, err := io.ReadAll(in)
 		if err != nil {
 			return err
 		}
@@ -1058,7 +1057,7 @@ func (o *Object) netStorageDownloadRequest(ctx context.Context, options []fs.Ope
 	if strings.HasSuffix(URL, ".rclonelink") && o.target != "" {
 		fs.Infof(nil, "Converting a symlink to the rclonelink file on download %q", URL)
 		reader := strings.NewReader(o.target)
-		readcloser := ioutil.NopCloser(reader)
+		readcloser := io.NopCloser(reader)
 		return readcloser, nil
 	}
 
diff --git a/backend/s3/s3.go b/backend/s3/s3.go
index c13a78a37..efea72410 100644
--- a/backend/s3/s3.go
+++ b/backend/s3/s3.go
@@ -15,7 +15,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"net/url"
 	"path"
@@ -1539,7 +1538,11 @@ This ACL is used for creating objects and if bucket_acl isn't set, for creating
 For more info visit https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl
 
 Note that this ACL is applied when server-side copying objects as S3
-doesn't copy the ACL from the source but rather writes a fresh one.`,
+doesn't copy the ACL from the source but rather writes a fresh one.
+
+If the acl is an empty string then no X-Amz-Acl: header is added and
+the default (private) will be used.
+`,
 			Provider: "!Storj,Cloudflare",
 			Examples: []fs.OptionExample{{
 				Value:    "default",
@@ -1593,7 +1596,11 @@ doesn't copy the ACL from the source but rather writes a fresh one.`,
 For more info visit https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl
 
 Note that this ACL is applied when only when creating buckets.  If it
-isn't set then "acl" is used instead.`,
+isn't set then "acl" is used instead.
+
+If the "acl" and "bucket_acl" are empty strings then no X-Amz-Acl:
+header is added and the default (private) will be used.
+`,
 			Advanced: true,
 			Examples: []fs.OptionExample{{
 				Value: "private",
@@ -2166,6 +2173,31 @@ can't check the size and hash but the file contents will be decompressed.
 `,
 			Advanced: true,
 			Default:  false,
+		}, {
+			Name: "might_gzip",
+			Help: strings.ReplaceAll(`Set this if the backend might gzip objects.
+
+Normally providers will not alter objects when they are downloaded. If
+an object was not uploaded with |Content-Encoding: gzip| then it won't
+be set on download.
+
+However some providers may gzip objects even if they weren't uploaded
+with |Content-Encoding: gzip| (eg Cloudflare).
+
+A symptom of this would be receiving errors like
+
+    ERROR corrupted on transfer: sizes differ NNN vs MMM
+
+If you set this flag and rclone downloads an object with
+Content-Encoding: gzip set and chunked transfer encoding, then rclone
+will decompress the object on the fly.
+
+If this is set to unset (the default) then rclone will choose
+according to the provider setting what to apply, but you can override
+rclone's choice here.
+`, "|", "`"),
+			Default:  fs.Tristate{},
+			Advanced: true,
 		}, {
 			Name:     "no_system_metadata",
 			Help:     `Suppress setting and reading of system metadata`,
@@ -2297,6 +2329,7 @@ type Options struct {
 	Versions              bool                 `config:"versions"`
 	VersionAt             fs.Time              `config:"version_at"`
 	Decompress            bool                 `config:"decompress"`
+	MightGzip             fs.Tristate          `config:"might_gzip"`
 	NoSystemMetadata      bool                 `config:"no_system_metadata"`
 }
 
@@ -2657,10 +2690,12 @@ func setQuirks(opt *Options) {
 		virtualHostStyle  = true
 		urlEncodeListings = true
 		useMultipartEtag  = true
+		mightGzip         = true // assume all providers might gzip until proven otherwise
 	)
 	switch opt.Provider {
 	case "AWS":
 		// No quirks
+		mightGzip = false // Never auto gzips objects
 	case "Alibaba":
 		useMultipartEtag = false // Alibaba seems to calculate multipart Etags differently from AWS
 	case "HuaweiOBS":
@@ -2774,6 +2809,12 @@ func setQuirks(opt *Options) {
 		opt.UseMultipartEtag.Valid = true
 		opt.UseMultipartEtag.Value = useMultipartEtag
 	}
+
+	// set MightGzip if not manually set
+	if !opt.MightGzip.Valid {
+		opt.MightGzip.Valid = true
+		opt.MightGzip.Value = mightGzip
+	}
 }
 
 // setRoot changes the root of the Fs
@@ -2782,6 +2823,14 @@ func (f *Fs) setRoot(root string) {
 	f.rootBucket, f.rootDirectory = bucket.Split(f.root)
 }
 
+// return a pointer to the string if non empty or nil if it is empty
+func stringPointerOrNil(s string) *string {
+	if s == "" {
+		return nil
+	}
+	return &s
+}
+
 // NewFs constructs an Fs from the path, bucket:path
 func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, error) {
 	// Parse config into Options struct
@@ -2801,9 +2850,6 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
 	if opt.Versions && opt.VersionAt.IsSet() {
 		return nil, errors.New("s3: cant use --s3-versions and --s3-version-at at the same time")
 	}
-	if opt.ACL == "" {
-		opt.ACL = "private"
-	}
 	if opt.BucketACL == "" {
 		opt.BucketACL = opt.ACL
 	}
@@ -3672,7 +3718,7 @@ func (f *Fs) makeBucket(ctx context.Context, bucket string) error {
 	return f.cache.Create(bucket, func() error {
 		req := s3.CreateBucketInput{
 			Bucket: &bucket,
-			ACL:    &f.opt.BucketACL,
+			ACL:    stringPointerOrNil(f.opt.BucketACL),
 		}
 		if f.opt.LocationConstraint != "" {
 			req.CreateBucketConfiguration = &s3.CreateBucketConfiguration{
@@ -3737,7 +3783,7 @@ func pathEscape(s string) string {
 // method
 func (f *Fs) copy(ctx context.Context, req *s3.CopyObjectInput, dstBucket, dstPath, srcBucket, srcPath string, src *Object) error {
 	req.Bucket = &dstBucket
-	req.ACL = &f.opt.ACL
+	req.ACL = stringPointerOrNil(f.opt.ACL)
 	req.Key = &dstPath
 	source := pathEscape(path.Join(srcBucket, srcPath))
 	if src.versionID != nil {
@@ -4854,7 +4900,7 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.Read
 
 	// Decompress body if necessary
 	if aws.StringValue(resp.ContentEncoding) == "gzip" {
-		if o.fs.opt.Decompress {
+		if o.fs.opt.Decompress || (resp.ContentLength == nil && o.fs.opt.MightGzip.Value) {
 			return readers.NewGzipReader(resp.Body)
 		}
 		o.fs.warnCompressed.Do(func() {
@@ -5101,7 +5147,7 @@ func (o *Object) uploadSinglepartPutObject(ctx context.Context, req *s3.PutObjec
 		// Can't upload zero length files like this for some reason
 		r.Body = bytes.NewReader([]byte{})
 	} else {
-		r.SetStreamingBody(ioutil.NopCloser(in))
+		r.SetStreamingBody(io.NopCloser(in))
 	}
 	r.SetContext(ctx)
 	r.HTTPRequest.Header.Set("X-Amz-Content-Sha256", "UNSIGNED-PAYLOAD")
@@ -5215,7 +5261,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
 
 	req := s3.PutObjectInput{
 		Bucket: &bucket,
-		ACL:    &o.fs.opt.ACL,
+		ACL:    stringPointerOrNil(o.fs.opt.ACL),
 		Key:    &bucketPath,
 	}
 
diff --git a/backend/seafile/webapi.go b/backend/seafile/webapi.go
index c1eb33da0..cf3e77bda 100644
--- a/backend/seafile/webapi.go
+++ b/backend/seafile/webapi.go
@@ -6,7 +6,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"net/url"
 	"path"
@@ -633,7 +632,7 @@ func (f *Fs) download(ctx context.Context, url string, size int64, options ...fs
 		})
 		if start > 0 {
 			// We need to read and discard the beginning of the data...
-			_, err = io.CopyN(ioutil.Discard, resp.Body, start)
+			_, err = io.CopyN(io.Discard, resp.Body, start)
 			if err != nil {
 				return nil, err
 			}
diff --git a/backend/sftp/sftp.go b/backend/sftp/sftp.go
index 19596e59d..b6afcd355 100644
--- a/backend/sftp/sftp.go
+++ b/backend/sftp/sftp.go
@@ -10,7 +10,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
 	"path"
 	"regexp"
@@ -123,7 +122,10 @@ This enables the use of the following insecure ciphers and key exchange methods:
 - diffie-hellman-group-exchange-sha256
 - diffie-hellman-group-exchange-sha1
 
-Those algorithms are insecure and may allow plaintext data to be recovered by an attacker.`,
+Those algorithms are insecure and may allow plaintext data to be recovered by an attacker.
+
+This must be false if you use either ciphers or key_exchange advanced options.
+`,
 			Default: false,
 			Examples: []fs.OptionExample{
 				{
@@ -325,6 +327,46 @@ and pass variables with spaces in in quotes, eg
 
     "VAR3=value with space" "VAR4=value with space" VAR5=nospacehere
 
+`,
+			Advanced: true,
+		}, {
+			Name:    "ciphers",
+			Default: fs.SpaceSepList{},
+			Help: `Space separated list of ciphers to be used for session encryption, ordered by preference.
+
+At least one must match with server configuration. This can be checked for example using ssh -Q cipher.
+
+This must not be set if use_insecure_cipher is true.
+
+Example:
+
+    aes128-ctr aes192-ctr aes256-ctr aes128-gcm@openssh.com aes256-gcm@openssh.com
+`,
+			Advanced: true,
+		}, {
+			Name:    "key_exchange",
+			Default: fs.SpaceSepList{},
+			Help: `Space separated list of key exchange algorithms, ordered by preference.
+
+At least one must match with server configuration. This can be checked for example using ssh -Q kex.
+
+This must not be set if use_insecure_cipher is true.
+
+Example:
+
+    sntrup761x25519-sha512@openssh.com curve25519-sha256 curve25519-sha256@libssh.org ecdh-sha2-nistp256
+`,
+			Advanced: true,
+		}, {
+			Name:    "macs",
+			Default: fs.SpaceSepList{},
+			Help: `Space separated list of MACs (message authentication code) algorithms, ordered by preference.
+
+At least one must match with server configuration. This can be checked for example using ssh -Q mac.
+
+Example:
+
+    umac-64-etm@openssh.com umac-128-etm@openssh.com hmac-sha2-256-etm@openssh.com
 `,
 			Advanced: true,
 		}},
@@ -362,6 +404,9 @@ type Options struct {
 	ChunkSize               fs.SizeSuffix   `config:"chunk_size"`
 	Concurrency             int             `config:"concurrency"`
 	SetEnv                  fs.SpaceSepList `config:"set_env"`
+	Ciphers                 fs.SpaceSepList `config:"ciphers"`
+	KeyExchange             fs.SpaceSepList `config:"key_exchange"`
+	MACs                    fs.SpaceSepList `config:"macs"`
 }
 
 // Fs stores the interface to the remote SFTP files
@@ -702,10 +747,25 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
 		sshConfig.HostKeyCallback = hostcallback
 	}
 
+	if opt.UseInsecureCipher && (opt.Ciphers != nil || opt.KeyExchange != nil) {
+		return nil, fmt.Errorf("use_insecure_cipher must be false if ciphers or key_exchange are set in advanced configuration")
+	}
+
+	sshConfig.Config.SetDefaults()
 	if opt.UseInsecureCipher {
-		sshConfig.Config.SetDefaults()
 		sshConfig.Config.Ciphers = append(sshConfig.Config.Ciphers, "aes128-cbc", "aes192-cbc", "aes256-cbc", "3des-cbc")
 		sshConfig.Config.KeyExchanges = append(sshConfig.Config.KeyExchanges, "diffie-hellman-group-exchange-sha1", "diffie-hellman-group-exchange-sha256")
+	} else {
+		if opt.Ciphers != nil {
+			sshConfig.Config.Ciphers = opt.Ciphers
+		}
+		if opt.KeyExchange != nil {
+			sshConfig.Config.KeyExchanges = opt.KeyExchange
+		}
+	}
+
+	if opt.MACs != nil {
+		sshConfig.Config.MACs = opt.MACs
 	}
 
 	keyFile := env.ShellExpand(opt.KeyFile)
@@ -722,7 +782,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
 			return nil, fmt.Errorf("couldn't read ssh agent signers: %w", err)
 		}
 		if keyFile != "" {
-			pubBytes, err := ioutil.ReadFile(keyFile + ".pub")
+			pubBytes, err := os.ReadFile(keyFile + ".pub")
 			if err != nil {
 				return nil, fmt.Errorf("failed to read public key file: %w", err)
 			}
@@ -751,7 +811,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
 	if keyFile != "" || opt.KeyPem != "" {
 		var key []byte
 		if opt.KeyPem == "" {
-			key, err = ioutil.ReadFile(keyFile)
+			key, err = os.ReadFile(keyFile)
 			if err != nil {
 				return nil, fmt.Errorf("failed to read private key file: %w", err)
 			}
@@ -782,7 +842,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
 
 		// If a public key has been specified then use that
 		if pubkeyFile != "" {
-			certfile, err := ioutil.ReadFile(pubkeyFile)
+			certfile, err := os.ReadFile(pubkeyFile)
 			if err != nil {
 				return nil, fmt.Errorf("unable to read cert file: %w", err)
 			}
diff --git a/backend/sharefile/sharefile.go b/backend/sharefile/sharefile.go
index acb5d65c8..11bbefa6a 100644
--- a/backend/sharefile/sharefile.go
+++ b/backend/sharefile/sharefile.go
@@ -77,7 +77,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"net/url"
 	"path"
@@ -479,7 +478,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
 	if err != nil {
 		return nil, fmt.Errorf("failed to open timezone db: %w", err)
 	}
-	tzdata, err := ioutil.ReadAll(timezone)
+	tzdata, err := io.ReadAll(timezone)
 	if err != nil {
 		return nil, fmt.Errorf("failed to read timezone: %w", err)
 	}
diff --git a/backend/sharefile/tzdata_vfsdata.go b/backend/sharefile/tzdata_vfsdata.go
index 601d82d45..23d2120f9 100644
--- a/backend/sharefile/tzdata_vfsdata.go
+++ b/backend/sharefile/tzdata_vfsdata.go
@@ -10,7 +10,6 @@ import (
 	"compress/gzip"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"os"
 	pathpkg "path"
@@ -119,7 +118,7 @@ func (f *vfsgen۰CompressedFile) Read(p []byte) (n int, err error) {
 	}
 	if f.grPos < f.seekPos {
 		// Fast-forward.
-		_, err = io.CopyN(ioutil.Discard, f.gr, f.seekPos-f.grPos)
+		_, err = io.CopyN(io.Discard, f.gr, f.seekPos-f.grPos)
 		if err != nil {
 			return 0, err
 		}
diff --git a/backend/union/entry.go b/backend/union/entry.go
index 023f3bb45..d794d6226 100644
--- a/backend/union/entry.go
+++ b/backend/union/entry.go
@@ -5,7 +5,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"sync"
 	"time"
 
@@ -87,7 +86,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
 				errs[i] = fmt.Errorf("%s: %w", o.UpstreamFs().Name(), err)
 				if len(entries) > 1 {
 					// Drain the input buffer to allow other uploads to continue
-					_, _ = io.Copy(ioutil.Discard, readers[i])
+					_, _ = io.Copy(io.Discard, readers[i])
 				}
 			}
 		} else {
diff --git a/backend/union/union.go b/backend/union/union.go
index fcfe4ac95..aeb0aeca6 100644
--- a/backend/union/union.go
+++ b/backend/union/union.go
@@ -7,7 +7,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"path"
 	"path/filepath"
 	"strings"
@@ -501,7 +500,7 @@ func (f *Fs) put(ctx context.Context, in io.Reader, src fs.ObjectInfo, stream bo
 			errs[i] = fmt.Errorf("%s: %w", u.Name(), err)
 			if len(upstreams) > 1 {
 				// Drain the input buffer to allow other uploads to continue
-				_, _ = io.Copy(ioutil.Discard, readers[i])
+				_, _ = io.Copy(io.Discard, readers[i])
 			}
 			return
 		}
diff --git a/backend/uptobox/uptobox.go b/backend/uptobox/uptobox.go
index d403341bf..172d50f75 100644
--- a/backend/uptobox/uptobox.go
+++ b/backend/uptobox/uptobox.go
@@ -7,7 +7,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"net/url"
 	"path"
@@ -239,7 +238,7 @@ func NewFs(ctx context.Context, name string, root string, config configmap.Mappe
 func (f *Fs) decodeError(resp *http.Response, response interface{}) (err error) {
 	defer fs.CheckClose(resp.Body, &err)
 
-	body, err := ioutil.ReadAll(resp.Body)
+	body, err := io.ReadAll(resp.Body)
 	if err != nil {
 		return err
 	}
diff --git a/backend/zoho/zoho.go b/backend/zoho/zoho.go
index 1e7556b03..f9fc6eb6b 100644
--- a/backend/zoho/zoho.go
+++ b/backend/zoho/zoho.go
@@ -7,7 +7,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"net/url"
 	"path"
@@ -1219,7 +1218,7 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.Read
 	if partialContent && resp.StatusCode == 200 {
 		if start > 0 {
 			// We need to read and discard the beginning of the data...
-			_, err = io.CopyN(ioutil.Discard, resp.Body, start)
+			_, err = io.CopyN(io.Discard, resp.Body, start)
 			if err != nil {
 				if resp != nil {
 					_ = resp.Body.Close()
diff --git a/bin/cross-compile.go b/bin/cross-compile.go
index c66b0eecc..0f0cb6466 100644
--- a/bin/cross-compile.go
+++ b/bin/cross-compile.go
@@ -9,7 +9,6 @@ import (
 	"encoding/json"
 	"flag"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"os"
 	"os/exec"
@@ -240,7 +239,7 @@ func buildWindowsResourceSyso(goarch string, versionTag string) string {
 		log.Printf("Failed to resolve path: %v", err)
 		return ""
 	}
-	err = ioutil.WriteFile(jsonPath, bs, 0644)
+	err = os.WriteFile(jsonPath, bs, 0644)
 	if err != nil {
 		log.Printf("Failed to write %s: %v", jsonPath, err)
 		return ""
@@ -476,7 +475,7 @@ func main() {
 		run("mkdir", "build")
 	}
 	chdir("build")
-	err := ioutil.WriteFile("version.txt", []byte(fmt.Sprintf("rclone %s\n", version)), 0666)
+	err := os.WriteFile("version.txt", []byte(fmt.Sprintf("rclone %s\n", version)), 0666)
 	if err != nil {
 		log.Fatalf("Couldn't write version.txt: %v", err)
 	}
diff --git a/bin/get-github-release.go b/bin/get-github-release.go
index 1aa98f16a..3aa97cd36 100644
--- a/bin/get-github-release.go
+++ b/bin/get-github-release.go
@@ -16,7 +16,6 @@ import (
 	"flag"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"net/http"
 	"net/url"
@@ -168,7 +167,7 @@ func defaultBinDir() string {
 
 // read the body or an error message
 func readBody(in io.Reader) string {
-	data, err := ioutil.ReadAll(in)
+	data, err := io.ReadAll(in)
 	if err != nil {
 		return fmt.Sprintf("Error reading body: %v", err.Error())
 	}
diff --git a/bin/not-in-stable.go b/bin/not-in-stable.go
index f6fae8226..dc685cb6f 100644
--- a/bin/not-in-stable.go
+++ b/bin/not-in-stable.go
@@ -5,7 +5,6 @@ import (
 	"bytes"
 	"flag"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"os"
 	"os/exec"
@@ -56,7 +55,7 @@ func main() {
 		log.Fatalf("Syntax: %s", os.Args[0])
 	}
 	// v1.54.0
-	versionBytes, err := ioutil.ReadFile("VERSION")
+	versionBytes, err := os.ReadFile("VERSION")
 	if err != nil {
 		log.Fatalf("Failed to read version: %v", err)
 	}
diff --git a/cmd/bisync/bilib/files.go b/cmd/bisync/bilib/files.go
index 9171c9700..e2c7bb1e8 100644
--- a/cmd/bisync/bilib/files.go
+++ b/cmd/bisync/bilib/files.go
@@ -5,7 +5,6 @@ package bilib
 import (
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"regexp"
@@ -106,7 +105,7 @@ func CopyDir(src string, dst string) (err error) {
 		return
 	}
 
-	entries, err := ioutil.ReadDir(src)
+	entries, err := os.ReadDir(src)
 	if err != nil {
 		return
 	}
@@ -122,7 +121,7 @@ func CopyDir(src string, dst string) (err error) {
 			}
 		} else {
 			// Skip symlinks.
-			if entry.Mode()&os.ModeSymlink != 0 {
+			if entry.Type()&os.ModeSymlink != 0 {
 				continue
 			}
 
diff --git a/cmd/bisync/bilib/names.go b/cmd/bisync/bilib/names.go
index 177a19f4a..41c4692b5 100644
--- a/cmd/bisync/bilib/names.go
+++ b/cmd/bisync/bilib/names.go
@@ -2,7 +2,7 @@ package bilib
 
 import (
 	"bytes"
-	"io/ioutil"
+	"os"
 	"sort"
 	"strconv"
 )
@@ -57,5 +57,5 @@ func SaveList(list []string, path string) error {
 		_, _ = buf.WriteString(strconv.Quote(s))
 		_ = buf.WriteByte('\n')
 	}
-	return ioutil.WriteFile(path, buf.Bytes(), PermSecure)
+	return os.WriteFile(path, buf.Bytes(), PermSecure)
 }
diff --git a/cmd/bisync/cmd.go b/cmd/bisync/cmd.go
index 42b6489bf..c14edf2e7 100644
--- a/cmd/bisync/cmd.go
+++ b/cmd/bisync/cmd.go
@@ -9,7 +9,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
@@ -198,7 +197,7 @@ func (opt *Options) applyFilters(ctx context.Context) (context.Context, error) {
 	_ = f.Close()
 
 	hashFile := filtersFile + ".md5"
-	wantHash, err := ioutil.ReadFile(hashFile)
+	wantHash, err := os.ReadFile(hashFile)
 	if err != nil && !opt.Resync {
 		return ctx, fmt.Errorf("filters file md5 hash not found (must run --resync): %s", filtersFile)
 	}
@@ -209,7 +208,7 @@ func (opt *Options) applyFilters(ctx context.Context) (context.Context, error) {
 
 	if opt.Resync {
 		fs.Infof(nil, "Storing filters file hash to %s", hashFile)
-		if err := ioutil.WriteFile(hashFile, []byte(gotHash), bilib.PermSecure); err != nil {
+		if err := os.WriteFile(hashFile, []byte(gotHash), bilib.PermSecure); err != nil {
 			return ctx, err
 		}
 	}
diff --git a/cmd/bisync/operations.go b/cmd/bisync/operations.go
index d32153edc..85b1e43d1 100644
--- a/cmd/bisync/operations.go
+++ b/cmd/bisync/operations.go
@@ -7,7 +7,6 @@ import (
 	"context"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strconv"
@@ -81,7 +80,7 @@ func Bisync(ctx context.Context, fs1, fs2 fs.Fs, optArg *Options) (err error) {
 		}
 
 		pidStr := []byte(strconv.Itoa(os.Getpid()))
-		if err = ioutil.WriteFile(lockFile, pidStr, bilib.PermSecure); err != nil {
+		if err = os.WriteFile(lockFile, pidStr, bilib.PermSecure); err != nil {
 			return fmt.Errorf("cannot create lock file: %s: %w", lockFile, err)
 		}
 		fs.Debugf(nil, "Lock file created: %s", lockFile)
diff --git a/cmd/cat/cat.go b/cmd/cat/cat.go
index 227ddb1f3..ead361455 100644
--- a/cmd/cat/cat.go
+++ b/cmd/cat/cat.go
@@ -4,7 +4,6 @@ package cat
 import (
 	"context"
 	"io"
-	"io/ioutil"
 	"log"
 	"os"
 	"strings"
@@ -77,7 +76,7 @@ Note that if offset is negative it will count from the end, so
 		fsrc := cmd.NewFsSrc(args)
 		var w io.Writer = os.Stdout
 		if discard {
-			w = ioutil.Discard
+			w = io.Discard
 		}
 		cmd.Run(false, false, command, func() error {
 			return operations.Cat(context.Background(), fsrc, w, offset, count)
diff --git a/cmd/gendocs/gendocs.go b/cmd/gendocs/gendocs.go
index e80b8a924..4965aa7a3 100644
--- a/cmd/gendocs/gendocs.go
+++ b/cmd/gendocs/gendocs.go
@@ -3,7 +3,6 @@ package gendocs
 
 import (
 	"bytes"
-	"io/ioutil"
 	"log"
 	"os"
 	"path"
@@ -71,7 +70,7 @@ rclone.org website.`,
 		if err != nil {
 			return err
 		}
-		err = ioutil.WriteFile(filepath.Join(root, "flags.md"), buf.Bytes(), 0777)
+		err = os.WriteFile(filepath.Join(root, "flags.md"), buf.Bytes(), 0777)
 		if err != nil {
 			return err
 		}
@@ -129,7 +128,7 @@ rclone.org website.`,
 				return err
 			}
 			if !info.IsDir() {
-				b, err := ioutil.ReadFile(path)
+				b, err := os.ReadFile(path)
 				if err != nil {
 					return err
 				}
@@ -140,7 +139,7 @@ See the [global flags page](/flags/) for global options not listed here.
 ### SEE ALSO`, 1)
 				// outdent all the titles by one
 				doc = outdentTitle.ReplaceAllString(doc, `$1`)
-				err = ioutil.WriteFile(path, []byte(doc), 0777)
+				err = os.WriteFile(path, []byte(doc), 0777)
 				if err != nil {
 					return err
 				}
diff --git a/cmd/rc/rc.go b/cmd/rc/rc.go
index 93c2e319e..2bb163d03 100644
--- a/cmd/rc/rc.go
+++ b/cmd/rc/rc.go
@@ -7,7 +7,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"os"
 	"strings"
@@ -204,7 +204,7 @@ func doCall(ctx context.Context, path string, in rc.Params) (out rc.Params, err
 
 	if resp.StatusCode != http.StatusOK {
 		var body []byte
-		body, err = ioutil.ReadAll(resp.Body)
+		body, err = io.ReadAll(resp.Body)
 		var bodyString string
 		if err == nil {
 			bodyString = string(body)
diff --git a/cmd/selfupdate/selfupdate.go b/cmd/selfupdate/selfupdate.go
index ab24290b3..c7f1a3cda 100644
--- a/cmd/selfupdate/selfupdate.go
+++ b/cmd/selfupdate/selfupdate.go
@@ -14,7 +14,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"net/http"
 	"os"
@@ -227,7 +226,7 @@ func InstallUpdate(ctx context.Context, opt *Options) error {
 }
 
 func installPackage(ctx context.Context, beta bool, version, siteURL, packageFormat string) error {
-	tempFile, err := ioutil.TempFile("", "rclone.*."+packageFormat)
+	tempFile, err := os.CreateTemp("", "rclone.*."+packageFormat)
 	if err != nil {
 		return fmt.Errorf("unable to write temporary package: %w", err)
 	}
@@ -357,7 +356,7 @@ func downloadUpdate(ctx context.Context, beta bool, version, siteURL, newFile, p
 	}
 
 	if packageFormat == "deb" || packageFormat == "rpm" {
-		if err := ioutil.WriteFile(newFile, archiveBuf, 0644); err != nil {
+		if err := os.WriteFile(newFile, archiveBuf, 0644); err != nil {
 			return fmt.Errorf("cannot write temporary .%s: %w", packageFormat, err)
 		}
 		return nil
@@ -471,5 +470,5 @@ func downloadFile(ctx context.Context, url string) ([]byte, error) {
 	if resp.StatusCode != http.StatusOK {
 		return nil, fmt.Errorf("failed with %s downloading %s", resp.Status, url)
 	}
-	return ioutil.ReadAll(resp.Body)
+	return io.ReadAll(resp.Body)
 }
diff --git a/cmd/serve/dlna/data/assets_vfsdata.go b/cmd/serve/dlna/data/assets_vfsdata.go
index 642be7b01..84fb623ad 100644
--- a/cmd/serve/dlna/data/assets_vfsdata.go
+++ b/cmd/serve/dlna/data/assets_vfsdata.go
@@ -9,7 +9,6 @@ import (
 	"compress/gzip"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"os"
 	pathpkg "path"
@@ -152,7 +151,7 @@ func (f *vfsgen۰CompressedFile) Read(p []byte) (n int, err error) {
 	}
 	if f.grPos < f.seekPos {
 		// Fast-forward.
-		_, err = io.CopyN(ioutil.Discard, f.gr, f.seekPos-f.grPos)
+		_, err = io.CopyN(io.Discard, f.gr, f.seekPos-f.grPos)
 		if err != nil {
 			return 0, err
 		}
diff --git a/cmd/serve/dlna/data/data.go b/cmd/serve/dlna/data/data.go
index a5a92880c..f0ee92f51 100644
--- a/cmd/serve/dlna/data/data.go
+++ b/cmd/serve/dlna/data/data.go
@@ -6,7 +6,7 @@ package data
 
 import (
 	"fmt"
-	"io/ioutil"
+	"io"
 	"text/template"
 
 	"github.com/rclone/rclone/fs"
@@ -21,7 +21,7 @@ func GetTemplate() (tpl *template.Template, err error) {
 
 	defer fs.CheckClose(templateFile, &err)
 
-	templateBytes, err := ioutil.ReadAll(templateFile)
+	templateBytes, err := io.ReadAll(templateFile)
 	if err != nil {
 		return nil, fmt.Errorf("get template read: %w", err)
 	}
diff --git a/cmd/serve/docker/driver.go b/cmd/serve/docker/driver.go
index 6b1db258c..dfdb190f9 100644
--- a/cmd/serve/docker/driver.go
+++ b/cmd/serve/docker/driver.go
@@ -4,7 +4,6 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
 	"math/rand"
 	"os"
 	"path/filepath"
@@ -329,7 +328,7 @@ func (drv *Driver) saveState() error {
 	ctx := context.Background()
 	retries := fs.GetConfig(ctx).LowLevelRetries
 	for i := 0; i <= retries; i++ {
-		err = ioutil.WriteFile(drv.statePath, data, 0600)
+		err = os.WriteFile(drv.statePath, data, 0600)
 		if err == nil {
 			return nil
 		}
@@ -342,7 +341,7 @@ func (drv *Driver) saveState() error {
 func (drv *Driver) restoreState(ctx context.Context) error {
 	fs.Debugf(nil, "Restore state from %s", drv.statePath)
 
-	data, err := ioutil.ReadFile(drv.statePath)
+	data, err := os.ReadFile(drv.statePath)
 	if os.IsNotExist(err) {
 		return nil
 	}
diff --git a/cmd/serve/docker/serve.go b/cmd/serve/docker/serve.go
index 94a1ce45b..9fa6e5642 100644
--- a/cmd/serve/docker/serve.go
+++ b/cmd/serve/docker/serve.go
@@ -4,7 +4,6 @@ import (
 	"context"
 	"crypto/tls"
 	"fmt"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"os"
@@ -93,7 +92,7 @@ func writeSpecFile(addr, proto, specDir string) (string, error) {
 	}
 	specFile := filepath.Join(specDir, "rclone.spec")
 	url := fmt.Sprintf("%s://%s", proto, addr)
-	if err := ioutil.WriteFile(specFile, []byte(url), 0644); err != nil {
+	if err := os.WriteFile(specFile, []byte(url), 0644); err != nil {
 		return "", err
 	}
 	fs.Debugf(nil, "Plugin spec has been written to %s", specFile)
diff --git a/cmd/serve/http/data/assets_vfsdata.go b/cmd/serve/http/data/assets_vfsdata.go
index 1324e34e4..69bc52ef7 100644
--- a/cmd/serve/http/data/assets_vfsdata.go
+++ b/cmd/serve/http/data/assets_vfsdata.go
@@ -10,7 +10,6 @@ import (
 	"compress/gzip"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"os"
 	pathpkg "path"
@@ -112,7 +111,7 @@ func (f *vfsgen۰CompressedFile) Read(p []byte) (n int, err error) {
 	}
 	if f.grPos < f.seekPos {
 		// Fast-forward.
-		_, err = io.CopyN(ioutil.Discard, f.gr, f.seekPos-f.grPos)
+		_, err = io.CopyN(io.Discard, f.gr, f.seekPos-f.grPos)
 		if err != nil {
 			return 0, err
 		}
diff --git a/cmd/serve/http/data/data.go b/cmd/serve/http/data/data.go
index 075acf8b7..d0ab355b5 100644
--- a/cmd/serve/http/data/data.go
+++ b/cmd/serve/http/data/data.go
@@ -7,7 +7,8 @@ package data
 import (
 	"fmt"
 	"html/template"
-	"io/ioutil"
+	"io"
+	"os"
 	"time"
 
 	"github.com/spf13/pflag"
@@ -70,7 +71,7 @@ func GetTemplate(tmpl string) (tpl *template.Template, err error) {
 
 		defer fs.CheckClose(templateFile, &err)
 
-		templateBytes, err := ioutil.ReadAll(templateFile)
+		templateBytes, err := io.ReadAll(templateFile)
 		if err != nil {
 			return nil, fmt.Errorf("get template read: %w", err)
 		}
@@ -78,7 +79,7 @@ func GetTemplate(tmpl string) (tpl *template.Template, err error) {
 		templateString = string(templateBytes)
 
 	} else {
-		templateFile, err := ioutil.ReadFile(tmpl)
+		templateFile, err := os.ReadFile(tmpl)
 		if err != nil {
 			return nil, fmt.Errorf("get template open: %w", err)
 		}
diff --git a/cmd/serve/httplib/httplib.go b/cmd/serve/httplib/httplib.go
index 10d8eb1ec..d8a625ea0 100644
--- a/cmd/serve/httplib/httplib.go
+++ b/cmd/serve/httplib/httplib.go
@@ -10,10 +10,10 @@ import (
 	"encoding/base64"
 	"fmt"
 	"html/template"
-	"io/ioutil"
 	"log"
 	"net"
 	"net/http"
+	"os"
 	"strings"
 	"time"
 
@@ -315,7 +315,7 @@ func NewServer(handler http.Handler, opt *Options) *Server {
 			log.Fatalf("Can't use --client-ca without --cert and --key")
 		}
 		certpool := x509.NewCertPool()
-		pem, err := ioutil.ReadFile(s.Opt.ClientCA)
+		pem, err := os.ReadFile(s.Opt.ClientCA)
 		if err != nil {
 			log.Fatalf("Failed to read client certificate authority: %v", err)
 		}
diff --git a/cmd/serve/sftp/server.go b/cmd/serve/sftp/server.go
index d1fa92bf8..b094ad48f 100644
--- a/cmd/serve/sftp/server.go
+++ b/cmd/serve/sftp/server.go
@@ -17,7 +17,6 @@ import (
 	"encoding/pem"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"net"
 	"os"
 	"path/filepath"
@@ -311,7 +310,7 @@ func (s *server) Close() {
 }
 
 func loadPrivateKey(keyPath string) (ssh.Signer, error) {
-	privateBytes, err := ioutil.ReadFile(keyPath)
+	privateBytes, err := os.ReadFile(keyPath)
 	if err != nil {
 		return nil, fmt.Errorf("failed to load private key: %w", err)
 	}
@@ -326,7 +325,7 @@ func loadPrivateKey(keyPath string) (ssh.Signer, error) {
 // the public key of a received connection
 // with the entries in the authorized_keys file.
 func loadAuthorizedKeys(authorizedKeysPath string) (authorizedKeysMap map[string]struct{}, err error) {
-	authorizedKeysBytes, err := ioutil.ReadFile(authorizedKeysPath)
+	authorizedKeysBytes, err := os.ReadFile(authorizedKeysPath)
 	if err != nil {
 		return nil, fmt.Errorf("failed to load authorized keys: %w", err)
 	}
@@ -369,7 +368,7 @@ func makeRSASSHKeyPair(bits int, pubKeyPath, privateKeyPath string) (err error)
 	if err != nil {
 		return err
 	}
-	return ioutil.WriteFile(pubKeyPath, ssh.MarshalAuthorizedKey(pub), 0644)
+	return os.WriteFile(pubKeyPath, ssh.MarshalAuthorizedKey(pub), 0644)
 }
 
 // makeECDSASSHKeyPair make a pair of public and private keys for ECDSA SSH access.
@@ -401,7 +400,7 @@ func makeECDSASSHKeyPair(pubKeyPath, privateKeyPath string) (err error) {
 	if err != nil {
 		return err
 	}
-	return ioutil.WriteFile(pubKeyPath, ssh.MarshalAuthorizedKey(pub), 0644)
+	return os.WriteFile(pubKeyPath, ssh.MarshalAuthorizedKey(pub), 0644)
 }
 
 // makeEd25519SSHKeyPair make a pair of public and private keys for Ed25519 SSH access.
@@ -433,5 +432,5 @@ func makeEd25519SSHKeyPair(pubKeyPath, privateKeyPath string) (err error) {
 	if err != nil {
 		return err
 	}
-	return ioutil.WriteFile(pubKeyPath, ssh.MarshalAuthorizedKey(pub), 0644)
+	return os.WriteFile(pubKeyPath, ssh.MarshalAuthorizedKey(pub), 0644)
 }
diff --git a/cmd/version/version.go b/cmd/version/version.go
index 25a17aa41..32416f89a 100644
--- a/cmd/version/version.go
+++ b/cmd/version/version.go
@@ -4,7 +4,7 @@ package version
 import (
 	"errors"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"strings"
 	"time"
@@ -95,7 +95,7 @@ func GetVersion(url string) (v *semver.Version, vs string, date time.Time, err e
 	if resp.StatusCode != http.StatusOK {
 		return v, vs, date, errors.New(resp.Status)
 	}
-	bodyBytes, err := ioutil.ReadAll(resp.Body)
+	bodyBytes, err := io.ReadAll(resp.Body)
 	if err != nil {
 		return v, vs, date, err
 	}
diff --git a/docs/content/authors.md b/docs/content/authors.md
index 012cd07b6..2e50aff32 100644
--- a/docs/content/authors.md
+++ b/docs/content/authors.md
@@ -650,3 +650,9 @@ put them back in again.` >}}
   * Manoj Ghosh <manoj.ghosh@oracle.com>
   * Tom Mombourquette <tom@devnode.com>
   * Robert Newson <rnewson@apache.org>
+  * Samuel Johnson <esamueljohnson@gmail.com>
+  * coultonluke <luke@luke.org.uk>
+  * Anthony Pessy <anthony@cogniteev.com>
+  * Philip Harvey <pharvey@battelleecology.org>
+  * dgouju <dgouju@users.noreply.github.com>
+  * Clément Notin <clement.notin@gmail.com>
diff --git a/docs/content/commands/rclone_serve_restic.md b/docs/content/commands/rclone_serve_restic.md
index fff9d2f20..40a4ed6d8 100644
--- a/docs/content/commands/rclone_serve_restic.md
+++ b/docs/content/commands/rclone_serve_restic.md
@@ -11,7 +11,7 @@ Serve the remote for restic's REST API.
 
 ## Synopsis
 
-Run a basic web server to serve a remove over restic's REST backend
+Run a basic web server to serve a remote over restic's REST backend
 API over HTTP.  This allows restic to use rclone as a data storage
 mechanism for cloud providers that restic does not support directly.
 
diff --git a/docs/content/local.md b/docs/content/local.md
index f123aa74b..8b7920c91 100644
--- a/docs/content/local.md
+++ b/docs/content/local.md
@@ -428,8 +428,8 @@ Properties:
 Don't check to see if the files change during upload.
 
 Normally rclone checks the size and modification time of files as they
-are being uploaded and aborts with a message which starts "can't copy
-- source file is being updated" if the file changes during upload.
+are being uploaded and aborts with a message which starts "can't copy - 
+source file is being updated" if the file changes during upload.
 
 However on some file systems this modification time check may fail (e.g.
 [Glusterfs #2206](https://github.com/rclone/rclone/issues/2206)) so this
diff --git a/fs/config/configfile/configfile.go b/fs/config/configfile/configfile.go
index c0bef0389..11763fd2e 100644
--- a/fs/config/configfile/configfile.go
+++ b/fs/config/configfile/configfile.go
@@ -4,7 +4,6 @@ package configfile
 import (
 	"bytes"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
@@ -113,7 +112,7 @@ func (s *Storage) Save() error {
 	if err != nil {
 		return fmt.Errorf("failed to create config directory: %w", err)
 	}
-	f, err := ioutil.TempFile(dir, name)
+	f, err := os.CreateTemp(dir, name)
 	if err != nil {
 		return fmt.Errorf("failed to create temp file for new config: %w", err)
 	}
diff --git a/fs/config/crypt.go b/fs/config/crypt.go
index 6792811dc..c1de69e7c 100644
--- a/fs/config/crypt.go
+++ b/fs/config/crypt.go
@@ -10,7 +10,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
 	"os/exec"
 	"strings"
@@ -128,7 +127,7 @@ func Decrypt(b io.ReadSeeker) (io.Reader, error) {
 
 	// Encrypted content is base64 encoded.
 	dec := base64.NewDecoder(base64.StdEncoding, r)
-	box, err := ioutil.ReadAll(dec)
+	box, err := io.ReadAll(dec)
 	if err != nil {
 		return nil, fmt.Errorf("failed to load base64 encoded data: %w", err)
 	}
@@ -140,7 +139,7 @@ func Decrypt(b io.ReadSeeker) (io.Reader, error) {
 	for {
 		if envKeyFile := os.Getenv("_RCLONE_CONFIG_KEY_FILE"); len(envKeyFile) > 0 {
 			fs.Debugf(nil, "attempting to obtain configKey from temp file %s", envKeyFile)
-			obscuredKey, err := ioutil.ReadFile(envKeyFile)
+			obscuredKey, err := os.ReadFile(envKeyFile)
 			if err != nil {
 				errRemove := os.Remove(envKeyFile)
 				if errRemove != nil {
@@ -212,7 +211,7 @@ func Encrypt(src io.Reader, dst io.Writer) error {
 	var key [32]byte
 	copy(key[:], configKey[:32])
 
-	data, err := ioutil.ReadAll(src)
+	data, err := io.ReadAll(src)
 	if err != nil {
 		return err
 	}
@@ -256,7 +255,7 @@ func SetConfigPassword(password string) error {
 	}
 	configKey = sha.Sum(nil)
 	if PassConfigKeyForDaemonization {
-		tempFile, err := ioutil.TempFile("", "rclone")
+		tempFile, err := os.CreateTemp("", "rclone")
 		if err != nil {
 			return fmt.Errorf("cannot create temp file to store configKey: %w", err)
 		}
diff --git a/fs/fshttp/http.go b/fs/fshttp/http.go
index 953a313dd..b37fd460c 100644
--- a/fs/fshttp/http.go
+++ b/fs/fshttp/http.go
@@ -6,12 +6,12 @@ import (
 	"context"
 	"crypto/tls"
 	"crypto/x509"
-	"io/ioutil"
 	"log"
 	"net"
 	"net/http"
 	"net/http/cookiejar"
 	"net/http/httputil"
+	"os"
 	"sync"
 	"time"
 
@@ -74,7 +74,7 @@ func NewTransportCustom(ctx context.Context, customize func(*http.Transport)) ht
 
 	// Load CA cert
 	if ci.CaCert != "" {
-		caCert, err := ioutil.ReadFile(ci.CaCert)
+		caCert, err := os.ReadFile(ci.CaCert)
 		if err != nil {
 			log.Fatalf("Failed to read --ca-cert: %v", err)
 		}
diff --git a/fs/newfs.go b/fs/newfs.go
index 927d278b4..cc921bb65 100644
--- a/fs/newfs.go
+++ b/fs/newfs.go
@@ -6,7 +6,6 @@ import (
 	"context"
 	"crypto/md5"
 	"encoding/base64"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
@@ -117,7 +116,7 @@ func ConfigString(f Fs) string {
 //
 // No cleanup is performed, the caller must call Purge on the Fs themselves.
 func TemporaryLocalFs(ctx context.Context) (Fs, error) {
-	path, err := ioutil.TempDir("", "rclone-spool")
+	path, err := os.MkdirTemp("", "rclone-spool")
 	if err == nil {
 		err = os.Remove(path)
 	}
diff --git a/fs/object/object.go b/fs/object/object.go
index c88b9da02..a2971941d 100644
--- a/fs/object/object.go
+++ b/fs/object/object.go
@@ -6,7 +6,6 @@ import (
 	"context"
 	"errors"
 	"io"
-	"io/ioutil"
 	"time"
 
 	"github.com/rclone/rclone/fs"
@@ -214,7 +213,7 @@ func (o *MemoryObject) Open(ctx context.Context, options ...fs.OpenOption) (io.R
 			}
 		}
 	}
-	return ioutil.NopCloser(bytes.NewBuffer(content)), nil
+	return io.NopCloser(bytes.NewBuffer(content)), nil
 }
 
 // Update in to the object with the modTime given of the given size
@@ -225,7 +224,7 @@ func (o *MemoryObject) Update(ctx context.Context, in io.Reader, src fs.ObjectIn
 	if size == 0 {
 		o.content = nil
 	} else if size < 0 || int64(cap(o.content)) < size {
-		o.content, err = ioutil.ReadAll(in)
+		o.content, err = io.ReadAll(in)
 	} else {
 		o.content = o.content[:size]
 		_, err = io.ReadFull(in, o.content)
diff --git a/fs/operations/operations.go b/fs/operations/operations.go
index 59de5aac0..26db4cc0d 100644
--- a/fs/operations/operations.go
+++ b/fs/operations/operations.go
@@ -11,7 +11,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"mime"
 	"net/http"
 	"os"
@@ -1430,7 +1429,7 @@ func Rcat(ctx context.Context, fdst fs.Fs, dstFileName string, in io.ReadCloser,
 
 	if SkipDestructive(ctx, dstFileName, "upload from pipe") {
 		// prevents "broken pipe" errors
-		_, err = io.Copy(ioutil.Discard, in)
+		_, err = io.Copy(io.Discard, in)
 		return nil, err
 	}
 
@@ -1735,12 +1734,12 @@ func RcatSize(ctx context.Context, fdst fs.Fs, dstFileName string, in io.ReadClo
 		defer func() {
 			tr.Done(ctx, err)
 		}()
-		body := ioutil.NopCloser(in) // we let the server close the body
-		in := tr.Account(ctx, body)  // account the transfer (no buffering)
+		body := io.NopCloser(in)    // we let the server close the body
+		in := tr.Account(ctx, body) // account the transfer (no buffering)
 
 		if SkipDestructive(ctx, dstFileName, "upload from pipe") {
 			// prevents "broken pipe" errors
-			_, err = io.Copy(ioutil.Discard, in)
+			_, err = io.Copy(io.Discard, in)
 			return nil, err
 		}
 
diff --git a/fs/rc/webgui/plugins.go b/fs/rc/webgui/plugins.go
index 859e6d999..c51eeab57 100644
--- a/fs/rc/webgui/plugins.go
+++ b/fs/rc/webgui/plugins.go
@@ -5,7 +5,6 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"net/http"
 	"net/http/httputil"
 	"net/url"
@@ -112,7 +111,7 @@ func (p *Plugins) readFromFile() (err error) {
 	availablePluginsJSON := filepath.Join(pluginsConfigPath, p.fileName)
 	_, err = os.Stat(availablePluginsJSON)
 	if err == nil {
-		data, err := ioutil.ReadFile(availablePluginsJSON)
+		data, err := os.ReadFile(availablePluginsJSON)
 		if err != nil {
 			return err
 		}
@@ -134,7 +133,7 @@ func (p *Plugins) readFromFile() (err error) {
 func (p *Plugins) addPlugin(pluginName string, packageJSONPath string) (err error) {
 	p.mutex.Lock()
 	defer p.mutex.Unlock()
-	data, err := ioutil.ReadFile(packageJSONPath)
+	data, err := os.ReadFile(packageJSONPath)
 	if err != nil {
 		return err
 	}
@@ -187,7 +186,7 @@ func (p *Plugins) writeToFile() (err error) {
 	if err != nil {
 		fs.Logf(nil, "%s", err)
 	}
-	err = ioutil.WriteFile(availablePluginsJSON, file, 0755)
+	err = os.WriteFile(availablePluginsJSON, file, 0755)
 	if err != nil {
 		fs.Logf(nil, "%s", err)
 	}
diff --git a/fs/rc/webgui/webgui.go b/fs/rc/webgui/webgui.go
index ee05730f7..bcc231d3a 100644
--- a/fs/rc/webgui/webgui.go
+++ b/fs/rc/webgui/webgui.go
@@ -7,7 +7,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"os"
 	"path/filepath"
@@ -64,7 +63,7 @@ func CheckAndDownloadWebGUIRelease(checkUpdate bool, forceUpdate bool, fetchURL
 	if err != nil {
 		return fmt.Errorf("error checking for web gui release update, skipping update: %w", err)
 	}
-	dat, err := ioutil.ReadFile(tagPath)
+	dat, err := os.ReadFile(tagPath)
 	tagsMatch := false
 	if err != nil {
 		fs.Errorf(nil, "Error reading tag file at %s ", tagPath)
@@ -129,7 +128,7 @@ func CheckAndDownloadWebGUIRelease(checkUpdate bool, forceUpdate bool, fetchURL
 			fs.Logf(nil, "Downloaded ZIP cannot be deleted")
 		}
 
-		err = ioutil.WriteFile(tagPath, []byte(tag), 0644)
+		err = os.WriteFile(tagPath, []byte(tag), 0644)
 		if err != nil {
 			fs.Infof(nil, "Cannot write tag file. You may be required to redownload the binary next time.")
 		}
diff --git a/lib/encoder/filename/gentable.go b/lib/encoder/filename/gentable.go
index 13c8a7d91..2fb5ed8a6 100644
--- a/lib/encoder/filename/gentable.go
+++ b/lib/encoder/filename/gentable.go
@@ -9,8 +9,8 @@ import (
 	"encoding/base64"
 	"flag"
 	"fmt"
-	"io/ioutil"
 	"math"
+	"os"
 	"strings"
 	"unicode/utf8"
 
@@ -43,7 +43,7 @@ func main() {
 		for i := range histogram[:] {
 			histogram[i] = 0
 		}
-		b, err := ioutil.ReadFile(*indexFile)
+		b, err := os.ReadFile(*indexFile)
 		if err != nil {
 			panic(err)
 		}
diff --git a/lib/http/http.go b/lib/http/http.go
index 527555504..0138b0fec 100644
--- a/lib/http/http.go
+++ b/lib/http/http.go
@@ -7,10 +7,10 @@ import (
 	"crypto/x509"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"net"
 	"net/http"
+	"os"
 	"strings"
 	"sync"
 	"time"
@@ -187,7 +187,7 @@ func NewServer(listeners, tlsListeners []net.Listener, opt Options) (Server, err
 			return nil, err
 		}
 		certpool := x509.NewCertPool()
-		pem, err := ioutil.ReadFile(opt.ClientCA)
+		pem, err := os.ReadFile(opt.ClientCA)
 		if err != nil {
 			log.Fatalf("Failed to read client certificate authority: %v", err)
 			return nil, err
diff --git a/lib/jwtutil/jwtutil.go b/lib/jwtutil/jwtutil.go
index dfb4dc33b..da3b0e7cb 100644
--- a/lib/jwtutil/jwtutil.go
+++ b/lib/jwtutil/jwtutil.go
@@ -10,7 +10,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"strings"
 	"time"
@@ -96,7 +95,7 @@ func Config(id, name string, claims *jws.ClaimSet, header *jws.Header, queryPara
 }
 
 func bodyToString(responseBody io.Reader) (bodyString string, err error) {
-	bodyBytes, err := ioutil.ReadAll(responseBody)
+	bodyBytes, err := io.ReadAll(responseBody)
 	if err != nil {
 		return "", err
 	}
diff --git a/lib/plugin/plugin.go b/lib/plugin/plugin.go
index fb08c9420..a57b83c0e 100644
--- a/lib/plugin/plugin.go
+++ b/lib/plugin/plugin.go
@@ -6,7 +6,6 @@ package plugin
 
 import (
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"plugin"
@@ -19,7 +18,7 @@ func init() {
 		return
 	}
 	// Get file names of plugin dir
-	listing, err := ioutil.ReadDir(dir)
+	listing, err := os.ReadDir(dir)
 	if err != nil {
 		fmt.Fprintln(os.Stderr, "Failed to open plugin directory:", err)
 	}
diff --git a/lib/rest/rest.go b/lib/rest/rest.go
index a424b9d3e..b5f575ce7 100644
--- a/lib/rest/rest.go
+++ b/lib/rest/rest.go
@@ -11,7 +11,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"mime/multipart"
 	"net/http"
 	"net/url"
@@ -44,7 +43,7 @@ func NewClient(c *http.Client) *Client {
 // ReadBody reads resp.Body into result, closing the body
 func ReadBody(resp *http.Response) (result []byte, err error) {
 	defer fs.CheckClose(resp.Body, &err)
-	return ioutil.ReadAll(resp.Body)
+	return io.ReadAll(resp.Body)
 }
 
 // defaultErrorHandler doesn't attempt to parse the http body, just
diff --git a/vfs/vfs.go b/vfs/vfs.go
index 77ccdf499..7e9b0043f 100644
--- a/vfs/vfs.go
+++ b/vfs/vfs.go
@@ -23,7 +23,7 @@ package vfs
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"os"
 	"path"
 	"sort"
@@ -697,7 +697,7 @@ func (vfs *VFS) ReadFile(filename string) (b []byte, err error) {
 		return nil, err
 	}
 	defer fs.CheckClose(f, &err)
-	return ioutil.ReadAll(f)
+	return io.ReadAll(f)
 }
 
 // AddVirtual adds the object (file or dir) to the directory cache


Test patch:
diff --git a/backend/cache/cache_internal_test.go b/backend/cache/cache_internal_test.go
index 4bea04291..849354274 100644
--- a/backend/cache/cache_internal_test.go
+++ b/backend/cache/cache_internal_test.go
@@ -11,7 +11,6 @@ import (
 	goflag "flag"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"math/rand"
 	"os"
@@ -167,7 +166,7 @@ func TestInternalVfsCache(t *testing.T) {
 			li2 := [2]string{path.Join("test", "one"), path.Join("test", "second")}
 			for _, r := range li2 {
 				var err error
-				ci, err := ioutil.ReadDir(path.Join(runInstance.chunkPath, runInstance.encryptRemoteIfNeeded(t, path.Join(id, r))))
+				ci, err := os.ReadDir(path.Join(runInstance.chunkPath, runInstance.encryptRemoteIfNeeded(t, path.Join(id, r))))
 				if err != nil || len(ci) == 0 {
 					log.Printf("========== '%v' not in cache", r)
 				} else {
@@ -841,7 +840,7 @@ func newRun() *run {
 	}
 
 	if uploadDir == "" {
-		r.tmpUploadDir, err = ioutil.TempDir("", "rclonecache-tmp")
+		r.tmpUploadDir, err = os.MkdirTemp("", "rclonecache-tmp")
 		if err != nil {
 			panic(fmt.Sprintf("Failed to create temp dir: %v", err))
 		}
@@ -984,7 +983,7 @@ func (r *run) randomReader(t *testing.T, size int64) io.ReadCloser {
 	chunk := int64(1024)
 	cnt := size / chunk
 	left := size % chunk
-	f, err := ioutil.TempFile("", "rclonecache-tempfile")
+	f, err := os.CreateTemp("", "rclonecache-tempfile")
 	require.NoError(t, err)
 
 	for i := 0; i < int(cnt); i++ {
diff --git a/backend/chunker/chunker_internal_test.go b/backend/chunker/chunker_internal_test.go
index c875c6762..dee3657d9 100644
--- a/backend/chunker/chunker_internal_test.go
+++ b/backend/chunker/chunker_internal_test.go
@@ -5,7 +5,7 @@ import (
 	"context"
 	"flag"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"path"
 	"regexp"
 	"strings"
@@ -413,7 +413,7 @@ func testSmallFileInternals(t *testing.T, f *Fs) {
 		if r == nil {
 			return
 		}
-		data, err := ioutil.ReadAll(r)
+		data, err := io.ReadAll(r)
 		assert.NoError(t, err)
 		assert.Equal(t, contents, string(data))
 		_ = r.Close()
@@ -538,7 +538,7 @@ func testPreventCorruption(t *testing.T, f *Fs) {
 	assert.NoError(t, err)
 	var chunkContents []byte
 	assert.NotPanics(t, func() {
-		chunkContents, err = ioutil.ReadAll(r)
+		chunkContents, err = io.ReadAll(r)
 		_ = r.Close()
 	})
 	assert.NoError(t, err)
@@ -573,7 +573,7 @@ func testPreventCorruption(t *testing.T, f *Fs) {
 	r, err = willyChunk.Open(ctx)
 	assert.NoError(t, err)
 	assert.NotPanics(t, func() {
-		_, err = ioutil.ReadAll(r)
+		_, err = io.ReadAll(r)
 		_ = r.Close()
 	})
 	assert.NoError(t, err)
@@ -672,7 +672,7 @@ func testMetadataInput(t *testing.T, f *Fs) {
 		assert.NoError(t, err, "open "+description)
 		assert.NotNil(t, r, "open stream of "+description)
 		if err == nil && r != nil {
-			data, err := ioutil.ReadAll(r)
+			data, err := io.ReadAll(r)
 			assert.NoError(t, err, "read all of "+description)
 			assert.Equal(t, contents, string(data), description+" contents is ok")
 			_ = r.Close()
@@ -758,7 +758,7 @@ func testFutureProof(t *testing.T, f *Fs) {
 	assert.Error(t, err)
 
 	// Rcat must fail
-	in := ioutil.NopCloser(bytes.NewBufferString("abc"))
+	in := io.NopCloser(bytes.NewBufferString("abc"))
 	robj, err := operations.Rcat(ctx, f, file, in, modTime)
 	assert.Nil(t, robj)
 	assert.NotNil(t, err)
@@ -854,7 +854,7 @@ func testChunkerServerSideMove(t *testing.T, f *Fs) {
 	r, err := dstFile.Open(ctx)
 	assert.NoError(t, err)
 	assert.NotNil(t, r)
-	data, err := ioutil.ReadAll(r)
+	data, err := io.ReadAll(r)
 	assert.NoError(t, err)
 	assert.Equal(t, contents, string(data))
 	_ = r.Close()
diff --git a/backend/crypt/cipher_test.go b/backend/crypt/cipher_test.go
index 565ef6af5..d0ba808be 100644
--- a/backend/crypt/cipher_test.go
+++ b/backend/crypt/cipher_test.go
@@ -8,7 +8,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"strings"
 	"testing"
 
@@ -1073,7 +1072,7 @@ func testEncryptDecrypt(t *testing.T, bufSize int, copySize int64) {
 	source := newRandomSource(copySize)
 	encrypted, err := c.newEncrypter(source, nil)
 	assert.NoError(t, err)
-	decrypted, err := c.newDecrypter(ioutil.NopCloser(encrypted))
+	decrypted, err := c.newDecrypter(io.NopCloser(encrypted))
 	assert.NoError(t, err)
 	sink := newRandomSource(copySize)
 	n, err := io.CopyBuffer(sink, decrypted, buf)
@@ -1144,15 +1143,15 @@ func TestEncryptData(t *testing.T) {
 		buf := bytes.NewBuffer(test.in)
 		encrypted, err := c.EncryptData(buf)
 		assert.NoError(t, err)
-		out, err := ioutil.ReadAll(encrypted)
+		out, err := io.ReadAll(encrypted)
 		assert.NoError(t, err)
 		assert.Equal(t, test.expected, out)
 
 		// Check we can decode the data properly too...
 		buf = bytes.NewBuffer(out)
-		decrypted, err := c.DecryptData(ioutil.NopCloser(buf))
+		decrypted, err := c.DecryptData(io.NopCloser(buf))
 		assert.NoError(t, err)
-		out, err = ioutil.ReadAll(decrypted)
+		out, err = io.ReadAll(decrypted)
 		assert.NoError(t, err)
 		assert.Equal(t, test.in, out)
 	}
@@ -1187,7 +1186,7 @@ func TestNewEncrypterErrUnexpectedEOF(t *testing.T) {
 	fh, err := c.newEncrypter(in, nil)
 	assert.NoError(t, err)
 
-	n, err := io.CopyN(ioutil.Discard, fh, 1e6)
+	n, err := io.CopyN(io.Discard, fh, 1e6)
 	assert.Equal(t, io.ErrUnexpectedEOF, err)
 	assert.Equal(t, int64(32), n)
 }
@@ -1257,12 +1256,12 @@ func TestNewDecrypterErrUnexpectedEOF(t *testing.T) {
 
 	in2 := &readers.ErrorReader{Err: io.ErrUnexpectedEOF}
 	in1 := bytes.NewBuffer(file16)
-	in := ioutil.NopCloser(io.MultiReader(in1, in2))
+	in := io.NopCloser(io.MultiReader(in1, in2))
 
 	fh, err := c.newDecrypter(in)
 	assert.NoError(t, err)
 
-	n, err := io.CopyN(ioutil.Discard, fh, 1e6)
+	n, err := io.CopyN(io.Discard, fh, 1e6)
 	assert.Equal(t, io.ErrUnexpectedEOF, err)
 	assert.Equal(t, int64(16), n)
 }
@@ -1274,14 +1273,14 @@ func TestNewDecrypterSeekLimit(t *testing.T) {
 
 	// Make random data
 	const dataSize = 150000
-	plaintext, err := ioutil.ReadAll(newRandomSource(dataSize))
+	plaintext, err := io.ReadAll(newRandomSource(dataSize))
 	assert.NoError(t, err)
 
 	// Encrypt the data
 	buf := bytes.NewBuffer(plaintext)
 	encrypted, err := c.EncryptData(buf)
 	assert.NoError(t, err)
-	ciphertext, err := ioutil.ReadAll(encrypted)
+	ciphertext, err := io.ReadAll(encrypted)
 	assert.NoError(t, err)
 
 	trials := []int{0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65,
@@ -1300,7 +1299,7 @@ func TestNewDecrypterSeekLimit(t *testing.T) {
 				end = len(ciphertext)
 			}
 		}
-		reader = ioutil.NopCloser(bytes.NewBuffer(ciphertext[int(underlyingOffset):end]))
+		reader = io.NopCloser(bytes.NewBuffer(ciphertext[int(underlyingOffset):end]))
 		return reader, nil
 	}
 
@@ -1490,7 +1489,7 @@ func TestDecrypterRead(t *testing.T) {
 			assert.NoError(t, err, what)
 			continue
 		}
-		_, err = ioutil.ReadAll(fh)
+		_, err = io.ReadAll(fh)
 		var expectedErr error
 		switch {
 		case i == fileHeaderSize:
@@ -1514,7 +1513,7 @@ func TestDecrypterRead(t *testing.T) {
 	cd := newCloseDetector(in)
 	fh, err := c.newDecrypter(cd)
 	assert.NoError(t, err)
-	_, err = ioutil.ReadAll(fh)
+	_, err = io.ReadAll(fh)
 	assert.Error(t, err, "potato")
 	assert.Equal(t, 0, cd.closed)
 
@@ -1524,13 +1523,13 @@ func TestDecrypterRead(t *testing.T) {
 	copy(file16copy, file16)
 	for i := range file16copy {
 		file16copy[i] ^= 0xFF
-		fh, err := c.newDecrypter(ioutil.NopCloser(bytes.NewBuffer(file16copy)))
+		fh, err := c.newDecrypter(io.NopCloser(bytes.NewBuffer(file16copy)))
 		if i < fileMagicSize {
 			assert.Error(t, err, ErrorEncryptedBadMagic.Error())
 			assert.Nil(t, fh)
 		} else {
 			assert.NoError(t, err)
-			_, err = ioutil.ReadAll(fh)
+			_, err = io.ReadAll(fh)
 			assert.Error(t, err, ErrorEncryptedFileBadHeader.Error())
 		}
 		file16copy[i] ^= 0xFF
@@ -1565,7 +1564,7 @@ func TestDecrypterClose(t *testing.T) {
 	assert.Equal(t, 0, cd.closed)
 
 	// close after reading
-	out, err := ioutil.ReadAll(fh)
+	out, err := io.ReadAll(fh)
 	assert.NoError(t, err)
 	assert.Equal(t, []byte{1}, out)
 	assert.Equal(t, io.EOF, fh.err)
diff --git a/backend/drive/drive_internal_test.go b/backend/drive/drive_internal_test.go
index c17da7062..58bdb1313 100644
--- a/backend/drive/drive_internal_test.go
+++ b/backend/drive/drive_internal_test.go
@@ -7,7 +7,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"mime"
 	"os"
 	"path"
@@ -78,7 +77,7 @@ var additionalMimeTypes = map[string]string{
 // Load the example export formats into exportFormats for testing
 func TestInternalLoadExampleFormats(t *testing.T) {
 	fetchFormatsOnce.Do(func() {})
-	buf, err := ioutil.ReadFile(filepath.FromSlash("test/about.json"))
+	buf, err := os.ReadFile(filepath.FromSlash("test/about.json"))
 	var about struct {
 		ExportFormats map[string][]string `json:"exportFormats,omitempty"`
 		ImportFormats map[string][]string `json:"importFormats,omitempty"`
diff --git a/backend/googlephotos/googlephotos_test.go b/backend/googlephotos/googlephotos_test.go
index 11cb28e1b..3cc3a2706 100644
--- a/backend/googlephotos/googlephotos_test.go
+++ b/backend/googlephotos/googlephotos_test.go
@@ -3,7 +3,7 @@ package googlephotos
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"path"
 	"testing"
@@ -99,7 +99,7 @@ func TestIntegration(t *testing.T) {
 			t.Run("ObjectOpen", func(t *testing.T) {
 				in, err := dstObj.Open(ctx)
 				require.NoError(t, err)
-				buf, err := ioutil.ReadAll(in)
+				buf, err := io.ReadAll(in)
 				require.NoError(t, err)
 				require.NoError(t, in.Close())
 				assert.True(t, len(buf) > 1000)
diff --git a/backend/http/http_internal_test.go b/backend/http/http_internal_test.go
index 17c58737c..8bf6be6b6 100644
--- a/backend/http/http_internal_test.go
+++ b/backend/http/http_internal_test.go
@@ -3,7 +3,7 @@ package http
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"net/http/httptest"
 	"net/url"
@@ -41,12 +41,12 @@ func prepareServer(t *testing.T) (configmap.Simple, func()) {
 	// verify the file path is correct, and also check which line endings
 	// are used to get sizes right ("\n" except on Windows, but even there
 	// we may have "\n" or "\r\n" depending on git crlf setting)
-	fileList, err := ioutil.ReadDir(filesPath)
+	fileList, err := os.ReadDir(filesPath)
 	require.NoError(t, err)
 	require.Greater(t, len(fileList), 0)
 	for _, file := range fileList {
 		if !file.IsDir() {
-			data, _ := ioutil.ReadFile(filepath.Join(filesPath, file.Name()))
+			data, _ := os.ReadFile(filepath.Join(filesPath, file.Name()))
 			if strings.HasSuffix(string(data), "\r\n") {
 				lineEndSize = 2
 			}
@@ -203,7 +203,7 @@ func TestOpen(t *testing.T) {
 	// Test normal read
 	fd, err := o.Open(context.Background())
 	require.NoError(t, err)
-	data, err := ioutil.ReadAll(fd)
+	data, err := io.ReadAll(fd)
 	require.NoError(t, err)
 	require.NoError(t, fd.Close())
 	if lineEndSize == 2 {
@@ -215,7 +215,7 @@ func TestOpen(t *testing.T) {
 	// Test with range request
 	fd, err = o.Open(context.Background(), &fs.RangeOption{Start: 1, End: 5})
 	require.NoError(t, err)
-	data, err = ioutil.ReadAll(fd)
+	data, err = io.ReadAll(fd)
 	require.NoError(t, err)
 	require.NoError(t, fd.Close())
 	assert.Equal(t, "eetro", string(data))
diff --git a/backend/local/local_internal_test.go b/backend/local/local_internal_test.go
index 7ecc9986b..a8c48329a 100644
--- a/backend/local/local_internal_test.go
+++ b/backend/local/local_internal_test.go
@@ -4,7 +4,7 @@ import (
 	"bytes"
 	"context"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"os"
 	"path"
 	"path/filepath"
@@ -150,7 +150,7 @@ func TestSymlink(t *testing.T) {
 	// Check reading the object
 	in, err := o.Open(ctx)
 	require.NoError(t, err)
-	contents, err := ioutil.ReadAll(in)
+	contents, err := io.ReadAll(in)
 	require.NoError(t, err)
 	require.Equal(t, "file.txt", string(contents))
 	require.NoError(t, in.Close())
@@ -158,7 +158,7 @@ func TestSymlink(t *testing.T) {
 	// Check reading the object with range
 	in, err = o.Open(ctx, &fs.RangeOption{Start: 2, End: 5})
 	require.NoError(t, err)
-	contents, err = ioutil.ReadAll(in)
+	contents, err = io.ReadAll(in)
 	require.NoError(t, err)
 	require.Equal(t, "file.txt"[2:5+1], string(contents))
 	require.NoError(t, in.Close())
diff --git a/backend/local/remove_test.go b/backend/local/remove_test.go
index b2e34cdc8..a80075000 100644
--- a/backend/local/remove_test.go
+++ b/backend/local/remove_test.go
@@ -1,7 +1,6 @@
 package local
 
 import (
-	"io/ioutil"
 	"os"
 	"sync"
 	"testing"
@@ -13,7 +12,7 @@ import (
 
 // Check we can remove an open file
 func TestRemove(t *testing.T) {
-	fd, err := ioutil.TempFile("", "rclone-remove-test")
+	fd, err := os.CreateTemp("", "rclone-remove-test")
 	require.NoError(t, err)
 	name := fd.Name()
 	defer func() {
diff --git a/backend/sugarsync/sugarsync_internal_test.go b/backend/sugarsync/sugarsync_internal_test.go
index 020970e04..59093be2f 100644
--- a/backend/sugarsync/sugarsync_internal_test.go
+++ b/backend/sugarsync/sugarsync_internal_test.go
@@ -2,7 +2,7 @@ package sugarsync
 
 import (
 	"bytes"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"testing"
 
@@ -48,7 +48,7 @@ func TestErrorHandler(t *testing.T) {
 	} {
 		t.Run(test.name, func(t *testing.T) {
 			resp := http.Response{
-				Body:       ioutil.NopCloser(bytes.NewBufferString(test.body)),
+				Body:       io.NopCloser(bytes.NewBufferString(test.body)),
 				StatusCode: test.code,
 				Status:     test.status,
 			}
diff --git a/backend/swift/swift_test.go b/backend/swift/swift_test.go
index 4669d9e90..528b86f46 100644
--- a/backend/swift/swift_test.go
+++ b/backend/swift/swift_test.go
@@ -6,7 +6,6 @@ import (
 	"context"
 	"errors"
 	"io"
-	"io/ioutil"
 	"testing"
 
 	"github.com/ncw/swift/v2"
@@ -136,7 +135,7 @@ func (f *Fs) testWithChunkFail(t *testing.T) {
 	buf := bytes.NewBufferString(contents[:errPosition])
 	errMessage := "potato"
 	er := &readers.ErrorReader{Err: errors.New(errMessage)}
-	in := ioutil.NopCloser(io.MultiReader(buf, er))
+	in := io.NopCloser(io.MultiReader(buf, er))
 
 	file.Size = contentSize
 	obji := object.NewStaticObjectInfo(file.Path, file.ModTime, file.Size, true, nil, nil)
diff --git a/cmd/bisync/bisync_test.go b/cmd/bisync/bisync_test.go
index 758d786ea..11299f012 100644
--- a/cmd/bisync/bisync_test.go
+++ b/cmd/bisync/bisync_test.go
@@ -10,7 +10,6 @@ import (
 	"errors"
 	"flag"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"os"
 	"path"
@@ -303,7 +302,7 @@ func (b *bisyncTest) runTestCase(ctx context.Context, t *testing.T, testCase str
 
 	// Execute test scenario
 	scenFile := filepath.Join(b.testDir, "scenario.txt")
-	scenBuf, err := ioutil.ReadFile(scenFile)
+	scenBuf, err := os.ReadFile(scenFile)
 	scenReplacer := b.newReplacer(false)
 	require.NoError(b.t, err)
 	b.step = 0
@@ -903,8 +902,8 @@ func (b *bisyncTest) compareResults() int {
 			// save mangled logs so difference is easier on eyes
 			goldenFile := filepath.Join(b.logDir, "mangled.golden.log")
 			resultFile := filepath.Join(b.logDir, "mangled.result.log")
-			require.NoError(b.t, ioutil.WriteFile(goldenFile, []byte(goldenText), bilib.PermSecure))
-			require.NoError(b.t, ioutil.WriteFile(resultFile, []byte(resultText), bilib.PermSecure))
+			require.NoError(b.t, os.WriteFile(goldenFile, []byte(goldenText), bilib.PermSecure))
+			require.NoError(b.t, os.WriteFile(resultFile, []byte(resultText), bilib.PermSecure))
 		}
 
 		if goldenText == resultText {
@@ -974,7 +973,7 @@ func (b *bisyncTest) storeGolden() {
 
 		goldName := b.toGolden(fileName)
 		goldPath := filepath.Join(b.goldenDir, goldName)
-		err := ioutil.WriteFile(goldPath, []byte(text), bilib.PermSecure)
+		err := os.WriteFile(goldPath, []byte(text), bilib.PermSecure)
 		assert.NoError(b.t, err, "writing golden file %s", goldName)
 
 		if goldName != fileName {
@@ -986,7 +985,7 @@ func (b *bisyncTest) storeGolden() {
 
 // mangleResult prepares test logs or listings for comparison
 func (b *bisyncTest) mangleResult(dir, file string, golden bool) string {
-	buf, err := ioutil.ReadFile(filepath.Join(dir, file))
+	buf, err := os.ReadFile(filepath.Join(dir, file))
 	require.NoError(b.t, err)
 	text := string(buf)
 
@@ -1205,7 +1204,7 @@ func (b *bisyncTest) ensureDir(parent, dir string, optional bool) string {
 }
 
 func (b *bisyncTest) listDir(dir string) (names []string) {
-	files, err := ioutil.ReadDir(dir)
+	files, err := os.ReadDir(dir)
 	require.NoError(b.t, err)
 	for _, file := range files {
 		names = append(names, filepath.Base(file.Name()))
diff --git a/cmd/genautocomplete/genautocomplete_test.go b/cmd/genautocomplete/genautocomplete_test.go
index ce766c6fd..fab32f3d1 100644
--- a/cmd/genautocomplete/genautocomplete_test.go
+++ b/cmd/genautocomplete/genautocomplete_test.go
@@ -1,7 +1,6 @@
 package genautocomplete
 
 import (
-	"io/ioutil"
 	"os"
 	"testing"
 
@@ -9,7 +8,7 @@ import (
 )
 
 func TestCompletionBash(t *testing.T) {
-	tempFile, err := ioutil.TempFile("", "completion_bash")
+	tempFile, err := os.CreateTemp("", "completion_bash")
 	assert.NoError(t, err)
 	defer func() {
 		_ = tempFile.Close()
@@ -18,14 +17,14 @@ func TestCompletionBash(t *testing.T) {
 
 	bashCommandDefinition.Run(bashCommandDefinition, []string{tempFile.Name()})
 
-	bs, err := ioutil.ReadFile(tempFile.Name())
+	bs, err := os.ReadFile(tempFile.Name())
 	assert.NoError(t, err)
 	assert.NotEmpty(t, string(bs))
 }
 
 func TestCompletionBashStdout(t *testing.T) {
 	originalStdout := os.Stdout
-	tempFile, err := ioutil.TempFile("", "completion_zsh")
+	tempFile, err := os.CreateTemp("", "completion_zsh")
 	assert.NoError(t, err)
 	defer func() {
 		_ = tempFile.Close()
@@ -37,13 +36,13 @@ func TestCompletionBashStdout(t *testing.T) {
 
 	bashCommandDefinition.Run(bashCommandDefinition, []string{"-"})
 
-	output, err := ioutil.ReadFile(tempFile.Name())
+	output, err := os.ReadFile(tempFile.Name())
 	assert.NoError(t, err)
 	assert.NotEmpty(t, string(output))
 }
 
 func TestCompletionZsh(t *testing.T) {
-	tempFile, err := ioutil.TempFile("", "completion_zsh")
+	tempFile, err := os.CreateTemp("", "completion_zsh")
 	assert.NoError(t, err)
 	defer func() {
 		_ = tempFile.Close()
@@ -52,14 +51,14 @@ func TestCompletionZsh(t *testing.T) {
 
 	zshCommandDefinition.Run(zshCommandDefinition, []string{tempFile.Name()})
 
-	bs, err := ioutil.ReadFile(tempFile.Name())
+	bs, err := os.ReadFile(tempFile.Name())
 	assert.NoError(t, err)
 	assert.NotEmpty(t, string(bs))
 }
 
 func TestCompletionZshStdout(t *testing.T) {
 	originalStdout := os.Stdout
-	tempFile, err := ioutil.TempFile("", "completion_zsh")
+	tempFile, err := os.CreateTemp("", "completion_zsh")
 	assert.NoError(t, err)
 	defer func() {
 		_ = tempFile.Close()
@@ -70,13 +69,13 @@ func TestCompletionZshStdout(t *testing.T) {
 	defer func() { os.Stdout = originalStdout }()
 
 	zshCommandDefinition.Run(zshCommandDefinition, []string{"-"})
-	output, err := ioutil.ReadFile(tempFile.Name())
+	output, err := os.ReadFile(tempFile.Name())
 	assert.NoError(t, err)
 	assert.NotEmpty(t, string(output))
 }
 
 func TestCompletionFish(t *testing.T) {
-	tempFile, err := ioutil.TempFile("", "completion_fish")
+	tempFile, err := os.CreateTemp("", "completion_fish")
 	assert.NoError(t, err)
 	defer func() {
 		_ = tempFile.Close()
@@ -85,14 +84,14 @@ func TestCompletionFish(t *testing.T) {
 
 	fishCommandDefinition.Run(fishCommandDefinition, []string{tempFile.Name()})
 
-	bs, err := ioutil.ReadFile(tempFile.Name())
+	bs, err := os.ReadFile(tempFile.Name())
 	assert.NoError(t, err)
 	assert.NotEmpty(t, string(bs))
 }
 
 func TestCompletionFishStdout(t *testing.T) {
 	originalStdout := os.Stdout
-	tempFile, err := ioutil.TempFile("", "completion_zsh")
+	tempFile, err := os.CreateTemp("", "completion_zsh")
 	assert.NoError(t, err)
 	defer func() {
 		_ = tempFile.Close()
@@ -104,7 +103,7 @@ func TestCompletionFishStdout(t *testing.T) {
 
 	fishCommandDefinition.Run(fishCommandDefinition, []string{"-"})
 
-	output, err := ioutil.ReadFile(tempFile.Name())
+	output, err := os.ReadFile(tempFile.Name())
 	assert.NoError(t, err)
 	assert.NotEmpty(t, string(output))
 }
diff --git a/cmd/mount/test/seeker.go b/cmd/mount/test/seeker.go
index bcc9bb323..42a2f0e9e 100644
--- a/cmd/mount/test/seeker.go
+++ b/cmd/mount/test/seeker.go
@@ -8,7 +8,6 @@ import (
 	"bytes"
 	"flag"
 	"io"
-	"io/ioutil"
 	"log"
 	"math/rand"
 	"os"
@@ -60,11 +59,11 @@ func randomSeekTest(size int64, in1, in2 *os.File, file1, file2 string) {
 
 	if !bytes.Equal(buf1, buf2) {
 		log.Printf("Dumping different blocks")
-		err = ioutil.WriteFile("/tmp/z1", buf1, 0777)
+		err = os.WriteFile("/tmp/z1", buf1, 0777)
 		if err != nil {
 			log.Fatalf("Failed to write /tmp/z1: %v", err)
 		}
-		err = ioutil.WriteFile("/tmp/z2", buf2, 0777)
+		err = os.WriteFile("/tmp/z2", buf2, 0777)
 		if err != nil {
 			log.Fatalf("Failed to write /tmp/z2: %v", err)
 		}
diff --git a/cmd/mountlib/rc_test.go b/cmd/mountlib/rc_test.go
index 5513ce4a7..0ac4d6e53 100644
--- a/cmd/mountlib/rc_test.go
+++ b/cmd/mountlib/rc_test.go
@@ -2,7 +2,6 @@ package mountlib_test
 
 import (
 	"context"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"runtime"
@@ -36,7 +35,7 @@ func TestRc(t *testing.T) {
 	assert.NotNil(t, getMountTypes)
 
 	localDir := t.TempDir()
-	err := ioutil.WriteFile(filepath.Join(localDir, "file.txt"), []byte("hello"), 0666)
+	err := os.WriteFile(filepath.Join(localDir, "file.txt"), []byte("hello"), 0666)
 	require.NoError(t, err)
 
 	mountPoint := t.TempDir()
diff --git a/cmd/selfupdate/selfupdate_test.go b/cmd/selfupdate/selfupdate_test.go
index b8c52a349..d4ce2ec2a 100644
--- a/cmd/selfupdate/selfupdate_test.go
+++ b/cmd/selfupdate/selfupdate_test.go
@@ -5,7 +5,6 @@ package selfupdate
 
 import (
 	"context"
-	"io/ioutil"
 	"os"
 	"os/exec"
 	"path/filepath"
@@ -86,7 +85,7 @@ func TestInstallOnLinux(t *testing.T) {
 	assert.NoError(t, InstallUpdate(ctx, &Options{Beta: true, Output: path, Version: fs.Version}))
 
 	// Must fail on non-writable file
-	assert.NoError(t, ioutil.WriteFile(path, []byte("test"), 0644))
+	assert.NoError(t, os.WriteFile(path, []byte("test"), 0644))
 	assert.NoError(t, os.Chmod(path, 0000))
 	err = (InstallUpdate(ctx, &Options{Beta: true, Output: path}))
 	assert.Error(t, err)
@@ -101,7 +100,7 @@ func TestInstallOnLinux(t *testing.T) {
 	assert.Equal(t, os.FileMode(0644), info.Mode().Perm())
 
 	// Must remove temporary files
-	files, err := ioutil.ReadDir(testDir)
+	files, err := os.ReadDir(testDir)
 	assert.NoError(t, err)
 	assert.Equal(t, 1, len(files))
 
@@ -141,7 +140,7 @@ func TestRenameOnWindows(t *testing.T) {
 	// Must not create temporary files when target doesn't exist
 	assert.NoError(t, InstallUpdate(ctx, &Options{Beta: true, Output: path}))
 
-	files, err := ioutil.ReadDir(testDir)
+	files, err := os.ReadDir(testDir)
 	assert.NoError(t, err)
 	assert.Equal(t, 1, len(files))
 
@@ -152,7 +151,7 @@ func TestRenameOnWindows(t *testing.T) {
 	assert.NoError(t, cmdWait.Start())
 
 	assert.NoError(t, InstallUpdate(ctx, &Options{Beta: false, Output: path}))
-	files, err = ioutil.ReadDir(testDir)
+	files, err = os.ReadDir(testDir)
 	assert.NoError(t, err)
 	assert.Equal(t, 2, len(files))
 
@@ -189,7 +188,7 @@ func TestRenameOnWindows(t *testing.T) {
 
 	// Updating when the "old" executable is running must produce a random "old" file
 	assert.NoError(t, InstallUpdate(ctx, &Options{Beta: true, Output: path}))
-	files, err = ioutil.ReadDir(testDir)
+	files, err = os.ReadDir(testDir)
 	assert.NoError(t, err)
 	assert.Equal(t, 3, len(files))
 
diff --git a/cmd/serve/dlna/dlna_test.go b/cmd/serve/dlna/dlna_test.go
index 3a3d2fa4b..389e3ba3c 100644
--- a/cmd/serve/dlna/dlna_test.go
+++ b/cmd/serve/dlna/dlna_test.go
@@ -5,7 +5,7 @@ import (
 	"context"
 	"fmt"
 	"html"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"os"
 	"strings"
@@ -60,7 +60,7 @@ func TestRootSCPD(t *testing.T) {
 	resp, err := http.DefaultClient.Do(req)
 	require.NoError(t, err)
 	assert.Equal(t, http.StatusOK, resp.StatusCode)
-	body, err := ioutil.ReadAll(resp.Body)
+	body, err := io.ReadAll(resp.Body)
 	require.NoError(t, err)
 	// Make sure that the SCPD contains a CDS service.
 	require.Contains(t, string(body),
@@ -80,7 +80,7 @@ func TestServeContent(t *testing.T) {
 	require.NoError(t, err)
 	defer fs.CheckClose(resp.Body, &err)
 	assert.Equal(t, http.StatusOK, resp.StatusCode)
-	actualContents, err := ioutil.ReadAll(resp.Body)
+	actualContents, err := io.ReadAll(resp.Body)
 	assert.NoError(t, err)
 
 	// Now compare the contents with the golden file.
@@ -90,7 +90,7 @@ func TestServeContent(t *testing.T) {
 	goldenReader, err := goldenFile.Open(os.O_RDONLY)
 	assert.NoError(t, err)
 	defer fs.CheckClose(goldenReader, &err)
-	goldenContents, err := ioutil.ReadAll(goldenReader)
+	goldenContents, err := io.ReadAll(goldenReader)
 	assert.NoError(t, err)
 
 	require.Equal(t, goldenContents, actualContents)
@@ -119,7 +119,7 @@ func TestContentDirectoryBrowseMetadata(t *testing.T) {
 	resp, err := http.DefaultClient.Do(req)
 	require.NoError(t, err)
 	assert.Equal(t, http.StatusOK, resp.StatusCode)
-	body, err := ioutil.ReadAll(resp.Body)
+	body, err := io.ReadAll(resp.Body)
 	require.NoError(t, err)
 	// should contain an appropriate URN
 	require.Contains(t, string(body), "urn:schemas-upnp-org:service:ContentDirectory:1")
@@ -145,7 +145,7 @@ func TestMediaReceiverRegistrarService(t *testing.T) {
 	resp, err := http.DefaultClient.Do(req)
 	require.NoError(t, err)
 	assert.Equal(t, http.StatusOK, resp.StatusCode)
-	body, err := ioutil.ReadAll(resp.Body)
+	body, err := io.ReadAll(resp.Body)
 	require.NoError(t, err)
 	require.Contains(t, string(body), "<RegistrationRespMsg>")
 }
@@ -173,7 +173,7 @@ func TestContentDirectoryBrowseDirectChildren(t *testing.T) {
 	resp, err := http.DefaultClient.Do(req)
 	require.NoError(t, err)
 	assert.Equal(t, http.StatusOK, resp.StatusCode)
-	body, err := ioutil.ReadAll(resp.Body)
+	body, err := io.ReadAll(resp.Body)
 	require.NoError(t, err)
 	// expect video.mp4, video.srt, video.en.srt URLs to be in the DIDL
 	require.Contains(t, string(body), "/r/video.mp4")
@@ -201,7 +201,7 @@ func TestContentDirectoryBrowseDirectChildren(t *testing.T) {
 	resp, err = http.DefaultClient.Do(req)
 	require.NoError(t, err)
 	assert.Equal(t, http.StatusOK, resp.StatusCode)
-	body, err = ioutil.ReadAll(resp.Body)
+	body, err = io.ReadAll(resp.Body)
 	require.NoError(t, err)
 	// expect video.mp4, video.srt, URLs to be in the DIDL
 	require.Contains(t, string(body), "/r/subdir/video.mp4")
diff --git a/cmd/serve/docker/docker_test.go b/cmd/serve/docker/docker_test.go
index 82f217016..4346b2f92 100644
--- a/cmd/serve/docker/docker_test.go
+++ b/cmd/serve/docker/docker_test.go
@@ -8,7 +8,7 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net"
 	"net/http"
 	"os"
@@ -280,7 +280,7 @@ func (a *APIClient) request(path string, in, out interface{}, wantErr bool) {
 	}
 	assert.Equal(t, wantStatus, res.StatusCode)
 
-	dataOut, err = ioutil.ReadAll(res.Body)
+	dataOut, err = io.ReadAll(res.Body)
 	require.NoError(t, err)
 	err = res.Body.Close()
 	require.NoError(t, err)
@@ -389,11 +389,11 @@ func testMountAPI(t *testing.T, sockAddr string) {
 	assert.Contains(t, res, "volume is in use")
 
 	text := []byte("banana")
-	err = ioutil.WriteFile(filepath.Join(mount1, "txt"), text, 0644)
+	err = os.WriteFile(filepath.Join(mount1, "txt"), text, 0644)
 	assert.NoError(t, err)
 	time.Sleep(tempDelay)
 
-	text2, err := ioutil.ReadFile(filepath.Join(path1, "txt"))
+	text2, err := os.ReadFile(filepath.Join(path1, "txt"))
 	assert.NoError(t, err)
 	if runtime.GOOS != "windows" {
 		// this check sometimes fails on windows - ignore
diff --git a/cmd/serve/http/http_test.go b/cmd/serve/http/http_test.go
index 148b5d22a..0c77e4b61 100644
--- a/cmd/serve/http/http_test.go
+++ b/cmd/serve/http/http_test.go
@@ -3,8 +3,9 @@ package http
 import (
 	"context"
 	"flag"
-	"io/ioutil"
+	"io"
 	"net/http"
+	"os"
 	"strings"
 	"testing"
 	"time"
@@ -91,10 +92,10 @@ func TestInit(t *testing.T) {
 func checkGolden(t *testing.T, fileName string, got []byte) {
 	if *updateGolden {
 		t.Logf("Updating golden file %q", fileName)
-		err := ioutil.WriteFile(fileName, got, 0666)
+		err := os.WriteFile(fileName, got, 0666)
 		require.NoError(t, err)
 	} else {
-		want, err := ioutil.ReadFile(fileName)
+		want, err := os.ReadFile(fileName)
 		require.NoError(t, err)
 		wants := strings.Split(string(want), "\n")
 		gots := strings.Split(string(got), "\n")
@@ -210,7 +211,7 @@ func TestGET(t *testing.T) {
 		resp, err := http.DefaultClient.Do(req)
 		require.NoError(t, err)
 		assert.Equal(t, test.Status, resp.StatusCode, test.Golden)
-		body, err := ioutil.ReadAll(resp.Body)
+		body, err := io.ReadAll(resp.Body)
 		require.NoError(t, err)
 
 		// Check we got a Last-Modified header and that it is a valid date
diff --git a/cmd/serve/webdav/webdav_test.go b/cmd/serve/webdav/webdav_test.go
index b6ee3259a..bb95b0793 100644
--- a/cmd/serve/webdav/webdav_test.go
+++ b/cmd/serve/webdav/webdav_test.go
@@ -11,7 +11,7 @@ package webdav
 import (
 	"context"
 	"flag"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"os"
 	"strings"
@@ -134,10 +134,10 @@ func TestHTTPFunction(t *testing.T) {
 func checkGolden(t *testing.T, fileName string, got []byte) {
 	if *updateGolden {
 		t.Logf("Updating golden file %q", fileName)
-		err := ioutil.WriteFile(fileName, got, 0666)
+		err := os.WriteFile(fileName, got, 0666)
 		require.NoError(t, err)
 	} else {
-		want, err := ioutil.ReadFile(fileName)
+		want, err := os.ReadFile(fileName)
 		require.NoError(t, err, "problem")
 		wants := strings.Split(string(want), "\n")
 		gots := strings.Split(string(got), "\n")
@@ -253,7 +253,7 @@ func HelpTestGET(t *testing.T, testURL string) {
 		resp, err := http.DefaultClient.Do(req)
 		require.NoError(t, err)
 		assert.Equal(t, test.Status, resp.StatusCode, test.Golden)
-		body, err := ioutil.ReadAll(resp.Body)
+		body, err := io.ReadAll(resp.Body)
 		require.NoError(t, err)
 
 		checkGolden(t, test.Golden, body)
diff --git a/cmd/version/version_test.go b/cmd/version/version_test.go
index d48110538..fa20a0a9c 100644
--- a/cmd/version/version_test.go
+++ b/cmd/version/version_test.go
@@ -1,7 +1,6 @@
 package version
 
 import (
-	"io/ioutil"
 	"os"
 	"runtime"
 	"testing"
@@ -13,7 +12,7 @@ import (
 
 func TestVersionWorksWithoutAccessibleConfigFile(t *testing.T) {
 	// create temp config file
-	tempFile, err := ioutil.TempFile("", "unreadable_config.conf")
+	tempFile, err := os.CreateTemp("", "unreadable_config.conf")
 	assert.NoError(t, err)
 	path := tempFile.Name()
 	defer func() {
diff --git a/cmdtest/cmdtest_test.go b/cmdtest/cmdtest_test.go
index 1d0c8487e..62f51f28a 100644
--- a/cmdtest/cmdtest_test.go
+++ b/cmdtest/cmdtest_test.go
@@ -6,7 +6,6 @@
 package cmdtest
 
 import (
-	"io/ioutil"
 	"log"
 	"os"
 	"os/exec"
@@ -121,7 +120,7 @@ func removeTestEnvironment(t *testing.T) {
 
 // createTestFile creates the file testFolder/name
 func createTestFile(name string, t *testing.T) string {
-	err := ioutil.WriteFile(testFolder+"/"+name, []byte("content_of_"+name), 0666)
+	err := os.WriteFile(testFolder+"/"+name, []byte("content_of_"+name), 0666)
 	require.NoError(t, err)
 	return testFolder + "/" + name
 }
diff --git a/fs/accounting/accounting_test.go b/fs/accounting/accounting_test.go
index 53cb09a7b..4b3a876df 100644
--- a/fs/accounting/accounting_test.go
+++ b/fs/accounting/accounting_test.go
@@ -5,7 +5,6 @@ import (
 	"context"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"strings"
 	"testing"
 	"unicode/utf8"
@@ -29,7 +28,7 @@ var (
 
 func TestNewAccountSizeName(t *testing.T) {
 	ctx := context.Background()
-	in := ioutil.NopCloser(bytes.NewBuffer([]byte{1}))
+	in := io.NopCloser(bytes.NewBuffer([]byte{1}))
 	stats := NewStats(ctx)
 	acc := newAccountSizeName(context.Background(), stats, in, 1, "test")
 	assert.Equal(t, in, acc.in)
@@ -44,7 +43,7 @@ func TestNewAccountSizeName(t *testing.T) {
 
 func TestAccountWithBuffer(t *testing.T) {
 	ctx := context.Background()
-	in := ioutil.NopCloser(bytes.NewBuffer([]byte{1}))
+	in := io.NopCloser(bytes.NewBuffer([]byte{1}))
 
 	stats := NewStats(ctx)
 	acc := newAccountSizeName(ctx, stats, in, -1, "test")
@@ -68,7 +67,7 @@ func TestAccountGetUpdateReader(t *testing.T) {
 	ctx := context.Background()
 	test := func(doClose bool) func(t *testing.T) {
 		return func(t *testing.T) {
-			in := ioutil.NopCloser(bytes.NewBuffer([]byte{1}))
+			in := io.NopCloser(bytes.NewBuffer([]byte{1}))
 			stats := NewStats(ctx)
 			acc := newAccountSizeName(ctx, stats, in, 1, "test")
 
@@ -80,7 +79,7 @@ func TestAccountGetUpdateReader(t *testing.T) {
 				require.NoError(t, acc.Close())
 			}
 
-			in2 := ioutil.NopCloser(bytes.NewBuffer([]byte{1}))
+			in2 := io.NopCloser(bytes.NewBuffer([]byte{1}))
 			acc.UpdateReader(ctx, in2)
 
 			assert.Equal(t, in2, acc.GetReader())
@@ -95,7 +94,7 @@ func TestAccountGetUpdateReader(t *testing.T) {
 
 func TestAccountRead(t *testing.T) {
 	ctx := context.Background()
-	in := ioutil.NopCloser(bytes.NewBuffer([]byte{1, 2, 3}))
+	in := io.NopCloser(bytes.NewBuffer([]byte{1, 2, 3}))
 	stats := NewStats(ctx)
 	acc := newAccountSizeName(ctx, stats, in, 1, "test")
 
@@ -137,7 +136,7 @@ func testAccountWriteTo(t *testing.T, withBuffer bool) {
 	for i := range buf {
 		buf[i] = byte(i % 251)
 	}
-	in := ioutil.NopCloser(bytes.NewBuffer(buf))
+	in := io.NopCloser(bytes.NewBuffer(buf))
 	stats := NewStats(ctx)
 	acc := newAccountSizeName(ctx, stats, in, int64(len(buf)), "test")
 	if withBuffer {
@@ -178,7 +177,7 @@ func TestAccountWriteToWithBuffer(t *testing.T) {
 
 func TestAccountString(t *testing.T) {
 	ctx := context.Background()
-	in := ioutil.NopCloser(bytes.NewBuffer([]byte{1, 2, 3}))
+	in := io.NopCloser(bytes.NewBuffer([]byte{1, 2, 3}))
 	stats := NewStats(ctx)
 	acc := newAccountSizeName(ctx, stats, in, 3, "test")
 
@@ -199,13 +198,13 @@ func TestAccountString(t *testing.T) {
 // Test the Accounter interface methods on Account and accountStream
 func TestAccountAccounter(t *testing.T) {
 	ctx := context.Background()
-	in := ioutil.NopCloser(bytes.NewBuffer([]byte{1, 2, 3}))
+	in := io.NopCloser(bytes.NewBuffer([]byte{1, 2, 3}))
 	stats := NewStats(ctx)
 	acc := newAccountSizeName(ctx, stats, in, 3, "test")
 
 	assert.True(t, in == acc.OldStream())
 
-	in2 := ioutil.NopCloser(bytes.NewBuffer([]byte{2, 3, 4}))
+	in2 := io.NopCloser(bytes.NewBuffer([]byte{2, 3, 4}))
 
 	acc.SetStream(in2)
 	assert.True(t, in2 == acc.OldStream())
@@ -228,7 +227,7 @@ func TestAccountAccounter(t *testing.T) {
 	assert.Equal(t, []byte{1, 2}, buf[:n])
 
 	// Test that we can get another accountstream out
-	in3 := ioutil.NopCloser(bytes.NewBuffer([]byte{3, 1, 2}))
+	in3 := io.NopCloser(bytes.NewBuffer([]byte{3, 1, 2}))
 	r2 := as.WrapStream(in3)
 	as2, ok := r2.(Accounter)
 	require.True(t, ok)
@@ -268,7 +267,7 @@ func TestAccountMaxTransfer(t *testing.T) {
 		ci.CutoffMode = oldMode
 	}()
 
-	in := ioutil.NopCloser(bytes.NewBuffer(make([]byte, 100)))
+	in := io.NopCloser(bytes.NewBuffer(make([]byte, 100)))
 	stats := NewStats(ctx)
 	acc := newAccountSizeName(ctx, stats, in, 1, "test")
 
@@ -312,7 +311,7 @@ func TestAccountMaxTransferWriteTo(t *testing.T) {
 		ci.CutoffMode = oldMode
 	}()
 
-	in := ioutil.NopCloser(readers.NewPatternReader(1024))
+	in := io.NopCloser(readers.NewPatternReader(1024))
 	stats := NewStats(ctx)
 	acc := newAccountSizeName(ctx, stats, in, 1, "test")
 
@@ -326,7 +325,7 @@ func TestAccountMaxTransferWriteTo(t *testing.T) {
 func TestAccountReadCtx(t *testing.T) {
 	ctx := context.Background()
 	ctx, cancel := context.WithCancel(ctx)
-	in := ioutil.NopCloser(bytes.NewBuffer(make([]byte, 100)))
+	in := io.NopCloser(bytes.NewBuffer(make([]byte, 100)))
 	stats := NewStats(ctx)
 	acc := newAccountSizeName(ctx, stats, in, 1, "test")
 
diff --git a/fs/asyncreader/asyncreader_test.go b/fs/asyncreader/asyncreader_test.go
index 342494d06..2cc3a8389 100644
--- a/fs/asyncreader/asyncreader_test.go
+++ b/fs/asyncreader/asyncreader_test.go
@@ -6,7 +6,6 @@ import (
 	"context"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"math/rand"
 	"strings"
 	"sync"
@@ -23,7 +22,7 @@ import (
 func TestAsyncReader(t *testing.T) {
 	ctx := context.Background()
 
-	buf := ioutil.NopCloser(bytes.NewBufferString("Testbuffer"))
+	buf := io.NopCloser(bytes.NewBufferString("Testbuffer"))
 	ar, err := New(ctx, buf, 4)
 	require.NoError(t, err)
 
@@ -48,7 +47,7 @@ func TestAsyncReader(t *testing.T) {
 	require.NoError(t, err)
 
 	// Test Close without reading everything
-	buf = ioutil.NopCloser(bytes.NewBuffer(make([]byte, 50000)))
+	buf = io.NopCloser(bytes.NewBuffer(make([]byte, 50000)))
 	ar, err = New(ctx, buf, 4)
 	require.NoError(t, err)
 	err = ar.Close()
@@ -59,7 +58,7 @@ func TestAsyncReader(t *testing.T) {
 func TestAsyncWriteTo(t *testing.T) {
 	ctx := context.Background()
 
-	buf := ioutil.NopCloser(bytes.NewBufferString("Testbuffer"))
+	buf := io.NopCloser(bytes.NewBufferString("Testbuffer"))
 	ar, err := New(ctx, buf, 4)
 	require.NoError(t, err)
 
@@ -85,7 +84,7 @@ func TestAsyncReaderErrors(t *testing.T) {
 	require.Error(t, err)
 
 	// invalid buffer number
-	buf := ioutil.NopCloser(bytes.NewBufferString("Testbuffer"))
+	buf := io.NopCloser(bytes.NewBufferString("Testbuffer"))
 	_, err = New(ctx, buf, 0)
 	require.Error(t, err)
 	_, err = New(ctx, buf, -1)
@@ -170,7 +169,7 @@ func TestAsyncReaderSizes(t *testing.T) {
 						bufsize := bufsizes[k]
 						read := readmaker.fn(strings.NewReader(text))
 						buf := bufio.NewReaderSize(read, bufsize)
-						ar, _ := New(ctx, ioutil.NopCloser(buf), l)
+						ar, _ := New(ctx, io.NopCloser(buf), l)
 						s := bufreader.fn(ar)
 						// "timeout" expects the Reader to recover, AsyncReader does not.
 						if s != text && readmaker.name != "timeout" {
@@ -211,7 +210,7 @@ func TestAsyncReaderWriteTo(t *testing.T) {
 						bufsize := bufsizes[k]
 						read := readmaker.fn(strings.NewReader(text))
 						buf := bufio.NewReaderSize(read, bufsize)
-						ar, _ := New(ctx, ioutil.NopCloser(buf), l)
+						ar, _ := New(ctx, io.NopCloser(buf), l)
 						dst := &bytes.Buffer{}
 						_, err := ar.WriteTo(dst)
 						if err != nil && err != io.EOF && err != iotest.ErrTimeout {
@@ -272,7 +271,7 @@ func testAsyncReaderClose(t *testing.T, writeto bool) {
 		close(started)
 		if writeto {
 			// exercise the WriteTo path
-			copyN, copyErr = a.WriteTo(ioutil.Discard)
+			copyN, copyErr = a.WriteTo(io.Discard)
 		} else {
 			// exercise the Read path
 			buf := make([]byte, 64*1024)
@@ -327,7 +326,7 @@ func TestAsyncReaderSkipBytes(t *testing.T) {
 				t.Run(fmt.Sprintf("%d", initialRead), func(t *testing.T) {
 					for _, skip := range skips {
 						t.Run(fmt.Sprintf("%d", skip), func(t *testing.T) {
-							ar, err := New(ctx, ioutil.NopCloser(bytes.NewReader(data)), buffers)
+							ar, err := New(ctx, io.NopCloser(bytes.NewReader(data)), buffers)
 							require.NoError(t, err)
 
 							wantSkipFalse := false
diff --git a/fs/config/configfile/configfile_test.go b/fs/config/configfile/configfile_test.go
index 2a85b6485..c7a325cce 100644
--- a/fs/config/configfile/configfile_test.go
+++ b/fs/config/configfile/configfile_test.go
@@ -2,7 +2,6 @@ package configfile
 
 import (
 	"fmt"
-	"io/ioutil"
 	"os"
 	"runtime"
 	"strings"
@@ -30,7 +29,7 @@ fruit = banana
 
 // Fill up a temporary config file with the testdata filename passed in
 func setConfigFile(t *testing.T, data string) func() {
-	out, err := ioutil.TempFile("", "rclone-configfile-test")
+	out, err := os.CreateTemp("", "rclone-configfile-test")
 	require.NoError(t, err)
 	filePath := out.Name()
 
@@ -160,7 +159,7 @@ type = number3
 `, toUnix(buf))
 					t.Run("Save", func(t *testing.T) {
 						require.NoError(t, data.Save())
-						buf, err := ioutil.ReadFile(config.GetConfigPath())
+						buf, err := os.ReadFile(config.GetConfigPath())
 						require.NoError(t, err)
 						assert.Equal(t, `[one]
 fruit = potato
diff --git a/fs/config/ui_test.go b/fs/config/ui_test.go
index 5aec20904..0756481d1 100644
--- a/fs/config/ui_test.go
+++ b/fs/config/ui_test.go
@@ -7,7 +7,6 @@ package config_test
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"testing"
 
@@ -37,7 +36,7 @@ func testConfigFile(t *testing.T, options []fs.Option, configFileName string) fu
 	_ = os.Unsetenv("_RCLONE_CONFIG_KEY_FILE")
 	_ = os.Unsetenv("RCLONE_CONFIG_PASS")
 	// create temp config file
-	tempFile, err := ioutil.TempFile("", configFileName)
+	tempFile, err := os.CreateTemp("", configFileName)
 	assert.NoError(t, err)
 	path := tempFile.Name()
 	assert.NoError(t, tempFile.Close())
diff --git a/fs/filter/filter_test.go b/fs/filter/filter_test.go
index aada638fc..6feab8ecc 100644
--- a/fs/filter/filter_test.go
+++ b/fs/filter/filter_test.go
@@ -3,7 +3,6 @@ package filter
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"strings"
 	"sync"
@@ -30,7 +29,7 @@ func TestNewFilterDefault(t *testing.T) {
 
 // testFile creates a temp file with the contents
 func testFile(t *testing.T, contents string) string {
-	out, err := ioutil.TempFile("", "filter_test")
+	out, err := os.CreateTemp("", "filter_test")
 	require.NoError(t, err)
 	defer func() {
 		err := out.Close()
diff --git a/fs/fspath/path_test.go b/fs/fspath/path_test.go
index e0f72681f..e1ac7866d 100644
--- a/fs/fspath/path_test.go
+++ b/fs/fspath/path_test.go
@@ -3,7 +3,6 @@ package fspath
 import (
 	"flag"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"runtime"
@@ -424,7 +423,7 @@ func TestParse(t *testing.T) {
 		if *makeCorpus {
 			// write the test corpus for fuzzing
 			require.NoError(t, os.MkdirAll("corpus", 0777))
-			require.NoError(t, ioutil.WriteFile(fmt.Sprintf("corpus/%02d", testNumber), []byte(test.in), 0666))
+			require.NoError(t, os.WriteFile(fmt.Sprintf("corpus/%02d", testNumber), []byte(test.in), 0666))
 		}
 
 	}
diff --git a/fs/object/object_test.go b/fs/object/object_test.go
index d5cdfe619..a00cf29a0 100644
--- a/fs/object/object_test.go
+++ b/fs/object/object_test.go
@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"context"
 	"io"
-	"io/ioutil"
 	"testing"
 	"time"
 
@@ -110,7 +109,7 @@ func TestMemoryObject(t *testing.T) {
 	assert.Equal(t, newNow, o.ModTime(context.Background()))
 
 	checkOpen := func(rc io.ReadCloser, expected string) {
-		actual, err := ioutil.ReadAll(rc)
+		actual, err := io.ReadAll(rc)
 		assert.NoError(t, err)
 		err = rc.Close()
 		assert.NoError(t, err)
diff --git a/fs/operations/operations_test.go b/fs/operations/operations_test.go
index 20fcc61a0..8f8229c4f 100644
--- a/fs/operations/operations_test.go
+++ b/fs/operations/operations_test.go
@@ -26,7 +26,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
 	"os"
@@ -320,7 +319,7 @@ func TestHashSumsWithErrors(t *testing.T) {
 
 func TestHashStream(t *testing.T) {
 	reader := strings.NewReader("")
-	in := ioutil.NopCloser(reader)
+	in := io.NopCloser(reader)
 	out := &bytes.Buffer{}
 	for _, test := range []struct {
 		input      string
@@ -1590,11 +1589,11 @@ func TestRcat(t *testing.T) {
 		data2 := string(make([]byte, ci.StreamingUploadCutoff+1))
 		path2 := prefix + "big_file_from_pipe"
 
-		in := ioutil.NopCloser(strings.NewReader(data1))
+		in := io.NopCloser(strings.NewReader(data1))
 		_, err := operations.Rcat(ctx, r.Fremote, path1, in, t1)
 		require.NoError(t, err)
 
-		in = ioutil.NopCloser(strings.NewReader(data2))
+		in = io.NopCloser(strings.NewReader(data2))
 		_, err = operations.Rcat(ctx, r.Fremote, path2, in, t2)
 		require.NoError(t, err)
 
@@ -1621,15 +1620,15 @@ func TestRcatSize(t *testing.T) {
 	file1 := r.WriteFile("potato1", body, t1)
 	file2 := r.WriteFile("potato2", body, t2)
 	// Test with known length
-	bodyReader := ioutil.NopCloser(strings.NewReader(body))
+	bodyReader := io.NopCloser(strings.NewReader(body))
 	obj, err := operations.RcatSize(ctx, r.Fremote, file1.Path, bodyReader, int64(len(body)), file1.ModTime)
 	require.NoError(t, err)
 	assert.Equal(t, int64(len(body)), obj.Size())
 	assert.Equal(t, file1.Path, obj.Remote())
 
 	// Test with unknown length
-	bodyReader = ioutil.NopCloser(strings.NewReader(body)) // reset Reader
-	ioutil.NopCloser(strings.NewReader(body))
+	bodyReader = io.NopCloser(strings.NewReader(body)) // reset Reader
+	io.NopCloser(strings.NewReader(body))
 	obj, err = operations.RcatSize(ctx, r.Fremote, file2.Path, bodyReader, -1, file2.ModTime)
 	require.NoError(t, err)
 	assert.Equal(t, int64(len(body)), obj.Size())
diff --git a/fs/operations/reopen_test.go b/fs/operations/reopen_test.go
index 3a75bc0bc..2a7195b56 100644
--- a/fs/operations/reopen_test.go
+++ b/fs/operations/reopen_test.go
@@ -4,7 +4,6 @@ import (
 	"context"
 	"errors"
 	"io"
-	"io/ioutil"
 	"testing"
 
 	"github.com/rclone/rclone/fs"
@@ -83,7 +82,7 @@ func TestReOpen(t *testing.T) {
 				assert.NoError(t, err)
 
 				// Check contents read correctly
-				got, err := ioutil.ReadAll(h)
+				got, err := io.ReadAll(h)
 				assert.NoError(t, err)
 				assert.Equal(t, expectedRead, got)
 
@@ -118,7 +117,7 @@ func TestReOpen(t *testing.T) {
 				assert.NoError(t, err)
 
 				// check contents
-				got, err := ioutil.ReadAll(h)
+				got, err := io.ReadAll(h)
 				assert.NoError(t, err)
 				assert.Equal(t, expectedRead, got)
 
@@ -132,7 +131,7 @@ func TestReOpen(t *testing.T) {
 				assert.NoError(t, err)
 
 				// check contents
-				got, err := ioutil.ReadAll(h)
+				got, err := io.ReadAll(h)
 				assert.Equal(t, errorTestError, err)
 				assert.Equal(t, expectedRead[:6], got)
 
diff --git a/fs/rc/rcserver/rcserver_test.go b/fs/rc/rcserver/rcserver_test.go
index 34181b631..62c5e2537 100644
--- a/fs/rc/rcserver/rcserver_test.go
+++ b/fs/rc/rcserver/rcserver_test.go
@@ -5,7 +5,6 @@ import (
 	"context"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
 	"os"
@@ -77,7 +76,7 @@ func TestRcServer(t *testing.T) {
 	}
 
 	require.NoError(t, err)
-	body, err := ioutil.ReadAll(resp.Body)
+	body, err := io.ReadAll(resp.Body)
 	_ = resp.Body.Close()
 
 	require.NoError(t, err)
@@ -132,7 +131,7 @@ func testServer(t *testing.T, tests []testRun, opt *rc.Options) {
 			resp := w.Result()
 
 			assert.Equal(t, test.Status, resp.StatusCode)
-			body, err := ioutil.ReadAll(resp.Body)
+			body, err := io.ReadAll(resp.Body)
 			require.NoError(t, err)
 
 			if test.Contains == nil {
diff --git a/fstest/fstest.go b/fstest/fstest.go
index c39ccdec7..1c23b5373 100644
--- a/fstest/fstest.go
+++ b/fstest/fstest.go
@@ -9,7 +9,6 @@ import (
 	"flag"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"math/rand"
 	"os"
@@ -411,7 +410,7 @@ func Time(timeString string) time.Time {
 
 // LocalRemote creates a temporary directory name for local remotes
 func LocalRemote() (path string, err error) {
-	path, err = ioutil.TempDir("", "rclone")
+	path, err = os.MkdirTemp("", "rclone")
 	if err == nil {
 		// Now remove the directory
 		err = os.Remove(path)
diff --git a/fstest/fstests/fstests.go b/fstest/fstests/fstests.go
index 3b9767eaf..f0aca7d3c 100644
--- a/fstest/fstests/fstests.go
+++ b/fstest/fstests/fstests.go
@@ -12,7 +12,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"math/bits"
 	"os"
 	"path"
@@ -303,7 +302,7 @@ func ReadObject(ctx context.Context, t *testing.T, obj fs.Object, limit int64, o
 	if limit >= 0 {
 		r = &io.LimitedReader{R: r, N: limit}
 	}
-	contents, err := ioutil.ReadAll(r)
+	contents, err := io.ReadAll(r)
 	require.NoError(t, err, what)
 	err = in.Close()
 	require.NoError(t, err, what)
diff --git a/fstest/run.go b/fstest/run.go
index 8751b3d23..eb9b0c3b5 100644
--- a/fstest/run.go
+++ b/fstest/run.go
@@ -29,7 +29,6 @@ import (
 	"context"
 	"flag"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"os"
 	"path"
@@ -99,7 +98,7 @@ func newRun() *Run {
 		r.Fatalf("Failed to open remote %q: %v", *RemoteName, err)
 	}
 
-	r.LocalName, err = ioutil.TempDir("", "rclone")
+	r.LocalName, err = os.MkdirTemp("", "rclone")
 	if err != nil {
 		r.Fatalf("Failed to create temp dir: %v", err)
 	}
@@ -221,7 +220,7 @@ func (r *Run) WriteFile(filePath, content string, t time.Time) Item {
 	if err != nil {
 		r.Fatalf("Failed to make directories %q: %v", dirPath, err)
 	}
-	err = ioutil.WriteFile(filePath, []byte(content), 0600)
+	err = os.WriteFile(filePath, []byte(content), 0600)
 	if err != nil {
 		r.Fatalf("Failed to write file %q: %v", filePath, err)
 	}
diff --git a/fstest/test_all/config.go b/fstest/test_all/config.go
index 62d8c057c..45920e25e 100644
--- a/fstest/test_all/config.go
+++ b/fstest/test_all/config.go
@@ -4,8 +4,8 @@ package main
 
 import (
 	"fmt"
-	"io/ioutil"
 	"log"
+	"os"
 	"path"
 
 	"github.com/rclone/rclone/fs"
@@ -110,7 +110,7 @@ type Config struct {
 
 // NewConfig reads the config file
 func NewConfig(configFile string) (*Config, error) {
-	d, err := ioutil.ReadFile(configFile)
+	d, err := os.ReadFile(configFile)
 	if err != nil {
 		return nil, fmt.Errorf("failed to read config file: %w", err)
 	}
diff --git a/fstest/test_all/report.go b/fstest/test_all/report.go
index cbc48f5cb..0dd4167d5 100644
--- a/fstest/test_all/report.go
+++ b/fstest/test_all/report.go
@@ -4,7 +4,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"html/template"
-	"io/ioutil"
 	"log"
 	"os"
 	"os/exec"
@@ -69,7 +68,7 @@ func NewReport() *Report {
 	r.DateTime = r.StartTime.Format(timeFormat)
 
 	// Find previous log directory if possible
-	names, err := ioutil.ReadDir(*outputDir)
+	names, err := os.ReadDir(*outputDir)
 	if err == nil && len(names) > 0 {
 		r.Previous = names[len(names)-1].Name()
 	}
@@ -152,7 +151,7 @@ func (r *Report) LogJSON() {
 	if err != nil {
 		log.Fatalf("Failed to marshal data for index.json: %v", err)
 	}
-	err = ioutil.WriteFile(path.Join(r.LogDir, "index.json"), out, 0666)
+	err = os.WriteFile(path.Join(r.LogDir, "index.json"), out, 0666)
 	if err != nil {
 		log.Fatalf("Failed to write index.json: %v", err)
 	}
diff --git a/lib/file/file_test.go b/lib/file/file_test.go
index 933fb4ca7..eab7bf7c1 100644
--- a/lib/file/file_test.go
+++ b/lib/file/file_test.go
@@ -3,7 +3,6 @@ package file
 import (
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
 	"path"
 	"runtime"
@@ -16,7 +15,7 @@ import (
 // This lists dir and checks the listing is as expected without checking the size
 func checkListingNoSize(t *testing.T, dir string, want []string) {
 	var got []string
-	nodes, err := ioutil.ReadDir(dir)
+	nodes, err := os.ReadDir(dir)
 	require.NoError(t, err)
 	for _, node := range nodes {
 		got = append(got, fmt.Sprintf("%s,%v", node.Name(), node.IsDir()))
@@ -27,10 +26,12 @@ func checkListingNoSize(t *testing.T, dir string, want []string) {
 // This lists dir and checks the listing is as expected
 func checkListing(t *testing.T, dir string, want []string) {
 	var got []string
-	nodes, err := ioutil.ReadDir(dir)
+	nodes, err := os.ReadDir(dir)
 	require.NoError(t, err)
 	for _, node := range nodes {
-		got = append(got, fmt.Sprintf("%s,%d,%v", node.Name(), node.Size(), node.IsDir()))
+		info, err := node.Info()
+		assert.NoError(t, err)
+		got = append(got, fmt.Sprintf("%s,%d,%v", node.Name(), info.Size(), node.IsDir()))
 	}
 	assert.Equal(t, want, got)
 }
diff --git a/lib/http/serve/dir_test.go b/lib/http/serve/dir_test.go
index bdcf3f948..be128ae33 100644
--- a/lib/http/serve/dir_test.go
+++ b/lib/http/serve/dir_test.go
@@ -3,7 +3,7 @@ package serve
 import (
 	"errors"
 	"html/template"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"net/http/httptest"
 	"net/url"
@@ -94,7 +94,7 @@ func TestError(t *testing.T) {
 	Error("potato", w, "sausage", err)
 	resp := w.Result()
 	assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
-	body, _ := ioutil.ReadAll(resp.Body)
+	body, _ := io.ReadAll(resp.Body)
 	assert.Equal(t, "sausage.\n", string(body))
 }
 
@@ -108,7 +108,7 @@ func TestServe(t *testing.T) {
 	d.Serve(w, r)
 	resp := w.Result()
 	assert.Equal(t, http.StatusOK, resp.StatusCode)
-	body, _ := ioutil.ReadAll(resp.Body)
+	body, _ := io.ReadAll(resp.Body)
 	assert.Equal(t, `<!DOCTYPE html>
 <html lang="en">
 <head>
diff --git a/lib/http/serve/serve_test.go b/lib/http/serve/serve_test.go
index 934fabe65..b6cc70976 100644
--- a/lib/http/serve/serve_test.go
+++ b/lib/http/serve/serve_test.go
@@ -1,7 +1,7 @@
 package serve
 
 import (
-	"io/ioutil"
+	"io"
 	"net/http"
 	"net/http/httptest"
 	"testing"
@@ -17,7 +17,7 @@ func TestObjectBadMethod(t *testing.T) {
 	Object(w, r, o)
 	resp := w.Result()
 	assert.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode)
-	body, _ := ioutil.ReadAll(resp.Body)
+	body, _ := io.ReadAll(resp.Body)
 	assert.Equal(t, "Method Not Allowed\n", string(body))
 }
 
@@ -30,7 +30,7 @@ func TestObjectHEAD(t *testing.T) {
 	assert.Equal(t, http.StatusOK, resp.StatusCode)
 	assert.Equal(t, "5", resp.Header.Get("Content-Length"))
 	assert.Equal(t, "bytes", resp.Header.Get("Accept-Ranges"))
-	body, _ := ioutil.ReadAll(resp.Body)
+	body, _ := io.ReadAll(resp.Body)
 	assert.Equal(t, "", string(body))
 }
 
@@ -43,7 +43,7 @@ func TestObjectGET(t *testing.T) {
 	assert.Equal(t, http.StatusOK, resp.StatusCode)
 	assert.Equal(t, "5", resp.Header.Get("Content-Length"))
 	assert.Equal(t, "bytes", resp.Header.Get("Accept-Ranges"))
-	body, _ := ioutil.ReadAll(resp.Body)
+	body, _ := io.ReadAll(resp.Body)
 	assert.Equal(t, "hello", string(body))
 }
 
@@ -58,7 +58,7 @@ func TestObjectRange(t *testing.T) {
 	assert.Equal(t, "3", resp.Header.Get("Content-Length"))
 	assert.Equal(t, "bytes", resp.Header.Get("Accept-Ranges"))
 	assert.Equal(t, "bytes 3-5/10", resp.Header.Get("Content-Range"))
-	body, _ := ioutil.ReadAll(resp.Body)
+	body, _ := io.ReadAll(resp.Body)
 	assert.Equal(t, "345", string(body))
 }
 
@@ -71,6 +71,6 @@ func TestObjectBadRange(t *testing.T) {
 	resp := w.Result()
 	assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
 	assert.Equal(t, "10", resp.Header.Get("Content-Length"))
-	body, _ := ioutil.ReadAll(resp.Body)
+	body, _ := io.ReadAll(resp.Body)
 	assert.Equal(t, "Bad Request\n", string(body))
 }
diff --git a/lib/readers/pattern_reader_test.go b/lib/readers/pattern_reader_test.go
index 10c21d064..36f57c921 100644
--- a/lib/readers/pattern_reader_test.go
+++ b/lib/readers/pattern_reader_test.go
@@ -2,7 +2,6 @@ package readers
 
 import (
 	"io"
-	"io/ioutil"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -13,7 +12,7 @@ func TestPatternReader(t *testing.T) {
 	b2 := make([]byte, 1)
 
 	r := NewPatternReader(0)
-	b, err := ioutil.ReadAll(r)
+	b, err := io.ReadAll(r)
 	require.NoError(t, err)
 	assert.Equal(t, []byte{}, b)
 	n, err := r.Read(b2)
@@ -21,7 +20,7 @@ func TestPatternReader(t *testing.T) {
 	require.Equal(t, 0, n)
 
 	r = NewPatternReader(10)
-	b, err = ioutil.ReadAll(r)
+	b, err = io.ReadAll(r)
 	require.NoError(t, err)
 	assert.Equal(t, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, b)
 	n, err = r.Read(b2)
@@ -31,7 +30,7 @@ func TestPatternReader(t *testing.T) {
 
 func TestPatternReaderSeek(t *testing.T) {
 	r := NewPatternReader(1024)
-	b, err := ioutil.ReadAll(r)
+	b, err := io.ReadAll(r)
 	require.NoError(t, err)
 
 	for i := range b {
diff --git a/vfs/file_test.go b/vfs/file_test.go
index 2143e34b6..a2b742315 100644
--- a/vfs/file_test.go
+++ b/vfs/file_test.go
@@ -3,7 +3,7 @@ package vfs
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"os"
 	"testing"
 
@@ -168,7 +168,7 @@ func fileCheckContents(t *testing.T, file *File) {
 	fd, err := file.Open(os.O_RDONLY)
 	require.NoError(t, err)
 
-	contents, err := ioutil.ReadAll(fd)
+	contents, err := io.ReadAll(fd)
 	require.NoError(t, err)
 	assert.Equal(t, "file1 contents", string(contents))
 
@@ -217,7 +217,7 @@ func TestFileOpenReadUnknownSize(t *testing.T) {
 	assert.Equal(t, int64(0), fd.Size())
 
 	// check the contents are not empty even though size is empty
-	gotContents, err := ioutil.ReadAll(fd)
+	gotContents, err := io.ReadAll(fd)
 	require.NoError(t, err)
 	assert.Equal(t, contents, gotContents)
 	t.Logf("gotContents = %q", gotContents)
diff --git a/vfs/make_open_tests.go b/vfs/make_open_tests.go
index d20a0e290..d4e64f058 100644
--- a/vfs/make_open_tests.go
+++ b/vfs/make_open_tests.go
@@ -17,7 +17,6 @@ package main
 import (
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"os"
 	"strings"
@@ -196,7 +195,7 @@ type openTest struct{
 // combination of flags.  This obeys Unix semantics even on Windows.
 var openTests = []openTest{
 `)
-	f, err := ioutil.TempFile("", "open-test")
+	f, err := os.CreateTemp("", "open-test")
 	if err != nil {
 		log.Fatal(err)
 	}
diff --git a/vfs/read_write_test.go b/vfs/read_write_test.go
index c43bc56ff..ee8051c86 100644
--- a/vfs/read_write_test.go
+++ b/vfs/read_write_test.go
@@ -5,7 +5,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
 	"testing"
 	"time"
@@ -620,7 +619,7 @@ func testRWFileHandleOpenTest(t *testing.T, vfs *VFS, test *openTest) {
 	// read the file
 	f, err = vfs.OpenFile(fileName, os.O_RDONLY, 0)
 	require.NoError(t, err)
-	buf, err := ioutil.ReadAll(f)
+	buf, err := io.ReadAll(f)
 	require.NoError(t, err)
 	err = f.Close()
 	require.NoError(t, err)
diff --git a/vfs/test_vfs/test_vfs.go b/vfs/test_vfs/test_vfs.go
index bd11ac02c..955362357 100644
--- a/vfs/test_vfs/test_vfs.go
+++ b/vfs/test_vfs/test_vfs.go
@@ -7,7 +7,6 @@ import (
 	"flag"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"math"
 	"math/rand"
@@ -112,7 +111,7 @@ func (t *Test) errorf(format string, a ...interface{}) {
 // list test
 func (t *Test) list() {
 	t.logf("list")
-	fis, err := ioutil.ReadDir(t.dir)
+	fis, err := os.ReadDir(t.dir)
 	if err != nil {
 		t.errorf("%s: failed to read directory: %v", t.dir, err)
 		return
diff --git a/vfs/vfscache/downloaders/downloaders_test.go b/vfs/vfscache/downloaders/downloaders_test.go
index fdbd942dc..9fa425192 100644
--- a/vfs/vfscache/downloaders/downloaders_test.go
+++ b/vfs/vfscache/downloaders/downloaders_test.go
@@ -3,7 +3,6 @@ package downloaders
 import (
 	"context"
 	"io"
-	"io/ioutil"
 	"sync"
 	"testing"
 	"time"
@@ -85,7 +84,7 @@ func TestDownloaders(t *testing.T) {
 	)
 
 	// Write the test file
-	in := ioutil.NopCloser(readers.NewPatternReader(size))
+	in := io.NopCloser(readers.NewPatternReader(size))
 	src, err := operations.RcatSize(ctx, r.Fremote, remote, in, size, time.Now())
 	require.NoError(t, err)
 	assert.Equal(t, size, src.Size())
diff --git a/vfs/vfscache/item_test.go b/vfs/vfscache/item_test.go
index 59561708b..676380f11 100644
--- a/vfs/vfscache/item_test.go
+++ b/vfs/vfscache/item_test.go
@@ -6,7 +6,6 @@ import (
 	"context"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"math/rand"
 	"os"
 	"sync"
@@ -42,7 +41,7 @@ func checkObject(t *testing.T, r *fstest.Run, remote string, contents string) {
 	require.NoError(t, err)
 	in, err := obj.Open(context.Background())
 	require.NoError(t, err)
-	buf, err := ioutil.ReadAll(in)
+	buf, err := io.ReadAll(in)
 	require.NoError(t, err)
 	require.NoError(t, in.Close())
 	assert.Equal(t, contents, string(buf))
diff --git a/vfs/vfstest/os.go b/vfs/vfstest/os.go
index a27f3ead2..d12f55ddb 100644
--- a/vfs/vfstest/os.go
+++ b/vfs/vfstest/os.go
@@ -1,7 +1,6 @@
 package vfstest
 
 import (
-	"io/ioutil"
 	"os"
 	"time"
 
@@ -88,12 +87,24 @@ func (r realOs) OpenFile(name string, flags int, perm os.FileMode) (vfs.Handle,
 
 // ReadDir
 func (r realOs) ReadDir(dirname string) ([]os.FileInfo, error) {
-	return ioutil.ReadDir(dirname)
+	entries, err := os.ReadDir(dirname)
+	if err != nil {
+		return nil, err
+	}
+	infos := make([]os.FileInfo, 0, len(entries))
+	for _, entry := range entries {
+		info, err := entry.Info()
+		if err != nil {
+			return nil, err
+		}
+		infos = append(infos, info)
+	}
+	return infos, nil
 }
 
 // ReadFile
 func (r realOs) ReadFile(filename string) (b []byte, err error) {
-	return ioutil.ReadFile(filename)
+	return os.ReadFile(filename)
 }
 
 // Remove
diff --git a/vfs/vfstest/read.go b/vfs/vfstest/read.go
index 469c5459f..6b34a771a 100644
--- a/vfs/vfstest/read.go
+++ b/vfs/vfstest/read.go
@@ -2,7 +2,6 @@ package vfstest
 
 import (
 	"io"
-	"io/ioutil"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -89,7 +88,7 @@ func TestReadSeek(t *testing.T) {
 	_, err = fd.Seek(5, io.SeekStart)
 	assert.NoError(t, err)
 
-	buf, err := ioutil.ReadAll(fd)
+	buf, err := io.ReadAll(fd)
 	assert.NoError(t, err)
 	assert.Equal(t, buf, []byte("HELLO"))
 
@@ -97,7 +96,7 @@ func TestReadSeek(t *testing.T) {
 	_, err = fd.Seek(10, io.SeekStart)
 	assert.NoError(t, err)
 
-	buf, err = ioutil.ReadAll(fd)
+	buf, err = io.ReadAll(fd)
 	assert.NoError(t, err)
 	assert.Equal(t, buf, []byte(""))
 
@@ -105,7 +104,7 @@ func TestReadSeek(t *testing.T) {
 	_, err = fd.Seek(1000000, io.SeekStart)
 	assert.NoError(t, err)
 
-	buf, err = ioutil.ReadAll(fd)
+	buf, err = io.ReadAll(fd)
 	assert.NoError(t, err)
 	assert.Equal(t, buf, []byte(""))
 
@@ -113,7 +112,7 @@ func TestReadSeek(t *testing.T) {
 	_, err = fd.Seek(0, io.SeekStart)
 	assert.NoError(t, err)
 
-	buf, err = ioutil.ReadAll(fd)
+	buf, err = io.ReadAll(fd)
 	assert.NoError(t, err)
 	assert.Equal(t, buf, []byte("helloHELLO"))
 
diff --git a/vfs/vfstest/submount.go b/vfs/vfstest/submount.go
index 7c28de8a9..d8e8fb6ff 100644
--- a/vfs/vfstest/submount.go
+++ b/vfs/vfstest/submount.go
@@ -7,7 +7,6 @@ import (
 	"flag"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"os"
 	"os/exec"
@@ -100,7 +99,7 @@ func (r *Run) startMountSubProcess() {
 // Find a free path to run the mount on
 func findMountPath() string {
 	if runtime.GOOS != "windows" {
-		mountPath, err := ioutil.TempDir("", "rclonefs-mount")
+		mountPath, err := os.MkdirTemp("", "rclonefs-mount")
 		if err != nil {
 			log.Fatalf("Failed to create mount dir: %v", err)
 		}