Repository: kubernetes-sigs/cloud-provider-azure
Pull request #7592 (2024-11-14 17:46:38)

build(deps): bump github/codeql-action from 3.27.3 to 3.27.4
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.27.3 to 3.27.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/github/codeql-action/releases">github/codeql-action's releases</a>.</em></p>
<blockquote>
<h2>v3.27.4</h2>
<h1>CodeQL Action Changelog</h1>
<p>See the <a href="https://github.com/github/codeql-action/releases">releases page</a> for the relevant changes to the CodeQL CLI and language packs.</p>
<p>Note that the only difference between <code>v2</code> and <code>v3</code> of the CodeQL Action is the node version they support, with <code>v3</code> running on node 20 while we continue to release <code>v2</code> to support running on node 16. For example <code>3.22.11</code> was the first <code>v3</code> release and is functionally identical to <code>2.22.11</code>. This approach ensures an easy way to track exactly which features are included in different versions, indicated by the minor and patch version numbers.</p>
<h2>3.27.4 - 14 Nov 2024</h2>
<p>No user facing changes.</p>
<p>See the full <a href="https://github.com/github/codeql-action/blob/v3.27.4/CHANGELOG.md">CHANGELOG.md</a> for more information.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action's changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a href="https://github.com/github/codeql-action/releases">releases page</a> for the relevant changes to the CodeQL CLI and language packs.</p>
<p>Note that the only difference between <code>v2</code> and <code>v3</code> of the CodeQL Action is the node version they support, with <code>v3</code> running on node 20 while we continue to release <code>v2</code> to support running on node 16. For example <code>3.22.11</code> was the first <code>v3</code> release and is functionally identical to <code>2.22.11</code>. This approach ensures an easy way to track exactly which features are included in different versions, indicated by the minor and patch version numbers.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>3.27.4 - 14 Nov 2024</h2>
<p>No user facing changes.</p>
<h2>3.27.3 - 12 Nov 2024</h2>
<p>No user facing changes.</p>
<h2>3.27.2 - 12 Nov 2024</h2>
<ul>
<li>Fixed an issue where setting up the CodeQL tools would sometimes fail with the message &quot;Invalid value 'undefined' for header 'authorization'&quot;. <a href="https://redirect.github.com/github/codeql-action/pull/2590">#2590</a></li>
</ul>
<h2>3.27.1 - 08 Nov 2024</h2>
<ul>
<li>The CodeQL Action now downloads bundles compressed using Zstandard on GitHub Enterprise Server when using Linux or macOS runners. This speeds up the installation of the CodeQL tools. This feature is already available to GitHub.com users. <a href="https://redirect.github.com/github/codeql-action/pull/2573">#2573</a></li>
<li>Update default CodeQL bundle version to 2.19.3. <a href="https://redirect.github.com/github/codeql-action/pull/2576">#2576</a></li>
</ul>
<h2>3.27.0 - 22 Oct 2024</h2>
<ul>
<li>Bump the minimum CodeQL bundle version to 2.14.6. <a href="https://redirect.github.com/github/codeql-action/pull/2549">#2549</a></li>
<li>Fix an issue where the <code>upload-sarif</code> Action would fail with &quot;upload-sarif post-action step failed: Input required and not supplied: token&quot; when called in a composite Action that had a different set of inputs to the ones expected by the <code>upload-sarif</code> Action. <a href="https://redirect.github.com/github/codeql-action/pull/2557">#2557</a></li>
<li>Update default CodeQL bundle version to 2.19.2. <a href="https://redirect.github.com/github/codeql-action/pull/2552">#2552</a></li>
</ul>
<h2>3.26.13 - 14 Oct 2024</h2>
<p>No user facing changes.</p>
<h2>3.26.12 - 07 Oct 2024</h2>
<ul>
<li>
<p><em>Upcoming breaking change</em>: Add a deprecation warning for customers using CodeQL version 2.14.5 and earlier. These versions of CodeQL were discontinued on 24 September 2024 alongside GitHub Enterprise Server 3.10, and will be unsupported by CodeQL Action versions 3.27.0 and later and versions 2.27.0 and later. <a href="https://redirect.github.com/github/codeql-action/pull/2520">#2520</a></p>
<ul>
<li>
<p>If you are using one of these versions, please update to CodeQL CLI version 2.14.6 or later. For instance, if you have specified a custom version of the CLI using the 'tools' input to the 'init' Action, you can remove this input to use the default version.</p>
</li>
<li>
<p>Alternatively, if you want to continue using a version of the CodeQL CLI between 2.13.5 and 2.14.5, you can replace <code>github/codeql-action/*@v3</code> by <code>github/codeql-action/*@v3.26.11</code> and <code>github/codeql-action/*@v2</code> by <code>github/codeql-action/*@v2.26.11</code> in your code scanning workflow to ensure you continue using this version of the CodeQL Action.</p>
</li>
</ul>
</li>
</ul>
<h2>3.26.11 - 03 Oct 2024</h2>
<ul>
<li>
<p><em>Upcoming breaking change</em>: Add support for using <code>actions/download-artifact@v4</code> to programmatically consume CodeQL Action debug artifacts.</p>
<p>Starting November 30, 2024, GitHub.com customers will <a href="https://github.blog/changelog/2024-04-16-deprecation-notice-v3-of-the-artifact-actions/">no longer be able to use <code>actions/download-artifact@v3</code></a>. Therefore, to avoid breakage, customers who programmatically download the CodeQL Action debug artifacts should set the <code>CODEQL_ACTION_ARTIFACT_V4_UPGRADE</code> environment variable to <code>true</code> and bump <code>actions/download-artifact@v3</code> to <code>actions/download-artifact@v4</code> in their workflows. The CodeQL Action will enable this behavior by default in early November and workflows that have not yet bumped to <code>actions/download-artifact@v3</code> to <code>actions/download-artifact@v4</code> will begin failing then.</p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/github/codeql-action/commit/ea9e4e37992a54ee68a9622e985e60c8e8f12d9f"><code>ea9e4e3</code></a> Merge pull request <a href="https://redirect.github.com/github/codeql-action/issues/2605">#2605</a> from github/update-v3.27.4-3ab67a219</li>
<li><a href="https://github.com/github/codeql-action/commit/845ea9230baa1c02f895802221f61eb448caa267"><code>845ea92</code></a> Update changelog for v3.27.4</li>
<li><a href="https://github.com/github/codeql-action/commit/3ab67a21932f9425e7dca53353787b8dda8e89d9"><code>3ab67a2</code></a> Merge pull request <a href="https://redirect.github.com/github/codeql-action/issues/2597">#2597</a> from github/mbg/caching/output-improvements</li>
<li><a href="https://github.com/github/codeql-action/commit/6e3a010dfe7e41114c548b680d885bbd55b2834e"><code>6e3a010</code></a> Merge pull request <a href="https://redirect.github.com/github/codeql-action/issues/2583">#2583</a> from jsoref/use-artifact-4</li>
<li><a href="https://github.com/github/codeql-action/commit/1c83cd12920695d0a30eb88c71a10f79f5ae22b4"><code>1c83cd1</code></a> Upgrade actions/upload-artifact to v4</li>
<li><a href="https://github.com/github/codeql-action/commit/024283fcc9914a2f29343fa25558256c1799501f"><code>024283f</code></a> Merge pull request <a href="https://redirect.github.com/github/codeql-action/issues/2602">#2602</a> from github/mergeback/v3.27.3-to-main-396bb3e4</li>
<li><a href="https://github.com/github/codeql-action/commit/613fe96926eddbad17ae49a608c6cd6fb07c4d10"><code>613fe96</code></a> Update checked-in dependencies</li>
<li><a href="https://github.com/github/codeql-action/commit/e35d4aa1da5d4ef385c387ee6ad1f286494398f5"><code>e35d4aa</code></a> Update changelog and version after v3.27.3</li>
<li><a href="https://github.com/github/codeql-action/commit/7bc6222c12a03ce44091a2506a776d9f02e2096a"><code>7bc6222</code></a> Allow warnings in <code>tryGetFolderBytes</code> to be suppressed</li>
<li><a href="https://github.com/github/codeql-action/commit/b0c0aadc56663a4555f83f707b1811a9b4add99c"><code>b0c0aad</code></a> Handle <code>ReserveCacheError</code> with a nicer message</li>
<li>See full diff in <a href="https://github.com/github/codeql-action/compare/396bb3e45325a47dd9ef434068033c6d5bb0d11a...ea9e4e37992a54ee68a9622e985e60c8e8f12d9f">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action&package-manager=github_actions&previous-version=3.27.3&new-version=3.27.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>

k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

Approval requirements bypassed by manually added approval.

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/cloud-provider-azure/pull/7592#" title="Author self-approved">dependabot[bot]</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fcloud-provider-azure).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/cloud-provider-azure/blob/release-1.30/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["nilo19"]} -->
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

Approval requirements bypassed by manually added approval.

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/cloud-provider-azure/pull/7592#" title="Author self-approved">dependabot[bot]</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fcloud-provider-azure).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/cloud-provider-azure/blob/release-1.30/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["nilo19"]} -->

Patch:
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 4c1f87d7d..f9386782e 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -70,7 +70,7 @@ jobs:
 
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@396bb3e45325a47dd9ef434068033c6d5bb0d11a # v3.27.3
+      uses: github/codeql-action/init@ea9e4e37992a54ee68a9622e985e60c8e8f12d9f # v3.27.4
       with:
         languages: ${{ matrix.language }}
         # If you wish to specify custom queries, you can do so here or in a config file.
@@ -93,4 +93,4 @@ jobs:
         $(go env GOPATH)/bin/goveralls -coverprofile=profile.cov -service=github
     
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@396bb3e45325a47dd9ef434068033c6d5bb0d11a # v3.27.3
+      uses: github/codeql-action/analyze@ea9e4e37992a54ee68a9622e985e60c8e8f12d9f # v3.27.4
diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml
index 2e3a1066f..57eb6a5ff 100644
--- a/.github/workflows/scorecards.yml
+++ b/.github/workflows/scorecards.yml
@@ -72,6 +72,6 @@ jobs:
 
       # Upload the results to GitHub's code scanning dashboard.
       - name: "Upload to code-scanning"
-        uses: github/codeql-action/upload-sarif@396bb3e45325a47dd9ef434068033c6d5bb0d11a # v3.27.3
+        uses: github/codeql-action/upload-sarif@ea9e4e37992a54ee68a9622e985e60c8e8f12d9f # v3.27.4
         with:
           sarif_file: results.sarif
diff --git a/.github/workflows/trivy.yaml b/.github/workflows/trivy.yaml
index 817a33a43..5559f2bbd 100644
--- a/.github/workflows/trivy.yaml
+++ b/.github/workflows/trivy.yaml
@@ -67,7 +67,7 @@ jobs:
           vuln-type: 'os,library'
           severity: 'CRITICAL,HIGH,MEDIUM,LOW,UNKNOWN'
       - name: Upload Trivy scan results to GitHub Security tab
-        uses: github/codeql-action/upload-sarif@396bb3e45325a47dd9ef434068033c6d5bb0d11a # v3.27.3
+        uses: github/codeql-action/upload-sarif@ea9e4e37992a54ee68a9622e985e60c8e8f12d9f # v3.27.4
         with:
           sarif_file: 'trivy-ccm-results.sarif'
           category: azure-cloud-controller-manager-image
@@ -81,7 +81,7 @@ jobs:
           vuln-type: 'os,library'
           severity: 'CRITICAL,HIGH,MEDIUM,LOW,UNKNOWN'
       - name: Upload Trivy scan results to GitHub Security tab
-        uses: github/codeql-action/upload-sarif@396bb3e45325a47dd9ef434068033c6d5bb0d11a # v3.27.3
+        uses: github/codeql-action/upload-sarif@ea9e4e37992a54ee68a9622e985e60c8e8f12d9f # v3.27.4
         with:
           sarif_file: 'trivy-cnm-linux-results.sarif'
           category: azure-cloud-node-manager-linux-image
@@ -95,7 +95,7 @@ jobs:
           vuln-type: 'os,library'
           severity: 'CRITICAL,HIGH,MEDIUM,LOW,UNKNOWN'
       - name: Upload Trivy scan results to GitHub Security tab
-        uses: github/codeql-action/upload-sarif@396bb3e45325a47dd9ef434068033c6d5bb0d11a # v3.27.3
+        uses: github/codeql-action/upload-sarif@ea9e4e37992a54ee68a9622e985e60c8e8f12d9f # v3.27.4
         with:
           sarif_file: 'trivy-health-probe-proxy-linux-results.sarif'
           category: health-probe-proxy-linux-image
diff --git a/Dockerfile b/Dockerfile
index 9c34c4835..9e73ee082 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -14,7 +14,7 @@
 
 # syntax=docker/dockerfile:1
 
-FROM --platform=linux/amd64 golang:1.23-bullseye@sha256:1002103155eb5802f6876dceb8cb8bf965cc6d3deeb34f11e4774926aca6a1fd AS builder
+FROM --platform=linux/amd64 golang:1.23-bullseye@sha256:d4387d8b2ee5b78a199fae168a761d0fb0f33f172642d20856f29a67204af16b AS builder
 
 ARG ENABLE_GIT_COMMAND=true
 ARG ARCH=amd64
diff --git a/cloud-node-manager.Dockerfile b/cloud-node-manager.Dockerfile
index be185d9f5..c4fc398b7 100644
--- a/cloud-node-manager.Dockerfile
+++ b/cloud-node-manager.Dockerfile
@@ -14,7 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-FROM --platform=linux/amd64 golang:1.23-bullseye@sha256:1002103155eb5802f6876dceb8cb8bf965cc6d3deeb34f11e4774926aca6a1fd AS builder
+FROM --platform=linux/amd64 golang:1.23-bullseye@sha256:d4387d8b2ee5b78a199fae168a761d0fb0f33f172642d20856f29a67204af16b AS builder
 
 ARG ENABLE_GIT_COMMAND=true
 ARG ARCH=amd64
diff --git a/go.mod b/go.mod
index 18cb6c521..dc95b4870 100644
--- a/go.mod
+++ b/go.mod
@@ -20,7 +20,7 @@ require (
 	github.com/Azure/go-autorest/autorest/mocks v0.4.2
 	github.com/Azure/go-autorest/tracing v0.6.0
 	github.com/evanphx/json-patch v5.9.0+incompatible
-	github.com/fsnotify/fsnotify v1.7.0
+	github.com/fsnotify/fsnotify v1.8.0
 	github.com/go-logr/logr v1.4.2
 	github.com/onsi/ginkgo/v2 v2.21.0
 	github.com/onsi/gomega v1.35.1
diff --git a/go.sum b/go.sum
index 762f14d54..c5e0ab139 100644
--- a/go.sum
+++ b/go.sum
@@ -115,8 +115,8 @@ github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lSh
 github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
-github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
-github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
+github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
 github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
diff --git a/pkg/provider/azure_loadbalancer.go b/pkg/provider/azure_loadbalancer.go
index d93bcdd93..a4389d78b 100644
--- a/pkg/provider/azure_loadbalancer.go
+++ b/pkg/provider/azure_loadbalancer.go
@@ -712,15 +712,12 @@ func (az *Cloud) cleanOrphanedLoadBalancer(ctx context.Context, lb *network.Load
 }
 
 // safeDeleteLoadBalancer deletes the load balancer after decoupling it from the vmSet
-func (az *Cloud) safeDeleteLoadBalancer(ctx context.Context, lb network.LoadBalancer, clusterName, vmSetName string, service *v1.Service) *retry.Error {
-	lbBackendPoolIDs := az.getBackendPoolIDs(clusterName, ptr.Deref(lb.Name, ""))
+func (az *Cloud) safeDeleteLoadBalancer(ctx context.Context, lb network.LoadBalancer, _, vmSetName string, service *v1.Service) *retry.Error {
 	lbBackendPoolIDsToDelete := []string{}
-	v4Enabled, v6Enabled := getIPFamiliesEnabled(service)
-	if v4Enabled {
-		lbBackendPoolIDsToDelete = append(lbBackendPoolIDsToDelete, lbBackendPoolIDs[consts.IPVersionIPv4])
-	}
-	if v6Enabled {
-		lbBackendPoolIDsToDelete = append(lbBackendPoolIDsToDelete, lbBackendPoolIDs[consts.IPVersionIPv6])
+	if lb.LoadBalancerPropertiesFormat != nil && lb.BackendAddressPools != nil {
+		for _, bp := range *lb.BackendAddressPools {
+			lbBackendPoolIDsToDelete = append(lbBackendPoolIDsToDelete, ptr.Deref(bp.ID, ""))
+		}
 	}
 	if _, err := az.VMSet.EnsureBackendPoolDeleted(ctx, service, lbBackendPoolIDsToDelete, vmSetName, lb.BackendAddressPools, true); err != nil {
 		return retry.NewError(false, fmt.Errorf("safeDeleteLoadBalancer: failed to EnsureBackendPoolDeleted: %w", err))
@@ -1508,7 +1505,7 @@ func (az *Cloud) isFrontendIPChanged(
 	if fipIPVersion != "" {
 		isIPv6 = fipIPVersion == network.IPv6
 	} else {
-		if isIPv6, err = az.isFIPIPv6(service, pipRG, &config); err != nil {
+		if isIPv6, err = az.isFIPIPv6(service, &config); err != nil {
 			return false, err
 		}
 	}
@@ -1658,7 +1655,6 @@ func (az *Cloud) findFrontendIPConfigsOfService(
 	service *v1.Service,
 ) (map[bool]*network.FrontendIPConfiguration, error) {
 	fipsOfServiceMap := map[bool]*network.FrontendIPConfiguration{}
-	pipRG := az.getPublicIPAddressResourceGroup(service)
 	for _, config := range *fipConfigs {
 		config := config
 		owns, _, fipIPVersion := az.serviceOwnsFrontendIP(ctx, config, service)
@@ -1668,7 +1664,7 @@ func (az *Cloud) findFrontendIPConfigsOfService(
 			if fipIPVersion != "" {
 				fipIsIPv6 = fipIPVersion == network.IPv6
 			} else {
-				if fipIsIPv6, err = az.isFIPIPv6(service, pipRG, &config); err != nil {
+				if fipIsIPv6, err = az.isFIPIPv6(service, &config); err != nil {
 					return nil, err
 				}
 			}
@@ -1844,7 +1840,6 @@ func (az *Cloud) reconcileLoadBalancer(ctx context.Context, clusterName string,
 	}
 
 	// update probes/rules
-	pipRG := az.getPublicIPAddressResourceGroup(service)
 	for _, ownedFIPConfig := range ownedFIPConfigs {
 		if ownedFIPConfig == nil {
 			continue
@@ -1859,7 +1854,7 @@ func (az *Cloud) reconcileLoadBalancer(ctx context.Context, clusterName string,
 		if fipIPVersion != "" {
 			isIPv6 = fipIPVersion == network.IPv6
 		} else {
-			if isIPv6, err = az.isFIPIPv6(service, pipRG, ownedFIPConfig); err != nil {
+			if isIPv6, err = az.isFIPIPv6(service, ownedFIPConfig); err != nil {
 				return nil, err
 			}
 		}
@@ -2520,8 +2515,6 @@ func (az *Cloud) reconcileFrontendIPConfigs(
 			}
 		}
 
-		pipRG := az.getPublicIPAddressResourceGroup(service)
-
 		for i := len(newConfigs) - 1; i >= 0; i-- {
 			config := newConfigs[i]
 			isServiceOwnsFrontendIP, _, fipIPVersion := az.serviceOwnsFrontendIP(ctx, config, service)
@@ -2535,7 +2528,7 @@ func (az *Cloud) reconcileFrontendIPConfigs(
 			if fipIPVersion != "" {
 				isIPv6 = fipIPVersion == network.IPv6
 			} else {
-				if isIPv6, err = az.isFIPIPv6(service, pipRG, &config); err != nil {
+				if isIPv6, err = az.isFIPIPv6(service, &config); err != nil {
 					return nil, toDeleteConfigs, false, err
 				}
 			}
diff --git a/pkg/provider/azure_privatelinkservice.go b/pkg/provider/azure_privatelinkservice.go
index 684a2e474..48852fe5d 100644
--- a/pkg/provider/azure_privatelinkservice.go
+++ b/pkg/provider/azure_privatelinkservice.go
@@ -49,7 +49,6 @@ func (az *Cloud) reconcilePrivateLinkService(
 	wantPLS bool,
 ) error {
 	isinternal := requiresInternalLoadBalancer(service)
-	pipRG := az.getPublicIPAddressResourceGroup(service)
 	_, _, fipIPVersion := az.serviceOwnsFrontendIP(ctx, *fipConfig, service)
 	serviceName := getServiceName(service)
 	var isIPv6 bool
@@ -57,7 +56,7 @@ func (az *Cloud) reconcilePrivateLinkService(
 	if fipIPVersion != "" {
 		isIPv6 = fipIPVersion == network.IPv6
 	} else {
-		if isIPv6, err = az.isFIPIPv6(service, pipRG, fipConfig); err != nil {
+		if isIPv6, err = az.isFIPIPv6(service, fipConfig); err != nil {
 			klog.Errorf("reconcilePrivateLinkService for service(%s): failed to get FIP IP family: %v", serviceName, err)
 			return err
 		}
diff --git a/pkg/provider/azure_utils.go b/pkg/provider/azure_utils.go
index 253957d19..af288671a 100644
--- a/pkg/provider/azure_utils.go
+++ b/pkg/provider/azure_utils.go
@@ -438,9 +438,12 @@ func getResourceByIPFamily(resource string, isDualStack, isIPv6 bool) string {
 
 // isFIPIPv6 checks if the frontend IP configuration is of IPv6.
 // NOTICE: isFIPIPv6 assumes the FIP is owned by the Service and it is the primary Service.
-func (az *Cloud) isFIPIPv6(service *v1.Service, _ string, fip *network.FrontendIPConfiguration) (bool, error) {
+func (az *Cloud) isFIPIPv6(service *v1.Service, fip *network.FrontendIPConfiguration) (bool, error) {
 	isDualStack := isServiceDualStack(service)
 	if !isDualStack {
+		if len(service.Spec.IPFamilies) == 0 {
+			return false, nil
+		}
 		return service.Spec.IPFamilies[0] == v1.IPv6Protocol, nil
 	}
 	return managedResourceHasIPv6Suffix(pointer.StringDeref(fip.Name, "")), nil
diff --git a/pkg/provider/azure_vmss.go b/pkg/provider/azure_vmss.go
index 99ed13968..dcdbf43fe 100644
--- a/pkg/provider/azure_vmss.go
+++ b/pkg/provider/azure_vmss.go
@@ -1970,7 +1970,9 @@ func (ss *ScaleSet) EnsureBackendPoolDeleted(ctx context.Context, service *v1.Se
 
 	for _, backendPool := range *backendAddressPools {
 		for _, backendPoolID := range backendPoolIDs {
-			if strings.EqualFold(*backendPool.ID, backendPoolID) && backendPool.BackendIPConfigurations != nil {
+			if strings.EqualFold(*backendPool.ID, backendPoolID) &&
+				backendPool.BackendAddressPoolPropertiesFormat != nil &&
+				backendPool.BackendIPConfigurations != nil {
 				for _, ipConf := range *backendPool.BackendIPConfigurations {
 					if ipConf.ID == nil {
 						continue
diff --git a/vendor/github.com/fsnotify/fsnotify/.cirrus.yml b/vendor/github.com/fsnotify/fsnotify/.cirrus.yml
index ffc7b992b..f4e7dbf37 100644
--- a/vendor/github.com/fsnotify/fsnotify/.cirrus.yml
+++ b/vendor/github.com/fsnotify/fsnotify/.cirrus.yml
@@ -1,7 +1,7 @@
 freebsd_task:
   name: 'FreeBSD'
   freebsd_instance:
-    image_family: freebsd-13-2
+    image_family: freebsd-14-1
   install_script:
     - pkg update -f
     - pkg install -y go
@@ -9,5 +9,6 @@ freebsd_task:
       # run tests as user "cirrus" instead of root
     - pw useradd cirrus -m
     - chown -R cirrus:cirrus .
-    - FSNOTIFY_BUFFER=4096 sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./...
-    -                      sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./...
+    - FSNOTIFY_BUFFER=4096 sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race    ./...
+    -                      sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race    ./...
+    - FSNOTIFY_DEBUG=1     sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race -v ./...
diff --git a/vendor/github.com/fsnotify/fsnotify/.editorconfig b/vendor/github.com/fsnotify/fsnotify/.editorconfig
deleted file mode 100644
index fad895851..000000000
--- a/vendor/github.com/fsnotify/fsnotify/.editorconfig
+++ /dev/null
@@ -1,12 +0,0 @@
-root = true
-
-[*.go]
-indent_style = tab
-indent_size = 4
-insert_final_newline = true
-
-[*.{yml,yaml}]
-indent_style = space
-indent_size = 2
-insert_final_newline = true
-trim_trailing_whitespace = true
diff --git a/vendor/github.com/fsnotify/fsnotify/.gitattributes b/vendor/github.com/fsnotify/fsnotify/.gitattributes
deleted file mode 100644
index 32f1001be..000000000
--- a/vendor/github.com/fsnotify/fsnotify/.gitattributes
+++ /dev/null
@@ -1,1 +0,0 @@
-go.sum linguist-generated
diff --git a/vendor/github.com/fsnotify/fsnotify/.gitignore b/vendor/github.com/fsnotify/fsnotify/.gitignore
index 391cc076b..daea9dd6d 100644
--- a/vendor/github.com/fsnotify/fsnotify/.gitignore
+++ b/vendor/github.com/fsnotify/fsnotify/.gitignore
@@ -5,3 +5,6 @@
 # Output of go build ./cmd/fsnotify
 /fsnotify
 /fsnotify.exe
+
+/test/kqueue
+/test/a.out
diff --git a/vendor/github.com/fsnotify/fsnotify/CHANGELOG.md b/vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
index e0e575754..fa854785d 100644
--- a/vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
+++ b/vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
@@ -1,8 +1,36 @@
 # Changelog
 
-Unreleased
-----------
-Nothing yet.
+1.8.0 2023-10-31
+----------------
+
+### Additions
+
+- all: add `FSNOTIFY_DEBUG` to print debug logs to stderr ([#619])
+
+### Changes and fixes
+
+- windows: fix behaviour of `WatchList()` to be consistent with other platforms ([#610])
+
+- kqueue: ignore events with Ident=0 ([#590])
+
+- kqueue: set O_CLOEXEC to prevent passing file descriptors to children ([#617])
+
+- kqueue: emit events as "/path/dir/file" instead of "path/link/file" when watching a symlink ([#625])
+
+- inotify: don't send event for IN_DELETE_SELF when also watching the parent ([#620])
+
+- inotify: fix panic when calling Remove() in a goroutine ([#650])
+
+- fen: allow watching subdirectories of watched directories ([#621])
+
+[#590]: https://github.com/fsnotify/fsnotify/pull/590
+[#610]: https://github.com/fsnotify/fsnotify/pull/610
+[#617]: https://github.com/fsnotify/fsnotify/pull/617
+[#619]: https://github.com/fsnotify/fsnotify/pull/619
+[#620]: https://github.com/fsnotify/fsnotify/pull/620
+[#621]: https://github.com/fsnotify/fsnotify/pull/621
+[#625]: https://github.com/fsnotify/fsnotify/pull/625
+[#650]: https://github.com/fsnotify/fsnotify/pull/650
 
 1.7.0 - 2023-10-22
 ------------------
diff --git a/vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md b/vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
index ea379759d..e4ac2a2ff 100644
--- a/vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
+++ b/vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
@@ -1,7 +1,7 @@
 Thank you for your interest in contributing to fsnotify! We try to review and
 merge PRs in a reasonable timeframe, but please be aware that:
 
-- To avoid "wasted" work, please discus changes on the issue tracker first. You
+- To avoid "wasted" work, please discuss changes on the issue tracker first. You
   can just send PRs, but they may end up being rejected for one reason or the
   other.
 
@@ -20,6 +20,124 @@ platforms. Testing different platforms locally can be done with something like
 
 Use the `-short` flag to make the "stress test" run faster.
 
+Writing new tests
+-----------------
+Scripts in the testdata directory allow creating test cases in a "shell-like"
+syntax. The basic format is:
+
+    script
+
+    Output:
+    desired output
+
+For example:
+
+    # Create a new empty file with some data.
+    watch /
+    echo data >/file
+
+    Output:
+        create  /file
+        write   /file
+
+Just create a new file to add a new test; select which tests to run with
+`-run TestScript/[path]`.
+
+script
+------
+The script is a "shell-like" script:
+
+    cmd arg arg
+
+Comments are supported with `#`:
+
+    # Comment
+    cmd arg arg  # Comment
+
+All operations are done in a temp directory; a path like "/foo" is rewritten to
+"/tmp/TestFoo/foo".
+
+Arguments can be quoted with `"` or `'`; there are no escapes and they're
+functionally identical right now, but this may change in the future, so best to
+assume shell-like rules.
+
+    touch "/file with spaces"
+
+End-of-line escapes with `\` are not supported.
+
+### Supported commands
+
+    watch path [ops]    # Watch the path, reporting events for it. Nothing is
+                        # watched by default. Optionally a list of ops can be
+                        # given, as with AddWith(path, WithOps(...)).
+    unwatch path        # Stop watching the path.
+    watchlist n         # Assert watchlist length.
+
+    stop                # Stop running the script; for debugging.
+    debug [yes/no]      # Enable/disable FSNOTIFY_DEBUG (tests are run in
+                          parallel by default, so -parallel=1 is probably a good
+                          idea).
+
+    touch path
+    mkdir [-p] dir
+    ln -s target link   # Only ln -s supported.
+    mkfifo path
+    mknod dev path
+    mv src dst
+    rm [-r] path
+    chmod mode path     # Octal only
+    sleep time-in-ms
+
+    cat path            # Read path (does nothing with the data; just reads it).
+    echo str >>path     # Append "str" to "path".
+    echo str >path      # Truncate "path" and write "str".
+
+    require reason      # Skip the test if "reason" is true; "skip" and
+    skip reason         # "require" behave identical; it supports both for
+                        # readability. Possible reasons are:
+                        #
+                        #   always    Always skip this test.
+                        #   symlink   Symlinks are supported (requires admin
+                        #             permissions on Windows).
+                        #   mkfifo    Platform doesn't support FIFO named sockets.
+                        #   mknod     Platform doesn't support device nodes.
+
+
+output
+------
+After `Output:` the desired output is given; this is indented by convention, but
+that's not required.
+
+The format of that is:
+
+    # Comment
+    event  path  # Comment
+
+    system:
+        event  path
+    system2:
+        event  path
+
+Every event is one line, and any whitespace between the event and path are
+ignored. The path can optionally be surrounded in ". Anything after a "#" is
+ignored.
+
+Platform-specific tests can be added after GOOS; for example:
+
+    watch /
+    touch /file
+
+    Output:
+        # Tested if nothing else matches
+        create    /file
+
+        # Windows-specific test.
+        windows:
+            write  /file
+
+You can specify multiple platforms with a comma (e.g. "windows, linux:").
+"kqueue" is a shortcut for all kqueue systems (BSD, macOS).
+
 
 [goon]: https://github.com/arp242/goon
 [Vagrant]: https://www.vagrantup.com/
diff --git a/vendor/github.com/fsnotify/fsnotify/backend_fen.go b/vendor/github.com/fsnotify/fsnotify/backend_fen.go
index 28497f1dd..c349c326c 100644
--- a/vendor/github.com/fsnotify/fsnotify/backend_fen.go
+++ b/vendor/github.com/fsnotify/fsnotify/backend_fen.go
@@ -1,8 +1,8 @@
 //go:build solaris
-// +build solaris
 
-// Note: the documentation on the Watcher type and methods is generated from
-// mkdoc.zsh
+// FEN backend for illumos (supported) and Solaris (untested, but should work).
+//
+// See port_create(3c) etc. for docs. https://www.illumos.org/man/3C/port_create
 
 package fsnotify
 
@@ -12,150 +12,33 @@ import (
 	"os"
 	"path/filepath"
 	"sync"
+	"time"
 
+	"github.com/fsnotify/fsnotify/internal"
 	"golang.org/x/sys/unix"
 )
 
-// Watcher watches a set of paths, delivering events on a channel.
-//
-// A watcher should not be copied (e.g. pass it by pointer, rather than by
-// value).
-//
-// # Linux notes
-//
-// When a file is removed a Remove event won't be emitted until all file
-// descriptors are closed, and deletes will always emit a Chmod. For example:
-//
-//	fp := os.Open("file")
-//	os.Remove("file")        // Triggers Chmod
-//	fp.Close()               // Triggers Remove
-//
-// This is the event that inotify sends, so not much can be changed about this.
-//
-// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
-// for the number of watches per user, and fs.inotify.max_user_instances
-// specifies the maximum number of inotify instances per user. Every Watcher you
-// create is an "instance", and every path you add is a "watch".
-//
-// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
-// /proc/sys/fs/inotify/max_user_instances
-//
-// To increase them you can use sysctl or write the value to the /proc file:
-//
-//	# Default values on Linux 5.18
-//	sysctl fs.inotify.max_user_watches=124983
-//	sysctl fs.inotify.max_user_instances=128
-//
-// To make the changes persist on reboot edit /etc/sysctl.conf or
-// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
-// your distro's documentation):
-//
-//	fs.inotify.max_user_watches=124983
-//	fs.inotify.max_user_instances=128
-//
-// Reaching the limit will result in a "no space left on device" or "too many open
-// files" error.
-//
-// # kqueue notes (macOS, BSD)
-//
-// kqueue requires opening a file descriptor for every file that's being watched;
-// so if you're watching a directory with five files then that's six file
-// descriptors. You will run in to your system's "max open files" limit faster on
-// these platforms.
-//
-// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
-// control the maximum number of open files, as well as /etc/login.conf on BSD
-// systems.
-//
-// # Windows notes
-//
-// Paths can be added as "C:\path\to\dir", but forward slashes
-// ("C:/path/to/dir") will also work.
-//
-// When a watched directory is removed it will always send an event for the
-// directory itself, but may not send events for all files in that directory.
-// Sometimes it will send events for all times, sometimes it will send no
-// events, and often only for some files.
-//
-// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
-// value that is guaranteed to work with SMB filesystems. If you have many
-// events in quick succession this may not be enough, and you will have to use
-// [WithBufferSize] to increase the value.
-type Watcher struct {
-	// Events sends the filesystem change events.
-	//
-	// fsnotify can send the following events; a "path" here can refer to a
-	// file, directory, symbolic link, or special file like a FIFO.
-	//
-	//   fsnotify.Create    A new path was created; this may be followed by one
-	//                      or more Write events if data also gets written to a
-	//                      file.
-	//
-	//   fsnotify.Remove    A path was removed.
-	//
-	//   fsnotify.Rename    A path was renamed. A rename is always sent with the
-	//                      old path as Event.Name, and a Create event will be
-	//                      sent with the new name. Renames are only sent for
-	//                      paths that are currently watched; e.g. moving an
-	//                      unmonitored file into a monitored directory will
-	//                      show up as just a Create. Similarly, renaming a file
-	//                      to outside a monitored directory will show up as
-	//                      only a Rename.
-	//
-	//   fsnotify.Write     A file or named pipe was written to. A Truncate will
-	//                      also trigger a Write. A single "write action"
-	//                      initiated by the user may show up as one or multiple
-	//                      writes, depending on when the system syncs things to
-	//                      disk. For example when compiling a large Go program
-	//                      you may get hundreds of Write events, and you may
-	//                      want to wait until you've stopped receiving them
-	//                      (see the dedup example in cmd/fsnotify).
-	//
-	//                      Some systems may send Write event for directories
-	//                      when the directory content changes.
-	//
-	//   fsnotify.Chmod     Attributes were changed. On Linux this is also sent
-	//                      when a file is removed (or more accurately, when a
-	//                      link to an inode is removed). On kqueue it's sent
-	//                      when a file is truncated. On Windows it's never
-	//                      sent.
+type fen struct {
 	Events chan Event
-
-	// Errors sends any errors.
-	//
-	// ErrEventOverflow is used to indicate there are too many events:
-	//
-	//  - inotify:      There are too many queued events (fs.inotify.max_queued_events sysctl)
-	//  - windows:      The buffer size is too small; WithBufferSize() can be used to increase it.
-	//  - kqueue, fen:  Not used.
 	Errors chan error
 
 	mu      sync.Mutex
 	port    *unix.EventPort
-	done    chan struct{}       // Channel for sending a "quit message" to the reader goroutine
-	dirs    map[string]struct{} // Explicitly watched directories
-	watches map[string]struct{} // Explicitly watched non-directories
+	done    chan struct{} // Channel for sending a "quit message" to the reader goroutine
+	dirs    map[string]Op // Explicitly watched directories
+	watches map[string]Op // Explicitly watched non-directories
 }
 
-// NewWatcher creates a new Watcher.
-func NewWatcher() (*Watcher, error) {
-	return NewBufferedWatcher(0)
+func newBackend(ev chan Event, errs chan error) (backend, error) {
+	return newBufferedBackend(0, ev, errs)
 }
 
-// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
-// channel.
-//
-// The main use case for this is situations with a very large number of events
-// where the kernel buffer size can't be increased (e.g. due to lack of
-// permissions). An unbuffered Watcher will perform better for almost all use
-// cases, and whenever possible you will be better off increasing the kernel
-// buffers instead of adding a large userspace buffer.
-func NewBufferedWatcher(sz uint) (*Watcher, error) {
-	w := &Watcher{
-		Events:  make(chan Event, sz),
-		Errors:  make(chan error),
-		dirs:    make(map[string]struct{}),
-		watches: make(map[string]struct{}),
+func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error) {
+	w := &fen{
+		Events:  ev,
+		Errors:  errs,
+		dirs:    make(map[string]Op),
+		watches: make(map[string]Op),
 		done:    make(chan struct{}),
 	}
 
@@ -171,27 +54,30 @@ func NewBufferedWatcher(sz uint) (*Watcher, error) {
 
 // sendEvent attempts to send an event to the user, returning true if the event
 // was put in the channel successfully and false if the watcher has been closed.
-func (w *Watcher) sendEvent(name string, op Op) (sent bool) {
+func (w *fen) sendEvent(name string, op Op) (sent bool) {
 	select {
-	case w.Events <- Event{Name: name, Op: op}:
-		return true
 	case <-w.done:
 		return false
+	case w.Events <- Event{Name: name, Op: op}:
+		return true
 	}
 }
 
 // sendError attempts to send an error to the user, returning true if the error
 // was put in the channel successfully and false if the watcher has been closed.
-func (w *Watcher) sendError(err error) (sent bool) {
-	select {
-	case w.Errors <- err:
+func (w *fen) sendError(err error) (sent bool) {
+	if err == nil {
 		return true
+	}
+	select {
 	case <-w.done:
 		return false
+	case w.Errors <- err:
+		return true
 	}
 }
 
-func (w *Watcher) isClosed() bool {
+func (w *fen) isClosed() bool {
 	select {
 	case <-w.done:
 		return true
@@ -200,8 +86,7 @@ func (w *Watcher) isClosed() bool {
 	}
 }
 
-// Close removes all watches and closes the Events channel.
-func (w *Watcher) Close() error {
+func (w *fen) Close() error {
 	// Take the lock used by associateFile to prevent lingering events from
 	// being processed after the close
 	w.mu.Lock()
@@ -213,60 +98,21 @@ func (w *Watcher) Close() error {
 	return w.port.Close()
 }
 
-// Add starts monitoring the path for changes.
-//
-// A path can only be watched once; watching it more than once is a no-op and will
-// not return an error. Paths that do not yet exist on the filesystem cannot be
-// watched.
-//
-// A watch will be automatically removed if the watched path is deleted or
-// renamed. The exception is the Windows backend, which doesn't remove the
-// watcher on renames.
-//
-// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
-// filesystems (/proc, /sys, etc.) generally don't work.
-//
-// Returns [ErrClosed] if [Watcher.Close] was called.
-//
-// See [Watcher.AddWith] for a version that allows adding options.
-//
-// # Watching directories
-//
-// All files in a directory are monitored, including new files that are created
-// after the watcher is started. Subdirectories are not watched (i.e. it's
-// non-recursive).
-//
-// # Watching files
-//
-// Watching individual files (rather than directories) is generally not
-// recommended as many programs (especially editors) update files atomically: it
-// will write to a temporary file which is then moved to to destination,
-// overwriting the original (or some variant thereof). The watcher on the
-// original file is now lost, as that no longer exists.
-//
-// The upshot of this is that a power failure or crash won't leave a
-// half-written file.
-//
-// Watch the parent directory and use Event.Name to filter out files you're not
-// interested in. There is an example of this in cmd/fsnotify/file.go.
-func (w *Watcher) Add(name string) error { return w.AddWith(name) }
+func (w *fen) Add(name string) error { return w.AddWith(name) }
 
-// AddWith is like [Watcher.Add], but allows adding options. When using Add()
-// the defaults described below are used.
-//
-// Possible options are:
-//
-//   - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
-//     other platforms. The default is 64K (65536 bytes).
-func (w *Watcher) AddWith(name string, opts ...addOpt) error {
+func (w *fen) AddWith(name string, opts ...addOpt) error {
 	if w.isClosed() {
 		return ErrClosed
 	}
-	if w.port.PathIsWatched(name) {
-		return nil
+	if debug {
+		fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s  AddWith(%q)\n",
+			time.Now().Format("15:04:05.000000000"), name)
 	}
 
-	_ = getOptions(opts...)
+	with := getOptions(opts...)
+	if !w.xSupports(with.op) {
+		return fmt.Errorf("%w: %s", xErrUnsupported, with.op)
+	}
 
 	// Currently we resolve symlinks that were explicitly requested to be
 	// watched. Otherwise we would use LStat here.
@@ -283,7 +129,7 @@ func (w *Watcher) AddWith(name string, opts ...addOpt) error {
 		}
 
 		w.mu.Lock()
-		w.dirs[name] = struct{}{}
+		w.dirs[name] = with.op
 		w.mu.Unlock()
 		return nil
 	}
@@ -294,26 +140,22 @@ func (w *Watcher) AddWith(name string, opts ...addOpt) error {
 	}
 
 	w.mu.Lock()
-	w.watches[name] = struct{}{}
+	w.watches[name] = with.op
 	w.mu.Unlock()
 	return nil
 }
 
-// Remove stops monitoring the path for changes.
-//
-// Directories are always removed non-recursively. For example, if you added
-// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
-//
-// Removing a path that has not yet been added returns [ErrNonExistentWatch].
-//
-// Returns nil if [Watcher.Close] was called.
-func (w *Watcher) Remove(name string) error {
+func (w *fen) Remove(name string) error {
 	if w.isClosed() {
 		return nil
 	}
 	if !w.port.PathIsWatched(name) {
 		return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
 	}
+	if debug {
+		fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s  Remove(%q)\n",
+			time.Now().Format("15:04:05.000000000"), name)
+	}
 
 	// The user has expressed an intent. Immediately remove this name from
 	// whichever watch list it might be in. If it's not in there the delete
@@ -346,7 +188,7 @@ func (w *Watcher) Remove(name string) error {
 }
 
 // readEvents contains the main loop that runs in a goroutine watching for events.
-func (w *Watcher) readEvents() {
+func (w *fen) readEvents() {
 	// If this function returns, the watcher has been closed and we can close
 	// these channels
 	defer func() {
@@ -382,17 +224,19 @@ func (w *Watcher) readEvents() {
 				continue
 			}
 
+			if debug {
+				internal.Debug(pevent.Path, pevent.Events)
+			}
+
 			err = w.handleEvent(&pevent)
-			if err != nil {
-				if !w.sendError(err) {
-					return
-				}
+			if !w.sendError(err) {
+				return
 			}
 		}
 	}
 }
 
-func (w *Watcher) handleDirectory(path string, stat os.FileInfo, follow bool, handler func(string, os.FileInfo, bool) error) error {
+func (w *fen) handleDirectory(path string, stat os.FileInfo, follow bool, handler func(string, os.FileInfo, bool) error) error {
 	files, err := os.ReadDir(path)
 	if err != nil {
 		return err
@@ -418,7 +262,7 @@ func (w *Watcher) handleDirectory(path string, stat os.FileInfo, follow bool, ha
 // bitmap matches more than one event type (e.g. the file was both modified and
 // had the attributes changed between when the association was created and the
 // when event was returned)
-func (w *Watcher) handleEvent(event *unix.PortEvent) error {
+func (w *fen) handleEvent(event *unix.PortEvent) error {
 	var (
 		events     = event.Events
 		path       = event.Path
@@ -510,15 +354,9 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error {
 	}
 
 	if events&unix.FILE_MODIFIED != 0 {
-		if fmode.IsDir() {
-			if watchedDir {
-				if err := w.updateDirectory(path); err != nil {
-					return err
-				}
-			} else {
-				if !w.sendEvent(path, Write) {
-					return nil
-				}
+		if fmode.IsDir() && watchedDir {
+			if err := w.updateDirectory(path); err != nil {
+				return err
 			}
 		} else {
 			if !w.sendEvent(path, Write) {
@@ -543,7 +381,7 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error {
 	return nil
 }
 
-func (w *Watcher) updateDirectory(path string) error {
+func (w *fen) updateDirectory(path string) error {
 	// The directory was modified, so we must find unwatched entities and watch
 	// them. If something was removed from the directory, nothing will happen,
 	// as everything else should still be watched.
@@ -563,10 +401,8 @@ func (w *Watcher) updateDirectory(path string) error {
 			return err
 		}
 		err = w.associateFile(path, finfo, false)
-		if err != nil {
-			if !w.sendError(err) {
-				return nil
-			}
+		if !w.sendError(err) {
+			return nil
 		}
 		if !w.sendEvent(path, Create) {
 			return nil
@@ -575,7 +411,7 @@ func (w *Watcher) updateDirectory(path string) error {
 	return nil
 }
 
-func (w *Watcher) associateFile(path string, stat os.FileInfo, follow bool) error {
+func (w *fen) associateFile(path string, stat os.FileInfo, follow bool) error {
 	if w.isClosed() {
 		return ErrClosed
 	}
@@ -593,34 +429,34 @@ func (w *Watcher) associateFile(path string, stat os.FileInfo, follow bool) erro
 		// cleared up that discrepancy. The most likely cause is that the event
 		// has fired but we haven't processed it yet.
 		err := w.port.DissociatePath(path)
-		if err != nil && err != unix.ENOENT {
+		if err != nil && !errors.Is(err, unix.ENOENT) {
 			return err
 		}
 	}
-	// FILE_NOFOLLOW means we watch symlinks themselves rather than their
-	// targets.
-	events := unix.FILE_MODIFIED | unix.FILE_ATTRIB | unix.FILE_NOFOLLOW
-	if follow {
-		// We *DO* follow symlinks for explicitly watched entries.
-		events = unix.FILE_MODIFIED | unix.FILE_ATTRIB
+
+	var events int
+	if !follow {
+		// Watch symlinks themselves rather than their targets unless this entry
+		// is explicitly watched.
+		events |= unix.FILE_NOFOLLOW
+	}
+	if true { // TODO: implement withOps()
+		events |= unix.FILE_MODIFIED
 	}
-	return w.port.AssociatePath(path, stat,
-		events,
-		stat.Mode())
+	if true {
+		events |= unix.FILE_ATTRIB
+	}
+	return w.port.AssociatePath(path, stat, events, stat.Mode())
 }
 
-func (w *Watcher) dissociateFile(path string, stat os.FileInfo, unused bool) error {
+func (w *fen) dissociateFile(path string, stat os.FileInfo, unused bool) error {
 	if !w.port.PathIsWatched(path) {
 		return nil
 	}
 	return w.port.DissociatePath(path)
 }
 
-// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
-// yet removed).
-//
-// Returns nil if [Watcher.Close] was called.
-func (w *Watcher) WatchList() []string {
+func (w *fen) WatchList() []string {
 	if w.isClosed() {
 		return nil
 	}
@@ -638,3 +474,11 @@ func (w *Watcher) WatchList() []string {
 
 	return entries
 }
+
+func (w *fen) xSupports(op Op) bool {
+	if op.Has(xUnportableOpen) || op.Has(xUnportableRead) ||
+		op.Has(xUnportableCloseWrite) || op.Has(xUnportableCloseRead) {
+		return false
+	}
+	return true
+}
diff --git a/vendor/github.com/fsnotify/fsnotify/backend_inotify.go b/vendor/github.com/fsnotify/fsnotify/backend_inotify.go
index 921c1c1e4..36c311694 100644
--- a/vendor/github.com/fsnotify/fsnotify/backend_inotify.go
+++ b/vendor/github.com/fsnotify/fsnotify/backend_inotify.go
@@ -1,8 +1,4 @@
 //go:build linux && !appengine
-// +build linux,!appengine
-
-// Note: the documentation on the Watcher type and methods is generated from
-// mkdoc.zsh
 
 package fsnotify
 
@@ -10,127 +6,20 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"io/fs"
 	"os"
 	"path/filepath"
 	"strings"
 	"sync"
+	"time"
 	"unsafe"
 
+	"github.com/fsnotify/fsnotify/internal"
 	"golang.org/x/sys/unix"
 )
 
-// Watcher watches a set of paths, delivering events on a channel.
-//
-// A watcher should not be copied (e.g. pass it by pointer, rather than by
-// value).
-//
-// # Linux notes
-//
-// When a file is removed a Remove event won't be emitted until all file
-// descriptors are closed, and deletes will always emit a Chmod. For example:
-//
-//	fp := os.Open("file")
-//	os.Remove("file")        // Triggers Chmod
-//	fp.Close()               // Triggers Remove
-//
-// This is the event that inotify sends, so not much can be changed about this.
-//
-// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
-// for the number of watches per user, and fs.inotify.max_user_instances
-// specifies the maximum number of inotify instances per user. Every Watcher you
-// create is an "instance", and every path you add is a "watch".
-//
-// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
-// /proc/sys/fs/inotify/max_user_instances
-//
-// To increase them you can use sysctl or write the value to the /proc file:
-//
-//	# Default values on Linux 5.18
-//	sysctl fs.inotify.max_user_watches=124983
-//	sysctl fs.inotify.max_user_instances=128
-//
-// To make the changes persist on reboot edit /etc/sysctl.conf or
-// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
-// your distro's documentation):
-//
-//	fs.inotify.max_user_watches=124983
-//	fs.inotify.max_user_instances=128
-//
-// Reaching the limit will result in a "no space left on device" or "too many open
-// files" error.
-//
-// # kqueue notes (macOS, BSD)
-//
-// kqueue requires opening a file descriptor for every file that's being watched;
-// so if you're watching a directory with five files then that's six file
-// descriptors. You will run in to your system's "max open files" limit faster on
-// these platforms.
-//
-// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
-// control the maximum number of open files, as well as /etc/login.conf on BSD
-// systems.
-//
-// # Windows notes
-//
-// Paths can be added as "C:\path\to\dir", but forward slashes
-// ("C:/path/to/dir") will also work.
-//
-// When a watched directory is removed it will always send an event for the
-// directory itself, but may not send events for all files in that directory.
-// Sometimes it will send events for all times, sometimes it will send no
-// events, and often only for some files.
-//
-// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
-// value that is guaranteed to work with SMB filesystems. If you have many
-// events in quick succession this may not be enough, and you will have to use
-// [WithBufferSize] to increase the value.
-type Watcher struct {
-	// Events sends the filesystem change events.
-	//
-	// fsnotify can send the following events; a "path" here can refer to a
-	// file, directory, symbolic link, or special file like a FIFO.
-	//
-	//   fsnotify.Create    A new path was created; this may be followed by one
-	//                      or more Write events if data also gets written to a
-	//                      file.
-	//
-	//   fsnotify.Remove    A path was removed.
-	//
-	//   fsnotify.Rename    A path was renamed. A rename is always sent with the
-	//                      old path as Event.Name, and a Create event will be
-	//                      sent with the new name. Renames are only sent for
-	//                      paths that are currently watched; e.g. moving an
-	//                      unmonitored file into a monitored directory will
-	//                      show up as just a Create. Similarly, renaming a file
-	//                      to outside a monitored directory will show up as
-	//                      only a Rename.
-	//
-	//   fsnotify.Write     A file or named pipe was written to. A Truncate will
-	//                      also trigger a Write. A single "write action"
-	//                      initiated by the user may show up as one or multiple
-	//                      writes, depending on when the system syncs things to
-	//                      disk. For example when compiling a large Go program
-	//                      you may get hundreds of Write events, and you may
-	//                      want to wait until you've stopped receiving them
-	//                      (see the dedup example in cmd/fsnotify).
-	//
-	//                      Some systems may send Write event for directories
-	//                      when the directory content changes.
-	//
-	//   fsnotify.Chmod     Attributes were changed. On Linux this is also sent
-	//                      when a file is removed (or more accurately, when a
-	//                      link to an inode is removed). On kqueue it's sent
-	//                      when a file is truncated. On Windows it's never
-	//                      sent.
+type inotify struct {
 	Events chan Event
-
-	// Errors sends any errors.
-	//
-	// ErrEventOverflow is used to indicate there are too many events:
-	//
-	//  - inotify:      There are too many queued events (fs.inotify.max_queued_events sysctl)
-	//  - windows:      The buffer size is too small; WithBufferSize() can be used to increase it.
-	//  - kqueue, fen:  Not used.
 	Errors chan error
 
 	// Store fd here as os.File.Read() will no longer return on close after
@@ -139,8 +28,26 @@ type Watcher struct {
 	inotifyFile *os.File
 	watches     *watches
 	done        chan struct{} // Channel for sending a "quit message" to the reader goroutine
-	closeMu     sync.Mutex
+	doneMu      sync.Mutex
 	doneResp    chan struct{} // Channel to respond to Close
+
+	// Store rename cookies in an array, with the index wrapping to 0. Almost
+	// all of the time what we get is a MOVED_FROM to set the cookie and the
+	// next event inotify sends will be MOVED_TO to read it. However, this is
+	// not guaranteed – as described in inotify(7) – and we may get other events
+	// between the two MOVED_* events (including other MOVED_* ones).
+	//
+	// A second issue is that moving a file outside the watched directory will
+	// trigger a MOVED_FROM to set the cookie, but we never see the MOVED_TO to
+	// read and delete it. So just storing it in a map would slowly leak memory.
+	//
+	// Doing it like this gives us a simple fast LRU-cache that won't allocate.
+	// Ten items should be more than enough for our purpose, and a loop over
+	// such a short array is faster than a map access anyway (not that it hugely
+	// matters since we're talking about hundreds of ns at the most, but still).
+	cookies     [10]koekje
+	cookieIndex uint8
+	cookiesMu   sync.Mutex
 }
 
 type (
@@ -150,9 +57,14 @@ type (
 		path map[string]uint32 // pathname → wd
 	}
 	watch struct {
-		wd    uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
-		flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
-		path  string // Watch path.
+		wd      uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
+		flags   uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
+		path    string // Watch path.
+		recurse bool   // Recursion with ./...?
+	}
+	koekje struct {
+		cookie uint32
+		path   string
 	}
 )
 
@@ -179,23 +91,45 @@ func (w *watches) add(ww *watch) {
 func (w *watches) remove(wd uint32) {
 	w.mu.Lock()
 	defer w.mu.Unlock()
-	delete(w.path, w.wd[wd].path)
+	watch := w.wd[wd] // Could have had Remove() called. See #616.
+	if watch == nil {
+		return
+	}
+	delete(w.path, watch.path)
 	delete(w.wd, wd)
 }
 
-func (w *watches) removePath(path string) (uint32, bool) {
+func (w *watches) removePath(path string) ([]uint32, error) {
 	w.mu.Lock()
 	defer w.mu.Unlock()
 
+	path, recurse := recursivePath(path)
 	wd, ok := w.path[path]
 	if !ok {
-		return 0, false
+		return nil, fmt.Errorf("%w: %s", ErrNonExistentWatch, path)
+	}
+
+	watch := w.wd[wd]
+	if recurse && !watch.recurse {
+		return nil, fmt.Errorf("can't use /... with non-recursive watch %q", path)
 	}
 
 	delete(w.path, path)
 	delete(w.wd, wd)
+	if !watch.recurse {
+		return []uint32{wd}, nil
+	}
 
-	return wd, true
+	wds := make([]uint32, 0, 8)
+	wds = append(wds, wd)
+	for p, rwd := range w.path {
+		if filepath.HasPrefix(p, path) {
+			delete(w.path, p)
+			delete(w.wd, rwd)
+			wds = append(wds, rwd)
+		}
+	}
+	return wds, nil
 }
 
 func (w *watches) byPath(path string) *watch {
@@ -236,20 +170,11 @@ func (w *watches) updatePath(path string, f func(*watch) (*watch, error)) error
 	return nil
 }
 
-// NewWatcher creates a new Watcher.
-func NewWatcher() (*Watcher, error) {
-	return NewBufferedWatcher(0)
+func newBackend(ev chan Event, errs chan error) (backend, error) {
+	return newBufferedBackend(0, ev, errs)
 }
 
-// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
-// channel.
-//
-// The main use case for this is situations with a very large number of events
-// where the kernel buffer size can't be increased (e.g. due to lack of
-// permissions). An unbuffered Watcher will perform better for almost all use
-// cases, and whenever possible you will be better off increasing the kernel
-// buffers instead of adding a large userspace buffer.
-func NewBufferedWatcher(sz uint) (*Watcher, error) {
+func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error) {
 	// Need to set nonblocking mode for SetDeadline to work, otherwise blocking
 	// I/O operations won't terminate on close.
 	fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC | unix.IN_NONBLOCK)
@@ -257,12 +182,12 @@ func NewBufferedWatcher(sz uint) (*Watcher, error) {
 		return nil, errno
 	}
 
-	w := &Watcher{
+	w := &inotify{
+		Events:      ev,
+		Errors:      errs,
 		fd:          fd,
 		inotifyFile: os.NewFile(uintptr(fd), ""),
 		watches:     newWatches(),
-		Events:      make(chan Event, sz),
-		Errors:      make(chan error),
 		done:        make(chan struct{}),
 		doneResp:    make(chan struct{}),
 	}
@@ -272,26 +197,29 @@ func NewBufferedWatcher(sz uint) (*Watcher, error) {
 }
 
 // Returns true if the event was sent, or false if watcher is closed.
-func (w *Watcher) sendEvent(e Event) bool {
+func (w *inotify) sendEvent(e Event) bool {
 	select {
-	case w.Events <- e:
-		return true
 	case <-w.done:
 		return false
+	case w.Events <- e:
+		return true
 	}
 }
 
 // Returns true if the error was sent, or false if watcher is closed.
-func (w *Watcher) sendError(err error) bool {
-	select {
-	case w.Errors <- err:
+func (w *inotify) sendError(err error) bool {
+	if err == nil {
 		return true
+	}
+	select {
 	case <-w.done:
 		return false
+	case w.Errors <- err:
+		return true
 	}
 }
 
-func (w *Watcher) isClosed() bool {
+func (w *inotify) isClosed() bool {
 	select {
 	case <-w.done:
 		return true
@@ -300,15 +228,14 @@ func (w *Watcher) isClosed() bool {
 	}
 }
 
-// Close removes all watches and closes the Events channel.
-func (w *Watcher) Close() error {
-	w.closeMu.Lock()
+func (w *inotify) Close() error {
+	w.doneMu.Lock()
 	if w.isClosed() {
-		w.closeMu.Unlock()
+		w.doneMu.Unlock()
 		return nil
 	}
 	close(w.done)
-	w.closeMu.Unlock()
+	w.doneMu.Unlock()
 
 	// Causes any blocking reads to return with an error, provided the file
 	// still supports deadline operations.
@@ -323,78 +250,104 @@ func (w *Watcher) Close() error {
 	return nil
 }
 
-// Add starts monitoring the path for changes.
-//
-// A path can only be watched once; watching it more than once is a no-op and will
-// not return an error. Paths that do not yet exist on the filesystem cannot be
-// watched.
-//
-// A watch will be automatically removed if the watched path is deleted or
-// renamed. The exception is the Windows backend, which doesn't remove the
-// watcher on renames.
-//
-// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
-// filesystems (/proc, /sys, etc.) generally don't work.
-//
-// Returns [ErrClosed] if [Watcher.Close] was called.
-//
-// See [Watcher.AddWith] for a version that allows adding options.
-//
-// # Watching directories
-//
-// All files in a directory are monitored, including new files that are created
-// after the watcher is started. Subdirectories are not watched (i.e. it's
-// non-recursive).
-//
-// # Watching files
-//
-// Watching individual files (rather than directories) is generally not
-// recommended as many programs (especially editors) update files atomically: it
-// will write to a temporary file which is then moved to to destination,
-// overwriting the original (or some variant thereof). The watcher on the
-// original file is now lost, as that no longer exists.
-//
-// The upshot of this is that a power failure or crash won't leave a
-// half-written file.
-//
-// Watch the parent directory and use Event.Name to filter out files you're not
-// interested in. There is an example of this in cmd/fsnotify/file.go.
-func (w *Watcher) Add(name string) error { return w.AddWith(name) }
-
-// AddWith is like [Watcher.Add], but allows adding options. When using Add()
-// the defaults described below are used.
-//
-// Possible options are:
-//
-//   - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
-//     other platforms. The default is 64K (65536 bytes).
-func (w *Watcher) AddWith(name string, opts ...addOpt) error {
+func (w *inotify) Add(name string) error { return w.AddWith(name) }
+
+func (w *inotify) AddWith(path string, opts ...addOpt) error {
 	if w.isClosed() {
 		return ErrClosed
 	}
+	if debug {
+		fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s  AddWith(%q)\n",
+			time.Now().Format("15:04:05.000000000"), path)
+	}
+
+	with := getOptions(opts...)
+	if !w.xSupports(with.op) {
+		return fmt.Errorf("%w: %s", xErrUnsupported, with.op)
+	}
 
-	name = filepath.Clean(name)
-	_ = getOptions(opts...)
+	path, recurse := recursivePath(path)
+	if recurse {
+		return filepath.WalkDir(path, func(root string, d fs.DirEntry, err error) error {
+			if err != nil {
+				return err
+			}
+			if !d.IsDir() {
+				if root == path {
+					return fmt.Errorf("fsnotify: not a directory: %q", path)
+				}
+				return nil
+			}
 
-	var flags uint32 = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
-		unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
-		unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
+			// Send a Create event when adding new directory from a recursive
+			// watch; this is for "mkdir -p one/two/three". Usually all those
+			// directories will be created before we can set up watchers on the
+			// subdirectories, so only "one" would be sent as a Create event and
+			// not "one/two" and "one/two/three" (inotifywait -r has the same
+			// problem).
+			if with.sendCreate && root != path {
+				w.sendEvent(Event{Name: root, Op: Create})
+			}
+
+			return w.add(root, with, true)
+		})
+	}
 
-	return w.watches.updatePath(name, func(existing *watch) (*watch, error) {
+	return w.add(path, with, false)
+}
+
+func (w *inotify) add(path string, with withOpts, recurse bool) error {
+	var flags uint32
+	if with.noFollow {
+		flags |= unix.IN_DONT_FOLLOW
+	}
+	if with.op.Has(Create) {
+		flags |= unix.IN_CREATE
+	}
+	if with.op.Has(Write) {
+		flags |= unix.IN_MODIFY
+	}
+	if with.op.Has(Remove) {
+		flags |= unix.IN_DELETE | unix.IN_DELETE_SELF
+	}
+	if with.op.Has(Rename) {
+		flags |= unix.IN_MOVED_TO | unix.IN_MOVED_FROM | unix.IN_MOVE_SELF
+	}
+	if with.op.Has(Chmod) {
+		flags |= unix.IN_ATTRIB
+	}
+	if with.op.Has(xUnportableOpen) {
+		flags |= unix.IN_OPEN
+	}
+	if with.op.Has(xUnportableRead) {
+		flags |= unix.IN_ACCESS
+	}
+	if with.op.Has(xUnportableCloseWrite) {
+		flags |= unix.IN_CLOSE_WRITE
+	}
+	if with.op.Has(xUnportableCloseRead) {
+		flags |= unix.IN_CLOSE_NOWRITE
+	}
+	return w.register(path, flags, recurse)
+}
+
+func (w *inotify) register(path string, flags uint32, recurse bool) error {
+	return w.watches.updatePath(path, func(existing *watch) (*watch, error) {
 		if existing != nil {
 			flags |= existing.flags | unix.IN_MASK_ADD
 		}
 
-		wd, err := unix.InotifyAddWatch(w.fd, name, flags)
+		wd, err := unix.InotifyAddWatch(w.fd, path, flags)
 		if wd == -1 {
 			return nil, err
 		}
 
 		if existing == nil {
 			return &watch{
-				wd:    uint32(wd),
-				path:  name,
-				flags: flags,
+				wd:      uint32(wd),
+				path:    path,
+				flags:   flags,
+				recurse: recurse,
 			}, nil
 		}
 
@@ -404,49 +357,44 @@ func (w *Watcher) AddWith(name string, opts ...addOpt) error {
 	})
 }
 
-// Remove stops monitoring the path for changes.
-//
-// Directories are always removed non-recursively. For example, if you added
-// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
-//
-// Removing a path that has not yet been added returns [ErrNonExistentWatch].
-//
-// Returns nil if [Watcher.Close] was called.
-func (w *Watcher) Remove(name string) error {
+func (w *inotify) Remove(name string) error {
 	if w.isClosed() {
 		return nil
 	}
+	if debug {
+		fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s  Remove(%q)\n",
+			time.Now().Format("15:04:05.000000000"), name)
+	}
 	return w.remove(filepath.Clean(name))
 }
 
-func (w *Watcher) remove(name string) error {
-	wd, ok := w.watches.removePath(name)
-	if !ok {
-		return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
-	}
-
-	success, errno := unix.InotifyRmWatch(w.fd, wd)
-	if success == -1 {
-		// TODO: Perhaps it's not helpful to return an error here in every case;
-		//       The only two possible errors are:
-		//
-		//       - EBADF, which happens when w.fd is not a valid file descriptor
-		//         of any kind.
-		//       - EINVAL, which is when fd is not an inotify descriptor or wd
-		//         is not a valid watch descriptor. Watch descriptors are
-		//         invalidated when they are removed explicitly or implicitly;
-		//         explicitly by inotify_rm_watch, implicitly when the file they
-		//         are watching is deleted.
-		return errno
+func (w *inotify) remove(name string) error {
+	wds, err := w.watches.removePath(name)
+	if err != nil {
+		return err
+	}
+
+	for _, wd := range wds {
+		_, err := unix.InotifyRmWatch(w.fd, wd)
+		if err != nil {
+			// TODO: Perhaps it's not helpful to return an error here in every
+			// case; the only two possible errors are:
+			//
+			// EBADF, which happens when w.fd is not a valid file descriptor of
+			// any kind.
+			//
+			// EINVAL, which is when fd is not an inotify descriptor or wd is
+			// not a valid watch descriptor. Watch descriptors are invalidated
+			// when they are removed explicitly or implicitly; explicitly by
+			// inotify_rm_watch, implicitly when the file they are watching is
+			// deleted.
+			return err
+		}
 	}
 	return nil
 }
 
-// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
-// yet removed).
-//
-// Returns nil if [Watcher.Close] was called.
-func (w *Watcher) WatchList() []string {
+func (w *inotify) WatchList() []string {
 	if w.isClosed() {
 		return nil
 	}
@@ -463,7 +411,7 @@ func (w *Watcher) WatchList() []string {
 
 // readEvents reads from the inotify file descriptor, converts the
 // received events into Event objects and sends them via the Events channel
-func (w *Watcher) readEvents() {
+func (w *inotify) readEvents() {
 	defer func() {
 		close(w.doneResp)
 		close(w.Errors)
@@ -506,15 +454,17 @@ func (w *Watcher) readEvents() {
 			continue
 		}
 
-		var offset uint32
 		// We don't know how many events we just read into the buffer
 		// While the offset points to at least one whole event...
+		var offset uint32
 		for offset <= uint32(n-unix.SizeofInotifyEvent) {
 			var (
 				// Point "raw" to the event in the buffer
 				raw     = (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
 				mask    = uint32(raw.Mask)
 				nameLen = uint32(raw.Len)
+				// Move to the next event in the buffer
+				next = func() { offset += unix.SizeofInotifyEvent + nameLen }
 			)
 
 			if mask&unix.IN_Q_OVERFLOW != 0 {
@@ -523,21 +473,53 @@ func (w *Watcher) readEvents() {
 				}
 			}
 
-			// If the event happened to the watched directory or the watched file, the kernel
-			// doesn't append the filename to the event, but we would like to always fill the
-			// the "Name" field with a valid filename. We retrieve the path of the watch from
-			// the "paths" map.
+			/// If the event happened to the watched directory or the watched
+			/// file, the kernel doesn't append the filename to the event, but
+			/// we would like to always fill the the "Name" field with a valid
+			/// filename. We retrieve the path of the watch from the "paths"
+			/// map.
 			watch := w.watches.byWd(uint32(raw.Wd))
+			/// Can be nil if Remove() was called in another goroutine for this
+			/// path inbetween reading the events from the kernel and reading
+			/// the internal state. Not much we can do about it, so just skip.
+			/// See #616.
+			if watch == nil {
+				next()
+				continue
+			}
+
+			name := watch.path
+			if nameLen > 0 {
+				/// Point "bytes" at the first byte of the filename
+				bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen]
+				/// The filename is padded with NULL bytes. TrimRight() gets rid of those.
+				name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
+			}
+
+			if debug {
+				internal.Debug(name, raw.Mask, raw.Cookie)
+			}
+
+			if mask&unix.IN_IGNORED != 0 { //&& event.Op != 0
+				next()
+				continue
+			}
 
 			// inotify will automatically remove the watch on deletes; just need
 			// to clean our state here.
-			if watch != nil && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
+			if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
 				w.watches.remove(watch.wd)
 			}
+
 			// We can't really update the state when a watched path is moved;
 			// only IN_MOVE_SELF is sent and not IN_MOVED_{FROM,TO}. So remove
 			// the watch.
-			if watch != nil && mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF {
+			if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF {
+				if watch.recurse {
+					next() // Do nothing
+					continue
+				}
+
 				err := w.remove(watch.path)
 				if err != nil && !errors.Is(err, ErrNonExistentWatch) {
 					if !w.sendError(err) {
@@ -546,34 +528,69 @@ func (w *Watcher) readEvents() {
 				}
 			}
 
-			var name string
-			if watch != nil {
-				name = watch.path
-			}
-			if nameLen > 0 {
-				// Point "bytes" at the first byte of the filename
-				bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen]
-				// The filename is padded with NULL bytes. TrimRight() gets rid of those.
-				name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
+			/// Skip if we're watching both this path and the parent; the parent
+			/// will already send a delete so no need to do it twice.
+			if mask&unix.IN_DELETE_SELF != 0 {
+				if _, ok := w.watches.path[filepath.Dir(watch.path)]; ok {
+					next()
+					continue
+				}
 			}
 
-			event := w.newEvent(name, mask)
+			ev := w.newEvent(name, mask, raw.Cookie)
+			// Need to update watch path for recurse.
+			if watch.recurse {
+				isDir := mask&unix.IN_ISDIR == unix.IN_ISDIR
+				/// New directory created: set up watch on it.
+				if isDir && ev.Has(Create) {
+					err := w.register(ev.Name, watch.flags, true)
+					if !w.sendError(err) {
+						return
+					}
 
-			// Send the events that are not ignored on the events channel
-			if mask&unix.IN_IGNORED == 0 {
-				if !w.sendEvent(event) {
-					return
+					// This was a directory rename, so we need to update all
+					// the children.
+					//
+					// TODO: this is of course pretty slow; we should use a
+					// better data structure for storing all of this, e.g. store
+					// children in the watch. I have some code for this in my
+					// kqueue refactor we can use in the future. For now I'm
+					// okay with this as it's not publicly available.
+					// Correctness first, performance second.
+					if ev.renamedFrom != "" {
+						w.watches.mu.Lock()
+						for k, ww := range w.watches.wd {
+							if k == watch.wd || ww.path == ev.Name {
+								continue
+							}
+							if strings.HasPrefix(ww.path, ev.renamedFrom) {
+								ww.path = strings.Replace(ww.path, ev.renamedFrom, ev.Name, 1)
+								w.watches.wd[k] = ww
+							}
+						}
+						w.watches.mu.Unlock()
+					}
 				}
 			}
 
-			// Move to the next event in the buffer
-			offset += unix.SizeofInotifyEvent + nameLen
+			/// Send the events that are not ignored on the events channel
+			if !w.sendEvent(ev) {
+				return
+			}
+			next()
 		}
 	}
 }
 
-// newEvent returns an platform-independent Event based on an inotify mask.
-func (w *Watcher) newEvent(name string, mask uint32) Event {
+func (w *inotify) isRecursive(path string) bool {
+	ww := w.watches.byPath(path)
+	if ww == nil { // path could be a file, so also check the Dir.
+		ww = w.watches.byPath(filepath.Dir(path))
+	}
+	return ww != nil && ww.recurse
+}
+
+func (w *inotify) newEvent(name string, mask, cookie uint32) Event {
 	e := Event{Name: name}
 	if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
 		e.Op |= Create
@@ -584,11 +601,58 @@ func (w *Watcher) newEvent(name string, mask uint32) Event {
 	if mask&unix.IN_MODIFY == unix.IN_MODIFY {
 		e.Op |= Write
 	}
+	if mask&unix.IN_OPEN == unix.IN_OPEN {
+		e.Op |= xUnportableOpen
+	}
+	if mask&unix.IN_ACCESS == unix.IN_ACCESS {
+		e.Op |= xUnportableRead
+	}
+	if mask&unix.IN_CLOSE_WRITE == unix.IN_CLOSE_WRITE {
+		e.Op |= xUnportableCloseWrite
+	}
+	if mask&unix.IN_CLOSE_NOWRITE == unix.IN_CLOSE_NOWRITE {
+		e.Op |= xUnportableCloseRead
+	}
 	if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
 		e.Op |= Rename
 	}
 	if mask&unix.IN_ATTRIB == unix.IN_ATTRIB {
 		e.Op |= Chmod
 	}
+
+	if cookie != 0 {
+		if mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
+			w.cookiesMu.Lock()
+			w.cookies[w.cookieIndex] = koekje{cookie: cookie, path: e.Name}
+			w.cookieIndex++
+			if w.cookieIndex > 9 {
+				w.cookieIndex = 0
+			}
+			w.cookiesMu.Unlock()
+		} else if mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
+			w.cookiesMu.Lock()
+			var prev string
+			for _, c := range w.cookies {
+				if c.cookie == cookie {
+					prev = c.path
+					break
+				}
+			}
+			w.cookiesMu.Unlock()
+			e.renamedFrom = prev
+		}
+	}
 	return e
 }
+
+func (w *inotify) xSupports(op Op) bool {
+	return true // Supports everything.
+}
+
+func (w *inotify) state() {
+	w.watches.mu.Lock()
+	defer w.watches.mu.Unlock()
+	for wd, ww := range w.watches.wd {
+		fmt.Fprintf(os.Stderr, "%4d: recurse=%t %q\n", wd, ww.recurse, ww.path)
+	}
+}
diff --git a/vendor/github.com/fsnotify/fsnotify/backend_kqueue.go b/vendor/github.com/fsnotify/fsnotify/backend_kqueue.go
index 063a0915a..d8de5ab76 100644
--- a/vendor/github.com/fsnotify/fsnotify/backend_kqueue.go
+++ b/vendor/github.com/fsnotify/fsnotify/backend_kqueue.go
@@ -1,8 +1,4 @@
 //go:build freebsd || openbsd || netbsd || dragonfly || darwin
-// +build freebsd openbsd netbsd dragonfly darwin
-
-// Note: the documentation on the Watcher type and methods is generated from
-// mkdoc.zsh
 
 package fsnotify
 
@@ -11,174 +7,195 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
+	"runtime"
 	"sync"
+	"time"
 
+	"github.com/fsnotify/fsnotify/internal"
 	"golang.org/x/sys/unix"
 )
 
-// Watcher watches a set of paths, delivering events on a channel.
-//
-// A watcher should not be copied (e.g. pass it by pointer, rather than by
-// value).
-//
-// # Linux notes
-//
-// When a file is removed a Remove event won't be emitted until all file
-// descriptors are closed, and deletes will always emit a Chmod. For example:
-//
-//	fp := os.Open("file")
-//	os.Remove("file")        // Triggers Chmod
-//	fp.Close()               // Triggers Remove
-//
-// This is the event that inotify sends, so not much can be changed about this.
-//
-// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
-// for the number of watches per user, and fs.inotify.max_user_instances
-// specifies the maximum number of inotify instances per user. Every Watcher you
-// create is an "instance", and every path you add is a "watch".
-//
-// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
-// /proc/sys/fs/inotify/max_user_instances
-//
-// To increase them you can use sysctl or write the value to the /proc file:
-//
-//	# Default values on Linux 5.18
-//	sysctl fs.inotify.max_user_watches=124983
-//	sysctl fs.inotify.max_user_instances=128
-//
-// To make the changes persist on reboot edit /etc/sysctl.conf or
-// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
-// your distro's documentation):
-//
-//	fs.inotify.max_user_watches=124983
-//	fs.inotify.max_user_instances=128
-//
-// Reaching the limit will result in a "no space left on device" or "too many open
-// files" error.
-//
-// # kqueue notes (macOS, BSD)
-//
-// kqueue requires opening a file descriptor for every file that's being watched;
-// so if you're watching a directory with five files then that's six file
-// descriptors. You will run in to your system's "max open files" limit faster on
-// these platforms.
-//
-// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
-// control the maximum number of open files, as well as /etc/login.conf on BSD
-// systems.
-//
-// # Windows notes
-//
-// Paths can be added as "C:\path\to\dir", but forward slashes
-// ("C:/path/to/dir") will also work.
-//
-// When a watched directory is removed it will always send an event for the
-// directory itself, but may not send events for all files in that directory.
-// Sometimes it will send events for all times, sometimes it will send no
-// events, and often only for some files.
-//
-// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
-// value that is guaranteed to work with SMB filesystems. If you have many
-// events in quick succession this may not be enough, and you will have to use
-// [WithBufferSize] to increase the value.
-type Watcher struct {
-	// Events sends the filesystem change events.
-	//
-	// fsnotify can send the following events; a "path" here can refer to a
-	// file, directory, symbolic link, or special file like a FIFO.
-	//
-	//   fsnotify.Create    A new path was created; this may be followed by one
-	//                      or more Write events if data also gets written to a
-	//                      file.
-	//
-	//   fsnotify.Remove    A path was removed.
-	//
-	//   fsnotify.Rename    A path was renamed. A rename is always sent with the
-	//                      old path as Event.Name, and a Create event will be
-	//                      sent with the new name. Renames are only sent for
-	//                      paths that are currently watched; e.g. moving an
-	//                      unmonitored file into a monitored directory will
-	//                      show up as just a Create. Similarly, renaming a file
-	//                      to outside a monitored directory will show up as
-	//                      only a Rename.
-	//
-	//   fsnotify.Write     A file or named pipe was written to. A Truncate will
-	//                      also trigger a Write. A single "write action"
-	//                      initiated by the user may show up as one or multiple
-	//                      writes, depending on when the system syncs things to
-	//                      disk. For example when compiling a large Go program
-	//                      you may get hundreds of Write events, and you may
-	//                      want to wait until you've stopped receiving them
-	//                      (see the dedup example in cmd/fsnotify).
-	//
-	//                      Some systems may send Write event for directories
-	//                      when the directory content changes.
-	//
-	//   fsnotify.Chmod     Attributes were changed. On Linux this is also sent
-	//                      when a file is removed (or more accurately, when a
-	//                      link to an inode is removed). On kqueue it's sent
-	//                      when a file is truncated. On Windows it's never
-	//                      sent.
+type kqueue struct {
 	Events chan Event
-
-	// Errors sends any errors.
-	//
-	// ErrEventOverflow is used to indicate there are too many events:
-	//
-	//  - inotify:      There are too many queued events (fs.inotify.max_queued_events sysctl)
-	//  - windows:      The buffer size is too small; WithBufferSize() can be used to increase it.
-	//  - kqueue, fen:  Not used.
 	Errors chan error
 
-	done         chan struct{}
-	kq           int                         // File descriptor (as returned by the kqueue() syscall).
-	closepipe    [2]int                      // Pipe used for closing.
-	mu           sync.Mutex                  // Protects access to watcher data
-	watches      map[string]int              // Watched file descriptors (key: path).
-	watchesByDir map[string]map[int]struct{} // Watched file descriptors indexed by the parent directory (key: dirname(path)).
-	userWatches  map[string]struct{}         // Watches added with Watcher.Add()
-	dirFlags     map[string]uint32           // Watched directories to fflags used in kqueue.
-	paths        map[int]pathInfo            // File descriptors to path names for processing kqueue events.
-	fileExists   map[string]struct{}         // Keep track of if we know this file exists (to stop duplicate create events).
-	isClosed     bool                        // Set to true when Close() is first called
+	kq        int    // File descriptor (as returned by the kqueue() syscall).
+	closepipe [2]int // Pipe used for closing kq.
+	watches   *watches
+	done      chan struct{}
+	doneMu    sync.Mutex
 }
 
-type pathInfo struct {
-	name  string
-	isDir bool
+type (
+	watches struct {
+		mu     sync.RWMutex
+		wd     map[int]watch               // wd → watch
+		path   map[string]int              // pathname → wd
+		byDir  map[string]map[int]struct{} // dirname(path) → wd
+		seen   map[string]struct{}         // Keep track of if we know this file exists.
+		byUser map[string]struct{}         // Watches added with Watcher.Add()
+	}
+	watch struct {
+		wd       int
+		name     string
+		linkName string // In case of links; name is the target, and this is the link.
+		isDir    bool
+		dirFlags uint32
+	}
+)
+
+func newWatches() *watches {
+	return &watches{
+		wd:     make(map[int]watch),
+		path:   make(map[string]int),
+		byDir:  make(map[string]map[int]struct{}),
+		seen:   make(map[string]struct{}),
+		byUser: make(map[string]struct{}),
+	}
 }
 
-// NewWatcher creates a new Watcher.
-func NewWatcher() (*Watcher, error) {
-	return NewBufferedWatcher(0)
+func (w *watches) listPaths(userOnly bool) []string {
+	w.mu.RLock()
+	defer w.mu.RUnlock()
+
+	if userOnly {
+		l := make([]string, 0, len(w.byUser))
+		for p := range w.byUser {
+			l = append(l, p)
+		}
+		return l
+	}
+
+	l := make([]string, 0, len(w.path))
+	for p := range w.path {
+		l = append(l, p)
+	}
+	return l
 }
 
-// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
-// channel.
-//
-// The main use case for this is situations with a very large number of events
-// where the kernel buffer size can't be increased (e.g. due to lack of
-// permissions). An unbuffered Watcher will perform better for almost all use
-// cases, and whenever possible you will be better off increasing the kernel
-// buffers instead of adding a large userspace buffer.
-func NewBufferedWatcher(sz uint) (*Watcher, error) {
+func (w *watches) watchesInDir(path string) []string {
+	w.mu.RLock()
+	defer w.mu.RUnlock()
+
+	l := make([]string, 0, 4)
+	for fd := range w.byDir[path] {
+		info := w.wd[fd]
+		if _, ok := w.byUser[info.name]; !ok {
+			l = append(l, info.name)
+		}
+	}
+	return l
+}
+
+// Mark path as added by the user.
+func (w *watches) addUserWatch(path string) {
+	w.mu.Lock()
+	defer w.mu.Unlock()
+	w.byUser[path] = struct{}{}
+}
+
+func (w *watches) addLink(path string, fd int) {
+	w.mu.Lock()
+	defer w.mu.Unlock()
+
+	w.path[path] = fd
+	w.seen[path] = struct{}{}
+}
+
+func (w *watches) add(path, linkPath string, fd int, isDir bool) {
+	w.mu.Lock()
+	defer w.mu.Unlock()
+
+	w.path[path] = fd
+	w.wd[fd] = watch{wd: fd, name: path, linkName: linkPath, isDir: isDir}
+
+	parent := filepath.Dir(path)
+	byDir, ok := w.byDir[parent]
+	if !ok {
+		byDir = make(map[int]struct{}, 1)
+		w.byDir[parent] = byDir
+	}
+	byDir[fd] = struct{}{}
+}
+
+func (w *watches) byWd(fd int) (watch, bool) {
+	w.mu.RLock()
+	defer w.mu.RUnlock()
+	info, ok := w.wd[fd]
+	return info, ok
+}
+
+func (w *watches) byPath(path string) (watch, bool) {
+	w.mu.RLock()
+	defer w.mu.RUnlock()
+	info, ok := w.wd[w.path[path]]
+	return info, ok
+}
+
+func (w *watches) updateDirFlags(path string, flags uint32) {
+	w.mu.Lock()
+	defer w.mu.Unlock()
+
+	fd := w.path[path]
+	info := w.wd[fd]
+	info.dirFlags = flags
+	w.wd[fd] = info
+}
+
+func (w *watches) remove(fd int, path string) bool {
+	w.mu.Lock()
+	defer w.mu.Unlock()
+
+	isDir := w.wd[fd].isDir
+	delete(w.path, path)
+	delete(w.byUser, path)
+
+	parent := filepath.Dir(path)
+	delete(w.byDir[parent], fd)
+
+	if len(w.byDir[parent]) == 0 {
+		delete(w.byDir, parent)
+	}
+
+	delete(w.wd, fd)
+	delete(w.seen, path)
+	return isDir
+}
+
+func (w *watches) markSeen(path string, exists bool) {
+	w.mu.Lock()
+	defer w.mu.Unlock()
+	if exists {
+		w.seen[path] = struct{}{}
+	} else {
+		delete(w.seen, path)
+	}
+}
+
+func (w *watches) seenBefore(path string) bool {
+	w.mu.RLock()
+	defer w.mu.RUnlock()
+	_, ok := w.seen[path]
+	return ok
+}
+
+func newBackend(ev chan Event, errs chan error) (backend, error) {
+	return newBufferedBackend(0, ev, errs)
+}
+
+func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error) {
 	kq, closepipe, err := newKqueue()
 	if err != nil {
 		return nil, err
 	}
 
-	w := &Watcher{
-		kq:           kq,
-		closepipe:    closepipe,
-		watches:      make(map[string]int),
-		watchesByDir: make(map[string]map[int]struct{}),
-		dirFlags:     make(map[string]uint32),
-		paths:        make(map[int]pathInfo),
-		fileExists:   make(map[string]struct{}),
-		userWatches:  make(map[string]struct{}),
-		Events:       make(chan Event, sz),
-		Errors:       make(chan error),
-		done:         make(chan struct{}),
+	w := &kqueue{
+		Events:    ev,
+		Errors:    errs,
+		kq:        kq,
+		closepipe: closepipe,
+		done:      make(chan struct{}),
+		watches:   newWatches(),
 	}
 
 	go w.readEvents()
@@ -203,6 +220,8 @@ func newKqueue() (kq int, closepipe [2]int, err error) {
 		unix.Close(kq)
 		return kq, closepipe, err
 	}
+	unix.CloseOnExec(closepipe[0])
+	unix.CloseOnExec(closepipe[1])
 
 	// Register changes to listen on the closepipe.
 	changes := make([]unix.Kevent_t, 1)
@@ -221,166 +240,108 @@ func newKqueue() (kq int, closepipe [2]int, err error) {
 }
 
 // Returns true if the event was sent, or false if watcher is closed.
-func (w *Watcher) sendEvent(e Event) bool {
+func (w *kqueue) sendEvent(e Event) bool {
 	select {
-	case w.Events <- e:
-		return true
 	case <-w.done:
 		return false
+	case w.Events <- e:
+		return true
 	}
 }
 
 // Returns true if the error was sent, or false if watcher is closed.
-func (w *Watcher) sendError(err error) bool {
+func (w *kqueue) sendError(err error) bool {
+	if err == nil {
+		return true
+	}
 	select {
+	case <-w.done:
+		return false
 	case w.Errors <- err:
 		return true
+	}
+}
+
+func (w *kqueue) isClosed() bool {
+	select {
 	case <-w.done:
+		return true
+	default:
 		return false
 	}
 }
 
-// Close removes all watches and closes the Events channel.
-func (w *Watcher) Close() error {
-	w.mu.Lock()
-	if w.isClosed {
-		w.mu.Unlock()
+func (w *kqueue) Close() error {
+	w.doneMu.Lock()
+	if w.isClosed() {
+		w.doneMu.Unlock()
 		return nil
 	}
-	w.isClosed = true
+	close(w.done)
+	w.doneMu.Unlock()
 
-	// copy paths to remove while locked
-	pathsToRemove := make([]string, 0, len(w.watches))
-	for name := range w.watches {
-		pathsToRemove = append(pathsToRemove, name)
-	}
-	w.mu.Unlock() // Unlock before calling Remove, which also locks
+	pathsToRemove := w.watches.listPaths(false)
 	for _, name := range pathsToRemove {
 		w.Remove(name)
 	}
 
 	// Send "quit" message to the reader goroutine.
 	unix.Close(w.closepipe[1])
-	close(w.done)
-
 	return nil
 }
 
-// Add starts monitoring the path for changes.
-//
-// A path can only be watched once; watching it more than once is a no-op and will
-// not return an error. Paths that do not yet exist on the filesystem cannot be
-// watched.
-//
-// A watch will be automatically removed if the watched path is deleted or
-// renamed. The exception is the Windows backend, which doesn't remove the
-// watcher on renames.
-//
-// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
-// filesystems (/proc, /sys, etc.) generally don't work.
-//
-// Returns [ErrClosed] if [Watcher.Close] was called.
-//
-// See [Watcher.AddWith] for a version that allows adding options.
-//
-// # Watching directories
-//
-// All files in a directory are monitored, including new files that are created
-// after the watcher is started. Subdirectories are not watched (i.e. it's
-// non-recursive).
-//
-// # Watching files
-//
-// Watching individual files (rather than directories) is generally not
-// recommended as many programs (especially editors) update files atomically: it
-// will write to a temporary file which is then moved to to destination,
-// overwriting the original (or some variant thereof). The watcher on the
-// original file is now lost, as that no longer exists.
-//
-// The upshot of this is that a power failure or crash won't leave a
-// half-written file.
-//
-// Watch the parent directory and use Event.Name to filter out files you're not
-// interested in. There is an example of this in cmd/fsnotify/file.go.
-func (w *Watcher) Add(name string) error { return w.AddWith(name) }
+func (w *kqueue) Add(name string) error { return w.AddWith(name) }
 
-// AddWith is like [Watcher.Add], but allows adding options. When using Add()
-// the defaults described below are used.
-//
-// Possible options are:
-//
-//   - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
-//     other platforms. The default is 64K (65536 bytes).
-func (w *Watcher) AddWith(name string, opts ...addOpt) error {
-	_ = getOptions(opts...)
+func (w *kqueue) AddWith(name string, opts ...addOpt) error {
+	if debug {
+		fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s  AddWith(%q)\n",
+			time.Now().Format("15:04:05.000000000"), name)
+	}
+
+	with := getOptions(opts...)
+	if !w.xSupports(with.op) {
+		return fmt.Errorf("%w: %s", xErrUnsupported, with.op)
+	}
 
-	w.mu.Lock()
-	w.userWatches[name] = struct{}{}
-	w.mu.Unlock()
 	_, err := w.addWatch(name, noteAllEvents)
-	return err
+	if err != nil {
+		return err
+	}
+	w.watches.addUserWatch(name)
+	return nil
 }
 
-// Remove stops monitoring the path for changes.
-//
-// Directories are always removed non-recursively. For example, if you added
-// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
-//
-// Removing a path that has not yet been added returns [ErrNonExistentWatch].
-//
-// Returns nil if [Watcher.Close] was called.
-func (w *Watcher) Remove(name string) error {
+func (w *kqueue) Remove(name string) error {
+	if debug {
+		fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s  Remove(%q)\n",
+			time.Now().Format("15:04:05.000000000"), name)
+	}
 	return w.remove(name, true)
 }
 
-func (w *Watcher) remove(name string, unwatchFiles bool) error {
-	name = filepath.Clean(name)
-	w.mu.Lock()
-	if w.isClosed {
-		w.mu.Unlock()
+func (w *kqueue) remove(name string, unwatchFiles bool) error {
+	if w.isClosed() {
 		return nil
 	}
-	watchfd, ok := w.watches[name]
-	w.mu.Unlock()
+
+	name = filepath.Clean(name)
+	info, ok := w.watches.byPath(name)
 	if !ok {
 		return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
 	}
 
-	err := w.register([]int{watchfd}, unix.EV_DELETE, 0)
+	err := w.register([]int{info.wd}, unix.EV_DELETE, 0)
 	if err != nil {
 		return err
 	}
 
-	unix.Close(watchfd)
-
-	w.mu.Lock()
-	isDir := w.paths[watchfd].isDir
-	delete(w.watches, name)
-	delete(w.userWatches, name)
-
-	parentName := filepath.Dir(name)
-	delete(w.watchesByDir[parentName], watchfd)
-
-	if len(w.watchesByDir[parentName]) == 0 {
-		delete(w.watchesByDir, parentName)
-	}
+	unix.Close(info.wd)
 
-	delete(w.paths, watchfd)
-	delete(w.dirFlags, name)
-	delete(w.fileExists, name)
-	w.mu.Unlock()
+	isDir := w.watches.remove(info.wd, name)
 
 	// Find all watched paths that are in this directory that are not external.
 	if unwatchFiles && isDir {
-		var pathsToRemove []string
-		w.mu.Lock()
-		for fd := range w.watchesByDir[name] {
-			path := w.paths[fd]
-			if _, ok := w.userWatches[path.name]; !ok {
-				pathsToRemove = append(pathsToRemove, path.name)
-			}
-		}
-		w.mu.Unlock()
+		pathsToRemove := w.watches.watchesInDir(name)
 		for _, name := range pathsToRemove {
 			// Since these are internal, not much sense in propagating error to
 			// the user, as that will just confuse them with an error about a
@@ -391,23 +352,11 @@ func (w *Watcher) remove(name string, unwatchFiles bool) error {
 	return nil
 }
 
-// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
-// yet removed).
-//
-// Returns nil if [Watcher.Close] was called.
-func (w *Watcher) WatchList() []string {
-	w.mu.Lock()
-	defer w.mu.Unlock()
-	if w.isClosed {
+func (w *kqueue) WatchList() []string {
+	if w.isClosed() {
 		return nil
 	}
-
-	entries := make([]string, 0, len(w.userWatches))
-	for pathname := range w.userWatches {
-		entries = append(entries, pathname)
-	}
-
-	return entries
+	return w.watches.listPaths(true)
 }
 
 // Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
@@ -417,34 +366,26 @@ const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | un
 // described in kevent(2).
 //
 // Returns the real path to the file which was added, with symlinks resolved.
-func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
-	var isDir bool
-	name = filepath.Clean(name)
-
-	w.mu.Lock()
-	if w.isClosed {
-		w.mu.Unlock()
+func (w *kqueue) addWatch(name string, flags uint32) (string, error) {
+	if w.isClosed() {
 		return "", ErrClosed
 	}
-	watchfd, alreadyWatching := w.watches[name]
-	// We already have a watch, but we can still override flags.
-	if alreadyWatching {
-		isDir = w.paths[watchfd].isDir
-	}
-	w.mu.Unlock()
 
+	name = filepath.Clean(name)
+
+	info, alreadyWatching := w.watches.byPath(name)
 	if !alreadyWatching {
 		fi, err := os.Lstat(name)
 		if err != nil {
 			return "", err
 		}
 
-		// Don't watch sockets or named pipes
+		// Don't watch sockets or named pipes.
 		if (fi.Mode()&os.ModeSocket == os.ModeSocket) || (fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe) {
 			return "", nil
 		}
 
-		// Follow Symlinks.
+		// Follow symlinks.
 		if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
 			link, err := os.Readlink(name)
 			if err != nil {
@@ -455,18 +396,15 @@ func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
 				return "", nil
 			}
 
-			w.mu.Lock()
-			_, alreadyWatching = w.watches[link]
-			w.mu.Unlock()
-
+			_, alreadyWatching = w.watches.byPath(link)
 			if alreadyWatching {
 				// Add to watches so we don't get spurious Create events later
 				// on when we diff the directories.
-				w.watches[name] = 0
-				w.fileExists[name] = struct{}{}
+				w.watches.addLink(name, 0)
 				return link, nil
 			}
 
+			info.linkName = name
 			name = link
 			fi, err = os.Lstat(name)
 			if err != nil {
@@ -477,7 +415,7 @@ func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
 		// Retry on EINTR; open() can return EINTR in practice on macOS.
 		// See #354, and Go issues 11180 and 39237.
 		for {
-			watchfd, err = unix.Open(name, openMode, 0)
+			info.wd, err = unix.Open(name, openMode, 0)
 			if err == nil {
 				break
 			}
@@ -488,40 +426,25 @@ func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
 			return "", err
 		}
 
-		isDir = fi.IsDir()
+		info.isDir = fi.IsDir()
 	}
 
-	err := w.register([]int{watchfd}, unix.EV_ADD|unix.EV_CLEAR|unix.EV_ENABLE, flags)
+	err := w.register([]int{info.wd}, unix.EV_ADD|unix.EV_CLEAR|unix.EV_ENABLE, flags)
 	if err != nil {
-		unix.Close(watchfd)
+		unix.Close(info.wd)
 		return "", err
 	}
 
 	if !alreadyWatching {
-		w.mu.Lock()
-		parentName := filepath.Dir(name)
-		w.watches[name] = watchfd
-
-		watchesByDir, ok := w.watchesByDir[parentName]
-		if !ok {
-			watchesByDir = make(map[int]struct{}, 1)
-			w.watchesByDir[parentName] = watchesByDir
-		}
-		watchesByDir[watchfd] = struct{}{}
-		w.paths[watchfd] = pathInfo{name: name, isDir: isDir}
-		w.mu.Unlock()
+		w.watches.add(name, info.linkName, info.wd, info.isDir)
 	}
 
-	if isDir {
-		// Watch the directory if it has not been watched before, or if it was
-		// watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
-		w.mu.Lock()
-
+	// Watch the directory if it has not been watched before, or if it was
+	// watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
+	if info.isDir {
 		watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
-			(!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE)
-		// Store flags so this watch can be updated later
-		w.dirFlags[name] = flags
-		w.mu.Unlock()
+			(!alreadyWatching || (info.dirFlags&unix.NOTE_WRITE) != unix.NOTE_WRITE)
+		w.watches.updateDirFlags(name, flags)
 
 		if watchDir {
 			if err := w.watchDirectoryFiles(name); err != nil {
@@ -534,7 +457,7 @@ func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
 
 // readEvents reads from kqueue and converts the received kevents into
 // Event values that it sends down the Events channel.
-func (w *Watcher) readEvents() {
+func (w *kqueue) readEvents() {
 	defer func() {
 		close(w.Events)
 		close(w.Errors)
@@ -543,50 +466,65 @@ func (w *Watcher) readEvents() {
 	}()
 
 	eventBuffer := make([]unix.Kevent_t, 10)
-	for closed := false; !closed; {
+	for {
 		kevents, err := w.read(eventBuffer)
 		// EINTR is okay, the syscall was interrupted before timeout expired.
 		if err != nil && err != unix.EINTR {
 			if !w.sendError(fmt.Errorf("fsnotify.readEvents: %w", err)) {
-				closed = true
+				return
 			}
-			continue
 		}
 
-		// Flush the events we received to the Events channel
 		for _, kevent := range kevents {
 			var (
-				watchfd = int(kevent.Ident)
-				mask    = uint32(kevent.Fflags)
+				wd   = int(kevent.Ident)
+				mask = uint32(kevent.Fflags)
 			)
 
 			// Shut down the loop when the pipe is closed, but only after all
 			// other events have been processed.
-			if watchfd == w.closepipe[0] {
-				closed = true
-				continue
+			if wd == w.closepipe[0] {
+				return
 			}
 
-			w.mu.Lock()
-			path := w.paths[watchfd]
-			w.mu.Unlock()
+			path, ok := w.watches.byWd(wd)
+			if debug {
+				internal.Debug(path.name, &kevent)
+			}
 
-			event := w.newEvent(path.name, mask)
+			// On macOS it seems that sometimes an event with Ident=0 is
+			// delivered, and no other flags/information beyond that, even
+			// though we never saw such a file descriptor. For example in
+			// TestWatchSymlink/277 (usually at the end, but sometimes sooner):
+			//
+			// fmt.Printf("READ: %2d  %#v\n", kevent.Ident, kevent)
+			// unix.Kevent_t{Ident:0x2a, Filter:-4, Flags:0x25, Fflags:0x2, Data:0, Udata:(*uint8)(nil)}
+			// unix.Kevent_t{Ident:0x0,  Filter:-4, Flags:0x25, Fflags:0x2, Data:0, Udata:(*uint8)(nil)}
+			//
+			// The first is a normal event, the second with Ident 0. No error
+			// flag, no data, no ... nothing.
+			//
+			// I read a bit through bsd/kern_event.c from the xnu source, but I
+			// don't really see an obvious location where this is triggered –
+			// this doesn't seem intentional, but idk...
+			//
+			// Technically fd 0 is a valid descriptor, so only skip it if
+			// there's no path, and if we're on macOS.
+			if !ok && kevent.Ident == 0 && runtime.GOOS == "darwin" {
+				continue
+			}
+
+			event := w.newEvent(path.name, path.linkName, mask)
 
 			if event.Has(Rename) || event.Has(Remove) {
 				w.remove(event.Name, false)
-				w.mu.Lock()
-				delete(w.fileExists, event.Name)
-				w.mu.Unlock()
+				w.watches.markSeen(event.Name, false)
 			}
 
 			if path.isDir && event.Has(Write) && !event.Has(Remove) {
-				w.sendDirectoryChangeEvents(event.Name)
-			} else {
-				if !w.sendEvent(event) {
-					closed = true
-					continue
-				}
+				w.dirChange(event.Name)
+			} else if !w.sendEvent(event) {
+				return
 			}
 
 			if event.Has(Remove) {
@@ -594,25 +532,34 @@ func (w *Watcher) readEvents() {
 				// mv f1 f2 will delete f2, then create f2.
 				if path.isDir {
 					fileDir := filepath.Clean(event.Name)
-					w.mu.Lock()
-					_, found := w.watches[fileDir]
-					w.mu.Unlock()
+					_, found := w.watches.byPath(fileDir)
 					if found {
-						err := w.sendDirectoryChangeEvents(fileDir)
-						if err != nil {
-							if !w.sendError(err) {
-								closed = true
-							}
+						// TODO: this branch is never triggered in any test.
+						// Added in d6220df (2012).
+						// isDir check added in 8611c35 (2016): https://github.com/fsnotify/fsnotify/pull/111
+						//
+						// I don't really get how this can be triggered either.
+						// And it wasn't triggered in the patch that added it,
+						// either.
+						//
+						// Original also had a comment:
+						//   make sure the directory exists before we watch for
+						//   changes. When we do a recursive watch and perform
+						//   rm -rf, the parent directory might have gone
+						//   missing, ignore the missing directory and let the
+						//   upcoming delete event remove the watch from the
+						//   parent directory.
+						err := w.dirChange(fileDir)
+						if !w.sendError(err) {
+							return
 						}
 					}
 				} else {
-					filePath := filepath.Clean(event.Name)
-					if fi, err := os.Lstat(filePath); err == nil {
-						err := w.sendFileCreatedEventIfNew(filePath, fi)
-						if err != nil {
-							if !w.sendError(err) {
-								closed = true
-							}
+					path := filepath.Clean(event.Name)
+					if fi, err := os.Lstat(path); err == nil {
+						err := w.sendCreateIfNew(path, fi)
+						if !w.sendError(err) {
+							return
 						}
 					}
 				}
@@ -622,8 +569,14 @@ func (w *Watcher) readEvents() {
 }
 
 // newEvent returns an platform-independent Event based on kqueue Fflags.
-func (w *Watcher) newEvent(name string, mask uint32) Event {
+func (w *kqueue) newEvent(name, linkName string, mask uint32) Event {
 	e := Event{Name: name}
+	if linkName != "" {
+		// If the user watched "/path/link" then emit events as "/path/link"
+		// rather than "/path/target".
+		e.Name = linkName
+	}
+
 	if mask&unix.NOTE_DELETE == unix.NOTE_DELETE {
 		e.Op |= Remove
 	}
@@ -645,8 +598,7 @@ func (w *Watcher) newEvent(name string, mask uint32) Event {
 }
 
 // watchDirectoryFiles to mimic inotify when adding a watch on a directory
-func (w *Watcher) watchDirectoryFiles(dirPath string) error {
-	// Get all files
+func (w *kqueue) watchDirectoryFiles(dirPath string) error {
 	files, err := os.ReadDir(dirPath)
 	if err != nil {
 		return err
@@ -674,9 +626,7 @@ func (w *Watcher) watchDirectoryFiles(dirPath string) error {
 			}
 		}
 
-		w.mu.Lock()
-		w.fileExists[cleanPath] = struct{}{}
-		w.mu.Unlock()
+		w.watches.markSeen(cleanPath, true)
 	}
 
 	return nil
@@ -686,7 +636,7 @@ func (w *Watcher) watchDirectoryFiles(dirPath string) error {
 //
 // This functionality is to have the BSD watcher match the inotify, which sends
 // a create event for files created in a watched directory.
-func (w *Watcher) sendDirectoryChangeEvents(dir string) error {
+func (w *kqueue) dirChange(dir string) error {
 	files, err := os.ReadDir(dir)
 	if err != nil {
 		// Directory no longer exists: we can ignore this safely. kqueue will
@@ -694,61 +644,51 @@ func (w *Watcher) sendDirectoryChangeEvents(dir string) error {
 		if errors.Is(err, os.ErrNotExist) {
 			return nil
 		}
-		return fmt.Errorf("fsnotify.sendDirectoryChangeEvents: %w", err)
+		return fmt.Errorf("fsnotify.dirChange: %w", err)
 	}
 
 	for _, f := range files {
 		fi, err := f.Info()
 		if err != nil {
-			return fmt.Errorf("fsnotify.sendDirectoryChangeEvents: %w", err)
+			return fmt.Errorf("fsnotify.dirChange: %w", err)
 		}
 
-		err = w.sendFileCreatedEventIfNew(filepath.Join(dir, fi.Name()), fi)
+		err = w.sendCreateIfNew(filepath.Join(dir, fi.Name()), fi)
 		if err != nil {
 			// Don't need to send an error if this file isn't readable.
 			if errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM) {
 				return nil
 			}
-			return fmt.Errorf("fsnotify.sendDirectoryChangeEvents: %w", err)
+			return fmt.Errorf("fsnotify.dirChange: %w", err)
 		}
 	}
 	return nil
 }
 
-// sendFileCreatedEvent sends a create event if the file isn't already being tracked.
-func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fi os.FileInfo) (err error) {
-	w.mu.Lock()
-	_, doesExist := w.fileExists[filePath]
-	w.mu.Unlock()
-	if !doesExist {
-		if !w.sendEvent(Event{Name: filePath, Op: Create}) {
-			return
+// Send a create event if the file isn't already being tracked, and start
+// watching this file.
+func (w *kqueue) sendCreateIfNew(path string, fi os.FileInfo) error {
+	if !w.watches.seenBefore(path) {
+		if !w.sendEvent(Event{Name: path, Op: Create}) {
+			return nil
 		}
 	}
 
-	// like watchDirectoryFiles (but without doing another ReadDir)
-	filePath, err = w.internalWatch(filePath, fi)
+	// Like watchDirectoryFiles, but without doing another ReadDir.
+	path, err := w.internalWatch(path, fi)
 	if err != nil {
 		return err
 	}
-
-	w.mu.Lock()
-	w.fileExists[filePath] = struct{}{}
-	w.mu.Unlock()
-
+	w.watches.markSeen(path, true)
 	return nil
 }
 
-func (w *Watcher) internalWatch(name string, fi os.FileInfo) (string, error) {
+func (w *kqueue) internalWatch(name string, fi os.FileInfo) (string, error) {
 	if fi.IsDir() {
 		// mimic Linux providing delete events for subdirectories, but preserve
 		// the flags used if currently watching subdirectory
-		w.mu.Lock()
-		flags := w.dirFlags[name]
-		w.mu.Unlock()
-
-		flags |= unix.NOTE_DELETE | unix.NOTE_RENAME
-		return w.addWatch(name, flags)
+		info, _ := w.watches.byPath(name)
+		return w.addWatch(name, info.dirFlags|unix.NOTE_DELETE|unix.NOTE_RENAME)
 	}
 
 	// watch file to mimic Linux inotify
@@ -756,7 +696,7 @@ func (w *Watcher) internalWatch(name string, fi os.FileInfo) (string, error) {
 }
 
 // Register events with the queue.
-func (w *Watcher) register(fds []int, flags int, fflags uint32) error {
+func (w *kqueue) register(fds []int, flags int, fflags uint32) error {
 	changes := make([]unix.Kevent_t, len(fds))
 	for i, fd := range fds {
 		// SetKevent converts int to the platform-specific types.
@@ -773,10 +713,21 @@ func (w *Watcher) register(fds []int, flags int, fflags uint32) error {
 }
 
 // read retrieves pending events, or waits until an event occurs.
-func (w *Watcher) read(events []unix.Kevent_t) ([]unix.Kevent_t, error) {
+func (w *kqueue) read(events []unix.Kevent_t) ([]unix.Kevent_t, error) {
 	n, err := unix.Kevent(w.kq, nil, events, nil)
 	if err != nil {
 		return nil, err
 	}
 	return events[0:n], nil
 }
+
+func (w *kqueue) xSupports(op Op) bool {
+	if runtime.GOOS == "freebsd" {
+		//return true // Supports everything.
+	}
+	if op.Has(xUnportableOpen) || op.Has(xUnportableRead) ||
+		op.Has(xUnportableCloseWrite) || op.Has(xUnportableCloseRead) {
+		return false
+	}
+	return true
+}
diff --git a/vendor/github.com/fsnotify/fsnotify/backend_other.go b/vendor/github.com/fsnotify/fsnotify/backend_other.go
index d34a23c01..5eb5dbc66 100644
--- a/vendor/github.com/fsnotify/fsnotify/backend_other.go
+++ b/vendor/github.com/fsnotify/fsnotify/backend_other.go
@@ -1,205 +1,23 @@
 //go:build appengine || (!darwin && !dragonfly && !freebsd && !openbsd && !linux && !netbsd && !solaris && !windows)
-// +build appengine !darwin,!dragonfly,!freebsd,!openbsd,!linux,!netbsd,!solaris,!windows
-
-// Note: the documentation on the Watcher type and methods is generated from
-// mkdoc.zsh
 
 package fsnotify
 
 import "errors"
 
-// Watcher watches a set of paths, delivering events on a channel.
-//
-// A watcher should not be copied (e.g. pass it by pointer, rather than by
-// value).
-//
-// # Linux notes
-//
-// When a file is removed a Remove event won't be emitted until all file
-// descriptors are closed, and deletes will always emit a Chmod. For example:
-//
-//	fp := os.Open("file")
-//	os.Remove("file")        // Triggers Chmod
-//	fp.Close()               // Triggers Remove
-//
-// This is the event that inotify sends, so not much can be changed about this.
-//
-// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
-// for the number of watches per user, and fs.inotify.max_user_instances
-// specifies the maximum number of inotify instances per user. Every Watcher you
-// create is an "instance", and every path you add is a "watch".
-//
-// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
-// /proc/sys/fs/inotify/max_user_instances
-//
-// To increase them you can use sysctl or write the value to the /proc file:
-//
-//	# Default values on Linux 5.18
-//	sysctl fs.inotify.max_user_watches=124983
-//	sysctl fs.inotify.max_user_instances=128
-//
-// To make the changes persist on reboot edit /etc/sysctl.conf or
-// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
-// your distro's documentation):
-//
-//	fs.inotify.max_user_watches=124983
-//	fs.inotify.max_user_instances=128
-//
-// Reaching the limit will result in a "no space left on device" or "too many open
-// files" error.
-//
-// # kqueue notes (macOS, BSD)
-//
-// kqueue requires opening a file descriptor for every file that's being watched;
-// so if you're watching a directory with five files then that's six file
-// descriptors. You will run in to your system's "max open files" limit faster on
-// these platforms.
-//
-// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
-// control the maximum number of open files, as well as /etc/login.conf on BSD
-// systems.
-//
-// # Windows notes
-//
-// Paths can be added as "C:\path\to\dir", but forward slashes
-// ("C:/path/to/dir") will also work.
-//
-// When a watched directory is removed it will always send an event for the
-// directory itself, but may not send events for all files in that directory.
-// Sometimes it will send events for all times, sometimes it will send no
-// events, and often only for some files.
-//
-// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
-// value that is guaranteed to work with SMB filesystems. If you have many
-// events in quick succession this may not be enough, and you will have to use
-// [WithBufferSize] to increase the value.
-type Watcher struct {
-	// Events sends the filesystem change events.
-	//
-	// fsnotify can send the following events; a "path" here can refer to a
-	// file, directory, symbolic link, or special file like a FIFO.
-	//
-	//   fsnotify.Create    A new path was created; this may be followed by one
-	//                      or more Write events if data also gets written to a
-	//                      file.
-	//
-	//   fsnotify.Remove    A path was removed.
-	//
-	//   fsnotify.Rename    A path was renamed. A rename is always sent with the
-	//                      old path as Event.Name, and a Create event will be
-	//                      sent with the new name. Renames are only sent for
-	//                      paths that are currently watched; e.g. moving an
-	//                      unmonitored file into a monitored directory will
-	//                      show up as just a Create. Similarly, renaming a file
-	//                      to outside a monitored directory will show up as
-	//                      only a Rename.
-	//
-	//   fsnotify.Write     A file or named pipe was written to. A Truncate will
-	//                      also trigger a Write. A single "write action"
-	//                      initiated by the user may show up as one or multiple
-	//                      writes, depending on when the system syncs things to
-	//                      disk. For example when compiling a large Go program
-	//                      you may get hundreds of Write events, and you may
-	//                      want to wait until you've stopped receiving them
-	//                      (see the dedup example in cmd/fsnotify).
-	//
-	//                      Some systems may send Write event for directories
-	//                      when the directory content changes.
-	//
-	//   fsnotify.Chmod     Attributes were changed. On Linux this is also sent
-	//                      when a file is removed (or more accurately, when a
-	//                      link to an inode is removed). On kqueue it's sent
-	//                      when a file is truncated. On Windows it's never
-	//                      sent.
+type other struct {
 	Events chan Event
-
-	// Errors sends any errors.
-	//
-	// ErrEventOverflow is used to indicate there are too many events:
-	//
-	//  - inotify:      There are too many queued events (fs.inotify.max_queued_events sysctl)
-	//  - windows:      The buffer size is too small; WithBufferSize() can be used to increase it.
-	//  - kqueue, fen:  Not used.
 	Errors chan error
 }
 
-// NewWatcher creates a new Watcher.
-func NewWatcher() (*Watcher, error) {
+func newBackend(ev chan Event, errs chan error) (backend, error) {
 	return nil, errors.New("fsnotify not supported on the current platform")
 }
-
-// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
-// channel.
-//
-// The main use case for this is situations with a very large number of events
-// where the kernel buffer size can't be increased (e.g. due to lack of
-// permissions). An unbuffered Watcher will perform better for almost all use
-// cases, and whenever possible you will be better off increasing the kernel
-// buffers instead of adding a large userspace buffer.
-func NewBufferedWatcher(sz uint) (*Watcher, error) { return NewWatcher() }
-
-// Close removes all watches and closes the Events channel.
-func (w *Watcher) Close() error { return nil }
-
-// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
-// yet removed).
-//
-// Returns nil if [Watcher.Close] was called.
-func (w *Watcher) WatchList() []string { return nil }
-
-// Add starts monitoring the path for changes.
-//
-// A path can only be watched once; watching it more than once is a no-op and will
-// not return an error. Paths that do not yet exist on the filesystem cannot be
-// watched.
-//
-// A watch will be automatically removed if the watched path is deleted or
-// renamed. The exception is the Windows backend, which doesn't remove the
-// watcher on renames.
-//
-// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
-// filesystems (/proc, /sys, etc.) generally don't work.
-//
-// Returns [ErrClosed] if [Watcher.Close] was called.
-//
-// See [Watcher.AddWith] for a version that allows adding options.
-//
-// # Watching directories
-//
-// All files in a directory are monitored, including new files that are created
-// after the watcher is started. Subdirectories are not watched (i.e. it's
-// non-recursive).
-//
-// # Watching files
-//
-// Watching individual files (rather than directories) is generally not
-// recommended as many programs (especially editors) update files atomically: it
-// will write to a temporary file which is then moved to to destination,
-// overwriting the original (or some variant thereof). The watcher on the
-// original file is now lost, as that no longer exists.
-//
-// The upshot of this is that a power failure or crash won't leave a
-// half-written file.
-//
-// Watch the parent directory and use Event.Name to filter out files you're not
-// interested in. There is an example of this in cmd/fsnotify/file.go.
-func (w *Watcher) Add(name string) error { return nil }
-
-// AddWith is like [Watcher.Add], but allows adding options. When using Add()
-// the defaults described below are used.
-//
-// Possible options are:
-//
-//   - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
-//     other platforms. The default is 64K (65536 bytes).
-func (w *Watcher) AddWith(name string, opts ...addOpt) error { return nil }
-
-// Remove stops monitoring the path for changes.
-//
-// Directories are always removed non-recursively. For example, if you added
-// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
-//
-// Removing a path that has not yet been added returns [ErrNonExistentWatch].
-//
-// Returns nil if [Watcher.Close] was called.
-func (w *Watcher) Remove(name string) error { return nil }
+func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error) {
+	return newBackend(ev, errs)
+}
+func (w *other) Close() error                              { return nil }
+func (w *other) WatchList() []string                       { return nil }
+func (w *other) Add(name string) error                     { return nil }
+func (w *other) AddWith(name string, opts ...addOpt) error { return nil }
+func (w *other) Remove(name string) error                  { return nil }
+func (w *other) xSupports(op Op) bool                      { return false }
diff --git a/vendor/github.com/fsnotify/fsnotify/backend_windows.go b/vendor/github.com/fsnotify/fsnotify/backend_windows.go
index 9bc91e5d6..c54a63083 100644
--- a/vendor/github.com/fsnotify/fsnotify/backend_windows.go
+++ b/vendor/github.com/fsnotify/fsnotify/backend_windows.go
@@ -1,12 +1,8 @@
 //go:build windows
-// +build windows
 
 // Windows backend based on ReadDirectoryChangesW()
 //
 // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw
-//
-// Note: the documentation on the Watcher type and methods is generated from
-// mkdoc.zsh
 
 package fsnotify
 
@@ -19,123 +15,15 @@ import (
 	"runtime"
 	"strings"
 	"sync"
+	"time"
 	"unsafe"
 
+	"github.com/fsnotify/fsnotify/internal"
 	"golang.org/x/sys/windows"
 )
 
-// Watcher watches a set of paths, delivering events on a channel.
-//
-// A watcher should not be copied (e.g. pass it by pointer, rather than by
-// value).
-//
-// # Linux notes
-//
-// When a file is removed a Remove event won't be emitted until all file
-// descriptors are closed, and deletes will always emit a Chmod. For example:
-//
-//	fp := os.Open("file")
-//	os.Remove("file")        // Triggers Chmod
-//	fp.Close()               // Triggers Remove
-//
-// This is the event that inotify sends, so not much can be changed about this.
-//
-// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
-// for the number of watches per user, and fs.inotify.max_user_instances
-// specifies the maximum number of inotify instances per user. Every Watcher you
-// create is an "instance", and every path you add is a "watch".
-//
-// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
-// /proc/sys/fs/inotify/max_user_instances
-//
-// To increase them you can use sysctl or write the value to the /proc file:
-//
-//	# Default values on Linux 5.18
-//	sysctl fs.inotify.max_user_watches=124983
-//	sysctl fs.inotify.max_user_instances=128
-//
-// To make the changes persist on reboot edit /etc/sysctl.conf or
-// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
-// your distro's documentation):
-//
-//	fs.inotify.max_user_watches=124983
-//	fs.inotify.max_user_instances=128
-//
-// Reaching the limit will result in a "no space left on device" or "too many open
-// files" error.
-//
-// # kqueue notes (macOS, BSD)
-//
-// kqueue requires opening a file descriptor for every file that's being watched;
-// so if you're watching a directory with five files then that's six file
-// descriptors. You will run in to your system's "max open files" limit faster on
-// these platforms.
-//
-// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
-// control the maximum number of open files, as well as /etc/login.conf on BSD
-// systems.
-//
-// # Windows notes
-//
-// Paths can be added as "C:\path\to\dir", but forward slashes
-// ("C:/path/to/dir") will also work.
-//
-// When a watched directory is removed it will always send an event for the
-// directory itself, but may not send events for all files in that directory.
-// Sometimes it will send events for all times, sometimes it will send no
-// events, and often only for some files.
-//
-// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
-// value that is guaranteed to work with SMB filesystems. If you have many
-// events in quick succession this may not be enough, and you will have to use
-// [WithBufferSize] to increase the value.
-type Watcher struct {
-	// Events sends the filesystem change events.
-	//
-	// fsnotify can send the following events; a "path" here can refer to a
-	// file, directory, symbolic link, or special file like a FIFO.
-	//
-	//   fsnotify.Create    A new path was created; this may be followed by one
-	//                      or more Write events if data also gets written to a
-	//                      file.
-	//
-	//   fsnotify.Remove    A path was removed.
-	//
-	//   fsnotify.Rename    A path was renamed. A rename is always sent with the
-	//                      old path as Event.Name, and a Create event will be
-	//                      sent with the new name. Renames are only sent for
-	//                      paths that are currently watched; e.g. moving an
-	//                      unmonitored file into a monitored directory will
-	//                      show up as just a Create. Similarly, renaming a file
-	//                      to outside a monitored directory will show up as
-	//                      only a Rename.
-	//
-	//   fsnotify.Write     A file or named pipe was written to. A Truncate will
-	//                      also trigger a Write. A single "write action"
-	//                      initiated by the user may show up as one or multiple
-	//                      writes, depending on when the system syncs things to
-	//                      disk. For example when compiling a large Go program
-	//                      you may get hundreds of Write events, and you may
-	//                      want to wait until you've stopped receiving them
-	//                      (see the dedup example in cmd/fsnotify).
-	//
-	//                      Some systems may send Write event for directories
-	//                      when the directory content changes.
-	//
-	//   fsnotify.Chmod     Attributes were changed. On Linux this is also sent
-	//                      when a file is removed (or more accurately, when a
-	//                      link to an inode is removed). On kqueue it's sent
-	//                      when a file is truncated. On Windows it's never
-	//                      sent.
+type readDirChangesW struct {
 	Events chan Event
-
-	// Errors sends any errors.
-	//
-	// ErrEventOverflow is used to indicate there are too many events:
-	//
-	//  - inotify:      There are too many queued events (fs.inotify.max_queued_events sysctl)
-	//  - windows:      The buffer size is too small; WithBufferSize() can be used to increase it.
-	//  - kqueue, fen:  Not used.
 	Errors chan error
 
 	port  windows.Handle // Handle to completion port
@@ -147,48 +35,40 @@ type Watcher struct {
 	closed  bool       // Set to true when Close() is first called
 }
 
-// NewWatcher creates a new Watcher.
-func NewWatcher() (*Watcher, error) {
-	return NewBufferedWatcher(50)
+func newBackend(ev chan Event, errs chan error) (backend, error) {
+	return newBufferedBackend(50, ev, errs)
 }
 
-// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
-// channel.
-//
-// The main use case for this is situations with a very large number of events
-// where the kernel buffer size can't be increased (e.g. due to lack of
-// permissions). An unbuffered Watcher will perform better for almost all use
-// cases, and whenever possible you will be better off increasing the kernel
-// buffers instead of adding a large userspace buffer.
-func NewBufferedWatcher(sz uint) (*Watcher, error) {
+func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error) {
 	port, err := windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 0)
 	if err != nil {
 		return nil, os.NewSyscallError("CreateIoCompletionPort", err)
 	}
-	w := &Watcher{
+	w := &readDirChangesW{
+		Events:  ev,
+		Errors:  errs,
 		port:    port,
 		watches: make(watchMap),
 		input:   make(chan *input, 1),
-		Events:  make(chan Event, sz),
-		Errors:  make(chan error),
 		quit:    make(chan chan<- error, 1),
 	}
 	go w.readEvents()
 	return w, nil
 }
 
-func (w *Watcher) isClosed() bool {
+func (w *readDirChangesW) isClosed() bool {
 	w.mu.Lock()
 	defer w.mu.Unlock()
 	return w.closed
 }
 
-func (w *Watcher) sendEvent(name string, mask uint64) bool {
+func (w *readDirChangesW) sendEvent(name, renamedFrom string, mask uint64) bool {
 	if mask == 0 {
 		return false
 	}
 
 	event := w.newEvent(name, uint32(mask))
+	event.renamedFrom = renamedFrom
 	select {
 	case ch := <-w.quit:
 		w.quit <- ch
@@ -198,17 +78,19 @@ func (w *Watcher) sendEvent(name string, mask uint64) bool {
 }
 
 // Returns true if the error was sent, or false if watcher is closed.
-func (w *Watcher) sendError(err error) bool {
+func (w *readDirChangesW) sendError(err error) bool {
+	if err == nil {
+		return true
+	}
 	select {
 	case w.Errors <- err:
 		return true
 	case <-w.quit:
+		return false
 	}
-	return false
 }
 
-// Close removes all watches and closes the Events channel.
-func (w *Watcher) Close() error {
+func (w *readDirChangesW) Close() error {
 	if w.isClosed() {
 		return nil
 	}
@@ -226,57 +108,21 @@ func (w *Watcher) Close() error {
 	return <-ch
 }
 
-// Add starts monitoring the path for changes.
-//
-// A path can only be watched once; watching it more than once is a no-op and will
-// not return an error. Paths that do not yet exist on the filesystem cannot be
-// watched.
-//
-// A watch will be automatically removed if the watched path is deleted or
-// renamed. The exception is the Windows backend, which doesn't remove the
-// watcher on renames.
-//
-// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
-// filesystems (/proc, /sys, etc.) generally don't work.
-//
-// Returns [ErrClosed] if [Watcher.Close] was called.
-//
-// See [Watcher.AddWith] for a version that allows adding options.
-//
-// # Watching directories
-//
-// All files in a directory are monitored, including new files that are created
-// after the watcher is started. Subdirectories are not watched (i.e. it's
-// non-recursive).
-//
-// # Watching files
-//
-// Watching individual files (rather than directories) is generally not
-// recommended as many programs (especially editors) update files atomically: it
-// will write to a temporary file which is then moved to to destination,
-// overwriting the original (or some variant thereof). The watcher on the
-// original file is now lost, as that no longer exists.
-//
-// The upshot of this is that a power failure or crash won't leave a
-// half-written file.
-//
-// Watch the parent directory and use Event.Name to filter out files you're not
-// interested in. There is an example of this in cmd/fsnotify/file.go.
-func (w *Watcher) Add(name string) error { return w.AddWith(name) }
+func (w *readDirChangesW) Add(name string) error { return w.AddWith(name) }
 
-// AddWith is like [Watcher.Add], but allows adding options. When using Add()
-// the defaults described below are used.
-//
-// Possible options are:
-//
-//   - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
-//     other platforms. The default is 64K (65536 bytes).
-func (w *Watcher) AddWith(name string, opts ...addOpt) error {
+func (w *readDirChangesW) AddWith(name string, opts ...addOpt) error {
 	if w.isClosed() {
 		return ErrClosed
 	}
+	if debug {
+		fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s  AddWith(%q)\n",
+			time.Now().Format("15:04:05.000000000"), filepath.ToSlash(name))
+	}
 
 	with := getOptions(opts...)
+	if !w.xSupports(with.op) {
+		return fmt.Errorf("%w: %s", xErrUnsupported, with.op)
+	}
 	if with.bufsize < 4096 {
 		return fmt.Errorf("fsnotify.WithBufferSize: buffer size cannot be smaller than 4096 bytes")
 	}
@@ -295,18 +141,14 @@ func (w *Watcher) AddWith(name string, opts ...addOpt) error {
 	return <-in.reply
 }
 
-// Remove stops monitoring the path for changes.
-//
-// Directories are always removed non-recursively. For example, if you added
-// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
-//
-// Removing a path that has not yet been added returns [ErrNonExistentWatch].
-//
-// Returns nil if [Watcher.Close] was called.
-func (w *Watcher) Remove(name string) error {
+func (w *readDirChangesW) Remove(name string) error {
 	if w.isClosed() {
 		return nil
 	}
+	if debug {
+		fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s  Remove(%q)\n",
+			time.Now().Format("15:04:05.000000000"), filepath.ToSlash(name))
+	}
 
 	in := &input{
 		op:    opRemoveWatch,
@@ -320,11 +162,7 @@ func (w *Watcher) Remove(name string) error {
 	return <-in.reply
 }
 
-// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
-// yet removed).
-//
-// Returns nil if [Watcher.Close] was called.
-func (w *Watcher) WatchList() []string {
+func (w *readDirChangesW) WatchList() []string {
 	if w.isClosed() {
 		return nil
 	}
@@ -335,7 +173,13 @@ func (w *Watcher) WatchList() []string {
 	entries := make([]string, 0, len(w.watches))
 	for _, entry := range w.watches {
 		for _, watchEntry := range entry {
-			entries = append(entries, watchEntry.path)
+			for name := range watchEntry.names {
+				entries = append(entries, filepath.Join(watchEntry.path, name))
+			}
+			// the directory itself is being watched
+			if watchEntry.mask != 0 {
+				entries = append(entries, watchEntry.path)
+			}
 		}
 	}
 
@@ -361,7 +205,7 @@ const (
 	sysFSIGNORED    = 0x8000
 )
 
-func (w *Watcher) newEvent(name string, mask uint32) Event {
+func (w *readDirChangesW) newEvent(name string, mask uint32) Event {
 	e := Event{Name: name}
 	if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO {
 		e.Op |= Create
@@ -417,7 +261,7 @@ type (
 	watchMap map[uint32]indexMap
 )
 
-func (w *Watcher) wakeupReader() error {
+func (w *readDirChangesW) wakeupReader() error {
 	err := windows.PostQueuedCompletionStatus(w.port, 0, 0, nil)
 	if err != nil {
 		return os.NewSyscallError("PostQueuedCompletionStatus", err)
@@ -425,7 +269,7 @@ func (w *Watcher) wakeupReader() error {
 	return nil
 }
 
-func (w *Watcher) getDir(pathname string) (dir string, err error) {
+func (w *readDirChangesW) getDir(pathname string) (dir string, err error) {
 	attr, err := windows.GetFileAttributes(windows.StringToUTF16Ptr(pathname))
 	if err != nil {
 		return "", os.NewSyscallError("GetFileAttributes", err)
@@ -439,7 +283,7 @@ func (w *Watcher) getDir(pathname string) (dir string, err error) {
 	return
 }
 
-func (w *Watcher) getIno(path string) (ino *inode, err error) {
+func (w *readDirChangesW) getIno(path string) (ino *inode, err error) {
 	h, err := windows.CreateFile(windows.StringToUTF16Ptr(path),
 		windows.FILE_LIST_DIRECTORY,
 		windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE,
@@ -482,9 +326,8 @@ func (m watchMap) set(ino *inode, watch *watch) {
 }
 
 // Must run within the I/O thread.
-func (w *Watcher) addWatch(pathname string, flags uint64, bufsize int) error {
-	//pathname, recurse := recursivePath(pathname)
-	recurse := false
+func (w *readDirChangesW) addWatch(pathname string, flags uint64, bufsize int) error {
+	pathname, recurse := recursivePath(pathname)
 
 	dir, err := w.getDir(pathname)
 	if err != nil {
@@ -538,7 +381,7 @@ func (w *Watcher) addWatch(pathname string, flags uint64, bufsize int) error {
 }
 
 // Must run within the I/O thread.
-func (w *Watcher) remWatch(pathname string) error {
+func (w *readDirChangesW) remWatch(pathname string) error {
 	pathname, recurse := recursivePath(pathname)
 
 	dir, err := w.getDir(pathname)
@@ -566,11 +409,11 @@ func (w *Watcher) remWatch(pathname string) error {
 		return fmt.Errorf("%w: %s", ErrNonExistentWatch, pathname)
 	}
 	if pathname == dir {
-		w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
+		w.sendEvent(watch.path, "", watch.mask&sysFSIGNORED)
 		watch.mask = 0
 	} else {
 		name := filepath.Base(pathname)
-		w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED)
+		w.sendEvent(filepath.Join(watch.path, name), "", watch.names[name]&sysFSIGNORED)
 		delete(watch.names, name)
 	}
 
@@ -578,23 +421,23 @@ func (w *Watcher) remWatch(pathname string) error {
 }
 
 // Must run within the I/O thread.
-func (w *Watcher) deleteWatch(watch *watch) {
+func (w *readDirChangesW) deleteWatch(watch *watch) {
 	for name, mask := range watch.names {
 		if mask&provisional == 0 {
-			w.sendEvent(filepath.Join(watch.path, name), mask&sysFSIGNORED)
+			w.sendEvent(filepath.Join(watch.path, name), "", mask&sysFSIGNORED)
 		}
 		delete(watch.names, name)
 	}
 	if watch.mask != 0 {
 		if watch.mask&provisional == 0 {
-			w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
+			w.sendEvent(watch.path, "", watch.mask&sysFSIGNORED)
 		}
 		watch.mask = 0
 	}
 }
 
 // Must run within the I/O thread.
-func (w *Watcher) startRead(watch *watch) error {
+func (w *readDirChangesW) startRead(watch *watch) error {
 	err := windows.CancelIo(watch.ino.handle)
 	if err != nil {
 		w.sendError(os.NewSyscallError("CancelIo", err))
@@ -624,7 +467,7 @@ func (w *Watcher) startRead(watch *watch) error {
 		err := os.NewSyscallError("ReadDirectoryChanges", rdErr)
 		if rdErr == windows.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
 			// Watched directory was probably removed
-			w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
+			w.sendEvent(watch.path, "", watch.mask&sysFSDELETESELF)
 			err = nil
 		}
 		w.deleteWatch(watch)
@@ -637,7 +480,7 @@ func (w *Watcher) startRead(watch *watch) error {
 // readEvents reads from the I/O completion port, converts the
 // received events into Event objects and sends them via the Events channel.
 // Entry point to the I/O thread.
-func (w *Watcher) readEvents() {
+func (w *readDirChangesW) readEvents() {
 	var (
 		n   uint32
 		key uintptr
@@ -700,7 +543,7 @@ func (w *Watcher) readEvents() {
 			}
 		case windows.ERROR_ACCESS_DENIED:
 			// Watched directory was probably removed
-			w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
+			w.sendEvent(watch.path, "", watch.mask&sysFSDELETESELF)
 			w.deleteWatch(watch)
 			w.startRead(watch)
 			continue
@@ -733,6 +576,10 @@ func (w *Watcher) readEvents() {
 			name := windows.UTF16ToString(buf)
 			fullname := filepath.Join(watch.path, name)
 
+			if debug {
+				internal.Debug(fullname, raw.Action)
+			}
+
 			var mask uint64
 			switch raw.Action {
 			case windows.FILE_ACTION_REMOVED:
@@ -761,21 +608,22 @@ func (w *Watcher) readEvents() {
 				}
 			}
 
-			sendNameEvent := func() {
-				w.sendEvent(fullname, watch.names[name]&mask)
-			}
 			if raw.Action != windows.FILE_ACTION_RENAMED_NEW_NAME {
-				sendNameEvent()
+				w.sendEvent(fullname, "", watch.names[name]&mask)
 			}
 			if raw.Action == windows.FILE_ACTION_REMOVED {
-				w.sendEvent(fullname, watch.names[name]&sysFSIGNORED)
+				w.sendEvent(fullname, "", watch.names[name]&sysFSIGNORED)
 				delete(watch.names, name)
 			}
 
-			w.sendEvent(fullname, watch.mask&w.toFSnotifyFlags(raw.Action))
+			if watch.rename != "" && raw.Action == windows.FILE_ACTION_RENAMED_NEW_NAME {
+				w.sendEvent(fullname, filepath.Join(watch.path, watch.rename), watch.mask&w.toFSnotifyFlags(raw.Action))
+			} else {
+				w.sendEvent(fullname, "", watch.mask&w.toFSnotifyFlags(raw.Action))
+			}
+
 			if raw.Action == windows.FILE_ACTION_RENAMED_NEW_NAME {
-				fullname = filepath.Join(watch.path, watch.rename)
-				sendNameEvent()
+				w.sendEvent(filepath.Join(watch.path, watch.rename), "", watch.names[name]&mask)
 			}
 
 			// Move to the next event in the buffer
@@ -787,8 +635,7 @@ func (w *Watcher) readEvents() {
 			// Error!
 			if offset >= n {
 				//lint:ignore ST1005 Windows should be capitalized
-				w.sendError(errors.New(
-					"Windows system assumed buffer larger than it is, events have likely been missed"))
+				w.sendError(errors.New("Windows system assumed buffer larger than it is, events have likely been missed"))
 				break
 			}
 		}
@@ -799,7 +646,7 @@ func (w *Watcher) readEvents() {
 	}
 }
 
-func (w *Watcher) toWindowsFlags(mask uint64) uint32 {
+func (w *readDirChangesW) toWindowsFlags(mask uint64) uint32 {
 	var m uint32
 	if mask&sysFSMODIFY != 0 {
 		m |= windows.FILE_NOTIFY_CHANGE_LAST_WRITE
@@ -810,7 +657,7 @@ func (w *Watcher) toWindowsFlags(mask uint64) uint32 {
 	return m
 }
 
-func (w *Watcher) toFSnotifyFlags(action uint32) uint64 {
+func (w *readDirChangesW) toFSnotifyFlags(action uint32) uint64 {
 	switch action {
 	case windows.FILE_ACTION_ADDED:
 		return sysFSCREATE
@@ -825,3 +672,11 @@ func (w *Watcher) toFSnotifyFlags(action uint32) uint64 {
 	}
 	return 0
 }
+
+func (w *readDirChangesW) xSupports(op Op) bool {
+	if op.Has(xUnportableOpen) || op.Has(xUnportableRead) ||
+		op.Has(xUnportableCloseWrite) || op.Has(xUnportableCloseRead) {
+		return false
+	}
+	return true
+}
diff --git a/vendor/github.com/fsnotify/fsnotify/fsnotify.go b/vendor/github.com/fsnotify/fsnotify/fsnotify.go
index 24c99cc49..0760efe91 100644
--- a/vendor/github.com/fsnotify/fsnotify/fsnotify.go
+++ b/vendor/github.com/fsnotify/fsnotify/fsnotify.go
@@ -3,19 +3,146 @@
 //
 // Currently supported systems:
 //
-//	Linux 2.6.32+    via inotify
-//	BSD, macOS       via kqueue
-//	Windows          via ReadDirectoryChangesW
-//	illumos          via FEN
+//   - Linux      via inotify
+//   - BSD, macOS via kqueue
+//   - Windows    via ReadDirectoryChangesW
+//   - illumos    via FEN
+//
+// # FSNOTIFY_DEBUG
+//
+// Set the FSNOTIFY_DEBUG environment variable to "1" to print debug messages to
+// stderr. This can be useful to track down some problems, especially in cases
+// where fsnotify is used as an indirect dependency.
+//
+// Every event will be printed as soon as there's something useful to print,
+// with as little processing from fsnotify.
+//
+// Example output:
+//
+//	FSNOTIFY_DEBUG: 11:34:23.633087586   256:IN_CREATE            → "/tmp/file-1"
+//	FSNOTIFY_DEBUG: 11:34:23.633202319     4:IN_ATTRIB            → "/tmp/file-1"
+//	FSNOTIFY_DEBUG: 11:34:28.989728764   512:IN_DELETE            → "/tmp/file-1"
 package fsnotify
 
 import (
 	"errors"
 	"fmt"
+	"os"
 	"path/filepath"
 	"strings"
 )
 
+// Watcher watches a set of paths, delivering events on a channel.
+//
+// A watcher should not be copied (e.g. pass it by pointer, rather than by
+// value).
+//
+// # Linux notes
+//
+// When a file is removed a Remove event won't be emitted until all file
+// descriptors are closed, and deletes will always emit a Chmod. For example:
+//
+//	fp := os.Open("file")
+//	os.Remove("file")        // Triggers Chmod
+//	fp.Close()               // Triggers Remove
+//
+// This is the event that inotify sends, so not much can be changed about this.
+//
+// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
+// for the number of watches per user, and fs.inotify.max_user_instances
+// specifies the maximum number of inotify instances per user. Every Watcher you
+// create is an "instance", and every path you add is a "watch".
+//
+// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
+// /proc/sys/fs/inotify/max_user_instances
+//
+// To increase them you can use sysctl or write the value to the /proc file:
+//
+//	# Default values on Linux 5.18
+//	sysctl fs.inotify.max_user_watches=124983
+//	sysctl fs.inotify.max_user_instances=128
+//
+// To make the changes persist on reboot edit /etc/sysctl.conf or
+// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
+// your distro's documentation):
+//
+//	fs.inotify.max_user_watches=124983
+//	fs.inotify.max_user_instances=128
+//
+// Reaching the limit will result in a "no space left on device" or "too many open
+// files" error.
+//
+// # kqueue notes (macOS, BSD)
+//
+// kqueue requires opening a file descriptor for every file that's being watched;
+// so if you're watching a directory with five files then that's six file
+// descriptors. You will run in to your system's "max open files" limit faster on
+// these platforms.
+//
+// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
+// control the maximum number of open files, as well as /etc/login.conf on BSD
+// systems.
+//
+// # Windows notes
+//
+// Paths can be added as "C:\\path\\to\\dir", but forward slashes
+// ("C:/path/to/dir") will also work.
+//
+// When a watched directory is removed it will always send an event for the
+// directory itself, but may not send events for all files in that directory.
+// Sometimes it will send events for all files, sometimes it will send no
+// events, and often only for some files.
+//
+// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
+// value that is guaranteed to work with SMB filesystems. If you have many
+// events in quick succession this may not be enough, and you will have to use
+// [WithBufferSize] to increase the value.
+type Watcher struct {
+	b backend
+
+	// Events sends the filesystem change events.
+	//
+	// fsnotify can send the following events; a "path" here can refer to a
+	// file, directory, symbolic link, or special file like a FIFO.
+	//
+	//   fsnotify.Create    A new path was created; this may be followed by one
+	//                      or more Write events if data also gets written to a
+	//                      file.
+	//
+	//   fsnotify.Remove    A path was removed.
+	//
+	//   fsnotify.Rename    A path was renamed. A rename is always sent with the
+	//                      old path as Event.Name, and a Create event will be
+	//                      sent with the new name. Renames are only sent for
+	//                      paths that are currently watched; e.g. moving an
+	//                      unmonitored file into a monitored directory will
+	//                      show up as just a Create. Similarly, renaming a file
+	//                      to outside a monitored directory will show up as
+	//                      only a Rename.
+	//
+	//   fsnotify.Write     A file or named pipe was written to. A Truncate will
+	//                      also trigger a Write. A single "write action"
+	//                      initiated by the user may show up as one or multiple
+	//                      writes, depending on when the system syncs things to
+	//                      disk. For example when compiling a large Go program
+	//                      you may get hundreds of Write events, and you may
+	//                      want to wait until you've stopped receiving them
+	//                      (see the dedup example in cmd/fsnotify).
+	//
+	//                      Some systems may send Write event for directories
+	//                      when the directory content changes.
+	//
+	//   fsnotify.Chmod     Attributes were changed. On Linux this is also sent
+	//                      when a file is removed (or more accurately, when a
+	//                      link to an inode is removed). On kqueue it's sent
+	//                      when a file is truncated. On Windows it's never
+	//                      sent.
+	Events chan Event
+
+	// Errors sends any errors.
+	Errors chan error
+}
+
 // Event represents a file system notification.
 type Event struct {
 	// Path to the file or directory.
@@ -30,6 +157,16 @@ type Event struct {
 	// This is a bitmask and some systems may send multiple operations at once.
 	// Use the Event.Has() method instead of comparing with ==.
 	Op Op
+
+	// Create events will have this set to the old path if it's a rename. This
+	// only works when both the source and destination are watched. It's not
+	// reliable when watching individual files, only directories.
+	//
+	// For example "mv /tmp/file /tmp/rename" will emit:
+	//
+	//   Event{Op: Rename, Name: "/tmp/file"}
+	//   Event{Op: Create, Name: "/tmp/rename", RenamedFrom: "/tmp/file"}
+	renamedFrom string
 }
 
 // Op describes a set of file operations.
@@ -50,7 +187,7 @@ const (
 	// example "remove to trash" is often a rename).
 	Remove
 
-	// The path was renamed to something else; any watched on it will be
+	// The path was renamed to something else; any watches on it will be
 	// removed.
 	Rename
 
@@ -60,15 +197,155 @@ const (
 	// get triggered very frequently by some software. For example, Spotlight
 	// indexing on macOS, anti-virus software, backup software, etc.
 	Chmod
+
+	// File descriptor was opened.
+	//
+	// Only works on Linux and FreeBSD.
+	xUnportableOpen
+
+	// File was read from.
+	//
+	// Only works on Linux and FreeBSD.
+	xUnportableRead
+
+	// File opened for writing was closed.
+	//
+	// Only works on Linux and FreeBSD.
+	//
+	// The advantage of using this over Write is that it's more reliable than
+	// waiting for Write events to stop. It's also faster (if you're not
+	// listening to Write events): copying a file of a few GB can easily
+	// generate tens of thousands of Write events in a short span of time.
+	xUnportableCloseWrite
+
+	// File opened for reading was closed.
+	//
+	// Only works on Linux and FreeBSD.
+	xUnportableCloseRead
 )
 
-// Common errors that can be reported.
 var (
+	// ErrNonExistentWatch is used when Remove() is called on a path that's not
+	// added.
 	ErrNonExistentWatch = errors.New("fsnotify: can't remove non-existent watch")
-	ErrEventOverflow    = errors.New("fsnotify: queue or buffer overflow")
-	ErrClosed           = errors.New("fsnotify: watcher already closed")
+
+	// ErrClosed is used when trying to operate on a closed Watcher.
+	ErrClosed = errors.New("fsnotify: watcher already closed")
+
+	// ErrEventOverflow is reported from the Errors channel when there are too
+	// many events:
+	//
+	//  - inotify:      inotify returns IN_Q_OVERFLOW – because there are too
+	//                  many queued events (the fs.inotify.max_queued_events
+	//                  sysctl can be used to increase this).
+	//  - windows:      The buffer size is too small; WithBufferSize() can be used to increase it.
+	//  - kqueue, fen:  Not used.
+	ErrEventOverflow = errors.New("fsnotify: queue or buffer overflow")
+
+	// ErrUnsupported is returned by AddWith() when WithOps() specified an
+	// Unportable event that's not supported on this platform.
+	xErrUnsupported = errors.New("fsnotify: not supported with this backend")
 )
 
+// NewWatcher creates a new Watcher.
+func NewWatcher() (*Watcher, error) {
+	ev, errs := make(chan Event), make(chan error)
+	b, err := newBackend(ev, errs)
+	if err != nil {
+		return nil, err
+	}
+	return &Watcher{b: b, Events: ev, Errors: errs}, nil
+}
+
+// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
+// channel.
+//
+// The main use case for this is situations with a very large number of events
+// where the kernel buffer size can't be increased (e.g. due to lack of
+// permissions). An unbuffered Watcher will perform better for almost all use
+// cases, and whenever possible you will be better off increasing the kernel
+// buffers instead of adding a large userspace buffer.
+func NewBufferedWatcher(sz uint) (*Watcher, error) {
+	ev, errs := make(chan Event), make(chan error)
+	b, err := newBufferedBackend(sz, ev, errs)
+	if err != nil {
+		return nil, err
+	}
+	return &Watcher{b: b, Events: ev, Errors: errs}, nil
+}
+
+// Add starts monitoring the path for changes.
+//
+// A path can only be watched once; watching it more than once is a no-op and will
+// not return an error. Paths that do not yet exist on the filesystem cannot be
+// watched.
+//
+// A watch will be automatically removed if the watched path is deleted or
+// renamed. The exception is the Windows backend, which doesn't remove the
+// watcher on renames.
+//
+// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
+// filesystems (/proc, /sys, etc.) generally don't work.
+//
+// Returns [ErrClosed] if [Watcher.Close] was called.
+//
+// See [Watcher.AddWith] for a version that allows adding options.
+//
+// # Watching directories
+//
+// All files in a directory are monitored, including new files that are created
+// after the watcher is started. Subdirectories are not watched (i.e. it's
+// non-recursive).
+//
+// # Watching files
+//
+// Watching individual files (rather than directories) is generally not
+// recommended as many programs (especially editors) update files atomically: it
+// will write to a temporary file which is then moved to destination,
+// overwriting the original (or some variant thereof). The watcher on the
+// original file is now lost, as that no longer exists.
+//
+// The upshot of this is that a power failure or crash won't leave a
+// half-written file.
+//
+// Watch the parent directory and use Event.Name to filter out files you're not
+// interested in. There is an example of this in cmd/fsnotify/file.go.
+func (w *Watcher) Add(path string) error { return w.b.Add(path) }
+
+// AddWith is like [Watcher.Add], but allows adding options. When using Add()
+// the defaults described below are used.
+//
+// Possible options are:
+//
+//   - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
+//     other platforms. The default is 64K (65536 bytes).
+func (w *Watcher) AddWith(path string, opts ...addOpt) error { return w.b.AddWith(path, opts...) }
+
+// Remove stops monitoring the path for changes.
+//
+// Directories are always removed non-recursively. For example, if you added
+// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
+//
+// Removing a path that has not yet been added returns [ErrNonExistentWatch].
+//
+// Returns nil if [Watcher.Close] was called.
+func (w *Watcher) Remove(path string) error { return w.b.Remove(path) }
+
+// Close removes all watches and closes the Events channel.
+func (w *Watcher) Close() error { return w.b.Close() }
+
+// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
+// yet removed).
+//
+// Returns nil if [Watcher.Close] was called.
+func (w *Watcher) WatchList() []string { return w.b.WatchList() }
+
+// Supports reports if all the listed operations are supported by this platform.
+//
+// Create, Write, Remove, Rename, and Chmod are always supported. It can only
+// return false for an Op starting with Unportable.
+func (w *Watcher) xSupports(op Op) bool { return w.b.xSupports(op) }
+
 func (o Op) String() string {
 	var b strings.Builder
 	if o.Has(Create) {
@@ -80,6 +357,18 @@ func (o Op) String() string {
 	if o.Has(Write) {
 		b.WriteString("|WRITE")
 	}
+	if o.Has(xUnportableOpen) {
+		b.WriteString("|OPEN")
+	}
+	if o.Has(xUnportableRead) {
+		b.WriteString("|READ")
+	}
+	if o.Has(xUnportableCloseWrite) {
+		b.WriteString("|CLOSE_WRITE")
+	}
+	if o.Has(xUnportableCloseRead) {
+		b.WriteString("|CLOSE_READ")
+	}
 	if o.Has(Rename) {
 		b.WriteString("|RENAME")
 	}
@@ -100,24 +389,48 @@ func (e Event) Has(op Op) bool { return e.Op.Has(op) }
 
 // String returns a string representation of the event with their path.
 func (e Event) String() string {
+	if e.renamedFrom != "" {
+		return fmt.Sprintf("%-13s %q ← %q", e.Op.String(), e.Name, e.renamedFrom)
+	}
 	return fmt.Sprintf("%-13s %q", e.Op.String(), e.Name)
 }
 
 type (
+	backend interface {
+		Add(string) error
+		AddWith(string, ...addOpt) error
+		Remove(string) error
+		WatchList() []string
+		Close() error
+		xSupports(Op) bool
+	}
 	addOpt   func(opt *withOpts)
 	withOpts struct {
-		bufsize int
+		bufsize    int
+		op         Op
+		noFollow   bool
+		sendCreate bool
 	}
 )
 
+var debug = func() bool {
+	// Check for exactly "1" (rather than mere existence) so we can add
+	// options/flags in the future. I don't know if we ever want that, but it's
+	// nice to leave the option open.
+	return os.Getenv("FSNOTIFY_DEBUG") == "1"
+}()
+
 var defaultOpts = withOpts{
 	bufsize: 65536, // 64K
+	op:      Create | Write | Remove | Rename | Chmod,
 }
 
 func getOptions(opts ...addOpt) withOpts {
 	with := defaultOpts
 	for _, o := range opts {
-		o(&with)
+		if o != nil {
+			o(&with)
+		}
 	}
 	return with
 }
@@ -136,9 +449,44 @@ func WithBufferSize(bytes int) addOpt {
 	return func(opt *withOpts) { opt.bufsize = bytes }
 }
 
+// WithOps sets which operations to listen for. The default is [Create],
+// [Write], [Remove], [Rename], and [Chmod].
+//
+// Excluding operations you're not interested in can save quite a bit of CPU
+// time; in some use cases there may be hundreds of thousands of useless Write
+// or Chmod operations per second.
+//
+// This can also be used to add unportable operations not supported by all
+// platforms; unportable operations all start with "Unportable":
+// [UnportableOpen], [UnportableRead], [UnportableCloseWrite], and
+// [UnportableCloseRead].
+//
+// AddWith returns an error when using an unportable operation that's not
+// supported. Use [Watcher.Support] to check for support.
+func withOps(op Op) addOpt {
+	return func(opt *withOpts) { opt.op = op }
+}
+
+// WithNoFollow disables following symlinks, so the symlinks themselves are
+// watched.
+func withNoFollow() addOpt {
+	return func(opt *withOpts) { opt.noFollow = true }
+}
+
+// "Internal" option for recursive watches on inotify.
+func withCreate() addOpt {
+	return func(opt *withOpts) { opt.sendCreate = true }
+}
+
+var enableRecurse = false
+
 // Check if this path is recursive (ends with "/..." or "\..."), and return the
 // path with the /... stripped.
 func recursivePath(path string) (string, bool) {
+	path = filepath.Clean(path)
+	if !enableRecurse { // Only enabled in tests for now.
+		return path, false
+	}
 	if filepath.Base(path) == "..." {
 		return filepath.Dir(path), true
 	}
diff --git a/vendor/github.com/fsnotify/fsnotify/internal/darwin.go b/vendor/github.com/fsnotify/fsnotify/internal/darwin.go
new file mode 100644
index 000000000..b0eab1009
--- /dev/null
+++ b/vendor/github.com/fsnotify/fsnotify/internal/darwin.go
@@ -0,0 +1,39 @@
+//go:build darwin
+
+package internal
+
+import (
+	"syscall"
+
+	"golang.org/x/sys/unix"
+)
+
+var (
+	SyscallEACCES = syscall.EACCES
+	UnixEACCES    = unix.EACCES
+)
+
+var maxfiles uint64
+
+// Go 1.19 will do this automatically: https://go-review.googlesource.com/c/go/+/393354/
+func SetRlimit() {
+	var l syscall.Rlimit
+	err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &l)
+	if err == nil && l.Cur != l.Max {
+		l.Cur = l.Max
+		syscall.Setrlimit(syscall.RLIMIT_NOFILE, &l)
+	}
+	maxfiles = l.Cur
+
+	if n, err := syscall.SysctlUint32("kern.maxfiles"); err == nil && uint64(n) < maxfiles {
+		maxfiles = uint64(n)
+	}
+
+	if n, err := syscall.SysctlUint32("kern.maxfilesperproc"); err == nil && uint64(n) < maxfiles {
+		maxfiles = uint64(n)
+	}
+}
+
+func Maxfiles() uint64                              { return maxfiles }
+func Mkfifo(path string, mode uint32) error         { return unix.Mkfifo(path, mode) }
+func Mknod(path string, mode uint32, dev int) error { return unix.Mknod(path, mode, dev) }
diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_darwin.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_darwin.go
new file mode 100644
index 000000000..928319fb0
--- /dev/null
+++ b/vendor/github.com/fsnotify/fsnotify/internal/debug_darwin.go
@@ -0,0 +1,57 @@
+package internal
+
+import "golang.org/x/sys/unix"
+
+var names = []struct {
+	n string
+	m uint32
+}{
+	{"NOTE_ABSOLUTE", unix.NOTE_ABSOLUTE},
+	{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
+	{"NOTE_BACKGROUND", unix.NOTE_BACKGROUND},
+	{"NOTE_CHILD", unix.NOTE_CHILD},
+	{"NOTE_CRITICAL", unix.NOTE_CRITICAL},
+	{"NOTE_DELETE", unix.NOTE_DELETE},
+	{"NOTE_EXEC", unix.NOTE_EXEC},
+	{"NOTE_EXIT", unix.NOTE_EXIT},
+	{"NOTE_EXITSTATUS", unix.NOTE_EXITSTATUS},
+	{"NOTE_EXIT_CSERROR", unix.NOTE_EXIT_CSERROR},
+	{"NOTE_EXIT_DECRYPTFAIL", unix.NOTE_EXIT_DECRYPTFAIL},
+	{"NOTE_EXIT_DETAIL", unix.NOTE_EXIT_DETAIL},
+	{"NOTE_EXIT_DETAIL_MASK", unix.NOTE_EXIT_DETAIL_MASK},
+	{"NOTE_EXIT_MEMORY", unix.NOTE_EXIT_MEMORY},
+	{"NOTE_EXIT_REPARENTED", unix.NOTE_EXIT_REPARENTED},
+	{"NOTE_EXTEND", unix.NOTE_EXTEND},
+	{"NOTE_FFAND", unix.NOTE_FFAND},
+	{"NOTE_FFCOPY", unix.NOTE_FFCOPY},
+	{"NOTE_FFCTRLMASK", unix.NOTE_FFCTRLMASK},
+	{"NOTE_FFLAGSMASK", unix.NOTE_FFLAGSMASK},
+	{"NOTE_FFNOP", unix.NOTE_FFNOP},
+	{"NOTE_FFOR", unix.NOTE_FFOR},
+	{"NOTE_FORK", unix.NOTE_FORK},
+	{"NOTE_FUNLOCK", unix.NOTE_FUNLOCK},
+	{"NOTE_LEEWAY", unix.NOTE_LEEWAY},
+	{"NOTE_LINK", unix.NOTE_LINK},
+	{"NOTE_LOWAT", unix.NOTE_LOWAT},
+	{"NOTE_MACHTIME", unix.NOTE_MACHTIME},
+	{"NOTE_MACH_CONTINUOUS_TIME", unix.NOTE_MACH_CONTINUOUS_TIME},
+	{"NOTE_NONE", unix.NOTE_NONE},
+	{"NOTE_NSECONDS", unix.NOTE_NSECONDS},
+	{"NOTE_OOB", unix.NOTE_OOB},
+	//{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK}, -0x100000 (?!)
+	{"NOTE_PDATAMASK", unix.NOTE_PDATAMASK},
+	{"NOTE_REAP", unix.NOTE_REAP},
+	{"NOTE_RENAME", unix.NOTE_RENAME},
+	{"NOTE_REVOKE", unix.NOTE_REVOKE},
+	{"NOTE_SECONDS", unix.NOTE_SECONDS},
+	{"NOTE_SIGNAL", unix.NOTE_SIGNAL},
+	{"NOTE_TRACK", unix.NOTE_TRACK},
+	{"NOTE_TRACKERR", unix.NOTE_TRACKERR},
+	{"NOTE_TRIGGER", unix.NOTE_TRIGGER},
+	{"NOTE_USECONDS", unix.NOTE_USECONDS},
+	{"NOTE_VM_ERROR", unix.NOTE_VM_ERROR},
+	{"NOTE_VM_PRESSURE", unix.NOTE_VM_PRESSURE},
+	{"NOTE_VM_PRESSURE_SUDDEN_TERMINATE", unix.NOTE_VM_PRESSURE_SUDDEN_TERMINATE},
+	{"NOTE_VM_PRESSURE_TERMINATE", unix.NOTE_VM_PRESSURE_TERMINATE},
+	{"NOTE_WRITE", unix.NOTE_WRITE},
+}
diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_dragonfly.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_dragonfly.go
new file mode 100644
index 000000000..3186b0c34
--- /dev/null
+++ b/vendor/github.com/fsnotify/fsnotify/internal/debug_dragonfly.go
@@ -0,0 +1,33 @@
+package internal
+
+import "golang.org/x/sys/unix"
+
+var names = []struct {
+	n string
+	m uint32
+}{
+	{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
+	{"NOTE_CHILD", unix.NOTE_CHILD},
+	{"NOTE_DELETE", unix.NOTE_DELETE},
+	{"NOTE_EXEC", unix.NOTE_EXEC},
+	{"NOTE_EXIT", unix.NOTE_EXIT},
+	{"NOTE_EXTEND", unix.NOTE_EXTEND},
+	{"NOTE_FFAND", unix.NOTE_FFAND},
+	{"NOTE_FFCOPY", unix.NOTE_FFCOPY},
+	{"NOTE_FFCTRLMASK", unix.NOTE_FFCTRLMASK},
+	{"NOTE_FFLAGSMASK", unix.NOTE_FFLAGSMASK},
+	{"NOTE_FFNOP", unix.NOTE_FFNOP},
+	{"NOTE_FFOR", unix.NOTE_FFOR},
+	{"NOTE_FORK", unix.NOTE_FORK},
+	{"NOTE_LINK", unix.NOTE_LINK},
+	{"NOTE_LOWAT", unix.NOTE_LOWAT},
+	{"NOTE_OOB", unix.NOTE_OOB},
+	{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK},
+	{"NOTE_PDATAMASK", unix.NOTE_PDATAMASK},
+	{"NOTE_RENAME", unix.NOTE_RENAME},
+	{"NOTE_REVOKE", unix.NOTE_REVOKE},
+	{"NOTE_TRACK", unix.NOTE_TRACK},
+	{"NOTE_TRACKERR", unix.NOTE_TRACKERR},
+	{"NOTE_TRIGGER", unix.NOTE_TRIGGER},
+	{"NOTE_WRITE", unix.NOTE_WRITE},
+}
diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_freebsd.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_freebsd.go
new file mode 100644
index 000000000..f69fdb930
--- /dev/null
+++ b/vendor/github.com/fsnotify/fsnotify/internal/debug_freebsd.go
@@ -0,0 +1,42 @@
+package internal
+
+import "golang.org/x/sys/unix"
+
+var names = []struct {
+	n string
+	m uint32
+}{
+	{"NOTE_ABSTIME", unix.NOTE_ABSTIME},
+	{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
+	{"NOTE_CHILD", unix.NOTE_CHILD},
+	{"NOTE_CLOSE", unix.NOTE_CLOSE},
+	{"NOTE_CLOSE_WRITE", unix.NOTE_CLOSE_WRITE},
+	{"NOTE_DELETE", unix.NOTE_DELETE},
+	{"NOTE_EXEC", unix.NOTE_EXEC},
+	{"NOTE_EXIT", unix.NOTE_EXIT},
+	{"NOTE_EXTEND", unix.NOTE_EXTEND},
+	{"NOTE_FFAND", unix.NOTE_FFAND},
+	{"NOTE_FFCOPY", unix.NOTE_FFCOPY},
+	{"NOTE_FFCTRLMASK", unix.NOTE_FFCTRLMASK},
+	{"NOTE_FFLAGSMASK", unix.NOTE_FFLAGSMASK},
+	{"NOTE_FFNOP", unix.NOTE_FFNOP},
+	{"NOTE_FFOR", unix.NOTE_FFOR},
+	{"NOTE_FILE_POLL", unix.NOTE_FILE_POLL},
+	{"NOTE_FORK", unix.NOTE_FORK},
+	{"NOTE_LINK", unix.NOTE_LINK},
+	{"NOTE_LOWAT", unix.NOTE_LOWAT},
+	{"NOTE_MSECONDS", unix.NOTE_MSECONDS},
+	{"NOTE_NSECONDS", unix.NOTE_NSECONDS},
+	{"NOTE_OPEN", unix.NOTE_OPEN},
+	{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK},
+	{"NOTE_PDATAMASK", unix.NOTE_PDATAMASK},
+	{"NOTE_READ", unix.NOTE_READ},
+	{"NOTE_RENAME", unix.NOTE_RENAME},
+	{"NOTE_REVOKE", unix.NOTE_REVOKE},
+	{"NOTE_SECONDS", unix.NOTE_SECONDS},
+	{"NOTE_TRACK", unix.NOTE_TRACK},
+	{"NOTE_TRACKERR", unix.NOTE_TRACKERR},
+	{"NOTE_TRIGGER", unix.NOTE_TRIGGER},
+	{"NOTE_USECONDS", unix.NOTE_USECONDS},
+	{"NOTE_WRITE", unix.NOTE_WRITE},
+}
diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_kqueue.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_kqueue.go
new file mode 100644
index 000000000..607e683bd
--- /dev/null
+++ b/vendor/github.com/fsnotify/fsnotify/internal/debug_kqueue.go
@@ -0,0 +1,32 @@
+//go:build freebsd || openbsd || netbsd || dragonfly || darwin
+
+package internal
+
+import (
+	"fmt"
+	"os"
+	"strings"
+	"time"
+
+	"golang.org/x/sys/unix"
+)
+
+func Debug(name string, kevent *unix.Kevent_t) {
+	mask := uint32(kevent.Fflags)
+
+	var (
+		l       []string
+		unknown = mask
+	)
+	for _, n := range names {
+		if mask&n.m == n.m {
+			l = append(l, n.n)
+			unknown ^= n.m
+		}
+	}
+	if unknown > 0 {
+		l = append(l, fmt.Sprintf("0x%x", unknown))
+	}
+	fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s  %10d:%-60s → %q\n",
+		time.Now().Format("15:04:05.000000000"), mask, strings.Join(l, " | "), name)
+}
diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_linux.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_linux.go
new file mode 100644
index 000000000..35c734be4
--- /dev/null
+++ b/vendor/github.com/fsnotify/fsnotify/internal/debug_linux.go
@@ -0,0 +1,56 @@
+package internal
+
+import (
+	"fmt"
+	"os"
+	"strings"
+	"time"
+
+	"golang.org/x/sys/unix"
+)
+
+func Debug(name string, mask, cookie uint32) {
+	names := []struct {
+		n string
+		m uint32
+	}{
+		{"IN_ACCESS", unix.IN_ACCESS},
+		{"IN_ATTRIB", unix.IN_ATTRIB},
+		{"IN_CLOSE", unix.IN_CLOSE},
+		{"IN_CLOSE_NOWRITE", unix.IN_CLOSE_NOWRITE},
+		{"IN_CLOSE_WRITE", unix.IN_CLOSE_WRITE},
+		{"IN_CREATE", unix.IN_CREATE},
+		{"IN_DELETE", unix.IN_DELETE},
+		{"IN_DELETE_SELF", unix.IN_DELETE_SELF},
+		{"IN_IGNORED", unix.IN_IGNORED},
+		{"IN_ISDIR", unix.IN_ISDIR},
+		{"IN_MODIFY", unix.IN_MODIFY},
+		{"IN_MOVE", unix.IN_MOVE},
+		{"IN_MOVED_FROM", unix.IN_MOVED_FROM},
+		{"IN_MOVED_TO", unix.IN_MOVED_TO},
+		{"IN_MOVE_SELF", unix.IN_MOVE_SELF},
+		{"IN_OPEN", unix.IN_OPEN},
+		{"IN_Q_OVERFLOW", unix.IN_Q_OVERFLOW},
+		{"IN_UNMOUNT", unix.IN_UNMOUNT},
+	}
+
+	var (
+		l       []string
+		unknown = mask
+	)
+	for _, n := range names {
+		if mask&n.m == n.m {
+			l = append(l, n.n)
+			unknown ^= n.m
+		}
+	}
+	if unknown > 0 {
+		l = append(l, fmt.Sprintf("0x%x", unknown))
+	}
+	var c string
+	if cookie > 0 {
+		c = fmt.Sprintf("(cookie: %d) ", cookie)
+	}
+	fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s  %-30s → %s%q\n",
+		time.Now().Format("15:04:05.000000000"), strings.Join(l, "|"), c, name)
+}
diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_netbsd.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_netbsd.go
new file mode 100644
index 000000000..e5b3b6f69
--- /dev/null
+++ b/vendor/github.com/fsnotify/fsnotify/internal/debug_netbsd.go
@@ -0,0 +1,25 @@
+package internal
+
+import "golang.org/x/sys/unix"
+
+var names = []struct {
+	n string
+	m uint32
+}{
+	{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
+	{"NOTE_CHILD", unix.NOTE_CHILD},
+	{"NOTE_DELETE", unix.NOTE_DELETE},
+	{"NOTE_EXEC", unix.NOTE_EXEC},
+	{"NOTE_EXIT", unix.NOTE_EXIT},
+	{"NOTE_EXTEND", unix.NOTE_EXTEND},
+	{"NOTE_FORK", unix.NOTE_FORK},
+	{"NOTE_LINK", unix.NOTE_LINK},
+	{"NOTE_LOWAT", unix.NOTE_LOWAT},
+	{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK},
+	{"NOTE_PDATAMASK", unix.NOTE_PDATAMASK},
+	{"NOTE_RENAME", unix.NOTE_RENAME},
+	{"NOTE_REVOKE", unix.NOTE_REVOKE},
+	{"NOTE_TRACK", unix.NOTE_TRACK},
+	{"NOTE_TRACKERR", unix.NOTE_TRACKERR},
+	{"NOTE_WRITE", unix.NOTE_WRITE},
+}
diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_openbsd.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_openbsd.go
new file mode 100644
index 000000000..1dd455bc5
--- /dev/null
+++ b/vendor/github.com/fsnotify/fsnotify/internal/debug_openbsd.go
@@ -0,0 +1,28 @@
+package internal
+
+import "golang.org/x/sys/unix"
+
+var names = []struct {
+	n string
+	m uint32
+}{
+	{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
+	// {"NOTE_CHANGE", unix.NOTE_CHANGE}, // Not on 386?
+	{"NOTE_CHILD", unix.NOTE_CHILD},
+	{"NOTE_DELETE", unix.NOTE_DELETE},
+	{"NOTE_EOF", unix.NOTE_EOF},
+	{"NOTE_EXEC", unix.NOTE_EXEC},
+	{"NOTE_EXIT", unix.NOTE_EXIT},
+	{"NOTE_EXTEND", unix.NOTE_EXTEND},
+	{"NOTE_FORK", unix.NOTE_FORK},
+	{"NOTE_LINK", unix.NOTE_LINK},
+	{"NOTE_LOWAT", unix.NOTE_LOWAT},
+	{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK},
+	{"NOTE_PDATAMASK", unix.NOTE_PDATAMASK},
+	{"NOTE_RENAME", unix.NOTE_RENAME},
+	{"NOTE_REVOKE", unix.NOTE_REVOKE},
+	{"NOTE_TRACK", unix.NOTE_TRACK},
+	{"NOTE_TRACKERR", unix.NOTE_TRACKERR},
+	{"NOTE_TRUNCATE", unix.NOTE_TRUNCATE},
+	{"NOTE_WRITE", unix.NOTE_WRITE},
+}
diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_solaris.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_solaris.go
new file mode 100644
index 000000000..f1b2e73bd
--- /dev/null
+++ b/vendor/github.com/fsnotify/fsnotify/internal/debug_solaris.go
@@ -0,0 +1,45 @@
+package internal
+
+import (
+	"fmt"
+	"os"
+	"strings"
+	"time"
+
+	"golang.org/x/sys/unix"
+)
+
+func Debug(name string, mask int32) {
+	names := []struct {
+		n string
+		m int32
+	}{
+		{"FILE_ACCESS", unix.FILE_ACCESS},
+		{"FILE_MODIFIED", unix.FILE_MODIFIED},
+		{"FILE_ATTRIB", unix.FILE_ATTRIB},
+		{"FILE_TRUNC", unix.FILE_TRUNC},
+		{"FILE_NOFOLLOW", unix.FILE_NOFOLLOW},
+		{"FILE_DELETE", unix.FILE_DELETE},
+		{"FILE_RENAME_TO", unix.FILE_RENAME_TO},
+		{"FILE_RENAME_FROM", unix.FILE_RENAME_FROM},
+		{"UNMOUNTED", unix.UNMOUNTED},
+		{"MOUNTEDOVER", unix.MOUNTEDOVER},
+		{"FILE_EXCEPTION", unix.FILE_EXCEPTION},
+	}
+
+	var (
+		l       []string
+		unknown = mask
+	)
+	for _, n := range names {
+		if mask&n.m == n.m {
+			l = append(l, n.n)
+			unknown ^= n.m
+		}
+	}
+	if unknown > 0 {
+		l = append(l, fmt.Sprintf("0x%x", unknown))
+	}
+	fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s  %10d:%-30s → %q\n",
+		time.Now().Format("15:04:05.000000000"), mask, strings.Join(l, " | "), name)
+}
diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_windows.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_windows.go
new file mode 100644
index 000000000..52bf4ce53
--- /dev/null
+++ b/vendor/github.com/fsnotify/fsnotify/internal/debug_windows.go
@@ -0,0 +1,40 @@
+package internal
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+
+	"golang.org/x/sys/windows"
+)
+
+func Debug(name string, mask uint32) {
+	names := []struct {
+		n string
+		m uint32
+	}{
+		{"FILE_ACTION_ADDED", windows.FILE_ACTION_ADDED},
+		{"FILE_ACTION_REMOVED", windows.FILE_ACTION_REMOVED},
+		{"FILE_ACTION_MODIFIED", windows.FILE_ACTION_MODIFIED},
+		{"FILE_ACTION_RENAMED_OLD_NAME", windows.FILE_ACTION_RENAMED_OLD_NAME},
+		{"FILE_ACTION_RENAMED_NEW_NAME", windows.FILE_ACTION_RENAMED_NEW_NAME},
+	}
+
+	var (
+		l       []string
+		unknown = mask
+	)
+	for _, n := range names {
+		if mask&n.m == n.m {
+			l = append(l, n.n)
+			unknown ^= n.m
+		}
+	}
+	if unknown > 0 {
+		l = append(l, fmt.Sprintf("0x%x", unknown))
+	}
+	fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s  %-65s → %q\n",
+		time.Now().Format("15:04:05.000000000"), strings.Join(l, " | "), filepath.ToSlash(name))
+}
diff --git a/vendor/github.com/fsnotify/fsnotify/internal/freebsd.go b/vendor/github.com/fsnotify/fsnotify/internal/freebsd.go
new file mode 100644
index 000000000..547df1df8
--- /dev/null
+++ b/vendor/github.com/fsnotify/fsnotify/internal/freebsd.go
@@ -0,0 +1,31 @@
+//go:build freebsd
+
+package internal
+
+import (
+	"syscall"
+
+	"golang.org/x/sys/unix"
+)
+
+var (
+	SyscallEACCES = syscall.EACCES
+	UnixEACCES    = unix.EACCES
+)
+
+var maxfiles uint64
+
+func SetRlimit() {
+	// Go 1.19 will do this automatically: https://go-review.googlesource.com/c/go/+/393354/
+	var l syscall.Rlimit
+	err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &l)
+	if err == nil && l.Cur != l.Max {
+		l.Cur = l.Max
+		syscall.Setrlimit(syscall.RLIMIT_NOFILE, &l)
+	}
+	maxfiles = uint64(l.Cur)
+}
+
+func Maxfiles() uint64                              { return maxfiles }
+func Mkfifo(path string, mode uint32) error         { return unix.Mkfifo(path, mode) }
+func Mknod(path string, mode uint32, dev int) error { return unix.Mknod(path, mode, uint64(dev)) }
diff --git a/vendor/github.com/fsnotify/fsnotify/internal/internal.go b/vendor/github.com/fsnotify/fsnotify/internal/internal.go
new file mode 100644
index 000000000..7daa45e19
--- /dev/null
+++ b/vendor/github.com/fsnotify/fsnotify/internal/internal.go
@@ -0,0 +1,2 @@
+// Package internal contains some helpers.
+package internal
diff --git a/vendor/github.com/fsnotify/fsnotify/internal/unix.go b/vendor/github.com/fsnotify/fsnotify/internal/unix.go
new file mode 100644
index 000000000..30976ce97
--- /dev/null
+++ b/vendor/github.com/fsnotify/fsnotify/internal/unix.go
@@ -0,0 +1,31 @@
+//go:build !windows && !darwin && !freebsd
+
+package internal
+
+import (
+	"syscall"
+
+	"golang.org/x/sys/unix"
+)
+
+var (
+	SyscallEACCES = syscall.EACCES
+	UnixEACCES    = unix.EACCES
+)
+
+var maxfiles uint64
+
+func SetRlimit() {
+	// Go 1.19 will do this automatically: https://go-review.googlesource.com/c/go/+/393354/
+	var l syscall.Rlimit
+	err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &l)
+	if err == nil && l.Cur != l.Max {
+		l.Cur = l.Max
+		syscall.Setrlimit(syscall.RLIMIT_NOFILE, &l)
+	}
+	maxfiles = uint64(l.Cur)
+}
+
+func Maxfiles() uint64                              { return maxfiles }
+func Mkfifo(path string, mode uint32) error         { return unix.Mkfifo(path, mode) }
+func Mknod(path string, mode uint32, dev int) error { return unix.Mknod(path, mode, dev) }
diff --git a/vendor/github.com/fsnotify/fsnotify/internal/unix2.go b/vendor/github.com/fsnotify/fsnotify/internal/unix2.go
new file mode 100644
index 000000000..37dfeddc2
--- /dev/null
+++ b/vendor/github.com/fsnotify/fsnotify/internal/unix2.go
@@ -0,0 +1,7 @@
+//go:build !windows
+
+package internal
+
+func HasPrivilegesForSymlink() bool {
+	return true
+}
diff --git a/vendor/github.com/fsnotify/fsnotify/internal/windows.go b/vendor/github.com/fsnotify/fsnotify/internal/windows.go
new file mode 100644
index 000000000..a72c64954
--- /dev/null
+++ b/vendor/github.com/fsnotify/fsnotify/internal/windows.go
@@ -0,0 +1,41 @@
+//go:build windows
+
+package internal
+
+import (
+	"errors"
+
+	"golang.org/x/sys/windows"
+)
+
+// Just a dummy.
+var (
+	SyscallEACCES = errors.New("dummy")
+	UnixEACCES    = errors.New("dummy")
+)
+
+func SetRlimit()                                    {}
+func Maxfiles() uint64                              { return 1<<64 - 1 }
+func Mkfifo(path string, mode uint32) error         { return errors.New("no FIFOs on Windows") }
+func Mknod(path string, mode uint32, dev int) error { return errors.New("no device nodes on Windows") }
+
+func HasPrivilegesForSymlink() bool {
+	var sid *windows.SID
+	err := windows.AllocateAndInitializeSid(
+		&windows.SECURITY_NT_AUTHORITY,
+		2,
+		windows.SECURITY_BUILTIN_DOMAIN_RID,
+		windows.DOMAIN_ALIAS_RID_ADMINS,
+		0, 0, 0, 0, 0, 0,
+		&sid)
+	if err != nil {
+		return false
+	}
+	defer windows.FreeSid(sid)
+	token := windows.Token(0)
+	member, err := token.IsMember(sid)
+	if err != nil {
+		return false
+	}
+	return member || token.IsElevated()
+}
diff --git a/vendor/github.com/fsnotify/fsnotify/mkdoc.zsh b/vendor/github.com/fsnotify/fsnotify/mkdoc.zsh
deleted file mode 100644
index 99012ae65..000000000
--- a/vendor/github.com/fsnotify/fsnotify/mkdoc.zsh
+++ /dev/null
@@ -1,259 +0,0 @@
-#!/usr/bin/env zsh
-[ "${ZSH_VERSION:-}" = "" ] && echo >&2 "Only works with zsh" && exit 1
-setopt err_exit no_unset pipefail extended_glob
-
-# Simple script to update the godoc comments on all watchers so you don't need
-# to update the same comment 5 times.
-
-watcher=$(<<EOF
-// Watcher watches a set of paths, delivering events on a channel.
-//
-// A watcher should not be copied (e.g. pass it by pointer, rather than by
-// value).
-//
-// # Linux notes
-//
-// When a file is removed a Remove event won't be emitted until all file
-// descriptors are closed, and deletes will always emit a Chmod. For example:
-//
-//	fp := os.Open("file")
-//	os.Remove("file")        // Triggers Chmod
-//	fp.Close()               // Triggers Remove
-//
-// This is the event that inotify sends, so not much can be changed about this.
-//
-// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
-// for the number of watches per user, and fs.inotify.max_user_instances
-// specifies the maximum number of inotify instances per user. Every Watcher you
-// create is an "instance", and every path you add is a "watch".
-//
-// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
-// /proc/sys/fs/inotify/max_user_instances
-//
-// To increase them you can use sysctl or write the value to the /proc file:
-//
-//	# Default values on Linux 5.18
-//	sysctl fs.inotify.max_user_watches=124983
-//	sysctl fs.inotify.max_user_instances=128
-//
-// To make the changes persist on reboot edit /etc/sysctl.conf or
-// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
-// your distro's documentation):
-//
-//	fs.inotify.max_user_watches=124983
-//	fs.inotify.max_user_instances=128
-//
-// Reaching the limit will result in a "no space left on device" or "too many open
-// files" error.
-//
-// # kqueue notes (macOS, BSD)
-//
-// kqueue requires opening a file descriptor for every file that's being watched;
-// so if you're watching a directory with five files then that's six file
-// descriptors. You will run in to your system's "max open files" limit faster on
-// these platforms.
-//
-// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
-// control the maximum number of open files, as well as /etc/login.conf on BSD
-// systems.
-//
-// # Windows notes
-//
-// Paths can be added as "C:\\path\\to\\dir", but forward slashes
-// ("C:/path/to/dir") will also work.
-//
-// When a watched directory is removed it will always send an event for the
-// directory itself, but may not send events for all files in that directory.
-// Sometimes it will send events for all times, sometimes it will send no
-// events, and often only for some files.
-//
-// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
-// value that is guaranteed to work with SMB filesystems. If you have many
-// events in quick succession this may not be enough, and you will have to use
-// [WithBufferSize] to increase the value.
-EOF
-)
-
-new=$(<<EOF
-// NewWatcher creates a new Watcher.
-EOF
-)
-
-newbuffered=$(<<EOF
-// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
-// channel.
-//
-// The main use case for this is situations with a very large number of events
-// where the kernel buffer size can't be increased (e.g. due to lack of
-// permissions). An unbuffered Watcher will perform better for almost all use
-// cases, and whenever possible you will be better off increasing the kernel
-// buffers instead of adding a large userspace buffer.
-EOF
-)
-
-add=$(<<EOF
-// Add starts monitoring the path for changes.
-//
-// A path can only be watched once; watching it more than once is a no-op and will
-// not return an error. Paths that do not yet exist on the filesystem cannot be
-// watched.
-//
-// A watch will be automatically removed if the watched path is deleted or
-// renamed. The exception is the Windows backend, which doesn't remove the
-// watcher on renames.
-//
-// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
-// filesystems (/proc, /sys, etc.) generally don't work.
-//
-// Returns [ErrClosed] if [Watcher.Close] was called.
-//
-// See [Watcher.AddWith] for a version that allows adding options.
-//
-// # Watching directories
-//
-// All files in a directory are monitored, including new files that are created
-// after the watcher is started. Subdirectories are not watched (i.e. it's
-// non-recursive).
-//
-// # Watching files
-//
-// Watching individual files (rather than directories) is generally not
-// recommended as many programs (especially editors) update files atomically: it
-// will write to a temporary file which is then moved to to destination,
-// overwriting the original (or some variant thereof). The watcher on the
-// original file is now lost, as that no longer exists.
-//
-// The upshot of this is that a power failure or crash won't leave a
-// half-written file.
-//
-// Watch the parent directory and use Event.Name to filter out files you're not
-// interested in. There is an example of this in cmd/fsnotify/file.go.
-EOF
-)
-
-addwith=$(<<EOF
-// AddWith is like [Watcher.Add], but allows adding options. When using Add()
-// the defaults described below are used.
-//
-// Possible options are:
-//
-//   - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
-//     other platforms. The default is 64K (65536 bytes).
-EOF
-)
-
-remove=$(<<EOF
-// Remove stops monitoring the path for changes.
-//
-// Directories are always removed non-recursively. For example, if you added
-// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
-//
-// Removing a path that has not yet been added returns [ErrNonExistentWatch].
-//
-// Returns nil if [Watcher.Close] was called.
-EOF
-)
-
-close=$(<<EOF
-// Close removes all watches and closes the Events channel.
-EOF
-)
-
-watchlist=$(<<EOF
-// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
-// yet removed).
-//
-// Returns nil if [Watcher.Close] was called.
-EOF
-)
-
-events=$(<<EOF
-	// Events sends the filesystem change events.
-	//
-	// fsnotify can send the following events; a "path" here can refer to a
-	// file, directory, symbolic link, or special file like a FIFO.
-	//
-	//   fsnotify.Create    A new path was created; this may be followed by one
-	//                      or more Write events if data also gets written to a
-	//                      file.
-	//
-	//   fsnotify.Remove    A path was removed.
-	//
-	//   fsnotify.Rename    A path was renamed. A rename is always sent with the
-	//                      old path as Event.Name, and a Create event will be
-	//                      sent with the new name. Renames are only sent for
-	//                      paths that are currently watched; e.g. moving an
-	//                      unmonitored file into a monitored directory will
-	//                      show up as just a Create. Similarly, renaming a file
-	//                      to outside a monitored directory will show up as
-	//                      only a Rename.
-	//
-	//   fsnotify.Write     A file or named pipe was written to. A Truncate will
-	//                      also trigger a Write. A single "write action"
-	//                      initiated by the user may show up as one or multiple
-	//                      writes, depending on when the system syncs things to
-	//                      disk. For example when compiling a large Go program
-	//                      you may get hundreds of Write events, and you may
-	//                      want to wait until you've stopped receiving them
-	//                      (see the dedup example in cmd/fsnotify).
-	//
-	//                      Some systems may send Write event for directories
-	//                      when the directory content changes.
-	//
-	//   fsnotify.Chmod     Attributes were changed. On Linux this is also sent
-	//                      when a file is removed (or more accurately, when a
-	//                      link to an inode is removed). On kqueue it's sent
-	//                      when a file is truncated. On Windows it's never
-	//                      sent.
-EOF
-)
-
-errors=$(<<EOF
-	// Errors sends any errors.
-	//
-	// ErrEventOverflow is used to indicate there are too many events:
-	//
-	//  - inotify:      There are too many queued events (fs.inotify.max_queued_events sysctl)
-	//  - windows:      The buffer size is too small; WithBufferSize() can be used to increase it.
-	//  - kqueue, fen:  Not used.
-EOF
-)
-
-set-cmt() {
-	local pat=$1
-	local cmt=$2
-
-	IFS=$'\n' local files=($(grep -n $pat backend_*~*_test.go))
-	for f in $files; do
-		IFS=':' local fields=($=f)
-		local file=$fields[1]
-		local end=$(( $fields[2] - 1 ))
-
-		# Find start of comment.
-		local start=0
-		IFS=$'\n' local lines=($(head -n$end $file))
-		for (( i = 1; i <= $#lines; i++ )); do
-			local line=$lines[-$i]
-			if ! grep -q '^[[:space:]]*//' <<<$line; then
-				start=$(( end - (i - 2) ))
-				break
-			fi
-		done
-
-		head -n $(( start - 1 )) $file  >/tmp/x
-		print -r -- $cmt                >>/tmp/x
-		tail -n+$(( end + 1 ))   $file  >>/tmp/x
-		mv /tmp/x $file
-	done
-}
-
-set-cmt '^type Watcher struct '             $watcher
-set-cmt '^func NewWatcher('                 $new
-set-cmt '^func NewBufferedWatcher('         $newbuffered
-set-cmt '^func (w \*Watcher) Add('          $add
-set-cmt '^func (w \*Watcher) AddWith('      $addwith
-set-cmt '^func (w \*Watcher) Remove('       $remove
-set-cmt '^func (w \*Watcher) Close('        $close
-set-cmt '^func (w \*Watcher) WatchList('    $watchlist
-set-cmt '^[[:space:]]*Events *chan Event$'  $events
-set-cmt '^[[:space:]]*Errors *chan error$'  $errors
diff --git a/vendor/github.com/fsnotify/fsnotify/system_bsd.go b/vendor/github.com/fsnotify/fsnotify/system_bsd.go
index 4322b0b88..f65e8fe3e 100644
--- a/vendor/github.com/fsnotify/fsnotify/system_bsd.go
+++ b/vendor/github.com/fsnotify/fsnotify/system_bsd.go
@@ -1,5 +1,4 @@
 //go:build freebsd || openbsd || netbsd || dragonfly
