Anmelden

Archiv verlassen und diese Seite im Standarddesign anzeigen : [C++] Fertiger Quellcode zum auslesen von angeschlossenen Festplatten und Partitionen



Primusio
07-12-2005, 22:41
Hallo Community,
ich möchte einmal ein kleines Programm vorstellen, dass vielleicht auch anderen Anfängern, so wie ich einer bin, hilfreich sein könnte.

Gleichzeitig würde ich gerne Verbesserungsvorschläge von "nicht-Anfängern" hören um es noch zu verbessern, kürzen oder übersichtlicher zu gestalten.

Programm zum Auslesen der angeschlossenen Festplatten mit deren Größe und Partitionen !
Da gerade für Anfänger der Zugriff auf Hardware und/oder Kernel-Funktionen wohl zu schwierig ist, habe ich eine andere Lösung für mein Problem gesucht.
Zum Glück stellt uns Linux im "/proc" Verzeichnis alle relevanten Daten zur Verfügung.

Mit folgendem Befehl schauen wir uns einmal die Datei "/proc/partitions" an.


% cat /proc/partitions

Bei mir erscheint folgende Tabelle



major minor #blocks name

3 0 8388608 hda
3 1 1 hda1
3 2 6434977 hda2
3 5 1953252 hda5
3 64 1048576 hdb
3 65 1 hdb1
3 69 97744 hdb5
240 0 1942016 cloop0
8 0 524288 sda
8 1 195568 sda1
8 2 1 sda2
8 5 328688 sda5


Wie man sehen kann, enthält diese Datei alle Daten die wir brauchen, die dritte Spalte gibt die Größe in kB an und die vierte den Devicenamen.

Darauf basierend habe ich folgendes Programm geschrieben.


#include <iostream> // Header für Input (cin) und Output (cout)
#include <fstream> // Header für das arbeiten mit Dateien
#include <string> // Header zum bearbeiten von strings
#include <vector> // Header zum arbeiten mit Vectoren
// Vorteil die dynamische Speicherverwaltung

using namespace std;

struct device // struct device anlegen
{ // in diesem wird das das
string deviceName; // Device als String
int deviceCapacity; // die Größe als Int
vector<string> partitions; // und die Partitionen als String Vector
}; // gespeichert.

// Vectoren sind kurz gesagt Arrays mit dynamischer Speicherverwaltung
// Hier erstellen wir im Grunde ein mehrdimensionales Array
vector<device> deviceArray;

