Work in progress of moving the (audo-)mounting functionality from Tracker into
a dedicated mount server. This is pretty much a straight copy from the AutoMounter code from Tracker, except * the eject on unmount setting has been added (in Tracker, it's part of the general settings, not the mount specific ones), * scripting features have been added, such that it becomes possible to trigger mounting the previoulsy mounted volumes from the outside, and most importantly block until the operation is done (waiting for the reply). TODO: * Change Tracker to not run it's own AutoMounter, but send messages to the new server. * Move the eject when unmounting setting to the mount settings window. * Enable the mount_server in the Bootscript. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@33484 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
parent
e10035a02e
commit
21f2402e20
20
headers/private/mount/MountServer.h
Normal file
20
headers/private/mount/MountServer.h
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2007-2009, Haiku, Inc. All rights reserved.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
#ifndef _MOUNT_SERVER_H
|
||||
#define _MOUNT_SERVER_H
|
||||
|
||||
#include <SupportDefs.h>
|
||||
|
||||
|
||||
const uint32 kMountVolume = 'mntv';
|
||||
const uint32 kMountAllNow = 'mntn';
|
||||
const uint32 kSetAutomounterParams = 'pmst';
|
||||
const uint32 kVolumeMounted = 'vmtd';
|
||||
const uint32 kUnmountVolume = 'umnt';
|
||||
|
||||
const char* kMountServerSignature = "application/x-vnd.Haiku-mount_server";
|
||||
|
||||
|
||||
#endif // _MOUNT_SERVER_H
|
@ -9,6 +9,7 @@ SubInclude HAIKU_TOP src servers mail ;
|
||||
SubInclude HAIKU_TOP src servers media ;
|
||||
SubInclude HAIKU_TOP src servers media_addon ;
|
||||
SubInclude HAIKU_TOP src servers midi ;
|
||||
SubInclude HAIKU_TOP src servers mount ;
|
||||
SubInclude HAIKU_TOP src servers net ;
|
||||
SubInclude HAIKU_TOP src servers power ;
|
||||
SubInclude HAIKU_TOP src servers print ;
|
||||
|
@ -34,6 +34,16 @@ typedef PixelFormat::agg_buffer agg_buffer;
|
||||
d[2] = (((((r) - _p.data8[2]) * (a)) + (_p.data8[2] << 8)) >> 8); \
|
||||
d[3] = 255; \
|
||||
}
|
||||
//#define BLEND(d, r, g, b, a) \
|
||||
//{ \
|
||||
// pixel32 _p; \
|
||||
// _p.data32 = *(uint32*)d; \
|
||||
// d[0] = (((((b) - _p.data8[0]) * (a)) + ((_p.data8[0] << 8) | _p.data8[0])) / 255); \
|
||||
// d[1] = (((((g) - _p.data8[1]) * (a)) + ((_p.data8[1] << 8) | _p.data8[1])) / 255); \
|
||||
// d[2] = (((((r) - _p.data8[2]) * (a)) + ((_p.data8[2] << 8) | _p.data8[2])) / 255); \
|
||||
// d[3] = 255; \
|
||||
//}
|
||||
|
||||
|
||||
#define BLEND_SUBPIX(d, r, g, b, a1, a2, a3) \
|
||||
{ \
|
||||
@ -44,6 +54,15 @@ typedef PixelFormat::agg_buffer agg_buffer;
|
||||
d[2] = (((((r) - _p.data8[2]) * (a3)) + (_p.data8[2] << 8)) >> 8); \
|
||||
d[3] = 255; \
|
||||
}
|
||||
//#define BLEND_SUBPIX(d, r, g, b, a1, a2, a3) \
|
||||
//{ \
|
||||
// pixel32 _p; \
|
||||
// _p.data32 = *(uint32*)d; \
|
||||
// d[0] = (((((b) - _p.data8[0]) * (a1)) + ((_p.data8[0] << 8) | _p.data8[0])) / 255); \
|
||||
// d[1] = (((((g) - _p.data8[1]) * (a2)) + ((_p.data8[1] << 8) | _p.data8[1])) / 255); \
|
||||
// d[2] = (((((r) - _p.data8[2]) * (a3)) + ((_p.data8[2] << 8) | _p.data8[2])) / 255); \
|
||||
// d[3] = 255; \
|
||||
//}
|
||||
|
||||
// BLEND_FROM
|
||||
//
|
||||
@ -59,6 +78,13 @@ typedef PixelFormat::agg_buffer agg_buffer;
|
||||
d[2] = (((((r2) - (r1)) * (a)) + ((r1) << 8)) >> 8); \
|
||||
d[3] = 255; \
|
||||
}
|
||||
//#define BLEND_FROM(d, r1, g1, b1, r2, g2, b2, a) \
|
||||
//{ \
|
||||
// d[0] = (((((b2) - (b1)) * (a)) + ((b1) << 8) | b1) / 255); \
|
||||
// d[1] = (((((g2) - (g1)) * (a)) + ((g1) << 8) | g1) / 255); \
|
||||
// d[2] = (((((r2) - (r1)) * (a)) + ((r1) << 8) | r1) / 255); \
|
||||
// d[3] = 255; \
|
||||
//}
|
||||
|
||||
#define BLEND_FROM_SUBPIX(d, r1, g1, b1, r2, g2, b2, a1, a2, a3) \
|
||||
{ \
|
||||
@ -67,6 +93,13 @@ typedef PixelFormat::agg_buffer agg_buffer;
|
||||
d[2] = (((((r2) - (r1)) * (a3)) + ((r1) << 8)) >> 8); \
|
||||
d[3] = 255; \
|
||||
}
|
||||
//#define BLEND_FROM_SUBPIX(d, r1, g1, b1, r2, g2, b2, a1, a2, a3) \
|
||||
//{ \
|
||||
// d[0] = (((((b2) - (b1)) * (a1)) + ((b1) << 8) | b1) / 255); \
|
||||
// d[1] = (((((g2) - (g1)) * (a2)) + ((g1) << 8) | g1) / 255); \
|
||||
// d[2] = (((((r2) - (r1)) * (a3)) + ((r1) << 8) | r1) / 255); \
|
||||
// d[3] = 255; \
|
||||
//}
|
||||
|
||||
// BLEND16
|
||||
//
|
||||
@ -83,15 +116,33 @@ typedef PixelFormat::agg_buffer agg_buffer;
|
||||
d[2] = (((((r) - _p.data8[2]) * (a)) + (_p.data8[2] << 16)) >> 16); \
|
||||
d[3] = 255; \
|
||||
}
|
||||
//#define BLEND16(d, r, g, b, a) \
|
||||
//{ \
|
||||
// pixel32 _p; \
|
||||
// _p.data32 = *(uint32*)d; \
|
||||
// d[0] = (((((b) - _p.data8[0]) * (a)) + (_p.data8[0] << 16)) / 65025); \
|
||||
// d[1] = (((((g) - _p.data8[1]) * (a)) + (_p.data8[1] << 16)) / 65025); \
|
||||
// d[2] = (((((r) - _p.data8[2]) * (a)) + (_p.data8[2] << 16)) / 65025); \
|
||||
// d[3] = 255; \
|
||||
//}
|
||||
|
||||
// BLEND16_SUBPIX
|
||||
//#define BLEND16_SUBPIX(d, r, g, b, a1, a2, a3) \
|
||||
//{ \
|
||||
// pixel32 _p; \
|
||||
// _p.data32 = *(uint32*)d; \
|
||||
// d[0] = (((((b) - _p.data8[0]) * (a1)) + (_p.data8[0] << 16)) >> 16); \
|
||||
// d[1] = (((((g) - _p.data8[1]) * (a2)) + (_p.data8[1] << 16)) >> 16); \
|
||||
// d[2] = (((((r) - _p.data8[2]) * (a3)) + (_p.data8[2] << 16)) >> 16); \
|
||||
// d[3] = 255; \
|
||||
//}
|
||||
#define BLEND16_SUBPIX(d, r, g, b, a1, a2, a3) \
|
||||
{ \
|
||||
pixel32 _p; \
|
||||
_p.data32 = *(uint32*)d; \
|
||||
d[0] = (((((b) - _p.data8[0]) * (a1)) + (_p.data8[0] << 16)) >> 16); \
|
||||
d[1] = (((((g) - _p.data8[1]) * (a2)) + (_p.data8[1] << 16)) >> 16); \
|
||||
d[2] = (((((r) - _p.data8[2]) * (a3)) + (_p.data8[2] << 16)) >> 16); \
|
||||
d[0] = (((((b) - _p.data8[0]) * (a1)) + (_p.data8[0] * 65025)) / 65025); \
|
||||
d[1] = (((((g) - _p.data8[1]) * (a2)) + (_p.data8[1] * 65025)) / 65025); \
|
||||
d[2] = (((((r) - _p.data8[2]) * (a3)) + (_p.data8[2] * 65025)) / 65025); \
|
||||
d[3] = 255; \
|
||||
}
|
||||
|
||||
|
908
src/servers/mount/AutoMounter.cpp
Normal file
908
src/servers/mount/AutoMounter.cpp
Normal file
@ -0,0 +1,908 @@
|
||||
/*
|
||||
* Copyright 2007-2009, Haiku, Inc. All rights reserved.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
|
||||
#include "AutoMounter.h"
|
||||
|
||||
#include <new>
|
||||
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <Alert.h>
|
||||
#include <AutoLocker.h>
|
||||
#include <Debug.h>
|
||||
#include <Directory.h>
|
||||
#include <DiskDevice.h>
|
||||
#include <DiskDeviceRoster.h>
|
||||
#include <DiskDeviceList.h>
|
||||
#include <DiskDeviceTypes.h>
|
||||
#include <DiskSystem.h>
|
||||
#include <FindDirectory.h>
|
||||
#include <fs_info.h>
|
||||
#include <fs_volume.h>
|
||||
#include <Message.h>
|
||||
#include <Node.h>
|
||||
#include <NodeMonitor.h>
|
||||
#include <Path.h>
|
||||
#include <PropertyInfo.h>
|
||||
#include <String.h>
|
||||
#include <VolumeRoster.h>
|
||||
|
||||
//#include "AutoMounterSettings.h"
|
||||
#include "MountServer.h"
|
||||
|
||||
|
||||
static const char* kMountServerSettings = "mount_server";
|
||||
static const uint32 kMsgInitialScan = 'insc';
|
||||
static const char* kMountFlagsKeyExtension = " mount flags";
|
||||
|
||||
|
||||
bool
|
||||
BootedInSafeMode()
|
||||
{
|
||||
const char *safeMode = getenv("SAFEMODE");
|
||||
return (safeMode && strcmp(safeMode, "yes") == 0);
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark -
|
||||
|
||||
|
||||
AutoMounter::AutoMounter()
|
||||
:
|
||||
BApplication(kMountServerSignature),
|
||||
fNormalMode(kRestorePreviousVolumes),
|
||||
fRemovableMode(kAllVolumes),
|
||||
fEjectWhenUnmounting(true)
|
||||
{
|
||||
set_thread_priority(Thread(), B_LOW_PRIORITY);
|
||||
|
||||
if (!BootedInSafeMode()) {
|
||||
_ReadSettings();
|
||||
} else {
|
||||
// defeat automounter in safe mode, don't even care about the settings
|
||||
fNormalMode = kNoVolumes;
|
||||
fRemovableMode = kNoVolumes;
|
||||
}
|
||||
|
||||
BDiskDeviceRoster().StartWatching(this,
|
||||
B_DEVICE_REQUEST_DEVICE | B_DEVICE_REQUEST_DEVICE_LIST);
|
||||
}
|
||||
|
||||
|
||||
AutoMounter::~AutoMounter()
|
||||
{
|
||||
BDiskDeviceRoster().StopWatching(this);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AutoMounter::MessageReceived(BMessage* message)
|
||||
{
|
||||
switch (message->what) {
|
||||
case B_EXECUTE_PROPERTY:
|
||||
{
|
||||
int32 index;
|
||||
BMessage specifier;
|
||||
int32 what;
|
||||
const char* property = NULL;
|
||||
if (message->GetCurrentSpecifier(&index, &specifier, &what,
|
||||
&property) < B_OK
|
||||
|| !_ScriptReceived(message, index, &specifier, what,
|
||||
property)) {
|
||||
BApplication::MessageReceived(message);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case kMsgInitialScan:
|
||||
_MountVolumes(fNormalMode, fRemovableMode, true);
|
||||
break;
|
||||
|
||||
case kMountVolume:
|
||||
_MountVolume(message);
|
||||
break;
|
||||
|
||||
case kUnmountVolume:
|
||||
_UnmountAndEjectVolume(message);
|
||||
break;
|
||||
|
||||
case kSetAutomounterParams:
|
||||
{
|
||||
bool rescanNow = false;
|
||||
message->FindBool("rescanNow", &rescanNow);
|
||||
|
||||
_UpdateSettingsFromMessage(message);
|
||||
_GetSettings(&fSettings);
|
||||
_WriteSettings();
|
||||
|
||||
if (rescanNow)
|
||||
_MountVolumes(fNormalMode, fRemovableMode);
|
||||
break;
|
||||
}
|
||||
|
||||
case kMountAllNow:
|
||||
_MountVolumes(kAllVolumes, kAllVolumes);
|
||||
break;
|
||||
|
||||
case B_DEVICE_UPDATE:
|
||||
int32 event;
|
||||
if (message->FindInt32("event", &event) != B_OK
|
||||
|| (event != B_DEVICE_MEDIA_CHANGED
|
||||
&& event != B_DEVICE_ADDED))
|
||||
break;
|
||||
|
||||
partition_id deviceID;
|
||||
if (message->FindInt32("id", &deviceID) != B_OK)
|
||||
break;
|
||||
|
||||
_MountVolumes(kNoVolumes, fRemovableMode, false, deviceID);
|
||||
break;
|
||||
|
||||
#if 0
|
||||
case B_NODE_MONITOR:
|
||||
{
|
||||
int32 opcode;
|
||||
if (message->FindInt32("opcode", &opcode) != B_OK)
|
||||
break;
|
||||
|
||||
switch (opcode) {
|
||||
// The name of a mount point has changed
|
||||
case B_ENTRY_MOVED: {
|
||||
WRITELOG(("*** Received Mount Point Renamed Notification"));
|
||||
|
||||
const char *newName;
|
||||
if (message->FindString("name", &newName) != B_OK) {
|
||||
WRITELOG(("ERROR: Couldn't find name field in update message"));
|
||||
PRINT_OBJECT(*message);
|
||||
break ;
|
||||
}
|
||||
|
||||
//
|
||||
// When the node monitor reports a move, it gives the
|
||||
// parent device and inode that moved. The problem is
|
||||
// that the inode is the inode of root *in* the filesystem,
|
||||
// which is generally always the same number for every
|
||||
// filesystem of a type.
|
||||
//
|
||||
// What we'd really like is the device that the moved
|
||||
// volume is mounted on. Find this by using the
|
||||
// *new* name and directory, and then stat()ing that to
|
||||
// find the device.
|
||||
//
|
||||
dev_t parentDevice;
|
||||
if (message->FindInt32("device", &parentDevice) != B_OK) {
|
||||
WRITELOG(("ERROR: Couldn't find 'device' field in update"
|
||||
" message"));
|
||||
PRINT_OBJECT(*message);
|
||||
break;
|
||||
}
|
||||
|
||||
ino_t toDirectory;
|
||||
if (message->FindInt64("to directory", &toDirectory)!=B_OK){
|
||||
WRITELOG(("ERROR: Couldn't find 'to directory' field in update"
|
||||
"message"));
|
||||
PRINT_OBJECT(*message);
|
||||
break;
|
||||
}
|
||||
|
||||
entry_ref root_entry(parentDevice, toDirectory, newName);
|
||||
|
||||
BNode entryNode(&root_entry);
|
||||
if (entryNode.InitCheck() != B_OK) {
|
||||
WRITELOG(("ERROR: Couldn't create mount point entry node: %s/n",
|
||||
strerror(entryNode.InitCheck())));
|
||||
break;
|
||||
}
|
||||
|
||||
node_ref mountPointNode;
|
||||
if (entryNode.GetNodeRef(&mountPointNode) != B_OK) {
|
||||
WRITELOG(("ERROR: Couldn't get node ref for new mount point"));
|
||||
break;
|
||||
}
|
||||
|
||||
WRITELOG(("Attempt to rename device %li to %s", mountPointNode.device,
|
||||
newName));
|
||||
|
||||
Partition *partition = FindPartition(mountPointNode.device);
|
||||
if (partition != NULL) {
|
||||
WRITELOG(("Found device, changing name."));
|
||||
|
||||
BVolume mountVolume(partition->VolumeDeviceID());
|
||||
BDirectory mountDir;
|
||||
mountVolume.GetRootDirectory(&mountDir);
|
||||
BPath dirPath(&mountDir, 0);
|
||||
|
||||
partition->SetMountedAt(dirPath.Path());
|
||||
partition->SetVolumeName(newName);
|
||||
break;
|
||||
} else {
|
||||
WRITELOG(("ERROR: Device %li does not appear to be present",
|
||||
mountPointNode.device));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
default:
|
||||
BLooper::MessageReceived(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
AutoMounter::QuitRequested()
|
||||
{
|
||||
if (!BootedInSafeMode()) {
|
||||
// don't write out settings in safe mode - this would overwrite the
|
||||
// normal, non-safe mode settings
|
||||
_WriteSettings();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark - scripting
|
||||
|
||||
|
||||
const uint32 kApplication = 0;
|
||||
|
||||
static property_info sPropertyInfo[] = {
|
||||
{
|
||||
"InitialScan",
|
||||
{B_EXECUTE_PROPERTY},
|
||||
{B_DIRECT_SPECIFIER},
|
||||
NULL, kApplication,
|
||||
{B_STRING_TYPE},
|
||||
{},
|
||||
{}
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
BHandler*
|
||||
AutoMounter::ResolveSpecifier(BMessage* message, int32 index,
|
||||
BMessage* specifier, int32 what, const char* property)
|
||||
{
|
||||
BPropertyInfo propInfo(sPropertyInfo);
|
||||
|
||||
uint32 data;
|
||||
if (propInfo.FindMatch(message, 0, specifier, what, property, &data) >= 0) {
|
||||
if (data == kApplication)
|
||||
return this;
|
||||
|
||||
BMessage reply(B_MESSAGE_NOT_UNDERSTOOD);
|
||||
reply.AddInt32("error", B_ERROR);
|
||||
reply.AddString("message", "Unkown specifier.");
|
||||
message->SendReply(&reply);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return BApplication::ResolveSpecifier(message, index, specifier, what,
|
||||
property);
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
AutoMounter::GetSupportedSuites(BMessage* data)
|
||||
{
|
||||
if (data == NULL)
|
||||
return B_BAD_VALUE;
|
||||
|
||||
status_t status = data->AddString("suites",
|
||||
"suite/vnd.Haiku-mount_server");
|
||||
if (status != B_OK)
|
||||
return status;
|
||||
|
||||
BPropertyInfo propertyInfo(sPropertyInfo);
|
||||
status = data->AddFlat("messages", &propertyInfo);
|
||||
if (status != B_OK)
|
||||
return status;
|
||||
|
||||
return BApplication::GetSupportedSuites(data);
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
AutoMounter::_ScriptReceived(BMessage *message, int32 index,
|
||||
BMessage *specifier, int32 what, const char *property)
|
||||
{
|
||||
BMessage reply(B_REPLY);
|
||||
status_t err = B_BAD_SCRIPT_SYNTAX;
|
||||
|
||||
switch (message->what) {
|
||||
case B_EXECUTE_PROPERTY:
|
||||
if (strcmp("InitialScan", property) == 0) {
|
||||
printf("performing initial scan.\n");
|
||||
_MountVolumes(fNormalMode, fRemovableMode, true);
|
||||
err = reply.AddString("result", "Previous volumes mounted.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (err == B_BAD_SCRIPT_SYNTAX)
|
||||
return false;
|
||||
|
||||
if (err != B_OK) {
|
||||
reply.what = B_MESSAGE_NOT_UNDERSTOOD;
|
||||
reply.AddString("message", strerror(err));
|
||||
}
|
||||
reply.AddInt32("error", err);
|
||||
message->SendReply(&reply);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark -
|
||||
|
||||
|
||||
static bool
|
||||
suggest_mount_flags(const BPartition* partition, uint32* _flags)
|
||||
{
|
||||
uint32 mountFlags = 0;
|
||||
|
||||
bool askReadOnly = true;
|
||||
bool isBFS = false;
|
||||
|
||||
if (partition->ContentType() != NULL
|
||||
&& strcmp(partition->ContentType(), kPartitionTypeBFS) == 0) {
|
||||
#if 0
|
||||
askReadOnly = false;
|
||||
#endif
|
||||
isBFS = true;
|
||||
}
|
||||
|
||||
BDiskSystem diskSystem;
|
||||
status_t status = partition->GetDiskSystem(&diskSystem);
|
||||
if (status == B_OK && !diskSystem.SupportsWriting())
|
||||
askReadOnly = false;
|
||||
|
||||
if (partition->IsReadOnly())
|
||||
askReadOnly = false;
|
||||
|
||||
if (askReadOnly) {
|
||||
// Suggest to the user to mount read-only until Haiku is more mature.
|
||||
BString string;
|
||||
string << "Mounting volume ";
|
||||
if (partition->ContentName() != NULL)
|
||||
string << "'" << partition->ContentName() << "'\n\n";
|
||||
else
|
||||
string << "<unnamed volume>\n\n";
|
||||
// TODO: Use distro name instead of "Haiku"...
|
||||
if (!isBFS) {
|
||||
string << "The file system on this volume is not the Haiku file "
|
||||
"system. It is strongly suggested to mount it in read-only "
|
||||
"mode. ";
|
||||
} else {
|
||||
string << "It is suggested to mount all additional Haiku volumes "
|
||||
"in read-only mode. ";
|
||||
}
|
||||
string << "This will prevent unintentional data loss because of "
|
||||
"errors in Haiku.";
|
||||
BAlert* alert = new BAlert("Mount Warning", string.String(),
|
||||
"Mount Read/Write", "Cancel", "Mount Read-only",
|
||||
B_WIDTH_FROM_WIDEST, B_WARNING_ALERT);
|
||||
alert->SetShortcut(1, B_ESCAPE);
|
||||
int32 choice = alert->Go();
|
||||
switch (choice) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
return false;
|
||||
case 2:
|
||||
mountFlags |= B_MOUNT_READ_ONLY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*_flags = mountFlags;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AutoMounter::_MountVolumes(mount_mode normal, mount_mode removable,
|
||||
bool initialRescan, partition_id deviceID)
|
||||
{
|
||||
if (normal == kNoVolumes && removable == kNoVolumes)
|
||||
return;
|
||||
|
||||
class InitialMountVisitor : public BDiskDeviceVisitor {
|
||||
public:
|
||||
InitialMountVisitor(mount_mode normalMode, mount_mode removableMode,
|
||||
bool initialRescan, BMessage& previous,
|
||||
partition_id deviceID)
|
||||
:
|
||||
fNormalMode(normalMode),
|
||||
fRemovableMode(removableMode),
|
||||
fInitialRescan(initialRescan),
|
||||
fPrevious(previous),
|
||||
fOnlyOnDeviceID(deviceID)
|
||||
{
|
||||
}
|
||||
|
||||
virtual
|
||||
~InitialMountVisitor()
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool
|
||||
Visit(BDiskDevice* device)
|
||||
{
|
||||
return Visit(device, 0);
|
||||
}
|
||||
|
||||
virtual bool
|
||||
Visit(BPartition* partition, int32 level)
|
||||
{
|
||||
if (fOnlyOnDeviceID >= 0) {
|
||||
// only mount partitions on the given device id
|
||||
// or if the partition ID is already matched
|
||||
BPartition* device = partition;
|
||||
while (device->Parent() != NULL) {
|
||||
if (device->ID() == fOnlyOnDeviceID) {
|
||||
// we are happy
|
||||
break;
|
||||
}
|
||||
device = device->Parent();
|
||||
}
|
||||
if (device->ID() != fOnlyOnDeviceID)
|
||||
return false;
|
||||
}
|
||||
|
||||
mount_mode mode = !fInitialRescan
|
||||
&& partition->Device()->IsRemovableMedia()
|
||||
? fRemovableMode : fNormalMode;
|
||||
if (mode == kNoVolumes
|
||||
|| partition->IsMounted()
|
||||
|| !partition->ContainsFileSystem())
|
||||
return false;
|
||||
|
||||
BPath path;
|
||||
if (partition->GetPath(&path) != B_OK)
|
||||
return false;
|
||||
|
||||
if (mode == kRestorePreviousVolumes) {
|
||||
// mount all volumes that were stored in the settings file
|
||||
const char *volumeName = NULL;
|
||||
if (partition->ContentName() == NULL
|
||||
|| fPrevious.FindString(path.Path(), &volumeName)
|
||||
!= B_OK
|
||||
|| strcmp(volumeName, partition->ContentName()))
|
||||
return false;
|
||||
} else if (mode == kOnlyBFSVolumes) {
|
||||
if (partition->ContentType() == NULL
|
||||
|| strcmp(partition->ContentType(), kPartitionTypeBFS))
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32 mountFlags;
|
||||
if (!fInitialRescan) {
|
||||
// Ask the user about mount flags if this is not the
|
||||
// initial scan.
|
||||
if (!suggest_mount_flags(partition, &mountFlags))
|
||||
return false;
|
||||
} else {
|
||||
BString mountFlagsKey(path.Path());
|
||||
mountFlagsKey << kMountFlagsKeyExtension;
|
||||
if (fPrevious.FindInt32(mountFlagsKey.String(),
|
||||
(int32*)&mountFlags) < B_OK) {
|
||||
mountFlags = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (partition->Mount(NULL, mountFlags) == B_OK
|
||||
&& partition->GetMountPoint(&path) == B_OK) {
|
||||
// notify Tracker that a new volume has been started
|
||||
BMessage note(kVolumeMounted);
|
||||
note.AddString("path", path.Path());
|
||||
note.AddBool("initial rescan", fInitialRescan);
|
||||
be_app->PostMessage(¬e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
mount_mode fNormalMode;
|
||||
mount_mode fRemovableMode;
|
||||
bool fInitialRescan;
|
||||
BMessage& fPrevious;
|
||||
partition_id fOnlyOnDeviceID;
|
||||
} visitor(normal, removable, initialRescan, fSettings, deviceID);
|
||||
|
||||
BDiskDeviceList devices;
|
||||
status_t status = devices.Fetch();
|
||||
if (status == B_OK)
|
||||
devices.VisitEachPartition(&visitor);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AutoMounter::_MountVolume(const BMessage* message)
|
||||
{
|
||||
int32 id;
|
||||
if (message->FindInt32("id", &id) != B_OK)
|
||||
return;
|
||||
|
||||
BDiskDeviceRoster roster;
|
||||
BPartition *partition;
|
||||
BDiskDevice device;
|
||||
if (roster.GetPartitionWithID(id, &device, &partition) != B_OK)
|
||||
return;
|
||||
|
||||
uint32 mountFlags;
|
||||
if (!suggest_mount_flags(partition, &mountFlags))
|
||||
return;
|
||||
|
||||
status_t status = partition->Mount(NULL, mountFlags);
|
||||
if (status < B_OK) {
|
||||
BString string;
|
||||
string << "Error mounting volume. (" << strerror(status) << ")";
|
||||
(new BAlert("", string.String(), "Ok"))->Go(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
AutoMounter::_SuggestForceUnmount(const char* name, status_t error)
|
||||
{
|
||||
BString text;
|
||||
text << "Could not unmount disk \"" << name << "\":\n\t";
|
||||
text << strerror(error);
|
||||
text << "\n\nShould unmounting be forced?\n\n"
|
||||
"Note: if an application is currently writing to the volume, "
|
||||
"unmounting it now might result in loss of data.\n";
|
||||
|
||||
BAlert* alert = new BAlert("", text.String(), "Cancel", "Force Unmount",
|
||||
NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
|
||||
alert->SetShortcut(0, B_ESCAPE);
|
||||
int32 choice = alert->Go();
|
||||
|
||||
return choice == 1;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AutoMounter::_ReportUnmountError(const char* name, status_t error)
|
||||
{
|
||||
BString text;
|
||||
text << "Could not unmount disk \"" << name << "\":\n\t";
|
||||
text << strerror(error);
|
||||
|
||||
(new BAlert("", text.String(), "OK", NULL, NULL, B_WIDTH_AS_USUAL,
|
||||
B_WARNING_ALERT))->Go(NULL);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AutoMounter::_UnmountAndEjectVolume(BPartition* partition, BPath& mountPoint,
|
||||
const char* name)
|
||||
{
|
||||
BDiskDevice device;
|
||||
if (partition == NULL) {
|
||||
// Try to retrieve partition
|
||||
BDiskDeviceRoster().FindPartitionByMountPoint(mountPoint.Path(),
|
||||
&device, &partition);
|
||||
}
|
||||
|
||||
status_t status;
|
||||
if (partition != NULL)
|
||||
status = partition->Unmount();
|
||||
else
|
||||
status = fs_unmount_volume(mountPoint.Path(), 0);
|
||||
|
||||
if (status < B_OK) {
|
||||
if (!_SuggestForceUnmount(name, status))
|
||||
return;
|
||||
|
||||
if (partition != NULL)
|
||||
status = partition->Unmount(B_FORCE_UNMOUNT);
|
||||
else
|
||||
status = fs_unmount_volume(mountPoint.Path(), B_FORCE_UNMOUNT);
|
||||
}
|
||||
|
||||
if (status < B_OK) {
|
||||
_ReportUnmountError(partition->ContentName(), status);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fEjectWhenUnmounting && partition != NULL) {
|
||||
// eject device if it doesn't have any mounted partitions left
|
||||
class IsMountedVisitor : public BDiskDeviceVisitor {
|
||||
public:
|
||||
IsMountedVisitor()
|
||||
:
|
||||
fHasMounted(false)
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool Visit(BDiskDevice* device)
|
||||
{
|
||||
return Visit(device, 0);
|
||||
}
|
||||
|
||||
virtual bool Visit(BPartition* partition, int32 level)
|
||||
{
|
||||
if (partition->IsMounted()) {
|
||||
fHasMounted = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HasMountedPartitions() const
|
||||
{
|
||||
return fHasMounted;
|
||||
}
|
||||
|
||||
private:
|
||||
bool fHasMounted;
|
||||
} visitor;
|
||||
|
||||
partition->Device()->VisitEachDescendant(&visitor);
|
||||
|
||||
if (!visitor.HasMountedPartitions())
|
||||
partition->Device()->Eject();
|
||||
}
|
||||
|
||||
// remove the directory if it's a directory in rootfs
|
||||
if (dev_for_path(mountPoint.Path()) == dev_for_path("/"))
|
||||
rmdir(mountPoint.Path());
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AutoMounter::_UnmountAndEjectVolume(BMessage* message)
|
||||
{
|
||||
int32 id;
|
||||
if (message->FindInt32("id", &id) == B_OK) {
|
||||
BDiskDeviceRoster roster;
|
||||
BPartition *partition;
|
||||
BDiskDevice device;
|
||||
if (roster.GetPartitionWithID(id, &device, &partition) != B_OK)
|
||||
return;
|
||||
|
||||
BPath path;
|
||||
if (partition->GetMountPoint(&path) == B_OK)
|
||||
_UnmountAndEjectVolume(partition, path, partition->ContentName());
|
||||
} else {
|
||||
// see if we got a dev_t
|
||||
|
||||
dev_t device;
|
||||
if (message->FindInt32("device_id", &device) != B_OK)
|
||||
return;
|
||||
|
||||
BVolume volume(device);
|
||||
status_t status = volume.InitCheck();
|
||||
|
||||
char name[B_FILE_NAME_LENGTH];
|
||||
if (status == B_OK)
|
||||
status = volume.GetName(name);
|
||||
if (status < B_OK)
|
||||
snprintf(name, sizeof(name), "device:%ld", device);
|
||||
|
||||
BPath path;
|
||||
if (status == B_OK) {
|
||||
BDirectory mountPoint;
|
||||
status = volume.GetRootDirectory(&mountPoint);
|
||||
if (status == B_OK)
|
||||
status = path.SetTo(&mountPoint, ".");
|
||||
}
|
||||
|
||||
if (status == B_OK)
|
||||
_UnmountAndEjectVolume(NULL, path, name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AutoMounter::_FromMode(mount_mode mode, bool& all, bool& bfs, bool& restore)
|
||||
{
|
||||
all = bfs = restore = false;
|
||||
|
||||
switch (mode) {
|
||||
case kAllVolumes:
|
||||
all = true;
|
||||
break;
|
||||
case kOnlyBFSVolumes:
|
||||
bfs = true;
|
||||
break;
|
||||
case kRestorePreviousVolumes:
|
||||
restore = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
AutoMounter::mount_mode
|
||||
AutoMounter::_ToMode(bool all, bool bfs, bool restore)
|
||||
{
|
||||
if (all)
|
||||
return kAllVolumes;
|
||||
if (bfs)
|
||||
return kOnlyBFSVolumes;
|
||||
if (restore)
|
||||
return kRestorePreviousVolumes;
|
||||
|
||||
return kNoVolumes;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AutoMounter::_ReadSettings()
|
||||
{
|
||||
BPath directoryPath;
|
||||
if (find_directory(B_USER_SETTINGS_DIRECTORY, &directoryPath, true)
|
||||
!= B_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
BPath path(directoryPath);
|
||||
path.Append(kMountServerSettings);
|
||||
fPrefsFile.SetTo(path.Path(), O_RDWR);
|
||||
|
||||
if (fPrefsFile.InitCheck() != B_OK) {
|
||||
// no prefs file yet, create a new one
|
||||
|
||||
BDirectory dir(directoryPath.Path());
|
||||
dir.CreateFile(kMountServerSettings, &fPrefsFile);
|
||||
return;
|
||||
}
|
||||
|
||||
ssize_t settingsSize = (ssize_t)fPrefsFile.Seek(0, SEEK_END);
|
||||
if (settingsSize == 0)
|
||||
return;
|
||||
|
||||
ASSERT(settingsSize != 0);
|
||||
char *buffer = new(std::nothrow) char[settingsSize];
|
||||
if (buffer == NULL) {
|
||||
PRINT(("error writing automounter settings, out of memory\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
fPrefsFile.Seek(0, 0);
|
||||
if (fPrefsFile.Read(buffer, (size_t)settingsSize) != settingsSize) {
|
||||
PRINT(("error reading automounter settings\n"));
|
||||
delete [] buffer;
|
||||
return;
|
||||
}
|
||||
|
||||
BMessage message('stng');
|
||||
status_t result = message.Unflatten(buffer);
|
||||
if (result != B_OK) {
|
||||
PRINT(("error %s unflattening automounter settings, size %d\n",
|
||||
strerror(result), settingsSize));
|
||||
delete [] buffer;
|
||||
return;
|
||||
}
|
||||
|
||||
delete [] buffer;
|
||||
|
||||
// update flags and modes from the message
|
||||
_UpdateSettingsFromMessage(&message);
|
||||
// copy the previously mounted partitions
|
||||
fSettings = message;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AutoMounter::_WriteSettings()
|
||||
{
|
||||
if (fPrefsFile.InitCheck() != B_OK)
|
||||
return;
|
||||
|
||||
BMessage message('stng');
|
||||
_GetSettings(&message);
|
||||
|
||||
ssize_t settingsSize = message.FlattenedSize();
|
||||
|
||||
char *buffer = new(std::nothrow) char[settingsSize];
|
||||
if (buffer == NULL) {
|
||||
PRINT(("error writing automounter settings, out of memory\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
status_t result = message.Flatten(buffer, settingsSize);
|
||||
|
||||
fPrefsFile.Seek(0, SEEK_SET);
|
||||
result = fPrefsFile.Write(buffer, (size_t)settingsSize);
|
||||
if (result != settingsSize)
|
||||
PRINT(("error writing automounter settings, %s\n", strerror(result)));
|
||||
|
||||
delete [] buffer;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AutoMounter::_UpdateSettingsFromMessage(BMessage* message)
|
||||
{
|
||||
// auto mounter settings
|
||||
|
||||
bool all, bfs, restore;
|
||||
if (message->FindBool("autoMountAll", &all) != B_OK)
|
||||
all = true;
|
||||
if (message->FindBool("autoMountAllBFS", &bfs) != B_OK)
|
||||
bfs = false;
|
||||
|
||||
fRemovableMode = _ToMode(all, bfs, false);
|
||||
|
||||
// initial mount settings
|
||||
|
||||
if (message->FindBool("initialMountAll", &all) != B_OK)
|
||||
all = false;
|
||||
if (message->FindBool("initialMountAllBFS", &bfs) != B_OK)
|
||||
bfs = false;
|
||||
if (message->FindBool("initialMountRestore", &restore) != B_OK)
|
||||
restore = true;
|
||||
|
||||
fNormalMode = _ToMode(all, bfs, restore);
|
||||
|
||||
// eject settings
|
||||
bool eject;
|
||||
if (message->FindBool("ejectWhenUnmounting", &eject) == B_OK)
|
||||
fEjectWhenUnmounting = eject;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AutoMounter::_GetSettings(BMessage *message)
|
||||
{
|
||||
message->MakeEmpty();
|
||||
|
||||
bool all, bfs, restore;
|
||||
|
||||
_FromMode(fNormalMode, all, bfs, restore);
|
||||
message->AddBool("initialMountAll", all);
|
||||
message->AddBool("initialMountAllBFS", bfs);
|
||||
message->AddBool("initialMountRestore", restore);
|
||||
|
||||
_FromMode(fRemovableMode, all, bfs, restore);
|
||||
message->AddBool("autoMountAll", all);
|
||||
message->AddBool("autoMountAllBFS", bfs);
|
||||
|
||||
message->AddBool("ejectWhenUnmounting", fEjectWhenUnmounting);
|
||||
|
||||
// Save mounted volumes so we can optionally mount them on next
|
||||
// startup
|
||||
BVolumeRoster volumeRoster;
|
||||
BVolume volume;
|
||||
while (volumeRoster.GetNextVolume(&volume) == B_OK) {
|
||||
fs_info info;
|
||||
if (fs_stat_dev(volume.Device(), &info) == 0
|
||||
&& (info.flags & (B_FS_IS_REMOVABLE | B_FS_IS_PERSISTENT)) != 0) {
|
||||
message->AddString(info.device_name, info.volume_name);
|
||||
|
||||
BString mountFlagsKey(info.device_name);
|
||||
mountFlagsKey << kMountFlagsKeyExtension;
|
||||
uint32 mountFlags = 0;
|
||||
if (volume.IsReadOnly())
|
||||
mountFlags |= B_MOUNT_READ_ONLY;
|
||||
message->AddInt32(mountFlagsKey.String(), mountFlags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark -
|
||||
|
||||
|
||||
int
|
||||
main(int argc, char* argv[])
|
||||
{
|
||||
AutoMounter app;
|
||||
app.Run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
74
src/servers/mount/AutoMounter.h
Normal file
74
src/servers/mount/AutoMounter.h
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2007-2009, Haiku, Inc. All rights reserved.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
#ifndef AUTO_MOUNTER_H
|
||||
#define AUTO_MOUNTER_H
|
||||
|
||||
#include <Application.h>
|
||||
#include <DiskDeviceDefs.h>
|
||||
#include <File.h>
|
||||
#include <Message.h>
|
||||
|
||||
class BPartition;
|
||||
class BPath;
|
||||
|
||||
|
||||
class AutoMounter : public BApplication {
|
||||
public:
|
||||
AutoMounter();
|
||||
virtual ~AutoMounter();
|
||||
|
||||
virtual void MessageReceived(BMessage* message);
|
||||
virtual bool QuitRequested();
|
||||
|
||||
virtual BHandler* ResolveSpecifier(BMessage* message,
|
||||
int32 index, BMessage* specifier,
|
||||
int32 what, const char* property);
|
||||
virtual status_t GetSupportedSuites(BMessage* data);
|
||||
|
||||
private:
|
||||
enum mount_mode {
|
||||
kNoVolumes,
|
||||
kOnlyBFSVolumes,
|
||||
kAllVolumes,
|
||||
kRestorePreviousVolumes
|
||||
};
|
||||
|
||||
void _GetSettings(BMessage* message);
|
||||
bool _ScriptReceived(BMessage* msg, int32 index,
|
||||
BMessage* specifier, int32 form,
|
||||
const char* property);
|
||||
|
||||
void _MountVolumes(mount_mode normal,
|
||||
mount_mode removable,
|
||||
bool initialRescan = false,
|
||||
partition_id deviceID = -1);
|
||||
void _MountVolume(const BMessage* message);
|
||||
bool _SuggestForceUnmount(const char* name,
|
||||
status_t error);
|
||||
void _ReportUnmountError(const char* name,
|
||||
status_t error);
|
||||
void _UnmountAndEjectVolume(BPartition* partition,
|
||||
BPath& mountPoint, const char* name);
|
||||
void _UnmountAndEjectVolume(BMessage* message);
|
||||
|
||||
void _FromMode(mount_mode mode, bool& all,
|
||||
bool& bfs, bool& restore);
|
||||
mount_mode _ToMode(bool all, bool bfs,
|
||||
bool restore = false);
|
||||
|
||||
void _UpdateSettingsFromMessage(BMessage* message);
|
||||
void _ReadSettings();
|
||||
void _WriteSettings();
|
||||
|
||||
private:
|
||||
mount_mode fNormalMode;
|
||||
mount_mode fRemovableMode;
|
||||
bool fEjectWhenUnmounting;
|
||||
|
||||
BFile fPrefsFile;
|
||||
BMessage fSettings;
|
||||
};
|
||||
|
||||
#endif // AUTO_MOUNTER_H
|
14
src/servers/mount/Jamfile
Normal file
14
src/servers/mount/Jamfile
Normal file
@ -0,0 +1,14 @@
|
||||
SubDir HAIKU_TOP src servers mount ;
|
||||
|
||||
UsePrivateHeaders mount shared storage ;
|
||||
|
||||
Server mount_server
|
||||
:
|
||||
AutoMounter.cpp
|
||||
# AutoMounterSettings.cpp
|
||||
:
|
||||
libbe.so
|
||||
$(TARGET_LIBSTDC++)
|
||||
:
|
||||
mount_server.rdef
|
||||
;
|
57
src/servers/mount/mount_server.rdef
Normal file
57
src/servers/mount/mount_server.rdef
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* mount_server.rdef
|
||||
*/
|
||||
|
||||
resource app_signature "application/x-vnd.Haiku-mount_server";
|
||||
|
||||
resource app_flags B_SINGLE_LAUNCH | B_BACKGROUND_APP;
|
||||
|
||||
resource app_version {
|
||||
major = 1,
|
||||
middle = 0,
|
||||
minor = 0,
|
||||
|
||||
variety = B_APPV_ALPHA,
|
||||
internal = 0,
|
||||
|
||||
short_info = "mount_server",
|
||||
long_info = "mount_server ©2009 Haiku"
|
||||
};
|
||||
|
||||
resource vector_icon array {
|
||||
$"6E6369661603010000020016023CC7EE389BC0BA16573E39B04A25E4468B7100"
|
||||
$"FFFFD3020016023C5A743B25DEBC0A703D0D214B89B149B4E00047FF9E020016"
|
||||
$"02396BD8394C21BC8C213CA4A24C06D7486315003DFF8102001602BC592FBB29"
|
||||
$"A73C0CE4BD0B7C4892C04B9966007DFFD40200060238DBB4399733BC4A333BA5"
|
||||
$"424866664A1B3D00596756FFEBB2B203A7FF0003FF0000040180020006023F20"
|
||||
$"000000000000003E3000C000000000005BF8F8FFFF5D5BBB020006023A6FFE3A"
|
||||
$"7CC1BD2F743D1F1D4AACC540BDFEA0372874E4151EC8020006023EAF513C0E74"
|
||||
$"BAAEAE3D808045F168C49DEB63FFFFFFE8878CE4020016023E72000000000000"
|
||||
$"003D8000BF800041C00000C5C8FF020006023C67EC3DCD85BDA9DD3C515E49AD"
|
||||
$"B5C589F5A7837BBDEFD4D5F5020006023F30000000000000003DC000C0000042"
|
||||
$"000000231587FFDBE2E7020006023E12000000000000003BD00046B000420000"
|
||||
$"9AF8FCC6FFFBF8A302000602400000000000000000400000C000004200000082"
|
||||
$"82FDFF124DA702000602400000000000000000400000C0000042000000FFFFFF"
|
||||
$"FFE8E2E202030605B812A5BE03E13DE784B802104A6BCF479BB000F1F1F136D9"
|
||||
$"DDF48A9996B9B4B8BEDBFFF4F4F4038B86B803837FA802011602B7AF7C3CA0E1"
|
||||
$"BF1E0BBA2F7E4A3050487E9E0046FFDA170605E202405F46C949C4CE6045604A"
|
||||
$"60415C400A062240224D405D5A435A363D2C0A062240404DC73BBE4F5A363D2C"
|
||||
$"B67B3B0607EA283D2C5A36C73BBE4F483E4E3E423E403A42B67B3B0607EA2822"
|
||||
$"40404DC73BBE4F483E4E3E423E403A42B67B3B0A04404D405DC746C3C7C746BE"
|
||||
$"450A04C746C3C75A435A36C746BE450A042240224D405D404D08022747B814C3"
|
||||
$"8908022647294802044626C76B26BCDC262E302EB72C2EBB93463ABCDC3AC76B"
|
||||
$"3A5E305EBB935EB72C0608BFFE4626C76B26BCDC262E302EB72C2EBAB7BC9DBC"
|
||||
$"A0BA2BBBE7BC9DBCA04A2E4C30BF67BD31BF67BD31C045BD4D463AC1303AC76B"
|
||||
$"3A5E305EBB935EB72C0604BE4A2EBC9DBCA0BC9DBCA0BD74BCE0BF67BD31BE64"
|
||||
$"BD11BF67BD314C300204462BC3E62BC0612BBEF32EBEF3B7EBBEF3B93C4631C0"
|
||||
$"6131C3E6314E2E4EB93C4EB7EB0A0621302133393C4B2E4B2B33250A044B2B4B"
|
||||
$"2E393B39380A04213321303938393B0A0627322D2E2E2E3731373232360A042A"
|
||||
$"322E2F30302C330806353A35373C322D2D273127340A0427322735323932360A"
|
||||
$"04BC03B57DB920B73BBE01B8C7C131B6C9080435262D2BBEDAB920462A180A08"
|
||||
$"0100000A0001011001178400040A010104000A150103000A020105000A030106"
|
||||
$"000A040107000A0501081001178520040A0701093024B39901178200040A0601"
|
||||
$"093020B2E601178200040A00010A1001178400040A13010C000A12010B000A14"
|
||||
$"010D1001178100040A09010E000A0A010F000A0B0110000A0C0111000A100112"
|
||||
$"000A0E01131001178100040A110114000A0D01161001178100040A0F0115000A"
|
||||
$"00010E100117820004"
|
||||
};
|
Loading…
Reference in New Issue
Block a user