All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Karan Tilak Kumar" <kartilak@cisco.com>
Cc: linux-scsi@vger.kernel.org
Subject: Re: [PATCH v5 06/13] scsi: fnic: Add the NVMe/FC transport path
Date: Wed, 24 Jun 2026 05:12:54 +0000	[thread overview]
Message-ID: <20260624051254.B19751F000E9@smtp.kernel.org> (raw)
In-Reply-To: <20260624044334.3079-7-kartilak@cisco.com>

Thank you for your contribution! Sashiko AI review found 11 potential issue(s) to consider:

New issues:
- [Critical] Premature freeing of I/O tags during ABTS completion leads to tag reuse and data corruption.
- [High] Non-sync timer cancel in nvfnic_ls_rsp_recv leads to Use-After-Free of lsreq and fnic structures.
- [High] Asynchronous deletion timeouts cause Use-After-Free of tport and lport objects.
- [High] Missing cancellation of nvme_io_cmpl_work allows a work item to execute after the fnic structure is freed.
- [High] Leaked LS requests and active timers when remote port unregistration fails.
- [High] A failed abort request combined with a concurrent completion causes an I/O to hang forever.
- [High] Concurrent I/O completion causes Use-After-Free of fcp_req when _terminate_tport_ios drops the lock.
- [High] Accessing lsreq->private after dropping the lock in nvfnic_ls_req_abort causes Use-After-Free.
- [Medium] Uninitialized sgl_mapped state leads to unbalanced dma_unmap_single calls for zero-length NVMe requests.
- [Medium] Permanent reference leak of the PCI device (pdev->dev) during localport registration.

Pre-existing issues:
- [High] Dropping the spinlock inside list_for_each_entry_safe() permits concurrent list corruption.
--

commit 78a3171c3dafe1bc2912707cce47ec94e43860f3
Author: Karan Tilak Kumar <kartilak@cisco.com>

scsi: fnic: Add the NVMe/FC transport path

This patch introduces the NVMe/FC transport path for the fnic driver.
It includes local and remote port registration, I/O submission, tag
management, abort handling, and cleanup logic.

Closes: https://lore.kernel.org/oe-kbuild-all/202605280430.wTYAqI3A-lkp@intel.com/
Closes: https://lore.kernel.org/oe-kbuild-all/202605280619.pmobiDWp-lkp@intel.com/
Closes: https://lore.kernel.org/oe-kbuild-all/202605280519.Jd4fmgAZ-lkp@intel.com/

> diff --git a/drivers/scsi/fnic/fnic_main.c b/drivers/scsi/fnic/fnic_main.c
> index 0d7828be244da..df8c8ebbc32b9 100644
> --- a/drivers/scsi/fnic/fnic_main.c
> +++ b/drivers/scsi/fnic/fnic_main.c
[ ... ]
> @@ -1215,6 +1286,11 @@ static void fnic_remove(struct pci_dev *pdev)
>  	 */
>  	fnic_cleanup(fnic);
>  
> +	if (IS_FNIC_NVME_INITIATOR(fnic)) {
> +		sbitmap_free(&fnic->nvfnic_tag_map);
> +		fnic_free_ioreq_tables_mq(fnic);
> +	}
> +
>  	spin_lock_irqsave(&fnic_list_lock, flags);
>  	list_del(&fnic->list);
>  	spin_unlock_irqrestore(&fnic_list_lock, flags);

[Severity: High]
Does this code leave work items running after the fnic structure is freed?

Looking at fnic_remove(), we call fnic_cleanup(fnic) which drains the hardware
completion queues, potentially finding completions and queueing work items via
queue_work(fnic_cmpl_queue, &fnic->nvme_io_cmpl_work).

Since there is no cancel_work_sync() before kfree(fnic), could a re-queued
work item execute after the memory is freed and cause a use-after-free crash?