int main (int argc, char *argv[])
{
// Da man nicht direkt in ein mehrdimensionalen Vector speichern können
// legen wir uns einmal temporär unser struct an
struct device deviceStruct;

// Datei öffnen
ifstream file("/proc/partitions");

// Dient zum speichern jeder Datei-Zeile
string line;

// Gibt den Status von unserem "deviceStruct" an
// 0 = "deviceStruct" wurde noch nicht verwendet
// 1 = "deviceStruct" wurde noch nicht an "deviceArray" übergeben
// 2 = "deviceStruct" wurde an "deviceArray" übergeben
int i = 0;

// Die Schleife wird für jede Zeile durchlaufen,
// die aktuelle Zeile wird in "line" gespeichert
while(getline(file,line))
{
// In "loc" wird festgehalten ab welchen Zeichen der Zeile, der Begriff
// "hd" vorkommt. IDE Festplatten beginnen mit "hd" und enden mit a-z
unsigned int loc = line.find( "hd", 0);

// Wenn der Begriff "hd" nicht gefunden wurde suche nach "sd"
// SCSI Festplatten beginnen immer mit "sd" und enden mit a-z
if( loc == string::npos )
loc = line.find( "sd", 0);

// Wenn eine Stelle mit "hd" oder "sd" gefunden wurde dann ...
if( loc != string::npos )
{
// "line.substr" können wir etwas aus einem String ausschneiden
// Start ist bei Zeichen Nummer "loc" und enden nach 5 Zeichen
// Sollte der ausgeschnittene String aber nur 3 Zeichen haben handelt
// es sich nicht um eine Partition sondern um eine Festplatte.
// Partitionen enden immer mit einer Nummer hätten also min. 4 Zeichen
if( line.substr( loc, 5 ).size() == 3 )
{

// Wenn "deviceStruct" schon verwendet wurde dann..
if( i != 0 )
{
// "deviceStruct" and "deviceArray" übergeben
deviceArray.push_back(deviceStruct);

// löschen der Partitionen in "deviceStruct"
deviceStruct.partitions.erase( deviceStruct.partitions.begin(), deviceStruct.partitions.end() );

// Status wird auf 2 geändert
i = 2;
}

// ermitteln an welcher Stelle die Festplattengröße beginnt
// "line.find_last_of("1234567890", loc)" sucht die erste Zahl
// in dieser Zeile beginnend ab "loc", allerdings rückwärts

// indiziert also die letzte Zahl von der Festplattengröße
// von diesem Punkt ausgehend sucht er nun wieder rückwärts, nach dem
// ersten Vorkommen von " " und addiert diesen Wert um 1

// Dies ergibt dann den index der ersten Zahl der Festplattengröße
int capacityIndex = line.find_last_of(" ", line.find_last_of("1234567890", loc)) + 1;

// Zum ausschneiden brauchen wir den Startpunkt "capacityIndex"
// Und die Anzahl der Zeichen (Zahlen) also
// line.find_last_of("1234567890", loc) = Index der letzten Zahl
// minus dem Index der ersten Zahl "capacityIndex" plus 1
int capacityNum = (line.find_last_of("1234567890", loc) - capacityIndex) + 1;

// Mit "line.substr" schneiden wir nun die Festplattengröße aus
// dann wandeln wir mit ".c_str()" denn C++ String in einen C String
// um und konvertieren ihn mit "atoi()" von einem String zu einem Int
deviceStruct.deviceCapacity =atoi( line.substr( capacityIndex, capacityNum ).c_str() );

// Nun speichern wir in "deviceStruct" den Namen des devices
// auschneiden tun wir wieder mit dem Anfangswert "loc" und der
// Anzahl der auszuschneidenden Zeichen (3)
deviceStruct.deviceName = line.substr( loc, 3 );

// Nun setzen wir noch den Status auf "in Bearbeitung"
// sodass unser Programm weiß, dass noch Daten im "deviceStruct" sind
i = 1;

// sollte der oben ausgeschnittene String länger als 3 Zeichen sein
// handelt es sich um eine Partition
} else {

// mit ".push_back()" können wir einem Vector ein neues
// Element hinzufügen, in dem Fall den Partitionsnamen
deviceStruct.partitions.push_back( line.substr( loc, 5 ) );
}
}
};

// Wenn die Datei durchgelaufen ist aber das "deviceStruct" immer noch
// "i = 1" ist z.B. wenn die letzte Zeile eine Partition war
if( i == 1 )
{
// "deviceStruct" an "deviceArray" übergeben
deviceArray.push_back(deviceStruct);

// "Partitionen in "deviceStruct" löschen"
deviceStruct.partitions.erase( deviceStruct.partitions.begin(), deviceStruct.partitions.end() );
}
return 0;
}


Dies sieht auf dem ersten Blick viel aus aber über die Hälfte sind nur kommentare zum besseren Verständnis.
Einiges wird etwas schwer verständlich geschrieben sein, da bitte ich um Nachsicht, dies ist das erste mal, dass ich was veröffentliche. ;)
Wenn jemand eine Idee hat, manche Abschnitte verständlicher zu machen, nur zu, schickt mir eure Ideen und ich werde es nachträglich editieren.

Manche funktionen wird man vielleicht erst verstehen, wenn man sich alles einmal angeschaut hat, z.B. die Status Variable "i", da sieht man erst zum Schluss für was diese gut ist.

Übrigens dieses Programm gibt noch nichts aus, da dies bei mir nur eine Funktion in einem Header ist, deswegen ist "deviceArray" auch global, um in anderen Funktionen darauf zugreifen zu können.