-// +build freebsd openbsd netbsd dragonfly
 
 package fsnotify
 
diff --git a/vendor/github.com/fsnotify/fsnotify/system_darwin.go b/vendor/github.com/fsnotify/fsnotify/system_darwin.go
index 5da5ffa78..a29fc7aab 100644
--- a/vendor/github.com/fsnotify/fsnotify/system_darwin.go
+++ b/vendor/github.com/fsnotify/fsnotify/system_darwin.go
@@ -1,5 +1,4 @@
 //go:build darwin
-// +build darwin
 
 package fsnotify
 
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 96d158c25..e8e723a4d 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -226,9 +226,10 @@ github.com/evanphx/json-patch
 # github.com/felixge/httpsnoop v1.0.4
 ## explicit; go 1.13
 github.com/felixge/httpsnoop
-# github.com/fsnotify/fsnotify v1.7.0
+# github.com/fsnotify/fsnotify v1.8.0
 ## explicit; go 1.17
 github.com/fsnotify/fsnotify
+github.com/fsnotify/fsnotify/internal
 # github.com/go-logr/logr v1.4.2
 ## explicit; go 1.18
 github.com/go-logr/logr


Test patch:
diff --git a/e2e.Dockerfile b/e2e.Dockerfile
index 6e6631e48..2c221c5a5 100644
--- a/e2e.Dockerfile
+++ b/e2e.Dockerfile
@@ -14,7 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-FROM golang:1.23-bullseye@sha256:1002103155eb5802f6876dceb8cb8bf965cc6d3deeb34f11e4774926aca6a1fd
+FROM golang:1.23-bullseye@sha256:d4387d8b2ee5b78a199fae168a761d0fb0f33f172642d20856f29a67204af16b
 
 WORKDIR /go/src/sigs.k8s.io/cloud-provider-azure
 
