Re: [PATCH] cifs: do not fail __smb_send_rqst if non-fatal signals are pending

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Pavel,

Sorry for the late review on this. A few minor comments on __smb_send_rqst():

    if ((total_len > 0) && (total_len != send_length)) { <<<< what's
special about total_len == 0? I'm guessing send_length will also be 0
in such a case.
        cifs_dbg(FYI, "partial send (wanted=%u sent=%zu): terminating
session\n",
             send_length, total_len);
        /*
         * If we have only sent part of an SMB then the next SMB could
         * be taken as the remainder of this one. We need to kill the
         * socket so the server throws away the partial SMB
         */
        server->tcpStatus = CifsNeedReconnect;
        trace_smb3_partial_send_reconnect(server->CurrentMid,
                          server->hostname);
    }

I'm not an expert on kernel socket programming, but if total_len !=
sent_length, shouldn't we iterate retrying till they become equal (or
abort if there's no progress)?
I see that we cork the connection before send, and I guess it's
unlikely why only a partial write will occur (Since these are just
in-memory writes).
But what is the reason for reconnecting on partial writes?

smbd_done:
    if (rc < 0 && rc != -EINTR)   <<<<< Not very critical, but
shouldn't we also check for rc != -ERESTARTSYS?
        cifs_server_dbg(VFS, "Error %d sending data on socket to server\n",
             rc);
    else if (rc > 0)
        rc = 0;

    return rc;
}

Regards,
Shyam