> diff --git a/drivers/scsi/fnic/fnic_nvme.c b/drivers/scsi/fnic/fnic_nvme.c
> new file mode 100644
> index 0000000000000..8374464e4fcc8
> --- /dev/null
> +++ b/drivers/scsi/fnic/fnic_nvme.c
[ ... ]
> +void nvfnic_dma_unmap_sgl(struct fnic *fnic, struct fnic_io_req *io_req)
> +{
> +	if (io_req->sgl_mapped) {
> +		dma_unmap_single(&fnic->pdev->dev, io_req->sgl_list_pa,
> +				 sizeof(io_req->sgl_list[0]) * io_req->sgl_cnt,
> +				 DMA_TO_DEVICE);
> +		io_req->sgl_mapped = 0;
> +		io_req->sgl_list_pa = 0;
> +	}
> +}

[Severity: Medium]
Could this lead to an unbalanced DMA unmap?

In nvfnic_fcpio_send(), io_req->sgl_mapped is not explicitly zeroed during
initialization:

nvfnic_fcpio_send() {
    ...
    io_req->sgl_list_pa = 0;
    io_req->wq = hw_queue_handle;
    ...
}

If a previous I/O set sgl_mapped to 1, and the current I/O has zero length,
the mapping step is skipped but sgl_mapped might still be 1. Will this cause
nvfnic_dma_unmap_sgl() to unmap a 0 address with 0 length?

[ ... ]
> +void nvfnic_fcpio_nvme_fast_cmpl_handler(struct fnic *fnic,
> +				       struct fcpio_fw_req *desc)
> +{
> +	u8 type;
> +	u8 hdr_status;
> +	struct fcpio_tag ftag;
> +	u32 id;
> +	struct fnic_io_req *io_req;
[ ... ]
> +	fcpio_header_dec(&desc->hdr, &type, &hdr_status, &ftag);
> +	fcpio_tag_id_dec(&ftag, &id);
> +	tag = id & FNIC_TAG_MASK;
> +
> +	if (tag >= fnic->fnic_max_tag_id) {
> +		FNIC_NVME_DBG(KERN_ERR, fnic,
> +			    "Tag out of range tag: 0x%x hdr status: %s\n", tag,
> +			    fnic_fcpio_status_to_str(hdr_status));
> +		return;
> +	}
> +	spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags);
> +
> +	io_req = nvfnic_find_io_req_by_tag(fnic, tag);
> +
> +	WARN_ON_ONCE(!io_req);
> +	if (!io_req) {
[ ... ]
> +	/* firmware completed the io */
> +	io_req->io_completed = 1;
> +	if (io_req->cmd_state == FNIC_IOREQ_ABTS_PENDING) {
> +		/*
> +		 * set the FNIC_IO_DONE so that this doesn't get
> +		 * flagged as 'out of order' if it was not aborted
> +		 */
> +		io_req->cmd_flags |= FNIC_IO_DONE;
> +		io_req->cmd_flags |= FNIC_IO_ABTS_PENDING;
> +		if (hdr_status == FCPIO_ABORTED)
> +			io_req->cmd_flags |= FNIC_IO_ABORTED;
> +		spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags);
> +
> +		FNIC_NVME_DBG(KERN_INFO, fnic,
> +			      "icmnd abts hdr:%d %s tag:0x%x io:%p",
> +			      hdr_status, fnic_fcpio_status_to_str(hdr_status),
> +			      id, io_req);
> +		return;
> +	}

[Severity: High]
Could an I/O hang indefinitely here if the abort request fails to queue?

When aborting an I/O, nvfnic_fcpio_abort() sets the state to
FNIC_IOREQ_ABTS_PENDING and drops the lock. If a hardware completion arrives
during this window, nvfnic_fcpio_nvme_fast_cmpl_handler() observes
ABTS_PENDING and intentionally drops the completion.

If the subsequent nvfnic_queue_abort_io_req() call fails, the state is
reverted back to FNIC_IOREQ_CMD_PENDING. Because the completion was
permanently discarded and the hardware was not told to abort, does this
leave the I/O hanging forever?

