a lot of love
authorLennart Poettering <lennart@poettering.net>
Mon, 30 Jun 2008 19:50:56 +0000 (21:50 +0200)
committerLennart Poettering <lennart@poettering.net>
Mon, 30 Jun 2008 19:50:56 +0000 (21:50 +0200)
.gitignore
Makefile
gnome-disk-health.glade [new file with mode: 0644]
gnome-disk-health.vala [new file with mode: 0644]
skdump.c
sktest.c [new file with mode: 0644]
smart.c
smart.h
smart.vapi [new file with mode: 0644]
smartkitd.vala [new file with mode: 0644]

index 884e119..05900f6 100644 (file)
@@ -1,2 +1,10 @@
 skdump
+sktest
+gnome-disk-health
+gnome-disk-health.c
+gnome-disk-health.h
+gnome-disk-health.ui
+smartkitd
+smartkitd.c
+smartkitd.h
 *.o
index 6621354..687adb5 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,22 @@
-CFLAGS=`pkg-config --cflags glib-2.0` -pipe -Wall -W -O0 -g
+CFLAGS=`pkg-config --cflags glib-2.0` -pipe -Wall -W -O0 -g -I.
 LIBS=`pkg-config --libs glib-2.0`
 
+all: skdump sktest smartkitd gnome-disk-health gnome-disk-health.ui
+
 skdump: smart.o skdump.o
        $(CC) -o $@ $^ $(CFLAGS) $(LIBS)
 
+sktest: smart.o sktest.o
+       $(CC) -o $@ $^ $(CFLAGS) $(LIBS)
+
+smartkitd: smart.c smartkitd.vala
+       valac --save-temps -g -o $@ --vapidir=. --pkg=smart --pkg=hal --pkg=dbus-glib-1 --Xcc=-I. $^
+
+gnome-disk-health: gnome-disk-health.vala
+       valac --save-temps -g -o $@ --pkg=gtk+-2.0 --pkg=dbus-glib-1 $^
+
+gnome-disk-health.ui: gnome-disk-health.glade
+       gtk-builder-convert $< $@
+
 clean:
-       rm -f skdump *.o
+       rm -f skdump sktest *.o smartkitd gnome-disk-health gnome-disk-health.ui 
diff --git a/gnome-disk-health.glade b/gnome-disk-health.glade
new file mode 100644 (file)
index 0000000..5453e84
--- /dev/null
@@ -0,0 +1,620 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--Generated with glade3 3.4.4 on Mon Jun 30 04:28:55 2008 -->
+<glade-interface>
+  <widget class="GtkDialog" id="DiskHealthDialog">
+    <property name="border_width">5</property>
+    <property name="title" translatable="yes">Disk Health</property>
+    <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+    <property name="icon_name">drive-harddisk</property>
+    <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+    <property name="has_separator">False</property>
+    <child internal-child="vbox">
+      <widget class="GtkVBox" id="dialog-vbox1">
+        <property name="visible">True</property>
+        <property name="spacing">2</property>
+        <child>
+          <widget class="GtkNotebook" id="notebook1">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <child>
+              <widget class="GtkVBox" id="vbox1">
+                <property name="visible">True</property>
+                <property name="border_width">12</property>
+                <child>
+                  <widget class="GtkHBox" id="hbox1">
+                    <property name="visible">True</property>
+                    <child>
+                      <widget class="GtkImage" id="image1">
+                        <property name="visible">True</property>
+                        <property name="xalign">0</property>
+                        <property name="yalign">0</property>
+                        <property name="xpad">12</property>
+                        <property name="ypad">12</property>
+                        <property name="icon_size">6</property>
+                        <property name="pixel_size">64</property>
+                        <property name="icon_name">drive-harddisk</property>
+                      </widget>
+                      <packing>
+                        <property name="expand">False</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <widget class="GtkTable" id="table1">
+                        <property name="visible">True</property>
+                        <property name="border_width">12</property>
+                        <property name="n_rows">5</property>
+                        <property name="n_columns">2</property>
+                        <property name="column_spacing">6</property>
+                        <property name="row_spacing">6</property>
+                        <child>
+                          <widget class="GtkLabel" id="firmwareLabel">
+                            <property name="visible">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">label</property>
+                          </widget>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">4</property>
+                            <property name="bottom_attach">5</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="serialLabel">
+                            <property name="visible">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">label</property>
+                          </widget>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">3</property>
+                            <property name="bottom_attach">4</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="modelLabel">
+                            <property name="visible">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">label</property>
+                          </widget>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">2</property>
+                            <property name="bottom_attach">3</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="sizeLabel">
+                            <property name="visible">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">label</property>
+                          </widget>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">1</property>
+                            <property name="bottom_attach">2</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="pathLabel">
+                            <property name="visible">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">label</property>
+                          </widget>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="label9">
+                            <property name="visible">True</property>
+                            <property name="xalign">1</property>
+                            <property name="label" translatable="yes">&lt;b&gt;Firmware:&lt;/b&gt;</property>
+                            <property name="use_markup">True</property>
+                          </widget>
+                          <packing>
+                            <property name="top_attach">4</property>
+                            <property name="bottom_attach">5</property>
+                            <property name="x_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="label8">
+                            <property name="visible">True</property>
+                            <property name="xalign">1</property>
+                            <property name="label" translatable="yes">&lt;b&gt;Serial Number:&lt;/b&gt;</property>
+                            <property name="use_markup">True</property>
+                          </widget>
+                          <packing>
+                            <property name="top_attach">3</property>
+                            <property name="bottom_attach">4</property>
+                            <property name="x_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="label7">
+                            <property name="visible">True</property>
+                            <property name="xalign">1</property>
+                            <property name="label" translatable="yes">&lt;b&gt;Model:&lt;/b&gt;</property>
+                            <property name="use_markup">True</property>
+                          </widget>
+                          <packing>
+                            <property name="top_attach">2</property>
+                            <property name="bottom_attach">3</property>
+                            <property name="x_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="label6">
+                            <property name="visible">True</property>
+                            <property name="xalign">1</property>
+                            <property name="label" translatable="yes">&lt;b&gt;Size:&lt;/b&gt;</property>
+                            <property name="use_markup">True</property>
+                          </widget>
+                          <packing>
+                            <property name="top_attach">1</property>
+                            <property name="bottom_attach">2</property>
+                            <property name="x_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="label5">
+                            <property name="visible">True</property>
+                            <property name="xalign">1</property>
+                            <property name="label" translatable="yes">&lt;b&gt;Path:&lt;/b&gt;</property>
+                            <property name="use_markup">True</property>
+                          </widget>
+                          <packing>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                      </widget>
+                      <packing>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkHSeparator" id="hseparator1">
+                    <property name="visible">True</property>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkVBox" id="vbox2">
+                    <property name="visible">True</property>
+                    <property name="spacing">24</property>
+                    <child>
+                      <widget class="GtkLabel" id="smartLabel">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes">Disk health functionality available (S.M.A.R.T.)</property>
+                      </widget>
+                      <packing>
+                        <property name="expand">False</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <widget class="GtkLabel" id="healthLabel">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes">Disk is healthy.</property>
+                      </widget>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <widget class="GtkLabel" id="badSectorsLabel">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes">A few sectors have been reallocated.</property>
+                      </widget>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="position">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <widget class="GtkLabel" id="temperatureLabel">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes">Current disk temperature is 40°C.</property>
+                      </widget>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="position">3</property>
+                      </packing>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="padding">16</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </widget>
+            </child>
+            <child>
+              <widget class="GtkLabel" id="label1">
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">Summary</property>
+              </widget>
+              <packing>
+                <property name="type">tab</property>
+                <property name="tab_fill">False</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkVBox" id="vbox3">
+                <property name="visible">True</property>
+                <property name="border_width">12</property>
+                <property name="spacing">6</property>
+                <child>
+                  <widget class="GtkLabel" id="label19">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">S.M.A.R.T. Attributes:</property>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkScrolledWindow" id="scrolledwindow1">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                    <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                    <property name="shadow_type">GTK_SHADOW_IN</property>
+                    <child>
+                      <widget class="GtkTreeView" id="attributeTreeView">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                      </widget>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkExpander" id="expander1">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <child>
+                      <widget class="GtkTable" id="table2">
+                        <property name="visible">True</property>
+                        <property name="n_rows">7</property>
+                        <property name="n_columns">2</property>
+                        <property name="column_spacing">6</property>
+                        <property name="row_spacing">6</property>
+                        <child>
+                          <widget class="GtkLabel" id="verdictLabel">
+                            <property name="visible">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">label</property>
+                          </widget>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">6</property>
+                            <property name="bottom_attach">7</property>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="label33">
+                            <property name="visible">True</property>
+                            <property name="xalign">1</property>
+                            <property name="label" translatable="yes">&lt;b&gt;Verdict:&lt;/b&gt;</property>
+                            <property name="use_markup">True</property>
+                          </widget>
+                          <packing>
+                            <property name="top_attach">6</property>
+                            <property name="bottom_attach">7</property>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="typeLabel">
+                            <property name="visible">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">label</property>
+                          </widget>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">5</property>
+                            <property name="bottom_attach">6</property>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="thresholdLabel">
+                            <property name="visible">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">label</property>
+                          </widget>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">4</property>
+                            <property name="bottom_attach">5</property>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="worstLabel">
+                            <property name="visible">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">label</property>
+                          </widget>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">3</property>
+                            <property name="bottom_attach">4</property>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="currentLabel">
+                            <property name="visible">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">label</property>
+                          </widget>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">2</property>
+                            <property name="bottom_attach">3</property>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="idLabel">
+                            <property name="visible">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">label</property>
+                          </widget>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">1</property>
+                            <property name="bottom_attach">2</property>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="nameLabel">
+                            <property name="visible">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">label</property>
+                          </widget>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="label26">
+                            <property name="visible">True</property>
+                            <property name="xalign">1</property>
+                            <property name="label" translatable="yes">&lt;b&gt;Type:&lt;/b&gt;</property>
+                            <property name="use_markup">True</property>
+                          </widget>
+                          <packing>
+                            <property name="top_attach">5</property>
+                            <property name="bottom_attach">6</property>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="label25">
+                            <property name="visible">True</property>
+                            <property name="xalign">1</property>
+                            <property name="label" translatable="yes">&lt;b&gt;Threshold:&lt;/b&gt;</property>
+                            <property name="use_markup">True</property>
+                          </widget>
+                          <packing>
+                            <property name="top_attach">4</property>
+                            <property name="bottom_attach">5</property>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="label24">
+                            <property name="visible">True</property>
+                            <property name="xalign">1</property>
+                            <property name="label" translatable="yes">&lt;b&gt;Worst Value:&lt;/b&gt;</property>
+                            <property name="use_markup">True</property>
+                          </widget>
+                          <packing>
+                            <property name="top_attach">3</property>
+                            <property name="bottom_attach">4</property>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="label23">
+                            <property name="visible">True</property>
+                            <property name="xalign">1</property>
+                            <property name="label" translatable="yes">&lt;b&gt;Current Value:&lt;/b&gt;</property>
+                            <property name="use_markup">True</property>
+                          </widget>
+                          <packing>
+                            <property name="top_attach">2</property>
+                            <property name="bottom_attach">3</property>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="label22">
+                            <property name="visible">True</property>
+                            <property name="xalign">1</property>
+                            <property name="label" translatable="yes">&lt;b&gt;ID:&lt;/b&gt;</property>
+                            <property name="use_markup">True</property>
+                          </widget>
+                          <packing>
+                            <property name="top_attach">1</property>
+                            <property name="bottom_attach">2</property>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="label21">
+                            <property name="visible">True</property>
+                            <property name="xalign">1</property>
+                            <property name="label" translatable="yes">&lt;b&gt;Name:&lt;/b&gt;</property>
+                            <property name="use_markup">True</property>
+                          </widget>
+                          <packing>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                      </widget>
+                    </child>
+                    <child>
+                      <widget class="GtkLabel" id="label20">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes">Explanation</property>
+                      </widget>
+                      <packing>
+                        <property name="type">label_item</property>
+                      </packing>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkLabel" id="label2">
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">Details</property>
+              </widget>
+              <packing>
+                <property name="type">tab</property>
+                <property name="position">1</property>
+                <property name="tab_fill">False</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkVBox" id="vbox4">
+                <property name="visible">True</property>
+                <property name="border_width">12</property>
+                <property name="spacing">16</property>
+                <child>
+                  <widget class="GtkHBox" id="hbox3">
+                    <property name="visible">True</property>
+                    <property name="spacing">6</property>
+                    <child>
+                      <widget class="GtkProgressBar" id="progressbar1">
+                        <property name="visible">True</property>
+                      </widget>
+                    </child>
+                    <child>
+                      <widget class="GtkButton" id="button2">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="label" translatable="yes">Start Self-Test</property>
+                        <property name="response_id">0</property>
+                      </widget>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="label10">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">Self-test functionality available. Approximate run-time is 120 min.</property>
+                    <property name="wrap">True</property>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="position">2</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkLabel" id="label3">
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">Self-Test</property>
+              </widget>
+              <packing>
+                <property name="type">tab</property>
+                <property name="position">2</property>
+                <property name="tab_fill">False</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <widget class="GtkHButtonBox" id="dialog-action_area1">
+            <property name="visible">True</property>
+            <property name="layout_style">GTK_BUTTONBOX_END</property>
+            <child>
+              <widget class="GtkButton" id="button1">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="label" translatable="yes">gtk-close</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">0</property>
+              </widget>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">GTK_PACK_END</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
+</glade-interface>
diff --git a/gnome-disk-health.vala b/gnome-disk-health.vala
new file mode 100644 (file)
index 0000000..011f7c0
--- /dev/null
@@ -0,0 +1,137 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+    This file is part of SmartKit.
+
+    Copyright 2008 Lennart Poettering
+
+    libcanberra is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as
+    published by the Free Software Foundation, either version 2.1 of the
+    License, or (at your option) any later version.
+
+    libcanberra is distributed in the hope that it will be useful, but
+    WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with libcanberra. If not, If not, see
+    <http://www.gnu.org/licenses/>.
+***/
+
+using DBus;
+using GLib;
+using Gtk;
+
+public class DiskHealth : Gtk.Builder {
+
+        string uifile = "gnome-disk-health.ui";
+
+        public bool create_widgets(string disk_string) {
+                try {
+                        add_from_file (uifile);
+                        Gtk.Widget window = (Gtk.Widget) get_object("DiskHealthDialog");
+
+                        if (!fill_in_data(disk_string))
+                                return false;
+
+                        window.show_all();
+                        window.destroy += Gtk.main_quit;
+                } catch (GLib.Error e) {
+                        stderr.printf("Failed to create main window: %s\n", e.message);
+                        return false;
+                }
+
+                return true;
+        }
+
+        private DBus.Connection connection;
+        private dynamic DBus.Object manager;
+        private DBus.ObjectPath dbus_path;
+        private dynamic DBus.Object disk;
+
+        public bool fill_in_data(string disk_string) {
+
+                try {
+                        connection = DBus.Bus.get(DBus.BusType.SYSTEM);
+
+                        manager = connection.get_object("net.poettering.SmartKit", "/", "net.poettering.SmartKit.Manager");
+
+                        DBus.ObjectPath p;
+
+                        try {
+                                p = manager.getDiskByPath(disk_string);
+                        } catch (DBus.Error e) {
+                                try {
+                                        p = manager.getDiskByUDI(disk_string);
+                                } catch (DBus.Error e) {
+                                        return false;
+                                }
+                        }
+
+                        stderr.printf("Using D-Bus path %s\n", p);
+
+                        disk = connection.get_object("net.poettering.SmartKit", p, "net.poettering.SmartKit.Disk");
+
+                        ((Gtk.Label) get_object("pathLabel")).set_label(disk.getPath());
+                        ((Gtk.Label) get_object("sizeLabel")).set_label(pretty_size(disk.getSize()));
+
+                        bool b = disk.isIdentifyAvailable();
+
+                        if (b) {
+                                ((Gtk.Label) get_object("modelLabel")).set_label(disk.getIdentifyModel());
+                                ((Gtk.Label) get_object("serialLabel")).set_label(disk.getIdentifySerial());
+                                ((Gtk.Label) get_object("firmwareLabel")).set_label(disk.getIdentifyFirmware());
+                        } else {
+                                ((Gtk.Label) get_object("modelLabel")).set_label("n/a");
+                                ((Gtk.Label) get_object("serialLabel")).set_label("n/a");
+                                ((Gtk.Label) get_object("firmwareLabel")).set_label("n/a");
+                        }
+
+                        if (b)
+                                b = disk.isSmartAvailable();
+
+                        if (b) {
+                                ((Gtk.Label) get_object("smartLabel")).set_label("Disk health functionality (S.M.A.R.T.) is available.");
+                        } else {
+                                ((Gtk.Label) get_object("smartLabel")).set_markup("Disk health functionality (S.M.A.R.T.) is <b>not</b> available.");
+                                ((Gtk.Label) get_object("healthLabel")).set_label("");
+                                ((Gtk.Label) get_object("badSectorsLabel")).set_label("");
+                                ((Gtk.Label) get_object("temperatureLabel")).set_label("");
+                        }
+
+                } catch (DBus.Error e) {
+                        stderr.printf("D-Bus error: %s\n", e.message);
+                        return false;
+                }
+
+                return true;
+        }
+
+        public static string pretty_size(uint64 size) {
+
+                if (size >= (uint64)1024*(uint64)1024*(uint64)1024*(uint64)1024)
+                        return "%0.1f TiB".printf((double) size/1024/1024/1024/1024);
+                else if (size >= (uint64)1024*(uint64)1024*(uint64)1024)
+                        return "%0.1f GiB".printf((double) size/1024/1024/1024);
+                else if (size >= (uint64)1024*(uint64)1024)
+                        return "%0.1f MiB".printf((double) size/1024/1024);
+                else if (size >= (uint64)1024)
+                        return "%0.1f KiB".printf((double) size/1024);
+                else
+                        return "%u B".printf((uint) size);
+        }
+
+
+        public static int main (string[] args) {
+                Gtk.init(ref args);
+
+                var dh  = new DiskHealth();
+
+                if (dh.create_widgets(args[1]))
+                        Gtk.main ();
+
+                return 0;
+        }
+}
index 6fa9167..7d1cdb6 100644 (file)
--- a/skdump.c
+++ b/skdump.c
@@ -1,23 +1,53 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+    This file is part of SmartKit.
+
+    Copyright 2008 Lennart Poettering
+
+    libcanberra is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as
+    published by the Free Software Foundation, either version 2.1 of the
+    License, or (at your option) any later version.
+
+    libcanberra is distributed in the hope that it will be useful, but
+    WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with libcanberra. If not, If not, see
+    <http://www.gnu.org/licenses/>.
+***/
+
 #include <string.h>
 #include <errno.h>
 
 #include "smart.h"
 
 int main(int argc, char *argv[]) {
-    int ret;
-    const char *device;
-    SkDevice *d;
+        int ret;
+        const char *device;
+        SkDisk *d;
+
+        if (argc != 2) {
+                g_printerr("%s [DEVICE]", argv[0]);
+                return 1;
+        }
 
-    device = argc >= 2 ? argv[1] : "/dev/sda";
+        device = argv[1];
 
-    if ((ret = sk_disk_open(device, &d)) < 0) {
-        g_printerr("Failed to open disk %s: %s\n", device, strerror(errno));
-        return 1;
-    }
+        if ((ret = sk_disk_open(device, &d)) < 0) {
+                g_printerr("Failed to open disk %s: %s\n", device, g_strerror(errno));
+                return 1;
+        }
 
-    sk_disk_dump(d);
+        if ((ret = sk_disk_dump(d)) < 0) {
+                g_printerr("Failed to dump disk data: %s\n", g_strerror(errno));
+                return 1;
+        }
 
-    sk_disk_free(d);
+        sk_disk_free(d);
 
-    return 0;
+        return 0;
 }