On Fri, Jan 22, 2021 at 11:34 PM Steve French <smfrench@xxxxxxxxx> wrote:
>
> Patch updated with Pavel's suggestion, and added a commit description
> from various comments in this email thread (see attached).
>
>     cifs: do not fail __smb_send_rqst if non-fatal signals are pending
>
>     RHBZ 1848178
>
>     The original intent of returning an error in this function
>     in the patch:
>       "CIFS: Mask off signals when sending SMB packets"
>     was to avoid interrupting packet send in the middle of
>     sending the data (and thus breaking an SMB connection),
>     but we also don't want to fail the request for non-fatal
>     signals even before we have had a chance to try to
>     send it (the reported problem could be reproduced e.g.
>     by exiting a child process when the parent process was in
>     the midst of calling futimens to update a file's timestamps).
>
>     In addition, since the signal may remain pending when we enter the
>     sending loop, we may end up not sending the whole packet before
>     TCP buffers become full. In this case the code returns -EINTR
>     but what we need here is to return -ERESTARTSYS instead to
>     allow system calls to be restarted.
>
>     Fixes: b30c74c73c78 ("CIFS: Mask off signals when sending SMB packets")
>     Cc: stable@xxxxxxxxxxxxxxx # v5.1+
>     Signed-off-by: Ronnie Sahlberg <lsahlber@xxxxxxxxxx>
>     Reviewed-by: Pavel Shilovsky <pshilov@xxxxxxxxxxxxx>
>     Signed-off-by: Steve French <stfrench@xxxxxxxxxxxxx>
>
> diff --git a/fs/cifs/transport.c b/fs/cifs/transport.c
> index e9abb41aa89b..95ef26b555b9 100644
> --- a/fs/cifs/transport.c
> +++ b/fs/cifs/transport.c
> @@ -338,7 +338,7 @@ __smb_send_rqst(struct TCP_Server_Info *server,
> int num_rqst,
>         if (ssocket == NULL)
>                 return -EAGAIN;
>
> -       if (signal_pending(current)) {
> +       if (fatal_signal_pending(current)) {
>                 cifs_dbg(FYI, "signal pending before send request\n");
>                 return -ERESTARTSYS;
>         }
> @@ -429,7 +429,7 @@ __smb_send_rqst(struct TCP_Server_Info *server,
> int num_rqst,
>
>         if (signal_pending(current) && (total_len != send_length)) {
>                 cifs_dbg(FYI, "signal is pending after attempt to send\n");
> -               rc = -EINTR;
> +               rc = -ERESTARTSYS;
>         }
>
>         /* uncork it */
>
> On Fri, Jan 22, 2021 at 3:46 PM ronnie sahlberg
> <ronniesahlberg@xxxxxxxxx> wrote:
> >
> > On Sat, Jan 23, 2021 at 5:47 AM Pavel Shilovsky <piastryyy@xxxxxxxxx> wrote:
> > >
> > > Ronnie,
> > >
> > > I still think that your patch needs additional fix here:
> > >
> > > -----------
> > > /*
> > > * If signal is pending but we have already sent the whole packet to
> > > * the server we need to return success status to allow a corresponding
> > > * mid entry to be kept in the pending requests queue thus allowing
> > > * to handle responses from the server by the client.
> > > *
> > > * If only part of the packet has been sent there is no need to hide
> > > * interrupt because the session will be reconnected anyway, so there
> > > * won't be any response from the server to handle.
> > > */
> > >
> > > if (signal_pending(current) && (total_len != send_length)) {
> > > cifs_dbg(FYI, "signal is pending after attempt to send\n");
> > > rc = -EINTR;
> > >         ^^^
> > >         This should be changed to -ERESTARTSYS to allow kernel to
> > > restart a syscall.
> > >
> > > }
> > > -----------
> > >
> > > Since the signal may remain pending when we enter the sending loop, we
> > > may end up not sending the whole packet before TCP buffers become
> > > full. In this case according to the condition above the code returns
> > > -EINTR but what we need here is to return -ERESTARTSYS instead to
> > > allow system calls to be restarted.
> > >
> > > Thoughts?
> >
> > Yes, that is probably a good idea to change too.
> > Steve, can you add this change to my patch?
> >
> >
> > >
> > > --
> > > Best regards,
> > > Pavel Shilovsky
> > >
> > > чт, 21 янв. 2021 г. в 14:41, ronnie sahlberg <ronniesahlberg@xxxxxxxxx>:
> > > >
> > > >
> > > >
> > > > On Thu, Jan 21, 2021 at 10:19 PM Paulo Alcantara <pc@xxxxxx> wrote:
> > > >>
> > > >> Aurélien Aptel <aaptel@xxxxxxxx> writes:
> > > >>
> > > >> > Pavel Shilovsky <piastryyy@xxxxxxxxx> writes:
> > > >> >
> > > >> >> вт, 19 янв. 2021 г. в 22:38, Steve French <smfrench@xxxxxxxxx>:
> > > >> >>>
> > > >> >>> The patch won't merge (also has some text corruptions in it).  This
> > > >> >>> line of code is different due to commit 6988a619f5b79
> > > >> >>>
> > > >> >>> 6988a619f5b79 (Paulo Alcantara 2020-11-28 15:57:06 -0300 342)
> > > >> >>>  cifs_dbg(FYI, "signal pending before send request\n");
> > > >> >>> 6988a619f5b79 (Paulo Alcantara 2020-11-28 15:57:06 -0300 343)
> > > >> >>>  return -ERESTARTSYS;
> > > >> >>>
> > > >> >>>         if (signal_pending(current)) {
> > > >> >>>                 cifs_dbg(FYI, "signal pending before send request\n");
> > > >> >>>                 return -ERESTARTSYS;
> > > >> >>>         }
> > > >> >>>
> > > >> >>> See:
> > > >> >>>
> > > >> >>> Author: Paulo Alcantara <pc@xxxxxx>
> > > >> >>> Date:   Sat Nov 28 15:57:06 2020 -0300
> > > >> >>>
> > > >> >>>     cifs: allow syscalls to be restarted in __smb_send_rqst()
> > > >> >>>
> > > >> >>>     A customer has reported that several files in their multi-threaded app
> > > >> >>>     were left with size of 0 because most of the read(2) calls returned
> > > >> >>>     -EINTR and they assumed no bytes were read.  Obviously, they could
> > > >> >>>     have fixed it by simply retrying on -EINTR.
> > > >> >>>
> > > >> >>>     We noticed that most of the -EINTR on read(2) were due to real-time
> > > >> >>>     signals sent by glibc to process wide credential changes (SIGRT_1),
> > > >> >>>     and its signal handler had been established with SA_RESTART, in which
> > > >> >>>     case those calls could have been automatically restarted by the
> > > >> >>>     kernel.
> > > >> >>>
> > > >> >>>     Let the kernel decide to whether or not restart the syscalls when
> > > >> >>>     there is a signal pending in __smb_send_rqst() by returning
> > > >> >>>     -ERESTARTSYS.  If it can't, it will return -EINTR anyway.
> > > >> >>>
> > > >> >>>     Signed-off-by: Paulo Alcantara (SUSE) <pc@xxxxxx>
> > > >> >>>     CC: Stable <stable@xxxxxxxxxxxxxxx>
> > > >> >>>     Reviewed-by: Ronnie Sahlberg <lsahlber@xxxxxxxxxx>
> > > >> >>>     Reviewed-by: Pavel Shilovsky <pshilov@xxxxxxxxxxxxx>
> > > >> >>>
> > > >> >>> On Tue, Jan 19, 2021 at 10:32 PM Ronnie Sahlberg <lsahlber@xxxxxxxxxx> wrote:
> > > >> >>> >
> > > >> >>> > RHBZ 1848178
> > > >> >>> >
> > > >> >>> > There is no need to fail this function if non-fatal signals are
> > > >> >>> > pending when we enter it.
> > > >> >>> >
> > > >> >>> > Signed-off-by: Ronnie Sahlberg <lsahlber@xxxxxxxxxx>
> > > >> >>> > ---
> > > >> >>> >  fs/cifs/transport.c | 2 +-
> > > >> >>> >  1 file changed, 1 insertion(+), 1 deletion(-)
> > > >> >>> >
> > > >> >>> > diff --git a/fs/cifs/transport.c b/fs/cifs/transport.c
> > > >> >>> > index c42bda5a5008..98752f7d2cd2 100644
> > > >> >>> > --- a/fs/cifs/transport.c
> > > >> >>> > +++ b/fs/cifs/transport.c
> > > >> >>> > @@ -339,7 +339,7 @@ __smb_send_rqst(struct TCP_Server_Info *server, int num_rqst,
> > > >> >>> >         if (ssocket == NULL)
> > > >> >>> >                 return -EAGAIN;
> > > >> >>> >
> > > >> >>> > -       if (signal_pending(current)) {
> > > >> >>> > +       if (fatal_signal_pending(current)) {
> > > >> >>> >                 cifs_dbg(FYI, "signal is pending before sending any data\n");
> > > >> >>> >                 return -EINTR;
> > > >> >>> >         }
> > > >> >
> > > >> > I've looked up the difference
> > > >> >
> > > >> > static inline int __fatal_signal_pending(struct task_struct *p)
> > > >> > {
> > > >> >       return unlikely(sigismember(&p->pending.signal, SIGKILL));
> > > >> > }
> > > >> >
> > > >> >
> > > >> >> I have been thinking around the same lines. The original intent of
> > > >> >> failing the function here was to avoid interrupting packet send in the
> > > >> >> middle of the packet and not breaking an SMB connection.
> > > >> >> That's also why signals are blocked around smb_send_kvec() calls. I
> > > >> >> guess most of the time a socket buffer is not full, so, those
> > > >> >> functions immediately return success without waiting internally and
> > > >> >> checking for pending signals. With this change the code may break SMB
> > > >> >
> > > >> > Ah, interesting.
> > > >> >
> > > >> > I looked up the difference between fatal/non-fatal and it seems
> > > >> > fatal_signal_pending() really only checks for SIGKILL, but I would
> > > >> > expect ^C (SIGINT) to return quickly as well.
> > > >> >
> > > >> > I thought the point of checking for pending signal early was to return
> > > >> > quickly to userspace and not be stuck in some unkillable state.
> > > >> >
> > > >> > After reading your explanation, you're saying the kernel funcs to send
> > > >> > on socket will check for any signal and err early in any case.
> > > >> >
> > > >> > some_syscall() {
> > > >> >
> > > >> >     if (pending_fatal_signal)  <===== if we ignore non-fatal here
> > > >> >         fail_early();
> > > >> >
> > > >> >     block_signals();
> > > >> >     r = kernel_socket_send {
> > > >> >         if (pending_signal) <==== they will be caught here
> > > >> >             return error;
> > > >> >
> > > >> >         ...
> > > >> >     }
> > > >> >     unblock_signals();
> > > >> >     if (r)
> > > >> >         fail();
> > > >> >     ...
> > > >> > }
> > > >> >
> > > >> > So this patch will (potentially) trigger more reconnect (because we
> > > >> > actually send the packet as a vector in a loop) but I'm not sure I
> > > >> > understand why it returns less errors to userspace?
> > > >> >
> > > >> > Also, shouldn't we move the pending_fatal_signal check *inside* the blocked
> > > >> > signal section?
> > > >> >
> > > >> > In any case I think we should try to test some of those changes given
> > > >> > how we have 3,4 patches trying to tweak it on top of each other.
> > > >>
> > > >> I think it would make sense to have something like
> > > >>
> > > >> diff --git a/fs/cifs/transport.c b/fs/cifs/transport.c
> > > >> index e9abb41aa89b..f7292c14863e 100644
> > > >> --- a/fs/cifs/transport.c
> > > >> +++ b/fs/cifs/transport.c
> > > >> @@ -340,7 +340,7 @@ __smb_send_rqst(struct TCP_Server_Info *server, int num_rqst,
> > > >>
> > > >>         if (signal_pending(current)) {
> > > >>                 cifs_dbg(FYI, "signal pending before send request\n");
> > > >> -               return -ERESTARTSYS;
> > > >> +               return __fatal_signal_pending(current) ? -EINTR : -ERESTARTSYS;
> > > >>         }
> > > >>
> > > >
> > > > That is not sufficient because there are syscalls that are never supposed to fail with -EINTR or -ERESTARTSYS
> > > > and thus will not be restarted by either the kernel or libc.
> > > >
> > > > For example utimensat(). The change to only fail here with -E* for a fatal signal (when the process will be killed anyway)
> > > > is to address an issue when IF there are signals pending, any signal, during the utimensat() system call then
> > > > this will lead to us returning -EINTR back to the application. Which can break some applications such as 'tar'.
> > > >
> > > >
> > > > ronnie s
> > > >
> > > >
> > > >>
> > > >>         /* cork the socket */
> > > >>
> > > >> so that we allow signal handlers to be executed before restarting
> > > >> syscalls when receiving non-fatal signals, otherwise -EINTR.
>
>
>
> --
> Thanks,
>
> Steve



-- 
-Shyam




[Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux