blob: ec72549a9425920206857f53c9b6439992b0424d [file] [log] [blame]
// Copyright 2020 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 drivefs
import (
"context"
"os"
"path"
"time"
"chromiumos/tast/errors"
"chromiumos/tast/local/cryptohome"
"chromiumos/tast/local/session"
"chromiumos/tast/local/sysutil"
"chromiumos/tast/testing"
)
const (
mountPointTimeout = 15 * time.Second // timeout waiting for CrosDisks to mount Drivefs.
fuseIoTimeout = 40 * time.Second // timeout waiting for the FUSE to be operational.
restartDelay = 4 * time.Second // delay waiting for DriveFS to restart.
)
// WaitForDriveFs checks that the Drivefs mount is ready for IO.
// The username supplied must come from a user logged in using GAIA login.
func WaitForDriveFs(ctx context.Context, username string) (string, error) {
// Attempt to normalize the logged in user
normUser, err := session.NormalizeEmail(username, true)
if err != nil {
return "", errors.Wrap(err, "failed to normalize user name")
}
// Check that cache folder was created by cryptohome.
homePath, err := cryptohome.UserPath(ctx, normUser)
if err != nil {
return "", errors.Wrap(err, "failed to get home path")
}
cachePath := path.Join(homePath, "GCache", "v2")
if dir, err := os.Stat(cachePath); !dir.IsDir() {
return "", errors.Wrapf(err, "failed as cache dir %s is missing", cachePath)
}
// It takes some time for request to mount Drive to be handled by CrosDisks
// that creates the mount point. Poll for a mount point until timeout.
mounts, err := waitForMatchingMount(ctx, mountPointTimeout, isDriveFs)
if err != nil {
return "", errors.Wrap(err, "failed with timeout while waiting for mountpoint creation")
}
if len(mounts) != 1 {
return "", errors.Wrapf(err, "failed one drivefs mount expected found %d. Mounts found: %v", len(mounts), mounts)
}
mountPath := mounts[0].MountPath
testing.ContextLog(ctx, "drivefs is mounted into ", mountPath)
// On a clean start DriveFS would fetch some settings from the server and will
// want to restart to apply them. Let's wait for a bit to allow it to do this.
testing.ContextLogf(ctx, "Waiting %v for drivefs to stabilize", restartDelay)
if testing.Sleep(ctx, restartDelay) != nil {
return "", errors.Wrap(err, "failed while waiting for drivefs to stabilize")
}
// We expect to find at least this folder in the mount point.
drivefsRoot := path.Join(mountPath, "root")
// As drivefs may not be fully initialized yet all access to the mount point
// may fail inside FUSE driver until the daemon is ready.
// Poll for stat to succeed in case the drivefs daemon is never ready due to
// some bug.
if err := waitForMountConnected(ctx, fuseIoTimeout, drivefsRoot); err != nil {
return "", errors.Wrap(err, "failed while waiting for stat")
}
dir, err := os.Stat(drivefsRoot)
if err != nil {
return "", errors.Wrapf(err, "failed trying to stat %s", drivefsRoot)
}
if !dir.IsDir() {
return "", errors.Wrapf(err, "failed with no root folder inside %s", mountPath)
}
return mountPath, nil
}
func findMatchingMount(matcher func(sysutil.MountInfo) bool) (matches []sysutil.MountInfo, err error) {
info, err := sysutil.MountInfoForPID(sysutil.SelfPID)
if err != nil {
return
}
for i := range info {
if matcher(info[i]) {
matches = append(matches, info[i])
}
}
return
}
func waitForMatchingMount(ctx context.Context, timeout time.Duration, matcher func(sysutil.MountInfo) bool) ([]sysutil.MountInfo, error) {
var matches []sysutil.MountInfo
var err error
testing.ContextLogf(ctx, "Waiting %v for a matching mount to appear", timeout)
err = testing.Poll(ctx, func(ctx context.Context) error {
matches, err = findMatchingMount(matcher)
if err != nil {
return errors.Wrap(err, "io error trying to list mounts")
}
if len(matches) == 0 {
return errors.Wrap(os.ErrNotExist, "matching mount was not found")
}
return nil
}, &testing.PollOptions{Timeout: timeout, Interval: time.Second})
return matches, err
}
func isDriveFs(info sysutil.MountInfo) bool {
return info.Fstype == "fuse.drivefs"
}
func waitForMountConnected(ctx context.Context, timeout time.Duration, path string) error {
testing.ContextLogf(ctx, "Waiting %v for mount to become connected", timeout)
return testing.Poll(ctx, func(ctx context.Context) error {
_, err := os.Stat(path)
return err
}, &testing.PollOptions{Timeout: timeout, Interval: time.Second})
}