1// Copyright 2009 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package os
6
7import (
8	"internal/poll"
9	"io/fs"
10)
11
12// Portable analogs of some common system call errors.
13//
14// Errors returned from this package may be tested against these errors
15// with [errors.Is].
16var (
17	// ErrInvalid indicates an invalid argument.
18	// Methods on File will return this error when the receiver is nil.
19	ErrInvalid = fs.ErrInvalid // "invalid argument"
20
21	ErrPermission = fs.ErrPermission // "permission denied"
22	ErrExist      = fs.ErrExist      // "file already exists"
23	ErrNotExist   = fs.ErrNotExist   // "file does not exist"
24	ErrClosed     = fs.ErrClosed     // "file already closed"
25
26	ErrNoDeadline       = errNoDeadline()       // "file type does not support deadline"
27	ErrDeadlineExceeded = errDeadlineExceeded() // "i/o timeout"
28)
29
30func errNoDeadline() error { return poll.ErrNoDeadline }
31
32// errDeadlineExceeded returns the value for os.ErrDeadlineExceeded.
33// This error comes from the internal/poll package, which is also
34// used by package net. Doing it this way ensures that the net
35// package will return os.ErrDeadlineExceeded for an exceeded deadline,
36// as documented by net.Conn.SetDeadline, without requiring any extra
37// work in the net package and without requiring the internal/poll
38// package to import os (which it can't, because that would be circular).
39func errDeadlineExceeded() error { return poll.ErrDeadlineExceeded }
40
41type timeout interface {
42	Timeout() bool
43}
44
45// PathError records an error and the operation and file path that caused it.
46type PathError = fs.PathError
47
48// SyscallError records an error from a specific system call.
49type SyscallError struct {
50	Syscall string
51	Err     error
52}
53
54func (e *SyscallError) Error() string { return e.Syscall + ": " + e.Err.Error() }
55
56func (e *SyscallError) Unwrap() error { return e.Err }
57
58// Timeout reports whether this error represents a timeout.
59func (e *SyscallError) Timeout() bool {
60	t, ok := e.Err.(timeout)
61	return ok && t.Timeout()
62}
63
64// NewSyscallError returns, as an error, a new [SyscallError]
65// with the given system call name and error details.
66// As a convenience, if err is nil, NewSyscallError returns nil.
67func NewSyscallError(syscall string, err error) error {
68	if err == nil {
69		return nil
70	}
71	return &SyscallError{syscall, err}
72}
73
74// IsExist returns a boolean indicating whether its argument is known to report
75// that a file or directory already exists. It is satisfied by [ErrExist] as
76// well as some syscall errors.
77//
78// This function predates [errors.Is]. It only supports errors returned by
79// the os package. New code should use errors.Is(err, fs.ErrExist).
80func IsExist(err error) bool {
81	return underlyingErrorIs(err, ErrExist)
82}
83
84// IsNotExist returns a boolean indicating whether its argument is known to
85// report that a file or directory does not exist. It is satisfied by
86// [ErrNotExist] as well as some syscall errors.
87//
88// This function predates [errors.Is]. It only supports errors returned by
89// the os package. New code should use errors.Is(err, fs.ErrNotExist).
90func IsNotExist(err error) bool {
91	return underlyingErrorIs(err, ErrNotExist)
92}
93
94// IsPermission returns a boolean indicating whether its argument is known to
95// report that permission is denied. It is satisfied by [ErrPermission] as well
96// as some syscall errors.
97//
98// This function predates [errors.Is]. It only supports errors returned by
99// the os package. New code should use errors.Is(err, fs.ErrPermission).
100func IsPermission(err error) bool {
101	return underlyingErrorIs(err, ErrPermission)
102}
103
104// IsTimeout returns a boolean indicating whether its argument is known
105// to report that a timeout occurred.
106//
107// This function predates [errors.Is], and the notion of whether an
108// error indicates a timeout can be ambiguous. For example, the Unix
109// error EWOULDBLOCK sometimes indicates a timeout and sometimes does not.
110// New code should use errors.Is with a value appropriate to the call
111// returning the error, such as [os.ErrDeadlineExceeded].
112func IsTimeout(err error) bool {
113	terr, ok := underlyingError(err).(timeout)
114	return ok && terr.Timeout()
115}
116
117func underlyingErrorIs(err, target error) bool {
118	// Note that this function is not errors.Is:
119	// underlyingError only unwraps the specific error-wrapping types
120	// that it historically did, not all errors implementing Unwrap().
121	err = underlyingError(err)
122	if err == target {
123		return true
124	}
125	// To preserve prior behavior, only examine syscall errors.
126	e, ok := err.(syscallErrorType)
127	return ok && e.Is(target)
128}
129
130// underlyingError returns the underlying error for known os error types.
131func underlyingError(err error) error {
132	switch err := err.(type) {
133	case *PathError:
134		return err.Err
135	case *LinkError:
136		return err.Err
137	case *SyscallError:
138		return err.Err
139	}
140	return err
141}
142