Skip to content

Commit bfcb401

Browse files
committed
Check that there are no changes during SR.scan
Currently, we are only checking that no VDIs have been removed during the SR scan performed by the SM plugin. However, there are situations where a VDI has been added, and if this VDI is not present in the list obtained from SR.scan, it will be forgotten. The checks only prevent this in the case where the VDI was added during the scan. There is still a TOCTOU situation if the issue happens after the scan, and there is room for that. Signed-off-by: Guillaume <[email protected]>
1 parent ec7baa3 commit bfcb401

File tree

1 file changed

+39
-14
lines changed

1 file changed

+39
-14
lines changed

ocaml/xapi/xapi_sr.ml

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,11 @@ let update_vdis ~__context ~sr db_vdis vdi_infos =
757757

758758
(* Perform a scan of this locally-attached SR *)
759759
let scan ~__context ~sr =
760+
let module RefSet = Set.Make (struct
761+
type t = [`VDI] Ref.t
762+
763+
let compare = Ref.compare
764+
end) in
760765
let open Storage_access in
761766
let task = Context.get_task_id __context in
762767
let module C = Storage_interface.StorageAPI (Idl.Exn.GenClient (struct
@@ -778,13 +783,42 @@ let scan ~__context ~sr =
778783
Db.VDI.get_records_where ~__context
779784
~expr:(Eq (Field "SR", Literal sr'))
780785
in
786+
let string_of_l lst =
787+
List.map (fun (_, v) -> v.vDI_uuid) lst |> String.concat ","
788+
in
781789
(* It is sufficient to just compare the refs in two db_vdis, as this
782790
is what update_vdis uses to determine what to delete *)
783791
let vdis_ref_equal db_vdi1 db_vdi2 =
784-
Listext.List.set_difference (List.map fst db_vdi1)
785-
(List.map fst db_vdi2)
786-
= []
792+
let refs1 = RefSet.of_list (List.map fst db_vdi1) in
793+
let refs2 = RefSet.of_list (List.map fst db_vdi2) in
794+
if RefSet.equal refs1 refs2 then
795+
true
796+
else
797+
let vdis_removed = RefSet.diff refs1 refs2 in
798+
let vdis_added = RefSet.diff refs2 refs1 in
799+
800+
( if not (RefSet.is_empty vdis_removed) then
801+
let removed =
802+
List.filter
803+
(fun (r, _) -> RefSet.mem r vdis_removed)
804+
db_vdi1
805+
in
806+
debug "%s VDIs removed during scan: %s" __FUNCTION__
807+
(string_of_l removed)
808+
) ;
809+
810+
( if not (RefSet.is_empty vdis_added) then
811+
let added =
812+
List.filter
813+
(fun (r, _) -> RefSet.mem r vdis_added)
814+
db_vdi2
815+
in
816+
debug "%s VDIs added during scan: %s" __FUNCTION__
817+
(string_of_l added)
818+
) ;
819+
false
787820
in
821+
788822
let db_vdis_before = find_vdis () in
789823
let vs, sr_info =
790824
C.SR.scan2 (Ref.string_of task)
@@ -793,17 +827,8 @@ let scan ~__context ~sr =
793827
let db_vdis_after = find_vdis () in
794828
if limit > 0 && not (vdis_ref_equal db_vdis_before db_vdis_after)
795829
then (
796-
debug
797-
"%s detected db change while scanning, before scan vdis %s, \
798-
after scan vdis %s, retry limit left %d"
799-
__FUNCTION__
800-
(List.map (fun (_, v) -> v.vDI_uuid) db_vdis_before
801-
|> String.concat ","
802-
)
803-
(List.map (fun (_, v) -> v.vDI_uuid) db_vdis_after
804-
|> String.concat ","
805-
)
806-
limit ;
830+
debug "%s detected db change while scanning, retry limit left %d"
831+
__FUNCTION__ limit ;
807832
(scan_rec [@tailcall]) (limit - 1)
808833
) else if limit = 0 then
809834
Helpers.internal_error "SR.scan retry limit exceeded"

0 commit comments

Comments
 (0)