├── OSS.pm ├── README.md ├── distro └── archlinux │ └── PKGBUILD ├── ossfs └── test.pl /OSS.pm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | package OSS; 7 | 8 | use LWP::UserAgent; 9 | use HTTP::Date; 10 | use MIME::Base64; 11 | use Digest::MD5 qw(md5_base64); 12 | use Digest::HMAC_SHA1; 13 | use XML::Simple; 14 | 15 | # for xml debug 16 | # use Data::Dumper; 17 | # print Dumper($xml); 18 | 19 | my $debug = 0; 20 | 21 | sub new { 22 | my $class = shift; 23 | my $access_id = shift; 24 | my $access_key = shift; 25 | my $host = shift; 26 | 27 | $host = "oss.aliyuncs.com" unless (defined($host)); 28 | my $ua = LWP::UserAgent->new(agent => "ossfs"); 29 | my $self = { 30 | access_id => $access_id, 31 | access_key => $access_key, 32 | host => $host, 33 | ua => $ua 34 | }; 35 | return bless $self, $class; 36 | } 37 | 38 | sub sign { 39 | my $self = shift; 40 | my $req = shift; 41 | my $canonicalized_resource = shift; 42 | 43 | my $verb = $req->method; 44 | my $md5 = ""; 45 | if ($req->content) { 46 | $md5 = md5_base64($req->content) . "=="; 47 | $req->header("Content-Md5" => $md5); 48 | } 49 | my $type = $req->header("Content-Type"); 50 | $type = "" unless (defined($type)); 51 | my $date = $req->header("Date"); 52 | unless (defined($date)) { 53 | $date = time2str(time); 54 | $req->header("Date" => $date); 55 | } 56 | 57 | my $hmac = Digest::HMAC_SHA1->new($self->{access_key}); 58 | $hmac->add("$verb\n$md5\n$type\n$date\n"); 59 | 60 | my %canonicalized_oss_headers; 61 | foreach my $key ( $req->headers->header_field_names ) { 62 | if ($key =~ /^x-oss-/i) { 63 | $canonicalized_oss_headers{lc $key} = $req->header($key); 64 | } 65 | } 66 | foreach my $key ( sort map { lc } keys %canonicalized_oss_headers ) { 67 | $hmac->add("$key:$canonicalized_oss_headers{$key}\n"); 68 | } 69 | $hmac->add($canonicalized_resource); 70 | 71 | $req->header("Authorization" => sprintf("OSS %s:%s", 72 | $self->{access_id}, 73 | encode_base64($hmac->digest))); 74 | return $req; 75 | } 76 | 77 | sub request { 78 | my $self = shift; 79 | my $req = shift; 80 | 81 | if ($debug) { 82 | print STDERR ">>> ", (caller(1))[3], "\n"; 83 | print STDERR $req->as_string, "\n"; 84 | } 85 | my $res = $self->{ua}->request($req); 86 | if ($debug) { 87 | print STDERR $res->as_string, "\n"; 88 | print STDERR "<<<\n"; 89 | } 90 | return $res; 91 | } 92 | 93 | # return 0|1 and a hash of { bucket_name => (creation_date, endpoint, internal_endpoint) } 94 | sub ListBucket { 95 | my $self = shift; 96 | 97 | my $req = $self->sign( 98 | HTTP::Request->new(GET => "http://$self->{host}/"), "/"); 99 | my $res = $self->request($req); 100 | if ($res->is_success) { 101 | my %buckets; 102 | my $xml = XMLin($res->decoded_content, ForceArray => ['Bucket']); 103 | foreach ( @{ $xml->{Buckets}->{Bucket} } ) { 104 | $buckets{$_->{Name}}{'creation_date'} = str2time($_->{CreationDate}); 105 | $buckets{$_->{Name}}{'endpoint'} = $_->{ExtranetEndpoint}; 106 | $buckets{$_->{Name}}{'internal_endpoint'} = $_->{IntranetEndpoint}; 107 | } 108 | return (1, %buckets); 109 | } 110 | return 0; 111 | } 112 | sub GetService { 113 | return ListBucket(@_); 114 | } 115 | 116 | # return string "private", "public-read" or "public-read-write" or 0 on error 117 | sub GetBucketACL { 118 | my $self = shift; 119 | my $bucket = shift; 120 | 121 | my $req = $self->sign( 122 | HTTP::Request->new(GET => "http://$bucket.$self->{host}/?acl"), 123 | "/$bucket/?acl"); 124 | my $res = $self->request($req); 125 | if ($res->is_success) { 126 | my $xml = XMLin($res->decoded_content); 127 | return $xml->{AccessControlList}->{Grant}; 128 | } 129 | return 0; 130 | } 131 | 132 | # return array of (0|1, files and dirs) 133 | sub GetBucket { 134 | my $self = shift; 135 | my $bucket = shift; 136 | my %param = ( @_ ); 137 | 138 | my @ret; 139 | my $param_str = "?"; 140 | foreach my $key ( sort keys %param ) { 141 | $param_str .= "$key=$param{$key}&"; 142 | } 143 | chop $param_str; 144 | my $req = $self->sign( 145 | HTTP::Request->new(GET => "http://$bucket.$self->{host}/$param_str"), 146 | "/$bucket/"); 147 | my $res = $self->request($req); 148 | if ($res->is_success) { 149 | my $xml = XMLin($res->decoded_content, 150 | ForceArray => ['Contents', 'CommonPrefixes']); 151 | # files 152 | foreach ( @{$xml->{Contents}} ) { 153 | my $f = $_->{Key}; 154 | if (exists($param{"prefix"})) { 155 | $f = substr($f, length($param{"prefix"})); 156 | } 157 | push @ret, $f if ($f); # prefix itself should be removed 158 | } 159 | # dirs 160 | foreach ( @{$xml->{CommonPrefixes}} ) { 161 | my $d = $_->{Prefix}; 162 | chop $d; # remove trailing "/" 163 | if (exists($param{"prefix"})) { 164 | $d = substr($d, length($param{"prefix"})); 165 | } 166 | push @ret, $d if ($d); # prefix itself should be removed 167 | } 168 | return (1, @ret); 169 | } 170 | return 0; 171 | } 172 | 173 | # return if success 174 | sub PutBucket { 175 | my $self = shift; 176 | my $bucket = shift; 177 | my $acl = shift; 178 | if (defined($acl)) { 179 | return 0 unless (grep {$_ eq $acl} ("public-read-write", "public-read", "private")); 180 | } 181 | 182 | my $req = HTTP::Request->new(PUT => "http://$bucket.$self->{host}/"); 183 | $req->header("x-oss-acl", $acl) if defined($acl); 184 | $req = $self->sign($req, "/$bucket/"); 185 | my $res = $self->request($req); 186 | return $res->is_success; 187 | } 188 | 189 | # return if success 190 | sub PutBucketACL { 191 | my $self = shift; 192 | my $bucket = shift; 193 | my $acl = shift; 194 | return 0 unless (grep {$_ eq $acl} ("public-read-write", "public-read", "private")); 195 | 196 | my $req = HTTP::Request->new(PUT => "http://$bucket.$self->{host}/?acl"); 197 | 198 | $req->header("x-oss-acl", $acl); 199 | $req = $self->sign($req, "/$bucket/?acl"); 200 | my $res = $self->request($req); 201 | return $res->is_success; 202 | } 203 | 204 | # return if success 205 | sub DeleteBucket { 206 | my $self = shift; 207 | my $bucket = shift; 208 | 209 | my $req = $self->sign( 210 | HTTP::Request->new(DELETE => "http://$bucket.$self->{host}/"), 211 | "/$bucket/"); 212 | my $res = $self->request($req); 213 | return $res->is_success; 214 | } 215 | 216 | # return if success 217 | sub PutObject { 218 | my $self = shift; 219 | my $bucket = shift; 220 | my $object = shift; 221 | my $content = shift; 222 | my $type = shift; 223 | 224 | my $req = HTTP::Request->new(PUT => "http://$bucket.$self->{host}/$object"); 225 | $req->header("Content-Type" => $type) if ($type); 226 | $req->header("Content-Length" => length($content)); 227 | $req->content($content); 228 | $req = $self->sign($req, "/$bucket/$object"); 229 | my $res = $self->request($req); 230 | return $res->is_success; 231 | } 232 | 233 | # return if success 234 | sub DeleteObject { 235 | my $self = shift; 236 | my $bucket = shift; 237 | my $object = shift; 238 | 239 | my $req = $self->sign( 240 | HTTP::Request->new(DELETE => "http://$bucket.$self->{host}/$object"), 241 | "/$bucket/$object"); 242 | my $res = $self->request($req); 243 | return $res->is_success; 244 | } 245 | 246 | # return array of (0|1, last-modified, content-size, content-type) 247 | sub HeadObject { 248 | my $self = shift; 249 | my $bucket = shift; 250 | my $object = shift; 251 | 252 | my $req = $self->sign( 253 | HTTP::Request->new(HEAD => "http://$bucket.$self->{host}/$object"), 254 | "/$bucket/$object"); 255 | my $res = $self->request($req); 256 | if ($res->is_success) { 257 | return (1, 258 | $res->header("Last-Modified"), 259 | $res->header("Content-Length"), 260 | $res->header("Content-Type")); 261 | } 262 | return 0; 263 | } 264 | 265 | # return string of content or undef on error 266 | sub GetObject { 267 | my $self = shift; 268 | my $bucket = shift; 269 | my $object = shift; 270 | my $begin = shift; 271 | my $end = shift; 272 | 273 | my $req = $self->sign( 274 | HTTP::Request->new(GET => "http://$bucket.$self->{host}/$object"), 275 | "/$bucket/$object"); 276 | if (defined($begin) and defined($end)) { 277 | $req->header(Range => "$begin-$end"); 278 | } 279 | my $res = $self->request($req); 280 | if ($res->is_success) { 281 | return $res->decoded_content; 282 | } 283 | return undef; 284 | } 285 | 286 | # return if success 287 | sub CopyObject { 288 | my $self = shift; 289 | my $src_bucket = shift; 290 | my $src_object = shift; 291 | my $dest_bucket = shift; 292 | my $dest_object = shift; 293 | 294 | my $req = HTTP::Request->new(PUT => "http://$dest_bucket.$self->{host}/$dest_object"); 295 | $req->header("x-oss-copy-source", "/$src_bucket/$src_object"); 296 | $req = $self->sign($req, "/$dest_bucket/$dest_object"); 297 | my $res = $self->request($req); 298 | return $res->is_success; 299 | } 300 | 301 | # TODO: DeleteMultipleObjects 302 | # TODO: MultipartUpload 303 | 304 | 1; 305 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### **_IMPORTANT NOTE_** 2 | **This repository is to be considered _DEPRECATED_ and all efforts are going towards developing the next version of the platform in [aliyun/ossfs](https://github.com/aliyun/ossfs).** 3 | 4 | # ossfs 5 | 6 | ossfs is a fuse client to mount your [aliyun oss](http://oss.aliyun.com/) bucket under linux. 7 | 8 | ## installation 9 | 10 | ### archlinux 11 | 12 | run `makepkg` in distro/archlinux, and `pacmann -U ossfs-git-*.pkg.tar.xfz` to install 13 | 14 | ### ubuntu 15 | 16 | make sure at least dependencies are installed: 17 | - libhttp-date-perl 18 | - libfuse-perl 19 | - libxml-simple-perl 20 | - libdigest-hmac-perl 21 | 22 | ## developer 23 | 24 | ossfs is written in perl, please make sure these perl packages could be cound in your enviromnent: 25 | 26 | - Fuse 27 | - LWP::UserAgent 28 | - HTTP::Date 29 | - Digest::MD5 30 | - Digest::HMAC_SHA1 31 | - XML::Simple 32 | 33 | the OSS.pm, delivered with ossfs, is a simple perl oss sdk module, which could be useful to perl coders. test.pl is a unittest script for this module. 34 | 35 | ## road map 36 | 37 | - review code of rmdir and see if recursively remove object is needed 38 | - optimize memory usage when writing big files 39 | - make OSS.pm a CPAN module 40 | - OSS.pm support multiple part upload 41 | - basic read/write 42 | 43 | ## reference 44 | 45 | - [perl Fuse module](http://search.cpan.org/~dpavlin/Fuse-0.14/Fuse.pm) 46 | - [fuse documentation](http://fuse.sourceforge.net/doxygen/structfuse__operations.html#dc6dc71274f185de72217e38d62142c4) 47 | - [post on aliyun official forum](http://bbs.aliyun.com/read.php?tid=132627) 48 | 49 | ## author 50 | 51 | Li Ruibo 52 | - http://twitter.com/lymanrb 53 | - http://weibo.com/lymanrb 54 | 55 | ## licence 56 | 57 | licensed under the [BSD License](http://www.linfo.org/bsdlicense.html) 58 | -------------------------------------------------------------------------------- /distro/archlinux/PKGBUILD: -------------------------------------------------------------------------------- 1 | # Contributor: Li Ruibo 2 | 3 | pkgname=ossfs-git 4 | pkgver=20151121 5 | pkgrel=1 6 | pkgdesc="a fuse client to mount ossfs bucket to local file system" 7 | arch=('any') 8 | url="https://github.com/alibaba/ossfs" 9 | license=('BSD') 10 | depends=('perl-fuse' 'perl-http-date' 'perl-libwww' 'perl-xml-simple' 'perl-digest-hmac') 11 | makedepends=('git') 12 | source=("$pkgname"::'git+https://github.com/alibaba/ossfs.git') 13 | md5sums=('SKIP') 14 | 15 | pkgver() { 16 | cd "$srcdir/$pkgname" 17 | 18 | # Use the tag of the last commit 19 | # git describe --long | sed -E 's/([^-]*-g)/r\1/;s/-/./g' 20 | 21 | # use last commit 22 | git log -1 | head -n 1 | cut -d ' ' -f 2 | head -c 8 23 | } 24 | 25 | build() { 26 | cd "$srcdir/$pkgname" 27 | } 28 | 29 | package() { 30 | cd "$srcdir/$pkgname" 31 | install -Dm644 OSS.pm "$pkgdir/usr/share/perl5/vendor_perl/OSS.pm" 32 | install -Dm755 ossfs "$pkgdir/usr/bin/ossfs" 33 | } 34 | -------------------------------------------------------------------------------- /ossfs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use POSIX; 7 | use Errno qw(:POSIX); 8 | use Fcntl ':mode'; 9 | use HTTP::Date; 10 | use Fuse; 11 | use Getopt::Long; 12 | 13 | push @INC, "."; 14 | use OSS; 15 | 16 | my $oss; 17 | my $default_host = "oss.aliyuncs.com"; 18 | 19 | my $access_id = undef; 20 | my $access_key = undef; 21 | my $bucket = undef; 22 | my $host = undef; 23 | 24 | my %buckets = (); 25 | 26 | my $uid = $<; 27 | my ($gid) = split / /, $(; 28 | 29 | my %acl = ("private" => 0770, 30 | "public-read" => 0775, 31 | "public-read-write" => 0777); 32 | my $create = "private"; # default create acl 33 | 34 | my $debug = 0; 35 | my $bucket_mode; 36 | my $bucket_time; 37 | 38 | my %wbuf; 39 | 40 | # file to object, remove leading "/" of input string 41 | sub f2o { 42 | my $str = shift; 43 | $str =~ s#^/+##; 44 | return $str; 45 | } 46 | 47 | sub oss_dummy { 0; } 48 | 49 | sub oss_getattr { 50 | my $file = shift @_; 51 | if ($file eq "/") { 52 | return (0, 0, S_IFDIR | $bucket_mode, 1, $uid, $gid, 0, 0, 53 | $bucket_time, 54 | $bucket_time, 55 | $bucket_time, 0, 0); 56 | } 57 | # try if is file 58 | my ($ret, $time, $size) = $oss->HeadObject($bucket, f2o($file)); 59 | if ($ret) { 60 | return (0, # dev 61 | 0, # inode 62 | S_IFREG | $bucket_mode, # mode 63 | 1, # nlink 64 | $uid, 65 | $gid, 66 | 0, # rdev 67 | $size, # size 68 | str2time($time), # atime 69 | str2time($time), # mtime 70 | str2time($time), # ctime 71 | 0, # blksize 72 | $size / 512); # blocks 73 | } 74 | # try if is dir 75 | ($ret, $time, $size) = $oss->HeadObject($bucket, f2o("$file/")); 76 | if ($ret) { 77 | return (0, # dev 78 | 0, # inode 79 | S_IFDIR | $bucket_mode, # mode 80 | 1, # nlink 81 | $uid, 82 | $gid, 83 | 0, # rdev 84 | $size, # size 85 | str2time($time), # atime 86 | str2time($time), # mtime 87 | str2time($time), # ctime 88 | 0, # blksize 89 | $size / 512); # blocks 90 | } 91 | # ok, file seems not exist 92 | return -ENOENT(); 93 | } 94 | 95 | sub oss_mknod { 96 | my ($file, $mode, $dev) = @_; 97 | # TODO: file already exist? 98 | my $ret = $oss->PutObject($bucket, f2o($file), ""); 99 | return 0 if ($ret); 100 | return -ENOENT(); 101 | } 102 | 103 | sub oss_mkdir { 104 | my ($file, $mode) = @_; 105 | # TODO: dir already exist? 106 | my $ret = $oss->PutObject($bucket, f2o("$file/"), ""); 107 | return 0 if ($ret); 108 | return -ENOENT(); 109 | } 110 | 111 | sub oss_getdir { 112 | my $file = shift @_; 113 | my %param = ( "delimiter" => "/" ); 114 | $param{"prefix"} = f2o("$file/"); 115 | my ($ret, @list) = $oss->GetBucket($bucket, %param); 116 | return (@list, 0) if ($ret); 117 | return -ENOENT(); # TODO 118 | } 119 | 120 | sub oss_unlink { 121 | my $file = shift @_; 122 | my $ret = $oss->DeleteObject($bucket, f2o($file)); 123 | return 0 if ($ret); 124 | # TODO: no such file 125 | # TODO: is a dir? 126 | return -ENOENT(); 127 | } 128 | 129 | sub oss_rmdir { 130 | my $file = shift @_; 131 | # TODO: delete a whole tree 132 | my $ret = $oss->DeleteObject($bucket, f2o("$file/")); 133 | return 0 if ($ret); 134 | return -ENOENT(); 135 | } 136 | 137 | sub oss_rename { 138 | my ($old, $new) = @_; 139 | my $ret = $oss->CopyObject($bucket, f2o($old), $bucket, f2o($new)); 140 | return -ENOENT() unless ($ret); 141 | return unlink($old); 142 | } 143 | 144 | sub oss_open { 145 | my ($file, $flags, $fi_ref) = @_; 146 | # store file size for reading 147 | my ($ret, $time, $size) = $oss->HeadObject($bucket, f2o($file)); 148 | if ($ret) { 149 | # file already exists 150 | # store file size for calc end in oss_read 151 | $$fi_ref{"filesize"} = $size; 152 | } else { 153 | # file not exist, create an empty one if necessary 154 | # TODO: refer to $flags here 155 | $ret = $oss->PutObject($bucket, f2o($file), ""); 156 | $$fi_ref{"filesize"} = 0; 157 | return -EIO() unless ($ret); 158 | } 159 | return (0, $fi_ref); 160 | } 161 | 162 | sub oss_read { 163 | my ($file, $size, $offset, $fi_ref) = @_; 164 | my $end = $offset + $size - 1; 165 | $end = $$fi_ref{"filesize"} - 1 166 | if ($fi_ref and exists($$fi_ref{"filesize"}) 167 | and $end >= $$fi_ref{"filesize"}); 168 | my $content = $oss->GetObject($bucket, f2o($file), $offset, $end); 169 | return $content if (defined($content)); 170 | return -ENOENT(); 171 | } 172 | 173 | sub oss_write { 174 | my ($file, $buffer, $offset, $fi_ref) = @_; 175 | # create a write buffer if necessary 176 | $wbuf{$file} = "" unless (exists $wbuf{$file}); 177 | my $len = length $wbuf{$file}; 178 | # to prevent hole in file 179 | if ($offset > $len) { 180 | $wbuf{$file} .= '\0' x ($offset - $len); 181 | } 182 | $len = length $buffer; 183 | substr($wbuf{$file}, $offset, $len, $buffer); 184 | return $len; 185 | } 186 | 187 | sub oss_release { 188 | my ($file, $flags, $fi_ref) = @_; 189 | my $ret = 0; 190 | if (exists $wbuf{$file}) { 191 | $ret = $oss->PutObject($bucket, f2o($file), $wbuf{$file}); 192 | delete $wbuf{$file}; 193 | } 194 | # undef %$fi_ref; 195 | return $ret ? 0 : -EIO(); 196 | } 197 | 198 | sub oss_create { 199 | my ($file, $flags) = @_; 200 | my $fi_ref = {}; 201 | return oss_open($file, $flags, $fi_ref); 202 | } 203 | 204 | sub oss_ftruncate { 205 | my ($file, $offset, $fi_ref) = @_; 206 | if (exists $wbuf{$file}) { 207 | my $len = length $wbuf{$file}; 208 | if ($offset > $len) { 209 | $wbuf{$file} .= '\0' x ($offset - $len); 210 | } elsif ($offset < $len) { 211 | $wbuf{$file} = substr($wbuf{$file}, 0, $offset); 212 | } 213 | } 214 | return 0; 215 | } 216 | 217 | sub print_usage { 218 | print < \$unmount, 244 | "b|bucket=s" => \$bucket, 245 | "c|create:s" => \$create, 246 | "host=s" => \$host, 247 | "h|help" => sub { &print_usage(); exit 0; }, 248 | "d|debug" => \$debug, 249 | "o|mountopts=s" => \$mountopts, 250 | "i|id=s" => \$access_id, 251 | "k|key=s" => \$access_key, 252 | "f|file=s" => \$file) 253 | or exit 1; 254 | 255 | $mountopts = "" unless $mountopts; 256 | my $mountpoint = shift @ARGV; 257 | unless ($mountpoint) { 258 | print STDERR "mountpoint not specified.\n"; 259 | &print_usage(); 260 | exit 1; 261 | } 262 | 263 | if ($unmount) { 264 | exec("fusermount", "-u", "-z", $mountpoint); 265 | } 266 | 267 | if ($file) { 268 | open FILE, "<", $file or die "$!: $file\n"; 269 | my $line1 = ; 270 | my $line2 = ; 271 | my $line3 = ; 272 | my $line4 = ; 273 | chomp($access_id = $line1) if (not $access_id and $line1); 274 | chomp($access_key = $line2) if (not $access_key and $line2); 275 | chomp($bucket = $line3) if (not $bucket and $line3); 276 | chomp($host = $line4) if (not $host and $line4); 277 | close FILE; 278 | } 279 | 280 | die "empty access id\n" unless ($access_id); 281 | die "empty access key\n" unless ($access_key); 282 | die "empry bucket\n" unless ($bucket); 283 | 284 | # special host 'internal' 285 | my $internal = 0; 286 | if ($host and $host eq 'internal') { 287 | $internal = 1; 288 | $host = undef; 289 | } 290 | $default_host = "oss-internal.aliyuncs.com" if ($internal); 291 | 292 | # get all buckets 293 | my $listed = 0; 294 | $oss = OSS->new($access_id, $access_key, $default_host); 295 | ($listed, %buckets) = $oss->ListBucket(); 296 | 297 | # create bucket if necessary 298 | if ($create and $listed and !exists($buckets{$bucket})) { 299 | die "failed to create bucket $bucket\n" 300 | unless ($oss->PutBucket($create)); 301 | # refresh buckets 302 | ($listed, %buckets) = $oss->ListBucket(); 303 | } 304 | 305 | # use server specified endpoint for specified bucket 306 | if ($listed and not $host) { 307 | $host = $internal ? $buckets{$bucket}{'internal_endpoint'} : $buckets{$bucket}{'endpoint'}; 308 | } else { 309 | $host = $default_host; 310 | } 311 | 312 | my $masked_key = substr($access_key, 0, 3) . '*' x (length($access_key) - 6) . substr($access_key, -3); 313 | print STDERR <new($access_id, $access_key, $host); 322 | 323 | # for current bucket 324 | $bucket_time = $buckets{$bucket}; 325 | $bucket_mode = $oss->GetBucketACL($bucket); 326 | die "invalid bucket mode: $bucket_mode\n" unless exists $acl{$bucket_mode}; 327 | $bucket_mode = $acl{$bucket_mode}; 328 | 329 | # unmount first 330 | if ($debug) { 331 | system("fusermount -u -z $mountpoint"); 332 | } 333 | 334 | Fuse::main( mountpoint => $mountpoint, 335 | mountopts => $mountopts, 336 | debug => $debug, 337 | 338 | getattr => "main::oss_getattr", 339 | readlink => "main::oss_dummy", 340 | getdir => "main::oss_getdir", 341 | mknod => "main::oss_mknod", 342 | mkdir => "main::oss_mkdir", 343 | unlink => "main::oss_unlink", 344 | rmdir => "main::oss_rmdir", 345 | symlink => "main::oss_dummy", 346 | rename => "main::oss_rename", 347 | link => "main::oss_dummy", 348 | chmod => "main::oss_dummy", 349 | chown => "main::oss_dummy", 350 | truncate => "main::oss_dummy", 351 | # utime => "main:oss_dummy", # deprecated 352 | open => "main::oss_open", 353 | read => "main::oss_read", 354 | write => "main::oss_write", 355 | statfs => "main::oss_dummy", 356 | flush => "main::oss_dummy", 357 | release => "main::oss_release", 358 | fsync => "main::oss_dummy", 359 | setxattr => "main::oss_dummy", 360 | getxattr => "main::oss_dummy", 361 | listxattr => "main::oss_dummy", 362 | removexattr => "main::oss_dummy", 363 | opendir => "main::oss_dummy", 364 | # readdir => "main::oss_readdir", 365 | releasedir => "main::oss_dummy", 366 | fsyncdir => "main::oss_dummy", 367 | 368 | # init => "main::oss_dummy", 369 | # destory => "main::oss_dummy", 370 | 371 | access => "main::oss_dummy", 372 | create => "main::oss_create", 373 | ftruncate => "main::ftruncate", 374 | fgetattr => "main::oss_getattr", 375 | # lock => "main::oss_dummy", 376 | utimens => "main::oss_dummy", 377 | # bmap => "main::oss_dummy", 378 | ); 379 | -------------------------------------------------------------------------------- /test.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | push @INC, "."; 4 | 5 | use strict; 6 | use warnings; 7 | 8 | use OSS; 9 | 10 | my $conf_file = shift @ARGV; 11 | unless (defined($conf_file)) { 12 | print "usage: $0 conf_file\n"; 13 | print " conf_file should contain access id & key, each in a line.\n"; 14 | exit 0; 15 | } 16 | my $access_id = undef; 17 | my $access_key = undef; 18 | open FILE, "<", $conf_file or die "$!: $conf_file\n"; 19 | chomp($access_id = ); 20 | chomp($access_key = ); 21 | close FILE; 22 | 23 | my $case_id = 0; 24 | sub case { 25 | my $name = shift; 26 | $case_id++; 27 | print "\n$case_id: $name\n\n"; 28 | } 29 | 30 | my $fail = 0; 31 | sub assert_eq { 32 | my $left = shift; 33 | my $right = shift; 34 | if ($left == $right) { 35 | print "OK, $right as expected.\n"; 36 | } else { 37 | print "FAIL, $left expected but got $right\n"; 38 | $fail++; 39 | } 40 | } 41 | sub assert_str_eq { 42 | my $left = shift; 43 | my $right = shift; 44 | if ($left eq $right) { 45 | print "OK, [$right] as expected.\n"; 46 | } else { 47 | print "FAIL, [$left] expected but got [$right]\n"; 48 | $fail++; 49 | } 50 | } 51 | 52 | my $oss = OSS->new($access_id, $access_key); 53 | 54 | my $bucket = "lyman-ossfs-unittest"; 55 | 56 | case("PutBucket"); { 57 | # clear first 58 | $oss->DeleteBucket($bucket); 59 | assert_eq(1, $oss->PutBucket($bucket)); 60 | } 61 | 62 | case("ListBucket"); 63 | { 64 | my ($ret, %buckets) = $oss->ListBucket; 65 | assert_eq(1, $ret); 66 | foreach my $bucket ( keys %buckets ) { 67 | print "$bucket => $buckets{$bucket}{'creation_date'}, $buckets{$bucket}{'endpoint'}, $buckets{$bucket}{'internal_endpoint'}\n"; 68 | } 69 | assert_eq(1, exists($buckets{$bucket})); 70 | } 71 | 72 | case("GetBucketACL"); 73 | { 74 | assert_str_eq("private", $oss->GetBucketACL($bucket)); 75 | } 76 | 77 | case("PutBucketACL as modifier"); 78 | { 79 | assert_eq(1, $oss->PutBucketACL($bucket, "public-read")); 80 | assert_str_eq("public-read", $oss->GetBucketACL($bucket)); 81 | } 82 | 83 | case("PutObject"); 84 | { 85 | assert_eq(1, $oss->PutObject($bucket, "foo", 86 | "hello world!", "text/plain")); 87 | } 88 | 89 | case("GetObject"); 90 | { 91 | assert_str_eq("hello world!", $oss->GetObject($bucket, "foo")); 92 | } 93 | 94 | case("PutObject empty file"); 95 | { 96 | assert_eq(1, $oss->PutObject($bucket, "empty", "")); 97 | } 98 | 99 | case("HeadObject"); 100 | { 101 | print "foo\n"; 102 | my ($ret, $ctime, $size, $type) = $oss->HeadObject($bucket, "foo"); 103 | assert_eq(1, $ret); 104 | assert_eq(12, $size); 105 | assert_str_eq("text/plain", $type); 106 | 107 | print "empty\n"; 108 | ($ret, $ctime, $size, $type) = $oss->HeadObject($bucket, "empty"); 109 | assert_eq(1, $ret); 110 | assert_eq(0, $size); 111 | print "$ctime\n"; 112 | print "$type\n" if (defined($type)); 113 | } 114 | 115 | case("CopyObject"); 116 | { 117 | assert_eq(1, $oss->CopyObject($bucket, "foo", $bucket, "bar/copy")); 118 | 119 | assert_str_eq("hello world!", $oss->GetObject($bucket, "bar/copy")); 120 | 121 | my ($ret, $ctime, $size, $type) = $oss->HeadObject($bucket, "bar/copy"); 122 | assert_eq(1, $ret); 123 | assert_eq(12, $size); 124 | assert_str_eq("text/plain", $type); 125 | } 126 | 127 | case("GetBucket"); 128 | { 129 | my ($ret, @files) = $oss->GetBucket($bucket); 130 | assert_eq(1, $ret); 131 | assert_str_eq("foo", grep {$_ eq "foo"} @files); 132 | assert_str_eq("bar/copy", grep {$_ eq "bar/copy"} @files); 133 | assert_str_eq("empty", grep {$_ eq "empty"} @files); 134 | } 135 | 136 | case("GetBucket w/ prefix and delimiter"); 137 | { 138 | my ($ret, @files) = $oss->GetBucket($bucket, 139 | prefix => "bar/", 140 | delimiter => "/"); 141 | assert_eq(1, $ret); 142 | assert_eq(0, $#files); 143 | assert_str_eq("copy", $files[0]); 144 | } 145 | 146 | case("DeleteObject"); 147 | { 148 | assert_eq(1, $oss->DeleteObject($bucket, "foo")); 149 | 150 | my ($ret, @files) = $oss->GetBucket($bucket); 151 | assert_eq(0, scalar grep {$_ eq "foo"} @files); 152 | assert_eq(1, $#files); 153 | assert_eq(1, $oss->DeleteObject($bucket, "empty")); 154 | assert_eq(1, $oss->DeleteObject($bucket, "bar/copy")); 155 | } 156 | 157 | case("DeleteBucket"); 158 | { 159 | assert_eq(1, $oss->DeleteBucket($bucket)); 160 | my ($ret, %buckets) = $oss->ListBucket; 161 | assert_eq(1, $ret); 162 | assert_eq(0, scalar grep {$_ eq $bucket} keys %buckets); 163 | } 164 | 165 | case("PutBucket as creator with acl"); 166 | { 167 | my $acl = "public-read"; 168 | my $bucket = "lyman-ossfs-unittest-1"; 169 | 170 | # create 171 | $oss->DeleteBucket($bucket); 172 | assert_eq(1, $oss->PutBucket($bucket, $acl)); 173 | # assert acl 174 | assert_str_eq($acl, $oss->GetBucketACL($bucket)); 175 | # delete 176 | $oss->DeleteBucket($bucket); 177 | } 178 | 179 | if ($fail == 0) { 180 | print "\ndone.\n"; 181 | exit 0; 182 | } else { 183 | print "\n$fail failed.\n"; 184 | exit 1; 185 | } 186 | --------------------------------------------------------------------------------