diff --git a/pkg/provider/azure_loadbalancer_test.go b/pkg/provider/azure_loadbalancer_test.go
index d2402fbb3..79f3b2aa9 100644
--- a/pkg/provider/azure_loadbalancer_test.go
+++ b/pkg/provider/azure_loadbalancer_test.go
@@ -6238,6 +6238,8 @@ func TestCleanOrphanedLoadBalancerLBInUseByVMSS(t *testing.T) {
 		expectedVMSS := buildTestVMSSWithLB(testVMSSName, "vmss-vm-", []string{testLBBackendpoolID0}, false)
 		mockVMSSClient := cloud.VirtualMachineScaleSetsClient.(*mockvmssclient.MockInterface)
 		mockVMSSClient.EXPECT().List(gomock.Any(), "rg").Return([]compute.VirtualMachineScaleSet{expectedVMSS}, nil)
+		mockVMSSClient.EXPECT().Get(gomock.Any(), "rg", testVMSSName).Return(expectedVMSS, nil)
+		mockVMSSClient.EXPECT().CreateOrUpdate(gomock.Any(), "rg", testVMSSName, gomock.Any()).Return(nil)
 
 		service := getTestService("test", v1.ProtocolTCP, nil, false, 80)
 		lb := getTestLoadBalancer(pointer.String("test"), pointer.String("rg"), pointer.String("test"), pointer.String("test"), service, consts.LoadBalancerSkuStandard)
diff --git a/pkg/provider/azure_utils_test.go b/pkg/provider/azure_utils_test.go
index 4d6e225e6..43b389ebb 100644
--- a/pkg/provider/azure_utils_test.go
+++ b/pkg/provider/azure_utils_test.go
@@ -871,12 +871,21 @@ func TestIsFIPIPv6(t *testing.T) {
 			},
 			expectedIsIPv6: true,
 		},
+		{
+			desc: "enpty ip families",
+			svc: v1.Service{
+				Spec: v1.ServiceSpec{
+					IPFamilies: []v1.IPFamily{},
+				},
+			},
+			expectedIsIPv6: false,
+		},
 	}
 	for _, tc := range testcases {
 		tc := tc
 		t.Run(tc.desc, func(t *testing.T) {
 			az := GetTestCloud(ctrl)
-			isIPv6, err := az.isFIPIPv6(&tc.svc, "rg", tc.fip)
+			isIPv6, err := az.isFIPIPv6(&tc.svc, tc.fip)
 			assert.Nil(t, err)
 			assert.Equal(t, tc.expectedIsIPv6, isIPv6)
 		})