* Copyright 2005, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
* Distributed under the terms of the MIT License.
*/
* can be the start of an application or the boot process.
* When a session is started, it will prefetch all files from an earlier
* session in order to speed up the launching or booting process.
*
* Note: this module is using private kernel API and is definitely not
* meant to be an example on how to write modules.
*/
#include "launch_speedup.h"
#include <KernelExport.h>
#include <Node.h>
#include <util/kernel_cpp.h>
#include <util/AutoLock.h>
#include <thread.h>
#include <team.h>
#include <file_cache.h>
#include <generic_syscall.h>
#include <syscalls.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
extern dev_t gBootDevice;
#define TRACE_CACHE_MODULE
#ifdef TRACE_CACHE_MODULE
# define TRACE(x) dprintf x
#else
# define TRACE(x) ;
#endif
#define VNODE_HASH(mountid, vnodeid) (((uint32)((vnodeid) >> 32) \
+ (uint32)(vnodeid)) ^ (uint32)(mountid))
struct data_part {
off_t offset;
off_t size;
};
struct node {
struct node *next;
node_ref ref;
int32 ref_count;
bigtime_t timestamp;
data_part parts[5];
size_t part_count;
};
struct NodeHash {
typedef node_ref KeyType;
typedef node ValueType;
size_t HashKey(KeyType key) const
{
return VNODE_HASH(key.device, key.node);
}
size_t Hash(ValueType* value) const
{
return HashKey(value->ref);
}
bool Compare(KeyType key, ValueType* node) const
{
return (node->ref.device == key.device && node->ref.node == key.node);
}
ValueType*& GetLink(ValueType* value) const
{
return value->next;
}
};
typedef BOpenHashTable<NodeHash> NodeTable;
class Session {
public:
Session(team_id team, const char *name, dev_t device,
ino_t node, int32 seconds);
Session(const char *name);
~Session();
status_t InitCheck();
team_id Team() const { return fTeam; }
const char *Name() const { return fName; }
const node_ref &NodeRef() const { return fNodeRef; }
bool IsActive() const { return fActiveUntil >= system_time(); }
bool IsClosing() const { return fClosing; }
bool IsMainSession() const;
bool IsWorthSaving() const;
void AddNode(dev_t device, ino_t node);
void RemoveNode(dev_t device, ino_t node);
void Lock() { mutex_lock(&fLock); }
void Unlock() { mutex_unlock(&fLock); }
status_t StartWatchingTeam();
void StopWatchingTeam();
status_t LoadFromDirectory(int fd);
status_t Save();
void Prefetch();
Session *&Next() { return fNext; }
private:
struct node *_FindNode(dev_t device, ino_t node);
Session *fNext;
char fName[B_OS_NAME_LENGTH];
mutex fLock;
NodeTable *fNodeHash;
struct node *fNodes;
int32 fNodeCount;
team_id fTeam;
node_ref fNodeRef;
bigtime_t fActiveUntil;
bigtime_t fTimestamp;
bool fClosing;
bool fIsWatchingTeam;
};
class SessionGetter {
public:
SessionGetter(team_id team, Session **_session);
~SessionGetter();
status_t New(const char *name, dev_t device, ino_t node,
Session **_session);
void Stop();
private:
Session *fSession;
};
static Session *sMainSession;
static SessionTable *sTeamHash;
static PrefetchTable *sPrefetchHash;
static Session *sMainPrefetchSessions;
static recursive_lock sLock;
node_ref::node_ref()
{
}
struct PrefetchHash {
typedef node_ref KeyType;
typedef Session ValueType;
size_t HashKey(KeyType key) const
{
return VNODE_HASH(key.device, key.node);
}
size_t Hash(ValueType* value) const
{
return HashKey(value->NodeRef());
}
bool Compare(KeyType key, ValueType* session) const
{
return (session->NodeRef().device == key.device
&& session->NodeRef().node == key.node);
}
ValueType*& GetLink(ValueType* value) const
{
return value->Next();
}
};
typedef BOpenHashTable<PrefetchHash> PrefetchTable;
struct SessionHash {
typedef team_id KeyType;
typedef Session ValueType;
size_t HashKey(KeyType key) const
{
return key;
}
size_t Hash(ValueType* value) const
{
return HashKey(value->Team());
}
bool Compare(KeyType key, ValueType* session) const
{
return session->Team == key;
}
ValueType*& GetLink(ValueType* value) const
{
return value->Next();
}
};
typedef BOpenHashTable<SessionHash> SessionTable;
static void
stop_session(Session *session)
{
if (session == NULL)
return;
TRACE(("stop_session(%s)\n", session->Name()));
if (session->IsWorthSaving())
session->Save();
{
RecursiveLocker locker(&sLock);
if (session->Team() >= B_OK)
sTeamHash->Remove(session);
if (session == sMainSession)
sMainSession = NULL;
}
delete session;
}
static Session *
start_session(team_id team, dev_t device, ino_t node, const char *name,
int32 seconds = 30)
{
RecursiveLocker locker(&sLock);
Session *session = new Session(team, name, device, node, seconds);
if (session == NULL)
return NULL;
if (session->InitCheck() != B_OK || session->StartWatchingTeam() != B_OK) {
delete session;
return NULL;
}
Session *prefetchSession;
if (session->IsMainSession()) {
for (prefetchSession = sMainPrefetchSessions;
prefetchSession != NULL;
prefetchSession = prefetchSession->Next()) {
if (!strcmp(prefetchSession->Name(), name)) {
break;
}
}
} else {
prefetchSession = NULL;
}
if (prefetchSession != NULL) {
TRACE(("found prefetch session %s\n", prefetchSession->Name()));
prefetchSession->Prefetch();
}
if (team >= B_OK)
sTeamHash->Insert(session);
session->Lock();
return session;
}
static void
team_gone(team_id team, void *_session)
{
Session *session = (Session *)_session;
session->Lock();
stop_session(session);
}
static bool
parse_node_ref(const char *string, node_ref &ref, const char **_end = NULL)
{
char *end;
ref.device = strtol(string, &end, 0);
if (end == NULL || ref.device == 0)
return false;
ref.node = strtoull(end + 1, &end, 0);
if (_end)
*_end = end;
return true;
}
static struct node *
new_node(dev_t device, ino_t id)
{
struct node *node = new ::node;
if (node == NULL)
return NULL;
node->ref.device = device;
node->ref.node = id;
node->timestamp = system_time();
return node;
}
static void
load_prefetch_data()
{
DIR *dir = opendir("/etc/launch_cache");
if (dir == NULL)
return;
struct dirent *dirent;
while ((dirent = readdir(dir)) != NULL) {
if (dirent->d_name[0] == '.')
continue;
Session *session = new Session(dirent->d_name);
if (session->LoadFromDirectory(dirfd(dir)) != B_OK) {
delete session;
continue;
}
if (session->IsMainSession()) {
session->Next() = sMainPrefetchSessions;
sMainPrefetchSessions = session;
} else {
sPrefetchHash->Insert(session);
}
}
closedir(dir);
}
Session::Session(team_id team, const char *name, dev_t device,
ino_t node, int32 seconds)
:
fNodes(NULL),
fNodeCount(0),
fTeam(team),
fClosing(false),
fIsWatchingTeam(false)
{
if (name != NULL) {
size_t length = strlen(name) + 1;
if (length > B_OS_NAME_LENGTH)
name += length - B_OS_NAME_LENGTH;
strlcpy(fName, name, B_OS_NAME_LENGTH);
} else
fName[0] = '\0';
mutex_init(&fLock, "launch speedup session");
fNodeHash = new(std::nothrow) NodeTable();
if (fNodeHash && fNodeHash->Init(64) != B_OK) {
delete fNodeHash;
fNodeHash = NULL;
}
fActiveUntil = system_time() + seconds * 1000000LL;
fTimestamp = system_time();
fNodeRef.device = device;
fNodeRef.node = node;
TRACE(("start session %ld:%lld \"%s\", system_time: %lld, active until: %lld\n",
device, node, Name(), system_time(), fActiveUntil));
}
Session::Session(const char *name)
:
fNodeHash(NULL),
fNodes(NULL),
fClosing(false),
fIsWatchingTeam(false)
{
fTeam = -1;
fNodeRef.device = -1;
fNodeRef.node = -1;
if (isdigit(name[0]))
parse_node_ref(name, fNodeRef);
strlcpy(fName, name, B_OS_NAME_LENGTH);
}
Session::~Session()
{
mutex_destroy(&fLock);
struct node *node, *next = NULL;
if (fNodeHash) {
node = fNodeHash->Clear(true);
} else {
node = fNodes;
}
for (; node != NULL; node = next) {
next = node->next;
free(node);
}
delete fNodeHash;
StopWatchingTeam();
}
status_t
Session::InitCheck()
{
if (fNodeHash == NULL)
return B_NO_MEMORY;
return B_OK;
}
node *
Session::_FindNode(dev_t device, ino_t node)
{
node_ref key;
key.device = device;
key.node = node;
return fNodeHash->Lookup(key);
}
void
Session::AddNode(dev_t device, ino_t id)
{
struct node *node = _FindNode(device, id);
if (node != NULL) {
node->ref_count++;
return;
}
node = new_node(device, id);
if (node == NULL)
return;
fNodeHash->Insert(node);
fNodeCount++;
}
void
Session::RemoveNode(dev_t device, ino_t id)
{
struct node *node = _FindNode(device, id);
if (node != NULL && --node->ref_count <= 0) {
fNodeHash->Remove(node);
fNodeCount--;
}
}
status_t
Session::StartWatchingTeam()
{
if (Team() < B_OK)
return B_OK;
status_t status = start_watching_team(Team(), team_gone, this);
if (status == B_OK)
fIsWatchingTeam = true;
return status;
}
void
Session::StopWatchingTeam()
{
if (fIsWatchingTeam)
stop_watching_team(Team(), team_gone, this);
}
void
Session::Prefetch()
{
if (fNodes == NULL || fNodeHash != NULL)
return;
for (struct node *node = fNodes; node != NULL; node = node->next) {
cache_prefetch(node->ref.device, node->ref.node, 0, ~0UL);
}
}
status_t
Session::LoadFromDirectory(int directoryFD)
{
TRACE(("load session %s\n", Name()));
int fd = _kern_open(directoryFD, Name(), O_RDONLY, 0);
if (fd < B_OK)
return fd;
struct stat stat;
if (fstat(fd, &stat) != 0) {
close(fd);
return errno;
}
if (stat.st_size > 32768) {
close(fd);
return B_BAD_DATA;
}
char *buffer = (char *)malloc(stat.st_size);
if (buffer == NULL) {
close(fd);
return B_NO_MEMORY;
}
if (read(fd, buffer, stat.st_size) < stat.st_size) {
free(buffer);
close(fd);
return B_ERROR;
}
const char *line = buffer;
node_ref nodeRef;
while (parse_node_ref(line, nodeRef, &line)) {
struct node *node = new_node(nodeRef.device, nodeRef.node);
if (node != NULL) {
node->next = fNodes;
fNodes = node;
}
line++;
}
free(buffer);
close(fd);
return B_OK;
}
status_t
Session::Save()
{
fClosing = true;
char name[B_OS_NAME_LENGTH + 25];
if (!IsMainSession()) {
snprintf(name, sizeof(name), "/etc/launch_cache/%ld:%lld %s",
fNodeRef.device, fNodeRef.node, Name());
} else
snprintf(name, sizeof(name), "/etc/launch_cache/%s", Name());
int fd = open(name, O_CREAT | O_TRUNC | O_WRONLY, 0644);
if (fd < B_OK)
return errno;
status_t status = B_OK;
off_t fileSize = 0;
ftruncate(fd, 512 * 1024);
NodeTable::Iterator iterator(fNodeHash);
while (iterator.HasNext()) {
struct node *node = iterator.Next();
snprintf(name, sizeof(name), "%ld:%lld\n", node->ref.device, node->ref.node);
ssize_t bytesWritten = write(fd, name, strlen(name));
if (bytesWritten < B_OK) {
status = bytesWritten;
break;
}
fileSize += bytesWritten;
}
ftruncate(fd, fileSize);
close(fd);
return status;
}
bool
Session::IsWorthSaving() const
{
if (fNodeCount < 5 || system_time() - fTimestamp < 400000) {
return false;
}
return true;
}
bool
Session::IsMainSession() const
{
return fNodeRef.node == -1;
}
SessionGetter::SessionGetter(team_id team, Session **_session)
{
RecursiveLocker locker(&sLock);
if (sMainSession != NULL)
fSession = sMainSession;
else
fSession = sTeamHash->Lookup(team);
if (fSession != NULL) {
if (!fSession->IsClosing())
fSession->Lock();
else
fSession = NULL;
}
*_session = fSession;
}
SessionGetter::~SessionGetter()
{
if (fSession != NULL)
fSession->Unlock();
}
status_t
SessionGetter::New(const char *name, dev_t device, ino_t node,
Session **_session)
{
Thread *thread = thread_get_current_thread();
fSession = start_session(thread->team->id, device, node, name);
if (fSession != NULL) {
*_session = fSession;
return B_OK;
}
return B_ERROR;
}
void
SessionGetter::Stop()
{
if (fSession == sMainSession)
sMainSession = NULL;
stop_session(fSession);
fSession = NULL;
}
static void
node_opened(struct vnode *vnode, dev_t device, ino_t parent,
ino_t node, const char *name, off_t size)
{
if (device < gBootDevice) {
return;
}
Session *session;
SessionGetter getter(team_get_current_team_id(), &session);
if (session == NULL) {
char buffer[B_FILE_NAME_LENGTH];
if (name == NULL
&& vfs_get_vnode_name(vnode, buffer, sizeof(buffer)) == B_OK)
name = buffer;
getter.New(name, device, node, &session);
}
if (session == NULL || !session->IsActive()) {
if (sMainSession != NULL) {
getter.Stop();
}
return;
}
session->AddNode(device, node);
}
static void
node_closed(struct vnode *vnode, dev_t device, ino_t node,
int32 accessType)
{
Session *session;
SessionGetter getter(team_get_current_team_id(), &session);
if (session == NULL)
return;
if (accessType == FILE_CACHE_NO_IO)
session->RemoveNode(device, node);
}
static status_t
launch_speedup_control(const char *subsystem, uint32 function,
void *buffer, size_t bufferSize)
{
switch (function) {
case LAUNCH_SPEEDUP_START_SESSION:
{
char name[B_OS_NAME_LENGTH];
if (!IS_USER_ADDRESS(buffer)
|| user_strlcpy(name, (const char *)buffer, B_OS_NAME_LENGTH) < B_OK)
return B_BAD_ADDRESS;
if (isdigit(name[0]) || name[0] == '.')
return B_BAD_VALUE;
sMainSession = start_session(-1, -1, -1, name, 60);
sMainSession->Unlock();
return B_OK;
}
case LAUNCH_SPEEDUP_STOP_SESSION:
{
char name[B_OS_NAME_LENGTH];
if (!IS_USER_ADDRESS(buffer)
|| user_strlcpy(name, (const char *)buffer, B_OS_NAME_LENGTH) < B_OK)
return B_BAD_ADDRESS;
if (sMainSession == NULL || strcmp(sMainSession->Name(), name))
return B_BAD_VALUE;
if (!strcmp(name, "system boot"))
dprintf("STOP BOOT %lld\n", system_time());
sMainSession->Lock();
stop_session(sMainSession);
sMainSession = NULL;
return B_OK;
}
}
return B_BAD_VALUE;
}
static void
uninit()
{
unregister_generic_syscall(LAUNCH_SPEEDUP_SYSCALLS, 1);
recursive_lock_lock(&sLock);
Session *session = sTeamHash->Clear(true);
while (session != NULL) {
Session *next = session->next;
delete session;
session = next;
}
session = sPrefetchHash->Clear(true);
while (session != NULL) {
Session *next = session->next;
delete session;
session = next;
}
for (session = sMainPrefetchSessions; session != NULL; ) {
sMainPrefetchSessions = session->Next();
delete session;
session = sMainPrefetchSessions;
}
delete sTeamHash;
delete sPrefetchHash;
recursive_lock_destroy(&sLock);
}
static status_t
init()
{
sTeamHash = new(std::nothrow) SessionTable();
if (sTeamHash == NULL || sTeamHash->Init(64) != B_OK)
return B_NO_MEMORY;
status_t status;
sPrefetchHash = new(std::nothrow) PrefetchTable();
if (sPrefetchHash == NULL || sPrefetchHash->Init(64) != B_OK) {
status = B_NO_MEMORY;
goto err1;
}
recursive_lock_init(&sLock, "launch speedup");
if (register_generic_syscall(LAUNCH_SPEEDUP_SYSCALLS,
launch_speedup_control, 1, 0) != B_OK) {
status = B_ERROR;
goto err3;
}
mkdir("/etc/launch_cache", 0755);
load_prefetch_data();
sMainSession = start_session(-1, -1, -1, "system boot");
sMainSession->Unlock();
dprintf("START BOOT %lld\n", system_time());
return B_OK;
err3:
recursive_lock_destroy(&sLock);
delete sPrefetchHash;
err1:
delete sTeamHash;
return status;
}
static status_t
std_ops(int32 op, ...)
{
switch (op) {
case B_MODULE_INIT:
return init();
case B_MODULE_UNINIT:
uninit();
return B_OK;
default:
return B_ERROR;
}
}
static struct cache_module_info sLaunchSpeedupModule = {
{
CACHE_MODULES_NAME "/launch_speedup/v1",
0,
std_ops,
},
node_opened,
node_closed,
NULL,
};
module_info *modules[] = {
(module_info *)&sLaunchSpeedupModule,
NULL
};