[ ... ]
> +void nvfnic_fcpio_nvme_itmf_cmpl_handler(struct fnic *fnic,
> +				       struct fcpio_fw_req *desc)
> +{
> +	u8 type;
> +	u8 hdr_status;
[ ... ]
> +	/* If the status is IO not found consider it as success.
> +	 * NVME sends abort even if rport is down in which case
> +	 * we will get FCPIO_TIMEOUT. Consider this as success.
> +	 */
> +	if ((hdr_status == FCPIO_IO_NOT_FOUND) ||
> +	    (hdr_status == FCPIO_TIMEOUT) ||
> +	    (hdr_status == FCPIO_ITMF_REJECTED))
> +		io_req->abts_state = FCPIO_SUCCESS;
> +
> +	io_req->cmd_flags |= FNIC_IO_ABT_TERM_DONE;
> +
> +
> +	io_req->fcp_req->transferred_length = 0;
> +	io_req->fcp_req->rcv_rsplen = 0;
> +	if (io_req->abts_state == FCPIO_SUCCESS)
> +		io_req->fcp_req->status = NVME_SC_ABORT_REQ;
> +	else
> +		io_req->fcp_req->status = NVME_SC_INTERNAL;
> +
> +	nvfnic_release_nvme_ioreq_buf(iport, io_req);
> +	if (io_req->done)
> +		io_req->done(io_req);
> +	spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags);
> +}

[Severity: Critical]
Does this allow a tag to be reused before the hardware has actually completed
the original request, potentially corrupting a new I/O?

When an ITMF (abort) completion times out (FCPIO_TIMEOUT), the tag is
forcefully freed via io_req->done() and can be reallocated to a new request.
Since the driver matches completions only by the tag without a generation
count, if the late completion for the original I/O arrives, will
nvfnic_fcpio_nvme_fast_cmpl_handler() match it to the new request and
prematurely complete it with stale data?

[ ... ]
> +bool _terminate_tport_ios(struct sbitmap *map, unsigned int tag,
> +				       void *data)
> +{
> +	struct fnic_tport_s *tport = data;
> +	struct fnic_iport_s *iport = tport->iport;
> +	struct fnic *fnic = iport->fnic;
> +	struct fnic_io_req *io_req;
> +	struct nvmefc_fcp_req *fcp_req = NULL;
> +	struct nvme_fc_local_port *lport = iport->nv_lport;
> +	struct nvme_fc_remote_port *rport = tport->nv_rport;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&fnic->fnic_lock, flags);
> +	io_req = fnic->sw_copy_wq[0].io_req_table[tag];
> +	if (!io_req) {
> +		spin_unlock_irqrestore(&fnic->fnic_lock, flags);
> +		return true;
> +	}
> +
> +	if (io_req->tport != tport) {
> +		spin_unlock_irqrestore(&fnic->fnic_lock, flags);
> +		return true;
> +	}
> +
> +	FNIC_NVME_DBG(KERN_INFO, fnic,
> +		      "Terminate tag: 0x%x (tport fcid 0x%x)\n",
> +		      io_req->tag, io_req->tport->fcid);
> +	fcp_req = io_req->fcp_req;
> +	spin_unlock_irqrestore(&fnic->fnic_lock, flags);
> +
> +	nvfnic_fcpio_abort(lport, rport, NULL, fcp_req);
> +	return true;
> +}

[Severity: High]
Can fcp_req be accessed after it has been freed?

Here, fcp_req is extracted from io_req under the lock. The lock is then dropped
without modifying io_req->cmd_state. During this unlocked window, a concurrent
hardware completion could run, process the I/O, invoke io_req->done(), and
return the request to the midlayer, which might free or reuse fcp_req.

When nvfnic_fcpio_abort() is subsequently called, could it dereference the
now-freed fcp_req structure?