So kann man auf "deviceArray" zugreifen und gibt laut meiner "/proc/partitions" wie oben gesehen, dann folgendes mit aus


cout << deviceArray[0].deviceName << endl;
// hda

cout << deviceArray[1].deviceName << endl;
// hdb

cout << deviceArray[2].deviceCapacity << endl;
// 524288

cout << deviceArray[0].partitions[0] << endl;
// hda1

cout << deviceArray[0].partitions[2] << endl;
// hda5

// Für schleifen interessant
cout << deviceArray.size(); << endl;
// 3

cout << deviceArray[1].partitions.size(); << endl;
// 2


Ich hoffe ich konnte manchen von euch helfen und muss nochmal dringen dazu sagen
Ich bin Anfänger !!! :D


Also nun ran, Ideen, Meinungen, Verbesserungen ... immer her damit, ich würd mich freuen.

MfG
Primusio

RHBaum
08-12-2005, 12:54
Fuern Anfaenger nicht schlecht :-)

Aber es gibt immer was zu meckern, die Welt waer sonst oede wenn nich ^^

Ganz Allgemein:
Du solltest dich fuer nen richtigen Weg (im Sinne vom Stil) entscheiden.
C oder C++. Trotz das C nen Subset von C++ ist, sind das Welten an Unterschied.

Dein Code bewegt sich irgendwie dazwischen ^^

positiv:
Du verwendest Strinklassen (std::string) und Container (std::vector)
negativ:
im gleichem Atemzug packst du die sachen in Ordinaere structs und meidest so einen Riesen Vorteil von C++, der Dir spaeter schlaflose Naechte vermeiden koennte: Kapselung der Daten ....

mach aus struct device, eine Klasse !
und schuetze die members vor direktem zugriff

class device
{
// was man sonst so braucht ...
private:
string deviceName; // Device als String
int deviceCapacity; // die Größe als Int
vector<string> partitions;
};


Zugriff auf die privaten Daten nur ueber methoden, so hasst du spaeter die 100%ige Kontrolle ueber den Inhalt, falls sich mal was aendert ^^

vector als container !

Uberlege noch mal, ob nen vector wirklich ist was du brauchst ^^
Viele, grade Anfaenger, nehmen den vector ohne seine Vorteile wirklich zu nutzen, und fangen sich die Nachteile damit ein .... (einfuegen kann unterschiedlich unperformant sein, die dauer einer einfuegeoperation ist nicht ohne weiteres bestimmbar ).

Vector macht nur wirklich sinn, wenn du den zugriff ueber den 0-basierten index brauchst, und der auch flott gehen soll ^^
Sind deine Devices indiziert ? sind es wirklich Indizies (immer um 1 aufteigend, ohne Luecke ? )
Gibt es device 0, 1, 2 vom BS aus vorgeschrieben und logisch fundamentiert, is dein vector auch ok, andernfalls soll sich der Anwender doch scheren und iterieren ....
Das selbe mit den partitionen ^^

Alternativen zum vector sind map und list ... z.b.

using namespace std;
Viele werden jetzt nicht ganz mit meiner Meinung mitgehen, aber mein Tipp:
gewoehn es Dir ab !!!

In ner Quellcode Datei(.c .cpp) ok, im Header(.h .hpp) der Horror. Das wirst spaeter sicher noch lernen warum ^^

Wie oft schreibt man code in ner quellcode datei, hat den genialen Anfall, das es doch toll zu generalisieren sei, man macht nen template draus und schwupps, steht das vielleicht im header ....
schreib lieber die namespaces aus std::cout und std::cin.

Werden die namespaces mal zu lang, nimm typedefs, die kosten dich zur laufzeit gar nix und koennen im header stehen. using is IMHO zu rabiat.

Denk mal mit Klassen, operatoren und Konstruktoren usw hasst dich noch ned viel beschaeftigt .... darin kannst dich als naechstes richtig vertiefen ^^

Danach wuerd ich mich mit Objektorientiertem design etwas auseinandersetzen ....

