AFP548

Disk Quota Reporting with PHP

I’ve had a few issues with disk quotas recently on my OS X Server file servers, and I needed to come up with a way for my student assistants to be able to view disk quota values for my staff so that they could easily tell who was over or approaching their quota.

Read on for a way to monitor disk quotas in a web browser…I really don’t want to give them any shell or Server Admin access for this server, so I decided to whip something up in PHP to do this. Be warned that I don’t consider myself the best PHP programmer on the face of the planet, so it is likely there are more efficient ways to do this, and I know there are definitely more elegant solutions….

Efficiency suggestions would be appreciated, I promise not to get all macho-geek and deride them. 🙂

To get started, issue the following command at the terminal:

<code>
/bin/date &gt; /tmp/quotas &amp;&amp; /usr/sbin/repquota -au &gt;&gt; /tmp/quotas
</code>

What this does is execute the command “date”, which will give you output something like:

<code>
Mon Dec  6 08:57:25 EST 2004
</code>

The ‘>’ redirects the output to replace the file “/tmp/quotas”.

The ‘&&’ means execute another command afterwards, in this case “repquota -au”, which reports all user quotas for all local filesystems.

The ‘>>’ redirects the output to append to the file “/tmp/quotas”.
If you have a look at this file, you’ll see something like:

<code>
me@server: ~ &#36; head /tmp/quotas 
Mon Dec  6 09:00:00 EST 2004
                        1K Block limits               File limits
User                used        soft        hard  grace    used  soft  hard  grace
cherylstaff--     2755724           0           0          15678     0     0       
brucestaff  --     5738512           0           0           7830     0     0       
</code>

The output of this is kind of ugly, so you’ll see there is a fair bit of munging going on in the php code to clean it up.

The output will look something like this example screenshot.

And here is the PHP code I use to display this. By default it will sort in descending order by the percent of quota used, so that the users closest to their quota will appear at the top. You can sort by any column however. The “$warnlimit” variable means that users who are above this value will appear in red and in a larger font.

You can also download the script as a file.