[ ... ]
> +void nvfnic_nvme_zero_devloss_tports(struct fnic *fnic)
> +{
> +	struct fnic_tport_s *tport, *next;
> +
> +	spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags);
> +	list_for_each_entry_safe(tport, next, &fnic->iport.tport_list, links) {
> +		if (tport->flags & FNIC_FDLS_NVME_REGISTERED) {
> +			spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags);
> +			nvme_fc_set_remoteport_devloss(tport->nv_rport, 0);
> +			spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags);
> +		}
> +	}
> +	spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags);
> +}

[Severity: High]
This is a pre-existing issue, but dropping the lock inside
list_for_each_entry_safe() allows concurrent list corruption.

list_for_each_entry_safe() protects against deletion of the *current* entry
by caching the 'next' pointer. However, while the fnic_lock is dropped,
concurrent threads can remove and free the cached 'next' entry from the list.

When the loop resumes and accesses 'next', does this result in a
use-after-free? This pattern also exists in fnic_delete_fcp_tports().

[ ... ]
> +void nvfnic_ls_rsp_recv(struct fnic_iport_s *iport,
> +		struct fc_frame_header *fchdr, int len)
> +{
> +	uint8_t *fcid;
[ ... ]
> +	nvfnic_ls_req->state = FNIC_LS_REQ_CMD_COMPLETE;
> +
> +	list_del_init(&nvfnic_ls_req->list);
> +	lsreq->private = NULL;
> +	fdls_free_oxid(iport, oxid, &nvfnic_ls_req->oxid);
> +	timer_delete(&nvfnic_ls_req->ls_req_timer);
> +
> +	if (status == 0) {
> +		FNIC_NVME_DBG(KERN_DEBUG, fnic,
> +			      "tport:0x%x lsreq:0x%x completed\n",
> +			      tport_fcid, oxid);
> +
> +		/* Copy the Response */
> +		memcpy(lsreq->rspaddr, (uint8_t *)fchdr + sizeof(*fchdr),
> +		       rsp_len);
> +	}
> +
> +	spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags);
> +	lsreq->done(lsreq, status);
> +	spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags);
> +}

[Severity: High]
Could the timer callback race with lsreq->done() and cause a use-after-free?

Because timer_delete() is used instead of timer_delete_sync(), the timer
callback might be concurrently executing on another CPU spinning on fnic_lock.
When the lock is dropped here, lsreq->done() is called, returning ownership
to the NVMe FC midlayer which frees lsreq.

Once the lock is released, won't the spinning timer callback wake up and
dereference the freed lsreq memory?