Objecte sind da, um probleme zu loesen ....
Du hasst objecte um daten zu speichern , ok,
aber dein problem, das interpretieren des Textes der Datei liegt in deiner Main ... dafuer bietet sich quasi nen Object an, was dir nur dieses Problem loest ^^
Wichtig in C++, Objecte die zigtausend probleme loesen wollen sind .... nicht wirklich gut ^^

Dein Beispiel iss zum lernen sehr gut geeignet, iss nich zu kompliziert und hasst von jedem thema bisserl was dabei ... Das kannst nu super ausbauen ^^

In der Praxis werden die meisten den aufwand scheuen und bei solch kleinen Problemchen die eine oder andere Regel der OOP mal schnell ignorieren. Das ist durchaus zulaessig wenn man genau weiss, das niemand niemals komponenten des Programs woanders verwenden will, und auch eine spaetere Erweiterung der Umfangs ausgeschlossen ist. (oder man bei Auftreten dieser Faelle zu einem tiefgreifendem Redesign sofort uneingeschraenkt bereit ist)
In allen anderen Faellen schlagt dann Murphy erbamungslos zu ! :-)

Ciao ...

anda_skoa
08-12-2005, 14:40
Ich finde struct ist schon ok wenn man keine Dateininvarianten aufrechterhalten muß.


Das einzige was mir gar nicht passt :D ist atoi()

Eine String -> Int Umwandlung kann man viel sauberer mit einem Stringstream machen, denn dann hat man die Möglichkeit über die Statusflags des Streams festzustellen, ob die Konversion geklappt hat

Ciao,
_

RHBaum
08-12-2005, 15:06
Eine String -> Int Umwandlung kann man viel sauberer mit einem Stringstream machen
Ok, wenn man eh schon nen stream hat ...

hab ichs in nem String, bin ich nich so c++ fanatisch, und handel mir die kosten einer erneuten kopie ein, nur um das ding zu transformieren ^^
Reden koennen wir, wenn ma zeilenweise daten in tabellarischer form per stringstream reinholt, und sich mehrere atoi's damit erspart ....

Wobei natuerlich die Linux ausgaben meist so designt sind, das ma die schoen mit streams abhandeln kann ....

while(!file.eof())
{
unsigned int major = 0;
unsigned int minor = 0;
unsigned int blocks = 0;
std::string strName;
file >> major;
file >> minor;
file >> blocks;
file >> strName;
}

Sollte eigentlich die sache nicht durcheinanderbringen ^^
in der ersten zeile sollte bei den ints uberall 0 stehen, weil er die zahlen nich erkennen kann (macht auch nen internes atoi)
ne leerzeilewo gar nix drinnen steht auser seperatoren und endl ignoriert er sowieso ....

Damit hat er sich jegliches parsen erspart, und damit is der ganze Spass vorbei ^^ (Die sehen nich umsonst grad so aus :-) )

Ciao ..

locus vivendi
08-12-2005, 18:43
while(!file.eof())
Das ist eine potentielle Endlosschleife. Wenn nämlich der Stream in einen Fehlerzustand übergeht, ohne das EOF erreicht ist, dann wird die Bedingung niemals wahr sein.

Primusio
08-12-2005, 20:57
Erst einmal vielen dank für eure Postings. Vor allem an RHBaum, hast sicher eine weile getippt für deine erste Antwort, danke ;-)

Ich habe nun versucht anhand eurer Hinweise das kleine Programm umzubauen aber bitte nicht böse werden, wenn es immer noch nicht perfekt ist :o

Also hier meine neue Version.



#include <iostream> // Header für Input (cin) und Output (cout)
#include <fstream> // Header für das arbeiten mit Dateien
#include <string> // Header zum bearbeiten von strings
#include <vector> // Header zum arbeiten mit Vectoren

