* refactor(shell): run volume.fsck purge once per volume, after all replicas
The purge step in findExtraChunksInVolumeServers was nested inside the
outer `for dataNodeId` loop, so it fired once per data-node iteration
rather than once total. Two consequences:
1. The replica-intersection safety net was broken. The code marks a fid
"found in all replicas" only after every replica has reported its
orphans, but the purge ran after the first data node already, so
fids contributed only by later replicas never got the `true` flag
in time. Without `-forcePurging` that meant some legitimate orphans
were never purged; with `-forcePurging` the flag was ignored so the
bug was hidden.
2. Visible output got noisy: "purging orphan data for volume X..."
printed 2-3 times per volume (N_datanodes * N_replicas RPCs to the
same locations) since purgeFileIdsForOneVolume already fans out to
every replica location via MasterClient.GetLocations.
Split the work into two explicit phases: collect orphans from every
replica first, then purge each volume once. Drop the per-replica loop
around purgeFileIdsForOneVolume since it already handles all replicas
internally. Keep the per-replica mark-writable loop (each replica's
readonly bit has to be flipped before the purge RPC fans out to it).
Also simplify the gating expression — `isSeveralReplicas &&
foundInAllReplicas` is redundant given the preceding `!isSeveralReplicas`
branch — and replace `!(X > 0)` with the more idiomatic `len(X) == 0`.
Related to #9116 follow-up on multiple fsck passes needed to fully
clean a volume.
* address review: per-replica readonly tracking, count-based intersection, defer-per-volume
Three issues raised on the v1:
1. The readonly cleanup stored a single isReadOnlyReplicas[volumeId]=bool
that flipped true if any replica was read-only, then the defer marked
every replica in serverReplicas[volumeId] read-only on exit. If a
volume had mixed replica modes (one RO, one RW), the originally-RW
replica ended up RO after fsck returned. Track read-only state per
replica in readOnlyServerReplicas[volumeId] and revert only those.
2. The defer inside the volumeId loop accumulated for the entire fsck
run, so every volume we processed stayed writable until the whole
command returned. Split the per-volume logic into purgeOneVolume so
the defers unwind between volumes.
3. The intersection logic used a sticky bool that treated "seen on any
2 of 3 replicas" as "seen on all replicas" — a 3+-replica volume
would get purged for fids only 2 replicas agreed on, which is what
-forcePurging is supposed to opt into. Switch to a count-based
map[fid]int compared against volumeReplicaCounts[volumeId], so we
only purge without -forcePurging when every replica agrees.
Also drop the now-unused serverReplicas map.