[ ... ]
> +void nvfnic_ls_req_abort(struct nvme_fc_local_port *lport,
> +		   struct nvme_fc_remote_port *rport,
> +		   struct nvmefc_ls_req *lsreq)
> +{
> +	struct fnic_iport_s *iport = lport->private;
> +	struct fnic *fnic = iport->fnic;
> +	struct fnic_tport_s *tport;
> +	struct nvfnic_ls_req *nvfnic_ls_req;
> +	uint16_t oxid;
> +	int timeout;
> +
> +	spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags);
[ ... ]
> +	nvfnic_ls_req->state = FNIC_LS_REQ_CMD_ABTS_STARTED;
> +	spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags);
> +	timer_delete_sync(&nvfnic_ls_req->ls_req_timer);
> +
> +	spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags);
> +	nvfnic_ls_req = lsreq->private;
> +
> +	if ((nvfnic_ls_req == NULL) ||
> +	    (nvfnic_ls_req->state == FNIC_LS_REQ_CMD_ABTS_PENDING)) {

[Severity: High]
Does this dereference lsreq->private after the lock is reacquired, when lsreq
might already be freed?

The fnic_lock is dropped to call timer_delete_sync(). During this window,
nvfnic_terminate_tport_ls_reqs() could process the request and invoke
lsreq->done(), returning it to the midlayer which then frees it.

When the lock is reacquired, nvfnic_ls_req = lsreq->private accesses the freed
lsreq memory. Can this result in a use-after-free crash?

[ ... ]
> +void nvfnic_delete_tport(struct fnic_iport_s *iport,
> +						struct fnic_tport_s *tport,
> +						unsigned long flags)
> +{
> +	struct fnic *fnic = iport->fnic;
> +	int ret;
> +	unsigned int time_wait = FNIC_NVME_TPORT_REMOVE_WAIT;
> +	unsigned int time_remain;
> +	DECLARE_COMPLETION_ONSTACK(tm_done);
> +	unsigned int fcid;
> +	int count = 0;
> +
> +	if (!tport)
> +		return;
> +
> +	fcid = tport->fcid;
> +	fdls_set_tport_state(tport, FDLS_TGT_STATE_OFFLINE);
> +
> +	FNIC_NVME_DBG(KERN_DEBUG, fnic,
> +			"0x%x: scheduled deletion for tport: 0x%x\n",
> +		    iport->fcid, tport->fcid);
> +
> +	if (!(tport->flags & FNIC_FDLS_NVME_REGISTERED)) {
> +		FNIC_NVME_DBG(KERN_ERR, fnic,
> +			"0x%x: tport: 0x%x not registered. Freeing\n",
> +		    iport->fcid, tport->fcid);
> +		list_del(&tport->links);
> +		kfree(tport);
> +		return;
> +	}
> +
> +	tport->tport_del_done = &tm_done;
> +
> +	tport->flags |= FNIC_FDLS_TPORT_DELETED;
> +	spin_unlock_irqrestore(&fnic->fnic_lock, flags);
> +	ret = nvme_fc_unregister_remoteport(tport->nv_rport);
> +	if (ret) {
> +		FNIC_NVME_DBG(KERN_ERR, fnic,
> +			    "tport: 0x%x unregister failed %d\n",
> +			    tport->fcid, ret);
> +		spin_lock_irqsave(&fnic->fnic_lock, flags);
> +		tport->tport_del_done = NULL;
> +		list_del(&tport->links);
> +		kfree(tport);
> +		return;
> +	}

[Severity: High]
Does this leak pending LS requests and leave their timers active?

If nvme_fc_unregister_remoteport() fails, kfree(tport) is called directly.
This bypasses nvfnic_terminate_tport_ls_reqs(), which leaks all nvfnic_ls_req
structures left on tport->ls_req_list.

Since their timers are never stopped, won't nvfnic_ls_req_timeout()
eventually fire and access the freed tport structure?

> +	time_remain = wait_for_completion_timeout(tport->tport_del_done,
> +				msecs_to_jiffies(time_wait));
> +
> +	FNIC_NVME_DBG(KERN_DEBUG, fnic,
> +		      "tport: 0x%x wait for deletion done\n",
> +		      tport->fcid);
> +
> +	spin_lock_irqsave(&fnic->fnic_lock, flags);
> +	tport->tport_del_done = NULL;
> +
> +	if (!time_remain) {
> +		FNIC_NVME_DBG(KERN_ERR, fnic,
> +			    "tport: 0x%x nvme midlayer completion timed out\n",
> +			    tport->fcid);
> +
> +		spin_unlock_irqrestore(&fnic->fnic_lock, flags);
> +		nvfnic_cleanup_tport_io(fnic, tport);
> +		spin_lock_irqsave(&fnic->fnic_lock, flags);
> +		tport->flags &= ~FNIC_FDLS_NVME_TPORT_CLEANUP_PENDING;
> +		list_del(&tport->links);
> +		kfree(tport);

[Severity: High]
If this timeout expires, will the NVMe FC midlayer still call the
remoteport_delete callback and cause a use-after-free?

When the timeout expires, the driver forcefully calls kfree(tport). However,
the NVMe FC midlayer retains pointers to these structures and will eventually
execute the asynchronous remoteport_delete callback.

When nvfnic_remote_port_delete() is called later, it dereferences
rport->private. Since tport was already freed here, does this lead to a
use-after-free crash?

[ ... ]
> +int nvfnic_add_lport(struct fnic *fnic)
> +{
> +	struct nvme_fc_port_info pinfo;
> +	struct fnic_iport_s *iport = &fnic->iport;
> +	int ret = 0;
[ ... ]
> +		ret = nvme_fc_register_localport(&pinfo, iport->nv_tmpl,
> +						 get_device(&fnic->pdev->dev),
> +						 &iport->nv_lport);
> +		if (ret) {
> +			FNIC_NVME_DBG(KERN_ERR, fnic,
> +					"Failed to add wwpn: 0x%llx ret: %d\n",
> +					iport->wwpn, ret);
> +			kfree(iport->nv_tmpl);
> +			return ret;
> +		}

[Severity: Medium]
Does this create a permanent reference leak of the PCI device?

The get_device(&fnic->pdev->dev) call takes a reference that is passed to
the NVMe FC midlayer, which internally takes its own reference when registering
the localport and puts it upon unregistration.

Because this driver-level get_device() is never balanced with a put_device()
call by fnic, will this leak prevent PCI device teardown or hot-unplug?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260624044334.3079-1-kartilak@cisco.com?part=6

  reply	other threads:[~2026-06-24  5:12 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-24  4:43 [PATCH v5 00/13] Introduce functionality for NVMe initiator Karan Tilak Kumar
2026-06-24  4:43 ` [PATCH v5 01/13] scsi: fnic: Make debug logging protocol independent Karan Tilak Kumar
2026-06-24  5:08   ` sashiko-bot
2026-06-24  4:43 ` [PATCH v5 02/13] scsi: fnic: Use fnic_num for non-SCSI identifiers Karan Tilak Kumar
2026-06-24  5:11   ` sashiko-bot
2026-06-24  4:43 ` [PATCH v5 03/13] scsi: fnic: Decode firmware role configuration Karan Tilak Kumar
2026-06-24  4:43 ` [PATCH v5 04/13] scsi: fnic: Advertise NVMe initiator service parameters Karan Tilak Kumar
2026-06-24  4:43 ` [PATCH v5 05/13] scsi: fnic: Add FDLS role handling for NVMe initiators Karan Tilak Kumar
2026-06-24  5:18   ` sashiko-bot
2026-06-24  4:43 ` [PATCH v5 06/13] scsi: fnic: Add the NVMe/FC transport path Karan Tilak Kumar
2026-06-24  5:12   ` sashiko-bot [this message]
2026-06-24  4:43 ` [PATCH v5 07/13] scsi: fnic: Route completions and resets by initiator role Karan Tilak Kumar
2026-06-24  5:11   ` sashiko-bot
2026-06-24  4:43 ` [PATCH v5 08/13] scsi: fnic: Handle NVMe LS frames in FDLS Karan Tilak Kumar
2026-06-24  5:13   ` sashiko-bot
2026-06-24  4:43 ` [PATCH v5 09/13] scsi: fnic: Send NVMe LS requests through FDLS Karan Tilak Kumar
2026-06-24  5:11   ` sashiko-bot
2026-06-24  4:43 ` [PATCH v5 10/13] scsi: fnic: Abort timed-out NVMe LS requests Karan Tilak Kumar
2026-06-24  5:13   ` sashiko-bot
2026-06-24  4:43 ` [PATCH v5 11/13] scsi: fnic: Track NVMe transport statistics Karan Tilak Kumar
2026-06-24  5:15   ` sashiko-bot
2026-06-24  4:43 ` [PATCH v5 12/13] scsi: fnic: Expose NVMe transport state in debugfs Karan Tilak Kumar
2026-06-24  5:17   ` sashiko-bot
2026-06-24  4:43 ` [PATCH v5 13/13] scsi: fnic: Bump up version number Karan Tilak Kumar

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260624051254.B19751F000E9@smtp.kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=kartilak@cisco.com \
    --cc=linux-scsi@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.