// Erstellen der Klasse "Device"
// Funktionen in einer Klasse werden Methoden genannt
class Device
{
// Alles im Bereich "private" kann nur innerhalb der Klasse
// verwendet werden
private:
std::string deviceName; // Name der Festplatte
int deviceCapacity; // Größe der Festplatte
std::vector<std::string> partitions; // Name der Partition

// Alles im Bereich "public" kann auch ausserhalb der Klasse
// verwendet werden
public:
// Konstruktor zum Initialisieren der Klasse
Device(std::string name, int capacity);
// Methode zum Ausgeben des FestplattenDevices als String
std::string getDeviceName(void) { return deviceName; };
// Methode zum Ausgeben der Festplattengröße
int getDeviceCapacity(void) { return deviceCapacity; };
// Methode zum Ausgeben des PartitionsDevices als Vector
std::vector<std::string> getPartitions(void) { return partitions; };
// Methode zum hinzufügen einer Partition
void addDevicePart( std::string devicePart );
};

// Die Functionen selbst können sowohl direkt in der Klasse
// oder der Übersichts halber ausserhalb geschrieben werden
// Dazu gibt man die Klasse an "Device" dann "::" und anschließend den Namen
// der Funktion und dessen Parameter
Device::Device(std::string name, int capacity)
{
deviceName = name;
deviceCapacity = capacity;
};

void Device::addDevicePart( std::string devicePart )
{
partitions.push_back(devicePart);
};

// Initialisieren des deviceArray auf Basis der Klasse "Device"
std::vector<Device> deviceArray;

int main (int argc, char *argv[])
{
std::ifstream file("/proc/partitions"); // öffnen der Datei
std::string line; // temporäre Variable, zum Speichern der Dateizeile

// Die Schleife läuft für jede Zeile einmal durch
// und speichert die Zeile in "line"
while(getline(file, line))
{
// Nun Initialisieren wir jeweils eine Variable für jede Spalte in
// "/proc/partitions"
unsigned int major = 0;
unsigned int minor = 0;
unsigned int blocks= 0;
std::string strName;

// fstream erkennt selbst dass es sich um eine Tabellenform handelt
// und kann jede Spalte in der Reihenfolge des auftretens in einer
// Variable speichern, so wie wir dies nun tun.
file >> major;
file >> minor;
file >> blocks;
file >> strName;

// Nun prüfen wir ob in der letzten Spalte der aktuellen Zeile
// ein "hd" oder "sd" vorkommt, wenn ja handelt es sich entweder
// um eine IDE oder SCSI Festplatte / Partition
if( !strName.find("hd", 0) || !strName.find("sd", 0) )
{
// Nun prüfen wir ob die letzte Spalte 3 Zeichen hat
// 3 Zeichen bedeuten es handelt sich um eine Festplatte z.B. "hda"
// mehr Zeichen bedeutet, dass es sich um eine Partition handelt "hda1"
if(strName.size() == 3)
// Hat der String "strName" 3 Zeichen dann fügen wir ein Element
// dem Vector "deviceArray" am Ende hinzu mit "push_back"
// Dieses Feld füllen wir mit unserer Klasse, die wir mit Konstruktor
// von "Device" aufrufen und den Namen sowie die Größe mitgeben.
deviceArray.push_back( Device(strName, blocks) );
else
// Sollte es sich um eine Partition handeln, fügen wir diese mit der
// Klassenfunktion "addDeviceParts" dem letzten Element hinzu
// Dazu zählen wir, wieviele Elemente "deviceArray" schon hat
// und subtrahieren den Wert mit 1, da ein Array bei 0 beginnt.
deviceArray[ deviceArray.size()-1 ].addDevicePart(strName);
}
}
return 0;
}


Und so, kann man auf "deviceArray" dank der Klasse zugreifen.



// Gibt die Anzahl der Festplatten aus
std::cout << deviceArray.size() << std::endl;

// Gibt die Anzahl der Partitionen der ersten Festplatte aus
std::cout << deviceArray[0].getPartitions().size() << std::endl;

// Gibt das Device von einer Partition von der ersten Festplatte aus
std::cout << deviceArray[0].getPartitions()[0] << std::endl;

