Subversion Repositories computer_asset_manager_v1

Rev

Rev 16 | Rev 110 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
3 rodolico 1
<?php
2
 
3
/*
4
 * Requires php-pear and libyaml under Debian Wheezy
11 rodolico 5
 *
6
 * apt-get install php-pear php5-dev libyaml-dev libyaml-0-2 libyaml-0-2-dbg
3 rodolico 7
 * pecl install yaml
11 rodolico 8
 * echo 'extension=yaml.so' >> /etc/php5/cli/php.ini
9
 * echo 'extension=yaml.so' >> /etc/apache2/cli/php.ini
5 rodolico 10
 *
11
 * V0.9 20160203 RWR
12
 * Changed to use yaml for the configuration file. Had to take the
13
 * anonymous functions which parsed the different file types and convert
14
 * them to simpl eval() blocks.
15
 * 
16 rodolico 16
 * V0.9.2 20160217 RWR
17
 * Fixed issue where if a device was marked as part of a Xen machine, it
18
 * was being deleted by pci updates.
36 rodolico 19
 * Fixed issue where serial number was not being updated
16 rodolico 20
 * 
3 rodolico 21
*/
22
 
23
require 'library.php';
16 rodolico 24
$VERSION='0.9.2';
3 rodolico 25
 
26
 
