blob: 8fa9dc83f0c9b8d7b1d3451ab22fc4f10de6677c [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 cleanup
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"
"chromiumos/tast/errors"
"chromiumos/tast/local/cryptohome"
"chromiumos/tast/local/disk"
"chromiumos/tast/local/syslog"
"chromiumos/tast/local/upstart"
"chromiumos/tast/testing"
)
// Constants taken from cryptohome.
const (
// Location of user directories.
UserHome = "/home/user"
// Shadow directory
ShadowDir = "/home/.shadow"
MiB uint64 = 1024 * 1024 // 1 MiB
GiB uint64 = 1024 * MiB // 1 GiB
MinimalFreeSpace = 512 * MiB // hard-coded in cryptohomed
CleanupTarget = 2 * GiB // Default value in cryptohomed, when to stop cleaning.
NotificationThreshold = 1 * GiB // Default value in cryptohomed, threshold for sending the low-disk-space notification.
)
// RunAutomaticCleanup triggers cleanup by restarting cryptohome and waits until it is done.
func RunAutomaticCleanup(ctx context.Context, cleanupThreshold, aggressiveCleanupThreshold, criticalCleanupThreshold, targetThreshold uint64) error {
testing.ContextLog(ctx, "Performing automatic cleanup")
cleanupThresholdsArgs := fmt.Sprintf("--cleanup_threshold=%d --aggressive_cleanup_threshold=%d --critical_cleanup_threshold=%d --target_free_space=%d", cleanupThreshold, aggressiveCleanupThreshold, criticalCleanupThreshold, targetThreshold)
reader, err := syslog.NewReader(ctx, syslog.Program(syslog.Cryptohomed))
if err != nil {
return errors.Wrap(err, "failed to start log reader")
}
defer reader.Close()
// Restart to trigger cleanup.
if err := upstart.RestartJob(ctx, "cryptohomed", upstart.WithArg("VMODULE_ARG", "*=1"), upstart.WithArg("CRYPTOHOMED_ARGS", cleanupThresholdsArgs)); err != nil {
return errors.Wrap(err, "failed to restart cryptohomed")
}
// Wait for cleanup to finish.
if _, err := reader.Wait(ctx, 60*time.Second, func(e *syslog.Entry) bool {
return strings.Contains(e.Content, "Disk cleanup complete.")
}); err != nil {
freeSpace, spaceErr := disk.FreeSpace(UserHome)
if spaceErr != nil {
return errors.Wrap(err, "failed to get the amount of free space")
}
if freeSpace >= cleanupThreshold {
return errors.Wrap(err, "space cleared up by other cleanup")
}
return errors.Wrap(err, "cleanup not complete")
}
return nil
}
// ForceAutomaticCleanup sets the cleanup thresholds above the current free space, forcing a cleanup.
// Does not continue past cache cleanup.
// cleanupThreshold = freeSpace + MinimalFreeSpace
// others = freeSpace + 10*CleanupTarget
func ForceAutomaticCleanup(ctx context.Context) error {
freeSpace, err := disk.FreeSpace(UserHome)
if err != nil {
return errors.Wrap(err, "failed to get the amount of free space")
}
cleanupThreshold := freeSpace + MinimalFreeSpace
targetThreshold := freeSpace + 10*CleanupTarget
return RunAutomaticCleanup(ctx, cleanupThreshold, targetThreshold, targetThreshold, targetThreshold)
}
// RunOnExistingUsers cleans up existing users so they don't affect the test by:
// 1. Unmount all users.
// 2. Trigger cleanup to clear all users.
// 3. If any cleanup was performed repeat 2.
func RunOnExistingUsers(ctx context.Context) error {
const (
temporaryUser = "cleanup-removal-user"
password = "1234"
)
removalLogMessages := []string{
"Deleting Cache",
"Deleting GCache",
"Deleting Android Cache",
"Freeing disk space by deleting user",
}
testing.ContextLog(ctx, "Cleaning up existing users")
// Unmount all users.
if err := cryptohome.UnmountAll(ctx); err != nil {
return errors.Wrap(err, "failed to unmount users")
}
// Cleanups are currently repeated so we need to remember what was already done.
performedCleanups := make(map[string]bool)
// Repeat cleanup until cleanup is ineffective.
if err := testing.Poll(ctx, func(ctx context.Context) error {
reader, err := syslog.NewReader(ctx, syslog.Program(syslog.Cryptohomed))
if err != nil {
return errors.Wrap(err, "failed to start log reader")
}
defer reader.Close()
if err := ForceAutomaticCleanup(ctx); err != nil {
return testing.PollBreak(errors.Wrap(err, "failed to run cleanup"))
}
// True iff any new cleanup was performed.
cleanupPerformed := false
for {
entry, err := reader.Read()
if err == io.EOF {
if cleanupPerformed {
return errors.New("logged deletion, repeating cleanup")
}
return nil
} else if err != nil {
return testing.PollBreak(errors.Wrap(err, "failed to read syslog"))
}
for _, message := range removalLogMessages {
if strings.Contains(entry.Content, message) && !performedCleanups[entry.Content] {
performedCleanups[entry.Content] = true
cleanupPerformed = true
}
}
}
}, &testing.PollOptions{
Timeout: 60 * time.Second,
}); err != nil {
return errors.Wrap(err, "failed to remove users")
}
if freeSpace, err := disk.FreeSpace(UserHome); err != nil {
return errors.Wrap(err, "failed get free space")
} else if freeSpace < CleanupTarget { // Validity check
return errors.Errorf("too little free space is available: %d", freeSpace)
} else {
testing.ContextLogf(ctx, "%d bytes available after automatic cleanup", freeSpace)
}
return nil
}
// CreateFilledUserHomedir creates a user taking up size space by filling dir.
func CreateFilledUserHomedir(ctx context.Context, user, pass, dir string, size uint64) (string, error) {
if err := cryptohome.CreateVault(ctx, user, pass); err != nil {
return "", errors.Wrap(err, "failed to create user vault")
}
ok := false
defer func() {
if !ok {
cryptohome.RemoveVault(ctx, user)
}
}()
hash, err := cryptohome.UserHash(ctx, user)
if err != nil {
return "", errors.Wrap(err, "failed to get user hash")
}
if err := testing.Poll(ctx, func(ctx context.Context) error {
if _, err := os.Stat(filepath.Join(UserHome, hash, dir)); err != nil {
return err
}
return nil
}, &testing.PollOptions{Timeout: 10 * time.Second}); err != nil {
return "", errors.Wrap(err, "folder not created")
}
fillFile, err := disk.Fill(filepath.Join(UserHome, hash, dir), size)
if err != nil {
return "", errors.Wrap(err, "failed to fill space")
}
ok = true
return fillFile, nil
}
// UserHomeExists checks if a users home exists on disk.
func UserHomeExists(ctx context.Context, user string) (bool, error) {
hash, err := cryptohome.UserHash(ctx, user)
if err != nil {
return false, errors.Wrap(err, "failed to get user hash")
}
if _, err := os.Stat(filepath.Join(ShadowDir, hash)); err == nil {
return true, nil
} else if errors.Is(err, os.ErrNotExist) {
return false, nil
} else {
return false, errors.Wrap(err, "failed to stat the user home dir")
}
}