Make WordPress Core

Opened 17 months ago

Closed 17 months ago

Last modified 13 months ago

#56966 closed defect (bug) (fixed)

Updating plugins with WP6.1 creates .maintenance file and leaves it

Reported by: jsh4's profile jsh4 Owned by: desrosj's profile desrosj
Milestone: 6.1.1 Priority: normal
Severity: normal Version: 6.1
Component: Upgrade/Install Keywords: has-testing-info has-patch commit fixed-major
Focuses: Cc:

Description

Pre WP6.1 updating a plug in creates a .maintenance file and removes it when updates are complete. WP6.1 creates and leaves in place the .maintenance file requiring a shell action to remove it.

Change History (33)

#1 @SergeyBiryukov
17 months ago

  • Component changed from Administration to Upgrade/Install

#2 follow-up: @afragen
17 months ago

Can you provide some data about your environment? Was anything missing afterwards?

#3 in reply to: ↑ 2 @jsh4
17 months ago

Replying to afragen:

Can you provide some data about your environment? Was anything missing afterwards?

Once I remove the .maintenance file manually, everything works fine.

My environment is getting older so maybe aging PHP version might have something to do with this.

This also happens when updating themes. Interesting, the .maintenance file isn't being created during WP core update.

I'm working my way through all my WP sites one by one learning all this as I go.

#4 @jsh4
17 months ago

I spoke too soon about WP updates not created a .maintenance file. It does... it just takes a while to show I guess during the actual code update moment.

My work around for dealing with this is to perform all other updates before I upgrade to 6.1.

#5 follow-up: @costdev
17 months ago

  • Keywords reporter-feedback added

Hi @jsh4, can you post the following information?

  • PHP Version
  • Server software (Apache, NGINX, or?)
  • Does this occur when updating one plugin, or when bulk updating, or both?
  • Does this occur with all plugins disabled, then updating them?

This should help with reproducing the problem and tracking down the cause

#6 in reply to: ↑ 5 @jsh4
17 months ago

Replying to costdev:

Hi @jsh4, can you post the following information?

  • PHP Version

7.4.21 (CLI)

  • Server software (Apache, NGINX, or?)

It's an NGINX front end passing off to Apache2.
Apache/2.4.48 (Ubuntu)

  • Does this occur when updating one plugin, or when bulk updating, or both?

Both.

  • Does this occur with all plugins disabled, then updating them?

I haven't tried that. Just trying to get WP up to 6.1 on all the sites first.

This should help with reproducing the problem and tracking down the cause

Thanks for your help.

#7 @costdev
17 months ago

  • Keywords has-testing-info needs-testing added; reporter-feedback removed

Thanks @jsh4! I'll see if I can reproduce the issue.

For others reading this, please help test updating plugins in 6.1 on PHP 7.4.21 and NGINX passing off to Apache (or test on NGINX or Apache directly - more testing the better!).

#8 @pkolenbr
17 months ago

I have the same issue as reported by jsh4. I did some more investigation: I'm using the ftpext method in my config.inc.php:

define('FS_METHOD','ftpext');

The issue lies in file /wp-admin/includes/class-wp-filesystem-ftpext.php. Check line 415 and 416. The new system sees the file .maintenance as a directory instead of a file, and tries to delete it by FTP_RMDIR (instead of FTP_DELETE). This results in the following error:

Got error 'PHP message: PHP Warning:  ftp_rmdir(): /.maintenance: No such file or directory in /var/www/website.org/wp-admin/includes/class-wp-filesystem-ftpext.php on line 397'

For now I used a workaround and copied the old file class-wp-filesystems-ftpext.php from WP 6.03 to my WP6.1 file. This solves the issue.

My environment: Debian (bullseye), Apache + PHP 7.4.30. For FTP I'm using ProFTPD.

#9 @SergeyBiryukov
17 months ago

  • Milestone changed from Awaiting Review to 6.1.1

Thanks for the ticket!

It looks like this has to do with [53860] / #51170, moving to 6.1.1 for investigation.

#10 @JeffPaul
17 months ago

  • Keywords needs-patch added

This ticket was mentioned in Slack in #core by jeffpaul. View the logs.


17 months ago

#12 @costdev
17 months ago

  • Keywords dev-feedback added

I've reproduced the issue using ProFTPD. After looking into this, I'm going to step through each part below.

This is a long one.


maintenance_mode() calls:

<?php
$file = $wp_filesystem->abspath() . '.maintenance';
$wp_filesystem->delete( $file );

Note the signature of WP_Filesystem_FTPext::delete():

<?php
public function delete( $file, $recursive = false, $type = false )

WP_Filesystem_FTPext::delete() contains this:

<?php
if ( 'f' === $type || $this->is_file( $file ) ) {
        return ftp_delete( $this->link, $file );
}

maintenance_mode() doesn't pass the $type argument, so let's focus on $this->is_file( $file ).

WP_Filesystem_FTPext::is_file()

<?php
public function is_file( $file ) {
        return $this->exists( $file ) && ! $this->is_dir( $file );
}

! $this->is_dir( $file ) returns true, which is fine, so let's focus on $this->exists( $file ).

WP_Filesystem_FTPext::exists() - 6.1

<?php
public function exists( $path ) {
        if ( $this->is_dir( $path ) ) {
                return true;
        }

        return ! empty( ftp_rawlist( $this->link, $path ) );
}

Let's go ahead and skip $this->is_dir( $path ) as we know this returns false.

On to ftp_rawlist()...

Most FTP servers have the -a switch enabled by default. This includes hidden files, and so for these servers, an existing .maintenance file will return a non-empty result from ftp_rawlist(), meaning the file exists. ✅

However, ProFTPD doesn't include this switch by default, so won't pick up the file. ❌ This will return to ::delete() and then call rmdir(). 💥🐞

In addition, including the switch when an FTP server already includes it can throw an error, so we can't just call ftp_rawlist( $this->link, ' -a ' . $path ). ❗


Therefore, from what I've seen so far, we need to:

  1. Call ftp_rawlist() without the switch. This will cover:
    • Existing non-hidden files. ✅
    • Existing hidden files on FTP servers who enable the switch by default. ✅
  2. If the file doesn't exist, we then call @ftp_rawlist() with the switch. This will cover:
    • Existing hidden files on FTP servers who don't enable the switch by default. ✅
    • Note: The @ operator is needed to suppress errors when $file doesn't exist on FTP servers that enable the switch by default. I know, we're trying to reduce the usage of this operator, and I still need to reproduce the error to see if it's a fatal, as @ won't work on PHP8+ in that case.

This looks like:

<?php
public function exists( $path ) {
        if ( $this->is_dir( $path ) ) {
                return true;
        }

        if ( ! empty( ftp_rawlist( $this->link, $path ) ) ) {
                return true;
        }

+       /*
+        * Include '-a' switch for FTP servers that do not enable it by default.
+        *
+        * For some FTP servers that enable the switch by default, including the
+        * switch again can produce an error, so the '@' operator must be used.
+        */
+       if ( ! empty( @ftp_rawlist( $this->link, ' -a ' . $path ) ) ) {
+               return true;
+       }

        return false;
}

Now, that requires confirmation of the type of error thrown, as well as discussion on whether this approach is agreeable to committers.

For those unfamiliar, the aim of #51170 was to make WP_Filesystem_FTPext::exists() compliant with RFC 959 (CTRL+F NAME LIST (NLST) for ftp_nlist() - 6.0, LIST (LIST) for ftp_rawlist() - 6.1). The current implementation, and the above code block, are compliant with RFC 959. The implementation up to WordPress 6.0 was not.

I've tested the above code block on vsftpd, PureFTPd and ProFTPD, and it's successful on all three.

For WP_Filesystem_FTPext, this means:

  • ::exists() will continue to be RFC 959 compliant.
  • ::exists() will work with FTP servers that do/do not enable the -a switch by default.

Adding dev-feedback.

I also still need to find out what type of warning/error is thrown on FTP servers that already enable the switch and -a is passed with ftp_rawlist(). MacOS' built-in FTP server does this, so if anyone can set up WordPress to use define( 'FS_METHOD', 'ftpext' ) running the Mac FTP server, implement the above change, remove the @ operator, and call ::exists() on a file that doesn't exist, that would be great!

Last edited 17 months ago by costdev (previous) (diff)

This ticket was mentioned in Slack in #core-upgrade-install by costdev. View the logs.


17 months ago

This ticket was mentioned in Slack in #core by costdev. View the logs.


17 months ago

#15 @dd32
17 months ago

For those unfamiliar, the aim of #51170 was to make WP_Filesystem_FTPext::exists() compliant with RFC 959 (CTRL+F NAME LIST (NLST) for ftp_nlist() - 6.0, LIST (LIST) for ftp_rawlist() - 6.1). The current implementation, and the above code block, are compliant with RFC 959. The implementation up to WordPress 6.0 was not.

I believe ftp_nlist() was previously used here for a reason, because being compliant with the specs and working with all FTP servers was impossible. #28013 has some related commits where changing that exists() method caused breakages on other servers (I believe #51170 may be a duplicate of that earlier ticket even)