27
class sysinfo {
28
   private $report; // this will hold an entire report
29
   private $messages = array();
30
   private $FATAL = false;
31
   private $parsingArray;   // this is a required array that teaches how to properly parse the input
32
 
33
   // following are used internally to track important information
34
   private $clientID;   // id in database
35
   private $clientName; // name from report
36
   private $machineID;  // id in database
37
   private $machineName;// name in report
38
   private $reportDate; // unix timestamp of report
39
   private $reportDateSQL; // used for inserts and compares in the database
40
   private $serialNumber; // the serial number, which is assumed to be unique
41
   private $fileContents;
42
   private $processDuplicates = false; // used for testing; allows duplicate reports
43
   private $returnCode = 0; // how we returned from this
44
   private $errors = array( 1 => 'Could not process file, not xml, yaml or ini',
45
                            2 => 'Invalid report, does not have one or more of report date, client name or computer name',
46
                            3 => 'Invalid Report, invalid machine name',
47
                            4 => 'Duplicate Report',
48
                            5 => 'New Report, must be added to CAMP',
36 rodolico 49
                            6 => 'Report is in queue for addition after updates to CAMP',
50
                            7 => 'Too many entries match criteria of name, client and serial number'
3 rodolico 51
                          );
52
 
53
   /*
54
    * $parsingArray contains one or more rows, each of which have three rows, starttag, endtag and eval
55
    * starttag and endtag are regex's which match the beginning and end of a document (ie, --- and ... for yaml)
56
    * eval is an anonymous function which will evaluate a particular type of document, returning it as an
57
    * array which we store in $report
58
    */
59
 
60
   function __construct( $parsingArray ) {
61
      $this->parsingArray = $parsingArray;
62
   }
63
 
64
   public function processReport () {
65
      $this->validateReport(); // validate the report appears to be ok
66
      // prepend the name of the report to messages BEFORE any error messages that may have appeared
67
      array_unshift( $this->messages,
68
            "Report on $this->reportDateSQL, client $this->clientName, system $this->machineName ($this->fileType)");
69
      // now, we do every step in turn. If they set the FATAL flag, we exit.
70
      if ( $this->FATAL ) return $this->returnCode;
71
      $this->getMachineID();
36 rodolico 72
      if ( empty( $this->machineID ) or $this->FATAL ) return $this->returnCode;
9 rodolico 73
      $this->checkDuplicate();
3 rodolico 74
      if ( $this->FATAL) return $this->returnCode;
75
      $this->updateComputerMakeup();
76
      $this->updateOS();
77
      $this->updateBootTime();
78
      $this->doIPAddresses();
79
      $this->processPCI();
80
      $this->processSoftwarePackages();
36 rodolico 81
      $this->processXen();
9 rodolico 82
      // at this point, we don't really need the report title information
83
      // so we will remove it before recordReport has a chance to put it
84
      // in database
85
      array_shift( $this->messages );
86
      // now record the report
87
      $this->recordReport();
3 rodolico 88
      return $this->returnCode;;
89
   } // function processReport
90
 
91
   /* 
92
    * Name: loadFromFile
93
    * Parameters: filename (path to load file from)
94
    * Returns: true if on success, false if not
95
    * Modifies: 
96
    *    report - added messages on why the report is not valid
97
    *    FATAL    - set to true if any of the required values do not exist
98
    */
99
 
100
   public function loadFromFile ( $filename ) {
101
      // load file into memory. Additionally, remove any \r's from it (leaving \n's for newlines)
102
      $this->fileContents = str_replace("\r","", file_get_contents( $filename ) );
103
      // try to parse the body out
104
      $this->getBody();
105
      if ( isset( $this->report ) ) {
106
         if ( $this->fileType == 'xml' ) $this->fixXML();
107
         if ( $this->fileType == 'ini' ) {
108
            $this->messages[] = 'We do not process ini files, but storing it instead';
109
         }
110
         return $this->fileType;
111
      } else { // we don't know what it is
112
         $this->FATAL = true;
113
         $this->messages[] = "Unable to parse [$filename] using YAML, XML or INI";
114
         return false;
115
      }
116
      //unset( $this->fileContents ); // free up memory
117
   } // function loadFromFile
118
 
119
 
120
   // once the file has been loaded, we need to figure out what kind of file it is
121
   // then use the function embedded in $parsingArray to import it.
122
   private function getBody ( ) {
123
      foreach ( $this->parsingArray as $key => $regexes ) {
124
         $matches;
125
         $pattern = $regexes['startTag'] . '.*' . $regexes['endTag'];
126
         if ( preg_match( "/$pattern/ms", $this->fileContents, $matches ) ) {
127
            $this->fileType = $key;
128
            $this->fileContents = $matches[0];
5 rodolico 129
            $body = $this->fileContents;
130
            $this->report = eval( $regexes['eval'] );
131
            if ( $this->report ) return true;
3 rodolico 132
         } // if
133
      } // foreach
134
      return false;
135
   }
136
 
137
   /*
138
    * Name: fixXML
139
    * Parameters: None
140
    * Returns: nothing
141
    * Modifies: $this->report
142
    * YAML creates the entries as ['network']['interface name']
143
    * XML  creates the entries as ["network"][x][name]=>
144
    * We will simply copy the XML information into a duplicate
145
    * in YAML style
146
    */
147
   private function fixXML() {
148
       $oldInfo = $this->report['network'];
149
       unset ($this->report['network']);
150
       foreach ( $oldInfo as $entry ) {
151
          #$this->messages[] = "Duplicating network entry for $entry[name]";
152
          foreach ($entry as $key => $value ) {
153
             $this->report['network'][$entry['name']][$key] = $value;
154
             #$this->messages[] = "  Adding  $value as value for network.name.$key";
155
          } // inner foreach
156
       } // outer foreach
157
   } // fixXML
158
 
159
 
160
   private function processINI() {
161
      return false;
162
   }
163
 
164
   public function dumpMessages () {
9 rodolico 165
      $return = '';
166
      if ( $this->messages ) {
167
         $return = implode( "\n", $this->messages );
168
         $return .= "\n";
169
      }
3 rodolico 170
      return $return;
171
   }
172
 
173
   /* 
174
    * Name: validateReport
175
    * Parameters: none
176
    * Returns: true if valid, false if not
177
    * Modifies: 
178
    *    messages - added messages on why the report is not valid
179
    *    FATAL    - set to true if any of the required values do not exist
180
    */
181
 
182
      public function validateReport () {
183
      /*
184
       * A report is considered valid if it has a date, client name and hostname
185
       * No check is made at this time for validity of that data
186
       */
187
      if ( empty( $this->report['report']['date']) ) {
188
         $this->messages[] = 'No report date';
189
      } else {
190
         $this->reportDate = strtotime( $this->report['report']['date'] ); // store in Unix timestamp
191
         $this->reportDateSQL = strftime( '%F %T', $this->reportDate ); // save a copy of the date in SQL format
192
         if ( empty( $this->reportDate ) )
193
            $this->messages[] = "Unable to parse report date [$this->report['report']['date']]";
194
      }
195
 
196
      if ( empty( $this->report['report']['client'] ) ) {
197
         $this->messages[] = 'No client name';
198
      } else {
199
         $this->clientName = $this->report['report']['client'];
200
      }
201
 
202
      if ( empty ( $this->report['system']['hostname'] ) ) {
203
         $this->messages[] = 'No Computer Name';
204
      } else {
205
         $this->machineName = $this->report['system']['hostname'];
206
      }
207
      $this->FATAL = ! empty( $this->messages );
208
 
209
      // serial number is fairly new, so it is not a critical error
210
      // it will be set in getMachineID if we have it here, but not there
211
      if ( empty ( $this->report['system']['serial'] ) ) {
212
         $this->messages[] = 'No Serial Number';
213
      } else {
214
         $this->serialNumber = $this->report['system']['serial'];
215
      }
216
 
217
   } // function validateReport
218
 
219
 
220
   /* 
221
    * Name: getMachineID
222
    * Parameters: none
223
    * Returns: true if found, false if not
224
    * Modifies: 
225
    *    clientID - index value of client in database
226
    *    machineID - index value of machine in database
227
    */
228
   private function getMachineID () {
229
      $this->clientID = getClientID( $this->clientName );
230
      if ( $this->clientID ) {
36 rodolico 231
         try {
232
            $this->machineID = getMachineID( $this->machineName, $this->clientID, $this->serialNumber );
233
         }
234
         catch (Exception $e) {
235
            $this->messages[] = "Duplicate entries found when trying to process Machine [$this->machineName], Client [$this->clientID], Serial [$this->serialNumber]";
236
            $this->FATAL = true;
237
            $this->returnCode = 7;
238
            return false;
239
         }
3 rodolico 240
      }
241
      if ( empty( $this->machineID ) ) {
242
         $this->messages[] = $this->makeUnknown( $this->clientName, $this->machineName, $this->reportDateSQL );
243
         return false;
244
      }
245
      return true;
246
   } // function getMachineID
247
 
36 rodolico 248
 
249
 
3 rodolico 250
   /* 
251
    * checks for a duplicate report, ie one that has already been run.
252
    * if this computer has a report already for this date/time
253
    */ 
9 rodolico 254
   private function checkDuplicate() {
3 rodolico 255
      $count = getOneDBValue("select count(*) from sysinfo_report where device_id = $this->machineID and report_date = '$this->reportDateSQL'");
256
      if ( $this->processDuplicates ) { $count = 0; } // for testing
257
      if ( $count ) {
258
         $this->messages[] = "Duplicate Report for $this->machineName (id $this->machineID) on $this->reportDateSQL";
259
         $this->returnCode = 4;
260
         $this->FATAL = true;
261
         return false;
262
      }
9 rodolico 263
      return true;
264
   } // recordReport
265
 
266
 
267
   /* 
268
    * Creates an entry in the sysinfo_report table
269
    */ 
270
   private function recordReport() {
3 rodolico 271
      $version = $this->report['report']['version'];
9 rodolico 272
      $messages = makeSafeSQLValue( $this->dumpMessages() );
273
      // if we made it this far, we are ok, so just add the report id
274
      queryDatabaseExtended("insert into sysinfo_report(device_id,version,report_date, notes,added_date) values ($this->machineID ,'$version','$this->reportDateSQL',$messages,now())");
3 rodolico 275
      $this->reportID = getOneDBValue("select max( sysinfo_report_id ) from sysinfo_report where device_id = $this->machineID and report_date = '$this->reportDateSQL'");
276
      return true;
9 rodolico 277
   } // recordReport
3 rodolico 278
 
279
 
280
   function makeUnknown( $clientName, $machineName, $reportDate ) {
281
      $result = getOneDBValue("select report_date from unknown_entry where client_name = '$clientName' and device_name = '$machineName'");
282
      if ( empty( $result ) ) {
283
         queryDatabaseExtended( "insert into unknown_entry(client_name,device_name,report_date) values ('$clientName', '$machineName', '$reportDate')");
284
         $this->returnCode = 5;
285
         return "New entry, client=$clientName, device=$machineName. You must update Camp before this report can be processed";
286
      } else {
287
         $this->returnCode = 6;
288
         return "New entry detected, but entry already made. You must update Camp before this can be processed";
289
      }
290
   }
291
 
9 rodolico 292
   // simply used to get an attrib_id. If it does not exit, will create it
3 rodolico 293
 
294
   private function getAttributeID ( $attributeName, $reportDate ) {
295
      $attributeName = makeSafeSQLValue( $attributeName );
296
      $result = getOneDBValue( "select attrib_id from attrib where name = $attributeName and removed_date is null" );
297
      if ( !isset( $result) ) {
298
         $result = queryDatabaseExtended( "insert into attrib (name,added_date) values ($attributeName,'$reportDate')");
299
         $this->messages[] = "Added a new attribute type [$attributeName]";
300
         $result = $result['insert_id'];
301
      }
302
      return $result;
303
   }
304
 
305
   /*
36 rodolico 306
    * checks to values to see if they are equal
307
    * If they are both numeric, and $fuzzyValue is non-zero, will determine
308
    * if $value2 is within $value1 +/- percentage of $fuzzyValue.
309
    * Thus, if $fuzzyValue is 0.10, they are "equal" as long as $value1 is 
310
    * within $value2 +/- 10%
311
    */
312
   private function sloppyEqual ( $value1, $value2, $fuzzyValue ) {
313
      return 
314
         ( is_numeric( $value1 ) and is_numeric( $value2 ) and $fuzzyValue and
315
            $value1 <= $value2 + $value2 * $fuzzyValue and
316
            $value1 >= $value2 - $value2 * $fuzzyValue 
317
         )
318
         or
319
         $value1 == $value2;
320
   }
321
 
322
 
323
   /*
3 rodolico 324
    * Checks an attribute from the device_attriburtes table. If the value has
325
    * changed, sets the old one's removed_date to this report date, then adds
326
    * new record for new value.
36 rodolico 327
    * If value is not off by more than $slop, will not update. Useful for memory and speed on
328
    * virtual devices where the values may be slightly different at different readings
3 rodolico 329
    * If the record does not exist, simply creates it
330
    */
36 rodolico 331
   public function checkAndUpdateAttribute( $attribute, $value, $slop = 0 ) {
3 rodolico 332
      if ( !isset($attribute) || !isset($value ) ) {
333
         $this->messages[] = "Error: attempt to use null value for [$attribute], value [$value] for ID $this->machineID in checkAndUPdateAttribute";
334
         return false;
335
      }
336
      $attrib_id = $this->getAttributeID( $attribute, $this->reportDateSQL );
337
      $result = getOneDBValue( "select device_attrib.value
338
         from device_attrib join attrib using (attrib_id)
339
         where device_attrib.device_id = $this->machineID
340
               and device_attrib.removed_date is null
341
               and attrib.attrib_id = $attrib_id
342
         ");
36 rodolico 343
      if ( isset( $result ) && ! $this->sloppyEqual( $value, $result, $slop ) ) { # got it, now see if it compares ok
344
         $this->messages[] = "New value [$value] for $attribute for device $this->machineID, voiding previous";
345
         $value = makeSafeSQLValue($value); # we want to always quote the value on this particular one
346
         queryDatabaseExtended( "update device_attrib
347
                                    set removed_date = '$this->reportDateSQL'
348
                                 where device_id = $this->machineID
349
                                       and attrib_id = $attrib_id
350
                                       and removed_date is null");
351
         unset( $result ); # this will force the insert in the next block of code
3 rodolico 352
      } # if ($result)
353
      if ( ! isset( $result ) ) { # we have no valid entry for this attribute
354
         //$this->messages[] = "In checkAndUpdateAttribute, adding new record with insert into device_attrib(device_id,attrib_id,value,added_date) values ($this->machineID,$attrib_id,$value,$this->reportDateSQL)";
355
         queryDatabaseExtended( "insert into device_attrib(device_id,attrib_id,value,added_date)
356
                                 values ($this->machineID,$attrib_id,$value,'$this->reportDateSQL')");
357
         return true;
358
      }
359
      return false;
360
   } // checkAndUpdateAttribute
361
 
362
   # simply verifies some attributes of the computer
363
   private function updateComputerMakeup() {
36 rodolico 364
      $this->checkAndUpdateAttribute('Memory',        $this->report['system']['memory'], 0.30); // memory can be plus/minus 30%
3 rodolico 365
      $this->checkAndUpdateAttribute('Number of CPUs',$this->report['system']['num_cpu']);
366
      $this->checkAndUpdateAttribute('CPU Type',      $this->report['system']['cpu_type']);
367
      $this->checkAndUpdateAttribute('CPU SubType',   $this->report['system']['cpu_sub']);
36 rodolico 368
      $this->checkAndUpdateAttribute('CPU Speed',     $this->report['system']['cpu_speed'], 0.30 ); // cpu speed can be plus/minus 30%
369
      // validate serial number and update if necessary
370
      if ( $this->serialNumber ) {
371
         $dbSerial = getOneDBValue( "select serial from device where device_id = $this->machineID" );
372
         if ( ! isset ( $dbSerial ) or $dbSerial != $this->serialNumber ) {
373
            $this->messages[] = "Updating serial number to $this->serialNumber";
374
            queryDatabaseExtended( "update device set serial = '$this->serialNumber' where device_id = $this->machineID" );
375
         }
376
      }
3 rodolico 377
   } // updateComputerMakeup
378
 
36 rodolico 379
 
380
 
3 rodolico 381
   // This is kind of funky, because column = null does not work, it must be column is null, so we have to look for it.
382
   function makeSQLEquals ( $field, $value ) {
383
      return "$field " . ( $value == 'null' ? 'is null' : '=' . $value );
384
   }
385
 
386
 
387
   private function updateOS () {
388
      // find os
389
      $osName =         makeSafeSQLValue( isset($this->report['operatingsystem']['os_name'])         ? $this->report['operatingsystem']['os_name'] : '');
390
      $kernel =         makeSafeSQLValue( isset($this->report['operatingsystem']['kernel'])          ? $this->report['operatingsystem']['kernel'] : '');
391
      $distro_name =    makeSafeSQLValue( isset($this->report['operatingsystem']['os_distribution']) ? $this->report['operatingsystem']['distribution'] : '');
392
      $release =        makeSafeSQLValue( isset($this->report['operatingsystem']['os_release'])      ? $this->report['operatingsystem']['release'] : '');
393
      $version =        makeSafeSQLValue( isset($this->report['operatingsystem']['os_version'])      ? $this->report['operatingsystem']['os_version'] : '');
394
      $description =    makeSafeSQLValue( isset($this->report['operatingsystem']['description'])     ? $this->report['operatingsystem']['description'] : '');
395
      $codename =       makeSafeSQLValue( isset($this->report['operatingsystem']['codename'])        ? $this->report['operatingsystem']['codename'] : '');
396
 
397
      $osID = getOneDBValue( "select operating_system_id from operating_system
398
               where " . $this->makeSQLEquals( 'name', $osName ) . "
399
                  and " . $this->makeSQLEquals( 'kernel', $kernel ) . "
400
                  and " . $this->makeSQLEquals( 'distro', $distro_name ) . "
401
                  and " . $this->makeSQLEquals( 'distro_release', $release ) );
402
      if ( !isset( $osID ) ) {
403
         $this->messages[] = "Creating a new OS with name = $osName, kernel = $kernel, distro = $distro_name,  distro_release = $release";
404
         $result = queryDatabaseExtended( "insert into operating_system (name,version,kernel,distro,distro_description,distro_release,distro_codename, added_date) values
405
                                          ($osName,$version,$kernel,$distro_name,$description,$release,$codename, '$this->reportDateSQL')" );
406
         $osID = $result['insert_id'];
407
      }
408
 
409
      # verify the operating system
410
      $registeredOS = getOneDBValue( "select operating_system_id from device_operating_system where device_id = $this->machineID and removed_date is null" );
411
      if ( ! $registeredOS || $registeredOS != $osID ) {
412
         if ( $registeredOS ) { #we have the same computer, but a new OS???
413
            queryDatabaseExtended( "update device_operating_system set removed_date = '$this->reportDateSQL' where device_id = $this->machineID and removed_date is null");
414
            $this->messages[] = "Computer $this->machineName has a new OS";
415
         }
416
         queryDatabaseExtended( "insert into device_operating_system( device_id,operating_system_id,added_date) values ($this->machineID,$osID,'$this->reportDateSQL')");
417
      }
418
   }
419
 
6 rodolico 420
   /* every time we get a report, we need to see if the computer was rebooted
421
    * There is some slop in the last reboot date, so we assume if it is less than
422
    * $fuzzyRebootTimes, it was not rebooted. $fuzzyRebootTimes is calculated in
423
    * seconds. Since most of our machines take a minimum of 5 minutes to reboot
424
    * we'll go with that (300), though it would be just as good to go with a day
425
    * (86400) since we only run this report daily
426
    * When one is found, we remove the old report and add a new one.
427
    */
3 rodolico 428
   private function updateBootTime() {
6 rodolico 429
      $fuzzyRebootTimes = 300; // this allows slop of this many seconds before we assume a machine has been rebooted, ie
3 rodolico 430
      $lastReboot;
431
      if ( isset( $this->report['system']['last_boot'] ) ) {
432
         $lastReboot = strftime( '%F %T',$this->report['system']['last_boot'] ); // convert unix timestamp to sql value
433
         if ( isset( $lastReboot ) ) {
6 rodolico 434
            //if ( ! getOneDBValue( "select computer_uptime_id from computer_uptime where device_id = $this->machineID and last_reboot = '$lastReboot'" ) ) {
435
            if ( ! getOneDBValue( "select computer_uptime_id from computer_uptime where abs(TIME_TO_SEC(timediff( last_reboot, '$lastReboot' ))) < 3600 and device_id = $this->machineID" ) ) {
3 rodolico 436
               $this->messages[] = "Computer was rebooted at $lastReboot";
437
               queryDatabaseExtended( "update computer_uptime set removed_date = '$this->reportDateSQL' where device_id = $this->machineID and removed_date is null" );
438
               queryDatabaseExtended( "insert into computer_uptime (device_id,added_date,last_reboot) values ($this->machineID,'$this->reportDateSQL','$lastReboot')");
439
            }
440
         } else {
441
            $this->messages[] = 'Invalid reboot time [' . $this->Report['system']['last_boot'] . ']';
442
         }
443
      } else {
444
         $this->messages[] = 'No Boot time given';
445
      }
446
   }
447
 
448
 
449
   // checks if the report and the database values are the same
450
   private function checkInterfaceEquals( $report, $database ) {
451
      //var_dump( $report );
452
      //var_dump( $database );
453
      return ( 
454
         $report['address'] == $database['address'] and
455
         $report['netmask'] == $database['netmask'] and
456
         $report['ip6address'] == $database['ip6'] and
457
         $report['ip6networkbits'] == $database['ip6net'] and
458
         $report['mtu'] == $database['mtu'] and
459
         $report['mac'] == $database['mac'] 
460
         );
461
   } // checkInterfaceEquals
462
 
463
 
464
   /*
465
    * routine will check for all IP addresses reported and check against those recorded in the
466
    * database. It will remove any no longer in the database, and add any new ones
467
    */
468
   private function doIPAddresses () {
469
      /*
470
       * get all interfaces from the database
471
       * remove any interfaces from database which are not in report
472
       * add any interfaces from report which are not in database or which have changed
473
       */
474
      $network = $this->report['network']; // make a copy so we don't modify the original report
475
      // we won't process lo
476
      unset ($network['lo']);
477
      // at this point, we could get rid of tun's also, if we wanted to
478
 
479
      // let's ensure that all rows in report have the minimum required data
480
      foreach ( array_keys( $network ) as $interface ) {
481
         foreach ( array( 'address','netmask','ip6address','ip6networkbits','mtu','mac' ) as $required ) {
482
            if ( ! isset( $network[$interface][$required] ) ) { 
483
               $network[$interface][$required]  = NULL; 
484
            }
485
         } // checking all keys
486
      } // checking all report rows
487
      // var_dump( $network );   
488
 
489
      // get current information on all active interfaces in the database
490
      $dbInterfaces = queryDatabaseExtended( "select network_id,interface,address,netmask,ip6,ip6net,mac,mtu from network where device_id = $this->machineID and removed_date is null" );
491
      //print "select network_id,interface,address,netmask,ip6,ip6net,mac,mtu from network where device_id = $this->machineID and removed_date is null\n";
492
      //var_dump($dbInterfaces);
493
 
494
      // now, get rid of any interfaces which are no longer in the database
495
      // get a list of interfaces being passed in by report as a comma delimited list for processing.
496
      foreach ( array_keys( $network) as $temp ) {
497
         $interfaces[] = "'$temp'";
498
      }
499
      $interfaces = implode( ',', $interfaces );
500
      if ( $interfaces ) { // are there any? Then simply remove anything not in their set.
501
         queryDatabaseExtended( "update network set removed_date = '$this->reportDateSQL' where device_id = $this->machineID and removed_date is null and interface not in ($interfaces)");
502
         // print "update network set removed_date = '$this->reportDateSQL' where device_id = $this->machineID and removed_date is null and interface not in ($interfaces)\n";
503
         // reload the database results to exclude the ones we removed above
504
         $dbInterfaces = queryDatabaseExtended( "select network_id,interface,address,netmask,ip6,ip6net,mac,mtu from network where device_id = $this->machineID and removed_date is null" );
505
         // print "select network_id,interface,address,netmask,ip6,ip6net,mac,mtu from network where device_id = $this->machineID and removed_date is null\n";
506
      } // if
507
      // at this point, the database is a subset of the report, ie there are no entries in the database which are not also in the report.
508
      // let's remove the ones which are the same from both sets.
509
      // we can take a shortcut since the database is a subset of the report, ie we can remove entries from the report as we process
510
      if ( $dbInterfaces ) { // do we have anything in the database?
511
         foreach ( $dbInterfaces['data'] as $row => $value ) {
512
            // print "checking if " . $network[$value['interface']] . " equals $value\n";
513
            if ( $this->checkInterfaceEquals( $network[$value['interface']], $value ) ) { // compare the two entries
514
               // print "\tIt Does, so deleting it from updates\n";
515
               unset( $network[$value['interface']] );
516
            } else { // they are not equal. We will simply void out the existing one and it will treat the report entry as a new entry.
517
               // print "\tit is an update, so setting to removed in database\n";
518
               queryDatabaseExtended( "update network set removed_date = '$this->reportDateSQL' where device_id = $this->machineID and removed_date is null and interface = '" . $value['interface'] . "'");
519
               // print "update network set removed_date = '$this->reportDateSQL' where device_id = $this->machineID and removed_date is null and interface = '" . $value['interface'] . "'\n";
520
            } // if..else
521
         } // foreach
522
      }
523
      // finally, we should have only new entries, or entries which have changed and have been voided
524
      // a new row should be created for anything left.
525
      foreach ( $network as $interface => $values ) {
526
         //var_dump( $values );
527
         $sql = implode ( ',', array( 
528
                              $this->machineID,
529
                              makeSafeSQLValue($this->reportDateSQL),
530
                              makeSafeSQLValue($interface),
531
                              makeSafeSQLValue($values['address']),
532
                              makeSafeSQLValue($values['netmask']),
533
                              makeSafeSQLValue($values['ip6address']),
534
                              makeSafeSQLValue($values['ip6networkbits']),
535
                              makeSafeSQLValue($values['mtu']),
536
                              makeSafeSQLValue($values['mac'])
537
                              )
538
                         );
539
         $sql = "insert into network (device_id,added_date,interface,address,netmask,ip6,ip6net,mtu,mac) values (" . $sql . ")";
540
         //$this->messages[] = $sql;
541
         queryDatabaseExtended( $sql );
542
         $this->messages[] = "Network Device $interface was added/modified";
543
      }
544
   } // doIPAddresses
545
 
546
 
547
   /*
548
    * function looks through every element of $report, for a subkey
549
    * named 'keyfield'. It turns those into a string appropriate for
550
    * inclusion in a query as 
551
    * where fieldname in ( $keylist )
552
    * For example, with values a, b, c, 54, q'
553
    * returns 'a','b','c','54','q\''
554
    * where $keylist is generated by this function
555
    * returns null if no values found
556
    */ 
557
   function getKeyList ( $report ) {
558
      $keys = array();
559
      $keyset = null;
560
      // every row should have a key 'keyfield'.Since we don't know 
561
      // what they are, be sure to make sure they are SQL Safe
562
      foreach ( $report as $key => $value ) {
563
         $keys[] = makeSafeSQLValue( $value['keyfield'] );
564
      }
565
      return $keys ? implode( ",\n", $keys ) : null;
566
   }
567
 
568
   /*
569
    * function checks all keys in $first and $second to see if they
570
    * match. If they do, returns true, else returns false
571
    * ignores any keys passed in as array $ignore
572
    */
573
   function rowMatches ( $first, $second, $ignore = array() ) {
574
      foreach ( $first as $fkey => $fValue ) {
575
         if ( in_array( $fkey, $ignore ) )
576
            continue;
577
         if ( $fValue == $second[$fkey] )
578
            continue;
579
         return false;
580
      }
581
      return true;
582
   } // rowMatches
583
 
584
   /* Generic function that will 
585
    * delete items in database not found in report
586
    * Clean up report to contain only those items which are to be added
587
    * Return a copy of the report.
588
    * 
589
    * NOTE: since we are deleting items not found in the report, we will then
590
    * subsequently add them. This is the update process since we never actually
591
    * "delete" anything from the database.
592
    * 
593
    * Requires the report, and report must have a key for each entry named
594
    * 'keyfield' which the query below may use (as special tag <keys>
595
    * Also uses the following queries. If a query does not exist, that
596
    * process is ignored
597
    * 'remove old' - should have an entry as <device_id> which will have the current
598
    *       machine ID placed in it
599
    * 'delete' - <ids> which will be replaced with a list of keys from the 
600
    *         report.
601
    * 'find' - should return a column named 'id' which is a primary key on the
602
    *          table (used by delete query), and a column named 'keyfield'
603
    *          which matches the same one in the report. Any other fields in
604
    *          the query are checked against the report, and the report and
605
    *          database entry are only considered matching if all query fields
606
    *          match a report entry.
607
    * 
608
    * Before passing in report, you should go through the entries and create
609
    * a new one called 'keyfield' for each entry which will be a unique key
610
    * for an entry. For example, software packages may be the package name
611
    * and the version.
612
    */
613
   private function AddDeleteUpdate ( $report, $queries, $reportLabel, $testing=false ) {
614
 
615
      if ( $testing ) {
616
         print "*****************Starting\n"; 
617
         print_r( $report ); 
618
         print "*****************Starting\n";
619
      }
620
      $database = null;
621
      $keySet = $this->getKeyList( $report );
622
      if ( is_null( $keySet ) ) // bail if we don't have any data
623
         return null;
624
 
625
      // find values which are in database but not in report and delete them from database
626
      if ( $queries['remove old'] ) {
627
         $queries['remove old'] = str_replace( '<keys>', $keySet, $queries['remove old'] );
628
         $queries['remove old'] = str_replace( '<device_id>', $this->machineID, $queries['remove old'] );
629
         if ( $testing )
630
            print "Remove Query***************************\n" . $queries['remove old'] . "\n***********************************\n";
631
         else {
632
            $database = queryDatabaseExtended( $queries['remove old'] );
633
            if ( $database and $database['affected_rows'] ) 
634
               $this->messages[] = $database['affected_rows'] . " $reportLabel removed from database because they were removed for superceded";
635
         } // else
636
      } // if
637
 
638
      // now, let's get all the values left in the database
639
      if ( $queries['find'] ) {
640
         $queries['find'] = str_replace( '<keys>', $keySet, $queries['find'] );
641
         $queries['find'] = str_replace( '<device_id>', $this->machineID, $queries['find'] );
642
         if ( $testing )
643
            print "Find Query***************************\n" . $queries['find'] . "\n***********************************\n";
644
         else
645
            $database = queryDatabaseExtended( $queries['find'] );
646
      }
647
      if ( $database ) { // there were entries in the database still
648
         $database = $database['data']; // get to the data
649
         $keysToDelete = array();
650
         // find values which are different in report, and delete the database entry
651
         foreach ( $database as $row => $entries ) {
652
            foreach ( $report as $key => $values ) {
653
               if ( $report[$key]['keyfield'] != $entries['keyfield'] )
654
                  continue;
655
               // if we made it here, our keyfields match, so we check
656
               // the contents of the other fields
657
               if ( $this->rowMatches( $entries, $values, array( 'keyfield', 'id' ) ) ) {
658
                  // they match, so remove it from the report
659
                  unset( $report[$key] );
660
               } else { // they are different in the report, so remove from database
661
                  $keysToDelete[] = $entries['id'];
662
               }
663
            } // inner foreach
664
         } // outer foreach
665
         if ( $keysToDelete and $queries['delete'] ) {
666
            $queries['delete'] = str_replace( '<ids>', implode( ',', $keysToDelete ), $queries['delete'] );
667
            if ( $testing )
668
               print "Delete Query***************************\n" . $queries['delete'] . "\n***********************************\n";
669
            else {
670
               $updates = queryDatabaseExtended( $queries['delete'] );
671
               $this->messages[] = $updates['affected_rows'] . " $reportLabel modified";
672
            }
673
         }
674
      } // if $database
675
      // return items to be added
676
      if ( $testing ) {
677
         print "*****************Ending\n"; 
678
         print_r( $report ); 
679
         print "*****************Ending\n";
680
      }
681
      return $report;
682
   } // function AddDeleteUpdate
683
 
684
 
685
   /*
686
    * routine to ensure the hardware returned as PCI hardware is in the attributes area
687
    */ 
688
   private function processPCI ( ) {
689
      // following are some of the synonyms we will use from the PCI
690
      // tables for the "name" of the device. They are listed in the
691
      // order of priority (ie, first one found is chosen).
692
      $nameSynomyms = array('name','device','sdevice','device0');
693
 
694
      // query which removes devices from database that are not in report
695
      // we assume if the report doesn't have it, it has been removed
696
      $queries['remove old'] = 
697
                    "update device
698
                        set removed_date = '$this->reportDateSQL'
699
                        where device_id in (
700
                           select device_id from (
701
                              select
702
                                    device_id
703
                                 from
704
                                    device join device_type using (device_type_id)
705
                                 where
706
                                    device.part_of = <device_id>
16 rodolico 707
                                    and device_type.show_as_system <> 'Y'
3 rodolico 708
                                    and device.removed_date is null
709
                                    and device.name not in ( <keys> )
710
                              ) as b
711
                              )";
712
 
713
 
714
      $queries['find'] = 
715
                    "select
716
                        device_id id,
717
                        device.name  'keyfield',
718
                        device.notes 'info'
719
                     from device join device_type using (device_type_id) 
720
                     where
721
                        device_type.name = 'PCI Card' 
722
                        and device.removed_date is null
723
                        and device.part_of = <device_id>";
724
 
725
      $queries['delete'] =
726
                    "update device
727
                        set removed_date = '$this->reportDateSQL'
728
                        where device_id in ( <ids> )";
729
 
730
 
731
      if ( ! isset( $this->report['pci'] ) ) return;
732
      // make a local copy so we can modify as needed
733
      $pciHash = $this->report['pci'];
734
      # normalize the data
735
      foreach ( array_keys( $pciHash ) as $key ) {
736
         if ( ! is_array( $pciHash[$key] ) ) {
737
            $this->messages[] = 'Invalid PCI format, not recording';
738
            return false;
739
         }
740
         if ( ! isset( $pciHash[$key]['slot'] ) ) { // doesn't have a slot field
741
            $slotField = '';
742
            foreach ( array_keys( $pciHash[$key] ) as $subkey ) { // scan through all keys and see if there is something with a "slot looking" value in it
743
               if ( preg_match ( '/^[0-9a-f:.]+$/' , $pciHash[$key][$subkey] ) )
744
                  $slotField = $subkey;
745
            }
746
            if ( $slotField ) {
747
               $pciHash[$key]['slot'] = $pciHash[$key][$slotField];
748
            } else {
749
               $pciHash[$key]['slot'] = 'Unknown';
750
            }
751
         }
752
         // Each entry must have a name. Use 'device' if it doesn't exist
753
         // Basically, we try each option in turn, with the first test having
754
         // priority.
755
 
756
         if ( ! isset( $pciHash[$key]['name'] ) ) {
757
            // if there is no name, try to use one of the synonyms
758
            foreach ( $nameSynomyms as $testName ) {
759
               if ( isset ( $pciHash[$key][$testName] ) ) {
760
                  $pciHash[$key]['name'] = $pciHash[$key][$testName];
761
                  break;
762
               } // if
763
            } // foreach
764
            if ( empty( $pciHash[$key]['name'] ) ) {
765
               $this->messages[] = "No name given for one or more PCI devices at normalize, Computer ID: [$this->machineID], Report Date: [$this->reportDate]";
766
               //print_r( $pciHash ); 
767
               //die;
768
               return;
769
            }
770
         } elseif ( $pciHash[$key]['name'] == $pciHash[$key]['slot'] ) {
771
            $pciHash[$key]['name'] = $pciHash[$key]['device'];
772
         } // if..else
773
         // Following is what will actually be put in the device table, ie device.name
774
         $pciHash[$key]['keyfield'] = $pciHash[$key]['slot'] . ' - ' . $pciHash[$key]['name'];
775
         ksort( $pciHash[$key] );
776
         $ignore = array( 'keyfield' );
777
         $info = '';
778
         foreach ( $pciHash[$key] as $subkey => $value ) {
779
            if ( in_array( $subkey, $ignore ) )
780
               continue;
781
            $info .= "[$subkey]=$value\n";
782
         }
783
         $pciHash[$key]['info'] = $info;
784
      } // foreach
785
      // at this point, we should have a slot and a name field in all pci devices
786
      $toAdd = $this->AddDeleteUpdate( $pciHash, $queries, 'PCI Devices' );
787
      if ( $toAdd ) {
788
         $pciCardID = getOneDBValue( "select device_type_id from device_type where name = 'PCI Card'");
789
         $count = 0;
790
         foreach ( $toAdd as $entry => $values ) {
791
            $keyfield = makeSafeSQLValue( $values['keyfield'] );
792
            $info = makeSafeSQLValue( $values['info'] );
793
            $query = "insert into device 
794
                        select 
795
                           null,
796
                           site_id,
797
                           $pciCardID,
798
                           $keyfield,
799
                           $info,
800
                           device_id,
801
                           '$this->reportDateSQL',
802
                           null,
803
                           null 
804
                        from device 
805
                        where device_id = $this->machineID";
806
            queryDatabaseExtended( $query );
807
            $count++;
808
         } // foreach
809
         $this->messages[] = "$count PCI devices added/modified";
810
      } // if
811
   } // processPCI
812
 
813
   function getSoftwareID ( $packageName,$versionInfo,$description ) {
814
      $packageName = makeSafeSQLValue( $packageName );
815
      $versionInfo = makeSafeSQLValue( $versionInfo );
816
      $description = makeSafeSQLValue( $description );
817
      // does the package exist?
818
      $packageID = getOneDBValue("select software_id from software where package_name = $packageName and removed_date is null");
819
      if ( !$packageID ) { # NO, package doesn't exist, so add it to the database
820
         $packageID = queryDatabaseExtended( "insert into software (package_name,description, added_date) values ($packageName,$description, '$this->reportDateSQL')");
821
         if ( $packageID['insert_id'] )
822
            $packageID = $packageID['insert_id'];
823
         else {
824
            $this->messages[] = "ERROR: Software Packages - Could not find version $packageName and could not add to database";
825
            $packageID = null;
826
         }
827
      }
828
      // does this version number exist?
829
      $versionID = getOneDBValue( "select software_version_id from software_version where version = $versionInfo and removed_date is null" );
830
      if ( ! $versionID ) { # nope, so add it
831
         $versionID = queryDatabaseExtended( "insert into software_version ( version,added_date ) values ($versionInfo,'$this->reportDateSQL')");
832
         if ( $versionID['insert_id'] )
833
            $versionID = $versionID['insert_id'];
834
         else {
835
            $this->messages[] = "ERROR: Software Packages - Could not find version $versionInfo and could not add to database";
836
            $versionID = null;
837
         }
838
      }
839
      return array( 'package id' => $packageID,'version id' => $versionID);
840
   } // getSoftwareID
841
 
842
 
843
   function processSoftwarePackages (  ) {
844
      // query which removes devices from database that are not in report
845
      // we assume if the report doesn't have it, it has been removed
846
      $queries['remove old'] = 
847
                    "update installed_packages
848
                        set removed_date = '$this->reportDateSQL'
849
                        where installed_packages_id in (
850
                           select installed_packages_id from (
851
                              select
852
                                    installed_packages_id
853
                              from 
854
                                 installed_packages 
855
                                 join software using ( software_id )
856
                                 join software_version using (software_version_id)
857
                              where
858
                                    installed_packages.device_id = <device_id>
859
                                    and installed_packages.removed_date is null
860
                                    and concat(software.package_name,software_version.version) not in ( <keys> )
861
                              ) as b
862
                              )";
863
      $queries['find'] = 
864
           "select 
865
               installed_packages.installed_packages_id 'id',
866
               concat(software.package_name,software_version.version) 'keyfield'
867
            from 
868
               installed_packages 
869
               join software using ( software_id )
870
               join software_version using (software_version_id)
871
            where 
872
               device_id = <device_id>
873
               and installed_packages.removed_date is null";
874
      $queries['delete'] =
875
                    "update installed_packages
876
                        set removed_date = '$this->reportDateSQL'
877
                        where installed_packages_id in ( <ids> )";
878
 
879
      if ( ! isset( $this->report['software'] ) ) return;
880
      // make a local copy so we can modify as needed
881
      $softwareHash = $this->report['software'];
882
      foreach ( array_keys( $softwareHash ) as $key ) {
883
         if ( ! isset( $softwareHash[$key]['name'] ) )
884
            $softwareHash[$key]['name'] = $key;
885
         if ( ! isset( $softwareHash[$key]['description'] ) )
886
            $softwareHash[$key]['description'] = '';
14 rodolico 887
         if ( isset( $softwareHash[$key]['release'] ) ) // some of them have a release number, so we just add that to the version
888
            $softwareHash[$key]['version'] = $softwareHash[$key]['version'] . '-r' . $softwareHash[$key]['release'];
3 rodolico 889
         // This is needed for matching
890
         $softwareHash[$key]['keyfield'] = $softwareHash[$key]['name'] . $softwareHash[$key]['version'];
891
      } // foreach
892
      // at this point, we should have a slot and a name field in all pci devices
893
      $toAdd = $this->AddDeleteUpdate( $softwareHash, $queries, 'Software Packages' );
894
      if ( $toAdd ) {
895
         $count = 0;
896
         foreach ( $toAdd as $key => $value ) {
897
            $ids = $this->getSoftwareID ( $value['name'],$value['version'],$value['description'] );
898
            if ( is_null( $ids['package id'] ) or is_null( $ids['version id'] ) ) {
899
               $this->messages[] = "Could not create entry for package " . $value['name'] . ", version " . $value['version'];
900
            } else {
901
               $insertValues = implode( ',', array( $this->machineID, $ids['package id'], $ids['version id'], "'$this->reportDateSQL'" ) );
902
               $query = "insert into installed_packages
903
                           (device_id,software_id,software_version_id,added_date) 
904
                           values 
905
                           ( $insertValues )";
906
               $database = queryDatabaseExtended( $query );
907
               if ( $database )
908
                  $count++;
909
            } // if..else
910
         } // foreach
911
         if ( $count )
912
            $this->messages[] = "$count software packages added/updated";
913
      }
914
   } // processSoftwarePackages
36 rodolico 915
 
916
 
917
   function processXenVirtual ( $name, $values ) {
918
      // Determine if the virtual is on a new machine or not
919
      $virtualID = getMachineID( $name ); // see if we can find the virtual
3 rodolico 920
 
36 rodolico 921
      if ( $virtualID ) {
922
         if ( $this->machineID == getOneDBValue( "select part_of from device where device.device_id = $virtualID" ) ) {
923
            return;
924
         }
925
      } else { // we could not find the device
926
         $this->messages[] = "Could not locate id for virtual $name which is running on $this->machineName";
927
         return;
928
      }
929
      // we made it this far, which means we found the virtual's ID, but it does not have
930
      // this machine as its parent, so we need to update it.
931
      queryDatabaseExtended( "update device set part_of = $this->machineID where device_id = $virtualID" );
932
      $this->messages[] = "Virtual $name ($virtualID) now running on $this->machineName";
933
   } // processXenVirtual
934
 
935
   function processXen ( ) {
936
      // Is this a Xen DOM0?
937
      if ( ! isset( $this->report['xen'] ) ) return;
938
      $machineID;  // id in database
939
      $machineName;// name in report
940
 
941
      foreach ( $this->report['xen']['virtual'] as $virtual => $data ) {
942
         $this->processXenVirtual( $virtual, $data );
943
      }
944
   } // processXen
945
 
3 rodolico 946
} // class sysinfo
947
 
948
 
949
 
5 rodolico 950
/*
951
 * we don't know where the configuration file will be, so we have a list
952
 * below (searchPaths) to look for it. It will load the first one it 
953
 * finds, then exit. THUS, you could have multiple configuration files
954
 * but only the first one in the list will be used
955
 */
3 rodolico 956
 
5 rodolico 957
$confFileName = "sysinfoRead.conf.yaml";
958
$searchPaths = array( '/etc/camp', '/opt/camp', '/opt/camp/sysinfo', $_SERVER['SCRIPT_FILENAME'], getcwd() );
959
$configuration = array();
960
foreach ( $searchPaths as $path ) {
961
   if ( file_exists( "$path/$confFileName" ) ) {
962
      #print "Found $path/$confFileName\n";
963
      $configuration = yaml_parse_file( "$path/$confFileName" );
964
      #require "$path/$confFileName";
965
      break; // exit out of the loop; we don't try to load it more than once
966
   } // if
967
} // foreach
3 rodolico 968
 
5 rodolico 969
mysql_connect( $configuration['database']['databaseServer'], $configuration['database']['databaseUsername'], $configuration['database']['databasePassword'] ) or die(mysql_error());
970
mysql_select_db( $configuration['database']['database'] ) or die(mysql_error());
971
 
972
 
973
$thisReport = new sysinfo( $configuration['bodyContents'] );
3 rodolico 974
$result; 
975
 
976
$result = $thisReport->loadFromFile( $argc > 1 ? $argv[1] : "php://stdin" );
977
if ( $result == 'ini' ) {
978
   print $thisReport->dumpMessages();
979
   exit(0);
980
}
981
if ( ! $result )
982
   exit ( 1 );
983
$result = $thisReport->processReport();
984
print $thisReport->dumpMessages();
36 rodolico 985
// var_dump( $thisReport );
3 rodolico 986
exit ( $result );
987
 
988
?>