Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1ukD5w-00H4yB-L2 for pgsql-bugs@arkaria.postgresql.org; Fri, 08 Aug 2025 02:44:36 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1ukD5u-002ljX-Fl for pgsql-bugs@arkaria.postgresql.org; Fri, 08 Aug 2025 02:44:34 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1ukD5u-002ljL-4S for pgsql-bugs@lists.postgresql.org; Fri, 08 Aug 2025 02:44:34 +0000 Received: from mail-oa1-x30.google.com ([2001:4860:4864:20::30]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.96) (envelope-from ) id 1ukD5r-001Me7-19 for pgsql-bugs@lists.postgresql.org; Fri, 08 Aug 2025 02:44:33 +0000 Received: by mail-oa1-x30.google.com with SMTP id 586e51a60fabf-30b8a7fa7e1so618790fac.2 for ; Thu, 07 Aug 2025 19:44:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1754621069; x=1755225869; darn=lists.postgresql.org; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=ymMGYjDxD4K2cTEGPlT3fhgHRG8HUGmc82Lzg0GQ2Ns=; b=gXSdvJEvmvgDceov3flW0GuNUr8pg22LtRcBB1vXdw3VyKr6l7R0PF4A+dmbMkMRWZ J5/0KbM/LkvrG1yGUNUpns7Fnyzmoy2jVwD8/+E64mrtVzIqFs+re6itJdWuV+yiV0Dp mNKYgE2FLSRTLlAXxVmBlYElE3pfVtO5l6lcrt4n+BHYzALCvvwd2paXTRrzfs8jTfzD 5ph9ZIvzxweGGgpbaEHg7z3zaIKnqwWMYc8X7ceZ19rUoOroRxnsueIxb8B2Lnu3gcWK nkagi83h3H83LKWBjgzNrWHwlXrwHX14dYvhHcjwEDcHUxhavCVLVWR9WAfSsitE9g9H bzaQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1754621069; x=1755225869; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=ymMGYjDxD4K2cTEGPlT3fhgHRG8HUGmc82Lzg0GQ2Ns=; b=OTceul8vydR7KdQyvKHakpz4fuAEwFyHzb9u5FFoc7ADbF9kTgMB7dtdlyVtbuxFjL 1bXkVy6AxxO/plmfLLDed0JbV3Jo9bS923QlPEls6GKB/IfXRicmGvrCNB2o0io9WLbb 3U05d2KKRtdRB7jzi9Go9lo8XwqfIBPWd0qBxU0tssUyehwzy5bAlCKq5/F8OzdVyW0L jzGNT0mn6qOqpGMK8VAg8uDI+dx78xrNHBrmGvaVbFbt8+78mImsrUQcHOtVFSL8IsMZ zez/4RYMVASf+e65Nh1KKC8Q8MDHAXXCzctfkZ+mXi7pT5i0q9SP6k7jPx+wJlKqJXuY C9dQ== X-Forwarded-Encrypted: i=1; AJvYcCUmTwDP8dtQZhFrt7XWJPJORs10hXStrJ0co/1J9mKsA33iZeUF9O2hd2S2kBAg77tKOLcwEtIuujo5@lists.postgresql.org X-Gm-Message-State: AOJu0YxzVOCl7rwLHYabqgA18GmtmR0yD7IrtNCBogqqWdkBhFzN61Vu cArCH9Lfe6BKzmP3qunoyWW84K8IVLdHBeJydVcwmv70SbgGUsUN81o6gkj01Zbw/8TaPdtDAPn l8dV43XMm+rwpntnYfpxeEAcnuAU/6RHsq/P2 X-Gm-Gg: ASbGncsJxsPWTax9D5Rh2ul/VeZiX0LaKNwRhPN4od4x2F4ks2W4IzLDHVAwM+IxuwU saNmuA5h2c85OpbkN/Rqifq0JYn7QS3Vaiv+xUHl4YJcqSfqyA7PCsBvnhszwjn/rf3hUI6MNKr 2Ikw/ufj2RJrAjTrUR5niKpcQTG8yXZkH9QOktTa4J3h8EI6EnzNQgmKgDIRvkJ+0bYPn/YlrBl MpH5sdC8uYWP1YYLxnST7X2tcoxwf2wjIUqEJfZ8zI62shTMf4= X-Google-Smtp-Source: AGHT+IFBOq2qMb1CPtnwwxaeM4T9kaOQb2Y5k6FKq0wIcJ2mJrE65mnneSBSxdBjr8KAy+7IEJpmrHHTrTXs0iQCNXM= X-Received: by 2002:a05:687c:300c:b0:30b:8df3:518f with SMTP id 586e51a60fabf-30c20b8073amr420722fac.0.1754621069318; Thu, 07 Aug 2025 19:44:29 -0700 (PDT) MIME-Version: 1.0 References: <19006-80fcaaf69000377e@postgresql.org> In-Reply-To: From: Thomas Munro Date: Fri, 8 Aug 2025 14:43:52 +1200 X-Gm-Features: Ac12FXyrZcD-SE7AnoDD7tB4F4U1tbrh0xvodh7379PBNXfAuNL7gigUbNDSczc Message-ID: Subject: Re: BUG #19006: Assert(BufferIsPinned) in BufferGetBlockNumber() is triggered for forwarded buffer To: Xuneng Zhou Cc: exclusion@gmail.com, pgsql-bugs@lists.postgresql.org, Michael Paquier , Tom Lane Content-Type: multipart/mixed; boundary="0000000000008d806a063bd18d63" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --0000000000008d806a063bd18d63 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable On Fri, Aug 8, 2025 at 3:09=E2=80=AFAM Xuneng Zhou w= rote: > Could you please elaborate on it a little bit more? Thanks. Sure. It's a bit complicated and involves many moving parts, so let me try to summarise the read_stream.c fast path, forwarded buffers and the motivation and complexities, and then finally the bug. This email turned out longer than I feared so sorry for the wall of text, but it seemed useful to work through it with illustrations to help more people understand this stuff and contribute improvements, and also recheck my assumptions at the same time. New version with #ifdefs. I believe that qualified for trivial dead code elimination in optimized builds but yeah it's still better this way, thanks all. This is the version I plan to push if there are no further comments. Before digging further, a brief recap of read_stream.c and its fast path: The goal is to be able to perform the equivalent of a stream of ReadBuffer() while minimising system calls, IOPS and I/O stalls by looking a variable distance ahead in the block number stream, combining consecutive blocks into a wider read operation according to your io_combine_limit setting, and running some number of I/Os asynchronously up to your {effective,maintenance}_io_concurrency setting. It starts off looking just one block into the future, but adjusts it up and down according to the history of demand and cache misses, as a prediction of future I/O needs. If the blocks are all cached, the lookahead distance is minimised and it becomes very similar to a plain loop of ReadBuffer() calls, since I/O and thus I/O combining and concurrency aren't predicted to be useful. It has a special fast path mode to squeeze a bit more performance out of the extreme case of all-cached blocks that don't use the facility for streaming optional data along with each block. It uses specialised code in read_stream.c and bufmgr.c to minimise bookkeeping, branching and data cache misses through queue maintenance and movement. The bug involves multiple fast path transitions and short reads, so next let me describe the bufmgr.c's interface for combined and asynchronous I/O and the need for short reads: read_stream.c works by issuing overlapping pairs of calls to StartReadBuffers() and WaitReadBuffers(), with stream->ios holding the queue of IOs with a pending call to WaitReadBuffers(). The interface of StartReadBuffers() is more complex than ReadBuffer() because it has to cope with a lot of edge cases. It takes a relation and fork, a block number, an input/output nblocks count, and an input/output array of buffers. It returns true if you need to call WaitReadBuffers(), meaning the operation wasn't completed immediately from cache. The reason nblocks is input/output is that the operation must sometimes be "split", also known as a short read. It splits IOs partly because we want StartReadBuffers() to start exactly zero or one IOs, and the caller would lose control of the I/O depth if StartReadBuffers() were capable of starting multiple IOs. We also want to avoid some avoidable waits. Here are some reasons for splits: 1. The leading block is BM_VALID, so we choose to give it to you immediately and not look further (we could look further and return more than one consecutive BM_VALID block at a time, but this isn't implemented) 2. An internal block is BM_VALID, so any non-BM_VALID blocks either side of it can't be combined into one operation 3. An internal block is BM_IO_IN_PROGRESS, meaning that another backend is reading or writing it 4. An internal or final block is in a different 1GB segment file (Reason 3 will usually turn in to reason 2 eventually, or the other backend's IO attempt might in theory fail and then it'll be this backend's turn to try, and there are some slightly complicated rules about whether we have to wait for that or split the IO here, for deadlock avoidance and progress guarantees.) Forwarded buffers are an artefact of the short reads. Let me summarize what changed in v18: In v17, we didn't have real AIO yet, just I/O combining and pseudo-AIO with POSIX_FADV_WILLNEED, so StartReadBuffers() pinned buffers and possibly shortened the read for reasons #1 and #2. This meant that it pinned at most one extra buffer that terminated the operation, and it used a bit of a kludge do deal with that: for reason #1 you get the buffer, and for reason #2, it pretended that block was part of the IO, it just didn't include it in the actual read operation. This was simple, but not scalable to later work. Then WaitReadBuffers() tried to acquire the BM_IO_IN_PROGRESS flags for all the blocks, and "hid" all four split reasons by looping: it skipped buffers that had become BM_VALID in the meantime (someone else read them) and waited for BM_IO_IN_PROGRESS to become BM_VALID (or not if another backend's read failed and this backend could now try), and then md.c internally looped to handle reason #4. WaitReadBuffers() *had* to acquire BM_IO_IN_PROGRESS in this second phase when I/O was really synchronous and StartReadBuffers() was just feeding advice to the kernel: if we acquired it sooner then other backends might wait for this backend to reach WaitReadBuffers(), which could deadlock, and we had no infrastructure to deal with that. In other words, I/Os were already split for all the same reasons, but #3 and #4 were handled at the later WaitReadBuffers() stage and in secret. In v18, the handling of reasons #3 and #4 moved into StartReadBuffers(). By definition we have to try to acquire BM_IO_IN_PROGRESS at that earlier stage now, because the IO really is in progress, and the deadlock risk is gone, because any backend can drive an IO started by any other backend to completion using the associated PgAioHandle. This increased the pin management problem: StartReadBuffers() can acquire a lot of pins, up to 128 (io_combine_limit=3D1MB) and then discover a reason to split the IO. So what do do with all those pins? A simple answer would be: unpin all the buffers after the split point. We didn't want to do that, because read_stream.c is almost certainly going to call StartReadBuffers() for the rest of the blocks (and possibly more that it now wants after those ones). So we wanted a way to transfer them from one StartReadBuffer() call to the following one, avoiding an unpin-and-repin dance. This was quite a difficult API design question, and this isn't the only design we considered, but we settled on making the buffer array that you give to StartReadBuffers() an input/output array for transporting these extra pinned buffers between calls, while in v17 it was a output-only array. In v18, you have to fill it up with InvalidBuffer initially. If it wants to report that it has pinned buffers but then excluded them from a read that is now a short read, it writes them into that array, but it doesn't count them in the *nblocks value that it gives you. The term I dreamed up for that is "forwarded" buffers. It is the caller's job to pass them in as input to the next StartReadBuffers() call, or if for whatever reason it doesn't want to continue, it has to unpin them. Next, let me describe how read_stream.c manages its circular buffer queue: |<------------- queue_size ------------>| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | | | | |0|1|2|3|4|5|6|7|8| | | | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ^ ^ | | oldest_buffer_index next_buffer_index oldest_buffer_index is the consumer end of the queue, and any associated IO has to be finished before that buffer can be return by read_stream_next_buffer(). next_buffer_index is the producer end of the queue, where IOs are started. StartReadBuffers() takes pointer to an array of buffers of the size of the requested read, so read_stream.c gives it the address of that part of its queue. This means that if StartReadBuffer() forwards some buffers because of a split, we don't have to do anything at all, because we advance next_buffer_index to the location of the start of the next operation, and that is where the forwarded buffers landed, ready for the next call to StartReadBuffers(): |<------------- queue_size ------------>| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | | | | |0|1|2|3|4|5|6|7|8|9|A| | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ^ ^ | | oldest_buffer_index next_buffer_index Well, not quite nothing, we do count how many there are in stream->forwarded_buffers. That's used by the code that limits lookahead when an unrealistically small buffer pool reports that we hold too many pins already. Those are pins that this backend holds, so they are not counted as extra pins that StartReadBuffer() would acquire. That's a nice way for successive StartReadBuffers() calls to communicate, without having to copy them to and from some other storage between calls. In exchange for that convenience, we also need to fill the entries with InvalidBuffer initially so they don't look like forwarded buffers. We don't want to do that up front: the queue might be up to {effective,maintenance}_io_concurrency * io_combine_limit in size (highest possible value: 128,000), and if this is a small or perfectly cached scan then we will never use most of the queue. So, we: * initialize them on the first go around the queue only, tracked in stream->initialized_buffers * invalidate entries consumed by read_stream_next_buffer() in preparation for reuse The fast path repeatedly uses the same queue entry instead of advancing oldest_buffer_index and next_buffer_index, so it doesn't need to invalidate queue entries, saving an instruction and reducing cache line pollution. Now I am getting pretty close to explaining the bug, but there is one final complication... Sometimes a StartReadBuffers() call spans past the physical end of the queue. It needs to wrap around. We could have chosen to give StartReadBuffers() an iov, iov_cnt style interface like preadv() so that we could use iov_cnt =3D 2 and locate some buffers at the physical end of the queue and the rest at the physical start of the queue, but StartReadBuffers()'s interface was complicated enough already. The solution I went with was to allocate extra array entries after the queue to allow for the maximum possible overflow if beginning a read into a buffer located in the final queue entry. The overflowing buffers are immediately copied to the physical start of the queue, ready for consumption in regular circular queue position. Note the twin positions for buffer 2 in this example: |<------------- queue_size ------------>|<--- overflow_size --->| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |2|3|4|5|6|7| | | | | | | | | | | | |0|1|2|3|4|5|6|7| | | | | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ^ ^ | | next_buffer_index oldest_buffer_index (Perhaps it would be clearer if overflow_size were a member variable in ReadStream instead of being open coded as io_combine_limit - 1 in a few places.) The non-fast-path invalidates the queue entry holding the buffer it is returning to the caller of read_stream_next_buffer(), as required to mark it as "not a forwarded buffer" when reused next time we go around the circular queue. If it's in the low physical entries that also might have a twin in the overflow zone, it must also be invalidated. The reason we keep both copies for a bit longer is that we don't know whether the following StartReadBuffers() will also be split, so we don't know whether it will also overflow, so we allow for any number of following StartReadBuffers() calls to overflow too OR move to the physical start of the queue. Now I can finally explain the bug :-) The fast-path doesn't have to invalidate queue entries, because it spins on the same queue entry repeatedly as an optimisation. Unfortunately I forgot to consider that there might be a copy of it in the overflow zone. If there is a multi-block StartReadBuffers() that spans into the overflow zone but is split, a continuing StartReadBuffers(), and then all the requirements for entering fast path mode are met, the fast path begins to spin on one buffer entry repeatedly near the physical start of the queue, and it initially holds a buffer that was copied from the overflow zone. Nothing goes wrong yet, but then we leave fast path mode because we need to do I/O, and begin to cycle around the queue again in the regular non-fast path code. Eventually we have another StartReadBuffers() call that spans into the overflow zone, and bufmgr.c sees a stale buffer number that we failed to clear out earlier. It thinks it's a forwarded buffer. Forwarded buffers must be pinned and map to blocknum + i, and it asserts that to avoid corrupting the buffer pool by overwriting a random buffer, which failed due to the bug. I don't want the fast path to do extra steps. The solution I found was to clear the overflow entry *before* entering the fast path. We already know which buffer entry the fast path is going to use, and we also know that there is no I/O split needing to be continued as part of the conditions of entry to the fast path, so the solution is simply to clear any corresponding overflow entry, very slightly ahead of the usual time. --0000000000008d806a063bd18d63 Content-Type: application/octet-stream; name="v2-0001-Fix-bug-in-read_stream.c-s-split-IO-handling.patch" Content-Disposition: attachment; filename="v2-0001-Fix-bug-in-read_stream.c-s-split-IO-handling.patch" Content-Transfer-Encoding: base64 Content-ID: X-Attachment-Id: f_me27gv630 RnJvbSA2MTlhZWExMDQzNTFjZjFiYzEwM2ZhNzE2MWU2MmU5M2JiYWNhZmE0IE1vbiBTZXAgMTcg MDA6MDA6MDAgMjAwMQpGcm9tOiBUaG9tYXMgTXVucm8gPHRob21hcy5tdW5yb0BnbWFpbC5jb20+ CkRhdGU6IFR1ZSwgNSBBdWcgMjAyNSAxNDozNTowOSArMTIwMApTdWJqZWN0OiBbUEFUQ0ggdjJd IEZpeCBidWcgaW4gcmVhZF9zdHJlYW0uYydzIHNwbGl0IElPIGhhbmRsaW5nLgoKSWYgYSBjaXJj dWxhciBxdWV1ZSB3cmFwYXJvdW5kLCBhIG11bHRpLWJsb2NrIElPIHNwbGl0IGFuZCBhIHRyYW5z aXRpb24KdG8gdGhlIGZhc3QgcGF0aCBoYXBwZW5lZCBpbiBhIGNlcnRhaW4gc2VxdWVuY2UsIGEg YnVmZmVyIGZvcndhcmRlZCBmcm9tCm9uZSBTdGFydFJlYWRCdWZmZXJzKCkgY2FsbCB0byBuZXh0 IHdvdWxkIG5vdCBiZSBjbGVhcmVkIG91dC4gIFRoYXQKY291bGQgY29uZnVzZSBhIGxhdGVyIHF1 ZXVlLXdyYXBwaW5nIFN0YXJ0UmVhZEJ1ZmZlcnMoKSBjYWxsIGJ5IHBhc3NpbmcKaXQgYSByYW5k b20gYnVmZmVyLgoKRml4LCBhbmQgYWRkIHNvbWUgdGlnaHRlciBhbmQgZWFybGllciBhc3NlcnRp b25zIGFib3V0IHRoZSBsYXlvdXQgYW5kCmlkZW50aXR5IG9mIGJ1ZmZlcnMgZm9yd2FyZGVkIGFj cm9zcyBJTyBzcGxpdHMsIGFuZCB0aGUgZm9sbG93aW5nCmVudHJpZXMgdGhhdCBzaG91bGQgaGF2 ZSBiZWVuIGNsZWFyZWQgYmVmb3JlIHJldXNlLgoKRGVmZWN0IGluIGNvbW1pdCBlZDBiODdjYS4K CkJ1ZzogMTkwMDYKQmFja3BhdGNoLXRocm91Z2g6IDE4ClJlcG9ydGVkLWJ5OiBBbGV4YW5kZXIg TGFraGluIDxleGNsdXNpb25AZ21haWwuY29tPgpSZXZpZXdlZC1ieTogVG9tIExhbmUgPHRnbEBz c3MucGdoLnBhLnVzPgpSZXZpZXdlZC1ieTogTWljaGFlbCBQYXF1aWVyIDxtaWNoYWVsQHBhcXVp ZXIueHl6PgpSZXZpZXdlZC1ieTogWHVuZW5nIFpob3UgPHh1bmVuZ3pob3VAZ21haWwuY29tPgpE aXNjdXNzaW9uOiBodHRwczovL3Bvc3Rnci5lcy9tLzE5MDA2LTgwZmNhYWY2OTAwMDM3N2UlNDBw b3N0Z3Jlc3FsLm9yZwotLS0KIHNyYy9iYWNrZW5kL3N0b3JhZ2UvYWlvL3JlYWRfc3RyZWFtLmMg fCAzNCArKysrKysrKysrKysrKysrKysrKysrKysrKysKIDEgZmlsZSBjaGFuZ2VkLCAzNCBpbnNl cnRpb25zKCspCgpkaWZmIC0tZ2l0IGEvc3JjL2JhY2tlbmQvc3RvcmFnZS9haW8vcmVhZF9zdHJl YW0uYyBiL3NyYy9iYWNrZW5kL3N0b3JhZ2UvYWlvL3JlYWRfc3RyZWFtLmMKaW5kZXggMGU3ZjU1 NTdmNWMuLjAzMWZkZTlmNGNiIDEwMDY0NAotLS0gYS9zcmMvYmFja2VuZC9zdG9yYWdlL2Fpby9y ZWFkX3N0cmVhbS5jCisrKyBiL3NyYy9iYWNrZW5kL3N0b3JhZ2UvYWlvL3JlYWRfc3RyZWFtLmMK QEAgLTI0NywxMiArMjQ3LDMzIEBAIHJlYWRfc3RyZWFtX3N0YXJ0X3BlbmRpbmdfcmVhZChSZWFk U3RyZWFtICpzdHJlYW0pCiAJQXNzZXJ0KHN0cmVhbS0+cGlubmVkX2J1ZmZlcnMgKyBzdHJlYW0t PnBlbmRpbmdfcmVhZF9uYmxvY2tzIDw9CiAJCSAgIHN0cmVhbS0+bWF4X3Bpbm5lZF9idWZmZXJz KTsKIAorI2lmZGVmIFVTRV9BU1NFUlRfQ0hFQ0tJTkcKIAkvKiBXZSBoYWQgYmV0dGVyIG5vdCBi ZSBvdmVyd3JpdGluZyBhbiBleGlzdGluZyBwaW5uZWQgYnVmZmVyLiAqLwogCWlmIChzdHJlYW0t PnBpbm5lZF9idWZmZXJzID4gMCkKIAkJQXNzZXJ0KHN0cmVhbS0+bmV4dF9idWZmZXJfaW5kZXgg IT0gc3RyZWFtLT5vbGRlc3RfYnVmZmVyX2luZGV4KTsKIAllbHNlCiAJCUFzc2VydChzdHJlYW0t Pm5leHRfYnVmZmVyX2luZGV4ID09IHN0cmVhbS0+b2xkZXN0X2J1ZmZlcl9pbmRleCk7CiAKKwkv KgorCSAqIFBpbm5lZCBidWZmZXJzIGZvcndhcmRlZCBieSBhIHByZWNlZGluZyBTdGFydFJlYWRC dWZmZXJzKCkgY2FsbCB0aGF0CisJICogaGFkIHRvIHNwbGl0IHRoZSBvcGVyYXRpb24gc2hvdWxk IG1hdGNoIHRoZSBsZWFkaW5nIGJsb2NrcyBvZiB0aGlzCisJICogZm9sbG93aW5nIFN0YXJ0UmVh ZEJ1ZmZlcnMoKSBjYWxsLgorCSAqLworCUFzc2VydChzdHJlYW0tPmZvcndhcmRlZF9idWZmZXJz IDw9IHN0cmVhbS0+cGVuZGluZ19yZWFkX25ibG9ja3MpOworCWZvciAoaW50IGkgPSAwOyBpIDwg c3RyZWFtLT5mb3J3YXJkZWRfYnVmZmVyczsgKytpKQorCQlBc3NlcnQoQnVmZmVyR2V0QmxvY2tO dW1iZXIoc3RyZWFtLT5idWZmZXJzW3N0cmVhbS0+bmV4dF9idWZmZXJfaW5kZXggKyBpXSkgPT0K KwkJCSAgIHN0cmVhbS0+cGVuZGluZ19yZWFkX2Jsb2NrbnVtICsgaSk7CisKKwkvKgorCSAqIENo ZWNrIHRoYXQgd2UndmUgY2xlYXJlZCB0aGUgcXVldWUvb3ZlcmZsb3cgZW50cmllcyBjb3JyZXNw b25kaW5nIHRvCisJICogdGhlIHJlc3Qgb2YgdGhlIGJsb2NrcyBjb3ZlcmVkIGJ5IHRoaXMgcmVh ZCwgdW5sZXNzIGl0J3MgdGhlIGZpcnN0IGdvCisJICogYXJvdW5kIGFuZCB3ZSBoYXZlbid0IGV2 ZW4gaW5pdGlhbGl6ZWQgdGhlbSB5ZXQuCisJICovCisJZm9yIChpbnQgaSA9IHN0cmVhbS0+Zm9y d2FyZGVkX2J1ZmZlcnM7IGkgPCBzdHJlYW0tPnBlbmRpbmdfcmVhZF9uYmxvY2tzOyArK2kpCisJ CUFzc2VydChzdHJlYW0tPm5leHRfYnVmZmVyX2luZGV4ICsgaSA+PSBzdHJlYW0tPmluaXRpYWxp emVkX2J1ZmZlcnMgfHwKKwkJCSAgIHN0cmVhbS0+YnVmZmVyc1tzdHJlYW0tPm5leHRfYnVmZmVy X2luZGV4ICsgaV0gPT0gSW52YWxpZEJ1ZmZlcik7CisjZW5kaWYKKwogCS8qIERvIHdlIG5lZWQg dG8gaXNzdWUgcmVhZC1haGVhZCBhZHZpY2U/ICovCiAJZmxhZ3MgPSBzdHJlYW0tPnJlYWRfYnVm ZmVyc19mbGFnczsKIAlpZiAoc3RyZWFtLT5hZHZpY2VfZW5hYmxlZCkKQEAgLTk3OSw2ICsxMDAw LDE5IEBAIHJlYWRfc3RyZWFtX25leHRfYnVmZmVyKFJlYWRTdHJlYW0gKnN0cmVhbSwgdm9pZCAq KnBlcl9idWZmZXJfZGF0YSkKIAkJc3RyZWFtLT5wZW5kaW5nX3JlYWRfbmJsb2NrcyA9PSAwICYm CiAJCXN0cmVhbS0+cGVyX2J1ZmZlcl9kYXRhX3NpemUgPT0gMCkKIAl7CisJCS8qCisJCSAqIFRo ZSBmYXN0IHBhdGggc3BpbnMgb24gb25lIGJ1ZmZlciBlbnRyeSByZXBlYXRlZGx5IGluc3RlYWQg b2YKKwkJICogcm90YXRpbmcgdGhyb3VnaCB0aGUgd2hvbGUgcXVldWUgYW5kIGNsZWFyaW5nIHRo ZSBlbnRyaWVzIGJlaGluZAorCQkgKiBpdC4gIElmIHRoZSBidWZmZXIgaXQgc3RhcnRzIHdpdGgg aGFwcGVuZWQgdG8gYmUgZm9yd2FyZGVkIGJldHdlZW4KKwkJICogU3RhcnRSZWFkQnVmZmVycygp IGNhbGxzIGFuZCBhbHNvIHdyYXBwZWQgYXJvdW5kIHRoZSBjaXJjdWxhciBxdWV1ZQorCQkgKiBw YXJ0d2F5IHRocm91Z2gsIHRoZW4gYSBjb3B5IGFsc28gZXhpc3RzIGluIHRoZSBvdmVyZmxvdyB6 b25lLCBhbmQKKwkJICogaXQgd29uJ3QgY2xlYXIgaXQgb3V0IGFzIHRoZSByZWd1bGFyIHBhdGgg d291bGQuICBEbyB0aGF0IG5vdywgc28KKwkJICogaXQgZG9lc24ndCBuZWVkIGNvZGUgZm9yIHRo YXQuCisJCSAqLworCQlpZiAoc3RyZWFtLT5vbGRlc3RfYnVmZmVyX2luZGV4IDwgc3RyZWFtLT5p b19jb21iaW5lX2xpbWl0IC0gMSkKKwkJCXN0cmVhbS0+YnVmZmVyc1tzdHJlYW0tPnF1ZXVlX3Np emUgKyBzdHJlYW0tPm9sZGVzdF9idWZmZXJfaW5kZXhdID0KKwkJCQlJbnZhbGlkQnVmZmVyOwor CiAJCXN0cmVhbS0+ZmFzdF9wYXRoID0gdHJ1ZTsKIAl9CiAjZW5kaWYKLS0gCjIuMzkuNSAoQXBw bGUgR2l0LTE1NCkKCg== --0000000000008d806a063bd18d63--