Personally, I would err on the side of caution and recommend reverting the changes in #51170 from 6.1.1, perform more testing, and try again in 6.2.

If you want to retain it, and use -a you can probably always send it, https://github.com/wordpress/wordpress-develop/blob/2dce57706fa56db2e931be853ee036049921884f/src/wp-admin/includes/class-wp-filesystem-ftpext.php#L711-L725.

Version 1, edited 17 months ago by dd32 (previous) (next) (diff)

#16 @peterwilsoncc
17 months ago

I agree with our lead-dev, reverting is safest given the historic problems on the earlier ticket.

Commits on the earlier ticket [53860,53862,53898].

#17 @gamecreature
17 months ago

Same issue here with update plugins on a FreeBSD ftp server.
The WPFileSystemFtpExt exists method relies on ftp_rawlist

Which returns 'No such file or directory' on FreeBSD.
So to result will not be empty.

Last edited 17 months ago by gamecreature (previous) (diff)

#18 @costdev
17 months ago

  • Keywords needs-testing needs-patch dev-feedback removed

Agreed, [53860] should be reverted.

Always sending -a may be problematic on some FTP servers and may also need to be changed in ::dirlist().

Let's investigate this further in 6.2.

This ticket was mentioned in Slack in #core by jeffpaul. View the logs.


17 months ago

#20 @desrosj
17 months ago

Personally, I would err on the side of caution and recommend reverting the changes in #51170 from 6.1.1, perform more testing, and try again in 6.2.

After looking at this and getting clarification from @dd32 on the statement above, let's do the following:

  • Leave the unrelated changes to WP_Filesystem_FTPext::size() in [53898]. This would only have a negative impact if someone is forcing FTPext AND performing comparisons such as -1 == $ftp->size() or 0 > $ftp->size() type comparisons. This is highly unlikely and also completely unrelated to the problematic changes.
  • Revert the first and third chunks of [53862]. The middle chunk was refined and corrected in [53898].
  • Revert parts of [53860] unrelated to size() method.

#22 @desrosj
17 months ago

I've attached a PR that I think reverts the right parts of [53862] and [53860] while making sure the changes made in [53872] are still present.

Last edited 17 months ago by desrosj (previous) (diff)

#23 @peterwilsoncc
17 months ago

  • Keywords commit added

The linked pull requests has multiple approvals, marking for commit.

#24 @desrosj
17 months ago

  • Owner set to desrosj
  • Resolution set to fixed
  • Status changed from new to closed

In 54815:

Filesystem: Return FTP/FTP Sockets exists() methods to a previous state.

This partially reverts [53860] and [53862], which refactored the exists() method to rely on ftp_rawlist() instead of ftp_nlist().

[53860] makes a similar attempt to the ones made in [33648] and [34733] (which were also reverted in [35944]). Being compliant with the specifications while continuing to work without issue for all FTP servers continues seem impossible. These little ghosts are the ones we’re scared of the most.

Props jsh4, afragen, costdev, pkolenbr, SergeyBiryukov, dd32, peterwilsoncc, gamecreature, desrosj.
Fixes #56966.
See #51170, #28013.

#26 @desrosj
17 months ago

  • Keywords fixed-major added
  • Resolution fixed deleted
  • Status changed from closed to reopened

Reopening for backport.

#27 @desrosj
17 months ago

  • Resolution set to fixed
  • Status changed from reopened to closed

In 54816:

Filesystem: Return FTP/FTP Sockets exists() methods to a previous state.

This partially reverts [53860] and [53862], which refactored the exists() method to rely on ftp_rawlist() instead of ftp_nlist().

[53860] makes a similar attempt to the ones made in [33648] and [34733] (which were also reverted in [35944]). Being compliant with the specifications while continuing to work without issue for all FTP servers continues seem impossible. These little ghosts are the ones we’re scared of the most.

Props jsh4, afragen, costdev, pkolenbr, SergeyBiryukov, dd32, peterwilsoncc, gamecreature, desrosj.
Merges [54815] to the 6.1 branch.
Fixes #56966.
See #51170, #28013.

This ticket was mentioned in Slack in #hosting-community by javier. View the logs.


17 months ago

#30 @costdev
17 months ago

@boldfish Thanks for posting another instance of this issue occurring. For future research, what FTP server do you use?

#31 @boldfish
17 months ago

@costdev happened on all the instances where I was using FTP user for WordPress to do updates:

ProFTPd

Versions 1.3.5e (CentOS 7), 1.3.6e (AlmaLinux 8)

#32 @costdev
17 months ago

Thanks @boldfish!

#33 @costdev
13 months ago

#57063 was marked as a duplicate.

Note: See TracTickets for help on using tickets.