// Gibt das Device der ersten Festplatte aus
std::cout << deviceArray[0].getDeviceName() << std::endl;

// Gibt die Größe der ersten Festplatte in kB aus
std::cout << deviceArray[0].getDeviceCapacity() << std::endl;


Viel weniger Code als vorher und damit viel übersichtlicher. Nur vielleicht etwas zuviel Kommentar aber vielleicht nützt es ja mal jemanden, der etwas in der Art sucht.

Auch jetzt interessieren mich wieder die Meinungen von euch allen.
Ist das jetzt besseres C++ ?
Kann man noch etwas verbessern oder effizienter gestallten ?

Es muss nochmal dazu gesagt werden, dass dies kein HowTo oder Anleitung sein soll, es dient nur als kleiner Wegweiser, schließlich war es mir etwas zu viel noch das Prinzip von Klassen zu erklären, dies wird auch in Lehrbüchern auf mehrere Seiten verteilt.

Also los schreibt mir, will es noch verbessern ;)

MfG
Primusio

PS: Warum gibt es bei mrunix eigentlich kein Forum wo man speziell komplette Programme oder Funktionen posten kann ?

RHBaum
09-12-2005, 08:25
@locus vivendi
Natuerlich hasst du recht ... den fehlerfall abfangen wuerde die sache noch robuster machen ....

Uf der anderen seite, wenn file an der stelle noch ok ist, was soll da noch schiefgehen, ausser wer anderes schreibt grad in dem file rum :-) Dann hasst aber auch andere probleme ^^

Aber schaden wuerd es natuerlich nicht, sollt ja auch nurn beispiel sein ^^

Ciao ...

RHBaum
09-12-2005, 08:34
@Primusio

getDeviceName(void)
Das ist C Unsitte :-)
In C++ sagt man ner funktion, das sie keine paramater hat, in dem man ihr gar keien gibt !
getDeviceName() ist in C++ richtig !

Um deine klasse noch flexibler zu machen :

Alle funktionen die deine klasse(member) nicht veraendern, solltest du als const deklarieren, damit du sie auch an einem constanten Object ausfuehren kannst .... und klar dem anwender auch expliziet mitteilst, das das Teil am Object nix anfasst :-)

// Methode zum Ausgeben des FestplattenDevices als String
std::string getDeviceName() const { return deviceName; };
// Methode zum Ausgeben der Festplattengröße
int getDeviceCapacity() const { return deviceCapacity; };

die Partitionen wuerd ich auch noch in ne klasse basteln ... iss aber ansichtssache ...

Ciao ...

Primusio
09-12-2005, 08:35
while(!file.eof())
{


erzeugt zum Beispiel in meinem Fall eine Endlosschleife, aus diesem Grund bin ich auch vorerst bei getline geblieben.

RHBaum
09-12-2005, 08:43
Urks, dann hab ich im moment grad nen Denkfehler^^

Kanns auch nich nachvollziehen, weil ich grad kein /proc filesystem zur hand hab, und auch keinen wirklich standard konformen c++ compiler ^^

Das lehrt uns natuerlich wieder eines, man sollt nie Code einfach so uebernehmen, sondern immer schoen kritisch beobachten ^^

Ciao ...

anda_skoa
11-12-2005, 14:40
hab ichs in nem String, bin ich nich so c++ fanatisch, und handel mir die kosten einer erneuten kopie ein, nur um das ding zu transformieren ^^


Wenn du vorher mittels einer anderen Methode festgestellt hast, daß der parsende String tatsächlich ein int ist, keine Problem.

Meiner Erfahrung nach kommen solche Werte aber meistens aus einer Textdatei und es liegt zur Laufzeit keinerlei Garantie für die Korrektheit der Daten vor.

Die Möglichkeit selber eine Kontrollroutine zu schreiben ist in meine Augen dann wesentlich aufwendiger als es dem Stringstream zu überlassen.

@Primusio: du kannst Funktionen, oder wirklich kleine Programme, im Unterforum für Tutorials, Tipps und Tricks posten.

Ciao,
_