<code>
&lt;html&gt;
&lt;head&gt;&lt;title&gt;File Quota Report&lt;/title&gt;
&lt;style&gt;
    body { color: black; background: white; margin-left: 10% }
    .list0 {font:12px Arial,Helvetica; color:#000000; background-color:#eeeeee}
    .list1 {font:12px Arial,Helvetica; color:#000000; background-color:#cccccc}
    .headerrow {font:18px Arial,Helvetica; color:#000000; background-color:#888888}
    .warn {font:14px Arial, Helvetica, sans-serif; color:#ff0000; }
&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;

&lt;?php

&#36;quotafile = file ("/tmp/quotas");
&#36;colour = 0;
&#36;self = &#36;_SERVER['PHP_SELF'];
&#36;warnlimit = 70;                

echo "&lt;b&gt;Report Date:&lt;/b&gt;&lt;i&gt; &#36;quotafile[0]&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;";

&#36;quotas = array();
for (&#36;i=3 ; &#36;i &lt; count(&#36;quotafile) ; &#36;i++) 
{
    &#36;thisline = &#36;quotafile[&#36;i];
    &#36;thisline = preg_replace("/([&#92;s]--)/", "", &#36;thisline);
    &#36;thisline = preg_split("/(&#92;s)/e", &#36;thisline, -1, PREG_SPLIT_NO_EMPTY);
    &#36;quotas[&#36;i]['user'] = str_replace("--", "", &#36;thisline[0]);
    &#36;quotas[&#36;i]['mb_used'] = round ((&#36;thisline[1] / 1024), 2);
    &#36;quotas[&#36;i]['mb_soft'] = round ((&#36;thisline[2] / 1024), 2);
    &#36;quotas[&#36;i]['mb_hard'] = round ((&#36;thisline[3] / 1024), 2);
    if (&#36;thisline[3] != 0) 
    {
        &#36;quotas[&#36;i]['mb_percent_used'] = round(((&#36;quotas[&#36;i]['mb_used'] / &#36;quotas[&#36;i]['mb_hard']) * 100), 1);
    }
    else
    {
        &#36;quotas[&#36;i]['mb_percent_used'] = 0;
    }
    &#36;quotas[&#36;i]['file_used'] = &#36;thisline[4];
    &#36;quotas[&#36;i]['file_soft'] = &#36;thisline[5];
    &#36;qoutas[&#36;i]['file_hard'] = &#36;thisline[6];
} 


if (!&#36;_GET['sort'])
{
    &#36;sort = 'mb_percent_used';
}
else
{
    &#36;sort = &#36;_GET['sort'];
}

if (!&#36;_GET['order'])
{
    &#36;order = "desc";
}
else
{
    &#36;order = &#36;_GET['order'];
}

foreach (&#36;quotas as &#36;key =&gt; &#36;row)
{
    &#36;user[&#36;key] = &#36;row['user'];
    &#36;mb_used[&#36;key] = &#36;row['mb_used'];
    &#36;mb_soft[&#36;key] = &#36;row['mb_soft'];
    &#36;mb_hard[&#36;key] = &#36;row['mb_hard'];
    &#36;mb_percent_used[&#36;key] = &#36;row['mb_percent_used'];
    &#36;file_used[&#36;key] = &#36;row['file_used'];
    &#36;file_soft[&#36;key] = &#36;row['file_soft'];
    &#36;file_hard[&#36;key] = &#36;row['file_hard'];
}

switch (&#36;sort)
{
    case "user": &#36;sortkey =&amp; &#36;user; break;
    case "mb_used": &#36;sortkey =&amp; &#36;mb_used; break;
    case "mb_soft": &#36;sortkey =&amp; &#36;mb_soft; break;
    case "mb_hard": &#36;sortkey =&amp; &#36;mb_hard; break;
    case "mb_percent_used":   &#36;sortkey =&amp; &#36;mb_percent_used; break;
    case "file_used": &#36;sortkey =&amp; &#36;file_used; break;
    case "file_soft": &#36;sortkey =&amp; &#36;file_used; break;
    case "file_hard": &#36;sortkey =&amp; &#36;file_hard; break;
}

switch (&#36;order)
{
    case "asc": &#36;sortorder = SORT_ASC; &#36;newsort = "desc"; break;
    case "desc": &#36;sortorder = SORT_DESC;&#36;newsort = "asc"; break;
}

array_multisort(&#36;sortkey, &#36;sortorder, &#36;quotas);

echo '&lt;table border="0" cellpadding="3"&gt;';

echo '&lt;tr class="headerrow"&gt;';
echo '&lt;td&gt;';
echo '&lt;a href="' . &#36;self . '?sort=user&amp;order=';
if (&#36;sort == "user") { echo &#36;newsort; } else { echo 'asc'; }
echo '"&gt;User&lt;/a&gt;';
echo '&lt;/td&gt;';
echo '&lt;td&gt;';
echo '&lt;a href="' . &#36;self . '?sort=mb_used&amp;order=';
if (&#36;sort == "mb_used") { echo &#36;newsort; } else { echo 'asc'; }
echo '"&gt;Used (Mb)&lt;/a&gt;';
echo '&lt;/td&gt;';
echo '&lt;td&gt;';
echo '&lt;a href="' . &#36;self . '?sort=mb_soft&amp;order=';
if (&#36;sort == "mb_soft") { echo &#36;newsort; } else { echo 'asc'; }
echo '"&gt;SoftQuota(Mb)&lt;/a&gt;';
echo '&lt;/td&gt;';
echo '&lt;td&gt;';
echo '&lt;a href="' . &#36;self . '?sort=mb_hard&amp;order=';
if (&#36;sort == "mb_hard") { echo &#36;newsort; } else { echo 'asc'; }
echo '"&gt;HardQuota(Mb)&lt;/a&gt;';
echo '&lt;/td&gt;';
echo '&lt;td&gt;';
echo '&lt;a href="' . &#36;self . '?sort=mb_percent_used&amp;order=';
if (&#36;sort == "mb_percent_used") { echo &#36;newsort; } else { echo 'asc'; }
echo '"&gt;% Used&lt;/a&gt;';
echo '&lt;/td&gt;';
echo '&lt;td&gt;';
echo '&lt;a href="' . &#36;self . '?sort=file_used&amp;order=';
if (&#36;sort == "file_used") { echo &#36;newsort; } else { echo 'asc'; }
echo '"&gt;Used(files)&lt;/a&gt;';
echo '&lt;/td&gt;';
echo '&lt;td&gt;';
echo '&lt;a href="' . &#36;self . '?sort=file_soft&amp;order=';
if (&#36;sort == "file_soft") { echo &#36;newsort; } else { echo 'asc'; }
echo '"&gt;SoftQuota(Files)&lt;/a&gt;';
echo '&lt;/td&gt;';

echo '&lt;/tr&gt;';

foreach (&#36;quotas as &#36;quotareport)
{
        if (&#36;colour == 0) { 
                &#36;colour = 1;
        } else {
                &#36;colour = 0;
        }
    if ( &#36;quotareport['mb_percent_used'] &gt; &#36;warnlimit)
    {
        &#36;warn = 1;
    }
    else
    {
        &#36;warn = 0;
    }
    echo '&lt;tr class="list' . &#36;colour . '"&gt;'; 
    foreach (&#36;quotareport as &#36;quotaelement)
    {
        if (&#36;warn)
        {
            echo '&lt;td class="warn"&gt;' . &#36;quotaelement . '&lt;/td&gt;';
        } 
        else
        {   
            echo '&lt;td&gt;' . &#36;quotaelement . '&lt;/td&gt;';
        }
    }
    echo '&lt;/tr&gt;';
}

echo '&lt;/table&gt;';

?&gt;

&lt;/table&gt;
&lt;/body&gt;
&lt;/html&gt;

</code>

Securing access to this file is up to you!

I’m not going to cover any Apache realm stuff as there is a wealth of documentation out there about it.

So, to keep this current, I have the following line in /etc/crontab

<code>
*/5     *       *       *       *       root    /bin/date &gt; /tmp/quotas &amp;&amp; /usr/sbin/repquota -au &gt;&gt; /tmp/quotas
</code>

This means that every five minutes, a quota report is dumped to “/tmp/quotas”. People viewing the php page can look at the date at the top of the file to see how current it is.

I’ve done the same thing for Cyrus mail quotas by the way, so if anyone is interested, I could post that code. It is almost exactly the same, but the file munging is slightly different.

Exit mobile version