restore_dir( $bucket, $bucket_dir, $local_dir ); class S3_Restore { var $s3; var $local_path; var $paths_cache; var $xml_cur_element; var $xml_depth; var $xml_contents; var $isTruncated; var $lastKey; function restore_dir( $bucket, $bucket_dir, $local_dir ) { $this->xml_depth = array(); $this->local_path = $local_dir; if( !is_dir( $local_dir )) { echo "creating directory $local_dir...\n"; mkdir( $local_dir ); } if( !$this->s3 ) { $s3 = new s3; $s3->setBucketName( $bucket ); $this->s3 = $s3; } $this->isTruncated = 'true'; $this->lastKey = null; while( $this->isTruncated == 'true' ) { echo "getting XML dump from S3...\n"; $xmldata = $s3->getObjects( $bucket, $bucket_dir.'/', $this->lastKey ); echo "parsing XML dump...\n"; $xml_parser = xml_parser_create(); xml_set_object( $xml_parser, $this ); xml_set_element_handler($xml_parser, 'startElement', 'endElement' ); xml_set_character_data_handler($xml_parser, 'characterData' ); if (!xml_parse($xml_parser, $xmldata, 1)) { die(sprintf("XML error: %s at line %d", xml_error_string(xml_get_error_code($xml_parser)), xml_get_current_line_number($xml_parser))); } xml_parser_free($xml_parser); } } function process_file( $contents ) { $s3 = $this->s3; if( !isset( $contents['KEY'] )) return; // array keys // KEY // LASTMODIFIED // ETAG // SIZE // ID // DISPLAYNAME // STORAGECLASS $this->lastKey = $contents['KEY']; $metadata = $this->get_metadata( $contents['KEY'] ); echo $contents['KEY']."\n"; $local_path = $this->local_path.'/'.$contents['KEY']; $etag = @md5_file( $local_path ); if( $etag == $metadata['etag'] ) { echo "etag matches for file {$contents['KEY']}, skipping...\n"; return; } $this->verify_dirpath( $contents['KEY'] ); $file_type = 'file'; if( isset( $metadata['uid'] )) { switch( $metadata['mode'] & 0170000 ) { case 040000: $file_type = 'dir'; break; case 010000: $file_type = 'fifo'; break; case 020000: $file_type = 'char'; break; case 060000: $file_type = 'block'; break; case 0140000: $file_type = 'sock'; break; case 0100000: $file_type = 'file'; break; case 0120000: $file_type = 'link'; break; } } // restore a sock if( $file_type=='sock' ) { echo "{$contents['KEY']} is a socket, not restored.\n"; return; } // restore a block device if( $file_type=='block' ) { echo "{$contents['KEY']} is a block special device and php4 has no mknod, not restored.\n"; return; } // restore a char device if( $file_type=='char' ) { echo "{$contents['KEY']} is a character special device and php4 has no mknod, not restored.\n"; return; } // restore a fifo if( $file_type=='fifo' ) { echo "{$contents['KEY']} is a FIFO and php4 has no mknod, not restored.\n"; return; } // restore a file if( $file_type == 'file' ) { $data = $s3->getObject( $contents['KEY'] ); $fp = fopen( $local_path, 'w' ); if( !$fp ) { echo "Error creating local file: $local_path, skipping\n"; return; } fwrite( $fp, $data ); fclose( $fp ); } // restore a link if( $file_type == 'link' ) { $data = trim( $s3->getObject( $contents['KEY'] )); symlink( $data, $local_path ); return; } // restore a dir if( $file_type == 'dir' ) { @mkdir( $local_path ); } // only links and dirs here chown( $local_path, intval($metadata['uid']) ); chgrp( $local_path, intval($metadata['gid']) ); touch( $local_path, $metadata['mtime'], $metadata['atime'] ); chmod( $local_path, $metadata['mode'] ); } function verify_dirpath( $filename ) { $paths =& $this->paths_cache; $local_path = $this->local_path; $parts = explode( '/', $filename ); $num_parts = count( $parts ); for( $x=0; $x<$num_parts-1;$x++ ) { $path = ''; for( $y=0;$y<=$x;$y++ ) { if( $y ) { $path .= '/'; } $path .= $parts[$y]; } if( !$paths[$path] ) { if( !is_dir( $local_path.'/'.$path )) { echo "created directory $path\n"; mkdir( $local_path.'/'.$path ); } $paths[$path] = 1; } } } function get_metadata( $filename ) { $s3 = $this->s3; $meta_keys = array( 'dev', 'ino', 'mode', 'nlink', 'uid', 'gid', 'rdev', 'size', 'atime', 'mtime', 'ctime', 'blksize', 'blocks' ); $info = $s3->getObjectInfo( $filename ); foreach( $meta_keys as $key ) $ret[$key] = $info['x-amz-meta-'.$key]; $ret['etag'] = trim( $info['etag'], '"'.'"' ); return $ret; } function startElement($parser, $name, $attrs) { $this->xml_cur_element = $name; $this->xml_depth[$parser]++; } function endElement($parser, $name) { if( $name == 'CONTENTS' ) { $this->process_file( $this->xml_contents ); $contents = array(); } if( $name == 'ISTRUNCATED' ) { $this->isTruncated = $this->xml_contents['ISTRUNCATED']; } $this->xml_depth[$parser]--; } function characterData($parser, $data) { $this->xml_contents[$this->xml_cur_element] = $data; } }