| // Copyright 2019 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Package removablemedia implements the testing sceanrio of arc.RemovableMedia |
| // test and its utilities. |
| package removablemedia |
| |
| import ( |
| "bytes" |
| "context" |
| "io/ioutil" |
| "os" |
| "path" |
| "path/filepath" |
| "strings" |
| "time" |
| |
| "chromiumos/tast/common/testexec" |
| "chromiumos/tast/ctxutil" |
| "chromiumos/tast/errors" |
| "chromiumos/tast/local/arc" |
| "chromiumos/tast/local/crosdisks" |
| "chromiumos/tast/testing" |
| ) |
| |
| // CreateZeroFile creates a file filled with size bytes of 0. |
| func CreateZeroFile(size int64, name string) (string, error) { |
| f, err := ioutil.TempFile("", name) |
| if err != nil { |
| return "", errors.Wrap(err, "failed to create an image file") |
| } |
| defer f.Close() |
| |
| if err := f.Truncate(size); err != nil { |
| os.Remove(f.Name()) // Ignore error. |
| return "", errors.Wrap(err, "failed to resize the image file") |
| } |
| |
| return f.Name(), nil |
| } |
| |
| // AttachLoopDevice attaches to loop device. |
| func AttachLoopDevice(ctx context.Context, path string) (string, error) { |
| b, err := testexec.CommandContext(ctx, "losetup", "-f", path, "--show").Output(testexec.DumpLogOnError) |
| if err != nil { |
| return "", errors.Wrap(err, "failed to attach to loop device") |
| } |
| return strings.TrimSpace(string(b)), nil |
| } |
| |
| // DetachLoopDevice detaches from loop device. |
| func DetachLoopDevice(ctx context.Context, devLoop string) error { |
| if err := testexec.CommandContext(ctx, "losetup", "-d", devLoop).Run(testexec.DumpLogOnError); err != nil { |
| return errors.Wrapf(err, "failed to detach from loop device at %s", devLoop) |
| } |
| |
| return nil |
| } |
| |
| // FormatVFAT formats the vfat file system. |
| func FormatVFAT(ctx context.Context, devLoop string) error { |
| if err := testexec.CommandContext(ctx, "mkfs", "-t", "vfat", "-F", "32", "-n", "MyDisk", devLoop).Run(testexec.DumpLogOnError); err != nil { |
| return errors.Wrap(err, "failed to format vfat file system") |
| } |
| return nil |
| } |
| |
| // Mount adds device to allowlist and mounts it. |
| func Mount(ctx context.Context, cd *crosdisks.CrosDisks, devLoop, name string) (mountPath string, retErr error) { |
| cleanupCtx := ctx |
| ctx, cancel := ctxutil.Shorten(ctx, time.Second) |
| defer cancel() |
| sysPath := path.Join("/sys/devices/virtual/block", path.Base(devLoop)) |
| if err := cd.AddDeviceToAllowlist(ctx, sysPath); err != nil { |
| return "", errors.Wrapf(err, "failed to add device %s to allowlist", sysPath) |
| } |
| defer func() { |
| if err := cd.RemoveDeviceFromAllowlist(cleanupCtx, sysPath); err != nil && retErr != nil { |
| retErr = errors.Wrapf(err, "failed to remove device %s from allowlist", sysPath) |
| } |
| }() |
| w, err := cd.WatchMountCompleted(ctx) |
| if err != nil { |
| return "", err |
| } |
| defer w.Close(ctx) |
| |
| if err := cd.Mount(ctx, devLoop, "", []string{"rw", "nodev", "noexec", "nosuid", "sync"}); err != nil { |
| return "", errors.Wrap(err, "failed to mount") |
| } |
| |
| path := filepath.Join("/media/removable", name) |
| for { |
| m, err := w.Wait(ctx) |
| if err != nil { |
| return "", errors.Wrap(err, "failed to mount") |
| } |
| if m.SourcePath == devLoop && m.MountPath == path { |
| // Target mount point is found. |
| if m.Status != crosdisks.MountErrorNone { |
| return "", errors.Errorf("failed to mount with status %d", m.Status) |
| } |
| return path, nil |
| } |
| } |
| } |
| |
| // Unmount unmounts the disk. |
| func Unmount(ctx context.Context, cd *crosdisks.CrosDisks, devLoop string) error { |
| if status, err := cd.Unmount(ctx, devLoop, []string{"lazy"}); err != nil { |
| return errors.Wrap(err, "failed to unmount") |
| } else if status != crosdisks.MountErrorNone { |
| return errors.Errorf("failed to unmount with status %d", status) |
| } |
| return nil |
| } |
| |
| // RunTest executes the testing scenario of arc.RemovableMedia. |
| func RunTest(ctx context.Context, s *testing.State, a *arc.ARC, testFile string) { |
| const ( |
| imageSize = 64 * 1024 * 1024 |
| diskName = "MyDisk" |
| ) |
| expected, err := ioutil.ReadFile(s.DataPath(testFile)) |
| if err != nil { |
| s.Fatalf("Failed to read %s: %v", testFile, err) |
| } |
| |
| // Set up a filesystem image. |
| image, err := CreateZeroFile(imageSize, "vfat.img") |
| if err != nil { |
| s.Fatal("Failed to create image: ", err) |
| } |
| defer os.Remove(image) |
| |
| devLoop, err := AttachLoopDevice(ctx, image) |
| if err != nil { |
| s.Fatal("Failed to attach loop device: ", err) |
| } |
| defer func() { |
| if err := DetachLoopDevice(ctx, devLoop); err != nil { |
| s.Error("Failed to detach from loop device: ", err) |
| } |
| }() |
| if err := FormatVFAT(ctx, devLoop); err != nil { |
| s.Fatal("Failed to format VFAT file system: ", err) |
| } |
| |
| // Mount the image via CrosDisks. |
| cd, err := crosdisks.New(ctx) |
| if err != nil { |
| s.Fatal("Failed to find crosdisks D-Bus service: ", err) |
| } |
| mountDir, err := Mount(ctx, cd, devLoop, diskName) |
| if err != nil { |
| s.Fatal("Failed to mount file system: ", err) |
| } |
| defer func() { |
| if err := Unmount(ctx, cd, devLoop); err != nil { |
| s.Error("Failed to unmount VFAT image: ", err) |
| } |
| }() |
| |
| if err := arc.WaitForARCRemovableMediaVolumeMount(ctx, a); err != nil { |
| s.Fatal("Failed to wait for the volume to be mounted in ARC: ", err) |
| } |
| |
| // Create a picture in the removable media. |
| tpath := filepath.Join(mountDir, testFile) |
| if err := ioutil.WriteFile(tpath, expected, 0644); err != nil { |
| s.Fatal("Failed to write a data file: ", err) |
| } |
| |
| // VolumeProvider should be able to read the file. |
| verify := func(uri, dumpPath string) error { |
| out, err := a.Command(ctx, "content", "read", "--uri", uri).Output(testexec.DumpLogOnError) |
| if err != nil { |
| return errors.Wrap(err, "failed to read the content") |
| } |
| |
| if !bytes.Equal(out, expected) { |
| if err := ioutil.WriteFile(dumpPath, out, 0644); err != nil { |
| s.Logf("Failed to dump the read content to %s: %v", dumpPath, err) |
| return errors.New("file content does not match with the original") |
| } |
| return errors.Errorf("file content does not match with the original (see %s for the read content)", dumpPath) |
| } |
| return nil |
| } |
| |
| uri := arc.VolumeProviderContentURIPrefix + path.Join(arc.RemovableMediaUUID, testFile) |
| if err := verify(uri, filepath.Join(s.OutDir(), testFile)); err != nil { |
| s.Fatalf("Failed to read the file via VolumeProvider using content URI %s: %v", uri, err) |
| } |
| } |