diff --git a/sktest.c b/sktest.c
new file mode 100644 (file)
index 0000000..374b11c
--- /dev/null
+++ b/sktest.c
@@ -0,0 +1,66 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+    This file is part of SmartKit.
+
+    Copyright 2008 Lennart Poettering
+
+    libcanberra is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as
+    published by the Free Software Foundation, either version 2.1 of the
+    License, or (at your option) any later version.
+
+    libcanberra is distributed in the hope that it will be useful, but
+    WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with libcanberra. If not, If not, see
+    <http://www.gnu.org/licenses/>.
+***/
+
+#include <string.h>
+#include <errno.h>
+
+#include "smart.h"
+
+int main(int argc, char *argv[]) {
+        int ret;
+        const char *device;
+        SkDisk *d;
+        SkSmartSelfTest test;
+
+        if (argc < 3) {
+            g_printerr("%s [DEVICE] [short|extended|conveyance]\n", argv[0]);
+            return 1;
+        }
+
+        device = argv[1];
+
+        if (!g_strcasecmp(argv[2], sk_smart_self_test_to_string(SK_SMART_SELF_TEST_SHORT)))
+            test = SK_SMART_SELF_TEST_SHORT;
+        else if (!g_strcasecmp(argv[2], sk_smart_self_test_to_string(SK_SMART_SELF_TEST_EXTENDED)))
+            test = SK_SMART_SELF_TEST_EXTENDED;
+        else if (!(g_strcasecmp(argv[2], sk_smart_self_test_to_string(SK_SMART_SELF_TEST_CONVEYANCE))))
+            test = SK_SMART_SELF_TEST_CONVEYANCE;
+        else {
+            g_printerr("Unknown test '%s'.\n", argv[2]);
+            return 1;
+        }
+
+        if ((ret = sk_disk_open(device, &d)) < 0) {
+                g_printerr("Failed to open disk %s: %s\n", device, g_strerror(errno));
+                return 1;
+        }
+
+        if ((ret = sk_disk_smart_self_test(d, test)) < 0) {
+                g_printerr("Failed to start sel-test: %s\n", g_strerror(errno));
+                return 1;
+
+        }
+
+        sk_disk_free(d);
+
+        return 0;
+}
diff --git a/smart.c b/smart.c
index 7d4c483..4cd9c6f 100644 (file)
--- a/smart.c
+++ b/smart.c
@@ -1,3 +1,25 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+    This file is part of SmartKit.
+
+    Copyright 2008 Lennart Poettering
+
+    libcanberra is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as
+    published by the Free Software Foundation, either version 2.1 of the
+    License, or (at your option) any later version.
+
+    libcanberra is distributed in the hope that it will be useful, but
+    WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with libcanberra. If not, If not, see
+    <http://www.gnu.org/licenses/>.
+***/
+
 #ifdef HAVE_CONFIG_H
 #include <config.h>
 #endif
@@ -6,6 +28,8 @@
 #include <unistd.h>
 #include <errno.h>
 #include <string.h>
+#include <stdio.h>
+#include <sys/stat.h>
 #include <sys/ioctl.h>
 #include <scsi/scsi.h>
 #include <scsi/sg.h>
 #define SK_TIMEOUT 2000
 
 typedef enum SkDirection {
-    SK_DIRECTION_NONE,
-    SK_DIRECTION_IN,
-    SK_DIRECTION_OUT,
-    _SK_DIRECTION_MAX
+        SK_DIRECTION_NONE,
+        SK_DIRECTION_IN,
+        SK_DIRECTION_OUT,
+        _SK_DIRECTION_MAX
 } SkDirection;
 
 typedef enum SkDiskType {
-    SK_DISK_TYPE_ATA_PASSTHROUGH, /* ATA passthrough over SCSI transport */
-    SK_DISK_TYPE_ATA,
-    SK_DISK_TYPE_UNKNOWN,
-    _SK_DISK_TYPE_MAX
+        SK_DISK_TYPE_ATA_PASSTHROUGH, /* ATA passthrough over SCSI transport */
+        SK_DISK_TYPE_ATA,
+        SK_DISK_TYPE_UNKNOWN,
+        _SK_DISK_TYPE_MAX
 } SkDiskType;
 
 struct SkDisk {
-    gchar *name;
-    int fd;
-    SkDiskType type;
+        gchar *name;
+        int fd;
+        SkDiskType type;
 
-    guint64 size;
+        guint64 size;
 
-    guint8 identify[512];
-    guint8 smart_data[512];
-    guint8 smart_threshold_data[512];
+        guint8 identify[512];
+        guint8 smart_data[512];
+        guint8 smart_threshold_data[512];
 
-    gboolean identify_data_valid:1;
-    gboolean smart_data_valid:1;
-    gboolean smart_threshold_data_valid:1;
+        gboolean identify_data_valid:1;
+        gboolean smart_data_valid:1;
+        gboolean smart_threshold_data_valid:1;
 
-    SkIdentifyParsedData identify_parsed_data;
-    SkSmartParsedData smart_parsed_data;
+        SkIdentifyParsedData identify_parsed_data;
+        SkSmartParsedData smart_parsed_data;
 };
 
 /* ATA commands */
 typedef enum SkAtaCommand {
-    SK_ATA_COMMAND_IDENTIFY_DEVICE = 0xEC,
-    SK_ATA_COMMAND_IDENTIFY_PACKET_DEVICE = 0xA1,
-    SK_ATA_COMMAND_SMART = 0xB0,
-    SK_ATA_COMMAND_CHECK_POWER_MODE = 0xE5
+        SK_ATA_COMMAND_IDENTIFY_DEVICE = 0xEC,
+        SK_ATA_COMMAND_IDENTIFY_PACKET_DEVICE = 0xA1,
+        SK_ATA_COMMAND_SMART = 0xB0,
+        SK_ATA_COMMAND_CHECK_POWER_MODE = 0xE5
 } SkAtaCommand;
 
 /* ATA SMART subcommands (ATA8 7.52.1) */
 typedef enum SkSmartCommand {
-    SK_SMART_COMMAND_READ_DATA = 0xD0,
-    SK_SMART_COMMAND_READ_THRESHOLDS = 0xD1,
-    SK_SMART_COMMAND_EXECUTE_OFFLINE_IMMEDIATE = 0xD4,
-    SK_SMART_COMMAND_ENABLE_OPERATIONS = 0xD8,
-    SK_SMART_COMMAND_DISABLE_OPERATIONS = 0xD9,
-    SK_SMART_COMMAND_RETURN_STATUS = 0xDA
+        SK_SMART_COMMAND_READ_DATA = 0xD0,
+        SK_SMART_COMMAND_READ_THRESHOLDS = 0xD1,
+        SK_SMART_COMMAND_EXECUTE_OFFLINE_IMMEDIATE = 0xD4,
+        SK_SMART_COMMAND_ENABLE_OPERATIONS = 0xD8,
+        SK_SMART_COMMAND_DISABLE_OPERATIONS = 0xD9,
+        SK_SMART_COMMAND_RETURN_STATUS = 0xDA
 } SkSmartCommand;
 
-/* ATA SMART test type (ATA8 7.52.5.2) */
-typedef enum SkSmartTest {
-    SK_SMART_TEST_OFFLINE_FULL = 0,
-    SK_SMART_TEST_OFFLINE_SHORT = 1,
-    SK_SMART_TEST_OFFLINE_EXTENDED = 2,
-    SK_SMART_TEST_OFFLINE_CONVEYANCE = 3,
-    SK_SMART_TEST_OFFLINE_SELECTIVE = 4,
+static gboolean disk_smart_is_available(SkDisk *d) {
+        return d->identify_data_valid && !!(d->identify[164] & 1);
+}
+
+static gboolean disk_smart_is_enabled(SkDisk *d) {
+        return d->identify_data_valid && !!(d->identify[170] & 1);
+}
 
-    SK_SMART_TEST_CAPTIVE_SHORT = 129,
-    SK_SMART_TEST_CAPTIVE_EXTENDED = 130,
-    SK_SMART_TEST_CAPTIVE_CONVEYANCE = 131,
-    SK_SMART_TEST_CAPTIVE_SELECTIVE = 132,
+static gboolean disk_smart_is_conveyance_test_available(SkDisk *d) {
+        g_assert(d->smart_data_valid);
 
-    SK_SMART_TEST_CAPTIVE_MASK = 128,
-    SK_SMART_TEST_ABORT = 127
-} SkSmartTest;
+        return !!(d->smart_data[367] & 32);
+}
+static gboolean disk_smart_is_short_and_extended_test_available(SkDisk *d) {
+        g_assert(d->smart_data_valid);
 
-static gboolean disk_smart_is_available(SkDisk *d) {
-    return d->identify_data_valid && !!(d->identify[164] & 1);
+        return !!(d->smart_data[367] & 16);
 }
 
-static gboolean disk_smart_is_enabled(SkDisk *d) {
-    return d->identify_data_valid && !!(d->identify[170] & 1);
+static gboolean disk_smart_is_start_test_available(SkDisk *d) {
+        g_assert(d->smart_data_valid);
+
+        return !!(d->smart_data[367] & 1);
+}
+
+static gboolean disk_smart_is_abort_test_available(SkDisk *d) {
+        g_assert(d->smart_data_valid);
+
+        return !!(d->smart_data[367] & 41);
 }
 
 static int disk_ata_command(SkDisk *d, SkAtaCommand command, SkDirection direction, gpointer cmd_data, gpointer data, size_t *len) {
-    guint8 *bytes = cmd_data;
-    int ret;
+        guint8 *bytes = cmd_data;
+        int ret;
 
-    g_assert(d->type == SK_DISK_TYPE_ATA);
+        g_assert(d->type == SK_DISK_TYPE_ATA);
 
-    switch (direction) {
+        switch (direction) {
 
-        case SK_DIRECTION_OUT:
+                case SK_DIRECTION_OUT:
 
-            /* We could use HDIO_DRIVE_TASKFILE here, but that's a
-             * deprecated ioctl(), hence we don't do it. */
+                        /* We could use HDIO_DRIVE_TASKFILE here, but
+                         * that's a deprecated ioctl(), hence we don't
+                         * do it. And we don't need writing anyway. */
 
-            errno = ENOTSUP;
-            return -1;
+                        errno = ENOTSUP;
+                        return -1;
 
-        case SK_DIRECTION_IN: {
-            guint8 *ioctl_data;
+                case SK_DIRECTION_IN: {
+                        guint8 *ioctl_data;
 
-            /* We have HDIO_DRIVE_CMD which can only read, but not write,
-             * and cannot do LBA. We use it for all read commands. */
+                        /* We have HDIO_DRIVE_CMD which can only read, but not write,
+                         * and cannot do LBA. We use it for all read commands. */
 
-            ioctl_data = g_alloca(4 + *len);
-            memset(ioctl_data, 0, 4 + *len);
+                        ioctl_data = g_alloca(4 + *len);
+                        memset(ioctl_data, 0, 4 + *len);
 
-            ioctl_data[0] = (guint8) command;  /* COMMAND */
-            ioctl_data[1] = ioctl_data[0] == WIN_SMART ? bytes[9] : bytes[3];  /* SECTOR/NSECTOR */
-            ioctl_data[2] = bytes[1];          /* FEATURE */
-            ioctl_data[3] = bytes[3];          /* NSECTOR */
+                        ioctl_data[0] = (guint8) command;  /* COMMAND */
+                        ioctl_data[1] = ioctl_data[0] == WIN_SMART ? bytes[9] : bytes[3];  /* SECTOR/NSECTOR */
+                        ioctl_data[2] = bytes[1];          /* FEATURE */
+                        ioctl_data[3] = bytes[3];          /* NSECTOR */
 
-            if ((ret = ioctl(d->fd, HDIO_DRIVE_CMD, ioctl_data)) < 0)
-                return ret;
+                        if ((ret = ioctl(d->fd, HDIO_DRIVE_CMD, ioctl_data)) < 0)
+                                return ret;
 
-            memset(bytes, 0, 12);
-            bytes[11] = ioctl_data[0];
-            bytes[1] = ioctl_data[1];
-            bytes[3] = ioctl_data[2];
+                        memset(bytes, 0, 12);
+                        bytes[11] = ioctl_data[0];
+                        bytes[1] = ioctl_data[1];
+                        bytes[3] = ioctl_data[2];
 
-            memcpy(data, ioctl_data+4, *len);
+                        memcpy(data, ioctl_data+4, *len);
 
-            return ret;
-        }
+                        return ret;
+                }
 
-        case SK_DIRECTION_NONE: {
-            guint8 ioctl_data[7];
+                case SK_DIRECTION_NONE: {
+                        guint8 ioctl_data[7];
 
-            /* We have HDIO_DRIVE_TASK which can neither read nor
-             * write, but can do LBA. We use it for all commands that
-             * do neither read nor write */
+                        /* We have HDIO_DRIVE_TASK which can neither read nor
+                         * write, but can do LBA. We use it for all commands that
+                         * do neither read nor write */
 
-            memset(ioctl_data, 0, sizeof(ioctl_data));
+                        memset(ioctl_data, 0, sizeof(ioctl_data));
 
-            ioctl_data[0] = (guint8) command;  /* COMMAND */
-            ioctl_data[1] = bytes[1];         /* FEATURE */
-            ioctl_data[2] = bytes[3];         /* NSECTOR */
+                        ioctl_data[0] = (guint8) command;  /* COMMAND */
+                        ioctl_data[1] = bytes[1];         /* FEATURE */
+                        ioctl_data[2] = bytes[3];         /* NSECTOR */
 
-            ioctl_data[3] = bytes[9];         /* LBA LOW */
-            ioctl_data[4] = bytes[8];         /* LBA MID */
-            ioctl_data[5] = bytes[7];         /* LBA HIGH */
-            ioctl_data[6] = bytes[10];        /* SELECT */
+                        ioctl_data[3] = bytes[9];         /* LBA LOW */
+                        ioctl_data[4] = bytes[8];         /* LBA MID */
+                        ioctl_data[5] = bytes[7];         /* LBA HIGH */
+                        ioctl_data[6] = bytes[10];        /* SELECT */
 
-            if ((ret = ioctl(d->fd, HDIO_DRIVE_TASK, ioctl_data)))
-                return ret;
+                        if ((ret = ioctl(d->fd, HDIO_DRIVE_TASK, ioctl_data)))
+                                return ret;
 
-            memset(bytes, 0, 12);
-            bytes[11] = ioctl_data[0];
-            bytes[1] = ioctl_data[1];
-            bytes[3] = ioctl_data[2];
+                        memset(bytes, 0, 12);
+                        bytes[11] = ioctl_data[0];
+                        bytes[1] = ioctl_data[1];
+                        bytes[3] = ioctl_data[2];
 
-            bytes[9] = ioctl_data[3];
-            bytes[8] = ioctl_data[4];
-            bytes[7] = ioctl_data[5];
+                        bytes[9] = ioctl_data[3];
+                        bytes[8] = ioctl_data[4];
+                        bytes[7] = ioctl_data[5];
 
-            bytes[10] = ioctl_data[6];
+                        bytes[10] = ioctl_data[6];
 
-            return ret;
-        }
+                        return ret;
+                }
 
-        default:
-            g_assert_not_reached();
-            return -1;
-    }
+                default:
+                        g_assert_not_reached();
+                        return -1;
+        }
 }
 
 /* Sends a SCSI command block */
 static int sg_io(int fd, int direction,
-          const void *cdb, size_t cdb_len,
-          void *data, size_t data_len,
-          void *sense, size_t sense_len) {
+                 const void *cdb, size_t cdb_len,
+                 void *data, size_t data_len,
+                 void *sense, size_t sense_len) {
 
-    struct sg_io_hdr io_hdr;
+        struct sg_io_hdr io_hdr;
 
-    memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
+        memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
 
-    io_hdr.interface_id = 'S';
-    io_hdr.cmdp = (unsigned char*) cdb;
-    io_hdr.cmd_len = cdb_len;
-    io_hdr.dxferp = data;
-    io_hdr.dxfer_len = data_len;
-    io_hdr.sbp = sense;
-    io_hdr.mx_sb_len = sense_len;
-    io_hdr.dxfer_direction = direction;
-    io_hdr.timeout = SK_TIMEOUT;
+        io_hdr.interface_id = 'S';
+        io_hdr.cmdp = (unsigned char*) cdb;
+        io_hdr.cmd_len = cdb_len;
+        io_hdr.dxferp = data;
+        io_hdr.dxfer_len = data_len;
+        io_hdr.sbp = sense;
+        io_hdr.mx_sb_len = sense_len;
+        io_hdr.dxfer_direction = direction;
+        io_hdr.timeout = SK_TIMEOUT;
 
-    return ioctl(fd, SG_IO, &io_hdr);
+        return ioctl(fd, SG_IO, &io_hdr);
 }
 
 static int disk_passthrough_command(SkDisk *d, SkAtaCommand command, SkDirection direction, gpointer cmd_data, gpointer data, size_t *len) {
-    guint8 *bytes = cmd_data;
-    guint8 cdb[16];
-    guint8 sense[32];
-    guint8 *desc = sense+8;
-    int ret;
+        guint8 *bytes = cmd_data;
+        guint8 cdb[16];
+        guint8 sense[32];
+        guint8 *desc = sense+8;
+        int ret;
 
-    static const int direction_map[] = {
-        [SK_DIRECTION_NONE] = SG_DXFER_NONE,
-        [SK_DIRECTION_IN] = SG_DXFER_FROM_DEV,
-        [SK_DIRECTION_OUT] = SG_DXFER_TO_DEV
-    };
+        static const int direction_map[] = {
+                [SK_DIRECTION_NONE] = SG_DXFER_NONE,
+                [SK_DIRECTION_IN] = SG_DXFER_FROM_DEV,
+                [SK_DIRECTION_OUT] = SG_DXFER_TO_DEV
+        };
 
-    g_assert(d->type == SK_DISK_TYPE_ATA_PASSTHROUGH);
+        g_assert(d->type == SK_DISK_TYPE_ATA_PASSTHROUGH);
 
-    /* ATA Pass-Through 16 byte command, as described in "T10 04-262r8
-     * ATA Command Pass-Through":
-     * http://www.t10.org/ftp/t10/document.04/04-262r8.pdf */
+        /* ATA Pass-Through 16 byte command, as described in "T10 04-262r8
+         * ATA Command Pass-Through":
+         * http://www.t10.org/ftp/t10/document.04/04-262r8.pdf */
 
-    memset(cdb, 0, sizeof(cdb));
+        memset(cdb, 0, sizeof(cdb));
 
-    cdb[0] = 0x85; /* OPERATION CODE: 16 byte pass through */
+        cdb[0] = 0x85; /* OPERATION CODE: 16 byte pass through */
 
-    if (direction == SK_DIRECTION_NONE) {
-        cdb[1] = 3 << 1; /* PROTOCOL: Non-Data */
-        cdb[2] = 0x20;     /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=0, T_LENGTH=0 */
+        if (direction == SK_DIRECTION_NONE) {
+                cdb[1] = 3 << 1;   /* PROTOCOL: Non-Data */
+                cdb[2] = 0x20;     /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=0, T_LENGTH=0 */
 
-    } else if (direction == SK_DIRECTION_IN) {
-        cdb[1] = 4 << 1; /* PROTOCOL: PIO Data-in */
-        cdb[2] = 0x2e;     /* OFF_LINE=0, CK_COND=1, T_DIR=1, BYT_BLOK=1, T_LENGTH=2 */
+        } else if (direction == SK_DIRECTION_IN) {
+                cdb[1] = 4 << 1;   /* PROTOCOL: PIO Data-in */
+                cdb[2] = 0x2e;     /* OFF_LINE=0, CK_COND=1, T_DIR=1, BYT_BLOK=1, T_LENGTH=2 */
 
-    } else if (direction == SK_DIRECTION_OUT) {
-        cdb[1] = 5 << 1; /* PROTOCOL: PIO Data-Out */
-        cdb[2] = 0x26;     /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=1, T_LENGTH=2 */
-    }
+        } else if (direction == SK_DIRECTION_OUT) {
+                cdb[1] = 5 << 1;   /* PROTOCOL: PIO Data-Out */
+                cdb[2] = 0x26;     /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=1, T_LENGTH=2 */
+        }
 
-    cdb[3] = bytes[0]; /* FEATURES */
-    cdb[4] = bytes[1];
+        cdb[3] = bytes[0]; /* FEATURES */
+        cdb[4] = bytes[1];
 
-    cdb[5] = bytes[2]; /* SECTORS */
-    cdb[6] = bytes[3];
+        cdb[5] = bytes[2]; /* SECTORS */
+        cdb[6] = bytes[3];
 
-    cdb[8] = bytes[9]; /* LBA LOW */
-    cdb[10] = bytes[8]; /* LBA MED */
-    cdb[12] = bytes[7]; /* LBA HIGH */
+        cdb[8] = bytes[9]; /* LBA LOW */
+        cdb[10] = bytes[8]; /* LBA MED */
+        cdb[12] = bytes[7]; /* LBA HIGH */
 
-    cdb[13] = bytes[10] & 0x4F; /* SELECT */
-    cdb[14] = (guint8) command;
+        cdb[13] = bytes[10] & 0x4F; /* SELECT */
+        cdb[14] = (guint8) command;
 
-    if ((ret = sg_io(d->fd, direction_map[direction], cdb, sizeof(cdb), data, (size_t) cdb[6] * 512, sense, sizeof(sense))) < 0)
-        return ret;
+        if ((ret = sg_io(d->fd, direction_map[direction], cdb, sizeof(cdb), data, (size_t) cdb[6] * 512, sense, sizeof(sense))) < 0)
+                return ret;
 
-    if (sense[0] != 0x72 || desc[0] != 0x9 || desc[1] != 0x0c) {
-        errno = EIO;
-        return -1;
-    }
+        if (sense[0] != 0x72 || desc[0] != 0x9 || desc[1] != 0x0c) {
+                errno = EIO;
+                return -1;
+        }
 
-    memset(bytes, 0, 12);
+        memset(bytes, 0, 12);
 
-    bytes[1] = desc[3];
-    bytes[2] = desc[4];
-    bytes[3] = desc[5];
-    bytes[9] = desc[7];
-    bytes[8] = desc[9];
-    bytes[7] = desc[10];
-    bytes[10] = desc[12];
-    bytes[11] = desc[13];
+        bytes[1] = desc[3];
+        bytes[2] = desc[4];
+        bytes[3] = desc[5];
+        bytes[9] = desc[7];
+        bytes[8] = desc[9];
+        bytes[7] = desc[10];
+        bytes[10] = desc[12];
+        bytes[11] = desc[13];
 
-    return ret;
+        return ret;
 }
 
 static int disk_command(SkDisk *d, SkAtaCommand command, SkDirection direction, gpointer cmd_data, gpointer data, size_t *len) {
 
-    static int (* const disk_command_table[_SK_DISK_TYPE_MAX]) (SkDisk *d, SkAtaCommand command, SkDirection direction, gpointer cmd_data, gpointer data, size_t *len) = {
-        [SK_DISK_TYPE_ATA] = disk_ata_command,
-        [SK_DISK_TYPE_ATA_PASSTHROUGH] = disk_passthrough_command,
-    };
+        static int (* const disk_command_table[_SK_DISK_TYPE_MAX]) (SkDisk *d, SkAtaCommand command, SkDirection direction, gpointer cmd_data, gpointer data, size_t *len) = {
+                [SK_DISK_TYPE_ATA] = disk_ata_command,
+                [SK_DISK_TYPE_ATA_PASSTHROUGH] = disk_passthrough_command,
+        };
 
-    g_assert(d);
-    g_assert(d->type <= _SK_DISK_TYPE_MAX);
-    g_assert(direction <= _SK_DIRECTION_MAX);
+        g_assert(d);
+        g_assert(d->type <= _SK_DISK_TYPE_MAX);
+        g_assert(direction <= _SK_DIRECTION_MAX);
 
-    g_assert(direction == SK_DIRECTION_NONE || (data && len && *len > 0));
-    g_assert(direction != SK_DIRECTION_NONE || (!data && !len));
+        g_assert(direction == SK_DIRECTION_NONE || (data && len && *len > 0));
+        g_assert(direction != SK_DIRECTION_NONE || (!data && !len));
 
-    return disk_command_table[d->type](d, command, direction, cmd_data, data, len);
+        return disk_command_table[d->type](d, command, direction, cmd_data, data, len);
 }
 
 static int disk_identify_device(SkDisk *d) {
-    guint16 cmd[6];
-    int ret;
-    size_t len = 512;
+        guint16 cmd[6];
+        int ret;
+        size_t len = 512;
 
-    memset(cmd, 0, sizeof(cmd));
+        memset(cmd, 0, sizeof(cmd));
 
-    cmd[1] = GUINT16_TO_BE(1);
+        cmd[1] = GUINT16_TO_BE(1);
 
-    if ((ret = disk_command(d, SK_ATA_COMMAND_IDENTIFY_DEVICE, SK_DIRECTION_IN, cmd, d->identify, &len)) < 0)
-        return ret;
+        if ((ret = disk_command(d, SK_ATA_COMMAND_IDENTIFY_DEVICE, SK_DIRECTION_IN, cmd, d->identify, &len)) < 0)
+                return ret;
 
-    if (len != 512) {
-        errno = EIO;
-        return -1;
-    }
+        if (len != 512) {
+                errno = EIO;
+                return -1;
+        }
 
-    d->identify_data_valid = TRUE;
+        d->identify_data_valid = TRUE;
 
-    return 0;
+        return 0;
 }
 
-int sk_disk_check_power_mode(SkDisk *d, gboolean *mode) {
-    int ret;
-    guint16 cmd[6];
+int sk_disk_check_sleep_mode(SkDisk *d, gboolean *awake) {
+        int ret;
+        guint16 cmd[6];
 
-    if (!d->identify_data_valid) {
-        errno = ENOTSUP;
-        return -1;
-    }
+        if (!d->identify_data_valid) {
+                errno = ENOTSUP;
+                return -1;
+        }
 
-    memset(cmd, 0, sizeof(cmd));
+        memset(cmd, 0, sizeof(cmd));
 
-    if ((ret = disk_command(d, SK_ATA_COMMAND_CHECK_POWER_MODE, SK_DIRECTION_NONE, cmd, NULL, 0)) < 0)
-        return ret;
+        if ((ret = disk_command(d, SK_ATA_COMMAND_CHECK_POWER_MODE, SK_DIRECTION_NONE, cmd, NULL, 0)) < 0)
+                return ret;
 
-    if (cmd[0] != 0 || (GUINT16_FROM_BE(cmd[5]) & 1) != 0) {
-        errno = EIO;
-        return -1;
-    }
+        if (cmd[0] != 0 || (GUINT16_FROM_BE(cmd[5]) & 1) != 0) {
+                errno = EIO;
+                return -1;
+        }
 
-    *mode = GUINT16_FROM_BE(cmd[1]) == 0xFF;
+        *awake = GUINT16_FROM_BE(cmd[1]) == 0xFF;
 
-    return 0;
+        return 0;
 }
 
 static int disk_smart_enable(SkDisk *d, gboolean b) {
-    guint16 cmd[6];
+        guint16 cmd[6];
 
-    if (!disk_smart_is_available(d)) {
-        errno = ENOTSUP;
-        return -1;
-    }
+        if (!disk_smart_is_available(d)) {
+                errno = ENOTSUP;
+                return -1;
+        }
 
-    memset(cmd, 0, sizeof(cmd));
+        memset(cmd, 0, sizeof(cmd));
 
-    cmd[0] = GUINT16_TO_BE(b ? SK_SMART_COMMAND_ENABLE_OPERATIONS : SK_SMART_COMMAND_DISABLE_OPERATIONS);
-    cmd[2] = GUINT16_TO_BE(0x0000U);
-    cmd[3] = GUINT16_TO_BE(0x00C2U);
-    cmd[4] = GUINT16_TO_BE(0x4F00U);
+        cmd[0] = GUINT16_TO_BE(b ? SK_SMART_COMMAND_ENABLE_OPERATIONS : SK_SMART_COMMAND_DISABLE_OPERATIONS);
+        cmd[2] = GUINT16_TO_BE(0x0000U);
+        cmd[3] = GUINT16_TO_BE(0x00C2U);
+        cmd[4] = GUINT16_TO_BE(0x4F00U);
 
-    return disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, 0);
+        return disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, 0);
 }
 
 int sk_disk_smart_read_data(SkDisk *d) {
-    guint16 cmd[6];
-    int ret;
-    size_t len = 512;
+        guint16 cmd[6];
+        int ret;
+        size_t len = 512;
 
-    if (!disk_smart_is_available(d)) {
-        errno = ENOTSUP;
-        return -1;
-    }
+        if (!disk_smart_is_available(d)) {
+                errno = ENOTSUP;
+                return -1;
+        }
 
-    memset(cmd, 0, sizeof(cmd));
+        memset(cmd, 0, sizeof(cmd));
 
-    cmd[0] = GUINT16_TO_BE(SK_SMART_COMMAND_READ_DATA);
-    cmd[1] = GUINT16_TO_BE(1);
-    cmd[2] = GUINT16_TO_BE(0x0000U);
-    cmd[3] = GUINT16_TO_BE(0x00C2U);
-    cmd[4] = GUINT16_TO_BE(0x4F00U);
+        cmd[0] = GUINT16_TO_BE(SK_SMART_COMMAND_READ_DATA);
+        cmd[1] = GUINT16_TO_BE(1);
+        cmd[2] = GUINT16_TO_BE(0x0000U);
+        cmd[3] = GUINT16_TO_BE(0x00C2U);
+        cmd[4] = GUINT16_TO_BE(0x4F00U);
 
-    if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_data, &len)) < 0)
-        return ret;
+        if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_data, &len)) < 0)
+                return ret;
 
-    d->smart_data_valid = TRUE;
+        d->smart_data_valid = TRUE;
 
-    return ret;
+        return ret;
 }
 
 static int disk_smart_read_thresholds(SkDisk *d) {
-    guint16 cmd[6];
-    int ret;
-    size_t len = 512;
+        guint16 cmd[6];
+        int ret;
+        size_t len = 512;
 
-    if (!disk_smart_is_available(d)) {
-        errno = ENOTSUP;
-        return -1;
-    }
+        if (!disk_smart_is_available(d)) {
+                errno = ENOTSUP;
+                return -1;
+        }
 
-    memset(cmd, 0, sizeof(cmd));
+        memset(cmd, 0, sizeof(cmd));
 
-    cmd[0] = GUINT16_TO_BE(SK_SMART_COMMAND_READ_THRESHOLDS);
-    cmd[1] = GUINT16_TO_BE(1);
-    cmd[2] = GUINT16_TO_BE(0x0000U);
-    cmd[3] = GUINT16_TO_BE(0x00C2U);
-    cmd[4] = GUINT16_TO_BE(0x4F00U);
+        cmd[0] = GUINT16_TO_BE(SK_SMART_COMMAND_READ_THRESHOLDS);
+        cmd[1] = GUINT16_TO_BE(1);
+        cmd[2] = GUINT16_TO_BE(0x0000U);
+        cmd[3] = GUINT16_TO_BE(0x00C2U);
+        cmd[4] = GUINT16_TO_BE(0x4F00U);
 
-    if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_threshold_data, &len)) < 0)
-        return ret;
+        if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_threshold_data, &len)) < 0)
+                return ret;
 
-    d->smart_threshold_data_valid = TRUE;
+        d->smart_threshold_data_valid = TRUE;
 
-    return ret;
+        return ret;
 }
 
 /* int disk_smart_status(SkDisk *d, SmartLogAddress a, gboolean *b) { */
@@ -415,626 +446,766 @@ static int disk_smart_read_thresholds(SkDisk *d) {
 /*     cmd[3] = GUINT16_TO_BE(0x00C2U); */
 /*     cmd[4] = GUINT16_TO_BE(0x4F00U | (guint16) a); */
 
-/*     ret = disk_ata_command(SK_ATA_SMART, cmd, sizeof(cmd), NULL, 0); */
+/*     ret = disk_command(SK_ATA_SMART, cmd, sizeof(cmd), NULL, 0); */
 
 /*     return ret; */
 /* } */
 
-/* int disk_smart_immediate_offline(SkDisk *d, SmartTestType type) { */
-/*     guint16 cmd[6]; */
+int sk_disk_smart_self_test(SkDisk *d, SkSmartSelfTest test) {
+        guint16 cmd[6];
+        int ret;
 
-/*     memset(cmd, 0, sizeof(cmd)); */
+        if (!disk_smart_is_available(d)) {
+                errno = ENOTSUP;
+                return -1;
+        }
 
-/*     cmd[0] = GUINT16_TO_BE(SMART_EXECUTE_OFFLINE_IMMEDIATE); */
-/*     cmd[2] = GUINT16_TO_BE(0x0000U); */
-/*     cmd[3] = GUINT16_TO_BE(0x00C2U); */
-/*     cmd[4] = GUINT16_TO_BE(0x4F00U | (guint16) type); */
+        if (!d->smart_data_valid)
+                if ((ret = sk_disk_smart_read_data(d)) < 0)
+                        return -1;
 
-/*     return disk_ata_command(SK_ATA_SMART, cmd, sizeof(cmd), NULL, 0); */
-/* } */
+        g_assert(d->smart_data_valid);
+
+        if (test != SK_SMART_SELF_TEST_SHORT &&
+            test != SK_SMART_SELF_TEST_EXTENDED &&
+            test != SK_SMART_SELF_TEST_CONVEYANCE &&
+            test != SK_SMART_SELF_TEST_ABORT) {
+                errno = EINVAL;
+                return -1;
+        }
+
+        if (!disk_smart_is_start_test_available(d)
+            || (test == SK_SMART_SELF_TEST_ABORT && !disk_smart_is_abort_test_available(d))
+            || ((test == SK_SMART_SELF_TEST_SHORT || test == SK_SMART_SELF_TEST_EXTENDED) && !disk_smart_is_short_and_extended_test_available(d))
+            || (test == SK_SMART_SELF_TEST_CONVEYANCE && !disk_smart_is_conveyance_test_available(d))) {
+                errno = ENOTSUP;
+                return -1;
+        }
+
+        if (test == SK_SMART_SELF_TEST_ABORT &&
+            !disk_smart_is_abort_test_available(d)) {
+                errno = ENOTSUP;
+                return -1;
+        }
+
+        memset(cmd, 0, sizeof(cmd));
+
+        cmd[0] = GUINT16_TO_BE(SK_SMART_COMMAND_EXECUTE_OFFLINE_IMMEDIATE);
+        cmd[2] = GUINT16_TO_BE(0x0000U);
+        cmd[3] = GUINT16_TO_BE(0x00C2U);
+        cmd[4] = GUINT16_TO_BE(0x4F00U | (guint16) test);
+
+        return disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, NULL);
+}
 
 static void swap_strings(gchar *s, size_t len) {
-    g_assert((len & 1) == 0);
-
-    for (; len > 0; s += 2, len -= 2) {
-        gchar t;
-        t = s[0];
-        s[0] = s[1];
-        s[1] = t;
-    }
+        g_assert((len & 1) == 0);
+
+        for (; len > 0; s += 2, len -= 2) {
+                gchar t;
+                t = s[0];
+                s[0] = s[1];
+                s[1] = t;
+        }
 }
 
 static void clean_strings(gchar *s) {
-    gchar *e;
+        gchar *e;
 
-    for (e = s; *e; e++)
-        if (!g_ascii_isprint(*e))
-            *e = ' ';
+        for (e = s; *e; e++)
+                if (!g_ascii_isprint(*e))
+                        *e = ' ';
 }
 
 static void drop_spaces(gchar *s) {
-    gchar *d = s;
-    gboolean prev_space = FALSE;
-
-    s += strspn(s, " ");
-
-    for (;*s; s++) {
-
-        if (prev_space) {
-            if (*s != ' ') {
-                prev_space = FALSE;
-                *(d++) = ' ';
-            }
-        } else {
-            if (*s == ' ')
-                prev_space = TRUE;
-            else
-                *(d++) = *s;
+        gchar *d = s;
+        gboolean prev_space = FALSE;
+
+        s += strspn(s, " ");
+
+        for (;*s; s++) {
+
+                if (prev_space) {
+                        if (*s != ' ') {
+                                prev_space = FALSE;
+                                *(d++) = ' ';
+                        }
+                } else {
+                        if (*s == ' ')
+                                prev_space = TRUE;
+                        else
+                                *(d++) = *s;
+                }
         }
-    }
 
-    *d = 0;
+        *d = 0;
 }
 
 static void read_string(gchar *d, guint8 *s, size_t len) {
-    memcpy(d, s, len);
-    d[len] = 0;
-    swap_strings(d, len);
-    clean_strings(d);
-    drop_spaces(d);
+        memcpy(d, s, len);
+        d[len] = 0;
+        swap_strings(d, len);
+        clean_strings(d);
+        drop_spaces(d);
 }
 
 int sk_disk_identify_parse(SkDisk *d, const SkIdentifyParsedData **ipd) {
 
-    if (!d->identify_data_valid) {
-        errno = ENOENT;
-        return -1;
-    }
+        if (!d->identify_data_valid) {
+                errno = ENOENT;
+                return -1;
+        }
 
-    read_string(d->identify_parsed_data.serial, d->identify+20, 20);
-    read_string(d->identify_parsed_data.firmware, d->identify+46, 8);
-    read_string(d->identify_parsed_data.model, d->identify+54, 40);
+        read_string(d->identify_parsed_data.serial, d->identify+20, 20);
+        read_string(d->identify_parsed_data.firmware, d->identify+46, 8);
+        read_string(d->identify_parsed_data.model, d->identify+54, 40);
 
-    *ipd = &d->identify_parsed_data;
+        *ipd = &d->identify_parsed_data;
 
-    return 0;
+        return 0;
 }
 
 int sk_disk_smart_is_available(SkDisk *d, gboolean *b) {
 
-    if (!d->identify_data_valid) {
-        errno = ENOTSUP;
-        return -1;
-    }
+        if (!d->identify_data_valid) {
+                errno = ENOTSUP;
+                return -1;
+        }
 
-    *b = disk_smart_is_available(d);
-    return 0;
+        *b = disk_smart_is_available(d);
+        return 0;
 }
 
 int sk_disk_identify_is_available(SkDisk *d, gboolean *b) {
 
-    *b = d->identify_data_valid;
-    return 0;
+        *b = d->identify_data_valid;
+        return 0;
 }
 
 const char *sk_smart_offline_data_collection_status_to_string(SkSmartOfflineDataCollectionStatus status) {
 
-    static const char* const map[] = {
-        [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER] = "Off-line data collection activity was never started.",
-        [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS] = "Off-line data collection activity was completed without error.",
-        [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS] = "Off-line activity in progress.",
-        [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED] = "Off-line data collection activity was suspended by an interrupting command from host.",
-        [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED] = "Off-line data collection activity was aborted by an interrupting command from host.",
-        [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL] = "Off-line data collection activity was aborted by the device with a fatal error.",
-        [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN] = "Unknown status"
-    };
-
-    if (status >= _SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_MAX)
+        static const char* const map[] = {
+                [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER] = "Off-line data collection activity was never started.",
+                [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS] = "Off-line data collection activity was completed without error.",
+                [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS] = "Off-line activity in progress.",
+                [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED] = "Off-line data collection activity was suspended by an interrupting command from host.",
+                [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED] = "Off-line data collection activity was aborted by an interrupting command from host.",
+                [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL] = "Off-line data collection activity was aborted by the device with a fatal error.",
+                [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN] = "Unknown status"
+        };
+
+        if (status >= _SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_MAX)
+                return NULL;
+
+        return map[status];
+}
+
+const char *sk_smart_self_test_execution_status_to_string(SkSmartSelfTestExecutionStatus status) {
+
+        static const char* const map[] = {
+                [SK_SMART_SELF_TEST_EXECUTION_STATUS_SUCCESS_OR_NEVER] = "The previous self-test routine completed without error or no self-test has ever been run.",
+                [SK_SMART_SELF_TEST_EXECUTION_STATUS_ABORTED] = "The self-test routine was aborted by the host.",
+                [SK_SMART_SELF_TEST_EXECUTION_STATUS_INTERRUPTED] = "The self-test routine was interrupted by the host with a hardware or software reset.",
+                [SK_SMART_SELF_TEST_EXECUTION_STATUS_FATAL] = "A fatal error or unknown test error occurred while the device was executing its self-test routine and the device was unable to complete the self-test routine.",
+                [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_UNKNOWN] = "The previous self-test completed having a test element that failed and the test element that failed.",
+                [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_ELECTRICAL] = "The previous self-test completed having the electrical element of the test failed.",
+                [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_SERVO] = "The previous self-test completed having the servo (and/or seek) test element of the test failed.",
+                [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_READ] = "The previous self-test completed having the read element of the test failed.",
+                [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_HANDLING] = "The previous self-test completed having a test element that failed and the device is suspected of having handling damage.",
+                [SK_SMART_SELF_TEST_EXECUTION_STATUS_INPROGRESS] = "Self-test routine in progress"
+        };
+
+        if (status >= _SK_SMART_SELF_TEST_EXECUTION_STATUS_MAX)
+                return NULL;
+
+        return map[status];
+}
+
+const char* sk_smart_self_test_to_string(SkSmartSelfTest test) {
+
+        switch (test) {
+                case SK_SMART_SELF_TEST_SHORT:
+                        return "short";
+                case SK_SMART_SELF_TEST_EXTENDED:
+                        return "extended";
+                case SK_SMART_SELF_TEST_CONVEYANCE:
+                        return "conveyance";
+                case SK_SMART_SELF_TEST_ABORT:
+                        return "abort";
+        }
+
         return NULL;
+}
 
-    return map[status];
+gboolean sk_smart_self_test_available(const SkSmartParsedData *d, SkSmartSelfTest test) {
+
+        if (!d->start_test_available)
+                return FALSE;
+
+        switch (test) {
+                case SK_SMART_SELF_TEST_SHORT:
+                case SK_SMART_SELF_TEST_EXTENDED:
+                        return d->short_and_extended_test_available;
+                case SK_SMART_SELF_TEST_CONVEYANCE:
+                        return d->conveyance_test_available;
+                case SK_SMART_SELF_TEST_ABORT:
+                        return d->abort_test_available;
+                default:
+                        return FALSE;
+        }
+}
+
+unsigned sk_smart_self_test_polling_minutes(const SkSmartParsedData *d, SkSmartSelfTest test) {
+
+        if (!sk_smart_self_test_available(d, test))
+                return 0;
+
+        switch (test) {
+                case SK_SMART_SELF_TEST_SHORT:
+                        return d->short_test_polling_minutes;
+                case SK_SMART_SELF_TEST_EXTENDED:
+                        return d->extended_test_polling_minutes;
+                case SK_SMART_SELF_TEST_CONVEYANCE:
+                        return d->conveyance_test_polling_minutes;
+                default:
+                        return 0;
+        }
 }
 
 typedef struct SkSmartAttributeInfo {
-    const char *name;
-    SkSmartAttributeUnit unit;
+        const char *name;
+        SkSmartAttributeUnit unit;
 } SkSmartAttributeInfo;
 
 /* This data is stolen from smartmontools */
 static const SkSmartAttributeInfo const attribute_info[255] = {
-    [1]   = { "raw-read-error-rate",         SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [2]   = { "throughput-perfomance",       SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
-    [3]   = { "spin-up-time",                SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
-    [4]   = { "start-stop-count",            SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [5]   = { "reallocated-sector-count",    SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [6]   = { "read-channel-margin",         SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
-    [7]   = { "seek-error-rate",             SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [8]   = { "seek-time-perfomance",        SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
-    [10]  = { "spin-retry-count",            SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [11]  = { "calibration-retry-count",     SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [12]  = { "power-cycle-count",           SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [13]  = { "read-soft-error-rate",        SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [187] = { "reported-uncorrect",          SK_SMART_ATTRIBUTE_UNIT_SECTORS },
-    [189] = { "high-fly-writes",             SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [190] = { "airflow-temperature-celsius", SK_SMART_ATTRIBUTE_UNIT_KELVIN },
-    [191] = { "g-sense-error-rate",          SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [192] = { "power-off-retract-count",     SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [193] = { "load-cycle-count",            SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [194] = { "temperature-celsius-2",       SK_SMART_ATTRIBUTE_UNIT_KELVIN },
-    [195] = { "hardware-ecc-recovered",      SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [196] = { "reallocated-event-count",     SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [197] = { "current-pending-sector",      SK_SMART_ATTRIBUTE_UNIT_SECTORS },
-    [198] = { "offline-uncorrectable",       SK_SMART_ATTRIBUTE_UNIT_SECTORS },
-    [199] = { "udma-crc-error-count",        SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [200] = { "multi-zone-error-rate",       SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [201] = { "soft-read-error-rate",        SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [202] = { "ta-increase-count",           SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [203] = { "run-out-cancel",              SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [204] = { "shock-count-write-opern",     SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [205] = { "shock-rate-write-opern",      SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [206] = { "flying-height",               SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
-    [207] = { "spin-high-current",           SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
-    [208] = { "spin-buzz",                   SK_SMART_ATTRIBUTE_UNIT_UNKNOWN},
-    [209] = { "offline-seek-perfomance",     SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
-    [220] = { "disk-shift",                  SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
-    [221] = { "g-sense-error-rate-2",        SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [222] = { "loaded-hours",                SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
-    [223] = { "load-retry-count",            SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [224] = { "load-friction",               SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
-    [225] = { "load-cycle-count",            SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [226] = { "load-in-time",                SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
-    [227] = { "torq-amp-count",              SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [228] = { "power-off-retract-count",     SK_SMART_ATTRIBUTE_UNIT_NONE },
-    [230] = { "head-amplitude",              SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
-    [231] = { "temperature-celsius-1",       SK_SMART_ATTRIBUTE_UNIT_KELVIN },
-    [240] = { "head-flying-hours",           SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
-    [250] = { "read-error-retry-rate",       SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [1]   = { "raw-read-error-rate",         SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [2]   = { "throughput-perfomance",       SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
+        [3]   = { "spin-up-time",                SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
+        [4]   = { "start-stop-count",            SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [5]   = { "reallocated-sector-count",    SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [6]   = { "read-channel-margin",         SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
+        [7]   = { "seek-error-rate",             SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [8]   = { "seek-time-perfomance",        SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
+        [10]  = { "spin-retry-count",            SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [11]  = { "calibration-retry-count",     SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [12]  = { "power-cycle-count",           SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [13]  = { "read-soft-error-rate",        SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [187] = { "reported-uncorrect",          SK_SMART_ATTRIBUTE_UNIT_SECTORS },
+        [189] = { "high-fly-writes",             SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [190] = { "airflow-temperature-celsius", SK_SMART_ATTRIBUTE_UNIT_KELVIN },
+        [191] = { "g-sense-error-rate",          SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [192] = { "power-off-retract-count",     SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [193] = { "load-cycle-count",            SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [194] = { "temperature-celsius-2",       SK_SMART_ATTRIBUTE_UNIT_KELVIN },
+        [195] = { "hardware-ecc-recovered",      SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [196] = { "reallocated-event-count",     SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [197] = { "current-pending-sector",      SK_SMART_ATTRIBUTE_UNIT_SECTORS },
+        [198] = { "offline-uncorrectable",       SK_SMART_ATTRIBUTE_UNIT_SECTORS },
+        [199] = { "udma-crc-error-count",        SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [200] = { "multi-zone-error-rate",       SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [201] = { "soft-read-error-rate",        SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [202] = { "ta-increase-count",           SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [203] = { "run-out-cancel",              SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [204] = { "shock-count-write-opern",     SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [205] = { "shock-rate-write-opern",      SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [206] = { "flying-height",               SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
+        [207] = { "spin-high-current",           SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
+        [208] = { "spin-buzz",                   SK_SMART_ATTRIBUTE_UNIT_UNKNOWN},
+        [209] = { "offline-seek-perfomance",     SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
+        [220] = { "disk-shift",                  SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
+        [221] = { "g-sense-error-rate-2",        SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [222] = { "loaded-hours",                SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
+        [223] = { "load-retry-count",            SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [224] = { "load-friction",               SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
+        [225] = { "load-cycle-count",            SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [226] = { "load-in-time",                SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
+        [227] = { "torq-amp-count",              SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [228] = { "power-off-retract-count",     SK_SMART_ATTRIBUTE_UNIT_NONE },
+        [230] = { "head-amplitude",              SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
+        [231] = { "temperature-celsius-1",       SK_SMART_ATTRIBUTE_UNIT_KELVIN },
+        [240] = { "head-flying-hours",           SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
+        [250] = { "read-error-retry-rate",       SK_SMART_ATTRIBUTE_UNIT_NONE }
 };
 
-static void make_pretty(SkSmartAttribute *a) {
-    guint64 fourtyeight;
-
-    if (!a->name)
-        return;
-
-    if (a->pretty_unit == SK_SMART_ATTRIBUTE_UNIT_UNKNOWN)
-        return;
-
-    fourtyeight =
-        ((guint64) a->raw[0]) |
-        (((guint64) a->raw[1]) << 8) |
-        (((guint64) a->raw[2]) << 16) |
-        (((guint64) a->raw[3]) << 24) |
-        (((guint64) a->raw[4]) << 32) |
-        (((guint64) a->raw[5]) << 40);
-
-    if (!strcmp(a->name, "spin-up-time"))
-        a->pretty_value = fourtyeight & 0xFFFF;
-    else if (!strcmp(a->name, "airflow-temperature-celsius") ||
-        !strcmp(a->name, "temperature-celsius-1") ||
-        !strcmp(a->name, "temperature-celsius-2")) {
-        a->pretty_value = (fourtyeight & 0xFFFF) + 273;
-    } else if (!strcmp(a->name, "power-on-minutes"))
-        a->pretty_value = fourtyeight * 60 * 1000;
-    else if (!strcmp(a->name, "power-on-seconds"))
-        a->pretty_value = fourtyeight * 1000;
-    else if (!strcmp(a->name, "power-on-hours") ||
-             !strcmp(a->name, "loaded-hours") ||
-             !strcmp(a->name, "head-flying-hours"))
-        a->pretty_value = fourtyeight * 60 * 60 * 1000;
-    else
-        a->pretty_value = fourtyeight;
+static void make_pretty(SkSmartAttributeParsedData *a) {
+        guint64 fourtyeight;
+
+        if (!a->name)
+                return;
+
+        if (a->pretty_unit == SK_SMART_ATTRIBUTE_UNIT_UNKNOWN)
+                return;
+
+        fourtyeight =
+                ((guint64) a->raw[0]) |
+                (((guint64) a->raw[1]) << 8) |
+                (((guint64) a->raw[2]) << 16) |
+                (((guint64) a->raw[3]) << 24) |
+                (((guint64) a->raw[4]) << 32) |
+                (((guint64) a->raw[5]) << 40);
+
+        if (!strcmp(a->name, "spin-up-time"))
+                a->pretty_value = fourtyeight & 0xFFFF;
+        else if (!strcmp(a->name, "airflow-temperature-celsius") ||
+                 !strcmp(a->name, "temperature-celsius-1") ||
+                 !strcmp(a->name, "temperature-celsius-2")) {
+                a->pretty_value = (fourtyeight & 0xFFFF) + 273;
+        } else if (!strcmp(a->name, "power-on-minutes"))
+                a->pretty_value = fourtyeight * 60 * 1000;
+        else if (!strcmp(a->name, "power-on-seconds"))
+                a->pretty_value = fourtyeight * 1000;
+        else if (!strcmp(a->name, "power-on-hours") ||
+                 !strcmp(a->name, "loaded-hours") ||
+                 !strcmp(a->name, "head-flying-hours"))
+                a->pretty_value = fourtyeight * 60 * 60 * 1000;
+        else
+                a->pretty_value = fourtyeight;
 
 }
 
-static const SkSmartAttributeInfo *lookup_attribute(SkDisk *d, guint8 id, SkSmartAttributeInfo *space) {
-    const SkIdentifyParsedData *ipd;
+static const SkSmartAttributeInfo *lookup_attribute(SkDisk *d, guint8 id) {
+        const SkIdentifyParsedData *ipd;
 
-    /* These are the simple cases */
-    if (attribute_info[id].name)
-        return &attribute_info[id];
+        /* These are the simple cases */
+        if (attribute_info[id].name)
+                return &attribute_info[id];
+
+        /* These are the complex ones */
+        if (sk_disk_identify_parse(d, &ipd) < 0)
+                return NULL;
+
+        switch (id) {
+                /* We might want to add further special cases/quirks
+                 * here eventually. */
+
+                case 9: {
+
+                        static const SkSmartAttributeInfo maxtor = {
+                                "power-on-minutes", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
+                        };
+                        static const SkSmartAttributeInfo fujitsu = {
+                                "power-on-seconds", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
+                        };
+                        static const SkSmartAttributeInfo others = {
+                                "power-on-hours", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
+                        };
+
+                        if (strstr(ipd->model, "Maxtor") || strstr(ipd->model, "MAXTOR"))
+                                return &maxtor;
+                        else if (strstr(ipd->model, "Fujitsu") || strstr(ipd->model, "FUJITSU"))
+                                return &fujitsu;
+
+                        return &others;
+                }
+        }
 
-    /* These are the complex ones */
-    if (sk_disk_identify_parse(d, &ipd) < 0)
         return NULL;
-
-    switch (id) {
-        case 9:
-
-            if (strstr(ipd->model, "Maxtor"))
-                space->name = "power-on-minutes";
-            else if (strstr(ipd->model, "Fujitsu") || strstr(ipd->model, "FUJITSU"))
-                space->name = "power-on-seconds";
-            else
-                space->name = "power-on-hours";
-
-            space->unit = SK_SMART_ATTRIBUTE_UNIT_MSECONDS;
-
-            return space;
-    }
-
-    return NULL;
 }
 
 int sk_disk_smart_parse(SkDisk *d, const SkSmartParsedData **spd) {
 
-    if (!d->smart_data_valid) {
-        errno = ENOENT;
-        return -1;
-    }
+        if (!d->smart_data_valid) {
+                errno = ENOENT;
+                return -1;
+        }
 
-    switch (d->smart_data[362]) {
-        case 0x00:
-        case 0x80:
-            d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER;
-            break;
+        switch (d->smart_data[362]) {
+                case 0x00:
+                case 0x80:
+                        d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER;
+                        break;
+
+                case 0x02:
+                case 0x82:
+                        d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS;
+                        break;
+
+                case 0x03:
+                        d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS;
+                        break;
+
+                case 0x04:
+                case 0x84:
+                        d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED;
+                        break;
+
+                case 0x05:
+                case 0x85:
+                        d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED;
+                        break;
+
+                case 0x06:
+                case 0x86:
+                        d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL;
+                        break;
+
+                default:
+                        d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN;
+                        break;
+        }
 
-        case 0x02:
-        case 0x82:
-            d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS;
-            break;
+        d->smart_parsed_data.self_test_execution_percent_remaining = 10*(d->smart_data[363] & 0xF);
+        d->smart_parsed_data.self_test_execution_status = (d->smart_data[363] >> 4) & 0xF;
 
-        case 0x03:
-            d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS;
-            break;
+        d->smart_parsed_data.total_offline_data_collection_seconds = (guint16) d->smart_data[364] | ((guint16) d->smart_data[365] << 8);
 
-        case 0x04:
-        case 0x84:
-            d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED;
-            break;
+        d->smart_parsed_data.conveyance_test_available = disk_smart_is_conveyance_test_available(d);
+        d->smart_parsed_data.short_and_extended_test_available = disk_smart_is_short_and_extended_test_available(d);
+        d->smart_parsed_data.start_test_available = disk_smart_is_start_test_available(d);
+        d->smart_parsed_data.abort_test_available = disk_smart_is_abort_test_available(d);
 
-        case 0x05:
-        case 0x85:
-            d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED;
-            break;
+        d->smart_parsed_data.short_test_polling_minutes = d->smart_data[372];
+        d->smart_parsed_data.extended_test_polling_minutes = d->smart_data[373] != 0xFF ? d->smart_data[373] : ((guint16) d->smart_data[376] << 8 | (guint16) d->smart_data[375]);
+        d->smart_parsed_data.conveyance_test_polling_minutes = d->smart_data[374];
 
-        case 0x06:
-        case 0x86:
-            d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL;
-            break;
+        *spd = &d->smart_parsed_data;
 
-        default:
-            d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN;
-            break;
-    }
+        return 0;
+}
 
-    d->smart_parsed_data.selftest_execution_percent_remaining = 10*(d->smart_data[363] & 0xF);
+static void find_threshold(SkDisk *d, SkSmartAttributeParsedData *a) {
+        guint8 *p;
+        unsigned n;
 
-    d->smart_parsed_data.total_offline_data_collection_seconds = (guint16) d->smart_data[364] | ((guint16) d->smart_data[365] << 8);
+        if (!d->smart_threshold_data_valid) {
+                a->threshold_valid = FALSE;
+                return;
+        }
 
-    d->smart_parsed_data.conveyance_test_available = !!(d->smart_data[367] & 32);
-    d->smart_parsed_data.short_and_extended_test_available = !!(d->smart_data[367] & 16);
-    d->smart_parsed_data.start_test_available = !!(d->smart_data[367] & 1);
-    d->smart_parsed_data.abort_test_available = !!(d->smart_data[367] & 41);
+        for (n = 0, p = d->smart_threshold_data+2; n < 30; n++, p+=12)
+                if (p[0] == a->id)
+                        break;
 
-    d->smart_parsed_data.short_test_polling_minutes = d->smart_data[372];
-    d->smart_parsed_data.extended_test_polling_minutes = d->smart_data[373] != 0xFF ? d->smart_data[373] : ((guint16) d->smart_data[376] << 8 | (guint16) d->smart_data[375]);
-    d->smart_parsed_data.conveyance_test_polling_minutes = d->smart_data[374];
+        if (n >= 30) {
+                a->threshold_valid = FALSE;
+                return;
+        }
 
-    *spd = &d->smart_parsed_data;
+        a->threshold = p[1];
+        a->threshold_valid = TRUE;
 
-    return 0;
+        a->bad =
+                a->worst_value <= a->threshold ||
+                a->current_value <= a->threshold;
 }
 
-static void find_threshold(SkDisk *d, SkSmartAttribute *a) {
-    guint8 *p;
-    unsigned n;
-
-    if (!d->smart_threshold_data_valid) {
-        a->threshold_valid = FALSE;
-        return;
-    }
-
-    for (n = 0, p = d->smart_threshold_data+2; n < 30; n++, p+=12)
-        if (p[0] == a->id)
-            break;
+int sk_disk_smart_parse_attributes(SkDisk *d, SkSmartAttributeParseCallback cb, gpointer userdata) {
+        guint8 *p;
+        unsigned n;
 
-    if (n >= 30) {
-        a->threshold_valid = FALSE;
-        return;
-    }
+        if (!d->smart_data_valid) {
+                errno = ENOENT;
+                return -1;
+        }
 
-    a->threshold = p[1];
-    a->threshold_valid = TRUE;
+        for (n = 0, p = d->smart_data + 2; n < 30; n++, p+=12) {
+                SkSmartAttributeParsedData a;
+                const SkSmartAttributeInfo *i;
+                gchar *an = NULL;
 
-    a->bad =
-        a->worst_value <= a->threshold ||
-        a->current_value <= a->threshold;
-}
+                if (p[0] == 0)
+                        continue;
 
-int sk_disk_smart_parse_attributes(SkDisk *d, SkSmartAttributeCallback cb, gpointer userdata) {
-    guint8 *p;
-    unsigned n;
+                memset(&a, 0, sizeof(a));
+                a.id = p[0];
+                a.current_value = p[3];
+                a.worst_value = p[4];
 
-    if (!d->smart_data_valid) {
-        errno = ENOENT;
-        return -1;
-    }
+                a.flags = ((guint16) p[2] << 8) | p[1];
+                a.prefailure = !!(p[1] & 1);
+                a.online = !!(p[1] & 2);
 
-    for (n = 0, p = d->smart_data + 2; n < 30; n++, p+=12) {
-        SkSmartAttribute a;
-        SkSmartAttributeInfo space;
-        const SkSmartAttributeInfo *i;
+                memcpy(a.raw, p+5, 6);
 
-        if (p[0] == 0)
-            continue;
+                if ((i = lookup_attribute(d, p[0]))) {
+                        a.name = i->name;
+                        a.pretty_unit = i->unit;
+                } else {
+                        a.name = an = g_strdup_printf("attribute-%u", a.id);
+                        a.pretty_unit = SK_SMART_ATTRIBUTE_UNIT_UNKNOWN;
+                }
 
-        memset(&a, 0, sizeof(a));
-        a.id = p[0];
-        a.current_value = p[3];
-        a.worst_value = p[4];
+                make_pretty(&a);
 
-        a.flag = p[2];
-        a.prefailure = !!(p[1] & 1);
-        a.online = !!(p[1] & 2);
+                find_threshold(d, &a);
 
-        memcpy(a.raw, p+5, 6);
+                cb(d, &a, userdata);
 
-        if ((i = lookup_attribute(d, p[0], &space))) {
-            a.name = i->name;
-            a.pretty_unit = i->unit;
+                g_free(an);
         }
 
-        make_pretty(&a);
-
-        find_threshold(d, &a);
-
-        if (cb)
-            cb(d, &a, userdata);
-    }
-
-    return 0;
+        return 0;
 }
 
 static const char *yes_no(gboolean b) {
-    return  b ? "yes" : "no";
+        return  b ? "yes" : "no";
 }
 
 const char* sk_smart_attribute_unit_to_string(SkSmartAttributeUnit unit) {
 
-    const char * const map[] = {
-        [SK_SMART_ATTRIBUTE_UNIT_UNKNOWN] = NULL,
-        [SK_SMART_ATTRIBUTE_UNIT_NONE] = "",
-        [SK_SMART_ATTRIBUTE_UNIT_MSECONDS] = "ms",
-        [SK_SMART_ATTRIBUTE_UNIT_SECTORS] = "sectors",
-        [SK_SMART_ATTRIBUTE_UNIT_KELVIN] = "K"
-    };
+        const char * const map[] = {
+                [SK_SMART_ATTRIBUTE_UNIT_UNKNOWN] = NULL,
+                [SK_SMART_ATTRIBUTE_UNIT_NONE] = "",
+                [SK_SMART_ATTRIBUTE_UNIT_MSECONDS] = "ms",
+                [SK_SMART_ATTRIBUTE_UNIT_SECTORS] = "sectors",
+                [SK_SMART_ATTRIBUTE_UNIT_KELVIN] = "K"
+        };
 
-    if (unit >= _SK_SMART_ATTRIBUTE_UNIT_MAX)
-        return NULL;
+        if (unit >= _SK_SMART_ATTRIBUTE_UNIT_MAX)
+                return NULL;
 
-    return map[unit];
+        return map[unit];
 }
 
 static char* print_name(char *s, size_t len, guint8 id, const char *k) {
 
-    if (k)
-        g_strlcpy(s, k, len);
-    else
-        g_snprintf(s, len, "%u", id);
+        if (k)
+                g_strlcpy(s, k, len);
+        else
+                g_snprintf(s, len, "%u", id);
 
-    return s;
+        return s;
 
 }
 
-static char *print_value(char *s, size_t len, const SkSmartAttribute *a) {
+static char *print_value(char *s, size_t len, const SkSmartAttributeParsedData *a) {
 
-    switch (a->pretty_unit) {
-        case SK_SMART_ATTRIBUTE_UNIT_MSECONDS:
+        switch (a->pretty_unit) {
+                case SK_SMART_ATTRIBUTE_UNIT_MSECONDS:
 
-            if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU*365LLU)
-                g_snprintf(s, len, "%0.1f years", ((double) a->pretty_value)/(1000.0*60*60*24*365));
-            else if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU*30LLU)
-                g_snprintf(s, len, "%0.1f months", ((double) a->pretty_value)/(1000.0*60*60*24*30));
-            else if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU)
-                g_snprintf(s, len, "%0.1f days", ((double) a->pretty_value)/(1000.0*60*60*24));
-            else if (a->pretty_value >= 1000LLU*60LLU*60LLU)
-                g_snprintf(s, len, "%0.1f h", ((double) a->pretty_value)/(1000.0*60*60));
-            else if (a->pretty_value >= 1000LLU*60LLU)
-                g_snprintf(s, len, "%0.1f min", ((double) a->pretty_value)/(1000.0*60));
-            else if (a->pretty_value >= 1000LLU)
-                g_snprintf(s, len, "%0.1f s", ((double) a->pretty_value)/(1000.0));
-            else
-                g_snprintf(s, len, "%llu ms", (unsigned long long) a->pretty_value);
+                        if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU*365LLU)
+                                g_snprintf(s, len, "%0.1f years", ((double) a->pretty_value)/(1000.0*60*60*24*365));
+                        else if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU*30LLU)
+                                g_snprintf(s, len, "%0.1f months", ((double) a->pretty_value)/(1000.0*60*60*24*30));
+                        else if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU)
+                                g_snprintf(s, len, "%0.1f days", ((double) a->pretty_value)/(1000.0*60*60*24));
+                        else if (a->pretty_value >= 1000LLU*60LLU*60LLU)
+                                g_snprintf(s, len, "%0.1f h", ((double) a->pretty_value)/(1000.0*60*60));
+                        else if (a->pretty_value >= 1000LLU*60LLU)
+                                g_snprintf(s, len, "%0.1f min", ((double) a->pretty_value)/(1000.0*60));
+                        else if (a->pretty_value >= 1000LLU)
+                                g_snprintf(s, len, "%0.1f s", ((double) a->pretty_value)/(1000.0));
+                        else
+                                g_snprintf(s, len, "%llu ms", (unsigned long long) a->pretty_value);
 
-            break;
+                        break;
 
-        case SK_SMART_ATTRIBUTE_UNIT_KELVIN:
+                case SK_SMART_ATTRIBUTE_UNIT_KELVIN:
 
-            g_snprintf(s, len, "%lli C", (long long) a->pretty_value - 273);
-            break;
+                        g_snprintf(s, len, "%lli C", (long long) a->pretty_value - 273);
+                        break;
 
-        case SK_SMART_ATTRIBUTE_UNIT_SECTORS:
-            g_snprintf(s, len, "%llu sectors", (unsigned long long) a->pretty_value);
-            break;
+                case SK_SMART_ATTRIBUTE_UNIT_SECTORS:
+                        g_snprintf(s, len, "%llu sectors", (unsigned long long) a->pretty_value);
+                        break;
 
-        case SK_SMART_ATTRIBUTE_UNIT_NONE:
-            g_snprintf(s, len, "%llu", (unsigned long long) a->pretty_value);
-            break;
+                case SK_SMART_ATTRIBUTE_UNIT_NONE:
+                        g_snprintf(s, len, "%llu", (unsigned long long) a->pretty_value);
+                        break;
 
-        case SK_SMART_ATTRIBUTE_UNIT_UNKNOWN:
-            g_snprintf(s, len, "n/a");
-            break;
+                case SK_SMART_ATTRIBUTE_UNIT_UNKNOWN:
+                        g_snprintf(s, len, "n/a");
+                        break;
 
-        case _SK_SMART_ATTRIBUTE_UNIT_MAX:
-            g_assert_not_reached();
-    }
+                case _SK_SMART_ATTRIBUTE_UNIT_MAX:
+                        g_assert_not_reached();
+        }
 
-    return s;
+        return s;
 };
 
-static void disk_dump_attributes(SkDisk *d, const SkSmartAttribute *a, gpointer userdata) {
-    char name[32];
-    char pretty[32];
-    char t[32];
-
-    g_snprintf(t, sizeof(t), "%3u", a->threshold);
-
-    g_print("%3u %-27s %3u   %3u   %-3s   %-11s %-7s %-7s %-3s\n",
-            a->id,
-            print_name(name, sizeof(name), a->id, a->name),
-            a->current_value,
-            a->worst_value,
-            a->threshold_valid ? t : "n/a",
-            print_value(pretty, sizeof(pretty), a),
-            a->prefailure ? "prefail" : "old-age",
-            a->online ? "online" : "offline",
-            yes_no(!a->bad));
+#define HIGHLIGHT "\x1B[1m"
+#define ENDHIGHLIGHT "\x1B[0m"
+
+static void disk_dump_attributes(SkDisk *d, const SkSmartAttributeParsedData *a, gpointer userdata) {
+        char name[32];
+        char pretty[32];
+        char t[32];
+
+        g_snprintf(t, sizeof(t), "%3u", a->threshold);
+
+        if (a->bad  && isatty(1))
+                fprintf(stderr, HIGHLIGHT);
+
+        g_print("%3u %-27s %3u   %3u   %-3s   %-11s %-7s %-7s %-3s\n",
+                a->id,
+                print_name(name, sizeof(name), a->id, a->name),
+                a->current_value,
+                a->worst_value,
+                a->threshold_valid ? t : "n/a",
+                print_value(pretty, sizeof(pretty), a),
+                a->prefailure ? "prefail" : "old-age",
+                a->online ? "online" : "offline",
+                yes_no(!a->bad));
+
+        if (a->bad && isatty(1))
+                fprintf(stderr, ENDHIGHLIGHT);
 }
 
 int sk_disk_dump(SkDisk *d) {
-    int ret;
-    gboolean powered = FALSE;
-
-    g_print("Device: %s\n"
-            "Size: %lu MiB\n",
-            d->name,
-            (unsigned long) (d->size/1024/1024));
+        int ret;
+        gboolean awake = FALSE;
+
+        g_print("Device: %s\n"
+                "Size: %lu MiB\n",
+                d->name,
+                (unsigned long) (d->size/1024/1024));
+
+        if (d->identify_data_valid) {
+                const SkIdentifyParsedData *ipd;
+
+                if ((ret = sk_disk_identify_parse(d, &ipd)) < 0)
+                        return ret;
+
+                g_print("Model: [%s]\n"
+                        "Serial: [%s]\n"
+                        "Firmware: [%s]\n"
+                        "SMART Available: %s\n",
+                        ipd->model,
+                        ipd->serial,
+                        ipd->firmware,
+                        yes_no(disk_smart_is_available(d)));
+        }
 
-    if (d->identify_data_valid) {
-        const SkIdentifyParsedData *ipd;
+        ret = sk_disk_check_sleep_mode(d, &awake);
+        g_print("Awake: %s\n",
+                ret >= 0 ? yes_no(awake) : "unknown");
+
+        if (disk_smart_is_available(d)) {
+                const SkSmartParsedData *spd;
+
+                if ((ret = sk_disk_smart_read_data(d)) < 0)
+                        return ret;
+
+                if ((ret = sk_disk_smart_parse(d, &spd)) < 0)
+                        return ret;
+
+                g_print("Off-line Data Collection Status: [%s]\n"
+                        "Total Time To Complete Off-Line Data Collection: %u s\n"
+                        "Self-Test Execution Status: [%s]\n"
+                        "Percent Self-Test Remaining: %u%%\n"
+                        "Conveyance Self-Test Available: %s\n"
+                        "Short/Extended Self-Test Available: %s\n"
+                        "Start Self-Test Available: %s\n"
+                        "Abort Self-Test Available: %s\n"
+                        "Short Self-Test Polling Time: %u min\n"
+                        "Extended Self-Test Polling Time: %u min\n"
+                        "Conveyance Self-Test Polling Time: %u min\n",
+                        sk_smart_offline_data_collection_status_to_string(spd->offline_data_collection_status),
+                        spd->total_offline_data_collection_seconds,
+                        sk_smart_self_test_execution_status_to_string(spd->self_test_execution_status),
+                        spd->self_test_execution_percent_remaining,
+                        yes_no(spd->conveyance_test_available),
+                        yes_no(spd->short_and_extended_test_available),
+                        yes_no(spd->start_test_available),
+                        yes_no(spd->abort_test_available),
+                        spd->short_test_polling_minutes,
+                        spd->extended_test_polling_minutes,
+                        spd->conveyance_test_polling_minutes);
+
+                g_print("%3s %-27s %5s %5s %5s %-11s %-7s %-7s %-3s\n",
+                        "ID#",
+                        "Name",
+                        "Value",
+                        "Worst",
+                        "Thres",
+                        "Pretty",
+                        "Type",
+                        "Updates",
+                        "Good");
+
+                if ((ret = sk_disk_smart_parse_attributes(d, disk_dump_attributes, NULL)) < 0)
+                        return ret;
+        }
 
-        if ((ret = sk_disk_identify_parse(d, &ipd)) < 0)
-            return ret;
-
-        g_print("Model: [%s]\n"
-                "Serial: [%s]\n"
-                "Firmware: [%s]\n"
-                "SMART Available: %s\n",
-                ipd->model,
-                ipd->serial,
-                ipd->firmware,
-                yes_no(disk_smart_is_available(d)));
-    }
-
-    ret = sk_disk_check_power_mode(d, &powered);
-    g_print("Spin-up: %s\n",
-            ret >= 0 ? yes_no(powered) : "unknown");
-
-    if (disk_smart_is_available(d)) {
-        const SkSmartParsedData *spd;
-
-        if ((ret = sk_disk_smart_read_data(d)) < 0)
-            return ret;
-
-        if ((ret = sk_disk_smart_parse(d, &spd)) < 0)
-            return ret;
-
-        g_print("Off-line Collection Status: %s\n"
-                "Percent Self-Test Remaining: %u%%\n"
-                "Total Time To Complete Off-Line Data Collection: %u s\n"
-                "Conveyance Self-Test Available: %s\n"
-                "Short/Extended Self-Test Available: %s\n"
-                "Start Self-Test Available: %s\n"
-                "Abort Self-Test Available: %s\n",
-                sk_smart_offline_data_collection_status_to_string(spd->offline_data_collection_status),
-                spd->selftest_execution_percent_remaining,
-                spd->total_offline_data_collection_seconds,
-                yes_no(spd->conveyance_test_available),
-                yes_no(spd->short_and_extended_test_available),
-                yes_no(spd->start_test_available),
-                yes_no(spd->abort_test_available));
-
-        if (spd->short_and_extended_test_available)
-            g_print("Short Self-Test Polling Time: %u min\n"
-                    "Extended Self-Test Polling Time: %u min\n",
-                    spd->short_test_polling_minutes,
-                    spd->extended_test_polling_minutes);
-
-        if (spd->conveyance_test_available)
-            g_print("Conveyance Self-Test Polling Time: %u min\n",
-                    spd->conveyance_test_polling_minutes);
-
-        g_print("%3s %-27s %5s %5s %5s %-11s %-7s %-7s %-3s\n",
-                "ID#",
-                "Name",
-                "Value",
-                "Worst",
-                "Thres",
-                "Pretty",
-                "Type",
-                "Updates",
-                "Good");
-
-        if ((ret = sk_disk_smart_parse_attributes(d, disk_dump_attributes, NULL)) < 0)
-            return ret;
-    }
-
-    return 0;
+        return 0;
 }
 
 int sk_disk_get_size(SkDisk *d, guint64 *bytes) {
 
-    *bytes = d->size;
-    return 0;
+        *bytes = d->size;
+        return 0;
 }
 
 int sk_disk_open(const gchar *name, SkDisk **_d) {
-    SkDisk *d;
-    int ret = -1;
+        SkDisk *d;
+        int ret = -1;
+        struct stat st;
 
-    g_assert(name);
-    g_assert(_d);
+        g_assert(name);
+        g_assert(_d);
 
-    d = g_new0(SkDisk, 1);
-    d->name = g_strdup(name);
+        d = g_new0(SkDisk, 1);
+        d->name = g_strdup(name);
 
-    if ((d->fd = open(name, O_RDWR|O_NOCTTY)) < 0) {
-        ret = d->fd;
-        goto fail;
-    }
-
-    if ((ret = ioctl(d->fd, BLKGETSIZE64, &d->size)) < 0)
-        goto fail;
-
-    if (d->size <= 0 || d->size == (guint64) -1) {
-        errno = EINVAL;
-        goto fail;
-    }
-
-    /* Find a way to identify the device */
-    for (d->type = 0; d->type != SK_DISK_TYPE_UNKNOWN; d->type++)
-        if (disk_identify_device(d) >= 0)
-            break;
+        if ((d->fd = open(name, O_RDWR|O_NOCTTY)) < 0) {
+                ret = d->fd;
+                goto fail;
+        }
 
-    /* Check if driver can do SMART, and enable if necessary */
-    if (disk_smart_is_available(d)) {
+        if ((ret = fstat(d->fd, &st)) < 0)
+                goto fail;
 
-        if (!disk_smart_is_enabled(d)) {
-            if ((ret = disk_smart_enable(d, TRUE)) < 0)
+        if (!S_ISBLK(st.st_mode)) {
+                errno = ENODEV;
                 goto fail;
+        }
+
+        /* So, it's a block device. Let's make sure the ioctls work */
 
-            if ((ret = disk_identify_device(d)) < 0)
+        if ((ret = ioctl(d->fd, BLKGETSIZE64, &d->size)) < 0)
                 goto fail;
 
-            if (!disk_smart_is_enabled(d)) {
+        if (d->size <= 0 || d->size == (guint64) -1) {
                 errno = EIO;
-                ret = -1;
                 goto fail;
-            }
         }
 
-        disk_smart_read_thresholds(d);
-    }
+        /* OK, it's a real block device with a size. Find a way to
+         * identify the device. */
+        for (d->type = 0; d->type != SK_DISK_TYPE_UNKNOWN; d->type++)
+                if (disk_identify_device(d) >= 0)
+                        break;
 
-    *_d = d;
+        /* Check if driver can do SMART, and enable if necessary */
+        if (disk_smart_is_available(d)) {
 
-    return 0;
+                if (!disk_smart_is_enabled(d)) {
+                        if ((ret = disk_smart_enable(d, TRUE)) < 0)
+                                goto fail;
+
+                        if ((ret = disk_identify_device(d)) < 0)
+                                goto fail;
+
+                        if (!disk_smart_is_enabled(d)) {
+                                errno = EIO;
+                                ret = -1;
+                                goto fail;
+                        }
+                }
+
+                disk_smart_read_thresholds(d);
+        }
+
+        *_d = d;
+
+        return 0;
 
 fail:
 
-    if (d)
-        sk_disk_free(d);
+        if (d)
+                sk_disk_free(d);
 
-    return ret;
+        return ret;
 }
 
 void sk_disk_free(SkDisk *d) {
-    g_assert(d);
+        g_assert(d);
 
-    if (d->fd >= 0)
-        close(d->fd);
+        if (d->fd >= 0)
+                close(d->fd);
 
-    g_free(d->name);
-    g_free(d);
+        g_free(d->name);
+        g_free(d);
 }
diff --git a/smart.h b/smart.h
index cc96efb..efdc4be 100644 (file)
--- a/smart.h
+++ b/smart.h
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
 #ifndef foosmarthfoo
 #define foosmarthfoo
 
+/***
+    This file is part of SmartKit.
+
+    Copyright 2008 Lennart Poettering
+
+    libcanberra is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as
+    published by the Free Software Foundation, either version 2.1 of the
+    License, or (at your option) any later version.
+
+    libcanberra is distributed in the hope that it will be useful, but
+    WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with libcanberra. If not, If not, see
+    <http://www.gnu.org/licenses/>.
+***/
+
 #include <glib.h>
 
-typedef struct SkDisk SkDisk;
+/* ATA SMART test type (ATA8 7.52.5.2) */
+typedef enum SkSmartSelfTest {
+        SK_SMART_SELF_TEST_SHORT = 1,
+        SK_SMART_SELF_TEST_EXTENDED = 2,
+        SK_SMART_SELF_TEST_CONVEYANCE = 3,
+        SK_SMART_SELF_TEST_ABORT = 127
+} SkSmartSelfTest;
+
+const char* sk_smart_self_test_to_string(SkSmartSelfTest test);
 
 typedef struct SkIdentifyParsedData {
-    gchar serial[21];
-    gchar firmware[9];
-    gchar model[41];
+        gchar serial[21];
+        gchar firmware[9];
+        gchar model[41];
+
+        /* This structure may be extended at any time without this being
+         * considered an ABI change. So take care when you copy it. */
 } SkIdentifyParsedData;
 
 typedef enum SkSmartOfflineDataCollectionStatus {
-    SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER,
-    SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS,
-    SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS,
-    SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED,
-    SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED,
-    SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL,
-    SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN,
-    _SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_MAX
+        SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER,
+        SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS,
+        SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS,
+        SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED,
+        SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED,
+        SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL,
+        SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN,
+        _SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_MAX
 } SkSmartOfflineDataCollectionStatus;
 
-typedef struct SkSmartParsedData {
-    SkSmartOfflineDataCollectionStatus offline_data_collection_status;
-    unsigned selftest_execution_percent_remaining;
-    unsigned total_offline_data_collection_seconds;
-
-    gboolean conveyance_test_available:1;
-    gboolean short_and_extended_test_available:1;
-    gboolean start_test_available:1;
-    gboolean abort_test_available:1;
+const char* sk_smart_offline_data_collection_status_to_string(SkSmartOfflineDataCollectionStatus status);
 
-    unsigned short_test_polling_minutes;
-    unsigned extended_test_polling_minutes;
-    unsigned conveyance_test_polling_minutes;
+typedef enum SkSmartSelfTestExecutionStatus {
+        SK_SMART_SELF_TEST_EXECUTION_STATUS_SUCCESS_OR_NEVER = 0,
+        SK_SMART_SELF_TEST_EXECUTION_STATUS_ABORTED = 1,
+        SK_SMART_SELF_TEST_EXECUTION_STATUS_INTERRUPTED = 2,
+        SK_SMART_SELF_TEST_EXECUTION_STATUS_FATAL = 3,
+        SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_UNKNOWN = 4,
+        SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_ELECTRICAL = 5,
+        SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_SERVO = 6,
+        SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_READ = 7,
+        SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_HANDLING = 8,
+        SK_SMART_SELF_TEST_EXECUTION_STATUS_INPROGRESS = 15,
+        _SK_SMART_SELF_TEST_EXECUTION_STATUS_MAX
+} SkSmartSelfTestExecutionStatus;
+
+const char *sk_smart_self_test_execution_status_to_string(SkSmartSelfTestExecutionStatus status);
 
-    /* This structure may be extended at any time without being
-     * considered an ABI change. So take care when you copy it.  */
+typedef struct SkSmartParsedData {
+        /* Volatile data */
+        SkSmartOfflineDataCollectionStatus offline_data_collection_status;
+        unsigned total_offline_data_collection_seconds;
+        SkSmartSelfTestExecutionStatus self_test_execution_status;
+        unsigned self_test_execution_percent_remaining;
+
+        /* Fixed data */
+        gboolean short_and_extended_test_available:1;
+        gboolean conveyance_test_available:1;
+        gboolean start_test_available:1;
+        gboolean abort_test_available:1;
+
+        unsigned short_test_polling_minutes;
+        unsigned extended_test_polling_minutes;
+        unsigned conveyance_test_polling_minutes;
+
+        /* This structure may be extended at any time without this being
+         * considered an ABI change. So take care when you copy it.  */
 } SkSmartParsedData;
 
+gboolean sk_smart_self_test_available(const SkSmartParsedData *d, SkSmartSelfTest test);
+unsigned sk_smart_self_test_polling_minutes(const SkSmartParsedData *d, SkSmartSelfTest test);
+
 typedef enum SkSmartAttributeUnit {
-    SK_SMART_ATTRIBUTE_UNIT_UNKNOWN,
-    SK_SMART_ATTRIBUTE_UNIT_NONE,
-    SK_SMART_ATTRIBUTE_UNIT_MSECONDS,
-    SK_SMART_ATTRIBUTE_UNIT_SECTORS,
-    SK_SMART_ATTRIBUTE_UNIT_KELVIN,
-    _SK_SMART_ATTRIBUTE_UNIT_MAX
+        SK_SMART_ATTRIBUTE_UNIT_UNKNOWN,
+        SK_SMART_ATTRIBUTE_UNIT_NONE,
+        SK_SMART_ATTRIBUTE_UNIT_MSECONDS,
+        SK_SMART_ATTRIBUTE_UNIT_SECTORS,
+        SK_SMART_ATTRIBUTE_UNIT_KELVIN,
+        _SK_SMART_ATTRIBUTE_UNIT_MAX
 } SkSmartAttributeUnit;
 
-typedef struct SkSmartAttribute {
-    /* Static data */
-    guint8 id;
-    const char *name;
-    SkSmartAttributeUnit pretty_unit; /* for pretty value */
+const char* sk_smart_attribute_unit_to_string(SkSmartAttributeUnit unit);
+
+typedef struct SkSmartAttributeParsedData {
+        /* Fixed data */
+        guint8 id;
+        const char *name;
+        SkSmartAttributeUnit pretty_unit; /* for pretty_value */
 
-    guint8 threshold;
-    gboolean threshold_valid:1;
+        guint16 flags;
 
-    gboolean online:1;
-    gboolean prefailure:1;
+        guint8 threshold;
+        gboolean threshold_valid:1;
 
-    guint8 flag;
+        gboolean online:1;
+        gboolean prefailure:1;
 
-    /* Volatile data */
-    gboolean bad:1;
-    guint8 current_value, worst_value;
-    guint64 pretty_value;
-    guint8 raw[6];
+        /* Volatile data */
+        gboolean bad:1;
+        guint8 current_value, worst_value;
+        guint64 pretty_value;
+        guint8 raw[6];
 
-    /* This structure may be extended at any time without being
-     * considered an ABI change. So take care when you copy it. */
-} SkSmartAttribute;
+        /* This structure may be extended at any time without this being
+         * considered an ABI change. So take care when you copy it. */
+} SkSmartAttributeParsedData;
 
-typedef void (*SkSmartAttributeCallback)(SkDisk *d, const SkSmartAttribute *a, gpointer userdata);
+typedef struct SkDisk SkDisk;
 
 int sk_disk_open(const gchar *name, SkDisk **d);
 
-int sk_disk_check_power_mode(SkDisk *d, gboolean *mode);
+int sk_disk_check_sleep_mode(SkDisk *d, gboolean *awake);
 
 int sk_disk_identify_is_available(SkDisk *d, gboolean *b);
 int sk_disk_identify_parse(SkDisk *d, const SkIdentifyParsedData **data);
 
 int sk_disk_smart_is_available(SkDisk *d, gboolean *b);
+
+/* Reading SMART data might cause the disk to wake up from
+ * sleep. Hence from monitoring daemons make sure to call
+ * sk_disk_check_power_mode() to check wether the disk is sleeping and
+ * skip the read if so. */
 int sk_disk_smart_read_data(SkDisk *d);
+
 int sk_disk_smart_parse(SkDisk *d, const SkSmartParsedData **data);
-int sk_disk_smart_parse_attributes(SkDisk *d, SkSmartAttributeCallback cb, gpointer userdata);
+
+typedef void (*SkSmartAttributeParseCallback)(SkDisk *d, const SkSmartAttributeParsedData *a, gpointer userdata);
+int sk_disk_smart_parse_attributes(SkDisk *d, SkSmartAttributeParseCallback cb, gpointer userdata);
 
 int sk_disk_get_size(SkDisk *d, guint64 *bytes);
 
+int sk_disk_smart_self_test(SkDisk *d, SkSmartSelfTest test);
+
 int sk_disk_dump(SkDisk *d);
 
 void sk_disk_free(SkDisk *d);
 
-const char* sk_smart_offline_data_collection_status_to_string(SkSmartOfflineDataCollectionStatus status);
-const char* sk_smart_attribute_unit_to_string(SkSmartAttributeUnit unit);
+/* TODO:
+ *
+ * Smart status
+ */
 
 #endif
diff --git a/smart.vapi b/smart.vapi
new file mode 100644 (file)
index 0000000..e92215f
--- /dev/null
@@ -0,0 +1,139 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+    This file is part of SmartKit.
+
+    Copyright 2008 Lennart Poettering
+
+    libcanberra is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as
+    published by the Free Software Foundation, either version 2.1 of the
+    License, or (at your option) any later version.
+
+    libcanberra is distributed in the hope that it will be useful, but
+    WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with libcanberra. If not, If not, see
+    <http://www.gnu.org/licenses/>.
+***/
+
+using GLib;
+
+[CCode (cheader_filename="smart.h")]
+namespace Smart {
+
+        [CCode (cname="SkSmartSelfTest", cprefix="SK_SMART_SELF_TEST_")]
+        public enum SmartSelfTest {
+                SHORT, EXTENDED, CONVEYANCE, ABORT
+        }
+
+        [Immutable]
+        [CCode (cname="SkIdentifyParsedData")]
+        public struct IdentifyParsedData {
+                public string serial;
+                public string firmware;
+                public string model;
+        }
+
+        [CCode (cname="SkSmartOfflineDataCollectionStatus", cprefix="SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_")]
+        public enum SmartOfflineDataCollectionStatus {
+                NEVER, SUCCESS, INPROGRESS, SUSPENDED, ABORTED, FATAL, UNKNOWN
+        }
+
+        [CCode (cname="sk_smart_offline_data_collection_status_to_string")]
+        public weak string smart_offline_data_collection_status_to_string(SmartOfflineDataCollectionStatus status);
+
+
+        [CCode (cname="SkSmartSelfTestExecutionStatus", cprefix="SK_SMART_SELF_TEST_EXECUTION_STATUS_")]
+        public enum SmartSelfTestExecutionStatus {
+                SUCCESS_OR_NEVER, ABORTED, INTERRUPTED, FATAL, ERROR_UNKNOWN, ERROR_ELECTRICAL, ERROR_SERVO, ERROR_READ, ERROR_HANDLING, INPROGRESS
+        }
+
+        [CCode (cname="sk_smart_self_test_execution_status_to_string")]
+        public weak string smart_self_test_execution_status_to_string(SmartSelfTestExecutionStatus status);
+
+        [Immutable]
+        [CCode (cname="SkSmartParsedData")]
+        public struct SmartParsedData {
+                public SmartOfflineDataCollectionStatus offline_data_collection_status;
+                public uint total_offline_data_collection_seconds;
+                public SmartSelfTestExecutionStatus self_test_execution_status;
+                public uint self_test_execution_percent_remaining;
+
+                public bool conveyance_test_available;
+                public bool short_and_extended_test_available;
+                public bool start_test_available;
+                public bool abort_test_available;
+
+                public uint short_test_polling_minutes;
+                public uint extended_test_polling_minutes;
+                public uint conveyance_test_polling_minutes;
+
+                [CCode (cname="sk_smart_self_test_available")]
+                public bool self_test_available(SmartSelfTest test);
+
+                [CCode (cname="sk_smart_self_test_polling_minutes")]
+                public uint self_test_polling_minutes(SmartSelfTest test);
+        }
+
+        [CCode (cname="SkSmartAttributeUnit", cprefix="CK_SMART_ATTRIBUTE_UNIT")]
+        public enum SmartAttributeUnit {
+                UNKNOWN, NONE, MSECONDS, SECTORS, KELVIN
+        }
+
+        [CCode (cname="sk_smart_attribute_unit_to_string")]
+        public weak string smart_attribute_unit_to_string(SmartAttributeUnit unit);
+
+        [Immutable]
+        [CCode (cname="SkSmartAttribute")]
+        public struct SmartAttribute {
+                public uint8 id;
+                public char *name;
+                public SmartAttributeUnit pretty_unit;
+                public uint16 flags;
+                public uint8 threshold;
+                public bool threshold_valid;
+                public bool online;
+                public bool prefailure;
+                public bool bad;
+                public uint8 current_value;
+                public uint8 worst_value;
+                public uint64 pretty_value;
+                public uint8[6] raw;
+        }
+
+        [Compact]
+        [CCode (free_function="sk_disk_free", cname="SkDisk", cprefix="sk_disk_")]
+        public class Disk {
+
+                public delegate void SmartAttributeCallback(Disk d, SmartAttribute a, void* userdata);
+
+                public static int open(string name, out Disk disk);
+
+                public int check_sleep_mode(out bool awake);
+
+                public int identify_is_available(out bool mode);
+                public int identify_parse(out weak IdentifyParsedData* data);
+
+                public int smart_is_available(out bool mode);
+                public int smart_read_data();
+                public int smart_parse_attributes(SmartAttributeCallback cb, void* userdata);
+                public int smart_parse(out weak SmartParsedData* data);
+
+                public int get_size(out uint64 bytes);
+
+                public int self_test(SmartSelfTest test);
+
+                public int dump();
+        }
+
+        /* These two should move to an official vala package */
+        [CCode (cname="errno", cheader_filename="errno.h")]
+        public int errno;
+
+        [CCode (cname="g_strerror", cheader_filename="glib.h")]
+        public weak string strerror(int err);
+}
diff --git a/smartkitd.vala b/smartkitd.vala
new file mode 100644 (file)
index 0000000..0ef72cf
--- /dev/null
@@ -0,0 +1,390 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+  This file is part of SmartKit.
+
+  Copyright 2008 Lennart Poettering
+
+  libcanberra is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as
+  published by the Free Software Foundation, either version 2.1 of the
+  License, or (at your option) any later version.
+
+  libcanberra is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with libcanberra. If not, If not, see
+  <http://www.gnu.org/licenses/>.
+***/
+
+using GLib;
+using DBus;
+using Hal;
+using Smart;
+
+errordomain Error {
+    SMART_NOT_AVAILABLE,
+    SYSTEM,
+    NOT_FOUND
+}
+
+[DBus (name = "net.poettering.SmartKit.Manager")]
+public interface ManagerAPI {
+    public abstract DBus.ObjectPath getDiskByUDI(string udi) throws Error;
+    public abstract DBus.ObjectPath getDiskByPath(string path) throws Error;
+/*     public abstract DBus.ObjectPath[] getDisks() throws Error; */
+}
+
+[DBus (name = "net.poettering.SmartKit.Disk")]
+public interface DiskAPI {
+
+/*     public abstract uint64 size { get; } */
+
+    public abstract string getPath() throws Error;
+    public abstract string getUDI() throws Error;
+
+    public abstract uint64 getSize() throws Error;
+
+    public abstract bool checkPowerMode() throws Error;
+
+    public abstract bool isIdentifyAvailable() throws Error;
+    public abstract string getIdentifySerial() throws Error;
+    public abstract string getIdentifyFirmware() throws Error;
+    public abstract string getIdentifyModel() throws Error;
+
+    public abstract bool isSmartAvailable() throws Error;
+    public abstract void readSmartData() throws Error;
+
+    public abstract string getOfflineDataCollectionStatus() throws Error;
+    public abstract uint getSelfTestExecutionPercentRemaining() throws Error;
+    public abstract uint getTotalOfflineDataCollectionSeconds() throws Error;
+    public abstract bool getConveyanceTestAvailable() throws Error;
+    public abstract bool getShortAndExtendedTestAvailable() throws Error;
+    public abstract bool getStartTestAvailable() throws Error;
+    public abstract bool getAbortTestAvailable() throws Error;
+
+    public abstract uint getShortTestPollingMinutes() throws Error;
+    public abstract uint getExtendedTestPollingMinutes() throws Error;
+    public abstract uint getConveyanceTestPollingMinutes() throws Error;
+}
+
+public class Disk : GLib.Object, DiskAPI {
+    private Smart.Disk disk;
+    public string dbus_path;
+
+    public string path { get; construct; }
+    public string udi { get; construct; }
+    public DBus.Connection connection { get; construct; }
+
+    Disk(DBus.Connection connection, string path, string udi) {
+        this.connection = connection;
+        this.path = path;
+        this.udi = udi;
+    }
+
+    private string clean_path(string s) {
+        var builder = new StringBuilder ();
+        string t;
+
+        for (int i = 0; i < s.size(); i++)
+            if (s[i].isalnum() || s[i] == '_')
+                builder.append_unichar(s[i]);
+            else
+                builder.append_unichar('_');
+
+        return builder.str;
+    }
+
+    public void open() throws Error {
+        if (Smart.Disk.open(this.path, out this.disk) < 0)
+            throw new Error.SYSTEM("open() failed");
+
+        weak Smart.IdentifyParsedData *d;
+
+        if (this.disk.identify_parse(out d) >= 0)
+            this.dbus_path = "/disk/%s/%s".printf(clean_path(d->model), clean_path(d->serial));
+        else
+            this.dbus_path = "/disk/%s".printf(clean_path(this.path));
+
+        stderr.printf("Registering D-Bus path %s\n", this.dbus_path);
+        this.connection.register_object(this.dbus_path, this);
+
+        this.disk.smart_read_data();
+    }
+
+    public string getPath() throws Error {
+        return this.path;
+    }
+
+    public string getUDI() throws Error {
+        return this.udi;
+    }
+
+    public uint64 getSize() throws Error {
+        uint64 s;
+        if (this.disk.get_size(out s) < 0)
+            throw new Error.SYSTEM("get_size() failed: %s", Smart.strerror(Smart.errno));
+        return s;
+    }
+
+    public bool checkPowerMode() throws Error {
+        bool b;
+        if (this.disk.check_sleep_mode(out b) < 0)
+            throw new Error.SYSTEM("check_power_mode() failed: %s", Smart.strerror(Smart.errno));
+        return b;
+    }
+
+    public bool isIdentifyAvailable() throws Error {
+        bool b;
+        if (this.disk.identify_is_available(out b) < 0)
+            throw new Error.SYSTEM("identify_is_available() failed: %s", Smart.strerror(Smart.errno));
+        return b;
+    }
+
+    public string getIdentifySerial() throws Error {
+        weak Smart.IdentifyParsedData *d;
+        if (this.disk.identify_parse(out d) < 0)
+            throw new Error.SYSTEM("identify_parse() failed: %s", Smart.strerror(Smart.errno));
+        return d->serial;
+    }
+
+    public string getIdentifyFirmware() throws Error {
+        weak Smart.IdentifyParsedData *d;
+        if (this.disk.identify_parse(out d) < 0)
+            throw new Error.SYSTEM("identify_parse() failed: %s", Smart.strerror(Smart.errno));
+        return d->firmware;
+    }
+
+    public string getIdentifyModel() throws Error {
+        weak Smart.IdentifyParsedData *d;
+        if (this.disk.identify_parse(out d) < 0)
+            throw new Error.SYSTEM("identify_parse() failed: %s", Smart.strerror(Smart.errno));
+        return d->model;
+    }
+
+    public bool isSmartAvailable() throws Error {
+        bool b;
+        if (this.disk.smart_is_available(out b) < 0)
+            throw new Error.SYSTEM("smart_is_available() failed: %s", Smart.strerror(Smart.errno));
+        return b;
+    }
+
+    public void readSmartData() throws Error {
+        if (this.disk.smart_read_data() < 0)
+            throw new Error.SYSTEM("smart_read_data() failed: %s", Smart.strerror(Smart.errno));
+    }
+
+    public string getOfflineDataCollectionStatus() throws Error {
+        weak Smart.SmartParsedData *d;
+
+        if (this.disk.smart_parse(out d) < 0)
+            throw new Error.SYSTEM("smart_parse() failed: %s", Smart.strerror(Smart.errno));
+
+        switch (d->offline_data_collection_status) {
+            case SmartOfflineDataCollectionStatus.NEVER:
+                return "never";
+            case SmartOfflineDataCollectionStatus.SUCCESS:
+                return "success";
+            case SmartOfflineDataCollectionStatus.INPROGRESS:
+                return "inprogress";
+            case SmartOfflineDataCollectionStatus.SUSPENDED:
+                return "suspended";
+            case SmartOfflineDataCollectionStatus.ABORTED:
+                return "aborted";
+            case SmartOfflineDataCollectionStatus.FATAL:
+                return "fatal";
+            default:
+                return "unknown";
+        }
+    }
+
+    public uint getSelfTestExecutionPercentRemaining() throws Error {
+        weak Smart.SmartParsedData *d;
+
+        if (this.disk.smart_parse(out d) < 0)
+            throw new Error.SYSTEM("smart_parse() failed: %s", Smart.strerror(Smart.errno));
+
+        return d->self_test_execution_percent_remaining;
+    }
+
+    public uint getTotalOfflineDataCollectionSeconds() throws Error {
+        weak Smart.SmartParsedData *d;
+
+        if (this.disk.smart_parse(out d) < 0)
+            throw new Error.SYSTEM("smart_parse() failed: %s", Smart.strerror(Smart.errno));
+
+        return d->total_offline_data_collection_seconds;
+    }
+
+    public bool getConveyanceTestAvailable() throws Error {
+        weak Smart.SmartParsedData *d;
+
+        if (this.disk.smart_parse(out d) < 0)
+            throw new Error.SYSTEM("smart_parse() failed: %s", Smart.strerror(Smart.errno));
+
+        return d->conveyance_test_available;
+    }
+
+    public bool getShortAndExtendedTestAvailable() throws Error {
+        weak Smart.SmartParsedData *d;
+
+        if (this.disk.smart_parse(out d) < 0)
+            throw new Error.SYSTEM("smart_parse() failed: %s", Smart.strerror(Smart.errno));
+
+        return d->short_and_extended_test_available;
+    }
+
+    public bool getStartTestAvailable() throws Error {
+        Smart.SmartParsedData *d;
+
+        if (this.disk.smart_parse(out d) < 0)
+            throw new Error.SYSTEM("smart_parse() failed: %s", Smart.strerror(Smart.errno));
+
+        return d->start_test_available;
+    }
+
+    public bool getAbortTestAvailable() throws Error {
+        Smart.SmartParsedData *d;
+
+        if (this.disk.smart_parse(out d) < 0)
+            throw new Error.SYSTEM("smart_parse() failed: %s", Smart.strerror(Smart.errno));
+
+        return d->abort_test_available;
+    }
+
+    public uint getShortTestPollingMinutes() throws Error {
+        Smart.SmartParsedData *d;
+
+        if (this.disk.smart_parse(out d) < 0)
+            throw new Error.SYSTEM("smart_parse() failed: %s", Smart.strerror(Smart.errno));
+
+        return d->short_test_polling_minutes;
+    }
+
+    public uint getExtendedTestPollingMinutes() throws Error {
+        Smart.SmartParsedData *d;
+
+        if (this.disk.smart_parse(out d) < 0)
+            throw new Error.SYSTEM("smart_parse() failed: %s", Smart.strerror(Smart.errno));
+
+        return d->extended_test_polling_minutes;
+    }
+
+    public uint getConveyanceTestPollingMinutes() throws Error {
+        Smart.SmartParsedData *d;
+
+        if (this.disk.smart_parse(out d) < 0)
+            throw new Error.SYSTEM("smart_parse() failed: %s", Smart.strerror(Smart.errno));
+
+        return d->conveyance_test_polling_minutes;
+    }
+
+/*     public uint64 size { */
+/*         get { */
+/*             uint64 s; */
+/*             this.disk.get_size(out s); */
+/*             return s; */
+
+/*         } */
+/*     } */
+}
+
+public class Manager : GLib.Object, ManagerAPI {
+    public DBus.Connection connection { get; construct; }
+    public List<Disk> disks;
+
+    public DBus.RawConnection raw_connection;
+    public Hal.Context hal_context;
+
+    Manager(DBus.Connection connection) {
+        this.connection = connection;
+    }
+
+    public void start() throws Error {
+        DBus.RawError err;
+
+        this.connection.register_object("/", this);
+
+        this.raw_connection = DBus.RawBus.get(DBus.BusType.SYSTEM, ref err);
+
+        this.hal_context = new Hal.Context();
+        this.hal_context.set_dbus_connection(this.raw_connection);
+
+        string[] haldisks = this.hal_context.find_device_by_capability("storage", ref err);
+
+        foreach (string udi in haldisks) {
+            string bdev = this.hal_context.device_get_property_string(udi, "block.device", ref err);
+
+            stderr.printf("Found device %s\n", bdev);
+
+            try {
+                Disk disk = new Disk(this.connection, bdev, udi);
+                disk.open();
+                this.disks.append(#disk);
+            } catch (Error e) {
+                stderr.printf("Failed to open disk %s: %s\n", bdev, e.message);
+            }
+        }
+    }
+
+    public DBus.ObjectPath getDiskByUDI(string udi) throws Error {
+
+        foreach (Disk d in this.disks)
+            if (d.udi == udi)
+                return new DBus.ObjectPath(d.dbus_path);
+
+        throw new Error.NOT_FOUND("Device not found");
+    }
+
+    public DBus.ObjectPath getDiskByPath(string path) throws Error {
+        foreach (Disk d in this.disks)
+            if (d.path == path)
+                return new DBus.ObjectPath(d.dbus_path);
+
+        throw new Error.NOT_FOUND("Device not found");
+    }
+
+/*     public DBus.ObjectPath[] getDisks() throws Error { */
+/*         DBus.ObjectPath[] o = new DBus.ObjectPath[this.disks.length()]; */
+
+/*         int i = 0; */
+/*         foreach (Disk d in this.disks) */
+/*             o[i++] = new DBus.ObjectPath(d.dbus_path); */
+
+/*         return o; */
+/*     } */
+
+}
+
+
+
+int main() {
+
+    try {
+        var c = DBus.Bus.get(DBus.BusType.SYSTEM);
+
+        dynamic DBus.Object bus = c.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus");
+
+        uint request_name_result = bus.RequestName("net.poettering.SmartKit", (uint) 0);
+
+        if (request_name_result == DBus.RequestNameReply.PRIMARY_OWNER) {
+
+            MainLoop loop = new MainLoop(null, false);
+            Manager manager = new Manager(c);
+
+            manager.start();
+
+            stdout.printf("Started\n");
+            loop.run();
+        }
+
+    } catch (Error e) {
+        stderr.printf("Error: %s\n", e.message);
+        return 1;
+    }
+
+    return 0;
+}